{"id":42286739,"url":"https://github.com/waddie/still","last_synced_at":"2026-06-06T02:01:35.737Z","repository":{"id":324904842,"uuid":"1098920600","full_name":"waddie/still","owner":"waddie","description":"Self-modifying snapshot testing for Clojure/ClojureScript/Babashka","archived":false,"fork":false,"pushed_at":"2026-05-06T09:48:40.000Z","size":1385,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-06T11:15:00.933Z","etag":null,"topics":["babashka","clojure","clojurescript","snapshot-testing","testing","works-on-my-machine"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/waddie.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-11-18T10:13:42.000Z","updated_at":"2026-05-06T09:48:16.000Z","dependencies_parsed_at":"2025-11-18T16:00:09.971Z","dependency_job_id":null,"html_url":"https://github.com/waddie/still","commit_stats":null,"previous_names":["waddie/still"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/waddie/still","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/waddie%2Fstill","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/waddie%2Fstill/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/waddie%2Fstill/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/waddie%2Fstill/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/waddie","download_url":"https://codeload.github.com/waddie/still/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/waddie%2Fstill/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33966639,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-06T02:00:07.033Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["babashka","clojure","clojurescript","snapshot-testing","testing","works-on-my-machine"],"created_at":"2026-01-27T09:18:10.375Z","updated_at":"2026-06-06T02:01:35.732Z","avatar_url":"https://github.com/waddie.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Clojars Project](https://img.shields.io/clojars/v/dev.tomwaddington/still.svg)](https://clojars.org/dev.tomwaddington/still)\n\n# Still\n\nSelf-modifying snapshot testing for Clojure/ClojureScript/Babashka, inspired by [juxt/snap](https://github.com/juxt/snap) and Ian Henry’s [\"My Kind of REPL\"](https://ianthehenry.com/posts/my-kind-of-repl/) and [Judge](https://github.com/ianthehenry/judge).\n\n`still.core/snap` saves snapshots to the filesystem. `still.core/snap!` will modify your source code in place.\n\nSnapshots behave like `clojure.test/is` when inside a `deftest`, like standard assertions when used inline, with REPL-friendly output in interactive sessions.\n\n- Optional colour diffing\n- Auto-update mode,\n- Custom serialisers to handle timestamps, UUIDs, and custom types\n- Snapshot metadata to track creation date, platform, etc.\n\n## Demo\n\n![An asciinema recording of using still in Helix via nrepl.hx](https://github.com/waddie/still/blob/main/images/still.gif?raw=true)\n\n## Installation\n\n### Clojure (deps.edn)\n\n```clojure\n{:deps {dev.tomwaddington/still {:mvn/version \"RELEASE\"}}\n :aliases {:repl {:extra-deps {nrepl/nrepl {:mvn/version \"1.5.1\"}}}}}\n```\n\n**Note:** For best REPL experience with `snap!`, use nREPL 1.5.0 or later with a client supporting filenames in regular eval. This enables automatic file detection during REPL eval operations.\n\n### Babashka (bb.edn)\n\n```clojure\n{:deps {dev.tomwaddington/still {:mvn/version \"RELEASE\"}\n        rewrite-clj/rewrite-clj {:mvn/version \"1.2.50\"}\n        lambdaisland/deep-diff2 {:mvn/version \"2.12.219\"}}}\n```\n\n## Quick start\n\n```clojure\n(ns my-app.test\n  (:require [clojure.test :refer [deftest testing is]]\n            [still.core :refer [snap snap!]]))\n\n(deftest user-creation-test\n  (testing \"creates user with correct shape\"\n    (let [user (create-user {:name \"Alice\" :email \"alice@example.com\"})]\n      ;; First run: creates snapshot in test/still/user_creation.edn\n      ;; Subsequent runs: compares against stored snapshot\n      (snap :user-creation user))))\n\n(deftest inline-snapshot-test\n  (testing \"inline snapshots\"\n    ;; First run: edits this file to add expected value\n    ;; Becomes: (snap! (compute-result) {:result 42})\n    (snap! (compute-result))))\n```\n\n## API\n\n### `snap` - Filesystem-based snapshots\n\nCompares a value against a stored snapshot file. Behaviour adapts to three contexts:\n\n**Inside deftest (test context):**\n\n- Uses `clojure.test/is` for assertions\n- Failures appear in test runner output\n- Integrates with CI/CD pipelines\n\n**Outside deftest in REPL (interactive context):**\n\n- Returns boolean (true if match, false if mismatch)\n- Prints friendly messages to `stdout`\n- No test framework overhead\n\n**Outside deftest and REPL (assertion context):**\n\n- Throws `AssertionError` on mismatch\n- Returns true on match\n- No output unless there’s an error\n- Prevents noise during namespace loading\n\n```clojure\n;; In a test\n(deftest api-test\n  (snap :api-response (fetch-data)))\n\n;; In the REPL\n(snap :api-response (fetch-data))\n;; =\u003e ✓ Snapshot matches: :api-response\n;; =\u003e true\n\n;; Disable all snapshots using *assert*\n(set! *assert* false)\n(snap :any-key {:any \"value\"})\n;; =\u003e true (always passes, no checking)\n```\n\n### `snap!` - inline snapshots (JVM/Babashka only)\n\nLike `snap`, but stores expected values directly in source code. When called without an expected value, automatically edits the source file.\n\n```clojure\n;; First run - edits source file\n(snap! (+ 1 2))\n\n;; After first run, the line becomes:\n(snap! (+ 1 2) 3)\n\n;; Subsequent runs compare against inline value\n```\n\n**REPL usage:** For `snap!` to work when evaluating forms in the REPL (not loading files):\n\n- Use nREPL 1.5.0+ with a supporting client\n- OR load the file instead of evaluating individual forms\n\n## Configuration\n\nConfigure `still` via multiple sources (later sources override earlier):\n\n1. Default configuration\n1. `deps.edn`/`bb.edn`/`project.clj` (`:still/config` key)\n1. Environment variables\n1. Runtime overrides (highest priority)\n\n### Configuration Options\n\n```clojure\n{:snapshot-dir \"test/still\"        ; Where snapshots are stored\n :auto-update? false               ; Auto-update mismatched snapshots\n :metadata? true                   ; Track snapshot metadata\n :serializers {}                   ; Custom type serialisers\n :diff-context-lines 3             ; Context lines in diffs\n :color? false}                    ; ANSI colours in output\n ; Note: :colour? and :serialisers are also accepted\n```\n\n**Enable/Disable:** Use Clojure’s `*assert*` to enable/disable snapshots:\n\n```clojure\n;; Disable snapshots (compiles out snap! macro completely)\n(set! *assert* false)\n\n;; Re-enable\n(set! *assert* true)\n```\n\n### In deps.edn\n\n```clojure\n{:still/config {:snapshot-dir \"test/snapshots\"\n                :auto-update? false}}\n```\n\n### Runtime Override\n\n```clojure\n(require '[still.config :as config])\n\n;; Replace entire config\n(config/override! {:snapshot-dir \"test/custom\"})\n\n;; Merge into config\n(config/merge-override! {:auto-update? true})\n```\n\n### Environment variables\n\n```sh\nexport STILL_SNAPSHOT_DIR=\"test/snapshots\"\nexport STILL_AUTO_UPDATE=\"false\"\n```\n\n## Auto-update mode\n\nUpdate all mismatched snapshots automatically\n\n```sh\n# Via environment variable\nSTILL_AUTO_UPDATE=true clj -M:test\n```\n\n```clojure\n;; Or programmatically\n(require '[still.update :as update])\n(update/enable-auto-update!)\n```\n\n## Custom serialisers\n\nHandle unstable values like timestamps and UUIDs:\n\n```clojure\n(require '[still.serialize :as serialize])\n\n;; Timestamps are automatically serialised as ISO-8601\n(snap :with-timestamp {:id 123 :created-at (java.util.Date.)})\n;; =\u003e {:id 123 :created-at {:type :still.serialize/date :iso8601 \"2025-...\"}}\n\n;; Register custom serialiser for your types\n(defrecord Person [name age])\n\n(serialize/register-serializer! Person\n  (fn [p] {:type ::person :name (:name p) :age (:age p)}))\n\n(snap :person (-\u003ePerson \"Alice\" 30))\n```\n\n### Diff visualisation\n\nFull-colour diffs powered by `deep-diff2`:\n\n```clojure\n(require '[still.diff :as diff])\n\n;; Generate and print a diff\n(diff/print-diff {:a 1 :b 2} {:a 1 :b 3})\n\n;; Get diff as string\n(diff/diff-str expected actual)\n\n;; Side-by-side comparison\n(println (diff/side-by-side expected actual))\n```\n\n### Snapshot management\n\n```clojure\n(require '[still.update :as update])\n\n;; List all snapshots\n(update/print-summary)\n\n;; Enable auto-update for session\n(update/enable-auto-update!)\n\n;; Delete all snapshots (careful!)\n(update/delete-all-snapshots!)\n```\n\n## REPL workflow\n\n`still` is designed for interactive development:\n\n```clojure\n;; Load your namespace\n(require '[my-app.core :as core])\n(require '[still.core :refer [snap snap!]])\n\n;; Test a function interactively\n(snap :user-response (core/create-user {:name \"Alice\"}))\n;; =\u003e ✓ Snapshot created: :user-response\n;; =\u003e true\n\n;; Modify the function, run again\n(snap :user-response (core/create-user {:name \"Alice\"}))\n;; =\u003e ✗ Snapshot mismatch: :user-response\n;; =\u003e (shows colourful diff)\n;; =\u003e false\n\n;; Looks good? Update the snapshot\n(config/merge-override! {:auto-update? true})\n(snap :user-response (core/create-user {:name \"Alice\"}))\n;; =\u003e ✓ Snapshot updated: :user-response\n;; =\u003e true\n```\n\n## Running tests\n\n### Clojure\n\n```sh\n# Run tests\nclj -M:test -m cognitect.test-runner\n\n# Update all snapshots\nSTILL_AUTO_UPDATE=true clj -M:test -m cognitect.test-runner\n\n# Disable assertions (and snapshots) at compile time\nclj -M:compile -e \"(set! *assert* false)\"\n```\n\n### Babashka\n\n```sh\n# Run tests\nbb test\n\n# Verify namespaces load\nbb verify\n\n# Start REPL with Still loaded\nbb repl\n\n# Update snapshots\nSTILL_AUTO_UPDATE=true bb test\n```\n\n## License\n\nCopyright © 2025 Tom Waddington\n\nDistributed under the MIT License. See LICENSE file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwaddie%2Fstill","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwaddie%2Fstill","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwaddie%2Fstill/lists"}