Skip to content

Commit 25978c6

Browse files
authored
Reinstate 2.12.5 and 2.13.0 (#4)
* reinstate 2.12.5 and 2.13.0 * lint errors * adding a missing file
1 parent fd9f921 commit 25978c6

12 files changed

+209
-76
lines changed

CHANGELOG.md

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
# Change log
22

3-
All notable changes to the LaunchDarkly Client-side SDK for React will be documented in this file. For the source code for versions 2.12.4 and earlier, see the corresponding tags in the [js-client-sdk](https://github.com/launchdarkly/js-client-sdk) repository; this code was previously in a monorepo package there. See also the [JavaScript SDK changelog](https://github.com/launchdarkly/js-client-sdk/blob/master/CHANGELOG.md), since the React SDK inherits all of the underlying functionality of the JavaScript SDK; this file covers only changes that are specific to the React interface. This project adheres to [Semantic Versioning](http://semver.org).
3+
All notable changes to the LaunchDarkly Client-side SDK for React will be documented in this file. For the source code for versions 2.13.0 and earlier, see the corresponding tags in the [js-client-sdk](https://github.com/launchdarkly/js-client-sdk) repository; this code was previously in a monorepo package there. See also the [JavaScript SDK changelog](https://github.com/launchdarkly/js-client-sdk/blob/master/CHANGELOG.md), since the React SDK inherits all of the underlying functionality of the JavaScript SDK; this file covers only changes that are specific to the React interface. This project adheres to [Semantic Versioning](http://semver.org).
4+
5+
## [2.13.0] - 2019-08-15
6+
### Added:
7+
- In the React SDK, the new `reactOptions` parameter to `withLDProvider` provides React-specific options that do not affect the underlying JavaScript SDK. Currently, the only such option is `useCamelCaseFlagKeys`, which is true by default but can be set to false to disable the automatic camel-casing of flag keys.
8+
9+
### Changed:
10+
- In the React SDK, when omitting the `user` parameter to `withLDProvider`, an anonymous user will be created. This user will remain constant across browser sessions. Previously a new user was generated on each page load.
11+
12+
## [2.12.5] - 2019-07-29
13+
### Fixed:
14+
- The React SDK was incompatible with Internet Explorer 11 due to using `String.startsWith()`. (Thanks, [cvetanov](https://github.com/launchdarkly/js-client-sdk/pull/169)!)
415

516
## [2.12.4] - 2019-07-10
617
### Fixed:

package-lock.json

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"enzyme-adapter-react-16": "1.10.0",
4141
"jest": "^24.1.0",
4242
"jest-junit": "^6.3.0",
43-
"prettier": "^1.16.4",
43+
"prettier": "^1.18.2",
4444
"prop-types": "^15.7.2",
4545
"react": "^16.8.4",
4646
"react-dom": "^16.8.4",

src/initLDClient.test.ts

+22-9
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,14 @@ jest.mock('launchdarkly-js-client-sdk', () => {
88
});
99
jest.mock('uuid', () => ({ v4: jest.fn() }));
1010

11-
import { v4 } from 'uuid';
1211
import { initialize, LDClient, LDOptions, LDUser } from 'launchdarkly-js-client-sdk';
12+
import { defaultReactOptions, LDReactOptions } from './types';
1313
import initLDClient from './initLDClient';
1414

1515
const ldClientInitialize = initialize as jest.Mock;
16-
const uuid = v4 as jest.Mock;
1716

1817
const clientSideID = 'deadbeef';
19-
const mockUserKey = 'abcdef';
20-
const defaulUser: LDUser = { key: mockUserKey };
18+
const defaultUser: LDUser = { key: 'abcdef' };
2119
const options: LDOptions = { bootstrap: 'localStorage' };
2220
const flags = { 'test-flag': false, 'another-test-flag': true };
2321

@@ -34,40 +32,55 @@ describe('initLDClient', () => {
3432
};
3533

3634
ldClientInitialize.mockImplementation(() => mockLDClient);
37-
uuid.mockImplementation(() => mockUserKey);
3835
});
3936

4037
afterEach(() => {
4138
jest.resetAllMocks();
4239
});
4340

4441
test('initialise with clientSideID only', async () => {
42+
const anonUser: LDUser = { anonymous: true };
4543
await initLDClient(clientSideID);
4644

47-
expect(ldClientInitialize.mock.calls[0]).toEqual([clientSideID, defaulUser, undefined]);
45+
expect(ldClientInitialize.mock.calls[0]).toEqual([clientSideID, anonUser, undefined]);
4846
expect(mockLDClient.variation).toHaveBeenCalledTimes(0);
4947
});
5048

5149
test('initialise with custom user and options', async () => {
5250
const customUser = { key: '[email protected]' };
53-
await initLDClient(clientSideID, customUser, options);
51+
await initLDClient(clientSideID, customUser, defaultReactOptions, options);
5452

5553
expect(ldClientInitialize.mock.calls[0]).toEqual([clientSideID, customUser, options]);
5654
expect(mockLDClient.variation).toHaveBeenCalledTimes(0);
5755
});
5856

59-
test('initialise should return camelCased flags', async () => {
57+
test('initialise should return camelCased flags by default', async () => {
6058
const flagsClient = await initLDClient(clientSideID);
6159

6260
expect(mockLDClient.variation).toHaveBeenCalledTimes(0);
6361
expect(flagsClient).toEqual({ flags: { anotherTestFlag: true, testFlag: false }, ldClient: mockLDClient });
6462
});
6563

64+
test('initialise should not transform keys to camel case if option is disabled', async () => {
65+
const reactOptions: LDReactOptions = { useCamelCaseFlagKeys: false };
66+
const flagsClient = await initLDClient(clientSideID, defaultUser, reactOptions, options);
67+
68+
expect(mockLDClient.variation).toHaveBeenCalledTimes(0);
69+
expect(flagsClient).toEqual({ flags: { 'another-test-flag': true, 'test-flag': false }, ldClient: mockLDClient });
70+
});
71+
72+
test('initialise should transform keys to camel case if option is absent', async () => {
73+
const flagsClient = await initLDClient(clientSideID, defaultUser, defaultReactOptions, options);
74+
75+
expect(mockLDClient.variation).toHaveBeenCalledTimes(0);
76+
expect(flagsClient).toEqual({ flags: { anotherTestFlag: true, testFlag: false }, ldClient: mockLDClient });
77+
});
78+
6679
test('initialise should call variation if flags are specified', async () => {
6780
const customUser = { key: '[email protected]' };
6881
const targetFlags = { 'lonely-flag': false, 'lonelier-flag': false };
6982

70-
const flagsClient = await initLDClient(clientSideID, customUser, options, targetFlags);
83+
const flagsClient = await initLDClient(clientSideID, customUser, defaultReactOptions, options, targetFlags);
7184

7285
expect(mockLDClient.variation).toHaveBeenCalledTimes(2);
7386
expect(mockLDClient.variation).toHaveBeenNthCalledWith(1, 'lonely-flag', false);

src/initLDClient.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { initialize as ldClientInitialize, LDClient, LDFlagSet, LDOptions, LDUser } from 'launchdarkly-js-client-sdk';
2-
import { v4 as uuid } from 'uuid';
2+
import { defaultReactOptions, LDReactOptions } from './types';
33
import { camelCaseKeys } from './utils';
44

55
interface AllFlagsLDClient {
@@ -9,7 +9,8 @@ interface AllFlagsLDClient {
99

1010
const initLDClient = async (
1111
clientSideID: string,
12-
user: LDUser = { key: uuid() },
12+
user: LDUser = { anonymous: true },
13+
reactOptions: LDReactOptions = defaultReactOptions,
1314
options?: LDOptions,
1415
targetFlags?: LDFlagSet,
1516
): Promise<AllFlagsLDClient> => {
@@ -27,7 +28,8 @@ const initLDClient = async (
2728
rawFlags = ldClient.allFlags();
2829
}
2930

30-
resolve({ flags: camelCaseKeys(rawFlags), ldClient });
31+
const flags = reactOptions.useCamelCaseFlagKeys ? camelCaseKeys(rawFlags) : rawFlags;
32+
resolve({ flags, ldClient });
3133
});
3234
});
3335
};

src/types.ts

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { LDClient, LDFlagSet, LDOptions, LDUser } from 'launchdarkly-js-client-sdk';
2+
import * as React from 'react';
3+
4+
/**
5+
* Initialization options for the LaunchDarkly React SDK. These are in addition to the options exposed
6+
* by [[LDOptions]] which are common to both the JavaScript and React SDKs.
7+
*/
8+
export interface LDReactOptions {
9+
/**
10+
* Whether the React SDK should transform flag keys into camel-cased format.
11+
* Using camel-cased flag keys allow for easier use as prop values, however,
12+
* these keys won't directly match the flag keys as known to LaunchDarkly.
13+
* Consequently, flag key collisions may be possible and the Code References feature
14+
* will not function properly.
15+
*
16+
* This is true by default, meaning that keys will automatically be converted to camel-case.
17+
*
18+
* For more information, see the React SDK Reference Guide on
19+
* [flag keys](https://docs.launchdarkly.com/docs/react-sdk-reference#section-flag-keys).
20+
*/
21+
useCamelCaseFlagKeys?: boolean;
22+
}
23+
24+
export const defaultReactOptions = { useCamelCaseFlagKeys: true };
25+
26+
/**
27+
* Configuration object used to initialise LaunchDarkly's js client.
28+
*/
29+
export interface ProviderConfig {
30+
/**
31+
* Your project and environment specific client side ID. You can find
32+
* this in your LaunchDarkly portal under Account settings. This is
33+
* the only mandatory property required to use the React SDK.
34+
*/
35+
clientSideID: string;
36+
37+
/**
38+
* A LaunchDarkly user object. If unspecified, a new user with a
39+
* random key will be created and used. This user's key will remain constant across browser sessions.
40+
*
41+
* @see http://docs.launchdarkly.com/docs/js-sdk-reference#section-users
42+
*/
43+
user?: LDUser;
44+
45+
/**
46+
* LaunchDarkly initialization options. These options are common between LaunchDarkly's JavaScript and React SDKs.
47+
*
48+
* @see https://docs.launchdarkly.com/docs/js-sdk-reference#section-customizing-your-client
49+
*/
50+
options?: LDOptions;
51+
52+
/**
53+
* Additional initialization options specific to the React SDK.
54+
*
55+
* @see options
56+
*/
57+
reactOptions?: LDReactOptions;
58+
59+
/**
60+
* If specified, launchdarkly-react-client-sdk will only request and listen to these flags.
61+
* Otherwise, all flags will be requested and listened to.
62+
*/
63+
flags?: LDFlagSet;
64+
}
65+
66+
/**
67+
* The return type of withLDProvider HOC. Exported for testing purposes only.
68+
*/
69+
export interface EnhancedComponent extends React.Component {
70+
subscribeToChanges(ldClient: LDClient): void;
71+
componentDidMount(): Promise<void>;
72+
}

src/utils.test.ts

-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { camelCaseKeys } from './utils';
2-
import { LDOptions } from 'launchdarkly-js-client-sdk';
32

43
describe('Utils', () => {
54
test('camelCaseKeys should ignore system keys', () => {

src/utils.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ export const camelCaseKeys = (rawFlags: LDFlagSet) => {
66
for (const rawFlag in rawFlags) {
77
// Exclude system keys
88
if (rawFlag.indexOf('$') !== 0) {
9-
const camelCasedKey = camelCase(rawFlag);
10-
flags[camelCasedKey] = rawFlags[rawFlag];
9+
flags[camelCase(rawFlag)] = rawFlags[rawFlag]; // tslint:disable-line:no-unsafe-any
1110
}
1211
}
1312

src/withLDConsumer.test.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ jest.mock('./context', () => {
1111
}
1212

1313
return {
14+
// tslint:disable-next-line:no-null-undefined-union
1415
Consumer(props: ConsumerChildren) {
1516
return props.children({ flags: { testFlag: true }, ldClient: { track: jest.fn() } });
1617
},
@@ -40,7 +41,7 @@ describe('withLDConsumer', () => {
4041
expect(component).toMatchSnapshot();
4142
});
4243

43-
test('only ldClient is passed down through context api', async () => {
44+
test('only ldClient is passed down through context api', () => {
4445
const Home = (props: HocProps) => (
4546
<div>
4647
{props.flags ? 'flags detected' : 'Negative, no flag'}

src/withLDProvider.test.tsx

+76-7
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import { create } from 'react-test-renderer';
66
import { shallow } from 'enzyme';
77
import { LDFlagChangeset, LDFlagSet, LDOptions, LDUser } from 'launchdarkly-js-client-sdk';
88
import initLDClient from './initLDClient';
9-
import withLDProvider, { EnhancedComponent } from './withLDProvider';
9+
import withLDProvider from './withLDProvider';
10+
import { LDReactOptions, EnhancedComponent, defaultReactOptions } from './types';
1011
import { LDContext as HocState } from './context';
1112

1213
const clientSideID = 'deadbeef';
@@ -31,7 +32,7 @@ describe('withLDProvider', () => {
3132
jest.resetAllMocks();
3233
});
3334

34-
test('render app', async () => {
35+
test('render app', () => {
3536
const LaunchDarklyApp = withLDProvider({ clientSideID })(App);
3637
const component = create(<LaunchDarklyApp />);
3738
expect(component).toMatchSnapshot();
@@ -44,10 +45,10 @@ describe('withLDProvider', () => {
4445
const instance = create(<LaunchDarklyApp />).root.instance as EnhancedComponent;
4546

4647
await instance.componentDidMount();
47-
expect(mockInitLDClient).toHaveBeenCalledWith(clientSideID, user, options, undefined);
48+
expect(mockInitLDClient).toHaveBeenCalledWith(clientSideID, user, defaultReactOptions, options, undefined);
4849
});
4950

50-
test('ld client is bootstrapped correctly', async () => {
51+
test('ld client is bootstrapped correctly and transforms keys to camel case', () => {
5152
const user: LDUser = { key: 'yus', name: 'yus ng' };
5253
const options: LDOptions = {
5354
bootstrap: {
@@ -68,7 +69,59 @@ describe('withLDProvider', () => {
6869
expect(initialState.flags).toEqual({ testFlag: true, anotherTestFlag: false });
6970
});
7071

71-
test('state.flags should be initialised to empty when bootstrapping from localStorage', async () => {
72+
test('ld client should not transform keys to camel case if option is disabled', () => {
73+
const user: LDUser = { key: 'yus', name: 'yus ng' };
74+
const options: LDOptions = {
75+
bootstrap: {
76+
'test-flag': true,
77+
'another-test-flag': false,
78+
},
79+
};
80+
const reactOptions: LDReactOptions = {
81+
useCamelCaseFlagKeys: false,
82+
};
83+
const LaunchDarklyApp = withLDProvider({ clientSideID, user, options, reactOptions })(App);
84+
const component = shallow(<LaunchDarklyApp />, { disableLifecycleMethods: true });
85+
const initialState = component.state() as HocState;
86+
87+
expect(mockInitLDClient).not.toHaveBeenCalled();
88+
expect(initialState.flags).toEqual({ 'test-flag': true, 'another-test-flag': false });
89+
});
90+
91+
test('ld client should transform keys to camel case if transform option is absent', () => {
92+
const user: LDUser = { key: 'yus', name: 'yus ng' };
93+
const options: LDOptions = {
94+
bootstrap: {
95+
'test-flag': true,
96+
'another-test-flag': false,
97+
},
98+
};
99+
const reactOptions: LDReactOptions = {};
100+
const LaunchDarklyApp = withLDProvider({ clientSideID, user, options, reactOptions })(App);
101+
const component = shallow(<LaunchDarklyApp />, { disableLifecycleMethods: true });
102+
const initialState = component.state() as HocState;
103+
104+
expect(mockInitLDClient).not.toHaveBeenCalled();
105+
expect(initialState.flags).toEqual({ testFlag: true, anotherTestFlag: false });
106+
});
107+
108+
test('ld client should transform keys to camel case if react options object is absent', () => {
109+
const user: LDUser = { key: 'yus', name: 'yus ng' };
110+
const options: LDOptions = {
111+
bootstrap: {
112+
'test-flag': true,
113+
'another-test-flag': false,
114+
},
115+
};
116+
const LaunchDarklyApp = withLDProvider({ clientSideID, user, options })(App);
117+
const component = shallow(<LaunchDarklyApp />, { disableLifecycleMethods: true });
118+
const initialState = component.state() as HocState;
119+
120+
expect(mockInitLDClient).not.toHaveBeenCalled();
121+
expect(initialState.flags).toEqual({ testFlag: true, anotherTestFlag: false });
122+
});
123+
124+
test('state.flags should be initialised to empty when bootstrapping from localStorage', () => {
72125
const user: LDUser = { key: 'yus', name: 'yus ng' };
73126
const options: LDOptions = {
74127
bootstrap: 'localStorage',
@@ -95,7 +148,7 @@ describe('withLDProvider', () => {
95148

96149
await instance.componentDidMount();
97150

98-
expect(mockInitLDClient).toHaveBeenCalledWith(clientSideID, user, options, flags);
151+
expect(mockInitLDClient).toHaveBeenCalledWith(clientSideID, user, defaultReactOptions, options, flags);
99152
expect(instance.setState).toHaveBeenCalledWith({
100153
flags: { devTestFlag: true, launchDoggly: true },
101154
ldClient: mockLDClient,
@@ -120,7 +173,7 @@ describe('withLDProvider', () => {
120173
expect(instance.subscribeToChanges).toHaveBeenCalled();
121174
});
122175

123-
test('subscribe to changes', async () => {
176+
test('subscribe to changes with camelCase', async () => {
124177
mockLDClient.on.mockImplementation((e: string, cb: (c: LDFlagChangeset) => void) => {
125178
cb({ 'test-flag': { current: false, previous: true } });
126179
});
@@ -135,4 +188,20 @@ describe('withLDProvider', () => {
135188
expect(mockLDClient.on).toHaveBeenCalledWith('change', expect.any(Function));
136189
expect(newState).toEqual({ flags: { anotherTestFlag: true, testFlag: false } });
137190
});
191+
192+
test('subscribe to changes with kebab-case', async () => {
193+
mockLDClient.on.mockImplementation((e: string, cb: (c: LDFlagChangeset) => void) => {
194+
cb({ 'another-test-flag': { current: false, previous: true }, 'test-flag': { current: false, previous: true } });
195+
});
196+
const LaunchDarklyApp = withLDProvider({ clientSideID, reactOptions: { useCamelCaseFlagKeys: false } })(App);
197+
const instance = create(<LaunchDarklyApp />).root.instance as EnhancedComponent;
198+
const mockSetState = jest.spyOn(instance, 'setState');
199+
200+
await instance.componentDidMount();
201+
const callback = mockSetState.mock.calls[1][0] as (flags: LDFlagSet) => LDFlagSet;
202+
const newState = callback({});
203+
204+
expect(mockLDClient.on).toHaveBeenCalledWith('change', expect.any(Function));
205+
expect(newState).toEqual({ flags: { 'another-test-flag': false, 'test-flag': false } });
206+
});
138207
});

0 commit comments

Comments
 (0)