feat: add individual post page (#4)

* chore: add markdown lint to ignore no h1 heading in markdown files

* chore: update example settings

* chore: update example development env file

* fix: remove fixed 100% height on container

* feat: add individual post page

* chore: update README.md

* feat: add links to headings in post and add copy button to code blocks

* tests: add tests for get post async method

* tests: remove heading for test blogs

* fix: only add links to headings in markdown body

* tests: add tests for post page

* tests: add test to check for blog content

* docs: add jsdoc comments

* chore: fix incorrect env variable format in example

* fix: don't render anything if blog post is null

* fix: don't render anything until posts are fetched

* chore: add docker compose file for prod
This commit is contained in:
Stevan Freeborn
2024-04-11 00:58:22 -05:00
committed by GitHub
parent 75831e5f45
commit dd7e7e1783
19 changed files with 1774 additions and 13 deletions
+48
View File
@@ -0,0 +1,48 @@
namespace Blog.Tests.EndToEnd;
[TestFixture]
public class PostTests : BlogTest
{
private const string TestPostSlug = "test-blog";
private const string TestPostTitle = "Test Blog";
private const string TestPostDate = "January 1, 2021";
[Test]
public async Task Post_WhenNavigatedTo_ItShouldDisplayCorrectPageTitle()
{
await Page.GotoAsync($"/{TestPostSlug}");
var title = await Page.TitleAsync();
title.Should().Be($"{TestPostTitle} - journal");
}
[Test]
public async Task Post_WhenNavigatedTo_ItShouldPostTitle()
{
await Page.GotoAsync($"/{TestPostSlug}");
var heading = Page.GetByRole(AriaRole.Heading, new() { Name = TestPostTitle });
await Expect(heading).ToBeVisibleAsync();
}
[Test]
public async Task Post_WhenNavigatedTo_ItShouldDisplayPostDate()
{
await Page.GotoAsync($"/{TestPostSlug}");
var date = Page.GetByText(TestPostDate);
await Expect(date).ToBeVisibleAsync();
}
[Test]
public async Task Post_WhenNavigatedTo_ItShouldDisplayContent()
{
await Page.GotoAsync($"/{TestPostSlug}");
var content = Page.GetByRole(AriaRole.Article);
await Expect(content).ToBeVisibleAsync();
}
[Test]
public async Task Post_WhenNavigatedToAndPostDoesNotExist_ItShouldDisplay404()
{
await Page.GotoAsync("/non-existent-post");
Page.Url.Should().Contain("/Error/404");
}
}
@@ -1,5 +1,3 @@
# Test Blog
```json meta
{
"title": "Another Test Blog",
@@ -1,5 +1,3 @@
# Test Blog
```json meta
{
"title": "Test Blog",
@@ -420,4 +420,197 @@ class FilePostServiceTests
}
]);
}
[Test]
public async Task GetPostAsync_WhenCalledAndSlugDoesNotExist_ItShouldReturnNull()
{
_mockFileSystem
.Setup(x => x.Directory.Exists(It.IsAny<string>()))
.Returns(false);
var result = await _sut.GetPostAsync("slug");
result.Should().BeNull();
}
[Test]
public async Task GetPostAsync_WhenCalledAndPostExistsButContainsNoMetaData_ItShouldReturnNull()
{
_mockFileSystem
.Setup(x => x.Directory.Exists(It.IsAny<string>()))
.Returns(true);
_mockFileSystem
.Setup(x => x.Path.Combine(It.IsAny<string>(), It.IsAny<string>()))
.Returns("slug/INDEX.md");
_mockFileSystem
.Setup(x => x.File.Exists(It.IsAny<string>()))
.Returns(true);
_mockFileSystem
.Setup(x => x.File.ReadAllTextAsync(It.IsAny<string>(), default))
.ReturnsAsync("# Post Title");
var result = await _sut.GetPostAsync("slug");
result.Should().BeNull();
}
[Test]
public async Task GetPostAsync_WhenCalledAndPostContainsNoContent_ItShouldReturnNull()
{
_mockFileSystem
.Setup(x => x.Directory.Exists(It.IsAny<string>()))
.Returns(true);
_mockFileSystem
.Setup(x => x.Path.Combine(It.IsAny<string>(), It.IsAny<string>()))
.Returns("slug/INDEX.md");
_mockFileSystem
.Setup(x => x.File.Exists(It.IsAny<string>()))
.Returns(true);
_mockFileSystem
.Setup(x => x.File.ReadAllTextAsync(It.IsAny<string>(), default))
.ReturnsAsync(
"""
```json meta
{
"title": "Post Title",
"date": "2021-01-01"
}
```
"""
);
var result = await _sut.GetPostAsync("slug");
result.Should().BeNull();
}
[Test]
public async Task GetPostAsync_WhenCalledAndPostExistsButContainsInvalidMetaData_ItShouldReturnNull()
{
_mockFileSystem
.Setup(x => x.Directory.Exists(It.IsAny<string>()))
.Returns(true);
_mockFileSystem
.Setup(x => x.Path.Combine(It.IsAny<string>(), It.IsAny<string>()))
.Returns("slug/INDEX.md");
_mockFileSystem
.Setup(x => x.File.Exists(It.IsAny<string>()))
.Returns(true);
_mockFileSystem
.Setup(x => x.File.ReadAllTextAsync(It.IsAny<string>(), default))
.ReturnsAsync(
"""
# Post Title
```json meta
{
"title": "Post Title",
"isPublished": "true",
"date": "2021-01-01"
}
```
"""
);
var result = await _sut.GetPostAsync("slug");
result.Should().BeNull();
}
[Test]
public async Task GetPostAsync_WhenCalledAndPostExistsButIsNotPublished_ItShouldReturnNull()
{
_mockFileSystem
.Setup(x => x.Directory.Exists(It.IsAny<string>()))
.Returns(true);
_mockFileSystem
.Setup(x => x.Path.Combine(It.IsAny<string>(), It.IsAny<string>()))
.Returns("slug/INDEX.md");
_mockFileSystem
.Setup(x => x.File.Exists(It.IsAny<string>()))
.Returns(true);
_mockFileSystem
.Setup(x => x.File.ReadAllTextAsync(It.IsAny<string>(), default))
.ReturnsAsync(
"""
# Post Title
```json meta
{
"title": "Post Title",
"lead": "Post lead",
"isPublished": false,
"publishedAt": "2021-01-01"
}
```
Post content
"""
);
var result = await _sut.GetPostAsync("slug");
result.Should().BeNull();
}
[Test]
public async Task GetPostAsync_WhenCalledAndPostExists_ItShouldReturnPostWithContent()
{
_mockFileSystem
.Setup(x => x.Directory.Exists(It.IsAny<string>()))
.Returns(true);
_mockFileSystem
.Setup(x => x.Path.Combine(It.IsAny<string>(), It.IsAny<string>()))
.Returns("slug/INDEX.md");
_mockFileSystem
.Setup(x => x.File.Exists(It.IsAny<string>()))
.Returns(true);
_mockFileSystem
.Setup(x => x.File.ReadAllTextAsync(It.IsAny<string>(), default))
.ReturnsAsync(
"""
# Post Title
```json meta
{
"title": "Post Title",
"lead": "Post lead",
"isPublished": true,
"publishedAt": "2021-01-01",
"slug": "slug"
}
```
Post content
"""
);
var result = await _sut.GetPostAsync("slug");
result.Should().NotBeNull();
result.Should().BeEquivalentTo(new PostWithContent
{
Title = "Post Title",
Lead = "Post lead",
IsPublished = true,
PublishedAt = new DateTime(2021, 1, 1),
Slug = "slug",
Content = "<h1 id=\"post-title\">Post Title</h1>\n<p>Post content</p>\n",
});
}
}