Created
April 24, 2023 06:23
-
-
Save antischematic/5eee4b7c7872937a3346e8e8bb5481c1 to your computer and use it in GitHub Desktop.
Typed BDD
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class User { | |
constructor(public name: string) {} | |
toString() { | |
return this.name | |
} | |
} | |
/// | |
const globalSteps = () => [] | |
globalSteps['step 1'] = () => {} | |
/// | |
const commonSteps = () => [globalSteps] | |
commonSteps['I log in as <user>'] = (user: User) => { | |
console.log(user) | |
} | |
/// | |
export const steps = () => [commonSteps] | |
steps['My name is <actor> and <foo>'] = (actor: string, foo: boolean, bar = 0) => { | |
console.log(actor, foo, bar) | |
} | |
steps[`My name is <string>`] = (actor: string) => { | |
console.log(actor) | |
} | |
/// | |
const { given, when, examples } = createTestSuite(steps) | |
skip.feature('Test feature', () => { | |
background(() => { | |
given('I log in as <user>', new User('Bob')) | |
}) | |
only.scenario('Can do thing', () => { | |
given('My name is <actor> and <foo>') | |
// @ts-expect-error | |
when('writing a new step') | |
examples([ | |
{ actor: 'Bob', foo: true } | |
]) | |
}) | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type ExtractParams<T> = T extends `${string}<${infer TName}>${infer TRemainder}` | |
? [TName, ...ExtractParams<TRemainder>] | |
: [] | |
type ExtractData<T extends keyof U, U> = { | |
[key in ExtractParams<T>[number]]?: Args<U[T]>[GetIndex<ExtractParams<T>, key>] | |
} | |
type GetIndex<T extends string[], R extends string> = { | |
[key in keyof T]: T[key] extends R ? key : never | |
}[number] | |
type ExtractAllData<T> = { | |
[key in keyof T]: key extends `${string}<${string}>` ? ExtractData<key, T> : never | |
}[keyof T] | |
type Args<T> = T extends (...args: infer R) => void ? R : [] | |
declare global { | |
function feature(tags: string, name: string, fn: () => void) | |
function feature(name: string, fn: () => void) | |
function background(fn: () => void) | |
function scenario(tags: string, name: string, fn: () => void) | |
function scenario(name: string, fn: () => void) | |
function beforeScenario(fn: () => void) | |
function beforeScenario(tags: string, fn: () => void) | |
function afterScenario(fn: () => void) | |
function afterScenario(tags: string, fn: () => void) | |
const skip: { | |
feature(tags: string, name: string, fn: () => void) | |
feature(name: string, fn: () => void) | |
scenario(tags: string, name: string, fn: () => void) | |
scenario(name: string, fn: () => void) | |
} | |
const only: { | |
feature(tags: string, name: string, fn: () => void) | |
feature(name: string, fn: () => void) | |
scenario(tags: string, name: string, fn: () => void) | |
scenario(name: string, fn: () => void) | |
} | |
} | |
interface TestSuite<T> { | |
given<U extends keyof T>(step: U): void | |
given<U extends keyof T>(step: U, ...params: Args<T[U]>): void | |
when<U extends keyof T>(step: U): void | |
when<U extends keyof T>(step: U, ...params: Args<T[U]>): void | |
then<U extends keyof T>(step: U): void | |
then<U extends keyof T>(step: U, ...params: Args<T[U]>): void | |
and<U extends keyof T>(step: U): void | |
and<U extends keyof T>(step: U, ...params: Args<T[U]>): void | |
but<U extends keyof T>(step: U): void | |
but<U extends keyof T>(step: U, ...params: Args<T[U]>): void | |
examples(tags: string, data: unknown extends T ? any[] : ExtractAllData<T>[]): void | |
examples(data: unknown extends T ? any[] : ExtractAllData<T>[]): void | |
} | |
type UnionToIntersection<U> = | |
(U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never | |
type ExtractReturn<T> = T extends () => infer R ? R extends [...infer S] ? S[number] | ExtractReturn<S[number]> : {} : {} | |
function createTestSuite(): TestSuite<unknown> | |
function createTestSuite<T extends () => any>(steps: T): TestSuite<T & UnionToIntersection<ExtractReturn<T>>> | |
function createTestSuite<T extends () => any>(steps?: T): TestSuite<any> { | |
return {} as any | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment