@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
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export {
|
|
2
|
+
UiChart,
|
|
3
|
+
UiChartTooltip,
|
|
4
|
+
type UiChartTooltipContext,
|
|
5
|
+
tooltip,
|
|
6
|
+
} from './chart';
|
|
7
|
+
export {
|
|
8
|
+
type ChartColorScheme,
|
|
9
|
+
type ChartColorSchemeOption,
|
|
10
|
+
type ChartColorTokens,
|
|
11
|
+
CHART_DARK_COLORS,
|
|
12
|
+
CHART_LIGHT_COLORS,
|
|
13
|
+
DEFAULT_CHART_THEME,
|
|
14
|
+
UI_CHART_THEME,
|
|
15
|
+
type UiChartTheme,
|
|
16
|
+
provideChartTheme,
|
|
17
|
+
resolveChartPaint,
|
|
18
|
+
} from './chart-theme';
|
|
19
|
+
export {
|
|
20
|
+
type CartesianPresetOptions,
|
|
21
|
+
type ChartPadding,
|
|
22
|
+
type XYDatum,
|
|
23
|
+
} from './chart-presets';
|
|
24
|
+
export { type ChartViewport, type ChartZoomAxes } from './chart-zoom';
|
|
@@ -1,59 +1,141 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { Component, signal } from '@angular/core';
|
|
2
|
+
import {
|
|
3
|
+
fireEvent,
|
|
4
|
+
render,
|
|
5
|
+
waitForUpdate,
|
|
6
|
+
} from '@blotch/angular-lynx-testing-library';
|
|
7
|
+
import {
|
|
8
|
+
type MockInstance,
|
|
9
|
+
afterEach,
|
|
10
|
+
beforeEach,
|
|
11
|
+
describe,
|
|
12
|
+
expect,
|
|
13
|
+
it,
|
|
14
|
+
vi,
|
|
15
|
+
} from 'vitest';
|
|
16
|
+
import {
|
|
17
|
+
type LynxMainThread,
|
|
18
|
+
lynxMainThread,
|
|
19
|
+
} from '../../../test-utils/lynx-main-thread';
|
|
4
20
|
import { UiCheckbox } from './checkbox';
|
|
5
21
|
|
|
22
|
+
@Component({
|
|
23
|
+
imports: [UiCheckbox],
|
|
24
|
+
template: `
|
|
25
|
+
<ui-checkbox
|
|
26
|
+
[(checked)]="checked"
|
|
27
|
+
[disabled]="disabled()"
|
|
28
|
+
class="mr-2"
|
|
29
|
+
(changed)="changes.push($event)"
|
|
30
|
+
/>
|
|
31
|
+
`,
|
|
32
|
+
})
|
|
33
|
+
class CheckboxHost {
|
|
34
|
+
readonly checked = signal(false);
|
|
35
|
+
readonly disabled = signal(false);
|
|
36
|
+
readonly changes: boolean[] = [];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const renderCheckbox = async () => {
|
|
40
|
+
const result = await render(CheckboxHost);
|
|
41
|
+
return {
|
|
42
|
+
...result,
|
|
43
|
+
host: result.componentRef.instance as CheckboxHost,
|
|
44
|
+
/** The 44pt tap target around the box. */
|
|
45
|
+
target: () => result.container.querySelector('.h-11')!,
|
|
46
|
+
box: () => result.container.querySelector('.rounded-sm')!,
|
|
47
|
+
checkmark: () => result.getByText('✓'),
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/** `__ElementAnimate`'s first argument for starting an animation. */
|
|
52
|
+
const START = 0;
|
|
53
|
+
/** `__ElementAnimate`'s first argument for canceling one. */
|
|
54
|
+
const CANCEL = 3;
|
|
55
|
+
|
|
6
56
|
describe('UiCheckbox', () => {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
57
|
+
// Watched per test: the testing library resets the main thread after each.
|
|
58
|
+
let animate: MockInstance<LynxMainThread['__ElementAnimate']>;
|
|
59
|
+
beforeEach(() => {
|
|
60
|
+
animate = vi.spyOn(lynxMainThread(), '__ElementAnimate');
|
|
10
61
|
});
|
|
62
|
+
afterEach(() => animate.mockRestore());
|
|
11
63
|
|
|
12
|
-
it('
|
|
13
|
-
const {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
inst.toggle();
|
|
19
|
-
expect(inst.checked()).toBe(true);
|
|
20
|
-
inst.toggle();
|
|
21
|
-
expect(inst.checked()).toBe(false);
|
|
22
|
-
expect(changes).toEqual([true, false]);
|
|
64
|
+
it('renders an empty box with the given class', async () => {
|
|
65
|
+
const { box, checkmark } = await renderCheckbox();
|
|
66
|
+
expect(box().classList).toContain('bg-transparent');
|
|
67
|
+
expect(box().classList).toContain('mr-2');
|
|
68
|
+
expect(checkmark().getAttribute('style')).toContain('opacity: 0');
|
|
23
69
|
});
|
|
24
70
|
|
|
25
|
-
it('
|
|
26
|
-
const {
|
|
27
|
-
const inst = componentRef.instance as UiCheckbox;
|
|
28
|
-
setInputSignal(inst.disabled, true);
|
|
29
|
-
const changed = vi.fn();
|
|
30
|
-
inst.changed.subscribe(changed);
|
|
71
|
+
it('checks and unchecks on tap, emitting `changed`', async () => {
|
|
72
|
+
const { host, target, box, checkmark } = await renderCheckbox();
|
|
31
73
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
expect(
|
|
74
|
+
fireEvent.tap(target());
|
|
75
|
+
await waitForUpdate();
|
|
76
|
+
expect(host.checked()).toBe(true);
|
|
77
|
+
expect(box().classList).toContain('bg-primary');
|
|
78
|
+
expect(checkmark().getAttribute('style')).toContain('opacity: 1');
|
|
79
|
+
|
|
80
|
+
fireEvent.tap(target());
|
|
81
|
+
await waitForUpdate();
|
|
82
|
+
expect(host.checked()).toBe(false);
|
|
83
|
+
expect(host.changes).toEqual([true, false]);
|
|
35
84
|
});
|
|
36
85
|
|
|
37
|
-
it('
|
|
38
|
-
const {
|
|
39
|
-
|
|
40
|
-
|
|
86
|
+
it("doesn't animate on mount, then pops the checkmark in and out", async () => {
|
|
87
|
+
const { target, checkmark } = await renderCheckbox();
|
|
88
|
+
expect(animate).not.toHaveBeenCalled();
|
|
89
|
+
|
|
90
|
+
fireEvent.tap(target());
|
|
41
91
|
await waitForUpdate();
|
|
92
|
+
expect(animate).toHaveBeenCalledWith(checkmark(), [
|
|
93
|
+
START,
|
|
94
|
+
expect.any(String),
|
|
95
|
+
[
|
|
96
|
+
{ transform: 'scale(0.6)', opacity: 0 },
|
|
97
|
+
{ transform: 'scale(1)', opacity: 1 },
|
|
98
|
+
],
|
|
99
|
+
expect.objectContaining({ fill: 'forwards' }),
|
|
100
|
+
]);
|
|
42
101
|
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
102
|
+
// Unchecking cuts the pop-in short and pops the checkmark out.
|
|
103
|
+
fireEvent.tap(target());
|
|
104
|
+
await waitForUpdate();
|
|
105
|
+
expect(animate).toHaveBeenCalledWith(checkmark(), [
|
|
106
|
+
CANCEL,
|
|
107
|
+
expect.any(String),
|
|
108
|
+
]);
|
|
109
|
+
expect(animate).toHaveBeenLastCalledWith(checkmark(), [
|
|
110
|
+
START,
|
|
111
|
+
expect.any(String),
|
|
112
|
+
[
|
|
113
|
+
{ transform: 'scale(1)', opacity: 1 },
|
|
114
|
+
{ transform: 'scale(0.8)', opacity: 0 },
|
|
115
|
+
],
|
|
116
|
+
expect.objectContaining({ fill: 'forwards' }),
|
|
117
|
+
]);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("doesn't animate two taps in one frame, which leave it as it was", async () => {
|
|
121
|
+
const { host, target } = await renderCheckbox();
|
|
122
|
+
fireEvent.tap(target());
|
|
123
|
+
fireEvent.tap(target());
|
|
124
|
+
await waitForUpdate();
|
|
125
|
+
expect(host.checked()).toBe(false);
|
|
126
|
+
expect(host.changes).toEqual([true, false]);
|
|
127
|
+
expect(animate).not.toHaveBeenCalled();
|
|
46
128
|
});
|
|
47
129
|
|
|
48
|
-
it('
|
|
49
|
-
const {
|
|
50
|
-
|
|
130
|
+
it('dims and ignores taps while disabled', async () => {
|
|
131
|
+
const { host, target, box } = await renderCheckbox();
|
|
132
|
+
host.disabled.set(true);
|
|
133
|
+
await waitForUpdate();
|
|
134
|
+
expect(box().classList).toContain('opacity-50');
|
|
135
|
+
|
|
136
|
+
fireEvent.tap(target());
|
|
51
137
|
await waitForUpdate();
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const hasDim = Array.from(boxes).some((el) =>
|
|
55
|
-
el.getAttribute('class')?.includes('opacity-50'),
|
|
56
|
-
);
|
|
57
|
-
expect(hasDim).toBe(true);
|
|
138
|
+
expect(host.checked()).toBe(false);
|
|
139
|
+
expect(host.changes).toEqual([]);
|
|
58
140
|
});
|
|
59
141
|
});
|
|
@@ -43,34 +43,34 @@ export class UiCheckbox {
|
|
|
43
43
|
|
|
44
44
|
readonly changed = output<boolean>();
|
|
45
45
|
|
|
46
|
-
readonly checkmarkRef = viewChild<ElementRef>('checkmark');
|
|
46
|
+
readonly checkmarkRef = viewChild.required<ElementRef>('checkmark');
|
|
47
47
|
#checkAnim?: AnimationHandle;
|
|
48
48
|
#previousChecked?: boolean;
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
if (isChecked === this.#previousChecked) return;
|
|
50
|
+
/**
|
|
51
|
+
* Pops the checkmark in when checked and out when unchecked. A field rather
|
|
52
|
+
* than a constructor: Angular's test build keeps a function for a
|
|
53
|
+
* constructor that nothing calls, which an app's coverage report would show
|
|
54
|
+
* as uncovered.
|
|
55
|
+
*
|
|
56
|
+
* `#previousChecked === undefined` guards the first run: we don't want to
|
|
57
|
+
* pop in the checkmark when the component mounts already checked. The
|
|
58
|
+
* equality guard skips a run that isn't a change: `checked` set and set
|
|
59
|
+
* back before the effect runs (two taps in one frame) still re-runs it.
|
|
60
|
+
*/
|
|
61
|
+
protected readonly checkmarkAnimation = effect(() => {
|
|
62
|
+
const isChecked = this.checked();
|
|
63
|
+
const el = this.checkmarkRef().nativeElement;
|
|
64
|
+
if (this.#previousChecked === undefined) {
|
|
67
65
|
this.#previousChecked = isChecked;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (isChecked === this.#previousChecked) return;
|
|
69
|
+
this.#previousChecked = isChecked;
|
|
68
70
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
});
|
|
73
|
-
}
|
|
71
|
+
this.#checkAnim?.cancel();
|
|
72
|
+
this.#checkAnim = isChecked ? popIn(el) : popOut(el);
|
|
73
|
+
});
|
|
74
74
|
|
|
75
75
|
protected readonly boxClass = computed(() =>
|
|
76
76
|
cn(
|
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
import { Component, signal } from '@angular/core';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
fireEvent,
|
|
4
|
+
render,
|
|
5
|
+
waitForUpdate,
|
|
6
|
+
} from '@blotch/angular-lynx-testing-library';
|
|
3
7
|
import { LYNX_ELEMENTS } from '@blotch/angular-lynx';
|
|
4
|
-
import {
|
|
5
|
-
|
|
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';
|
|
6
21
|
import {
|
|
7
22
|
UiCollapsible,
|
|
8
23
|
UiCollapsibleContent,
|
|
@@ -10,103 +25,165 @@ import {
|
|
|
10
25
|
} from './collapsible';
|
|
11
26
|
|
|
12
27
|
@Component({
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
28
|
+
imports: [
|
|
29
|
+
UiCollapsible,
|
|
30
|
+
UiCollapsibleTrigger,
|
|
31
|
+
UiCollapsibleContent,
|
|
32
|
+
LYNX_ELEMENTS,
|
|
33
|
+
],
|
|
34
|
+
template: `
|
|
35
|
+
<ui-collapsible [(open)]="open" [disabled]="disabled()" class="gap-2">
|
|
36
|
+
<ui-collapsible-trigger class="px-2">
|
|
37
|
+
<text>Toggle</text>
|
|
38
|
+
</ui-collapsible-trigger>
|
|
39
|
+
<ui-collapsible-content class="pt-1">
|
|
40
|
+
<text>Details</text>
|
|
41
|
+
</ui-collapsible-content>
|
|
42
|
+
</ui-collapsible>
|
|
43
|
+
`,
|
|
16
44
|
})
|
|
17
|
-
class CollapsibleHost {
|
|
45
|
+
class CollapsibleHost {
|
|
46
|
+
readonly open = signal(false);
|
|
47
|
+
readonly disabled = signal(false);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const renderCollapsible = async () => {
|
|
51
|
+
const result = await render(CollapsibleHost);
|
|
52
|
+
return {
|
|
53
|
+
...result,
|
|
54
|
+
host: result.componentRef.instance as CollapsibleHost,
|
|
55
|
+
// The trigger's own view holds the tap listener; the projected text is
|
|
56
|
+
// inside it.
|
|
57
|
+
trigger: () => result.getByText('Toggle').parentElement!,
|
|
58
|
+
// The content's own view wraps the projected text while it's open.
|
|
59
|
+
content: () => result.queryByText('Details')?.parentElement ?? null,
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/** `__ElementAnimate`'s first argument for starting an animation. */
|
|
64
|
+
const START = 0;
|
|
65
|
+
/** `__ElementAnimate`'s first argument for canceling an animation. */
|
|
66
|
+
const CANCEL = 3;
|
|
18
67
|
|
|
19
68
|
describe('UiCollapsible', () => {
|
|
20
|
-
it('
|
|
21
|
-
const {
|
|
22
|
-
|
|
23
|
-
|
|
69
|
+
it("merges each part's class with its own", async () => {
|
|
70
|
+
const { container, host, trigger, content } = await renderCollapsible();
|
|
71
|
+
// The host element carries the class too, so look for the view that has
|
|
72
|
+
// both its own and the given class.
|
|
73
|
+
expect(container.querySelector('.flex-col.gap-2')).not.toBeNull();
|
|
74
|
+
expect(trigger().classList).toContain('flex-row');
|
|
75
|
+
expect(trigger().classList).toContain('px-2');
|
|
24
76
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
expect(
|
|
29
|
-
inst.toggle();
|
|
30
|
-
expect(inst.open()).toBe(true);
|
|
31
|
-
inst.toggle();
|
|
32
|
-
expect(inst.open()).toBe(false);
|
|
77
|
+
host.open.set(true);
|
|
78
|
+
await waitForUpdate();
|
|
79
|
+
expect(content()!.classList).toContain('flex-col');
|
|
80
|
+
expect(content()!.classList).toContain('pt-1');
|
|
33
81
|
});
|
|
34
82
|
|
|
35
|
-
it('
|
|
36
|
-
const {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
inst.toggle();
|
|
40
|
-
expect(inst.open()).toBe(false);
|
|
41
|
-
});
|
|
42
|
-
});
|
|
83
|
+
it('opens and closes when the trigger is tapped', async () => {
|
|
84
|
+
const { host, trigger, content } = await renderCollapsible();
|
|
85
|
+
// Closed, the content isn't mounted at all.
|
|
86
|
+
expect(content()).toBeNull();
|
|
43
87
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
88
|
+
fireEvent.tap(trigger());
|
|
89
|
+
await waitForUpdate();
|
|
90
|
+
expect(content()).not.toBeNull();
|
|
91
|
+
expect(host.open()).toBe(true);
|
|
48
92
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
(trigger as unknown as { onTap: () => void }).onTap();
|
|
54
|
-
expect(parent.open()).toBe(true);
|
|
93
|
+
fireEvent.tap(trigger());
|
|
94
|
+
await waitForUpdate();
|
|
95
|
+
expect(content()).toBeNull();
|
|
96
|
+
expect(host.open()).toBe(false);
|
|
55
97
|
});
|
|
56
98
|
|
|
57
|
-
it('
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
setInputSignal(parent.disabled, true);
|
|
61
|
-
|
|
62
|
-
const triggerResult = await render(UiCollapsibleTrigger, {
|
|
63
|
-
providers: [{ provide: UiCollapsible, useValue: parent }],
|
|
64
|
-
});
|
|
99
|
+
it('opens when its `open` is set', async () => {
|
|
100
|
+
const { host, content } = await renderCollapsible();
|
|
101
|
+
host.open.set(true);
|
|
65
102
|
await waitForUpdate();
|
|
66
|
-
|
|
67
|
-
expect(view?.getAttribute('class')).toContain('opacity-50');
|
|
103
|
+
expect(content()).not.toBeNull();
|
|
68
104
|
});
|
|
69
|
-
});
|
|
70
105
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const parent = parentResult.componentRef.instance as UiCollapsible;
|
|
106
|
+
it('dims its trigger and ignores taps while disabled', async () => {
|
|
107
|
+
const { host, trigger, content } = await renderCollapsible();
|
|
108
|
+
expect(trigger().classList).not.toContain('opacity-50');
|
|
75
109
|
|
|
76
|
-
|
|
77
|
-
providers: [{ provide: UiCollapsible, useValue: parent }],
|
|
78
|
-
});
|
|
79
|
-
const content = contentResult.componentRef.instance as UiCollapsibleContent;
|
|
80
|
-
// JIT + jsdom doesn't populate the `#content` viewChild — patch it with a
|
|
81
|
-
// signal returning the outer view so the setTimeout body's `el` check
|
|
82
|
-
// passes and revealIn actually runs.
|
|
83
|
-
const view = contentResult.container.querySelector('view') as Element;
|
|
84
|
-
(content as unknown as { contentRef: unknown }).contentRef = signal({
|
|
85
|
-
nativeElement: view,
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
parent.open.set(true);
|
|
110
|
+
host.disabled.set(true);
|
|
89
111
|
await waitForUpdate();
|
|
90
|
-
|
|
112
|
+
expect(trigger().classList).toContain('opacity-50');
|
|
91
113
|
|
|
92
|
-
|
|
93
|
-
parent.open.set(false);
|
|
94
|
-
await waitForUpdate();
|
|
95
|
-
parent.open.set(true);
|
|
96
|
-
await waitForUpdate();
|
|
114
|
+
fireEvent.tap(trigger());
|
|
97
115
|
await waitForUpdate();
|
|
98
|
-
expect(
|
|
116
|
+
expect(content()).toBeNull();
|
|
117
|
+
expect(host.open()).toBe(false);
|
|
99
118
|
});
|
|
100
119
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
120
|
+
describe('reveal animation', () => {
|
|
121
|
+
// Watched per test: the testing library resets the main thread after each.
|
|
122
|
+
let animate: MockInstance<LynxMainThread['__ElementAnimate']>;
|
|
123
|
+
beforeEach(() => {
|
|
124
|
+
animate = vi.spyOn(lynxMainThread(), '__ElementAnimate');
|
|
125
|
+
});
|
|
126
|
+
afterEach(() => animate.mockRestore());
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* The keys of the animations started on an element.
|
|
130
|
+
*/
|
|
131
|
+
const startedOn = (element: Element | null) =>
|
|
132
|
+
animate.mock.calls
|
|
133
|
+
.filter(
|
|
134
|
+
([target, [operation]]) => target === element && operation === START,
|
|
135
|
+
)
|
|
136
|
+
.map(([, [, key]]) => key);
|
|
137
|
+
|
|
138
|
+
it('slides the content down into place each time it opens', async () => {
|
|
139
|
+
const { trigger, content } = await renderCollapsible();
|
|
140
|
+
|
|
141
|
+
fireEvent.tap(trigger());
|
|
142
|
+
// The first update runs the effect, which waits a macrotask so the
|
|
143
|
+
// content is committed to Lynx's tree; the second lets that run.
|
|
144
|
+
await waitForUpdate();
|
|
145
|
+
await waitForUpdate();
|
|
146
|
+
expect(animate).toHaveBeenCalledWith(content(), [
|
|
147
|
+
START,
|
|
148
|
+
expect.any(String),
|
|
149
|
+
[
|
|
150
|
+
{ transform: 'translateY(-8px)', opacity: 0 },
|
|
151
|
+
{ transform: 'translateY(0)', opacity: 1 },
|
|
152
|
+
],
|
|
153
|
+
// No fill: once it ends, the content's inline resting style applies.
|
|
154
|
+
expect.objectContaining({ fill: 'none' }),
|
|
155
|
+
]);
|
|
156
|
+
const [firstKey] = startedOn(content());
|
|
157
|
+
|
|
158
|
+
fireEvent.tap(trigger());
|
|
159
|
+
await waitForUpdate();
|
|
160
|
+
fireEvent.tap(trigger());
|
|
161
|
+
await waitForUpdate();
|
|
162
|
+
await waitForUpdate();
|
|
163
|
+
// Reopening stops the previous reveal before it plays a new one on the
|
|
164
|
+
// content's new view.
|
|
165
|
+
expect(animate).toHaveBeenCalledWith(expect.anything(), [
|
|
166
|
+
CANCEL,
|
|
167
|
+
firstKey,
|
|
168
|
+
]);
|
|
169
|
+
expect(startedOn(content())).toHaveLength(1);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("doesn't animate content that closed before its reveal began", async () => {
|
|
173
|
+
const { componentRef, trigger, content } = await renderCollapsible();
|
|
174
|
+
|
|
175
|
+
fireEvent.tap(trigger());
|
|
176
|
+
// The effect has scheduled the reveal for the next macrotask.
|
|
177
|
+
await waitForUpdate();
|
|
178
|
+
// Close it before then, and render that straight away.
|
|
179
|
+
fireEvent.tap(trigger());
|
|
180
|
+
componentRef.changeDetectorRef.detectChanges();
|
|
181
|
+
expect(content()).toBeNull();
|
|
182
|
+
|
|
183
|
+
await waitForUpdate();
|
|
184
|
+
expect(
|
|
185
|
+
animate.mock.calls.filter(([, [operation]]) => operation === START),
|
|
186
|
+
).toEqual([]);
|
|
106
187
|
});
|
|
107
|
-
parent.open.set(true);
|
|
108
|
-
await waitForUpdate();
|
|
109
|
-
await waitForUpdate();
|
|
110
|
-
expect(parent.open()).toBe(true);
|
|
111
188
|
});
|
|
112
189
|
});
|
|
@@ -101,20 +101,25 @@ export class UiCollapsibleContent {
|
|
|
101
101
|
readonly contentRef = viewChild<ElementRef>('content');
|
|
102
102
|
#anim?: AnimationHandle;
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
104
|
+
/**
|
|
105
|
+
* Animates the reveal whenever the panel opens. A field rather than a
|
|
106
|
+
* constructor: Angular's test build keeps a function for a constructor that
|
|
107
|
+
* nothing calls, which an app's coverage report would show as uncovered.
|
|
108
|
+
*
|
|
109
|
+
* contentRef only resolves while the `@if` content is mounted; defer one
|
|
110
|
+
* macrotask so the freshly (re)created element is committed to the native
|
|
111
|
+
* tree before we animate it. By then the panel may have closed again, which
|
|
112
|
+
* leaves nothing to animate.
|
|
113
|
+
*/
|
|
114
|
+
protected readonly revealing = effect(() => {
|
|
115
|
+
if (!this.collapsible.open()) return;
|
|
116
|
+
setTimeout(() => {
|
|
117
|
+
const el = this.contentRef()?.nativeElement;
|
|
118
|
+
if (!el) return;
|
|
119
|
+
this.#anim?.cancel();
|
|
120
|
+
this.#anim = revealIn(el, { fromY: -8, fill: 'none' });
|
|
121
|
+
}, 0);
|
|
122
|
+
});
|
|
118
123
|
|
|
119
124
|
protected readonly contentClass = computed(() =>
|
|
120
125
|
cn('flex-col flex', this.userClass()),
|