@react-native-harness/runtime 1.0.0-alpha.1
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/.babelrc.js +23 -0
- package/LICENSE +20 -0
- package/README.md +7 -0
- package/assets/logo.png +0 -0
- package/assets/moduleSystem.flow.js +1062 -0
- package/dist/bundler/bundle.d.ts +2 -0
- package/dist/bundler/bundle.d.ts.map +1 -0
- package/dist/bundler/bundle.js +16 -0
- package/dist/bundler/dev-server.d.ts +2 -0
- package/dist/bundler/dev-server.d.ts.map +1 -0
- package/dist/bundler/dev-server.js +5 -0
- package/dist/bundler/errors.d.ts +10 -0
- package/dist/bundler/errors.d.ts.map +1 -0
- package/dist/bundler/errors.js +18 -0
- package/dist/bundler/evaluate.d.ts +2 -0
- package/dist/bundler/evaluate.d.ts.map +1 -0
- package/dist/bundler/evaluate.js +18 -0
- package/dist/bundler/index.d.ts +3 -0
- package/dist/bundler/index.d.ts.map +1 -0
- package/dist/bundler/index.js +2 -0
- package/dist/client/factory.d.ts +2 -0
- package/dist/client/factory.d.ts.map +1 -0
- package/dist/client/factory.js +41 -0
- package/dist/client/getDeviceDescriptor.d.ts +8 -0
- package/dist/client/getDeviceDescriptor.d.ts.map +1 -0
- package/dist/client/getDeviceDescriptor.js +20 -0
- package/dist/client/getWSServer.d.ts +2 -0
- package/dist/client/getWSServer.d.ts.map +1 -0
- package/dist/client/getWSServer.js +7 -0
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +1 -0
- package/dist/collector/errors.d.ts +8 -0
- package/dist/collector/errors.d.ts.map +1 -0
- package/dist/collector/errors.js +20 -0
- package/dist/collector/factory.d.ts +3 -0
- package/dist/collector/factory.d.ts.map +1 -0
- package/dist/collector/factory.js +25 -0
- package/dist/collector/functions.d.ts +22 -0
- package/dist/collector/functions.d.ts.map +1 -0
- package/dist/collector/functions.js +271 -0
- package/dist/collector/index.d.ts +5 -0
- package/dist/collector/index.d.ts.map +1 -0
- package/dist/collector/index.js +3 -0
- package/dist/collector/types.d.ts +10 -0
- package/dist/collector/types.d.ts.map +1 -0
- package/dist/collector/types.js +1 -0
- package/dist/collector/validation.d.ts +4 -0
- package/dist/collector/validation.d.ts.map +1 -0
- package/dist/collector/validation.js +15 -0
- package/dist/constants.d.ts +3 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +2 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +13 -0
- package/dist/expect/index.d.ts +9 -0
- package/dist/expect/index.d.ts.map +1 -0
- package/dist/expect/index.js +71 -0
- package/dist/expect/setup.d.ts +2 -0
- package/dist/expect/setup.d.ts.map +1 -0
- package/dist/expect/setup.js +5 -0
- package/dist/exports.d.ts +7 -0
- package/dist/exports.d.ts.map +1 -0
- package/dist/exports.js +6 -0
- package/dist/getEntryComponent.d.ts +6 -0
- package/dist/getEntryComponent.d.ts.map +1 -0
- package/dist/getEntryComponent.js +6 -0
- package/dist/globals.d.ts +5 -0
- package/dist/globals.d.ts.map +1 -0
- package/dist/globals.js +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/initialize.d.ts +2 -0
- package/dist/initialize.d.ts.map +1 -0
- package/dist/initialize.js +16 -0
- package/dist/logger.d.ts +6 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +14 -0
- package/dist/mock.d.ts +15 -0
- package/dist/mock.d.ts.map +1 -0
- package/dist/mock.js +37 -0
- package/dist/mocker/index.d.ts +2 -0
- package/dist/mocker/index.d.ts.map +1 -0
- package/dist/mocker/index.js +1 -0
- package/dist/mocker/registry.d.ts +7 -0
- package/dist/mocker/registry.d.ts.map +1 -0
- package/dist/mocker/registry.js +41 -0
- package/dist/mocker/types.d.ts +6 -0
- package/dist/mocker/types.d.ts.map +1 -0
- package/dist/mocker/types.js +1 -0
- package/dist/module.d.ts +3 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +19 -0
- package/dist/module.web.d.ts +2 -0
- package/dist/module.web.d.ts.map +1 -0
- package/dist/module.web.js +12 -0
- package/dist/rntl/client.d.ts +3 -0
- package/dist/rntl/client.d.ts.map +1 -0
- package/dist/rntl/client.js +8 -0
- package/dist/rntl/describe.d.ts +2 -0
- package/dist/rntl/describe.d.ts.map +1 -0
- package/dist/rntl/describe.js +1 -0
- package/dist/rntl/expect.d.ts +128 -0
- package/dist/rntl/expect.d.ts.map +1 -0
- package/dist/rntl/expect.js +670 -0
- package/dist/rntl/fn.d.ts +2 -0
- package/dist/rntl/fn.d.ts.map +1 -0
- package/dist/rntl/fn.js +1 -0
- package/dist/rntl/mock.d.ts +2 -0
- package/dist/rntl/mock.d.ts.map +1 -0
- package/dist/rntl/mock.js +1 -0
- package/dist/rntl/render.d.ts +4 -0
- package/dist/rntl/render.d.ts.map +1 -0
- package/dist/rntl/render.js +11 -0
- package/dist/rntl/screen.d.ts +45 -0
- package/dist/rntl/screen.d.ts.map +1 -0
- package/dist/rntl/screen.js +31 -0
- package/dist/rntl/spies.d.ts +45 -0
- package/dist/rntl/spies.d.ts.map +1 -0
- package/dist/rntl/spies.js +553 -0
- package/dist/rntl/userEvent.d.ts +22 -0
- package/dist/rntl/userEvent.d.ts.map +1 -0
- package/dist/rntl/userEvent.js +19 -0
- package/dist/runner/errors.d.ts +9 -0
- package/dist/runner/errors.d.ts.map +1 -0
- package/dist/runner/errors.js +23 -0
- package/dist/runner/factory.d.ts +3 -0
- package/dist/runner/factory.d.ts.map +1 -0
- package/dist/runner/factory.js +17 -0
- package/dist/runner/hooks.d.ts +4 -0
- package/dist/runner/hooks.d.ts.map +1 -0
- package/dist/runner/hooks.js +39 -0
- package/dist/runner/index.d.ts +4 -0
- package/dist/runner/index.d.ts.map +1 -0
- package/dist/runner/index.js +2 -0
- package/dist/runner/runSuite.d.ts +4 -0
- package/dist/runner/runSuite.d.ts.map +1 -0
- package/dist/runner/runSuite.js +147 -0
- package/dist/runner/types.d.ts +13 -0
- package/dist/runner/types.d.ts.map +1 -0
- package/dist/runner/types.js +1 -0
- package/dist/runner.d.ts +7 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +201 -0
- package/dist/runtime.d.ts +2 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +44 -0
- package/dist/spy/index.d.ts +2 -0
- package/dist/spy/index.d.ts.map +1 -0
- package/dist/spy/index.js +2 -0
- package/dist/state.d.ts +25 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +37 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -0
- package/dist/ui/ReadyScreen.d.ts +2 -0
- package/dist/ui/ReadyScreen.d.ts.map +1 -0
- package/dist/ui/ReadyScreen.js +110 -0
- package/dist/ui/UI.d.ts +13 -0
- package/dist/ui/UI.d.ts.map +1 -0
- package/dist/ui/UI.js +121 -0
- package/dist/ui/WrongEnvironmentScreen.d.ts +2 -0
- package/dist/ui/WrongEnvironmentScreen.d.ts.map +1 -0
- package/dist/ui/WrongEnvironmentScreen.js +87 -0
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +3 -0
- package/dist/ui/state.d.ts +7 -0
- package/dist/ui/state.d.ts.map +1 -0
- package/dist/ui/state.js +6 -0
- package/dist/utils/dev-server.d.ts +2 -0
- package/dist/utils/dev-server.d.ts.map +1 -0
- package/dist/utils/dev-server.js +5 -0
- package/dist/utils/emitter.d.ts +16 -0
- package/dist/utils/emitter.d.ts.map +1 -0
- package/dist/utils/emitter.js +39 -0
- package/eslint.config.mjs +16 -0
- package/package.json +38 -0
- package/src/__tests__/collector.test.ts +553 -0
- package/src/__tests__/error-handling.test.ts +132 -0
- package/src/__tests__/expect.test.ts +619 -0
- package/src/__tests__/spy.test.ts +538 -0
- package/src/bundler/bundle.ts +19 -0
- package/src/bundler/errors.ts +16 -0
- package/src/bundler/evaluate.ts +25 -0
- package/src/bundler/index.ts +2 -0
- package/src/client/factory.ts +56 -0
- package/src/client/getDeviceDescriptor.ts +30 -0
- package/src/client/getWSServer.ts +9 -0
- package/src/client/index.ts +1 -0
- package/src/collector/errors.ts +27 -0
- package/src/collector/factory.ts +32 -0
- package/src/collector/functions.ts +376 -0
- package/src/collector/index.ts +12 -0
- package/src/collector/types.ts +15 -0
- package/src/collector/validation.ts +21 -0
- package/src/constants.ts +2 -0
- package/src/errors.ts +12 -0
- package/src/expect/index.ts +117 -0
- package/src/expect/setup.ts +10 -0
- package/src/globals.ts +5 -0
- package/src/index.ts +7 -0
- package/src/initialize.ts +22 -0
- package/src/mocker/index.ts +1 -0
- package/src/mocker/metro-require.d.ts +5 -0
- package/src/mocker/registry.ts +58 -0
- package/src/mocker/types.ts +6 -0
- package/src/react-native.d.ts +16 -0
- package/src/runner/errors.ts +31 -0
- package/src/runner/factory.ts +21 -0
- package/src/runner/hooks.ts +51 -0
- package/src/runner/index.ts +7 -0
- package/src/runner/runSuite.ts +201 -0
- package/src/runner/types.ts +19 -0
- package/src/spy/index.ts +2 -0
- package/src/ui/ReadyScreen.tsx +151 -0
- package/src/ui/WrongEnvironmentScreen.tsx +113 -0
- package/src/ui/index.ts +3 -0
- package/src/ui/state.ts +13 -0
- package/src/utils/dev-server.ts +6 -0
- package/src/utils/emitter.ts +64 -0
- package/tsconfig.json +16 -0
- package/tsconfig.lib.json +33 -0
- package/tsconfig.spec.json +30 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/types/global.d.ts +2 -0
- package/types/index.d.ts +1 -0
- package/vite.config.ts +27 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundle.d.ts","sourceRoot":"","sources":["../../src/bundler/bundle.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,WAAW,GAAU,UAAU,MAAM,KAAG,OAAO,CAAC,MAAM,CAIlE,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
import { getDevServerUrl } from '../utils/dev-server.js';
|
|
3
|
+
const getModuleUrl = (fileName) => {
|
|
4
|
+
const devServerUrl = getDevServerUrl();
|
|
5
|
+
const bundleName = fileName.split('.').slice(0, -1).join('.') + '.bundle';
|
|
6
|
+
const urlSearchParams = new URLSearchParams({
|
|
7
|
+
modulesOnly: 'true',
|
|
8
|
+
platform: Platform.OS,
|
|
9
|
+
});
|
|
10
|
+
return `${devServerUrl}/${bundleName}?${urlSearchParams.toString()}`;
|
|
11
|
+
};
|
|
12
|
+
export const fetchModule = async (fileName) => {
|
|
13
|
+
const url = getModuleUrl(fileName);
|
|
14
|
+
const response = await fetch(url);
|
|
15
|
+
return response.text();
|
|
16
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev-server.d.ts","sourceRoot":"","sources":["../../src/bundler/dev-server.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,eAAe,QAAO,MAGlC,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare class ModuleNotFoundError extends Error {
|
|
2
|
+
readonly modulePath: string;
|
|
3
|
+
constructor(modulePath: string);
|
|
4
|
+
}
|
|
5
|
+
export declare class MalformedModuleError extends Error {
|
|
6
|
+
readonly modulePath: string;
|
|
7
|
+
readonly reason: string;
|
|
8
|
+
constructor(modulePath: string, reason: string);
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/bundler/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,mBAAoB,SAAQ,KAAK;aAChB,UAAU,EAAE,MAAM;gBAAlB,UAAU,EAAE,MAAM;CAI/C;AAED,qBAAa,oBAAqB,SAAQ,KAAK;aAE3B,UAAU,EAAE,MAAM;aAClB,MAAM,EAAE,MAAM;gBADd,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM;CAKjC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export class ModuleNotFoundError extends Error {
|
|
2
|
+
modulePath;
|
|
3
|
+
constructor(modulePath) {
|
|
4
|
+
super(`Module ${modulePath} not found`);
|
|
5
|
+
this.modulePath = modulePath;
|
|
6
|
+
this.name = 'ModuleNotFoundError';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export class MalformedModuleError extends Error {
|
|
10
|
+
modulePath;
|
|
11
|
+
reason;
|
|
12
|
+
constructor(modulePath, reason) {
|
|
13
|
+
super(`Module ${modulePath} is malformed: ${reason}`);
|
|
14
|
+
this.modulePath = modulePath;
|
|
15
|
+
this.reason = reason;
|
|
16
|
+
this.name = 'MalformedModuleError';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evaluate.d.ts","sourceRoot":"","sources":["../../src/bundler/evaluate.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,cAAc,GAAI,UAAU,MAAM,EAAE,YAAY,MAAM,KAAG,IAqBrE,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { MalformedModuleError } from './errors.js';
|
|
2
|
+
import { EnvironmentError } from '../errors.js';
|
|
3
|
+
export const evaluateModule = (moduleJs, modulePath) => {
|
|
4
|
+
const __rMatch = moduleJs.match(/__r\((\d+)\)/);
|
|
5
|
+
if (!__rMatch) {
|
|
6
|
+
throw new MalformedModuleError(modulePath, 'No __r function found');
|
|
7
|
+
}
|
|
8
|
+
const __rParam = __rMatch[1];
|
|
9
|
+
if (!__rParam) {
|
|
10
|
+
throw new MalformedModuleError(modulePath, 'No __r parameter found');
|
|
11
|
+
}
|
|
12
|
+
// eslint-disable-next-line no-eval
|
|
13
|
+
eval(moduleJs);
|
|
14
|
+
if (!__r) {
|
|
15
|
+
throw new EnvironmentError('module evaluation', '__r is not defined');
|
|
16
|
+
}
|
|
17
|
+
__r(Number(__rParam));
|
|
18
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bundler/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/client/factory.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,SAAS,2EA2CrB,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { getBridgeClient } from '@react-native-harness/bridge/client';
|
|
2
|
+
import { store } from '../ui/state.js';
|
|
3
|
+
import { getTestRunner } from '../runner/index.js';
|
|
4
|
+
import { getTestCollector } from '../collector/index.js';
|
|
5
|
+
import { combineEventEmitters } from '../utils/emitter.js';
|
|
6
|
+
import { getWSServer } from './getWSServer.js';
|
|
7
|
+
import { fetchModule, evaluateModule } from '../bundler/index.js';
|
|
8
|
+
export const getClient = async () => {
|
|
9
|
+
const client = await getBridgeClient(getWSServer(), {
|
|
10
|
+
runTests: async () => {
|
|
11
|
+
throw new Error('Not implemented');
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
client.rpc.$functions.runTests = async (path) => {
|
|
15
|
+
if (store.getState().status === 'running') {
|
|
16
|
+
throw new Error('Already running tests');
|
|
17
|
+
}
|
|
18
|
+
store.getState().setStatus('running');
|
|
19
|
+
let collector = null;
|
|
20
|
+
let runner = null;
|
|
21
|
+
let events = null;
|
|
22
|
+
try {
|
|
23
|
+
collector = getTestCollector();
|
|
24
|
+
runner = getTestRunner();
|
|
25
|
+
events = combineEventEmitters(collector.events, runner.events);
|
|
26
|
+
events.addListener((event) => {
|
|
27
|
+
client.rpc.emitEvent(event.type, event);
|
|
28
|
+
});
|
|
29
|
+
const moduleJs = await fetchModule(path);
|
|
30
|
+
const collectionResult = await collector.collect(() => evaluateModule(moduleJs, path), path);
|
|
31
|
+
return await runner.run(collectionResult.testSuite, path);
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
collector?.dispose();
|
|
35
|
+
runner?.dispose();
|
|
36
|
+
events?.clearAllListeners();
|
|
37
|
+
store.getState().setStatus('idle');
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
return client;
|
|
41
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getDeviceDescriptor.d.ts","sourceRoot":"","sources":["../../src/client/getDeviceDescriptor.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,EAAE,KAAK,GAAG,SAAS,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,eAAO,MAAM,mBAAmB,QAAO,gBAoBtC,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
export const getDeviceDescriptor = () => {
|
|
3
|
+
if (Platform.OS === 'ios') {
|
|
4
|
+
return {
|
|
5
|
+
platform: 'ios',
|
|
6
|
+
manufacturer: 'Apple',
|
|
7
|
+
model: 'Unknown',
|
|
8
|
+
osVersion: Platform.constants.osVersion,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
if (Platform.OS === 'android') {
|
|
12
|
+
return {
|
|
13
|
+
platform: 'android',
|
|
14
|
+
manufacturer: Platform.constants.Manufacturer,
|
|
15
|
+
model: Platform.constants.Model,
|
|
16
|
+
osVersion: Platform.constants.Release,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
throw new Error('Unsupported platform');
|
|
20
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getWSServer.d.ts","sourceRoot":"","sources":["../../src/client/getWSServer.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,WAAW,QAAO,MAK9B,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { getDevServerUrl } from '../utils/dev-server.js';
|
|
2
|
+
import { WS_SERVER_PORT } from '../constants.js';
|
|
3
|
+
export const getWSServer = () => {
|
|
4
|
+
const devServerUrl = getDevServerUrl();
|
|
5
|
+
const hostname = devServerUrl.split('://')[1].split(':')[0];
|
|
6
|
+
return `ws://${hostname}:${WS_SERVER_PORT}`;
|
|
7
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { getClient } from './factory.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type TestErrorCode = 'CONTEXT_NOT_INITIALIZED' | 'OUTSIDE_DESCRIBE_BLOCK' | 'INVALID_TEST_NAME' | 'DUPLICATE_TEST_NAME' | 'INVALID_FUNCTION';
|
|
2
|
+
export declare class TestError extends Error {
|
|
3
|
+
code: TestErrorCode;
|
|
4
|
+
functionName: string;
|
|
5
|
+
context?: Record<string, unknown> | undefined;
|
|
6
|
+
constructor(code: TestErrorCode, functionName: string, context?: Record<string, unknown> | undefined);
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/collector/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GACrB,yBAAyB,GACzB,wBAAwB,GACxB,mBAAmB,GACnB,qBAAqB,GACrB,kBAAkB,CAAC;AAEvB,qBAAa,SAAU,SAAQ,KAAK;IAEzB,IAAI,EAAE,aAAa;IACnB,YAAY,EAAE,MAAM;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;gBAFjC,IAAI,EAAE,aAAa,EACnB,YAAY,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,YAAA;CAe3C"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export class TestError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
functionName;
|
|
4
|
+
context;
|
|
5
|
+
constructor(code, functionName, context) {
|
|
6
|
+
const baseMessages = {
|
|
7
|
+
CONTEXT_NOT_INITIALIZED: 'Test context not initialized. Call collectTests() first.',
|
|
8
|
+
OUTSIDE_DESCRIBE_BLOCK: `${functionName}() must be called within a describe() block`,
|
|
9
|
+
INVALID_TEST_NAME: `${functionName}() requires a non-empty string name`,
|
|
10
|
+
DUPLICATE_TEST_NAME: `Duplicate test name "${context?.name}" in suite "${context?.suiteName}"`,
|
|
11
|
+
INVALID_FUNCTION: `${functionName}() requires a function as the second parameter`,
|
|
12
|
+
};
|
|
13
|
+
const message = baseMessages[code] || `Unknown error in ${functionName}()`;
|
|
14
|
+
super(message);
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.functionName = functionName;
|
|
17
|
+
this.context = context;
|
|
18
|
+
this.name = 'TestError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/collector/factory.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,eAAO,MAAM,gBAAgB,QAAO,aA0BnC,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { getEmitter } from '../utils/emitter.js';
|
|
2
|
+
import { collectTests } from './functions.js';
|
|
3
|
+
export const getTestCollector = () => {
|
|
4
|
+
const events = getEmitter();
|
|
5
|
+
return {
|
|
6
|
+
events,
|
|
7
|
+
collect: async (fn, testFilePath) => {
|
|
8
|
+
const start = Date.now();
|
|
9
|
+
events.emit({
|
|
10
|
+
type: 'collection-started',
|
|
11
|
+
file: testFilePath,
|
|
12
|
+
});
|
|
13
|
+
const result = await collectTests(fn);
|
|
14
|
+
events.emit({
|
|
15
|
+
type: 'collection-finished',
|
|
16
|
+
file: testFilePath,
|
|
17
|
+
duration: Date.now() - start,
|
|
18
|
+
});
|
|
19
|
+
return result;
|
|
20
|
+
},
|
|
21
|
+
dispose: () => {
|
|
22
|
+
events.clearAllListeners();
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { CollectionResult } from '@react-native-harness/bridge';
|
|
2
|
+
import type { TestFn } from './types.js';
|
|
3
|
+
export declare const describe: ((name: string, fn: () => void) => void) & {
|
|
4
|
+
skip: (name: string, fn: () => void) => void;
|
|
5
|
+
only: (name: string, fn: () => void) => void;
|
|
6
|
+
};
|
|
7
|
+
export declare const test: ((name: string, fn: TestFn) => void) & {
|
|
8
|
+
skip: (name: string, fn: TestFn) => void;
|
|
9
|
+
only: (name: string, fn: TestFn) => void;
|
|
10
|
+
todo: (name: string) => void;
|
|
11
|
+
};
|
|
12
|
+
export declare const it: ((name: string, fn: TestFn) => void) & {
|
|
13
|
+
skip: (name: string, fn: TestFn) => void;
|
|
14
|
+
only: (name: string, fn: TestFn) => void;
|
|
15
|
+
todo: (name: string) => void;
|
|
16
|
+
};
|
|
17
|
+
export declare function beforeAll(fn: TestFn): void;
|
|
18
|
+
export declare function afterAll(fn: TestFn): void;
|
|
19
|
+
export declare function beforeEach(fn: TestFn): void;
|
|
20
|
+
export declare function afterEach(fn: TestFn): void;
|
|
21
|
+
export declare const collectTests: (fn: () => void) => CollectionResult;
|
|
22
|
+
//# sourceMappingURL=functions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"functions.d.ts","sourceRoot":"","sources":["../../src/collector/functions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,gBAAgB,EACjB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AA8KzC,eAAO,MAAM,QAAQ,UACZ,MAAM,MAAM,MAAM,IAAI;iBAsBd,MAAM,MAAM,MAAM,IAAI;iBAqBtB,MAAM,MAAM,MAAM,IAAI;CAsBtC,CAAC;AAEF,eAAO,MAAM,IAAI,UACR,MAAM,MAAM,MAAM;iBAaV,MAAM,MAAM,MAAM;iBAWlB,MAAM,MAAM,MAAM;iBAWlB,MAAM;CAiBtB,CAAC;AAEF,eAAO,MAAM,EAAE,UAtDN,MAAM,MAAM,MAAM;iBAaV,MAAM,MAAM,MAAM;iBAWlB,MAAM,MAAM,MAAM;iBAWlB,MAAM;CAmBD,CAAC;AAEvB,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,QAQnC;AAED,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,QAQlC;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,QAQpC;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,QAQnC;AAgBD,eAAO,MAAM,YAAY,GAAI,IAAI,MAAM,IAAI,KAAG,gBAiB7C,CAAC"}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { TestError } from './errors.js';
|
|
2
|
+
import { validateTestName, validateTestFunction } from './validation.js';
|
|
3
|
+
// Computation functions for two-phase approach
|
|
4
|
+
const computeTestStatus = (test, suiteContext) => {
|
|
5
|
+
if (test.options.todo)
|
|
6
|
+
return 'todo';
|
|
7
|
+
if (test.options.skip)
|
|
8
|
+
return 'skipped';
|
|
9
|
+
if (test.options.only)
|
|
10
|
+
return 'active';
|
|
11
|
+
if (suiteContext.hasFocusedTests)
|
|
12
|
+
return 'skipped';
|
|
13
|
+
return 'active';
|
|
14
|
+
};
|
|
15
|
+
const computeSuiteStatus = (suite, parentContext) => {
|
|
16
|
+
if (suite.options.skip)
|
|
17
|
+
return 'skipped';
|
|
18
|
+
if (suite.options.only)
|
|
19
|
+
return 'active';
|
|
20
|
+
if (parentContext.hasFocusedChildren)
|
|
21
|
+
return 'skipped';
|
|
22
|
+
return 'active';
|
|
23
|
+
};
|
|
24
|
+
const convertRawTestCaseToTestCase = (rawTest, suiteContext) => {
|
|
25
|
+
return {
|
|
26
|
+
name: rawTest.name,
|
|
27
|
+
fn: rawTest.fn,
|
|
28
|
+
status: computeTestStatus(rawTest, suiteContext),
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
const convertRawTestSuiteToTestSuite = (rawSuite, parentContext = {
|
|
32
|
+
hasFocusedChildren: false,
|
|
33
|
+
}, parentSuite) => {
|
|
34
|
+
// Validate duplicate test names within this suite
|
|
35
|
+
const testNames = new Set();
|
|
36
|
+
for (const test of rawSuite.tests) {
|
|
37
|
+
if (testNames.has(test.name)) {
|
|
38
|
+
throw new TestError('DUPLICATE_TEST_NAME', 'test', {
|
|
39
|
+
name: test.name,
|
|
40
|
+
suiteName: rawSuite.name,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
testNames.add(test.name);
|
|
44
|
+
}
|
|
45
|
+
// Check if this suite has focused tests
|
|
46
|
+
const hasFocusedTests = rawSuite.tests.some((test) => test.options.only);
|
|
47
|
+
// Check if this suite has focused children
|
|
48
|
+
const hasFocusedChildren = rawSuite.suites.some((suite) => suite.options.only || suite.tests.some((test) => test.options.only));
|
|
49
|
+
// Convert tests
|
|
50
|
+
const tests = rawSuite.tests.map((test) => convertRawTestCaseToTestCase(test, { hasFocusedTests }));
|
|
51
|
+
// Create the suite first so we can reference it when converting children
|
|
52
|
+
const suite = {
|
|
53
|
+
name: rawSuite.name,
|
|
54
|
+
tests,
|
|
55
|
+
suites: [],
|
|
56
|
+
parent: parentSuite,
|
|
57
|
+
beforeAll: rawSuite.hooks.beforeAll,
|
|
58
|
+
afterAll: rawSuite.hooks.afterAll,
|
|
59
|
+
beforeEach: rawSuite.hooks.beforeEach,
|
|
60
|
+
afterEach: rawSuite.hooks.afterEach,
|
|
61
|
+
status: computeSuiteStatus(rawSuite, parentContext),
|
|
62
|
+
_hasFocused: hasFocusedTests || hasFocusedChildren || rawSuite.options.only,
|
|
63
|
+
};
|
|
64
|
+
// Convert child suites with this suite as their parent
|
|
65
|
+
suite.suites = rawSuite.suites.map((childSuite) => convertRawTestSuiteToTestSuite(childSuite, { hasFocusedChildren }, suite));
|
|
66
|
+
return suite;
|
|
67
|
+
};
|
|
68
|
+
let currentContext = null;
|
|
69
|
+
const clearState = () => {
|
|
70
|
+
const rootSuite = createRawSuite('root');
|
|
71
|
+
return {
|
|
72
|
+
rootSuite,
|
|
73
|
+
currentSuite: rootSuite,
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
const getCurrentSuite = () => {
|
|
77
|
+
if (!currentContext) {
|
|
78
|
+
throw new TestError('CONTEXT_NOT_INITIALIZED', 'getCurrentSuite');
|
|
79
|
+
}
|
|
80
|
+
return currentContext.currentSuite;
|
|
81
|
+
};
|
|
82
|
+
const getRootSuite = () => {
|
|
83
|
+
if (!currentContext) {
|
|
84
|
+
throw new TestError('CONTEXT_NOT_INITIALIZED', 'getRootSuite');
|
|
85
|
+
}
|
|
86
|
+
return currentContext.rootSuite;
|
|
87
|
+
};
|
|
88
|
+
const setCurrentSuite = (suite) => {
|
|
89
|
+
if (!currentContext) {
|
|
90
|
+
throw new TestError('CONTEXT_NOT_INITIALIZED', 'setCurrentSuite');
|
|
91
|
+
}
|
|
92
|
+
currentContext.currentSuite = suite;
|
|
93
|
+
};
|
|
94
|
+
const createRawSuite = (name, options = {}) => {
|
|
95
|
+
return {
|
|
96
|
+
name,
|
|
97
|
+
tests: [],
|
|
98
|
+
suites: [],
|
|
99
|
+
hooks: {
|
|
100
|
+
beforeAll: [],
|
|
101
|
+
afterAll: [],
|
|
102
|
+
beforeEach: [],
|
|
103
|
+
afterEach: [],
|
|
104
|
+
},
|
|
105
|
+
options,
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
export const describe = Object.assign((name, fn) => {
|
|
109
|
+
validateTestName(name, 'describe');
|
|
110
|
+
validateTestFunction(fn, 'describe');
|
|
111
|
+
const suite = createRawSuite(name);
|
|
112
|
+
const previousSuite = getCurrentSuite();
|
|
113
|
+
setCurrentSuite(suite);
|
|
114
|
+
try {
|
|
115
|
+
fn();
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
setCurrentSuite(previousSuite);
|
|
119
|
+
}
|
|
120
|
+
// Add the suite to its parent
|
|
121
|
+
if (previousSuite) {
|
|
122
|
+
previousSuite.suites.push(suite);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
getRootSuite().suites.push(suite);
|
|
126
|
+
}
|
|
127
|
+
}, {
|
|
128
|
+
skip: (name, fn) => {
|
|
129
|
+
validateTestName(name, 'describe.skip');
|
|
130
|
+
validateTestFunction(fn, 'describe.skip');
|
|
131
|
+
const suite = createRawSuite(name, { skip: true });
|
|
132
|
+
const previousSuite = getCurrentSuite();
|
|
133
|
+
setCurrentSuite(suite);
|
|
134
|
+
try {
|
|
135
|
+
fn();
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
setCurrentSuite(previousSuite);
|
|
139
|
+
}
|
|
140
|
+
// Add the suite to its parent
|
|
141
|
+
if (previousSuite) {
|
|
142
|
+
previousSuite.suites.push(suite);
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
getRootSuite().suites.push(suite);
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
only: (name, fn) => {
|
|
149
|
+
validateTestName(name, 'describe.only');
|
|
150
|
+
validateTestFunction(fn, 'describe.only');
|
|
151
|
+
const suite = createRawSuite(name, { only: true });
|
|
152
|
+
const previousSuite = getCurrentSuite();
|
|
153
|
+
setCurrentSuite(suite);
|
|
154
|
+
try {
|
|
155
|
+
fn();
|
|
156
|
+
}
|
|
157
|
+
finally {
|
|
158
|
+
setCurrentSuite(previousSuite);
|
|
159
|
+
}
|
|
160
|
+
// Add the suite to its parent
|
|
161
|
+
if (previousSuite) {
|
|
162
|
+
previousSuite.suites.push(suite);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
getRootSuite().suites.push(suite);
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
export const test = Object.assign((name, fn) => {
|
|
170
|
+
validateTestName(name, 'test');
|
|
171
|
+
validateTestFunction(fn, 'test');
|
|
172
|
+
const currentSuite = getCurrentSuite();
|
|
173
|
+
if (!currentSuite) {
|
|
174
|
+
throw new TestError('OUTSIDE_DESCRIBE_BLOCK', 'test');
|
|
175
|
+
}
|
|
176
|
+
// Add test with default options
|
|
177
|
+
currentSuite.tests.push({ name, fn, options: {} });
|
|
178
|
+
}, {
|
|
179
|
+
skip: (name, fn) => {
|
|
180
|
+
validateTestName(name, 'test.skip');
|
|
181
|
+
validateTestFunction(fn, 'test.skip');
|
|
182
|
+
const currentSuite = getCurrentSuite();
|
|
183
|
+
if (!currentSuite) {
|
|
184
|
+
throw new TestError('OUTSIDE_DESCRIBE_BLOCK', 'test.skip');
|
|
185
|
+
}
|
|
186
|
+
currentSuite.tests.push({ name, fn, options: { skip: true } });
|
|
187
|
+
},
|
|
188
|
+
only: (name, fn) => {
|
|
189
|
+
validateTestName(name, 'test.only');
|
|
190
|
+
validateTestFunction(fn, 'test.only');
|
|
191
|
+
const currentSuite = getCurrentSuite();
|
|
192
|
+
if (!currentSuite) {
|
|
193
|
+
throw new TestError('OUTSIDE_DESCRIBE_BLOCK', 'test.only');
|
|
194
|
+
}
|
|
195
|
+
currentSuite.tests.push({ name, fn, options: { only: true } });
|
|
196
|
+
},
|
|
197
|
+
todo: (name) => {
|
|
198
|
+
validateTestName(name, 'test.todo');
|
|
199
|
+
const currentSuite = getCurrentSuite();
|
|
200
|
+
if (!currentSuite) {
|
|
201
|
+
throw new TestError('OUTSIDE_DESCRIBE_BLOCK', 'test.todo');
|
|
202
|
+
}
|
|
203
|
+
currentSuite.tests.push({
|
|
204
|
+
name,
|
|
205
|
+
fn: () => {
|
|
206
|
+
// Empty function for todo tests
|
|
207
|
+
},
|
|
208
|
+
options: { todo: true },
|
|
209
|
+
});
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
export const it = test;
|
|
213
|
+
export function beforeAll(fn) {
|
|
214
|
+
validateTestFunction(fn, 'beforeAll');
|
|
215
|
+
const currentSuite = getCurrentSuite();
|
|
216
|
+
if (!currentSuite) {
|
|
217
|
+
throw new TestError('OUTSIDE_DESCRIBE_BLOCK', 'beforeAll');
|
|
218
|
+
}
|
|
219
|
+
currentSuite.hooks.beforeAll.push(fn);
|
|
220
|
+
}
|
|
221
|
+
export function afterAll(fn) {
|
|
222
|
+
validateTestFunction(fn, 'afterAll');
|
|
223
|
+
const currentSuite = getCurrentSuite();
|
|
224
|
+
if (!currentSuite) {
|
|
225
|
+
throw new TestError('OUTSIDE_DESCRIBE_BLOCK', 'afterAll');
|
|
226
|
+
}
|
|
227
|
+
currentSuite.hooks.afterAll.push(fn);
|
|
228
|
+
}
|
|
229
|
+
export function beforeEach(fn) {
|
|
230
|
+
validateTestFunction(fn, 'beforeEach');
|
|
231
|
+
const currentSuite = getCurrentSuite();
|
|
232
|
+
if (!currentSuite) {
|
|
233
|
+
throw new TestError('OUTSIDE_DESCRIBE_BLOCK', 'beforeEach');
|
|
234
|
+
}
|
|
235
|
+
currentSuite.hooks.beforeEach.push(fn);
|
|
236
|
+
}
|
|
237
|
+
export function afterEach(fn) {
|
|
238
|
+
validateTestFunction(fn, 'afterEach');
|
|
239
|
+
const currentSuite = getCurrentSuite();
|
|
240
|
+
if (!currentSuite) {
|
|
241
|
+
throw new TestError('OUTSIDE_DESCRIBE_BLOCK', 'afterEach');
|
|
242
|
+
}
|
|
243
|
+
currentSuite.hooks.afterEach.push(fn);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Recursively counts the total number of tests that will actually be executed.
|
|
247
|
+
* Only counts active tests since skipped and todo tests are not executed.
|
|
248
|
+
*/
|
|
249
|
+
const countTests = (suite) => {
|
|
250
|
+
let count = suite.tests.filter((test) => test.status === 'active').length;
|
|
251
|
+
for (const childSuite of suite.suites) {
|
|
252
|
+
count += countTests(childSuite);
|
|
253
|
+
}
|
|
254
|
+
return count;
|
|
255
|
+
};
|
|
256
|
+
export const collectTests = (fn) => {
|
|
257
|
+
currentContext = clearState();
|
|
258
|
+
try {
|
|
259
|
+
fn();
|
|
260
|
+
// Convert raw structure to final structure using computation phase
|
|
261
|
+
const testSuite = convertRawTestSuiteToTestSuite(getRootSuite());
|
|
262
|
+
const totalTests = countTests(testSuite);
|
|
263
|
+
return {
|
|
264
|
+
testSuite,
|
|
265
|
+
totalTests,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
finally {
|
|
269
|
+
currentContext = null;
|
|
270
|
+
}
|
|
271
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { describe, test, it, beforeAll, afterAll, beforeEach, afterEach, } from './functions.js';
|
|
2
|
+
export { TestError, type TestErrorCode } from './errors.js';
|
|
3
|
+
export type { TestCollector, TestCollectorEventsEmitter } from './types.js';
|
|
4
|
+
export { getTestCollector } from './factory.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/collector/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EACR,IAAI,EACJ,EAAE,EACF,SAAS,EACT,QAAQ,EACR,UAAU,EACV,SAAS,GACV,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5D,YAAY,EAAE,aAAa,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAC5E,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { EventEmitter } from '../utils/emitter.js';
|
|
2
|
+
import { TestCollectorEvents, CollectionResult } from '@react-native-harness/bridge';
|
|
3
|
+
export type TestFn = () => void | Promise<void>;
|
|
4
|
+
export type TestCollectorEventsEmitter = EventEmitter<TestCollectorEvents>;
|
|
5
|
+
export type TestCollector = {
|
|
6
|
+
events: TestCollectorEventsEmitter;
|
|
7
|
+
collect: (fn: () => void, testFilePath: string) => Promise<CollectionResult>;
|
|
8
|
+
dispose: () => void;
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/collector/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EACjB,MAAM,8BAA8B,CAAC;AAEtC,MAAM,MAAM,MAAM,GAAG,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAEhD,MAAM,MAAM,0BAA0B,GAAG,YAAY,CAAC,mBAAmB,CAAC,CAAC;AAE3E,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,EAAE,0BAA0B,CAAC;IACnC,OAAO,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC7E,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/collector/validation.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEpC,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,EAAE,cAAc,MAAM,KAAG,IAMrE,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAC/B,IAAI,MAAM,EACV,cAAc,MAAM,KACnB,IAMF,CAAC"}
|