@wordpress/core-data 6.15.0 → 6.17.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 (240) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +1 -1
  3. package/build/actions.js +70 -135
  4. package/build/actions.js.map +1 -1
  5. package/build/batch/create-batch.js +0 -22
  6. package/build/batch/create-batch.js.map +1 -1
  7. package/build/batch/default-processor.js +3 -15
  8. package/build/batch/default-processor.js.map +1 -1
  9. package/build/batch/index.js +0 -3
  10. package/build/batch/index.js.map +1 -1
  11. package/build/entities.js +109 -36
  12. package/build/entities.js.map +1 -1
  13. package/build/entity-provider.js +34 -53
  14. package/build/entity-provider.js.map +1 -1
  15. package/build/entity-types/attachment.js.map +1 -1
  16. package/build/entity-types/base-entity-records.js +0 -3
  17. package/build/entity-types/base-entity-records.js.map +1 -1
  18. package/build/entity-types/comment.js.map +1 -1
  19. package/build/entity-types/helpers.js.map +1 -1
  20. package/build/entity-types/index.js.map +1 -1
  21. package/build/entity-types/menu-location.js.map +1 -1
  22. package/build/entity-types/nav-menu-item.js.map +1 -1
  23. package/build/entity-types/nav-menu.js.map +1 -1
  24. package/build/entity-types/page.js.map +1 -1
  25. package/build/entity-types/plugin.js.map +1 -1
  26. package/build/entity-types/post.js.map +1 -1
  27. package/build/entity-types/settings.js.map +1 -1
  28. package/build/entity-types/sidebar.js.map +1 -1
  29. package/build/entity-types/taxonomy.js.map +1 -1
  30. package/build/entity-types/theme.js.map +1 -1
  31. package/build/entity-types/type.js.map +1 -1
  32. package/build/entity-types/user.js.map +1 -1
  33. package/build/entity-types/widget-type.js.map +1 -1
  34. package/build/entity-types/widget.js.map +1 -1
  35. package/build/entity-types/wp-template-part.js.map +1 -1
  36. package/build/entity-types/wp-template.js.map +1 -1
  37. package/build/fetch/__experimental-fetch-link-suggestions.js +14 -20
  38. package/build/fetch/__experimental-fetch-link-suggestions.js.map +1 -1
  39. package/build/fetch/__experimental-fetch-url-data.js +4 -13
  40. package/build/fetch/__experimental-fetch-url-data.js.map +1 -1
  41. package/build/fetch/index.js +0 -3
  42. package/build/fetch/index.js.map +1 -1
  43. package/build/hooks/constants.js +4 -5
  44. package/build/hooks/constants.js.map +1 -1
  45. package/build/hooks/index.js +0 -5
  46. package/build/hooks/index.js.map +1 -1
  47. package/build/hooks/memoize.js +0 -3
  48. package/build/hooks/memoize.js.map +1 -1
  49. package/build/hooks/use-entity-record.js +0 -9
  50. package/build/hooks/use-entity-record.js.map +1 -1
  51. package/build/hooks/use-entity-records.js +2 -9
  52. package/build/hooks/use-entity-records.js.map +1 -1
  53. package/build/hooks/use-query-select.js +1 -13
  54. package/build/hooks/use-query-select.js.map +1 -1
  55. package/build/hooks/use-resource-permissions.js +0 -15
  56. package/build/hooks/use-resource-permissions.js.map +1 -1
  57. package/build/index.js +9 -41
  58. package/build/index.js.map +1 -1
  59. package/build/locks/actions.js +1 -6
  60. package/build/locks/actions.js.map +1 -1
  61. package/build/locks/engine.js +1 -9
  62. package/build/locks/engine.js.map +1 -1
  63. package/build/locks/reducer.js +8 -9
  64. package/build/locks/reducer.js.map +1 -1
  65. package/build/locks/selectors.js +8 -11
  66. package/build/locks/selectors.js.map +1 -1
  67. package/build/locks/utils.js +4 -19
  68. package/build/locks/utils.js.map +1 -1
  69. package/build/name.js +0 -1
  70. package/build/name.js.map +1 -1
  71. package/build/private-apis.js +1 -2
  72. package/build/private-apis.js.map +1 -1
  73. package/build/private-selectors.js +2 -5
  74. package/build/private-selectors.js.map +1 -1
  75. package/build/queried-data/actions.js +4 -6
  76. package/build/queried-data/actions.js.map +1 -1
  77. package/build/queried-data/get-query-parts.js +11 -24
  78. package/build/queried-data/get-query-parts.js.map +1 -1
  79. package/build/queried-data/index.js +0 -6
  80. package/build/queried-data/index.js.map +1 -1
  81. package/build/queried-data/reducer.js +33 -51
  82. package/build/queried-data/reducer.js.map +1 -1
  83. package/build/queried-data/selectors.js +4 -25
  84. package/build/queried-data/selectors.js.map +1 -1
  85. package/build/reducer.js +76 -128
  86. package/build/reducer.js.map +1 -1
  87. package/build/resolvers.js +91 -133
  88. package/build/resolvers.js.map +1 -1
  89. package/build/selectors.js +51 -145
  90. package/build/selectors.js.map +1 -1
  91. package/build/sync.js +19 -0
  92. package/build/sync.js.map +1 -0
  93. package/build/types.js.map +1 -1
  94. package/build/utils/conservative-map-item.js +3 -10
  95. package/build/utils/conservative-map-item.js.map +1 -1
  96. package/build/utils/forward-resolver.js +0 -2
  97. package/build/utils/forward-resolver.js.map +1 -1
  98. package/build/utils/get-normalized-comma-separable.js +0 -3
  99. package/build/utils/get-normalized-comma-separable.js.map +1 -1
  100. package/build/utils/if-matching-action.js +0 -3
  101. package/build/utils/if-matching-action.js.map +1 -1
  102. package/build/utils/index.js +0 -10
  103. package/build/utils/index.js.map +1 -1
  104. package/build/utils/is-raw-attribute.js +0 -1
  105. package/build/utils/is-raw-attribute.js.map +1 -1
  106. package/build/utils/on-sub-key.js +5 -9
  107. package/build/utils/on-sub-key.js.map +1 -1
  108. package/build/utils/replace-action.js +0 -2
  109. package/build/utils/replace-action.js.map +1 -1
  110. package/build/utils/set-nested-value.js +0 -4
  111. package/build/utils/set-nested-value.js.map +1 -1
  112. package/build/utils/with-weak-map-cache.js +3 -6
  113. package/build/utils/with-weak-map-cache.js.map +1 -1
  114. package/build-module/actions.js +73 -96
  115. package/build-module/actions.js.map +1 -1
  116. package/build-module/batch/create-batch.js +1 -20
  117. package/build-module/batch/create-batch.js.map +1 -1
  118. package/build-module/batch/default-processor.js +4 -13
  119. package/build-module/batch/default-processor.js.map +1 -1
  120. package/build-module/batch/index.js.map +1 -1
  121. package/build-module/entities.js +109 -25
  122. package/build-module/entities.js.map +1 -1
  123. package/build-module/entity-provider.js +35 -45
  124. package/build-module/entity-provider.js.map +1 -1
  125. package/build-module/entity-types/attachment.js.map +1 -1
  126. package/build-module/entity-types/base-entity-records.js +0 -2
  127. package/build-module/entity-types/base-entity-records.js.map +1 -1
  128. package/build-module/entity-types/comment.js.map +1 -1
  129. package/build-module/entity-types/helpers.js.map +1 -1
  130. package/build-module/entity-types/index.js.map +1 -1
  131. package/build-module/entity-types/menu-location.js.map +1 -1
  132. package/build-module/entity-types/nav-menu-item.js.map +1 -1
  133. package/build-module/entity-types/nav-menu.js.map +1 -1
  134. package/build-module/entity-types/page.js.map +1 -1
  135. package/build-module/entity-types/plugin.js.map +1 -1
  136. package/build-module/entity-types/post.js.map +1 -1
  137. package/build-module/entity-types/settings.js.map +1 -1
  138. package/build-module/entity-types/sidebar.js.map +1 -1
  139. package/build-module/entity-types/taxonomy.js.map +1 -1
  140. package/build-module/entity-types/theme.js.map +1 -1
  141. package/build-module/entity-types/type.js.map +1 -1
  142. package/build-module/entity-types/user.js.map +1 -1
  143. package/build-module/entity-types/widget-type.js.map +1 -1
  144. package/build-module/entity-types/widget.js.map +1 -1
  145. package/build-module/entity-types/wp-template-part.js.map +1 -1
  146. package/build-module/entity-types/wp-template.js.map +1 -1
  147. package/build-module/fetch/__experimental-fetch-link-suggestions.js +15 -15
  148. package/build-module/fetch/__experimental-fetch-link-suggestions.js.map +1 -1
  149. package/build-module/fetch/__experimental-fetch-url-data.js +5 -10
  150. package/build-module/fetch/__experimental-fetch-url-data.js.map +1 -1
  151. package/build-module/fetch/index.js.map +1 -1
  152. package/build-module/hooks/constants.js +3 -4
  153. package/build-module/hooks/constants.js.map +1 -1
  154. package/build-module/hooks/index.js.map +1 -1
  155. package/build-module/hooks/memoize.js +2 -1
  156. package/build-module/hooks/memoize.js.map +1 -1
  157. package/build-module/hooks/use-entity-record.js +1 -3
  158. package/build-module/hooks/use-entity-record.js.map +1 -1
  159. package/build-module/hooks/use-entity-records.js +2 -3
  160. package/build-module/hooks/use-entity-records.js.map +1 -1
  161. package/build-module/hooks/use-query-select.js +1 -9
  162. package/build-module/hooks/use-query-select.js.map +1 -1
  163. package/build-module/hooks/use-resource-permissions.js +1 -10
  164. package/build-module/hooks/use-resource-permissions.js.map +1 -1
  165. package/build-module/index.js +11 -19
  166. package/build-module/index.js.map +1 -1
  167. package/build-module/locks/actions.js +0 -3
  168. package/build-module/locks/actions.js.map +1 -1
  169. package/build-module/locks/engine.js +0 -5
  170. package/build-module/locks/engine.js.map +1 -1
  171. package/build-module/locks/reducer.js +7 -7
  172. package/build-module/locks/reducer.js.map +1 -1
  173. package/build-module/locks/selectors.js +7 -8
  174. package/build-module/locks/selectors.js.map +1 -1
  175. package/build-module/locks/utils.js +4 -14
  176. package/build-module/locks/utils.js.map +1 -1
  177. package/build-module/name.js.map +1 -1
  178. package/build-module/private-apis.js.map +1 -1
  179. package/build-module/private-selectors.js +2 -2
  180. package/build-module/private-selectors.js.map +1 -1
  181. package/build-module/queried-data/actions.js +4 -3
  182. package/build-module/queried-data/actions.js.map +1 -1
  183. package/build-module/queried-data/get-query-parts.js +13 -21
  184. package/build-module/queried-data/get-query-parts.js.map +1 -1
  185. package/build-module/queried-data/index.js.map +1 -1
  186. package/build-module/queried-data/reducer.js +33 -42
  187. package/build-module/queried-data/reducer.js.map +1 -1
  188. package/build-module/queried-data/selectors.js +6 -21
  189. package/build-module/queried-data/selectors.js.map +1 -1
  190. package/build-module/reducer.js +79 -104
  191. package/build-module/reducer.js.map +1 -1
  192. package/build-module/resolvers.js +94 -96
  193. package/build-module/resolvers.js.map +1 -1
  194. package/build-module/selectors.js +56 -92
  195. package/build-module/selectors.js.map +1 -1
  196. package/build-module/sync.js +12 -0
  197. package/build-module/sync.js.map +1 -0
  198. package/build-module/types.js.map +1 -1
  199. package/build-module/utils/conservative-map-item.js +4 -8
  200. package/build-module/utils/conservative-map-item.js.map +1 -1
  201. package/build-module/utils/forward-resolver.js +0 -1
  202. package/build-module/utils/forward-resolver.js.map +1 -1
  203. package/build-module/utils/get-normalized-comma-separable.js +0 -2
  204. package/build-module/utils/get-normalized-comma-separable.js.map +1 -1
  205. package/build-module/utils/if-matching-action.js +0 -2
  206. package/build-module/utils/if-matching-action.js.map +1 -1
  207. package/build-module/utils/index.js.map +1 -1
  208. package/build-module/utils/is-raw-attribute.js.map +1 -1
  209. package/build-module/utils/on-sub-key.js +5 -7
  210. package/build-module/utils/on-sub-key.js.map +1 -1
  211. package/build-module/utils/replace-action.js +0 -1
  212. package/build-module/utils/replace-action.js.map +1 -1
  213. package/build-module/utils/set-nested-value.js +0 -3
  214. package/build-module/utils/set-nested-value.js.map +1 -1
  215. package/build-module/utils/with-weak-map-cache.js +3 -5
  216. package/build-module/utils/with-weak-map-cache.js.map +1 -1
  217. package/build-types/actions.d.ts.map +1 -1
  218. package/build-types/entities.d.ts +53 -0
  219. package/build-types/entities.d.ts.map +1 -1
  220. package/build-types/entity-provider.d.ts +1 -1
  221. package/build-types/entity-provider.d.ts.map +1 -1
  222. package/build-types/hooks/memoize.d.ts +1 -1
  223. package/build-types/hooks/memoize.d.ts.map +1 -1
  224. package/build-types/index.d.ts +4 -4
  225. package/build-types/index.d.ts.map +1 -1
  226. package/build-types/resolvers.d.ts.map +1 -1
  227. package/build-types/selectors.d.ts +1 -1
  228. package/build-types/selectors.d.ts.map +1 -1
  229. package/build-types/sync.d.ts +2 -0
  230. package/build-types/sync.d.ts.map +1 -0
  231. package/package.json +17 -15
  232. package/src/actions.js +30 -17
  233. package/src/entities.js +91 -0
  234. package/src/entity-provider.js +11 -2
  235. package/src/resolvers.js +89 -41
  236. package/src/selectors.ts +1 -1
  237. package/src/sync.js +18 -0
  238. package/src/test/entity-provider.js +272 -0
  239. package/tsconfig.json +1 -0
  240. package/tsconfig.tsbuildinfo +1 -1
package/src/resolvers.js CHANGED
@@ -15,6 +15,7 @@ import apiFetch from '@wordpress/api-fetch';
15
15
  import { STORE_NAME } from './name';
16
16
  import { getOrLoadEntitiesConfig, DEFAULT_ENTITY_KEY } from './entities';
17
17
  import { forwardResolver, getNormalizedCommaSeparable } from './utils';
18
+ import { getSyncProvider } from './sync';
18
19
 
19
20
  /**
20
21
  * Requests authors from the REST API.
@@ -71,51 +72,98 @@ export const getEntityRecord =
71
72
  );
72
73
 
73
74
  try {
74
- if ( query !== undefined && query._fields ) {
75
- // If requesting specific fields, items and query association to said
76
- // records are stored by ID reference. Thus, fields must always include
77
- // the ID.
78
- query = {
79
- ...query,
80
- _fields: [
81
- ...new Set( [
82
- ...( getNormalizedCommaSeparable( query._fields ) ||
83
- [] ),
84
- entityConfig.key || DEFAULT_ENTITY_KEY,
85
- ] ),
86
- ].join(),
87
- };
88
- }
89
-
90
- // Disable reason: While true that an early return could leave `path`
91
- // unused, it's important that path is derived using the query prior to
92
- // additional query modifications in the condition below, since those
93
- // modifications are relevant to how the data is tracked in state, and not
94
- // for how the request is made to the REST API.
95
-
96
- // eslint-disable-next-line @wordpress/no-unused-vars-before-return
97
- const path = addQueryArgs(
98
- entityConfig.baseURL + ( key ? '/' + key : '' ),
99
- {
100
- ...entityConfig.baseURLParams,
101
- ...query,
75
+ // Entity supports configs,
76
+ // use the sync algorithm instead of the old fetch behavior.
77
+ if (
78
+ window.__experimentalEnableSync &&
79
+ entityConfig.syncConfig &&
80
+ ! query
81
+ ) {
82
+ const objectId = entityConfig.getSyncObjectId( key );
83
+
84
+ // Loads the persisted document.
85
+ await getSyncProvider().bootstrap(
86
+ entityConfig.syncObjectType,
87
+ objectId,
88
+ ( record ) => {
89
+ dispatch.receiveEntityRecords(
90
+ kind,
91
+ name,
92
+ record,
93
+ query
94
+ );
95
+ }
96
+ );
97
+
98
+ // Boostraps the edited document as well (and load from peers).
99
+ await getSyncProvider().bootstrap(
100
+ entityConfig.syncObjectType + '--edit',
101
+ objectId,
102
+ ( record ) => {
103
+ dispatch( {
104
+ type: 'EDIT_ENTITY_RECORD',
105
+ kind,
106
+ name,
107
+ recordId: key,
108
+ edits: record,
109
+ meta: {
110
+ undo: undefined,
111
+ },
112
+ } );
113
+ }
114
+ );
115
+ } else {
116
+ if ( query !== undefined && query._fields ) {
117
+ // If requesting specific fields, items and query association to said
118
+ // records are stored by ID reference. Thus, fields must always include
119
+ // the ID.
120
+ query = {
121
+ ...query,
122
+ _fields: [
123
+ ...new Set( [
124
+ ...( getNormalizedCommaSeparable(
125
+ query._fields
126
+ ) || [] ),
127
+ entityConfig.key || DEFAULT_ENTITY_KEY,
128
+ ] ),
129
+ ].join(),
130
+ };
102
131
  }
103
- );
104
-
105
- if ( query !== undefined ) {
106
- query = { ...query, include: [ key ] };
107
132
 
108
- // The resolution cache won't consider query as reusable based on the
109
- // fields, so it's tested here, prior to initiating the REST request,
110
- // and without causing `getEntityRecords` resolution to occur.
111
- const hasRecords = select.hasEntityRecords( kind, name, query );
112
- if ( hasRecords ) {
113
- return;
133
+ // Disable reason: While true that an early return could leave `path`
134
+ // unused, it's important that path is derived using the query prior to
135
+ // additional query modifications in the condition below, since those
136
+ // modifications are relevant to how the data is tracked in state, and not
137
+ // for how the request is made to the REST API.
138
+
139
+ // eslint-disable-next-line @wordpress/no-unused-vars-before-return
140
+ const path = addQueryArgs(
141
+ entityConfig.baseURL + ( key ? '/' + key : '' ),
142
+ {
143
+ ...entityConfig.baseURLParams,
144
+ ...query,
145
+ }
146
+ );
147
+
148
+ if ( query !== undefined ) {
149
+ query = { ...query, include: [ key ] };
150
+
151
+ // The resolution cache won't consider query as reusable based on the
152
+ // fields, so it's tested here, prior to initiating the REST request,
153
+ // and without causing `getEntityRecords` resolution to occur.
154
+ const hasRecords = select.hasEntityRecords(
155
+ kind,
156
+ name,
157
+ query
158
+ );
159
+ if ( hasRecords ) {
160
+ return;
161
+ }
114
162
  }
115
- }
116
163
 
117
- const record = await apiFetch( { path } );
118
- dispatch.receiveEntityRecords( kind, name, record, query );
164
+ const record = await apiFetch( { path } );
165
+ dispatch.receiveEntityRecords( kind, name, record, query );
166
+ }
119
167
  } finally {
120
168
  dispatch.__unstableReleaseStoreLock( lock );
121
169
  }
package/src/selectors.ts CHANGED
@@ -1266,7 +1266,7 @@ export function getBlockPatternCategories( state: State ): Array< any > {
1266
1266
  */
1267
1267
  export function getCurrentThemeGlobalStylesRevisions(
1268
1268
  state: State
1269
- ): Object | null {
1269
+ ): Array< object > | null {
1270
1270
  const currentGlobalStylesId =
1271
1271
  __experimentalGetCurrentGlobalStylesId( state );
1272
1272
 
package/src/sync.js ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import {
5
+ createSyncProvider,
6
+ connectIndexDb,
7
+ connectWebRTC,
8
+ } from '@wordpress/sync';
9
+
10
+ let syncProvider;
11
+
12
+ export function getSyncProvider() {
13
+ if ( ! syncProvider ) {
14
+ syncProvider = createSyncProvider( connectIndexDb, connectWebRTC );
15
+ }
16
+
17
+ return syncProvider;
18
+ }
@@ -0,0 +1,272 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { act, render } from '@testing-library/react';
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import {
10
+ createBlock,
11
+ registerBlockType,
12
+ unregisterBlockType,
13
+ getBlockTypes,
14
+ } from '@wordpress/blocks';
15
+ import { RichText, useBlockProps } from '@wordpress/block-editor';
16
+ import { createRegistry, RegistryProvider } from '@wordpress/data';
17
+
18
+ /**
19
+ * Internal dependencies
20
+ */
21
+ import { store as coreDataStore } from '../index';
22
+ import { useEntityBlockEditor } from '../entity-provider';
23
+
24
+ const postTypeConfig = {
25
+ kind: 'postType',
26
+ name: 'post',
27
+ baseURL: '/wp/v2/posts',
28
+ transientEdits: { blocks: true, selection: true },
29
+ mergedEdits: { meta: true },
30
+ rawAttributes: [ 'title', 'excerpt', 'content' ],
31
+ };
32
+
33
+ const postTypeEntity = {
34
+ slug: 'post',
35
+ rest_base: 'posts',
36
+ labels: {
37
+ item_updated: 'Updated Post',
38
+ item_published: 'Post published',
39
+ item_reverted_to_draft: 'Post reverted to draft.',
40
+ },
41
+ };
42
+
43
+ const aSinglePost = {
44
+ id: 1,
45
+ type: 'post',
46
+ content: {
47
+ raw: '<!-- wp:test-block-with-array-of-strings --><div><p>apples</p><p></p><p>oranges</p></div><!-- /wp:test-block-with-array-of-strings --><!-- wp:test-block --><p>A paragraph</p><!-- /wp:test-block -->',
48
+ rendered: '<p>A paragraph</p>',
49
+ },
50
+ meta: {
51
+ footnotes: '[]',
52
+ },
53
+ };
54
+
55
+ function createRegistryWithStores() {
56
+ // Create a registry.
57
+ const registry = createRegistry();
58
+
59
+ // Register store.
60
+ registry.register( coreDataStore );
61
+
62
+ // Register post type entity.
63
+ registry.dispatch( coreDataStore ).addEntities( [ postTypeConfig ] );
64
+
65
+ // Store post type entity.
66
+ registry
67
+ .dispatch( coreDataStore )
68
+ .receiveEntityRecords( 'root', 'postType', [ postTypeEntity ] );
69
+
70
+ // Store a single post for use by the tests.
71
+ registry
72
+ .dispatch( coreDataStore )
73
+ .receiveEntityRecords( 'postType', 'post', [ aSinglePost ] );
74
+
75
+ return registry;
76
+ }
77
+
78
+ describe( 'useEntityBlockEditor', () => {
79
+ let registry;
80
+
81
+ beforeEach( () => {
82
+ registry = createRegistryWithStores();
83
+
84
+ const edit = ( { children } ) => <>{ children }</>;
85
+
86
+ registerBlockType( 'core/test-block', {
87
+ supports: {
88
+ className: false,
89
+ },
90
+ save: ( { attributes } ) => {
91
+ const { content } = attributes;
92
+ return (
93
+ <p { ...useBlockProps.save() }>
94
+ <RichText.Content value={ content } />
95
+ </p>
96
+ );
97
+ },
98
+ category: 'text',
99
+ attributes: {
100
+ content: {
101
+ type: 'string',
102
+ source: 'html',
103
+ selector: 'p',
104
+ default: '',
105
+ __experimentalRole: 'content',
106
+ },
107
+ },
108
+ title: 'block title',
109
+ edit,
110
+ } );
111
+
112
+ registerBlockType( 'core/test-block-with-array-of-strings', {
113
+ supports: {
114
+ className: false,
115
+ },
116
+ save: ( { attributes } ) => {
117
+ const { items } = attributes;
118
+ return (
119
+ <div>
120
+ { items.map( ( item, index ) => (
121
+ <p key={ index }>{ item }</p>
122
+ ) ) }
123
+ </div>
124
+ );
125
+ },
126
+ category: 'text',
127
+ attributes: {
128
+ items: {
129
+ type: 'array',
130
+ items: {
131
+ type: 'string',
132
+ },
133
+ default: [ 'apples', null, 'oranges' ],
134
+ },
135
+ },
136
+ title: 'block title',
137
+ edit,
138
+ } );
139
+ } );
140
+
141
+ afterEach( () => {
142
+ getBlockTypes().forEach( ( block ) => {
143
+ unregisterBlockType( block.name );
144
+ } );
145
+ } );
146
+
147
+ it( 'does not mutate block attributes that include an array of strings or null values', async () => {
148
+ let blocks, onChange;
149
+ const TestComponent = () => {
150
+ [ blocks, , onChange ] = useEntityBlockEditor( 'postType', 'post', {
151
+ id: 1,
152
+ } );
153
+
154
+ return <div />;
155
+ };
156
+
157
+ render(
158
+ <RegistryProvider value={ registry }>
159
+ <TestComponent />
160
+ </RegistryProvider>
161
+ );
162
+
163
+ expect( blocks[ 0 ].name ).toEqual(
164
+ 'core/test-block-with-array-of-strings'
165
+ );
166
+ expect( blocks[ 0 ].attributes.items ).toEqual( [
167
+ 'apples',
168
+ null,
169
+ 'oranges',
170
+ ] );
171
+
172
+ // Add a block with content that will match against footnotes logic, causing
173
+ // `updateFootnotes` to iterate over blocks and their attributes.
174
+ act( () => {
175
+ onChange(
176
+ [
177
+ ...blocks,
178
+ createBlock( 'core/test-block', {
179
+ content:
180
+ '<p><sup data-fn="1234" class="fn"><a href="#1234" id="1234-link">1</a></sup></p>',
181
+ } ),
182
+ ],
183
+ {
184
+ selection: {
185
+ selectionStart: {},
186
+ selectionEnd: {},
187
+ initialPosition: {},
188
+ },
189
+ }
190
+ );
191
+ } );
192
+
193
+ // Ensure the first block remains the same, with unaltered attributes.
194
+ expect( blocks[ 0 ].name ).toEqual(
195
+ 'core/test-block-with-array-of-strings'
196
+ );
197
+ expect( blocks[ 0 ].attributes.items ).toEqual( [
198
+ 'apples',
199
+ null,
200
+ 'oranges',
201
+ ] );
202
+ } );
203
+
204
+ it( 'updates the order of footnotes when a new footnote is inserted', async () => {
205
+ // Start with a post containing a block with a single footnote (set to 1).
206
+ registry
207
+ .dispatch( coreDataStore )
208
+ .receiveEntityRecords( 'postType', 'post', [
209
+ {
210
+ id: 1,
211
+ type: 'post',
212
+ content: {
213
+ raw: '<!-- wp:test-block --><p>A paragraph<sup data-fn="abcd" class="fn"><a href="#abcd" id="abcd-link">1</a></sup></p><!-- /wp:test-block -->',
214
+ rendered: '<p>A paragraph</p>',
215
+ },
216
+ meta: {
217
+ footnotes: '[]',
218
+ },
219
+ },
220
+ ] );
221
+
222
+ let blocks, onChange;
223
+
224
+ const TestComponent = () => {
225
+ [ blocks, , onChange ] = useEntityBlockEditor( 'postType', 'post', {
226
+ id: 1,
227
+ } );
228
+
229
+ return <div />;
230
+ };
231
+
232
+ render(
233
+ <RegistryProvider value={ registry }>
234
+ <TestComponent />
235
+ </RegistryProvider>
236
+ );
237
+
238
+ // The first block should have the footnote number 1.
239
+ expect( blocks[ 0 ].attributes.content ).toEqual(
240
+ 'A paragraph<sup data-fn="abcd" class="fn"><a href="#abcd" id="abcd-link">1</a></sup>'
241
+ );
242
+
243
+ // Add a block with a new footnote with an arbitrary footnote number that will be overwritten after insertion.
244
+ act( () => {
245
+ onChange(
246
+ [
247
+ createBlock( 'core/test-block', {
248
+ content:
249
+ 'A new paragraph<sup data-fn="xyz" class="xyz"><a href="#xyz" id="xyz-link">999</a></sup>',
250
+ } ),
251
+ ...blocks,
252
+ ],
253
+ {
254
+ selection: {
255
+ selectionStart: {},
256
+ selectionEnd: {},
257
+ initialPosition: {},
258
+ },
259
+ }
260
+ );
261
+ } );
262
+
263
+ // The newly inserted block should have the footnote number 1, and the
264
+ // existing footnote number 1 should be updated to 2.
265
+ expect( blocks[ 0 ].attributes.content ).toEqual(
266
+ 'A new paragraph<sup data-fn="xyz" class="xyz"><a href="#xyz" id="xyz-link">1</a></sup>'
267
+ );
268
+ expect( blocks[ 1 ].attributes.content ).toEqual(
269
+ 'A paragraph<sup data-fn="abcd" class="fn"><a href="#abcd" id="abcd-link">2</a></sup>'
270
+ );
271
+ } );
272
+ } );
package/tsconfig.json CHANGED
@@ -18,6 +18,7 @@
18
18
  { "path": "../i18n" },
19
19
  { "path": "../is-shallow-equal" },
20
20
  { "path": "../private-apis" },
21
+ { "path": "../sync" },
21
22
  { "path": "../url" }
22
23
  ],
23
24
  "include": [ "src/**/*" ]