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.
@@ -1,10 +1,12 @@
1
- import * as execa from "execa";
2
1
  import fse from "fs-extra";
3
2
  import { afterEach, describe, expect, it, vi } from "vitest";
4
- import type { PathLike } from "fs";
5
3
 
6
4
  import * as testingFn from "../../src/utils/addPrettierConfig";
7
5
 
6
+ vi.mock("execa", () => ({
7
+ $: vi.fn(),
8
+ }));
9
+
8
10
  const currentDir = process.cwd();
9
11
 
10
12
  describe("utils/addPrettierConfig", () => {
@@ -14,7 +16,7 @@ describe("utils/addPrettierConfig", () => {
14
16
  });
15
17
 
16
18
  it("should ask the user if they want to overwrite the existing prettier config file if one exists", async () => {
17
- vi.spyOn(fse, "existsSync").mockImplementation((path: PathLike) => true);
19
+ vi.spyOn(fse, "existsSync").mockImplementation(() => true);
18
20
  vi.mock("prompts", async () => {
19
21
  const prompts = await vi.importActual<typeof import("prompts")>("prompts");
20
22
  return {
@@ -44,28 +46,19 @@ describe("utils/addPrettierConfig", () => {
44
46
  });
45
47
 
46
48
  it("should format files with prettier if format is true", async () => {
49
+ const { $ } = await import("execa");
50
+ const mockExeca$ = vi.mocked($);
51
+
47
52
  vi.spyOn(testingFn, "addPrettierConfig");
48
53
  vi.spyOn(fse, "existsSync").mockImplementation(() => false);
49
54
  vi.spyOn(fse, "writeFile").mockResolvedValue();
50
- vi.mock("execa", async () => {
51
- const execa = await vi.importActual<typeof import("execa")>("execa");
52
- return {
53
- ...execa,
54
- $: async () => {
55
- return true;
56
- },
57
- default: async () => {
58
- return true;
59
- },
60
- };
61
- });
62
- vi.spyOn(execa, "$");
55
+ mockExeca$.mockResolvedValue(true as any);
63
56
 
64
57
  const result = await testingFn.addPrettierConfig(currentDir, true);
65
58
  expect(result).toBe(true);
66
59
  expect(fse.existsSync).toHaveBeenCalledTimes(1);
67
60
  expect(fse.writeFile).toHaveBeenCalledTimes(1);
68
- expect(execa.$).toHaveBeenCalledTimes(1);
61
+ expect(mockExeca$).toHaveBeenCalledTimes(1);
69
62
  expect(testingFn.addPrettierConfig).toHaveBeenCalledTimes(1);
70
63
  });
71
64
  });
@@ -1,4 +1,4 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
2
 
3
3
  import { UIConfig } from "../../src/types";
4
4
  import * as testingFn from "../../src/utils/compareUIConfig";
@@ -8,13 +8,13 @@ const goodConfig: UIConfig = {
8
8
  nuxtVersion: 3,
9
9
  theme: "string",
10
10
  tailwindCSSLocation: "string",
11
- tailwindConfigLocation: "string",
12
11
  componentsLocation: "string",
13
12
  composablesLocation: "string",
14
13
  utilsLocation: "string",
15
14
  force: true,
16
15
  useDefaultFilename: true,
17
16
  packageManager: "string",
17
+ pluginsLocation: "string",
18
18
  };
19
19
 
20
20
  const badConfig = {
@@ -33,7 +33,7 @@ describe("utils/compareUIConfig", () => {
33
33
 
34
34
  it("should return false if properties are missing", async () => {
35
35
  // create spies
36
- vi.spyOn(configModule, "getUIConfig").mockResolvedValue(badConfig as UIConfig);
36
+ vi.spyOn(configModule, "getUIConfig").mockResolvedValue(badConfig as unknown as UIConfig);
37
37
  vi.spyOn(testingFn, "compareUIConfig");
38
38
 
39
39
  // call the function we are testing
@@ -0,0 +1,136 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import {
4
+ CSS_THEME_OPTIONS,
5
+ DEFAULT_CONFIG,
6
+ DEFAULT_CONFIG_NUXT4,
7
+ INIT_DEPS,
8
+ INIT_DEV_DEPS,
9
+ INIT_DEV_DEPS_PRETTIER,
10
+ INIT_MODULES,
11
+ PACKAGE_MANAGER_CHOICES,
12
+ UI_CONFIG_FILENAME,
13
+ } from "../../src/utils/constants";
14
+
15
+ describe("utils/constants", () => {
16
+ describe("UI_CONFIG_FILENAME", () => {
17
+ it("should be a string with correct filename", () => {
18
+ expect(UI_CONFIG_FILENAME).toBe("ui-thing.config.ts");
19
+ expect(typeof UI_CONFIG_FILENAME).toBe("string");
20
+ });
21
+ });
22
+
23
+ describe("DEFAULT_CONFIG", () => {
24
+ it("should have required properties for Nuxt 3", () => {
25
+ expect(DEFAULT_CONFIG).toHaveProperty("theme");
26
+ expect(DEFAULT_CONFIG).toHaveProperty("tailwindCSSLocation");
27
+ expect(DEFAULT_CONFIG).toHaveProperty("componentsLocation");
28
+ expect(DEFAULT_CONFIG).toHaveProperty("composablesLocation");
29
+ expect(DEFAULT_CONFIG).toHaveProperty("utilsLocation");
30
+ expect(DEFAULT_CONFIG).toHaveProperty("force");
31
+ expect(DEFAULT_CONFIG).toHaveProperty("useDefaultFilename");
32
+ expect(DEFAULT_CONFIG).toHaveProperty("packageManager");
33
+ });
34
+
35
+ it("should have correct Nuxt 3 paths", () => {
36
+ expect(DEFAULT_CONFIG.tailwindCSSLocation).toBe("assets/css/tailwind.css");
37
+ expect(DEFAULT_CONFIG.componentsLocation).toBe("components/Ui");
38
+ expect(DEFAULT_CONFIG.composablesLocation).toBe("composables");
39
+ });
40
+ });
41
+
42
+ describe("DEFAULT_CONFIG_NUXT4", () => {
43
+ it("should have required properties for Nuxt 4", () => {
44
+ expect(DEFAULT_CONFIG_NUXT4).toHaveProperty("theme");
45
+ expect(DEFAULT_CONFIG_NUXT4).toHaveProperty("tailwindCSSLocation");
46
+ expect(DEFAULT_CONFIG_NUXT4).toHaveProperty("componentsLocation");
47
+ });
48
+
49
+ it("should have correct Nuxt 4 paths with app/ prefix", () => {
50
+ expect(DEFAULT_CONFIG_NUXT4.tailwindCSSLocation).toBe("app/assets/css/tailwind.css");
51
+ expect(DEFAULT_CONFIG_NUXT4.componentsLocation).toBe("app/components/Ui");
52
+ expect(DEFAULT_CONFIG_NUXT4.composablesLocation).toBe("app/composables");
53
+ });
54
+ });
55
+
56
+ describe("INIT_DEPS", () => {
57
+ it("should be an array of required dependencies", () => {
58
+ expect(Array.isArray(INIT_DEPS)).toBe(true);
59
+ expect(INIT_DEPS.length).toBeGreaterThan(0);
60
+ });
61
+
62
+ it("should include essential packages", () => {
63
+ expect(INIT_DEPS).toContain("tailwindcss");
64
+ expect(INIT_DEPS).toContain("reka-ui");
65
+ expect(INIT_DEPS).toContain("@nuxt/icon");
66
+ });
67
+ });
68
+
69
+ describe("INIT_DEV_DEPS", () => {
70
+ it("should be an array of dev dependencies", () => {
71
+ expect(Array.isArray(INIT_DEV_DEPS)).toBe(true);
72
+ });
73
+
74
+ it("should include typescript", () => {
75
+ expect(INIT_DEV_DEPS).toContain("typescript");
76
+ });
77
+ });
78
+
79
+ describe("INIT_DEV_DEPS_PRETTIER", () => {
80
+ it("should include prettier-related packages", () => {
81
+ expect(INIT_DEV_DEPS_PRETTIER).toContain("prettier");
82
+ expect(INIT_DEV_DEPS_PRETTIER).toContain("prettier-plugin-tailwindcss");
83
+ });
84
+ });
85
+
86
+ describe("INIT_MODULES", () => {
87
+ it("should be an array of Nuxt modules", () => {
88
+ expect(Array.isArray(INIT_MODULES)).toBe(true);
89
+ });
90
+
91
+ it("should include essential Nuxt modules", () => {
92
+ expect(INIT_MODULES).toContain("@nuxtjs/color-mode");
93
+ expect(INIT_MODULES).toContain("@nuxt/icon");
94
+ });
95
+ });
96
+
97
+ describe("PACKAGE_MANAGER_CHOICES", () => {
98
+ it("should include all major package managers", () => {
99
+ const values = PACKAGE_MANAGER_CHOICES.map((c) => c.value);
100
+ expect(values).toContain("npm");
101
+ expect(values).toContain("yarn");
102
+ expect(values).toContain("pnpm");
103
+ expect(values).toContain("bun");
104
+ });
105
+
106
+ it("should have title and value for each choice", () => {
107
+ PACKAGE_MANAGER_CHOICES.forEach((choice) => {
108
+ expect(choice).toHaveProperty("title");
109
+ expect(choice).toHaveProperty("value");
110
+ });
111
+ });
112
+ });
113
+
114
+ describe("CSS_THEME_OPTIONS", () => {
115
+ it("should include various theme options", () => {
116
+ const values = CSS_THEME_OPTIONS.map((c) => c.value);
117
+ expect(values).toContain("zinc");
118
+ expect(values).toContain("slate");
119
+ expect(values).toContain("blue");
120
+ expect(values).toContain("green");
121
+ });
122
+
123
+ it("should have at least 10 theme options", () => {
124
+ expect(CSS_THEME_OPTIONS.length).toBeGreaterThanOrEqual(10);
125
+ });
126
+
127
+ it("should have title and value for each theme", () => {
128
+ CSS_THEME_OPTIONS.forEach((theme) => {
129
+ expect(theme).toHaveProperty("title");
130
+ expect(theme).toHaveProperty("value");
131
+ expect(typeof theme.title).toBe("string");
132
+ expect(typeof theme.value).toBe("string");
133
+ });
134
+ });
135
+ });
136
+ });
@@ -0,0 +1,97 @@
1
+ import fs from "node:fs";
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
+
4
+ import { detectNuxtVersion } from "../../src/utils/detectNuxtVersion";
5
+
6
+ describe("utils/detectNuxtVersion", () => {
7
+ beforeEach(() => {
8
+ vi.clearAllMocks();
9
+ });
10
+
11
+ afterEach(() => {
12
+ vi.restoreAllMocks();
13
+ });
14
+
15
+ it("should detect Nuxt 4 from dependencies", () => {
16
+ const mockPackageJson = JSON.stringify({
17
+ dependencies: {
18
+ nuxt: "^4.0.0",
19
+ },
20
+ });
21
+ vi.spyOn(fs, "readFileSync").mockReturnValue(mockPackageJson);
22
+
23
+ const version = detectNuxtVersion();
24
+
25
+ expect(version).toBe(4);
26
+ expect(fs.readFileSync).toHaveBeenCalledWith("package.json", "utf-8");
27
+ });
28
+
29
+ it("should detect Nuxt 4 from devDependencies", () => {
30
+ const mockPackageJson = JSON.stringify({
31
+ devDependencies: {
32
+ nuxt: "~4.1.0",
33
+ },
34
+ });
35
+ vi.spyOn(fs, "readFileSync").mockReturnValue(mockPackageJson);
36
+
37
+ expect(detectNuxtVersion()).toBe(4);
38
+ });
39
+
40
+ it("should detect Nuxt 3 from dependencies", () => {
41
+ const mockPackageJson = JSON.stringify({
42
+ dependencies: {
43
+ nuxt: "^3.12.0",
44
+ },
45
+ });
46
+ vi.spyOn(fs, "readFileSync").mockReturnValue(mockPackageJson);
47
+
48
+ expect(detectNuxtVersion()).toBe(3);
49
+ });
50
+
51
+ it("should detect Nuxt 3 from devDependencies", () => {
52
+ const mockPackageJson = JSON.stringify({
53
+ devDependencies: {
54
+ nuxt: "3.10.0",
55
+ },
56
+ });
57
+ vi.spyOn(fs, "readFileSync").mockReturnValue(mockPackageJson);
58
+
59
+ expect(detectNuxtVersion()).toBe(3);
60
+ });
61
+
62
+ it("should handle various version formats for Nuxt 4", () => {
63
+ const versions = ["^4.0.0", "~4.1.0", ">=4.0.0", "4.x", "4.0.0"];
64
+ versions.forEach((version) => {
65
+ const mockPackageJson = JSON.stringify({
66
+ dependencies: { nuxt: version },
67
+ });
68
+ vi.spyOn(fs, "readFileSync").mockReturnValue(mockPackageJson);
69
+ expect(detectNuxtVersion()).toBe(4);
70
+ });
71
+ });
72
+
73
+ it("should return 4 as default when package.json is missing", () => {
74
+ vi.spyOn(fs, "readFileSync").mockImplementation(() => {
75
+ throw new Error("File not found");
76
+ });
77
+
78
+ expect(detectNuxtVersion()).toBe(4);
79
+ });
80
+
81
+ it("should return 4 as default when nuxt is not in dependencies", () => {
82
+ const mockPackageJson = JSON.stringify({
83
+ dependencies: {
84
+ vue: "^3.0.0",
85
+ },
86
+ });
87
+ vi.spyOn(fs, "readFileSync").mockReturnValue(mockPackageJson);
88
+
89
+ expect(detectNuxtVersion()).toBe(4);
90
+ });
91
+
92
+ it("should return 4 when package.json has invalid JSON", () => {
93
+ vi.spyOn(fs, "readFileSync").mockReturnValue("invalid json");
94
+
95
+ expect(detectNuxtVersion()).toBe(4);
96
+ });
97
+ });
@@ -0,0 +1,59 @@
1
+ import axios from "axios";
2
+ import MockAdapter from "axios-mock-adapter";
3
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4
+
5
+ import { fetchBlockCategories } from "../../src/utils/fetchBlockCategories";
6
+
7
+ describe("utils/fetchBlockCategories", () => {
8
+ let mockAxios: MockAdapter;
9
+
10
+ beforeEach(() => {
11
+ mockAxios = new MockAdapter(axios);
12
+ vi.clearAllMocks();
13
+ });
14
+
15
+ afterEach(() => {
16
+ mockAxios.restore();
17
+ vi.restoreAllMocks();
18
+ });
19
+
20
+ it("should fetch block categories from default API endpoint", async () => {
21
+ const mockCategories = ["landing", "dashboard", "auth", "marketing"];
22
+
23
+ mockAxios.onGet("https://uithing.com/api/blocks/categories").reply(200, mockCategories);
24
+
25
+ const result = await fetchBlockCategories();
26
+
27
+ expect(result).toEqual(mockCategories);
28
+ expect(result).toHaveLength(4);
29
+ });
30
+
31
+ it("should fetch categories from custom API endpoint via env var", async () => {
32
+ const customUrl = "https://custom-api.com/blocks/categories";
33
+ process.env.BLOCK_CATEGORIES_API = customUrl;
34
+
35
+ const mockCategories = ["category1", "category2"];
36
+
37
+ mockAxios.onGet(customUrl).reply(200, mockCategories);
38
+
39
+ const result = await fetchBlockCategories();
40
+
41
+ expect(result).toEqual(mockCategories);
42
+ delete process.env.BLOCK_CATEGORIES_API;
43
+ });
44
+
45
+ it("should handle API errors", async () => {
46
+ mockAxios.onGet("https://uithing.com/api/blocks/categories").reply(404);
47
+
48
+ await expect(fetchBlockCategories()).rejects.toThrow();
49
+ });
50
+
51
+ it("should return empty array when no categories exist", async () => {
52
+ mockAxios.onGet("https://uithing.com/api/blocks/categories").reply(200, []);
53
+
54
+ const result = await fetchBlockCategories();
55
+
56
+ expect(result).toEqual([]);
57
+ expect(result).toHaveLength(0);
58
+ });
59
+ });
@@ -0,0 +1,59 @@
1
+ import axios from "axios";
2
+ import MockAdapter from "axios-mock-adapter";
3
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4
+
5
+ import { BlockComponent } from "../../src/types";
6
+ import { fetchBlocks } from "../../src/utils/fetchBlocks";
7
+
8
+ describe("utils/fetchBlocks", () => {
9
+ let mockAxios: MockAdapter;
10
+
11
+ beforeEach(() => {
12
+ mockAxios = new MockAdapter(axios);
13
+ vi.clearAllMocks();
14
+ });
15
+
16
+ afterEach(() => {
17
+ mockAxios.restore();
18
+ vi.restoreAllMocks();
19
+ });
20
+
21
+ it("should fetch blocks from default API endpoint", async () => {
22
+ const mockBlocks: BlockComponent[] = [
23
+ {
24
+ name: "Hero",
25
+ fileName: "Hero.vue",
26
+ file: "<template></template>",
27
+ category: "landing",
28
+ path: "/landing",
29
+ },
30
+ ];
31
+
32
+ mockAxios.onGet("https://uithing.com/api/blocks").reply(200, mockBlocks);
33
+
34
+ const result = await fetchBlocks();
35
+
36
+ expect(result).toEqual(mockBlocks);
37
+ expect(result).toHaveLength(1);
38
+ });
39
+
40
+ it("should fetch blocks from custom API endpoint via env var", async () => {
41
+ const customUrl = "https://custom-api.com/blocks";
42
+ process.env.BLOCKS_API = customUrl;
43
+
44
+ const mockBlocks: BlockComponent[] = [];
45
+
46
+ mockAxios.onGet(customUrl).reply(200, mockBlocks);
47
+
48
+ const result = await fetchBlocks();
49
+
50
+ expect(result).toEqual(mockBlocks);
51
+ delete process.env.BLOCKS_API;
52
+ });
53
+
54
+ it("should handle API errors", async () => {
55
+ mockAxios.onGet("https://uithing.com/api/blocks").reply(500);
56
+
57
+ await expect(fetchBlocks()).rejects.toThrow();
58
+ });
59
+ });
@@ -0,0 +1,92 @@
1
+ import axios from "axios";
2
+ import MockAdapter from "axios-mock-adapter";
3
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4
+
5
+ import { Component } from "../../src/types";
6
+ import { fetchComponents } from "../../src/utils/fetchComponents";
7
+
8
+ describe("utils/fetchComponents", () => {
9
+ let mockAxios: MockAdapter;
10
+
11
+ beforeEach(() => {
12
+ mockAxios = new MockAdapter(axios);
13
+ vi.clearAllMocks();
14
+ });
15
+
16
+ afterEach(() => {
17
+ mockAxios.restore();
18
+ vi.restoreAllMocks();
19
+ });
20
+
21
+ it("should fetch components from default API endpoint", async () => {
22
+ const mockComponents: Component[] = [
23
+ {
24
+ name: "Button",
25
+ value: "button",
26
+ files: [],
27
+ utils: [],
28
+ composables: [],
29
+ plugins: [],
30
+ },
31
+ {
32
+ name: "Input",
33
+ value: "input",
34
+ files: [],
35
+ utils: [],
36
+ composables: [],
37
+ plugins: [],
38
+ },
39
+ ];
40
+
41
+ mockAxios.onGet().reply(200, mockComponents);
42
+
43
+ const result = await fetchComponents();
44
+
45
+ expect(result).toEqual(mockComponents);
46
+ expect(result).toHaveLength(2);
47
+ });
48
+
49
+ it("should fetch components from custom API endpoint via env var", async () => {
50
+ const customUrl = "https://custom-api.com/components";
51
+ process.env.COMPONENTS_API = customUrl;
52
+
53
+ const mockComponents: Component[] = [
54
+ {
55
+ name: "Card",
56
+ value: "card",
57
+ files: [],
58
+ utils: [],
59
+ composables: [],
60
+ plugins: [],
61
+ },
62
+ ];
63
+
64
+ mockAxios.onGet(customUrl).reply(200, mockComponents);
65
+
66
+ const result = await fetchComponents();
67
+
68
+ expect(result).toEqual(mockComponents);
69
+ delete process.env.COMPONENTS_API;
70
+ });
71
+
72
+ it("should handle API errors gracefully", async () => {
73
+ mockAxios.onGet("https://uithing.com/api/components").reply(500);
74
+
75
+ await expect(fetchComponents()).rejects.toThrow();
76
+ });
77
+
78
+ it("should handle network errors", async () => {
79
+ mockAxios.onGet("https://uithing.com/api/components").networkError();
80
+
81
+ await expect(fetchComponents()).rejects.toThrow();
82
+ });
83
+
84
+ it("should return empty array when API returns no data", async () => {
85
+ mockAxios.onGet("https://uithing.com/api/components").reply(200, []);
86
+
87
+ const result = await fetchComponents();
88
+
89
+ expect(result).toEqual([]);
90
+ expect(result).toHaveLength(0);
91
+ });
92
+ });
@@ -0,0 +1,62 @@
1
+ import axios from "axios";
2
+ import MockAdapter from "axios-mock-adapter";
3
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4
+
5
+ import { ProseComponent } from "../../src/types";
6
+ import { fetchProseComponents } from "../../src/utils/fetchProseComponents";
7
+
8
+ describe("utils/fetchProseComponents", () => {
9
+ let mockAxios: MockAdapter;
10
+
11
+ beforeEach(() => {
12
+ mockAxios = new MockAdapter(axios);
13
+ vi.clearAllMocks();
14
+ });
15
+
16
+ afterEach(() => {
17
+ mockAxios.restore();
18
+ vi.restoreAllMocks();
19
+ });
20
+
21
+ it("should fetch prose components from default API endpoint", async () => {
22
+ const mockProseComponents: ProseComponent[] = [
23
+ {
24
+ name: "Heading",
25
+ value: "heading",
26
+ fileName: "ProseH1.vue",
27
+ filePath: "/prose/ProseH1.vue",
28
+ file: { fileName: "ProseH1.vue", dirPath: "/prose", fileContent: "<template></template>" },
29
+ utils: [],
30
+ composables: [],
31
+ plugins: [],
32
+ },
33
+ ];
34
+
35
+ mockAxios.onGet("https://uithing.com/api/prose").reply(200, mockProseComponents);
36
+
37
+ const result = await fetchProseComponents();
38
+
39
+ expect(result).toEqual(mockProseComponents);
40
+ expect(result).toHaveLength(1);
41
+ });
42
+
43
+ it("should fetch prose components from custom API endpoint via env var", async () => {
44
+ const customUrl = "https://custom-api.com/prose";
45
+ process.env.PROSE_COMPONENTS_API = customUrl;
46
+
47
+ const mockProseComponents: ProseComponent[] = [];
48
+
49
+ mockAxios.onGet(customUrl).reply(200, mockProseComponents);
50
+
51
+ const result = await fetchProseComponents();
52
+
53
+ expect(result).toEqual(mockProseComponents);
54
+ delete process.env.PROSE_COMPONENTS_API;
55
+ });
56
+
57
+ it("should handle API errors", async () => {
58
+ mockAxios.onGet("https://uithing.com/api/prose").reply(500);
59
+
60
+ await expect(fetchProseComponents()).rejects.toThrow();
61
+ });
62
+ });