@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,7 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
2
|
import { readFileSync, writeFileSync, existsSync, readdirSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
import { createFixture, DEFAULT_CONFIG } from '../test-utils';
|
|
4
|
+
import { createFixture, DEFAULT_CONFIG, shippedTheme, } from '../test-utils';
|
|
5
5
|
import { hashContent } from '../lockfile';
|
|
6
6
|
const spinnerMock = { start: vi.fn(), stop: vi.fn() };
|
|
7
7
|
vi.mock('@clack/prompts', () => ({
|
|
@@ -20,14 +20,29 @@ vi.mock('@clack/prompts', () => ({
|
|
|
20
20
|
cancel: vi.fn(),
|
|
21
21
|
isCancel: vi.fn().mockReturnValue(false),
|
|
22
22
|
}));
|
|
23
|
+
// Vitest's stdin isn't a terminal, which reads as "nobody can answer". Most
|
|
24
|
+
// tests are about the questions, so they run as if in one.
|
|
25
|
+
let interactive = true;
|
|
26
|
+
vi.mock('../utils/interactive.js', () => ({
|
|
27
|
+
isInteractive: () => interactive,
|
|
28
|
+
}));
|
|
23
29
|
let fixture;
|
|
24
30
|
vi.mock('../utils/resolve-paths.js', () => ({
|
|
25
31
|
getUiSourceDir: () => fixture.uiDir,
|
|
26
32
|
getComponentSourceDir: (name) => join(fixture.uiDir, 'components', name),
|
|
27
|
-
getComponentFiles: (name) => readdirSync(join(fixture.uiDir, 'components', name)).filter((f) => f.endsWith('.ts')),
|
|
33
|
+
getComponentFiles: (name, { tests }) => readdirSync(join(fixture.uiDir, 'components', name)).filter((f) => f.endsWith('.ts') && (tests || !f.endsWith('.test.ts'))),
|
|
28
34
|
rewriteImports: (content) => content,
|
|
29
35
|
}));
|
|
36
|
+
// The install step runs the project's package manager; record calls instead.
|
|
37
|
+
const installComponentPackages = vi.fn();
|
|
38
|
+
vi.mock('./install-packages.js', () => ({
|
|
39
|
+
installComponentPackages: (...args) => installComponentPackages(...args),
|
|
40
|
+
}));
|
|
30
41
|
beforeEach(() => {
|
|
42
|
+
interactive = true;
|
|
43
|
+
// Each test sees only its own calls. Without this, an assertion could pass
|
|
44
|
+
// on a message an earlier test logged.
|
|
45
|
+
vi.clearAllMocks();
|
|
31
46
|
vi.spyOn(process, 'exit').mockImplementation((code) => {
|
|
32
47
|
throw new Error(`process.exit(${code})`);
|
|
33
48
|
});
|
|
@@ -91,28 +106,28 @@ describe('updateCommand --selective', () => {
|
|
|
91
106
|
it('handles mixed decisions across multiple files', async () => {
|
|
92
107
|
const cardOriginal = 'export const Card = { v: 1 };';
|
|
93
108
|
const cardUpstream = 'export const Card = { v: 2 };';
|
|
94
|
-
const
|
|
95
|
-
const
|
|
109
|
+
const skeletonOriginal = 'export const Skeleton = { v: 1 };';
|
|
110
|
+
const skeletonUpstream = 'export const Skeleton = { v: 2 };';
|
|
96
111
|
const cardHash = hashContent(cardOriginal);
|
|
97
|
-
const
|
|
112
|
+
const skeletonHash = hashContent(skeletonOriginal);
|
|
98
113
|
fixture = createFixture({
|
|
99
114
|
config: DEFAULT_CONFIG,
|
|
100
115
|
lockfile: {
|
|
101
116
|
version: 1,
|
|
102
117
|
components: {
|
|
103
118
|
card: { 'card.ts': { hash: cardHash } },
|
|
104
|
-
|
|
119
|
+
skeleton: { 'skeleton.ts': { hash: skeletonHash } },
|
|
105
120
|
},
|
|
106
121
|
utils: {},
|
|
107
122
|
theme: {},
|
|
108
123
|
},
|
|
109
124
|
components: {
|
|
110
125
|
card: { 'card.ts': cardOriginal },
|
|
111
|
-
|
|
126
|
+
skeleton: { 'skeleton.ts': skeletonOriginal },
|
|
112
127
|
},
|
|
113
128
|
uiSource: {
|
|
114
129
|
card: { 'card.ts': cardUpstream },
|
|
115
|
-
|
|
130
|
+
skeleton: { 'skeleton.ts': skeletonUpstream },
|
|
116
131
|
},
|
|
117
132
|
});
|
|
118
133
|
vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
|
|
@@ -124,11 +139,11 @@ describe('updateCommand --selective', () => {
|
|
|
124
139
|
await updateCommand({ selective: true });
|
|
125
140
|
// Card should be updated (first select → upstream)
|
|
126
141
|
expect(readInstalledFile(fixture.dir, 'card', 'card.ts')).toBe(cardUpstream);
|
|
127
|
-
//
|
|
128
|
-
expect(readInstalledFile(fixture.dir, '
|
|
142
|
+
// Skeleton should be skipped (second select → keep)
|
|
143
|
+
expect(readInstalledFile(fixture.dir, 'skeleton', 'skeleton.ts')).toBe(skeletonOriginal);
|
|
129
144
|
const lockfile = readLockfile(fixture.dir);
|
|
130
145
|
expect(lockfile.components.card['card.ts'].hash).toBe(hashContent(cardUpstream));
|
|
131
|
-
expect(lockfile.components.
|
|
146
|
+
expect(lockfile.components.skeleton['skeleton.ts'].hash).toBe(skeletonHash);
|
|
132
147
|
});
|
|
133
148
|
it('does not create new-upstream file when user chooses skip', async () => {
|
|
134
149
|
const upstream = 'export const Card = {};';
|
|
@@ -136,7 +151,7 @@ describe('updateCommand --selective', () => {
|
|
|
136
151
|
config: DEFAULT_CONFIG,
|
|
137
152
|
lockfile: {
|
|
138
153
|
version: 1,
|
|
139
|
-
components: {},
|
|
154
|
+
components: { card: {} },
|
|
140
155
|
utils: {},
|
|
141
156
|
theme: {},
|
|
142
157
|
},
|
|
@@ -169,7 +184,6 @@ describe('updateCommand --selective', () => {
|
|
|
169
184
|
});
|
|
170
185
|
vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
|
|
171
186
|
const p = await import('@clack/prompts');
|
|
172
|
-
p.select.mockClear();
|
|
173
187
|
const { updateCommand } = await import('./update');
|
|
174
188
|
await updateCommand({ force: true, selective: true });
|
|
175
189
|
expect(readInstalledFile(fixture.dir, 'card', 'card.ts')).toBe(upstream);
|
|
@@ -219,7 +233,6 @@ describe('updateCommand --selective', () => {
|
|
|
219
233
|
vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
|
|
220
234
|
const p = await import('@clack/prompts');
|
|
221
235
|
p.confirm.mockResolvedValue(true);
|
|
222
|
-
p.select.mockClear();
|
|
223
236
|
const { updateCommand } = await import('./update');
|
|
224
237
|
await updateCommand({});
|
|
225
238
|
expect(p.confirm).toHaveBeenCalled();
|
|
@@ -306,8 +319,6 @@ describe('updateCommand --force', () => {
|
|
|
306
319
|
});
|
|
307
320
|
vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
|
|
308
321
|
const p = await import('@clack/prompts');
|
|
309
|
-
p.select.mockClear();
|
|
310
|
-
p.confirm.mockClear();
|
|
311
322
|
const { updateCommand } = await import('./update');
|
|
312
323
|
await updateCommand({ force: true });
|
|
313
324
|
// Local edit discarded, file reset to upstream.
|
|
@@ -337,7 +348,6 @@ describe('updateCommand --force', () => {
|
|
|
337
348
|
});
|
|
338
349
|
vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
|
|
339
350
|
const p = await import('@clack/prompts');
|
|
340
|
-
p.select.mockClear();
|
|
341
351
|
const { updateCommand } = await import('./update');
|
|
342
352
|
await updateCommand({ force: true });
|
|
343
353
|
expect(readInstalledFile(fixture.dir, 'card', 'card.ts')).toBe(upstream);
|
|
@@ -345,14 +355,14 @@ describe('updateCommand --force', () => {
|
|
|
345
355
|
const lockfile = readLockfile(fixture.dir);
|
|
346
356
|
expect(lockfile.components.card['card.ts'].hash).toBe(hashContent(upstream));
|
|
347
357
|
});
|
|
348
|
-
it('overwrites a drifted file with no lockfile
|
|
358
|
+
it('overwrites a drifted file with no lockfile hash (unknown base → conflict)', async () => {
|
|
349
359
|
// With no stored hash the base is unknown, so any local difference is
|
|
350
360
|
// treated as a conflict. --force must still overwrite it.
|
|
351
361
|
const userVersion = 'export const Card = { v: 1, local: true };';
|
|
352
362
|
const upstream = 'export const Card = { v: 2 };';
|
|
353
363
|
fixture = createFixture({
|
|
354
364
|
config: DEFAULT_CONFIG,
|
|
355
|
-
lockfile: { version: 1, components: {}, utils: {}, theme: {} },
|
|
365
|
+
lockfile: { version: 1, components: { card: {} }, utils: {}, theme: {} },
|
|
356
366
|
components: { card: { 'card.ts': userVersion } },
|
|
357
367
|
uiSource: { card: { 'card.ts': upstream } },
|
|
358
368
|
});
|
|
@@ -477,25 +487,25 @@ describe('updateCommand --force', () => {
|
|
|
477
487
|
version: 1,
|
|
478
488
|
components: {
|
|
479
489
|
card: { 'card.ts': { hash: hashContent('card-base') } },
|
|
480
|
-
|
|
490
|
+
skeleton: { 'skeleton.ts': { hash: hashContent('skeleton-base') } },
|
|
481
491
|
},
|
|
482
492
|
utils: {},
|
|
483
493
|
theme: {},
|
|
484
494
|
},
|
|
485
495
|
components: {
|
|
486
496
|
card: { 'card.ts': 'card-local' },
|
|
487
|
-
|
|
497
|
+
skeleton: { 'skeleton.ts': 'skeleton-local' },
|
|
488
498
|
},
|
|
489
499
|
uiSource: {
|
|
490
500
|
card: { 'card.ts': 'card-upstream' },
|
|
491
|
-
|
|
501
|
+
skeleton: { 'skeleton.ts': 'skeleton-upstream' },
|
|
492
502
|
},
|
|
493
503
|
});
|
|
494
504
|
vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
|
|
495
505
|
const { updateCommand } = await import('./update');
|
|
496
506
|
await updateCommand({ force: true });
|
|
497
507
|
expect(readInstalledFile(fixture.dir, 'card', 'card.ts')).toBe('card-upstream');
|
|
498
|
-
expect(readInstalledFile(fixture.dir, '
|
|
508
|
+
expect(readInstalledFile(fixture.dir, 'skeleton', 'skeleton.ts')).toBe('skeleton-upstream');
|
|
499
509
|
});
|
|
500
510
|
it('does not delete untracked local files sitting alongside components', async () => {
|
|
501
511
|
// Force overwrites tracked files but must not touch files the registry
|
|
@@ -606,6 +616,26 @@ describe('updateCommand --force', () => {
|
|
|
606
616
|
const lockfile = readLockfile(fixture.dir);
|
|
607
617
|
expect(lockfile.theme['default.css'].hash).toBe(hashContent(upstream));
|
|
608
618
|
});
|
|
619
|
+
it("adds the Tailwind plugin's test once the project has the testing library", async () => {
|
|
620
|
+
const pluginTest = "import { blotchPlugin } from './tailwind-plugin';";
|
|
621
|
+
fixture = createFixture({
|
|
622
|
+
config: DEFAULT_CONFIG,
|
|
623
|
+
lockfile: { version: 1, components: {}, utils: {}, theme: {} },
|
|
624
|
+
themeFiles: { 'tailwind-plugin.test.ts': pluginTest },
|
|
625
|
+
});
|
|
626
|
+
vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
|
|
627
|
+
const { updateCommand } = await import('./update');
|
|
628
|
+
await updateCommand({ force: true });
|
|
629
|
+
expect(existsSync(join(fixture.dir, DEFAULT_CONFIG.aliases.theme, 'tailwind-plugin.test.ts'))).toBe(false);
|
|
630
|
+
writeFileSync(join(fixture.dir, 'package.json'), JSON.stringify({
|
|
631
|
+
devDependencies: { '@blotch/angular-lynx-testing-library': '^22.2.3' },
|
|
632
|
+
}));
|
|
633
|
+
await updateCommand({ force: true });
|
|
634
|
+
expect(readTheme(fixture, 'tailwind-plugin.test.ts')).toBe(pluginTest);
|
|
635
|
+
expect(readLockfile(fixture.dir).theme['tailwind-plugin.test.ts']).toEqual({
|
|
636
|
+
hash: hashContent(pluginTest),
|
|
637
|
+
});
|
|
638
|
+
});
|
|
609
639
|
it('never prompts even when conflicts, user-modifications and new files coexist', async () => {
|
|
610
640
|
fixture = createFixture({
|
|
611
641
|
config: DEFAULT_CONFIG,
|
|
@@ -635,8 +665,6 @@ describe('updateCommand --force', () => {
|
|
|
635
665
|
seedTheme(fixture, 'default.css', 'theme-local'); // theme conflict
|
|
636
666
|
vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
|
|
637
667
|
const p = await import('@clack/prompts');
|
|
638
|
-
p.select.mockClear();
|
|
639
|
-
p.confirm.mockClear();
|
|
640
668
|
const { updateCommand } = await import('./update');
|
|
641
669
|
await updateCommand({ force: true });
|
|
642
670
|
expect(p.select).not.toHaveBeenCalled();
|
|
@@ -781,8 +809,6 @@ describe('updateCommand — config / no-op paths', () => {
|
|
|
781
809
|
});
|
|
782
810
|
vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
|
|
783
811
|
const p = await import('@clack/prompts');
|
|
784
|
-
p.confirm.mockClear();
|
|
785
|
-
p.select.mockClear();
|
|
786
812
|
const { updateCommand } = await import('./update');
|
|
787
813
|
await updateCommand({});
|
|
788
814
|
// Nothing prompts — a lone user-modified file with no force/selective is
|
|
@@ -874,7 +900,6 @@ describe('updateCommand — config / no-op paths', () => {
|
|
|
874
900
|
vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
|
|
875
901
|
const p = await import('@clack/prompts');
|
|
876
902
|
p.confirm.mockResolvedValueOnce(true);
|
|
877
|
-
p.select.mockClear();
|
|
878
903
|
const { updateCommand } = await import('./update');
|
|
879
904
|
await updateCommand({});
|
|
880
905
|
expect(readInstalledFile(fixture.dir, 'card', 'card.ts')).toBe(cardUpstream);
|
|
@@ -888,15 +913,15 @@ describe('updateCommand — config / no-op paths', () => {
|
|
|
888
913
|
// The already up-to-date theme file's hash is still recorded.
|
|
889
914
|
expect(lockfile.theme['default.css'].hash).toBe(hashContent(themeContent));
|
|
890
915
|
});
|
|
891
|
-
it('advances the lockfile for an up-to-date component and theme file with no
|
|
892
|
-
//
|
|
893
|
-
//
|
|
894
|
-
//
|
|
916
|
+
it('advances the lockfile for an up-to-date component with no file hashes and a theme file with no entry', async () => {
|
|
917
|
+
// The component is tracked without file hashes, and the theme file has
|
|
918
|
+
// no lockfile record at all — buildLockfile() must create fresh entries
|
|
919
|
+
// for both on the "nothing to do" fast path.
|
|
895
920
|
const cardContent = 'export const Card = {};';
|
|
896
921
|
const themeContent = ':root { --primary: rgba(1, 1, 1, 1); }';
|
|
897
922
|
fixture = createFixture({
|
|
898
923
|
config: DEFAULT_CONFIG,
|
|
899
|
-
lockfile: { version: 1, components: {}, utils: {}, theme: {} },
|
|
924
|
+
lockfile: { version: 1, components: { card: {} }, utils: {}, theme: {} },
|
|
900
925
|
components: { card: { 'card.ts': cardContent } },
|
|
901
926
|
uiSource: { card: { 'card.ts': cardContent } },
|
|
902
927
|
themeFiles: { 'default.css': themeContent },
|
|
@@ -1230,4 +1255,533 @@ describe('updateCommand — selective review of shared theme files', () => {
|
|
|
1230
1255
|
expect(lockfile.theme['default.css'].hash).toBe(hashContent(upstream));
|
|
1231
1256
|
});
|
|
1232
1257
|
});
|
|
1258
|
+
describe('updateCommand dependencies', () => {
|
|
1259
|
+
it("adds a dependency a component's new version needs, and its packages", async () => {
|
|
1260
|
+
// line-chart used to stand alone; upstream it now builds on chart, which
|
|
1261
|
+
// the project doesn't have yet.
|
|
1262
|
+
const lineChart = "import { UiChart } from '../chart/chart';";
|
|
1263
|
+
fixture = createFixture({
|
|
1264
|
+
config: DEFAULT_CONFIG,
|
|
1265
|
+
lockfile: {
|
|
1266
|
+
version: 1,
|
|
1267
|
+
components: {
|
|
1268
|
+
'line-chart': { 'line-chart.ts': { hash: hashContent('old') } },
|
|
1269
|
+
},
|
|
1270
|
+
utils: {},
|
|
1271
|
+
theme: {},
|
|
1272
|
+
},
|
|
1273
|
+
components: { 'line-chart': { 'line-chart.ts': 'old' } },
|
|
1274
|
+
uiSource: {
|
|
1275
|
+
'line-chart': { 'line-chart.ts': lineChart },
|
|
1276
|
+
chart: { 'chart.ts': 'export const UiChart = {};' },
|
|
1277
|
+
},
|
|
1278
|
+
});
|
|
1279
|
+
vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
|
|
1280
|
+
const { updateCommand } = await import('./update');
|
|
1281
|
+
await updateCommand({ force: true });
|
|
1282
|
+
expect(readInstalledFile(fixture.dir, 'chart', 'chart.ts')).toBe('export const UiChart = {};');
|
|
1283
|
+
expect(readInstalledFile(fixture.dir, 'line-chart', 'line-chart.ts')).toBe(lineChart);
|
|
1284
|
+
expect(installComponentPackages).toHaveBeenCalledWith(fixture.dir, [
|
|
1285
|
+
'chart',
|
|
1286
|
+
'line-chart',
|
|
1287
|
+
]);
|
|
1288
|
+
});
|
|
1289
|
+
it('leaves out a new dependency the user skips in selective mode', async () => {
|
|
1290
|
+
const lineChart = "import { UiChart } from '../chart/chart';";
|
|
1291
|
+
fixture = createFixture({
|
|
1292
|
+
config: DEFAULT_CONFIG,
|
|
1293
|
+
lockfile: {
|
|
1294
|
+
version: 1,
|
|
1295
|
+
components: {
|
|
1296
|
+
'line-chart': { 'line-chart.ts': { hash: hashContent(lineChart) } },
|
|
1297
|
+
},
|
|
1298
|
+
utils: {},
|
|
1299
|
+
theme: {},
|
|
1300
|
+
},
|
|
1301
|
+
components: { 'line-chart': { 'line-chart.ts': lineChart } },
|
|
1302
|
+
uiSource: {
|
|
1303
|
+
'line-chart': { 'line-chart.ts': lineChart },
|
|
1304
|
+
chart: { 'chart.ts': 'export const UiChart = {};' },
|
|
1305
|
+
},
|
|
1306
|
+
});
|
|
1307
|
+
vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
|
|
1308
|
+
const p = await import('@clack/prompts');
|
|
1309
|
+
p.select.mockResolvedValueOnce('keep');
|
|
1310
|
+
const { updateCommand } = await import('./update');
|
|
1311
|
+
await updateCommand({ selective: true });
|
|
1312
|
+
expect(existsSync(join(fixture.dir, DEFAULT_CONFIG.aliases.components, 'chart/chart.ts'))).toBe(false);
|
|
1313
|
+
expect(readLockfile(fixture.dir).components['chart']).toEqual({});
|
|
1314
|
+
});
|
|
1315
|
+
});
|
|
1316
|
+
describe('updateCommand — ejected components', () => {
|
|
1317
|
+
/**
|
|
1318
|
+
* `dolan eject card` deleted card's lockfile entry and kept its files,
|
|
1319
|
+
* which the app has since changed.
|
|
1320
|
+
*/
|
|
1321
|
+
const ejectedCard = (lockfileComponents = {}) => createFixture({
|
|
1322
|
+
config: DEFAULT_CONFIG,
|
|
1323
|
+
lockfile: {
|
|
1324
|
+
version: 1,
|
|
1325
|
+
components: lockfileComponents,
|
|
1326
|
+
utils: {},
|
|
1327
|
+
theme: {},
|
|
1328
|
+
},
|
|
1329
|
+
components: {
|
|
1330
|
+
card: { 'card.ts': 'export const Card = { mine: true };' },
|
|
1331
|
+
},
|
|
1332
|
+
uiSource: { card: { 'card.ts': 'export const Card = { v: 2 };' } },
|
|
1333
|
+
});
|
|
1334
|
+
it("doesn't overwrite or track them again, even with --force", async () => {
|
|
1335
|
+
fixture = ejectedCard();
|
|
1336
|
+
vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
|
|
1337
|
+
const { updateCommand } = await import('./update');
|
|
1338
|
+
await updateCommand({ force: true });
|
|
1339
|
+
expect(readInstalledFile(fixture.dir, 'card', 'card.ts')).toBe('export const Card = { mine: true };');
|
|
1340
|
+
expect(readLockfile(fixture.dir).components).not.toHaveProperty('card');
|
|
1341
|
+
});
|
|
1342
|
+
it("doesn't report them as a conflict", async () => {
|
|
1343
|
+
fixture = ejectedCard();
|
|
1344
|
+
vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
|
|
1345
|
+
const p = await import('@clack/prompts');
|
|
1346
|
+
const { updateCommand } = await import('./update');
|
|
1347
|
+
await updateCommand({});
|
|
1348
|
+
const messages = p.log.message.mock.calls
|
|
1349
|
+
.map((c) => c[0])
|
|
1350
|
+
.join('\n');
|
|
1351
|
+
expect(messages).not.toContain('card');
|
|
1352
|
+
expect(p.select).not.toHaveBeenCalled();
|
|
1353
|
+
});
|
|
1354
|
+
it('leaves an ejected dependency of a tracked component alone', async () => {
|
|
1355
|
+
// select is tracked and depends on bottom-sheet, which was ejected.
|
|
1356
|
+
const select = 'export const Select = {};';
|
|
1357
|
+
fixture = createFixture({
|
|
1358
|
+
config: DEFAULT_CONFIG,
|
|
1359
|
+
lockfile: {
|
|
1360
|
+
version: 1,
|
|
1361
|
+
components: { select: { 'select.ts': { hash: hashContent(select) } } },
|
|
1362
|
+
utils: {},
|
|
1363
|
+
theme: {},
|
|
1364
|
+
},
|
|
1365
|
+
components: {
|
|
1366
|
+
select: { 'select.ts': select },
|
|
1367
|
+
'bottom-sheet': {
|
|
1368
|
+
'bottom-sheet.ts': 'export const BottomSheet = { mine: true };',
|
|
1369
|
+
},
|
|
1370
|
+
},
|
|
1371
|
+
uiSource: {
|
|
1372
|
+
select: { 'select.ts': select },
|
|
1373
|
+
'bottom-sheet': { 'bottom-sheet.ts': 'export const BottomSheet = {};' },
|
|
1374
|
+
},
|
|
1375
|
+
});
|
|
1376
|
+
vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
|
|
1377
|
+
const { updateCommand } = await import('./update');
|
|
1378
|
+
await updateCommand({ force: true });
|
|
1379
|
+
expect(readInstalledFile(fixture.dir, 'bottom-sheet', 'bottom-sheet.ts')).toBe('export const BottomSheet = { mine: true };');
|
|
1380
|
+
expect(readLockfile(fixture.dir).components).not.toHaveProperty('bottom-sheet');
|
|
1381
|
+
});
|
|
1382
|
+
});
|
|
1383
|
+
// CI, agents and scripts: a question there would wait for input that never
|
|
1384
|
+
// comes.
|
|
1385
|
+
describe('updateCommand without a terminal', () => {
|
|
1386
|
+
const base = 'export const Card = { v: 1 };';
|
|
1387
|
+
const userVersion = 'export const Card = { v: 1, local: true };';
|
|
1388
|
+
const upstream = 'export const Card = { v: 2 };';
|
|
1389
|
+
const themeBase = ':root { --primary: rgba(1, 1, 1, 1); }';
|
|
1390
|
+
const themeMine = ':root { --primary: rgba(9, 9, 9, 1); }';
|
|
1391
|
+
const themeUpstream = ':root { --primary: rgba(2, 2, 2, 1); }';
|
|
1392
|
+
/**
|
|
1393
|
+
* `card` and `default.css` conflict (both sides changed), and `skeleton` is
|
|
1394
|
+
* a plain auto-update.
|
|
1395
|
+
*/
|
|
1396
|
+
const setup = () => {
|
|
1397
|
+
fixture = createFixture({
|
|
1398
|
+
config: DEFAULT_CONFIG,
|
|
1399
|
+
lockfile: {
|
|
1400
|
+
version: 1,
|
|
1401
|
+
components: {
|
|
1402
|
+
card: { 'card.ts': { hash: hashContent(base) } },
|
|
1403
|
+
skeleton: { 'skeleton.ts': { hash: hashContent(base) } },
|
|
1404
|
+
},
|
|
1405
|
+
utils: {},
|
|
1406
|
+
theme: { 'default.css': { hash: hashContent(themeBase) } },
|
|
1407
|
+
},
|
|
1408
|
+
components: {
|
|
1409
|
+
card: { 'card.ts': userVersion },
|
|
1410
|
+
skeleton: { 'skeleton.ts': base },
|
|
1411
|
+
},
|
|
1412
|
+
uiSource: {
|
|
1413
|
+
card: { 'card.ts': upstream },
|
|
1414
|
+
skeleton: { 'skeleton.ts': upstream },
|
|
1415
|
+
},
|
|
1416
|
+
themeFiles: { 'default.css': themeUpstream },
|
|
1417
|
+
});
|
|
1418
|
+
writeFileSync(join(fixture.dir, DEFAULT_CONFIG.aliases.theme, 'default.css'), themeMine);
|
|
1419
|
+
vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
|
|
1420
|
+
};
|
|
1421
|
+
const readTheme = () => readFileSync(join(fixture.dir, DEFAULT_CONFIG.aliases.theme, 'default.css'), 'utf-8');
|
|
1422
|
+
beforeEach(() => {
|
|
1423
|
+
interactive = false;
|
|
1424
|
+
});
|
|
1425
|
+
it('applies auto-updates without asking', async () => {
|
|
1426
|
+
fixture = createFixture({
|
|
1427
|
+
config: DEFAULT_CONFIG,
|
|
1428
|
+
lockfile: {
|
|
1429
|
+
version: 1,
|
|
1430
|
+
components: { card: { 'card.ts': { hash: hashContent(base) } } },
|
|
1431
|
+
utils: {},
|
|
1432
|
+
theme: {},
|
|
1433
|
+
},
|
|
1434
|
+
components: { card: { 'card.ts': base } },
|
|
1435
|
+
uiSource: { card: { 'card.ts': upstream } },
|
|
1436
|
+
});
|
|
1437
|
+
vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
|
|
1438
|
+
const p = await import('@clack/prompts');
|
|
1439
|
+
const { updateCommand } = await import('./update');
|
|
1440
|
+
await updateCommand({});
|
|
1441
|
+
expect(p.confirm).not.toHaveBeenCalled();
|
|
1442
|
+
expect(readInstalledFile(fixture.dir, 'card', 'card.ts')).toBe(upstream);
|
|
1443
|
+
expect(p.outro).toHaveBeenCalledWith('Update complete.');
|
|
1444
|
+
});
|
|
1445
|
+
it('leaves conflicts as they are for a terminal to ask about, and exits 1', async () => {
|
|
1446
|
+
setup();
|
|
1447
|
+
const p = await import('@clack/prompts');
|
|
1448
|
+
const { updateCommand } = await import('./update');
|
|
1449
|
+
await expect(updateCommand({})).rejects.toThrow('process.exit(1)');
|
|
1450
|
+
expect(p.select).not.toHaveBeenCalled();
|
|
1451
|
+
// The count is bold, and CI turns colors on, so match the plain text.
|
|
1452
|
+
const [warning] = p.log.warn.mock.calls[0];
|
|
1453
|
+
// oxlint-disable-next-line no-control-regex -- matching ANSI color codes
|
|
1454
|
+
expect(warning.replace(/\u001b\[\d+m/g, '')).toMatch(/2 conflict\(s\) left.*dolan update --force/);
|
|
1455
|
+
// The auto-update still applies.
|
|
1456
|
+
expect(readInstalledFile(fixture.dir, 'skeleton', 'skeleton.ts')).toBe(upstream);
|
|
1457
|
+
// The conflicts keep both their files and their lockfile base. Keeping
|
|
1458
|
+
// the user's version would advance the base and lose the upstream change.
|
|
1459
|
+
expect(readInstalledFile(fixture.dir, 'card', 'card.ts')).toBe(userVersion);
|
|
1460
|
+
expect(readTheme()).toBe(themeMine);
|
|
1461
|
+
const lockfile = readLockfile(fixture.dir);
|
|
1462
|
+
expect(lockfile.components.card['card.ts'].hash).toBe(hashContent(base));
|
|
1463
|
+
expect(lockfile.theme['default.css'].hash).toBe(hashContent(themeBase));
|
|
1464
|
+
// So an update in a terminal still asks about both.
|
|
1465
|
+
interactive = true;
|
|
1466
|
+
vi.clearAllMocks();
|
|
1467
|
+
// A reset drops answers an earlier test queued and never used.
|
|
1468
|
+
p.select
|
|
1469
|
+
.mockReset()
|
|
1470
|
+
.mockResolvedValue('upstream');
|
|
1471
|
+
await updateCommand({});
|
|
1472
|
+
expect(p.select).toHaveBeenCalledTimes(2);
|
|
1473
|
+
expect(readInstalledFile(fixture.dir, 'card', 'card.ts')).toBe(upstream);
|
|
1474
|
+
expect(readTheme()).toBe(themeUpstream);
|
|
1475
|
+
});
|
|
1476
|
+
it("takes upstream's version of conflicts with --force, which wins over --selective", async () => {
|
|
1477
|
+
setup();
|
|
1478
|
+
const p = await import('@clack/prompts');
|
|
1479
|
+
const { updateCommand } = await import('./update');
|
|
1480
|
+
await updateCommand({ force: true, selective: true });
|
|
1481
|
+
expect(p.log.error).not.toHaveBeenCalled();
|
|
1482
|
+
expect(readInstalledFile(fixture.dir, 'card', 'card.ts')).toBe(upstream);
|
|
1483
|
+
expect(readTheme()).toBe(themeUpstream);
|
|
1484
|
+
expect(p.outro).toHaveBeenCalledWith('Update complete.');
|
|
1485
|
+
});
|
|
1486
|
+
it('fails with --selective, which asks about each file', async () => {
|
|
1487
|
+
setup();
|
|
1488
|
+
const p = await import('@clack/prompts');
|
|
1489
|
+
const { updateCommand } = await import('./update');
|
|
1490
|
+
await expect(updateCommand({ selective: true })).rejects.toThrow('process.exit(1)');
|
|
1491
|
+
expect(p.log.error).toHaveBeenCalledWith(expect.stringContaining('--selective'));
|
|
1492
|
+
expect(readInstalledFile(fixture.dir, 'skeleton', 'skeleton.ts')).toBe(base);
|
|
1493
|
+
});
|
|
1494
|
+
});
|
|
1495
|
+
// 22.2.2's `init` copied the theme but recorded no hashes for it, so the first
|
|
1496
|
+
// `update` after a release that changed the theme called every theme file a
|
|
1497
|
+
// conflict, though nobody had touched them.
|
|
1498
|
+
describe('updateCommand — theme files an older init copied', () => {
|
|
1499
|
+
const themePath = (file) => join(fixture.dir, DEFAULT_CONFIG.aliases.theme, file);
|
|
1500
|
+
const upstream = {
|
|
1501
|
+
'default.css': ':root { --primary: rgba(2, 2, 2, 1); }',
|
|
1502
|
+
'dark.css': 'page.dark { --primary: rgba(3, 3, 3, 1); }',
|
|
1503
|
+
};
|
|
1504
|
+
/**
|
|
1505
|
+
* Writes `files` where init put the theme, with `"theme": {}` locked.
|
|
1506
|
+
*/
|
|
1507
|
+
const setup = (files) => {
|
|
1508
|
+
fixture = createFixture({
|
|
1509
|
+
config: DEFAULT_CONFIG,
|
|
1510
|
+
lockfile: { version: 1, components: {}, theme: {} },
|
|
1511
|
+
themeFiles: upstream,
|
|
1512
|
+
});
|
|
1513
|
+
for (const [file, content] of Object.entries(files)) {
|
|
1514
|
+
writeFileSync(themePath(file), content);
|
|
1515
|
+
}
|
|
1516
|
+
vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
|
|
1517
|
+
};
|
|
1518
|
+
it('updates an untouched copy as an auto-update, not a conflict', async () => {
|
|
1519
|
+
setup({
|
|
1520
|
+
'default.css': shippedTheme('default.css'),
|
|
1521
|
+
'dark.css': shippedTheme('dark.css'),
|
|
1522
|
+
});
|
|
1523
|
+
const p = await import('@clack/prompts');
|
|
1524
|
+
const { updateCommand } = await import('./update');
|
|
1525
|
+
await updateCommand({});
|
|
1526
|
+
expect(p.select).not.toHaveBeenCalled();
|
|
1527
|
+
expect(p.confirm).toHaveBeenCalledWith(expect.objectContaining({ message: 'Apply 2 auto-update(s)?' }));
|
|
1528
|
+
expect(readFileSync(themePath('default.css'), 'utf-8')).toBe(upstream['default.css']);
|
|
1529
|
+
expect(readFileSync(themePath('dark.css'), 'utf-8')).toBe(upstream['dark.css']);
|
|
1530
|
+
expect(readLockfile(fixture.dir).theme).toEqual({
|
|
1531
|
+
'default.css': { hash: hashContent(upstream['default.css']) },
|
|
1532
|
+
'dark.css': { hash: hashContent(upstream['dark.css']) },
|
|
1533
|
+
});
|
|
1534
|
+
});
|
|
1535
|
+
it('applies them without a terminal, rather than leaving conflicts and exiting 1', async () => {
|
|
1536
|
+
interactive = false;
|
|
1537
|
+
setup({ 'default.css': shippedTheme('default.css') });
|
|
1538
|
+
const p = await import('@clack/prompts');
|
|
1539
|
+
const { updateCommand } = await import('./update');
|
|
1540
|
+
await updateCommand({});
|
|
1541
|
+
expect(readFileSync(themePath('default.css'), 'utf-8')).toBe(upstream['default.css']);
|
|
1542
|
+
expect(p.outro).toHaveBeenCalledWith('Update complete.');
|
|
1543
|
+
});
|
|
1544
|
+
it('still asks about a copy that was edited, since its base is unknown', async () => {
|
|
1545
|
+
const edited = `${shippedTheme('default.css')}\npage { --mine: red; }\n`;
|
|
1546
|
+
setup({ 'default.css': edited });
|
|
1547
|
+
const p = await import('@clack/prompts');
|
|
1548
|
+
p.select.mockResolvedValueOnce('keep');
|
|
1549
|
+
const { updateCommand } = await import('./update');
|
|
1550
|
+
await updateCommand({});
|
|
1551
|
+
expect(p.select).toHaveBeenCalledWith(expect.objectContaining({
|
|
1552
|
+
message: expect.stringContaining('how to resolve?'),
|
|
1553
|
+
}));
|
|
1554
|
+
expect(readFileSync(themePath('default.css'), 'utf-8')).toBe(edited);
|
|
1555
|
+
});
|
|
1556
|
+
});
|
|
1557
|
+
// 22.2.3 stopped copying `*-render.test.ts`, but `update` only looked at the
|
|
1558
|
+
// files upstream ships now and kept every old lockfile entry, so the stale
|
|
1559
|
+
// spec stayed, importing a helper dolan doesn't copy, and broke `ng test`.
|
|
1560
|
+
describe('updateCommand — files upstream no longer ships', () => {
|
|
1561
|
+
const card = 'export const Card = {};';
|
|
1562
|
+
const stale = "import { setInputSignal } from '../../../test-utils/x';";
|
|
1563
|
+
const edited = `${stale}\n// mine\n`;
|
|
1564
|
+
const STALE = 'card-render.test.ts';
|
|
1565
|
+
const stalePath = () => join(fixture.dir, DEFAULT_CONFIG.aliases.components, 'card', STALE);
|
|
1566
|
+
/**
|
|
1567
|
+
* card is up to date, and it has STALE (as add copied it, unless `onDisk`
|
|
1568
|
+
* says otherwise), which upstream no longer ships. `upstreamCard` changes
|
|
1569
|
+
* card.ts upstream too.
|
|
1570
|
+
*/
|
|
1571
|
+
const setup = ({ onDisk = stale, upstreamCard = card, } = {}) => {
|
|
1572
|
+
fixture = createFixture({
|
|
1573
|
+
config: DEFAULT_CONFIG,
|
|
1574
|
+
lockfile: {
|
|
1575
|
+
version: 1,
|
|
1576
|
+
components: {
|
|
1577
|
+
card: {
|
|
1578
|
+
'card.ts': { hash: hashContent(card) },
|
|
1579
|
+
[STALE]: { hash: hashContent(stale) },
|
|
1580
|
+
},
|
|
1581
|
+
},
|
|
1582
|
+
theme: {},
|
|
1583
|
+
},
|
|
1584
|
+
components: {
|
|
1585
|
+
card: {
|
|
1586
|
+
'card.ts': card,
|
|
1587
|
+
...(onDisk === null ? {} : { [STALE]: onDisk }),
|
|
1588
|
+
},
|
|
1589
|
+
},
|
|
1590
|
+
uiSource: { card: { 'card.ts': upstreamCard } },
|
|
1591
|
+
});
|
|
1592
|
+
vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
|
|
1593
|
+
};
|
|
1594
|
+
const lockedCard = () => readLockfile(fixture.dir).components.card;
|
|
1595
|
+
/**
|
|
1596
|
+
* The log messages, colors stripped.
|
|
1597
|
+
*/
|
|
1598
|
+
const messages = async () => {
|
|
1599
|
+
const p = await import('@clack/prompts');
|
|
1600
|
+
const text = p.log.message.mock.calls
|
|
1601
|
+
.map(([m]) => m)
|
|
1602
|
+
.join('\n');
|
|
1603
|
+
// oxlint-disable-next-line no-control-regex -- matching ANSI color codes
|
|
1604
|
+
return text.replace(/\u001b\[\d+m/g, '');
|
|
1605
|
+
};
|
|
1606
|
+
/**
|
|
1607
|
+
* The options of the select prompt's first call.
|
|
1608
|
+
*/
|
|
1609
|
+
const selectOptions = async () => {
|
|
1610
|
+
const p = await import('@clack/prompts');
|
|
1611
|
+
const [{ options }] = p.select.mock
|
|
1612
|
+
.calls[0];
|
|
1613
|
+
return options;
|
|
1614
|
+
};
|
|
1615
|
+
beforeEach(async () => {
|
|
1616
|
+
// A reset drops answers an earlier test queued and never used.
|
|
1617
|
+
const p = await import('@clack/prompts');
|
|
1618
|
+
p.select
|
|
1619
|
+
.mockReset()
|
|
1620
|
+
.mockResolvedValue('upstream');
|
|
1621
|
+
});
|
|
1622
|
+
it('deletes one nobody edited, with its lockfile entry, after the usual question', async () => {
|
|
1623
|
+
setup();
|
|
1624
|
+
const p = await import('@clack/prompts');
|
|
1625
|
+
const { updateCommand } = await import('./update');
|
|
1626
|
+
await updateCommand({});
|
|
1627
|
+
expect(p.confirm).toHaveBeenCalledWith(expect.objectContaining({ message: 'Apply 1 auto-update(s)?' }));
|
|
1628
|
+
expect(p.select).not.toHaveBeenCalled();
|
|
1629
|
+
expect(await messages()).toContain(`card/${STALE} — upstream no longer ships it → delete`);
|
|
1630
|
+
expect(existsSync(stalePath())).toBe(false);
|
|
1631
|
+
expect(lockedCard()).toEqual({ 'card.ts': { hash: hashContent(card) } });
|
|
1632
|
+
expect(p.log.success).toHaveBeenCalledWith(expect.stringContaining('deleted: upstream no longer ships them'));
|
|
1633
|
+
});
|
|
1634
|
+
it('deletes one nobody edited without a terminal too', async () => {
|
|
1635
|
+
interactive = false;
|
|
1636
|
+
setup();
|
|
1637
|
+
const p = await import('@clack/prompts');
|
|
1638
|
+
const { updateCommand } = await import('./update');
|
|
1639
|
+
await updateCommand({});
|
|
1640
|
+
expect(p.confirm).not.toHaveBeenCalled();
|
|
1641
|
+
expect(existsSync(stalePath())).toBe(false);
|
|
1642
|
+
expect(lockedCard()).not.toHaveProperty(STALE);
|
|
1643
|
+
expect(p.outro).toHaveBeenCalledWith('Update complete.');
|
|
1644
|
+
});
|
|
1645
|
+
it('asks before deleting one you edited, and deletes it when told to', async () => {
|
|
1646
|
+
setup({ onDisk: edited });
|
|
1647
|
+
const p = await import('@clack/prompts');
|
|
1648
|
+
const { updateCommand } = await import('./update');
|
|
1649
|
+
await updateCommand({});
|
|
1650
|
+
expect(await messages()).toContain(`card/${STALE} — upstream no longer ships it, and you modified it`);
|
|
1651
|
+
expect(p.select).toHaveBeenCalledTimes(1);
|
|
1652
|
+
expect(p.select).toHaveBeenCalledWith(expect.objectContaining({
|
|
1653
|
+
message: expect.stringContaining('you modified it, and upstream no longer ships it'),
|
|
1654
|
+
}));
|
|
1655
|
+
// A deletion is all or nothing, so there are no hunks to review.
|
|
1656
|
+
expect(await selectOptions()).toEqual([
|
|
1657
|
+
expect.objectContaining({ value: 'diff' }),
|
|
1658
|
+
{
|
|
1659
|
+
value: 'keep',
|
|
1660
|
+
label: 'Keep my version',
|
|
1661
|
+
hint: 'keep it as my own file',
|
|
1662
|
+
},
|
|
1663
|
+
{ value: 'upstream', label: 'Delete it', hint: 'discard my changes' },
|
|
1664
|
+
]);
|
|
1665
|
+
expect(existsSync(stalePath())).toBe(false);
|
|
1666
|
+
expect(lockedCard()).not.toHaveProperty(STALE);
|
|
1667
|
+
});
|
|
1668
|
+
it('shows what deleting it would take away', async () => {
|
|
1669
|
+
setup({ onDisk: edited });
|
|
1670
|
+
const p = await import('@clack/prompts');
|
|
1671
|
+
p.select
|
|
1672
|
+
.mockResolvedValueOnce('diff')
|
|
1673
|
+
.mockResolvedValueOnce('upstream');
|
|
1674
|
+
const { updateCommand } = await import('./update');
|
|
1675
|
+
await updateCommand({});
|
|
1676
|
+
expect(await messages()).toContain(`- // mine`);
|
|
1677
|
+
expect(existsSync(stalePath())).toBe(false);
|
|
1678
|
+
});
|
|
1679
|
+
it('keeps one you edited when told to, as your own file from then on', async () => {
|
|
1680
|
+
setup({ onDisk: edited });
|
|
1681
|
+
const p = await import('@clack/prompts');
|
|
1682
|
+
p.select.mockResolvedValueOnce('keep');
|
|
1683
|
+
const { updateCommand } = await import('./update');
|
|
1684
|
+
await updateCommand({});
|
|
1685
|
+
expect(readFileSync(stalePath(), 'utf-8')).toBe(edited);
|
|
1686
|
+
// Upstream has nothing left to merge into it, so dolan stops tracking it,
|
|
1687
|
+
// and the next update doesn't ask again.
|
|
1688
|
+
expect(lockedCard()).toEqual({ 'card.ts': { hash: hashContent(card) } });
|
|
1689
|
+
expect(p.log.info).toHaveBeenCalledWith(expect.stringContaining('file(s) kept as-is'));
|
|
1690
|
+
vi.clearAllMocks();
|
|
1691
|
+
await updateCommand({});
|
|
1692
|
+
expect(p.select).not.toHaveBeenCalled();
|
|
1693
|
+
expect(readFileSync(stalePath(), 'utf-8')).toBe(edited);
|
|
1694
|
+
});
|
|
1695
|
+
it('leaves one you edited for a terminal to ask about without one, and exits 1', async () => {
|
|
1696
|
+
interactive = false;
|
|
1697
|
+
setup({ onDisk: edited });
|
|
1698
|
+
const p = await import('@clack/prompts');
|
|
1699
|
+
const { updateCommand } = await import('./update');
|
|
1700
|
+
await expect(updateCommand({})).rejects.toThrow('process.exit(1)');
|
|
1701
|
+
expect(p.select).not.toHaveBeenCalled();
|
|
1702
|
+
expect(p.log.warn).toHaveBeenCalledWith(expect.stringContaining('conflict(s) left as they are'));
|
|
1703
|
+
expect(readFileSync(stalePath(), 'utf-8')).toBe(edited);
|
|
1704
|
+
expect(lockedCard()[STALE]).toEqual({ hash: hashContent(stale) });
|
|
1705
|
+
});
|
|
1706
|
+
it('deletes one you edited with --force', async () => {
|
|
1707
|
+
interactive = false;
|
|
1708
|
+
setup({ onDisk: edited });
|
|
1709
|
+
const { updateCommand } = await import('./update');
|
|
1710
|
+
await updateCommand({ force: true });
|
|
1711
|
+
expect(existsSync(stalePath())).toBe(false);
|
|
1712
|
+
expect(lockedCard()).not.toHaveProperty(STALE);
|
|
1713
|
+
});
|
|
1714
|
+
describe('with --selective', () => {
|
|
1715
|
+
it('asks about the deletion, and "Skip for now" keeps the file and its entry', async () => {
|
|
1716
|
+
setup();
|
|
1717
|
+
const p = await import('@clack/prompts');
|
|
1718
|
+
p.select.mockResolvedValueOnce('keep');
|
|
1719
|
+
const { updateCommand } = await import('./update');
|
|
1720
|
+
await updateCommand({ selective: true });
|
|
1721
|
+
expect(p.select).toHaveBeenCalledWith(expect.objectContaining({
|
|
1722
|
+
message: expect.stringMatching(/upstream no longer ships it$/),
|
|
1723
|
+
}));
|
|
1724
|
+
expect(await selectOptions()).toEqual([
|
|
1725
|
+
expect.objectContaining({ value: 'diff' }),
|
|
1726
|
+
expect.objectContaining({ value: 'keep', label: 'Skip for now' }),
|
|
1727
|
+
{ value: 'upstream', label: 'Delete it', hint: 'remove the file' },
|
|
1728
|
+
]);
|
|
1729
|
+
expect(readFileSync(stalePath(), 'utf-8')).toBe(stale);
|
|
1730
|
+
expect(lockedCard()[STALE]).toEqual({ hash: hashContent(stale) });
|
|
1731
|
+
});
|
|
1732
|
+
it('deletes it when accepted', async () => {
|
|
1733
|
+
setup();
|
|
1734
|
+
const { updateCommand } = await import('./update');
|
|
1735
|
+
await updateCommand({ selective: true });
|
|
1736
|
+
expect(existsSync(stalePath())).toBe(false);
|
|
1737
|
+
expect(lockedCard()).not.toHaveProperty(STALE);
|
|
1738
|
+
});
|
|
1739
|
+
});
|
|
1740
|
+
describe('already gone from disk', () => {
|
|
1741
|
+
it('drops its entry when nothing else changed', async () => {
|
|
1742
|
+
setup({ onDisk: null });
|
|
1743
|
+
const p = await import('@clack/prompts');
|
|
1744
|
+
const { updateCommand } = await import('./update');
|
|
1745
|
+
await updateCommand({});
|
|
1746
|
+
expect(p.confirm).not.toHaveBeenCalled();
|
|
1747
|
+
expect(await messages()).not.toContain(STALE);
|
|
1748
|
+
expect(p.outro).toHaveBeenCalledWith('Everything is up to date.');
|
|
1749
|
+
expect(lockedCard()).toEqual({ 'card.ts': { hash: hashContent(card) } });
|
|
1750
|
+
});
|
|
1751
|
+
it('drops its entry alongside an update', async () => {
|
|
1752
|
+
const upstreamCard = 'export const Card = { v: 2 };';
|
|
1753
|
+
setup({ onDisk: null, upstreamCard });
|
|
1754
|
+
const { updateCommand } = await import('./update');
|
|
1755
|
+
await updateCommand({});
|
|
1756
|
+
expect(lockedCard()).toEqual({
|
|
1757
|
+
'card.ts': { hash: hashContent(upstreamCard) },
|
|
1758
|
+
});
|
|
1759
|
+
});
|
|
1760
|
+
});
|
|
1761
|
+
it("leaves the tests a project without the testing library doesn't get", async () => {
|
|
1762
|
+
// Copied before dolan checked for the library. Upstream still ships them,
|
|
1763
|
+
// so they aren't deleted: `doctor` says what to do about them instead.
|
|
1764
|
+
const test = "import '@blotch/angular-lynx-testing-library';";
|
|
1765
|
+
fixture = createFixture({
|
|
1766
|
+
config: DEFAULT_CONFIG,
|
|
1767
|
+
lockfile: {
|
|
1768
|
+
version: 1,
|
|
1769
|
+
components: {
|
|
1770
|
+
card: {
|
|
1771
|
+
'card.ts': { hash: hashContent(card) },
|
|
1772
|
+
'card.test.ts': { hash: hashContent(test) },
|
|
1773
|
+
},
|
|
1774
|
+
},
|
|
1775
|
+
theme: {},
|
|
1776
|
+
},
|
|
1777
|
+
components: { card: { 'card.ts': card, 'card.test.ts': test } },
|
|
1778
|
+
uiSource: { card: { 'card.ts': card, 'card.test.ts': test } },
|
|
1779
|
+
});
|
|
1780
|
+
vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
|
|
1781
|
+
const { updateCommand } = await import('./update');
|
|
1782
|
+
await updateCommand({});
|
|
1783
|
+
expect(readInstalledFile(fixture.dir, 'card', 'card.test.ts')).toBe(test);
|
|
1784
|
+
expect(lockedCard()['card.test.ts']).toEqual({ hash: hashContent(test) });
|
|
1785
|
+
});
|
|
1786
|
+
});
|
|
1233
1787
|
//# sourceMappingURL=update.test.js.map
|