{"id":19715343,"url":"https://github.com/axentro/spinach","last_synced_at":"2026-01-01T23:43:03.656Z","repository":{"id":140836681,"uuid":"206990177","full_name":"Axentro/spinach","owner":"Axentro","description":"BDD Style test runner","archived":false,"fork":false,"pushed_at":"2021-04-24T12:50:11.000Z","size":1142,"stargazers_count":4,"open_issues_count":4,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-01-21T20:48:27.856Z","etag":null,"topics":["bdd","crystal"],"latest_commit_sha":null,"homepage":"","language":"Crystal","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/Axentro.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":"2019-09-07T15:48:18.000Z","updated_at":"2023-10-17T14:49:36.000Z","dependencies_parsed_at":null,"dependency_job_id":"db1ab77d-aeca-401e-99e0-1fc466cb7559","html_url":"https://github.com/Axentro/spinach","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Axentro%2Fspinach","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Axentro%2Fspinach/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Axentro%2Fspinach/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Axentro%2Fspinach/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Axentro","download_url":"https://codeload.github.com/Axentro/spinach/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243685653,"owners_count":20330995,"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":["bdd","crystal"],"created_at":"2024-11-11T22:38:04.611Z","updated_at":"2026-01-01T23:43:03.607Z","avatar_url":"https://github.com/Axentro.png","language":"Crystal","funding_links":[],"categories":[],"sub_categories":[],"readme":"# spinach\n\nBDD Style spec runner for Crystal. This project is fairly experimental and was written in a day so will be gradually improved over time. Please raise any feature requests or bugs.\n\n## Installation\n\n1. Add the dependency to your `shard.yml`:\n\n   ```yaml\n   dependencies:\n     spinach:\n       github: sushichain/spinach\n   ```\n\n2. Run `shards install`\n\n## Usage\n\n```crystal\nrequire \"spinach\"\n```\n\nThe basic concept is that you write executable specifications in an HTML file that has a companion supporting crystal file. You then execute the crystal file and it will execute a spec and produce an augmented HTML file with the results.\n\nYou put your html file and supporting crystal file in the `spec` folder.\n\nThere are 3 directives:\n\n* scenario\n* table_scenario\n* assert_equals\n* set\n* execute\n* status\n\nIn your HTML you use these to write the specs. See below. See also the specs of this project for examples.\n\nFor each scenario the directives are located and then executed. The order in which directives are located is as follows:\n\n1. table_scenario\n2. scenario\n3. set\n4. execute\n5. assert_equals\n6. status\n\n## Scenario\n\n```html\n\u003cdiv spinach:scenario=\"#scenario1\"\u003e\n  If my name is \u003cb spinach:set=\"#username\"\u003eChuck Norris\u003c/b\u003e.\u003cbr/\u003e\n  Then my username should be \u003cb spinach:assert_equals=\"#username\"\u003eChuck Norris2\u003c/b\u003e.\n\u003c/div\u003e\n```\n\nEach spec file must contain at least one scenario. A scenario is used to provide scope for the commands. When a spec runs\nthe variables used in the scenario are scoped to that specific scenario. So a scenario **MUST** be placed on a `Div` HTML element that\nis a parent and then all the spec commands should go on HTML nodes that are children of this parent node. See the specs folder for\nexamples\n\n## Table Scenario\n\n```html\n  \u003ctable spinach:table_scenario=\"scenario 1\" class=\"table table-striped\"\u003e\n      \u003cthead class=\"thead-dark\"\u003e\n        \u003ctr\u003e\n          \u003cth spinach:set=\"firstname\"\u003eFirst name\u003c/th\u003e\n          \u003cth spinach:set=\"lastname\"\u003eLast name\u003c/th\u003e\n          \u003cth spinach:assert_equals=\"greeting.login_greeting\"\u003eGreeting\u003c/th\u003e\n          \u003cth spinach:assert_equals=\"greeting.login_message\"\u003eMessage\u003c/th\u003e\n          \u003cth spinach:execute=\"greeting = greeting_for(firstname, lastname)\"\u003e\u003c/th\u003e\n        \u003c/tr\u003e\n        \u003c/thead/\u003e\n        \u003ctbody\u003e\n          \u003ctr\u003e\n            \u003ctd\u003eBob2\u003c/td\u003e\n            \u003ctd\u003eBobbington\u003c/td\u003e\n            \u003ctd\u003eHello Bob!\u003c/td\u003e\n            \u003ctd\u003eYour last name is Bobbington!\u003c/td\u003e\n            \u003ctd\u003e\u003c/td\u003e\n          \u003c/tr\u003e\n        \u003c/tbody\u003e\n    \u003c/table\u003e\n```\nYou add `spinach:table_scenario` to the table and then define your directives in the first row of the `thead`. You must use the `thead` and `tbody` elements. Also you must **NOT** put any directives on the first `tr` in the `thead` and instead only put directives on the `th` nodes. This is because each `tr` will be turned into a scenario and the appropriate directives dynamically added.\n\n![](.README/tables.png)\n\n## Set Variable\n\n```html\n\u003cp\u003e\n  If my username is \u003cb spinach:set=\"#username\"\u003eChuck Norris\u003c/b\u003e.\n  Then the system should greet me with \u003cb spinach:assert_equals=\"greeting_for(#username)\"\u003eHello Chuck Norris\u003c/b\u003e.\n\u003c/p\u003e\n```\nThis will set the value `Chuck Norris` onto a variable called `#username` which you can use in a later assert_equals to assert a value.\n\n![](.README/set_variable.png)\n\n## Assert Equals\n\n```html\n\u003cblockquote\u003e\n  The greeting should be \u003cb spinach:assert_equals=\"get_greeting()\"\u003eHello World!\u003c/b\u003e.\n\u003c/blockquote\u003e\n```\n\nThis will assert the value returned by the method `get_greeting` with the supplied text: `Hello World!`\n\nThe return type of `get_greeting` **MUST** be a `String`\n\n![](.README/assert_equals.png)\n\nA second way to use assert_equals is when there is just a variable being asserted - either that has been set in the html or that is the result of an execute.\n\n\n```html\n\u003cp\u003e\n  If my name is \u003cb spinach:set=\"#username\"\u003eChuck Norris\u003c/b\u003e.\u003cbr/\u003e\n  Then my username should be \u003cb spinach:assert_equals=\"#username\"\u003eChuck Norris\u003c/b\u003e.\n\u003c/p\u003e\n```\n\n![](.README/assert_equals_with_variable.png)\n\n## Execute\n\n```html\n\u003cp spinach:execute=\"#greeting = greeting_for(#firstname, #lastname)\"\u003e\n  The greeting \u003cb spinach:assert_equals=\"#greeting.login_greeting\"\u003eHello Bob!\u003c/b\u003e\u003cbr/\u003e\n  And the message \u003cb spinach:assert_equals=\"#greeting.login_message\"\u003eYour last name is Bobbington!\u003c/b\u003e\u003cbr/\u003e\n  should be given to user \u003cb spinach:set=\"#firstname\"\u003eBob\u003c/b\u003e \u003cb spinach:set=\"#lastname\"\u003eBobbington\u003c/b\u003e\u003cbr/\u003e\n  when he logs in.\n\u003c/p\u003e\n```\n\nThis will store the result of the method `greeting_for` in a result `HashMap` which you can then use in later asserts.\n\nWhen returning a result in an execute you **MUST** return a `HashMap`\n\n![](.README/execute_function.png)\n\n## Implementation Status\n\n```html\n\u003cdiv spinach:scenario=\"#scenario 2\" spinach:status=\"expected_to_fail\"\u003e\n  \u003cblockquote\u003e\n    (expected_to_fail) The failed greeting should be \u003cb spinach:assert_equals=\"failed_greeting()\"\u003eHello World!\u003c/b\u003e.\n  \u003c/blockquote\u003e\n\u003c/div\u003e\n\n```\n\nYou can add the `status` directive to either the node with `spinach:scenario` to apply it to the whole scenario or you can add it to a specific assert_equals node.\n\n![](.README/implementation_status.png)\n\n## Running\n\nTo run a spec you can do the following:\n\nNOTE - if you get a compiler crash while running then use the `--no-debug` flag when running and report the crash to the Crystal devs. Depending on what you put in the mapping Proc might cause the compiler to crash.\n\ne.g `crystal run --no-debug spec/*.cr` or `crystal run spec/individual_file.cr`\n\nbut in general:\n\n`crystal run spec/*.cr` or `crystal run spec/individual_file.cr`\n\nif you want to put your specs somewhere else you can also do this:\n\n`crystal run spec/spinach/*.cr -- -l \"spec/spinach\"`\n\nThere is a basic command line report:\n\n![](.README/cli.png)\n\n* a green dot is passed\n* a red F is failed\n* a green F is failed but expected to fail\n* a yellow I is ignored\n* a blue P is pending\n\n## Writing Specs\n\nIn the `spec` folder of your project you write 2 files:\n\n1. assert_something.cr\n2. assert_something.html\n\nThe names of the files must be the same. Also the name of the class in the crystal file must be the camel case equivalent of the file name. e.g. assert_something.cr must have a class called `AssertSomething`\n\nhere is an example of the files:\n\n```crystal\nclass AssertEquals \u003c SpinachTestCase\n\n  @[Spinach]\n  def get_greeting(args)\n    \"Hello World!\"\n  end\n\nend\n```\n\nYou must extend from `SpinachTestCase`. You must also annotate any methods that will be used by the spec from the html file. This will add the method to a mapping of *method_name* =\u003e Proc of method.\n\nThe annotated method **MUST** always take a single argument of `args`. The args are passed to the method from the html where required. See the spec folder of this project for examples.\n\n```html\n\u003chtml\u003e\n\n\u003chead\u003e\n  \u003clink rel=\"stylesheet\" href=\"https://stackpath.bootstrapcdn.com/bootswatch/4.3.1/spacelab/bootstrap.min.css\"\u003e\n\u003c/head\u003e\n\n\u003cbody\u003e\n  \u003cdiv class=\"container\"\u003e\n    \u003ch1\u003eAssert Equals\u003c/h1\u003e\n    \u003ch4\u003eThis is an example of a basic assertion using \u003cb\u003eassert_equals\u003c/b\u003e.\u003c/h4\u003e\n    \u003cp\u003e\n      \u003cblockquote\u003e\n        The greeting should be \u003cb spinach:assert_equals=\"get_greeting()\"\u003eHello World!\u003c/b\u003e.\n      \u003c/blockquote\u003e\n    \u003c/p\u003e\n  \u003c/div\u003e\n\u003c/body\u003e\n\n\u003c/html\u003e\n```\n\n## Development\n\nWhen running the specs locally in the project use this:\n\n`crystal run --no-debug spec/*.cr -- -l \"./projects/spinach/spec\"`\n\n## Contributing\n\n1. Fork it (\u003chttps://github.com/sushichain/spinach/fork\u003e)\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create a new Pull Request\n\n## Contributors\n\n- [Kingsley Hendrickse](https://github.com/kingsleyh) - creator and maintainer\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faxentro%2Fspinach","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faxentro%2Fspinach","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faxentro%2Fspinach/lists"}