@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,186 +1,599 @@
|
|
|
1
|
-
import { Component } 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 { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
9
|
+
import { fireLynxEvent } from '../../../test-utils/fire-lynx-event';
|
|
10
|
+
import { UiIcon } from '../icon/icon';
|
|
11
|
+
import { ICON_LIGHT_COLORS, UI_ICON_COLOR } from '../icon/icon-theme';
|
|
12
|
+
import {
|
|
13
|
+
type ToggleGroupVariant,
|
|
14
|
+
type ToggleSize,
|
|
15
|
+
type ToggleVariant,
|
|
16
|
+
UiToggle,
|
|
17
|
+
UiToggleGroup,
|
|
18
|
+
UiToggleGroupItem,
|
|
19
|
+
} from './toggle';
|
|
7
20
|
|
|
8
21
|
@Component({
|
|
9
|
-
standalone: true,
|
|
10
22
|
imports: [UiToggle, LYNX_ELEMENTS],
|
|
11
|
-
template:
|
|
23
|
+
template: `
|
|
24
|
+
<ui-toggle
|
|
25
|
+
[(pressed)]="bold"
|
|
26
|
+
[variant]="variant()"
|
|
27
|
+
[size]="size()"
|
|
28
|
+
[disabled]="disabled()"
|
|
29
|
+
class="w-20"
|
|
30
|
+
><text>Bold</text></ui-toggle
|
|
31
|
+
>
|
|
32
|
+
`,
|
|
12
33
|
})
|
|
13
|
-
class ToggleHost {
|
|
34
|
+
class ToggleHost {
|
|
35
|
+
readonly bold = signal(false);
|
|
36
|
+
readonly variant = signal<ToggleVariant>('default');
|
|
37
|
+
readonly size = signal<ToggleSize>('default');
|
|
38
|
+
readonly disabled = signal(false);
|
|
39
|
+
}
|
|
14
40
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
41
|
+
@Component({
|
|
42
|
+
imports: [UiToggleGroup, UiToggleGroupItem, LYNX_ELEMENTS],
|
|
43
|
+
template: `
|
|
44
|
+
<ui-toggle-group
|
|
45
|
+
[type]="type()"
|
|
46
|
+
[(value)]="align"
|
|
47
|
+
[variant]="variant()"
|
|
48
|
+
[size]="size()"
|
|
49
|
+
[disabled]="disabled()"
|
|
50
|
+
class="w-full"
|
|
51
|
+
>
|
|
52
|
+
<ui-toggle-group-item value="left" class="flex-1"
|
|
53
|
+
><text>Left</text></ui-toggle-group-item
|
|
54
|
+
>
|
|
55
|
+
<ui-toggle-group-item value="center"
|
|
56
|
+
><text>Center</text></ui-toggle-group-item
|
|
57
|
+
>
|
|
58
|
+
<ui-toggle-group-item value="right"
|
|
59
|
+
><text>Right</text></ui-toggle-group-item
|
|
60
|
+
>
|
|
61
|
+
</ui-toggle-group>
|
|
62
|
+
`,
|
|
63
|
+
})
|
|
64
|
+
class ToggleGroupHost {
|
|
65
|
+
readonly type = signal<'single' | 'multiple'>('single');
|
|
66
|
+
readonly align = signal<string[]>([]);
|
|
67
|
+
readonly variant = signal<ToggleGroupVariant>('default');
|
|
68
|
+
readonly size = signal<ToggleSize>('default');
|
|
69
|
+
readonly disabled = signal(false);
|
|
70
|
+
readonly group = viewChild.required(UiToggleGroup);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The view (the one that takes presses) a toggle or toggle group item
|
|
75
|
+
* projects its label into.
|
|
76
|
+
*/
|
|
77
|
+
const viewOf = (label: Element) => label.parentElement!;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The scale a view's inline style holds, if any.
|
|
81
|
+
*/
|
|
82
|
+
const scaleOf = (view: Element) =>
|
|
83
|
+
(view as HTMLElement).style.getPropertyValue('transform');
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Presses and releases a view by touch, by mouse, and by a touch a scroll
|
|
87
|
+
* takes over, checking it scales down while pressed and back after each.
|
|
88
|
+
*/
|
|
89
|
+
const expectPressFeedback = (view: Element) => {
|
|
90
|
+
fireEvent.touchstart(view);
|
|
91
|
+
expect(scaleOf(view)).toBe('scale(0.97)');
|
|
92
|
+
fireEvent.touchend(view);
|
|
93
|
+
expect(scaleOf(view)).toBe('scale(1)');
|
|
94
|
+
|
|
95
|
+
fireEvent.touchstart(view);
|
|
96
|
+
fireEvent.touchcancel(view);
|
|
97
|
+
expect(scaleOf(view)).toBe('scale(1)');
|
|
20
98
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
99
|
+
// Lynx on the web fires mouse events for a cursor, never touch ones.
|
|
100
|
+
fireEvent.mousedown(view);
|
|
101
|
+
expect(scaleOf(view)).toBe('scale(0.97)');
|
|
102
|
+
fireEvent.mouseup(view);
|
|
103
|
+
expect(scaleOf(view)).toBe('scale(1)');
|
|
104
|
+
|
|
105
|
+
fireEvent.mousedown(view);
|
|
106
|
+
fireLynxEvent(view, 'mouseleave');
|
|
107
|
+
expect(scaleOf(view)).toBe('scale(1)');
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Presses a disabled view by touch and by mouse: it shouldn't scale.
|
|
112
|
+
*/
|
|
113
|
+
const expectNoPressFeedback = (view: Element) => {
|
|
114
|
+
fireEvent.touchstart(view);
|
|
115
|
+
fireEvent.mousedown(view);
|
|
116
|
+
expect(scaleOf(view)).toBe('');
|
|
117
|
+
fireEvent.touchend(view);
|
|
118
|
+
fireEvent.mouseup(view);
|
|
119
|
+
expect(scaleOf(view)).toBe('');
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
describe('UiToggle', () => {
|
|
123
|
+
const renderToggle = async () => {
|
|
124
|
+
const result = await render(ToggleHost);
|
|
125
|
+
return {
|
|
126
|
+
...result,
|
|
127
|
+
host: result.componentRef.instance as ToggleHost,
|
|
128
|
+
toggle: () => viewOf(result.getByText('Bold')),
|
|
25
129
|
};
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
it('renders its content unpressed, with the given class', async () => {
|
|
133
|
+
const { toggle } = await renderToggle();
|
|
134
|
+
expect(toggle().classList).toContain('bg-transparent');
|
|
135
|
+
expect(toggle().classList).toContain('h-10');
|
|
136
|
+
expect(toggle().classList).toContain('w-20');
|
|
137
|
+
expect(toggle().classList).not.toContain('opacity-50');
|
|
30
138
|
});
|
|
31
139
|
|
|
32
|
-
it('
|
|
33
|
-
const {
|
|
34
|
-
|
|
35
|
-
onTap: () => void;
|
|
36
|
-
};
|
|
37
|
-
setInputSignal(inst.disabled, true);
|
|
140
|
+
it('flips `pressed` and its highlight on each tap', async () => {
|
|
141
|
+
const { host, toggle } = await renderToggle();
|
|
142
|
+
fireEvent.tap(toggle());
|
|
38
143
|
await waitForUpdate();
|
|
39
|
-
|
|
40
|
-
expect(
|
|
144
|
+
expect(host.bold()).toBe(true);
|
|
145
|
+
expect(toggle().classList).toContain('bg-accent');
|
|
146
|
+
|
|
147
|
+
fireEvent.tap(toggle());
|
|
148
|
+
await waitForUpdate();
|
|
149
|
+
expect(host.bold()).toBe(false);
|
|
150
|
+
expect(toggle().classList).toContain('bg-transparent');
|
|
41
151
|
});
|
|
42
152
|
|
|
43
|
-
it('
|
|
44
|
-
const {
|
|
45
|
-
|
|
46
|
-
onPressStart: () => void;
|
|
47
|
-
onPressEnd: () => void;
|
|
48
|
-
onPressCancel: () => void;
|
|
49
|
-
};
|
|
50
|
-
expect(() => inst.onPressStart()).not.toThrow();
|
|
51
|
-
expect(() => inst.onPressEnd()).not.toThrow();
|
|
52
|
-
expect(() => inst.onPressCancel()).not.toThrow();
|
|
53
|
-
setInputSignal((inst as unknown as UiToggle).disabled, true);
|
|
153
|
+
it('highlights when the app presses it', async () => {
|
|
154
|
+
const { host, toggle } = await renderToggle();
|
|
155
|
+
host.bold.set(true);
|
|
54
156
|
await waitForUpdate();
|
|
55
|
-
expect(()
|
|
56
|
-
expect(() => inst.onPressEnd()).not.toThrow();
|
|
57
|
-
expect(() => inst.onPressCancel()).not.toThrow();
|
|
157
|
+
expect(toggle().classList).toContain('bg-accent');
|
|
58
158
|
});
|
|
59
159
|
|
|
60
|
-
it(
|
|
61
|
-
const {
|
|
62
|
-
|
|
63
|
-
inst.pressed.set(true);
|
|
160
|
+
it("dims and ignores taps while it's disabled", async () => {
|
|
161
|
+
const { host, toggle } = await renderToggle();
|
|
162
|
+
host.disabled.set(true);
|
|
64
163
|
await waitForUpdate();
|
|
65
|
-
|
|
66
|
-
|
|
164
|
+
expect(toggle().classList).toContain('opacity-50');
|
|
165
|
+
fireEvent.tap(toggle());
|
|
166
|
+
expect(host.bold()).toBe(false);
|
|
67
167
|
});
|
|
68
168
|
|
|
69
|
-
it('applies
|
|
70
|
-
const {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
setInputSignal(inst.size, 'sm');
|
|
169
|
+
it('applies the classes for its variant and size', async () => {
|
|
170
|
+
const { host, toggle } = await renderToggle();
|
|
171
|
+
host.variant.set('outline');
|
|
172
|
+
host.size.set('sm');
|
|
74
173
|
await waitForUpdate();
|
|
75
|
-
|
|
76
|
-
expect(
|
|
77
|
-
|
|
174
|
+
expect(toggle().classList).toContain('border-border');
|
|
175
|
+
expect(toggle().classList).toContain('h-9');
|
|
176
|
+
|
|
177
|
+
host.size.set('lg');
|
|
178
|
+
await waitForUpdate();
|
|
179
|
+
expect(toggle().classList).toContain('h-11');
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('scales down while pressed, and back on release or cancel', async () => {
|
|
183
|
+
const { toggle } = await renderToggle();
|
|
184
|
+
expectPressFeedback(toggle());
|
|
78
185
|
});
|
|
79
186
|
|
|
80
|
-
it('
|
|
81
|
-
const {
|
|
82
|
-
|
|
83
|
-
setInputSignal(inst.size, 'lg');
|
|
187
|
+
it("doesn't react to presses while it's disabled", async () => {
|
|
188
|
+
const { host, toggle } = await renderToggle();
|
|
189
|
+
host.disabled.set(true);
|
|
84
190
|
await waitForUpdate();
|
|
85
|
-
|
|
86
|
-
'h-11',
|
|
87
|
-
);
|
|
191
|
+
expectNoPressFeedback(toggle());
|
|
88
192
|
});
|
|
89
193
|
});
|
|
90
194
|
|
|
91
195
|
describe('UiToggleGroup', () => {
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
expect(
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it('toggle() is a no-op when the group is disabled', async () => {
|
|
117
|
-
const { componentRef } = await render(UiToggleGroup);
|
|
118
|
-
const inst = componentRef.instance as UiToggleGroup;
|
|
119
|
-
setInputSignal(inst.disabled, true);
|
|
120
|
-
await waitForUpdate();
|
|
121
|
-
inst.toggle('a');
|
|
122
|
-
expect(inst.value()).toEqual([]);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it('isSelected reflects the current value list', async () => {
|
|
126
|
-
const { componentRef } = await render(UiToggleGroup);
|
|
127
|
-
const inst = componentRef.instance as UiToggleGroup;
|
|
128
|
-
inst.toggle('a');
|
|
129
|
-
expect(inst.isSelected('a')).toBe(true);
|
|
130
|
-
expect(inst.isSelected('b')).toBe(false);
|
|
196
|
+
const renderGroup = async () => {
|
|
197
|
+
const result = await render(ToggleGroupHost);
|
|
198
|
+
return {
|
|
199
|
+
...result,
|
|
200
|
+
host: result.componentRef.instance as ToggleGroupHost,
|
|
201
|
+
group: () => result.container.querySelector('.gap-1')!,
|
|
202
|
+
item: (label: string) => viewOf(result.getByText(label)),
|
|
203
|
+
};
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Taps an item, then lets the selection reach its highlight.
|
|
208
|
+
*/
|
|
209
|
+
const tap = async (item: Element) => {
|
|
210
|
+
fireEvent.tap(item);
|
|
211
|
+
await waitForUpdate();
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
it('lays its items out in a row, with the given class', async () => {
|
|
215
|
+
const { group } = await renderGroup();
|
|
216
|
+
expect(group().classList).toContain('flex-row');
|
|
217
|
+
expect(group().classList).toContain('w-full');
|
|
131
218
|
});
|
|
132
|
-
});
|
|
133
219
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
220
|
+
it('selects one item at a time, and deselects it on a second tap', async () => {
|
|
221
|
+
const { host, item } = await renderGroup();
|
|
222
|
+
await tap(item('Left'));
|
|
223
|
+
expect(host.align()).toEqual(['left']);
|
|
224
|
+
expect(item('Left').classList).toContain('bg-accent');
|
|
225
|
+
|
|
226
|
+
await tap(item('Center'));
|
|
227
|
+
expect(host.align()).toEqual(['center']);
|
|
228
|
+
expect(item('Left').classList).toContain('bg-transparent');
|
|
229
|
+
expect(item('Center').classList).toContain('bg-accent');
|
|
230
|
+
|
|
231
|
+
// Unlike a radio group, a single toggle group can have nothing selected.
|
|
232
|
+
await tap(item('Center'));
|
|
233
|
+
expect(host.align()).toEqual([]);
|
|
234
|
+
expect(item('Center').classList).toContain('bg-transparent');
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('toggles each item on its own when it allows multiple', async () => {
|
|
238
|
+
const { host, item } = await renderGroup();
|
|
239
|
+
host.type.set('multiple');
|
|
143
240
|
await waitForUpdate();
|
|
144
241
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
242
|
+
await tap(item('Left'));
|
|
243
|
+
await tap(item('Right'));
|
|
244
|
+
expect(host.align()).toEqual(['left', 'right']);
|
|
245
|
+
|
|
246
|
+
await tap(item('Left'));
|
|
247
|
+
expect(host.align()).toEqual(['right']);
|
|
248
|
+
expect(item('Left').classList).toContain('bg-transparent');
|
|
249
|
+
expect(item('Right').classList).toContain('bg-accent');
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('highlights the items the app selects', async () => {
|
|
253
|
+
const { host, item } = await renderGroup();
|
|
254
|
+
host.align.set(['right']);
|
|
150
255
|
await waitForUpdate();
|
|
151
|
-
|
|
152
|
-
|
|
256
|
+
expect(item('Right').classList).toContain('bg-accent');
|
|
257
|
+
expect(item('Left').classList).toContain('bg-transparent');
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('dims every item and ignores taps while disabled', async () => {
|
|
261
|
+
const { host, item } = await renderGroup();
|
|
262
|
+
host.disabled.set(true);
|
|
263
|
+
await waitForUpdate();
|
|
264
|
+
for (const label of ['Left', 'Center', 'Right']) {
|
|
265
|
+
expect(item(label).classList).toContain('opacity-50');
|
|
266
|
+
await tap(item(label));
|
|
267
|
+
}
|
|
268
|
+
expect(host.align()).toEqual([]);
|
|
269
|
+
});
|
|
153
270
|
|
|
154
|
-
it(
|
|
155
|
-
const {
|
|
156
|
-
|
|
157
|
-
|
|
271
|
+
it("ignores toggle() from code while it's disabled", async () => {
|
|
272
|
+
const { host } = await renderGroup();
|
|
273
|
+
host.disabled.set(true);
|
|
274
|
+
await waitForUpdate();
|
|
275
|
+
host.group().toggle('left');
|
|
276
|
+
expect(host.align()).toEqual([]);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it("styles every item with the group's variant and size, and its own class", async () => {
|
|
280
|
+
const { host, item } = await renderGroup();
|
|
281
|
+
expect(item('Left').classList).toContain('h-10');
|
|
282
|
+
expect(item('Left').classList).toContain('flex-1');
|
|
283
|
+
expect(item('Left').classList).not.toContain('opacity-50');
|
|
284
|
+
|
|
285
|
+
host.variant.set('outline');
|
|
286
|
+
host.size.set('sm');
|
|
287
|
+
await waitForUpdate();
|
|
288
|
+
for (const label of ['Left', 'Center', 'Right']) {
|
|
289
|
+
expect(item(label).classList).toContain('border-border');
|
|
290
|
+
expect(item(label).classList).toContain('h-9');
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('scales an item down while pressed, and back on release or cancel', async () => {
|
|
295
|
+
const { item } = await renderGroup();
|
|
296
|
+
expectPressFeedback(item('Center'));
|
|
158
297
|
});
|
|
159
298
|
|
|
160
|
-
it('
|
|
161
|
-
const {
|
|
162
|
-
|
|
163
|
-
|
|
299
|
+
it("doesn't react to presses while the group is disabled", async () => {
|
|
300
|
+
const { host, item } = await renderGroup();
|
|
301
|
+
host.disabled.set(true);
|
|
302
|
+
await waitForUpdate();
|
|
303
|
+
expectNoPressFeedback(item('Center'));
|
|
164
304
|
});
|
|
305
|
+
});
|
|
165
306
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
307
|
+
@Component({
|
|
308
|
+
imports: [UiToggleGroup, UiToggleGroupItem, UiIcon, LYNX_ELEMENTS],
|
|
309
|
+
template: `
|
|
310
|
+
<ui-toggle-group
|
|
311
|
+
[variant]="variant()"
|
|
312
|
+
[type]="type()"
|
|
313
|
+
[value]="range()"
|
|
314
|
+
(valueChange)="range.set($event)"
|
|
315
|
+
[disabled]="disabled()"
|
|
316
|
+
>
|
|
317
|
+
@for (option of options(); track option) {
|
|
318
|
+
<ui-toggle-group-item
|
|
319
|
+
[value]="option"
|
|
320
|
+
[accessibility-label]="option"
|
|
321
|
+
[class]="option === 'Month' ? 'flex-[2]' : ''"
|
|
322
|
+
><text class="text-foreground">{{
|
|
323
|
+
option
|
|
324
|
+
}}</text></ui-toggle-group-item
|
|
325
|
+
>
|
|
326
|
+
}
|
|
327
|
+
<ui-toggle-group-item value="Star"
|
|
328
|
+
><ui-icon name="star"
|
|
329
|
+
/></ui-toggle-group-item>
|
|
330
|
+
</ui-toggle-group>
|
|
331
|
+
`,
|
|
332
|
+
})
|
|
333
|
+
class SegmentedGroupHost {
|
|
334
|
+
readonly variant = signal<ToggleGroupVariant>('segmented');
|
|
335
|
+
readonly type = signal<'single' | 'multiple'>('single');
|
|
336
|
+
readonly range = signal<string[]>(['Week']);
|
|
337
|
+
readonly options = signal(['Day', 'Week', 'Month']);
|
|
338
|
+
readonly disabled = signal(false);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* A group inside something that colors its icons, such as glass.
|
|
343
|
+
*/
|
|
344
|
+
@Component({
|
|
345
|
+
imports: [UiToggleGroup, UiToggleGroupItem, UiIcon, LYNX_ELEMENTS],
|
|
346
|
+
providers: [
|
|
347
|
+
{ provide: UI_ICON_COLOR, useValue: signal('var(--primary)').asReadonly() },
|
|
348
|
+
],
|
|
349
|
+
template: `
|
|
350
|
+
<ui-toggle-group [variant]="variant()">
|
|
351
|
+
<ui-toggle-group-item value="star"
|
|
352
|
+
><ui-icon name="star"
|
|
353
|
+
/></ui-toggle-group-item>
|
|
354
|
+
</ui-toggle-group>
|
|
355
|
+
`,
|
|
356
|
+
})
|
|
357
|
+
class TintedGroupHost {
|
|
358
|
+
readonly variant = signal<ToggleGroupVariant>('default');
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** Accessibility settings a test can change, as the host app would. */
|
|
362
|
+
const settings = {
|
|
363
|
+
reduceMotion: signal(false),
|
|
364
|
+
reduceTransparency: signal(false),
|
|
365
|
+
increaseContrast: signal(false),
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
const IOS_26 = { platform: 'iOS', osVersion: '26.0' };
|
|
369
|
+
const ANDROID = { platform: 'Android', osVersion: '15' };
|
|
370
|
+
|
|
371
|
+
describe('UiToggleGroup, segmented', () => {
|
|
372
|
+
// The indicator's and the icon's selectors aren't Lynx elements, so the
|
|
373
|
+
// renderer falls back to a <view> for them and warns.
|
|
374
|
+
beforeEach(() => {
|
|
375
|
+
vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
afterEach(() => {
|
|
379
|
+
vi.restoreAllMocks();
|
|
380
|
+
vi.unstubAllGlobals();
|
|
381
|
+
settings.reduceMotion.set(false);
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
const renderSegmented = async (
|
|
385
|
+
platform: { platform: string; osVersion: string } = IOS_26,
|
|
386
|
+
) => {
|
|
387
|
+
vi.stubGlobal('SystemInfo', platform);
|
|
388
|
+
const result = await render(SegmentedGroupHost, {
|
|
389
|
+
providers: [{ provide: LynxAccessibilitySettings, useValue: settings }],
|
|
390
|
+
});
|
|
391
|
+
/**
|
|
392
|
+
* An item's own view: around its label, or around the star icon.
|
|
393
|
+
*/
|
|
394
|
+
const item = (label: string) =>
|
|
395
|
+
label === 'Star'
|
|
396
|
+
? result.container.querySelector('svg')!.parentElement!.parentElement!
|
|
397
|
+
: viewOf(result.getByText(label));
|
|
398
|
+
/**
|
|
399
|
+
* An item's host, the track's flex item.
|
|
400
|
+
*/
|
|
401
|
+
const slot = (label: string) => item(label).parentElement!;
|
|
402
|
+
const track = () => slot('Day').parentElement!;
|
|
403
|
+
/**
|
|
404
|
+
* The indicator's frame, which moves between the items.
|
|
405
|
+
*/
|
|
406
|
+
const frame = () =>
|
|
407
|
+
track().querySelector<HTMLElement>('.h-full .bottom-0.top-0');
|
|
408
|
+
const measure = async () => {
|
|
409
|
+
for (const label of ['Day', 'Week', 'Month', 'Star']) {
|
|
410
|
+
fireEvent.layoutchange(slot(label), {
|
|
411
|
+
detail: { width: label === 'Month' ? 160 : 80 },
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
await waitForUpdate();
|
|
172
415
|
};
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
416
|
+
return {
|
|
417
|
+
...result,
|
|
418
|
+
host: result.componentRef.instance as SegmentedGroupHost,
|
|
419
|
+
item,
|
|
420
|
+
slot,
|
|
421
|
+
track,
|
|
422
|
+
frame,
|
|
423
|
+
measure,
|
|
424
|
+
tap: async (label: string) => {
|
|
425
|
+
fireEvent.tap(item(label));
|
|
426
|
+
await waitForUpdate();
|
|
427
|
+
},
|
|
181
428
|
};
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
it("draws the platform's segmented control, its items sharing the track", async () => {
|
|
432
|
+
const { track, slot, item, frame } = await renderSegmented();
|
|
433
|
+
expect(track().classList).toContain('rounded-full');
|
|
434
|
+
expect(track().classList).toContain('bg-muted');
|
|
435
|
+
expect(slot('Day').classList).toContain('flex-1');
|
|
436
|
+
// An item's own class sizes its slot too.
|
|
437
|
+
expect(slot('Month').classList).toContain('flex-[2]');
|
|
438
|
+
expect(slot('Month').classList).not.toContain('flex-1');
|
|
439
|
+
expect(item('Day').classList).toContain('justify-center');
|
|
440
|
+
expect(frame()).not.toBeNull();
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
it('keeps one item selected: tapping it again leaves it selected', async () => {
|
|
444
|
+
const { host, tap } = await renderSegmented();
|
|
445
|
+
await tap('Day');
|
|
446
|
+
expect(host.range()).toEqual(['Day']);
|
|
447
|
+
await tap('Day');
|
|
448
|
+
expect(host.range()).toEqual(['Day']);
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
it('marks the selected item selected for screen readers', async () => {
|
|
452
|
+
const { getByRole, tap } = await renderSegmented();
|
|
453
|
+
expect(getByRole('button', { name: 'Week', selected: true })).toBeTruthy();
|
|
454
|
+
expect(getByRole('button', { name: 'Day', selected: false })).toBeTruthy();
|
|
455
|
+
await tap('Day');
|
|
456
|
+
expect(getByRole('button', { name: 'Day', selected: true })).toBeTruthy();
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it('moves the indicator to the item it was dragged to', async () => {
|
|
460
|
+
const { host, item, frame, measure } = await renderSegmented();
|
|
461
|
+
await measure();
|
|
462
|
+
expect(frame()!.style.left).toBe('80px');
|
|
463
|
+
|
|
464
|
+
fireEvent.touchstart(item('Week'), { touches: [{ pageX: 120 }] });
|
|
465
|
+
fireEvent.touchmove(item('Week'), { touches: [{ pageX: 220 }] });
|
|
466
|
+
fireEvent.touchend(item('Week'));
|
|
467
|
+
// Lynx may send a tap as the finger lifts: the drag chose already.
|
|
468
|
+
fireEvent.tap(item('Week'));
|
|
469
|
+
await waitForUpdate();
|
|
470
|
+
expect(host.range()).toEqual(['Month']);
|
|
471
|
+
expect(frame()!.style.left).toBe('160px');
|
|
472
|
+
expect(frame()!.style.width).toBe('160px');
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it('lets go of a drag the touch is taken from, or the mouse leaves', async () => {
|
|
476
|
+
const { host, item, track, frame, measure } = await renderSegmented();
|
|
477
|
+
await measure();
|
|
478
|
+
|
|
479
|
+
fireEvent.touchstart(item('Week'), { touches: [{ pageX: 120 }] });
|
|
480
|
+
fireEvent.touchmove(item('Week'), { touches: [{ pageX: 220 }] });
|
|
481
|
+
fireEvent.touchcancel(item('Week'));
|
|
482
|
+
await waitForUpdate();
|
|
483
|
+
expect(frame()!.style.left).toBe('80px');
|
|
484
|
+
|
|
485
|
+
fireEvent.mousedown(item('Week'), { pageX: 120 });
|
|
486
|
+
fireEvent.mousemove(item('Week'), { pageX: 220 });
|
|
487
|
+
fireLynxEvent(item('Week'), 'mouseleave');
|
|
488
|
+
await waitForUpdate();
|
|
489
|
+
expect(frame()!.style.left).toBe('180px');
|
|
490
|
+
fireLynxEvent(track(), 'mouseleave');
|
|
491
|
+
await waitForUpdate();
|
|
492
|
+
expect(frame()!.style.left).toBe('80px');
|
|
493
|
+
|
|
494
|
+
fireEvent.mousedown(item('Week'), { pageX: 120 });
|
|
495
|
+
fireEvent.mousemove(item('Day'), { pageX: 20 });
|
|
496
|
+
fireEvent.mouseup(item('Day'));
|
|
497
|
+
await waitForUpdate();
|
|
498
|
+
expect(host.range()).toEqual(['Day']);
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
it("ignores touches while it's disabled", async () => {
|
|
502
|
+
const { host, item, frame, measure } = await renderSegmented();
|
|
503
|
+
await measure();
|
|
504
|
+
host.disabled.set(true);
|
|
505
|
+
await waitForUpdate();
|
|
506
|
+
fireEvent.touchstart(item('Week'), { touches: [{ pageX: 120 }] });
|
|
507
|
+
fireEvent.touchmove(item('Week'), { touches: [{ pageX: 220 }] });
|
|
508
|
+
fireEvent.touchend(item('Week'));
|
|
509
|
+
fireEvent.tap(item('Day'));
|
|
510
|
+
await waitForUpdate();
|
|
511
|
+
expect(host.range()).toEqual(['Week']);
|
|
512
|
+
expect(frame()!.style.left).toBe('80px');
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
it('gives each pressed item its own thumb in a multiple selection', async () => {
|
|
516
|
+
const { host, item, frame, tap } = await renderSegmented();
|
|
517
|
+
host.type.set('multiple');
|
|
518
|
+
await waitForUpdate();
|
|
519
|
+
expect(frame()).toBeNull();
|
|
520
|
+
expect(item('Week').classList).toContain('bg-background');
|
|
521
|
+
|
|
522
|
+
await tap('Day');
|
|
523
|
+
expect(host.range()).toEqual(['Week', 'Day']);
|
|
524
|
+
expect(item('Day').classList).toContain('bg-background');
|
|
525
|
+
await tap('Day');
|
|
526
|
+
expect(host.range()).toEqual(['Week']);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
it("is Material's connected button group on Android, its icons in the fill's content color", async () => {
|
|
530
|
+
const { host, item, getByText, container, tap } =
|
|
531
|
+
await renderSegmented(ANDROID);
|
|
532
|
+
expect(item('Week').classList).toContain('bg-primary');
|
|
533
|
+
expect(item('Week').style.transition).toContain('background-color');
|
|
534
|
+
expect(item('Day').classList).toContain('bg-secondary');
|
|
535
|
+
const star = () =>
|
|
536
|
+
container.querySelector('svg')!.getAttribute('content') ?? '';
|
|
537
|
+
expect(star()).toContain(ICON_LIGHT_COLORS['secondary-foreground']);
|
|
538
|
+
|
|
539
|
+
await tap('Star');
|
|
540
|
+
expect(host.range()).toEqual(['Star']);
|
|
541
|
+
expect(star()).toContain(ICON_LIGHT_COLORS['primary-foreground']);
|
|
542
|
+
|
|
543
|
+
// A press shows Material's state layer.
|
|
544
|
+
fireEvent.touchstart(item('Day'), { touches: [{ pageX: 20 }] });
|
|
545
|
+
await waitForUpdate();
|
|
546
|
+
expect(item('Day').querySelector('.opacity-10')).not.toBeNull();
|
|
547
|
+
fireEvent.touchend(item('Day'));
|
|
548
|
+
await waitForUpdate();
|
|
549
|
+
expect(item('Day').querySelector('.opacity-10')).toBeNull();
|
|
550
|
+
expect(getByText('Day')).toBeTruthy();
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
it("rounds the group's ends on Android, as items come and go", async () => {
|
|
554
|
+
const { host, item } = await renderSegmented(ANDROID);
|
|
555
|
+
expect(item('Day').classList).toContain('rounded-l-[20px]');
|
|
556
|
+
expect(item('Month').classList).toContain('rounded-[8px]');
|
|
557
|
+
expect(item('Star').classList).toContain('rounded-r-[20px]');
|
|
558
|
+
|
|
559
|
+
// The items before a new one are redrawn before it has a value, so each
|
|
560
|
+
// finds its place from the items rather than their values.
|
|
561
|
+
host.options.set(['Day', 'Week', 'Month', 'Year']);
|
|
562
|
+
await waitForUpdate();
|
|
563
|
+
expect(item('Year').classList).toContain('rounded-[8px]');
|
|
564
|
+
expect(item('Star').classList).toContain('rounded-r-[20px]');
|
|
565
|
+
|
|
566
|
+
host.options.set(['Month']);
|
|
567
|
+
await waitForUpdate();
|
|
568
|
+
expect(item('Month').classList).toContain('rounded-l-[20px]');
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
it('leaves an icon the color around the group in the other variants', async () => {
|
|
572
|
+
vi.stubGlobal('SystemInfo', IOS_26);
|
|
573
|
+
const tinted = await render(TintedGroupHost);
|
|
574
|
+
const star = () =>
|
|
575
|
+
tinted.container.querySelector('svg')!.getAttribute('content') ?? '';
|
|
576
|
+
expect(star()).toContain(ICON_LIGHT_COLORS['primary']);
|
|
577
|
+
|
|
578
|
+
// A segmented control on iOS leaves it alone too.
|
|
579
|
+
(tinted.componentRef.instance as TintedGroupHost).variant.set('segmented');
|
|
580
|
+
await waitForUpdate();
|
|
581
|
+
expect(star()).toContain(ICON_LIGHT_COLORS['primary']);
|
|
582
|
+
tinted.unmount();
|
|
583
|
+
|
|
584
|
+
// With nothing around it, an icon takes its own default.
|
|
585
|
+
const plain = await renderSegmented();
|
|
586
|
+
expect(
|
|
587
|
+
plain.container.querySelector('svg')!.getAttribute('content'),
|
|
588
|
+
).toContain(ICON_LIGHT_COLORS['foreground']);
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
it('goes back to the toggle look when the variant changes', async () => {
|
|
592
|
+
const { host, item, frame } = await renderSegmented();
|
|
593
|
+
host.variant.set('outline');
|
|
594
|
+
await waitForUpdate();
|
|
595
|
+
expect(frame()).toBeNull();
|
|
596
|
+
expect(item('Week').classList).toContain('border-border');
|
|
597
|
+
expect(item('Week').classList).toContain('bg-accent');
|
|
185
598
|
});
|
|
186
599
|
});
|