@servicetitan/launchdarkly-service 33.1.0 → 34.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/feature-flag-provider.d.ts.map +1 -1
- package/dist/feature-flag-provider.js.map +1 -1
- package/dist/ld-provider.d.ts.map +1 -1
- package/dist/ld-provider.js +7 -3
- package/dist/ld-provider.js.map +1 -1
- package/dist/ld-service-provider.d.ts +7 -1
- package/dist/ld-service-provider.d.ts.map +1 -1
- package/dist/ld-service-provider.js +18 -2
- package/dist/ld-service-provider.js.map +1 -1
- package/dist/tests/feature-flag-provider.test.d.ts.map +1 -1
- package/dist/tests/feature-flag-provider.test.js +2 -2
- package/dist/tests/feature-flag-provider.test.js.map +1 -1
- package/dist/tests/get-ld-client.test.js.map +1 -1
- package/dist/tests/ld-provider.test.d.ts.map +1 -1
- package/dist/tests/ld-provider.test.js +21 -4
- package/dist/tests/ld-provider.test.js.map +1 -1
- package/dist/tests/ld-service-provider.test.js +89 -2
- package/dist/tests/ld-service-provider.test.js.map +1 -1
- package/dist/tests/with-feature-flag.test.js.map +1 -1
- package/dist/types.d.ts +3 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +9 -9
- package/src/feature-flag-provider.tsx +0 -1
- package/src/ld-provider.tsx +4 -2
- package/src/ld-service-provider.tsx +25 -5
- package/src/tests/feature-flag-provider.test.tsx +3 -4
- package/src/tests/get-ld-client.test.ts +0 -1
- package/src/tests/ld-provider.test.tsx +21 -5
- package/src/tests/ld-service-provider.test.tsx +105 -9
- package/src/tests/with-feature-flag.test.tsx +0 -1
- package/src/types.ts +4 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feature-flag-provider.d.ts","sourceRoot":"","sources":["../src/feature-flag-provider.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAuB,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"feature-flag-provider.d.ts","sourceRoot":"","sources":["../src/feature-flag-provider.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAuB,MAAM,OAAO,CAAC;AAG/D,wBAAgB,mBAAmB,CAAC,EAAE,QAAQ,EAAE,EAAE,iBAAiB,6BAclE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/feature-flag-provider.tsx"],"sourcesContent":["import { useDependencies } from '@servicetitan/react-ioc';\nimport { useFlags, useLDClient } from 'launchdarkly-react-client-sdk';\nimport { PropsWithChildren, useEffect, useState } from 'react';\
|
|
1
|
+
{"version":3,"sources":["../src/feature-flag-provider.tsx"],"sourcesContent":["import { useDependencies } from '@servicetitan/react-ioc';\nimport { useFlags, useLDClient } from 'launchdarkly-react-client-sdk';\nimport { PropsWithChildren, useEffect, useState } from 'react';\nimport { FeatureFlagStore } from './feature-flag-store';\n\nexport function FeatureFlagProvider({ children }: PropsWithChildren) {\n const [initialized, setInitialized] = useState(false);\n const [{ setFlags }] = useDependencies(FeatureFlagStore);\n const client = useLDClient();\n const flags = useFlags();\n\n useEffect(() => {\n if (client) {\n setFlags(flags);\n setInitialized(true);\n }\n }, [client, flags, setFlags]);\n\n return initialized ? children : null;\n}\n"],"names":["useDependencies","useFlags","useLDClient","useEffect","useState","FeatureFlagStore","FeatureFlagProvider","children","initialized","setInitialized","setFlags","client","flags"],"mappings":"AAAA,SAASA,eAAe,QAAQ,0BAA0B;AAC1D,SAASC,QAAQ,EAAEC,WAAW,QAAQ,gCAAgC;AACtE,SAA4BC,SAAS,EAAEC,QAAQ,QAAQ,QAAQ;AAC/D,SAASC,gBAAgB,QAAQ,uBAAuB;AAExD,OAAO,SAASC,oBAAoB,EAAEC,QAAQ,EAAqB;IAC/D,MAAM,CAACC,aAAaC,eAAe,GAAGL,SAAS;IAC/C,MAAM,CAAC,EAAEM,QAAQ,EAAE,CAAC,GAAGV,gBAAgBK;IACvC,MAAMM,SAAST;IACf,MAAMU,QAAQX;IAEdE,UAAU;QACN,IAAIQ,QAAQ;YACRD,SAASE;YACTH,eAAe;QACnB;IACJ,GAAG;QAACE;QAAQC;QAAOF;KAAS;IAE5B,OAAOF,cAAcD,WAAW;AACpC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ld-provider.d.ts","sourceRoot":"","sources":["../src/ld-provider.tsx"],"names":[],"mappings":"AAGA,OAAO,EAEH,cAAc,IAAI,kBAAkB,EACvC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,EAAE,EAAE,iBAAiB,EAAmC,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"ld-provider.d.ts","sourceRoot":"","sources":["../src/ld-provider.tsx"],"names":[],"mappings":"AAGA,OAAO,EAEH,cAAc,IAAI,kBAAkB,EACvC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,EAAE,EAAE,iBAAiB,EAAmC,MAAM,OAAO,CAAC;AAM/E,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,kBAAkB,EAAE,cAAc,GAAG,SAAS,CAAC,GAAG;IAChF,WAAW,EAAE,MAAM,CAAC;IACpB,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACnC,CAAC;AAEF,eAAO,MAAM,UAAU,EAAE,EAAE,CAAC,iBAAiB,CAAC,cAAc,CAAC,CAkD3D,CAAC"}
|
package/dist/ld-provider.js
CHANGED
|
@@ -18,12 +18,16 @@ export const LDProvider = provide({
|
|
|
18
18
|
const [counter, forceUpdate] = useReducer((x)=>x + 1, 0);
|
|
19
19
|
useEffect(()=>{
|
|
20
20
|
(async function initializeClient() {
|
|
21
|
+
var _ref;
|
|
22
|
+
const options = (ldService === null || ldService === void 0 ? void 0 : ldService.application) ? {
|
|
23
|
+
application: ldService.application
|
|
24
|
+
} : undefined;
|
|
21
25
|
const config = {
|
|
22
26
|
clientSideID,
|
|
23
|
-
context
|
|
27
|
+
context,
|
|
28
|
+
options
|
|
24
29
|
};
|
|
25
|
-
|
|
26
|
-
const client = (_ldService_getClient = ldService === null || ldService === void 0 ? void 0 : ldService.getClient(config)) !== null && _ldService_getClient !== void 0 ? _ldService_getClient : getLDClient(config);
|
|
30
|
+
const client = (_ref = ldService === null || ldService === void 0 ? void 0 : ldService.getClient(config)) !== null && _ref !== void 0 ? _ref : getLDClient(config);
|
|
27
31
|
if (waitForInitialization) {
|
|
28
32
|
try {
|
|
29
33
|
await client.waitForInitialization(5);
|
package/dist/ld-provider.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ld-provider.tsx"],"sourcesContent":["import { Log } from '@servicetitan/log-service';\nimport { provide, useOptionalDependencies } from '@servicetitan/react-ioc';\nimport { LDClient } from 'launchdarkly-js-client-sdk';\nimport {\n LDProvider as BaseLDProvider,\n ProviderConfig as BaseProviderConfig,\n} from 'launchdarkly-react-client-sdk';\nimport { FC, PropsWithChildren, useEffect, useReducer, useState } from 'react';\
|
|
1
|
+
{"version":3,"sources":["../src/ld-provider.tsx"],"sourcesContent":["import { Log } from '@servicetitan/log-service';\nimport { provide, useOptionalDependencies } from '@servicetitan/react-ioc';\nimport { LDClient } from 'launchdarkly-js-client-sdk';\nimport {\n LDProvider as BaseLDProvider,\n ProviderConfig as BaseProviderConfig,\n} from 'launchdarkly-react-client-sdk';\nimport { FC, PropsWithChildren, useEffect, useReducer, useState } from 'react';\nimport { FeatureFlagProvider } from './feature-flag-provider';\nimport { FeatureFlagStore } from './feature-flag-store';\nimport { getLDClient } from './get-ld-client';\nimport { LD_SERVICE_TOKEN } from './types';\n\nexport type ProviderConfig = Pick<BaseProviderConfig, 'clientSideID' | 'context'> & {\n projectName: string;\n waitForInitialization?: boolean;\n};\n\nexport const LDProvider: FC<PropsWithChildren<ProviderConfig>> = provide({\n singletons: [FeatureFlagStore],\n})(props => {\n const { children, clientSideID, context, projectName, waitForInitialization = true } = props;\n const [ldClient, setLDClient] = useState<LDClient>();\n const [ldService, log] = useOptionalDependencies(LD_SERVICE_TOKEN, Log);\n const [counter, forceUpdate] = useReducer((x: number) => x + 1, 0);\n\n useEffect(() => {\n (async function initializeClient() {\n const options = ldService?.application\n ? { application: ldService.application }\n : undefined;\n const config: BaseProviderConfig = { clientSideID, context, options };\n const client = ldService?.getClient(config) ?? getLDClient(config);\n if (waitForInitialization) {\n try {\n await client.waitForInitialization(5);\n } catch (error) {\n log?.error({\n category: 'LaunchDarkly',\n code: 'InitializationFailed',\n error,\n message: `\"${projectName}\" client failed to initialize`,\n });\n }\n }\n setLDClient(client);\n })();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [clientSideID]); // only reload if clientSideID changes\n\n useEffect(() => {\n if (!waitForInitialization && ldClient) {\n ldClient.on('initialized', forceUpdate);\n return () => {\n ldClient.off('initialized', forceUpdate);\n };\n }\n }, [ldClient, waitForInitialization]);\n\n if (!ldClient) {\n return null;\n }\n\n return (\n <BaseLDProvider key={counter.toString()} clientSideID={clientSideID} ldClient={ldClient}>\n <FeatureFlagProvider>{children}</FeatureFlagProvider>\n </BaseLDProvider>\n );\n});\n"],"names":["Log","provide","useOptionalDependencies","LDProvider","BaseLDProvider","useEffect","useReducer","useState","FeatureFlagProvider","FeatureFlagStore","getLDClient","LD_SERVICE_TOKEN","singletons","props","children","clientSideID","context","projectName","waitForInitialization","ldClient","setLDClient","ldService","log","counter","forceUpdate","x","initializeClient","options","application","undefined","config","client","getClient","error","category","code","message","on","off","toString"],"mappings":";AAAA,SAASA,GAAG,QAAQ,4BAA4B;AAChD,SAASC,OAAO,EAAEC,uBAAuB,QAAQ,0BAA0B;AAE3E,SACIC,cAAcC,cAAc,QAEzB,gCAAgC;AACvC,SAAgCC,SAAS,EAAEC,UAAU,EAAEC,QAAQ,QAAQ,QAAQ;AAC/E,SAASC,mBAAmB,QAAQ,0BAA0B;AAC9D,SAASC,gBAAgB,QAAQ,uBAAuB;AACxD,SAASC,WAAW,QAAQ,kBAAkB;AAC9C,SAASC,gBAAgB,QAAQ,UAAU;AAO3C,OAAO,MAAMR,aAAoDF,QAAQ;IACrEW,YAAY;QAACH;KAAiB;AAClC,GAAGI,CAAAA;IACC,MAAM,EAAEC,QAAQ,EAAEC,YAAY,EAAEC,OAAO,EAAEC,WAAW,EAAEC,wBAAwB,IAAI,EAAE,GAAGL;IACvF,MAAM,CAACM,UAAUC,YAAY,GAAGb;IAChC,MAAM,CAACc,WAAWC,IAAI,GAAGpB,wBAAwBS,kBAAkBX;IACnE,MAAM,CAACuB,SAASC,YAAY,GAAGlB,WAAW,CAACmB,IAAcA,IAAI,GAAG;IAEhEpB,UAAU;QACL,CAAA,eAAeqB;;YACZ,MAAMC,UAAUN,CAAAA,sBAAAA,gCAAAA,UAAWO,WAAW,IAChC;gBAAEA,aAAaP,UAAUO,WAAW;YAAC,IACrCC;YACN,MAAMC,SAA6B;gBAAEf;gBAAcC;gBAASW;YAAQ;YACpE,MAAMI,iBAASV,sBAAAA,gCAAAA,UAAWW,SAAS,CAACF,8CAAWpB,YAAYoB;YAC3D,IAAIZ,uBAAuB;gBACvB,IAAI;oBACA,MAAMa,OAAOb,qBAAqB,CAAC;gBACvC,EAAE,OAAOe,OAAO;oBACZX,gBAAAA,0BAAAA,IAAKW,KAAK,CAAC;wBACPC,UAAU;wBACVC,MAAM;wBACNF;wBACAG,SAAS,CAAC,CAAC,EAAEnB,YAAY,6BAA6B,CAAC;oBAC3D;gBACJ;YACJ;YACAG,YAAYW;QAChB,CAAA;IACA,uDAAuD;IAC3D,GAAG;QAAChB;KAAa,GAAG,sCAAsC;IAE1DV,UAAU;QACN,IAAI,CAACa,yBAAyBC,UAAU;YACpCA,SAASkB,EAAE,CAAC,eAAeb;YAC3B,OAAO;gBACHL,SAASmB,GAAG,CAAC,eAAed;YAChC;QACJ;IACJ,GAAG;QAACL;QAAUD;KAAsB;IAEpC,IAAI,CAACC,UAAU;QACX,OAAO;IACX;IAEA,qBACI,KAACf;QAAwCW,cAAcA;QAAcI,UAAUA;kBAC3E,cAAA,KAACX;sBAAqBM;;OADLS,QAAQgB,QAAQ;AAI7C,GAAG"}
|
|
@@ -1,2 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
import { FC, PropsWithChildren } from 'react';
|
|
2
|
+
import { LDApplication } from './types';
|
|
3
|
+
interface LDServiceProviderProps {
|
|
4
|
+
application?: LDApplication;
|
|
5
|
+
}
|
|
6
|
+
export declare const LDServiceProvider: FC<PropsWithChildren<LDServiceProviderProps>>;
|
|
7
|
+
export {};
|
|
2
8
|
//# sourceMappingURL=ld-service-provider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ld-service-provider.d.ts","sourceRoot":"","sources":["../src/ld-service-provider.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ld-service-provider.d.ts","sourceRoot":"","sources":["../src/ld-service-provider.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,EAAE,EAAE,iBAAiB,EAAqB,MAAM,OAAO,CAAC;AAEjE,OAAO,EAAoB,aAAa,EAAa,MAAM,SAAS,CAAC;AAMrE,UAAU,sBAAsB;IAC5B,WAAW,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED,eAAO,MAAM,iBAAiB,EAAE,EAAE,CAAC,iBAAiB,CAAC,sBAAsB,CAAC,CAkB1E,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { provide } from '@servicetitan/react-ioc';
|
|
1
|
+
import { provide, useDependencies } from '@servicetitan/react-ioc';
|
|
2
|
+
import { useEffect, useRef } from 'react';
|
|
2
3
|
import { getLDClient } from './get-ld-client';
|
|
3
4
|
import { LD_SERVICE_TOKEN } from './types';
|
|
4
5
|
const ldService = {
|
|
@@ -12,6 +13,21 @@ export const LDServiceProvider = provide({
|
|
|
12
13
|
inherit: true
|
|
13
14
|
}
|
|
14
15
|
]
|
|
15
|
-
})(({ children })=>
|
|
16
|
+
})(({ children, application })=>{
|
|
17
|
+
const [providedLDService] = useDependencies(LD_SERVICE_TOKEN);
|
|
18
|
+
const prevApplication = useRef(providedLDService.application);
|
|
19
|
+
if (providedLDService === ldService || !providedLDService.application) {
|
|
20
|
+
providedLDService.application = application;
|
|
21
|
+
}
|
|
22
|
+
useEffect(()=>{
|
|
23
|
+
return ()=>{
|
|
24
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
25
|
+
providedLDService.application = prevApplication.current;
|
|
26
|
+
};
|
|
27
|
+
}, [
|
|
28
|
+
providedLDService
|
|
29
|
+
]);
|
|
30
|
+
return children;
|
|
31
|
+
});
|
|
16
32
|
|
|
17
33
|
//# sourceMappingURL=ld-service-provider.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ld-service-provider.tsx"],"sourcesContent":["import { provide } from '@servicetitan/react-ioc';\
|
|
1
|
+
{"version":3,"sources":["../src/ld-service-provider.tsx"],"sourcesContent":["import { provide, useDependencies } from '@servicetitan/react-ioc';\nimport { FC, PropsWithChildren, useEffect, useRef } from 'react';\nimport { getLDClient } from './get-ld-client';\nimport { LD_SERVICE_TOKEN, LDApplication, LDService } from './types';\n\nconst ldService: LDService = {\n getClient: getLDClient,\n};\n\ninterface LDServiceProviderProps {\n application?: LDApplication;\n}\n\nexport const LDServiceProvider: FC<PropsWithChildren<LDServiceProviderProps>> = provide({\n singletons: [{ provide: LD_SERVICE_TOKEN, useValue: ldService, inherit: true }],\n})(({ children, application }) => {\n const [providedLDService] = useDependencies(LD_SERVICE_TOKEN);\n const prevApplication = useRef(providedLDService.application);\n\n if (providedLDService === ldService || !providedLDService.application) {\n providedLDService.application = application;\n }\n\n useEffect(() => {\n return () => {\n // eslint-disable-next-line react-hooks/exhaustive-deps\n providedLDService.application = prevApplication.current;\n };\n }, [providedLDService]);\n\n return children;\n});\n"],"names":["provide","useDependencies","useEffect","useRef","getLDClient","LD_SERVICE_TOKEN","ldService","getClient","LDServiceProvider","singletons","useValue","inherit","children","application","providedLDService","prevApplication","current"],"mappings":"AAAA,SAASA,OAAO,EAAEC,eAAe,QAAQ,0BAA0B;AACnE,SAAgCC,SAAS,EAAEC,MAAM,QAAQ,QAAQ;AACjE,SAASC,WAAW,QAAQ,kBAAkB;AAC9C,SAASC,gBAAgB,QAAkC,UAAU;AAErE,MAAMC,YAAuB;IACzBC,WAAWH;AACf;AAMA,OAAO,MAAMI,oBAAmER,QAAQ;IACpFS,YAAY;QAAC;YAAET,SAASK;YAAkBK,UAAUJ;YAAWK,SAAS;QAAK;KAAE;AACnF,GAAG,CAAC,EAAEC,QAAQ,EAAEC,WAAW,EAAE;IACzB,MAAM,CAACC,kBAAkB,GAAGb,gBAAgBI;IAC5C,MAAMU,kBAAkBZ,OAAOW,kBAAkBD,WAAW;IAE5D,IAAIC,sBAAsBR,aAAa,CAACQ,kBAAkBD,WAAW,EAAE;QACnEC,kBAAkBD,WAAW,GAAGA;IACpC;IAEAX,UAAU;QACN,OAAO;YACH,uDAAuD;YACvDY,kBAAkBD,WAAW,GAAGE,gBAAgBC,OAAO;QAC3D;IACJ,GAAG;QAACF;KAAkB;IAEtB,OAAOF;AACX,GAAG"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feature-flag-provider.test.d.ts","sourceRoot":"","sources":["../../src/tests/feature-flag-provider.test.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"feature-flag-provider.test.d.ts","sourceRoot":"","sources":["../../src/tests/feature-flag-provider.test.tsx"],"names":[],"mappings":"AACA,OAAO,2BAA2B,CAAC"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import '@testing-library/jest-dom';
|
|
3
2
|
import { Provider } from '@servicetitan/react-ioc';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
4
4
|
import { render, screen } from '@testing-library/react';
|
|
5
|
+
import { useFlags, useLDClient } from 'launchdarkly-react-client-sdk';
|
|
5
6
|
import { FeatureFlagProvider } from '../feature-flag-provider';
|
|
6
7
|
import { FeatureFlagStore } from '../feature-flag-store';
|
|
7
|
-
import { useFlags, useLDClient } from 'launchdarkly-react-client-sdk';
|
|
8
8
|
jest.mock('launchdarkly-react-client-sdk', ()=>({
|
|
9
9
|
useLDClient: jest.fn(),
|
|
10
10
|
useFlags: jest.fn()
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/tests/feature-flag-provider.test.tsx"],"sourcesContent":["import '@testing-library/jest-dom';\nimport {
|
|
1
|
+
{"version":3,"sources":["../../src/tests/feature-flag-provider.test.tsx"],"sourcesContent":["import { Provider } from '@servicetitan/react-ioc';\nimport '@testing-library/jest-dom';\nimport { render, screen } from '@testing-library/react';\nimport { useFlags, useLDClient } from 'launchdarkly-react-client-sdk';\nimport { FC } from 'react';\nimport { FeatureFlagProvider } from '../feature-flag-provider';\nimport { FeatureFlagStore } from '../feature-flag-store';\n\njest.mock('launchdarkly-react-client-sdk', () => ({\n useLDClient: jest.fn(),\n useFlags: jest.fn(),\n}));\n\ndescribe(`[launchdarkly-service] ${FeatureFlagProvider.name}`, () => {\n const flags = { foo: 'bar', baz: 'qux' };\n const componentText = '<Component />';\n const mockFeatureFlagStore = {\n setFlags: jest.fn(),\n };\n\n const Component: FC = () => {\n return componentText;\n };\n\n beforeEach(() => {\n jest.clearAllMocks();\n jest.mocked(useLDClient).mockImplementation(() => ({}) as any);\n jest.mocked(useFlags).mockImplementation(() => flags);\n });\n\n const subject = () =>\n render(\n <Provider singletons={[{ provide: FeatureFlagStore, useValue: mockFeatureFlagStore }]}>\n <FeatureFlagProvider>\n <Component />\n </FeatureFlagProvider>\n </Provider>\n );\n\n test('renders children', () => {\n subject();\n\n expect(screen.getByText(componentText)).toBeInTheDocument();\n });\n\n test('sets feature flags in store', () => {\n subject();\n\n expect(mockFeatureFlagStore.setFlags).toHaveBeenCalledWith(flags);\n });\n\n describe('when client is not ready', () => {\n beforeEach(() => jest.mocked(useLDClient).mockReturnValue(undefined));\n\n test('does nothing', () => {\n subject();\n\n expect(screen.queryByText(componentText)).not.toBeInTheDocument();\n expect(mockFeatureFlagStore.setFlags).not.toHaveBeenCalledWith(flags);\n });\n });\n});\n"],"names":["Provider","render","screen","useFlags","useLDClient","FeatureFlagProvider","FeatureFlagStore","jest","mock","fn","describe","name","flags","foo","baz","componentText","mockFeatureFlagStore","setFlags","Component","beforeEach","clearAllMocks","mocked","mockImplementation","subject","singletons","provide","useValue","test","expect","getByText","toBeInTheDocument","toHaveBeenCalledWith","mockReturnValue","undefined","queryByText","not"],"mappings":";AAAA,SAASA,QAAQ,QAAQ,0BAA0B;AACnD,OAAO,4BAA4B;AACnC,SAASC,MAAM,EAAEC,MAAM,QAAQ,yBAAyB;AACxD,SAASC,QAAQ,EAAEC,WAAW,QAAQ,gCAAgC;AAEtE,SAASC,mBAAmB,QAAQ,2BAA2B;AAC/D,SAASC,gBAAgB,QAAQ,wBAAwB;AAEzDC,KAAKC,IAAI,CAAC,iCAAiC,IAAO,CAAA;QAC9CJ,aAAaG,KAAKE,EAAE;QACpBN,UAAUI,KAAKE,EAAE;IACrB,CAAA;AAEAC,SAAS,CAAC,uBAAuB,EAAEL,oBAAoBM,IAAI,EAAE,EAAE;IAC3D,MAAMC,QAAQ;QAAEC,KAAK;QAAOC,KAAK;IAAM;IACvC,MAAMC,gBAAgB;IACtB,MAAMC,uBAAuB;QACzBC,UAAUV,KAAKE,EAAE;IACrB;IAEA,MAAMS,YAAgB;QAClB,OAAOH;IACX;IAEAI,WAAW;QACPZ,KAAKa,aAAa;QAClBb,KAAKc,MAAM,CAACjB,aAAakB,kBAAkB,CAAC,IAAO,CAAA,CAAC,CAAA;QACpDf,KAAKc,MAAM,CAAClB,UAAUmB,kBAAkB,CAAC,IAAMV;IACnD;IAEA,MAAMW,UAAU,IACZtB,qBACI,KAACD;YAASwB,YAAY;gBAAC;oBAAEC,SAASnB;oBAAkBoB,UAAUV;gBAAqB;aAAE;sBACjF,cAAA,KAACX;0BACG,cAAA,KAACa;;;IAKjBS,KAAK,oBAAoB;QACrBJ;QAEAK,OAAO1B,OAAO2B,SAAS,CAACd,gBAAgBe,iBAAiB;IAC7D;IAEAH,KAAK,+BAA+B;QAChCJ;QAEAK,OAAOZ,qBAAqBC,QAAQ,EAAEc,oBAAoB,CAACnB;IAC/D;IAEAF,SAAS,4BAA4B;QACjCS,WAAW,IAAMZ,KAAKc,MAAM,CAACjB,aAAa4B,eAAe,CAACC;QAE1DN,KAAK,gBAAgB;YACjBJ;YAEAK,OAAO1B,OAAOgC,WAAW,CAACnB,gBAAgBoB,GAAG,CAACL,iBAAiB;YAC/DF,OAAOZ,qBAAqBC,QAAQ,EAAEkB,GAAG,CAACJ,oBAAoB,CAACnB;QACnE;IACJ;AACJ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/tests/get-ld-client.test.ts"],"sourcesContent":["import { initialize } from 'launchdarkly-js-client-sdk';\
|
|
1
|
+
{"version":3,"sources":["../../src/tests/get-ld-client.test.ts"],"sourcesContent":["import { initialize } from 'launchdarkly-js-client-sdk';\nimport { getLDClient } from '../get-ld-client';\n\njest.mock('launchdarkly-js-client-sdk', () => ({\n initialize: jest.fn(),\n}));\n\ndescribe(`[launchdarkly-service] ${getLDClient.name}`, () => {\n const mockClient: any = Symbol('mockClient');\n let config: Parameters<typeof getLDClient>[0];\n\n const subject = () => getLDClient(config);\n\n beforeEach(() => {\n jest.clearAllMocks();\n jest.mocked(initialize).mockImplementation(() => mockClient);\n config = { clientSideID: makeId() };\n });\n\n function anonymousUserContext() {\n return { kind: 'user', anonymous: true };\n }\n\n test('returns new LaunchDarkly client with anonymous user context', () => {\n expect(subject()).toBe(mockClient);\n expect(initialize).toHaveBeenCalledWith(\n config.clientSideID,\n anonymousUserContext(),\n undefined\n );\n });\n\n describe('when given a context', () => {\n beforeEach(() => (config.context = { kind: 'multi' }));\n\n test('initializes client with context', () => {\n subject();\n\n expect(initialize).toHaveBeenCalledWith(config.clientSideID, config.context, undefined);\n });\n });\n\n describe('when given options', () => {\n beforeEach(() => (config.options = { hash: 'foo' }));\n\n test('initializes client with options', () => {\n subject();\n\n expect(initialize).toHaveBeenCalledWith(\n config.clientSideID,\n anonymousUserContext(),\n config.options\n );\n });\n });\n\n describe('when client already exists', () => {\n const existingClient: any = Symbol('existingClient');\n\n beforeEach(() => {\n jest.mocked(initialize).mockImplementationOnce(() => existingClient);\n subject();\n });\n\n test('returns existing client', () => {\n expect(subject()).toBe(existingClient);\n expect(initialize).toHaveBeenCalledTimes(1);\n });\n });\n});\n\nfunction makeId() {\n return Math.round(1e6 * Math.random()).toString(36);\n}\n"],"names":["initialize","getLDClient","jest","mock","fn","describe","name","mockClient","Symbol","config","subject","beforeEach","clearAllMocks","mocked","mockImplementation","clientSideID","makeId","anonymousUserContext","kind","anonymous","test","expect","toBe","toHaveBeenCalledWith","undefined","context","options","hash","existingClient","mockImplementationOnce","toHaveBeenCalledTimes","Math","round","random","toString"],"mappings":"AAAA,SAASA,UAAU,QAAQ,6BAA6B;AACxD,SAASC,WAAW,QAAQ,mBAAmB;AAE/CC,KAAKC,IAAI,CAAC,8BAA8B,IAAO,CAAA;QAC3CH,YAAYE,KAAKE,EAAE;IACvB,CAAA;AAEAC,SAAS,CAAC,uBAAuB,EAAEJ,YAAYK,IAAI,EAAE,EAAE;IACnD,MAAMC,aAAkBC,OAAO;IAC/B,IAAIC;IAEJ,MAAMC,UAAU,IAAMT,YAAYQ;IAElCE,WAAW;QACPT,KAAKU,aAAa;QAClBV,KAAKW,MAAM,CAACb,YAAYc,kBAAkB,CAAC,IAAMP;QACjDE,SAAS;YAAEM,cAAcC;QAAS;IACtC;IAEA,SAASC;QACL,OAAO;YAAEC,MAAM;YAAQC,WAAW;QAAK;IAC3C;IAEAC,KAAK,+DAA+D;QAChEC,OAAOX,WAAWY,IAAI,CAACf;QACvBc,OAAOrB,YAAYuB,oBAAoB,CACnCd,OAAOM,YAAY,EACnBE,wBACAO;IAER;IAEAnB,SAAS,wBAAwB;QAC7BM,WAAW,IAAOF,OAAOgB,OAAO,GAAG;gBAAEP,MAAM;YAAQ;QAEnDE,KAAK,mCAAmC;YACpCV;YAEAW,OAAOrB,YAAYuB,oBAAoB,CAACd,OAAOM,YAAY,EAAEN,OAAOgB,OAAO,EAAED;QACjF;IACJ;IAEAnB,SAAS,sBAAsB;QAC3BM,WAAW,IAAOF,OAAOiB,OAAO,GAAG;gBAAEC,MAAM;YAAM;QAEjDP,KAAK,mCAAmC;YACpCV;YAEAW,OAAOrB,YAAYuB,oBAAoB,CACnCd,OAAOM,YAAY,EACnBE,wBACAR,OAAOiB,OAAO;QAEtB;IACJ;IAEArB,SAAS,8BAA8B;QACnC,MAAMuB,iBAAsBpB,OAAO;QAEnCG,WAAW;YACPT,KAAKW,MAAM,CAACb,YAAY6B,sBAAsB,CAAC,IAAMD;YACrDlB;QACJ;QAEAU,KAAK,2BAA2B;YAC5BC,OAAOX,WAAWY,IAAI,CAACM;YACvBP,OAAOrB,YAAY8B,qBAAqB,CAAC;QAC7C;IACJ;AACJ;AAEA,SAASd;IACL,OAAOe,KAAKC,KAAK,CAAC,MAAMD,KAAKE,MAAM,IAAIC,QAAQ,CAAC;AACpD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ld-provider.test.d.ts","sourceRoot":"","sources":["../../src/tests/ld-provider.test.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ld-provider.test.d.ts","sourceRoot":"","sources":["../../src/tests/ld-provider.test.tsx"],"names":[],"mappings":"AAEA,OAAO,2BAA2B,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import '@testing-library/jest-dom';
|
|
3
2
|
import { Log } from '@servicetitan/log-service';
|
|
4
3
|
import { Provider, useDependencies } from '@servicetitan/react-ioc';
|
|
4
|
+
import '@testing-library/jest-dom';
|
|
5
5
|
import { render, screen, waitFor } from '@testing-library/react';
|
|
6
6
|
import { act, Fragment } from 'react';
|
|
7
7
|
import { FeatureFlagStore } from '../feature-flag-store';
|
|
@@ -159,10 +159,11 @@ describe(`[launchdarkly-service] ${LDProvider.name}`, ()=>{
|
|
|
159
159
|
waitForInitialization: jest.fn().mockResolvedValue(undefined),
|
|
160
160
|
toString: ()=>'ldServiceClient'
|
|
161
161
|
};
|
|
162
|
-
|
|
163
|
-
getClient: jest.fn().mockReturnValue(ldServiceClient)
|
|
164
|
-
};
|
|
162
|
+
let ldService;
|
|
165
163
|
beforeEach(()=>{
|
|
164
|
+
ldService = {
|
|
165
|
+
getClient: jest.fn().mockReturnValue(ldServiceClient)
|
|
166
|
+
};
|
|
166
167
|
singletons === null || singletons === void 0 ? void 0 : singletons.push({
|
|
167
168
|
provide: LD_SERVICE_TOKEN,
|
|
168
169
|
useValue: ldService
|
|
@@ -176,6 +177,22 @@ describe(`[launchdarkly-service] ${LDProvider.name}`, ()=>{
|
|
|
176
177
|
}));
|
|
177
178
|
expect(ldServiceClient.waitForInitialization).toHaveBeenCalled();
|
|
178
179
|
});
|
|
180
|
+
describe('when application is provided', ()=>{
|
|
181
|
+
const application = {
|
|
182
|
+
id: 'some-application',
|
|
183
|
+
version: '1.2.3'
|
|
184
|
+
};
|
|
185
|
+
beforeEach(()=>ldService.application = application);
|
|
186
|
+
test('passes application to LDClient', async ()=>{
|
|
187
|
+
subject();
|
|
188
|
+
await waitFor(()=>expectLDProvider());
|
|
189
|
+
expect(ldService.getClient).toHaveBeenCalledWith(expect.objectContaining({
|
|
190
|
+
options: {
|
|
191
|
+
application
|
|
192
|
+
}
|
|
193
|
+
}));
|
|
194
|
+
});
|
|
195
|
+
});
|
|
179
196
|
});
|
|
180
197
|
});
|
|
181
198
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/tests/ld-provider.test.tsx"],"sourcesContent":["import '@testing-library/jest-dom';\nimport { Log } from '@servicetitan/log-service';\nimport { Provider, useDependencies } from '@servicetitan/react-ioc';\nimport { render, screen, waitFor } from '@testing-library/react';\nimport { LDClient } from 'launchdarkly-js-client-sdk';\nimport { act, FC, Fragment } from 'react';\n\nimport { FeatureFlagStore } from '../feature-flag-store';\nimport { getLDClient } from '../get-ld-client';\nimport { LDProvider } from '../ld-provider';\nimport { LD_SERVICE_TOKEN, LDService } from '../types';\n\nfunction formatProps(props: Record<string, any>) {\n return Object.entries(props).map(([key, value]) => {\n return `${key}=${value.toString()}`;\n });\n}\n\nfunction mockComponent(name: string) {\n return {\n [name]: ({ children, ...props }: any) => (\n <Fragment>\n <div>{`<${name} ${formatProps(props)} />`}</div>\n {children}\n </Fragment>\n ),\n };\n}\n\njest.mock('launchdarkly-react-client-sdk', () => ({\n ...mockComponent('LDProvider'),\n}));\njest.mock('../feature-flag-provider', () => ({\n ...mockComponent('FeatureFlagProvider'),\n}));\njest.mock('../get-ld-client', () => ({\n getLDClient: jest.fn(),\n}));\n\ndescribe(`[launchdarkly-service] ${LDProvider.name}`, () => {\n const log: Partial<Log> = {\n error: jest.fn(),\n };\n const ldClient: Partial<LDClient> & { toString: () => string } = {\n on: jest.fn(),\n off: jest.fn(),\n waitUntilReady: jest.fn().mockResolvedValue(undefined),\n waitForInitialization: jest.fn().mockResolvedValue(undefined),\n toString: () => 'ldClient',\n };\n let singletons: Required<Parameters<typeof Provider>[0]>['singletons'];\n let props: Parameters<typeof LDProvider>[0];\n let providedFeatureFlagStore: FeatureFlagStore;\n let renderCount: number;\n\n const Component: FC = () => {\n [providedFeatureFlagStore] = useDependencies(FeatureFlagStore);\n renderCount += 1;\n return <div>{'<Component />'}</div>;\n };\n\n beforeEach(() => {\n jest.clearAllMocks();\n jest.mocked(getLDClient).mockReturnValue(ldClient as LDClient);\n renderCount = 0;\n singletons = [];\n props = { clientSideID: 'foo', projectName: 'bar' };\n });\n\n const subject = () => {\n return render(\n <Provider singletons={singletons}>\n <LDProvider {...props}>\n <Component />\n </LDProvider>\n </Provider>\n );\n };\n\n function expectLDProvider(props?: Record<string, any>) {\n if (props) {\n expect(screen.getByText(`<LDProvider ${formatProps(props)} />`)).toBeInTheDocument();\n } else {\n expect(screen.getByText(/<LDProvider .* \\/>/)).toBeInTheDocument();\n }\n }\n\n test('renders base LDProvider and FeatureFlagProvider after client is ready and initialized', async () => {\n subject();\n\n await waitFor(() => expectLDProvider({ clientSideID: props.clientSideID, ldClient }));\n\n expect(screen.getByText('<FeatureFlagProvider />')).toBeInTheDocument();\n expect(screen.getByText('<Component />')).toBeInTheDocument();\n expect(ldClient.waitForInitialization).toHaveBeenCalled();\n });\n\n test('provides FeatureFlagStore', async () => {\n subject();\n\n await waitFor(() => expectLDProvider());\n\n expect(providedFeatureFlagStore).toBeDefined();\n });\n\n describe('when waitForInitialization is false', () => {\n let initializedCallback: Function;\n\n beforeEach(() => {\n props.waitForInitialization = false;\n jest.mocked(ldClient.on)?.mockImplementation((event, callback) => {\n if (event === 'initialized') {\n initializedCallback = callback;\n }\n });\n });\n\n test('does not wait for client to initialize', async () => {\n subject();\n\n await waitFor(() => expectLDProvider());\n\n expect(ldClient.waitForInitialization).not.toHaveBeenCalled();\n });\n\n test('re-renders when client is initialized', async () => {\n subject();\n\n await waitFor(() => expectLDProvider());\n expect(renderCount).toBe(1);\n\n act(() => initializedCallback?.());\n expect(renderCount).toBe(2);\n });\n\n test('deregisters listener when component is unmounted', async () => {\n const { unmount } = subject();\n\n await waitFor(() => expectLDProvider());\n\n unmount();\n\n expect(jest.mocked(ldClient.off)).toHaveBeenCalledWith(\n 'initialized',\n initializedCallback\n );\n });\n });\n\n describe('when initialization fails', () => {\n beforeEach(() => {\n jest.mocked(ldClient.waitForInitialization)?.mockRejectedValue('Oops!');\n });\n\n test('renders base LDProvider', async () => {\n subject();\n\n await waitFor(() => expectLDProvider());\n });\n\n describe('when log service is provided', () => {\n beforeEach(() => singletons.push({ provide: Log, useValue: log }));\n\n test('logs error', async () => {\n subject();\n\n await waitFor(() => expectLDProvider());\n\n expect(log.error).toHaveBeenCalledWith({\n category: 'LaunchDarkly',\n code: 'InitializationFailed',\n error: 'Oops!',\n message: `\"${props.projectName}\" client failed to initialize`,\n });\n });\n });\n });\n\n describe('when LDService is provided', () => {\n const ldServiceClient: Partial<LDClient> & { toString: () => string } = {\n waitUntilReady: jest.fn().mockResolvedValue(undefined),\n waitForInitialization: jest.fn().mockResolvedValue(undefined),\n toString: () => 'ldServiceClient',\n };\n const ldService: LDService = {\n getClient: jest.fn().mockReturnValue(ldServiceClient),\n };\n\n beforeEach(() => {\n singletons?.push({ provide: LD_SERVICE_TOKEN, useValue: ldService });\n });\n\n test('uses LDService client', async () => {\n subject();\n\n await waitFor(() =>\n expectLDProvider({ clientSideID: props.clientSideID, ldClient: ldServiceClient })\n );\n\n expect(ldServiceClient.waitForInitialization).toHaveBeenCalled();\n });\n });\n});\n"],"names":["Log","Provider","useDependencies","render","screen","waitFor","act","Fragment","FeatureFlagStore","getLDClient","LDProvider","LD_SERVICE_TOKEN","formatProps","props","Object","entries","map","key","value","toString","mockComponent","name","children","div","jest","mock","fn","describe","log","error","ldClient","on","off","waitUntilReady","mockResolvedValue","undefined","waitForInitialization","singletons","providedFeatureFlagStore","renderCount","Component","beforeEach","clearAllMocks","mocked","mockReturnValue","clientSideID","projectName","subject","expectLDProvider","expect","getByText","toBeInTheDocument","test","toHaveBeenCalled","toBeDefined","initializedCallback","mockImplementation","event","callback","not","toBe","unmount","toHaveBeenCalledWith","mockRejectedValue","push","provide","useValue","category","code","message","ldServiceClient","ldService","getClient"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/tests/ld-provider.test.tsx"],"sourcesContent":["import { Log } from '@servicetitan/log-service';\nimport { Provider, useDependencies } from '@servicetitan/react-ioc';\nimport '@testing-library/jest-dom';\nimport { render, screen, waitFor } from '@testing-library/react';\nimport { LDClient } from 'launchdarkly-js-client-sdk';\nimport { act, FC, Fragment } from 'react';\nimport { FeatureFlagStore } from '../feature-flag-store';\nimport { getLDClient } from '../get-ld-client';\nimport { LDProvider } from '../ld-provider';\nimport { LD_SERVICE_TOKEN, LDService } from '../types';\n\nfunction formatProps(props: Record<string, any>) {\n return Object.entries(props).map(([key, value]) => {\n return `${key}=${value.toString()}`;\n });\n}\n\nfunction mockComponent(name: string) {\n return {\n [name]: ({ children, ...props }: any) => (\n <Fragment>\n <div>{`<${name} ${formatProps(props)} />`}</div>\n {children}\n </Fragment>\n ),\n };\n}\n\njest.mock('launchdarkly-react-client-sdk', () => ({\n ...mockComponent('LDProvider'),\n}));\njest.mock('../feature-flag-provider', () => ({\n ...mockComponent('FeatureFlagProvider'),\n}));\njest.mock('../get-ld-client', () => ({\n getLDClient: jest.fn(),\n}));\n\ndescribe(`[launchdarkly-service] ${LDProvider.name}`, () => {\n const log: Partial<Log> = {\n error: jest.fn(),\n };\n const ldClient: Partial<LDClient> & { toString: () => string } = {\n on: jest.fn(),\n off: jest.fn(),\n waitUntilReady: jest.fn().mockResolvedValue(undefined),\n waitForInitialization: jest.fn().mockResolvedValue(undefined),\n toString: () => 'ldClient',\n };\n let singletons: Required<Parameters<typeof Provider>[0]>['singletons'];\n let props: Parameters<typeof LDProvider>[0];\n let providedFeatureFlagStore: FeatureFlagStore;\n let renderCount: number;\n\n const Component: FC = () => {\n [providedFeatureFlagStore] = useDependencies(FeatureFlagStore);\n renderCount += 1;\n return <div>{'<Component />'}</div>;\n };\n\n beforeEach(() => {\n jest.clearAllMocks();\n jest.mocked(getLDClient).mockReturnValue(ldClient as LDClient);\n renderCount = 0;\n singletons = [];\n props = { clientSideID: 'foo', projectName: 'bar' };\n });\n\n const subject = () => {\n return render(\n <Provider singletons={singletons}>\n <LDProvider {...props}>\n <Component />\n </LDProvider>\n </Provider>\n );\n };\n\n function expectLDProvider(props?: Record<string, any>) {\n if (props) {\n expect(screen.getByText(`<LDProvider ${formatProps(props)} />`)).toBeInTheDocument();\n } else {\n expect(screen.getByText(/<LDProvider .* \\/>/)).toBeInTheDocument();\n }\n }\n\n test('renders base LDProvider and FeatureFlagProvider after client is ready and initialized', async () => {\n subject();\n\n await waitFor(() => expectLDProvider({ clientSideID: props.clientSideID, ldClient }));\n\n expect(screen.getByText('<FeatureFlagProvider />')).toBeInTheDocument();\n expect(screen.getByText('<Component />')).toBeInTheDocument();\n expect(ldClient.waitForInitialization).toHaveBeenCalled();\n });\n\n test('provides FeatureFlagStore', async () => {\n subject();\n\n await waitFor(() => expectLDProvider());\n\n expect(providedFeatureFlagStore).toBeDefined();\n });\n\n describe('when waitForInitialization is false', () => {\n let initializedCallback: Function;\n\n beforeEach(() => {\n props.waitForInitialization = false;\n jest.mocked(ldClient.on)?.mockImplementation((event, callback) => {\n if (event === 'initialized') {\n initializedCallback = callback;\n }\n });\n });\n\n test('does not wait for client to initialize', async () => {\n subject();\n\n await waitFor(() => expectLDProvider());\n\n expect(ldClient.waitForInitialization).not.toHaveBeenCalled();\n });\n\n test('re-renders when client is initialized', async () => {\n subject();\n\n await waitFor(() => expectLDProvider());\n expect(renderCount).toBe(1);\n\n act(() => initializedCallback?.());\n expect(renderCount).toBe(2);\n });\n\n test('deregisters listener when component is unmounted', async () => {\n const { unmount } = subject();\n\n await waitFor(() => expectLDProvider());\n\n unmount();\n\n expect(jest.mocked(ldClient.off)).toHaveBeenCalledWith(\n 'initialized',\n initializedCallback\n );\n });\n });\n\n describe('when initialization fails', () => {\n beforeEach(() => {\n jest.mocked(ldClient.waitForInitialization)?.mockRejectedValue('Oops!');\n });\n\n test('renders base LDProvider', async () => {\n subject();\n\n await waitFor(() => expectLDProvider());\n });\n\n describe('when log service is provided', () => {\n beforeEach(() => singletons.push({ provide: Log, useValue: log }));\n\n test('logs error', async () => {\n subject();\n\n await waitFor(() => expectLDProvider());\n\n expect(log.error).toHaveBeenCalledWith({\n category: 'LaunchDarkly',\n code: 'InitializationFailed',\n error: 'Oops!',\n message: `\"${props.projectName}\" client failed to initialize`,\n });\n });\n });\n });\n\n describe('when LDService is provided', () => {\n const ldServiceClient: Partial<LDClient> & { toString: () => string } = {\n waitUntilReady: jest.fn().mockResolvedValue(undefined),\n waitForInitialization: jest.fn().mockResolvedValue(undefined),\n toString: () => 'ldServiceClient',\n };\n let ldService: LDService;\n\n beforeEach(() => {\n ldService = { getClient: jest.fn().mockReturnValue(ldServiceClient) };\n singletons?.push({ provide: LD_SERVICE_TOKEN, useValue: ldService });\n });\n\n test('uses LDService client', async () => {\n subject();\n\n await waitFor(() =>\n expectLDProvider({ clientSideID: props.clientSideID, ldClient: ldServiceClient })\n );\n\n expect(ldServiceClient.waitForInitialization).toHaveBeenCalled();\n });\n\n describe('when application is provided', () => {\n const application = { id: 'some-application', version: '1.2.3' };\n\n beforeEach(() => (ldService.application = application));\n\n test('passes application to LDClient', async () => {\n subject();\n\n await waitFor(() => expectLDProvider());\n\n expect(ldService.getClient).toHaveBeenCalledWith(\n expect.objectContaining({\n options: { application },\n })\n );\n });\n });\n });\n});\n"],"names":["Log","Provider","useDependencies","render","screen","waitFor","act","Fragment","FeatureFlagStore","getLDClient","LDProvider","LD_SERVICE_TOKEN","formatProps","props","Object","entries","map","key","value","toString","mockComponent","name","children","div","jest","mock","fn","describe","log","error","ldClient","on","off","waitUntilReady","mockResolvedValue","undefined","waitForInitialization","singletons","providedFeatureFlagStore","renderCount","Component","beforeEach","clearAllMocks","mocked","mockReturnValue","clientSideID","projectName","subject","expectLDProvider","expect","getByText","toBeInTheDocument","test","toHaveBeenCalled","toBeDefined","initializedCallback","mockImplementation","event","callback","not","toBe","unmount","toHaveBeenCalledWith","mockRejectedValue","push","provide","useValue","category","code","message","ldServiceClient","ldService","getClient","application","id","version","objectContaining","options"],"mappings":"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"}
|
|
@@ -5,17 +5,24 @@ import { getLDClient } from '../get-ld-client';
|
|
|
5
5
|
import { LDServiceProvider } from '../ld-service-provider';
|
|
6
6
|
import { LD_SERVICE_TOKEN } from '../types';
|
|
7
7
|
describe(`[launchdarkly-service] ${LDServiceProvider.name}`, ()=>{
|
|
8
|
+
let providedApplication;
|
|
8
9
|
let providedLDService;
|
|
9
10
|
let singletons;
|
|
11
|
+
let props;
|
|
10
12
|
const Component = ()=>{
|
|
11
13
|
[providedLDService] = useDependencies(LD_SERVICE_TOKEN);
|
|
14
|
+
providedApplication = providedLDService.application;
|
|
12
15
|
return null;
|
|
13
16
|
};
|
|
14
|
-
beforeEach(()=>
|
|
17
|
+
beforeEach(()=>{
|
|
18
|
+
singletons = [];
|
|
19
|
+
props = {};
|
|
20
|
+
});
|
|
15
21
|
const subject = ()=>{
|
|
16
22
|
return render(/*#__PURE__*/ _jsx(Provider, {
|
|
17
23
|
singletons: singletons,
|
|
18
24
|
children: /*#__PURE__*/ _jsx(LDServiceProvider, {
|
|
25
|
+
...props,
|
|
19
26
|
children: /*#__PURE__*/ _jsx(Component, {})
|
|
20
27
|
})
|
|
21
28
|
}));
|
|
@@ -26,8 +33,14 @@ describe(`[launchdarkly-service] ${LDServiceProvider.name}`, ()=>{
|
|
|
26
33
|
getClient: getLDClient
|
|
27
34
|
});
|
|
28
35
|
});
|
|
36
|
+
test('provides no application', ()=>{
|
|
37
|
+
subject();
|
|
38
|
+
expect(providedApplication).toBeUndefined();
|
|
39
|
+
});
|
|
29
40
|
describe('when LDService is already provided', ()=>{
|
|
30
|
-
const ldService =
|
|
41
|
+
const ldService = {
|
|
42
|
+
getClient: jest.fn()
|
|
43
|
+
};
|
|
31
44
|
beforeEach(()=>singletons === null || singletons === void 0 ? void 0 : singletons.push({
|
|
32
45
|
provide: LD_SERVICE_TOKEN,
|
|
33
46
|
useValue: ldService
|
|
@@ -37,6 +50,80 @@ describe(`[launchdarkly-service] ${LDServiceProvider.name}`, ()=>{
|
|
|
37
50
|
expect(providedLDService).toBe(ldService);
|
|
38
51
|
});
|
|
39
52
|
});
|
|
53
|
+
describe('with "application" prop', ()=>{
|
|
54
|
+
const application = {
|
|
55
|
+
id: 'some-application',
|
|
56
|
+
version: '1.0.0'
|
|
57
|
+
};
|
|
58
|
+
beforeEach(()=>props.application = application);
|
|
59
|
+
function itProvidesApplication() {
|
|
60
|
+
test('provides application', ()=>{
|
|
61
|
+
subject();
|
|
62
|
+
expect(providedApplication).toBe(application);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
itProvidesApplication();
|
|
66
|
+
describe('when rerendered with new application', ()=>{
|
|
67
|
+
const newApplication = {
|
|
68
|
+
id: 'foo'
|
|
69
|
+
};
|
|
70
|
+
beforeEach(()=>{
|
|
71
|
+
const { rerender } = subject();
|
|
72
|
+
rerender(/*#__PURE__*/ _jsx(LDServiceProvider, {
|
|
73
|
+
application: newApplication,
|
|
74
|
+
children: /*#__PURE__*/ _jsx(Component, {})
|
|
75
|
+
}));
|
|
76
|
+
});
|
|
77
|
+
test('provides new application', ()=>{
|
|
78
|
+
expect(providedApplication).toBe(newApplication);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
describe('when rerendered without application', ()=>{
|
|
82
|
+
beforeEach(()=>{
|
|
83
|
+
const { rerender } = subject();
|
|
84
|
+
rerender(/*#__PURE__*/ _jsx(LDServiceProvider, {
|
|
85
|
+
children: /*#__PURE__*/ _jsx(Component, {})
|
|
86
|
+
}));
|
|
87
|
+
});
|
|
88
|
+
test('drops application', ()=>{
|
|
89
|
+
expect(providedApplication).toBeUndefined();
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
describe('when LDService is already provided', ()=>{
|
|
93
|
+
let providedLDService;
|
|
94
|
+
beforeEach(()=>{
|
|
95
|
+
providedLDService = {
|
|
96
|
+
getClient: jest.fn()
|
|
97
|
+
};
|
|
98
|
+
singletons === null || singletons === void 0 ? void 0 : singletons.push({
|
|
99
|
+
provide: LD_SERVICE_TOKEN,
|
|
100
|
+
useValue: providedLDService
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
itProvidesApplication();
|
|
104
|
+
test('unmount resets LDService.application', ()=>{
|
|
105
|
+
const { unmount } = subject();
|
|
106
|
+
unmount();
|
|
107
|
+
expect(providedLDService.application).toBeUndefined();
|
|
108
|
+
});
|
|
109
|
+
describe('when provided LDService has application', ()=>{
|
|
110
|
+
const otherApplication = {
|
|
111
|
+
id: 'other-application',
|
|
112
|
+
version: '2.0.0'
|
|
113
|
+
};
|
|
114
|
+
beforeEach(()=>providedLDService.application = otherApplication);
|
|
115
|
+
test('provides inherited application', ()=>{
|
|
116
|
+
subject();
|
|
117
|
+
expect(providedApplication).toBe(otherApplication);
|
|
118
|
+
});
|
|
119
|
+
test('unmount preserves LDService.application', ()=>{
|
|
120
|
+
const { unmount } = subject();
|
|
121
|
+
unmount();
|
|
122
|
+
expect(providedLDService.application).toBe(otherApplication);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
});
|
|
40
127
|
});
|
|
41
128
|
|
|
42
129
|
//# sourceMappingURL=ld-service-provider.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/tests/ld-service-provider.test.tsx"],"sourcesContent":["import { Provider, useDependencies } from '@servicetitan/react-ioc';\nimport { render } from '@testing-library/react';\nimport { FC } from 'react';\
|
|
1
|
+
{"version":3,"sources":["../../src/tests/ld-service-provider.test.tsx"],"sourcesContent":["import { Provider, useDependencies } from '@servicetitan/react-ioc';\nimport { render } from '@testing-library/react';\nimport { ComponentProps, FC } from 'react';\nimport { getLDClient } from '../get-ld-client';\nimport { LDServiceProvider } from '../ld-service-provider';\nimport { LD_SERVICE_TOKEN, LDApplication, LDService } from '../types';\n\ndescribe(`[launchdarkly-service] ${LDServiceProvider.name}`, () => {\n let providedApplication: LDApplication | undefined;\n let providedLDService: LDService;\n let singletons: Parameters<typeof Provider>[0]['singletons'];\n let props: ComponentProps<typeof LDServiceProvider>;\n\n const Component: FC = () => {\n [providedLDService] = useDependencies(LD_SERVICE_TOKEN);\n providedApplication = providedLDService.application;\n return null;\n };\n\n beforeEach(() => {\n singletons = [];\n props = {};\n });\n\n const subject = () => {\n return render(\n <Provider singletons={singletons}>\n <LDServiceProvider {...props}>\n <Component />\n </LDServiceProvider>\n </Provider>\n );\n };\n\n test('provides LDService instance', () => {\n subject();\n\n expect(providedLDService).toEqual({ getClient: getLDClient });\n });\n\n test('provides no application', () => {\n subject();\n\n expect(providedApplication).toBeUndefined();\n });\n\n describe('when LDService is already provided', () => {\n const ldService: LDService = { getClient: jest.fn() };\n\n beforeEach(() => singletons?.push({ provide: LD_SERVICE_TOKEN, useValue: ldService }));\n\n test('provides existing LDService', () => {\n subject();\n\n expect(providedLDService).toBe(ldService);\n });\n });\n\n describe('with \"application\" prop', () => {\n const application = { id: 'some-application', version: '1.0.0' };\n\n beforeEach(() => (props.application = application));\n\n function itProvidesApplication() {\n test('provides application', () => {\n subject();\n\n expect(providedApplication).toBe(application);\n });\n }\n\n itProvidesApplication();\n\n describe('when rerendered with new application', () => {\n const newApplication = { id: 'foo' };\n\n beforeEach(() => {\n const { rerender } = subject();\n\n rerender(\n <LDServiceProvider application={newApplication}>\n <Component />\n </LDServiceProvider>\n );\n });\n\n test('provides new application', () => {\n expect(providedApplication).toBe(newApplication);\n });\n });\n\n describe('when rerendered without application', () => {\n beforeEach(() => {\n const { rerender } = subject();\n\n rerender(\n <LDServiceProvider>\n <Component />\n </LDServiceProvider>\n );\n });\n\n test('drops application', () => {\n expect(providedApplication).toBeUndefined();\n });\n });\n\n describe('when LDService is already provided', () => {\n let providedLDService: LDService;\n\n beforeEach(() => {\n providedLDService = { getClient: jest.fn() };\n singletons?.push({ provide: LD_SERVICE_TOKEN, useValue: providedLDService });\n });\n\n itProvidesApplication();\n\n test('unmount resets LDService.application', () => {\n const { unmount } = subject();\n unmount();\n\n expect(providedLDService.application).toBeUndefined();\n });\n\n describe('when provided LDService has application', () => {\n const otherApplication = { id: 'other-application', version: '2.0.0' };\n\n beforeEach(() => (providedLDService.application = otherApplication));\n\n test('provides inherited application', () => {\n subject();\n\n expect(providedApplication).toBe(otherApplication);\n });\n\n test('unmount preserves LDService.application', () => {\n const { unmount } = subject();\n unmount();\n\n expect(providedLDService.application).toBe(otherApplication);\n });\n });\n });\n });\n});\n"],"names":["Provider","useDependencies","render","getLDClient","LDServiceProvider","LD_SERVICE_TOKEN","describe","name","providedApplication","providedLDService","singletons","props","Component","application","beforeEach","subject","test","expect","toEqual","getClient","toBeUndefined","ldService","jest","fn","push","provide","useValue","toBe","id","version","itProvidesApplication","newApplication","rerender","unmount","otherApplication"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/tests/with-feature-flag.test.tsx"],"sourcesContent":["import '@testing-library/jest-dom';\nimport { render, screen } from '@testing-library/react';\nimport { LDFlagValue, useFlags } from 'launchdarkly-react-client-sdk';\
|
|
1
|
+
{"version":3,"sources":["../../src/tests/with-feature-flag.test.tsx"],"sourcesContent":["import '@testing-library/jest-dom';\nimport { render, screen } from '@testing-library/react';\nimport { LDFlagValue, useFlags } from 'launchdarkly-react-client-sdk';\nimport { WithFeatureFlag } from '../with-feature-flag';\n\njest.mock('launchdarkly-react-client-sdk', () => ({\n useFlags: jest.fn(),\n}));\n\ndescribe(`[launchdarkly-service] ${WithFeatureFlag.name}`, () => {\n const flagKey = 'foo';\n const childContent = 'Flag is ON';\n let props: Parameters<typeof WithFeatureFlag>[0];\n let flagValue: LDFlagValue;\n\n beforeEach(() => {\n props = { flagKey, children: childContent };\n jest.mocked(useFlags).mockImplementation(() => ({ [flagKey]: flagValue }));\n });\n\n const subject = () => render(<WithFeatureFlag {...props} />);\n\n function itRendersNothing() {\n test('renders nothing', () => {\n subject();\n\n expect(screen.queryByText(childContent)).not.toBeInTheDocument();\n });\n }\n\n function itRendersChildren() {\n test('renders children', () => {\n subject();\n\n expect(screen.getByText(childContent)).toBeInTheDocument();\n });\n }\n\n itRendersNothing();\n\n describe('when flag is turned on', () => {\n beforeEach(() => (flagValue = true));\n\n itRendersChildren();\n\n describe('with on prop', () => {\n const onContent = 'Flag is ENABLED';\n\n beforeEach(() => (props.on = () => onContent));\n\n test('renders on content', () => {\n subject();\n\n expect(screen.getByText(onContent)).toBeInTheDocument();\n });\n });\n });\n\n describe('when flag is turned off', () => {\n beforeEach(() => (flagValue = false));\n\n itRendersNothing();\n\n describe('with off prop', () => {\n const offContent = 'flag is OFF';\n\n beforeEach(() => (props.off = () => offContent));\n\n test('renders off content', () => {\n subject();\n\n expect(screen.getByText(offContent)).toBeInTheDocument();\n });\n });\n });\n\n describe('with flag value', () => {\n beforeEach(() => (props.value = 'bar'));\n\n itRendersNothing();\n\n describe('when flag matches value', () => {\n beforeEach(() => (flagValue = props.value));\n\n itRendersChildren();\n });\n });\n});\n"],"names":["render","screen","useFlags","WithFeatureFlag","jest","mock","fn","describe","name","flagKey","childContent","props","flagValue","beforeEach","children","mocked","mockImplementation","subject","itRendersNothing","test","expect","queryByText","not","toBeInTheDocument","itRendersChildren","getByText","onContent","on","offContent","off","value"],"mappings":";AAAA,OAAO,4BAA4B;AACnC,SAASA,MAAM,EAAEC,MAAM,QAAQ,yBAAyB;AACxD,SAAsBC,QAAQ,QAAQ,gCAAgC;AACtE,SAASC,eAAe,QAAQ,uBAAuB;AAEvDC,KAAKC,IAAI,CAAC,iCAAiC,IAAO,CAAA;QAC9CH,UAAUE,KAAKE,EAAE;IACrB,CAAA;AAEAC,SAAS,CAAC,uBAAuB,EAAEJ,gBAAgBK,IAAI,EAAE,EAAE;IACvD,MAAMC,UAAU;IAChB,MAAMC,eAAe;IACrB,IAAIC;IACJ,IAAIC;IAEJC,WAAW;QACPF,QAAQ;YAAEF;YAASK,UAAUJ;QAAa;QAC1CN,KAAKW,MAAM,CAACb,UAAUc,kBAAkB,CAAC,IAAO,CAAA;gBAAE,CAACP,QAAQ,EAAEG;YAAU,CAAA;IAC3E;IAEA,MAAMK,UAAU,IAAMjB,qBAAO,KAACG;YAAiB,GAAGQ,KAAK;;IAEvD,SAASO;QACLC,KAAK,mBAAmB;YACpBF;YAEAG,OAAOnB,OAAOoB,WAAW,CAACX,eAAeY,GAAG,CAACC,iBAAiB;QAClE;IACJ;IAEA,SAASC;QACLL,KAAK,oBAAoB;YACrBF;YAEAG,OAAOnB,OAAOwB,SAAS,CAACf,eAAea,iBAAiB;QAC5D;IACJ;IAEAL;IAEAX,SAAS,0BAA0B;QAC/BM,WAAW,IAAOD,YAAY;QAE9BY;QAEAjB,SAAS,gBAAgB;YACrB,MAAMmB,YAAY;YAElBb,WAAW,IAAOF,MAAMgB,EAAE,GAAG,IAAMD;YAEnCP,KAAK,sBAAsB;gBACvBF;gBAEAG,OAAOnB,OAAOwB,SAAS,CAACC,YAAYH,iBAAiB;YACzD;QACJ;IACJ;IAEAhB,SAAS,2BAA2B;QAChCM,WAAW,IAAOD,YAAY;QAE9BM;QAEAX,SAAS,iBAAiB;YACtB,MAAMqB,aAAa;YAEnBf,WAAW,IAAOF,MAAMkB,GAAG,GAAG,IAAMD;YAEpCT,KAAK,uBAAuB;gBACxBF;gBAEAG,OAAOnB,OAAOwB,SAAS,CAACG,aAAaL,iBAAiB;YAC1D;QACJ;IACJ;IAEAhB,SAAS,mBAAmB;QACxBM,WAAW,IAAOF,MAAMmB,KAAK,GAAG;QAEhCZ;QAEAX,SAAS,2BAA2B;YAChCM,WAAW,IAAOD,YAAYD,MAAMmB,KAAK;YAEzCN;QACJ;IACJ;AACJ"}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type { LDClient, LDContext, LDOptions } from 'launchdarkly-js-client-sdk';
|
|
2
2
|
export declare const LD_SERVICE_TOKEN: import("@servicetitan/react-ioc").SymbolToken<LDService>;
|
|
3
|
+
export type LDApplication = LDOptions['application'];
|
|
3
4
|
export interface ClientConfig {
|
|
4
5
|
clientSideID: string;
|
|
5
6
|
context?: LDContext;
|
|
@@ -7,6 +8,7 @@ export interface ClientConfig {
|
|
|
7
8
|
}
|
|
8
9
|
export interface LDService {
|
|
9
10
|
getClient: (config: ClientConfig) => LDClient;
|
|
11
|
+
application?: LDApplication;
|
|
10
12
|
}
|
|
11
13
|
export type FlagKey<S, K extends keyof S> = K & {
|
|
12
14
|
typeRef?: S[K];
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAEjF,eAAO,MAAM,gBAAgB,0DAA6C,CAAC;AAE3E,MAAM,MAAM,aAAa,GAAG,SAAS,CAAC,aAAa,CAAC,CAAC;AAErD,MAAM,WAAW,YAAY;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,OAAO,CAAC,EAAE,SAAS,CAAC;CACvB;AAED,MAAM,WAAW,SAAS;IACtB,SAAS,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,QAAQ,CAAC;IAC9C,WAAW,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED,MAAM,MAAM,OAAO,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,CAAC,IAAI,CAAC,GAAG;IAC5C,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;CAClB,CAAC"}
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["import { symbolToken } from '@servicetitan/react-ioc';\nimport
|
|
1
|
+
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["import { symbolToken } from '@servicetitan/react-ioc';\nimport type { LDClient, LDContext, LDOptions } from 'launchdarkly-js-client-sdk';\n\nexport const LD_SERVICE_TOKEN = symbolToken<LDService>('LD_SERVICE_TOKEN');\n\nexport type LDApplication = LDOptions['application'];\n\nexport interface ClientConfig {\n clientSideID: string;\n context?: LDContext;\n options?: LDOptions;\n}\n\nexport interface LDService {\n getClient: (config: ClientConfig) => LDClient;\n application?: LDApplication;\n}\n\nexport type FlagKey<S, K extends keyof S> = K & {\n typeRef?: S[K];\n};\n"],"names":["symbolToken","LD_SERVICE_TOKEN"],"mappings":"AAAA,SAASA,WAAW,QAAQ,0BAA0B;AAGtD,OAAO,MAAMC,mBAAmBD,YAAuB,oBAAoB"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@servicetitan/launchdarkly-service",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "",
|
|
5
|
-
"homepage": "https://docs.st.dev/docs/frontend/launchdarkly-service",
|
|
3
|
+
"version": "34.0.0",
|
|
4
|
+
"description": "Service for creating and reusing client connections for LaunchDarkly",
|
|
5
|
+
"homepage": "https://docs.st.dev/docs/frontend/uikit/launchdarkly-service",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "https://github.com/servicetitan/uikit.git",
|
|
@@ -16,19 +16,19 @@
|
|
|
16
16
|
"src"
|
|
17
17
|
],
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"@servicetitan/log-service": "
|
|
20
|
-
"@servicetitan/react-ioc": "
|
|
19
|
+
"@servicetitan/log-service": "34.0.0",
|
|
20
|
+
"@servicetitan/react-ioc": "34.0.0",
|
|
21
21
|
"@testing-library/dom": "^10.4.1",
|
|
22
22
|
"@testing-library/jest-dom": "^6.9.1",
|
|
23
|
-
"@testing-library/react": "^16.3.
|
|
23
|
+
"@testing-library/react": "^16.3.2",
|
|
24
24
|
"launchdarkly-js-client-sdk": "3.5.0",
|
|
25
25
|
"launchdarkly-react-client-sdk": "3.6.1",
|
|
26
26
|
"mobx": "~6.13.7",
|
|
27
27
|
"react": "~18.3.1"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
|
-
"@servicetitan/log-service": "
|
|
31
|
-
"@servicetitan/react-ioc": "
|
|
30
|
+
"@servicetitan/log-service": "34.0.0",
|
|
31
|
+
"@servicetitan/react-ioc": "34.0.0",
|
|
32
32
|
"launchdarkly-js-client-sdk": "3.5.0",
|
|
33
33
|
"launchdarkly-react-client-sdk": "3.6.1",
|
|
34
34
|
"mobx": ">= 6.13.7",
|
|
@@ -40,5 +40,5 @@
|
|
|
40
40
|
"cli": {
|
|
41
41
|
"webpack": false
|
|
42
42
|
},
|
|
43
|
-
"gitHead": "
|
|
43
|
+
"gitHead": "e193dc22703963f67099874a24de535d0696b6e2"
|
|
44
44
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { useDependencies } from '@servicetitan/react-ioc';
|
|
2
2
|
import { useFlags, useLDClient } from 'launchdarkly-react-client-sdk';
|
|
3
3
|
import { PropsWithChildren, useEffect, useState } from 'react';
|
|
4
|
-
|
|
5
4
|
import { FeatureFlagStore } from './feature-flag-store';
|
|
6
5
|
|
|
7
6
|
export function FeatureFlagProvider({ children }: PropsWithChildren) {
|
package/src/ld-provider.tsx
CHANGED
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
ProviderConfig as BaseProviderConfig,
|
|
7
7
|
} from 'launchdarkly-react-client-sdk';
|
|
8
8
|
import { FC, PropsWithChildren, useEffect, useReducer, useState } from 'react';
|
|
9
|
-
|
|
10
9
|
import { FeatureFlagProvider } from './feature-flag-provider';
|
|
11
10
|
import { FeatureFlagStore } from './feature-flag-store';
|
|
12
11
|
import { getLDClient } from './get-ld-client';
|
|
@@ -27,7 +26,10 @@ export const LDProvider: FC<PropsWithChildren<ProviderConfig>> = provide({
|
|
|
27
26
|
|
|
28
27
|
useEffect(() => {
|
|
29
28
|
(async function initializeClient() {
|
|
30
|
-
const
|
|
29
|
+
const options = ldService?.application
|
|
30
|
+
? { application: ldService.application }
|
|
31
|
+
: undefined;
|
|
32
|
+
const config: BaseProviderConfig = { clientSideID, context, options };
|
|
31
33
|
const client = ldService?.getClient(config) ?? getLDClient(config);
|
|
32
34
|
if (waitForInitialization) {
|
|
33
35
|
try {
|
|
@@ -1,12 +1,32 @@
|
|
|
1
|
-
import { provide } from '@servicetitan/react-ioc';
|
|
2
|
-
|
|
1
|
+
import { provide, useDependencies } from '@servicetitan/react-ioc';
|
|
2
|
+
import { FC, PropsWithChildren, useEffect, useRef } from 'react';
|
|
3
3
|
import { getLDClient } from './get-ld-client';
|
|
4
|
-
import { LD_SERVICE_TOKEN, LDService } from './types';
|
|
4
|
+
import { LD_SERVICE_TOKEN, LDApplication, LDService } from './types';
|
|
5
5
|
|
|
6
6
|
const ldService: LDService = {
|
|
7
7
|
getClient: getLDClient,
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
interface LDServiceProviderProps {
|
|
11
|
+
application?: LDApplication;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const LDServiceProvider: FC<PropsWithChildren<LDServiceProviderProps>> = provide({
|
|
11
15
|
singletons: [{ provide: LD_SERVICE_TOKEN, useValue: ldService, inherit: true }],
|
|
12
|
-
})(({ children }) =>
|
|
16
|
+
})(({ children, application }) => {
|
|
17
|
+
const [providedLDService] = useDependencies(LD_SERVICE_TOKEN);
|
|
18
|
+
const prevApplication = useRef(providedLDService.application);
|
|
19
|
+
|
|
20
|
+
if (providedLDService === ldService || !providedLDService.application) {
|
|
21
|
+
providedLDService.application = application;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
return () => {
|
|
26
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
27
|
+
providedLDService.application = prevApplication.current;
|
|
28
|
+
};
|
|
29
|
+
}, [providedLDService]);
|
|
30
|
+
|
|
31
|
+
return children;
|
|
32
|
+
});
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import '@testing-library/jest-dom';
|
|
2
1
|
import { Provider } from '@servicetitan/react-ioc';
|
|
3
|
-
import
|
|
2
|
+
import '@testing-library/jest-dom';
|
|
4
3
|
import { render, screen } from '@testing-library/react';
|
|
5
|
-
|
|
4
|
+
import { useFlags, useLDClient } from 'launchdarkly-react-client-sdk';
|
|
5
|
+
import { FC } from 'react';
|
|
6
6
|
import { FeatureFlagProvider } from '../feature-flag-provider';
|
|
7
7
|
import { FeatureFlagStore } from '../feature-flag-store';
|
|
8
|
-
import { useFlags, useLDClient } from 'launchdarkly-react-client-sdk';
|
|
9
8
|
|
|
10
9
|
jest.mock('launchdarkly-react-client-sdk', () => ({
|
|
11
10
|
useLDClient: jest.fn(),
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import '@testing-library/jest-dom';
|
|
2
1
|
import { Log } from '@servicetitan/log-service';
|
|
3
2
|
import { Provider, useDependencies } from '@servicetitan/react-ioc';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
4
4
|
import { render, screen, waitFor } from '@testing-library/react';
|
|
5
5
|
import { LDClient } from 'launchdarkly-js-client-sdk';
|
|
6
6
|
import { act, FC, Fragment } from 'react';
|
|
7
|
-
|
|
8
7
|
import { FeatureFlagStore } from '../feature-flag-store';
|
|
9
8
|
import { getLDClient } from '../get-ld-client';
|
|
10
9
|
import { LDProvider } from '../ld-provider';
|
|
@@ -182,11 +181,10 @@ describe(`[launchdarkly-service] ${LDProvider.name}`, () => {
|
|
|
182
181
|
waitForInitialization: jest.fn().mockResolvedValue(undefined),
|
|
183
182
|
toString: () => 'ldServiceClient',
|
|
184
183
|
};
|
|
185
|
-
|
|
186
|
-
getClient: jest.fn().mockReturnValue(ldServiceClient),
|
|
187
|
-
};
|
|
184
|
+
let ldService: LDService;
|
|
188
185
|
|
|
189
186
|
beforeEach(() => {
|
|
187
|
+
ldService = { getClient: jest.fn().mockReturnValue(ldServiceClient) };
|
|
190
188
|
singletons?.push({ provide: LD_SERVICE_TOKEN, useValue: ldService });
|
|
191
189
|
});
|
|
192
190
|
|
|
@@ -199,5 +197,23 @@ describe(`[launchdarkly-service] ${LDProvider.name}`, () => {
|
|
|
199
197
|
|
|
200
198
|
expect(ldServiceClient.waitForInitialization).toHaveBeenCalled();
|
|
201
199
|
});
|
|
200
|
+
|
|
201
|
+
describe('when application is provided', () => {
|
|
202
|
+
const application = { id: 'some-application', version: '1.2.3' };
|
|
203
|
+
|
|
204
|
+
beforeEach(() => (ldService.application = application));
|
|
205
|
+
|
|
206
|
+
test('passes application to LDClient', async () => {
|
|
207
|
+
subject();
|
|
208
|
+
|
|
209
|
+
await waitFor(() => expectLDProvider());
|
|
210
|
+
|
|
211
|
+
expect(ldService.getClient).toHaveBeenCalledWith(
|
|
212
|
+
expect.objectContaining({
|
|
213
|
+
options: { application },
|
|
214
|
+
})
|
|
215
|
+
);
|
|
216
|
+
});
|
|
217
|
+
});
|
|
202
218
|
});
|
|
203
219
|
});
|
|
@@ -1,26 +1,31 @@
|
|
|
1
1
|
import { Provider, useDependencies } from '@servicetitan/react-ioc';
|
|
2
2
|
import { render } from '@testing-library/react';
|
|
3
|
-
import { FC } from 'react';
|
|
4
|
-
|
|
3
|
+
import { ComponentProps, FC } from 'react';
|
|
5
4
|
import { getLDClient } from '../get-ld-client';
|
|
6
5
|
import { LDServiceProvider } from '../ld-service-provider';
|
|
7
|
-
import { LD_SERVICE_TOKEN, LDService } from '../types';
|
|
6
|
+
import { LD_SERVICE_TOKEN, LDApplication, LDService } from '../types';
|
|
8
7
|
|
|
9
8
|
describe(`[launchdarkly-service] ${LDServiceProvider.name}`, () => {
|
|
9
|
+
let providedApplication: LDApplication | undefined;
|
|
10
10
|
let providedLDService: LDService;
|
|
11
11
|
let singletons: Parameters<typeof Provider>[0]['singletons'];
|
|
12
|
+
let props: ComponentProps<typeof LDServiceProvider>;
|
|
12
13
|
|
|
13
14
|
const Component: FC = () => {
|
|
14
15
|
[providedLDService] = useDependencies(LD_SERVICE_TOKEN);
|
|
16
|
+
providedApplication = providedLDService.application;
|
|
15
17
|
return null;
|
|
16
18
|
};
|
|
17
19
|
|
|
18
|
-
beforeEach(() =>
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
singletons = [];
|
|
22
|
+
props = {};
|
|
23
|
+
});
|
|
19
24
|
|
|
20
25
|
const subject = () => {
|
|
21
26
|
return render(
|
|
22
27
|
<Provider singletons={singletons}>
|
|
23
|
-
<LDServiceProvider>
|
|
28
|
+
<LDServiceProvider {...props}>
|
|
24
29
|
<Component />
|
|
25
30
|
</LDServiceProvider>
|
|
26
31
|
</Provider>
|
|
@@ -30,13 +35,17 @@ describe(`[launchdarkly-service] ${LDServiceProvider.name}`, () => {
|
|
|
30
35
|
test('provides LDService instance', () => {
|
|
31
36
|
subject();
|
|
32
37
|
|
|
33
|
-
expect(providedLDService).toEqual({
|
|
34
|
-
|
|
35
|
-
|
|
38
|
+
expect(providedLDService).toEqual({ getClient: getLDClient });
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('provides no application', () => {
|
|
42
|
+
subject();
|
|
43
|
+
|
|
44
|
+
expect(providedApplication).toBeUndefined();
|
|
36
45
|
});
|
|
37
46
|
|
|
38
47
|
describe('when LDService is already provided', () => {
|
|
39
|
-
const ldService =
|
|
48
|
+
const ldService: LDService = { getClient: jest.fn() };
|
|
40
49
|
|
|
41
50
|
beforeEach(() => singletons?.push({ provide: LD_SERVICE_TOKEN, useValue: ldService }));
|
|
42
51
|
|
|
@@ -46,4 +55,91 @@ describe(`[launchdarkly-service] ${LDServiceProvider.name}`, () => {
|
|
|
46
55
|
expect(providedLDService).toBe(ldService);
|
|
47
56
|
});
|
|
48
57
|
});
|
|
58
|
+
|
|
59
|
+
describe('with "application" prop', () => {
|
|
60
|
+
const application = { id: 'some-application', version: '1.0.0' };
|
|
61
|
+
|
|
62
|
+
beforeEach(() => (props.application = application));
|
|
63
|
+
|
|
64
|
+
function itProvidesApplication() {
|
|
65
|
+
test('provides application', () => {
|
|
66
|
+
subject();
|
|
67
|
+
|
|
68
|
+
expect(providedApplication).toBe(application);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
itProvidesApplication();
|
|
73
|
+
|
|
74
|
+
describe('when rerendered with new application', () => {
|
|
75
|
+
const newApplication = { id: 'foo' };
|
|
76
|
+
|
|
77
|
+
beforeEach(() => {
|
|
78
|
+
const { rerender } = subject();
|
|
79
|
+
|
|
80
|
+
rerender(
|
|
81
|
+
<LDServiceProvider application={newApplication}>
|
|
82
|
+
<Component />
|
|
83
|
+
</LDServiceProvider>
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('provides new application', () => {
|
|
88
|
+
expect(providedApplication).toBe(newApplication);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('when rerendered without application', () => {
|
|
93
|
+
beforeEach(() => {
|
|
94
|
+
const { rerender } = subject();
|
|
95
|
+
|
|
96
|
+
rerender(
|
|
97
|
+
<LDServiceProvider>
|
|
98
|
+
<Component />
|
|
99
|
+
</LDServiceProvider>
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('drops application', () => {
|
|
104
|
+
expect(providedApplication).toBeUndefined();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('when LDService is already provided', () => {
|
|
109
|
+
let providedLDService: LDService;
|
|
110
|
+
|
|
111
|
+
beforeEach(() => {
|
|
112
|
+
providedLDService = { getClient: jest.fn() };
|
|
113
|
+
singletons?.push({ provide: LD_SERVICE_TOKEN, useValue: providedLDService });
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
itProvidesApplication();
|
|
117
|
+
|
|
118
|
+
test('unmount resets LDService.application', () => {
|
|
119
|
+
const { unmount } = subject();
|
|
120
|
+
unmount();
|
|
121
|
+
|
|
122
|
+
expect(providedLDService.application).toBeUndefined();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('when provided LDService has application', () => {
|
|
126
|
+
const otherApplication = { id: 'other-application', version: '2.0.0' };
|
|
127
|
+
|
|
128
|
+
beforeEach(() => (providedLDService.application = otherApplication));
|
|
129
|
+
|
|
130
|
+
test('provides inherited application', () => {
|
|
131
|
+
subject();
|
|
132
|
+
|
|
133
|
+
expect(providedApplication).toBe(otherApplication);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('unmount preserves LDService.application', () => {
|
|
137
|
+
const { unmount } = subject();
|
|
138
|
+
unmount();
|
|
139
|
+
|
|
140
|
+
expect(providedLDService.application).toBe(otherApplication);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
});
|
|
49
145
|
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import '@testing-library/jest-dom';
|
|
2
2
|
import { render, screen } from '@testing-library/react';
|
|
3
3
|
import { LDFlagValue, useFlags } from 'launchdarkly-react-client-sdk';
|
|
4
|
-
|
|
5
4
|
import { WithFeatureFlag } from '../with-feature-flag';
|
|
6
5
|
|
|
7
6
|
jest.mock('launchdarkly-react-client-sdk', () => ({
|
package/src/types.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { symbolToken } from '@servicetitan/react-ioc';
|
|
2
|
-
import
|
|
2
|
+
import type { LDClient, LDContext, LDOptions } from 'launchdarkly-js-client-sdk';
|
|
3
3
|
|
|
4
4
|
export const LD_SERVICE_TOKEN = symbolToken<LDService>('LD_SERVICE_TOKEN');
|
|
5
5
|
|
|
6
|
+
export type LDApplication = LDOptions['application'];
|
|
7
|
+
|
|
6
8
|
export interface ClientConfig {
|
|
7
9
|
clientSideID: string;
|
|
8
10
|
context?: LDContext;
|
|
@@ -11,6 +13,7 @@ export interface ClientConfig {
|
|
|
11
13
|
|
|
12
14
|
export interface LDService {
|
|
13
15
|
getClient: (config: ClientConfig) => LDClient;
|
|
16
|
+
application?: LDApplication;
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
export type FlagKey<S, K extends keyof S> = K & {
|