@kahitsan/ksui 0.37.0 → 0.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/base/AddAttachmentTile.tsx +8 -2
- package/src/components/base/BadgeSelect.tsx +8 -2
- package/src/components/base/DatePicker.test.tsx +58 -1
- package/src/components/base/DatePicker.tsx +36 -3
- package/src/components/base/Modal.tsx +9 -1
- package/src/components/composite/ComboBox.tsx +15 -5
- package/src/components/composite/MentionTextarea.tsx +8 -2
- package/src/components/composite/PaymentAccountPicker.tsx +8 -2
- package/src/components/composite/SearchableSelect.tsx +8 -2
- package/src/components/composite/VoucherPicker.test.tsx +341 -21
- package/src/components/composite/VoucherPicker.tsx +396 -135
- package/src/utils/modal-layer.ts +28 -0
- package/src/utils/top-layer.ts +66 -0
|
@@ -1,36 +1,71 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
// point the picker at a peer proxy route with the same response shape.
|
|
1
|
+
// The picker pages the list in from the server (page/limit) and delegates the
|
|
2
|
+
// search to it, so these assert the request contract as well as the rendering.
|
|
4
3
|
import { describe, expect, it, vi } from "vitest";
|
|
5
4
|
import { fireEvent, render, waitFor } from "@solidjs/testing-library";
|
|
6
|
-
import VoucherPicker from "./VoucherPicker";
|
|
5
|
+
import VoucherPicker, { type VoucherOption } from "./VoucherPicker";
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
/** Captures every requested URL and serves pages out of `rows`. */
|
|
8
|
+
function mockPagedFetch(rows: VoucherOption[]) {
|
|
9
|
+
const calls: string[] = [];
|
|
10
|
+
const impl = vi.fn(async (url: string) => {
|
|
11
|
+
calls.push(url);
|
|
12
|
+
const parsed = new URL(url, "http://localhost");
|
|
13
|
+
const page = Number(parsed.searchParams.get("page") ?? "1");
|
|
14
|
+
const limit = Number(parsed.searchParams.get("limit") ?? "25");
|
|
15
|
+
const search = (parsed.searchParams.get("search") ?? "").toLowerCase();
|
|
16
|
+
const matched = search
|
|
17
|
+
? rows.filter((r) => r.code.toLowerCase().includes(search))
|
|
18
|
+
: rows;
|
|
19
|
+
const start = (page - 1) * limit;
|
|
20
|
+
return {
|
|
21
|
+
ok: true,
|
|
22
|
+
json: async () => ({
|
|
23
|
+
data: matched.slice(start, start + limit),
|
|
24
|
+
total: matched.length,
|
|
25
|
+
page,
|
|
26
|
+
limit,
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
}) as unknown as typeof fetch;
|
|
13
30
|
vi.stubGlobal("fetch", impl);
|
|
14
|
-
return impl;
|
|
31
|
+
return { impl, calls };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function voucher(over: Partial<VoucherOption> & Pick<VoucherOption, "id" | "code">): VoucherOption {
|
|
35
|
+
return {
|
|
36
|
+
type: "percentage",
|
|
37
|
+
value: 20,
|
|
38
|
+
max_discount_amount: null,
|
|
39
|
+
applicable_packages: null,
|
|
40
|
+
minimum_purchase: 0,
|
|
41
|
+
valid_from: null,
|
|
42
|
+
valid_until: null,
|
|
43
|
+
is_active: true,
|
|
44
|
+
...over,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function manyVouchers(n: number): VoucherOption[] {
|
|
49
|
+
return Array.from({ length: n }, (_, i) =>
|
|
50
|
+
voucher({ id: i + 1, code: `BULK_${String(i + 1).padStart(3, "0")}` }),
|
|
51
|
+
);
|
|
15
52
|
}
|
|
16
53
|
|
|
17
54
|
describe("VoucherPicker fetchUrl", () => {
|
|
18
55
|
it("defaults to the vouchers plugin's own API when fetchUrl is omitted", async () => {
|
|
19
|
-
const
|
|
56
|
+
const { calls } = mockPagedFetch([]);
|
|
20
57
|
const { getByTestId } = render(() => (
|
|
21
58
|
<VoucherPicker selected={null} onChange={vi.fn()} subtotal={100} packageIds={[]} />
|
|
22
59
|
));
|
|
23
60
|
fireEvent.click(getByTestId("voucher-picker-trigger"));
|
|
24
61
|
|
|
25
|
-
await waitFor(() => expect(
|
|
26
|
-
expect(
|
|
27
|
-
|
|
28
|
-
expect.objectContaining({ credentials: "include" }),
|
|
29
|
-
);
|
|
62
|
+
await waitFor(() => expect(calls.length).toBeGreaterThan(0));
|
|
63
|
+
expect(calls[0]).toContain("/api/vouchers");
|
|
64
|
+
expect(calls[0]).toContain("status=active");
|
|
30
65
|
});
|
|
31
66
|
|
|
32
67
|
it("fetches the overridden URL when fetchUrl is provided", async () => {
|
|
33
|
-
const
|
|
68
|
+
const { calls } = mockPagedFetch([]);
|
|
34
69
|
const { getByTestId } = render(() => (
|
|
35
70
|
<VoucherPicker
|
|
36
71
|
selected={null}
|
|
@@ -42,10 +77,295 @@ describe("VoucherPicker fetchUrl", () => {
|
|
|
42
77
|
));
|
|
43
78
|
fireEvent.click(getByTestId("voucher-picker-trigger"));
|
|
44
79
|
|
|
45
|
-
await waitFor(() => expect(
|
|
46
|
-
expect(
|
|
47
|
-
|
|
48
|
-
|
|
80
|
+
await waitFor(() => expect(calls.length).toBeGreaterThan(0));
|
|
81
|
+
expect(calls[0]).toContain("/api/counter/vouchers");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("requests only the first page up front, not the whole table", async () => {
|
|
85
|
+
const { calls } = mockPagedFetch(manyVouchers(120));
|
|
86
|
+
const { getByTestId, getAllByTestId } = render(() => (
|
|
87
|
+
<VoucherPicker selected={null} onChange={vi.fn()} subtotal={1000} packageIds={[]} />
|
|
88
|
+
));
|
|
89
|
+
fireEvent.click(getByTestId("voucher-picker-trigger"));
|
|
90
|
+
|
|
91
|
+
await waitFor(() =>
|
|
92
|
+
expect(getAllByTestId(/^voucher-picker-result-/).length).toBeGreaterThan(0),
|
|
93
|
+
);
|
|
94
|
+
expect(calls[0]).toContain("page=1");
|
|
95
|
+
expect(calls[0]).toContain("limit=25");
|
|
96
|
+
// 120 rows exist but only the first page is mounted.
|
|
97
|
+
expect(getAllByTestId(/^voucher-picker-result-/).length).toBe(25);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe("VoucherPicker dialog", () => {
|
|
102
|
+
it("opens a dialog and only commits the pick on Confirm", async () => {
|
|
103
|
+
mockPagedFetch([voucher({ id: 1, code: "SAVE20" })]);
|
|
104
|
+
const onChange = vi.fn();
|
|
105
|
+
const { getByTestId, queryByTestId } = render(() => (
|
|
106
|
+
<VoucherPicker selected={null} onChange={onChange} subtotal={1000} packageIds={[]} />
|
|
107
|
+
));
|
|
108
|
+
|
|
109
|
+
expect(queryByTestId("voucher-picker-popup")).toBeNull();
|
|
110
|
+
fireEvent.click(getByTestId("voucher-picker-trigger"));
|
|
111
|
+
|
|
112
|
+
await waitFor(() => expect(getByTestId("voucher-picker-result-1")).toBeTruthy());
|
|
113
|
+
expect(getByTestId("voucher-picker-popup").closest("dialog")).not.toBeNull();
|
|
114
|
+
|
|
115
|
+
// Staging a row must not reach the consumer yet.
|
|
116
|
+
fireEvent.click(getByTestId("voucher-picker-result-1"));
|
|
117
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
118
|
+
expect(getByTestId("voucher-picker-draft-summary").textContent).toContain("SAVE20");
|
|
119
|
+
|
|
120
|
+
fireEvent.click(getByTestId("voucher-picker-confirm"));
|
|
121
|
+
expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ id: 1, code: "SAVE20" }));
|
|
122
|
+
await waitFor(() => expect(queryByTestId("voucher-picker-popup")).toBeNull());
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("discards the staged pick on Cancel", async () => {
|
|
126
|
+
mockPagedFetch([voucher({ id: 1, code: "SAVE20" })]);
|
|
127
|
+
const onChange = vi.fn();
|
|
128
|
+
const { getByTestId, queryByTestId } = render(() => (
|
|
129
|
+
<VoucherPicker selected={null} onChange={onChange} subtotal={1000} packageIds={[]} />
|
|
130
|
+
));
|
|
131
|
+
fireEvent.click(getByTestId("voucher-picker-trigger"));
|
|
132
|
+
await waitFor(() => expect(getByTestId("voucher-picker-result-1")).toBeTruthy());
|
|
133
|
+
|
|
134
|
+
fireEvent.click(getByTestId("voucher-picker-result-1"));
|
|
135
|
+
fireEvent.click(getByTestId("voucher-picker-cancel"));
|
|
136
|
+
|
|
137
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
138
|
+
await waitFor(() => expect(queryByTestId("voucher-picker-popup")).toBeNull());
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("re-tapping the staged row unstages it", async () => {
|
|
142
|
+
mockPagedFetch([voucher({ id: 1, code: "SAVE20" })]);
|
|
143
|
+
const { getByTestId } = render(() => (
|
|
144
|
+
<VoucherPicker selected={null} onChange={vi.fn()} subtotal={1000} packageIds={[]} />
|
|
145
|
+
));
|
|
146
|
+
fireEvent.click(getByTestId("voucher-picker-trigger"));
|
|
147
|
+
await waitFor(() => expect(getByTestId("voucher-picker-result-1")).toBeTruthy());
|
|
148
|
+
|
|
149
|
+
fireEvent.click(getByTestId("voucher-picker-result-1"));
|
|
150
|
+
expect(getByTestId("voucher-picker-draft-summary").textContent).toContain("SAVE20");
|
|
151
|
+
|
|
152
|
+
fireEvent.click(getByTestId("voucher-picker-result-1"));
|
|
153
|
+
expect(getByTestId("voucher-picker-draft-summary").textContent).toContain("No voucher selected");
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("delegates the search to the server and highlights the match", async () => {
|
|
157
|
+
const { calls } = mockPagedFetch([
|
|
158
|
+
voucher({ id: 1, code: "SAVE20" }),
|
|
159
|
+
voucher({ id: 2, code: "PARTNER_ACES" }),
|
|
160
|
+
]);
|
|
161
|
+
const { getByTestId, queryByTestId } = render(() => (
|
|
162
|
+
<VoucherPicker selected={null} onChange={vi.fn()} subtotal={1000} packageIds={[]} />
|
|
163
|
+
));
|
|
164
|
+
fireEvent.click(getByTestId("voucher-picker-trigger"));
|
|
165
|
+
await waitFor(() => expect(getByTestId("voucher-picker-result-1")).toBeTruthy());
|
|
166
|
+
|
|
167
|
+
fireEvent.input(getByTestId("voucher-picker-search"), { target: { value: "partner" } });
|
|
168
|
+
|
|
169
|
+
await waitFor(() => expect(calls.some((u) => u.includes("search=partner"))).toBe(true));
|
|
170
|
+
await waitFor(() => expect(queryByTestId("voucher-picker-result-1")).toBeNull());
|
|
171
|
+
|
|
172
|
+
const row = getByTestId("voucher-picker-result-2");
|
|
173
|
+
const mark = row.querySelector("mark");
|
|
174
|
+
expect(mark).not.toBeNull();
|
|
175
|
+
expect(mark!.textContent?.toLowerCase()).toBe("partner");
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("shows when a voucher expires", async () => {
|
|
179
|
+
const soon = new Date(Date.now() + 3 * 86400000).toISOString().slice(0, 10);
|
|
180
|
+
mockPagedFetch([voucher({ id: 9, code: "ENDINGSOON", valid_until: soon })]);
|
|
181
|
+
const { getByTestId } = render(() => (
|
|
182
|
+
<VoucherPicker selected={null} onChange={vi.fn()} subtotal={1000} packageIds={[]} />
|
|
183
|
+
));
|
|
184
|
+
fireEvent.click(getByTestId("voucher-picker-trigger"));
|
|
185
|
+
|
|
186
|
+
await waitFor(() => expect(getByTestId("voucher-picker-result-9")).toBeTruthy());
|
|
187
|
+
expect(getByTestId("voucher-picker-result-9").textContent).toContain("Expires in 3 days");
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("reads a full timestamp date the same as a bare date", async () => {
|
|
191
|
+
const soon = new Date(Date.now() + 3 * 86400000).toISOString().slice(0, 10);
|
|
192
|
+
mockPagedFetch([
|
|
193
|
+
voucher({ id: 10, code: "TIMESTAMPED", valid_until: `${soon}T16:00:00.000Z` }),
|
|
194
|
+
]);
|
|
195
|
+
const { getByTestId } = render(() => (
|
|
196
|
+
<VoucherPicker selected={null} onChange={vi.fn()} subtotal={1000} packageIds={[]} />
|
|
197
|
+
));
|
|
198
|
+
fireEvent.click(getByTestId("voucher-picker-trigger"));
|
|
199
|
+
|
|
200
|
+
await waitFor(() => expect(getByTestId("voucher-picker-result-10")).toBeTruthy());
|
|
201
|
+
const text = getByTestId("voucher-picker-result-10").textContent ?? "";
|
|
202
|
+
expect(text).toContain("Expires in 3 days");
|
|
203
|
+
expect(text).not.toContain("T16:00:00");
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("shows how many redemptions are used against the limit", async () => {
|
|
207
|
+
mockPagedFetch([
|
|
208
|
+
voucher({ id: 20, code: "LIMITED", usage_count: 3, usage_limit_total: 10 }),
|
|
209
|
+
]);
|
|
210
|
+
const { getByTestId } = render(() => (
|
|
211
|
+
<VoucherPicker selected={null} onChange={vi.fn()} subtotal={1000} packageIds={[]} />
|
|
212
|
+
));
|
|
213
|
+
fireEvent.click(getByTestId("voucher-picker-trigger"));
|
|
214
|
+
|
|
215
|
+
await waitFor(() => expect(getByTestId("voucher-picker-result-20")).toBeTruthy());
|
|
216
|
+
expect(getByTestId("voucher-picker-result-20").textContent).toContain("3/10 used");
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("omits the usage line for an unlimited voucher", async () => {
|
|
220
|
+
mockPagedFetch([voucher({ id: 21, code: "UNLIMITED", usage_count: 42 })]);
|
|
221
|
+
const { getByTestId } = render(() => (
|
|
222
|
+
<VoucherPicker selected={null} onChange={vi.fn()} subtotal={1000} packageIds={[]} />
|
|
223
|
+
));
|
|
224
|
+
fireEvent.click(getByTestId("voucher-picker-trigger"));
|
|
225
|
+
|
|
226
|
+
await waitFor(() => expect(getByTestId("voucher-picker-result-21")).toBeTruthy());
|
|
227
|
+
expect(getByTestId("voucher-picker-result-21").textContent).not.toContain("used");
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("blocks a fully-redeemed voucher the same way the server does", async () => {
|
|
231
|
+
mockPagedFetch([
|
|
232
|
+
voucher({ id: 22, code: "SPENT", usage_count: 10, usage_limit_total: 10 }),
|
|
233
|
+
]);
|
|
234
|
+
const { getByTestId, queryByTestId } = render(() => (
|
|
235
|
+
<VoucherPicker selected={null} onChange={vi.fn()} subtotal={1000} packageIds={[]} />
|
|
236
|
+
));
|
|
237
|
+
fireEvent.click(getByTestId("voucher-picker-trigger"));
|
|
238
|
+
|
|
239
|
+
await waitFor(() => expect(getByTestId("voucher-picker-inapplicable-22")).toBeTruthy());
|
|
240
|
+
// Never selectable — it would be rejected at charge time.
|
|
241
|
+
expect(queryByTestId("voucher-picker-result-22")).toBeNull();
|
|
242
|
+
expect(getByTestId("voucher-picker-inapplicable-22").textContent).toContain("Fully redeemed");
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it("explains why an ineligible voucher can't be used", async () => {
|
|
246
|
+
mockPagedFetch([voucher({ id: 7, code: "BIGSPEND", minimum_purchase: 5000 })]);
|
|
247
|
+
const { getByTestId } = render(() => (
|
|
248
|
+
<VoucherPicker selected={null} onChange={vi.fn()} subtotal={1000} packageIds={[]} />
|
|
249
|
+
));
|
|
250
|
+
fireEvent.click(getByTestId("voucher-picker-trigger"));
|
|
251
|
+
|
|
252
|
+
await waitFor(() => expect(getByTestId("voucher-picker-inapplicable-7")).toBeTruthy());
|
|
253
|
+
expect(getByTestId("voucher-picker-inapplicable-7").textContent).toContain("minimum");
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it("previews a discount range while nothing is priced yet", async () => {
|
|
257
|
+
mockPagedFetch([voucher({ id: 3, code: "SAVE20" })]);
|
|
258
|
+
const { getByTestId } = render(() => (
|
|
259
|
+
<VoucherPicker
|
|
260
|
+
selected={null}
|
|
261
|
+
onChange={vi.fn()}
|
|
262
|
+
subtotal={0}
|
|
263
|
+
subtotalRange={{ min: 99, max: 118 }}
|
|
264
|
+
packageIds={[]}
|
|
265
|
+
/>
|
|
266
|
+
));
|
|
267
|
+
fireEvent.click(getByTestId("voucher-picker-trigger"));
|
|
268
|
+
|
|
269
|
+
await waitFor(() => expect(getByTestId("voucher-picker-result-3")).toBeTruthy());
|
|
270
|
+
// 20% of 99 and of 118, both rounded the same way the server rounds.
|
|
271
|
+
expect(getByTestId("voucher-picker-result-3").textContent).toContain("₱20.00 to ₱24.00");
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("shows a single amount once the cart has a real subtotal", async () => {
|
|
275
|
+
mockPagedFetch([voucher({ id: 4, code: "SAVE20" })]);
|
|
276
|
+
const { getByTestId } = render(() => (
|
|
277
|
+
<VoucherPicker
|
|
278
|
+
selected={null}
|
|
279
|
+
onChange={vi.fn()}
|
|
280
|
+
subtotal={99}
|
|
281
|
+
subtotalRange={{ min: 99, max: 118 }}
|
|
282
|
+
packageIds={[]}
|
|
283
|
+
/>
|
|
284
|
+
));
|
|
285
|
+
fireEvent.click(getByTestId("voucher-picker-trigger"));
|
|
286
|
+
|
|
287
|
+
await waitFor(() => expect(getByTestId("voucher-picker-result-4")).toBeTruthy());
|
|
288
|
+
const text = getByTestId("voucher-picker-result-4").textContent ?? "";
|
|
289
|
+
expect(text).toContain("₱20.00");
|
|
290
|
+
expect(text).not.toContain("₱24.00");
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("names the specific reason each ineligible voucher can't be used", async () => {
|
|
294
|
+
const yesterday = new Date(Date.now() - 86400000).toISOString().slice(0, 10);
|
|
295
|
+
const nextWeek = new Date(Date.now() + 7 * 86400000).toISOString().slice(0, 10);
|
|
296
|
+
mockPagedFetch([
|
|
297
|
+
voucher({ id: 30, code: "TOO_SMALL", minimum_purchase: 5000 }),
|
|
298
|
+
voucher({ id: 31, code: "GONE", valid_until: yesterday }),
|
|
299
|
+
voucher({ id: 32, code: "NOT_YET", valid_from: nextWeek }),
|
|
300
|
+
voucher({ id: 33, code: "OFF", is_active: false }),
|
|
301
|
+
voucher({ id: 34, code: "OTHER_ITEMS", applicable_packages: [99] }),
|
|
302
|
+
]);
|
|
303
|
+
const { getByTestId } = render(() => (
|
|
304
|
+
<VoucherPicker selected={null} onChange={vi.fn()} subtotal={1000} packageIds={[1]} />
|
|
305
|
+
));
|
|
306
|
+
fireEvent.click(getByTestId("voucher-picker-trigger"));
|
|
307
|
+
|
|
308
|
+
await waitFor(() => expect(getByTestId("voucher-picker-inapplicable-30")).toBeTruthy());
|
|
309
|
+
expect(getByTestId("voucher-picker-inapplicable-30").textContent).toContain("minimum");
|
|
310
|
+
expect(getByTestId("voucher-picker-inapplicable-31").textContent).toContain("Expired");
|
|
311
|
+
expect(getByTestId("voucher-picker-inapplicable-32").textContent).toContain("Starts");
|
|
312
|
+
expect(getByTestId("voucher-picker-inapplicable-33").textContent).toContain("Inactive");
|
|
313
|
+
expect(getByTestId("voucher-picker-inapplicable-34").textContent).toContain(
|
|
314
|
+
"Doesn't cover every item",
|
|
315
|
+
);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it("surfaces a load failure instead of rendering an empty list", async () => {
|
|
319
|
+
vi.stubGlobal(
|
|
320
|
+
"fetch",
|
|
321
|
+
vi.fn(async () => ({ ok: false, status: 403, json: async () => ({}) })) as unknown as typeof fetch,
|
|
322
|
+
);
|
|
323
|
+
const { getByTestId, queryByTestId } = render(() => (
|
|
324
|
+
<VoucherPicker selected={null} onChange={vi.fn()} subtotal={1000} packageIds={[]} />
|
|
325
|
+
));
|
|
326
|
+
fireEvent.click(getByTestId("voucher-picker-trigger"));
|
|
327
|
+
|
|
328
|
+
await waitFor(() =>
|
|
329
|
+
expect(getByTestId("voucher-picker-popup").textContent).toContain("Permission denied"),
|
|
49
330
|
);
|
|
331
|
+
// The misleading "nothing here" copy must not stand in for a real failure.
|
|
332
|
+
expect(queryByTestId("voucher-picker-popup")!.textContent).not.toContain(
|
|
333
|
+
"No vouchers available.",
|
|
334
|
+
);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it("reopening discards a pick that was staged but never confirmed", async () => {
|
|
338
|
+
mockPagedFetch([voucher({ id: 40, code: "STAGED" }), voucher({ id: 41, code: "OTHER" })]);
|
|
339
|
+
const onChange = vi.fn();
|
|
340
|
+
const { getByTestId } = render(() => (
|
|
341
|
+
<VoucherPicker selected={null} onChange={onChange} subtotal={1000} packageIds={[]} />
|
|
342
|
+
));
|
|
343
|
+
|
|
344
|
+
fireEvent.click(getByTestId("voucher-picker-trigger"));
|
|
345
|
+
await waitFor(() => expect(getByTestId("voucher-picker-result-40")).toBeTruthy());
|
|
346
|
+
fireEvent.click(getByTestId("voucher-picker-result-40"));
|
|
347
|
+
fireEvent.click(getByTestId("voucher-picker-cancel"));
|
|
348
|
+
|
|
349
|
+
fireEvent.click(getByTestId("voucher-picker-trigger"));
|
|
350
|
+
await waitFor(() => expect(getByTestId("voucher-picker-result-40")).toBeTruthy());
|
|
351
|
+
expect(getByTestId("voucher-picker-draft-summary").textContent).toContain("No voucher selected");
|
|
352
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it("keeps Escape from reaching an ancestor's document-level dismiss handler", async () => {
|
|
356
|
+
mockPagedFetch([]);
|
|
357
|
+
const ancestorEsc = vi.fn();
|
|
358
|
+
document.addEventListener("keydown", ancestorEsc);
|
|
359
|
+
|
|
360
|
+
const { getByTestId } = render(() => (
|
|
361
|
+
<VoucherPicker selected={null} onChange={vi.fn()} subtotal={100} packageIds={[]} />
|
|
362
|
+
));
|
|
363
|
+
fireEvent.click(getByTestId("voucher-picker-trigger"));
|
|
364
|
+
await waitFor(() => expect(getByTestId("voucher-picker-popup")).toBeTruthy());
|
|
365
|
+
|
|
366
|
+
fireEvent.keyDown(getByTestId("voucher-picker-search"), { key: "Escape" });
|
|
367
|
+
expect(ancestorEsc).not.toHaveBeenCalled();
|
|
368
|
+
|
|
369
|
+
document.removeEventListener("keydown", ancestorEsc);
|
|
50
370
|
});
|
|
51
371
|
});
|