@wordpress/core-data 7.39.1-next.v.202602091733.0 → 7.40.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 (124) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/README.md +41 -0
  3. package/build/actions.cjs +52 -0
  4. package/build/actions.cjs.map +2 -2
  5. package/build/awareness/base-awareness.cjs +1 -8
  6. package/build/awareness/base-awareness.cjs.map +2 -2
  7. package/build/awareness/types.cjs.map +1 -1
  8. package/build/awareness/utils.cjs +8 -51
  9. package/build/awareness/utils.cjs.map +2 -2
  10. package/build/entities.cjs +7 -1
  11. package/build/entities.cjs.map +2 -2
  12. package/build/hooks/use-entity-block-editor.cjs +13 -19
  13. package/build/hooks/use-entity-block-editor.cjs.map +2 -2
  14. package/build/index.cjs +6 -1
  15. package/build/index.cjs.map +2 -2
  16. package/build/private-actions.cjs +8 -0
  17. package/build/private-actions.cjs.map +2 -2
  18. package/build/private-apis.cjs +2 -1
  19. package/build/private-apis.cjs.map +2 -2
  20. package/build/private-selectors.cjs +5 -0
  21. package/build/private-selectors.cjs.map +2 -2
  22. package/build/reducer.cjs +31 -1
  23. package/build/reducer.cjs.map +2 -2
  24. package/build/resolvers.cjs +26 -1
  25. package/build/resolvers.cjs.map +2 -2
  26. package/build/selectors.cjs +15 -0
  27. package/build/selectors.cjs.map +2 -2
  28. package/build/utils/crdt-blocks.cjs +5 -3
  29. package/build/utils/crdt-blocks.cjs.map +2 -2
  30. package/build/utils/crdt.cjs +23 -19
  31. package/build/utils/crdt.cjs.map +2 -2
  32. package/build-module/actions.mjs +50 -0
  33. package/build-module/actions.mjs.map +2 -2
  34. package/build-module/awareness/base-awareness.mjs +1 -8
  35. package/build-module/awareness/base-awareness.mjs.map +2 -2
  36. package/build-module/awareness/utils.mjs +8 -51
  37. package/build-module/awareness/utils.mjs.map +2 -2
  38. package/build-module/entities.mjs +7 -1
  39. package/build-module/entities.mjs.map +2 -2
  40. package/build-module/hooks/use-entity-block-editor.mjs +13 -19
  41. package/build-module/hooks/use-entity-block-editor.mjs.map +2 -2
  42. package/build-module/index.mjs +3 -0
  43. package/build-module/index.mjs.map +2 -2
  44. package/build-module/private-actions.mjs +7 -0
  45. package/build-module/private-actions.mjs.map +2 -2
  46. package/build-module/private-apis.mjs +6 -2
  47. package/build-module/private-apis.mjs.map +2 -2
  48. package/build-module/private-selectors.mjs +8 -1
  49. package/build-module/private-selectors.mjs.map +2 -2
  50. package/build-module/reducer.mjs +29 -1
  51. package/build-module/reducer.mjs.map +2 -2
  52. package/build-module/resolvers.mjs +25 -1
  53. package/build-module/resolvers.mjs.map +2 -2
  54. package/build-module/selectors.mjs +14 -0
  55. package/build-module/selectors.mjs.map +2 -2
  56. package/build-module/utils/crdt-blocks.mjs +3 -2
  57. package/build-module/utils/crdt-blocks.mjs.map +2 -2
  58. package/build-module/utils/crdt.mjs +25 -20
  59. package/build-module/utils/crdt.mjs.map +2 -2
  60. package/build-types/actions.d.ts +12 -0
  61. package/build-types/actions.d.ts.map +1 -1
  62. package/build-types/awareness/base-awareness.d.ts.map +1 -1
  63. package/build-types/awareness/test/awareness-state.d.ts +2 -0
  64. package/build-types/awareness/test/awareness-state.d.ts.map +1 -0
  65. package/build-types/awareness/test/base-awareness.d.ts +2 -0
  66. package/build-types/awareness/test/base-awareness.d.ts.map +1 -0
  67. package/build-types/awareness/test/post-editor-awareness.d.ts +2 -0
  68. package/build-types/awareness/test/post-editor-awareness.d.ts.map +1 -0
  69. package/build-types/awareness/test/typed-awareness.d.ts +2 -0
  70. package/build-types/awareness/test/typed-awareness.d.ts.map +1 -0
  71. package/build-types/awareness/test/utils.d.ts +2 -0
  72. package/build-types/awareness/test/utils.d.ts.map +1 -0
  73. package/build-types/awareness/types.d.ts +0 -1
  74. package/build-types/awareness/types.d.ts.map +1 -1
  75. package/build-types/awareness/utils.d.ts +2 -3
  76. package/build-types/awareness/utils.d.ts.map +1 -1
  77. package/build-types/entities.d.ts.map +1 -1
  78. package/build-types/hooks/test/use-post-editor-awareness-state.d.ts +2 -0
  79. package/build-types/hooks/test/use-post-editor-awareness-state.d.ts.map +1 -0
  80. package/build-types/hooks/use-entity-block-editor.d.ts.map +1 -1
  81. package/build-types/index.d.ts +5 -0
  82. package/build-types/index.d.ts.map +1 -1
  83. package/build-types/private-actions.d.ts +8 -0
  84. package/build-types/private-actions.d.ts.map +1 -1
  85. package/build-types/private-apis.d.ts.map +1 -1
  86. package/build-types/private-selectors.d.ts +8 -1
  87. package/build-types/private-selectors.d.ts.map +1 -1
  88. package/build-types/reducer.d.ts +23 -0
  89. package/build-types/reducer.d.ts.map +1 -1
  90. package/build-types/resolvers.d.ts +3 -0
  91. package/build-types/resolvers.d.ts.map +1 -1
  92. package/build-types/selectors.d.ts +17 -0
  93. package/build-types/selectors.d.ts.map +1 -1
  94. package/build-types/utils/crdt-blocks.d.ts +9 -0
  95. package/build-types/utils/crdt-blocks.d.ts.map +1 -1
  96. package/build-types/utils/crdt.d.ts +6 -4
  97. package/build-types/utils/crdt.d.ts.map +1 -1
  98. package/package.json +18 -18
  99. package/src/actions.js +78 -0
  100. package/src/awareness/base-awareness.ts +1 -14
  101. package/src/awareness/test/awareness-state.ts +417 -0
  102. package/src/awareness/test/base-awareness.ts +321 -0
  103. package/src/awareness/test/post-editor-awareness.ts +561 -0
  104. package/src/awareness/test/typed-awareness.ts +148 -0
  105. package/src/awareness/test/utils.ts +305 -0
  106. package/src/awareness/types.ts +0 -1
  107. package/src/awareness/utils.ts +8 -82
  108. package/src/entities.js +7 -1
  109. package/src/hooks/test/use-post-editor-awareness-state.ts +477 -0
  110. package/src/hooks/use-entity-block-editor.js +15 -21
  111. package/src/index.js +7 -0
  112. package/src/private-actions.js +14 -0
  113. package/src/private-apis.js +5 -1
  114. package/src/private-selectors.ts +16 -1
  115. package/src/reducer.js +45 -0
  116. package/src/resolvers.js +31 -2
  117. package/src/selectors.ts +41 -0
  118. package/src/test/actions.js +79 -0
  119. package/src/test/entity-provider.js +74 -0
  120. package/src/test/resolvers.js +2 -0
  121. package/src/test/store.js +30 -0
  122. package/src/utils/crdt-blocks.ts +2 -2
  123. package/src/utils/crdt.ts +44 -29
  124. package/src/utils/test/crdt.ts +212 -7
@@ -0,0 +1,321 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { Y } from '@wordpress/sync';
5
+ import { resolveSelect } from '@wordpress/data';
6
+
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+ import {
11
+ BaseAwarenessState,
12
+ BaseAwareness,
13
+ baseEqualityFieldChecks,
14
+ } from '../base-awareness';
15
+ import { areCollaboratorInfosEqual } from '../utils';
16
+ import type { BaseState, CollaboratorInfo } from '../types';
17
+
18
+ // Mock WordPress data
19
+ jest.mock( '@wordpress/data', () => ( {
20
+ resolveSelect: jest.fn(),
21
+ } ) );
22
+
23
+ // Mock window.navigator.userAgent
24
+ const mockUserAgent = ( userAgent: string ) => {
25
+ Object.defineProperty( window.navigator, 'userAgent', {
26
+ value: userAgent,
27
+ configurable: true,
28
+ } );
29
+ };
30
+
31
+ const mockAvatarUrls = {
32
+ '24': 'https://example.com/avatar-24.png',
33
+ '48': 'https://example.com/avatar-48.png',
34
+ '96': 'https://example.com/avatar-96.png',
35
+ };
36
+
37
+ const createMockCollaboratorInfo = () => ( {
38
+ id: 1,
39
+ name: 'Test User',
40
+ slug: 'test-user',
41
+ avatar_urls: mockAvatarUrls,
42
+ } );
43
+
44
+ describe( 'BaseAwareness', () => {
45
+ let doc: Y.Doc;
46
+
47
+ beforeEach( () => {
48
+ jest.useFakeTimers();
49
+ doc = new Y.Doc();
50
+
51
+ // Reset to Chrome
52
+ mockUserAgent(
53
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
54
+ );
55
+
56
+ // Mock Date.now for consistent timestamps
57
+ jest.spyOn( Date, 'now' ).mockReturnValue( 1704067200000 );
58
+
59
+ // Mock resolveSelect to return getCurrentUser
60
+ ( resolveSelect as jest.Mock ).mockReturnValue( {
61
+ getCurrentUser: jest
62
+ .fn()
63
+ .mockResolvedValue( createMockCollaboratorInfo() ),
64
+ } );
65
+ } );
66
+
67
+ afterEach( () => {
68
+ jest.useRealTimers();
69
+ jest.restoreAllMocks();
70
+ doc.destroy();
71
+ } );
72
+
73
+ describe( 'construction', () => {
74
+ test( 'should create instance with Y.Doc', () => {
75
+ const awareness = new BaseAwareness( doc );
76
+ expect( awareness ).toBeInstanceOf( BaseAwareness );
77
+ } );
78
+
79
+ test( 'should have correct clientID from doc', () => {
80
+ const awareness = new BaseAwareness( doc );
81
+ expect( awareness.clientID ).toBe( doc.clientID );
82
+ } );
83
+ } );
84
+
85
+ describe( 'setUp', () => {
86
+ test( 'should be idempotent', () => {
87
+ const awareness = new BaseAwareness( doc );
88
+
89
+ awareness.setUp();
90
+ awareness.setUp();
91
+
92
+ // Should not throw and should only call getCurrentUser once
93
+ expect( resolveSelect ).toHaveBeenCalledTimes( 1 );
94
+ } );
95
+
96
+ test( 'should fetch current user and set userInfo', async () => {
97
+ const awareness = new BaseAwareness( doc );
98
+
99
+ awareness.setUp();
100
+
101
+ // Wait for async operations
102
+ await Promise.resolve();
103
+
104
+ const collaboratorInfo =
105
+ awareness.getLocalStateField( 'collaboratorInfo' );
106
+ expect( collaboratorInfo ).toBeDefined();
107
+ expect( collaboratorInfo?.id ).toBe( 1 );
108
+ expect( collaboratorInfo?.name ).toBe( 'Test User' );
109
+ expect( collaboratorInfo?.browserType ).toBe( 'Chrome' );
110
+ } );
111
+ } );
112
+
113
+ describe( 'baseEqualityFieldChecks', () => {
114
+ test( 'should have userInfo equality check', () => {
115
+ expect( baseEqualityFieldChecks.collaboratorInfo ).toBe(
116
+ areCollaboratorInfosEqual
117
+ );
118
+ } );
119
+ } );
120
+ } );
121
+
122
+ describe( 'BaseAwarenessState', () => {
123
+ /**
124
+ * Concrete implementation for testing the abstract class
125
+ */
126
+ class TestBaseAwarenessState extends BaseAwarenessState< BaseState > {
127
+ protected equalityFieldChecks = {
128
+ collaboratorInfo: areCollaboratorInfosEqual,
129
+ };
130
+ }
131
+
132
+ let doc: Y.Doc;
133
+
134
+ beforeEach( () => {
135
+ jest.useFakeTimers();
136
+ doc = new Y.Doc();
137
+
138
+ mockUserAgent(
139
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
140
+ );
141
+
142
+ jest.spyOn( Date, 'now' ).mockReturnValue( 1704067200000 );
143
+
144
+ ( resolveSelect as jest.Mock ).mockReturnValue( {
145
+ getCurrentUser: jest
146
+ .fn()
147
+ .mockResolvedValue( createMockCollaboratorInfo() ),
148
+ } );
149
+ } );
150
+
151
+ afterEach( () => {
152
+ jest.useRealTimers();
153
+ jest.restoreAllMocks();
154
+ doc.destroy();
155
+ } );
156
+
157
+ describe( 'onSetUp', () => {
158
+ test( 'should call setCurrentUserInfo', async () => {
159
+ const awareness = new TestBaseAwarenessState( doc );
160
+
161
+ awareness.setUp();
162
+ await Promise.resolve();
163
+
164
+ expect( resolveSelect ).toHaveBeenCalledWith( 'core' );
165
+ } );
166
+
167
+ test( 'should set collaboratorInfo for other users', async () => {
168
+ // Set up another user state first
169
+ const doc2 = new Y.Doc();
170
+ const awareness = new TestBaseAwarenessState( doc );
171
+
172
+ // Manually add another user's state
173
+ awareness.setLocalStateField( 'collaboratorInfo', {
174
+ id: 2,
175
+ name: 'Other User',
176
+ slug: 'other-user',
177
+ avatar_urls: mockAvatarUrls,
178
+ browserType: 'Firefox',
179
+ enteredAt: 1704067200000,
180
+ } );
181
+
182
+ awareness.setUp();
183
+ await Promise.resolve();
184
+
185
+ const collaboratorInfo =
186
+ awareness.getLocalStateField( 'collaboratorInfo' );
187
+ expect( collaboratorInfo ).toBeDefined();
188
+
189
+ doc2.destroy();
190
+ } );
191
+ } );
192
+
193
+ describe( 'getLocalStateField', () => {
194
+ test( 'should return null when field not set', () => {
195
+ const awareness = new TestBaseAwarenessState( doc );
196
+ expect(
197
+ awareness.getLocalStateField( 'collaboratorInfo' )
198
+ ).toBeNull();
199
+ } );
200
+
201
+ test( 'should return collaboratorInfo after setUp', async () => {
202
+ const awareness = new TestBaseAwarenessState( doc );
203
+ awareness.setUp();
204
+ await Promise.resolve();
205
+
206
+ const collaboratorInfo =
207
+ awareness.getLocalStateField( 'collaboratorInfo' );
208
+ expect( collaboratorInfo ).not.toBeNull();
209
+ expect( collaboratorInfo?.name ).toBe( 'Test User' );
210
+ } );
211
+ } );
212
+
213
+ describe( 'setLocalStateField', () => {
214
+ test( 'should set collaboratorInfo field', () => {
215
+ const awareness = new TestBaseAwarenessState( doc );
216
+ const collaboratorInfo: CollaboratorInfo = {
217
+ id: 42,
218
+ name: 'Custom User',
219
+ slug: 'custom-user',
220
+ avatar_urls: mockAvatarUrls,
221
+ browserType: 'Safari',
222
+ enteredAt: 1704067200000,
223
+ };
224
+
225
+ awareness.setLocalStateField(
226
+ 'collaboratorInfo',
227
+ collaboratorInfo
228
+ );
229
+
230
+ expect(
231
+ awareness.getLocalStateField( 'collaboratorInfo' )
232
+ ).toEqual( collaboratorInfo );
233
+ } );
234
+
235
+ test( 'should not update if collaboratorInfo is equal', () => {
236
+ const awareness = new TestBaseAwarenessState( doc );
237
+ const collaboratorInfo: CollaboratorInfo = {
238
+ id: 42,
239
+ name: 'Custom User',
240
+ slug: 'custom-user',
241
+ avatar_urls: mockAvatarUrls,
242
+ browserType: 'Safari',
243
+ enteredAt: 1704067200000,
244
+ };
245
+
246
+ awareness.setLocalStateField(
247
+ 'collaboratorInfo',
248
+ collaboratorInfo
249
+ );
250
+
251
+ // Subscribe to detect updates
252
+ const callback = jest.fn();
253
+ awareness.onStateChange( callback );
254
+
255
+ // Set same collaboratorInfo
256
+ awareness.setLocalStateField( 'collaboratorInfo', {
257
+ ...collaboratorInfo,
258
+ } );
259
+
260
+ // Trigger awareness change event to test if callback is called
261
+ awareness.emit( 'change', [
262
+ {
263
+ added: [],
264
+ updated: [ awareness.clientID ],
265
+ removed: [],
266
+ },
267
+ ] );
268
+
269
+ // Callback should not be called for equal values
270
+ expect( callback ).not.toHaveBeenCalled();
271
+ } );
272
+ } );
273
+
274
+ describe( 'state subscription', () => {
275
+ test( 'should notify subscribers on state change', async () => {
276
+ const awareness = new TestBaseAwarenessState( doc );
277
+ const callback = jest.fn();
278
+
279
+ awareness.onStateChange( callback );
280
+ awareness.setUp();
281
+ await Promise.resolve();
282
+
283
+ // Trigger awareness change event
284
+ awareness.emit( 'change', [
285
+ {
286
+ added: [],
287
+ updated: [ awareness.clientID ],
288
+ removed: [],
289
+ },
290
+ ] );
291
+
292
+ expect( callback ).toHaveBeenCalled();
293
+ } );
294
+
295
+ test( 'should include enhanced state with isMe and isConnected', async () => {
296
+ const awareness = new TestBaseAwarenessState( doc );
297
+ let receivedStates: any[] = [];
298
+
299
+ awareness.onStateChange( ( states ) => {
300
+ receivedStates = states;
301
+ } );
302
+
303
+ awareness.setUp();
304
+ await Promise.resolve();
305
+
306
+ // Trigger awareness change event
307
+ awareness.emit( 'change', [
308
+ {
309
+ added: [],
310
+ updated: [ awareness.clientID ],
311
+ removed: [],
312
+ },
313
+ ] );
314
+
315
+ expect( receivedStates.length ).toBeGreaterThan( 0 );
316
+ const myState = receivedStates.find( ( s ) => s.isMe );
317
+ expect( myState ).toBeDefined();
318
+ expect( myState.isConnected ).toBe( true );
319
+ } );
320
+ } );
321
+ } );