diff --git a/packages/e2e/default/src/simple.test.ts b/packages/e2e/default/src/simple.test.ts index df62118..57d18bf 100644 --- a/packages/e2e/default/src/simple.test.ts +++ b/packages/e2e/default/src/simple.test.ts @@ -1,5 +1,17 @@ describe('Simple suite', () => { + beforeAll(() => { + console.log('beforeAll'); + }); + it('should pass', () => { expect(true).toBe(true); }); + + it('should also pass', () => { + expect(true).toBe(true); + }); + + afterEach(() => { + console.log('afterEach'); + }); }); diff --git a/packages/library/package.json b/packages/library/package.json index c7abe2b..2afb4ab 100644 --- a/packages/library/package.json +++ b/packages/library/package.json @@ -45,7 +45,7 @@ "@types/glob": "^8.0.0", "@types/lodash": "^4.14.186", "@types/node": "^14.18.46", - "@types/rimraf": "^3.0.2", + "@types/rimraf": "^4.0.5", "fs-extra": "^10.1.0", "glob": "^8.0.3", "lodash": "^4.17.21", @@ -56,9 +56,9 @@ "dependencies": { "@noomorph/allure-js-commons": "^2.3.0", "ci-info": "^3.8.0", - "jest-metadata": "^1.0.0-beta.11", + "jest-metadata": "^1.0.0-beta.15", "pkg-up": "^3.1.0", - "rimraf": "^3.0.2", + "rimraf": "^4.3.1", "strip-ansi": "^6.0.0" }, "prettier": "@wix/prettier-config-jest-allure2-reporter", diff --git a/packages/library/src/JestAllure2Reporter.ts b/packages/library/src/JestAllure2Reporter.ts index baddccb..c587488 100644 --- a/packages/library/src/JestAllure2Reporter.ts +++ b/packages/library/src/JestAllure2Reporter.ts @@ -6,22 +6,24 @@ import type { ReporterOnStartOptions, Test, TestCaseResult, - TestContext, - TestResult, } from '@jest/reporters'; -import { query, JestMetadataReporter } from 'jest-metadata/reporter'; +import {JestMetadataReporter, query} from 'jest-metadata/reporter'; import rimraf from 'rimraf'; -import { AllureRuntime } from '@noomorph/allure-js-commons'; +import { + AllureRuntime, + Stage, + Status, +} from '@noomorph/allure-js-commons'; import type { + AllureTestCaseMetadata, + AllureTestStepMetadata, GlobalExtractorContext, ReporterOptions, - AllureRunMetadata, - AllureTestCaseMetadata, -} from './ReporterOptions'; -import { resolveOptions } from './options'; +} from './options/ReporterOptions'; +import {resolveOptions} from './options'; -const NAMESPACE = 'allure2' as const; +const ns = (key?: string) => (key ? ['allure2', key] : ['allure2']); export class JestAllure2Reporter extends JestMetadataReporter { private readonly _allure: AllureRuntime; @@ -35,6 +37,10 @@ export class JestAllure2Reporter extends JestMetadataReporter { super(globalConfig, options); this._options = resolveOptions(options); + if (this._options.overwrite) { + rimraf.sync(this._options.resultsDir); + } + this._allure = new AllureRuntime({ resultsDir: this._options.resultsDir, }); @@ -52,9 +58,6 @@ export class JestAllure2Reporter extends JestMetadataReporter { ): Promise { await super.onRunStart(aggregatedResult, options); - if (this._options.overwrite) { - rimraf.sync(this._options.resultsDir); - } const environment = this._options.environment(this._globalContext); if (environment) { @@ -70,49 +73,96 @@ export class JestAllure2Reporter extends JestMetadataReporter { onTestFileStart(test: Test) { super.onTestFileStart(test); - const run = query.test(test)!.get(NAMESPACE, {}) as AllureRunMetadata; - run.startedAt = Date.now(); - run.threadId = '1'; - } - - onTestCaseStart(_test: unknown, _testCaseStartInfo: unknown) { - super.onTestCaseStart(_test, _testCaseStartInfo); - - const testEntry = query.testCaseResult(testCaseResult)!; - const metadata = testEntry.get(NAMESPACE, {}) as AllureTestCaseMetadata; - - metadata.start = Date.now(); + query.test(test)!.assign(ns(), { + start: Date.now(), + threadId: '1', + }); } onTestCaseResult(test: Test, testCaseResult: TestCaseResult) { super.onTestCaseResult(test, testCaseResult); const testEntry = query.testCaseResult(testCaseResult)!; - const metadata = testEntry.get(NAMESPACE, {}) as AllureTestCaseMetadata; - - metadata.identifier = testEntry.id; - metadata.start = - metadata.start ?? Date.now() - (testCaseResult.duration ?? 0); - metadata.stop = Date.now(); - } - - onTestFileResult( - test: Test, - testResult: TestResult, - aggregatedResult: AggregatedResult, - ): Promise | void { - super.onTestFileResult(test, testResult, aggregatedResult); - - const run = query.test(test)!.get(NAMESPACE, {}) as AllureRunMetadata; - const fileContext = this._testRunContext.getFileContext(test.path)!; - fileContext.handleTestFileResult(testResult); - } - - async onRunComplete( - testContexts: Set, - aggregatedResult: AggregatedResult, - ): Promise { - await super.onRunComplete(testContexts, aggregatedResult); + const lastInvocation = testEntry.lastInvocation; + if (lastInvocation) { + const userTestMetadata = { + ...(testEntry.get(ns()) as AllureTestCaseMetadata), + ...(lastInvocation.get(ns()) as AllureTestCaseMetadata), + } as AllureTestStepMetadata; + + const group = this._allure.startGroup(testCaseResult.fullName); + + const test = group.startTest(testCaseResult.title, userTestMetadata.start); + test.fullName = testCaseResult.fullName; + + const allInvocations = [ + [() => group.addBefore(), lastInvocation.beforeAll], + [() => group.addBefore(), lastInvocation.before], + [() => test.startStep('test'), lastInvocation.fn ? [lastInvocation.fn] : []], + [() => group.addAfter(), lastInvocation.after], + [() => group.addAfter(), lastInvocation.afterAll], + ] as const; + + for (const [createGroup, invocations] of allInvocations) { + for (const invocation of invocations) { + const definition = invocation.definition.get( + ns(), + )! as AllureTestStepMetadata; + const executable = invocation.get(ns())! as AllureTestStepMetadata; + const userMetadata: AllureTestStepMetadata = { + ...userTestMetadata, + ...definition, + ...executable, + }; + + const step = createGroup(); + step.stage = userMetadata.stage ?? Stage.SCHEDULED; + step.status = userMetadata.status ?? step.status; + if (userMetadata.start != null) { + step.wrappedItem.start = userMetadata.start; + } + if (userMetadata.stop != null) { + step.wrappedItem.stop = userMetadata.stop; + } + if (userMetadata.statusDetails != null) { + step.statusDetails = userMetadata.statusDetails; + } + if (userMetadata.name != null) { + step.name = userMetadata.name; + } + } + } + + let code = ''; + const attachCodeAs = (type: string, content: unknown) => { + if (content) { + code += `${type}(${content})\n`; + } + }; + for (const block of lastInvocation.beforeAll) { + attachCodeAs('beforeAll', block.definition.get(ns('code'))); + } + for (const block of lastInvocation.before) { + attachCodeAs('beforeEach', block.definition.get(ns('code'))); + } + attachCodeAs('test', lastInvocation.entry.get(ns('code'))); + debugger; + for (const block of lastInvocation.after) { + attachCodeAs('afterEach', block.definition.get(ns('code'))); + } + for (const block of lastInvocation.afterAll) { + attachCodeAs('afterAll', block.definition.get(ns('code'))); + } + + test.descriptionHtml = '
Test code
\n' + code + '\n
'; + test.status = testCaseResult.status === 'passed' ? Status.PASSED : Status.FAILED; + test.stage = Stage.FINISHED; + test.statusDetails = { + message: testCaseResult.failureMessages.join('\n'), + }; + test.endTest(userTestMetadata.stop); + group.endGroup(); + } } getLastError(): Error | void { diff --git a/packages/library/src/__tests__/environment.ts b/packages/library/src/__tests__/environment.ts deleted file mode 100644 index 14fe2ab..0000000 --- a/packages/library/src/__tests__/environment.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type { QueryResults } from '../__utils__/runReporter'; -import { runReporter } from '../__utils__/runReporter'; - -describe('environment.properties', () => { - let query: QueryResults; - - describe('with default options', () => { - beforeAll(async () => { - query = await runReporter({}); - query = query.byFileName(/environment\.properties$/); - }); - - it('should produce environment.properties file', () => { - expect(query.value).toHaveLength(1); - expect(query.value[0].lines).toEqual( - expect.arrayContaining([expect.stringContaining(' = ')]), - ); - expect(query.value[0].lines.length).toBeGreaterThan(1); - }); - }); - - describe('with options.environmentInfo = (...)', () => { - beforeAll(async () => { - query = await runReporter({ - environment: () => ({ - CUSTOM_INFO: 'CUSTOM_VALUE', - }), - }); - query = query.byFileName(/environment\.properties$/); - }); - - it('should produce a custom environment.properties file', () => { - expect(query.value).toEqual([ - expect.objectContaining({ - lines: ['CUSTOM_INFO = CUSTOM_VALUE'], - }), - ]); - }); - }); -}); diff --git a/packages/library/src/__tests__/statuses.ts b/packages/library/src/__tests__/statuses.ts deleted file mode 100644 index ddcaa40..0000000 --- a/packages/library/src/__tests__/statuses.ts +++ /dev/null @@ -1,132 +0,0 @@ -import type { QueryResults } from '../__utils__/runReporter'; -import { runReporter } from '../__utils__/runReporter'; - -describe('statuses.test.js', () => { - let query: QueryResults; - - describe('with default options', () => { - beforeAll(async () => { - query = await runReporter({}); - query = query.bySuite(/statuses/).sortBy('start'); - }); - - it('should have 2 broken tests', () => - expect(query.byStatus('broken').value).toEqual([ - expect.objectContaining({ - name: 'root broken test', - fullName: 'root broken test', - status: 'broken', - statusDetails: expect.objectContaining({ - message: 'Error: Simulated error', - }), - }), - expect.objectContaining({ - name: 'inner broken test', - fullName: 'Suite inner broken test', - status: 'broken', - statusDetails: expect.objectContaining({ - message: 'Error: Simulated error', - }), - }), - ])); - - it('should have 2 failed tests', () => - expect(query.byStatus('failed').value).toEqual([ - expect.objectContaining({ - name: 'root failed test', - fullName: 'root failed test', - status: 'failed', - statusDetails: expect.objectContaining({ - message: expect.any(String), - }), - }), - expect.objectContaining({ - name: 'inner failed test', - fullName: 'Suite inner failed test', - status: 'failed', - statusDetails: expect.objectContaining({ - message: expect.any(String), - }), - }), - ])); - - it('should have 2 skipped tests', () => - expect(query.byStatus('skipped').sortBy('name').value).toEqual([ - expect.objectContaining({ - name: 'inner skipped test', - fullName: 'Suite inner skipped test', - status: 'skipped', - }), - expect.objectContaining({ - name: 'root skipped test', - fullName: 'root skipped test', - status: 'skipped', - }), - ])); - - it('should have 2 passed tests', () => - expect(query.byStatus('passed').value).toEqual([ - expect.objectContaining({ - name: 'root passed test (600ms)', - fullName: 'root passed test (600ms)', - status: 'passed', - }), - expect.objectContaining({ - name: 'inner passed test (600ms)', - fullName: 'Suite inner passed test (600ms)', - status: 'passed', - }), - ])); - - it('should have correct test durations', () => { - const results = query.byName(/600ms/).value; - const durations = results.map((r) => r.stop - r.start); - - expect(Math.min(...durations)).toBeGreaterThanOrEqual(600); - }); - - it('should have package label taken from package.json', () => { - const expected = Array.from({ length: 8 }, () => 'jest-allure2-reporter'); - expect(query.labels('package').value.map((label) => label.value)).toEqual( - expected, - ); - }); - }); - - describe('with options.errorsAsFailedAssertions = true', () => { - beforeAll(async () => { - query = await runReporter({ - errorsAsFailedAssertions: true, - }); - query = query.bySuite(/statuses/).sortBy('start'); - }); - - it('should have 0 broken tests', () => { - expect(query.byStatus('broken').value).toEqual([]); - }); - - it('should have 4 failed tests', () => { - expect(query.byStatus('failed').value).toHaveLength(4); - }); - }); - - describe('with options.packageName = "custom"', () => { - beforeAll(async () => { - query = await runReporter({ - testCase: { - labels: { - package: 'custom', - }, - }, - }); - query = query.bySuite(/statuses/); - }); - - it('should have package label taken from package.json', () => { - const expected = Array.from({ length: 8 }, () => 'custom'); - expect(query.labels('package').value.map((label) => label.value)).toEqual( - expected, - ); - }); - }); -}); diff --git a/packages/library/src/__utils__/runReporter.ts b/packages/library/src/__utils__/runReporter.ts deleted file mode 100644 index bfe0b03..0000000 --- a/packages/library/src/__utils__/runReporter.ts +++ /dev/null @@ -1,116 +0,0 @@ -import path from 'node:path'; - -import fs from 'fs-extra'; -import glob from 'glob'; -import _ from 'lodash'; -import tempfile from 'tempfile'; - -import { JestAllure2Reporter } from '../JestAllure2Reporter'; -import type { ReporterOptions } from '../ReporterOptions'; - -const rootDirectory = path.join(__dirname, '../..'); - -export async function runReporter( - overrides: Partial, -) { - const options = { - ...overrides, - resultsDir: tempfile(''), - }; - - try { - jest.useFakeTimers(); - - const globalConfig = { rootDir: rootDirectory } as any; - const reporter = new JestAllure2Reporter(globalConfig, options); - - const testReporterCalls = await readTestReporterCalls(); - for (const call of testReporterCalls) { - jest.setSystemTime(call.time); - if (call.method in reporter) { - await (reporter as any)[call.method](...call.params); - } - } - - const allureResults = await readAllureResults(options.resultsDir); - return new QueryResults(allureResults); - } finally { - jest.useRealTimers(); - await fs.remove(options.resultsDir); - } -} - -async function readTestReporterCalls() { - const jestVersion = process.env.JEST_VERSION; - const testReporterCallsPath = jestVersion - ? path.join( - rootDirectory, - '__fixtures__/recordings', - `${jestVersion}.jsonl`, - ) - : glob - .sync(path.join(rootDirectory, '__fixtures__/recordings/*.jsonl')) - .reverse()[0]; - - return fs - .readFileSync(testReporterCallsPath, 'utf8') - .split('\n') - .filter(Boolean) - .map((x) => JSON.parse(x)); -} - -async function readAllureResults( - allureResultsDirectory: string, -): Promise[]> { - const files = await fs.readdir(allureResultsDirectory); - return await Promise.all( - files.map(async (file) => { - const content = await fs.readFile( - path.join(allureResultsDirectory, file), - 'utf8', - ); - - return file.endsWith('.json') - ? JSON.parse(content as any) - : { filename: file, lines: content.split('\n').filter(Boolean) }; - }), - ); -} - -export class QueryResults { - constructor(public readonly value: any[]) {} - - bySuite(name: RegExp) { - const subset = this.value.filter((result) => { - const labels = _.get(result, 'labels', []) as object[]; - const suiteLabel = _.find(labels, { name: 'suite' }) as any; - return suiteLabel?.value?.match(name); - }); - - return new QueryResults(subset); - } - - byName(name: RegExp) { - const subset = this.value.filter((r) => r.name?.match(name)); - return new QueryResults(subset); - } - - byFileName(name: RegExp) { - const subset = this.value.filter((r) => r.filename?.match(name)); - return new QueryResults(subset); - } - - byStatus(value: string) { - const subset = _.filter(this.value, { status: value }); - return new QueryResults(subset); - } - - labels(name: string) { - const subset = _(this.value).flatMap('labels').filter({ name }).value(); - return new QueryResults(subset); - } - - sortBy(property: string) { - return new QueryResults(_.sortBy(this.value, property)); - } -} diff --git a/packages/library/src/allure/index.ts b/packages/library/src/allure/index.ts deleted file mode 100644 index e69de29..0000000 diff --git a/packages/library/src/annotations/$AllureId.ts b/packages/library/src/annotations/$AllureId.ts index 4ca4356..36c834b 100644 --- a/packages/library/src/annotations/$AllureId.ts +++ b/packages/library/src/annotations/$AllureId.ts @@ -1,4 +1,4 @@ -import { $Set } from 'jest-metadata/annotations'; +import { $Set } from 'jest-metadata'; import { ALLURE_ID } from '../constants'; diff --git a/packages/library/src/annotations/$Description.ts b/packages/library/src/annotations/$Description.ts index b7408f1..eafae45 100644 --- a/packages/library/src/annotations/$Description.ts +++ b/packages/library/src/annotations/$Description.ts @@ -1,4 +1,4 @@ -import {$Push} from "jest-metadata/annotations"; +import { $Push } from 'jest-metadata'; import { DESCRIPTION } from '../constants'; diff --git a/packages/library/src/annotations/$DescriptionHtml.ts b/packages/library/src/annotations/$DescriptionHtml.ts index c8dab1c..6811f89 100644 --- a/packages/library/src/annotations/$DescriptionHtml.ts +++ b/packages/library/src/annotations/$DescriptionHtml.ts @@ -1,4 +1,6 @@ -import {$Push} from "jest-metadata/annotations"; +import { $Push } from 'jest-metadata'; + +import { DESCRIPTION_HTML } from '../constants'; export const $DescriptionHtml = (descriptionHtml: string) => $Push(DESCRIPTION_HTML, descriptionHtml); diff --git a/packages/library/src/annotations/$Epic.ts b/packages/library/src/annotations/$Epic.ts new file mode 100644 index 0000000..1777623 --- /dev/null +++ b/packages/library/src/annotations/$Epic.ts @@ -0,0 +1,5 @@ +import { $Push } from 'jest-metadata'; + +import { EPIC } from '../constants'; + +export const $Epic = (name: string) => $Push(EPIC, name); diff --git a/packages/library/src/annotations/$Feature.ts b/packages/library/src/annotations/$Feature.ts new file mode 100644 index 0000000..1a5b32a --- /dev/null +++ b/packages/library/src/annotations/$Feature.ts @@ -0,0 +1,5 @@ +import { $Push } from 'jest-metadata'; + +import { FEATURE } from '../constants'; + +export const $FEATURE = (name: string) => $Push(FEATURE, name); diff --git a/packages/library/src/annotations/$Lead.ts b/packages/library/src/annotations/$Lead.ts index 05fbfe6..3159e84 100644 --- a/packages/library/src/annotations/$Lead.ts +++ b/packages/library/src/annotations/$Lead.ts @@ -1,3 +1,5 @@ -import {$Push} from "jest-metadata/annotations"; +import { $Push } from 'jest-metadata'; + +import { LEAD } from '../constants'; export const $Lead = (lead: string) => $Push(LEAD, lead); diff --git a/packages/library/src/annotations/$Link.ts b/packages/library/src/annotations/$Link.ts index fab8002..e93cbd7 100644 --- a/packages/library/src/annotations/$Link.ts +++ b/packages/library/src/annotations/$Link.ts @@ -1,4 +1,4 @@ -import { $Push } from 'jest-metadata/annotations'; +import { $Push } from 'jest-metadata'; import { LINK } from '../constants'; diff --git a/packages/library/src/annotations/$Owner.ts b/packages/library/src/annotations/$Owner.ts index 5336ec5..a406480 100644 --- a/packages/library/src/annotations/$Owner.ts +++ b/packages/library/src/annotations/$Owner.ts @@ -1,3 +1,5 @@ -import {$Set} from "jest-metadata/annotations"; +import { $Push } from 'jest-metadata'; + +import { OWNER } from '../constants'; export const $Owner = (owner: string) => $Push(OWNER, owner); diff --git a/packages/library/src/annotations/$Severity.ts b/packages/library/src/annotations/$Severity.ts index e142712..5d27fed 100644 --- a/packages/library/src/annotations/$Severity.ts +++ b/packages/library/src/annotations/$Severity.ts @@ -1,3 +1,5 @@ -import {$Set} from "jest-metadata/annotations"; +import { $Set } from 'jest-metadata'; + +import { SEVERITY } from '../constants'; export const $Severity = (severity: string) => $Set(SEVERITY, severity); diff --git a/packages/library/src/annotations/$Story.ts b/packages/library/src/annotations/$Story.ts new file mode 100644 index 0000000..a27f50a --- /dev/null +++ b/packages/library/src/annotations/$Story.ts @@ -0,0 +1,5 @@ +import { $Push } from 'jest-metadata'; + +import { STORY } from '../constants'; + +export const $Story = (name: string) => $Push(STORY, name); diff --git a/packages/library/src/annotations/$Tag.ts b/packages/library/src/annotations/$Tag.ts index dbb924e..df6df90 100644 --- a/packages/library/src/annotations/$Tag.ts +++ b/packages/library/src/annotations/$Tag.ts @@ -1,3 +1,5 @@ -import {$Push} from "jest-metadata/annotations"; +import { $Push } from 'jest-metadata'; + +import { TAG } from '../constants'; export const $Tag = (...tagNames: string[]) => $Push(TAG, ...tagNames); diff --git a/packages/library/src/annotations/index.ts b/packages/library/src/annotations/index.ts index 237c1a6..00af037 100644 --- a/packages/library/src/annotations/index.ts +++ b/packages/library/src/annotations/index.ts @@ -1,2 +1,13 @@ -import { $Push, $Set } from 'jest-metadata/annotations'; - +export * from './$AllureId'; +export * from './$Description'; +export * from './$DescriptionHtml'; +export * from './$Epic'; +export * from './$Feature'; +export * from './$Issue'; +export * from './$Lead'; +export * from './$Link'; +export * from './$Owner'; +export * from './$Severity'; +export * from './$Story'; +export * from './$Tag'; +export * from './$TmsLink'; diff --git a/packages/library/src/constants.ts b/packages/library/src/constants.ts index 6801ac4..9e5e418 100644 --- a/packages/library/src/constants.ts +++ b/packages/library/src/constants.ts @@ -1,11 +1,13 @@ export const PREFIX = 'allure2' as const; + export const ALLURE_ID = [PREFIX, 'allureId'] as const; export const DESCRIPTION = [PREFIX, 'description'] as const; export const DESCRIPTION_HTML = [PREFIX, 'descriptionHtml'] as const; -export const ISSUE = [PREFIX, 'issue'] as const; +export const EPIC = [PREFIX, 'epic'] as const; +export const FEATURE = [PREFIX, 'feature'] as const; export const LEAD = [PREFIX, 'lead'] as const; export const LINK = [PREFIX, 'link'] as const; export const OWNER = [PREFIX, 'owner'] as const; export const SEVERITY = [PREFIX, 'severity'] as const; -export const TMS_LINK = [PREFIX, 'tmsLink'] as const; +export const STORY = [PREFIX, 'story'] as const; export const TAG = [PREFIX, 'tags'] as const; diff --git a/packages/library/src/context/TestFileContext.ts b/packages/library/src/context/TestFileContext.ts deleted file mode 100644 index e0dd454..0000000 --- a/packages/library/src/context/TestFileContext.ts +++ /dev/null @@ -1,133 +0,0 @@ -import type { TestCaseResult, TestResult } from '@jest/reporters'; -import type { AllureGroup } from '@noomorph/allure-js-commons'; -import { LabelName, Stage, Status } from '@noomorph/allure-js-commons'; - -import shallowEqualArrays from '../utils/shallowEqualArrays'; - -import type { TestFileContextConfig } from './TestFileContextConfig'; - -export class TestFileContext { - private _testFileGroup!: AllureGroup; - private _rootSuiteGroup!: AllureGroup; - private _subsuiteGroup!: AllureGroup; - private _ancestorTitles: string[] = []; - - constructor(private readonly _config: TestFileContextConfig) { - this._testFileGroup = this._config.testFileGroup; - this._rootSuiteGroup = this._testFileGroup.startGroup( - 'ROOT_DESCRIBE_BLOCK', - ); - this._subsuiteGroup = this._rootSuiteGroup; - this._ancestorTitles = []; - } - - handleTestCaseResult(testCaseResult: TestCaseResult) { - if ( - !shallowEqualArrays(this._ancestorTitles, testCaseResult.ancestorTitles) - ) { - this._changeSubsuiteGroup(testCaseResult); - } - - const { select } = this._config; - const allureTest = this._subsuiteGroup.startTest( - select.testCase.name(testCaseResult), - select.testCase.start(testCaseResult), - ); - allureTest.fullName = select.testCase.fullName(testCaseResult); - allureTest.description = select.testCase.description(testCaseResult); - allureTest.stage = Stage.FINISHED; - allureTest.status = select.testCase.status(testCaseResult); - - const statusDetails = select.testCase.statusDetails(testCaseResult); - if (statusDetails) { - allureTest.statusDetails = statusDetails; - } - - allureTest.historyId = select.testCase.historyId(testCaseResult); - - const labelsToAdd: [string, string | undefined][] = [ - [LabelName.PACKAGE, select.testCase.labels.package()], - [LabelName.TEST_CLASS, select.testCase.labels.testClass(testCaseResult)], - [ - LabelName.TEST_METHOD, - select.testCase.labels.testMethod(testCaseResult), - ], - [ - LabelName.PARENT_SUITE, - select.testCase.labels.parentSuite(testCaseResult), - ], - [LabelName.SUITE, select.testCase.labels.suite(testCaseResult)], - [LabelName.SUB_SUITE, select.testCase.labels.subsuite(testCaseResult)], - [LabelName.THREAD, select.testCase.labels.thread(testCaseResult)], - ]; - - for (const [labelName, labelValue] of labelsToAdd) { - if (labelValue) { - allureTest.addLabel(labelName, labelValue); - } - } - - allureTest.endTest(select.testCase.end(testCaseResult)); - } - - handleTestFileResult(result: TestResult) { - if (result.testResults.length === 0 && result.testExecError) { - this._handleEarlyError(result); - } else { - this._handleSkippedTests(result); - } - - if (this._subsuiteGroup !== this._rootSuiteGroup) { - this._subsuiteGroup.endGroup(); - } - - this._rootSuiteGroup.endGroup(); - this._testFileGroup.endGroup(); - } - - _changeSubsuiteGroup(testCaseResult: TestCaseResult) { - const rootSuiteGroup = this._rootSuiteGroup; - if (this._subsuiteGroup !== rootSuiteGroup) { - this._subsuiteGroup.endGroup(); - } - - this._ancestorTitles = testCaseResult.ancestorTitles; - this._subsuiteGroup = - testCaseResult.ancestorTitles.length > 0 - ? rootSuiteGroup.startGroup(testCaseResult.ancestorTitles.join(' > ')) - : rootSuiteGroup; - } - - _handleEarlyError(result: TestResult) { - const { select } = this._config; - const allureTest = this._subsuiteGroup.startTest( - '(generic failure)', - select.testFile.start(result), - ); - allureTest.fullName = select.testFile.fullName(result); - allureTest.stage = Stage.FINISHED; - allureTest.status = Status.BROKEN; - const statusDetails = select.testFile.statusDetails(result); - if (statusDetails) { - allureTest.statusDetails = statusDetails; - } - allureTest.description = select.testFile.description(result); - allureTest.historyId = select.testFile.historyId(result); - allureTest.addLabel(LabelName.TAG, 'unhandled-error'); - const packageName = select.testFile.labels.package(); - if (packageName) { - allureTest.addLabel(LabelName.PACKAGE, packageName); - } - allureTest.addLabel(LabelName.SUITE, select.testFile.labels.suite(result)); - allureTest.addLabel(LabelName.THREAD, '1'); - allureTest.endTest(select.testFile.end(result)); - } - - _handleSkippedTests(result: TestResult) { - for (const testResult of result.testResults) { - if (testResult.status !== 'passed' && testResult.status !== 'failed') { - this.handleTestCaseResult(testResult); - } - } - } -} diff --git a/packages/library/src/context/TestFileContextConfig.ts b/packages/library/src/context/TestFileContextConfig.ts deleted file mode 100644 index bcc882e..0000000 --- a/packages/library/src/context/TestFileContextConfig.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { AllureGroup } from '@noomorph/allure-js-commons'; - -import type { Selectors } from '../selectors'; - -export type TestFileContextConfig = { - select: Selectors; - testFileGroup: AllureGroup; -}; diff --git a/packages/library/src/context/TestRunContext.ts b/packages/library/src/context/TestRunContext.ts deleted file mode 100644 index 6704016..0000000 --- a/packages/library/src/context/TestRunContext.ts +++ /dev/null @@ -1,52 +0,0 @@ -import path from 'node:path'; - -import type { Test } from '@jest/reporters'; - -import type { TestRunContextConfig } from './TestRunContextConfig'; -import { TestFileContext } from './TestFileContext'; - -export class TestRunContext { - private readonly _fileContexts = new Map(); - - constructor(private readonly _config: TestRunContextConfig) {} - - getFileContext(testFilePath: string) { - return this._fileContexts.get(testFilePath); - } - - async writeMetadata() { - const runtime = this._config.allureRuntime; - - runtime.writeEnvironmentInfo(await this._getEnvironmentInfo()); - runtime.writeCategoriesDefinitions([]); - } - - registerFileContext(test: Test) { - const testFilePath = path.relative(this._config.rootDir, test.path); - - this._fileContexts.set( - test.path, - new TestFileContext({ - ...this._config, - - testFileGroup: this._config.allureRuntime.startGroup(testFilePath), - }), - ); - } - - private async _getEnvironmentInfo(): Promise { - const { environmentInfo } = this._config; - - if (typeof environmentInfo === 'boolean') { - return environmentInfo ? process.env : {}; - } else if (typeof environmentInfo === 'function') { - return environmentInfo({ - cwd: process.cwd(), - env: process.env, - // TODO: packageName: requireCwd('./package.json').name, - }); - } else { - return process.env; - } - } -} diff --git a/packages/library/src/context/TestRunContextConfig.ts b/packages/library/src/context/TestRunContextConfig.ts deleted file mode 100644 index 0ce2b10..0000000 --- a/packages/library/src/context/TestRunContextConfig.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { AllureRuntime } from '@noomorph/allure-js-commons'; - -import type { Selectors } from '../selectors'; -import type { ReporterOptions } from '../ReporterOptions'; - -export type TestRunContextConfig = { - allureRuntime: AllureRuntime; - - environmentInfo: ReporterOptions['environment']; - select: Selectors; - rootDir: string; -}; diff --git a/packages/library/src/context/index.ts b/packages/library/src/context/index.ts deleted file mode 100644 index 7ac9dfc..0000000 --- a/packages/library/src/context/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { TestFileContext } from './TestFileContext'; -export { TestRunContext } from './TestRunContext'; diff --git a/packages/library/src/environment-node.ts b/packages/library/src/environment-node.ts index 1241ccd..8f42386 100644 --- a/packages/library/src/environment-node.ts +++ b/packages/library/src/environment-node.ts @@ -2,6 +2,10 @@ import { state } from 'jest-metadata'; import { TestEnvironment } from 'jest-metadata/environment-node'; import type { ForwardedCircusEvent } from 'jest-metadata/environment-decorator'; +import { PREFIX } from './constants'; +import type { AllureTestCaseMetadata, AllureTestStepMetadata } from './options'; +import {Stage, Status} from "@noomorph/allure-js-commons"; + export class AllureNodeJestEnvironment extends TestEnvironment { constructor(config: any, context: any) { super(config, context); @@ -12,13 +16,69 @@ export class AllureNodeJestEnvironment extends TestEnvironment { ); this.testEvents - .on('add_hook', this.#attachCode) - .on('add_test', this.#attachCode); + .on('add_hook', this.#addHook.bind(this)) + .on('add_test', this.#addTest.bind(this)) + .on('hook_start', this.#executableStart.bind(this)) + .on('hook_failure', this.#executableFailure.bind(this)) + .on('hook_success', this.#executableSuccess.bind(this)) + .on('test_fn_start', this.#executableStart.bind(this)) + .on('test_fn_success', this.#executableSuccess.bind(this)) + .on('test_fn_failure', this.#executableFailure.bind(this)); + } + + #addHook({event}: ForwardedCircusEvent) { + const metadata = { + name: event.hookType, + start: Date.now(), + code: event.fn.toString(), + } as AllureTestStepMetadata; + + state.currentMetadata.assign(PREFIX, metadata); } - #attachCode = ({ event }: ForwardedCircusEvent) => { - state.currentMetadata.set(['allure2', 'code'], event.fn.toString()); - }; + #addTest({event}: ForwardedCircusEvent) { + const metadata: AllureTestCaseMetadata = { + identifier: state.currentMetadata.id, + stage: Stage.SCHEDULED, + code: event.fn.toString(), + }; + + state.currentMetadata.assign(PREFIX, metadata); + } + + #executableStart({}: ForwardedCircusEvent) { + const metadata: AllureTestStepMetadata = { + start: Date.now(), + stage: Stage.RUNNING, + }; + + state.currentMetadata.assign(PREFIX, metadata); + } + + #executableFailure({event}: ForwardedCircusEvent) { + const metadata: AllureTestStepMetadata = { + stop: Date.now(), + stage: Stage.FINISHED, + status: Status.FAILED, + statusDetails: event.error ? { + message: event.error.message, + trace: event.error.stack, + } : {}, + }; + + state.currentMetadata.assign(PREFIX, metadata); + } + + #executableSuccess({}: ForwardedCircusEvent) { + const metadata: AllureTestStepMetadata = { + stop: Date.now(), + stage: Stage.FINISHED, + status: Status.PASSED, + statusDetails: {}, + }; + + state.currentMetadata.assign(PREFIX, metadata); + } } export default AllureNodeJestEnvironment; diff --git a/packages/library/src/index.ts b/packages/library/src/index.ts index 1bb7558..8423fd3 100644 --- a/packages/library/src/index.ts +++ b/packages/library/src/index.ts @@ -1,3 +1,3 @@ export { JestAllure2Reporter } from './JestAllure2Reporter'; export { JestAllure2Reporter as default } from './JestAllure2Reporter'; -export { ReporterOptions } from './ReporterOptions'; +export { ReporterOptions } from './options/ReporterOptions'; diff --git a/packages/library/src/ReporterOptions.ts b/packages/library/src/options/ReporterOptions.ts similarity index 83% rename from packages/library/src/ReporterOptions.ts rename to packages/library/src/options/ReporterOptions.ts index 6cb85b3..703021c 100644 --- a/packages/library/src/ReporterOptions.ts +++ b/packages/library/src/options/ReporterOptions.ts @@ -177,21 +177,21 @@ type LinkCustomizer = TestCaseExtractor; type LabelsCustomizer = | TestCaseExtractor | Partial<{ - readonly allureId: TestCaseExtractor; - readonly package: TestCaseExtractor; - readonly testClass: TestCaseExtractor; - readonly testMethod: TestCaseExtractor; - readonly parentSuite: TestCaseExtractor; - readonly suite: TestCaseExtractor; - readonly subSuite: TestCaseExtractor; - readonly epic: TestCaseExtractor; - readonly feature: TestCaseExtractor; - readonly story: TestCaseExtractor; - readonly framework: TestCaseExtractor; - readonly language: TestCaseExtractor; - readonly layer: TestCaseExtractor; - readonly thread: TestCaseExtractor; - readonly host: TestCaseExtractor; + readonly allureId: TestCaseExtractor; // TestEntryMetadata → (invocations) + readonly package: TestCaseExtractor; // N/A + readonly testClass: TestCaseExtractor; // N/A + readonly testMethod: TestCaseExtractor; // N/A + readonly parentSuite: TestCaseExtractor; // N/A + readonly suite: TestCaseExtractor; // N/A + readonly subSuite: TestCaseExtractor; // N/A + readonly epic: TestCaseExtractor; // uniq | AggregatedResultMetadata → ... → TestEntryMetadata → (invocations) + readonly feature: TestCaseExtractor; // uniq | AggregatedResultMetadata → ... → TestEntryMetadata → (invocations) + readonly story: TestCaseExtractor; // uniq | AggregatedResultMetadata → ... → TestEntryMetadata → (invocations) + readonly framework: TestCaseExtractor; // last | AggregatedResultMetadata → ... → TestEntryMetadata → (invocations) + readonly language: TestCaseExtractor; // last | AggregatedResultMetadata → ... → TestEntryMetadata → (invocations) + readonly layer: TestCaseExtractor; // last | AggregatedResultMetadata → ... → TestEntryMetadata → (invocations) + readonly thread: TestCaseExtractor; // N/A + readonly host: TestCaseExtractor; // N/A readonly severity: TestCaseExtractor; readonly tag: TestCaseExtractor; readonly owner: TestCaseExtractor; @@ -230,7 +230,8 @@ export interface TestCaseExtractorContext extends GlobalExtractorContext { readonly errors: readonly Error[]; } -export interface TestStepExtractorContext extends TestCaseExtractorContext { +export interface TestStepExtractorContext + extends TestCaseExtractorContext { readonly step?: TestStepContext; } @@ -298,6 +299,8 @@ export interface AllureRunMetadata { export interface AllureTestCaseMetadata extends AllureTestStepMetadata { name?: never; identifier: string; + description?: readonly string[]; + descriptionHtml?: readonly string[]; labels?: readonly Label[]; links?: readonly Link[]; } @@ -305,8 +308,9 @@ export interface AllureTestCaseMetadata extends AllureTestStepMetadata { export interface AllureTestStepMetadata { name?: string; code?: string; - description?: readonly string[]; - descriptionHtml?: readonly string[]; + status?: Status; + statusDetails?: StatusDetails; + stage?: Stage; steps?: readonly AllureTestStepMetadata[]; attachments?: readonly Attachment[]; parameters?: readonly Parameter[]; diff --git a/packages/library/src/options/index.ts b/packages/library/src/options/index.ts index 305dd00..970ee2c 100644 --- a/packages/library/src/options/index.ts +++ b/packages/library/src/options/index.ts @@ -1,4 +1,6 @@ -import type { ReporterOptions } from '../ReporterOptions'; +import type { ReporterOptions } from './ReporterOptions'; + +export * from './ReporterOptions'; export function resolveOptions( options: Partial | undefined, diff --git a/packages/library/src/selectors/fallbacks/MetadataService.ts b/packages/library/src/selectors/fallbacks/MetadataService.ts deleted file mode 100644 index edfb1e8..0000000 --- a/packages/library/src/selectors/fallbacks/MetadataService.ts +++ /dev/null @@ -1,68 +0,0 @@ -import type { TestCaseResult } from '@jest/reporters'; -import { query } from 'jest-metadata/reporter'; - -const NS = 'allure2'; - -export class MetadataService { - getWorkerId(testCaseResult: TestCaseResult): string | undefined { - const testMetadata = query.testCaseResult(testCaseResult); - const runMetadata = testMetadata?.describeBlock.run; - const $run = runMetadata?.get(NS) as AllureRunMetadata | undefined; - return $run?.workerId; - } - - getCode(testCaseResult: TestCaseResult): MetadataService$GetCode { - const testMetadata = query.testCaseResult(testCaseResult); - const $entry = testMetadata?.get(NS) as AllureTestEntryMetadata | undefined; - const $before = - testMetadata?.lastInvocation?.before - .map((hook) => hook.definition.get(NS) as AllureHookDefinitionMetadata) - .map((data) => data?.code ?? 'Code is not available for preview') ?? []; - - const $after = - testMetadata?.lastInvocation?.after - .map((hook) => hook.definition.get(NS) as AllureHookDefinitionMetadata) - .map((data) => data?.code ?? 'Code is not available for preview') ?? []; - - return { - beforeHooks: ($before ?? []).map((code) => ({ code })), - testFn: { code: $entry?.code ?? 'Code is not available for preview' }, - afterHooks: ($after ?? []).map((code) => ({ code })), - }; - } -} - -export type MetadataService$GetCode = { - beforeHooks: AllureCodeMetadata[]; - testFn: AllureCodeMetadata; - afterHooks: AllureCodeMetadata[]; -}; - -export type AllureMetadata = - | AllureRunMetadata - | AllureTestEntryMetadata - | AllureHookDefinitionMetadata - | AllureCodeMetadata - | AllureAttachmentMetadata; - -export type AllureRunMetadata = { - workerId: string; -}; - -export type AllureCodeMetadata = { - code: string; -}; - -export type AllureTestEntryMetadata = { - code: string; -}; - -export type AllureHookDefinitionMetadata = { - code: string; -}; - -export type AllureAttachmentMetadata = { - filePath: string; - fileName?: string; - mimeType?: string; -}; diff --git a/packages/library/src/selectors/fallbacks/ProjectService.ts b/packages/library/src/selectors/fallbacks/ProjectService.ts deleted file mode 100644 index f8ac258..0000000 --- a/packages/library/src/selectors/fallbacks/ProjectService.ts +++ /dev/null @@ -1,41 +0,0 @@ -import path from 'node:path'; - -import pkgUp from 'pkg-up'; - -import attempt from '../../utils/attempt'; -import isError from '../../utils/isError'; - -type Config = { - rootDir: string; - packageName?: string; -}; - -export class ProjectService { - public readonly packageName: string | undefined; - - public readonly rootDir: string; - - constructor({ packageName, rootDir }: Config) { - this.rootDir = rootDir; - - if (packageName === undefined) { - const closestJson = pkgUp.sync({ cwd: rootDir }); - const packageJson = closestJson - ? attempt(() => require(closestJson)) - : undefined; - if (isError(packageJson)) { - console.warn( - `jest-allure2-reporter: Failed to load package.json from: ${closestJson}`, - ); - } else { - this.packageName = packageJson?.name; - } - } else { - this.packageName = packageName; - } - } - - public relative(testPath: string) { - return path.relative(this.rootDir, testPath); - } -} diff --git a/packages/library/src/selectors/fallbacks/QueryService.ts b/packages/library/src/selectors/fallbacks/QueryService.ts deleted file mode 100644 index 2d4b841..0000000 --- a/packages/library/src/selectors/fallbacks/QueryService.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { Test, TestCaseResult } from '@jest/reporters'; - -import type { ReporterEmitter } from '../../ReporterEmitter'; - -export class QueryService { - private _testMap = new Map(); - - constructor(emitter: ReporterEmitter) { - emitter.on('testCaseResult', (event) => - this._saveTestFilePath(event.test, event.testCaseResult), - ); - emitter.on('testFileResult', (event) => { - for (const testCaseResult of event.testResult.testResults) { - this._saveTestFilePath(event.test, testCaseResult); - } - }); - } - - public getTest(testCaseResult: TestCaseResult): Test { - return this._testMap.get(testCaseResult)!; - } - - private _saveTestFilePath(test: Test, testCaseResult: TestCaseResult) { - this._testMap.set(testCaseResult, test); - } -} diff --git a/packages/library/src/selectors/fallbacks/TimeService.ts b/packages/library/src/selectors/fallbacks/TimeService.ts deleted file mode 100644 index c4be49c..0000000 --- a/packages/library/src/selectors/fallbacks/TimeService.ts +++ /dev/null @@ -1,56 +0,0 @@ -import type { Test, TestCaseResult, TestResult } from '@jest/reporters'; - -import type { ReporterEmitter } from '../../ReporterEmitter'; - -export class TimeService { - private _testFileStarts = new Map(); - private _testFileEnds = new Map(); - private _testCaseTimes = new Map(); - - constructor( - private readonly _emitter: ReporterEmitter, - private readonly _nowProvider = () => Date.now(), - ) { - this._emitter.on('testFileStart', (event) => - this._onTestFileStart(event.test), - ); - this._emitter.on('testFileResult', (event) => - this._onTestFileResult(event.test), - ); - this._emitter.on('testCaseResult', (event) => - this._onTestCaseResult(event.testCaseResult), - ); - } - - getCaseStartTime(testCaseResult: TestCaseResult): number { - return this._testCaseTimes.get(testCaseResult)?.[0] ?? Number.NaN; - } - - getCaseEndTime(testCaseResult: TestCaseResult): number { - return this._testCaseTimes.get(testCaseResult)?.[1] ?? Number.NaN; - } - - getFileStartTime(testResult: TestResult): number { - return this._testFileStarts.get(testResult.testFilePath) ?? Number.NaN; - } - - getFileEndTime(testResult: TestResult): number { - return this._testFileEnds.get(testResult.testFilePath) ?? Number.NaN; - } - - private _onTestFileStart(test: Test): void { - const now = this._nowProvider(); - this._testFileStarts.set(test.path, now); - } - - private _onTestFileResult(test: Test): void { - const now = this._nowProvider(); - this._testFileEnds.set(test.path, now); - } - - private _onTestCaseResult(testCaseResult: TestCaseResult): void { - const now = this._nowProvider(); - const duration = testCaseResult.duration ?? 0; - this._testCaseTimes.set(testCaseResult, [now - duration, now]); - } -} diff --git a/packages/library/src/selectors/fallbacks/index.ts b/packages/library/src/selectors/fallbacks/index.ts deleted file mode 100644 index be14c27..0000000 --- a/packages/library/src/selectors/fallbacks/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { MetadataService } from './MetadataService'; -export { ProjectService } from './ProjectService'; -export { QueryService } from './QueryService'; -export { ThreadService } from './ThreadService'; -export { TimeService } from './TimeService'; diff --git a/packages/library/src/selectors/index.ts b/packages/library/src/selectors/index.ts deleted file mode 100644 index a4ff4ed..0000000 --- a/packages/library/src/selectors/index.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { ReporterOptions } from '../ReporterOptions'; -import type { ReporterEmitter } from '../ReporterEmitter'; - -import { - MetadataService, - ProjectService, - QueryService, - ThreadService, - TimeService, -} from './fallbacks'; -import { TestCaseSelectors } from './testCase'; -import { TestFileSelectors } from './testFile'; - -type SelectorsConfig = { - emitter: ReporterEmitter; - reporterOptions: Partial; - rootDir: string; -}; - -export class Selectors { - public readonly testCase: TestCaseSelectors; - public readonly testFile: TestFileSelectors; - - constructor(selectorsConfig: SelectorsConfig) { - const emitter = selectorsConfig.emitter; - const metadataService = new MetadataService(); - const queryService = new QueryService(emitter); - const timeService = new TimeService(emitter); - const threadService = new ThreadService(emitter); - const projectService = new ProjectService({ - rootDir: selectorsConfig.rootDir, - packageName: selectorsConfig.reporterOptions.testCase?.labels - ?.package as string, // TODO: rewrite label system - }); - - this.testCase = new TestCaseSelectors({ - reporterOptions: selectorsConfig.reporterOptions, - meta: metadataService, - query: queryService, - project: projectService, - thread: threadService, - time: timeService, - }); - - this.testFile = new TestFileSelectors({ - project: projectService, - time: timeService, - }); - } -} diff --git a/packages/library/src/selectors/testCase.ts b/packages/library/src/selectors/testCase.ts deleted file mode 100644 index 90d33d3..0000000 --- a/packages/library/src/selectors/testCase.ts +++ /dev/null @@ -1,154 +0,0 @@ -import path from 'node:path'; - -import { Status } from '@noomorph/allure-js-commons'; -import stripAnsi from 'strip-ansi'; -import type { TestCaseResult } from '@jest/reporters'; - -import type { ReporterOptions } from '../ReporterOptions'; -import isEmptyObject from '../utils/isEmptyObject'; -import md5 from '../utils/md5'; - -import type { - MetadataService, - QueryService, - ProjectService, - ThreadService, - TimeService, -} from './fallbacks'; - -type Services = { - reporterOptions: Partial; - meta: MetadataService; - project: ProjectService; - query: QueryService; - thread: ThreadService; - time: TimeService; -}; - -export class TestCaseSelectors { - public readonly labels = { - parentSuite: (_testCaseResult: TestCaseResult): string => { - return undefined as unknown as string; - }, - - suite: (testCaseResult: TestCaseResult): string => { - const test = this._services.query.getTest(testCaseResult); - return this._services.project.relative(test.path); - }, - - subsuite: (testCaseResult: TestCaseResult): string => { - return testCaseResult.ancestorTitles.join(' » '); - }, - - package: (): string | undefined => { - return this._services.project.packageName; - }, - - testClass: (testCaseResult: TestCaseResult): string => { - const test = this._services.query.getTest(testCaseResult); - return this._services.project.relative(test.path); - }, - - testMethod: (testCaseResult: TestCaseResult): string => { - return testCaseResult.fullName; - }, - - thread: (testCaseResult: TestCaseResult): string => { - const workerId = this._services.meta.getWorkerId(testCaseResult); - if (workerId == null) { - const test = this._services.query.getTest(testCaseResult); - return `${1 + this._services.thread.getThreadId(test)}`; - } else { - return workerId; - } - }, - }; - - constructor(private readonly _services: Services) {} - - public name(testCaseResult: TestCaseResult) { - return testCaseResult.title; - } - - public start(testCaseResult: TestCaseResult) { - return this._services.time.getCaseStartTime(testCaseResult); - } - - public end(testCaseResult: TestCaseResult) { - return this._services.time.getCaseEndTime(testCaseResult); - } - - public fullName(testCaseResult: TestCaseResult) { - return testCaseResult.fullName; - } - - public description(testCaseResult: TestCaseResult) { - const metadata = this._services.meta.getCode(testCaseResult); - - const toCodeBlock = (code: string) => '```javascript\n' + code + '\n```'; - const h2 = (text: string) => `## ${text}\n`; - const codeBlocks = [ - ...metadata.beforeHooks.map((hook) => - [h2('beforeEach'), toCodeBlock(hook.code)].join('\n'), - ), - [h2('test'), toCodeBlock(metadata.testFn.code)].join('\n'), - ...metadata.afterHooks.map((hook) => - [h2('afterEach'), toCodeBlock(hook.code)].join('\n'), - ), - ]; - - return codeBlocks.join('\n\n'); - } - - public relativePath(testCaseResult: TestCaseResult) { - const testFilePath = this._services.query.getTest(testCaseResult).path; - return this._services.project.relative(testFilePath); - } - - public historyId(testCaseResult: TestCaseResult) { - return md5([ - ...this.relativePath(testCaseResult).split(path.sep), - ...testCaseResult.ancestorTitles, - testCaseResult.title, - ]); - } - - public status(testCaseResult: TestCaseResult) { - switch (testCaseResult.status) { - case 'passed': { - return Status.PASSED; - } - case 'failed': { - if (this._services.reporterOptions.errorsAsFailedAssertions) { - return Status.FAILED; - } else { - const hasUnhandledErrors = - // eslint-disable-next-line unicorn/no-array-callback-reference - testCaseResult.failureDetails.some(isEmptyObject); - return hasUnhandledErrors ? Status.BROKEN : Status.FAILED; - } - } - // case 'pending': - // case 'todo': - // case 'skipped': - // case 'disabled': - default: { - return Status.SKIPPED; - } - } - } - - public statusDetails(testCaseResult: TestCaseResult) { - if (testCaseResult.status !== 'failed') { - return; - } - - const [message] = testCaseResult.failureMessages[0].split('\n', 1); - const trace = testCaseResult.failureMessages.join('\n\n'); - - return { - message: stripAnsi(message), - trace: stripAnsi(trace), - }; - } -} diff --git a/packages/library/src/selectors/testFile.ts b/packages/library/src/selectors/testFile.ts deleted file mode 100644 index 3f2a3a1..0000000 --- a/packages/library/src/selectors/testFile.ts +++ /dev/null @@ -1,73 +0,0 @@ -import fs from 'node:fs'; -import path from 'node:path'; - -import stripAnsi from 'strip-ansi'; -import type { TestResult } from '@jest/reporters'; - -import md5 from '../utils/md5'; - -import type { ProjectService, TimeService } from './fallbacks'; - -type Services = { - project: ProjectService; - time: TimeService; -}; - -export class TestFileSelectors { - public readonly labels = { - suite: (testResult: TestResult): string => { - return this._services.project.relative(testResult.testFilePath); - }, - - package: (): string | undefined => { - return this._services.project.packageName; - }, - }; - - constructor(private readonly _services: Services) {} - - public start(testResult: TestResult) { - return ( - testResult.perfStats.end ?? - this._services.time.getFileStartTime(testResult) - ); - } - - public end(testResult: TestResult) { - return ( - testResult.perfStats.end ?? this._services.time.getFileEndTime(testResult) - ); - } - - public description(testResult: TestResult) { - const fileContents = fs.readFileSync(testResult.testFilePath, 'utf8'); - return '```typescript\n' + fileContents + '\n```'; - } - - public fullName(testResult: TestResult) { - return this._services.project.relative(testResult.testFilePath); - } - - public historyId(testResult: TestResult) { - const relativePath = this._services.project.relative( - testResult.testFilePath, - ); - return md5([...relativePath.split(path.sep), '']); - } - - public statusDetails(testResult: TestResult) { - if (!testResult.testExecError) { - return; - } - - return { - message: - testResult.testExecError.message || - testResult.testExecError.stack || - `${testResult.testExecError}`, - trace: testResult.failureMessage - ? stripAnsi(testResult.failureMessage) - : undefined, - }; - } -} diff --git a/packages/library/src/selectors/fallbacks/ThreadService.ts b/packages/library/src/utils/ThreadService.ts similarity index 73% rename from packages/library/src/selectors/fallbacks/ThreadService.ts rename to packages/library/src/utils/ThreadService.ts index 831f472..b29a0db 100644 --- a/packages/library/src/selectors/fallbacks/ThreadService.ts +++ b/packages/library/src/utils/ThreadService.ts @@ -1,20 +1,11 @@ import type { Test } from '@jest/reporters'; -import type { ReporterEmitter } from '../../ReporterEmitter'; - const FREE_SLOT = undefined; export class ThreadService { private readonly _activeThreads: (string | undefined)[] = []; private readonly _threadMap = new Map(); - constructor(private readonly _emitter: ReporterEmitter) { - this._emitter.on('testFileStart', (event) => - this.allocateThread(event.test), - ); - this._emitter.on('testFileResult', (event) => this.freeThread(event.test)); - } - allocateThread(test: Test): void { const freeIndex = this._activeThreads.indexOf(FREE_SLOT); if (freeIndex === -1) { diff --git a/packages/library/src/utils/squash.ts b/packages/library/src/utils/squash.ts new file mode 100644 index 0000000..2ffdfde --- /dev/null +++ b/packages/library/src/utils/squash.ts @@ -0,0 +1,5 @@ +import type { TestEntryMetadata } from 'jest-metadata'; + +export function squash(test: TestEntryMetadata): unknown[] { + return [test]; +}