import { describe, it, expect } from 'vitest'; import { buildInlinedImportmap, extractSpecifiers, rewriteModuleSpecifiers, } from '@/lib/export/inline-assets-importmap'; const enc = (s: string) => ({ bytes: new TextEncoder().encode(s), contentType: 'text/javascript' }); describe('buildInlinedImportmap', () => { it('ignores import-like text in comments and strings', async () => { const code = ` const example = "import 'string-only'"; // import 'comment-only'; /* export { value } from 'block-comment-only'; */ import 'real-module'; `; expect(extractSpecifiers(code)).toEqual(['real-module']); expect( await rewriteModuleSpecifiers(code, (specifier) => specifier === 'real-module' ? 'packaged-module' : 'unexpected-rewrite', ), ).toContain("import 'packaged-module'"); expect(await rewriteModuleSpecifiers(code, () => undefined)).toBe(code); const escaped = String.raw`import './\u0061.js';`; expect(await rewriteModuleSpecifiers(escaped, () => undefined)).toBe(escaped); expect(await rewriteModuleSpecifiers("import('real-module')", () => 'packaged-module')).toBe( "import('packaged-module')", ); }); it('inlines a direct module entry to a data: URI', async () => { const fetchAsset = async (url: string) => url === 'https://unpkg.com/three@0.160.0/build/three.module.js' ? enc('export const THREE=1') : null; const { imports, report } = await buildInlinedImportmap( { three: 'https://unpkg.com/three@0.160.0/build/three.module.js' }, ["import * as THREE from 'three';"], fetchAsset, ); expect(imports.three).toMatch(/^data:text\/javascript;base64,/); expect(report.inlined).toContain('https://unpkg.com/three@0.160.0/build/three.module.js'); }); it('expands a prefix entry (three/addons/) into explicit full-specifier data: entries and drops the prefix', async () => { const base = 'https://unpkg.com/three@0.160.0/examples/jsm/'; const fetchAsset = async (url: string) => { if (url === 'https://unpkg.com/three@0.160.0/build/three.module.js') return enc('export const THREE=1'); if (url === base + 'controls/OrbitControls.js') return enc("import * as THREE from 'three'; export class OrbitControls{}"); return null; }; const moduleBodies = [ "import * as THREE from 'three'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js';", ]; const { imports } = await buildInlinedImportmap( { three: 'https://unpkg.com/three@0.160.0/build/three.module.js', 'three/addons/': base }, moduleBodies, fetchAsset, ); expect(imports['three']).toMatch(/^data:/); expect(imports['three/addons/controls/OrbitControls.js']).toMatch(/^data:/); expect(imports['three/addons/']).toBeUndefined(); }); it('recursively resolves nested addon imports', async () => { const base = 'https://unpkg.com/three@0.160.0/examples/jsm/'; const fetchAsset = async (url: string) => { if (url === 'https://x/three.js') return enc('export const THREE=1'); if (url === base + 'a.js') return enc("import 'three/addons/b.js'; export const a=1;"); if (url === base + 'b.js') return enc("import * as THREE from 'three'; export const b=2;"); return null; }; const { imports } = await buildInlinedImportmap( { three: 'https://x/three.js', 'three/addons/': base }, ["import 'three/addons/a.js';"], fetchAsset, ); expect(imports['three/addons/a.js']).toMatch(/^data:/); expect(imports['three/addons/b.js']).toMatch(/^data:/); }); it('records failures for unfetchable modules and leaves their specifier unmapped', async () => { const { imports, report } = await buildInlinedImportmap( { three: 'https://dead/three.js' }, ["import 'three';"], async () => null, ); expect(imports.three).toBeUndefined(); expect(report.failed.map((f) => f.url)).toContain('https://dead/three.js'); }); it('ignores relative/bare specifiers not present in the importmap', async () => { const { imports } = await buildInlinedImportmap( { three: 'https://x/three.js' }, ["import './local.js'; import 'react';"], async (url: string) => (url === 'https://x/three.js' ? enc('export const T=1') : null), ); // only 'three' was imported among mapped specifiers? Actually 'three' is NOT imported here. // local.js and react are not in the importmap → no entries produced. expect(Object.keys(imports)).toEqual([]); }); it('handles dynamic import() specifiers', async () => { const fetchAsset = async (url: string) => url === 'https://x/three.js' ? enc('export const T=1') : null; const { imports } = await buildInlinedImportmap( { three: 'https://x/three.js' }, ["const m = await import('three');"], fetchAsset, ); expect(imports.three).toMatch(/^data:/); }); it('rejects cyclic external modules as an explicit unresolved resource', async () => { const modules: Record = { 'https://cdn.example/a.js': "import './b.js'; export const a = 1;", 'https://cdn.example/b.js': "import './a.js'; export const b = 1;", }; const { report } = await buildInlinedImportmap( { entry: 'https://cdn.example/a.js' }, ["import 'entry';"], async (url) => (modules[url] ? enc(modules[url]) : null), ); expect(report.failed).toContainEqual({ url: 'https://cdn.example/a.js', reason: 'cyclic module dependency', }); }); });