// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. import { DescribeDefinition, HookNames, ItDefinition, TestSuite, TestSuiteInternal, } from "./_test_suite.ts"; export type { DescribeDefinition, ItDefinition, TestSuite }; /** The arguments for an ItFunction. */ export type ItArgs = | [options: ItDefinition] | [ name: string, options: Omit, "name">, ] | [ name: string, fn: (this: T, t: Deno.TestContext) => void | Promise, ] | [fn: (this: T, t: Deno.TestContext) => void | Promise] | [ name: string, options: Omit, "fn" | "name">, fn: (this: T, t: Deno.TestContext) => void | Promise, ] | [ options: Omit, "fn">, fn: (this: T, t: Deno.TestContext) => void | Promise, ] | [ options: Omit, "fn" | "name">, fn: (this: T, t: Deno.TestContext) => void | Promise, ] | [ suite: TestSuite, name: string, options: Omit, "name" | "suite">, ] | [ suite: TestSuite, name: string, fn: (this: T, t: Deno.TestContext) => void | Promise, ] | [ suite: TestSuite, fn: (this: T, t: Deno.TestContext) => void | Promise, ] | [ suite: TestSuite, name: string, options: Omit, "fn" | "name" | "suite">, fn: (this: T, t: Deno.TestContext) => void | Promise, ] | [ suite: TestSuite, options: Omit, "fn" | "suite">, fn: (this: T, t: Deno.TestContext) => void | Promise, ] | [ suite: TestSuite, options: Omit, "fn" | "name" | "suite">, fn: (this: T, t: Deno.TestContext) => void | Promise, ]; /** Generates an ItDefinition from ItArgs. */ function itDefinition(...args: ItArgs): ItDefinition { let [ suiteOptionsOrNameOrFn, optionsOrNameOrFn, optionsOrFn, fn, ] = args; let suite: TestSuite | undefined = undefined; let name: string; let options: | ItDefinition | Omit, "fn"> | Omit, "name"> | Omit, "fn" | "name">; if ( typeof suiteOptionsOrNameOrFn === "object" && typeof (suiteOptionsOrNameOrFn as TestSuite).symbol === "symbol" ) { suite = suiteOptionsOrNameOrFn as TestSuite; } else { fn = optionsOrFn as typeof fn; optionsOrFn = optionsOrNameOrFn as typeof optionsOrFn; optionsOrNameOrFn = suiteOptionsOrNameOrFn as typeof optionsOrNameOrFn; } if (typeof optionsOrNameOrFn === "string") { name = optionsOrNameOrFn; if (typeof optionsOrFn === "function") { fn = optionsOrFn; options = {}; } else { options = optionsOrFn!; if (!fn) fn = (options as Omit, "name">).fn; } } else if (typeof optionsOrNameOrFn === "function") { fn = optionsOrNameOrFn; name = fn.name; options = {}; } else { options = optionsOrNameOrFn!; if (typeof optionsOrFn === "function") { fn = optionsOrFn; } else { fn = (options as ItDefinition).fn; } name = (options as ItDefinition).name ?? fn.name; } return { suite, ...options, name, fn, }; } /** Registers an individual test case. */ export interface it { (...args: ItArgs): void; /** Registers an individual test case with only set to true. */ only(...args: ItArgs): void; /** Registers an individual test case with ignore set to true. */ ignore(...args: ItArgs): void; } /** Registers an individual test case. */ export function it(...args: ItArgs): void { if (TestSuiteInternal.runningCount > 0) { throw new Error( "cannot register new test cases after already registered test cases start running", ); } const options = itDefinition(...args); const { suite } = options; const testSuite = suite ? TestSuiteInternal.suites.get(suite.symbol) : TestSuiteInternal.current; if (!TestSuiteInternal.started) TestSuiteInternal.started = true; if (testSuite) { TestSuiteInternal.addStep(testSuite, options); } else { const { name, fn, ignore, only, permissions, sanitizeExit, sanitizeOps, sanitizeResources, } = options; TestSuiteInternal.registerTest({ name, ignore, only, permissions, sanitizeExit, sanitizeOps, sanitizeResources, async fn(t) { TestSuiteInternal.runningCount++; try { await fn.call({} as T, t); } finally { TestSuiteInternal.runningCount--; } }, }); } } it.only = function itOnly(...args: ItArgs): void { const options = itDefinition(...args); return it({ ...options, only: true, }); }; it.ignore = function itIgnore(...args: ItArgs): void { const options = itDefinition(...args); return it({ ...options, ignore: true, }); }; function addHook( name: HookNames, fn: (this: T) => void | Promise, ): void { if (!TestSuiteInternal.current) { if (TestSuiteInternal.started) { throw new Error( "cannot add global hooks after a global test is registered", ); } TestSuiteInternal.current = new TestSuiteInternal({ name: "global", [name]: fn, }); } else { TestSuiteInternal.setHook(TestSuiteInternal.current!, name, fn); } } /** Run some shared setup before all of the tests in the suite. */ export function beforeAll( fn: (this: T) => void | Promise, ): void { addHook("beforeAll", fn); } /** Run some shared teardown after all of the tests in the suite. */ export function afterAll( fn: (this: T) => void | Promise, ): void { addHook("afterAll", fn); } /** Run some shared setup before each test in the suite. */ export function beforeEach( fn: (this: T) => void | Promise, ): void { addHook("beforeEach", fn); } /** Run some shared teardown after each test in the suite. */ export function afterEach( fn: (this: T) => void | Promise, ): void { addHook("afterEach", fn); } /** The arguments for a DescribeFunction. */ export type DescribeArgs = | [options: DescribeDefinition] | [name: string] | [ name: string, options: Omit, "name">, ] | [name: string, fn: () => void] | [fn: () => void] | [ name: string, options: Omit, "fn" | "name">, fn: () => void, ] | [ options: Omit, "fn">, fn: () => void, ] | [ options: Omit, "fn" | "name">, fn: () => void, ] | [ suite: TestSuite, name: string, ] | [ suite: TestSuite, name: string, options: Omit, "name" | "suite">, ] | [ suite: TestSuite, name: string, fn: () => void, ] | [ suite: TestSuite, fn: () => void, ] | [ suite: TestSuite, name: string, options: Omit, "fn" | "name" | "suite">, fn: () => void, ] | [ suite: TestSuite, options: Omit, "fn" | "suite">, fn: () => void, ] | [ suite: TestSuite, options: Omit, "fn" | "name" | "suite">, fn: () => void, ]; /** Generates a DescribeDefinition from DescribeArgs. */ function describeDefinition( ...args: DescribeArgs ): DescribeDefinition { let [ suiteOptionsOrNameOrFn, optionsOrNameOrFn, optionsOrFn, fn, ] = args; let suite: TestSuite | undefined = undefined; let name: string; let options: | DescribeDefinition | Omit, "fn"> | Omit, "name"> | Omit, "fn" | "name">; if ( typeof suiteOptionsOrNameOrFn === "object" && typeof (suiteOptionsOrNameOrFn as TestSuite).symbol === "symbol" ) { suite = suiteOptionsOrNameOrFn as TestSuite; } else { fn = optionsOrFn as typeof fn; optionsOrFn = optionsOrNameOrFn as typeof optionsOrFn; optionsOrNameOrFn = suiteOptionsOrNameOrFn as typeof optionsOrNameOrFn; } if (typeof optionsOrNameOrFn === "string") { name = optionsOrNameOrFn; if (typeof optionsOrFn === "function") { fn = optionsOrFn; options = {}; } else { options = optionsOrFn ?? {}; if (!fn) fn = (options as Omit, "name">).fn; } } else if (typeof optionsOrNameOrFn === "function") { fn = optionsOrNameOrFn; name = fn.name; options = {}; } else { options = optionsOrNameOrFn ?? {}; if (typeof optionsOrFn === "function") { fn = optionsOrFn; } else { fn = (options as DescribeDefinition).fn; } name = (options as DescribeDefinition).name ?? fn?.name ?? ""; } if (!suite) { suite = options.suite; } if (!suite && TestSuiteInternal.current) { const { symbol } = TestSuiteInternal.current; suite = { symbol }; } return { ...options, suite, name, fn, }; } /** Registers a test suite. */ export interface describe { (...args: DescribeArgs): TestSuite; /** Registers a test suite with only set to true. */ only(...args: DescribeArgs): TestSuite; /** Registers a test suite with ignore set to true. */ ignore(...args: DescribeArgs): TestSuite; } /** Registers a test suite. */ export function describe( ...args: DescribeArgs ): TestSuite { if (TestSuiteInternal.runningCount > 0) { throw new Error( "cannot register new test suites after already registered test cases start running", ); } const options = describeDefinition(...args); if (!TestSuiteInternal.started) TestSuiteInternal.started = true; const { symbol } = new TestSuiteInternal(options); return { symbol }; } describe.only = function describeOnly( ...args: DescribeArgs ): TestSuite { const options = describeDefinition(...args); return describe({ ...options, only: true, }); }; describe.ignore = function describeIgnore( ...args: DescribeArgs ): TestSuite { const options = describeDefinition(...args); return describe({ ...options, ignore: true, }); };