srcdev-nuxt-components 9.6.19 → 9.6.20
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/rules/consumer/ids-from-useid.md +3 -2
- package/.claude/skills/component-compliance-checklist.md +5 -0
- package/.claude/skills/components/auto-grid.md +3 -1
- package/.claude/skills/components/capture-qr-code.md +9 -5
- package/.claude/skills/components/carousel-flip.md +41 -30
- package/.claude/skills/components/column-flow-grid.md +2 -0
- package/.claude/skills/components/dashboard-quad-grid.md +7 -2
- package/.claude/skills/components/dashboard-stats-grid.md +4 -0
- package/.claude/skills/components/decode-qr-code.md +7 -2
- package/.claude/skills/components/deep-expanding-menu-classic.md +31 -30
- package/.claude/skills/components/deep-expanding-menu.md +22 -21
- package/.claude/skills/components/grid-stack.md +2 -0
- package/.claude/skills/components/layout-grid-by-cols.md +2 -0
- package/.claude/skills/components/masonry-grid.md +9 -1
- package/.claude/skills/components/page-row.md +3 -0
- package/.claude/skills/components/samaritan-prompt.md +24 -8
- package/.claude/skills/components/site-header.md +16 -2
- package/.claude/skills/index.md +12 -12
- package/.claude/skills/storybook-add-story.md +25 -0
- package/.claude/skills/testing-add-unit-test.md +26 -0
- package/.vscode/srcdev-component-capture-decode-qr-code.code-snippets +11 -2
- package/.vscode/srcdev-component-carousel-flip.code-snippets +22 -4
- package/.vscode/srcdev-component-deep-expanding-menu-classic.code-snippets +32 -2
- package/.vscode/srcdev-component-deep-expanding-menu.code-snippets +31 -1
- package/.vscode/srcdev-component-samaritan-prompt.code-snippets +5 -4
- package/.vscode/srcdev-component-site-header.code-snippets +13 -1
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/CONSUMER-STYLING.md +12 -2
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/DashboardQuadGrid.vue +19 -17
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/stories/DashboardQuadGrid.stories.ts +42 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/tests/DashboardQuadGrid.spec.ts +31 -44
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/CONSUMER-STYLING.md +13 -1
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/DashboardStatsGrid.vue +40 -31
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/stories/DashboardStatsGrid.stories.ts +47 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/tests/DashboardStatsGrid.spec.ts +51 -53
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/CONSUMER-STYLING.md +3 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/LayoutGridByCols.vue +4 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/stories/LayoutGridByCols.stories.ts +47 -2
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/tests/LayoutGridByCols.spec.ts +36 -23
- package/app/components/01.atoms/grids/auto-grid/AutoGrid.vue +7 -3
- package/app/components/01.atoms/grids/auto-grid/CONSUMER-STYLING.md +1 -1
- package/app/components/01.atoms/grids/auto-grid/stories/AutoGrid.stories.ts +46 -1
- package/app/components/01.atoms/grids/auto-grid/tests/AutoGrid.spec.ts +40 -27
- package/app/components/01.atoms/grids/auto-grid/tests/__snapshots__/AutoGrid.spec.ts.snap +1 -1
- package/app/components/01.atoms/grids/column-flow-grid/CONSUMER-STYLING.md +9 -0
- package/app/components/01.atoms/grids/column-flow-grid/ColumnFlowGrid.vue +3 -2
- package/app/components/01.atoms/grids/column-flow-grid/stories/ColumnFlowGrid.stories.ts +46 -0
- package/app/components/01.atoms/grids/column-flow-grid/tests/ColumnFlowGrid.spec.ts +24 -11
- package/app/components/01.atoms/grids/grid-stack/CONSUMER-STYLING.md +3 -0
- package/app/components/01.atoms/grids/grid-stack/GridStack.vue +2 -0
- package/app/components/01.atoms/grids/grid-stack/stories/GridStack.stories.ts +41 -0
- package/app/components/01.atoms/grids/grid-stack/tests/GridStack.spec.ts +26 -13
- package/app/components/01.atoms/grids/masonry-grid/CONSUMER-STYLING.md +15 -0
- package/app/components/01.atoms/grids/masonry-grid/MasonryGrid.vue +51 -31
- package/app/components/01.atoms/grids/masonry-grid/stories/MasonryGrid.stories.ts +48 -0
- package/app/components/01.atoms/grids/masonry-grid/tests/MasonryGrid.spec.ts +84 -29
- package/app/components/01.atoms/page-row/CONSUMER-STYLING.md +6 -1
- package/app/components/01.atoms/page-row/PageRow.vue +16 -15
- package/app/components/01.atoms/page-row/stories/PageRow.stories.ts +23 -0
- package/app/components/01.atoms/page-row/tests/PageRow.spec.ts +6 -5
- package/app/components/02.molecules/navigation/deep-expanding-menu/CONSUMER-STYLING.md +63 -44
- package/app/components/02.molecules/navigation/deep-expanding-menu/DeepExpandingMenu.vue +74 -37
- package/app/components/02.molecules/navigation/deep-expanding-menu/stories/DeepExpandingMenu.stories.ts +139 -19
- package/app/components/02.molecules/navigation/deep-expanding-menu/tests/DeepExpandingMenu.spec.ts +90 -27
- package/app/components/02.molecules/navigation/deep-expanding-menu/tests/__snapshots__/DeepExpandingMenu.spec.ts.snap +8 -8
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/CONSUMER-STYLING.md +58 -26
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/DeepExpandingMenuClassic.vue +113 -57
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/stories/DeepExpandingMenuClassic.stories.ts +139 -19
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/tests/DeepExpandingMenuClassic.spec.ts +140 -22
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/tests/__snapshots__/DeepExpandingMenuClassic.spec.ts.snap +9 -9
- package/app/components/02.molecules/qr-code/CONSUMER-STYLING.md +22 -7
- package/app/components/02.molecules/qr-code/CaptureQrCode.vue +63 -71
- package/app/components/02.molecules/qr-code/DecodeQrCode.vue +47 -10
- package/app/components/02.molecules/qr-code/stories/CaptureQrCode.stories.ts +114 -26
- package/app/components/02.molecules/qr-code/stories/DecodeQrCode.stories.ts +115 -24
- package/app/components/02.molecules/qr-code/tests/CaptureQrCode.spec.ts +124 -51
- package/app/components/02.molecules/qr-code/tests/DecodeQrCode.spec.ts +74 -25
- package/app/components/02.molecules/samaritan-prompt/CONSUMER-STYLING.md +22 -10
- package/app/components/02.molecules/samaritan-prompt/SamaritanPrompt.vue +30 -12
- package/app/components/02.molecules/samaritan-prompt/SamaritanPromptMixed.vue +34 -10
- package/app/components/02.molecules/samaritan-prompt/stories/SamaritanPrompt.stories.ts +55 -4
- package/app/components/02.molecules/samaritan-prompt/stories/SamaritanPromptMixed.stories.ts +67 -7
- package/app/components/02.molecules/samaritan-prompt/tests/SamaritanPrompt.spec.ts +113 -29
- package/app/components/02.molecules/samaritan-prompt/tests/SamaritanPromptMixed.spec.ts +92 -45
- package/app/components/03.organisms/image-galleries/carousel-flip/CONSUMER-STYLING.md +82 -29
- package/app/components/03.organisms/image-galleries/carousel-flip/CarouselFlip.vue +176 -113
- package/app/components/03.organisms/image-galleries/carousel-flip/stories/CarouselFlip.stories.ts +153 -23
- package/app/components/03.organisms/image-galleries/carousel-flip/tests/CarouselFlip.spec.ts +119 -63
- package/app/components/03.organisms/services/service-detail/tests/ServiceDetail.spec.ts +8 -8
- package/app/components/03.organisms/services/service-detail/tests/__snapshots__/ServiceDetail.spec.ts.snap +4 -4
- package/app/components/03.organisms/site-header/CONSUMER-STYLING.md +41 -23
- package/app/components/03.organisms/site-header/SiteHeader.vue +16 -21
- package/app/components/03.organisms/site-header/stories/SiteHeader.stories.ts +138 -0
- package/app/components/03.organisms/site-header/tests/SiteHeader.spec.ts +53 -26
- package/app/components/03.organisms/site-header/tests/__snapshots__/SiteHeader.spec.ts.snap +2 -2
- package/app/types/components/qr-code.d.ts +12 -0
- package/app/utils/graphemes.ts +8 -0
- package/package.json +1 -1
- package/app/components/01.atoms/animations/entry/.nuxt/app.config.mjs +0 -21
- package/app/components/01.atoms/animations/entry/.nuxt/components.d.ts +0 -66
- package/app/components/01.atoms/animations/entry/.nuxt/imports.d.ts +0 -30
- package/app/components/01.atoms/animations/entry/.nuxt/manifest/meta/test.json +0 -1
- package/app/components/01.atoms/animations/entry/.nuxt/types/app.config.d.ts +0 -35
- package/app/components/01.atoms/animations/entry/.nuxt/types/build.d.ts +0 -23
- package/app/components/01.atoms/animations/entry/.nuxt/types/builder-env.d.ts +0 -1
- package/app/components/01.atoms/animations/entry/.nuxt/types/components.d.ts +0 -71
- package/app/components/01.atoms/animations/entry/.nuxt/types/imports.d.ts +0 -386
- package/app/components/01.atoms/animations/entry/.nuxt/types/middleware.d.ts +0 -11
- package/app/components/01.atoms/animations/entry/.nuxt/types/modules.d.ts +0 -79
- package/app/components/01.atoms/animations/entry/.nuxt/types/nitro-layouts.d.ts +0 -17
- package/app/components/01.atoms/animations/entry/.nuxt/types/nitro-nuxt.d.ts +0 -64
- package/app/components/01.atoms/animations/entry/.nuxt/types/plugins.d.ts +0 -29
- package/app/components/01.atoms/animations/entry/.nuxt/types/runtime-config.d.ts +0 -32
- package/app/components/01.atoms/animations/entry/.nuxt/types/shared-imports.d.ts +0 -10
- package/app/components/01.atoms/animations/entry/.nuxt/types/vue-shim.d.ts +0 -0
- package/app/components/01.atoms/animations/entry/.nuxtrc +0 -1
- package/app/components/01.atoms/animations/entry/node_modules/.cache/nuxt/chrome-workspace.json +0 -1
- package/app/components/01.atoms/animations/entry/node_modules/.cache/vite/client/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
- package/app/components/03.organisms/responsive-header/tests/.nuxt/app.config.mjs +0 -21
- package/app/components/03.organisms/responsive-header/tests/.nuxt/components.d.ts +0 -66
- package/app/components/03.organisms/responsive-header/tests/.nuxt/imports.d.ts +0 -30
- package/app/components/03.organisms/responsive-header/tests/.nuxt/manifest/meta/test.json +0 -1
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/app.config.d.ts +0 -35
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/build.d.ts +0 -23
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/builder-env.d.ts +0 -1
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/components.d.ts +0 -71
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/imports.d.ts +0 -386
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/middleware.d.ts +0 -11
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/modules.d.ts +0 -79
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/nitro-layouts.d.ts +0 -17
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/nitro-nuxt.d.ts +0 -64
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/plugins.d.ts +0 -29
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/runtime-config.d.ts +0 -32
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/shared-imports.d.ts +0 -10
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/vue-shim.d.ts +0 -0
- package/app/components/03.organisms/responsive-header/tests/node_modules/.cache/nuxt/chrome-workspace.json +0 -1
|
@@ -1,8 +1,21 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
1
|
+
import { describe, it, expect, afterEach, vi } from "vitest";
|
|
2
2
|
import { mountSuspended } from "@nuxt/test-utils/runtime";
|
|
3
3
|
import { nextTick } from "vue";
|
|
4
4
|
import SamaritanPrompt from "../SamaritanPrompt.vue";
|
|
5
5
|
|
|
6
|
+
const mounted: Awaited<ReturnType<typeof mountSuspended>>[] = [];
|
|
7
|
+
|
|
8
|
+
const mountPrompt = async (options?: Parameters<typeof mountSuspended<typeof SamaritanPrompt>>[1]) => {
|
|
9
|
+
const wrapper = await mountSuspended(SamaritanPrompt, options);
|
|
10
|
+
mounted.push(wrapper);
|
|
11
|
+
return wrapper;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
mounted.splice(0).forEach((wrapper) => wrapper.unmount());
|
|
16
|
+
vi.restoreAllMocks();
|
|
17
|
+
});
|
|
18
|
+
|
|
6
19
|
const messages: string[] = ["Hello, world.", "Surveillance active.", "Threat detected."];
|
|
7
20
|
const [firstMessage, secondMessage] = messages as [string, string, string];
|
|
8
21
|
|
|
@@ -10,35 +23,35 @@ const [firstMessage, secondMessage] = messages as [string, string, string];
|
|
|
10
23
|
|
|
11
24
|
describe("SamaritanPrompt — typewriter", () => {
|
|
12
25
|
it("mounts without error", async () => {
|
|
13
|
-
const wrapper = await
|
|
26
|
+
const wrapper = await mountPrompt({
|
|
14
27
|
props: { messages },
|
|
15
28
|
});
|
|
16
29
|
expect(wrapper.vm).toBeTruthy();
|
|
17
30
|
});
|
|
18
31
|
|
|
19
32
|
it("renders the triangle cursor", async () => {
|
|
20
|
-
const wrapper = await
|
|
33
|
+
const wrapper = await mountPrompt({
|
|
21
34
|
props: { messages },
|
|
22
35
|
});
|
|
23
36
|
expect(wrapper.find(".samaritan-prompt__cursor").text()).toBe("▲");
|
|
24
37
|
});
|
|
25
38
|
|
|
26
39
|
it("renders the underline element", async () => {
|
|
27
|
-
const wrapper = await
|
|
40
|
+
const wrapper = await mountPrompt({
|
|
28
41
|
props: { messages },
|
|
29
42
|
});
|
|
30
43
|
expect(wrapper.find(".samaritan-prompt__underline").exists()).toBe(true);
|
|
31
44
|
});
|
|
32
45
|
|
|
33
46
|
it("starts with empty display text", async () => {
|
|
34
|
-
const wrapper = await
|
|
47
|
+
const wrapper = await mountPrompt({
|
|
35
48
|
props: { messages },
|
|
36
49
|
});
|
|
37
50
|
expect(wrapper.find(".samaritan-prompt__text").text()).toBe("");
|
|
38
51
|
});
|
|
39
52
|
|
|
40
53
|
it("types one character per typeSpeed tick", async () => {
|
|
41
|
-
const wrapper = await
|
|
54
|
+
const wrapper = await mountPrompt({
|
|
42
55
|
props: { messages, typeSpeed: 100 },
|
|
43
56
|
});
|
|
44
57
|
|
|
@@ -52,7 +65,7 @@ describe("SamaritanPrompt — typewriter", () => {
|
|
|
52
65
|
});
|
|
53
66
|
|
|
54
67
|
it("fully types the first message", async () => {
|
|
55
|
-
const wrapper = await
|
|
68
|
+
const wrapper = await mountPrompt({
|
|
56
69
|
props: { messages, typeSpeed: 10 },
|
|
57
70
|
});
|
|
58
71
|
|
|
@@ -62,7 +75,7 @@ describe("SamaritanPrompt — typewriter", () => {
|
|
|
62
75
|
});
|
|
63
76
|
|
|
64
77
|
it("deletes text after hold duration", async () => {
|
|
65
|
-
const wrapper = await
|
|
78
|
+
const wrapper = await mountPrompt({
|
|
66
79
|
props: { messages, typeSpeed: 10, holdDuration: 500, deleteSpeed: 10 },
|
|
67
80
|
});
|
|
68
81
|
|
|
@@ -77,7 +90,7 @@ describe("SamaritanPrompt — typewriter", () => {
|
|
|
77
90
|
});
|
|
78
91
|
|
|
79
92
|
it("advances to the next message after deleting", async () => {
|
|
80
|
-
const wrapper = await
|
|
93
|
+
const wrapper = await mountPrompt({
|
|
81
94
|
props: { messages, typeSpeed: 10, holdDuration: 100, deleteSpeed: 10, pauseDuration: 100 },
|
|
82
95
|
});
|
|
83
96
|
|
|
@@ -97,7 +110,7 @@ describe("SamaritanPrompt — typewriter", () => {
|
|
|
97
110
|
|
|
98
111
|
it("cycles back to first message after last message", async () => {
|
|
99
112
|
const singleMessage = "Only one.";
|
|
100
|
-
const wrapper = await
|
|
113
|
+
const wrapper = await mountPrompt({
|
|
101
114
|
props: { messages: [singleMessage], typeSpeed: 10, holdDuration: 100, deleteSpeed: 10, pauseDuration: 100 },
|
|
102
115
|
});
|
|
103
116
|
|
|
@@ -109,21 +122,21 @@ describe("SamaritanPrompt — typewriter", () => {
|
|
|
109
122
|
});
|
|
110
123
|
|
|
111
124
|
it("applies styleClassPassthrough", async () => {
|
|
112
|
-
const wrapper = await
|
|
125
|
+
const wrapper = await mountPrompt({
|
|
113
126
|
props: { messages, styleClassPassthrough: "custom-class" },
|
|
114
127
|
});
|
|
115
128
|
expect(wrapper.find(".samaritan-prompt").classes()).toContain("custom-class");
|
|
116
129
|
});
|
|
117
130
|
|
|
118
131
|
it("cursor is visible before typing starts", async () => {
|
|
119
|
-
const wrapper = await
|
|
132
|
+
const wrapper = await mountPrompt({
|
|
120
133
|
props: { messages, typeSpeed: 100 },
|
|
121
134
|
});
|
|
122
135
|
expect(wrapper.find(".samaritan-prompt__cursor").attributes("style")).toContain("opacity: 1");
|
|
123
136
|
});
|
|
124
137
|
|
|
125
138
|
it("cursor hides when typewriter starts typing first character", async () => {
|
|
126
|
-
const wrapper = await
|
|
139
|
+
const wrapper = await mountPrompt({
|
|
127
140
|
props: { messages, typeSpeed: 100 },
|
|
128
141
|
});
|
|
129
142
|
|
|
@@ -134,7 +147,7 @@ describe("SamaritanPrompt — typewriter", () => {
|
|
|
134
147
|
});
|
|
135
148
|
|
|
136
149
|
it("cursor returns when typewriter enters pause phase", async () => {
|
|
137
|
-
const wrapper = await
|
|
150
|
+
const wrapper = await mountPrompt({
|
|
138
151
|
props: { messages, typeSpeed: 10, holdDuration: 100, deleteSpeed: 10, pauseDuration: 100 },
|
|
139
152
|
});
|
|
140
153
|
|
|
@@ -150,7 +163,7 @@ describe("SamaritanPrompt — typewriter", () => {
|
|
|
150
163
|
});
|
|
151
164
|
|
|
152
165
|
it("cursor is always visible when hideCursorInCycle is false", async () => {
|
|
153
|
-
const wrapper = await
|
|
166
|
+
const wrapper = await mountPrompt({
|
|
154
167
|
props: { messages, typeSpeed: 100, hideCursorInCycle: false },
|
|
155
168
|
});
|
|
156
169
|
|
|
@@ -165,14 +178,14 @@ describe("SamaritanPrompt — typewriter", () => {
|
|
|
165
178
|
|
|
166
179
|
describe("SamaritanPrompt — word-pulse", () => {
|
|
167
180
|
it("mounts without error", async () => {
|
|
168
|
-
const wrapper = await
|
|
181
|
+
const wrapper = await mountPrompt({
|
|
169
182
|
props: { messages, effect: "word-pulse" },
|
|
170
183
|
});
|
|
171
184
|
expect(wrapper.vm).toBeTruthy();
|
|
172
185
|
});
|
|
173
186
|
|
|
174
187
|
it("starts with empty display text and content visible", async () => {
|
|
175
|
-
const wrapper = await
|
|
188
|
+
const wrapper = await mountPrompt({
|
|
176
189
|
props: { messages, effect: "word-pulse", pauseDuration: 500 },
|
|
177
190
|
});
|
|
178
191
|
await nextTick();
|
|
@@ -182,7 +195,7 @@ describe("SamaritanPrompt — word-pulse", () => {
|
|
|
182
195
|
});
|
|
183
196
|
|
|
184
197
|
it("makes content invisible when cycle starts after pause", async () => {
|
|
185
|
-
const wrapper = await
|
|
198
|
+
const wrapper = await mountPrompt({
|
|
186
199
|
props: { messages, effect: "word-pulse", pauseDuration: 500, fadeDuration: 400, wordDuration: 1200 },
|
|
187
200
|
});
|
|
188
201
|
await nextTick();
|
|
@@ -198,7 +211,7 @@ describe("SamaritanPrompt — word-pulse", () => {
|
|
|
198
211
|
});
|
|
199
212
|
|
|
200
213
|
it("makes content visible after expansion delay", async () => {
|
|
201
|
-
const wrapper = await
|
|
214
|
+
const wrapper = await mountPrompt({
|
|
202
215
|
props: { messages, effect: "word-pulse", pauseDuration: 500, fadeDuration: 400, wordDuration: 1200 },
|
|
203
216
|
});
|
|
204
217
|
await nextTick();
|
|
@@ -222,7 +235,7 @@ describe("SamaritanPrompt — word-pulse", () => {
|
|
|
222
235
|
});
|
|
223
236
|
|
|
224
237
|
it("fades content back out after word duration", async () => {
|
|
225
|
-
const wrapper = await
|
|
238
|
+
const wrapper = await mountPrompt({
|
|
226
239
|
props: { messages, effect: "word-pulse", pauseDuration: 500, fadeDuration: 400, wordDuration: 1200 },
|
|
227
240
|
});
|
|
228
241
|
await nextTick();
|
|
@@ -250,7 +263,7 @@ describe("SamaritanPrompt — word-pulse", () => {
|
|
|
250
263
|
});
|
|
251
264
|
|
|
252
265
|
it("advances to the next message after fade out", async () => {
|
|
253
|
-
const wrapper = await
|
|
266
|
+
const wrapper = await mountPrompt({
|
|
254
267
|
props: { messages, effect: "word-pulse", pauseDuration: 500, fadeDuration: 400, wordDuration: 1200 },
|
|
255
268
|
});
|
|
256
269
|
await nextTick();
|
|
@@ -284,7 +297,7 @@ describe("SamaritanPrompt — word-pulse", () => {
|
|
|
284
297
|
|
|
285
298
|
it("collapses displayText and restores opacity at end of cycle", async () => {
|
|
286
299
|
const singleMsg = "Only.";
|
|
287
|
-
const wrapper = await
|
|
300
|
+
const wrapper = await mountPrompt({
|
|
288
301
|
props: { messages: [singleMsg], effect: "word-pulse", pauseDuration: 300, fadeDuration: 200, wordDuration: 500 },
|
|
289
302
|
});
|
|
290
303
|
await nextTick();
|
|
@@ -318,7 +331,7 @@ describe("SamaritanPrompt — word-pulse", () => {
|
|
|
318
331
|
});
|
|
319
332
|
|
|
320
333
|
it("cursor hides when word-pulse cycle starts", async () => {
|
|
321
|
-
const wrapper = await
|
|
334
|
+
const wrapper = await mountPrompt({
|
|
322
335
|
props: { messages, effect: "word-pulse", pauseDuration: 500, fadeDuration: 400, wordDuration: 1200 },
|
|
323
336
|
});
|
|
324
337
|
await nextTick();
|
|
@@ -334,7 +347,7 @@ describe("SamaritanPrompt — word-pulse", () => {
|
|
|
334
347
|
|
|
335
348
|
it("cursor returns after word-pulse cycle resets", async () => {
|
|
336
349
|
const singleMsg = "Only.";
|
|
337
|
-
const wrapper = await
|
|
350
|
+
const wrapper = await mountPrompt({
|
|
338
351
|
props: { messages: [singleMsg], effect: "word-pulse", pauseDuration: 300, fadeDuration: 200, wordDuration: 500 },
|
|
339
352
|
});
|
|
340
353
|
await nextTick();
|
|
@@ -371,7 +384,7 @@ describe("SamaritanPrompt — word-pulse", () => {
|
|
|
371
384
|
|
|
372
385
|
describe("SamaritanPrompt — accessibility and pause", () => {
|
|
373
386
|
it("hides the animated content from assistive tech and exposes a polite live region", async () => {
|
|
374
|
-
const wrapper = await
|
|
387
|
+
const wrapper = await mountPrompt({ props: { messages } });
|
|
375
388
|
expect(wrapper.find(".samaritan-prompt__content").attributes("aria-hidden")).toBe("true");
|
|
376
389
|
const live = wrapper.find(".samaritan-prompt__sr-text");
|
|
377
390
|
expect(live.attributes("aria-live")).toBe("polite");
|
|
@@ -380,7 +393,7 @@ describe("SamaritanPrompt — accessibility and pause", () => {
|
|
|
380
393
|
});
|
|
381
394
|
|
|
382
395
|
it("announces the full message once typed, and clears it when deleting starts", async () => {
|
|
383
|
-
const wrapper = await
|
|
396
|
+
const wrapper = await mountPrompt({
|
|
384
397
|
props: { messages, typeSpeed: 10, holdDuration: 500, deleteSpeed: 10 },
|
|
385
398
|
});
|
|
386
399
|
|
|
@@ -394,7 +407,7 @@ describe("SamaritanPrompt — accessibility and pause", () => {
|
|
|
394
407
|
});
|
|
395
408
|
|
|
396
409
|
it("stops typing while hovered and resumes on leave", async () => {
|
|
397
|
-
const wrapper = await
|
|
410
|
+
const wrapper = await mountPrompt({ props: { messages, typeSpeed: 100 } });
|
|
398
411
|
const root = wrapper.find(".samaritan-prompt");
|
|
399
412
|
|
|
400
413
|
vi.advanceTimersByTime(200);
|
|
@@ -415,7 +428,7 @@ describe("SamaritanPrompt — accessibility and pause", () => {
|
|
|
415
428
|
});
|
|
416
429
|
|
|
417
430
|
it("renders a custom cursor from the cursor slot", async () => {
|
|
418
|
-
const wrapper = await
|
|
431
|
+
const wrapper = await mountPrompt({
|
|
419
432
|
props: { messages },
|
|
420
433
|
slots: { cursor: () => "_" },
|
|
421
434
|
});
|
|
@@ -423,7 +436,7 @@ describe("SamaritanPrompt — accessibility and pause", () => {
|
|
|
423
436
|
});
|
|
424
437
|
|
|
425
438
|
it("updates passthrough classes when the prop changes", async () => {
|
|
426
|
-
const wrapper = await
|
|
439
|
+
const wrapper = await mountPrompt({
|
|
427
440
|
props: { messages, styleClassPassthrough: "first-class" },
|
|
428
441
|
});
|
|
429
442
|
await wrapper.setProps({ styleClassPassthrough: "second-class" });
|
|
@@ -432,3 +445,74 @@ describe("SamaritanPrompt — accessibility and pause", () => {
|
|
|
432
445
|
expect(classes).not.toContain("first-class");
|
|
433
446
|
});
|
|
434
447
|
});
|
|
448
|
+
|
|
449
|
+
// ─── Worst-case data ──────────────────────────────────────────────────────────
|
|
450
|
+
|
|
451
|
+
const stubReducedMotion = () => {
|
|
452
|
+
const original = window.matchMedia;
|
|
453
|
+
window.matchMedia = ((query: string) => ({
|
|
454
|
+
matches: query.includes("prefers-reduced-motion: reduce"),
|
|
455
|
+
media: query,
|
|
456
|
+
onchange: null,
|
|
457
|
+
addEventListener: () => {},
|
|
458
|
+
removeEventListener: () => {},
|
|
459
|
+
addListener: () => {},
|
|
460
|
+
removeListener: () => {},
|
|
461
|
+
dispatchEvent: () => false,
|
|
462
|
+
})) as unknown as typeof window.matchMedia;
|
|
463
|
+
return () => {
|
|
464
|
+
window.matchMedia = original;
|
|
465
|
+
};
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
describe("SamaritanPrompt — worst-case data", () => {
|
|
469
|
+
const text = (wrapper: Awaited<ReturnType<typeof mountPrompt>>) => wrapper.find(".samaritan-prompt__text").text();
|
|
470
|
+
|
|
471
|
+
it("types an emoji with a skin-tone modifier as one character", async () => {
|
|
472
|
+
const wrapper = await mountPrompt({ props: { messages: ["👍🏽A"], typeSpeed: 100 } });
|
|
473
|
+
vi.advanceTimersByTime(100);
|
|
474
|
+
await nextTick();
|
|
475
|
+
expect(text(wrapper)).toBe("👍🏽");
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
it("moves past an empty message instead of stopping", async () => {
|
|
479
|
+
const wrapper = await mountPrompt({
|
|
480
|
+
props: { messages: ["", "B"], typeSpeed: 10, holdDuration: 10, deleteSpeed: 10, pauseDuration: 10 },
|
|
481
|
+
});
|
|
482
|
+
const seen = new Set<string>();
|
|
483
|
+
for (let i = 0; i < 12; i++) {
|
|
484
|
+
vi.advanceTimersByTime(10);
|
|
485
|
+
await nextTick();
|
|
486
|
+
seen.add(text(wrapper));
|
|
487
|
+
}
|
|
488
|
+
expect(seen.has("B")).toBe(true);
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
it("restarts when messages change by value, but not for an equal new array", async () => {
|
|
492
|
+
const wrapper = await mountPrompt({ props: { messages: ["AB"], typeSpeed: 100 } });
|
|
493
|
+
vi.advanceTimersByTime(100);
|
|
494
|
+
await nextTick();
|
|
495
|
+
expect(text(wrapper)).toBe("A");
|
|
496
|
+
|
|
497
|
+
await wrapper.setProps({ messages: ["AB"] });
|
|
498
|
+
expect(text(wrapper)).toBe("A");
|
|
499
|
+
|
|
500
|
+
await wrapper.setProps({ messages: ["XY"] });
|
|
501
|
+
expect(text(wrapper)).toBe("");
|
|
502
|
+
vi.advanceTimersByTime(100);
|
|
503
|
+
await nextTick();
|
|
504
|
+
expect(text(wrapper)).toBe("X");
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
it("shows the whole message at once under reduced motion", async () => {
|
|
508
|
+
const restore = stubReducedMotion();
|
|
509
|
+
try {
|
|
510
|
+
const wrapper = await mountPrompt({ props: { messages: ["HELLO"], typeSpeed: 100 } });
|
|
511
|
+
vi.advanceTimersByTime(100);
|
|
512
|
+
await nextTick();
|
|
513
|
+
expect(text(wrapper)).toBe("HELLO");
|
|
514
|
+
} finally {
|
|
515
|
+
restore();
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
});
|
|
@@ -1,9 +1,22 @@
|
|
|
1
|
-
import { describe, it, expect, afterEach } from "vitest";
|
|
1
|
+
import { describe, it, expect, afterEach, vi } from "vitest";
|
|
2
2
|
import { mountSuspended } from "@nuxt/test-utils/runtime";
|
|
3
3
|
import { nextTick } from "vue";
|
|
4
4
|
import SamaritanPromptMixed from "../SamaritanPromptMixed.vue";
|
|
5
5
|
import type { SamaritanPromptMessageConfig as MessageConfig } from "~/types/components/samaritan-prompt";
|
|
6
6
|
|
|
7
|
+
const mounted: Awaited<ReturnType<typeof mountSuspended>>[] = [];
|
|
8
|
+
|
|
9
|
+
const mountMixed = async (options: Parameters<typeof mountSuspended<typeof SamaritanPromptMixed>>[1]) => {
|
|
10
|
+
const wrapper = await mountSuspended(SamaritanPromptMixed, options);
|
|
11
|
+
mounted.push(wrapper);
|
|
12
|
+
return wrapper;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
mounted.splice(0).forEach((wrapper) => wrapper.unmount());
|
|
17
|
+
vi.restoreAllMocks();
|
|
18
|
+
});
|
|
19
|
+
|
|
7
20
|
// ─── Typewriter ───────────────────────────────────────────────────────────────
|
|
8
21
|
//
|
|
9
22
|
// runTypewriter sets the first char and hides the cursor synchronously on mount,
|
|
@@ -22,30 +35,26 @@ describe("SamaritanPromptMixed — typewriter", () => {
|
|
|
22
35
|
|
|
23
36
|
let wrapper: Awaited<ReturnType<typeof mountSuspended>>;
|
|
24
37
|
|
|
25
|
-
afterEach(() => {
|
|
26
|
-
wrapper?.unmount();
|
|
27
|
-
});
|
|
28
|
-
|
|
29
38
|
it("mounts without error", async () => {
|
|
30
|
-
wrapper = await
|
|
39
|
+
wrapper = await mountMixed({ props: { messageConfigs: [config] } });
|
|
31
40
|
expect(wrapper.vm).toBeTruthy();
|
|
32
41
|
});
|
|
33
42
|
|
|
34
43
|
it("renders cursor, underline, and stage", async () => {
|
|
35
|
-
wrapper = await
|
|
44
|
+
wrapper = await mountMixed({ props: { messageConfigs: [config] } });
|
|
36
45
|
expect(wrapper.find(".samaritan-prompt__cursor").text()).toBe("▲");
|
|
37
46
|
expect(wrapper.find(".samaritan-prompt__underline").exists()).toBe(true);
|
|
38
47
|
expect(wrapper.find(".samaritan-prompt__stage").exists()).toBe(true);
|
|
39
48
|
});
|
|
40
49
|
|
|
41
50
|
it("types the first character immediately on mount", async () => {
|
|
42
|
-
wrapper = await
|
|
51
|
+
wrapper = await mountMixed({ props: { messageConfigs: [config], introDelay: 0 } });
|
|
43
52
|
await nextTick();
|
|
44
53
|
expect(wrapper.find(".samaritan-prompt__text").text()).toBe("H");
|
|
45
54
|
});
|
|
46
55
|
|
|
47
56
|
it("types one character per typeSpeed tick", async () => {
|
|
48
|
-
wrapper = await
|
|
57
|
+
wrapper = await mountMixed({ props: { messageConfigs: [config], introDelay: 0 } });
|
|
49
58
|
await nextTick();
|
|
50
59
|
expect(wrapper.find(".samaritan-prompt__text").text()).toBe("H");
|
|
51
60
|
|
|
@@ -56,13 +65,13 @@ describe("SamaritanPromptMixed — typewriter", () => {
|
|
|
56
65
|
});
|
|
57
66
|
|
|
58
67
|
it("hides cursor when typing starts", async () => {
|
|
59
|
-
wrapper = await
|
|
68
|
+
wrapper = await mountMixed({ props: { messageConfigs: [config], introDelay: 0 } });
|
|
60
69
|
await nextTick();
|
|
61
70
|
expect(wrapper.find(".samaritan-prompt__cursor").attributes("style")).toContain("opacity: 0");
|
|
62
71
|
});
|
|
63
72
|
|
|
64
73
|
it("cursor stays visible when hideCursorInCycle is false", async () => {
|
|
65
|
-
wrapper = await
|
|
74
|
+
wrapper = await mountMixed({
|
|
66
75
|
props: { messageConfigs: [{ ...config, hideCursorInCycle: false }] },
|
|
67
76
|
});
|
|
68
77
|
await nextTick();
|
|
@@ -78,7 +87,7 @@ describe("SamaritanPromptMixed — typewriter", () => {
|
|
|
78
87
|
holdDuration: 200,
|
|
79
88
|
pauseDuration: 100,
|
|
80
89
|
};
|
|
81
|
-
wrapper = await
|
|
90
|
+
wrapper = await mountMixed({ props: { messageConfigs: [short] } });
|
|
82
91
|
await nextTick(); // "H" — first char immediate
|
|
83
92
|
|
|
84
93
|
vi.advanceTimersByTime(100); // fire i=1 timer → "HI" typed, await wait(100) for i=2
|
|
@@ -106,7 +115,7 @@ describe("SamaritanPromptMixed — typewriter", () => {
|
|
|
106
115
|
holdDuration: 200,
|
|
107
116
|
pauseDuration: 100,
|
|
108
117
|
};
|
|
109
|
-
wrapper = await
|
|
118
|
+
wrapper = await mountMixed({ props: { messageConfigs: [short] } });
|
|
110
119
|
await nextTick(); // "H" — cursor hidden
|
|
111
120
|
|
|
112
121
|
vi.advanceTimersByTime(100); // exit type loop (single char)
|
|
@@ -125,7 +134,7 @@ describe("SamaritanPromptMixed — typewriter", () => {
|
|
|
125
134
|
});
|
|
126
135
|
|
|
127
136
|
it("applies styleClassPassthrough", async () => {
|
|
128
|
-
wrapper = await
|
|
137
|
+
wrapper = await mountMixed({
|
|
129
138
|
props: { messageConfigs: [config], styleClassPassthrough: "custom-class" },
|
|
130
139
|
});
|
|
131
140
|
expect(wrapper.find(".samaritan-prompt").classes()).toContain("custom-class");
|
|
@@ -150,24 +159,20 @@ describe("SamaritanPromptMixed — word-pulse", () => {
|
|
|
150
159
|
|
|
151
160
|
let wrapper: Awaited<ReturnType<typeof mountSuspended>>;
|
|
152
161
|
|
|
153
|
-
afterEach(() => {
|
|
154
|
-
wrapper?.unmount();
|
|
155
|
-
});
|
|
156
|
-
|
|
157
162
|
it("mounts without error", async () => {
|
|
158
|
-
wrapper = await
|
|
163
|
+
wrapper = await mountMixed({ props: { messageConfigs: [config] } });
|
|
159
164
|
expect(wrapper.vm).toBeTruthy();
|
|
160
165
|
});
|
|
161
166
|
|
|
162
167
|
it("starts with empty display text", async () => {
|
|
163
168
|
// mountSuspended flushes pending promises, resolving the component's initial await nextTick()
|
|
164
169
|
// which immediately triggers the fade-out. Text is still empty until fadeDuration elapses.
|
|
165
|
-
wrapper = await
|
|
170
|
+
wrapper = await mountMixed({ props: { messageConfigs: [config] } });
|
|
166
171
|
expect(wrapper.find(".samaritan-prompt__text").text()).toBe("");
|
|
167
172
|
});
|
|
168
173
|
|
|
169
174
|
it("fades content out when cycle starts", async () => {
|
|
170
|
-
wrapper = await
|
|
175
|
+
wrapper = await mountMixed({ props: { messageConfigs: [config], introDelay: 0 } });
|
|
171
176
|
await nextTick(); // component's await nextTick() resolves → sets opacity=0
|
|
172
177
|
await nextTick(); // DOM update
|
|
173
178
|
|
|
@@ -175,7 +180,7 @@ describe("SamaritanPromptMixed — word-pulse", () => {
|
|
|
175
180
|
});
|
|
176
181
|
|
|
177
182
|
it("hides cursor when cycle starts", async () => {
|
|
178
|
-
wrapper = await
|
|
183
|
+
wrapper = await mountMixed({ props: { messageConfigs: [config], introDelay: 0 } });
|
|
179
184
|
await nextTick();
|
|
180
185
|
await nextTick();
|
|
181
186
|
|
|
@@ -183,7 +188,7 @@ describe("SamaritanPromptMixed — word-pulse", () => {
|
|
|
183
188
|
});
|
|
184
189
|
|
|
185
190
|
it("cursor stays visible when hideCursorInCycle is false", async () => {
|
|
186
|
-
wrapper = await
|
|
191
|
+
wrapper = await mountMixed({
|
|
187
192
|
props: { messageConfigs: [{ ...config, hideCursorInCycle: false }] },
|
|
188
193
|
});
|
|
189
194
|
await nextTick();
|
|
@@ -193,7 +198,7 @@ describe("SamaritanPromptMixed — word-pulse", () => {
|
|
|
193
198
|
});
|
|
194
199
|
|
|
195
200
|
it("shows text and restores opacity after fade duration and settle delay", async () => {
|
|
196
|
-
wrapper = await
|
|
201
|
+
wrapper = await mountMixed({ props: { messageConfigs: [config], introDelay: 0 } });
|
|
197
202
|
await nextTick(); // component's nextTick resolves → opacity=0, await wait(fadeDuration)
|
|
198
203
|
await nextTick(); // DOM update
|
|
199
204
|
|
|
@@ -212,7 +217,7 @@ describe("SamaritanPromptMixed — word-pulse", () => {
|
|
|
212
217
|
});
|
|
213
218
|
|
|
214
219
|
it("fades text back out after word duration", async () => {
|
|
215
|
-
wrapper = await
|
|
220
|
+
wrapper = await mountMixed({ props: { messageConfigs: [config], introDelay: 0 } });
|
|
216
221
|
await nextTick();
|
|
217
222
|
await nextTick();
|
|
218
223
|
|
|
@@ -237,12 +242,8 @@ describe("SamaritanPromptMixed — word-pulse", () => {
|
|
|
237
242
|
// ─── Config resolution ────────────────────────────────────────────────────────
|
|
238
243
|
|
|
239
244
|
describe("SamaritanPromptMixed — config resolution", () => {
|
|
240
|
-
afterEach(() => {
|
|
241
|
-
// wrappers are unmounted inline in each test
|
|
242
|
-
});
|
|
243
|
-
|
|
244
245
|
it("falls back to global effect prop when not specified per message", async () => {
|
|
245
|
-
const wrapper = await
|
|
246
|
+
const wrapper = await mountMixed({
|
|
246
247
|
props: {
|
|
247
248
|
messageConfigs: [{ text: "TEST" }], // no effect
|
|
248
249
|
introDelay: 0,
|
|
@@ -253,11 +254,10 @@ describe("SamaritanPromptMixed — config resolution", () => {
|
|
|
253
254
|
await nextTick();
|
|
254
255
|
// typewriter first char is immediate
|
|
255
256
|
expect(wrapper.find(".samaritan-prompt__text").text()).toBe("T");
|
|
256
|
-
wrapper.unmount();
|
|
257
257
|
});
|
|
258
258
|
|
|
259
259
|
it("falls back to global typeSpeed when not specified per message", async () => {
|
|
260
|
-
const wrapper = await
|
|
260
|
+
const wrapper = await mountMixed({
|
|
261
261
|
props: {
|
|
262
262
|
messageConfigs: [{ text: "HI", effect: "typewriter" }],
|
|
263
263
|
introDelay: 0,
|
|
@@ -276,11 +276,10 @@ describe("SamaritanPromptMixed — config resolution", () => {
|
|
|
276
276
|
await nextTick();
|
|
277
277
|
expect(wrapper.find(".samaritan-prompt__text").text()).toBe("HI");
|
|
278
278
|
|
|
279
|
-
wrapper.unmount();
|
|
280
279
|
});
|
|
281
280
|
|
|
282
281
|
it("per-message holdDuration overrides global default", async () => {
|
|
283
|
-
const wrapper = await
|
|
282
|
+
const wrapper = await mountMixed({
|
|
284
283
|
props: {
|
|
285
284
|
messageConfigs: [
|
|
286
285
|
{ text: "H", effect: "typewriter", typeSpeed: 100, holdDuration: 200, deleteSpeed: 50, pauseDuration: 100 },
|
|
@@ -303,7 +302,6 @@ describe("SamaritanPromptMixed — config resolution", () => {
|
|
|
303
302
|
await nextTick();
|
|
304
303
|
|
|
305
304
|
expect(wrapper.find(".samaritan-prompt__text").text()).toBe("");
|
|
306
|
-
wrapper.unmount();
|
|
307
305
|
});
|
|
308
306
|
});
|
|
309
307
|
|
|
@@ -321,12 +319,8 @@ describe("SamaritanPromptMixed — accessibility and pause", () => {
|
|
|
321
319
|
|
|
322
320
|
let wrapper: Awaited<ReturnType<typeof mountSuspended>>;
|
|
323
321
|
|
|
324
|
-
afterEach(() => {
|
|
325
|
-
wrapper?.unmount();
|
|
326
|
-
});
|
|
327
|
-
|
|
328
322
|
it("hides the animated content from assistive tech and exposes a polite live region", async () => {
|
|
329
|
-
wrapper = await
|
|
323
|
+
wrapper = await mountMixed({ props: { messageConfigs: [config], introDelay: 0 } });
|
|
330
324
|
expect(wrapper.find(".samaritan-prompt__content").attributes("aria-hidden")).toBe("true");
|
|
331
325
|
const live = wrapper.find(".samaritan-prompt__sr-text");
|
|
332
326
|
expect(live.attributes("aria-live")).toBe("polite");
|
|
@@ -334,14 +328,14 @@ describe("SamaritanPromptMixed — accessibility and pause", () => {
|
|
|
334
328
|
});
|
|
335
329
|
|
|
336
330
|
it("announces the full message once it is typed", async () => {
|
|
337
|
-
wrapper = await
|
|
331
|
+
wrapper = await mountMixed({ props: { messageConfigs: [config], introDelay: 0 } });
|
|
338
332
|
await vi.advanceTimersByTimeAsync(200);
|
|
339
333
|
await nextTick();
|
|
340
334
|
expect(wrapper.find(".samaritan-prompt__sr-text").text()).toBe("HI");
|
|
341
335
|
});
|
|
342
336
|
|
|
343
337
|
it("sets data-paused on hover and removes it on leave", async () => {
|
|
344
|
-
wrapper = await
|
|
338
|
+
wrapper = await mountMixed({ props: { messageConfigs: [config] } });
|
|
345
339
|
const root = wrapper.find(".samaritan-prompt");
|
|
346
340
|
await root.trigger("pointerenter");
|
|
347
341
|
expect(root.attributes("data-paused")).toBeDefined();
|
|
@@ -350,7 +344,7 @@ describe("SamaritanPromptMixed — accessibility and pause", () => {
|
|
|
350
344
|
});
|
|
351
345
|
|
|
352
346
|
it("stops typing while hovered and resumes on leave", async () => {
|
|
353
|
-
wrapper = await
|
|
347
|
+
wrapper = await mountMixed({
|
|
354
348
|
props: { messageConfigs: [{ ...config, text: "HELLO" }], introDelay: 0 },
|
|
355
349
|
});
|
|
356
350
|
await vi.advanceTimersByTimeAsync(100);
|
|
@@ -369,7 +363,7 @@ describe("SamaritanPromptMixed — accessibility and pause", () => {
|
|
|
369
363
|
});
|
|
370
364
|
|
|
371
365
|
it("renders a custom cursor from the cursor slot", async () => {
|
|
372
|
-
wrapper = await
|
|
366
|
+
wrapper = await mountMixed({
|
|
373
367
|
props: { messageConfigs: [config] },
|
|
374
368
|
slots: { cursor: () => "_" },
|
|
375
369
|
});
|
|
@@ -377,8 +371,61 @@ describe("SamaritanPromptMixed — accessibility and pause", () => {
|
|
|
377
371
|
});
|
|
378
372
|
|
|
379
373
|
it("exposes the fade duration as an inline private token", async () => {
|
|
380
|
-
wrapper = await
|
|
374
|
+
wrapper = await mountMixed({ props: { messageConfigs: [config], fadeDuration: 900 } });
|
|
381
375
|
const style = (wrapper.find(".samaritan-prompt").element as HTMLElement).style;
|
|
382
376
|
expect(style.getPropertyValue("--_fade-duration")).toBe("900ms");
|
|
383
377
|
});
|
|
384
378
|
});
|
|
379
|
+
|
|
380
|
+
// ─── Worst-case data ──────────────────────────────────────────────────────────
|
|
381
|
+
|
|
382
|
+
describe("SamaritanPromptMixed — worst-case data", () => {
|
|
383
|
+
const text = (wrapper: Awaited<ReturnType<typeof mountMixed>>) => wrapper.find(".samaritan-prompt__text").text();
|
|
384
|
+
|
|
385
|
+
it("idles without spinning when messageConfigs is empty and introDelay is 0", async () => {
|
|
386
|
+
const wrapper = await mountMixed({ props: { messageConfigs: [], introDelay: 0 } });
|
|
387
|
+
vi.advanceTimersByTime(2000);
|
|
388
|
+
await nextTick();
|
|
389
|
+
expect(text(wrapper)).toBe("");
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it("starts once messages arrive after an empty start", async () => {
|
|
393
|
+
const wrapper = await mountMixed({ props: { messageConfigs: [], introDelay: 0 } });
|
|
394
|
+
await wrapper.setProps({ messageConfigs: [{ text: "GO", effect: "typewriter", typeSpeed: 100 }] });
|
|
395
|
+
vi.advanceTimersByTime(500);
|
|
396
|
+
await Promise.resolve();
|
|
397
|
+
await nextTick();
|
|
398
|
+
expect(text(wrapper)).toBe("G");
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it("types an emoji with a skin-tone modifier as one character", async () => {
|
|
402
|
+
const wrapper = await mountMixed({
|
|
403
|
+
props: { messageConfigs: [{ text: "👍🏽A", effect: "typewriter", typeSpeed: 100 }], introDelay: 0 },
|
|
404
|
+
});
|
|
405
|
+
await nextTick();
|
|
406
|
+
expect(text(wrapper)).toBe("👍🏽");
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it("shows the whole message at once under reduced motion", async () => {
|
|
410
|
+
const original = window.matchMedia;
|
|
411
|
+
window.matchMedia = ((query: string) => ({
|
|
412
|
+
matches: query.includes("prefers-reduced-motion: reduce"),
|
|
413
|
+
media: query,
|
|
414
|
+
onchange: null,
|
|
415
|
+
addEventListener: () => {},
|
|
416
|
+
removeEventListener: () => {},
|
|
417
|
+
addListener: () => {},
|
|
418
|
+
removeListener: () => {},
|
|
419
|
+
dispatchEvent: () => false,
|
|
420
|
+
})) as unknown as typeof window.matchMedia;
|
|
421
|
+
try {
|
|
422
|
+
const wrapper = await mountMixed({
|
|
423
|
+
props: { messageConfigs: [{ text: "HELLO", effect: "typewriter", typeSpeed: 100 }], introDelay: 0 },
|
|
424
|
+
});
|
|
425
|
+
await nextTick();
|
|
426
|
+
expect(text(wrapper)).toBe("HELLO");
|
|
427
|
+
} finally {
|
|
428
|
+
window.matchMedia = original;
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
});
|