@shopify/shop-minis-react
Version:
React component library for Shopify Shop Minis with Tailwind CSS v4 support (source-only, requires TypeScript)
315 lines (255 loc) • 8.56 kB
text/typescript
import {describe, expect, it, vi, beforeEach} from 'vitest'
import {resizeImage} from './resizeImage'
describe('resizeImage', () => {
let mockCanvas: any
let mockContext: any
let mockImage: any
beforeEach(() => {
// Mock canvas context
mockContext = {
drawImage: vi.fn(),
}
// Mock canvas
mockCanvas = {
width: 0,
height: 0,
getContext: vi.fn(() => mockContext),
toBlob: vi.fn(),
}
// Mock document.createElement
const originalCreateElement = document.createElement
document.createElement = vi.fn((tag: string) => {
if (tag === 'canvas') {
return mockCanvas as any
}
return originalCreateElement(tag)
})
// Mock URL methods
global.URL.createObjectURL = vi.fn(() => 'blob:mock-url')
global.URL.revokeObjectURL = vi.fn()
// Mock Image constructor
mockImage = {
width: 3000,
height: 4000,
onload: null as any,
onerror: null as any,
src: '',
}
global.Image = vi.fn(() => mockImage) as any
})
describe('Quality settings', () => {
it('returns original file for "original" quality', async () => {
const originalFile = new File(['test'], 'test.jpg', {type: 'image/jpeg'})
const result = await resizeImage({
file: originalFile,
quality: 'original',
})
expect(result).toBe(originalFile)
expect(global.Image).not.toHaveBeenCalled()
expect(document.createElement).not.toHaveBeenCalled()
})
it('resizes to 1080px for low quality', async () => {
const originalFile = new File(['test'], 'test.jpg', {type: 'image/jpeg'})
// Set up canvas toBlob to call callback
mockCanvas.toBlob.mockImplementation((callback: any) => {
const blob = new Blob(['resized'], {type: 'image/jpeg'})
callback(blob)
})
// Trigger image load after src is set
global.Image = vi.fn(() => {
const img = mockImage
setTimeout(() => {
if (img.onload) img.onload()
}, 0)
return img
}) as any
const resultPromise = resizeImage({
file: originalFile,
quality: 'low',
})
await new Promise(resolve => setTimeout(resolve, 10))
const result = await resultPromise
expect(result).toBeInstanceOf(File)
expect(result.type).toBe('image/jpeg')
expect(result.name).toBe('test.jpg')
// Check canvas dimensions were set correctly (maintaining aspect ratio)
expect(mockCanvas.width).toBe(810) // 1080 * (3000/4000)
expect(mockCanvas.height).toBe(1080)
})
it('resizes to 1600px for medium quality', async () => {
const originalFile = new File(['test'], 'test.jpg', {type: 'image/jpeg'})
mockCanvas.toBlob.mockImplementation((callback: any) => {
const blob = new Blob(['resized'], {type: 'image/jpeg'})
callback(blob)
})
global.Image = vi.fn(() => {
const img = mockImage
setTimeout(() => {
if (img.onload) img.onload()
}, 0)
return img
}) as any
const result = await resizeImage({
file: originalFile,
quality: 'medium',
})
expect(result).toBeInstanceOf(File)
expect(mockCanvas.width).toBe(1200) // 1600 * (3000/4000)
expect(mockCanvas.height).toBe(1600)
})
it('resizes to 2048px for high quality', async () => {
const originalFile = new File(['test'], 'test.jpg', {type: 'image/jpeg'})
mockCanvas.toBlob.mockImplementation((callback: any) => {
const blob = new Blob(['resized'], {type: 'image/jpeg'})
callback(blob)
})
global.Image = vi.fn(() => {
const img = mockImage
setTimeout(() => {
if (img.onload) img.onload()
}, 0)
return img
}) as any
const result = await resizeImage({
file: originalFile,
quality: 'high',
})
expect(result).toBeInstanceOf(File)
expect(mockCanvas.width).toBe(1536) // 2048 * (3000/4000)
expect(mockCanvas.height).toBe(2048)
})
})
describe('Custom quality', () => {
it('uses custom size when provided', async () => {
const originalFile = new File(['test'], 'test.jpg', {type: 'image/jpeg'})
mockCanvas.toBlob.mockImplementation((callback: any) => {
const blob = new Blob(['resized'], {type: 'image/jpeg'})
callback(blob)
})
global.Image = vi.fn(() => {
const img = mockImage
setTimeout(() => {
if (img.onload) img.onload()
}, 0)
return img
}) as any
const result = await resizeImage({
file: originalFile,
quality: 'low',
customQuality: {size: 500, compression: 0.6},
})
expect(result).toBeInstanceOf(File)
expect(mockCanvas.width).toBe(375) // 500 * (3000/4000)
expect(mockCanvas.height).toBe(500)
})
})
describe('Aspect ratio handling', () => {
it('maintains aspect ratio for landscape images', async () => {
const originalFile = new File(['test'], 'test.jpg', {type: 'image/jpeg'})
// Set landscape dimensions
mockImage.width = 4000
mockImage.height = 3000
mockCanvas.toBlob.mockImplementation((callback: any) => {
const blob = new Blob(['resized'], {type: 'image/jpeg'})
callback(blob)
})
global.Image = vi.fn(() => {
const img = mockImage
setTimeout(() => {
if (img.onload) img.onload()
}, 0)
return img
}) as any
await resizeImage({
file: originalFile,
quality: 'low',
})
expect(mockCanvas.width).toBe(1080)
expect(mockCanvas.height).toBe(810) // 1080 * (3000/4000)
})
it('does not resize if image is smaller than target size', async () => {
const originalFile = new File(['test'], 'test.jpg', {type: 'image/jpeg'})
// Set small dimensions
mockImage.width = 800
mockImage.height = 600
mockCanvas.toBlob.mockImplementation((callback: any) => {
const blob = new Blob(['resized'], {type: 'image/jpeg'})
callback(blob)
})
global.Image = vi.fn(() => {
const img = mockImage
setTimeout(() => {
if (img.onload) img.onload()
}, 0)
return img
}) as any
await resizeImage({
file: originalFile,
quality: 'high',
})
// Should maintain original dimensions
expect(mockCanvas.width).toBe(800)
expect(mockCanvas.height).toBe(600)
})
})
describe('Error handling', () => {
it('rejects when image fails to load', async () => {
const originalFile = new File(['test'], 'test.jpg', {type: 'image/jpeg'})
global.Image = vi.fn(() => {
const img = mockImage
setTimeout(() => {
if (img.onerror) img.onerror()
}, 0)
return img
}) as any
await expect(
resizeImage({
file: originalFile,
quality: 'medium',
})
).rejects.toThrow('Failed to load image')
expect(URL.revokeObjectURL).toHaveBeenCalledWith('blob:mock-url')
})
})
describe('Resource cleanup', () => {
it('revokes object URL after successful resize', async () => {
const originalFile = new File(['test'], 'test.jpg', {type: 'image/jpeg'})
mockCanvas.toBlob.mockImplementation((callback: any) => {
const blob = new Blob(['resized'], {type: 'image/jpeg'})
callback(blob)
})
global.Image = vi.fn(() => {
const img = mockImage
setTimeout(() => {
if (img.onload) img.onload()
}, 0)
return img
}) as any
await resizeImage({
file: originalFile,
quality: 'medium',
})
expect(URL.createObjectURL).toHaveBeenCalledWith(originalFile)
expect(URL.revokeObjectURL).toHaveBeenCalledWith('blob:mock-url')
})
it('revokes object URL even when error occurs', async () => {
const originalFile = new File(['test'], 'test.jpg', {type: 'image/jpeg'})
global.Image = vi.fn(() => {
const img = mockImage
setTimeout(() => {
if (img.onerror) img.onerror()
}, 0)
return img
}) as any
try {
await resizeImage({
file: originalFile,
quality: 'medium',
})
} catch {
// Expected to throw
}
expect(URL.revokeObjectURL).toHaveBeenCalledWith('blob:mock-url')
})
})
})