@blotch/dolan 0.0.0-stage → 22.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/add.d.ts +22 -0
- package/dist/commands/add.js +132 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/add.test.d.ts +1 -0
- package/dist/commands/add.test.js +209 -0
- package/dist/commands/add.test.js.map +1 -0
- package/dist/commands/diff.d.ts +13 -0
- package/dist/commands/diff.js +155 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/diff.test.d.ts +1 -0
- package/dist/commands/diff.test.js +327 -0
- package/dist/commands/diff.test.js.map +1 -0
- package/dist/commands/doctor.d.ts +15 -0
- package/dist/commands/doctor.js +248 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/doctor.test.d.ts +1 -0
- package/dist/commands/doctor.test.js +301 -0
- package/dist/commands/doctor.test.js.map +1 -0
- package/dist/commands/eject.d.ts +11 -0
- package/dist/commands/eject.js +55 -0
- package/dist/commands/eject.js.map +1 -0
- package/dist/commands/eject.test.d.ts +1 -0
- package/dist/commands/eject.test.js +151 -0
- package/dist/commands/eject.test.js.map +1 -0
- package/dist/commands/info.d.ts +1 -0
- package/dist/commands/info.js +73 -0
- package/dist/commands/info.js.map +1 -0
- package/dist/commands/info.test.d.ts +1 -0
- package/dist/commands/info.test.js +143 -0
- package/dist/commands/info.test.js.map +1 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +98 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/init.test.d.ts +1 -0
- package/dist/commands/init.test.js +154 -0
- package/dist/commands/init.test.js.map +1 -0
- package/dist/commands/list.d.ts +16 -0
- package/dist/commands/list.js +124 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/list.test.d.ts +1 -0
- package/dist/commands/list.test.js +285 -0
- package/dist/commands/list.test.js.map +1 -0
- package/dist/commands/outdated.d.ts +13 -0
- package/dist/commands/outdated.js +116 -0
- package/dist/commands/outdated.js.map +1 -0
- package/dist/commands/outdated.test.d.ts +1 -0
- package/dist/commands/outdated.test.js +208 -0
- package/dist/commands/outdated.test.js.map +1 -0
- package/dist/commands/remove.d.ts +18 -0
- package/dist/commands/remove.js +161 -0
- package/dist/commands/remove.js.map +1 -0
- package/dist/commands/remove.test.d.ts +1 -0
- package/dist/commands/remove.test.js +361 -0
- package/dist/commands/remove.test.js.map +1 -0
- package/dist/commands/theme.d.ts +1 -0
- package/dist/commands/theme.js +66 -0
- package/dist/commands/theme.js.map +1 -0
- package/dist/commands/theme.test.d.ts +1 -0
- package/dist/commands/theme.test.js +133 -0
- package/dist/commands/theme.test.js.map +1 -0
- package/dist/commands/update.d.ts +31 -0
- package/dist/commands/update.js +601 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/update.test.d.ts +1 -0
- package/dist/commands/update.test.js +1233 -0
- package/dist/commands/update.test.js.map +1 -0
- package/dist/config.d.ts +11 -0
- package/dist/config.js +19 -0
- package/dist/config.js.map +1 -0
- package/dist/config.test.d.ts +1 -0
- package/dist/config.test.js +48 -0
- package/dist/config.test.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +89 -0
- package/dist/index.js.map +1 -0
- package/dist/lockfile.d.ts +21 -0
- package/dist/lockfile.js +39 -0
- package/dist/lockfile.js.map +1 -0
- package/dist/registry.d.ts +14 -0
- package/dist/registry.js +85 -0
- package/dist/registry.js.map +1 -0
- package/dist/registry.test.d.ts +1 -0
- package/dist/registry.test.js +95 -0
- package/dist/registry.test.js.map +1 -0
- package/dist/test-utils.d.ts +16 -0
- package/dist/test-utils.js +65 -0
- package/dist/test-utils.js.map +1 -0
- package/dist/ui/components/accordion/accordion.test.ts +194 -0
- package/dist/ui/components/accordion/accordion.ts +173 -0
- package/dist/ui/components/accordion/index.ts +6 -0
- package/dist/ui/components/action-sheet/action-sheet.test.ts +211 -0
- package/dist/ui/components/action-sheet/action-sheet.ts +353 -0
- package/dist/ui/components/action-sheet/index.ts +6 -0
- package/dist/ui/components/alert/alert.test.ts +48 -0
- package/dist/ui/components/alert/alert.ts +104 -0
- package/dist/ui/components/alert/index.ts +1 -0
- package/dist/ui/components/alert-dialog/alert-dialog.test.ts +141 -0
- package/dist/ui/components/alert-dialog/alert-dialog.ts +243 -0
- package/dist/ui/components/alert-dialog/index.ts +7 -0
- package/dist/ui/components/area-chart/area-chart-render.test.ts +121 -0
- package/dist/ui/components/area-chart/area-chart.test.ts +158 -0
- package/dist/ui/components/area-chart/area-chart.ts +349 -0
- package/dist/ui/components/area-chart/index.ts +6 -0
- package/dist/ui/components/aspect-ratio/aspect-ratio.test.ts +10 -0
- package/dist/ui/components/aspect-ratio/aspect-ratio.ts +28 -0
- package/dist/ui/components/aspect-ratio/index.ts +1 -0
- package/dist/ui/components/avatar/avatar.test.ts +69 -0
- package/dist/ui/components/avatar/avatar.ts +131 -0
- package/dist/ui/components/avatar/index.ts +1 -0
- package/dist/ui/components/badge/badge.test.ts +49 -0
- package/dist/ui/components/badge/badge.ts +97 -0
- package/dist/ui/components/badge/index.ts +1 -0
- package/dist/ui/components/bar-chart/bar-chart-render.test.ts +174 -0
- package/dist/ui/components/bar-chart/bar-chart.test.ts +73 -0
- package/dist/ui/components/bar-chart/bar-chart.ts +327 -0
- package/dist/ui/components/bar-chart/index.ts +6 -0
- package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +163 -0
- package/dist/ui/components/bottom-sheet/bottom-sheet.ts +339 -0
- package/dist/ui/components/bottom-sheet/index.ts +1 -0
- package/dist/ui/components/button/button-render.test.ts +113 -0
- package/dist/ui/components/button/button.test.ts +70 -0
- package/dist/ui/components/button/button.ts +170 -0
- package/dist/ui/components/button/index.ts +6 -0
- package/dist/ui/components/button-group/button-group-render.test.ts +146 -0
- package/dist/ui/components/button-group/button-group.test.ts +54 -0
- package/dist/ui/components/button-group/button-group.ts +209 -0
- package/dist/ui/components/button-group/index.ts +8 -0
- package/dist/ui/components/candlestick-chart/candlestick-chart-render.test.ts +174 -0
- package/dist/ui/components/candlestick-chart/candlestick-chart.test.ts +96 -0
- package/dist/ui/components/candlestick-chart/candlestick-chart.ts +355 -0
- package/dist/ui/components/candlestick-chart/index.ts +7 -0
- package/dist/ui/components/card/card.test.ts +110 -0
- package/dist/ui/components/card/card.ts +162 -0
- package/dist/ui/components/card/index.ts +8 -0
- package/dist/ui/components/cartesian-chart/cartesian-chart-render.test.ts +188 -0
- package/dist/ui/components/cartesian-chart/cartesian-chart.test.ts +549 -0
- package/dist/ui/components/cartesian-chart/cartesian-chart.ts +1140 -0
- package/dist/ui/components/cartesian-chart/index.ts +18 -0
- package/dist/ui/components/checkbox/checkbox.test.ts +59 -0
- package/dist/ui/components/checkbox/checkbox.ts +96 -0
- package/dist/ui/components/checkbox/index.ts +1 -0
- package/dist/ui/components/collapsible/collapsible.test.ts +112 -0
- package/dist/ui/components/collapsible/collapsible.ts +122 -0
- package/dist/ui/components/collapsible/index.ts +5 -0
- package/dist/ui/components/dialog/dialog.test.ts +157 -0
- package/dist/ui/components/dialog/dialog.ts +248 -0
- package/dist/ui/components/dialog/index.ts +7 -0
- package/dist/ui/components/empty-state/empty-state.test.ts +60 -0
- package/dist/ui/components/empty-state/empty-state.ts +46 -0
- package/dist/ui/components/empty-state/index.ts +1 -0
- package/dist/ui/components/icon/icon.test.ts +52 -0
- package/dist/ui/components/icon/icon.ts +54 -0
- package/dist/ui/components/icon/icons.ts +569 -0
- package/dist/ui/components/icon/index.ts +2 -0
- package/dist/ui/components/input/index.ts +1 -0
- package/dist/ui/components/input/input.test.ts +134 -0
- package/dist/ui/components/input/input.ts +189 -0
- package/dist/ui/components/label/index.ts +1 -0
- package/dist/ui/components/label/label.test.ts +38 -0
- package/dist/ui/components/label/label.ts +24 -0
- package/dist/ui/components/line-chart/index.ts +6 -0
- package/dist/ui/components/line-chart/line-chart-render.test.ts +121 -0
- package/dist/ui/components/line-chart/line-chart.test.ts +81 -0
- package/dist/ui/components/line-chart/line-chart.ts +273 -0
- package/dist/ui/components/list/index.ts +1 -0
- package/dist/ui/components/list/list.test.ts +35 -0
- package/dist/ui/components/list/list.ts +71 -0
- package/dist/ui/components/nav-drawer/index.ts +8 -0
- package/dist/ui/components/nav-drawer/nav-drawer.test.ts +267 -0
- package/dist/ui/components/nav-drawer/nav-drawer.ts +504 -0
- package/dist/ui/components/pagination/index.ts +1 -0
- package/dist/ui/components/pagination/pagination.test.ts +96 -0
- package/dist/ui/components/pagination/pagination.ts +147 -0
- package/dist/ui/components/progress/index.ts +1 -0
- package/dist/ui/components/progress/progress.test.ts +71 -0
- package/dist/ui/components/progress/progress.ts +118 -0
- package/dist/ui/components/radio-group/index.ts +1 -0
- package/dist/ui/components/radio-group/radio-group.test.ts +115 -0
- package/dist/ui/components/radio-group/radio-group.ts +129 -0
- package/dist/ui/components/scatter-chart/index.ts +6 -0
- package/dist/ui/components/scatter-chart/scatter-chart-render.test.ts +157 -0
- package/dist/ui/components/scatter-chart/scatter-chart.test.ts +57 -0
- package/dist/ui/components/scatter-chart/scatter-chart.ts +277 -0
- package/dist/ui/components/scroll-area/index.ts +1 -0
- package/dist/ui/components/scroll-area/scroll-area.test.ts +40 -0
- package/dist/ui/components/scroll-area/scroll-area.ts +73 -0
- package/dist/ui/components/select/index.ts +1 -0
- package/dist/ui/components/select/select.test.ts +107 -0
- package/dist/ui/components/select/select.ts +146 -0
- package/dist/ui/components/separator/index.ts +1 -0
- package/dist/ui/components/separator/separator.test.ts +26 -0
- package/dist/ui/components/separator/separator.ts +45 -0
- package/dist/ui/components/sheet/index.ts +7 -0
- package/dist/ui/components/sheet/sheet.test.ts +44 -0
- package/dist/ui/components/sheet/sheet.ts +114 -0
- package/dist/ui/components/skeleton/index.ts +1 -0
- package/dist/ui/components/skeleton/skeleton.test.ts +13 -0
- package/dist/ui/components/skeleton/skeleton.ts +45 -0
- package/dist/ui/components/spinner/index.ts +1 -0
- package/dist/ui/components/spinner/spinner.test.ts +39 -0
- package/dist/ui/components/spinner/spinner.ts +83 -0
- package/dist/ui/components/switch/index.ts +1 -0
- package/dist/ui/components/switch/switch.test.ts +63 -0
- package/dist/ui/components/switch/switch.ts +98 -0
- package/dist/ui/components/tabs/index.ts +1 -0
- package/dist/ui/components/tabs/tabs.test.ts +199 -0
- package/dist/ui/components/tabs/tabs.ts +317 -0
- package/dist/ui/components/textarea/index.ts +1 -0
- package/dist/ui/components/textarea/textarea.test.ts +138 -0
- package/dist/ui/components/textarea/textarea.ts +221 -0
- package/dist/ui/components/toast/index.ts +9 -0
- package/dist/ui/components/toast/toast-state.test.ts +54 -0
- package/dist/ui/components/toast/toast-state.ts +46 -0
- package/dist/ui/components/toast/toast.test.ts +342 -0
- package/dist/ui/components/toast/toast.ts +527 -0
- package/dist/ui/components/toggle/index.ts +7 -0
- package/dist/ui/components/toggle/toggle.test.ts +186 -0
- package/dist/ui/components/toggle/toggle.ts +218 -0
- package/dist/ui/components/typography/index.ts +5 -0
- package/dist/ui/components/typography/typography.test.ts +55 -0
- package/dist/ui/components/typography/typography.ts +44 -0
- package/dist/ui/theme/dark.css +41 -0
- package/dist/ui/theme/default.css +56 -0
- package/dist/ui/theme/tailwind-plugin.test.ts +124 -0
- package/dist/ui/theme/tailwind-plugin.ts +151 -0
- package/dist/ui/utils/animate.test.ts +324 -0
- package/dist/ui/utils/animate.ts +544 -0
- package/dist/ui/utils/cn.test.ts +27 -0
- package/dist/ui/utils/cn.ts +6 -0
- package/dist/utils/analyze.d.ts +32 -0
- package/dist/utils/analyze.js +109 -0
- package/dist/utils/analyze.js.map +1 -0
- package/dist/utils/analyze.test.d.ts +1 -0
- package/dist/utils/analyze.test.js +146 -0
- package/dist/utils/analyze.test.js.map +1 -0
- package/dist/utils/animate.d.ts +210 -0
- package/dist/utils/animate.js +423 -0
- package/dist/utils/animate.js.map +1 -0
- package/dist/utils/animate.test.d.ts +1 -0
- package/dist/utils/animate.test.js +365 -0
- package/dist/utils/animate.test.js.map +1 -0
- package/dist/utils/banner.d.ts +1 -0
- package/dist/utils/banner.js +15 -0
- package/dist/utils/banner.js.map +1 -0
- package/dist/utils/banner.test.d.ts +1 -0
- package/dist/utils/banner.test.js +29 -0
- package/dist/utils/banner.test.js.map +1 -0
- package/dist/utils/cn.d.ts +2 -0
- package/dist/utils/cn.js +6 -0
- package/dist/utils/cn.js.map +1 -0
- package/dist/utils/cn.test.d.ts +1 -0
- package/dist/utils/cn.test.js +21 -0
- package/dist/utils/cn.test.js.map +1 -0
- package/dist/utils/format.d.ts +31 -0
- package/dist/utils/format.js +95 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/format.test.d.ts +1 -0
- package/dist/utils/format.test.js +71 -0
- package/dist/utils/format.test.js.map +1 -0
- package/dist/utils/logger.d.ts +6 -0
- package/dist/utils/logger.js +16 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/logger.test.d.ts +1 -0
- package/dist/utils/logger.test.js +36 -0
- package/dist/utils/logger.test.js.map +1 -0
- package/dist/utils/resolve-paths.d.ts +12 -0
- package/dist/utils/resolve-paths.js +29 -0
- package/dist/utils/resolve-paths.js.map +1 -0
- package/dist/utils/resolve-paths.test.d.ts +1 -0
- package/dist/utils/resolve-paths.test.js +52 -0
- package/dist/utils/resolve-paths.test.js.map +1 -0
- package/package.json +56 -4
- package/README.md +0 -3
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
export const createFixture = (options = {}) => {
|
|
6
|
+
const dir = join(tmpdir(), `dolan-test-${randomUUID().slice(0, 8)}`);
|
|
7
|
+
const uiDir = join(dir, '__ui_source');
|
|
8
|
+
mkdirSync(dir, { recursive: true });
|
|
9
|
+
mkdirSync(uiDir, { recursive: true });
|
|
10
|
+
if (options.config) {
|
|
11
|
+
writeFileSync(join(dir, 'dolan.config.json'), JSON.stringify(options.config, null, 2));
|
|
12
|
+
// Create the directories referenced in config
|
|
13
|
+
mkdirSync(join(dir, options.config.aliases.components), {
|
|
14
|
+
recursive: true,
|
|
15
|
+
});
|
|
16
|
+
if (options.config.aliases.utils) {
|
|
17
|
+
mkdirSync(join(dir, options.config.aliases.utils), { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
mkdirSync(join(dir, options.config.aliases.theme), { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
if (options.lockfile) {
|
|
22
|
+
writeFileSync(join(dir, 'dolan.lock.json'), JSON.stringify(options.lockfile, null, 2));
|
|
23
|
+
}
|
|
24
|
+
if (options.components && options.config) {
|
|
25
|
+
const componentsDir = join(dir, options.config.aliases.components);
|
|
26
|
+
for (const [name, files] of Object.entries(options.components)) {
|
|
27
|
+
const componentDir = join(componentsDir, name);
|
|
28
|
+
mkdirSync(componentDir, { recursive: true });
|
|
29
|
+
for (const [fileName, content] of Object.entries(files)) {
|
|
30
|
+
writeFileSync(join(componentDir, fileName), content);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (options.uiSource) {
|
|
35
|
+
const componentsDir = join(uiDir, 'components');
|
|
36
|
+
mkdirSync(componentsDir, { recursive: true });
|
|
37
|
+
for (const [name, files] of Object.entries(options.uiSource)) {
|
|
38
|
+
const componentDir = join(componentsDir, name);
|
|
39
|
+
mkdirSync(componentDir, { recursive: true });
|
|
40
|
+
for (const [fileName, content] of Object.entries(files)) {
|
|
41
|
+
writeFileSync(join(componentDir, fileName), content);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (options.themeFiles) {
|
|
46
|
+
const themeDir = join(uiDir, 'theme');
|
|
47
|
+
mkdirSync(themeDir, { recursive: true });
|
|
48
|
+
for (const [fileName, content] of Object.entries(options.themeFiles)) {
|
|
49
|
+
writeFileSync(join(themeDir, fileName), content);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
dir,
|
|
54
|
+
uiDir,
|
|
55
|
+
cleanup: () => rmSync(dir, { recursive: true, force: true }),
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
export const DEFAULT_CONFIG = {
|
|
59
|
+
aliases: {
|
|
60
|
+
components: 'src/components/ui',
|
|
61
|
+
utils: 'src/lib/utils',
|
|
62
|
+
theme: 'src/styles',
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
//# sourceMappingURL=test-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-utils.js","sourceRoot":"","sources":["../src/test-utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { Component, signal } from '@angular/core';
|
|
2
|
+
import { render, waitForUpdate } from '@blotch/angular-lynx-testing-library';
|
|
3
|
+
import { LYNX_ELEMENTS } from '@blotch/angular-lynx';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
import { setInputSignal } from '../../../test-utils/set-input-signal';
|
|
6
|
+
import {
|
|
7
|
+
UiAccordion,
|
|
8
|
+
UiAccordionContent,
|
|
9
|
+
UiAccordionItem,
|
|
10
|
+
UiAccordionTrigger,
|
|
11
|
+
} from './accordion';
|
|
12
|
+
|
|
13
|
+
@Component({
|
|
14
|
+
standalone: true,
|
|
15
|
+
imports: [
|
|
16
|
+
UiAccordion,
|
|
17
|
+
UiAccordionItem,
|
|
18
|
+
UiAccordionTrigger,
|
|
19
|
+
UiAccordionContent,
|
|
20
|
+
LYNX_ELEMENTS,
|
|
21
|
+
],
|
|
22
|
+
template: `
|
|
23
|
+
<ui-accordion [type]="type">
|
|
24
|
+
<ui-accordion-item value="a">
|
|
25
|
+
<ui-accordion-trigger>A</ui-accordion-trigger>
|
|
26
|
+
<ui-accordion-content><text>Alpha</text></ui-accordion-content>
|
|
27
|
+
</ui-accordion-item>
|
|
28
|
+
<ui-accordion-item value="b">
|
|
29
|
+
<ui-accordion-trigger>B</ui-accordion-trigger>
|
|
30
|
+
<ui-accordion-content><text>Beta</text></ui-accordion-content>
|
|
31
|
+
</ui-accordion-item>
|
|
32
|
+
</ui-accordion>
|
|
33
|
+
`,
|
|
34
|
+
})
|
|
35
|
+
class AccordionHost {
|
|
36
|
+
type: 'single' | 'multiple' = 'single';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe('UiAccordion', () => {
|
|
40
|
+
it('renders each item trigger', async () => {
|
|
41
|
+
const { getByText } = await render(AccordionHost);
|
|
42
|
+
expect(getByText('A')).toBeTruthy();
|
|
43
|
+
expect(getByText('B')).toBeTruthy();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('toggle() in single mode replaces the expanded value', async () => {
|
|
47
|
+
const { componentRef } = await render(UiAccordion);
|
|
48
|
+
const acc = componentRef.instance as UiAccordion;
|
|
49
|
+
acc.toggle('a');
|
|
50
|
+
expect(acc.isExpanded('a')).toBe(true);
|
|
51
|
+
acc.toggle('b');
|
|
52
|
+
expect(acc.isExpanded('a')).toBe(false);
|
|
53
|
+
expect(acc.isExpanded('b')).toBe(true);
|
|
54
|
+
acc.toggle('b');
|
|
55
|
+
expect(acc.isExpanded('b')).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('renders type="multiple" host without throwing', async () => {
|
|
59
|
+
@Component({
|
|
60
|
+
standalone: true,
|
|
61
|
+
imports: [UiAccordion, LYNX_ELEMENTS],
|
|
62
|
+
template: `<ui-accordion type="multiple" />`,
|
|
63
|
+
})
|
|
64
|
+
class MultiHost {}
|
|
65
|
+
const { container } = await render(MultiHost);
|
|
66
|
+
expect(container).toBeTruthy();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('type="multiple" retains previously expanded items', async () => {
|
|
70
|
+
// Directly exercises the multiple-mode branch of `toggle()`. Without this,
|
|
71
|
+
// the `.clear()`-skip branch stays uncovered.
|
|
72
|
+
const { componentRef } = await render(UiAccordion);
|
|
73
|
+
const acc = componentRef.instance as UiAccordion;
|
|
74
|
+
setInputSignal(acc.type, 'multiple');
|
|
75
|
+
await waitForUpdate();
|
|
76
|
+
acc.toggle('a');
|
|
77
|
+
acc.toggle('b');
|
|
78
|
+
expect(acc.isExpanded('a')).toBe(true);
|
|
79
|
+
expect(acc.isExpanded('b')).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('UiAccordionItem / Trigger / Content', () => {
|
|
84
|
+
/**
|
|
85
|
+
* Render UiAccordionItem directly with a UiAccordion instance provided via
|
|
86
|
+
* DI. Using `render(UiAccordionItem, { providers: [...] })` alone would fail
|
|
87
|
+
* because the required `value` input has no template binding and the
|
|
88
|
+
* component's initial computed reads it — so we set the input before any
|
|
89
|
+
* effect fires. The parent instance comes from rendering UiAccordion (which
|
|
90
|
+
* sets up the internal injection context inputs) and stashing its instance.
|
|
91
|
+
*/
|
|
92
|
+
const renderItemWithParent = async () => {
|
|
93
|
+
// Render the accordion first so we have an instance with initialized signals.
|
|
94
|
+
const parentResult = await render(UiAccordion);
|
|
95
|
+
const parent = parentResult.componentRef.instance as UiAccordion;
|
|
96
|
+
|
|
97
|
+
// Now render the child with the parent as a DI value. This unmounts the
|
|
98
|
+
// previous render but the parent instance object (a plain JS class) still
|
|
99
|
+
// holds its signals.
|
|
100
|
+
const childResult = await render(UiAccordionItem, {
|
|
101
|
+
providers: [{ provide: UiAccordion, useValue: parent }],
|
|
102
|
+
});
|
|
103
|
+
const item = childResult.componentRef.instance as UiAccordionItem;
|
|
104
|
+
setInputSignal(item.itemValue, 'a');
|
|
105
|
+
await waitForUpdate();
|
|
106
|
+
return { parent, item, result: childResult };
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
it('item.toggle() delegates to the parent accordion', async () => {
|
|
110
|
+
const { parent, item } = await renderItemWithParent();
|
|
111
|
+
item.toggle();
|
|
112
|
+
expect(parent.isExpanded('a')).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('trigger onTap toggles the parent item', async () => {
|
|
116
|
+
// The trigger injects UiAccordionItem (not UiAccordion) — same pattern.
|
|
117
|
+
const parentResult = await render(UiAccordion);
|
|
118
|
+
const parent = parentResult.componentRef.instance as UiAccordion;
|
|
119
|
+
|
|
120
|
+
const itemResult = await render(UiAccordionItem, {
|
|
121
|
+
providers: [{ provide: UiAccordion, useValue: parent }],
|
|
122
|
+
});
|
|
123
|
+
const item = itemResult.componentRef.instance as UiAccordionItem;
|
|
124
|
+
setInputSignal(item.itemValue, 'a');
|
|
125
|
+
await waitForUpdate();
|
|
126
|
+
|
|
127
|
+
const triggerResult = await render(UiAccordionTrigger, {
|
|
128
|
+
providers: [{ provide: UiAccordionItem, useValue: item }],
|
|
129
|
+
});
|
|
130
|
+
const trigger = triggerResult.componentRef.instance as UiAccordionTrigger;
|
|
131
|
+
(trigger as unknown as { onTap: () => void }).onTap();
|
|
132
|
+
expect(parent.isExpanded('a')).toBe(true);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe('content effect', () => {
|
|
136
|
+
it('runs the reveal animation after expansion via setTimeout', async () => {
|
|
137
|
+
const parentResult = await render(UiAccordion);
|
|
138
|
+
const parent = parentResult.componentRef.instance as UiAccordion;
|
|
139
|
+
|
|
140
|
+
const itemResult = await render(UiAccordionItem, {
|
|
141
|
+
providers: [{ provide: UiAccordion, useValue: parent }],
|
|
142
|
+
});
|
|
143
|
+
const item = itemResult.componentRef.instance as UiAccordionItem;
|
|
144
|
+
setInputSignal(item.itemValue, 'a');
|
|
145
|
+
await waitForUpdate();
|
|
146
|
+
|
|
147
|
+
const contentResult = await render(UiAccordionContent, {
|
|
148
|
+
providers: [{ provide: UiAccordionItem, useValue: item }],
|
|
149
|
+
});
|
|
150
|
+
const content = contentResult.componentRef.instance as UiAccordionContent;
|
|
151
|
+
// JIT + jsdom doesn't populate the `#content` viewChild — patch it with a
|
|
152
|
+
// signal returning the outer view so the setTimeout body's `el` check
|
|
153
|
+
// passes and revealIn/cancel actually run.
|
|
154
|
+
const view = contentResult.container.querySelector('view') as Element;
|
|
155
|
+
(content as unknown as { contentRef: unknown }).contentRef = signal({
|
|
156
|
+
nativeElement: view,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
parent.toggle('a');
|
|
160
|
+
await waitForUpdate();
|
|
161
|
+
await waitForUpdate(); // wait for the effect's setTimeout(_, 0) to fire
|
|
162
|
+
|
|
163
|
+
// Cancellation path: toggle off then on to re-schedule with `#anim` set.
|
|
164
|
+
parent.toggle('a');
|
|
165
|
+
await waitForUpdate();
|
|
166
|
+
parent.toggle('a');
|
|
167
|
+
await waitForUpdate();
|
|
168
|
+
await waitForUpdate();
|
|
169
|
+
expect(parent.isExpanded('a')).toBe(true);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('returns early inside setTimeout when contentRef is undefined', async () => {
|
|
173
|
+
// Covers the `if (!el) return;` true branch — no monkey-patch here, so
|
|
174
|
+
// the viewChild is unresolved and the setTimeout body bails out.
|
|
175
|
+
const parentResult = await render(UiAccordion);
|
|
176
|
+
const parent = parentResult.componentRef.instance as UiAccordion;
|
|
177
|
+
|
|
178
|
+
const itemResult = await render(UiAccordionItem, {
|
|
179
|
+
providers: [{ provide: UiAccordion, useValue: parent }],
|
|
180
|
+
});
|
|
181
|
+
const item = itemResult.componentRef.instance as UiAccordionItem;
|
|
182
|
+
setInputSignal(item.itemValue, 'a');
|
|
183
|
+
await waitForUpdate();
|
|
184
|
+
|
|
185
|
+
await render(UiAccordionContent, {
|
|
186
|
+
providers: [{ provide: UiAccordionItem, useValue: item }],
|
|
187
|
+
});
|
|
188
|
+
parent.toggle('a');
|
|
189
|
+
await waitForUpdate();
|
|
190
|
+
await waitForUpdate();
|
|
191
|
+
expect(parent.isExpanded('a')).toBe(true);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
});
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ElementRef,
|
|
3
|
+
Component,
|
|
4
|
+
ViewEncapsulation,
|
|
5
|
+
computed,
|
|
6
|
+
effect,
|
|
7
|
+
inject,
|
|
8
|
+
input,
|
|
9
|
+
signal,
|
|
10
|
+
viewChild,
|
|
11
|
+
} from '@angular/core';
|
|
12
|
+
import { LYNX_ELEMENTS } from '@blotch/angular-lynx';
|
|
13
|
+
|
|
14
|
+
import { type AnimationHandle, revealIn } from '../../utils/animate';
|
|
15
|
+
import { cn } from '../../utils/cn';
|
|
16
|
+
|
|
17
|
+
@Component({
|
|
18
|
+
selector: 'ui-accordion',
|
|
19
|
+
standalone: true,
|
|
20
|
+
imports: [LYNX_ELEMENTS],
|
|
21
|
+
encapsulation: ViewEncapsulation.None,
|
|
22
|
+
template: `
|
|
23
|
+
<view [class]="containerClass()">
|
|
24
|
+
<ng-content />
|
|
25
|
+
</view>
|
|
26
|
+
`,
|
|
27
|
+
})
|
|
28
|
+
export class UiAccordion {
|
|
29
|
+
readonly type = input<'single' | 'multiple'>('single');
|
|
30
|
+
readonly userClass = input<string>('', { alias: 'class' });
|
|
31
|
+
|
|
32
|
+
readonly expandedItems = signal<Set<string>>(new Set());
|
|
33
|
+
|
|
34
|
+
protected readonly containerClass = computed(() =>
|
|
35
|
+
cn('flex-col flex', this.userClass()),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
toggle(value: string): void {
|
|
39
|
+
// `new Set(current)` creates a fresh Set on every toggle. Angular signals
|
|
40
|
+
// use reference equality — mutating the existing Set in-place wouldn't
|
|
41
|
+
// trigger change detection since the signal value (the Set reference) stays
|
|
42
|
+
// the same. A new Set forces the signal to see a changed reference.
|
|
43
|
+
const current = new Set(this.expandedItems());
|
|
44
|
+
if (current.has(value)) {
|
|
45
|
+
current.delete(value);
|
|
46
|
+
} else {
|
|
47
|
+
if (this.type() === 'single') {
|
|
48
|
+
current.clear();
|
|
49
|
+
}
|
|
50
|
+
current.add(value);
|
|
51
|
+
}
|
|
52
|
+
this.expandedItems.set(current);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
isExpanded(value: string): boolean {
|
|
56
|
+
return this.expandedItems().has(value);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@Component({
|
|
61
|
+
selector: 'ui-accordion-item',
|
|
62
|
+
standalone: true,
|
|
63
|
+
imports: [LYNX_ELEMENTS],
|
|
64
|
+
encapsulation: ViewEncapsulation.None,
|
|
65
|
+
template: `
|
|
66
|
+
<view [class]="itemClass()">
|
|
67
|
+
<ng-content />
|
|
68
|
+
</view>
|
|
69
|
+
`,
|
|
70
|
+
})
|
|
71
|
+
export class UiAccordionItem {
|
|
72
|
+
readonly #accordion = inject(UiAccordion);
|
|
73
|
+
|
|
74
|
+
readonly itemValue = input.required<string>({ alias: 'value' });
|
|
75
|
+
readonly userClass = input<string>('', { alias: 'class' });
|
|
76
|
+
|
|
77
|
+
readonly isExpanded = computed(() =>
|
|
78
|
+
this.#accordion.expandedItems().has(this.itemValue()),
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
protected readonly itemClass = computed(() =>
|
|
82
|
+
cn('flex-col border-b border-border flex', this.userClass()),
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
toggle(): void {
|
|
86
|
+
this.#accordion.toggle(this.itemValue());
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@Component({
|
|
91
|
+
selector: 'ui-accordion-trigger',
|
|
92
|
+
standalone: true,
|
|
93
|
+
imports: [LYNX_ELEMENTS],
|
|
94
|
+
encapsulation: ViewEncapsulation.None,
|
|
95
|
+
template: `
|
|
96
|
+
<view [class]="triggerClass()" (bindtap)="onTap()">
|
|
97
|
+
<text [class]="textClass()"><ng-content /></text>
|
|
98
|
+
<text [class]="chevronClass()">{{ item.isExpanded() ? '▼' : '▶' }}</text>
|
|
99
|
+
</view>
|
|
100
|
+
`,
|
|
101
|
+
})
|
|
102
|
+
export class UiAccordionTrigger {
|
|
103
|
+
protected readonly item = inject(UiAccordionItem);
|
|
104
|
+
readonly userClass = input<string>('', { alias: 'class' });
|
|
105
|
+
|
|
106
|
+
protected readonly triggerClass = computed(() =>
|
|
107
|
+
cn('flex-row items-center py-4 flex justify-between', this.userClass()),
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
protected readonly textClass = computed(() =>
|
|
111
|
+
cn('text-sm font-medium text-foreground'),
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
protected readonly chevronClass = computed(() =>
|
|
115
|
+
cn('text-xs text-muted-foreground'),
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
protected onTap(): void {
|
|
119
|
+
this.item.toggle();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@Component({
|
|
124
|
+
selector: 'ui-accordion-content',
|
|
125
|
+
standalone: true,
|
|
126
|
+
imports: [LYNX_ELEMENTS],
|
|
127
|
+
encapsulation: ViewEncapsulation.None,
|
|
128
|
+
// Mount the content only while expanded, via `@if`. On Lynx this works because
|
|
129
|
+
// the renderer recreates a destroyed element's native painting node when it is
|
|
130
|
+
// re-inserted (see LynxElement.#recreateSubtree in @blotch/angular-lynx): when
|
|
131
|
+
// the panel re-expands, the projected `<ng-content>` — recycled with the
|
|
132
|
+
// re-created wrapper — is rebuilt from cache and renders again, cycle after
|
|
133
|
+
// cycle. The inline opacity/transform is the resting state; revealIn animates
|
|
134
|
+
// from opacity:0 / translateY(-8px) with fill:'none' so the inline style takes
|
|
135
|
+
// over once the animation ends.
|
|
136
|
+
template: `
|
|
137
|
+
@if (item.isExpanded()) {
|
|
138
|
+
<view
|
|
139
|
+
#content
|
|
140
|
+
[class]="contentClass()"
|
|
141
|
+
style="opacity: 1; transform: translateY(0);"
|
|
142
|
+
>
|
|
143
|
+
<ng-content />
|
|
144
|
+
</view>
|
|
145
|
+
}
|
|
146
|
+
`,
|
|
147
|
+
})
|
|
148
|
+
export class UiAccordionContent {
|
|
149
|
+
protected readonly item = inject(UiAccordionItem);
|
|
150
|
+
readonly userClass = input<string>('', { alias: 'class' });
|
|
151
|
+
|
|
152
|
+
readonly contentRef = viewChild<ElementRef>('content');
|
|
153
|
+
#anim?: AnimationHandle;
|
|
154
|
+
|
|
155
|
+
constructor() {
|
|
156
|
+
// Animate the reveal whenever the panel expands. contentRef only resolves
|
|
157
|
+
// while the `@if` content is mounted; defer one macrotask so the freshly
|
|
158
|
+
// (re)created element is committed to the native tree before we animate it.
|
|
159
|
+
effect(() => {
|
|
160
|
+
if (!this.item.isExpanded()) return;
|
|
161
|
+
setTimeout(() => {
|
|
162
|
+
const el = this.contentRef()?.nativeElement;
|
|
163
|
+
if (!el) return;
|
|
164
|
+
this.#anim?.cancel();
|
|
165
|
+
this.#anim = revealIn(el, { fromY: -8, fill: 'none' });
|
|
166
|
+
}, 0);
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
protected readonly contentClass = computed(() =>
|
|
171
|
+
cn('flex-col pb-4 flex', this.userClass()),
|
|
172
|
+
);
|
|
173
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { Component, signal } from '@angular/core';
|
|
2
|
+
import { render, waitForUpdate } from '@blotch/angular-lynx-testing-library';
|
|
3
|
+
import { LYNX_ELEMENTS } from '@blotch/angular-lynx';
|
|
4
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import { setInputSignal } from '../../../test-utils/set-input-signal';
|
|
6
|
+
import {
|
|
7
|
+
UiActionSheet,
|
|
8
|
+
UiActionSheetCancel,
|
|
9
|
+
UiActionSheetItem,
|
|
10
|
+
UiActionSheetTitle,
|
|
11
|
+
} from './action-sheet';
|
|
12
|
+
|
|
13
|
+
@Component({
|
|
14
|
+
standalone: true,
|
|
15
|
+
imports: [
|
|
16
|
+
UiActionSheet,
|
|
17
|
+
UiActionSheetTitle,
|
|
18
|
+
UiActionSheetItem,
|
|
19
|
+
UiActionSheetCancel,
|
|
20
|
+
LYNX_ELEMENTS,
|
|
21
|
+
],
|
|
22
|
+
template: `
|
|
23
|
+
<ui-action-sheet [(open)]="open">
|
|
24
|
+
<ui-action-sheet-title>Pick</ui-action-sheet-title>
|
|
25
|
+
<ui-action-sheet-item>Option</ui-action-sheet-item>
|
|
26
|
+
<ui-action-sheet-cancel>Cancel</ui-action-sheet-cancel>
|
|
27
|
+
</ui-action-sheet>
|
|
28
|
+
`,
|
|
29
|
+
})
|
|
30
|
+
class ActionSheetHost {
|
|
31
|
+
open = true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe('UiActionSheet', () => {
|
|
35
|
+
const patchRefs = (inst: UiActionSheet, view: Element) => {
|
|
36
|
+
(inst as unknown as { backdropRef: unknown }).backdropRef = signal({
|
|
37
|
+
nativeElement: view,
|
|
38
|
+
});
|
|
39
|
+
(inst as unknown as { panelRef: unknown }).panelRef = signal({
|
|
40
|
+
nativeElement: view,
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
it('renders the sheet, title, item, and cancel content', async () => {
|
|
45
|
+
const { getByText } = await render(ActionSheetHost);
|
|
46
|
+
expect(getByText('Pick')).toBeTruthy();
|
|
47
|
+
expect(getByText('Option')).toBeTruthy();
|
|
48
|
+
expect(getByText('Cancel')).toBeTruthy();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('backdrop tap closes the sheet', async () => {
|
|
52
|
+
const { componentRef } = await render(UiActionSheet);
|
|
53
|
+
const inst = componentRef.instance as UiActionSheet;
|
|
54
|
+
inst.open.set(true);
|
|
55
|
+
await waitForUpdate();
|
|
56
|
+
(inst as unknown as { onBackdropTap: () => void }).onBackdropTap();
|
|
57
|
+
expect(inst.open()).toBe(false);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('close() sets open to false', async () => {
|
|
61
|
+
const { componentRef } = await render(UiActionSheet);
|
|
62
|
+
const inst = componentRef.instance as UiActionSheet;
|
|
63
|
+
inst.open.set(true);
|
|
64
|
+
inst.close();
|
|
65
|
+
expect(inst.open()).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('panel tap is a no-op', async () => {
|
|
69
|
+
const { componentRef } = await render(UiActionSheet);
|
|
70
|
+
const inst = componentRef.instance as UiActionSheet;
|
|
71
|
+
expect(() =>
|
|
72
|
+
(inst as unknown as { onPanelTap: () => void }).onPanelTap(),
|
|
73
|
+
).not.toThrow();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('open effect runs the animate-in and animate-out lifecycles', async () => {
|
|
77
|
+
const { componentRef, container } = await render(UiActionSheet);
|
|
78
|
+
const inst = componentRef.instance as UiActionSheet;
|
|
79
|
+
const view = container.querySelector('view') as Element;
|
|
80
|
+
patchRefs(inst, view);
|
|
81
|
+
|
|
82
|
+
inst.open.set(true);
|
|
83
|
+
await waitForUpdate();
|
|
84
|
+
await waitForUpdate();
|
|
85
|
+
await waitForUpdate();
|
|
86
|
+
|
|
87
|
+
inst.open.set(false);
|
|
88
|
+
await new Promise((r) => setTimeout(r, 320));
|
|
89
|
+
await waitForUpdate();
|
|
90
|
+
expect(inst.open()).toBe(false);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('open effect bails when backdrop/panel refs are undefined', async () => {
|
|
94
|
+
const { componentRef } = await render(UiActionSheet);
|
|
95
|
+
const inst = componentRef.instance as UiActionSheet;
|
|
96
|
+
inst.open.set(true);
|
|
97
|
+
await waitForUpdate();
|
|
98
|
+
await waitForUpdate();
|
|
99
|
+
inst.open.set(false);
|
|
100
|
+
await waitForUpdate();
|
|
101
|
+
expect(inst.open()).toBe(false);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('applies user class on the panel', async () => {
|
|
105
|
+
const { componentRef, container } = await render(UiActionSheet);
|
|
106
|
+
const inst = componentRef.instance as UiActionSheet;
|
|
107
|
+
setInputSignal(inst.userClass, 'my-panel');
|
|
108
|
+
await waitForUpdate();
|
|
109
|
+
expect(
|
|
110
|
+
Array.from(container.querySelectorAll('view')).some((el) =>
|
|
111
|
+
el.getAttribute('class')?.includes('my-panel'),
|
|
112
|
+
),
|
|
113
|
+
).toBe(true);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('UiActionSheetTitle', () => {
|
|
118
|
+
it('renders projected content', async () => {
|
|
119
|
+
const { container } = await render(UiActionSheetTitle);
|
|
120
|
+
expect(container.querySelector('view')).toBeTruthy();
|
|
121
|
+
expect(container.querySelector('text')).toBeTruthy();
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('UiActionSheetItem', () => {
|
|
126
|
+
const renderItem = async () => {
|
|
127
|
+
const parentResult = await render(UiActionSheet);
|
|
128
|
+
const parent = parentResult.componentRef.instance as UiActionSheet;
|
|
129
|
+
parent.open.set(true);
|
|
130
|
+
await waitForUpdate();
|
|
131
|
+
|
|
132
|
+
const itemResult = await render(UiActionSheetItem, {
|
|
133
|
+
providers: [{ provide: UiActionSheet, useValue: parent }],
|
|
134
|
+
});
|
|
135
|
+
return { parent, itemResult };
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
it('onTap emits `pressed` and closes the parent sheet', async () => {
|
|
139
|
+
const { parent, itemResult } = await renderItem();
|
|
140
|
+
const inst = itemResult.componentRef
|
|
141
|
+
.instance as unknown as UiActionSheetItem & {
|
|
142
|
+
onTap: () => void;
|
|
143
|
+
};
|
|
144
|
+
const emitted = vi.fn();
|
|
145
|
+
inst.pressed.subscribe(emitted);
|
|
146
|
+
inst.onTap();
|
|
147
|
+
expect(emitted).toHaveBeenCalledTimes(1);
|
|
148
|
+
expect(parent.open()).toBe(false);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('press handlers run cleanly', async () => {
|
|
152
|
+
const { itemResult } = await renderItem();
|
|
153
|
+
const inst = itemResult.componentRef.instance as unknown as {
|
|
154
|
+
onPressStart: () => void;
|
|
155
|
+
onPressEnd: () => void;
|
|
156
|
+
onPressCancel: () => void;
|
|
157
|
+
};
|
|
158
|
+
expect(() => inst.onPressStart()).not.toThrow();
|
|
159
|
+
expect(() => inst.onPressEnd()).not.toThrow();
|
|
160
|
+
expect(() => inst.onPressCancel()).not.toThrow();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('applies destructive variant text class', async () => {
|
|
164
|
+
const { itemResult } = await renderItem();
|
|
165
|
+
const inst = itemResult.componentRef.instance as UiActionSheetItem;
|
|
166
|
+
setInputSignal(inst.variant, 'destructive');
|
|
167
|
+
await waitForUpdate();
|
|
168
|
+
const text = itemResult.container.querySelector('text');
|
|
169
|
+
expect(text?.getAttribute('class')).toContain('text-destructive');
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
describe('UiActionSheetCancel', () => {
|
|
174
|
+
const renderCancel = async () => {
|
|
175
|
+
const parentResult = await render(UiActionSheet);
|
|
176
|
+
const parent = parentResult.componentRef.instance as UiActionSheet;
|
|
177
|
+
parent.open.set(true);
|
|
178
|
+
await waitForUpdate();
|
|
179
|
+
|
|
180
|
+
const cancelResult = await render(UiActionSheetCancel, {
|
|
181
|
+
providers: [{ provide: UiActionSheet, useValue: parent }],
|
|
182
|
+
});
|
|
183
|
+
return { parent, cancelResult };
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
it('renders the default cancel label', async () => {
|
|
187
|
+
const { cancelResult } = await renderCancel();
|
|
188
|
+
expect(cancelResult.container.textContent).toContain('Cancel');
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('onTap closes the parent sheet', async () => {
|
|
192
|
+
const { parent, cancelResult } = await renderCancel();
|
|
193
|
+
const inst = cancelResult.componentRef.instance as unknown as {
|
|
194
|
+
onTap: () => void;
|
|
195
|
+
};
|
|
196
|
+
inst.onTap();
|
|
197
|
+
expect(parent.open()).toBe(false);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('press handlers run cleanly', async () => {
|
|
201
|
+
const { cancelResult } = await renderCancel();
|
|
202
|
+
const inst = cancelResult.componentRef.instance as unknown as {
|
|
203
|
+
onPressStart: () => void;
|
|
204
|
+
onPressEnd: () => void;
|
|
205
|
+
onPressCancel: () => void;
|
|
206
|
+
};
|
|
207
|
+
expect(() => inst.onPressStart()).not.toThrow();
|
|
208
|
+
expect(() => inst.onPressEnd()).not.toThrow();
|
|
209
|
+
expect(() => inst.onPressCancel()).not.toThrow();
|
|
210
|
+
});
|
|
211
|
+
});
|