Skip to content

Commit 946f2f4

Browse files
omerfardemirTimeraa
authored andcommitted
feat: add auto-icons tests for icon generation
1 parent 539b340 commit 946f2f4

File tree

3 files changed

+310
-1
lines changed

3 files changed

+310
-1
lines changed

packages/auto-icons/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
],
4040
"scripts": {
4141
"build": "buildc -- unbuild",
42-
"check": "pnpm build && check"
42+
"check": "pnpm build && check",
43+
"test": "buildc --deps-only -- vitest"
4344
},
4445
"peerDependencies": {
4546
"wxt": ">=0.19.0"
Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
import { describe, it, expect, vi, beforeEach } from 'vitest';
2+
import { resolve, relative } from 'node:path';
3+
import * as fsExtra from 'fs-extra';
4+
import defu from 'defu';
5+
6+
// Import the module definition type
7+
import type { AutoIconsOptions } from '../index';
8+
import { UserManifest } from 'wxt';
9+
10+
// Mock dependencies
11+
vi.mock('fs-extra', () => ({
12+
exists: vi.fn().mockResolvedValue(true),
13+
ensureDir: vi.fn().mockResolvedValue(undefined),
14+
}));
15+
16+
// Mock process.cwd
17+
vi.spyOn(process, 'cwd').mockReturnValue('/mock');
18+
19+
describe('auto-icons module', () => {
20+
// Create a simple mock of the WXT object
21+
const mockWxt = {
22+
config: {
23+
srcDir: '/mock/src',
24+
outDir: '/mock/dist',
25+
mode: 'development',
26+
},
27+
logger: {
28+
warn: vi.fn(),
29+
},
30+
};
31+
32+
beforeEach(() => {
33+
vi.clearAllMocks();
34+
});
35+
36+
describe('options handling', () => {
37+
it('should use default options when not provided', () => {
38+
// Execute
39+
const options: AutoIconsOptions = {};
40+
const parsedOptions = defu<
41+
Required<AutoIconsOptions>,
42+
AutoIconsOptions[]
43+
>(options, {
44+
enabled: true,
45+
baseIconPath: resolve('/mock/src', 'assets/icon.png'),
46+
grayscaleOnDevelopment: true,
47+
sizes: [128, 48, 32, 16],
48+
});
49+
50+
// Verify
51+
expect(parsedOptions.enabled).toBe(true);
52+
expect(parsedOptions.baseIconPath).toBe(
53+
resolve('/mock/src', 'assets/icon.png'),
54+
);
55+
expect(parsedOptions.grayscaleOnDevelopment).toBe(true);
56+
expect(parsedOptions.sizes).toEqual([128, 48, 32, 16]);
57+
});
58+
59+
it('should merge custom options with defaults', () => {
60+
// Execute
61+
const options: AutoIconsOptions = {
62+
sizes: [64, 32],
63+
grayscaleOnDevelopment: false,
64+
};
65+
const parsedOptions = defu<
66+
Required<AutoIconsOptions>,
67+
AutoIconsOptions[]
68+
>(options, {
69+
enabled: true,
70+
baseIconPath: resolve('/mock/src', 'assets/icon.png'),
71+
grayscaleOnDevelopment: true,
72+
sizes: [128, 48, 32, 16],
73+
});
74+
75+
// Verify
76+
expect(parsedOptions.enabled).toBe(true); // Default
77+
expect(parsedOptions.baseIconPath).toBe(
78+
resolve('/mock/src', 'assets/icon.png'),
79+
); // Default
80+
expect(parsedOptions.grayscaleOnDevelopment).toBe(false); // Custom
81+
expect(parsedOptions.sizes).toEqual([64, 32, 128, 48, 32, 16]); // Custom
82+
});
83+
});
84+
85+
describe('error handling', () => {
86+
it('should warn when disabled', () => {
87+
// Setup
88+
const parsedOptions: AutoIconsOptions = {
89+
enabled: false,
90+
baseIconPath: 'assets/icon.png',
91+
grayscaleOnDevelopment: true,
92+
sizes: [128, 48, 32, 16],
93+
};
94+
95+
// Execute - simulate the module's logic
96+
if (!parsedOptions.enabled) {
97+
mockWxt.logger.warn(`\`[auto-icons]\` module-name disabled`);
98+
}
99+
100+
// Verify
101+
expect(mockWxt.logger.warn).toHaveBeenCalledWith(
102+
expect.stringContaining('disabled'),
103+
);
104+
});
105+
106+
it('should warn when base icon not found', async () => {
107+
// Setup
108+
vi.mocked(fsExtra.exists).mockResolvedValue();
109+
const parsedOptions: AutoIconsOptions = {
110+
enabled: true,
111+
baseIconPath: 'assets/icon.png',
112+
grayscaleOnDevelopment: true,
113+
sizes: [128, 48, 32, 16],
114+
};
115+
116+
const resolvedPath = resolve('/mock/src', parsedOptions.baseIconPath!);
117+
118+
// Execute - simulate the module's logic
119+
if (!(await fsExtra.exists(resolvedPath))) {
120+
mockWxt.logger.warn(
121+
`\`[auto-icons]\` Skipping icon generation, no base icon found at ${relative(process.cwd(), resolvedPath)}`,
122+
);
123+
}
124+
125+
// Verify
126+
expect(mockWxt.logger.warn).toHaveBeenCalledWith(
127+
expect.stringContaining('Skipping icon generation'),
128+
);
129+
});
130+
});
131+
132+
describe('manifest generation', () => {
133+
it('should update manifest with icons', () => {
134+
// Setup
135+
const options: AutoIconsOptions = {
136+
enabled: true,
137+
baseIconPath: 'assets/icon.png',
138+
grayscaleOnDevelopment: true,
139+
sizes: [96],
140+
};
141+
const parsedOptions = defu<
142+
Required<AutoIconsOptions>,
143+
AutoIconsOptions[]
144+
>(options, {
145+
enabled: true,
146+
baseIconPath: resolve('/mock/src', 'assets/icon.png'),
147+
grayscaleOnDevelopment: true,
148+
sizes: [128, 48, 32, 16],
149+
});
150+
const manifest: UserManifest = {
151+
icons: {
152+
128: 'icon/128.png',
153+
48: 'icon/48.png',
154+
32: 'icon/32.png',
155+
},
156+
};
157+
158+
// Execute - simulate the build:manifestGenerated hook logic
159+
manifest.icons = Object.fromEntries(
160+
parsedOptions.sizes!.map((size) => [size, `icons/${size}.png`]),
161+
);
162+
163+
// Verify
164+
expect(manifest).toEqual({
165+
icons: {
166+
'96': 'icons/96.png',
167+
'128': 'icons/128.png',
168+
'48': 'icons/48.png',
169+
'32': 'icons/32.png',
170+
'16': 'icons/16.png',
171+
},
172+
});
173+
});
174+
175+
it('should warn when overwriting existing icons in manifest', () => {
176+
const manifest: UserManifest = {
177+
icons: {
178+
128: 'icon/128.png',
179+
48: 'icon/48.png',
180+
32: 'icon/32.png',
181+
},
182+
};
183+
184+
// Execute - simulate the build:manifestGenerated hook logic
185+
if (manifest.icons) {
186+
mockWxt.logger.warn(
187+
'`[auto-icons]` icons property found in manifest, overwriting with auto-generated icons',
188+
);
189+
}
190+
191+
// Verify
192+
expect(mockWxt.logger.warn).toHaveBeenCalledWith(
193+
expect.stringContaining('overwriting with auto-generated icons'),
194+
);
195+
});
196+
});
197+
198+
describe('icon generation', () => {
199+
it('should generate icons with correct sizes', async () => {
200+
const options: AutoIconsOptions = {
201+
enabled: true,
202+
baseIconPath: 'assets/icon.png',
203+
grayscaleOnDevelopment: true,
204+
sizes: [96],
205+
};
206+
const parsedOptions = defu<
207+
Required<AutoIconsOptions>,
208+
AutoIconsOptions[]
209+
>(options, {
210+
enabled: true,
211+
baseIconPath: resolve('/mock/src', 'assets/icon.png'),
212+
grayscaleOnDevelopment: true,
213+
sizes: [128, 48, 32, 16],
214+
});
215+
216+
const mockOutput = {
217+
publicAssets: [] as { type: string; fileName: string }[],
218+
};
219+
220+
// Execute - simulate the logic without actually calling sharp
221+
for (const size of parsedOptions.sizes!) {
222+
await fsExtra.ensureDir(resolve(mockWxt.config.outDir, 'icons'));
223+
224+
// Add to public assets
225+
mockOutput.publicAssets.push({
226+
type: 'asset',
227+
fileName: `icons/${size}.png`,
228+
});
229+
}
230+
231+
// Verify
232+
expect(fsExtra.ensureDir).toHaveBeenCalledWith(
233+
resolve('/mock/dist', 'icons'),
234+
);
235+
expect(mockOutput.publicAssets).toEqual([
236+
{ type: 'asset', fileName: 'icons/96.png' },
237+
{ type: 'asset', fileName: 'icons/128.png' },
238+
{ type: 'asset', fileName: 'icons/48.png' },
239+
{ type: 'asset', fileName: 'icons/32.png' },
240+
{ type: 'asset', fileName: 'icons/16.png' },
241+
]);
242+
});
243+
244+
it('should apply grayscale in development mode but not in production', () => {
245+
// Setup
246+
const parsedOptions: AutoIconsOptions = {
247+
enabled: true,
248+
baseIconPath: 'assets/icon.png',
249+
grayscaleOnDevelopment: true,
250+
};
251+
252+
// Test development mode
253+
const devMode = 'development';
254+
const shouldApplyGrayscale =
255+
devMode === 'development' && parsedOptions.grayscaleOnDevelopment;
256+
expect(shouldApplyGrayscale).toBe(true);
257+
258+
// Test production mode
259+
const prodMode = 'production';
260+
const shouldNotApplyGrayscale = prodMode === 'production';
261+
expect(shouldNotApplyGrayscale).toBe(true);
262+
});
263+
});
264+
265+
describe('public paths', () => {
266+
it('should add icon paths to public paths', () => {
267+
const options: AutoIconsOptions = {
268+
enabled: true,
269+
baseIconPath: 'assets/icon.png',
270+
grayscaleOnDevelopment: true,
271+
sizes: [96],
272+
};
273+
const parsedOptions = defu<
274+
Required<AutoIconsOptions>,
275+
AutoIconsOptions[]
276+
>(options, {
277+
enabled: true,
278+
baseIconPath: resolve('/mock/src', 'assets/icon.png'),
279+
grayscaleOnDevelopment: true,
280+
sizes: [128, 48, 32, 16],
281+
});
282+
283+
const paths: string[] = [];
284+
285+
// Execute - simulate the prepare:publicPaths hook logic
286+
for (const size of parsedOptions.sizes!) {
287+
paths.push(`icons/${size}.png`);
288+
}
289+
290+
// Verify
291+
expect(paths).toEqual([
292+
'icons/96.png',
293+
'icons/128.png',
294+
'icons/48.png',
295+
'icons/32.png',
296+
'icons/16.png',
297+
]);
298+
});
299+
});
300+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { defineConfig } from 'vitest/config';
2+
3+
export default defineConfig({
4+
test: {
5+
mockReset: true,
6+
restoreMocks: true,
7+
},
8+
});

0 commit comments

Comments
 (0)