Skip to content

fix(inject-testid): skip functions with 'widget'/'worklet' directives#127

Open
joernroeder wants to merge 1 commit into
ohah:mainfrom
joernroeder:fix/skip-widget-worklet-directives-in-inject-testid
Open

fix(inject-testid): skip functions with 'widget'/'worklet' directives#127
joernroeder wants to merge 1 commit into
ohah:mainfrom
joernroeder:fix/skip-widget-worklet-directives-in-inject-testid

Conversation

@joernroeder

Copy link
Copy Markdown

Summary

babel-plugin-inject-testid injects Component.displayName = "Component" onto every PascalCase function that returns JSX. When another Babel plugin in the pipeline rewrites such a function into a non-object, that assignment throws at runtime.

The concrete trigger is expo-widgets: its 'widget' directive (via babel-preset-expo) serializes the layout function into a string literal. The injected assignment then runs against a string at module-eval time:

TypeError: Cannot create property 'displayName' on string 'function(props){return _jsxs(VStack,{…})}'

Because it fires during module initialization, it aborts the module and can cascade into confusing downstream errors (in the original report, the app root failed with Cannot read property 'ErrorBoundary' of undefined).

Fix

Tag the component scope when the function body carries a 'widget' or 'worklet' directive, and skip both displayName and testID injection for those functions (their bodies are serialized/relocated, so injected testIDs wouldn't behave as expected anyway).

Applied consistently to all three implementations:

  • babel-plugin-inject-testid.cjs (runtime artifact)
  • src/babel-plugin-inject-testid.ts (babel-plugin source)
  • src/babel/inject-testid.ts (injectTestIds transformer)

Tests

Added regression tests to src/__tests__/babel-inject-testid.test.ts:

  • 'widget' directive function → no testID, no displayName
  • 'worklet' directive function → no testID, no displayName
  • a skipped directive function does not affect injection on sibling components

bun test src/__tests__/babel-inject-testid.test.ts → 18 pass. oxlint, oxfmt --check, and tsc --noEmit all clean.

Fixes #126

The testID/displayName injector assigns `Component.displayName = "..."` to
PascalCase functions that return JSX. When another Babel plugin rewrites such
a function into a non-object — e.g. expo-widgets' `'widget'` directive
serializes the layout function into a string literal — the injected
assignment runs against a string at module-eval time and throws:

  TypeError: Cannot create property 'displayName' on string

The throw aborts module initialization and can cascade into unrelated
downstream errors.

Tag the component scope when the function body carries a `'widget'` or
`'worklet'` directive, and skip both displayName and testID injection for
those functions. Applied to both babel-plugin variants and the transformer
(`injectTestIds`), with regression tests.

Fixes ohah#126

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

babel-plugin-inject-testid crashes when another plugin rewrites a PascalCase JSX function into a non-object (e.g. expo-widgets 'widget' directive)

1 participant