Skip to content

Commit 662d3d5

Browse files
authored
Merge pull request #7 from linkdotnet/feature/draft-post
Feature/draft post
2 parents 6c93740 + 2967350 commit 662d3d5

File tree

15 files changed

+181
-11
lines changed

15 files changed

+181
-11
lines changed

LinkDotNet.Blog.IntegrationTests/Infrastructure/Persistence/Sql/SqlRepositoryTests.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public sealed class SqlRepositoryTests : SqlDatabaseTestBase
1313
[Fact]
1414
public async Task ShouldLoadBlogPost()
1515
{
16-
var blogPost = BlogPost.Create("Title", "Subtitle", "Content", "url", new[] { "Tag 1", "Tag 2" });
16+
var blogPost = BlogPost.Create("Title", "Subtitle", "Content", "url", true, new[] { "Tag 1", "Tag 2" });
1717
await DbContext.BlogPosts.AddAsync(blogPost);
1818
await DbContext.SaveChangesAsync();
1919

@@ -24,6 +24,7 @@ public async Task ShouldLoadBlogPost()
2424
blogPostFromRepo.ShortDescription.Should().Be("Subtitle");
2525
blogPostFromRepo.Content.Should().Be("Content");
2626
blogPostFromRepo.PreviewImageUrl.Should().Be("url");
27+
blogPostFromRepo.IsPublished.Should().BeTrue();
2728
blogPostFromRepo.Tags.Should().HaveCount(2);
2829
var tagContent = blogPostFromRepo.Tags.Select(t => t.Content).ToList();
2930
tagContent.Should().Contain(new[] { "Tag 1", "Tag 2" });
@@ -32,7 +33,7 @@ public async Task ShouldLoadBlogPost()
3233
[Fact]
3334
public async Task ShouldSaveBlogPost()
3435
{
35-
var blogPost = BlogPost.Create("Title", "Subtitle", "Content", "url", new[] { "Tag 1", "Tag 2" });
36+
var blogPost = BlogPost.Create("Title", "Subtitle", "Content", "url", true, new[] { "Tag 1", "Tag 2" });
3637

3738
await BlogPostRepository.StoreAsync(blogPost);
3839

@@ -41,6 +42,7 @@ public async Task ShouldSaveBlogPost()
4142
blogPostFromContext.Title.Should().Be("Title");
4243
blogPostFromContext.ShortDescription.Should().Be("Subtitle");
4344
blogPostFromContext.Content.Should().Be("Content");
45+
blogPostFromContext.IsPublished.Should().BeTrue();
4446
blogPostFromContext.PreviewImageUrl.Should().Be("url");
4547
blogPostFromContext.Tags.Should().HaveCount(2);
4648
var tagContent = blogPostFromContext.Tags.Select(t => t.Content).ToList();
@@ -50,7 +52,7 @@ public async Task ShouldSaveBlogPost()
5052
[Fact]
5153
public async Task ShouldGetAllBlogPosts()
5254
{
53-
var blogPost = BlogPost.Create("Title", "Subtitle", "Content", "url", new[] { "Tag 1", "Tag 2" });
55+
var blogPost = BlogPost.Create("Title", "Subtitle", "Content", "url", true, new[] { "Tag 1", "Tag 2" });
5456
await DbContext.BlogPosts.AddAsync(blogPost);
5557
await DbContext.SaveChangesAsync();
5658

@@ -63,6 +65,7 @@ public async Task ShouldGetAllBlogPosts()
6365
blogPostFromRepo.ShortDescription.Should().Be("Subtitle");
6466
blogPostFromRepo.Content.Should().Be("Content");
6567
blogPostFromRepo.PreviewImageUrl.Should().Be("url");
68+
blogPostFromRepo.IsPublished.Should().BeTrue();
6669
blogPostFromRepo.Tags.Should().HaveCount(2);
6770
var tagContent = blogPostFromRepo.Tags.Select(t => t.Content).ToList();
6871
tagContent.Should().Contain(new[] { "Tag 1", "Tag 2" });
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System.Threading.Tasks;
2+
using Bunit;
3+
using FluentAssertions;
4+
using LinkDotNet.Blog.TestUtilities;
5+
using LinkDotNet.Blog.Web.Pages.Admin;
6+
using LinkDotNet.Blog.Web.Shared;
7+
using LinkDotNet.Infrastructure.Persistence;
8+
using Microsoft.Extensions.DependencyInjection;
9+
using Xunit;
10+
11+
namespace LinkDotNet.Blog.IntegrationTests.Web.Pages.Admin
12+
{
13+
public class DraftBlogPostPageTests : SqlDatabaseTestBase
14+
{
15+
[Fact]
16+
public async Task ShouldOnlyShowPublishedPosts()
17+
{
18+
var publishedPost = new BlogPostBuilder().WithTitle("Published").IsPublished().Build();
19+
var unpublishedPost = new BlogPostBuilder().WithTitle("Not published").IsPublished(false).Build();
20+
await BlogPostRepository.StoreAsync(publishedPost);
21+
await BlogPostRepository.StoreAsync(unpublishedPost);
22+
using var ctx = new TestContext();
23+
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
24+
ctx.Services.AddScoped<IRepository>(_ => BlogPostRepository);
25+
var cut = ctx.RenderComponent<DraftBlogPosts>();
26+
27+
var blogPosts = cut.FindComponents<ShortBlogPost>();
28+
29+
blogPosts.Should().HaveCount(1);
30+
blogPosts[0].Find(".description h1").InnerHtml.Should().Be("Not published");
31+
}
32+
}
33+
}

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,25 @@ public async Task ShouldShowAllBlogPostsWithLatestOneFirst()
3434
blogPosts[1].Find(".description h1").InnerHtml.Should().Be("Old");
3535
}
3636

37+
[Fact]
38+
public async Task ShouldOnlyShowPublishedPosts()
39+
{
40+
var publishedPost = new BlogPostBuilder().WithTitle("Published").IsPublished().Build();
41+
var unpublishedPost = new BlogPostBuilder().WithTitle("Not published").IsPublished(false).Build();
42+
await BlogPostRepository.StoreAsync(publishedPost);
43+
await BlogPostRepository.StoreAsync(unpublishedPost);
44+
using var ctx = new TestContext();
45+
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
46+
ctx.Services.AddScoped<IRepository>(_ => BlogPostRepository);
47+
ctx.Services.AddScoped(_ => CreateSampleAppConfiguration());
48+
var cut = ctx.RenderComponent<Index>();
49+
50+
var blogPosts = cut.FindComponents<ShortBlogPost>();
51+
52+
blogPosts.Should().HaveCount(1);
53+
blogPosts[0].Find(".description h1").InnerHtml.Should().Be("Published");
54+
}
55+
3756
private static AppConfiguration CreateSampleAppConfiguration()
3857
{
3958
return new()

LinkDotNet.Blog.TestUtilities/BlogPostBuilder.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ public class BlogPostBuilder
88
private string shortDescription = "Some Text";
99
private string content = "Some Content";
1010
private string url = "localhost";
11+
private bool isPublished = true;
1112
private string[] tags;
1213

1314
public BlogPostBuilder WithTitle(string title)
@@ -40,9 +41,15 @@ public BlogPostBuilder WithTags(params string[] tags)
4041
return this;
4142
}
4243

44+
public BlogPostBuilder IsPublished(bool isPublished = true)
45+
{
46+
this.isPublished = isPublished;
47+
return this;
48+
}
49+
4350
public BlogPost Build()
4451
{
45-
return BlogPost.Create(title, shortDescription, content, url, tags);
52+
return BlogPost.Create(title, shortDescription, content, url, isPublished, tags);
4653
}
4754
}
4855
}

LinkDotNet.Blog.UnitTests/Domain/BlogPostTests.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public void ShouldUpdateBlogPost()
1313
{
1414
var blogPostToUpdate = new BlogPostBuilder().Build();
1515
blogPostToUpdate.Id = "random-id";
16-
var blogPost = BlogPost.Create("Title", "Desc", "Content", "Url");
16+
var blogPost = BlogPost.Create("Title", "Desc", "Content", "Url", true);
1717
blogPost.Id = "something else";
1818

1919
blogPostToUpdate.Update(blogPost);
@@ -22,13 +22,14 @@ public void ShouldUpdateBlogPost()
2222
blogPostToUpdate.ShortDescription.Should().Be("Desc");
2323
blogPostToUpdate.Content.Should().Be("Content");
2424
blogPostToUpdate.PreviewImageUrl.Should().Be("Url");
25+
blogPostToUpdate.IsPublished.Should().BeTrue();
2526
blogPostToUpdate.Tags.Should().BeNullOrEmpty();
2627
}
2728

2829
[Fact]
2930
public void ShouldTrimWhitespacesFromTags()
3031
{
31-
var blogPost = BlogPost.Create("Title", "Sub", "Content", "Ppeview", new[] { " Tag 1", " Tag 2 " });
32+
var blogPost = BlogPost.Create("Title", "Sub", "Content", "Preview", false, new[] { " Tag 1", " Tag 2 " });
3233

3334
blogPost.Tags.Select(t => t.Content).Should().Contain("Tag 1");
3435
blogPost.Tags.Select(t => t.Content).Should().Contain("Tag 2");
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using System.Collections.Generic;
2+
using FluentAssertions;
3+
using LinkDotNet.Blog.Web;
4+
using Microsoft.Extensions.Configuration;
5+
using Xunit;
6+
7+
namespace LinkDotNet.Blog.UnitTests.Web
8+
{
9+
public class AppConfigurationFactoryTests
10+
{
11+
[Fact]
12+
public void ShouldMapFromAppConfiguration()
13+
{
14+
var inMemorySettings = new Dictionary<string, string>
15+
{
16+
{ "BlogName", "UnitTest" },
17+
{ "GithubAccountUrl", "github" },
18+
{ "LinkedInAccountUrl", "linkedIn" },
19+
{ "ConnectionString", "cs" },
20+
{ "DatabaseName", "db" },
21+
{ "Introduction:BackgroundUrl", "someurl" },
22+
{ "Introduction:ProfilePictureUrl", "anotherurl" },
23+
{ "Introduction:Description", "desc" },
24+
};
25+
var configuration = new ConfigurationBuilder()
26+
.AddInMemoryCollection(inMemorySettings)
27+
.Build();
28+
29+
var appConfiguration = AppConfigurationFactory.Create(configuration);
30+
31+
appConfiguration.BlogName.Should().Be("UnitTest");
32+
appConfiguration.GithubAccountUrl.Should().Be("github");
33+
appConfiguration.HasGithubAccount.Should().BeTrue();
34+
appConfiguration.LinkedinAccountUrl.Should().Be("linkedIn");
35+
appConfiguration.HasLinkedinAccount.Should().BeTrue();
36+
appConfiguration.ConnectionString.Should().Be("cs");
37+
appConfiguration.DatabaseName.Should().Be("db");
38+
appConfiguration.Introduction.BackgroundUrl.Should().Be("someurl");
39+
appConfiguration.Introduction.ProfilePictureUrl.Should().Be("anotherurl");
40+
appConfiguration.Introduction.Description.Should().Be("desc");
41+
}
42+
43+
[Fact]
44+
public void ShouldSetGithubLinkedAccountAccordingToValueSet()
45+
{
46+
var inMemorySettings = new Dictionary<string, string>
47+
{
48+
{ "Introduction:BackgroundUrl", "someurl" },
49+
{ "Introduction:ProfilePictureUrl", "anotherurl" },
50+
{ "Introduction:Description", "desc" },
51+
};
52+
var configuration = new ConfigurationBuilder()
53+
.AddInMemoryCollection(inMemorySettings)
54+
.Build();
55+
56+
var appConfiguration = AppConfigurationFactory.Create(configuration);
57+
58+
appConfiguration.HasGithubAccount.Should().BeFalse();
59+
appConfiguration.HasLinkedinAccount.Should().BeFalse();
60+
}
61+
}
62+
}

LinkDotNet.Blog.UnitTests/Web/Shared/Admin/CreateNewBlogPostTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public void ShouldCreateNewBlogPostWhenValidDataGiven()
2222
cut.Find("#short").Change("My short Description");
2323
cut.Find("#content").Change("My content");
2424
cut.Find("#preview").Change("My preview url");
25+
cut.Find("#published").Change(false);
2526
cut.Find("#tags").Change("Tag1,Tag2,Tag3");
2627

2728
cut.Find("form").Submit();
@@ -32,6 +33,7 @@ public void ShouldCreateNewBlogPostWhenValidDataGiven()
3233
blogPost.ShortDescription.Should().Be("My short Description");
3334
blogPost.Content.Should().Be("My content");
3435
blogPost.PreviewImageUrl.Should().Be("My preview url");
36+
blogPost.IsPublished.Should().BeFalse();
3537
blogPost.Tags.Should().HaveCount(3);
3638
blogPost.Tags.Select(t => t.Content).Should().Contain(new[] { "Tag1", "Tag2", "Tag3" });
3739
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
@inject IRepository _repository
88
@inject IToastService _toastService
99

10-
<CreateNewBlogPost Title="Create new BlogPost" OnBlogPostCreated="@(blogPost => StoreBlogPostAsync(blogPost))" ></CreateNewBlogPost>
10+
<CreateNewBlogPost Title="Create new BlogPost" OnBlogPostCreated="@(StoreBlogPostAsync)" ></CreateNewBlogPost>
1111

1212
@code {
1313
private async Task StoreBlogPostAsync(BlogPost blogPost)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
@page "/draft"
2+
@using LinkDotNet.Infrastructure.Persistence
3+
@using LinkDotNet.Domain
4+
@attribute [Authorize]
5+
@inject IRepository _repository
6+
<h3>Draft Blog Posts</h3>
7+
8+
<div class="content px-4">
9+
@for (var i = 0; i < _blogPosts.Count; i++)
10+
{
11+
<ShortBlogPost BlogPost="_blogPosts[i]" UseAlternativeStyle="@(i % 2 != 0)"></ShortBlogPost>
12+
}
13+
</div>
14+
15+
@code {
16+
private IList<BlogPost> _blogPosts = new List<BlogPost>();
17+
18+
protected override async Task OnInitializedAsync()
19+
{
20+
_blogPosts = (await _repository.GetAllAsync(p => !p.IsPublished, b => b.UpdatedDate)).ToList();
21+
}
22+
23+
}

LinkDotNet.Blog.Web/Pages/Index.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
IList<BlogPost> _blogPosts = new List<BlogPost>();
2424
protected override async Task OnInitializedAsync()
2525
{
26-
_blogPosts = (await _repository.GetAllAsync(orderBy: b => b.UpdatedDate)).ToList();
26+
_blogPosts = (await _repository.GetAllAsync(p => p.IsPublished, b => b.UpdatedDate)).ToList();
2727
}
2828

2929
private string GetAbsolutePreviewImageUrl()

LinkDotNet.Blog.Web/Shared/AccessControl.razor

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<AuthorizeView>
22
<Authorized>
33
<a href="create">Create</a>
4+
<a href="draft">Draft Blog Posts</a>
45
<a href="logout">Log out</a>
56
</Authorized>
67
<NotAuthorized>

LinkDotNet.Blog.Web/Shared/Admin/CreateNewBlogPost.razor

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@
2525
<label for="preview">Preview-Url</label>
2626
<InputText class="form-control" id="preview" @bind-Value="_model.PreviewImageUrl"/>
2727
</div>
28+
<div class="form-check">
29+
<InputCheckbox class="form-check-input" id="published" @bind-Value="_model.IsPublished" />
30+
<label class="form-check-label" for="published">Publish</label><br/>
31+
<small id="published" class="form-text text-muted">If this blog post is only draft uncheck the box</small>
32+
</div>
2833
<div class="form-group">
2934
<label for="tags">Tags</label>
3035
<InputText class="form-control" id="tags" @bind-Value="_model.Tags"/>

LinkDotNet.Blog.Web/Shared/Admin/CreateNewModel.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ public class CreateNewModel
2121
[Required]
2222
public string PreviewImageUrl { get; set; }
2323

24+
[Required]
25+
public bool IsPublished { get; set; } = true;
26+
2427
public string Tags { get; set; }
2528

2629
public static CreateNewModel FromBlogPost(BlogPost blogPost)
@@ -32,6 +35,7 @@ public static CreateNewModel FromBlogPost(BlogPost blogPost)
3235
Tags = blogPost.Tags != null ? string.Join(",", blogPost.Tags.Select(t => t.Content)) : null,
3336
Title = blogPost.Title,
3437
ShortDescription = blogPost.ShortDescription,
38+
IsPublished = blogPost.IsPublished,
3539
PreviewImageUrl = blogPost.PreviewImageUrl,
3640
};
3741
}
@@ -40,7 +44,7 @@ public BlogPost ToBlogPost()
4044
{
4145
var tags = string.IsNullOrWhiteSpace(Tags) ? ArraySegment<string>.Empty : Tags.Split(",");
4246

43-
var blogPost = BlogPost.Create(Title, ShortDescription, Content, PreviewImageUrl, tags);
47+
var blogPost = BlogPost.Create(Title, ShortDescription, Content, PreviewImageUrl, IsPublished, tags);
4448
blogPost.Id = Id;
4549
return blogPost;
4650
}

LinkDotNet.Domain/BlogPost.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,15 @@ private BlogPost()
2424

2525
public virtual ICollection<Tag> Tags { get; private set; }
2626

27-
public static BlogPost Create(string title, string shortDescription, string content, string previewImageUrl, IEnumerable<string> tags = null)
27+
public bool IsPublished { get; set; }
28+
29+
public static BlogPost Create(
30+
string title,
31+
string shortDescription,
32+
string content,
33+
string previewImageUrl,
34+
bool isPublished,
35+
IEnumerable<string> tags = null)
2836
{
2937
var blogPost = new BlogPost
3038
{
@@ -33,6 +41,7 @@ public static BlogPost Create(string title, string shortDescription, string cont
3341
Content = content,
3442
UpdatedDate = DateTime.Now,
3543
PreviewImageUrl = previewImageUrl,
44+
IsPublished = isPublished,
3645
Tags = tags?.Select(t => new Tag { Content = t.Trim() }).ToList(),
3746
};
3847

@@ -46,6 +55,7 @@ public void Update(BlogPost from)
4655
Content = from.Content;
4756
UpdatedDate = from.UpdatedDate;
4857
PreviewImageUrl = from.PreviewImageUrl;
58+
IsPublished = from.IsPublished;
4959
Tags = from.Tags;
5060
}
5161
}

LinkDotNet.Infrastructure/Persistence/Sql/BlogPostRepository.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public async Task<BlogPost> GetByIdAsync(string blogPostId)
2424

2525
public async Task<IEnumerable<BlogPost>> GetAllAsync(Expression<Func<BlogPost, bool>> filter = null, Expression<Func<BlogPost, object>> orderBy = null, bool descending = true)
2626
{
27-
var blogPosts = blogPostContext.BlogPosts.Include(b => b.Tags).AsQueryable();
27+
var blogPosts = blogPostContext.BlogPosts.AsNoTracking().Include(b => b.Tags).AsQueryable();
2828

2929
if (filter != null)
3030
{

0 commit comments

Comments
 (0)