@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,808 @@
|
|
|
1
|
+
import { Injector } from '@furystack/inject'
|
|
2
|
+
import { createComponent, initializeShadeRoot } from '@furystack/shades'
|
|
3
|
+
import { sleepAsync } from '@furystack/utils'
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
5
|
+
import { ThemeProviderService } from '../../services/theme-provider-service.js'
|
|
6
|
+
import { Form, FormService } from '../form.js'
|
|
7
|
+
import { Input } from './input.js'
|
|
8
|
+
|
|
9
|
+
describe('Input', () => {
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
document.body.innerHTML = '<div id="root"></div>'
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
document.body.innerHTML = ''
|
|
16
|
+
vi.restoreAllMocks()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('should render with shadow DOM', async () => {
|
|
20
|
+
const injector = new Injector()
|
|
21
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
22
|
+
|
|
23
|
+
initializeShadeRoot({
|
|
24
|
+
injector,
|
|
25
|
+
rootElement,
|
|
26
|
+
jsxElement: <Input />,
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
await sleepAsync(50)
|
|
30
|
+
|
|
31
|
+
const input = document.querySelector('shade-input')
|
|
32
|
+
expect(input).not.toBeNull()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('should render the inner input element', async () => {
|
|
36
|
+
const injector = new Injector()
|
|
37
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
38
|
+
|
|
39
|
+
initializeShadeRoot({
|
|
40
|
+
injector,
|
|
41
|
+
rootElement,
|
|
42
|
+
jsxElement: <Input name="testField" />,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
await sleepAsync(50)
|
|
46
|
+
|
|
47
|
+
const input = document.querySelector('shade-input input') as HTMLInputElement
|
|
48
|
+
expect(input).not.toBeNull()
|
|
49
|
+
expect(input.name).toBe('testField')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('should render the label title', async () => {
|
|
53
|
+
const injector = new Injector()
|
|
54
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
55
|
+
|
|
56
|
+
initializeShadeRoot({
|
|
57
|
+
injector,
|
|
58
|
+
rootElement,
|
|
59
|
+
jsxElement: <Input labelTitle="Test Label" />,
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
await sleepAsync(50)
|
|
63
|
+
|
|
64
|
+
const label = document.querySelector('shade-input label') as HTMLLabelElement
|
|
65
|
+
expect(label).not.toBeNull()
|
|
66
|
+
expect(label.textContent).toContain('Test Label')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
describe('variants', () => {
|
|
70
|
+
it('should set data-variant attribute for outlined variant', async () => {
|
|
71
|
+
const injector = new Injector()
|
|
72
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
73
|
+
|
|
74
|
+
initializeShadeRoot({
|
|
75
|
+
injector,
|
|
76
|
+
rootElement,
|
|
77
|
+
jsxElement: <Input variant="outlined" />,
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
await sleepAsync(50)
|
|
81
|
+
|
|
82
|
+
const input = document.querySelector('shade-input') as HTMLElement
|
|
83
|
+
expect(input).not.toBeNull()
|
|
84
|
+
expect(input.getAttribute('data-variant')).toBe('outlined')
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('should set data-variant attribute for contained variant', async () => {
|
|
88
|
+
const injector = new Injector()
|
|
89
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
90
|
+
|
|
91
|
+
initializeShadeRoot({
|
|
92
|
+
injector,
|
|
93
|
+
rootElement,
|
|
94
|
+
jsxElement: <Input variant="contained" />,
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
await sleepAsync(50)
|
|
98
|
+
|
|
99
|
+
const input = document.querySelector('shade-input') as HTMLElement
|
|
100
|
+
expect(input).not.toBeNull()
|
|
101
|
+
expect(input.getAttribute('data-variant')).toBe('contained')
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('should not have data-variant when variant is not specified', async () => {
|
|
105
|
+
const injector = new Injector()
|
|
106
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
107
|
+
|
|
108
|
+
initializeShadeRoot({
|
|
109
|
+
injector,
|
|
110
|
+
rootElement,
|
|
111
|
+
jsxElement: <Input />,
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
await sleepAsync(50)
|
|
115
|
+
|
|
116
|
+
const input = document.querySelector('shade-input') as HTMLElement
|
|
117
|
+
expect(input).not.toBeNull()
|
|
118
|
+
expect(input.hasAttribute('data-variant')).toBe(false)
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
describe('disabled state', () => {
|
|
123
|
+
it('should set data-disabled attribute when disabled', async () => {
|
|
124
|
+
const injector = new Injector()
|
|
125
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
126
|
+
|
|
127
|
+
initializeShadeRoot({
|
|
128
|
+
injector,
|
|
129
|
+
rootElement,
|
|
130
|
+
jsxElement: <Input disabled />,
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
await sleepAsync(50)
|
|
134
|
+
|
|
135
|
+
const input = document.querySelector('shade-input') as HTMLElement
|
|
136
|
+
expect(input).not.toBeNull()
|
|
137
|
+
expect(input.hasAttribute('data-disabled')).toBe(true)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('should not have data-disabled attribute when not disabled', async () => {
|
|
141
|
+
const injector = new Injector()
|
|
142
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
143
|
+
|
|
144
|
+
initializeShadeRoot({
|
|
145
|
+
injector,
|
|
146
|
+
rootElement,
|
|
147
|
+
jsxElement: <Input disabled={false} />,
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
await sleepAsync(50)
|
|
151
|
+
|
|
152
|
+
const input = document.querySelector('shade-input') as HTMLElement
|
|
153
|
+
expect(input).not.toBeNull()
|
|
154
|
+
expect(input.hasAttribute('data-disabled')).toBe(false)
|
|
155
|
+
})
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
describe('validation', () => {
|
|
159
|
+
it('should call custom validation callback', async () => {
|
|
160
|
+
const injector = new Injector()
|
|
161
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
162
|
+
const getValidationResult = vi.fn().mockReturnValue({ isValid: true })
|
|
163
|
+
|
|
164
|
+
initializeShadeRoot({
|
|
165
|
+
injector,
|
|
166
|
+
rootElement,
|
|
167
|
+
jsxElement: <Input name="email" getValidationResult={getValidationResult} />,
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
await sleepAsync(50)
|
|
171
|
+
|
|
172
|
+
const input = document.querySelector('shade-input input') as HTMLInputElement
|
|
173
|
+
input.value = 'test@example.com'
|
|
174
|
+
input.dispatchEvent(new Event('change', { bubbles: true }))
|
|
175
|
+
|
|
176
|
+
await sleepAsync(50)
|
|
177
|
+
|
|
178
|
+
expect(getValidationResult).toHaveBeenCalled()
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
it('should set data-invalid when validation fails', async () => {
|
|
182
|
+
const injector = new Injector()
|
|
183
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
184
|
+
|
|
185
|
+
initializeShadeRoot({
|
|
186
|
+
injector,
|
|
187
|
+
rootElement,
|
|
188
|
+
jsxElement: (
|
|
189
|
+
<Input
|
|
190
|
+
name="email"
|
|
191
|
+
getValidationResult={() => ({ isValid: false, message: 'Invalid email' })}
|
|
192
|
+
value="invalid"
|
|
193
|
+
/>
|
|
194
|
+
),
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
await sleepAsync(50)
|
|
198
|
+
|
|
199
|
+
const inputWrapper = document.querySelector('shade-input') as HTMLElement
|
|
200
|
+
const input = inputWrapper.querySelector('input') as HTMLInputElement
|
|
201
|
+
input.value = 'invalid'
|
|
202
|
+
input.dispatchEvent(new Event('change', { bubbles: true }))
|
|
203
|
+
|
|
204
|
+
await sleepAsync(50)
|
|
205
|
+
|
|
206
|
+
expect(inputWrapper.hasAttribute('data-invalid')).toBe(true)
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
it('should not have data-invalid when validation passes', async () => {
|
|
210
|
+
const injector = new Injector()
|
|
211
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
212
|
+
|
|
213
|
+
initializeShadeRoot({
|
|
214
|
+
injector,
|
|
215
|
+
rootElement,
|
|
216
|
+
jsxElement: <Input name="email" getValidationResult={() => ({ isValid: true })} value="valid@email.com" />,
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
await sleepAsync(50)
|
|
220
|
+
|
|
221
|
+
const inputWrapper = document.querySelector('shade-input') as HTMLElement
|
|
222
|
+
const input = inputWrapper.querySelector('input') as HTMLInputElement
|
|
223
|
+
input.value = 'valid@email.com'
|
|
224
|
+
input.dispatchEvent(new Event('change', { bubbles: true }))
|
|
225
|
+
|
|
226
|
+
await sleepAsync(50)
|
|
227
|
+
|
|
228
|
+
expect(inputWrapper.hasAttribute('data-invalid')).toBe(false)
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it('should display validation message in helper text when validation fails', async () => {
|
|
232
|
+
const injector = new Injector()
|
|
233
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
234
|
+
|
|
235
|
+
initializeShadeRoot({
|
|
236
|
+
injector,
|
|
237
|
+
rootElement,
|
|
238
|
+
jsxElement: (
|
|
239
|
+
<Input name="email" getValidationResult={() => ({ isValid: false, message: 'Email is required' })} />
|
|
240
|
+
),
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
await sleepAsync(50)
|
|
244
|
+
|
|
245
|
+
const inputWrapper = document.querySelector('shade-input') as HTMLElement
|
|
246
|
+
const input = inputWrapper.querySelector('input') as HTMLInputElement
|
|
247
|
+
input.value = ''
|
|
248
|
+
input.dispatchEvent(new Event('change', { bubbles: true }))
|
|
249
|
+
|
|
250
|
+
await sleepAsync(50)
|
|
251
|
+
|
|
252
|
+
const helperText = inputWrapper.querySelector('.helperText') as HTMLElement
|
|
253
|
+
expect(helperText.textContent).toBe('Email is required')
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
it('should show default validation message for required field', async () => {
|
|
257
|
+
const injector = new Injector()
|
|
258
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
259
|
+
|
|
260
|
+
initializeShadeRoot({
|
|
261
|
+
injector,
|
|
262
|
+
rootElement,
|
|
263
|
+
jsxElement: <Input name="field" required />,
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
await sleepAsync(50)
|
|
267
|
+
|
|
268
|
+
const inputWrapper = document.querySelector('shade-input') as HTMLElement
|
|
269
|
+
const input = inputWrapper.querySelector('input') as HTMLInputElement
|
|
270
|
+
|
|
271
|
+
const invalidEvent = new Event('invalid', { bubbles: true, cancelable: true })
|
|
272
|
+
input.dispatchEvent(invalidEvent)
|
|
273
|
+
|
|
274
|
+
await sleepAsync(50)
|
|
275
|
+
|
|
276
|
+
const helperText = inputWrapper.querySelector('.helperText') as HTMLElement
|
|
277
|
+
expect(helperText.textContent).toBe('Value is required')
|
|
278
|
+
})
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
describe('helper text', () => {
|
|
282
|
+
it('should render custom helper text', async () => {
|
|
283
|
+
const injector = new Injector()
|
|
284
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
285
|
+
|
|
286
|
+
initializeShadeRoot({
|
|
287
|
+
injector,
|
|
288
|
+
rootElement,
|
|
289
|
+
jsxElement: <Input name="email" getHelperText={() => 'Enter your email address'} />,
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
await sleepAsync(50)
|
|
293
|
+
|
|
294
|
+
const inputWrapper = document.querySelector('shade-input') as HTMLElement
|
|
295
|
+
const helperText = inputWrapper.querySelector('.helperText') as HTMLElement
|
|
296
|
+
|
|
297
|
+
expect(helperText.textContent).toBe('Enter your email address')
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
it('should call getHelperText with state and validation result when validation passes', async () => {
|
|
301
|
+
const injector = new Injector()
|
|
302
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
303
|
+
const getHelperText = vi.fn().mockReturnValue('Helper text')
|
|
304
|
+
|
|
305
|
+
initializeShadeRoot({
|
|
306
|
+
injector,
|
|
307
|
+
rootElement,
|
|
308
|
+
jsxElement: (
|
|
309
|
+
<Input name="email" getHelperText={getHelperText} getValidationResult={() => ({ isValid: true })} />
|
|
310
|
+
),
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
await sleepAsync(50)
|
|
314
|
+
|
|
315
|
+
const input = document.querySelector('shade-input input') as HTMLInputElement
|
|
316
|
+
input.value = 'test'
|
|
317
|
+
input.dispatchEvent(new Event('change', { bubbles: true }))
|
|
318
|
+
|
|
319
|
+
await sleepAsync(50)
|
|
320
|
+
|
|
321
|
+
expect(getHelperText).toHaveBeenCalled()
|
|
322
|
+
|
|
323
|
+
const { calls } = getHelperText.mock
|
|
324
|
+
const callWithValidation = calls.find(
|
|
325
|
+
(call: Array<{ validationResult?: unknown }>) => call[0].validationResult !== undefined,
|
|
326
|
+
)
|
|
327
|
+
expect(callWithValidation).toBeDefined()
|
|
328
|
+
expect((callWithValidation as [{ validationResult: unknown; state: unknown }])[0].validationResult).toEqual({
|
|
329
|
+
isValid: true,
|
|
330
|
+
})
|
|
331
|
+
expect((callWithValidation as [{ validationResult: unknown; state: unknown }])[0].state).toBeDefined()
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
it('should use validation message instead of getHelperText when validation fails with message', async () => {
|
|
335
|
+
const injector = new Injector()
|
|
336
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
337
|
+
const getHelperText = vi.fn().mockReturnValue('Fallback helper')
|
|
338
|
+
|
|
339
|
+
initializeShadeRoot({
|
|
340
|
+
injector,
|
|
341
|
+
rootElement,
|
|
342
|
+
jsxElement: (
|
|
343
|
+
<Input
|
|
344
|
+
name="email"
|
|
345
|
+
getHelperText={getHelperText}
|
|
346
|
+
getValidationResult={() => ({ isValid: false, message: 'Validation error message' })}
|
|
347
|
+
/>
|
|
348
|
+
),
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
await sleepAsync(50)
|
|
352
|
+
|
|
353
|
+
const input = document.querySelector('shade-input input') as HTMLInputElement
|
|
354
|
+
input.value = 'test'
|
|
355
|
+
input.dispatchEvent(new Event('change', { bubbles: true }))
|
|
356
|
+
|
|
357
|
+
await sleepAsync(50)
|
|
358
|
+
|
|
359
|
+
const inputWrapper = document.querySelector('shade-input') as HTMLElement
|
|
360
|
+
const helperText = inputWrapper.querySelector('.helperText') as HTMLElement
|
|
361
|
+
|
|
362
|
+
expect(helperText.textContent).toBe('Validation error message')
|
|
363
|
+
})
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
describe('icons', () => {
|
|
367
|
+
it('should render start icon when getStartIcon is provided', async () => {
|
|
368
|
+
const injector = new Injector()
|
|
369
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
370
|
+
|
|
371
|
+
initializeShadeRoot({
|
|
372
|
+
injector,
|
|
373
|
+
rootElement,
|
|
374
|
+
jsxElement: <Input name="search" getStartIcon={() => '🔍'} />,
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
await sleepAsync(50)
|
|
378
|
+
|
|
379
|
+
const inputWrapper = document.querySelector('shade-input') as HTMLElement
|
|
380
|
+
const startIcon = inputWrapper.querySelector('.startIcon') as HTMLElement
|
|
381
|
+
|
|
382
|
+
expect(startIcon).not.toBeNull()
|
|
383
|
+
expect(startIcon.textContent).toBe('🔍')
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
it('should render end icon when getEndIcon is provided', async () => {
|
|
387
|
+
const injector = new Injector()
|
|
388
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
389
|
+
|
|
390
|
+
initializeShadeRoot({
|
|
391
|
+
injector,
|
|
392
|
+
rootElement,
|
|
393
|
+
jsxElement: <Input name="password" getEndIcon={() => '👁️'} />,
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
await sleepAsync(50)
|
|
397
|
+
|
|
398
|
+
const inputWrapper = document.querySelector('shade-input') as HTMLElement
|
|
399
|
+
const endIcon = inputWrapper.querySelector('.endIcon') as HTMLElement
|
|
400
|
+
|
|
401
|
+
expect(endIcon).not.toBeNull()
|
|
402
|
+
expect(endIcon.textContent).toBe('👁️')
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
it('should not render icon container when getStartIcon is not provided', async () => {
|
|
406
|
+
const injector = new Injector()
|
|
407
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
408
|
+
|
|
409
|
+
initializeShadeRoot({
|
|
410
|
+
injector,
|
|
411
|
+
rootElement,
|
|
412
|
+
jsxElement: <Input name="field" />,
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
await sleepAsync(50)
|
|
416
|
+
|
|
417
|
+
const inputWrapper = document.querySelector('shade-input') as HTMLElement
|
|
418
|
+
const startIcon = inputWrapper.querySelector('.startIcon')
|
|
419
|
+
|
|
420
|
+
expect(startIcon).toBeNull()
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
it('should update icons on state change', async () => {
|
|
424
|
+
const injector = new Injector()
|
|
425
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
426
|
+
|
|
427
|
+
initializeShadeRoot({
|
|
428
|
+
injector,
|
|
429
|
+
rootElement,
|
|
430
|
+
jsxElement: <Input name="field" getEndIcon={({ state }) => (state.focused ? '✓' : '○')} />,
|
|
431
|
+
})
|
|
432
|
+
|
|
433
|
+
await sleepAsync(50)
|
|
434
|
+
|
|
435
|
+
const inputWrapper = document.querySelector('shade-input') as HTMLElement
|
|
436
|
+
const input = inputWrapper.querySelector('input') as HTMLInputElement
|
|
437
|
+
const endIcon = inputWrapper.querySelector('.endIcon') as HTMLElement
|
|
438
|
+
|
|
439
|
+
expect(endIcon.textContent).toBe('○')
|
|
440
|
+
|
|
441
|
+
input.dispatchEvent(new FocusEvent('focus'))
|
|
442
|
+
await sleepAsync(50)
|
|
443
|
+
|
|
444
|
+
expect(endIcon.textContent).toBe('✓')
|
|
445
|
+
})
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
describe('theme integration', () => {
|
|
449
|
+
it('should set CSS color variables from theme', async () => {
|
|
450
|
+
const injector = new Injector()
|
|
451
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
452
|
+
|
|
453
|
+
initializeShadeRoot({
|
|
454
|
+
injector,
|
|
455
|
+
rootElement,
|
|
456
|
+
jsxElement: <Input name="field" />,
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
await sleepAsync(50)
|
|
460
|
+
|
|
461
|
+
const inputWrapper = document.querySelector('shade-input') as HTMLElement
|
|
462
|
+
|
|
463
|
+
const themeService = injector.getInstance(ThemeProviderService)
|
|
464
|
+
expect(inputWrapper.style.getPropertyValue('--input-primary-color')).toBe(themeService.theme.palette.primary.main)
|
|
465
|
+
expect(inputWrapper.style.getPropertyValue('--input-error-color')).toBe(themeService.theme.palette.error.main)
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
it('should use custom color from defaultColor prop', async () => {
|
|
469
|
+
const injector = new Injector()
|
|
470
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
471
|
+
|
|
472
|
+
initializeShadeRoot({
|
|
473
|
+
injector,
|
|
474
|
+
rootElement,
|
|
475
|
+
jsxElement: <Input name="field" defaultColor="secondary" />,
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
await sleepAsync(50)
|
|
479
|
+
|
|
480
|
+
const inputWrapper = document.querySelector('shade-input') as HTMLElement
|
|
481
|
+
|
|
482
|
+
const themeService = injector.getInstance(ThemeProviderService)
|
|
483
|
+
expect(inputWrapper.style.getPropertyValue('--input-primary-color')).toBe(
|
|
484
|
+
themeService.theme.palette.secondary.main,
|
|
485
|
+
)
|
|
486
|
+
})
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
describe('callbacks', () => {
|
|
490
|
+
it('should call onTextChange when input value changes', async () => {
|
|
491
|
+
const injector = new Injector()
|
|
492
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
493
|
+
const onTextChange = vi.fn()
|
|
494
|
+
|
|
495
|
+
initializeShadeRoot({
|
|
496
|
+
injector,
|
|
497
|
+
rootElement,
|
|
498
|
+
jsxElement: <Input name="field" onTextChange={onTextChange} />,
|
|
499
|
+
})
|
|
500
|
+
|
|
501
|
+
await sleepAsync(50)
|
|
502
|
+
|
|
503
|
+
const input = document.querySelector('shade-input input') as HTMLInputElement
|
|
504
|
+
input.value = 'new value'
|
|
505
|
+
input.dispatchEvent(new Event('change', { bubbles: true }))
|
|
506
|
+
|
|
507
|
+
await sleepAsync(50)
|
|
508
|
+
|
|
509
|
+
expect(onTextChange).toHaveBeenCalledWith('new value')
|
|
510
|
+
})
|
|
511
|
+
|
|
512
|
+
it('should call onchange when input value changes', async () => {
|
|
513
|
+
const injector = new Injector()
|
|
514
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
515
|
+
const onchange = vi.fn()
|
|
516
|
+
|
|
517
|
+
initializeShadeRoot({
|
|
518
|
+
injector,
|
|
519
|
+
rootElement,
|
|
520
|
+
jsxElement: <Input name="field" onchange={onchange} />,
|
|
521
|
+
})
|
|
522
|
+
|
|
523
|
+
await sleepAsync(50)
|
|
524
|
+
|
|
525
|
+
const input = document.querySelector('shade-input input') as HTMLInputElement
|
|
526
|
+
input.value = 'test'
|
|
527
|
+
input.dispatchEvent(new Event('change', { bubbles: true }))
|
|
528
|
+
|
|
529
|
+
await sleepAsync(50)
|
|
530
|
+
|
|
531
|
+
expect(onchange).toHaveBeenCalled()
|
|
532
|
+
})
|
|
533
|
+
})
|
|
534
|
+
|
|
535
|
+
describe('focus and blur', () => {
|
|
536
|
+
it('should update state on focus', async () => {
|
|
537
|
+
const injector = new Injector()
|
|
538
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
539
|
+
const getEndIcon = vi.fn().mockReturnValue('icon')
|
|
540
|
+
|
|
541
|
+
initializeShadeRoot({
|
|
542
|
+
injector,
|
|
543
|
+
rootElement,
|
|
544
|
+
jsxElement: <Input name="field" getEndIcon={getEndIcon} />,
|
|
545
|
+
})
|
|
546
|
+
|
|
547
|
+
await sleepAsync(50)
|
|
548
|
+
|
|
549
|
+
const input = document.querySelector('shade-input input') as HTMLInputElement
|
|
550
|
+
input.dispatchEvent(new FocusEvent('focus'))
|
|
551
|
+
|
|
552
|
+
await sleepAsync(50)
|
|
553
|
+
|
|
554
|
+
expect(getEndIcon).toHaveBeenLastCalledWith(
|
|
555
|
+
expect.objectContaining({
|
|
556
|
+
state: expect.objectContaining({
|
|
557
|
+
focused: true,
|
|
558
|
+
}) as unknown,
|
|
559
|
+
}),
|
|
560
|
+
)
|
|
561
|
+
})
|
|
562
|
+
|
|
563
|
+
it('should update state on blur', async () => {
|
|
564
|
+
const injector = new Injector()
|
|
565
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
566
|
+
const getEndIcon = vi.fn().mockReturnValue('icon')
|
|
567
|
+
|
|
568
|
+
initializeShadeRoot({
|
|
569
|
+
injector,
|
|
570
|
+
rootElement,
|
|
571
|
+
jsxElement: <Input name="field" getEndIcon={getEndIcon} />,
|
|
572
|
+
})
|
|
573
|
+
|
|
574
|
+
await sleepAsync(50)
|
|
575
|
+
|
|
576
|
+
const input = document.querySelector('shade-input input') as HTMLInputElement
|
|
577
|
+
|
|
578
|
+
input.dispatchEvent(new FocusEvent('focus'))
|
|
579
|
+
await sleepAsync(50)
|
|
580
|
+
|
|
581
|
+
input.dispatchEvent(new FocusEvent('blur'))
|
|
582
|
+
await sleepAsync(50)
|
|
583
|
+
|
|
584
|
+
expect(getEndIcon).toHaveBeenLastCalledWith(
|
|
585
|
+
expect.objectContaining({
|
|
586
|
+
state: expect.objectContaining({
|
|
587
|
+
focused: false,
|
|
588
|
+
}) as unknown,
|
|
589
|
+
}),
|
|
590
|
+
)
|
|
591
|
+
})
|
|
592
|
+
})
|
|
593
|
+
|
|
594
|
+
describe('autofocus', () => {
|
|
595
|
+
it('should set initial focused state based on autofocus prop', async () => {
|
|
596
|
+
const injector = new Injector()
|
|
597
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
598
|
+
const getEndIcon = vi.fn().mockReturnValue('icon')
|
|
599
|
+
|
|
600
|
+
initializeShadeRoot({
|
|
601
|
+
injector,
|
|
602
|
+
rootElement,
|
|
603
|
+
jsxElement: <Input name="field" autofocus getEndIcon={getEndIcon} />,
|
|
604
|
+
})
|
|
605
|
+
|
|
606
|
+
await sleepAsync(50)
|
|
607
|
+
|
|
608
|
+
expect(getEndIcon).toHaveBeenCalledWith(
|
|
609
|
+
expect.objectContaining({
|
|
610
|
+
state: expect.objectContaining({
|
|
611
|
+
focused: true,
|
|
612
|
+
}) as unknown,
|
|
613
|
+
}),
|
|
614
|
+
)
|
|
615
|
+
})
|
|
616
|
+
})
|
|
617
|
+
|
|
618
|
+
describe('FormService integration', () => {
|
|
619
|
+
it('should register input with FormService when inside a Form', async () => {
|
|
620
|
+
const injector = new Injector()
|
|
621
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
622
|
+
|
|
623
|
+
type TestFormData = { email: string }
|
|
624
|
+
|
|
625
|
+
initializeShadeRoot({
|
|
626
|
+
injector,
|
|
627
|
+
rootElement,
|
|
628
|
+
jsxElement: (
|
|
629
|
+
<Form<TestFormData> onSubmit={() => {}} validate={(_data): _data is TestFormData => true}>
|
|
630
|
+
<Input name="email" labelTitle="Email" />
|
|
631
|
+
</Form>
|
|
632
|
+
),
|
|
633
|
+
})
|
|
634
|
+
|
|
635
|
+
await sleepAsync(50)
|
|
636
|
+
|
|
637
|
+
const form = document.querySelector('form[is="shade-form"]') as HTMLFormElement
|
|
638
|
+
const formInjector = (form as unknown as { injector: Injector }).injector
|
|
639
|
+
const formService = formInjector.getInstance(FormService)
|
|
640
|
+
|
|
641
|
+
expect(formService.inputs.size).toBe(1)
|
|
642
|
+
})
|
|
643
|
+
|
|
644
|
+
it('should update FormService field state on validation', async () => {
|
|
645
|
+
const injector = new Injector()
|
|
646
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
647
|
+
|
|
648
|
+
type TestFormData = { email: string }
|
|
649
|
+
|
|
650
|
+
initializeShadeRoot({
|
|
651
|
+
injector,
|
|
652
|
+
rootElement,
|
|
653
|
+
jsxElement: (
|
|
654
|
+
<Form<TestFormData> onSubmit={() => {}} validate={(_data): _data is TestFormData => true}>
|
|
655
|
+
<Input
|
|
656
|
+
name="email"
|
|
657
|
+
labelTitle="Email"
|
|
658
|
+
getValidationResult={({ state }) => {
|
|
659
|
+
if (state.value.includes('@')) {
|
|
660
|
+
return { isValid: true }
|
|
661
|
+
}
|
|
662
|
+
return { isValid: false, message: 'Invalid email format' }
|
|
663
|
+
}}
|
|
664
|
+
/>
|
|
665
|
+
</Form>
|
|
666
|
+
),
|
|
667
|
+
})
|
|
668
|
+
|
|
669
|
+
await sleepAsync(50)
|
|
670
|
+
|
|
671
|
+
const form = document.querySelector('form[is="shade-form"]') as HTMLFormElement
|
|
672
|
+
const inputWrapper = form.querySelector('shade-input') as HTMLElement
|
|
673
|
+
const input = inputWrapper.querySelector('input') as HTMLInputElement
|
|
674
|
+
|
|
675
|
+
input.value = 'invalid'
|
|
676
|
+
input.dispatchEvent(new Event('change', { bubbles: true }))
|
|
677
|
+
|
|
678
|
+
await sleepAsync(50)
|
|
679
|
+
|
|
680
|
+
const formInjector = (form as unknown as { injector: Injector }).injector
|
|
681
|
+
const formService = formInjector.getInstance(FormService)
|
|
682
|
+
const fieldErrors = formService.fieldErrors.getValue()
|
|
683
|
+
|
|
684
|
+
expect(fieldErrors.email).toBeDefined()
|
|
685
|
+
expect(fieldErrors.email?.validationResult).toEqual({
|
|
686
|
+
isValid: false,
|
|
687
|
+
message: 'Invalid email format',
|
|
688
|
+
})
|
|
689
|
+
})
|
|
690
|
+
|
|
691
|
+
it('should unregister input from FormService on cleanup', async () => {
|
|
692
|
+
const injector = new Injector()
|
|
693
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
694
|
+
|
|
695
|
+
type TestFormData = { email: string }
|
|
696
|
+
|
|
697
|
+
initializeShadeRoot({
|
|
698
|
+
injector,
|
|
699
|
+
rootElement,
|
|
700
|
+
jsxElement: (
|
|
701
|
+
<Form<TestFormData> onSubmit={() => {}} validate={(_data): _data is TestFormData => true}>
|
|
702
|
+
<Input name="email" labelTitle="Email" />
|
|
703
|
+
</Form>
|
|
704
|
+
),
|
|
705
|
+
})
|
|
706
|
+
|
|
707
|
+
await sleepAsync(50)
|
|
708
|
+
|
|
709
|
+
const form = document.querySelector('form[is="shade-form"]') as HTMLFormElement
|
|
710
|
+
const formInjector = (form as unknown as { injector: Injector }).injector
|
|
711
|
+
const formService = formInjector.getInstance(FormService)
|
|
712
|
+
|
|
713
|
+
expect(formService.inputs.size).toBe(1)
|
|
714
|
+
|
|
715
|
+
rootElement.innerHTML = ''
|
|
716
|
+
|
|
717
|
+
await sleepAsync(50)
|
|
718
|
+
})
|
|
719
|
+
})
|
|
720
|
+
|
|
721
|
+
describe('default validation messages', () => {
|
|
722
|
+
it('should show message for typeMismatch', async () => {
|
|
723
|
+
const injector = new Injector()
|
|
724
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
725
|
+
|
|
726
|
+
initializeShadeRoot({
|
|
727
|
+
injector,
|
|
728
|
+
rootElement,
|
|
729
|
+
jsxElement: <Input name="email" type="email" />,
|
|
730
|
+
})
|
|
731
|
+
|
|
732
|
+
await sleepAsync(50)
|
|
733
|
+
|
|
734
|
+
const inputWrapper = document.querySelector('shade-input') as HTMLElement
|
|
735
|
+
const input = inputWrapper.querySelector('input') as HTMLInputElement
|
|
736
|
+
|
|
737
|
+
input.value = 'not-an-email'
|
|
738
|
+
const invalidEvent = new Event('invalid', { bubbles: true, cancelable: true })
|
|
739
|
+
input.dispatchEvent(invalidEvent)
|
|
740
|
+
|
|
741
|
+
await sleepAsync(50)
|
|
742
|
+
|
|
743
|
+
const helperText = inputWrapper.querySelector('.helperText') as HTMLElement
|
|
744
|
+
expect(helperText.textContent).toBe('Value is not valid')
|
|
745
|
+
})
|
|
746
|
+
|
|
747
|
+
it('should handle pattern mismatch validation', async () => {
|
|
748
|
+
const injector = new Injector()
|
|
749
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
750
|
+
|
|
751
|
+
initializeShadeRoot({
|
|
752
|
+
injector,
|
|
753
|
+
rootElement,
|
|
754
|
+
jsxElement: <Input name="code" pattern="[A-Z]{3}" />,
|
|
755
|
+
})
|
|
756
|
+
|
|
757
|
+
await sleepAsync(50)
|
|
758
|
+
|
|
759
|
+
const inputWrapper = document.querySelector('shade-input') as HTMLElement
|
|
760
|
+
const input = inputWrapper.querySelector('input') as HTMLInputElement
|
|
761
|
+
|
|
762
|
+
input.value = '123'
|
|
763
|
+
const invalidEvent = new Event('invalid', { bubbles: true, cancelable: true })
|
|
764
|
+
input.dispatchEvent(invalidEvent)
|
|
765
|
+
|
|
766
|
+
await sleepAsync(50)
|
|
767
|
+
|
|
768
|
+
const helperText = inputWrapper.querySelector('.helperText') as HTMLElement
|
|
769
|
+
expect(helperText.textContent).toBe('Value does not match the pattern')
|
|
770
|
+
})
|
|
771
|
+
})
|
|
772
|
+
|
|
773
|
+
describe('value handling', () => {
|
|
774
|
+
it('should use initial value prop', async () => {
|
|
775
|
+
const injector = new Injector()
|
|
776
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
777
|
+
|
|
778
|
+
initializeShadeRoot({
|
|
779
|
+
injector,
|
|
780
|
+
rootElement,
|
|
781
|
+
jsxElement: <Input name="field" value="initial value" />,
|
|
782
|
+
})
|
|
783
|
+
|
|
784
|
+
await sleepAsync(50)
|
|
785
|
+
|
|
786
|
+
const input = document.querySelector('shade-input input') as HTMLInputElement
|
|
787
|
+
expect(input.value).toBe('initial value')
|
|
788
|
+
})
|
|
789
|
+
})
|
|
790
|
+
|
|
791
|
+
describe('labelProps', () => {
|
|
792
|
+
it('should pass labelProps to the label element', async () => {
|
|
793
|
+
const injector = new Injector()
|
|
794
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
795
|
+
|
|
796
|
+
initializeShadeRoot({
|
|
797
|
+
injector,
|
|
798
|
+
rootElement,
|
|
799
|
+
jsxElement: <Input name="field" labelProps={{ className: 'custom-label' }} />,
|
|
800
|
+
})
|
|
801
|
+
|
|
802
|
+
await sleepAsync(50)
|
|
803
|
+
|
|
804
|
+
const label = document.querySelector('shade-input label') as HTMLLabelElement
|
|
805
|
+
expect(label.className).toContain('custom-label')
|
|
806
|
+
})
|
|
807
|
+
})
|
|
808
|
+
})
|