Skip to content

fix(Example): add ESLint rule to enforce top-level component exports in SFT and CIT#3959

Open
sgaczol wants to merge 1 commit intomainfrom
@sgaczol/eslint-rule-exports
Open

fix(Example): add ESLint rule to enforce top-level component exports in SFT and CIT#3959
sgaczol wants to merge 1 commit intomainfrom
@sgaczol/eslint-rule-exports

Conversation

@sgaczol
Copy link
Copy Markdown
Collaborator

@sgaczol sgaczol commented Apr 30, 2026

Description

This PR introduces a local ESLint rule require-top-level-exports to ensure that all top-level React component declarations in our test suites are exported.

This approach fixes an issue with Fast Refresh where some of the unexported components were being fully remounted instead of seamlessly updated. This behavior is tied to the Metro bundler's specification regarding module boundaries and state preservation. By ensuring all top-level components are explicitly exported, Metro can properly track them, which fixes the remounting issue and allows Fast Refresh to work as intended.

Changes

  • add an ESLint rule
  • apply the rule to single-feature and component-integration tests

Before & after - visual documentation

Before

without.exports.mov

After

with.exports.mov

Test plan

Make sure that updating components of single-feature and component-integration tests triggers a correct Fast Refresh.

Checklist

  • Included code example that can be used to test this change.
  • For visual changes, included screenshots / GIFs / recordings documenting the change.
  • For API changes, updated relevant public types.
  • Ensured that CI passes

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a local ESLint rule to enforce that top-level React component declarations in the single-feature-tests (SFT) and component-integration-tests (CIT) suites are exported, addressing Fast Refresh remounting caused by Metro module boundary heuristics.

Changes:

  • Introduce a local ESLint plugin (local-rules) with a require-top-level-exports rule.
  • Add ESLint configuration under apps/src/tests to apply the rule to SFT and CIT.
  • Update SFT/CIT files to export their top-level components (e.g., ConfigScreen, TestScreen, HomeScreen, etc.).

Reviewed changes

Copilot reviewed 32 out of 32 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
apps/src/tests/single-feature-tests/tabs/test-tabs-tab-bar-minimize-behavior-ios/index.tsx Export top-level tab test components.
apps/src/tests/single-feature-tests/tabs/test-tabs-tab-bar-layout-direction/index.tsx Export top-level config component.
apps/src/tests/single-feature-tests/tabs/test-tabs-tab-bar-hidden/index.tsx Export top-level config component.
apps/src/tests/single-feature-tests/tabs/test-tabs-tab-bar-controller-mode-ios/index.tsx Export top-level tab test components.
apps/src/tests/single-feature-tests/tabs/test-tabs-tab-bar-color-scheme/index.tsx Export top-level tab test components.
apps/src/tests/single-feature-tests/tabs/test-tabs-stale-update-rejection.tsx Export top-level components in the scenario.
apps/src/tests/single-feature-tests/tabs/test-tabs-simple-nav.tsx Export top-level components in the scenario.
apps/src/tests/single-feature-tests/tabs/test-tabs-prevent-native-selection/index.tsx Export top-level components in the scenario.
apps/src/tests/single-feature-tests/tabs/test-tabs-more-navigation-controller.tsx Export top-level components in the scenario.
apps/src/tests/single-feature-tests/tabs/test-tabs-ime-insets.tsx Export top-level config component.
apps/src/tests/single-feature-tests/tabs/test-tabs-appearance-defined-by-selected-tab.tsx Export top-level tab screen component.
apps/src/tests/single-feature-tests/tabs/tabs-screen-orientation.tsx Export top-level config component.
apps/src/tests/single-feature-tests/tabs/override-scroll-view-content-inset.tsx Export top-level tab/content components.
apps/src/tests/single-feature-tests/tabs/bottom-accessory-layout.tsx Export top-level accessory/config components.
apps/src/tests/single-feature-tests/stack-v5/test-stack-subviews-android/index.tsx Export top-level stack setup/config components.
apps/src/tests/single-feature-tests/stack-v5/test-stack-simple-nav/index.tsx Export top-level stack components/screens.
apps/src/tests/single-feature-tests/stack-v5/test-stack-back-button-android/index.tsx Export top-level stack screens/controls.
apps/src/tests/single-feature-tests/stack-v5/test-animation-android.tsx Export top-level stack screens/setup.
apps/src/tests/single-feature-tests/stack-v5/prevent-native-dismiss-single-stack.tsx Export top-level stack screens/helpers.
apps/src/tests/single-feature-tests/stack-v5/prevent-native-dismiss-nested-stack.tsx Export top-level nested stack screens/helpers.
apps/src/tests/single-feature-tests/stack-v4/stack-v4-orientation.tsx Export top-level config component.
apps/src/tests/single-feature-tests/split/test-top-column-for-collapsing.tsx Export top-level split column component.
apps/src/tests/single-feature-tests/split/test-command-show-column.tsx Export top-level split column component.
apps/src/tests/single-feature-tests/scroll-view-marker/test-svm-configures-scroll-view.tsx Export top-level content screen component.
apps/src/tests/single-feature-tests/index.tsx Export top-level HomeScreen.
apps/src/tests/eslint-plugin-local-rules/require-top-level-exports.js Implement local ESLint rule logic.
apps/src/tests/eslint-plugin-local-rules/package.json Define local plugin package metadata.
apps/src/tests/eslint-plugin-local-rules/index.js Export plugin rules entrypoint.
apps/src/tests/component-integration-tests/orientation/orientation-tabs-in-stack.tsx Export top-level orientation test components.
apps/src/tests/component-integration-tests/orientation/orientation-stack-in-tabs.tsx Export top-level orientation test components.
apps/src/tests/component-integration-tests/index.tsx Export top-level HomeScreen.
apps/src/tests/.eslintrc.js Apply local rule to SFT/CIT via overrides.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +51 to +63
if (stmt.type === 'VariableDeclaration' && stmt.declarations.length === 1) {
const decl = stmt.declarations[0];
const name = decl.id?.name;
if (
name &&
isPascalCase(name) &&
decl.init &&
(decl.init.type === 'ArrowFunctionExpression' ||
decl.init.type === 'FunctionExpression')
) {
return name;
}
}
Comment on lines +1 to +14
const path = require('path');
const Module = require('module');

const pluginPath = require.resolve(
path.join(__dirname, 'eslint-plugin-local-rules'),
);
const originalResolve = Module._resolveFilename;
Module._resolveFilename = function (request, ...args) {
if (request === 'eslint-plugin-local-rules') {
return pluginPath;
}
return originalResolve.call(this, request, ...args);
};

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.

2 participants