mirror of
https://github.com/Smaug123/WoofWare.Expect
synced 2025-10-19 18:48:44 +00:00
Compare commits
2 Commits
WoofWare.E
...
WoofWare.E
Author | SHA1 | Date | |
---|---|---|---|
|
6df614ab57 | ||
|
1cc253cb69 |
@@ -13,6 +13,12 @@
|
||||
"commands": [
|
||||
"fsharp-analyzers"
|
||||
]
|
||||
},
|
||||
"woofware.nunittestrunner": {
|
||||
"version": "0.3.4",
|
||||
"commands": [
|
||||
"woofware.nunittestrunner"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
90
CLAUDE.md
Normal file
90
CLAUDE.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
WoofWare.Expect is an F# expect/snapshot testing library (similar to Jest snapshots). The project consists of two main components:
|
||||
|
||||
- **WoofWare.Expect**: Core library that provides the `expect` computation expression and snapshot comparison functionality
|
||||
- **WoofWare.Expect.Test**: Test suite using NUnit that demonstrates and validates the library functionality
|
||||
|
||||
## Build and Development Commands
|
||||
|
||||
This project uses Nix for development environment management. All commands should be run within the Nix development shell:
|
||||
|
||||
```bash
|
||||
# Restore dependencies
|
||||
nix develop --command dotnet restore
|
||||
|
||||
# Build the project
|
||||
nix develop --command dotnet build --configuration Release
|
||||
|
||||
# Run tests
|
||||
nix develop --command dotnet test
|
||||
|
||||
# Pack NuGet package
|
||||
nix develop --command dotnet pack --configuration Release
|
||||
```
|
||||
|
||||
### Code Formatting and Analysis
|
||||
|
||||
```bash
|
||||
# Format F# code (via Nix)
|
||||
nix run .#fantomas -- .
|
||||
|
||||
# Check formatting without modifying files
|
||||
nix run .#fantomas -- --check .
|
||||
|
||||
# Run F# analyzers
|
||||
nix run .#fsharp-analyzers -- --project ./WoofWare.Expect/WoofWare.Expect.fsproj --analyzers-path ./.analyzerpackages/g-research.fsharp.analyzers/*/
|
||||
```
|
||||
|
||||
### Single Test Execution
|
||||
|
||||
NUnit's filtering is pretty borked.
|
||||
You can't apply filters that contain special characters in the test name (like a space character).
|
||||
You have to do e.g. `FullyQualifiedName~singleword` rather than `FullyQualifiedName~single word test`, but this only works on tests whose names are single words to begin with.
|
||||
|
||||
Instead of running `dotnet test`, you can perform a build (`dotnet build`) and then run `dotnet woofware.nunittestrunner WoofWare.Expect.Test/bin/Debug/net9.0/WoofWare.Expect.Test.dll`.
|
||||
This is an NUnit test runner which accepts a `--filter` arg that takes the same filter syntax as `dotnet test`, but actually parses it correctly: test names can contain spaces.
|
||||
(The most foolproof way to provide test names to WoofWare.NUnitTestRunner is by XML-encoding: e.g. `FullyQualifiedName="MyNamespace.MyTestsClass<ParameterType1%2CParameterType2>.MyTestMethod"`. The `~` query operator is also supported.)
|
||||
|
||||
## Architecture
|
||||
|
||||
### Core Components
|
||||
|
||||
- **Builder.fs**: Contains the `ExpectBuilder` computation expression that powers the `expect` syntax
|
||||
- **Domain.fs**: Core types including `CallerInfo`, `ExpectState`, and `SnapshotValue`
|
||||
- **SnapshotUpdate.fs**: Handles updating snapshot files when tests fail
|
||||
- **AstWalker.fs**: Uses Fantomas.FCS to parse F# source and locate/update snapshot strings
|
||||
- **Diff.fs**: Provides Patience and Myers diff algorithms for readable test output
|
||||
- **Dot.fs**: ASCII art rendering of dot files (requires `graph-easy`)
|
||||
|
||||
### Key Design Patterns
|
||||
|
||||
- **Computation Expression**: The library is built around F#'s computation expression syntax with `expect { }` blocks
|
||||
- **Source Code Manipulation**: Uses F# compiler services to locate and update snapshot strings in source files
|
||||
- **Dual Mode Operation**: Tests can run in assertion mode (normal) or update mode (to fix failing snapshots)
|
||||
|
||||
### Snapshot Types
|
||||
|
||||
- `snapshot`: Plain text comparison using `ToString()`
|
||||
- `snapshotJson`: JSON serialization with configurable options
|
||||
- `snapshotList`: Formatted list comparison
|
||||
- `snapshotThrows`: Exception expectation testing
|
||||
- `withFormat`: Custom formatting functions
|
||||
- `withJsonSerializerOptions`: Custom JSON serialization
|
||||
- `withJsonDocOptions`: Custom JSON parsing (e.g., for comments)
|
||||
|
||||
### Test Snapshot Management
|
||||
|
||||
- Individual updates: Add `'` to `expect` (making it `expect'`), run test to update, then remove `'`
|
||||
- Bulk updates: Use `GlobalBuilderConfig.enterBulkUpdateMode()` in test setup and `GlobalBuilderConfig.updateAllSnapshots()` in teardown
|
||||
|
||||
## Important Notes
|
||||
|
||||
- Use verbatim string literals for snapshots - the update mechanism requires this
|
||||
- Avoid format strings (`$"..."`) or string concatenation in snapshot definitions
|
||||
- The project follows strict F# conventions with warnings as errors
|
||||
- All commits go through comprehensive CI including formatting, analysis, and testing
|
@@ -598,10 +598,13 @@ type ExpectBuilder (mode : Mode) =
|
||||
match CompletedListSnapshotGeneric.passesAssertion state with
|
||||
| None ->
|
||||
match mode, GlobalBuilderConfig.isBulkUpdateMode () with
|
||||
| Mode.Update, _
|
||||
| _, true ->
|
||||
| Mode.Update, _ ->
|
||||
failwith
|
||||
"Snapshot assertion passed, but we are in snapshot-updating mode. Use the `expect` builder instead of `expect'` to assert the contents of a single snapshot; disable `GlobalBuilderConfig.bulkUpdate` to move back to assertion-checking mode."
|
||||
"Snapshot assertion passed, but we are in snapshot-updating mode. Use the `expect` builder instead of `expect'` to assert the contents of a single snapshot."
|
||||
| _, true ->
|
||||
// In bulk update mode, register passing tests instead of failing immediately
|
||||
// This allows tests with multiple snapshots to continue processing
|
||||
GlobalBuilderConfig.registerPassingTest state.Caller
|
||||
| _ -> ()
|
||||
| Some (expected, actual) -> raiseError expected actual
|
||||
|
||||
@@ -646,10 +649,13 @@ type ExpectBuilder (mode : Mode) =
|
||||
match CompletedSnapshotGeneric.passesAssertion state with
|
||||
| None ->
|
||||
match mode, GlobalBuilderConfig.isBulkUpdateMode () with
|
||||
| Mode.Update, _
|
||||
| _, true ->
|
||||
| Mode.Update, _ ->
|
||||
failwith
|
||||
"Snapshot assertion passed, but we are in snapshot-updating mode. Use the `expect` builder instead of `expect'` to assert the contents of a single snapshot; disable `GlobalBuilderConfig.bulkUpdate` to move back to assertion-checking mode."
|
||||
"Snapshot assertion passed, but we are in snapshot-updating mode. Use the `expect` builder instead of `expect'` to assert the contents of a single snapshot."
|
||||
| _, true ->
|
||||
// In bulk update mode, register passing tests instead of failing immediately
|
||||
// This allows tests with multiple snapshots to continue processing
|
||||
GlobalBuilderConfig.registerPassingTest state.Caller
|
||||
| _ -> ()
|
||||
| Some (expected, actual) -> raiseError expected actual
|
||||
|
||||
|
@@ -12,6 +12,8 @@ module GlobalBuilderConfig =
|
||||
|
||||
let private allTests : ResizeArray<CompletedSnapshot> = ResizeArray ()
|
||||
|
||||
let private passingTests : ResizeArray<CallerInfo> = ResizeArray ()
|
||||
|
||||
let internal isBulkUpdateMode () : bool =
|
||||
lock locker (fun () -> bulkUpdate.Value > 0)
|
||||
|
||||
@@ -34,18 +36,27 @@ module GlobalBuilderConfig =
|
||||
)
|
||||
|
||||
/// <summary>
|
||||
/// Clear the set of failing tests registered by any previous bulk-update runs.
|
||||
/// Clear the set of failing and passing tests registered by any previous bulk-update runs.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// You probably don't need to do this, because your test runner is probably tearing down
|
||||
/// anyway after the tests have failed; this is mainly here for WoofWare.Expect's own internal testing.
|
||||
/// </remarks>
|
||||
let clearTests () = lock locker allTests.Clear
|
||||
let clearTests () =
|
||||
lock
|
||||
locker
|
||||
(fun () ->
|
||||
allTests.Clear ()
|
||||
passingTests.Clear ()
|
||||
)
|
||||
|
||||
let internal registerTest (toAdd : CompletedSnapshot) : unit =
|
||||
lock locker (fun () -> allTests.Add toAdd)
|
||||
|
||||
let internal registerPassingTest (caller : CallerInfo) : unit =
|
||||
lock locker (fun () -> passingTests.Add caller)
|
||||
|
||||
/// <summary>
|
||||
/// For all tests whose failures have already been registered,
|
||||
/// transform the files on disk so that the failing snapshots now pass.
|
||||
@@ -60,8 +71,14 @@ module GlobalBuilderConfig =
|
||||
locker
|
||||
(fun () ->
|
||||
let allTests = Seq.toArray allTests
|
||||
let passingTestsArray = Seq.toArray passingTests
|
||||
|
||||
try
|
||||
// Check if we only had passing tests in bulk update mode - this should be an error
|
||||
if allTests.Length = 0 && passingTestsArray.Length > 0 then
|
||||
failwith
|
||||
"All snapshot assertions passed, but bulk-update mode was enabled. Disable bulk-update mode by not calling `GlobalBuilderConfig.enterBulkUpdateMode` to return to normal assertion-checking mode."
|
||||
|
||||
SnapshotUpdate.updateAll allTests
|
||||
finally
|
||||
// double acquiring of reentrant lock is OK, we're not switching threads
|
||||
|
@@ -333,5 +333,10 @@
|
||||
"pname": "Testably.Abstractions.FileSystem.Interface",
|
||||
"version": "9.0.0",
|
||||
"hash": "sha256-6JW+qDtqQT9StP4oTR7uO0NnmVc2xcjSZ6ds2H71wtg="
|
||||
},
|
||||
{
|
||||
"pname": "WoofWare.NUnitTestRunner",
|
||||
"version": "0.3.4",
|
||||
"hash": "sha256-OaBYMEAXUDiz9ei2/Zg4Q1A8BNDK1oaMB44uVz4UG/0="
|
||||
}
|
||||
]
|
||||
|
Reference in New Issue
Block a user