-
-
Notifications
You must be signed in to change notification settings - Fork 10
Match RN Text accessibility/disabled behavior + add differential parity tests #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
869a808
test(parity): add differential parity test harness for Text/View
mfkrause 5765097
fix(text): match RN Text accessibility/disabled reconciliation
mfkrause 04a67a8
refactor(text): dedupe normalized-property predicate, tidy imports
mfkrause 04b66ae
perf(text): resolve the `accessible` default at build time
mfkrause 30e6e74
fix(text): don't drop the `accessible` default on styled Text
mfkrause b944338
test(parity): assert optimization intent and native host kind
mfkrause 472d15b
test(parity): convert file URLs to paths with fileURLToPath
mfkrause File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
39 changes: 39 additions & 0 deletions
39
packages/react-native-boost/src/plugin/__tests__/build-time-accessible-default.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| import { describe, it, expect } from 'vitest'; | ||
| import { transformSync, type TransformCaller } from '@babel/core'; | ||
| import boostPlugin from '../index'; | ||
|
|
||
| // Compile a bare `<Text>` (common path: no accessibility props) with the given Babel caller platform, | ||
| // mirroring how Metro invokes the plugin per platform bundle. | ||
| const compile = (platform?: string): string => | ||
| transformSync(`import { Text } from 'react-native';\nexport default () => <Text>hi</Text>;`, { | ||
| configFile: false, | ||
| babelrc: false, | ||
| filename: 'case.jsx', | ||
| caller: (platform ? { name: 'metro', platform } : { name: 'test' }) as TransformCaller, | ||
| plugins: ['@babel/plugin-syntax-jsx', [boostPlugin, { silent: true }]], | ||
| })!.code!; | ||
|
|
||
| describe('build-time `accessible` default', () => { | ||
| it('inlines accessible={true} for iOS without importing the runtime resolver', () => { | ||
| const code = compile('ios'); | ||
| expect(code).toContain('accessible={true}'); | ||
| expect(code).not.toContain('getDefaultTextAccessible'); | ||
| }); | ||
|
|
||
| it('inlines accessible={false} for Android without importing the runtime resolver', () => { | ||
| const code = compile('android'); | ||
| expect(code).toContain('accessible={false}'); | ||
| expect(code).not.toContain('getDefaultTextAccessible'); | ||
| }); | ||
|
|
||
| it('omits the accessible default entirely on web (default is undefined there)', () => { | ||
| const code = compile('web'); | ||
| expect(code).not.toContain('accessible'); | ||
| expect(code).not.toContain('getDefaultTextAccessible'); | ||
| }); | ||
|
|
||
| it('falls back to the runtime resolver when the platform is unknown', () => { | ||
| const code = compile(); | ||
| expect(code).toContain('accessible={_getDefaultTextAccessible()}'); | ||
| }); | ||
| }); |
2 changes: 2 additions & 0 deletions
2
packages/react-native-boost/src/plugin/__tests__/parity/__generated__/.gitignore
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
52 changes: 52 additions & 0 deletions
52
packages/react-native-boost/src/plugin/__tests__/parity/boost.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| import { writeFileSync } from 'node:fs'; | ||
| import { fileURLToPath } from 'node:url'; | ||
| import { transformSync, type TransformCaller } from '@babel/core'; | ||
| import * as React from 'react'; | ||
| import boostPlugin from '../../index'; // src/plugin/index.ts — the full Boost plugin | ||
| import { RUNTIME_MODULE_NAME } from '../../utils/constants'; | ||
| import { renderAndCapture } from './capture'; | ||
| import { setPlatformOS } from './mocks/Platform'; | ||
|
|
||
| let counter = 0; | ||
|
|
||
| interface BoostBailed { | ||
| optimized: false; | ||
| } | ||
|
|
||
| interface BoostOptimized { | ||
| optimized: true; | ||
| which?: string; | ||
| props: Record<string, unknown>; | ||
| } | ||
|
|
||
| /** | ||
| * Transform a JSX body with the full Boost plugin and render the result with the REAL runtime | ||
| * helpers (the components are mocked to the shared capturers by the test). Returns | ||
| * `{ optimized: false }` when Boost bailed — it then defers to the wrapper, so the case is | ||
| * equivalent by construction and the test skips it. | ||
| * | ||
| * Compiled and rendered under the given platform (like {@link captureWrapper}): the caller `platform` | ||
| * mirrors how Metro builds per platform, so the plugin inlines build-time defaults (e.g. `accessible`) | ||
| * exactly as it would in production, and the runtime helpers read the matching `Platform.OS` at render. | ||
| */ | ||
| export async function captureBoost(os: 'ios' | 'android', jsxBody: string): Promise<BoostBailed | BoostOptimized> { | ||
| setPlatformOS(os); | ||
| const source = `import { Text, View } from 'react-native';\nexport default function Case(){ return ${jsxBody}; }`; | ||
| const out = transformSync(source, { | ||
| configFile: false, | ||
| babelrc: false, | ||
| filename: 'boost-case.jsx', | ||
| caller: { name: 'metro', platform: os } as TransformCaller, | ||
| presets: [['@babel/preset-react', { runtime: 'automatic' }]], | ||
| plugins: [[boostPlugin, { silent: true }]], | ||
| }); | ||
| const code = out!.code!; | ||
| // Single-element snippet: the runtime import is injected iff that element was optimized. | ||
| if (!code.includes(RUNTIME_MODULE_NAME)) return { optimized: false }; | ||
|
|
||
| const file = fileURLToPath(new URL(`./__generated__/boost-${counter++}.js`, import.meta.url)); | ||
| writeFileSync(file, code); | ||
| const mod = await import(/* @vite-ignore */ file); | ||
| const { which, props } = renderAndCapture(React.createElement(mod.default)); | ||
| return { optimized: true, which, props }; | ||
| } |
44 changes: 44 additions & 0 deletions
44
packages/react-native-boost/src/plugin/__tests__/parity/capture.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| import * as React from 'react'; | ||
| import { renderToStaticMarkup } from 'react-dom/server'; | ||
|
|
||
| /** | ||
| * The single shared sink both the wrapper and Boost sides funnel their native props into. Whichever | ||
| * native host capturer renders last writes here, so {@link renderAndCapture} resets it before every | ||
| * render and reads it immediately after. | ||
| */ | ||
| interface Sink { | ||
| props?: Record<string, unknown>; | ||
| which?: string; | ||
| } | ||
|
|
||
| export const sink: Sink = {}; | ||
|
|
||
| /** | ||
| * Builds a prop-recording function component standing in for a native host. It records the exact | ||
| * prop bag it receives into the shared {@link sink} and renders its children so nested hosts (e.g. | ||
| * a `NativeVirtualText` inside a `NativeText`) are captured too. | ||
| * | ||
| * It must be a function component, not a host string token (e.g. `'RCTText'`): the DOM serializer | ||
| * would lowercase attribute names and stringify values, destroying the fidelity we compare on. | ||
| */ | ||
| const makeCapturer = | ||
| (which: string): React.FC<Record<string, unknown>> => | ||
| (props) => { | ||
| sink.props = props; | ||
| sink.which = which; | ||
| return (props.children as React.ReactNode) ?? null; | ||
| }; | ||
|
|
||
| export const NativeTextCapturer = makeCapturer('NativeText'); | ||
| export const NativeVirtualTextCapturer = makeCapturer('NativeVirtualText'); | ||
| export const NativeViewCapturer = makeCapturer('NativeView'); | ||
|
|
||
| /** Render an element and return the props its single native host received (children stripped). */ | ||
| export function renderAndCapture(element: React.ReactElement): { which?: string; props: Record<string, unknown> } { | ||
| sink.props = undefined; | ||
| sink.which = undefined; | ||
| renderToStaticMarkup(element); | ||
| const captured: Record<string, unknown> = sink.props ?? {}; | ||
| const { children: _children, ...rest } = captured; | ||
| return { which: sink.which, props: rest }; | ||
| } |
18 changes: 18 additions & 0 deletions
18
packages/react-native-boost/src/plugin/__tests__/parity/mocks/Platform.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| // Switchable Platform mock. `Text.js` reads `Platform.OS` / `Platform.select` at render time, so the | ||
| // parity test flips the OS via `setPlatformOS` before each wrapper render. | ||
| let os: 'ios' | 'android' = 'ios'; | ||
|
|
||
| export function setPlatformOS(value: 'ios' | 'android') { | ||
| os = value; | ||
| } | ||
|
|
||
| const Platform = { | ||
| get OS() { | ||
| return os; | ||
| }, | ||
| select<T>(spec: Record<string, T>): T | undefined { | ||
| return os in spec ? spec[os] : spec.default; | ||
| }, | ||
| }; | ||
|
|
||
| export default Platform; |
4 changes: 4 additions & 0 deletions
4
packages/react-native-boost/src/plugin/__tests__/parity/mocks/PressabilityDebug.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| // `Text.js` imports `PressabilityDebug` for its dev-only debug overlay. Disable it. | ||
| export function isEnabled() { | ||
| return false; | ||
| } |
4 changes: 4 additions & 0 deletions
4
packages/react-native-boost/src/plugin/__tests__/parity/mocks/ReactNativeFeatureFlags.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| // `Text.js` gates its implementation on this JS feature flag (native default: false), which selects | ||
| // the legacy code path whose `Platform.select` `accessible` logic we compare against. If a future | ||
| // RN `Text.js` consults another flag, the import fails loud at module load — add it here. | ||
| export const reduceDefaultPropsInText = () => false; |
5 changes: 5 additions & 0 deletions
5
packages/react-native-boost/src/plugin/__tests__/parity/mocks/TextNativeComponent.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| import { NativeTextCapturer, NativeVirtualTextCapturer } from '../capture'; | ||
|
|
||
| // `Text.js` renders `NativeText` for a root text and `NativeVirtualText` for nested text. | ||
| export const NativeText = NativeTextCapturer; | ||
| export const NativeVirtualText = NativeVirtualTextCapturer; |
4 changes: 4 additions & 0 deletions
4
packages/react-native-boost/src/plugin/__tests__/parity/mocks/ViewNativeComponent.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| import { NativeViewCapturer } from '../capture'; | ||
|
|
||
| // `View.js` renders `ViewNativeComponent`'s default export; point it at the shared capturer. | ||
| export default NativeViewCapturer; |
6 changes: 6 additions & 0 deletions
6
packages/react-native-boost/src/plugin/__tests__/parity/mocks/processColor.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| // Identity stand-in for `react-native/Libraries/StyleSheet/processColor`. Its real implementation | ||
| // does a CJS `require('../Utilities/Platform')` that escapes vite resolution and pulls raw Flow into | ||
| // Node. We don't exercise `selectionColor`, so cutting that subtree here is safe. | ||
| export default function processColor(color: unknown) { | ||
| return color; | ||
| } |
13 changes: 13 additions & 0 deletions
13
packages/react-native-boost/src/plugin/__tests__/parity/mocks/react-native.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import Platform from './Platform'; | ||
| import { NativeTextCapturer, NativeViewCapturer } from '../capture'; | ||
|
|
||
| // Bare-specifier `react-native` surface for the Boost side. It backs the runtime index's | ||
| // `import { StyleSheet } from 'react-native'` plus the dead leftover `import { Text, View }` the | ||
| // plugin leaves in generated code. The host components resolve to the shared capturers so any path | ||
| // that bottoms out here is still captured. | ||
| export { Platform }; | ||
| export const unstable_NativeText = NativeTextCapturer; | ||
| export const unstable_NativeView = NativeViewCapturer; | ||
| export const Text = NativeTextCapturer; | ||
| export const View = NativeViewCapturer; | ||
| export const StyleSheet = { flatten: <T>(style: T) => style }; |
5 changes: 5 additions & 0 deletions
5
packages/react-native-boost/src/plugin/__tests__/parity/mocks/usePressability.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| // `Text.js` calls `usePressability` to derive event handlers. We don't compare press handlers (they | ||
| // are stripped by `normalize`), so a no-op hook returning no handlers is sufficient. | ||
| export default function usePressability() { | ||
| return {}; | ||
| } |
75 changes: 75 additions & 0 deletions
75
packages/react-native-boost/src/plugin/__tests__/parity/parity.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| import { describe, it, expect, vi } from 'vitest'; | ||
|
|
||
| // Mock the runtime's host COMPONENTS to the shared capturers, keeping the real runtime HELPERS | ||
| // (`processAccessibilityProps` / `processTextStyle`) under test. This is also what stops | ||
| // native-text.tsx / native-view.tsx from running their CJS `require('react-native')` (see §4.5 of | ||
| // the implementation plan), which would otherwise pull raw Flow source into node. | ||
| vi.mock('../../../runtime/components/native-text', async () => ({ | ||
| NativeText: (await import('./capture')).NativeTextCapturer, | ||
| })); | ||
| vi.mock('../../../runtime/components/native-view', async () => ({ | ||
| NativeView: (await import('./capture')).NativeViewCapturer, | ||
| })); | ||
|
|
||
| import { captureWrapper } from './wrapper'; | ||
| import { captureBoost } from './boost'; | ||
|
|
||
| const PLATFORMS = ['ios', 'android'] as const; | ||
|
|
||
| // `<Text>` cases use a string child so they render to NativeText (not NativeVirtualText). | ||
| const TEXT_CASES = [ | ||
| '<Text>hello</Text>', | ||
| '<Text aria-label="x">hello</Text>', | ||
| '<Text accessibilityLabel="x">hello</Text>', | ||
| '<Text accessible={false}>hello</Text>', | ||
| '<Text disabled={true}>hello</Text>', | ||
| '<Text accessibilityState={{ disabled: true }}>hello</Text>', | ||
| '<Text numberOfLines={2}>hello</Text>', | ||
| '<Text aria-busy={true}>hello</Text>', | ||
| '<Text style={{ color: "red" }}>hello</Text>', // styled, no a11y: `accessible` default must survive the style spread | ||
| '<Text style={{ color: "red" }} accessibilityLabel="x">hello</Text>', | ||
| ]; | ||
|
|
||
| const VIEW_CASES = [ | ||
| '<View testID="v" />', | ||
| '<View accessibilityRole="button" />', | ||
| '<View accessibilityValue={{ now: 5 }} />', | ||
| '<View pointerEvents="none" />', | ||
| '<View aria-label="x" />', // blacklisted → Boost bails → defers to wrapper | ||
| '<View tabIndex={0} />', // blacklisted → Boost bails → defers to wrapper | ||
| '<View style={{ width: 1 }} />', // blacklisted → Boost bails → defers to wrapper | ||
| ]; | ||
|
|
||
| // Cases Boost is expected to bail on (blacklisted props). Asserting the bail explicitly stops an | ||
| // unexpected bailout — a silent loss of optimization — from masquerading as a passing parity test. | ||
| const BAILED_VIEW_CASES = new Set([ | ||
| '<View aria-label="x" />', | ||
| '<View tabIndex={0} />', | ||
| '<View style={{ width: 1 }} />', | ||
| ]); | ||
|
|
||
| // Treat `undefined`-valued keys as absent and deep-clean nested objects (also drops function values | ||
| // such as event handlers) so the comparison is a clean structural prop-bag equality. | ||
| const normalize = (props: Record<string, unknown>) => JSON.parse(JSON.stringify(props)); | ||
|
|
||
| describe('differential parity', () => { | ||
| describe.each(PLATFORMS)('Platform.OS=%s', (os) => { | ||
| it.each(TEXT_CASES)('Text: %s', async (jsx) => { | ||
| const boost = await captureBoost(os, jsx); | ||
| expect(boost.optimized).toBe(true); // every Text case here is expected to optimize | ||
| if (!boost.optimized) return; | ||
| const wrapper = await captureWrapper(os, jsx); | ||
| expect(boost.which).toEqual(wrapper.which); // same native host kind | ||
| expect(normalize(boost.props)).toEqual(normalize(wrapper.props)); | ||
| }); | ||
|
|
||
| it.each(VIEW_CASES)('View: %s', async (jsx) => { | ||
| const boost = await captureBoost(os, jsx); | ||
| expect(boost.optimized).toBe(!BAILED_VIEW_CASES.has(jsx)); | ||
| if (!boost.optimized) return; // bailed → defers to the wrapper, equivalent by construction | ||
| const wrapper = await captureWrapper(os, jsx); | ||
| expect(boost.which).toEqual(wrapper.which); // same native host kind | ||
| expect(normalize(boost.props)).toEqual(normalize(wrapper.props)); | ||
| }); | ||
| }); | ||
| }); | ||
5 changes: 5 additions & 0 deletions
5
packages/react-native-boost/src/plugin/__tests__/parity/setup.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| // React Native source files reference these runtime globals at module-evaluation time. They are | ||
| // normally injected by the Metro/RN runtime; define them here so RN's `Text`/`View` modules load | ||
| // under vitest's plain `node` environment. | ||
| (globalThis as Record<string, unknown>).__DEV__ = false; | ||
| (globalThis as Record<string, unknown>).RN$Bridgeless = true; |
77 changes: 77 additions & 0 deletions
77
packages/react-native-boost/src/plugin/__tests__/parity/vitest.config.parity.mts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| import { createRequire } from 'node:module'; | ||
| import { dirname, join } from 'node:path'; | ||
| import { fileURLToPath } from 'node:url'; | ||
| import { transformSync } from '@babel/core'; | ||
| import { defineConfig } from 'vitest/config'; | ||
|
|
||
| const require = createRequire(import.meta.url); | ||
| // `fileURLToPath` (not `URL.pathname`) so paths survive percent-encodable characters (e.g. a space | ||
| // in a parent directory becomes `%20` in `.pathname`) and the Windows drive-letter prefix. | ||
| const u = (p: string) => fileURLToPath(new URL(p, import.meta.url)); | ||
|
|
||
| // React Native ships its own source as Flow `.js`. We only want to transform RN's own files. | ||
| const RN_SRC = /\/node_modules\/react-native\/(Libraries|src)\/.*\.js$/; | ||
| const rnDir = dirname(require.resolve('react-native/package.json')); | ||
|
|
||
| // Pin React + its JSX runtimes to the single copy `react-dom` resolves, so the wrapper's hooks and | ||
| // the renderer share one React instance (pnpm/hoisting can otherwise leave two copies side by side). | ||
| const reactRequire = createRequire(require.resolve('react-dom')); | ||
| const exactRedirects: Record<string, string> = { | ||
| 'react': reactRequire.resolve('react'), | ||
| 'react/jsx-runtime': reactRequire.resolve('react/jsx-runtime'), | ||
| 'react/jsx-dev-runtime': reactRequire.resolve('react/jsx-dev-runtime'), | ||
| 'react-native-boost/runtime': u('../../../runtime/index.ts'), | ||
| }; | ||
|
|
||
| // Leaf RN modules that touch native code or can't load under node. Matched by basename so both the | ||
| // `react-native/Libraries/...` import and the relative `./Foo` / `../Foo` imports are redirected. | ||
| const basenameRedirects: Array<[RegExp, string]> = [ | ||
| [/(^|[./])ViewNativeComponent$/, u('./mocks/ViewNativeComponent.ts')], | ||
| [/(^|[./])TextNativeComponent$/, u('./mocks/TextNativeComponent.ts')], | ||
| [/(^|[./])Platform$/, u('./mocks/Platform.ts')], | ||
| [/(^|[./])usePressability$/, u('./mocks/usePressability.ts')], | ||
| [/(^|[./])PressabilityDebug$/, u('./mocks/PressabilityDebug.ts')], | ||
| [/(^|[./])ReactNativeFeatureFlags$/, u('./mocks/ReactNativeFeatureFlags.ts')], | ||
| [/(^|[./])processColor$/, u('./mocks/processColor.ts')], | ||
| ]; | ||
|
|
||
| export default defineConfig({ | ||
| plugins: [ | ||
| { | ||
| name: 'rn-parity', | ||
| enforce: 'pre', // run before vite's built-in alias plugin | ||
| resolveId(source) { | ||
| if (exactRedirects[source]) return exactRedirects[source]; | ||
| for (const [re, target] of basenameRedirects) if (re.test(source)) return target; | ||
| // Wrapper side: pin deep RN source to the real files (RN's exports map omits the `.js`). | ||
| if (/^react-native\/(Libraries|src)\//.test(source)) { | ||
| return join(rnDir, source.slice('react-native/'.length) + '.js'); | ||
| } | ||
| return null; | ||
| }, | ||
| transform(code, id) { | ||
| if (!RN_SRC.test(id)) return null; | ||
| const out = transformSync(code, { | ||
| configFile: false, | ||
| babelrc: false, | ||
| filename: id, | ||
| presets: [[require.resolve('@react-native/babel-preset'), { disableImportExportTransform: true }]], | ||
| }); | ||
| // Keeping ESM (no CJS transform) ensures transitive imports resolve back through vite. | ||
| return out?.code ? { code: out.code, map: out.map } : null; | ||
| }, | ||
| }, | ||
| ], | ||
| // Regex-exact so it only matches the BARE `react-native` specifier (the runtime index's | ||
| // `import { StyleSheet } from 'react-native'` and the dead leftover import in generated Boost | ||
| // code). Deep `react-native/Libraries/...` paths are handled by the pre-plugin above. | ||
| resolve: { alias: [{ find: /^react-native$/, replacement: u('./mocks/react-native.ts') }] }, | ||
| test: { | ||
| name: 'parity', | ||
| globals: true, | ||
| environment: 'node', | ||
| setupFiles: [u('./setup.ts')], | ||
| include: [u('./parity.test.ts')], | ||
| server: { deps: { inline: [/react-native/] } }, // force RN source through the transform pipeline | ||
| }, | ||
| }); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.