Skip to content

Commit f3f3685

Browse files
authored
feat: Add evaluation options to FeatureFlag component (#1322)
<!-- Please use this template for your pull request. --> <!-- Please use the sections that you need and delete other sections --> ## This PR This PR is adding evaluation options to the FeatureFlag component to allow the use of suspend options. ### Related Issues <!-- add here the GitHub issue that this PR resolves if applicable --> #1321 ### Notes <!-- any additional notes for this PR --> ### Follow-up Tasks <!-- anything that is related to this PR but not done here should be noted under this section --> <!-- if there is a need for a new issue, please link it here --> ### How to test <!-- if applicable, add testing instructions under this section --> --------- Signed-off-by: marcozabel <marco.zabel@dynatrace.com>
1 parent aebc2aa commit f3f3685

File tree

2 files changed

+54
-3
lines changed

2 files changed

+54
-3
lines changed

packages/react/src/declarative/FeatureFlag.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useFlag } from '../evaluation';
33
import type { FlagQuery } from '../query';
44
import type { FlagValue, EvaluationDetails } from '@openfeature/core';
55
import { isEqual } from '../internal';
6+
import type { ReactFlagEvaluationOptions } from '../options';
67

78
/**
89
* Default predicate function that checks if the expected value equals the actual flag value.
@@ -44,6 +45,11 @@ interface FeatureFlagProps<T extends FlagValue = FlagValue> {
4445
* Can be a React node or a function that receives evaluation details and returns a React node.
4546
*/
4647
fallback?: React.ReactNode | ((details: EvaluationDetails<T>) => React.ReactNode);
48+
49+
/**
50+
* Flag evaluation options that will be passed to useFlag hook.
51+
*/
52+
evaluationOptions?: ReactFlagEvaluationOptions;
4753
}
4854

4955
/**
@@ -86,10 +92,12 @@ export function FeatureFlag<T extends FlagValue = FlagValue>({
8692
predicate,
8793
defaultValue,
8894
children,
95+
evaluationOptions = {},
8996
fallback = null,
9097
}: FeatureFlagComponentProps<T>): React.ReactElement | null {
9198
const details = useFlag(flagKey, defaultValue, {
9299
updateOnContextChanged: true,
100+
...evaluationOptions,
93101
});
94102

95103
// If the flag evaluation failed, we render the fallback

packages/react/test/declarative.spec.tsx

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import React from 'react';
1+
import React, { Suspense } from 'react';
22
import '@testing-library/jest-dom'; // see: https://testing-library.com/docs/react-testing-library/setup
33
import { render, screen } from '@testing-library/react';
44
import { FeatureFlag } from '../src/declarative/FeatureFlag'; // Assuming Feature.tsx is in the same directory or adjust path
5-
import { InMemoryProvider, OpenFeature, OpenFeatureProvider } from '../src';
5+
import type { Provider } from '../src';
6+
import { InMemoryProvider, OpenFeature, OpenFeatureProvider, ProviderStatus } from '../src';
67
import type { EvaluationDetails } from '@openfeature/core';
78

89
describe('Feature Component', () => {
@@ -47,13 +48,25 @@ describe('Feature Component', () => {
4748
return new InMemoryProvider(FLAG_CONFIG);
4849
};
4950

50-
OpenFeature.setProvider(EVALUATION, makeProvider());
51+
const makeAsyncProvider = () => {
52+
class MockAsyncProvider {
53+
metadata = {
54+
name: 'mock-async',
55+
};
56+
status = ProviderStatus.NOT_READY;
57+
58+
async initialize(): Promise<void> {}
59+
}
60+
61+
return new MockAsyncProvider() as Provider;
62+
};
5163

5264
const childText = 'Feature is active';
5365
const ChildComponent = () => <div>{childText}</div>;
5466

5567
beforeEach(() => {
5668
jest.clearAllMocks();
69+
OpenFeature.setProvider(EVALUATION, makeProvider());
5770
});
5871

5972
describe('<FeatureFlag />', () => {
@@ -108,6 +121,36 @@ describe('Feature Component', () => {
108121
expect(screen.queryByText(childText)).toBeInTheDocument();
109122
});
110123

124+
it('should render the fallback if suspended and flag is unresolved', () => {
125+
OpenFeature.setProvider(EVALUATION, makeAsyncProvider());
126+
127+
render(
128+
<OpenFeatureProvider domain={EVALUATION}>
129+
<Suspense fallback={<div>Suspense</div>}>
130+
<FeatureFlag flagKey={BOOL_FLAG_KEY} defaultValue={false} evaluationOptions={{ suspend: true }}>
131+
<ChildComponent />
132+
</FeatureFlag>
133+
</Suspense>
134+
</OpenFeatureProvider>,
135+
);
136+
137+
expect(screen.queryByText('Suspense')).toBeInTheDocument();
138+
});
139+
140+
it('should render child if provider is ready', () => {
141+
render(
142+
<OpenFeatureProvider domain={EVALUATION}>
143+
<Suspense fallback={<div>Suspense</div>}>
144+
<FeatureFlag flagKey={BOOL_FLAG_KEY} defaultValue={false} evaluationOptions={{ suspend: true }}>
145+
<ChildComponent />
146+
</FeatureFlag>
147+
</Suspense>
148+
</OpenFeatureProvider>,
149+
);
150+
151+
expect(screen.queryByText(childText)).toBeInTheDocument();
152+
});
153+
111154
it('should support custom predicate function', () => {
112155
const customPredicate = (expected: boolean | undefined, actual: { value: boolean }) => {
113156
// Custom logic: render if flag is NOT the expected value (negation)

0 commit comments

Comments
 (0)