Skip to content

Commit c2fa0f6

Browse files
committed
chore: pr comments
1 parent a1694eb commit c2fa0f6

5 files changed

Lines changed: 143 additions & 180 deletions

File tree

packages/tooling/client-testing-plugin/README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,15 @@ client.stringVariation('greeting', '(default)'); // 'Hello!'
3939
td.setBool('new-ui', false).setString('greeting', 'Welcome');
4040
```
4141

42-
### Why these options matter
42+
### Required LD client options
43+
In order to successfully set up a LD client to use the testing plugin, you **MUST** set the following options:
4344

44-
- **`plugins: [td]`** -- registers the testing plugin so it can inject overrides.
45-
- **`sendEvents: false`** -- keeps analytics events off in tests.
46-
- **`streaming: false`** -- prevents the SDK from auto-starting a streaming connection when a `change` listener is registered. Without this, the React SDK provider (and any other code that registers `change` listeners) will trigger a real network call to `clientstream.launchdarkly.com`.
45+
- **`plugins: [td]`** - registers the testing plugin so it can inject overrides.
46+
- **`sendEvents: false`** - keeps analytics events off in tests.
47+
- **`streaming: false`** - (required for `js-client-sdk` and its derivativs, eg `react-sdk`), having streaming on will cause the `js-client-sdk` to automatically open a streaming connection.
4748
- **`bootstrap: {}` (passed to `start()`)** -- gives the SDK an empty initial flag set so it does not block on a network identify call. The plugin's overrides are applied immediately afterward.
4849

49-
If you forget any of these, the SDK may attempt to fetch flags from LaunchDarkly during initialization and produce real network traffic, console errors, or stray evaluation events.
50+
> Refer to the usage example above.
5051
5152
## API
5253

@@ -67,6 +68,6 @@ class TestData implements LDPlugin {
6768
```
6869

6970
- **`new TestData(initialFlags?)`** -- seed the instance with a base map of flag keys to values. The values are applied to the SDK client when it initializes.
70-
- **`setBool` / `setString` / `setNumber` / `setJson`** -- set or update a single flag. If the SDK is already running, the change propagates immediately and listeners receive a `change:<key>` event. Updates dedup by reference equality (`===`); pass a fresh object/array reference if you want a change event after mutating a previous value.
71+
- **`setBool` / `setString` / `setNumber` / `setJson`** -- set or update a single flag. If the SDK is already running, the change propagates immediately and listeners receive a `change:<key>` event. Every write applies the override, even when the value is unchanged -- mirroring a real connection that can re-deliver a flag and fire a `change` event without the value differing.
7172
- **`remove(key)`** -- drop the override for a single key. If the SDK is connected, also calls `removeOverride`.
7273
- **`clear()`** -- drop all overrides. Useful in `beforeEach` for shared `TestData` instances.

packages/tooling/client-testing-plugin/__tests__/TestData.test.ts

Lines changed: 130 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -21,194 +21,172 @@ function createMockDebugOverride(): LDDebugOverride & {
2121
};
2222
}
2323

24-
describe('TestData', () => {
25-
it('returns correct plugin metadata', () => {
26-
expect(new TestData().getMetadata()).toEqual({ name: 'test-data' });
27-
});
28-
29-
it('register is a no-op', () => {
30-
const td = new TestData();
31-
expect(() =>
32-
td.register(undefined, {
33-
sdk: { name: 'test', version: '0.0.0' },
34-
clientSideId: 'test-key',
35-
} as never),
36-
).not.toThrow();
37-
});
38-
39-
it('seeds initial flags from the constructor and applies them on registerDebug', () => {
40-
const td = new TestData({
41-
'show-banner': true,
42-
greeting: 'Hello',
43-
'max-retries': 3,
44-
config: { theme: 'dark' },
45-
});
46-
47-
const debugOverride = createMockDebugOverride();
48-
td.registerDebug(debugOverride);
49-
50-
expect(debugOverride.overrides['show-banner']).toBe(true);
51-
expect(debugOverride.overrides.greeting).toBe('Hello');
52-
expect(debugOverride.overrides['max-retries']).toBe(3);
53-
expect(debugOverride.overrides.config).toEqual({ theme: 'dark' });
54-
});
55-
56-
it('typed setters chain and apply pre-registration', () => {
57-
const td = new TestData()
58-
.setBool('show-banner', true)
59-
.setString('greeting', 'Hello')
60-
.setNumber('max-retries', 3)
61-
.setJson('config', { theme: 'dark' });
24+
it('returns correct plugin metadata', () => {
25+
expect(new TestData().getMetadata()).toEqual({ name: 'test-data' });
26+
});
6227

63-
const debugOverride = createMockDebugOverride();
64-
td.registerDebug(debugOverride);
28+
it('register is a no-op', () => {
29+
const td = new TestData();
30+
expect(() =>
31+
td.register(undefined, {
32+
sdk: { name: 'test', version: '0.0.0' },
33+
clientSideId: 'test-key',
34+
} as never),
35+
).not.toThrow();
36+
});
6537

66-
expect(debugOverride.overrides['show-banner']).toBe(true);
67-
expect(debugOverride.overrides.greeting).toBe('Hello');
68-
expect(debugOverride.overrides['max-retries']).toBe(3);
69-
expect(debugOverride.overrides.config).toEqual({ theme: 'dark' });
38+
it('seeds initial flags from the constructor and applies them on registerDebug', () => {
39+
const td = new TestData({
40+
'show-banner': true,
41+
greeting: 'Hello',
42+
'max-retries': 3,
43+
config: { theme: 'dark' },
7044
});
7145

72-
it('typed setters propagate live updates after registration', () => {
73-
const td = new TestData();
74-
const debugOverride = createMockDebugOverride();
75-
td.registerDebug(debugOverride);
76-
77-
td.setBool('show-banner', true);
78-
expect(debugOverride.setOverride).toHaveBeenCalledWith('show-banner', true);
46+
const debugOverride = createMockDebugOverride();
47+
td.registerDebug(debugOverride);
7948

80-
td.setString('greeting', 'Howdy');
81-
expect(debugOverride.setOverride).toHaveBeenCalledWith('greeting', 'Howdy');
49+
expect(debugOverride.overrides['show-banner']).toBe(true);
50+
expect(debugOverride.overrides.greeting).toBe('Hello');
51+
expect(debugOverride.overrides['max-retries']).toBe(3);
52+
expect(debugOverride.overrides.config).toEqual({ theme: 'dark' });
53+
});
8254

83-
td.setNumber('max-retries', 5);
84-
expect(debugOverride.setOverride).toHaveBeenCalledWith('max-retries', 5);
55+
it('typed setters chain and apply pre-registration', () => {
56+
const td = new TestData()
57+
.setBool('show-banner', true)
58+
.setString('greeting', 'Hello')
59+
.setNumber('max-retries', 3)
60+
.setJson('config', { theme: 'dark' });
8561

86-
td.setJson('config', [1, 2, 3]);
87-
expect(debugOverride.setOverride).toHaveBeenCalledWith('config', [1, 2, 3]);
88-
});
62+
const debugOverride = createMockDebugOverride();
63+
td.registerDebug(debugOverride);
8964

90-
it('skips setOverride when the same primitive value is set twice', () => {
91-
const td = new TestData();
92-
const debugOverride = createMockDebugOverride();
93-
td.registerDebug(debugOverride);
65+
expect(debugOverride.overrides['show-banner']).toBe(true);
66+
expect(debugOverride.overrides.greeting).toBe('Hello');
67+
expect(debugOverride.overrides['max-retries']).toBe(3);
68+
expect(debugOverride.overrides.config).toEqual({ theme: 'dark' });
69+
});
9470

95-
td.setBool('flag', true);
96-
expect(debugOverride.setOverride).toHaveBeenCalledTimes(1);
71+
it('typed setters propagate live updates after registration', () => {
72+
const td = new TestData();
73+
const debugOverride = createMockDebugOverride();
74+
td.registerDebug(debugOverride);
9775

98-
td.setBool('flag', true);
99-
expect(debugOverride.setOverride).toHaveBeenCalledTimes(1);
76+
td.setBool('show-banner', true);
77+
expect(debugOverride.setOverride).toHaveBeenCalledWith('show-banner', true);
10078

101-
td.setBool('flag', false);
102-
expect(debugOverride.setOverride).toHaveBeenCalledTimes(2);
103-
});
79+
td.setString('greeting', 'Howdy');
80+
expect(debugOverride.setOverride).toHaveBeenCalledWith('greeting', 'Howdy');
10481

105-
it('dedups by reference equality, so passing a new object always fires', () => {
106-
const td = new TestData();
107-
const debugOverride = createMockDebugOverride();
108-
td.registerDebug(debugOverride);
82+
td.setNumber('max-retries', 5);
83+
expect(debugOverride.setOverride).toHaveBeenCalledWith('max-retries', 5);
10984

110-
td.setJson('cfg', { showBanner: true });
111-
expect(debugOverride.setOverride).toHaveBeenCalledTimes(1);
85+
td.setJson('config', [1, 2, 3]);
86+
expect(debugOverride.setOverride).toHaveBeenCalledWith('config', [1, 2, 3]);
87+
});
11288

113-
// New object reference -- fires even though structurally identical.
114-
td.setJson('cfg', { showBanner: true });
115-
expect(debugOverride.setOverride).toHaveBeenCalledTimes(2);
89+
it('fires setOverride on every write, including repeated identical values', () => {
90+
const td = new TestData();
91+
const debugOverride = createMockDebugOverride();
92+
td.registerDebug(debugOverride);
11693

117-
// Same reference twice in a row -- deduped.
118-
const same = { showBanner: false };
119-
td.setJson('cfg', same);
120-
td.setJson('cfg', same);
121-
expect(debugOverride.setOverride).toHaveBeenCalledTimes(3);
122-
});
94+
td.setBool('flag', true);
95+
td.setBool('flag', true);
96+
td.setBool('flag', true);
12397

124-
it('remove clears stored state and the active override', () => {
125-
const td = new TestData({ flag: true });
126-
const debugOverride = createMockDebugOverride();
127-
td.registerDebug(debugOverride);
98+
expect(debugOverride.setOverride).toHaveBeenCalledTimes(3);
99+
expect(debugOverride.setOverride).toHaveBeenNthCalledWith(1, 'flag', true);
100+
expect(debugOverride.setOverride).toHaveBeenNthCalledWith(3, 'flag', true);
101+
});
128102

129-
td.remove('flag');
103+
it('fires setOverride for repeated NaN and object writes', () => {
104+
const td = new TestData();
105+
const debugOverride = createMockDebugOverride();
106+
td.registerDebug(debugOverride);
130107

131-
expect(debugOverride.removeOverride).toHaveBeenCalledWith('flag');
132-
expect(debugOverride.overrides.flag).toBeUndefined();
133-
});
108+
td.setNumber('n', NaN);
109+
td.setNumber('n', NaN);
110+
expect(debugOverride.setOverride).toHaveBeenCalledTimes(2);
134111

135-
it('remove before registerDebug prevents the flag from being applied later', () => {
136-
const td = new TestData({ flag: true });
137-
td.remove('flag');
112+
const same = { showBanner: true };
113+
td.setJson('cfg', same);
114+
td.setJson('cfg', same);
115+
expect(debugOverride.setOverride).toHaveBeenCalledTimes(4);
116+
});
138117

139-
const debugOverride = createMockDebugOverride();
140-
td.registerDebug(debugOverride);
118+
it('remove clears stored state and the active override', () => {
119+
const td = new TestData({ flag: true });
120+
const debugOverride = createMockDebugOverride();
121+
td.registerDebug(debugOverride);
141122

142-
expect(debugOverride.setOverride).not.toHaveBeenCalled();
143-
expect(debugOverride.overrides.flag).toBeUndefined();
144-
});
123+
td.remove('flag');
145124

146-
it('clear resets all flags and clears the override interface', () => {
147-
const td = new TestData({ a: true, b: 'x' });
148-
const debugOverride = createMockDebugOverride();
149-
td.registerDebug(debugOverride);
125+
expect(debugOverride.removeOverride).toHaveBeenCalledWith('flag');
126+
expect(debugOverride.overrides.flag).toBeUndefined();
127+
});
150128

151-
td.clear();
129+
it('remove before registerDebug prevents the flag from being applied later', () => {
130+
const td = new TestData({ flag: true });
131+
td.remove('flag');
152132

153-
expect(debugOverride.clearAllOverrides).toHaveBeenCalledTimes(1);
154-
});
133+
const debugOverride = createMockDebugOverride();
134+
td.registerDebug(debugOverride);
155135

156-
it('clear before registerDebug drops queued flags', () => {
157-
const td = new TestData({ a: true });
158-
td.clear();
136+
expect(debugOverride.setOverride).not.toHaveBeenCalled();
137+
expect(debugOverride.overrides.flag).toBeUndefined();
138+
});
159139

160-
const debugOverride = createMockDebugOverride();
161-
td.registerDebug(debugOverride);
140+
it('clear resets all flags and clears the override interface', () => {
141+
const td = new TestData({ a: true, b: 'x' });
142+
const debugOverride = createMockDebugOverride();
143+
td.registerDebug(debugOverride);
162144

163-
expect(debugOverride.setOverride).not.toHaveBeenCalled();
164-
});
145+
td.clear();
165146

166-
it('throws if registerDebug is called twice', () => {
167-
const td = new TestData();
168-
td.registerDebug(createMockDebugOverride());
147+
expect(debugOverride.clearAllOverrides).toHaveBeenCalledTimes(1);
148+
});
169149

170-
expect(() => td.registerDebug(createMockDebugOverride())).toThrow(
171-
/already been registered/,
172-
);
173-
});
150+
it('clear before registerDebug drops queued flags', () => {
151+
const td = new TestData({ a: true });
152+
td.clear();
174153

175-
it('setJson rejects undefined and other non-object values', () => {
176-
const td = new TestData();
177-
expect(() => td.setJson('flag', undefined as unknown as object)).toThrow(TypeError);
178-
expect(() => td.setJson('flag', null as unknown as object)).toThrow(TypeError);
179-
expect(() => td.setJson('flag', 'string' as unknown as object)).toThrow(TypeError);
180-
expect(() => td.setJson('flag', 42 as unknown as object)).toThrow(TypeError);
181-
});
154+
const debugOverride = createMockDebugOverride();
155+
td.registerDebug(debugOverride);
182156

183-
it('dedups NaN values via Object.is semantics', () => {
184-
const td = new TestData();
185-
const debugOverride = createMockDebugOverride();
186-
td.registerDebug(debugOverride);
157+
expect(debugOverride.setOverride).not.toHaveBeenCalled();
158+
});
187159

188-
td.setNumber('flag', NaN);
189-
expect(debugOverride.setOverride).toHaveBeenCalledTimes(1);
160+
it('throws if registerDebug is called twice', () => {
161+
const td = new TestData();
162+
td.registerDebug(createMockDebugOverride());
190163

191-
td.setNumber('flag', NaN);
192-
expect(debugOverride.setOverride).toHaveBeenCalledTimes(1);
164+
expect(() => td.registerDebug(createMockDebugOverride())).toThrow(
165+
/already been registered/,
166+
);
167+
});
193168

194-
td.setNumber('flag', 0);
195-
expect(debugOverride.setOverride).toHaveBeenCalledTimes(2);
196-
});
169+
it('setJson rejects undefined and other non-object values', () => {
170+
const td = new TestData();
171+
expect(() => td.setJson('flag', undefined as unknown as object)).toThrow(TypeError);
172+
expect(() => td.setJson('flag', null as unknown as object)).toThrow(TypeError);
173+
expect(() => td.setJson('flag', 'string' as unknown as object)).toThrow(TypeError);
174+
expect(() => td.setJson('flag', 42 as unknown as object)).toThrow(TypeError);
175+
});
197176

198-
it('remove and clear return this for chaining', () => {
199-
const td = new TestData({ a: true, b: 'x' });
200-
expect(td.remove('a')).toBe(td);
201-
expect(td.clear()).toBe(td);
202-
});
177+
it('remove and clear return this for chaining', () => {
178+
const td = new TestData({ a: true, b: 'x' });
179+
expect(td.remove('a')).toBe(td);
180+
expect(td.clear()).toBe(td);
181+
});
203182

204-
it('handles flag keys that collide with Object prototype names safely', () => {
205-
const td = new TestData();
206-
const debugOverride = createMockDebugOverride();
207-
td.registerDebug(debugOverride);
183+
it('handles flag keys that collide with Object prototype names safely', () => {
184+
const td = new TestData();
185+
const debugOverride = createMockDebugOverride();
186+
td.registerDebug(debugOverride);
208187

209-
td.setString('toString', 'overridden').setNumber('hasOwnProperty', 42);
188+
td.setString('toString', 'overridden').setNumber('hasOwnProperty', 42);
210189

211-
expect(debugOverride.setOverride).toHaveBeenCalledWith('toString', 'overridden');
212-
expect(debugOverride.setOverride).toHaveBeenCalledWith('hasOwnProperty', 42);
213-
});
190+
expect(debugOverride.setOverride).toHaveBeenCalledWith('toString', 'overridden');
191+
expect(debugOverride.setOverride).toHaveBeenCalledWith('hasOwnProperty', 42);
214192
});

packages/tooling/client-testing-plugin/setup-jest.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,8 @@ if (typeof global.EventSource === 'undefined') {
1313
constructor() {
1414
// no-op
1515
}
16-
1716
addEventListener() {}
18-
1917
removeEventListener() {}
20-
2118
close() {}
2219
};
2320
}

0 commit comments

Comments
 (0)