Convert module funcs to ToString/Parse

This commit is contained in:
Daniel J. Summers 2025-01-30 19:22:45 -05:00
parent 5240b78487
commit facc294d66
9 changed files with 498 additions and 465 deletions

View File

@ -36,9 +36,9 @@ module private Helpers =
IsPublic = row.bool "is_public"
PageSize = row.int "page_size"
TimeZoneId = TimeZoneId (row.string "time_zone_id")
RequestSort = RequestSort.fromCode (row.string "request_sort")
DefaultEmailType = EmailFormat.fromCode (row.string "default_email_type")
AsOfDateDisplay = AsOfDateDisplay.fromCode (row.string "as_of_date_display")
RequestSort = RequestSort.Parse (row.string "request_sort")
DefaultEmailType = EmailFormat.Parse (row.string "default_email_type")
AsOfDateDisplay = AsOfDateDisplay.Parse (row.string "as_of_date_display")
}
/// Map a row to a Member instance
@ -47,7 +47,7 @@ module private Helpers =
SmallGroupId = SmallGroupId (row.uuid "small_group_id")
Name = row.string "member_name"
Email = row.string "email"
Format = row.stringOrNone "email_format" |> Option.map EmailFormat.fromCode
Format = row.stringOrNone "email_format" |> Option.map EmailFormat.Parse
}
/// Map a row to a Prayer Request instance
@ -60,8 +60,8 @@ module private Helpers =
Requestor = row.stringOrNone "requestor"
Text = row.string "request_text"
NotifyChaplain = row.bool "notify_chaplain"
RequestType = PrayerRequestType.fromCode (row.string "request_type")
Expiration = Expiration.fromCode (row.string "expiration")
RequestType = PrayerRequestType.Parse (row.string "request_type")
Expiration = Expiration.Parse (row.string "expiration")
}
/// Map a row to a Small Group instance
@ -185,7 +185,7 @@ module Members =
"@groupId", Sql.uuid mbr.SmallGroupId.Value
"@name", Sql.string mbr.Name
"@email", Sql.string mbr.Email
"@format", Sql.stringOrNone (mbr.Format |> Option.map EmailFormat.toCode) ]
"@format", Sql.stringOrNone (mbr.Format |> Option.map string) ]
/// Retrieve a small group member by its ID
let tryById (memberId : MemberId) =
@ -257,10 +257,10 @@ module PrayerRequests =
OR request_type = @expecting)
AND expiration <> @forced",
[ "@asOf", Sql.parameter asOf
"@manual", Sql.string (Expiration.toCode Manual)
"@longTerm", Sql.string (PrayerRequestType.toCode LongTermRequest)
"@expecting", Sql.string (PrayerRequestType.toCode Expecting)
"@forced", Sql.string (Expiration.toCode Forced) ]
"@manual", Sql.string (string Manual)
"@longTerm", Sql.string (string LongTermRequest)
"@expecting", Sql.string (string Expecting)
"@forced", Sql.string (string Forced) ]
else "", []
Custom.list
$"SELECT *
@ -287,7 +287,7 @@ module PrayerRequests =
notify_chaplain = EXCLUDED.notify_chaplain,
expiration = EXCLUDED.expiration"
[ "@id", Sql.uuid req.Id.Value
"@type", Sql.string (PrayerRequestType.toCode req.RequestType)
"@type", Sql.string (string req.RequestType)
"@userId", Sql.uuid req.UserId.Value
"@groupId", Sql.uuid req.SmallGroupId.Value
"@entered", Sql.parameter (NpgsqlParameter("@entered", req.EnteredDate))
@ -295,7 +295,7 @@ module PrayerRequests =
"@requestor", Sql.stringOrNone req.Requestor
"@text", Sql.string req.Text
"@notifyChaplain", Sql.bool req.NotifyChaplain
"@expiration", Sql.string (Expiration.toCode req.Expiration) ]
"@expiration", Sql.string (string req.Expiration) ]
/// Search prayer requests for the given term
let searchForGroup group searchTerm pageNbr =
@ -320,8 +320,7 @@ module PrayerRequests =
[ "@updated", Sql.parameter (NpgsqlParameter ("@updated", req.UpdatedDate)) ]
else "", []
Custom.nonQuery $"UPDATE pt.prayer_request SET expiration = @expiration{sql} WHERE id = @id"
([ "@expiration", Sql.string (Expiration.toCode req.Expiration)
"@id", Sql.uuid req.Id.Value ]
([ "@expiration", Sql.string (string req.Expiration); "@id", Sql.uuid req.Id.Value ]
|> List.append parameters)
@ -455,13 +454,13 @@ module SmallGroups =
"@lineColor", Sql.string pref.LineColor
"@headingFontSize", Sql.int pref.HeadingFontSize
"@textFontSize", Sql.int pref.TextFontSize
"@requestSort", Sql.string (RequestSort.toCode pref.RequestSort)
"@requestSort", Sql.string (string pref.RequestSort)
"@groupPassword", Sql.string pref.GroupPassword
"@defaultEmailType", Sql.string (EmailFormat.toCode pref.DefaultEmailType)
"@defaultEmailType", Sql.string (string pref.DefaultEmailType)
"@isPublic", Sql.bool pref.IsPublic
"@timeZoneId", Sql.string (TimeZoneId.toString pref.TimeZoneId)
"@pageSize", Sql.int pref.PageSize
"@asOfDateDisplay", Sql.string (AsOfDateDisplay.toCode pref.AsOfDateDisplay) ]
"@asOfDateDisplay", Sql.string (string pref.AsOfDateDisplay) ]
/// Get a small group by its ID
let tryById (groupId : SmallGroupId) =

View File

@ -11,20 +11,21 @@ type AsOfDateDisplay =
/// The as-of date should be displayed in the culture's long date format
| LongDate
/// Functions to support as-of date display options
module AsOfDateDisplay =
/// Convert this to a single-character code
override this.ToString() =
match this with
| NoDisplay -> "N"
| ShortDate -> "S"
| LongDate -> "L"
/// Convert to a DU case from a single-character string
let fromCode code =
/// <summary>Create an <c>AsOfDateDisplay</c> from a single-character code</summary>
static member Parse code =
match code with
| "N" -> NoDisplay
| "S" -> ShortDate
| "L" -> LongDate
| _ -> invalidArg "code" $"Unknown code {code}"
/// Convert this DU case to a single-character string
let toCode = function NoDisplay -> "N" | ShortDate -> "S" | LongDate -> "L"
/// Acceptable e-mail formats
type EmailFormat =
@ -33,19 +34,19 @@ type EmailFormat =
/// Plain-text e-mail
| PlainTextFormat
/// Functions to support e-mail formats
module EmailFormat =
/// Convert this to a single-character code
override this.ToString() =
match this with
| HtmlFormat -> "H"
| PlainTextFormat -> "P"
/// Convert to a DU case from a single-character string
let fromCode code =
/// <summary>Create an <c>EmailFormat</c> from a single-character code</summary>
static member Parse code =
match code with
| "H" -> HtmlFormat
| "P" -> PlainTextFormat
| _ -> invalidArg "code" $"Unknown code {code}"
/// Convert this DU case to a single-character string
let toCode = function HtmlFormat -> "H" | PlainTextFormat -> "P"
/// Expiration for requests
type Expiration =
@ -56,20 +57,21 @@ type Expiration =
/// Force immediate expiration
| Forced
/// Functions to support expirations
module Expiration =
/// Convert this to a single-character code
override this.ToString() =
match this with
| Automatic -> "A"
| Manual -> "M"
| Forced -> "F"
/// Convert to a DU case from a single-character string
let fromCode code =
/// <summary>Create an <c>Expiration</c> from a single-character code</summary>
static member Parse code =
match code with
| "A" -> Automatic
| "M" -> Manual
| "F" -> Forced
| _ -> invalidArg "code" $"Unknown code {code}"
/// Convert this DU case to a single-character string
let toCode = function Automatic -> "A" | Manual -> "M" | Forced -> "F"
/// Types of prayer requests
type PrayerRequestType =
@ -84,11 +86,17 @@ type PrayerRequestType =
/// Announcements
| Announcement
/// Functions to support prayer request types
module PrayerRequestType =
/// Convert this to a single-character code
override this.ToString() =
match this with
| CurrentRequest -> "C"
| LongTermRequest -> "L"
| Expecting -> "E"
| PraiseReport -> "P"
| Announcement -> "A"
/// Convert to a DU case from a single-character string
let fromCode code =
/// <summary>Create a <c>PrayerRequestType</c> from a single-character code</summary>
static member Parse code =
match code with
| "C" -> CurrentRequest
| "L" -> LongTermRequest
@ -97,15 +105,6 @@ module PrayerRequestType =
| "A" -> Announcement
| _ -> invalidArg "code" $"Unknown code {code}"
/// Convert this DU case to a single-character string
let toCode =
function
| CurrentRequest -> "C"
| LongTermRequest -> "L"
| Expecting -> "E"
| PraiseReport -> "P"
| Announcement -> "A"
/// How requests should be sorted
type RequestSort =
@ -114,52 +113,64 @@ type RequestSort =
/// Sort by requestor/subject, then by date
| SortByRequestor
/// Functions to support request sorts
module RequestSort =
/// Convert this to a single-character code
override this.ToString() =
match this with
| SortByDate -> "D"
| SortByRequestor -> "R"
/// Convert to a DU case from a single-character string
let fromCode code =
/// <summary>Create a <c>RequestSort</c> from a single-character code</summary>
static member Parse code =
match code with
| "D" -> SortByDate
| "R" -> SortByRequestor
| _ -> invalidArg "code" $"Unknown code {code}"
/// Convert this DU case to a single-character string
let toCode = function SortByDate -> "D" | SortByRequestor -> "R"
open System
/// PK type for the Church entity
type ChurchId =
| ChurchId of Guid
with
/// The GUID value of the church ID
member this.Value = this |> function ChurchId guid -> guid
member this.Value =
this
|> function
| ChurchId guid -> guid
/// PK type for the Member entity
type MemberId =
| MemberId of Guid
with
/// The GUID value of the member ID
member this.Value = this |> function MemberId guid -> guid
member this.Value =
this
|> function
| MemberId guid -> guid
/// PK type for the PrayerRequest entity
type PrayerRequestId =
| PrayerRequestId of Guid
with
/// The GUID value of the prayer request ID
member this.Value = this |> function PrayerRequestId guid -> guid
member this.Value =
this
|> function
| PrayerRequestId guid -> guid
/// PK type for the SmallGroup entity
type SmallGroupId =
| SmallGroupId of Guid
with
/// The GUID value of the small group ID
member this.Value = this |> function SmallGroupId guid -> guid
member this.Value =
this
|> function
| SmallGroupId guid -> guid
/// PK type for the TimeZone entity
@ -169,22 +180,28 @@ type TimeZoneId = TimeZoneId of string
module TimeZoneId =
/// Convert a time zone ID to its string value
let toString = function TimeZoneId it -> it
let toString =
function
| TimeZoneId it -> it
/// PK type for the User entity
type UserId =
| UserId of Guid
with
/// The GUID value of the user ID
member this.Value = this |> function UserId guid -> guid
member this.Value =
this
|> function
| UserId guid -> guid
(*-- SPECIFIC VIEW TYPES --*)
/// Statistics for churches
[<NoComparison; NoEquality>]
type ChurchStats =
{ /// The number of small groups in the church
{
/// The number of small groups in the church
SmallGroups: int
/// The number of prayer requests in the church
@ -198,7 +215,8 @@ type ChurchStats =
/// Information needed to display the public/protected request list and small group maintenance pages
[<NoComparison; NoEquality>]
type SmallGroupInfo =
{ /// The ID of the small group
{
/// The ID of the small group
Id: string
/// The name of the small group
@ -221,7 +239,8 @@ open NodaTime
/// This represents a church
[<NoComparison; NoEquality>]
type Church =
{ /// The ID of this church
{
/// The ID of this church
Id: ChurchId
/// The name of the church
@ -251,14 +270,14 @@ module Church =
City = ""
State = ""
HasVpsInterface = false
InterfaceAddress = None
}
InterfaceAddress = None }
/// Preferences for the form and format of the prayer request list
[<NoComparison; NoEquality>]
type ListPreferences =
{ /// The Id of the small group to which these preferences belong
{
/// The Id of the small group to which these preferences belong
SmallGroupId: SmallGroupId
/// The days after which regular requests expire
@ -312,13 +331,13 @@ type ListPreferences =
/// How the as-of date should be automatically displayed
AsOfDateDisplay: AsOfDateDisplay
}
with
/// The list of fonts to use when displaying request lists (converts "native" to native font stack)
member this.FontStack =
if this.Fonts = "native" then
"""system-ui,-apple-system,"Segoe UI",Roboto,Ubuntu,"Liberation Sans",Cantarell,"Helvetica Neue",sans-serif"""
else this.Fonts
else
this.Fonts
/// Functions to support list preferences
module ListPreferences =
@ -342,14 +361,14 @@ module ListPreferences =
IsPublic = false
TimeZoneId = TimeZoneId "America/Denver"
PageSize = 100
AsOfDateDisplay = NoDisplay
}
AsOfDateDisplay = NoDisplay }
/// A member of a small group
[<NoComparison; NoEquality>]
type Member =
{ /// The ID of the small group member
{
/// The ID of the small group member
Id: MemberId
/// The Id of the small group to which this member belongs
@ -374,14 +393,14 @@ module Member =
SmallGroupId = SmallGroupId Guid.Empty
Name = ""
Email = ""
Format = None
}
Format = None }
/// This represents a single prayer request
[<NoComparison; NoEquality>]
type PrayerRequest =
{ /// The ID of this request
{
/// The ID of this request
Id: PrayerRequestId
/// The type of the request
@ -417,7 +436,8 @@ type PrayerRequest =
/// This represents a small group (Sunday School class, Bible study group, etc.)
[<NoComparison; NoEquality>]
type SmallGroup =
{ /// The ID of this small group
{
/// The ID of this small group
Id: SmallGroupId
/// The church to which this group belongs
@ -438,23 +458,26 @@ module SmallGroup =
{ Id = SmallGroupId Guid.Empty
ChurchId = ChurchId Guid.Empty
Name = ""
Preferences = ListPreferences.empty
}
Preferences = ListPreferences.empty }
/// The DateTimeZone for the time zone ID for this small group
let timeZone group =
let tzId = TimeZoneId.toString group.Preferences.TimeZoneId
if DateTimeZoneProviders.Tzdb.Ids.Contains tzId then DateTimeZoneProviders.Tzdb[tzId]
else DateTimeZone.Utc
if DateTimeZoneProviders.Tzdb.Ids.Contains tzId then
DateTimeZoneProviders.Tzdb[tzId]
else
DateTimeZone.Utc
/// Get the local date/time for this group
let localTimeNow (clock: IClock) group =
if isNull clock then nullArg (nameof clock)
if isNull clock then
nullArg (nameof clock)
clock.GetCurrentInstant().InZone(timeZone group).LocalDateTime
/// Get the local date for this group
let localDateNow clock group =
(localTimeNow clock group).Date
let localDateNow clock group = (localTimeNow clock group).Date
/// Functions to support prayer requests
@ -471,8 +494,7 @@ module PrayerRequest =
Requestor = None
Text = ""
NotifyChaplain = false
Expiration = Automatic
}
Expiration = Automatic }
/// Is this request expired?
let isExpired (asOf: LocalDate) group req =
@ -483,20 +505,25 @@ module PrayerRequest =
| Automatic, Expecting -> false
| Automatic, _ ->
// Automatic expiration
Period.Between(req.UpdatedDate.InZone(SmallGroup.timeZone group).Date, asOf, PeriodUnits.Days).Days
Period
.Between(req.UpdatedDate.InZone(SmallGroup.timeZone group).Date, asOf, PeriodUnits.Days)
.Days
>= group.Preferences.DaysToExpire
/// Is an update required for this long-term request?
let updateRequired asOf group req =
if isExpired asOf group req then false
else asOf.PlusWeeks -group.Preferences.LongTermUpdateWeeks
if isExpired asOf group req then
false
else
asOf.PlusWeeks -group.Preferences.LongTermUpdateWeeks
>= req.UpdatedDate.InZone(SmallGroup.timeZone group).Date
/// This represents a user of PrayerTracker
[<NoComparison; NoEquality>]
type User =
{ /// The ID of this user
{
/// The ID of this user
Id: UserId
/// The first name of this user
@ -517,10 +544,9 @@ type User =
/// The last time the user was seen (set whenever the user is loaded into a session)
LastSeen: Instant option
}
with
/// The full name of the user
member this.Name =
$"{this.FirstName} {this.LastName}"
member this.Name = $"{this.FirstName} {this.LastName}"
/// Functions to support users
module User =
@ -533,14 +559,14 @@ module User =
Email = ""
IsAdmin = false
PasswordHash = ""
LastSeen = None
}
LastSeen = None }
/// Cross-reference between user and small group
[<NoComparison; NoEquality>]
type UserSmallGroup =
{ /// The Id of the user who has access to the small group
{
/// The Id of the user who has access to the small group
UserId: UserId
/// The Id of the small group to which the user has access
@ -553,5 +579,4 @@ module UserSmallGroup =
/// An empty user/small group xref
let empty =
{ UserId = UserId Guid.Empty
SmallGroupId = SmallGroupId Guid.Empty
}
SmallGroupId = SmallGroupId Guid.Empty }

View File

@ -8,29 +8,33 @@ open System
[<Tests>]
let asOfDateDisplayTests =
testList "AsOfDateDisplay" [
testList "ToString" [
test "NoDisplay code is correct" {
Expect.equal (AsOfDateDisplay.toCode NoDisplay) "N" "The code for NoDisplay should have been \"N\""
Expect.equal (string NoDisplay) "N" "The code for NoDisplay should have been \"N\""
}
test "ShortDate code is correct" {
Expect.equal (AsOfDateDisplay.toCode ShortDate) "S" "The code for ShortDate should have been \"S\""
Expect.equal (string ShortDate) "S" "The code for ShortDate should have been \"S\""
}
test "LongDate code is correct" {
Expect.equal (AsOfDateDisplay.toCode LongDate) "L" "The code for LongDate should have been \"N\""
Expect.equal (string LongDate) "L" "The code for LongDate should have been \"N\""
}
test "fromCode N should return NoDisplay" {
Expect.equal (AsOfDateDisplay.fromCode "N") NoDisplay "\"N\" should have been converted to NoDisplay"
]
testList "Parse" [
test "N should return NoDisplay" {
Expect.equal (AsOfDateDisplay.Parse "N") NoDisplay "\"N\" should have been parsed to NoDisplay"
}
test "fromCode S should return ShortDate" {
Expect.equal (AsOfDateDisplay.fromCode "S") ShortDate "\"S\" should have been converted to ShortDate"
test "S should return ShortDate" {
Expect.equal (AsOfDateDisplay.Parse "S") ShortDate "\"S\" should have been parsed to ShortDate"
}
test "fromCode L should return LongDate" {
Expect.equal (AsOfDateDisplay.fromCode "L") LongDate "\"L\" should have been converted to LongDate"
test "L should return LongDate" {
Expect.equal (AsOfDateDisplay.Parse "L") LongDate "\"L\" should have been parsed to LongDate"
}
test "fromCode X should raise" {
Expect.throws (fun () -> AsOfDateDisplay.fromCode "X" |> ignore)
test "X should raise" {
Expect.throws (fun () -> AsOfDateDisplay.Parse "X" |> ignore)
"An unknown code should have raised an exception"
}
]
]
[<Tests>]
let churchTests =
@ -49,51 +53,59 @@ let churchTests =
[<Tests>]
let emailFormatTests =
testList "EmailFormat" [
testList "ToString" [
test "HtmlFormat code is correct" {
Expect.equal (EmailFormat.toCode HtmlFormat) "H" "The code for HtmlFormat should have been \"H\""
Expect.equal (string HtmlFormat) "H" "The code for HtmlFormat should have been \"H\""
}
test "PlainTextFormat code is correct" {
Expect.equal (EmailFormat.toCode PlainTextFormat) "P" "The code for PlainTextFormat should have been \"P\""
Expect.equal (string PlainTextFormat) "P" "The code for PlainTextFormat should have been \"P\""
}
test "fromCode H should return HtmlFormat" {
Expect.equal (EmailFormat.fromCode "H") HtmlFormat "\"H\" should have been converted to HtmlFormat"
]
testList "Parse" [
test "H should return HtmlFormat" {
Expect.equal (EmailFormat.Parse "H") HtmlFormat "\"H\" should have been converted to HtmlFormat"
}
test "fromCode P should return ShortDate" {
Expect.equal (EmailFormat.fromCode "P") PlainTextFormat
test "P should return ShortDate" {
Expect.equal (EmailFormat.Parse "P") PlainTextFormat
"\"P\" should have been converted to PlainTextFormat"
}
test "fromCode Z should raise" {
Expect.throws (fun () -> EmailFormat.fromCode "Z" |> ignore)
test "Z should raise" {
Expect.throws (fun () -> EmailFormat.Parse "Z" |> ignore)
"An unknown code should have raised an exception"
}
]
]
[<Tests>]
let expirationTests =
testList "Expiration" [
testList "ToString" [
test "Automatic code is correct" {
Expect.equal (Expiration.toCode Automatic) "A" "The code for Automatic should have been \"A\""
Expect.equal (string Automatic) "A" "The code for Automatic should have been \"A\""
}
test "Manual code is correct" {
Expect.equal (Expiration.toCode Manual) "M" "The code for Manual should have been \"M\""
Expect.equal (string Manual) "M" "The code for Manual should have been \"M\""
}
test "Forced code is correct" {
Expect.equal (Expiration.toCode Forced) "F" "The code for Forced should have been \"F\""
Expect.equal (string Forced) "F" "The code for Forced should have been \"F\""
}
test "fromCode A should return Automatic" {
Expect.equal (Expiration.fromCode "A") Automatic "\"A\" should have been converted to Automatic"
]
testList "Parse" [
test "A should return Automatic" {
Expect.equal (Expiration.Parse "A") Automatic "\"A\" should have been converted to Automatic"
}
test "fromCode M should return Manual" {
Expect.equal (Expiration.fromCode "M") Manual "\"M\" should have been converted to Manual"
test "M should return Manual" {
Expect.equal (Expiration.Parse "M") Manual "\"M\" should have been converted to Manual"
}
test "fromCode F should return Forced" {
Expect.equal (Expiration.fromCode "F") Forced "\"F\" should have been converted to Forced"
test "F should return Forced" {
Expect.equal (Expiration.Parse "F") Forced "\"F\" should have been converted to Forced"
}
test "fromCode V should raise" {
Expect.throws (fun () -> Expiration.fromCode "V" |> ignore)
Expect.throws (fun () -> Expiration.Parse "V" |> ignore)
"An unknown code should have raised an exception"
}
]
]
[<Tests>]
let listPreferencesTests =
@ -223,69 +235,75 @@ let prayerRequestTests =
[<Tests>]
let prayerRequestTypeTests =
testList "PrayerRequestType" [
testList "ToString" [
test "CurrentRequest code is correct" {
Expect.equal (PrayerRequestType.toCode CurrentRequest) "C"
"The code for CurrentRequest should have been \"C\""
Expect.equal (string CurrentRequest) "C" "The code for CurrentRequest should have been \"C\""
}
test "LongTermRequest code is correct" {
Expect.equal (PrayerRequestType.toCode LongTermRequest) "L"
"The code for LongTermRequest should have been \"L\""
Expect.equal (string LongTermRequest) "L" "The code for LongTermRequest should have been \"L\""
}
test "PraiseReport code is correct" {
Expect.equal (PrayerRequestType.toCode PraiseReport) "P" "The code for PraiseReport should have been \"P\""
Expect.equal (string PraiseReport) "P" "The code for PraiseReport should have been \"P\""
}
test "Expecting code is correct" {
Expect.equal (PrayerRequestType.toCode Expecting) "E" "The code for Expecting should have been \"E\""
Expect.equal (string Expecting) "E" "The code for Expecting should have been \"E\""
}
test "Announcement code is correct" {
Expect.equal (PrayerRequestType.toCode Announcement) "A" "The code for Announcement should have been \"A\""
Expect.equal (string Announcement) "A" "The code for Announcement should have been \"A\""
}
test "fromCode C should return CurrentRequest" {
Expect.equal (PrayerRequestType.fromCode "C") CurrentRequest
]
testList "Parse" [
test "C should return CurrentRequest" {
Expect.equal (PrayerRequestType.Parse "C") CurrentRequest
"\"C\" should have been converted to CurrentRequest"
}
test "fromCode L should return LongTermRequest" {
Expect.equal (PrayerRequestType.fromCode "L") LongTermRequest
test "L should return LongTermRequest" {
Expect.equal (PrayerRequestType.Parse "L") LongTermRequest
"\"L\" should have been converted to LongTermRequest"
}
test "fromCode P should return PraiseReport" {
Expect.equal (PrayerRequestType.fromCode "P") PraiseReport
test "P should return PraiseReport" {
Expect.equal (PrayerRequestType.Parse "P") PraiseReport
"\"P\" should have been converted to PraiseReport"
}
test "fromCode E should return Expecting" {
Expect.equal (PrayerRequestType.fromCode "E") Expecting "\"E\" should have been converted to Expecting"
test "E should return Expecting" {
Expect.equal (PrayerRequestType.Parse "E") Expecting "\"E\" should have been converted to Expecting"
}
test "fromCode A should return Announcement" {
Expect.equal (PrayerRequestType.fromCode "A") Announcement
test "A should return Announcement" {
Expect.equal (PrayerRequestType.Parse "A") Announcement
"\"A\" should have been converted to Announcement"
}
test "fromCode R should raise" {
Expect.throws (fun () -> PrayerRequestType.fromCode "R" |> ignore)
test "R should raise" {
Expect.throws (fun () -> PrayerRequestType.Parse "R" |> ignore)
"An unknown code should have raised an exception"
}
]
]
[<Tests>]
let requestSortTests =
testList "RequestSort" [
testList "ToString" [
test "SortByDate code is correct" {
Expect.equal (RequestSort.toCode SortByDate) "D" "The code for SortByDate should have been \"D\""
Expect.equal (string SortByDate) "D" "The code for SortByDate should have been \"D\""
}
test "SortByRequestor code is correct" {
Expect.equal (RequestSort.toCode SortByRequestor) "R" "The code for SortByRequestor should have been \"R\""
Expect.equal (string SortByRequestor) "R" "The code for SortByRequestor should have been \"R\""
}
test "fromCode D should return SortByDate" {
Expect.equal (RequestSort.fromCode "D") SortByDate "\"D\" should have been converted to SortByDate"
]
testList "Parse" [
test "D should return SortByDate" {
Expect.equal (RequestSort.Parse "D") SortByDate "\"D\" should have been converted to SortByDate"
}
test "fromCode R should return SortByRequestor" {
Expect.equal (RequestSort.fromCode "R") SortByRequestor
test "R should return SortByRequestor" {
Expect.equal (RequestSort.Parse "R") SortByRequestor
"\"R\" should have been converted to SortByRequestor"
}
test "fromCode Q should raise" {
Expect.throws (fun () -> RequestSort.fromCode "Q" |> ignore)
test "Q should raise" {
Expect.throws (fun () -> RequestSort.Parse "Q" |> ignore)
"An unknown code should have raised an exception"
}
]
]
[<Tests>]
let smallGroupTests =

View File

@ -22,12 +22,9 @@ module ReferenceListTests =
test "has all three options listed" {
let asOf = ReferenceList.asOfDateList _s
Expect.hasCountOf asOf 3u countAll "There should have been 3 as-of choices returned"
Expect.exists asOf (fun (x, _) -> x = AsOfDateDisplay.toCode NoDisplay)
"The option for no display was not found"
Expect.exists asOf (fun (x, _) -> x = AsOfDateDisplay.toCode ShortDate)
"The option for a short date was not found"
Expect.exists asOf (fun (x, _) -> x = AsOfDateDisplay.toCode LongDate)
"The option for a full date was not found"
Expect.exists asOf (fun (x, _) -> x = string NoDisplay) "The option for no display was not found"
Expect.exists asOf (fun (x, _) -> x = string ShortDate) "The option for a short date was not found"
Expect.exists asOf (fun (x, _) -> x = string LongDate) "The option for a full date was not found"
}
]
@ -41,9 +38,9 @@ module ReferenceListTests =
Expect.equal (fst top) "" "The default option should have been blank"
Expect.equal (snd top).Value "Group Default (HTML Format)" "The default option label was incorrect"
let nxt = typs |> Seq.skip 1 |> Seq.head
Expect.equal (fst nxt) (EmailFormat.toCode HtmlFormat) "The 2nd option should have been HTML"
Expect.equal (fst nxt) (string HtmlFormat) "The 2nd option should have been HTML"
let lst = typs |> Seq.last
Expect.equal (fst lst) (EmailFormat.toCode PlainTextFormat) "The 3rd option should have been plain text"
Expect.equal (fst lst) (string PlainTextFormat) "The 3rd option should have been plain text"
}
]
@ -53,19 +50,19 @@ module ReferenceListTests =
test "excludes immediate expiration if not required" {
let exps = ReferenceList.expirationList _s false
Expect.hasCountOf exps 2u countAll "There should have been 2 expiration types returned"
Expect.exists exps (fun (exp, _) -> exp = Expiration.toCode Automatic)
Expect.exists exps (fun (exp, _) -> exp = string Automatic)
"The option for automatic expiration was not found"
Expect.exists exps (fun (exp, _) -> exp = Expiration.toCode Manual)
Expect.exists exps (fun (exp, _) -> exp = string Manual)
"The option for manual expiration was not found"
}
test "includes immediate expiration if required" {
let exps = ReferenceList.expirationList _s true
Expect.hasCountOf exps 3u countAll "There should have been 3 expiration types returned"
Expect.exists exps (fun (exp, _) -> exp = Expiration.toCode Automatic)
Expect.exists exps (fun (exp, _) -> exp = string Automatic)
"The option for automatic expiration was not found"
Expect.exists exps (fun (exp, _) -> exp = Expiration.toCode Manual)
Expect.exists exps (fun (exp, _) -> exp = string Manual)
"The option for manual expiration was not found"
Expect.exists exps (fun (exp, _) -> exp = Expiration.toCode Forced)
Expect.exists exps (fun (exp, _) -> exp = string Forced)
"The option for immediate expiration was not found"
}
]
@ -240,7 +237,7 @@ let editMemberTests =
}
test "fromMember populates with specific format" {
let edit = EditMember.fromMember { Member.empty with Format = Some HtmlFormat }
Expect.equal edit.Format (EmailFormat.toCode HtmlFormat) "The e-mail format was not filled correctly"
Expect.equal edit.Format (string HtmlFormat) "The e-mail format was not filled correctly"
}
test "empty is as expected" {
let edit = EditMember.empty
@ -268,11 +265,10 @@ let editPreferencesTests =
Expect.equal edit.DaysToKeepNew prefs.DaysToKeepNew "The days to keep new were not filled correctly"
Expect.equal edit.LongTermUpdateWeeks prefs.LongTermUpdateWeeks
"The weeks for update were not filled correctly"
Expect.equal edit.RequestSort (RequestSort.toCode prefs.RequestSort)
"The request sort was not filled correctly"
Expect.equal edit.RequestSort (string prefs.RequestSort) "The request sort was not filled correctly"
Expect.equal edit.EmailFromName prefs.EmailFromName "The e-mail from name was not filled correctly"
Expect.equal edit.EmailFromAddress prefs.EmailFromAddress "The e-mail from address was not filled correctly"
Expect.equal edit.DefaultEmailType (EmailFormat.toCode prefs.DefaultEmailType)
Expect.equal edit.DefaultEmailType (string prefs.DefaultEmailType)
"The default e-mail type was not filled correctly"
Expect.equal edit.LineColorType "Name" "The heading line color type was not derived correctly"
Expect.equal edit.LineColor prefs.LineColor "The heading line color was not filled correctly"
@ -288,8 +284,7 @@ let editPreferencesTests =
Expect.equal edit.Visibility GroupVisibility.PrivateList
"The list visibility was not derived correctly"
Expect.equal edit.PageSize prefs.PageSize "The page size was not filled correctly"
Expect.equal edit.AsOfDate (AsOfDateDisplay.toCode prefs.AsOfDateDisplay)
"The as-of date display was not filled correctly"
Expect.equal edit.AsOfDate (string prefs.AsOfDateDisplay) "The as-of date display was not filled correctly"
}
test "fromPreferences succeeds for RGB line color and password-protected list" {
let prefs = { ListPreferences.empty with LineColor = "#ff0000"; GroupPassword = "pw" }
@ -326,13 +321,11 @@ let editRequestTests =
test "empty is as expected" {
let mt = EditRequest.empty
Expect.equal mt.RequestId emptyGuid "The request ID should be an empty GUID"
Expect.equal mt.RequestType (PrayerRequestType.toCode CurrentRequest)
"The request type should have been \"Current\""
Expect.equal mt.RequestType (string CurrentRequest) "The request type should have been \"Current\""
Expect.isNone mt.EnteredDate "The entered date should have been None"
Expect.isNone mt.SkipDateUpdate """The "skip date update" flag should have been None"""
Expect.isNone mt.Requestor "The requestor should have been None"
Expect.equal mt.Expiration (Expiration.toCode Automatic)
"""The expiration should have been "A" (Automatic)"""
Expect.equal mt.Expiration (string Automatic) """The expiration should have been "A" (Automatic)"""
Expect.equal mt.Text "" "The text should have been blank"
}
test "fromRequest succeeds" {
@ -346,10 +339,9 @@ let editRequestTests =
}
let edit = EditRequest.fromRequest req
Expect.equal edit.RequestId (shortGuid req.Id.Value) "The request ID was not filled correctly"
Expect.equal edit.RequestType (PrayerRequestType.toCode req.RequestType)
"The request type was not filled correctly"
Expect.equal edit.RequestType (string req.RequestType) "The request type was not filled correctly"
Expect.equal edit.Requestor req.Requestor "The requestor was not filled correctly"
Expect.equal edit.Expiration (Expiration.toCode Manual) "The expiration was not filled correctly"
Expect.equal edit.Expiration (string Manual) "The expiration was not filled correctly"
Expect.equal edit.Text req.Text "The text was not filled correctly"
}
test "isNew works for a new request" {

View File

@ -29,7 +29,7 @@ let edit (model : EditRequest) today ctx viewInfo =
label [ _for (nameof model.RequestType) ] [ locStr s["Request Type"] ]
ReferenceList.requestTypeList s
|> Seq.ofList
|> Seq.map (fun (typ, desc) -> PrayerRequestType.toCode typ, desc.Value)
|> Seq.map (fun (typ, desc) -> string typ, desc.Value)
|> selectList (nameof model.RequestType) model.RequestType [ _required; _autofocus ]
]
div [ _inputField ] [

View File

@ -54,8 +54,8 @@ let announcement isAdmin ctx viewInfo =
label [ _for (nameof model.RequestType) ] [ locStr s["Request Type"] ]
reqTypes
|> Seq.ofList
|> Seq.map (fun (typ, desc) -> PrayerRequestType.toCode typ, desc.Value)
|> selectList (nameof model.RequestType) (PrayerRequestType.toCode Announcement) []
|> Seq.map (fun (typ, desc) -> string typ, desc.Value)
|> selectList (nameof model.RequestType) (string Announcement) []
]
]
div [ _fieldRow ] [ submit [] "send" s["Send Announcement"] ]
@ -273,7 +273,7 @@ let members (members : Member list) (emailTypes : Map<string, LocalizedString>)
div [ _class "cell" ] [ str mbr.Name ]
div [ _class "cell" ] [ str mbr.Email ]
div [ _class "cell" ] [
locStr emailTypes[defaultArg (mbr.Format |> Option.map EmailFormat.toCode) ""]
locStr emailTypes[defaultArg (mbr.Format |> Option.map string) ""]
]
]
]

View File

@ -11,9 +11,9 @@ module ReferenceList =
/// A localized list of the AsOfDateDisplay DU cases
let asOfDateList (s: IStringLocalizer) = [
AsOfDateDisplay.toCode NoDisplay, s["Do not display the “as of” date"]
AsOfDateDisplay.toCode ShortDate, s["Display a short “as of” date"]
AsOfDateDisplay.toCode LongDate, s["Display a full “as of” date"]
string NoDisplay, s["Do not display the “as of” date"]
string ShortDate, s["Display a short “as of” date"]
string LongDate, s["Display a full “as of” date"]
]
/// A list of e-mail type options
@ -23,16 +23,15 @@ module ReferenceList =
s[match def with HtmlFormat -> "HTML Format" | PlainTextFormat -> "Plain-Text Format"].Value
seq {
"", LocalizedString ("", $"""{s["Group Default"].Value} ({defaultType})""")
EmailFormat.toCode HtmlFormat, s["HTML Format"]
EmailFormat.toCode PlainTextFormat, s["Plain-Text Format"]
string HtmlFormat, s["HTML Format"]
string PlainTextFormat, s["Plain-Text Format"]
}
/// A list of expiration options
let expirationList (s: IStringLocalizer) includeExpireNow = [
Expiration.toCode Automatic, s["Expire Normally"]
Expiration.toCode Manual, s["Request Never Expires"]
if includeExpireNow then Expiration.toCode Forced, s["Expire Immediately"]
]
let expirationList (s: IStringLocalizer) includeExpireNow =
[ string Automatic, s["Expire Normally"]
string Manual, s["Request Never Expires"]
if includeExpireNow then string Forced, s["Expire Immediately"] ]
/// A list of request types
let requestTypeList (s: IStringLocalizer) = [
@ -326,7 +325,7 @@ module EditMember =
{ MemberId = shortGuid mbr.Id.Value
Name = mbr.Name
Email = mbr.Email
Format = match mbr.Format with Some fmt -> EmailFormat.toCode fmt | None -> ""
Format = mbr.Format |> Option.map string |> Option.defaultValue ""
}
/// An empty instance
@ -413,10 +412,10 @@ with
DaysToExpire = this.ExpireDays
DaysToKeepNew = this.DaysToKeepNew
LongTermUpdateWeeks = this.LongTermUpdateWeeks
RequestSort = RequestSort.fromCode this.RequestSort
RequestSort = RequestSort.Parse this.RequestSort
EmailFromName = this.EmailFromName
EmailFromAddress = this.EmailFromAddress
DefaultEmailType = EmailFormat.fromCode this.DefaultEmailType
DefaultEmailType = EmailFormat.Parse this.DefaultEmailType
LineColor = this.LineColor
HeadingColor = this.HeadingColor
Fonts = if this.IsNative || Option.isNone this.Fonts then "native" else this.Fonts.Value
@ -426,7 +425,7 @@ with
IsPublic = isPublic
GroupPassword = grpPw
PageSize = this.PageSize
AsOfDateDisplay = AsOfDateDisplay.fromCode this.AsOfDate
AsOfDateDisplay = AsOfDateDisplay.Parse this.AsOfDate
}
/// Support for the EditPreferences type
@ -437,10 +436,10 @@ module EditPreferences =
{ ExpireDays = prefs.DaysToExpire
DaysToKeepNew = prefs.DaysToKeepNew
LongTermUpdateWeeks = prefs.LongTermUpdateWeeks
RequestSort = RequestSort.toCode prefs.RequestSort
RequestSort = string prefs.RequestSort
EmailFromName = prefs.EmailFromName
EmailFromAddress = prefs.EmailFromAddress
DefaultEmailType = EmailFormat.toCode prefs.DefaultEmailType
DefaultEmailType = string prefs.DefaultEmailType
LineColorType = setType prefs.LineColor
LineColor = prefs.LineColor
HeadingColorType = setType prefs.HeadingColor
@ -452,7 +451,7 @@ module EditPreferences =
TimeZone = TimeZoneId.toString prefs.TimeZoneId
GroupPassword = Some prefs.GroupPassword
PageSize = prefs.PageSize
AsOfDate = AsOfDateDisplay.toCode prefs.AsOfDateDisplay
AsOfDate = string prefs.AsOfDateDisplay
Visibility =
if prefs.IsPublic then GroupVisibility.PublicList
elif prefs.GroupPassword = "" then GroupVisibility.PrivateList
@ -495,11 +494,11 @@ module EditRequest =
/// An empty instance to use for new requests
let empty =
{ RequestId = emptyGuid
RequestType = PrayerRequestType.toCode CurrentRequest
RequestType = string CurrentRequest
EnteredDate = None
SkipDateUpdate = None
Requestor = None
Expiration = Expiration.toCode Automatic
Expiration = string Automatic
Text = ""
}
@ -507,9 +506,9 @@ module EditRequest =
let fromRequest (req: PrayerRequest) =
{ empty with
RequestId = shortGuid req.Id.Value
RequestType = PrayerRequestType.toCode req.RequestType
RequestType = string req.RequestType
Requestor = req.Requestor
Expiration = Expiration.toCode req.Expiration
Expiration = string req.Expiration
Text = req.Text
}

View File

@ -238,10 +238,10 @@ let save : HttpHandler = requireAccess [ User ] >=> validateCsrf >=> fun next ct
let now = SmallGroup.localDateNow ctx.Clock group
let updated =
{ pr with
RequestType = PrayerRequestType.fromCode model.RequestType
RequestType = PrayerRequestType.Parse model.RequestType
Requestor = match model.Requestor with Some x when x.Trim() = "" -> None | x -> x
Text = ckEditorToText model.Text
Expiration = Expiration.fromCode model.Expiration
Expiration = Expiration.Parse model.Expiration
}
|> function
| it when model.IsNew ->

View File

@ -210,7 +210,7 @@ let saveMember : HttpHandler = requireAccess [ User ] >=> validateCsrf >=> fun n
{ mbr with
Name = model.Name
Email = model.Email
Format = String.noneIfBlank model.Format |> Option.map EmailFormat.fromCode }
Format = String.noneIfBlank model.Format |> Option.map EmailFormat.Parse }
let act = ctx.Strings[if model.IsNew then "Added" else "Updated"].Value.ToLower()
addInfo ctx ctx.Strings["Successfully {0} group member", act]
return! redirectTo false "/small-group/members" next ctx
@ -288,7 +288,7 @@ let sendAnnouncement : HttpHandler = requireAccess [ User ] >=> validateCsrf >=>
Id = (Guid.NewGuid >> PrayerRequestId) ()
SmallGroupId = group.Id
UserId = usr.Id
RequestType = (Option.get >> PrayerRequestType.fromCode) model.RequestType
RequestType = (Option.get >> PrayerRequestType.Parse) model.RequestType
Text = requestText
EnteredDate = now.Date.AtStartOfDayInZone(zone).ToInstant()
UpdatedDate = now.InZoneLeniently(zone).ToInstant() }