@wordpress/editor 14.47.0 → 14.48.1

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 (213) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/build/components/block-removal-warnings/index.cjs +0 -3
  3. package/build/components/block-removal-warnings/index.cjs.map +2 -2
  4. package/build/components/collab-sidebar/note-indicator-toolbar.cjs +49 -43
  5. package/build/components/collab-sidebar/note-indicator-toolbar.cjs.map +3 -3
  6. package/build/components/collaborators-overlay/use-block-highlighting.cjs +1 -8
  7. package/build/components/collaborators-overlay/use-block-highlighting.cjs.map +3 -3
  8. package/build/components/collaborators-overlay/use-render-cursors.cjs +1 -7
  9. package/build/components/collaborators-overlay/use-render-cursors.cjs.map +3 -3
  10. package/build/components/more-menu/view-more-menu-group.cjs +1 -2
  11. package/build/components/more-menu/view-more-menu-group.cjs.map +2 -2
  12. package/build/components/page-attributes/parent.cjs +1 -0
  13. package/build/components/page-attributes/parent.cjs.map +2 -2
  14. package/build/components/post-publish-button/index.cjs +114 -157
  15. package/build/components/post-publish-button/index.cjs.map +3 -3
  16. package/build/components/post-revisions-preview/block-diff.cjs +21 -9
  17. package/build/components/post-revisions-preview/block-diff.cjs.map +2 -2
  18. package/build/components/post-revisions-preview/preserve-client-ids.cjs +2 -2
  19. package/build/components/post-revisions-preview/preserve-client-ids.cjs.map +2 -2
  20. package/build/components/provider/index.cjs +2 -0
  21. package/build/components/provider/index.cjs.map +3 -3
  22. package/build/components/provider/use-block-editor-settings.cjs +1 -1
  23. package/build/components/provider/use-block-editor-settings.cjs.map +2 -2
  24. package/build/components/provider/use-network-reconnect.cjs +51 -0
  25. package/build/components/provider/use-network-reconnect.cjs.map +7 -0
  26. package/build/components/revision-fields-diff/index.cjs +2 -2
  27. package/build/components/revision-fields-diff/index.cjs.map +2 -2
  28. package/build/components/sidebar/index.cjs +1 -4
  29. package/build/components/sidebar/index.cjs.map +2 -2
  30. package/build/components/template-actions-panel/block-theme-content.cjs +7 -1
  31. package/build/components/template-actions-panel/block-theme-content.cjs.map +2 -2
  32. package/build/components/upload-progress-snackbar/index.cjs +161 -0
  33. package/build/components/upload-progress-snackbar/index.cjs.map +7 -0
  34. package/build/components/upload-progress-snackbar/tracker.cjs +90 -0
  35. package/build/components/upload-progress-snackbar/tracker.cjs.map +7 -0
  36. package/build/private-apis.cjs +2 -0
  37. package/build/private-apis.cjs.map +3 -3
  38. package/build/store/selectors.cjs +1 -2
  39. package/build/store/selectors.cjs.map +2 -2
  40. package/build/utils/media-upload/index.cjs +16 -0
  41. package/build/utils/media-upload/index.cjs.map +3 -3
  42. package/build-module/components/block-removal-warnings/index.mjs +0 -3
  43. package/build-module/components/block-removal-warnings/index.mjs.map +2 -2
  44. package/build-module/components/collab-sidebar/note-indicator-toolbar.mjs +53 -44
  45. package/build-module/components/collab-sidebar/note-indicator-toolbar.mjs.map +2 -2
  46. package/build-module/components/collaborators-overlay/use-block-highlighting.mjs +1 -8
  47. package/build-module/components/collaborators-overlay/use-block-highlighting.mjs.map +2 -2
  48. package/build-module/components/collaborators-overlay/use-render-cursors.mjs +1 -7
  49. package/build-module/components/collaborators-overlay/use-render-cursors.mjs.map +2 -2
  50. package/build-module/components/more-menu/view-more-menu-group.mjs +1 -2
  51. package/build-module/components/more-menu/view-more-menu-group.mjs.map +2 -2
  52. package/build-module/components/page-attributes/parent.mjs +1 -0
  53. package/build-module/components/page-attributes/parent.mjs.map +2 -2
  54. package/build-module/components/post-publish-button/index.mjs +116 -159
  55. package/build-module/components/post-publish-button/index.mjs.map +2 -2
  56. package/build-module/components/post-revisions-preview/block-diff.mjs +20 -8
  57. package/build-module/components/post-revisions-preview/block-diff.mjs.map +2 -2
  58. package/build-module/components/post-revisions-preview/preserve-client-ids.mjs +1 -1
  59. package/build-module/components/post-revisions-preview/preserve-client-ids.mjs.map +1 -1
  60. package/build-module/components/provider/index.mjs +2 -0
  61. package/build-module/components/provider/index.mjs.map +2 -2
  62. package/build-module/components/provider/use-block-editor-settings.mjs +1 -1
  63. package/build-module/components/provider/use-block-editor-settings.mjs.map +2 -2
  64. package/build-module/components/provider/use-network-reconnect.mjs +30 -0
  65. package/build-module/components/provider/use-network-reconnect.mjs.map +7 -0
  66. package/build-module/components/revision-fields-diff/index.mjs +2 -2
  67. package/build-module/components/revision-fields-diff/index.mjs.map +2 -2
  68. package/build-module/components/sidebar/index.mjs +2 -11
  69. package/build-module/components/sidebar/index.mjs.map +2 -2
  70. package/build-module/components/template-actions-panel/block-theme-content.mjs +7 -1
  71. package/build-module/components/template-actions-panel/block-theme-content.mjs.map +2 -2
  72. package/build-module/components/upload-progress-snackbar/index.mjs +135 -0
  73. package/build-module/components/upload-progress-snackbar/index.mjs.map +7 -0
  74. package/build-module/components/upload-progress-snackbar/tracker.mjs +61 -0
  75. package/build-module/components/upload-progress-snackbar/tracker.mjs.map +7 -0
  76. package/build-module/private-apis.mjs +2 -0
  77. package/build-module/private-apis.mjs.map +2 -2
  78. package/build-module/store/selectors.mjs +1 -2
  79. package/build-module/store/selectors.mjs.map +2 -2
  80. package/build-module/utils/media-upload/index.mjs +19 -0
  81. package/build-module/utils/media-upload/index.mjs.map +2 -2
  82. package/build-style/style-rtl.css +479 -84
  83. package/build-style/style.css +479 -84
  84. package/build-types/components/block-removal-warnings/index.d.ts.map +1 -1
  85. package/build-types/components/collab-sidebar/add-comment.d.ts +6 -0
  86. package/build-types/components/collab-sidebar/add-comment.d.ts.map +1 -0
  87. package/build-types/components/collab-sidebar/comment-author-info.d.ts +8 -0
  88. package/build-types/components/collab-sidebar/comment-author-info.d.ts.map +1 -0
  89. package/build-types/components/collab-sidebar/comment-form.d.ts +9 -0
  90. package/build-types/components/collab-sidebar/comment-form.d.ts.map +1 -0
  91. package/build-types/components/collab-sidebar/comment-indicator-toolbar.d.ts +6 -0
  92. package/build-types/components/collab-sidebar/comment-indicator-toolbar.d.ts.map +1 -0
  93. package/build-types/components/collab-sidebar/comment-menu-item.d.ts +6 -0
  94. package/build-types/components/collab-sidebar/comment-menu-item.d.ts.map +1 -0
  95. package/build-types/components/collab-sidebar/comments.d.ts +10 -0
  96. package/build-types/components/collab-sidebar/comments.d.ts.map +1 -0
  97. package/build-types/components/collab-sidebar/note-indicator-toolbar.d.ts.map +1 -1
  98. package/build-types/components/collaborators-overlay/use-block-highlighting.d.ts +0 -3
  99. package/build-types/components/collaborators-overlay/use-block-highlighting.d.ts.map +1 -1
  100. package/build-types/components/collaborators-overlay/use-render-cursors.d.ts.map +1 -1
  101. package/build-types/components/document-bar/index.d.ts +2 -2
  102. package/build-types/components/document-bar/index.d.ts.map +1 -1
  103. package/build-types/components/global-styles-provider/index.d.ts +16 -0
  104. package/build-types/components/global-styles-provider/index.d.ts.map +1 -0
  105. package/build-types/components/media/index.d.ts +3 -0
  106. package/build-types/components/media/index.d.ts.map +1 -0
  107. package/build-types/components/media/metadata-panel.d.ts +12 -0
  108. package/build-types/components/media/metadata-panel.d.ts.map +1 -0
  109. package/build-types/components/media/preview.d.ts +9 -0
  110. package/build-types/components/media/preview.d.ts.map +1 -0
  111. package/build-types/components/more-menu/view-more-menu-group.d.ts.map +1 -1
  112. package/build-types/components/page-attributes/parent.d.ts.map +1 -1
  113. package/build-types/components/post-locked-modal/index.d.ts +6 -1
  114. package/build-types/components/post-publish-button/index.d.ts +9 -9
  115. package/build-types/components/post-publish-button/index.d.ts.map +1 -1
  116. package/build-types/components/post-revisions-preview/block-diff.d.ts +3 -0
  117. package/build-types/components/post-revisions-preview/block-diff.d.ts.map +1 -1
  118. package/build-types/components/post-taxonomies/flat-term-selector.d.ts +6 -1
  119. package/build-types/components/post-taxonomies/hierarchical-term-selector.d.ts +6 -1
  120. package/build-types/components/post-text-editor/index.d.ts +1 -1
  121. package/build-types/components/post-text-editor/index.d.ts.map +1 -1
  122. package/build-types/components/post-text-editor/utils.d.ts +29 -0
  123. package/build-types/components/post-text-editor/utils.d.ts.map +1 -0
  124. package/build-types/components/provider/index.d.ts.map +1 -1
  125. package/build-types/components/provider/use-network-reconnect.d.ts +8 -0
  126. package/build-types/components/provider/use-network-reconnect.d.ts.map +1 -0
  127. package/build-types/components/revision-fields-diff/index.d.ts +3 -0
  128. package/build-types/components/revision-fields-diff/index.d.ts.map +1 -1
  129. package/build-types/components/sidebar/index.d.ts.map +1 -1
  130. package/build-types/components/template-actions-panel/block-theme-content.d.ts.map +1 -1
  131. package/build-types/components/upload-progress-snackbar/index.d.ts +19 -0
  132. package/build-types/components/upload-progress-snackbar/index.d.ts.map +1 -0
  133. package/build-types/components/upload-progress-snackbar/stories/index.story.d.ts +28 -0
  134. package/build-types/components/upload-progress-snackbar/stories/index.story.d.ts.map +1 -0
  135. package/build-types/components/upload-progress-snackbar/tracker.d.ts +41 -0
  136. package/build-types/components/upload-progress-snackbar/tracker.d.ts.map +1 -0
  137. package/build-types/private-apis.d.ts.map +1 -1
  138. package/build-types/store/selectors.d.ts.map +1 -1
  139. package/build-types/utils/get-template-part-icon.d.ts.map +1 -1
  140. package/build-types/utils/media-upload/index.d.ts.map +1 -1
  141. package/package.json +55 -52
  142. package/src/components/README.md +1 -1
  143. package/src/components/block-removal-warnings/index.js +0 -7
  144. package/src/components/collab-sidebar/note-indicator-toolbar.js +73 -60
  145. package/src/components/collaborators-overlay/use-block-highlighting.ts +0 -9
  146. package/src/components/collaborators-overlay/use-render-cursors.ts +0 -8
  147. package/src/components/collaborators-presence/avatar/test/index.tsx +8 -3
  148. package/src/components/more-menu/view-more-menu-group.js +1 -2
  149. package/src/components/page-attributes/parent.js +1 -0
  150. package/src/components/post-publish-button/index.js +143 -192
  151. package/src/components/post-publish-button/test/index.js +137 -114
  152. package/src/components/post-revisions-preview/block-diff.js +63 -19
  153. package/src/components/post-revisions-preview/preserve-client-ids.js +1 -1
  154. package/src/components/post-revisions-preview/test/block-diff.js +109 -6
  155. package/src/components/provider/index.js +4 -0
  156. package/src/components/provider/test/use-network-reconnect.js +137 -0
  157. package/src/components/provider/use-block-editor-settings.js +2 -2
  158. package/src/components/provider/use-network-reconnect.js +44 -0
  159. package/src/components/revision-fields-diff/index.js +7 -2
  160. package/src/components/sidebar/index.js +2 -11
  161. package/src/components/template-actions-panel/block-theme-content.js +10 -1
  162. package/src/components/upload-progress-snackbar/README.md +26 -0
  163. package/src/components/upload-progress-snackbar/index.js +216 -0
  164. package/src/components/upload-progress-snackbar/stories/index.story.tsx +85 -0
  165. package/src/components/upload-progress-snackbar/style.scss +30 -0
  166. package/src/components/upload-progress-snackbar/test/index.js +199 -0
  167. package/src/components/upload-progress-snackbar/tracker.js +105 -0
  168. package/src/private-apis.js +2 -0
  169. package/src/store/selectors.js +1 -3
  170. package/src/style.scss +1 -0
  171. package/src/utils/media-upload/index.js +27 -0
  172. package/src/components/commands/index.native.js +0 -2
  173. package/src/components/deprecated.native.js +0 -47
  174. package/src/components/editor-help/add-blocks.native.js +0 -40
  175. package/src/components/editor-help/customize-blocks.native.js +0 -40
  176. package/src/components/editor-help/help-detail-navigation-screen.native.js +0 -67
  177. package/src/components/editor-help/help-get-support-button.native.js +0 -38
  178. package/src/components/editor-help/help-section-title.native.js +0 -29
  179. package/src/components/editor-help/help-topic-row.native.js +0 -33
  180. package/src/components/editor-help/icon-move-blocks.native.js +0 -10
  181. package/src/components/editor-help/index.native.js +0 -208
  182. package/src/components/editor-help/intro-to-blocks.native.js +0 -91
  183. package/src/components/editor-help/move-blocks.native.js +0 -55
  184. package/src/components/editor-help/remove-blocks.native.js +0 -35
  185. package/src/components/editor-help/style.android.scss +0 -6
  186. package/src/components/editor-help/style.ios.scss +0 -6
  187. package/src/components/editor-help/test/index.native.js +0 -81
  188. package/src/components/editor-help/view-sections.native.js +0 -79
  189. package/src/components/error-boundary/index.native.js +0 -192
  190. package/src/components/error-boundary/style.native.scss +0 -116
  191. package/src/components/index.native.js +0 -15
  192. package/src/components/offline-status/index.native.js +0 -99
  193. package/src/components/offline-status/style.native.scss +0 -28
  194. package/src/components/offline-status/test/index.native.js +0 -108
  195. package/src/components/post-title/index.native.js +0 -282
  196. package/src/components/post-title/style.native.scss +0 -13
  197. package/src/components/post-title/test/__snapshots__/index.native.js.snap +0 -25
  198. package/src/components/post-title/test/index.native.js +0 -78
  199. package/src/components/provider/index.native.js +0 -497
  200. package/src/components/provider/use-block-editor-settings.native.js +0 -48
  201. package/src/components/template-part-menu-items/index.native.js +0 -3
  202. package/src/hooks/index.native.js +0 -0
  203. package/src/index.native.js +0 -16
  204. package/src/private-apis.native.js +0 -33
  205. package/src/store/actions.native.js +0 -27
  206. package/src/store/reducer.native.js +0 -94
  207. package/src/store/selectors.native.js +0 -57
  208. package/src/store/test/actions.native.js +0 -16
  209. package/src/store/test/reducer.native.js +0 -36
  210. package/src/store/test/selectors.native.js +0 -28
  211. package/src/utils/index.native.js +0 -6
  212. package/src/utils/media-sideload/index.native.js +0 -1
  213. package/src/utils/media-upload/index.native.js +0 -1
@@ -4,186 +4,209 @@
4
4
  import { render, screen } from '@testing-library/react';
5
5
  import userEvent from '@testing-library/user-event';
6
6
 
7
+ /**
8
+ * WordPress dependencies
9
+ */
10
+ import { dispatch, select } from '@wordpress/data';
11
+
7
12
  /**
8
13
  * Internal dependencies
9
14
  */
10
- import { PostPublishButton } from '../';
15
+ import PostPublishButton from '../';
16
+ import { store as editorStore } from '../../../store';
11
17
 
12
18
  describe( 'PostPublishButton', () => {
19
+ beforeEach( () => {
20
+ jest.spyOn( select( editorStore ), 'getCurrentPost' ).mockReturnValue( {
21
+ _links: {},
22
+ } );
23
+ jest.spyOn( dispatch( editorStore ), 'editPost' ).mockReturnValue();
24
+ jest.spyOn( dispatch( editorStore ), 'savePost' ).mockReturnValue();
25
+ } );
26
+
27
+ afterEach( () => {
28
+ jest.restoreAllMocks();
29
+ } );
30
+
31
+ function mockSelector( name, value ) {
32
+ jest.spyOn( select( editorStore ), name ).mockReturnValue( value );
33
+ }
34
+
35
+ function mockHasPublishAction( hasPublishAction ) {
36
+ jest.spyOn( select( editorStore ), 'getCurrentPost' ).mockReturnValue( {
37
+ _links: hasPublishAction ? { 'wp:action-publish': true } : {},
38
+ } );
39
+ }
40
+
13
41
  describe( 'aria-disabled', () => {
14
42
  it( 'should be true if post is currently saving', () => {
15
- render( <PostPublishButton isPublishable isSaveable isSaving /> );
43
+ mockSelector( 'isEditedPostPublishable', true );
44
+ mockSelector( 'isEditedPostSaveable', true );
45
+ mockSelector( 'isSavingPost', true );
46
+
47
+ render( <PostPublishButton /> );
16
48
 
17
- expect(
18
- screen.getByRole( 'button', { name: 'Submit for Review' } )
19
- ).toHaveAttribute( 'aria-disabled', 'true' );
49
+ expect( screen.getByRole( 'button' ) ).toHaveAttribute(
50
+ 'aria-disabled',
51
+ 'true'
52
+ );
20
53
  } );
21
54
 
22
55
  it( 'should be true if post is not publishable and not forceIsDirty', () => {
23
- render(
24
- <PostPublishButton
25
- isSaveable
26
- isPublishable={ false }
27
- forceIsDirty={ false }
28
- />
29
- );
56
+ mockSelector( 'isEditedPostSaveable', true );
57
+ mockSelector( 'isEditedPostPublishable', false );
58
+
59
+ render( <PostPublishButton forceIsDirty={ false } /> );
30
60
 
31
- expect(
32
- screen.getByRole( 'button', { name: 'Submit for Review' } )
33
- ).toHaveAttribute( 'aria-disabled', 'true' );
61
+ expect( screen.getByRole( 'button' ) ).toHaveAttribute(
62
+ 'aria-disabled',
63
+ 'true'
64
+ );
34
65
  } );
35
66
 
36
67
  it( 'should be true if post is not saveable', () => {
37
- render( <PostPublishButton isPublishable isSaveable={ false } /> );
68
+ mockSelector( 'isEditedPostPublishable', true );
69
+ mockSelector( 'isEditedPostSaveable', false );
70
+
71
+ render( <PostPublishButton /> );
38
72
 
39
- expect(
40
- screen.getByRole( 'button', { name: 'Submit for Review' } )
41
- ).toHaveAttribute( 'aria-disabled', 'true' );
73
+ expect( screen.getByRole( 'button' ) ).toHaveAttribute(
74
+ 'aria-disabled',
75
+ 'true'
76
+ );
42
77
  } );
43
78
 
44
79
  it( 'should be true if post saving is locked', () => {
45
- render(
46
- <PostPublishButton
47
- isPublishable
48
- isSaveable
49
- isPostSavingLocked
50
- />
51
- );
80
+ mockSelector( 'isEditedPostPublishable', true );
81
+ mockSelector( 'isEditedPostSaveable', true );
82
+ mockSelector( 'isPostSavingLocked', true );
83
+
84
+ render( <PostPublishButton /> );
52
85
 
53
- expect(
54
- screen.getByRole( 'button', { name: 'Submit for Review' } )
55
- ).toHaveAttribute( 'aria-disabled', 'true' );
86
+ expect( screen.getByRole( 'button' ) ).toHaveAttribute(
87
+ 'aria-disabled',
88
+ 'true'
89
+ );
56
90
  } );
57
91
 
58
92
  it( 'should be false if post is saveable but not publishable and forceIsDirty is true', () => {
59
- render(
60
- <PostPublishButton
61
- isSaveable
62
- isPublishable={ false }
63
- forceIsDirty
64
- />
65
- );
93
+ mockSelector( 'isEditedPostSaveable', true );
94
+ mockSelector( 'isEditedPostPublishable', false );
95
+
96
+ render( <PostPublishButton forceIsDirty /> );
66
97
 
67
- expect(
68
- screen.getByRole( 'button', { name: 'Submit for Review' } )
69
- ).toHaveAttribute( 'aria-disabled', 'false' );
98
+ expect( screen.getByRole( 'button' ) ).toHaveAttribute(
99
+ 'aria-disabled',
100
+ 'false'
101
+ );
70
102
  } );
71
103
 
72
104
  it( 'should be false if post is publishave and saveable', () => {
73
- render( <PostPublishButton isPublishable isSaveable /> );
105
+ mockSelector( 'isEditedPostPublishable', true );
106
+ mockSelector( 'isEditedPostSaveable', true );
74
107
 
75
- expect(
76
- screen.getByRole( 'button', { name: 'Submit for Review' } )
77
- ).toHaveAttribute( 'aria-disabled', 'false' );
108
+ render( <PostPublishButton /> );
109
+
110
+ expect( screen.getByRole( 'button' ) ).toHaveAttribute(
111
+ 'aria-disabled',
112
+ 'false'
113
+ );
78
114
  } );
79
115
  } );
80
116
 
81
117
  describe( 'publish status', () => {
82
118
  it( 'should be pending for contributor', async () => {
83
119
  const user = userEvent.setup();
84
- const savePostStatus = jest.fn();
85
- render(
86
- <PostPublishButton
87
- hasPublishAction={ false }
88
- savePostStatus={ savePostStatus }
89
- isSaveable
90
- isPublishable
91
- />
92
- );
120
+ mockHasPublishAction( false );
121
+ mockSelector( 'isEditedPostSaveable', true );
122
+ mockSelector( 'isEditedPostPublishable', true );
93
123
 
94
- await user.click(
95
- screen.getByRole( 'button', { name: 'Submit for Review' } )
96
- );
124
+ render( <PostPublishButton /> );
125
+
126
+ await user.click( screen.getByRole( 'button' ) );
97
127
 
98
- expect( savePostStatus ).toHaveBeenCalledWith( 'pending' );
128
+ expect( dispatch( editorStore ).editPost ).toHaveBeenCalledWith(
129
+ { status: 'pending' },
130
+ { undoIgnore: true }
131
+ );
99
132
  } );
100
133
 
101
134
  it( 'should be future for scheduled post', async () => {
102
135
  const user = userEvent.setup();
103
- const savePostStatus = jest.fn();
104
- render(
105
- <PostPublishButton
106
- hasPublishAction
107
- savePostStatus={ savePostStatus }
108
- isBeingScheduled
109
- isSaveable
110
- isPublishable
111
- />
112
- );
136
+ mockHasPublishAction( true );
137
+ mockSelector( 'isEditedPostBeingScheduled', true );
138
+ mockSelector( 'isEditedPostSaveable', true );
139
+ mockSelector( 'isEditedPostPublishable', true );
113
140
 
114
- await user.click(
115
- screen.getByRole( 'button', { name: 'Submit for Review' } )
116
- );
141
+ render( <PostPublishButton /> );
142
+
143
+ await user.click( screen.getByRole( 'button' ) );
117
144
 
118
- expect( savePostStatus ).toHaveBeenCalledWith( 'future' );
145
+ expect( dispatch( editorStore ).editPost ).toHaveBeenCalledWith(
146
+ { status: 'future' },
147
+ { undoIgnore: true }
148
+ );
119
149
  } );
120
150
 
121
151
  it( 'should be private for private visibility', async () => {
122
152
  const user = userEvent.setup();
123
- const savePostStatus = jest.fn();
124
- render(
125
- <PostPublishButton
126
- hasPublishAction
127
- savePostStatus={ savePostStatus }
128
- visibility="private"
129
- isSaveable
130
- isPublishable
131
- />
132
- );
153
+ mockHasPublishAction( true );
154
+ mockSelector( 'getEditedPostVisibility', 'private' );
155
+ mockSelector( 'isEditedPostSaveable', true );
156
+ mockSelector( 'isEditedPostPublishable', true );
133
157
 
134
- await user.click(
135
- screen.getByRole( 'button', { name: 'Submit for Review' } )
136
- );
158
+ render( <PostPublishButton /> );
137
159
 
138
- expect( savePostStatus ).toHaveBeenCalledWith( 'private' );
160
+ await user.click( screen.getByRole( 'button' ) );
161
+
162
+ expect( dispatch( editorStore ).editPost ).toHaveBeenCalledWith(
163
+ { status: 'private' },
164
+ { undoIgnore: true }
165
+ );
139
166
  } );
140
167
 
141
168
  it( 'should be publish otherwise', async () => {
142
169
  const user = userEvent.setup();
143
- const savePostStatus = jest.fn();
144
- render(
145
- <PostPublishButton
146
- hasPublishAction
147
- savePostStatus={ savePostStatus }
148
- isSaveable
149
- isPublishable
150
- />
151
- );
170
+ mockHasPublishAction( true );
171
+ mockSelector( 'isEditedPostSaveable', true );
172
+ mockSelector( 'isEditedPostPublishable', true );
152
173
 
153
- await user.click(
154
- screen.getByRole( 'button', { name: 'Submit for Review' } )
155
- );
174
+ render( <PostPublishButton /> );
175
+
176
+ await user.click( screen.getByRole( 'button' ) );
156
177
 
157
- expect( savePostStatus ).toHaveBeenCalledWith( 'publish' );
178
+ expect( dispatch( editorStore ).editPost ).toHaveBeenCalledWith(
179
+ { status: 'publish' },
180
+ { undoIgnore: true }
181
+ );
158
182
  } );
159
183
  } );
160
184
 
161
185
  describe( 'click', () => {
162
186
  it( 'should save with status', async () => {
163
187
  const user = userEvent.setup();
164
- const savePostStatus = jest.fn();
165
- render(
166
- <PostPublishButton
167
- hasPublishAction
168
- savePostStatus={ savePostStatus }
169
- isSaveable
170
- isPublishable
171
- />
172
- );
188
+ mockHasPublishAction( true );
189
+ mockSelector( 'isEditedPostSaveable', true );
190
+ mockSelector( 'isEditedPostPublishable', true );
173
191
 
174
- await user.click(
175
- screen.getByRole( 'button', { name: 'Submit for Review' } )
176
- );
192
+ render( <PostPublishButton /> );
193
+
194
+ await user.click( screen.getByRole( 'button' ) );
177
195
 
178
- expect( savePostStatus ).toHaveBeenCalledWith( 'publish' );
196
+ expect( dispatch( editorStore ).editPost ).toHaveBeenCalledWith(
197
+ { status: 'publish' },
198
+ { undoIgnore: true }
199
+ );
200
+ expect( dispatch( editorStore ).savePost ).toHaveBeenCalled();
179
201
  } );
180
202
  } );
181
203
 
182
204
  it( 'should have save modifier class', () => {
183
- render( <PostPublishButton isSaving isPublished /> );
205
+ mockSelector( 'isSavingPost', true );
206
+ mockSelector( 'isCurrentPostPublished', true );
207
+
208
+ render( <PostPublishButton /> );
184
209
 
185
- expect(
186
- screen.getByRole( 'button', { name: 'Submit for Review' } )
187
- ).toHaveClass( 'is-busy' );
210
+ expect( screen.getByRole( 'button' ) ).toHaveClass( 'is-busy' );
188
211
  } );
189
212
  } );
@@ -1,8 +1,12 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import { diffArrays } from 'diff/lib/diff/array';
5
- import { diffWords } from 'diff/lib/diff/word';
4
+ /*
5
+ * `diffWordsWithSpace` preserves the v4-style per-word output. v6+
6
+ * stopped treating whitespace as a token in `diffWords`, which coalesces
7
+ * adjacent word changes into a single removed/added pair.
8
+ */
9
+ import { diffArrays, diffWordsWithSpace } from 'diff';
6
10
 
7
11
  /**
8
12
  * WordPress dependencies
@@ -28,6 +32,26 @@ import { unlock } from '../../lock-unlock';
28
32
 
29
33
  const { parseRawBlock } = unlock( blocksPrivateApis );
30
34
 
35
+ /**
36
+ * Whether a grammar-parsed raw block is a whitespace-only freeform pseudo-block
37
+ * (the `\n\n` between block markers, etc). These are stripped from both arrays
38
+ * before LCS to keep the matching pivot stable: under `diff` v6's tie-breaker,
39
+ * a whitespace block could otherwise be selected as the LCS anchor in
40
+ * `[paragraph, whitespace, paragraph]` swaps, mis-pairing the surrounding
41
+ * paragraphs in `pairSimilarBlocks`. Whitespace pseudo-blocks don't render
42
+ * anyway (`parseRawBlock` returns undefined for them), so dropping them
43
+ * before the diff has no user-visible effect.
44
+ *
45
+ * @param {Object} rawBlock A raw block from `@wordpress/block-serialization-default-parser`.
46
+ * @return {boolean} True if the block should be excluded from LCS matching.
47
+ */
48
+ function isWhitespaceRawBlock( rawBlock ) {
49
+ return (
50
+ rawBlock.blockName === null &&
51
+ ( ! rawBlock.innerHTML || ! rawBlock.innerHTML.trim() )
52
+ );
53
+ }
54
+
31
55
  /**
32
56
  * Safely stringifies a value for display and comparison.
33
57
  *
@@ -233,27 +257,34 @@ function pairSimilarBlocks( blocks ) {
233
257
  };
234
258
 
235
259
  // Decide where to place the modified block by checking
236
- // what's between the removed and added positions.
237
- // If there are unpaired added blocks between them,
238
- // placing at the removed position would put the modified
239
- // block before content that comes before it in the
240
- // current revision so use the added position.
241
- // Otherwise, use the removed position to keep the
242
- // previous revision's order intact.
260
+ // what's between the removed and added positions. If any
261
+ // block between them is in the current revision (an
262
+ // unchanged block, or an unpaired added block), placing
263
+ // the modification at the removed position would put it
264
+ // before content that already comes before it in the
265
+ // current revision — so use the added position instead.
266
+ // Otherwise, use the removed position to keep the previous
267
+ // revision's reading order intact.
268
+ //
269
+ // 'removed' blocks (and added blocks already absorbed via
270
+ // `pairedAdded`) aren't checked because they aren't in the
271
+ // current revision and so don't count as crossing it.
243
272
  const lo = Math.min( rem.index, bestMatch.index );
244
273
  const hi = Math.max( rem.index, bestMatch.index );
245
- let hasAddedBetween = false;
274
+ let crossesCurrentContent = false;
246
275
  for ( let i = lo + 1; i < hi; i++ ) {
247
- if (
248
- blocks[ i ].__revisionDiffStatus?.status === 'added' &&
249
- ! pairedAdded.has( i )
250
- ) {
251
- hasAddedBetween = true;
276
+ const status = blocks[ i ].__revisionDiffStatus?.status;
277
+ if ( status === undefined ) {
278
+ crossesCurrentContent = true;
279
+ break;
280
+ }
281
+ if ( status === 'added' && ! pairedAdded.has( i ) ) {
282
+ crossesCurrentContent = true;
252
283
  break;
253
284
  }
254
285
  }
255
286
 
256
- if ( hasAddedBetween ) {
287
+ if ( crossesCurrentContent ) {
257
288
  // Use the added position — don't jump before
258
289
  // current-revision content.
259
290
  modifications.set( bestMatch.index, modifiedBlock );
@@ -287,11 +318,21 @@ function pairSimilarBlocks( blocks ) {
287
318
  * Detects modifications when exactly 1 block is removed and 1 is added
288
319
  * with the same blockName (1:1 replacement = modification).
289
320
  *
321
+ * Whitespace-only freeform pseudo-blocks are filtered at every recursive
322
+ * level so this function is safe to call directly with raw output from
323
+ * `@wordpress/block-serialization-default-parser`. The duplicate work for
324
+ * inner-block recursion is negligible and keeps the contract self-contained.
325
+ *
290
326
  * @param {Array} currentRaw Current revision's raw blocks.
291
327
  * @param {Array} previousRaw Previous revision's raw blocks.
292
328
  * @return {Array} Merged raw blocks with diff status injected.
293
329
  */
294
330
  function diffRawBlocks( currentRaw, previousRaw ) {
331
+ // Strip whitespace-only freeform pseudo-blocks before LCS — see
332
+ // `isWhitespaceRawBlock` for why.
333
+ currentRaw = currentRaw.filter( ( b ) => ! isWhitespaceRawBlock( b ) );
334
+ previousRaw = previousRaw.filter( ( b ) => ! isWhitespaceRawBlock( b ) );
335
+
295
336
  const createBlockSignature = ( rawBlock ) =>
296
337
  JSON.stringify( {
297
338
  name: rawBlock.blockName,
@@ -502,8 +543,8 @@ function applyRichTextDiff( currentRichText, previousRichText ) {
502
543
  const currentText = currentRichText.toPlainText();
503
544
  const previousText = previousRichText.toPlainText();
504
545
 
505
- // Diff the plain text (words for cleaner output)
506
- const textDiff = diffWords( previousText, currentText );
546
+ // Diff the plain text (words for cleaner output).
547
+ const textDiff = diffWordsWithSpace( previousText, currentText );
507
548
 
508
549
  let result = create( { text: '' } );
509
550
  let currentIdx = 0;
@@ -660,7 +701,10 @@ function applyDiffToBlock( currentBlock, previousBlock, diffStatus ) {
660
701
  previousBlock.attributes[ attrName ]
661
702
  );
662
703
  if ( currStr !== prevStr ) {
663
- changedAttributes[ attrName ] = diffWords( prevStr, currStr );
704
+ changedAttributes[ attrName ] = diffWordsWithSpace(
705
+ prevStr,
706
+ currStr
707
+ );
664
708
  }
665
709
  }
666
710
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import { diffArrays } from 'diff/lib/diff/array';
4
+ import { diffArrays } from 'diff';
5
5
 
6
6
  /**
7
7
  * Preserves clientIds from previously rendered blocks to prevent flashing.
@@ -339,30 +339,32 @@ describe( 'diffRevisionContent', () => {
339
339
  ] );
340
340
  const blocks = diffRevisionContent( current, previous );
341
341
 
342
- // LCS matches one block ("First block content" at prev[0] -> curr[1]).
342
+ // LCS matches one block ("Second block content" at prev[1] -> curr[0]).
343
343
  // The other block appears as removed + added (showing the reorder).
344
344
  // We intentionally don't pair identical blocks as "modified" since
345
345
  // there's no actual content change - just a position change.
346
+ // (Pre-v8, LCS matched the other block. Both are equally-valid
347
+ // choices for a pure swap.)
346
348
  expect( normalizeBlockTree( blocks ) ).toMatchObject( [
347
349
  {
348
350
  name: 'core/paragraph',
349
351
  attributes: {
350
- content: 'Second block content',
351
- __revisionDiffStatus: { status: 'added' },
352
+ content: 'First block content',
353
+ __revisionDiffStatus: { status: 'removed' },
352
354
  },
353
355
  },
354
356
  {
355
357
  name: 'core/paragraph',
356
358
  attributes: {
357
- content: 'First block content',
359
+ content: 'Second block content',
358
360
  __revisionDiffStatus: undefined,
359
361
  },
360
362
  },
361
363
  {
362
364
  name: 'core/paragraph',
363
365
  attributes: {
364
- content: 'Second block content',
365
- __revisionDiffStatus: { status: 'removed' },
366
+ content: 'First block content',
367
+ __revisionDiffStatus: { status: 'added' },
366
368
  },
367
369
  },
368
370
  ] );
@@ -441,6 +443,107 @@ describe( 'diffRevisionContent', () => {
441
443
  ] );
442
444
  } );
443
445
 
446
+ it( 'filters whitespace-only freeform pseudo-blocks before LCS', () => {
447
+ /*
448
+ * Direct canary for the whitespace-pseudo-block filter in
449
+ * `diffRawBlocks`. The grammar parser emits
450
+ * `{ blockName: null, innerHTML: '\n\n' }` for the whitespace
451
+ * between block markers; under `diff` v6+'s LCS tie-breaker,
452
+ * those pseudo-blocks would otherwise be selected as the match
453
+ * anchor in [paragraph, whitespace, paragraph] swaps, leaving
454
+ * `pairSimilarBlocks` with two removed and two added paragraphs
455
+ * to mis-match by similarity. With the filter, the LCS picks a
456
+ * content block and the surrounding paragraphs pair cleanly.
457
+ */
458
+ const previous = serialize( [
459
+ createBlock( 'core/paragraph', { content: 'Alpha content' } ),
460
+ createBlock( 'core/paragraph', { content: 'Beta content' } ),
461
+ ] );
462
+ const current = serialize( [
463
+ createBlock( 'core/paragraph', {
464
+ content: 'Beta content modified',
465
+ } ),
466
+ createBlock( 'core/paragraph', { content: 'Alpha content' } ),
467
+ ] );
468
+ const blocks = diffRevisionContent( current, previous );
469
+ const normalized = normalizeBlockTree( blocks );
470
+
471
+ const statuses = normalized.map(
472
+ ( b ) => b.attributes.__revisionDiffStatus?.status
473
+ );
474
+ // Exactly one modified pair and one unchanged anchor — not the
475
+ // double-modified mis-pair that the unfiltered LCS would yield.
476
+ expect( statuses.filter( ( s ) => s === 'modified' ) ).toHaveLength(
477
+ 1
478
+ );
479
+ expect( statuses.filter( ( s ) => s === undefined ) ).toHaveLength( 1 );
480
+
481
+ const unchanged = normalized.find(
482
+ ( b ) => b.attributes.__revisionDiffStatus === undefined
483
+ );
484
+ expect( unchanged.attributes.content ).toBe( 'Alpha content' );
485
+ } );
486
+
487
+ it( 'places paired modification at current-revision position when only unchanged blocks sit between', () => {
488
+ /*
489
+ * Direct canary for the `crossesCurrentContent` "unchanged
490
+ * between removed and added" branch. The modified block crosses
491
+ * two unchanged paragraphs; the placement heuristic should
492
+ * anchor it at its current-revision position (index 0), not at
493
+ * the removed position (index 3) — otherwise the modified block
494
+ * would render after content that already comes before it in
495
+ * the current revision.
496
+ */
497
+ const previous = serialize( [
498
+ createBlock( 'core/paragraph', {
499
+ content: 'Stays one anchor sentence',
500
+ } ),
501
+ createBlock( 'core/paragraph', {
502
+ content: 'Stays two anchor sentence',
503
+ } ),
504
+ createBlock( 'core/paragraph', {
505
+ content: 'Original tail content sentence',
506
+ } ),
507
+ ] );
508
+ const current = serialize( [
509
+ createBlock( 'core/paragraph', {
510
+ content: 'Original tail content sentence rewritten',
511
+ } ),
512
+ createBlock( 'core/paragraph', {
513
+ content: 'Stays one anchor sentence',
514
+ } ),
515
+ createBlock( 'core/paragraph', {
516
+ content: 'Stays two anchor sentence',
517
+ } ),
518
+ ] );
519
+ const blocks = diffRevisionContent( current, previous );
520
+
521
+ expect( normalizeBlockTree( blocks ) ).toMatchObject( [
522
+ {
523
+ name: 'core/paragraph',
524
+ attributes: {
525
+ content:
526
+ 'Original tail content sentence<ins title="Added" class="revision-diff-added"> rewritten</ins>',
527
+ __revisionDiffStatus: { status: 'modified' },
528
+ },
529
+ },
530
+ {
531
+ name: 'core/paragraph',
532
+ attributes: {
533
+ content: 'Stays one anchor sentence',
534
+ __revisionDiffStatus: undefined,
535
+ },
536
+ },
537
+ {
538
+ name: 'core/paragraph',
539
+ attributes: {
540
+ content: 'Stays two anchor sentence',
541
+ __revisionDiffStatus: undefined,
542
+ },
543
+ },
544
+ ] );
545
+ } );
546
+
444
547
  describe( 'inner blocks', () => {
445
548
  it( 'handles deeply nested inner blocks', () => {
446
549
  const previous = serialize( [
@@ -36,6 +36,7 @@ import { useHideBlocksFromInserter } from './use-hide-blocks-from-inserter';
36
36
  import { useRevisionBlocks } from './use-revision-blocks';
37
37
  import useCommands from '../commands';
38
38
  import useUploadSaveLock from './use-upload-save-lock';
39
+ import useNetworkReconnect from './use-network-reconnect';
39
40
  import BlockRemovalWarnings from '../block-removal-warnings';
40
41
  import StartPageOptions from '../start-page-options';
41
42
  import KeyboardShortcutHelpModal from '../keyboard-shortcut-help-modal';
@@ -403,6 +404,9 @@ export const ExperimentalEditorProvider = withRegistryProvider(
403
404
  // Lock post saving when media uploads are in progress (experimental feature).
404
405
  useUploadSaveLock();
405
406
 
407
+ // Pause/resume media upload queue on network disconnect/reconnect.
408
+ useNetworkReconnect();
409
+
406
410
  if ( ! isReady || ! mode ) {
407
411
  return null;
408
412
  }