mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-04 18:58:39 +00:00
Port katex-a11y.js to KaTeX (#2062)
* copy katex-a11y.js from webapp to contrib/to-a11y-string.js * first pass of updating to-a11y-string to use flow * Set up test harness and write some tests - add flow types for jest - create wallaby config file - add prettier as a dep and set up .prettierrc so that we use .toMatchInlineSnapshot() and have jest insert the snapshot for us - add lots of TODOs (I'll create tickets for these later) * Add some to get the tests passing for now * remove commented out parts of wallaby.js config file * remove some stale strings, fix flow issue, add more coverage * add , add few more tests, handle middle * fix minor nits in tests * rename to-a11y-string to render-a11y-string, updatte webpack config to build extension * add test for \boxed * remove commented out code
This commit is contained in:
@@ -75,7 +75,8 @@
|
||||
"env": {
|
||||
"es6": true,
|
||||
"node": true,
|
||||
"browser": true
|
||||
"browser": true,
|
||||
"jest": true
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
|
20
.flowconfig
20
.flowconfig
@@ -1,3 +1,6 @@
|
||||
[version]
|
||||
0.102.0
|
||||
|
||||
[ignore]
|
||||
<PROJECT_ROOT>/dist
|
||||
|
||||
@@ -8,3 +11,20 @@
|
||||
[lints]
|
||||
|
||||
[options]
|
||||
# $FlowFixMe
|
||||
# This suppression hould be used to suppress an issue that requires additional
|
||||
# effort to be resolved. That effort should be recorded in our issue tracking
|
||||
# system for follow-up. Usually, this suppression is added by tooling during
|
||||
# flow upgrades and should not be used directly by developers. If it is
|
||||
# necessary, make sure to raise the corresponding issue and add the issue
|
||||
# number to a TODO comment associated with the suppression.
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe
|
||||
|
||||
# $FlowIgnore
|
||||
# Every now and then, flow cannot understand our code the way we can. We just
|
||||
# know better and we have to tell flow to trust us. Since this isn't something
|
||||
# we expect to "fix", we can annotate them to just have flow ignore them.
|
||||
# Using this suppression makes it clear that we know there's an error but it's
|
||||
# ok to ignore it. Make sure the suppression has associated commentary to state
|
||||
# why it's okay to ignore the flow error.
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIgnore
|
||||
|
7
.prettierrc
Normal file
7
.prettierrc
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"trailingComma": "all",
|
||||
"tabWidth": 4,
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"bracketSpacing": false
|
||||
}
|
706
contrib/render-a11y-string/render-a11y-string.js
Normal file
706
contrib/render-a11y-string/render-a11y-string.js
Normal file
@@ -0,0 +1,706 @@
|
||||
// @flow
|
||||
/**
|
||||
* renderA11yString returns a readable string.
|
||||
*
|
||||
* In some cases the string will have the proper semantic math
|
||||
* meaning,:
|
||||
* renderA11yString("\\frac{1}{2}"")
|
||||
* -> "start fraction, 1, divided by, 2, end fraction"
|
||||
*
|
||||
* However, other cases do not:
|
||||
* renderA11yString("f(x) = x^2")
|
||||
* -> "f, left parenthesis, x, right parenthesis, equals, x, squared"
|
||||
*
|
||||
* The commas in the string aim to increase ease of understanding
|
||||
* when read by a screenreader.
|
||||
*/
|
||||
|
||||
// NOTE: since we're importing types here these files won't actually be
|
||||
// included in the build.
|
||||
import type {Atom} from "../../src/symbols";
|
||||
import type {AnyParseNode} from "../../src/parseNode";
|
||||
import type {SettingsOptions} from "../../src/Settings";
|
||||
|
||||
// $FlowIgnore: we import the types directly anyways
|
||||
import katex from "katex";
|
||||
|
||||
const stringMap = {
|
||||
"(": "left parenthesis",
|
||||
")": "right parenthesis",
|
||||
"[": "open bracket",
|
||||
"]": "close bracket",
|
||||
"\\{": "left brace",
|
||||
"\\}": "right brace",
|
||||
"\\lvert": "open vertical bar",
|
||||
"\\rvert": "close vertical bar",
|
||||
"|": "vertical bar",
|
||||
"\\uparrow": "up arrow",
|
||||
"\\Uparrow": "up arrow",
|
||||
"\\downarrow": "down arrow",
|
||||
"\\Downarrow": "down arrow",
|
||||
"\\updownarrow": "up down arrow",
|
||||
"\\leftarrow": "left arrow",
|
||||
"\\Leftarrow": "left arrow",
|
||||
"\\rightarrow": "right arrow",
|
||||
"\\Rightarrow": "right arrow",
|
||||
"\\langle": "open angle",
|
||||
"\\rangle": "close angle",
|
||||
"\\lfloor": "open floor",
|
||||
"\\rfloor": "close floor",
|
||||
"\\int": "integral",
|
||||
"\\intop": "integral",
|
||||
"\\lim": "limit",
|
||||
"\\ln": "natural log",
|
||||
"\\log": "log",
|
||||
"\\sin": "sine",
|
||||
"\\cos": "cosine",
|
||||
"\\tan": "tangent",
|
||||
"\\cot": "cotangent",
|
||||
"\\sum": "sum",
|
||||
"/": "slash",
|
||||
",": "comma",
|
||||
".": "point",
|
||||
"-": "negative",
|
||||
"+": "plus",
|
||||
"~": "tilde",
|
||||
":": "colon",
|
||||
"?": "question mark",
|
||||
"'": "apostrophe",
|
||||
"\\%": "percent",
|
||||
" ": "space",
|
||||
"\\ ": "space",
|
||||
"\\$": "dollar sign",
|
||||
"\\angle": "angle",
|
||||
"\\degree": "degree",
|
||||
"\\circ": "circle",
|
||||
"\\vec": "vector",
|
||||
"\\triangle": "triangle",
|
||||
"\\pi": "pi",
|
||||
"\\prime": "prime",
|
||||
"\\infty": "infinity",
|
||||
"\\alpha": "alpha",
|
||||
"\\beta": "beta",
|
||||
"\\gamma": "gamma",
|
||||
"\\omega": "omega",
|
||||
"\\theta": "theta",
|
||||
"\\sigma": "sigma",
|
||||
"\\lambda": "lambda",
|
||||
"\\tau": "tau",
|
||||
"\\Delta": "delta",
|
||||
"\\delta": "delta",
|
||||
"\\mu": "mu",
|
||||
"\\rho": "rho",
|
||||
"\\nabla": "del",
|
||||
"\\ell": "ell",
|
||||
"\\ldots": "dots",
|
||||
// TODO: add entries for all accents
|
||||
"\\hat": "hat",
|
||||
"\\acute": "acute",
|
||||
};
|
||||
|
||||
const powerMap = {
|
||||
"prime": "prime",
|
||||
"degree": "degrees",
|
||||
"circle": "degrees",
|
||||
"2": "squared",
|
||||
"3": "cubed",
|
||||
};
|
||||
|
||||
const openMap = {
|
||||
"|": "open vertical bar",
|
||||
".": "",
|
||||
};
|
||||
|
||||
const closeMap = {
|
||||
"|": "close vertical bar",
|
||||
".": "",
|
||||
};
|
||||
|
||||
const binMap = {
|
||||
"+": "plus",
|
||||
"-": "minus",
|
||||
"\\pm": "plus minus",
|
||||
"\\cdot": "dot",
|
||||
"*": "times",
|
||||
"/": "divided by",
|
||||
"\\times": "times",
|
||||
"\\div": "divided by",
|
||||
"\\circ": "circle",
|
||||
"\\bullet": "bullet",
|
||||
};
|
||||
|
||||
const relMap = {
|
||||
"=": "equals",
|
||||
"\\approx": "approximately equals",
|
||||
"≠": "does not equal",
|
||||
"\\geq": "is greater than or equal to",
|
||||
"\\ge": "is greater than or equal to",
|
||||
"\\leq": "is less than or equal to",
|
||||
"\\le": "is less than or equal to",
|
||||
">": "is greater than",
|
||||
"<": "is less than",
|
||||
"\\leftarrow": "left arrow",
|
||||
"\\Leftarrow": "left arrow",
|
||||
"\\rightarrow": "right arrow",
|
||||
"\\Rightarrow": "right arrow",
|
||||
":": "colon",
|
||||
};
|
||||
|
||||
const accentUnderMap = {
|
||||
"\\underleftarrow": "left arrow",
|
||||
"\\underrightarrow": "right arrow",
|
||||
"\\underleftrightarrow": "left-right arrow",
|
||||
"\\undergroup": "group",
|
||||
"\\underlinesegment": "line segment",
|
||||
"\\utilde": "tilde",
|
||||
};
|
||||
|
||||
type NestedArray<T> = Array<T | NestedArray<T>>;
|
||||
|
||||
const buildString = (
|
||||
str: string,
|
||||
type: Atom | "normal",
|
||||
a11yStrings: NestedArray<string>,
|
||||
) => {
|
||||
if (!str) {
|
||||
return;
|
||||
}
|
||||
|
||||
let ret;
|
||||
|
||||
if (type === "open") {
|
||||
ret = str in openMap ? openMap[str] : stringMap[str] || str;
|
||||
} else if (type === "close") {
|
||||
ret = str in closeMap ? closeMap[str] : stringMap[str] || str;
|
||||
} else if (type === "bin") {
|
||||
ret = binMap[str] || str;
|
||||
} else if (type === "rel") {
|
||||
ret = relMap[str] || str;
|
||||
} else {
|
||||
ret = stringMap[str] || str;
|
||||
}
|
||||
|
||||
// If the text to add is a number and there is already a string
|
||||
// in the list and the last string is a number then we should
|
||||
// combine them into a single number
|
||||
if (
|
||||
/^\d+$/.test(ret) &&
|
||||
a11yStrings.length > 0 &&
|
||||
// TODO(kevinb): check that the last item in a11yStrings is a string
|
||||
// I think we might be able to drop the nested arrays, which would make
|
||||
// this easier to type - $FlowFixMe
|
||||
/^\d+$/.test(a11yStrings[a11yStrings.length - 1])
|
||||
) {
|
||||
a11yStrings[a11yStrings.length - 1] += ret;
|
||||
} else if (ret) {
|
||||
a11yStrings.push(ret);
|
||||
}
|
||||
};
|
||||
|
||||
const buildRegion = (
|
||||
a11yStrings: NestedArray<string>,
|
||||
callback: (regionStrings: NestedArray<string>) => void,
|
||||
) => {
|
||||
const regionStrings: NestedArray<string> = [];
|
||||
a11yStrings.push(regionStrings);
|
||||
callback(regionStrings);
|
||||
};
|
||||
|
||||
const handleObject = (
|
||||
tree: AnyParseNode,
|
||||
a11yStrings: NestedArray<string>,
|
||||
atomType: Atom | "normal",
|
||||
) => {
|
||||
// Everything else is assumed to be an object...
|
||||
switch (tree.type) {
|
||||
case "accent": {
|
||||
buildRegion(a11yStrings, (a11yStrings) => {
|
||||
buildA11yStrings(tree.base, a11yStrings, atomType);
|
||||
a11yStrings.push("with");
|
||||
buildString(tree.label, "normal", a11yStrings);
|
||||
a11yStrings.push("on top");
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "accentUnder": {
|
||||
buildRegion(a11yStrings, (a11yStrings) => {
|
||||
buildA11yStrings(tree.base, a11yStrings, atomType);
|
||||
a11yStrings.push("with");
|
||||
buildString(accentUnderMap[tree.label], "normal", a11yStrings);
|
||||
a11yStrings.push("underneath");
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "accent-token": {
|
||||
// Used internally by accent symbols.
|
||||
break;
|
||||
}
|
||||
|
||||
case "atom": {
|
||||
const {text} = tree;
|
||||
switch (tree.family) {
|
||||
case "bin": {
|
||||
buildString(text, "bin", a11yStrings);
|
||||
break;
|
||||
}
|
||||
case "close": {
|
||||
buildString(text, "close", a11yStrings);
|
||||
break;
|
||||
}
|
||||
// TODO(kevinb): figure out what should be done for inner
|
||||
case "inner": {
|
||||
buildString(tree.text, "inner", a11yStrings);
|
||||
break;
|
||||
}
|
||||
case "open": {
|
||||
buildString(text, "open", a11yStrings);
|
||||
break;
|
||||
}
|
||||
case "punct": {
|
||||
buildString(text, "punct", a11yStrings);
|
||||
break;
|
||||
}
|
||||
case "rel": {
|
||||
buildString(text, "rel", a11yStrings);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
(tree.family: empty);
|
||||
throw new Error(`"${tree.family}" is not a valid atom type`);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "color": {
|
||||
const color = tree.color.replace(/katex-/, "");
|
||||
|
||||
buildRegion(a11yStrings, (regionStrings) => {
|
||||
regionStrings.push("start color " + color);
|
||||
buildA11yStrings(tree.body, regionStrings, atomType);
|
||||
regionStrings.push("end color " + color);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "color-token": {
|
||||
// Used by \color, \colorbox, and \fcolorbox but not directly rendered.
|
||||
// It's a leaf node and has no children so just break.
|
||||
break;
|
||||
}
|
||||
|
||||
case "delimsizing": {
|
||||
if (tree.delim && tree.delim !== ".") {
|
||||
buildString(tree.delim, "normal", a11yStrings);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "genfrac": {
|
||||
buildRegion(a11yStrings, (regionStrings) => {
|
||||
// genfrac can have unbalanced delimiters
|
||||
const {leftDelim, rightDelim} = tree;
|
||||
|
||||
// NOTE: Not sure if this is a safe assumption
|
||||
// hasBarLine true -> fraction, false -> binomial
|
||||
if (tree.hasBarLine) {
|
||||
regionStrings.push("start fraction");
|
||||
leftDelim && buildString(leftDelim, "open", regionStrings);
|
||||
buildA11yStrings(tree.numer, regionStrings, atomType);
|
||||
regionStrings.push("divided by");
|
||||
buildA11yStrings(tree.denom, regionStrings, atomType);
|
||||
rightDelim && buildString(rightDelim, "close", regionStrings);
|
||||
regionStrings.push("end fraction");
|
||||
} else {
|
||||
regionStrings.push("start binomial");
|
||||
leftDelim && buildString(leftDelim, "open", regionStrings);
|
||||
buildA11yStrings(tree.numer, regionStrings, atomType);
|
||||
regionStrings.push("over");
|
||||
buildA11yStrings(tree.denom, regionStrings, atomType);
|
||||
rightDelim && buildString(rightDelim, "close", regionStrings);
|
||||
regionStrings.push("end binomial");
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "kern": {
|
||||
// No op: we don't attempt to present kerning information
|
||||
// to the screen reader.
|
||||
break;
|
||||
}
|
||||
|
||||
case "leftright": {
|
||||
buildRegion(a11yStrings, (regionStrings) => {
|
||||
buildString(tree.left, "open", regionStrings);
|
||||
buildA11yStrings(tree.body, regionStrings, atomType);
|
||||
buildString(tree.right, "close", regionStrings);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "leftright-right": {
|
||||
// TODO: double check that this is a no-op
|
||||
break;
|
||||
}
|
||||
|
||||
case "lap": {
|
||||
buildA11yStrings(tree.body, a11yStrings, atomType);
|
||||
break;
|
||||
}
|
||||
|
||||
case "mathord": {
|
||||
buildString(tree.text, "normal", a11yStrings);
|
||||
break;
|
||||
}
|
||||
|
||||
case "op": {
|
||||
const {body, name} = tree;
|
||||
if (body) {
|
||||
buildA11yStrings(body, a11yStrings, atomType);
|
||||
} else if (name) {
|
||||
buildString(name, "normal", a11yStrings);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "op-token": {
|
||||
// Used internally by operator symbols.
|
||||
buildString(tree.text, atomType, a11yStrings);
|
||||
break;
|
||||
}
|
||||
|
||||
case "ordgroup": {
|
||||
buildA11yStrings(tree.body, a11yStrings, atomType);
|
||||
break;
|
||||
}
|
||||
|
||||
case "overline": {
|
||||
buildRegion(a11yStrings, function(a11yStrings) {
|
||||
a11yStrings.push("start overline");
|
||||
buildA11yStrings(tree.body, a11yStrings, atomType);
|
||||
a11yStrings.push("end overline");
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "phantom": {
|
||||
a11yStrings.push("empty space");
|
||||
break;
|
||||
}
|
||||
|
||||
case "raisebox": {
|
||||
buildA11yStrings(tree.body, a11yStrings, atomType);
|
||||
break;
|
||||
}
|
||||
|
||||
case "rule": {
|
||||
a11yStrings.push("rectangle");
|
||||
break;
|
||||
}
|
||||
|
||||
case "sizing": {
|
||||
buildA11yStrings(tree.body, a11yStrings, atomType);
|
||||
break;
|
||||
}
|
||||
|
||||
case "spacing": {
|
||||
a11yStrings.push("space");
|
||||
break;
|
||||
}
|
||||
|
||||
case "styling": {
|
||||
// We ignore the styling and just pass through the contents
|
||||
buildA11yStrings(tree.body, a11yStrings, atomType);
|
||||
break;
|
||||
}
|
||||
|
||||
case "sqrt": {
|
||||
buildRegion(a11yStrings, (regionStrings) => {
|
||||
const {body, index} = tree;
|
||||
if (index) {
|
||||
const indexString = flatten(
|
||||
buildA11yStrings(index, [], atomType)).join(",");
|
||||
if (indexString === "3") {
|
||||
regionStrings.push("cube root of");
|
||||
buildA11yStrings(body, regionStrings, atomType);
|
||||
regionStrings.push("end cube root");
|
||||
return;
|
||||
}
|
||||
|
||||
regionStrings.push("root");
|
||||
regionStrings.push("start index");
|
||||
buildA11yStrings(index, regionStrings, atomType);
|
||||
regionStrings.push("end index");
|
||||
return;
|
||||
}
|
||||
|
||||
regionStrings.push("square root of");
|
||||
buildA11yStrings(body, regionStrings, atomType);
|
||||
regionStrings.push("end square root");
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "supsub": {
|
||||
const {base, sub, sup} = tree;
|
||||
let isLog = false;
|
||||
|
||||
if (base) {
|
||||
buildA11yStrings(base, a11yStrings, atomType);
|
||||
isLog = base.type === "op" && base.name === "\\log";
|
||||
}
|
||||
|
||||
if (sub) {
|
||||
const regionName = isLog ? "base" : "subscript";
|
||||
buildRegion(a11yStrings, function(regionStrings) {
|
||||
regionStrings.push(`start ${regionName}`);
|
||||
buildA11yStrings(sub, regionStrings, atomType);
|
||||
regionStrings.push(`end ${regionName}`);
|
||||
});
|
||||
}
|
||||
|
||||
if (sup) {
|
||||
buildRegion(a11yStrings, function(regionStrings) {
|
||||
const supString = flatten(
|
||||
buildA11yStrings(sup, [], atomType)).join(",");
|
||||
|
||||
if (supString in powerMap) {
|
||||
regionStrings.push(powerMap[supString]);
|
||||
return;
|
||||
}
|
||||
|
||||
regionStrings.push("start superscript");
|
||||
buildA11yStrings(sup, regionStrings, atomType);
|
||||
regionStrings.push("end superscript");
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "text": {
|
||||
// TODO: handle other fonts
|
||||
if (tree.font === "\\textbf") {
|
||||
buildRegion(a11yStrings, function(regionStrings) {
|
||||
regionStrings.push("start bold text");
|
||||
buildA11yStrings(tree.body, regionStrings, atomType);
|
||||
regionStrings.push("end bold text");
|
||||
});
|
||||
break;
|
||||
}
|
||||
buildRegion(a11yStrings, function(regionStrings) {
|
||||
regionStrings.push("start text");
|
||||
buildA11yStrings(tree.body, regionStrings, atomType);
|
||||
regionStrings.push("end text");
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "textord": {
|
||||
buildString(tree.text, atomType, a11yStrings);
|
||||
break;
|
||||
}
|
||||
|
||||
case "smash": {
|
||||
buildA11yStrings(tree.body, a11yStrings, atomType);
|
||||
break;
|
||||
}
|
||||
|
||||
case "enclose": {
|
||||
// TODO: create a map for these.
|
||||
// TODO: differentiate between a body with a single atom, e.g.
|
||||
// "cancel a" instead of "start cancel, a, end cancel"
|
||||
if (/cancel/.test(tree.label)) {
|
||||
buildRegion(a11yStrings, function(regionStrings) {
|
||||
regionStrings.push("start cancel");
|
||||
buildA11yStrings(tree.body, regionStrings, atomType);
|
||||
regionStrings.push("end cancel");
|
||||
});
|
||||
break;
|
||||
} else if (/box/.test(tree.label)) {
|
||||
buildRegion(a11yStrings, function(regionStrings) {
|
||||
regionStrings.push("start box");
|
||||
buildA11yStrings(tree.body, regionStrings, atomType);
|
||||
regionStrings.push("end box");
|
||||
});
|
||||
break;
|
||||
} else if (/sout/.test(tree.label)) {
|
||||
buildRegion(a11yStrings, function(regionStrings) {
|
||||
regionStrings.push("start strikeout");
|
||||
buildA11yStrings(tree.body, regionStrings, atomType);
|
||||
regionStrings.push("end strikeout");
|
||||
});
|
||||
break;
|
||||
}
|
||||
throw new Error(
|
||||
`KaTeX-a11y: enclose node with ${tree.label} not supported yet`);
|
||||
}
|
||||
|
||||
case "vphantom": {
|
||||
throw new Error("KaTeX-a11y: vphantom not implemented yet");
|
||||
}
|
||||
|
||||
case "hphantom": {
|
||||
throw new Error("KaTeX-a11y: hphantom not implemented yet");
|
||||
}
|
||||
|
||||
case "operatorname": {
|
||||
buildA11yStrings(tree.body, a11yStrings, atomType);
|
||||
break;
|
||||
}
|
||||
|
||||
case "array": {
|
||||
throw new Error("KaTeX-a11y: array not implemented yet");
|
||||
}
|
||||
|
||||
case "keyVals": {
|
||||
throw new Error("KaTeX-a11y: keyVals not implemented yet");
|
||||
}
|
||||
|
||||
case "raw": {
|
||||
throw new Error("KaTeX-a11y: raw not implemented yet");
|
||||
}
|
||||
|
||||
case "size": {
|
||||
// Although there are nodes of type "size" in the parse tree, they have
|
||||
// no semantic meaning and should be ignored.
|
||||
break;
|
||||
}
|
||||
|
||||
case "url": {
|
||||
throw new Error("KaTeX-a11y: url not implemented yet");
|
||||
}
|
||||
|
||||
case "tag": {
|
||||
throw new Error("KaTeX-a11y: tag not implemented yet");
|
||||
}
|
||||
|
||||
case "verb": {
|
||||
buildString(`start verbatim`, "normal", a11yStrings);
|
||||
buildString(tree.body, "normal", a11yStrings);
|
||||
buildString(`end verbatim`, "normal", a11yStrings);
|
||||
break;
|
||||
}
|
||||
|
||||
case "environment": {
|
||||
throw new Error("KaTeX-a11y: environment not implemented yet");
|
||||
}
|
||||
|
||||
case "horizBrace": {
|
||||
buildString(`start ${tree.label.slice(1)}`, "normal", a11yStrings);
|
||||
buildA11yStrings(tree.base, a11yStrings, atomType);
|
||||
buildString(`end ${tree.label.slice(1)}`, "normal", a11yStrings);
|
||||
break;
|
||||
}
|
||||
|
||||
case "infix": {
|
||||
// All infix nodes are replace with other nodes.
|
||||
break;
|
||||
}
|
||||
|
||||
case "includegraphics": {
|
||||
throw new Error("KaTeX-a11y: includegraphics not implemented yet");
|
||||
}
|
||||
|
||||
case "font": {
|
||||
// TODO: callout the start/end of specific fonts
|
||||
// TODO: map \BBb{N} to "the naturals" or something like that
|
||||
buildA11yStrings(tree.body, a11yStrings, atomType);
|
||||
break;
|
||||
}
|
||||
|
||||
case "href": {
|
||||
throw new Error("KaTeX-a11y: href not implemented yet");
|
||||
}
|
||||
|
||||
case "cr": {
|
||||
// This is used by environments.
|
||||
throw new Error("KaTeX-a11y: cr not implemented yet");
|
||||
}
|
||||
|
||||
case "underline": {
|
||||
buildRegion(a11yStrings, function(a11yStrings) {
|
||||
a11yStrings.push("start underline");
|
||||
buildA11yStrings(tree.body, a11yStrings, atomType);
|
||||
a11yStrings.push("end underline");
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "xArrow": {
|
||||
throw new Error("KaTeX-a11y: xArrow not implemented yet");
|
||||
}
|
||||
|
||||
case "mclass": {
|
||||
// \neq and \ne are macros so we let "htmlmathml" render the mathmal
|
||||
// side of things and extract the text from that.
|
||||
const atomType = tree.mclass.slice(1);
|
||||
// $FlowFixMe: drop the leading "m" from the values in mclass
|
||||
buildA11yStrings(tree.body, a11yStrings, atomType);
|
||||
break;
|
||||
}
|
||||
|
||||
case "mathchoice": {
|
||||
// TODO: track which which style we're using, e.g. dispaly, text, etc.
|
||||
// default to text style if even that may not be the correct style
|
||||
buildA11yStrings(tree.text, a11yStrings, atomType);
|
||||
break;
|
||||
}
|
||||
|
||||
case "htmlmathml": {
|
||||
buildA11yStrings(tree.mathml, a11yStrings, atomType);
|
||||
break;
|
||||
}
|
||||
|
||||
case "middle": {
|
||||
buildString(tree.delim, atomType, a11yStrings);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
(tree.type: empty);
|
||||
throw new Error("KaTeX a11y un-recognized type: " + tree.type);
|
||||
}
|
||||
};
|
||||
|
||||
const buildA11yStrings = (
|
||||
tree: AnyParseNode | AnyParseNode[],
|
||||
a11yStrings: NestedArray<string> = [],
|
||||
atomType: Atom | "normal",
|
||||
) => {
|
||||
if (tree instanceof Array) {
|
||||
for (let i = 0; i < tree.length; i++) {
|
||||
buildA11yStrings(tree[i], a11yStrings, atomType);
|
||||
}
|
||||
} else {
|
||||
handleObject(tree, a11yStrings, atomType);
|
||||
}
|
||||
|
||||
return a11yStrings;
|
||||
};
|
||||
|
||||
|
||||
const flatten = function(array) {
|
||||
let result = [];
|
||||
|
||||
array.forEach(function(item) {
|
||||
if (item instanceof Array) {
|
||||
result = result.concat(flatten(item));
|
||||
} else {
|
||||
result.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const renderA11yString = function(text: string, settings?: SettingsOptions) {
|
||||
|
||||
const tree = katex.__parse(text, settings);
|
||||
const a11yStrings = buildA11yStrings(tree, [], "normal");
|
||||
return flatten(a11yStrings).join(", ");
|
||||
};
|
||||
|
||||
export default renderA11yString;
|
526
contrib/render-a11y-string/test/render-a11y-string-spec.js
Normal file
526
contrib/render-a11y-string/test/render-a11y-string-spec.js
Normal file
@@ -0,0 +1,526 @@
|
||||
/* eslint-disable max-len */
|
||||
// @flow
|
||||
import renderA11yString from "../render-a11y-string";
|
||||
|
||||
describe("renderA11yString", () => {
|
||||
describe("basic expressions", () => {
|
||||
test("simple addition", () => {
|
||||
const result = renderA11yString("1 + 2");
|
||||
expect(result).toMatchInlineSnapshot(`"1, plus, 2"`);
|
||||
});
|
||||
});
|
||||
|
||||
describe("accent", () => {
|
||||
test("\\vec", () => {
|
||||
const result = renderA11yString("\\vec{a}");
|
||||
expect(result).toMatchInlineSnapshot(`"a, with, vector, on top"`);
|
||||
});
|
||||
|
||||
test("\\acute{a}", () => {
|
||||
const result = renderA11yString("\\acute{a}");
|
||||
expect(result).toMatchInlineSnapshot(`"a, with, acute, on top"`);
|
||||
});
|
||||
|
||||
test("\\hat{a}", () => {
|
||||
const result = renderA11yString("\\hat{a}");
|
||||
expect(result).toMatchInlineSnapshot(`"a, with, hat, on top"`);
|
||||
});
|
||||
});
|
||||
|
||||
describe("accentUnder", () => {
|
||||
test("\\underleftarrow", () => {
|
||||
const result = renderA11yString("\\underleftarrow{1+2}");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"1, plus, 2, with, left arrow, underneath"`,
|
||||
);
|
||||
});
|
||||
|
||||
test("\\underlinesegment", () => {
|
||||
const result = renderA11yString("\\underlinesegment{1+2}");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"1, plus, 2, with, line segment, underneath"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("atom", () => {
|
||||
test("punct", () => {
|
||||
const result = renderA11yString("1, 2, 3");
|
||||
expect(result).toMatchInlineSnapshot(`"1, comma, 2, comma, 3"`);
|
||||
});
|
||||
});
|
||||
|
||||
describe("color", () => {
|
||||
test("\\color{red}", () => {
|
||||
const result = renderA11yString("\\color{red}1+2");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"start color red, 1, plus, 2, end color red"`,
|
||||
);
|
||||
});
|
||||
|
||||
test("\\color{FF0000}", () => {
|
||||
const result = renderA11yString("\\color{FF0000}1+2");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"start color #FF0000, 1, plus, 2, end color #FF0000"`,
|
||||
);
|
||||
});
|
||||
|
||||
// colorIsTextColor is an option added in KaTeX 0.9.0 for backward
|
||||
// compatibility. It makes \color parse like \textcolor. We use it
|
||||
// in the KA webapp, and need it here because the tests are written
|
||||
// assuming it is set.
|
||||
test("\\color{red} with {colorIsTextColor: true}", () => {
|
||||
const result = renderA11yString("\\color{red}1+2", {
|
||||
colorIsTextColor: true,
|
||||
});
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"start color red, 1, end color red, plus, 2"`,
|
||||
);
|
||||
});
|
||||
|
||||
test("\\textcolor{red}", () => {
|
||||
const result = renderA11yString("\\textcolor{red}1+2");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"start color red, 1, end color red, plus, 2"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("delimiters", () => {
|
||||
test("simple parens", () => {
|
||||
const result = renderA11yString("(1 + 3)");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"left parenthesis, 1, plus, 3, right parenthesis"`,
|
||||
);
|
||||
});
|
||||
|
||||
test("simple brackets", () => {
|
||||
const result = renderA11yString("[1 + 3]");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"open bracket, 1, plus, 3, close bracket"`,
|
||||
);
|
||||
});
|
||||
|
||||
test("nested parens", () => {
|
||||
const result = renderA11yString("(a + (b + c))");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"left parenthesis, a, plus, left parenthesis, b, plus, c, right parenthesis, right parenthesis"`,
|
||||
);
|
||||
});
|
||||
|
||||
test("stretchy parens around fractions", () => {
|
||||
const result = renderA11yString("\\left(\\frac{1}{x}\\right)");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"left parenthesis, start fraction, 1, divided by, x, end fraction, right parenthesis"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("delimsizing", () => {
|
||||
test("\\bigl(1+2\\bigr)", () => {
|
||||
const result = renderA11yString("\\bigl(1+2\\bigr)");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"left parenthesis, 1, plus, 2, right parenthesis"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("enclose", () => {
|
||||
test("\\cancel", () => {
|
||||
const result = renderA11yString("\\cancel{a}");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"start cancel, a, end cancel"`,
|
||||
);
|
||||
});
|
||||
|
||||
test("\\fbox", () => {
|
||||
const result = renderA11yString("\\fbox{a}");
|
||||
expect(result).toMatchInlineSnapshot(`"start box, a, end box"`);
|
||||
});
|
||||
|
||||
test("\\boxed", () => {
|
||||
const result = renderA11yString("\\boxed{a}");
|
||||
expect(result).toMatchInlineSnapshot(`"start box, a, end box"`);
|
||||
});
|
||||
|
||||
test("\\sout", () => {
|
||||
const result = renderA11yString("\\sout{a}");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"start strikeout, a, end strikeout"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("exponents", () => {
|
||||
test("simple exponent", () => {
|
||||
const result = renderA11yString("e^x");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"e, start superscript, x, end superscript"`,
|
||||
);
|
||||
});
|
||||
|
||||
test("^{\\circ} => degrees", () => {
|
||||
const result = renderA11yString("90^{\\circ}");
|
||||
expect(result).toMatchInlineSnapshot(`"90, degrees"`);
|
||||
});
|
||||
|
||||
test("^{\\degree} => degrees", () => {
|
||||
const result = renderA11yString("90^{\\degree}");
|
||||
expect(result).toMatchInlineSnapshot(`"90, degrees"`);
|
||||
});
|
||||
|
||||
test("^{\\prime} => prime", () => {
|
||||
const result = renderA11yString("f^{\\prime}");
|
||||
expect(result).toMatchInlineSnapshot(`"f, prime"`);
|
||||
});
|
||||
|
||||
test("^2 => squared", () => {
|
||||
const result = renderA11yString("x^2");
|
||||
expect(result).toMatchInlineSnapshot(`"x, squared"`);
|
||||
});
|
||||
|
||||
test("^3 => cubed", () => {
|
||||
const result = renderA11yString("x^3");
|
||||
expect(result).toMatchInlineSnapshot(`"x, cubed"`);
|
||||
});
|
||||
|
||||
test("log_2", () => {
|
||||
const result = renderA11yString("\\log_2{x+1}");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"log, start base, 2, end base, x, plus, 1"`,
|
||||
);
|
||||
});
|
||||
|
||||
test("a_{n+1}", () => {
|
||||
const result = renderA11yString("a_{n+1}");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"a, start subscript, n, plus, 1, end subscript"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("genfrac", () => {
|
||||
test("simple fractions", () => {
|
||||
const result = renderA11yString("\\frac{2}{3}");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"start fraction, 2, divided by, 3, end fraction"`,
|
||||
);
|
||||
});
|
||||
|
||||
test("nested fractions", () => {
|
||||
const result = renderA11yString("\\frac{1}{1+\\frac{1}{x}}");
|
||||
// TODO: this result is ambiguous, we need to fix this
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"start fraction, 1, divided by, 1, plus, start fraction, 1, divided by, x, end fraction, end fraction"`,
|
||||
);
|
||||
});
|
||||
|
||||
test("binomials", () => {
|
||||
const result = renderA11yString("\\binom{n}{k}");
|
||||
// TODO: drop the parenthesis as they're not normally read
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"start binomial, left parenthesis, n, over, k, right parenthesis, end binomial"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("horizBrace", () => {
|
||||
test("\\overbrace", () => {
|
||||
const result = renderA11yString("\\overbrace{1+2}");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"start overbrace, 1, plus, 2, end overbrace"`,
|
||||
);
|
||||
});
|
||||
|
||||
test("\\underbrace", () => {
|
||||
const result = renderA11yString("\\underbrace{1+2}");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"start underbrace, 1, plus, 2, end underbrace"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("infix", () => {
|
||||
test("\\over", () => {
|
||||
const result = renderA11yString("a \\over b");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"start fraction, a, divided by, b, end fraction"`,
|
||||
);
|
||||
});
|
||||
|
||||
test("\\choose", () => {
|
||||
const result = renderA11yString("a \\choose b");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"start binomial, left parenthesis, a, over, b, right parenthesis, end binomial"`,
|
||||
);
|
||||
});
|
||||
|
||||
test("\\above", () => {
|
||||
const result = renderA11yString("a \\above{2pt} b");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"start fraction, a, divided by, b, end fraction"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("inner", () => {
|
||||
test("\\ldots", () => {
|
||||
const result = renderA11yString("\\ldots");
|
||||
expect(result).toMatchInlineSnapshot(`"dots"`);
|
||||
});
|
||||
});
|
||||
|
||||
describe("lap", () => {
|
||||
test("\\llap", () => {
|
||||
const result = renderA11yString("a\\llap{b}");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"a, start text, b, end text"`,
|
||||
);
|
||||
});
|
||||
|
||||
test("\\rlap", () => {
|
||||
const result = renderA11yString("a\\rlap{b}");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"a, start text, b, end text"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("middle", () => {
|
||||
test("\\middle", () => {
|
||||
const result = renderA11yString("\\left(a\\middle|b\\right)");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"left parenthesis, a, vertical bar, b, right parenthesis"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mod", () => {
|
||||
test("\\mod", () => {
|
||||
const result = renderA11yString("\\mod{23}");
|
||||
// TODO: drop the "space"
|
||||
// TODO: collate m, o, d... we should fix this inside of KaTeX since
|
||||
// this affects the HTML and MathML output as well
|
||||
expect(result).toMatchInlineSnapshot(`"space, m, o, d, 23"`);
|
||||
});
|
||||
});
|
||||
|
||||
describe("op", () => {
|
||||
test("\\lim", () => {
|
||||
const result = renderA11yString("\\lim{x+1}");
|
||||
// TODO: add begin/end to track argument of operators
|
||||
expect(result).toMatchInlineSnapshot(`"limit, x, plus, 1"`);
|
||||
});
|
||||
|
||||
test("\\sin 2\\pi", () => {
|
||||
const result = renderA11yString("\\sin{2\\pi}");
|
||||
// TODO: add begin/end to track argument of operators
|
||||
expect(result).toMatchInlineSnapshot(`"sine, 2, pi"`);
|
||||
});
|
||||
|
||||
test("\\sum_{i=0}", () => {
|
||||
const result = renderA11yString("\\sum_{i=0}");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"sum, start subscript, i, equals, 0, end subscript"`,
|
||||
);
|
||||
});
|
||||
|
||||
test("\u2211_{i=0}", () => {
|
||||
const result = renderA11yString("\u2211_{i=0}");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"sum, start subscript, i, equals, 0, end subscript"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("operatorname", () => {
|
||||
test("\\limsup", () => {
|
||||
const result = renderA11yString("\\limsup");
|
||||
// TODO: collate strings so that this is "lim, sup"
|
||||
// NOTE: this also affect HTML and MathML output
|
||||
expect(result).toMatchInlineSnapshot(`"l, i, m, s, u, p"`);
|
||||
});
|
||||
|
||||
test("\\liminf", () => {
|
||||
const result = renderA11yString("\\liminf");
|
||||
expect(result).toMatchInlineSnapshot(`"l, i, m, i, n, f"`);
|
||||
});
|
||||
|
||||
test("\\argmin", () => {
|
||||
const result = renderA11yString("\\argmin");
|
||||
expect(result).toMatchInlineSnapshot(`"a, r, g, m, i, n"`);
|
||||
});
|
||||
});
|
||||
|
||||
describe("overline", () => {
|
||||
test("\\overline", () => {
|
||||
const result = renderA11yString("\\overline{1+2}");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"start overline, 1, plus, 2, end overline"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("phantom", () => {
|
||||
test("\\phantom", () => {
|
||||
const result = renderA11yString("1+\\phantom{2}");
|
||||
expect(result).toMatchInlineSnapshot(`"1, plus, empty space"`);
|
||||
});
|
||||
});
|
||||
|
||||
describe("raisebox", () => {
|
||||
test("\\raisebox", () => {
|
||||
const result = renderA11yString("x+\\raisebox{1em}{y}");
|
||||
expect(result).toMatchInlineSnapshot(`"x, plus, y"`);
|
||||
});
|
||||
});
|
||||
|
||||
describe("relations", () => {
|
||||
test("1 \\neq 2", () => {
|
||||
const result = renderA11yString("1 \\neq 2");
|
||||
expect(result).toMatchInlineSnapshot(`"1, does not equal, 2"`);
|
||||
});
|
||||
|
||||
test("1 \\ne 2", () => {
|
||||
const result = renderA11yString("1 \\ne 2");
|
||||
expect(result).toMatchInlineSnapshot(`"1, does not equal, 2"`);
|
||||
});
|
||||
|
||||
test("1 \\geq 2", () => {
|
||||
const result = renderA11yString("1 \\geq 2");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"1, is greater than or equal to, 2"`,
|
||||
);
|
||||
});
|
||||
|
||||
test("1 \\ge 2", () => {
|
||||
const result = renderA11yString("1 \\ge 2");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"1, is greater than or equal to, 2"`,
|
||||
);
|
||||
});
|
||||
|
||||
test("1 \\leq 2", () => {
|
||||
const result = renderA11yString("1 \\leq 3");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"1, is less than or equal to, 3"`,
|
||||
);
|
||||
});
|
||||
|
||||
test("1 \\le 2", () => {
|
||||
const result = renderA11yString("1 \\le 3");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"1, is less than or equal to, 3"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("rule", () => {
|
||||
test("\\rule", () => {
|
||||
const result = renderA11yString("\\rule{1em}{1em}");
|
||||
expect(result).toMatchInlineSnapshot(`"rectangle"`);
|
||||
});
|
||||
});
|
||||
|
||||
describe("smash", () => {
|
||||
test("1 + \\smash{2}", () => {
|
||||
const result = renderA11yString("1 + \\smash{2}");
|
||||
expect(result).toMatchInlineSnapshot(`"1, plus, 2"`);
|
||||
});
|
||||
});
|
||||
|
||||
describe("sqrt", () => {
|
||||
test("square root", () => {
|
||||
const result = renderA11yString("\\sqrt{x + 1}");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"square root of, x, plus, 1, end square root"`,
|
||||
);
|
||||
});
|
||||
|
||||
test("nest square root", () => {
|
||||
const result = renderA11yString("\\sqrt{x + \\sqrt{y}}");
|
||||
// TODO: this sounds ambiguous as well... we should probably say "start square root"
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"square root of, x, plus, square root of, y, end square root, end square root"`,
|
||||
);
|
||||
});
|
||||
|
||||
test("cube root", () => {
|
||||
const result = renderA11yString("\\sqrt[3]{x + 1}");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"cube root of, x, plus, 1, end cube root"`,
|
||||
);
|
||||
});
|
||||
|
||||
test("nth root", () => {
|
||||
const result = renderA11yString("\\sqrt[n]{x + 1}");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"root, start index, n, end index"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("sizing", () => {
|
||||
test("\\Huge is ignored", () => {
|
||||
const result = renderA11yString("\\Huge{a+b}");
|
||||
expect(result).toMatchInlineSnapshot(`"a, plus, b"`);
|
||||
});
|
||||
|
||||
test("\\small is ignored", () => {
|
||||
const result = renderA11yString("\\small{a+b}");
|
||||
expect(result).toMatchInlineSnapshot(`"a, plus, b"`);
|
||||
});
|
||||
|
||||
// We don't need to test all sizing commands since all style
|
||||
// nodes are treated in the same way.
|
||||
});
|
||||
|
||||
describe("styling", () => {
|
||||
test("\\displaystyle is ignored", () => {
|
||||
const result = renderA11yString("\\displaystyle{a+b}");
|
||||
expect(result).toMatchInlineSnapshot(`"a, plus, b"`);
|
||||
});
|
||||
|
||||
test("\\textstyle is ignored", () => {
|
||||
const result = renderA11yString("\\textstyle{a+b}");
|
||||
expect(result).toMatchInlineSnapshot(`"a, plus, b"`);
|
||||
});
|
||||
|
||||
// We don't need to test all styling commands since all style
|
||||
// nodes are treated in the same way.
|
||||
});
|
||||
|
||||
describe("text", () => {
|
||||
test("\\text", () => {
|
||||
const result = renderA11yString("\\text{hello}");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"start text, h, e, l, l, o, end text"`,
|
||||
);
|
||||
});
|
||||
|
||||
test("\\textbf", () => {
|
||||
const result = renderA11yString("\\textbf{hello}");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"start bold text, h, e, l, l, o, end bold text"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("underline", () => {
|
||||
test("\\underline", () => {
|
||||
const result = renderA11yString("\\underline{1+2}");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"start underline, 1, plus, 2, end underline"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("verb", () => {
|
||||
test("\\verb", () => {
|
||||
const result = renderA11yString("\\verb|hello|");
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"start verbatim, hello, end verbatim"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
1201
flow-typed/npm/jest_v24.x.x.js
vendored
Normal file
1201
flow-typed/npm/jest_v24.x.x.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@@ -56,6 +56,7 @@
|
||||
"mkdirp": "^0.5.1",
|
||||
"pako": "^1.0.8",
|
||||
"postcss-loader": "^3.0.0",
|
||||
"prettier": "^1.18.2",
|
||||
"query-string": "^6.2.0",
|
||||
"rimraf": "^2.6.3",
|
||||
"rollup": "^1.2.2",
|
||||
|
@@ -71,7 +71,7 @@ defineFunction({
|
||||
return {
|
||||
type: "mclass",
|
||||
mode: parser.mode,
|
||||
mclass: "m" + funcName.substr(5),
|
||||
mclass: "m" + funcName.substr(5), // TODO(kevinb): don't prefix with 'm'
|
||||
body: ordargument(body),
|
||||
isCharacterBox: utils.isCharacterBox(body),
|
||||
};
|
||||
|
@@ -11,7 +11,7 @@ const target = path.join(__dirname, 'unicodeSymbols.js');
|
||||
const targetMtime = fs.statSync(target).mtime;
|
||||
if (fs.statSync(__filename).mtime <= targetMtime && fs.statSync(
|
||||
path.join(__dirname, 'unicodeAccents.js')).mtime <= targetMtime) {
|
||||
return;
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
require('@babel/register');
|
||||
|
39
wallaby.js
Normal file
39
wallaby.js
Normal file
@@ -0,0 +1,39 @@
|
||||
const babelConfig = require("./babel.config.js");
|
||||
|
||||
module.exports = function(wallaby) {
|
||||
const tests = [
|
||||
"test/*-spec.js",
|
||||
"contrib/**/test/*-spec.js",
|
||||
];
|
||||
|
||||
return {
|
||||
tests,
|
||||
|
||||
// Wallaby needs to know about all files that may be loaded because
|
||||
// of running a test.
|
||||
files: [
|
||||
"src/**/*.js",
|
||||
"test/**/*.js",
|
||||
"contrib/**/*.js",
|
||||
"submodules/**/*.js",
|
||||
"katex.js",
|
||||
|
||||
// These paths are excluded.
|
||||
...tests.map((test) => `!${test}`),
|
||||
],
|
||||
|
||||
// Wallaby does its own compilation of .js files to support its
|
||||
// advanced logging features. Wallaby can't parse the flow and
|
||||
// JSX in our source files so need to provide a babel configuration.
|
||||
compilers: {
|
||||
"**/*.js": wallaby.compilers.babel(babelConfig),
|
||||
},
|
||||
|
||||
env: {
|
||||
type: "node",
|
||||
runner: "node",
|
||||
},
|
||||
|
||||
testFramework: "jest",
|
||||
};
|
||||
};
|
@@ -45,6 +45,10 @@ const targets /*: Array<Target> */ = [
|
||||
name: 'contrib/mathtex-script-type',
|
||||
entry: './contrib/mathtex-script-type/mathtex-script-type.js',
|
||||
},
|
||||
{
|
||||
name: 'contrib/render-a11y-string',
|
||||
entry: './contrib/render-a11y-string/render-a11y-string.js',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
|
@@ -6673,6 +6673,11 @@ prelude-ls@~1.1.2:
|
||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
|
||||
integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=
|
||||
|
||||
prettier@^1.18.2:
|
||||
version "1.18.2"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.18.2.tgz#6823e7c5900017b4bd3acf46fe9ac4b4d7bda9ea"
|
||||
integrity sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==
|
||||
|
||||
pretty-format@^24.0.0:
|
||||
version "24.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.0.0.tgz#cb6599fd73ac088e37ed682f61291e4678f48591"
|
||||
|
Reference in New Issue
Block a user