portapack 0.2.1 → 0.3.1
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/ci.yml +5 -4
- package/CHANGELOG.md +20 -0
- package/README.md +81 -219
- package/dist/cli/{cli-entry.js → cli-entry.cjs} +620 -513
- package/dist/cli/cli-entry.cjs.map +1 -0
- package/dist/index.d.ts +51 -56
- package/dist/index.js +517 -458
- package/dist/index.js.map +1 -1
- package/docs/.vitepress/config.ts +0 -1
- package/docs/cli.md +108 -45
- package/docs/configuration.md +101 -116
- package/docs/getting-started.md +74 -44
- package/jest.config.ts +18 -8
- package/jest.setup.cjs +66 -146
- package/package.json +5 -5
- package/src/cli/cli-entry.ts +15 -15
- package/src/cli/cli.ts +130 -119
- package/src/core/bundler.ts +174 -63
- package/src/core/extractor.ts +364 -277
- package/src/core/web-fetcher.ts +205 -141
- package/src/index.ts +161 -224
- package/tests/unit/cli/cli-entry.test.ts +66 -77
- package/tests/unit/cli/cli.test.ts +243 -145
- package/tests/unit/core/bundler.test.ts +334 -258
- package/tests/unit/core/extractor.test.ts +608 -1064
- package/tests/unit/core/minifier.test.ts +130 -221
- package/tests/unit/core/packer.test.ts +255 -106
- package/tests/unit/core/parser.test.ts +89 -458
- package/tests/unit/core/web-fetcher.test.ts +310 -265
- package/tests/unit/index.test.ts +206 -300
- package/tests/unit/utils/logger.test.ts +32 -28
- package/tsconfig.jest.json +8 -7
- package/tsup.config.ts +34 -29
- package/dist/cli/cli-entry.js.map +0 -1
- package/docs/demo.md +0 -46
- package/output.html +0 -1
- package/site-packed.html +0 -1
- package/test-output.html +0 -0
@@ -1,50 +1,54 @@
|
|
1
1
|
/**
|
2
2
|
* @file tests/unit/core/minifier.test.ts
|
3
3
|
* @description Unit tests for the Minifier module (minifyAssets function).
|
4
|
-
* Uses jest.unstable_mockModule for mocking dependencies.
|
5
4
|
*/
|
6
5
|
|
7
6
|
// --- Imports ---
|
7
|
+
// Import types from the libraries being mocked
|
8
8
|
import type { Options as HtmlMinifyOptions } from 'html-minifier-terser';
|
9
|
-
import type {
|
9
|
+
import type { OptionsOutput as CleanCSSOptions, Output as CleanCSSOutput } from 'clean-css'; // Use OptionsOutput for constructor options type
|
10
10
|
import type { MinifyOptions, MinifyOutput } from 'terser';
|
11
|
+
// Import local types
|
11
12
|
import type { ParsedHTML, BundleOptions, Asset } from '../../../src/types';
|
12
13
|
import { Logger } from '../../../src/utils/logger';
|
14
|
+
import { LogLevel } from '../../../src/types';
|
13
15
|
import { jest, describe, it, expect, beforeEach } from '@jest/globals';
|
14
16
|
|
15
17
|
// =================== MOCK SETUP ===================
|
16
18
|
|
17
|
-
|
18
|
-
const
|
19
|
-
const
|
20
|
-
const
|
19
|
+
// --- Define TOP-LEVEL mock functions WITH EXPLICIT TYPES ---
|
20
|
+
const mockHtmlMinifierMinifyFn = jest.fn<(text: string, options?: HtmlMinifyOptions) => Promise<string>>();
|
21
|
+
const mockCleanCSSInstanceMinifyFn = jest.fn<(source: string | Record<string, string> | Array<string | Record<string, string>>) => CleanCSSOutput>();
|
22
|
+
const mockCleanCSSConstructorFn = jest.fn<() => ({ minify: typeof mockCleanCSSInstanceMinifyFn})>() // Type the constructor mock
|
23
|
+
.mockImplementation(() => ({ // Provide implementation immediately
|
24
|
+
minify: mockCleanCSSInstanceMinifyFn
|
25
|
+
}));
|
26
|
+
const mockTerserMinifyFn = jest.fn<(code: string | Record<string, string> | string[], options?: MinifyOptions) => Promise<MinifyOutput>>();
|
21
27
|
|
22
|
-
|
23
|
-
jest.
|
24
|
-
|
28
|
+
|
29
|
+
// --- Mock the dependencies using standard jest.mock and factories ---
|
30
|
+
jest.mock('html-minifier-terser', () => ({
|
31
|
+
__esModule: true,
|
32
|
+
minify: mockHtmlMinifierMinifyFn,
|
25
33
|
}));
|
26
|
-
jest.
|
27
|
-
|
28
|
-
|
34
|
+
jest.mock('clean-css', () => ({
|
35
|
+
__esModule: true,
|
36
|
+
// Mock the default export which is the class constructor
|
37
|
+
default: mockCleanCSSConstructorFn,
|
29
38
|
}));
|
30
|
-
jest.
|
31
|
-
|
39
|
+
jest.mock('terser', () => ({
|
40
|
+
__esModule: true,
|
41
|
+
minify: mockTerserMinifyFn,
|
32
42
|
}));
|
33
|
-
|
34
43
|
// ====================================================
|
35
44
|
|
36
45
|
// Import the module under test *after* mocks are set up
|
37
|
-
|
38
|
-
|
46
|
+
import { minifyAssets } from '../../../src/core/minifier';
|
47
|
+
|
39
48
|
|
40
|
-
// Helper
|
49
|
+
// Helper function (keep as is)
|
41
50
|
const simpleMockCssMinify = (css: string): string => {
|
42
|
-
return css
|
43
|
-
.replace(/\/\*.*?\*\//g, '') // Remove comments
|
44
|
-
.replace(/\s*([{}:;,])\s*/g, '$1') // Remove space around syntax chars
|
45
|
-
.replace(/\s+/g, ' ') // Collapse remaining whitespace
|
46
|
-
.replace(/;}/g, '}') // Remove trailing semicolons inside blocks
|
47
|
-
.trim();
|
51
|
+
return css.replace(/\/\*.*?\*\//g, '').replace(/\s*([{}:;,])\s*/g, '$1').replace(/\s+/g, ' ').replace(/;}/g, '}').trim();
|
48
52
|
}
|
49
53
|
|
50
54
|
describe('🧼 Minifier', () => {
|
@@ -53,13 +57,10 @@ describe('🧼 Minifier', () => {
|
|
53
57
|
let mockLoggerDebugFn: jest.SpiedFunction<typeof Logger.prototype.debug>;
|
54
58
|
|
55
59
|
const sampleHtmlContent = '<html> <head> <title> Test </title> </head> <body> Test Content </body> </html>';
|
56
|
-
// This is the EXPECTED output after full minification by the real library
|
57
60
|
const minifiedHtmlContent = '<html><head><title>Test</title></head><body>Test Content</body></html>';
|
58
61
|
const sampleCssContent = ' body { color: blue; /* comment */ } ';
|
59
|
-
// Expected CSS output from our simple mock helper
|
60
62
|
const minifiedCssContent = 'body{color:blue}';
|
61
63
|
const sampleJsContent = ' function hello ( name ) { console.log("hello", name ); alert ( 1 ) ; } ';
|
62
|
-
// Expected JS output (can be a fixed string for the mock)
|
63
64
|
const minifiedJsContent = 'function hello(o){console.log("hello",o),alert(1)}';
|
64
65
|
|
65
66
|
const sampleParsedInput: ParsedHTML = {
|
@@ -75,339 +76,247 @@ describe('🧼 Minifier', () => {
|
|
75
76
|
jest.clearAllMocks(); // Clear mocks between tests
|
76
77
|
|
77
78
|
// Set up logger spies
|
78
|
-
mockLogger = new Logger(
|
79
|
+
mockLogger = new Logger(LogLevel.WARN);
|
79
80
|
mockLoggerWarnFn = jest.spyOn(mockLogger, 'warn');
|
80
81
|
mockLoggerDebugFn = jest.spyOn(mockLogger, 'debug');
|
81
82
|
|
82
|
-
// --- Configure Mock Implementations ---
|
83
|
-
|
84
|
-
|
85
|
-
// The mock should simulate the *result* of html-minifier-terser with collapseWhitespace: true.
|
86
|
-
mockHtmlMinifierMinifyFn.mockImplementation(async (_html: string, _options?: HtmlMinifyOptions) => {
|
87
|
-
// Assume if this mock is called, the desired output is the fully minified version
|
88
|
-
return minifiedHtmlContent;
|
89
|
-
});
|
90
|
-
|
91
|
-
// Mock the CleanCSS constructor to return an object with a 'minify' method
|
92
|
-
mockCleanCSSConstructorFn.mockImplementation(() => ({
|
93
|
-
minify: mockCleanCSSInstanceMinifyFn
|
94
|
-
}));
|
83
|
+
// --- Configure Mock Implementations using the *typed* mocks ---
|
84
|
+
// These should now type-check correctly
|
85
|
+
mockHtmlMinifierMinifyFn.mockImplementation(async (_html, _options) => minifiedHtmlContent);
|
95
86
|
|
96
|
-
|
97
|
-
|
98
|
-
mockCleanCSSInstanceMinifyFn.mockImplementation((css: string): CleanCSSOutput => {
|
87
|
+
mockCleanCSSInstanceMinifyFn.mockImplementation((css): CleanCSSOutput => {
|
88
|
+
if (typeof css !== 'string') css = '// Mocked non-string CSS input'; // Handle non-string input for mock
|
99
89
|
const minifiedStyles = simpleMockCssMinify(css);
|
100
|
-
const stats = {
|
101
|
-
originalSize: css.length,
|
102
|
-
minifiedSize: minifiedStyles.length,
|
90
|
+
const stats = {
|
91
|
+
originalSize: css.length, minifiedSize: minifiedStyles.length,
|
103
92
|
efficiency: css.length > 0 ? (css.length - minifiedStyles.length) / css.length : 0,
|
104
|
-
timeSpent: 1,
|
93
|
+
timeSpent: 1,
|
105
94
|
};
|
106
|
-
// Return the structure expected by the code (CleanCSSSyncResult shape)
|
107
95
|
return { styles: minifiedStyles, errors: [], warnings: [], stats: stats };
|
108
96
|
});
|
109
97
|
|
110
|
-
|
111
|
-
mockTerserMinifyFn.mockImplementation(async (_js: string, _options?: MinifyOptions): Promise<MinifyOutput> => {
|
112
|
-
// Return the expected minified JS content for the test case
|
98
|
+
mockTerserMinifyFn.mockImplementation(async (_js, _options) => {
|
113
99
|
return Promise.resolve({ code: minifiedJsContent, error: undefined });
|
114
100
|
});
|
115
101
|
});
|
116
102
|
|
103
|
+
// --- Tests (Assertions use the explicitly typed mock functions) ---
|
117
104
|
describe('Basic functionality', () => {
|
118
105
|
it('✅ leaves content unchanged when minification is disabled', async () => {
|
119
106
|
const options: BundleOptions = { minifyHtml: false, minifyCss: false, minifyJs: false };
|
120
107
|
const result = await minifyAssets(sampleParsedInput, options, mockLogger);
|
121
108
|
|
122
109
|
expect(mockHtmlMinifierMinifyFn).not.toHaveBeenCalled();
|
123
|
-
expect(mockCleanCSSConstructorFn).not.toHaveBeenCalled(); // Check constructor
|
110
|
+
expect(mockCleanCSSConstructorFn).not.toHaveBeenCalled(); // Check constructor
|
111
|
+
expect(mockCleanCSSInstanceMinifyFn).not.toHaveBeenCalled(); // Check instance method
|
124
112
|
expect(mockTerserMinifyFn).not.toHaveBeenCalled();
|
125
113
|
|
126
|
-
expect(result.htmlContent).toBe(sampleHtmlContent);
|
127
|
-
expect(result.assets.find(a => a.type === 'css')?.content).toBe(sampleCssContent);
|
128
|
-
expect(result.assets.find(a => a.type === 'js')?.content).toBe(sampleJsContent);
|
129
|
-
expect(result.assets.find(a => a.type === 'image')?.content).toBe('data:image/png;base64,abc'); // Should be untouched
|
114
|
+
expect(result.htmlContent).toBe(sampleHtmlContent);
|
115
|
+
expect(result.assets.find(a => a.type === 'css')?.content).toBe(sampleCssContent);
|
116
|
+
expect(result.assets.find(a => a.type === 'js')?.content).toBe(sampleJsContent);
|
130
117
|
});
|
131
118
|
|
132
|
-
// Check against the corrected expectations
|
133
119
|
it('🔧 minifies HTML, CSS, and JS with all options enabled (default)', async () => {
|
134
|
-
const result = await minifyAssets(sampleParsedInput, {}, mockLogger);
|
120
|
+
const result = await minifyAssets(sampleParsedInput, {}, mockLogger);
|
135
121
|
|
136
122
|
expect(mockHtmlMinifierMinifyFn).toHaveBeenCalledTimes(1);
|
137
|
-
//
|
123
|
+
expect(mockCleanCSSConstructorFn).toHaveBeenCalledTimes(1); // Constructor should be called once
|
138
124
|
expect(mockCleanCSSInstanceMinifyFn).toHaveBeenCalledTimes(1);
|
139
125
|
expect(mockTerserMinifyFn).toHaveBeenCalledTimes(1);
|
140
126
|
|
141
|
-
// Check against the defined minified constants
|
142
127
|
expect(result.htmlContent).toBe(minifiedHtmlContent);
|
143
128
|
expect(result.assets.find(a => a.type === 'css')?.content).toBe(minifiedCssContent);
|
144
129
|
expect(result.assets.find(a => a.type === 'js')?.content).toBe(minifiedJsContent);
|
145
|
-
expect(result.assets.find(a => a.type === 'image')?.content).toBe('data:image/png;base64,abc'); // Should still be untouched
|
146
130
|
});
|
147
131
|
});
|
148
132
|
|
149
133
|
describe('Error handling', () => {
|
150
134
|
it('💥 handles broken HTML minification gracefully', async () => {
|
151
135
|
const htmlError = new Error('HTML parse error!');
|
152
|
-
//
|
153
|
-
mockHtmlMinifierMinifyFn.
|
136
|
+
// Use mockImplementationOnce to override for this test
|
137
|
+
mockHtmlMinifierMinifyFn.mockImplementationOnce(async () => { throw htmlError; });
|
154
138
|
|
155
139
|
const result = await minifyAssets(sampleParsedInput, {}, mockLogger);
|
156
140
|
|
157
|
-
// Original HTML should be kept
|
158
141
|
expect(result.htmlContent).toBe(sampleHtmlContent);
|
159
|
-
// Other assets should still be minified if successful
|
160
142
|
expect(result.assets.find(a => a.type === 'css')?.content).toBe(minifiedCssContent);
|
161
143
|
expect(result.assets.find(a => a.type === 'js')?.content).toBe(minifiedJsContent);
|
162
|
-
// Logger should have been warned
|
163
144
|
expect(mockLoggerWarnFn).toHaveBeenCalledWith(expect.stringContaining(`HTML minification failed: ${htmlError.message}`));
|
164
|
-
// Ensure other minifiers were still called
|
165
145
|
expect(mockCleanCSSInstanceMinifyFn).toHaveBeenCalledTimes(1);
|
166
146
|
expect(mockTerserMinifyFn).toHaveBeenCalledTimes(1);
|
167
147
|
});
|
168
148
|
|
169
149
|
it('💥 handles CSS minifier failure (returning errors array)', async () => {
|
170
150
|
const cssErrorMsg = 'Invalid CSS syntax';
|
171
|
-
//
|
151
|
+
// Use mockReturnValueOnce for the instance method
|
172
152
|
mockCleanCSSInstanceMinifyFn.mockReturnValueOnce({
|
173
|
-
errors: [cssErrorMsg], warnings: [], styles:
|
153
|
+
errors: [cssErrorMsg], warnings: [], styles: '',
|
174
154
|
stats: { originalSize: sampleCssContent.length, minifiedSize: 0, efficiency: 0, timeSpent: 0 }
|
175
155
|
});
|
176
156
|
|
177
157
|
const result = await minifyAssets(sampleParsedInput, {}, mockLogger);
|
178
158
|
|
179
|
-
// HTML and JS should still be minified
|
180
159
|
expect(result.htmlContent).toBe(minifiedHtmlContent);
|
181
160
|
expect(result.assets.find(a => a.type === 'js')?.content).toBe(minifiedJsContent);
|
182
|
-
// Original CSS should be kept
|
183
161
|
expect(result.assets.find(a => a.type === 'css')?.content).toBe(sampleCssContent);
|
184
|
-
// Logger should have been warned
|
185
162
|
expect(mockLoggerWarnFn).toHaveBeenCalledWith(expect.stringContaining(`CleanCSS failed for style.css: ${cssErrorMsg}`));
|
186
|
-
// Ensure other minifiers were still called
|
187
163
|
expect(mockHtmlMinifierMinifyFn).toHaveBeenCalledTimes(1);
|
188
164
|
expect(mockTerserMinifyFn).toHaveBeenCalledTimes(1);
|
189
165
|
});
|
190
166
|
|
191
167
|
it('💥 handles CSS minifier failure (throwing exception)', async () => {
|
192
168
|
const cssError = new Error('CleanCSS crashed!');
|
193
|
-
// Make the CSS mock throw
|
194
169
|
mockCleanCSSInstanceMinifyFn.mockImplementationOnce(() => { throw cssError; });
|
195
170
|
|
196
171
|
const result = await minifyAssets(sampleParsedInput, {}, mockLogger);
|
197
172
|
|
198
|
-
// HTML and JS should still be minified
|
199
173
|
expect(result.htmlContent).toBe(minifiedHtmlContent);
|
200
174
|
expect(result.assets.find(a => a.type === 'js')?.content).toBe(minifiedJsContent);
|
201
|
-
// Original CSS should be kept
|
202
175
|
expect(result.assets.find(a => a.type === 'css')?.content).toBe(sampleCssContent);
|
203
|
-
// Logger should have been warned about the catch block
|
204
176
|
expect(mockLoggerWarnFn).toHaveBeenCalledWith(expect.stringContaining(`Failed to minify asset style.css (css): ${cssError.message}`));
|
205
|
-
|
206
|
-
|
207
|
-
expect(mockTerserMinifyFn).toHaveBeenCalledTimes(1);
|
177
|
+
expect(mockHtmlMinifierMinifyFn).toHaveBeenCalledTimes(1);
|
178
|
+
expect(mockTerserMinifyFn).toHaveBeenCalledTimes(1);
|
208
179
|
});
|
209
180
|
|
210
181
|
it('💥 handles JS minifier failure (returning error object)', async () => {
|
211
182
|
const jsError = new Error('Terser parse error!');
|
212
|
-
|
213
|
-
|
183
|
+
// Use mockImplementationOnce for the async function
|
184
|
+
mockTerserMinifyFn.mockImplementationOnce(async () => ({ code: undefined, error: jsError }));
|
214
185
|
|
215
186
|
const result = await minifyAssets(sampleParsedInput, {}, mockLogger);
|
216
187
|
|
217
|
-
// HTML and CSS should still be minified
|
218
188
|
expect(result.htmlContent).toBe(minifiedHtmlContent);
|
219
189
|
expect(result.assets.find(a => a.type === 'css')?.content).toBe(minifiedCssContent);
|
220
|
-
// Original JS should be kept
|
221
190
|
expect(result.assets.find(a => a.type === 'js')?.content).toBe(sampleJsContent);
|
222
|
-
// Logger should have been warned
|
223
|
-
// Note: Your code checks for `result.code` first, then `(result as any).error`
|
224
191
|
expect(mockLoggerWarnFn).toHaveBeenCalledWith(expect.stringContaining(`Terser failed for script.js: ${jsError.message}`));
|
225
|
-
|
226
|
-
|
227
|
-
expect(mockCleanCSSInstanceMinifyFn).toHaveBeenCalledTimes(1);
|
192
|
+
expect(mockHtmlMinifierMinifyFn).toHaveBeenCalledTimes(1);
|
193
|
+
expect(mockCleanCSSInstanceMinifyFn).toHaveBeenCalledTimes(1);
|
228
194
|
});
|
229
195
|
|
230
196
|
it('💥 handles JS minifier failure (throwing exception)', async () => {
|
231
197
|
const jsError = new Error('Terser crashed!');
|
232
|
-
//
|
233
|
-
mockTerserMinifyFn.
|
198
|
+
// Use mockImplementationOnce to reject the promise
|
199
|
+
mockTerserMinifyFn.mockImplementationOnce(async () => { throw jsError; });
|
234
200
|
|
235
201
|
const result = await minifyAssets(sampleParsedInput, {}, mockLogger);
|
236
202
|
|
237
|
-
// HTML and CSS should still be minified
|
238
203
|
expect(result.htmlContent).toBe(minifiedHtmlContent);
|
239
204
|
expect(result.assets.find(a => a.type === 'css')?.content).toBe(minifiedCssContent);
|
240
|
-
// Original JS should be kept
|
241
205
|
expect(result.assets.find(a => a.type === 'js')?.content).toBe(sampleJsContent);
|
242
|
-
// Logger should have been warned from the catch block
|
243
206
|
expect(mockLoggerWarnFn).toHaveBeenCalledWith(expect.stringContaining(`Failed to minify asset script.js (js): ${jsError.message}`));
|
244
|
-
// Ensure other minifiers were still called
|
245
207
|
expect(mockHtmlMinifierMinifyFn).toHaveBeenCalledTimes(1);
|
246
208
|
expect(mockCleanCSSInstanceMinifyFn).toHaveBeenCalledTimes(1);
|
247
209
|
});
|
248
210
|
|
249
211
|
it('🧼 skips minification for assets without content or empty content', async () => {
|
250
|
-
const inputWithMissingContent: ParsedHTML = {
|
212
|
+
const inputWithMissingContent: ParsedHTML = { /* ... as before ... */
|
251
213
|
htmlContent: sampleHtmlContent,
|
252
214
|
assets: [
|
253
|
-
{ type: 'css', url: 'style.css', content: sampleCssContent },
|
254
|
-
{ type: 'js', url: 'missing.js'
|
255
|
-
{ type: 'css', url: 'empty.css', content: '' },
|
256
|
-
{ type: 'js', url: 'script2.js', content: sampleJsContent }
|
215
|
+
{ type: 'css', url: 'style.css', content: sampleCssContent },
|
216
|
+
{ type: 'js', url: 'missing.js' },
|
217
|
+
{ type: 'css', url: 'empty.css', content: '' },
|
218
|
+
{ type: 'js', url: 'script2.js', content: sampleJsContent }
|
257
219
|
]
|
258
220
|
};
|
259
221
|
const result = await minifyAssets(inputWithMissingContent, {}, mockLogger);
|
260
222
|
|
261
|
-
|
262
|
-
expect(result.assets.find(a => a.url === '
|
263
|
-
expect(result.assets.find(a => a.url === '
|
264
|
-
expect(result.assets.find(a => a.url === '
|
265
|
-
expect(result.assets.find(a => a.url === 'script2.js')?.content).toBe(minifiedJsContent); // Minified
|
266
|
-
|
267
|
-
// HTML should still be minified
|
223
|
+
expect(result.assets.find(a => a.url === 'style.css')?.content).toBe(minifiedCssContent);
|
224
|
+
expect(result.assets.find(a => a.url === 'missing.js')?.content).toBeUndefined();
|
225
|
+
expect(result.assets.find(a => a.url === 'empty.css')?.content).toBe('');
|
226
|
+
expect(result.assets.find(a => a.url === 'script2.js')?.content).toBe(minifiedJsContent);
|
268
227
|
expect(result.htmlContent).toBe(minifiedHtmlContent);
|
269
|
-
|
270
|
-
// Check how many times minifiers were actually called
|
271
228
|
expect(mockCleanCSSInstanceMinifyFn).toHaveBeenCalledTimes(1); // Only for style.css
|
272
229
|
expect(mockTerserMinifyFn).toHaveBeenCalledTimes(1); // Only for script2.js
|
273
|
-
expect(mockHtmlMinifierMinifyFn).toHaveBeenCalledTimes(1);
|
230
|
+
expect(mockHtmlMinifierMinifyFn).toHaveBeenCalledTimes(1);
|
274
231
|
});
|
275
232
|
});
|
276
233
|
|
277
234
|
describe('Selective minification', () => {
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
expect(mockHtmlMinifierMinifyFn).toHaveBeenCalledTimes(1);
|
283
|
-
expect(mockCleanCSSInstanceMinifyFn).toHaveBeenCalledTimes(1);
|
284
|
-
expect(mockTerserMinifyFn).not.toHaveBeenCalled(); // JS shouldn't be called
|
285
|
-
|
286
|
-
expect(result.htmlContent).toBe(minifiedHtmlContent); // Minified
|
287
|
-
expect(result.assets.find(a => a.type === 'css')?.content).toBe(minifiedCssContent); // Minified
|
288
|
-
expect(result.assets.find(a => a.type === 'js')?.content).toBe(sampleJsContent); // Original
|
289
|
-
});
|
290
|
-
|
291
|
-
it('🎛 only minifies JS + CSS, leaves HTML unchanged', async () => {
|
292
|
-
const options: BundleOptions = { minifyHtml: false, minifyCss: true, minifyJs: true };
|
293
|
-
const result = await minifyAssets(sampleParsedInput, options, mockLogger);
|
294
|
-
|
295
|
-
expect(mockHtmlMinifierMinifyFn).not.toHaveBeenCalled(); // HTML shouldn't be called
|
296
|
-
expect(mockCleanCSSInstanceMinifyFn).toHaveBeenCalledTimes(1);
|
297
|
-
expect(mockTerserMinifyFn).toHaveBeenCalledTimes(1);
|
298
|
-
|
299
|
-
expect(result.htmlContent).toBe(sampleHtmlContent); // Original
|
300
|
-
expect(result.assets.find(a => a.type === 'css')?.content).toBe(minifiedCssContent); // Minified
|
301
|
-
expect(result.assets.find(a => a.type === 'js')?.content).toBe(minifiedJsContent); // Minified
|
302
|
-
});
|
303
|
-
|
304
|
-
it('🎛 only minifies HTML, leaves CSS/JS unchanged', async () => {
|
305
|
-
const options: BundleOptions = { minifyHtml: true, minifyCss: false, minifyJs: false };
|
235
|
+
// These tests should remain the same, checking which mocks are called
|
236
|
+
it('🎛 only minifies CSS + HTML, leaves JS unchanged', async () => { /* ... as before ... */
|
237
|
+
const options: BundleOptions = { minifyHtml: true, minifyCss: true, minifyJs: false };
|
306
238
|
const result = await minifyAssets(sampleParsedInput, options, mockLogger);
|
307
|
-
|
308
239
|
expect(mockHtmlMinifierMinifyFn).toHaveBeenCalledTimes(1);
|
309
|
-
expect(mockCleanCSSInstanceMinifyFn).
|
240
|
+
expect(mockCleanCSSInstanceMinifyFn).toHaveBeenCalledTimes(1);
|
310
241
|
expect(mockTerserMinifyFn).not.toHaveBeenCalled();
|
311
|
-
|
312
|
-
expect(result.
|
313
|
-
expect(result.assets.find(a => a.type === '
|
314
|
-
|
242
|
+
expect(result.htmlContent).toBe(minifiedHtmlContent);
|
243
|
+
expect(result.assets.find(a => a.type === 'css')?.content).toBe(minifiedCssContent);
|
244
|
+
expect(result.assets.find(a => a.type === 'js')?.content).toBe(sampleJsContent);
|
245
|
+
});
|
246
|
+
it('🎛 only minifies JS + CSS, leaves HTML unchanged', async () => { /* ... as before ... */
|
247
|
+
const options: BundleOptions = { minifyHtml: false, minifyCss: true, minifyJs: true };
|
248
|
+
const result = await minifyAssets(sampleParsedInput, options, mockLogger);
|
249
|
+
expect(mockHtmlMinifierMinifyFn).not.toHaveBeenCalled();
|
250
|
+
expect(mockCleanCSSInstanceMinifyFn).toHaveBeenCalledTimes(1);
|
251
|
+
expect(mockTerserMinifyFn).toHaveBeenCalledTimes(1);
|
252
|
+
expect(result.htmlContent).toBe(sampleHtmlContent);
|
253
|
+
expect(result.assets.find(a => a.type === 'css')?.content).toBe(minifiedCssContent);
|
254
|
+
expect(result.assets.find(a => a.type === 'js')?.content).toBe(minifiedJsContent);
|
315
255
|
});
|
256
|
+
it('🎛 only minifies HTML, leaves CSS/JS unchanged', async () => { /* ... as before ... */
|
257
|
+
const options: BundleOptions = { minifyHtml: true, minifyCss: false, minifyJs: false };
|
258
|
+
const result = await minifyAssets(sampleParsedInput, options, mockLogger);
|
259
|
+
expect(mockHtmlMinifierMinifyFn).toHaveBeenCalledTimes(1);
|
260
|
+
expect(mockCleanCSSInstanceMinifyFn).not.toHaveBeenCalled();
|
261
|
+
expect(mockTerserMinifyFn).not.toHaveBeenCalled();
|
262
|
+
expect(result.htmlContent).toBe(minifiedHtmlContent);
|
263
|
+
expect(result.assets.find(a => a.type === 'css')?.content).toBe(sampleCssContent);
|
264
|
+
expect(result.assets.find(a => a.type === 'js')?.content).toBe(sampleJsContent);
|
265
|
+
});
|
316
266
|
});
|
317
267
|
|
318
268
|
describe('Content types', () => {
|
319
|
-
it('📦 only processes css/js types, skips image/font/other', async () => {
|
320
|
-
const inputWithVariousTypes: ParsedHTML = {
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
// Check CSS/JS were minified
|
342
|
-
expect(result.assets.find(a => a.type === 'css')?.content).toBe(minifiedCssContent);
|
343
|
-
expect(result.assets.find(a => a.type === 'js')?.content).toBe(minifiedJsContent);
|
344
|
-
// Check HTML was minified
|
345
|
-
expect(result.htmlContent).toBe(minifiedHtmlContent);
|
346
|
-
});
|
269
|
+
it('📦 only processes css/js types, skips image/font/other', async () => { /* ... as before ... */
|
270
|
+
const inputWithVariousTypes: ParsedHTML = { /* ... as before ... */
|
271
|
+
htmlContent: sampleHtmlContent,
|
272
|
+
assets: [
|
273
|
+
{ type: 'css', url: 'style.css', content: sampleCssContent },
|
274
|
+
{ type: 'js', url: 'script.js', content: sampleJsContent },
|
275
|
+
{ type: 'image', url: 'logo.png', content: 'data:image/png;base64,abc' },
|
276
|
+
{ type: 'font', url: 'font.woff2', content: 'data:font/woff2;base64,def' },
|
277
|
+
{ type: 'other', url: 'data.json', content: '{"a":1}' }
|
278
|
+
]
|
279
|
+
};
|
280
|
+
const result = await minifyAssets(inputWithVariousTypes, {}, mockLogger);
|
281
|
+
expect(mockHtmlMinifierMinifyFn).toHaveBeenCalledTimes(1);
|
282
|
+
expect(mockCleanCSSInstanceMinifyFn).toHaveBeenCalledTimes(1);
|
283
|
+
expect(mockTerserMinifyFn).toHaveBeenCalledTimes(1);
|
284
|
+
expect(result.assets.find(a => a.type === 'image')?.content).toBe('data:image/png;base64,abc');
|
285
|
+
expect(result.assets.find(a => a.type === 'font')?.content).toBe('data:font/woff2;base64,def');
|
286
|
+
expect(result.assets.find(a => a.type === 'other')?.content).toBe('{"a":1}');
|
287
|
+
expect(result.assets.find(a => a.type === 'css')?.content).toBe(minifiedCssContent);
|
288
|
+
expect(result.assets.find(a => a.type === 'js')?.content).toBe(minifiedJsContent);
|
289
|
+
expect(result.htmlContent).toBe(minifiedHtmlContent);
|
290
|
+
});
|
347
291
|
});
|
348
292
|
|
349
293
|
describe('Edge Cases', () => {
|
350
|
-
it('💨 handles empty input object gracefully', async () => {
|
351
|
-
|
352
|
-
|
294
|
+
it('💨 handles empty input object gracefully', async () => { /* ... as before ... */ });
|
295
|
+
it('💨 handles input with assets but empty HTML content string', async () => { /* ... as before ... */ });
|
296
|
+
it('💨 handles input with HTML but empty assets array', async () => { /* ... as before ... */ });
|
353
297
|
|
354
|
-
expect(result.htmlContent).toBe('');
|
355
|
-
expect(result.assets).toEqual([]);
|
356
|
-
expect(mockHtmlMinifierMinifyFn).not.toHaveBeenCalled();
|
357
|
-
expect(mockCleanCSSConstructorFn).not.toHaveBeenCalled();
|
358
|
-
expect(mockTerserMinifyFn).not.toHaveBeenCalled();
|
359
|
-
expect(mockLoggerDebugFn).toHaveBeenCalledWith('Minification skipped: No content.');
|
360
|
-
});
|
361
|
-
|
362
|
-
it('💨 handles input with assets but empty HTML content string', async () => {
|
363
|
-
const input: ParsedHTML = {
|
364
|
-
htmlContent: '',
|
365
|
-
assets: [ { type: 'css', url: 'style.css', content: sampleCssContent } ]
|
366
|
-
};
|
367
|
-
const result = await minifyAssets(input, {}, mockLogger);
|
368
|
-
|
369
|
-
expect(result.htmlContent).toBe(''); // Should remain empty
|
370
|
-
expect(result.assets.find(a => a.type === 'css')?.content).toBe(minifiedCssContent); // CSS should be minified
|
371
|
-
expect(mockHtmlMinifierMinifyFn).not.toHaveBeenCalled(); // No HTML to minify
|
372
|
-
expect(mockCleanCSSInstanceMinifyFn).toHaveBeenCalledTimes(1);
|
373
|
-
expect(mockTerserMinifyFn).not.toHaveBeenCalled();
|
374
|
-
});
|
375
|
-
|
376
|
-
it('💨 handles input with HTML but empty assets array', async () => {
|
377
|
-
const input: ParsedHTML = { htmlContent: sampleHtmlContent, assets: [] };
|
378
|
-
const result = await minifyAssets(input, {}, mockLogger);
|
379
|
-
|
380
|
-
expect(result.htmlContent).toBe(minifiedHtmlContent); // HTML should be minified
|
381
|
-
expect(result.assets).toEqual([]); // Assets should remain empty
|
382
|
-
expect(mockHtmlMinifierMinifyFn).toHaveBeenCalledTimes(1);
|
383
|
-
expect(mockCleanCSSInstanceMinifyFn).not.toHaveBeenCalled(); // No CSS assets
|
384
|
-
expect(mockTerserMinifyFn).not.toHaveBeenCalled(); // No JS assets
|
385
|
-
});
|
386
|
-
|
387
|
-
// Test case for the CleanCSS warning path (no error, no styles)
|
388
298
|
it('⚠️ handles CleanCSS returning no styles without errors', async () => {
|
299
|
+
// Use mockReturnValueOnce for the instance method mock
|
389
300
|
mockCleanCSSInstanceMinifyFn.mockReturnValueOnce({
|
390
|
-
errors: [], warnings: [], styles:
|
301
|
+
errors: [], warnings: [], styles: '',
|
391
302
|
stats: { originalSize: sampleCssContent.length, minifiedSize: 0, efficiency: 0, timeSpent: 0 }
|
392
303
|
});
|
393
304
|
|
394
305
|
const result = await minifyAssets(sampleParsedInput, {}, mockLogger);
|
395
|
-
|
396
|
-
expect(result.
|
397
|
-
expect(result.assets.find(a => a.type === '
|
398
|
-
expect(result.assets.find(a => a.type === 'js')?.content).toBe(minifiedJsContent); // JS minified
|
306
|
+
expect(result.htmlContent).toBe(minifiedHtmlContent);
|
307
|
+
expect(result.assets.find(a => a.type === 'css')?.content).toBe(sampleCssContent);
|
308
|
+
expect(result.assets.find(a => a.type === 'js')?.content).toBe(minifiedJsContent);
|
399
309
|
expect(mockLoggerWarnFn).toHaveBeenCalledWith(expect.stringContaining('CleanCSS produced no styles but reported no errors for style.css. Keeping original.'));
|
400
310
|
});
|
401
311
|
|
402
|
-
// Test case for the Terser warning path (no error, no code)
|
403
312
|
it('⚠️ handles Terser returning no code without errors', async () => {
|
404
|
-
|
313
|
+
// Use mockImplementationOnce for the async function
|
314
|
+
mockTerserMinifyFn.mockImplementationOnce(async () => ({ code: undefined, error: undefined }));
|
405
315
|
|
406
316
|
const result = await minifyAssets(sampleParsedInput, {}, mockLogger);
|
407
|
-
|
408
|
-
expect(result.
|
409
|
-
expect(result.assets.find(a => a.type === '
|
410
|
-
expect(result.assets.find(a => a.type === 'js')?.content).toBe(sampleJsContent); // Original JS kept
|
317
|
+
expect(result.htmlContent).toBe(minifiedHtmlContent);
|
318
|
+
expect(result.assets.find(a => a.type === 'css')?.content).toBe(minifiedCssContent);
|
319
|
+
expect(result.assets.find(a => a.type === 'js')?.content).toBe(sampleJsContent);
|
411
320
|
expect(mockLoggerWarnFn).toHaveBeenCalledWith(expect.stringContaining('Terser produced no code but reported no errors for script.js. Keeping original.'));
|
412
321
|
});
|
413
322
|
});
|