diff --git a/packages/vitest/src/node/error.ts b/packages/vitest/src/node/error.ts index 0746af6f2b2f..50c5b3aa7912 100644 --- a/packages/vitest/src/node/error.ts +++ b/packages/vitest/src/node/error.ts @@ -56,19 +56,23 @@ export async function printError(error: unknown, ctx: Vitest, options: PrintErro if (type) printErrorType(type, ctx) - printErrorMessage(e, ctx.logger) - printStack(ctx, stacks, nearest, errorProperties, (s, pos) => { - if (showCodeFrame && s === nearest && nearest) { - const file = fileFromParsedStack(nearest) - // could point to non-existing original file - // for example, when there is a source map file, but no source in node_modules - if (existsSync(file)) { - const sourceCode = readFileSync(file, 'utf-8') - ctx.logger.log(c.yellow(generateCodeFrame(sourceCode, 4, pos))) + if (e.frame) { + ctx.logger.log(c.yellow(e.frame)) + } + else { + printStack(ctx, stacks, nearest, errorProperties, (s, pos) => { + if (showCodeFrame && s === nearest && nearest) { + const file = fileFromParsedStack(nearest) + // could point to non-existing original file + // for example, when there is a source map file, but no source in node_modules + if (existsSync(file)) { + const sourceCode = readFileSync(file, 'utf-8') + ctx.logger.log(c.yellow(generateCodeFrame(sourceCode, 4, pos))) + } } - } - }) + }) + } if (e.cause && 'name' in e.cause) { (e.cause as any).name = `Caused by: ${(e.cause as any).name}` diff --git a/packages/vitest/src/types/general.ts b/packages/vitest/src/types/general.ts index 54f036653da9..3ddfd5ea07b6 100644 --- a/packages/vitest/src/types/general.ts +++ b/packages/vitest/src/types/general.ts @@ -72,6 +72,7 @@ export interface ErrorWithDiff extends Error { expected?: any operator?: string type?: string + frame?: string } export interface ModuleGraphData { diff --git a/test/stacktraces/fixtures/frame.spec.imba b/test/stacktraces/fixtures/frame.spec.imba new file mode 100644 index 000000000000..b8bffe118079 --- /dev/null +++ b/test/stacktraces/fixtures/frame.spec.imba @@ -0,0 +1,4 @@ +describe "Test" do + test("1+1") do + expect(1+1).toBe 2 + ciyet. \ No newline at end of file diff --git a/test/stacktraces/fixtures/vite.config.ts b/test/stacktraces/fixtures/vite.config.ts index 766ce8757db1..06b1f1e82a9b 100644 --- a/test/stacktraces/fixtures/vite.config.ts +++ b/test/stacktraces/fixtures/vite.config.ts @@ -4,6 +4,22 @@ export default defineConfig({ plugins: [{ name: 'vite-plugin-imba', transform(code, id) { + if (id.endsWith('frame.spec.imba')) { + // eslint-disable-next-line no-throw-literal + throw { + name: 'imba-parser error', + id, + message: 'Unexpected \'CALL_END\'', + code, + frame: + '4 | test("1+1") do\n5 | expect(1+1).toBe 2\n6 | frame.\n | ^\n7 |\n', + loc: { + line: 3, + column: 11, + file: id, + }, + } + } if (id.endsWith('.imba')) { return { code: diff --git a/test/stacktraces/test/__snapshots__/runner.test.ts.snap b/test/stacktraces/test/__snapshots__/runner.test.ts.snap index 54b488ab5a80..c3eba873f8a9 100644 --- a/test/stacktraces/test/__snapshots__/runner.test.ts.snap +++ b/test/stacktraces/test/__snapshots__/runner.test.ts.snap @@ -1,5 +1,16 @@ // Vitest Snapshot v1 +exports[`stacktraces should pick error frame if present > frame.spec.imba > frame.spec.imba 1`] = ` +" ❯ frame.spec.imba (0 test) + +4 | test(\\"1+1\\") do +5 | expect(1+1).toBe 2 +6 | frame. + | ^ +7 | +" +`; + exports[`stacktraces should respect sourcemaps > add.test.ts > add.test.ts 1`] = ` " ❯ add.test.ts:12:23 10| diff --git a/test/stacktraces/test/runner.test.ts b/test/stacktraces/test/runner.test.ts index 748cf22cae5a..2e96047586e6 100644 --- a/test/stacktraces/test/runner.test.ts +++ b/test/stacktraces/test/runner.test.ts @@ -34,3 +34,35 @@ describe('stacktraces should respect sourcemaps', async () => { }, 30000) } }) + +describe('stacktraces should pick error frame if present', async () => { + const root = resolve(__dirname, '../fixtures') + const files = ['frame.spec.imba'] + + for (const file of files) { + it(file, async () => { + // in Windows child_process is very unstable, we skip testing it + if (process.platform === 'win32' && process.env.CI) + return + + let error: any + await execa('npx', ['vitest', 'run', file], { + cwd: root, + env: { + ...process.env, + CI: 'true', + NO_COLOR: 'true', + }, + }) + .catch((e) => { + error = e + }) + + expect(error).toBeTruthy() + const lines = String(error).split(/\n/g) + const index = lines.findIndex(val => val.includes('(0 test)')) + const msg = lines.slice(index, index + 8).join('\n') + expect(msg).toMatchSnapshot(file) + }, 30000) + } +})