@happyvertical/smrt-svelte 0.53.0 → 0.53.2
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/package.json +15 -15
- package/dist/__tests__/app-state.test.js +0 -156
- package/dist/__tests__/data-surface-bridge.test.js +0 -351
- package/dist/__tests__/provider-mode-harness.svelte +0 -14
- package/dist/__tests__/provider-mode-harness.svelte.d.ts +0 -11
- package/dist/__tests__/provider-mode-harness.svelte.d.ts.map +0 -1
- package/dist/__tests__/provider-mode-probe.svelte +0 -13
- package/dist/__tests__/provider-mode-probe.svelte.d.ts +0 -8
- package/dist/__tests__/provider-mode-probe.svelte.d.ts.map +0 -1
- package/dist/__tests__/provider-webmcp-harness.svelte +0 -14
- package/dist/__tests__/provider-webmcp-harness.svelte.d.ts +0 -8
- package/dist/__tests__/provider-webmcp-harness.svelte.d.ts.map +0 -1
- package/dist/__tests__/success-never-text.test.js +0 -100
- package/dist/__tests__/warm-clients.test.js +0 -186
- package/dist/browser-ai/adapters/llm/bitgpu-inference-backend.test.js +0 -464
- package/dist/browser-ai/adapters/llm/webllm-inference-backend.test.js +0 -646
- package/dist/browser-ai/adapters/stt/browser-speech.test.js +0 -113
- package/dist/browser-ai/adapters/stt/dictation-source.test.js +0 -52
- package/dist/browser-ai/svelte/components/__tests__/ModelStatusControl.test.js +0 -172
- package/dist/components/board/__tests__/Board.test.js +0 -498
- package/dist/components/board/__tests__/BoardSsrHarness.svelte +0 -30
- package/dist/components/board/__tests__/BoardSsrHarness.svelte.d.ts +0 -19
- package/dist/components/board/__tests__/BoardSsrHarness.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/AddressInput.behavior.test.js +0 -216
- package/dist/components/forms/__tests__/CheckboxInput.test.js +0 -92
- package/dist/components/forms/__tests__/DateRangeInput.behavior.test.js +0 -135
- package/dist/components/forms/__tests__/DateTimeInput.behavior.test.js +0 -113
- package/dist/components/forms/__tests__/FileUpload.error-alert.test.js +0 -37
- package/dist/components/forms/__tests__/FileUpload.native-form.test.js +0 -249
- package/dist/components/forms/__tests__/FileUpload.test.js +0 -90
- package/dist/components/forms/__tests__/FileUploadReset.fixture.svelte +0 -24
- package/dist/components/forms/__tests__/FileUploadReset.fixture.svelte.d.ts +0 -10
- package/dist/components/forms/__tests__/FileUploadReset.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/Form.behavior.test.js +0 -137
- package/dist/components/forms/__tests__/Form.extraction.test.js +0 -68
- package/dist/components/forms/__tests__/Form.stt-error.test.js +0 -57
- package/dist/components/forms/__tests__/Form.test.js +0 -86
- package/dist/components/forms/__tests__/Form.ui-controls.test.js +0 -119
- package/dist/components/forms/__tests__/Form.webmcp.test.js +0 -2336
- package/dist/components/forms/__tests__/FormMicButton.test.js +0 -92
- package/dist/components/forms/__tests__/FormScope.test.js +0 -39
- package/dist/components/forms/__tests__/MeasurementInput.behavior.test.js +0 -129
- package/dist/components/forms/__tests__/MoneyInput.behavior.test.js +0 -124
- package/dist/components/forms/__tests__/NumberInput.behavior.test.js +0 -151
- package/dist/components/forms/__tests__/PhoneInput.behavior.test.js +0 -96
- package/dist/components/forms/__tests__/SearchInput.test.js +0 -79
- package/dist/components/forms/__tests__/SelectInput.behavior.test.js +0 -132
- package/dist/components/forms/__tests__/TextInput.behavior.test.js +0 -131
- package/dist/components/forms/__tests__/TextareaInput.behavior.test.js +0 -96
- package/dist/components/forms/__tests__/async-invalid-field.fixture.svelte +0 -30
- package/dist/components/forms/__tests__/async-invalid-field.fixture.svelte.d.ts +0 -7
- package/dist/components/forms/__tests__/async-invalid-field.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/async-validation-form.fixture.svelte +0 -19
- package/dist/components/forms/__tests__/async-validation-form.fixture.svelte.d.ts +0 -9
- package/dist/components/forms/__tests__/async-validation-form.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/async-validation-snapshot-form.fixture.svelte +0 -21
- package/dist/components/forms/__tests__/async-validation-snapshot-form.fixture.svelte.d.ts +0 -8
- package/dist/components/forms/__tests__/async-validation-snapshot-form.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/composite-inputs-a11y.test.js +0 -69
- package/dist/components/forms/__tests__/compound-registration-wrapper.fixture.svelte +0 -27
- package/dist/components/forms/__tests__/compound-registration-wrapper.fixture.svelte.d.ts +0 -9
- package/dist/components/forms/__tests__/compound-registration-wrapper.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/custom-registration-field.fixture.svelte +0 -48
- package/dist/components/forms/__tests__/custom-registration-field.fixture.svelte.d.ts +0 -10
- package/dist/components/forms/__tests__/custom-registration-field.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/form-extraction-values.fixture.svelte +0 -26
- package/dist/components/forms/__tests__/form-extraction-values.fixture.svelte.d.ts +0 -13
- package/dist/components/forms/__tests__/form-extraction-values.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/form-registration-lifecycle.fixture.svelte +0 -52
- package/dist/components/forms/__tests__/form-registration-lifecycle.fixture.svelte.d.ts +0 -16
- package/dist/components/forms/__tests__/form-registration-lifecycle.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/form-scope.fixture.svelte +0 -30
- package/dist/components/forms/__tests__/form-scope.fixture.svelte.d.ts +0 -10
- package/dist/components/forms/__tests__/form-scope.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/form-with-fields.fixture.svelte +0 -158
- package/dist/components/forms/__tests__/form-with-fields.fixture.svelte.d.ts +0 -51
- package/dist/components/forms/__tests__/form-with-fields.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/form-with-policy-field.fixture.svelte +0 -24
- package/dist/components/forms/__tests__/form-with-policy-field.fixture.svelte.d.ts +0 -11
- package/dist/components/forms/__tests__/form-with-policy-field.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/form-with-structured-fields.fixture.svelte +0 -105
- package/dist/components/forms/__tests__/form-with-structured-fields.fixture.svelte.d.ts +0 -42
- package/dist/components/forms/__tests__/form-with-structured-fields.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/form-with-ui-controls.fixture.svelte +0 -84
- package/dist/components/forms/__tests__/form-with-ui-controls.fixture.svelte.d.ts +0 -14
- package/dist/components/forms/__tests__/form-with-ui-controls.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/legacy-form-context.fixture.svelte +0 -44
- package/dist/components/forms/__tests__/legacy-form-context.fixture.svelte.d.ts +0 -9
- package/dist/components/forms/__tests__/legacy-form-context.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/mic-keyboard-a11y.test.js +0 -74
- package/dist/components/forms/__tests__/policy-field.fixture.svelte +0 -35
- package/dist/components/forms/__tests__/policy-field.fixture.svelte.d.ts +0 -8
- package/dist/components/forms/__tests__/policy-field.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/rich-inputs-a11y.test.js +0 -87
- package/dist/components/settings/__tests__/SettingsCatalog.test.js +0 -69
- package/dist/components/settings/__tests__/catalog.test.js +0 -46
- package/dist/components/workspace/__tests__/AdminShell.responsive.test.js +0 -597
- package/dist/components/workspace/__tests__/AdminShell.test.js +0 -572
- package/dist/components/workspace/__tests__/RoleShell.test.js +0 -772
- package/dist/components/workspace/__tests__/TenantNav.test.js +0 -208
- package/dist/components/workspace/__tests__/WorkspaceAccountMenu.test.js +0 -79
- package/dist/components/workspace/__tests__/WorkspaceShell.test.js +0 -630
- package/dist/components/workspace/__tests__/admin-shell-header-overflow.test.js +0 -25
- package/dist/components/workspace/__tests__/admin-shell-hotkeys.test.js +0 -34
- package/dist/components/workspace/__tests__/admin-shell-mobile.test.js +0 -217
- package/dist/components/workspace/__tests__/admin-shell-mutation-effect-harness.svelte +0 -52
- package/dist/components/workspace/__tests__/admin-shell-mutation-effect-harness.svelte.d.ts +0 -23
- package/dist/components/workspace/__tests__/admin-shell-mutation-effect-harness.svelte.d.ts.map +0 -1
- package/dist/components/workspace/__tests__/admin-shell-page-contracts.test.js +0 -30
- package/dist/components/workspace/__tests__/admin-shell-responsive-state.test.js +0 -184
- package/dist/components/workspace/__tests__/admin-shell-settings.test.js +0 -25
- package/dist/components/workspace/__tests__/admin-shell-state.test.js +0 -286
- package/dist/components/workspace/__tests__/admin-shell-theme-hooks.test.js +0 -36
- package/dist/components/workspace/__tests__/breadcrumbs-helpers.test.js +0 -141
- package/dist/components/workspace/__tests__/context-forwarding-harness.svelte +0 -45
- package/dist/components/workspace/__tests__/context-forwarding-harness.svelte.d.ts +0 -21
- package/dist/components/workspace/__tests__/context-forwarding-harness.svelte.d.ts.map +0 -1
- package/dist/components/workspace/__tests__/define-tools-dock.test.js +0 -1188
- package/dist/components/workspace/__tests__/harness.svelte +0 -25
- package/dist/components/workspace/__tests__/harness.svelte.d.ts +0 -14
- package/dist/components/workspace/__tests__/harness.svelte.d.ts.map +0 -1
- package/dist/components/workspace/__tests__/index.test.js +0 -28
- package/dist/components/workspace/__tests__/legacy-index.test.js +0 -9
- package/dist/components/workspace/__tests__/manifest-nav-helpers.test.js +0 -24
- package/dist/components/workspace/__tests__/manifest-nav.test.js +0 -599
- package/dist/components/workspace/__tests__/nav-helpers.test.js +0 -95
- package/dist/components/workspace/__tests__/render-harness.svelte +0 -66
- package/dist/components/workspace/__tests__/render-harness.svelte.d.ts +0 -32
- package/dist/components/workspace/__tests__/render-harness.svelte.d.ts.map +0 -1
- package/dist/components/workspace/__tests__/render-tools-dock.test.js +0 -260
- package/dist/components/workspace/__tests__/role-shell-bind-harness.svelte +0 -58
- package/dist/components/workspace/__tests__/role-shell-bind-harness.svelte.d.ts +0 -16
- package/dist/components/workspace/__tests__/role-shell-bind-harness.svelte.d.ts.map +0 -1
- package/dist/components/workspace/__tests__/role-shell-switch-harness.svelte +0 -41
- package/dist/components/workspace/__tests__/role-shell-switch-harness.svelte.d.ts +0 -13
- package/dist/components/workspace/__tests__/role-shell-switch-harness.svelte.d.ts.map +0 -1
- package/dist/components/workspace/__tests__/shell-chrome-components.test.js +0 -108
- package/dist/components/workspace/__tests__/system-feed.test.js +0 -327
- package/dist/components/workspace/__tests__/test-icon.svelte +0 -17
- package/dist/components/workspace/__tests__/test-icon.svelte.d.ts +0 -19
- package/dist/components/workspace/__tests__/test-icon.svelte.d.ts.map +0 -1
- package/dist/components/workspace/__tests__/typed-tool-fixture/TypedTool.svelte +0 -38
- package/dist/components/workspace/__tests__/typed-tool-fixture/TypedTool.svelte.d.ts +0 -22
- package/dist/components/workspace/__tests__/typed-tool-fixture/TypedTool.svelte.d.ts.map +0 -1
- package/dist/components/workspace/__tests__/typed-tool-fixture/register-typed-tool.d.ts +0 -66
- package/dist/components/workspace/__tests__/typed-tool-fixture/register-typed-tool.d.ts.map +0 -1
- package/dist/components/workspace/__tests__/typed-tool-fixture/register-typed-tool.js +0 -115
- package/dist/components/workspace/__tests__/typed-tool-fixture/typed-tool-types.d.ts +0 -15
- package/dist/components/workspace/__tests__/typed-tool-fixture/typed-tool-types.d.ts.map +0 -1
- package/dist/components/workspace/__tests__/typed-tool-fixture/typed-tool-types.js +0 -7
- package/dist/components/workspace/__tests__/typed-tool-fixture.test.js +0 -115
- package/dist/components/workspace/__tests__/use-harness-orphan.svelte +0 -9
- package/dist/components/workspace/__tests__/use-harness-orphan.svelte.d.ts +0 -19
- package/dist/components/workspace/__tests__/use-harness-orphan.svelte.d.ts.map +0 -1
- package/dist/components/workspace/__tests__/use-harness.svelte +0 -23
- package/dist/components/workspace/__tests__/use-harness.svelte.d.ts +0 -8
- package/dist/components/workspace/__tests__/use-harness.svelte.d.ts.map +0 -1
- package/dist/components/workspace/__tests__/use-tools-dock.test.js +0 -33
- package/dist/components/workspace/__tests__/workspace-shell-bind-harness.svelte +0 -43
- package/dist/components/workspace/__tests__/workspace-shell-bind-harness.svelte.d.ts +0 -11
- package/dist/components/workspace/__tests__/workspace-shell-bind-harness.svelte.d.ts.map +0 -1
- package/dist/components/workspace/server/__tests__/compose-availability.test.js +0 -383
- package/dist/components/workspace/server/__tests__/typed-context-fixture.d.ts +0 -78
- package/dist/components/workspace/server/__tests__/typed-context-fixture.d.ts.map +0 -1
- package/dist/components/workspace/server/__tests__/typed-context-fixture.js +0 -104
- package/dist/hooks/__tests__/stt-consumer.fixture.svelte +0 -25
- package/dist/hooks/__tests__/stt-consumer.fixture.svelte.d.ts +0 -15
- package/dist/hooks/__tests__/stt-consumer.fixture.svelte.d.ts.map +0 -1
- package/dist/hooks/__tests__/stt-ownership-harness.svelte +0 -42
- package/dist/hooks/__tests__/stt-ownership-harness.svelte.d.ts +0 -20
- package/dist/hooks/__tests__/stt-ownership-harness.svelte.d.ts.map +0 -1
- package/dist/hooks/__tests__/useSTT-ownership.test.js +0 -102
- package/dist/i18n/__tests__/server.spec.js +0 -57
- package/dist/internal/logger.test.js +0 -54
- package/dist/provider-mode.test.js +0 -86
- package/dist/provider-webmcp.test.js +0 -244
- package/dist/state/__tests__/app-state-ai-lifecycle.test.js +0 -240
- package/dist/state/__tests__/warm-clients.test.js +0 -40
- package/dist/web/__tests__/activity-feed-harness.svelte +0 -32
- package/dist/web/__tests__/activity-feed-harness.svelte.d.ts +0 -18
- package/dist/web/__tests__/activity-feed-harness.svelte.d.ts.map +0 -1
- package/dist/web/__tests__/activity-feed.integration.svelte.test.js +0 -190
- package/dist/web/__tests__/activity-feed.svelte.test.js +0 -235
- package/dist/web/__tests__/assistant-dock.integration.svelte.test.js +0 -204
- package/dist/web/__tests__/data-surface-conformance.integration.svelte.test.js +0 -1392
- package/dist/web/__tests__/harness.svelte +0 -25
- package/dist/web/__tests__/harness.svelte.d.ts +0 -15
- package/dist/web/__tests__/harness.svelte.d.ts.map +0 -1
- package/dist/web/__tests__/list-data-surface.fixture.svelte +0 -59
- package/dist/web/__tests__/list-data-surface.fixture.svelte.d.ts +0 -14
- package/dist/web/__tests__/list-data-surface.fixture.svelte.d.ts.map +0 -1
- package/dist/web/__tests__/list-data-surface.svelte.test.js +0 -106
- package/dist/web/__tests__/list-data-surface.test.js +0 -651
- package/dist/web/__tests__/list-sort-surface.fixture.svelte +0 -41
- package/dist/web/__tests__/list-sort-surface.fixture.svelte.d.ts +0 -15
- package/dist/web/__tests__/list-sort-surface.fixture.svelte.d.ts.map +0 -1
- package/dist/web/__tests__/list-sort-surface.svelte.test.js +0 -59
- package/dist/web/__tests__/live-collection.svelte.test.js +0 -185
- package/dist/web/__tests__/provider-ui-registry.fixture.svelte +0 -43
- package/dist/web/__tests__/provider-ui-registry.fixture.svelte.d.ts +0 -13
- package/dist/web/__tests__/provider-ui-registry.fixture.svelte.d.ts.map +0 -1
- package/dist/web/__tests__/provider-ui-registry.test.js +0 -235
- package/dist/web/__tests__/remote-query-harness.svelte +0 -27
- package/dist/web/__tests__/remote-query-harness.svelte.d.ts +0 -11
- package/dist/web/__tests__/remote-query-harness.svelte.d.ts.map +0 -1
- package/dist/web/__tests__/remote-query.svelte.test.js +0 -101
- package/dist/web/__tests__/surfaces.child.fixture.svelte +0 -69
- package/dist/web/__tests__/surfaces.child.fixture.svelte.d.ts +0 -16
- package/dist/web/__tests__/surfaces.child.fixture.svelte.d.ts.map +0 -1
- package/dist/web/__tests__/surfaces.fixture.svelte +0 -26
- package/dist/web/__tests__/surfaces.fixture.svelte.d.ts +0 -18
- package/dist/web/__tests__/surfaces.fixture.svelte.d.ts.map +0 -1
- package/dist/web/__tests__/surfaces.svelte.test.js +0 -140
- package/dist/web/__tests__/update-available-harness.svelte +0 -46
- package/dist/web/__tests__/update-available-harness.svelte.d.ts +0 -22
- package/dist/web/__tests__/update-available-harness.svelte.d.ts.map +0 -1
- package/dist/web/__tests__/update-available.svelte.test.js +0 -137
- package/dist/web/__tests__/view-intent-provider.fixture.svelte +0 -28
- package/dist/web/__tests__/view-intent-provider.fixture.svelte.d.ts +0 -13
- package/dist/web/__tests__/view-intent-provider.fixture.svelte.d.ts.map +0 -1
- package/dist/web/__tests__/view-intent.fixture.svelte +0 -33
- package/dist/web/__tests__/view-intent.fixture.svelte.d.ts +0 -15
- package/dist/web/__tests__/view-intent.fixture.svelte.d.ts.map +0 -1
- package/dist/web/__tests__/view-intent.intents.d.ts +0 -14
- package/dist/web/__tests__/view-intent.intents.d.ts.map +0 -1
- package/dist/web/__tests__/view-intent.intents.js +0 -38
- package/dist/web/__tests__/view-intent.svelte.test.js +0 -244
- package/dist/web/__tests__/webmcp-composed.child.fixture.svelte +0 -73
- package/dist/web/__tests__/webmcp-composed.child.fixture.svelte.d.ts +0 -8
- package/dist/web/__tests__/webmcp-composed.child.fixture.svelte.d.ts.map +0 -1
- package/dist/web/__tests__/webmcp-composed.fixture.svelte +0 -52
- package/dist/web/__tests__/webmcp-composed.fixture.svelte.d.ts +0 -12
- package/dist/web/__tests__/webmcp-composed.fixture.svelte.d.ts.map +0 -1
- package/dist/web/__tests__/webmcp-composed.integration.svelte.test.js +0 -251
- package/dist/web/__tests__/webmcp-effects-reactivity.child.fixture.svelte +0 -16
- package/dist/web/__tests__/webmcp-effects-reactivity.child.fixture.svelte.d.ts +0 -19
- package/dist/web/__tests__/webmcp-effects-reactivity.child.fixture.svelte.d.ts.map +0 -1
- package/dist/web/__tests__/webmcp-effects-reactivity.fixture.svelte +0 -11
- package/dist/web/__tests__/webmcp-effects-reactivity.fixture.svelte.d.ts +0 -8
- package/dist/web/__tests__/webmcp-effects-reactivity.fixture.svelte.d.ts.map +0 -1
- package/dist/web/__tests__/webmcp-harness-reactive-samename.svelte +0 -20
- package/dist/web/__tests__/webmcp-harness-reactive-samename.svelte.d.ts +0 -7
- package/dist/web/__tests__/webmcp-harness-reactive-samename.svelte.d.ts.map +0 -1
- package/dist/web/__tests__/webmcp-harness-unannotated.svelte +0 -14
- package/dist/web/__tests__/webmcp-harness-unannotated.svelte.d.ts +0 -19
- package/dist/web/__tests__/webmcp-harness-unannotated.svelte.d.ts.map +0 -1
- package/dist/web/__tests__/webmcp-harness.svelte +0 -18
- package/dist/web/__tests__/webmcp-harness.svelte.d.ts +0 -7
- package/dist/web/__tests__/webmcp-harness.svelte.d.ts.map +0 -1
- package/dist/web/__tests__/webmcp-ssr.fixture.svelte +0 -13
- package/dist/web/__tests__/webmcp-ssr.fixture.svelte.d.ts +0 -19
- package/dist/web/__tests__/webmcp-ssr.fixture.svelte.d.ts.map +0 -1
- package/dist/web/__tests__/webmcp-ui.test.js +0 -723
- package/dist/web/__tests__/webmcp.test.js +0 -234
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FileUpload native-form wiring (#3260).
|
|
3
|
-
*
|
|
4
|
-
* jsdom has no `DataTransfer` constructor, so these tests cover attribute
|
|
5
|
-
* forwarding, the visually-hidden (not `hidden`) input, the required-error
|
|
6
|
-
* announcement, and the no-DataTransfer fallback. The DataTransfer path and the
|
|
7
|
-
* `new FormData(form)` post are proven in a real browser by
|
|
8
|
-
* `e2e/file-upload.spec.ts`; the form-reset cases below also run the mirror
|
|
9
|
-
* against a jsdom `DataTransfer` stand-in.
|
|
10
|
-
*/
|
|
11
|
-
import { expectNoA11yViolations } from '@happyvertical/smrt-ui/test-support/a11y';
|
|
12
|
-
import { fireEvent, render, screen } from '@testing-library/svelte';
|
|
13
|
-
import userEvent from '@testing-library/user-event';
|
|
14
|
-
import { tick } from 'svelte';
|
|
15
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
16
|
-
import FileUpload from '../FileUpload.svelte';
|
|
17
|
-
import FileUploadResetFixture from './FileUploadReset.fixture.svelte';
|
|
18
|
-
function fileInput(container) {
|
|
19
|
-
const input = container.querySelector('input[type="file"]');
|
|
20
|
-
if (!input)
|
|
21
|
-
throw new Error('file input not found');
|
|
22
|
-
return input;
|
|
23
|
-
}
|
|
24
|
-
const txt = (name, size = 4) => new File(['a'.repeat(size)], name, { type: 'text/plain' });
|
|
25
|
-
describe('FileUpload — native form wiring', () => {
|
|
26
|
-
it('forwards name, capture and required to the file input', () => {
|
|
27
|
-
const { container } = render(FileUpload, {
|
|
28
|
-
props: { name: 'photo', capture: 'environment', required: true },
|
|
29
|
-
});
|
|
30
|
-
const input = fileInput(container);
|
|
31
|
-
expect(input).toHaveAttribute('name', 'photo');
|
|
32
|
-
expect(input).toHaveAttribute('capture', 'environment');
|
|
33
|
-
expect(input).toBeRequired();
|
|
34
|
-
});
|
|
35
|
-
it('omits name, capture and required by default', () => {
|
|
36
|
-
const { container } = render(FileUpload);
|
|
37
|
-
const input = fileInput(container);
|
|
38
|
-
expect(input).not.toHaveAttribute('name');
|
|
39
|
-
expect(input).not.toHaveAttribute('capture');
|
|
40
|
-
expect(input).not.toBeRequired();
|
|
41
|
-
});
|
|
42
|
-
it('keeps the input focusable for native validation but out of the a11y tree and tab order', () => {
|
|
43
|
-
const { container } = render(FileUpload, { props: { required: true } });
|
|
44
|
-
const input = fileInput(container);
|
|
45
|
-
// `hidden` would make the control unfocusable, so the browser could not
|
|
46
|
-
// show its required bubble and would silently block the submit.
|
|
47
|
-
expect(input).not.toHaveAttribute('hidden');
|
|
48
|
-
expect(input).toHaveClass('file-upload__input');
|
|
49
|
-
expect(input).toHaveAttribute('aria-hidden', 'true');
|
|
50
|
-
expect(input).toHaveAttribute('tabindex', '-1');
|
|
51
|
-
// Not nested inside the role="button" drop zone (nested-interactive).
|
|
52
|
-
expect(screen.getByRole('button').contains(input)).toBe(false);
|
|
53
|
-
});
|
|
54
|
-
it('announces a failed required check through the error live region', async () => {
|
|
55
|
-
const { container } = render(FileUpload, { props: { required: true } });
|
|
56
|
-
const input = fileInput(container);
|
|
57
|
-
expect(input.checkValidity()).toBe(false);
|
|
58
|
-
await fireEvent(input, new Event('invalid'));
|
|
59
|
-
expect(screen.getByRole('alert').textContent?.trim()).not.toBe('');
|
|
60
|
-
});
|
|
61
|
-
it('is axe-clean with name, capture and required', async () => {
|
|
62
|
-
const { container } = render(FileUpload, {
|
|
63
|
-
props: {
|
|
64
|
-
label: 'Mill cert photo',
|
|
65
|
-
name: 'photo',
|
|
66
|
-
capture: 'environment',
|
|
67
|
-
required: true,
|
|
68
|
-
},
|
|
69
|
-
});
|
|
70
|
-
await expectNoA11yViolations(container);
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
describe('FileUpload — fallback without the DataTransfer constructor', () => {
|
|
74
|
-
it('runs in an environment without DataTransfer', () => {
|
|
75
|
-
expect(typeof globalThis.DataTransfer).toBe('undefined');
|
|
76
|
-
});
|
|
77
|
-
it('keeps the native selection when it matches the accepted list', async () => {
|
|
78
|
-
const { container } = render(FileUpload, { props: { name: 'doc' } });
|
|
79
|
-
const input = fileInput(container);
|
|
80
|
-
await userEvent.upload(input, txt('a.txt'));
|
|
81
|
-
expect(screen.getByText('a.txt')).toBeInTheDocument();
|
|
82
|
-
expect(input.files).toHaveLength(1);
|
|
83
|
-
expect(input.files?.[0].name).toBe('a.txt');
|
|
84
|
-
});
|
|
85
|
-
it('clears the input when a file is removed, so it never posts', async () => {
|
|
86
|
-
const { container } = render(FileUpload, { props: { name: 'doc' } });
|
|
87
|
-
const input = fileInput(container);
|
|
88
|
-
await userEvent.upload(input, txt('a.txt'));
|
|
89
|
-
await userEvent.click(screen.getByRole('button', { name: /remove a\.txt/i }));
|
|
90
|
-
expect(input.files).toHaveLength(0);
|
|
91
|
-
});
|
|
92
|
-
it('clears the input when the pick is rejected, so it never posts', async () => {
|
|
93
|
-
const { container } = render(FileUpload, {
|
|
94
|
-
props: { name: 'doc', maxSize: 1 },
|
|
95
|
-
});
|
|
96
|
-
const input = fileInput(container);
|
|
97
|
-
await userEvent.upload(input, txt('big.txt'));
|
|
98
|
-
expect(screen.queryByText('big.txt')).not.toBeInTheDocument();
|
|
99
|
-
expect(input.files).toHaveLength(0);
|
|
100
|
-
});
|
|
101
|
-
it('clears the input when an accumulated list no longer matches it', async () => {
|
|
102
|
-
const { container } = render(FileUpload, {
|
|
103
|
-
props: { name: 'doc', multiple: true },
|
|
104
|
-
});
|
|
105
|
-
const input = fileInput(container);
|
|
106
|
-
await userEvent.upload(input, txt('a.txt'));
|
|
107
|
-
await userEvent.upload(input, txt('b.txt'));
|
|
108
|
-
expect(screen.getByText('a.txt')).toBeInTheDocument();
|
|
109
|
-
expect(screen.getByText('b.txt')).toBeInTheDocument();
|
|
110
|
-
expect(input.files).toHaveLength(0);
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
/**
|
|
114
|
-
* A real jsdom `FileList` holding `files`: jsdom's `input.files` setter only
|
|
115
|
-
* accepts genuine instances and has no constructor, so the list comes from a
|
|
116
|
-
* detached input and is filled through its implementation object (mirrors
|
|
117
|
-
* smrt-ui's capture test environment).
|
|
118
|
-
*/
|
|
119
|
-
function createFileList(files) {
|
|
120
|
-
const implOf = (wrapper, guard) => {
|
|
121
|
-
const holder = wrapper;
|
|
122
|
-
const key = Object.getOwnPropertySymbols(wrapper).find((s) => guard(holder[s]));
|
|
123
|
-
if (!key)
|
|
124
|
-
throw new Error('jsdom wrapper has no implementation symbol');
|
|
125
|
-
return holder[key];
|
|
126
|
-
};
|
|
127
|
-
const list = document.createElement('input');
|
|
128
|
-
list.type = 'file';
|
|
129
|
-
const fileList = list.files;
|
|
130
|
-
const impl = implOf(fileList, Array.isArray);
|
|
131
|
-
for (const file of files) {
|
|
132
|
-
impl.push(implOf(file, (v) => typeof v === 'object' && v !== null));
|
|
133
|
-
}
|
|
134
|
-
return fileList;
|
|
135
|
-
}
|
|
136
|
-
/** Browser-shaped `DataTransfer` for jsdom: `items.add()` plus a real `files`. */
|
|
137
|
-
class TestDataTransfer {
|
|
138
|
-
#files = [];
|
|
139
|
-
items = {
|
|
140
|
-
add: (file) => {
|
|
141
|
-
this.#files.push(file);
|
|
142
|
-
return null;
|
|
143
|
-
},
|
|
144
|
-
};
|
|
145
|
-
get files() {
|
|
146
|
-
return createFileList(this.#files);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* A reset of the owning form (#3260): SvelteKit `enhance`'s `update()` after a
|
|
151
|
-
* success, `createFormRetry()`'s conditional reset, a reset button and
|
|
152
|
-
* `form.reset()` all empty a native file input, so the list must empty with
|
|
153
|
-
* it. jsdom's reset does not empty file inputs (browsers do), so these
|
|
154
|
-
* assertions prove the component empties its own input.
|
|
155
|
-
*/
|
|
156
|
-
describe.each([
|
|
157
|
-
['the DataTransfer mirror', true],
|
|
158
|
-
['the no-DataTransfer fallback', false],
|
|
159
|
-
])('FileUpload — form reset with %s', (_label, withDataTransfer) => {
|
|
160
|
-
beforeEach(() => {
|
|
161
|
-
if (withDataTransfer)
|
|
162
|
-
vi.stubGlobal('DataTransfer', TestDataTransfer);
|
|
163
|
-
});
|
|
164
|
-
afterEach(() => {
|
|
165
|
-
vi.unstubAllGlobals();
|
|
166
|
-
});
|
|
167
|
-
function mount(props = {}) {
|
|
168
|
-
const onchange = vi.fn();
|
|
169
|
-
const result = render(FileUploadResetFixture, {
|
|
170
|
-
props: { onchange, ...props },
|
|
171
|
-
});
|
|
172
|
-
const form = result.container.querySelector('form');
|
|
173
|
-
const bound = () => screen.getByTestId('bound').getAttribute('data-files');
|
|
174
|
-
return {
|
|
175
|
-
...result,
|
|
176
|
-
form,
|
|
177
|
-
onchange,
|
|
178
|
-
bound,
|
|
179
|
-
input: fileInput(result.container),
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
async function pick(input, files) {
|
|
183
|
-
input.files = createFileList(files);
|
|
184
|
-
await fireEvent.change(input);
|
|
185
|
-
}
|
|
186
|
-
/** Names of the non-empty `doc` entries a native submit would post. */
|
|
187
|
-
const posted = (form) => new FormData(form).getAll('doc')
|
|
188
|
-
.filter((file) => file.name !== '')
|
|
189
|
-
.map((file) => file.name);
|
|
190
|
-
it('form.reset() empties the list, its error and what posts', async () => {
|
|
191
|
-
const { form, input, onchange, bound } = mount({ maxSize: 10 });
|
|
192
|
-
await pick(input, [txt('a.txt'), txt('big.txt', 50)]);
|
|
193
|
-
expect(screen.getByText('a.txt')).toBeInTheDocument();
|
|
194
|
-
expect(bound()).toBe('a.txt');
|
|
195
|
-
expect(screen.getByRole('alert')).toBeInTheDocument();
|
|
196
|
-
if (withDataTransfer)
|
|
197
|
-
expect(posted(form)).toEqual(['a.txt']);
|
|
198
|
-
form.reset();
|
|
199
|
-
await tick();
|
|
200
|
-
expect(screen.queryByText('a.txt')).not.toBeInTheDocument();
|
|
201
|
-
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
|
|
202
|
-
expect(onchange).toHaveBeenLastCalledWith([]);
|
|
203
|
-
expect(bound()).toBe('');
|
|
204
|
-
expect(input.files).toHaveLength(0);
|
|
205
|
-
expect(posted(form)).toEqual([]);
|
|
206
|
-
// The next pick posts alone; the reset list is never re-mirrored.
|
|
207
|
-
await pick(input, [txt('b.txt')]);
|
|
208
|
-
expect(screen.getByText('b.txt')).toBeInTheDocument();
|
|
209
|
-
expect(screen.queryByText('a.txt')).not.toBeInTheDocument();
|
|
210
|
-
expect(posted(form)).toEqual(['b.txt']);
|
|
211
|
-
});
|
|
212
|
-
it('a reset button empties the list and required blocks again', async () => {
|
|
213
|
-
const { form, input } = mount({ required: true });
|
|
214
|
-
await pick(input, [txt('a.txt')]);
|
|
215
|
-
expect(input.checkValidity()).toBe(true);
|
|
216
|
-
await fireEvent.click(screen.getByRole('button', { name: 'Reset form' }));
|
|
217
|
-
expect(screen.queryByText('a.txt')).not.toBeInTheDocument();
|
|
218
|
-
expect(posted(form)).toEqual([]);
|
|
219
|
-
expect(input.validity.valueMissing).toBe(true);
|
|
220
|
-
});
|
|
221
|
-
it('empties the list even while disabled, as it empties a disabled input', async () => {
|
|
222
|
-
const { form, input, rerender, bound } = mount();
|
|
223
|
-
await pick(input, [txt('a.txt')]);
|
|
224
|
-
await rerender({ disabled: true });
|
|
225
|
-
expect(screen.getByText('a.txt')).toBeInTheDocument();
|
|
226
|
-
form.reset();
|
|
227
|
-
await tick();
|
|
228
|
-
expect(screen.queryByText('a.txt')).not.toBeInTheDocument();
|
|
229
|
-
expect(bound()).toBe('');
|
|
230
|
-
expect(input.files).toHaveLength(0);
|
|
231
|
-
});
|
|
232
|
-
it('does not report an empty list that was already empty', async () => {
|
|
233
|
-
const { form, onchange } = mount();
|
|
234
|
-
form.reset();
|
|
235
|
-
await tick();
|
|
236
|
-
expect(onchange).not.toHaveBeenCalled();
|
|
237
|
-
});
|
|
238
|
-
it('ignores the reset of another form', async () => {
|
|
239
|
-
const { input, onchange } = mount();
|
|
240
|
-
await pick(input, [txt('a.txt')]);
|
|
241
|
-
onchange.mockClear();
|
|
242
|
-
const other = document.createElement('form');
|
|
243
|
-
document.body.append(other);
|
|
244
|
-
other.reset();
|
|
245
|
-
await tick();
|
|
246
|
-
expect(screen.getByText('a.txt')).toBeInTheDocument();
|
|
247
|
-
expect(onchange).not.toHaveBeenCalled();
|
|
248
|
-
});
|
|
249
|
-
});
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Component test for FileUpload (Sweep S11, #1416).
|
|
3
|
-
*
|
|
4
|
-
* Follows the golden pattern (src/components/ui/__tests__/Button.test.ts).
|
|
5
|
-
* The native `<input type="file">` is visually hidden (so not in the a11y tree);
|
|
6
|
-
* the interactive surface is a `role="button"` drop zone. We assert the input's
|
|
7
|
-
* accept/multiple wiring and that selecting a file fires the change handler.
|
|
8
|
-
*/
|
|
9
|
-
import { expectNoA11yViolations } from '@happyvertical/smrt-ui/test-support/a11y';
|
|
10
|
-
import { render, screen } from '@testing-library/svelte';
|
|
11
|
-
import userEvent from '@testing-library/user-event';
|
|
12
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
13
|
-
import FileUpload from '../FileUpload.svelte';
|
|
14
|
-
function fileInput(container) {
|
|
15
|
-
const input = container.querySelector('input[type="file"]');
|
|
16
|
-
if (!input)
|
|
17
|
-
throw new Error('file input not found');
|
|
18
|
-
return input;
|
|
19
|
-
}
|
|
20
|
-
const png = (name = 'photo.png') => new File(['data'], name, { type: 'image/png' });
|
|
21
|
-
describe('FileUpload', () => {
|
|
22
|
-
it('renders a file input and a drop-zone button with the label', () => {
|
|
23
|
-
const { container } = render(FileUpload, {
|
|
24
|
-
props: { label: 'Drop files', hint: 'or browse' },
|
|
25
|
-
});
|
|
26
|
-
expect(screen.getByRole('button')).toBeInTheDocument();
|
|
27
|
-
expect(screen.getByText('Drop files')).toBeInTheDocument();
|
|
28
|
-
expect(fileInput(container)).toBeInTheDocument();
|
|
29
|
-
});
|
|
30
|
-
it('forwards accept and multiple to the file input', () => {
|
|
31
|
-
const { container } = render(FileUpload, {
|
|
32
|
-
props: { accept: 'image/png,.pdf', multiple: true },
|
|
33
|
-
});
|
|
34
|
-
const input = fileInput(container);
|
|
35
|
-
expect(input).toHaveAttribute('accept', 'image/png,.pdf');
|
|
36
|
-
expect(input).toHaveAttribute('multiple');
|
|
37
|
-
});
|
|
38
|
-
it('omits the multiple attribute by default', () => {
|
|
39
|
-
const { container } = render(FileUpload);
|
|
40
|
-
expect(fileInput(container)).not.toHaveAttribute('multiple');
|
|
41
|
-
});
|
|
42
|
-
it('fires onchange and lists the file when one is selected', async () => {
|
|
43
|
-
const onchange = vi.fn();
|
|
44
|
-
const { container } = render(FileUpload, {
|
|
45
|
-
props: { accept: 'image/png', onchange },
|
|
46
|
-
});
|
|
47
|
-
await userEvent.upload(fileInput(container), png('selfie.png'));
|
|
48
|
-
expect(onchange).toHaveBeenCalledTimes(1);
|
|
49
|
-
expect(onchange.mock.calls[0][0]).toHaveLength(1);
|
|
50
|
-
expect(onchange.mock.calls[0][0][0].name).toBe('selfie.png');
|
|
51
|
-
expect(screen.getByText('selfie.png')).toBeInTheDocument();
|
|
52
|
-
});
|
|
53
|
-
it('removes a selected file via its remove button', async () => {
|
|
54
|
-
const onchange = vi.fn();
|
|
55
|
-
const { container } = render(FileUpload, { props: { onchange } });
|
|
56
|
-
await userEvent.upload(fileInput(container), png('doc.png'));
|
|
57
|
-
expect(screen.getByText('doc.png')).toBeInTheDocument();
|
|
58
|
-
await userEvent.click(screen.getByRole('button', { name: /remove doc\.png/i }));
|
|
59
|
-
expect(screen.queryByText('doc.png')).not.toBeInTheDocument();
|
|
60
|
-
expect(onchange).toHaveBeenLastCalledWith([]);
|
|
61
|
-
});
|
|
62
|
-
it('rejects an oversized file and shows a max-size error', async () => {
|
|
63
|
-
// userEvent.upload honors the `accept` filter, so we exercise rejection via
|
|
64
|
-
// maxSize (which it does not enforce). A 4-byte file exceeds maxSize: 1.
|
|
65
|
-
const onchange = vi.fn();
|
|
66
|
-
const { container } = render(FileUpload, {
|
|
67
|
-
props: { accept: 'image/png', maxSize: 1, onchange },
|
|
68
|
-
});
|
|
69
|
-
await userEvent.upload(fileInput(container), png('big.png'));
|
|
70
|
-
expect(screen.queryByText('big.png')).not.toBeInTheDocument();
|
|
71
|
-
expect(screen.getByText(/exceed the maximum size/i)).toBeInTheDocument();
|
|
72
|
-
});
|
|
73
|
-
it('disables the drop zone when disabled', () => {
|
|
74
|
-
render(FileUpload, { props: { disabled: true } });
|
|
75
|
-
expect(screen.getByRole('button')).toHaveAttribute('tabindex', '-1');
|
|
76
|
-
});
|
|
77
|
-
it('is axe-clean in the default state', async () => {
|
|
78
|
-
const { container } = render(FileUpload, {
|
|
79
|
-
props: { label: 'Upload your documents' },
|
|
80
|
-
});
|
|
81
|
-
await expectNoA11yViolations(container);
|
|
82
|
-
});
|
|
83
|
-
it('is axe-clean after a file is selected', async () => {
|
|
84
|
-
const { container } = render(FileUpload, {
|
|
85
|
-
props: { label: 'Upload your documents' },
|
|
86
|
-
});
|
|
87
|
-
await userEvent.upload(fileInput(container), png('report.png'));
|
|
88
|
-
await expectNoA11yViolations(container);
|
|
89
|
-
});
|
|
90
|
-
});
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import FileUpload from '../FileUpload.svelte';
|
|
3
|
-
|
|
4
|
-
let {
|
|
5
|
-
disabled = false,
|
|
6
|
-
required = false,
|
|
7
|
-
maxSize,
|
|
8
|
-
onchange,
|
|
9
|
-
}: {
|
|
10
|
-
disabled?: boolean;
|
|
11
|
-
required?: boolean;
|
|
12
|
-
maxSize?: number;
|
|
13
|
-
onchange?: (files: File[]) => void;
|
|
14
|
-
} = $props();
|
|
15
|
-
|
|
16
|
-
let files = $state<File[]>([]);
|
|
17
|
-
</script>
|
|
18
|
-
|
|
19
|
-
<!-- A plain form with a bound list and a native reset button (#3260). -->
|
|
20
|
-
<form>
|
|
21
|
-
<FileUpload name="doc" multiple {disabled} {required} {maxSize} {onchange} bind:files />
|
|
22
|
-
<output data-testid="bound" data-files={files.map((file) => file.name).join(',')}></output>
|
|
23
|
-
<button type="reset">Reset form</button>
|
|
24
|
-
</form>
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
type $$ComponentProps = {
|
|
2
|
-
disabled?: boolean;
|
|
3
|
-
required?: boolean;
|
|
4
|
-
maxSize?: number;
|
|
5
|
-
onchange?: (files: File[]) => void;
|
|
6
|
-
};
|
|
7
|
-
declare const FileUploadReset: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
8
|
-
type FileUploadReset = ReturnType<typeof FileUploadReset>;
|
|
9
|
-
export default FileUploadReset;
|
|
10
|
-
//# sourceMappingURL=FileUploadReset.fixture.svelte.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"FileUploadReset.fixture.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/forms/__tests__/FileUploadReset.fixture.svelte.ts"],"names":[],"mappings":"AAKC,KAAK,gBAAgB,GAAI;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;CACpC,CAAC;AAuBF,QAAA,MAAM,eAAe,sDAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Deep behavior tests for Form (Sweep S11, #1416).
|
|
3
|
-
*
|
|
4
|
-
* The shallow golden test (Form.test.ts) only covers the <form> element, child
|
|
5
|
-
* rendering, the onsubmit contract, and axe. This suite exercises the real
|
|
6
|
-
* value-collection logic: Form registers child fields through its context, and
|
|
7
|
-
* on submit it walks every registered field's getValue() to build the data
|
|
8
|
-
* object. We mount Form via an inline fixture that nests TextInput +
|
|
9
|
-
* NumberInput so the registration → collection → onsubmit pipeline runs for
|
|
10
|
-
* real (no mocked field logic).
|
|
11
|
-
*
|
|
12
|
-
* Form depends on the Provider-backed useAppState/useSTT hooks (they throw
|
|
13
|
-
* outside a <Provider>), so both are mocked to non-listening stubs — the
|
|
14
|
-
* reference pattern from Form.test.ts.
|
|
15
|
-
*/
|
|
16
|
-
import { expectNoA11yViolations } from '@happyvertical/smrt-ui/test-support/a11y';
|
|
17
|
-
import { render, screen } from '@testing-library/svelte';
|
|
18
|
-
import userEvent from '@testing-library/user-event';
|
|
19
|
-
import { createRawSnippet } from 'svelte';
|
|
20
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
21
|
-
vi.mock('../../../hooks/useAppState.svelte.js', () => ({
|
|
22
|
-
useAppState: () => ({ state: { mode: 'default' }, setMode: vi.fn() }),
|
|
23
|
-
}));
|
|
24
|
-
vi.mock('../../../hooks/useSTT.svelte.js', () => ({
|
|
25
|
-
useSTT: () => ({
|
|
26
|
-
isListening: false,
|
|
27
|
-
lastResult: '',
|
|
28
|
-
isReady: false,
|
|
29
|
-
adapterType: null,
|
|
30
|
-
isInitializing: false,
|
|
31
|
-
downloadProgress: 0,
|
|
32
|
-
initialize: vi.fn(),
|
|
33
|
-
start: vi.fn(),
|
|
34
|
-
stop: vi.fn(),
|
|
35
|
-
}),
|
|
36
|
-
}));
|
|
37
|
-
import Form from '../Form.svelte';
|
|
38
|
-
import FormWithFields from './form-with-fields.fixture.svelte';
|
|
39
|
-
function submitButton() {
|
|
40
|
-
return createRawSnippet(() => ({
|
|
41
|
-
render: () => `<button type="submit">Submit</button>`,
|
|
42
|
-
}));
|
|
43
|
-
}
|
|
44
|
-
describe('Form — submit value collection', () => {
|
|
45
|
-
it('collects each registered field value into the onsubmit data object', async () => {
|
|
46
|
-
const onsubmit = vi.fn();
|
|
47
|
-
render(FormWithFields, {
|
|
48
|
-
props: { onsubmit, textValue: 'Ada Lovelace', numberValue: 36 },
|
|
49
|
-
});
|
|
50
|
-
await userEvent.click(screen.getByRole('button', { name: 'Submit' }));
|
|
51
|
-
expect(onsubmit).toHaveBeenCalledTimes(1);
|
|
52
|
-
expect(onsubmit).toHaveBeenCalledWith({
|
|
53
|
-
fullname: 'Ada Lovelace',
|
|
54
|
-
age: 36,
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
it('reflects live typing into the collected submit data', async () => {
|
|
58
|
-
const onsubmit = vi.fn();
|
|
59
|
-
render(FormWithFields, { props: { onsubmit } });
|
|
60
|
-
await userEvent.type(screen.getByLabelText('Full name'), 'Grace');
|
|
61
|
-
await userEvent.type(screen.getByLabelText('Age'), '50');
|
|
62
|
-
await userEvent.click(screen.getByRole('button', { name: 'Submit' }));
|
|
63
|
-
expect(onsubmit).toHaveBeenCalledWith({ fullname: 'Grace', age: 50 });
|
|
64
|
-
});
|
|
65
|
-
it('prevents native submission when an onsubmit handler is provided', async () => {
|
|
66
|
-
const onsubmit = vi.fn();
|
|
67
|
-
const { container } = render(FormWithFields, { props: { onsubmit } });
|
|
68
|
-
const form = container.querySelector('form');
|
|
69
|
-
expect(form).toBeInTheDocument();
|
|
70
|
-
const submitEvent = new Event('submit', {
|
|
71
|
-
bubbles: true,
|
|
72
|
-
cancelable: true,
|
|
73
|
-
});
|
|
74
|
-
form.dispatchEvent(submitEvent);
|
|
75
|
-
expect(onsubmit).toHaveBeenCalledTimes(1);
|
|
76
|
-
expect(submitEvent.defaultPrevented).toBe(true);
|
|
77
|
-
});
|
|
78
|
-
it('does NOT prevent native submission when no onsubmit handler is set', () => {
|
|
79
|
-
const { container } = render(FormWithFields, { props: {} });
|
|
80
|
-
const form = container.querySelector('form');
|
|
81
|
-
const submitEvent = new Event('submit', {
|
|
82
|
-
bubbles: true,
|
|
83
|
-
cancelable: true,
|
|
84
|
-
});
|
|
85
|
-
form.dispatchEvent(submitEvent);
|
|
86
|
-
// Native submission left intact for SvelteKit form actions / use:enhance.
|
|
87
|
-
expect(submitEvent.defaultPrevented).toBe(false);
|
|
88
|
-
});
|
|
89
|
-
it('drops a field from the collected data once it unmounts (unregister)', async () => {
|
|
90
|
-
const onsubmit = vi.fn();
|
|
91
|
-
const { rerender } = render(FormWithFields, {
|
|
92
|
-
props: { onsubmit, textValue: 'Keep', numberValue: 7, showAge: true },
|
|
93
|
-
});
|
|
94
|
-
// Both fields registered → both collected.
|
|
95
|
-
await userEvent.click(screen.getByRole('button', { name: 'Submit' }));
|
|
96
|
-
expect(onsubmit).toHaveBeenLastCalledWith({ fullname: 'Keep', age: 7 });
|
|
97
|
-
// Unmount the age field; its generation-bound disposer drops it from submit.
|
|
98
|
-
await rerender({
|
|
99
|
-
onsubmit,
|
|
100
|
-
textValue: 'Keep',
|
|
101
|
-
numberValue: 7,
|
|
102
|
-
showAge: false,
|
|
103
|
-
});
|
|
104
|
-
expect(screen.queryByLabelText('Age')).toBeNull();
|
|
105
|
-
await userEvent.click(screen.getByRole('button', { name: 'Submit' }));
|
|
106
|
-
expect(onsubmit).toHaveBeenLastCalledWith({ fullname: 'Keep' });
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
describe('Form — native form attributes', () => {
|
|
110
|
-
it('forwards method and action onto the <form> element', () => {
|
|
111
|
-
const { container } = render(Form, {
|
|
112
|
-
props: { children: submitButton(), method: 'POST', action: '/save' },
|
|
113
|
-
});
|
|
114
|
-
const form = container.querySelector('form');
|
|
115
|
-
expect(form).toHaveAttribute('method', 'POST');
|
|
116
|
-
expect(form).toHaveAttribute('action', '/save');
|
|
117
|
-
});
|
|
118
|
-
it('submits empty data when no fields are registered', async () => {
|
|
119
|
-
const onsubmit = vi.fn();
|
|
120
|
-
render(Form, { props: { children: submitButton(), onsubmit } });
|
|
121
|
-
await userEvent.click(screen.getByRole('button', { name: 'Submit' }));
|
|
122
|
-
expect(onsubmit).toHaveBeenCalledWith({});
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
describe('Form — status region & a11y', () => {
|
|
126
|
-
it('renders a polite, visually-hidden status region', () => {
|
|
127
|
-
render(Form, { props: { children: submitButton() } });
|
|
128
|
-
const status = screen.getByRole('status');
|
|
129
|
-
expect(status).toHaveAttribute('aria-live', 'polite');
|
|
130
|
-
});
|
|
131
|
-
it('is axe-clean with nested labelled fields', async () => {
|
|
132
|
-
const { container } = render(FormWithFields, {
|
|
133
|
-
props: { onsubmit: vi.fn() },
|
|
134
|
-
});
|
|
135
|
-
await expectNoA11yViolations(container);
|
|
136
|
-
});
|
|
137
|
-
});
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { render, screen, waitFor } from '@testing-library/svelte';
|
|
2
|
-
import userEvent from '@testing-library/user-event';
|
|
3
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
-
const sttStub = {
|
|
5
|
-
isListening: false,
|
|
6
|
-
isInitializing: false,
|
|
7
|
-
isReady: true,
|
|
8
|
-
adapterType: 'browser-speech',
|
|
9
|
-
downloadProgress: 0,
|
|
10
|
-
lastResult: '',
|
|
11
|
-
initialize: vi.fn(async () => { }),
|
|
12
|
-
start: vi.fn(async () => { }),
|
|
13
|
-
stop: vi.fn(async () => { }),
|
|
14
|
-
};
|
|
15
|
-
vi.mock('../../../hooks/useAppState.svelte.js', () => ({
|
|
16
|
-
useAppState: () => ({ state: { mode: 'smrt' }, setMode: vi.fn() }),
|
|
17
|
-
}));
|
|
18
|
-
vi.mock('../../../hooks/useSTT.svelte.js', () => ({
|
|
19
|
-
useSTT: () => sttStub,
|
|
20
|
-
}));
|
|
21
|
-
import FormExtractionValues from './form-extraction-values.fixture.svelte';
|
|
22
|
-
describe('Form voice extraction', () => {
|
|
23
|
-
beforeEach(() => {
|
|
24
|
-
sttStub.lastResult = '';
|
|
25
|
-
sttStub.start.mockClear();
|
|
26
|
-
sttStub.stop.mockClear();
|
|
27
|
-
});
|
|
28
|
-
it('skips an invalid earlier field and applies a later valid field', async () => {
|
|
29
|
-
const ageChanged = vi.fn();
|
|
30
|
-
const textChanged = vi.fn();
|
|
31
|
-
render(FormExtractionValues, {
|
|
32
|
-
props: { onagechange: ageChanged, ontextchange: textChanged },
|
|
33
|
-
});
|
|
34
|
-
const listen = screen.getByRole('button', { name: 'Speak all fields' });
|
|
35
|
-
await userEvent.click(listen);
|
|
36
|
-
sttStub.lastResult = 'age nonsense full name Ada Lovelace';
|
|
37
|
-
await userEvent.click(listen);
|
|
38
|
-
await waitFor(() => expect(screen.getByRole('textbox', { name: 'Full name' })).toHaveValue('Ada Lovelace'));
|
|
39
|
-
expect(ageChanged).not.toHaveBeenCalled();
|
|
40
|
-
expect(textChanged).toHaveBeenLastCalledWith('Ada Lovelace');
|
|
41
|
-
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
|
|
42
|
-
expect(screen.queryByText('staged_value_invalid')).not.toBeInTheDocument();
|
|
43
|
-
});
|
|
44
|
-
it('parses a spoken date range and continues to a later field', async () => {
|
|
45
|
-
const datesChanged = vi.fn();
|
|
46
|
-
const textChanged = vi.fn();
|
|
47
|
-
render(FormExtractionValues, {
|
|
48
|
-
props: {
|
|
49
|
-
showDateRange: true,
|
|
50
|
-
ondateschange: datesChanged,
|
|
51
|
-
ontextchange: textChanged,
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
|
-
const listen = screen.getByRole('button', { name: 'Speak all fields' });
|
|
55
|
-
await userEvent.click(listen);
|
|
56
|
-
sttStub.lastResult =
|
|
57
|
-
'dates from January 15 2026 to January 20 2026 full name Grace Hopper';
|
|
58
|
-
await userEvent.click(listen);
|
|
59
|
-
await waitFor(() => expect(datesChanged).toHaveBeenLastCalledWith({
|
|
60
|
-
startDate: '2026-01-15',
|
|
61
|
-
endDate: '2026-01-20',
|
|
62
|
-
}));
|
|
63
|
-
expect(screen.getByRole('textbox', { name: 'Full name' })).toHaveValue('Grace Hopper');
|
|
64
|
-
expect(textChanged).toHaveBeenLastCalledWith('Grace Hopper');
|
|
65
|
-
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
|
|
66
|
-
expect(screen.queryByText('staged_value_invalid')).not.toBeInTheDocument();
|
|
67
|
-
});
|
|
68
|
-
});
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Regression: Form voice-listening init failures don't wedge the form (C2/C3).
|
|
3
|
-
*
|
|
4
|
-
* `startFormListening` awaited `stt.initialize()`/`stt.start()` with no
|
|
5
|
-
* try/catch. On rejection the listening flags stayed set, the auto-stop $effect
|
|
6
|
-
* (gated on !isStarting) was suppressed, and the form was stuck in a permanent
|
|
7
|
-
* "listening" state with no surfaced error. The fix wraps the flow in
|
|
8
|
-
* try/catch/finally: reset the flags and show the error.
|
|
9
|
-
*/
|
|
10
|
-
import { render, screen, waitFor } from '@testing-library/svelte';
|
|
11
|
-
import userEvent from '@testing-library/user-event';
|
|
12
|
-
import { createRawSnippet } from 'svelte';
|
|
13
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
14
|
-
const sttStub = {
|
|
15
|
-
isListening: false,
|
|
16
|
-
isInitializing: false,
|
|
17
|
-
isReady: false,
|
|
18
|
-
adapterType: null,
|
|
19
|
-
downloadProgress: 0,
|
|
20
|
-
lastResult: '',
|
|
21
|
-
initialize: vi.fn(async () => {
|
|
22
|
-
throw new Error('model download failed');
|
|
23
|
-
}),
|
|
24
|
-
start: vi.fn(async () => { }),
|
|
25
|
-
stop: vi.fn(async () => { }),
|
|
26
|
-
};
|
|
27
|
-
vi.mock('../../../hooks/useAppState.svelte.js', () => ({
|
|
28
|
-
useAppState: () => ({ state: { mode: 'smrt' }, setMode: vi.fn() }),
|
|
29
|
-
}));
|
|
30
|
-
vi.mock('../../../hooks/useSTT.svelte.js', () => ({
|
|
31
|
-
useSTT: () => sttStub,
|
|
32
|
-
}));
|
|
33
|
-
import Form from '../Form.svelte';
|
|
34
|
-
function child() {
|
|
35
|
-
return createRawSnippet(() => ({
|
|
36
|
-
render: () => '<span>fields</span>',
|
|
37
|
-
}));
|
|
38
|
-
}
|
|
39
|
-
describe('Form — voice-listening init failure (C2)', () => {
|
|
40
|
-
it('surfaces an error and does not wedge when STT init rejects', async () => {
|
|
41
|
-
render(Form, {
|
|
42
|
-
props: { children: child(), showFormListen: true },
|
|
43
|
-
});
|
|
44
|
-
// The form-listen button is shown in smrt mode; clicking it starts listening.
|
|
45
|
-
const listenBtn = screen.getByRole('button');
|
|
46
|
-
await userEvent.click(listenBtn);
|
|
47
|
-
// initialize() rejected — an error must be surfaced (role=alert region).
|
|
48
|
-
await waitFor(() => {
|
|
49
|
-
expect(screen.getByRole('alert')).toBeInTheDocument();
|
|
50
|
-
});
|
|
51
|
-
expect(screen.getByRole('alert').textContent).toMatch(/model download failed/i);
|
|
52
|
-
// And the form is NOT wedged: the polite status region is not stuck on the
|
|
53
|
-
// "listening" message (listening flag was reset).
|
|
54
|
-
const status = screen.getByRole('status');
|
|
55
|
-
expect(status.textContent?.trim()).toBe('');
|
|
56
|
-
});
|
|
57
|
-
});
|