Current section
Files
Jump to
Current section
Files
src/dream_test@matchers@snapshot.erl
-module(dream_test@matchers@snapshot).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/dream_test/matchers/snapshot.gleam").
-export([clear_snapshot/1, clear_snapshots_in_directory/1, match_snapshot/2, match_snapshot_inspect/2]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Snapshot matchers for dream_test.\n"
"\n"
" Snapshot testing compares a value against a stored \"golden\" file.\n"
" On first run, the snapshot is created automatically. On subsequent runs,\n"
" the value is compared against the stored snapshot—any difference is a failure.\n"
"\n"
" ## Why Snapshot Testing?\n"
"\n"
" Snapshot tests excel at detecting **unintended changes** in complex outputs:\n"
"\n"
" - Rendered HTML/JSON/XML\n"
" - Error messages and logs\n"
" - Serialized data structures\n"
" - Any output where \"expected\" is hard to specify manually\n"
"\n"
" ## Basic Usage\n"
"\n"
" ```gleam\n"
" import dream_test/assertions/should.{should, match_snapshot, or_fail_with}\n"
"\n"
" it(\"renders user profile\", fn() {\n"
" render_profile(user)\n"
" |> should()\n"
" |> match_snapshot(\"./test/snapshots/user_profile.snap\")\n"
" |> or_fail_with(\"Profile should match snapshot\")\n"
" })\n"
" ```\n"
"\n"
" ## How It Works\n"
"\n"
" | Scenario | Behavior |\n"
" |-----------------------|-------------------------------------------|\n"
" | Snapshot missing | Creates it, test **passes** |\n"
" | Snapshot matches | Test **passes** |\n"
" | Snapshot differs | Test **fails** with diff |\n"
"\n"
" ## Updating Snapshots\n"
"\n"
" When you intentionally change output, update snapshots by deleting them:\n"
"\n"
" ```bash\n"
" # Update one snapshot\n"
" rm ./test/snapshots/user_profile.snap\n"
" gleam test\n"
"\n"
" # Update all snapshots in a directory\n"
" rm ./test/snapshots/*.snap\n"
" gleam test\n"
" ```\n"
"\n"
" Or use the helper functions:\n"
"\n"
" ```gleam\n"
" // In a setup script or before tests\n"
" let _ = snapshot.clear_snapshot(\"./test/snapshots/user_profile.snap\")\n"
" let _ = snapshot.clear_snapshots_in_directory(\"./test/snapshots\")\n"
" ```\n"
"\n"
" ## Snapshot File Organization\n"
"\n"
" Recommended structure:\n"
"\n"
" ```text\n"
" test/\n"
" ├── snapshots/\n"
" │ ├── api/\n"
" │ │ ├── users_list.snap\n"
" │ │ └── user_detail.snap\n"
" │ └── components/\n"
" │ ├── header.snap\n"
" │ └── footer.snap\n"
" └── my_test.gleam\n"
" ```\n"
"\n"
" Use descriptive paths that mirror your test structure.\n"
"\n"
" ## Testing Non-Strings\n"
"\n"
" For complex data structures, use `match_snapshot_inspect`:\n"
"\n"
" ```gleam\n"
" build_complex_result()\n"
" |> should()\n"
" |> match_snapshot_inspect(\"./test/snapshots/complex.snap\")\n"
" |> or_fail_with(\"Result should match snapshot\")\n"
" ```\n"
"\n"
" This uses Gleam's `string.inspect` to serialize the value.\n"
).
-file("src/dream_test/matchers/snapshot.gleam", 292).
?DOC(
" Delete a snapshot file to force regeneration.\n"
"\n"
" Use this to programmatically clear a snapshot. The next test run will\n"
" create a fresh snapshot with the current output.\n"
"\n"
" ## Parameters\n"
"\n"
" - `snapshot_path` - Path to the snapshot file to delete\n"
"\n"
" ## Returns\n"
"\n"
" - `Ok(Nil)` - Snapshot was deleted (or didn't exist)\n"
" - `Error(String)` - Human-readable error message\n"
"\n"
" ## Examples\n"
"\n"
" ### Clear Before Test\n"
"\n"
" ```gleam\n"
" // In a test setup\n"
" let _ = snapshot.clear_snapshot(\"./test/snapshots/user.snap\")\n"
" // Next test will create a fresh snapshot\n"
" ```\n"
"\n"
" ### Conditional Update\n"
"\n"
" ```gleam\n"
" case env.get(\"UPDATE_SNAPSHOTS\") {\n"
" Ok(\"true\") -> {\n"
" let _ = snapshot.clear_snapshot(\"./test/snapshots/output.snap\")\n"
" }\n"
" _ -> Nil\n"
" }\n"
" ```\n"
"\n"
" ## Idempotent Behavior\n"
"\n"
" This function succeeds even if the file doesn't exist:\n"
"\n"
" ```gleam\n"
" // Both calls succeed\n"
" let _ = snapshot.clear_snapshot(\"./test/snapshots/new.snap\")\n"
" let _ = snapshot.clear_snapshot(\"./test/snapshots/new.snap\")\n"
" ```\n"
).
-spec clear_snapshot(binary()) -> {ok, nil} | {error, binary()}.
clear_snapshot(Snapshot_path) ->
case dream_test_file_ffi:delete_file(Snapshot_path) of
{ok, nil} ->
{ok, nil};
{error, Error} ->
{error, dream_test@file:error_to_string(Error)}
end.
-file("src/dream_test/matchers/snapshot.gleam", 338).
?DOC(
" Delete all `.snap` files in a directory.\n"
"\n"
" Clears all snapshot files in the given directory (non-recursively).\n"
" Useful for resetting all snapshots before a major refactor.\n"
"\n"
" ## Parameters\n"
"\n"
" - `directory` - Path to the directory containing snapshots\n"
"\n"
" ## Returns\n"
"\n"
" - `Ok(Int)` - Number of snapshot files deleted\n"
" - `Error(String)` - Human-readable error message\n"
"\n"
" ## Examples\n"
"\n"
" ### Clear All Snapshots\n"
"\n"
" ```gleam\n"
" case snapshot.clear_snapshots_in_directory(\"./test/snapshots\") {\n"
" Ok(0) -> io.println(\"No snapshots to clear\")\n"
" Ok(n) -> io.println(\"Cleared \" <> int.to_string(n) <> \" snapshots\")\n"
" Error(msg) -> io.println(\"Error: \" <> msg)\n"
" }\n"
" ```\n"
"\n"
" ### Clear Subdirectory\n"
"\n"
" ```gleam\n"
" // Clear only API snapshots\n"
" let _ = snapshot.clear_snapshots_in_directory(\"./test/snapshots/api\")\n"
" ```\n"
"\n"
" ## Notes\n"
"\n"
" - Only deletes files with `.snap` extension\n"
" - Does **not** recurse into subdirectories\n"
" - Non-snapshot files are left untouched\n"
).
-spec clear_snapshots_in_directory(binary()) -> {ok, integer()} |
{error, binary()}.
clear_snapshots_in_directory(Directory) ->
case dream_test_file_ffi:delete_files_matching(Directory, <<".snap"/utf8>>) of
{ok, Count} ->
{ok, Count};
{error, Error} ->
{error, dream_test@file:error_to_string(Error)}
end.
-file("src/dream_test/matchers/snapshot.gleam", 368).
-spec make_mismatch_failure(binary(), binary(), binary()) -> dream_test@types:match_result(binary()).
make_mismatch_failure(Actual, Expected, Snapshot_path) ->
Payload = {snapshot_failure, Actual, Expected, Snapshot_path, false},
{match_failed,
{assertion_failure,
<<"match_snapshot"/utf8>>,
<<""/utf8>>,
{some, Payload}}}.
-file("src/dream_test/matchers/snapshot.gleam", 357).
-spec compare_snapshot(binary(), binary(), binary()) -> dream_test@types:match_result(binary()).
compare_snapshot(Actual, Expected, Snapshot_path) ->
case Actual =:= Expected of
true ->
{match_ok, Actual};
false ->
make_mismatch_failure(Actual, Expected, Snapshot_path)
end.
-file("src/dream_test/matchers/snapshot.gleam", 387).
-spec make_read_error_failure(binary(), dream_test@file:file_error()) -> dream_test@types:match_result(binary()).
make_read_error_failure(Snapshot_path, Error) ->
Payload = {snapshot_failure, <<""/utf8>>, <<""/utf8>>, Snapshot_path, true},
{match_failed,
{assertion_failure,
<<"match_snapshot"/utf8>>,
<<"Failed to read snapshot: "/utf8,
(dream_test@file:error_to_string(Error))/binary>>,
{some, Payload}}}.
-file("src/dream_test/matchers/snapshot.gleam", 412).
-spec make_write_failure(binary(), dream_test@file:file_error()) -> dream_test@types:match_result(binary()).
make_write_failure(Snapshot_path, Error) ->
Payload = {snapshot_failure, <<""/utf8>>, <<""/utf8>>, Snapshot_path, true},
{match_failed,
{assertion_failure,
<<"match_snapshot"/utf8>>,
<<"Failed to write snapshot: "/utf8,
(dream_test@file:error_to_string(Error))/binary>>,
{some, Payload}}}.
-file("src/dream_test/matchers/snapshot.gleam", 405).
-spec create_snapshot(binary(), binary()) -> dream_test@types:match_result(binary()).
create_snapshot(Actual, Snapshot_path) ->
case dream_test_file_ffi:write_file(Snapshot_path, Actual) of
{ok, nil} ->
{match_ok, Actual};
{error, Error} ->
make_write_failure(Snapshot_path, Error)
end.
-file("src/dream_test/matchers/snapshot.gleam", 349).
-spec check_snapshot(binary(), binary()) -> dream_test@types:match_result(binary()).
check_snapshot(Actual, Snapshot_path) ->
case dream_test_file_ffi:read_file(Snapshot_path) of
{ok, Expected} ->
compare_snapshot(Actual, Expected, Snapshot_path);
{error, {not_found, _}} ->
create_snapshot(Actual, Snapshot_path);
{error, Error} ->
make_read_error_failure(Snapshot_path, Error)
end.
-file("src/dream_test/matchers/snapshot.gleam", 163).
?DOC(
" Assert that a string value matches the content of a snapshot file.\n"
"\n"
" This is the primary snapshot matcher. Use it when you have string output\n"
" (JSON, HTML, plain text, etc.) that you want to compare against a snapshot.\n"
"\n"
" ## Behavior\n"
"\n"
" - **Snapshot doesn't exist**: Creates it and the test **passes**\n"
" - **Snapshot exists and matches**: Test **passes**\n"
" - **Snapshot exists but differs**: Test **fails** with detailed diff\n"
"\n"
" ## Parameters\n"
"\n"
" - `value_or_result` - The `MatchResult(String)` from the assertion chain\n"
" - `snapshot_path` - Path to the snapshot file (will be created if missing)\n"
"\n"
" ## Examples\n"
"\n"
" ### Basic Usage\n"
"\n"
" ```gleam\n"
" it(\"serializes user to JSON\", fn() {\n"
" user_to_json(sample_user)\n"
" |> should()\n"
" |> match_snapshot(\"./test/snapshots/user.json\")\n"
" |> or_fail_with(\"User JSON should match snapshot\")\n"
" })\n"
" ```\n"
"\n"
" ### With Transformation\n"
"\n"
" ```gleam\n"
" it(\"renders HTML correctly\", fn() {\n"
" render_page(data)\n"
" |> string.trim() // Normalize whitespace\n"
" |> should()\n"
" |> match_snapshot(\"./test/snapshots/page.html\")\n"
" |> or_fail_with(\"Page HTML should match snapshot\")\n"
" })\n"
" ```\n"
"\n"
" ### Error Handling\n"
"\n"
" ```gleam\n"
" it(\"handles parse errors gracefully\", fn() {\n"
" case parse(invalid_input) {\n"
" Ok(_) -> fail(\"Should have failed\")\n"
" Error(msg) ->\n"
" msg\n"
" |> should()\n"
" |> match_snapshot(\"./test/snapshots/parse_error.snap\")\n"
" |> or_fail_with(\"Error message should match snapshot\")\n"
" }\n"
" })\n"
" ```\n"
"\n"
" ## Updating the Snapshot\n"
"\n"
" ```bash\n"
" rm ./test/snapshots/user.json && gleam test\n"
" ```\n"
).
-spec match_snapshot(dream_test@types:match_result(binary()), binary()) -> dream_test@types:match_result(binary()).
match_snapshot(Value_or_result, Snapshot_path) ->
case Value_or_result of
{match_failed, Failure} ->
{match_failed, Failure};
{match_ok, Actual} ->
check_snapshot(Actual, Snapshot_path)
end.
-file("src/dream_test/matchers/snapshot.gleam", 456).
-spec make_mismatch_failure_inspect(binary(), binary(), binary()) -> dream_test@types:match_result(any()).
make_mismatch_failure_inspect(Actual, Expected, Snapshot_path) ->
Payload = {snapshot_failure, Actual, Expected, Snapshot_path, false},
{match_failed,
{assertion_failure,
<<"match_snapshot_inspect"/utf8>>,
<<""/utf8>>,
{some, Payload}}}.
-file("src/dream_test/matchers/snapshot.gleam", 444).
-spec compare_snapshot_inspect(GDG, binary(), binary(), binary()) -> dream_test@types:match_result(GDG).
compare_snapshot_inspect(Value, Serialized, Expected, Snapshot_path) ->
case Serialized =:= Expected of
true ->
{match_ok, Value};
false ->
make_mismatch_failure_inspect(Serialized, Expected, Snapshot_path)
end.
-file("src/dream_test/matchers/snapshot.gleam", 475).
-spec make_read_error_failure_inspect(binary(), dream_test@file:file_error()) -> dream_test@types:match_result(any()).
make_read_error_failure_inspect(Snapshot_path, Error) ->
Payload = {snapshot_failure, <<""/utf8>>, <<""/utf8>>, Snapshot_path, true},
{match_failed,
{assertion_failure,
<<"match_snapshot_inspect"/utf8>>,
<<"Failed to read snapshot: "/utf8,
(dream_test@file:error_to_string(Error))/binary>>,
{some, Payload}}}.
-file("src/dream_test/matchers/snapshot.gleam", 504).
-spec make_write_failure_inspect(binary(), dream_test@file:file_error()) -> dream_test@types:match_result(any()).
make_write_failure_inspect(Snapshot_path, Error) ->
Payload = {snapshot_failure, <<""/utf8>>, <<""/utf8>>, Snapshot_path, true},
{match_failed,
{assertion_failure,
<<"match_snapshot_inspect"/utf8>>,
<<"Failed to write snapshot: "/utf8,
(dream_test@file:error_to_string(Error))/binary>>,
{some, Payload}}}.
-file("src/dream_test/matchers/snapshot.gleam", 493).
-spec create_snapshot_inspect(GDM, binary(), binary()) -> dream_test@types:match_result(GDM).
create_snapshot_inspect(Value, Serialized, Snapshot_path) ->
case dream_test_file_ffi:write_file(Snapshot_path, Serialized) of
{ok, nil} ->
{match_ok, Value};
{error, Error} ->
make_write_failure_inspect(Snapshot_path, Error)
end.
-file("src/dream_test/matchers/snapshot.gleam", 430).
-spec check_snapshot_inspect(GDE, binary()) -> dream_test@types:match_result(GDE).
check_snapshot_inspect(Value, Snapshot_path) ->
Serialized = gleam@string:inspect(Value),
case dream_test_file_ffi:read_file(Snapshot_path) of
{ok, Expected} ->
compare_snapshot_inspect(Value, Serialized, Expected, Snapshot_path);
{error, {not_found, _}} ->
create_snapshot_inspect(Value, Serialized, Snapshot_path);
{error, Error} ->
make_read_error_failure_inspect(Snapshot_path, Error)
end.
-file("src/dream_test/matchers/snapshot.gleam", 233).
?DOC(
" Assert that any value matches a snapshot when serialized.\n"
"\n"
" Use this when testing complex data structures that aren't strings.\n"
" The value is converted to a string using `string.inspect`, which\n"
" produces Gleam's debug representation.\n"
"\n"
" ## When to Use This\n"
"\n"
" - Testing return values of functions (records, tuples, lists)\n"
" - Comparing parsed AST structures\n"
" - Verifying complex state after operations\n"
"\n"
" ## Parameters\n"
"\n"
" - `value_or_result` - The `MatchResult(value)` from the assertion chain\n"
" - `snapshot_path` - Path to the snapshot file\n"
"\n"
" ## Examples\n"
"\n"
" ### Testing a Record\n"
"\n"
" ```gleam\n"
" it(\"parses config correctly\", fn() {\n"
" parse_config(raw_toml)\n"
" |> should()\n"
" |> match_snapshot_inspect(\"./test/snapshots/config.snap\")\n"
" |> or_fail_with(\"Parsed config should match snapshot\")\n"
" })\n"
" ```\n"
"\n"
" ### Testing a List\n"
"\n"
" ```gleam\n"
" it(\"filters users correctly\", fn() {\n"
" users\n"
" |> list.filter(is_active)\n"
" |> should()\n"
" |> match_snapshot_inspect(\"./test/snapshots/active_users.snap\")\n"
" |> or_fail_with(\"Active users should match snapshot\")\n"
" })\n"
" ```\n"
"\n"
" ## Snapshot Format\n"
"\n"
" The snapshot will contain the Gleam debug representation:\n"
"\n"
" ```text\n"
" User(name: \"Alice\", age: 30, active: True)\n"
" ```\n"
"\n"
" ```text\n"
" [User(name: \"Alice\", age: 30), User(name: \"Bob\", age: 25)]\n"
" ```\n"
"\n"
" ## Note on Stability\n"
"\n"
" The `string.inspect` output may change between Gleam versions.\n"
" If you need stable serialization, convert to JSON or another format\n"
" and use `match_snapshot` instead.\n"
).
-spec match_snapshot_inspect(dream_test@types:match_result(GCR), binary()) -> dream_test@types:match_result(GCR).
match_snapshot_inspect(Value_or_result, Snapshot_path) ->
case Value_or_result of
{match_failed, Failure} ->
{match_failed, Failure};
{match_ok, Value} ->
check_snapshot_inspect(Value, Snapshot_path)
end.