Skip to content

Commit faf2476

Browse files
authored
Merge pull request #34 from linkdotnet/feature/navigation-page
Page navigation on main site
2 parents f5e57b3 + 38f37fd commit faf2476

File tree

10 files changed

+137
-33
lines changed

10 files changed

+137
-33
lines changed

.github/workflows/dotnet.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323
- name: Build
2424
run: dotnet build --no-restore
2525
- name: Test
26-
run: dotnet test --collect:"XPlat Code Coverage" --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=opencover
26+
run: dotnet test --collect:"XPlat Code Coverage" --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=opencover --blame-hang --blame-hang-timeout 2m
2727
- name: Codecov
2828
uses: codecov/codecov-action@v2
2929
with:

LinkDotNet.Blog.IntegrationTests/Web/Pages/Admin/Dashboard/VisitCountPerPageTests.cs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ public async Task ShouldShowCounts()
2222
await Repository.StoreAsync(blogPost);
2323
using var ctx = new TestContext();
2424
ctx.Services.AddScoped<IRepository<BlogPost>>(_ => Repository);
25-
var visits = new List<KeyValuePair<string, int>>();
26-
visits.Add(new KeyValuePair<string, int>($"blogPost/{blogPost.Id}", 5));
25+
var visits = new List<KeyValuePair<string, int>> { new($"blogPost/{blogPost.Id}", 5) };
2726
var pageVisitCounts = visits.OrderByDescending(s => s.Value);
2827

2928
var cut = ctx.RenderComponent<VisitCountPerPage>(p => p.Add(
@@ -39,5 +38,33 @@ public async Task ShouldShowCounts()
3938
elements[1].InnerHtml.Should().Be("5");
4039
elements[2].InnerHtml.Should().Be("2");
4140
}
41+
42+
[Fact]
43+
public void ShouldIgnoreNullForBlogPostVisits()
44+
{
45+
using var ctx = new TestContext();
46+
ctx.Services.AddScoped<IRepository<BlogPost>>(_ => Repository);
47+
48+
var cut = ctx.RenderComponent<VisitCountPerPage>(p => p.Add(
49+
s => s.PageVisitCount, null));
50+
51+
var elements = cut.FindAll("td").ToList();
52+
elements.Should().BeEmpty();
53+
}
54+
55+
[Fact]
56+
public void ShouldIgnoreNotBlogPosts()
57+
{
58+
using var ctx = new TestContext();
59+
ctx.Services.AddScoped<IRepository<BlogPost>>(_ => Repository);
60+
var visits = new List<KeyValuePair<string, int>> { new("notablogpost", 5) };
61+
var pageVisitCounts = visits.OrderByDescending(s => s.Value);
62+
63+
var cut = ctx.RenderComponent<VisitCountPerPage>(p => p.Add(
64+
s => s.PageVisitCount, pageVisitCounts));
65+
66+
var elements = cut.FindAll("td").ToList();
67+
elements.Should().BeEmpty();
68+
}
4269
}
4370
}

LinkDotNet.Blog.IntegrationTests/Web/Pages/IndexTests.cs

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using LinkDotNet.Blog.Web;
99
using LinkDotNet.Blog.Web.Shared;
1010
using LinkDotNet.Blog.Web.Shared.Services;
11+
using Microsoft.AspNetCore.Components;
1112
using Microsoft.Extensions.DependencyInjection;
1213
using Moq;
1314
using Toolbelt.Blazor.HeadElement;
@@ -74,7 +75,7 @@ public async Task ShouldOnlyLoadTenEntities()
7475
}
7576

7677
[Fact]
77-
public async Task ShouldLoadNextBatchOnClick()
78+
public async Task ShouldGoToNextPageOnNextClick()
7879
{
7980
await CreatePublishedBlogPosts(11);
8081
using var ctx = new TestContext();
@@ -84,28 +85,42 @@ public async Task ShouldLoadNextBatchOnClick()
8485

8586
cut.FindComponent<BlogPostNavigation>().Find("li:last-child a").Click();
8687

87-
cut.WaitForState(() => cut.FindAll(".blog-card").Count == 1);
88-
var blogPosts = cut.FindComponents<ShortBlogPost>();
89-
blogPosts.Count.Should().Be(1);
88+
var navigationManager = ctx.Services.GetService<NavigationManager>();
89+
cut.WaitForState(() => navigationManager.Uri.Contains("/2"));
90+
navigationManager.Uri.Should().Contain("/2");
9091
}
9192

9293
[Fact]
93-
public async Task ShouldLoadPreviousBatchOnClick()
94+
public async Task ShouldGoToPreviousPageOnPreviousClick()
9495
{
9596
await CreatePublishedBlogPosts(11);
9697
using var ctx = new TestContext();
9798
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
9899
RegisterComponents(ctx);
99-
var cut = ctx.RenderComponent<Index>();
100-
cut.WaitForState(() => cut.FindAll(".blog-card").Any());
101-
cut.FindComponent<BlogPostNavigation>().Find("li:last-child a").Click();
102-
cut.WaitForState(() => cut.FindAll(".blog-card").Count == 1);
100+
var cut = ctx.RenderComponent<Index>(
101+
p => p.Add(s => s.Page, 2));
103102

104103
cut.FindComponent<BlogPostNavigation>().Find("li:first-child a").Click();
105104

106-
cut.WaitForState(() => cut.FindAll(".blog-card").Count > 1);
105+
var navigationManager = ctx.Services.GetService<NavigationManager>();
106+
cut.WaitForState(() => navigationManager.Uri.Contains("/1"));
107+
navigationManager.Uri.Should().Contain("/1");
108+
}
109+
110+
[Fact]
111+
public async Task ShouldLoadOnlyItemsInPage()
112+
{
113+
await CreatePublishedBlogPosts(11);
114+
using var ctx = new TestContext();
115+
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
116+
RegisterComponents(ctx);
117+
118+
var cut = ctx.RenderComponent<Index>(
119+
p => p.Add(s => s.Page, 2));
120+
121+
cut.WaitForState(() => cut.FindAll(".blog-card").Any());
107122
var blogPosts = cut.FindComponents<ShortBlogPost>();
108-
blogPosts.Count.Should().Be(10);
123+
blogPosts.Should().HaveCount(1);
109124
}
110125

111126
[Fact]

LinkDotNet.Blog.UnitTests/LinkDotNet.Blog.UnitTests.csproj

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,7 @@
4343
<ItemGroup>
4444
<AdditionalFiles Include="..\stylecop.json" Link="stylecop.json" />
4545
</ItemGroup>
46-
47-
<ItemGroup>
48-
<Compile Remove="Web\Shared\Services\LocalStorageServiceTests.cs" />
49-
</ItemGroup>
46+
5047
<PropertyGroup>
5148
<CodeAnalysisRuleSet>..\stylecop.analyzers.ruleset</CodeAnalysisRuleSet>
5249
</PropertyGroup>

LinkDotNet.Blog.UnitTests/Web/Shared/Services/UserRecordServiceTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public class UserRecordServiceTests : TestContext
2222
public UserRecordServiceTests()
2323
{
2424
repositoryMock = new Mock<IRepository<UserRecord>>();
25-
fakeNavigationManager = new FakeNavigationManager(this.Renderer);
25+
fakeNavigationManager = new FakeNavigationManager(Renderer);
2626
fakeAuthenticationStateProvider = new FakeAuthenticationStateProvider();
2727
localStorageService = new Mock<ILocalStorageService>();
2828
sut = new UserRecordService(

LinkDotNet.Blog.Web/Pages/Admin/UpdateBlogPostPage.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
}
1919
else
2020
{
21-
<p>Loading...</p>
21+
<Loading></Loading>
2222
}
2323
</div>
2424

LinkDotNet.Blog.Web/Pages/BlogPostPage.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<div class="page">
1414
@if (BlogPost == null)
1515
{
16-
<h3>Loading...</h3>
16+
<Loading></Loading>
1717
}
1818
else
1919
{

LinkDotNet.Blog.Web/Pages/Index.razor

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
@page "/"
2+
@page "/{page:int}"
23
@using Markdig
34
@using X.PagedList
45
@using LinkDotNet.Blog.Web.Shared.Services
@@ -27,21 +28,25 @@
2728
<ShortBlogPost BlogPost="context" UseAlternativeStyle="@(currentLine++ % 2 != 0)"></ShortBlogPost>
2829
</Virtualize>
2930
</div>
30-
<BlogPostNavigation CurrentPage="@currentPage" OnPageChanged="@GetPage"></BlogPostNavigation>
31+
<BlogPostNavigation CurrentPage="@currentPage" OnPageChanged="@SetPageNumber"></BlogPostNavigation>
3132
</section>
3233

3334
@code {
35+
[Parameter]
36+
public int? Page { get; set; }
37+
3438
private IPagedList<BlogPost> currentPage = new PagedList<BlogPost>(null, 1, 1);
3539
private int currentLine;
3640

3741
private string ImageUrl => appConfiguration.Introduction.BackgroundUrl.ToAbsoluteUrl(navigationManager.BaseUri);
3842

39-
protected override async Task OnInitializedAsync()
43+
protected override async Task OnParametersSetAsync()
4044
{
4145
currentPage = await blogPostRepository.GetAllAsync(
4246
p => p.IsPublished,
4347
b => b.UpdatedDate,
44-
pageSize: appConfiguration.BlogPostsPerPage);
48+
pageSize: appConfiguration.BlogPostsPerPage,
49+
page: Page ?? 1);
4550
}
4651

4752
protected override async Task OnAfterRenderAsync(bool firstRender)
@@ -52,12 +57,8 @@
5257
}
5358
}
5459

55-
private async Task GetPage(int newPage)
60+
private void SetPageNumber(int newPage)
5661
{
57-
currentPage = await blogPostRepository.GetAllAsync(
58-
p => p.IsPublished,
59-
b => b.UpdatedDate,
60-
pageSize: appConfiguration.BlogPostsPerPage,
61-
page: newPage);
62+
navigationManager.NavigateTo($"/{newPage}");
6263
}
6364
}

LinkDotNet.Blog.Web/Shared/Admin/Dashboard/VisitCountPerPage.razor

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,16 @@
4343

4444
foreach (var (blogPostUrl, clickCount) in PageVisitCount)
4545
{
46-
var blogPostId = blogPostUrl[(blogPostUrl.IndexOf('/') + 1)..];
46+
const string urlToBlogPost = "blogPost/";
47+
var possibleBlogPostId = blogPostUrl.IndexOf(urlToBlogPost, StringComparison.InvariantCultureIgnoreCase);
4748

48-
if (string.IsNullOrEmpty(blogPostId))
49+
if (possibleBlogPostId == -1)
4950
{
5051
continue;
5152
}
5253

53-
var blogPost = (await blogPostRepository.GetByIdAsync(blogPostId));
54+
var blogPostId = blogPostUrl[urlToBlogPost.Length..];
55+
var blogPost = await blogPostRepository.GetByIdAsync(blogPostId);
5456

5557
blogPostToCountList.Add(new KeyValuePair<BlogPost, int>(blogPost, clickCount));
5658
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<div>
2+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin: auto; background: rgb(255, 255, 255); display: block; shape-rendering: auto;" width="200px" height="200px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
3+
<g transform="rotate(0 50 50)">
4+
<rect x="44" y="13" rx="6" ry="6" width="12" height="12" fill="var(--button-primary-color)">
5+
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.9285714285714286s" repeatCount="indefinite"></animate>
6+
</rect>
7+
</g><g transform="rotate(25.714285714285715 50 50)">
8+
<rect x="44" y="13" rx="6" ry="6" width="12" height="12" fill="var(--button-primary-color)">
9+
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.8571428571428571s" repeatCount="indefinite"></animate>
10+
</rect>
11+
</g><g transform="rotate(51.42857142857143 50 50)">
12+
<rect x="44" y="13" rx="6" ry="6" width="12" height="12" fill="var(--button-primary-color)">
13+
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.7857142857142857s" repeatCount="indefinite"></animate>
14+
</rect>
15+
</g><g transform="rotate(77.14285714285714 50 50)">
16+
<rect x="44" y="13" rx="6" ry="6" width="12" height="12" fill="var(--button-primary-color)">
17+
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.7142857142857143s" repeatCount="indefinite"></animate>
18+
</rect>
19+
</g><g transform="rotate(102.85714285714286 50 50)">
20+
<rect x="44" y="13" rx="6" ry="6" width="12" height="12" fill="var(--button-primary-color)">
21+
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.6428571428571429s" repeatCount="indefinite"></animate>
22+
</rect>
23+
</g><g transform="rotate(128.57142857142858 50 50)">
24+
<rect x="44" y="13" rx="6" ry="6" width="12" height="12" fill="var(--button-primary-color)">
25+
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.5714285714285714s" repeatCount="indefinite"></animate>
26+
</rect>
27+
</g><g transform="rotate(154.28571428571428 50 50)">
28+
<rect x="44" y="13" rx="6" ry="6" width="12" height="12" fill="var(--button-primary-color)">
29+
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.5s" repeatCount="indefinite"></animate>
30+
</rect>
31+
</g><g transform="rotate(180 50 50)">
32+
<rect x="44" y="13" rx="6" ry="6" width="12" height="12" fill="var(--button-primary-color)">
33+
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.42857142857142855s" repeatCount="indefinite"></animate>
34+
</rect>
35+
</g><g transform="rotate(205.71428571428572 50 50)">
36+
<rect x="44" y="13" rx="6" ry="6" width="12" height="12" fill="var(--button-primary-color)">
37+
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.35714285714285715s" repeatCount="indefinite"></animate>
38+
</rect>
39+
</g><g transform="rotate(231.42857142857142 50 50)">
40+
<rect x="44" y="13" rx="6" ry="6" width="12" height="12" fill="var(--button-primary-color)">
41+
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.2857142857142857s" repeatCount="indefinite"></animate>
42+
</rect>
43+
</g><g transform="rotate(257.14285714285717 50 50)">
44+
<rect x="44" y="13" rx="6" ry="6" width="12" height="12" fill="var(--button-primary-color)">
45+
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.21428571428571427s" repeatCount="indefinite"></animate>
46+
</rect>
47+
</g><g transform="rotate(282.85714285714283 50 50)">
48+
<rect x="44" y="13" rx="6" ry="6" width="12" height="12" fill="var(--button-primary-color)">
49+
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.14285714285714285s" repeatCount="indefinite"></animate>
50+
</rect>
51+
</g><g transform="rotate(308.57142857142856 50 50)">
52+
<rect x="44" y="13" rx="6" ry="6" width="12" height="12" fill="var(--button-primary-color)">
53+
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.07142857142857142s" repeatCount="indefinite"></animate>
54+
</rect>
55+
</g><g transform="rotate(334.2857142857143 50 50)">
56+
<rect x="44" y="13" rx="6" ry="6" width="12" height="12" fill="var(--button-primary-color)">
57+
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="0s" repeatCount="indefinite"></animate>
58+
</rect>
59+
</g>
60+
</svg>
61+
<h1 class="text-center">Getting latest information</h1>
62+
</div>

0 commit comments

Comments
 (0)