@blotch/dolan 22.2.3 → 22.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/add.js +9 -1
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/add.test.js +38 -3
- package/dist/commands/add.test.js.map +1 -1
- package/dist/commands/doctor.js +258 -21
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/doctor.test.js +425 -11
- package/dist/commands/doctor.test.js.map +1 -1
- package/dist/commands/init.d.ts +2 -2
- package/dist/commands/init.js +38 -18
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/init.test.js +105 -2
- package/dist/commands/init.test.js.map +1 -1
- package/dist/commands/theme.js +1 -1
- package/dist/commands/theme.js.map +1 -1
- package/dist/commands/update.d.ts +7 -0
- package/dist/commands/update.js +194 -50
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/update.test.js +570 -3
- package/dist/commands/update.test.js.map +1 -1
- package/dist/lockfile.d.ts +17 -0
- package/dist/lockfile.js +56 -1
- package/dist/lockfile.js.map +1 -1
- package/dist/lockfile.test.d.ts +1 -0
- package/dist/lockfile.test.js +65 -0
- package/dist/lockfile.test.js.map +1 -0
- package/dist/registry.d.ts +6 -0
- package/dist/registry.js +119 -11
- package/dist/registry.js.map +1 -1
- package/dist/registry.test.js +61 -6
- package/dist/registry.test.js.map +1 -1
- package/dist/test-utils.d.ts +27 -0
- package/dist/test-utils.js +45 -1
- package/dist/test-utils.js.map +1 -1
- package/dist/testing/fire-lynx-event.d.ts +14 -0
- package/dist/testing/fire-lynx-event.js +19 -0
- package/dist/testing/fire-lynx-event.js.map +1 -0
- package/dist/testing/fire-lynx-event.test.d.ts +1 -0
- package/dist/testing/fire-lynx-event.test.js +44 -0
- package/dist/testing/fire-lynx-event.test.js.map +1 -0
- package/dist/testing/lynx-main-thread.d.ts +22 -0
- package/dist/testing/lynx-main-thread.js +11 -0
- package/dist/testing/lynx-main-thread.js.map +1 -0
- package/dist/testing/lynx-main-thread.test.d.ts +1 -0
- package/dist/testing/lynx-main-thread.test.js +24 -0
- package/dist/testing/lynx-main-thread.test.js.map +1 -0
- package/dist/testing/set-input-signal.d.ts +5 -4
- package/dist/testing/set-input-signal.js +5 -4
- package/dist/testing/set-input-signal.js.map +1 -1
- package/dist/ui/boolean-inputs.test.ts +76 -0
- package/dist/ui/components/accordion/accordion.test.ts +158 -144
- package/dist/ui/components/accordion/accordion.ts +19 -14
- package/dist/ui/components/action-sheet/action-sheet.test.ts +637 -183
- package/dist/ui/components/action-sheet/action-sheet.ts +339 -55
- package/dist/ui/components/action-sheet/index.ts +1 -0
- package/dist/ui/components/alert/alert.test.ts +67 -19
- package/dist/ui/components/alert/alert.ts +14 -16
- package/dist/ui/components/alert-dialog/alert-dialog.test.ts +740 -129
- package/dist/ui/components/alert-dialog/alert-dialog.ts +672 -58
- package/dist/ui/components/alert-dialog/index.ts +5 -0
- package/dist/ui/components/area-chart/area-chart.test.ts +81 -1
- package/dist/ui/components/area-chart/area-chart.ts +3 -2
- package/dist/ui/components/avatar/avatar.test.ts +89 -45
- package/dist/ui/components/badge/badge.test.ts +57 -23
- package/dist/ui/components/badge/badge.ts +14 -15
- package/dist/ui/components/bar-chart/bar-chart.test.ts +82 -1
- package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +1314 -273
- package/dist/ui/components/bottom-sheet/bottom-sheet.ts +1046 -119
- package/dist/ui/components/bottom-sheet/index.ts +7 -1
- package/dist/ui/components/button/button.test.ts +646 -2
- package/dist/ui/components/button/button.ts +443 -64
- package/dist/ui/components/button/index.ts +2 -0
- package/dist/ui/components/button-group/button-group.test.ts +528 -2
- package/dist/ui/components/button-group/button-group.ts +428 -70
- package/dist/ui/components/button-group/index.ts +3 -0
- package/dist/ui/components/candlestick-chart/candlestick-chart.test.ts +82 -1
- package/dist/ui/components/card/card.test.ts +97 -61
- package/dist/ui/components/card/card.ts +2 -1
- package/dist/ui/components/chart/chart-presets.test.ts +197 -1
- package/dist/ui/components/chart/chart-presets.ts +14 -7
- package/dist/ui/components/chart/chart-theme.ts +12 -0
- package/dist/ui/components/chart/chart.test.ts +1031 -0
- package/dist/ui/components/chart/chart.ts +66 -46
- package/dist/ui/components/checkbox/checkbox.test.ts +124 -42
- package/dist/ui/components/checkbox/checkbox.ts +25 -24
- package/dist/ui/components/collapsible/collapsible.test.ts +158 -81
- package/dist/ui/components/collapsible/collapsible.ts +21 -15
- package/dist/ui/components/context-menu/context-menu.test.ts +224 -0
- package/dist/ui/components/context-menu/context-menu.ts +110 -0
- package/dist/ui/components/context-menu/index.ts +1 -0
- package/dist/ui/components/dialog/dialog.test.ts +210 -152
- package/dist/ui/components/dialog/dialog.ts +35 -29
- package/dist/ui/components/dock/dock-scroll.test.ts +130 -0
- package/dist/ui/components/dock/dock-scroll.ts +142 -0
- package/dist/ui/components/dock/dock-search.test.ts +594 -0
- package/dist/ui/components/dock/dock.test.ts +752 -0
- package/dist/ui/components/dock/dock.ts +1438 -0
- package/dist/ui/components/dock/index.ts +2 -0
- package/dist/ui/components/dynamic-color/dynamic-color.test.ts +144 -0
- package/dist/ui/components/dynamic-color/dynamic-color.ts +146 -0
- package/dist/ui/components/dynamic-color/index.ts +5 -0
- package/dist/ui/components/empty-state/empty-state.test.ts +28 -39
- package/dist/ui/components/glass/concentric-radius.test.ts +13 -0
- package/dist/ui/components/glass/concentric-radius.ts +17 -0
- package/dist/ui/components/glass/glass-design.test.ts +78 -0
- package/dist/ui/components/glass/glass-design.ts +119 -0
- package/dist/ui/components/glass/glass.test.ts +376 -0
- package/dist/ui/components/glass/glass.ts +430 -0
- package/dist/ui/components/glass/index.ts +19 -0
- package/dist/ui/components/icon/icon-theme.test.ts +64 -0
- package/dist/ui/components/icon/icon-theme.ts +70 -16
- package/dist/ui/components/icon/icon.test.ts +52 -41
- package/dist/ui/components/input/input.test.ts +178 -101
- package/dist/ui/components/input/input.ts +17 -13
- package/dist/ui/components/label/label.test.ts +11 -24
- package/dist/ui/components/label/label.ts +8 -2
- package/dist/ui/components/line-chart/line-chart.test.ts +76 -1
- package/dist/ui/components/line-chart/line-chart.ts +3 -2
- package/dist/ui/components/list/list.test.ts +80 -49
- package/dist/ui/components/list/list.ts +5 -4
- package/dist/ui/components/menu/index.ts +25 -0
- package/dist/ui/components/menu/menu-placement.test.ts +153 -0
- package/dist/ui/components/menu/menu-placement.ts +159 -0
- package/dist/ui/components/menu/menu-popup.test.ts +636 -0
- package/dist/ui/components/menu/menu-popup.ts +565 -0
- package/dist/ui/components/menu/menu.test.ts +523 -0
- package/dist/ui/components/menu/menu.ts +455 -0
- package/dist/ui/components/nav-drawer/nav-drawer.test.ts +425 -260
- package/dist/ui/components/nav-drawer/nav-drawer.ts +75 -58
- package/dist/ui/components/navigation-bar/index.ts +1 -0
- package/dist/ui/components/navigation-bar/navigation-bar.test.ts +425 -0
- package/dist/ui/components/navigation-bar/navigation-bar.ts +411 -0
- package/dist/ui/components/pagination/pagination.test.ts +136 -86
- package/dist/ui/components/popover/index.ts +9 -0
- package/dist/ui/components/popover/popover-panel.test.ts +735 -0
- package/dist/ui/components/popover/popover-panel.ts +603 -0
- package/dist/ui/components/popover/popover.test.ts +286 -0
- package/dist/ui/components/popover/popover.ts +207 -0
- package/dist/ui/components/progress/progress.test.ts +102 -46
- package/dist/ui/components/progress/progress.ts +54 -55
- package/dist/ui/components/radio-group/radio-group.test.ts +128 -83
- package/dist/ui/components/radio-group/radio-group.ts +26 -25
- package/dist/ui/components/scatter-chart/scatter-chart.test.ts +74 -1
- package/dist/ui/components/scroll-area/scroll-area.test.ts +72 -36
- package/dist/ui/components/scroll-area/scroll-area.ts +4 -3
- package/dist/ui/components/search-field/index.ts +15 -0
- package/dist/ui/components/search-field/keyboard.test.ts +147 -0
- package/dist/ui/components/search-field/keyboard.ts +123 -0
- package/dist/ui/components/search-field/search-field.test.ts +549 -0
- package/dist/ui/components/search-field/search-field.ts +628 -0
- package/dist/ui/components/segmented-control/index.ts +8 -0
- package/dist/ui/components/segmented-control/segmented-control.test.ts +861 -0
- package/dist/ui/components/segmented-control/segmented-control.ts +884 -0
- package/dist/ui/components/select/select.test.ts +138 -78
- package/dist/ui/components/select/select.ts +19 -12
- package/dist/ui/components/separator/separator.test.ts +18 -17
- package/dist/ui/components/sheet/sheet.test.ts +151 -11
- package/dist/ui/components/sheet/sheet.ts +68 -5
- package/dist/ui/components/skeleton/skeleton.test.ts +24 -7
- package/dist/ui/components/skeleton/skeleton.ts +10 -16
- package/dist/ui/components/slider/index.ts +1 -0
- package/dist/ui/components/slider/slider.test.ts +695 -0
- package/dist/ui/components/slider/slider.ts +901 -0
- package/dist/ui/components/spinner/spinner.test.ts +64 -46
- package/dist/ui/components/spinner/spinner.ts +16 -19
- package/dist/ui/components/switch/index.ts +1 -1
- package/dist/ui/components/switch/switch.test.ts +534 -48
- package/dist/ui/components/switch/switch.ts +613 -48
- package/dist/ui/components/tab-bar/index.ts +1 -0
- package/dist/ui/components/tab-bar/tab-bar.test.ts +280 -0
- package/dist/ui/components/tab-bar/tab-bar.ts +230 -0
- package/dist/ui/components/tabs/index.ts +7 -1
- package/dist/ui/components/tabs/tabs.test.ts +484 -150
- package/dist/ui/components/tabs/tabs.ts +344 -149
- package/dist/ui/components/textarea/textarea.test.ts +206 -100
- package/dist/ui/components/textarea/textarea.ts +16 -13
- package/dist/ui/components/toast/toast.test.ts +434 -350
- package/dist/ui/components/toast/toast.ts +42 -51
- package/dist/ui/components/toggle/index.ts +1 -0
- package/dist/ui/components/toggle/toggle.test.ts +559 -146
- package/dist/ui/components/toggle/toggle.ts +241 -26
- package/dist/ui/components/toolbar/bar-scroll.test.ts +310 -0
- package/dist/ui/components/toolbar/bar-scroll.ts +372 -0
- package/dist/ui/components/toolbar/index.ts +12 -0
- package/dist/ui/components/toolbar/toolbar.test.ts +438 -0
- package/dist/ui/components/toolbar/toolbar.ts +535 -0
- package/dist/ui/theme/chart-colors.test.ts +8 -3
- package/dist/ui/theme/dark.css +38 -2
- package/dist/ui/theme/default.css +55 -7
- package/dist/ui/theme/icon-colors.test.ts +8 -3
- package/dist/ui/theme/rgb-channels.test.ts +84 -0
- package/dist/ui/theme/tailwind-build.test.ts +114 -0
- package/dist/ui/theme/tailwind-css.test.ts +83 -0
- package/dist/ui/theme/tailwind-plugin.test.ts +42 -2
- package/dist/ui/theme/tailwind-plugin.ts +93 -29
- package/dist/ui/theme/tailwind.css +113 -0
- package/dist/ui/utils/animate.test.ts +13 -1
- package/dist/utils/analyze.d.ts +19 -0
- package/dist/utils/analyze.js +16 -0
- package/dist/utils/analyze.js.map +1 -1
- package/dist/utils/analyze.test.js +14 -1
- package/dist/utils/analyze.test.js.map +1 -1
- package/dist/utils/installed.d.ts +25 -1
- package/dist/utils/installed.js +33 -0
- package/dist/utils/installed.js.map +1 -1
- package/dist/utils/installed.test.js +55 -1
- package/dist/utils/installed.test.js.map +1 -1
- package/dist/utils/resolve-paths.d.ts +4 -4
- package/dist/utils/resolve-paths.js +5 -7
- package/dist/utils/resolve-paths.js.map +1 -1
- package/dist/utils/resolve-paths.test.js +9 -5
- package/dist/utils/resolve-paths.test.js.map +1 -1
- package/dist/utils/tailwind.d.ts +45 -0
- package/dist/utils/tailwind.js +90 -0
- package/dist/utils/tailwind.js.map +1 -0
- package/dist/utils/tailwind.test.d.ts +1 -0
- package/dist/utils/tailwind.test.js +134 -0
- package/dist/utils/tailwind.test.js.map +1 -0
- package/package.json +9 -1
- package/dist/ui/components/area-chart/area-chart-render.test.ts +0 -63
- package/dist/ui/components/bar-chart/bar-chart-render.test.ts +0 -58
- package/dist/ui/components/button/button-render.test.ts +0 -132
- package/dist/ui/components/button-group/button-group-render.test.ts +0 -146
- package/dist/ui/components/candlestick-chart/candlestick-chart-render.test.ts +0 -59
- package/dist/ui/components/chart/chart-render.test.ts +0 -730
- package/dist/ui/components/line-chart/line-chart-render.test.ts +0 -83
- package/dist/ui/components/scatter-chart/scatter-chart-render.test.ts +0 -64
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { readFileSync, writeFileSync, existsSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync, readdirSync, rmSync, } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
import {
|
|
4
|
+
import { stripVTControlCharacters } from 'node:util';
|
|
5
|
+
import { createFixture, DEFAULT_CONFIG, onTailwind, shippedTheme, } from '../test-utils';
|
|
5
6
|
import { hashContent } from '../lockfile';
|
|
6
7
|
const spinnerMock = { start: vi.fn(), stop: vi.fn() };
|
|
7
8
|
vi.mock('@clack/prompts', () => ({
|
|
@@ -30,7 +31,7 @@ let fixture;
|
|
|
30
31
|
vi.mock('../utils/resolve-paths.js', () => ({
|
|
31
32
|
getUiSourceDir: () => fixture.uiDir,
|
|
32
33
|
getComponentSourceDir: (name) => join(fixture.uiDir, 'components', name),
|
|
33
|
-
getComponentFiles: (name) => readdirSync(join(fixture.uiDir, 'components', name)).filter((f) => f.endsWith('.ts')),
|
|
34
|
+
getComponentFiles: (name, { tests }) => readdirSync(join(fixture.uiDir, 'components', name)).filter((f) => f.endsWith('.ts') && (tests || !f.endsWith('.test.ts'))),
|
|
34
35
|
rewriteImports: (content) => content,
|
|
35
36
|
}));
|
|
36
37
|
// The install step runs the project's package manager; record calls instead.
|
|
@@ -616,6 +617,26 @@ describe('updateCommand --force', () => {
|
|
|
616
617
|
const lockfile = readLockfile(fixture.dir);
|
|
617
618
|
expect(lockfile.theme['default.css'].hash).toBe(hashContent(upstream));
|
|
618
619
|
});
|
|
620
|
+
it("adds the Tailwind plugin's test once the project has the testing library", async () => {
|
|
621
|
+
const pluginTest = "import { blotchPlugin } from './tailwind-plugin';";
|
|
622
|
+
fixture = createFixture({
|
|
623
|
+
config: DEFAULT_CONFIG,
|
|
624
|
+
lockfile: { version: 1, components: {}, utils: {}, theme: {} },
|
|
625
|
+
themeFiles: { 'tailwind-plugin.test.ts': pluginTest },
|
|
626
|
+
});
|
|
627
|
+
vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
|
|
628
|
+
const { updateCommand } = await import('./update');
|
|
629
|
+
await updateCommand({ force: true });
|
|
630
|
+
expect(existsSync(join(fixture.dir, DEFAULT_CONFIG.aliases.theme, 'tailwind-plugin.test.ts'))).toBe(false);
|
|
631
|
+
writeFileSync(join(fixture.dir, 'package.json'), JSON.stringify({
|
|
632
|
+
devDependencies: { '@blotch/angular-lynx-testing-library': '^22.2.3' },
|
|
633
|
+
}));
|
|
634
|
+
await updateCommand({ force: true });
|
|
635
|
+
expect(readTheme(fixture, 'tailwind-plugin.test.ts')).toBe(pluginTest);
|
|
636
|
+
expect(readLockfile(fixture.dir).theme['tailwind-plugin.test.ts']).toEqual({
|
|
637
|
+
hash: hashContent(pluginTest),
|
|
638
|
+
});
|
|
639
|
+
});
|
|
619
640
|
it('never prompts even when conflicts, user-modifications and new files coexist', async () => {
|
|
620
641
|
fixture = createFixture({
|
|
621
642
|
config: DEFAULT_CONFIG,
|
|
@@ -1254,6 +1275,7 @@ describe('updateCommand dependencies', () => {
|
|
|
1254
1275
|
uiSource: {
|
|
1255
1276
|
'line-chart': { 'line-chart.ts': lineChart },
|
|
1256
1277
|
chart: { 'chart.ts': 'export const UiChart = {};' },
|
|
1278
|
+
icon: { 'icon.ts': 'export const UiIcon = {};' },
|
|
1257
1279
|
},
|
|
1258
1280
|
});
|
|
1259
1281
|
vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
|
|
@@ -1263,6 +1285,7 @@ describe('updateCommand dependencies', () => {
|
|
|
1263
1285
|
expect(readInstalledFile(fixture.dir, 'line-chart', 'line-chart.ts')).toBe(lineChart);
|
|
1264
1286
|
expect(installComponentPackages).toHaveBeenCalledWith(fixture.dir, [
|
|
1265
1287
|
'chart',
|
|
1288
|
+
'icon',
|
|
1266
1289
|
'line-chart',
|
|
1267
1290
|
]);
|
|
1268
1291
|
});
|
|
@@ -1282,6 +1305,7 @@ describe('updateCommand dependencies', () => {
|
|
|
1282
1305
|
uiSource: {
|
|
1283
1306
|
'line-chart': { 'line-chart.ts': lineChart },
|
|
1284
1307
|
chart: { 'chart.ts': 'export const UiChart = {};' },
|
|
1308
|
+
icon: { 'icon.ts': 'export const UiIcon = {};' },
|
|
1285
1309
|
},
|
|
1286
1310
|
});
|
|
1287
1311
|
vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
|
|
@@ -1351,6 +1375,9 @@ describe('updateCommand — ejected components', () => {
|
|
|
1351
1375
|
uiSource: {
|
|
1352
1376
|
select: { 'select.ts': select },
|
|
1353
1377
|
'bottom-sheet': { 'bottom-sheet.ts': 'export const BottomSheet = {};' },
|
|
1378
|
+
// What bottom-sheet's glass variant draws with.
|
|
1379
|
+
glass: { 'glass.ts': 'export const Glass = {};' },
|
|
1380
|
+
icon: { 'icon.ts': 'export const Icon = {};' },
|
|
1354
1381
|
},
|
|
1355
1382
|
});
|
|
1356
1383
|
vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
|
|
@@ -1472,4 +1499,544 @@ describe('updateCommand without a terminal', () => {
|
|
|
1472
1499
|
expect(readInstalledFile(fixture.dir, 'skeleton', 'skeleton.ts')).toBe(base);
|
|
1473
1500
|
});
|
|
1474
1501
|
});
|
|
1502
|
+
// 22.2.2's `init` copied the theme but recorded no hashes for it, so the first
|
|
1503
|
+
// `update` after a release that changed the theme called every theme file a
|
|
1504
|
+
// conflict, though nobody had touched them.
|
|
1505
|
+
describe('updateCommand — theme files an older init copied', () => {
|
|
1506
|
+
const themePath = (file) => join(fixture.dir, DEFAULT_CONFIG.aliases.theme, file);
|
|
1507
|
+
const upstream = {
|
|
1508
|
+
'default.css': ':root { --primary: rgba(2, 2, 2, 1); }',
|
|
1509
|
+
'dark.css': 'page.dark { --primary: rgba(3, 3, 3, 1); }',
|
|
1510
|
+
};
|
|
1511
|
+
/**
|
|
1512
|
+
* Writes `files` where init put the theme, with `"theme": {}` locked.
|
|
1513
|
+
*/
|
|
1514
|
+
const setup = (files) => {
|
|
1515
|
+
fixture = createFixture({
|
|
1516
|
+
config: DEFAULT_CONFIG,
|
|
1517
|
+
lockfile: { version: 1, components: {}, theme: {} },
|
|
1518
|
+
themeFiles: upstream,
|
|
1519
|
+
});
|
|
1520
|
+
for (const [file, content] of Object.entries(files)) {
|
|
1521
|
+
writeFileSync(themePath(file), content);
|
|
1522
|
+
}
|
|
1523
|
+
vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
|
|
1524
|
+
};
|
|
1525
|
+
it('updates an untouched copy as an auto-update, not a conflict', async () => {
|
|
1526
|
+
setup({
|
|
1527
|
+
'default.css': shippedTheme('default.css'),
|
|
1528
|
+
'dark.css': shippedTheme('dark.css'),
|
|
1529
|
+
});
|
|
1530
|
+
const p = await import('@clack/prompts');
|
|
1531
|
+
const { updateCommand } = await import('./update');
|
|
1532
|
+
await updateCommand({});
|
|
1533
|
+
expect(p.select).not.toHaveBeenCalled();
|
|
1534
|
+
expect(p.confirm).toHaveBeenCalledWith(expect.objectContaining({ message: 'Apply 2 auto-update(s)?' }));
|
|
1535
|
+
expect(readFileSync(themePath('default.css'), 'utf-8')).toBe(upstream['default.css']);
|
|
1536
|
+
expect(readFileSync(themePath('dark.css'), 'utf-8')).toBe(upstream['dark.css']);
|
|
1537
|
+
expect(readLockfile(fixture.dir).theme).toEqual({
|
|
1538
|
+
'default.css': { hash: hashContent(upstream['default.css']) },
|
|
1539
|
+
'dark.css': { hash: hashContent(upstream['dark.css']) },
|
|
1540
|
+
});
|
|
1541
|
+
});
|
|
1542
|
+
it('applies them without a terminal, rather than leaving conflicts and exiting 1', async () => {
|
|
1543
|
+
interactive = false;
|
|
1544
|
+
setup({ 'default.css': shippedTheme('default.css') });
|
|
1545
|
+
const p = await import('@clack/prompts');
|
|
1546
|
+
const { updateCommand } = await import('./update');
|
|
1547
|
+
await updateCommand({});
|
|
1548
|
+
expect(readFileSync(themePath('default.css'), 'utf-8')).toBe(upstream['default.css']);
|
|
1549
|
+
expect(p.outro).toHaveBeenCalledWith('Update complete.');
|
|
1550
|
+
});
|
|
1551
|
+
it('still asks about a copy that was edited, since its base is unknown', async () => {
|
|
1552
|
+
const edited = `${shippedTheme('default.css')}\npage { --mine: red; }\n`;
|
|
1553
|
+
setup({ 'default.css': edited });
|
|
1554
|
+
const p = await import('@clack/prompts');
|
|
1555
|
+
p.select.mockResolvedValueOnce('keep');
|
|
1556
|
+
const { updateCommand } = await import('./update');
|
|
1557
|
+
await updateCommand({});
|
|
1558
|
+
expect(p.select).toHaveBeenCalledWith(expect.objectContaining({
|
|
1559
|
+
message: expect.stringContaining('how to resolve?'),
|
|
1560
|
+
}));
|
|
1561
|
+
expect(readFileSync(themePath('default.css'), 'utf-8')).toBe(edited);
|
|
1562
|
+
});
|
|
1563
|
+
});
|
|
1564
|
+
// 22.2.3 stopped copying `*-render.test.ts`, but `update` only looked at the
|
|
1565
|
+
// files upstream ships now and kept every old lockfile entry, so the stale
|
|
1566
|
+
// spec stayed, importing a helper dolan doesn't copy, and broke `ng test`.
|
|
1567
|
+
describe('updateCommand — files upstream no longer ships', () => {
|
|
1568
|
+
const card = 'export const Card = {};';
|
|
1569
|
+
const stale = "import { setInputSignal } from '../../../test-utils/x';";
|
|
1570
|
+
const edited = `${stale}\n// mine\n`;
|
|
1571
|
+
const STALE = 'card-render.test.ts';
|
|
1572
|
+
const stalePath = () => join(fixture.dir, DEFAULT_CONFIG.aliases.components, 'card', STALE);
|
|
1573
|
+
/**
|
|
1574
|
+
* card is up to date, and it has STALE (as add copied it, unless `onDisk`
|
|
1575
|
+
* says otherwise), which upstream no longer ships. `upstreamCard` changes
|
|
1576
|
+
* card.ts upstream too.
|
|
1577
|
+
*/
|
|
1578
|
+
const setup = ({ onDisk = stale, upstreamCard = card, } = {}) => {
|
|
1579
|
+
fixture = createFixture({
|
|
1580
|
+
config: DEFAULT_CONFIG,
|
|
1581
|
+
lockfile: {
|
|
1582
|
+
version: 1,
|
|
1583
|
+
components: {
|
|
1584
|
+
card: {
|
|
1585
|
+
'card.ts': { hash: hashContent(card) },
|
|
1586
|
+
[STALE]: { hash: hashContent(stale) },
|
|
1587
|
+
},
|
|
1588
|
+
},
|
|
1589
|
+
theme: {},
|
|
1590
|
+
},
|
|
1591
|
+
components: {
|
|
1592
|
+
card: {
|
|
1593
|
+
'card.ts': card,
|
|
1594
|
+
...(onDisk === null ? {} : { [STALE]: onDisk }),
|
|
1595
|
+
},
|
|
1596
|
+
},
|
|
1597
|
+
uiSource: { card: { 'card.ts': upstreamCard } },
|
|
1598
|
+
});
|
|
1599
|
+
vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
|
|
1600
|
+
};
|
|
1601
|
+
const lockedCard = () => readLockfile(fixture.dir).components.card;
|
|
1602
|
+
/**
|
|
1603
|
+
* The log messages, colors stripped.
|
|
1604
|
+
*/
|
|
1605
|
+
const messages = async () => {
|
|
1606
|
+
const p = await import('@clack/prompts');
|
|
1607
|
+
const text = p.log.message.mock.calls
|
|
1608
|
+
.map(([m]) => m)
|
|
1609
|
+
.join('\n');
|
|
1610
|
+
// oxlint-disable-next-line no-control-regex -- matching ANSI color codes
|
|
1611
|
+
return text.replace(/\u001b\[\d+m/g, '');
|
|
1612
|
+
};
|
|
1613
|
+
/**
|
|
1614
|
+
* The options of the select prompt's first call.
|
|
1615
|
+
*/
|
|
1616
|
+
const selectOptions = async () => {
|
|
1617
|
+
const p = await import('@clack/prompts');
|
|
1618
|
+
const [{ options }] = p.select.mock
|
|
1619
|
+
.calls[0];
|
|
1620
|
+
return options;
|
|
1621
|
+
};
|
|
1622
|
+
beforeEach(async () => {
|
|
1623
|
+
// A reset drops answers an earlier test queued and never used.
|
|
1624
|
+
const p = await import('@clack/prompts');
|
|
1625
|
+
p.select
|
|
1626
|
+
.mockReset()
|
|
1627
|
+
.mockResolvedValue('upstream');
|
|
1628
|
+
});
|
|
1629
|
+
it('deletes one nobody edited, with its lockfile entry, after the usual question', async () => {
|
|
1630
|
+
setup();
|
|
1631
|
+
const p = await import('@clack/prompts');
|
|
1632
|
+
const { updateCommand } = await import('./update');
|
|
1633
|
+
await updateCommand({});
|
|
1634
|
+
expect(p.confirm).toHaveBeenCalledWith(expect.objectContaining({ message: 'Apply 1 auto-update(s)?' }));
|
|
1635
|
+
expect(p.select).not.toHaveBeenCalled();
|
|
1636
|
+
expect(await messages()).toContain(`card/${STALE} — upstream no longer ships it → delete`);
|
|
1637
|
+
expect(existsSync(stalePath())).toBe(false);
|
|
1638
|
+
expect(lockedCard()).toEqual({ 'card.ts': { hash: hashContent(card) } });
|
|
1639
|
+
expect(p.log.success).toHaveBeenCalledWith(expect.stringContaining('deleted: upstream no longer ships them'));
|
|
1640
|
+
});
|
|
1641
|
+
it('deletes one nobody edited without a terminal too', async () => {
|
|
1642
|
+
interactive = false;
|
|
1643
|
+
setup();
|
|
1644
|
+
const p = await import('@clack/prompts');
|
|
1645
|
+
const { updateCommand } = await import('./update');
|
|
1646
|
+
await updateCommand({});
|
|
1647
|
+
expect(p.confirm).not.toHaveBeenCalled();
|
|
1648
|
+
expect(existsSync(stalePath())).toBe(false);
|
|
1649
|
+
expect(lockedCard()).not.toHaveProperty(STALE);
|
|
1650
|
+
expect(p.outro).toHaveBeenCalledWith('Update complete.');
|
|
1651
|
+
});
|
|
1652
|
+
it('asks before deleting one you edited, and deletes it when told to', async () => {
|
|
1653
|
+
setup({ onDisk: edited });
|
|
1654
|
+
const p = await import('@clack/prompts');
|
|
1655
|
+
const { updateCommand } = await import('./update');
|
|
1656
|
+
await updateCommand({});
|
|
1657
|
+
expect(await messages()).toContain(`card/${STALE} — upstream no longer ships it, and you modified it`);
|
|
1658
|
+
expect(p.select).toHaveBeenCalledTimes(1);
|
|
1659
|
+
expect(p.select).toHaveBeenCalledWith(expect.objectContaining({
|
|
1660
|
+
message: expect.stringContaining('you modified it, and upstream no longer ships it'),
|
|
1661
|
+
}));
|
|
1662
|
+
// A deletion is all or nothing, so there are no hunks to review.
|
|
1663
|
+
expect(await selectOptions()).toEqual([
|
|
1664
|
+
expect.objectContaining({ value: 'diff' }),
|
|
1665
|
+
{
|
|
1666
|
+
value: 'keep',
|
|
1667
|
+
label: 'Keep my version',
|
|
1668
|
+
hint: 'keep it as my own file',
|
|
1669
|
+
},
|
|
1670
|
+
{ value: 'upstream', label: 'Delete it', hint: 'discard my changes' },
|
|
1671
|
+
]);
|
|
1672
|
+
expect(existsSync(stalePath())).toBe(false);
|
|
1673
|
+
expect(lockedCard()).not.toHaveProperty(STALE);
|
|
1674
|
+
});
|
|
1675
|
+
it('shows what deleting it would take away', async () => {
|
|
1676
|
+
setup({ onDisk: edited });
|
|
1677
|
+
const p = await import('@clack/prompts');
|
|
1678
|
+
p.select
|
|
1679
|
+
.mockResolvedValueOnce('diff')
|
|
1680
|
+
.mockResolvedValueOnce('upstream');
|
|
1681
|
+
const { updateCommand } = await import('./update');
|
|
1682
|
+
await updateCommand({});
|
|
1683
|
+
expect(await messages()).toContain(`- // mine`);
|
|
1684
|
+
expect(existsSync(stalePath())).toBe(false);
|
|
1685
|
+
});
|
|
1686
|
+
it('keeps one you edited when told to, as your own file from then on', async () => {
|
|
1687
|
+
setup({ onDisk: edited });
|
|
1688
|
+
const p = await import('@clack/prompts');
|
|
1689
|
+
p.select.mockResolvedValueOnce('keep');
|
|
1690
|
+
const { updateCommand } = await import('./update');
|
|
1691
|
+
await updateCommand({});
|
|
1692
|
+
expect(readFileSync(stalePath(), 'utf-8')).toBe(edited);
|
|
1693
|
+
// Upstream has nothing left to merge into it, so dolan stops tracking it,
|
|
1694
|
+
// and the next update doesn't ask again.
|
|
1695
|
+
expect(lockedCard()).toEqual({ 'card.ts': { hash: hashContent(card) } });
|
|
1696
|
+
expect(p.log.info).toHaveBeenCalledWith(expect.stringContaining('file(s) kept as-is'));
|
|
1697
|
+
vi.clearAllMocks();
|
|
1698
|
+
await updateCommand({});
|
|
1699
|
+
expect(p.select).not.toHaveBeenCalled();
|
|
1700
|
+
expect(readFileSync(stalePath(), 'utf-8')).toBe(edited);
|
|
1701
|
+
});
|
|
1702
|
+
it('leaves one you edited for a terminal to ask about without one, and exits 1', async () => {
|
|
1703
|
+
interactive = false;
|
|
1704
|
+
setup({ onDisk: edited });
|
|
1705
|
+
const p = await import('@clack/prompts');
|
|
1706
|
+
const { updateCommand } = await import('./update');
|
|
1707
|
+
await expect(updateCommand({})).rejects.toThrow('process.exit(1)');
|
|
1708
|
+
expect(p.select).not.toHaveBeenCalled();
|
|
1709
|
+
expect(p.log.warn).toHaveBeenCalledWith(expect.stringContaining('conflict(s) left as they are'));
|
|
1710
|
+
expect(readFileSync(stalePath(), 'utf-8')).toBe(edited);
|
|
1711
|
+
expect(lockedCard()[STALE]).toEqual({ hash: hashContent(stale) });
|
|
1712
|
+
});
|
|
1713
|
+
it('deletes one you edited with --force', async () => {
|
|
1714
|
+
interactive = false;
|
|
1715
|
+
setup({ onDisk: edited });
|
|
1716
|
+
const { updateCommand } = await import('./update');
|
|
1717
|
+
await updateCommand({ force: true });
|
|
1718
|
+
expect(existsSync(stalePath())).toBe(false);
|
|
1719
|
+
expect(lockedCard()).not.toHaveProperty(STALE);
|
|
1720
|
+
});
|
|
1721
|
+
describe('with --selective', () => {
|
|
1722
|
+
it('asks about the deletion, and "Skip for now" keeps the file and its entry', async () => {
|
|
1723
|
+
setup();
|
|
1724
|
+
const p = await import('@clack/prompts');
|
|
1725
|
+
p.select.mockResolvedValueOnce('keep');
|
|
1726
|
+
const { updateCommand } = await import('./update');
|
|
1727
|
+
await updateCommand({ selective: true });
|
|
1728
|
+
expect(p.select).toHaveBeenCalledWith(expect.objectContaining({
|
|
1729
|
+
message: expect.stringMatching(/upstream no longer ships it$/),
|
|
1730
|
+
}));
|
|
1731
|
+
expect(await selectOptions()).toEqual([
|
|
1732
|
+
expect.objectContaining({ value: 'diff' }),
|
|
1733
|
+
expect.objectContaining({ value: 'keep', label: 'Skip for now' }),
|
|
1734
|
+
{ value: 'upstream', label: 'Delete it', hint: 'remove the file' },
|
|
1735
|
+
]);
|
|
1736
|
+
expect(readFileSync(stalePath(), 'utf-8')).toBe(stale);
|
|
1737
|
+
expect(lockedCard()[STALE]).toEqual({ hash: hashContent(stale) });
|
|
1738
|
+
});
|
|
1739
|
+
it('deletes it when accepted', async () => {
|
|
1740
|
+
setup();
|
|
1741
|
+
const { updateCommand } = await import('./update');
|
|
1742
|
+
await updateCommand({ selective: true });
|
|
1743
|
+
expect(existsSync(stalePath())).toBe(false);
|
|
1744
|
+
expect(lockedCard()).not.toHaveProperty(STALE);
|
|
1745
|
+
});
|
|
1746
|
+
});
|
|
1747
|
+
describe('already gone from disk', () => {
|
|
1748
|
+
it('drops its entry when nothing else changed', async () => {
|
|
1749
|
+
setup({ onDisk: null });
|
|
1750
|
+
const p = await import('@clack/prompts');
|
|
1751
|
+
const { updateCommand } = await import('./update');
|
|
1752
|
+
await updateCommand({});
|
|
1753
|
+
expect(p.confirm).not.toHaveBeenCalled();
|
|
1754
|
+
expect(await messages()).not.toContain(STALE);
|
|
1755
|
+
expect(p.outro).toHaveBeenCalledWith('Everything is up to date.');
|
|
1756
|
+
expect(lockedCard()).toEqual({ 'card.ts': { hash: hashContent(card) } });
|
|
1757
|
+
});
|
|
1758
|
+
it('drops its entry alongside an update', async () => {
|
|
1759
|
+
const upstreamCard = 'export const Card = { v: 2 };';
|
|
1760
|
+
setup({ onDisk: null, upstreamCard });
|
|
1761
|
+
const { updateCommand } = await import('./update');
|
|
1762
|
+
await updateCommand({});
|
|
1763
|
+
expect(lockedCard()).toEqual({
|
|
1764
|
+
'card.ts': { hash: hashContent(upstreamCard) },
|
|
1765
|
+
});
|
|
1766
|
+
});
|
|
1767
|
+
});
|
|
1768
|
+
it("leaves the tests a project without the testing library doesn't get", async () => {
|
|
1769
|
+
// Copied before dolan checked for the library. Upstream still ships them,
|
|
1770
|
+
// so they aren't deleted: `doctor` says what to do about them instead.
|
|
1771
|
+
const test = "import '@blotch/angular-lynx-testing-library';";
|
|
1772
|
+
fixture = createFixture({
|
|
1773
|
+
config: DEFAULT_CONFIG,
|
|
1774
|
+
lockfile: {
|
|
1775
|
+
version: 1,
|
|
1776
|
+
components: {
|
|
1777
|
+
card: {
|
|
1778
|
+
'card.ts': { hash: hashContent(card) },
|
|
1779
|
+
'card.test.ts': { hash: hashContent(test) },
|
|
1780
|
+
},
|
|
1781
|
+
},
|
|
1782
|
+
theme: {},
|
|
1783
|
+
},
|
|
1784
|
+
components: { card: { 'card.ts': card, 'card.test.ts': test } },
|
|
1785
|
+
uiSource: { card: { 'card.ts': card, 'card.test.ts': test } },
|
|
1786
|
+
});
|
|
1787
|
+
vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
|
|
1788
|
+
const { updateCommand } = await import('./update');
|
|
1789
|
+
await updateCommand({});
|
|
1790
|
+
expect(readInstalledFile(fixture.dir, 'card', 'card.test.ts')).toBe(test);
|
|
1791
|
+
expect(lockedCard()['card.test.ts']).toEqual({ hash: hashContent(test) });
|
|
1792
|
+
});
|
|
1793
|
+
});
|
|
1794
|
+
// An app that moved from Tailwind 3 to Tailwind 4 kept the plugin and its
|
|
1795
|
+
// test: `update` only looked at the theme files the app takes now, so
|
|
1796
|
+
// nothing offered to delete them, though nothing imports the plugin and the
|
|
1797
|
+
// app's coverage counted it.
|
|
1798
|
+
describe('updateCommand — theme files for the Tailwind version the app left', () => {
|
|
1799
|
+
const theme = {
|
|
1800
|
+
'default.css': ':root { --primary: rgba(1, 1, 1, 1); }',
|
|
1801
|
+
'dark.css': 'page.dark { --primary: rgba(2, 2, 2, 1); }',
|
|
1802
|
+
'tailwind.css': '@theme inline { --color-primary: var(--primary); }',
|
|
1803
|
+
'tailwind-plugin.ts': 'export const blotchPlugin = {};',
|
|
1804
|
+
'tailwind-plugin.test.ts': "import { blotchPlugin } from './tailwind-plugin';",
|
|
1805
|
+
};
|
|
1806
|
+
const PLUGIN = 'tailwind-plugin.ts';
|
|
1807
|
+
const PLUGIN_TEST = 'tailwind-plugin.test.ts';
|
|
1808
|
+
const edited = `${theme[PLUGIN]}\n// mine\n`;
|
|
1809
|
+
const themePath = (file) => join(fixture.dir, DEFAULT_CONFIG.aliases.theme, file);
|
|
1810
|
+
const recorded = (files) => Object.fromEntries(files.map((file) => [file, { hash: hashContent(theme[file]) }]));
|
|
1811
|
+
/**
|
|
1812
|
+
* An app `init` set up on Tailwind 3 with the testing library, now on
|
|
1813
|
+
* `tailwind` (4 unless it says otherwise). Dolan's theme ships every file.
|
|
1814
|
+
* `locked` are the theme files the lockfile records and the folder has as
|
|
1815
|
+
* copied, and `onDisk` overrides what the folder has: a string replaces a
|
|
1816
|
+
* file, and null deletes it.
|
|
1817
|
+
*/
|
|
1818
|
+
const setup = ({ tailwind = 4, locked = ['default.css', 'dark.css', PLUGIN, PLUGIN_TEST], onDisk = {}, } = {}) => {
|
|
1819
|
+
fixture = createFixture({
|
|
1820
|
+
config: DEFAULT_CONFIG,
|
|
1821
|
+
lockfile: { version: 1, components: {}, theme: recorded(locked) },
|
|
1822
|
+
themeFiles: theme,
|
|
1823
|
+
});
|
|
1824
|
+
onTailwind(fixture.dir, tailwind, { testing: true });
|
|
1825
|
+
for (const file of locked)
|
|
1826
|
+
writeFileSync(themePath(file), theme[file]);
|
|
1827
|
+
for (const [file, content] of Object.entries(onDisk)) {
|
|
1828
|
+
if (content === null)
|
|
1829
|
+
rmSync(themePath(file));
|
|
1830
|
+
else
|
|
1831
|
+
writeFileSync(themePath(file), content);
|
|
1832
|
+
}
|
|
1833
|
+
vi.spyOn(process, 'cwd').mockReturnValue(fixture.dir);
|
|
1834
|
+
};
|
|
1835
|
+
const lockedTheme = () => readLockfile(fixture.dir).theme;
|
|
1836
|
+
/**
|
|
1837
|
+
* What a log function was called with, colors stripped.
|
|
1838
|
+
*/
|
|
1839
|
+
const logged = (log) => log.mock.calls
|
|
1840
|
+
.map(([message]) => stripVTControlCharacters(message))
|
|
1841
|
+
.join('\n');
|
|
1842
|
+
beforeEach(async () => {
|
|
1843
|
+
// A reset drops answers an earlier test queued and never used.
|
|
1844
|
+
const p = await import('@clack/prompts');
|
|
1845
|
+
p.select
|
|
1846
|
+
.mockReset()
|
|
1847
|
+
.mockResolvedValue('upstream');
|
|
1848
|
+
});
|
|
1849
|
+
it('deletes the plugin and its test nobody edited, with their lockfile entries, after the usual question', async () => {
|
|
1850
|
+
setup();
|
|
1851
|
+
const p = await import('@clack/prompts');
|
|
1852
|
+
const { updateCommand } = await import('./update');
|
|
1853
|
+
await updateCommand({});
|
|
1854
|
+
expect(p.confirm).toHaveBeenCalledWith(expect.objectContaining({ message: 'Apply 3 auto-update(s)?' }));
|
|
1855
|
+
expect(p.select).not.toHaveBeenCalled();
|
|
1856
|
+
expect(logged(p.log.message)).toContain([
|
|
1857
|
+
'Shared files:',
|
|
1858
|
+
' ✓ theme/default.css — up to date',
|
|
1859
|
+
' ✓ theme/dark.css — up to date',
|
|
1860
|
+
' + theme/tailwind.css — new file from upstream',
|
|
1861
|
+
' − theme/tailwind-plugin.ts — the app uses Tailwind 4 → delete',
|
|
1862
|
+
' − theme/tailwind-plugin.test.ts — the app uses Tailwind 4 → delete',
|
|
1863
|
+
].join('\n'));
|
|
1864
|
+
expect(existsSync(themePath(PLUGIN))).toBe(false);
|
|
1865
|
+
expect(existsSync(themePath(PLUGIN_TEST))).toBe(false);
|
|
1866
|
+
expect(readFileSync(themePath('tailwind.css'), 'utf-8')).toBe(theme['tailwind.css']);
|
|
1867
|
+
expect(lockedTheme()).toEqual(recorded(['default.css', 'dark.css', 'tailwind.css']));
|
|
1868
|
+
expect(logged(p.log.success)).toContain('2 theme file(s) deleted: the app uses Tailwind 4.');
|
|
1869
|
+
});
|
|
1870
|
+
it('deletes them without a terminal too', async () => {
|
|
1871
|
+
interactive = false;
|
|
1872
|
+
setup();
|
|
1873
|
+
const p = await import('@clack/prompts');
|
|
1874
|
+
const { updateCommand } = await import('./update');
|
|
1875
|
+
await updateCommand({});
|
|
1876
|
+
expect(p.confirm).not.toHaveBeenCalled();
|
|
1877
|
+
expect(existsSync(themePath(PLUGIN))).toBe(false);
|
|
1878
|
+
expect(lockedTheme()).not.toHaveProperty(PLUGIN);
|
|
1879
|
+
expect(p.outro).toHaveBeenCalledWith('Update complete.');
|
|
1880
|
+
});
|
|
1881
|
+
it('asks before deleting a plugin you edited, and deletes it when told to', async () => {
|
|
1882
|
+
setup({ onDisk: { [PLUGIN]: edited } });
|
|
1883
|
+
const p = await import('@clack/prompts');
|
|
1884
|
+
const { updateCommand } = await import('./update');
|
|
1885
|
+
await updateCommand({});
|
|
1886
|
+
expect(logged(p.log.message)).toContain(' ⚠ theme/tailwind-plugin.ts — the app uses Tailwind 4, and you modified it');
|
|
1887
|
+
expect(p.select).toHaveBeenCalledTimes(1);
|
|
1888
|
+
const [{ message, options }] = p.select.mock
|
|
1889
|
+
.calls[0];
|
|
1890
|
+
expect(stripVTControlCharacters(message)).toBe('shared/theme/tailwind-plugin.ts — you modified it, and the app uses Tailwind 4');
|
|
1891
|
+
// A deletion is all or nothing, so there are no hunks to review.
|
|
1892
|
+
expect(options.map(({ value }) => value)).toEqual([
|
|
1893
|
+
'diff',
|
|
1894
|
+
'keep',
|
|
1895
|
+
'upstream',
|
|
1896
|
+
]);
|
|
1897
|
+
expect(existsSync(themePath(PLUGIN))).toBe(false);
|
|
1898
|
+
expect(existsSync(themePath(PLUGIN_TEST))).toBe(false);
|
|
1899
|
+
expect(lockedTheme()).not.toHaveProperty(PLUGIN);
|
|
1900
|
+
});
|
|
1901
|
+
it('keeps a plugin you edited when told to, as your own file from then on', async () => {
|
|
1902
|
+
setup({ onDisk: { [PLUGIN]: edited } });
|
|
1903
|
+
const p = await import('@clack/prompts');
|
|
1904
|
+
p.select.mockResolvedValueOnce('keep');
|
|
1905
|
+
const { updateCommand } = await import('./update');
|
|
1906
|
+
await updateCommand({});
|
|
1907
|
+
expect(readFileSync(themePath(PLUGIN), 'utf-8')).toBe(edited);
|
|
1908
|
+
expect(lockedTheme()).not.toHaveProperty(PLUGIN);
|
|
1909
|
+
expect(logged(p.log.info)).toContain('1 file(s) kept as-is.');
|
|
1910
|
+
vi.clearAllMocks();
|
|
1911
|
+
await updateCommand({});
|
|
1912
|
+
expect(p.select).not.toHaveBeenCalled();
|
|
1913
|
+
expect(readFileSync(themePath(PLUGIN), 'utf-8')).toBe(edited);
|
|
1914
|
+
});
|
|
1915
|
+
it('leaves a plugin you edited for a terminal to ask about without one, and exits 1', async () => {
|
|
1916
|
+
interactive = false;
|
|
1917
|
+
setup({ onDisk: { [PLUGIN]: edited } });
|
|
1918
|
+
const p = await import('@clack/prompts');
|
|
1919
|
+
const { updateCommand } = await import('./update');
|
|
1920
|
+
await expect(updateCommand({})).rejects.toThrow('process.exit(1)');
|
|
1921
|
+
expect(p.select).not.toHaveBeenCalled();
|
|
1922
|
+
expect(logged(p.log.warn)).toContain('1 conflict(s) left as they are');
|
|
1923
|
+
expect(readFileSync(themePath(PLUGIN), 'utf-8')).toBe(edited);
|
|
1924
|
+
expect(lockedTheme()[PLUGIN]).toEqual({
|
|
1925
|
+
hash: hashContent(theme[PLUGIN]),
|
|
1926
|
+
});
|
|
1927
|
+
// The test nobody edited goes anyway, as auto-updates do.
|
|
1928
|
+
expect(existsSync(themePath(PLUGIN_TEST))).toBe(false);
|
|
1929
|
+
});
|
|
1930
|
+
it('deletes a plugin you edited with --force', async () => {
|
|
1931
|
+
interactive = false;
|
|
1932
|
+
setup({ onDisk: { [PLUGIN]: edited } });
|
|
1933
|
+
const { updateCommand } = await import('./update');
|
|
1934
|
+
await updateCommand({ force: true });
|
|
1935
|
+
expect(existsSync(themePath(PLUGIN))).toBe(false);
|
|
1936
|
+
expect(lockedTheme()).not.toHaveProperty(PLUGIN);
|
|
1937
|
+
});
|
|
1938
|
+
describe('with --selective', () => {
|
|
1939
|
+
it('asks about each deletion, and "Skip for now" keeps the file and its entry', async () => {
|
|
1940
|
+
setup();
|
|
1941
|
+
const p = await import('@clack/prompts');
|
|
1942
|
+
p.select.mockResolvedValue('keep');
|
|
1943
|
+
const { updateCommand } = await import('./update');
|
|
1944
|
+
await updateCommand({ selective: true });
|
|
1945
|
+
const messages = p.select.mock.calls.map(([{ message }]) => stripVTControlCharacters(message));
|
|
1946
|
+
expect(messages).toEqual([
|
|
1947
|
+
'shared/theme/tailwind.css — new file from upstream',
|
|
1948
|
+
'shared/theme/tailwind-plugin.ts — the app uses Tailwind 4',
|
|
1949
|
+
'shared/theme/tailwind-plugin.test.ts — the app uses Tailwind 4',
|
|
1950
|
+
]);
|
|
1951
|
+
expect(readFileSync(themePath(PLUGIN), 'utf-8')).toBe(theme[PLUGIN]);
|
|
1952
|
+
expect(lockedTheme()).toEqual(recorded(['default.css', 'dark.css', PLUGIN, PLUGIN_TEST]));
|
|
1953
|
+
expect(logged(p.log.info)).toContain('3 file(s) skipped');
|
|
1954
|
+
});
|
|
1955
|
+
it('deletes them when accepted', async () => {
|
|
1956
|
+
setup();
|
|
1957
|
+
const { updateCommand } = await import('./update');
|
|
1958
|
+
await updateCommand({ selective: true });
|
|
1959
|
+
expect(existsSync(themePath(PLUGIN))).toBe(false);
|
|
1960
|
+
expect(existsSync(themePath(PLUGIN_TEST))).toBe(false);
|
|
1961
|
+
expect(lockedTheme()).toEqual(recorded(['default.css', 'dark.css', 'tailwind.css']));
|
|
1962
|
+
});
|
|
1963
|
+
});
|
|
1964
|
+
describe('already deleted by hand', () => {
|
|
1965
|
+
it('drops their entries when nothing else changed', async () => {
|
|
1966
|
+
setup({
|
|
1967
|
+
locked: [
|
|
1968
|
+
'default.css',
|
|
1969
|
+
'dark.css',
|
|
1970
|
+
'tailwind.css',
|
|
1971
|
+
PLUGIN,
|
|
1972
|
+
PLUGIN_TEST,
|
|
1973
|
+
],
|
|
1974
|
+
onDisk: { [PLUGIN]: null, [PLUGIN_TEST]: null },
|
|
1975
|
+
});
|
|
1976
|
+
const p = await import('@clack/prompts');
|
|
1977
|
+
const { updateCommand } = await import('./update');
|
|
1978
|
+
await updateCommand({});
|
|
1979
|
+
expect(p.confirm).not.toHaveBeenCalled();
|
|
1980
|
+
expect(logged(p.log.message)).not.toContain(PLUGIN);
|
|
1981
|
+
expect(p.outro).toHaveBeenCalledWith('Everything is up to date.');
|
|
1982
|
+
expect(lockedTheme()).toEqual(recorded(['default.css', 'dark.css', 'tailwind.css']));
|
|
1983
|
+
});
|
|
1984
|
+
it('drops their entries alongside an update', async () => {
|
|
1985
|
+
setup({ onDisk: { [PLUGIN]: null, [PLUGIN_TEST]: null } });
|
|
1986
|
+
const p = await import('@clack/prompts');
|
|
1987
|
+
const { updateCommand } = await import('./update');
|
|
1988
|
+
await updateCommand({});
|
|
1989
|
+
expect(p.confirm).toHaveBeenCalledWith(expect.objectContaining({ message: 'Apply 1 auto-update(s)?' }));
|
|
1990
|
+
expect(logged(p.log.success)).not.toContain('deleted');
|
|
1991
|
+
expect(lockedTheme()).toEqual(recorded(['default.css', 'dark.css', 'tailwind.css']));
|
|
1992
|
+
});
|
|
1993
|
+
});
|
|
1994
|
+
it('deletes tailwind.css after a move back to Tailwind 3, and brings the plugin back', async () => {
|
|
1995
|
+
setup({ tailwind: 3, locked: ['default.css', 'dark.css', 'tailwind.css'] });
|
|
1996
|
+
const p = await import('@clack/prompts');
|
|
1997
|
+
const { updateCommand } = await import('./update');
|
|
1998
|
+
await updateCommand({});
|
|
1999
|
+
expect(logged(p.log.message)).toContain(' − theme/tailwind.css — the app uses Tailwind 3 → delete');
|
|
2000
|
+
expect(existsSync(themePath('tailwind.css'))).toBe(false);
|
|
2001
|
+
expect(readFileSync(themePath(PLUGIN), 'utf-8')).toBe(theme[PLUGIN]);
|
|
2002
|
+
expect(lockedTheme()).toEqual(recorded(['default.css', 'dark.css', PLUGIN, PLUGIN_TEST]));
|
|
2003
|
+
expect(logged(p.log.success)).toContain('1 theme file(s) deleted: the app uses Tailwind 3.');
|
|
2004
|
+
});
|
|
2005
|
+
it("keeps the plugin's test that a project without the testing library doesn't get", async () => {
|
|
2006
|
+
// Upstream still ships it to a Tailwind 3 app, as it does a component's
|
|
2007
|
+
// tests, so it isn't deleted for want of the library.
|
|
2008
|
+
setup({ tailwind: 3 });
|
|
2009
|
+
onTailwind(fixture.dir, 3);
|
|
2010
|
+
const { updateCommand } = await import('./update');
|
|
2011
|
+
await updateCommand({});
|
|
2012
|
+
expect(readFileSync(themePath(PLUGIN_TEST), 'utf-8')).toBe(theme[PLUGIN_TEST]);
|
|
2013
|
+
expect(lockedTheme()[PLUGIN_TEST]).toEqual({
|
|
2014
|
+
hash: hashContent(theme[PLUGIN_TEST]),
|
|
2015
|
+
});
|
|
2016
|
+
});
|
|
2017
|
+
// Tailwind 3 apps were mostly set up by a release whose `init` recorded no
|
|
2018
|
+
// theme hashes, so their plugin isn't in the lockfile.
|
|
2019
|
+
describe('copied by an older init', () => {
|
|
2020
|
+
it('deletes an untouched copy', async () => {
|
|
2021
|
+
setup({
|
|
2022
|
+
locked: [],
|
|
2023
|
+
onDisk: { [PLUGIN]: shippedTheme('tailwind-plugin.ts') },
|
|
2024
|
+
});
|
|
2025
|
+
const { updateCommand } = await import('./update');
|
|
2026
|
+
await updateCommand({});
|
|
2027
|
+
expect(existsSync(themePath(PLUGIN))).toBe(false);
|
|
2028
|
+
expect(lockedTheme()).toEqual(recorded(['default.css', 'dark.css', 'tailwind.css']));
|
|
2029
|
+
});
|
|
2030
|
+
it('leaves an edited copy alone, since nothing says it was dolan’s', async () => {
|
|
2031
|
+
const mine = `${shippedTheme('tailwind-plugin.ts')}\n// mine\n`;
|
|
2032
|
+
setup({ locked: [], onDisk: { [PLUGIN]: mine } });
|
|
2033
|
+
const p = await import('@clack/prompts');
|
|
2034
|
+
const { updateCommand } = await import('./update');
|
|
2035
|
+
await updateCommand({});
|
|
2036
|
+
expect(p.select).not.toHaveBeenCalled();
|
|
2037
|
+
expect(logged(p.log.message)).not.toContain(PLUGIN);
|
|
2038
|
+
expect(readFileSync(themePath(PLUGIN), 'utf-8')).toBe(mine);
|
|
2039
|
+
});
|
|
2040
|
+
});
|
|
2041
|
+
});
|
|
1475
2042
|
//# sourceMappingURL=update.test.js.map
|