Skip to content

Feature/give kudos #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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