@flowscripter/mpeg-sdl-parser
Version:
ISO/IEC 14496-34 Syntactic Description Language (MPEG SDL) parser implemented in TypeScript
129 lines (117 loc) • 3.93 kB
text/typescript
import { describe, expect, test } from "bun:test";
import type { Text } from "@codemirror/state";
import { SyntacticParseError } from "../src/ParseError.ts";
// Minimal mock for @codemirror/state Text
class MockText {
private _lines: string[];
constructor(text: string) {
this._lines = text.split("\n");
}
get lines(): number {
return this._lines.length;
}
lineAt(pos: number) {
let total = 0;
for (let i = 0; i < this._lines.length; i++) {
const lineLen = this._lines[i].length + 1; // +1 for '\n'
if (pos < total + lineLen) {
return {
number: i + 1,
from: total,
to: total + this._lines[i].length,
text: this._lines[i],
length: this._lines[i].length,
};
}
total += lineLen;
}
// fallback to last line
const last = this._lines.length - 1;
return {
number: this._lines.length,
from: total - (this._lines[last].length + 1),
to: total,
text: this._lines[last],
length: this._lines[last].length,
};
}
line(n: number) {
// Line numbers in Text interface are 1-indexed, array is 0-indexed
const index = n - 1;
if (index < 0 || index >= this._lines.length) {
throw new Error(`Line ${n} is out of range (1-${this._lines.length})`);
}
return {
number: n,
from: this.getLineStart(n),
to: this.getLineStart(n) + this._lines[index].length,
text: this._lines[index],
length: this._lines[index].length,
};
}
getLineStart(n: number) {
let pos = 0;
for (let i = 0; i < n - 1; i++) {
pos += this._lines[i].length + 1;
}
return pos;
}
}
describe("ParseError Tests", () => {
test("creates a SyntacticParseError with correct location and errorLine", () => {
const text = new MockText("foo\nbar\nbaz");
// Position 5 is in line 2 ("bar"), column 1
const err = SyntacticParseError.fromTextAndPosition(
text as unknown as Text,
4,
);
expect(err).toBeInstanceOf(SyntacticParseError);
expect(err.location).toEqual({ row: 2, column: 1, position: 4 });
expect(err.errorLine).toBe("bar");
expect(err.preceedingLines).toEqual(["foo"]);
expect(err.message).toContain("Parse error");
});
test("creates a SyntacticParseError handling position at start of text", () => {
const text = new MockText("abc\ndef");
const err = SyntacticParseError.fromTextAndPosition(
text as unknown as Text,
0,
);
expect(err.location).toEqual({ row: 1, column: 1, position: 0 });
expect(err.errorLine).toBe("abc");
expect(err.preceedingLines).toEqual([]);
});
test("creates a SyntacticParseError handling position at start of a later line", () => {
const text = new MockText("abc\ndef\nghi");
// Position 4 is start of line 2 ("def")
const err = SyntacticParseError.fromTextAndPosition(
text as unknown as Text,
4,
);
expect(err.location).toEqual({ row: 2, column: 1, position: 4 });
expect(err.errorLine).toBe("def");
expect(err.preceedingLines).toEqual(["abc"]);
});
test("creates a SyntacticParseError including up to two preceding lines", () => {
const text = new MockText("a\nb\nc\nd");
// Position 6 is in line 4 ("d")
const err = SyntacticParseError.fromTextAndPosition(
text as unknown as Text,
6,
);
expect(err.location?.row).toBe(4);
expect(err.errorLine).toBe("d");
expect(err.preceedingLines).toEqual(["b", "c"]);
});
test("creates a SyntacticParseError handling position at last line", () => {
const text = new MockText("x\ny\nz");
// Position 4 is in line 3 ("z")
const err = SyntacticParseError.fromTextAndPosition(
text as unknown as Text,
4,
);
expect(err.location?.row).toBe(3);
expect(err.errorLine).toBe("z");
expect(err.preceedingLines).toEqual(["x", "y"]);
});
});