diff --git a/src/PrayerTracker.Data/DataAccess.fs b/src/PrayerTracker.Data/DataAccess.fs index 1567533..46a39f8 100644 --- a/src/PrayerTracker.Data/DataAccess.fs +++ b/src/PrayerTracker.Data/DataAccess.fs @@ -14,11 +14,11 @@ module private Helpers = /// Central place to append sort criteria for prayer request queries let reqSort sort (query : IQueryable) = match sort with - | "D" -> + | SortByDate -> query.OrderByDescending(fun pr -> pr.updatedDate) .ThenByDescending(fun pr -> pr.enteredDate) .ThenBy(fun pr -> pr.requestor) - | _ -> + | SortByRequestor -> query.OrderBy(fun pr -> pr.requestor) .ThenByDescending(fun pr -> pr.updatedDate) .ThenByDescending(fun pr -> pr.enteredDate) diff --git a/src/PrayerTracker.Data/Entities.fs b/src/PrayerTracker.Data/Entities.fs index e881d13..824a995 100644 --- a/src/PrayerTracker.Data/Entities.fs +++ b/src/PrayerTracker.Data/Entities.fs @@ -54,31 +54,65 @@ with | "L" -> LongDate | _ -> invalidArg "code" (sprintf "Unknown code %s" code) /// Convert this DU case to a single-character string - member this.toCode () = + member this.code = match this with | NoDisplay -> "N" | ShortDate -> "S" | LongDate -> "L" +/// How requests should be sorted +type RequestSort = + /// Sort by date, then by requestor/subject + | SortByDate + /// Sort by requestor/subject, then by date + | SortByRequestor +with + /// Convert to a DU case from a single-character string + static member fromCode code = + match code with + | "D" -> SortByDate + | "R" -> SortByRequestor + | _ -> invalidArg "code" (sprintf "Unknown code %s" code) + /// Convert this DU case to a single-character string + member this.code = + match this with + | SortByDate -> "D" + | SortByRequestor -> "R" + + module Converters = open Microsoft.EntityFrameworkCore.Storage.ValueConversion open Microsoft.FSharp.Linq.RuntimeHelpers open System.Linq.Expressions - let private fromDU = - <@ Func(fun (x : AsOfDateDisplay) -> x.toCode ()) @> + let private asOfFromDU = + <@ Func(fun (x : AsOfDateDisplay) -> x.code) @> |> LeafExpressionConverter.QuotationToExpression |> unbox>> - let private toDU = + let private asOfToDU = <@ Func(AsOfDateDisplay.fromCode) @> |> LeafExpressionConverter.QuotationToExpression |> unbox>> + let private sortFromDU = + <@ Func(fun (x : RequestSort) -> x.code) @> + |> LeafExpressionConverter.QuotationToExpression + |> unbox>> + + let private sortToDU = + <@ Func(RequestSort.fromCode) @> + |> LeafExpressionConverter.QuotationToExpression + |> unbox>> + /// Conversion between a string and an AsOfDateDisplay DU value type AsOfDateDisplayConverter () = - inherit ValueConverter (fromDU, toDU) + inherit ValueConverter (asOfFromDU, asOfToDU) + + /// Conversion between a string and a RequestSort DU value + type RequestSortConverter () = + inherit ValueConverter (sortFromDU, sortToDU) /// Statistics for churches @@ -189,7 +223,7 @@ and [] ListPreferences = /// The font size for the text on the prayer request list textFontSize : int /// The order in which the prayer requests are sorted - requestSort : string + requestSort : RequestSort /// The password used for "small group login" (view-only request list) groupPassword : string /// The default e-mail type for this class @@ -219,7 +253,7 @@ and [] ListPreferences = lineColor = "navy" headingFontSize = 16 textFontSize = 12 - requestSort = "D" + requestSort = SortByDate groupPassword = "" defaultEmailType = EmailType.Html isPublic = false @@ -289,7 +323,7 @@ and [] ListPreferences = .HasColumnName("RequestSort") .IsRequired() .HasMaxLength(1) - .HasDefaultValue "D" + .HasDefaultValue SortByDate |> ignore m.Property(fun e -> e.groupPassword) .HasColumnName("GroupPassword") @@ -323,6 +357,8 @@ and [] ListPreferences = .HasDefaultValue NoDisplay |> ignore) |> ignore + mb.Model.FindEntityType(typeof).FindProperty("requestSort") + .SetValueConverter(Converters.RequestSortConverter ()) mb.Model.FindEntityType(typeof).FindProperty("asOfDateDisplay") .SetValueConverter(Converters.AsOfDateDisplayConverter ()) diff --git a/src/PrayerTracker.Tests/Data/EntitiesTests.fs b/src/PrayerTracker.Tests/Data/EntitiesTests.fs index 688abbe..241c20d 100644 --- a/src/PrayerTracker.Tests/Data/EntitiesTests.fs +++ b/src/PrayerTracker.Tests/Data/EntitiesTests.fs @@ -5,6 +5,33 @@ open NodaTime.Testing open NodaTime open System +[] +let asOfDateDisplayTests = + testList "AsOfDateDisplay" [ + test "NoDisplay code is correct" { + Expect.equal NoDisplay.code "N" "The code for NoDisplay should have been \"N\"" + } + test "ShortDate code is correct" { + Expect.equal ShortDate.code "S" "The code for ShortDate should have been \"S\"" + } + test "LongDate code is correct" { + Expect.equal LongDate.code "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" + } + test "fromCode S should return ShortDate" { + Expect.equal (AsOfDateDisplay.fromCode "S") ShortDate "\"S\" should have been converted to ShortDate" + } + test "fromCode L should return LongDate" { + Expect.equal (AsOfDateDisplay.fromCode "L") LongDate "\"L\" should have been converted to LongDate" + } + test "fromCode X should raise" { + Expect.throws (fun () -> AsOfDateDisplay.fromCode "X" |> ignore) + "An unknown code should have raised an exception" + } + ] + [] let churchTests = testList "Church" [ @@ -38,7 +65,7 @@ let listPreferencesTests = Expect.equal mt.lineColor "navy" "The default heding line color should have been navy" Expect.equal mt.headingFontSize 16 "The default heading font size should have been 16" Expect.equal mt.textFontSize 12 "The default text font size should have been 12" - Expect.equal mt.requestSort "D" "The default request sort should have been D (date)" + Expect.equal mt.requestSort SortByDate "The default request sort should have been by date" Expect.equal mt.groupPassword "" "The default group password should have been blank" Expect.equal mt.defaultEmailType EmailType.Html "The default e-mail type should have been HTML" Expect.isFalse mt.isPublic "The isPublic flag should not have been set" @@ -130,6 +157,26 @@ let prayerRequestTests = } ] +[] +let requestSortTests = + testList "RequestSort" [ + test "SortByDate code is correct" { + Expect.equal SortByDate.code "D" "The code for SortByDate should have been \"D\"" + } + test "SortByRequestor code is correct" { + Expect.equal SortByRequestor.code "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" + } + test "fromCode R should return SortByRequestor" { + Expect.equal (RequestSort.fromCode "R") SortByRequestor "\"R\" should have been converted to SortByRequestor" + } + test "fromCode Q should raise" { + Expect.throws (fun () -> RequestSort.fromCode "Q" |> ignore) "An unknown code should have raised an exception" + } + ] + [] let smallGroupTests = testList "SmallGroup" [ diff --git a/src/PrayerTracker.Tests/UI/ViewModelsTests.fs b/src/PrayerTracker.Tests/UI/ViewModelsTests.fs index ba58dbe..c831f86 100644 --- a/src/PrayerTracker.Tests/UI/ViewModelsTests.fs +++ b/src/PrayerTracker.Tests/UI/ViewModelsTests.fs @@ -15,6 +15,18 @@ let countAll _ = true module ReferenceListTests = + [] + let asOfDateListTests = + testList "ReferenceList.asOfDateList" [ + 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 = NoDisplay.code) "The option for no display was not found" + Expect.exists asOf (fun (x, _) -> x = ShortDate.code) "The option for a short date was not found" + Expect.exists asOf (fun (x, _) -> x = LongDate.code) "The option for a full date was not found" + } + ] + [] let emailTypeListTests = testList "ReferenceList.emailTypeList" [ @@ -248,7 +260,7 @@ let editPreferencesTests = Expect.equal edit.expireDays prefs.daysToExpire "The expiration days were not filled correctly" 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 prefs.requestSort "The request sort was not filled correctly" + Expect.equal edit.requestSort prefs.requestSort.code "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 prefs.defaultEmailType "The default e-mail type was not filled correctly" @@ -572,7 +584,9 @@ let requestListTests = let newList = { reqList with listGroup = - { reqList.listGroup with preferences = { reqList.listGroup.preferences with requestSort = "R" } } + { reqList.listGroup with + preferences = { reqList.listGroup.preferences with requestSort = SortByRequestor } + } } let reqs = newList.requestsInCategory RequestType.Current Expect.hasCountOf reqs 2u countAll "There should have been two requests" diff --git a/src/PrayerTracker.UI/ViewModels.fs b/src/PrayerTracker.UI/ViewModels.fs index d1f1248..fd9307c 100644 --- a/src/PrayerTracker.UI/ViewModels.fs +++ b/src/PrayerTracker.UI/ViewModels.fs @@ -12,9 +12,9 @@ module ReferenceList = /// A localized list of the AsOfDateDisplay DU cases let asOfDateList (s : IStringLocalizer) = - [ NoDisplay.toCode (), s.["Do not display the “as of” date"] - ShortDate.toCode (), s.["Display a short “as of” date"] - LongDate.toCode (), s.["Display a full “as of” date"] + [ NoDisplay.code, s.["Do not display the “as of” date"] + ShortDate.code, s.["Display a short “as of” date"] + LongDate.code, s.["Display a full “as of” date"] ] /// A list of e-mail type options @@ -289,7 +289,7 @@ with { expireDays = prefs.daysToExpire daysToKeepNew = prefs.daysToKeepNew longTermUpdateWeeks = prefs.longTermUpdateWeeks - requestSort = prefs.requestSort + requestSort = prefs.requestSort.code emailFromName = prefs.emailFromName emailFromAddress = prefs.emailFromAddress defaultEmailType = prefs.defaultEmailType @@ -303,7 +303,7 @@ with timeZone = prefs.timeZoneId groupPassword = Some prefs.groupPassword pageSize = prefs.pageSize - asOfDate = prefs.asOfDateDisplay.toCode () + asOfDate = prefs.asOfDateDisplay.code listVisibility = match true with | _ when prefs.isPublic -> RequestVisibility.``public`` @@ -322,7 +322,7 @@ with daysToExpire = this.expireDays daysToKeepNew = this.daysToKeepNew longTermUpdateWeeks = this.longTermUpdateWeeks - requestSort = this.requestSort + requestSort = RequestSort.fromCode this.requestSort emailFromName = this.emailFromName emailFromAddress = this.emailFromAddress defaultEmailType = this.defaultEmailType @@ -573,8 +573,8 @@ with |> Seq.ofList |> Seq.filter (fun req -> req.requestType = cat) match this.listGroup.preferences.requestSort with - | "D" -> reqs |> Seq.sortByDescending (fun req -> req.updatedDate) - | _ -> reqs |> Seq.sortBy (fun req -> req.requestor) + | SortByDate -> reqs |> Seq.sortByDescending (fun req -> req.updatedDate) + | SortByRequestor -> reqs |> Seq.sortBy (fun req -> req.requestor) |> List.ofSeq /// Is this request new? member this.isNew (req : PrayerRequest) =