Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 108 additions & 2 deletions packages/vitest-react-native/src/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,11 +302,117 @@ mock(
}`
);

// Feature Flags - use Proxy to auto-handle any current or future flag
// Feature Flags - explicit defaults for every known flag (RN 0.83) so named
// imports / Object.keys / spreads see the full surface. Proxy fallback covers
// flags added in future RN releases without needing a setup.ts update.
mock(
'react-native/src/private/featureflags/ReactNativeFeatureFlags',
() => `(() => {
const defaults = { isLayoutAnimationEnabled: () => true };
const f = (v) => () => v;
const defaults = {
animatedShouldDebounceQueueFlush: f(false),
animatedShouldUseSingleOp: f(false),
cdpInteractionMetricsEnabled: f(false),
commonTestFlag: f(false),
commonTestFlagWithoutNativeImplementation: f(false),
configurePressabilityDuringInsertion: f(false),
cxxNativeAnimatedEnabled: f(false),
cxxNativeAnimatedRemoveJsSync: f(false),
deferFlatListFocusChangeRenderUpdate: f(false),
disableEarlyViewCommandExecution: f(false),
disableFabricCommitInCXXAnimated: f(false),
disableMaintainVisibleContentPosition: f(false),
disableMountItemReorderingAndroid: f(false),
disableOldAndroidAttachmentMetricsWorkarounds: f(true),
disableTextLayoutManagerCacheAndroid: f(false),
enableAccessibilityOrder: f(false),
enableAccumulatedUpdatesInRawPropsAndroid: f(false),
enableAndroidLinearText: f(false),
enableAndroidTextMeasurementOptimizations: f(false),
enableBridgelessArchitecture: f(false),
enableCppPropsIteratorSetter: f(false),
enableCustomFocusSearchOnClippedElementsAndroid: f(true),
enableDestroyShadowTreeRevisionAsync: f(false),
enableDoubleMeasurementFixAndroid: f(false),
enableEagerMainQueueModulesOnIOS: f(false),
enableEagerRootViewAttachment: f(false),
enableFabricLogs: f(false),
enableFabricRenderer: f(false),
enableFontScaleChangesUpdatingLayout: f(true),
enableImagePrefetchingAndroid: f(false),
enableImagePrefetchingOnUiThreadAndroid: f(false),
enableImmediateUpdateModeForContentOffsetChanges: f(false),
enableImperativeFocus: f(false),
enableInteropViewManagerClassLookUpOptimizationIOS: f(false),
enableIntersectionObserverByDefault: f(false),
enableIOSTextBaselineOffsetPerLine: f(false),
enableIOSViewClipToPaddingBox: f(false),
enableKeyEvents: f(false),
enableLayoutAnimationsOnAndroid: f(false),
enableLayoutAnimationsOnIOS: f(true),
enableMainQueueCoordinatorOnIOS: f(false),
enableModuleArgumentNSNullConversionIOS: f(false),
enableNativeCSSParsing: f(false),
enableNetworkEventReporting: f(true),
enablePreparedTextLayout: f(false),
enablePropsUpdateReconciliationAndroid: f(false),
enableResourceTimingAPI: f(true),
enableSwiftUIBasedFilters: f(false),
enableViewCulling: f(false),
enableViewRecycling: f(false),
enableViewRecyclingForImage: f(true),
enableViewRecyclingForScrollView: f(false),
enableViewRecyclingForText: f(true),
enableViewRecyclingForView: f(true),
enableVirtualViewClippingWithoutScrollViewClipping: f(true),
enableVirtualViewContainerStateExperimental: f(false),
enableVirtualViewDebugFeatures: f(false),
enableVirtualViewExperimental: f(false),
enableVirtualViewRenderState: f(true),
enableVirtualViewWindowFocusDetection: f(false),
enableWebPerformanceAPIsByDefault: f(true),
fixMappingOfEventPrioritiesBetweenFabricAndReact: f(false),
fixVirtualizeListCollapseWindowSize: f(false),
fuseboxAssertSingleHostState: f(true),
fuseboxEnabledRelease: f(false),
fuseboxNetworkInspectionEnabled: f(true),
hideOffscreenVirtualViewsOnIOS: f(false),
isLayoutAnimationEnabled: f(true),
jsOnlyTestFlag: f(false),
overrideBySynchronousMountPropsAtMountingAndroid: f(false),
perfIssuesEnabled: f(false),
perfMonitorV2Enabled: f(false),
preparedTextCacheSize: f(200),
preventShadowTreeCommitExhaustion: f(false),
reduceDefaultPropsInImage: f(false),
reduceDefaultPropsInText: f(false),
shouldPressibilityUseW3CPointerEventsForHover: f(false),
shouldTriggerResponderTransferOnScrollAndroid: f(false),
shouldUseAnimatedObjectForTransform: f(false),
shouldUseRemoveClippedSubviewsAsDefaultOnIOS: f(false),
shouldUseSetNativePropsInFabric: f(true),
skipActivityIdentityAssertionOnHostPause: f(false),
sweepActiveTouchOnChildNativeGesturesAndroid: f(true),
traceTurboModulePromiseRejectionsOnAndroid: f(false),
updateRuntimeShadowNodeReferencesOnCommit: f(false),
useAlwaysAvailableJSErrorHandling: f(false),
useFabricInterop: f(true),
useNativeEqualsInNativeReadableArrayAndroid: f(true),
useNativeTransformHelperAndroid: f(true),
useNativeViewConfigsInBridgelessMode: f(false),
useOptimizedEventBatchingOnAndroid: f(false),
useRawPropsJsiValue: f(true),
useShadowNodeStateOnClone: f(false),
useSharedAnimatedBackend: f(false),
useTraitHiddenOnAndroid: f(false),
useTurboModuleInterop: f(false),
useTurboModules: f(false),
viewCullingOutsetRatio: f(0),
virtualViewActivityBehavior: f("no-activity"),
virtualViewHysteresisRatio: f(0),
virtualViewPrerenderRatio: f(5),
override: () => {},
};
return new Proxy(defaults, {
get: (target, prop) => {
if (prop in target) return target[prop];
Expand Down
37 changes: 25 additions & 12 deletions test/__tests__/FeatureFlags.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,46 @@
import { test, expect, describe } from 'vitest';

/**
* Issue #13: ReactNativeFeatureFlags.enableNativeCSSParsing is not a function
* Issues #13 and #24: ReactNativeFeatureFlags missing functions
*
* Verifies that the feature flags mock handles any current or future flag
* via a Proxy, so new flags don't require manual updates to setup.ts.
* Every flag from the current RN release is an explicit own-property so named
* imports, Object.keys, and spreads work. The Proxy fallback keeps unknown /
* future flags returning () => false without a setup.ts update.
*/
describe('ReactNativeFeatureFlags (issue #13)', () => {
test('known flags are callable', () => {
describe('ReactNativeFeatureFlags', () => {
test('known boolean flags are callable with correct defaults', () => {
const flags = require('react-native/src/private/featureflags/ReactNativeFeatureFlags');
expect(typeof flags.enableNativeCSSParsing).toBe('function');
expect(flags.enableNativeCSSParsing()).toBe(false);
expect(flags.isLayoutAnimationEnabled()).toBe(true);
expect(flags.enableLayoutAnimationsOnIOS()).toBe(true);
expect(flags.enableFabricRenderer()).toBe(false);
expect(flags.useTurboModules()).toBe(false);
});

test('isLayoutAnimationEnabled defaults to true', () => {
test('non-boolean flags return correct default types', () => {
const flags = require('react-native/src/private/featureflags/ReactNativeFeatureFlags');
expect(flags.isLayoutAnimationEnabled()).toBe(true);
expect(flags.preparedTextCacheSize()).toBe(200);
expect(flags.virtualViewPrerenderRatio()).toBe(5);
expect(flags.virtualViewActivityBehavior()).toBe('no-activity');
});

test('flags are own properties (Object.keys / spread / for...in)', () => {
const flags = require('react-native/src/private/featureflags/ReactNativeFeatureFlags');
const keys = Object.keys(flags);
expect(keys).toContain('enableNativeCSSParsing');
expect(keys).toContain('isLayoutAnimationEnabled');
expect(keys.length).toBeGreaterThan(50);
const spread = { ...flags };
expect(typeof spread.enableNativeCSSParsing).toBe('function');
});

test('unknown/future flags return () => false automatically', () => {
const flags = require('react-native/src/private/featureflags/ReactNativeFeatureFlags');
// Any arbitrary flag name should work without hardcoding
expect(typeof flags.someFutureFlag).toBe('function');
expect(flags.someFutureFlag()).toBe(false);
expect(typeof flags.anotherNewFeature).toBe('function');
expect(flags.anotherNewFeature()).toBe(false);
});

test('turbo module proxy also handles unknown flags', () => {
test('turbo module proxy handles any flag name', () => {
const proxy = (globalThis as Record<string, unknown>).__turboModuleProxy as (
name: string,
) => Record<string, unknown> | null;
Expand Down
Loading