{"id":16819698,"url":"https://github.com/knz/catwalk","last_synced_at":"2025-03-22T03:31:34.880Z","repository":{"id":59045665,"uuid":"531005672","full_name":"knz/catwalk","owner":"knz","description":"test library for Bubbletea TUI models","archived":false,"fork":false,"pushed_at":"2023-06-13T08:41:32.000Z","size":130,"stargazers_count":48,"open_issues_count":2,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-18T07:43:12.636Z","etag":null,"topics":["golang","hacktoberfest","testing"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/knz.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}},"created_at":"2022-08-31T08:55:39.000Z","updated_at":"2025-02-10T16:21:12.000Z","dependencies_parsed_at":"2024-06-19T01:44:45.894Z","dependency_job_id":null,"html_url":"https://github.com/knz/catwalk","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/knz%2Fcatwalk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/knz%2Fcatwalk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/knz%2Fcatwalk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/knz%2Fcatwalk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/knz","download_url":"https://codeload.github.com/knz/catwalk/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244902929,"owners_count":20529114,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["golang","hacktoberfest","testing"],"created_at":"2024-10-13T10:54:23.374Z","updated_at":"2025-03-22T03:31:34.411Z","avatar_url":"https://github.com/knz.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# catwalk\n\n[![Latest Release](https://img.shields.io/github/release/knz/catwalk.svg)](https://github.com/knz/catwalk/releases)\n[![GoDoc](https://godoc.org/github.com/golang/gddo?status.svg)](https://pkg.go.dev/github.com/knz/catwalk)\n[![Build Status](https://github.com/knz/catwalk/workflows/build/badge.svg)](https://github.com/knz/catwalk/actions)\n[![Go ReportCard](https://goreportcard.com/badge/knz/catwalk)](https://goreportcard.com/report/knz/catwalk)\n[![Coverage Status](https://coveralls.io/repos/github/knz/catwalk/badge.svg)](https://coveralls.io/github/knz/catwalk)\n\n**catwalk** is a unit test library for [Bubbletea](https://github.com/charmbracelet/bubbletea) TUI models (a.k.a. “bubbles”).\n\nIt enables implementers to verify the state of models and their `View`\nas they process `tea.Msg` objects through their `Update` method.\n\nIt is implemented on top of\n[datadriven](https://github.com/cockroachdb/datadriven), an extension\nto Go's simple \"table-driven testing\" idiom.\n\nDatadriven tests use data files containing both the reference input\nand output, instead of a data structure. Each data file can contain\nmultiple tests. When the implementation changes, the reference output\ncan be quickly updated by re-running the tests with the `-rewrite`\nflag.\n\n**Why the name:** the framework forces the Tea models to \"show\nthemselves\" on the runway of the test input files.\n\n## Example\n\nLet's test the `viewport` [Bubble](https://github.com/charmbracelet/bubbles)!\n\nFirst, we define a top-level model around `viewport`:\n\n```go\ntype Model struct {\n    viewport.Model\n}\n\nvar _ tea.Model = (*Model)(nil)\n\n// New initializes a new model.\nfunc New(width, height int) *Model {\n    return \u0026Model{\n        Model: viewport.New(width, height),\n    }\n}\n\n// Init adds some initial text inside the viewport.\nfunc (m *Model) Init() tea.Cmd {\n    cmd := m.Model.Init()\n    m.SetContent(`first line\nsecond line\nthird line\nfourth line\nfifth line\nsixth line`)\n    return cmd\n}\n```\n\nThen, we define a Go test which runs the above model:\n\n```go\nfunc TestModel(t *testing.T) {\n\t// Initialize the model to test.\n\tm := New(40, 3)\n\n    // Run all the tests in input file \"testdata/viewport_tests\"\n\tcatwalk.RunModel(t, \"testdata/viewport_tests\", m)\n}\n```\n\nThen, we populate some test directives inside `testdata/viewport_tests`:\n\n``` go\nrun\n----\n\n# One line down\nrun\ntype j\n----\n\n# Two lines down\nrun\ntype jj\n----\n\n# One line up\nrun\nkey up\n----\n```\n\nThen, we run the test: `go test .`.\n\nWhat happens: the test fails!\n\n```\n--- FAIL: TestModel (0.00s)\n    catwalk.go:64:\n        testdata/viewport_tests:1:\n        expected:\n\n        found:\n        -- view:\n        first line␤\n        second line␤\n        third line🛇\n```\n\nThis is because we haven't yet expressed\nwhat is the **expected output** for each step.\n\nBecause it's tedious to do this manually, we can auto-generate\nthe expected output from the actual output, using the `rewrite` flag:\n\n     go test . -args -rewrite\n\nObserve what happened with the input file:\n\n``` go\nrun\n----\n-- view:\nfirst line␤\nsecond line␤\nthird line🛇\n\n# One line down\nrun\ntype j\n----\n-- view:\nsecond line␤\nthird line␤\nfourth line🛇\n\n# Two lines down\nrun\ntype jj\n----\n-- view:\nfourth line␤\nfifth line␤\nsixth line🛇\n\n# One line up\nrun\nkey up\n----\n-- view:\nthird line␤\nfourth line␤\nfifth line🛇\n```\n\nNow each expected output reflects how the `viewport` reacts\nto the key presses. Now also `go test .` succeeds.\n\n## Structure of a test file\n\nTest files contain zero or more tests, with the following structure:\n\n``` go\n\u003cdirective\u003e \u003carguments\u003e\n\u003coptional: input commands...\u003e\n----\n\u003cexpected output\u003e\n```\n\nFor example:\n\n``` go\nrun\n----\n-- view:\nMy bubble rendered here.\n\nrun\ntype q\n----\n-- view:\nMy bubble reacted to \"q\".\n```\n\nCatwalk supports the following directives:\n\n- `run`: apply state changes to the  model via its `Update` method, then show the results.\n- `set`/`reset`: change configuration variables.\n\nFinally, directives can take arguments. For example:\n\n```\nrun observe=(gostruct,view)\n----\n```\n\nThis is explained further in the next sections.\n\n## The `run` directive\n\n`run` defines one unit test. It applies some input commands to the\nmodel then compares the resulting state of the model with a reference\nexpected output.\n\nUnder `run`, the following input commands are supported:\n\n- `type \u003ctext\u003e`: produce a series of `tea.KeyMsg` with type\n  `tea.KeyRunes`. Can contain spaces.\n\n  For example: `type abc` produces 3 key presses for a, b, c.\n\n- `enter \u003ctext\u003e`: like `type`, but also add a key press for the\n  `enter` key at the end.\n\n- `key \u003ckeyname\u003e`: produce one `tea.KeyMsg` for the given key.\n\n  For example: `key ctrl+c`\n\n- `paste \"\u003ctext\u003e\"`: paste the text as a single key event.\n  The text can contain Go escape sequences.\n\n  For example: `paste \"hello\\nworld\"`\n\n- `resize \u003cW\u003e \u003cH\u003e`: produce a `tea.WindowSizeMsg` with the specified size.\n\nYou can also add support for your own input commands by passing an\n`Updater` function to `catwalk.RunModel` with the `WithUpdater()`\noption, and combine multiple updaters together using the\n`ChainUpdater()` function.\n\nThe `run` directive accepts the following arguments:\n\n- `observe`: what to look at as expected output (`observe=xx` or `observe=(xx,yy)`).\n\n  By default, `observe` is set to `view`: look at the model's `View()` method.\n  Alternatively, you can use the following observers:\n\n  - `gostruct`: show the contents of the model object as a go struct.\n  - `debug`: call the model's `Debug() string` method, if defined.\n\n  You can also add your own observers using the `WithObserver()` option.\n\n- `trace`: detail the intermediate steps of the test.\n\n  Used for debugging tests.\n\n## The `set` and `reset` directives\n\nThese can be used to configure parameters in the test driver.\n\nFor example:\n\n``` go\nset cmd_timeout=100ms\n----\ncmd_timeout: 100ms\n\nreset cmd_timeout\n----\nok\n```\n\nThe following parameters are currently recognized:\n\n- `cmd_timeout`: how long to wait for a `tea.Cmd` to complete.\n  This is set by default to 20ms, which is sufficient to\n  ignore the commands of a blinking cursor.\n\n## Advanced topic: testing style changes\n\nMany [bubbles](https://github.com/charmbracelet/bubbles) have a\n`Styles` struct with configurable styles (using [lipgloss](https://github.com/charmbracelet/lipgloss)).  It's useful to verify\nthat the bubbles react properly when the styles are reconfigured at\nrun-time.\n\nFor this, you can tell catwalk about your styles\nthis will activate the following special `run` input commands:\n\n```\nrestyle \u003cstylefield\u003e \u003cnewstyle...\u003e`\n```\n\n\nFor example: `restyle mymodel.ValueStyle foreground: #f00` changes the\n`ValueStyle` style to use the color red, as if `.ValueStyle.Foreground(lipgloss.Color(\"#f00\"))` was called.\n\nTo activate, use the option `catwalk.WithUpdater(catwalk.StylesUpdater(...))`. For example:\n\n``` go\nfunc TestStyles(t *testing.T) {\n  m := New(...)\n  catwalk.RunModel(t, \"testdata/styles\", m, catwalk.WithUpdater(\n    // The string \"hello\" is the prefix for identifying the styles container in tests.\n    // Useful when there are multiple nested models.\n    catwalk.StylesUpdater(\"hello\",\n      func(m tea.Model, fn func(interface{}) error) (tea.Model, error) {\n        tm := m.(myModel)\n        err := fn(\u0026tm)\n        return tm, err\n    }),\n  ))\n}\n```\n\nAfter this, the input command `restyle hello.X ...` will automatically\naffect the style `.X` in your model.\n\nAlternatively, if your model implements `tea.Model` by reference\n(i.e. the address of its styles does not change between `Update`\ncalls), you can simplify as follows:\n\n``` go\nfunc TestStyles(t *testing.T) {\n  m := New(...)\n  catwalk.RunModel(t, \"testdata/bindings\", \u0026m, catwalk.WithUpdater(\n    // The string \"hello\" is the prefix for identifying the styles container in tests.\n    // Useful when there are multiple nested models.\n    KeyMapUpdater(\"hello\", catwalk.SimpleStylesApplier(\u0026m))))\n}\n```\n\nSee the test `TestStyles` in `styles_test.go` and the input file\n`testdata/styles` for an example.\n\n## Advanced topic: testing key bindings\n\nMany [bubbles](https://github.com/charmbracelet/bubbles) have a\n`KeyMap` struct with configurable key bindings.  It's useful to verify\nthat the bubbles react properly when the keymaps are reconfigured at\nrun-time.\n\nFor this, you can tell catwalk about your `KeyMap` struct and\nthis will activate the following special `run` input commands:\n\n- `keybind \u003ckeymapfield\u003e \u003cnewbinding\u003e`\n\n  For example: `keybind mykeys.CursorUp up j` rebinds the `CursorUp`\n  binding in the KeyMap `mykeys` as if\n  `key.NewBinding(key.WithKeys(\"up\", \"j\"))` was called.\n\n- `keyhelp \u003ckeymapfield\u003e \u003chelpkey\u003e \u003chelptext\u003e`\n\n  For example: `keybind mykeys.CursorUp up move the cursor up` rebinds\n  the `CursorUp` binding in the KeyMap `mykeys` as if\n  `key.NewBinding(key.WithHelp(\"up\", \"move the cursor up\"))` was\n  called.\n\nTo declare a `KeyMap` in a test, use the option `catwalk.WithUpdater(catwalk.KeyMapUpdater(...))`. For example:\n\n``` go\nfunc TestBindings(t *testing.T) {\n  m := New(...)\n  catwalk.RunModel(t, \"testdata/bindings\", m, catwalk.WithUpdater(\n    // The string \"hello\" is the prefix for identifying the keymap in tests.\n\t// Useful when the model contains multiple keymaps.\n    catwalk.KeyMapUpdater(\"hello\",\n      func(m tea.Model, fn func(interface{}) error) (tea.Model, error) {\n        tm := m.(YourModel)\n        err := fn(\u0026tm.KeyMap)\n        return tm, err\n    }),\n  ))\n}\n```\n\nAfter this, the input command `keybind hello.X ...` will automatically\naffect the binding `.KeyMap.X` in your model.\n\nAlternatively, if your model implements `tea.Model` by reference (i.e. the address of its KeyMap does not change between `Update` calls), you can simplify as follows:\n\n``` go\nfunc TestBindings(t *testing.T) {\n  m := New(...)\n  catwalk.RunModel(t, \"testdata/bindings\", \u0026m, catwalk.WithUpdater(\n    // The string \"hello\" is the prefix for identifying the keymap in tests.\n\t// Useful when the model contains multiple keymaps.\n    KeyMapUpdater(\"hello\", catwalk.SimpleKeyMapApplier(\u0026m.KeyMap))))\n}\n```\n\nSee the test `TestRebind` in `bindings_test.go` and the input file\n`testdata/bindings` for an example.\n\n## Your turn!\n\nYou can start using `catwalk` in your Bubbletea / Charm projects right\naway!\n\nIf you have any questions or comments:\n\n- for bug fixes, feature requests, etc., [file an issue]()\n- for questions, suggestions, etc. you can come chat on the [Charm\n  Discord](https://charm.sh/chat/).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fknz%2Fcatwalk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fknz%2Fcatwalk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fknz%2Fcatwalk/lists"}