You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ghost/apps/posts/test/unit/hooks/withFeatureFlag.test.tsx

183 lines
7.5 KiB

/* eslint-disable @typescript-eslint/no-explicit-any */
import React from 'react';
import {beforeEach, describe, expect, it, vi} from 'vitest';
import {createTestWrapper, setupUniversalMocks} from '../../utils/test-helpers';
import {render, screen} from '@testing-library/react';
import {withFeatureFlag} from '@src/hooks/withFeatureFlag';
// Mock the Navigate component
vi.mock('@tryghost/admin-x-framework', () => ({
Navigate: ({to}: {to: string}) => React.createElement('div', {'data-testid': 'navigate', 'data-to': to}, `Redirecting to ${to}`)
}));
// Centralized API mocking
vi.mock('@tryghost/admin-x-framework/api/posts');
vi.mock('@tryghost/admin-x-framework/api/stats');
vi.mock('@tryghost/admin-x-framework/api/links');
vi.mock('@src/providers/PostAnalyticsContext');
vi.mock('@tryghost/admin-x-framework/api/settings');
describe('withFeatureFlag', () => {
const TestComponent = ({message}: {message: string}) => <div data-testid="test-component">{message}</div>;
let wrapper: any;
let mocks: any;
beforeEach(async () => {
vi.clearAllMocks();
wrapper = createTestWrapper();
// Universal setup - mocks ALL API hooks with sensible defaults
mocks = await setupUniversalMocks();
});
it('renders the wrapped component when feature flag is enabled', () => {
mocks.mockGetSettingValue.mockReturnValue('{"testFlag": true}');
mocks.mockUseGlobalData.mockReturnValue({
isLoading: false,
settings: [
{key: 'labs', value: '{"testFlag": true}'}
]
});
const WrappedComponent = withFeatureFlag(TestComponent, 'testFlag', '/fallback', 'Test Title');
render(<WrappedComponent message="Hello World" />, {wrapper});
expect(screen.getByTestId('test-component')).toBeInTheDocument();
expect(screen.getByText('Hello World')).toBeInTheDocument();
});
it('prevents access when feature flag is disabled', () => {
mocks.mockUseGlobalData.mockReturnValue({
isLoading: false,
settings: [
{key: 'labs', value: '{"testFlag": false}'}
]
});
const WrappedComponent = withFeatureFlag(TestComponent, 'testFlag', '/fallback', 'Test Title');
render(<WrappedComponent message="Hello World" />, {wrapper});
// Component should not render when feature is disabled
expect(screen.queryByTestId('test-component')).not.toBeInTheDocument();
expect(screen.queryByText('Hello World')).not.toBeInTheDocument();
});
it('prevents access when feature flag is missing', () => {
mocks.mockUseGlobalData.mockReturnValue({
isLoading: false,
settings: [
{key: 'labs', value: '{"otherFlag": true}'}
]
});
const WrappedComponent = withFeatureFlag(TestComponent, 'testFlag', '/fallback', 'Test Title');
render(<WrappedComponent message="Hello World" />, {wrapper});
// Component should not render when feature flag doesn't exist
expect(screen.queryByTestId('test-component')).not.toBeInTheDocument();
expect(screen.queryByText('Hello World')).not.toBeInTheDocument();
});
it('shows loading state during data loading', () => {
mocks.mockUseGlobalData.mockReturnValue({
isLoading: true,
settings: []
});
const WrappedComponent = withFeatureFlag(TestComponent, 'testFlag', '/fallback', 'Test Title');
render(<WrappedComponent message="Hello World" />, {wrapper});
// Component should not render during loading, should show title
expect(screen.queryByTestId('test-component')).not.toBeInTheDocument();
expect(screen.getByText('Test Title')).toBeInTheDocument();
});
it('passes through props to the wrapped component', () => {
mocks.mockGetSettingValue.mockReturnValue('{"analytics": true}');
mocks.mockUseGlobalData.mockReturnValue({
isLoading: false,
settings: [
{key: 'labs', value: '{"analytics": true}'}
]
});
const WrappedComponent = withFeatureFlag(TestComponent, 'analytics', '/dashboard', 'Analytics');
render(<WrappedComponent message="Custom Message" />, {wrapper});
expect(screen.getByText('Custom Message')).toBeInTheDocument();
});
it('sets correct display name for the wrapped component', () => {
const WrappedComponent = withFeatureFlag(TestComponent, 'testFlag', '/fallback', 'Test Title');
expect(WrappedComponent.displayName).toBe('withFeatureFlag(TestComponent)');
});
it('works with different feature flags', () => {
mocks.mockGetSettingValue.mockReturnValue('{"customFeature": true, "otherFeature": false}');
mocks.mockUseGlobalData.mockReturnValue({
isLoading: false,
settings: [
{key: 'labs', value: '{"customFeature": true, "otherFeature": false}'}
]
});
const CustomWrapped = withFeatureFlag(TestComponent, 'customFeature', '/home', 'Custom Feature');
const OtherWrapped = withFeatureFlag(TestComponent, 'otherFeature', '/home', 'Other Feature');
render(
<div>
<CustomWrapped message="Custom Feature Active" />
<OtherWrapped message="Other Feature Active" />
</div>,
{wrapper}
);
// Only the enabled feature should render, disabled shows redirect
expect(screen.getByText('Custom Feature Active')).toBeInTheDocument();
expect(screen.getByText('Redirecting to /home')).toBeInTheDocument();
expect(screen.queryByText('Other Feature Active')).not.toBeInTheDocument();
});
it('handles complex feature flag scenarios', () => {
mocks.mockGetSettingValue.mockReturnValue('{"analytics": true, "webAnalytics": false, "trafficAnalytics": true}');
mocks.mockUseGlobalData.mockReturnValue({
isLoading: false,
settings: [
{key: 'labs', value: '{"analytics": true, "webAnalytics": false, "trafficAnalytics": true}'}
]
});
const AnalyticsComponent = withFeatureFlag(TestComponent, 'analytics', '/dashboard', 'Analytics');
const WebAnalyticsComponent = withFeatureFlag(TestComponent, 'webAnalytics', '/dashboard', 'Web Analytics');
const TrafficComponent = withFeatureFlag(TestComponent, 'trafficAnalytics', '/dashboard', 'Traffic');
render(
<div>
<AnalyticsComponent message="Analytics Enabled" />
<WebAnalyticsComponent message="Web Analytics Enabled" />
<TrafficComponent message="Traffic Enabled" />
</div>,
{wrapper}
);
// Only enabled features should render, disabled shows redirect
expect(screen.getByText('Analytics Enabled')).toBeInTheDocument();
expect(screen.getByText('Traffic Enabled')).toBeInTheDocument();
expect(screen.getByText('Redirecting to /dashboard')).toBeInTheDocument();
expect(screen.queryByText('Web Analytics Enabled')).not.toBeInTheDocument();
});
it('works with components that have no display name', () => {
const AnonymousComponent = ({text}: {text: string}) => <div>{text}</div>;
const WrappedComponent = withFeatureFlag(AnonymousComponent, 'testFlag', '/fallback', 'Test');
expect(WrappedComponent.displayName).toBe('withFeatureFlag(AnonymousComponent)');
});
});