Skip to content

Commit

Permalink
Merge pull request #13 from linkdotnet/feature/pagination
Browse files Browse the repository at this point in the history
Feature/pagination
  • Loading branch information
linkdotnet committed Jul 14, 2021
2 parents fc9ef0c + 4101283 commit 65b9246
Show file tree
Hide file tree
Showing 14 changed files with 237 additions and 26 deletions.
4 changes: 2 additions & 2 deletions LinkDotNet.Blog.IntegrationTests/SqlDatabaseTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ protected SqlDatabaseTestBase()
BlogPostRepository = new BlogPostRepository(new BlogPostContext(options));
}

protected BlogPostRepository BlogPostRepository { get; private set; }
protected BlogPostRepository BlogPostRepository { get; }

protected BlogPostContext DbContext { get; private set; }
protected BlogPostContext DbContext { get; }

public Task InitializeAsync()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Threading.Tasks;
using System.Linq;
using System.Threading.Tasks;
using Bunit;
using FluentAssertions;
using LinkDotNet.Blog.TestUtilities;
Expand All @@ -23,6 +24,7 @@ public async Task ShouldOnlyShowPublishedPosts()
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.Services.AddScoped<IRepository>(_ => BlogPostRepository);
var cut = ctx.RenderComponent<DraftBlogPosts>();
cut.WaitForState(() => cut.FindAll(".blog-card").Any());

var blogPosts = cut.FindComponents<ShortBlogPost>();

Expand Down
67 changes: 66 additions & 1 deletion LinkDotNet.Blog.IntegrationTests/Web/Pages/IndexTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Threading.Tasks;
using System.Linq;
using System.Threading.Tasks;
using Bunit;
using FluentAssertions;
using LinkDotNet.Blog.TestUtilities;
Expand Down Expand Up @@ -26,6 +27,7 @@ public async Task ShouldShowAllBlogPostsWithLatestOneFirst()
ctx.Services.AddScoped<IRepository>(_ => BlogPostRepository);
ctx.Services.AddScoped(_ => CreateSampleAppConfiguration());
var cut = ctx.RenderComponent<Index>();
cut.WaitForState(() => cut.FindAll(".blog-card").Any());

var blogPosts = cut.FindComponents<ShortBlogPost>();

Expand All @@ -46,13 +48,67 @@ public async Task ShouldOnlyShowPublishedPosts()
ctx.Services.AddScoped<IRepository>(_ => BlogPostRepository);
ctx.Services.AddScoped(_ => CreateSampleAppConfiguration());
var cut = ctx.RenderComponent<Index>();
cut.WaitForState(() => cut.FindAll(".blog-card").Any());

var blogPosts = cut.FindComponents<ShortBlogPost>();

blogPosts.Should().HaveCount(1);
blogPosts[0].Find(".description h1").InnerHtml.Should().Be("Published");
}

[Fact]
public async Task ShouldOnlyLoadTenEntities()
{
await CreatePublishedBlogPosts(11);
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.Services.AddScoped<IRepository>(_ => BlogPostRepository);
ctx.Services.AddScoped(_ => CreateSampleAppConfiguration());
var cut = ctx.RenderComponent<Index>();
cut.WaitForState(() => cut.FindAll(".blog-card").Any());

var blogPosts = cut.FindComponents<ShortBlogPost>();

blogPosts.Count.Should().Be(10);
}

[Fact]
public async Task ShouldLoadNextBatchOnClick()
{
await CreatePublishedBlogPosts(11);
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.Services.AddScoped<IRepository>(_ => BlogPostRepository);
ctx.Services.AddScoped(_ => CreateSampleAppConfiguration());
var cut = ctx.RenderComponent<Index>();

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

cut.WaitForState(() => cut.FindAll(".blog-card").Count == 1);
var blogPosts = cut.FindComponents<ShortBlogPost>();
blogPosts.Count.Should().Be(1);
}

[Fact]
public async Task ShouldLoadPreviousBatchOnClick()
{
await CreatePublishedBlogPosts(11);
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.Services.AddScoped<IRepository>(_ => BlogPostRepository);
ctx.Services.AddScoped(_ => CreateSampleAppConfiguration());
var cut = ctx.RenderComponent<Index>();
cut.WaitForState(() => cut.FindAll(".blog-card").Any());
cut.FindComponent<BlogPostNavigation>().Find("li:last-child a").Click();
cut.WaitForState(() => cut.FindAll(".blog-card").Count == 1);

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

cut.WaitForState(() => cut.FindAll(".blog-card").Count > 1);
var blogPosts = cut.FindComponents<ShortBlogPost>();
blogPosts.Count.Should().Be(10);
}

private static AppConfiguration CreateSampleAppConfiguration()
{
return new()
Expand All @@ -66,5 +122,14 @@ private static AppConfiguration CreateSampleAppConfiguration()
},
};
}

private async Task CreatePublishedBlogPosts(int amount)
{
for (var i = 0; i < amount; i++)
{
var blogPost = new BlogPostBuilder().IsPublished().Build();
await BlogPostRepository.StoreAsync(blogPost);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Bunit;
using FluentAssertions;
Expand All @@ -21,6 +22,7 @@ public async Task ShouldOnlyDisplayTagsGivenByParameter()
await AddBlogPostWithTagAsync("Tag 2");
ctx.Services.AddScoped<IRepository>(_ => BlogPostRepository);
var cut = ctx.RenderComponent<SearchByTag>(p => p.Add(s => s.Tag, "Tag 1"));
cut.WaitForState(() => cut.FindAll(".blog-card").Any());

var tags = cut.FindAll(".blog-card");

Expand All @@ -34,6 +36,7 @@ public async Task ShouldHandleSpecialCharacters()
await AddBlogPostWithTagAsync("C#");
ctx.Services.AddScoped<IRepository>(_ => BlogPostRepository);
var cut = ctx.RenderComponent<SearchByTag>(p => p.Add(s => s.Tag, Uri.EscapeDataString("C#")));
cut.WaitForState(() => cut.FindAll(".blog-card").Any());

var tags = cut.FindAll(".blog-card");

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Linq;
using AngleSharp.Dom;
using Bunit;
using FluentAssertions;
using LinkDotNet.Blog.TestUtilities;
Expand Down
70 changes: 70 additions & 0 deletions LinkDotNet.Blog.UnitTests/Web/Shared/BlogPostNavigationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using Bunit;
using FluentAssertions;
using LinkDotNet.Blog.Web.Shared;
using LinkDotNet.Domain;
using Moq;
using X.PagedList;
using Xunit;

namespace LinkDotNet.Blog.UnitTests.Web.Shared
{
public class BlogPostNavigationTests : TestContext
{
[Fact]
public void ShouldFireEventWhenGoingToNextPage()
{
var actualNewPage = 0;
var page = CreatePagedList(2, 3);
var cut = RenderComponent<BlogPostNavigation>(p => p.Add(param => param.CurrentPage, page.Object)
.Add(param => param.OnPageChanged, newPage => actualNewPage = newPage));

cut.Find("li:last-child a").Click();

actualNewPage.Should().Be(3);
}

[Fact]
public void ShouldFireEventWhenGoingToPreviousPage()
{
var actualNewPage = 0;
var page = CreatePagedList(2, 3);
var cut = RenderComponent<BlogPostNavigation>(p => p.Add(param => param.CurrentPage, page.Object)
.Add(param => param.OnPageChanged, newPage => actualNewPage = newPage));

cut.Find("li:first-child a").Click();

actualNewPage.Should().Be(1);
}

[Fact]
public void ShouldNotFireNextWhenOnLastPage()
{
var page = CreatePagedList(2, 2);
var cut = RenderComponent<BlogPostNavigation>(p =>
p.Add(param => param.CurrentPage, page.Object));

cut.Find("li:last-child").ClassList.Should().Contain("disabled");
}

[Fact]
public void ShouldNotFireNextWhenOnFirstPage()
{
var page = CreatePagedList(1, 2);
var cut = RenderComponent<BlogPostNavigation>(p =>
p.Add(param => param.CurrentPage, page.Object));

cut.Find("li:first-child").ClassList.Should().Contain("disabled");
}

private static Mock<IPagedList<BlogPost>> CreatePagedList(int currentPage, int pageCount)
{
var page = new Mock<IPagedList<BlogPost>>();
page.Setup(p => p.PageNumber).Returns(currentPage);
page.Setup(p => p.PageCount).Returns(pageCount);
page.Setup(p => p.IsFirstPage).Returns(currentPage == 1);
page.Setup(p => p.IsLastPage).Returns(currentPage == pageCount);

return page;
}
}
}
17 changes: 13 additions & 4 deletions LinkDotNet.Blog.Web/Pages/Index.razor
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
@using LinkDotNet.Domain
@using LinkDotNet.Infrastructure.Persistence
@using Markdig
@using X.PagedList
@inject IRepository _repository
@inject AppConfiguration _appConfiguration
@inject NavigationManager _navigationManager
Expand All @@ -13,17 +14,19 @@
<IntroductionCard Introduction="_appConfiguration.Introduction"></IntroductionCard>

<div class="content px-4">
@for (var i = 0; i < _blogPosts.Count; i++)
@for (var i = 0; i < _currentPage.Count; i++)
{
<ShortBlogPost BlogPost="_blogPosts[i]" UseAlternativeStyle="@(i % 2 != 0)"></ShortBlogPost>
<ShortBlogPost BlogPost="_currentPage[i]" UseAlternativeStyle="@(i % 2 != 0)"></ShortBlogPost>
}
</div>
<BlogPostNavigation CurrentPage="@_currentPage" OnPageChanged="@GetPage"></BlogPostNavigation>
</section>
@code {
IList<BlogPost> _blogPosts = new List<BlogPost>();
IPagedList<BlogPost> _currentPage = new PagedList<BlogPost>(Array.Empty<BlogPost>(), 1, 1);

protected override async Task OnInitializedAsync()
{
_blogPosts = (await _repository.GetAllAsync(p => p.IsPublished, b => b.UpdatedDate)).ToList();
_currentPage = await _repository.GetAllAsync(p => p.IsPublished, b => b.UpdatedDate, pageSize: 10);
}

private string GetAbsolutePreviewImageUrl()
Expand All @@ -42,4 +45,10 @@
{
return Uri.TryCreate(url, UriKind.Absolute, out _);
}

private async Task GetPage(int newPage)
{
_currentPage = await _repository.GetAllAsync(p => p.IsPublished, b => b.UpdatedDate, pageSize: 10, page:
newPage);
}
}
5 changes: 4 additions & 1 deletion LinkDotNet.Blog.Web/Pages/SearchByTag.razor
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
protected override async Task OnInitializedAsync()
{
Tag = Uri.UnescapeDataString(Tag);
_blogPosts = (await _repository.GetAllAsync(b => b.Tags.Any(t => t.Content == Tag), b => b.UpdatedDate)).ToList();
_blogPosts = (await _repository.GetAllAsync(
b => b.Tags.Any(t => t.Content == Tag),
b => b.UpdatedDate))
.ToList();
}
}
37 changes: 37 additions & 0 deletions LinkDotNet.Blog.Web/Shared/BlogPostNavigation.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
@using LinkDotNet.Domain
@using X.PagedList
<nav aria-label="Page navigation example">
<ul class="pagination justify-content-center">
<li class="page-item @(!CurrentPage.IsFirstPage ? string.Empty : "disabled")">
<a class="page-link" href="#" tabindex="-1" @onclick="PreviousPage">Previous</a>
</li>
<li class="page-item @(!CurrentPage.IsLastPage ? string.Empty : "disabled")">
<a class="page-link"
@onclick="NextPage"
href="#">Next</a>
</li>
</ul>
</nav>

@code {
[Parameter]
public IPagedList<BlogPost> CurrentPage { get; set; }

[Parameter]
public EventCallback<int> OnPageChanged { get; set; }

private async Task PageHasChanged(int newPage)
{
await OnPageChanged.InvokeAsync(newPage);
}

private async Task PreviousPage()
{
await PageHasChanged(CurrentPage.PageNumber - 1);
}

private async Task NextPage()
{
await PageHasChanged(CurrentPage.PageNumber + 1);
}
}
1 change: 1 addition & 0 deletions LinkDotNet.Infrastructure/LinkDotNet.Infrastructure.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="X.PagedList" Version="8.1.0" />
</ItemGroup>

<ItemGroup>
Expand Down
10 changes: 8 additions & 2 deletions LinkDotNet.Infrastructure/Persistence/IRepository.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading.Tasks;
using LinkDotNet.Domain;
using X.PagedList;

namespace LinkDotNet.Infrastructure.Persistence
{
public interface IRepository
{
Task<BlogPost> GetByIdAsync(string blogPostId);

Task<IEnumerable<BlogPost>> GetAllAsync(Expression<Func<BlogPost, bool>> filter = null, Expression<Func<BlogPost, object>> orderBy = null, bool descending = true);
Task<IPagedList<BlogPost>> GetAllAsync(
Expression<Func<BlogPost, bool>> filter = null,
Expression<Func<BlogPost,
object>> orderBy = null,
bool descending = true,
int page = 1,
int pageSize = 5);

Task StoreAsync(BlogPost blogPost);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Linq.Expressions;
using System.Threading.Tasks;
using LinkDotNet.Domain;
using X.PagedList;

namespace LinkDotNet.Infrastructure.Persistence.InMemory
{
Expand All @@ -17,7 +18,12 @@ public Task<BlogPost> GetByIdAsync(string blogPostId)
return Task.FromResult(blogPost);
}

public Task<IEnumerable<BlogPost>> GetAllAsync(Expression<Func<BlogPost, bool>> filter = null, Expression<Func<BlogPost, object>> orderBy = null, bool descending = true)
public Task<IPagedList<BlogPost>> GetAllAsync(
Expression<Func<BlogPost, bool>> filter = null,
Expression<Func<BlogPost, object>> orderBy = null,
bool descending = true,
int page = 1,
int pageSize = 5)
{
var result = blogPosts.AsEnumerable();
if (filter != null)
Expand All @@ -29,13 +35,13 @@ public Task<IEnumerable<BlogPost>> GetAllAsync(Expression<Func<BlogPost, bool>>
{
if (descending)
{
return Task.FromResult(result.OrderByDescending(orderBy.Compile()).AsEnumerable());
return Task.FromResult(result.OrderByDescending(orderBy.Compile()).ToPagedList(page, pageSize));
}

return Task.FromResult(result.OrderBy(orderBy.Compile()).AsEnumerable());
return Task.FromResult(result.OrderBy(orderBy.Compile()).ToPagedList(page, pageSize));
}

return Task.FromResult(blogPosts.AsEnumerable());
return Task.FromResult(blogPosts.ToPagedList(page, pageSize));
}

public Task StoreAsync(BlogPost blogPost)
Expand Down
Loading

0 comments on commit 65b9246

Please sign in to comment.