Skip to content

Commit 84f1c0c

Browse files
committed
FI-1727 feat: support JSX rendering for HTML report
1 parent 16601a8 commit 84f1c0c

16 files changed

Lines changed: 205 additions & 68 deletions

src/types/global.d.ts

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,78 @@
1+
// eslint-disable-next-line import/no-unused-modules
2+
import type {SafeHtml} from './html';
3+
14
/**
2-
* Extends node's require function.
5+
* Extends global namespaces.
36
* @internal
47
*/
5-
// eslint-disable-next-line import/no-unused-modules
68
declare global {
9+
/**
10+
* JSX namespace for checking types of JSX elements.
11+
*/
12+
namespace JSX {
13+
/**
14+
* Child element (value of `children` property).
15+
*/
16+
type Child = Element | number | string | readonly Child[];
17+
18+
/**
19+
* JSX functional component.
20+
* @internal
21+
*/
22+
type Component<Props extends Properties = Properties> = (
23+
this: void,
24+
properties?: Props,
25+
) => Element;
26+
27+
/**
28+
* Creates JSX elements.
29+
*/
30+
type CreateElement = (
31+
this: void,
32+
type: ElementType,
33+
properties: object | null,
34+
...children: readonly Child[]
35+
) => Element;
36+
37+
type Element = SafeHtml;
38+
39+
type ElementChildrenAttribute = {children: {}};
40+
41+
type ElementType = Component | HtmlTag;
42+
43+
/**
44+
* Creates fragment (`<>...</>`).
45+
*/
46+
type Fragment = Component;
47+
48+
/**
49+
* HTML tag name, e.g. `'a'`.
50+
*/
51+
type HtmlTag = keyof HTMLElementTagNameMap;
52+
53+
type IntrinsicElements = {
54+
[Tag in HtmlTag]: Partial<
55+
Omit<HTMLElementTagNameMap[Tag], 'children' | 'className'> & {
56+
children?: Child;
57+
class?: string;
58+
}
59+
>;
60+
};
61+
62+
/**
63+
* JSX properties.
64+
*/
65+
type Properties = Readonly<{children?: Child}>;
66+
67+
/**
68+
* JSX runtime (functions `createElement` and `Fragment`).
69+
*/
70+
type Runtime = Readonly<{Fragment: Fragment; createElement: CreateElement}>;
71+
}
72+
73+
/**
74+
* Extends nodejs's `require` function.
75+
*/
776
namespace NodeJS {
877
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
978
interface Require {
@@ -12,5 +81,3 @@ declare global {
1281
}
1382
}
1483
}
15-
16-
export type {};

src/types/report.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import type {CreateLocatorOptions, LocatorFunction} from 'create-locator';
2+
13
import type {EndE2edReason, ExitCode, TestRunStatus} from '../constants/internal';
24

35
import type {ApiStatistics} from './apiStatistics';
46
import type {FullPackConfig} from './config';
57
import type {UtcTimeInMs} from './date';
8+
import type {SafeHtml} from './html';
69
import type {TestFilePath} from './paths';
710
import type {StartInfo} from './startInfo';
811
import type {FullTestRun, LiteTestRun, RunHash, RunId} from './testRun';
@@ -89,9 +92,13 @@ export type ReportClientData = Readonly<{
8992
*/
9093
export type ReportClientState = {
9194
clickListeners?: Record<string, (event: HTMLElement) => void>;
95+
readonly createLocatorOptions: CreateLocatorOptions;
96+
readonly e2edRightColumnContainer: HTMLElement;
9297
readonly fullTestRuns: readonly FullTestRun[];
9398
readonly internalDirectoryName: string;
99+
readonly jsxRuntime: JSX.Runtime;
94100
lengthOfReadedJsonReportDataParts: number;
101+
readonly locator: LocatorFunction<SafeHtml>;
95102
readonly pathToScreenshotsDirectoryForReport: string | null;
96103
readonly readJsonReportDataObservers: MutationObserver[];
97104
reportClientData?: ReportClientData;

src/utils/apiStatistics/getUrlTemplate.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,10 @@ export const getUrlTemplate = (url: Url): Return => {
2727
const extension = extname(pathname).slice(1);
2828

2929
if (
30-
extension.length >= minExtensionLength &&
31-
extension.length <= maxExtensionLength &&
32-
/^[a-z]+$/.test(extension)
30+
(extension.length >= minExtensionLength &&
31+
extension.length <= maxExtensionLength &&
32+
/^[a-z]+$/.test(extension)) ||
33+
extension === 'woff2'
3334
) {
3435
return {hasExtension: true, urlTemplate: `${origin}${pathname}` as Url};
3536
}

src/utils/report/client/assertValueIsDefined.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* This base client function should not use scope variables (except other base functions).
44
* @internal
55
*/
6-
export function assertValueIsDefined<T>(value: T): asserts value is Exclude<T, undefined> {
6+
export function assertValueIsDefined<Type>(value: Type): asserts value is Exclude<Type, undefined> {
77
if (value === undefined) {
88
throw new TypeError('Asserted value is not defined');
99
}

src/utils/report/client/chooseTestRun.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ const assertValueIsDefined: typeof clientAssertValueIsDefined = clientAssertValu
1515
const renderApiStatistics = clientRenderApiStatistics;
1616
const renderTestRunDetails = clientRenderTestRunDetails;
1717

18-
declare const e2edRightColumnContainer: HTMLElement;
1918
declare const reportClientState: ReportClientState;
2019

2120
/**
@@ -25,6 +24,7 @@ declare const reportClientState: ReportClientState;
2524
*/
2625
// eslint-disable-next-line max-statements
2726
export function chooseTestRun(runHash: RunHash): void {
27+
const {e2edRightColumnContainer} = reportClientState;
2828
const previousHash = window.location.hash as RunHash;
2929

3030
window.location.hash = runHash;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* Creates JSX runtime (functions `createElement` and `Fragment`).
3+
* This client function should not use scope variables (except global functions).
4+
* @internal
5+
*/
6+
export function createJsxRuntime(): JSX.Runtime {
7+
const createElement: JSX.CreateElement = (type, properties, ...children) => '';
8+
const Fragment: JSX.Fragment = ({children}) => '';
9+
10+
return {createElement, Fragment};
11+
}

src/utils/report/client/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export {clickOnStep} from './clickOnStep';
1313
/** @internal */
1414
export {clickOnTestRun} from './clickOnTestRun';
1515
/** @internal */
16+
export {createJsxRuntime} from './createJsxRuntime';
17+
/** @internal */
1618
export {onDomContentLoad} from './onDomContentLoad';
1719
/** @internal */
1820
export {onFirstJsonReportDataLoad} from './onFirstJsonReportDataLoad';

src/utils/report/client/initialScript.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,30 @@
11
import {
2-
type CreateLocatorOptions,
32
createSimpleLocator as clientCreateSimpleLocator,
43
type LocatorFunction,
54
} from 'create-locator';
65

76
import {addDomContentLoadedHandler as clientAddDomContentLoadedHandler} from './addDomContentLoadedHandler';
87
import {addOnClickOnClass as clientAddOnClickOnClass} from './addOnClickOnClass';
8+
import {assertValueIsDefined as clientAssertValueIsDefined} from './assertValueIsDefined';
99
import {clickOnRetry as clientClickOnRetry} from './clickOnRetry';
1010
import {clickOnStep as clientClickOnStep} from './clickOnStep';
1111
import {clickOnTestRun as clientClickOnTestRun} from './clickOnTestRun';
12+
import {createJsxRuntime as clientCreateJsxRuntime} from './createJsxRuntime';
1213
import {onDomContentLoad as clientOnDomContentLoad} from './onDomContentLoad';
1314
import {renderAttributes as clientRenderAttributes} from './render';
1415
import {setReadJsonReportDataObservers as clientSetReadJsonReportDataObservers} from './setReadJsonReportDataObservers';
1516

16-
import type {SafeHtml} from '../../../types/internal';
17+
import type {ReportClientState, SafeHtml} from '../../../types/internal';
1718

18-
declare const createLocatorOptions: CreateLocatorOptions;
19-
declare let locator: LocatorFunction<SafeHtml>;
19+
declare const reportClientState: ReportClientState;
2020

2121
const addDomContentLoadedHandler = clientAddDomContentLoadedHandler;
2222
const addOnClickOnClass = clientAddOnClickOnClass;
23+
const assertValueIsDefined: typeof clientAssertValueIsDefined = clientAssertValueIsDefined;
2324
const clickOnRetry = clientClickOnRetry;
2425
const clickOnStep = clientClickOnStep;
2526
const clickOnTestRun = clientClickOnTestRun;
27+
const createJsxRuntime = clientCreateJsxRuntime;
2628
const createSimpleLocator = clientCreateSimpleLocator;
2729
const onDomContentLoad = clientOnDomContentLoad;
2830
const renderAttributes = clientRenderAttributes;
@@ -34,11 +36,20 @@ const setReadJsonReportDataObservers = clientSetReadJsonReportDataObservers;
3436
* @internal
3537
*/
3638
export function initialScript(): void {
37-
const {locator: locatorAttributes} = createSimpleLocator(createLocatorOptions);
39+
const jsxRuntime = createJsxRuntime();
40+
const e2edRightColumnContainer = document.getElementById('e2edRightColumnContainer') ?? undefined;
3841

39-
locator = (...args): SafeHtml => renderAttributes(locatorAttributes(...(args as [string])));
42+
assertValueIsDefined(e2edRightColumnContainer);
4043

41-
locator('');
44+
const {locator: locatorAttributes} = createSimpleLocator(reportClientState.createLocatorOptions);
45+
const locator: LocatorFunction<SafeHtml> = (...args) =>
46+
renderAttributes(locatorAttributes(...(args as [string])));
47+
48+
Object.assign<ReportClientState, Partial<ReportClientState>>(reportClientState, {
49+
e2edRightColumnContainer,
50+
jsxRuntime,
51+
locator,
52+
});
4253

4354
addOnClickOnClass('nav-tabs__button', clickOnRetry);
4455
addOnClickOnClass('step-expanded', clickOnStep);

src/utils/report/client/onFirstJsonReportDataLoad.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import {clickOnTestRun as clientClickOnTestRun} from './clickOnTestRun';
22

3+
import type {ReportClientState} from '../../../types/internal';
4+
35
const clickOnTestRun = clientClickOnTestRun;
46

5-
declare const e2edRightColumnContainer: HTMLElement;
7+
declare const reportClientState: ReportClientState;
68

79
/**
810
* Handler of loading first part of JSON report data for report page.
@@ -25,6 +27,7 @@ export function onFirstJsonReportDataLoad(): void {
2527
clickOnTestRun(buttonForFailedTestRun as HTMLElement);
2628

2729
const buttonOfOpenStep = document.querySelector('.step-expanded[aria-expanded="true"]');
30+
const {e2edRightColumnContainer} = reportClientState;
2831
const scrollDelayInMs = 8;
2932

3033
if (buttonOfOpenStep) {

src/utils/report/client/render/renderApiStatistics.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,15 @@ export function renderApiStatistics({apiStatistics, hash}: Options): SafeHtml {
6464

6565
for (const [url, byStatusCode] of Object.entries(apiStatistics.resources)) {
6666
for (const [statusCode, {count, duration, size}] of Object.entries(byStatusCode)) {
67-
items.push(renderApiStatisticsItem({count, duration, name: `${url} ${statusCode}`, size}));
67+
items.push(
68+
renderApiStatisticsItem({
69+
count,
70+
duration,
71+
isUrl: true,
72+
name: `${url} ${statusCode}`,
73+
size,
74+
}),
75+
);
6876
}
6977
}
7078
}

0 commit comments

Comments
 (0)