@react-native-harness/reporters 1.0.0-alpha.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Callstack
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # @react-native-harness/reporters
2
+
3
+ This library was generated with [Nx](https://nx.dev).
4
+
5
+ ## Building
6
+
7
+ Run `nx build tools` to build the library.
@@ -0,0 +1,3 @@
1
+ import { Reporter } from './reporter.js';
2
+ export declare const defaultReporter: Reporter;
3
+ //# sourceMappingURL=default-reporter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"default-reporter.d.ts","sourceRoot":"","sources":["../src/default-reporter.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,eAAO,MAAM,eAAe,EAAE,QAmC7B,CAAC"}
@@ -0,0 +1,119 @@
1
+ import { color, spinner } from '@react-native-harness/tools';
2
+ export const defaultReporter = {
3
+ report: async (results) => {
4
+ const resultsSpinner = spinner();
5
+ resultsSpinner.start('Test Results');
6
+ for (const suite of results) {
7
+ resultsSpinner.message(formatSuiteResult(suite));
8
+ }
9
+ // Summary
10
+ let totalPassed = 0, totalFailed = 0, totalSkipped = 0, totalTodo = 0;
11
+ let totalDuration = 0;
12
+ for (const suite of results) {
13
+ const summary = getTestSummary(suite);
14
+ totalPassed += summary.passed;
15
+ totalFailed += summary.failed;
16
+ totalSkipped += summary.skipped;
17
+ totalTodo += summary.todo;
18
+ totalDuration += suite.duration || 0;
19
+ }
20
+ const summaryText = [
21
+ color.green(`${totalPassed} passed`),
22
+ color.red(`${totalFailed} failed`),
23
+ color.yellow(`${totalSkipped} skipped`),
24
+ color.blue(`${totalTodo} todo`),
25
+ ].join(', ');
26
+ resultsSpinner.message(`Summary: ${summaryText}`);
27
+ resultsSpinner.message(`Total time: ${formatDuration(totalDuration)}`);
28
+ },
29
+ };
30
+ const formatDuration = (duration) => {
31
+ if (duration == null)
32
+ return '';
33
+ return color.dim(` (${duration}ms)`);
34
+ };
35
+ const getStatusIcon = (status) => {
36
+ switch (status) {
37
+ case 'passed':
38
+ return color.green('✓');
39
+ case 'failed':
40
+ return color.red('✗');
41
+ case 'skipped':
42
+ return color.yellow('○');
43
+ case 'todo':
44
+ return color.blue('◐');
45
+ default:
46
+ return '?';
47
+ }
48
+ };
49
+ const formatTestResult = (test, indent = '') => {
50
+ const icon = getStatusIcon(test.status);
51
+ const name = test.status === 'failed' ? color.red(test.name) : test.name;
52
+ const duration = formatDuration(test.duration);
53
+ let result = `${indent}${icon} ${name}${duration}`;
54
+ if (test.error) {
55
+ const errorLines = test.error.message?.split('\n') || [];
56
+ result +=
57
+ '\n' +
58
+ errorLines
59
+ .map((line) => `${indent} ${color.red(line)}`)
60
+ .join('\n');
61
+ if (test.error?.codeFrame) {
62
+ result += '\n' + test.error.codeFrame.content;
63
+ }
64
+ }
65
+ return result;
66
+ };
67
+ const formatSuiteResult = (suite, indent = '') => {
68
+ const icon = getStatusIcon(suite.status);
69
+ const name = suite.status === 'failed' ? color.red(suite.name) : color.bold(suite.name);
70
+ const duration = formatDuration(suite.duration);
71
+ let result = `${indent}${icon} ${name}${duration}`;
72
+ if (suite.error) {
73
+ const errorLines = suite.error.message.split('\n');
74
+ result +=
75
+ '\n' +
76
+ errorLines
77
+ .map((line) => `${indent} ${color.red(line)}`)
78
+ .join('\n');
79
+ }
80
+ const childIndent = indent + ' ';
81
+ // Format tests
82
+ for (const test of suite.tests) {
83
+ result += '\n' + formatTestResult(test, childIndent);
84
+ }
85
+ // Format nested suites
86
+ for (const childSuite of suite.suites) {
87
+ result += '\n' + formatSuiteResult(childSuite, childIndent);
88
+ }
89
+ return result;
90
+ };
91
+ const getTestSummary = (suite) => {
92
+ let passed = 0, failed = 0, skipped = 0, todo = 0;
93
+ // Count tests in current suite
94
+ for (const test of suite.tests) {
95
+ switch (test.status) {
96
+ case 'passed':
97
+ passed++;
98
+ break;
99
+ case 'failed':
100
+ failed++;
101
+ break;
102
+ case 'skipped':
103
+ skipped++;
104
+ break;
105
+ case 'todo':
106
+ todo++;
107
+ break;
108
+ }
109
+ }
110
+ // Count tests in nested suites
111
+ for (const childSuite of suite.suites) {
112
+ const childSummary = getTestSummary(childSuite);
113
+ passed += childSummary.passed;
114
+ failed += childSummary.failed;
115
+ skipped += childSummary.skipped;
116
+ todo += childSummary.todo;
117
+ }
118
+ return { passed, failed, skipped, todo };
119
+ };
@@ -0,0 +1,4 @@
1
+ export { defaultReporter } from './default-reporter.js';
2
+ export { junitReporter } from './junit-reporter.js';
3
+ export type { Reporter } from './reporter.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { defaultReporter } from './default-reporter.js';
2
+ export { junitReporter } from './junit-reporter.js';
@@ -0,0 +1,3 @@
1
+ import { Reporter } from './reporter.js';
2
+ export declare const junitReporter: Reporter;
3
+ //# sourceMappingURL=junit-reporter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"junit-reporter.d.ts","sourceRoot":"","sources":["../src/junit-reporter.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,eAAO,MAAM,aAAa,EAAE,QAU3B,CAAC"}
@@ -0,0 +1,119 @@
1
+ import { writeFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ export const junitReporter = {
4
+ report: async (results) => {
5
+ const xml = generateJUnitXML(results);
6
+ // Write to junit.xml file
7
+ const outputPath = join(process.cwd(), 'junit.xml');
8
+ writeFileSync(outputPath, xml, 'utf8');
9
+ console.log(`📄 JUnit report written to: ${outputPath}`);
10
+ },
11
+ };
12
+ const generateJUnitXML = (results) => {
13
+ const { totalTests, totalFailures, totalSkipped, totalTime } = calculateTotals(results);
14
+ let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
15
+ xml += `<testsuites tests="${totalTests}" failures="${totalFailures}" skipped="${totalSkipped}" time="${totalTime / 1000}">\n`;
16
+ for (const suite of results) {
17
+ xml += generateTestSuiteXML(suite, ' ');
18
+ }
19
+ xml += '</testsuites>\n';
20
+ return xml;
21
+ };
22
+ const generateTestSuiteXML = (suite, indent) => {
23
+ const { tests, failures, skipped, time } = getSuiteStats(suite);
24
+ let xml = `${indent}<testsuite name="${escapeXML(suite.name)}" tests="${tests}" failures="${failures}" skipped="${skipped}" time="${time / 1000}"`;
25
+ if (suite.error) {
26
+ xml += ` errors="1"`;
27
+ }
28
+ xml += '>\n';
29
+ // Add suite-level error if present
30
+ if (suite.error) {
31
+ xml += `${indent} <error message="${escapeXML(suite.error.message || 'Suite failed')}"`;
32
+ if (suite.error.name) {
33
+ xml += ` type="${escapeXML(suite.error.name)}"`;
34
+ }
35
+ xml += '>';
36
+ if (suite.error.codeFrame) {
37
+ xml += escapeXML(suite.error.codeFrame.content);
38
+ }
39
+ xml += '</error>\n';
40
+ }
41
+ // Add individual test cases
42
+ for (const test of suite.tests) {
43
+ xml += generateTestCaseXML(test, indent + ' ');
44
+ }
45
+ // Add nested suites
46
+ for (const nestedSuite of suite.suites) {
47
+ xml += generateTestSuiteXML(nestedSuite, indent + ' ');
48
+ }
49
+ xml += `${indent}</testsuite>\n`;
50
+ return xml;
51
+ };
52
+ const generateTestCaseXML = (test, indent) => {
53
+ const time = (test.duration || 0) / 1000;
54
+ let xml = `${indent}<testcase name="${escapeXML(test.name)}" time="${time}"`;
55
+ if (test.status === 'passed') {
56
+ xml += '/>\n';
57
+ }
58
+ else {
59
+ xml += '>\n';
60
+ switch (test.status) {
61
+ case 'failed':
62
+ xml += `${indent} <failure message="${escapeXML(test.error?.message || 'Test failed')}"`;
63
+ if (test.error?.name) {
64
+ xml += ` type="${escapeXML(test.error.name)}"`;
65
+ }
66
+ xml += '>';
67
+ if (test.error?.codeFrame) {
68
+ xml += escapeXML(test.error.codeFrame.content);
69
+ }
70
+ xml += '</failure>\n';
71
+ break;
72
+ case 'skipped':
73
+ xml += `${indent} <skipped/>\n`;
74
+ break;
75
+ case 'todo':
76
+ xml += `${indent} <skipped message="TODO: Test not implemented"/>\n`;
77
+ break;
78
+ }
79
+ xml += `${indent}</testcase>\n`;
80
+ }
81
+ return xml;
82
+ };
83
+ const calculateTotals = (results) => {
84
+ let totalTests = 0;
85
+ let totalFailures = 0;
86
+ let totalSkipped = 0;
87
+ let totalTime = 0;
88
+ for (const suite of results) {
89
+ const stats = getSuiteStats(suite);
90
+ totalTests += stats.tests;
91
+ totalFailures += stats.failures;
92
+ totalSkipped += stats.skipped;
93
+ totalTime += stats.time;
94
+ }
95
+ return { totalTests, totalFailures, totalSkipped, totalTime };
96
+ };
97
+ const getSuiteStats = (suite) => {
98
+ let tests = suite.tests.length;
99
+ let failures = suite.tests.filter((t) => t.status === 'failed').length;
100
+ let skipped = suite.tests.filter((t) => t.status === 'skipped' || t.status === 'todo').length;
101
+ let time = suite.duration || 0;
102
+ // Add stats from nested suites
103
+ for (const nestedSuite of suite.suites) {
104
+ const nestedStats = getSuiteStats(nestedSuite);
105
+ tests += nestedStats.tests;
106
+ failures += nestedStats.failures;
107
+ skipped += nestedStats.skipped;
108
+ time += nestedStats.time;
109
+ }
110
+ return { tests, failures, skipped, time };
111
+ };
112
+ const escapeXML = (str) => {
113
+ return str
114
+ .replace(/&/g, '&amp;')
115
+ .replace(/</g, '&lt;')
116
+ .replace(/>/g, '&gt;')
117
+ .replace(/"/g, '&quot;')
118
+ .replace(/'/g, '&apos;');
119
+ };
@@ -0,0 +1,5 @@
1
+ import { TestSuiteResult } from '@react-native-harness/bridge';
2
+ export type Reporter = {
3
+ report: (results: TestSuiteResult[]) => Promise<void>;
4
+ };
5
+ //# sourceMappingURL=reporter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../src/reporter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAE/D,MAAM,MAAM,QAAQ,GAAG;IACrB,MAAM,EAAE,CAAC,OAAO,EAAE,eAAe,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvD,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ {"fileNames":["../../../node_modules/typescript/lib/lib.es5.d.ts","../../../node_modules/typescript/lib/lib.es2015.d.ts","../../../node_modules/typescript/lib/lib.es2016.d.ts","../../../node_modules/typescript/lib/lib.es2017.d.ts","../../../node_modules/typescript/lib/lib.es2018.d.ts","../../../node_modules/typescript/lib/lib.es2019.d.ts","../../../node_modules/typescript/lib/lib.es2020.d.ts","../../../node_modules/typescript/lib/lib.es2021.d.ts","../../../node_modules/typescript/lib/lib.es2022.d.ts","../../../node_modules/typescript/lib/lib.es2015.core.d.ts","../../../node_modules/typescript/lib/lib.es2015.collection.d.ts","../../../node_modules/typescript/lib/lib.es2015.generator.d.ts","../../../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../../node_modules/typescript/lib/lib.es2015.promise.d.ts","../../../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../../node_modules/typescript/lib/lib.es2016.intl.d.ts","../../../node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../../node_modules/typescript/lib/lib.es2017.date.d.ts","../../../node_modules/typescript/lib/lib.es2017.object.d.ts","../../../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../../node_modules/typescript/lib/lib.es2017.string.d.ts","../../../node_modules/typescript/lib/lib.es2017.intl.d.ts","../../../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../../node_modules/typescript/lib/lib.es2018.intl.d.ts","../../../node_modules/typescript/lib/lib.es2018.promise.d.ts","../../../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../../node_modules/typescript/lib/lib.es2019.array.d.ts","../../../node_modules/typescript/lib/lib.es2019.object.d.ts","../../../node_modules/typescript/lib/lib.es2019.string.d.ts","../../../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../../node_modules/typescript/lib/lib.es2019.intl.d.ts","../../../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../../node_modules/typescript/lib/lib.es2020.date.d.ts","../../../node_modules/typescript/lib/lib.es2020.promise.d.ts","../../../node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../../node_modules/typescript/lib/lib.es2020.string.d.ts","../../../node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../../node_modules/typescript/lib/lib.es2020.intl.d.ts","../../../node_modules/typescript/lib/lib.es2020.number.d.ts","../../../node_modules/typescript/lib/lib.es2021.promise.d.ts","../../../node_modules/typescript/lib/lib.es2021.string.d.ts","../../../node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../../node_modules/typescript/lib/lib.es2021.intl.d.ts","../../../node_modules/typescript/lib/lib.es2022.array.d.ts","../../../node_modules/typescript/lib/lib.es2022.error.d.ts","../../../node_modules/typescript/lib/lib.es2022.intl.d.ts","../../../node_modules/typescript/lib/lib.es2022.object.d.ts","../../../node_modules/typescript/lib/lib.es2022.string.d.ts","../../../node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../../node_modules/typescript/lib/lib.esnext.intl.d.ts","../../../node_modules/typescript/lib/lib.decorators.d.ts","../../../node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../../node_modules/tslib/tslib.d.ts","../../../node_modules/tslib/modules/index.d.ts","../../tools/dist/abort.d.ts","../../../node_modules/picocolors/types.d.ts","../../../node_modules/picocolors/picocolors.d.ts","../../tools/dist/color.d.ts","../../tools/dist/logger.d.ts","../../../node_modules/@clack/core/dist/index.d.mts","../../../node_modules/@clack/prompts/dist/index.d.mts","../../tools/dist/prompts.d.ts","../../../node_modules/nano-spawn/source/index.d.ts","../../tools/dist/spawn.d.ts","../../tools/dist/react-native.d.ts","../../tools/dist/index.d.ts","../../bridge/dist/shared/test-runner.d.ts","../../bridge/dist/shared/test-collector.d.ts","../../bridge/dist/shared.d.ts","../../bridge/dist/index.d.ts","../src/reporter.ts","../src/default-reporter.ts","../src/junit-reporter.ts","../src/index.ts","../../../node_modules/@types/node/assert.d.ts","../../../node_modules/@types/node/assert/strict.d.ts","../../../node_modules/@types/node/globals.d.ts","../../../node_modules/@types/node/async_hooks.d.ts","../../../node_modules/@types/node/buffer.d.ts","../../../node_modules/@types/node/child_process.d.ts","../../../node_modules/@types/node/cluster.d.ts","../../../node_modules/@types/node/console.d.ts","../../../node_modules/@types/node/constants.d.ts","../../../node_modules/@types/node/crypto.d.ts","../../../node_modules/@types/node/dgram.d.ts","../../../node_modules/@types/node/diagnostics_channel.d.ts","../../../node_modules/@types/node/dns.d.ts","../../../node_modules/@types/node/dns/promises.d.ts","../../../node_modules/@types/node/domain.d.ts","../../../node_modules/@types/node/dom-events.d.ts","../../../node_modules/@types/node/events.d.ts","../../../node_modules/@types/node/fs.d.ts","../../../node_modules/@types/node/fs/promises.d.ts","../../../node_modules/@types/node/http.d.ts","../../../node_modules/@types/node/http2.d.ts","../../../node_modules/@types/node/https.d.ts","../../../node_modules/@types/node/inspector.d.ts","../../../node_modules/@types/node/module.d.ts","../../../node_modules/@types/node/net.d.ts","../../../node_modules/@types/node/os.d.ts","../../../node_modules/@types/node/path.d.ts","../../../node_modules/@types/node/perf_hooks.d.ts","../../../node_modules/@types/node/process.d.ts","../../../node_modules/@types/node/punycode.d.ts","../../../node_modules/@types/node/querystring.d.ts","../../../node_modules/@types/node/readline.d.ts","../../../node_modules/@types/node/readline/promises.d.ts","../../../node_modules/@types/node/repl.d.ts","../../../node_modules/@types/node/stream.d.ts","../../../node_modules/@types/node/stream/promises.d.ts","../../../node_modules/@types/node/stream/consumers.d.ts","../../../node_modules/@types/node/stream/web.d.ts","../../../node_modules/@types/node/string_decoder.d.ts","../../../node_modules/@types/node/test.d.ts","../../../node_modules/@types/node/timers.d.ts","../../../node_modules/@types/node/timers/promises.d.ts","../../../node_modules/@types/node/tls.d.ts","../../../node_modules/@types/node/trace_events.d.ts","../../../node_modules/@types/node/tty.d.ts","../../../node_modules/@types/node/url.d.ts","../../../node_modules/@types/node/util.d.ts","../../../node_modules/@types/node/v8.d.ts","../../../node_modules/@types/node/vm.d.ts","../../../node_modules/@types/node/wasi.d.ts","../../../node_modules/@types/node/worker_threads.d.ts","../../../node_modules/@types/node/zlib.d.ts","../../../node_modules/@types/node/globals.global.d.ts","../../../node_modules/@types/node/index.d.ts"],"fileIdsList":[[112,115,127],[66,115,127],[81,127],[84,127],[85,90,118,127],[86,97,98,105,115,126,127],[86,87,97,105,127],[88,127],[89,90,98,106,127],[90,115,123,127],[91,93,97,105,127],[92,127],[93,94,127],[97,127],[95,97,127],[97,98,99,115,126,127],[97,98,99,112,115,118,127],[127,131],[127],[93,97,100,105,115,126,127],[97,98,100,101,105,115,123,126,127],[100,102,115,123,126,127],[81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133],[97,103,127],[104,126,127],[93,97,105,115,127],[106,127],[107,127],[84,108,127],[109,125,127,131],[110,127],[111,127],[97,112,113,127],[112,114,127,129],[85,97,115,116,117,118,127],[85,115,117,127],[115,116,127],[118,127],[119,127],[97,121,122,127],[121,122,127],[90,105,115,123,127],[124,127],[105,125,127],[85,100,111,126,127],[90,127],[115,127,128],[127,129],[127,130],[85,90,97,99,108,115,126,127,129,131],[115,127,132],[86,127],[62,127],[59,127],[75,127],[73,74,127],[60,72,76,77,127],[60,77,78,79,127],[60,76,77,98,107,127],[60,76,127],[63,127],[61,64,65,68,70,71,127],[67,127],[69,127]],"fileInfos":[{"version":"69684132aeb9b5642cbcd9e22dff7818ff0ee1aa831728af0ecf97d3364d5546","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","impliedFormat":1},{"version":"ee7bad0c15b58988daa84371e0b89d313b762ab83cb5b31b8a2d1162e8eb41c2","impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"936e80ad36a2ee83fc3caf008e7c4c5afe45b3cf3d5c24408f039c1d47bdc1df","affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"fef8cfad2e2dc5f5b3d97a6f4f2e92848eb1b88e897bb7318cef0e2820bceaab","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"d6d7ae4d1f1f3772e2a3cde568ed08991a8ae34a080ff1151af28b7f798e22ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true,"impliedFormat":1},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true,"impliedFormat":1},{"version":"959d36cddf5e7d572a65045b876f2956c973a586da58e5d26cde519184fd9b8a","affectsGlobalScope":true,"impliedFormat":1},{"version":"965f36eae237dd74e6cca203a43e9ca801ce38824ead814728a2807b1910117d","affectsGlobalScope":true,"impliedFormat":1},{"version":"3925a6c820dcb1a06506c90b1577db1fdbf7705d65b62b99dce4be75c637e26b","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a3d63ef2b853447ec4f749d3f368ce642264246e02911fcb1590d8c161b8005","affectsGlobalScope":true,"impliedFormat":1},{"version":"b5ce7a470bc3628408429040c4e3a53a27755022a32fd05e2cb694e7015386c7","affectsGlobalScope":true,"impliedFormat":1},{"version":"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a","affectsGlobalScope":true,"impliedFormat":1},{"version":"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004","affectsGlobalScope":true,"impliedFormat":1},{"version":"b4b67b1a91182421f5df999988c690f14d813b9850b40acd06ed44691f6727ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"811c71eee4aa0ac5f7adf713323a5c41b0cf6c4e17367a34fbce379e12bbf0a4","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"a6a5253138c5432c68a1510c70fe78a644fe2e632111ba778e1978010d6edfec","impliedFormat":1},{"version":"b8f34dd1757f68e03262b1ca3ddfa668a855b872f8bdd5224d6f993a7b37dc2c","impliedFormat":99},{"version":"3e442c402123b264ccdf7123c1366e1473d9c39f5db69373e938284fc01c49ce","impliedFormat":99},{"version":"590595c1230ebb7c43ebac51b2b2da956a719b213355b4358e2a6b16a8b5936c","impliedFormat":1},{"version":"8c5f0739f00f89f89b03a1fe6658c6d78000d7ebd7f556f0f8d6908fa679de35","impliedFormat":1},{"version":"a15c0df1ac9b2a4d833490522614fc57ae97d70a07bf4964a4d6bb73f3f56399","impliedFormat":99},{"version":"e4cead4a8596a6f051c36ef184f5fb6f117232eefdc1f98ef9847c9efc2c8911","impliedFormat":99},{"version":"df7225806785ade68c6cf2b1cf3ba0d1c7fed1ee7605f34e37d4a901f1d29fd2","impliedFormat":99},{"version":"6bf8a7596880ff1b8f8fd52ee8de8000df00c0c98adacb0b0af0e6a3f16cabe6","impliedFormat":99},{"version":"8af66ec621033f41ccb2bcee775e883c2649fa0db71192fe863185b0281082a6","impliedFormat":99},{"version":"9d4af7bde3e345d3071b5fefbece2d3482971aae89ded50f302c8cb246e81581","impliedFormat":99},{"version":"f3eb748185038bca941a02994b1b0b5803c0a2ce818b8a7d72a24a6b814f5d3e","impliedFormat":99},{"version":"2ba26b5ab00dd3495eeb733cdb46d939a8eb755a395a91169158cdf962aff6c3","impliedFormat":99},{"version":"2e1c22b413f96fd388774b174588816f4bd787486ecfe74f5bcb361dd6bfce50","impliedFormat":99},{"version":"90abb413e835f4b1651ada4f4bf4344b4da5e142f9e6886d94e728de6c744e63","impliedFormat":99},{"version":"402ac3b4c96c5718d18c00339c6e4c66ff0b1a0d8a166570107472342fd57e7a","impliedFormat":99},{"version":"8efa448d458ea512f5bf522f43bffaead1079aa9f97a946fc7f1d4c7c8ec3dff","impliedFormat":99},{"version":"a955c49e9dc38915d1a9860dc049a3b5fa46cb4e9b832ebcde89d96d6e68b8e3","impliedFormat":99},{"version":"af3a654a2f18cbeb067e19142e5342eb9fec08b63e1da931645f07e7a7950590","signature":"827b23664c31b340a726306de97cf35995cd0f8187de0c300e878cd371f54b20","impliedFormat":99},{"version":"1dcb917de0af5368666d96494f01e45efd9117215942e488fc6b7c193c8790ac","signature":"3fe5571471b5fe1076d0cf0a5423f02afede95ef4767cc564e84e8b021f6f637","impliedFormat":99},{"version":"4365260ac74cf2f2da6160000201372d63cca52b7c2e465cbb56935e669e9486","signature":"be07ea611be12cf505a53bbac38ac822b6789a19ebdbf994a912c93bec7ce27c","impliedFormat":99},{"version":"94399f874cb5a0b65f1567401478b6ef72f2f41ddd0cdf4cf82a856d1a00bb5c","impliedFormat":99},{"version":"7e771891adaa85b690266bc37bd6eb43bc57eecc4b54693ead36467e7369952a","impliedFormat":1},{"version":"a69c09dbea52352f479d3e7ac949fde3d17b195abe90b045d619f747b38d6d1a","impliedFormat":1},{"version":"f749812878fecfa53cfc13b36e5d35086fb6377983a9df44175da83ccc23af1f","affectsGlobalScope":true,"impliedFormat":1},{"version":"7d2e3fea24c712c99c03ad8f556abedbfe105f87f1be10b95dbd409d24bc05a3","impliedFormat":1},{"version":"211e3f15fbced4ab4be19f49ffa990b9ff20d749d33b65ff753be691e7616239","affectsGlobalScope":true,"impliedFormat":1},{"version":"3719525a8f6ab731e3dfd585d9f87df55ec7d50d461df84f74eb4d68bb165244","impliedFormat":1},{"version":"5a94487653355b56018122d92392beb2e5f4a6c63ba5cef83bbe1c99775ef713","impliedFormat":1},{"version":"d5135ad93b33adcce80b18f8065087934cdc1730d63db58562edcf017e1aad9b","affectsGlobalScope":true,"impliedFormat":1},{"version":"82408ed3e959ddc60d3e9904481b5a8dc16469928257af22a3f7d1a3bc7fd8c4","impliedFormat":1},{"version":"e596c9bb2f29a2699fdd4ae89139612652245192f67f45617c5a4b20832aaae9","impliedFormat":1},{"version":"bb9c4ffa5e6290c6980b63c815cdd1625876dadb2efaf77edbe82984be93e55e","impliedFormat":1},{"version":"1cdcfc1f624d6c08aa12c73935f6e13f095919cd99edf95752951796eb225729","impliedFormat":1},{"version":"4eaff3d8e10676fd7913d8c108890e71c688e1e7d52f6d1d55c39514f493dc47","impliedFormat":1},{"version":"14b5aa23c5d0ae1907bc696ac7b6915d88f7d85799cc0dc2dcf98fbce2c5a67c","impliedFormat":1},{"version":"5c439dafdc09abe4d6c260a96b822fa0ba5be7203c71a63ab1f1423cd9e838ea","impliedFormat":1},{"version":"6b526a5ec4a401ca7c26cfe6a48e641d8f30af76673bad3b06a1b4504594a960","affectsGlobalScope":true,"impliedFormat":1},{"version":"00dee7cdca8b8420c47ea4a31a34b8e8294013ebc4f463fd941e867e7bf05029","affectsGlobalScope":true,"impliedFormat":1},{"version":"3256f3cccd578f9e7fe3a28096c505634bebcee8afb738ffa99368e536ca3a0b","impliedFormat":1},{"version":"1c84b46267610a34028edfd0d035509341751262bac1062857f3c8df7aff7153","impliedFormat":1},{"version":"7f138842074d0a40681775af008c8452093b68c383c94de31759e853c6d06b5c","impliedFormat":1},{"version":"a3d541d303ee505053f5dcbf9fafb65cac3d5631037501cd616195863a6c5740","impliedFormat":1},{"version":"8d3c583a07e0c37e876908c2d5da575019f689df8d9fa4c081d99119d53dba22","impliedFormat":1},{"version":"2c828a5405191d006115ab34e191b8474bc6c86ffdc401d1a9864b1b6e088a58","impliedFormat":1},{"version":"e630e5528e899219ae319e83bef54bf3bcb91b01d76861ecf881e8e614b167f0","affectsGlobalScope":true,"impliedFormat":1},{"version":"bcebb922784739bdb34c18ee51095d25a92b560c78ccd2eaacd6bd00f7443d83","impliedFormat":1},{"version":"7ee6ed878c4528215c82b664fe0cfe80e8b4da6c0d4cc80869367868774db8b1","impliedFormat":1},{"version":"b0973c3cbcdc59b37bf477731d468696ecaf442593ec51bab497a613a580fe30","impliedFormat":1},{"version":"4989e92ba5b69b182d2caaea6295af52b7dc73a4f7a2e336a676722884e7139d","affectsGlobalScope":true,"impliedFormat":1},{"version":"0715e4cd28ad471b2a93f3e552ff51a3ae423417a01a10aa1d3bc7c6b95059d6","affectsGlobalScope":true,"impliedFormat":1},{"version":"5153a2fd150e46ce57bb3f8db1318d33f6ad3261ed70ceeff92281c0608c74a3","impliedFormat":1},{"version":"210d54cd652ec0fec8c8916e4af59bb341065576ecda039842f9ffb2e908507c","impliedFormat":1},{"version":"36b03690b628eab08703d63f04eaa89c5df202e5f1edf3989f13ad389cd2c091","impliedFormat":1},{"version":"0effadd232a20498b11308058e334d3339cc5bf8c4c858393e38d9d4c0013dcf","impliedFormat":1},{"version":"25846d43937c672bab7e8195f3d881f93495df712ee901860effc109918938cc","impliedFormat":1},{"version":"4f3fdeba4e28e21aa719c081b8dc8f91d47e12e773389b9d35679c08151c9d37","impliedFormat":1},{"version":"1b952304137851e45bc009785de89ada562d9376177c97e37702e39e60c2f1ff","impliedFormat":1},{"version":"69ee23dd0d215b09907ad30d23f88b7790c93329d1faf31d7835552a10cf7cbf","impliedFormat":1},{"version":"44b8b584a338b190a59f4f6929d072431950c7bd92ec2694821c11bce180c8a5","impliedFormat":1},{"version":"23b89798789dffbd437c0c423f5d02d11f9736aea73d6abf16db4f812ff36eda","impliedFormat":1},{"version":"f69ff39996a61a0dd10f4bce73272b52e8024a4d58b13ab32bf4712909d0a2b7","impliedFormat":1},{"version":"3c4ba1dd9b12ffa284b565063108f2f031d150ea15b8fafbdc17f5d2a07251f3","affectsGlobalScope":true,"impliedFormat":1},{"version":"e10177274a35a9d07c825615340b2fcde2f610f53f3fb40269fd196b4288dda6","impliedFormat":1},{"version":"1422cd9e705adcc09088fda85a900c2b70e3ad36ea85846f68bd1a884cdf4e2b","impliedFormat":1},{"version":"3c13ef48634e7b5012fcf7e8fce7496352c2d779a7201389ca96a2a81ee4314d","impliedFormat":1},{"version":"5d0a25ec910fa36595f85a67ac992d7a53dd4064a1ba6aea1c9f14ab73a023f2","impliedFormat":1},{"version":"a73ae8c0e62103bb9e21bb6538700881bf135b9a8b125b857ec68edfa0da4ed3","affectsGlobalScope":true,"impliedFormat":1},{"version":"e1c1b2fbe236bf7ee3e342eeae7e20efb8988a0ac7da1cbbfa2c1f66b76c3423","affectsGlobalScope":true,"impliedFormat":1},{"version":"868831cab82b65dfe1d68180e898af1f2101e89ba9b754d1db6fb8cc2fac1921","impliedFormat":1},{"version":"0fe8985a28f82c450a04a6edf1279d7181c0893f37da7d2a27f8efd4fd5edb03","impliedFormat":1},{"version":"e59a892d87e72733e2a9ca21611b9beb52977be2696c7ba4b216cbbb9a48f5aa","impliedFormat":1},{"version":"52120bb7e4583612225bdf08e7c12559548170f11e660d33a33623bae9bbdbba","affectsGlobalScope":true,"impliedFormat":1},{"version":"8a300fa9b698845a1f9c41ecbe2c5966634582a8e2020d51abcace9b55aa959e","impliedFormat":1},{"version":"ab9b9a36e5284fd8d3bf2f7d5fcbc60052f25f27e4d20954782099282c60d23e","affectsGlobalScope":true,"impliedFormat":1},{"version":"a6dd3dba8e665ac43d279e0fdf5219edda0eed69b5e9a5061f46cd6a65c4f7a1","impliedFormat":1}],"root":[[77,80]],"options":{"composite":true,"declarationMap":true,"emitDeclarationOnly":false,"importHelpers":true,"module":199,"noEmitOnError":true,"noFallthroughCasesInSwitch":true,"noImplicitOverride":true,"noImplicitReturns":true,"noUnusedLocals":true,"outDir":"./","rootDir":"../src","skipLibCheck":true,"strict":true,"target":9,"tsBuildInfoFile":"./tsconfig.lib.tsbuildinfo"},"referencedMap":[[66,1],[67,2],[81,3],[82,3],[84,4],[85,5],[86,6],[87,7],[88,8],[89,9],[90,10],[91,11],[92,12],[93,13],[94,13],[96,14],[95,15],[97,14],[98,16],[99,17],[83,18],[133,19],[100,20],[101,21],[102,22],[134,23],[103,24],[104,25],[105,26],[106,27],[107,28],[108,29],[109,30],[110,31],[111,32],[112,33],[113,33],[114,34],[115,35],[117,36],[116,37],[118,38],[119,39],[120,19],[121,40],[122,41],[123,42],[124,43],[125,44],[126,45],[127,46],[128,47],[129,48],[130,49],[131,50],[132,51],[69,52],[63,53],[62,19],[60,54],[59,19],[57,19],[58,19],[11,19],[10,19],[2,19],[12,19],[13,19],[14,19],[15,19],[16,19],[17,19],[18,19],[19,19],[3,19],[20,19],[21,19],[4,19],[22,19],[26,19],[23,19],[24,19],[25,19],[27,19],[28,19],[29,19],[5,19],[30,19],[31,19],[32,19],[33,19],[6,19],[37,19],[34,19],[35,19],[36,19],[38,19],[7,19],[39,19],[44,19],[45,19],[40,19],[41,19],[42,19],[43,19],[8,19],[49,19],[46,19],[47,19],[48,19],[50,19],[9,19],[51,19],[52,19],[53,19],[55,19],[54,19],[1,19],[56,19],[76,55],[75,56],[74,19],[73,19],[78,57],[80,58],[79,59],[77,60],[61,19],[64,61],[72,62],[65,19],[68,63],[71,19],[70,64]],"latestChangedDtsFile":"./index.d.ts","version":"5.8.3"}
package/package.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "@react-native-harness/reporters",
3
+ "version": "1.0.0-alpha.7",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "dependencies": {
8
+ "@react-native-harness/tools": "1.0.0-alpha.7"
9
+ },
10
+ "devDependencies": {
11
+ "@react-native-harness/bridge": "1.0.0-alpha.7"
12
+ }
13
+ }
@@ -0,0 +1,153 @@
1
+ import { color, spinner } from '@react-native-harness/tools';
2
+ import type { TestSuiteResult, TestResult } from '@react-native-harness/bridge';
3
+ import { Reporter } from './reporter.js';
4
+
5
+ export const defaultReporter: Reporter = {
6
+ report: async (results) => {
7
+ const resultsSpinner = spinner();
8
+ resultsSpinner.start('Test Results');
9
+
10
+ for (const suite of results) {
11
+ resultsSpinner.message(formatSuiteResult(suite));
12
+ }
13
+
14
+ // Summary
15
+ let totalPassed = 0,
16
+ totalFailed = 0,
17
+ totalSkipped = 0,
18
+ totalTodo = 0;
19
+ let totalDuration = 0;
20
+
21
+ for (const suite of results) {
22
+ const summary = getTestSummary(suite);
23
+ totalPassed += summary.passed;
24
+ totalFailed += summary.failed;
25
+ totalSkipped += summary.skipped;
26
+ totalTodo += summary.todo;
27
+ totalDuration += suite.duration || 0;
28
+ }
29
+
30
+ const summaryText = [
31
+ color.green(`${totalPassed} passed`),
32
+ color.red(`${totalFailed} failed`),
33
+ color.yellow(`${totalSkipped} skipped`),
34
+ color.blue(`${totalTodo} todo`),
35
+ ].join(', ');
36
+
37
+ resultsSpinner.message(`Summary: ${summaryText}`);
38
+ resultsSpinner.message(`Total time: ${formatDuration(totalDuration)}`);
39
+ },
40
+ };
41
+
42
+ const formatDuration = (duration?: number): string => {
43
+ if (duration == null) return '';
44
+ return color.dim(` (${duration}ms)`);
45
+ };
46
+
47
+ const getStatusIcon = (status: string): string => {
48
+ switch (status) {
49
+ case 'passed':
50
+ return color.green('✓');
51
+ case 'failed':
52
+ return color.red('✗');
53
+ case 'skipped':
54
+ return color.yellow('○');
55
+ case 'todo':
56
+ return color.blue('◐');
57
+ default:
58
+ return '?';
59
+ }
60
+ };
61
+
62
+ const formatTestResult = (test: TestResult, indent = ''): string => {
63
+ const icon = getStatusIcon(test.status);
64
+ const name = test.status === 'failed' ? color.red(test.name) : test.name;
65
+ const duration = formatDuration(test.duration);
66
+
67
+ let result = `${indent}${icon} ${name}${duration}`;
68
+
69
+ if (test.error) {
70
+ const errorLines = test.error.message?.split('\n') || [];
71
+ result +=
72
+ '\n' +
73
+ errorLines
74
+ .map((line: string) => `${indent} ${color.red(line)}`)
75
+ .join('\n');
76
+
77
+ if (test.error?.codeFrame) {
78
+ result += '\n' + test.error.codeFrame.content;
79
+ }
80
+ }
81
+
82
+ return result;
83
+ };
84
+
85
+ const formatSuiteResult = (suite: TestSuiteResult, indent = ''): string => {
86
+ const icon = getStatusIcon(suite.status);
87
+ const name =
88
+ suite.status === 'failed' ? color.red(suite.name) : color.bold(suite.name);
89
+ const duration = formatDuration(suite.duration);
90
+
91
+ let result = `${indent}${icon} ${name}${duration}`;
92
+
93
+ if (suite.error) {
94
+ const errorLines = suite.error.message.split('\n');
95
+ result +=
96
+ '\n' +
97
+ errorLines
98
+ .map((line: string) => `${indent} ${color.red(line)}`)
99
+ .join('\n');
100
+ }
101
+
102
+ const childIndent = indent + ' ';
103
+
104
+ // Format tests
105
+ for (const test of suite.tests) {
106
+ result += '\n' + formatTestResult(test, childIndent);
107
+ }
108
+
109
+ // Format nested suites
110
+ for (const childSuite of suite.suites) {
111
+ result += '\n' + formatSuiteResult(childSuite, childIndent);
112
+ }
113
+
114
+ return result;
115
+ };
116
+
117
+ const getTestSummary = (
118
+ suite: TestSuiteResult
119
+ ): { passed: number; failed: number; skipped: number; todo: number } => {
120
+ let passed = 0,
121
+ failed = 0,
122
+ skipped = 0,
123
+ todo = 0;
124
+
125
+ // Count tests in current suite
126
+ for (const test of suite.tests) {
127
+ switch (test.status) {
128
+ case 'passed':
129
+ passed++;
130
+ break;
131
+ case 'failed':
132
+ failed++;
133
+ break;
134
+ case 'skipped':
135
+ skipped++;
136
+ break;
137
+ case 'todo':
138
+ todo++;
139
+ break;
140
+ }
141
+ }
142
+
143
+ // Count tests in nested suites
144
+ for (const childSuite of suite.suites) {
145
+ const childSummary = getTestSummary(childSuite);
146
+ passed += childSummary.passed;
147
+ failed += childSummary.failed;
148
+ skipped += childSummary.skipped;
149
+ todo += childSummary.todo;
150
+ }
151
+
152
+ return { passed, failed, skipped, todo };
153
+ };
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { defaultReporter } from './default-reporter.js';
2
+ export { junitReporter } from './junit-reporter.js';
3
+ export type { Reporter } from './reporter.js';
@@ -0,0 +1,179 @@
1
+ import type { TestSuiteResult, TestResult } from '@react-native-harness/bridge';
2
+ import { writeFileSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { Reporter } from './reporter.js';
5
+
6
+ export const junitReporter: Reporter = {
7
+ report: async (results) => {
8
+ const xml = generateJUnitXML(results);
9
+
10
+ // Write to junit.xml file
11
+ const outputPath = join(process.cwd(), 'junit.xml');
12
+ writeFileSync(outputPath, xml, 'utf8');
13
+
14
+ console.log(`📄 JUnit report written to: ${outputPath}`);
15
+ },
16
+ };
17
+
18
+ const generateJUnitXML = (results: TestSuiteResult[]): string => {
19
+ const { totalTests, totalFailures, totalSkipped, totalTime } =
20
+ calculateTotals(results);
21
+
22
+ let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
23
+ xml += `<testsuites tests="${totalTests}" failures="${totalFailures}" skipped="${totalSkipped}" time="${
24
+ totalTime / 1000
25
+ }">\n`;
26
+
27
+ for (const suite of results) {
28
+ xml += generateTestSuiteXML(suite, ' ');
29
+ }
30
+
31
+ xml += '</testsuites>\n';
32
+
33
+ return xml;
34
+ };
35
+
36
+ const generateTestSuiteXML = (
37
+ suite: TestSuiteResult,
38
+ indent: string
39
+ ): string => {
40
+ const { tests, failures, skipped, time } = getSuiteStats(suite);
41
+
42
+ let xml = `${indent}<testsuite name="${escapeXML(
43
+ suite.name
44
+ )}" tests="${tests}" failures="${failures}" skipped="${skipped}" time="${
45
+ time / 1000
46
+ }"`;
47
+
48
+ if (suite.error) {
49
+ xml += ` errors="1"`;
50
+ }
51
+
52
+ xml += '>\n';
53
+
54
+ // Add suite-level error if present
55
+ if (suite.error) {
56
+ xml += `${indent} <error message="${escapeXML(
57
+ suite.error.message || 'Suite failed'
58
+ )}"`;
59
+ if (suite.error.name) {
60
+ xml += ` type="${escapeXML(suite.error.name)}"`;
61
+ }
62
+ xml += '>';
63
+ if (suite.error.codeFrame) {
64
+ xml += escapeXML(suite.error.codeFrame.content);
65
+ }
66
+ xml += '</error>\n';
67
+ }
68
+
69
+ // Add individual test cases
70
+ for (const test of suite.tests) {
71
+ xml += generateTestCaseXML(test, indent + ' ');
72
+ }
73
+
74
+ // Add nested suites
75
+ for (const nestedSuite of suite.suites) {
76
+ xml += generateTestSuiteXML(nestedSuite, indent + ' ');
77
+ }
78
+
79
+ xml += `${indent}</testsuite>\n`;
80
+
81
+ return xml;
82
+ };
83
+
84
+ const generateTestCaseXML = (test: TestResult, indent: string): string => {
85
+ const time = (test.duration || 0) / 1000;
86
+ let xml = `${indent}<testcase name="${escapeXML(test.name)}" time="${time}"`;
87
+
88
+ if (test.status === 'passed') {
89
+ xml += '/>\n';
90
+ } else {
91
+ xml += '>\n';
92
+
93
+ switch (test.status) {
94
+ case 'failed':
95
+ xml += `${indent} <failure message="${escapeXML(
96
+ test.error?.message || 'Test failed'
97
+ )}"`;
98
+ if (test.error?.name) {
99
+ xml += ` type="${escapeXML(test.error.name)}"`;
100
+ }
101
+ xml += '>';
102
+ if (test.error?.codeFrame) {
103
+ xml += escapeXML(test.error.codeFrame.content);
104
+ }
105
+ xml += '</failure>\n';
106
+ break;
107
+ case 'skipped':
108
+ xml += `${indent} <skipped/>\n`;
109
+ break;
110
+ case 'todo':
111
+ xml += `${indent} <skipped message="TODO: Test not implemented"/>\n`;
112
+ break;
113
+ }
114
+
115
+ xml += `${indent}</testcase>\n`;
116
+ }
117
+
118
+ return xml;
119
+ };
120
+
121
+ const calculateTotals = (
122
+ results: TestSuiteResult[]
123
+ ): {
124
+ totalTests: number;
125
+ totalFailures: number;
126
+ totalSkipped: number;
127
+ totalTime: number;
128
+ } => {
129
+ let totalTests = 0;
130
+ let totalFailures = 0;
131
+ let totalSkipped = 0;
132
+ let totalTime = 0;
133
+
134
+ for (const suite of results) {
135
+ const stats = getSuiteStats(suite);
136
+ totalTests += stats.tests;
137
+ totalFailures += stats.failures;
138
+ totalSkipped += stats.skipped;
139
+ totalTime += stats.time;
140
+ }
141
+
142
+ return { totalTests, totalFailures, totalSkipped, totalTime };
143
+ };
144
+
145
+ const getSuiteStats = (
146
+ suite: TestSuiteResult
147
+ ): {
148
+ tests: number;
149
+ failures: number;
150
+ skipped: number;
151
+ time: number;
152
+ } => {
153
+ let tests = suite.tests.length;
154
+ let failures = suite.tests.filter((t) => t.status === 'failed').length;
155
+ let skipped = suite.tests.filter(
156
+ (t) => t.status === 'skipped' || t.status === 'todo'
157
+ ).length;
158
+ let time = suite.duration || 0;
159
+
160
+ // Add stats from nested suites
161
+ for (const nestedSuite of suite.suites) {
162
+ const nestedStats = getSuiteStats(nestedSuite);
163
+ tests += nestedStats.tests;
164
+ failures += nestedStats.failures;
165
+ skipped += nestedStats.skipped;
166
+ time += nestedStats.time;
167
+ }
168
+
169
+ return { tests, failures, skipped, time };
170
+ };
171
+
172
+ const escapeXML = (str: string): string => {
173
+ return str
174
+ .replace(/&/g, '&amp;')
175
+ .replace(/</g, '&lt;')
176
+ .replace(/>/g, '&gt;')
177
+ .replace(/"/g, '&quot;')
178
+ .replace(/'/g, '&apos;');
179
+ };
@@ -0,0 +1,5 @@
1
+ import { TestSuiteResult } from '@react-native-harness/bridge';
2
+
3
+ export type Reporter = {
4
+ report: (results: TestSuiteResult[]) => Promise<void>;
5
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "extends": "../../../../tsconfig.base.json",
3
+ "files": [],
4
+ "include": [],
5
+ "references": [
6
+ {
7
+ "path": "../bridge"
8
+ },
9
+ {
10
+ "path": "./tsconfig.lib.json"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "baseUrl": ".",
5
+ "rootDir": "src",
6
+ "outDir": "dist",
7
+ "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo",
8
+ "emitDeclarationOnly": false,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "types": ["node"]
11
+ },
12
+ "include": ["src/**/*.ts"],
13
+ "references": [
14
+ {
15
+ "path": "../tools/tsconfig.lib.json"
16
+ },
17
+ {
18
+ "path": "../bridge/tsconfig.lib.json"
19
+ }
20
+ ]
21
+ }