Move podcast types to own file; more WIP on role (#17)

This commit is contained in:
2026-08-02 14:02:47 -04:00
parent e7261f6019
commit be4f621d42
7 changed files with 1188 additions and 721 deletions
@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<ItemGroup> <ItemGroup>
<Compile Include="PodcastTypes.fs" />
<Compile Include="SupportTypes.fs" /> <Compile Include="SupportTypes.fs" />
<Compile Include="DataTypes.fs" /> <Compile Include="DataTypes.fs" />
<Compile Include="ViewModels.fs" /> <Compile Include="ViewModels.fs" />
+531
View File
@@ -0,0 +1,531 @@
namespace MyWebLog
/// <summary>Valid values for the iTunes explicit rating</summary>
[<Struct>]
type ExplicitRating =
| Yes
| No
| Clean
/// <summary>Parse a string into an explicit rating</summary>
/// <param name="rating">The string representation of the rating</param>
/// <returns>The <c>ExplicitRating</c> parsed from the string</returns>
/// <exception cref="InvalidArgumentException">If the string is not valid</exception>
static member Parse rating =
match rating with
| "yes" -> Yes
| "no" -> No
| "clean" -> Clean
| _ -> invalidArg (nameof rating) $"{rating} is not a valid explicit rating"
/// <inheritdoc />
override this.ToString() =
match this with Yes -> "yes" | No -> "no" | Clean -> "clean"
/// <summary>A location (specified by Podcast Index)</summary>
type Location = {
/// <summary>The name of the location (free-form text)</summary>
Name: string
/// <summary>A geographic coordinate string (RFC 5870)</summary>
Geo: string
/// <summary>An OpenStreetMap query</summary>
Osm: string option
}
open NodaTime
/// <summary>A chapter in a podcast episode</summary>
type Chapter = {
/// <summary>The start time for the chapter</summary>
StartTime: Duration
/// <summary>The title for this chapter</summary>
Title: string option
/// <summary>A URL for an image for this chapter</summary>
ImageUrl: string option
/// <summary>A URL with information pertaining to this chapter</summary>
Url: string option
/// <summary>Whether this chapter is hidden</summary>
IsHidden: bool option
/// <summary>The episode end time for the chapter</summary>
EndTime: Duration option
/// <summary>A location that applies to a chapter</summary>
Location: Location option
} with
/// <summary>An empty chapter</summary>
static member Empty =
{ StartTime = Duration.Zero
Title = None
ImageUrl = None
Url = None
IsHidden = None
EndTime = None
Location = None }
/// <summary>A group to which a podcast person can belong</summary>
[<Struct>]
type PersonGroup =
| Administration
| AudioPostProduction
| AudioProduction
| Cast
| Community
| CreativeDirection
| Misc
| VideoPostProduction
| VideoProduction
| Visuals
| Writing
/// <summary>Parse a string into a group</summary>
/// <param name="group">The string representation of the group</param>
/// <returns>The <c>PersonGroup</c> instance parsed from the string</returns>
/// <exception cref="InvalidArgumentException">If the string is not valid</exception>
static member Parse group =
match group with
| "Administration" -> Administration
| "Audio Post-Production" -> AudioPostProduction
| "Audio Production" -> AudioProduction
| "Cast" -> Cast
| "Community" -> Community
| "Creative Direction" -> CreativeDirection
| "Misc." -> Misc
| "Video Post-Production" -> VideoPostProduction
| "Video Production" -> VideoProduction
| "Visuals" -> Visuals
| "Writing" -> Writing
| _ -> invalidArg (nameof group) $"{group} is not a valid group"
/// <inheritdoc />
override this.ToString() =
match this with
| Administration -> "Administration"
| AudioPostProduction -> "Audio Post-Production"
| AudioProduction -> "Audio Production"
| Cast -> "Cast"
| Community -> "Community"
| CreativeDirection -> "Creative Direction"
| Misc -> "Misc."
| VideoPostProduction -> "Video Post-Production"
| VideoProduction -> "Video Production"
| Visuals -> "Visuals"
| Writing -> "Writing"
/// <summary>The role a person plays for a podcast or a podcast episode</summary>
type PersonRole =
| Announcer
| AssistantCamera
| AssistantDirector
| AssistantEditor
| AssociateProducer
| AudioEditor
| AudioEngineer
| Author
| BookingCoordinator
| CameraGrip
| CameraOperator
| CoHost
| Composer
| Consultant
| ContentManager
| CoverArtDesigner
| CoWriter
| CreativeDirector
| DevelopmentProducer
| Director
| EditorialDirector
| ExecutiveProducer
| FactChecker
| FoleyArtist
| GraphicDesigner
| Guest
| GuestHost
| GuestWriter
| Host
| Intern
| LightingDesigner
| Logger
| ManagingEditor
| MarketingManager
| MusicContributor
| MusicProduction
| Narrator
| PostProductionEngineer
| Producer
| ProductionAssistant
| ProductionCoordinator
| RemoteRecordingEngineer
| Reporter
| Researcher
| SalesManager
| SalesRepresentative
| ScriptCoordinator
| ScriptEditor
| SeniorProducer
| SocialMediaManager
| Songwriter
| SoundDesigner
| StoryEditor
| StudioCoordinator
| TechnicalDirector
| TechnicalManager
| ThemeMusic
| Transcriber
| Translator
| VideoEditor
| VoiceActor
| Writer
| WritingEditor
/// <summary>Parse a string into a role</summary>
/// <param name="role">The string representation of the role</param>
/// <returns>The <c>PersonRole</c> instance parsed from the string</returns>
/// <exception cref="InvalidArgumentException">If the string is not valid</exception>
static member Parse role =
match role with
| "Announcer" -> Announcer
| "Assistant Camera" -> AssistantCamera
| "Assistant Director" -> AssistantDirector
| "Assistant Editor" -> AssistantEditor
| "Associate Producer" -> AssociateProducer
| "Audio Editor" -> AudioEditor
| "Audio Engineer" -> AudioEngineer
| "Author" -> Author
| "Booking Coordinator" -> BookingCoordinator
| "Camera Grip" -> CameraGrip
| "Camera Operator" -> CameraOperator
| "Co-Host" -> CoHost
| "Composer" -> Composer
| "Consultant" -> Consultant
| "Content Manager" -> ContentManager
| "Cover Art Designer" -> CoverArtDesigner
| "Co-Writer" -> CoWriter
| "Creative Director" -> CreativeDirector
| "Development Producer" -> DevelopmentProducer
| "Director" -> Director
| "Editorial Director" -> EditorialDirector
| "Executive Producer" -> ExecutiveProducer
| "Fact Checker" -> FactChecker
| "Foley Artist" -> FoleyArtist
| "Graphic Designer" -> GraphicDesigner
| "Guest" -> Guest
| "Guest Host" -> GuestHost
| "Guest Writer" -> GuestWriter
| "Host" -> Host
| "Intern" -> Intern
| "Lighting Designer" -> LightingDesigner
| "Logger" -> Logger
| "Managing Editor" -> ManagingEditor
| "Marketing Manager" -> MarketingManager
| "Music Contributor" -> MusicContributor
| "Music Production" -> MusicProduction
| "Narrator" -> Narrator
| "Post Production Engineer" -> PostProductionEngineer
| "Producer" -> Producer
| "Production Assistant" -> ProductionAssistant
| "Production Coordinator" -> ProductionCoordinator
| "Remote Recording Engineer" -> RemoteRecordingEngineer
| "Reporter" -> Reporter
| "Researcher" -> Researcher
| "Sales Manager" -> SalesManager
| "Sales Representative" -> SalesRepresentative
| "Script Coordinator" -> ScriptCoordinator
| "Script Editor" -> ScriptEditor
| "Senior Producer" -> SeniorProducer
| "Social Media Manager" -> SocialMediaManager
| "Songwriter" -> Songwriter
| "Sound Designer" -> SoundDesigner
| "Story Editor" -> StoryEditor
| "Studio Coordinator" -> StudioCoordinator
| "Technical Director" -> TechnicalDirector
| "Technical Manager" -> TechnicalManager
| "Theme Music" -> ThemeMusic
| "Transcriber" -> Transcriber
| "Translator" -> Translator
| "Editor (Video)" -> VideoEditor
| "Voice Actor" -> VoiceActor
| "Writer" -> Writer
| "Editor (Writing)" -> WritingEditor
| _ -> invalidArg (nameof role) $"{role} is not a valid role"
/// <summary>Role / group cross-reference</summary>
static member GroupXref =
[ Announcer, Cast
AssistantCamera, VideoProduction
AssistantDirector, CreativeDirection
AssistantEditor, VideoPostProduction
AssociateProducer, CreativeDirection
AudioEditor, AudioPostProduction
AudioEngineer, AudioProduction
Author, Writing
BookingCoordinator, Administration
CameraGrip, VideoProduction
CameraOperator, VideoProduction
CoHost, Cast
Composer, AudioPostProduction
ContentManager, Administration
Consultant, Misc
CoverArtDesigner, Visuals
CoWriter, Writing
CreativeDirector, CreativeDirection
DevelopmentProducer, CreativeDirection
Director, CreativeDirection
EditorialDirector, Writing
ExecutiveProducer, CreativeDirection
FactChecker, Writing
FoleyArtist, AudioPostProduction
GraphicDesigner, Visuals
Guest, Cast
GuestHost, Cast
GuestWriter, Writing
Host, Cast
Intern, Misc
LightingDesigner, VideoProduction
Logger, Writing
ManagingEditor, Writing
MarketingManager, Administration
MusicContributor, AudioPostProduction
MusicProduction, AudioPostProduction
Narrator, Cast
PostProductionEngineer, AudioProduction
Producer, CreativeDirection
ProductionAssistant, Administration
ProductionCoordinator, Administration
RemoteRecordingEngineer, AudioProduction
Reporter, Cast
Researcher, Writing
SalesManager, Administration
SalesRepresentative, Administration
ScriptCoordinator, Writing
ScriptEditor, Writing
SeniorProducer, CreativeDirection
SocialMediaManager, Community
Songwriter, Writing
SoundDesigner, AudioPostProduction
StoryEditor, Writing
StudioCoordinator, AudioProduction
TechnicalDirector, AudioProduction
TechnicalManager, AudioProduction
ThemeMusic, AudioPostProduction
Transcriber, Writing
Translator, Writing
VideoEditor, VideoPostProduction
VoiceActor, Cast
Writer, Writing
WritingEditor, Writing ]
/// <inheritdoc />
override this.ToString() =
match this with
| Announcer -> "Announcer"
| AssistantCamera -> "Assistant Camera"
| AssistantDirector -> "Assistant Director"
| AssistantEditor -> "Assistant Editor"
| AssociateProducer -> "Associate Producer"
| AudioEditor -> "Audio Editor"
| AudioEngineer -> "Audio Engineer"
| Author -> "Author"
| BookingCoordinator -> "Booking Coordinator"
| CameraGrip -> "Camera Grip"
| CameraOperator -> "Camera Operator"
| CoHost -> "Co-Host"
| Composer -> "Composer"
| Consultant -> "Consultant"
| ContentManager -> "Content Manager"
| CoverArtDesigner -> "Cover Art Designer"
| CoWriter -> "Co-Writer"
| CreativeDirector -> "Creative Director"
| DevelopmentProducer -> "Development Producer"
| Director -> "Director"
| EditorialDirector -> "Editorial Director"
| ExecutiveProducer -> "Executive Producer"
| FactChecker -> "Fact Checker"
| FoleyArtist -> "Foley Artist"
| GraphicDesigner -> "Graphic Designer"
| Guest -> "Guest"
| GuestHost -> "Guest Host"
| GuestWriter -> "Guest Writer"
| Host -> "Host"
| Intern -> "Intern"
| LightingDesigner -> "Lighting Designer"
| Logger -> "Logger"
| ManagingEditor -> "Managing Editor"
| MarketingManager -> "Marketing Manager"
| MusicContributor -> "Music Contributor"
| MusicProduction -> "Music Production"
| Narrator -> "Narrator"
| PostProductionEngineer -> "Post Production Engineer"
| Producer -> "Producer"
| ProductionAssistant -> "Production Assistant"
| ProductionCoordinator -> "Production Coordinator"
| RemoteRecordingEngineer -> "Remote Recording Engineer"
| Reporter -> "Reporter"
| Researcher -> "Researcher"
| SalesManager -> "Sales Manager"
| SalesRepresentative -> "Sales Representative"
| ScriptCoordinator -> "Script Coordinator"
| ScriptEditor -> "Script Editor"
| SeniorProducer -> "Senior Producer"
| SocialMediaManager -> "Social Media Manager"
| Songwriter -> "Songwriter"
| SoundDesigner -> "Sound Designer"
| StoryEditor -> "Story Editor"
| StudioCoordinator -> "Studio Coordinator"
| TechnicalDirector -> "Technical Director"
| TechnicalManager -> "Technical Manager"
| ThemeMusic -> "Theme Music"
| Transcriber -> "Transcriber"
| Translator -> "Translator"
| VideoEditor -> "Editor (Video)"
| VoiceActor -> "Voice Actor"
| Writer -> "Writer"
| WritingEditor -> "Editor (Writing)"
/// <summary>Get the display name of the role</summary>
/// <remarks>Both Video Production and Writing have an "Editor"; this is how it should be rendered in RSS feeds and
/// in the UI</remarks>
member this.ToDisplay() =
match this with VideoEditor | WritingEditor -> "Editor" | _ -> string this
/// <summary>Get the <c>PersonGroup</c> to which this role belongs</summary>
member this.Group =
PersonRole.GroupXref |> List.find (fun it -> fst it = this) |> snd
open NodaTime.Text
/// <summary>A podcast episode</summary>
type Episode = {
/// <summary>The URL to the media file for the episode (may be permalink)</summary>
Media: string
/// <summary>The length of the media file, in bytes</summary>
Length: int64
/// <summary>The duration of the episode</summary>
Duration: Duration option
/// <summary>The media type of the file (overrides podcast default if present)</summary>
MediaType: string option
/// <summary>The URL to the image file for this episode (overrides podcast image if present, may be permalink)</summary>
ImageUrl: string option
/// <summary>A subtitle for this episode</summary>
Subtitle: string option
/// <summary>This episode's explicit rating (overrides podcast rating if present)</summary>
Explicit: ExplicitRating option
/// <summary>Chapters for this episode</summary>
Chapters: Chapter list option
/// <summary>A link to a chapter file</summary>
ChapterFile: string option
/// <summary>The MIME type for the chapter file</summary>
ChapterType: string option
/// <summary>Whether the chapters have locations that should be displayed as waypoints</summary>
ChapterWaypoints: bool option
/// <summary>The URL for the transcript of the episode (may be permalink)</summary>
TranscriptUrl: string option
/// <summary>The MIME type of the transcript</summary>
TranscriptType: string option
/// <summary>The language in which the transcript is written</summary>
TranscriptLang: string option
/// <summary>If true, the transcript will be declared (in the feed) to be a captions file</summary>
TranscriptCaptions: bool option
/// <summary>The season number (for serialized podcasts)</summary>
SeasonNumber: int option
/// <summary>A description of the season</summary>
SeasonDescription: string option
/// <summary>The episode number</summary>
EpisodeNumber: double option
/// <summary>A description of the episode</summary>
EpisodeDescription: string option
} with
/// <summary>An empty episode</summary>
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 }
/// <summary>Format a duration for an episode</summary>
/// <returns>A duration formatted in hours, minutes, and seconds</returns>
member this.FormatDuration() =
this.Duration |> Option.map (DurationPattern.CreateWithInvariantCulture("H:mm:ss").Format)
/// <summary>PodcastIndex.org podcast:medium allowed values</summary>
[<Struct>]
type PodcastMedium =
| Podcast
| Music
| Video
| Film
| Audiobook
| Newsletter
| Blog
/// <summary>Parse a string into a podcast medium</summary>
/// <param name="medium">The string to be parsed</param>
/// <returns>The <c>PodcastMedium</c> parsed from the string</returns>
/// <exception cref="InvalidArgumentException">If the string is not valid</exception>
static member Parse medium =
match medium with
| "podcast" -> Podcast
| "music" -> Music
| "video" -> Video
| "film" -> Film
| "audiobook" -> Audiobook
| "newsletter" -> Newsletter
| "blog" -> Blog
| _ -> invalidArg (nameof medium) $"{medium} is not a valid podcast medium"
/// <inheritdoc />
override this.ToString() =
match this with
| Podcast -> "podcast"
| Music -> "music"
| Video -> "video"
| Film -> "film"
| Audiobook -> "audiobook"
| Newsletter -> "newsletter"
| Blog -> "blog"
-524
View File
@@ -203,492 +203,6 @@ type CommentStatus =
match this with Approved -> "Approved" | Pending -> "Pending" | Spam -> "Spam" match this with Approved -> "Approved" | Pending -> "Pending" | Spam -> "Spam"
/// <summary>Valid values for the iTunes explicit rating</summary>
[<Struct>]
type ExplicitRating =
| Yes
| No
| Clean
/// <summary>Parse a string into an explicit rating</summary>
/// <param name="rating">The string representation of the rating</param>
/// <returns>The <c>ExplicitRating</c> parsed from the string</returns>
/// <exception cref="InvalidArgumentException">If the string is not valid</exception>
static member Parse rating =
match rating with
| "yes" -> Yes
| "no" -> No
| "clean" -> Clean
| _ -> invalidArg (nameof rating) $"{rating} is not a valid explicit rating"
/// <inheritdoc />
override this.ToString() =
match this with Yes -> "yes" | No -> "no" | Clean -> "clean"
/// <summary>A location (specified by Podcast Index)</summary>
type Location = {
/// <summary>The name of the location (free-form text)</summary>
Name: string
/// <summary>A geographic coordinate string (RFC 5870)</summary>
Geo: string
/// <summary>An OpenStreetMap query</summary>
Osm: string option
}
/// <summary>A chapter in a podcast episode</summary>
type Chapter = {
/// <summary>The start time for the chapter</summary>
StartTime: Duration
/// <summary>The title for this chapter</summary>
Title: string option
/// <summary>A URL for an image for this chapter</summary>
ImageUrl: string option
/// <summary>A URL with information pertaining to this chapter</summary>
Url: string option
/// <summary>Whether this chapter is hidden</summary>
IsHidden: bool option
/// <summary>The episode end time for the chapter</summary>
EndTime: Duration option
/// <summary>A location that applies to a chapter</summary>
Location: Location option
} with
/// <summary>An empty chapter</summary>
static member Empty =
{ StartTime = Duration.Zero
Title = None
ImageUrl = None
Url = None
IsHidden = None
EndTime = None
Location = None }
/// <summary>A group to which a podcast person can belong</summary>
type PersonGroup =
| Administration
| AudioPostProduction
| AudioProduction
| Cast
| Community
| CreativeDirection
| Misc
| VideoPostProduction
| VideoProduction
| Visuals
| Writing
/// <summary>Parse a string into a group</summary>
/// <param name="group">The string representation of the group</param>
/// <returns>The <c>PersonGroup</c> instance parsed from the string</returns>
/// <exception cref="InvalidArgumentException">If the string is not valid</exception>
static member Parse group =
match group with
| "Administration" -> Administration
| "Audio Post-Production" -> AudioPostProduction
| "Audio Production" -> AudioProduction
| "Cast" -> Cast
| "Community" -> Community
| "Creative Direction" -> CreativeDirection
| "Misc." -> Misc
| "Video Post-Production" -> VideoPostProduction
| "Video Production" -> VideoProduction
| "Visuals" -> Visuals
| "Writing" -> Writing
| _ -> invalidArg (nameof group) $"{group} is not a valid group"
/// <inheritdoc />
override this.ToString() =
match this with
| Administration -> "Administration"
| AudioPostProduction -> "Audio Post-Production"
| AudioProduction -> "Audio Production"
| Cast -> "Cast"
| Community -> "Community"
| CreativeDirection -> "Creative Direction"
| Misc -> "Misc."
| VideoPostProduction -> "Video Post-Production"
| VideoProduction -> "Video Production"
| Visuals -> "Visuals"
| Writing -> "Writing"
/// <summary>The role a person plays for a podcast or a podcast episode</summary>
type PersonRole =
| Announcer
| AssistantCamera
| AssistantDirector
| AssistantEditor
| AssociateProducer
| AudioEditor
| AudioEngineer
| Author
| BookingCoordinator
| CameraGrip
| CameraOperator
| CoHost
| Composer
| Consultant
| ContentManager
| CoverArtDesigner
| CoWriter
| CreativeDirector
| DevelopmentProducer
| Director
| EditorialDirector
| ExecutiveProducer
| FactChecker
| FoleyArtist
| GraphicDesigner
| Guest
| GuestHost
| GuestWriter
| Host
| Intern
| LightingDesigner
| Logger
| ManagingEditor
| MarketingManager
| MusicContributor
| MusicProduction
| Narrator
| PostProductionEngineer
| Producer
| ProductionAssistant
| ProductionCoordinator
| RemoteRecordingEngineer
| Reporter
| Researcher
| SalesManager
| SalesRepresentative
| ScriptCoordinator
| ScriptEditor
| SeniorProducer
| SocialMediaManager
| Songwriter
| SoundDesigner
| StoryEditor
| StudioCoordinator
| TechnicalDirector
| TechnicalManager
| ThemeMusic
| Transcriber
| Translator
| VideoEditor
| VoiceActor
| Writer
| WritingEditor
/// <summary>Parse a string into a role</summary>
/// <param name="role">The string representation of the role</param>
/// <returns>The <c>PersonRole</c> instance parsed from the string</returns>
/// <exception cref="InvalidArgumentException">If the string is not valid</exception>
static member Parse role =
match role with
| "Announcer" -> Announcer
| "Assistant Camera" -> AssistantCamera
| "Assistant Director" -> AssistantDirector
| "Assistant Editor" -> AssistantEditor
| "Associate Producer" -> AssociateProducer
| "Audio Editor" -> AudioEditor
| "Audio Engineer" -> AudioEngineer
| "Author" -> Author
| "Booking Coordinator" -> BookingCoordinator
| "Camera Grip" -> CameraGrip
| "Camera Operator" -> CameraOperator
| "Co-Host" -> CoHost
| "Composer" -> Composer
| "Consultant" -> Consultant
| "Content Manager" -> ContentManager
| "Cover Art Designer" -> CoverArtDesigner
| "Co-Writer" -> CoWriter
| "Creative Director" -> CreativeDirector
| "Development Producer" -> DevelopmentProducer
| "Director" -> Director
| "Editorial Director" -> EditorialDirector
| "Executive Producer" -> ExecutiveProducer
| "Fact Checker" -> FactChecker
| "Foley Artist" -> FoleyArtist
| "Graphic Designer" -> GraphicDesigner
| "Guest" -> Guest
| "Guest Host" -> GuestHost
| "Guest Writer" -> GuestWriter
| "Host" -> Host
| "Intern" -> Intern
| "Lighting Designer" -> LightingDesigner
| "Logger" -> Logger
| "Managing Editor" -> ManagingEditor
| "Marketing Manager" -> MarketingManager
| "Music Contributor" -> MusicContributor
| "Music Production" -> MusicProduction
| "Narrator" -> Narrator
| "Post Production Engineer" -> PostProductionEngineer
| "Producer" -> Producer
| "Production Assistant" -> ProductionAssistant
| "Production Coordinator" -> ProductionCoordinator
| "Remote Recording Engineer" -> RemoteRecordingEngineer
| "Reporter" -> Reporter
| "Researcher" -> Researcher
| "Sales Manager" -> SalesManager
| "Sales Representative" -> SalesRepresentative
| "Script Coordinator" -> ScriptCoordinator
| "Script Editor" -> ScriptEditor
| "Senior Producer" -> SeniorProducer
| "Social Media Manager" -> SocialMediaManager
| "Songwriter" -> Songwriter
| "Sound Designer" -> SoundDesigner
| "Story Editor" -> StoryEditor
| "Studio Coordinator" -> StudioCoordinator
| "Technical Director" -> TechnicalDirector
| "Technical Manager" -> TechnicalManager
| "Theme Music" -> ThemeMusic
| "Transcriber" -> Transcriber
| "Translator" -> Translator
| "Editor (Video)" -> VideoEditor
| "Voice Actor" -> VoiceActor
| "Writer" -> Writer
| "Editor (Writing)" -> WritingEditor
| _ -> invalidArg (nameof role) $"{role} is not a valid role"
/// <inheritdoc />
override this.ToString() =
match this with
| Announcer -> "Announcer"
| AssistantCamera -> "Assistant Camera"
| AssistantDirector -> "Assistant Director"
| AssistantEditor -> "Assistant Editor"
| AssociateProducer -> "Associate Producer"
| AudioEditor -> "Audio Editor"
| AudioEngineer -> "Audio Engineer"
| Author -> "Author"
| BookingCoordinator -> "Booking Coordinator"
| CameraGrip -> "Camera Grip"
| CameraOperator -> "Camera Operator"
| CoHost -> "Co-Host"
| Composer -> "Composer"
| Consultant -> "Consultant"
| ContentManager -> "Content Manager"
| CoverArtDesigner -> "Cover Art Designer"
| CoWriter -> "Co-Writer"
| CreativeDirector -> "Creative Director"
| DevelopmentProducer -> "Development Producer"
| Director -> "Director"
| EditorialDirector -> "Editorial Director"
| ExecutiveProducer -> "Executive Producer"
| FactChecker -> "Fact Checker"
| FoleyArtist -> "Foley Artist"
| GraphicDesigner -> "Graphic Designer"
| Guest -> "Guest"
| GuestHost -> "Guest Host"
| GuestWriter -> "Guest Writer"
| Host -> "Host"
| Intern -> "Intern"
| LightingDesigner -> "Lighting Designer"
| Logger -> "Logger"
| ManagingEditor -> "Managing Editor"
| MarketingManager -> "Marketing Manager"
| MusicContributor -> "Music Contributor"
| MusicProduction -> "Music Production"
| Narrator -> "Narrator"
| PostProductionEngineer -> "Post Production Engineer"
| Producer -> "Producer"
| ProductionAssistant -> "Production Assistant"
| ProductionCoordinator -> "Production Coordinator"
| RemoteRecordingEngineer -> "Remote Recording Engineer"
| Reporter -> "Reporter"
| Researcher -> "Researcher"
| SalesManager -> "Sales Manager"
| SalesRepresentative -> "Sales Representative"
| ScriptCoordinator -> "Script Coordinator"
| ScriptEditor -> "Script Editor"
| SeniorProducer -> "Senior Producer"
| SocialMediaManager -> "Social Media Manager"
| Songwriter -> "Songwriter"
| SoundDesigner -> "Sound Designer"
| StoryEditor -> "Story Editor"
| StudioCoordinator -> "Studio Coordinator"
| TechnicalDirector -> "Technical Director"
| TechnicalManager -> "Technical Manager"
| ThemeMusic -> "Theme Music"
| Transcriber -> "Transcriber"
| Translator -> "Translator"
| VideoEditor -> "Editor (Video)"
| VoiceActor -> "Voice Actor"
| Writer -> "Writer"
| WritingEditor -> "Editor (Writing)"
/// <summary>Get the display name of the role</summary>
/// <remarks>Both Video Production and Writing have an "Editor"; this is how it should be rendered in RSS feeds and
/// in the UI</remarks>
member this.ToDisplay() =
match this with VideoEditor | WritingEditor -> "Editor" | _ -> string this
/// <summary>Role / group cross-reference</summary>
static member GroupXref =
[ Announcer, Cast
AssistantCamera, VideoProduction
AssistantDirector, CreativeDirection
AssistantEditor, VideoPostProduction
AssociateProducer, CreativeDirection
AudioEditor, AudioPostProduction
AudioEngineer, AudioProduction
Author, Writing
BookingCoordinator, Administration
CameraGrip, VideoProduction
CameraOperator, VideoProduction
CoHost, Cast
Composer, AudioPostProduction
ContentManager, Administration
Consultant, Misc
CoverArtDesigner, Visuals
CoWriter, Writing
CreativeDirector, CreativeDirection
DevelopmentProducer, CreativeDirection
Director, CreativeDirection
EditorialDirector, Writing
ExecutiveProducer, CreativeDirection
FactChecker, Writing
FoleyArtist, AudioPostProduction
GraphicDesigner, Visuals
Guest, Cast
GuestHost, Cast
GuestWriter, Writing
Host, Cast
Intern, Misc
LightingDesigner, VideoProduction
Logger, Writing
ManagingEditor, Writing
MarketingManager, Administration
MusicContributor, AudioPostProduction
MusicProduction, AudioPostProduction
Narrator, Cast
PostProductionEngineer, AudioProduction
Producer, CreativeDirection
ProductionAssistant, Administration
ProductionCoordinator, Administration
RemoteRecordingEngineer, AudioProduction
Reporter, Cast
Researcher, Writing
SalesManager, Administration
SalesRepresentative, Administration
ScriptCoordinator, Writing
ScriptEditor, Writing
SeniorProducer, CreativeDirection
SocialMediaManager, Community
Songwriter, Writing
SoundDesigner, AudioPostProduction
StoryEditor, Writing
StudioCoordinator, AudioProduction
TechnicalDirector, AudioProduction
TechnicalManager, AudioProduction
ThemeMusic, AudioPostProduction
Transcriber, Writing
Translator, Writing
VideoEditor, VideoPostProduction
VoiceActor, Cast
Writer, Writing
WritingEditor, Writing ]
open NodaTime.Text
/// <summary>A podcast episode</summary>
type Episode = {
/// <summary>The URL to the media file for the episode (may be permalink)</summary>
Media: string
/// <summary>The length of the media file, in bytes</summary>
Length: int64
/// <summary>The duration of the episode</summary>
Duration: Duration option
/// <summary>The media type of the file (overrides podcast default if present)</summary>
MediaType: string option
/// <summary>The URL to the image file for this episode (overrides podcast image if present, may be permalink)</summary>
ImageUrl: string option
/// <summary>A subtitle for this episode</summary>
Subtitle: string option
/// <summary>This episode's explicit rating (overrides podcast rating if present)</summary>
Explicit: ExplicitRating option
/// <summary>Chapters for this episode</summary>
Chapters: Chapter list option
/// <summary>A link to a chapter file</summary>
ChapterFile: string option
/// <summary>The MIME type for the chapter file</summary>
ChapterType: string option
/// <summary>Whether the chapters have locations that should be displayed as waypoints</summary>
ChapterWaypoints: bool option
/// <summary>The URL for the transcript of the episode (may be permalink)</summary>
TranscriptUrl: string option
/// <summary>The MIME type of the transcript</summary>
TranscriptType: string option
/// <summary>The language in which the transcript is written</summary>
TranscriptLang: string option
/// <summary>If true, the transcript will be declared (in the feed) to be a captions file</summary>
TranscriptCaptions: bool option
/// <summary>The season number (for serialized podcasts)</summary>
SeasonNumber: int option
/// <summary>A description of the season</summary>
SeasonDescription: string option
/// <summary>The episode number</summary>
EpisodeNumber: double option
/// <summary>A description of the episode</summary>
EpisodeDescription: string option
} with
/// <summary>An empty episode</summary>
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 }
/// <summary>Format a duration for an episode</summary>
/// <returns>A duration formatted in hours, minutes, and seconds</returns>
member this.FormatDuration() =
this.Duration |> Option.map (DurationPattern.CreateWithInvariantCulture("H:mm:ss").Format)
/// <summary>Types of markup text</summary> /// <summary>Types of markup text</summary>
type MarkupText = type MarkupText =
/// <summary>Markdown text</summary> /// <summary>Markdown text</summary>
@@ -1069,44 +583,6 @@ type PageId =
match this with PageId it -> it match this with PageId it -> it
/// <summary>PodcastIndex.org podcast:medium allowed values</summary>
[<Struct>]
type PodcastMedium =
| Podcast
| Music
| Video
| Film
| Audiobook
| Newsletter
| Blog
/// <summary>Parse a string into a podcast medium</summary>
/// <param name="medium">The string to be parsed</param>
/// <returns>The <c>PodcastMedium</c> parsed from the string</returns>
/// <exception cref="InvalidArgumentException">If the string is not valid</exception>
static member Parse medium =
match medium with
| "podcast" -> Podcast
| "music" -> Music
| "video" -> Video
| "film" -> Film
| "audiobook" -> Audiobook
| "newsletter" -> Newsletter
| "blog" -> Blog
| _ -> invalidArg (nameof medium) $"{medium} is not a valid podcast medium"
/// <inheritdoc />
override this.ToString() =
match this with
| Podcast -> "podcast"
| Music -> "music"
| Video -> "video"
| Film -> "film"
| Audiobook -> "audiobook"
| Newsletter -> "newsletter"
| Blog -> "blog"
/// <summary>Statuses for posts</summary> /// <summary>Statuses for posts</summary>
[<Struct>] [<Struct>]
type PostStatus = type PostStatus =
@@ -0,0 +1,654 @@
module PodcastTypesTests
open System
open Expecto
open MyWebLog
open NodaTime
/// Tests for the ExplicitRating type
let explicitRatingTests = testList "ExplicitRating" [
testList "Parse" [
test "succeeds for \"yes\"" {
Expect.equal (ExplicitRating.Parse "yes") Yes "\"yes\" not parsed correctly"
}
test "succeeds for \"no\"" {
Expect.equal (ExplicitRating.Parse "no") No "\"no\" not parsed correctly"
}
test "succeeds for \"clean\"" {
Expect.equal (ExplicitRating.Parse "clean") Clean "\"clean\" not parsed correctly"
}
test "fails for unrecognized value" {
Expect.throwsT<ArgumentException>
(fun () -> ignore (ExplicitRating.Parse "maybe")) "Invalid value should have raised an exception"
}
]
testList "ToString" [
test "Yes succeeds" {
Expect.equal (string Yes) "yes" "Yes string incorrect"
}
test "No succeeds" {
Expect.equal (string No) "no" "No string incorrect"
}
test "Clean succeeds" {
Expect.equal (string Clean) "clean" "Clean string incorrect"
}
]
]
/// Tests for the Episode type
let episodeTests = testList "Episode" [
testList "FormatDuration" [
test "succeeds when no duration is specified" {
Expect.isNone (Episode.Empty.FormatDuration()) "A missing duration should have returned None"
}
test "succeeds when duration is specified" {
Expect.equal
({ Episode.Empty with
Duration = Some (Duration.FromMinutes 3L + Duration.FromSeconds 13L) }.FormatDuration())
(Some "0:03:13")
"Duration not formatted correctly"
}
test "succeeds when duration is > 10 hours" {
Expect.equal
({ Episode.Empty with Duration = Some (Duration.FromHours 11) }.FormatDuration())
(Some "11:00:00")
"Duration not formatted correctly"
}
]
]
/// Tests for the PersonGroup type
let personGroupTests = testList "PersonGroup" [
testList "Parse" [
test "succeeds for \"Administration\"" {
Expect.equal (PersonGroup.Parse "Administration") Administration "Administration not parsed correctly"
}
test "succeeds for \"Audio Post-Production\"" {
Expect.equal
(PersonGroup.Parse "Audio Post-Production")
AudioPostProduction
"Audio Post-Production not parsed correctly"
}
test "succeeds for \"Audio Production\"" {
Expect.equal
(PersonGroup.Parse "Audio Production") AudioProduction "Audio Production not parsed correctly"
}
test "succeeds for \"Cast\"" {
Expect.equal (PersonGroup.Parse "Cast") Cast "Cast not parsed correctly"
}
test "succeeds for \"Community\"" {
Expect.equal (PersonGroup.Parse "Community") Community "Community not parsed correctly"
}
test "succeeds for \"Creative Direction\"" {
Expect.equal
(PersonGroup.Parse "Creative Direction") CreativeDirection "Creative Direction not parsed correctly"
}
test "succeeds for \"Misc.\"" {
Expect.equal (PersonGroup.Parse "Misc.") Misc "Misc. not parsed correctly"
}
test "succeeds for \"Video Post-Production\"" {
Expect.equal
(PersonGroup.Parse "Video Post-Production")
VideoPostProduction
"Video Post-Production not parsed correctly"
}
test "succeeds for \"Video Production\"" {
Expect.equal
(PersonGroup.Parse "Video Production") VideoProduction "Video Production not parsed correctly"
}
test "succeeds for \"Visuals\"" {
Expect.equal (PersonGroup.Parse "Visuals") Visuals "Visuals not parsed correctly"
}
test "succeeds for \"Writing\"" {
Expect.equal (PersonGroup.Parse "Writing") Writing "Writing not parsed correctly"
}
test "fails for unrecognized value" {
Expect.throwsT<ArgumentException>
(fun () -> ignore (PersonGroup.Parse "Fan")) "Invalid value should have raised an exception"
}
]
testList "ToString" [
test "Administration succeeds" {
Expect.equal (string Administration) "Administration" "Administration string incorrect"
}
test "Audio Post-Production succeeds" {
Expect.equal (string AudioPostProduction) "Audio Post-Production" "Audio Post-Production string incorrect"
}
test "Audio Production succeeds" {
Expect.equal (string AudioProduction) "Audio Production" "Audio Production string incorrect"
}
test "Cast succeeds" {
Expect.equal (string Cast) "Cast" "Cast string incorrect"
}
test "Community succeeds" {
Expect.equal (string Community) "Community" "Community string incorrect"
}
test "Creative Direction succeeds" {
Expect.equal (string CreativeDirection) "Creative Direction" "Creative Direction string incorrect"
}
test "Misc succeeds" {
Expect.equal (string Misc) "Misc." "Misc string incorrect"
}
test "Video Post-Production succeeds" {
Expect.equal (string VideoPostProduction) "Video Post-Production" "Video Post-Production string incorrect"
}
test "Video Production succeeds" {
Expect.equal (string VideoProduction) "Video Production" "Video Production string incorrect"
}
test "Visuals succeeds" {
Expect.equal (string Visuals) "Visuals" "Visuals string incorrect"
}
test "Writing succeeds" {
Expect.equal (string Writing) "Writing" "Writing string incorrect"
}
]
]
/// Unit tests for the PersonRole type
let personRoleTests = testList "PersonRole" [
testList "Parse" [
test "succeeds for \"Announcer\"" {
Expect.equal (PersonRole.Parse "Announcer") Announcer "Announcer not parsed correctly"
}
test "succeeds for \"Assistant Camera\"" {
Expect.equal (PersonRole.Parse "Assistant Camera") AssistantCamera "Assistant Camera not parsed correctly"
}
test "succeeds for \"Assistant Director\"" {
Expect.equal
(PersonRole.Parse "Assistant Director") AssistantDirector "Assistant Director not parsed correctly"
}
test "succeeds for \"Assistant Editor\"" {
Expect.equal (PersonRole.Parse "Assistant Editor") AssistantEditor "Assistant Editor not parsed correctly"
}
test "succeeds for \"Associate Producer\"" {
Expect.equal
(PersonRole.Parse "Associate Producer") AssociateProducer "Associate Producer not parsed correctly"
}
test "succeeds for \"Audio Editor\"" {
Expect.equal (PersonRole.Parse "Audio Editor") AudioEditor "Audio Editor not parsed correctly"
}
test "succeeds for \"Audio Engineer\"" {
Expect.equal (PersonRole.Parse "Audio Engineer") AudioEngineer "Audio Engineer not parsed correctly"
}
test "succeeds for \"Author\"" {
Expect.equal (PersonRole.Parse "Author") Author "Author not parsed correctly"
}
test "succeeds for \"Booking Coordinator\"" {
Expect.equal
(PersonRole.Parse "Booking Coordinator") BookingCoordinator "Booking Coordinator not parsed correctly"
}
test "succeeds for \"Camera Grip\"" {
Expect.equal (PersonRole.Parse "Camera Grip") CameraGrip "Camera Grip not parsed correctly"
}
test "succeeds for \"Camera Operator\"" {
Expect.equal (PersonRole.Parse "Camera Operator") CameraOperator "Camera Operator not parsed correctly"
}
test "succeeds for \"Co-Host\"" {
Expect.equal (PersonRole.Parse "Co-Host") CoHost "Co-Host not parsed correctly"
}
test "succeeds for \"Composer\"" {
Expect.equal (PersonRole.Parse "Composer") Composer "Composer not parsed correctly"
}
test "succeeds for \"Consultant\"" {
Expect.equal (PersonRole.Parse "Consultant") Consultant "Consultant not parsed correctly"
}
test "succeeds for \"Content Manager\"" {
Expect.equal (PersonRole.Parse "Content Manager") ContentManager "Content Manager not parsed correctly"
}
test "succeeds for \"Cover Art Designer\"" {
Expect.equal
(PersonRole.Parse "Cover Art Designer") CoverArtDesigner "Cover Art Designer not parsed correctly"
}
test "succeeds for \"Co-Writer\"" {
Expect.equal (PersonRole.Parse "Co-Writer") CoWriter "Co-Writer not parsed correctly"
}
test "succeeds for \"Creative Director\"" {
Expect.equal
(PersonRole.Parse "Creative Director") CreativeDirector "Creative Director not parsed correctly"
}
test "succeeds for \"Development Producer\"" {
Expect.equal
(PersonRole.Parse "Development Producer")
DevelopmentProducer
"Development Producer not parsed correctly"
}
test "succeeds for \"Director\"" {
Expect.equal (PersonRole.Parse "Director") Director "Director not parsed correctly"
}
test "succeeds for \"Editorial Director\"" {
Expect.equal
(PersonRole.Parse "Editorial Director") EditorialDirector "Editorial Director not parsed correctly"
}
test "succeeds for \"Executive Producer\"" {
Expect.equal
(PersonRole.Parse "Executive Producer") ExecutiveProducer "Executive Producer not parsed correctly"
}
test "succeeds for \"Fact Checker\"" {
Expect.equal (PersonRole.Parse "Fact Checker") FactChecker "Fact Checker not parsed correctly"
}
test "succeeds for \"Foley Artist\"" {
Expect.equal (PersonRole.Parse "Foley Artist") FoleyArtist "Foley Artist not parsed correctly"
}
test "succeeds for \"Graphic Designer\"" {
Expect.equal (PersonRole.Parse "Graphic Designer") GraphicDesigner "Graphic Designer not parsed correctly"
}
test "succeeds for \"Guest\"" {
Expect.equal (PersonRole.Parse "Guest") Guest "Guest not parsed correctly"
}
test "succeeds for \"Guest Host\"" {
Expect.equal (PersonRole.Parse "Guest Host") GuestHost "Guest Host not parsed correctly"
}
test "succeeds for \"Guest Writer\"" {
Expect.equal (PersonRole.Parse "Guest Writer") GuestWriter "Guest Writer not parsed correctly"
}
test "succeeds for \"Host\"" {
Expect.equal (PersonRole.Parse "Host") Host "Host not parsed correctly"
}
test "succeeds for \"Intern\"" {
Expect.equal (PersonRole.Parse "Intern") Intern "Intern not parsed correctly"
}
test "succeeds for \"Lighting Designer\"" {
Expect.equal
(PersonRole.Parse "Lighting Designer") LightingDesigner "Lighting Designer not parsed correctly"
}
test "succeeds for \"Logger\"" {
Expect.equal (PersonRole.Parse "Logger") Logger "Logger not parsed correctly"
}
test "succeeds for \"Managing Editor\"" {
Expect.equal (PersonRole.Parse "Managing Editor") ManagingEditor "Managing Editor not parsed correctly"
}
test "succeeds for \"Marketing Manager\"" {
Expect.equal
(PersonRole.Parse "Marketing Manager") MarketingManager "Marketing Manager not parsed correctly"
}
test "succeeds for \"Music Contributor\"" {
Expect.equal
(PersonRole.Parse "Music Contributor") MusicContributor "Music Contributor not parsed correctly"
}
test "succeeds for \"Music Production\"" {
Expect.equal (PersonRole.Parse "Music Production") MusicProduction "Music Production not parsed correctly"
}
test "succeeds for \"Narrator\"" {
Expect.equal (PersonRole.Parse "Narrator") Narrator "Narrator not parsed correctly"
}
test "succeeds for \"Post Production Engineer\"" {
Expect.equal
(PersonRole.Parse "Post Production Engineer")
PostProductionEngineer
"Post Production Engineer not parsed correctly"
}
test "succeeds for \"Producer\"" {
Expect.equal (PersonRole.Parse "Producer") Producer "Producer not parsed correctly"
}
test "succeeds for \"Production Assistant\"" {
Expect.equal
(PersonRole.Parse "Production Assistant")
ProductionAssistant
"Production Assistant not parsed correctly"
}
test "succeeds for \"Production Coordinator\"" {
Expect.equal
(PersonRole.Parse "Production Coordinator")
ProductionCoordinator
"Production Coordinator not parsed correctly"
}
test "succeeds for \"Remote Recording Engineer\"" {
Expect.equal
(PersonRole.Parse "Remote Recording Engineer")
RemoteRecordingEngineer
"Remote Recording Engineer not parsed correctly"
}
test "succeeds for \"Reporter\"" {
Expect.equal (PersonRole.Parse "Reporter") Reporter "Reporter not parsed correctly"
}
test "succeeds for \"Researcher\"" {
Expect.equal (PersonRole.Parse "Researcher") Researcher "Researcher not parsed correctly"
}
test "succeeds for \"Sales Manager\"" {
Expect.equal (PersonRole.Parse "Sales Manager") SalesManager "Sales Manager not parsed correctly"
}
test "succeeds for \"Sales Representative\"" {
Expect.equal
(PersonRole.Parse "Sales Representative")
SalesRepresentative
"Sales Representative not parsed correctly"
}
test "succeeds for \"Script Coordinator\"" {
Expect.equal
(PersonRole.Parse "Script Coordinator") ScriptCoordinator "Script Coordinator not parsed correctly"
}
test "succeeds for \"Script Editor\"" {
Expect.equal (PersonRole.Parse "Script Editor") ScriptEditor "Script Editor not parsed correctly"
}
test "succeeds for \"Senior Producer\"" {
Expect.equal (PersonRole.Parse "Senior Producer") SeniorProducer "Senior Producer not parsed correctly"
}
test "succeeds for \"Social Media Manager\"" {
Expect.equal
(PersonRole.Parse "Social Media Manager") SocialMediaManager "Social Media Manager not parsed correctly"
}
test "succeeds for \"Songwriter\"" {
Expect.equal (PersonRole.Parse "Songwriter") Songwriter "Songwriter not parsed correctly"
}
test "succeeds for \"Sound Designer\"" {
Expect.equal (PersonRole.Parse "Sound Designer") SoundDesigner "Sound Designer not parsed correctly"
}
test "succeeds for \"Story Editor\"" {
Expect.equal (PersonRole.Parse "Story Editor") StoryEditor "Story Editor not parsed correctly"
}
test "succeeds for \"Studio Coordinator\"" {
Expect.equal
(PersonRole.Parse "Studio Coordinator") StudioCoordinator "Studio Coordinator not parsed correctly"
}
test "succeeds for \"Technical Director\"" {
Expect.equal
(PersonRole.Parse "Technical Director") TechnicalDirector "Technical Director not parsed correctly"
}
test "succeeds for \"Technical Manager\"" {
Expect.equal
(PersonRole.Parse "Technical Manager") TechnicalManager "Technical Manager not parsed correctly"
}
test "succeeds for \"Theme Music\"" {
Expect.equal (PersonRole.Parse "Theme Music") ThemeMusic "Theme Music not parsed correctly"
}
test "succeeds for \"Transcriber\"" {
Expect.equal (PersonRole.Parse "Transcriber") Transcriber "Transcriber not parsed correctly"
}
test "succeeds for \"Translator\"" {
Expect.equal (PersonRole.Parse "Translator") Translator "Translator not parsed correctly"
}
test "succeeds for \"Editor (Video)\"" {
Expect.equal (PersonRole.Parse "Editor (Video)") VideoEditor "Editor (Video) not parsed correctly"
}
test "succeeds for \"Voice Actor\"" {
Expect.equal (PersonRole.Parse "Voice Actor") VoiceActor "Voice Actor not parsed correctly"
}
test "succeeds for \"Writer\"" {
Expect.equal (PersonRole.Parse "Writer") Writer "Writer not parsed correctly"
}
test "succeeds for \"Editor (Writing)\"" {
Expect.equal (PersonRole.Parse "Editor (Writing)") WritingEditor "Editor (Writing) not parsed correctly"
}
test "fails for unrecognized value" {
Expect.throwsT<ArgumentException>
(fun () -> ignore (PersonRole.Parse "Gaffer")) "Invalid value should have raised an exception"
}
]
testList "ToString" [
test "succeeds for Announcer" {
Expect.equal (string Announcer) "Announcer" "Announcer string incorrect"
}
test "succeeds for Assistant Camera" {
Expect.equal (string AssistantCamera) "Assistant Camera" "Assistant Camera string incorrect"
}
test "succeeds for Assistant Director" {
Expect.equal (string AssistantDirector) "Assistant Director" "Assistant Director string incorrect"
}
test "succeeds for Assistant Editor" {
Expect.equal (string AssistantEditor) "Assistant Editor" "Assistant Editor string incorrect"
}
test "succeeds for Associate Producer" {
Expect.equal (string AssociateProducer) "Associate Producer" "Associate Producer string incorrect"
}
test "succeeds for Audio Editor" {
Expect.equal (string AudioEditor) "Audio Editor" "Audio Editor string incorrect"
}
test "succeeds for Audio Engineer" {
Expect.equal (string AudioEngineer) "Audio Engineer" "Audio Engineer string incorrect"
}
test "succeeds for Author" {
Expect.equal (string Author) "Author" "Author string incorrect"
}
test "succeeds for Booking Coordinator" {
Expect.equal (string BookingCoordinator) "Booking Coordinator" "Booking Coordinator string incorrect"
}
test "succeeds for Camera Grip" {
Expect.equal (string CameraGrip) "Camera Grip" "Camera Grip string incorrect"
}
test "succeeds for Camera Operator" {
Expect.equal (string CameraOperator) "Camera Operator" "Camera Operator string incorrect"
}
test "succeeds for Co-Host" {
Expect.equal (string CoHost) "Co-Host" "Co-Host string incorrect"
}
test "succeeds for Composer" {
Expect.equal (string Composer) "Composer" "Composer string incorrect"
}
test "succeeds for Consultant" {
Expect.equal (string Consultant) "Consultant" "Consultant string incorrect"
}
test "succeeds for Content Manager" {
Expect.equal (string ContentManager) "Content Manager" "Content Manager string incorrect"
}
test "succeeds for Cover Art Designer" {
Expect.equal (string CoverArtDesigner) "Cover Art Designer" "Cover Art Designer string incorrect"
}
test "succeeds for Co-Writer" {
Expect.equal (string CoWriter) "Co-Writer" "Co-Writer string incorrect"
}
test "succeeds for Creative Director" {
Expect.equal (string CreativeDirector) "Creative Director" "Creative Director string incorrect"
}
test "succeeds for Development Producer" {
Expect.equal (string DevelopmentProducer) "Development Producer" "Development Producer string incorrect"
}
test "succeeds for Director" {
Expect.equal (string Director) "Director" "Director string incorrect"
}
test "succeeds for Editorial Director" {
Expect.equal (string EditorialDirector) "Editorial Director" "Editorial Director string incorrect"
}
test "succeeds for Executive Producer" {
Expect.equal (string ExecutiveProducer) "Executive Producer" "Executive Producer string incorrect"
}
test "succeeds for Fact Checker" {
Expect.equal (string FactChecker) "Fact Checker" "Fact Checker string incorrect"
}
test "succeeds for Foley Artist" {
Expect.equal (string FoleyArtist) "Foley Artist" "Foley Artist string incorrect"
}
test "succeeds for Graphic Designer" {
Expect.equal (string GraphicDesigner) "Graphic Designer" "Graphic Designer string incorrect"
}
test "succeeds for Guest" {
Expect.equal (string Guest) "Guest" "Guest string incorrect"
}
test "succeeds for Guest Host" {
Expect.equal (string GuestHost) "Guest Host" "Guest Host string incorrect"
}
test "succeeds for Guest Writer" {
Expect.equal (string GuestWriter) "Guest Writer" "Guest Writer string incorrect"
}
test "succeeds for Host" {
Expect.equal (string Host) "Host" "Host string incorrect"
}
test "succeeds for Intern" {
Expect.equal (string Intern) "Intern" "Intern string incorrect"
}
test "succeeds for Lighting Designer" {
Expect.equal (string LightingDesigner) "Lighting Designer" "Lighting Designer string incorrect"
}
test "succeeds for Logger" {
Expect.equal (string Logger) "Logger" "Logger string incorrect"
}
test "succeeds for Managing Editor" {
Expect.equal (string ManagingEditor) "Managing Editor" "Managing Editor string incorrect"
}
test "succeeds for Marketing Manager" {
Expect.equal (string MarketingManager) "Marketing Manager" "Marketing Manager string incorrect"
}
test "succeeds for Music Contributor" {
Expect.equal (string MusicContributor) "Music Contributor" "Music Contributor string incorrect"
}
test "succeeds for Music Production" {
Expect.equal (string MusicProduction) "Music Production" "Music Production string incorrect"
}
test "succeeds for Narrator" {
Expect.equal (string Narrator) "Narrator" "Narrator string incorrect"
}
test "succeeds for Post Production Engineer" {
Expect.equal
(string PostProductionEngineer) "Post Production Engineer" "Post Production Engineer string incorrect"
}
test "succeeds for Producer" {
Expect.equal (string Producer) "Producer" "Producer string incorrect"
}
test "succeeds for Production Assistant" {
Expect.equal (string ProductionAssistant) "Production Assistant" "Production Assistant string incorrect"
}
test "succeeds for Production Coordinator" {
Expect.equal
(string ProductionCoordinator) "Production Coordinator" "Production Coordinator string incorrect"
}
test "succeeds for Remote Recording Engineer" {
Expect.equal
(string RemoteRecordingEngineer)
"Remote Recording Engineer"
"Remote Recording Engineer string incorrect"
}
test "succeeds for Reporter" {
Expect.equal (string Reporter) "Reporter" "Reporter string incorrect"
}
test "succeeds for Researcher" {
Expect.equal (string Researcher) "Researcher" "Researcher string incorrect"
}
test "succeeds for Sales Manager" {
Expect.equal (string SalesManager) "Sales Manager" "Sales Manager string incorrect"
}
test "succeeds for Sales Representative" {
Expect.equal (string SalesRepresentative) "Sales Representative" "Sales Representative string incorrect"
}
test "succeeds for Script Coordinator" {
Expect.equal (string ScriptCoordinator) "Script Coordinator" "Script Coordinator string incorrect"
}
test "succeeds for Script Editor" {
Expect.equal (string ScriptEditor) "Script Editor" "Script Editor string incorrect"
}
test "succeeds for Senior Producer" {
Expect.equal (string SeniorProducer) "Senior Producer" "Senior Producer string incorrect"
}
test "succeeds for Social Media Manager" {
Expect.equal (string SocialMediaManager) "Social Media Manager" "Social Media Manager string incorrect"
}
test "succeeds for Songwriter" {
Expect.equal (string Songwriter) "Songwriter" "Songwriter string incorrect"
}
test "succeeds for Sound Designer" {
Expect.equal (string SoundDesigner) "Sound Designer" "Sound Designer string incorrect"
}
test "succeeds for Story Editor" {
Expect.equal (string StoryEditor) "Story Editor" "Story Editor string incorrect"
}
test "succeeds for Studio Coordinator" {
Expect.equal (string StudioCoordinator) "Studio Coordinator" "Studio Coordinator string incorrect"
}
test "succeeds for Technical Director" {
Expect.equal (string TechnicalDirector) "Technical Director" "Technical Director string incorrect"
}
test "succeeds for Technical Manager" {
Expect.equal (string TechnicalManager) "Technical Manager" "Technical Manager string incorrect"
}
test "succeeds for Theme Music" {
Expect.equal (string ThemeMusic) "Theme Music" "Theme Music string incorrect"
}
test "succeeds for Transcriber" {
Expect.equal (string Transcriber) "Transcriber" "Transcriber string incorrect"
}
test "succeeds for Translator" {
Expect.equal (string Translator) "Translator" "Translator string incorrect"
}
test "succeeds for Video Editor" {
Expect.equal (string VideoEditor) "Editor (Video)" "Video Editor string incorrect"
}
test "succeeds for Voice Actor" {
Expect.equal (string VoiceActor) "Voice Actor" "Voice Editor string incorrect"
}
test "succeeds for Writer" {
Expect.equal (string Writer) "Writer" "Writer string incorrect"
}
test "succeeds for Writing Editor" {
Expect.equal (string WritingEditor) "Editor (Writing)" "Writing Editor string incorrect"
}
]
test "Role / Group Xref has all entries" {
Expect.hasLength PersonRole.GroupXref 63 "There should be 63 roles in the role / group xref list"
}
test "all values round-trip between ToString and Parse" {
PersonRole.GroupXref
|> List.map fst
|> List.iter (fun it ->
Expect.equal (it |> string |> PersonRole.Parse) it "The value did not round-trip correctly")
}
test "ToDisplay succeeds for all values" {
PersonRole.GroupXref
|> List.map fst
|> List.iter (fun it ->
match it with
| VideoEditor | WritingEditor ->
Expect.equal (it.ToDisplay()) "Editor" "The display string should be \"Editor\""
| _ -> Expect.equal (it.ToDisplay()) (string it) "The display string should be its string value")
}
]
/// Unit tests for the PodcastMedium type
let podcastMediumTests = testList "PodcastMedium" [
testList "Parse" [
test "succeeds for \"podcast\"" {
Expect.equal (PodcastMedium.Parse "podcast") Podcast "\"podcast\" not parsed correctly"
}
test "succeeds for \"music\"" {
Expect.equal (PodcastMedium.Parse "music") Music "\"music\" not parsed correctly"
}
test "succeeds for \"video\"" {
Expect.equal (PodcastMedium.Parse "video") Video "\"video\" not parsed correctly"
}
test "succeeds for \"film\"" {
Expect.equal (PodcastMedium.Parse "film") Film "\"film\" not parsed correctly"
}
test "succeeds for \"audiobook\"" {
Expect.equal (PodcastMedium.Parse "audiobook") Audiobook "\"audiobook\" not parsed correctly"
}
test "succeeds for \"newsletter\"" {
Expect.equal (PodcastMedium.Parse "newsletter") Newsletter "\"newsletter\" not parsed correctly"
}
test "succeeds for \"blog\"" {
Expect.equal (PodcastMedium.Parse "blog") Blog "\"blog\" not parsed correctly"
}
test "fails for invalid type" {
Expect.throwsT<ArgumentException>
(fun () -> ignore (PodcastMedium.Parse "laser")) "Invalid value should have raised an exception"
}
]
testList "ToString" [
test "succeeds for Podcast" {
Expect.equal (string Podcast) "podcast" "Podcast string incorrect"
}
test "succeeds for Music" {
Expect.equal (string Music) "music" "Music string incorrect"
}
test "succeeds for Video" {
Expect.equal (string Video) "video" "Video string incorrect"
}
test "succeeds for Film" {
Expect.equal (string Film) "film" "Film string incorrect"
}
test "succeeds for Audiobook" {
Expect.equal (string Audiobook) "audiobook" "Audiobook string incorrect"
}
test "succeeds for Newsletter" {
Expect.equal (string Newsletter) "newsletter" "Newsletter string incorrect"
}
test "succeeds for Blog" {
Expect.equal (string Blog) "blog" "Blog string incorrect"
}
]
]
/// All tests for the Domain.PodcastTypes file
let all = testList "SupportTypes" [
explicitRatingTests
episodeTests
personGroupTests
personRoleTests
podcastMediumTests
]
@@ -173,57 +173,6 @@ let commentStatusTests = testList "CommentStatus" [
] ]
] ]
/// Tests for the ExplicitRating type
let explicitRatingTests = testList "ExplicitRating" [
testList "Parse" [
test "succeeds for \"yes\"" {
Expect.equal (ExplicitRating.Parse "yes") Yes "\"yes\" not parsed correctly"
}
test "succeeds for \"no\"" {
Expect.equal (ExplicitRating.Parse "no") No "\"no\" not parsed correctly"
}
test "succeeds for \"clean\"" {
Expect.equal (ExplicitRating.Parse "clean") Clean "\"clean\" not parsed correctly"
}
test "fails for unrecognized value" {
Expect.throwsT<ArgumentException>
(fun () -> ignore (ExplicitRating.Parse "maybe")) "Invalid value should have raised an exception"
}
]
testList "ToString" [
test "Yes succeeds" {
Expect.equal (string Yes) "yes" "Yes string incorrect"
}
test "No succeeds" {
Expect.equal (string No) "no" "No string incorrect"
}
test "Clean succeeds" {
Expect.equal (string Clean) "clean" "Clean string incorrect"
}
]
]
/// Tests for the Episode type
let episodeTests = testList "Episode" [
testList "FormatDuration" [
test "succeeds when no duration is specified" {
Expect.isNone (Episode.Empty.FormatDuration()) "A missing duration should have returned None"
}
test "succeeds when duration is specified" {
Expect.equal
({ Episode.Empty with
Duration = Some (Duration.FromMinutes 3L + Duration.FromSeconds 13L) }.FormatDuration())
(Some "0:03:13")
"Duration not formatted correctly"
}
test "succeeds when duration is > 10 hours" {
Expect.equal
({ Episode.Empty with Duration = Some (Duration.FromHours 11) }.FormatDuration())
(Some "11:00:00")
"Duration not formatted correctly"
}
]
]
/// Unit tests for the MarkupText type /// Unit tests for the MarkupText type
let markupTextTests = testList "MarkupText" [ let markupTextTests = testList "MarkupText" [
@@ -684,147 +633,6 @@ let openGraphPropertiesTests = testList "OpenGraphProperties" [
] ]
] ]
/// Tests for the PersonGroup type
let personGroupTests = testList "PersonGroup" [
testList "Parse" [
test "succeeds for \"Administration\"" {
Expect.equal (PersonGroup.Parse "Administration") Administration "Administration not parsed correctly"
}
test "succeeds for \"Audio Post-Production\"" {
Expect.equal
(PersonGroup.Parse "Audio Post-Production")
AudioPostProduction
"Audio Post-Production not parsed correctly"
}
test "succeeds for \"Audio Production\"" {
Expect.equal
(PersonGroup.Parse "Audio Production") AudioProduction "Audio Production not parsed correctly"
}
test "succeeds for \"Cast\"" {
Expect.equal (PersonGroup.Parse "Cast") Cast "Cast not parsed correctly"
}
test "succeeds for \"Community\"" {
Expect.equal (PersonGroup.Parse "Community") Community "Community not parsed correctly"
}
test "succeeds for \"Creative Direction\"" {
Expect.equal
(PersonGroup.Parse "Creative Direction") CreativeDirection "Creative Direction not parsed correctly"
}
test "succeeds for \"Misc.\"" {
Expect.equal (PersonGroup.Parse "Misc.") Misc "Misc. not parsed correctly"
}
test "succeeds for \"Video Post-Production\"" {
Expect.equal
(PersonGroup.Parse "Video Post-Production")
VideoPostProduction
"Video Post-Production not parsed correctly"
}
test "succeeds for \"Video Production\"" {
Expect.equal
(PersonGroup.Parse "Video Production") VideoProduction "Video Production not parsed correctly"
}
test "succeeds for \"Visuals\"" {
Expect.equal (PersonGroup.Parse "Visuals") Visuals "Visuals not parsed correctly"
}
test "succeeds for \"Writing\"" {
Expect.equal (PersonGroup.Parse "Writing") Writing "Writing not parsed correctly"
}
test "fails for unrecognized value" {
Expect.throwsT<ArgumentException>
(fun () -> ignore (PersonGroup.Parse "Fan")) "Invalid value should have raised an exception"
}
]
testList "ToString" [
test "Administration succeeds" {
Expect.equal (string Administration) "Administration" "Administration string incorrect"
}
test "Audio Post-Production succeeds" {
Expect.equal (string AudioPostProduction) "Audio Post-Production" "Audio Post-Production string incorrect"
}
test "Audio Production succeeds" {
Expect.equal (string AudioProduction) "Audio Production" "Audio Production string incorrect"
}
test "Cast succeeds" {
Expect.equal (string Cast) "Cast" "Cast string incorrect"
}
test "Community succeeds" {
Expect.equal (string Community) "Community" "Community string incorrect"
}
test "Creative Direction succeeds" {
Expect.equal (string CreativeDirection) "Creative Direction" "Creative Direction string incorrect"
}
test "Misc succeeds" {
Expect.equal (string Misc) "Misc." "Misc string incorrect"
}
test "Video Post-Production succeeds" {
Expect.equal (string VideoPostProduction) "Video Post-Production" "Video Post-Production string incorrect"
}
test "Video Production succeeds" {
Expect.equal (string VideoProduction) "Video Production" "Video Production string incorrect"
}
test "Visuals succeeds" {
Expect.equal (string Visuals) "Visuals" "Visuals string incorrect"
}
test "Writing succeeds" {
Expect.equal (string Writing) "Writing" "Writing string incorrect"
}
]
]
/// Unit tests for the PodcastMedium type
let podcastMediumTests = testList "PodcastMedium" [
testList "Parse" [
test "succeeds for \"podcast\"" {
Expect.equal (PodcastMedium.Parse "podcast") Podcast "\"podcast\" not parsed correctly"
}
test "succeeds for \"music\"" {
Expect.equal (PodcastMedium.Parse "music") Music "\"music\" not parsed correctly"
}
test "succeeds for \"video\"" {
Expect.equal (PodcastMedium.Parse "video") Video "\"video\" not parsed correctly"
}
test "succeeds for \"film\"" {
Expect.equal (PodcastMedium.Parse "film") Film "\"film\" not parsed correctly"
}
test "succeeds for \"audiobook\"" {
Expect.equal (PodcastMedium.Parse "audiobook") Audiobook "\"audiobook\" not parsed correctly"
}
test "succeeds for \"newsletter\"" {
Expect.equal (PodcastMedium.Parse "newsletter") Newsletter "\"newsletter\" not parsed correctly"
}
test "succeeds for \"blog\"" {
Expect.equal (PodcastMedium.Parse "blog") Blog "\"blog\" not parsed correctly"
}
test "fails for invalid type" {
Expect.throwsT<ArgumentException>
(fun () -> ignore (PodcastMedium.Parse "laser")) "Invalid value should have raised an exception"
}
]
testList "ToString" [
test "succeeds for Podcast" {
Expect.equal (string Podcast) "podcast" "Podcast string incorrect"
}
test "succeeds for Music" {
Expect.equal (string Music) "music" "Music string incorrect"
}
test "succeeds for Video" {
Expect.equal (string Video) "video" "Video string incorrect"
}
test "succeeds for Film" {
Expect.equal (string Film) "film" "Film string incorrect"
}
test "succeeds for Audiobook" {
Expect.equal (string Audiobook) "audiobook" "Audiobook string incorrect"
}
test "succeeds for Newsletter" {
Expect.equal (string Newsletter) "newsletter" "Newsletter string incorrect"
}
test "succeeds for Blog" {
Expect.equal (string Blog) "blog" "Blog string incorrect"
}
]
]
/// Unit tests for the PostStatus type /// Unit tests for the PostStatus type
let postStatusTests = testList "PostStatus" [ let postStatusTests = testList "PostStatus" [
testList "Parse" [ testList "Parse" [
@@ -919,16 +727,12 @@ let all = testList "SupportTypes" [
accessLevelTests accessLevelTests
autoHtmxTypeTests autoHtmxTypeTests
commentStatusTests commentStatusTests
explicitRatingTests
episodeTests
markupTextTests markupTextTests
openGraphAudioTests openGraphAudioTests
openGraphImageTests openGraphImageTests
openGraphVideoTests openGraphVideoTests
openGraphTypeTests openGraphTypeTests
openGraphPropertiesTests openGraphPropertiesTests
personGroupTests
podcastMediumTests
postStatusTests postStatusTests
customFeedSourceTests customFeedSourceTests
themeAssetIdTests themeAssetIdTests
+1
View File
@@ -5,6 +5,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Domain\PodcastTypesTests.fs" />
<Compile Include="Domain\SupportTypesTests.fs" /> <Compile Include="Domain\SupportTypesTests.fs" />
<Compile Include="Domain\DataTypesTests.fs" /> <Compile Include="Domain\DataTypesTests.fs" />
<Compile Include="Domain\ViewModelsTests.fs" /> <Compile Include="Domain\ViewModelsTests.fs" />
+1 -1
View File
@@ -15,7 +15,7 @@ let allDatabases = not (rethinkOnly || sqliteOnly || postgresOnly)
let allTests = testList "MyWebLog" [ let allTests = testList "MyWebLog" [
// Skip unit tests if running an isolated database test // Skip unit tests if running an isolated database test
if allDatabases then if allDatabases then
testList "Domain" [ SupportTypesTests.all; DataTypesTests.all; ViewModelsTests.all ] testList "Domain" [ PodcastTypesTests.all; SupportTypesTests.all; DataTypesTests.all; ViewModelsTests.all ]
testList "Data (Unit)" [ ConvertersTests.all; UtilsTests.all ] testList "Data (Unit)" [ ConvertersTests.all; UtilsTests.all ]
// Whether to skip integration tests // Whether to skip integration tests
if RethinkDbDataTests.env "UNIT_ONLY" "0" <> "1" then if RethinkDbDataTests.env "UNIT_ONLY" "0" <> "1" then