@blotch/dolan 22.2.2 → 22.2.4
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 +6 -2
- package/dist/commands/add.js +41 -11
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/add.test.js +162 -9
- package/dist/commands/add.test.js.map +1 -1
- package/dist/commands/diff.d.ts +2 -1
- package/dist/commands/diff.js +38 -25
- package/dist/commands/diff.js.map +1 -1
- package/dist/commands/diff.test.js +72 -11
- package/dist/commands/diff.test.js.map +1 -1
- package/dist/commands/doctor.js +228 -34
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/doctor.test.js +367 -10
- package/dist/commands/doctor.test.js.map +1 -1
- package/dist/commands/eject.js +7 -0
- package/dist/commands/eject.js.map +1 -1
- package/dist/commands/eject.test.js +39 -0
- package/dist/commands/eject.test.js.map +1 -1
- package/dist/commands/info.js +3 -2
- package/dist/commands/info.js.map +1 -1
- package/dist/commands/init.d.ts +37 -1
- package/dist/commands/init.js +169 -40
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/init.test.js +392 -27
- package/dist/commands/init.test.js.map +1 -1
- package/dist/commands/install-packages.d.ts +6 -0
- package/dist/commands/install-packages.js +20 -0
- package/dist/commands/install-packages.js.map +1 -0
- package/dist/commands/install-packages.test.d.ts +1 -0
- package/dist/commands/install-packages.test.js +55 -0
- package/dist/commands/install-packages.test.js.map +1 -0
- package/dist/commands/list.js +21 -14
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/list.test.js +62 -5
- package/dist/commands/list.test.js.map +1 -1
- package/dist/commands/outdated.js +11 -10
- package/dist/commands/outdated.js.map +1 -1
- package/dist/commands/outdated.test.js +20 -5
- package/dist/commands/outdated.test.js.map +1 -1
- package/dist/commands/remove.d.ts +4 -0
- package/dist/commands/remove.js +17 -4
- package/dist/commands/remove.js.map +1 -1
- package/dist/commands/remove.test.js +53 -2
- package/dist/commands/remove.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 +11 -0
- package/dist/commands/update.js +174 -58
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/update.test.js +587 -33
- package/dist/commands/update.test.js.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/lockfile.d.ts +7 -0
- package/dist/lockfile.js +36 -0
- package/dist/lockfile.js.map +1 -1
- package/dist/lockfile.test.d.ts +1 -0
- package/dist/lockfile.test.js +25 -0
- package/dist/lockfile.test.js.map +1 -0
- package/dist/registry.d.ts +14 -0
- package/dist/registry.js +78 -24
- package/dist/registry.js.map +1 -1
- package/dist/registry.test.js +119 -38
- package/dist/registry.test.js.map +1 -1
- package/dist/test-utils.d.ts +5 -0
- package/dist/test-utils.js +6 -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 +14 -0
- package/dist/testing/set-input-signal.js +21 -0
- package/dist/testing/set-input-signal.js.map +1 -0
- package/dist/testing/set-input-signal.test.d.ts +1 -0
- package/dist/testing/set-input-signal.test.js +20 -0
- package/dist/testing/set-input-signal.test.js.map +1 -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 +283 -151
- package/dist/ui/components/action-sheet/action-sheet.ts +77 -43
- 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 +208 -94
- package/dist/ui/components/alert-dialog/alert-dialog.ts +75 -38
- package/dist/ui/components/area-chart/area-chart.test.ts +134 -119
- package/dist/ui/components/area-chart/area-chart.ts +100 -315
- package/dist/ui/components/area-chart/index.ts +2 -3
- 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 +12 -14
- package/dist/ui/components/bar-chart/bar-chart.test.ts +172 -45
- package/dist/ui/components/bar-chart/bar-chart.ts +140 -294
- package/dist/ui/components/bar-chart/index.ts +3 -3
- package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +386 -128
- package/dist/ui/components/bottom-sheet/bottom-sheet.ts +170 -53
- package/dist/ui/components/button/button.test.ts +611 -2
- package/dist/ui/components/button/button.ts +434 -49
- package/dist/ui/components/button/index.ts +2 -0
- package/dist/ui/components/button-group/button-group.test.ts +484 -2
- package/dist/ui/components/button-group/button-group.ts +380 -68
- package/dist/ui/components/button-group/index.ts +3 -0
- package/dist/ui/components/candlestick-chart/candlestick-chart.test.ts +183 -58
- package/dist/ui/components/candlestick-chart/candlestick-chart.ts +150 -323
- package/dist/ui/components/candlestick-chart/index.ts +3 -4
- package/dist/ui/components/card/card.test.ts +82 -62
- package/dist/ui/components/chart/chart-focus.test.ts +132 -0
- package/dist/ui/components/chart/chart-focus.ts +115 -0
- package/dist/ui/components/chart/chart-presets.test.ts +335 -0
- package/dist/ui/components/chart/chart-presets.ts +278 -0
- package/dist/ui/components/chart/chart-scene.test.ts +589 -0
- package/dist/ui/components/chart/chart-scene.ts +483 -0
- package/dist/ui/components/chart/chart-theme.test.ts +85 -0
- package/dist/ui/components/chart/chart-theme.ts +210 -0
- package/dist/ui/components/chart/chart-zoom.test.ts +159 -0
- package/dist/ui/components/chart/chart-zoom.ts +162 -0
- package/dist/ui/components/chart/chart.test.ts +1017 -0
- package/dist/ui/components/chart/chart.ts +934 -0
- package/dist/ui/components/chart/index.ts +24 -0
- package/dist/ui/components/checkbox/checkbox.test.ts +124 -42
- package/dist/ui/components/checkbox/checkbox.ts +23 -23
- package/dist/ui/components/collapsible/collapsible.test.ts +158 -81
- package/dist/ui/components/collapsible/collapsible.ts +19 -14
- 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 +213 -108
- package/dist/ui/components/dialog/dialog.ts +72 -35
- package/dist/ui/components/dock/dock-scroll.test.ts +130 -0
- package/dist/ui/components/dock/dock-scroll.ts +133 -0
- package/dist/ui/components/dock/dock.test.ts +749 -0
- package/dist/ui/components/dock/dock.ts +960 -0
- package/dist/ui/components/dock/index.ts +2 -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 +338 -0
- package/dist/ui/components/glass/glass.ts +360 -0
- package/dist/ui/components/glass/index.ts +18 -0
- package/dist/ui/components/icon/icon-theme.test.ts +169 -0
- package/dist/ui/components/icon/icon-theme.ts +227 -0
- package/dist/ui/components/icon/icon.test.ts +85 -25
- package/dist/ui/components/icon/icon.ts +38 -21
- package/dist/ui/components/input/input.test.ts +178 -101
- package/dist/ui/components/input/input.ts +15 -12
- package/dist/ui/components/label/label.test.ts +11 -24
- package/dist/ui/components/line-chart/index.ts +2 -3
- package/dist/ui/components/line-chart/line-chart.test.ts +149 -56
- package/dist/ui/components/line-chart/line-chart.ts +83 -241
- package/dist/ui/components/list/list.test.ts +119 -21
- package/dist/ui/components/list/list.ts +21 -15
- 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 +632 -0
- package/dist/ui/components/menu/menu-popup.ts +544 -0
- package/dist/ui/components/menu/menu.test.ts +429 -0
- package/dist/ui/components/menu/menu.ts +415 -0
- package/dist/ui/components/nav-drawer/nav-drawer.test.ts +435 -191
- package/dist/ui/components/nav-drawer/nav-drawer.ts +127 -56
- package/dist/ui/components/pagination/pagination.test.ts +177 -65
- package/dist/ui/components/pagination/pagination.ts +55 -8
- package/dist/ui/components/progress/progress.test.ts +102 -46
- package/dist/ui/components/progress/progress.ts +52 -54
- package/dist/ui/components/radio-group/radio-group.test.ts +128 -83
- package/dist/ui/components/radio-group/radio-group.ts +24 -24
- package/dist/ui/components/scatter-chart/index.ts +2 -3
- package/dist/ui/components/scatter-chart/scatter-chart.test.ts +131 -26
- package/dist/ui/components/scatter-chart/scatter-chart.ts +104 -250
- package/dist/ui/components/scroll-area/scroll-area.test.ts +95 -24
- package/dist/ui/components/scroll-area/scroll-area.ts +8 -8
- package/dist/ui/components/select/select.test.ts +138 -78
- package/dist/ui/components/select/select.ts +17 -11
- package/dist/ui/components/separator/separator.test.ts +18 -17
- package/dist/ui/components/sheet/sheet.test.ts +64 -11
- package/dist/ui/components/sheet/sheet.ts +9 -1
- package/dist/ui/components/skeleton/skeleton.test.ts +24 -7
- package/dist/ui/components/skeleton/skeleton.ts +10 -16
- package/dist/ui/components/spinner/spinner.test.ts +67 -22
- package/dist/ui/components/spinner/spinner.ts +48 -41
- package/dist/ui/components/switch/switch.test.ts +116 -46
- package/dist/ui/components/switch/switch.ts +8 -13
- 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/tabs.test.ts +215 -157
- package/dist/ui/components/tabs/tabs.ts +110 -118
- package/dist/ui/components/textarea/textarea.test.ts +206 -100
- package/dist/ui/components/textarea/textarea.ts +14 -12
- package/dist/ui/components/toast/toast.test.ts +436 -315
- package/dist/ui/components/toast/toast.ts +62 -55
- package/dist/ui/components/toggle/toggle.test.ts +247 -140
- package/dist/ui/theme/chart-colors.test.ts +35 -0
- package/dist/ui/theme/dark.css +46 -2
- package/dist/ui/theme/default.css +63 -7
- package/dist/ui/theme/icon-colors.test.ts +35 -0
- 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 +98 -25
- package/dist/ui/theme/tailwind.css +113 -0
- package/dist/ui/utils/animate.test.ts +13 -1
- package/dist/utils/analyze.d.ts +14 -0
- package/dist/utils/analyze.js +15 -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 +29 -0
- package/dist/utils/installed.js +43 -0
- package/dist/utils/installed.js.map +1 -0
- package/dist/utils/installed.test.d.ts +1 -0
- package/dist/utils/installed.test.js +57 -0
- package/dist/utils/installed.test.js.map +1 -0
- package/dist/utils/interactive.d.ts +9 -0
- package/dist/utils/interactive.js +10 -0
- package/dist/utils/interactive.js.map +1 -0
- package/dist/utils/interactive.test.d.ts +1 -0
- package/dist/utils/interactive.test.js +24 -0
- package/dist/utils/interactive.test.js.map +1 -0
- package/dist/utils/packages.d.ts +36 -0
- package/dist/utils/packages.js +70 -0
- package/dist/utils/packages.js.map +1 -0
- package/dist/utils/packages.test.d.ts +1 -0
- package/dist/utils/packages.test.js +89 -0
- package/dist/utils/packages.test.js.map +1 -0
- package/dist/utils/resolve-paths.d.ts +17 -1
- package/dist/utils/resolve-paths.js +21 -3
- package/dist/utils/resolve-paths.js.map +1 -1
- package/dist/utils/resolve-paths.test.js +38 -2
- package/dist/utils/resolve-paths.test.js.map +1 -1
- package/dist/utils/tailwind.d.ts +25 -0
- package/dist/utils/tailwind.js +51 -0
- package/dist/utils/tailwind.js.map +1 -0
- package/dist/utils/tailwind.test.d.ts +1 -0
- package/dist/utils/tailwind.test.js +71 -0
- package/dist/utils/tailwind.test.js.map +1 -0
- package/package.json +13 -1
- package/dist/ui/components/area-chart/area-chart-render.test.ts +0 -121
- package/dist/ui/components/bar-chart/bar-chart-render.test.ts +0 -174
- package/dist/ui/components/button/button-render.test.ts +0 -113
- 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 -174
- package/dist/ui/components/cartesian-chart/cartesian-chart-render.test.ts +0 -188
- package/dist/ui/components/cartesian-chart/cartesian-chart.test.ts +0 -525
- package/dist/ui/components/cartesian-chart/cartesian-chart.ts +0 -1140
- package/dist/ui/components/cartesian-chart/index.ts +0 -18
- package/dist/ui/components/line-chart/line-chart-render.test.ts +0 -121
- package/dist/ui/components/scatter-chart/scatter-chart-render.test.ts +0 -157
|
@@ -1,5 +1,481 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Component, type Provider, signal } from '@angular/core';
|
|
2
|
+
import { LynxAccessibilitySettings } from '@blotch/angular-lynx';
|
|
3
|
+
import {
|
|
4
|
+
fireEvent,
|
|
5
|
+
render,
|
|
6
|
+
waitForUpdate,
|
|
7
|
+
} from '@blotch/angular-lynx-testing-library';
|
|
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 } from '../icon/icon-theme';
|
|
12
|
+
import {
|
|
13
|
+
type ButtonGroupOrientation,
|
|
14
|
+
type ButtonGroupSize,
|
|
15
|
+
type ButtonGroupVariant,
|
|
16
|
+
UiButtonGroup,
|
|
17
|
+
UiButtonGroupItem,
|
|
18
|
+
buttonGroupItemVariants,
|
|
19
|
+
} from './button-group';
|
|
20
|
+
|
|
21
|
+
@Component({
|
|
22
|
+
imports: [UiButtonGroup, UiButtonGroupItem, UiIcon],
|
|
23
|
+
template: `
|
|
24
|
+
<ui-button-group
|
|
25
|
+
[orientation]="orientation()"
|
|
26
|
+
[variant]="variant()"
|
|
27
|
+
[size]="size()"
|
|
28
|
+
[disabled]="groupDisabled()"
|
|
29
|
+
class="w-full"
|
|
30
|
+
>
|
|
31
|
+
<ui-button-group-item
|
|
32
|
+
class="flex-1"
|
|
33
|
+
[accessibility-label]="copyLabel()"
|
|
34
|
+
(pressed)="presses.push('Copy')"
|
|
35
|
+
>
|
|
36
|
+
@if (withIcon()) {
|
|
37
|
+
<ui-icon name="copy" />
|
|
38
|
+
}
|
|
39
|
+
Copy
|
|
40
|
+
</ui-button-group-item>
|
|
41
|
+
<ui-button-group-item
|
|
42
|
+
[disabled]="pasteDisabled()"
|
|
43
|
+
(pressed)="presses.push('Paste')"
|
|
44
|
+
>Paste</ui-button-group-item
|
|
45
|
+
>
|
|
46
|
+
</ui-button-group>
|
|
47
|
+
`,
|
|
48
|
+
})
|
|
49
|
+
class ButtonGroupHost {
|
|
50
|
+
readonly orientation = signal<ButtonGroupOrientation>('horizontal');
|
|
51
|
+
readonly variant = signal<ButtonGroupVariant>('default');
|
|
52
|
+
readonly size = signal<ButtonGroupSize>('default');
|
|
53
|
+
readonly groupDisabled = signal(false);
|
|
54
|
+
readonly pasteDisabled = signal(false);
|
|
55
|
+
readonly withIcon = signal(false);
|
|
56
|
+
readonly copyLabel = signal<string | undefined>(undefined);
|
|
57
|
+
readonly presses: string[] = [];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Accessibility settings a test can change, as the host app would. */
|
|
61
|
+
const settings = {
|
|
62
|
+
reduceMotion: signal(false),
|
|
63
|
+
reduceTransparency: signal(false),
|
|
64
|
+
increaseContrast: signal(false),
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Renders the host on a platform, as `SystemInfo` reports it, or with no
|
|
69
|
+
* SystemInfo at all, which gives the glass variants Material's design.
|
|
70
|
+
*/
|
|
71
|
+
const renderGroup = async (
|
|
72
|
+
platform?: { platform: string; osVersion: string },
|
|
73
|
+
providers: Provider[] = [],
|
|
74
|
+
) => {
|
|
75
|
+
vi.stubGlobal('SystemInfo', platform);
|
|
76
|
+
const result = await render(ButtonGroupHost, {
|
|
77
|
+
providers: [
|
|
78
|
+
{ provide: LynxAccessibilitySettings, useValue: settings },
|
|
79
|
+
...providers,
|
|
80
|
+
],
|
|
81
|
+
});
|
|
82
|
+
return {
|
|
83
|
+
...result,
|
|
84
|
+
host: result.componentRef.instance as ButtonGroupHost,
|
|
85
|
+
/**
|
|
86
|
+
* The group's own box, around the items' hosts: the bar of the solid
|
|
87
|
+
* variants, Material's connected group, or the glass's frame.
|
|
88
|
+
*/
|
|
89
|
+
group: (): HTMLElement => {
|
|
90
|
+
const parent =
|
|
91
|
+
result.getByText('Paste').parentElement!.parentElement!.parentElement!;
|
|
92
|
+
return parent.localName === 'blur-view' ? parent.parentElement! : parent;
|
|
93
|
+
},
|
|
94
|
+
glass: () => result.container.querySelector<HTMLElement>('blur-view')!,
|
|
95
|
+
// An item's view (the one that takes presses) holds its label.
|
|
96
|
+
item: (label: string) => result.getByText(label).parentElement!,
|
|
97
|
+
icon: () => result.container.querySelector('svg')!,
|
|
98
|
+
update: async (change: () => void) => {
|
|
99
|
+
change();
|
|
100
|
+
await waitForUpdate();
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const IOS_26 = { platform: 'iOS', osVersion: '26.0' };
|
|
106
|
+
const ANDROID = { platform: 'Android', osVersion: '15' };
|
|
107
|
+
const WEB = { platform: 'web', osVersion: '' };
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The scale an item's inline style holds, if any.
|
|
111
|
+
*/
|
|
112
|
+
const scaleOf = (view: Element) =>
|
|
113
|
+
(view as HTMLElement).style.getPropertyValue('transform');
|
|
114
|
+
|
|
115
|
+
// The icon's selector isn't a Lynx element, so the renderer falls back to a
|
|
116
|
+
// <view> for it and warns.
|
|
117
|
+
beforeEach(() => {
|
|
118
|
+
vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
afterEach(() => {
|
|
122
|
+
vi.restoreAllMocks();
|
|
123
|
+
vi.unstubAllGlobals();
|
|
124
|
+
settings.reduceMotion.set(false);
|
|
125
|
+
settings.reduceTransparency.set(false);
|
|
126
|
+
settings.increaseContrast.set(false);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe('UiButtonGroup', () => {
|
|
130
|
+
it('lays its items out in a row, or in a column when vertical', async () => {
|
|
131
|
+
const { host, group } = await renderGroup();
|
|
132
|
+
expect(group().classList).toContain('flex-row');
|
|
133
|
+
expect(group().classList).toContain('w-full');
|
|
134
|
+
|
|
135
|
+
host.orientation.set('vertical');
|
|
136
|
+
await waitForUpdate();
|
|
137
|
+
expect(group().classList).toContain('flex-col');
|
|
138
|
+
expect(group().classList).not.toContain('flex-row');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("styles every item and label with the group's variant and size", async () => {
|
|
142
|
+
const { host, item, getByText } = await renderGroup();
|
|
143
|
+
expect(item('Copy').classList).toContain('bg-secondary');
|
|
144
|
+
expect(item('Copy').classList).toContain('h-10');
|
|
145
|
+
expect(item('Copy').classList).toContain('flex-1');
|
|
146
|
+
expect(getByText('Copy').classList).toContain('text-secondary-foreground');
|
|
147
|
+
expect(getByText('Copy').classList).toContain('text-sm');
|
|
148
|
+
|
|
149
|
+
host.variant.set('outline');
|
|
150
|
+
host.size.set('sm');
|
|
151
|
+
await waitForUpdate();
|
|
152
|
+
for (const label of ['Copy', 'Paste']) {
|
|
153
|
+
expect(item(label).classList).toContain('bg-background');
|
|
154
|
+
expect(item(label).classList).toContain('h-9');
|
|
155
|
+
expect(getByText(label).classList).toContain('text-foreground');
|
|
156
|
+
expect(getByText(label).classList).toContain('text-xs');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
host.size.set('lg');
|
|
160
|
+
await waitForUpdate();
|
|
161
|
+
expect(item('Paste').classList).toContain('h-11');
|
|
162
|
+
expect(getByText('Paste').classList).toContain('text-base');
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe('UiButtonGroupItem', () => {
|
|
167
|
+
it('emits `pressed` when tapped', async () => {
|
|
168
|
+
const { host, item } = await renderGroup();
|
|
169
|
+
fireEvent.tap(item('Copy'));
|
|
170
|
+
fireEvent.tap(item('Paste'));
|
|
171
|
+
expect(host.presses).toEqual(['Copy', 'Paste']);
|
|
172
|
+
expect(item('Copy').classList).not.toContain('opacity-50');
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('dims and ignores taps when disabled itself, leaving the others working', async () => {
|
|
176
|
+
const { host, item } = await renderGroup();
|
|
177
|
+
host.pasteDisabled.set(true);
|
|
178
|
+
await waitForUpdate();
|
|
179
|
+
expect(item('Paste').classList).toContain('opacity-50');
|
|
180
|
+
expect(item('Copy').classList).not.toContain('opacity-50');
|
|
181
|
+
|
|
182
|
+
fireEvent.tap(item('Paste'));
|
|
183
|
+
fireEvent.tap(item('Copy'));
|
|
184
|
+
expect(host.presses).toEqual(['Copy']);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('dims and ignores taps on every item when the group is disabled', async () => {
|
|
188
|
+
const { host, item } = await renderGroup();
|
|
189
|
+
host.groupDisabled.set(true);
|
|
190
|
+
await waitForUpdate();
|
|
191
|
+
for (const label of ['Copy', 'Paste']) {
|
|
192
|
+
expect(item(label).classList).toContain('opacity-50');
|
|
193
|
+
fireEvent.tap(item(label));
|
|
194
|
+
}
|
|
195
|
+
expect(host.presses).toEqual([]);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('scales down while pressed by touch, and back on release', async () => {
|
|
199
|
+
const { item } = await renderGroup();
|
|
200
|
+
fireEvent.touchstart(item('Copy'));
|
|
201
|
+
expect(scaleOf(item('Copy'))).toBe('scale(0.97)');
|
|
202
|
+
fireEvent.touchend(item('Copy'));
|
|
203
|
+
expect(scaleOf(item('Copy'))).toBe('scale(1)');
|
|
204
|
+
|
|
205
|
+
// A scroll that takes the touch over cancels it: the item scales back
|
|
206
|
+
// rather than staying pressed.
|
|
207
|
+
fireEvent.touchstart(item('Copy'));
|
|
208
|
+
fireEvent.touchcancel(item('Copy'));
|
|
209
|
+
expect(scaleOf(item('Copy'))).toBe('scale(1)');
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('scales down while pressed by mouse, and back on release or leave', async () => {
|
|
213
|
+
// Lynx on the web fires mouse events for a cursor, never touch ones.
|
|
214
|
+
const { item } = await renderGroup();
|
|
215
|
+
fireEvent.mousedown(item('Copy'));
|
|
216
|
+
expect(scaleOf(item('Copy'))).toBe('scale(0.97)');
|
|
217
|
+
fireEvent.mouseup(item('Copy'));
|
|
218
|
+
expect(scaleOf(item('Copy'))).toBe('scale(1)');
|
|
219
|
+
|
|
220
|
+
fireEvent.mousedown(item('Copy'));
|
|
221
|
+
fireLynxEvent(item('Copy'), 'mouseleave');
|
|
222
|
+
expect(scaleOf(item('Copy'))).toBe('scale(1)');
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("doesn't react to presses while it or its group is disabled", async () => {
|
|
226
|
+
const { host, item } = await renderGroup();
|
|
227
|
+
host.pasteDisabled.set(true);
|
|
228
|
+
host.groupDisabled.set(true);
|
|
229
|
+
await waitForUpdate();
|
|
230
|
+
for (const label of ['Copy', 'Paste']) {
|
|
231
|
+
fireEvent.touchstart(item(label));
|
|
232
|
+
fireEvent.mousedown(item(label));
|
|
233
|
+
expect(scaleOf(item(label))).toBe('');
|
|
234
|
+
fireEvent.touchend(item(label));
|
|
235
|
+
fireEvent.mouseup(item(label));
|
|
236
|
+
expect(scaleOf(item(label))).toBe('');
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
describe('UiButtonGroupItem icons and accessibility', () => {
|
|
242
|
+
it('puts a projected icon before the label, in the label color', async () => {
|
|
243
|
+
const { host, item, getByText, icon, update } = await renderGroup();
|
|
244
|
+
await update(() => host.withIcon.set(true));
|
|
245
|
+
|
|
246
|
+
expect(item('Copy').classList).toContain('gap-2');
|
|
247
|
+
expect(icon().compareDocumentPosition(getByText('Copy'))).toBe(
|
|
248
|
+
Node.DOCUMENT_POSITION_FOLLOWING,
|
|
249
|
+
);
|
|
250
|
+
expect(icon().getAttribute('content')).toContain(
|
|
251
|
+
ICON_LIGHT_COLORS['secondary-foreground'],
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
await update(() => host.variant.set('outline'));
|
|
255
|
+
expect(icon().getAttribute('content')).toContain(
|
|
256
|
+
ICON_LIGHT_COLORS['foreground'],
|
|
257
|
+
);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('shows only the icon in square items, named by its accessibility label', async () => {
|
|
261
|
+
const { host, queryByText, getByRole, update } = await renderGroup();
|
|
262
|
+
await update(() => {
|
|
263
|
+
host.size.set('icon');
|
|
264
|
+
host.withIcon.set(true);
|
|
265
|
+
host.copyLabel.set('Copy the text');
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
expect(queryByText('Copy')).toBeNull();
|
|
269
|
+
const copy = getByRole('button', { name: 'Copy the text' });
|
|
270
|
+
expect(copy.classList).toContain('w-10');
|
|
271
|
+
expect(copy.classList).not.toContain('gap-2');
|
|
272
|
+
// An item with a label is read by it, as a button.
|
|
273
|
+
expect(getByRole('button', { name: 'Paste' }).localName).toBe('text');
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('dims at once instead of shrinking under Reduce Motion', async () => {
|
|
277
|
+
const { item, update } = await renderGroup();
|
|
278
|
+
await update(() => settings.reduceMotion.set(true));
|
|
279
|
+
|
|
280
|
+
fireEvent.touchstart(item('Copy'));
|
|
281
|
+
await waitForUpdate();
|
|
282
|
+
expect(item('Copy').classList).toContain('opacity-70');
|
|
283
|
+
expect(scaleOf(item('Copy'))).toBe('');
|
|
284
|
+
|
|
285
|
+
fireEvent.touchend(item('Copy'));
|
|
286
|
+
await waitForUpdate();
|
|
287
|
+
expect(item('Copy').classList).not.toContain('opacity-70');
|
|
288
|
+
expect(scaleOf(item('Copy'))).toBe('');
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
describe('UiButtonGroup glass variants', () => {
|
|
293
|
+
it('is one interactive glass shape on iOS 26, with plain items inside', async () => {
|
|
294
|
+
const { host, group, glass, item, getByText, update } =
|
|
295
|
+
await renderGroup(IOS_26);
|
|
296
|
+
await update(() => host.variant.set('glass'));
|
|
297
|
+
|
|
298
|
+
expect(glass().parentElement).toBe(group());
|
|
299
|
+
expect(glass().getAttribute('blur-effect')).toBe('glass');
|
|
300
|
+
expect(glass().getAttribute('glass-style')).toBe('regular');
|
|
301
|
+
expect(glass().getAttribute('glass-interactive')).toBe('true');
|
|
302
|
+
expect(glass().classList).toContain('flex-row');
|
|
303
|
+
expect(glass().classList).toContain('p-1');
|
|
304
|
+
// A row is a capsule.
|
|
305
|
+
expect(glass().classList).toContain('rounded-full');
|
|
306
|
+
expect(glass().style.getPropertyValue('border-radius')).toBe('');
|
|
307
|
+
expect(group().classList).toContain('rounded-full');
|
|
308
|
+
expect(group().classList).toContain('self-start');
|
|
309
|
+
expect(group().classList).toContain('w-full');
|
|
310
|
+
expect(group().classList).not.toContain('bg-glass-dim');
|
|
311
|
+
for (const label of ['Copy', 'Paste']) {
|
|
312
|
+
expect(glass().contains(item(label))).toBe(true);
|
|
313
|
+
expect(item(label).classList).toContain('h-10');
|
|
314
|
+
expect(item(label).classList).not.toContain('bg-secondary');
|
|
315
|
+
expect(getByText(label).classList).toContain('text-glass-foreground');
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// A column of wider items has corners of half an item's height plus
|
|
319
|
+
// the 4px inset.
|
|
320
|
+
await update(() => host.orientation.set('vertical'));
|
|
321
|
+
expect(glass().classList).toContain('flex-col');
|
|
322
|
+
expect(glass().classList).not.toContain('rounded-full');
|
|
323
|
+
expect(glass().style.getPropertyValue('border-radius')).toBe('24px');
|
|
324
|
+
expect(group().style.getPropertyValue('border-radius')).toBe('24px');
|
|
325
|
+
|
|
326
|
+
// A column of square icons is a capsule again.
|
|
327
|
+
await update(() => host.size.set('icon'));
|
|
328
|
+
expect(glass().classList).toContain('rounded-full');
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('highlights a pressed item in corners concentric with the glass', async () => {
|
|
332
|
+
const { host, item, getByText, update } = await renderGroup(IOS_26);
|
|
333
|
+
await update(() => host.variant.set('glass'));
|
|
334
|
+
const highlight = () => item('Copy').querySelector('.absolute');
|
|
335
|
+
|
|
336
|
+
fireEvent.touchstart(item('Copy'));
|
|
337
|
+
await waitForUpdate();
|
|
338
|
+
expect(highlight()!.classList).toContain('bg-glass-foreground');
|
|
339
|
+
// 24px glass corners, 4px out: 20px.
|
|
340
|
+
expect((highlight() as HTMLElement).style.borderRadius).toBe('20px');
|
|
341
|
+
expect(highlight()!.compareDocumentPosition(getByText('Copy'))).toBe(
|
|
342
|
+
Node.DOCUMENT_POSITION_FOLLOWING,
|
|
343
|
+
);
|
|
344
|
+
expect(scaleOf(item('Copy'))).toBe('');
|
|
345
|
+
|
|
346
|
+
fireEvent.touchend(item('Copy'));
|
|
347
|
+
await waitForUpdate();
|
|
348
|
+
expect(highlight()).toBeNull();
|
|
349
|
+
|
|
350
|
+
// Smaller items, smaller corners: 22px and 18px.
|
|
351
|
+
await update(() => host.size.set('sm'));
|
|
352
|
+
fireEvent.mousedown(item('Copy'));
|
|
353
|
+
await waitForUpdate();
|
|
354
|
+
expect((highlight() as HTMLElement).style.borderRadius).toBe('18px');
|
|
355
|
+
fireLynxEvent(item('Copy'), 'mouseleave');
|
|
356
|
+
await waitForUpdate();
|
|
357
|
+
expect(highlight()).toBeNull();
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it('sits clear glass on the dimming layer, with white icons', async () => {
|
|
361
|
+
const { host, group, glass, icon, update } = await renderGroup(IOS_26);
|
|
362
|
+
await update(() => {
|
|
363
|
+
host.variant.set('glass-clear');
|
|
364
|
+
host.withIcon.set(true);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
expect(glass().getAttribute('glass-style')).toBe('clear');
|
|
368
|
+
expect(group().classList).toContain('bg-glass-dim');
|
|
369
|
+
expect(group().classList).toContain('rounded-full');
|
|
370
|
+
expect(icon().getAttribute('content')).toContain(
|
|
371
|
+
ICON_LIGHT_COLORS['glass-clear-foreground'],
|
|
372
|
+
);
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it("stops the glass reacting and dims the items while it's disabled", async () => {
|
|
376
|
+
const { host, glass, item, update } = await renderGroup(IOS_26);
|
|
377
|
+
await update(() => {
|
|
378
|
+
host.variant.set('glass');
|
|
379
|
+
host.groupDisabled.set(true);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
expect(glass().hasAttribute('glass-interactive')).toBe(false);
|
|
383
|
+
expect(item('Copy').classList).toContain('opacity-50');
|
|
384
|
+
fireEvent.touchstart(item('Copy'));
|
|
385
|
+
fireEvent.tap(item('Copy'));
|
|
386
|
+
expect(item('Copy').querySelector('.absolute')).toBeNull();
|
|
387
|
+
expect(host.presses).toEqual([]);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it('stops the glass swelling under Reduce Motion in the web preview, still highlighting', async () => {
|
|
391
|
+
const { host, glass, item, update } = await renderGroup(WEB);
|
|
392
|
+
await update(() => {
|
|
393
|
+
host.variant.set('glass');
|
|
394
|
+
settings.reduceMotion.set(true);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
expect(glass().hasAttribute('glass-interactive')).toBe(false);
|
|
398
|
+
fireEvent.mousedown(item('Copy'));
|
|
399
|
+
await waitForUpdate();
|
|
400
|
+
expect(item('Copy').querySelector('.absolute')).not.toBeNull();
|
|
401
|
+
expect(scaleOf(item('Copy'))).toBe('');
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it('drops the dimming layer when Reduce Transparency makes the glass solid', async () => {
|
|
405
|
+
const { host, group, icon, update } = await renderGroup(WEB);
|
|
406
|
+
await update(() => {
|
|
407
|
+
host.variant.set('glass-clear');
|
|
408
|
+
host.withIcon.set(true);
|
|
409
|
+
settings.reduceTransparency.set(true);
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
expect(group().classList).not.toContain('bg-glass-dim');
|
|
413
|
+
expect(icon().getAttribute('content')).toContain(
|
|
414
|
+
ICON_LIGHT_COLORS['glass-foreground'],
|
|
415
|
+
);
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
describe('UiButtonGroup glass variants in Material (Android)', () => {
|
|
420
|
+
it("is Material's connected button group", async () => {
|
|
421
|
+
const { host, group, glass, item, getByText, icon, update } =
|
|
422
|
+
await renderGroup(ANDROID);
|
|
423
|
+
await update(() => {
|
|
424
|
+
host.variant.set('glass');
|
|
425
|
+
host.withIcon.set(true);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
expect(glass()).toBeNull();
|
|
429
|
+
for (const name of ['gap-0.5', 'rounded-full', 'overflow-hidden']) {
|
|
430
|
+
expect(group().classList).toContain(name);
|
|
431
|
+
}
|
|
432
|
+
expect(group().classList).not.toContain('bg-border');
|
|
433
|
+
expect(item('Copy').classList).toContain('bg-secondary');
|
|
434
|
+
expect(item('Copy').classList).toContain('rounded-[8px]');
|
|
435
|
+
expect(getByText('Copy').classList).toContain('text-secondary-foreground');
|
|
436
|
+
expect(icon().getAttribute('content')).toContain(
|
|
437
|
+
ICON_LIGHT_COLORS['secondary-foreground'],
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
// Clear glass is the same tonal group here.
|
|
441
|
+
await update(() => host.variant.set('glass-clear'));
|
|
442
|
+
expect(item('Copy').classList).toContain('bg-secondary');
|
|
443
|
+
|
|
444
|
+
// A column's ends are half an item round, or a capsule of square icons.
|
|
445
|
+
await update(() => host.orientation.set('vertical'));
|
|
446
|
+
expect(group().classList).not.toContain('rounded-full');
|
|
447
|
+
expect(group().style.borderRadius).toBe('20px');
|
|
448
|
+
await update(() => host.size.set('icon'));
|
|
449
|
+
expect(group().classList).toContain('rounded-full');
|
|
450
|
+
expect(group().style.borderRadius).toBe('');
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it('shows the state layer on a pressed item', async () => {
|
|
454
|
+
const { host, item, update } = await renderGroup(ANDROID);
|
|
455
|
+
await update(() => host.variant.set('glass'));
|
|
456
|
+
const layer = () => item('Copy').querySelector('.absolute');
|
|
457
|
+
|
|
458
|
+
fireEvent.touchstart(item('Copy'));
|
|
459
|
+
await waitForUpdate();
|
|
460
|
+
expect(layer()!.classList).toContain('bg-secondary-foreground');
|
|
461
|
+
expect(layer()!.classList).toContain('opacity-10');
|
|
462
|
+
expect(scaleOf(item('Copy'))).toBe('');
|
|
463
|
+
|
|
464
|
+
fireEvent.touchcancel(item('Copy'));
|
|
465
|
+
await waitForUpdate();
|
|
466
|
+
expect(layer()).toBeNull();
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
it('outlines the items under Increase Contrast', async () => {
|
|
470
|
+
const { host, item, update } = await renderGroup(ANDROID);
|
|
471
|
+
await update(() => {
|
|
472
|
+
host.variant.set('glass');
|
|
473
|
+
settings.increaseContrast.set(true);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
expect(item('Copy').classList).toContain('border-foreground');
|
|
477
|
+
});
|
|
478
|
+
});
|
|
3
479
|
|
|
4
480
|
describe('buttonGroupItemVariants', () => {
|
|
5
481
|
it('returns default variant and size classes', () => {
|
|
@@ -26,6 +502,12 @@ describe('buttonGroupItemVariants', () => {
|
|
|
26
502
|
expect(result).toContain('px-6');
|
|
27
503
|
});
|
|
28
504
|
|
|
505
|
+
it('returns square icon size classes', () => {
|
|
506
|
+
const result = buttonGroupItemVariants({ size: 'icon' });
|
|
507
|
+
expect(result).toContain('h-10');
|
|
508
|
+
expect(result).toContain('w-10');
|
|
509
|
+
});
|
|
510
|
+
|
|
29
511
|
it('always includes base layout classes', () => {
|
|
30
512
|
const result = buttonGroupItemVariants({ variant: 'outline', size: 'lg' });
|
|
31
513
|
expect(result).toContain('flex');
|