@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.
- package/CHANGELOG.md +2 -0
- package/README.md +41 -0
- package/build/actions.cjs +52 -0
- package/build/actions.cjs.map +2 -2
- package/build/awareness/base-awareness.cjs +1 -8
- package/build/awareness/base-awareness.cjs.map +2 -2
- package/build/awareness/types.cjs.map +1 -1
- package/build/awareness/utils.cjs +8 -51
- package/build/awareness/utils.cjs.map +2 -2
- package/build/entities.cjs +7 -1
- package/build/entities.cjs.map +2 -2
- package/build/hooks/use-entity-block-editor.cjs +13 -19
- package/build/hooks/use-entity-block-editor.cjs.map +2 -2
- package/build/index.cjs +6 -1
- package/build/index.cjs.map +2 -2
- package/build/private-actions.cjs +8 -0
- package/build/private-actions.cjs.map +2 -2
- package/build/private-apis.cjs +2 -1
- package/build/private-apis.cjs.map +2 -2
- package/build/private-selectors.cjs +5 -0
- package/build/private-selectors.cjs.map +2 -2
- package/build/reducer.cjs +31 -1
- package/build/reducer.cjs.map +2 -2
- package/build/resolvers.cjs +26 -1
- package/build/resolvers.cjs.map +2 -2
- package/build/selectors.cjs +15 -0
- package/build/selectors.cjs.map +2 -2
- package/build/utils/crdt-blocks.cjs +5 -3
- package/build/utils/crdt-blocks.cjs.map +2 -2
- package/build/utils/crdt.cjs +23 -19
- package/build/utils/crdt.cjs.map +2 -2
- package/build-module/actions.mjs +50 -0
- package/build-module/actions.mjs.map +2 -2
- package/build-module/awareness/base-awareness.mjs +1 -8
- package/build-module/awareness/base-awareness.mjs.map +2 -2
- package/build-module/awareness/utils.mjs +8 -51
- package/build-module/awareness/utils.mjs.map +2 -2
- package/build-module/entities.mjs +7 -1
- package/build-module/entities.mjs.map +2 -2
- package/build-module/hooks/use-entity-block-editor.mjs +13 -19
- package/build-module/hooks/use-entity-block-editor.mjs.map +2 -2
- package/build-module/index.mjs +3 -0
- package/build-module/index.mjs.map +2 -2
- package/build-module/private-actions.mjs +7 -0
- package/build-module/private-actions.mjs.map +2 -2
- package/build-module/private-apis.mjs +6 -2
- package/build-module/private-apis.mjs.map +2 -2
- package/build-module/private-selectors.mjs +8 -1
- package/build-module/private-selectors.mjs.map +2 -2
- package/build-module/reducer.mjs +29 -1
- package/build-module/reducer.mjs.map +2 -2
- package/build-module/resolvers.mjs +25 -1
- package/build-module/resolvers.mjs.map +2 -2
- package/build-module/selectors.mjs +14 -0
- package/build-module/selectors.mjs.map +2 -2
- package/build-module/utils/crdt-blocks.mjs +3 -2
- package/build-module/utils/crdt-blocks.mjs.map +2 -2
- package/build-module/utils/crdt.mjs +25 -20
- package/build-module/utils/crdt.mjs.map +2 -2
- package/build-types/actions.d.ts +12 -0
- package/build-types/actions.d.ts.map +1 -1
- package/build-types/awareness/base-awareness.d.ts.map +1 -1
- package/build-types/awareness/test/awareness-state.d.ts +2 -0
- package/build-types/awareness/test/awareness-state.d.ts.map +1 -0
- package/build-types/awareness/test/base-awareness.d.ts +2 -0
- package/build-types/awareness/test/base-awareness.d.ts.map +1 -0
- package/build-types/awareness/test/post-editor-awareness.d.ts +2 -0
- package/build-types/awareness/test/post-editor-awareness.d.ts.map +1 -0
- package/build-types/awareness/test/typed-awareness.d.ts +2 -0
- package/build-types/awareness/test/typed-awareness.d.ts.map +1 -0
- package/build-types/awareness/test/utils.d.ts +2 -0
- package/build-types/awareness/test/utils.d.ts.map +1 -0
- package/build-types/awareness/types.d.ts +0 -1
- package/build-types/awareness/types.d.ts.map +1 -1
- package/build-types/awareness/utils.d.ts +2 -3
- package/build-types/awareness/utils.d.ts.map +1 -1
- package/build-types/entities.d.ts.map +1 -1
- package/build-types/hooks/test/use-post-editor-awareness-state.d.ts +2 -0
- package/build-types/hooks/test/use-post-editor-awareness-state.d.ts.map +1 -0
- package/build-types/hooks/use-entity-block-editor.d.ts.map +1 -1
- package/build-types/index.d.ts +5 -0
- package/build-types/index.d.ts.map +1 -1
- package/build-types/private-actions.d.ts +8 -0
- package/build-types/private-actions.d.ts.map +1 -1
- package/build-types/private-apis.d.ts.map +1 -1
- package/build-types/private-selectors.d.ts +8 -1
- package/build-types/private-selectors.d.ts.map +1 -1
- package/build-types/reducer.d.ts +23 -0
- package/build-types/reducer.d.ts.map +1 -1
- package/build-types/resolvers.d.ts +3 -0
- package/build-types/resolvers.d.ts.map +1 -1
- package/build-types/selectors.d.ts +17 -0
- package/build-types/selectors.d.ts.map +1 -1
- package/build-types/utils/crdt-blocks.d.ts +9 -0
- package/build-types/utils/crdt-blocks.d.ts.map +1 -1
- package/build-types/utils/crdt.d.ts +6 -4
- package/build-types/utils/crdt.d.ts.map +1 -1
- package/package.json +18 -18
- package/src/actions.js +78 -0
- package/src/awareness/base-awareness.ts +1 -14
- package/src/awareness/test/awareness-state.ts +417 -0
- package/src/awareness/test/base-awareness.ts +321 -0
- package/src/awareness/test/post-editor-awareness.ts +561 -0
- package/src/awareness/test/typed-awareness.ts +148 -0
- package/src/awareness/test/utils.ts +305 -0
- package/src/awareness/types.ts +0 -1
- package/src/awareness/utils.ts +8 -82
- package/src/entities.js +7 -1
- package/src/hooks/test/use-post-editor-awareness-state.ts +477 -0
- package/src/hooks/use-entity-block-editor.js +15 -21
- package/src/index.js +7 -0
- package/src/private-actions.js +14 -0
- package/src/private-apis.js +5 -1
- package/src/private-selectors.ts +16 -1
- package/src/reducer.js +45 -0
- package/src/resolvers.js +31 -2
- package/src/selectors.ts +41 -0
- package/src/test/actions.js +79 -0
- package/src/test/entity-provider.js +74 -0
- package/src/test/resolvers.js +2 -0
- package/src/test/store.js +30 -0
- package/src/utils/crdt-blocks.ts +2 -2
- package/src/utils/crdt.ts +44 -29
- package/src/utils/test/crdt.ts +212 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wordpress/core-data",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.40.0",
|
|
4
4
|
"description": "Access to and manipulation of core WordPress entities.",
|
|
5
5
|
"author": "The WordPress Contributors",
|
|
6
6
|
"license": "GPL-2.0-or-later",
|
|
@@ -49,22 +49,22 @@
|
|
|
49
49
|
"build-module/index.mjs"
|
|
50
50
|
],
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@wordpress/api-fetch": "^7.
|
|
53
|
-
"@wordpress/block-editor": "^15.
|
|
54
|
-
"@wordpress/blocks": "^15.
|
|
55
|
-
"@wordpress/compose": "^7.
|
|
56
|
-
"@wordpress/data": "^10.
|
|
57
|
-
"@wordpress/deprecated": "^4.
|
|
58
|
-
"@wordpress/element": "^6.
|
|
59
|
-
"@wordpress/html-entities": "^4.
|
|
60
|
-
"@wordpress/i18n": "^6.
|
|
61
|
-
"@wordpress/is-shallow-equal": "^5.
|
|
62
|
-
"@wordpress/private-apis": "^1.
|
|
63
|
-
"@wordpress/rich-text": "^7.
|
|
64
|
-
"@wordpress/sync": "^1.
|
|
65
|
-
"@wordpress/undo-manager": "^1.
|
|
66
|
-
"@wordpress/url": "^4.
|
|
67
|
-
"@wordpress/warning": "^3.
|
|
52
|
+
"@wordpress/api-fetch": "^7.40.0",
|
|
53
|
+
"@wordpress/block-editor": "^15.13.0",
|
|
54
|
+
"@wordpress/blocks": "^15.13.0",
|
|
55
|
+
"@wordpress/compose": "^7.40.0",
|
|
56
|
+
"@wordpress/data": "^10.40.0",
|
|
57
|
+
"@wordpress/deprecated": "^4.40.0",
|
|
58
|
+
"@wordpress/element": "^6.40.0",
|
|
59
|
+
"@wordpress/html-entities": "^4.40.0",
|
|
60
|
+
"@wordpress/i18n": "^6.13.0",
|
|
61
|
+
"@wordpress/is-shallow-equal": "^5.40.0",
|
|
62
|
+
"@wordpress/private-apis": "^1.40.0",
|
|
63
|
+
"@wordpress/rich-text": "^7.40.0",
|
|
64
|
+
"@wordpress/sync": "^1.40.0",
|
|
65
|
+
"@wordpress/undo-manager": "^1.40.0",
|
|
66
|
+
"@wordpress/url": "^4.40.0",
|
|
67
|
+
"@wordpress/warning": "^3.40.0",
|
|
68
68
|
"change-case": "^4.1.2",
|
|
69
69
|
"equivalent-key-map": "^0.2.2",
|
|
70
70
|
"fast-deep-equal": "^3.1.3",
|
|
@@ -84,5 +84,5 @@
|
|
|
84
84
|
"publishConfig": {
|
|
85
85
|
"access": "public"
|
|
86
86
|
},
|
|
87
|
-
"gitHead": "
|
|
87
|
+
"gitHead": "376124aa10dbc2cc0c81c964ec00b99fcfee5382"
|
|
88
88
|
}
|
package/src/actions.js
CHANGED
|
@@ -480,6 +480,55 @@ export const editEntityRecord =
|
|
|
480
480
|
} );
|
|
481
481
|
};
|
|
482
482
|
|
|
483
|
+
/**
|
|
484
|
+
* Action triggered to clear all edits from
|
|
485
|
+
* an entity record.
|
|
486
|
+
*
|
|
487
|
+
* @param {string} kind Kind of the entity.
|
|
488
|
+
* @param {string} name Name of the entity.
|
|
489
|
+
* @param {number|string} recordId Record ID of the entity record.
|
|
490
|
+
*
|
|
491
|
+
* @return {Object} Action object.
|
|
492
|
+
*/
|
|
493
|
+
export const clearEntityRecordEdits =
|
|
494
|
+
( kind, name, recordId ) =>
|
|
495
|
+
( { select, dispatch } ) => {
|
|
496
|
+
const entityConfig = select.getEntityConfig( kind, name );
|
|
497
|
+
logEntityDeprecation( kind, name, 'clearEntityRecordEdits' );
|
|
498
|
+
if ( ! entityConfig ) {
|
|
499
|
+
throw new Error(
|
|
500
|
+
`The entity being edited (${ kind }, ${ name }) does not have a loaded config.`
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const currentEdits = select.getEntityRecordEdits(
|
|
505
|
+
kind,
|
|
506
|
+
name,
|
|
507
|
+
recordId
|
|
508
|
+
);
|
|
509
|
+
if ( ! currentEdits ) {
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Build an edits object with all current edit keys set to undefined
|
|
514
|
+
// so the reducer removes them.
|
|
515
|
+
const clearedEdits = Object.keys( currentEdits ).reduce(
|
|
516
|
+
( acc, key ) => {
|
|
517
|
+
acc[ key ] = undefined;
|
|
518
|
+
return acc;
|
|
519
|
+
},
|
|
520
|
+
{}
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
dispatch( {
|
|
524
|
+
type: 'EDIT_ENTITY_RECORD',
|
|
525
|
+
kind,
|
|
526
|
+
name,
|
|
527
|
+
recordId,
|
|
528
|
+
edits: clearedEdits,
|
|
529
|
+
} );
|
|
530
|
+
};
|
|
531
|
+
|
|
483
532
|
/**
|
|
484
533
|
* Action triggered to undo the last edit to
|
|
485
534
|
* an entity record, if any.
|
|
@@ -1074,3 +1123,32 @@ export const receiveRevisions =
|
|
|
1074
1123
|
invalidateCache,
|
|
1075
1124
|
} );
|
|
1076
1125
|
};
|
|
1126
|
+
|
|
1127
|
+
/**
|
|
1128
|
+
* Returns an action object used to set the sync connection status for an entity or collection.
|
|
1129
|
+
*
|
|
1130
|
+
* @param {string} kind Kind of the entity.
|
|
1131
|
+
* @param {string} name Name of the entity.
|
|
1132
|
+
* @param {number|string|null} key The entity key, or null for collections.
|
|
1133
|
+
* @param {Object|null} status The connection state object or null on unload.
|
|
1134
|
+
*
|
|
1135
|
+
* @return {Object} Action object.
|
|
1136
|
+
*/
|
|
1137
|
+
export function setSyncConnectionStatus( kind, name, key, status ) {
|
|
1138
|
+
if ( ! status ) {
|
|
1139
|
+
return {
|
|
1140
|
+
type: 'CLEAR_SYNC_CONNECTION_STATUS',
|
|
1141
|
+
kind,
|
|
1142
|
+
name,
|
|
1143
|
+
key,
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
return {
|
|
1148
|
+
type: 'SET_SYNC_CONNECTION_STATUS',
|
|
1149
|
+
kind,
|
|
1150
|
+
name,
|
|
1151
|
+
key,
|
|
1152
|
+
status,
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
@@ -23,21 +23,8 @@ export abstract class BaseAwarenessState<
|
|
|
23
23
|
* Set the current collaborator info in the local state.
|
|
24
24
|
*/
|
|
25
25
|
private async setCurrentCollaboratorInfo(): Promise< void > {
|
|
26
|
-
const states = this.getStates();
|
|
27
|
-
const otherCollaboratorColors = Array.from( states.entries() )
|
|
28
|
-
.filter(
|
|
29
|
-
( [ clientId, state ] ) =>
|
|
30
|
-
state.collaboratorInfo && clientId !== this.clientID
|
|
31
|
-
)
|
|
32
|
-
.map( ( [ , state ] ) => state.collaboratorInfo.color )
|
|
33
|
-
.filter( Boolean );
|
|
34
|
-
|
|
35
|
-
// Get current user info and set it in local state.
|
|
36
26
|
const currentUser = await resolveSelect( coreStore ).getCurrentUser();
|
|
37
|
-
const collaboratorInfo = generateCollaboratorInfo(
|
|
38
|
-
currentUser,
|
|
39
|
-
otherCollaboratorColors
|
|
40
|
-
);
|
|
27
|
+
const collaboratorInfo = generateCollaboratorInfo( currentUser );
|
|
41
28
|
this.setLocalStateField( 'collaboratorInfo', collaboratorInfo );
|
|
42
29
|
}
|
|
43
30
|
}
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import {
|
|
5
|
+
describe,
|
|
6
|
+
expect,
|
|
7
|
+
test,
|
|
8
|
+
jest,
|
|
9
|
+
beforeEach,
|
|
10
|
+
afterEach,
|
|
11
|
+
} from '@jest/globals';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* WordPress dependencies
|
|
15
|
+
*/
|
|
16
|
+
import { Y } from '@wordpress/sync';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Internal dependencies
|
|
20
|
+
*/
|
|
21
|
+
import { AwarenessState } from '../awareness-state';
|
|
22
|
+
import type { EnhancedState, EqualityFieldCheck } from '../types';
|
|
23
|
+
|
|
24
|
+
interface TestState {
|
|
25
|
+
name: string;
|
|
26
|
+
count: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Concrete implementation of AwarenessState for testing
|
|
31
|
+
*/
|
|
32
|
+
class TestAwarenessState extends AwarenessState< TestState > {
|
|
33
|
+
public onSetUpCalled = false;
|
|
34
|
+
|
|
35
|
+
protected equalityFieldChecks: {
|
|
36
|
+
[ FieldName in keyof TestState ]: EqualityFieldCheck<
|
|
37
|
+
TestState,
|
|
38
|
+
FieldName
|
|
39
|
+
>;
|
|
40
|
+
} = {
|
|
41
|
+
name: ( a, b ) => a === b,
|
|
42
|
+
count: ( a, b ) => a === b,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
protected onSetUp(): void {
|
|
46
|
+
this.onSetUpCalled = true;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Expose protected methods for testing
|
|
50
|
+
public testIsFieldEqual< FieldName extends keyof TestState >(
|
|
51
|
+
field: FieldName,
|
|
52
|
+
value1?: TestState[ FieldName ],
|
|
53
|
+
value2?: TestState[ FieldName ]
|
|
54
|
+
): boolean {
|
|
55
|
+
return this.isFieldEqual( field, value1, value2 );
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public testIsStateEqual( state1: TestState, state2: TestState ): boolean {
|
|
59
|
+
return this.isStateEqual( state1, state2 );
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public testUpdateSubscribers( forceUpdate = false ): void {
|
|
63
|
+
this.updateSubscribers( forceUpdate );
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
describe( 'AwarenessState', () => {
|
|
68
|
+
let doc: Y.Doc;
|
|
69
|
+
let awareness: TestAwarenessState;
|
|
70
|
+
|
|
71
|
+
beforeEach( () => {
|
|
72
|
+
jest.useFakeTimers();
|
|
73
|
+
doc = new Y.Doc();
|
|
74
|
+
awareness = new TestAwarenessState( doc );
|
|
75
|
+
} );
|
|
76
|
+
|
|
77
|
+
afterEach( () => {
|
|
78
|
+
jest.useRealTimers();
|
|
79
|
+
doc.destroy();
|
|
80
|
+
} );
|
|
81
|
+
|
|
82
|
+
describe( 'setUp', () => {
|
|
83
|
+
test( 'should call onSetUp on first invocation', () => {
|
|
84
|
+
expect( awareness.onSetUpCalled ).toBe( false );
|
|
85
|
+
awareness.setUp();
|
|
86
|
+
expect( awareness.onSetUpCalled ).toBe( true );
|
|
87
|
+
} );
|
|
88
|
+
|
|
89
|
+
test( 'should only run once (idempotent)', () => {
|
|
90
|
+
awareness.setUp();
|
|
91
|
+
awareness.onSetUpCalled = false;
|
|
92
|
+
awareness.setUp();
|
|
93
|
+
expect( awareness.onSetUpCalled ).toBe( false );
|
|
94
|
+
} );
|
|
95
|
+
} );
|
|
96
|
+
|
|
97
|
+
describe( 'getCurrentState', () => {
|
|
98
|
+
test( 'should return empty array initially', () => {
|
|
99
|
+
awareness.setUp();
|
|
100
|
+
const states = awareness.getCurrentState();
|
|
101
|
+
expect( states ).toEqual( [] );
|
|
102
|
+
} );
|
|
103
|
+
|
|
104
|
+
test( 'should return current state after setting local state', () => {
|
|
105
|
+
awareness.setUp();
|
|
106
|
+
awareness.setLocalStateField( 'name', 'Test' );
|
|
107
|
+
awareness.setLocalStateField( 'count', 42 );
|
|
108
|
+
|
|
109
|
+
// Subscribe to trigger state update
|
|
110
|
+
const callback = jest.fn();
|
|
111
|
+
awareness.onStateChange( callback );
|
|
112
|
+
awareness.testUpdateSubscribers( true );
|
|
113
|
+
|
|
114
|
+
const states = awareness.getCurrentState();
|
|
115
|
+
expect( states.length ).toBe( 1 );
|
|
116
|
+
expect( states[ 0 ].name ).toBe( 'Test' );
|
|
117
|
+
expect( states[ 0 ].count ).toBe( 42 );
|
|
118
|
+
} );
|
|
119
|
+
} );
|
|
120
|
+
|
|
121
|
+
describe( 'getSeenStates', () => {
|
|
122
|
+
test( 'should return empty map initially', () => {
|
|
123
|
+
awareness.setUp();
|
|
124
|
+
const seenStates = awareness.getSeenStates();
|
|
125
|
+
expect( seenStates.size ).toBe( 0 );
|
|
126
|
+
} );
|
|
127
|
+
|
|
128
|
+
test( 'should track seen states after update', () => {
|
|
129
|
+
awareness.setUp();
|
|
130
|
+
awareness.setLocalStateField( 'name', 'Test User' );
|
|
131
|
+
awareness.setLocalStateField( 'count', 1 );
|
|
132
|
+
|
|
133
|
+
// Subscribe and update to trigger state tracking
|
|
134
|
+
const callback = jest.fn();
|
|
135
|
+
awareness.onStateChange( callback );
|
|
136
|
+
awareness.testUpdateSubscribers( true );
|
|
137
|
+
|
|
138
|
+
const seenStates = awareness.getSeenStates();
|
|
139
|
+
expect( seenStates.size ).toBe( 1 );
|
|
140
|
+
expect( seenStates.get( awareness.clientID )?.name ).toBe(
|
|
141
|
+
'Test User'
|
|
142
|
+
);
|
|
143
|
+
} );
|
|
144
|
+
} );
|
|
145
|
+
|
|
146
|
+
describe( 'onStateChange', () => {
|
|
147
|
+
test( 'should register callback and receive updates', () => {
|
|
148
|
+
awareness.setUp();
|
|
149
|
+
const callback = jest.fn();
|
|
150
|
+
|
|
151
|
+
awareness.onStateChange( callback );
|
|
152
|
+
awareness.setLocalStateField( 'name', 'Test' );
|
|
153
|
+
awareness.setLocalStateField( 'count', 5 );
|
|
154
|
+
awareness.testUpdateSubscribers( true );
|
|
155
|
+
|
|
156
|
+
expect( callback ).toHaveBeenCalled();
|
|
157
|
+
} );
|
|
158
|
+
|
|
159
|
+
test( 'should return unsubscribe function', () => {
|
|
160
|
+
awareness.setUp();
|
|
161
|
+
const callback = jest.fn();
|
|
162
|
+
|
|
163
|
+
const unsubscribe = awareness.onStateChange( callback );
|
|
164
|
+
awareness.setLocalStateField( 'name', 'Test' );
|
|
165
|
+
awareness.setLocalStateField( 'count', 5 );
|
|
166
|
+
awareness.testUpdateSubscribers( true );
|
|
167
|
+
callback.mockClear();
|
|
168
|
+
|
|
169
|
+
unsubscribe();
|
|
170
|
+
awareness.setLocalStateField( 'name', 'Changed' );
|
|
171
|
+
awareness.testUpdateSubscribers( true );
|
|
172
|
+
|
|
173
|
+
expect( callback ).not.toHaveBeenCalled();
|
|
174
|
+
} );
|
|
175
|
+
|
|
176
|
+
test( 'should not call callback when state has not changed', () => {
|
|
177
|
+
awareness.setUp();
|
|
178
|
+
const callback = jest.fn();
|
|
179
|
+
|
|
180
|
+
awareness.onStateChange( callback );
|
|
181
|
+
awareness.setLocalStateField( 'name', 'Test' );
|
|
182
|
+
awareness.setLocalStateField( 'count', 5 );
|
|
183
|
+
awareness.testUpdateSubscribers( true );
|
|
184
|
+
callback.mockClear();
|
|
185
|
+
|
|
186
|
+
// Update without changing state
|
|
187
|
+
awareness.testUpdateSubscribers( false );
|
|
188
|
+
|
|
189
|
+
expect( callback ).not.toHaveBeenCalled();
|
|
190
|
+
} );
|
|
191
|
+
|
|
192
|
+
test( 'should call callback when forceUpdate is true', () => {
|
|
193
|
+
awareness.setUp();
|
|
194
|
+
const callback = jest.fn();
|
|
195
|
+
|
|
196
|
+
awareness.onStateChange( callback );
|
|
197
|
+
awareness.setLocalStateField( 'name', 'Test' );
|
|
198
|
+
awareness.setLocalStateField( 'count', 5 );
|
|
199
|
+
awareness.testUpdateSubscribers( true );
|
|
200
|
+
callback.mockClear();
|
|
201
|
+
|
|
202
|
+
// Force update without changing state
|
|
203
|
+
awareness.testUpdateSubscribers( true );
|
|
204
|
+
|
|
205
|
+
expect( callback ).toHaveBeenCalled();
|
|
206
|
+
} );
|
|
207
|
+
} );
|
|
208
|
+
|
|
209
|
+
describe( 'setThrottledLocalStateField', () => {
|
|
210
|
+
test( 'should set field immediately', () => {
|
|
211
|
+
awareness.setUp();
|
|
212
|
+
awareness.setThrottledLocalStateField( 'name', 'Throttled', 100 );
|
|
213
|
+
|
|
214
|
+
expect( awareness.getLocalStateField( 'name' ) ).toBe(
|
|
215
|
+
'Throttled'
|
|
216
|
+
);
|
|
217
|
+
} );
|
|
218
|
+
|
|
219
|
+
test( 'should update field after throttle period', () => {
|
|
220
|
+
awareness.setUp();
|
|
221
|
+
awareness.setThrottledLocalStateField( 'name', 'First', 100 );
|
|
222
|
+
|
|
223
|
+
// Verify initial throttled value is set
|
|
224
|
+
expect( awareness.getLocalStateField( 'name' ) ).toBe( 'First' );
|
|
225
|
+
|
|
226
|
+
// Set to a new value
|
|
227
|
+
awareness.setLocalStateField( 'name', 'Second' );
|
|
228
|
+
|
|
229
|
+
// Verify the value was updated
|
|
230
|
+
expect( awareness.getLocalStateField( 'name' ) ).toBe( 'Second' );
|
|
231
|
+
|
|
232
|
+
jest.advanceTimersByTime( 100 );
|
|
233
|
+
|
|
234
|
+
// After throttle period, the value should still be the last set value
|
|
235
|
+
expect( awareness.getLocalStateField( 'name' ) ).toBe( 'Second' );
|
|
236
|
+
} );
|
|
237
|
+
} );
|
|
238
|
+
|
|
239
|
+
describe( 'setConnectionStatus', () => {
|
|
240
|
+
test( 'should trigger subscriber update when connection changes', () => {
|
|
241
|
+
awareness.setUp();
|
|
242
|
+
const callback = jest.fn();
|
|
243
|
+
awareness.onStateChange( callback );
|
|
244
|
+
|
|
245
|
+
awareness.setLocalStateField( 'name', 'Test' );
|
|
246
|
+
awareness.setLocalStateField( 'count', 1 );
|
|
247
|
+
awareness.testUpdateSubscribers( true );
|
|
248
|
+
callback.mockClear();
|
|
249
|
+
|
|
250
|
+
awareness.setConnectionStatus( false );
|
|
251
|
+
|
|
252
|
+
expect( callback ).toHaveBeenCalled();
|
|
253
|
+
} );
|
|
254
|
+
} );
|
|
255
|
+
|
|
256
|
+
describe( 'setLocalStateField with equality checks', () => {
|
|
257
|
+
test( 'should not trigger update when value is equal', () => {
|
|
258
|
+
awareness.setUp();
|
|
259
|
+
awareness.setLocalStateField( 'name', 'Same' );
|
|
260
|
+
|
|
261
|
+
const callback = jest.fn();
|
|
262
|
+
awareness.onStateChange( callback );
|
|
263
|
+
awareness.testUpdateSubscribers( true );
|
|
264
|
+
callback.mockClear();
|
|
265
|
+
|
|
266
|
+
// Set same value
|
|
267
|
+
awareness.setLocalStateField( 'name', 'Same' );
|
|
268
|
+
awareness.testUpdateSubscribers( false );
|
|
269
|
+
|
|
270
|
+
expect( callback ).not.toHaveBeenCalled();
|
|
271
|
+
} );
|
|
272
|
+
|
|
273
|
+
test( 'should trigger update when value changes', () => {
|
|
274
|
+
awareness.setUp();
|
|
275
|
+
awareness.setLocalStateField( 'name', 'Original' );
|
|
276
|
+
|
|
277
|
+
const callback = jest.fn();
|
|
278
|
+
awareness.onStateChange( callback );
|
|
279
|
+
awareness.testUpdateSubscribers( true );
|
|
280
|
+
callback.mockClear();
|
|
281
|
+
|
|
282
|
+
// Set different value
|
|
283
|
+
awareness.setLocalStateField( 'name', 'Changed' );
|
|
284
|
+
awareness.testUpdateSubscribers( true );
|
|
285
|
+
|
|
286
|
+
expect( callback ).toHaveBeenCalled();
|
|
287
|
+
} );
|
|
288
|
+
} );
|
|
289
|
+
|
|
290
|
+
describe( 'isFieldEqual', () => {
|
|
291
|
+
test( 'should use strict equality for clientId', () => {
|
|
292
|
+
// Cast to access protected method via test helper
|
|
293
|
+
expect(
|
|
294
|
+
awareness.testIsFieldEqual( 'name' as any, 123, 123 )
|
|
295
|
+
).toBe( true );
|
|
296
|
+
} );
|
|
297
|
+
|
|
298
|
+
test( 'should use custom equality check for defined fields', () => {
|
|
299
|
+
expect( awareness.testIsFieldEqual( 'name', 'test', 'test' ) ).toBe(
|
|
300
|
+
true
|
|
301
|
+
);
|
|
302
|
+
expect(
|
|
303
|
+
awareness.testIsFieldEqual( 'name', 'test', 'other' )
|
|
304
|
+
).toBe( false );
|
|
305
|
+
} );
|
|
306
|
+
|
|
307
|
+
test( 'should throw error for fields without equality check', () => {
|
|
308
|
+
expect( () => {
|
|
309
|
+
awareness.testIsFieldEqual( 'unknown' as any, 1, 2 );
|
|
310
|
+
} ).toThrow(
|
|
311
|
+
'No equality check implemented for awareness state field "unknown".'
|
|
312
|
+
);
|
|
313
|
+
} );
|
|
314
|
+
} );
|
|
315
|
+
|
|
316
|
+
describe( 'isStateEqual', () => {
|
|
317
|
+
test( 'should return true for identical states', () => {
|
|
318
|
+
const state1: TestState = { name: 'Test', count: 5 };
|
|
319
|
+
const state2: TestState = { name: 'Test', count: 5 };
|
|
320
|
+
|
|
321
|
+
expect( awareness.testIsStateEqual( state1, state2 ) ).toBe( true );
|
|
322
|
+
} );
|
|
323
|
+
|
|
324
|
+
test( 'should return false when name differs', () => {
|
|
325
|
+
const state1: TestState = { name: 'Test1', count: 5 };
|
|
326
|
+
const state2: TestState = { name: 'Test2', count: 5 };
|
|
327
|
+
|
|
328
|
+
expect( awareness.testIsStateEqual( state1, state2 ) ).toBe(
|
|
329
|
+
false
|
|
330
|
+
);
|
|
331
|
+
} );
|
|
332
|
+
|
|
333
|
+
test( 'should return false when count differs', () => {
|
|
334
|
+
const state1: TestState = { name: 'Test', count: 5 };
|
|
335
|
+
const state2: TestState = { name: 'Test', count: 10 };
|
|
336
|
+
|
|
337
|
+
expect( awareness.testIsStateEqual( state1, state2 ) ).toBe(
|
|
338
|
+
false
|
|
339
|
+
);
|
|
340
|
+
} );
|
|
341
|
+
} );
|
|
342
|
+
|
|
343
|
+
describe( 'updateSubscribers', () => {
|
|
344
|
+
test( 'should not call subscribers when no subscriptions exist', () => {
|
|
345
|
+
awareness.setUp();
|
|
346
|
+
// This should not throw
|
|
347
|
+
awareness.testUpdateSubscribers();
|
|
348
|
+
} );
|
|
349
|
+
|
|
350
|
+
test( 'should include enhanced state properties', () => {
|
|
351
|
+
awareness.setUp();
|
|
352
|
+
let receivedStates: EnhancedState< TestState >[] = [];
|
|
353
|
+
|
|
354
|
+
awareness.onStateChange( ( states ) => {
|
|
355
|
+
receivedStates = states;
|
|
356
|
+
} );
|
|
357
|
+
|
|
358
|
+
awareness.setLocalStateField( 'name', 'Test' );
|
|
359
|
+
awareness.setLocalStateField( 'count', 1 );
|
|
360
|
+
awareness.testUpdateSubscribers( true );
|
|
361
|
+
|
|
362
|
+
expect( receivedStates.length ).toBe( 1 );
|
|
363
|
+
expect( receivedStates[ 0 ].clientId ).toBe( awareness.clientID );
|
|
364
|
+
expect( receivedStates[ 0 ].isMe ).toBe( true );
|
|
365
|
+
expect( receivedStates[ 0 ].isConnected ).toBe( true );
|
|
366
|
+
} );
|
|
367
|
+
} );
|
|
368
|
+
|
|
369
|
+
describe( 'change event handling', () => {
|
|
370
|
+
test( 'should update subscribers on awareness change', () => {
|
|
371
|
+
awareness.setUp();
|
|
372
|
+
const callback = jest.fn();
|
|
373
|
+
awareness.onStateChange( callback );
|
|
374
|
+
|
|
375
|
+
awareness.setLocalStateField( 'name', 'Test' );
|
|
376
|
+
awareness.setLocalStateField( 'count', 1 );
|
|
377
|
+
|
|
378
|
+
// Emit a change event manually
|
|
379
|
+
awareness.emit( 'change', [
|
|
380
|
+
{
|
|
381
|
+
added: [],
|
|
382
|
+
updated: [ awareness.clientID ],
|
|
383
|
+
removed: [],
|
|
384
|
+
},
|
|
385
|
+
] );
|
|
386
|
+
|
|
387
|
+
expect( callback ).toHaveBeenCalled();
|
|
388
|
+
} );
|
|
389
|
+
|
|
390
|
+
test( 'should handle user removal and delayed cleanup', () => {
|
|
391
|
+
awareness.setUp();
|
|
392
|
+
const callback = jest.fn();
|
|
393
|
+
awareness.onStateChange( callback );
|
|
394
|
+
|
|
395
|
+
awareness.setLocalStateField( 'name', 'Test' );
|
|
396
|
+
awareness.setLocalStateField( 'count', 1 );
|
|
397
|
+
awareness.testUpdateSubscribers( true );
|
|
398
|
+
|
|
399
|
+
// Simulate user removal
|
|
400
|
+
awareness.emit( 'change', [
|
|
401
|
+
{
|
|
402
|
+
added: [],
|
|
403
|
+
updated: [],
|
|
404
|
+
removed: [ 999 ],
|
|
405
|
+
},
|
|
406
|
+
] );
|
|
407
|
+
|
|
408
|
+
// Should trigger update for removal
|
|
409
|
+
expect( callback ).toHaveBeenCalled();
|
|
410
|
+
callback.mockClear();
|
|
411
|
+
|
|
412
|
+
// After REMOVAL_DELAY_IN_MS, should trigger another update
|
|
413
|
+
jest.advanceTimersByTime( 5000 );
|
|
414
|
+
expect( callback ).toHaveBeenCalled();
|
|
415
|
+
} );
|
|
416
|
+
} );
|
|
417
|
+
} );
|