mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-05 19:28:39 +00:00
* Improve macro argument parsing
* Make \above a primitive command
* Fix screenshotter data
* Normalize argument where necessary
* Improve argument location info
* Update comments
* Minor refactor
* Modularize group parsers
* Allow braced and blank size argument
for non-strict mode and \genfrac, respectively.
* Minor refactor & update comments
* Remove raw option in parseStringGroup
* Update tests
* Fix { delimited parameter
* Update tests
* Update tests
* Normalize argument in \genfrac
* Update tests
* Consume space before scanning an optional argument
* Fix \\, \newline, and \cr behavior
* Fix flow error
* Update comments
* Remove unnecessary mode switching
Parser mode affects neither fetch nor consume.
* Allow single (active) character macro
* Add function property `primitive`
* Set \mathchoice and \*style primitive
* Separate size-related improvements out to #2139
* Fix flow error
* Update screenshots
* Update demo example
* Add a migration guide
* Fix capitalization
* Make a primitive function unexpandable
* Update screenshots
* Update screenshots
* Revert "Document \def doesn't support delimiters (#2288) (#2289)"
This reverts commit f96fba6f7f
.
* Update comments, errors, and tests
* Update screenshots
326 lines
14 KiB
JavaScript
326 lines
14 KiB
JavaScript
describe("Parser:", function() {
|
|
|
|
describe("#handleInfixNodes", function() {
|
|
it("rejects repeated infix operators", function() {
|
|
expect`1\over 2\over 3`.toFailWithParseError(
|
|
"only one infix operator per group at position 9: " +
|
|
"1\\over 2\\̲o̲v̲e̲r̲ ̲3");
|
|
});
|
|
it("rejects conflicting infix operators", function() {
|
|
expect`1\over 2\choose 3`.toFailWithParseError(
|
|
"only one infix operator per group at position 9: " +
|
|
"1\\over 2\\̲c̲h̲o̲o̲s̲e̲ ̲3");
|
|
});
|
|
});
|
|
|
|
describe("#handleSupSubscript", function() {
|
|
it("rejects ^ at end of group", function() {
|
|
expect`{1^}`.toFailWithParseError(
|
|
"Expected group after '^' at position 3: {1^̲}");
|
|
});
|
|
it("rejects _ at end of input", function() {
|
|
expect`1_`.toFailWithParseError(
|
|
"Expected group after '_' at position 2: 1_̲");
|
|
});
|
|
it("rejects \\sqrt as argument to ^", function() {
|
|
expect`1^\sqrt{2}`.toFailWithParseError(
|
|
"Got function '\\sqrt' with no arguments as superscript" +
|
|
" at position 3: 1^\\̲s̲q̲r̲t̲{2}");
|
|
});
|
|
});
|
|
|
|
describe("#parseAtom", function() {
|
|
it("rejects \\limits without operator", function() {
|
|
expect`\alpha\limits\omega`.toFailWithParseError(
|
|
"Limit controls must follow a math operator" +
|
|
" at position 7: \\alpha\\̲l̲i̲m̲i̲t̲s̲\\omega");
|
|
});
|
|
it("rejects \\limits at the beginning of the input", function() {
|
|
expect`\limits\omega`.toFailWithParseError(
|
|
"Limit controls must follow a math operator" +
|
|
" at position 1: \\̲l̲i̲m̲i̲t̲s̲\\omega");
|
|
});
|
|
it("rejects double superscripts", function() {
|
|
expect`1^2^3`.toFailWithParseError(
|
|
"Double superscript at position 4: 1^2^̲3");
|
|
expect`1^{2+3}_4^5`.toFailWithParseError(
|
|
"Double superscript at position 10: 1^{2+3}_4^̲5");
|
|
});
|
|
it("rejects double superscripts involving primes", function() {
|
|
expect`1'_2^3`.toFailWithParseError(
|
|
"Double superscript at position 5: 1'_2^̲3");
|
|
expect`1^2'`.toFailWithParseError(
|
|
"Double superscript at position 4: 1^2'̲");
|
|
expect`1^2_3'`.toFailWithParseError(
|
|
"Double superscript at position 6: 1^2_3'̲");
|
|
expect`1'_2'`.toFailWithParseError(
|
|
"Double superscript at position 5: 1'_2'̲");
|
|
});
|
|
it("rejects double subscripts", function() {
|
|
expect`1_2_3`.toFailWithParseError(
|
|
"Double subscript at position 4: 1_2_̲3");
|
|
expect`1_{2+3}^4_5`.toFailWithParseError(
|
|
"Double subscript at position 10: 1_{2+3}^4_̲5");
|
|
});
|
|
});
|
|
|
|
describe("#parseImplicitGroup", function() {
|
|
it("reports unknown environments", function() {
|
|
expect`\begin{foo}bar\end{foo}`.toFailWithParseError(
|
|
"No such environment: foo at position 7:" +
|
|
" \\begin{̲f̲o̲o̲}̲bar\\end{foo}");
|
|
});
|
|
it("reports mismatched environments", function() {
|
|
expect`\begin{pmatrix}1&2\\3&4\end{bmatrix}+5`
|
|
.toFailWithParseError(
|
|
"Mismatch: \\begin{pmatrix} matched by \\end{bmatrix}" +
|
|
" at position 24: …matrix}1&2\\\\3&4\\̲e̲n̲d̲{bmatrix}+5");
|
|
});
|
|
});
|
|
|
|
describe("#parseFunction", function() {
|
|
it("rejects math-mode functions in text mode", function() {
|
|
expect`\text{\sqrt2 is irrational}`.toFailWithParseError(
|
|
"Can't use function '\\sqrt' in text mode" +
|
|
" at position 7: \\text{\\̲s̲q̲r̲t̲2 is irrational…");
|
|
});
|
|
it("rejects text-mode-only functions in math mode", function() {
|
|
expect`\'echec`.toFailWithParseError(
|
|
"Can't use function '\\'' in math mode" +
|
|
" at position 1: \\̲'̲echec");
|
|
});
|
|
});
|
|
|
|
describe("#parseArguments", function() {
|
|
it("complains about missing argument at end of input", function() {
|
|
expect`2\sqrt`.toFailWithParseError(
|
|
"Expected group as argument to '\\sqrt'" +
|
|
" at end of input: 2\\sqrt");
|
|
});
|
|
it("complains about missing argument at end of group", function() {
|
|
expect`1^{2\sqrt}`.toFailWithParseError(
|
|
"Expected group as argument to '\\sqrt'" +
|
|
" at position 10: 1^{2\\sqrt}̲");
|
|
});
|
|
it("complains about functions as arguments to others", function() {
|
|
expect`\sqrt\over2`.toFailWithParseError(
|
|
"Got function '\\over' with no arguments as argument to" +
|
|
" '\\sqrt' at position 6: \\sqrt\\̲o̲v̲e̲r̲2");
|
|
});
|
|
});
|
|
|
|
describe("#parseGroup", function() {
|
|
it("complains about undefined control sequence", function() {
|
|
expect`\xyz`.toFailWithParseError(
|
|
"Undefined control sequence: \\xyz" +
|
|
" at position 1: \\̲x̲y̲z̲");
|
|
});
|
|
});
|
|
|
|
describe("#verb", function() {
|
|
it("complains about mismatched \\verb with end of string", function() {
|
|
expect`\verb|hello`.toFailWithParseError(
|
|
"\\verb ended by end of line instead of matching delimiter");
|
|
});
|
|
it("complains about mismatched \\verb with end of line", function() {
|
|
expect("\\verb|hello\nworld|").toFailWithParseError(
|
|
"\\verb ended by end of line instead of matching delimiter");
|
|
});
|
|
});
|
|
|
|
});
|
|
|
|
describe("Parser.expect calls:", function() {
|
|
|
|
describe("#parseInput expecting EOF", function() {
|
|
it("complains about extra }", function() {
|
|
expect`{1+2}}`.toFailWithParseError(
|
|
"Expected 'EOF', got '}' at position 6: {1+2}}̲");
|
|
});
|
|
it("complains about extra \\end", function() {
|
|
expect`x\end{matrix}`.toFailWithParseError(
|
|
"Expected 'EOF', got '\\end' at position 2:" +
|
|
" x\\̲e̲n̲d̲{matrix}");
|
|
});
|
|
it("complains about top-level &", function() {
|
|
expect`1&2`.toFailWithParseError(
|
|
"Expected 'EOF', got '&' at position 2: 1&̲2");
|
|
});
|
|
});
|
|
|
|
describe("#parseImplicitGroup expecting \\right", function() {
|
|
it("rejects missing \\right", function() {
|
|
expect`\left(1+2)`.toFailWithParseError(
|
|
"Expected '\\right', got 'EOF' at end of input:" +
|
|
" \\left(1+2)");
|
|
});
|
|
it("rejects incorrectly scoped \\right", function() {
|
|
expect`{\left(1+2}\right)`.toFailWithParseError(
|
|
"Expected '\\right', got '}' at position 11:" +
|
|
" {\\left(1+2}̲\\right)");
|
|
});
|
|
});
|
|
|
|
// Can't test the expectation for \end after an environment
|
|
// since all existing arrays use parseArray which has its own expectation.
|
|
|
|
describe("#parseSpecialGroup expecting braces", function() {
|
|
it("complains about missing { for color", function() {
|
|
expect`\textcolor#ffffff{text}`.toFailWithParseError(
|
|
"Invalid color: '#' at position 11:" +
|
|
" \\textcolor#̲ffffff{text}");
|
|
});
|
|
it("complains about missing { for size", function() {
|
|
expect`\rule{1em}[2em]`.toFailWithParseError(
|
|
"Invalid size: '[' at position 11: \\rule{1em}[̲2em]");
|
|
});
|
|
// Can't test for the [ of an optional group since it's optional
|
|
it("complains about missing } for color", function() {
|
|
expect`\textcolor{#ffffff{text}`.toFailWithParseError(
|
|
"Unexpected end of input in a macro argument," +
|
|
" expected '}' at end of input: …r{#ffffff{text}");
|
|
});
|
|
it("complains about missing ] for size", function() {
|
|
expect`\rule[1em{2em}{3em}`.toFailWithParseError(
|
|
"Unexpected end of input in a macro argument," +
|
|
" expected ']' at end of input: …e[1em{2em}{3em}");
|
|
});
|
|
it("complains about missing ] for size at end of input", function() {
|
|
expect`\rule[1em`.toFailWithParseError(
|
|
"Unexpected end of input in a macro argument," +
|
|
" expected ']' at end of input: \\rule[1em");
|
|
});
|
|
it("complains about missing } for color at end of input", function() {
|
|
expect`\textcolor{#123456`.toFailWithParseError(
|
|
"Unexpected end of input in a macro argument," +
|
|
" expected '}' at end of input: …xtcolor{#123456");
|
|
});
|
|
});
|
|
|
|
describe("#parseGroup expecting }", function() {
|
|
it("at end of file", function() {
|
|
expect`\sqrt{2`.toFailWithParseError(
|
|
"Expected '}', got 'EOF' at end of input: \\sqrt{2");
|
|
});
|
|
});
|
|
|
|
describe("#parseOptionalGroup expecting ]", function() {
|
|
it("at end of file", function() {
|
|
expect`\sqrt[3`.toFailWithParseError(
|
|
"Unexpected end of input in a macro argument," +
|
|
" expected ']' at end of input: \\sqrt[3");
|
|
});
|
|
it("before group", function() {
|
|
expect`\sqrt[3{2}`.toFailWithParseError(
|
|
"Unexpected end of input in a macro argument," +
|
|
" expected ']' at end of input: \\sqrt[3{2}");
|
|
});
|
|
});
|
|
|
|
});
|
|
|
|
describe("environments.js:", function() {
|
|
|
|
describe("parseArray", function() {
|
|
it("rejects missing \\end", function() {
|
|
expect`\begin{matrix}1`.toFailWithParseError(
|
|
"Expected & or \\\\ or \\cr or \\end at end of input:" +
|
|
" \\begin{matrix}1");
|
|
});
|
|
it("rejects incorrectly scoped \\end", function() {
|
|
expect`{\begin{matrix}1}\end{matrix}`.toFailWithParseError(
|
|
"Expected & or \\\\ or \\cr or \\end at position 17:" +
|
|
" …\\begin{matrix}1}̲\\end{matrix}");
|
|
});
|
|
});
|
|
|
|
describe("array environment", function() {
|
|
it("rejects unknown column types", function() {
|
|
expect`\begin{array}{cba}\end{array}`.toFailWithParseError(
|
|
"Unknown column alignment: b at position 16:" +
|
|
" \\begin{array}{cb̲a}\\end{array}");
|
|
});
|
|
});
|
|
|
|
});
|
|
|
|
describe("functions.js:", function() {
|
|
|
|
describe("delimiter functions", function() {
|
|
it("reject invalid opening delimiters", function() {
|
|
expect`\bigl 1 + 2 \bigr`.toFailWithParseError(
|
|
"Invalid delimiter '1' after '\\bigl' at position 7:" +
|
|
" \\bigl 1̲ + 2 \\bigr");
|
|
});
|
|
it("reject invalid closing delimiters", function() {
|
|
expect`\bigl(1+2\bigr=3`.toFailWithParseError(
|
|
"Invalid delimiter '=' after '\\bigr' at position 15:" +
|
|
" \\bigl(1+2\\bigr=̲3");
|
|
});
|
|
it("reject group opening delimiters", function() {
|
|
expect`\bigl{(}1+2\bigr)3`.toFailWithParseError(
|
|
"Invalid delimiter type 'ordgroup' at position 6:" +
|
|
" \\bigl{̲(̲}̲1+2\\bigr)3");
|
|
});
|
|
it("reject group closing delimiters", function() {
|
|
expect`\bigl(1+2\bigr{)}3`.toFailWithParseError(
|
|
"Invalid delimiter type 'ordgroup' at position 15:" +
|
|
" \\bigl(1+2\\bigr{̲)̲}̲3");
|
|
});
|
|
});
|
|
|
|
describe("\\begin and \\end", function() {
|
|
it("reject invalid environment names", function() {
|
|
expect`\begin x\end y`.toFailWithParseError(
|
|
"No such environment: x at position 8: \\begin x̲\\end y");
|
|
});
|
|
});
|
|
|
|
});
|
|
|
|
describe("Lexer:", function() {
|
|
|
|
describe("#_innerLex", function() {
|
|
it("rejects lone surrogate char", function() {
|
|
expect("\udcba ").toFailWithParseError(
|
|
"Unexpected character: '\udcba' at position 1:" +
|
|
" \udcba\u0332 ");
|
|
});
|
|
it("rejects lone backslash at end of input", function() {
|
|
expect("\\").toFailWithParseError(
|
|
"Unexpected character: '\\' at position 1: \\̲");
|
|
});
|
|
});
|
|
|
|
describe("#_innerLexColor", function() {
|
|
it("reject 3-digit hex notation without #", function() {
|
|
expect`\textcolor{1a2}{foo}`.toFailWithParseError(
|
|
"Invalid color: '1a2'" +
|
|
" at position 11: \\textcolor{̲1̲a̲2̲}̲{foo}");
|
|
});
|
|
});
|
|
|
|
describe("#_innerLexSize", function() {
|
|
it("reject size without unit", function() {
|
|
expect`\rule{0}{2em}`.toFailWithParseError(
|
|
"Invalid size: '0' at position 6: \\rule{̲0̲}̲{2em}");
|
|
});
|
|
it("reject size with bogus unit", function() {
|
|
expect`\rule{1au}{2em}`.toFailWithParseError(
|
|
"Invalid unit: 'au' at position 6: \\rule{̲1̲a̲u̲}̲{2em}");
|
|
});
|
|
it("reject size without number", function() {
|
|
expect`\rule{em}{2em}`.toFailWithParseError(
|
|
"Invalid size: 'em' at position 6: \\rule{̲e̲m̲}̲{2em}");
|
|
});
|
|
});
|
|
|
|
});
|
|
|
|
describe("Unicode accents", function() {
|
|
it("should return error for invalid combining characters", function() {
|
|
expect("A\u0328").toFailWithParseError(
|
|
"Unknown accent ' ̨' at position 1: Ą̲̲");
|
|
});
|
|
});
|