Skip to content

Commit 72779a1

Browse files
committed
fix: removed vi.resetModules() to force vitest to resolve test files
1 parent dfd7dec commit 72779a1

File tree

1 file changed

+49
-75
lines changed

1 file changed

+49
-75
lines changed

packages/core/src/__tests__/image-plugin.test.ts

Lines changed: 49 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,21 @@ import os from 'node:os';
44
import path from 'node:path';
55
import type { RouteManifest } from 'pyrajs-shared';
66

7-
// Mock pyrajs-shared so `log` is available after vi.resetModules() calls.
8-
// vi.mock() is hoisted and survives module resets, so image-plugin.ts always
9-
// gets this mock when it's re-imported in each test.
10-
vi.mock('pyrajs-shared', () => ({
11-
log: {
12-
info: vi.fn(),
13-
success: vi.fn(),
14-
warn: vi.fn(),
15-
error: vi.fn(),
16-
},
7+
// ─── Mocks ────────────────────────────────────────────────────────────────────
8+
// vi.mock() is hoisted above all imports by vitest, so image-plugin.ts gets the
9+
// mocked optimizer when it is statically imported below. This removes the need
10+
// for vi.resetModules() + dynamic imports, which bypassed the Vite alias and
11+
// caused "Failed to resolve entry for pyrajs-shared" in CI environments where
12+
// packages/shared/dist/ has not been built yet.
13+
vi.mock('../image-optimizer.js', () => ({
14+
isSharpAvailable: vi.fn(),
15+
getImageMetadata: vi.fn(),
16+
optimizeImage: vi.fn(),
1717
}));
1818

19+
import { pyraImages } from '../plugins/image-plugin.js';
20+
import * as imageOptimizer from '../image-optimizer.js';
21+
1922
// ─── Filesystem helpers ───────────────────────────────────────────────────────
2023

2124
let tmpDir: string;
@@ -27,30 +30,25 @@ function writeFakeImage(relPath: string, content = 'fake-image-bytes'): string {
2730
return abs;
2831
}
2932

30-
// ─── Mocks ────────────────────────────────────────────────────────────────────
33+
// ─── Mock configuration helpers ───────────────────────────────────────────────
34+
35+
function mockSharpAvailable(
36+
buffer = Buffer.from('optimized-image-data'),
37+
metadata = { width: 1000, height: 750, format: 'jpeg' },
38+
) {
39+
vi.mocked(imageOptimizer.isSharpAvailable).mockResolvedValue(true);
40+
vi.mocked(imageOptimizer.getImageMetadata).mockResolvedValue(metadata);
41+
vi.mocked(imageOptimizer.optimizeImage).mockResolvedValue({
42+
buffer,
43+
width: metadata.width,
44+
height: metadata.height,
45+
format: 'webp',
46+
size: buffer.length,
47+
});
48+
}
3149

32-
/** Resets module registry so fresh imports get fresh module-level state. */
33-
function resetAndMockOptimizer(overrides: {
34-
available?: boolean;
35-
metadata?: { width: number; height: number; format: string };
36-
buffer?: Buffer;
37-
} = {}) {
38-
vi.resetModules();
39-
const available = overrides.available ?? true;
40-
const metadata = overrides.metadata ?? { width: 1000, height: 750, format: 'jpeg' };
41-
const buffer = overrides.buffer ?? Buffer.from('optimized-image-data');
42-
43-
vi.doMock('../image-optimizer.js', () => ({
44-
isSharpAvailable: vi.fn().mockResolvedValue(available),
45-
getImageMetadata: vi.fn().mockResolvedValue(metadata),
46-
optimizeImage: vi.fn().mockResolvedValue({
47-
buffer,
48-
width: metadata.width,
49-
height: metadata.height,
50-
format: 'webp',
51-
size: buffer.length,
52-
}),
53-
}));
50+
function mockSharpUnavailable() {
51+
vi.mocked(imageOptimizer.isSharpAvailable).mockResolvedValue(false);
5452
}
5553

5654
// ─── Setup / teardown ─────────────────────────────────────────────────────────
@@ -61,47 +59,39 @@ beforeEach(() => {
6159

6260
afterEach(() => {
6361
fs.rmSync(tmpDir, { recursive: true, force: true });
64-
vi.doUnmock('../image-optimizer.js');
62+
vi.clearAllMocks();
6563
});
6664

67-
// ─── Plugin identity ─────────────────────────────────────────────────────────
65+
// ─── Plugin identity ─────────────────────────────────────────────────────────
6866

6967
describe('pyraImages() — plugin identity', () => {
70-
beforeEach(() => resetAndMockOptimizer());
68+
beforeEach(() => mockSharpAvailable());
7169

72-
it('has name "pyra:images"', async () => {
73-
const { pyraImages } = await import('../plugins/image-plugin.js');
70+
it('has name "pyra:images"', () => {
7471
const plugin = pyraImages();
7572
expect(plugin.name).toBe('pyra:images');
7673
});
7774

78-
it('works with no arguments (all defaults)', async () => {
79-
const { pyraImages } = await import('../plugins/image-plugin.js');
75+
it('works with no arguments (all defaults)', () => {
8076
expect(() => pyraImages()).not.toThrow();
8177
});
8278

83-
it('works with partial config', async () => {
84-
const { pyraImages } = await import('../plugins/image-plugin.js');
79+
it('works with partial config', () => {
8580
expect(() => pyraImages({ formats: ['avif'], quality: 90 })).not.toThrow();
8681
});
8782
});
8883

8984
// ─── Plugin hooks — buildEnd ──────────────────────────────────────────────────
9085

9186
describe('pyraImages() — buildEnd()', () => {
92-
beforeEach(() => resetAndMockOptimizer());
87+
beforeEach(() => mockSharpAvailable());
9388

9489
it('sets manifest.images when variants were built', async () => {
95-
const { pyraImages } = await import('../plugins/image-plugin.js');
9690
const plugin = pyraImages();
97-
98-
// Seed internal builtImages by simulating a successful buildStart
99-
const publicDir = path.join(tmpDir, 'public');
10091
const outDir = path.join(tmpDir, 'dist');
10192
writeFakeImage('public/hero.jpg');
10293
fs.mkdirSync(path.join(outDir, 'client', '_images'), { recursive: true });
10394

104-
// Simulate setup and buildStart lifecycle
10595
await plugin.setup?.({
10696
addEsbuildPlugin: vi.fn(),
10797
getConfig: vi.fn().mockReturnValue({
@@ -120,10 +110,7 @@ describe('pyraImages() — buildEnd()', () => {
120110
});
121111

122112
it('does not set manifest.images when no images were found', async () => {
123-
const { pyraImages } = await import('../plugins/image-plugin.js');
124113
const plugin = pyraImages();
125-
126-
// Empty public dir — no images
127114
fs.mkdirSync(path.join(tmpDir, 'public'), { recursive: true });
128115
fs.mkdirSync(path.join(tmpDir, 'dist', 'client', '_images'), { recursive: true });
129116

@@ -143,8 +130,7 @@ describe('pyraImages() — buildEnd()', () => {
143130
expect(manifest.images).toBeUndefined();
144131
});
145132

146-
it('buildEnd does not throw when called before buildStart', async () => {
147-
const { pyraImages } = await import('../plugins/image-plugin.js');
133+
it('buildEnd does not throw when called before buildStart', () => {
148134
const plugin = pyraImages();
149135
const manifest: RouteManifest = { routes: {} };
150136
expect(() =>
@@ -156,14 +142,14 @@ describe('pyraImages() — buildEnd()', () => {
156142
// ─── Plugin hooks — buildStart (sharp available) ──────────────────────────────
157143

158144
describe('pyraImages() — buildStart() with sharp available', () => {
159-
beforeEach(() => resetAndMockOptimizer({
160-
available: true,
161-
metadata: { width: 1000, height: 750, format: 'jpeg' },
162-
buffer: Buffer.from('x'.repeat(1234)),
163-
}));
145+
beforeEach(() =>
146+
mockSharpAvailable(
147+
Buffer.from('x'.repeat(1234)),
148+
{ width: 1000, height: 750, format: 'jpeg' },
149+
)
150+
);
164151

165152
async function setupPlugin(config: Record<string, unknown> = {}) {
166-
const { pyraImages } = await import('../plugins/image-plugin.js');
167153
const plugin = pyraImages({ formats: ['webp'], sizes: [640, 1280], quality: 80 });
168154
fs.mkdirSync(path.join(tmpDir, 'public'), { recursive: true });
169155
fs.mkdirSync(path.join(tmpDir, 'dist', 'client', '_images'), { recursive: true });
@@ -181,16 +167,13 @@ describe('pyraImages() — buildStart() with sharp available', () => {
181167

182168
it('skips when public dir has no images', async () => {
183169
const plugin = await setupPlugin();
184-
// No files in public/
185170
await expect(plugin.buildStart?.()).resolves.not.toThrow();
186171
const manifest: RouteManifest = { routes: {} };
187172
plugin.buildEnd?.({ manifest, outDir: path.join(tmpDir, 'dist'), root: tmpDir });
188173
expect(manifest.images).toBeUndefined();
189174
});
190175

191176
it('skips when public dir does not exist', async () => {
192-
// Don't create the public dir
193-
const { pyraImages } = await import('../plugins/image-plugin.js');
194177
const plugin = pyraImages({ formats: ['webp'] });
195178
await plugin.setup?.({
196179
addEsbuildPlugin: vi.fn(),
@@ -247,7 +230,6 @@ describe('pyraImages() — buildStart() with sharp available', () => {
247230
const manifest: RouteManifest = { routes: {} };
248231
plugin.buildEnd?.({ manifest, outDir: path.join(tmpDir, 'dist'), root: tmpDir });
249232
const entry = manifest.images?.['/img.jpg'];
250-
// Should have keys like "640:webp", "1280:webp"
251233
expect(Object.keys(entry?.variants ?? {})).toEqual(
252234
expect.arrayContaining(['640:webp'])
253235
);
@@ -277,9 +259,7 @@ describe('pyraImages() — buildStart() with sharp available', () => {
277259
});
278260

279261
it('never generates variants wider than the original image', async () => {
280-
// Original is 1000px wide; requesting 1280w should be skipped
281262
writeFakeImage('public/small.jpg');
282-
const { pyraImages } = await import('../plugins/image-plugin.js');
283263
const plugin = pyraImages({ formats: ['webp'], sizes: [640, 1280] });
284264
fs.mkdirSync(path.join(tmpDir, 'dist', 'client', '_images'), { recursive: true });
285265
await plugin.setup?.({
@@ -291,7 +271,6 @@ describe('pyraImages() — buildStart() with sharp available', () => {
291271
const manifest: RouteManifest = { routes: {} };
292272
plugin.buildEnd?.({ manifest, outDir: path.join(tmpDir, 'dist'), root: tmpDir });
293273
const entry = manifest.images?.['/small.jpg'];
294-
// 640w should exist (640 < 1000), 1280w should not (1280 > 1000)
295274
expect(entry?.variants['640:webp']).toBeDefined();
296275
expect(entry?.variants['1280:webp']).toBeUndefined();
297276
});
@@ -318,7 +297,6 @@ describe('pyraImages() — buildStart() with sharp available', () => {
318297

319298
it('ignores non-image files in public/', async () => {
320299
writeFakeImage('public/img.jpg');
321-
// Non-image files that should be ignored
322300
fs.writeFileSync(path.join(tmpDir, 'public', 'styles.css'), 'body {}');
323301
fs.writeFileSync(path.join(tmpDir, 'public', 'robots.txt'), 'User-agent: *');
324302
const plugin = await setupPlugin();
@@ -333,11 +311,10 @@ describe('pyraImages() — buildStart() with sharp available', () => {
333311
// ─── Plugin hooks — buildStart (sharp unavailable) ───────────────────────────
334312

335313
describe('pyraImages() — buildStart() with sharp unavailable', () => {
336-
beforeEach(() => resetAndMockOptimizer({ available: false }));
314+
beforeEach(() => mockSharpUnavailable());
337315

338316
it('returns without throwing when sharp is missing', async () => {
339317
writeFakeImage('public/hero.jpg');
340-
const { pyraImages } = await import('../plugins/image-plugin.js');
341318
const plugin = pyraImages();
342319
fs.mkdirSync(path.join(tmpDir, 'dist', 'client', '_images'), { recursive: true });
343320
await plugin.setup?.({
@@ -350,7 +327,6 @@ describe('pyraImages() — buildStart() with sharp unavailable', () => {
350327

351328
it('does not populate manifest.images when sharp is missing', async () => {
352329
writeFakeImage('public/hero.jpg');
353-
const { pyraImages } = await import('../plugins/image-plugin.js');
354330
const plugin = pyraImages();
355331
fs.mkdirSync(path.join(tmpDir, 'dist', 'client', '_images'), { recursive: true });
356332
await plugin.setup?.({
@@ -368,17 +344,15 @@ describe('pyraImages() — buildStart() with sharp unavailable', () => {
368344
// ─── config() hook ────────────────────────────────────────────────────────────
369345

370346
describe('pyraImages() — config() hook', () => {
371-
beforeEach(() => resetAndMockOptimizer());
347+
beforeEach(() => mockSharpAvailable());
372348

373-
it('returns null (does not mutate config)', async () => {
374-
const { pyraImages } = await import('../plugins/image-plugin.js');
349+
it('returns null (does not mutate config)', () => {
375350
const plugin = pyraImages();
376351
const result = plugin.config?.({ root: '/app' }, 'production');
377352
expect(result).toBeNull();
378353
});
379354

380-
it('config hook is defined', async () => {
381-
const { pyraImages } = await import('../plugins/image-plugin.js');
355+
it('config hook is defined', () => {
382356
const plugin = pyraImages();
383357
expect(typeof plugin.config).toBe('function');
384358
});

0 commit comments

Comments
 (0)