@uploadista/flow-images-sharp 0.0.13-beta.4 → 0.0.13
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/package.json +12 -7
- package/tests/image-plugin.test.ts +470 -0
- package/vitest.config.ts +39 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uploadista/flow-images-sharp",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.13
|
|
4
|
+
"version": "0.0.13",
|
|
5
5
|
"description": "Sharp image processing service for Uploadista Flow",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Uploadista",
|
|
@@ -15,21 +15,26 @@
|
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"sharp": "0.34.5",
|
|
18
|
-
"effect": "3.19.
|
|
18
|
+
"effect": "3.19.3",
|
|
19
19
|
"tinycolor2": "1.6.0",
|
|
20
20
|
"zod": "4.1.12",
|
|
21
|
-
"@uploadista/core": "0.0.13
|
|
21
|
+
"@uploadista/core": "0.0.13"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@
|
|
24
|
+
"@effect/vitest": "0.27.0",
|
|
25
|
+
"@types/node": "24.10.1",
|
|
25
26
|
"@types/tinycolor2": "1.4.6",
|
|
26
|
-
"tsdown": "0.16.
|
|
27
|
-
"
|
|
27
|
+
"tsdown": "0.16.3",
|
|
28
|
+
"vitest": "4.0.8",
|
|
29
|
+
"@uploadista/typescript-config": "0.0.13"
|
|
28
30
|
},
|
|
29
31
|
"scripts": {
|
|
30
32
|
"build": "tsdown",
|
|
31
33
|
"format": "biome format --write ./src",
|
|
32
34
|
"lint": "biome lint --write ./src",
|
|
33
|
-
"check": "biome check --write ./src"
|
|
35
|
+
"check": "biome check --write ./src",
|
|
36
|
+
"test": "vitest",
|
|
37
|
+
"test:run": "vitest run",
|
|
38
|
+
"test:watch": "vitest watch"
|
|
34
39
|
}
|
|
35
40
|
}
|
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
import { describe, expect, it } from "@effect/vitest";
|
|
2
|
+
import { ImagePlugin } from "@uploadista/core/flow";
|
|
3
|
+
import { Effect } from "effect";
|
|
4
|
+
import sharp from "sharp";
|
|
5
|
+
import { imagePlugin } from "../src/image-plugin";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Test utilities for creating sample images
|
|
9
|
+
*/
|
|
10
|
+
const createTestImage = async (
|
|
11
|
+
width: number,
|
|
12
|
+
height: number,
|
|
13
|
+
color: { r: number; g: number; b: number } = { r: 255, g: 0, b: 0 },
|
|
14
|
+
): Promise<Uint8Array> => {
|
|
15
|
+
const buffer = await sharp({
|
|
16
|
+
create: {
|
|
17
|
+
width,
|
|
18
|
+
height,
|
|
19
|
+
channels: 3,
|
|
20
|
+
background: color,
|
|
21
|
+
},
|
|
22
|
+
})
|
|
23
|
+
.png()
|
|
24
|
+
.toBuffer();
|
|
25
|
+
|
|
26
|
+
return new Uint8Array(buffer);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const getImageMetadata = async (imageBytes: Uint8Array) => {
|
|
30
|
+
return await sharp(Buffer.from(imageBytes)).metadata();
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
describe("Sharp Image Plugin", () => {
|
|
34
|
+
describe("optimize", () => {
|
|
35
|
+
it.effect("should optimize image with specified quality and format", () =>
|
|
36
|
+
Effect.gen(function* () {
|
|
37
|
+
const plugin = yield* ImagePlugin;
|
|
38
|
+
const inputImage = yield* Effect.promise(() =>
|
|
39
|
+
createTestImage(100, 100),
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const optimized = yield* plugin.optimize(inputImage, {
|
|
43
|
+
quality: 80,
|
|
44
|
+
format: "jpeg",
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
expect(optimized).toBeInstanceOf(Uint8Array);
|
|
48
|
+
expect(optimized.length).toBeGreaterThan(0);
|
|
49
|
+
|
|
50
|
+
// Verify output is JPEG
|
|
51
|
+
const metadata = yield* Effect.promise(() =>
|
|
52
|
+
getImageMetadata(optimized),
|
|
53
|
+
);
|
|
54
|
+
expect(metadata.format).toBe("jpeg");
|
|
55
|
+
}).pipe(Effect.provide(imagePlugin)),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
it.effect("should optimize to webp format", () =>
|
|
59
|
+
Effect.gen(function* () {
|
|
60
|
+
const plugin = yield* ImagePlugin;
|
|
61
|
+
const inputImage = yield* Effect.promise(() =>
|
|
62
|
+
createTestImage(100, 100),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const optimized = yield* plugin.optimize(inputImage, {
|
|
66
|
+
quality: 85,
|
|
67
|
+
format: "webp",
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const metadata = yield* Effect.promise(() =>
|
|
71
|
+
getImageMetadata(optimized),
|
|
72
|
+
);
|
|
73
|
+
expect(metadata.format).toBe("webp");
|
|
74
|
+
}).pipe(Effect.provide(imagePlugin)),
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
it.effect("should optimize to png format", () =>
|
|
78
|
+
Effect.gen(function* () {
|
|
79
|
+
const plugin = yield* ImagePlugin;
|
|
80
|
+
const inputImage = yield* Effect.promise(() =>
|
|
81
|
+
createTestImage(100, 100),
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const optimized = yield* plugin.optimize(inputImage, {
|
|
85
|
+
quality: 90,
|
|
86
|
+
format: "png",
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const metadata = yield* Effect.promise(() =>
|
|
90
|
+
getImageMetadata(optimized),
|
|
91
|
+
);
|
|
92
|
+
expect(metadata.format).toBe("png");
|
|
93
|
+
}).pipe(Effect.provide(imagePlugin)),
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
it.effect("should handle different quality levels", () =>
|
|
97
|
+
Effect.gen(function* () {
|
|
98
|
+
const plugin = yield* ImagePlugin;
|
|
99
|
+
const inputImage = yield* Effect.promise(() =>
|
|
100
|
+
createTestImage(200, 200),
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
// High quality
|
|
104
|
+
const highQuality = yield* plugin.optimize(inputImage, {
|
|
105
|
+
quality: 95,
|
|
106
|
+
format: "jpeg",
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Low quality
|
|
110
|
+
const lowQuality = yield* plugin.optimize(inputImage, {
|
|
111
|
+
quality: 50,
|
|
112
|
+
format: "jpeg",
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Lower quality should result in smaller file size
|
|
116
|
+
expect(lowQuality.length).toBeLessThan(highQuality.length);
|
|
117
|
+
}).pipe(Effect.provide(imagePlugin)),
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe("resize", () => {
|
|
122
|
+
it.effect("should resize image with both width and height", () =>
|
|
123
|
+
Effect.gen(function* () {
|
|
124
|
+
const plugin = yield* ImagePlugin;
|
|
125
|
+
const inputImage = yield* Effect.promise(() =>
|
|
126
|
+
createTestImage(400, 300),
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const resized = yield* plugin.resize(inputImage, {
|
|
130
|
+
width: 200,
|
|
131
|
+
height: 150,
|
|
132
|
+
fit: "cover",
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const metadata = yield* Effect.promise(() => getImageMetadata(resized));
|
|
136
|
+
expect(metadata.width).toBe(200);
|
|
137
|
+
expect(metadata.height).toBe(150);
|
|
138
|
+
}).pipe(Effect.provide(imagePlugin)),
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
it.effect("should resize with width only", () =>
|
|
142
|
+
Effect.gen(function* () {
|
|
143
|
+
const plugin = yield* ImagePlugin;
|
|
144
|
+
const inputImage = yield* Effect.promise(() =>
|
|
145
|
+
createTestImage(400, 300),
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
const resized = yield* plugin.resize(inputImage, {
|
|
149
|
+
width: 200,
|
|
150
|
+
fit: "cover",
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const metadata = yield* Effect.promise(() => getImageMetadata(resized));
|
|
154
|
+
expect(metadata.width).toBe(200);
|
|
155
|
+
// Height should be proportional
|
|
156
|
+
expect(metadata.height).toBeLessThanOrEqual(300);
|
|
157
|
+
}).pipe(Effect.provide(imagePlugin)),
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
it.effect("should resize with height only", () =>
|
|
161
|
+
Effect.gen(function* () {
|
|
162
|
+
const plugin = yield* ImagePlugin;
|
|
163
|
+
const inputImage = yield* Effect.promise(() =>
|
|
164
|
+
createTestImage(400, 300),
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
const resized = yield* plugin.resize(inputImage, {
|
|
168
|
+
height: 150,
|
|
169
|
+
fit: "cover",
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const metadata = yield* Effect.promise(() => getImageMetadata(resized));
|
|
173
|
+
expect(metadata.height).toBe(150);
|
|
174
|
+
expect(metadata.width).toBeLessThanOrEqual(400);
|
|
175
|
+
}).pipe(Effect.provide(imagePlugin)),
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
it.effect("should handle 'cover' fit mode", () =>
|
|
179
|
+
Effect.gen(function* () {
|
|
180
|
+
const plugin = yield* ImagePlugin;
|
|
181
|
+
const inputImage = yield* Effect.promise(() =>
|
|
182
|
+
createTestImage(400, 300),
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
const resized = yield* plugin.resize(inputImage, {
|
|
186
|
+
width: 200,
|
|
187
|
+
height: 200,
|
|
188
|
+
fit: "cover",
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const metadata = yield* Effect.promise(() => getImageMetadata(resized));
|
|
192
|
+
expect(metadata.width).toBe(200);
|
|
193
|
+
expect(metadata.height).toBe(200);
|
|
194
|
+
}).pipe(Effect.provide(imagePlugin)),
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
it.effect("should handle 'contain' fit mode", () =>
|
|
198
|
+
Effect.gen(function* () {
|
|
199
|
+
const plugin = yield* ImagePlugin;
|
|
200
|
+
const inputImage = yield* Effect.promise(() =>
|
|
201
|
+
createTestImage(400, 300),
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
const resized = yield* plugin.resize(inputImage, {
|
|
205
|
+
width: 200,
|
|
206
|
+
height: 200,
|
|
207
|
+
fit: "contain",
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const metadata = yield* Effect.promise(() => getImageMetadata(resized));
|
|
211
|
+
expect(metadata.width).toBeLessThanOrEqual(200);
|
|
212
|
+
expect(metadata.height).toBeLessThanOrEqual(200);
|
|
213
|
+
}).pipe(Effect.provide(imagePlugin)),
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
it.effect("should handle 'fill' fit mode (maps to cover)", () =>
|
|
217
|
+
Effect.gen(function* () {
|
|
218
|
+
const plugin = yield* ImagePlugin;
|
|
219
|
+
const inputImage = yield* Effect.promise(() =>
|
|
220
|
+
createTestImage(400, 300),
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
const resized = yield* plugin.resize(inputImage, {
|
|
224
|
+
width: 200,
|
|
225
|
+
height: 200,
|
|
226
|
+
fit: "fill",
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const metadata = yield* Effect.promise(() => getImageMetadata(resized));
|
|
230
|
+
expect(metadata.width).toBe(200);
|
|
231
|
+
expect(metadata.height).toBe(200);
|
|
232
|
+
}).pipe(Effect.provide(imagePlugin)),
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
it.effect("should handle upscaling", () =>
|
|
236
|
+
Effect.gen(function* () {
|
|
237
|
+
const plugin = yield* ImagePlugin;
|
|
238
|
+
const inputImage = yield* Effect.promise(() =>
|
|
239
|
+
createTestImage(100, 100),
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
const resized = yield* plugin.resize(inputImage, {
|
|
243
|
+
width: 200,
|
|
244
|
+
height: 200,
|
|
245
|
+
fit: "cover",
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
const metadata = yield* Effect.promise(() => getImageMetadata(resized));
|
|
249
|
+
expect(metadata.width).toBe(200);
|
|
250
|
+
expect(metadata.height).toBe(200);
|
|
251
|
+
}).pipe(Effect.provide(imagePlugin)),
|
|
252
|
+
);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
describe("transform", () => {
|
|
256
|
+
it.effect("should apply rotation transformation", () =>
|
|
257
|
+
Effect.gen(function* () {
|
|
258
|
+
const plugin = yield* ImagePlugin;
|
|
259
|
+
const inputImage = yield* Effect.promise(() =>
|
|
260
|
+
createTestImage(200, 100),
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
const transformed = yield* plugin.transform(inputImage, {
|
|
264
|
+
type: "rotate",
|
|
265
|
+
angle: 90,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
const metadata = yield* Effect.promise(() =>
|
|
269
|
+
getImageMetadata(transformed),
|
|
270
|
+
);
|
|
271
|
+
// After 90° rotation, dimensions should swap
|
|
272
|
+
expect(metadata.width).toBe(100);
|
|
273
|
+
expect(metadata.height).toBe(200);
|
|
274
|
+
}).pipe(Effect.provide(imagePlugin)),
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
it.effect("should apply flip transformation", () =>
|
|
278
|
+
Effect.gen(function* () {
|
|
279
|
+
const plugin = yield* ImagePlugin;
|
|
280
|
+
const inputImage = yield* Effect.promise(() =>
|
|
281
|
+
createTestImage(200, 100),
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
const transformed = yield* plugin.transform(inputImage, {
|
|
285
|
+
type: "flip",
|
|
286
|
+
direction: "horizontal",
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
expect(transformed).toBeInstanceOf(Uint8Array);
|
|
290
|
+
const metadata = yield* Effect.promise(() =>
|
|
291
|
+
getImageMetadata(transformed),
|
|
292
|
+
);
|
|
293
|
+
expect(metadata.width).toBe(200);
|
|
294
|
+
expect(metadata.height).toBe(100);
|
|
295
|
+
}).pipe(Effect.provide(imagePlugin)),
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
it.effect("should apply flop transformation", () =>
|
|
299
|
+
Effect.gen(function* () {
|
|
300
|
+
const plugin = yield* ImagePlugin;
|
|
301
|
+
const inputImage = yield* Effect.promise(() =>
|
|
302
|
+
createTestImage(200, 100),
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
const transformed = yield* plugin.transform(inputImage, {
|
|
306
|
+
type: "flip",
|
|
307
|
+
direction: "vertical",
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
expect(transformed).toBeInstanceOf(Uint8Array);
|
|
311
|
+
const metadata = yield* Effect.promise(() =>
|
|
312
|
+
getImageMetadata(transformed),
|
|
313
|
+
);
|
|
314
|
+
expect(metadata.width).toBe(200);
|
|
315
|
+
expect(metadata.height).toBe(100);
|
|
316
|
+
}).pipe(Effect.provide(imagePlugin)),
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
it.effect("should apply blur transformation", () =>
|
|
320
|
+
Effect.gen(function* () {
|
|
321
|
+
const plugin = yield* ImagePlugin;
|
|
322
|
+
const inputImage = yield* Effect.promise(() =>
|
|
323
|
+
createTestImage(200, 100),
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
const transformed = yield* plugin.transform(inputImage, {
|
|
327
|
+
type: "blur",
|
|
328
|
+
sigma: 5,
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
expect(transformed).toBeInstanceOf(Uint8Array);
|
|
332
|
+
expect(transformed.length).toBeGreaterThan(0);
|
|
333
|
+
}).pipe(Effect.provide(imagePlugin)),
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
it.effect("should apply grayscale transformation", () =>
|
|
337
|
+
Effect.gen(function* () {
|
|
338
|
+
const plugin = yield* ImagePlugin;
|
|
339
|
+
const inputImage = yield* Effect.promise(() =>
|
|
340
|
+
createTestImage(200, 100),
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
const transformed = yield* plugin.transform(inputImage, {
|
|
344
|
+
type: "grayscale",
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
expect(transformed).toBeInstanceOf(Uint8Array);
|
|
348
|
+
expect(transformed.length).toBeGreaterThan(0);
|
|
349
|
+
}).pipe(Effect.provide(imagePlugin)),
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
it.effect("should apply multiple transformations together", () =>
|
|
353
|
+
Effect.gen(function* () {
|
|
354
|
+
const plugin = yield* ImagePlugin;
|
|
355
|
+
const inputImage = yield* Effect.promise(() =>
|
|
356
|
+
createTestImage(200, 100),
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
const transformedRotated = yield* plugin.transform(inputImage, {
|
|
360
|
+
type: "rotate",
|
|
361
|
+
angle: 180,
|
|
362
|
+
});
|
|
363
|
+
const transformedFlipped = yield* plugin.transform(transformedRotated, {
|
|
364
|
+
type: "flip",
|
|
365
|
+
direction: "horizontal",
|
|
366
|
+
});
|
|
367
|
+
const transformedBlurred = yield* plugin.transform(transformedFlipped, {
|
|
368
|
+
type: "blur",
|
|
369
|
+
sigma: 3,
|
|
370
|
+
});
|
|
371
|
+
const transformedGrayscale = yield* plugin.transform(
|
|
372
|
+
transformedBlurred,
|
|
373
|
+
{
|
|
374
|
+
type: "grayscale",
|
|
375
|
+
},
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
expect(transformedGrayscale).toBeInstanceOf(Uint8Array);
|
|
379
|
+
const metadata = yield* Effect.promise(() =>
|
|
380
|
+
getImageMetadata(transformedGrayscale),
|
|
381
|
+
);
|
|
382
|
+
// Dimensions should remain same after 180° rotation
|
|
383
|
+
expect(metadata.width).toBe(200);
|
|
384
|
+
expect(metadata.height).toBe(100);
|
|
385
|
+
}).pipe(Effect.provide(imagePlugin)),
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
it.effect("should handle sepia transformation", () =>
|
|
389
|
+
Effect.gen(function* () {
|
|
390
|
+
const plugin = yield* ImagePlugin;
|
|
391
|
+
const inputImage = yield* Effect.promise(() =>
|
|
392
|
+
createTestImage(200, 100),
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
const transformed = yield* plugin.transform(inputImage, {
|
|
396
|
+
type: "sepia",
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
expect(transformed).toBeInstanceOf(Uint8Array);
|
|
400
|
+
expect(transformed.length).toBeGreaterThan(0);
|
|
401
|
+
}).pipe(Effect.provide(imagePlugin)),
|
|
402
|
+
);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
describe("error handling", () => {
|
|
406
|
+
it.effect("should fail with invalid image data", () =>
|
|
407
|
+
Effect.gen(function* () {
|
|
408
|
+
const plugin = yield* ImagePlugin;
|
|
409
|
+
const invalidData = new Uint8Array([1, 2, 3, 4, 5]);
|
|
410
|
+
|
|
411
|
+
const result = yield* Effect.either(
|
|
412
|
+
plugin.optimize(invalidData, {
|
|
413
|
+
quality: 80,
|
|
414
|
+
format: "jpeg",
|
|
415
|
+
}),
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
expect(result._tag).toBe("Left");
|
|
419
|
+
}).pipe(Effect.provide(imagePlugin)),
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
it.effect("should fail resize with invalid dimensions", () =>
|
|
423
|
+
Effect.gen(function* () {
|
|
424
|
+
const plugin = yield* ImagePlugin;
|
|
425
|
+
const inputImage = yield* Effect.promise(() =>
|
|
426
|
+
createTestImage(100, 100),
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
// Test with negative width
|
|
430
|
+
const result = yield* Effect.either(
|
|
431
|
+
plugin.resize(inputImage, {
|
|
432
|
+
width: -100,
|
|
433
|
+
fit: "cover",
|
|
434
|
+
}),
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
expect(result._tag).toBe("Left");
|
|
438
|
+
}).pipe(Effect.provide(imagePlugin)),
|
|
439
|
+
);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
describe("performance", () => {
|
|
443
|
+
it.effect("should process large images efficiently", () =>
|
|
444
|
+
Effect.gen(function* () {
|
|
445
|
+
const plugin = yield* ImagePlugin;
|
|
446
|
+
const largeImage = yield* Effect.promise(() =>
|
|
447
|
+
createTestImage(2000, 2000),
|
|
448
|
+
);
|
|
449
|
+
|
|
450
|
+
const startTime = Date.now();
|
|
451
|
+
|
|
452
|
+
const resized = yield* plugin.resize(largeImage, {
|
|
453
|
+
width: 500,
|
|
454
|
+
height: 500,
|
|
455
|
+
fit: "cover",
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
const duration = Date.now() - startTime;
|
|
459
|
+
|
|
460
|
+
expect(resized).toBeInstanceOf(Uint8Array);
|
|
461
|
+
// Should complete within reasonable time (5 seconds)
|
|
462
|
+
expect(duration).toBeLessThan(5000);
|
|
463
|
+
|
|
464
|
+
const metadata = yield* Effect.promise(() => getImageMetadata(resized));
|
|
465
|
+
expect(metadata.width).toBe(500);
|
|
466
|
+
expect(metadata.height).toBe(500);
|
|
467
|
+
}).pipe(Effect.provide(imagePlugin)),
|
|
468
|
+
);
|
|
469
|
+
});
|
|
470
|
+
});
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { defineConfig } from "vitest/config";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared vitest configuration template for uploadista-sdk packages
|
|
5
|
+
*
|
|
6
|
+
* This template should be used by all SDK packages to ensure consistent
|
|
7
|
+
* testing configuration across the monorepo.
|
|
8
|
+
*
|
|
9
|
+
* Key features:
|
|
10
|
+
* - Tests in dedicated `tests/` directories (not colocated with src)
|
|
11
|
+
* - Node environment for server-side code
|
|
12
|
+
* - V8 coverage provider
|
|
13
|
+
* - Global test functions available
|
|
14
|
+
* - Effect testing support via @effect/vitest
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* Copy this file to your package root as `vitest.config.ts` and customize
|
|
18
|
+
* if needed (though most packages should use this as-is).
|
|
19
|
+
*/
|
|
20
|
+
export default defineConfig({
|
|
21
|
+
test: {
|
|
22
|
+
globals: true,
|
|
23
|
+
environment: "node",
|
|
24
|
+
include: ["tests/**/*.test.ts"],
|
|
25
|
+
exclude: ["node_modules", "dist"],
|
|
26
|
+
coverage: {
|
|
27
|
+
provider: "v8",
|
|
28
|
+
reporter: ["text", "json", "html"],
|
|
29
|
+
exclude: [
|
|
30
|
+
"node_modules/",
|
|
31
|
+
"dist/",
|
|
32
|
+
"**/*.d.ts",
|
|
33
|
+
"**/*.test.ts",
|
|
34
|
+
"**/*.spec.ts",
|
|
35
|
+
"tests/",
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
});
|