Skip to content

Commit 16aafa6

Browse files
committed
Merge remote-tracking branch 'origin/master' into ai/alexjoeyyong/FSSDK-12958-local-holdout-flow-fix
2 parents 6f26cb1 + 5a388f5 commit 16aafa6

22 files changed

Lines changed: 9066 additions & 0 deletions

‎.github/workflows/javascript.yml‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,23 @@ jobs:
5757
npm install
5858
npm run test-commonjs
5959
60+
react_native_metro_test:
61+
runs-on: ubuntu-latest
62+
steps:
63+
- uses: actions/checkout@v3
64+
- name: Set up Node
65+
uses: actions/setup-node@v3
66+
with:
67+
node-version: 22
68+
cache-dependency-path: |
69+
./package-lock.json
70+
./tests/react-native-metro/fixtures/*/package-lock.json
71+
cache: 'npm'
72+
- name: Run React Native Metro tests
73+
run: |
74+
npm install
75+
npm run test-rn-metro
76+
6077
integration_tests:
6178
uses: optimizely/javascript-sdk/.github/workflows/integration_test.yml@master
6279
secrets:

‎.gitignore‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,7 @@ local.log
1818
**/*.gen.ts
1919

2020
.env
21+
22+
# React Native Metro tests: installed SDK and built bundles
23+
tests/react-native-metro/.sdk/
24+
tests/react-native-metro/.out/

‎package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
"test-umd": "node ./scripts/run-umd-tests.js",
6868
"test-umd-local": "USE_LOCAL_BROWSER=true node ./scripts/run-umd-tests.js",
6969
"test-umd-browserstack": "USE_LOCAL_BROWSER=false node ./scripts/run-umd-tests.js",
70+
"test-rn-metro": "node tests/react-native-metro/run.mjs",
7071
"test-mocha": "mocha -r lib/tests/uuid_cjs_shim.js -r ts-node/register -r tsconfig-paths/register -r lib/tests/exit_on_unhandled_rejection.js 'lib/**/*.tests.ts' 'lib/**/*.tests.js'",
7172
"test": "npm run test-mocha && npm run test-vitest",
7273
"posttest": "npm run lint",

‎tests/react-native-metro/README.md‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# React Native Metro tests
2+
3+
These tests check how the SDK behaves in React Native apps when optional dependencies are installed or missing: `fast-text-encoding`, `react-native-get-random-values` and `@react-native-async-storage/async-storage`. They run on a Metro version with the dependency-map bug (Metro 0.80, React Native 0.73) and on one with the fix (Metro 0.87, React Native 0.87). See `lib/import.react_native/README.md` for the bug and how the SDK's optional import shims avoid it.
4+
5+
Run them with:
6+
7+
```sh
8+
npm run test-rn-metro
9+
```
10+
11+
To test a specific package or a subset: `node tests/react-native-metro/run.mjs [--sdk <package.tgz>] [--fixture rn-0.73] [--case P2]`.
12+
13+
## How it works
14+
15+
For each case, the test app in `app/` is bundled with a real Metro and React Native's own Metro config, then run in Node:
16+
17+
1. **The SDK is packed and installed** into `.sdk/` the way an app installs it: `npm pack` (which builds it, as `npm publish` does), with its dependencies and without its optional peer dependencies.
18+
2. **The bundle is built** by `build.cjs` with the fixture's Metro, through `metro-config`'s `loadConfig` as the React Native CLI does. `metro.config.base.js` wraps the fixture's `@react-native/metro-config`, so transforms, inline requires, the serializer and React Native's JS polyfills (including `ErrorUtils`) match an app build. It changes two things:
19+
- **React Native's startup module is skipped** (`getModulesRunBeforeMainModule`), because it needs native code.
20+
- **`resolveRequest` decides which optional packages exist.** An installed package resolves to the real `fast-text-encoding` or to a stand-in from `stubs/`, for packages that need native code. A missing package fails to resolve, so Metro handles it as an optional dependency that isn't installed. netinfo always resolves to its stand-in, and bundling `react-native` itself fails the build.
21+
3. **The bundle runs in a fresh Node `vm` context.** It only has standard JS built-ins, so there is no `TextEncoder` or `crypto`, like Hermes before React Native 0.74. Cases with `nativeTextEncoder` add Node's `TextEncoder`.
22+
4. **The app records what happened and `run.mjs` checks it against the case's expectation.** The app records which implementation provides `TextEncoder` and `crypto.getRandomValues`, decisions, dispatched events, and any errors. Decisions must match `EXPECTED_DECISIONS` in `cases.mjs` exactly, so failed decisions don't pass. In failure cases, every recorded error must be the expected one.
23+
24+
Metro's `require` is part of the bundle (`metro-runtime`), so the bundle's modules are loaded by the same code as in an app. Two things make the run behave like an app:
25+
26+
- **`app/setup.js` installs a non-throwing `ErrorUtils` handler.** React Native's startup code replaces the polyfill's handler, which rethrows, with one that reports the error (red box or fatal) and returns. Without it, a failed `require` outside module loading would be rethrown to the caller instead of reported as fatal, and the test couldn't tell the two apart. The test treats a recorded fatal error as the red box.
27+
- **The SDK is loaded on a later tick (`app/index.js`),** when no module is loading, as app code does after startup.
28+
29+
## Files
30+
31+
| Path | Purpose |
32+
|---|---|
33+
| `run.mjs` | Packs and installs the SDK, builds the bundles, runs and checks every case, prints a results table |
34+
| `cases.mjs` | The cases and their expected results |
35+
| `build.cjs` | Builds one bundle with a fixture's Metro |
36+
| `metro.config.base.js` | Metro config shared by the fixtures |
37+
| `app/` | The test app: `index.js` (entry), `setup.js` (error handler, custom polyfills), `test.js` (the test), `datafile.json` |
38+
| `stubs/` | Stand-ins for netinfo, async-storage (v2 with `multiGet`, v3 with `getMany`) and `react-native-get-random-values` |
39+
| `fixtures/rn-0.73`, `fixtures/rn-0.87` | The Metro setup of each React Native version: `@react-native/metro-config`, `metro`, `metro-config`, `@babel/core`, `@babel/runtime` and `fast-text-encoding`, pinned by the committed lockfiles |
40+
41+
`.sdk/` and `.out/` (the bundles) are generated and not committed.
42+
43+
## Adding a case
44+
45+
Add an entry to `CASES` in `cases.mjs`. `modules` sets which optional packages are installed, `runtime` sets what the app does at runtime, and `expect` is one of:
46+
- `works`: which implementation of `TextEncoder` and `getRandomValues` the SDK must use;
47+
- `fatal`: the error reported while the SDK loads;
48+
- `caught`: the error thrown to the app's code.
49+
50+
The comment at the top of `cases.mjs` lists every option.
51+
52+
## Updating a fixture
53+
54+
Change the versions in the fixture's `package.json` to what that React Native version uses, then run `npm install` in the fixture folder and commit the updated lockfile. A fixture only needs React Native's Metro setup, not the `react-native` package.

0 commit comments

Comments
 (0)