srcdev-nuxt-components 9.4.9 → 9.4.11
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/.claude/commands/migrate-component.md +38 -6
- package/.claude/component-ledger/audit.json +1 -1
- package/.claude/component-ledger/build.mjs +26 -0
- package/.claude/component-ledger/output.html +8 -3
- package/.claude/component-ledger/template.html +7 -2
- package/.claude/skills/component-dynamic-slots.md +1 -1
- package/.claude/skills/components/alert-mask-core.md +84 -0
- package/.claude/skills/components/canvas-switcher.md +1 -1
- package/.claude/skills/components/carousel-flip.md +112 -168
- package/.claude/skills/components/container-glow.md +123 -0
- package/.claude/skills/components/dashboard-quad-grid.md +98 -0
- package/.claude/skills/components/dashboard-stats-grid.md +103 -0
- package/.claude/skills/components/display-theme-switch.md +60 -0
- package/.claude/skills/components/glowing-border.md +66 -0
- package/.claude/skills/components/input-number-core.md +214 -0
- package/.claude/skills/components/input-range-core.md +142 -0
- package/.claude/skills/components/input-select-core.md +151 -0
- package/.claude/skills/components/input-text-core.md +16 -0
- package/.claude/skills/components/input-textarea-core.md +154 -0
- package/.claude/skills/components/section-parallax.md +32 -10
- package/.claude/skills/components/select-menu.md +195 -0
- package/.claude/skills/components/toggle-switch-core.md +150 -0
- package/.claude/skills/components/ui-block-decorated.md +56 -0
- package/.claude/skills/components/wipe-away-vertical.md +98 -0
- package/.claude/skills/icon-sets.md +1 -1
- package/.claude/skills/index.md +15 -1
- package/.vscode/css-custom-data.json +38 -0
- package/.vscode/settings.json +3 -0
- package/.vscode/srcdev-component-alert-mask-core.code-snippets +31 -0
- package/.vscode/srcdev-component-carousel-flip.code-snippets +41 -0
- package/.vscode/srcdev-component-container-glow.code-snippets +33 -0
- package/.vscode/srcdev-component-dashboard-quad-grid.code-snippets +36 -0
- package/.vscode/srcdev-component-dashboard-stats-grid.code-snippets +35 -0
- package/.vscode/srcdev-component-display-theme-switch.code-snippets +27 -0
- package/.vscode/srcdev-component-glowing-border.code-snippets +23 -0
- package/.vscode/srcdev-component-input-number.code-snippets +65 -0
- package/.vscode/srcdev-component-input-range.code-snippets +85 -0
- package/.vscode/srcdev-component-input-select.code-snippets +42 -0
- package/.vscode/srcdev-component-input-textarea.code-snippets +40 -0
- package/.vscode/srcdev-component-section-parallax.code-snippets +27 -0
- package/.vscode/srcdev-component-select-menu.code-snippets +65 -0
- package/.vscode/srcdev-component-toggle-switch.code-snippets +49 -0
- package/.vscode/srcdev-component-ui-block-decorated.code-snippets +24 -0
- package/.vscode/srcdev-component-wipe-away-vertical.code-snippets +25 -0
- package/README.md +18 -37
- package/app/components/{alert-mask → 01.atoms/alert-mask}/AlertMaskCore.vue +16 -17
- package/app/components/01.atoms/alert-mask/CONSUMER-STYLING.md +46 -0
- package/app/components/01.atoms/alert-mask/stories/AlertMaskCore.stories.ts +79 -0
- package/app/components/{alert-mask → 01.atoms/alert-mask}/tests/AlertMaskCore.spec.ts +10 -10
- package/app/components/01.atoms/animations/container-glow/CONSUMER-STYLING.md +86 -0
- package/app/components/01.atoms/animations/container-glow/ContainerGlow.vue +299 -0
- package/app/components/01.atoms/animations/container-glow/stories/ContainerGlow.stories.ts +175 -0
- package/app/components/01.atoms/animations/container-glow/tests/ContainerGlow.spec.ts +131 -0
- package/app/components/01.atoms/animations/container-glow/tests/__snapshots__/ContainerGlow.spec.ts.snap +14 -0
- package/app/components/01.atoms/animations/glowing-border/CONSUMER-STYLING.md +61 -0
- package/app/components/01.atoms/animations/glowing-border/GlowingBorder.vue +132 -0
- package/app/components/01.atoms/animations/glowing-border/stories/GlowingBorder.stories.ts +165 -0
- package/app/components/01.atoms/animations/glowing-border/tests/GlowingBorder.spec.ts +94 -0
- package/app/components/01.atoms/animations/glowing-border/tests/__snapshots__/GlowingBorder.spec.ts.snap +3 -0
- package/app/components/01.atoms/animations/marquee-scroller/CONSUMER-STYLING.md +1 -1
- package/app/components/01.atoms/animations/marquee-scroller/MarqueeScroller.vue +1 -1
- package/app/components/01.atoms/animations/marquee-scroller/stories/MarqueeScroller.stories.ts +1 -1
- package/app/components/01.atoms/animations/section-parallax/CONSUMER-STYLING.md +44 -0
- package/app/components/{parallax → 01.atoms/animations/section-parallax}/SectionParallax.vue +11 -10
- package/app/components/01.atoms/animations/section-parallax/stories/SectionParallax.stories.ts +98 -0
- package/app/components/01.atoms/animations/section-parallax/tests/SectionParallax.spec.ts +102 -0
- package/app/components/01.atoms/animations/section-parallax/tests/__snapshots__/SectionParallax.spec.ts.snap +9 -0
- package/app/components/01.atoms/animations/view-timeline/CONSUMER-STYLING.md +70 -0
- package/app/components/01.atoms/animations/view-timeline/WipeAwayVertical.vue +181 -0
- package/app/components/01.atoms/animations/view-timeline/stories/WipeAwayVertical.stories.ts +269 -0
- package/app/components/01.atoms/animations/view-timeline/tests/WipeAwayVertical.spec.ts +127 -0
- package/app/components/01.atoms/canvas-switcher/CONSUMER-STYLING.md +3 -3
- package/app/components/01.atoms/canvas-switcher/CanvasSwitcher.vue +2 -2
- package/app/components/01.atoms/canvas-switcher/stories/CanvasSwitcher.stories.ts +1 -1
- package/app/components/01.atoms/card/CardCore.vue +4 -4
- package/app/components/01.atoms/clipped-panel/CONSUMER-STYLING.md +2 -2
- package/app/components/01.atoms/clipped-panel/ClippedPanel.vue +2 -2
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/CONSUMER-STYLING.md +42 -0
- package/app/components/{layout-grids/LayoutGridA.vue → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/DashboardQuadGrid.vue} +23 -13
- package/app/components/{layout-grids/stories/LayoutGridA.stories.ts → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/stories/DashboardQuadGrid.stories.ts} +151 -23
- package/app/components/{layout-grids/tests/LayoutGridA.spec.ts → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/tests/DashboardQuadGrid.spec.ts} +25 -25
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/CONSUMER-STYLING.md +41 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/DashboardStatsGrid.vue +133 -0
- package/app/components/{layout-grids/stories/LayoutGridB.stories.ts → 01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/stories/DashboardStatsGrid.stories.ts} +24 -24
- package/app/components/{layout-grids/tests/LayoutGridB.spec.ts → 01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/tests/DashboardStatsGrid.spec.ts} +23 -23
- package/app/components/01.atoms/display-tooltip/CONSUMER-STYLING.md +8 -8
- package/app/components/01.atoms/display-tooltip/DisplayTooltip.vue +8 -8
- package/app/components/01.atoms/pop-over/CONSUMER-STYLING.md +6 -6
- package/app/components/01.atoms/pop-over/PopOver.vue +6 -6
- package/app/components/01.atoms/ui-block-decorated/CONSUMER-STYLING.md +39 -0
- package/app/components/01.atoms/ui-block-decorated/UiBlockDecorated.vue +141 -0
- package/app/components/01.atoms/ui-block-decorated/stories/UiBlockDecorated.stories.ts +148 -0
- package/app/components/01.atoms/ui-block-decorated/tests/UiBlockDecorated.spec.ts +80 -0
- package/app/components/01.atoms/ui-block-decorated/tests/__snapshots__/UiBlockDecorated.spec.ts.snap +3 -0
- package/app/components/02.molecules/alert-masked-content/tests/AlertMaskedContent.spec.ts +6 -6
- package/app/components/02.molecules/display-theme-switch/CONSUMER-STYLING.md +34 -0
- package/app/components/02.molecules/display-theme-switch/DisplayThemeSwitch.vue +102 -0
- package/app/components/02.molecules/display-theme-switch/stories/DisplayThemeSwitch.stories.ts +115 -0
- package/app/components/02.molecules/display-theme-switch/tests/DisplayThemeSwitch.spec.ts +86 -0
- package/app/components/02.molecules/navigation/deep-expanding-menu/CONSUMER-STYLING.md +1 -1
- package/app/components/02.molecules/navigation/deep-expanding-menu/DeepExpandingMenu.vue +1 -1
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/CONSUMER-STYLING.md +1 -1
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/DeepExpandingMenuClassic.vue +1 -1
- package/app/components/02.molecules/select-menu/CONSUMER-STYLING.md +168 -0
- package/app/components/02.molecules/select-menu/SelectMenu.vue +384 -0
- package/app/components/02.molecules/select-menu/stories/SelectMenu.stories.ts +359 -0
- package/app/components/02.molecules/select-menu/tests/SelectMenu.spec.ts +476 -0
- package/app/components/02.molecules/select-menu/tests/__snapshots__/SelectMenu.spec.ts.snap +42 -0
- package/app/components/03.organisms/image-galleries/carousel-flip/CONSUMER-STYLING.md +46 -0
- package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/CarouselFlip.vue +63 -42
- package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/stories/CarouselFlip.stories.ts +114 -29
- package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/tests/CarouselFlip.spec.ts +80 -0
- package/app/components/05.forms/form-field/FormField.vue +1 -1
- package/app/components/05.forms/form-wrapper/FormWrapper.vue +1 -1
- package/app/components/05.forms/input-button/CONSUMER-STYLING.md +2 -7
- package/app/components/05.forms/input-button/InputButtonCore.vue +1 -5
- package/app/components/05.forms/input-number/CONSUMER-STYLING.md +26 -6
- package/app/components/05.forms/input-number/InputNumberCore.vue +125 -85
- package/app/components/05.forms/input-number/stories/InputNumberCore.stories.ts +92 -0
- package/app/components/05.forms/input-number/tests/InputNumberCore.spec.ts +119 -0
- package/app/components/05.forms/input-number/variants/InputNumberDefault.vue +30 -22
- package/app/components/05.forms/input-number/variants/stories/InputNumberDefault.stories.ts +123 -0
- package/app/components/05.forms/input-number/variants/tests/InputNumberDefault.spec.ts +143 -0
- package/app/components/05.forms/input-range/CONSUMER-STYLING.md +45 -0
- package/app/components/05.forms/input-range/InputRangeCore.vue +79 -143
- package/app/components/05.forms/input-range/stories/InputRangeCore.stories.ts +126 -0
- package/app/components/05.forms/input-range/tests/InputRangeCore.spec.ts +124 -0
- package/app/components/05.forms/input-range/variants/InputRangeDefault.vue +9 -32
- package/app/components/05.forms/input-range/variants/stories/InputRangeDefault.stories.ts +108 -0
- package/app/components/05.forms/input-range/variants/tests/InputRangeDefault.spec.ts +140 -0
- package/app/components/05.forms/input-select/InputSelectCore.vue +23 -30
- package/app/components/05.forms/input-select/stories/InputSelectCore.stories.ts +2 -2
- package/app/components/05.forms/input-select/stories/InputSelectWithLabel.stories.ts +115 -0
- package/app/components/05.forms/input-select/tests/InputSelectCore.spec.ts +129 -0
- package/app/components/05.forms/input-select/variants/InputSelectWithLabel.vue +1 -1
- package/app/components/05.forms/input-select/variants/tests/InputSelectWithLabel.spec.ts +87 -0
- package/app/components/05.forms/input-text/tests/InputTextAsNumberWithLabel.spec.ts +14 -0
- package/app/components/05.forms/input-text/variants/InputTextAsNumberWithLabel.vue +1 -1
- package/app/components/05.forms/input-textarea/CONSUMER-STYLING.md +55 -0
- package/app/components/05.forms/input-textarea/InputTextareaCore.vue +24 -16
- package/app/components/05.forms/input-textarea/stories/InputTextareaWithLabel.stories.ts +1 -1
- package/app/components/05.forms/input-textarea/tests/InputTextareaCore.spec.ts +119 -0
- package/app/components/05.forms/input-textarea/variants/InputTextareaWithLabel.vue +3 -13
- package/app/components/05.forms/input-textarea/variants/tests/InputTextareaWithLabel.spec.ts +93 -0
- package/app/components/05.forms/patterns/stories/MigratedFieldsForm.stories.ts +220 -0
- package/app/components/05.forms/toggle-switch/CONSUMER-STYLING.md +29 -0
- package/app/components/05.forms/toggle-switch/ToggleSwitchCore.vue +120 -119
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchCore.stories.ts +1 -1
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabel.stories.ts +1 -1
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabelInline.stories.ts +2 -2
- package/app/components/05.forms/toggle-switch/tests/ToggleSwitchCore.spec.ts +101 -0
- package/app/components/05.forms/toggle-switch/variants/ToggleSwitchWithLabel.vue +3 -5
- package/app/components/05.forms/toggle-switch/variants/ToggleSwitchWithLabelInline.vue +1 -2
- package/app/components/05.forms/toggle-switch/variants/tests/ToggleSwitchWithLabel.spec.ts +77 -0
- package/app/components/05.forms/toggle-switch/variants/tests/ToggleSwitchWithLabelInline.spec.ts +66 -0
- package/app/components/05.forms/triple-toggle-switch/CONSUMER-STYLING.md +20 -6
- package/app/components/05.forms/triple-toggle-switch/TripleToggleSwitchCore.vue +12 -10
- package/app/types/components/container-glow.d.ts +8 -0
- package/app/types/components/index.ts +2 -1
- package/app/types/components/select-menu.d.ts +5 -0
- package/package.json +3 -2
- package/.vscode/srcdev-component-toggle-switch-core.code-snippets +0 -13
- package/app/components/05.forms/input-range-fancy/InputRangeFancyCore.vue +0 -426
- package/app/components/05.forms/input-range-fancy/InputRangeFancyWithLabel.vue +0 -94
- package/app/components/carousels/CarouselBasic.vue +0 -317
- package/app/components/carousels/CarouselInfinite.vue +0 -358
- package/app/components/container-glow/ContainerGlowCore.vue +0 -296
- package/app/components/container-glow/stories/ContainerGlowCore.stories.ts +0 -337
- package/app/components/display-theme-switch/DisplayThemeSwitch.vue +0 -189
- package/app/components/display-theme-switch/stories/DisplayThemeSwitch.stories.ts +0 -154
- package/app/components/glowing-border/GlowingBorder.vue +0 -142
- package/app/components/glowing-border/stories/GlowingBorder.stories.ts +0 -141
- package/app/components/layout-grids/LayoutGridB.vue +0 -134
- package/app/components/test-storybook/TestStorybook.vue +0 -49
- package/app/components/test-storybook/stories/TestStorybook.stories.ts +0 -28
- package/app/components/ui-block-decorated/UiBlockDecorated.vue +0 -167
- package/app/components/view-timeline/WipeAwayVertical.vue +0 -180
- package/app/types/components/carousel-basic.d.ts +0 -19
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { Meta, StoryFn } from "@nuxtjs/storybook";
|
|
2
|
+
import StorybookComponent from "../InputRangeDefault.vue";
|
|
3
|
+
import type { FormUiTheme } from "~/types/forms/types.forms.d";
|
|
4
|
+
|
|
5
|
+
interface InputRangeDefaultStoryArgs {
|
|
6
|
+
modelValue: number;
|
|
7
|
+
name: string;
|
|
8
|
+
label: string;
|
|
9
|
+
min: number;
|
|
10
|
+
max: number;
|
|
11
|
+
step: number;
|
|
12
|
+
errorMessage: string;
|
|
13
|
+
fieldHasError: boolean;
|
|
14
|
+
required: boolean;
|
|
15
|
+
theme: FormUiTheme;
|
|
16
|
+
stepDownLabel: string;
|
|
17
|
+
stepUpLabel: string;
|
|
18
|
+
useStepButtons: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default {
|
|
22
|
+
title: "Components/Forms/Input Range/InputRangeDefault",
|
|
23
|
+
component: StorybookComponent,
|
|
24
|
+
argTypes: {
|
|
25
|
+
modelValue: { control: "number", table: { category: "Model" } },
|
|
26
|
+
name: { control: "text", table: { category: "Basic" } },
|
|
27
|
+
label: { control: "text", table: { category: "Basic" } },
|
|
28
|
+
min: { control: "number", table: { category: "Basic" } },
|
|
29
|
+
max: { control: "number", table: { category: "Basic" } },
|
|
30
|
+
step: { control: "number", table: { category: "Basic" } },
|
|
31
|
+
errorMessage: { control: "text", table: { category: "Validation" } },
|
|
32
|
+
fieldHasError: { control: "boolean", table: { category: "Validation" } },
|
|
33
|
+
required: { control: "boolean", table: { category: "Validation" } },
|
|
34
|
+
theme: {
|
|
35
|
+
control: { type: "select" },
|
|
36
|
+
options: ["default", "success", "error", "warning"],
|
|
37
|
+
table: { category: "Styling" },
|
|
38
|
+
},
|
|
39
|
+
stepDownLabel: { control: "text", description: "Localisation override for the step-down button", table: { category: "Accessibility" } },
|
|
40
|
+
stepUpLabel: { control: "text", description: "Localisation override for the step-up button", table: { category: "Accessibility" } },
|
|
41
|
+
useStepButtons: { control: "boolean", description: "Render +/- step buttons via left/right slots", table: { category: "Slots" } },
|
|
42
|
+
},
|
|
43
|
+
args: {
|
|
44
|
+
modelValue: 50,
|
|
45
|
+
name: "priceRange",
|
|
46
|
+
label: "Price range",
|
|
47
|
+
min: 0,
|
|
48
|
+
max: 100,
|
|
49
|
+
step: 1,
|
|
50
|
+
errorMessage: "",
|
|
51
|
+
fieldHasError: false,
|
|
52
|
+
required: false,
|
|
53
|
+
theme: "default",
|
|
54
|
+
stepDownLabel: "Step down",
|
|
55
|
+
stepUpLabel: "Step up",
|
|
56
|
+
useStepButtons: false,
|
|
57
|
+
},
|
|
58
|
+
} as Meta<InputRangeDefaultStoryArgs>;
|
|
59
|
+
|
|
60
|
+
const Template: StoryFn<InputRangeDefaultStoryArgs> = (args) => ({
|
|
61
|
+
components: { StorybookComponent },
|
|
62
|
+
setup() {
|
|
63
|
+
return { args };
|
|
64
|
+
},
|
|
65
|
+
template: `
|
|
66
|
+
<div style="margin: 36px; max-width: 320px;">
|
|
67
|
+
<StorybookComponent
|
|
68
|
+
v-model="args.modelValue"
|
|
69
|
+
:name="args.name"
|
|
70
|
+
:label="args.label"
|
|
71
|
+
:min="args.min"
|
|
72
|
+
:max="args.max"
|
|
73
|
+
:step="args.step"
|
|
74
|
+
:error-message="args.errorMessage"
|
|
75
|
+
:field-has-error="args.fieldHasError"
|
|
76
|
+
:required="args.required"
|
|
77
|
+
:theme="args.theme"
|
|
78
|
+
:step-down-label="args.stepDownLabel"
|
|
79
|
+
:step-up-label="args.stepUpLabel"
|
|
80
|
+
>
|
|
81
|
+
<template v-if="args.useStepButtons" #left><span>−</span></template>
|
|
82
|
+
<template v-if="args.useStepButtons" #right><span>+</span></template>
|
|
83
|
+
</StorybookComponent>
|
|
84
|
+
</div>
|
|
85
|
+
`,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
export const Default = Template.bind({});
|
|
89
|
+
|
|
90
|
+
export const WithStepButtons = Template.bind({});
|
|
91
|
+
WithStepButtons.args = { useStepButtons: true };
|
|
92
|
+
|
|
93
|
+
export const WithError = Template.bind({});
|
|
94
|
+
WithError.args = {
|
|
95
|
+
fieldHasError: true,
|
|
96
|
+
errorMessage: "Please choose a value within the allowed range",
|
|
97
|
+
theme: "error",
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const Required = Template.bind({});
|
|
101
|
+
Required.args = { required: true };
|
|
102
|
+
|
|
103
|
+
export const LocalisedLabels = Template.bind({});
|
|
104
|
+
LocalisedLabels.args = {
|
|
105
|
+
useStepButtons: true,
|
|
106
|
+
stepDownLabel: "Diminuer",
|
|
107
|
+
stepUpLabel: "Augmenter",
|
|
108
|
+
};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// https://nuxt.com/docs/getting-started/testing#unit-testing
|
|
2
|
+
import type { VueWrapper } from "@vue/test-utils";
|
|
3
|
+
import { mountSuspended } from "@nuxt/test-utils/runtime";
|
|
4
|
+
import ComponentUnderTest from "../InputRangeDefault.vue";
|
|
5
|
+
|
|
6
|
+
const initialPropsData = {
|
|
7
|
+
name: "testName",
|
|
8
|
+
label: "Test label",
|
|
9
|
+
min: 0,
|
|
10
|
+
max: 100,
|
|
11
|
+
errorMessage: "",
|
|
12
|
+
modelValue: 50,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
let wrapper: VueWrapper<InstanceType<typeof ComponentUnderTest>>;
|
|
16
|
+
const wrapperFactory = (propsData = {}, slots = {}) => {
|
|
17
|
+
const mockPropsData = { ...initialPropsData, ...propsData };
|
|
18
|
+
|
|
19
|
+
return mountSuspended(ComponentUnderTest, {
|
|
20
|
+
attachTo: document.body,
|
|
21
|
+
props: mockPropsData,
|
|
22
|
+
slots,
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
describe("InputRangeDefault", () => {
|
|
27
|
+
it("mounts without error", async () => {
|
|
28
|
+
wrapper = await wrapperFactory();
|
|
29
|
+
expect(wrapper.vm).toBeTruthy();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("renders the label text", async () => {
|
|
33
|
+
wrapper = await wrapperFactory({ label: "Price range" });
|
|
34
|
+
expect(wrapper.text()).toContain("Price range");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("renders a range input", async () => {
|
|
38
|
+
wrapper = await wrapperFactory();
|
|
39
|
+
expect(wrapper.find('input[type="range"]').exists()).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("sets data-invalid and the error class when fieldHasError is true", async () => {
|
|
43
|
+
wrapper = await wrapperFactory({ fieldHasError: true });
|
|
44
|
+
expect(wrapper.find(".input-range-with-label").attributes("data-invalid")).toBe("");
|
|
45
|
+
expect(wrapper.find(".input-range-with-label").classes()).toContain("error");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("does not render step buttons when the left/right slots are not used", async () => {
|
|
49
|
+
wrapper = await wrapperFactory();
|
|
50
|
+
expect(wrapper.findAllComponents({ name: "InputButtonCore" })).toHaveLength(0);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("renders step buttons with default labels when the left/right slots are used", async () => {
|
|
54
|
+
wrapper = await wrapperFactory(
|
|
55
|
+
{},
|
|
56
|
+
{
|
|
57
|
+
left: '<span data-testid="left-icon">-</span>',
|
|
58
|
+
right: '<span data-testid="right-icon">+</span>',
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
expect(wrapper.text()).toContain("Step down");
|
|
63
|
+
expect(wrapper.text()).toContain("Step up");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("overrides step button labels via stepDownLabel/stepUpLabel props", async () => {
|
|
67
|
+
wrapper = await wrapperFactory(
|
|
68
|
+
{ stepDownLabel: "Decrease", stepUpLabel: "Increase" },
|
|
69
|
+
{
|
|
70
|
+
left: '<span>-</span>',
|
|
71
|
+
right: '<span>+</span>',
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
expect(wrapper.text()).toContain("Decrease");
|
|
76
|
+
expect(wrapper.text()).toContain("Increase");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("increments the model value when the step-up button is clicked", async () => {
|
|
80
|
+
wrapper = await wrapperFactory(
|
|
81
|
+
{ modelValue: 50, step: 5 },
|
|
82
|
+
{ right: '<span>+</span>' }
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const buttons = wrapper.findAll("button");
|
|
86
|
+
const stepUpButton = buttons.find((btn) => btn.text().includes("+") || btn.attributes("aria-label")?.includes("Step up"));
|
|
87
|
+
await stepUpButton?.trigger("click");
|
|
88
|
+
|
|
89
|
+
expect(wrapper.emitted("update:modelValue")?.[0]).toEqual([55]);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("decrements the model value when the step-down button is clicked", async () => {
|
|
93
|
+
wrapper = await wrapperFactory(
|
|
94
|
+
{ modelValue: 50, step: 5 },
|
|
95
|
+
{ left: '<span>-</span>' }
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const buttons = wrapper.findAll("button");
|
|
99
|
+
const stepDownButton = buttons.find((btn) => btn.text().includes("-") || btn.attributes("aria-label")?.includes("Step down"));
|
|
100
|
+
await stepDownButton?.trigger("click");
|
|
101
|
+
|
|
102
|
+
expect(wrapper.emitted("update:modelValue")?.[0]).toEqual([45]);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("does not step below min", async () => {
|
|
106
|
+
wrapper = await wrapperFactory(
|
|
107
|
+
{ modelValue: 0, min: 0, step: 5 },
|
|
108
|
+
{ left: '<span>-</span>' }
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const buttons = wrapper.findAll("button");
|
|
112
|
+
const stepDownButton = buttons.find((btn) => btn.text().includes("-"));
|
|
113
|
+
await stepDownButton?.trigger("click");
|
|
114
|
+
|
|
115
|
+
expect(wrapper.emitted("update:modelValue")).toBeUndefined();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("does not step above max", async () => {
|
|
119
|
+
wrapper = await wrapperFactory(
|
|
120
|
+
{ modelValue: 100, max: 100, step: 5 },
|
|
121
|
+
{ right: '<span>+</span>' }
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const buttons = wrapper.findAll("button");
|
|
125
|
+
const stepUpButton = buttons.find((btn) => btn.text().includes("+"));
|
|
126
|
+
await stepUpButton?.trigger("click");
|
|
127
|
+
|
|
128
|
+
expect(wrapper.emitted("update:modelValue")).toBeUndefined();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("renders the error message when fieldHasError is true", async () => {
|
|
132
|
+
wrapper = await wrapperFactory({ fieldHasError: true, errorMessage: "This field is required" });
|
|
133
|
+
expect(wrapper.text()).toContain("This field is required");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("applies styleClassPassthrough", async () => {
|
|
137
|
+
wrapper = await wrapperFactory({ styleClassPassthrough: "custom-range" });
|
|
138
|
+
expect(wrapper.find(".input-range-with-label").classes()).toContain("custom-range");
|
|
139
|
+
});
|
|
140
|
+
});
|
|
@@ -9,12 +9,16 @@
|
|
|
9
9
|
:id
|
|
10
10
|
v-model="modelValue"
|
|
11
11
|
:aria-invalid="fieldHasError ? 'true' : undefined"
|
|
12
|
-
class="input-select-core"
|
|
12
|
+
:class="['input-select-core', elementClasses]"
|
|
13
13
|
:name
|
|
14
14
|
:title
|
|
15
|
+
:required
|
|
15
16
|
:aria-describedby="ariaDescribedby"
|
|
17
|
+
@focusin="isActive = true"
|
|
18
|
+
@focusout="isActive = false"
|
|
19
|
+
@change="isDirty = true"
|
|
16
20
|
>
|
|
17
|
-
<option v-if="placeholder" value=""
|
|
21
|
+
<option v-if="placeholder" value="" disabled :selected="!modelValue" class="input-select-core-option placeholder">
|
|
18
22
|
{{ placeholder }}
|
|
19
23
|
</option>
|
|
20
24
|
<option
|
|
@@ -38,7 +42,7 @@
|
|
|
38
42
|
</template>
|
|
39
43
|
|
|
40
44
|
<script setup lang="ts">
|
|
41
|
-
import type { IFormMultipleOptions } from "~/types/forms/types.forms";
|
|
45
|
+
import type { IFormMultipleOptions, FormUiTheme, InputUiVariant } from "~/types/forms/types.forms";
|
|
42
46
|
|
|
43
47
|
interface Props {
|
|
44
48
|
id: string;
|
|
@@ -48,12 +52,12 @@ interface Props {
|
|
|
48
52
|
required?: boolean;
|
|
49
53
|
fieldHasError?: boolean;
|
|
50
54
|
styleClassPassthrough?: string | string[];
|
|
51
|
-
theme?:
|
|
52
|
-
inputVariant?:
|
|
55
|
+
theme?: FormUiTheme;
|
|
56
|
+
inputVariant?: InputUiVariant;
|
|
53
57
|
ariaDescribedby?: string;
|
|
54
58
|
}
|
|
55
59
|
|
|
56
|
-
withDefaults(defineProps<Props>(), {
|
|
60
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
57
61
|
placeholder: "",
|
|
58
62
|
title: "Please select an option",
|
|
59
63
|
required: false,
|
|
@@ -67,7 +71,9 @@ withDefaults(defineProps<Props>(), {
|
|
|
67
71
|
const modelValue = defineModel<string | number | readonly number[]>({ required: true });
|
|
68
72
|
const isDirty = defineModel<boolean>("isDirty");
|
|
69
73
|
const isActive = defineModel<boolean>("isActive");
|
|
70
|
-
const fieldData = defineModel("fieldData"
|
|
74
|
+
const fieldData = defineModel<IFormMultipleOptions>("fieldData", { required: true });
|
|
75
|
+
|
|
76
|
+
const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
|
|
71
77
|
</script>
|
|
72
78
|
|
|
73
79
|
<style lang="css">
|
|
@@ -75,22 +81,18 @@ const fieldData = defineModel("fieldData") as Ref<IFormMultipleOptions>;
|
|
|
75
81
|
.input-select-wrapper {
|
|
76
82
|
/* Public --input-select-* tokens, inline-fallback to the shared --theme-* tokens (see
|
|
77
83
|
theming-component-token-pattern.md) — overriding one here doesn't touch every other themed
|
|
78
|
-
input/control that also reads --theme-input-surface/--theme-border. --
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
--_surface: var(--input-select-surface, var(--theme-input-surface));
|
|
84
|
-
--_surface-hover: var(--input-select-surface-hover, var(--theme-input-surface-hover));
|
|
84
|
+
input/control that also reads --theme-input-surface/--theme-border. --_border is the one
|
|
85
|
+
genuinely reused value (border, underlined border-bottom, open-picker border); the rest of
|
|
86
|
+
this component's tokens are used at exactly one point each, so they're inlined directly
|
|
87
|
+
rather than wrapped in a private var with no composition or state-swap behind it (see
|
|
88
|
+
pitfall #20 in CLAUDE.md). */
|
|
85
89
|
--_border: var(--input-select-border, var(--theme-border));
|
|
86
90
|
/* Split so hover (mouse) and :focus-visible (keyboard/assistive) can diverge — both default to
|
|
87
91
|
the same --theme-border-focus today, identical appearance to before this token existed, but
|
|
88
92
|
each now has its own override point. */
|
|
89
|
-
--_border-hover: var(--input-select-border-hover, var(--theme-border-focus));
|
|
90
93
|
--_border-focus: var(--input-select-border-focus, var(--theme-border-focus));
|
|
91
|
-
--_outline-color: var(--_border-focus);
|
|
92
94
|
|
|
93
|
-
background-color: var(--
|
|
95
|
+
background-color: var(--input-select-surface, var(--theme-input-surface));
|
|
94
96
|
overflow: hidden;
|
|
95
97
|
|
|
96
98
|
z-index: 2;
|
|
@@ -105,11 +107,11 @@ const fieldData = defineModel("fieldData") as Ref<IFormMultipleOptions>;
|
|
|
105
107
|
|
|
106
108
|
&.underlined {
|
|
107
109
|
border-bottom: var(--form-element-border-bottom-width-underlined) solid var(--_border);
|
|
108
|
-
background-color: var(--_surface);
|
|
109
110
|
}
|
|
110
111
|
|
|
111
112
|
&:has(:hover) {
|
|
112
|
-
outline: var(--form-element-outline-width-focus) solid
|
|
113
|
+
outline: var(--form-element-outline-width-focus) solid
|
|
114
|
+
var(--input-select-border-hover, var(--theme-border-focus));
|
|
113
115
|
outline-offset: var(--form-element-outline-offset-focus);
|
|
114
116
|
}
|
|
115
117
|
|
|
@@ -124,15 +126,6 @@ const fieldData = defineModel("fieldData") as Ref<IFormMultipleOptions>;
|
|
|
124
126
|
display: flex;
|
|
125
127
|
align-items: center;
|
|
126
128
|
|
|
127
|
-
/* For legacy support - eg, Safari */
|
|
128
|
-
/* &::after {
|
|
129
|
-
content: '';
|
|
130
|
-
width: 0.8em;
|
|
131
|
-
height: 0.5em;
|
|
132
|
-
background-color: var(--_border);
|
|
133
|
-
clip-path: polygon(100% 0%, 0 0%, 50% 100%);
|
|
134
|
-
} */
|
|
135
|
-
|
|
136
129
|
/* Start modern Select CSS */
|
|
137
130
|
&,
|
|
138
131
|
&::picker(select) {
|
|
@@ -153,7 +146,7 @@ const fieldData = defineModel("fieldData") as Ref<IFormMultipleOptions>;
|
|
|
153
146
|
&:open::picker(select) {
|
|
154
147
|
opacity: 1;
|
|
155
148
|
border: var(--form-element-border-width) solid var(--_border);
|
|
156
|
-
outline: var(--form-element-outline-width) solid var(--
|
|
149
|
+
outline: var(--form-element-outline-width) solid var(--_border-focus);
|
|
157
150
|
|
|
158
151
|
@starting-style {
|
|
159
152
|
opacity: 0;
|
|
@@ -180,7 +173,7 @@ const fieldData = defineModel("fieldData") as Ref<IFormMultipleOptions>;
|
|
|
180
173
|
transition: all var(--theme-form-transition-duration) ease-in-out;
|
|
181
174
|
|
|
182
175
|
&:hover {
|
|
183
|
-
background-color: var(--
|
|
176
|
+
background-color: var(--input-select-surface-hover, var(--theme-input-surface-hover));
|
|
184
177
|
}
|
|
185
178
|
|
|
186
179
|
.input-select-core-option-decorator-icon {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Meta, StoryFn } from "@nuxtjs/storybook";
|
|
2
2
|
import { ref } from "vue";
|
|
3
3
|
import StorybookComponent from "../InputSelectCore.vue";
|
|
4
|
-
import type { IFormMultipleOptions } from "~/types/forms/types.forms.d";
|
|
4
|
+
import type { IFormMultipleOptions, InputUiVariant } from "~/types/forms/types.forms.d";
|
|
5
5
|
|
|
6
6
|
interface InputSelectCoreStoryArgs {
|
|
7
7
|
id: string;
|
|
@@ -9,7 +9,7 @@ interface InputSelectCoreStoryArgs {
|
|
|
9
9
|
placeholder: string;
|
|
10
10
|
required: boolean;
|
|
11
11
|
fieldHasError: boolean;
|
|
12
|
-
inputVariant:
|
|
12
|
+
inputVariant: InputUiVariant;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export default {
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { Meta, StoryFn } from "@nuxtjs/storybook";
|
|
2
|
+
import { ref, computed } from "vue";
|
|
3
|
+
import StorybookComponent from "../variants/InputSelectWithLabel.vue";
|
|
4
|
+
import type { FormUiTheme, InputUiVariant, IFormMultipleOptions } from "~/types/forms/types.forms.d";
|
|
5
|
+
|
|
6
|
+
interface InputSelectWithLabelStoryArgs {
|
|
7
|
+
modelValue: string | number;
|
|
8
|
+
name: string;
|
|
9
|
+
label: string;
|
|
10
|
+
placeholder: string;
|
|
11
|
+
errorMessage: string;
|
|
12
|
+
fieldHasError: boolean;
|
|
13
|
+
required: boolean;
|
|
14
|
+
theme: FormUiTheme;
|
|
15
|
+
inputVariant: InputUiVariant;
|
|
16
|
+
styleClassPassthrough: string[];
|
|
17
|
+
useDescriptionSlot: boolean;
|
|
18
|
+
descriptionContent: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default {
|
|
22
|
+
title: "Components/Forms/Input Select/InputSelectWithLabel",
|
|
23
|
+
component: StorybookComponent,
|
|
24
|
+
argTypes: {
|
|
25
|
+
modelValue: { control: "text", table: { category: "Model" } },
|
|
26
|
+
name: { control: "text", table: { category: "Basic" } },
|
|
27
|
+
label: { control: "text", table: { category: "Basic" } },
|
|
28
|
+
placeholder: { control: "text", table: { category: "Basic" } },
|
|
29
|
+
errorMessage: { control: "text", table: { category: "Validation" } },
|
|
30
|
+
fieldHasError: { control: "boolean", table: { category: "Validation" } },
|
|
31
|
+
required: { control: "boolean", table: { category: "Validation" } },
|
|
32
|
+
theme: {
|
|
33
|
+
control: { type: "select" },
|
|
34
|
+
options: ["default", "success", "error", "warning"],
|
|
35
|
+
table: { category: "Styling" },
|
|
36
|
+
},
|
|
37
|
+
inputVariant: {
|
|
38
|
+
control: { type: "select" },
|
|
39
|
+
options: ["normal", "outlined", "underlined"],
|
|
40
|
+
table: { category: "Styling" },
|
|
41
|
+
},
|
|
42
|
+
styleClassPassthrough: { control: "object", table: { category: "Styling" } },
|
|
43
|
+
useDescriptionSlot: { control: "boolean", table: { category: "Slots" } },
|
|
44
|
+
descriptionContent: { control: "text", table: { category: "Slots" } },
|
|
45
|
+
},
|
|
46
|
+
args: {
|
|
47
|
+
modelValue: "",
|
|
48
|
+
name: "colour",
|
|
49
|
+
label: "Favourite colour",
|
|
50
|
+
placeholder: "Choose a colour",
|
|
51
|
+
errorMessage: "",
|
|
52
|
+
fieldHasError: false,
|
|
53
|
+
required: false,
|
|
54
|
+
theme: "default",
|
|
55
|
+
inputVariant: "normal",
|
|
56
|
+
styleClassPassthrough: [],
|
|
57
|
+
useDescriptionSlot: false,
|
|
58
|
+
descriptionContent: "Pick the colour you like best",
|
|
59
|
+
},
|
|
60
|
+
} as Meta<InputSelectWithLabelStoryArgs>;
|
|
61
|
+
|
|
62
|
+
// `args` is Storybook's own reactive object — bind to it directly (`args.x`) rather than
|
|
63
|
+
// destructuring it into local variables/refs, which would snapshot the values once at setup()
|
|
64
|
+
// and stop reflecting later Controls-panel changes. `componentArgs` strips the non-prop
|
|
65
|
+
// slot-toggle args so they don't leak onto the component as unknown attributes.
|
|
66
|
+
const Template: StoryFn<InputSelectWithLabelStoryArgs> = (args) => ({
|
|
67
|
+
components: { StorybookComponent },
|
|
68
|
+
setup() {
|
|
69
|
+
const fieldData = ref<IFormMultipleOptions>({
|
|
70
|
+
data: [
|
|
71
|
+
{ id: "1", name: "red", value: "red", label: "Red" },
|
|
72
|
+
{ id: "2", name: "blue", value: "blue", label: "Blue" },
|
|
73
|
+
{ id: "3", name: "green", value: "green", label: "Green" },
|
|
74
|
+
],
|
|
75
|
+
total: 3,
|
|
76
|
+
skip: 0,
|
|
77
|
+
limit: 10,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const componentArgs = computed(() => {
|
|
81
|
+
const { useDescriptionSlot, descriptionContent, ...rest } = args;
|
|
82
|
+
return rest;
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return { args, componentArgs, fieldData };
|
|
86
|
+
},
|
|
87
|
+
template: `
|
|
88
|
+
<div style="margin: 36px; max-width: 320px;">
|
|
89
|
+
<StorybookComponent v-model="args.modelValue" v-model:field-data="fieldData" v-bind="componentArgs">
|
|
90
|
+
<template v-if="args.useDescriptionSlot" #descriptionText>{{ args.descriptionContent }}</template>
|
|
91
|
+
</StorybookComponent>
|
|
92
|
+
</div>
|
|
93
|
+
`,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
export const Default = Template.bind({});
|
|
97
|
+
|
|
98
|
+
export const WithError = Template.bind({});
|
|
99
|
+
WithError.args = {
|
|
100
|
+
fieldHasError: true,
|
|
101
|
+
errorMessage: "Please choose a colour",
|
|
102
|
+
theme: "error",
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export const Required = Template.bind({});
|
|
106
|
+
Required.args = { required: true };
|
|
107
|
+
|
|
108
|
+
export const WithDescription = Template.bind({});
|
|
109
|
+
WithDescription.args = { useDescriptionSlot: true };
|
|
110
|
+
|
|
111
|
+
export const Underlined = Template.bind({});
|
|
112
|
+
Underlined.args = { inputVariant: "underlined" };
|
|
113
|
+
|
|
114
|
+
export const Outlined = Template.bind({});
|
|
115
|
+
Outlined.args = { inputVariant: "outlined", useDescriptionSlot: true };
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// https://nuxt.com/docs/getting-started/testing#unit-testing
|
|
2
|
+
import type { VueWrapper } from "@vue/test-utils";
|
|
3
|
+
import { mountSuspended } from "@nuxt/test-utils/runtime";
|
|
4
|
+
import ComponentUnderTest from "../InputSelectCore.vue";
|
|
5
|
+
|
|
6
|
+
const initialFieldData = {
|
|
7
|
+
data: [
|
|
8
|
+
{ id: "1", name: "red", value: "red", label: "Red" },
|
|
9
|
+
{ id: "2", name: "blue", value: "blue", label: "Blue" },
|
|
10
|
+
],
|
|
11
|
+
total: 2,
|
|
12
|
+
skip: 0,
|
|
13
|
+
limit: 10,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const initialPropsData = {
|
|
17
|
+
id: "test-select",
|
|
18
|
+
name: "testName",
|
|
19
|
+
modelValue: "red",
|
|
20
|
+
fieldData: initialFieldData,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
let wrapper: VueWrapper<InstanceType<typeof ComponentUnderTest>>;
|
|
24
|
+
const wrapperFactory = (propsData = {}) => {
|
|
25
|
+
const mockPropsData = { ...initialPropsData, ...propsData };
|
|
26
|
+
|
|
27
|
+
return mountSuspended(ComponentUnderTest, {
|
|
28
|
+
attachTo: document.body,
|
|
29
|
+
props: mockPropsData,
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
describe("InputSelectCore", () => {
|
|
34
|
+
it("mounts without error", async () => {
|
|
35
|
+
wrapper = await wrapperFactory();
|
|
36
|
+
expect(wrapper.vm).toBeTruthy();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("renders an option per fieldData item", async () => {
|
|
40
|
+
wrapper = await wrapperFactory();
|
|
41
|
+
const options = wrapper.findAll("option");
|
|
42
|
+
expect(options.map((o) => o.text())).toEqual(["Red", "Blue"]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("binds v-model to the select value", async () => {
|
|
46
|
+
wrapper = await wrapperFactory({ modelValue: "blue" });
|
|
47
|
+
expect(wrapper.find("select").element.value).toBe("blue");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("renders a disabled placeholder option when placeholder is set", async () => {
|
|
51
|
+
wrapper = await wrapperFactory({ placeholder: "Choose a colour", modelValue: "" });
|
|
52
|
+
const placeholderOption = wrapper.find("option.placeholder");
|
|
53
|
+
|
|
54
|
+
expect(placeholderOption.exists()).toBe(true);
|
|
55
|
+
expect(placeholderOption.text()).toBe("Choose a colour");
|
|
56
|
+
expect(placeholderOption.attributes("disabled")).toBeDefined();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("does not render a placeholder option when placeholder is unset", async () => {
|
|
60
|
+
wrapper = await wrapperFactory();
|
|
61
|
+
expect(wrapper.find("option.placeholder").exists()).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("sets required attribute when required is true", async () => {
|
|
65
|
+
wrapper = await wrapperFactory({ required: true });
|
|
66
|
+
expect(wrapper.find("select").attributes("required")).toBeDefined();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("does not set required attribute by default", async () => {
|
|
70
|
+
wrapper = await wrapperFactory();
|
|
71
|
+
expect(wrapper.find("select").attributes("required")).toBeUndefined();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("sets data-theme from the theme prop", async () => {
|
|
75
|
+
wrapper = await wrapperFactory({ theme: "error" });
|
|
76
|
+
expect(wrapper.find(".input-select-wrapper").attributes("data-theme")).toBe("error");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("sets data-invalid and aria-invalid when fieldHasError is true", async () => {
|
|
80
|
+
wrapper = await wrapperFactory({ fieldHasError: true });
|
|
81
|
+
expect(wrapper.find(".input-select-wrapper").attributes("data-invalid")).toBe("");
|
|
82
|
+
expect(wrapper.find("select").attributes("aria-invalid")).toBe("true");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("defaults inputVariant to normal", async () => {
|
|
86
|
+
wrapper = await wrapperFactory();
|
|
87
|
+
expect(wrapper.find(".input-select-wrapper").classes()).toContain("normal");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("applies inputVariant as a class on the wrapper", async () => {
|
|
91
|
+
wrapper = await wrapperFactory({ inputVariant: "underlined" });
|
|
92
|
+
expect(wrapper.find(".input-select-wrapper").classes()).toContain("underlined");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("applies styleClassPassthrough to the select", async () => {
|
|
96
|
+
wrapper = await wrapperFactory({ styleClassPassthrough: ["custom-select-class"] });
|
|
97
|
+
expect(wrapper.find("select").classes()).toContain("custom-select-class");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("sets isActive true on focus and false on blur", async () => {
|
|
101
|
+
wrapper = await wrapperFactory();
|
|
102
|
+
const select = wrapper.find("select");
|
|
103
|
+
|
|
104
|
+
await select.trigger("focusin");
|
|
105
|
+
expect(wrapper.emitted("update:isActive")?.at(-1)).toEqual([true]);
|
|
106
|
+
|
|
107
|
+
await select.trigger("focusout");
|
|
108
|
+
expect(wrapper.emitted("update:isActive")?.at(-1)).toEqual([false]);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("sets isDirty true when the value changes", async () => {
|
|
112
|
+
wrapper = await wrapperFactory();
|
|
113
|
+
await wrapper.find("select").trigger("change");
|
|
114
|
+
expect(wrapper.emitted("update:isDirty")?.at(-1)).toEqual([true]);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("renders an icon for an option that has one, hidden from assistive tech", async () => {
|
|
118
|
+
wrapper = await wrapperFactory({
|
|
119
|
+
fieldData: {
|
|
120
|
+
...initialFieldData,
|
|
121
|
+
data: [{ id: "1", name: "red", value: "red", label: "Red", icon: "mdi:circle" }],
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const icon = wrapper.find(".input-select-core-option-decorator-icon");
|
|
126
|
+
expect(icon.exists()).toBe(true);
|
|
127
|
+
expect(icon.attributes("aria-hidden")).toBe("true");
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -117,5 +117,5 @@ const { id, errorId, descriptionId, ariaDescribedby } = useAriaDescribedById(
|
|
|
117
117
|
const modelValue = defineModel<string | number | readonly number[]>({ required: true });
|
|
118
118
|
const isDirty = defineModel<boolean>("isDirty");
|
|
119
119
|
const isActive = defineModel<boolean>("isActive");
|
|
120
|
-
const fieldData = defineModel<IFormMultipleOptions>("fieldData");
|
|
120
|
+
const fieldData = defineModel<IFormMultipleOptions>("fieldData", { required: true });
|
|
121
121
|
</script>
|