@api-client/ui 0.5.39 → 0.5.40

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 (281) hide show
  1. package/build/tsconfig.tsbuildinfo +1 -0
  2. package/package.json +1 -1
  3. package/.aiexclude +0 -3
  4. package/.cursor/rules/html-and-css-best-practices.mdc +0 -63
  5. package/.cursor/rules/lit-best-practices.mdc +0 -89
  6. package/.editorconfig +0 -29
  7. package/.github/CONTRIBUTING.md +0 -24
  8. package/.github/instructions/html-and-css-best-practices.instructions.md +0 -70
  9. package/.github/instructions/lit-best-practices.instructions.md +0 -90
  10. package/.github/release.yml +0 -14
  11. package/.github/stale.yml +0 -23
  12. package/.github/workflows/auto-release.yml +0 -182
  13. package/.github/workflows/release.yml +0 -82
  14. package/.prettierrc.js +0 -14
  15. package/.vscode/settings.json +0 -18
  16. package/RELEASE.md +0 -163
  17. package/RELEASE_SETUP.md +0 -235
  18. package/build/src/demo/DemoPage.d.ts +0 -81
  19. package/build/src/demo/DemoPage.d.ts.map +0 -1
  20. package/build/src/demo/DemoPage.js +0 -175
  21. package/build/src/demo/DemoPage.js.map +0 -1
  22. package/build/src/demo/DemoStyles.d.ts +0 -3
  23. package/build/src/demo/DemoStyles.d.ts.map +0 -1
  24. package/build/src/demo/DemoStyles.js +0 -60
  25. package/build/src/demo/DemoStyles.js.map +0 -1
  26. package/build/test/elements/navigation/Navigation.test.d.ts +0 -3
  27. package/build/test/elements/navigation/Navigation.test.d.ts.map +0 -1
  28. package/build/test/elements/navigation/Navigation.test.js +0 -113
  29. package/build/test/elements/navigation/Navigation.test.js.map +0 -1
  30. package/commitlint.config.cjs +0 -2
  31. package/demo/elements/authorization/AuthPlugin.js +0 -57
  32. package/demo/elements/authorization/AuthProxy.js +0 -215
  33. package/demo/elements/authorization/api-key.html +0 -27
  34. package/demo/elements/authorization/api-key.ts +0 -44
  35. package/demo/elements/authorization/basic.html +0 -27
  36. package/demo/elements/authorization/basic.ts +0 -43
  37. package/demo/elements/authorization/bearer.html +0 -27
  38. package/demo/elements/authorization/bearer.ts +0 -43
  39. package/demo/elements/authorization/env.js +0 -8
  40. package/demo/elements/authorization/index.html +0 -44
  41. package/demo/elements/authorization/ntlm.html +0 -27
  42. package/demo/elements/authorization/ntlm.ts +0 -43
  43. package/demo/elements/authorization/oauth-authorize.html +0 -75
  44. package/demo/elements/authorization/oauth-authorize.ts +0 -40
  45. package/demo/elements/authorization/oauth-error.html +0 -18
  46. package/demo/elements/authorization/oauth-error.ts +0 -10
  47. package/demo/elements/authorization/oauth-popup.html +0 -36
  48. package/demo/elements/authorization/oauth2.html +0 -27
  49. package/demo/elements/authorization/oauth2.ts +0 -100
  50. package/demo/elements/authorization/oidc.html +0 -27
  51. package/demo/elements/authorization/oidc.ts +0 -139
  52. package/demo/elements/authorization/private.crt +0 -31
  53. package/demo/elements/authorization/private.csr +0 -28
  54. package/demo/elements/authorization/private.key +0 -51
  55. package/demo/elements/authorization/private.pem +0 -31
  56. package/demo/elements/authorization/redirect.html +0 -20
  57. package/demo/elements/authorization/ssl-commands.sh +0 -30
  58. package/demo/elements/authorization/ssl.conf +0 -24
  59. package/demo/elements/autocomplete/index.html +0 -64
  60. package/demo/elements/autocomplete/index.ts +0 -171
  61. package/demo/elements/code-editor/CodeEditorDemo.ts +0 -173
  62. package/demo/elements/code-editor/index.html +0 -19
  63. package/demo/elements/context-menu/DemoIcons.ts +0 -21
  64. package/demo/elements/context-menu/basic.html +0 -25
  65. package/demo/elements/context-menu/basic.ts +0 -119
  66. package/demo/elements/context-menu/custom-data.html +0 -25
  67. package/demo/elements/context-menu/custom-data.ts +0 -62
  68. package/demo/elements/context-menu/demo.css +0 -28
  69. package/demo/elements/context-menu/enabled-state.html +0 -25
  70. package/demo/elements/context-menu/enabled-state.ts +0 -73
  71. package/demo/elements/context-menu/icons.html +0 -25
  72. package/demo/elements/context-menu/icons.ts +0 -64
  73. package/demo/elements/context-menu/index.html +0 -43
  74. package/demo/elements/context-menu/nested.html +0 -25
  75. package/demo/elements/context-menu/nestedt.ts +0 -152
  76. package/demo/elements/context-menu/no-execute.html +0 -25
  77. package/demo/elements/context-menu/no-execute.ts +0 -134
  78. package/demo/elements/context-menu/radio-menu.html +0 -25
  79. package/demo/elements/context-menu/radio-menu.ts +0 -83
  80. package/demo/elements/context-menu/separators.html +0 -25
  81. package/demo/elements/context-menu/separators.ts +0 -172
  82. package/demo/elements/currency/index.html +0 -91
  83. package/demo/elements/currency/index.ts +0 -352
  84. package/demo/elements/environment/environment-editor.html +0 -20
  85. package/demo/elements/environment/environment-editor.ts +0 -49
  86. package/demo/elements/environment/index.html +0 -33
  87. package/demo/elements/environment/server-editor.html +0 -20
  88. package/demo/elements/environment/server-editor.ts +0 -67
  89. package/demo/elements/environment/variables-editor.html +0 -20
  90. package/demo/elements/environment/variables-editor.ts +0 -94
  91. package/demo/elements/har/har-viewer.html +0 -20
  92. package/demo/elements/har/har-viewer.ts +0 -76
  93. package/demo/elements/har/har1.har +0 -3044
  94. package/demo/elements/har/har2.json +0 -439
  95. package/demo/elements/har/index.html +0 -26
  96. package/demo/elements/highlight/example.md +0 -27
  97. package/demo/elements/highlight/index.html +0 -31
  98. package/demo/elements/highlight/marked-highlight.html +0 -132
  99. package/demo/elements/highlight/marked-highlight.ts +0 -22
  100. package/demo/elements/highlight/prism-highlight.html +0 -62
  101. package/demo/elements/highlight/prism-highlight.ts +0 -17
  102. package/demo/elements/http/body-editor.html +0 -17
  103. package/demo/elements/http/body-editor.ts +0 -115
  104. package/demo/elements/http/headers.html +0 -17
  105. package/demo/elements/http/headers.ts +0 -59
  106. package/demo/elements/http/http-assertions.html +0 -20
  107. package/demo/elements/http/http-assertions.ts +0 -89
  108. package/demo/elements/http/http-flows.html +0 -23
  109. package/demo/elements/http/http-flows.ts +0 -89
  110. package/demo/elements/http/index.html +0 -45
  111. package/demo/elements/http/request-editor.html +0 -26
  112. package/demo/elements/http/request-editor.ts +0 -197
  113. package/demo/elements/http/request-log.html +0 -16
  114. package/demo/elements/http/request-log.ts +0 -136
  115. package/demo/elements/http/url-editing.html +0 -17
  116. package/demo/elements/http/url-editing.ts +0 -112
  117. package/demo/elements/icons/index.html +0 -81
  118. package/demo/elements/icons/index.ts +0 -52
  119. package/demo/elements/index.html +0 -72
  120. package/demo/elements/mention-textarea/index.html +0 -19
  121. package/demo/elements/mention-textarea/index.ts +0 -205
  122. package/demo/elements/navigation/navigation-item.html +0 -49
  123. package/demo/elements/navigation/navigation-item.ts +0 -131
  124. package/demo/elements/navigation/navigation.html +0 -20
  125. package/demo/elements/navigation/navigation.ts +0 -45
  126. package/demo/elements/project/index.html +0 -29
  127. package/demo/elements/project/project-run-report.html +0 -20
  128. package/demo/elements/project/project-run-report.ts +0 -132
  129. package/demo/elements/project/request-editor.html +0 -23
  130. package/demo/elements/project/request-editor.ts +0 -232
  131. package/demo/elements/user/user-avatar.html +0 -17
  132. package/demo/elements/user/user-avatar.ts +0 -60
  133. package/demo/env.js +0 -4
  134. package/demo/index.html +0 -34
  135. package/demo/layout/index.html +0 -94
  136. package/demo/layout/index.ts +0 -190
  137. package/demo/md/DemoStyles.ts +0 -61
  138. package/demo/md/UiDemoPage.ts +0 -6
  139. package/demo/md/buttons/button.html +0 -121
  140. package/demo/md/buttons/button.ts +0 -246
  141. package/demo/md/buttons/group.html +0 -36
  142. package/demo/md/buttons/group.ts +0 -171
  143. package/demo/md/checkbox/index.html +0 -39
  144. package/demo/md/checkbox/index.ts +0 -220
  145. package/demo/md/chip/chip.html +0 -70
  146. package/demo/md/chip/chip.ts +0 -219
  147. package/demo/md/chip/pawel6c9a.jpg +0 -0
  148. package/demo/md/collapse/CustomDetail.ts +0 -89
  149. package/demo/md/collapse/collapse.html +0 -21
  150. package/demo/md/collapse/collapse.ts +0 -78
  151. package/demo/md/date-picker/date-picker.ts +0 -336
  152. package/demo/md/date-picker/index.html +0 -171
  153. package/demo/md/dialog/confirm-dialog.html +0 -49
  154. package/demo/md/dialog/confirm-dialog.ts +0 -121
  155. package/demo/md/dialog/dialog.html +0 -25
  156. package/demo/md/dialog/dialog.ts +0 -468
  157. package/demo/md/dropdown-list/index.html +0 -31
  158. package/demo/md/dropdown-list/index.ts +0 -158
  159. package/demo/md/icon-button/index.html +0 -122
  160. package/demo/md/icon-button/index.ts +0 -132
  161. package/demo/md/index.html +0 -73
  162. package/demo/md/inputs/input.html +0 -73
  163. package/demo/md/inputs/input.ts +0 -278
  164. package/demo/md/inputs/radio.html +0 -39
  165. package/demo/md/inputs/radio.ts +0 -156
  166. package/demo/md/inputs/switch.html +0 -45
  167. package/demo/md/inputs/switch.ts +0 -144
  168. package/demo/md/list/list.html +0 -65
  169. package/demo/md/list/list.ts +0 -204
  170. package/demo/md/listbox/listbox.html +0 -31
  171. package/demo/md/listbox/listbox.ts +0 -27
  172. package/demo/md/menu/index.html +0 -19
  173. package/demo/md/menu/index.ts +0 -514
  174. package/demo/md/notification/snack.html +0 -21
  175. package/demo/md/notification/snack.ts +0 -70
  176. package/demo/md/progress/progress.html +0 -46
  177. package/demo/md/progress/progress.ts +0 -161
  178. package/demo/md/segmented-button/index.html +0 -21
  179. package/demo/md/segmented-button/index.ts +0 -55
  180. package/demo/md/select/index.html +0 -16
  181. package/demo/md/select/index.ts +0 -217
  182. package/demo/md/tabs/tabs.html +0 -40
  183. package/demo/md/tabs/tabs.ts +0 -214
  184. package/demo/oauth-popup.html +0 -36
  185. package/demo/page.css +0 -8
  186. package/demo/resources/calendar-month.png +0 -0
  187. package/demo/resources/favorite.png +0 -0
  188. package/demo/resources/fingerprint.png +0 -0
  189. package/demo/resources/home-work.png +0 -0
  190. package/demo/resources/mood.png +0 -0
  191. package/demo/resources/print.png +0 -0
  192. package/demo/resources/stars.png +0 -0
  193. package/demo/resources/theaters.png +0 -0
  194. package/demo/tsconfig.json +0 -4
  195. package/eslint.config.js +0 -97
  196. package/scripts/copy-assets.js +0 -21
  197. package/scripts/release.js +0 -66
  198. package/src/demo/DemoPage.ts +0 -169
  199. package/src/demo/DemoStyles.ts +0 -60
  200. package/test/README.md +0 -375
  201. package/test/contextual-menu/ContextMenu.test.ts +0 -760
  202. package/test/contextual-menu/ContextMenuElement.test.ts +0 -569
  203. package/test/core/activity.spec.ts +0 -413
  204. package/test/core/activity_manager.spec.ts +0 -544
  205. package/test/core/application.spec.ts +0 -218
  206. package/test/core/fragment.spec.ts +0 -565
  207. package/test/core/fragment_manager.spec.ts +0 -404
  208. package/test/core/live_data.spec.ts +0 -558
  209. package/test/core/renderer.spec.ts +0 -113
  210. package/test/dom-assertions.test.ts +0 -182
  211. package/test/elements/MonacoSetup.ts +0 -65
  212. package/test/elements/authorization/basic-method.test.ts +0 -177
  213. package/test/elements/authorization/bearer-method.test.ts +0 -143
  214. package/test/elements/authorization/ntlm-method.test.ts +0 -219
  215. package/test/elements/authorization/oauth2-client-credentials-method.test.ts +0 -334
  216. package/test/elements/authorization/oauth2-code-method.test.ts +0 -320
  217. package/test/elements/authorization/oauth2-custom-grant-method.test.ts +0 -255
  218. package/test/elements/authorization/oauth2-device-code-method.test.ts +0 -371
  219. package/test/elements/authorization/oauth2-implicit-method.test.ts +0 -407
  220. package/test/elements/authorization/oauth2-jwt-method.test.ts +0 -217
  221. package/test/elements/authorization/oauth2-password-method.test.ts +0 -275
  222. package/test/elements/authorization/openid-method.test.ts +0 -591
  223. package/test/elements/autocomplete/autocomplete-input.spec.ts +0 -646
  224. package/test/elements/code-editor/code-editor.accessibility.test.ts +0 -298
  225. package/test/elements/code-editor/code-editor.test.ts +0 -574
  226. package/test/elements/currency/CurrencyPicker.accessibility.test.ts +0 -328
  227. package/test/elements/currency/CurrencyPicker.core.test.ts +0 -318
  228. package/test/elements/currency/CurrencyPicker.integration.test.ts +0 -482
  229. package/test/elements/currency/CurrencyPicker.test.ts +0 -486
  230. package/test/elements/data-table/DataTable.browser.test.ts +0 -649
  231. package/test/elements/har/HarUtils.test.ts +0 -45
  232. package/test/elements/har/HarViewerElement.test.ts +0 -687
  233. package/test/elements/har/test-data/har1.har +0 -3044
  234. package/test/elements/highlight/MarkedHighlightElement.test.ts +0 -452
  235. package/test/elements/highlight/PrismHighlightElement.test.ts +0 -79
  236. package/test/elements/highlight/PrismHighlighter.test.ts +0 -94
  237. package/test/elements/highlight/remoteSanitization.md +0 -1
  238. package/test/elements/highlight/test.md +0 -3
  239. package/test/elements/highlight/test1.md +0 -3
  240. package/test/elements/highlight/test2.md +0 -1
  241. package/test/elements/http/BodyFormdataEditorElement.test.ts +0 -482
  242. package/test/elements/http/BodyMultipartEditorElement.test.ts +0 -658
  243. package/test/elements/http/BodyRawEditorElement.test.ts +0 -90
  244. package/test/elements/http/CertificateAdd.test.ts +0 -457
  245. package/test/elements/http/HttpAssertions.test.ts +0 -994
  246. package/test/elements/http/HttpFlows.test.ts +0 -502
  247. package/test/elements/http/UrlEncodeUtils.test.ts +0 -202
  248. package/test/elements/layout/SplitItem.test.ts +0 -440
  249. package/test/elements/layout/SplitLayoutManager.test.ts +0 -1501
  250. package/test/elements/layout/SplitPanel.test.ts +0 -1109
  251. package/test/elements/mention-textarea/MentionTextArea.basic.test.ts +0 -114
  252. package/test/elements/mention-textarea/MentionTextArea.test.ts +0 -613
  253. package/test/elements/navigation/Navigation.test.ts +0 -120
  254. package/test/env.ts +0 -15
  255. package/test/events/EventTypes.test.ts +0 -363
  256. package/test/events/EventsTestHelpers.ts +0 -16
  257. package/test/helpers/TestUtils.ts +0 -243
  258. package/test/helpers/UiMock.ts +0 -185
  259. package/test/lib/Dom.test.ts +0 -231
  260. package/test/md/button/UiButton.test.ts +0 -347
  261. package/test/md/button/UiIconButton.test.ts +0 -155
  262. package/test/md/chip/UiChip.test.ts +0 -219
  263. package/test/md/collapse/UiCollapse.test.ts +0 -250
  264. package/test/md/collapse/flex-layout.test.ts +0 -105
  265. package/test/md/date-time/DateTime.test.ts +0 -348
  266. package/test/md/dialog/UiConfirmDialog.test.ts +0 -131
  267. package/test/md/dialog/UiDialog.test.ts +0 -759
  268. package/test/md/menu/Menu.test.ts +0 -855
  269. package/test/md/menu/MenuIntegration.test.ts +0 -426
  270. package/test/md/menu/MenuItem.test.ts +0 -652
  271. package/test/md/menu/SubMenu.test.ts +0 -410
  272. package/test/md/progress/UiCircularProgressElement.test.ts +0 -481
  273. package/test/md/progress/UiProgressElement.test.ts +0 -117
  274. package/test/md/progress/UiRangeElement.test.ts +0 -156
  275. package/test/md/select/Select.test.ts +0 -925
  276. package/test/plugins/takeScreenshotPlugin.js +0 -35
  277. package/test/setup.test.ts +0 -217
  278. package/test/setup.ts +0 -117
  279. package/test/tsconfig.json +0 -7
  280. package/web-dev-server.config.js +0 -21
  281. package/web-test-runner.config.js +0 -90
@@ -1,646 +0,0 @@
1
- import { fixture, assert, html, oneEvent, nextFrame, aTimeout } from '@open-wc/testing'
2
- import sinon from 'sinon'
3
-
4
- import { AutocompleteInput } from '../../../src/elements/autocomplete/autocomplete-input.js'
5
- import '../../../src/elements/autocomplete/autocomplete-input.js' // Registers the element
6
-
7
- import '../../../src/md/list/ui-list-item.js'
8
- import '../../../src/md/listbox/ui-listbox.js'
9
-
10
- import type MdListItem from '../../../src/md/list/internals/ListItem.js'
11
- import type MdListbox from '../../../src/md/listbox/internals/Listbox.js'
12
-
13
- describe('AutocompleteInput', () => {
14
- async function basicFixture(): Promise<AutocompleteInput> {
15
- return fixture(html`
16
- <autocomplete-input>
17
- <input id="test-input" slot="input" type="text" placeholder="Search..." />
18
- <ui-listbox slot="suggestions" aria-label="Suggestions">
19
- <ui-list-item data-value="apple">Apple</ui-list-item>
20
- <ui-list-item data-value="banana">Banana</ui-list-item>
21
- <ui-list-item data-value="cherry" data-index="value customField" data-custom-field="Sweet Cherry"
22
- >Cherry</ui-list-item
23
- >
24
- </ui-listbox>
25
- </autocomplete-input>
26
- `)
27
- }
28
-
29
- async function noSuggestionsFixture(): Promise<AutocompleteInput> {
30
- return fixture(html`
31
- <autocomplete-input>
32
- <input slot="input" type="text" placeholder="Search..." />
33
- <ui-listbox slot="suggestions" aria-label="Suggestions"></ui-listbox>
34
- </autocomplete-input>
35
- `)
36
- }
37
-
38
- async function noListboxFixture(): Promise<AutocompleteInput> {
39
- return fixture(html`
40
- <autocomplete-input>
41
- <input slot="input" type="text" placeholder="Search..." />
42
- </autocomplete-input>
43
- `)
44
- }
45
-
46
- function getSlottedInput(el: AutocompleteInput): HTMLInputElement | null {
47
- return el.querySelector('[slot="input"]') as HTMLInputElement | null
48
- }
49
-
50
- function getSlottedSuggestions(el: AutocompleteInput): MdListbox | null {
51
- return el.querySelector('[slot="suggestions"]') as MdListbox | null
52
- }
53
-
54
- function getSuggestionItems(suggestionsEl: MdListbox): MdListItem[] {
55
- // Assuming MdListbox.items returns the slotted items that are MdListItem
56
- return (suggestionsEl.items as MdListItem[]).filter(
57
- (item) => item instanceof HTMLElement && item.matches('ui-list-item')
58
- )
59
- }
60
-
61
- describe('Initialization', () => {
62
- it('correctly identifies slotted input and suggestions', async () => {
63
- const el = await basicFixture()
64
- await el.updateComplete // Ensure firstUpdated has run
65
-
66
- const input = getSlottedInput(el)
67
- const suggestions = getSlottedSuggestions(el)
68
-
69
- assert.ok(input)
70
- assert.equal(input?.id, 'test-input')
71
- assert.ok(suggestions)
72
- assert.equal(suggestions?.popover, 'manual')
73
- })
74
-
75
- it('generates an ID for the input if not provided and sets up anchor names', async () => {
76
- const el = (await fixture(html`
77
- <autocomplete-input>
78
- <input slot="input" type="text" />
79
- <ui-listbox slot="suggestions"></ui-listbox>
80
- </autocomplete-input>
81
- `)) as AutocompleteInput
82
- await el.updateComplete
83
- await nextFrame() // Allow for internal state updates
84
-
85
- const input = getSlottedInput(el)!
86
- const suggestions = getSlottedSuggestions(el)!
87
-
88
- assert.match(input.id, /^autocomplete-input-/)
89
- // @ts-expect-error _inputId is a protected member
90
- const internalInputId = el.inputId
91
- assert.equal(input.style.getPropertyValue('anchor-name'), `--${internalInputId}`, 'input has anchor name')
92
- const anchorValue = getComputedStyle(suggestions).getPropertyValue('position-anchor')
93
- assert.equal(anchorValue, `--${internalInputId}`, 'suggestions element has position-anchor')
94
- })
95
-
96
- it('popover is initially closed', async () => {
97
- const el = await basicFixture()
98
- await el.updateComplete
99
- const suggestions = getSlottedSuggestions(el)
100
- assert.isFalse(suggestions?.matches(':popover-open'))
101
- assert.isFalse(el.opened)
102
- })
103
- })
104
-
105
- describe('Popover behavior', () => {
106
- it('opens on input focus with suggestions', async () => {
107
- const el = await basicFixture()
108
- await el.updateComplete
109
- const input = getSlottedInput(el)!
110
- const suggestions = getSlottedSuggestions(el)!
111
-
112
- input.focus()
113
- await nextFrame() // Allow focus event to propagate and popover to open
114
-
115
- assert.isTrue(suggestions.matches(':popover-open'))
116
- assert.isTrue(el.opened)
117
- })
118
-
119
- it('does not open on input focus without suggestions', async () => {
120
- const el = await noSuggestionsFixture()
121
- await el.updateComplete
122
- const input = getSlottedInput(el)!
123
- const suggestions = getSlottedSuggestions(el)!
124
-
125
- input.focus()
126
- await nextFrame()
127
-
128
- assert.isFalse(suggestions.matches(':popover-open'))
129
- })
130
-
131
- it('does not open on input focus if suggestions element is missing', async () => {
132
- const el = await noListboxFixture()
133
- await el.updateComplete
134
- const input = getSlottedInput(el)!
135
-
136
- input.focus()
137
- await nextFrame()
138
- // No suggestions ref, so opened should be false
139
- assert.isFalse(el.opened)
140
- })
141
-
142
- it('closes on input blur', async () => {
143
- const el = await basicFixture()
144
- await el.updateComplete
145
- const input = getSlottedInput(el)!
146
- const suggestions = getSlottedSuggestions(el)!
147
-
148
- input.focus()
149
- await nextFrame()
150
- assert.isTrue(suggestions.matches(':popover-open'))
151
-
152
- input.blur()
153
- // Blur handler uses requestAnimationFrame
154
- await aTimeout(0) // Wait for rAF
155
- await nextFrame() // Wait for potential re-render
156
-
157
- assert.isFalse(suggestions.matches(':popover-open'))
158
- assert.isFalse(el.opened)
159
- })
160
-
161
- it('closes on Escape key', async () => {
162
- const el = await basicFixture()
163
- await el.updateComplete
164
- const input = getSlottedInput(el)!
165
- const suggestions = getSlottedSuggestions(el)!
166
-
167
- input.focus()
168
- await nextFrame()
169
- assert.isTrue(suggestions.matches(':popover-open'))
170
-
171
- input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, composed: true }))
172
- await nextFrame()
173
-
174
- assert.isFalse(suggestions.matches(':popover-open'))
175
- })
176
- })
177
-
178
- describe('Filtering', () => {
179
- let el: AutocompleteInput
180
- let input: HTMLInputElement
181
- let suggestionsBox: MdListbox
182
- let items: MdListItem[]
183
-
184
- beforeEach(async () => {
185
- el = await basicFixture()
186
- await el.updateComplete
187
- input = getSlottedInput(el)!
188
- suggestionsBox = getSlottedSuggestions(el)!
189
- items = getSuggestionItems(suggestionsBox)
190
- input.focus() // Open popover for filtering tests
191
- await nextFrame()
192
- })
193
-
194
- it('shows all items for empty query', () => {
195
- input.value = ''
196
- input.dispatchEvent(new Event('input', { bubbles: true, composed: true }))
197
- items.forEach((item) => assert.isFalse(item.hidden))
198
- })
199
-
200
- it('filters items based on data-value', async () => {
201
- input.value = 'app'
202
- input.dispatchEvent(new Event('input', { bubbles: true, composed: true }))
203
- await nextFrame()
204
-
205
- assert.isFalse(items[0].hidden) // Apple
206
- assert.isTrue(items[1].hidden) // Banana
207
- assert.isTrue(items[2].hidden) // Cherry
208
- })
209
-
210
- it('filters items based on textContent if data-value is not present or does not match', async () => {
211
- // Modify first item to not have data-value for this test
212
- items[0].removeAttribute('data-value')
213
- items[0].textContent = 'Pineapple'
214
- await nextFrame()
215
-
216
- input.value = 'pine'
217
- input.dispatchEvent(new Event('input', { bubbles: true, composed: true }))
218
- await nextFrame()
219
-
220
- assert.isFalse(items[0].hidden) // Pineapple (via textContent)
221
- assert.isTrue(items[1].hidden) // Banana
222
- assert.isTrue(items[2].hidden) // Cherry
223
- })
224
-
225
- it('filters items based on data-index fields', async () => {
226
- input.value = 'Sweet' // Matches data-custom-field="Sweet Cherry" via data-index on items[2]
227
- input.dispatchEvent(new Event('input', { bubbles: true, composed: true }))
228
- await nextFrame()
229
-
230
- assert.isTrue(items[0].hidden) // Apple
231
- assert.isTrue(items[1].hidden) // Banana
232
- assert.isFalse(items[2].hidden) // Cherry
233
- })
234
-
235
- it('is case-insensitive', async () => {
236
- input.value = 'BaNaNa'
237
- input.dispatchEvent(new Event('input', { bubbles: true, composed: true }))
238
- await nextFrame()
239
-
240
- assert.isTrue(items[0].hidden) // Apple
241
- assert.isFalse(items[1].hidden) // Banana
242
- assert.isTrue(items[2].hidden) // Cherry
243
- })
244
-
245
- it('closes popover if no items match', async () => {
246
- input.value = 'nonexistent'
247
- input.dispatchEvent(new Event('input', { bubbles: true, composed: true }))
248
- await nextFrame()
249
- await aTimeout(0) // filterSuggestions might call closeSuggestions, which might be async
250
-
251
- items.forEach((item) => assert.isTrue(item.hidden))
252
- assert.isFalse(suggestionsBox.matches(':popover-open'))
253
- })
254
- })
255
-
256
- describe('Keyboard navigation', () => {
257
- let el: AutocompleteInput
258
- let input: HTMLInputElement
259
- let suggestionsBox: MdListbox
260
- let notifySelectSpy: sinon.SinonSpy
261
-
262
- beforeEach(async () => {
263
- el = await basicFixture()
264
- await el.updateComplete
265
- input = getSlottedInput(el)!
266
- suggestionsBox = getSlottedSuggestions(el)!
267
- // For keyboard nav, listbox needs to handle highlight internally.
268
- // We spy on its methods.
269
- sinon.spy(suggestionsBox, 'highlightNext')
270
- sinon.spy(suggestionsBox, 'highlightPrevious')
271
- notifySelectSpy = sinon.spy(suggestionsBox, 'notifySelect')
272
- })
273
-
274
- afterEach(() => {
275
- sinon.restore()
276
- })
277
-
278
- it('ArrowDown highlights next item and opens popover if closed', async () => {
279
- assert.isFalse(suggestionsBox.matches(':popover-open'))
280
- input.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true, composed: true }))
281
- // handleKeydown uses rAF if popover was closed
282
- await aTimeout(0) // for rAF
283
- await nextFrame() // for popover to open and highlight
284
-
285
- assert.isTrue(suggestionsBox.matches(':popover-open'))
286
- assert.isTrue((suggestionsBox.highlightNext as sinon.SinonSpy).calledOnce)
287
- })
288
-
289
- it('ArrowUp highlights previous item', async () => {
290
- input.focus() // Open popover
291
- await nextFrame()
292
- input.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp', bubbles: true, composed: true }))
293
- await nextFrame()
294
- assert.isTrue((suggestionsBox.highlightPrevious as sinon.SinonSpy).calledOnce)
295
- })
296
-
297
- it('Enter selects highlighted item and dispatches event', async () => {
298
- input.focus() // Open popover
299
- await nextFrame()
300
-
301
- // Simulate item being highlighted by listbox (e.g., first item)
302
- const items = getSuggestionItems(suggestionsBox)
303
- suggestionsBox.highlightListItem = items[0]
304
- await nextFrame()
305
-
306
- const eventPromise = oneEvent(el, 'autocomplete')
307
- input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true, composed: true }))
308
-
309
- const event = await eventPromise
310
-
311
- assert.isTrue((suggestionsBox.notifySelect as sinon.SinonSpy).calledWith(items[0]))
312
- assert.equal(event.detail.item, items[0])
313
- assert.isFalse(suggestionsBox.matches(':popover-open')) // Popover should close
314
- })
315
-
316
- it('Enter does nothing if popover closed or no item highlighted', async () => {
317
- assert.isFalse(suggestionsBox.matches(':popover-open'))
318
- suggestionsBox.highlightListItem = null // Ensure no item is highlighted
319
-
320
- const spy = sinon.spy()
321
- el.addEventListener('autocomplete', spy)
322
-
323
- input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true, composed: true }))
324
- await nextFrame()
325
-
326
- assert.isFalse(spy.called)
327
- assert.isFalse(notifySelectSpy.called)
328
- })
329
- })
330
-
331
- describe('Suggestion selection (click)', () => {
332
- it('selects item on click and dispatches event', async () => {
333
- const el = await basicFixture()
334
- await el.updateComplete
335
- const input = getSlottedInput(el)!
336
- const suggestionsBox = getSlottedSuggestions(el)!
337
- const items = getSuggestionItems(suggestionsBox)
338
-
339
- input.focus() // Open popover
340
- await nextFrame()
341
-
342
- const eventPromise = oneEvent(el, 'autocomplete')
343
- // Simulate click on the first item.
344
- // The `ui-listbox` should dispatch 'select' upon item click.
345
- // We simulate the 'select' event from the listbox.
346
- suggestionsBox.dispatchEvent(
347
- new CustomEvent('select', {
348
- detail: { item: items[1] }, // Select 'Banana'
349
- bubbles: true, // Ensure it bubbles to autocomplete-input if needed by its listener
350
- composed: true,
351
- })
352
- )
353
-
354
- const event = await eventPromise
355
- assert.equal(event.detail.item, items[1])
356
- assert.isFalse(suggestionsBox.matches(':popover-open')) // Popover should close
357
- })
358
- })
359
-
360
- describe('Dynamic content', () => {
361
- it('handles dynamically added input', async () => {
362
- const el = await fixture<AutocompleteInput>(html`<autocomplete-input></autocomplete-input>`)
363
- await el.updateComplete
364
-
365
- const inputEl = document.createElement('input')
366
- inputEl.slot = 'input'
367
- inputEl.type = 'text'
368
- el.appendChild(inputEl)
369
- await nextFrame() // MutationObserver is async
370
- await el.updateComplete // Ensure component processes the new input
371
-
372
- const slottedInput = getSlottedInput(el)
373
- assert.equal(slottedInput, inputEl)
374
- assert.match(inputEl.id, /^autocomplete-input-/)
375
- })
376
-
377
- it('handles dynamically added suggestions', async () => {
378
- const el = await fixture<AutocompleteInput>(
379
- html`<autocomplete-input><input slot="input" id="dyn-input" /></autocomplete-input>`
380
- )
381
- await el.updateComplete
382
- const input = getSlottedInput(el)!
383
-
384
- const suggestionsEl = document.createElement('ui-listbox') as MdListbox
385
- suggestionsEl.slot = 'suggestions'
386
- el.appendChild(suggestionsEl)
387
- await nextFrame() // MutationObserver
388
- await el.updateComplete
389
-
390
- const slottedSuggestions = getSlottedSuggestions(el)
391
- assert.equal(slottedSuggestions, suggestionsEl)
392
- assert.equal(suggestionsEl.popover, 'manual')
393
- const anchorValue = getComputedStyle(suggestionsEl).getPropertyValue('position-anchor')
394
- assert.equal(anchorValue, `--${input.id}`)
395
- })
396
-
397
- it('re-filters when suggestion items change (via itemschange event)', async () => {
398
- const el = await basicFixture()
399
- await el.updateComplete
400
- const input = getSlottedInput(el)!
401
- const suggestionsBox = getSlottedSuggestions(el)!
402
- let items = getSuggestionItems(suggestionsBox)
403
-
404
- input.focus()
405
- input.value = 'ban' // Should show "Banana"
406
- input.dispatchEvent(new Event('input', { bubbles: true, composed: true }))
407
- await nextFrame()
408
- assert.isTrue(items[0].hidden) // Apple
409
- assert.isFalse(items[1].hidden) // Banana
410
-
411
- // Add a new item that matches "ban"
412
- const newItem = document.createElement('ui-list-item') as MdListItem
413
- newItem.dataset.value = 'bandana'
414
- newItem.textContent = 'Bandana'
415
- suggestionsBox.appendChild(newItem)
416
-
417
- // Simulate 'itemschange' event from listbox
418
- suggestionsBox.dispatchEvent(new CustomEvent('itemschange'))
419
- await nextFrame() // Allow handler to run
420
-
421
- items = getSuggestionItems(suggestionsBox) // Re-fetch items
422
- assert.isFalse(items.find((i) => i.dataset.value === 'bandana')?.hidden)
423
- assert.isFalse(items.find((i) => i.dataset.value === 'banana')?.hidden)
424
- assert.isTrue(items.find((i) => i.dataset.value === 'apple')?.hidden)
425
- })
426
- })
427
-
428
- describe('`opened` property', () => {
429
- it('reflects popover state', async () => {
430
- const el = await basicFixture()
431
- await el.updateComplete
432
- const input = getSlottedInput(el)!
433
- const suggestions = getSlottedSuggestions(el)!
434
-
435
- assert.isFalse(el.opened)
436
-
437
- input.focus()
438
- await nextFrame()
439
- assert.isTrue(el.opened)
440
- assert.isTrue(suggestions.matches(':popover-open'))
441
-
442
- // @ts-expect-error protected method
443
- el.closeSuggestions()
444
- await nextFrame()
445
- assert.isFalse(el.opened)
446
- assert.isFalse(suggestions.matches(':popover-open'))
447
- })
448
- })
449
-
450
- describe('Popover Positioning', () => {
451
- let el: AutocompleteInput
452
- let input: HTMLInputElement
453
- let suggestionsBox: MdListbox
454
- let getBoundingClientRectStub: sinon.SinonStub
455
-
456
- // Define a standard popover height for consistent threshold calculation
457
- const popoverVisibleHeight = 200 // px, used for scrollHeight
458
-
459
- beforeEach(async () => {
460
- el = await basicFixture() // This fixture has suggestions
461
- await el.updateComplete // Ensures inputRef is set up
462
- input = getSlottedInput(el)!
463
- suggestionsBox = getSlottedSuggestions(el)!
464
-
465
- // Set a known height for the suggestions box to make its scrollHeight predictable
466
- suggestionsBox.style.height = `${popoverVisibleHeight}px`
467
- suggestionsBox.style.display = 'block' // Ensure it's not display:none from other CSS
468
- suggestionsBox.style.overflow = 'auto'
469
- // Ensure it has some content to have a scrollHeight if not stubbed
470
- if (getSuggestionItems(suggestionsBox).length === 0) {
471
- const item = document.createElement('ui-list-item')
472
- item.textContent = 'Dummy Item'
473
- suggestionsBox.appendChild(item)
474
- }
475
- await nextFrame() // Allow DOM to update for scrollHeight calculation
476
-
477
- // @ts-expect-error accessing protected member `inputRef`
478
- const anchorEl = el.inputRef as HTMLElement // Default anchor
479
- if (!anchorEl) {
480
- throw new Error('el.inputRef was not initialized')
481
- }
482
- getBoundingClientRectStub = sinon.stub(anchorEl, 'getBoundingClientRect')
483
- })
484
-
485
- afterEach(() => {
486
- sinon.restore() // Restores window.innerHeight and the getBoundingClientRectStub
487
- })
488
-
489
- function setAnchorPosition(
490
- anchorRect: Partial<DOMRect & { height: number; width: number }>,
491
- viewportHeight: number
492
- ) {
493
- getBoundingClientRectStub.returns(anchorRect as DOMRect)
494
- // Stub window.innerHeight for this call, will be restored by sinon.restore() in afterEach
495
- sinon.stub(window, 'innerHeight').get(() => viewportHeight)
496
- }
497
-
498
- it('positions at bottom when ample space below', async () => {
499
- // Anchor near top, viewport large. popoverThresholdHeight will be popoverVisibleHeight (200)
500
- setAnchorPosition({ top: 50, bottom: 80, height: 30, x: 0, y: 50, width: 100, left: 0, right: 100 }, 800)
501
- // spaceBelow = 800 - 80 = 720. 720 > 200.
502
-
503
- input.focus() // Triggers openSuggestions
504
- await el.updateComplete // For positionArea state change and re-render
505
- await nextFrame() // For DOM to reflect changes
506
-
507
- assert.equal(el.positionArea, 'bottom')
508
- })
509
-
510
- it('positions at top when insufficient space below but sufficient space above', async () => {
511
- // popoverThresholdHeight = 200
512
- // Anchor bottom: 750. Viewport: 800. spaceBelow = 50. (50 < 200)
513
- // Anchor top: 500. spaceAbove = 500. (500 > 50) && (500 > 200) -> true
514
- setAnchorPosition({ top: 500, bottom: 750, height: 250, x: 0, y: 500, width: 100, left: 0, right: 100 }, 800)
515
-
516
- input.focus()
517
- await el.updateComplete
518
- await nextFrame()
519
-
520
- assert.equal(el.positionArea, 'top')
521
- })
522
-
523
- it('positions at top when insufficient space below and more space above', async () => {
524
- // popoverThresholdHeight = 200
525
- // spaceBelow = 50 (anchor.bottom = viewportHeight - 50)
526
- // spaceAbove = 100 (anchor.top = 100)
527
- // viewportHeight = anchor.top + anchor.height + spaceBelow = 100 + 30 + 50 = 180
528
- // Test case: spaceBelow=50, spaceAbove=100. popoverThresholdHeight=200.
529
- // (50 < 200) is true.
530
- // (100 > 50) is true.
531
- // (100 > 200) is false. -> first 'top' condition fails.
532
- // Second 'top' condition: (spaceBelow < popoverThresholdHeight && spaceAbove > spaceBelow) -> true.
533
- setAnchorPosition({ top: 100, bottom: 130, height: 30, x: 0, y: 100, width: 100, left: 0, right: 100 }, 180)
534
-
535
- input.focus()
536
- await el.updateComplete
537
- await nextFrame()
538
-
539
- assert.equal(el.positionArea, 'top')
540
- })
541
-
542
- it('positions at bottom when insufficient space below and also insufficient (or less) space above', async () => {
543
- // popoverThresholdHeight = 200
544
- // spaceBelow = 50 (anchor.bottom = viewportHeight - 50)
545
- // spaceAbove = 40 (anchor.top = 40)
546
- // viewportHeight = anchor.top + anchor.height + spaceBelow = 40 + 30 + 50 = 120
547
- // Test case: spaceBelow=50, spaceAbove=40.
548
- // (50 < 200) is true.
549
- // (40 > 50) is false. -> both 'top' conditions fail. Defaults to 'bottom'.
550
- setAnchorPosition({ top: 40, bottom: 70, height: 30, x: 0, y: 40, width: 100, left: 0, right: 100 }, 120)
551
-
552
- input.focus()
553
- await el.updateComplete
554
- await nextFrame()
555
-
556
- assert.equal(el.positionArea, 'bottom')
557
- })
558
-
559
- it('uses fallback threshold of 150px if popover scrollHeight is 0', async () => {
560
- // Stub scrollHeight to be 0 for this test.
561
- const scrollHeightStub = sinon.stub(suggestionsBox, 'scrollHeight').get(() => 0)
562
-
563
- // popoverThresholdHeight will be 150 (the fallback).
564
- // Scenario: spaceBelow = 100, spaceAbove = 200.
565
- // (100 < 150) is true.
566
- // (200 > 100) is true. (200 > 150) is true. -> Should be 'top'.
567
- setAnchorPosition({ top: 500, bottom: 700, height: 200, x: 0, y: 500, width: 100, left: 0, right: 100 }, 800)
568
- // spaceBelow = 800 - 700 = 100
569
- // spaceAbove = 500 (mistake in manual calc above, should be 500)
570
- // Corrected: spaceBelow = 100. spaceAbove = 500. Threshold = 150.
571
- // (100 < 150) -> true
572
- // (500 > 100) -> true. (500 > 150) -> true. Result: 'top'.
573
-
574
- input.focus()
575
- await el.updateComplete
576
- await nextFrame()
577
-
578
- assert.equal(el.positionArea, 'top')
579
- scrollHeightStub.restore()
580
- })
581
-
582
- it('uses slotted anchor for positioning if provided', async () => {
583
- // Need to create a new fixture for this specific setup
584
- el = await fixture(html`
585
- <autocomplete-input>
586
- <div slot="anchor" id="custom-anchor" style="height: 20px; width: 100px; border: 1px solid red;"></div>
587
- <input slot="input" type="text" />
588
- <ui-listbox slot="suggestions" style="height: ${popoverVisibleHeight}px; overflow: auto;">
589
- <ui-list-item>Item 1</ui-list-item>
590
- </ui-listbox>
591
- </autocomplete-input>
592
- `)
593
- await el.updateComplete
594
- input = getSlottedInput(el)! // Still need to focus input
595
- const customAnchor = el.querySelector('#custom-anchor') as HTMLElement
596
-
597
- getBoundingClientRectStub.restore() // remove stub from default input anchor
598
- getBoundingClientRectStub = sinon.stub(customAnchor, 'getBoundingClientRect')
599
-
600
- // Scenario: custom anchor is near bottom, should open top. popoverThresholdHeight = 200.
601
- setAnchorPosition({ top: 720, bottom: 750, height: 30, x: 0, y: 720, width: 100, left: 0, right: 100 }, 800)
602
-
603
- input.focus() // Triggers openSuggestions
604
- await el.updateComplete
605
- await nextFrame()
606
-
607
- assert.equal(el.positionArea, 'top')
608
- assert.isTrue(getBoundingClientRectStub.called)
609
- })
610
- })
611
-
612
- describe('Accessibility', () => {
613
- it('is accessible when initially rendered', async () => {
614
- const el = await basicFixture()
615
- await el.updateComplete
616
- await assert.isAccessible(el)
617
- })
618
-
619
- it('is accessible when popover is open', async () => {
620
- const el = await basicFixture()
621
- await el.updateComplete
622
- const input = getSlottedInput(el)!
623
-
624
- input.focus() // Opens popover
625
- await nextFrame() // Allow popover to open
626
- await el.updateComplete // Ensure state updates related to opening are done
627
-
628
- await assert.isAccessible(el)
629
- })
630
-
631
- it('is accessible when popover is open and an item is highlighted', async () => {
632
- const el = await basicFixture()
633
- await el.updateComplete
634
- const input = getSlottedInput(el)!
635
-
636
- input.focus() // Opens popover
637
- await nextFrame()
638
-
639
- // Simulate highlighting the first item
640
- input.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true, composed: true }))
641
- await aTimeout(0) // for rAF in handleKeydown
642
- await nextFrame() // for highlight to apply
643
- await assert.isAccessible(el)
644
- })
645
- })
646
- })