@travetto/test
Version:
Declarative test framework
133 lines (119 loc) • 3.96 kB
text/typescript
import type { RegistryAdapter } from '@travetto/registry';
import { AppError, asFull, type Class, describeFunction, Runtime, safeAssign } from '@travetto/runtime';
import { SchemaRegistryIndex } from '@travetto/schema';
import type { SuiteConfig } from '../model/suite.ts';
import type { TestConfig } from '../model/test.ts';
function combineClasses(baseConfig: SuiteConfig, ...subConfig: Partial<SuiteConfig>[]): SuiteConfig {
for (const config of subConfig) {
if (config.beforeAll) {
baseConfig.beforeAll = [...baseConfig.beforeAll, ...config.beforeAll];
}
if (config.beforeEach) {
baseConfig.beforeEach = [...baseConfig.beforeEach, ...config.beforeEach];
}
if (config.afterAll) {
baseConfig.afterAll = [...baseConfig.afterAll, ...config.afterAll];
}
if (config.afterEach) {
baseConfig.afterEach = [...baseConfig.afterEach, ...config.afterEach];
}
if (config.tags) {
baseConfig.tags = [...baseConfig.tags ?? [], ...config.tags];
}
baseConfig.skip = config.skip ?? baseConfig.skip;
if (config.tests) {
for (const [key, test] of Object.entries(config.tests ?? {})) {
baseConfig.tests[key] = {
...test,
sourceImport: Runtime.getImport(baseConfig.class),
class: baseConfig.class,
classId: baseConfig.classId,
import: baseConfig.import,
};
}
}
}
return baseConfig;
}
function combineMethods(suite: SuiteConfig, baseConfig: TestConfig, ...subConfig: Partial<TestConfig>[]): TestConfig {
baseConfig.classId = suite.classId;
baseConfig.import = suite.import;
for (const config of subConfig) {
safeAssign(baseConfig, config, {
tags: [
...baseConfig.tags ?? [],
...config.tags ?? []
]
});
}
return baseConfig;
}
export class SuiteRegistryAdapter implements RegistryAdapter<SuiteConfig> {
#cls: Class;
#config: SuiteConfig;
constructor(cls: Class) {
this.#cls = cls;
}
register(...data: Partial<SuiteConfig>[]): SuiteConfig {
if (!this.#config) {
const { lines, hash } = describeFunction(this.#cls) ?? {};
this.#config = asFull<SuiteConfig>({
class: this.#cls,
classId: this.#cls.Ⲑid,
tags: [],
skip: false,
import: Runtime.getImport(this.#cls),
lineStart: lines?.[0],
lineEnd: lines?.[1],
sourceHash: hash,
tests: {},
beforeAll: [],
beforeEach: [],
afterAll: [],
afterEach: []
});
}
combineClasses(this.#config, ...data);
return this.#config;
}
registerTest(method: string, ...data: Partial<TestConfig>[]): TestConfig {
const suite = this.register();
if (!(method in this.#config.tests)) {
const { lines, hash } = describeFunction(this.#cls)?.methods?.[method] ?? {};
const config = asFull<TestConfig>({
class: this.#cls,
tags: [],
skip: false,
import: Runtime.getImport(this.#cls),
lineStart: lines?.[0],
lineEnd: lines?.[1],
lineBodyStart: lines?.[2],
methodName: method,
sourceHash: hash,
});
this.#config.tests[method] = config;
}
const result = this.#config.tests[method];
combineMethods(suite, result, ...data);
return result;
}
finalize(parent?: SuiteConfig): void {
if (parent) {
combineClasses(this.#config, parent);
}
for (const test of Object.values(this.#config.tests)) {
test.tags = [...test.tags ?? [], ...this.#config.tags ?? []];
test.description ||= SchemaRegistryIndex.get(this.#cls).getMethod(test.methodName).description;
}
}
get(): SuiteConfig {
return this.#config;
}
getMethod(method: string): TestConfig {
const test = this.#config.tests[method];
if (!test) {
throw new AppError(`Test not registered: ${String(method)} on ${this.#cls.name}`);
}
return test;
}
}