@wordpress/core-data 7.48.0 → 7.48.2-next.v.202606191442.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.
Files changed (148) hide show
  1. package/CHANGELOG.md +7 -1
  2. package/build/actions.cjs +1 -7
  3. package/build/actions.cjs.map +3 -3
  4. package/build/awareness/block-lookup.cjs +14 -26
  5. package/build/awareness/block-lookup.cjs.map +2 -2
  6. package/build/awareness/post-editor-awareness.cjs +4 -3
  7. package/build/awareness/post-editor-awareness.cjs.map +2 -2
  8. package/build/entities.cjs +6 -3
  9. package/build/entities.cjs.map +2 -2
  10. package/build/entity-types/helpers.cjs.map +1 -1
  11. package/build/hooks/use-entity-record.cjs +21 -19
  12. package/build/hooks/use-entity-record.cjs.map +3 -3
  13. package/build/hooks/use-entity-records.cjs +22 -20
  14. package/build/hooks/use-entity-records.cjs.map +3 -3
  15. package/build/hooks/use-post-editor-awareness-state.cjs +8 -2
  16. package/build/hooks/use-post-editor-awareness-state.cjs.map +2 -2
  17. package/build/hooks/use-query-select.cjs +2 -20
  18. package/build/hooks/use-query-select.cjs.map +2 -2
  19. package/build/hooks/utils.cjs +53 -0
  20. package/build/hooks/utils.cjs.map +7 -0
  21. package/build/private-actions.cjs +8 -0
  22. package/build/private-actions.cjs.map +2 -2
  23. package/build/private-selectors.cjs.map +2 -2
  24. package/build/reducer.cjs +23 -7
  25. package/build/reducer.cjs.map +2 -2
  26. package/build/resolvers.cjs +13 -8
  27. package/build/resolvers.cjs.map +2 -2
  28. package/build/selectors.cjs +7 -0
  29. package/build/selectors.cjs.map +2 -2
  30. package/build/types.cjs.map +1 -1
  31. package/build/utils/clear-unchanged-edits.cjs +51 -0
  32. package/build/utils/clear-unchanged-edits.cjs.map +7 -0
  33. package/build/utils/crdt-blocks.cjs +12 -2
  34. package/build/utils/crdt-blocks.cjs.map +2 -2
  35. package/build/utils/crdt-user-selections.cjs.map +1 -1
  36. package/build/utils/crdt-utils.cjs.map +1 -1
  37. package/build/utils/crdt.cjs +2 -1
  38. package/build/utils/crdt.cjs.map +2 -2
  39. package/build/utils/index.cjs +6 -0
  40. package/build/utils/index.cjs.map +2 -2
  41. package/build/utils/save-crdt-doc.cjs +75 -0
  42. package/build/utils/save-crdt-doc.cjs.map +7 -0
  43. package/build/utils/set-nested-value.cjs.map +1 -1
  44. package/build-module/actions.mjs +2 -8
  45. package/build-module/actions.mjs.map +2 -2
  46. package/build-module/awareness/block-lookup.mjs +13 -26
  47. package/build-module/awareness/block-lookup.mjs.map +2 -2
  48. package/build-module/awareness/post-editor-awareness.mjs +4 -3
  49. package/build-module/awareness/post-editor-awareness.mjs.map +2 -2
  50. package/build-module/entities.mjs +6 -3
  51. package/build-module/entities.mjs.map +2 -2
  52. package/build-module/hooks/use-entity-record.mjs +21 -19
  53. package/build-module/hooks/use-entity-record.mjs.map +2 -2
  54. package/build-module/hooks/use-entity-records.mjs +20 -18
  55. package/build-module/hooks/use-entity-records.mjs.map +2 -2
  56. package/build-module/hooks/use-post-editor-awareness-state.mjs +9 -3
  57. package/build-module/hooks/use-post-editor-awareness-state.mjs.map +2 -2
  58. package/build-module/hooks/use-query-select.mjs +2 -20
  59. package/build-module/hooks/use-query-select.mjs.map +2 -2
  60. package/build-module/hooks/utils.mjs +28 -0
  61. package/build-module/hooks/utils.mjs.map +7 -0
  62. package/build-module/private-actions.mjs +7 -0
  63. package/build-module/private-actions.mjs.map +2 -2
  64. package/build-module/private-selectors.mjs.map +2 -2
  65. package/build-module/reducer.mjs +23 -8
  66. package/build-module/reducer.mjs.map +2 -2
  67. package/build-module/resolvers.mjs +15 -9
  68. package/build-module/resolvers.mjs.map +2 -2
  69. package/build-module/selectors.mjs +7 -0
  70. package/build-module/selectors.mjs.map +2 -2
  71. package/build-module/utils/clear-unchanged-edits.mjs +20 -0
  72. package/build-module/utils/clear-unchanged-edits.mjs.map +7 -0
  73. package/build-module/utils/crdt-blocks.mjs +12 -2
  74. package/build-module/utils/crdt-blocks.mjs.map +2 -2
  75. package/build-module/utils/crdt-user-selections.mjs.map +1 -1
  76. package/build-module/utils/crdt-utils.mjs.map +1 -1
  77. package/build-module/utils/crdt.mjs +2 -1
  78. package/build-module/utils/crdt.mjs.map +2 -2
  79. package/build-module/utils/index.mjs +24 -20
  80. package/build-module/utils/index.mjs.map +2 -2
  81. package/build-module/utils/save-crdt-doc.mjs +40 -0
  82. package/build-module/utils/save-crdt-doc.mjs.map +7 -0
  83. package/build-module/utils/set-nested-value.mjs.map +1 -1
  84. package/build-types/actions.d.ts.map +1 -1
  85. package/build-types/awareness/block-lookup.d.ts +27 -7
  86. package/build-types/awareness/block-lookup.d.ts.map +1 -1
  87. package/build-types/awareness/post-editor-awareness.d.ts +3 -1
  88. package/build-types/awareness/post-editor-awareness.d.ts.map +1 -1
  89. package/build-types/entities.d.ts.map +1 -1
  90. package/build-types/hooks/use-entity-record.d.ts +4 -0
  91. package/build-types/hooks/use-entity-record.d.ts.map +1 -1
  92. package/build-types/hooks/use-entity-records.d.ts +5 -1
  93. package/build-types/hooks/use-entity-records.d.ts.map +1 -1
  94. package/build-types/hooks/use-post-editor-awareness-state.d.ts.map +1 -1
  95. package/build-types/hooks/utils.d.ts +22 -0
  96. package/build-types/hooks/utils.d.ts.map +1 -0
  97. package/build-types/index.d.ts +8 -8
  98. package/build-types/private-actions.d.ts +15 -0
  99. package/build-types/private-actions.d.ts.map +1 -1
  100. package/build-types/private-selectors.d.ts +0 -12
  101. package/build-types/private-selectors.d.ts.map +1 -1
  102. package/build-types/reducer.d.ts +15 -0
  103. package/build-types/reducer.d.ts.map +1 -1
  104. package/build-types/resolvers.d.ts.map +1 -1
  105. package/build-types/selectors.d.ts +12 -8
  106. package/build-types/selectors.d.ts.map +1 -1
  107. package/build-types/utils/clear-unchanged-edits.d.ts +12 -0
  108. package/build-types/utils/clear-unchanged-edits.d.ts.map +1 -0
  109. package/build-types/utils/crdt-blocks.d.ts +5 -1
  110. package/build-types/utils/crdt-blocks.d.ts.map +1 -1
  111. package/build-types/utils/crdt.d.ts.map +1 -1
  112. package/build-types/utils/index.d.ts +2 -0
  113. package/build-types/utils/index.d.ts.map +1 -1
  114. package/build-types/utils/save-crdt-doc.d.ts +8 -0
  115. package/build-types/utils/save-crdt-doc.d.ts.map +1 -0
  116. package/package.json +27 -20
  117. package/src/actions.js +2 -10
  118. package/src/awareness/block-lookup.ts +21 -62
  119. package/src/awareness/post-editor-awareness.ts +8 -3
  120. package/src/awareness/test/block-lookup.ts +98 -94
  121. package/src/awareness/test/post-editor-awareness.ts +177 -180
  122. package/src/entities.js +14 -3
  123. package/src/hooks/test/use-entity-record.js +5 -1
  124. package/src/hooks/test/use-post-editor-awareness-state.ts +10 -2
  125. package/src/hooks/use-entity-record.ts +26 -19
  126. package/src/hooks/use-entity-records.ts +26 -18
  127. package/src/hooks/use-post-editor-awareness-state.ts +20 -7
  128. package/src/hooks/use-query-select.ts +2 -23
  129. package/src/hooks/utils.ts +40 -0
  130. package/src/private-actions.js +18 -0
  131. package/src/private-selectors.ts +0 -12
  132. package/src/reducer.js +30 -9
  133. package/src/resolvers.js +20 -13
  134. package/src/selectors.ts +11 -0
  135. package/src/test/entities.js +51 -0
  136. package/src/test/private-selectors.js +66 -0
  137. package/src/test/reducer.js +44 -0
  138. package/src/test/resolvers.js +121 -113
  139. package/src/test/selectors.js +48 -0
  140. package/src/utils/clear-unchanged-edits.ts +34 -0
  141. package/src/utils/crdt-blocks.ts +27 -22
  142. package/src/utils/crdt.ts +2 -1
  143. package/src/utils/index.js +2 -0
  144. package/src/utils/save-crdt-doc.js +64 -0
  145. package/src/utils/test/clear-unchanged-edits.js +42 -0
  146. package/src/utils/test/crdt-blocks.ts +57 -2
  147. package/src/utils/test/rtc-rich-text-cursor-scope.test.js +2 -2
  148. package/src/utils/test/save-crdt-doc.js +185 -0
@@ -2,7 +2,7 @@
2
2
  * External dependencies
3
3
  */
4
4
  import { Y } from '@wordpress/sync';
5
- import { select } from '@wordpress/data';
5
+ import { renderHook } from '@testing-library/react';
6
6
 
7
7
  /**
8
8
  * Internal dependencies
@@ -11,23 +11,45 @@ import {
11
11
  getBlockPathInYdoc,
12
12
  getContainingBlockYMap,
13
13
  resolveBlockClientIdByPath,
14
+ usePostContentBlocks,
14
15
  } from '../block-lookup';
15
16
 
16
- // Mock WordPress dependencies
17
+ import type { EditorStoreBlock } from '../block-lookup';
18
+
19
+ type MockBlock = EditorStoreBlock & {
20
+ name: string;
21
+ innerBlocks: MockBlock[];
22
+ };
23
+
24
+ let mockGetClientIdsTree: jest.Mock;
25
+
26
+ function mockFlattenBlocks( blocks: MockBlock[] ): MockBlock[] {
27
+ return blocks.flatMap( ( b ) => [
28
+ b,
29
+ ...mockFlattenBlocks( b.innerBlocks ),
30
+ ] );
31
+ }
32
+
33
+ jest.mock( '../../lock-unlock', () => ( {
34
+ unlock: ( obj: any ) => obj,
35
+ } ) );
36
+
17
37
  jest.mock( '@wordpress/data', () => ( {
18
- select: jest.fn(),
38
+ useSelect: ( selector: Function ) =>
39
+ selector( () => ( {
40
+ getClientIdsTree: ( ...args: any[] ) =>
41
+ mockGetClientIdsTree( ...args ),
42
+ getBlocksByName: ( blockName: string ) =>
43
+ mockFlattenBlocks( mockGetClientIdsTree( '' ) )
44
+ .filter( ( b ) => b.name === blockName )
45
+ .map( ( b ) => b.clientId ),
46
+ } ) ),
19
47
  } ) );
20
48
 
21
49
  jest.mock( '@wordpress/block-editor', () => ( {
22
50
  store: 'core/block-editor',
23
51
  } ) );
24
52
 
25
- type MockBlock = {
26
- clientId: string;
27
- name: string;
28
- innerBlocks: MockBlock[];
29
- };
30
-
31
53
  /**
32
54
  * Create a Y.Map block with a clientId and empty innerBlocks, matching the
33
55
  * shape used by the Yjs block tree.
@@ -127,40 +149,6 @@ function createNestedYDoc( {
127
149
  return { ydoc, rootBlocks, innerBlocksArray };
128
150
  }
129
151
 
130
- /**
131
- * Mock the block-editor store's `getBlocks` selector.
132
- *
133
- * When called without an argument (or undefined), returns `rootBlocks`.
134
- * When called with a clientId, looks up the block by clientId and returns
135
- * its innerBlocks. This mimics how `getBlocks( clientId )` works in the
136
- * real store for controlled inner blocks.
137
- * @param rootBlocks
138
- */
139
- function mockBlockEditorStore( rootBlocks: MockBlock[] ) {
140
- const allBlocks = new Map< string, MockBlock >();
141
-
142
- function indexBlocks( blocks: MockBlock[] ) {
143
- for ( const block of blocks ) {
144
- allBlocks.set( block.clientId, block );
145
- if ( block.innerBlocks?.length ) {
146
- indexBlocks( block.innerBlocks );
147
- }
148
- }
149
- }
150
- indexBlocks( rootBlocks );
151
-
152
- const getBlocks = jest.fn( ( rootClientId?: string ) => {
153
- if ( rootClientId === undefined ) {
154
- return rootBlocks;
155
- }
156
- const block = allBlocks.get( rootClientId );
157
- return block ? block.innerBlocks : [];
158
- } );
159
-
160
- ( select as jest.Mock ).mockReturnValue( { getBlocks } );
161
- return { getBlocks };
162
- }
163
-
164
152
  describe( 'getBlockPathInYdoc', () => {
165
153
  it( 'should return path [0] for the first root block', () => {
166
154
  const { blocks } = createFlatYDoc( 3 );
@@ -313,35 +301,30 @@ describe( 'getContainingBlockYMap', () => {
313
301
  } );
314
302
 
315
303
  describe( 'resolveBlockClientIdByPath', () => {
316
- afterEach( () => {
317
- jest.restoreAllMocks();
318
- } );
319
-
320
304
  it( 'should return null for an empty path', () => {
321
- mockBlockEditorStore( [] );
322
- expect( resolveBlockClientIdByPath( [] ) ).toBeNull();
305
+ expect( resolveBlockClientIdByPath( [], [] ) ).toBeNull();
323
306
  } );
324
307
 
325
308
  it( 'should resolve a root block by single-element path', () => {
326
- mockBlockEditorStore( [
309
+ const blocks: MockBlock[] = [
327
310
  { clientId: 'a', name: 'core/paragraph', innerBlocks: [] },
328
311
  { clientId: 'b', name: 'core/heading', innerBlocks: [] },
329
- ] );
312
+ ];
330
313
 
331
- expect( resolveBlockClientIdByPath( [ 0 ] ) ).toBe( 'a' );
332
- expect( resolveBlockClientIdByPath( [ 1 ] ) ).toBe( 'b' );
314
+ expect( resolveBlockClientIdByPath( [ 0 ], blocks ) ).toBe( 'a' );
315
+ expect( resolveBlockClientIdByPath( [ 1 ], blocks ) ).toBe( 'b' );
333
316
  } );
334
317
 
335
318
  it( 'should return null for an out-of-bounds index', () => {
336
- mockBlockEditorStore( [
319
+ const blocks: MockBlock[] = [
337
320
  { clientId: 'a', name: 'core/paragraph', innerBlocks: [] },
338
- ] );
321
+ ];
339
322
 
340
- expect( resolveBlockClientIdByPath( [ 5 ] ) ).toBeNull();
323
+ expect( resolveBlockClientIdByPath( [ 5 ], blocks ) ).toBeNull();
341
324
  } );
342
325
 
343
326
  it( 'should resolve a nested inner block', () => {
344
- mockBlockEditorStore( [
327
+ const blocks: MockBlock[] = [
345
328
  {
346
329
  clientId: 'parent',
347
330
  name: 'core/group',
@@ -358,13 +341,15 @@ describe( 'resolveBlockClientIdByPath', () => {
358
341
  },
359
342
  ],
360
343
  },
361
- ] );
344
+ ];
362
345
 
363
- expect( resolveBlockClientIdByPath( [ 0, 1 ] ) ).toBe( 'child-1' );
346
+ expect( resolveBlockClientIdByPath( [ 0, 1 ], blocks ) ).toBe(
347
+ 'child-1'
348
+ );
364
349
  } );
365
350
 
366
351
  it( 'should return null when inner path index is out of bounds', () => {
367
- mockBlockEditorStore( [
352
+ const blocks: MockBlock[] = [
368
353
  {
369
354
  clientId: 'parent',
370
355
  name: 'core/group',
@@ -376,12 +361,12 @@ describe( 'resolveBlockClientIdByPath', () => {
376
361
  },
377
362
  ],
378
363
  },
379
- ] );
364
+ ];
380
365
 
381
- expect( resolveBlockClientIdByPath( [ 0, 5 ] ) ).toBeNull();
366
+ expect( resolveBlockClientIdByPath( [ 0, 5 ], blocks ) ).toBeNull();
382
367
  } );
383
368
 
384
- describe( 'template mode (getPostContentBlocks behavior)', () => {
369
+ describe( 'template mode (usePostContentBlocks behavior)', () => {
385
370
  it( 'should navigate through core/post-content in template mode', () => {
386
371
  const postContentInnerBlocks: MockBlock[] = [
387
372
  {
@@ -398,7 +383,7 @@ describe( 'resolveBlockClientIdByPath', () => {
398
383
 
399
384
  // Template structure: header → post-content → footer.
400
385
  // post-content's innerBlocks are empty in the tree (controlled
401
- // inner blocks), so getBlocks( postContentClientId ) is used.
386
+ // inner blocks), so getClientIdsTree( postContentClientId ) is used.
402
387
  const templateBlocks: MockBlock[] = [
403
388
  {
404
389
  clientId: 'header',
@@ -417,13 +402,11 @@ describe( 'resolveBlockClientIdByPath', () => {
417
402
  },
418
403
  ];
419
404
 
420
- const { getBlocks } = mockBlockEditorStore( templateBlocks );
421
-
422
- // Override getBlocks to return post content blocks when called
423
- // with the post-content clientId (mimicking controlled inner
424
- // blocks behavior in useBlockSync).
425
- getBlocks.mockImplementation( ( rootClientId?: string ) => {
426
- if ( rootClientId === undefined ) {
405
+ // Override getClientIdsTree to return post content blocks when
406
+ // called with the post-content clientId (mimicking controlled
407
+ // inner blocks behavior in useBlockSync).
408
+ mockGetClientIdsTree = jest.fn( ( rootClientId: string = '' ) => {
409
+ if ( rootClientId === '' ) {
427
410
  return templateBlocks;
428
411
  }
429
412
  if ( rootClientId === 'post-content' ) {
@@ -432,13 +415,18 @@ describe( 'resolveBlockClientIdByPath', () => {
432
415
  return [];
433
416
  } );
434
417
 
435
- // Path [0] should resolve to the first post content block,
436
- // not the first template block.
437
- expect( resolveBlockClientIdByPath( [ 0 ] ) ).toBe( 'post-para-0' );
438
- expect( resolveBlockClientIdByPath( [ 1 ] ) ).toBe( 'post-para-1' );
418
+ // The returned blocks should be post content blocks, not the template blocks.
419
+ const blocks = renderHook( () => usePostContentBlocks() ).result
420
+ .current;
421
+ expect( resolveBlockClientIdByPath( [ 0 ], blocks ) ).toBe(
422
+ 'post-para-0'
423
+ );
424
+ expect( resolveBlockClientIdByPath( [ 1 ], blocks ) ).toBe(
425
+ 'post-para-1'
426
+ );
439
427
  } );
440
428
 
441
- it( 'should call getBlocks with post-content clientId', () => {
429
+ it( 'should call getClientIdsTree with post-content clientId', () => {
442
430
  const templateBlocks: MockBlock[] = [
443
431
  {
444
432
  clientId: 'header',
@@ -452,9 +440,8 @@ describe( 'resolveBlockClientIdByPath', () => {
452
440
  },
453
441
  ];
454
442
 
455
- const { getBlocks } = mockBlockEditorStore( templateBlocks );
456
- getBlocks.mockImplementation( ( rootClientId?: string ) => {
457
- if ( rootClientId === undefined ) {
443
+ mockGetClientIdsTree = jest.fn( ( rootClientId: string = '' ) => {
444
+ if ( rootClientId === '' ) {
458
445
  return templateBlocks;
459
446
  }
460
447
  if ( rootClientId === 'pc' ) {
@@ -469,10 +456,10 @@ describe( 'resolveBlockClientIdByPath', () => {
469
456
  return [];
470
457
  } );
471
458
 
472
- resolveBlockClientIdByPath( [ 0 ] );
459
+ renderHook( () => usePostContentBlocks() );
473
460
 
474
- // Verify getBlocks was called with the post-content clientId.
475
- expect( getBlocks ).toHaveBeenCalledWith( 'pc' );
461
+ // Verify getClientIdsTree was called with the post-content clientId.
462
+ expect( mockGetClientIdsTree ).toHaveBeenCalledWith( 'pc' );
476
463
  } );
477
464
 
478
465
  it( 'should find core/post-content nested inside template parts', () => {
@@ -500,9 +487,8 @@ describe( 'resolveBlockClientIdByPath', () => {
500
487
  },
501
488
  ];
502
489
 
503
- const { getBlocks } = mockBlockEditorStore( templateBlocks );
504
- getBlocks.mockImplementation( ( rootClientId?: string ) => {
505
- if ( rootClientId === undefined ) {
490
+ mockGetClientIdsTree = jest.fn( ( rootClientId: string = '' ) => {
491
+ if ( rootClientId === '' ) {
506
492
  return templateBlocks;
507
493
  }
508
494
  if ( rootClientId === 'nested-pc' ) {
@@ -511,12 +497,16 @@ describe( 'resolveBlockClientIdByPath', () => {
511
497
  return [];
512
498
  } );
513
499
 
514
- expect( resolveBlockClientIdByPath( [ 0 ] ) ).toBe( 'deep-para' );
500
+ const blocks = renderHook( () => usePostContentBlocks() ).result
501
+ .current;
502
+ expect( resolveBlockClientIdByPath( [ 0 ], blocks ) ).toBe(
503
+ 'deep-para'
504
+ );
515
505
  } );
516
506
 
517
507
  it( 'should use root blocks directly when no core/post-content exists', () => {
518
508
  // No template mode — plain post editing.
519
- const blocks: MockBlock[] = [
509
+ const postContentBlocks: MockBlock[] = [
520
510
  {
521
511
  clientId: 'para-0',
522
512
  name: 'core/paragraph',
@@ -529,10 +519,22 @@ describe( 'resolveBlockClientIdByPath', () => {
529
519
  },
530
520
  ];
531
521
 
532
- mockBlockEditorStore( blocks );
522
+ mockGetClientIdsTree = jest.fn( ( rootClientId: string = '' ) => {
523
+ if ( rootClientId === '' ) {
524
+ return postContentBlocks;
525
+ }
526
+ return [];
527
+ } );
533
528
 
534
- expect( resolveBlockClientIdByPath( [ 0 ] ) ).toBe( 'para-0' );
535
- expect( resolveBlockClientIdByPath( [ 1 ] ) ).toBe( 'para-1' );
529
+ const blocks = renderHook( () => usePostContentBlocks() ).result
530
+ .current;
531
+
532
+ expect( resolveBlockClientIdByPath( [ 0 ], blocks ) ).toBe(
533
+ 'para-0'
534
+ );
535
+ expect( resolveBlockClientIdByPath( [ 1 ], blocks ) ).toBe(
536
+ 'para-1'
537
+ );
536
538
  } );
537
539
 
538
540
  it( 'should return null for invalid path in template mode', () => {
@@ -549,9 +551,8 @@ describe( 'resolveBlockClientIdByPath', () => {
549
551
  },
550
552
  ];
551
553
 
552
- const { getBlocks } = mockBlockEditorStore( templateBlocks );
553
- getBlocks.mockImplementation( ( rootClientId?: string ) => {
554
- if ( rootClientId === undefined ) {
554
+ mockGetClientIdsTree = jest.fn( ( rootClientId: string = '' ) => {
555
+ if ( rootClientId === '' ) {
555
556
  return templateBlocks;
556
557
  }
557
558
  if ( rootClientId === 'pc' ) {
@@ -567,8 +568,11 @@ describe( 'resolveBlockClientIdByPath', () => {
567
568
  return [];
568
569
  } );
569
570
 
571
+ const blocks = renderHook( () => usePostContentBlocks() ).result
572
+ .current;
573
+
570
574
  // Index 1 is out of bounds for the post content blocks.
571
- expect( resolveBlockClientIdByPath( [ 1 ] ) ).toBeNull();
575
+ expect( resolveBlockClientIdByPath( [ 1 ], blocks ) ).toBeNull();
572
576
  } );
573
577
  } );
574
578
  } );