render-a11y-string-spec.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  1. /* eslint-disable max-len */
  2. import renderA11yString from "../render-a11y-string";
  3. describe("renderA11yString", () => {
  4. describe("basic expressions", () => {
  5. test("simple addition", () => {
  6. const result = renderA11yString("1 + 2");
  7. expect(result).toMatchInlineSnapshot(`"1, plus, 2"`);
  8. });
  9. });
  10. describe("accent", () => {
  11. test("\\vec", () => {
  12. const result = renderA11yString("\\vec{a}");
  13. expect(result).toMatchInlineSnapshot(`"a, with, vector, on top"`);
  14. });
  15. test("\\acute{a}", () => {
  16. const result = renderA11yString("\\acute{a}");
  17. expect(result).toMatchInlineSnapshot(`"a, with, acute, on top"`);
  18. });
  19. test("\\hat{a}", () => {
  20. const result = renderA11yString("\\hat{a}");
  21. expect(result).toMatchInlineSnapshot(`"a, with, hat, on top"`);
  22. });
  23. });
  24. describe("accentUnder", () => {
  25. test("\\underleftarrow", () => {
  26. const result = renderA11yString("\\underleftarrow{1+2}");
  27. expect(result).toMatchInlineSnapshot(
  28. `"1, plus, 2, with, left arrow, underneath"`,
  29. );
  30. });
  31. test("\\underlinesegment", () => {
  32. const result = renderA11yString("\\underlinesegment{1+2}");
  33. expect(result).toMatchInlineSnapshot(
  34. `"1, plus, 2, with, line segment, underneath"`,
  35. );
  36. });
  37. });
  38. describe("atom", () => {
  39. test("punct", () => {
  40. const result = renderA11yString("1, 2, 3");
  41. expect(result).toMatchInlineSnapshot(`"1, comma, 2, comma, 3"`);
  42. });
  43. });
  44. describe("color", () => {
  45. test("\\color{red}", () => {
  46. const result = renderA11yString("\\color{red}1+2");
  47. expect(result).toMatchInlineSnapshot(
  48. `"start color red, 1, plus, 2, end color red"`,
  49. );
  50. });
  51. test("\\color{FF0000}", () => {
  52. const result = renderA11yString("\\color{FF0000}1+2");
  53. expect(result).toMatchInlineSnapshot(
  54. `"start color #FF0000, 1, plus, 2, end color #FF0000"`,
  55. );
  56. });
  57. // colorIsTextColor is an option added in KaTeX 0.9.0 for backward
  58. // compatibility. It makes \color parse like \textcolor. We use it
  59. // in the KA webapp, and need it here because the tests are written
  60. // assuming it is set.
  61. test("\\color{red} with {colorIsTextColor: true}", () => {
  62. const result = renderA11yString("\\color{red}1+2", {
  63. colorIsTextColor: true,
  64. });
  65. expect(result).toMatchInlineSnapshot(
  66. `"start color red, 1, end color red, plus, 2"`,
  67. );
  68. });
  69. test("\\textcolor{red}", () => {
  70. const result = renderA11yString("\\textcolor{red}1+2");
  71. expect(result).toMatchInlineSnapshot(
  72. `"start color red, 1, end color red, plus, 2"`,
  73. );
  74. });
  75. });
  76. describe("delimiters", () => {
  77. test("simple parens", () => {
  78. const result = renderA11yString("(1 + 3)");
  79. expect(result).toMatchInlineSnapshot(
  80. `"left parenthesis, 1, plus, 3, right parenthesis"`,
  81. );
  82. });
  83. test("simple brackets", () => {
  84. const result = renderA11yString("[1 + 3]");
  85. expect(result).toMatchInlineSnapshot(
  86. `"open bracket, 1, plus, 3, close bracket"`,
  87. );
  88. });
  89. test("nested parens", () => {
  90. const result = renderA11yString("(a + (b + c))");
  91. expect(result).toMatchInlineSnapshot(
  92. `"left parenthesis, a, plus, left parenthesis, b, plus, c, right parenthesis, right parenthesis"`,
  93. );
  94. });
  95. test("stretchy parens around fractions", () => {
  96. const result = renderA11yString("\\left(\\frac{1}{x}\\right)");
  97. expect(result).toMatchInlineSnapshot(
  98. `"left parenthesis, start fraction, 1, divided by, x, end fraction, right parenthesis"`,
  99. );
  100. });
  101. });
  102. describe("delimsizing", () => {
  103. test("\\bigl(1+2\\bigr)", () => {
  104. const result = renderA11yString("\\bigl(1+2\\bigr)");
  105. expect(result).toMatchInlineSnapshot(
  106. `"left parenthesis, 1, plus, 2, right parenthesis"`,
  107. );
  108. });
  109. });
  110. describe("enclose", () => {
  111. test("\\cancel", () => {
  112. const result = renderA11yString("\\cancel{a}");
  113. expect(result).toMatchInlineSnapshot(
  114. `"start cancel, a, end cancel"`,
  115. );
  116. });
  117. test("\\fbox", () => {
  118. const result = renderA11yString("\\fbox{a}");
  119. expect(result).toMatchInlineSnapshot(`"start box, a, end box"`);
  120. });
  121. test("\\boxed", () => {
  122. const result = renderA11yString("\\boxed{a}");
  123. expect(result).toMatchInlineSnapshot(`"start box, a, end box"`);
  124. });
  125. test("\\sout", () => {
  126. const result = renderA11yString("\\text{\\sout{a}}");
  127. expect(result).toMatchInlineSnapshot(
  128. `"start text, start strikeout, a, end strikeout, end text"`,
  129. );
  130. });
  131. });
  132. describe("phase", () => {
  133. test("\\phase", () => {
  134. const result = renderA11yString("\\phase{a}");
  135. expect(result).toMatchInlineSnapshot(
  136. `"start phase angle, a, end phase angle"`,
  137. );
  138. });
  139. });
  140. describe("exponents", () => {
  141. test("simple exponent", () => {
  142. const result = renderA11yString("e^x");
  143. expect(result).toMatchInlineSnapshot(
  144. `"e, start superscript, x, end superscript"`,
  145. );
  146. });
  147. test("^{\\circ} => degrees", () => {
  148. const result = renderA11yString("90^{\\circ}");
  149. expect(result).toMatchInlineSnapshot(`"90, degrees"`);
  150. });
  151. test("^{\\degree} => degrees", () => {
  152. const result = renderA11yString("90^{\\degree}");
  153. expect(result).toMatchInlineSnapshot(`"90, degrees"`);
  154. });
  155. test("^{\\prime} => prime", () => {
  156. const result = renderA11yString("f^{\\prime}");
  157. expect(result).toMatchInlineSnapshot(`"f, prime"`);
  158. });
  159. test("^2 => squared", () => {
  160. const result = renderA11yString("x^2");
  161. expect(result).toMatchInlineSnapshot(`"x, squared"`);
  162. });
  163. test("^3 => cubed", () => {
  164. const result = renderA11yString("x^3");
  165. expect(result).toMatchInlineSnapshot(`"x, cubed"`);
  166. });
  167. test("log_2", () => {
  168. const result = renderA11yString("\\log_2{x+1}");
  169. expect(result).toMatchInlineSnapshot(
  170. `"log, start base, 2, end base, x, plus, 1"`,
  171. );
  172. });
  173. test("a_{n+1}", () => {
  174. const result = renderA11yString("a_{n+1}");
  175. expect(result).toMatchInlineSnapshot(
  176. `"a, start subscript, n, plus, 1, end subscript"`,
  177. );
  178. });
  179. });
  180. describe("genfrac", () => {
  181. test("simple fractions", () => {
  182. const result = renderA11yString("\\frac{2}{3}");
  183. expect(result).toMatchInlineSnapshot(
  184. `"start fraction, 2, divided by, 3, end fraction"`,
  185. );
  186. });
  187. test("nested fractions", () => {
  188. const result = renderA11yString("\\frac{1}{1+\\frac{1}{x}}");
  189. // TODO: this result is ambiguous, we need to fix this
  190. expect(result).toMatchInlineSnapshot(
  191. `"start fraction, 1, divided by, 1, plus, start fraction, 1, divided by, x, end fraction, end fraction"`,
  192. );
  193. });
  194. test("binomials", () => {
  195. const result = renderA11yString("\\binom{n}{k}");
  196. // TODO: drop the parenthesis as they're not normally read
  197. expect(result).toMatchInlineSnapshot(
  198. `"start binomial, left parenthesis, n, over, k, right parenthesis, end binomial"`,
  199. );
  200. });
  201. });
  202. describe("horizBrace", () => {
  203. test("\\overbrace", () => {
  204. const result = renderA11yString("\\overbrace{1+2}");
  205. expect(result).toMatchInlineSnapshot(
  206. `"start overbrace, 1, plus, 2, end overbrace"`,
  207. );
  208. });
  209. test("\\underbrace", () => {
  210. const result = renderA11yString("\\underbrace{1+2}");
  211. expect(result).toMatchInlineSnapshot(
  212. `"start underbrace, 1, plus, 2, end underbrace"`,
  213. );
  214. });
  215. test("\\overbracket", () => {
  216. const result = renderA11yString("\\overbracket{1+2}");
  217. expect(result).toMatchInlineSnapshot(
  218. `"start overbracket, 1, plus, 2, end overbracket"`,
  219. );
  220. });
  221. test("\\underbracket", () => {
  222. const result = renderA11yString("\\underbracket{1+2}");
  223. expect(result).toMatchInlineSnapshot(
  224. `"start underbracket, 1, plus, 2, end underbracket"`,
  225. );
  226. });
  227. });
  228. describe("infix", () => {
  229. test("\\over", () => {
  230. const result = renderA11yString("a \\over b");
  231. expect(result).toMatchInlineSnapshot(
  232. `"start fraction, a, divided by, b, end fraction"`,
  233. );
  234. });
  235. test("\\choose", () => {
  236. const result = renderA11yString("a \\choose b");
  237. expect(result).toMatchInlineSnapshot(
  238. `"start binomial, left parenthesis, a, over, b, right parenthesis, end binomial"`,
  239. );
  240. });
  241. test("\\above", () => {
  242. const result = renderA11yString("a \\above{2pt} b");
  243. expect(result).toMatchInlineSnapshot(
  244. `"start fraction, a, divided by, b, end fraction"`,
  245. );
  246. });
  247. });
  248. describe("hbox", () => {
  249. test("\\hbox", () => {
  250. const result = renderA11yString("x+\\hbox{y}");
  251. expect(result).toMatchInlineSnapshot(`"x, plus, y"`);
  252. });
  253. });
  254. describe("inner", () => {
  255. test("\\ldots", () => {
  256. const result = renderA11yString("\\ldots");
  257. expect(result).toMatchInlineSnapshot(`"dots"`);
  258. });
  259. });
  260. describe("lap", () => {
  261. test("\\llap", () => {
  262. const result = renderA11yString("a\\llap{b}");
  263. expect(result).toMatchInlineSnapshot(
  264. `"a, start text, b, end text"`,
  265. );
  266. });
  267. test("\\rlap", () => {
  268. const result = renderA11yString("a\\rlap{b}");
  269. expect(result).toMatchInlineSnapshot(
  270. `"a, start text, b, end text"`,
  271. );
  272. });
  273. });
  274. describe("middle", () => {
  275. test("\\middle", () => {
  276. const result = renderA11yString("\\left(a\\middle|b\\right)");
  277. expect(result).toMatchInlineSnapshot(
  278. `"left parenthesis, a, vertical bar, b, right parenthesis"`,
  279. );
  280. });
  281. });
  282. describe("mod", () => {
  283. test("\\mod", () => {
  284. const result = renderA11yString("\\mod{23}");
  285. // TODO: drop the "space"
  286. // TODO: collate m, o, d... we should fix this inside of KaTeX since
  287. // this affects the HTML and MathML output as well
  288. expect(result).toMatchInlineSnapshot(`"space, m, o, d, 23"`);
  289. });
  290. });
  291. describe("op", () => {
  292. test("\\lim", () => {
  293. const result = renderA11yString("\\lim{x+1}");
  294. // TODO: add begin/end to track argument of operators
  295. expect(result).toMatchInlineSnapshot(`"limit, x, plus, 1"`);
  296. });
  297. test("\\sin 2\\pi", () => {
  298. const result = renderA11yString("\\sin{2\\pi}");
  299. // TODO: add begin/end to track argument of operators
  300. expect(result).toMatchInlineSnapshot(`"sine, 2, pi"`);
  301. });
  302. test("\\sum_{i=0}", () => {
  303. const result = renderA11yString("\\sum_{i=0}");
  304. expect(result).toMatchInlineSnapshot(
  305. `"sum, start subscript, i, equals, 0, end subscript"`,
  306. );
  307. });
  308. test("\u2211_{i=0}", () => {
  309. const result = renderA11yString("\u2211_{i=0}");
  310. expect(result).toMatchInlineSnapshot(
  311. `"sum, start subscript, i, equals, 0, end subscript"`,
  312. );
  313. });
  314. });
  315. describe("operatorname", () => {
  316. test("\\limsup", () => {
  317. const result = renderA11yString("\\limsup");
  318. // TODO: collate strings so that this is "lim, sup"
  319. // NOTE: this also affect HTML and MathML output
  320. expect(result).toMatchInlineSnapshot(`"l, i, m, s, u, p"`);
  321. });
  322. test("\\liminf", () => {
  323. const result = renderA11yString("\\liminf");
  324. expect(result).toMatchInlineSnapshot(`"l, i, m, i, n, f"`);
  325. });
  326. test("\\argmin", () => {
  327. const result = renderA11yString("\\argmin");
  328. expect(result).toMatchInlineSnapshot(`"a, r, g, m, i, n"`);
  329. });
  330. });
  331. describe("overline", () => {
  332. test("\\overline", () => {
  333. const result = renderA11yString("\\overline{1+2}");
  334. expect(result).toMatchInlineSnapshot(
  335. `"start overline, 1, plus, 2, end overline"`,
  336. );
  337. });
  338. });
  339. describe("phantom", () => {
  340. test("\\phantom", () => {
  341. const result = renderA11yString("1+\\phantom{2}");
  342. expect(result).toMatchInlineSnapshot(`"1, plus, empty space"`);
  343. });
  344. });
  345. describe("raisebox", () => {
  346. test("\\raisebox", () => {
  347. const result = renderA11yString("x+\\raisebox{1em}{y}");
  348. expect(result).toMatchInlineSnapshot(`"x, plus, y"`);
  349. });
  350. });
  351. describe("relations", () => {
  352. test("1 \\neq 2", () => {
  353. const result = renderA11yString("1 \\neq 2");
  354. expect(result).toMatchInlineSnapshot(`"1, does not equal, 2"`);
  355. });
  356. test("1 \\ne 2", () => {
  357. const result = renderA11yString("1 \\ne 2");
  358. expect(result).toMatchInlineSnapshot(`"1, does not equal, 2"`);
  359. });
  360. test("1 \\geq 2", () => {
  361. const result = renderA11yString("1 \\geq 2");
  362. expect(result).toMatchInlineSnapshot(
  363. `"1, is greater than or equal to, 2"`,
  364. );
  365. });
  366. test("1 \\ge 2", () => {
  367. const result = renderA11yString("1 \\ge 2");
  368. expect(result).toMatchInlineSnapshot(
  369. `"1, is greater than or equal to, 2"`,
  370. );
  371. });
  372. test("1 \\leq 2", () => {
  373. const result = renderA11yString("1 \\leq 3");
  374. expect(result).toMatchInlineSnapshot(
  375. `"1, is less than or equal to, 3"`,
  376. );
  377. });
  378. test("1 \\le 2", () => {
  379. const result = renderA11yString("1 \\le 3");
  380. expect(result).toMatchInlineSnapshot(
  381. `"1, is less than or equal to, 3"`,
  382. );
  383. });
  384. });
  385. describe("rule", () => {
  386. test("\\rule", () => {
  387. const result = renderA11yString("\\rule{1em}{1em}");
  388. expect(result).toMatchInlineSnapshot(`"rectangle"`);
  389. });
  390. });
  391. describe("smash", () => {
  392. test("1 + \\smash{2}", () => {
  393. const result = renderA11yString("1 + \\smash{2}");
  394. expect(result).toMatchInlineSnapshot(`"1, plus, 2"`);
  395. });
  396. });
  397. describe("sqrt", () => {
  398. test("square root", () => {
  399. const result = renderA11yString("\\sqrt{x + 1}");
  400. expect(result).toMatchInlineSnapshot(
  401. `"square root of, x, plus, 1, end square root"`,
  402. );
  403. });
  404. test("nest square root", () => {
  405. const result = renderA11yString("\\sqrt{x + \\sqrt{y}}");
  406. // TODO: this sounds ambiguous as well... we should probably say "start square root"
  407. expect(result).toMatchInlineSnapshot(
  408. `"square root of, x, plus, square root of, y, end square root, end square root"`,
  409. );
  410. });
  411. test("cube root", () => {
  412. const result = renderA11yString("\\sqrt[3]{x + 1}");
  413. expect(result).toMatchInlineSnapshot(
  414. `"cube root of, x, plus, 1, end cube root"`,
  415. );
  416. });
  417. test("nth root", () => {
  418. const result = renderA11yString("\\sqrt[n]{x + 1}");
  419. expect(result).toMatchInlineSnapshot(
  420. `"root, start index, n, end index"`,
  421. );
  422. });
  423. });
  424. describe("sizing", () => {
  425. test("\\Huge is ignored", () => {
  426. const result = renderA11yString("\\Huge{a+b}");
  427. expect(result).toMatchInlineSnapshot(`"a, plus, b"`);
  428. });
  429. test("\\small is ignored", () => {
  430. const result = renderA11yString("\\small{a+b}");
  431. expect(result).toMatchInlineSnapshot(`"a, plus, b"`);
  432. });
  433. // We don't need to test all sizing commands since all style
  434. // nodes are treated in the same way.
  435. });
  436. describe("styling", () => {
  437. test("\\displaystyle is ignored", () => {
  438. const result = renderA11yString("\\displaystyle{a+b}");
  439. expect(result).toMatchInlineSnapshot(`"a, plus, b"`);
  440. });
  441. test("\\textstyle is ignored", () => {
  442. const result = renderA11yString("\\textstyle{a+b}");
  443. expect(result).toMatchInlineSnapshot(`"a, plus, b"`);
  444. });
  445. // We don't need to test all styling commands since all style
  446. // nodes are treated in the same way.
  447. });
  448. describe("text", () => {
  449. test("\\text", () => {
  450. const result = renderA11yString("\\text{hello}");
  451. expect(result).toMatchInlineSnapshot(
  452. `"start text, h, e, l, l, o, end text"`,
  453. );
  454. });
  455. test("\\textbf", () => {
  456. const result = renderA11yString("\\textbf{hello}");
  457. expect(result).toMatchInlineSnapshot(
  458. `"start bold text, h, e, l, l, o, end bold text"`,
  459. );
  460. });
  461. });
  462. describe("underline", () => {
  463. test("\\underline", () => {
  464. const result = renderA11yString("\\underline{1+2}");
  465. expect(result).toMatchInlineSnapshot(
  466. `"start underline, 1, plus, 2, end underline"`,
  467. );
  468. });
  469. });
  470. describe("vcenter", () => {
  471. test("\\vcenter", () => {
  472. const result = renderA11yString("x+\\vcenter{y}");
  473. expect(result).toMatchInlineSnapshot(`"x, plus, y"`);
  474. });
  475. });
  476. describe("verb", () => {
  477. test("\\verb", () => {
  478. const result = renderA11yString("\\verb|hello|");
  479. expect(result).toMatchInlineSnapshot(
  480. `"start verbatim, hello, end verbatim"`,
  481. );
  482. });
  483. });
  484. });