mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-11 22:18:41 +00:00
\newcommand, \renewcommand, \providecommand (#1382)
* \newcommand, \renewcommand, \providecommand * Tests * Add comment * Add symbols to the set of already defined things * Add implicitCommands, catch \hline outside array * Add \relax * Move isDefined to be a method of MacroExpander * Namespace.has * Reword error messages * Add \hdashline given #1407
This commit is contained in:
@@ -5,15 +5,10 @@ import ParseError from "../src/ParseError";
|
||||
import parseTree from "../src/parseTree";
|
||||
import Settings from "../src/Settings";
|
||||
|
||||
export const defaultSettings = new Settings({
|
||||
strict: false, // deal with warnings only when desired
|
||||
});
|
||||
export const nonstrictSettings = new Settings({strict: false});
|
||||
export const strictSettings = new Settings({strict: true});
|
||||
|
||||
export const _getBuilt = function(expr, settings = defaultSettings) {
|
||||
if (settings === defaultSettings) {
|
||||
settings.macros = {};
|
||||
}
|
||||
export const _getBuilt = function(expr, settings = new Settings()) {
|
||||
let rootNode = katex.__renderToDomTree(expr, settings);
|
||||
|
||||
if (rootNode.classes.indexOf('katex-error') >= 0) {
|
||||
@@ -43,7 +38,7 @@ export const _getBuilt = function(expr, settings = defaultSettings) {
|
||||
* @param settings
|
||||
* @returns {Object}
|
||||
*/
|
||||
export const getBuilt = function(expr, settings = defaultSettings) {
|
||||
export const getBuilt = function(expr, settings = new Settings()) {
|
||||
expect(expr).toBuild(settings);
|
||||
return _getBuilt(expr, settings);
|
||||
};
|
||||
@@ -54,7 +49,7 @@ export const getBuilt = function(expr, settings = defaultSettings) {
|
||||
* @param settings
|
||||
* @returns {Object}
|
||||
*/
|
||||
export const getParsed = function(expr, settings = defaultSettings) {
|
||||
export const getParsed = function(expr, settings = new Settings()) {
|
||||
expect(expr).toParse(settings);
|
||||
return parseTree(expr, settings);
|
||||
};
|
||||
@@ -73,7 +68,7 @@ export const stripPositions = function(expr) {
|
||||
};
|
||||
|
||||
export const parseAndSetResult = function(expr, result,
|
||||
settings = defaultSettings) {
|
||||
settings = new Settings()) {
|
||||
try {
|
||||
return parseTree(expr, settings);
|
||||
} catch (e) {
|
||||
@@ -89,7 +84,7 @@ export const parseAndSetResult = function(expr, result,
|
||||
};
|
||||
|
||||
export const buildAndSetResult = function(expr, result,
|
||||
settings = defaultSettings) {
|
||||
settings = new Settings()) {
|
||||
try {
|
||||
return _getBuilt(expr, settings);
|
||||
} catch (e) {
|
||||
|
@@ -11,7 +11,7 @@ import Options from "../src/Options";
|
||||
import Settings from "../src/Settings";
|
||||
import Style from "../src/Style";
|
||||
import {
|
||||
defaultSettings, strictSettings,
|
||||
strictSettings, nonstrictSettings,
|
||||
_getBuilt, getBuilt, getParsed, stripPositions,
|
||||
} from "./helpers";
|
||||
|
||||
@@ -1140,6 +1140,10 @@ describe("A begin/end parser", function() {
|
||||
expect("\\begin{matrix}\\hdashline a&b\\\\ \\hdashline c&d\\end{matrix}").toParse();
|
||||
});
|
||||
|
||||
it("should forbid hlines outside array environment", () => {
|
||||
expect("\\hline").toNotParse();
|
||||
});
|
||||
|
||||
it("should error when name is mismatched", function() {
|
||||
expect("\\begin{matrix}a&b\\\\c&d\\end{pmatrix}").toNotParse();
|
||||
});
|
||||
@@ -2282,7 +2286,7 @@ describe("A smash builder", function() {
|
||||
describe("A parser error", function() {
|
||||
it("should report the position of an error", function() {
|
||||
try {
|
||||
parseTree("\\sqrt}", defaultSettings);
|
||||
parseTree("\\sqrt}", new Settings());
|
||||
} catch (e) {
|
||||
expect(e.position).toEqual(5);
|
||||
}
|
||||
@@ -2490,7 +2494,7 @@ describe("A macro expander", function() {
|
||||
const compareParseTree = function(actual, expected, macros) {
|
||||
const settings = new Settings({macros: macros});
|
||||
actual = stripPositions(parseTree(actual, settings));
|
||||
expected = stripPositions(parseTree(expected, defaultSettings));
|
||||
expected = stripPositions(parseTree(expected, new Settings()));
|
||||
expect(actual).toEqual(expected);
|
||||
};
|
||||
|
||||
@@ -2775,6 +2779,57 @@ describe("A macro expander", function() {
|
||||
expect(macros["\\foo"]).toBeFalsy();
|
||||
});
|
||||
|
||||
it("\\newcommand defines new macros", () => {
|
||||
compareParseTree("\\newcommand\\foo{x^2}\\foo+\\foo", "x^2+x^2");
|
||||
compareParseTree("\\newcommand{\\foo}{x^2}\\foo+\\foo", "x^2+x^2");
|
||||
// Function detection
|
||||
expect("\\newcommand\\bar{x^2}\\bar+\\bar").toNotParse();
|
||||
expect("\\newcommand{\\bar}{x^2}\\bar+\\bar").toNotParse();
|
||||
// Symbol detection
|
||||
expect("\\newcommand\\lambda{x^2}\\lambda").toNotParse();
|
||||
expect("\\newcommand\\textdollar{x^2}\\textdollar").toNotParse();
|
||||
// Macro detection
|
||||
expect("\\newcommand{\\foo}{1}\\foo\\newcommand{\\foo}{2}\\foo")
|
||||
.toNotParse();
|
||||
// Implicit detection
|
||||
expect("\\newcommand\\limits{}").toNotParse();
|
||||
});
|
||||
|
||||
it("\\renewcommand redefines macros", () => {
|
||||
expect("\\renewcommand\\foo{x^2}\\foo+\\foo").toNotParse();
|
||||
expect("\\renewcommand{\\foo}{x^2}\\foo+\\foo").toNotParse();
|
||||
compareParseTree("\\renewcommand\\bar{x^2}\\bar+\\bar", "x^2+x^2");
|
||||
compareParseTree("\\renewcommand{\\bar}{x^2}\\bar+\\bar", "x^2+x^2");
|
||||
expect("\\newcommand{\\foo}{1}\\foo\\renewcommand{\\foo}{2}\\foo")
|
||||
.toParseLike("12");
|
||||
});
|
||||
|
||||
it("\\providecommand (re)defines macros", () => {
|
||||
compareParseTree("\\providecommand\\foo{x^2}\\foo+\\foo", "x^2+x^2");
|
||||
compareParseTree("\\providecommand{\\foo}{x^2}\\foo+\\foo", "x^2+x^2");
|
||||
compareParseTree("\\providecommand\\bar{x^2}\\bar+\\bar", "x^2+x^2");
|
||||
compareParseTree("\\providecommand{\\bar}{x^2}\\bar+\\bar", "x^2+x^2");
|
||||
expect("\\newcommand{\\foo}{1}\\foo\\providecommand{\\foo}{2}\\foo")
|
||||
.toParseLike("12");
|
||||
expect("\\providecommand{\\foo}{1}\\foo\\renewcommand{\\foo}{2}\\foo")
|
||||
.toParseLike("12");
|
||||
expect("\\providecommand{\\foo}{1}\\foo\\providecommand{\\foo}{2}\\foo")
|
||||
.toParseLike("12");
|
||||
});
|
||||
|
||||
it("\\newcommand is local", () => {
|
||||
expect("\\newcommand\\foo{1}\\foo{\\renewcommand\\foo{2}\\foo}\\foo")
|
||||
.toParseLike("1{2}1");
|
||||
});
|
||||
|
||||
it("\\newcommand accepts number of arguments", () => {
|
||||
compareParseTree("\\newcommand\\foo[1]{#1^2}\\foo x+\\foo{y}",
|
||||
"x^2+y^2");
|
||||
compareParseTree("\\newcommand\\foo[10]{#1^2}\\foo 0123456789", "0^2");
|
||||
expect("\\newcommand\\foo[x]{}").toNotParse();
|
||||
expect("\\newcommand\\foo[1.5]{}").toNotParse();
|
||||
});
|
||||
|
||||
// This may change in the future, if we support the extra features of
|
||||
// \hspace.
|
||||
it("should treat \\hspace, \\hskip like \\kern", function() {
|
||||
@@ -2847,7 +2902,7 @@ describe("Unicode accents", function() {
|
||||
"\\tilde n" +
|
||||
"\\grave o\\acute o\\hat o\\tilde o\\ddot o" +
|
||||
"\\grave u\\acute u\\hat u\\ddot u" +
|
||||
"\\acute y\\ddot y");
|
||||
"\\acute y\\ddot y", nonstrictSettings);
|
||||
});
|
||||
|
||||
it("should parse Latin-1 letters in text mode", function() {
|
||||
@@ -2877,19 +2932,19 @@ describe("Unicode accents", function() {
|
||||
});
|
||||
|
||||
it("should parse combining characters", function() {
|
||||
expect("A\u0301C\u0301").toParseLike("Á\\acute C");
|
||||
expect("A\u0301C\u0301").toParseLike("Á\\acute C", nonstrictSettings);
|
||||
expect("\\text{A\u0301C\u0301}").toParseLike("\\text{Á\\'C}", strictSettings);
|
||||
});
|
||||
|
||||
it("should parse multi-accented characters", function() {
|
||||
expect("ấā́ắ\\text{ấā́ắ}").toParse();
|
||||
expect("ấā́ắ\\text{ấā́ắ}").toParse(nonstrictSettings);
|
||||
// Doesn't parse quite the same as
|
||||
// "\\text{\\'{\\^a}\\'{\\=a}\\'{\\u a}}" because of the ordgroups.
|
||||
});
|
||||
|
||||
it("should parse accented i's and j's", function() {
|
||||
expect("íȷ́").toParseLike("\\acute ı\\acute ȷ");
|
||||
expect("ấā́ắ\\text{ấā́ắ}").toParse();
|
||||
expect("íȷ́").toParseLike("\\acute ı\\acute ȷ", nonstrictSettings);
|
||||
expect("ấā́ắ\\text{ấā́ắ}").toParse(nonstrictSettings);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3076,8 +3131,8 @@ describe("Symbols", function() {
|
||||
|
||||
describe("strict setting", function() {
|
||||
it("should allow unicode text when not strict", () => {
|
||||
expect("é").toParse(new Settings({strict: false}));
|
||||
expect("試").toParse(new Settings({strict: false}));
|
||||
expect("é").toParse(new Settings(nonstrictSettings));
|
||||
expect("試").toParse(new Settings(nonstrictSettings));
|
||||
expect("é").toParse(new Settings({strict: "ignore"}));
|
||||
expect("試").toParse(new Settings({strict: "ignore"}));
|
||||
expect("é").toParse(new Settings({strict: () => false}));
|
||||
@@ -3103,7 +3158,7 @@ describe("strict setting", function() {
|
||||
});
|
||||
|
||||
it("should always allow unicode text in text mode", () => {
|
||||
expect("\\text{é試}").toParse(new Settings({strict: false}));
|
||||
expect("\\text{é試}").toParse(nonstrictSettings);
|
||||
expect("\\text{é試}").toParse(strictSettings);
|
||||
expect("\\text{é試}").toParse();
|
||||
});
|
||||
|
@@ -8,13 +8,9 @@ import Options from "../src/Options";
|
||||
import Settings from "../src/Settings";
|
||||
import Style from "../src/Style";
|
||||
|
||||
const defaultSettings = new Settings({});
|
||||
|
||||
const getMathML = function(expr, settings) {
|
||||
const usedSettings = settings ? settings : defaultSettings;
|
||||
|
||||
const getMathML = function(expr, settings = new Settings()) {
|
||||
let startStyle = Style.TEXT;
|
||||
if (usedSettings.displayMode) {
|
||||
if (settings.displayMode) {
|
||||
startStyle = Style.DISPLAY;
|
||||
}
|
||||
|
||||
@@ -24,7 +20,7 @@ const getMathML = function(expr, settings) {
|
||||
maxSize: Infinity,
|
||||
});
|
||||
|
||||
const built = buildMathML(parseTree(expr, usedSettings), expr, options);
|
||||
const built = buildMathML(parseTree(expr, settings), expr, options);
|
||||
|
||||
// Strip off the surrounding <span>
|
||||
return built.children[0].toMarkup();
|
||||
|
@@ -4,10 +4,10 @@
|
||||
import katex from "../katex";
|
||||
import ParseError from "../src/ParseError";
|
||||
import parseTree from "../src/parseTree";
|
||||
import Settings from "../src/Settings";
|
||||
import Warning from "./Warning";
|
||||
import stringify from 'json-stable-stringify';
|
||||
import {
|
||||
defaultSettings,
|
||||
_getBuilt, buildAndSetResult, parseAndSetResult, stripPositions,
|
||||
} from "./helpers";
|
||||
|
||||
@@ -44,7 +44,7 @@ global.console.warn = jest.fn((warning) => {
|
||||
// Expect extensions
|
||||
|
||||
expect.extend({
|
||||
toParse: function(actual, settings = defaultSettings) {
|
||||
toParse: function(actual, settings = new Settings()) {
|
||||
const result = {
|
||||
pass: true,
|
||||
message: () => `'${actual}' succeeded parsing`,
|
||||
@@ -53,7 +53,7 @@ expect.extend({
|
||||
return result;
|
||||
},
|
||||
|
||||
toNotParse: function(actual, settings = defaultSettings) {
|
||||
toNotParse: function(actual, settings = new Settings()) {
|
||||
const result = {
|
||||
pass: false,
|
||||
message: () =>
|
||||
@@ -79,7 +79,7 @@ expect.extend({
|
||||
toFailWithParseError: function(actual, expected) {
|
||||
const prefix = "KaTeX parse error: ";
|
||||
try {
|
||||
parseTree(actual, defaultSettings);
|
||||
parseTree(actual, new Settings());
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `'${actual}' parsed without error`,
|
||||
@@ -115,7 +115,7 @@ expect.extend({
|
||||
}
|
||||
},
|
||||
|
||||
toBuild: function(actual, settings = defaultSettings) {
|
||||
toBuild: function(actual, settings = new Settings()) {
|
||||
const result = {
|
||||
pass: true,
|
||||
message: () => `'${actual}' succeeded in building`,
|
||||
@@ -124,7 +124,7 @@ expect.extend({
|
||||
return result;
|
||||
},
|
||||
|
||||
toNotBuild: function(actual, settings = defaultSettings) {
|
||||
toNotBuild: function(actual, settings = new Settings()) {
|
||||
const result = {
|
||||
pass: false,
|
||||
message: () =>
|
||||
@@ -147,7 +147,7 @@ expect.extend({
|
||||
return result;
|
||||
},
|
||||
|
||||
toParseLike: function(actual, expected, settings = defaultSettings) {
|
||||
toParseLike: function(actual, expected, settings = new Settings()) {
|
||||
const result = {
|
||||
pass: true,
|
||||
message: () =>
|
||||
@@ -174,7 +174,7 @@ expect.extend({
|
||||
return result;
|
||||
},
|
||||
|
||||
toBuildLike: function(actual, expected, settings = defaultSettings) {
|
||||
toBuildLike: function(actual, expected, settings = new Settings()) {
|
||||
const result = {
|
||||
pass: true,
|
||||
message: () =>
|
||||
@@ -201,7 +201,7 @@ expect.extend({
|
||||
return result;
|
||||
},
|
||||
|
||||
toWarn: function(actual, settings = defaultSettings) {
|
||||
toWarn: function(actual, settings = new Settings()) {
|
||||
const result = {
|
||||
pass: false,
|
||||
message: () =>
|
||||
|
@@ -4,7 +4,7 @@
|
||||
/* global describe: false */
|
||||
import Settings from "../src/Settings";
|
||||
import {scriptFromCodepoint, supportedCodepoint} from "../src/unicodeScripts";
|
||||
import {strictSettings} from "./helpers";
|
||||
import {strictSettings, nonstrictSettings} from "./helpers";
|
||||
|
||||
describe("unicode", function() {
|
||||
it("should parse Latin-1 inside \\text{}", function() {
|
||||
@@ -21,7 +21,7 @@ describe("unicode", function() {
|
||||
|
||||
it("should parse Latin-1 outside \\text{}", function() {
|
||||
expect('ÀÁÂÃÄÅÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝàáâãäåèéêëìíîïñòóôõöùúûüýÿ' +
|
||||
'ÇÐÞçðþ').toParse();
|
||||
'ÇÐÞçðþ').toParse(nonstrictSettings);
|
||||
});
|
||||
|
||||
it("should parse all lower case Greek letters", function() {
|
||||
|
Reference in New Issue
Block a user