@wordpress/editor 14.41.1-next.v.202603102151.0 → 14.42.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 (301) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/build/components/collaborators-overlay/avatar-iframe-styles.cjs +12 -4
  3. package/build/components/collaborators-overlay/avatar-iframe-styles.cjs.map +2 -2
  4. package/build/components/collaborators-overlay/compute-selection.cjs +181 -0
  5. package/build/components/collaborators-overlay/compute-selection.cjs.map +7 -0
  6. package/build/components/collaborators-overlay/cursor-dom-utils.cjs +243 -0
  7. package/build/components/collaborators-overlay/cursor-dom-utils.cjs.map +7 -0
  8. package/build/components/collaborators-overlay/overlay-iframe-styles.cjs +6 -0
  9. package/build/components/collaborators-overlay/overlay-iframe-styles.cjs.map +2 -2
  10. package/build/components/collaborators-overlay/overlay.cjs +61 -37
  11. package/build/components/collaborators-overlay/overlay.cjs.map +2 -2
  12. package/build/components/collaborators-overlay/timing-utils.cjs +46 -0
  13. package/build/components/collaborators-overlay/timing-utils.cjs.map +7 -0
  14. package/build/components/collaborators-overlay/use-block-highlighting.cjs +5 -6
  15. package/build/components/collaborators-overlay/use-block-highlighting.cjs.map +2 -2
  16. package/build/components/collaborators-overlay/use-render-cursors.cjs +50 -140
  17. package/build/components/collaborators-overlay/use-render-cursors.cjs.map +3 -3
  18. package/build/components/collaborators-presence/index.cjs +38 -12
  19. package/build/components/collaborators-presence/index.cjs.map +2 -2
  20. package/build/components/collaborators-presence/list.cjs +27 -24
  21. package/build/components/collaborators-presence/list.cjs.map +2 -2
  22. package/build/components/collaborators-presence/use-collaborator-notifications.cjs +79 -107
  23. package/build/components/collaborators-presence/use-collaborator-notifications.cjs.map +3 -3
  24. package/build/components/editor-interface/index.cjs +9 -6
  25. package/build/components/editor-interface/index.cjs.map +2 -2
  26. package/build/components/inserter-sidebar/index.cjs +2 -1
  27. package/build/components/inserter-sidebar/index.cjs.map +2 -2
  28. package/build/components/page-attributes/parent.cjs +1 -1
  29. package/build/components/page-attributes/parent.cjs.map +2 -2
  30. package/build/components/post-locked-modal/index.cjs +16 -3
  31. package/build/components/post-locked-modal/index.cjs.map +2 -2
  32. package/build/components/post-revisions-preview/block-diff.cjs +39 -11
  33. package/build/components/post-revisions-preview/block-diff.cjs.map +2 -2
  34. package/build/components/post-revisions-preview/diff-markers.cjs +2 -2
  35. package/build/components/post-revisions-preview/diff-markers.cjs.map +2 -2
  36. package/build/components/post-revisions-preview/revisions-canvas.cjs +12 -75
  37. package/build/components/post-revisions-preview/revisions-canvas.cjs.map +3 -3
  38. package/build/components/post-revisions-preview/revisions-slider.cjs +5 -1
  39. package/build/components/post-revisions-preview/revisions-slider.cjs.map +2 -2
  40. package/build/components/post-template/block-theme.cjs +7 -4
  41. package/build/components/post-template/block-theme.cjs.map +2 -2
  42. package/build/components/post-template/hooks.cjs +39 -2
  43. package/build/components/post-template/hooks.cjs.map +2 -2
  44. package/build/components/post-template/panel.cjs +5 -42
  45. package/build/components/post-template/panel.cjs.map +3 -3
  46. package/build/components/preferences-modal/index.cjs +24 -0
  47. package/build/components/preferences-modal/index.cjs.map +2 -2
  48. package/build/components/provider/disable-non-page-content-blocks.cjs +31 -28
  49. package/build/components/provider/disable-non-page-content-blocks.cjs.map +3 -3
  50. package/build/components/provider/index.cjs +17 -5
  51. package/build/components/provider/index.cjs.map +2 -2
  52. package/build/components/provider/use-block-editor-settings.cjs +19 -5
  53. package/build/components/provider/use-block-editor-settings.cjs.map +3 -3
  54. package/build/components/provider/{use-post-content-blocks.cjs → use-post-content-block-types.cjs} +8 -19
  55. package/build/components/provider/use-post-content-block-types.cjs.map +7 -0
  56. package/build/components/provider/use-revision-blocks.cjs +106 -0
  57. package/build/components/provider/use-revision-blocks.cjs.map +7 -0
  58. package/build/components/revision-block-diff/index.cjs +84 -0
  59. package/build/components/revision-block-diff/index.cjs.map +7 -0
  60. package/build/components/sidebar/dataform-post-summary.cjs +36 -6
  61. package/build/components/sidebar/dataform-post-summary.cjs.map +2 -2
  62. package/build/components/sidebar/header.cjs +1 -1
  63. package/build/components/sidebar/header.cjs.map +2 -2
  64. package/build/components/sidebar/index.cjs +5 -1
  65. package/build/components/sidebar/index.cjs.map +3 -3
  66. package/build/components/{sync-connection-modal → sync-connection-error-modal}/index.cjs +90 -78
  67. package/build/components/sync-connection-error-modal/index.cjs.map +7 -0
  68. package/build/components/{sync-connection-modal → sync-connection-error-modal}/use-retry-countdown.cjs +14 -27
  69. package/build/components/sync-connection-error-modal/use-retry-countdown.cjs.map +7 -0
  70. package/build/components/template-content-panel/index.cjs +35 -31
  71. package/build/components/template-content-panel/index.cjs.map +3 -3
  72. package/build/components/visual-editor/index.cjs +2 -2
  73. package/build/components/visual-editor/index.cjs.map +2 -2
  74. package/build/store/actions.cjs +1 -3
  75. package/build/store/actions.cjs.map +2 -2
  76. package/build/store/private-actions.cjs +11 -2
  77. package/build/store/private-actions.cjs.map +2 -2
  78. package/build/store/private-selectors.cjs +52 -13
  79. package/build/store/private-selectors.cjs.map +2 -2
  80. package/build/store/reducer.cjs +12 -0
  81. package/build/store/reducer.cjs.map +2 -2
  82. package/build/utils/media-finalize/index.cjs +43 -0
  83. package/build/utils/media-finalize/index.cjs.map +7 -0
  84. package/build/utils/sync-error-messages.cjs +29 -16
  85. package/build/utils/sync-error-messages.cjs.map +3 -3
  86. package/build-module/components/collaborators-overlay/avatar-iframe-styles.mjs +12 -4
  87. package/build-module/components/collaborators-overlay/avatar-iframe-styles.mjs.map +2 -2
  88. package/build-module/components/collaborators-overlay/compute-selection.mjs +162 -0
  89. package/build-module/components/collaborators-overlay/compute-selection.mjs.map +7 -0
  90. package/build-module/components/collaborators-overlay/cursor-dom-utils.mjs +213 -0
  91. package/build-module/components/collaborators-overlay/cursor-dom-utils.mjs.map +7 -0
  92. package/build-module/components/collaborators-overlay/overlay-iframe-styles.mjs +6 -0
  93. package/build-module/components/collaborators-overlay/overlay-iframe-styles.mjs.map +2 -2
  94. package/build-module/components/collaborators-overlay/overlay.mjs +61 -37
  95. package/build-module/components/collaborators-overlay/overlay.mjs.map +2 -2
  96. package/build-module/components/collaborators-overlay/timing-utils.mjs +21 -0
  97. package/build-module/components/collaborators-overlay/timing-utils.mjs.map +7 -0
  98. package/build-module/components/collaborators-overlay/use-block-highlighting.mjs +5 -6
  99. package/build-module/components/collaborators-overlay/use-block-highlighting.mjs.map +2 -2
  100. package/build-module/components/collaborators-overlay/use-render-cursors.mjs +50 -140
  101. package/build-module/components/collaborators-overlay/use-render-cursors.mjs.map +2 -2
  102. package/build-module/components/collaborators-presence/index.mjs +39 -13
  103. package/build-module/components/collaborators-presence/index.mjs.map +2 -2
  104. package/build-module/components/collaborators-presence/list.mjs +27 -24
  105. package/build-module/components/collaborators-presence/list.mjs.map +2 -2
  106. package/build-module/components/collaborators-presence/use-collaborator-notifications.mjs +80 -108
  107. package/build-module/components/collaborators-presence/use-collaborator-notifications.mjs.map +2 -2
  108. package/build-module/components/editor-interface/index.mjs +10 -7
  109. package/build-module/components/editor-interface/index.mjs.map +2 -2
  110. package/build-module/components/inserter-sidebar/index.mjs +2 -1
  111. package/build-module/components/inserter-sidebar/index.mjs.map +2 -2
  112. package/build-module/components/page-attributes/parent.mjs +1 -1
  113. package/build-module/components/page-attributes/parent.mjs.map +2 -2
  114. package/build-module/components/post-locked-modal/index.mjs +16 -3
  115. package/build-module/components/post-locked-modal/index.mjs.map +2 -2
  116. package/build-module/components/post-revisions-preview/block-diff.mjs +39 -11
  117. package/build-module/components/post-revisions-preview/block-diff.mjs.map +2 -2
  118. package/build-module/components/post-revisions-preview/diff-markers.mjs +2 -2
  119. package/build-module/components/post-revisions-preview/diff-markers.mjs.map +2 -2
  120. package/build-module/components/post-revisions-preview/revisions-canvas.mjs +14 -80
  121. package/build-module/components/post-revisions-preview/revisions-canvas.mjs.map +2 -2
  122. package/build-module/components/post-revisions-preview/revisions-slider.mjs +5 -1
  123. package/build-module/components/post-revisions-preview/revisions-slider.mjs.map +2 -2
  124. package/build-module/components/post-template/block-theme.mjs +7 -4
  125. package/build-module/components/post-template/block-theme.mjs.map +2 -2
  126. package/build-module/components/post-template/hooks.mjs +37 -1
  127. package/build-module/components/post-template/hooks.mjs.map +2 -2
  128. package/build-module/components/post-template/panel.mjs +5 -42
  129. package/build-module/components/post-template/panel.mjs.map +2 -2
  130. package/build-module/components/preferences-modal/index.mjs +24 -0
  131. package/build-module/components/preferences-modal/index.mjs.map +2 -2
  132. package/build-module/components/provider/disable-non-page-content-blocks.mjs +31 -28
  133. package/build-module/components/provider/disable-non-page-content-blocks.mjs.map +2 -2
  134. package/build-module/components/provider/index.mjs +17 -5
  135. package/build-module/components/provider/index.mjs.map +2 -2
  136. package/build-module/components/provider/use-block-editor-settings.mjs +19 -5
  137. package/build-module/components/provider/use-block-editor-settings.mjs.map +2 -2
  138. package/build-module/components/provider/use-post-content-block-types.mjs +23 -0
  139. package/build-module/components/provider/use-post-content-block-types.mjs.map +7 -0
  140. package/build-module/components/provider/use-revision-blocks.mjs +81 -0
  141. package/build-module/components/provider/use-revision-blocks.mjs.map +7 -0
  142. package/build-module/components/revision-block-diff/index.mjs +53 -0
  143. package/build-module/components/revision-block-diff/index.mjs.map +7 -0
  144. package/build-module/components/sidebar/dataform-post-summary.mjs +36 -6
  145. package/build-module/components/sidebar/dataform-post-summary.mjs.map +2 -2
  146. package/build-module/components/sidebar/header.mjs +1 -1
  147. package/build-module/components/sidebar/header.mjs.map +2 -2
  148. package/build-module/components/sidebar/index.mjs +5 -1
  149. package/build-module/components/sidebar/index.mjs.map +2 -2
  150. package/build-module/components/sync-connection-error-modal/index.mjs +177 -0
  151. package/build-module/components/sync-connection-error-modal/index.mjs.map +7 -0
  152. package/build-module/components/sync-connection-error-modal/use-retry-countdown.mjs +36 -0
  153. package/build-module/components/sync-connection-error-modal/use-retry-countdown.mjs.map +7 -0
  154. package/build-module/components/template-content-panel/index.mjs +25 -31
  155. package/build-module/components/template-content-panel/index.mjs.map +2 -2
  156. package/build-module/components/visual-editor/index.mjs +2 -2
  157. package/build-module/components/visual-editor/index.mjs.map +2 -2
  158. package/build-module/store/actions.mjs +1 -3
  159. package/build-module/store/actions.mjs.map +2 -2
  160. package/build-module/store/private-actions.mjs +10 -2
  161. package/build-module/store/private-actions.mjs.map +2 -2
  162. package/build-module/store/private-selectors.mjs +50 -12
  163. package/build-module/store/private-selectors.mjs.map +2 -2
  164. package/build-module/store/reducer.mjs +11 -0
  165. package/build-module/store/reducer.mjs.map +2 -2
  166. package/build-module/utils/media-finalize/index.mjs +12 -0
  167. package/build-module/utils/media-finalize/index.mjs.map +7 -0
  168. package/build-module/utils/sync-error-messages.mjs +24 -16
  169. package/build-module/utils/sync-error-messages.mjs.map +3 -3
  170. package/build-style/style-rtl.css +95 -16
  171. package/build-style/style.css +95 -16
  172. package/build-types/components/collaborators-overlay/avatar-iframe-styles.d.ts +1 -1
  173. package/build-types/components/collaborators-overlay/avatar-iframe-styles.d.ts.map +1 -1
  174. package/build-types/components/collaborators-overlay/compute-selection.d.ts +24 -0
  175. package/build-types/components/collaborators-overlay/compute-selection.d.ts.map +1 -0
  176. package/build-types/components/collaborators-overlay/cursor-dom-utils.d.ts +72 -0
  177. package/build-types/components/collaborators-overlay/cursor-dom-utils.d.ts.map +1 -0
  178. package/build-types/components/collaborators-overlay/overlay-iframe-styles.d.ts +1 -1
  179. package/build-types/components/collaborators-overlay/overlay-iframe-styles.d.ts.map +1 -1
  180. package/build-types/components/collaborators-overlay/overlay.d.ts.map +1 -1
  181. package/build-types/components/collaborators-overlay/timing-utils.d.ts +11 -0
  182. package/build-types/components/collaborators-overlay/timing-utils.d.ts.map +1 -0
  183. package/build-types/components/collaborators-overlay/use-block-highlighting.d.ts.map +1 -1
  184. package/build-types/components/collaborators-overlay/use-render-cursors.d.ts +4 -0
  185. package/build-types/components/collaborators-overlay/use-render-cursors.d.ts.map +1 -1
  186. package/build-types/components/collaborators-presence/index.d.ts.map +1 -1
  187. package/build-types/components/collaborators-presence/list.d.ts +2 -1
  188. package/build-types/components/collaborators-presence/list.d.ts.map +1 -1
  189. package/build-types/components/collaborators-presence/use-collaborator-notifications.d.ts.map +1 -1
  190. package/build-types/components/editor-interface/index.d.ts.map +1 -1
  191. package/build-types/components/inserter-sidebar/index.d.ts.map +1 -1
  192. package/build-types/components/post-locked-modal/index.d.ts +2 -2
  193. package/build-types/components/post-locked-modal/index.d.ts.map +1 -1
  194. package/build-types/components/post-revisions-preview/block-diff.d.ts.map +1 -1
  195. package/build-types/components/post-revisions-preview/revisions-canvas.d.ts +2 -5
  196. package/build-types/components/post-revisions-preview/revisions-canvas.d.ts.map +1 -1
  197. package/build-types/components/post-revisions-preview/revisions-slider.d.ts.map +1 -1
  198. package/build-types/components/post-template/block-theme.d.ts +1 -3
  199. package/build-types/components/post-template/block-theme.d.ts.map +1 -1
  200. package/build-types/components/post-template/hooks.d.ts +1 -0
  201. package/build-types/components/post-template/hooks.d.ts.map +1 -1
  202. package/build-types/components/post-template/panel.d.ts.map +1 -1
  203. package/build-types/components/provider/disable-non-page-content-blocks.d.ts.map +1 -1
  204. package/build-types/components/provider/index.d.ts.map +1 -1
  205. package/build-types/components/provider/use-block-editor-settings.d.ts.map +1 -1
  206. package/build-types/components/provider/use-post-content-block-types.d.ts +9 -0
  207. package/build-types/components/provider/use-post-content-block-types.d.ts.map +1 -0
  208. package/build-types/components/provider/use-revision-blocks.d.ts +10 -0
  209. package/build-types/components/provider/use-revision-blocks.d.ts.map +1 -0
  210. package/build-types/components/revision-block-diff/index.d.ts +6 -0
  211. package/build-types/components/revision-block-diff/index.d.ts.map +1 -0
  212. package/build-types/components/sidebar/dataform-post-summary.d.ts.map +1 -1
  213. package/build-types/components/sidebar/index.d.ts.map +1 -1
  214. package/build-types/components/sync-connection-error-modal/index.d.ts +22 -0
  215. package/build-types/components/sync-connection-error-modal/index.d.ts.map +1 -0
  216. package/build-types/components/sync-connection-error-modal/use-retry-countdown.d.ts +11 -0
  217. package/build-types/components/sync-connection-error-modal/use-retry-countdown.d.ts.map +1 -0
  218. package/build-types/components/template-content-panel/index.d.ts.map +1 -1
  219. package/build-types/store/actions.d.ts.map +1 -1
  220. package/build-types/store/private-actions.d.ts +7 -0
  221. package/build-types/store/private-actions.d.ts.map +1 -1
  222. package/build-types/store/private-selectors.d.ts +7 -0
  223. package/build-types/store/private-selectors.d.ts.map +1 -1
  224. package/build-types/store/reducer.d.ts +14 -3
  225. package/build-types/store/reducer.d.ts.map +1 -1
  226. package/build-types/utils/media-finalize/index.d.ts +2 -0
  227. package/build-types/utils/media-finalize/index.d.ts.map +1 -0
  228. package/build-types/utils/sync-error-messages.d.ts +17 -3
  229. package/build-types/utils/sync-error-messages.d.ts.map +1 -1
  230. package/package.json +44 -44
  231. package/src/components/collaborators-overlay/avatar-iframe-styles.ts +12 -4
  232. package/src/components/collaborators-overlay/compute-selection.ts +307 -0
  233. package/src/components/collaborators-overlay/cursor-dom-utils.ts +382 -0
  234. package/src/components/collaborators-overlay/overlay-iframe-styles.ts +6 -0
  235. package/src/components/collaborators-overlay/overlay.tsx +59 -27
  236. package/src/components/collaborators-overlay/timing-utils.ts +30 -0
  237. package/src/components/collaborators-overlay/use-block-highlighting.ts +11 -10
  238. package/src/components/collaborators-overlay/use-render-cursors.ts +70 -242
  239. package/src/components/collaborators-presence/avatar/styles.scss +20 -4
  240. package/src/components/collaborators-presence/index.tsx +30 -5
  241. package/src/components/collaborators-presence/list.tsx +38 -24
  242. package/src/components/collaborators-presence/test/use-collaborator-notifications.ts +188 -246
  243. package/src/components/collaborators-presence/use-collaborator-notifications.ts +109 -166
  244. package/src/components/document-bar/style.scss +1 -1
  245. package/src/components/editor-interface/index.js +8 -6
  246. package/src/components/inserter-sidebar/index.js +4 -1
  247. package/src/components/page-attributes/parent.js +1 -1
  248. package/src/components/post-locked-modal/index.js +21 -3
  249. package/src/components/post-revisions-preview/block-diff.js +59 -20
  250. package/src/components/post-revisions-preview/diff-markers.js +2 -2
  251. package/src/components/post-revisions-preview/revisions-canvas.js +20 -98
  252. package/src/components/post-revisions-preview/revisions-slider.js +6 -1
  253. package/src/components/post-revisions-preview/test/block-diff.js +69 -31
  254. package/src/components/post-template/block-theme.js +4 -1
  255. package/src/components/post-template/hooks.js +42 -0
  256. package/src/components/post-template/panel.js +5 -59
  257. package/src/components/preferences-modal/index.js +18 -0
  258. package/src/components/provider/disable-non-page-content-blocks.js +42 -40
  259. package/src/components/provider/index.js +20 -2
  260. package/src/components/provider/use-block-editor-settings.js +21 -8
  261. package/src/components/provider/use-post-content-block-types.js +30 -0
  262. package/src/components/provider/use-revision-blocks.js +105 -0
  263. package/src/components/revision-block-diff/index.js +74 -0
  264. package/src/components/revision-block-diff/style.scss +13 -0
  265. package/src/components/sidebar/dataform-post-summary.js +61 -16
  266. package/src/components/sidebar/header.js +1 -1
  267. package/src/components/sidebar/index.js +2 -0
  268. package/src/components/sync-connection-error-modal/index.tsx +265 -0
  269. package/src/components/sync-connection-error-modal/style.scss +14 -0
  270. package/src/components/sync-connection-error-modal/use-retry-countdown.ts +57 -0
  271. package/src/components/template-content-panel/index.js +30 -38
  272. package/src/components/visual-editor/index.js +2 -2
  273. package/src/store/actions.js +1 -4
  274. package/src/store/private-actions.js +21 -2
  275. package/src/store/private-selectors.js +75 -10
  276. package/src/store/reducer.js +19 -0
  277. package/src/style.scss +2 -1
  278. package/src/utils/media-finalize/index.js +11 -0
  279. package/src/utils/media-finalize/test/index.js +34 -0
  280. package/src/utils/sync-error-messages.ts +72 -0
  281. package/src/utils/test/sync-error-messages.js +9 -32
  282. package/build/components/provider/use-post-content-blocks.cjs.map +0 -7
  283. package/build/components/sync-connection-modal/index.cjs.map +0 -7
  284. package/build/components/sync-connection-modal/use-retry-countdown.cjs.map +0 -7
  285. package/build-module/components/provider/use-post-content-blocks.mjs +0 -34
  286. package/build-module/components/provider/use-post-content-blocks.mjs.map +0 -7
  287. package/build-module/components/sync-connection-modal/index.mjs +0 -167
  288. package/build-module/components/sync-connection-modal/index.mjs.map +0 -7
  289. package/build-module/components/sync-connection-modal/use-retry-countdown.mjs +0 -49
  290. package/build-module/components/sync-connection-modal/use-retry-countdown.mjs.map +0 -7
  291. package/build-types/components/provider/use-post-content-blocks.d.ts +0 -2
  292. package/build-types/components/provider/use-post-content-blocks.d.ts.map +0 -1
  293. package/build-types/components/sync-connection-modal/index.d.ts +0 -8
  294. package/build-types/components/sync-connection-modal/index.d.ts.map +0 -1
  295. package/build-types/components/sync-connection-modal/use-retry-countdown.d.ts +0 -9
  296. package/build-types/components/sync-connection-modal/use-retry-countdown.d.ts.map +0 -1
  297. package/src/components/provider/use-post-content-blocks.js +0 -42
  298. package/src/components/sync-connection-modal/index.js +0 -200
  299. package/src/components/sync-connection-modal/style.scss +0 -9
  300. package/src/components/sync-connection-modal/use-retry-countdown.js +0 -70
  301. package/src/utils/sync-error-messages.js +0 -58
@@ -16,12 +16,16 @@ import { useCollaboratorNotifications } from '../use-collaborator-notifications'
16
16
  // --- Mocks ---
17
17
 
18
18
  const mockCreateNotice = jest.fn();
19
- let mockActiveCollaborators: any[] = [];
20
- let mockLastPostSave: { savedAt: number; savedByClientId: number } | null =
21
- null;
19
+ let mockOnJoinCallback: Function | null = null;
20
+ let mockOnLeaveCallback: Function | null = null;
21
+ let mockOnPostSaveCallback: Function | null = null;
22
+ let lastJoinPostId: unknown;
23
+ let lastLeavePostId: unknown;
24
+ let lastSavePostId: unknown;
22
25
  let mockEditorState = {
23
26
  postStatus: 'draft',
24
27
  isCollaborationEnabled: true,
28
+ showCollaborationNotifications: true,
25
29
  };
26
30
 
27
31
  jest.mock( '@wordpress/data', () => ( {
@@ -33,6 +37,10 @@ jest.mock( '@wordpress/notices', () => ( {
33
37
  store: 'core/notices',
34
38
  } ) );
35
39
 
40
+ jest.mock( '@wordpress/preferences', () => ( {
41
+ store: 'core/preferences',
42
+ } ) );
43
+
36
44
  // Mock the editor store to prevent deep import chain (blocks, rich-text, etc.)
37
45
  jest.mock( '../../../store', () => ( {
38
46
  store: 'core/editor',
@@ -45,8 +53,24 @@ jest.mock( '@wordpress/core-data', () => ( {
45
53
 
46
54
  jest.mock( '../../../lock-unlock', () => ( {
47
55
  unlock: jest.fn( () => ( {
48
- useActiveCollaborators: jest.fn( () => mockActiveCollaborators ),
49
- useLastPostSave: jest.fn( () => mockLastPostSave ),
56
+ useOnCollaboratorJoin: jest.fn(
57
+ ( postId: unknown, _postType: unknown, callback: Function ) => {
58
+ lastJoinPostId = postId;
59
+ mockOnJoinCallback = callback;
60
+ }
61
+ ),
62
+ useOnCollaboratorLeave: jest.fn(
63
+ ( postId: unknown, _postType: unknown, callback: Function ) => {
64
+ lastLeavePostId = postId;
65
+ mockOnLeaveCallback = callback;
66
+ }
67
+ ),
68
+ useOnPostSave: jest.fn(
69
+ ( postId: unknown, _postType: unknown, callback: Function ) => {
70
+ lastSavePostId = postId;
71
+ mockOnPostSaveCallback = callback;
72
+ }
73
+ ),
50
74
  } ) ),
51
75
  } ) );
52
76
 
@@ -90,20 +114,40 @@ function makeMe( overrides: Record< string, unknown > = {} ) {
90
114
  // --- Setup ---
91
115
 
92
116
  function buildMockSelect() {
93
- return () => ( {
94
- getCurrentPostAttribute: ( attr: string ) =>
95
- attr === 'status' ? mockEditorState.postStatus : undefined,
96
- isCollaborationEnabledForCurrentPost: () =>
97
- mockEditorState.isCollaborationEnabled,
98
- } );
117
+ return ( storeKey: string ) => {
118
+ if ( storeKey === 'core/preferences' ) {
119
+ return {
120
+ get: ( scope: string, name: string ) => {
121
+ if (
122
+ scope === 'core' &&
123
+ name === 'showCollaborationNotifications'
124
+ ) {
125
+ return mockEditorState.showCollaborationNotifications;
126
+ }
127
+ return undefined;
128
+ },
129
+ };
130
+ }
131
+ return {
132
+ getCurrentPostAttribute: ( attr: string ) =>
133
+ attr === 'status' ? mockEditorState.postStatus : undefined,
134
+ isCollaborationEnabledForCurrentPost: () =>
135
+ mockEditorState.isCollaborationEnabled,
136
+ };
137
+ };
99
138
  }
100
139
 
101
140
  beforeEach( () => {
102
- mockActiveCollaborators = [];
103
- mockLastPostSave = null;
141
+ mockOnJoinCallback = null;
142
+ mockOnLeaveCallback = null;
143
+ mockOnPostSaveCallback = null;
144
+ lastJoinPostId = undefined;
145
+ lastLeavePostId = undefined;
146
+ lastSavePostId = undefined;
104
147
  mockEditorState = {
105
148
  postStatus: 'draft',
106
149
  isCollaborationEnabled: true,
150
+ showCollaborationNotifications: true,
107
151
  };
108
152
  mockCreateNotice.mockClear();
109
153
  ( useSelect as jest.Mock ).mockImplementation( ( selector: Function ) =>
@@ -117,77 +161,7 @@ beforeEach( () => {
117
161
  // --- Tests ---
118
162
 
119
163
  describe( 'useCollaboratorNotifications', () => {
120
- describe( 'initial mount', () => {
121
- it( 'does not fire join notifications for collaborators already present on mount', () => {
122
- mockActiveCollaborators = [ makeMe(), makeCollaborator() ];
123
-
124
- renderHook( () => useCollaboratorNotifications( 123, 'post' ) );
125
-
126
- expect( mockCreateNotice ).not.toHaveBeenCalled();
127
- } );
128
-
129
- it( 'does not fire any notification when no collaborators are present', () => {
130
- mockActiveCollaborators = [];
131
-
132
- renderHook( () => useCollaboratorNotifications( 123, 'post' ) );
133
-
134
- expect( mockCreateNotice ).not.toHaveBeenCalled();
135
- } );
136
-
137
- it( 'does not fire join notifications when collaborators load after an initially empty state', () => {
138
- // Simulates the store hydrating: first render has no collaborators,
139
- // second render receives the full list.
140
- mockActiveCollaborators = [];
141
- const { rerender } = renderHook( () =>
142
- useCollaboratorNotifications( 123, 'post' )
143
- );
144
-
145
- mockActiveCollaborators = [ makeMe(), makeCollaborator() ];
146
- rerender();
147
-
148
- expect( mockCreateNotice ).not.toHaveBeenCalled();
149
- } );
150
- } );
151
-
152
164
  describe( 'collaborator join notifications', () => {
153
- it( 'does not fire a join notification for the current user', () => {
154
- mockActiveCollaborators = [];
155
- const { rerender } = renderHook( () =>
156
- useCollaboratorNotifications( 123, 'post' )
157
- );
158
-
159
- mockActiveCollaborators = [ makeMe() ];
160
- rerender();
161
-
162
- expect( mockCreateNotice ).not.toHaveBeenCalled();
163
- } );
164
-
165
- it( 'skips join notification for collaborators who joined before current user', () => {
166
- // Alice joined BEFORE the current user (smaller enteredAt)
167
- const me = makeMe(); // enteredAt: BASE_ENTERED_AT + 5000
168
- const aliceJoinedFirst = makeCollaborator( {
169
- collaboratorInfo: {
170
- id: 100,
171
- name: 'Alice',
172
- slug: 'alice',
173
- avatar_urls: {},
174
- browserType: 'Chrome',
175
- enteredAt: BASE_ENTERED_AT + 1000, // joined earlier than me
176
- },
177
- } );
178
-
179
- mockActiveCollaborators = [ me ];
180
- const { rerender } = renderHook( () =>
181
- useCollaboratorNotifications( 123, 'post' )
182
- );
183
-
184
- // Alice appears in the state — but she was there before us
185
- mockActiveCollaborators = [ me, aliceJoinedFirst ];
186
- rerender();
187
-
188
- expect( mockCreateNotice ).not.toHaveBeenCalled();
189
- } );
190
-
191
165
  it( 'fires join notification for a collaborator who joined after current user', () => {
192
166
  const me = makeMe(); // enteredAt: BASE_ENTERED_AT + 5000
193
167
  const bobJoinedAfter = makeCollaborator( {
@@ -202,13 +176,10 @@ describe( 'useCollaboratorNotifications', () => {
202
176
  },
203
177
  } );
204
178
 
205
- mockActiveCollaborators = [ me ];
206
- const { rerender } = renderHook( () =>
207
- useCollaboratorNotifications( 123, 'post' )
208
- );
179
+ renderHook( () => useCollaboratorNotifications( 123, 'post' ) );
209
180
 
210
- mockActiveCollaborators = [ me, bobJoinedAfter ];
211
- rerender();
181
+ // Simulate the core-data hook firing the join callback
182
+ mockOnJoinCallback?.( bobJoinedAfter, me );
212
183
 
213
184
  expect( mockCreateNotice ).toHaveBeenCalledWith(
214
185
  'info',
@@ -218,67 +189,36 @@ describe( 'useCollaboratorNotifications', () => {
218
189
  } )
219
190
  );
220
191
  } );
221
- } );
222
-
223
- describe( 'collaborator leave notifications', () => {
224
- it( 'fires a leave notification when a collaborator disconnects (isConnected → false)', () => {
225
- const alice = makeCollaborator();
226
- mockActiveCollaborators = [ makeMe(), alice ];
227
- const { rerender } = renderHook( () =>
228
- useCollaboratorNotifications( 123, 'post' )
229
- );
230
-
231
- // Alice disconnects — still in the list but greyed out.
232
- mockActiveCollaborators = [
233
- makeMe(),
234
- { ...alice, isConnected: false },
235
- ];
236
- rerender();
237
-
238
- expect( mockCreateNotice ).toHaveBeenCalledWith(
239
- 'info',
240
- 'Alice has left the post.',
241
- expect.objectContaining( {
242
- type: 'snackbar',
243
- isDismissible: false,
244
- id: 'collab-user-exited-100',
245
- } )
246
- );
247
- } );
248
192
 
249
- it( 'does not fire a duplicate leave notification when a disconnected collaborator is removed from the list', () => {
250
- const alice = makeCollaborator();
251
- mockActiveCollaborators = [ makeMe(), alice ];
252
- const { rerender } = renderHook( () =>
253
- useCollaboratorNotifications( 123, 'post' )
254
- );
193
+ it( 'skips join notification for collaborators who joined before current user', () => {
194
+ const me = makeMe(); // enteredAt: BASE_ENTERED_AT + 5000
195
+ const aliceJoinedFirst = makeCollaborator( {
196
+ collaboratorInfo: {
197
+ id: 100,
198
+ name: 'Alice',
199
+ slug: 'alice',
200
+ avatar_urls: {},
201
+ browserType: 'Chrome',
202
+ enteredAt: BASE_ENTERED_AT + 1000, // joined earlier than me
203
+ },
204
+ } );
255
205
 
256
- // Alice disconnects (greyed out).
257
- mockActiveCollaborators = [
258
- makeMe(),
259
- { ...alice, isConnected: false },
260
- ];
261
- rerender();
262
- mockCreateNotice.mockClear();
206
+ renderHook( () => useCollaboratorNotifications( 123, 'post' ) );
263
207
 
264
- // After the 5s delay Alice is fully removed from the list.
265
- mockActiveCollaborators = [ makeMe() ];
266
- rerender();
208
+ // Simulate the core-data hook firing the join callback
209
+ mockOnJoinCallback?.( aliceJoinedFirst, me );
267
210
 
268
211
  expect( mockCreateNotice ).not.toHaveBeenCalled();
269
212
  } );
213
+ } );
270
214
 
271
- it( 'fires a leave notification when a connected collaborator is removed from the list directly', () => {
215
+ describe( 'collaborator leave notifications', () => {
216
+ it( 'fires a leave notification when a collaborator leaves', () => {
272
217
  const alice = makeCollaborator();
273
- mockActiveCollaborators = [ makeMe(), alice ];
274
- const { rerender } = renderHook( () =>
275
- useCollaboratorNotifications( 123, 'post' )
276
- );
218
+ renderHook( () => useCollaboratorNotifications( 123, 'post' ) );
277
219
 
278
- // Alice disappears from the list without going through isConnected=false
279
- // (e.g. polling detects the disconnect and removes in one update).
280
- mockActiveCollaborators = [ makeMe() ];
281
- rerender();
220
+ // Simulate the core-data hook firing the leave callback
221
+ mockOnLeaveCallback?.( alice );
282
222
 
283
223
  expect( mockCreateNotice ).toHaveBeenCalledWith(
284
224
  'info',
@@ -290,43 +230,23 @@ describe( 'useCollaboratorNotifications', () => {
290
230
  } )
291
231
  );
292
232
  } );
293
-
294
- it( 'does not fire a leave notification for the current user', () => {
295
- const me = makeMe();
296
- mockActiveCollaborators = [ me, makeCollaborator() ];
297
- const { rerender } = renderHook( () =>
298
- useCollaboratorNotifications( 123, 'post' )
299
- );
300
-
301
- // "Me" disconnects
302
- mockActiveCollaborators = [
303
- { ...me, isConnected: false },
304
- makeCollaborator(),
305
- ];
306
- rerender();
307
-
308
- // Should not notify about self
309
- const selfLeaveCall = mockCreateNotice.mock.calls.find(
310
- ( [ , message ] ) => message.includes( 'Me' )
311
- );
312
- expect( selfLeaveCall ).toBeUndefined();
313
- } );
314
233
  } );
315
234
 
316
235
  describe( 'post updated notifications', () => {
317
236
  it( 'fires a post updated notification when a collaborator saves (draft)', () => {
318
237
  const alice = makeCollaborator();
319
- mockActiveCollaborators = [ makeMe(), alice ];
320
- const { rerender } = renderHook( () =>
321
- useCollaboratorNotifications( 123, 'post' )
322
- );
238
+ renderHook( () => useCollaboratorNotifications( 123, 'post' ) );
323
239
 
324
- // State map reports Alice saved
325
- mockLastPostSave = {
326
- savedAt: Date.now(),
327
- savedByClientId: alice.clientId,
328
- };
329
- rerender();
240
+ // Simulate the core-data hook firing the save callback
241
+ mockOnPostSaveCallback?.(
242
+ {
243
+ savedAt: Date.now(),
244
+ savedByClientId: alice.clientId,
245
+ postStatus: undefined,
246
+ },
247
+ alice,
248
+ null
249
+ );
330
250
 
331
251
  expect( mockCreateNotice ).toHaveBeenCalledWith(
332
252
  'info',
@@ -345,16 +265,17 @@ describe( 'useCollaboratorNotifications', () => {
345
265
  postStatus: 'publish',
346
266
  };
347
267
  const alice = makeCollaborator();
348
- mockActiveCollaborators = [ makeMe(), alice ];
349
- const { rerender } = renderHook( () =>
350
- useCollaboratorNotifications( 123, 'post' )
351
- );
268
+ renderHook( () => useCollaboratorNotifications( 123, 'post' ) );
352
269
 
353
- mockLastPostSave = {
354
- savedAt: Date.now(),
355
- savedByClientId: alice.clientId,
356
- };
357
- rerender();
270
+ mockOnPostSaveCallback?.(
271
+ {
272
+ savedAt: Date.now(),
273
+ savedByClientId: alice.clientId,
274
+ postStatus: 'publish',
275
+ },
276
+ alice,
277
+ null
278
+ );
358
279
 
359
280
  expect( mockCreateNotice ).toHaveBeenCalledWith(
360
281
  'info',
@@ -365,90 +286,111 @@ describe( 'useCollaboratorNotifications', () => {
365
286
  );
366
287
  } );
367
288
 
368
- it( 'does not fire a notification when the current user saves', () => {
369
- const me = makeMe();
370
- mockActiveCollaborators = [ me, makeCollaborator() ];
371
- const { rerender } = renderHook( () =>
372
- useCollaboratorNotifications( 123, 'post' )
373
- );
374
-
375
- // State map reports "me" saved
376
- mockLastPostSave = {
377
- savedAt: Date.now(),
378
- savedByClientId: me.clientId,
289
+ it( 'fires a "Post published" notification on first publish (no previous save)', () => {
290
+ mockEditorState = {
291
+ ...mockEditorState,
292
+ postStatus: 'draft',
379
293
  };
380
- rerender();
294
+ const alice = makeCollaborator();
295
+ renderHook( () => useCollaboratorNotifications( 123, 'post' ) );
381
296
 
382
- expect( mockCreateNotice ).not.toHaveBeenCalled();
297
+ mockOnPostSaveCallback?.(
298
+ {
299
+ savedAt: Date.now(),
300
+ savedByClientId: alice.clientId,
301
+ postStatus: 'publish',
302
+ },
303
+ alice,
304
+ null
305
+ );
306
+
307
+ expect( mockCreateNotice ).toHaveBeenCalledWith(
308
+ 'info',
309
+ 'Post published by Alice.',
310
+ expect.objectContaining( {
311
+ id: 'collab-post-updated-100',
312
+ } )
313
+ );
383
314
  } );
384
315
 
385
- it( 'does not fire duplicate notifications for the same savedAt timestamp', () => {
316
+ it( 'fires a "Post published" notification using prevEvent status for transition detection', () => {
317
+ // Redux postStatus may already be 'publish' by the time the
318
+ // callback fires. The prevEvent carries the accurate prior status.
319
+ mockEditorState = {
320
+ ...mockEditorState,
321
+ postStatus: 'publish',
322
+ };
386
323
  const alice = makeCollaborator();
387
- mockActiveCollaborators = [ makeMe(), alice ];
388
- const savedAt = Date.now();
324
+ renderHook( () => useCollaboratorNotifications( 123, 'post' ) );
389
325
 
390
- const { rerender } = renderHook( () =>
391
- useCollaboratorNotifications( 123, 'post' )
326
+ mockOnPostSaveCallback?.(
327
+ {
328
+ savedAt: Date.now() + 2000,
329
+ savedByClientId: alice.clientId,
330
+ postStatus: 'publish',
331
+ },
332
+ alice,
333
+ {
334
+ savedAt: Date.now() + 1000,
335
+ savedByClientId: alice.clientId,
336
+ postStatus: 'draft',
337
+ }
392
338
  );
393
339
 
394
- // First save event
395
- mockLastPostSave = {
396
- savedAt,
397
- savedByClientId: alice.clientId,
340
+ expect( mockCreateNotice ).toHaveBeenCalledWith(
341
+ 'info',
342
+ 'Post published by Alice.',
343
+ expect.objectContaining( {
344
+ id: 'collab-post-updated-100',
345
+ } )
346
+ );
347
+ } );
348
+
349
+ it( 'does not fire a notification when postStatus is undefined', () => {
350
+ mockEditorState = {
351
+ ...mockEditorState,
352
+ postStatus: undefined as any,
398
353
  };
399
- rerender();
400
- mockCreateNotice.mockClear();
354
+ const alice = makeCollaborator();
355
+ renderHook( () => useCollaboratorNotifications( 123, 'post' ) );
401
356
 
402
- // Rerender with same savedAt — should not notify again
403
- rerender();
357
+ mockOnPostSaveCallback?.(
358
+ {
359
+ savedAt: Date.now(),
360
+ savedByClientId: alice.clientId,
361
+ postStatus: undefined,
362
+ },
363
+ alice,
364
+ null
365
+ );
404
366
 
405
367
  expect( mockCreateNotice ).not.toHaveBeenCalled();
406
368
  } );
369
+ } );
407
370
 
408
- it( 'does not fire a notification when a peer reconnects without a new save', () => {
409
- const alice = makeCollaborator();
410
- mockActiveCollaborators = [ makeMe(), alice ];
411
- const { rerender } = renderHook( () =>
412
- useCollaboratorNotifications( 123, 'post' )
413
- );
371
+ describe( 'when notifications are disabled', () => {
372
+ it( 'passes null postId to hooks when showCollaborationNotifications preference is false', () => {
373
+ mockEditorState = {
374
+ ...mockEditorState,
375
+ showCollaborationNotifications: false,
376
+ };
377
+ renderHook( () => useCollaboratorNotifications( 123, 'post' ) );
414
378
 
415
- // Alice disconnects and reconnects — useLastPostSave filters
416
- // pre-existing state map values via its baseline check, so
417
- // lastPostSave stays null.
418
- mockActiveCollaborators = [
419
- makeMe(),
420
- { ...alice, isConnected: false },
421
- ];
422
- rerender();
423
- mockCreateNotice.mockClear();
424
-
425
- mockActiveCollaborators = [
426
- makeMe(),
427
- { ...alice, clientId: 50, isConnected: true },
428
- ];
429
- rerender();
430
-
431
- // Only the leave/join notices should have fired, no save notice.
432
- const saveNotice = mockCreateNotice.mock.calls.find(
433
- ( [ , msg ] ) => ( msg as string ).includes( 'saved' )
434
- );
435
- expect( saveNotice ).toBeUndefined();
379
+ expect( lastJoinPostId ).toBeNull();
380
+ expect( lastLeavePostId ).toBeNull();
381
+ expect( lastSavePostId ).toBeNull();
436
382
  } );
437
383
 
438
- it( 'does not fire a notification when the saver is not in the collaborator list', () => {
439
- mockActiveCollaborators = [ makeMe(), makeCollaborator() ];
440
- const { rerender } = renderHook( () =>
441
- useCollaboratorNotifications( 123, 'post' )
442
- );
443
-
444
- // State map reports a save by unknown clientId
445
- mockLastPostSave = {
446
- savedAt: Date.now(),
447
- savedByClientId: 12345,
384
+ it( 'passes null postId to hooks when collaboration is disabled', () => {
385
+ mockEditorState = {
386
+ ...mockEditorState,
387
+ isCollaborationEnabled: false,
448
388
  };
449
- rerender();
389
+ renderHook( () => useCollaboratorNotifications( 123, 'post' ) );
450
390
 
451
- expect( mockCreateNotice ).not.toHaveBeenCalled();
391
+ expect( lastJoinPostId ).toBeNull();
392
+ expect( lastLeavePostId ).toBeNull();
393
+ expect( lastSavePostId ).toBeNull();
452
394
  } );
453
395
  } );
454
396
  } );