@blotch/dolan 0.0.0-stage → 22.2.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/dist/commands/add.d.ts +22 -0
- package/dist/commands/add.js +132 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/add.test.d.ts +1 -0
- package/dist/commands/add.test.js +209 -0
- package/dist/commands/add.test.js.map +1 -0
- package/dist/commands/diff.d.ts +13 -0
- package/dist/commands/diff.js +155 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/diff.test.d.ts +1 -0
- package/dist/commands/diff.test.js +327 -0
- package/dist/commands/diff.test.js.map +1 -0
- package/dist/commands/doctor.d.ts +15 -0
- package/dist/commands/doctor.js +248 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/doctor.test.d.ts +1 -0
- package/dist/commands/doctor.test.js +301 -0
- package/dist/commands/doctor.test.js.map +1 -0
- package/dist/commands/eject.d.ts +11 -0
- package/dist/commands/eject.js +55 -0
- package/dist/commands/eject.js.map +1 -0
- package/dist/commands/eject.test.d.ts +1 -0
- package/dist/commands/eject.test.js +151 -0
- package/dist/commands/eject.test.js.map +1 -0
- package/dist/commands/info.d.ts +1 -0
- package/dist/commands/info.js +73 -0
- package/dist/commands/info.js.map +1 -0
- package/dist/commands/info.test.d.ts +1 -0
- package/dist/commands/info.test.js +143 -0
- package/dist/commands/info.test.js.map +1 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +98 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/init.test.d.ts +1 -0
- package/dist/commands/init.test.js +154 -0
- package/dist/commands/init.test.js.map +1 -0
- package/dist/commands/list.d.ts +16 -0
- package/dist/commands/list.js +124 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/list.test.d.ts +1 -0
- package/dist/commands/list.test.js +285 -0
- package/dist/commands/list.test.js.map +1 -0
- package/dist/commands/outdated.d.ts +13 -0
- package/dist/commands/outdated.js +116 -0
- package/dist/commands/outdated.js.map +1 -0
- package/dist/commands/outdated.test.d.ts +1 -0
- package/dist/commands/outdated.test.js +208 -0
- package/dist/commands/outdated.test.js.map +1 -0
- package/dist/commands/remove.d.ts +18 -0
- package/dist/commands/remove.js +161 -0
- package/dist/commands/remove.js.map +1 -0
- package/dist/commands/remove.test.d.ts +1 -0
- package/dist/commands/remove.test.js +361 -0
- package/dist/commands/remove.test.js.map +1 -0
- package/dist/commands/theme.d.ts +1 -0
- package/dist/commands/theme.js +66 -0
- package/dist/commands/theme.js.map +1 -0
- package/dist/commands/theme.test.d.ts +1 -0
- package/dist/commands/theme.test.js +133 -0
- package/dist/commands/theme.test.js.map +1 -0
- package/dist/commands/update.d.ts +31 -0
- package/dist/commands/update.js +601 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/update.test.d.ts +1 -0
- package/dist/commands/update.test.js +1233 -0
- package/dist/commands/update.test.js.map +1 -0
- package/dist/config.d.ts +11 -0
- package/dist/config.js +19 -0
- package/dist/config.js.map +1 -0
- package/dist/config.test.d.ts +1 -0
- package/dist/config.test.js +48 -0
- package/dist/config.test.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +89 -0
- package/dist/index.js.map +1 -0
- package/dist/lockfile.d.ts +21 -0
- package/dist/lockfile.js +39 -0
- package/dist/lockfile.js.map +1 -0
- package/dist/registry.d.ts +14 -0
- package/dist/registry.js +85 -0
- package/dist/registry.js.map +1 -0
- package/dist/registry.test.d.ts +1 -0
- package/dist/registry.test.js +95 -0
- package/dist/registry.test.js.map +1 -0
- package/dist/test-utils.d.ts +16 -0
- package/dist/test-utils.js +65 -0
- package/dist/test-utils.js.map +1 -0
- package/dist/ui/components/accordion/accordion.test.ts +194 -0
- package/dist/ui/components/accordion/accordion.ts +173 -0
- package/dist/ui/components/accordion/index.ts +6 -0
- package/dist/ui/components/action-sheet/action-sheet.test.ts +211 -0
- package/dist/ui/components/action-sheet/action-sheet.ts +353 -0
- package/dist/ui/components/action-sheet/index.ts +6 -0
- package/dist/ui/components/alert/alert.test.ts +48 -0
- package/dist/ui/components/alert/alert.ts +104 -0
- package/dist/ui/components/alert/index.ts +1 -0
- package/dist/ui/components/alert-dialog/alert-dialog.test.ts +141 -0
- package/dist/ui/components/alert-dialog/alert-dialog.ts +243 -0
- package/dist/ui/components/alert-dialog/index.ts +7 -0
- package/dist/ui/components/area-chart/area-chart-render.test.ts +121 -0
- package/dist/ui/components/area-chart/area-chart.test.ts +158 -0
- package/dist/ui/components/area-chart/area-chart.ts +349 -0
- package/dist/ui/components/area-chart/index.ts +6 -0
- package/dist/ui/components/aspect-ratio/aspect-ratio.test.ts +10 -0
- package/dist/ui/components/aspect-ratio/aspect-ratio.ts +28 -0
- package/dist/ui/components/aspect-ratio/index.ts +1 -0
- package/dist/ui/components/avatar/avatar.test.ts +69 -0
- package/dist/ui/components/avatar/avatar.ts +131 -0
- package/dist/ui/components/avatar/index.ts +1 -0
- package/dist/ui/components/badge/badge.test.ts +49 -0
- package/dist/ui/components/badge/badge.ts +97 -0
- package/dist/ui/components/badge/index.ts +1 -0
- package/dist/ui/components/bar-chart/bar-chart-render.test.ts +174 -0
- package/dist/ui/components/bar-chart/bar-chart.test.ts +73 -0
- package/dist/ui/components/bar-chart/bar-chart.ts +327 -0
- package/dist/ui/components/bar-chart/index.ts +6 -0
- package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +163 -0
- package/dist/ui/components/bottom-sheet/bottom-sheet.ts +339 -0
- package/dist/ui/components/bottom-sheet/index.ts +1 -0
- package/dist/ui/components/button/button-render.test.ts +113 -0
- package/dist/ui/components/button/button.test.ts +70 -0
- package/dist/ui/components/button/button.ts +170 -0
- package/dist/ui/components/button/index.ts +6 -0
- package/dist/ui/components/button-group/button-group-render.test.ts +146 -0
- package/dist/ui/components/button-group/button-group.test.ts +54 -0
- package/dist/ui/components/button-group/button-group.ts +209 -0
- package/dist/ui/components/button-group/index.ts +8 -0
- package/dist/ui/components/candlestick-chart/candlestick-chart-render.test.ts +174 -0
- package/dist/ui/components/candlestick-chart/candlestick-chart.test.ts +96 -0
- package/dist/ui/components/candlestick-chart/candlestick-chart.ts +355 -0
- package/dist/ui/components/candlestick-chart/index.ts +7 -0
- package/dist/ui/components/card/card.test.ts +110 -0
- package/dist/ui/components/card/card.ts +162 -0
- package/dist/ui/components/card/index.ts +8 -0
- package/dist/ui/components/cartesian-chart/cartesian-chart-render.test.ts +188 -0
- package/dist/ui/components/cartesian-chart/cartesian-chart.test.ts +549 -0
- package/dist/ui/components/cartesian-chart/cartesian-chart.ts +1140 -0
- package/dist/ui/components/cartesian-chart/index.ts +18 -0
- package/dist/ui/components/checkbox/checkbox.test.ts +59 -0
- package/dist/ui/components/checkbox/checkbox.ts +96 -0
- package/dist/ui/components/checkbox/index.ts +1 -0
- package/dist/ui/components/collapsible/collapsible.test.ts +112 -0
- package/dist/ui/components/collapsible/collapsible.ts +122 -0
- package/dist/ui/components/collapsible/index.ts +5 -0
- package/dist/ui/components/dialog/dialog.test.ts +157 -0
- package/dist/ui/components/dialog/dialog.ts +248 -0
- package/dist/ui/components/dialog/index.ts +7 -0
- package/dist/ui/components/empty-state/empty-state.test.ts +60 -0
- package/dist/ui/components/empty-state/empty-state.ts +46 -0
- package/dist/ui/components/empty-state/index.ts +1 -0
- package/dist/ui/components/icon/icon.test.ts +52 -0
- package/dist/ui/components/icon/icon.ts +54 -0
- package/dist/ui/components/icon/icons.ts +569 -0
- package/dist/ui/components/icon/index.ts +2 -0
- package/dist/ui/components/input/index.ts +1 -0
- package/dist/ui/components/input/input.test.ts +134 -0
- package/dist/ui/components/input/input.ts +189 -0
- package/dist/ui/components/label/index.ts +1 -0
- package/dist/ui/components/label/label.test.ts +38 -0
- package/dist/ui/components/label/label.ts +24 -0
- package/dist/ui/components/line-chart/index.ts +6 -0
- package/dist/ui/components/line-chart/line-chart-render.test.ts +121 -0
- package/dist/ui/components/line-chart/line-chart.test.ts +81 -0
- package/dist/ui/components/line-chart/line-chart.ts +273 -0
- package/dist/ui/components/list/index.ts +1 -0
- package/dist/ui/components/list/list.test.ts +35 -0
- package/dist/ui/components/list/list.ts +71 -0
- package/dist/ui/components/nav-drawer/index.ts +8 -0
- package/dist/ui/components/nav-drawer/nav-drawer.test.ts +267 -0
- package/dist/ui/components/nav-drawer/nav-drawer.ts +504 -0
- package/dist/ui/components/pagination/index.ts +1 -0
- package/dist/ui/components/pagination/pagination.test.ts +96 -0
- package/dist/ui/components/pagination/pagination.ts +147 -0
- package/dist/ui/components/progress/index.ts +1 -0
- package/dist/ui/components/progress/progress.test.ts +71 -0
- package/dist/ui/components/progress/progress.ts +118 -0
- package/dist/ui/components/radio-group/index.ts +1 -0
- package/dist/ui/components/radio-group/radio-group.test.ts +115 -0
- package/dist/ui/components/radio-group/radio-group.ts +129 -0
- package/dist/ui/components/scatter-chart/index.ts +6 -0
- package/dist/ui/components/scatter-chart/scatter-chart-render.test.ts +157 -0
- package/dist/ui/components/scatter-chart/scatter-chart.test.ts +57 -0
- package/dist/ui/components/scatter-chart/scatter-chart.ts +277 -0
- package/dist/ui/components/scroll-area/index.ts +1 -0
- package/dist/ui/components/scroll-area/scroll-area.test.ts +40 -0
- package/dist/ui/components/scroll-area/scroll-area.ts +73 -0
- package/dist/ui/components/select/index.ts +1 -0
- package/dist/ui/components/select/select.test.ts +107 -0
- package/dist/ui/components/select/select.ts +146 -0
- package/dist/ui/components/separator/index.ts +1 -0
- package/dist/ui/components/separator/separator.test.ts +26 -0
- package/dist/ui/components/separator/separator.ts +45 -0
- package/dist/ui/components/sheet/index.ts +7 -0
- package/dist/ui/components/sheet/sheet.test.ts +44 -0
- package/dist/ui/components/sheet/sheet.ts +114 -0
- package/dist/ui/components/skeleton/index.ts +1 -0
- package/dist/ui/components/skeleton/skeleton.test.ts +13 -0
- package/dist/ui/components/skeleton/skeleton.ts +45 -0
- package/dist/ui/components/spinner/index.ts +1 -0
- package/dist/ui/components/spinner/spinner.test.ts +39 -0
- package/dist/ui/components/spinner/spinner.ts +83 -0
- package/dist/ui/components/switch/index.ts +1 -0
- package/dist/ui/components/switch/switch.test.ts +63 -0
- package/dist/ui/components/switch/switch.ts +98 -0
- package/dist/ui/components/tabs/index.ts +1 -0
- package/dist/ui/components/tabs/tabs.test.ts +199 -0
- package/dist/ui/components/tabs/tabs.ts +317 -0
- package/dist/ui/components/textarea/index.ts +1 -0
- package/dist/ui/components/textarea/textarea.test.ts +138 -0
- package/dist/ui/components/textarea/textarea.ts +221 -0
- package/dist/ui/components/toast/index.ts +9 -0
- package/dist/ui/components/toast/toast-state.test.ts +54 -0
- package/dist/ui/components/toast/toast-state.ts +46 -0
- package/dist/ui/components/toast/toast.test.ts +342 -0
- package/dist/ui/components/toast/toast.ts +527 -0
- package/dist/ui/components/toggle/index.ts +7 -0
- package/dist/ui/components/toggle/toggle.test.ts +186 -0
- package/dist/ui/components/toggle/toggle.ts +218 -0
- package/dist/ui/components/typography/index.ts +5 -0
- package/dist/ui/components/typography/typography.test.ts +55 -0
- package/dist/ui/components/typography/typography.ts +44 -0
- package/dist/ui/theme/dark.css +41 -0
- package/dist/ui/theme/default.css +56 -0
- package/dist/ui/theme/tailwind-plugin.test.ts +124 -0
- package/dist/ui/theme/tailwind-plugin.ts +151 -0
- package/dist/ui/utils/animate.test.ts +324 -0
- package/dist/ui/utils/animate.ts +544 -0
- package/dist/ui/utils/cn.test.ts +27 -0
- package/dist/ui/utils/cn.ts +6 -0
- package/dist/utils/analyze.d.ts +32 -0
- package/dist/utils/analyze.js +109 -0
- package/dist/utils/analyze.js.map +1 -0
- package/dist/utils/analyze.test.d.ts +1 -0
- package/dist/utils/analyze.test.js +146 -0
- package/dist/utils/analyze.test.js.map +1 -0
- package/dist/utils/animate.d.ts +210 -0
- package/dist/utils/animate.js +423 -0
- package/dist/utils/animate.js.map +1 -0
- package/dist/utils/animate.test.d.ts +1 -0
- package/dist/utils/animate.test.js +365 -0
- package/dist/utils/animate.test.js.map +1 -0
- package/dist/utils/banner.d.ts +1 -0
- package/dist/utils/banner.js +15 -0
- package/dist/utils/banner.js.map +1 -0
- package/dist/utils/banner.test.d.ts +1 -0
- package/dist/utils/banner.test.js +29 -0
- package/dist/utils/banner.test.js.map +1 -0
- package/dist/utils/cn.d.ts +2 -0
- package/dist/utils/cn.js +6 -0
- package/dist/utils/cn.js.map +1 -0
- package/dist/utils/cn.test.d.ts +1 -0
- package/dist/utils/cn.test.js +21 -0
- package/dist/utils/cn.test.js.map +1 -0
- package/dist/utils/format.d.ts +31 -0
- package/dist/utils/format.js +95 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/format.test.d.ts +1 -0
- package/dist/utils/format.test.js +71 -0
- package/dist/utils/format.test.js.map +1 -0
- package/dist/utils/logger.d.ts +6 -0
- package/dist/utils/logger.js +16 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/logger.test.d.ts +1 -0
- package/dist/utils/logger.test.js +36 -0
- package/dist/utils/logger.test.js.map +1 -0
- package/dist/utils/resolve-paths.d.ts +12 -0
- package/dist/utils/resolve-paths.js +29 -0
- package/dist/utils/resolve-paths.js.map +1 -0
- package/dist/utils/resolve-paths.test.d.ts +1 -0
- package/dist/utils/resolve-paths.test.js +52 -0
- package/dist/utils/resolve-paths.test.js.map +1 -0
- package/package.json +56 -4
- package/README.md +0 -3
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, expect, it, beforeEach } from 'vitest';
|
|
2
|
+
import { dismissToast, toast, toasts } from './toast-state';
|
|
3
|
+
|
|
4
|
+
describe('toast-state', () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
toasts.set([]);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('toast() appends a new entry with defaults', () => {
|
|
10
|
+
const id = toast({ title: 'Hello' });
|
|
11
|
+
const list = toasts();
|
|
12
|
+
expect(list).toHaveLength(1);
|
|
13
|
+
expect(list[0]!.id).toBe(id);
|
|
14
|
+
expect(list[0]!.title).toBe('Hello');
|
|
15
|
+
expect(list[0]!.variant).toBe('default');
|
|
16
|
+
expect(list[0]!.duration).toBe(5000);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('toast() accepts overrides for variant, duration, and action', () => {
|
|
20
|
+
const onAction = () => {};
|
|
21
|
+
const id = toast({
|
|
22
|
+
title: 'Danger',
|
|
23
|
+
variant: 'destructive',
|
|
24
|
+
duration: 1000,
|
|
25
|
+
action: { label: 'Undo', onAction },
|
|
26
|
+
});
|
|
27
|
+
const [entry] = toasts();
|
|
28
|
+
expect(entry!.id).toBe(id);
|
|
29
|
+
expect(entry!.variant).toBe('destructive');
|
|
30
|
+
expect(entry!.duration).toBe(1000);
|
|
31
|
+
expect(entry!.action).toEqual({ label: 'Undo', onAction });
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('dismissToast() removes only the entry with the matching id', () => {
|
|
35
|
+
const a = toast({ title: 'A' });
|
|
36
|
+
const b = toast({ title: 'B' });
|
|
37
|
+
dismissToast(a);
|
|
38
|
+
expect(toasts().map((t) => t.id)).toEqual([b]);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('dismissToast() with an unknown id leaves the queue untouched', () => {
|
|
42
|
+
toast({ title: 'A' });
|
|
43
|
+
const before = toasts().length;
|
|
44
|
+
dismissToast('never-existed');
|
|
45
|
+
expect(toasts()).toHaveLength(before);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('successive toast() calls produce monotonically-unique ids', () => {
|
|
49
|
+
const a = toast({ title: 'A' });
|
|
50
|
+
const b = toast({ title: 'B' });
|
|
51
|
+
const c = toast({ title: 'C' });
|
|
52
|
+
expect(new Set([a, b, c]).size).toBe(3);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { signal } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
export type ToastAction = {
|
|
4
|
+
readonly label: string;
|
|
5
|
+
readonly onAction: () => void;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type ToastData = {
|
|
9
|
+
readonly id: string;
|
|
10
|
+
readonly title?: string;
|
|
11
|
+
readonly description?: string;
|
|
12
|
+
readonly variant: 'default' | 'destructive';
|
|
13
|
+
readonly duration: number;
|
|
14
|
+
readonly action?: ToastAction;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type ToastOptions = Partial<Omit<ToastData, 'id'>> & {
|
|
18
|
+
readonly title: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
let nextId = 0;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Module-level signal (not in an injectable service) so toast() can be called
|
|
25
|
+
* from anywhere — including plain functions and router guards — without requiring
|
|
26
|
+
* a DI context. UiToastContainer reads this signal reactively to render the queue.
|
|
27
|
+
*/
|
|
28
|
+
export const toasts = signal<readonly ToastData[]>([]);
|
|
29
|
+
|
|
30
|
+
export const toast = (options: ToastOptions): string => {
|
|
31
|
+
const id = `toast-${nextId++}`;
|
|
32
|
+
toasts.update((list) => [
|
|
33
|
+
...list,
|
|
34
|
+
{
|
|
35
|
+
id,
|
|
36
|
+
variant: 'default',
|
|
37
|
+
duration: 5000,
|
|
38
|
+
...options,
|
|
39
|
+
},
|
|
40
|
+
]);
|
|
41
|
+
return id;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const dismissToast = (id: string): void => {
|
|
45
|
+
toasts.update((list) => list.filter((t) => t.id !== id));
|
|
46
|
+
};
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { ElementRef } from '@angular/core';
|
|
2
|
+
import { render, waitForUpdate } from '@blotch/angular-lynx-testing-library';
|
|
3
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import { setInputSignal } from '../../../test-utils/set-input-signal';
|
|
5
|
+
import { UiToastItem, UiToaster } from './toast';
|
|
6
|
+
import { toast, toasts, type ToastData } from './toast-state';
|
|
7
|
+
|
|
8
|
+
describe('UiToaster', () => {
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
toasts.set([]);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('renders without any active toasts', async () => {
|
|
14
|
+
const { container } = await render(UiToaster);
|
|
15
|
+
expect(container).toBeTruthy();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('renders the stack container when a toast is added', async () => {
|
|
19
|
+
toast({ title: 'Saved!' });
|
|
20
|
+
const { container } = await render(UiToaster);
|
|
21
|
+
await waitForUpdate();
|
|
22
|
+
// Toast content isn't asserted here — JIT does not wire the required
|
|
23
|
+
// `data` input on the projected <ui-toast-item> so the child template
|
|
24
|
+
// silently fails. UiToastItem's lifecycle is exercised directly below.
|
|
25
|
+
expect(container.querySelectorAll('view').length).toBeGreaterThan(0);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('onDismissed removes the toast from the queue', async () => {
|
|
29
|
+
const id = toast({ title: 'ByeBye' });
|
|
30
|
+
const { componentRef } = await render(UiToaster);
|
|
31
|
+
const inst = componentRef.instance as unknown as {
|
|
32
|
+
onDismissed: (id: string) => void;
|
|
33
|
+
};
|
|
34
|
+
inst.onDismissed(id);
|
|
35
|
+
expect(toasts()).toHaveLength(0);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('UiToastItem', () => {
|
|
40
|
+
const baseData = (over: Partial<ToastData> = {}): ToastData => ({
|
|
41
|
+
id: 'toast-x',
|
|
42
|
+
title: 'Hello',
|
|
43
|
+
description: 'World',
|
|
44
|
+
variant: 'default',
|
|
45
|
+
duration: 60_000,
|
|
46
|
+
...over,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('renders default and destructive variants', async () => {
|
|
50
|
+
const { componentRef, container } = await render(UiToastItem);
|
|
51
|
+
const inst = componentRef.instance as UiToastItem;
|
|
52
|
+
setInputSignal(inst.data, baseData({ variant: 'destructive' }));
|
|
53
|
+
setInputSignal(inst.depth, 0);
|
|
54
|
+
await waitForUpdate();
|
|
55
|
+
// JIT + input.required races the initial CD: even with the signal set, the
|
|
56
|
+
// template DOM may not be materialized in this synthetic bootstrap. The
|
|
57
|
+
// variant computed is exercised elsewhere; asserting on the container ref
|
|
58
|
+
// proves the render didn't throw.
|
|
59
|
+
expect(container).toBeTruthy();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('exercises all computed presentation getters (default + destructive)', async () => {
|
|
63
|
+
// The template computed getters (`cardClass`, `titleClass`, etc.) are only
|
|
64
|
+
// read by the template when it materializes; JIT + required inputs races
|
|
65
|
+
// that, so read them directly so their branches run under coverage.
|
|
66
|
+
const { componentRef } = await render(UiToastItem);
|
|
67
|
+
const inst = componentRef.instance as UiToastItem;
|
|
68
|
+
const cast = inst as unknown as {
|
|
69
|
+
hostStyle: () => string;
|
|
70
|
+
cardClass: () => string;
|
|
71
|
+
titleClass: () => string;
|
|
72
|
+
descriptionClass: () => string;
|
|
73
|
+
actionClass: () => string;
|
|
74
|
+
actionTextClass: () => string;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
setInputSignal(inst.data, baseData());
|
|
78
|
+
setInputSignal(inst.depth, 0);
|
|
79
|
+
await waitForUpdate();
|
|
80
|
+
expect(cast.hostStyle()).toContain('translateY');
|
|
81
|
+
expect(cast.cardClass()).toContain('bg-background');
|
|
82
|
+
expect(cast.titleClass()).toContain('text-foreground');
|
|
83
|
+
expect(cast.descriptionClass()).toContain('text-muted-foreground');
|
|
84
|
+
expect(cast.actionClass()).toContain('border-border');
|
|
85
|
+
expect(cast.actionTextClass()).toContain('text-foreground');
|
|
86
|
+
|
|
87
|
+
setInputSignal(inst.data, baseData({ variant: 'destructive' }));
|
|
88
|
+
setInputSignal(inst.depth, 2);
|
|
89
|
+
await waitForUpdate();
|
|
90
|
+
expect(cast.cardClass()).toContain('bg-destructive');
|
|
91
|
+
expect(cast.titleClass()).toContain('text-destructive-foreground');
|
|
92
|
+
expect(cast.descriptionClass()).toContain('text-destructive-foreground');
|
|
93
|
+
expect(cast.actionClass()).toContain('border-destructive-foreground');
|
|
94
|
+
expect(cast.actionTextClass()).toContain('text-destructive-foreground');
|
|
95
|
+
expect(cast.hostStyle()).toContain('translateY');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('renders an action button and invokes it via onAction', async () => {
|
|
99
|
+
const cb = vi.fn();
|
|
100
|
+
const { componentRef } = await render(UiToastItem);
|
|
101
|
+
const inst = componentRef.instance as UiToastItem;
|
|
102
|
+
setInputSignal(
|
|
103
|
+
inst.data,
|
|
104
|
+
baseData({ action: { label: 'Do it', onAction: cb } }),
|
|
105
|
+
);
|
|
106
|
+
setInputSignal(inst.depth, 0);
|
|
107
|
+
await waitForUpdate();
|
|
108
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
109
|
+
(inst as unknown as { onAction: () => void }).onAction();
|
|
110
|
+
expect(cb).toHaveBeenCalledTimes(1);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('onAction handles missing action gracefully', async () => {
|
|
114
|
+
const { componentRef } = await render(UiToastItem);
|
|
115
|
+
const inst = componentRef.instance as UiToastItem;
|
|
116
|
+
setInputSignal(inst.data, baseData());
|
|
117
|
+
setInputSignal(inst.depth, 0);
|
|
118
|
+
await waitForUpdate();
|
|
119
|
+
expect(() =>
|
|
120
|
+
(inst as unknown as { onAction: () => void }).onAction(),
|
|
121
|
+
).not.toThrow();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('onTap dismisses only when the toast is front (depth 0)', async () => {
|
|
125
|
+
const { componentRef } = await render(UiToastItem);
|
|
126
|
+
const inst = componentRef.instance as UiToastItem;
|
|
127
|
+
setInputSignal(inst.data, baseData());
|
|
128
|
+
setInputSignal(inst.depth, 1); // behind — no-op
|
|
129
|
+
await waitForUpdate();
|
|
130
|
+
expect(() =>
|
|
131
|
+
(inst as unknown as { onTap: () => void }).onTap(),
|
|
132
|
+
).not.toThrow();
|
|
133
|
+
|
|
134
|
+
setInputSignal(inst.depth, 0);
|
|
135
|
+
await waitForUpdate();
|
|
136
|
+
(inst as unknown as { onTap: () => void }).onTap();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('auto-dismisses after the duration timer fires', async () => {
|
|
140
|
+
const dismissed = vi.fn();
|
|
141
|
+
const { componentRef } = await render(UiToastItem);
|
|
142
|
+
const inst = componentRef.instance as UiToastItem;
|
|
143
|
+
inst.dismissed.subscribe(dismissed);
|
|
144
|
+
setInputSignal(inst.data, baseData({ duration: 30 }));
|
|
145
|
+
setInputSignal(inst.depth, 0);
|
|
146
|
+
await waitForUpdate();
|
|
147
|
+
// Manually call ngOnInit after data is set so the auto-dismiss timer
|
|
148
|
+
// registers (JIT's initial CD ran ngOnInit before the required-input's
|
|
149
|
+
// value was available, so the timer was skipped).
|
|
150
|
+
inst.ngOnInit();
|
|
151
|
+
// Wait long enough for both the auto-dismiss (30ms) and the dismissed
|
|
152
|
+
// emit setTimeout(DURATION.fast + 20 = 170ms) to fire.
|
|
153
|
+
await new Promise((r) => setTimeout(r, 400));
|
|
154
|
+
expect(dismissed).toHaveBeenCalled();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('touch drag beyond threshold dismisses; below threshold snaps back', async () => {
|
|
158
|
+
const { componentRef } = await render(UiToastItem);
|
|
159
|
+
const inst = componentRef.instance as UiToastItem;
|
|
160
|
+
setInputSignal(inst.data, baseData({ duration: 60_000 }));
|
|
161
|
+
setInputSignal(inst.depth, 0);
|
|
162
|
+
await waitForUpdate();
|
|
163
|
+
// Call ngOnInit manually so the auto-dismiss timer is set — that's the
|
|
164
|
+
// branch inside dismiss() that touchend beyond threshold exercises.
|
|
165
|
+
inst.ngOnInit();
|
|
166
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
167
|
+
|
|
168
|
+
const cast = inst as unknown as {
|
|
169
|
+
onTouchStart: (e: { touches?: { clientY: number }[] }) => void;
|
|
170
|
+
onTouchMove: (e: { touches?: { clientY: number }[] }) => void;
|
|
171
|
+
onTouchEnd: () => void;
|
|
172
|
+
};
|
|
173
|
+
// Below threshold → snap back.
|
|
174
|
+
cast.onTouchStart({ touches: [{ clientY: 100 }] });
|
|
175
|
+
cast.onTouchMove({ touches: [{ clientY: 130 }] });
|
|
176
|
+
cast.onTouchEnd();
|
|
177
|
+
await waitForUpdate();
|
|
178
|
+
|
|
179
|
+
// Beyond threshold → dismiss.
|
|
180
|
+
cast.onTouchStart({ touches: [{ clientY: 100 }] });
|
|
181
|
+
cast.onTouchMove({ touches: [{ clientY: 400 }] });
|
|
182
|
+
cast.onTouchEnd();
|
|
183
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('touch handlers guard non-front toasts and missing touches', async () => {
|
|
187
|
+
const { componentRef } = await render(UiToastItem);
|
|
188
|
+
const inst = componentRef.instance as UiToastItem;
|
|
189
|
+
setInputSignal(inst.data, baseData());
|
|
190
|
+
setInputSignal(inst.depth, 1); // behind — early-return in onTouchStart
|
|
191
|
+
await waitForUpdate();
|
|
192
|
+
|
|
193
|
+
const cast = inst as unknown as {
|
|
194
|
+
onTouchStart: (e: { touches?: { clientY: number }[] }) => void;
|
|
195
|
+
onTouchMove: (e: { touches?: { clientY: number }[] }) => void;
|
|
196
|
+
onTouchEnd: () => void;
|
|
197
|
+
};
|
|
198
|
+
cast.onTouchStart({ touches: [{ clientY: 100 }] });
|
|
199
|
+
cast.onTouchMove({ touches: [{ clientY: 200 }] }); // not dragging → no-op
|
|
200
|
+
cast.onTouchEnd(); // not dragging → no-op
|
|
201
|
+
|
|
202
|
+
setInputSignal(inst.depth, 0);
|
|
203
|
+
await waitForUpdate();
|
|
204
|
+
// Empty touches array → no-op
|
|
205
|
+
cast.onTouchStart({ touches: [] });
|
|
206
|
+
cast.onTouchMove({ touches: [] });
|
|
207
|
+
// No prior touchstart with real coords, so onTouchEnd is a no-op.
|
|
208
|
+
cast.onTouchEnd();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('onTouchMove ignores subsequent moves with missing touches once dragging', async () => {
|
|
212
|
+
const { componentRef } = await render(UiToastItem);
|
|
213
|
+
const inst = componentRef.instance as UiToastItem;
|
|
214
|
+
setInputSignal(inst.data, baseData());
|
|
215
|
+
setInputSignal(inst.depth, 0);
|
|
216
|
+
await waitForUpdate();
|
|
217
|
+
inst.ngOnInit();
|
|
218
|
+
const cast = inst as unknown as {
|
|
219
|
+
onTouchStart: (e: { touches?: { clientY: number }[] }) => void;
|
|
220
|
+
onTouchMove: (e: { touches?: { clientY: number }[] }) => void;
|
|
221
|
+
};
|
|
222
|
+
// Start a real drag (sets #isDragging = true).
|
|
223
|
+
cast.onTouchStart({ touches: [{ clientY: 50 }] });
|
|
224
|
+
// A subsequent move without touches must fall through the `y == null` guard
|
|
225
|
+
// in onTouchMove (line 257) — this is the "finger left the surface without
|
|
226
|
+
// a touchend" native quirk we defend against.
|
|
227
|
+
cast.onTouchMove({ touches: [] });
|
|
228
|
+
cast.onTouchMove({}); // touches undefined
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('onTouchEnd is a no-op when dismissal is already in progress', async () => {
|
|
232
|
+
const { componentRef } = await render(UiToastItem);
|
|
233
|
+
const inst = componentRef.instance as UiToastItem;
|
|
234
|
+
setInputSignal(inst.data, baseData());
|
|
235
|
+
setInputSignal(inst.depth, 0);
|
|
236
|
+
await waitForUpdate();
|
|
237
|
+
inst.ngOnInit();
|
|
238
|
+
const cast = inst as unknown as {
|
|
239
|
+
onTouchStart: (e: { touches?: { clientY: number }[] }) => void;
|
|
240
|
+
onTouchMove: (e: { touches?: { clientY: number }[] }) => void;
|
|
241
|
+
onTouchEnd: () => void;
|
|
242
|
+
};
|
|
243
|
+
// Start a drag.
|
|
244
|
+
cast.onTouchStart({ touches: [{ clientY: 100 }] });
|
|
245
|
+
cast.onTouchMove({ touches: [{ clientY: 200 }] });
|
|
246
|
+
// Race the dismiss(): tapping or the timer already started tearing down.
|
|
247
|
+
inst.dismiss();
|
|
248
|
+
// touchend must observe #isDismissing and bail out (line 271).
|
|
249
|
+
cast.onTouchEnd();
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('snap-back reschedules the auto-dismiss timer that eventually fires', async () => {
|
|
253
|
+
const dismissed = vi.fn();
|
|
254
|
+
const { componentRef } = await render(UiToastItem);
|
|
255
|
+
const inst = componentRef.instance as UiToastItem;
|
|
256
|
+
inst.dismissed.subscribe(dismissed);
|
|
257
|
+
// Short duration so the snap-back's rescheduled setTimeout callback
|
|
258
|
+
// (line 419) actually fires before the test times out.
|
|
259
|
+
setInputSignal(inst.data, baseData({ duration: 30 }));
|
|
260
|
+
setInputSignal(inst.depth, 0);
|
|
261
|
+
await waitForUpdate();
|
|
262
|
+
inst.ngOnInit();
|
|
263
|
+
const cast = inst as unknown as {
|
|
264
|
+
onTouchStart: (e: { touches?: { clientY: number }[] }) => void;
|
|
265
|
+
onTouchMove: (e: { touches?: { clientY: number }[] }) => void;
|
|
266
|
+
onTouchEnd: () => void;
|
|
267
|
+
};
|
|
268
|
+
// Drag below threshold → snap back reschedules the auto-dismiss.
|
|
269
|
+
cast.onTouchStart({ touches: [{ clientY: 100 }] });
|
|
270
|
+
cast.onTouchMove({ touches: [{ clientY: 120 }] });
|
|
271
|
+
cast.onTouchEnd();
|
|
272
|
+
// Wait long enough for the rescheduled dismiss (30ms) + dismissed emit
|
|
273
|
+
// (DURATION.fast + 20 ≈ 170ms) to fire.
|
|
274
|
+
await new Promise((r) => setTimeout(r, 400));
|
|
275
|
+
expect(dismissed).toHaveBeenCalled();
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('restack effect runs when depth changes on an already-mounted item', async () => {
|
|
279
|
+
const { componentRef } = await render(UiToastItem);
|
|
280
|
+
const inst = componentRef.instance as UiToastItem;
|
|
281
|
+
setInputSignal(inst.data, baseData());
|
|
282
|
+
setInputSignal(inst.depth, 0);
|
|
283
|
+
await waitForUpdate();
|
|
284
|
+
setInputSignal(inst.depth, 1); // triggers restack animation
|
|
285
|
+
await waitForUpdate();
|
|
286
|
+
setInputSignal(inst.depth, 2);
|
|
287
|
+
await waitForUpdate();
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('hostStyle flips opacity to 1 after the entrance animation runs', async () => {
|
|
291
|
+
const { componentRef } = await render(UiToastItem);
|
|
292
|
+
const inst = componentRef.instance as UiToastItem;
|
|
293
|
+
setInputSignal(inst.data, baseData());
|
|
294
|
+
setInputSignal(inst.depth, 0);
|
|
295
|
+
await waitForUpdate();
|
|
296
|
+
const cast = inst as unknown as { hostStyle: () => string };
|
|
297
|
+
// Before ngOnInit → #hasEntered = false → opacity: 0.
|
|
298
|
+
expect(cast.hostStyle()).toContain('opacity: 0');
|
|
299
|
+
inst.ngOnInit();
|
|
300
|
+
// Wait past the setTimeout(0) that runs #animateIn, which sets #hasEntered.
|
|
301
|
+
await new Promise((r) => setTimeout(r, 30));
|
|
302
|
+
// After animateIn → #hasEntered = true → opacity: 1 (the ternary's truthy arm).
|
|
303
|
+
expect(cast.hostStyle()).toContain('opacity: 1');
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it('dismiss() is idempotent (re-entry guard)', async () => {
|
|
307
|
+
const dismissed = vi.fn();
|
|
308
|
+
const { componentRef } = await render(UiToastItem);
|
|
309
|
+
const inst = componentRef.instance as UiToastItem;
|
|
310
|
+
inst.dismissed.subscribe(dismissed);
|
|
311
|
+
setInputSignal(inst.data, baseData({ duration: 60_000 }));
|
|
312
|
+
setInputSignal(inst.depth, 0);
|
|
313
|
+
await waitForUpdate();
|
|
314
|
+
inst.dismiss();
|
|
315
|
+
inst.dismiss(); // guarded
|
|
316
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
317
|
+
expect(dismissed).toHaveBeenCalledTimes(1);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('behind toast (depth > 0) uses the promoted-from-queue entrance animation', async () => {
|
|
321
|
+
const { componentRef } = await render(UiToastItem);
|
|
322
|
+
const inst = componentRef.instance as UiToastItem;
|
|
323
|
+
setInputSignal(inst.data, baseData());
|
|
324
|
+
setInputSignal(inst.depth, 2);
|
|
325
|
+
await waitForUpdate();
|
|
326
|
+
inst.ngOnInit();
|
|
327
|
+
await new Promise((r) => setTimeout(r, 30));
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('behind toast (depth > 0) also dismisses via a per-depth exit animation', async () => {
|
|
331
|
+
const { componentRef } = await render(UiToastItem);
|
|
332
|
+
const inst = componentRef.instance as UiToastItem;
|
|
333
|
+
setInputSignal(inst.data, baseData());
|
|
334
|
+
setInputSignal(inst.depth, 2);
|
|
335
|
+
await waitForUpdate();
|
|
336
|
+
inst.ngOnInit();
|
|
337
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
338
|
+
inst.dismiss();
|
|
339
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
void ElementRef;
|