import path from 'path'; import fs from 'fs'; import { test } from 'uvu'; import * as assert from 'uvu/assert'; import {webcrypto as crypto} from 'node:crypto'; let bundleAsync; if (process.env.TEST_WASM === 'node') { bundleAsync = (await import('../../wasm/wasm-node.mjs')).bundleAsync; } else if (process.env.TEST_WASM === 'browser') { // Define crypto globally for old node. // @ts-ignore globalThis.crypto ??= crypto; let wasm = await import('../../wasm/index.mjs'); await wasm.default(); bundleAsync = function (options) { if (!options.resolver?.read) { options.resolver = { ...options.resolver, read: (filePath) => fs.readFileSync(filePath, 'utf8') }; } return wasm.bundleAsync(options); } } else { bundleAsync = (await import('../index.mjs')).bundleAsync; } test('resolver', async () => { const inMemoryFs = new Map(Object.entries({ 'foo.css': ` @import 'root:bar.css'; .foo { color: red; } `.trim(), 'bar.css': ` @import 'root:hello/world.css'; .bar { color: green; } `.trim(), 'hello/world.css': ` .baz { color: blue; } `.trim(), })); const { code: buffer } = await bundleAsync({ filename: 'foo.css', resolver: { read(file) { const result = inMemoryFs.get(path.normalize(file)); if (!result) throw new Error(`Could not find ${file} in ${Array.from(inMemoryFs.keys()).join(', ')}.`); return result; }, resolve(specifier) { return specifier.slice('root:'.length); }, }, }); const code = buffer.toString('utf-8').trim(); const expected = ` .baz { color: #00f; } .bar { color: green; } .foo { color: red; } `.trim(); if (code !== expected) throw new Error(`\`testResolver()\` failed. Expected:\n${expected}\n\nGot:\n${code}`); }); test('only custom read', async () => { const inMemoryFs = new Map(Object.entries({ 'foo.css': ` @import 'hello/world.css'; .foo { color: red; } `.trim(), 'hello/world.css': ` @import '../bar.css'; .bar { color: green; } `.trim(), 'bar.css': ` .baz { color: blue; } `.trim(), })); const { code: buffer } = await bundleAsync({ filename: 'foo.css', resolver: { read(file) { const result = inMemoryFs.get(path.normalize(file)); if (!result) throw new Error(`Could not find ${file} in ${Array.from(inMemoryFs.keys()).join(', ')}.`); return result; }, }, }); const code = buffer.toString('utf-8').trim(); const expected = ` .baz { color: #00f; } .bar { color: green; } .foo { color: red; } `.trim(); if (code !== expected) throw new Error(`\`testOnlyCustomRead()\` failed. Expected:\n${expected}\n\nGot:\n${code}`); }); test('only custom resolve', async () => { const root = path.join('tests', 'testdata'); const { code: buffer } = await bundleAsync({ filename: path.join(root, 'foo.css'), resolver: { resolve(specifier) { // Strip `root:` prefix off specifier and resolve it as an absolute path // in the test data root. return path.join(root, specifier.slice('root:'.length)); }, }, }); const code = buffer.toString('utf-8').trim(); const expected = ` .baz { color: #00f; } .bar { color: green; } .foo { color: red; } `.trim(); if (code !== expected) throw new Error(`\`testOnlyCustomResolve()\` failed. Expected:\n${expected}\n\nGot:\n${code}`); }); test('async read', async () => { const root = path.join('tests', 'testdata'); const { code: buffer } = await bundleAsync({ filename: path.join(root, 'foo.css'), resolver: { async read(file) { return await fs.promises.readFile(file, 'utf8'); }, resolve(specifier) { // Strip `root:` prefix off specifier and resolve it as an absolute path // in the test data root. return path.join(root, specifier.slice('root:'.length)); }, }, }); const code = buffer.toString('utf-8').trim(); const expected = ` .baz { color: #00f; } .bar { color: green; } .foo { color: red; } `.trim(); if (code !== expected) throw new Error(`\`testAsyncRead()\` failed. Expected:\n${expected}\n\nGot:\n${code}`); }); test('read throw', async () => { let error = undefined; try { await bundleAsync({ filename: 'foo.css', resolver: { read(file) { throw new Error(`Oh noes! Failed to read \`${file}\`.`); } }, }); } catch (err) { error = err; } if (!error) throw new Error(`\`testReadThrow()\` failed. Expected \`bundleAsync()\` to throw, but it did not.`); assert.equal(error.message, `Oh noes! Failed to read \`foo.css\`.`); assert.equal(error.loc, undefined); // error occurred when reading initial file, no location info available. }); test('async read throw', async () => { let error = undefined; try { await bundleAsync({ filename: 'foo.css', resolver: { async read(file) { throw new Error(`Oh noes! Failed to read \`${file}\`.`); } }, }); } catch (err) { error = err; } if (!error) throw new Error(`\`testReadThrow()\` failed. Expected \`bundleAsync()\` to throw, but it did not.`); assert.equal(error.message, `Oh noes! Failed to read \`foo.css\`.`); assert.equal(error.loc, undefined); // error occurred when reading initial file, no location info available. }); test('read throw with location info', async () => { let error = undefined; try { await bundleAsync({ filename: 'foo.css', resolver: { read(file) { if (file === 'foo.css') { return '@import "bar.css"'; } throw new Error(`Oh noes! Failed to read \`${file}\`.`); } }, }); } catch (err) { error = err; } if (!error) throw new Error(`\`testReadThrow()\` failed. Expected \`bundleAsync()\` to throw, but it did not.`); assert.equal(error.message, `Oh noes! Failed to read \`bar.css\`.`); assert.equal(error.fileName, 'foo.css'); assert.equal(error.loc, { line: 1, column: 1 }); }); test('async read throw with location info', async () => { let error = undefined; try { await bundleAsync({ filename: 'foo.css', resolver: { async read(file) { if (file === 'foo.css') { return '@import "bar.css"'; } throw new Error(`Oh noes! Failed to read \`${file}\`.`); } }, }); } catch (err) { error = err; } if (!error) throw new Error(`\`testReadThrow()\` failed. Expected \`bundleAsync()\` to throw, but it did not.`); assert.equal(error.message, `Oh noes! Failed to read \`bar.css\`.`); assert.equal(error.fileName, 'foo.css'); assert.equal(error.loc, { line: 1, column: 1 }); }); test('resolve throw', async () => { let error = undefined; try { await bundleAsync({ filename: 'tests/testdata/foo.css', resolver: { resolve(specifier, originatingFile) { throw new Error(`Oh noes! Failed to resolve \`${specifier}\` from \`${originatingFile}\`.`); } }, }); } catch (err) { error = err; } if (!error) throw new Error(`\`testResolveThrow()\` failed. Expected \`bundleAsync()\` to throw, but it did not.`); assert.equal(error.message, `Oh noes! Failed to resolve \`root:hello/world.css\` from \`tests/testdata/foo.css\`.`); assert.equal(error.fileName, 'tests/testdata/foo.css'); assert.equal(error.loc, { line: 1, column: 1 }); }); test('async resolve throw', async () => { let error = undefined; try { await bundleAsync({ filename: 'tests/testdata/foo.css', resolver: { async resolve(specifier, originatingFile) { throw new Error(`Oh noes! Failed to resolve \`${specifier}\` from \`${originatingFile}\`.`); } }, }); } catch (err) { error = err; } if (!error) throw new Error(`\`testResolveThrow()\` failed. Expected \`bundleAsync()\` to throw, but it did not.`); assert.equal(error.message, `Oh noes! Failed to resolve \`root:hello/world.css\` from \`tests/testdata/foo.css\`.`); assert.equal(error.fileName, 'tests/testdata/foo.css'); assert.equal(error.loc, { line: 1, column: 1 }); }); test('read return non-string', async () => { let error = undefined; try { await bundleAsync({ filename: 'foo.css', resolver: { read() { return 1234; // Returns a non-string value. } }, }); } catch (err) { error = err; } if (!error) throw new Error(`\`testReadReturnNonString()\` failed. Expected \`bundleAsync()\` to throw, but it did not.`); assert.equal(error.message, 'expect String, got: Number'); }); test('resolve return non-string', async () => { let error = undefined; try { await bundleAsync({ filename: 'tests/testdata/foo.css', resolver: { resolve() { return 1234; // Returns a non-string value. } }, }); } catch (err) { error = err; } if (!error) throw new Error(`\`testResolveReturnNonString()\` failed. Expected \`bundleAsync()\` to throw, but it did not.`); assert.equal(error.message, 'expect String, got: Number'); assert.equal(error.fileName, 'tests/testdata/foo.css'); assert.equal(error.loc, { line: 1, column: 1 }); }); test('should throw with location info on syntax errors', async () => { let error = undefined; try { await bundleAsync({ filename: 'tests/testdata/foo.css', resolver: { read() { return '.foo' } }, }); } catch (err) { error = err; } assert.equal(error.message, `Unexpected end of input`); assert.equal(error.fileName, 'tests/testdata/foo.css'); assert.equal(error.loc, { line: 1, column: 5 }); }); test('should support throwing in visitors', async () => { let error = undefined; try { await bundleAsync({ filename: 'tests/testdata/a.css', visitor: { Rule() { throw new Error('Some error') } } }); } catch (err) { error = err; } assert.equal(error.message, 'Some error'); }); test.run();