Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
noomorph committed Sep 22, 2023
1 parent f4df64f commit 87f38f0
Show file tree
Hide file tree
Showing 22 changed files with 385 additions and 103 deletions.
2 changes: 1 addition & 1 deletion packages/library/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export const PREFIX = 'allure2' as const;

export const $POINTER = [PREFIX, '$pointer'] as const;
export const CURRENT_STEP = [PREFIX, 'currentStep'] as const;
export const DESCRIPTION = [PREFIX, 'description'] as const;
export const DESCRIPTION_HTML = [PREFIX, 'descriptionHtml'] as const;
export const LABELS = [PREFIX, 'labels'] as const;
Expand Down
4 changes: 2 additions & 2 deletions packages/library/src/environment/decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Stage, Status } from '@noomorph/allure-js-commons';
import type {
AllureTestCaseMetadata,
AllureTestStepMetadata,
} from '../runtime';
} from '../metadata';
import { PREFIX } from '../constants';
import { AllureRuntime } from '../runtime';

Expand Down Expand Up @@ -67,7 +67,7 @@ export function WithAllure2<E extends WithEmitter>(
}: ForwardedCircusEvent<Circus.Event & { name: 'add_test' }>) {
const metadata: AllureTestCaseMetadata = {
stage: Stage.SCHEDULED,
code: event.fn.toString(),
code: [event.fn.toString()],
};

state.currentMetadata.assign(PREFIX, metadata);
Expand Down
102 changes: 102 additions & 0 deletions packages/library/src/metadata/MetadataSquasher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import type {
AggregatedResultMetadata,
DescribeBlockMetadata,
RunMetadata,
TestEntryMetadata,
TestFnInvocationMetadata,
TestInvocationMetadata,
} from 'jest-metadata';

import { PREFIX } from '../constants';

import { chain, extractCode, getStart, getStop } from './utils';
import type { AllureTestCaseMetadata } from './metadata';

export class MetadataSquasher {
protected readonly testInvocationConfig: MetadataSquasherConfig<AllureTestCaseMetadata>;

constructor(flat: boolean) {
this.testInvocationConfig = flat
? MetadataSquasher.flatConfig()
: MetadataSquasher.deepConfig();
}

testInvocation(metadata: TestInvocationMetadata): AllureTestCaseMetadata {
const config = this.testInvocationConfig as any;
const keys = Object.keys(config) as (keyof AllureTestCaseMetadata)[];
const result: Partial<AllureTestCaseMetadata> = {};
const context = {
aggregatedResult: metadata.entry.describeBlock.run.aggregatedResult,
run: metadata.entry.describeBlock.run,
describeBlock: [...metadata.entry.ancestors()],
testEntry: metadata.entry,
testInvocation: metadata,
testFnInvocation: metadata.fn,
};

for (const key of keys) {
result[key] = config[key](context, key);
}

return result as AllureTestCaseMetadata;
}

private static flatConfig(): MetadataSquasherConfig<AllureTestCaseMetadata> {
return {
code: extractCode,
workerId: ({ run }) => run?.get([PREFIX, 'workerId']) as string,
description: chain(['testEntry', 'testInvocation', 'testFnInvocation']),
descriptionHtml: chain([
'testEntry',
'testInvocation',
'testFnInvocation',
]),
attachments: chain(['testEntry', 'testInvocation', 'testFnInvocation']),
parameters: chain(['testEntry', 'testInvocation', 'testFnInvocation']),
labels: chain([
'aggregatedResult',
'run',
'describeBlock',
'testEntry',
'testInvocation',
'testFnInvocation',
]),
links: chain([
'aggregatedResult',
'run',
'describeBlock',
'testEntry',
'testInvocation',
'testFnInvocation',
]),
start: getStart,
stop: getStop,
};
}

private static deepConfig(): MetadataSquasherConfig<AllureTestCaseMetadata> {
return {
...this.flatConfig(),
attachments: chain(['testEntry', 'testInvocation']),
parameters: chain(['testEntry', 'testInvocation']),
};
}
}

export type MetadataSquasherConfig<T extends object> = {
[K in keyof T]: MetadataSquasherMapping<T, K>;
};

export type MetadataSquasherMapping<T, K extends keyof T = keyof T> = (
context: MetadataSquasherContext,
key: K,
) => T[K];

export type MetadataSquasherContext = Partial<{
aggregatedResult: AggregatedResultMetadata;
run: RunMetadata;
describeBlock: DescribeBlockMetadata[];
testEntry: TestEntryMetadata;
testInvocation: TestInvocationMetadata;
testFnInvocation: TestFnInvocationMetadata;
}>;
44 changes: 44 additions & 0 deletions packages/library/src/metadata/StepExtractor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type {
HookInvocationMetadata,
TestFnInvocationMetadata,
} from 'jest-metadata';

import { PREFIX } from '../constants';

import type { AllureTestStepMetadata } from './metadata';

export class StepExtractor {
constructor(protected readonly flat: boolean) {}

public extractFromHook(
metadata: HookInvocationMetadata,
): AllureTestStepMetadata {
const data = {
name: metadata.definition.hookType,
...(metadata.get([PREFIX]) as AllureTestStepMetadata),
};

if (this.flat) {
delete data.attachments;
delete data.parameters;
}

return data;
}

public extractFromTestFn(
metadata: TestFnInvocationMetadata,
): AllureTestStepMetadata {
const data = {
name: 'test',
...(metadata.get([PREFIX]) as AllureTestStepMetadata),
};

if (this.flat) {
delete data.attachments;
delete data.parameters;
}

return data;
}
}
3 changes: 3 additions & 0 deletions packages/library/src/metadata/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './metadata';
export * from './MetadataSquasher';
export * from './StepExtractor';
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export interface AllureTestStepMetadata {
steps?: AllureTestStepMetadata[];

name?: string;
code?: string;
status?: Status;
statusDetails?: StatusDetails;
stage?: Stage;
Expand All @@ -28,14 +27,17 @@ export interface AllureTestCaseMetadata extends AllureTestStepMetadata {
* @example ['steps', '0', 'steps', '0']
* @internal
*/
$pointer?: string[];
currentStep?: string[];
/**
* Jest worker ID.
* @internal Used to generate unique thread names.
* @see {LabelName.THREAD}
* @see {import('@noomorph/allure-js-commons').LabelName.THREAD}
*/
$workerId?: string;

workerId?: string;
/**
* Source code of the test case, glued from all hooks and test function itself.
*/
code?: string[];
/**
* Only steps can have names.
*/
Expand Down
22 changes: 22 additions & 0 deletions packages/library/src/metadata/utils/chain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { Metadata } from 'jest-metadata';

import { PREFIX } from '../../constants';
import type {
MetadataSquasherContext,
MetadataSquasherMapping,
} from '../MetadataSquasher';

export function chain<T extends object, K extends keyof T>(
sources: (keyof MetadataSquasherContext)[],
): MetadataSquasherMapping<T, K> {
return (context: MetadataSquasherContext, key: K) => {
const path = [PREFIX, key as string];
const metadatas: Metadata[] = sources.flatMap((sourceName) => {
const value: Metadata | Metadata[] | undefined = context[sourceName];
if (!value) return [];
return Array.isArray(value) ? value : [value];
});

return metadatas.flatMap((metadata) => metadata.get(path, [])) as T[K];
};
}
29 changes: 29 additions & 0 deletions packages/library/src/metadata/utils/extractCode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { HookInvocationMetadata } from 'jest-metadata';
import type { Metadata } from 'jest-metadata';

import type { MetadataSquasherMapping } from '../MetadataSquasher';
import type { AllureTestCaseMetadata } from '../metadata';

export const extractCode: MetadataSquasherMapping<
AllureTestCaseMetadata,
'code'
> = ({ testInvocation }) => {
if (!testInvocation) return [];

const getHookDefinition = (metadata: HookInvocationMetadata) =>
metadata.definition;
const getCode = (functionName: string) => (metadata: Metadata) => {
const code = metadata.get(['allure2', 'code']);
return code ? `${functionName}(${code})` : '';
};

return [
...testInvocation.beforeAll
.map(getHookDefinition)
.map(getCode('beforeAll')),
...testInvocation.before.map(getHookDefinition).map(getCode('beforeEach')),
getCode('test')(testInvocation.entry),
...testInvocation.after.map(getHookDefinition).map(getCode('afterEach')),
...testInvocation.afterAll.map(getHookDefinition).map(getCode('afterAll')),
].filter(Boolean);
};
16 changes: 16 additions & 0 deletions packages/library/src/metadata/utils/getStart.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { MetadataSquasherMapping } from '../MetadataSquasher';
import type { AllureTestCaseMetadata } from '../metadata';

export const getStart: MetadataSquasherMapping<
AllureTestCaseMetadata,
'start'
> = ({ testEntry, testInvocation }) => {
const first =
(testInvocation &&
(testInvocation.beforeAll[0] ??
testInvocation.before[0] ??
testInvocation.fn)) ??
testEntry;

return (first?.get(['allure2', 'start']) as number) ?? Number.NaN;
};
18 changes: 18 additions & 0 deletions packages/library/src/metadata/utils/getStop.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { MetadataSquasherMapping } from '../MetadataSquasher';
import type { AllureTestCaseMetadata } from '../metadata';
import { PREFIX } from '../../constants';

export const getStop: MetadataSquasherMapping<
AllureTestCaseMetadata,
'stop'
> = ({ testEntry, testInvocation }) => {
const last =
(testInvocation &&
(testInvocation.afterAll.at(-1) ??
testInvocation.after.at(-1) ??
testInvocation.fn)) ??
testEntry;

const stop: number = (last?.get([PREFIX, 'stop']) as number);
return stop;
};
4 changes: 4 additions & 0 deletions packages/library/src/metadata/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './chain';
export * from './extractCode';
export * from './getStart';
export * from './getStop';
28 changes: 17 additions & 11 deletions packages/library/src/options/ReporterOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import type { Config } from '@jest/reporters';
import type {
AllureTestCaseMetadata,
AllureTestStepMetadata,
} from '../runtime';
} from '../metadata';

/**
* Configuration options for the `jest-allure2-reporter` package.
Expand Down Expand Up @@ -61,9 +61,13 @@ export type ReporterOptions = {
*/
resultsDir?: string;
/**
* Configures globally how test cases are reported: names, descriptions, labels, status, etc.
* Customize how test cases are reported: names, descriptions, labels, status, etc.
*/
testCase?: Partial<TestCaseCustomizer>;
/**
* Customize how individual test steps are reported.
*/
testStep?: Partial<TestStepCustomizer>;
/**
* Configures the environment information that will be reported.
*/
Expand All @@ -86,6 +90,7 @@ export type ReporterOptions = {

export type ReporterConfig = Required<ReporterOptions> & {
testCase: ResolvedTestCaseCustomizer;
testStep: ResolvedTestStepCustomizer;
};

/**
Expand Down Expand Up @@ -119,20 +124,20 @@ export interface TestCaseCustomizer {
* @example ({ testCaseMetadata }) => '<pre><code>' + testCaseMetadata.code + '</code></pre>'
*/
descriptionHtml: TestCaseExtractor<string>;
/**
* Extractor for the test case stage.
*/
stage: TestCaseExtractor<Stage>;
/**
* Extractor for the test case status.
* @see https://wix-incubator.github.io/jest-allure2-reporter/docs/config/statuses/
* @example ({ value }) => value === 'broken' ? 'failed' : value
*/
status: TestCaseExtractor<Status[keyof Status]>;
status: TestCaseExtractor<Status>;
/**
* Extractor for the test case status details.
*/
statusDetails: TestCaseExtractor<StatusDetails>;
/**
* Customize step details for the test case.
*/
steps: Partial<TestStepCustomizer>;
/**
* Customize Allure labels for the test case.
*
Expand Down Expand Up @@ -167,21 +172,22 @@ export interface TestCaseCustomizer {
}

export type ResolvedTestCaseCustomizer = Required<TestCaseCustomizer> & {
steps: Required<TestStepCustomizer>;
labels: TestCaseExtractor<Label[]>;
links: TestCaseExtractor<Link[]>;
};

export type ResolvedTestStepCustomizer = Required<TestStepCustomizer>;

export interface TestStepCustomizer {
/**
* Extractor for the step name.
* @example ({ testStep }) => ['beforeEach', 'afterEach'].includes(testStep.name) ? testStep.name.replace('Each', ' each') : testStep.name
* @example ({ value }) => value.replace(/(before|after)(Each|All)/, (_, p1, p2) => p1 + ' ' + p2.toLowerCase())
*/
name: TestStepExtractor<string>;
/**
* Extractor for the test step stage.
* @see https://wix-incubator.github.io/jest-allure2-reporter/docs/config/statuses/
* @example ({ value }) => value === 'broken' ? 'failed' : value
* TODO: add example
*/
stage: TestStepExtractor<Stage>;
/**
Expand Down Expand Up @@ -280,6 +286,6 @@ export interface TestStepExtractorContext<T>

// TODO: improve typings (less never patches, please)
export type AllureTestStepContext = AllureTestStepMetadata & {
$pointer: readonly string[];
currentStep: readonly string[];
steps: never;
};
Loading

0 comments on commit 87f38f0

Please sign in to comment.