UNPKG

@travetto/test

Version:

Declarative test framework

143 lines (127 loc) 4.6 kB
import { Class, Runtime, classConstruct, describeFunction, asFull } from '@travetto/runtime'; import { MetadataRegistry } from '@travetto/registry'; import { SuiteConfig } from '../model/suite.ts'; import { TestConfig, TestRun } from '../model/test.ts'; /** * Test Suite registry */ class $SuiteRegistry extends MetadataRegistry<SuiteConfig, TestConfig> { /** * Find all valid tests (ignoring abstract) */ getValidClasses(): Class[] { return this.getClasses().filter(c => !describeFunction(c).abstract); } createPending(cls: Class): Partial<SuiteConfig> { const lines = describeFunction(cls)?.lines; return { class: cls, classId: cls.Ⲑid, tags: [], import: Runtime.getImport(cls), lineStart: lines?.[0], lineEnd: lines?.[1], tests: [], beforeAll: [], beforeEach: [], afterAll: [], afterEach: [] }; } override createPendingField(cls: Class, fn: Function): Partial<TestConfig> { const lines = describeFunction(cls)?.methods?.[fn.name].lines; return { class: cls, tags: [], import: Runtime.getImport(cls), lineStart: lines?.[0], lineEnd: lines?.[1], lineBodyStart: lines?.[2], methodName: fn.name }; } /** * Add a new phase listeners */ registerPendingListener<T>(cls: Class<T>, listener: Function, phase: 'beforeAll' | 'beforeEach' | 'afterAll' | 'afterEach'): void { const suiteConfig = this.getOrCreatePending(cls); suiteConfig[phase]!.push(listener); } /** * On finalize, collapse state with super classes to create * a full projection of all listeners and tests. */ onInstallFinalize<T>(cls: Class<T>): SuiteConfig { const config = asFull(this.getOrCreatePending(cls)); const tests = [...this.pendingFields.get(cls.Ⲑid)!.values()]; const parent = this.getParentClass(cls); if (parent && this.has(parent)) { const pConf = this.get(parent); config.afterAll.push(...pConf.afterAll); config.beforeAll.push(...pConf.beforeAll); config.afterEach.push(...pConf.afterEach); config.beforeEach.push(...pConf.beforeEach); tests.push(...[...pConf.tests.values()].map(t => ({ ...t, sourceImport: pConf.import, class: cls }))); } config.instance = classConstruct(config.class); config.tests = tests!.map(x => asFull(x)); config.description ||= config.classId; for (const t of config.tests) { t.classId = config.classId; t.import = config.import; t.tags = [...t.tags!, ...config.tags!]; } return config; } /** * Get run parameters from provided input */ getSuiteTests(run: TestRun): { suite: SuiteConfig, tests: TestConfig[] }[] { const clsId = run.classId; const imp = run.import; const methodNames = run.methodNames ?? []; if (clsId && /^\d+$/.test(clsId)) { // If we only have a line number const line = parseInt(clsId, 10); const suites = this.getValidClasses() .filter(cls => Runtime.getImport(cls) === imp) .map(x => this.get(x)).filter(x => !x.skip); const suite = suites.find(x => line >= x.lineStart && line <= x.lineEnd); if (suite) { const test = suite.tests.find(x => line >= x.lineStart && line <= x.lineEnd); return test ? [{ suite, tests: [test] }] : [{ suite, tests: suite.tests }]; } else { return suites.map(x => ({ suite: x, tests: x.tests })); } } else { // Else lookup directly if (methodNames.length) { const cls = this.getValidClasses().find(x => x.Ⲑid === clsId)!; const suite = this.get(cls); const tests = suite.tests.filter(x => methodNames.includes(x.methodName))!; return [{ suite, tests }]; } else if (clsId) { const cls = this.getValidClasses().find(x => x.Ⲑid === clsId)!; const suite = this.get(cls); return suite ? [{ suite, tests: suite.tests }] : []; } else { const suites = this.getValidClasses() .map(x => this.get(x)) .filter(x => !describeFunction(x.class).abstract); // Do not run abstract suites return suites.map(x => ({ suite: x, tests: x.tests })); } } } /** * Find a test configuration given class and optionally a method */ getByClassAndMethod(cls: Class, method: Function): TestConfig | undefined { if (this.has(cls)) { const conf = this.get(cls); return conf.tests.find(x => x.methodName === method.name); } } } export const SuiteRegistry = new $SuiteRegistry();