Skip to content

Commit 2a84c0f

Browse files
feat: update context change listeners to fire immediately for client-level subscriptions
Signed-off-by: Jonathan Norris <[email protected]>
1 parent 2519ad1 commit 2a84c0f

File tree

2 files changed

+49
-14
lines changed

2 files changed

+49
-14
lines changed

packages/web/src/client/internal/open-feature-client.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,8 @@ export class OpenFeatureClient implements Client {
268268
throw new Error(`Unsupported flag type: ${flagType}`);
269269
}
270270

271+
callback(currentDetails, { ...currentDetails });
272+
271273
const handler = () => {
272274
const oldDetails = { ...currentDetails };
273275
let newDetails: EvaluationDetails<T>;

packages/web/test/context-change-subscription.spec.ts

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -69,33 +69,43 @@ describe('Context Change Subscriptions', () => {
6969
});
7070

7171
describe('Client-level onBooleanContextChanged', () => {
72-
it('should fire callback when context changes', (done) => {
72+
it('should fire callback immediately and when context changes', (done) => {
7373
const client = OpenFeature.getClient(domain);
7474
let callCount = 0;
7575

7676
client.onBooleanContextChanged('test-flag', false, (newDetails, oldDetails) => {
7777
callCount++;
7878
if (callCount === 1) {
79+
// Initial callback
7980
expect(newDetails.value).toBe(BOOLEAN_VALUE);
8081
expect(oldDetails.value).toBe(BOOLEAN_VALUE);
8182
expect(newDetails.flagKey).toBe('test-flag');
8283
expect(oldDetails.flagKey).toBe('test-flag');
84+
} else if (callCount === 2) {
85+
// Context change callback
86+
expect(newDetails.value).toBe(BOOLEAN_VALUE);
87+
expect(oldDetails.value).toBe(BOOLEAN_VALUE);
8388
done();
8489
}
8590
});
8691

8792
OpenFeature.setContext(domain, { user: 'test' });
8893
});
8994

90-
it('should pass correct old and new details on context change', (done) => {
95+
it('should pass correct old and new details immediately and on context change', (done) => {
9196
const client = OpenFeature.getClient(domain);
97+
let callCount = 0;
9298

9399
client.onBooleanContextChanged('test-flag', false, (newDetails, oldDetails) => {
100+
callCount++;
94101
expect(oldDetails.value).toBeDefined();
95102
expect(newDetails.value).toBeDefined();
96103
expect(oldDetails.flagKey).toBe('test-flag');
97104
expect(newDetails.flagKey).toBe('test-flag');
98-
done();
105+
106+
if (callCount === 2) {
107+
done();
108+
}
99109
});
100110

101111
OpenFeature.setContext(domain, { user: 'test' });
@@ -109,14 +119,17 @@ describe('Context Change Subscriptions', () => {
109119
callCount++;
110120
});
111121

122+
// Callback fires immediately (callCount = 1)
112123
OpenFeature.setContext(domain, { user: 'test1' });
113124

114125
setTimeout(() => {
126+
// Callback fires on context change (callCount = 2)
115127
unsubscribe();
116128
OpenFeature.setContext(domain, { user: 'test2' });
117129

118130
setTimeout(() => {
119-
expect(callCount).toBe(1);
131+
// Should only be 2 (initial + one context change before unsubscribe)
132+
expect(callCount).toBe(2);
120133
done();
121134
}, 50);
122135
}, 50);
@@ -133,7 +146,8 @@ describe('Context Change Subscriptions', () => {
133146

134147
client.onBooleanContextChanged('test-flag', false, () => {
135148
callCount2++;
136-
if (callCount1 === 1 && callCount2 === 1) {
149+
// Both should have fired once initially, then once on context change
150+
if (callCount1 === 2 && callCount2 === 2) {
137151
done();
138152
}
139153
});
@@ -143,44 +157,61 @@ describe('Context Change Subscriptions', () => {
143157
});
144158

145159
describe('Client-level onStringContextChanged', () => {
146-
it('should fire callback when context changes', (done) => {
160+
it('should fire callback immediately and when context changes', (done) => {
147161
const client = OpenFeature.getClient(domain);
162+
let callCount = 0;
148163

149164
client.onStringContextChanged('test-flag', 'default', (newDetails, oldDetails) => {
165+
callCount++;
150166
expect(newDetails.value).toBe(STRING_VALUE);
151167
expect(oldDetails.value).toBe(STRING_VALUE);
152168
expect(newDetails.flagKey).toBe('test-flag');
153-
done();
169+
if (callCount === 2) {
170+
done();
171+
}
154172
});
155173

156174
OpenFeature.setContext(domain, { user: 'test' });
157175
});
158176
});
159177

160178
describe('Client-level onNumberContextChanged', () => {
161-
it('should fire callback when context changes', (done) => {
179+
it('should fire callback immediately and when context changes', (done) => {
162180
const client = OpenFeature.getClient(domain);
181+
let callCount = 0;
163182

164183
client.onNumberContextChanged('test-flag', 0, (newDetails, oldDetails) => {
184+
callCount++;
165185
expect(newDetails.value).toBe(NUMBER_VALUE);
166186
expect(oldDetails.value).toBe(NUMBER_VALUE);
167187
expect(newDetails.flagKey).toBe('test-flag');
168-
done();
188+
if (callCount === 2) {
189+
done();
190+
}
169191
});
170192

171193
OpenFeature.setContext(domain, { user: 'test' });
172194
});
173195
});
174196

175197
describe('Client-level onObjectContextChanged', () => {
176-
it('should fire callback when context changes', (done) => {
198+
it('should fire callback immediately and when context changes', (done) => {
177199
const client = OpenFeature.getClient(domain);
200+
let callCount = 0;
178201

179202
client.onObjectContextChanged('test-flag', {}, (newDetails, oldDetails) => {
180-
expect(newDetails.value).toEqual(OBJECT_VALUE);
181-
expect(oldDetails.value).toEqual({});
182-
expect(newDetails.flagKey).toBe('test-flag');
183-
done();
203+
callCount++;
204+
if (callCount === 1) {
205+
// Initial callback - both old and new are same initially
206+
expect(newDetails.value).toEqual(OBJECT_VALUE);
207+
expect(oldDetails.value).toEqual(OBJECT_VALUE);
208+
} else if (callCount === 2) {
209+
// Context change callback
210+
expect(newDetails.value).toEqual(OBJECT_VALUE);
211+
expect(oldDetails.value).toEqual(OBJECT_VALUE);
212+
expect(newDetails.flagKey).toBe('test-flag');
213+
done();
214+
}
184215
});
185216

186217
OpenFeature.setContext(domain, { user: 'test' });
@@ -254,6 +285,7 @@ describe('Context Change Subscriptions', () => {
254285
OpenFeature.setContext(domain, { user: 'test2' });
255286

256287
setTimeout(() => {
288+
// Should only be 1 (one context change before unsubscribe)
257289
expect(callCount).toBe(1);
258290
done();
259291
}, 50);
@@ -337,6 +369,7 @@ describe('Context Change Subscriptions', () => {
337369
if ('onContextChanged' in details && typeof details.onContextChanged === 'function') {
338370
details.onContextChanged(() => {
339371
callCount++;
372+
// Two context changes
340373
if (callCount === 2) {
341374
done();
342375
}

0 commit comments

Comments
 (0)