@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,481 @@
|
|
|
1
|
+
import { Injector } from '@furystack/inject'
|
|
2
|
+
import { createComponent, initializeShadeRoot } from '@furystack/shades'
|
|
3
|
+
import { sleepAsync, using } from '@furystack/utils'
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
5
|
+
import { Form, FormService } from './form.js'
|
|
6
|
+
|
|
7
|
+
describe('FormService', () => {
|
|
8
|
+
describe('initialization', () => {
|
|
9
|
+
it('should initialize with null validatedFormData', () => {
|
|
10
|
+
using(new FormService(), (service) => {
|
|
11
|
+
expect(service.validatedFormData.getValue()).toBeNull()
|
|
12
|
+
})
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('should initialize with null rawFormData', () => {
|
|
16
|
+
using(new FormService(), (service) => {
|
|
17
|
+
expect(service.rawFormData.getValue()).toBeNull()
|
|
18
|
+
})
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('should initialize with unknown validation result', () => {
|
|
22
|
+
using(new FormService(), (service) => {
|
|
23
|
+
expect(service.validationResult.getValue()).toEqual({ isValid: null })
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('should initialize with empty fieldErrors', () => {
|
|
28
|
+
using(new FormService(), (service) => {
|
|
29
|
+
expect(service.fieldErrors.getValue()).toEqual({})
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('should initialize with empty inputs set', () => {
|
|
34
|
+
using(new FormService(), (service) => {
|
|
35
|
+
expect(service.inputs.size).toBe(0)
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
describe('setFieldState', () => {
|
|
41
|
+
it('should update field errors with valid result', () => {
|
|
42
|
+
using(new FormService<{ email: string }>(), (service) => {
|
|
43
|
+
const validity = { valid: true } as ValidityState
|
|
44
|
+
|
|
45
|
+
service.setFieldState('email', { isValid: true }, validity)
|
|
46
|
+
|
|
47
|
+
expect(service.fieldErrors.getValue()).toEqual({
|
|
48
|
+
email: { validationResult: { isValid: true }, validity },
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('should update field errors with invalid result', () => {
|
|
54
|
+
using(new FormService<{ email: string }>(), (service) => {
|
|
55
|
+
const validity = { valid: false, valueMissing: true } as ValidityState
|
|
56
|
+
const validationResult = { isValid: false as const, message: 'Email is required' }
|
|
57
|
+
|
|
58
|
+
service.setFieldState('email', validationResult, validity)
|
|
59
|
+
|
|
60
|
+
expect(service.fieldErrors.getValue()).toEqual({
|
|
61
|
+
email: { validationResult, validity },
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('should merge field errors when updating multiple fields', () => {
|
|
67
|
+
using(new FormService<{ email: string; password: string }>(), (service) => {
|
|
68
|
+
const validity = { valid: true } as ValidityState
|
|
69
|
+
|
|
70
|
+
service.setFieldState('email', { isValid: true }, validity)
|
|
71
|
+
service.setFieldState('password', { isValid: true }, validity)
|
|
72
|
+
|
|
73
|
+
const errors = service.fieldErrors.getValue()
|
|
74
|
+
expect(errors.email).toBeDefined()
|
|
75
|
+
expect(errors.password).toBeDefined()
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
describe('disposal', () => {
|
|
81
|
+
it('should dispose all observables', () => {
|
|
82
|
+
const service = new FormService()
|
|
83
|
+
|
|
84
|
+
const validatedFormDataDisposeSpy = vi.spyOn(service.validatedFormData, Symbol.dispose)
|
|
85
|
+
const rawFormDataDisposeSpy = vi.spyOn(service.rawFormData, Symbol.dispose)
|
|
86
|
+
const validationResultDisposeSpy = vi.spyOn(service.validationResult, Symbol.dispose)
|
|
87
|
+
|
|
88
|
+
service[Symbol.dispose]()
|
|
89
|
+
|
|
90
|
+
expect(validatedFormDataDisposeSpy).toHaveBeenCalled()
|
|
91
|
+
expect(rawFormDataDisposeSpy).toHaveBeenCalled()
|
|
92
|
+
expect(validationResultDisposeSpy).toHaveBeenCalled()
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
describe('Form component', () => {
|
|
98
|
+
beforeEach(() => {
|
|
99
|
+
document.body.innerHTML = '<div id="root"></div>'
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
afterEach(() => {
|
|
103
|
+
document.body.innerHTML = ''
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('should render children', async () => {
|
|
107
|
+
const injector = new Injector()
|
|
108
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
109
|
+
|
|
110
|
+
type FormData = { name: string }
|
|
111
|
+
|
|
112
|
+
initializeShadeRoot({
|
|
113
|
+
injector,
|
|
114
|
+
rootElement,
|
|
115
|
+
jsxElement: (
|
|
116
|
+
<Form<FormData>
|
|
117
|
+
onSubmit={() => {}}
|
|
118
|
+
validate={(data): data is FormData => {
|
|
119
|
+
const d = data as Record<string, unknown>
|
|
120
|
+
return typeof d.name === 'string'
|
|
121
|
+
}}
|
|
122
|
+
>
|
|
123
|
+
<input name="name" type="text" />
|
|
124
|
+
<button type="submit">Submit</button>
|
|
125
|
+
</Form>
|
|
126
|
+
),
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
await sleepAsync(50)
|
|
130
|
+
|
|
131
|
+
const form = document.querySelector('form[is="shade-form"]')
|
|
132
|
+
expect(form).not.toBeNull()
|
|
133
|
+
expect(form?.querySelector('input[name="name"]')).not.toBeNull()
|
|
134
|
+
expect(form?.querySelector('button[type="submit"]')).not.toBeNull()
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('should call onSubmit with validated data when form is valid', async () => {
|
|
138
|
+
const injector = new Injector()
|
|
139
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
140
|
+
const onSubmit = vi.fn()
|
|
141
|
+
|
|
142
|
+
type FormData = { name: string }
|
|
143
|
+
|
|
144
|
+
initializeShadeRoot({
|
|
145
|
+
injector,
|
|
146
|
+
rootElement,
|
|
147
|
+
jsxElement: (
|
|
148
|
+
<Form<FormData>
|
|
149
|
+
onSubmit={onSubmit}
|
|
150
|
+
validate={(data): data is FormData => {
|
|
151
|
+
const d = data as Record<string, unknown>
|
|
152
|
+
return typeof d.name === 'string'
|
|
153
|
+
}}
|
|
154
|
+
>
|
|
155
|
+
<input name="name" type="text" value="Test Name" />
|
|
156
|
+
<button type="submit">Submit</button>
|
|
157
|
+
</Form>
|
|
158
|
+
),
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
await sleepAsync(50)
|
|
162
|
+
|
|
163
|
+
const form = document.querySelector('form[is="shade-form"]') as HTMLFormElement
|
|
164
|
+
const input = form.querySelector('input[name="name"]') as HTMLInputElement
|
|
165
|
+
input.value = 'Test Name'
|
|
166
|
+
|
|
167
|
+
const submitEvent = new Event('submit', { bubbles: true, cancelable: true })
|
|
168
|
+
form.dispatchEvent(submitEvent)
|
|
169
|
+
|
|
170
|
+
await sleepAsync(50)
|
|
171
|
+
|
|
172
|
+
expect(onSubmit).toHaveBeenCalledWith({ name: 'Test Name' })
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('should not call onSubmit when validation fails', async () => {
|
|
176
|
+
const injector = new Injector()
|
|
177
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
178
|
+
const onSubmit = vi.fn()
|
|
179
|
+
|
|
180
|
+
type FormData = { name: string; email: string }
|
|
181
|
+
|
|
182
|
+
initializeShadeRoot({
|
|
183
|
+
injector,
|
|
184
|
+
rootElement,
|
|
185
|
+
jsxElement: (
|
|
186
|
+
<Form<FormData>
|
|
187
|
+
onSubmit={onSubmit}
|
|
188
|
+
validate={(data): data is FormData => {
|
|
189
|
+
const d = data as Record<string, unknown>
|
|
190
|
+
return typeof d.name === 'string' && typeof d.email === 'string' && d.email.includes('@')
|
|
191
|
+
}}
|
|
192
|
+
>
|
|
193
|
+
<input name="name" type="text" />
|
|
194
|
+
<input name="email" type="text" />
|
|
195
|
+
<button type="submit">Submit</button>
|
|
196
|
+
</Form>
|
|
197
|
+
),
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
await sleepAsync(50)
|
|
201
|
+
|
|
202
|
+
const form = document.querySelector('form[is="shade-form"]') as HTMLFormElement
|
|
203
|
+
const nameInput = form.querySelector('input[name="name"]') as HTMLInputElement
|
|
204
|
+
const emailInput = form.querySelector('input[name="email"]') as HTMLInputElement
|
|
205
|
+
|
|
206
|
+
nameInput.value = 'Test'
|
|
207
|
+
emailInput.value = 'invalid-email'
|
|
208
|
+
|
|
209
|
+
const submitEvent = new Event('submit', { bubbles: true, cancelable: true })
|
|
210
|
+
form.dispatchEvent(submitEvent)
|
|
211
|
+
|
|
212
|
+
await sleepAsync(50)
|
|
213
|
+
|
|
214
|
+
expect(onSubmit).not.toHaveBeenCalled()
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
it('should set validation result to validation-failed when validate returns false', async () => {
|
|
218
|
+
const injector = new Injector()
|
|
219
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
220
|
+
|
|
221
|
+
type FormData = { email: string }
|
|
222
|
+
|
|
223
|
+
initializeShadeRoot({
|
|
224
|
+
injector,
|
|
225
|
+
rootElement,
|
|
226
|
+
jsxElement: (
|
|
227
|
+
<Form<FormData>
|
|
228
|
+
onSubmit={() => {}}
|
|
229
|
+
validate={(data): data is FormData => {
|
|
230
|
+
const d = data as Record<string, unknown>
|
|
231
|
+
return typeof d.email === 'string' && d.email.includes('@')
|
|
232
|
+
}}
|
|
233
|
+
>
|
|
234
|
+
<input name="email" type="text" />
|
|
235
|
+
<button type="submit">Submit</button>
|
|
236
|
+
</Form>
|
|
237
|
+
),
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
await sleepAsync(50)
|
|
241
|
+
|
|
242
|
+
const form = document.querySelector('form[is="shade-form"]') as HTMLFormElement
|
|
243
|
+
const input = form.querySelector('input[name="email"]') as HTMLInputElement
|
|
244
|
+
input.value = 'no-at-sign'
|
|
245
|
+
|
|
246
|
+
const changeEvent = new Event('change', { bubbles: true })
|
|
247
|
+
form.dispatchEvent(changeEvent)
|
|
248
|
+
|
|
249
|
+
await sleepAsync(50)
|
|
250
|
+
|
|
251
|
+
const formInjector = (form as unknown as { injector: Injector }).injector
|
|
252
|
+
const formService = formInjector.getInstance(FormService)
|
|
253
|
+
|
|
254
|
+
expect(formService.validationResult.getValue()).toEqual({
|
|
255
|
+
isValid: false,
|
|
256
|
+
reason: 'validation-failed',
|
|
257
|
+
})
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
it('should reset form state on reset event', async () => {
|
|
261
|
+
const injector = new Injector()
|
|
262
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
263
|
+
const onReset = vi.fn()
|
|
264
|
+
|
|
265
|
+
type FormData = { name: string }
|
|
266
|
+
|
|
267
|
+
initializeShadeRoot({
|
|
268
|
+
injector,
|
|
269
|
+
rootElement,
|
|
270
|
+
jsxElement: (
|
|
271
|
+
<Form<FormData>
|
|
272
|
+
onSubmit={() => {}}
|
|
273
|
+
onReset={onReset}
|
|
274
|
+
validate={(data): data is FormData => {
|
|
275
|
+
const d = data as Record<string, unknown>
|
|
276
|
+
return typeof d.name === 'string'
|
|
277
|
+
}}
|
|
278
|
+
>
|
|
279
|
+
<input name="name" type="text" />
|
|
280
|
+
<button type="submit">Submit</button>
|
|
281
|
+
<button type="reset">Reset</button>
|
|
282
|
+
</Form>
|
|
283
|
+
),
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
await sleepAsync(50)
|
|
287
|
+
|
|
288
|
+
const form = document.querySelector('form[is="shade-form"]') as HTMLFormElement
|
|
289
|
+
const input = form.querySelector('input[name="name"]') as HTMLInputElement
|
|
290
|
+
input.value = 'Test'
|
|
291
|
+
|
|
292
|
+
const changeEvent = new Event('change', { bubbles: true })
|
|
293
|
+
form.dispatchEvent(changeEvent)
|
|
294
|
+
|
|
295
|
+
await sleepAsync(50)
|
|
296
|
+
|
|
297
|
+
const formInjector = (form as unknown as { injector: Injector }).injector
|
|
298
|
+
const formService = formInjector.getInstance(FormService)
|
|
299
|
+
|
|
300
|
+
expect(formService.rawFormData.getValue()).toEqual({ name: 'Test' })
|
|
301
|
+
|
|
302
|
+
const resetEvent = new Event('reset', { bubbles: true })
|
|
303
|
+
form.dispatchEvent(resetEvent)
|
|
304
|
+
|
|
305
|
+
await sleepAsync(50)
|
|
306
|
+
|
|
307
|
+
expect(formService.rawFormData.getValue()).toBeNull()
|
|
308
|
+
expect(formService.validationResult.getValue()).toEqual({ isValid: null })
|
|
309
|
+
expect(formService.validatedFormData.getValue()).toBeNull()
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
it('should update rawFormData on change event', async () => {
|
|
313
|
+
const injector = new Injector()
|
|
314
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
315
|
+
|
|
316
|
+
type FormData = { username: string }
|
|
317
|
+
|
|
318
|
+
initializeShadeRoot({
|
|
319
|
+
injector,
|
|
320
|
+
rootElement,
|
|
321
|
+
jsxElement: (
|
|
322
|
+
<Form<FormData>
|
|
323
|
+
onSubmit={() => {}}
|
|
324
|
+
validate={(data): data is FormData => {
|
|
325
|
+
const d = data as Record<string, unknown>
|
|
326
|
+
return typeof d.username === 'string'
|
|
327
|
+
}}
|
|
328
|
+
>
|
|
329
|
+
<input name="username" type="text" />
|
|
330
|
+
</Form>
|
|
331
|
+
),
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
await sleepAsync(50)
|
|
335
|
+
|
|
336
|
+
const form = document.querySelector('form[is="shade-form"]') as HTMLFormElement
|
|
337
|
+
const input = form.querySelector('input[name="username"]') as HTMLInputElement
|
|
338
|
+
input.value = 'testuser'
|
|
339
|
+
|
|
340
|
+
const changeEvent = new Event('change', { bubbles: true })
|
|
341
|
+
form.dispatchEvent(changeEvent)
|
|
342
|
+
|
|
343
|
+
await sleepAsync(50)
|
|
344
|
+
|
|
345
|
+
const formInjector = (form as unknown as { injector: Injector }).injector
|
|
346
|
+
const formService = formInjector.getInstance(FormService)
|
|
347
|
+
|
|
348
|
+
expect(formService.rawFormData.getValue()).toEqual({ username: 'testuser' })
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
it('should set validatedFormData when validation passes', async () => {
|
|
352
|
+
const injector = new Injector()
|
|
353
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
354
|
+
|
|
355
|
+
type FormData = { title: string }
|
|
356
|
+
|
|
357
|
+
initializeShadeRoot({
|
|
358
|
+
injector,
|
|
359
|
+
rootElement,
|
|
360
|
+
jsxElement: (
|
|
361
|
+
<Form<FormData>
|
|
362
|
+
onSubmit={() => {}}
|
|
363
|
+
validate={(data): data is FormData => {
|
|
364
|
+
const d = data as Record<string, unknown>
|
|
365
|
+
return typeof d.title === 'string'
|
|
366
|
+
}}
|
|
367
|
+
>
|
|
368
|
+
<input name="title" type="text" />
|
|
369
|
+
</Form>
|
|
370
|
+
),
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
await sleepAsync(50)
|
|
374
|
+
|
|
375
|
+
const form = document.querySelector('form[is="shade-form"]') as HTMLFormElement
|
|
376
|
+
const input = form.querySelector('input[name="title"]') as HTMLInputElement
|
|
377
|
+
input.value = 'My Title'
|
|
378
|
+
|
|
379
|
+
const changeEvent = new Event('change', { bubbles: true })
|
|
380
|
+
form.dispatchEvent(changeEvent)
|
|
381
|
+
|
|
382
|
+
await sleepAsync(50)
|
|
383
|
+
|
|
384
|
+
const formInjector = (form as unknown as { injector: Injector }).injector
|
|
385
|
+
const formService = formInjector.getInstance(FormService)
|
|
386
|
+
|
|
387
|
+
expect(formService.validatedFormData.getValue()).toEqual({ title: 'My Title' })
|
|
388
|
+
expect(formService.validationResult.getValue()).toEqual({ isValid: true })
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
it('should prevent default on submit event', async () => {
|
|
392
|
+
const injector = new Injector()
|
|
393
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
394
|
+
|
|
395
|
+
type FormData = { field: string }
|
|
396
|
+
|
|
397
|
+
initializeShadeRoot({
|
|
398
|
+
injector,
|
|
399
|
+
rootElement,
|
|
400
|
+
jsxElement: (
|
|
401
|
+
<Form<FormData> onSubmit={() => {}} validate={(_data): _data is FormData => true}>
|
|
402
|
+
<input name="field" type="text" />
|
|
403
|
+
<button type="submit">Submit</button>
|
|
404
|
+
</Form>
|
|
405
|
+
),
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
await sleepAsync(50)
|
|
409
|
+
|
|
410
|
+
const form = document.querySelector('form[is="shade-form"]') as HTMLFormElement
|
|
411
|
+
|
|
412
|
+
const submitEvent = new Event('submit', { bubbles: true, cancelable: true })
|
|
413
|
+
const preventDefaultSpy = vi.spyOn(submitEvent, 'preventDefault')
|
|
414
|
+
|
|
415
|
+
form.dispatchEvent(submitEvent)
|
|
416
|
+
|
|
417
|
+
expect(preventDefaultSpy).toHaveBeenCalled()
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
it('should create child injector with FormService', async () => {
|
|
421
|
+
const injector = new Injector()
|
|
422
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
423
|
+
|
|
424
|
+
type FormData = { data: string }
|
|
425
|
+
|
|
426
|
+
initializeShadeRoot({
|
|
427
|
+
injector,
|
|
428
|
+
rootElement,
|
|
429
|
+
jsxElement: (
|
|
430
|
+
<Form<FormData> onSubmit={() => {}} validate={(_data): _data is FormData => true}>
|
|
431
|
+
<input name="data" type="text" />
|
|
432
|
+
</Form>
|
|
433
|
+
),
|
|
434
|
+
})
|
|
435
|
+
|
|
436
|
+
await sleepAsync(50)
|
|
437
|
+
|
|
438
|
+
const form = document.querySelector('form[is="shade-form"]') as HTMLFormElement
|
|
439
|
+
const formInjector = (form as unknown as { injector: Injector }).injector
|
|
440
|
+
|
|
441
|
+
expect(formInjector).toBeInstanceOf(Injector)
|
|
442
|
+
expect(formInjector).not.toBe(injector)
|
|
443
|
+
|
|
444
|
+
const formService = formInjector.getInstance(FormService)
|
|
445
|
+
expect(formService).toBeInstanceOf(FormService)
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
it('should handle oninvalid event and trigger validation', async () => {
|
|
449
|
+
const injector = new Injector()
|
|
450
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
451
|
+
|
|
452
|
+
type FormData = { required: string }
|
|
453
|
+
|
|
454
|
+
initializeShadeRoot({
|
|
455
|
+
injector,
|
|
456
|
+
rootElement,
|
|
457
|
+
jsxElement: (
|
|
458
|
+
<Form<FormData>
|
|
459
|
+
onSubmit={() => {}}
|
|
460
|
+
validate={(data): data is FormData => {
|
|
461
|
+
const d = data as Record<string, unknown>
|
|
462
|
+
return typeof d.required === 'string' && d.required.length > 0
|
|
463
|
+
}}
|
|
464
|
+
>
|
|
465
|
+
<input name="required" type="text" required />
|
|
466
|
+
<button type="submit">Submit</button>
|
|
467
|
+
</Form>
|
|
468
|
+
),
|
|
469
|
+
})
|
|
470
|
+
|
|
471
|
+
await sleepAsync(50)
|
|
472
|
+
|
|
473
|
+
const form = document.querySelector('form[is="shade-form"]') as HTMLFormElement
|
|
474
|
+
const input = form.querySelector('input[name="required"]') as HTMLInputElement
|
|
475
|
+
|
|
476
|
+
const invalidEvent = new Event('invalid', { bubbles: true })
|
|
477
|
+
input.dispatchEvent(invalidEvent)
|
|
478
|
+
|
|
479
|
+
await sleepAsync(50)
|
|
480
|
+
})
|
|
481
|
+
})
|