Skip to content

Commit ad8898e

Browse files
ro1heptoddbaert
andauthored
test: add testing for HookFlagQuery (#922)
<!-- Please use this template for your pull request. --> <!-- Please use the sections that you need and delete other sections --> ## This PR <!-- add the description of the PR here --> - introduces tests for `HookFlagQuery` ### Related Issues <!-- add here the GitHub issue that this PR resolves if applicable --> Resolves #915 ### Notes Placed these tests within `evaluation.spec.tsx`, happy to move them into their own file if more appropriate --------- Signed-off-by: Rowan Heptinstall <[email protected]> Signed-off-by: Rowan Heptinstall <[email protected]> Signed-off-by: Todd Baert <[email protected]> Co-authored-by: Todd Baert <[email protected]>
1 parent 4bce2a0 commit ad8898e

File tree

3 files changed

+137
-50
lines changed

3 files changed

+137
-50
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import {
2+
EvaluationDetails,
3+
FlagValue,
4+
StandardResolutionReasons
5+
} from '@openfeature/web-sdk';
6+
import { FlagQuery } from '../query';
7+
8+
9+
// FlagQuery implementation, do not export
10+
export class HookFlagQuery<T extends FlagValue = FlagValue> implements FlagQuery {
11+
constructor(private _details: EvaluationDetails<T>) {}
12+
13+
get details() {
14+
return this._details;
15+
}
16+
17+
get value() {
18+
return this._details?.value;
19+
}
20+
21+
get variant() {
22+
return this._details.variant;
23+
}
24+
25+
get flagMetadata() {
26+
return this._details.flagMetadata;
27+
}
28+
29+
get reason() {
30+
return this._details.reason;
31+
}
32+
33+
get isError() {
34+
return !!this._details?.errorCode || this._details.reason == StandardResolutionReasons.ERROR;
35+
}
36+
37+
get errorCode() {
38+
return this._details?.errorCode;
39+
}
40+
41+
get errorMessage() {
42+
return this._details?.errorMessage;
43+
}
44+
45+
get isAuthoritative() {
46+
return (
47+
!this.isError &&
48+
this._details.reason != StandardResolutionReasons.STALE &&
49+
this._details.reason != StandardResolutionReasons.DISABLED
50+
);
51+
}
52+
53+
get type() {
54+
return typeof this._details.value;
55+
}
56+
}

packages/react/src/evaluation/use-feature-flag.ts

Lines changed: 1 addition & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {
66
JsonValue,
77
ProviderEvents,
88
ProviderStatus,
9-
StandardResolutionReasons
109
} from '@openfeature/web-sdk';
1110
import { useEffect, useState } from 'react';
1211
import { DEFAULT_OPTIONS, ReactFlagEvaluationOptions, normalizeOptions } from '../common/options';
@@ -15,6 +14,7 @@ import { useProviderOptions } from '../provider/context';
1514
import { useOpenFeatureClient } from '../provider/use-open-feature-client';
1615
import { useOpenFeatureClientStatus } from '../provider/use-open-feature-client-status';
1716
import { FlagQuery } from '../query';
17+
import { HookFlagQuery } from './hook-flag-query';
1818

1919
// This type is a bit wild-looking, but I think we need it.
2020
// We have to use the conditional, because otherwise useFlag('key', false) would return false, not boolean (too constrained).
@@ -298,52 +298,3 @@ function attachHandlersAndResolve<T extends FlagValue>(
298298

299299
return evalutationDetails;
300300
}
301-
302-
// FlagQuery implementation, do not export
303-
class HookFlagQuery<T extends FlagValue = FlagValue> implements FlagQuery {
304-
constructor(private _details: EvaluationDetails<T>) {}
305-
306-
get details() {
307-
return this._details;
308-
}
309-
310-
get value() {
311-
return this._details?.value;
312-
}
313-
314-
get variant() {
315-
return this._details.variant;
316-
}
317-
318-
get flagMetadata() {
319-
return this._details.flagMetadata;
320-
}
321-
322-
get reason() {
323-
return this._details.reason;
324-
}
325-
326-
get isError() {
327-
return !!this._details?.errorCode || this._details.reason == StandardResolutionReasons.ERROR;
328-
}
329-
330-
get errorCode() {
331-
return this._details?.errorCode;
332-
}
333-
334-
get errorMessage() {
335-
return this._details?.errorMessage;
336-
}
337-
338-
get isAuthoritative() {
339-
return (
340-
!this.isError &&
341-
this._details.reason != StandardResolutionReasons.STALE &&
342-
this._details.reason != StandardResolutionReasons.DISABLED
343-
);
344-
}
345-
346-
get type() {
347-
return typeof this._details.value;
348-
}
349-
}

packages/react/test/evaluation.spec.tsx

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import {
44
InMemoryProvider,
55
OpenFeature,
66
StandardResolutionReasons,
7+
EvaluationDetails,
8+
ErrorCode,
79
} from '@openfeature/web-sdk';
810
import '@testing-library/jest-dom'; // see: https://testing-library.com/docs/react-testing-library/setup
911
import { act, render, renderHook, screen, waitFor } from '@testing-library/react';
@@ -21,6 +23,7 @@ import {
2123
useStringFlagValue,
2224
} from '../src/';
2325
import { TestingProvider } from './test.utils';
26+
import { HookFlagQuery } from '../src/evaluation/hook-flag-query';
2427
import { startTransition, useState } from 'react';
2528

2629
describe('evaluation', () => {
@@ -681,5 +684,82 @@ describe('evaluation', () => {
681684
);
682685
});
683686
});
687+
688+
describe('HookFlagQuery', () => {
689+
it('should return details', () => {
690+
const details: EvaluationDetails<string> = {
691+
flagKey: 'flag-key',
692+
flagMetadata : {},
693+
value: 'string'
694+
};
695+
const hookFlagQuery = new HookFlagQuery(details);
696+
expect(hookFlagQuery.details).toEqual(details);
697+
});
698+
699+
it('should return flag metadata', () => {
700+
const flagMetadata = {
701+
'ping': 'pong'
702+
};
703+
const details: EvaluationDetails<boolean> = {
704+
flagKey: 'with-flagMetadata',
705+
flagMetadata,
706+
value: true,
707+
};
708+
const hookFlagQuery = new HookFlagQuery(details);
709+
expect(hookFlagQuery.flagMetadata).toEqual(expect.objectContaining(flagMetadata));
710+
});
711+
712+
it.each([
713+
[{
714+
flagKey: 'i-dont-exist',
715+
flagMetadata: {},
716+
errorMessage: 'no flag found with key i-dont-exist',
717+
errorCode: ErrorCode.FLAG_NOT_FOUND,
718+
value: true
719+
}],
720+
[{
721+
flagKey: 'i-dont-exist',
722+
flagMetadata: {},
723+
errorMessage: 'no flag found with key i-dont-exist',
724+
errorCode: undefined,
725+
reason: StandardResolutionReasons.ERROR,
726+
value: true
727+
}],
728+
])('should return errors if reason is error or errorCode is set',(details) => {;
729+
const hookFlagQuery = new HookFlagQuery(details);
730+
expect(hookFlagQuery.isError).toEqual(true);
731+
expect(hookFlagQuery.errorCode).toEqual(details.errorCode);
732+
expect(hookFlagQuery.errorMessage).toEqual(details.errorMessage);
733+
});
734+
735+
it.each([
736+
[{
737+
flagKey: 'isAuthorative-true',
738+
flagMetadata : {},
739+
value: 7,
740+
}, true],
741+
[{
742+
flagKey: 'with-error',
743+
flagMetadata : {},
744+
value: 7,
745+
errorCode: ErrorCode.FLAG_NOT_FOUND
746+
}, false],
747+
[{
748+
flagKey: 'with-reason-stale',
749+
flagMetadata : {},
750+
value: 7,
751+
reason: StandardResolutionReasons.STALE
752+
}, false],
753+
[{
754+
flagKey: 'with-reason-disabled',
755+
flagMetadata : {},
756+
value: 7,
757+
reason: StandardResolutionReasons.DISABLED
758+
}, false],
759+
])('should return isAuthorative if Reason != STALE/DISABLED and errorCode unset',(details, expected) => {
760+
const hookFlagQuery = new HookFlagQuery(details);
761+
expect(hookFlagQuery.isAuthoritative).toEqual(expected);
762+
});
763+
});
684764
});
685765
});

0 commit comments

Comments
 (0)