| 1 | test("a trace without frames is only its message", () => { |
| 2 | const error = new RangeError("out of it"); |
| 3 | error.stack = String(error); |
| 4 | assert.deepEqual(stack.parse(error), []); |
| 5 | assert.equal(stack.format(error), "RangeError: out of it\n"); |
| 6 | }); |
| 7 | |
| 8 | test("an AggregateError of values that are not errors still formats", () => { |
| 9 | assert.doesNotThrow(() => stack.format(new AggregateError(["not an error"], "all failed"))); |
| 10 | }); |
| 11 | |
| 12 | test("a trace without colors has no escape codes", () => { |
| 13 | const error = new Error("x"); |
| 14 | error.stack = "Error: x\n at foo (native)"; |
| 15 | assert.doesNotMatch(stack.format(error, false), /\x1b/); |
| 16 | }); |
| 17 | |
| 18 | test("an error is labeled with its severity, unless its name says more", () => { |
| 19 | const label = (error: Error, severity?: stack.Severity) => stack.describe(error, { severity }).label; |
| 20 | assert.equal(label(new Error("x")), "error"); |
| 21 | assert.equal(label(new TypeError("x"), "warning"), "warning"); |
| 22 | assert.equal(label(new RangeError("x")), "RangeError"); |
| 23 | assert.equal(label(Object.assign(new Error("x"), { name: "Spelling" }), "warning"), "Spelling"); |
| 24 | }); |
| 25 | |
| 26 | test("the place an error says it is about is shown once, with the lines around it, before where it was thrown", () => { |
| 27 | const error = Object.assign(new Error("first test"), { file: "lib/log/stack.test.ts", line: 1, col: 1, code: "E1" }); |
| 28 | const diagnostic = stack.describe(error, { root: repo }); |
| 29 | assert.equal(diagnostic.location?.file, "lib/log/stack.test.ts"); |
| 30 | assert.deepEqual(diagnostic.location?.code, [ |
| 31 | `test("a trace without frames is only its message", () => {`, |
| 32 | ` const error = new RangeError("out of it");`, |
| 33 | ]); |
| 34 | assert.deepEqual(diagnostic.location?.span, [0, 4]); |
| 35 | assert.deepEqual(diagnostic.fields, [["code", `"E1"`]]); |
| 36 | assert.equal(diagnostic.notes[0]?.text, "thrown here"); |
| 37 | assert.equal(diagnostic.notes[0]?.frames[0]?.file, "lib/log/stack.test.ts"); |
| 38 | |
| 39 | const [header, location, code, field, note] = stack.format(error, false, { root: repo }).split("\n"); |
| 40 | assert.equal(header, "error: first test"); |
| 41 | assert.equal(location, "lib/log/stack.test.ts:1:1"); |
| 42 | assert.equal(code, `test("a trace without frames is only its message", () => {`); |
| 43 | assert.equal(field, ` code: "E1"`); |
| 44 | assert.equal(note, "note: thrown here"); |
| 45 | }); |
| 46 | |
| 47 | test("a thrown error is about its first frame outside of a runtime or package", () => { |
| 48 | const error = new Error("x"); |
| 49 | error.stack = [ |
| 50 | "Error: x", |
| 51 | " at open (node:fs:10:5)", |
| 52 | " at load (/app/node_modules/pkg/index.js:3:4)", |
| 53 | " at config (/app/src/config.ts:3:4)", |
| 54 | " at main (/app/src/main.ts:1:1)", |
| 55 | ].join("\n"); |
| 56 | const { location, notes } = stack.describe(error, { root: "/app" }); |
| 57 | assert.equal(location?.file, "src/config.ts"); |
| 58 | assert.equal(location?.fn, "config"); |
| 59 | assert.deepEqual(notes.map((note) => [note.text, note.frames.map((frame) => frame.fn)]), [ |
| 60 | ["thrown inside", ["open", "load"]], |
| 61 | ["called from", ["main"]], |
| 62 | ]); |
| 63 | }); |
| 64 | |
| 65 | test("notes point at their own places", () => { |
| 66 | const error = Object.assign(new Error("duplicate"), { |
| 67 | file: "lib/log/stack.test.ts", |
| 68 | line: 2, |
| 69 | col: 3, |
| 70 | notes: [{ text: "first defined here", file: "lib/log/stack.test.ts", line: 1, col: 1 }], |
| 71 | }); |
| 72 | const [note] = stack.describe(error, { root: repo }).notes; |
| 73 | assert.equal(note?.text, "first defined here"); |
| 74 | assert.equal(note?.location?.line, 1); |
| 75 | assert.deepEqual(note?.location?.code, [`test("a trace without frames is only its message", () => {`]); |
| 76 | }); |
| 77 | |
| 78 | test("a width marks that much of the line, where a column alone marks a word", () => { |
| 79 | const place = { file: "lib/log/stack.test.ts", line: 1, col: 6, width: 30 }; |
| 80 | const { location, notes } = stack.describe( |
| 81 | Object.assign(new Error("x"), { ...place, notes: [{ text: "and here", ...place, width: 2 }] }), |
| 82 | { root: repo }, |
| 83 | ); |
| 84 | assert.deepEqual(location?.span, [5, 35]); |
| 85 | assert.deepEqual(notes[0]?.location?.span, [5, 7]); |
| 86 | }); |
| 87 | |
| 88 | const repo = path.resolve(import.meta.dirname, "../.."); |
| 89 | |
| 90 | import assert from "node:assert/strict"; |
| 91 | import * as path from "node:path"; |
| 92 | import { test } from "vitest"; |
| 93 | import * as stack from "./stack.ts"; |