@furystack/shades-common-components 10.0.35 → 11.0.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.
- package/CHANGELOG.md +66 -0
- package/esm/components/animations.spec.d.ts +2 -0
- package/esm/components/animations.spec.d.ts.map +1 -0
- package/esm/components/animations.spec.js +201 -0
- package/esm/components/animations.spec.js.map +1 -0
- package/esm/components/app-bar-link.js +21 -20
- package/esm/components/app-bar-link.js.map +1 -1
- package/esm/components/app-bar-link.spec.d.ts +2 -0
- package/esm/components/app-bar-link.spec.d.ts.map +1 -0
- package/esm/components/app-bar-link.spec.js +252 -0
- package/esm/components/app-bar-link.spec.js.map +1 -0
- package/esm/components/app-bar.js +21 -21
- package/esm/components/app-bar.js.map +1 -1
- package/esm/components/app-bar.spec.d.ts +2 -0
- package/esm/components/app-bar.spec.d.ts.map +1 -0
- package/esm/components/app-bar.spec.js +117 -0
- package/esm/components/app-bar.spec.js.map +1 -0
- package/esm/components/avatar.d.ts.map +1 -1
- package/esm/components/avatar.js +15 -19
- package/esm/components/avatar.js.map +1 -1
- package/esm/components/avatar.spec.d.ts +2 -0
- package/esm/components/avatar.spec.d.ts.map +1 -0
- package/esm/components/avatar.spec.js +114 -0
- package/esm/components/avatar.spec.js.map +1 -0
- package/esm/components/button.d.ts.map +1 -1
- package/esm/components/button.js +145 -156
- package/esm/components/button.js.map +1 -1
- package/esm/components/button.spec.d.ts +2 -0
- package/esm/components/button.spec.d.ts.map +1 -0
- package/esm/components/button.spec.js +155 -0
- package/esm/components/button.spec.js.map +1 -0
- package/esm/components/command-palette/command-palette-input.d.ts.map +1 -1
- package/esm/components/command-palette/command-palette-input.js +18 -16
- package/esm/components/command-palette/command-palette-input.js.map +1 -1
- package/esm/components/command-palette/command-palette-input.spec.d.ts +2 -0
- package/esm/components/command-palette/command-palette-input.spec.d.ts.map +1 -0
- package/esm/components/command-palette/command-palette-input.spec.js +233 -0
- package/esm/components/command-palette/command-palette-input.spec.js.map +1 -0
- package/esm/components/command-palette/command-palette-manager.spec.d.ts +2 -0
- package/esm/components/command-palette/command-palette-manager.spec.d.ts.map +1 -0
- package/esm/components/command-palette/command-palette-manager.spec.js +362 -0
- package/esm/components/command-palette/command-palette-manager.spec.js.map +1 -0
- package/esm/components/command-palette/command-palette-suggestion-list.d.ts.map +1 -1
- package/esm/components/command-palette/command-palette-suggestion-list.js +42 -46
- package/esm/components/command-palette/command-palette-suggestion-list.js.map +1 -1
- package/esm/components/command-palette/command-palette-suggestion-list.spec.d.ts +2 -0
- package/esm/components/command-palette/command-palette-suggestion-list.spec.d.ts.map +1 -0
- package/esm/components/command-palette/command-palette-suggestion-list.spec.js +376 -0
- package/esm/components/command-palette/command-palette-suggestion-list.spec.js.map +1 -0
- package/esm/components/command-palette/index.d.ts.map +1 -1
- package/esm/components/command-palette/index.js +100 -110
- package/esm/components/command-palette/index.js.map +1 -1
- package/esm/components/command-palette/index.spec.d.ts +2 -0
- package/esm/components/command-palette/index.spec.d.ts.map +1 -0
- package/esm/components/command-palette/index.spec.js +509 -0
- package/esm/components/command-palette/index.spec.js.map +1 -0
- package/esm/components/data-grid/body.js +1 -1
- package/esm/components/data-grid/body.js.map +1 -1
- package/esm/components/data-grid/body.spec.d.ts +2 -0
- package/esm/components/data-grid/body.spec.d.ts.map +1 -0
- package/esm/components/data-grid/body.spec.js +228 -0
- package/esm/components/data-grid/body.spec.js.map +1 -0
- package/esm/components/data-grid/data-grid-row.d.ts.map +1 -1
- package/esm/components/data-grid/data-grid-row.js +49 -73
- package/esm/components/data-grid/data-grid-row.js.map +1 -1
- package/esm/components/data-grid/data-grid-row.spec.d.ts +2 -0
- package/esm/components/data-grid/data-grid-row.spec.d.ts.map +1 -0
- package/esm/components/data-grid/data-grid-row.spec.js +296 -0
- package/esm/components/data-grid/data-grid-row.spec.js.map +1 -0
- package/esm/components/data-grid/data-grid.d.ts.map +1 -1
- package/esm/components/data-grid/data-grid.js +35 -28
- package/esm/components/data-grid/data-grid.js.map +1 -1
- package/esm/components/data-grid/data-grid.spec.d.ts +2 -0
- package/esm/components/data-grid/data-grid.spec.d.ts.map +1 -0
- package/esm/components/data-grid/data-grid.spec.js +544 -0
- package/esm/components/data-grid/data-grid.spec.js.map +1 -0
- package/esm/components/data-grid/footer.js +21 -15
- package/esm/components/data-grid/footer.js.map +1 -1
- package/esm/components/data-grid/footer.spec.d.ts +2 -0
- package/esm/components/data-grid/footer.spec.d.ts.map +1 -0
- package/esm/components/data-grid/footer.spec.js +264 -0
- package/esm/components/data-grid/footer.spec.js.map +1 -0
- package/esm/components/data-grid/header.d.ts.map +1 -1
- package/esm/components/data-grid/header.js +55 -33
- package/esm/components/data-grid/header.js.map +1 -1
- package/esm/components/data-grid/header.spec.d.ts +2 -0
- package/esm/components/data-grid/header.spec.d.ts.map +1 -0
- package/esm/components/data-grid/header.spec.js +421 -0
- package/esm/components/data-grid/header.spec.js.map +1 -0
- package/esm/components/data-grid/selection-cell.d.ts.map +1 -1
- package/esm/components/data-grid/selection-cell.js +13 -6
- package/esm/components/data-grid/selection-cell.js.map +1 -1
- package/esm/components/data-grid/selection-cell.spec.d.ts +2 -0
- package/esm/components/data-grid/selection-cell.spec.d.ts.map +1 -0
- package/esm/components/data-grid/selection-cell.spec.js +118 -0
- package/esm/components/data-grid/selection-cell.spec.js.map +1 -0
- package/esm/components/fab.d.ts.map +1 -1
- package/esm/components/fab.js +10 -1
- package/esm/components/fab.js.map +1 -1
- package/esm/components/fab.spec.d.ts +2 -0
- package/esm/components/fab.spec.d.ts.map +1 -0
- package/esm/components/fab.spec.js +95 -0
- package/esm/components/fab.spec.js.map +1 -0
- package/esm/components/form.spec.d.ts +2 -0
- package/esm/components/form.spec.d.ts.map +1 -0
- package/esm/components/form.spec.js +314 -0
- package/esm/components/form.spec.js.map +1 -0
- package/esm/components/grid.d.ts.map +1 -1
- package/esm/components/grid.js +40 -37
- package/esm/components/grid.js.map +1 -1
- package/esm/components/grid.spec.d.ts +2 -0
- package/esm/components/grid.spec.d.ts.map +1 -0
- package/esm/components/grid.spec.js +316 -0
- package/esm/components/grid.spec.js.map +1 -0
- package/esm/components/inputs/autocomplete.spec.d.ts +2 -0
- package/esm/components/inputs/autocomplete.spec.d.ts.map +1 -0
- package/esm/components/inputs/autocomplete.spec.js +194 -0
- package/esm/components/inputs/autocomplete.spec.js.map +1 -0
- package/esm/components/inputs/input.d.ts.map +1 -1
- package/esm/components/inputs/input.js +141 -109
- package/esm/components/inputs/input.js.map +1 -1
- package/esm/components/inputs/input.spec.d.ts +2 -0
- package/esm/components/inputs/input.spec.d.ts.map +1 -0
- package/esm/components/inputs/input.spec.js +577 -0
- package/esm/components/inputs/input.spec.js.map +1 -0
- package/esm/components/inputs/text-area.d.ts.map +1 -1
- package/esm/components/inputs/text-area.js +54 -58
- package/esm/components/inputs/text-area.js.map +1 -1
- package/esm/components/inputs/text-area.spec.d.ts +2 -0
- package/esm/components/inputs/text-area.spec.d.ts.map +1 -0
- package/esm/components/inputs/text-area.spec.js +214 -0
- package/esm/components/inputs/text-area.spec.js.map +1 -0
- package/esm/components/loader.js +1 -1
- package/esm/components/loader.js.map +1 -1
- package/esm/components/loader.spec.d.ts +2 -0
- package/esm/components/loader.spec.d.ts.map +1 -0
- package/esm/components/loader.spec.js +251 -0
- package/esm/components/loader.spec.js.map +1 -0
- package/esm/components/modal.d.ts.map +1 -1
- package/esm/components/modal.js +11 -9
- package/esm/components/modal.js.map +1 -1
- package/esm/components/modal.spec.d.ts +2 -0
- package/esm/components/modal.spec.d.ts.map +1 -0
- package/esm/components/modal.spec.js +227 -0
- package/esm/components/modal.spec.js.map +1 -0
- package/esm/components/noty-list.d.ts.map +1 -1
- package/esm/components/noty-list.js +39 -40
- package/esm/components/noty-list.js.map +1 -1
- package/esm/components/noty-list.spec.d.ts +2 -0
- package/esm/components/noty-list.spec.d.ts.map +1 -0
- package/esm/components/noty-list.spec.js +486 -0
- package/esm/components/noty-list.spec.js.map +1 -0
- package/esm/components/paper.d.ts.map +1 -1
- package/esm/components/paper.js +15 -12
- package/esm/components/paper.js.map +1 -1
- package/esm/components/paper.spec.d.ts +2 -0
- package/esm/components/paper.spec.d.ts.map +1 -0
- package/esm/components/paper.spec.js +63 -0
- package/esm/components/paper.spec.js.map +1 -0
- package/esm/components/skeleton.js +1 -1
- package/esm/components/skeleton.js.map +1 -1
- package/esm/components/skeleton.spec.d.ts +2 -0
- package/esm/components/skeleton.spec.d.ts.map +1 -0
- package/esm/components/skeleton.spec.js +159 -0
- package/esm/components/skeleton.spec.js.map +1 -0
- package/esm/components/styles.spec.d.ts +2 -0
- package/esm/components/styles.spec.d.ts.map +1 -0
- package/esm/components/styles.spec.js +56 -0
- package/esm/components/styles.spec.js.map +1 -0
- package/esm/components/suggest/index.d.ts.map +1 -1
- package/esm/components/suggest/index.js +74 -83
- package/esm/components/suggest/index.js.map +1 -1
- package/esm/components/suggest/index.spec.d.ts +2 -0
- package/esm/components/suggest/index.spec.d.ts.map +1 -0
- package/esm/components/suggest/index.spec.js +515 -0
- package/esm/components/suggest/index.spec.js.map +1 -0
- package/esm/components/suggest/suggest-input.d.ts.map +1 -1
- package/esm/components/suggest/suggest-input.js +16 -17
- package/esm/components/suggest/suggest-input.js.map +1 -1
- package/esm/components/suggest/suggest-input.spec.d.ts +2 -0
- package/esm/components/suggest/suggest-input.spec.d.ts.map +1 -0
- package/esm/components/suggest/suggest-input.spec.js +138 -0
- package/esm/components/suggest/suggest-input.spec.js.map +1 -0
- package/esm/components/suggest/suggest-manager.spec.d.ts +2 -0
- package/esm/components/suggest/suggest-manager.spec.d.ts.map +1 -0
- package/esm/components/suggest/suggest-manager.spec.js +308 -0
- package/esm/components/suggest/suggest-manager.spec.js.map +1 -0
- package/esm/components/suggest/suggestion-list.d.ts.map +1 -1
- package/esm/components/suggest/suggestion-list.js +43 -48
- package/esm/components/suggest/suggestion-list.js.map +1 -1
- package/esm/components/suggest/suggestion-list.spec.d.ts +2 -0
- package/esm/components/suggest/suggestion-list.spec.d.ts.map +1 -0
- package/esm/components/suggest/suggestion-list.spec.js +252 -0
- package/esm/components/suggest/suggestion-list.spec.js.map +1 -0
- package/esm/components/tabs.d.ts.map +1 -1
- package/esm/components/tabs.js +32 -18
- package/esm/components/tabs.js.map +1 -1
- package/esm/components/tabs.spec.d.ts +2 -0
- package/esm/components/tabs.spec.d.ts.map +1 -0
- package/esm/components/tabs.spec.js +187 -0
- package/esm/components/tabs.spec.js.map +1 -0
- package/esm/components/wizard/index.d.ts.map +1 -1
- package/esm/components/wizard/index.js +10 -7
- package/esm/components/wizard/index.js.map +1 -1
- package/esm/components/wizard/index.spec.d.ts +2 -0
- package/esm/components/wizard/index.spec.d.ts.map +1 -0
- package/esm/components/wizard/index.spec.js +171 -0
- package/esm/components/wizard/index.spec.js.map +1 -0
- package/esm/services/collection-service.spec.js +391 -2
- package/esm/services/collection-service.spec.js.map +1 -1
- package/esm/services/css-variable-theme.d.ts.map +1 -1
- package/esm/services/css-variable-theme.js +21 -1
- package/esm/services/css-variable-theme.js.map +1 -1
- package/esm/services/css-variable-theme.spec.d.ts +2 -0
- package/esm/services/css-variable-theme.spec.d.ts.map +1 -0
- package/esm/services/css-variable-theme.spec.js +169 -0
- package/esm/services/css-variable-theme.spec.js.map +1 -0
- package/esm/services/default-palette.d.ts +4 -0
- package/esm/services/default-palette.d.ts.map +1 -1
- package/esm/services/default-palette.js +22 -0
- package/esm/services/default-palette.js.map +1 -1
- package/esm/services/theme-provider-service.d.ts +59 -1
- package/esm/services/theme-provider-service.d.ts.map +1 -1
- package/esm/services/theme-provider-service.js.map +1 -1
- package/esm/services/theme-provider-service.spec.d.ts +2 -0
- package/esm/services/theme-provider-service.spec.d.ts.map +1 -0
- package/esm/services/theme-provider-service.spec.js +166 -0
- package/esm/services/theme-provider-service.spec.js.map +1 -0
- package/package.json +2 -2
- package/src/components/animations.spec.ts +299 -0
- package/src/components/app-bar-link.spec.tsx +341 -0
- package/src/components/app-bar-link.tsx +21 -21
- package/src/components/app-bar.spec.tsx +142 -0
- package/src/components/app-bar.tsx +22 -22
- package/src/components/avatar.spec.tsx +146 -0
- package/src/components/avatar.tsx +17 -20
- package/src/components/button.spec.tsx +193 -0
- package/src/components/button.tsx +162 -197
- package/src/components/command-palette/command-palette-input.spec.tsx +320 -0
- package/src/components/command-palette/command-palette-input.tsx +19 -22
- package/src/components/command-palette/command-palette-manager.spec.ts +470 -0
- package/src/components/command-palette/command-palette-suggestion-list.spec.tsx +499 -0
- package/src/components/command-palette/command-palette-suggestion-list.tsx +42 -46
- package/src/components/command-palette/index.spec.tsx +684 -0
- package/src/components/command-palette/index.tsx +107 -136
- package/src/components/data-grid/body.spec.tsx +340 -0
- package/src/components/data-grid/body.tsx +1 -1
- package/src/components/data-grid/data-grid-row.spec.tsx +382 -0
- package/src/components/data-grid/data-grid-row.tsx +50 -82
- package/src/components/data-grid/data-grid.spec.tsx +939 -0
- package/src/components/data-grid/data-grid.tsx +38 -35
- package/src/components/data-grid/footer.spec.tsx +344 -0
- package/src/components/data-grid/footer.tsx +19 -19
- package/src/components/data-grid/header.spec.tsx +563 -0
- package/src/components/data-grid/header.tsx +53 -44
- package/src/components/data-grid/selection-cell.spec.tsx +150 -0
- package/src/components/data-grid/selection-cell.tsx +12 -6
- package/src/components/fab.spec.tsx +108 -0
- package/src/components/fab.tsx +10 -1
- package/src/components/form.spec.tsx +481 -0
- package/src/components/grid.spec.tsx +334 -0
- package/src/components/grid.tsx +57 -63
- package/src/components/inputs/autocomplete.spec.tsx +258 -0
- package/src/components/inputs/input.spec.tsx +808 -0
- package/src/components/inputs/input.tsx +153 -139
- package/src/components/inputs/text-area.spec.tsx +285 -0
- package/src/components/inputs/text-area.tsx +53 -79
- package/src/components/loader.spec.tsx +346 -0
- package/src/components/loader.tsx +1 -1
- package/src/components/modal.spec.tsx +304 -0
- package/src/components/modal.tsx +11 -9
- package/src/components/noty-list.spec.tsx +631 -0
- package/src/components/noty-list.tsx +39 -50
- package/src/components/paper.spec.tsx +72 -0
- package/src/components/paper.tsx +15 -13
- package/src/components/skeleton.spec.tsx +219 -0
- package/src/components/skeleton.tsx +1 -1
- package/src/components/styles.spec.ts +70 -0
- package/src/components/suggest/index.spec.tsx +861 -0
- package/src/components/suggest/index.tsx +74 -101
- package/src/components/suggest/suggest-input.spec.tsx +181 -0
- package/src/components/suggest/suggest-input.tsx +16 -24
- package/src/components/suggest/suggest-manager.spec.ts +409 -0
- package/src/components/suggest/suggestion-list.spec.tsx +334 -0
- package/src/components/suggest/suggestion-list.tsx +43 -48
- package/src/components/tabs.spec.tsx +236 -0
- package/src/components/tabs.tsx +33 -21
- package/src/components/wizard/index.spec.tsx +224 -0
- package/src/components/wizard/index.tsx +10 -9
- package/src/services/collection-service.spec.ts +492 -3
- package/src/services/css-variable-theme.spec.ts +204 -0
- package/src/services/css-variable-theme.ts +21 -1
- package/src/services/default-palette.ts +22 -0
- package/src/services/theme-provider-service.spec.ts +195 -0
- package/src/services/theme-provider-service.ts +60 -2
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { Injector } from '@furystack/inject'
|
|
2
|
+
import { createComponent, initializeShadeRoot } from '@furystack/shades'
|
|
3
|
+
import { using } from '@furystack/utils'
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
5
|
+
import { SuggestManager } from './suggest-manager.js'
|
|
6
|
+
import { SuggestionList } from './suggestion-list.js'
|
|
7
|
+
import type { SuggestionResult } from './suggestion-result.js'
|
|
8
|
+
|
|
9
|
+
type TestEntry = { id: number; name: string }
|
|
10
|
+
|
|
11
|
+
const createTestEntries = (): TestEntry[] => [
|
|
12
|
+
{ id: 1, name: 'alpha' },
|
|
13
|
+
{ id: 2, name: 'beta' },
|
|
14
|
+
{ id: 3, name: 'gamma' },
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
const createSuggestionResult = (entry: TestEntry): SuggestionResult => ({
|
|
18
|
+
element: (<span>{entry.name}</span>) as unknown as JSX.Element,
|
|
19
|
+
score: entry.id,
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
describe('SuggestionList', () => {
|
|
23
|
+
let originalAnimate: typeof Element.prototype.animate
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
document.body.innerHTML = '<div id="root"></div>'
|
|
27
|
+
vi.useFakeTimers()
|
|
28
|
+
originalAnimate = Element.prototype.animate
|
|
29
|
+
|
|
30
|
+
Element.prototype.animate = vi.fn(() => {
|
|
31
|
+
const mockAnimation = {
|
|
32
|
+
onfinish: null as ((event: AnimationPlaybackEvent) => void) | null,
|
|
33
|
+
oncancel: null as ((event: AnimationPlaybackEvent) => void) | null,
|
|
34
|
+
cancel: vi.fn(),
|
|
35
|
+
play: vi.fn(),
|
|
36
|
+
pause: vi.fn(),
|
|
37
|
+
finish: vi.fn(),
|
|
38
|
+
addEventListener: vi.fn(),
|
|
39
|
+
removeEventListener: vi.fn(),
|
|
40
|
+
}
|
|
41
|
+
return mockAnimation as unknown as Animation
|
|
42
|
+
}) as typeof Element.prototype.animate
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
document.body.innerHTML = ''
|
|
47
|
+
Element.prototype.animate = originalAnimate
|
|
48
|
+
vi.useRealTimers()
|
|
49
|
+
vi.restoreAllMocks()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
const createManager = () => {
|
|
53
|
+
const getEntries = vi.fn().mockResolvedValue(createTestEntries())
|
|
54
|
+
const getSuggestionEntry = vi.fn().mockImplementation(createSuggestionResult)
|
|
55
|
+
return new SuggestManager<TestEntry>(getEntries, getSuggestionEntry)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
it('should render with shadow DOM', async () => {
|
|
59
|
+
const injector = new Injector()
|
|
60
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
61
|
+
|
|
62
|
+
using(createManager(), (manager) => {
|
|
63
|
+
initializeShadeRoot({
|
|
64
|
+
injector,
|
|
65
|
+
rootElement,
|
|
66
|
+
jsxElement: <SuggestionList manager={manager} />,
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
await vi.advanceTimersByTimeAsync(50)
|
|
71
|
+
|
|
72
|
+
const suggestionList = document.querySelector('shade-suggest-suggestion-list')
|
|
73
|
+
expect(suggestionList).not.toBeNull()
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('should render the suggestions container', async () => {
|
|
77
|
+
const injector = new Injector()
|
|
78
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
79
|
+
|
|
80
|
+
using(createManager(), (manager) => {
|
|
81
|
+
initializeShadeRoot({
|
|
82
|
+
injector,
|
|
83
|
+
rootElement,
|
|
84
|
+
jsxElement: <SuggestionList manager={manager} />,
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
await vi.advanceTimersByTimeAsync(50)
|
|
89
|
+
|
|
90
|
+
const container = document.querySelector('.suggestion-items-container')
|
|
91
|
+
expect(container).not.toBeNull()
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('should render suggestion items when suggestions are present', async () => {
|
|
95
|
+
const injector = new Injector()
|
|
96
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
97
|
+
const manager = createManager()
|
|
98
|
+
|
|
99
|
+
initializeShadeRoot({
|
|
100
|
+
injector,
|
|
101
|
+
rootElement,
|
|
102
|
+
jsxElement: <SuggestionList manager={manager} />,
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
await vi.advanceTimersByTimeAsync(50)
|
|
106
|
+
|
|
107
|
+
void manager.getSuggestion({ injector, term: 'test' })
|
|
108
|
+
await vi.advanceTimersByTimeAsync(250)
|
|
109
|
+
await vi.advanceTimersByTimeAsync(50)
|
|
110
|
+
|
|
111
|
+
const suggestionItems = document.querySelectorAll('.suggestion-item')
|
|
112
|
+
expect(suggestionItems.length).toBe(3)
|
|
113
|
+
|
|
114
|
+
expect(suggestionItems[0].textContent).toContain('alpha')
|
|
115
|
+
expect(suggestionItems[1].textContent).toContain('beta')
|
|
116
|
+
expect(suggestionItems[2].textContent).toContain('gamma')
|
|
117
|
+
|
|
118
|
+
manager[Symbol.dispose]()
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('should apply selected class to the correct suggestion item', async () => {
|
|
122
|
+
const injector = new Injector()
|
|
123
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
124
|
+
const manager = createManager()
|
|
125
|
+
|
|
126
|
+
initializeShadeRoot({
|
|
127
|
+
injector,
|
|
128
|
+
rootElement,
|
|
129
|
+
jsxElement: <SuggestionList manager={manager} />,
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
await vi.advanceTimersByTimeAsync(50)
|
|
133
|
+
|
|
134
|
+
void manager.getSuggestion({ injector, term: 'test' })
|
|
135
|
+
await vi.advanceTimersByTimeAsync(250)
|
|
136
|
+
await vi.advanceTimersByTimeAsync(50)
|
|
137
|
+
|
|
138
|
+
const suggestionItems = document.querySelectorAll('.suggestion-item')
|
|
139
|
+
expect(suggestionItems[0].classList.contains('selected')).toBe(true)
|
|
140
|
+
expect(suggestionItems[1].classList.contains('selected')).toBe(false)
|
|
141
|
+
expect(suggestionItems[2].classList.contains('selected')).toBe(false)
|
|
142
|
+
|
|
143
|
+
manager[Symbol.dispose]()
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('should update selected class when selectedIndex changes', async () => {
|
|
147
|
+
const injector = new Injector()
|
|
148
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
149
|
+
const manager = createManager()
|
|
150
|
+
|
|
151
|
+
initializeShadeRoot({
|
|
152
|
+
injector,
|
|
153
|
+
rootElement,
|
|
154
|
+
jsxElement: <SuggestionList manager={manager} />,
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
await vi.advanceTimersByTimeAsync(50)
|
|
158
|
+
|
|
159
|
+
void manager.getSuggestion({ injector, term: 'test' })
|
|
160
|
+
await vi.advanceTimersByTimeAsync(250)
|
|
161
|
+
await vi.advanceTimersByTimeAsync(50)
|
|
162
|
+
|
|
163
|
+
manager.selectedIndex.setValue(1)
|
|
164
|
+
await vi.advanceTimersByTimeAsync(50)
|
|
165
|
+
|
|
166
|
+
const suggestionItems = document.querySelectorAll('.suggestion-item')
|
|
167
|
+
expect(suggestionItems[0].classList.contains('selected')).toBe(false)
|
|
168
|
+
expect(suggestionItems[1].classList.contains('selected')).toBe(true)
|
|
169
|
+
expect(suggestionItems[2].classList.contains('selected')).toBe(false)
|
|
170
|
+
|
|
171
|
+
manager.selectedIndex.setValue(2)
|
|
172
|
+
await vi.advanceTimersByTimeAsync(50)
|
|
173
|
+
|
|
174
|
+
expect(suggestionItems[0].classList.contains('selected')).toBe(false)
|
|
175
|
+
expect(suggestionItems[1].classList.contains('selected')).toBe(false)
|
|
176
|
+
expect(suggestionItems[2].classList.contains('selected')).toBe(true)
|
|
177
|
+
|
|
178
|
+
manager[Symbol.dispose]()
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
it('should call selectSuggestion when a suggestion item is clicked', async () => {
|
|
182
|
+
const injector = new Injector()
|
|
183
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
184
|
+
const manager = createManager()
|
|
185
|
+
const selectSpy = vi.spyOn(manager, 'selectSuggestion')
|
|
186
|
+
|
|
187
|
+
initializeShadeRoot({
|
|
188
|
+
injector,
|
|
189
|
+
rootElement,
|
|
190
|
+
jsxElement: <SuggestionList manager={manager} />,
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
await vi.advanceTimersByTimeAsync(50)
|
|
194
|
+
|
|
195
|
+
void manager.getSuggestion({ injector, term: 'test' })
|
|
196
|
+
await vi.advanceTimersByTimeAsync(250)
|
|
197
|
+
await vi.advanceTimersByTimeAsync(50)
|
|
198
|
+
|
|
199
|
+
manager.isOpened.setValue(true)
|
|
200
|
+
await vi.advanceTimersByTimeAsync(50)
|
|
201
|
+
|
|
202
|
+
const suggestionItems = document.querySelectorAll('.suggestion-item')
|
|
203
|
+
;(suggestionItems[1] as HTMLElement).click()
|
|
204
|
+
|
|
205
|
+
expect(selectSpy).toHaveBeenCalledWith(1)
|
|
206
|
+
|
|
207
|
+
manager[Symbol.dispose]()
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
it('should not call selectSuggestion when list is not opened', async () => {
|
|
211
|
+
const injector = new Injector()
|
|
212
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
213
|
+
const manager = createManager()
|
|
214
|
+
|
|
215
|
+
initializeShadeRoot({
|
|
216
|
+
injector,
|
|
217
|
+
rootElement,
|
|
218
|
+
jsxElement: <SuggestionList manager={manager} />,
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
await vi.advanceTimersByTimeAsync(50)
|
|
222
|
+
|
|
223
|
+
manager.currentSuggestions.setValue([
|
|
224
|
+
{ entry: { id: 1, name: 'alpha' }, suggestion: createSuggestionResult({ id: 1, name: 'alpha' }) },
|
|
225
|
+
{ entry: { id: 2, name: 'beta' }, suggestion: createSuggestionResult({ id: 2, name: 'beta' }) },
|
|
226
|
+
])
|
|
227
|
+
await vi.advanceTimersByTimeAsync(50)
|
|
228
|
+
|
|
229
|
+
const selectSpy = vi.spyOn(manager, 'selectSuggestion')
|
|
230
|
+
|
|
231
|
+
const suggestionItems = document.querySelectorAll('.suggestion-item')
|
|
232
|
+
;(suggestionItems[1] as HTMLElement).click()
|
|
233
|
+
|
|
234
|
+
expect(selectSpy).not.toHaveBeenCalled()
|
|
235
|
+
|
|
236
|
+
manager[Symbol.dispose]()
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
it('should render empty container when no suggestions', async () => {
|
|
240
|
+
const injector = new Injector()
|
|
241
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
242
|
+
|
|
243
|
+
const getEntries = vi.fn().mockResolvedValue([])
|
|
244
|
+
const getSuggestionEntry = vi.fn().mockImplementation(createSuggestionResult)
|
|
245
|
+
const manager = new SuggestManager<TestEntry>(getEntries, getSuggestionEntry)
|
|
246
|
+
|
|
247
|
+
initializeShadeRoot({
|
|
248
|
+
injector,
|
|
249
|
+
rootElement,
|
|
250
|
+
jsxElement: <SuggestionList manager={manager} />,
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
await vi.advanceTimersByTimeAsync(50)
|
|
254
|
+
|
|
255
|
+
const suggestionItems = document.querySelectorAll('.suggestion-item')
|
|
256
|
+
expect(suggestionItems.length).toBe(0)
|
|
257
|
+
|
|
258
|
+
manager[Symbol.dispose]()
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
describe('animations', () => {
|
|
262
|
+
it('should animate container when isOpened changes to true', async () => {
|
|
263
|
+
const injector = new Injector()
|
|
264
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
265
|
+
const manager = createManager()
|
|
266
|
+
|
|
267
|
+
initializeShadeRoot({
|
|
268
|
+
injector,
|
|
269
|
+
rootElement,
|
|
270
|
+
jsxElement: <SuggestionList manager={manager} />,
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
await vi.advanceTimersByTimeAsync(50)
|
|
274
|
+
|
|
275
|
+
const container = document.querySelector('.suggestion-items-container') as HTMLDivElement
|
|
276
|
+
|
|
277
|
+
manager.isOpened.setValue(true)
|
|
278
|
+
await vi.advanceTimersByTimeAsync(50)
|
|
279
|
+
|
|
280
|
+
expect(container.style.zIndex).toBe('1')
|
|
281
|
+
|
|
282
|
+
manager[Symbol.dispose]()
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
it('should animate container when isOpened changes to false', async () => {
|
|
286
|
+
const injector = new Injector()
|
|
287
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
288
|
+
const manager = createManager()
|
|
289
|
+
|
|
290
|
+
initializeShadeRoot({
|
|
291
|
+
injector,
|
|
292
|
+
rootElement,
|
|
293
|
+
jsxElement: <SuggestionList manager={manager} />,
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
await vi.advanceTimersByTimeAsync(50)
|
|
297
|
+
|
|
298
|
+
manager.isOpened.setValue(true)
|
|
299
|
+
await vi.advanceTimersByTimeAsync(50)
|
|
300
|
+
|
|
301
|
+
const container = document.querySelector('.suggestion-items-container') as HTMLDivElement
|
|
302
|
+
|
|
303
|
+
manager.isOpened.setValue(false)
|
|
304
|
+
await vi.advanceTimersByTimeAsync(50)
|
|
305
|
+
|
|
306
|
+
expect(container.style.zIndex).toBe('-1')
|
|
307
|
+
|
|
308
|
+
manager[Symbol.dispose]()
|
|
309
|
+
})
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
describe('container width', () => {
|
|
313
|
+
it('should set container width based on parent element', async () => {
|
|
314
|
+
const injector = new Injector()
|
|
315
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
316
|
+
rootElement.style.width = '400px'
|
|
317
|
+
|
|
318
|
+
const manager = createManager()
|
|
319
|
+
|
|
320
|
+
initializeShadeRoot({
|
|
321
|
+
injector,
|
|
322
|
+
rootElement,
|
|
323
|
+
jsxElement: <SuggestionList manager={manager} />,
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
await vi.advanceTimersByTimeAsync(50)
|
|
327
|
+
|
|
328
|
+
const container = document.querySelector('.suggestion-items-container') as HTMLDivElement
|
|
329
|
+
expect(container.style.width).toBeDefined()
|
|
330
|
+
|
|
331
|
+
manager[Symbol.dispose]()
|
|
332
|
+
})
|
|
333
|
+
})
|
|
334
|
+
})
|
|
@@ -1,31 +1,59 @@
|
|
|
1
1
|
import type { ChildrenList } from '@furystack/shades'
|
|
2
2
|
import { Shade, createComponent } from '@furystack/shades'
|
|
3
|
-
import {
|
|
3
|
+
import { cssVariableTheme } from '../../services/css-variable-theme.js'
|
|
4
4
|
import { promisifyAnimation } from '../../utils/promisify-animation.js'
|
|
5
5
|
import type { SuggestManager } from './suggest-manager.js'
|
|
6
6
|
|
|
7
7
|
export const SuggestionList: <T>(props: { manager: SuggestManager<T> }, children: ChildrenList) => JSX.Element<any> =
|
|
8
8
|
Shade<{ manager: SuggestManager<any> }>({
|
|
9
9
|
shadowDomName: 'shade-suggest-suggestion-list',
|
|
10
|
-
|
|
10
|
+
css: {
|
|
11
|
+
'& .suggestion-items-container': {
|
|
12
|
+
borderTop: 'none',
|
|
13
|
+
position: 'absolute',
|
|
14
|
+
borderRadius: '0px 0px 12px 12px',
|
|
15
|
+
marginLeft: '14px',
|
|
16
|
+
marginTop: '4px',
|
|
17
|
+
overflow: 'hidden',
|
|
18
|
+
zIndex: '1',
|
|
19
|
+
left: 'auto',
|
|
20
|
+
backgroundColor: cssVariableTheme.background.paper,
|
|
21
|
+
color: cssVariableTheme.text.secondary,
|
|
22
|
+
boxShadow: '0 8px 24px rgba(0,0,0,0.18), 0 2px 8px rgba(0,0,0,0.12)',
|
|
23
|
+
backdropFilter: 'blur(20px)',
|
|
24
|
+
border: '1px solid rgba(128,128,128,0.2)',
|
|
25
|
+
},
|
|
26
|
+
'& .suggestion-item': {
|
|
27
|
+
padding: '0.875em 1.25em',
|
|
28
|
+
cursor: 'pointer',
|
|
29
|
+
background: 'transparent',
|
|
30
|
+
fontWeight: '400',
|
|
31
|
+
borderLeft: '3px solid transparent',
|
|
32
|
+
transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
|
|
33
|
+
fontSize: '0.95em',
|
|
34
|
+
letterSpacing: '0.01em',
|
|
35
|
+
},
|
|
36
|
+
'& .suggestion-item:hover': {
|
|
37
|
+
background: 'rgba(128,128,128,0.1)',
|
|
38
|
+
},
|
|
39
|
+
'& .suggestion-item.selected': {
|
|
40
|
+
background: 'rgba(128,128,128,0.2)',
|
|
41
|
+
fontWeight: '500',
|
|
42
|
+
borderLeft: `3px solid ${cssVariableTheme.text.primary}`,
|
|
43
|
+
},
|
|
44
|
+
'& .suggestion-item.selected:hover': {
|
|
45
|
+
background: 'rgba(128,128,128,0.2)',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
render: ({ element, props, useObservable }) => {
|
|
11
49
|
const { manager } = props
|
|
12
|
-
const { theme } = injector.getInstance(ThemeProviderService)
|
|
13
50
|
|
|
14
51
|
const [suggestions] = useObservable('suggestions', manager.currentSuggestions)
|
|
15
52
|
|
|
16
|
-
// todo: GetLast is eliminated, do we need it?
|
|
17
53
|
const [selectedIndex] = useObservable('selectedIndex', manager.selectedIndex, {
|
|
18
54
|
onChange: (idx) => {
|
|
19
|
-
;([...element.querySelectorAll('.suggestion-item')] as HTMLDivElement[]).
|
|
20
|
-
|
|
21
|
-
s.style.background = 'rgba(128,128,128,0.2)'
|
|
22
|
-
s.style.fontWeight = '500'
|
|
23
|
-
s.style.borderLeft = `3px solid ${theme.text.primary}`
|
|
24
|
-
} else {
|
|
25
|
-
s.style.background = 'transparent'
|
|
26
|
-
s.style.fontWeight = '400'
|
|
27
|
-
s.style.borderLeft = '3px solid transparent'
|
|
28
|
-
}
|
|
55
|
+
;([...element.querySelectorAll('.suggestion-item')] as HTMLDivElement[]).forEach((s, i) => {
|
|
56
|
+
s.classList.toggle('selected', i === idx)
|
|
29
57
|
})
|
|
30
58
|
},
|
|
31
59
|
})
|
|
@@ -64,50 +92,17 @@ export const SuggestionList: <T>(props: { manager: SuggestManager<T> }, children
|
|
|
64
92
|
<div
|
|
65
93
|
className="suggestion-items-container"
|
|
66
94
|
style={{
|
|
67
|
-
borderTop: 'none',
|
|
68
|
-
position: 'absolute',
|
|
69
|
-
borderRadius: '0px 0px 12px 12px',
|
|
70
|
-
marginLeft: '14px',
|
|
71
|
-
marginTop: '4px',
|
|
72
|
-
overflow: 'hidden',
|
|
73
|
-
zIndex: '1',
|
|
74
|
-
left: 'auto',
|
|
75
|
-
backgroundColor: theme.background.paper,
|
|
76
|
-
color: theme.text.secondary,
|
|
77
|
-
boxShadow: '0 8px 24px rgba(0,0,0,0.18), 0 2px 8px rgba(0,0,0,0.12)',
|
|
78
|
-
backdropFilter: 'blur(20px)',
|
|
79
|
-
border: '1px solid rgba(128,128,128,0.2)',
|
|
80
95
|
width: `calc(${Math.round(element.parentElement?.getBoundingClientRect().width || 200)}px - 3em)`,
|
|
81
96
|
}}
|
|
82
97
|
>
|
|
83
98
|
{suggestions.map((s, i) => (
|
|
84
99
|
<div
|
|
85
|
-
className=
|
|
100
|
+
className={`suggestion-item${i === selectedIndex ? ' selected' : ''}`}
|
|
86
101
|
onclick={() => {
|
|
87
102
|
if (isListOpened) {
|
|
88
103
|
manager.selectSuggestion(i)
|
|
89
104
|
}
|
|
90
105
|
}}
|
|
91
|
-
onmouseenter={(ev) => {
|
|
92
|
-
if (i !== selectedIndex) {
|
|
93
|
-
;(ev.target as HTMLElement).style.background = 'rgba(128,128,128,0.1)'
|
|
94
|
-
}
|
|
95
|
-
}}
|
|
96
|
-
onmouseleave={(ev) => {
|
|
97
|
-
if (i !== selectedIndex) {
|
|
98
|
-
;(ev.target as HTMLElement).style.background = 'transparent'
|
|
99
|
-
}
|
|
100
|
-
}}
|
|
101
|
-
style={{
|
|
102
|
-
padding: '0.875em 1.25em',
|
|
103
|
-
cursor: 'pointer',
|
|
104
|
-
background: i === selectedIndex ? 'rgba(128,128,128,0.2)' : 'transparent',
|
|
105
|
-
fontWeight: i === selectedIndex ? '500' : '400',
|
|
106
|
-
borderLeft: i === selectedIndex ? `3px solid ${theme.text.primary}` : '3px solid transparent',
|
|
107
|
-
transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
|
|
108
|
-
fontSize: '0.95em',
|
|
109
|
-
letterSpacing: '0.01em',
|
|
110
|
-
}}
|
|
111
106
|
>
|
|
112
107
|
{s.suggestion.element}
|
|
113
108
|
</div>
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { Injector } from '@furystack/inject'
|
|
2
|
+
import { createComponent, initializeShadeRoot, LocationService } from '@furystack/shades'
|
|
3
|
+
import { sleepAsync, usingAsync } from '@furystack/utils'
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
5
|
+
import { Tabs, type Tab } from './tabs.js'
|
|
6
|
+
|
|
7
|
+
describe('Tabs', () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
document.body.innerHTML = '<div id="root"></div>'
|
|
10
|
+
window.location.hash = ''
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
document.body.innerHTML = ''
|
|
15
|
+
window.location.hash = ''
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const createTabs = (): Tab[] => [
|
|
19
|
+
{
|
|
20
|
+
hash: 'tab1',
|
|
21
|
+
header: <span>Tab 1</span>,
|
|
22
|
+
component: <div id="content-1">Content 1</div>,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
hash: 'tab2',
|
|
26
|
+
header: <span>Tab 2</span>,
|
|
27
|
+
component: <div id="content-2">Content 2</div>,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
hash: 'tab3',
|
|
31
|
+
header: <span>Tab 3</span>,
|
|
32
|
+
component: <div id="content-3">Content 3</div>,
|
|
33
|
+
},
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
it('should render all tab headers', async () => {
|
|
37
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
38
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
39
|
+
const tabs = createTabs()
|
|
40
|
+
|
|
41
|
+
initializeShadeRoot({
|
|
42
|
+
injector,
|
|
43
|
+
rootElement,
|
|
44
|
+
jsxElement: <Tabs tabs={tabs} />,
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
await sleepAsync(100)
|
|
48
|
+
|
|
49
|
+
expect(document.body.innerHTML).toContain('Tab 1')
|
|
50
|
+
expect(document.body.innerHTML).toContain('Tab 2')
|
|
51
|
+
expect(document.body.innerHTML).toContain('Tab 3')
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('should display the active tab content based on URL hash', async () => {
|
|
56
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
57
|
+
window.location.hash = '#tab2'
|
|
58
|
+
|
|
59
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
60
|
+
const tabs = createTabs()
|
|
61
|
+
|
|
62
|
+
initializeShadeRoot({
|
|
63
|
+
injector,
|
|
64
|
+
rootElement,
|
|
65
|
+
jsxElement: <Tabs tabs={tabs} />,
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
await sleepAsync(100)
|
|
69
|
+
|
|
70
|
+
expect(document.getElementById('content-2')).toBeTruthy()
|
|
71
|
+
expect(document.getElementById('content-1')).toBeFalsy()
|
|
72
|
+
expect(document.getElementById('content-3')).toBeFalsy()
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('should not display any tab content when hash does not match', async () => {
|
|
77
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
78
|
+
window.location.hash = '#nonexistent'
|
|
79
|
+
|
|
80
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
81
|
+
const tabs = createTabs()
|
|
82
|
+
|
|
83
|
+
initializeShadeRoot({
|
|
84
|
+
injector,
|
|
85
|
+
rootElement,
|
|
86
|
+
jsxElement: <Tabs tabs={tabs} />,
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
await sleepAsync(100)
|
|
90
|
+
|
|
91
|
+
expect(document.getElementById('content-1')).toBeFalsy()
|
|
92
|
+
expect(document.getElementById('content-2')).toBeFalsy()
|
|
93
|
+
expect(document.getElementById('content-3')).toBeFalsy()
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('should update active tab when hash changes', async () => {
|
|
98
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
99
|
+
window.location.hash = '#tab1'
|
|
100
|
+
|
|
101
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
102
|
+
const tabs = createTabs()
|
|
103
|
+
|
|
104
|
+
initializeShadeRoot({
|
|
105
|
+
injector,
|
|
106
|
+
rootElement,
|
|
107
|
+
jsxElement: <Tabs tabs={tabs} />,
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
await sleepAsync(100)
|
|
111
|
+
|
|
112
|
+
expect(document.getElementById('content-1')).toBeTruthy()
|
|
113
|
+
|
|
114
|
+
// Change hash
|
|
115
|
+
window.location.hash = '#tab3'
|
|
116
|
+
injector.getInstance(LocationService).updateState()
|
|
117
|
+
|
|
118
|
+
await sleepAsync(100)
|
|
119
|
+
|
|
120
|
+
expect(document.getElementById('content-3')).toBeTruthy()
|
|
121
|
+
expect(document.getElementById('content-1')).toBeFalsy()
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('should render tab headers as anchor elements with correct href', async () => {
|
|
126
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
127
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
128
|
+
const tabs = createTabs()
|
|
129
|
+
|
|
130
|
+
initializeShadeRoot({
|
|
131
|
+
injector,
|
|
132
|
+
rootElement,
|
|
133
|
+
jsxElement: <Tabs tabs={tabs} />,
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
await sleepAsync(100)
|
|
137
|
+
|
|
138
|
+
// Tab headers extend anchor elements
|
|
139
|
+
const html = document.body.innerHTML
|
|
140
|
+
expect(html).toContain('href="#tab1"')
|
|
141
|
+
expect(html).toContain('href="#tab2"')
|
|
142
|
+
expect(html).toContain('href="#tab3"')
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('should mark the active tab header with active class', async () => {
|
|
147
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
148
|
+
window.location.hash = '#tab2'
|
|
149
|
+
|
|
150
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
151
|
+
const tabs = createTabs()
|
|
152
|
+
|
|
153
|
+
initializeShadeRoot({
|
|
154
|
+
injector,
|
|
155
|
+
rootElement,
|
|
156
|
+
jsxElement: <Tabs tabs={tabs} />,
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
await sleepAsync(100)
|
|
160
|
+
|
|
161
|
+
// The tab header with tab2 hash should have active class
|
|
162
|
+
const html = document.body.innerHTML
|
|
163
|
+
// Verify the active tab content is shown
|
|
164
|
+
expect(document.getElementById('content-2')).toBeTruthy()
|
|
165
|
+
// Check for active class in the tab-header element containing Tab 2
|
|
166
|
+
expect(html).toMatch(/shade-tab-header[^>]*class="active"[^>]*href="#tab2"/)
|
|
167
|
+
})
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
it('should switch active class when hash changes', async () => {
|
|
171
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
172
|
+
window.location.hash = '#tab1'
|
|
173
|
+
|
|
174
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
175
|
+
const tabs = createTabs()
|
|
176
|
+
|
|
177
|
+
initializeShadeRoot({
|
|
178
|
+
injector,
|
|
179
|
+
rootElement,
|
|
180
|
+
jsxElement: <Tabs tabs={tabs} />,
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
await sleepAsync(100)
|
|
184
|
+
|
|
185
|
+
// Verify tab1 is active
|
|
186
|
+
expect(document.getElementById('content-1')).toBeTruthy()
|
|
187
|
+
expect(document.body.innerHTML).toMatch(/shade-tab-header[^>]*class="active"[^>]*href="#tab1"/)
|
|
188
|
+
|
|
189
|
+
// Change hash
|
|
190
|
+
window.location.hash = '#tab2'
|
|
191
|
+
injector.getInstance(LocationService).updateState()
|
|
192
|
+
|
|
193
|
+
await sleepAsync(100)
|
|
194
|
+
|
|
195
|
+
// Verify tab2 is now active
|
|
196
|
+
expect(document.getElementById('content-2')).toBeTruthy()
|
|
197
|
+
expect(document.getElementById('content-1')).toBeFalsy()
|
|
198
|
+
expect(document.body.innerHTML).toMatch(/shade-tab-header[^>]*class="active"[^>]*href="#tab2"/)
|
|
199
|
+
})
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
it('should apply containerStyle to the element', async () => {
|
|
203
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
204
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
205
|
+
const tabs = createTabs()
|
|
206
|
+
|
|
207
|
+
initializeShadeRoot({
|
|
208
|
+
injector,
|
|
209
|
+
rootElement,
|
|
210
|
+
jsxElement: <Tabs tabs={tabs} containerStyle={{ maxWidth: '800px' }} />,
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
await sleepAsync(100)
|
|
214
|
+
|
|
215
|
+
const tabsElement = document.querySelector('shade-tabs') as HTMLElement
|
|
216
|
+
expect(tabsElement.style.maxWidth).toBe('800px')
|
|
217
|
+
})
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
it('should work with empty tabs array', async () => {
|
|
221
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
222
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
223
|
+
|
|
224
|
+
initializeShadeRoot({
|
|
225
|
+
injector,
|
|
226
|
+
rootElement,
|
|
227
|
+
jsxElement: <Tabs tabs={[]} />,
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
await sleepAsync(100)
|
|
231
|
+
|
|
232
|
+
const tabHeaders = document.querySelectorAll('shade-tab-header')
|
|
233
|
+
expect(tabHeaders.length).toBe(0)
|
|
234
|
+
})
|
|
235
|
+
})
|
|
236
|
+
})
|