Version 2.1 #41
|
@ -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>]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) =
|
||||
|
|
|
@ -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
|
||||
]
|
||||
|
|
Loading…
Reference in New Issue
Block a user