1
- import React from 'react' ;
1
+ import React , { type FC } from 'react' ;
2
2
import { render , screen , waitFor } from '@testing-library/react' ;
3
3
import { EVENTS , UnleashClient } from 'unleash-proxy-client' ;
4
4
import FlagProvider from './FlagProvider' ;
@@ -31,6 +31,10 @@ const fetchMock = vi.fn(async () => {
31
31
} ) ;
32
32
} ) ;
33
33
34
+ beforeEach ( ( ) => {
35
+ vi . clearAllMocks ( ) ;
36
+ } ) ;
37
+
34
38
test ( 'should render toggles' , async ( ) => {
35
39
const client = new UnleashClient ( {
36
40
url : 'http://localhost:4242/api/frontend' ,
@@ -78,21 +82,20 @@ test('should render toggles', async () => {
78
82
) ;
79
83
80
84
// After client initialization
81
- expect ( fetchMock ) . toHaveBeenCalled ( ) ;
82
- rerender ( ui ) ;
83
85
expect ( screen . getByTestId ( 'ready' ) ) . toHaveTextContent ( 'true' ) ;
84
86
expect ( screen . getByTestId ( 'state' ) ) . toHaveTextContent ( 'true' ) ;
85
87
expect ( screen . getByTestId ( 'variant' ) ) . toHaveTextContent (
86
88
'{"name":"A","payload":{"type":"string","value":"A"},"enabled":true,"feature_enabled":true}'
87
89
) ;
90
+ expect ( fetchMock ) . toHaveBeenCalledOnce ( ) ;
88
91
} ) ;
89
92
90
93
test ( 'should be ready from the start if bootstrapped' , ( ) => {
91
- const Component = React . memo ( ( ) => {
94
+ const Component : FC = ( ) => {
92
95
const { flagsReady } = useFlagContext ( ) ;
93
96
94
97
return < > { flagsReady ? 'ready' : '' } </ > ;
95
- } ) ;
98
+ }
96
99
97
100
render (
98
101
< FlagProvider
@@ -262,3 +265,80 @@ test('should resolve values before setting flagsReady', async () => {
262
265
expect ( renders ) . toBe ( 3 ) ;
263
266
} ) ;
264
267
} ) ;
268
+
269
+ test ( 'should only re-render if flag changed, and not on status or context change' , async ( ) => {
270
+ const client = new UnleashClient ( {
271
+ url : 'http://localhost:4242/api/frontend' ,
272
+ appName : 'test' ,
273
+ clientKey : 'test' ,
274
+ fetch : fetchMock ,
275
+ } ) ;
276
+ let renders = 0 ;
277
+
278
+ const FlagTestComponent : FC = ( ) => {
279
+ const flag = useFlag ( 'another-flag' ) ;
280
+ renders += 1 ;
281
+
282
+ return (
283
+ < div data-testid = "flag" > { flag . toString ( ) } </ div >
284
+ ) ;
285
+ } ;
286
+
287
+ const ui = (
288
+ < FlagProvider unleashClient = { client } >
289
+ < FlagTestComponent />
290
+ </ FlagProvider >
291
+ ) ;
292
+
293
+ render ( ui ) ;
294
+
295
+ // Before client initialization
296
+ expect ( fetchMock ) . not . toHaveBeenCalled ( ) ;
297
+ expect ( screen . getByTestId ( 'flag' ) ) . toHaveTextContent ( 'false' ) ;
298
+ expect ( renders ) . toBe ( 1 ) ;
299
+
300
+ // Wait for client initialization
301
+ await act (
302
+ ( ) =>
303
+ new Promise ( ( resolve ) => {
304
+ client . on ( EVENTS . READY , ( ) => {
305
+ setTimeout ( resolve , 1 ) ;
306
+ } ) ;
307
+ } )
308
+ ) ;
309
+
310
+ expect ( screen . getByTestId ( 'flag' ) ) . toHaveTextContent ( 'false' ) ;
311
+
312
+ fetchMock . mockImplementationOnce ( async ( ) => {
313
+ return Promise . resolve ( {
314
+ ok : true ,
315
+ status : 200 ,
316
+ headers : new Headers ( { } ) ,
317
+ json : ( ) => {
318
+ return Promise . resolve ( {
319
+ toggles : [
320
+ {
321
+ name : 'another-flag' ,
322
+ enabled : true ,
323
+ variant : {
324
+ name : 'A' ,
325
+ payload : { type : 'string' , value : 'A' } ,
326
+ enabled : true ,
327
+ } ,
328
+ } ,
329
+ ] ,
330
+ } ) ;
331
+ } ,
332
+ } ) ;
333
+ } ) ;
334
+
335
+ // Simulate flag update
336
+ await act ( ( ) => client . updateToggles ( ) ) ;
337
+ expect ( screen . getByTestId ( 'flag' ) ) . toHaveTextContent ( 'true' ) ;
338
+
339
+ await act ( ( ) => client . updateToggles ( ) ) ;
340
+ expect ( screen . getByTestId ( 'flag' ) ) . toHaveTextContent ( 'false' ) ;
341
+
342
+ expect ( fetchMock ) . toHaveBeenCalledTimes ( 3 ) ;
343
+ expect ( renders ) . toBe ( 3 ) ;
344
+ } ) ;
0 commit comments