@travetto/test
Version:
Declarative test framework
117 lines (98 loc) • 4.2 kB
text/typescript
import { AppError, type Class, Runtime, describeFunction } from '@travetto/runtime';
import { type RegistryIndex, RegistryIndexStore, Registry } from '@travetto/registry';
import type { SuiteConfig } from '../model/suite.ts';
import type { TestConfig, TestRun } from '../model/test.ts';
import { SuiteRegistryAdapter } from './registry-adapter.ts';
const sortedTests = (config: SuiteConfig): TestConfig[] =>
Object.values(config.tests).toSorted((a, b) => a.lineStart - b.lineStart);
type SuiteTests = { suite: SuiteConfig, tests: TestConfig[] };
/**
* Test Suite registry
*/
export class SuiteRegistryIndex implements RegistryIndex {
static #instance = Registry.registerIndex(this);
static getForRegister(cls: Class): SuiteRegistryAdapter {
return this.#instance.store.getForRegister(cls);
}
static getTestConfig(cls: Class, method: Function | string): TestConfig | undefined {
return this.#instance.getTestConfig(cls, method);
}
static getSuiteTests(run: TestRun): SuiteTests[] {
return this.#instance.getSuiteTests(run);
}
static getConfig(cls: Class): SuiteConfig {
return this.#instance.store.get(cls).get();
}
static getClasses(): Class[] {
return this.#instance.store.getClasses();
}
static hasConfig(cls: Class): boolean {
return this.#instance.store.has(cls);
}
store = new RegistryIndexStore(SuiteRegistryAdapter);
/** @private */ constructor(source: unknown) { Registry.validateConstructor(source); }
/**
* Find all valid tests (ignoring abstract)
*/
getValidClasses(): Class[] {
return this.store.getClasses().filter(cls => !describeFunction(cls).abstract);
}
getConfig(cls: Class): SuiteConfig {
return this.store.get(cls).get();
}
/**
* Get run parameters from provided input
*/
getSuiteTests(run: TestRun): SuiteTests[] {
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(cls => this.getConfig(cls))
.filter(config => !config.skip);
const suite = suites.find(config => line >= config.lineStart && line <= config.lineEnd);
if (suite) {
const tests = sortedTests(suite);
const test = tests.find(config => line >= config.lineStart && line <= config.lineEnd);
return test ? [{ suite, tests: [test] }] : [{ suite, tests }];
} else {
return suites.map(config => ({ suite: config, tests: sortedTests(config) }));
}
} else { // Else lookup directly
if (methodNames.length) {
const cls = this.getValidClasses().find(type => type.Ⲑid === clsId);
if (!cls) {
throw new AppError('Unable to find suite for class ID', { details: { classId: clsId } });
}
const suite = this.getConfig(cls);
const tests = sortedTests(suite).filter(config => methodNames.includes(config.methodName));
return [{ suite, tests }];
} else if (clsId) {
const cls = this.getValidClasses().find(type => type.Ⲑid === clsId)!;
if (!cls) {
throw new AppError('Unable to find suite for class ID', { details: { classId: clsId } });
}
const suite = this.getConfig(cls);
return suite ? [{ suite, tests: sortedTests(suite) }] : [];
} else {
const suites = this.getValidClasses()
.map(type => this.getConfig(type))
.filter(config => !describeFunction(config.class).abstract); // Do not run abstract suites
return suites.map(config => ({ suite: config, tests: sortedTests(config) }));
}
}
}
/**
* Find a test configuration given class and optionally a method
*/
getTestConfig(cls: Class, method: Function | string): TestConfig | undefined {
if (this.store.has(cls)) {
const config = this.getConfig(cls);
const methodName = typeof method === 'string' ? method : method.name;
return Object.values(config.tests).find(item => item.methodName === methodName);
}
}
}