@wordpress/block-editor 9.7.0 → 9.7.1-next.d6164808d3.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 (279) hide show
  1. package/build/components/block-alignment-control/use-available-alignments.js +1 -1
  2. package/build/components/block-alignment-control/use-available-alignments.js.map +1 -1
  3. package/build/components/block-edit-visually-button/index.js +46 -0
  4. package/build/components/block-edit-visually-button/index.js.map +1 -0
  5. package/build/components/block-popover/inbetween.js +4 -2
  6. package/build/components/block-popover/inbetween.js.map +1 -1
  7. package/build/components/block-settings-menu/index.js +2 -6
  8. package/build/components/block-settings-menu/index.js.map +1 -1
  9. package/build/components/block-switcher/index.js +10 -16
  10. package/build/components/block-switcher/index.js.map +1 -1
  11. package/build/components/block-toolbar/index.js +5 -1
  12. package/build/components/block-toolbar/index.js.map +1 -1
  13. package/build/components/border-radius-control/all-input-control.js +31 -3
  14. package/build/components/border-radius-control/all-input-control.js.map +1 -1
  15. package/build/components/border-radius-control/index.js +20 -6
  16. package/build/components/border-radius-control/index.js.map +1 -1
  17. package/build/components/border-radius-control/input-controls.js +21 -6
  18. package/build/components/border-radius-control/input-controls.js.map +1 -1
  19. package/build/components/border-radius-control/utils.js +13 -16
  20. package/build/components/border-radius-control/utils.js.map +1 -1
  21. package/build/components/colors/with-colors.js +17 -4
  22. package/build/components/colors/with-colors.js.map +1 -1
  23. package/build/components/copy-handler/index.js +6 -0
  24. package/build/components/copy-handler/index.js.map +1 -1
  25. package/build/components/date-format-picker/index.js +2 -7
  26. package/build/components/date-format-picker/index.js.map +1 -1
  27. package/build/components/duotone/components.js +5 -5
  28. package/build/components/duotone/components.js.map +1 -1
  29. package/build/components/font-family/index.js +1 -1
  30. package/build/components/font-family/index.js.map +1 -1
  31. package/build/components/font-sizes/with-font-sizes.js +17 -4
  32. package/build/components/font-sizes/with-font-sizes.js.map +1 -1
  33. package/build/components/index.js +9 -0
  34. package/build/components/index.js.map +1 -1
  35. package/build/components/inserter/search-items.js +22 -4
  36. package/build/components/inserter/search-items.js.map +1 -1
  37. package/build/components/link-control/link-preview.js +0 -1
  38. package/build/components/link-control/link-preview.js.map +1 -1
  39. package/build/components/list-view/block-select-button.js +5 -2
  40. package/build/components/list-view/block-select-button.js.map +1 -1
  41. package/build/components/list-view/use-block-selection.js +1 -7
  42. package/build/components/list-view/use-block-selection.js.map +1 -1
  43. package/build/components/rich-text/use-enter.js +0 -4
  44. package/build/components/rich-text/use-enter.js.map +1 -1
  45. package/build/components/rich-text/use-format-types.js +8 -11
  46. package/build/components/rich-text/use-format-types.js.map +1 -1
  47. package/build/components/spacing-sizes-control/all-input-control.js +53 -0
  48. package/build/components/spacing-sizes-control/all-input-control.js.map +1 -0
  49. package/build/components/spacing-sizes-control/axial-input-controls.js +69 -0
  50. package/build/components/spacing-sizes-control/axial-input-controls.js.map +1 -0
  51. package/build/components/spacing-sizes-control/index.js +100 -0
  52. package/build/components/spacing-sizes-control/index.js.map +1 -0
  53. package/build/components/spacing-sizes-control/input-controls.js +52 -0
  54. package/build/components/spacing-sizes-control/input-controls.js.map +1 -0
  55. package/build/components/spacing-sizes-control/linked-button.js +38 -0
  56. package/build/components/spacing-sizes-control/linked-button.js.map +1 -0
  57. package/build/components/spacing-sizes-control/spacing-input-control.js +208 -0
  58. package/build/components/spacing-sizes-control/spacing-input-control.js.map +1 -0
  59. package/build/components/spacing-sizes-control/utils.js +202 -0
  60. package/build/components/spacing-sizes-control/utils.js.map +1 -0
  61. package/build/components/url-input/index.js +1 -1
  62. package/build/components/url-input/index.js.map +1 -1
  63. package/build/components/writing-flow/use-multi-selection.js +4 -2
  64. package/build/components/writing-flow/use-multi-selection.js.map +1 -1
  65. package/build/components/writing-flow/use-selection-observer.js +10 -2
  66. package/build/components/writing-flow/use-selection-observer.js.map +1 -1
  67. package/build/hooks/border-radius.js +2 -7
  68. package/build/hooks/border-radius.js.map +1 -1
  69. package/build/hooks/border.js +2 -2
  70. package/build/hooks/border.js.map +1 -1
  71. package/build/hooks/color.js +4 -1
  72. package/build/hooks/color.js.map +1 -1
  73. package/build/hooks/dimensions.js +15 -0
  74. package/build/hooks/dimensions.js.map +1 -1
  75. package/build/hooks/duotone.js +4 -4
  76. package/build/hooks/duotone.js.map +1 -1
  77. package/build/hooks/gap.js +6 -4
  78. package/build/hooks/gap.js.map +1 -1
  79. package/build/hooks/generated-class-name.js +1 -7
  80. package/build/hooks/generated-class-name.js.map +1 -1
  81. package/build/hooks/layout.js +20 -12
  82. package/build/hooks/layout.js.map +1 -1
  83. package/build/hooks/margin.js +28 -12
  84. package/build/hooks/margin.js.map +1 -1
  85. package/build/hooks/padding.js +19 -8
  86. package/build/hooks/padding.js.map +1 -1
  87. package/build/hooks/style.js +4 -50
  88. package/build/hooks/style.js.map +1 -1
  89. package/build/layouts/constrained.js +215 -0
  90. package/build/layouts/constrained.js.map +1 -0
  91. package/build/layouts/flex.js +1 -1
  92. package/build/layouts/flex.js.map +1 -1
  93. package/build/layouts/flow.js +7 -169
  94. package/build/layouts/flow.js.map +1 -1
  95. package/build/layouts/index.js +3 -1
  96. package/build/layouts/index.js.map +1 -1
  97. package/build/layouts/utils.js +43 -0
  98. package/build/layouts/utils.js.map +1 -1
  99. package/build/store/actions.js +25 -3
  100. package/build/store/actions.js.map +1 -1
  101. package/build/store/selectors.js +4 -6
  102. package/build/store/selectors.js.map +1 -1
  103. package/build-module/components/block-alignment-control/use-available-alignments.js +1 -1
  104. package/build-module/components/block-alignment-control/use-available-alignments.js.map +1 -1
  105. package/build-module/components/block-edit-visually-button/index.js +35 -0
  106. package/build-module/components/block-edit-visually-button/index.js.map +1 -0
  107. package/build-module/components/block-popover/inbetween.js +4 -2
  108. package/build-module/components/block-popover/inbetween.js.map +1 -1
  109. package/build-module/components/block-settings-menu/index.js +3 -6
  110. package/build-module/components/block-settings-menu/index.js.map +1 -1
  111. package/build-module/components/block-switcher/index.js +10 -16
  112. package/build-module/components/block-switcher/index.js.map +1 -1
  113. package/build-module/components/block-toolbar/index.js +4 -1
  114. package/build-module/components/block-toolbar/index.js.map +1 -1
  115. package/build-module/components/border-radius-control/all-input-control.js +32 -4
  116. package/build-module/components/border-radius-control/all-input-control.js.map +1 -1
  117. package/build-module/components/border-radius-control/index.js +20 -6
  118. package/build-module/components/border-radius-control/index.js.map +1 -1
  119. package/build-module/components/border-radius-control/input-controls.js +22 -7
  120. package/build-module/components/border-radius-control/input-controls.js.map +1 -1
  121. package/build-module/components/border-radius-control/utils.js +13 -16
  122. package/build-module/components/border-radius-control/utils.js.map +1 -1
  123. package/build-module/components/colors/with-colors.js +16 -3
  124. package/build-module/components/colors/with-colors.js.map +1 -1
  125. package/build-module/components/copy-handler/index.js +7 -1
  126. package/build-module/components/copy-handler/index.js.map +1 -1
  127. package/build-module/components/date-format-picker/index.js +2 -6
  128. package/build-module/components/date-format-picker/index.js.map +1 -1
  129. package/build-module/components/duotone/components.js +5 -5
  130. package/build-module/components/duotone/components.js.map +1 -1
  131. package/build-module/components/font-family/index.js +1 -1
  132. package/build-module/components/font-family/index.js.map +1 -1
  133. package/build-module/components/font-sizes/with-font-sizes.js +16 -3
  134. package/build-module/components/font-sizes/with-font-sizes.js.map +1 -1
  135. package/build-module/components/index.js +1 -0
  136. package/build-module/components/index.js.map +1 -1
  137. package/build-module/components/inserter/search-items.js +19 -5
  138. package/build-module/components/inserter/search-items.js.map +1 -1
  139. package/build-module/components/link-control/link-preview.js +0 -1
  140. package/build-module/components/link-control/link-preview.js.map +1 -1
  141. package/build-module/components/list-view/block-select-button.js +5 -2
  142. package/build-module/components/list-view/block-select-button.js.map +1 -1
  143. package/build-module/components/list-view/use-block-selection.js +1 -6
  144. package/build-module/components/list-view/use-block-selection.js.map +1 -1
  145. package/build-module/components/rich-text/use-enter.js +0 -4
  146. package/build-module/components/rich-text/use-enter.js.map +1 -1
  147. package/build-module/components/rich-text/use-format-types.js +8 -10
  148. package/build-module/components/rich-text/use-format-types.js.map +1 -1
  149. package/build-module/components/spacing-sizes-control/all-input-control.js +41 -0
  150. package/build-module/components/spacing-sizes-control/all-input-control.js.map +1 -0
  151. package/build-module/components/spacing-sizes-control/axial-input-controls.js +57 -0
  152. package/build-module/components/spacing-sizes-control/axial-input-controls.js.map +1 -0
  153. package/build-module/components/spacing-sizes-control/index.js +83 -0
  154. package/build-module/components/spacing-sizes-control/index.js.map +1 -0
  155. package/build-module/components/spacing-sizes-control/input-controls.js +41 -0
  156. package/build-module/components/spacing-sizes-control/input-controls.js.map +1 -0
  157. package/build-module/components/spacing-sizes-control/linked-button.js +28 -0
  158. package/build-module/components/spacing-sizes-control/linked-button.js.map +1 -0
  159. package/build-module/components/spacing-sizes-control/spacing-input-control.js +192 -0
  160. package/build-module/components/spacing-sizes-control/spacing-input-control.js.map +1 -0
  161. package/build-module/components/spacing-sizes-control/utils.js +174 -0
  162. package/build-module/components/spacing-sizes-control/utils.js.map +1 -0
  163. package/build-module/components/url-input/index.js +1 -1
  164. package/build-module/components/url-input/index.js.map +1 -1
  165. package/build-module/components/writing-flow/use-multi-selection.js +4 -2
  166. package/build-module/components/writing-flow/use-multi-selection.js.map +1 -1
  167. package/build-module/components/writing-flow/use-selection-observer.js +10 -2
  168. package/build-module/components/writing-flow/use-selection-observer.js.map +1 -1
  169. package/build-module/hooks/border-radius.js +2 -7
  170. package/build-module/hooks/border-radius.js.map +1 -1
  171. package/build-module/hooks/border.js +2 -2
  172. package/build-module/hooks/border.js.map +1 -1
  173. package/build-module/hooks/color.js +4 -1
  174. package/build-module/hooks/color.js.map +1 -1
  175. package/build-module/hooks/dimensions.js +13 -0
  176. package/build-module/hooks/dimensions.js.map +1 -1
  177. package/build-module/hooks/duotone.js +4 -4
  178. package/build-module/hooks/duotone.js.map +1 -1
  179. package/build-module/hooks/gap.js +3 -2
  180. package/build-module/hooks/gap.js.map +1 -1
  181. package/build-module/hooks/generated-class-name.js +1 -6
  182. package/build-module/hooks/generated-class-name.js.map +1 -1
  183. package/build-module/hooks/layout.js +20 -12
  184. package/build-module/hooks/layout.js.map +1 -1
  185. package/build-module/hooks/margin.js +26 -12
  186. package/build-module/hooks/margin.js.map +1 -1
  187. package/build-module/hooks/padding.js +17 -8
  188. package/build-module/hooks/padding.js.map +1 -1
  189. package/build-module/hooks/style.js +7 -53
  190. package/build-module/hooks/style.js.map +1 -1
  191. package/build-module/layouts/constrained.js +197 -0
  192. package/build-module/layouts/constrained.js.map +1 -0
  193. package/build-module/layouts/flex.js +1 -1
  194. package/build-module/layouts/flex.js.map +1 -1
  195. package/build-module/layouts/flow.js +8 -163
  196. package/build-module/layouts/flow.js.map +1 -1
  197. package/build-module/layouts/index.js +2 -1
  198. package/build-module/layouts/index.js.map +1 -1
  199. package/build-module/layouts/utils.js +40 -0
  200. package/build-module/layouts/utils.js.map +1 -1
  201. package/build-module/store/actions.js +25 -3
  202. package/build-module/store/actions.js.map +1 -1
  203. package/build-module/store/selectors.js +5 -7
  204. package/build-module/store/selectors.js.map +1 -1
  205. package/build-style/style-rtl.css +115 -20
  206. package/build-style/style.css +115 -20
  207. package/package.json +30 -28
  208. package/src/components/block-alignment-control/use-available-alignments.js +1 -1
  209. package/src/components/block-edit-visually-button/index.js +39 -0
  210. package/src/components/block-popover/inbetween.js +4 -1
  211. package/src/components/block-settings-menu/index.js +11 -15
  212. package/src/components/block-switcher/index.js +9 -13
  213. package/src/components/block-switcher/test/index.js +1 -0
  214. package/src/components/block-toolbar/index.js +2 -0
  215. package/src/components/border-radius-control/all-input-control.js +41 -4
  216. package/src/components/border-radius-control/index.js +25 -5
  217. package/src/components/border-radius-control/input-controls.js +40 -13
  218. package/src/components/border-radius-control/test/utils.js +22 -60
  219. package/src/components/border-radius-control/utils.js +12 -16
  220. package/src/components/colors/with-colors.js +11 -1
  221. package/src/components/copy-handler/index.js +18 -0
  222. package/src/components/date-format-picker/index.js +12 -14
  223. package/src/components/date-format-picker/style.scss +0 -4
  224. package/src/components/duotone/components.js +5 -5
  225. package/src/components/duotone-control/style.scss +0 -4
  226. package/src/components/font-appearance-control/style.scss +0 -2
  227. package/src/components/font-family/index.js +1 -1
  228. package/src/components/font-sizes/with-font-sizes.js +11 -1
  229. package/src/components/index.js +1 -0
  230. package/src/components/inserter/search-items.js +17 -5
  231. package/src/components/link-control/link-preview.js +0 -1
  232. package/src/components/link-control/test/index.js +540 -893
  233. package/src/components/list-view/block-select-button.js +7 -2
  234. package/src/components/list-view/style.scss +11 -4
  235. package/src/components/list-view/use-block-selection.js +2 -8
  236. package/src/components/media-replace-flow/style.scss +1 -0
  237. package/src/components/rich-text/use-enter.js +0 -3
  238. package/src/components/rich-text/use-format-types.js +6 -6
  239. package/src/components/spacing-sizes-control/all-input-control.js +40 -0
  240. package/src/components/spacing-sizes-control/axial-input-controls.js +62 -0
  241. package/src/components/spacing-sizes-control/index.js +91 -0
  242. package/src/components/spacing-sizes-control/input-controls.js +46 -0
  243. package/src/components/spacing-sizes-control/linked-button.js +25 -0
  244. package/src/components/spacing-sizes-control/spacing-input-control.js +280 -0
  245. package/src/components/spacing-sizes-control/style.scss +122 -0
  246. package/src/components/spacing-sizes-control/test/utils.js +156 -0
  247. package/src/components/spacing-sizes-control/utils.js +195 -0
  248. package/src/components/url-input/index.js +1 -1
  249. package/src/components/url-input/style.scss +2 -2
  250. package/src/components/url-popover/style.scss +0 -3
  251. package/src/components/writing-flow/use-multi-selection.js +4 -1
  252. package/src/components/writing-flow/use-selection-observer.js +10 -2
  253. package/src/hooks/border-radius.js +2 -6
  254. package/src/hooks/border.js +2 -2
  255. package/src/hooks/color.js +13 -3
  256. package/src/hooks/dimensions.js +15 -0
  257. package/src/hooks/duotone.js +4 -4
  258. package/src/hooks/gap.js +7 -2
  259. package/src/hooks/generated-class-name.js +6 -9
  260. package/src/hooks/layout.js +45 -14
  261. package/src/hooks/margin.js +49 -17
  262. package/src/hooks/padding.js +41 -14
  263. package/src/hooks/style.js +5 -56
  264. package/src/hooks/test/gap.js +22 -0
  265. package/src/hooks/typography.scss +0 -1
  266. package/src/layouts/constrained.js +217 -0
  267. package/src/layouts/flex.js +1 -1
  268. package/src/layouts/flow.js +6 -173
  269. package/src/layouts/index.js +2 -1
  270. package/src/layouts/test/constrained.js +21 -0
  271. package/src/layouts/utils.js +34 -0
  272. package/src/store/actions.js +32 -4
  273. package/src/store/selectors.js +5 -4
  274. package/src/style.scss +1 -0
  275. package/build/components/block-settings-menu/block-edit-visually-button.js +0 -70
  276. package/build/components/block-settings-menu/block-edit-visually-button.js.map +0 -1
  277. package/build-module/components/block-settings-menu/block-edit-visually-button.js +0 -56
  278. package/build-module/components/block-settings-menu/block-edit-visually-button.js.map +0 -1
  279. package/src/components/block-settings-menu/block-edit-visually-button.js +0 -52
@@ -1,14 +1,14 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import { render, unmountComponentAtNode } from 'react-dom';
5
- import { act, Simulate } from 'react-dom/test-utils';
6
- import { queryByText, queryByRole } from '@testing-library/react';
4
+ import { act, fireEvent, render, screen } from '@testing-library/react';
5
+ import userEvent from '@testing-library/user-event';
6
+
7
7
  /**
8
8
  * WordPress dependencies
9
9
  */
10
10
  import { useState } from '@wordpress/element';
11
- import { UP, DOWN, ENTER } from '@wordpress/keycodes';
11
+
12
12
  /**
13
13
  * WordPress dependencies
14
14
  */
@@ -71,33 +71,26 @@ function eventLoopTick() {
71
71
  return new Promise( ( resolve ) => setImmediate( resolve ) );
72
72
  }
73
73
 
74
- let container = null;
75
-
76
74
  beforeEach( () => {
77
75
  // Setup a DOM element as a render target.
78
- container = document.createElement( 'div' );
79
- document.body.appendChild( container );
80
76
  mockFetchSearchSuggestions.mockImplementation( fetchFauxEntitySuggestions );
81
77
  } );
82
78
 
83
79
  afterEach( () => {
84
80
  // Cleanup on exiting.
85
- unmountComponentAtNode( container );
86
- container.remove();
87
- container = null;
88
81
  mockFetchSearchSuggestions.mockReset();
89
82
  mockFetchRichUrlData?.mockReset(); // Conditionally reset as it may NOT be a mock.
90
83
  } );
91
84
 
92
85
  function getURLInput() {
93
- return container.querySelector( 'input[aria-label="URL"]' );
86
+ return screen.queryByRole( 'combobox', { name: 'URL' } );
94
87
  }
95
88
 
96
- function getSearchResults() {
89
+ function getSearchResults( container ) {
97
90
  const input = getURLInput();
98
- // The input has `aria-owns` to indicate that it owns (and is related to)
91
+ // The input has `aria-controls` to indicate that it owns (and is related to)
99
92
  // the search results with `role="listbox"`.
100
- const relatedSelector = input.getAttribute( 'aria-owns' );
93
+ const relatedSelector = input.getAttribute( 'aria-controls' );
101
94
 
102
95
  // Select by relationship as well as role.
103
96
  return container.querySelectorAll(
@@ -106,24 +99,91 @@ function getSearchResults() {
106
99
  }
107
100
 
108
101
  function getCurrentLink() {
109
- return container.querySelector(
110
- '.block-editor-link-control__search-item.is-current'
111
- );
102
+ return screen.queryByLabelText( 'Currently selected' );
103
+ }
104
+
105
+ function getSelectedResultElement() {
106
+ return screen.queryByRole( 'option', { selected: true } );
107
+ }
108
+
109
+ /**
110
+ * Workaround to trigger an arrow up keypress event.
111
+ *
112
+ * @todo Remove this workaround in favor of userEvent.keyboard() or userEvent.type().
113
+ *
114
+ * For some reason, this doesn't work:
115
+ *
116
+ * ```
117
+ * await user.keyboard( '[ArrowDown]' );
118
+ * ```
119
+ *
120
+ * because the event sent has a `keyCode` of `0`.
121
+ *
122
+ * @param {Element} element Element to trigger the event on.
123
+ */
124
+ function triggerArrowUp( element ) {
125
+ fireEvent.keyDown( element, {
126
+ key: 'ArrowUp',
127
+ keyCode: 38,
128
+ } );
129
+ }
130
+
131
+ /**
132
+ * Workaround to trigger an arrow down keypress event.
133
+ *
134
+ * @todo Remove this workaround in favor of userEvent.keyboard() or userEvent.type().
135
+ *
136
+ * For some reason, this doesn't work:
137
+ *
138
+ * ```
139
+ * await user.keyboard( '[ArrowDown]' );
140
+ * ```
141
+ *
142
+ * because the event sent has a `keyCode` of `0`.
143
+ *
144
+ * @param {Element} element Element to trigger the event on.
145
+ */
146
+ function triggerArrowDown( element ) {
147
+ fireEvent.keyDown( element, {
148
+ key: 'ArrowDown',
149
+ keyCode: 40,
150
+ } );
151
+ }
152
+
153
+ /**
154
+ * Workaround to trigger an Enter keypress event.
155
+ *
156
+ * @todo Remove this workaround in favor of userEvent.keyboard() or userEvent.type().
157
+ *
158
+ * For some reason, this doesn't work:
159
+ *
160
+ * ```
161
+ * await user.keyboard( '[Enter]' );
162
+ * ```
163
+ *
164
+ * because the event sent has a `keyCode` of `0`.
165
+ *
166
+ * @param {Element} element Element to trigger the event on.
167
+ */
168
+ function triggerEnter( element ) {
169
+ fireEvent.keyDown( element, {
170
+ key: 'Enter',
171
+ keyCode: 13,
172
+ } );
112
173
  }
113
174
 
114
175
  describe( 'Basic rendering', () => {
115
176
  it( 'should render', () => {
116
- act( () => {
117
- render( <LinkControl />, container );
118
- } );
177
+ render( <LinkControl /> );
119
178
 
120
179
  // Search Input UI.
121
180
  const searchInput = getURLInput();
122
181
 
123
- expect( searchInput ).not.toBeNull();
182
+ expect( searchInput ).toBeInTheDocument();
124
183
  } );
125
184
 
126
185
  it( 'should not render protocol in links', async () => {
186
+ const user = userEvent.setup();
127
187
  mockFetchSearchSuggestions.mockImplementation( () =>
128
188
  Promise.resolve( [
129
189
  {
@@ -145,94 +205,61 @@ describe( 'Basic rendering', () => {
145
205
 
146
206
  const searchTerm = 'Hello';
147
207
 
148
- act( () => {
149
- render( <LinkControl />, container );
150
- } );
208
+ render( <LinkControl /> );
151
209
 
152
210
  // Search Input UI.
153
211
  const searchInput = getURLInput();
154
212
 
155
213
  // Simulate searching for a term.
156
- act( () => {
157
- Simulate.change( searchInput, { target: { value: searchTerm } } );
158
- } );
159
-
160
- // fetchFauxEntitySuggestions resolves on next "tick" of event loop.
161
- await eventLoopTick();
162
-
163
- // Find all elements with link
164
- // Filter out the element with the text 'ENTER' because it doesn't contain link.
165
- const linkElements = Array.from(
166
- container.querySelectorAll(
167
- '.block-editor-link-control__search-item-info'
168
- )
169
- ).filter( ( elem ) => ! elem.innerHTML.includes( 'ENTER' ) );
214
+ searchInput.focus();
215
+ await user.keyboard( searchTerm );
170
216
 
171
- linkElements.forEach( ( elem ) => {
172
- expect( elem.innerHTML ).not.toContain( '://' );
173
- } );
217
+ expect( screen.queryByText( '://' ) ).not.toBeInTheDocument();
174
218
  } );
175
219
 
176
220
  describe( 'forceIsEditingLink', () => {
177
- const isEditing = () => !! getURLInput();
178
-
179
221
  it( 'undefined', () => {
180
- act( () => {
181
- render(
182
- <LinkControl value={ { url: 'https://example.com' } } />,
183
- container
184
- );
185
- } );
222
+ render( <LinkControl value={ { url: 'https://example.com' } } /> );
186
223
 
187
- expect( isEditing() ).toBe( false );
224
+ expect( getURLInput() ).not.toBeInTheDocument();
188
225
  } );
189
226
 
190
227
  it( 'true', () => {
191
- act( () => {
192
- render(
193
- <LinkControl
194
- value={ { url: 'https://example.com' } }
195
- forceIsEditingLink
196
- />,
197
- container
198
- );
199
- } );
228
+ render(
229
+ <LinkControl
230
+ value={ { url: 'https://example.com' } }
231
+ forceIsEditingLink
232
+ />
233
+ );
200
234
 
201
- expect( isEditing() ).toBe( true );
235
+ expect( getURLInput() ).toBeVisible();
202
236
  } );
203
237
 
204
- it( 'false', () => {
205
- act( () => {
206
- render(
207
- <LinkControl value={ { url: 'https://example.com' } } />,
208
- container
209
- );
210
- } );
238
+ it( 'false', async () => {
239
+ const user = userEvent.setup();
240
+ const { rerender } = render(
241
+ <LinkControl value={ { url: 'https://example.com' } } />
242
+ );
211
243
 
212
244
  // Click the "Edit" button to trigger into the editing mode.
213
- const editButton = queryByRole( container, 'button', {
245
+ const editButton = screen.queryByRole( 'button', {
214
246
  name: 'Edit',
215
247
  } );
216
248
 
217
- act( () => {
218
- Simulate.click( editButton );
219
- } );
249
+ await user.click( editButton );
220
250
 
221
- expect( isEditing() ).toBe( true );
251
+ expect( getURLInput() ).toBeVisible();
222
252
 
223
253
  // If passed `forceIsEditingLink` of `false` while editing, should
224
254
  // forcefully reset to the preview state.
225
- act( () => {
226
- render(
227
- <LinkControl
228
- value={ { url: 'https://example.com' } }
229
- forceIsEditingLink={ false }
230
- />,
231
- container
232
- );
233
- } );
255
+ rerender(
256
+ <LinkControl
257
+ value={ { url: 'https://example.com' } }
258
+ forceIsEditingLink={ false }
259
+ />
260
+ );
234
261
 
235
- expect( isEditing() ).toBe( false );
262
+ expect( getURLInput() ).not.toBeInTheDocument();
236
263
  } );
237
264
 
238
265
  it( 'should display human friendly error message if value URL prop is empty when component is forced into no-editing (preview) mode', async () => {
@@ -249,65 +276,49 @@ describe( 'Basic rendering', () => {
249
276
  type: 'post',
250
277
  };
251
278
 
252
- act( () => {
253
- render(
254
- <LinkControl
255
- value={ valueWithEmptyURL }
256
- forceIsEditingLink={ false }
257
- />,
258
- container
259
- );
260
- } );
279
+ render(
280
+ <LinkControl
281
+ value={ valueWithEmptyURL }
282
+ forceIsEditingLink={ false }
283
+ />
284
+ );
261
285
 
262
- const linkPreview = queryByRole( container, 'generic', {
263
- name: 'Currently selected',
264
- } );
286
+ const linkPreview = getCurrentLink();
265
287
 
266
288
  const isPreviewError = linkPreview.classList.contains( 'is-error' );
267
289
  expect( isPreviewError ).toBe( true );
268
290
 
269
- expect( queryByText( linkPreview, 'Link is empty' ) ).toBeTruthy();
291
+ expect( screen.queryByText( 'Link is empty' ) ).toBeVisible();
270
292
  } );
271
293
  } );
272
294
 
273
295
  describe( 'Unlinking', () => {
274
296
  it( 'should not show "Unlink" button if no onRemove handler is provided', () => {
275
- act( () => {
276
- render(
277
- <LinkControl value={ { url: 'https://example.com' } } />,
278
- container
279
- );
280
- } );
297
+ render( <LinkControl value={ { url: 'https://example.com' } } /> );
281
298
 
282
- const unLinkButton = queryByRole( container, 'button', {
299
+ const unLinkButton = screen.queryByRole( 'button', {
283
300
  name: 'Unlink',
284
301
  } );
285
302
 
286
- expect( unLinkButton ).toBeNull();
287
303
  expect( unLinkButton ).not.toBeInTheDocument();
288
304
  } );
289
305
 
290
- it( 'should show "Unlink" button if a onRemove handler is provided', () => {
306
+ it( 'should show "Unlink" button if a onRemove handler is provided', async () => {
307
+ const user = userEvent.setup();
291
308
  const mockOnRemove = jest.fn();
292
- act( () => {
293
- render(
294
- <LinkControl
295
- value={ { url: 'https://example.com' } }
296
- onRemove={ mockOnRemove }
297
- />,
298
- container
299
- );
300
- } );
309
+ render(
310
+ <LinkControl
311
+ value={ { url: 'https://example.com' } }
312
+ onRemove={ mockOnRemove }
313
+ />
314
+ );
301
315
 
302
- const unLinkButton = queryByRole( container, 'button', {
316
+ const unLinkButton = screen.queryByRole( 'button', {
303
317
  name: 'Unlink',
304
318
  } );
305
- expect( unLinkButton ).toBeTruthy();
306
- expect( unLinkButton ).toBeInTheDocument();
319
+ expect( unLinkButton ).toBeVisible();
307
320
 
308
- act( () => {
309
- Simulate.click( unLinkButton );
310
- } );
321
+ await user.click( unLinkButton );
311
322
 
312
323
  expect( mockOnRemove ).toHaveBeenCalled();
313
324
  } );
@@ -316,6 +327,7 @@ describe( 'Basic rendering', () => {
316
327
 
317
328
  describe( 'Searching for a link', () => {
318
329
  it( 'should display loading UI when input is valid but search results have yet to be returned', async () => {
330
+ const user = userEvent.setup();
319
331
  const searchTerm = 'Hello';
320
332
 
321
333
  let resolver;
@@ -327,28 +339,25 @@ describe( 'Searching for a link', () => {
327
339
 
328
340
  mockFetchSearchSuggestions.mockImplementation( fauxRequest );
329
341
 
330
- act( () => {
331
- render( <LinkControl />, container );
332
- } );
342
+ const { container } = render( <LinkControl /> );
333
343
 
334
344
  // Search Input UI.
335
345
  const searchInput = getURLInput();
336
346
 
337
347
  // Simulate searching for a term.
338
- act( () => {
339
- Simulate.change( searchInput, { target: { value: searchTerm } } );
340
- } );
348
+ searchInput.focus();
349
+ await user.keyboard( searchTerm );
341
350
 
342
351
  // fetchFauxEntitySuggestions resolves on next "tick" of event loop.
343
352
  await eventLoopTick();
344
353
 
345
- const searchResultElements = getSearchResults();
354
+ const searchResultElements = getSearchResults( container );
346
355
 
347
- let loadingUI = container.querySelector( '.components-spinner' );
356
+ let loadingUI = screen.queryByRole( 'presentation' );
348
357
 
349
358
  expect( searchResultElements ).toHaveLength( 0 );
350
359
 
351
- expect( loadingUI ).not.toBeNull();
360
+ expect( loadingUI ).toBeVisible();
352
361
 
353
362
  act( () => {
354
363
  resolver( fauxEntitySuggestions );
@@ -356,36 +365,29 @@ describe( 'Searching for a link', () => {
356
365
 
357
366
  await eventLoopTick();
358
367
 
359
- loadingUI = container.querySelector( '.components-spinner' );
368
+ loadingUI = screen.queryByRole( 'presentation' );
360
369
 
361
- expect( loadingUI ).toBeNull();
370
+ expect( loadingUI ).not.toBeInTheDocument();
362
371
  } );
363
372
 
364
373
  it( 'should display only search suggestions when current input value is not URL-like', async () => {
374
+ const user = userEvent.setup();
365
375
  const searchTerm = 'Hello world';
366
376
  const firstFauxSuggestion = fauxEntitySuggestions[ 0 ];
367
377
 
368
- act( () => {
369
- render( <LinkControl />, container );
370
- } );
378
+ const { container } = render( <LinkControl /> );
371
379
 
372
380
  // Search Input UI.
373
381
  const searchInput = getURLInput();
374
382
 
375
383
  // Simulate searching for a term.
376
- act( () => {
377
- Simulate.change( searchInput, { target: { value: searchTerm } } );
378
- } );
384
+ searchInput.focus();
385
+ await user.keyboard( searchTerm );
379
386
 
380
387
  // fetchFauxEntitySuggestions resolves on next "tick" of event loop.
381
388
  await eventLoopTick();
382
- // TODO: select these by aria relationship to autocomplete rather than arbitrary selector.
383
389
 
384
- const searchResultElements = getSearchResults();
385
-
386
- const firstSearchResultItemHTML = searchResultElements[ 0 ].innerHTML;
387
- const lastSearchResultItemHTML =
388
- searchResultElements[ searchResultElements.length - 1 ].innerHTML;
390
+ const searchResultElements = getSearchResults( container );
389
391
 
390
392
  expect( searchResultElements ).toHaveLength(
391
393
  fauxEntitySuggestions.length
@@ -394,35 +396,31 @@ describe( 'Searching for a link', () => {
394
396
  expect( searchInput.getAttribute( 'aria-expanded' ) ).toBe( 'true' );
395
397
 
396
398
  // Sanity check that a search suggestion shows up corresponding to the data.
397
- expect( firstSearchResultItemHTML ).toEqual(
398
- expect.stringContaining( firstFauxSuggestion.title )
399
+ expect( searchResultElements[ 0 ] ).toHaveTextContent(
400
+ firstFauxSuggestion.title
399
401
  );
400
- expect( firstSearchResultItemHTML ).toEqual(
401
- expect.stringContaining( firstFauxSuggestion.type )
402
+ expect( searchResultElements[ 0 ] ).toHaveTextContent(
403
+ firstFauxSuggestion.type
402
404
  );
403
405
 
404
406
  // The fallback URL suggestion should not be shown when input is not URL-like.
405
- expect( lastSearchResultItemHTML ).not.toEqual(
406
- expect.stringContaining( 'URL' )
407
- );
407
+ expect(
408
+ searchResultElements[ searchResultElements.length - 1 ]
409
+ ).not.toHaveTextContent( 'URL' );
408
410
  } );
409
411
 
410
412
  it( 'should trim search term', async () => {
413
+ const user = userEvent.setup();
411
414
  const searchTerm = ' Hello ';
412
415
 
413
- act( () => {
414
- render( <LinkControl />, container );
415
- } );
416
+ const { container } = render( <LinkControl /> );
416
417
 
417
418
  // Search Input UI.
418
- const searchInput = container.querySelector(
419
- 'input[aria-label="URL"]'
420
- );
419
+ const searchInput = getURLInput();
421
420
 
422
421
  // Simulate searching for a term.
423
- act( () => {
424
- Simulate.change( searchInput, { target: { value: searchTerm } } );
425
- } );
422
+ searchInput.focus();
423
+ await user.keyboard( searchTerm );
426
424
 
427
425
  // fetchFauxEntitySuggestions resolves on next "tick" of event loop.
428
426
  await eventLoopTick();
@@ -437,11 +435,6 @@ describe( 'Searching for a link', () => {
437
435
  ( mark ) => mark.innerHTML !== 'Hello'
438
436
  );
439
437
 
440
- // Grab the first argument that was passed to the fetchSuggestions
441
- // handler (which is mocked out).
442
- const mockFetchSuggestionsFirstArg =
443
- mockFetchSearchSuggestions.mock.calls[ 0 ][ 0 ];
444
-
445
438
  // Given we're mocking out the results we should always have 4 mark elements.
446
439
  expect( searchResultTextHighlightElements ).toHaveLength( 4 );
447
440
 
@@ -453,30 +446,30 @@ describe( 'Searching for a link', () => {
453
446
  // with the trimmed search value. We do this because we are mocking out
454
447
  // the fetch handler in our test so we need to assert it would be called
455
448
  // correctly in a real world scenario.
456
- expect( mockFetchSuggestionsFirstArg ).toEqual( 'Hello' );
449
+ expect( mockFetchSearchSuggestions ).toHaveBeenCalledWith(
450
+ 'Hello',
451
+ expect.anything()
452
+ );
457
453
  } );
458
454
 
459
455
  it( 'should not call search handler when showSuggestions is false', async () => {
460
- act( () => {
461
- render( <LinkControl showSuggestions={ false } />, container );
462
- } );
456
+ const user = userEvent.setup();
457
+ const { container } = render(
458
+ <LinkControl showSuggestions={ false } />
459
+ );
463
460
 
464
461
  // Search Input UI.
465
462
  const searchInput = getURLInput();
466
463
 
467
464
  // Simulate searching for a term.
468
- act( () => {
469
- Simulate.change( searchInput, {
470
- target: { value: 'anything' },
471
- } );
472
- } );
465
+ searchInput.focus();
466
+ await user.keyboard( 'anything' );
473
467
 
474
- const searchResultElements = getSearchResults();
468
+ const searchResultElements = getSearchResults( container );
475
469
 
476
470
  // fetchFauxEntitySuggestions resolves on next "tick" of event loop.
477
471
  await eventLoopTick();
478
472
 
479
- // TODO: select these by aria relationship to autocomplete rather than arbitrary selector.
480
473
  expect( searchResultElements ).toHaveLength( 0 );
481
474
  expect( mockFetchSearchSuggestions ).not.toHaveBeenCalled();
482
475
  } );
@@ -487,71 +480,54 @@ describe( 'Searching for a link', () => {
487
480
  ] )(
488
481
  'should display a URL suggestion as a default fallback for the search term "%s" which could potentially be a valid url.',
489
482
  async ( searchTerm ) => {
490
- act( () => {
491
- render( <LinkControl />, container );
492
- } );
483
+ const user = userEvent.setup();
484
+ const { container } = render( <LinkControl /> );
493
485
 
494
486
  // Search Input UI.
495
487
  const searchInput = getURLInput();
496
488
 
497
489
  // Simulate searching for a term.
498
- act( () => {
499
- Simulate.change( searchInput, {
500
- target: { value: searchTerm },
501
- } );
502
- } );
490
+ searchInput.focus();
491
+ await user.keyboard( searchTerm );
503
492
 
504
493
  // fetchFauxEntitySuggestions resolves on next "tick" of event loop.
505
494
  await eventLoopTick();
506
- // TODO: select these by aria relationship to autocomplete rather than arbitrary selector.
507
495
 
508
- const searchResultElements = getSearchResults();
496
+ const searchResultElements = getSearchResults( container );
509
497
 
510
- const lastSearchResultItemHTML =
511
- searchResultElements[ searchResultElements.length - 1 ]
512
- .innerHTML;
513
- const additionalDefaultFallbackURLSuggestionLength = 1;
498
+ const lastSearchResultItem =
499
+ searchResultElements[ searchResultElements.length - 1 ];
514
500
 
515
501
  // We should see a search result for each of the expect search suggestions
516
502
  // plus 1 additional one for the fallback URL suggestion.
517
503
  expect( searchResultElements ).toHaveLength(
518
- fauxEntitySuggestions.length +
519
- additionalDefaultFallbackURLSuggestionLength
504
+ fauxEntitySuggestions.length + 1
520
505
  );
521
506
 
522
507
  // The last item should be a URL search suggestion.
523
- expect( lastSearchResultItemHTML ).toEqual(
524
- expect.stringContaining( searchTerm )
525
- );
526
- expect( lastSearchResultItemHTML ).toEqual(
527
- expect.stringContaining( 'URL' )
528
- );
529
- expect( lastSearchResultItemHTML ).toEqual(
530
- expect.stringContaining( 'Press ENTER to add this link' )
508
+ expect( lastSearchResultItem ).toHaveTextContent( searchTerm );
509
+ expect( lastSearchResultItem ).toHaveTextContent( 'URL' );
510
+ expect( lastSearchResultItem ).toHaveTextContent(
511
+ 'Press ENTER to add this link'
531
512
  );
532
513
  }
533
514
  );
534
515
 
535
516
  it( 'should not display a URL suggestion as a default fallback when noURLSuggestion is passed.', async () => {
536
- act( () => {
537
- render( <LinkControl noURLSuggestion />, container );
538
- } );
517
+ const user = userEvent.setup();
518
+ const { container } = render( <LinkControl noURLSuggestion /> );
539
519
 
540
520
  // Search Input UI.
541
521
  const searchInput = getURLInput();
542
522
 
543
523
  // Simulate searching for a term.
544
- act( () => {
545
- Simulate.change( searchInput, {
546
- target: { value: 'couldbeurlorentitysearchterm' },
547
- } );
548
- } );
524
+ searchInput.focus();
525
+ await user.keyboard( 'couldbeurlorentitysearchterm' );
549
526
 
550
527
  // fetchFauxEntitySuggestions resolves on next "tick" of event loop.
551
528
  await eventLoopTick();
552
- // TODO: select these by aria relationship to autocomplete rather than arbitrary selector.
553
529
 
554
- const searchResultElements = getSearchResults();
530
+ const searchResultElements = getSearchResults( container );
555
531
 
556
532
  // We should see a search result for each of the expect search suggestions and nothing else.
557
533
  expect( searchResultElements ).toHaveLength(
@@ -568,40 +544,26 @@ describe( 'Manual link entry', () => {
568
544
  ] )(
569
545
  'should display a single suggestion result when the current input value is URL-like (eg: %s)',
570
546
  async ( searchTerm ) => {
571
- act( () => {
572
- render( <LinkControl />, container );
573
- } );
547
+ const user = userEvent.setup();
548
+ const { container } = render( <LinkControl /> );
574
549
 
575
550
  // Search Input UI.
576
551
  const searchInput = getURLInput();
577
552
 
578
553
  // Simulate searching for a term.
579
- act( () => {
580
- Simulate.change( searchInput, {
581
- target: { value: searchTerm },
582
- } );
583
- } );
554
+ searchInput.focus();
555
+ await user.keyboard( searchTerm );
584
556
 
585
557
  // fetchFauxEntitySuggestions resolves on next "tick" of event loop.
586
558
  await eventLoopTick();
587
559
 
588
- const searchResultElements = getSearchResults();
560
+ const searchResultElements = getSearchResults( container );
589
561
 
590
- const firstSearchResultItemHTML =
591
- searchResultElements[ 0 ].innerHTML;
592
- const expectedResultsLength = 1;
593
-
594
- expect( searchResultElements ).toHaveLength(
595
- expectedResultsLength
596
- );
597
- expect( firstSearchResultItemHTML ).toEqual(
598
- expect.stringContaining( searchTerm )
599
- );
600
- expect( firstSearchResultItemHTML ).toEqual(
601
- expect.stringContaining( 'URL' )
602
- );
603
- expect( firstSearchResultItemHTML ).toEqual(
604
- expect.stringContaining( 'Press ENTER to add this link' )
562
+ expect( searchResultElements ).toHaveLength( 1 );
563
+ expect( searchResultElements[ 0 ] ).toHaveTextContent( searchTerm );
564
+ expect( searchResultElements[ 0 ] ).toHaveTextContent( 'URL' );
565
+ expect( searchResultElements[ 0 ] ).toHaveTextContent(
566
+ 'Press ENTER to add this link'
605
567
  );
606
568
  }
607
569
  );
@@ -609,97 +571,94 @@ describe( 'Manual link entry', () => {
609
571
  describe( 'Handling of empty values', () => {
610
572
  const testTable = [
611
573
  [ 'containing only spaces', ' ' ],
612
- [ 'containing only tabs', ' ' ],
574
+ [ 'containing only tabs', '[Tab]' ],
613
575
  [ 'from strings with no length', '' ],
614
576
  ];
615
577
 
616
578
  it.each( testTable )(
617
579
  'should not allow creation of links %s when using the keyboard',
618
580
  async ( _desc, searchString ) => {
619
- act( () => {
620
- render( <LinkControl />, container );
621
- } );
581
+ const user = userEvent.setup();
582
+
583
+ render( <LinkControl /> );
622
584
 
623
585
  // Search Input UI.
624
586
  const searchInput = getURLInput();
625
587
 
626
- let submitButton = queryByRole( container, 'button', {
588
+ let submitButton = screen.queryByRole( 'button', {
627
589
  name: 'Submit',
628
590
  } );
629
591
 
630
- expect( submitButton.disabled ).toBeTruthy();
631
- expect( submitButton ).not.toBeNull();
632
- expect( submitButton ).toBeInTheDocument();
592
+ expect( submitButton ).toBeDisabled();
593
+ expect( submitButton ).toBeVisible();
633
594
 
634
- // Simulate searching for a term.
635
- act( () => {
636
- Simulate.change( searchInput, {
637
- target: { value: searchString },
638
- } );
639
- } );
595
+ searchInput.focus();
596
+ if ( searchString.length ) {
597
+ // Simulate searching for a term.
598
+ await user.keyboard( searchString );
599
+ } else {
600
+ // Simulate clearing the search term.
601
+ await userEvent.clear( searchInput );
602
+ }
640
603
 
641
604
  // fetchFauxEntitySuggestions resolves on next "tick" of event loop.
642
605
  await eventLoopTick();
643
606
 
644
607
  // Attempt to submit the empty search value in the input.
645
- act( () => {
646
- Simulate.keyDown( searchInput, { keyCode: ENTER } );
647
- } );
608
+ await user.keyboard( '[Enter]' );
648
609
 
649
- submitButton = queryByRole( container, 'button', {
610
+ submitButton = screen.queryByRole( 'button', {
650
611
  name: 'Submit',
651
612
  } );
652
613
 
653
614
  // Verify the UI hasn't allowed submission.
654
615
  expect( searchInput ).toBeInTheDocument();
655
- expect( submitButton.disabled ).toBeTruthy();
656
- expect( submitButton ).not.toBeNull();
657
- expect( submitButton ).toBeInTheDocument();
616
+ expect( submitButton ).toBeDisabled();
617
+ expect( submitButton ).toBeVisible();
658
618
  }
659
619
  );
660
620
 
661
621
  it.each( testTable )(
662
622
  'should not allow creation of links %s via the UI "submit" button',
663
623
  async ( _desc, searchString ) => {
664
- act( () => {
665
- render( <LinkControl />, container );
666
- } );
624
+ const user = userEvent.setup();
625
+
626
+ render( <LinkControl /> );
667
627
 
668
628
  // Search Input UI.
669
629
  const searchInput = getURLInput();
670
630
 
671
- let submitButton = queryByRole( container, 'button', {
631
+ let submitButton = screen.queryByRole( 'button', {
672
632
  name: 'Submit',
673
633
  } );
674
634
 
675
- expect( submitButton.disabled ).toBeTruthy();
676
- expect( submitButton ).not.toBeNull();
677
- expect( submitButton ).toBeInTheDocument();
635
+ expect( submitButton ).toBeDisabled();
636
+ expect( submitButton ).toBeVisible();
678
637
 
679
638
  // Simulate searching for a term.
680
- act( () => {
681
- Simulate.change( searchInput, {
682
- target: { value: searchString },
683
- } );
684
- } );
639
+ searchInput.focus();
640
+ if ( searchString.length ) {
641
+ // Simulate searching for a term.
642
+ await user.keyboard( searchString );
643
+ } else {
644
+ // Simulate clearing the search term.
645
+ await userEvent.clear( searchInput );
646
+ }
685
647
 
686
648
  // fetchFauxEntitySuggestions resolves on next "tick" of event loop.
687
649
  await eventLoopTick();
688
650
 
689
651
  // Attempt to submit the empty search value in the input.
690
- act( () => {
691
- Simulate.click( submitButton );
692
- } );
652
+ await user.click( submitButton );
693
653
 
694
- submitButton = queryByRole( container, 'button', {
654
+ submitButton = screen.queryByRole( 'button', {
695
655
  name: 'Submit',
696
656
  } );
697
657
 
698
658
  // Verify the UI hasn't allowed submission.
699
659
  expect( searchInput ).toBeInTheDocument();
700
- expect( submitButton.disabled ).toBeTruthy();
701
- expect( submitButton ).not.toBeNull();
702
- expect( submitButton ).toBeInTheDocument();
660
+ expect( submitButton ).toBeDisabled();
661
+ expect( submitButton ).toBeVisible();
703
662
  }
704
663
  );
705
664
  } );
@@ -712,40 +671,30 @@ describe( 'Manual link entry', () => {
712
671
  ] )(
713
672
  'should recognise "%s" as a %s link and handle as manual entry by displaying a single suggestion',
714
673
  async ( searchTerm, searchType ) => {
715
- act( () => {
716
- render( <LinkControl />, container );
717
- } );
674
+ const user = userEvent.setup();
675
+ const { container } = render( <LinkControl /> );
718
676
 
719
677
  // Search Input UI.
720
678
  const searchInput = getURLInput();
721
679
 
722
680
  // Simulate searching for a term.
723
- act( () => {
724
- Simulate.change( searchInput, {
725
- target: { value: searchTerm },
726
- } );
727
- } );
681
+ searchInput.focus();
682
+ await user.keyboard( searchTerm );
728
683
 
729
684
  // fetchFauxEntitySuggestions resolves on next "tick" of event loop.
730
685
  await eventLoopTick();
731
686
 
732
- const searchResultElements = getSearchResults();
687
+ const searchResultElements = getSearchResults( container );
733
688
 
734
- const firstSearchResultItemHTML =
735
- searchResultElements[ 0 ].innerHTML;
736
- const expectedResultsLength = 1;
737
-
738
- expect( searchResultElements ).toHaveLength(
739
- expectedResultsLength
740
- );
741
- expect( firstSearchResultItemHTML ).toEqual(
742
- expect.stringContaining( searchTerm )
689
+ expect( searchResultElements ).toHaveLength( 1 );
690
+ expect( searchResultElements[ 0 ] ).toHaveTextContent(
691
+ searchTerm
743
692
  );
744
- expect( firstSearchResultItemHTML ).toEqual(
745
- expect.stringContaining( searchType )
693
+ expect( searchResultElements[ 0 ] ).toHaveTextContent(
694
+ searchType
746
695
  );
747
- expect( firstSearchResultItemHTML ).toEqual(
748
- expect.stringContaining( 'Press ENTER to add this link' )
696
+ expect( searchResultElements[ 0 ] ).toHaveTextContent(
697
+ 'Press ENTER to add this link'
749
698
  );
750
699
  }
751
700
  );
@@ -756,27 +705,20 @@ describe( 'Default search suggestions', () => {
756
705
  it( 'should display a list of initial search suggestions when there is no search value or suggestions', async () => {
757
706
  const expectedResultsLength = 3; // Set within `LinkControl`.
758
707
 
759
- act( () => {
760
- render( <LinkControl showInitialSuggestions />, container );
761
- } );
708
+ render( <LinkControl showInitialSuggestions /> );
762
709
 
763
710
  await eventLoopTick();
764
711
 
765
- // Search Input UI.
766
- const searchInput = getURLInput();
767
-
768
- const searchResultsWrapper =
769
- container.querySelector( '[role="listbox"]' );
770
- const initialSearchResultElements =
771
- searchResultsWrapper.querySelectorAll( '[role="option"]' );
772
-
773
- const searchResultsLabel = container.querySelector(
774
- `#${ searchResultsWrapper.getAttribute( 'aria-labelledby' ) }`
775
- );
712
+ expect(
713
+ screen.queryByRole( 'listbox', {
714
+ name: 'Recently updated',
715
+ } )
716
+ ).toBeVisible();
776
717
 
777
718
  // Verify input has no value has default suggestions should only show
778
719
  // when this does not have a value.
779
- expect( searchInput.value ).toBe( '' );
720
+ // Search Input UI.
721
+ expect( getURLInput() ).toHaveValue( '' );
780
722
 
781
723
  // Ensure only called once as a guard against potential infinite
782
724
  // re-render loop within `componentDidUpdate` calling `updateSuggestions`
@@ -784,25 +726,22 @@ describe( 'Default search suggestions', () => {
784
726
  expect( mockFetchSearchSuggestions ).toHaveBeenCalledTimes( 1 );
785
727
 
786
728
  // Verify the search results already display the initial suggestions.
787
- expect( initialSearchResultElements ).toHaveLength(
729
+ expect( screen.queryAllByRole( 'option' ) ).toHaveLength(
788
730
  expectedResultsLength
789
731
  );
790
-
791
- expect( searchResultsLabel.innerHTML ).toEqual( 'Recently updated' );
792
732
  } );
793
733
 
794
734
  it( 'should not display initial suggestions when input value is present', async () => {
735
+ const user = userEvent.setup();
736
+
795
737
  // Render with an initial value an ensure that no initial suggestions
796
738
  // are shown.
797
- act( () => {
798
- render(
799
- <LinkControl
800
- showInitialSuggestions
801
- value={ fauxEntitySuggestions[ 0 ] }
802
- />,
803
- container
804
- );
805
- } );
739
+ const { container } = render(
740
+ <LinkControl
741
+ showInitialSuggestions
742
+ value={ fauxEntitySuggestions[ 0 ] }
743
+ />
744
+ );
806
745
 
807
746
  await eventLoopTick();
808
747
 
@@ -813,34 +752,31 @@ describe( 'Default search suggestions', () => {
813
752
  const currentLinkUI = getCurrentLink();
814
753
  const currentLinkBtn = currentLinkUI.querySelector( 'button' );
815
754
 
816
- act( () => {
817
- Simulate.click( currentLinkBtn );
818
- } );
755
+ await user.click( currentLinkBtn );
819
756
 
820
757
  const searchInput = getURLInput();
821
758
  searchInput.focus();
822
759
 
823
760
  await eventLoopTick();
824
761
 
825
- const searchResultElements = getSearchResults();
762
+ const searchResultElements = getSearchResults( container );
826
763
 
827
764
  // Search input is set to the URL value.
828
- expect( searchInput.value ).toEqual( fauxEntitySuggestions[ 0 ].url );
765
+ expect( searchInput ).toHaveValue( fauxEntitySuggestions[ 0 ].url );
829
766
 
830
767
  // It should match any url that's like ?p= and also include a URL option.
831
768
  expect( searchResultElements ).toHaveLength( 5 );
832
769
 
833
- expect( searchInput.getAttribute( 'aria-expanded' ) ).toBe( 'true' );
770
+ expect( searchInput ).toHaveAttribute( 'aria-expanded', 'true' );
834
771
 
835
772
  expect( mockFetchSearchSuggestions ).toHaveBeenCalledTimes( 1 );
836
773
  } );
837
774
 
838
775
  it( 'should display initial suggestions when input value is manually deleted', async () => {
776
+ const user = userEvent.setup();
839
777
  const searchTerm = 'Hello world';
840
778
 
841
- act( () => {
842
- render( <LinkControl showInitialSuggestions />, container );
843
- } );
779
+ const { container } = render( <LinkControl showInitialSuggestions /> );
844
780
 
845
781
  let searchResultElements;
846
782
  let searchInput;
@@ -849,36 +785,33 @@ describe( 'Default search suggestions', () => {
849
785
  searchInput = getURLInput();
850
786
 
851
787
  // Simulate searching for a term.
852
- act( () => {
853
- Simulate.change( searchInput, { target: { value: searchTerm } } );
854
- } );
788
+ searchInput.focus();
789
+ await user.keyboard( searchTerm );
855
790
 
856
791
  // fetchFauxEntitySuggestions resolves on next "tick" of event loop.
857
792
  await eventLoopTick();
858
793
 
859
- expect( searchInput.value ).toBe( searchTerm );
794
+ expect( searchInput ).toHaveValue( searchTerm );
860
795
 
861
- searchResultElements = getSearchResults();
796
+ searchResultElements = getSearchResults( container );
862
797
 
863
798
  // Delete the text.
864
- act( () => {
865
- Simulate.change( searchInput, { target: { value: '' } } );
866
- } );
799
+ await userEvent.clear( searchInput );
867
800
 
868
801
  await eventLoopTick();
869
802
 
870
- searchResultElements = getSearchResults();
803
+ searchResultElements = getSearchResults( container );
871
804
 
872
805
  searchInput = getURLInput();
873
806
 
874
807
  // Check the input is empty now.
875
- expect( searchInput.value ).toBe( '' );
808
+ expect( searchInput ).toHaveValue( '' );
876
809
 
877
- const searchResultLabel = container.querySelector(
878
- '.block-editor-link-control__search-results-label'
879
- );
880
-
881
- expect( searchResultLabel.innerHTML ).toBe( 'Recently updated' );
810
+ expect(
811
+ screen.queryByRole( 'listbox', {
812
+ name: 'Recently updated',
813
+ } )
814
+ ).toBeVisible();
882
815
 
883
816
  expect( searchResultElements ).toHaveLength( 3 );
884
817
  } );
@@ -890,25 +823,23 @@ describe( 'Default search suggestions', () => {
890
823
  Promise.resolve( noResults )
891
824
  );
892
825
 
893
- act( () => {
894
- render( <LinkControl showInitialSuggestions />, container );
895
- } );
826
+ const { container } = render( <LinkControl showInitialSuggestions /> );
896
827
 
897
828
  await eventLoopTick();
898
829
 
899
830
  const searchInput = getURLInput();
900
831
 
901
- const searchResultElements = getSearchResults();
832
+ const searchResultElements = getSearchResults( container );
902
833
 
903
834
  const searchResultLabel = container.querySelector(
904
835
  '.block-editor-link-control__search-results-label'
905
836
  );
906
837
 
907
- expect( searchResultLabel ).toBeFalsy();
838
+ expect( searchResultLabel ).not.toBeInTheDocument();
908
839
 
909
840
  expect( searchResultElements ).toHaveLength( 0 );
910
841
 
911
- expect( searchInput.getAttribute( 'aria-expanded' ) ).toBe( 'false' );
842
+ expect( searchInput ).toHaveAttribute( 'aria-expanded', 'false' );
912
843
  } );
913
844
  } );
914
845
 
@@ -928,6 +859,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => {
928
859
  ] )(
929
860
  'should allow creating a link for a valid Entity title "%s" (%s)',
930
861
  async ( entityNameText ) => {
862
+ const user = userEvent.setup();
931
863
  let resolver;
932
864
  let resolvedEntity;
933
865
 
@@ -958,43 +890,29 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => {
958
890
  );
959
891
  };
960
892
 
961
- act( () => {
962
- render( <LinkControlConsumer />, container );
963
- } );
893
+ const { container } = render( <LinkControlConsumer /> );
964
894
 
965
895
  // Search Input UI.
966
- const searchInput = container.querySelector(
967
- 'input[aria-label="URL"]'
968
- );
896
+ const searchInput = getURLInput();
969
897
 
970
898
  // Simulate searching for a term.
971
- act( () => {
972
- Simulate.change( searchInput, {
973
- target: { value: entityNameText },
974
- } );
975
- } );
899
+ searchInput.focus();
900
+ await user.keyboard( entityNameText );
976
901
 
977
902
  await eventLoopTick();
978
903
 
979
- // TODO: select these by aria relationship to autocomplete rather than arbitrary selector.
980
- const searchResultElements = container.querySelectorAll(
981
- '[role="listbox"] [role="option"]'
982
- );
904
+ const searchResultElements = screen.queryAllByRole( 'option' );
983
905
 
984
906
  const createButton = Array.from( searchResultElements ).filter(
985
907
  ( result ) => result.innerHTML.includes( 'Create:' )
986
908
  )[ 0 ];
987
909
 
988
- expect( createButton ).not.toBeNull();
989
- expect( createButton.innerHTML ).toEqual(
990
- expect.stringContaining( entityNameText )
991
- );
910
+ expect( createButton ).toBeVisible();
911
+ expect( createButton ).toHaveTextContent( entityNameText );
992
912
 
993
913
  // No need to wait in this test because we control the Promise
994
914
  // resolution manually via the `resolver` reference.
995
- act( () => {
996
- Simulate.click( createButton );
997
- } );
915
+ await user.click( createButton );
998
916
 
999
917
  await eventLoopTick();
1000
918
 
@@ -1002,14 +920,10 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => {
1002
920
  const loadingIndicator = container.querySelector(
1003
921
  '.block-editor-link-control__loading'
1004
922
  );
1005
- const currentLinkLabel = container.querySelector(
1006
- '[aria-label="Currently selected"]'
1007
- );
923
+ const currentLinkLabel = getCurrentLink();
1008
924
 
1009
- expect( currentLinkLabel ).toBeNull();
1010
- expect( loadingIndicator.innerHTML ).toEqual(
1011
- expect.stringContaining( 'Creating' )
1012
- );
925
+ expect( currentLinkLabel ).not.toBeInTheDocument();
926
+ expect( loadingIndicator ).toHaveTextContent( 'Creating' );
1013
927
 
1014
928
  // Resolve the `createSuggestion` promise.
1015
929
  await act( async () => {
@@ -1018,22 +932,15 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => {
1018
932
 
1019
933
  await eventLoopTick();
1020
934
 
1021
- const currentLink = container.querySelector(
1022
- '[aria-label="Currently selected"]'
1023
- );
1024
-
1025
- const currentLinkHTML = currentLink.innerHTML;
935
+ const currentLink = getCurrentLink();
1026
936
 
1027
- expect( currentLinkHTML ).toEqual(
1028
- expect.stringContaining( entityNameText )
1029
- );
1030
- expect( currentLinkHTML ).toEqual(
1031
- expect.stringContaining( '/?p=123' )
1032
- );
937
+ expect( currentLink ).toHaveTextContent( entityNameText );
938
+ expect( currentLink ).toHaveTextContent( '/?p=123' );
1033
939
  }
1034
940
  );
1035
941
 
1036
942
  it( 'should allow createSuggestion prop to return a non-Promise value', async () => {
943
+ const user = userEvent.setup();
1037
944
  const LinkControlConsumer = () => {
1038
945
  const [ link, setLink ] = useState( null );
1039
946
 
@@ -1053,21 +960,14 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => {
1053
960
  );
1054
961
  };
1055
962
 
1056
- act( () => {
1057
- render( <LinkControlConsumer />, container );
1058
- } );
963
+ const { container } = render( <LinkControlConsumer /> );
1059
964
 
1060
965
  // Search Input UI.
1061
- const searchInput = container.querySelector(
1062
- 'input[aria-label="URL"]'
1063
- );
966
+ const searchInput = getURLInput();
1064
967
 
1065
968
  // Simulate searching for a term.
1066
- act( () => {
1067
- Simulate.change( searchInput, {
1068
- target: { value: 'Some new page to create' },
1069
- } );
1070
- } );
969
+ searchInput.focus();
970
+ await user.keyboard( 'Some new page to create' );
1071
971
 
1072
972
  await eventLoopTick();
1073
973
 
@@ -1080,27 +980,18 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => {
1080
980
  ( result ) => result.innerHTML.includes( 'Create:' )
1081
981
  )[ 0 ];
1082
982
 
1083
- await act( async () => {
1084
- Simulate.click( createButton );
1085
- } );
983
+ await user.click( createButton );
1086
984
 
1087
985
  await eventLoopTick();
1088
986
 
1089
- const currentLink = container.querySelector(
1090
- '[aria-label="Currently selected"]'
1091
- );
1092
-
1093
- const currentLinkHTML = currentLink.innerHTML;
987
+ const currentLink = getCurrentLink();
1094
988
 
1095
- expect( currentLinkHTML ).toEqual(
1096
- expect.stringContaining( 'Some new page to create' )
1097
- );
1098
- expect( currentLinkHTML ).toEqual(
1099
- expect.stringContaining( '/?p=123' )
1100
- );
989
+ expect( currentLink ).toHaveTextContent( 'Some new page to create' );
990
+ expect( currentLink ).toHaveTextContent( '/?p=123' );
1101
991
  } );
1102
992
 
1103
993
  it( 'should allow creation of entities via the keyboard', async () => {
994
+ const user = userEvent.setup();
1104
995
  const entityNameText = 'A new page to be created';
1105
996
 
1106
997
  const LinkControlConsumer = () => {
@@ -1124,21 +1015,14 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => {
1124
1015
  );
1125
1016
  };
1126
1017
 
1127
- act( () => {
1128
- render( <LinkControlConsumer />, container );
1129
- } );
1018
+ const { container } = render( <LinkControlConsumer /> );
1130
1019
 
1131
1020
  // Search Input UI.
1132
- const searchInput = container.querySelector(
1133
- 'input[aria-label="URL"]'
1134
- );
1021
+ const searchInput = getURLInput();
1135
1022
 
1136
1023
  // Simulate searching for a term.
1137
- act( () => {
1138
- Simulate.change( searchInput, {
1139
- target: { value: entityNameText },
1140
- } );
1141
- } );
1024
+ searchInput.focus();
1025
+ await user.keyboard( entityNameText );
1142
1026
 
1143
1027
  await eventLoopTick();
1144
1028
 
@@ -1151,32 +1035,21 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => {
1151
1035
  )[ 0 ];
1152
1036
 
1153
1037
  // Step down into the search results, highlighting the first result item.
1154
- act( () => {
1155
- Simulate.keyDown( searchInput, { keyCode: DOWN } );
1156
- } );
1038
+ triggerArrowDown( searchInput );
1157
1039
 
1158
- act( () => {
1159
- Simulate.keyDown( createButton, { keyCode: ENTER } );
1160
- } );
1040
+ createButton.focus();
1041
+ await user.keyboard( '[Enter]' );
1161
1042
 
1162
- await act( async () => {
1163
- Simulate.keyDown( searchInput, { keyCode: ENTER } );
1164
- } );
1043
+ searchInput.focus();
1044
+ await user.keyboard( '[Enter]' );
1165
1045
 
1166
1046
  await eventLoopTick();
1167
1047
 
1168
- const currentLink = container.querySelector(
1169
- '[aria-label="Currently selected"]'
1170
- );
1171
-
1172
- const currentLinkHTML = currentLink.innerHTML;
1173
-
1174
- expect( currentLinkHTML ).toEqual(
1175
- expect.stringContaining( entityNameText )
1176
- );
1048
+ expect( getCurrentLink() ).toHaveTextContent( entityNameText );
1177
1049
  } );
1178
1050
 
1179
1051
  it( 'should allow customisation of button text', async () => {
1052
+ const user = userEvent.setup();
1180
1053
  const entityNameText = 'A new page to be created';
1181
1054
 
1182
1055
  const LinkControlConsumer = () => {
@@ -1188,21 +1061,14 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => {
1188
1061
  );
1189
1062
  };
1190
1063
 
1191
- act( () => {
1192
- render( <LinkControlConsumer />, container );
1193
- } );
1064
+ const { container } = render( <LinkControlConsumer /> );
1194
1065
 
1195
1066
  // Search Input UI.
1196
- const searchInput = container.querySelector(
1197
- 'input[aria-label="URL"]'
1198
- );
1067
+ const searchInput = getURLInput();
1199
1068
 
1200
1069
  // Simulate searching for a term.
1201
- act( () => {
1202
- Simulate.change( searchInput, {
1203
- target: { value: entityNameText },
1204
- } );
1205
- } );
1070
+ searchInput.focus();
1071
+ await user.keyboard( entityNameText );
1206
1072
 
1207
1073
  await eventLoopTick();
1208
1074
 
@@ -1215,26 +1081,22 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => {
1215
1081
  ( result ) => result.innerHTML.includes( 'Custom suggestion text' )
1216
1082
  )[ 0 ];
1217
1083
 
1218
- expect( createButton ).not.toBeNull();
1084
+ expect( createButton ).toBeVisible();
1219
1085
  } );
1220
1086
 
1221
1087
  describe( 'Do not show create option', () => {
1222
1088
  it.each( [ [ undefined ], [ null ], [ false ] ] )(
1223
1089
  'should not show not show an option to create an entity when "createSuggestion" handler is %s',
1224
1090
  async ( handler ) => {
1225
- act( () => {
1226
- render(
1227
- <LinkControl createSuggestion={ handler } />,
1228
- container
1229
- );
1230
- } );
1091
+ const { container } = render(
1092
+ <LinkControl createSuggestion={ handler } />
1093
+ );
1094
+
1231
1095
  // Await the initial suggestions to be fetched.
1232
1096
  await eventLoopTick();
1233
1097
 
1234
1098
  // Search Input UI.
1235
- const searchInput = container.querySelector(
1236
- 'input[aria-label="URL"]'
1237
- );
1099
+ const searchInput = getURLInput();
1238
1100
 
1239
1101
  // TODO: select these by aria relationship to autocomplete rather than arbitrary selector.
1240
1102
  const searchResultElements = container.querySelectorAll(
@@ -1245,28 +1107,24 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => {
1245
1107
  )[ 0 ];
1246
1108
 
1247
1109
  // Verify input has no value.
1248
- expect( searchInput.value ).toBe( '' );
1110
+ expect( searchInput ).toHaveValue( '' );
1249
1111
  expect( createButton ).toBeFalsy(); // Shouldn't exist!
1250
1112
  }
1251
1113
  );
1252
1114
 
1253
1115
  it( 'should not show not show an option to create an entity when input is empty', async () => {
1254
- act( () => {
1255
- render(
1256
- <LinkControl
1257
- showInitialSuggestions={ true } // Should show even if we're not showing initial suggestions.
1258
- createSuggestion={ jest.fn() }
1259
- />,
1260
- container
1261
- );
1262
- } );
1116
+ const { container } = render(
1117
+ <LinkControl
1118
+ showInitialSuggestions={ true } // Should show even if we're not showing initial suggestions.
1119
+ createSuggestion={ jest.fn() }
1120
+ />
1121
+ );
1122
+
1263
1123
  // Await the initial suggestions to be fetched.
1264
1124
  await eventLoopTick();
1265
1125
 
1266
1126
  // Search Input UI.
1267
- const searchInput = container.querySelector(
1268
- 'input[aria-label="URL"]'
1269
- );
1127
+ const searchInput = getURLInput();
1270
1128
 
1271
1129
  // TODO: select these by aria relationship to autocomplete rather than arbitrary selector.
1272
1130
  const searchResultElements = container.querySelectorAll(
@@ -1277,7 +1135,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => {
1277
1135
  )[ 0 ];
1278
1136
 
1279
1137
  // Verify input has no value.
1280
- expect( searchInput.value ).toBe( '' );
1138
+ expect( searchInput ).toHaveValue( '' );
1281
1139
  expect( createButton ).toBeFalsy(); // Shouldn't exist!
1282
1140
  } );
1283
1141
 
@@ -1290,24 +1148,17 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => {
1290
1148
  ] )(
1291
1149
  'should not show option to "Create Page" when text is a form of direct entry (eg: %s)',
1292
1150
  async ( inputText ) => {
1293
- act( () => {
1294
- render(
1295
- <LinkControl createSuggestion={ jest.fn() } />,
1296
- container
1297
- );
1298
- } );
1151
+ const user = userEvent.setup();
1152
+ const { container } = render(
1153
+ <LinkControl createSuggestion={ jest.fn() } />
1154
+ );
1299
1155
 
1300
1156
  // Search Input UI.
1301
- const searchInput = container.querySelector(
1302
- 'input[aria-label="URL"]'
1303
- );
1157
+ const searchInput = getURLInput();
1304
1158
 
1305
1159
  // Simulate searching for a term.
1306
- act( () => {
1307
- Simulate.change( searchInput, {
1308
- target: { value: inputText },
1309
- } );
1310
- } );
1160
+ searchInput.focus();
1161
+ await user.keyboard( inputText );
1311
1162
 
1312
1163
  await eventLoopTick();
1313
1164
 
@@ -1327,6 +1178,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => {
1327
1178
 
1328
1179
  describe( 'Error handling', () => {
1329
1180
  it( 'should display human-friendly, perceivable error notice and re-show create button and search input if page creation request fails', async () => {
1181
+ const user = userEvent.setup();
1330
1182
  const searchText = 'This page to be created';
1331
1183
  let searchInput;
1332
1184
 
@@ -1336,22 +1188,16 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => {
1336
1188
 
1337
1189
  const createSuggestion = () => Promise.reject( throwsError() );
1338
1190
 
1339
- act( () => {
1340
- render(
1341
- <LinkControl createSuggestion={ createSuggestion } />,
1342
- container
1343
- );
1344
- } );
1191
+ const { container } = render(
1192
+ <LinkControl createSuggestion={ createSuggestion } />
1193
+ );
1345
1194
 
1346
1195
  // Search Input UI.
1347
- searchInput = container.querySelector( 'input[aria-label="URL"]' );
1196
+ searchInput = getURLInput();
1348
1197
 
1349
1198
  // Simulate searching for a term.
1350
- act( () => {
1351
- Simulate.change( searchInput, {
1352
- target: { value: searchText },
1353
- } );
1354
- } );
1199
+ searchInput.focus();
1200
+ await user.keyboard( searchText );
1355
1201
 
1356
1202
  await eventLoopTick();
1357
1203
 
@@ -1363,13 +1209,11 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => {
1363
1209
  ( result ) => result.innerHTML.includes( 'Create:' )
1364
1210
  )[ 0 ];
1365
1211
 
1366
- await act( async () => {
1367
- Simulate.click( createButton );
1368
- } );
1212
+ await user.click( createButton );
1369
1213
 
1370
1214
  await eventLoopTick();
1371
1215
 
1372
- searchInput = container.querySelector( 'input[aria-label="URL"]' );
1216
+ searchInput = getURLInput();
1373
1217
 
1374
1218
  // This is a Notice component
1375
1219
  // we allow selecting by className here as an edge case because the
@@ -1383,16 +1227,14 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => {
1383
1227
  expect( throwsError ).toThrow( Error );
1384
1228
 
1385
1229
  // Check human readable error notice is perceivable.
1386
- expect( errorNotice ).not.toBeFalsy();
1387
- expect( errorNotice.innerHTML ).toEqual(
1388
- expect.stringContaining(
1389
- 'API response returned invalid entity'
1390
- )
1230
+ expect( errorNotice ).toBeVisible();
1231
+ expect( errorNotice ).toHaveTextContent(
1232
+ 'API response returned invalid entity'
1391
1233
  );
1392
1234
 
1393
1235
  // Verify input is repopulated with original search text.
1394
- expect( searchInput ).not.toBeFalsy();
1395
- expect( searchInput.value ).toBe( searchText );
1236
+ expect( searchInput ).toBeVisible();
1237
+ expect( searchInput ).toHaveValue( searchText );
1396
1238
 
1397
1239
  // Verify search results are re-shown and create button is available.
1398
1240
  searchResultElements = container.querySelectorAll(
@@ -1415,27 +1257,22 @@ describe( 'Selecting links', () => {
1415
1257
  return <LinkControl value={ link } />;
1416
1258
  };
1417
1259
 
1418
- act( () => {
1419
- render( <LinkControlConsumer />, container );
1420
- } );
1260
+ render( <LinkControlConsumer /> );
1421
1261
 
1422
- // TODO: select by aria role or visible text.
1423
1262
  const currentLink = getCurrentLink();
1424
- const currentLinkHTML = currentLink.innerHTML;
1425
1263
  const currentLinkAnchor = currentLink.querySelector(
1426
1264
  `[href="${ selectedLink.url }"]`
1427
1265
  );
1428
1266
 
1429
- expect( currentLinkHTML ).toEqual(
1430
- expect.stringContaining( selectedLink.title )
1431
- );
1267
+ expect( currentLink ).toHaveTextContent( selectedLink.title );
1432
1268
  expect(
1433
- queryByRole( currentLink, 'button', { name: 'Edit' } )
1434
- ).toBeTruthy();
1435
- expect( currentLinkAnchor ).not.toBeNull();
1269
+ screen.queryByRole( 'button', { name: 'Edit' } )
1270
+ ).toBeVisible();
1271
+ expect( currentLinkAnchor ).toBeVisible();
1436
1272
  } );
1437
1273
 
1438
- it( 'should hide "selected" link UI and display search UI prepopulated with previously selected link title when "Change" button is clicked', () => {
1274
+ it( 'should hide "selected" link UI and display search UI prepopulated with previously selected link title when "Change" button is clicked', async () => {
1275
+ const user = userEvent.setup();
1439
1276
  const selectedLink = fauxEntitySuggestions[ 0 ];
1440
1277
 
1441
1278
  const LinkControlConsumer = () => {
@@ -1449,26 +1286,22 @@ describe( 'Selecting links', () => {
1449
1286
  );
1450
1287
  };
1451
1288
 
1452
- act( () => {
1453
- render( <LinkControlConsumer />, container );
1454
- } );
1289
+ render( <LinkControlConsumer /> );
1455
1290
 
1456
1291
  // Required in order to select the button below.
1457
1292
  let currentLinkUI = getCurrentLink();
1458
1293
  const currentLinkBtn = currentLinkUI.querySelector( 'button' );
1459
1294
 
1460
1295
  // Simulate searching for a term.
1461
- act( () => {
1462
- Simulate.click( currentLinkBtn );
1463
- } );
1296
+ await user.click( currentLinkBtn );
1464
1297
 
1465
1298
  const searchInput = getURLInput();
1466
1299
  currentLinkUI = getCurrentLink();
1467
1300
 
1468
1301
  // We should be back to showing the search input.
1469
- expect( searchInput ).not.toBeNull();
1470
- expect( searchInput.value ).toBe( selectedLink.url ); // Prepopulated with previous link's URL.
1471
- expect( currentLinkUI ).toBeNull();
1302
+ expect( searchInput ).toBeVisible();
1303
+ expect( searchInput ).toHaveValue( selectedLink.url ); // Prepopulated with previous link's URL.
1304
+ expect( currentLinkUI ).not.toBeInTheDocument();
1472
1305
  } );
1473
1306
 
1474
1307
  describe( 'Selection using mouse click', () => {
@@ -1487,6 +1320,7 @@ describe( 'Selecting links', () => {
1487
1320
  ] )(
1488
1321
  'should display a current selected link UI when a %s suggestion for the search "%s" is clicked',
1489
1322
  async ( type, searchTerm, selectedLink ) => {
1323
+ const user = userEvent.setup();
1490
1324
  const LinkControlConsumer = () => {
1491
1325
  const [ link, setLink ] = useState();
1492
1326
 
@@ -1498,48 +1332,36 @@ describe( 'Selecting links', () => {
1498
1332
  );
1499
1333
  };
1500
1334
 
1501
- act( () => {
1502
- render( <LinkControlConsumer />, container );
1503
- } );
1335
+ const { container } = render( <LinkControlConsumer /> );
1504
1336
 
1505
1337
  // Search Input UI.
1506
1338
  const searchInput = getURLInput();
1507
1339
 
1508
1340
  // Simulate searching for a term.
1509
- act( () => {
1510
- Simulate.change( searchInput, {
1511
- target: { value: searchTerm },
1512
- } );
1513
- } );
1341
+ searchInput.focus();
1342
+ await user.keyboard( searchTerm );
1514
1343
 
1515
1344
  // fetchFauxEntitySuggestions resolves on next "tick" of event loop.
1516
1345
  await eventLoopTick();
1517
1346
 
1518
- const searchResultElements = getSearchResults();
1347
+ const searchResultElements = getSearchResults( container );
1519
1348
 
1520
1349
  const firstSearchSuggestion = searchResultElements[ 0 ];
1521
1350
 
1522
1351
  // Simulate selecting the first of the search suggestions.
1523
- act( () => {
1524
- Simulate.click( firstSearchSuggestion );
1525
- } );
1352
+ await user.click( firstSearchSuggestion );
1526
1353
 
1527
- const currentLink = container.querySelector(
1528
- '.block-editor-link-control__search-item.is-current'
1529
- );
1530
- const currentLinkHTML = currentLink.innerHTML;
1354
+ const currentLink = getCurrentLink();
1531
1355
  const currentLinkAnchor = currentLink.querySelector(
1532
1356
  `[href="${ selectedLink.url }"]`
1533
1357
  );
1534
1358
 
1535
1359
  // Check that this suggestion is now shown as selected.
1536
- expect( currentLinkHTML ).toEqual(
1537
- expect.stringContaining( selectedLink.title )
1538
- );
1539
- expect( currentLinkHTML ).toEqual(
1540
- expect.stringContaining( 'Edit' )
1541
- );
1542
- expect( currentLinkAnchor ).not.toBeNull();
1360
+ expect( currentLink ).toHaveTextContent( selectedLink.title );
1361
+ expect(
1362
+ screen.getByRole( 'button', { name: 'Edit' } )
1363
+ ).toBeVisible();
1364
+ expect( currentLinkAnchor ).toBeVisible();
1543
1365
  }
1544
1366
  );
1545
1367
  } );
@@ -1560,6 +1382,7 @@ describe( 'Selecting links', () => {
1560
1382
  ] )(
1561
1383
  'should display a current selected link UI when an %s suggestion for the search "%s" is selected using the keyboard',
1562
1384
  async ( type, searchTerm, selectedLink ) => {
1385
+ const user = userEvent.setup();
1563
1386
  const LinkControlConsumer = () => {
1564
1387
  const [ link, setLink ] = useState();
1565
1388
 
@@ -1571,37 +1394,27 @@ describe( 'Selecting links', () => {
1571
1394
  );
1572
1395
  };
1573
1396
 
1574
- act( () => {
1575
- render( <LinkControlConsumer />, container );
1576
- } );
1397
+ const { container } = render( <LinkControlConsumer /> );
1577
1398
 
1578
1399
  // Search Input UI.
1579
1400
  const searchInput = getURLInput();
1580
- searchInput.focus();
1581
1401
 
1582
1402
  // Simulate searching for a term.
1583
- act( () => {
1584
- Simulate.change( searchInput, {
1585
- target: { value: searchTerm },
1586
- } );
1587
- } );
1403
+ searchInput.focus();
1404
+ await user.keyboard( searchTerm );
1588
1405
 
1589
1406
  // fetchFauxEntitySuggestions resolves on next "tick" of event loop.
1590
1407
  await eventLoopTick();
1591
1408
 
1592
1409
  // Step down into the search results, highlighting the first result item.
1593
- act( () => {
1594
- Simulate.keyDown( searchInput, { keyCode: DOWN } );
1595
- } );
1410
+ triggerArrowDown( searchInput );
1596
1411
 
1597
- const searchResultElements = getSearchResults();
1412
+ const searchResultElements = getSearchResults( container );
1598
1413
 
1599
1414
  const firstSearchSuggestion = searchResultElements[ 0 ];
1600
1415
  const secondSearchSuggestion = searchResultElements[ 1 ];
1601
1416
 
1602
- let selectedSearchResultElement = container.querySelector(
1603
- '[role="option"][aria-selected="true"]'
1604
- );
1417
+ let selectedSearchResultElement = getSelectedResultElement();
1605
1418
 
1606
1419
  // We should have highlighted the first item using the keyboard.
1607
1420
  expect( selectedSearchResultElement ).toEqual(
@@ -1611,13 +1424,9 @@ describe( 'Selecting links', () => {
1611
1424
  // Only entity searches contain more than 1 suggestion.
1612
1425
  if ( type === 'entity' ) {
1613
1426
  // Check we can go down again using the down arrow.
1614
- act( () => {
1615
- Simulate.keyDown( searchInput, { keyCode: DOWN } );
1616
- } );
1427
+ triggerArrowDown( searchInput );
1617
1428
 
1618
- selectedSearchResultElement = container.querySelector(
1619
- '[role="option"][aria-selected="true"]'
1620
- );
1429
+ selectedSearchResultElement = getSelectedResultElement();
1621
1430
 
1622
1431
  // We should have highlighted the first item using the keyboard
1623
1432
  // eslint-disable-next-line jest/no-conditional-expect
@@ -1626,13 +1435,9 @@ describe( 'Selecting links', () => {
1626
1435
  );
1627
1436
 
1628
1437
  // Check we can go back up via up arrow.
1629
- act( () => {
1630
- Simulate.keyDown( searchInput, { keyCode: UP } );
1631
- } );
1438
+ triggerArrowUp( searchInput );
1632
1439
 
1633
- selectedSearchResultElement = container.querySelector(
1634
- '[role="option"][aria-selected="true"]'
1635
- );
1440
+ selectedSearchResultElement = getSelectedResultElement();
1636
1441
 
1637
1442
  // We should be back to highlighting the first search result again
1638
1443
  // eslint-disable-next-line jest/no-conditional-expect
@@ -1642,70 +1447,52 @@ describe( 'Selecting links', () => {
1642
1447
  }
1643
1448
 
1644
1449
  // Submit the selected item as the current link.
1645
- act( () => {
1646
- Simulate.keyDown( searchInput, { keyCode: ENTER } );
1647
- } );
1450
+ triggerEnter( searchInput );
1648
1451
 
1649
1452
  // Check that the suggestion selected via is now shown as selected.
1650
- const currentLink = container.querySelector(
1651
- '.block-editor-link-control__search-item.is-current'
1652
- );
1653
- const currentLinkHTML = currentLink.innerHTML;
1453
+ const currentLink = getCurrentLink();
1654
1454
  const currentLinkAnchor = currentLink.querySelector(
1655
1455
  `[href="${ selectedLink.url }"]`
1656
1456
  );
1657
1457
 
1658
1458
  // Make sure focus is retained after submission.
1659
- expect( container.contains( document.activeElement ) ).toBe(
1660
- true
1661
- );
1459
+ expect( container ).toContainElement( document.activeElement );
1662
1460
 
1663
- expect( currentLinkHTML ).toEqual(
1664
- expect.stringContaining( selectedLink.title )
1665
- );
1666
- expect( currentLinkHTML ).toEqual(
1667
- expect.stringContaining( 'Edit' )
1668
- );
1669
- expect( currentLinkAnchor ).not.toBeNull();
1461
+ expect( currentLink ).toHaveTextContent( selectedLink.title );
1462
+ expect(
1463
+ screen.getByRole( 'button', { name: 'Edit' } )
1464
+ ).toBeVisible();
1465
+ expect( currentLinkAnchor ).toBeVisible();
1670
1466
  }
1671
1467
  );
1672
1468
 
1673
1469
  it( 'should allow selection of initial search results via the keyboard', async () => {
1674
- act( () => {
1675
- render( <LinkControl showInitialSuggestions />, container );
1676
- } );
1470
+ const { container } = render(
1471
+ <LinkControl showInitialSuggestions />
1472
+ );
1677
1473
 
1678
1474
  await eventLoopTick();
1679
1475
 
1680
- const searchResultsWrapper =
1681
- container.querySelector( '[role="listbox"]' );
1682
-
1683
- const searchResultsLabel = container.querySelector(
1684
- `#${ searchResultsWrapper.getAttribute( 'aria-labelledby' ) }`
1685
- );
1686
-
1687
- expect( searchResultsLabel.innerHTML ).toEqual(
1688
- 'Recently updated'
1689
- );
1476
+ expect(
1477
+ screen.queryByRole( 'listbox', {
1478
+ name: 'Recently updated',
1479
+ } )
1480
+ ).toBeVisible();
1690
1481
 
1691
1482
  // Search Input UI.
1692
1483
  const searchInput = getURLInput();
1693
1484
 
1694
1485
  // Step down into the search results, highlighting the first result item.
1695
- act( () => {
1696
- Simulate.keyDown( searchInput, { keyCode: DOWN } );
1697
- } );
1486
+ triggerArrowDown( searchInput );
1698
1487
 
1699
1488
  await eventLoopTick();
1700
1489
 
1701
- const searchResultElements = getSearchResults();
1490
+ const searchResultElements = getSearchResults( container );
1702
1491
 
1703
1492
  const firstSearchSuggestion = searchResultElements[ 0 ];
1704
1493
  const secondSearchSuggestion = searchResultElements[ 1 ];
1705
1494
 
1706
- let selectedSearchResultElement = container.querySelector(
1707
- '[role="option"][aria-selected="true"]'
1708
- );
1495
+ let selectedSearchResultElement = getSelectedResultElement();
1709
1496
 
1710
1497
  // We should have highlighted the first item using the keyboard.
1711
1498
  expect( selectedSearchResultElement ).toEqual(
@@ -1713,13 +1500,9 @@ describe( 'Selecting links', () => {
1713
1500
  );
1714
1501
 
1715
1502
  // Check we can go down again using the down arrow.
1716
- act( () => {
1717
- Simulate.keyDown( searchInput, { keyCode: DOWN } );
1718
- } );
1503
+ triggerArrowDown( searchInput );
1719
1504
 
1720
- selectedSearchResultElement = container.querySelector(
1721
- '[role="option"][aria-selected="true"]'
1722
- );
1505
+ selectedSearchResultElement = getSelectedResultElement();
1723
1506
 
1724
1507
  // We should have highlighted the first item using the keyboard.
1725
1508
  expect( selectedSearchResultElement ).toEqual(
@@ -1727,13 +1510,9 @@ describe( 'Selecting links', () => {
1727
1510
  );
1728
1511
 
1729
1512
  // Check we can go back up via up arrow.
1730
- act( () => {
1731
- Simulate.keyDown( searchInput, { keyCode: UP } );
1732
- } );
1513
+ triggerArrowUp( searchInput );
1733
1514
 
1734
- selectedSearchResultElement = container.querySelector(
1735
- '[role="option"][aria-selected="true"]'
1736
- );
1515
+ selectedSearchResultElement = getSelectedResultElement();
1737
1516
 
1738
1517
  // We should be back to highlighting the first search result again.
1739
1518
  expect( selectedSearchResultElement ).toEqual(
@@ -1756,27 +1535,18 @@ describe( 'Addition Settings UI', () => {
1756
1535
  return <LinkControl value={ link } />;
1757
1536
  };
1758
1537
 
1759
- act( () => {
1760
- render( <LinkControlConsumer />, container );
1761
- } );
1762
-
1763
- const newTabSettingLabel = Array.from(
1764
- container.querySelectorAll( 'label' )
1765
- ).find(
1766
- ( label ) =>
1767
- label.innerHTML &&
1768
- label.innerHTML.includes( expectedSettingText )
1769
- );
1538
+ const { container } = render( <LinkControlConsumer /> );
1770
1539
 
1771
- expect( newTabSettingLabel ).not.toBeUndefined(); // find() returns "undefined" if not found.
1540
+ const newTabSettingLabel = screen.getByText( expectedSettingText );
1541
+ expect( newTabSettingLabel ).toBeVisible();
1772
1542
 
1773
1543
  const newTabSettingLabelForAttr =
1774
1544
  newTabSettingLabel.getAttribute( 'for' );
1775
1545
  const newTabSettingInput = container.querySelector(
1776
1546
  `#${ newTabSettingLabelForAttr }`
1777
1547
  );
1778
- expect( newTabSettingInput ).not.toBeNull();
1779
- expect( newTabSettingInput.checked ).toBe( false );
1548
+ expect( newTabSettingInput ).toBeVisible();
1549
+ expect( newTabSettingInput ).not.toBeChecked();
1780
1550
  } );
1781
1551
 
1782
1552
  it( 'should display a setting control with correct default state for each of the custom settings provided', async () => {
@@ -1793,10 +1563,6 @@ describe( 'Addition Settings UI', () => {
1793
1563
  },
1794
1564
  ];
1795
1565
 
1796
- const customSettingsLabelsText = customSettings.map(
1797
- ( setting ) => setting.title
1798
- );
1799
-
1800
1566
  const LinkControlConsumer = () => {
1801
1567
  const [ link ] = useState( selectedLink );
1802
1568
 
@@ -1808,108 +1574,78 @@ describe( 'Addition Settings UI', () => {
1808
1574
  );
1809
1575
  };
1810
1576
 
1811
- act( () => {
1812
- render( <LinkControlConsumer />, container );
1813
- } );
1814
-
1815
- // Grab the elements using user perceivable DOM queries.
1816
- const settingsLegend = Array.from(
1817
- container.querySelectorAll( 'legend' )
1818
- ).find(
1819
- ( legend ) =>
1820
- legend.innerHTML &&
1821
- legend.innerHTML.includes( 'Currently selected link settings' )
1822
- );
1823
- const settingsFieldset = settingsLegend.closest( 'fieldset' );
1824
- const settingControlsLabels = Array.from(
1825
- settingsFieldset.querySelectorAll( 'label' )
1826
- );
1827
- const settingControlsInputs = settingControlsLabels.map( ( label ) => {
1828
- return settingsFieldset.querySelector(
1829
- `#${ label.getAttribute( 'for' ) }`
1830
- );
1831
- } );
1832
-
1833
- const settingControlLabelsText = Array.from(
1834
- settingControlsLabels
1835
- ).map( ( label ) => label.innerHTML );
1577
+ render( <LinkControlConsumer /> );
1836
1578
 
1837
- // Check we have the correct number of controls.
1838
- expect( settingControlsLabels ).toHaveLength( 2 );
1579
+ expect( screen.queryAllByRole( 'checkbox' ) ).toHaveLength( 2 );
1839
1580
 
1840
- // Check the labels match.
1841
- expect( settingControlLabelsText ).toEqual(
1842
- expect.arrayContaining( customSettingsLabelsText )
1843
- );
1844
-
1845
- // Assert the default "checked" states match the expected.
1846
- expect( settingControlsInputs[ 0 ].checked ).toEqual( false );
1847
- expect( settingControlsInputs[ 1 ].checked ).toEqual( true );
1581
+ expect(
1582
+ screen.getByRole( 'checkbox', {
1583
+ name: customSettings[ 0 ].title,
1584
+ } )
1585
+ ).not.toBeChecked();
1586
+ expect(
1587
+ screen.getByRole( 'checkbox', {
1588
+ name: customSettings[ 1 ].title,
1589
+ } )
1590
+ ).toBeChecked();
1848
1591
  } );
1849
1592
  } );
1850
1593
 
1851
1594
  describe( 'Post types', () => {
1852
1595
  it( 'should display post type in search results of link', async () => {
1596
+ const user = userEvent.setup();
1853
1597
  const searchTerm = 'Hello world';
1854
1598
 
1855
- act( () => {
1856
- render( <LinkControl />, container );
1857
- } );
1599
+ const { container } = render( <LinkControl /> );
1858
1600
 
1859
1601
  // Search Input UI.
1860
1602
  const searchInput = getURLInput();
1861
1603
 
1862
1604
  // Simulate searching for a term.
1863
- act( () => {
1864
- Simulate.change( searchInput, { target: { value: searchTerm } } );
1865
- } );
1605
+ searchInput.focus();
1606
+ await user.keyboard( searchTerm );
1866
1607
 
1867
1608
  // fetchFauxEntitySuggestions resolves on next "tick" of event loop.
1868
1609
  await eventLoopTick();
1869
1610
 
1870
- const searchResultElements = getSearchResults();
1611
+ const searchResultElements = getSearchResults( container );
1871
1612
 
1872
1613
  searchResultElements.forEach( ( resultItem, index ) => {
1873
- expect(
1874
- queryByText( resultItem, fauxEntitySuggestions[ index ].type )
1875
- ).toBeTruthy();
1614
+ expect( resultItem ).toHaveTextContent(
1615
+ fauxEntitySuggestions[ index ].type
1616
+ );
1876
1617
  } );
1877
1618
  } );
1878
1619
 
1879
1620
  it.each( [ 'page', 'post', 'tag', 'post_tag', 'category' ] )(
1880
1621
  'should NOT display post type in search results of %s',
1881
1622
  async ( postType ) => {
1623
+ const user = userEvent.setup();
1882
1624
  const searchTerm = 'Hello world';
1883
1625
 
1884
- act( () => {
1885
- render(
1886
- <LinkControl suggestionsQuery={ { type: postType } } />,
1887
- container
1888
- );
1889
- } );
1626
+ const { container } = render(
1627
+ <LinkControl suggestionsQuery={ { type: postType } } />
1628
+ );
1890
1629
 
1891
1630
  // Search Input UI.
1892
1631
  const searchInput = getURLInput();
1893
1632
 
1894
1633
  // Simulate searching for a term.
1895
- act( () => {
1896
- Simulate.change( searchInput, {
1897
- target: { value: searchTerm },
1898
- } );
1899
- } );
1634
+ searchInput.focus();
1635
+ await user.keyboard( searchTerm );
1900
1636
 
1901
1637
  // fetchFauxEntitySuggestions resolves on next "tick" of event loop.
1902
1638
  await eventLoopTick();
1903
1639
 
1904
- const searchResultElements = getSearchResults();
1640
+ const searchResultElements = getSearchResults( container );
1905
1641
 
1906
1642
  searchResultElements.forEach( ( resultItem, index ) => {
1907
1643
  expect(
1908
- queryByText(
1644
+ screen.queryByText(
1909
1645
  resultItem,
1910
1646
  fauxEntitySuggestions[ index ].type
1911
1647
  )
1912
- ).toBeFalsy();
1648
+ ).not.toBeInTheDocument();
1913
1649
  } );
1914
1650
  }
1915
1651
  );
@@ -1944,18 +1680,14 @@ describe( 'Rich link previews', () => {
1944
1680
  } )
1945
1681
  );
1946
1682
 
1947
- act( () => {
1948
- render( <LinkControl value={ selectedLink } />, container );
1949
- } );
1683
+ render( <LinkControl value={ selectedLink } /> );
1950
1684
 
1951
1685
  // mockFetchRichUrlData resolves on next "tick" of event loop.
1952
1686
  await act( async () => {
1953
1687
  await eventLoopTick();
1954
1688
  } );
1955
1689
 
1956
- const linkPreview = container.querySelector(
1957
- "[aria-label='Currently selected']"
1958
- );
1690
+ const linkPreview = getCurrentLink();
1959
1691
 
1960
1692
  const isRichLinkPreview = linkPreview.classList.contains( 'is-rich' );
1961
1693
 
@@ -1974,21 +1706,14 @@ describe( 'Rich link previews', () => {
1974
1706
  } )
1975
1707
  );
1976
1708
 
1977
- act( () => {
1978
- render(
1979
- <LinkControl value={ selectedLink } hasRichPreviews />,
1980
- container
1981
- );
1982
- } );
1709
+ render( <LinkControl value={ selectedLink } hasRichPreviews /> );
1983
1710
 
1984
1711
  // mockFetchRichUrlData resolves on next "tick" of event loop.
1985
1712
  await act( async () => {
1986
1713
  await eventLoopTick();
1987
1714
  } );
1988
1715
 
1989
- const linkPreview = container.querySelector(
1990
- "[aria-label='Currently selected']"
1991
- );
1716
+ const linkPreview = getCurrentLink();
1992
1717
 
1993
1718
  const isRichLinkPreview = linkPreview.classList.contains( 'is-rich' );
1994
1719
 
@@ -2005,21 +1730,14 @@ describe( 'Rich link previews', () => {
2005
1730
  } )
2006
1731
  );
2007
1732
 
2008
- act( () => {
2009
- render(
2010
- <LinkControl value={ selectedLink } hasRichPreviews />,
2011
- container
2012
- );
2013
- } );
1733
+ render( <LinkControl value={ selectedLink } hasRichPreviews /> );
2014
1734
 
2015
1735
  // mockFetchRichUrlData resolves on next "tick" of event loop.
2016
1736
  await act( async () => {
2017
1737
  await eventLoopTick();
2018
1738
  } );
2019
1739
 
2020
- const linkPreview = container.querySelector(
2021
- "[aria-label='Currently selected']"
2022
- );
1740
+ const linkPreview = getCurrentLink();
2023
1741
 
2024
1742
  // Todo: refactor to use user-facing queries.
2025
1743
  const hasRichImagePreview = linkPreview.querySelector(
@@ -2031,8 +1749,8 @@ describe( 'Rich link previews', () => {
2031
1749
  '.block-editor-link-control__search-item-description'
2032
1750
  );
2033
1751
 
2034
- expect( hasRichImagePreview ).toBeFalsy();
2035
- expect( hasRichDescriptionPreview ).toBeFalsy();
1752
+ expect( hasRichImagePreview ).not.toBeInTheDocument();
1753
+ expect( hasRichDescriptionPreview ).not.toBeInTheDocument();
2036
1754
  } );
2037
1755
 
2038
1756
  it( 'should display a fallback when title is missing from rich data', async () => {
@@ -2045,21 +1763,14 @@ describe( 'Rich link previews', () => {
2045
1763
  } )
2046
1764
  );
2047
1765
 
2048
- act( () => {
2049
- render(
2050
- <LinkControl value={ selectedLink } hasRichPreviews />,
2051
- container
2052
- );
2053
- } );
1766
+ render( <LinkControl value={ selectedLink } hasRichPreviews /> );
2054
1767
 
2055
1768
  // mockFetchRichUrlData resolves on next "tick" of event loop.
2056
1769
  await act( async () => {
2057
1770
  await eventLoopTick();
2058
1771
  } );
2059
1772
 
2060
- const linkPreview = container.querySelector(
2061
- "[aria-label='Currently selected']"
2062
- );
1773
+ const linkPreview = getCurrentLink();
2063
1774
 
2064
1775
  const isRichLinkPreview = linkPreview.classList.contains( 'is-rich' );
2065
1776
  expect( isRichLinkPreview ).toBe( true );
@@ -2068,9 +1779,7 @@ describe( 'Rich link previews', () => {
2068
1779
  '.block-editor-link-control__search-item-title'
2069
1780
  );
2070
1781
 
2071
- expect( titlePreview.textContent ).toEqual(
2072
- expect.stringContaining( selectedLink.title )
2073
- );
1782
+ expect( titlePreview ).toHaveTextContent( selectedLink.title );
2074
1783
  } );
2075
1784
 
2076
1785
  it( 'should display a fallback when icon is missing from rich data', async () => {
@@ -2083,21 +1792,14 @@ describe( 'Rich link previews', () => {
2083
1792
  } )
2084
1793
  );
2085
1794
 
2086
- act( () => {
2087
- render(
2088
- <LinkControl value={ selectedLink } hasRichPreviews />,
2089
- container
2090
- );
2091
- } );
1795
+ render( <LinkControl value={ selectedLink } hasRichPreviews /> );
2092
1796
 
2093
1797
  // mockFetchRichUrlData resolves on next "tick" of event loop.
2094
1798
  await act( async () => {
2095
1799
  await eventLoopTick();
2096
1800
  } );
2097
1801
 
2098
- const linkPreview = container.querySelector(
2099
- "[aria-label='Currently selected']"
2100
- );
1802
+ const linkPreview = getCurrentLink();
2101
1803
 
2102
1804
  const isRichLinkPreview = linkPreview.classList.contains( 'is-rich' );
2103
1805
  expect( isRichLinkPreview ).toBe( true );
@@ -2109,8 +1811,8 @@ describe( 'Rich link previews', () => {
2109
1811
  const fallBackIcon = iconPreview.querySelector( 'svg' );
2110
1812
  const richIcon = iconPreview.querySelector( 'img' );
2111
1813
 
2112
- expect( fallBackIcon ).toBeTruthy();
2113
- expect( richIcon ).toBeFalsy();
1814
+ expect( fallBackIcon ).toBeVisible();
1815
+ expect( richIcon ).not.toBeInTheDocument();
2114
1816
  } );
2115
1817
 
2116
1818
  it.each( [ 'image', 'description' ] )(
@@ -2128,21 +1830,14 @@ describe( 'Rich link previews', () => {
2128
1830
  return Promise.resolve( data );
2129
1831
  } );
2130
1832
 
2131
- act( () => {
2132
- render(
2133
- <LinkControl value={ selectedLink } hasRichPreviews />,
2134
- container
2135
- );
2136
- } );
1833
+ render( <LinkControl value={ selectedLink } hasRichPreviews /> );
2137
1834
 
2138
1835
  // mockFetchRichUrlData resolves on next "tick" of event loop.
2139
1836
  await act( async () => {
2140
1837
  await eventLoopTick();
2141
1838
  } );
2142
1839
 
2143
- const linkPreview = container.querySelector(
2144
- "[aria-label='Currently selected']"
2145
- );
1840
+ const linkPreview = getCurrentLink();
2146
1841
 
2147
1842
  const isRichLinkPreview =
2148
1843
  linkPreview.classList.contains( 'is-rich' );
@@ -2152,7 +1847,7 @@ describe( 'Rich link previews', () => {
2152
1847
  `.block-editor-link-control__search-item-${ dataItem }`
2153
1848
  );
2154
1849
 
2155
- expect( missingDataItem ).toBeFalsy();
1850
+ expect( missingDataItem ).not.toBeInTheDocument();
2156
1851
  }
2157
1852
  );
2158
1853
 
@@ -2166,21 +1861,14 @@ describe( 'Rich link previews', () => {
2166
1861
  Promise.resolve( data )
2167
1862
  );
2168
1863
 
2169
- act( () => {
2170
- render(
2171
- <LinkControl value={ selectedLink } hasRichPreviews />,
2172
- container
2173
- );
2174
- } );
1864
+ render( <LinkControl value={ selectedLink } hasRichPreviews /> );
2175
1865
 
2176
1866
  // mockFetchRichUrlData resolves on next "tick" of event loop.
2177
1867
  await act( async () => {
2178
1868
  await eventLoopTick();
2179
1869
  } );
2180
1870
 
2181
- const linkPreview = container.querySelector(
2182
- "[aria-label='Currently selected']"
2183
- );
1871
+ const linkPreview = getCurrentLink();
2184
1872
 
2185
1873
  const isRichLinkPreview =
2186
1874
  linkPreview.classList.contains( 'is-rich' );
@@ -2194,21 +1882,14 @@ describe( 'Rich link previews', () => {
2194
1882
 
2195
1883
  mockFetchRichUrlData.mockImplementation( nonResolvingPromise );
2196
1884
 
2197
- act( () => {
2198
- render(
2199
- <LinkControl value={ selectedLink } hasRichPreviews />,
2200
- container
2201
- );
2202
- } );
1885
+ render( <LinkControl value={ selectedLink } hasRichPreviews /> );
2203
1886
 
2204
1887
  // mockFetchRichUrlData resolves on next "tick" of event loop.
2205
1888
  await act( async () => {
2206
1889
  await eventLoopTick();
2207
1890
  } );
2208
1891
 
2209
- const linkPreview = container.querySelector(
2210
- "[aria-label='Currently selected']"
2211
- );
1892
+ const linkPreview = getCurrentLink();
2212
1893
 
2213
1894
  const isFetchingRichPreview =
2214
1895
  linkPreview.classList.contains( 'is-fetching' );
@@ -2223,21 +1904,14 @@ describe( 'Rich link previews', () => {
2223
1904
 
2224
1905
  mockFetchRichUrlData.mockImplementation( simulateFailedFetch );
2225
1906
 
2226
- act( () => {
2227
- render(
2228
- <LinkControl value={ selectedLink } hasRichPreviews />,
2229
- container
2230
- );
2231
- } );
1907
+ render( <LinkControl value={ selectedLink } hasRichPreviews /> );
2232
1908
 
2233
1909
  // mockFetchRichUrlData resolves on next "tick" of event loop.
2234
1910
  await act( async () => {
2235
1911
  await eventLoopTick();
2236
1912
  } );
2237
1913
 
2238
- const linkPreview = container.querySelector(
2239
- "[aria-label='Currently selected']"
2240
- );
1914
+ const linkPreview = getCurrentLink();
2241
1915
 
2242
1916
  const isFetchingRichPreview =
2243
1917
  linkPreview.classList.contains( 'is-fetching' );
@@ -2258,16 +1932,11 @@ describe( 'Controlling link title text', () => {
2258
1932
  const selectedLink = fauxEntitySuggestions[ 0 ];
2259
1933
 
2260
1934
  it( 'should not show a means to alter the link title text by default', async () => {
2261
- act( () => {
2262
- render(
2263
- <LinkControl value={ selectedLink } forceIsEditingLink />,
2264
- container
2265
- );
2266
- } );
1935
+ render( <LinkControl value={ selectedLink } forceIsEditingLink /> );
2267
1936
 
2268
1937
  expect(
2269
- queryByRole( container, 'textbox', { name: 'Text' } )
2270
- ).toBeFalsy();
1938
+ screen.queryByRole( 'textbox', { name: 'Text' } )
1939
+ ).not.toBeInTheDocument();
2271
1940
  } );
2272
1941
 
2273
1942
  it.each( [ null, undefined, ' ' ] )(
@@ -2278,38 +1947,32 @@ describe( 'Controlling link title text', () => {
2278
1947
  url: urlValue,
2279
1948
  };
2280
1949
 
2281
- act( () => {
2282
- render(
2283
- <LinkControl
2284
- value={ selectedLinkWithoutURL }
2285
- forceIsEditingLink
2286
- hasTextControl
2287
- />,
2288
- container
2289
- );
2290
- } );
1950
+ render(
1951
+ <LinkControl
1952
+ value={ selectedLinkWithoutURL }
1953
+ forceIsEditingLink
1954
+ hasTextControl
1955
+ />
1956
+ );
2291
1957
 
2292
1958
  expect(
2293
- queryByRole( container, 'textbox', { name: 'Text' } )
2294
- ).toBeFalsy();
1959
+ screen.queryByRole( 'textbox', { name: 'Text' } )
1960
+ ).not.toBeInTheDocument();
2295
1961
  }
2296
1962
  );
2297
1963
 
2298
1964
  it( 'should show a text input to alter the link title text when hasTextControl prop is truthy', async () => {
2299
- act( () => {
2300
- render(
2301
- <LinkControl
2302
- value={ selectedLink }
2303
- forceIsEditingLink
2304
- hasTextControl
2305
- />,
2306
- container
2307
- );
2308
- } );
1965
+ render(
1966
+ <LinkControl
1967
+ value={ selectedLink }
1968
+ forceIsEditingLink
1969
+ hasTextControl
1970
+ />
1971
+ );
2309
1972
 
2310
1973
  expect(
2311
- queryByRole( container, 'textbox', { name: 'Text' } )
2312
- ).toBeTruthy();
1974
+ screen.queryByRole( 'textbox', { name: 'Text' } )
1975
+ ).toBeVisible();
2313
1976
  } );
2314
1977
 
2315
1978
  it.each( [
@@ -2325,60 +1988,49 @@ describe( 'Controlling link title text', () => {
2325
1988
  "should ensure text input reflects the current link value's `title` property %s",
2326
1989
  async ( _unused, titleValue ) => {
2327
1990
  const linkWithTitle = { ...selectedLink, title: titleValue };
2328
- act( () => {
2329
- render(
2330
- <LinkControl
2331
- value={ linkWithTitle }
2332
- forceIsEditingLink
2333
- hasTextControl
2334
- />,
2335
- container
2336
- );
2337
- } );
1991
+ render(
1992
+ <LinkControl
1993
+ value={ linkWithTitle }
1994
+ forceIsEditingLink
1995
+ hasTextControl
1996
+ />
1997
+ );
2338
1998
 
2339
- const textInput = queryByRole( container, 'textbox', {
1999
+ const textInput = screen.queryByRole( 'textbox', {
2340
2000
  name: 'Text',
2341
2001
  } );
2342
2002
 
2343
- expect( textInput.value ).toEqual( titleValue );
2003
+ expect( textInput ).toHaveValue( titleValue );
2344
2004
  }
2345
2005
  );
2346
2006
 
2347
2007
  it( "should ensure title value matching the text input's current value is included in onChange handler value on submit", async () => {
2008
+ const user = userEvent.setup();
2348
2009
  const mockOnChange = jest.fn();
2349
2010
  const textValue = 'My new text value';
2350
2011
 
2351
- act( () => {
2352
- render(
2353
- <LinkControl
2354
- value={ selectedLink }
2355
- forceIsEditingLink
2356
- hasTextControl
2357
- onChange={ mockOnChange }
2358
- />,
2359
- container
2360
- );
2361
- } );
2362
-
2363
- let textInput = queryByRole( container, 'textbox', { name: 'Text' } );
2012
+ render(
2013
+ <LinkControl
2014
+ value={ selectedLink }
2015
+ forceIsEditingLink
2016
+ hasTextControl
2017
+ onChange={ mockOnChange }
2018
+ />
2019
+ );
2364
2020
 
2365
- act( () => {
2366
- Simulate.change( textInput, {
2367
- target: { value: textValue },
2368
- } );
2369
- } );
2021
+ const textInput = screen.queryByRole( 'textbox', { name: 'Text' } );
2370
2022
 
2371
- textInput = queryByRole( container, 'textbox', { name: 'Text' } );
2023
+ textInput.focus();
2024
+ await userEvent.clear( textInput );
2025
+ await user.keyboard( textValue );
2372
2026
 
2373
- expect( textInput.value ).toEqual( textValue );
2027
+ expect( textInput ).toHaveValue( textValue );
2374
2028
 
2375
- const submitButton = queryByRole( container, 'button', {
2029
+ const submitButton = screen.queryByRole( 'button', {
2376
2030
  name: 'Submit',
2377
2031
  } );
2378
2032
 
2379
- act( () => {
2380
- Simulate.click( submitButton );
2381
- } );
2033
+ await user.click( submitButton );
2382
2034
 
2383
2035
  expect( mockOnChange ).toHaveBeenCalledWith(
2384
2036
  expect.objectContaining( {
@@ -2388,34 +2040,29 @@ describe( 'Controlling link title text', () => {
2388
2040
  } );
2389
2041
 
2390
2042
  it( 'should allow `ENTER` keypress within the text field to trigger submission of value', async () => {
2043
+ const user = userEvent.setup();
2391
2044
  const textValue = 'My new text value';
2392
2045
  const mockOnChange = jest.fn();
2393
- act( () => {
2394
- render(
2395
- <LinkControl
2396
- value={ selectedLink }
2397
- forceIsEditingLink
2398
- hasTextControl
2399
- onChange={ mockOnChange }
2400
- />,
2401
- container
2402
- );
2403
- } );
2404
2046
 
2405
- const textInput = queryByRole( container, 'textbox', { name: 'Text' } );
2047
+ render(
2048
+ <LinkControl
2049
+ value={ selectedLink }
2050
+ forceIsEditingLink
2051
+ hasTextControl
2052
+ onChange={ mockOnChange }
2053
+ />
2054
+ );
2406
2055
 
2407
- expect( textInput ).toBeTruthy();
2056
+ const textInput = screen.queryByRole( 'textbox', { name: 'Text' } );
2408
2057
 
2409
- act( () => {
2410
- Simulate.change( textInput, {
2411
- target: { value: textValue },
2412
- } );
2413
- } );
2058
+ expect( textInput ).toBeVisible();
2059
+
2060
+ textInput.focus();
2061
+ await userEvent.clear( textInput );
2062
+ await user.keyboard( textValue );
2414
2063
 
2415
2064
  // Attempt to submit the empty search value in the input.
2416
- act( () => {
2417
- Simulate.keyDown( textInput, { keyCode: ENTER } );
2418
- } );
2065
+ triggerEnter( textInput );
2419
2066
 
2420
2067
  expect( mockOnChange ).toHaveBeenCalledWith(
2421
2068
  expect.objectContaining( {
@@ -2426,7 +2073,7 @@ describe( 'Controlling link title text', () => {
2426
2073
 
2427
2074
  // The text input should not be showing as the form is submitted.
2428
2075
  expect(
2429
- queryByRole( container, 'textbox', { name: 'Text' } )
2430
- ).toBeFalsy();
2076
+ screen.queryByRole( 'textbox', { name: 'Text' } )
2077
+ ).not.toBeInTheDocument();
2431
2078
  } );
2432
2079
  } );