{"id":23160556,"url":"https://github.com/lvlyke/detest-bdd","last_synced_at":"2026-04-15T14:07:09.203Z","repository":{"id":49130647,"uuid":"124807109","full_name":"lVlyke/detest-bdd","owner":"lVlyke","description":"Detest BDD is a type-safe set of utilities that focuses on reducing the amount of test code that you write while maximizing coverage. Compatible with BDD testing frameworks like Jasmine and Mocha.","archived":false,"fork":false,"pushed_at":"2023-04-08T02:56:58.000Z","size":71,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-02T18:39:58.429Z","etag":null,"topics":["bdd","chai","detest","helper","helpers","jasmine","javascript","mocha","template","test","testing","tests","typescript","util","utilities","utils"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/lVlyke.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}},"created_at":"2018-03-11T22:51:07.000Z","updated_at":"2023-04-08T02:55:06.000Z","dependencies_parsed_at":"2022-09-05T02:51:09.085Z","dependency_job_id":null,"html_url":"https://github.com/lVlyke/detest-bdd","commit_stats":{"total_commits":35,"total_committers":4,"mean_commits":8.75,"dds":"0.19999999999999996","last_synced_commit":"0861f1153a3f8ece9d92e3a463322a62d38545f7"},"previous_names":["lvlyke/bdd-test-helpers"],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/lVlyke/detest-bdd","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lVlyke%2Fdetest-bdd","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lVlyke%2Fdetest-bdd/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lVlyke%2Fdetest-bdd/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lVlyke%2Fdetest-bdd/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lVlyke","download_url":"https://codeload.github.com/lVlyke/detest-bdd/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lVlyke%2Fdetest-bdd/sbom","scorecard":{"id":576182,"data":{"date":"2025-08-11","repo":{"name":"github.com/lVlyke/detest-bdd","commit":"0861f1153a3f8ece9d92e3a463322a62d38545f7"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.4,"checks":[{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Code-Review","score":0,"reason":"Found 0/24 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 6 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":5,"reason":"5 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-2pr6-76vf-7546","Warn: Project is vulnerable to: GHSA-8j8c-7jfh-h6hx","Warn: Project is vulnerable to: GHSA-vh95-rmgr-6w4m","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-20T17:50:55.641Z","repository_id":49130647,"created_at":"2025-08-20T17:50:55.642Z","updated_at":"2025-08-20T17:50:55.642Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31464101,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-05T21:22:52.476Z","status":"online","status_checked_at":"2026-04-06T02:00:07.287Z","response_time":112,"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":["bdd","chai","detest","helper","helpers","jasmine","javascript","mocha","template","test","testing","tests","typescript","util","utilities","utils"],"created_at":"2024-12-17T23:11:14.672Z","updated_at":"2026-04-06T08:01:14.145Z","avatar_url":"https://github.com/lVlyke.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!-- markdownlint-disable MD024 MD031 --\u003e\n\n# Detest BDD\n\nDetest BDD is a type-safe set of utilities that focuses on reducing the amount of test code that you write while maximizing coverage. Compatible with BDD testing frameworks like Jasmine and Mocha.\n\n* [Installation](#installation)\n* [Examples](#examples)\n* [API](#api)\n\n## Installation\n\nThe project can be installed [via **npm**](https://www.npmjs.com/package/detest-bdd) using the following command:\n\n```bash\nnpm install detest-bdd\n```\n\nNo further installation is required.\n\nDetest BDD works with any JavaScript testing framework that uses the ```describe``` ```beforeEach``` ```it``` syntax (i.e. Jasmine, Mocha + Chai, etc.).\n\n## Examples\n\n* [Template](#template)\n* [Input Builder](#input-builder)\n* [Spec](#spec)\n\n(For more information, see the full [**API reference**](#api))\n\n### Template\n\nWhen writing unit tests, it's easy to write redundant test cases repeatedly with only minor variations. This leads to code that's not only tedious to write, but also hard to read and refactor in the future. Test templates solve this issue by allowing you to write parts of your test code in a container that allow you to pass input values and modify the shape of the test code based on those inputs.\n\n#### Example\n\nGiven this piece of code:\n\n```ts\nexport interface Options {\n    round?: boolean;\n    absolute?: boolean;\n}\n\nexport class Calculator {\n\n    public divide(a: number, b: number, options?: Options): number {\n        options = options || {};\n        let result = a / b;\n\n        result = options.round ? Math.round(result) : result;\n        return options.absolute ? Math.abs(result) : result;\n    }\n}\n```\n\nFirst, let's look at an example of writing unit tests for ```Calculator.divide``` _without_ test templates:\n\n```ts\ndescribe(\"Given a Calculator\", () =\u003e {\n\n    beforeEach(function () {\n        this.calculator = new Calculator();\n    });\n\n    beforeEach(function () {\n        this.a = Random.number();\n        this.b = Random.number();\n    });\n\n    describe(\"when the divide method is called\", () =\u003e {\n\n        describe(\"when options are passed\", () =\u003e {\n\n            describe(\"when options.round is true\", () =\u003e {\n\n                describe(\"when options.absolute is true\", () =\u003e {\n\n                    it(\"then it should return the expected value\", function () {\n                        this.expected = Math.abs(Math.round(this.a / this.b));\n\n                        expect(this.calculator.divide(this.a, this.b, {\n                            round: true,\n                            absolute: true\n                        })).toEqual(this.expected);\n                    });\n                });\n\n                describe(\"when options.absolute is false\", () =\u003e {\n\n                    it(\"then it should return the expected value\", function () {\n                        this.expected = Math.round(this.a / this.b);\n\n                        expect(this.calculator.divide(this.a, this.b, {\n                            round: true,\n                            absolute: false\n                        })).toEqual(this.expected);\n                    });\n                });\n\n                describe(\"when options.absolute is undefined\", () =\u003e {\n\n                    it(\"then it should return the expected value\", function () {\n                        this.expected = Math.round(this.a / this.b);\n\n                        expect(this.calculator.divide(this.a, this.b, {\n                            round: true\n                        })).toEqual(this.expected);\n                    });\n                });\n            });\n\n            describe(\"when options.round is false\", () =\u003e {\n\n                describe(\"when options.absolute is true\", () =\u003e {\n\n                    it(\"then it should return the expected value\", function () {\n                        this.expected = Math.abs(this.a / this.b);\n\n                        expect(this.calculator.divide(this.a, this.b, {\n                            round: false,\n                            absolute: true\n                        })).toEqual(this.expected);\n                    });\n                });\n\n                describe(\"when options.absolute is false\", () =\u003e {\n\n                    it(\"then it should return the expected value\", function () {\n                        this.expected = this.a / this.b;\n\n                        expect(this.calculator.divide(this.a, this.b, {\n                            round: false,\n                            absolute: false\n                        })).toEqual(this.expected);\n                    });\n                });\n\n                describe(\"when options.absolute is undefined\", () =\u003e {\n\n                    it(\"then it should return the expected value\", function () {\n                        this.expected = this.a / this.b;\n\n                        expect(this.calculator.divide(this.a, this.b, {\n                            round: false\n                        })).toEqual(this.expected);\n                    });\n                });\n            });\n\n            describe(\"when options.round is not specified\", () =\u003e {\n\n                describe(\"when options.absolute is true\", () =\u003e {\n\n                    it(\"then it should return the expected value\", function () {\n                        this.expected = Math.abs(this.a / this.b);\n\n                        expect(this.calculator.divide(this.a, this.b, {\n                            absolute: true\n                        })).toEqual(this.expected);\n                    });\n                });\n\n                describe(\"when options.absolute is false\", () =\u003e {\n\n                    it(\"then it should return the expected value\", function () {\n                        this.expected = this.a / this.b;\n\n                        expect(this.calculator.divide(this.a, this.b, {\n                            absolute: false\n                        })).toEqual(this.expected);\n                    });\n                });\n\n                describe(\"when options.absolute is undefined\", () =\u003e {\n\n                    it(\"then it should return the expected value\", function () {\n                        this.expected = this.a / this.b;\n\n                        expect(this.calculator.divide(this.a, this.b, {})).toEqual(this.expected);\n                    });\n                });\n            });\n        });\n\n        describe(\"when options are NOT passed\", () =\u003e {\n\n            it(\"then it should return the expected value\", function () {\n                this.expected = this.a / this.b;\n\n                expect(this.calculator.divide(this.a, this.b)).toEqual(this.expected);\n            });\n        });\n    });\n});\n```\n\nOutput:\n\n```bash\n..........\n\n10 specs, 0 failures\nFinished in 0.026 seconds\n```\n\nYou will notice that each of the tests basically does the same thing. There are some minor variations, such as a call to ```Math.abs``` or ```Math.round``` depending on the flags passed to ```divide```, but otherwise it is just redundant code. If we want to add new flags for ```divide```, we'll have to go through the same process and add more redundant test code to cover all of the cases.\n\nUsing ```Template``` however, we can abstract this code redundancy into a set of input data. Like a for loop allows us to express logic only once for a series of inputs, ```Template``` allows us to write the test code logic once and then execute it for multiple inputs.\n\nHere are the unit tests from the previous example, but rewritten with ```Template```:\n\n```ts\ndescribe(\"Given a Calculator\", () =\u003e {\n\n    beforeEach(function () {\n        this.calculator = new Calculator();\n    });\n\n    beforeEach(function () {\n        this.a = Random.number();\n        this.b = Random.number();\n        this.expected = this.a / this.b;\n    });\n\n    describe(\"when the divide method is called\", Template.withInputs([\"options\"], (options: Options) =\u003e {\n\n        if (options) {\n            describe(`when the round flag is ${options.round} and the absolute flag is ${options.absolute}`, () =\u003e {\n\n                beforeEach(function () {\n                    this.expected = options.round ? Math.round(this.expected) : this.expected;\n                    this.expected = options.absolute ? Math.abs(this.expected) : this.expected;\n                });\n\n                it(\"then it should return the expected value\", function () {\n                    expect(this.calculator.divide(this.a, this.b, options)).toEqual(this.expected);\n                });\n            });\n        }\n        else {\n            it(\"then it should return the expected value\", function () {\n                expect(this.calculator.divide(this.a, this.b)).toEqual(this.expected);\n            });\n        }\n    }, { options: undefined },\n       { options: {}},\n       { options: { round: true }},\n       { options: { round: false }},\n       { options: { absolute: true }},\n       { options: { absolute: false }},\n       { options: { round: true, absolute: true }},\n       { options: { round: false, absolute: true }},\n       { options: { round: true, absolute: false }},\n       { options: { round: false, absolute: false }}\n    ));\n});\n```\n\nOutput:\n\n```bash\n..........\n\n10 specs, 0 failures\nFinished in 0.026 seconds\n```\n\nWithout losing any code coverage in our tests, we've gone from having to write **10** unit tests to only **2**.\n\nFor more information, see the [**API reference**](#template-1).\n\n### Input Builder\n\nThe previous section explained ```Template``` and how it simplifies the amount of test code we have to write, but we can still do better. The input data that is passed to the templated test code often simply ends up being different permutations of the same data. For example, here's the input to the test template we wrote for ```Calculator.divide```:\n\n```ts\n[ { options: undefined },\n  { options: {}},\n  { options: { round: true }},\n  { options: { round: false }},\n  { options: { absolute: true }},\n  { options: { absolute: false }},\n  { options: { round: true, absolute: true }},\n  { options: { round: false, absolute: true }},\n  { options: { round: true, absolute: false }},\n  { options: { round: false, absolute: false }} ]\n```\n\nAs you can see, all of the inputs are just different combinations of values (and lack of values) to test all of the possible scenarios. When we have more than two or three input properties to test, the list of input data can get long quickly. This is where ```InputBuilder``` comes in handy. ```InputBuilder``` allows you to define values for individual input properties (called _fragments_), and then generates a list of all possible permutations of inputs to your test template based on those fragments.\n\nLet's see what an ```InputBuilder``` would look like for our input data above:\n\n```ts\nInputBuilder\n    .fragment({ options: undefined })\n    .fragment({ options: {} })\n    .fragmentBuilder(\"options\", InputBuilder.\n        .fragment({ round: true })\n        .fragment({ round: false })\n        .fragment({ absolute: true })\n        .fragment({ absolute: false })\n    )\n    .build();\n```\n\nEach fragment defines a possible permutation of a specific input property that will be used to build the final list. As we can see above, it's also possible to generate fragments from another ```InputBuilder``` using the ```fragmentBuilder``` operator.\n\nThere's still some redundancy we can eliminate, however, by using the ```fragmentList``` operator to consolidate our fragment definitions:\n\n```ts\nInputBuilder\n    .fragmentList({ options: [undefined, {}] })\n    .fragmentBuilder(\"options\", InputBuilder.\n        .fragmentList({ round: [true, false] })\n        .fragmentList({ absolute: [true, false] })\n    )\n    .build();\n```\n\nThis generates the following output:\n\n```ts\n[ { options: undefined },\n  { options: {}},\n  { options: { round: true }},\n  { options: { round: false }},\n  { options: { absolute: true }},\n  { options: { absolute: false }},\n  { options: { round: true, absolute: true }},\n  { options: { round: false, absolute: true }},\n  { options: { round: true, absolute: false }},\n  { options: { round: false, absolute: false }} ]\n```\n\nNow, let's put it all together by modifying the ```Calculator.divide``` unit tests we wrote earlier:\n\n```ts\nconst DivideTemplateInput = InputBuilder\n    .fragmentList({ options: [undefined, {}] })\n    .fragmentBuilder(\"options\", InputBuilder.\n        .fragmentList({ round: [true, false] })\n        .fragmentList({ absolute: [true, false] })\n    );\n\ndescribe(\"Given a Calculator\", () =\u003e {\n\n    beforeEach(function () {\n        this.calculator = new Calculator();\n    });\n\n    beforeEach(function () {\n        this.a = Random.number();\n        this.b = Random.number();\n        this.expected = this.a / this.b;\n    });\n\n    describe(\"when the divide method is called\", Template([\"options\"], DivideTemplateInput, (options: Options) =\u003e {\n\n        if (options) {\n            describe(`when the round flag is ${options.round} and the absolute flag is ${options.absolute}`, () =\u003e {\n\n                beforeEach(function () {\n                    this.expected = options.round ? Math.round(this.expected) : this.expected;\n                    this.expected = options.absolute ? Math.abs(this.expected) : this.expected;\n                });\n\n                it(\"then it should return the expected value\", function () {\n                    expect(this.calculator.divide(this.a, this.b, options)).toEqual(this.expected);\n                });\n            });\n        }\n        else {\n            it(\"then it should return the expected value\", function () {\n                expect(this.calculator.divide(this.a, this.b)).toEqual(this.expected);\n            });\n        }\n    });\n});\n```\n\nOutput:\n\n```bash\n..........\n\n10 specs, 0 failures\nFinished in 0.026 seconds\n```\n\n```InputBuilder``` also supports ```when``` clauses, which allow you to control how permutations are generated by filtering the possible combinations of input fragments.\n\nFor example, given the following class:\n\n```ts\ninterface AddOptions {\n    item: Item;\n    front: boolean;\n}\n\nclass ItemList {\n\n    private _items: Item[];\n\n    public readonly get items(): Item[] {\n        return this._items;\n    }\n\n    public addItem(itemOrOptions: Item | AddOptions) {\n        if (itemOrOptions instanceof Item || !itemOrOptions.front) {\n            this._items.push(item);\n        }\n        else {\n            this._items = [itemOrOptions.item].concat(this._items);\n        }\n    }\n}\n```\n\nNow, let's say we want to write a test for ```addItem```. If we use a ```Template```, we might come up with this:\n\n```ts\nconst AddItemTemplateInput = InputBuilder\n    .fragmentList({ item: [undefined, new Item()] })\n    .fragment({ options: undefined })\n    .fragmentBuilder(\"options\", InputBuilder.\n        .fragment({ item: new Item() })\n        .fragmentList({ front: [undefined, true, false] })\n    );\n\ndescribe(\"Given an ItemList\", () =\u003e {\n\n    beforeEach(function () {\n        this.itemList = new ItemList();\n        this.dummyItem = new Item();\n        this.itemList.addItem(this.dummyItem);\n    });\n\n    describe(\"when the addItem method is called\", Template([\"item\", \"options\"], DivideTemplateInput, (item?: Item, options?: AddOptions) =\u003e {\n\n        if (item) {\n            beforeEach(function () {\n                this.itemList.addItem(item);\n            });\n\n            it(\"then it should add the item to the end of the list\", function () {\n                expect(this.itemList.items).toEqual([this.dummyItem, item]);\n            });\n        }\n        else {\n            beforeEach(function () {\n                this.itemList.addItem(options);\n            });\n\n            if (options.front) {\n                it(\"then it should add the item to the front of the list\", function () {\n                    expect(this.itemList.items).toEqual([item, this.dummyItem]);\n                });\n            }\n            else {\n                it(\"then it should add the item to the end of the list\", function () {\n                    expect(this.itemList.items).toEqual([this.dummyItem, item]);\n                });\n            }\n        }\n    });\n});\n```\n\nThis may look fine, but when we run these tests one of the cases will fail. This is because we've defined an input fragment where ```item``` is undefined and one where ```options``` is undefined. While we want these inputs independently, we never want them to both be undefined at the same time.\n\nAlso notice that we only ever read the value of ```options``` when ```item``` isn't defined. This means that when an input permutation containing a definition for both ```options``` and ```item``` is given to the template, we are effectively running a redundant test case, as the test path for when ```item``` is defined will be executed twice. While this may not mean much in this example, this concern can grow exponentially as new input fragments are added. This means potentially hundreds of redundant test cases being executed in your test suite.\n\nThese two problems are what ```when``` clauses help solve. To remedy these issues, we just need to tweak the definition of ```AddItemTemplateInput``` from the above example:\n\n```ts\nconst AddItemTemplateInput = InputBuilder\n    .fragment({ item: undefined }, permutation =\u003e permutation.options != undefined)\n    .fragment({ item: new Item() }, permutation =\u003e permutation.options == undefined)\n    .fragment({ options: undefined })\n    .fragmentBuilder(\"options\", InputBuilder.\n        .fragment({ item: new Item() })\n        .fragmentList({ front: [undefined, true, false] })\n    );\n```\n\nThis now says that a permutation with an undefined ```item``` property can only exist when ```options``` is defined, and vice versa.\n\nWith the additional restrictions to our ```item``` input fragments, we've eliminated the errant failure and the redundant test case.\n\nFor more information, see the [**API reference**](#input-builder-1).\n\n### Spec\n\nThe ```Spec``` namespace contains a helper object that enables more type-safe testing by exposing a type for all test spec parameters. ```Spec``` proxies the built-in testing functions (```beforeEach```, ```afterEach```, and ```it```) and provides the test spec's parameters as a type-safe argument to the callback.\n\n#### Example\n\n```ts\nimport { Spec, Template, Random } from \"detest-bdd\";\n\ninterface CalculatorTest {\n    calculator: Calculator;\n    a: number;\n    b: number;\n    expected: number;\n}\n\nconst spec = Spec.create\u003cCalculatorTest\u003e();\n\ndescribe(\"Given a Calculator\", () =\u003e {\n\n    spec.beforeEach((params: CalculatorTest) =\u003e {\n        params.calculator = new Calculator();\n    });\n\n    spec.beforeEach((params: CalculatorTest) =\u003e {\n        params.a = Random.number();\n        params.b = Random.number();\n        params.expected = params.a / params.b;\n    });\n\n    describe(\"when the divide method is called\", Template([\"options\"], (options: Options) =\u003e {\n\n        if (options) {\n            describe(`when the round flag is ${options.round} and the absolute flag is ${options.absolute}`, () =\u003e {\n\n                spec.beforeEach((params: CalculatorTest) =\u003e {\n                    params.expected = options.round ? Math.round(params.expected) : params.expected;\n                    params.expected = options.absolute ? Math.abs(params.expected) : params.expected;\n                });\n\n                spec.it(\"then it should return the expected value\", (params: CalculatorTest) =\u003e {\n                    expect(params.calculator.divide(params.a, params.b, options)).toEqual(params.expected);\n                });\n            });\n        }\n        else {\n            spec.it(\"then it should return the expected value\", (params: CalculatorTest) =\u003e {\n                expect(params.calculator.divide(params.a, params.b)).toEqual(params.expected);\n            });\n        }\n    }, { options: undefined },\n       { options: {}},\n       { options: { round: true }},\n       { options: { round: false }},\n       { options: { absolute: true }},\n       { options: { absolute: false }},\n       { options: { round: true, absolute: true }},\n       { options: { round: false, absolute: true }},\n       { options: { round: true, absolute: false }},\n       { options: { round: false, absolute: false }}\n    ));\n});\n```\n\n```Spec``` also replaces the traditional injection of the ```doneFn``` for asynchronous tests in favor of returning a Promise from the callback when waiting for an asynchronous task.\n\n#### Example\n\n```ts\ndeclare var FooService: {\n    getResult: Promise\u003cFooResult\u003e;\n};\n\ndescribe(\"Given a FooService\", () =\u003e {\n\n    describe(\"when getResult is called\", () =\u003e {\n\n        spec.beforeEach((params: FooServiceTest): Promise\u003cany\u003e =\u003e {\n            params.fooParams = {\n                input: \"foobar\"\n            };\n\n            return FooService.getResult(params.fooParams).then(result =\u003e params.fooResult = result);\n        });\n\n        spec.it(\"should return the expected result\", (params: FooServiceTest) =\u003e {\n            expect(params.fooResult.output).toEqual(\"foobaz\");\n        });\n    });\n});\n```\n\nFor more information, see the [**API reference**](#spec-1).\n\n## API\n\n### Template\n\n```ts\ninterface Template\u003cT extends object\u003e {\n    paramNames: string[];\n    invoke: Template.InvokeFn\u003cT\u003e;\n    run: Template.RunFn\u003cT\u003e;\n}\n```\n\nRepresents a template, which is an object that takes an input and runs a block of test code with that input.\n\n* ```paramNames``` The ordered list of input parameter names that the template callback will use as arguments.\n* ```invoke``` A function that can be called to invoke the template once. For more information, see [```Template.InvokeFn```](#templateinvokefn).\n* ```run``` A function that can be called to run the template with multiple inputs. For more information, see [```Template.RunFn```](#templaterunfn).\n\n```ts\nfunction Template\u003cT extends object\u003e(paramNames: string[], input: InputBuilder\u003cT\u003e | inputBuilder\u003cT\u003e[], callback: Template.CallbackFn): () =\u003e void;\n```\n\nShorthand method that creates a ```Template``` and returns a function that executes the template callback with the inputs from the given ```InputBuilder``` when called.\n\n* ```paramNames``` The ordered list of input parameter names that the template callback will use as arguments. See [```Template```](#template-1).\n* ```input``` The ```InputBuilder```(s) that will be used to generate the template inputs. For more information, see [```InputBuilder```](#input-builder-1).\n* ```callback``` The callback that contains the test code to execute. See [```Template.CallbackFn```](#templatecallbackfn).\n\nReturns a ```Function``` that when called will execute the template with the template callback with the inputs from the given ```InputBuilder```.\n\n#### Template.withInputs\n\n```ts\nfunction Template.withInputs\u003cT extends object\u003e(paramNames: string[], callback: Template.CallbackFn, ...paramsList: T[]): () =\u003e void;\n```\n\nShorthand method that creates a ```Template``` and returns a function that executes it with the given ```paramsList``` when called.\n\n* ```paramNames``` The ordered list of input parameter names that the template callback will use as arguments. See [```Template```](#template-1).\n* ```callback``` The callback that contains the test code to execute. See [```Template.CallbackFn```](#templatecallbackfn).\n* ```paramsList``` The list of input values to pass into the template callback. For more information, see [```Template.RunFn```](#templaterunfn).\n\nReturns a ```Function``` that when called will execute the template with the given ```paramsList```.\n\n#### Template.create\n\n```ts\nfunction Template.create\u003cT extends object\u003e(paramNames: string[], callback: CallbackFn): Template\u003cT\u003e;\n```\n\nCreates a new ```Template``` object.\n\n* ```paramNames``` The ordered list of input parameter names that the template callback will use as arguments. See [```Template```](#template-1).\n* ```callback``` The callback that contains the test code to execute. See [```Template.CallbackFn```](#templatecallbackfn).\n\nReturns a new ```Template```.\n\n#### Template.CallbackFn\n\n```ts\ntype Template.CallbackFn = (...paramList: any[]) =\u003e void;\n```\n\n* ```paramList``` The list of inputs to the template that correspond to [```paramNames```](#templatecreate).\n\n#### Template.InvokeFn\n\n```ts\ntype Template.InvokeFn\u003cT extends object\u003e = (params: T) =\u003e void;\n```\n\n* ```params``` An object containing key-value pairs corresponding to [```paramNames```](#templatecreate).\n\n#### Template.RunFn\n\n```ts\ntype Template.RunFn\u003cT extends object\u003e = (...paramsList: T[]) =\u003e void;\n```\n\n* ```paramsList``` The list of objects containing key-value pairs corresponding to [```paramNames```](#templatecreate).\n\n### Input Builder\n\n```ts\nclass InputBuilder\u003cT\u003e {\n\n  public static fragment\u003cT\u003e(fragmentDictionary: FragmentDictionary\u003cT\u003e, when?: FragmentWhenFn\u003cT\u003e): InputBuilder\u003cT\u003e;\n\n  public static fragmentList\u003cT\u003e(fragmentListDictionary: FragmentListDictionary\u003cT\u003e, when?: FragmentWhenFn\u003cT\u003e): InputBuilder\u003cT\u003e;\n\n  public static fragmentBuilder\u003cT, _T\u003e(key: keyof T, builder: InputBuilder\u003c_T\u003e, when?: FragmentWhenFn\u003cT\u003e): InputBuilder\u003cT\u003e;\n\n  public get dictionary(): Readonly\u003cFragmentListDictionary\u003cT\u003e\u003e;\n\n  public build(): T[];\n\n  public fragment(fragmentDictionary: FragmentDictionary\u003cT\u003e, when?: FragmentWhenFn\u003cT\u003e): InputBuilder\u003cT\u003e;\n\n  public fragmentList(fragmentListDictionary: FragmentListDictionary\u003cT\u003e, when?: FragmentWhenFn\u003cT\u003e): InputBuilder\u003cT\u003e;\n\n  public fragmentBuilder\u003c_T\u003e(key: keyof T, builder: InputBuilder\u003c_T\u003e, when?: FragmentWhenFn\u003cT\u003e): InputBuilder\u003cT\u003e;\n}\n```\n\n#### InputBuilder.fragment\n\n```ts\npublic fragment(fragmentDictionary: FragmentDictionary\u003cT\u003e, when?: FragmentWhenFn\u003cT\u003e): InputBuilder\u003cT\u003e;\n```\n\n```ts\npublic static fragment\u003cT\u003e(fragmentDictionary: FragmentDictionary\u003cT\u003e, when?: FragmentWhenFn\u003cT\u003e): InputBuilder\u003cT\u003e;\n```\n\nAdds all fragment definitions to the builder.\n\n* ```fragmentDictionary``` The fragment dictionary to add to the builder. A fragment dictionary is an object where the keys are ```keyof T``` (the input properties) and the values are possible permutations to be generated by the builder. For more information, see [```InputBuilder.FragmentDictionary```](#inputbuilderfragmentdictionary).\n* ```when``` [Optional] The when clause to be executed when building the permutation list. For more information, see [```InputBuilder.FragmentWhenFn```](#inputbuilderfragmentwhenfn).\n\n#### InputBuilder.fragmentList\n\n```ts\npublic fragmentList(fragmentListDictionary: FragmentListDictionary\u003cT\u003e, when?: FragmentWhenFn\u003cT\u003e): InputBuilder\u003cT\u003e;\n```\n\n```ts\npublic static fragmentList\u003cT\u003e(fragmentListDictionary: FragmentListDictionary\u003cT\u003e, when?: FragmentWhenFn\u003cT\u003e): InputBuilder\u003cT\u003e;\n```\n\nAdds all fragment list definitions to the builder.\n\n* ```fragmentListDictionary``` The fragment list dictionary definitions to add to the builder. A fragment list dictionary is an object where the keys are ```keyof T``` (the input properties) and the values are arrays of the possible permutations to be generated by the builder. For more information, see [```InputBuilder.FragmentListDictionary```](#inputbuilderfragmentlistdictionary).\n* ```when``` [Optional] The when clause to be executed when building the permutation list. For more information, see [```InputBuilder.FragmentWhenFn```](#inputbuilderfragmentwhenfn).\n\n#### InputBuilder.fragmentBuilder\n\n```ts\npublic fragmentBuilder\u003c_T\u003e(key: keyof T, builder: InputBuilder\u003c_T\u003e, when?: FragmentWhenFn\u003cT\u003e): InputBuilder\u003cT\u003e;\n```\n\n```ts\npublic static fragmentBuilder\u003cT, _T\u003e(key: keyof T, builder: InputBuilder\u003c_T\u003e, when?: FragmentWhenFn\u003cT\u003e): InputBuilder\u003cT\u003e;\n```\n\nAdds all permutations from another ```InputBuilder``` as input fragments for this builder.\n\n* ```key``` The property key that the fragment values will be added to.\n* ```builder``` The ```InputBuilder``` to read the fragment values from.\n* ```when``` [Optional] The when clause to be executed when building the permutation list. For more information, see [```InputBuilder.FragmentWhenFn```](#inputbuilderfragmentwhenfn).\n\n#### InputBuilder.build\n\n```ts\npublic build(): T[]\n```\n\nBuilds a list of all possible permutations for ```T``` using the given input fragments.\n\nReturns an array of all possible permutations for the given input fragments.\n\n#### InputBuilder.Fragment\n\n```ts\ntype Fragment\u003cT, P extends keyof T\u003e = T[P];\n```\n\nA fragment is a possible value for a given property of ```T```.\n\n#### InputBuilder.FragmentDictionary\n\n```ts\ntype FragmentDictionary\u003cT\u003e = { [P in keyof T]?: Fragment\u003cT, P\u003e };\n```\n\nA fragment dictionary is an object where the keys are ```keyof T``` (the input properties) and the values are possible values for those input properties.\n\n#### InputBuilder.FragmentList\n\n```ts\ntype FragmentList\u003cT, P extends keyof T\u003e = Fragment\u003cT, P\u003e[];\n```\n\nA fragment list is an array of possible value for a given property of ```T```.\n\n#### InputBuilder.FragmentListDictionary\n\n```ts\ntype FragmentListDictionary\u003cT\u003e = { [P in keyof T]?: FragmentList\u003cT, P\u003e };\n```\n\nA fragment list dictionary is an object where the keys are ```keyof T``` (the input properties) and the values are arrays of possible values for those input properties.\n\n#### InputBuilder.FragmentWhenFn\n\n```ts\ntype FragmentWhenFn\u003cT\u003e = (input: Permutation\u003cT\u003e) =\u003e boolean;\n```\n\nA ```when``` function allows control over permutation generation by controlling which fragments are allowed to exist together. The function is executed during building of the permutation list.\n\n* ```input``` The current permutation being inspected. This object is a permutation of the given input fragments.\n\nThe ```when``` function should return a ```boolean``` value that if ```true``` will keep the given input permutation and if ```false``` will discard the input permutation.\n\n### Spec\n\n```ts\ninterface Spec\u003cT\u003e {\n    beforeEach(callback: Spec.Callback\u003cT\u003e): void;\n    afterEach(callback: Spec.Callback\u003cT\u003e): void;\n    it(description: string, callback: Spec.Callback\u003cT\u003e): void;\n\n    beforeAll?(callback: Spec.StatelessCallback): void;\n    before?(callback: Spec.StatelessCallback): void;\n    afterAll?(callback: Spec.StatelessCallback): void;\n    after?(callback: Spec.StatelessCallback): void;\n\n    xit?(description: string, callback: Spec.Callback\u003cT\u003e): void;\n    fit?(description: string, callback: Spec.Callback\u003cT\u003e): void;\n}\n```\n\nAn object that provides a simple type-safe and async-enabled wrapper around functions used in a test spec. For more information, see [```Spec.Callback```](#speccallback) and [```Spec.StatelessCallback```](#specstatelesscallback).\n\nThe following proxy functions are provided:\n\n* ```beforeEach```\n* ```afterEach```\n* ```it```\n* ```beforeAll```\n  \u003e **NOTE**: This function is only available in test runners that natively support ```beforeAll```.\n* ```before```\n  \u003e **NOTE**: This function is only available in test runners that natively support ```before```.\n* ```afterAll```\n  \u003e **NOTE**: This function is only available in test runners that natively support ```afterAll```.\n* ```after```\n  \u003e **NOTE**: This function is only available in test runners that natively support ```after```.\n* ```xit```\n  \u003e **NOTE**: This function is only available in test runners that natively support ```xit```.\n* ```fit```\n  \u003e **NOTE**: This function is only available in test runners that natively support ```fit```.\n\n#### Spec.create\n\n```ts\nfunction Spec.create\u003cT\u003e(): Spec\u003cT\u003e;\n```\n\nCreates a new ```Spec``` with the given ```T```.\n\n#### Spec.inject\n\n```ts\nfunction Spec.inject\u003cT\u003e(callback: Spec.Callback\u003cT\u003e): (doneFn: () =\u003e void) =\u003e void;\n```\n\nProvides a function that when called injects a type-safe collection of properties as the first argument to the callback. For more information, see [```Spec.Callback```](#speccallback).\n\n* ```callback``` The callback function that will receive the injected properties. For more information, see [```Spec.Callback```](#speccallback).\n\n#### Spec.Callback\n\n```ts\ntype Spec.Callback\u003cT\u003e = (params: T) =\u003e Promise\u003cvoid\u003e | void;\n```\n\nRepresents a potentially async-enabled callback that receives a type-safe collection of test properties as the first argument.\n\n* ```params``` The test properties for the spec.\n\nIf the callback doesn't return a value, the test context will complete immediately. If the callback returns a ```Promise```, the test context will wait for the returned ```Promise``` to resolve or reject before completing.\n\n#### Spec.StatelessCallback\n\n```ts\ntype Spec.StatelessCallback = () =\u003e Promise\u003cvoid\u003e | void;\n```\n\nRepresents a stateless, potentially async-enabled callback.\n\nIf the callback doesn't return a value, the test context will complete immediately. If the callback returns a ```Promise```, the test context will wait for the returned ```Promise``` to resolve or reject before completing.\n\n### Random\n\n#### Random.number\n\n```ts\nfunction Random.number(min?: number, max?: number): number;\n```\n\nReturns a random number between ```min``` and ```max```. ```min``` defaults to 0 and ```max``` defaults to ```MAX_VALUE```.\n\n#### Random.integer\n\n```ts\nfunction Random.integer(min?: number, max?: number): number;\n```\n\nReturns a random integer between ```min``` and ```max```. ```min``` defaults to 0 and ```max``` defaults to ```MAX_SAFE_INTEGER```.\n\n#### Random.boolean\n\n```ts\nfunction Random.boolean(): boolean;\n```\n\nReturns a random boolean value.\n\n#### Random.string\n\n```ts\nfunction Random.string(minLength?: number, maxLength?: number, options?: StringOptions): string;\n```\n\nReturns a random string between ```minLength``` and ```maxLength```. ```minLength``` defaults to 0 and ```maxLength``` defaults to ```20```.\n\nIf ```options.alpha``` is true, the string will contain letters. This is true by default.\n\nIf ```options.numeric``` is true, the string will contain letters. This is true by default.\n\nIf both ```options.alpha``` and ```options.numeric``` are false, an error will be thrown.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flvlyke%2Fdetest-bdd","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flvlyke%2Fdetest-bdd","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flvlyke%2Fdetest-bdd/lists"}