@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.
- package/build/tsconfig.tsbuildinfo +1 -0
- package/package.json +1 -1
- package/.aiexclude +0 -3
- package/.cursor/rules/html-and-css-best-practices.mdc +0 -63
- package/.cursor/rules/lit-best-practices.mdc +0 -89
- package/.editorconfig +0 -29
- package/.github/CONTRIBUTING.md +0 -24
- package/.github/instructions/html-and-css-best-practices.instructions.md +0 -70
- package/.github/instructions/lit-best-practices.instructions.md +0 -90
- package/.github/release.yml +0 -14
- package/.github/stale.yml +0 -23
- package/.github/workflows/auto-release.yml +0 -182
- package/.github/workflows/release.yml +0 -82
- package/.prettierrc.js +0 -14
- package/.vscode/settings.json +0 -18
- package/RELEASE.md +0 -163
- package/RELEASE_SETUP.md +0 -235
- package/build/src/demo/DemoPage.d.ts +0 -81
- package/build/src/demo/DemoPage.d.ts.map +0 -1
- package/build/src/demo/DemoPage.js +0 -175
- package/build/src/demo/DemoPage.js.map +0 -1
- package/build/src/demo/DemoStyles.d.ts +0 -3
- package/build/src/demo/DemoStyles.d.ts.map +0 -1
- package/build/src/demo/DemoStyles.js +0 -60
- package/build/src/demo/DemoStyles.js.map +0 -1
- package/build/test/elements/navigation/Navigation.test.d.ts +0 -3
- package/build/test/elements/navigation/Navigation.test.d.ts.map +0 -1
- package/build/test/elements/navigation/Navigation.test.js +0 -113
- package/build/test/elements/navigation/Navigation.test.js.map +0 -1
- package/commitlint.config.cjs +0 -2
- package/demo/elements/authorization/AuthPlugin.js +0 -57
- package/demo/elements/authorization/AuthProxy.js +0 -215
- package/demo/elements/authorization/api-key.html +0 -27
- package/demo/elements/authorization/api-key.ts +0 -44
- package/demo/elements/authorization/basic.html +0 -27
- package/demo/elements/authorization/basic.ts +0 -43
- package/demo/elements/authorization/bearer.html +0 -27
- package/demo/elements/authorization/bearer.ts +0 -43
- package/demo/elements/authorization/env.js +0 -8
- package/demo/elements/authorization/index.html +0 -44
- package/demo/elements/authorization/ntlm.html +0 -27
- package/demo/elements/authorization/ntlm.ts +0 -43
- package/demo/elements/authorization/oauth-authorize.html +0 -75
- package/demo/elements/authorization/oauth-authorize.ts +0 -40
- package/demo/elements/authorization/oauth-error.html +0 -18
- package/demo/elements/authorization/oauth-error.ts +0 -10
- package/demo/elements/authorization/oauth-popup.html +0 -36
- package/demo/elements/authorization/oauth2.html +0 -27
- package/demo/elements/authorization/oauth2.ts +0 -100
- package/demo/elements/authorization/oidc.html +0 -27
- package/demo/elements/authorization/oidc.ts +0 -139
- package/demo/elements/authorization/private.crt +0 -31
- package/demo/elements/authorization/private.csr +0 -28
- package/demo/elements/authorization/private.key +0 -51
- package/demo/elements/authorization/private.pem +0 -31
- package/demo/elements/authorization/redirect.html +0 -20
- package/demo/elements/authorization/ssl-commands.sh +0 -30
- package/demo/elements/authorization/ssl.conf +0 -24
- package/demo/elements/autocomplete/index.html +0 -64
- package/demo/elements/autocomplete/index.ts +0 -171
- package/demo/elements/code-editor/CodeEditorDemo.ts +0 -173
- package/demo/elements/code-editor/index.html +0 -19
- package/demo/elements/context-menu/DemoIcons.ts +0 -21
- package/demo/elements/context-menu/basic.html +0 -25
- package/demo/elements/context-menu/basic.ts +0 -119
- package/demo/elements/context-menu/custom-data.html +0 -25
- package/demo/elements/context-menu/custom-data.ts +0 -62
- package/demo/elements/context-menu/demo.css +0 -28
- package/demo/elements/context-menu/enabled-state.html +0 -25
- package/demo/elements/context-menu/enabled-state.ts +0 -73
- package/demo/elements/context-menu/icons.html +0 -25
- package/demo/elements/context-menu/icons.ts +0 -64
- package/demo/elements/context-menu/index.html +0 -43
- package/demo/elements/context-menu/nested.html +0 -25
- package/demo/elements/context-menu/nestedt.ts +0 -152
- package/demo/elements/context-menu/no-execute.html +0 -25
- package/demo/elements/context-menu/no-execute.ts +0 -134
- package/demo/elements/context-menu/radio-menu.html +0 -25
- package/demo/elements/context-menu/radio-menu.ts +0 -83
- package/demo/elements/context-menu/separators.html +0 -25
- package/demo/elements/context-menu/separators.ts +0 -172
- package/demo/elements/currency/index.html +0 -91
- package/demo/elements/currency/index.ts +0 -352
- package/demo/elements/environment/environment-editor.html +0 -20
- package/demo/elements/environment/environment-editor.ts +0 -49
- package/demo/elements/environment/index.html +0 -33
- package/demo/elements/environment/server-editor.html +0 -20
- package/demo/elements/environment/server-editor.ts +0 -67
- package/demo/elements/environment/variables-editor.html +0 -20
- package/demo/elements/environment/variables-editor.ts +0 -94
- package/demo/elements/har/har-viewer.html +0 -20
- package/demo/elements/har/har-viewer.ts +0 -76
- package/demo/elements/har/har1.har +0 -3044
- package/demo/elements/har/har2.json +0 -439
- package/demo/elements/har/index.html +0 -26
- package/demo/elements/highlight/example.md +0 -27
- package/demo/elements/highlight/index.html +0 -31
- package/demo/elements/highlight/marked-highlight.html +0 -132
- package/demo/elements/highlight/marked-highlight.ts +0 -22
- package/demo/elements/highlight/prism-highlight.html +0 -62
- package/demo/elements/highlight/prism-highlight.ts +0 -17
- package/demo/elements/http/body-editor.html +0 -17
- package/demo/elements/http/body-editor.ts +0 -115
- package/demo/elements/http/headers.html +0 -17
- package/demo/elements/http/headers.ts +0 -59
- package/demo/elements/http/http-assertions.html +0 -20
- package/demo/elements/http/http-assertions.ts +0 -89
- package/demo/elements/http/http-flows.html +0 -23
- package/demo/elements/http/http-flows.ts +0 -89
- package/demo/elements/http/index.html +0 -45
- package/demo/elements/http/request-editor.html +0 -26
- package/demo/elements/http/request-editor.ts +0 -197
- package/demo/elements/http/request-log.html +0 -16
- package/demo/elements/http/request-log.ts +0 -136
- package/demo/elements/http/url-editing.html +0 -17
- package/demo/elements/http/url-editing.ts +0 -112
- package/demo/elements/icons/index.html +0 -81
- package/demo/elements/icons/index.ts +0 -52
- package/demo/elements/index.html +0 -72
- package/demo/elements/mention-textarea/index.html +0 -19
- package/demo/elements/mention-textarea/index.ts +0 -205
- package/demo/elements/navigation/navigation-item.html +0 -49
- package/demo/elements/navigation/navigation-item.ts +0 -131
- package/demo/elements/navigation/navigation.html +0 -20
- package/demo/elements/navigation/navigation.ts +0 -45
- package/demo/elements/project/index.html +0 -29
- package/demo/elements/project/project-run-report.html +0 -20
- package/demo/elements/project/project-run-report.ts +0 -132
- package/demo/elements/project/request-editor.html +0 -23
- package/demo/elements/project/request-editor.ts +0 -232
- package/demo/elements/user/user-avatar.html +0 -17
- package/demo/elements/user/user-avatar.ts +0 -60
- package/demo/env.js +0 -4
- package/demo/index.html +0 -34
- package/demo/layout/index.html +0 -94
- package/demo/layout/index.ts +0 -190
- package/demo/md/DemoStyles.ts +0 -61
- package/demo/md/UiDemoPage.ts +0 -6
- package/demo/md/buttons/button.html +0 -121
- package/demo/md/buttons/button.ts +0 -246
- package/demo/md/buttons/group.html +0 -36
- package/demo/md/buttons/group.ts +0 -171
- package/demo/md/checkbox/index.html +0 -39
- package/demo/md/checkbox/index.ts +0 -220
- package/demo/md/chip/chip.html +0 -70
- package/demo/md/chip/chip.ts +0 -219
- package/demo/md/chip/pawel6c9a.jpg +0 -0
- package/demo/md/collapse/CustomDetail.ts +0 -89
- package/demo/md/collapse/collapse.html +0 -21
- package/demo/md/collapse/collapse.ts +0 -78
- package/demo/md/date-picker/date-picker.ts +0 -336
- package/demo/md/date-picker/index.html +0 -171
- package/demo/md/dialog/confirm-dialog.html +0 -49
- package/demo/md/dialog/confirm-dialog.ts +0 -121
- package/demo/md/dialog/dialog.html +0 -25
- package/demo/md/dialog/dialog.ts +0 -468
- package/demo/md/dropdown-list/index.html +0 -31
- package/demo/md/dropdown-list/index.ts +0 -158
- package/demo/md/icon-button/index.html +0 -122
- package/demo/md/icon-button/index.ts +0 -132
- package/demo/md/index.html +0 -73
- package/demo/md/inputs/input.html +0 -73
- package/demo/md/inputs/input.ts +0 -278
- package/demo/md/inputs/radio.html +0 -39
- package/demo/md/inputs/radio.ts +0 -156
- package/demo/md/inputs/switch.html +0 -45
- package/demo/md/inputs/switch.ts +0 -144
- package/demo/md/list/list.html +0 -65
- package/demo/md/list/list.ts +0 -204
- package/demo/md/listbox/listbox.html +0 -31
- package/demo/md/listbox/listbox.ts +0 -27
- package/demo/md/menu/index.html +0 -19
- package/demo/md/menu/index.ts +0 -514
- package/demo/md/notification/snack.html +0 -21
- package/demo/md/notification/snack.ts +0 -70
- package/demo/md/progress/progress.html +0 -46
- package/demo/md/progress/progress.ts +0 -161
- package/demo/md/segmented-button/index.html +0 -21
- package/demo/md/segmented-button/index.ts +0 -55
- package/demo/md/select/index.html +0 -16
- package/demo/md/select/index.ts +0 -217
- package/demo/md/tabs/tabs.html +0 -40
- package/demo/md/tabs/tabs.ts +0 -214
- package/demo/oauth-popup.html +0 -36
- package/demo/page.css +0 -8
- package/demo/resources/calendar-month.png +0 -0
- package/demo/resources/favorite.png +0 -0
- package/demo/resources/fingerprint.png +0 -0
- package/demo/resources/home-work.png +0 -0
- package/demo/resources/mood.png +0 -0
- package/demo/resources/print.png +0 -0
- package/demo/resources/stars.png +0 -0
- package/demo/resources/theaters.png +0 -0
- package/demo/tsconfig.json +0 -4
- package/eslint.config.js +0 -97
- package/scripts/copy-assets.js +0 -21
- package/scripts/release.js +0 -66
- package/src/demo/DemoPage.ts +0 -169
- package/src/demo/DemoStyles.ts +0 -60
- package/test/README.md +0 -375
- package/test/contextual-menu/ContextMenu.test.ts +0 -760
- package/test/contextual-menu/ContextMenuElement.test.ts +0 -569
- package/test/core/activity.spec.ts +0 -413
- package/test/core/activity_manager.spec.ts +0 -544
- package/test/core/application.spec.ts +0 -218
- package/test/core/fragment.spec.ts +0 -565
- package/test/core/fragment_manager.spec.ts +0 -404
- package/test/core/live_data.spec.ts +0 -558
- package/test/core/renderer.spec.ts +0 -113
- package/test/dom-assertions.test.ts +0 -182
- package/test/elements/MonacoSetup.ts +0 -65
- package/test/elements/authorization/basic-method.test.ts +0 -177
- package/test/elements/authorization/bearer-method.test.ts +0 -143
- package/test/elements/authorization/ntlm-method.test.ts +0 -219
- package/test/elements/authorization/oauth2-client-credentials-method.test.ts +0 -334
- package/test/elements/authorization/oauth2-code-method.test.ts +0 -320
- package/test/elements/authorization/oauth2-custom-grant-method.test.ts +0 -255
- package/test/elements/authorization/oauth2-device-code-method.test.ts +0 -371
- package/test/elements/authorization/oauth2-implicit-method.test.ts +0 -407
- package/test/elements/authorization/oauth2-jwt-method.test.ts +0 -217
- package/test/elements/authorization/oauth2-password-method.test.ts +0 -275
- package/test/elements/authorization/openid-method.test.ts +0 -591
- package/test/elements/autocomplete/autocomplete-input.spec.ts +0 -646
- package/test/elements/code-editor/code-editor.accessibility.test.ts +0 -298
- package/test/elements/code-editor/code-editor.test.ts +0 -574
- package/test/elements/currency/CurrencyPicker.accessibility.test.ts +0 -328
- package/test/elements/currency/CurrencyPicker.core.test.ts +0 -318
- package/test/elements/currency/CurrencyPicker.integration.test.ts +0 -482
- package/test/elements/currency/CurrencyPicker.test.ts +0 -486
- package/test/elements/data-table/DataTable.browser.test.ts +0 -649
- package/test/elements/har/HarUtils.test.ts +0 -45
- package/test/elements/har/HarViewerElement.test.ts +0 -687
- package/test/elements/har/test-data/har1.har +0 -3044
- package/test/elements/highlight/MarkedHighlightElement.test.ts +0 -452
- package/test/elements/highlight/PrismHighlightElement.test.ts +0 -79
- package/test/elements/highlight/PrismHighlighter.test.ts +0 -94
- package/test/elements/highlight/remoteSanitization.md +0 -1
- package/test/elements/highlight/test.md +0 -3
- package/test/elements/highlight/test1.md +0 -3
- package/test/elements/highlight/test2.md +0 -1
- package/test/elements/http/BodyFormdataEditorElement.test.ts +0 -482
- package/test/elements/http/BodyMultipartEditorElement.test.ts +0 -658
- package/test/elements/http/BodyRawEditorElement.test.ts +0 -90
- package/test/elements/http/CertificateAdd.test.ts +0 -457
- package/test/elements/http/HttpAssertions.test.ts +0 -994
- package/test/elements/http/HttpFlows.test.ts +0 -502
- package/test/elements/http/UrlEncodeUtils.test.ts +0 -202
- package/test/elements/layout/SplitItem.test.ts +0 -440
- package/test/elements/layout/SplitLayoutManager.test.ts +0 -1501
- package/test/elements/layout/SplitPanel.test.ts +0 -1109
- package/test/elements/mention-textarea/MentionTextArea.basic.test.ts +0 -114
- package/test/elements/mention-textarea/MentionTextArea.test.ts +0 -613
- package/test/elements/navigation/Navigation.test.ts +0 -120
- package/test/env.ts +0 -15
- package/test/events/EventTypes.test.ts +0 -363
- package/test/events/EventsTestHelpers.ts +0 -16
- package/test/helpers/TestUtils.ts +0 -243
- package/test/helpers/UiMock.ts +0 -185
- package/test/lib/Dom.test.ts +0 -231
- package/test/md/button/UiButton.test.ts +0 -347
- package/test/md/button/UiIconButton.test.ts +0 -155
- package/test/md/chip/UiChip.test.ts +0 -219
- package/test/md/collapse/UiCollapse.test.ts +0 -250
- package/test/md/collapse/flex-layout.test.ts +0 -105
- package/test/md/date-time/DateTime.test.ts +0 -348
- package/test/md/dialog/UiConfirmDialog.test.ts +0 -131
- package/test/md/dialog/UiDialog.test.ts +0 -759
- package/test/md/menu/Menu.test.ts +0 -855
- package/test/md/menu/MenuIntegration.test.ts +0 -426
- package/test/md/menu/MenuItem.test.ts +0 -652
- package/test/md/menu/SubMenu.test.ts +0 -410
- package/test/md/progress/UiCircularProgressElement.test.ts +0 -481
- package/test/md/progress/UiProgressElement.test.ts +0 -117
- package/test/md/progress/UiRangeElement.test.ts +0 -156
- package/test/md/select/Select.test.ts +0 -925
- package/test/plugins/takeScreenshotPlugin.js +0 -35
- package/test/setup.test.ts +0 -217
- package/test/setup.ts +0 -117
- package/test/tsconfig.json +0 -7
- package/web-dev-server.config.js +0 -21
- 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
|
-
})
|