mirror of
https://github.com/Smaug123/WoofWare.Expect
synced 2025-10-06 13:08:39 +00:00
Comments and break the build to indicate missing functionality
This commit is contained in:
21
README.md
21
README.md
@@ -16,8 +16,7 @@ An [expect-testing](https://blog.janestreet.com/the-joy-of-expect-tests/) librar
|
||||
# Current status
|
||||
|
||||
The basic mechanism works.
|
||||
Snapshot updating is vibe-coded with Opus 4 and is purely text-based; I didn't want to use the F# compiler services because that's a pretty heavyweight dependency which should be confined to a separate test runner entity.
|
||||
It's fairly well tested, but you will certainly be able to find ways to break it; try not to be too fancy with your syntax around the `snapshot` statement.
|
||||
It's fairly well tested, but you will almost certainly be able to find ways to break it; try not to be too fancy with your syntax around the `snapshot` statement.
|
||||
|
||||
# How to use
|
||||
|
||||
@@ -51,8 +50,17 @@ let ``With return! and snapshotThrows, you can see exceptions too`` () =
|
||||
snapshotThrows @"System.Exception: oh no"
|
||||
return! (fun () -> failwith<int> "oh no")
|
||||
}
|
||||
|
||||
[<Test>]
|
||||
let ``You can do lists more neatly with the snapshotList keyword`` () =
|
||||
expect {
|
||||
snapshotList [ "8" ; "9" ; "10" ; "11" ; "12" ]
|
||||
return [ 8..12 ]
|
||||
}
|
||||
```
|
||||
|
||||
(JSON output for elements is not yet supported with `snapshotList`.)
|
||||
|
||||
You can adjust the formatting:
|
||||
|
||||
```fsharp
|
||||
@@ -64,6 +72,15 @@ let ``Overriding the formatting`` () =
|
||||
snapshot @"Int32"
|
||||
return 123
|
||||
}
|
||||
|
||||
[<Test>]
|
||||
let ``Overriding the formatting with lists`` () =
|
||||
expect {
|
||||
// these two lines *do* have to be in this order, for annoying reasons
|
||||
snapshotList [ "8" ; "9" ; "0" ; "1" ; "2" ]
|
||||
withFormat (fun x -> string<int> (x % 10))
|
||||
return [ 8..12 ]
|
||||
}
|
||||
```
|
||||
|
||||
You can override the JSON serialisation if you find the snapshot format displeasing:
|
||||
|
@@ -5,18 +5,27 @@ open WoofWare.Expect
|
||||
|
||||
[<TestFixture>]
|
||||
module TestSnapshotList =
|
||||
[<OneTimeSetUp>]
|
||||
let ``Prepare to bulk-update tests`` () =
|
||||
// If you don't want to enter bulk-update mode, just replace this line with a no-op `()`.
|
||||
// The `updateAllSnapshots` tear-down below will simply do nothing in that case.
|
||||
GlobalBuilderConfig.enterBulkUpdateMode ()
|
||||
|
||||
[<OneTimeTearDown>]
|
||||
let ``Update all tests`` () =
|
||||
GlobalBuilderConfig.updateAllSnapshots ()
|
||||
|
||||
[<Test>]
|
||||
let ``simple list test`` () =
|
||||
expect {
|
||||
snapshotList [ "1" ; "2" ; "3" ]
|
||||
snapshotList []
|
||||
return [ 1..3 ]
|
||||
}
|
||||
|
||||
[<Test>]
|
||||
let ``list test with formatting`` () =
|
||||
expect {
|
||||
snapshotList [ "8" ; "9" ; "0" ; "1" ; "2" ]
|
||||
snapshotList []
|
||||
withFormat (fun x -> string<int> (x % 10))
|
||||
return [ 8..12 ]
|
||||
}
|
||||
|
@@ -17,8 +17,6 @@ type Mode =
|
||||
| Update
|
||||
| AssertMockingSource of (string * int)
|
||||
|
||||
type BuilderKindNormal<'T> = | BuilderKindNormal of unit
|
||||
|
||||
/// <summary>
|
||||
/// The builder which powers WoofWare.Expect.
|
||||
/// </summary>
|
||||
@@ -35,6 +33,7 @@ type ExpectBuilder (mode : Mode) =
|
||||
else
|
||||
ExpectBuilder Mode.Assert
|
||||
|
||||
/// Combine two `ExpectStateListy`s. The first one is the "expected" snapshot; the second is the "actual".
|
||||
member _.Bind<'U> (state : ExpectStateListy<'U>, f : unit -> ExpectStateListy<'U>) : ExpectStateListy<'U> =
|
||||
let actual = f ()
|
||||
|
||||
@@ -58,6 +57,8 @@ type ExpectBuilder (mode : Mode) =
|
||||
Actual = actual.Actual
|
||||
}
|
||||
|
||||
/// Combine an `ExpectStateListy` with an `ExpectState`. The first one is the "expected" snapshot; the second is
|
||||
/// the "actual".
|
||||
member _.Bind<'U, 'elt when 'U :> IEnumerable<'elt>>
|
||||
(state : ExpectStateListy<'elt>, f : unit -> ExpectState<'U>)
|
||||
: ExpectStateListy<'elt>
|
||||
@@ -126,6 +127,7 @@ type ExpectBuilder (mode : Mode) =
|
||||
JsonDocOptions = jsonDocOptions
|
||||
}
|
||||
|
||||
/// <summary>Express that the actual value's <c>ToString</c> should identically equal this string.</summary>
|
||||
[<CustomOperation("snapshot", MaintainsVariableSpaceUsingBind = true)>]
|
||||
member _.Snapshot<'a>
|
||||
(
|
||||
@@ -190,6 +192,13 @@ type ExpectBuilder (mode : Mode) =
|
||||
Actual = None
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Express that the actual value, when converted to JSON, should result in a JSON document
|
||||
/// which matches the JSON document that is this string.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For example, <c>snapshotJson "123"</c> indicates the JSON integer 123.
|
||||
/// </remarks>
|
||||
[<CustomOperation("snapshotJson", MaintainsVariableSpaceUsingBind = true)>]
|
||||
member this.SnapshotJson<'a>
|
||||
(
|
||||
@@ -263,6 +272,13 @@ type ExpectBuilder (mode : Mode) =
|
||||
Actual = None
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Express that the actual value, which is a sequence, should have elements which individually (in order) match
|
||||
/// this snapshot list.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For example, <c>snapshotList ["123" ; "456"]</c> indicates an exactly-two-element list <c>[123 ; 456]</c>.
|
||||
/// </remarks>
|
||||
[<CustomOperation("snapshotList", MaintainsVariableSpaceUsingBind = true)>]
|
||||
member _.SnapshotList<'a>
|
||||
(
|
||||
@@ -340,6 +356,17 @@ type ExpectBuilder (mode : Mode) =
|
||||
Actual = None
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Expresses that the given expression throws during evaluation.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// expect {
|
||||
/// snapshotThrows @"System.Exception: oh no"
|
||||
/// return! (fun () -> failwith "oh no")
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
[<CustomOperation("snapshotThrows", MaintainsVariableSpaceUsingBind = true)>]
|
||||
member _.SnapshotThrows<'a>
|
||||
(
|
||||
@@ -377,7 +404,6 @@ type ExpectBuilder (mode : Mode) =
|
||||
/// <summary>
|
||||
/// Express that the <c>return</c> value of this builder should be formatted using this function, before
|
||||
/// comparing to the snapshot.
|
||||
/// this value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For example, <c>withFormat (fun x -> x.ToString ()) "123"</c> is equivalent to <c>snapshot "123"</c>.
|
||||
@@ -391,6 +417,14 @@ type ExpectBuilder (mode : Mode) =
|
||||
Formatter = Some (fun f -> f () |> formatter)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Express that the <c>return</c> value of this builder should be formatted using this function, before
|
||||
/// comparing to the snapshot.
|
||||
/// In the case of <c>snapshotList</c>, this applies to the elements of the sequence, not to the sequence itself.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For example, <c>withFormat (fun x -> x.ToString ()) "123"</c> is equivalent to <c>snapshot "123"</c>.
|
||||
/// </remarks>
|
||||
[<CustomOperation("withFormat", MaintainsVariableSpaceUsingBind = true)>]
|
||||
member _.WithFormat<'T> (state : ExpectStateListy<'T>, formatter : 'T -> string) =
|
||||
match state.Formatter with
|
||||
@@ -423,6 +457,20 @@ type ExpectBuilder (mode : Mode) =
|
||||
JsonSerialiserOptions = Some jsonOptions
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Express that these JsonSerializerOptions should be used to construct the JSON object to which the snapshot
|
||||
/// is to be compared (or, in write-out-the-snapshot mode, to construct the JSON object to be written out).
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// If you want your snapshots to be written out compactly, rather than the default indenting:
|
||||
/// <code>
|
||||
/// expect {
|
||||
/// snapshotJson @"{""a"":3}"
|
||||
/// withJsonSerializerOptions (JsonSerializerOptions (WriteIndented = false))
|
||||
/// return Map.ofList ["a", 3]
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
[<CustomOperation("withJsonSerializerOptions", MaintainsVariableSpaceUsingBind = true)>]
|
||||
member _.WithJsonSerializerOptions<'T> (state : ExpectStateListy<'T>, jsonOptions : JsonSerializerOptions) =
|
||||
match state.Snapshot with
|
||||
@@ -506,6 +554,7 @@ type ExpectBuilder (mode : Mode) =
|
||||
/// Computation expression `Delay`.
|
||||
member _.Delay (f : unit -> ExpectStateListy<'T>) : unit -> ExpectStateListy<'T> = f
|
||||
|
||||
/// Computation expression `Run`, which runs a `Delay`ed snapshot assertion, throwing if the assertion fails.
|
||||
member _.Run (f : unit -> ExpectStateListy<'T>) : unit =
|
||||
let state = f ()
|
||||
|
||||
@@ -528,14 +577,16 @@ type ExpectBuilder (mode : Mode) =
|
||||
|
||||
if snapshot <> actual then
|
||||
let diff =
|
||||
Diff.patienceLines (Array.ofList snapshot) (Array.ofList actual)
|
||||
|> Diff.format
|
||||
Diff.patienceLines (Array.ofList snapshot) (Array.ofList actual) |> Diff.format
|
||||
|
||||
match mode with
|
||||
| Mode.Assert ->
|
||||
$"snapshot mismatch! snapshot at %s{caller.FilePath}:%i{caller.LineNumber} (%s{caller.MemberName}) diff:\n%s{diff}"
|
||||
|> ExpectException
|
||||
|> raise
|
||||
if GlobalBuilderConfig.isBulkUpdateMode () then
|
||||
GlobalBuilderConfig.registerTest state
|
||||
else
|
||||
$"snapshot mismatch! snapshot at %s{caller.FilePath}:%i{caller.LineNumber} (%s{caller.MemberName}) diff:\n%s{diff}"
|
||||
|> ExpectException
|
||||
|> raise
|
||||
| Mode.AssertMockingSource (mockSource, line) ->
|
||||
$"snapshot mismatch! snapshot at %s{mockSource}:%i{line} (%s{caller.MemberName}) diff:\n%s{diff}"
|
||||
|> ExpectException
|
||||
|
@@ -44,6 +44,7 @@ type internal UniqueLines<'line when 'line : comparison> =
|
||||
LineCounts : Map<'line, int>
|
||||
}
|
||||
|
||||
/// The diff between two line-oriented pieces of text.
|
||||
[<RequireQualifiedAccess>]
|
||||
module Diff =
|
||||
/// Find lines that appear exactly once in a sequence
|
||||
@@ -260,6 +261,7 @@ module Diff =
|
||||
)
|
||||
|> String.concat "\n"
|
||||
|
||||
/// Format the diff as a human-readable string, including line numbers at the left.
|
||||
let formatWithLineNumbers (d : Diff) : string = formatWithLineNumbers' id d
|
||||
|
||||
/// Format the diff as a human-readable string.
|
||||
|
Reference in New Issue
Block a user