diff --git a/src/MyWebLog.Domain/DataTypes.fs b/src/MyWebLog.Domain/DataTypes.fs index 2ea9f55..65c2325 100644 --- a/src/MyWebLog.Domain/DataTypes.fs +++ b/src/MyWebLog.Domain/DataTypes.fs @@ -26,14 +26,13 @@ type Category = { } with /// An empty category - static member Empty = { - Id = CategoryId.Empty - WebLogId = WebLogId.Empty - Name = "" - Slug = "" - Description = None - ParentId = None - } + static member Empty = + { Id = CategoryId.Empty + WebLogId = WebLogId.Empty + Name = "" + Slug = "" + Description = None + ParentId = None } /// A comment on a post @@ -68,17 +67,16 @@ type Comment = { } with /// An empty comment - static member Empty = { - Id = CommentId.Empty - PostId = PostId.Empty - InReplyToId = None - Name = "" - Email = "" - Url = None - Status = Pending - PostedOn = Noda.epoch - Text = "" - } + static member Empty = + { Id = CommentId.Empty + PostId = PostId.Empty + InReplyToId = None + Name = "" + Email = "" + Url = None + Status = Pending + PostedOn = Noda.epoch + Text = "" } /// A page (text not associated with a date/time) @@ -125,21 +123,20 @@ type Page = { } with /// An empty page - static member Empty = { - Id = PageId.Empty - WebLogId = WebLogId.Empty - AuthorId = WebLogUserId.Empty - Title = "" - Permalink = Permalink.Empty - PublishedOn = Noda.epoch - UpdatedOn = Noda.epoch - IsInPageList = false - Template = None - Text = "" - Metadata = [] - PriorPermalinks = [] - Revisions = [] - } + static member Empty = + { Id = PageId.Empty + WebLogId = WebLogId.Empty + AuthorId = WebLogUserId.Empty + Title = "" + Permalink = Permalink.Empty + PublishedOn = Noda.epoch + UpdatedOn = Noda.epoch + IsInPageList = false + Template = None + Text = "" + Metadata = [] + PriorPermalinks = [] + Revisions = [] } /// A web log post @@ -195,24 +192,23 @@ type Post = { } with /// An empty post - static member Empty = { - Id = PostId.Empty - WebLogId = WebLogId.Empty - AuthorId = WebLogUserId.Empty - Status = Draft - Title = "" - Permalink = Permalink.Empty - PublishedOn = None - UpdatedOn = Noda.epoch - Text = "" - Template = None - CategoryIds = [] - Tags = [] - Episode = None - Metadata = [] - PriorPermalinks = [] - Revisions = [] - } + static member Empty = + { Id = PostId.Empty + WebLogId = WebLogId.Empty + AuthorId = WebLogUserId.Empty + Status = Draft + Title = "" + Permalink = Permalink.Empty + PublishedOn = None + UpdatedOn = Noda.epoch + Text = "" + Template = None + CategoryIds = [] + Tags = [] + Episode = None + Metadata = [] + PriorPermalinks = [] + Revisions = [] } /// A mapping between a tag and its URL value, used to translate restricted characters (ex. "#1" -> "number-1") @@ -232,12 +228,8 @@ type TagMap = { } with /// An empty tag mapping - static member Empty = { - Id = TagMapId.Empty - WebLogId = WebLogId.Empty - Tag = "" - UrlValue = "" - } + static member Empty = + { Id = TagMapId.Empty; WebLogId = WebLogId.Empty; Tag = ""; UrlValue = "" } /// A theme @@ -257,12 +249,8 @@ type Theme = { } with /// An empty theme - static member Empty = { - Id = ThemeId.Empty - Name = "" - Version = "" - Templates = [] - } + static member Empty = + { Id = ThemeId.Empty; Name = ""; Version = ""; Templates = [] } /// A theme asset (a file served as part of a theme, at /themes/[theme]/[asset-path]) @@ -279,11 +267,8 @@ type ThemeAsset = { } with /// An empty theme asset - static member Empty = { - Id = ThemeAssetId.Empty - UpdatedOn = Noda.epoch - Data = [||] - } + static member Empty = + { Id = ThemeAssetId.Empty; UpdatedOn = Noda.epoch; Data = [||] } /// An uploaded file @@ -306,13 +291,8 @@ type Upload = { } with /// An empty upload - static member Empty = { - Id = UploadId.Empty - WebLogId = WebLogId.Empty - Path = Permalink.Empty - UpdatedOn = Noda.epoch - Data = [||] - } + static member Empty = + { Id = UploadId.Empty; WebLogId = WebLogId.Empty; Path = Permalink.Empty; UpdatedOn = Noda.epoch; Data = [||] } open Newtonsoft.Json @@ -361,21 +341,20 @@ type WebLog = { } with /// An empty web log - static member Empty = { - Id = WebLogId.Empty - Name = "" - Slug = "" - Subtitle = None - DefaultPage = "" - PostsPerPage = 10 - ThemeId = ThemeId "default" - UrlBase = "" - TimeZone = "" - Rss = RssOptions.Empty - AutoHtmx = false - Uploads = Database - RedirectRules = [] - } + static member Empty = + { Id = WebLogId.Empty + Name = "" + Slug = "" + Subtitle = None + DefaultPage = "" + PostsPerPage = 10 + ThemeId = ThemeId "default" + UrlBase = "" + TimeZone = "" + Rss = RssOptions.Empty + AutoHtmx = false + Uploads = Database + RedirectRules = [] } /// Any extra path where this web log is hosted (blank if web log is hosted at the root of the domain) [] @@ -441,19 +420,18 @@ type WebLogUser = { } with /// An empty web log user - static member Empty = { - Id = WebLogUserId.Empty - WebLogId = WebLogId.Empty - Email = "" - FirstName = "" - LastName = "" - PreferredName = "" - PasswordHash = "" - Url = None - AccessLevel = Author - CreatedOn = Noda.epoch - LastSeenOn = None - } + static member Empty = + { Id = WebLogUserId.Empty + WebLogId = WebLogId.Empty + Email = "" + FirstName = "" + LastName = "" + PreferredName = "" + PasswordHash = "" + Url = None + AccessLevel = Author + CreatedOn = Noda.epoch + LastSeenOn = None } /// Get the user's displayed name [] diff --git a/src/MyWebLog.Domain/SupportTypes.fs b/src/MyWebLog.Domain/SupportTypes.fs index 1266fd7..53df258 100644 --- a/src/MyWebLog.Domain/SupportTypes.fs +++ b/src/MyWebLog.Domain/SupportTypes.fs @@ -256,27 +256,26 @@ type Episode = { } with /// An empty episode - static member Empty = { - Media = "" - Length = 0L - Duration = None - MediaType = None - ImageUrl = None - Subtitle = None - Explicit = None - Chapters = None - ChapterFile = None - ChapterType = None - ChapterWaypoints = None - TranscriptUrl = None - TranscriptType = None - TranscriptLang = None - TranscriptCaptions = None - SeasonNumber = None - SeasonDescription = None - EpisodeNumber = None - EpisodeDescription = None - } + static member Empty = + { Media = "" + Length = 0L + Duration = None + MediaType = None + ImageUrl = None + Subtitle = None + Explicit = None + Chapters = None + ChapterFile = None + ChapterType = None + ChapterWaypoints = None + TranscriptUrl = None + TranscriptType = None + TranscriptLang = None + TranscriptCaptions = None + SeasonNumber = None + SeasonDescription = None + EpisodeNumber = None + EpisodeDescription = None } /// Format a duration for an episode member this.FormatDuration() = @@ -460,11 +459,8 @@ type RedirectRule = { } with /// An empty redirect rule - static member Empty = { - From = "" - To = "" - IsRegex = false - } + static member Empty = + { From = ""; To = ""; IsRegex = false } /// An identifier for a custom feed @@ -557,24 +553,23 @@ type PodcastOptions = { } with /// A default set of podcast options - static member Empty = { - Title = "" - Subtitle = None - ItemsInFeed = 0 - Summary = "" - DisplayedAuthor = "" - Email = "" - ImageUrl = Permalink.Empty - AppleCategory = "" - AppleSubcategory = None - Explicit = No - DefaultMediaType = None - MediaBaseUrl = None - PodcastGuid = None - FundingUrl = None - FundingText = None - Medium = None - } + static member Empty = + { Title = "" + Subtitle = None + ItemsInFeed = 0 + Summary = "" + DisplayedAuthor = "" + Email = "" + ImageUrl = Permalink.Empty + AppleCategory = "" + AppleSubcategory = None + Explicit = No + DefaultMediaType = None + MediaBaseUrl = None + PodcastGuid = None + FundingUrl = None + FundingText = None + Medium = None } /// A custom feed @@ -594,12 +589,11 @@ type CustomFeed = { } with /// An empty custom feed - static member Empty = { - Id = CustomFeedId.Empty - Source = Category CategoryId.Empty - Path = Permalink.Empty - Podcast = None - } + static member Empty = + { Id = CustomFeedId.Empty + Source = Category CategoryId.Empty + Path = Permalink.Empty + Podcast = None } /// Really Simple Syndication (RSS) options for this web log @@ -628,15 +622,14 @@ type RssOptions = { } with /// An empty set of RSS options - static member Empty = { - IsFeedEnabled = true - FeedName = "feed.xml" - ItemsInFeed = None - IsCategoryEnabled = true - IsTagEnabled = true - Copyright = None - CustomFeeds = [] - } + static member Empty = + { IsFeedEnabled = true + FeedName = "feed.xml" + ItemsInFeed = None + IsCategoryEnabled = true + IsTagEnabled = true + Copyright = None + CustomFeeds = [] } /// An identifier for a tag mapping diff --git a/src/MyWebLog.Domain/ViewModels.fs b/src/MyWebLog.Domain/ViewModels.fs index cd722eb..c5c93a3 100644 --- a/src/MyWebLog.Domain/ViewModels.fs +++ b/src/MyWebLog.Domain/ViewModels.fs @@ -1146,17 +1146,16 @@ type SettingsModel = { } with /// Create a settings model from a web log - static member FromWebLog(webLog: WebLog) = { - Name = webLog.Name - Slug = webLog.Slug - Subtitle = defaultArg webLog.Subtitle "" - DefaultPage = webLog.DefaultPage - PostsPerPage = webLog.PostsPerPage - TimeZone = webLog.TimeZone - ThemeId = string webLog.ThemeId - AutoHtmx = webLog.AutoHtmx - Uploads = string webLog.Uploads - } + static member FromWebLog(webLog: WebLog) = + { Name = webLog.Name + Slug = webLog.Slug + Subtitle = defaultArg webLog.Subtitle "" + DefaultPage = webLog.DefaultPage + PostsPerPage = webLog.PostsPerPage + TimeZone = webLog.TimeZone + ThemeId = string webLog.ThemeId + AutoHtmx = webLog.AutoHtmx + Uploads = string webLog.Uploads } /// Update a web log with settings from the form member this.Update(webLog: WebLog) = diff --git a/src/MyWebLog.Tests/ViewModelsTests.fs b/src/MyWebLog.Tests/ViewModelsTests.fs index 7b2aaf3..86bc476 100644 --- a/src/MyWebLog.Tests/ViewModelsTests.fs +++ b/src/MyWebLog.Tests/ViewModelsTests.fs @@ -1074,6 +1074,147 @@ let manageRevisionsModelTests = testList "ManageRevisionsModel" [ } ] +/// Unit tests for the PostListItem type +let postListItemTests = testList "PostListItem" [ + testList "FromPost" [ + test "succeeds for a draft post" { + let post = + { Post.Empty with + Id = PostId "draft-post" + AuthorId = WebLogUserId "myself" + Title = "Not Ready for Prime Time" + Permalink = Permalink "2021/draft.html" + UpdatedOn = Noda.epoch + Duration.FromHours 8 + Text = "

WIP

" } + let model = PostListItem.FromPost { WebLog.Empty with TimeZone = "Etc/GMT-1" } post + Expect.equal model.Id "draft-post" "Id not filled properly" + Expect.equal model.AuthorId "myself" "AuthorId not filled properly" + Expect.equal model.Status "Draft" "Status not filled properly" + Expect.equal model.Title "Not Ready for Prime Time" "Title not filled properly" + Expect.equal model.Permalink "2021/draft.html" "Permalink not filled properly" + Expect.isFalse model.PublishedOn.HasValue "PublishedOn should not have had a value" + Expect.equal + model.UpdatedOn ((Noda.epoch + Duration.FromHours 9).ToDateTimeUtc()) "UpdatedOn not filled properly" + Expect.equal model.Text "

WIP

" "Text not filled properly" + Expect.isEmpty model.CategoryIds "There should have been no category IDs" + Expect.isEmpty model.Tags "There should have been no tags" + Expect.isNone model.Episode "There should not have been an episode" + Expect.isEmpty model.Metadata "There should have been no metadata" + } + test "succeeds for a published post in a non-root domain" { + let post = + { Post.Empty with + Id = PostId "full-post" + AuthorId = WebLogUserId "me" + Status = Published + Title = "Finished Product" + Permalink = Permalink "2021/post.html" + PublishedOn = Some (Noda.epoch + Duration.FromHours 12) + UpdatedOn = Noda.epoch + Duration.FromHours 13 + Text = """Click""" + CategoryIds = [ CategoryId "z"; CategoryId "y" ] + Tags = [ "test"; "unit" ] + Episode = Some { Episode.Empty with Media = "test.mp3" } + Metadata = [ { Name = "MyMeta"; Value = "MyValue" } ] } + let model = + PostListItem.FromPost { WebLog.Empty with UrlBase = "https://u.t/w"; TimeZone = "Etc/GMT+1" } post + Expect.equal model.Id "full-post" "Id not filled properly" + Expect.equal model.AuthorId "me" "AuthorId not filled properly" + Expect.equal model.Status "Published" "Status not filled properly" + Expect.equal model.Title "Finished Product" "Title not filled properly" + Expect.equal model.Permalink "2021/post.html" "Permalink not filled properly" + Expect.isTrue model.PublishedOn.HasValue "PublishedOn should not have had a value" + Expect.equal + model.PublishedOn.Value + ((Noda.epoch + Duration.FromHours 11).ToDateTimeUtc()) + "PublishedOn not filled properly" + Expect.equal + model.UpdatedOn ((Noda.epoch + Duration.FromHours 12).ToDateTimeUtc()) "UpdatedOn not filled properly" + Expect.equal model.Text """Click""" "Text not filled properly" + Expect.equal model.CategoryIds [ "z"; "y" ] "CategoryIds not filled properly" + Expect.equal model.Tags [ "test"; "unit" ] "Tags not filled properly" + Expect.isSome model.Episode "There should have been an episode" + Expect.equal model.Episode.Value.Media "test.mp3" "Episode not filled properly" + Expect.equal model.Metadata.Length 1 "There should have been 1 metadata item" + Expect.equal model.Metadata[0].Name "MyMeta" "Metadata not filled properly" + } + ] +] + +/// Unit tests for the SettingModel type +let settingsModelTests = testList "SettingsModel" [ + testList "FromWebLog" [ + test "succeeds with no subtitle" { + let model = + SettingsModel.FromWebLog + { WebLog.Empty with + Name = "The Web Log" + Slug = "the-web-log" + DefaultPage = "this-one" + PostsPerPage = 18 + TimeZone = "America/Denver" + ThemeId = ThemeId "my-theme" + AutoHtmx = true } + Expect.equal model.Name "The Web Log" "Name not filled properly" + Expect.equal model.Slug "the-web-log" "Slug not filled properly" + Expect.equal model.Subtitle "" "Subtitle not filled properly" + Expect.equal model.DefaultPage "this-one" "DefaultPage not filled properly" + Expect.equal model.PostsPerPage 18 "PostsPerPage not filled properly" + Expect.equal model.TimeZone "America/Denver" "TimeZone not filled properly" + Expect.equal model.ThemeId "my-theme" "ThemeId not filled properly" + Expect.isTrue model.AutoHtmx "AutoHtmx should have been set" + Expect.equal model.Uploads "Database" "Uploads not filled properly" + } + test "succeeds with a subtitle" { + let model = SettingsModel.FromWebLog { WebLog.Empty with Subtitle = Some "sub here!" } + Expect.equal model.Subtitle "sub here!" "Subtitle not filled properly" + } + ] + testList "Update" [ + test "succeeds with no subtitle" { + let webLog = + { Name = "Interesting" + Slug = "some-stuff" + Subtitle = "" + DefaultPage = "that-one" + PostsPerPage = 8 + TimeZone = "America/Chicago" + ThemeId = "test-theme" + AutoHtmx = true + Uploads = "Disk" }.Update WebLog.Empty + Expect.equal webLog.Name "Interesting" "Name not filled properly" + Expect.equal webLog.Slug "some-stuff" "Slug not filled properly" + Expect.isNone webLog.Subtitle "Subtitle should not have had a value" + Expect.equal webLog.DefaultPage "that-one" "DefaultPage not filled properly" + Expect.equal webLog.PostsPerPage 8 "PostsPerPage not filled properly" + Expect.equal webLog.TimeZone "America/Chicago" "TimeZone not filled properly" + Expect.equal webLog.ThemeId (ThemeId "test-theme") "ThemeId not filled properly" + Expect.isTrue webLog.AutoHtmx "AutoHtmx should have been set" + Expect.equal webLog.Uploads Disk "Uploads not filled properly" + } + test "succeeds with a subtitle" { + let webLog = { SettingsModel.FromWebLog WebLog.Empty with Subtitle = "Sub" }.Update WebLog.Empty + Expect.equal webLog.Subtitle (Some "Sub") "Subtitle should have had a value" + } + ] +] + +/// Unit tests for the UserMessage type +let userMessageTests = testList "UserMessage" [ + test "Success succeeds" { + Expect.equal UserMessage.Success.Level "success" "Level incorrect" + } + test "Info succeeds" { + Expect.equal UserMessage.Info.Level "primary" "Level incorrect" + } + test "Warning succeeds" { + Expect.equal UserMessage.Warning.Level "warning" "Level incorrect" + } + test "Error succeeds" { + Expect.equal UserMessage.Error.Level "danger" "Level incorrect" + } +] + /// All tests in the Domain.ViewModels file let all = testList "ViewModels" [ addBaseToRelativeUrlsTests @@ -1094,4 +1235,7 @@ let all = testList "ViewModels" [ editUserModelTests managePermalinksModelTests manageRevisionsModelTests + postListItemTests + settingsModelTests + userMessageTests ]