-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.