@wordpress/data 6.5.0 → 6.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import TestRenderer, { act } from 'react-test-renderer';
4
+ import { act, render } from '@testing-library/react';
5
5
 
6
6
  /**
7
7
  * WordPress dependencies
@@ -11,10 +11,15 @@ import { useState, useReducer } from '@wordpress/element';
11
11
  /**
12
12
  * Internal dependencies
13
13
  */
14
- import { createRegistry } from '../../../registry';
15
- import { createRegistrySelector } from '../../../factory';
16
- import { RegistryProvider } from '../../registry-provider';
17
- import useSelect from '../index';
14
+ import {
15
+ createRegistry,
16
+ createRegistrySelector,
17
+ RegistryProvider,
18
+ AsyncModeProvider,
19
+ } from '../../..';
20
+ import useSelect from '..';
21
+
22
+ jest.useRealTimers();
18
23
 
19
24
  describe( 'useSelect', () => {
20
25
  let registry;
@@ -22,15 +27,6 @@ describe( 'useSelect', () => {
22
27
  registry = createRegistry();
23
28
  } );
24
29
 
25
- const getTestComponent = ( mapSelectSpy, dependencyKey ) => ( props ) => {
26
- const dependencies = props[ dependencyKey ];
27
- mapSelectSpy.mockImplementation( ( select ) => ( {
28
- results: select( 'testStore' ).testSelector( props.keyName ),
29
- } ) );
30
- const data = useSelect( mapSelectSpy, [ dependencies ] );
31
- return <div>{ data.results }</div>;
32
- };
33
-
34
30
  it( 'passes the relevant data to the component', () => {
35
31
  registry.registerStore( 'testStore', {
36
32
  reducer: () => ( { foo: 'bar' } ),
@@ -38,19 +34,22 @@ describe( 'useSelect', () => {
38
34
  testSelector: ( state, key ) => state[ key ],
39
35
  },
40
36
  } );
37
+
41
38
  const selectSpy = jest.fn();
42
- const TestComponent = jest
43
- .fn()
44
- .mockImplementation( getTestComponent( selectSpy, 'keyName' ) );
45
- let renderer;
46
- act( () => {
47
- renderer = TestRenderer.create(
48
- <RegistryProvider value={ registry }>
49
- <TestComponent keyName="foo" />
50
- </RegistryProvider>
51
- );
39
+ const TestComponent = jest.fn( ( props ) => {
40
+ selectSpy.mockImplementation( ( select ) => ( {
41
+ results: select( 'testStore' ).testSelector( props.keyName ),
42
+ } ) );
43
+ const data = useSelect( selectSpy, [ props.keyName ] );
44
+ return <div role="status">{ data.results }</div>;
52
45
  } );
53
- const testInstance = renderer.root;
46
+
47
+ const rendered = render(
48
+ <RegistryProvider value={ registry }>
49
+ <TestComponent keyName="foo" />
50
+ </RegistryProvider>
51
+ );
52
+
54
53
  // 2 times expected
55
54
  // - 1 for initial mount
56
55
  // - 1 for after mount before subscription set.
@@ -58,9 +57,7 @@ describe( 'useSelect', () => {
58
57
  expect( TestComponent ).toHaveBeenCalledTimes( 1 );
59
58
 
60
59
  // Ensure expected state was rendered.
61
- expect( testInstance.findByType( 'div' ).props ).toEqual( {
62
- children: 'bar',
63
- } );
60
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent( 'bar' );
64
61
  } );
65
62
 
66
63
  it( 'uses memoized selector if dependencies do not change', () => {
@@ -71,73 +68,62 @@ describe( 'useSelect', () => {
71
68
  },
72
69
  } );
73
70
 
74
- const selectSpyFoo = jest.fn().mockImplementation( () => 'foo' );
75
- const selectSpyBar = jest.fn().mockImplementation( () => 'bar' );
76
- const TestComponent = jest.fn().mockImplementation( ( props ) => {
71
+ const selectSpyFoo = jest.fn( () => 'foo' );
72
+ const selectSpyBar = jest.fn( () => 'bar' );
73
+ const TestComponent = jest.fn( ( props ) => {
77
74
  const mapSelect = props.change ? selectSpyFoo : selectSpyBar;
78
75
  const data = useSelect( mapSelect, [ props.keyName ] );
79
- return <div>{ data }</div>;
80
- } );
81
- let renderer;
82
- act( () => {
83
- renderer = TestRenderer.create(
84
- <RegistryProvider value={ registry }>
85
- <TestComponent keyName="foo" change={ true } />
86
- </RegistryProvider>
87
- );
76
+ return <div role="status">{ data }</div>;
88
77
  } );
89
- const testInstance = renderer.root;
78
+
79
+ const rendered = render(
80
+ <RegistryProvider value={ registry }>
81
+ <TestComponent keyName="foo" change={ true } />
82
+ </RegistryProvider>
83
+ );
90
84
 
91
85
  expect( selectSpyFoo ).toHaveBeenCalledTimes( 2 );
92
86
  expect( selectSpyBar ).toHaveBeenCalledTimes( 0 );
93
87
  expect( TestComponent ).toHaveBeenCalledTimes( 1 );
94
88
 
95
89
  // Ensure expected state was rendered.
96
- expect( testInstance.findByType( 'div' ).props ).toEqual( {
97
- children: 'foo',
98
- } );
90
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent( 'foo' );
99
91
 
100
92
  // Rerender with non dependency changed.
101
- act( () => {
102
- renderer.update(
103
- <RegistryProvider value={ registry }>
104
- <TestComponent keyName="foo" change={ false } />
105
- </RegistryProvider>
106
- );
107
- } );
93
+ rendered.rerender(
94
+ <RegistryProvider value={ registry }>
95
+ <TestComponent keyName="foo" change={ false } />
96
+ </RegistryProvider>
97
+ );
108
98
 
109
99
  expect( selectSpyFoo ).toHaveBeenCalledTimes( 2 );
110
100
  expect( selectSpyBar ).toHaveBeenCalledTimes( 0 );
111
101
  expect( TestComponent ).toHaveBeenCalledTimes( 2 );
112
102
 
113
103
  // Ensure expected state was rendered.
114
- expect( testInstance.findByType( 'div' ).props ).toEqual( {
115
- children: 'foo',
116
- } );
104
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent( 'foo' );
117
105
 
118
106
  // Rerender with dependency changed.
119
- act( () => {
120
- renderer.update(
121
- <RegistryProvider value={ registry }>
122
- <TestComponent keyName="bar" change={ false } />
123
- </RegistryProvider>
124
- );
125
- } );
107
+ rendered.rerender(
108
+ <RegistryProvider value={ registry }>
109
+ <TestComponent keyName="bar" change={ false } />
110
+ </RegistryProvider>
111
+ );
126
112
 
127
113
  expect( selectSpyFoo ).toHaveBeenCalledTimes( 2 );
128
114
  expect( selectSpyBar ).toHaveBeenCalledTimes( 2 );
129
115
  expect( TestComponent ).toHaveBeenCalledTimes( 3 );
130
116
 
131
117
  // Ensure expected state was rendered.
132
- expect( testInstance.findByType( 'div' ).props ).toEqual( {
133
- children: 'bar',
134
- } );
118
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent( 'bar' );
135
119
  } );
120
+
136
121
  describe( 'rerenders as expected with various mapSelect return types', () => {
137
122
  const getComponent = ( mapSelectSpy ) => () => {
138
123
  const data = useSelect( mapSelectSpy, [] );
139
- return <div data={ data } />;
124
+ return <div role="status" data-d={ JSON.stringify( data ) } />;
140
125
  };
126
+
141
127
  let TestComponent;
142
128
  const mapSelectSpy = jest.fn( ( select ) =>
143
129
  select( 'testStore' ).testSelector()
@@ -156,6 +142,7 @@ describe( 'useSelect', () => {
156
142
  } );
157
143
  TestComponent = getComponent( mapSelectSpy );
158
144
  } );
145
+
159
146
  afterEach( () => {
160
147
  selectorSpy.mockClear();
161
148
  mapSelectSpy.mockClear();
@@ -180,18 +167,14 @@ describe( 'useSelect', () => {
180
167
  ( type, testValues ) => {
181
168
  const [ valueA, valueB ] = testValues;
182
169
  selectorSpy.mockReturnValue( valueA );
183
- let renderer;
184
- act( () => {
185
- renderer = TestRenderer.create(
186
- <RegistryProvider value={ registry }>
187
- <TestComponent />
188
- </RegistryProvider>
189
- );
190
- } );
191
- const testInstance = renderer.root;
170
+ const rendered = render(
171
+ <RegistryProvider value={ registry }>
172
+ <TestComponent />
173
+ </RegistryProvider>
174
+ );
192
175
  // Ensure expected state was rendered.
193
- expect( testInstance.findByType( 'div' ).props.data ).toEqual(
194
- valueA
176
+ expect( rendered.getByRole( 'status' ).dataset.d ).toBe(
177
+ JSON.stringify( valueA )
195
178
  );
196
179
 
197
180
  // Update the returned value from the selector and trigger the
@@ -200,15 +183,15 @@ describe( 'useSelect', () => {
200
183
  selectorSpy.mockReturnValue( valueB );
201
184
  registry.dispatch( 'testStore' ).forceUpdate();
202
185
  } );
203
- expect( testInstance.findByType( 'div' ).props.data ).toEqual(
204
- valueB
186
+ expect( rendered.getByRole( 'status' ).dataset.d ).toBe(
187
+ JSON.stringify( valueB )
205
188
  );
206
189
  expect( mapSelectSpy ).toHaveBeenCalledTimes( 3 );
207
190
  }
208
191
  );
209
192
  } );
210
193
 
211
- describe( 're-calls the selector as minimal times as possible', () => {
194
+ describe( 're-calls the selector as few times as possible', () => {
212
195
  const counterStore = {
213
196
  actions: {
214
197
  increment: () => ( { type: 'INCREMENT' } ),
@@ -231,8 +214,6 @@ describe( 'useSelect', () => {
231
214
  registry.registerStore( 'store-1', counterStore );
232
215
  registry.registerStore( 'store-2', counterStore );
233
216
 
234
- let renderer;
235
-
236
217
  const selectCount1 = jest.fn();
237
218
  const selectCount2 = jest.fn();
238
219
 
@@ -248,23 +229,19 @@ describe( 'useSelect', () => {
248
229
  []
249
230
  );
250
231
 
251
- return <div data={ count1 } />;
232
+ return <div role="status">{ count1 }</div>;
252
233
  } );
253
234
 
254
- act( () => {
255
- renderer = TestRenderer.create(
256
- <RegistryProvider value={ registry }>
257
- <TestComponent />
258
- </RegistryProvider>
259
- );
260
- } );
261
-
262
- const testInstance = renderer.root;
235
+ const rendered = render(
236
+ <RegistryProvider value={ registry }>
237
+ <TestComponent />
238
+ </RegistryProvider>
239
+ );
263
240
 
264
241
  expect( selectCount1 ).toHaveBeenCalledTimes( 2 );
265
242
  expect( selectCount2 ).toHaveBeenCalledTimes( 2 );
266
243
  expect( TestComponent ).toHaveBeenCalledTimes( 1 );
267
- expect( testInstance.findByType( 'div' ).props.data ).toBe( 0 );
244
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent( 0 );
268
245
 
269
246
  act( () => {
270
247
  registry.dispatch( 'store-2' ).increment();
@@ -273,7 +250,7 @@ describe( 'useSelect', () => {
273
250
  expect( selectCount1 ).toHaveBeenCalledTimes( 2 );
274
251
  expect( selectCount2 ).toHaveBeenCalledTimes( 3 );
275
252
  expect( TestComponent ).toHaveBeenCalledTimes( 2 );
276
- expect( testInstance.findByType( 'div' ).props.data ).toBe( 0 );
253
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent( 0 );
277
254
 
278
255
  act( () => {
279
256
  registry.dispatch( 'store-1' ).increment();
@@ -282,10 +259,10 @@ describe( 'useSelect', () => {
282
259
  expect( selectCount1 ).toHaveBeenCalledTimes( 3 );
283
260
  expect( selectCount2 ).toHaveBeenCalledTimes( 3 );
284
261
  expect( TestComponent ).toHaveBeenCalledTimes( 3 );
285
- expect( testInstance.findByType( 'div' ).props.data ).toBe( 1 );
262
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent( 1 );
286
263
 
287
264
  // Test if the unsubscribers get called correctly.
288
- renderer.unmount();
265
+ rendered.unmount();
289
266
  } );
290
267
 
291
268
  it( 'can subscribe to multiple stores at once', () => {
@@ -293,8 +270,6 @@ describe( 'useSelect', () => {
293
270
  registry.registerStore( 'store-2', counterStore );
294
271
  registry.registerStore( 'store-3', counterStore );
295
272
 
296
- let renderer;
297
-
298
273
  const selectCount1And2 = jest.fn();
299
274
 
300
275
  const TestComponent = jest.fn( () => {
@@ -307,44 +282,35 @@ describe( 'useSelect', () => {
307
282
  []
308
283
  );
309
284
 
310
- return <div data={ { count1, count2 } } />;
311
- } );
312
-
313
- act( () => {
314
- renderer = TestRenderer.create(
315
- <RegistryProvider value={ registry }>
316
- <TestComponent />
317
- </RegistryProvider>
285
+ return (
286
+ <div role="status">
287
+ { count1 },{ count2 }
288
+ </div>
318
289
  );
319
290
  } );
320
291
 
321
- const testInstance = renderer.root;
292
+ const rendered = render(
293
+ <RegistryProvider value={ registry }>
294
+ <TestComponent />
295
+ </RegistryProvider>
296
+ );
322
297
 
323
298
  expect( selectCount1And2 ).toHaveBeenCalledTimes( 2 );
324
- expect( testInstance.findByType( 'div' ).props.data ).toEqual( {
325
- count1: 0,
326
- count2: 0,
327
- } );
299
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent( '0,0' );
328
300
 
329
301
  act( () => {
330
302
  registry.dispatch( 'store-2' ).increment();
331
303
  } );
332
304
 
333
305
  expect( selectCount1And2 ).toHaveBeenCalledTimes( 3 );
334
- expect( testInstance.findByType( 'div' ).props.data ).toEqual( {
335
- count1: 0,
336
- count2: 1,
337
- } );
306
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent( '0,1' );
338
307
 
339
308
  act( () => {
340
309
  registry.dispatch( 'store-3' ).increment();
341
310
  } );
342
311
 
343
312
  expect( selectCount1And2 ).toHaveBeenCalledTimes( 3 );
344
- expect( testInstance.findByType( 'div' ).props.data ).toEqual( {
345
- count1: 0,
346
- count2: 1,
347
- } );
313
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent( '0,1' );
348
314
  } );
349
315
 
350
316
  it( 're-calls the selector when deps changed', () => {
@@ -352,7 +318,7 @@ describe( 'useSelect', () => {
352
318
  registry.registerStore( 'store-2', counterStore );
353
319
  registry.registerStore( 'store-3', counterStore );
354
320
 
355
- let renderer, dep, setDep;
321
+ let dep, setDep;
356
322
  const selectCount1AndDep = jest.fn();
357
323
 
358
324
  const TestComponent = jest.fn( () => {
@@ -366,44 +332,41 @@ describe( 'useSelect', () => {
366
332
  [ dep ]
367
333
  );
368
334
 
369
- return <div data={ state } />;
370
- } );
371
-
372
- act( () => {
373
- renderer = TestRenderer.create(
374
- <RegistryProvider value={ registry }>
375
- <TestComponent />
376
- </RegistryProvider>
335
+ return (
336
+ <div role="status">
337
+ count:{ state.count1 },dep:{ state.dep }
338
+ </div>
377
339
  );
378
340
  } );
379
341
 
380
- const testInstance = renderer.root;
342
+ const rendered = render(
343
+ <RegistryProvider value={ registry }>
344
+ <TestComponent />
345
+ </RegistryProvider>
346
+ );
381
347
 
382
348
  expect( selectCount1AndDep ).toHaveBeenCalledTimes( 2 );
383
- expect( testInstance.findByType( 'div' ).props.data ).toEqual( {
384
- count1: 0,
385
- dep: 0,
386
- } );
349
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent(
350
+ 'count:0,dep:0'
351
+ );
387
352
 
388
353
  act( () => {
389
354
  setDep( 1 );
390
355
  } );
391
356
 
392
357
  expect( selectCount1AndDep ).toHaveBeenCalledTimes( 4 );
393
- expect( testInstance.findByType( 'div' ).props.data ).toEqual( {
394
- count1: 0,
395
- dep: 1,
396
- } );
358
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent(
359
+ 'count:0,dep:1'
360
+ );
397
361
 
398
362
  act( () => {
399
363
  registry.dispatch( 'store-1' ).increment();
400
364
  } );
401
365
 
402
366
  expect( selectCount1AndDep ).toHaveBeenCalledTimes( 5 );
403
- expect( testInstance.findByType( 'div' ).props.data ).toEqual( {
404
- count1: 1,
405
- dep: 1,
406
- } );
367
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent(
368
+ 'count:1,dep:1'
369
+ );
407
370
  } );
408
371
 
409
372
  it( 'handles registry selectors', () => {
@@ -423,7 +386,6 @@ describe( 'useSelect', () => {
423
386
  } );
424
387
  registry.registerStore( 'store-2', counterStore );
425
388
 
426
- let renderer;
427
389
  const selectCount1And2 = jest.fn();
428
390
 
429
391
  const TestComponent = jest.fn( () => {
@@ -434,46 +396,43 @@ describe( 'useSelect', () => {
434
396
  []
435
397
  );
436
398
 
437
- return <div data={ state } />;
438
- } );
439
-
440
- act( () => {
441
- renderer = TestRenderer.create(
442
- <RegistryProvider value={ registry }>
443
- <TestComponent />
444
- </RegistryProvider>
399
+ return (
400
+ <div role="status">
401
+ count1:{ state.count1 },count2:{ state.count2 }
402
+ </div>
445
403
  );
446
404
  } );
447
405
 
448
- const testInstance = renderer.root;
406
+ const rendered = render(
407
+ <RegistryProvider value={ registry }>
408
+ <TestComponent />
409
+ </RegistryProvider>
410
+ );
449
411
 
450
412
  expect( selectCount1And2 ).toHaveBeenCalledTimes( 2 );
451
- expect( testInstance.findByType( 'div' ).props.data ).toEqual( {
452
- count1: 0,
453
- count2: 0,
454
- } );
413
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent(
414
+ 'count1:0,count2:0'
415
+ );
455
416
 
456
417
  act( () => {
457
418
  registry.dispatch( 'store-2' ).increment();
458
419
  } );
459
420
 
460
421
  expect( selectCount1And2 ).toHaveBeenCalledTimes( 3 );
461
- expect( testInstance.findByType( 'div' ).props.data ).toEqual( {
462
- count1: 0,
463
- count2: 1,
464
- } );
422
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent(
423
+ 'count1:0,count2:1'
424
+ );
465
425
  } );
466
426
 
467
427
  it( 'handles conditional statements in selectors', () => {
468
428
  registry.registerStore( 'store-1', counterStore );
469
429
  registry.registerStore( 'store-2', counterStore );
470
430
 
471
- let renderer, shouldSelectCount1, toggle;
472
431
  const selectCount1 = jest.fn();
473
432
  const selectCount2 = jest.fn();
474
433
 
475
434
  const TestComponent = jest.fn( () => {
476
- [ shouldSelectCount1, toggle ] = useReducer(
435
+ const [ shouldSelectCount1, toggle ] = useReducer(
477
436
  ( should ) => ! should,
478
437
  false
479
438
  );
@@ -492,32 +451,31 @@ describe( 'useSelect', () => {
492
451
  [ shouldSelectCount1 ]
493
452
  );
494
453
 
495
- return <div data={ state } />;
496
- } );
497
-
498
- act( () => {
499
- renderer = TestRenderer.create(
500
- <RegistryProvider value={ registry }>
501
- <TestComponent />
502
- </RegistryProvider>
454
+ return (
455
+ <>
456
+ <div role="status">{ state }</div>
457
+ <button onClick={ toggle }>Toggle</button>
458
+ </>
503
459
  );
504
460
  } );
505
461
 
506
- const testInstance = renderer.root;
462
+ const rendered = render(
463
+ <RegistryProvider value={ registry }>
464
+ <TestComponent />
465
+ </RegistryProvider>
466
+ );
507
467
 
508
468
  expect( selectCount1 ).toHaveBeenCalledTimes( 0 );
509
469
  expect( selectCount2 ).toHaveBeenCalledTimes( 2 );
510
- expect( testInstance.findByType( 'div' ).props.data ).toBe(
470
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent(
511
471
  'count2'
512
472
  );
513
473
 
514
- act( () => {
515
- toggle();
516
- } );
474
+ rendered.getByText( 'Toggle' ).click();
517
475
 
518
476
  expect( selectCount1 ).toHaveBeenCalledTimes( 2 );
519
477
  expect( selectCount2 ).toHaveBeenCalledTimes( 2 );
520
- expect( testInstance.findByType( 'div' ).props.data ).toBe(
478
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent(
521
479
  'count1'
522
480
  );
523
481
  } );
@@ -528,8 +486,6 @@ describe( 'useSelect', () => {
528
486
  const subRegistry = createRegistry( {}, registry );
529
487
  subRegistry.registerStore( 'child-store', counterStore );
530
488
 
531
- let renderer;
532
-
533
489
  const TestComponent = jest.fn( () => {
534
490
  const state = useSelect(
535
491
  ( select ) => ( {
@@ -539,106 +495,95 @@ describe( 'useSelect', () => {
539
495
  []
540
496
  );
541
497
 
542
- return <div data={ state } />;
543
- } );
544
-
545
- act( () => {
546
- renderer = TestRenderer.create(
547
- <RegistryProvider value={ registry }>
548
- <RegistryProvider value={ subRegistry }>
549
- <TestComponent />
550
- </RegistryProvider>
551
- </RegistryProvider>
498
+ return (
499
+ <div role="status">
500
+ parent:{ state.parentCount },child:{ state.childCount }
501
+ </div>
552
502
  );
553
503
  } );
554
504
 
555
- const testInstance = renderer.root;
505
+ const rendered = render(
506
+ <RegistryProvider value={ registry }>
507
+ <RegistryProvider value={ subRegistry }>
508
+ <TestComponent />
509
+ </RegistryProvider>
510
+ </RegistryProvider>
511
+ );
556
512
 
557
- expect( testInstance.findByType( 'div' ).props.data ).toEqual( {
558
- parentCount: 0,
559
- childCount: 0,
560
- } );
513
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent(
514
+ 'parent:0,child:0'
515
+ );
561
516
 
562
517
  act( () => {
563
518
  registry.dispatch( 'parent-store' ).increment();
564
519
  } );
565
520
 
566
- expect( testInstance.findByType( 'div' ).props.data ).toEqual( {
567
- parentCount: 1,
568
- childCount: 0,
569
- } );
521
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent(
522
+ 'parent:1,child:0'
523
+ );
570
524
  } );
571
525
 
572
526
  it( 'handles non-existing stores', () => {
573
527
  registry.registerStore( 'store-1', counterStore );
574
528
 
575
- let renderer;
576
-
577
529
  const TestComponent = jest.fn( () => {
578
530
  const state = useSelect(
579
531
  ( select ) => ( {
580
532
  count1: select( 'store-1' ).getCounter(),
581
- blank: select( 'non-existing-store' )?.getCounter(),
533
+ count2: select( 'store-2' )?.getCounter() ?? 'blank',
582
534
  } ),
583
535
  []
584
536
  );
585
537
 
586
- return <div data={ state } />;
587
- } );
588
-
589
- act( () => {
590
- renderer = TestRenderer.create(
591
- <RegistryProvider value={ registry }>
592
- <TestComponent />
593
- </RegistryProvider>
538
+ return (
539
+ <div role="status">
540
+ count1:{ state.count1 },count2:{ state.count2 }
541
+ </div>
594
542
  );
595
543
  } );
596
544
 
597
- const testInstance = renderer.root;
545
+ const rendered = render(
546
+ <RegistryProvider value={ registry }>
547
+ <TestComponent />
548
+ </RegistryProvider>
549
+ );
598
550
 
599
- expect( testInstance.findByType( 'div' ).props.data ).toEqual( {
600
- count1: 0,
601
- blank: undefined,
602
- } );
551
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent(
552
+ 'count1:0,count2:blank'
553
+ );
603
554
 
604
555
  act( () => {
605
556
  registry.dispatch( 'store-1' ).increment();
606
557
  } );
607
558
 
608
- expect( testInstance.findByType( 'div' ).props.data ).toEqual( {
609
- count1: 1,
610
- blank: undefined,
611
- } );
559
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent(
560
+ 'count1:1,count2:blank'
561
+ );
612
562
 
613
563
  // Test if the unsubscribers get called correctly.
614
- renderer.unmount();
564
+ rendered.unmount();
615
565
  } );
616
566
 
617
567
  it( 'handles registration of a non-existing store during rendering', () => {
618
- let renderer;
619
-
620
568
  const TestComponent = jest.fn( () => {
621
569
  const state = useSelect(
622
570
  ( select ) =>
623
- select( 'not-yet-registered-store' )?.getCounter(),
571
+ select( 'not-yet-registered-store' )?.getCounter() ??
572
+ 'blank',
624
573
  []
625
574
  );
626
575
 
627
- return <div data={ state } />;
576
+ return <div role="status">{ state }</div>;
628
577
  } );
629
578
 
630
- act( () => {
631
- renderer = TestRenderer.create(
632
- <RegistryProvider value={ registry }>
633
- <TestComponent />
634
- </RegistryProvider>
635
- );
636
- } );
637
-
638
- const testInstance = renderer.root;
579
+ const rendered = render(
580
+ <RegistryProvider value={ registry }>
581
+ <TestComponent />
582
+ </RegistryProvider>
583
+ );
639
584
 
640
- expect( testInstance.findByType( 'div' ).props.data ).toBe(
641
- undefined
585
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent(
586
+ 'blank'
642
587
  );
643
588
 
644
589
  act( () => {
@@ -649,23 +594,21 @@ describe( 'useSelect', () => {
649
594
  } );
650
595
 
651
596
  // This is not ideal, but is the way it's working before and we want to prevent breaking changes.
652
- expect( testInstance.findByType( 'div' ).props.data ).toBe(
653
- undefined
597
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent(
598
+ 'blank'
654
599
  );
655
600
 
656
601
  act( () => {
657
602
  registry.dispatch( 'not-yet-registered-store' ).increment();
658
603
  } );
659
604
 
660
- expect( testInstance.findByType( 'div' ).props.data ).toBe( 1 );
605
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent( 1 );
661
606
 
662
607
  // Test if the unsubscribers get called correctly.
663
- renderer.unmount();
608
+ rendered.unmount();
664
609
  } );
665
610
 
666
611
  it( 'handles registration of a non-existing store of sub-registry during rendering', () => {
667
- let renderer;
668
-
669
612
  const subRegistry = createRegistry( {}, registry );
670
613
 
671
614
  const TestComponent = jest.fn( () => {
@@ -673,27 +616,23 @@ describe( 'useSelect', () => {
673
616
  ( select ) =>
674
617
  select(
675
618
  'not-yet-registered-child-store'
676
- )?.getCounter(),
619
+ )?.getCounter() ?? 'blank',
677
620
  []
678
621
  );
679
622
 
680
- return <div data={ state } />;
623
+ return <div role="status">{ state }</div>;
681
624
  } );
682
625
 
683
- act( () => {
684
- renderer = TestRenderer.create(
685
- <RegistryProvider value={ registry }>
686
- <RegistryProvider value={ subRegistry }>
687
- <TestComponent />
688
- </RegistryProvider>
626
+ const rendered = render(
627
+ <RegistryProvider value={ registry }>
628
+ <RegistryProvider value={ subRegistry }>
629
+ <TestComponent />
689
630
  </RegistryProvider>
690
- );
691
- } );
692
-
693
- const testInstance = renderer.root;
631
+ </RegistryProvider>
632
+ );
694
633
 
695
- expect( testInstance.findByType( 'div' ).props.data ).toBe(
696
- undefined
634
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent(
635
+ 'blank'
697
636
  );
698
637
 
699
638
  act( () => {
@@ -704,8 +643,8 @@ describe( 'useSelect', () => {
704
643
  } );
705
644
 
706
645
  // This is not ideal, but is the way it's working before and we want to prevent breaking changes.
707
- expect( testInstance.findByType( 'div' ).props.data ).toBe(
708
- undefined
646
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent(
647
+ 'blank'
709
648
  );
710
649
 
711
650
  act( () => {
@@ -714,15 +653,13 @@ describe( 'useSelect', () => {
714
653
  .increment();
715
654
  } );
716
655
 
717
- expect( testInstance.findByType( 'div' ).props.data ).toBe( 1 );
656
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent( '1' );
718
657
 
719
658
  // Test if the unsubscribers get called correctly.
720
- renderer.unmount();
659
+ rendered.unmount();
721
660
  } );
722
661
 
723
662
  it( 'handles custom generic stores without a unsubscribe function', () => {
724
- let renderer;
725
-
726
663
  const customStore = {
727
664
  name: 'generic-store',
728
665
  instantiate() {
@@ -762,28 +699,265 @@ describe( 'useSelect', () => {
762
699
  []
763
700
  );
764
701
 
765
- return <div data={ state } />;
702
+ return <div role="status">{ state }</div>;
766
703
  } );
767
704
 
705
+ const rendered = render(
706
+ <RegistryProvider value={ registry }>
707
+ <TestComponent />
708
+ </RegistryProvider>
709
+ );
710
+
711
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent( 0 );
712
+
768
713
  act( () => {
769
- renderer = TestRenderer.create(
714
+ registry.dispatch( customStore ).increment();
715
+ } );
716
+
717
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent( 1 );
718
+
719
+ expect( () => rendered.unmount() ).not.toThrow();
720
+ } );
721
+ } );
722
+
723
+ describe( 'async mode', () => {
724
+ function registerCounterStore( reg, initialCount = 0 ) {
725
+ reg.registerStore( 'counter', {
726
+ reducer: ( state = initialCount, action ) => {
727
+ if ( action.type === 'INCREMENT' ) {
728
+ return state + 1;
729
+ }
730
+ return state;
731
+ },
732
+ actions: {
733
+ inc: () => ( { type: 'INCREMENT' } ),
734
+ },
735
+ selectors: {
736
+ get: ( state ) => state,
737
+ },
738
+ } );
739
+ }
740
+
741
+ beforeEach( () => {
742
+ registerCounterStore( registry );
743
+ } );
744
+
745
+ it( 'renders with async mode', async () => {
746
+ const selectSpy = jest.fn( ( select ) =>
747
+ select( 'counter' ).get()
748
+ );
749
+
750
+ const TestComponent = jest.fn( () => {
751
+ const count = useSelect( selectSpy, [] );
752
+ return <div role="status">{ count }</div>;
753
+ } );
754
+
755
+ const rendered = render(
756
+ <AsyncModeProvider value={ true }>
770
757
  <RegistryProvider value={ registry }>
771
758
  <TestComponent />
772
759
  </RegistryProvider>
773
- );
760
+ </AsyncModeProvider>
761
+ );
762
+
763
+ // initial render + missed update catcher in subscribing effect
764
+ expect( selectSpy ).toHaveBeenCalledTimes( 2 );
765
+ expect( TestComponent ).toHaveBeenCalledTimes( 1 );
766
+
767
+ // Ensure expected state was rendered.
768
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent( 0 );
769
+
770
+ act( () => {
771
+ registry.dispatch( 'counter' ).inc();
774
772
  } );
775
773
 
776
- const testInstance = renderer.root;
774
+ // still not called right after increment
775
+ expect( selectSpy ).toHaveBeenCalledTimes( 2 );
776
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent( 0 );
777
777
 
778
- expect( testInstance.findByType( 'div' ).props.data ).toBe( 0 );
778
+ expect( await rendered.findByText( 1 ) ).toBeInTheDocument();
779
779
 
780
+ expect( selectSpy ).toHaveBeenCalledTimes( 3 );
781
+ expect( TestComponent ).toHaveBeenCalledTimes( 2 );
782
+ } );
783
+
784
+ // Tests render queue fixes done in https://github.com/WordPress/gutenberg/pull/19286
785
+ it( 'catches updates while switching from async to sync', () => {
786
+ const selectSpy = jest.fn( ( select ) =>
787
+ select( 'counter' ).get()
788
+ );
789
+
790
+ const TestComponent = jest.fn( () => {
791
+ const count = useSelect( selectSpy, [] );
792
+ return <div role="status">{ count }</div>;
793
+ } );
794
+
795
+ const App = ( { async } ) => (
796
+ <AsyncModeProvider value={ async }>
797
+ <RegistryProvider value={ registry }>
798
+ <TestComponent />
799
+ </RegistryProvider>
800
+ </AsyncModeProvider>
801
+ );
802
+
803
+ const rendered = render( <App async={ true } /> );
804
+
805
+ // Ensure expected state was rendered.
806
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent( 0 );
807
+
808
+ // Schedules an async update of the component.
780
809
  act( () => {
781
- registry.dispatch( customStore ).increment();
810
+ registry.dispatch( 'counter' ).inc();
782
811
  } );
783
812
 
784
- expect( testInstance.findByType( 'div' ).props.data ).toBe( 1 );
813
+ // Ensure the async update wasn't processed yet.
814
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent( 0 );
785
815
 
786
- expect( () => renderer.unmount() ).not.toThrow();
816
+ // Switch from async mode to sync.
817
+ rendered.rerender( <App async={ false } /> );
818
+
819
+ // Ensure the async update was flushed during the rerender.
820
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent( 1 );
821
+
822
+ // initial render + subscription check + flushed store update
823
+ expect( selectSpy ).toHaveBeenCalledTimes( 3 );
824
+ // initial render + rerender with isAsync=false + store state update
825
+ expect( TestComponent ).toHaveBeenCalledTimes( 3 );
826
+ } );
827
+
828
+ it( 'cancels scheduled updates when mapSelect function changes', async () => {
829
+ const selectA = jest.fn(
830
+ ( select ) => 'a:' + select( 'counter' ).get()
831
+ );
832
+ const selectB = jest.fn(
833
+ ( select ) => 'b:' + select( 'counter' ).get()
834
+ );
835
+
836
+ const TestComponent = jest.fn( ( { variant } ) => {
837
+ const count = useSelect( variant === 'a' ? selectA : selectB, [
838
+ variant,
839
+ ] );
840
+ return <div role="status">{ count }</div>;
841
+ } );
842
+
843
+ const App = ( { variant } ) => (
844
+ <AsyncModeProvider value={ true }>
845
+ <RegistryProvider value={ registry }>
846
+ <TestComponent variant={ variant } />
847
+ </RegistryProvider>
848
+ </AsyncModeProvider>
849
+ );
850
+
851
+ const rendered = render( <App variant="a" /> );
852
+
853
+ // Ensure expected state was rendered.
854
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent( 'a:0' );
855
+
856
+ // Schedules an async update of the component.
857
+ act( () => {
858
+ registry.dispatch( 'counter' ).inc();
859
+ } );
860
+
861
+ // Ensure the async update wasn't processed yet.
862
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent( 'a:0' );
863
+
864
+ // Rerender with a prop change that causes dependency change.
865
+ rendered.rerender( <App variant="b" /> );
866
+
867
+ // Ensure the async update was flushed (cancelled) during the rerender.
868
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent( 'b:1' );
869
+
870
+ // Give the async update time to run in case it wasn't cancelled
871
+ await new Promise( setImmediate );
872
+
873
+ expect( selectA ).toHaveBeenCalledTimes( 2 );
874
+ expect( selectB ).toHaveBeenCalledTimes( 2 );
875
+ expect( TestComponent ).toHaveBeenCalledTimes( 2 );
876
+ } );
877
+
878
+ it( 'cancels scheduled updates when unmounting', async () => {
879
+ const selectSpy = jest.fn( ( select ) =>
880
+ select( 'counter' ).get()
881
+ );
882
+
883
+ const TestComponent = jest.fn( () => {
884
+ const count = useSelect( selectSpy, [] );
885
+ return <div role="status">{ count }</div>;
886
+ } );
887
+
888
+ const App = () => (
889
+ <AsyncModeProvider value={ true }>
890
+ <RegistryProvider value={ registry }>
891
+ <TestComponent />
892
+ </RegistryProvider>
893
+ </AsyncModeProvider>
894
+ );
895
+
896
+ const rendered = render( <App /> );
897
+
898
+ // Ensure expected state was rendered.
899
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent( 0 );
900
+
901
+ // Schedules an async update of the component.
902
+ act( () => {
903
+ registry.dispatch( 'counter' ).inc();
904
+ } );
905
+
906
+ // Ensure the async update wasn't processed yet.
907
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent( 0 );
908
+
909
+ // Unmount
910
+ rendered.unmount();
911
+
912
+ // Give the async update time to run in case it wasn't cancelled
913
+ await new Promise( setImmediate );
914
+
915
+ // only the initial render, no state updates
916
+ expect( selectSpy ).toHaveBeenCalledTimes( 2 );
917
+ expect( TestComponent ).toHaveBeenCalledTimes( 1 );
918
+ } );
919
+
920
+ it( 'cancels scheduled updates when registry changes', async () => {
921
+ const registry2 = createRegistry();
922
+ registerCounterStore( registry2, 100 );
923
+
924
+ const selectSpy = jest.fn( ( select ) =>
925
+ select( 'counter' ).get()
926
+ );
927
+
928
+ const TestComponent = jest.fn( () => {
929
+ const count = useSelect( selectSpy, [] );
930
+ return <div role="status">{ count }</div>;
931
+ } );
932
+
933
+ const App = ( { reg } ) => (
934
+ <AsyncModeProvider value={ true }>
935
+ <RegistryProvider value={ reg }>
936
+ <TestComponent />
937
+ </RegistryProvider>
938
+ </AsyncModeProvider>
939
+ );
940
+
941
+ const rendered = render( <App reg={ registry } /> );
942
+
943
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent( 0 );
944
+
945
+ act( () => {
946
+ registry.dispatch( 'counter' ).inc();
947
+ } );
948
+
949
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent( 0 );
950
+
951
+ rendered.rerender( <App reg={ registry2 } /> );
952
+
953
+ expect( rendered.getByRole( 'status' ) ).toHaveTextContent( 100 );
954
+
955
+ // Give the async update time to run in case it wasn't cancelled
956
+ await new Promise( setImmediate );
957
+
958
+ // initial render + registry change rerender, no state updates
959
+ expect( selectSpy ).toHaveBeenCalledTimes( 4 );
960
+ expect( TestComponent ).toHaveBeenCalledTimes( 2 );
787
961
  } );
788
962
  } );
789
963
  } );