{"id":24932473,"url":"https://github.com/a-viv-a/generate-combinations","last_synced_at":"2025-03-28T14:25:54.756Z","repository":{"id":38321763,"uuid":"506522264","full_name":"a-viv-a/generate-combinations","owner":"a-viv-a","description":"Generate all combinations of an object from a description, with type safety","archived":false,"fork":false,"pushed_at":"2022-06-28T22:28:40.000Z","size":128,"stargazers_count":0,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-14T22:16:19.980Z","etag":null,"topics":["generation","typescript","unit-testing"],"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/a-viv-a.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}},"created_at":"2022-06-23T06:27:17.000Z","updated_at":"2022-06-24T04:52:47.000Z","dependencies_parsed_at":"2022-08-17T17:50:50.873Z","dependency_job_id":null,"html_url":"https://github.com/a-viv-a/generate-combinations","commit_stats":null,"previous_names":["isaec/generate-combinations"],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/a-viv-a%2Fgenerate-combinations","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/a-viv-a%2Fgenerate-combinations/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/a-viv-a%2Fgenerate-combinations/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/a-viv-a%2Fgenerate-combinations/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/a-viv-a","download_url":"https://codeload.github.com/a-viv-a/generate-combinations/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246044396,"owners_count":20714587,"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":["generation","typescript","unit-testing"],"created_at":"2025-02-02T14:39:41.721Z","updated_at":"2025-03-28T14:25:54.738Z","avatar_url":"https://github.com/a-viv-a.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# generate-combinations\n\nGenerate all combinations of an object from a description, with advanced type safety. `generate-combinations` supports javascript and typescript, with esm or commonjs style imports. No dependencies,and easy to extend. Ergonomic and declarative API. MIT Licensed.\n\nIdeal for snapshot unit testing. Use `test.each` to generate a test case for each object, and expect your output to match a snapshot. With a few lines, you can detect a change to the output of your unit for any valid input data.\n\n## installation\n\n```bash\nnpm i -D generate-combinations\n# or\nyarn add -D generate-combinations\n# or\npnpm i -D generate-combinations\n```\n\n## usage\n\n```typescript\nimport {\n  generate,\n  one,\n  optional,\n  some,\n} from \"generate-combinations\";\n\nimport { QuoteData, Quote } from \"./Quote\";\n\n// generate all combinations of a quote as described in a declarative api!\nconst testCases = generate\u003cQuoteData\u003e({\n  type: \"quote\",\n  entries: one([[\"body of the quote\"], [\"two entries\", \"for this quote\"]]),\n  by: optional(\"author\"),\n  from: optional(\"source\")\n})\n\n// vitest + solidjs syntax, but generate would work with any js and test framework\nit.each(testCases)(`rendering %s matches snapshot`, (data) =\u003e {\n  const { unmount, container } = render(() =\u003e \u003cQuote data={data} /\u003e);\n  expect(container).toMatchSnapshot();\n  unmount();\n});\n```\n\n## features\n\nAdvanced type safety - the `generate` function is typed such that it will not create an object that does not conform to its generic.\n\nIf you can make `generate` create data that does not conform to its generic without usage of `illegal` or similar ts assertions - its a bug! Please report it!\n\n```typescript\ntype Data = {\n  data: string;\n  optionalData?: string;\n}\n```\n\n\u003cdetails\u003e\u003csummary\u003e\n  Expand to view type error for snippet below.\n  \u003c/summary\u003e\u003cp\u003e\n\n\u003e ``` typescript\n\u003e (property) data: string | Combination\u003cstring\u003e\n\u003e   Type 'Combination\u003cstring | typeof KeyValueUndefined\u003e' is not assignable to type 'string | Combination\u003cstring\u003e'.\n\u003e     Type 'Combination\u003cstring | typeof KeyValueUndefined\u003e' is not assignable to type 'Combination\u003cstring\u003e'.\n\u003e       Type 'string | typeof KeyValueUndefined' is not assignable to type 'string'.\n\u003e         Type 'typeof KeyValueUndefined' is not assignable to type 'string'.ts(2322)\n\u003e README.md: The expected type comes from property 'data' which is declared here on type 'GenerationTemplate\u003cData\u003e'\n\u003e ```\n\n\u003c/p\u003e\u003c/details\u003e\n\n```typescript\ngenerate\u003cData\u003e({\n  data: optional(\"hello\"),\n  // ^ typescript errors because data cannot be undefined\n  // optional is the combination of a data and the key data undefined\n  optionalData: \"world\",\n});\n```\n\n```typescript\ngenerate\u003cData\u003e({\n  data: \"hello\",\n  optionalData: optional(\"world\"),\n  // ^ this is type safe because optionalData is optional\n});\n```\n\nProduces all combinations of your description of an object.\n\n```typescript\ngenerate\u003cData\u003e({\n  data: one([\"hello\", \"hey\", \"hi\"]),\n  optionalData: optional(\"world\"),\n});\n```\n\n```js\n[\n  { data: 'hello', optionalData: 'world' },\n  { data: 'hey', optionalData: 'world' },\n  { data: 'hi', optionalData: 'world' },\n  { data: 'hello' },\n  { data: 'hey' },\n  { data: 'hi' }\n]\n```\n\nWhile easily supporting advanced custom combination types.\n\n```typescript\nconst upperAndLowerCase = (string: string): Combination\u003cstring\u003e =\u003e\n  new Combination([string, string.toUpperCase(), string.toLowerCase()]);\n\ngenerate\u003c{\n  str: string;\n  num: number;\n}\u003e({\n  str: upperAndLowerCase(\"Wow!\"),\n  num: one([1, 2, 3]),\n});\n```\n\n```js\n[\n  { str: 'Wow!', num: 1 },\n  { str: 'WOW!', num: 1 },\n  { str: 'wow!', num: 1 },\n  { str: 'Wow!', num: 2 },\n  { str: 'WOW!', num: 2 },\n  { str: 'wow!', num: 2 },\n  { str: 'Wow!', num: 3 },\n  { str: 'WOW!', num: 3 },\n  { str: 'wow!', num: 3 }\n]\n```\n\nSupports nesting of generate calls.\n\n```typescript\ntype DataNest = {\n  val: string;\n  nested: {\n    val: string;\n    otherVal?: number;\n  };\n};\n```\n\n```typescript\ngenerate\u003cNestData\u003e({\n  val: one([\"yo\", \"hey\"]),\n  nested: generate.nest\u003cNestData[\"nested\"]\u003e({\n    val: one([\"str\", \"other str\"]),\n    otherVal: optional(5),\n  }),\n})\n```\n\nAnd maintains its type safety even when nested.\n\n\u003cdetails\u003e\u003csummary\u003e\n  Expand to view type error for snippet below.\n  \u003c/summary\u003e\u003cp\u003e\n\n\u003e ```typescript\n\u003e (property) val: string | Combination\u003cstring\u003e\n\u003e Type 'Combination\u003cstring | number\u003e' is not assignable to type 'string | Combination\u003cstring\u003e'.\n\u003e   Type 'Combination\u003cstring | number\u003e' is not assignable to type 'Combination\u003cstring\u003e'.\n\u003e     Type 'string | number' is not assignable to type 'string'.\n\u003e       Type 'number' is not assignable to type 'string'.ts(2322)\n\u003e README.md: The expected type comes from property 'val' which is declared here on type 'GenerationTemplate\u003c{ val: string; otherVal?: number | undefined; }\u003e'\n\u003e ```\n\n\u003c/p\u003e\u003c/details\u003e\n\n```typescript\ngenerate\u003cDataNest\u003e({\n  val: one([\"yo\", \"hey\"]),\n  nested: generate.nest\u003cDataNest[\"nested\"]\u003e({\n    val: one([\"str\", 43]),\n    // ^ typescript is angry because val cannot be a number\n    otherVal: optional(5),\n  }),\n});\n```\n\nWhile also offering an escape hatch from the otherwise safe type system to allow you to create invalid test data - your input data won't always conform to your types, so why should your test data?\n\n\u003cdetails\u003e\u003csummary\u003e\nNote that using \u003ccode\u003eillegal\u003c/code\u003e can have unexpected implications.\n  \u003c/summary\u003e\u003cp\u003e\n\n\u003e ```typescript\n\u003e const illegal = \u003cT, R\u003e(combination: Combination\u003cT\u003e): Combination\u003cR\u003e =\u003e\n\u003e   combination as unknown as Combination\u003cR\u003e;\n\u003e ```\n\u003e \n\u003e This means `'R' could be instantiated with an arbitrary type which could be unrelated to 'T'.` per `ts(2352)`\n\u003e \n\u003e ```typescript\n\u003e generate\u003c{\n\u003e   key: string[];\n\u003e }\u003e({\n\u003e   key: illegal(one([1, 2, 3])),\n\u003e   // ^ illegal\u003cnumber, string[]\u003e(combination: Combination\u003cnumber\u003e): Combination\u003cstring[]\u003e\n\u003e   // typescript will not be alarmed about this\n\u003e });\n\u003e ```\n\u003e \n\u003e In this example, key will be instantiated with one of `[1, 2, 3]` even though `key: string[]`.\n\u003e This will almost certainly throw a a runtime error.\n\u003e By using `illegal`, you are telling TS not to worry about the type of this key.\n\n\u003c/p\u003e\u003c/details\u003e\n\n\n```typescript\ngenerate\u003c{\n  key: string;\n}\u003e({\n  key: illegal(optional(\"value\"))\n  // ^ illegal\u003cstring | typeof KeyValueUndefined, string\u003e(combination: Combination\u003cstring | typeof KeyValueUndefined\u003e): Combination\u003cstring\u003e\n});\n```\n\n## ⚠️ **Beware Combinatorial explosion**\n\nThe following innocuous looking code will produce just over a million (`1_048_576`) combinations. `generate` can spit it out in just a few ms (~230 ms on my machine in deno), but your unit test, test framework, and test runner will likely buckle under the pressure.\n\n```typescript\ngenerate\u003c{}\u003e({\n  a: some([1, 2, 3, 4]),\n  b: some([1, 2, 3, 4]),\n  c: some([1, 2, 3, 4]),\n  d: some([1, 2, 3, 4]),\n  e: some([1, 2, 3, 4]),\n})\n```\n\nThe reasons for this are twofold\n\n- `some` produces a *lot* of combinations\n\n  ```typescript\n  some([1, 2, 3])\n  // is the combination:\n  [[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]\n  ```\n  \n- generate returns the combination of *every* combination it is supplied\n  - in the explosive example, it needs to return every combination of every some combination\n\nUsually, you will want to use `one` or `optional` instead of `some` to limit the number of combinations - `some` will produce combinations you are not interested in testing.\n\n## Contributing\n\nContributions welcome. If a new general purpose `Combination` is needed, please open an issue or pull request. Permutations were intentionally left out of this library to avoid combinatorial explosion - they are so rarely intentional.\n\nIf you can break the typing of `generate` please open an issue with code example.\n\nMIT License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fa-viv-a%2Fgenerate-combinations","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fa-viv-a%2Fgenerate-combinations","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fa-viv-a%2Fgenerate-combinations/lists"}