@happyvertical/smrt-ui 0.52.0 → 0.53.1
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/AGENTS.md +11 -1
- package/README.md +300 -2
- package/dist/components/data/DataTable.svelte +60 -15
- package/dist/components/data/DataTable.svelte.d.ts.map +1 -1
- package/dist/components/data/DataTableResponsive.d.ts +4 -0
- package/dist/components/data/DataTableResponsive.d.ts.map +1 -0
- package/dist/components/data/DataTableResponsive.js +22 -0
- package/dist/components/data/__tests__/DataTableResponsive.test.js +80 -0
- package/dist/components/data/__tests__/responsive-table.fixture.svelte +29 -0
- package/dist/components/data/__tests__/responsive-table.fixture.svelte.d.ts +7 -0
- package/dist/components/data/__tests__/responsive-table.fixture.svelte.d.ts.map +1 -0
- package/dist/components/data/types.d.ts +8 -0
- package/dist/components/data/types.d.ts.map +1 -1
- package/dist/components/display/DateDisplay.svelte +76 -14
- package/dist/components/display/DateDisplay.svelte.d.ts +2 -0
- package/dist/components/display/DateDisplay.svelte.d.ts.map +1 -1
- package/dist/components/display/Icon.svelte +3 -16
- package/dist/components/display/Icon.svelte.d.ts +0 -9
- package/dist/components/display/Icon.svelte.d.ts.map +1 -1
- package/dist/components/display/StatusBadge.svelte +29 -2
- package/dist/components/display/StatusBadge.svelte.d.ts +4 -2
- package/dist/components/display/StatusBadge.svelte.d.ts.map +1 -1
- package/dist/components/display/__tests__/DateDisplay.test.js +82 -1
- package/dist/components/display/__tests__/Icon.test.js +132 -2
- package/dist/components/display/__tests__/StatusBadge.test.js +78 -0
- package/dist/components/display/icons.svelte.d.ts +11 -0
- package/dist/components/display/icons.svelte.d.ts.map +1 -0
- package/dist/components/display/icons.svelte.js +60 -0
- package/dist/components/display/index.d.ts +3 -1
- package/dist/components/display/index.d.ts.map +1 -1
- package/dist/components/display/index.js +1 -0
- package/dist/components/display/types.d.ts +2 -0
- package/dist/components/display/types.d.ts.map +1 -1
- package/dist/components/feedback/ConfirmDialog.svelte +59 -48
- package/dist/components/feedback/ConfirmDialog.svelte.d.ts +14 -2
- package/dist/components/feedback/ConfirmDialog.svelte.d.ts.map +1 -1
- package/dist/components/feedback/ToastViewport.svelte +156 -8
- package/dist/components/feedback/ToastViewport.svelte.d.ts +5 -1
- package/dist/components/feedback/ToastViewport.svelte.d.ts.map +1 -1
- package/dist/components/feedback/__tests__/ConfirmDialog.test.js +38 -20
- package/dist/components/feedback/__tests__/confirm-message.fixture.svelte +9 -0
- package/dist/components/feedback/__tests__/confirm-message.fixture.svelte.d.ts +19 -0
- package/dist/components/feedback/__tests__/confirm-message.fixture.svelte.d.ts.map +1 -0
- package/dist/components/feedback/__tests__/toast.test.js +142 -1
- package/dist/components/feedback/index.d.ts +1 -1
- package/dist/components/feedback/index.d.ts.map +1 -1
- package/dist/components/feedback/toast.d.ts +13 -1
- package/dist/components/feedback/toast.d.ts.map +1 -1
- package/dist/components/feedback/toast.js +51 -10
- package/dist/components/forms/CameraCapture.svelte +619 -0
- package/dist/components/forms/CameraCapture.svelte.d.ts +42 -0
- package/dist/components/forms/CameraCapture.svelte.d.ts.map +1 -0
- package/dist/components/forms/Checkbox.svelte +8 -1
- package/dist/components/forms/Checkbox.svelte.d.ts +2 -0
- package/dist/components/forms/Checkbox.svelte.d.ts.map +1 -1
- package/dist/components/forms/Combobox.svelte +40 -0
- package/dist/components/forms/Combobox.svelte.d.ts.map +1 -1
- package/dist/components/forms/Form.svelte +5 -0
- package/dist/components/forms/Form.svelte.d.ts +1 -0
- package/dist/components/forms/Form.svelte.d.ts.map +1 -1
- package/dist/components/forms/Input.svelte +8 -1
- package/dist/components/forms/Input.svelte.d.ts +2 -0
- package/dist/components/forms/Input.svelte.d.ts.map +1 -1
- package/dist/components/forms/Listbox.svelte +39 -1
- package/dist/components/forms/Listbox.svelte.d.ts.map +1 -1
- package/dist/components/forms/MultiSelect.svelte +41 -1
- package/dist/components/forms/MultiSelect.svelte.d.ts.map +1 -1
- package/dist/components/forms/Radio.svelte +1 -0
- package/dist/components/forms/RadioGroup.svelte +7 -1
- package/dist/components/forms/RadioGroup.svelte.d.ts +2 -0
- package/dist/components/forms/RadioGroup.svelte.d.ts.map +1 -1
- package/dist/components/forms/SegmentedControl.svelte +152 -11
- package/dist/components/forms/SegmentedControl.svelte.d.ts +4 -0
- package/dist/components/forms/SegmentedControl.svelte.d.ts.map +1 -1
- package/dist/components/forms/Select.svelte +8 -1
- package/dist/components/forms/Select.svelte.d.ts +2 -0
- package/dist/components/forms/Select.svelte.d.ts.map +1 -1
- package/dist/components/forms/SignaturePad.svelte +436 -0
- package/dist/components/forms/SignaturePad.svelte.d.ts +32 -0
- package/dist/components/forms/SignaturePad.svelte.d.ts.map +1 -0
- package/dist/components/forms/Switch.svelte +8 -1
- package/dist/components/forms/Switch.svelte.d.ts +2 -0
- package/dist/components/forms/Switch.svelte.d.ts.map +1 -1
- package/dist/components/forms/Textarea.svelte +8 -1
- package/dist/components/forms/Textarea.svelte.d.ts +2 -0
- package/dist/components/forms/Textarea.svelte.d.ts.map +1 -1
- package/dist/components/forms/__tests__/CameraCapture.test.js +504 -0
- package/dist/components/forms/__tests__/Combobox.test.js +279 -12
- package/dist/components/forms/__tests__/Form.test.js +46 -0
- package/dist/components/forms/__tests__/Listbox.test.js +303 -0
- package/dist/components/forms/__tests__/MultiSelect.test.js +281 -0
- package/dist/components/forms/__tests__/SignaturePad.test.js +338 -0
- package/dist/components/forms/__tests__/camera-capture-session.test.js +207 -0
- package/dist/components/forms/__tests__/capture-form-retry.test.js +166 -0
- package/dist/components/forms/__tests__/capture-form.fixture.svelte +38 -0
- package/dist/components/forms/__tests__/capture-form.fixture.svelte.d.ts +10 -0
- package/dist/components/forms/__tests__/capture-form.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/capture-test-env.d.ts +70 -0
- package/dist/components/forms/__tests__/capture-test-env.d.ts.map +1 -0
- package/dist/components/forms/__tests__/capture-test-env.js +122 -0
- package/dist/components/forms/__tests__/capture-touch.fixture.svelte +15 -0
- package/dist/components/forms/__tests__/capture-touch.fixture.svelte.d.ts +8 -0
- package/dist/components/forms/__tests__/capture-touch.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/combobox-form.fixture.svelte +29 -0
- package/dist/components/forms/__tests__/combobox-form.fixture.svelte.d.ts +14 -0
- package/dist/components/forms/__tests__/combobox-form.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/form-attachment.fixture.svelte +28 -0
- package/dist/components/forms/__tests__/form-attachment.fixture.svelte.d.ts +11 -0
- package/dist/components/forms/__tests__/form-attachment.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/form-retry-form.fixture.svelte +31 -0
- package/dist/components/forms/__tests__/form-retry-form.fixture.svelte.d.ts +13 -0
- package/dist/components/forms/__tests__/form-retry-form.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/form-retry-form.test.js +107 -0
- package/dist/components/forms/__tests__/hidden-controls-form-retry.test.js +69 -0
- package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte +29 -0
- package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte.d.ts +9 -0
- package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/listbox-form.fixture.svelte +27 -0
- package/dist/components/forms/__tests__/listbox-form.fixture.svelte.d.ts +13 -0
- package/dist/components/forms/__tests__/listbox-form.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/multiselect-form.fixture.svelte +27 -0
- package/dist/components/forms/__tests__/multiselect-form.fixture.svelte.d.ts +13 -0
- package/dist/components/forms/__tests__/multiselect-form.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/native-file-field.test.js +177 -0
- package/dist/components/forms/__tests__/segmented-posting.fixture.svelte +28 -0
- package/dist/components/forms/__tests__/segmented-posting.fixture.svelte.d.ts +12 -0
- package/dist/components/forms/__tests__/segmented-posting.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/segmented-posting.test.js +92 -0
- package/dist/components/forms/__tests__/signature-pad-logic.test.js +54 -0
- package/dist/components/forms/__tests__/touch-density.fixture.svelte +34 -0
- package/dist/components/forms/__tests__/touch-density.fixture.svelte.d.ts +8 -0
- package/dist/components/forms/__tests__/touch-density.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/touch-density.test.js +66 -0
- package/dist/components/forms/camera-capture-session.d.ts +81 -0
- package/dist/components/forms/camera-capture-session.d.ts.map +1 -0
- package/dist/components/forms/camera-capture-session.js +130 -0
- package/dist/components/forms/form-retry/__tests__/boundaries.test.js +23 -0
- package/dist/components/forms/form-retry/__tests__/content-digest.test.js +70 -0
- package/dist/components/forms/form-retry/__tests__/controller.test.js +802 -0
- package/dist/components/forms/form-retry/__tests__/fake-kit.d.ts +82 -0
- package/dist/components/forms/form-retry/__tests__/fake-kit.d.ts.map +1 -0
- package/dist/components/forms/form-retry/__tests__/fake-kit.js +180 -0
- package/dist/components/forms/form-retry/__tests__/form-draft.test.js +169 -0
- package/dist/components/forms/form-retry/__tests__/ssr.test.js +37 -0
- package/dist/components/forms/form-retry/__tests__/storage.d.ts +26 -0
- package/dist/components/forms/form-retry/__tests__/storage.d.ts.map +1 -0
- package/dist/components/forms/form-retry/__tests__/storage.js +63 -0
- package/dist/components/forms/form-retry/__tests__/submission-key.test.js +236 -0
- package/dist/components/forms/form-retry/__tests__/submitted-draft.test.js +334 -0
- package/dist/components/forms/form-retry/content-digest.d.ts +54 -0
- package/dist/components/forms/form-retry/content-digest.d.ts.map +1 -0
- package/dist/components/forms/form-retry/content-digest.js +169 -0
- package/dist/components/forms/form-retry/controller.d.ts +214 -0
- package/dist/components/forms/form-retry/controller.d.ts.map +1 -0
- package/dist/components/forms/form-retry/controller.js +404 -0
- package/dist/components/forms/form-retry/form-draft.d.ts +68 -0
- package/dist/components/forms/form-retry/form-draft.d.ts.map +1 -0
- package/dist/components/forms/form-retry/form-draft.js +161 -0
- package/dist/components/forms/form-retry/index.d.ts +19 -0
- package/dist/components/forms/form-retry/index.d.ts.map +1 -0
- package/dist/components/forms/form-retry/index.js +16 -0
- package/dist/components/forms/form-retry/submission-key.d.ts +244 -0
- package/dist/components/forms/form-retry/submission-key.d.ts.map +1 -0
- package/dist/components/forms/form-retry/submission-key.js +342 -0
- package/dist/components/forms/form-retry/submitted-draft.d.ts +112 -0
- package/dist/components/forms/form-retry/submitted-draft.d.ts.map +1 -0
- package/dist/components/forms/form-retry/submitted-draft.js +270 -0
- package/dist/components/forms/form-retry/types.d.ts +84 -0
- package/dist/components/forms/form-retry/types.d.ts.map +1 -0
- package/dist/components/forms/form-retry/types.js +13 -0
- package/dist/components/forms/index.d.ts +7 -1
- package/dist/components/forms/index.d.ts.map +1 -1
- package/dist/components/forms/index.js +8 -0
- package/dist/components/forms/native-file-field.d.ts +94 -0
- package/dist/components/forms/native-file-field.d.ts.map +1 -0
- package/dist/components/forms/native-file-field.js +147 -0
- package/dist/components/forms/signature-pad-logic.d.ts +50 -0
- package/dist/components/forms/signature-pad-logic.d.ts.map +1 -0
- package/dist/components/forms/signature-pad-logic.js +48 -0
- package/dist/components/forms/types.d.ts +41 -0
- package/dist/components/forms/types.d.ts.map +1 -1
- package/dist/components/nav/FilterChips.svelte +8 -1
- package/dist/components/nav/FilterChips.svelte.d.ts +2 -0
- package/dist/components/nav/FilterChips.svelte.d.ts.map +1 -1
- package/dist/components/ui/Button.svelte +9 -2
- package/dist/components/ui/Button.svelte.d.ts +2 -0
- package/dist/components/ui/Button.svelte.d.ts.map +1 -1
- package/dist/i18n/strings.ui.d.ts +24 -0
- package/dist/i18n/strings.ui.d.ts.map +1 -1
- package/dist/i18n/strings.ui.js +26 -0
- package/dist/styles/tokens.css +2 -0
- package/dist/svelte/__tests__/playground.test.js +2 -1
- package/dist/svelte/playground/CapturePreview.svelte +56 -0
- package/dist/svelte/playground/CapturePreview.svelte.d.ts +4 -0
- package/dist/svelte/playground/CapturePreview.svelte.d.ts.map +1 -0
- package/dist/svelte/playground.d.ts.map +1 -1
- package/dist/svelte/playground.js +10 -0
- package/dist/themes/ThemeProvider.svelte +9 -0
- package/dist/themes/ThemeProvider.svelte.d.ts +2 -0
- package/dist/themes/ThemeProvider.svelte.d.ts.map +1 -1
- package/dist/themes/css-generator.d.ts.map +1 -1
- package/dist/themes/css-generator.js +3 -0
- package/dist/themes/styles/glass.css +4 -0
- package/dist/themes/styles/happyvertical.css +4 -0
- package/dist/themes/styles/material.css +4 -0
- package/dist/themes/styles/smrt.css +4 -0
- package/dist/themes/styles/studio.css +4 -0
- package/package.json +8 -2
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure SignaturePad input logic, no DOM (smrt#3290). Ported from teamworks-os
|
|
3
|
+
* `signature-pad-logic.test.ts`.
|
|
4
|
+
*/
|
|
5
|
+
import { describe, expect, it } from 'vitest';
|
|
6
|
+
import { isAcceptedPointerType, mapPointerToCanvasPoint, } from '../signature-pad-logic.js';
|
|
7
|
+
describe('isAcceptedPointerType', () => {
|
|
8
|
+
it('accepts only pen input when stylusOnly is true', () => {
|
|
9
|
+
expect(isAcceptedPointerType('pen', true)).toBe(true);
|
|
10
|
+
expect(isAcceptedPointerType('touch', true)).toBe(false);
|
|
11
|
+
expect(isAcceptedPointerType('mouse', true)).toBe(false);
|
|
12
|
+
});
|
|
13
|
+
it('accepts pen, touch, and mouse when stylusOnly is false', () => {
|
|
14
|
+
expect(isAcceptedPointerType('pen', false)).toBe(true);
|
|
15
|
+
expect(isAcceptedPointerType('touch', false)).toBe(true);
|
|
16
|
+
expect(isAcceptedPointerType('mouse', false)).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
it('rejects unknown and empty pointer types in both modes', () => {
|
|
19
|
+
for (const stylusOnly of [true, false]) {
|
|
20
|
+
expect(isAcceptedPointerType('unknown', stylusOnly)).toBe(false);
|
|
21
|
+
expect(isAcceptedPointerType('', stylusOnly)).toBe(false);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
describe('mapPointerToCanvasPoint', () => {
|
|
26
|
+
it('subtracts the rect offset unscaled when display and backing sizes match', () => {
|
|
27
|
+
const rect = { left: 10, top: 20, width: 600, height: 240 };
|
|
28
|
+
expect(mapPointerToCanvasPoint(110, 70, rect, 600, 240)).toEqual({
|
|
29
|
+
x: 100,
|
|
30
|
+
y: 50,
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
it('scales up when displayed smaller than the backing resolution', () => {
|
|
34
|
+
const rect = { left: 0, top: 0, width: 300, height: 120 };
|
|
35
|
+
expect(mapPointerToCanvasPoint(30, 12, rect, 600, 240)).toEqual({
|
|
36
|
+
x: 60,
|
|
37
|
+
y: 24,
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
it('scales down when displayed larger than the backing resolution', () => {
|
|
41
|
+
const rect = { left: 0, top: 0, width: 1200, height: 480 };
|
|
42
|
+
expect(mapPointerToCanvasPoint(1200, 480, rect, 600, 240)).toEqual({
|
|
43
|
+
x: 600,
|
|
44
|
+
y: 240,
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
it('falls back to 1:1 for a zero-size (not laid out) rect', () => {
|
|
48
|
+
const rect = { left: 5, top: 5, width: 0, height: 0 };
|
|
49
|
+
expect(mapPointerToCanvasPoint(15, 25, rect, 600, 240)).toEqual({
|
|
50
|
+
x: 10,
|
|
51
|
+
y: 20,
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import ThemeProvider from '../../../themes/ThemeProvider.svelte';
|
|
3
|
+
import DataTable from '../../data/DataTable.svelte';
|
|
4
|
+
import FilterChips from '../../nav/FilterChips.svelte';
|
|
5
|
+
import Button from '../../ui/Button.svelte';
|
|
6
|
+
import Checkbox from '../Checkbox.svelte';
|
|
7
|
+
import Input from '../Input.svelte';
|
|
8
|
+
import Radio from '../Radio.svelte';
|
|
9
|
+
import RadioGroup from '../RadioGroup.svelte';
|
|
10
|
+
import SegmentedControl from '../SegmentedControl.svelte';
|
|
11
|
+
import Select from '../Select.svelte';
|
|
12
|
+
import Switch from '../Switch.svelte';
|
|
13
|
+
import Textarea from '../Textarea.svelte';
|
|
14
|
+
|
|
15
|
+
let {
|
|
16
|
+
density = 'touch',
|
|
17
|
+
target = '48px',
|
|
18
|
+
}: { density?: 'comfortable' | 'touch'; target?: string } = $props();
|
|
19
|
+
</script>
|
|
20
|
+
<ThemeProvider {density} overrides={{ '--smrt-touch-target-min': target }} persist={false} inlineVariables>
|
|
21
|
+
<Input aria-label="Name" name="name" size={12} />
|
|
22
|
+
<Select aria-label="Choice"><option value="one">One</option></Select>
|
|
23
|
+
<Textarea aria-label="Notes" />
|
|
24
|
+
<Checkbox label="Approve" />
|
|
25
|
+
<Switch label="Enable" />
|
|
26
|
+
<RadioGroup name="radio" label="Radio"><Radio value="one" label="Option one" /></RadioGroup>
|
|
27
|
+
<Button size="sm">Save</Button>
|
|
28
|
+
<Button href="/details" size="sm">Details</Button>
|
|
29
|
+
<FilterChips size="sm" options={[{ value: 'one', label: 'One' }, { value: 'off', label: 'Disabled', disabled: true }]} selected="one" />
|
|
30
|
+
<SegmentedControl label="Segments" options={[{ value: 'one', label: 'One' }, { value: 'two', label: 'Two' }]} />
|
|
31
|
+
<DataTable data={[{ id: 'one', title: 'Item' }]} columns={[{ id: 'title', label: 'Title', sortable: true }]} sortable rowKey="id" />
|
|
32
|
+
<Input aria-label="Local touch" density="touch" />
|
|
33
|
+
<Input aria-label="Local comfortable" density="comfortable" />
|
|
34
|
+
</ThemeProvider>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
density?: 'comfortable' | 'touch';
|
|
3
|
+
target?: string;
|
|
4
|
+
};
|
|
5
|
+
declare const TouchDensity: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
6
|
+
type TouchDensity = ReturnType<typeof TouchDensity>;
|
|
7
|
+
export default TouchDensity;
|
|
8
|
+
//# sourceMappingURL=touch-density.fixture.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"touch-density.fixture.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/forms/__tests__/touch-density.fixture.svelte.ts"],"names":[],"mappings":"AAgBC,KAAK,gBAAgB,GAAI;IAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAsCjF,QAAA,MAAM,YAAY,sDAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/svelte';
|
|
2
|
+
import { createRawSnippet } from 'svelte';
|
|
3
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import DataTable from '../../data/DataTable.svelte';
|
|
5
|
+
import FilterChips from '../../nav/FilterChips.svelte';
|
|
6
|
+
import Button from '../../ui/Button.svelte';
|
|
7
|
+
import Checkbox from '../Checkbox.svelte';
|
|
8
|
+
import Input from '../Input.svelte';
|
|
9
|
+
import RadioGroup from '../RadioGroup.svelte';
|
|
10
|
+
import SegmentedControl from '../SegmentedControl.svelte';
|
|
11
|
+
import Select from '../Select.svelte';
|
|
12
|
+
import Switch from '../Switch.svelte';
|
|
13
|
+
import Textarea from '../Textarea.svelte';
|
|
14
|
+
import TouchDensityFixture from './touch-density.fixture.svelte';
|
|
15
|
+
describe('touch density', () => {
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
window.matchMedia = vi.fn().mockReturnValue({
|
|
18
|
+
matches: false,
|
|
19
|
+
addEventListener: vi.fn(),
|
|
20
|
+
removeEventListener: vi.fn(),
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
it.each([
|
|
24
|
+
['Input', Input, {}],
|
|
25
|
+
[
|
|
26
|
+
'RadioGroup',
|
|
27
|
+
RadioGroup,
|
|
28
|
+
{
|
|
29
|
+
name: 'choice',
|
|
30
|
+
children: createRawSnippet(() => ({
|
|
31
|
+
render: () => '<span>Options</span>',
|
|
32
|
+
})),
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
[
|
|
36
|
+
'Select',
|
|
37
|
+
Select,
|
|
38
|
+
{
|
|
39
|
+
children: createRawSnippet(() => ({
|
|
40
|
+
render: () => '<option value="one">One</option>',
|
|
41
|
+
})),
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
['Textarea', Textarea, {}],
|
|
45
|
+
['Checkbox', Checkbox, {}],
|
|
46
|
+
['Switch', Switch, {}],
|
|
47
|
+
['Button', Button, {}],
|
|
48
|
+
['FilterChips', FilterChips, { options: [], selected: '' }],
|
|
49
|
+
['SegmentedControl', SegmentedControl, { options: [], label: 'Segments' }],
|
|
50
|
+
['DataTable', DataTable, { columns: [], data: [] }],
|
|
51
|
+
])('%s consumes density on its target root', (_name, component, props) => {
|
|
52
|
+
const { container } = render(component, {
|
|
53
|
+
props: { ...props, density: 'touch' },
|
|
54
|
+
});
|
|
55
|
+
expect(container.querySelector('[data-density="touch"]')).toBeTruthy();
|
|
56
|
+
expect(container.querySelector('[density]')).toBeNull();
|
|
57
|
+
});
|
|
58
|
+
it('inherits global density while preserving native size and local overrides', () => {
|
|
59
|
+
const { container } = render(TouchDensityFixture);
|
|
60
|
+
expect(container.querySelector('.smrt-theme-root')).toHaveAttribute('style', expect.stringContaining('--smrt-control-target-min: var(--smrt-touch-target-min, 48px)'));
|
|
61
|
+
expect(screen.getByRole('textbox', { name: 'Name' })).toHaveAttribute('size', '12');
|
|
62
|
+
expect(screen.getByRole('textbox', { name: 'Name' })).not.toHaveAttribute('data-density');
|
|
63
|
+
expect(screen.getByRole('textbox', { name: 'Local comfortable' })).toHaveAttribute('data-density', 'comfortable');
|
|
64
|
+
expect(screen.getByRole('textbox', { name: 'Local touch' })).toHaveAttribute('data-density', 'touch');
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework-free `getUserMedia()` lifecycle for `CameraCapture.svelte`.
|
|
3
|
+
*
|
|
4
|
+
* Owns exactly one acquired `MediaStream`: requesting it, classifying a
|
|
5
|
+
* rejection into one of the states the component renders, and releasing every
|
|
6
|
+
* track it acquired. It never touches a `<video>` or `<canvas>`, so permission
|
|
7
|
+
* denied, no camera, unsupported browser and stream cleanup are unit-testable
|
|
8
|
+
* against a fake `MediaDevices` with no DOM at all. The component stays thin
|
|
9
|
+
* DOM glue around it.
|
|
10
|
+
*
|
|
11
|
+
* Guards one race a naive "await getUserMedia, then store the stream" misses:
|
|
12
|
+
* the user can dismiss the permission prompt late, or the component can be
|
|
13
|
+
* disabled or unmounted, while `getUserMedia()` is still pending. Storing that
|
|
14
|
+
* late result would leave the camera light on with nothing left to stop it.
|
|
15
|
+
* `stop()` and every new `start()` bump a generation counter; `start()`
|
|
16
|
+
* compares the generation it was issued under once the promise settles and
|
|
17
|
+
* discards a stale result, stopping any tracks it just acquired.
|
|
18
|
+
*
|
|
19
|
+
* Ported from teamworks-os (`camera-capture-session.ts` and
|
|
20
|
+
* `camera-capture-logic.ts`), smrt#3290.
|
|
21
|
+
*/
|
|
22
|
+
/** Camera facing mode requested from `getUserMedia()`. */
|
|
23
|
+
export type CameraFacingMode = 'environment' | 'user';
|
|
24
|
+
/** The failure states a camera request can surface. */
|
|
25
|
+
export type CameraCaptureErrorKind = 'permission-denied' | 'no-camera' | 'unsupported' | 'error';
|
|
26
|
+
/**
|
|
27
|
+
* Structural stand-in for `{ mediaDevices?: MediaDevices }`. lib.dom types
|
|
28
|
+
* `Navigator.mediaDevices` as always present, but a browser without camera
|
|
29
|
+
* support (or a page outside a secure context) leaves it `undefined`.
|
|
30
|
+
*/
|
|
31
|
+
export interface MaybeMediaDevicesHost {
|
|
32
|
+
mediaDevices?: {
|
|
33
|
+
getUserMedia?: unknown;
|
|
34
|
+
} | undefined;
|
|
35
|
+
}
|
|
36
|
+
/** The part of `MediaDevices` the session needs. */
|
|
37
|
+
export interface MediaDevicesLike {
|
|
38
|
+
getUserMedia(constraints: MediaStreamConstraints): Promise<MediaStream>;
|
|
39
|
+
}
|
|
40
|
+
/** Result of one `start()` call. `cancelled` means a `stop()` superseded it. */
|
|
41
|
+
export type CameraSessionResult = {
|
|
42
|
+
ok: true;
|
|
43
|
+
stream: MediaStream;
|
|
44
|
+
} | {
|
|
45
|
+
ok: false;
|
|
46
|
+
kind: CameraCaptureErrorKind | 'cancelled';
|
|
47
|
+
/** Diagnostic message from the rejection; not localized, not for display. */
|
|
48
|
+
message: string;
|
|
49
|
+
};
|
|
50
|
+
export interface CameraSessionDeps {
|
|
51
|
+
/**
|
|
52
|
+
* `navigator.mediaDevices`, resolved by the caller (`undefined` when the
|
|
53
|
+
* browser has none) so this module never reaches for a global itself.
|
|
54
|
+
*/
|
|
55
|
+
mediaDevices: MediaDevicesLike | undefined;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Whether this runtime exposes `getUserMedia` at all. An unsupported browser is
|
|
59
|
+
* never reported as "no camera": those states need different recovery copy.
|
|
60
|
+
*/
|
|
61
|
+
export declare function isCameraApiSupported(nav: MaybeMediaDevicesHost | undefined): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Maps a `getUserMedia()` rejection to a rendered state by `DOMException.name`
|
|
64
|
+
* (the cross-browser contract), never by message text.
|
|
65
|
+
*
|
|
66
|
+
* Deliberately does not gate on `instanceof Error`: a browser `DOMException`
|
|
67
|
+
* does not inherit from `Error` in shipping engines, so that check would send
|
|
68
|
+
* every real rejection to the generic state. Node's `DOMException` happening to
|
|
69
|
+
* extend `Error` is what hides that bug in local tests.
|
|
70
|
+
*/
|
|
71
|
+
export declare function classifyGetUserMediaError(error: unknown): CameraCaptureErrorKind;
|
|
72
|
+
/** Create a session that owns at most one live camera stream. */
|
|
73
|
+
export declare function createCameraSession(deps: CameraSessionDeps): {
|
|
74
|
+
start: (facingMode: CameraFacingMode) => Promise<CameraSessionResult>;
|
|
75
|
+
stop: () => void;
|
|
76
|
+
/** The live stream, or `null` when none is held. */
|
|
77
|
+
readonly stream: MediaStream | null;
|
|
78
|
+
};
|
|
79
|
+
/** A camera session created by {@link createCameraSession}. */
|
|
80
|
+
export type CameraSession = ReturnType<typeof createCameraSession>;
|
|
81
|
+
//# sourceMappingURL=camera-capture-session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"camera-capture-session.d.ts","sourceRoot":"","sources":["../../../src/components/forms/camera-capture-session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,0DAA0D;AAC1D,MAAM,MAAM,gBAAgB,GAAG,aAAa,GAAG,MAAM,CAAC;AAEtD,uDAAuD;AACvD,MAAM,MAAM,sBAAsB,GAC9B,mBAAmB,GACnB,WAAW,GACX,aAAa,GACb,OAAO,CAAC;AAEZ;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,YAAY,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,SAAS,CAAC;CACvD;AAED,oDAAoD;AACpD,MAAM,WAAW,gBAAgB;IAC/B,YAAY,CAAC,WAAW,EAAE,sBAAsB,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;CACzE;AAED,gFAAgF;AAChF,MAAM,MAAM,mBAAmB,GAC3B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,WAAW,CAAA;CAAE,GACjC;IACE,EAAE,EAAE,KAAK,CAAC;IACV,IAAI,EAAE,sBAAsB,GAAG,WAAW,CAAC;IAC3C,6EAA6E;IAC7E,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEN,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,YAAY,EAAE,gBAAgB,GAAG,SAAS,CAAC;CAC5C;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,qBAAqB,GAAG,SAAS,GACrC,OAAO,CAET;AAUD;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,OAAO,GACb,sBAAsB,CAaxB;AAMD,iEAAiE;AACjE,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,iBAAiB;wBAgB3C,gBAAgB,KAC3B,OAAO,CAAC,mBAAmB,CAAC;gBAuCd,IAAI;IASnB,oDAAoD;qBACtC,WAAW,GAAG,IAAI;EAInC;AAED,+DAA+D;AAC/D,MAAM,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework-free `getUserMedia()` lifecycle for `CameraCapture.svelte`.
|
|
3
|
+
*
|
|
4
|
+
* Owns exactly one acquired `MediaStream`: requesting it, classifying a
|
|
5
|
+
* rejection into one of the states the component renders, and releasing every
|
|
6
|
+
* track it acquired. It never touches a `<video>` or `<canvas>`, so permission
|
|
7
|
+
* denied, no camera, unsupported browser and stream cleanup are unit-testable
|
|
8
|
+
* against a fake `MediaDevices` with no DOM at all. The component stays thin
|
|
9
|
+
* DOM glue around it.
|
|
10
|
+
*
|
|
11
|
+
* Guards one race a naive "await getUserMedia, then store the stream" misses:
|
|
12
|
+
* the user can dismiss the permission prompt late, or the component can be
|
|
13
|
+
* disabled or unmounted, while `getUserMedia()` is still pending. Storing that
|
|
14
|
+
* late result would leave the camera light on with nothing left to stop it.
|
|
15
|
+
* `stop()` and every new `start()` bump a generation counter; `start()`
|
|
16
|
+
* compares the generation it was issued under once the promise settles and
|
|
17
|
+
* discards a stale result, stopping any tracks it just acquired.
|
|
18
|
+
*
|
|
19
|
+
* Ported from teamworks-os (`camera-capture-session.ts` and
|
|
20
|
+
* `camera-capture-logic.ts`), smrt#3290.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Whether this runtime exposes `getUserMedia` at all. An unsupported browser is
|
|
24
|
+
* never reported as "no camera": those states need different recovery copy.
|
|
25
|
+
*/
|
|
26
|
+
export function isCameraApiSupported(nav) {
|
|
27
|
+
return typeof nav?.mediaDevices?.getUserMedia === 'function';
|
|
28
|
+
}
|
|
29
|
+
function readStringField(value, field) {
|
|
30
|
+
if (typeof value !== 'object' || value === null || !(field in value)) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
const read = value[field];
|
|
34
|
+
return typeof read === 'string' ? read : undefined;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Maps a `getUserMedia()` rejection to a rendered state by `DOMException.name`
|
|
38
|
+
* (the cross-browser contract), never by message text.
|
|
39
|
+
*
|
|
40
|
+
* Deliberately does not gate on `instanceof Error`: a browser `DOMException`
|
|
41
|
+
* does not inherit from `Error` in shipping engines, so that check would send
|
|
42
|
+
* every real rejection to the generic state. Node's `DOMException` happening to
|
|
43
|
+
* extend `Error` is what hides that bug in local tests.
|
|
44
|
+
*/
|
|
45
|
+
export function classifyGetUserMediaError(error) {
|
|
46
|
+
switch (readStringField(error, 'name')) {
|
|
47
|
+
case 'NotAllowedError':
|
|
48
|
+
case 'PermissionDeniedError':
|
|
49
|
+
case 'SecurityError':
|
|
50
|
+
return 'permission-denied';
|
|
51
|
+
case 'NotFoundError':
|
|
52
|
+
case 'DevicesNotFoundError':
|
|
53
|
+
case 'OverconstrainedError':
|
|
54
|
+
return 'no-camera';
|
|
55
|
+
default:
|
|
56
|
+
return 'error';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const UNSUPPORTED_MESSAGE = 'getUserMedia is not available in this runtime.';
|
|
60
|
+
const GENERIC_ERROR_MESSAGE = 'Could not access the camera.';
|
|
61
|
+
const CANCELLED_MESSAGE = 'Camera request was superseded by stop().';
|
|
62
|
+
/** Create a session that owns at most one live camera stream. */
|
|
63
|
+
export function createCameraSession(deps) {
|
|
64
|
+
let stream = null;
|
|
65
|
+
let generation = 0;
|
|
66
|
+
function releaseTracks(media) {
|
|
67
|
+
for (const track of media.getTracks())
|
|
68
|
+
track.stop();
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Request the camera. `facingMode` is a parameter, not a construction-time
|
|
72
|
+
* dependency, so a caller re-reading a reactive prop always requests the
|
|
73
|
+
* current value. A new `start()` supersedes the held stream (released now)
|
|
74
|
+
* and any request still pending (released when it settles), so overlapping
|
|
75
|
+
* calls can never leak a stream.
|
|
76
|
+
*/
|
|
77
|
+
async function start(facingMode) {
|
|
78
|
+
if (stream) {
|
|
79
|
+
releaseTracks(stream);
|
|
80
|
+
stream = null;
|
|
81
|
+
}
|
|
82
|
+
generation += 1;
|
|
83
|
+
const requestGeneration = generation;
|
|
84
|
+
const mediaDevices = deps.mediaDevices;
|
|
85
|
+
if (!mediaDevices || !isCameraApiSupported({ mediaDevices })) {
|
|
86
|
+
return { ok: false, kind: 'unsupported', message: UNSUPPORTED_MESSAGE };
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
const media = await mediaDevices.getUserMedia({
|
|
90
|
+
video: { facingMode },
|
|
91
|
+
audio: false,
|
|
92
|
+
});
|
|
93
|
+
if (requestGeneration !== generation) {
|
|
94
|
+
releaseTracks(media);
|
|
95
|
+
return { ok: false, kind: 'cancelled', message: CANCELLED_MESSAGE };
|
|
96
|
+
}
|
|
97
|
+
stream = media;
|
|
98
|
+
return { ok: true, stream: media };
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
if (requestGeneration !== generation) {
|
|
102
|
+
return { ok: false, kind: 'cancelled', message: CANCELLED_MESSAGE };
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
ok: false,
|
|
106
|
+
kind: classifyGetUserMediaError(error),
|
|
107
|
+
message: readStringField(error, 'message') ?? GENERIC_ERROR_MESSAGE,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Stop every track of the held stream, forget it, and invalidate any
|
|
113
|
+
* `start()` still in flight. Idempotent: a second call (for example from a
|
|
114
|
+
* `disabled` toggle and then unmount) never stops a track twice.
|
|
115
|
+
*/
|
|
116
|
+
function stop() {
|
|
117
|
+
generation += 1;
|
|
118
|
+
if (stream)
|
|
119
|
+
releaseTracks(stream);
|
|
120
|
+
stream = null;
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
start,
|
|
124
|
+
stop,
|
|
125
|
+
/** The live stream, or `null` when none is held. */
|
|
126
|
+
get stream() {
|
|
127
|
+
return stream;
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* smrt-ui is the dependency leaf, and the form-retry entry is published
|
|
3
|
+
* Svelte-free (#3291): its sources must never import `@sveltejs/kit` or
|
|
4
|
+
* `svelte`, so plain SvelteKit apps, smrt-svelte and plain Node can load it.
|
|
5
|
+
*/
|
|
6
|
+
import { readdirSync, readFileSync } from 'node:fs';
|
|
7
|
+
import { dirname, join } from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { describe, expect, it } from 'vitest';
|
|
10
|
+
const root = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
11
|
+
describe('form-retry import boundary', () => {
|
|
12
|
+
it('imports neither @sveltejs/kit nor svelte', () => {
|
|
13
|
+
const sources = readdirSync(root).filter((name) => name.endsWith('.ts'));
|
|
14
|
+
expect(sources.length).toBeGreaterThan(0);
|
|
15
|
+
for (const name of sources) {
|
|
16
|
+
const text = readFileSync(join(root, name), 'utf8');
|
|
17
|
+
const imports = [...text.matchAll(/from\s+['"]([^'"]+)['"]/g)].map((match) => match[1]);
|
|
18
|
+
for (const specifier of imports) {
|
|
19
|
+
expect(specifier, `${name} imports ${specifier}`).toMatch(/^\.\//);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The browser port of `runOnce()`'s content digest (#3291) must agree with
|
|
3
|
+
* core's `digestRunOnceContent()` byte for byte. These vectors were computed
|
|
4
|
+
* with `@happyvertical/smrt-core`'s implementation; the same table lives in
|
|
5
|
+
* `packages/core/src/__tests__/issue-3291-digest-vectors.test.ts`, so a change
|
|
6
|
+
* to either side fails the other.
|
|
7
|
+
*/
|
|
8
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
9
|
+
import { digestSubmissionContent } from '../content-digest.js';
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
vi.unstubAllGlobals();
|
|
12
|
+
});
|
|
13
|
+
function orders() {
|
|
14
|
+
const fd1 = new FormData();
|
|
15
|
+
fd1.append('supplier', 'acme');
|
|
16
|
+
fd1.append('qty', '2');
|
|
17
|
+
fd1.append('qty', '3');
|
|
18
|
+
fd1.append('note', 'héllo ✓');
|
|
19
|
+
const fd2 = new FormData();
|
|
20
|
+
fd2.append('supplier', 'acme');
|
|
21
|
+
fd2.append('qty', '3');
|
|
22
|
+
fd2.append('qty', '2');
|
|
23
|
+
fd2.append('note', 'héllo ✓');
|
|
24
|
+
return { fd1, fd2 };
|
|
25
|
+
}
|
|
26
|
+
describe('digestSubmissionContent', () => {
|
|
27
|
+
it('matches core for plain JSON, toJSON values and the NUL-key escape', async () => {
|
|
28
|
+
expect(await digestSubmissionContent({
|
|
29
|
+
b: 1,
|
|
30
|
+
a: [true, null, 'x'],
|
|
31
|
+
c: { z: 1.5, y: 'é' },
|
|
32
|
+
})).toBe('d9ae830ae60cc25876ef0bf6a94b95396a9feb02d8104d0ccca51dbed2b3483d');
|
|
33
|
+
expect(await digestSubmissionContent({
|
|
34
|
+
when: new Date('2026-10-01T12:00:00.000Z'),
|
|
35
|
+
skip: undefined,
|
|
36
|
+
arr: [undefined, 1],
|
|
37
|
+
})).toBe('d5333d325d8f37a2421db409a9532616e23153c6c4312a51e42b645c045b7a00');
|
|
38
|
+
expect(await digestSubmissionContent({ '\u0000FormData': [['a', 'b']] })).toBe('0d345acb479395e8c56d2a4abf52124dd22714777e38accfbe92df2c82bf7174');
|
|
39
|
+
expect(await digestSubmissionContent(undefined)).toBe('74234e98afe7498fb5daf1f36ac2d78acc339464f950703b8c019892f982b90b');
|
|
40
|
+
});
|
|
41
|
+
it('matches core for FormData: ordered, repeated fields significant', async () => {
|
|
42
|
+
const { fd1, fd2 } = orders();
|
|
43
|
+
expect(await digestSubmissionContent(fd1)).toBe('67fc035f5a3f342757e9579c1520b2dd34ae91b921f5e5eab05e56b02553a1d5');
|
|
44
|
+
expect(await digestSubmissionContent(fd2)).toBe('bc3d36f0e0441e887ffa641432f87a60cc3de592cff32a29cf35f5cb6fc0cd7d');
|
|
45
|
+
expect(await digestSubmissionContent(new FormData())).toBe('c926873e5af4105c2192c4a1a4e4cd87665c0869cf24c025160416e1c167faec');
|
|
46
|
+
expect(await digestSubmissionContent({ form: fd1, extra: 'x' })).toBe('2e89c1d455e7007af1f0826465c7e417a5c07900fc862293be42e7ae5928d00c');
|
|
47
|
+
});
|
|
48
|
+
it('digests a File by name, type and size — never its bytes', async () => {
|
|
49
|
+
const withFile = (bytes) => {
|
|
50
|
+
const data = new FormData();
|
|
51
|
+
data.append('title', 'Report');
|
|
52
|
+
data.append('photo', new File([bytes], 'a.jpg', { type: 'image/jpeg' }));
|
|
53
|
+
data.append('empty', new File([], '', { type: 'application/octet-stream' }));
|
|
54
|
+
return data;
|
|
55
|
+
};
|
|
56
|
+
expect(await digestSubmissionContent(withFile('abc'))).toBe('f16bbad3bdfd6fe4dc441855ecb2260e3fc14ed05d228deb50b5c0860c8f7208');
|
|
57
|
+
// Same name, type and size, different bytes: the same digest. Re-choosing
|
|
58
|
+
// the same file after a reload is therefore the same claim.
|
|
59
|
+
expect(await digestSubmissionContent(withFile('xyz'))).toBe(await digestSubmissionContent(withFile('abc')));
|
|
60
|
+
});
|
|
61
|
+
it('rejects values JSON would misrepresent instead of hashing them as {}', async () => {
|
|
62
|
+
await expect(digestSubmissionContent(new Map([['a', 1]]))).rejects.toThrow(TypeError);
|
|
63
|
+
await expect(digestSubmissionContent({ s: new Set([1]) })).rejects.toThrow(/Set/);
|
|
64
|
+
await expect(digestSubmissionContent({ n: 1n })).rejects.toThrow(/bigint/);
|
|
65
|
+
});
|
|
66
|
+
it('explains a missing Web Crypto (insecure context)', async () => {
|
|
67
|
+
vi.stubGlobal('crypto', {});
|
|
68
|
+
await expect(digestSubmissionContent({ a: 1 })).rejects.toThrow(/secure context/);
|
|
69
|
+
});
|
|
70
|
+
});
|