Version 2.1 #41

Merged
danieljsummers merged 123 commits from version-2.1 into main 2024-03-27 00:13:28 +00:00
4 changed files with 288 additions and 174 deletions
Showing only changes of commit d50056cd66 - Show all commits

View File

@ -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)
[<JsonIgnore>]
@ -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
[<JsonIgnore>]

View File

@ -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

View File

@ -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) =

View File

@ -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 = "<h1>WIP</h1>" }
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 "<h1>WIP</h1>" "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 = """<a href="/other-post.html">Click</a>"""
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 """<a href="/w/other-post.html">Click</a>""" "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
]