@blotch/dolan 22.2.3 → 22.2.5
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.js +9 -1
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/add.test.js +38 -3
- package/dist/commands/add.test.js.map +1 -1
- package/dist/commands/doctor.js +258 -21
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/doctor.test.js +425 -11
- package/dist/commands/doctor.test.js.map +1 -1
- package/dist/commands/init.d.ts +2 -2
- package/dist/commands/init.js +38 -18
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/init.test.js +105 -2
- package/dist/commands/init.test.js.map +1 -1
- package/dist/commands/theme.js +1 -1
- package/dist/commands/theme.js.map +1 -1
- package/dist/commands/update.d.ts +7 -0
- package/dist/commands/update.js +194 -50
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/update.test.js +570 -3
- package/dist/commands/update.test.js.map +1 -1
- package/dist/lockfile.d.ts +17 -0
- package/dist/lockfile.js +56 -1
- package/dist/lockfile.js.map +1 -1
- package/dist/lockfile.test.d.ts +1 -0
- package/dist/lockfile.test.js +65 -0
- package/dist/lockfile.test.js.map +1 -0
- package/dist/registry.d.ts +6 -0
- package/dist/registry.js +119 -11
- package/dist/registry.js.map +1 -1
- package/dist/registry.test.js +61 -6
- package/dist/registry.test.js.map +1 -1
- package/dist/test-utils.d.ts +27 -0
- package/dist/test-utils.js +45 -1
- package/dist/test-utils.js.map +1 -1
- package/dist/testing/fire-lynx-event.d.ts +14 -0
- package/dist/testing/fire-lynx-event.js +19 -0
- package/dist/testing/fire-lynx-event.js.map +1 -0
- package/dist/testing/fire-lynx-event.test.d.ts +1 -0
- package/dist/testing/fire-lynx-event.test.js +44 -0
- package/dist/testing/fire-lynx-event.test.js.map +1 -0
- package/dist/testing/lynx-main-thread.d.ts +22 -0
- package/dist/testing/lynx-main-thread.js +11 -0
- package/dist/testing/lynx-main-thread.js.map +1 -0
- package/dist/testing/lynx-main-thread.test.d.ts +1 -0
- package/dist/testing/lynx-main-thread.test.js +24 -0
- package/dist/testing/lynx-main-thread.test.js.map +1 -0
- package/dist/testing/set-input-signal.d.ts +5 -4
- package/dist/testing/set-input-signal.js +5 -4
- package/dist/testing/set-input-signal.js.map +1 -1
- package/dist/ui/boolean-inputs.test.ts +76 -0
- package/dist/ui/components/accordion/accordion.test.ts +158 -144
- package/dist/ui/components/accordion/accordion.ts +19 -14
- package/dist/ui/components/action-sheet/action-sheet.test.ts +637 -183
- package/dist/ui/components/action-sheet/action-sheet.ts +339 -55
- package/dist/ui/components/action-sheet/index.ts +1 -0
- package/dist/ui/components/alert/alert.test.ts +67 -19
- package/dist/ui/components/alert/alert.ts +14 -16
- package/dist/ui/components/alert-dialog/alert-dialog.test.ts +740 -129
- package/dist/ui/components/alert-dialog/alert-dialog.ts +672 -58
- package/dist/ui/components/alert-dialog/index.ts +5 -0
- package/dist/ui/components/area-chart/area-chart.test.ts +81 -1
- package/dist/ui/components/area-chart/area-chart.ts +3 -2
- package/dist/ui/components/avatar/avatar.test.ts +89 -45
- package/dist/ui/components/badge/badge.test.ts +57 -23
- package/dist/ui/components/badge/badge.ts +14 -15
- package/dist/ui/components/bar-chart/bar-chart.test.ts +82 -1
- package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +1314 -273
- package/dist/ui/components/bottom-sheet/bottom-sheet.ts +1046 -119
- package/dist/ui/components/bottom-sheet/index.ts +7 -1
- package/dist/ui/components/button/button.test.ts +646 -2
- package/dist/ui/components/button/button.ts +443 -64
- package/dist/ui/components/button/index.ts +2 -0
- package/dist/ui/components/button-group/button-group.test.ts +528 -2
- package/dist/ui/components/button-group/button-group.ts +428 -70
- package/dist/ui/components/button-group/index.ts +3 -0
- package/dist/ui/components/candlestick-chart/candlestick-chart.test.ts +82 -1
- package/dist/ui/components/card/card.test.ts +97 -61
- package/dist/ui/components/card/card.ts +2 -1
- package/dist/ui/components/chart/chart-presets.test.ts +197 -1
- package/dist/ui/components/chart/chart-presets.ts +14 -7
- package/dist/ui/components/chart/chart-theme.ts +12 -0
- package/dist/ui/components/chart/chart.test.ts +1031 -0
- package/dist/ui/components/chart/chart.ts +66 -46
- package/dist/ui/components/checkbox/checkbox.test.ts +124 -42
- package/dist/ui/components/checkbox/checkbox.ts +25 -24
- package/dist/ui/components/collapsible/collapsible.test.ts +158 -81
- package/dist/ui/components/collapsible/collapsible.ts +21 -15
- package/dist/ui/components/context-menu/context-menu.test.ts +224 -0
- package/dist/ui/components/context-menu/context-menu.ts +110 -0
- package/dist/ui/components/context-menu/index.ts +1 -0
- package/dist/ui/components/dialog/dialog.test.ts +210 -152
- package/dist/ui/components/dialog/dialog.ts +35 -29
- package/dist/ui/components/dock/dock-scroll.test.ts +130 -0
- package/dist/ui/components/dock/dock-scroll.ts +142 -0
- package/dist/ui/components/dock/dock-search.test.ts +594 -0
- package/dist/ui/components/dock/dock.test.ts +752 -0
- package/dist/ui/components/dock/dock.ts +1438 -0
- package/dist/ui/components/dock/index.ts +2 -0
- package/dist/ui/components/dynamic-color/dynamic-color.test.ts +144 -0
- package/dist/ui/components/dynamic-color/dynamic-color.ts +146 -0
- package/dist/ui/components/dynamic-color/index.ts +5 -0
- package/dist/ui/components/empty-state/empty-state.test.ts +28 -39
- package/dist/ui/components/glass/concentric-radius.test.ts +13 -0
- package/dist/ui/components/glass/concentric-radius.ts +17 -0
- package/dist/ui/components/glass/glass-design.test.ts +78 -0
- package/dist/ui/components/glass/glass-design.ts +119 -0
- package/dist/ui/components/glass/glass.test.ts +376 -0
- package/dist/ui/components/glass/glass.ts +430 -0
- package/dist/ui/components/glass/index.ts +19 -0
- package/dist/ui/components/icon/icon-theme.test.ts +64 -0
- package/dist/ui/components/icon/icon-theme.ts +70 -16
- package/dist/ui/components/icon/icon.test.ts +52 -41
- package/dist/ui/components/input/input.test.ts +178 -101
- package/dist/ui/components/input/input.ts +17 -13
- package/dist/ui/components/label/label.test.ts +11 -24
- package/dist/ui/components/label/label.ts +8 -2
- package/dist/ui/components/line-chart/line-chart.test.ts +76 -1
- package/dist/ui/components/line-chart/line-chart.ts +3 -2
- package/dist/ui/components/list/list.test.ts +80 -49
- package/dist/ui/components/list/list.ts +5 -4
- package/dist/ui/components/menu/index.ts +25 -0
- package/dist/ui/components/menu/menu-placement.test.ts +153 -0
- package/dist/ui/components/menu/menu-placement.ts +159 -0
- package/dist/ui/components/menu/menu-popup.test.ts +636 -0
- package/dist/ui/components/menu/menu-popup.ts +565 -0
- package/dist/ui/components/menu/menu.test.ts +523 -0
- package/dist/ui/components/menu/menu.ts +455 -0
- package/dist/ui/components/nav-drawer/nav-drawer.test.ts +425 -260
- package/dist/ui/components/nav-drawer/nav-drawer.ts +75 -58
- package/dist/ui/components/navigation-bar/index.ts +1 -0
- package/dist/ui/components/navigation-bar/navigation-bar.test.ts +425 -0
- package/dist/ui/components/navigation-bar/navigation-bar.ts +411 -0
- package/dist/ui/components/pagination/pagination.test.ts +136 -86
- package/dist/ui/components/popover/index.ts +9 -0
- package/dist/ui/components/popover/popover-panel.test.ts +735 -0
- package/dist/ui/components/popover/popover-panel.ts +603 -0
- package/dist/ui/components/popover/popover.test.ts +286 -0
- package/dist/ui/components/popover/popover.ts +207 -0
- package/dist/ui/components/progress/progress.test.ts +102 -46
- package/dist/ui/components/progress/progress.ts +54 -55
- package/dist/ui/components/radio-group/radio-group.test.ts +128 -83
- package/dist/ui/components/radio-group/radio-group.ts +26 -25
- package/dist/ui/components/scatter-chart/scatter-chart.test.ts +74 -1
- package/dist/ui/components/scroll-area/scroll-area.test.ts +72 -36
- package/dist/ui/components/scroll-area/scroll-area.ts +4 -3
- package/dist/ui/components/search-field/index.ts +15 -0
- package/dist/ui/components/search-field/keyboard.test.ts +147 -0
- package/dist/ui/components/search-field/keyboard.ts +123 -0
- package/dist/ui/components/search-field/search-field.test.ts +549 -0
- package/dist/ui/components/search-field/search-field.ts +628 -0
- package/dist/ui/components/segmented-control/index.ts +8 -0
- package/dist/ui/components/segmented-control/segmented-control.test.ts +861 -0
- package/dist/ui/components/segmented-control/segmented-control.ts +884 -0
- package/dist/ui/components/select/select.test.ts +138 -78
- package/dist/ui/components/select/select.ts +19 -12
- package/dist/ui/components/separator/separator.test.ts +18 -17
- package/dist/ui/components/sheet/sheet.test.ts +151 -11
- package/dist/ui/components/sheet/sheet.ts +68 -5
- package/dist/ui/components/skeleton/skeleton.test.ts +24 -7
- package/dist/ui/components/skeleton/skeleton.ts +10 -16
- package/dist/ui/components/slider/index.ts +1 -0
- package/dist/ui/components/slider/slider.test.ts +695 -0
- package/dist/ui/components/slider/slider.ts +901 -0
- package/dist/ui/components/spinner/spinner.test.ts +64 -46
- package/dist/ui/components/spinner/spinner.ts +16 -19
- package/dist/ui/components/switch/index.ts +1 -1
- package/dist/ui/components/switch/switch.test.ts +534 -48
- package/dist/ui/components/switch/switch.ts +613 -48
- package/dist/ui/components/tab-bar/index.ts +1 -0
- package/dist/ui/components/tab-bar/tab-bar.test.ts +280 -0
- package/dist/ui/components/tab-bar/tab-bar.ts +230 -0
- package/dist/ui/components/tabs/index.ts +7 -1
- package/dist/ui/components/tabs/tabs.test.ts +484 -150
- package/dist/ui/components/tabs/tabs.ts +344 -149
- package/dist/ui/components/textarea/textarea.test.ts +206 -100
- package/dist/ui/components/textarea/textarea.ts +16 -13
- package/dist/ui/components/toast/toast.test.ts +434 -350
- package/dist/ui/components/toast/toast.ts +42 -51
- package/dist/ui/components/toggle/index.ts +1 -0
- package/dist/ui/components/toggle/toggle.test.ts +559 -146
- package/dist/ui/components/toggle/toggle.ts +241 -26
- package/dist/ui/components/toolbar/bar-scroll.test.ts +310 -0
- package/dist/ui/components/toolbar/bar-scroll.ts +372 -0
- package/dist/ui/components/toolbar/index.ts +12 -0
- package/dist/ui/components/toolbar/toolbar.test.ts +438 -0
- package/dist/ui/components/toolbar/toolbar.ts +535 -0
- package/dist/ui/theme/chart-colors.test.ts +8 -3
- package/dist/ui/theme/dark.css +38 -2
- package/dist/ui/theme/default.css +55 -7
- package/dist/ui/theme/icon-colors.test.ts +8 -3
- package/dist/ui/theme/rgb-channels.test.ts +84 -0
- package/dist/ui/theme/tailwind-build.test.ts +114 -0
- package/dist/ui/theme/tailwind-css.test.ts +83 -0
- package/dist/ui/theme/tailwind-plugin.test.ts +42 -2
- package/dist/ui/theme/tailwind-plugin.ts +93 -29
- package/dist/ui/theme/tailwind.css +113 -0
- package/dist/ui/utils/animate.test.ts +13 -1
- package/dist/utils/analyze.d.ts +19 -0
- package/dist/utils/analyze.js +16 -0
- package/dist/utils/analyze.js.map +1 -1
- package/dist/utils/analyze.test.js +14 -1
- package/dist/utils/analyze.test.js.map +1 -1
- package/dist/utils/installed.d.ts +25 -1
- package/dist/utils/installed.js +33 -0
- package/dist/utils/installed.js.map +1 -1
- package/dist/utils/installed.test.js +55 -1
- package/dist/utils/installed.test.js.map +1 -1
- package/dist/utils/resolve-paths.d.ts +4 -4
- package/dist/utils/resolve-paths.js +5 -7
- package/dist/utils/resolve-paths.js.map +1 -1
- package/dist/utils/resolve-paths.test.js +9 -5
- package/dist/utils/resolve-paths.test.js.map +1 -1
- package/dist/utils/tailwind.d.ts +45 -0
- package/dist/utils/tailwind.js +90 -0
- package/dist/utils/tailwind.js.map +1 -0
- package/dist/utils/tailwind.test.d.ts +1 -0
- package/dist/utils/tailwind.test.js +134 -0
- package/dist/utils/tailwind.test.js.map +1 -0
- package/package.json +9 -1
- package/dist/ui/components/area-chart/area-chart-render.test.ts +0 -63
- package/dist/ui/components/bar-chart/bar-chart-render.test.ts +0 -58
- package/dist/ui/components/button/button-render.test.ts +0 -132
- package/dist/ui/components/button-group/button-group-render.test.ts +0 -146
- package/dist/ui/components/candlestick-chart/candlestick-chart-render.test.ts +0 -59
- package/dist/ui/components/chart/chart-render.test.ts +0 -730
- package/dist/ui/components/line-chart/line-chart-render.test.ts +0 -83
- package/dist/ui/components/scatter-chart/scatter-chart-render.test.ts +0 -64
|
@@ -1,199 +1,533 @@
|
|
|
1
|
-
import { Component, signal } from '@angular/core';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { Component, signal, viewChild } from '@angular/core';
|
|
2
|
+
import {
|
|
3
|
+
fireEvent,
|
|
4
|
+
render,
|
|
5
|
+
waitForUpdate,
|
|
6
|
+
} from '@blotch/angular-lynx-testing-library';
|
|
7
|
+
import { LYNX_ELEMENTS, LynxAccessibilitySettings } from '@blotch/angular-lynx';
|
|
8
|
+
import {
|
|
9
|
+
type MockInstance,
|
|
10
|
+
afterEach,
|
|
11
|
+
beforeEach,
|
|
12
|
+
describe,
|
|
13
|
+
expect,
|
|
14
|
+
it,
|
|
15
|
+
vi,
|
|
16
|
+
} from 'vitest';
|
|
17
|
+
import {
|
|
18
|
+
type LynxMainThread,
|
|
19
|
+
lynxMainThread,
|
|
20
|
+
} from '../../../test-utils/lynx-main-thread';
|
|
21
|
+
import { fireLynxEvent } from '../../../test-utils/fire-lynx-event';
|
|
22
|
+
import {
|
|
23
|
+
type TabsListVariant,
|
|
24
|
+
UiTabs,
|
|
25
|
+
UiTabsContent,
|
|
26
|
+
UiTabsList,
|
|
27
|
+
UiTabsTrigger,
|
|
28
|
+
} from './tabs';
|
|
7
29
|
|
|
8
30
|
@Component({
|
|
9
|
-
standalone: true,
|
|
10
31
|
imports: [UiTabs, UiTabsList, UiTabsTrigger, UiTabsContent, LYNX_ELEMENTS],
|
|
11
32
|
template: `
|
|
12
|
-
<ui-tabs [(value)]="value">
|
|
13
|
-
<ui-tabs-list>
|
|
14
|
-
<ui-tabs-trigger value="
|
|
15
|
-
<ui-tabs-trigger value="
|
|
33
|
+
<ui-tabs [(value)]="value" (changed)="changes.push($event)" class="w-80">
|
|
34
|
+
<ui-tabs-list class="mb-2">
|
|
35
|
+
<ui-tabs-trigger value="account" class="px-4">Account</ui-tabs-trigger>
|
|
36
|
+
<ui-tabs-trigger value="billing">Billing</ui-tabs-trigger>
|
|
37
|
+
<ui-tabs-trigger value="contact">Contact</ui-tabs-trigger>
|
|
16
38
|
</ui-tabs-list>
|
|
17
|
-
<ui-tabs-content value="
|
|
18
|
-
|
|
39
|
+
<ui-tabs-content value="account" class="gap-2">
|
|
40
|
+
<text>Account panel</text>
|
|
41
|
+
</ui-tabs-content>
|
|
42
|
+
<ui-tabs-content value="billing">
|
|
43
|
+
<text>Billing panel</text>
|
|
44
|
+
</ui-tabs-content>
|
|
45
|
+
<ui-tabs-content value="contact">
|
|
46
|
+
<text>Contact panel</text>
|
|
47
|
+
</ui-tabs-content>
|
|
19
48
|
</ui-tabs>
|
|
20
49
|
`,
|
|
21
50
|
})
|
|
22
51
|
class TabsHost {
|
|
23
|
-
value = '
|
|
52
|
+
readonly value = signal('account');
|
|
53
|
+
readonly changes: string[] = [];
|
|
54
|
+
readonly tabs = viewChild.required(UiTabs);
|
|
24
55
|
}
|
|
25
56
|
|
|
57
|
+
const renderTabs = async () => {
|
|
58
|
+
const result = await render(TabsHost);
|
|
59
|
+
/**
|
|
60
|
+
* A trigger's own view, which holds the tap listener, around its label.
|
|
61
|
+
*/
|
|
62
|
+
const trigger = (label: string) => result.getByText(label).parentElement!;
|
|
63
|
+
/**
|
|
64
|
+
* A panel's own view, which wraps its projected text.
|
|
65
|
+
*/
|
|
66
|
+
const panel = (text: string) => result.getByText(text).parentElement!;
|
|
67
|
+
return {
|
|
68
|
+
...result,
|
|
69
|
+
host: result.componentRef.instance as TabsHost,
|
|
70
|
+
trigger,
|
|
71
|
+
panel,
|
|
72
|
+
/** The display of a panel's host element, which shows or hides it. */
|
|
73
|
+
displayOf: (text: string) =>
|
|
74
|
+
(panel(text).parentElement as HTMLElement).style.display,
|
|
75
|
+
tap: async (label: string) => {
|
|
76
|
+
fireEvent.tap(trigger(label));
|
|
77
|
+
await waitForUpdate();
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/** `__ElementAnimate`'s first argument for starting an animation. */
|
|
83
|
+
const START = 0;
|
|
84
|
+
/** `__ElementAnimate`'s first argument for canceling an animation. */
|
|
85
|
+
const CANCEL = 3;
|
|
86
|
+
|
|
26
87
|
describe('UiTabs', () => {
|
|
27
|
-
it(
|
|
28
|
-
const {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
expect(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
expect(inst.value()).toBe('x');
|
|
41
|
-
expect(emitted).toEqual(['x']);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('registerTab() records tab order without duplicates', async () => {
|
|
45
|
-
const { componentRef } = await render(UiTabs);
|
|
46
|
-
const inst = componentRef.instance as UiTabs;
|
|
47
|
-
inst.registerTab('a');
|
|
48
|
-
inst.registerTab('b');
|
|
49
|
-
inst.registerTab('a'); // duplicate, should be ignored
|
|
50
|
-
// Seed the effect with a known previousValue so subsequent transitions
|
|
51
|
-
// exercise the direction-set branch (not the initial-run early return).
|
|
52
|
-
inst.value.set('a');
|
|
53
|
-
await waitForUpdate();
|
|
54
|
-
// a=0, b=1 → nextIndex > prevIndex → direction=1 (forward branch)
|
|
55
|
-
inst.value.set('b');
|
|
56
|
-
await waitForUpdate();
|
|
57
|
-
await waitForUpdate();
|
|
58
|
-
expect(inst.direction()).toBe(1);
|
|
59
|
-
// Reverse → direction=-1 (backward branch)
|
|
60
|
-
inst.value.set('a');
|
|
61
|
-
await waitForUpdate();
|
|
62
|
-
await waitForUpdate();
|
|
63
|
-
expect(inst.direction()).toBe(-1);
|
|
88
|
+
it("merges each part's class with its own", async () => {
|
|
89
|
+
const { container, trigger, panel } = await renderTabs();
|
|
90
|
+
// Each host element carries the given class too, so look for the view
|
|
91
|
+
// that has both its own and the given class.
|
|
92
|
+
expect(container.querySelector('.flex-col.w-80')).not.toBeNull();
|
|
93
|
+
expect(container.querySelector('.rounded-lg.mb-2')).not.toBeNull();
|
|
94
|
+
expect(
|
|
95
|
+
container.querySelector('[accessibility-traits="tabbar"]'),
|
|
96
|
+
).not.toBeNull();
|
|
97
|
+
expect(trigger('Account').classList).toContain('rounded-sm');
|
|
98
|
+
expect(trigger('Account').classList).toContain('px-4');
|
|
99
|
+
expect(panel('Account panel').classList).toContain('mt-2');
|
|
100
|
+
expect(panel('Account panel').classList).toContain('gap-2');
|
|
64
101
|
});
|
|
65
102
|
|
|
66
|
-
it(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
expect(
|
|
103
|
+
it("shows only the selected tab's panel, and highlights its trigger", async () => {
|
|
104
|
+
const { getByText, trigger, displayOf } = await renderTabs();
|
|
105
|
+
expect(displayOf('Account panel')).toBe('flex');
|
|
106
|
+
expect(displayOf('Billing panel')).toBe('none');
|
|
107
|
+
expect(displayOf('Contact panel')).toBe('none');
|
|
108
|
+
|
|
109
|
+
expect(trigger('Account').classList).toContain('bg-background');
|
|
110
|
+
expect(getByText('Account').classList).toContain('text-foreground');
|
|
111
|
+
expect(trigger('Billing').classList).toContain('bg-transparent');
|
|
112
|
+
expect(getByText('Billing').classList).toContain('text-muted-foreground');
|
|
75
113
|
});
|
|
76
114
|
|
|
77
|
-
it('
|
|
78
|
-
const {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
115
|
+
it('selects a tab when its trigger is tapped', async () => {
|
|
116
|
+
const { host, tap, trigger, displayOf } = await renderTabs();
|
|
117
|
+
await tap('Billing');
|
|
118
|
+
expect(host.value()).toBe('billing');
|
|
119
|
+
expect(host.changes).toEqual(['billing']);
|
|
120
|
+
expect(displayOf('Account panel')).toBe('none');
|
|
121
|
+
expect(displayOf('Billing panel')).toBe('flex');
|
|
122
|
+
expect(trigger('Billing').classList).toContain('bg-background');
|
|
123
|
+
expect(trigger('Account').classList).toContain('bg-transparent');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('reads to a screen reader as a tab bar, with the selected tab selected', async () => {
|
|
127
|
+
const { getByRole, tap } = await renderTabs();
|
|
128
|
+
expect(getByRole('tablist')).toBeTruthy();
|
|
129
|
+
expect(
|
|
130
|
+
getByRole('button', { name: 'Account', selected: true }),
|
|
131
|
+
).toBeTruthy();
|
|
132
|
+
expect(
|
|
133
|
+
getByRole('button', { name: 'Billing', selected: false }),
|
|
134
|
+
).toBeTruthy();
|
|
135
|
+
await tap('Billing');
|
|
136
|
+
expect(
|
|
137
|
+
getByRole('button', { name: 'Billing', selected: true }),
|
|
138
|
+
).toBeTruthy();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('clips a hidden panel, whose text iOS would draw from its corner', async () => {
|
|
142
|
+
const { panel } = await renderTabs();
|
|
143
|
+
const hostOf = (text: string) => panel(text).parentElement as HTMLElement;
|
|
144
|
+
expect(hostOf('Billing panel').style.overflow).toBe('hidden');
|
|
145
|
+
expect(hostOf('Account panel').style.overflow).toBe('');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('shows the tab its value is set to, without reporting a change', async () => {
|
|
149
|
+
const { host, displayOf } = await renderTabs();
|
|
150
|
+
host.value.set('contact');
|
|
85
151
|
await waitForUpdate();
|
|
86
|
-
expect(
|
|
152
|
+
expect(displayOf('Contact panel')).toBe('flex');
|
|
153
|
+
expect(displayOf('Account panel')).toBe('none');
|
|
154
|
+
// `changed` reports what the user selects, not what the app sets.
|
|
155
|
+
expect(host.changes).toEqual([]);
|
|
87
156
|
});
|
|
88
157
|
|
|
89
|
-
it('
|
|
90
|
-
const {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
158
|
+
it('keeps its slide direction for a value with no trigger', async () => {
|
|
159
|
+
const { host, tap } = await renderTabs();
|
|
160
|
+
await tap('Contact');
|
|
161
|
+
await tap('Billing');
|
|
162
|
+
expect(host.tabs().direction()).toBe(-1);
|
|
163
|
+
|
|
164
|
+
// Neither moving to nor moving from a value without a trigger has a
|
|
165
|
+
// place in the tab order to slide from.
|
|
166
|
+
host.value.set('archived');
|
|
95
167
|
await waitForUpdate();
|
|
96
|
-
|
|
168
|
+
expect(host.tabs().direction()).toBe(-1);
|
|
169
|
+
host.value.set('contact');
|
|
97
170
|
await waitForUpdate();
|
|
98
|
-
expect(
|
|
171
|
+
expect(host.tabs().direction()).toBe(-1);
|
|
99
172
|
});
|
|
100
|
-
});
|
|
101
173
|
|
|
102
|
-
describe('
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
174
|
+
describe('animations', () => {
|
|
175
|
+
// Watched per test: the testing library resets the main thread after each.
|
|
176
|
+
let animate: MockInstance<LynxMainThread['__ElementAnimate']>;
|
|
177
|
+
beforeEach(() => {
|
|
178
|
+
animate = vi.spyOn(lynxMainThread(), '__ElementAnimate');
|
|
179
|
+
});
|
|
180
|
+
afterEach(() => animate.mockRestore());
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* The keyframes of the animations started on an element.
|
|
184
|
+
*/
|
|
185
|
+
const keyframesOn = (element: Element) =>
|
|
186
|
+
animate.mock.calls
|
|
187
|
+
.filter(
|
|
188
|
+
([target, [operation]]) => target === element && operation === START,
|
|
189
|
+
)
|
|
190
|
+
.map(([, [, , keyframes]]) => keyframes);
|
|
109
191
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
192
|
+
/**
|
|
193
|
+
* The keys of the animations started on an element.
|
|
194
|
+
*/
|
|
195
|
+
const keysOn = (element: Element) =>
|
|
196
|
+
animate.mock.calls
|
|
197
|
+
.filter(
|
|
198
|
+
([target, [operation]]) => target === element && operation === START,
|
|
199
|
+
)
|
|
200
|
+
.map(([, [, key]]) => key);
|
|
115
201
|
|
|
116
|
-
const
|
|
117
|
-
|
|
202
|
+
const reveal = [
|
|
203
|
+
{ transform: 'translateY(6px)', opacity: 0 },
|
|
204
|
+
{ transform: 'translateY(0)', opacity: 1 },
|
|
205
|
+
];
|
|
206
|
+
const slideFrom = (x: number) => [
|
|
207
|
+
{ transform: `translateX(${x}px)`, opacity: 0 },
|
|
208
|
+
{ transform: 'translateX(0px)', opacity: 1 },
|
|
209
|
+
];
|
|
210
|
+
const activate = [
|
|
211
|
+
{ transform: 'scaleX(0.95)', opacity: 0.7 },
|
|
212
|
+
{ transform: 'scaleX(1)', opacity: 1 },
|
|
213
|
+
];
|
|
214
|
+
const deactivate = [
|
|
215
|
+
{ transform: 'scaleX(1)', opacity: 1 },
|
|
216
|
+
{ transform: 'scaleX(0.97)', opacity: 0.7 },
|
|
217
|
+
];
|
|
218
|
+
|
|
219
|
+
it('reveals a panel the first time it shows, then slides it in from the side of its tab', async () => {
|
|
220
|
+
const { panel, tap } = await renderTabs();
|
|
221
|
+
// The panel waits a macrotask so its display has applied before it
|
|
222
|
+
// animates; this update lets that run.
|
|
223
|
+
await waitForUpdate();
|
|
224
|
+
expect(keyframesOn(panel('Account panel'))).toEqual([reveal]);
|
|
225
|
+
|
|
226
|
+
await tap('Contact');
|
|
227
|
+
await waitForUpdate();
|
|
228
|
+
expect(keyframesOn(panel('Contact panel'))).toEqual([reveal]);
|
|
229
|
+
|
|
230
|
+
// Account is left of Contact, so its panel slides in from the left.
|
|
231
|
+
await tap('Account');
|
|
232
|
+
await waitForUpdate();
|
|
233
|
+
expect(keyframesOn(panel('Account panel'))).toEqual([
|
|
234
|
+
reveal,
|
|
235
|
+
slideFrom(-24),
|
|
236
|
+
]);
|
|
237
|
+
// Showing it again stopped its earlier reveal first.
|
|
238
|
+
const [revealKey] = keysOn(panel('Account panel'));
|
|
239
|
+
expect(animate).toHaveBeenCalledWith(panel('Account panel'), [
|
|
240
|
+
CANCEL,
|
|
241
|
+
revealKey,
|
|
242
|
+
]);
|
|
243
|
+
|
|
244
|
+
// Contact is right of Account, so its panel slides in from the right.
|
|
245
|
+
await tap('Contact');
|
|
246
|
+
await waitForUpdate();
|
|
247
|
+
expect(keyframesOn(panel('Contact panel'))).toEqual([
|
|
248
|
+
reveal,
|
|
249
|
+
slideFrom(24),
|
|
250
|
+
]);
|
|
118
251
|
});
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
252
|
+
|
|
253
|
+
it('scales a trigger up when selected and down when deselected', async () => {
|
|
254
|
+
const { trigger, tap } = await renderTabs();
|
|
255
|
+
// Nothing changed selection yet, so no trigger animates.
|
|
256
|
+
expect(keyframesOn(trigger('Account'))).toEqual([]);
|
|
257
|
+
|
|
258
|
+
await tap('Billing');
|
|
259
|
+
expect(keyframesOn(trigger('Billing'))).toEqual([activate]);
|
|
260
|
+
expect(keyframesOn(trigger('Account'))).toEqual([deactivate]);
|
|
261
|
+
|
|
262
|
+
await tap('Account');
|
|
263
|
+
expect(keyframesOn(trigger('Account'))).toEqual([deactivate, activate]);
|
|
264
|
+
// Each animation stops the trigger's previous one.
|
|
265
|
+
const [deactivateKey] = keysOn(trigger('Account'));
|
|
266
|
+
expect(animate).toHaveBeenCalledWith(trigger('Account'), [
|
|
267
|
+
CANCEL,
|
|
268
|
+
deactivateKey,
|
|
269
|
+
]);
|
|
124
270
|
});
|
|
125
|
-
await waitForUpdate();
|
|
126
|
-
await waitForUpdate();
|
|
127
|
-
return { tabs, trigger, triggerResult };
|
|
128
|
-
};
|
|
129
271
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
272
|
+
it('changes at once under Reduce Motion', async () => {
|
|
273
|
+
const reduced = {
|
|
274
|
+
reduceMotion: signal(true),
|
|
275
|
+
reduceTransparency: signal(false),
|
|
276
|
+
increaseContrast: signal(false),
|
|
277
|
+
};
|
|
278
|
+
const result = await render(TabsHost, {
|
|
279
|
+
providers: [{ provide: LynxAccessibilitySettings, useValue: reduced }],
|
|
280
|
+
});
|
|
281
|
+
await waitForUpdate();
|
|
282
|
+
// Only this test's animations: the last test's panels may still have
|
|
283
|
+
// had a reveal waiting for its tick.
|
|
284
|
+
animate.mockClear();
|
|
285
|
+
fireEvent.tap(result.getByText('Billing').parentElement!);
|
|
286
|
+
await waitForUpdate();
|
|
287
|
+
await waitForUpdate();
|
|
288
|
+
expect(animate).not.toHaveBeenCalled();
|
|
289
|
+
expect(
|
|
290
|
+
(
|
|
291
|
+
result.getByText('Billing panel').parentElement!
|
|
292
|
+
.parentElement as HTMLElement
|
|
293
|
+
).style.display,
|
|
294
|
+
).toBe('flex');
|
|
295
|
+
});
|
|
135
296
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
297
|
+
it("doesn't animate a selection that changed back within one frame", async () => {
|
|
298
|
+
const { host, trigger, tap } = await renderTabs();
|
|
299
|
+
await tap('Billing');
|
|
300
|
+
await waitForUpdate();
|
|
301
|
+
animate.mockClear();
|
|
302
|
+
|
|
303
|
+
// Two taps before the next update: Billing is selected again.
|
|
304
|
+
fireEvent.tap(trigger('Contact'));
|
|
305
|
+
fireEvent.tap(trigger('Billing'));
|
|
306
|
+
await waitForUpdate();
|
|
307
|
+
await waitForUpdate();
|
|
308
|
+
expect(host.changes).toEqual(['billing', 'contact', 'billing']);
|
|
309
|
+
expect(animate).not.toHaveBeenCalled();
|
|
310
|
+
// The direction still points the way Billing was reached, from Account.
|
|
311
|
+
expect(host.tabs().direction()).toBe(1);
|
|
312
|
+
});
|
|
150
313
|
});
|
|
151
314
|
});
|
|
152
315
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
316
|
+
@Component({
|
|
317
|
+
imports: [UiTabs, UiTabsList, UiTabsTrigger, UiTabsContent, LYNX_ELEMENTS],
|
|
318
|
+
template: `
|
|
319
|
+
<ui-tabs
|
|
320
|
+
[value]="value()"
|
|
321
|
+
(valueChange)="value.set($event)"
|
|
322
|
+
(changed)="changes.push($event)"
|
|
323
|
+
>
|
|
324
|
+
<ui-tabs-list [variant]="variant()">
|
|
325
|
+
@for (tab of tabs; track tab) {
|
|
326
|
+
<ui-tabs-trigger
|
|
327
|
+
[value]="tab"
|
|
328
|
+
[class]="tab === 'People' ? 'flex-[2]' : ''"
|
|
329
|
+
>{{ tab }}</ui-tabs-trigger
|
|
330
|
+
>
|
|
331
|
+
}
|
|
332
|
+
</ui-tabs-list>
|
|
333
|
+
@for (tab of tabs; track tab) {
|
|
334
|
+
<ui-tabs-content [value]="tab"
|
|
335
|
+
><text>{{ tab }} panel</text></ui-tabs-content
|
|
336
|
+
>
|
|
337
|
+
}
|
|
338
|
+
</ui-tabs>
|
|
339
|
+
`,
|
|
340
|
+
})
|
|
341
|
+
class SegmentedTabsHost {
|
|
342
|
+
readonly tabs = ['Photos', 'Albums', 'People'];
|
|
343
|
+
readonly value = signal('Photos');
|
|
344
|
+
readonly variant = signal<TabsListVariant>('segmented');
|
|
345
|
+
readonly changes: string[] = [];
|
|
346
|
+
}
|
|
159
347
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
348
|
+
/**
|
|
349
|
+
* A trigger outside a list, which has no segmented control to report to.
|
|
350
|
+
*/
|
|
351
|
+
@Component({
|
|
352
|
+
imports: [UiTabs, UiTabsTrigger, LYNX_ELEMENTS],
|
|
353
|
+
template: `
|
|
354
|
+
<ui-tabs value="a">
|
|
355
|
+
<ui-tabs-trigger value="a">Alone</ui-tabs-trigger>
|
|
356
|
+
</ui-tabs>
|
|
357
|
+
`,
|
|
358
|
+
})
|
|
359
|
+
class LoneTriggerHost {}
|
|
360
|
+
|
|
361
|
+
/** Accessibility settings, as the host app would report them. */
|
|
362
|
+
const settings = {
|
|
363
|
+
reduceMotion: signal(false),
|
|
364
|
+
reduceTransparency: signal(false),
|
|
365
|
+
increaseContrast: signal(false),
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
describe('UiTabsList, segmented', () => {
|
|
369
|
+
// The indicator's selector isn't a Lynx element, so the renderer falls
|
|
370
|
+
// back to a <view> for it and warns.
|
|
371
|
+
beforeEach(() => {
|
|
372
|
+
vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
afterEach(() => {
|
|
376
|
+
vi.restoreAllMocks();
|
|
377
|
+
vi.unstubAllGlobals();
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
const renderSegmented = async (
|
|
381
|
+
platform = { platform: 'iOS', osVersion: '26.0' },
|
|
382
|
+
) => {
|
|
383
|
+
vi.stubGlobal('SystemInfo', platform);
|
|
384
|
+
const result = await render(SegmentedTabsHost, {
|
|
385
|
+
providers: [{ provide: LynxAccessibilitySettings, useValue: settings }],
|
|
168
386
|
});
|
|
387
|
+
/**
|
|
388
|
+
* A trigger's own view, around its label.
|
|
389
|
+
*/
|
|
390
|
+
const trigger = (label: string) => result.getByText(label).parentElement!;
|
|
391
|
+
/**
|
|
392
|
+
* A trigger's host, the track's flex item.
|
|
393
|
+
*/
|
|
394
|
+
const slot = (label: string) => trigger(label).parentElement!;
|
|
395
|
+
const track = () => slot('Photos').parentElement!;
|
|
396
|
+
/**
|
|
397
|
+
* The indicator's frame, which moves between the tabs.
|
|
398
|
+
*/
|
|
399
|
+
const frame = () =>
|
|
400
|
+
track().querySelector<HTMLElement>('.h-full .bottom-0.top-0');
|
|
401
|
+
return {
|
|
402
|
+
...result,
|
|
403
|
+
host: result.componentRef.instance as SegmentedTabsHost,
|
|
404
|
+
trigger,
|
|
405
|
+
slot,
|
|
406
|
+
track,
|
|
407
|
+
frame,
|
|
408
|
+
measure: async () => {
|
|
409
|
+
for (const [label, width] of [
|
|
410
|
+
['Photos', 90],
|
|
411
|
+
['Albums', 90],
|
|
412
|
+
['People', 180],
|
|
413
|
+
] as const) {
|
|
414
|
+
fireEvent.layoutchange(slot(label), { detail: { width } });
|
|
415
|
+
}
|
|
416
|
+
await waitForUpdate();
|
|
417
|
+
},
|
|
418
|
+
/** The display of a panel's host element, which shows or hides it. */
|
|
419
|
+
displayOf: (text: string) =>
|
|
420
|
+
(result.getByText(text).parentElement!.parentElement as HTMLElement)
|
|
421
|
+
.style.display,
|
|
422
|
+
};
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
it("draws the platform's segmented control, filling its width, its tabs sharing it", async () => {
|
|
426
|
+
const { track, slot, trigger, frame, getByText } = await renderSegmented();
|
|
427
|
+
expect(track().classList).toContain('rounded-full');
|
|
428
|
+
expect(track().classList).not.toContain('self-start');
|
|
429
|
+
expect(slot('Albums').classList).toContain('flex-1');
|
|
430
|
+
expect(slot('People').classList).toContain('flex-[2]');
|
|
431
|
+
expect(trigger('Photos').classList).toContain('justify-center');
|
|
432
|
+
expect(getByText('Photos').classList).toContain('font-medium');
|
|
433
|
+
expect(getByText('Albums').classList).not.toContain('font-medium');
|
|
434
|
+
expect(frame()).not.toBeNull();
|
|
435
|
+
});
|
|
169
436
|
|
|
170
|
-
|
|
171
|
-
|
|
437
|
+
it("switches panels on a tap, without the default tabs' scale animation", async () => {
|
|
438
|
+
const animate = vi.spyOn(lynxMainThread(), '__ElementAnimate');
|
|
439
|
+
const { host, trigger, displayOf, getByRole } = await renderSegmented();
|
|
440
|
+
fireEvent.tap(trigger('Albums'));
|
|
172
441
|
await waitForUpdate();
|
|
173
|
-
|
|
442
|
+
expect(host.value()).toBe('Albums');
|
|
443
|
+
expect(host.changes).toEqual(['Albums']);
|
|
444
|
+
expect(displayOf('Albums panel')).toBe('flex');
|
|
445
|
+
expect(
|
|
446
|
+
getByRole('button', { name: 'Albums', selected: true }),
|
|
447
|
+
).toBeTruthy();
|
|
448
|
+
expect(
|
|
449
|
+
animate.mock.calls.filter(([target]) => target === trigger('Albums')),
|
|
450
|
+
).toEqual([]);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it('switches to the tab the indicator is dragged to', async () => {
|
|
454
|
+
const { host, trigger, track, frame, measure } = await renderSegmented();
|
|
455
|
+
await measure();
|
|
456
|
+
expect(frame()!.style.left).toBe('0px');
|
|
457
|
+
|
|
458
|
+
fireEvent.touchstart(trigger('Photos'), { touches: [{ pageX: 45 }] });
|
|
459
|
+
fireEvent.touchmove(trigger('Photos'), { touches: [{ pageX: 300 }] });
|
|
460
|
+
fireEvent.touchend(trigger('Photos'));
|
|
461
|
+
// The tap Lynx may send as the finger lifts doesn't pick Photos again.
|
|
462
|
+
fireEvent.tap(trigger('Photos'));
|
|
174
463
|
await waitForUpdate();
|
|
464
|
+
expect(host.value()).toBe('People');
|
|
465
|
+
expect(host.changes).toEqual(['People']);
|
|
466
|
+
expect(frame()!.style.left).toBe('180px');
|
|
175
467
|
|
|
176
|
-
//
|
|
177
|
-
|
|
468
|
+
// Taken away, a drag goes back.
|
|
469
|
+
fireEvent.touchstart(trigger('People'), { touches: [{ pageX: 270 }] });
|
|
470
|
+
fireEvent.touchmove(trigger('People'), { touches: [{ pageX: 20 }] });
|
|
471
|
+
fireEvent.touchcancel(trigger('People'));
|
|
178
472
|
await waitForUpdate();
|
|
179
|
-
|
|
473
|
+
expect(frame()!.style.left).toBe('180px');
|
|
474
|
+
|
|
475
|
+
// In the web preview, with the mouse, until it leaves the row.
|
|
476
|
+
fireEvent.mousedown(trigger('People'), { pageX: 270 });
|
|
477
|
+
fireEvent.mousemove(trigger('Albums'), { pageX: 150 });
|
|
478
|
+
fireLynxEvent(trigger('People'), 'mouseleave');
|
|
180
479
|
await waitForUpdate();
|
|
480
|
+
expect(frame()!.style.left).toBe('60px');
|
|
481
|
+
fireLynxEvent(track(), 'mouseleave');
|
|
181
482
|
await waitForUpdate();
|
|
483
|
+
expect(frame()!.style.left).toBe('180px');
|
|
182
484
|
|
|
183
|
-
|
|
485
|
+
fireEvent.mousedown(trigger('People'), { pageX: 270 });
|
|
486
|
+
fireEvent.mousemove(trigger('Albums'), { pageX: 150 });
|
|
487
|
+
fireEvent.mouseup(trigger('Albums'));
|
|
488
|
+
await waitForUpdate();
|
|
489
|
+
expect(host.value()).toBe('Albums');
|
|
184
490
|
});
|
|
185
491
|
|
|
186
|
-
it('
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
providers: [{ provide: UiTabs, useValue: tabs }],
|
|
492
|
+
it("is Material's connected button group on Android", async () => {
|
|
493
|
+
const { trigger, getByText } = await renderSegmented({
|
|
494
|
+
platform: 'Android',
|
|
495
|
+
osVersion: '15',
|
|
191
496
|
});
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
497
|
+
expect(trigger('Photos').classList).toContain('bg-primary');
|
|
498
|
+
expect(trigger('Photos').style.transition).toContain('background-color');
|
|
499
|
+
expect(getByText('Photos').classList).toContain('text-primary-foreground');
|
|
500
|
+
// The last tab rounds the group's end, and the one between has small
|
|
501
|
+
// corners.
|
|
502
|
+
expect(trigger('Albums').classList).toContain('rounded-[8px]');
|
|
503
|
+
expect(trigger('People').classList).toContain('rounded-r-[20px]');
|
|
504
|
+
// A press shows Material's state layer.
|
|
505
|
+
fireEvent.touchstart(trigger('Albums'), { touches: [{ pageX: 150 }] });
|
|
195
506
|
await waitForUpdate();
|
|
507
|
+
expect(trigger('Albums').querySelector('.opacity-10')).not.toBeNull();
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
it('goes back to the default tabs when the variant changes', async () => {
|
|
511
|
+
const { host, track, slot, frame } = await renderSegmented();
|
|
512
|
+
host.variant.set('default');
|
|
513
|
+
await waitForUpdate();
|
|
514
|
+
expect(track().classList).toContain('self-start');
|
|
515
|
+
expect(slot('Photos').classList).not.toContain('flex-1');
|
|
516
|
+
expect(frame()).toBeNull();
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
it('lets a trigger outside a list take touches as the default tabs do', async () => {
|
|
520
|
+
const result = await render(LoneTriggerHost);
|
|
521
|
+
const trigger = result.getByText('Alone').parentElement!;
|
|
522
|
+
fireEvent.layoutchange(trigger.parentElement!, { detail: { width: 80 } });
|
|
523
|
+
fireEvent.touchstart(trigger, { touches: [{ pageX: 10 }] });
|
|
524
|
+
fireEvent.touchmove(trigger, { touches: [{ pageX: 60 }] });
|
|
525
|
+
fireEvent.touchend(trigger);
|
|
526
|
+
fireEvent.touchcancel(trigger);
|
|
527
|
+
fireLynxEvent(trigger, 'mouseleave');
|
|
528
|
+
fireEvent.tap(trigger);
|
|
196
529
|
await waitForUpdate();
|
|
197
|
-
expect(
|
|
530
|
+
expect(trigger.classList).toContain('bg-background');
|
|
531
|
+
expect(trigger.parentElement!.classList).not.toContain('flex-1');
|
|
198
532
|
});
|
|
199
533
|
});
|