one
Version:
One is a new React Framework that makes Vite serve both native and web.
273 lines (220 loc) • 8.23 kB
text/typescript
import { resolve } from 'node:path'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
// Platform-correct test paths — resolve() normalizes to native separators
const PROJECT_ROOT = resolve('/project')
const PUBLIC_DIR = resolve('/project/public')
const COMPONENTS_DIR = resolve('/project/src/components')
const HERO_FILE = resolve('/project/src/components/Hero.tsx')
// Mock fs before importing the plugin
vi.mock('node:fs', async () => {
const actual = await vi.importActual('node:fs')
return {
...actual,
existsSync: vi.fn((path: string) => {
return path.includes('test-image.jpg') || path.includes('hero.png')
}),
}
})
function mockConfig() {
return {
publicDir: PUBLIC_DIR,
root: PROJECT_ROOT,
}
}
describe('imageDataPlugin', () => {
beforeEach(() => {
vi.resetModules()
})
afterEach(() => {
vi.clearAllMocks()
})
describe('resolveId', () => {
it('should return null for non-imagedata imports', async () => {
const { imageDataPlugin } = await import('./imageDataPlugin')
const plugin = imageDataPlugin()
if (plugin.configResolved) {
;(plugin.configResolved as any)(mockConfig())
}
const result = await (plugin.resolveId as any)('./image.jpg', undefined)
expect(result).toBeNull()
})
it('should return null for imports with imagedata not as suffix', async () => {
const { imageDataPlugin } = await import('./imageDataPlugin')
const plugin = imageDataPlugin()
if (plugin.configResolved) {
;(plugin.configResolved as any)(mockConfig())
}
// ?imagedata in the middle should not match
const result = await (plugin.resolveId as any)('./image?imagedata.jpg', undefined)
expect(result).toBeNull()
})
it('should resolve public dir paths starting with /', async () => {
const { imageDataPlugin } = await import('./imageDataPlugin')
const plugin = imageDataPlugin()
if (plugin.configResolved) {
;(plugin.configResolved as any)(mockConfig())
}
const result = await (plugin.resolveId as any)(
'/test-image.jpg?imagedata',
undefined
)
expect(result).toBe('\0imagedata:' + resolve(PUBLIC_DIR, 'test-image.jpg'))
})
it('should resolve relative imports', async () => {
const { imageDataPlugin } = await import('./imageDataPlugin')
const plugin = imageDataPlugin()
if (plugin.configResolved) {
;(plugin.configResolved as any)(mockConfig())
}
const result = await (plugin.resolveId as any)(
'./test-image.jpg?imagedata',
HERO_FILE
)
expect(result).toBe('\0imagedata:' + resolve(COMPONENTS_DIR, 'test-image.jpg'))
})
it('should return null for non-existent files', async () => {
const { imageDataPlugin } = await import('./imageDataPlugin')
const plugin = imageDataPlugin()
if (plugin.configResolved) {
;(plugin.configResolved as any)(mockConfig())
}
const result = await (plugin.resolveId as any)(
'/nonexistent.jpg?imagedata',
undefined
)
expect(result).toBeNull()
})
})
describe('path traversal prevention', () => {
it('should block path traversal from public dir', async () => {
const { imageDataPlugin } = await import('./imageDataPlugin')
const plugin = imageDataPlugin()
if (plugin.configResolved) {
;(plugin.configResolved as any)(mockConfig())
}
const result = await (plugin.resolveId as any)(
'/../../../etc/passwd?imagedata',
undefined
)
expect(result).toBeNull()
})
it('should block path traversal from relative imports', async () => {
const { imageDataPlugin } = await import('./imageDataPlugin')
const plugin = imageDataPlugin()
if (plugin.configResolved) {
;(plugin.configResolved as any)(mockConfig())
}
const result = await (plugin.resolveId as any)(
'../../../../etc/passwd?imagedata',
HERO_FILE
)
expect(result).toBeNull()
})
it('should block path traversal from root', async () => {
const { imageDataPlugin } = await import('./imageDataPlugin')
const plugin = imageDataPlugin()
if (plugin.configResolved) {
;(plugin.configResolved as any)(mockConfig())
}
const result = await (plugin.resolveId as any)(
'../../../etc/passwd?imagedata',
undefined
)
expect(result).toBeNull()
})
it('should allow paths within project boundaries', async () => {
const { imageDataPlugin } = await import('./imageDataPlugin')
const plugin = imageDataPlugin()
if (plugin.configResolved) {
;(plugin.configResolved as any)(mockConfig())
}
// Going up and back down should still work if within bounds
// From /project/src/components, ../.. goes to /project, then src/test-image.jpg
const result = await (plugin.resolveId as any)(
'../../src/test-image.jpg?imagedata',
HERO_FILE
)
expect(result).toBe('\0imagedata:' + resolve(PROJECT_ROOT, 'src/test-image.jpg'))
})
})
describe('load', () => {
it('should return null for non-virtual modules', async () => {
const { imageDataPlugin } = await import('./imageDataPlugin')
const plugin = imageDataPlugin()
if (plugin.configResolved) {
;(plugin.configResolved as any)(mockConfig())
}
const mockContext = {
addWatchFile: vi.fn(),
}
const result = await (plugin.load as any).call(mockContext, './image.jpg')
expect(result).toBeNull()
})
it('should return fallback data when sharp fails', async () => {
const { imageDataPlugin } = await import('./imageDataPlugin')
const plugin = imageDataPlugin()
if (plugin.configResolved) {
;(plugin.configResolved as any)(mockConfig())
}
const mockContext = {
addWatchFile: vi.fn(),
}
const testFilePath = resolve(PUBLIC_DIR, 'test-image.jpg')
// This will fail because the file doesn't actually exist
// But it should gracefully fallback
const result = await (plugin.load as any).call(
mockContext,
'\0imagedata:' + testFilePath
)
expect(result).toContain('export default')
expect(result).toContain('"src":"/test-image.jpg"')
expect(result).toContain('"width":')
expect(result).toContain('"height":')
expect(mockContext.addWatchFile).toHaveBeenCalledWith(testFilePath)
})
})
describe('plugin metadata', () => {
it('should have correct name', async () => {
const { imageDataPlugin } = await import('./imageDataPlugin')
const plugin = imageDataPlugin()
expect(plugin.name).toBe('one:imagedata')
})
it('should enforce pre', async () => {
const { imageDataPlugin } = await import('./imageDataPlugin')
const plugin = imageDataPlugin()
expect(plugin.enforce).toBe('pre')
})
})
})
describe('imageDataPlugin output format', () => {
it('should export a valid JSON object', async () => {
vi.resetModules()
const { imageDataPlugin } = await import('./imageDataPlugin')
const plugin = imageDataPlugin()
if (plugin.configResolved) {
;(plugin.configResolved as any)(mockConfig())
}
const mockContext = {
addWatchFile: vi.fn(),
}
const testFilePath = resolve(PUBLIC_DIR, 'test-image.jpg')
const result = await (plugin.load as any).call(
mockContext,
'\0imagedata:' + testFilePath
)
// Extract JSON from the export
const jsonMatch = result?.match(/export default (.+)$/)
expect(jsonMatch).toBeTruthy()
if (jsonMatch) {
const parsed = JSON.parse(jsonMatch[1])
expect(parsed).toHaveProperty('src')
expect(parsed).toHaveProperty('width')
expect(parsed).toHaveProperty('height')
expect(parsed).toHaveProperty('blurDataURL')
expect(typeof parsed.src).toBe('string')
expect(typeof parsed.width).toBe('number')
expect(typeof parsed.height).toBe('number')
expect(typeof parsed.blurDataURL).toBe('string')
}
})
})