Skip to content

Commit

Permalink
Merge pull request #9 from linkdotnet/feature/give-kudos
Browse files Browse the repository at this point in the history
Feature/give kudos
  • Loading branch information
linkdotnet authored Jul 8, 2021
2 parents 662d3d5 + 2823dd0 commit 4bf27a5
Show file tree
Hide file tree
Showing 9 changed files with 256 additions and 1 deletion.
67 changes: 67 additions & 0 deletions LinkDotNet.Blog.IntegrationTests/Web/Pages/BlogPostPageTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System.Threading.Tasks;
using Blazored.LocalStorage;
using Blazored.Toast.Services;
using Bunit;
using Bunit.TestDoubles;
using FluentAssertions;
using LinkDotNet.Blog.TestUtilities;
using LinkDotNet.Blog.Web.Pages;
using LinkDotNet.Blog.Web.Shared;
using LinkDotNet.Infrastructure.Persistence;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using Xunit;

namespace LinkDotNet.Blog.IntegrationTests.Web.Pages
{
public class BlogPostPageTests : SqlDatabaseTestBase
{
[Fact]
public async Task ShouldAddLikeOnEvent()
{
var publishedPost = new BlogPostBuilder().WithLikes(2).IsPublished().Build();
await BlogPostRepository.StoreAsync(publishedPost);
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.Services.AddScoped<IRepository>(_ => BlogPostRepository);
ctx.Services.AddScoped(_ => new Mock<ILocalStorageService>().Object);
ctx.Services.AddScoped(_ => new Mock<IToastService>().Object);
ctx.AddTestAuthorization().SetAuthorized("s");
var cut = ctx.RenderComponent<BlogPostPage>(
p => p.Add(b => b.BlogPostId, publishedPost.Id));
var likeComponent = cut.FindComponent<Like>();
likeComponent.SetParametersAndRender(c => c.Add(p => p.BlogPost, publishedPost));

likeComponent.Find("button").Click();

var fromDb = await DbContext.BlogPosts.AsNoTracking().SingleAsync(d => d.Id == publishedPost.Id);
fromDb.Likes.Should().Be(3);
}

[Fact]
public async Task ShouldSubtractLikeOnEvent()
{
var publishedPost = new BlogPostBuilder().WithLikes(2).IsPublished().Build();
await BlogPostRepository.StoreAsync(publishedPost);
using var ctx = new TestContext();
var localStorage = new Mock<ILocalStorageService>();
localStorage.Setup(l => l.ContainKeyAsync("hasLiked", default)).ReturnsAsync(true);
localStorage.Setup(l => l.GetItemAsync<bool>("hasLiked", default)).ReturnsAsync(true);
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.Services.AddScoped<IRepository>(_ => BlogPostRepository);
ctx.Services.AddScoped(_ => localStorage.Object);
ctx.Services.AddScoped(_ => new Mock<IToastService>().Object);
ctx.AddTestAuthorization().SetAuthorized("s");
var cut = ctx.RenderComponent<BlogPostPage>(
p => p.Add(b => b.BlogPostId, publishedPost.Id));
var likeComponent = cut.FindComponent<Like>();
likeComponent.SetParametersAndRender(c => c.Add(p => p.BlogPost, publishedPost));

likeComponent.Find("button").Click();

var fromDb = await DbContext.BlogPosts.AsNoTracking().SingleAsync(d => d.Id == publishedPost.Id);
fromDb.Likes.Should().Be(1);
}
}
}
11 changes: 10 additions & 1 deletion LinkDotNet.Blog.TestUtilities/BlogPostBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public class BlogPostBuilder
private string url = "localhost";
private bool isPublished = true;
private string[] tags;
private int likes;

public BlogPostBuilder WithTitle(string title)
{
Expand Down Expand Up @@ -47,9 +48,17 @@ public BlogPostBuilder IsPublished(bool isPublished = true)
return this;
}

public BlogPostBuilder WithLikes(int likes)
{
this.likes = likes;
return this;
}

public BlogPost Build()
{
return BlogPost.Create(title, shortDescription, content, url, isPublished, tags);
var blogPost = BlogPost.Create(title, shortDescription, content, url, isPublished, tags);
blogPost.Likes = likes;
return blogPost;
}
}
}
101 changes: 101 additions & 0 deletions LinkDotNet.Blog.UnitTests/Web/Shared/LikeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
using Blazored.LocalStorage;
using Bunit;
using FluentAssertions;
using LinkDotNet.Blog.TestUtilities;
using LinkDotNet.Blog.Web.Shared;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using Xunit;

namespace LinkDotNet.Blog.UnitTests.Web.Shared
{
public class LikeTests : TestContext
{
[Theory]
[InlineData(0, "0 Likes")]
[InlineData(1, "1 Like")]
[InlineData(2, "2 Likes")]
public void ShouldDisplayLikes(int likes, string expectedText)
{
Services.AddScoped(_ => new Mock<ILocalStorageService>().Object);
var blogPost = new BlogPostBuilder().WithLikes(likes).Build();
var cut = RenderComponent<Like>(
p => p.Add(l => l.BlogPost, blogPost));

var label = cut.Find("small").TextContent;

label.Should().Be(expectedText);
}

[Fact]
public void ShouldInvokeEventWhenButtonClicked()
{
Services.AddScoped(_ => new Mock<ILocalStorageService>().Object);
var blogPost = new BlogPostBuilder().Build();
var wasClicked = false;
var wasLike = false;
var cut = RenderComponent<Like>(
p => p.Add(l => l.BlogPost, blogPost)
.Add(l => l.OnBlogPostLiked, b =>
{
wasClicked = true;
wasLike = b;
}));

cut.Find("button").Click();

wasClicked.Should().BeTrue();
wasLike.Should().BeTrue();
}

[Fact]
public void ShouldSetLocalStorageVariableOnClick()
{
var localStorage = new Mock<ILocalStorageService>();
Services.AddScoped(_ => localStorage.Object);
var blogPost = new BlogPostBuilder().Build();
var cut = RenderComponent<Like>(
p => p.Add(l => l.BlogPost, blogPost));

cut.Find("button").Click();

localStorage.Verify(l => l.SetItemAsync("hasLiked", true, default), Times.Once);
}

[Fact]
public void ShouldCheckLocalStorageOnInit()
{
var localStorage = new Mock<ILocalStorageService>();
localStorage.Setup(l => l.ContainKeyAsync("hasLiked", default)).ReturnsAsync(true);
localStorage.Setup(l => l.GetItemAsync<bool>("hasLiked", default)).ReturnsAsync(true);
Services.AddScoped(_ => localStorage.Object);
var blogPost = new BlogPostBuilder().Build();
var wasLike = true;
var cut = RenderComponent<Like>(
p => p.Add(l => l.BlogPost, blogPost)
.Add(l => l.OnBlogPostLiked, b => wasLike = b));

cut.Find("button").Click();

wasLike.Should().BeFalse();
}

[Fact]
public void ShouldCheckStorageOnClickAgainAndDoNothingOnMismatch()
{
var localStorage = new Mock<ILocalStorageService>();
Services.AddScoped(_ => localStorage.Object);
var blogPost = new BlogPostBuilder().Build();
var wasClicked = false;
var cut = RenderComponent<Like>(
p => p.Add(l => l.BlogPost, blogPost)
.Add(l => l.OnBlogPostLiked, _ => wasClicked = true));
localStorage.Setup(l => l.ContainKeyAsync("hasLiked", default)).ReturnsAsync(true);
localStorage.Setup(l => l.GetItemAsync<bool>("hasLiked", default)).ReturnsAsync(true);

cut.Find("button").Click();

wasClicked.Should().BeFalse();
}
}
}
1 change: 1 addition & 0 deletions LinkDotNet.Blog.Web/LinkDotNet.Blog.Web.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Blazored.LocalStorage" Version="4.1.2" />
<PackageReference Include="Blazored.Toast" Version="3.1.2" />
<PackageReference Include="Markdig" Version="0.25.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="5.0.7" />
Expand Down
8 changes: 8 additions & 0 deletions LinkDotNet.Blog.Web/Pages/BlogPostPage.razor
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ else
@(RenderMarkupString(BlogPost.Content))
</div>
</div>
<Like BlogPost="@BlogPost" OnBlogPostLiked="@UpdateLikes"></Like>
</div>
</div>
}
Expand All @@ -55,4 +56,11 @@ else
StateHasChanged();
}
}

private async Task UpdateLikes(bool hasLiked)
{
BlogPost = await _repository.GetByIdAsync(BlogPostId);
BlogPost.Likes = hasLiked ? BlogPost.Likes + 1 : BlogPost.Likes - 1;
await _repository.StoreAsync(BlogPost);
}
}
57 changes: 57 additions & 0 deletions LinkDotNet.Blog.Web/Shared/Like.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
@using LinkDotNet.Domain
@using Blazored.LocalStorage
@using LinkDotNet.Infrastructure.Persistence
@inject ILocalStorageService _localStorage
<div class="like-container">
<small>@BlogPost.Likes @LikeText</small>
<button class="btn @BtnClass" @onclick="LikeBlogPost"><i class="far fa-thumbs-up"></i> @LikeTextButton</button>
</div>

@code {
[Parameter]
public BlogPost BlogPost { get; set; }

[Parameter]
public EventCallback<bool> OnBlogPostLiked { get; set; }

private bool HasLiked { get; set; }

private string BtnClass => HasLiked ? "btn-secondary" : "btn-primary";

private string LikeTextButton => HasLiked ? "Unlike" : "Like";

private string LikeText => BlogPost.Likes == 1 ? "Like" : "Likes";

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
HasLiked = await GetHasLiked();
StateHasChanged();
}
}

private async Task LikeBlogPost()
{
// Prevent multiple open sites to like / unlike multiple times
var hasLikedFromLocalStorage = await GetHasLiked();
if (HasLiked != hasLikedFromLocalStorage)
{
return;
}

HasLiked = !HasLiked;
await OnBlogPostLiked.InvokeAsync(HasLiked);
await _localStorage.SetItemAsync("hasLiked", HasLiked);
}

private async Task<bool> GetHasLiked()
{
if (await _localStorage.ContainKeyAsync("hasLiked"))
{
return await _localStorage.GetItemAsync<bool>("hasLiked");
}

return false;
}
}
8 changes: 8 additions & 0 deletions LinkDotNet.Blog.Web/Shared/Like.razor.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.like-container {
float: right;
margin-top: 20px;
}

.like-container button {
margin-left: 10px;
}
2 changes: 2 additions & 0 deletions LinkDotNet.Blog.Web/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Blazored.LocalStorage;
using Blazored.Toast;
using LinkDotNet.Blog.Web.Authentication.Auth0;
using LinkDotNet.Blog.Web.RegistrationExtensions;
Expand Down Expand Up @@ -39,6 +40,7 @@ public void ConfigureServices(IServiceCollection services)
services.UseAuth0Authentication(Configuration);

services.AddBlazoredToast();
services.AddBlazoredLocalStorage();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
Expand Down
2 changes: 2 additions & 0 deletions LinkDotNet.Domain/BlogPost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ private BlogPost()

public bool IsPublished { get; set; }

public int Likes { get; set; }

public static BlogPost Create(
string title,
string shortDescription,
Expand Down

0 comments on commit 4bf27a5

Please sign in to comment.