Skip to content

Commit 5c526b1

Browse files
prepare 2.19.0 release (#41)
1 parent 1aef0aa commit 5c526b1

File tree

10 files changed

+460
-4580
lines changed

10 files changed

+460
-4580
lines changed

package-lock.json

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
"typescript": "~3.8.3"
5757
},
5858
"dependencies": {
59-
"launchdarkly-js-client-sdk": "2.17.5",
59+
"launchdarkly-js-client-sdk": "2.17.6",
6060
"lodash.camelcase": "^4.3.0",
6161
"uuid": "^3.3.2"
6262
},
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`LDProvider render app 1`] = `
4+
<Provider
5+
value={
6+
Object {
7+
"flags": Object {},
8+
"ldClient": undefined,
9+
}
10+
}
11+
>
12+
<div>
13+
My App
14+
</div>
15+
</Provider>
16+
`;

src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
import LDProvider from './provider';
12
import withLDProvider from './withLDProvider';
23
import asyncWithLDProvider from './asyncWithLDProvider';
34
import withLDConsumer from './withLDConsumer';
45
import useFlags from './useFlags';
56
import useLDClient from './useLDClient';
67
import camelCaseKeys from './utils';
78

8-
export { withLDProvider, withLDConsumer, useFlags, useLDClient, asyncWithLDProvider, camelCaseKeys };
9+
export { LDProvider, withLDProvider, withLDConsumer, useFlags, useLDClient, asyncWithLDProvider, camelCaseKeys };

src/provider.test.tsx

Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
jest.mock('./initLDClient', () => jest.fn());
2+
jest.mock('./context', () => ({ Provider: 'Provider' }));
3+
4+
import * as React from 'react';
5+
import { create } from 'react-test-renderer';
6+
import { shallow } from 'enzyme';
7+
import { LDFlagChangeset, LDFlagSet, LDOptions, LDUser } from 'launchdarkly-js-client-sdk';
8+
import initLDClient from './initLDClient';
9+
import { LDReactOptions, EnhancedComponent, defaultReactOptions, ProviderConfig } from './types';
10+
import { LDContext as HocState } from './context';
11+
import LDProvider from './provider';
12+
13+
const clientSideID = 'deadbeef';
14+
const App = () => <div>My App</div>;
15+
const mockInitLDClient = initLDClient as jest.Mock;
16+
const mockFlags = { testFlag: true, anotherTestFlag: true };
17+
const mockLDClient = {
18+
on: jest.fn((e: string, cb: () => void) => {
19+
cb();
20+
}),
21+
};
22+
23+
describe('LDProvider', () => {
24+
beforeEach(() => {
25+
mockInitLDClient.mockImplementation(() => ({
26+
flags: mockFlags,
27+
ldClient: mockLDClient,
28+
}));
29+
});
30+
31+
afterEach(() => {
32+
jest.resetAllMocks();
33+
});
34+
35+
test('render app', () => {
36+
const props: ProviderConfig = { clientSideID };
37+
const LaunchDarklyApp = (
38+
<LDProvider {...props}>
39+
<App />
40+
</LDProvider>
41+
);
42+
const component = create(LaunchDarklyApp);
43+
expect(component).toMatchSnapshot();
44+
});
45+
46+
test('ld client is initialised correctly', async () => {
47+
const user: LDUser = { key: 'yus', name: 'yus ng' };
48+
const options: LDOptions = { bootstrap: {} };
49+
const props: ProviderConfig = { clientSideID, user, options };
50+
const LaunchDarklyApp = (
51+
<LDProvider {...props}>
52+
<App />
53+
</LDProvider>
54+
);
55+
const instance = create(LaunchDarklyApp).root.findByType(LDProvider).instance as EnhancedComponent;
56+
57+
await instance.componentDidMount();
58+
expect(mockInitLDClient).toHaveBeenCalledWith(clientSideID, user, defaultReactOptions, options, undefined);
59+
});
60+
61+
test('ldClient bootstraps with empty flags', () => {
62+
const user: LDUser = { key: 'yus', name: 'yus ng' };
63+
const options: LDOptions = {
64+
bootstrap: {},
65+
};
66+
const props: ProviderConfig = { clientSideID, user, options };
67+
const LaunchDarklyApp = (
68+
<LDProvider {...props}>
69+
<App />
70+
</LDProvider>
71+
);
72+
const component = shallow(LaunchDarklyApp, { disableLifecycleMethods: true });
73+
const initialState = component.state() as HocState;
74+
75+
expect(initialState.flags).toEqual({});
76+
});
77+
78+
test('ld client is bootstrapped correctly and transforms keys to camel case', () => {
79+
const user: LDUser = { key: 'yus', name: 'yus ng' };
80+
const options: LDOptions = {
81+
bootstrap: {
82+
'test-flag': true,
83+
'another-test-flag': false,
84+
$flagsState: {
85+
'test-flag': { version: 125, variation: 0, trackEvents: true },
86+
'another-test-flag': { version: 18, variation: 1 },
87+
},
88+
$valid: true,
89+
},
90+
};
91+
const props: ProviderConfig = { clientSideID, user, options };
92+
const LaunchDarklyApp = (
93+
<LDProvider {...props}>
94+
<App />
95+
</LDProvider>
96+
);
97+
const component = shallow(LaunchDarklyApp, { disableLifecycleMethods: true });
98+
const initialState = component.state() as HocState;
99+
100+
expect(mockInitLDClient).not.toHaveBeenCalled();
101+
expect(initialState.flags).toEqual({ testFlag: true, anotherTestFlag: false });
102+
});
103+
104+
test('ld client should not transform keys to camel case if option is disabled', () => {
105+
const user: LDUser = { key: 'yus', name: 'yus ng' };
106+
const options: LDOptions = {
107+
bootstrap: {
108+
'test-flag': true,
109+
'another-test-flag': false,
110+
},
111+
};
112+
const reactOptions: LDReactOptions = {
113+
useCamelCaseFlagKeys: false,
114+
};
115+
const props: ProviderConfig = { clientSideID, user, options, reactOptions };
116+
const LaunchDarklyApp = (
117+
<LDProvider {...props}>
118+
<App />
119+
</LDProvider>
120+
);
121+
const component = shallow(LaunchDarklyApp, { disableLifecycleMethods: true });
122+
const initialState = component.state() as HocState;
123+
124+
expect(mockInitLDClient).not.toHaveBeenCalled();
125+
expect(initialState.flags).toEqual({ 'test-flag': true, 'another-test-flag': false });
126+
});
127+
128+
test('ld client should transform keys to camel case if transform option is absent', () => {
129+
const user: LDUser = { key: 'yus', name: 'yus ng' };
130+
const options: LDOptions = {
131+
bootstrap: {
132+
'test-flag': true,
133+
'another-test-flag': false,
134+
},
135+
};
136+
const reactOptions: LDReactOptions = {};
137+
const props: ProviderConfig = { clientSideID, user, options, reactOptions };
138+
const LaunchDarklyApp = (
139+
<LDProvider {...props}>
140+
<App />
141+
</LDProvider>
142+
);
143+
const component = shallow(LaunchDarklyApp, { disableLifecycleMethods: true });
144+
const initialState = component.state() as HocState;
145+
146+
expect(mockInitLDClient).not.toHaveBeenCalled();
147+
expect(initialState.flags).toEqual({ testFlag: true, anotherTestFlag: false });
148+
});
149+
150+
test('ld client should transform keys to camel case if react options object is absent', () => {
151+
const user: LDUser = { key: 'yus', name: 'yus ng' };
152+
const options: LDOptions = {
153+
bootstrap: {
154+
'test-flag': true,
155+
'another-test-flag': false,
156+
},
157+
};
158+
const props: ProviderConfig = { clientSideID, user, options };
159+
const LaunchDarklyApp = (
160+
<LDProvider {...props}>
161+
<App />
162+
</LDProvider>
163+
);
164+
const component = shallow(LaunchDarklyApp, { disableLifecycleMethods: true });
165+
const initialState = component.state() as HocState;
166+
167+
expect(mockInitLDClient).not.toHaveBeenCalled();
168+
expect(initialState.flags).toEqual({ testFlag: true, anotherTestFlag: false });
169+
});
170+
171+
test('state.flags should be initialised to empty when bootstrapping from localStorage', () => {
172+
const user: LDUser = { key: 'yus', name: 'yus ng' };
173+
const options: LDOptions = {
174+
bootstrap: 'localStorage',
175+
};
176+
const props: ProviderConfig = { clientSideID, user, options };
177+
const LaunchDarklyApp = (
178+
<LDProvider {...props}>
179+
<App />
180+
</LDProvider>
181+
);
182+
const component = shallow(LaunchDarklyApp, { disableLifecycleMethods: true });
183+
const initialState = component.state() as HocState;
184+
185+
expect(mockInitLDClient).not.toHaveBeenCalled();
186+
expect(initialState.flags).toEqual({});
187+
});
188+
189+
test('ld client is initialised correctly with target flags', async () => {
190+
mockInitLDClient.mockImplementation(() => ({
191+
flags: { devTestFlag: true, launchDoggly: true },
192+
ldClient: mockLDClient,
193+
}));
194+
const user: LDUser = { key: 'yus', name: 'yus ng' };
195+
const options: LDOptions = { bootstrap: {} };
196+
const flags = { 'dev-test-flag': false, 'launch-doggly': false };
197+
const props: ProviderConfig = { clientSideID, user, options, flags };
198+
const LaunchDarklyApp = (
199+
<LDProvider {...props}>
200+
<App />
201+
</LDProvider>
202+
);
203+
const instance = create(LaunchDarklyApp).root.findByType(LDProvider).instance as EnhancedComponent;
204+
instance.setState = jest.fn();
205+
206+
await instance.componentDidMount();
207+
208+
expect(mockInitLDClient).toHaveBeenCalledWith(clientSideID, user, defaultReactOptions, options, flags);
209+
expect(instance.setState).toHaveBeenCalledWith({
210+
flags: { devTestFlag: true, launchDoggly: true },
211+
ldClient: mockLDClient,
212+
});
213+
});
214+
215+
test('flags and ldClient are saved in state on mount', async () => {
216+
const props: ProviderConfig = { clientSideID };
217+
const LaunchDarklyApp = (
218+
<LDProvider {...props}>
219+
<App />
220+
</LDProvider>
221+
);
222+
const instance = create(LaunchDarklyApp).root.findByType(LDProvider).instance as EnhancedComponent;
223+
instance.setState = jest.fn();
224+
225+
await instance.componentDidMount();
226+
expect(instance.setState).toHaveBeenCalledWith({ flags: mockFlags, ldClient: mockLDClient });
227+
});
228+
229+
test('subscribeToChanges is called on mount', async () => {
230+
const props: ProviderConfig = { clientSideID };
231+
const LaunchDarklyApp = (
232+
<LDProvider {...props}>
233+
<App />
234+
</LDProvider>
235+
);
236+
const instance = create(LaunchDarklyApp).root.findByType(LDProvider).instance as EnhancedComponent;
237+
instance.subscribeToChanges = jest.fn();
238+
239+
await instance.componentDidMount();
240+
expect(instance.subscribeToChanges).toHaveBeenCalled();
241+
});
242+
243+
test('subscribe to changes with camelCase', async () => {
244+
mockLDClient.on.mockImplementation((e: string, cb: (c: LDFlagChangeset) => void) => {
245+
cb({ 'test-flag': { current: false, previous: true } });
246+
});
247+
const props: ProviderConfig = { clientSideID };
248+
const LaunchDarklyApp = (
249+
<LDProvider {...props}>
250+
<App />
251+
</LDProvider>
252+
);
253+
const instance = create(LaunchDarklyApp).root.findByType(LDProvider).instance as EnhancedComponent;
254+
const mockSetState = jest.spyOn(instance, 'setState');
255+
256+
await instance.componentDidMount();
257+
const callback = mockSetState.mock.calls[1][0] as (flags: LDFlagSet) => LDFlagSet;
258+
const newState = callback({ flags: mockFlags });
259+
260+
expect(mockLDClient.on).toHaveBeenCalledWith('change', expect.any(Function));
261+
expect(newState).toEqual({ flags: { anotherTestFlag: true, testFlag: false } });
262+
});
263+
264+
test('subscribe to changes with kebab-case', async () => {
265+
mockLDClient.on.mockImplementation((e: string, cb: (c: LDFlagChangeset) => void) => {
266+
cb({ 'another-test-flag': { current: false, previous: true }, 'test-flag': { current: false, previous: true } });
267+
});
268+
const props: ProviderConfig = { clientSideID, reactOptions: { useCamelCaseFlagKeys: false } };
269+
const LaunchDarklyApp = (
270+
<LDProvider {...props}>
271+
<App />
272+
</LDProvider>
273+
);
274+
const instance = create(LaunchDarklyApp).root.findByType(LDProvider).instance as EnhancedComponent;
275+
const mockSetState = jest.spyOn(instance, 'setState');
276+
277+
await instance.componentDidMount();
278+
const callback = mockSetState.mock.calls[1][0] as (flags: LDFlagSet) => LDFlagSet;
279+
const newState = callback({});
280+
281+
expect(mockLDClient.on).toHaveBeenCalledWith('change', expect.any(Function));
282+
expect(newState).toEqual({ flags: { 'another-test-flag': false, 'test-flag': false } });
283+
});
284+
285+
test(`if props.deferInitialization is true, ld client will only initialize once props.user is defined`, async () => {
286+
const options: LDOptions = { bootstrap: {} };
287+
const props: ProviderConfig = { clientSideID, deferInitialization: true, options };
288+
const LaunchDarklyApp = (
289+
<LDProvider {...props}>
290+
<App />
291+
</LDProvider>
292+
);
293+
const renderer = create(LaunchDarklyApp);
294+
const instance = renderer.root.findByType(LDProvider).instance as EnhancedComponent;
295+
296+
await instance.componentDidMount();
297+
298+
expect(mockInitLDClient).toHaveBeenCalledTimes(0);
299+
300+
const user: LDUser = { key: 'yus', name: 'yus ng' };
301+
const newProps = { ...props, user };
302+
const UpdatedLaunchDarklyApp = (
303+
<LDProvider {...newProps}>
304+
<App />
305+
</LDProvider>
306+
);
307+
renderer.update(UpdatedLaunchDarklyApp);
308+
if (instance.componentDidUpdate) {
309+
await instance.componentDidUpdate(props);
310+
}
311+
312+
expect(mockInitLDClient).toHaveBeenCalledWith(clientSideID, user, defaultReactOptions, options, undefined);
313+
});
314+
});

0 commit comments

Comments
 (0)