ui-thing 0.2.6 → 0.2.7
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/.github/workflows/test.yml +4 -4
- package/CHANGELOG.md +35 -0
- package/dist/index.js +24 -28
- package/dist/index.js.map +1 -1
- package/package.json +22 -21
- package/src/commands/block.ts +165 -0
- package/src/commands/prose.ts +200 -0
- package/src/index.ts +4 -0
- package/src/templates/prettier.ts +1 -1
- package/src/templates/shortcuts.ts +1 -7
- package/src/templates/vs-code.ts +10 -2
- package/src/types.ts +52 -7
- package/src/utils/fetchBlockCategories.ts +19 -0
- package/src/utils/fetchBlocks.ts +21 -0
- package/src/utils/fetchProseComponents.ts +21 -0
- package/src/utils/promptForComponents.ts +7 -4
- package/tests/utils/addPrettierConfig.test.ts +10 -17
- package/tests/utils/compareUIConfig.test.ts +3 -3
- package/tests/utils/constants.test.ts +136 -0
- package/tests/utils/detectNuxtVersion.test.ts +97 -0
- package/tests/utils/fetchBlockCategories.test.ts +59 -0
- package/tests/utils/fetchBlocks.test.ts +59 -0
- package/tests/utils/fetchComponents.test.ts +92 -0
- package/tests/utils/fetchProseComponents.test.ts +62 -0
- package/tests/utils/installPackages.test.ts +114 -0
- package/tests/utils/printFancyBoxMessage.test.ts +66 -0
- package/tests/utils/promptForComponents.test.ts +94 -0
- package/tests/utils/writeFile.test.ts +56 -0
- package/vite.config.ts +14 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { installPackages } from "../../src/utils/installPackages";
|
|
4
|
+
|
|
5
|
+
// Mock execa at the module level
|
|
6
|
+
vi.mock("execa", () => ({
|
|
7
|
+
execa: vi.fn(),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
describe("utils/installPackages", () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
vi.clearAllMocks();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
vi.restoreAllMocks();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("should install dependencies with npm", async () => {
|
|
20
|
+
const { execa } = await import("execa");
|
|
21
|
+
const mockExeca = vi.mocked(execa);
|
|
22
|
+
mockExeca.mockResolvedValue({} as any);
|
|
23
|
+
|
|
24
|
+
await installPackages("npm", ["vue", "axios"]);
|
|
25
|
+
|
|
26
|
+
expect(mockExeca).toHaveBeenCalledWith("npm", ["install", "vue", "axios"]);
|
|
27
|
+
expect(mockExeca).toHaveBeenCalledWith(["npx -y nuxt prepare"]);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("should install dev dependencies with npm", async () => {
|
|
31
|
+
const { execa } = await import("execa");
|
|
32
|
+
const mockExeca = vi.mocked(execa);
|
|
33
|
+
mockExeca.mockResolvedValue({} as any);
|
|
34
|
+
|
|
35
|
+
await installPackages("npm", undefined, ["typescript", "vitest"]);
|
|
36
|
+
|
|
37
|
+
expect(mockExeca).toHaveBeenCalledWith("npm", ["install", "-D", "typescript", "vitest"]);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should install both dependencies and dev dependencies", async () => {
|
|
41
|
+
const { execa } = await import("execa");
|
|
42
|
+
const mockExeca = vi.mocked(execa);
|
|
43
|
+
mockExeca.mockResolvedValue({} as any);
|
|
44
|
+
|
|
45
|
+
await installPackages("npm", ["vue"], ["typescript"]);
|
|
46
|
+
|
|
47
|
+
expect(mockExeca).toHaveBeenCalledWith("npm", ["install", "vue"]);
|
|
48
|
+
expect(mockExeca).toHaveBeenCalledWith("npm", ["install", "-D", "typescript"]);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should use yarn add instead of install", async () => {
|
|
52
|
+
const { execa } = await import("execa");
|
|
53
|
+
const mockExeca = vi.mocked(execa);
|
|
54
|
+
mockExeca.mockResolvedValue({} as any);
|
|
55
|
+
|
|
56
|
+
await installPackages("yarn", ["vue"], ["typescript"]);
|
|
57
|
+
|
|
58
|
+
expect(mockExeca).toHaveBeenCalledWith("yarn", ["add", "vue"]);
|
|
59
|
+
expect(mockExeca).toHaveBeenCalledWith("yarn", ["add", "-D", "typescript"]);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("should handle string input for deps", async () => {
|
|
63
|
+
const { execa } = await import("execa");
|
|
64
|
+
const mockExeca = vi.mocked(execa);
|
|
65
|
+
mockExeca.mockResolvedValue({} as any);
|
|
66
|
+
|
|
67
|
+
await installPackages("npm", "vue");
|
|
68
|
+
|
|
69
|
+
expect(mockExeca).toHaveBeenCalledWith("npm", ["install", "vue"]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("should handle string input for devDeps", async () => {
|
|
73
|
+
const { execa } = await import("execa");
|
|
74
|
+
const mockExeca = vi.mocked(execa);
|
|
75
|
+
mockExeca.mockResolvedValue({} as any);
|
|
76
|
+
|
|
77
|
+
await installPackages("npm", undefined, "typescript");
|
|
78
|
+
|
|
79
|
+
expect(mockExeca).toHaveBeenCalledWith("npm", ["install", "-D", "typescript"]);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("should work with pnpm", async () => {
|
|
83
|
+
const { execa } = await import("execa");
|
|
84
|
+
const mockExeca = vi.mocked(execa);
|
|
85
|
+
mockExeca.mockResolvedValue({} as any);
|
|
86
|
+
|
|
87
|
+
await installPackages("pnpm", ["vue"], ["typescript"]);
|
|
88
|
+
|
|
89
|
+
expect(mockExeca).toHaveBeenCalledWith("pnpm", ["install", "vue"]);
|
|
90
|
+
expect(mockExeca).toHaveBeenCalledWith("pnpm", ["install", "-D", "typescript"]);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("should run nuxt prepare after installation", async () => {
|
|
94
|
+
const { execa } = await import("execa");
|
|
95
|
+
const mockExeca = vi.mocked(execa);
|
|
96
|
+
mockExeca.mockResolvedValue({} as any);
|
|
97
|
+
|
|
98
|
+
await installPackages("npm", ["vue"]);
|
|
99
|
+
|
|
100
|
+
expect(mockExeca).toHaveBeenCalledWith(["npx -y nuxt prepare"]);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("should skip installing empty dependencies", async () => {
|
|
104
|
+
const { execa } = await import("execa");
|
|
105
|
+
const mockExeca = vi.mocked(execa);
|
|
106
|
+
mockExeca.mockResolvedValue({} as any);
|
|
107
|
+
|
|
108
|
+
await installPackages("npm", [], []);
|
|
109
|
+
|
|
110
|
+
// Should only call nuxt prepare
|
|
111
|
+
expect(mockExeca).toHaveBeenCalledTimes(1);
|
|
112
|
+
expect(mockExeca).toHaveBeenCalledWith(["npx -y nuxt prepare"]);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { printFancyBoxMessage } from "../../src/utils/printFancyBoxMessage";
|
|
4
|
+
|
|
5
|
+
describe("utils/printFancyBoxMessage", () => {
|
|
6
|
+
let consoleLogSpy: any;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
10
|
+
vi.clearAllMocks();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
vi.restoreAllMocks();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("should print a fancy box message with title", () => {
|
|
18
|
+
printFancyBoxMessage("Test Title");
|
|
19
|
+
|
|
20
|
+
expect(consoleLogSpy).toHaveBeenCalled();
|
|
21
|
+
const output = consoleLogSpy.mock.calls[0][0];
|
|
22
|
+
// ASCII art contains the title but not as plain text
|
|
23
|
+
expect(output).toBeTruthy();
|
|
24
|
+
expect(typeof output).toBe("string");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should print a fancy box message with title and description", () => {
|
|
28
|
+
printFancyBoxMessage("Test Title", "Test description");
|
|
29
|
+
|
|
30
|
+
// Should be called at least once (box is always printed)
|
|
31
|
+
expect(consoleLogSpy).toHaveBeenCalled();
|
|
32
|
+
const lastCall = consoleLogSpy.mock.calls[consoleLogSpy.mock.calls.length - 1][0];
|
|
33
|
+
expect(lastCall).toContain("Test description");
|
|
34
|
+
printFancyBoxMessage("Test", undefined, {
|
|
35
|
+
box: { borderColor: "red", borderStyle: "double" },
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
expect(consoleLogSpy).toHaveBeenCalled();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should accept custom figlet font", () => {
|
|
42
|
+
printFancyBoxMessage("Test", undefined, {
|
|
43
|
+
figletFont: "Banner",
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
expect(consoleLogSpy).toHaveBeenCalled();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("should handle empty title gracefully", () => {
|
|
50
|
+
printFancyBoxMessage("");
|
|
51
|
+
|
|
52
|
+
expect(consoleLogSpy).toHaveBeenCalled();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should handle special characters in title", () => {
|
|
56
|
+
printFancyBoxMessage("Test!@#$%");
|
|
57
|
+
|
|
58
|
+
expect(consoleLogSpy).toHaveBeenCalled();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should only print box when no description provided", () => {
|
|
62
|
+
printFancyBoxMessage("Test");
|
|
63
|
+
|
|
64
|
+
expect(consoleLogSpy).toHaveBeenCalledTimes(1);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { Component } from "../../src/types";
|
|
4
|
+
import { promptUserForComponents } from "../../src/utils/promptForComponents";
|
|
5
|
+
|
|
6
|
+
// Mock prompts
|
|
7
|
+
vi.mock("prompts");
|
|
8
|
+
|
|
9
|
+
describe("utils/promptForComponents", () => {
|
|
10
|
+
const mockComponents: Component[] = [
|
|
11
|
+
{
|
|
12
|
+
name: "Button",
|
|
13
|
+
value: "button",
|
|
14
|
+
files: [],
|
|
15
|
+
utils: [],
|
|
16
|
+
composables: [],
|
|
17
|
+
plugins: [],
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: "Input",
|
|
21
|
+
value: "input",
|
|
22
|
+
files: [],
|
|
23
|
+
utils: [],
|
|
24
|
+
composables: [],
|
|
25
|
+
plugins: [],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: "Card",
|
|
29
|
+
value: "card",
|
|
30
|
+
files: [],
|
|
31
|
+
utils: [],
|
|
32
|
+
composables: [],
|
|
33
|
+
plugins: [],
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
vi.clearAllMocks();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
vi.restoreAllMocks();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should return all component values when all is true", async () => {
|
|
46
|
+
const result = await promptUserForComponents(true, mockComponents);
|
|
47
|
+
|
|
48
|
+
expect(result).toEqual(["button", "input", "card"]);
|
|
49
|
+
expect(result).toHaveLength(3);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("should prompt user for selection when all is false", async () => {
|
|
53
|
+
const prompts = await import("prompts");
|
|
54
|
+
vi.mocked(prompts.default).mockResolvedValue({ components: ["button", "input"] });
|
|
55
|
+
|
|
56
|
+
const result = await promptUserForComponents(false, mockComponents);
|
|
57
|
+
|
|
58
|
+
expect(result).toEqual(["button", "input"]);
|
|
59
|
+
expect(prompts.default).toHaveBeenCalled();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("should return empty array when user cancels selection", async () => {
|
|
63
|
+
const prompts = await import("prompts");
|
|
64
|
+
vi.mocked(prompts.default).mockResolvedValue({ components: undefined });
|
|
65
|
+
|
|
66
|
+
const result = await promptUserForComponents(false, mockComponents);
|
|
67
|
+
|
|
68
|
+
expect(result).toBeUndefined();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("should handle empty components list", async () => {
|
|
72
|
+
const result = await promptUserForComponents(true, []);
|
|
73
|
+
|
|
74
|
+
expect(result).toEqual([]);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("should pass correct choices to prompts", async () => {
|
|
78
|
+
const prompts = await import("prompts");
|
|
79
|
+
vi.mocked(prompts.default).mockResolvedValue({ components: [] });
|
|
80
|
+
|
|
81
|
+
await promptUserForComponents(false, mockComponents);
|
|
82
|
+
|
|
83
|
+
expect(prompts.default).toHaveBeenCalledWith({
|
|
84
|
+
type: "autocompleteMultiselect",
|
|
85
|
+
name: "components",
|
|
86
|
+
message: "Select the components you want to add",
|
|
87
|
+
choices: [
|
|
88
|
+
{ title: "Button", value: "button" },
|
|
89
|
+
{ title: "Input", value: "input" },
|
|
90
|
+
{ title: "Card", value: "card" },
|
|
91
|
+
],
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
|
|
5
|
+
import * as fileExistsModule from "../../src/utils/fileExists";
|
|
6
|
+
import { writeFile } from "../../src/utils/writeFile";
|
|
7
|
+
|
|
8
|
+
describe("utils/writeFile", () => {
|
|
9
|
+
const mockFilePath = "/test/dir/file.txt";
|
|
10
|
+
const mockContent = "test content";
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vi.clearAllMocks();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
vi.restoreAllMocks();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should create directory and write file when file does not exist", async () => {
|
|
21
|
+
// Mock fileExists to return false
|
|
22
|
+
vi.spyOn(fileExistsModule, "fileExists").mockResolvedValue(false);
|
|
23
|
+
vi.spyOn(fs, "existsSync").mockReturnValue(false);
|
|
24
|
+
vi.spyOn(fs, "mkdirSync").mockImplementation(() => undefined);
|
|
25
|
+
vi.spyOn(fs, "writeFileSync").mockImplementation(() => {});
|
|
26
|
+
|
|
27
|
+
await writeFile(mockFilePath, mockContent);
|
|
28
|
+
|
|
29
|
+
expect(fileExistsModule.fileExists).toHaveBeenCalledWith(mockFilePath);
|
|
30
|
+
expect(fs.existsSync).toHaveBeenCalledWith(path.dirname(mockFilePath));
|
|
31
|
+
expect(fs.mkdirSync).toHaveBeenCalledWith(path.dirname(mockFilePath), { recursive: true });
|
|
32
|
+
expect(fs.writeFileSync).toHaveBeenCalledWith(mockFilePath, mockContent);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("should write file when file already exists", async () => {
|
|
36
|
+
vi.spyOn(fileExistsModule, "fileExists").mockResolvedValue(true);
|
|
37
|
+
vi.spyOn(fs, "writeFileSync").mockImplementation(() => {});
|
|
38
|
+
|
|
39
|
+
await writeFile(mockFilePath, mockContent);
|
|
40
|
+
|
|
41
|
+
expect(fileExistsModule.fileExists).toHaveBeenCalledWith(mockFilePath);
|
|
42
|
+
expect(fs.writeFileSync).toHaveBeenCalledWith(mockFilePath, mockContent);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should not create directory if it already exists", async () => {
|
|
46
|
+
vi.spyOn(fileExistsModule, "fileExists").mockResolvedValue(false);
|
|
47
|
+
vi.spyOn(fs, "existsSync").mockReturnValue(true);
|
|
48
|
+
const mkdirSpy = vi.spyOn(fs, "mkdirSync").mockImplementation(() => undefined);
|
|
49
|
+
vi.spyOn(fs, "writeFileSync").mockImplementation(() => {});
|
|
50
|
+
|
|
51
|
+
await writeFile(mockFilePath, mockContent);
|
|
52
|
+
|
|
53
|
+
expect(mkdirSpy).not.toHaveBeenCalled();
|
|
54
|
+
expect(fs.writeFileSync).toHaveBeenCalledWith(mockFilePath, mockContent);
|
|
55
|
+
});
|
|
56
|
+
});
|
package/vite.config.ts
CHANGED
|
@@ -2,8 +2,22 @@ import { defineConfig } from "vitest/config";
|
|
|
2
2
|
|
|
3
3
|
export default defineConfig({
|
|
4
4
|
test: {
|
|
5
|
+
globals: true,
|
|
6
|
+
environment: "node",
|
|
5
7
|
coverage: {
|
|
6
8
|
provider: "v8",
|
|
9
|
+
reporter: ["text", "json", "html"],
|
|
10
|
+
exclude: [
|
|
11
|
+
"node_modules/",
|
|
12
|
+
"dist/",
|
|
13
|
+
"tests/",
|
|
14
|
+
"**/*.test.ts",
|
|
15
|
+
"**/*.config.ts",
|
|
16
|
+
"**/types.ts",
|
|
17
|
+
],
|
|
7
18
|
},
|
|
19
|
+
mockReset: true,
|
|
20
|
+
restoreMocks: true,
|
|
21
|
+
clearMocks: true,
|
|
8
22
|
},
|
|
9
23
|
});
|