From 60c10c940733f691c6d367466ddf67905ac02a0b Mon Sep 17 00:00:00 2001 From: Tim Fischbach Date: Fri, 22 May 2026 09:51:38 +0200 Subject: [PATCH] Add useFakeFeatures test helper Bundle the feature-flag enable + restore dance that several specs currently repeat (`beforeEach(() => features.enable(...))` paired with `afterEach(() => features.enabledFeatureNames = [])`) into a single hook taking the same `(scope, names)` signature as `features.enable`. The afterEach snapshots and restores the previous enabled set instead of clearing unconditionally, so multiple `useFakeFeatures` calls compose across nested describes the same way `useFakeTranslations` does. Adopt the helper in the four specs (commentBadges, ContentElement, InlineImage, ExternalLink) that already followed the before-hook setup pattern. --- .../frontend/ExternalLink-spec.js | 6 +- .../inlineImage/InlineImage-spec.js | 4 +- .../spec/editor/models/ContentElement-spec.js | 6 +- .../features/commentBadges-spec.js | 7 +-- .../spec/testHelpers/useFakeFeatures-spec.js | 57 +++++++++++++++++++ package/src/testHelpers/index.js | 1 + package/src/testHelpers/useFakeFeatures.js | 47 +++++++++++++++ 7 files changed, 114 insertions(+), 14 deletions(-) create mode 100644 package/spec/testHelpers/useFakeFeatures-spec.js create mode 100644 package/src/testHelpers/useFakeFeatures.js diff --git a/entry_types/scrolled/package/spec/contentElements/externalLinkList/frontend/ExternalLink-spec.js b/entry_types/scrolled/package/spec/contentElements/externalLinkList/frontend/ExternalLink-spec.js index 6b4e03808c..6fd7fe18dd 100644 --- a/entry_types/scrolled/package/spec/contentElements/externalLinkList/frontend/ExternalLink-spec.js +++ b/entry_types/scrolled/package/spec/contentElements/externalLinkList/frontend/ExternalLink-spec.js @@ -5,10 +5,9 @@ import React from 'react'; import {renderInEntry} from 'support'; import {renderInContentElement} from 'pageflow-scrolled/testHelpers'; -import {useFakeTranslations} from 'pageflow/testHelpers'; +import {useFakeFeatures, useFakeTranslations} from 'pageflow/testHelpers'; import {screen} from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; -import {features} from 'pageflow/frontend'; describe('ExternalLink', () => { it('renders link with href', () => { @@ -286,8 +285,7 @@ describe('ExternalLink', () => { }); describe('srcset', () => { - beforeEach(() => features.enable('frontend', ['image_srcset'])); - afterEach(() => features.enabledFeatureNames = []); + useFakeFeatures('frontend', ['image_srcset']); it('uses medium and large srcset for linkWidth m', () => { const {getByRole} = renderInEntry( diff --git a/entry_types/scrolled/package/spec/contentElements/inlineImage/InlineImage-spec.js b/entry_types/scrolled/package/spec/contentElements/inlineImage/InlineImage-spec.js index dac55df488..a817757692 100644 --- a/entry_types/scrolled/package/spec/contentElements/inlineImage/InlineImage-spec.js +++ b/entry_types/scrolled/package/spec/contentElements/inlineImage/InlineImage-spec.js @@ -6,6 +6,7 @@ import '@testing-library/jest-dom/extend-expect' import {InlineImage} from 'contentElements/inlineImage/InlineImage'; import {features} from 'pageflow/frontend'; +import {useFakeFeatures} from 'pageflow/testHelpers'; import {usePortraitOrientation} from 'frontend/usePortraitOrientation'; jest.mock('frontend/usePortraitOrientation'); @@ -239,8 +240,7 @@ describe('InlineImage', () => { }); describe('srcset', () => { - beforeEach(() => features.enable('frontend', ['image_srcset'])); - afterEach(() => features.enabledFeatureNames = []); + useFakeFeatures('frontend', ['image_srcset']); function renderInlineImage({contentElementWidth = 0, ...seedOptions} = {}) { const result = renderInContentElement( diff --git a/entry_types/scrolled/package/spec/editor/models/ContentElement-spec.js b/entry_types/scrolled/package/spec/editor/models/ContentElement-spec.js index cb8f17e410..6e4bba6689 100644 --- a/entry_types/scrolled/package/spec/editor/models/ContentElement-spec.js +++ b/entry_types/scrolled/package/spec/editor/models/ContentElement-spec.js @@ -1,13 +1,13 @@ import {editor} from 'pageflow-scrolled/editor'; import {ScrolledEntry} from 'editor/models/ScrolledEntry'; import {factories, normalizeSeed} from 'support'; -import {features} from 'pageflow/frontend'; +import {useFakeFeatures} from 'pageflow/testHelpers'; describe('ContentElement', () => { describe('getAvailablePositions', () => { - beforeEach(() => { - features.enable('frontend', ['backdrop_content_elements']); + useFakeFeatures('frontend', ['backdrop_content_elements']); + beforeEach(() => { editor.contentElementTypes.register('inlineImage', {}); editor.contentElementTypes.register('soundDisclaimer', {supportedPositions: ['inline']}); }); diff --git a/entry_types/scrolled/package/spec/frontend/inlineEditing/features/commentBadges-spec.js b/entry_types/scrolled/package/spec/frontend/inlineEditing/features/commentBadges-spec.js index b9276af769..8d45206941 100644 --- a/entry_types/scrolled/package/spec/frontend/inlineEditing/features/commentBadges-spec.js +++ b/entry_types/scrolled/package/spec/frontend/inlineEditing/features/commentBadges-spec.js @@ -1,6 +1,6 @@ import '@testing-library/jest-dom/extend-expect'; import {act, waitFor} from '@testing-library/react'; -import {features} from 'pageflow/frontend'; +import {useFakeFeatures} from 'pageflow/testHelpers'; import {useInlineEditingPageObjects, renderEntry} from 'support/pageObjects/inlineEditing'; @@ -8,10 +8,7 @@ import badgeStyles from 'review/Badge.module.css'; describe('inline editing comment badges', () => { useInlineEditingPageObjects(); - - beforeEach(() => { - features.enable('frontend', ['commenting']); - }); + useFakeFeatures('frontend', ['commenting']); it('does not display comment icon when element is not selected', () => { const {queryByRole} = renderEntry({ diff --git a/package/spec/testHelpers/useFakeFeatures-spec.js b/package/spec/testHelpers/useFakeFeatures-spec.js new file mode 100644 index 0000000000..5ecc2cc903 --- /dev/null +++ b/package/spec/testHelpers/useFakeFeatures-spec.js @@ -0,0 +1,57 @@ +import {useFakeFeatures} from 'testHelpers/useFakeFeatures'; + +import {features} from 'pageflow/frontend'; + +describe('useFakeFeatures', () => { + describe('with one feature', () => { + useFakeFeatures('frontend', ['commenting']); + + it('marks the feature as enabled', () => { + expect(features.isEnabled('commenting')).toBe(true); + }); + }); + + describe('with multiple features', () => { + useFakeFeatures('frontend', ['commenting', 'image_srcset']); + + it('enables all listed features', () => { + expect(features.isEnabled('commenting')).toBe(true); + expect(features.isEnabled('image_srcset')).toBe(true); + }); + }); + + describe('with registered functions', () => { + const fn = jest.fn(); + features.register('frontend', 'with_callback', fn); + + useFakeFeatures('frontend', ['with_callback']); + + it('invokes scope-registered callbacks for enabled features', () => { + expect(fn).toHaveBeenCalled(); + }); + }); + + describe('when called multiple times', () => { + useFakeFeatures('frontend', ['commenting']); + useFakeFeatures('editor', ['special_content_element']); + + it('enables features from all calls', () => { + expect(features.isEnabled('commenting')).toBe(true); + expect(features.isEnabled('special_content_element')).toBe(true); + }); + }); + + describe('cleanup', () => { + describe('inner describe enabling a feature', () => { + useFakeFeatures('frontend', ['scoped_feature']); + + it('enables the feature inside', () => { + expect(features.isEnabled('scoped_feature')).toBe(true); + }); + }); + + it('does not leak enabled features to sibling tests', () => { + expect(features.isEnabled('scoped_feature')).toBe(false); + }); + }); +}); diff --git a/package/src/testHelpers/index.js b/package/src/testHelpers/index.js index 1948c83293..a9cce8d5b5 100644 --- a/package/src/testHelpers/index.js +++ b/package/src/testHelpers/index.js @@ -3,5 +3,6 @@ export * from './dominos/ui'; export * from './factories'; export * from './fakeBrowserAgent'; export * from './setupGlobals'; +export * from './useFakeFeatures'; export * from './useFakeTranslations'; export * from './renderBackboneView'; diff --git a/package/src/testHelpers/useFakeFeatures.js b/package/src/testHelpers/useFakeFeatures.js new file mode 100644 index 0000000000..7e7536e851 --- /dev/null +++ b/package/src/testHelpers/useFakeFeatures.js @@ -0,0 +1,47 @@ +import {features} from 'pageflow/frontend'; + +/** + * Enable feature flags for the surrounding describe block. + * + * Each `beforeEach` calls `features.enable(scope, names)`, which both + * flips `features.isEnabled` to true for the given names and runs any + * functions registered via `features.register` for them. Each + * `afterEach` restores the set of enabled features to whatever was + * enabled before the helper was invoked, so suites do not leak state + * into each other. + * + * Multiple calls within the same describe block (or across nested + * describes) compose: each call adds its own features on top of + * whatever previous `useFakeFeatures` calls already enabled. + * + * @param {String} scope - + * Name of the scope to enable the features in (e.g. `'frontend'`, + * `'editor'`). + * @param {String[]} names - + * Feature names to enable in the given scope. + * + * @example + * import {useFakeFeatures} from 'pageflow/testHelpers'; + * import {features} from 'pageflow/frontend'; + * + * describe('...', () => { + * useFakeFeatures('frontend', ['commenting']); + * + * it('...', () => { + * expect(features.isEnabled('commenting')).toBe(true); + * }); + * }); + */ +export function useFakeFeatures(scope, names) { + let originalEnabledFeatureNames; + + beforeEach(() => { + originalEnabledFeatureNames = features.enabledFeatureNames; + + features.enable(scope, names); + }); + + afterEach(() => { + features.enabledFeatureNames = originalEnabledFeatureNames; + }); +}