import assert from 'node:assert/strict'; import test from 'node:test'; import { devErrorLocation, formatDevError } from '../src/devErrors.ts'; test('a start failure is mapped back to the plugin file line (evaluated with a sourceURL)', () => { // What the host evaluates: one wrapper line, then the file (dev_start_script, START_SCRIPT_LINE_OFFSET = 1). const file = 'My Plugin.plugin.js'; const source = 'module.exports = class {\n start() {\n null.boom;\n }\n};'; let caught: unknown; try { // eslint-disable-next-line no-eval const Plugin = (0, eval)(`(function(){const module={exports:{}};(function(module){\n${source}\n})(module);return module.exports;})\n//# sourceURL=wb-dev/My%20Plugin.plugin.js`)(); new Plugin().start(); } catch (error) { caught = error; } assert.ok(caught instanceof TypeError); const location = devErrorLocation(caught, file); assert.equal(location.line, 3, 'line 3 of the file, not of the wrapper'); assert.ok(location.column && location.column > 0); assert.match(formatDevError(location), /^My Plugin\.plugin\.js:3:\d+ — TypeError: /); }); test('errors without a frame in the file keep the file name and a one-line message', () => { const location = devErrorLocation('bad\nsecond line', 'a.plugin.js'); assert.deepEqual(location, { file: 'a.plugin.js', line: null, column: null, message: 'bad' }); assert.equal(formatDevError(location), 'a.plugin.js — bad'); assert.equal(devErrorLocation(new Error('x'.repeat(1000)), 'a.plugin.js').message.length, 300); });