diff --git a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap index ce3f5c07e529..7e05929b0d3b 100644 --- a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap +++ b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap @@ -9814,6 +9814,24 @@ Map { "children": Object { "type": "node", }, + "enableExperimentalFocusWrapWithoutSentinels": Object { + "type": "bool", + }, + "enableTreeviewControllable": Object { + "type": "bool", + }, + "enableUseControlledStateWithValue": Object { + "type": "bool", + }, + "enableV12Overflowmenu": Object { + "type": "bool", + }, + "enableV12TileDefaultIcons": Object { + "type": "bool", + }, + "enableV12TileRadioIcons": Object { + "type": "bool", + }, "flags": Object { "args": Array [ Object { diff --git a/packages/react/src/components/FeatureFlags/__tests__/FeatureFlags-test.js b/packages/react/src/components/FeatureFlags/__tests__/FeatureFlags-test.js index 0b423bc56490..cb95de54b7c2 100644 --- a/packages/react/src/components/FeatureFlags/__tests__/FeatureFlags-test.js +++ b/packages/react/src/components/FeatureFlags/__tests__/FeatureFlags-test.js @@ -71,6 +71,46 @@ describe('FeatureFlags', () => { }); }); + it('should provide access to the feature flags for a scope', () => { + const checkFlags = jest.fn(); + const checkFlag = jest.fn(); + + function TestComponent() { + const featureFlags = useFeatureFlags(); + const flag1 = useFeatureFlag('enable-v12-overflowmenu'); + const flag2 = useFeatureFlag('enable-treeview-controllable'); + + checkFlags({ + enableV12Overflowmenu: featureFlags.enabled('enable-v12-overflowmenu'), + enableTreeviewControllable: featureFlags.enabled( + 'enable-treeview-controllable' + ), + }); + + checkFlag({ + enableV12Overflowmenu: flag1, + enableTreeviewControllable: flag2, + }); + + return null; + } + + render( + + + + ); + + expect(checkFlags).toHaveBeenLastCalledWith({ + enableV12Overflowmenu: true, + enableTreeviewControllable: false, + }); + expect(checkFlag).toHaveBeenLastCalledWith({ + enableV12Overflowmenu: true, + enableTreeviewControllable: false, + }); + }); + it('should re-render when flags change', () => { const checkFlags = jest.fn(); const checkFlag = jest.fn(); @@ -124,6 +164,94 @@ describe('FeatureFlags', () => { }); }); + it('should handle boolean props correctly when no flags object is provided', () => { + const checkFlags = jest.fn(); + const checkFlag = jest.fn(); + + function TestComponent() { + const featureFlags = useFeatureFlags(); + const enableV12Overflowmenu = useFeatureFlag('enable-v12-overflowmenu'); + const enableTreeviewControllable = useFeatureFlag( + 'enable-treeview-controllable' + ); + + checkFlags({ + enableV12Overflowmenu: featureFlags.enabled('enable-v12-overflowmenu'), + enableTreeviewControllable: featureFlags.enabled( + 'enable-treeview-controllable' + ), + }); + + checkFlag({ + enableV12Overflowmenu, + enableTreeviewControllable, + }); + + return null; + } + + render( + + + + ); + + expect(checkFlags).toHaveBeenLastCalledWith({ + enableV12Overflowmenu: true, + enableTreeviewControllable: false, + }); + expect(checkFlag).toHaveBeenLastCalledWith({ + enableV12Overflowmenu: true, + enableTreeviewControllable: false, + }); + }); + + it('should handle boolean props and flags object with no overlapping keys', () => { + const checkFlags = jest.fn(); + const checkFlag = jest.fn(); + + function TestComponent() { + const featureFlags = useFeatureFlags(); + const enableV12Overflowmenu = useFeatureFlag('enable-v12-overflowmenu'); + const enableExperimentalFocusWrapWithoutSentinels = useFeatureFlag( + 'enable-experimental-focus-wrap-without-sentinels' + ); + + checkFlags({ + enableV12Overflowmenu: featureFlags.enabled('enable-v12-overflowmenu'), + enableExperimentalFocusWrapWithoutSentinels: featureFlags.enabled( + 'enable-experimental-focus-wrap-without-sentinels' + ), + }); + + checkFlag({ + enableV12Overflowmenu, + enableExperimentalFocusWrapWithoutSentinels, + }); + + return null; + } + + render( + + + + ); + + expect(checkFlags).toHaveBeenLastCalledWith({ + enableV12Overflowmenu: false, + enableExperimentalFocusWrapWithoutSentinels: true, + }); + expect(checkFlag).toHaveBeenLastCalledWith({ + enableV12Overflowmenu: false, + enableExperimentalFocusWrapWithoutSentinels: true, + }); + }); + it('should merge scopes and overwrite duplicate keys', () => { GlobalFeatureFlags.add('global', true); diff --git a/packages/react/src/components/FeatureFlags/index.js b/packages/react/src/components/FeatureFlags/index.js index f8b00c764b4a..f5054e69343f 100644 --- a/packages/react/src/components/FeatureFlags/index.js +++ b/packages/react/src/components/FeatureFlags/index.js @@ -29,17 +29,37 @@ const FeatureFlagContext = createContext(GlobalFeatureFlags); * along with the current `FeatureFlagContext` to provide consumers to check if * a feature flag is enabled or disabled in a given React tree */ -function FeatureFlags({ children, flags = {} }) { +function FeatureFlags({ + children, + flags = {}, + enableUseControlledStateWithValue, + enableV12TileDefaultIcons, + enableV12TileRadioIcons, + enableV12Overflowmenu, + enableTreeviewControllable, + enableExperimentalFocusWrapWithoutSentinels, +}) { const parentScope = useContext(FeatureFlagContext); const [prevParentScope, setPrevParentScope] = useState(parentScope); + + const combinedFlags = { + 'enable-use-controlled-state-with-value': enableUseControlledStateWithValue, + 'enable-v12-tile-default-icons': enableV12TileDefaultIcons, + 'enable-v12-tile-radio-icons': enableV12TileRadioIcons, + 'enable-v12-overflowmenu': enableV12Overflowmenu, + 'enable-treeview-controllable': enableTreeviewControllable, + 'enable-experimental-focus-wrap-without-sentinels': + enableExperimentalFocusWrapWithoutSentinels, + ...flags, + }; const [scope, updateScope] = useState(() => { - const scope = createScope(flags); + const scope = createScope(combinedFlags); scope.mergeWithScope(parentScope); return scope; }); if (parentScope !== prevParentScope) { - const scope = createScope(flags); + const scope = createScope(combinedFlags); scope.mergeWithScope(parentScope); updateScope(scope); setPrevParentScope(parentScope); @@ -48,7 +68,7 @@ function FeatureFlags({ children, flags = {} }) { // We use a custom hook to detect if any of the keys or their values change // for flags that are passed in. If they have changed, then we re-create the // FeatureFlagScope using the new flags - useChangedValue(flags, isEqual, (changedFlags) => { + useChangedValue(combinedFlags, isEqual, (changedFlags) => { const scope = createScope(changedFlags); scope.mergeWithScope(parentScope); updateScope(scope); @@ -68,6 +88,12 @@ FeatureFlags.propTypes = { * Provide the feature flags to enabled or disabled in the current React tree */ flags: PropTypes.objectOf(PropTypes.bool), + enableUseControlledStateWithValue: PropTypes.bool, + enableV12TileDefaultIcons: PropTypes.bool, + enableV12TileRadioIcons: PropTypes.bool, + enableV12Overflowmenu: PropTypes.bool, + enableTreeviewControllable: PropTypes.bool, + enableExperimentalFocusWrapWithoutSentinels: PropTypes.bool, }; /**