katex-spec.js 125 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518
  1. /* eslint max-len:0 */
  2. /* global expect: false */
  3. /* global it: false */
  4. /* global describe: false */
  5. /* global beforeAll: false */
  6. import buildMathML from "../src/buildMathML";
  7. import buildTree from "../src/buildTree";
  8. import katex from "../katex";
  9. import parseTree from "../src/parseTree";
  10. import Options from "../src/Options";
  11. import Settings from "../src/Settings";
  12. import Style from "../src/Style";
  13. import {
  14. strictSettings, nonstrictSettings, r,
  15. getBuilt, getParsed, stripPositions,
  16. } from "./helpers";
  17. const defaultOptions = new Options({
  18. style: Style.TEXT,
  19. size: 5,
  20. maxSize: Infinity,
  21. });
  22. describe("A parser", function() {
  23. it("should not fail on an empty string", function() {
  24. expect``.toParse(strictSettings);
  25. });
  26. it("should ignore whitespace", function() {
  27. expect` x y `.toParseLike("xy", strictSettings);
  28. });
  29. it("should ignore whitespace in atom", function() {
  30. expect` x ^ y `.toParseLike("x^y", strictSettings);
  31. });
  32. });
  33. describe("An ord parser", function() {
  34. const expression = "1234|/@.\"`abcdefgzABCDEFGZ";
  35. it("should not fail", function() {
  36. expect(expression).toParse();
  37. });
  38. it("should build a list of ords", function() {
  39. const parse = getParsed(expression);
  40. for (let i = 0; i < parse.length; i++) {
  41. const group = parse[i];
  42. expect(group.type).toMatch("ord");
  43. }
  44. });
  45. it("should parse the right number of ords", function() {
  46. const parse = getParsed(expression);
  47. expect(parse).toHaveLength(expression.length);
  48. });
  49. });
  50. describe("A bin parser", function() {
  51. const expression = r`+-*\cdot\pm\div`;
  52. it("should not fail", function() {
  53. expect(expression).toParse();
  54. });
  55. it("should build a list of bins", function() {
  56. const parse = getParsed(expression);
  57. for (let i = 0; i < parse.length; i++) {
  58. const group = parse[i];
  59. expect(group.type).toEqual("atom");
  60. expect(group.family).toEqual("bin");
  61. }
  62. });
  63. });
  64. describe("A rel parser", function() {
  65. const expression = r`=<>\leq\geq\neq\nleq\ngeq\cong`;
  66. const notExpression = r`\not=\not<\not>\not\leq\not\geq\not\in`;
  67. it("should not fail", function() {
  68. expect(expression).toParse();
  69. expect(notExpression).toParse();
  70. });
  71. it("should build a list of rels", function() {
  72. const parse = getParsed(expression);
  73. for (let i = 0; i < parse.length; i++) {
  74. let group = parse[i];
  75. if (group.type === "htmlmathml") {
  76. expect(group.html).toHaveLength(1);
  77. group = group.html[0];
  78. }
  79. if (group.type === "mclass") {
  80. expect(group.mclass).toEqual("mrel");
  81. } else {
  82. expect(group.type).toEqual("atom");
  83. expect(group.family).toEqual("rel");
  84. }
  85. }
  86. });
  87. });
  88. describe("A punct parser", function() {
  89. const expression = ",;";
  90. it("should not fail", function() {
  91. expect(expression).toParse(strictSettings);
  92. });
  93. it("should build a list of puncts", function() {
  94. const parse = getParsed(expression);
  95. for (let i = 0; i < parse.length; i++) {
  96. const group = parse[i];
  97. expect(group.type).toEqual("atom");
  98. expect(group.family).toEqual("punct");
  99. }
  100. });
  101. });
  102. describe("An open parser", function() {
  103. const expression = "([";
  104. it("should not fail", function() {
  105. expect(expression).toParse();
  106. });
  107. it("should build a list of opens", function() {
  108. const parse = getParsed(expression);
  109. for (let i = 0; i < parse.length; i++) {
  110. const group = parse[i];
  111. expect(group.type).toEqual("atom");
  112. expect(group.family).toEqual("open");
  113. }
  114. });
  115. });
  116. describe("A close parser", function() {
  117. const expression = ")]?!";
  118. it("should not fail", function() {
  119. expect(expression).toParse();
  120. });
  121. it("should build a list of closes", function() {
  122. const parse = getParsed(expression);
  123. for (let i = 0; i < parse.length; i++) {
  124. const group = parse[i];
  125. expect(group.type).toEqual("atom");
  126. expect(group.family).toEqual("close");
  127. }
  128. });
  129. });
  130. describe("A \\KaTeX parser", function() {
  131. it("should not fail", function() {
  132. expect`\KaTeX`.toParse();
  133. });
  134. });
  135. describe("A subscript and superscript parser", function() {
  136. it("should not fail on superscripts", function() {
  137. expect`x^2`.toParse();
  138. });
  139. it("should not fail on subscripts", function() {
  140. expect`x_3`.toParse();
  141. });
  142. it("should not fail on both subscripts and superscripts", function() {
  143. expect`x^2_3`.toParse();
  144. expect`x_2^3`.toParse();
  145. });
  146. it("should not fail when there is no nucleus", function() {
  147. expect`^3`.toParse();
  148. expect`^3+`.toParse();
  149. expect`_2`.toParse();
  150. expect`^3_2`.toParse();
  151. expect`_2^3`.toParse();
  152. });
  153. it("should produce supsubs for superscript", function() {
  154. const parse = getParsed`x^2`[0];
  155. expect(parse.type).toBe("supsub");
  156. expect(parse.base).toBeDefined();
  157. expect(parse.sup).toBeDefined();
  158. expect(parse.sub).toBeUndefined();
  159. });
  160. it("should produce supsubs for subscript", function() {
  161. const parse = getParsed`x_3`[0];
  162. expect(parse.type).toBe("supsub");
  163. expect(parse.base).toBeDefined();
  164. expect(parse.sub).toBeDefined();
  165. expect(parse.sup).toBeUndefined();
  166. });
  167. it("should produce supsubs for ^_", function() {
  168. const parse = getParsed`x^2_3`[0];
  169. expect(parse.type).toBe("supsub");
  170. expect(parse.base).toBeDefined();
  171. expect(parse.sup).toBeDefined();
  172. expect(parse.sub).toBeDefined();
  173. });
  174. it("should produce supsubs for _^", function() {
  175. const parse = getParsed`x_3^2`[0];
  176. expect(parse.type).toBe("supsub");
  177. expect(parse.base).toBeDefined();
  178. expect(parse.sup).toBeDefined();
  179. expect(parse.sub).toBeDefined();
  180. });
  181. it("should produce the same thing regardless of order", function() {
  182. expect`x^2_3`.toParseLike`x_3^2`;
  183. });
  184. it("should not parse double subscripts or superscripts", function() {
  185. expect`x^x^x`.not.toParse();
  186. expect`x_x_x`.not.toParse();
  187. expect`x_x^x_x`.not.toParse();
  188. expect`x_x^x^x`.not.toParse();
  189. expect`x^x_x_x`.not.toParse();
  190. expect`x^x_x^x`.not.toParse();
  191. });
  192. it("should work correctly with {}s", function() {
  193. expect`x^{2+3}`.toParse();
  194. expect`x_{3-2}`.toParse();
  195. expect`x^{2+3}_3`.toParse();
  196. expect`x^2_{3-2}`.toParse();
  197. expect`x^{2+3}_{3-2}`.toParse();
  198. expect`x_{3-2}^{2+3}`.toParse();
  199. expect`x_3^{2+3}`.toParse();
  200. expect`x_{3-2}^2`.toParse();
  201. });
  202. it("should work with nested super/subscripts", function() {
  203. expect`x^{x^x}`.toParse();
  204. expect`x^{x_x}`.toParse();
  205. expect`x_{x^x}`.toParse();
  206. expect`x_{x_x}`.toParse();
  207. });
  208. });
  209. describe("A subscript and superscript tree-builder", function() {
  210. it("should not fail when there is no nucleus", function() {
  211. expect`^3`.toBuild();
  212. expect`_2`.toBuild();
  213. expect`^3_2`.toBuild();
  214. expect`_2^3`.toBuild();
  215. });
  216. });
  217. describe("A parser with limit controls", function() {
  218. it("should fail when the limit control is not preceded by an op node", function() {
  219. expect`3\nolimits_2^2`.not.toParse();
  220. expect`\sqrt\limits_2^2`.not.toParse();
  221. expect`45 +\nolimits 45`.not.toParse();
  222. });
  223. it("should parse when the limit control directly follows an op node", function() {
  224. expect`\int\limits_2^2 3`.toParse();
  225. expect`\sum\nolimits_3^4 4`.toParse();
  226. });
  227. it("should parse when the limit control is in the sup/sub area of an op node", function() {
  228. expect`\int_2^2\limits`.toParse();
  229. expect`\int^2\nolimits_2`.toParse();
  230. expect`\int_2\limits^2`.toParse();
  231. });
  232. it("should allow multiple limit controls in the sup/sub area of an op node", function() {
  233. expect`\int_2\nolimits^2\limits 3`.toParse();
  234. expect`\int\nolimits\limits_2^2`.toParse();
  235. expect`\int\limits\limits\limits_2^2`.toParse();
  236. });
  237. it("should have the rightmost limit control determine the limits property " +
  238. "of the preceding op node", function() {
  239. let parsedInput = getParsed`\int\nolimits\limits_2^2`;
  240. expect(parsedInput[0].base.limits).toBe(true);
  241. parsedInput = getParsed`\int\limits_2\nolimits^2`;
  242. expect(parsedInput[0].base.limits).toBe(false);
  243. });
  244. });
  245. describe("A group parser", function() {
  246. it("should not fail", function() {
  247. expect`{xy}`.toParse();
  248. });
  249. it("should produce a single ord", function() {
  250. const parse = getParsed`{xy}`;
  251. expect(parse).toHaveLength(1);
  252. const ord = parse[0];
  253. expect(ord.type).toMatch("ord");
  254. expect(ord.body).toBeTruthy();
  255. });
  256. });
  257. describe("A \\begingroup...\\endgroup parser", function() {
  258. it("should not fail", function() {
  259. expect`\begingroup xy \endgroup`.toParse();
  260. });
  261. it("should fail when it is mismatched", function() {
  262. expect`\begingroup xy`.not.toParse();
  263. expect`\begingroup xy }`.not.toParse();
  264. });
  265. it("should produce a semi-simple group", function() {
  266. const parse = getParsed`\begingroup xy \endgroup`;
  267. expect(parse).toHaveLength(1);
  268. const ord = parse[0];
  269. expect(ord.type).toMatch("ord");
  270. expect(ord.body).toBeTruthy();
  271. expect(ord.semisimple).toBeTruthy();
  272. });
  273. it("should not affect spacing in math mode", function() {
  274. expect`\begingroup x+ \endgroup y`.toBuildLike`x+y`;
  275. });
  276. });
  277. describe("An implicit group parser", function() {
  278. it("should not fail", function() {
  279. expect`\Large x`.toParse();
  280. expect`abc {abc \Large xyz} abc`.toParse();
  281. });
  282. it("should produce a single object", function() {
  283. const parse = getParsed`\Large abc`;
  284. expect(parse).toHaveLength(1);
  285. const sizing = parse[0];
  286. expect(sizing.type).toEqual("sizing");
  287. expect(sizing.body).toBeTruthy();
  288. expect(sizing.size).toBeDefined();
  289. });
  290. it("should apply only after the function", function() {
  291. const parse = getParsed`a \Large abc`;
  292. expect(parse).toHaveLength(2);
  293. const sizing = parse[1];
  294. expect(sizing.type).toEqual("sizing");
  295. expect(sizing.body).toHaveLength(3);
  296. });
  297. it("should stop at the ends of groups", function() {
  298. const parse = getParsed`a { b \Large c } d`;
  299. const group = parse[1];
  300. const sizing = group.body[1];
  301. expect(sizing.type).toEqual("sizing");
  302. expect(sizing.body).toHaveLength(1);
  303. });
  304. describe("within optional groups", () => {
  305. it("should work with sizing commands: \\sqrt[\\small 3]{x}", () => {
  306. const tree = stripPositions(getParsed`\sqrt[\small 3]{x}`);
  307. expect(tree).toMatchSnapshot();
  308. });
  309. it("should work with \\color: \\sqrt[\\color{red} 3]{x}", () => {
  310. const tree = stripPositions(getParsed`\sqrt[\color{red} 3]{x}`);
  311. expect(tree).toMatchSnapshot();
  312. });
  313. it("should work style commands \\sqrt[\\textstyle 3]{x}", () => {
  314. const tree = stripPositions(getParsed`\sqrt[\textstyle 3]{x}`);
  315. expect(tree).toMatchSnapshot();
  316. });
  317. it("should work with old font functions: \\sqrt[\\tt 3]{x}", () => {
  318. const tree = stripPositions(getParsed`\sqrt[\tt 3]{x}`);
  319. expect(tree).toMatchSnapshot();
  320. });
  321. });
  322. });
  323. describe("A function parser", function() {
  324. it("should parse no argument functions", function() {
  325. expect`\div`.toParse();
  326. });
  327. it("should parse 1 argument functions", function() {
  328. expect`\blue x`.toParse();
  329. });
  330. it("should parse 2 argument functions", function() {
  331. expect`\frac 1 2`.toParse();
  332. });
  333. it("should not parse 1 argument functions with no arguments", function() {
  334. expect`\blue`.not.toParse();
  335. });
  336. it("should not parse 2 argument functions with 0 or 1 arguments", function() {
  337. expect`\frac`.not.toParse();
  338. expect`\frac 1`.not.toParse();
  339. });
  340. it("should not parse a function with text right after it", function() {
  341. expect`\redx`.not.toParse();
  342. });
  343. it("should parse a function with a number right after it", function() {
  344. expect`\frac12`.toParse();
  345. });
  346. it("should parse some functions with text right after it", function() {
  347. expect`\;x`.toParse();
  348. });
  349. });
  350. describe("A frac parser", function() {
  351. const expression = r`\frac{x}{y}`;
  352. const dfracExpression = r`\dfrac{x}{y}`;
  353. const tfracExpression = r`\tfrac{x}{y}`;
  354. const cfracExpression = r`\cfrac{x}{y}`;
  355. const genfrac1 = r`\genfrac ( ] {0.06em}{0}{a}{b+c}`;
  356. const genfrac2 = r`\genfrac ( ] {0.8pt}{}{a}{b+c}`;
  357. it("should not fail", function() {
  358. expect(expression).toParse();
  359. });
  360. it("should produce a frac", function() {
  361. const parse = getParsed(expression)[0];
  362. expect(parse.type).toEqual("genfrac");
  363. expect(parse.numer).toBeDefined();
  364. expect(parse.denom).toBeDefined();
  365. });
  366. it("should also parse cfrac, dfrac, tfrac, and genfrac", function() {
  367. expect(cfracExpression).toParse();
  368. expect(dfracExpression).toParse();
  369. expect(tfracExpression).toParse();
  370. expect(genfrac1).toParse();
  371. expect(genfrac2).toParse();
  372. });
  373. it("should parse cfrac, dfrac, tfrac, and genfrac as fracs", function() {
  374. const dfracParse = getParsed(dfracExpression)[0];
  375. expect(dfracParse.type).toEqual("genfrac");
  376. expect(dfracParse.numer).toBeDefined();
  377. expect(dfracParse.denom).toBeDefined();
  378. const tfracParse = getParsed(tfracExpression)[0];
  379. expect(tfracParse.type).toEqual("genfrac");
  380. expect(tfracParse.numer).toBeDefined();
  381. expect(tfracParse.denom).toBeDefined();
  382. const cfracParse = getParsed(cfracExpression)[0];
  383. expect(cfracParse.type).toEqual("genfrac");
  384. expect(cfracParse.numer).toBeDefined();
  385. expect(cfracParse.denom).toBeDefined();
  386. const genfracParse = getParsed(genfrac1)[0];
  387. expect(genfracParse.type).toEqual("genfrac");
  388. expect(genfracParse.numer).toBeDefined();
  389. expect(genfracParse.denom).toBeDefined();
  390. expect(genfracParse.leftDelim).toBeDefined();
  391. expect(genfracParse.rightDelim).toBeDefined();
  392. });
  393. it("should fail, given math as a line thickness to genfrac", function() {
  394. const badGenFrac = "\\genfrac ( ] {b+c}{0}{a}{b+c}";
  395. expect(badGenFrac).not.toParse();
  396. });
  397. it("should fail if genfrac is given less than 6 arguments", function() {
  398. const badGenFrac = "\\genfrac ( ] {0.06em}{0}{a}";
  399. expect(badGenFrac).not.toParse();
  400. });
  401. it("should parse atop", function() {
  402. const parse = getParsed`x \atop y`[0];
  403. expect(parse.type).toEqual("genfrac");
  404. expect(parse.numer).toBeDefined();
  405. expect(parse.denom).toBeDefined();
  406. expect(parse.hasBarLine).toEqual(false);
  407. });
  408. });
  409. describe("An over/brace/brack parser", function() {
  410. const simpleOver = r`1 \over x`;
  411. const complexOver = r`1+2i \over 3+4i`;
  412. const braceFrac = r`a+b \brace c+d`;
  413. const brackFrac = r`a+b \brack c+d`;
  414. it("should not fail", function() {
  415. expect(simpleOver).toParse();
  416. expect(complexOver).toParse();
  417. expect(braceFrac).toParse();
  418. expect(brackFrac).toParse();
  419. });
  420. it("should produce a frac", function() {
  421. let parse;
  422. parse = getParsed(simpleOver)[0];
  423. expect(parse.type).toEqual("genfrac");
  424. expect(parse.numer).toBeDefined();
  425. expect(parse.denom).toBeDefined();
  426. parse = getParsed(complexOver)[0];
  427. expect(parse.type).toEqual("genfrac");
  428. expect(parse.numer).toBeDefined();
  429. expect(parse.denom).toBeDefined();
  430. const parseBraceFrac = getParsed(braceFrac)[0];
  431. expect(parseBraceFrac.type).toEqual("genfrac");
  432. expect(parseBraceFrac.numer).toBeDefined();
  433. expect(parseBraceFrac.denom).toBeDefined();
  434. expect(parseBraceFrac.leftDelim).toBeDefined();
  435. expect(parseBraceFrac.rightDelim).toBeDefined();
  436. const parseBrackFrac = getParsed(brackFrac)[0];
  437. expect(parseBrackFrac.type).toEqual("genfrac");
  438. expect(parseBrackFrac.numer).toBeDefined();
  439. expect(parseBrackFrac.denom).toBeDefined();
  440. expect(parseBrackFrac.leftDelim).toBeDefined();
  441. expect(parseBrackFrac.rightDelim).toBeDefined();
  442. });
  443. it("should create a numerator from the atoms before \\over", function() {
  444. const parse = getParsed(complexOver)[0];
  445. const numer = parse.numer;
  446. expect(numer.body).toHaveLength(4);
  447. });
  448. it("should create a demonimator from the atoms after \\over", function() {
  449. const parse = getParsed(complexOver)[0];
  450. const denom = parse.numer;
  451. expect(denom.body).toHaveLength(4);
  452. });
  453. it("should handle empty numerators", function() {
  454. const emptyNumerator = r`\over x`;
  455. const parse = getParsed(emptyNumerator)[0];
  456. expect(parse.type).toEqual("genfrac");
  457. expect(parse.numer).toBeDefined();
  458. expect(parse.denom).toBeDefined();
  459. });
  460. it("should handle empty denominators", function() {
  461. const emptyDenominator = r`1 \over`;
  462. const parse = getParsed(emptyDenominator)[0];
  463. expect(parse.type).toEqual("genfrac");
  464. expect(parse.numer).toBeDefined();
  465. expect(parse.denom).toBeDefined();
  466. });
  467. it("should handle \\displaystyle correctly", function() {
  468. const displaystyleExpression = r`\displaystyle 1 \over 2`;
  469. const parse = getParsed(displaystyleExpression)[0];
  470. expect(parse.type).toEqual("genfrac");
  471. expect(parse.numer.body[0].type).toEqual("styling");
  472. expect(parse.denom).toBeDefined();
  473. });
  474. it("should handle \\textstyle correctly", function() {
  475. expect`\textstyle 1 \over 2`.toParseLike`\frac{\textstyle 1}{2}`;
  476. expect`{\textstyle 1} \over 2`.toParseLike`\frac{\textstyle 1}{2}`;
  477. });
  478. it("should handle nested factions", function() {
  479. const nestedOverExpression = r`{1 \over 2} \over 3`;
  480. const parse = getParsed(nestedOverExpression)[0];
  481. expect(parse.type).toEqual("genfrac");
  482. expect(parse.numer.body[0].type).toEqual("genfrac");
  483. expect(parse.numer.body[0].numer.body[0].text).toEqual("1");
  484. expect(parse.numer.body[0].denom.body[0].text).toEqual("2");
  485. expect(parse.denom).toBeDefined();
  486. expect(parse.denom.body[0].text).toEqual("3");
  487. });
  488. it("should fail with multiple overs in the same group", function() {
  489. const badMultipleOvers = r`1 \over 2 + 3 \over 4`;
  490. expect(badMultipleOvers).not.toParse();
  491. const badOverChoose = r`1 \over 2 \choose 3`;
  492. expect(badOverChoose).not.toParse();
  493. });
  494. });
  495. describe("A genfrac builder", function() {
  496. it("should not fail", function() {
  497. expect("\\frac{x}{y}").toBuild();
  498. expect("\\dfrac{x}{y}").toBuild();
  499. expect("\\tfrac{x}{y}").toBuild();
  500. expect("\\cfrac{x}{y}").toBuild();
  501. expect("\\genfrac ( ] {0.06em}{0}{a}{b+c}").toBuild();
  502. expect("\\genfrac ( ] {0.8pt}{}{a}{b+c}").toBuild();
  503. });
  504. });
  505. describe("A infix builder", function() {
  506. it("should not fail", function() {
  507. expect("a \\over b").toBuild();
  508. expect("a \\atop b").toBuild();
  509. expect("a \\choose b").toBuild();
  510. expect("a \\brace b").toBuild();
  511. expect("a \\brack b").toBuild();
  512. });
  513. });
  514. describe("A sizing parser", function() {
  515. const sizeExpression = r`\Huge{x}\small{x}`;
  516. it("should not fail", function() {
  517. expect(sizeExpression).toParse();
  518. });
  519. it("should produce a sizing node", function() {
  520. const parse = getParsed(sizeExpression)[0];
  521. expect(parse.type).toEqual("sizing");
  522. expect(parse.size).toBeDefined();
  523. expect(parse.body).toBeDefined();
  524. });
  525. });
  526. describe("A text parser", function() {
  527. const textExpression = r`\text{a b}`;
  528. const noBraceTextExpression = r`\text x`;
  529. const nestedTextExpression =
  530. r`\text{a {b} \blue{c} \textcolor{#fff}{x} \llap{x}}`;
  531. const spaceTextExpression = r`\text{ a \ }`;
  532. const leadingSpaceTextExpression = r`\text {moo}`;
  533. const badTextExpression = r`\text{a b%}`;
  534. const badFunctionExpression = r`\text{\sqrt{x}}`;
  535. const mathTokenAfterText = r`\text{sin}^2`;
  536. it("should not fail", function() {
  537. expect(textExpression).toParse();
  538. });
  539. it("should produce a text", function() {
  540. const parse = getParsed(textExpression)[0];
  541. expect(parse.type).toEqual("text");
  542. expect(parse.body).toBeDefined();
  543. });
  544. it("should produce textords instead of mathords", function() {
  545. const parse = getParsed(textExpression)[0];
  546. const group = parse.body;
  547. expect(group[0].type).toEqual("textord");
  548. });
  549. it("should not parse bad text", function() {
  550. expect(badTextExpression).not.toParse();
  551. });
  552. it("should not parse bad functions inside text", function() {
  553. expect(badFunctionExpression).not.toParse();
  554. });
  555. it("should parse text with no braces around it", function() {
  556. expect(noBraceTextExpression).toParse();
  557. });
  558. it("should parse nested expressions", function() {
  559. expect(nestedTextExpression).toParse();
  560. });
  561. it("should contract spaces", function() {
  562. const parse = getParsed(spaceTextExpression)[0];
  563. const group = parse.body;
  564. expect(group[0].type).toEqual("spacing");
  565. expect(group[1].type).toEqual("textord");
  566. expect(group[2].type).toEqual("spacing");
  567. expect(group[3].type).toEqual("spacing");
  568. });
  569. it("should accept math mode tokens after its argument", function() {
  570. expect(mathTokenAfterText).toParse();
  571. });
  572. it("should ignore a space before the text group", function() {
  573. const parse = getParsed(leadingSpaceTextExpression)[0];
  574. // [m, o, o]
  575. expect(parse.body).toHaveLength(3);
  576. expect(parse.body.map(n => n.text).join("")).toBe("moo");
  577. });
  578. it("should parse math within text group", function() {
  579. expect`\text{graph: $y = mx + b$}`.toParse(strictSettings);
  580. expect`\text{graph: \(y = mx + b\)}`.toParse(strictSettings);
  581. });
  582. it("should parse math within text within math within text", function() {
  583. expect`\text{hello $x + \text{world $y$} + z$}`.toParse(strictSettings);
  584. expect`\text{hello \(x + \text{world $y$} + z\)}`.toParse(strictSettings);
  585. expect`\text{hello $x + \text{world \(y\)} + z$}`.toParse(strictSettings);
  586. expect`\text{hello \(x + \text{world \(y\)} + z\)}`.toParse(strictSettings);
  587. });
  588. it("should forbid \\( within math mode", function() {
  589. expect`\(`.not.toParse();
  590. expect`\text{$\(x\)$}`.not.toParse();
  591. });
  592. it("should forbid $ within math mode", function() {
  593. expect`$x$`.not.toParse();
  594. expect`\text{\($x$\)}`.not.toParse();
  595. });
  596. it("should detect unbalanced \\)", function() {
  597. expect`\)`.not.toParse();
  598. expect`\text{\)}`.not.toParse();
  599. });
  600. it("should detect unbalanced $", function() {
  601. expect`$`.not.toParse();
  602. expect`\text{$}`.not.toParse();
  603. });
  604. it("should not mix $ and \\(..\\)", function() {
  605. expect`\text{$x\)}`.not.toParse();
  606. expect`\text{\(x$}`.not.toParse();
  607. });
  608. it("should parse spacing functions", function() {
  609. expect`a b\, \; \! \: \> ~ \thinspace \medspace \quad \ `.toBuild();
  610. expect`\enspace \thickspace \qquad \space \nobreakspace`.toBuild();
  611. });
  612. it("should omit spaces after commands", function() {
  613. expect`\text{\textellipsis !}`.toParseLike`\text{\textellipsis!}`;
  614. });
  615. });
  616. describe("A texvc builder", function() {
  617. it("should not fail", function() {
  618. expect("\\lang\\N\\darr\\R\\dArr\\Z\\Darr\\alef\\rang").toBuild();
  619. expect("\\alefsym\\uarr\\Alpha\\uArr\\Beta\\Uarr\\Chi").toBuild();
  620. expect("\\clubs\\diamonds\\hearts\\spades\\cnums\\Complex").toBuild();
  621. expect("\\Dagger\\empty\\harr\\Epsilon\\hArr\\Eta\\Harr\\exist").toBuild();
  622. expect("\\image\\larr\\infin\\lArr\\Iota\\Larr\\isin\\Kappa").toBuild();
  623. expect("\\Mu\\lrarr\\natnums\\lrArr\\Nu\\Lrarr\\Omicron").toBuild();
  624. expect("\\real\\rarr\\plusmn\\rArr\\reals\\Rarr\\Reals\\Rho").toBuild();
  625. expect("\\text{\\sect}\\sdot\\sub\\sube\\supe").toBuild();
  626. expect("\\Tau\\thetasym\\weierp\\Zeta").toBuild();
  627. });
  628. });
  629. describe("A color parser", function() {
  630. const colorExpression = r`\blue{x}`;
  631. const newColorExpression = r`\redA{x}`;
  632. const customColorExpression1 = r`\textcolor{#fA6}{x}`;
  633. const customColorExpression2 = r`\textcolor{#fA6fA6}{x}`;
  634. const customColorExpression3 = r`\textcolor{fA6fA6}{x}`;
  635. const badCustomColorExpression1 = r`\textcolor{bad-color}{x}`;
  636. const badCustomColorExpression2 = r`\textcolor{#fA6f}{x}`;
  637. const badCustomColorExpression3 = r`\textcolor{#gA6}{x}`;
  638. const oldColorExpression = r`\color{#fA6}xy`;
  639. it("should not fail", function() {
  640. expect(colorExpression).toParse();
  641. });
  642. it("should build a color node", function() {
  643. const parse = getParsed(colorExpression)[0];
  644. expect(parse.type).toEqual("color");
  645. expect(parse.color).toBeDefined();
  646. expect(parse.body).toBeDefined();
  647. });
  648. it("should parse a custom color", function() {
  649. expect(customColorExpression1).toParse();
  650. expect(customColorExpression2).toParse();
  651. expect(customColorExpression3).toParse();
  652. });
  653. it("should correctly extract the custom color", function() {
  654. const parse1 = getParsed(customColorExpression1)[0];
  655. const parse2 = getParsed(customColorExpression2)[0];
  656. const parse3 = getParsed(customColorExpression3)[0];
  657. expect(parse1.color).toEqual("#fA6");
  658. expect(parse2.color).toEqual("#fA6fA6");
  659. expect(parse3.color).toEqual("#fA6fA6");
  660. });
  661. it("should not parse a bad custom color", function() {
  662. expect(badCustomColorExpression1).not.toParse();
  663. expect(badCustomColorExpression2).not.toParse();
  664. expect(badCustomColorExpression3).not.toParse();
  665. });
  666. it("should parse new colors from the branding guide", function() {
  667. expect(newColorExpression).toParse();
  668. });
  669. it("should have correct greediness", function() {
  670. expect`\textcolor{red}a`.toParse();
  671. expect`\textcolor{red}{\text{a}}`.toParse();
  672. expect`\textcolor{red}\text{a}`.not.toParse();
  673. expect`\textcolor{red}\frac12`.not.toParse();
  674. });
  675. it("should use one-argument \\color by default", function() {
  676. expect(oldColorExpression).toParseLike`\textcolor{#fA6}{xy}`;
  677. });
  678. it("should use one-argument \\color if requested", function() {
  679. expect(oldColorExpression).toParseLike(r`\textcolor{#fA6}{xy}`, {
  680. colorIsTextColor: false,
  681. });
  682. });
  683. it("should use two-argument \\color if requested", function() {
  684. expect(oldColorExpression).toParseLike(r`\textcolor{#fA6}{x}y`, {
  685. colorIsTextColor: true,
  686. });
  687. });
  688. it("should not define \\color in global context", function() {
  689. const macros = {};
  690. expect(oldColorExpression).toParseLike(r`\textcolor{#fA6}{x}y`, {
  691. colorIsTextColor: true,
  692. macros: macros,
  693. });
  694. expect(macros).toEqual({});
  695. });
  696. });
  697. describe("A tie parser", function() {
  698. const mathTie = "a~b";
  699. const textTie = r`\text{a~ b}`;
  700. it("should parse ties in math mode", function() {
  701. expect(mathTie).toParse();
  702. });
  703. it("should parse ties in text mode", function() {
  704. expect(textTie).toParse();
  705. });
  706. it("should produce spacing in math mode", function() {
  707. const parse = getParsed(mathTie);
  708. expect(parse[1].type).toEqual("spacing");
  709. });
  710. it("should produce spacing in text mode", function() {
  711. const text = getParsed(textTie)[0];
  712. const parse = text.body;
  713. expect(parse[1].type).toEqual("spacing");
  714. });
  715. it("should not contract with spaces in text mode", function() {
  716. const text = getParsed(textTie)[0];
  717. const parse = text.body;
  718. expect(parse[2].type).toEqual("spacing");
  719. });
  720. });
  721. describe("A delimiter sizing parser", function() {
  722. const normalDelim = r`\bigl |`;
  723. const notDelim = r`\bigl x`;
  724. const bigDelim = r`\Biggr \langle`;
  725. it("should parse normal delimiters", function() {
  726. expect(normalDelim).toParse();
  727. expect(bigDelim).toParse();
  728. });
  729. it("should not parse not-delimiters", function() {
  730. expect(notDelim).not.toParse();
  731. });
  732. it("should produce a delimsizing", function() {
  733. const parse = getParsed(normalDelim)[0];
  734. expect(parse.type).toEqual("delimsizing");
  735. });
  736. it("should produce the correct direction delimiter", function() {
  737. const leftParse = getParsed(normalDelim)[0];
  738. const rightParse = getParsed(bigDelim)[0];
  739. expect(leftParse.mclass).toEqual("mopen");
  740. expect(rightParse.mclass).toEqual("mclose");
  741. });
  742. it("should parse the correct size delimiter", function() {
  743. const smallParse = getParsed(normalDelim)[0];
  744. const bigParse = getParsed(bigDelim)[0];
  745. expect(smallParse.size).toEqual(1);
  746. expect(bigParse.size).toEqual(4);
  747. });
  748. });
  749. describe("An overline parser", function() {
  750. const overline = r`\overline{x}`;
  751. it("should not fail", function() {
  752. expect(overline).toParse();
  753. });
  754. it("should produce an overline", function() {
  755. const parse = getParsed(overline)[0];
  756. expect(parse.type).toEqual("overline");
  757. });
  758. });
  759. describe("An lap parser", function() {
  760. it("should not fail on a text argument", function() {
  761. expect`\rlap{\,/}{=}`.toParse();
  762. expect`\mathrlap{\,/}{=}`.toParse();
  763. expect`{=}\llap{/\,}`.toParse();
  764. expect`{=}\mathllap{/\,}`.toParse();
  765. expect`\sum_{\clap{ABCDEFG}}`.toParse();
  766. expect`\sum_{\mathclap{ABCDEFG}}`.toParse();
  767. });
  768. it("should not fail if math version is used", function() {
  769. expect`\mathrlap{\frac{a}{b}}{=}`.toParse();
  770. expect`{=}\mathllap{\frac{a}{b}}`.toParse();
  771. expect`\sum_{\mathclap{\frac{a}{b}}}`.toParse();
  772. });
  773. it("should fail on math if AMS version is used", function() {
  774. expect`\rlap{\frac{a}{b}}{=}`.not.toParse();
  775. expect`{=}\llap{\frac{a}{b}}`.not.toParse();
  776. expect`\sum_{\clap{\frac{a}{b}}}`.not.toParse();
  777. });
  778. it("should produce a lap", function() {
  779. const parse = getParsed`\mathrlap{\,/}`[0];
  780. expect(parse.type).toEqual("lap");
  781. });
  782. });
  783. describe("A rule parser", function() {
  784. const emRule = r`\rule{1em}{2em}`;
  785. const exRule = r`\rule{1ex}{2em}`;
  786. const badUnitRule = r`\rule{1au}{2em}`;
  787. const noNumberRule = r`\rule{1em}{em}`;
  788. const incompleteRule = r`\rule{1em}`;
  789. const hardNumberRule = r`\rule{ 01.24ex}{2.450 em }`;
  790. it("should not fail", function() {
  791. expect(emRule).toParse();
  792. expect(exRule).toParse();
  793. });
  794. it("should not parse invalid units", function() {
  795. expect(badUnitRule).not.toParse();
  796. expect(noNumberRule).not.toParse();
  797. });
  798. it("should not parse incomplete rules", function() {
  799. expect(incompleteRule).not.toParse();
  800. });
  801. it("should produce a rule", function() {
  802. const parse = getParsed(emRule)[0];
  803. expect(parse.type).toEqual("rule");
  804. });
  805. it("should list the correct units", function() {
  806. const emParse = getParsed(emRule)[0];
  807. const exParse = getParsed(exRule)[0];
  808. expect(emParse.width.unit).toEqual("em");
  809. expect(emParse.height.unit).toEqual("em");
  810. expect(exParse.width.unit).toEqual("ex");
  811. expect(exParse.height.unit).toEqual("em");
  812. });
  813. it("should parse the number correctly", function() {
  814. const hardNumberParse = getParsed(hardNumberRule)[0];
  815. expect(hardNumberParse.width.number).toBeCloseTo(1.24);
  816. expect(hardNumberParse.height.number).toBeCloseTo(2.45);
  817. });
  818. it("should parse negative sizes", function() {
  819. const parse = getParsed`\rule{-1em}{- 0.2em}`[0];
  820. expect(parse.width.number).toBeCloseTo(-1);
  821. expect(parse.height.number).toBeCloseTo(-0.2);
  822. });
  823. });
  824. describe("A kern parser", function() {
  825. const emKern = r`\kern{1em}`;
  826. const exKern = r`\kern{1ex}`;
  827. const muKern = r`\mkern{1mu}`;
  828. const abKern = r`a\kern{1em}b`;
  829. const badUnitRule = r`\kern{1au}`;
  830. const noNumberRule = r`\kern{em}`;
  831. it("should list the correct units", function() {
  832. const emParse = getParsed(emKern)[0];
  833. const exParse = getParsed(exKern)[0];
  834. const muParse = getParsed(muKern)[0];
  835. const abParse = getParsed(abKern)[1];
  836. expect(emParse.dimension.unit).toEqual("em");
  837. expect(exParse.dimension.unit).toEqual("ex");
  838. expect(muParse.dimension.unit).toEqual("mu");
  839. expect(abParse.dimension.unit).toEqual("em");
  840. });
  841. it("should not parse invalid units", function() {
  842. expect(badUnitRule).not.toParse();
  843. expect(noNumberRule).not.toParse();
  844. });
  845. it("should parse negative sizes", function() {
  846. const parse = getParsed`\kern{-1em}`[0];
  847. expect(parse.dimension.number).toBeCloseTo(-1);
  848. });
  849. it("should parse positive sizes", function() {
  850. const parse = getParsed`\kern{+1em}`[0];
  851. expect(parse.dimension.number).toBeCloseTo(1);
  852. });
  853. });
  854. describe("A non-braced kern parser", function() {
  855. const emKern = r`\kern1em`;
  856. const exKern = r`\kern 1 ex`;
  857. const muKern = r`\mkern 1mu`;
  858. const abKern1 = r`a\mkern1mub`;
  859. const abKern2 = r`a\mkern-1mub`;
  860. const abKern3 = r`a\mkern-1mu b`;
  861. const badUnitRule = r`\kern1au`;
  862. const noNumberRule = r`\kern em`;
  863. it("should list the correct units", function() {
  864. const emParse = getParsed(emKern)[0];
  865. const exParse = getParsed(exKern)[0];
  866. const muParse = getParsed(muKern)[0];
  867. const abParse1 = getParsed(abKern1)[1];
  868. const abParse2 = getParsed(abKern2)[1];
  869. const abParse3 = getParsed(abKern3)[1];
  870. expect(emParse.dimension.unit).toEqual("em");
  871. expect(exParse.dimension.unit).toEqual("ex");
  872. expect(muParse.dimension.unit).toEqual("mu");
  873. expect(abParse1.dimension.unit).toEqual("mu");
  874. expect(abParse2.dimension.unit).toEqual("mu");
  875. expect(abParse3.dimension.unit).toEqual("mu");
  876. });
  877. it("should parse elements on either side of a kern", function() {
  878. const abParse1 = getParsed(abKern1);
  879. const abParse2 = getParsed(abKern2);
  880. const abParse3 = getParsed(abKern3);
  881. expect(abParse1).toHaveLength(3);
  882. expect(abParse1[0].text).toEqual("a");
  883. expect(abParse1[2].text).toEqual("b");
  884. expect(abParse2).toHaveLength(3);
  885. expect(abParse2[0].text).toEqual("a");
  886. expect(abParse2[2].text).toEqual("b");
  887. expect(abParse3).toHaveLength(3);
  888. expect(abParse3[0].text).toEqual("a");
  889. expect(abParse3[2].text).toEqual("b");
  890. });
  891. it("should not parse invalid units", function() {
  892. expect(badUnitRule).not.toParse();
  893. expect(noNumberRule).not.toParse();
  894. });
  895. it("should parse negative sizes", function() {
  896. const parse = getParsed`\kern-1em`[0];
  897. expect(parse.dimension.number).toBeCloseTo(-1);
  898. });
  899. it("should parse positive sizes", function() {
  900. const parse = getParsed`\kern+1em`[0];
  901. expect(parse.dimension.number).toBeCloseTo(1);
  902. });
  903. it("should handle whitespace", function() {
  904. const abKern = "a\\mkern\t-\r1 \n mu\nb";
  905. const abParse = getParsed(abKern);
  906. expect(abParse).toHaveLength(3);
  907. expect(abParse[0].text).toEqual("a");
  908. expect(abParse[1].dimension.unit).toEqual("mu");
  909. expect(abParse[2].text).toEqual("b");
  910. });
  911. });
  912. describe("A left/right parser", function() {
  913. const normalLeftRight = r`\left( \dfrac{x}{y} \right)`;
  914. const emptyRight = r`\left( \dfrac{x}{y} \right.`;
  915. it("should not fail", function() {
  916. expect(normalLeftRight).toParse();
  917. });
  918. it("should produce a leftright", function() {
  919. const parse = getParsed(normalLeftRight)[0];
  920. expect(parse.type).toEqual("leftright");
  921. expect(parse.left).toEqual("(");
  922. expect(parse.right).toEqual(")");
  923. });
  924. it("should error when it is mismatched", function() {
  925. const unmatchedLeft = r`\left( \dfrac{x}{y}`;
  926. const unmatchedRight = r`\dfrac{x}{y} \right)`;
  927. expect(unmatchedLeft).not.toParse();
  928. expect(unmatchedRight).not.toParse();
  929. });
  930. it("should error when braces are mismatched", function() {
  931. const unmatched = r`{ \left( \dfrac{x}{y} } \right)`;
  932. expect(unmatched).not.toParse();
  933. });
  934. it("should error when non-delimiters are provided", function() {
  935. const nonDelimiter = r`\left$ \dfrac{x}{y} \right)`;
  936. expect(nonDelimiter).not.toParse();
  937. });
  938. it("should parse the empty '.' delimiter", function() {
  939. expect(emptyRight).toParse();
  940. });
  941. it("should parse the '.' delimiter with normal sizes", function() {
  942. const normalEmpty = r`\Bigl .`;
  943. expect(normalEmpty).toParse();
  944. });
  945. it("should handle \\middle", function() {
  946. const normalMiddle = r`\left( \dfrac{x}{y} \middle| \dfrac{y}{z} \right)`;
  947. expect(normalMiddle).toParse();
  948. });
  949. it("should handle multiple \\middles", function() {
  950. const multiMiddle = r`\left( \dfrac{x}{y} \middle| \dfrac{y}{z} \middle/ \dfrac{z}{q} \right)`;
  951. expect(multiMiddle).toParse();
  952. });
  953. it("should handle nested \\middles", function() {
  954. const nestedMiddle = r`\left( a^2 \middle| \left( b \middle/ c \right) \right)`;
  955. expect(nestedMiddle).toParse();
  956. });
  957. it("should error when \\middle is not in \\left...\\right", function() {
  958. const unmatchedMiddle = r`(\middle|\dfrac{x}{y})`;
  959. expect(unmatchedMiddle).not.toParse();
  960. });
  961. });
  962. describe("left/right builder", () => {
  963. const cases = [
  964. [r`\left\langle \right\rangle`, r`\left< \right>`],
  965. [r`\left\langle \right\rangle`, '\\left\u27e8 \\right\u27e9'],
  966. [r`\left\lparen \right\rparen`, r`\left( \right)`],
  967. ];
  968. for (const [actual, expected] of cases) {
  969. it(`should build "${actual}" like "${expected}"`, () => {
  970. expect(actual).toBuildLike(expected);
  971. });
  972. }
  973. });
  974. describe("A begin/end parser", function() {
  975. it("should parse a simple environment", function() {
  976. expect`\begin{matrix}a&b\\c&d\end{matrix}`.toParse();
  977. });
  978. it("should parse an environment with argument", function() {
  979. expect`\begin{array}{cc}a&b\\c&d\end{array}`.toParse();
  980. });
  981. it("should parse an environment with hlines", function() {
  982. expect`\begin{matrix}\hline a&b\\ \hline c&d\end{matrix}`.toParse();
  983. expect`\begin{matrix}\hdashline a&b\\ \hdashline c&d\end{matrix}`.toParse();
  984. });
  985. it("should forbid hlines outside array environment", () => {
  986. expect`\hline`.not.toParse();
  987. });
  988. it("should error when name is mismatched", function() {
  989. expect`\begin{matrix}a&b\\c&d\end{pmatrix}`.not.toParse();
  990. });
  991. it("should error when commands are mismatched", function() {
  992. expect`\begin{matrix}a&b\\c&d\right{pmatrix}`.not.toParse();
  993. });
  994. it("should error when end is missing", function() {
  995. expect`\begin{matrix}a&b\\c&d`.not.toParse();
  996. });
  997. it("should error when braces are mismatched", function() {
  998. expect`{\begin{matrix}a&b\\c&d}\end{matrix}`.not.toParse();
  999. });
  1000. it("should cooperate with infix notation", function() {
  1001. expect`\begin{matrix}0&1\over2&3\\4&5&6\end{matrix}`.toParse();
  1002. });
  1003. it("should nest", function() {
  1004. const m1 = r`\begin{pmatrix}1&2\\3&4\end{pmatrix}`;
  1005. const m2 = `\\begin{array}{rl}${m1}&0\\\\0&${m1}\\end{array}`;
  1006. expect(m2).toParse();
  1007. });
  1008. it("should allow \\cr as a line terminator", function() {
  1009. expect`\begin{matrix}a&b\cr c&d\end{matrix}`.toParse();
  1010. });
  1011. it("should eat a final newline", function() {
  1012. const m3 = getParsed`\begin{matrix}a&b\\ c&d \\ \end{matrix}`[0];
  1013. expect(m3.body).toHaveLength(2);
  1014. });
  1015. it("should grab \\arraystretch", function() {
  1016. const parse = getParsed`\def\arraystretch{1.5}\begin{matrix}a&b\\c&d\end{matrix}`;
  1017. expect(parse).toMatchSnapshot();
  1018. });
  1019. });
  1020. describe("A sqrt parser", function() {
  1021. const sqrt = r`\sqrt{x}`;
  1022. const missingGroup = r`\sqrt`;
  1023. it("should parse square roots", function() {
  1024. expect(sqrt).toParse();
  1025. });
  1026. it("should error when there is no group", function() {
  1027. expect(missingGroup).not.toParse();
  1028. });
  1029. it("should produce sqrts", function() {
  1030. const parse = getParsed(sqrt)[0];
  1031. expect(parse.type).toEqual("sqrt");
  1032. });
  1033. it("should build sized square roots", function() {
  1034. expect("\\Large\\sqrt[3]{x}").toBuild();
  1035. });
  1036. });
  1037. describe("A TeX-compliant parser", function() {
  1038. it("should work", function() {
  1039. expect`\frac 2 3`.toParse();
  1040. });
  1041. it("should fail if there are not enough arguments", function() {
  1042. const missingGroups = [
  1043. r`\frac{x}`,
  1044. r`\textcolor{#fff}`,
  1045. r`\rule{1em}`,
  1046. r`\llap`,
  1047. r`\bigl`,
  1048. r`\text`,
  1049. ];
  1050. for (let i = 0; i < missingGroups.length; i++) {
  1051. expect(missingGroups[i]).not.toParse();
  1052. }
  1053. });
  1054. it("should fail when there are missing sup/subscripts", function() {
  1055. expect`x^`.not.toParse();
  1056. expect`x_`.not.toParse();
  1057. });
  1058. it("should fail when arguments require arguments", function() {
  1059. const badArguments = [
  1060. r`\frac \frac x y z`,
  1061. r`\frac x \frac y z`,
  1062. r`\frac \sqrt x y`,
  1063. r`\frac x \sqrt y`,
  1064. r`\frac \mathllap x y`,
  1065. r`\frac x \mathllap y`,
  1066. // This actually doesn't work in real TeX, but it is suprisingly
  1067. // hard to get this to correctly work. So, we take hit of very small
  1068. // amounts of non-compatiblity in order for the rest of the tests to
  1069. // work
  1070. // r`\llap \frac x y`,
  1071. r`\mathllap \mathllap x`,
  1072. r`\sqrt \mathllap x`,
  1073. ];
  1074. for (let i = 0; i < badArguments.length; i++) {
  1075. expect(badArguments[i]).not.toParse();
  1076. }
  1077. });
  1078. it("should work when the arguments have braces", function() {
  1079. const goodArguments = [
  1080. r`\frac {\frac x y} z`,
  1081. r`\frac x {\frac y z}`,
  1082. r`\frac {\sqrt x} y`,
  1083. r`\frac x {\sqrt y}`,
  1084. r`\frac {\mathllap x} y`,
  1085. r`\frac x {\mathllap y}`,
  1086. r`\mathllap {\frac x y}`,
  1087. r`\mathllap {\mathllap x}`,
  1088. r`\sqrt {\mathllap x}`,
  1089. ];
  1090. for (let i = 0; i < goodArguments.length; i++) {
  1091. expect(goodArguments[i]).toParse();
  1092. }
  1093. });
  1094. it("should fail when sup/subscripts require arguments", function() {
  1095. const badSupSubscripts = [
  1096. r`x^\sqrt x`,
  1097. r`x^\mathllap x`,
  1098. r`x_\sqrt x`,
  1099. r`x_\mathllap x`,
  1100. ];
  1101. for (let i = 0; i < badSupSubscripts.length; i++) {
  1102. expect(badSupSubscripts[i]).not.toParse();
  1103. }
  1104. });
  1105. it("should work when sup/subscripts arguments have braces", function() {
  1106. const goodSupSubscripts = [
  1107. r`x^{\sqrt x}`,
  1108. r`x^{\mathllap x}`,
  1109. r`x_{\sqrt x}`,
  1110. r`x_{\mathllap x}`,
  1111. ];
  1112. for (let i = 0; i < goodSupSubscripts.length; i++) {
  1113. expect(goodSupSubscripts[i]).toParse();
  1114. }
  1115. });
  1116. it("should parse multiple primes correctly", function() {
  1117. expect`x''''`.toParse();
  1118. expect`x_2''`.toParse();
  1119. expect`x''_2`.toParse();
  1120. });
  1121. it("should fail when sup/subscripts are interspersed with arguments", function() {
  1122. expect`\sqrt^23`.not.toParse();
  1123. expect`\frac^234`.not.toParse();
  1124. expect`\frac2^34`.not.toParse();
  1125. });
  1126. it("should succeed when sup/subscripts come after whole functions", function() {
  1127. expect`\sqrt2^3`.toParse();
  1128. expect`\frac23^4`.toParse();
  1129. });
  1130. it("should succeed with a sqrt around a text/frac", function() {
  1131. expect`\sqrt \frac x y`.toParse();
  1132. expect`\sqrt \text x`.toParse();
  1133. expect`x^\frac x y`.toParse();
  1134. expect`x_\text x`.toParse();
  1135. });
  1136. it("should fail when arguments are \\left", function() {
  1137. const badLeftArguments = [
  1138. r`\frac \left( x \right) y`,
  1139. r`\frac x \left( y \right)`,
  1140. r`\mathllap \left( x \right)`,
  1141. r`\sqrt \left( x \right)`,
  1142. r`x^\left( x \right)`,
  1143. ];
  1144. for (let i = 0; i < badLeftArguments.length; i++) {
  1145. expect(badLeftArguments[i]).not.toParse();
  1146. }
  1147. });
  1148. it("should succeed when there are braces around the \\left/\\right", function() {
  1149. const goodLeftArguments = [
  1150. r`\frac {\left( x \right)} y`,
  1151. r`\frac x {\left( y \right)}`,
  1152. r`\mathllap {\left( x \right)}`,
  1153. r`\sqrt {\left( x \right)}`,
  1154. r`x^{\left( x \right)}`,
  1155. ];
  1156. for (let i = 0; i < goodLeftArguments.length; i++) {
  1157. expect(goodLeftArguments[i]).toParse();
  1158. }
  1159. });
  1160. });
  1161. describe("An op symbol builder", function() {
  1162. it("should not fail", function() {
  1163. expect("\\int_i^n").toBuild();
  1164. expect("\\iint_i^n").toBuild();
  1165. expect("\\iiint_i^n").toBuild();
  1166. expect("\\int\nolimits_i^n").toBuild();
  1167. expect("\\iint\nolimits_i^n").toBuild();
  1168. expect("\\iiint\nolimits_i^n").toBuild();
  1169. expect("\\oint_i^n").toBuild();
  1170. expect("\\oiint_i^n").toBuild();
  1171. expect("\\oiiint_i^n").toBuild();
  1172. expect("\\oint\nolimits_i^n").toBuild();
  1173. expect("\\oiint\nolimits_i^n").toBuild();
  1174. expect("\\oiiint\nolimits_i^n").toBuild();
  1175. });
  1176. });
  1177. describe("A style change parser", function() {
  1178. it("should not fail", function() {
  1179. expect`\displaystyle x`.toParse();
  1180. expect`\textstyle x`.toParse();
  1181. expect`\scriptstyle x`.toParse();
  1182. expect`\scriptscriptstyle x`.toParse();
  1183. });
  1184. it("should produce the correct style", function() {
  1185. const displayParse = getParsed`\displaystyle x`[0];
  1186. expect(displayParse.style).toEqual("display");
  1187. const scriptscriptParse = getParsed`\scriptscriptstyle x`[0];
  1188. expect(scriptscriptParse.style).toEqual("scriptscript");
  1189. });
  1190. it("should only change the style within its group", function() {
  1191. const text = r`a b { c d \displaystyle e f } g h`;
  1192. const parse = getParsed(text);
  1193. const displayNode = parse[2].body[2];
  1194. expect(displayNode.type).toEqual("styling");
  1195. const displayBody = displayNode.body;
  1196. expect(displayBody).toHaveLength(2);
  1197. expect(displayBody[0].text).toEqual("e");
  1198. });
  1199. });
  1200. describe("A font parser", function() {
  1201. it("should parse \\mathrm, \\mathbb, \\mathit, and \\mathnormal", function() {
  1202. expect`\mathrm x`.toParse();
  1203. expect`\mathbb x`.toParse();
  1204. expect`\mathit x`.toParse();
  1205. expect`\mathnormal x`.toParse();
  1206. expect`\mathrm {x + 1}`.toParse();
  1207. expect`\mathbb {x + 1}`.toParse();
  1208. expect`\mathit {x + 1}`.toParse();
  1209. expect`\mathnormal {x + 1}`.toParse();
  1210. });
  1211. it("should parse \\mathcal and \\mathfrak", function() {
  1212. expect`\mathcal{ABC123}`.toParse();
  1213. expect`\mathfrak{abcABC123}`.toParse();
  1214. });
  1215. it("should produce the correct fonts", function() {
  1216. const mathbbParse = getParsed`\mathbb x`[0];
  1217. expect(mathbbParse.font).toEqual("mathbb");
  1218. expect(mathbbParse.type).toEqual("font");
  1219. const mathrmParse = getParsed`\mathrm x`[0];
  1220. expect(mathrmParse.font).toEqual("mathrm");
  1221. expect(mathrmParse.type).toEqual("font");
  1222. const mathitParse = getParsed`\mathit x`[0];
  1223. expect(mathitParse.font).toEqual("mathit");
  1224. expect(mathitParse.type).toEqual("font");
  1225. const mathnormalParse = getParsed`\mathnormal x`[0];
  1226. expect(mathnormalParse.font).toEqual("mathnormal");
  1227. expect(mathnormalParse.type).toEqual("font");
  1228. const mathcalParse = getParsed`\mathcal C`[0];
  1229. expect(mathcalParse.font).toEqual("mathcal");
  1230. expect(mathcalParse.type).toEqual("font");
  1231. const mathfrakParse = getParsed`\mathfrak C`[0];
  1232. expect(mathfrakParse.font).toEqual("mathfrak");
  1233. expect(mathfrakParse.type).toEqual("font");
  1234. });
  1235. it("should parse nested font commands", function() {
  1236. const nestedParse = getParsed`\mathbb{R \neq \mathrm{R}}`[0];
  1237. expect(nestedParse.font).toEqual("mathbb");
  1238. expect(nestedParse.type).toEqual("font");
  1239. const bbBody = nestedParse.body.body;
  1240. expect(bbBody).toHaveLength(3);
  1241. expect(bbBody[0].type).toEqual("mathord");
  1242. expect(bbBody[2].type).toEqual("font");
  1243. expect(bbBody[2].font).toEqual("mathrm");
  1244. expect(bbBody[2].type).toEqual("font");
  1245. });
  1246. it("should work with \\textcolor", function() {
  1247. const colorMathbbParse = getParsed`\textcolor{blue}{\mathbb R}`[0];
  1248. expect(colorMathbbParse.type).toEqual("color");
  1249. expect(colorMathbbParse.color).toEqual("blue");
  1250. const body = colorMathbbParse.body;
  1251. expect(body).toHaveLength(1);
  1252. expect(body[0].type).toEqual("font");
  1253. expect(body[0].font).toEqual("mathbb");
  1254. });
  1255. it("should not parse a series of font commands", function() {
  1256. expect`\mathbb \mathrm R`.not.toParse();
  1257. });
  1258. it("should nest fonts correctly", function() {
  1259. const bf = getParsed`\mathbf{a\mathrm{b}c}`[0];
  1260. expect(bf.type).toEqual("font");
  1261. expect(bf.font).toEqual("mathbf");
  1262. expect(bf.body.body).toHaveLength(3);
  1263. expect(bf.body.body[0].text).toEqual("a");
  1264. expect(bf.body.body[1].type).toEqual("font");
  1265. expect(bf.body.body[1].font).toEqual("mathrm");
  1266. expect(bf.body.body[2].text).toEqual("c");
  1267. });
  1268. it("should have the correct greediness", function() {
  1269. expect`e^\mathbf{x}`.toParse();
  1270. });
  1271. it("\\boldsymbol should inherit mbin/mrel from argument", () => {
  1272. const built = getBuilt`a\boldsymbol{}b\boldsymbol{=}c\boldsymbol{+}d\boldsymbol{++}e\boldsymbol{xyz}f`;
  1273. expect(built).toMatchSnapshot();
  1274. });
  1275. });
  1276. describe("A \\pmb builder", function() {
  1277. it("should not fail", function() {
  1278. expect("\\pmb{\\mu}").toBuild();
  1279. expect("\\pmb{=}").toBuild();
  1280. expect("\\pmb{+}").toBuild();
  1281. expect("\\pmb{\\frac{x^2}{x_1}}").toBuild();
  1282. expect("\\pmb{}").toBuild();
  1283. expect("\\def\\x{1}\\pmb{\\x\\def\\x{2}}").toParseLike("\\pmb{1}");
  1284. });
  1285. });
  1286. describe("A comment parser", function() {
  1287. it("should parse comments at the end of a line", () => {
  1288. expect("a^2 + b^2 = c^2 % Pythagoras' Theorem\n").toParse();
  1289. });
  1290. it("should parse comments at the start of a line", () => {
  1291. expect("% comment\n").toParse();
  1292. });
  1293. it("should parse multiple lines of comments in a row", () => {
  1294. expect("% comment 1\n% comment 2\n").toParse();
  1295. });
  1296. it("should parse comments between subscript and superscript", () => {
  1297. expect("x_3 %comment\n^2").toParseLike`x_3^2`;
  1298. expect("x^ %comment\n{2}").toParseLike`x^{2}`;
  1299. expect("x^ %comment\n\\frac{1}{2}").toParseLike`x^\frac{1}{2}`;
  1300. });
  1301. it("should parse comments in size and color groups", () => {
  1302. expect("\\kern{1 %kern\nem}").toParse();
  1303. expect("\\kern1 %kern\nem").toParse();
  1304. expect("\\color{#f00%red\n}").toParse();
  1305. });
  1306. it("should parse comments before an expression", () => {
  1307. expect("%comment\n{2}").toParseLike`{2}`;
  1308. });
  1309. it("should parse comments before and between \\hline", () => {
  1310. expect("\\begin{matrix}a&b\\\\ %hline\n" +
  1311. "\\hline %hline\n" +
  1312. "\\hline c&d\\end{matrix}").toParse();
  1313. });
  1314. it("should parse comments in the macro definition", () => {
  1315. expect("\\def\\foo{1 %}\n2}\n\\foo").toParseLike`12`;
  1316. });
  1317. it("should not expand nor ignore spaces after a command sequence in a comment", () => {
  1318. expect("\\def\\foo{1\n2}\nx %\\foo\n").toParseLike`x`;
  1319. });
  1320. it("should not parse a comment without newline in strict mode", () => {
  1321. expect`x%y`.not.toParse(strictSettings);
  1322. expect`x%y`.toParse(nonstrictSettings);
  1323. });
  1324. it("should not produce or consume space", () => {
  1325. expect("\\text{hello% comment 1\nworld}").toParseLike`\text{helloworld}`;
  1326. expect("\\text{hello% comment\n\nworld}").toParseLike`\text{hello world}`;
  1327. });
  1328. it("should not include comments in the output", () => {
  1329. expect("5 % comment\n").toParseLike`5`;
  1330. });
  1331. });
  1332. describe("An HTML font tree-builder", function() {
  1333. it("should render \\mathbb{R} with the correct font", function() {
  1334. const markup = katex.renderToString(r`\mathbb{R}`);
  1335. expect(markup).toContain("<span class=\"mord mathbb\">R</span>");
  1336. });
  1337. it("should render \\mathrm{R} with the correct font", function() {
  1338. const markup = katex.renderToString(r`\mathrm{R}`);
  1339. expect(markup).toContain("<span class=\"mord mathrm\">R</span>");
  1340. });
  1341. it("should render \\mathcal{R} with the correct font", function() {
  1342. const markup = katex.renderToString(r`\mathcal{R}`);
  1343. expect(markup).toContain("<span class=\"mord mathcal\">R</span>");
  1344. });
  1345. it("should render \\mathfrak{R} with the correct font", function() {
  1346. const markup = katex.renderToString(r`\mathfrak{R}`);
  1347. expect(markup).toContain("<span class=\"mord mathfrak\">R</span>");
  1348. });
  1349. it("should render \\text{R} with the correct font", function() {
  1350. const markup = katex.renderToString(r`\text{R}`);
  1351. expect(markup).toContain("<span class=\"mord\">R</span>");
  1352. });
  1353. it("should render \\textit{R} with the correct font", function() {
  1354. const markup = katex.renderToString(r`\textit{R}`);
  1355. expect(markup).toContain("<span class=\"mord textit\">R</span>");
  1356. });
  1357. it("should render \\text{\\textit{R}} with the correct font", function() {
  1358. const markup = katex.renderToString(r`\text{\textit{R}}`);
  1359. expect(markup).toContain("<span class=\"mord textit\">R</span>");
  1360. });
  1361. it("should render \\text{R\\textit{S}T} with the correct fonts", function() {
  1362. const markup = katex.renderToString(r`\text{R\textit{S}T}`);
  1363. expect(markup).toContain("<span class=\"mord\">R</span>");
  1364. expect(markup).toContain("<span class=\"mord textit\">S</span>");
  1365. expect(markup).toContain("<span class=\"mord\">T</span>");
  1366. });
  1367. it("should render \\textbf{R} with the correct font", function() {
  1368. const markup = katex.renderToString(r`\textbf{R}`);
  1369. expect(markup).toContain("<span class=\"mord textbf\">R</span>");
  1370. });
  1371. it("should render \\textsf{R} with the correct font", function() {
  1372. const markup = katex.renderToString(r`\textsf{R}`);
  1373. expect(markup).toContain("<span class=\"mord textsf\">R</span>");
  1374. });
  1375. it("should render \\textsf{\\textit{R}G\\textbf{B}} with the correct font", function() {
  1376. const markup = katex.renderToString(r`\textsf{\textit{R}G\textbf{B}}`);
  1377. expect(markup).toContain("<span class=\"mord textsf textit\">R</span>");
  1378. expect(markup).toContain("<span class=\"mord textsf\">G</span>");
  1379. expect(markup).toContain("<span class=\"mord textsf textbf\">B</span>");
  1380. });
  1381. it("should render \\textsf{\\textbf{$\\mathrm{A}$}} with the correct font", function() {
  1382. const markup = katex.renderToString(r`\textsf{\textbf{$\mathrm{A}$}}`);
  1383. expect(markup).toContain("<span class=\"mord mathrm\">A</span>");
  1384. });
  1385. it("should render \\textsf{\\textbf{$\\mathrm{\\textsf{A}}$}} with the correct font", function() {
  1386. const markup = katex.renderToString(r`\textsf{\textbf{$\mathrm{\textsf{A}}$}}`);
  1387. expect(markup).toContain("<span class=\"mord textsf textbf\">A</span>");
  1388. });
  1389. it("should render \\texttt{R} with the correct font", function() {
  1390. const markup = katex.renderToString(r`\texttt{R}`);
  1391. expect(markup).toContain("<span class=\"mord texttt\">R</span>");
  1392. });
  1393. it("should render a combination of font and color changes", function() {
  1394. let markup = katex.renderToString(r`\textcolor{blue}{\mathbb R}`);
  1395. let span = "<span class=\"mord mathbb\" style=\"color:blue;\">R</span>";
  1396. expect(markup).toContain(span);
  1397. markup = katex.renderToString(r`\mathbb{\textcolor{blue}{R}}`);
  1398. span = "<span class=\"mord mathbb\" style=\"color:blue;\">R</span>";
  1399. expect(markup).toContain(span);
  1400. });
  1401. it("should render wide characters with mord and with the correct font", function() {
  1402. const markup = katex.renderToString(String.fromCharCode(0xD835, 0xDC00));
  1403. expect(markup).toContain("<span class=\"mord mathbf\">A</span>");
  1404. expect(String.fromCharCode(0xD835, 0xDC00) +
  1405. " = " + String.fromCharCode(0xD835, 0xDC1A))
  1406. .toBuildLike`\mathbf A = \mathbf a`;
  1407. });
  1408. it("should throw TypeError when the expression is of the wrong type", function() {
  1409. expect(function() {
  1410. katex.renderToString({badInputType: "yes"});
  1411. }).toThrowError(TypeError);
  1412. expect(function() {
  1413. katex.renderToString([1, 2]);
  1414. }).toThrowError(TypeError);
  1415. expect(function() {
  1416. katex.renderToString(undefined);
  1417. }).toThrowError(TypeError);
  1418. expect(function() {
  1419. katex.renderToString(null);
  1420. }).toThrowError(TypeError);
  1421. expect(function() {
  1422. katex.renderToString(1.234);
  1423. }).toThrowError(TypeError);
  1424. });
  1425. it("should not throw TypeError when the expression is a supported type", function() {
  1426. expect(function() {
  1427. katex.renderToString(r`\sqrt{123}`);
  1428. }).not.toThrowError(TypeError);
  1429. expect(function() {
  1430. katex.renderToString(new String(r`\sqrt{123}`));
  1431. }).not.toThrowError(TypeError);
  1432. });
  1433. });
  1434. describe("A MathML font tree-builder", function() {
  1435. const contents = r`Ax2k\omega\Omega\imath+`;
  1436. it("should render " + contents + " with the correct mathvariants", function() {
  1437. const tree = getParsed(contents);
  1438. const markup = buildMathML(tree, contents, defaultOptions).toMarkup();
  1439. expect(markup).toContain("<mi>A</mi>");
  1440. expect(markup).toContain("<mi>x</mi>");
  1441. expect(markup).toContain("<mn>2</mn>");
  1442. expect(markup).toContain("<mi>\u03c9</mi>"); // \omega
  1443. expect(markup).toContain("<mi mathvariant=\"normal\">\u03A9</mi>"); // \Omega
  1444. expect(markup).toContain("<mi>\u0131</mi>"); // \imath
  1445. expect(markup).toContain("<mo>+</mo>");
  1446. });
  1447. it("should render \\mathbb{" + contents + "} with the correct mathvariants", function() {
  1448. const tex = `\\mathbb{${contents}}`;
  1449. const tree = getParsed(tex);
  1450. const markup = buildMathML(tree, tex, defaultOptions).toMarkup();
  1451. expect(markup).toContain("<mi mathvariant=\"double-struck\">A</mi>");
  1452. expect(markup).toContain("<mi>x</mi>");
  1453. expect(markup).toContain("<mn>2</mn>");
  1454. expect(markup).toContain("<mi>\u03c9</mi>"); // \omega
  1455. expect(markup).toContain("<mi mathvariant=\"normal\">\u03A9</mi>"); // \Omega
  1456. expect(markup).toContain("<mi>\u0131</mi>"); // \imath
  1457. expect(markup).toContain("<mo>+</mo>");
  1458. });
  1459. it("should render \\mathrm{" + contents + "} with the correct mathvariants", function() {
  1460. const tex = `\\mathrm{${contents}}`;
  1461. const tree = getParsed(tex);
  1462. const markup = buildMathML(tree, tex, defaultOptions).toMarkup();
  1463. expect(markup).toContain("<mi mathvariant=\"normal\">A</mi>");
  1464. expect(markup).toContain("<mi mathvariant=\"normal\">x</mi>");
  1465. expect(markup).toContain("<mn>2</mn>");
  1466. expect(markup).toContain("<mi>\u03c9</mi>"); // \omega
  1467. expect(markup).toContain("<mi mathvariant=\"normal\">\u03A9</mi>"); // \Omega
  1468. expect(markup).toContain("<mi>\u0131</mi>"); // \imath
  1469. expect(markup).toContain("<mo>+</mo>");
  1470. });
  1471. it("should render \\mathit{" + contents + "} with the correct mathvariants", function() {
  1472. const tex = `\\mathit{${contents}}`;
  1473. const tree = getParsed(tex);
  1474. const markup = buildMathML(tree, tex, defaultOptions).toMarkup();
  1475. expect(markup).toContain("<mi>A</mi>");
  1476. expect(markup).toContain("<mi>x</mi>");
  1477. expect(markup).toContain("<mn mathvariant=\"italic\">2</mn>");
  1478. expect(markup).toContain("<mi>\u03c9</mi>"); // \omega
  1479. expect(markup).toContain("<mi>\u03A9</mi>"); // \Omega
  1480. expect(markup).toContain("<mi>\u0131</mi>"); // \imath
  1481. expect(markup).toContain("<mo>+</mo>");
  1482. });
  1483. it("should render \\mathnormal{" + contents + "} with the correct mathvariants", function() {
  1484. const tex = `\\mathnormal{${contents}}`;
  1485. const tree = getParsed(tex);
  1486. const markup = buildMathML(tree, tex, defaultOptions).toMarkup();
  1487. expect(markup).toContain("<mi>A</mi>");
  1488. expect(markup).toContain("<mi>x</mi>");
  1489. expect(markup).toContain("<mn>2</mn>");
  1490. expect(markup).toContain("<mi>\u03c9</mi>"); // \omega
  1491. expect(markup).toContain("<mi mathvariant=\"normal\">\u03A9</mi>"); // \Omega
  1492. expect(markup).toContain("<mi>\u0131</mi>"); // \imath
  1493. expect(markup).toContain("<mo>+</mo>");
  1494. });
  1495. it("should render \\mathbf{" + contents + "} with the correct mathvariants", function() {
  1496. const tex = `\\mathbf{${contents}}`;
  1497. const tree = getParsed(tex);
  1498. const markup = buildMathML(tree, tex, defaultOptions).toMarkup();
  1499. expect(markup).toContain("<mi mathvariant=\"bold\">A</mi>");
  1500. expect(markup).toContain("<mi mathvariant=\"bold\">x</mi>");
  1501. expect(markup).toContain("<mn mathvariant=\"bold\">2</mn>");
  1502. expect(markup).toContain("<mi>\u03c9</mi>"); // \omega
  1503. expect(markup).toContain("<mi mathvariant=\"bold\">\u03A9</mi>"); // \Omega
  1504. expect(markup).toContain("<mi>\u0131</mi>"); // \imath
  1505. expect(markup).toContain("<mo>+</mo>");
  1506. });
  1507. it("should render \\mathcal{" + contents + "} with the correct mathvariants", function() {
  1508. const tex = `\\mathcal{${contents}}`;
  1509. const tree = getParsed(tex);
  1510. const markup = buildMathML(tree, tex, defaultOptions).toMarkup();
  1511. expect(markup).toContain("<mi mathvariant=\"script\">A</mi>");
  1512. expect(markup).toContain("<mi>x</mi>"); // script is caps only
  1513. expect(markup).toContain("<mn mathvariant=\"script\">2</mn>");
  1514. // MathJax marks everything below as "script" except \omega
  1515. // We don't have these glyphs in "caligraphic" and neither does MathJax
  1516. expect(markup).toContain("<mi>\u03c9</mi>"); // \omega
  1517. expect(markup).toContain("<mi mathvariant=\"normal\">\u03A9</mi>"); // \Omega
  1518. expect(markup).toContain("<mi>\u0131</mi>"); // \imath
  1519. expect(markup).toContain("<mo>+</mo>");
  1520. });
  1521. it("should render \\mathfrak{" + contents + "} with the correct mathvariants", function() {
  1522. const tex = `\\mathfrak{${contents}}`;
  1523. const tree = getParsed(tex);
  1524. const markup = buildMathML(tree, tex, defaultOptions).toMarkup();
  1525. expect(markup).toContain("<mi mathvariant=\"fraktur\">A</mi>");
  1526. expect(markup).toContain("<mi mathvariant=\"fraktur\">x</mi>");
  1527. expect(markup).toContain("<mn mathvariant=\"fraktur\">2</mn>");
  1528. // MathJax marks everything below as "fraktur" except \omega
  1529. // We don't have these glyphs in "fraktur" and neither does MathJax
  1530. expect(markup).toContain("<mi>\u03c9</mi>"); // \omega
  1531. expect(markup).toContain("<mi mathvariant=\"normal\">\u03A9</mi>"); // \Omega
  1532. expect(markup).toContain("<mi>\u0131</mi>"); // \imath
  1533. expect(markup).toContain("<mo>+</mo>");
  1534. });
  1535. it("should render \\mathscr{" + contents + "} with the correct mathvariants", function() {
  1536. const tex = `\\mathscr{${contents}}`;
  1537. const tree = getParsed(tex);
  1538. const markup = buildMathML(tree, tex, defaultOptions).toMarkup();
  1539. expect(markup).toContain("<mi mathvariant=\"script\">A</mi>");
  1540. // MathJax marks everything below as "script" except \omega
  1541. // We don't have these glyphs in "script" and neither does MathJax
  1542. expect(markup).toContain("<mi>x</mi>");
  1543. expect(markup).toContain("<mn>2</mn>");
  1544. expect(markup).toContain("<mi>\u03c9</mi>"); // \omega
  1545. expect(markup).toContain("<mi mathvariant=\"normal\">\u03A9</mi>"); // \Omega
  1546. expect(markup).toContain("<mi>\u0131</mi>"); // \imath
  1547. expect(markup).toContain("<mo>+</mo>");
  1548. });
  1549. it("should render \\mathsf{" + contents + "} with the correct mathvariants", function() {
  1550. const tex = `\\mathsf{${contents}}`;
  1551. const tree = getParsed(tex);
  1552. const markup = buildMathML(tree, tex, defaultOptions).toMarkup();
  1553. expect(markup).toContain("<mi mathvariant=\"sans-serif\">A</mi>");
  1554. expect(markup).toContain("<mi mathvariant=\"sans-serif\">x</mi>");
  1555. expect(markup).toContain("<mn mathvariant=\"sans-serif\">2</mn>");
  1556. expect(markup).toContain("<mi>\u03c9</mi>"); // \omega
  1557. expect(markup).toContain("<mi mathvariant=\"sans-serif\">\u03A9</mi>"); // \Omega
  1558. expect(markup).toContain("<mi>\u0131</mi>"); // \imath
  1559. expect(markup).toContain("<mo>+</mo>");
  1560. });
  1561. it("should render a combination of font and color changes", function() {
  1562. let tex = r`\textcolor{blue}{\mathbb R}`;
  1563. let tree = getParsed(tex);
  1564. let markup = buildMathML(tree, tex, defaultOptions).toMarkup();
  1565. let node = "<mstyle mathcolor=\"blue\">" +
  1566. "<mi mathvariant=\"double-struck\">R</mi>" +
  1567. "</mstyle>";
  1568. expect(markup).toContain(node);
  1569. // reverse the order of the commands
  1570. tex = r`\mathbb{\textcolor{blue}{R}}`;
  1571. tree = getParsed(tex);
  1572. markup = buildMathML(tree, tex, defaultOptions).toMarkup();
  1573. node = "<mstyle mathcolor=\"blue\">" +
  1574. "<mi mathvariant=\"double-struck\">R</mi>" +
  1575. "</mstyle>";
  1576. expect(markup).toContain(node);
  1577. });
  1578. it("should render text as <mtext>", function() {
  1579. const tex = r`\text{for }`;
  1580. const tree = getParsed(tex);
  1581. const markup = buildMathML(tree, tex, defaultOptions).toMarkup();
  1582. expect(markup).toContain("<mtext>for\u00a0</mtext>");
  1583. });
  1584. it("should render math within text as side-by-side children", function() {
  1585. const tex = r`\text{graph: $y = mx + b$}`;
  1586. const tree = getParsed(tex);
  1587. const markup = buildMathML(tree, tex, defaultOptions).toMarkup();
  1588. expect(markup).toContain("<mrow><mtext>graph:\u00a0</mtext>");
  1589. expect(markup).toContain(
  1590. "<mi>y</mi><mo>=</mo><mi>m</mi><mi>x</mi><mo>+</mo><mi>b</mi>");
  1591. });
  1592. });
  1593. describe("An includegraphics builder", function() {
  1594. const img = "\\includegraphics[height=0.9em, totalheight=0.9em, width=0.9em, alt=KA logo]{https://cdn.kastatic.org/images/apple-touch-icon-57x57-precomposed.new.png}";
  1595. it("should not fail", function() {
  1596. expect(img).toBuild();
  1597. });
  1598. it("should produce mords", function() {
  1599. expect(getBuilt(img)[0].classes).toContain("mord");
  1600. });
  1601. });
  1602. describe("A bin builder", function() {
  1603. it("should create mbins normally", function() {
  1604. const built = getBuilt`x + y`;
  1605. // we add glue elements around the '+'
  1606. expect(built[2].classes).toContain("mbin");
  1607. });
  1608. it("should create ords when at the beginning of lists", function() {
  1609. const built = getBuilt`+ x`;
  1610. expect(built[0].classes).toContain("mord");
  1611. expect(built[0].classes).not.toContain("mbin");
  1612. });
  1613. it("should create ords after some other objects", function() {
  1614. expect(getBuilt`x + + 2`[4].classes).toContain("mord");
  1615. expect(getBuilt`( + 2`[2].classes).toContain("mord");
  1616. expect(getBuilt`= + 2`[2].classes).toContain("mord");
  1617. expect(getBuilt`\sin + 2`[2].classes).toContain("mord");
  1618. expect(getBuilt`, + 2`[2].classes).toContain("mord");
  1619. });
  1620. it("should correctly interact with color objects", function() {
  1621. expect(getBuilt`\blue{x}+y`[2].classes).toContain("mbin");
  1622. expect(getBuilt`\blue{x+}+y`[2].classes).toContain("mbin");
  1623. expect(getBuilt`\blue{x+}+y`[4].classes).toContain("mord");
  1624. });
  1625. });
  1626. describe("A markup generator", function() {
  1627. it("marks trees up", function() {
  1628. // Just a few quick sanity checks here...
  1629. const markup = katex.renderToString(r`\sigma^2`);
  1630. expect(markup.indexOf("<span")).toBe(0);
  1631. expect(markup).toContain("\u03c3"); // sigma
  1632. expect(markup).toContain("margin-right");
  1633. expect(markup).not.toContain("marginRight");
  1634. });
  1635. it("generates both MathML and HTML", function() {
  1636. const markup = katex.renderToString("a");
  1637. expect(markup).toContain("<span");
  1638. expect(markup).toContain("<math");
  1639. });
  1640. });
  1641. describe("A parse tree generator", function() {
  1642. it("generates a tree", function() {
  1643. const tree = stripPositions(getParsed`\sigma^2`);
  1644. expect(tree).toMatchSnapshot();
  1645. });
  1646. });
  1647. describe("An accent parser", function() {
  1648. it("should not fail", function() {
  1649. expect`\vec{x}`.toParse();
  1650. expect`\vec{x^2}`.toParse();
  1651. expect`\vec{x}^2`.toParse();
  1652. expect`\vec x`.toParse();
  1653. });
  1654. it("should produce accents", function() {
  1655. const parse = getParsed`\vec x`[0];
  1656. expect(parse.type).toEqual("accent");
  1657. });
  1658. it("should be grouped more tightly than supsubs", function() {
  1659. const parse = getParsed`\vec x^2`[0];
  1660. expect(parse.type).toEqual("supsub");
  1661. });
  1662. it("should parse stretchy, shifty accents", function() {
  1663. expect`\widehat{x}`.toParse();
  1664. expect`\widecheck{x}`.toParse();
  1665. });
  1666. it("should parse stretchy, non-shifty accents", function() {
  1667. expect`\overrightarrow{x}`.toParse();
  1668. });
  1669. });
  1670. describe("An accent builder", function() {
  1671. it("should not fail", function() {
  1672. expect`\vec{x}`.toBuild();
  1673. expect`\vec{x}^2`.toBuild();
  1674. expect`\vec{x}_2`.toBuild();
  1675. expect`\vec{x}_2^2`.toBuild();
  1676. });
  1677. it("should produce mords", function() {
  1678. expect(getBuilt`\vec x`[0].classes).toContain("mord");
  1679. expect(getBuilt`\vec +`[0].classes).toContain("mord");
  1680. expect(getBuilt`\vec +`[0].classes).not.toContain("mbin");
  1681. expect(getBuilt`\vec )^2`[0].classes).toContain("mord");
  1682. expect(getBuilt`\vec )^2`[0].classes).not.toContain("mclose");
  1683. });
  1684. });
  1685. describe("A stretchy and shifty accent builder", function() {
  1686. it("should not fail", function() {
  1687. expect`\widehat{AB}`.toBuild();
  1688. expect`\widecheck{AB}`.toBuild();
  1689. expect`\widehat{AB}^2`.toBuild();
  1690. expect`\widehat{AB}_2`.toBuild();
  1691. expect`\widehat{AB}_2^2`.toBuild();
  1692. });
  1693. it("should produce mords", function() {
  1694. expect(getBuilt`\widehat{AB}`[0].classes).toContain("mord");
  1695. expect(getBuilt`\widehat +`[0].classes).toContain("mord");
  1696. expect(getBuilt`\widehat +`[0].classes).not.toContain("mbin");
  1697. expect(getBuilt`\widehat )^2`[0].classes).toContain("mord");
  1698. expect(getBuilt`\widehat )^2`[0].classes).not.toContain("mclose");
  1699. });
  1700. });
  1701. describe("A stretchy and non-shifty accent builder", function() {
  1702. it("should not fail", function() {
  1703. expect`\overrightarrow{AB}`.toBuild();
  1704. expect`\overrightarrow{AB}^2`.toBuild();
  1705. expect`\overrightarrow{AB}_2`.toBuild();
  1706. expect`\overrightarrow{AB}_2^2`.toBuild();
  1707. });
  1708. it("should produce mords", function() {
  1709. expect(getBuilt`\overrightarrow{AB}`[0].classes).toContain("mord");
  1710. expect(getBuilt`\overrightarrow +`[0].classes).toContain("mord");
  1711. expect(getBuilt`\overrightarrow +`[0].classes).not.toContain("mbin");
  1712. expect(getBuilt`\overrightarrow )^2`[0].classes).toContain("mord");
  1713. expect(getBuilt`\overrightarrow )^2`[0].classes).not.toContain("mclose");
  1714. });
  1715. });
  1716. describe("An under-accent parser", function() {
  1717. it("should not fail", function() {
  1718. expect("\\underrightarrow{x}").toParse();
  1719. expect("\\underrightarrow{x^2}").toParse();
  1720. expect("\\underrightarrow{x}^2").toParse();
  1721. expect("\\underrightarrow x").toParse();
  1722. });
  1723. it("should produce accentUnder", function() {
  1724. const parse = getParsed("\\underrightarrow x")[0];
  1725. expect(parse.type).toEqual("accentUnder");
  1726. });
  1727. it("should be grouped more tightly than supsubs", function() {
  1728. const parse = getParsed("\\underrightarrow x^2")[0];
  1729. expect(parse.type).toEqual("supsub");
  1730. });
  1731. });
  1732. describe("An under-accent builder", function() {
  1733. it("should not fail", function() {
  1734. expect("\\underrightarrow{x}").toBuild();
  1735. expect("\\underrightarrow{x}^2").toBuild();
  1736. expect("\\underrightarrow{x}_2").toBuild();
  1737. expect("\\underrightarrow{x}_2^2").toBuild();
  1738. });
  1739. it("should produce mords", function() {
  1740. expect(getBuilt("\\underrightarrow x")[0].classes).toContain("mord");
  1741. expect(getBuilt("\\underrightarrow +")[0].classes).toContain("mord");
  1742. expect(getBuilt("\\underrightarrow +")[0].classes).not.toContain("mbin");
  1743. expect(getBuilt("\\underrightarrow )^2")[0].classes).toContain("mord");
  1744. expect(getBuilt("\\underrightarrow )^2")[0].classes).not.toContain("mclose");
  1745. });
  1746. });
  1747. describe("An extensible arrow parser", function() {
  1748. it("should not fail", function() {
  1749. expect("\\xrightarrow{x}").toParse();
  1750. expect("\\xrightarrow{x^2}").toParse();
  1751. expect("\\xrightarrow{x}^2").toParse();
  1752. expect("\\xrightarrow x").toParse();
  1753. expect("\\xrightarrow[under]{over}").toParse();
  1754. });
  1755. it("should produce xArrow", function() {
  1756. const parse = getParsed("\\xrightarrow x")[0];
  1757. expect(parse.type).toEqual("xArrow");
  1758. });
  1759. it("should be grouped more tightly than supsubs", function() {
  1760. const parse = getParsed("\\xrightarrow x^2")[0];
  1761. expect(parse.type).toEqual("supsub");
  1762. });
  1763. });
  1764. describe("An extensible arrow builder", function() {
  1765. it("should not fail", function() {
  1766. expect("\\xrightarrow{x}").toBuild();
  1767. expect("\\xrightarrow{x}^2").toBuild();
  1768. expect("\\xrightarrow{x}_2").toBuild();
  1769. expect("\\xrightarrow{x}_2^2").toBuild();
  1770. expect("\\xrightarrow[under]{over}").toBuild();
  1771. });
  1772. it("should produce mrell", function() {
  1773. expect(getBuilt("\\xrightarrow x")[0].classes).toContain("mrel");
  1774. expect(getBuilt("\\xrightarrow [under]{over}")[0].classes).toContain("mrel");
  1775. expect(getBuilt("\\xrightarrow +")[0].classes).toContain("mrel");
  1776. expect(getBuilt("\\xrightarrow +")[0].classes).not.toContain("mbin");
  1777. expect(getBuilt("\\xrightarrow )^2")[0].classes).toContain("mrel");
  1778. expect(getBuilt("\\xrightarrow )^2")[0].classes).not.toContain("mclose");
  1779. });
  1780. });
  1781. describe("A horizontal brace parser", function() {
  1782. it("should not fail", function() {
  1783. expect`\overbrace{x}`.toParse();
  1784. expect`\overbrace{x^2}`.toParse();
  1785. expect`\overbrace{x}^2`.toParse();
  1786. expect`\overbrace x`.toParse();
  1787. expect("\\underbrace{x}_2").toParse();
  1788. expect("\\underbrace{x}_2^2").toParse();
  1789. });
  1790. it("should produce horizBrace", function() {
  1791. const parse = getParsed`\overbrace x`[0];
  1792. expect(parse.type).toEqual("horizBrace");
  1793. });
  1794. it("should be grouped more tightly than supsubs", function() {
  1795. const parse = getParsed`\overbrace x^2`[0];
  1796. expect(parse.type).toEqual("supsub");
  1797. });
  1798. });
  1799. describe("A horizontal brace builder", function() {
  1800. it("should not fail", function() {
  1801. expect`\overbrace{x}`.toBuild();
  1802. expect`\overbrace{x}^2`.toBuild();
  1803. expect("\\underbrace{x}_2").toBuild();
  1804. expect("\\underbrace{x}_2^2").toBuild();
  1805. });
  1806. it("should produce mords", function() {
  1807. expect(getBuilt`\overbrace x`[0].classes).toContain("mord");
  1808. expect(getBuilt`\overbrace{x}^2`[0].classes).toContain("mord");
  1809. expect(getBuilt`\overbrace +`[0].classes).toContain("mord");
  1810. expect(getBuilt`\overbrace +`[0].classes).not.toContain("mbin");
  1811. expect(getBuilt`\overbrace )^2`[0].classes).toContain("mord");
  1812. expect(getBuilt`\overbrace )^2`[0].classes).not.toContain("mclose");
  1813. });
  1814. });
  1815. describe("A boxed parser", function() {
  1816. it("should not fail", function() {
  1817. expect`\boxed{x}`.toParse();
  1818. expect`\boxed{x^2}`.toParse();
  1819. expect`\boxed{x}^2`.toParse();
  1820. expect`\boxed x`.toParse();
  1821. });
  1822. it("should produce enclose", function() {
  1823. const parse = getParsed`\boxed x`[0];
  1824. expect(parse.type).toEqual("enclose");
  1825. });
  1826. });
  1827. describe("A boxed builder", function() {
  1828. it("should not fail", function() {
  1829. expect`\boxed{x}`.toBuild();
  1830. expect`\boxed{x}^2`.toBuild();
  1831. expect`\boxed{x}_2`.toBuild();
  1832. expect`\boxed{x}_2^2`.toBuild();
  1833. });
  1834. it("should produce mords", function() {
  1835. expect(getBuilt`\boxed x`[0].classes).toContain("mord");
  1836. expect(getBuilt`\boxed +`[0].classes).toContain("mord");
  1837. expect(getBuilt`\boxed +`[0].classes).not.toContain("mbin");
  1838. expect(getBuilt`\boxed )^2`[0].classes).toContain("mord");
  1839. expect(getBuilt`\boxed )^2`[0].classes).not.toContain("mclose");
  1840. });
  1841. });
  1842. describe("An fbox parser, unlike a boxed parser,", function() {
  1843. it("should fail when given math", function() {
  1844. expect`\fbox{\frac a b}`.not.toParse();
  1845. });
  1846. });
  1847. describe("A colorbox parser", function() {
  1848. it("should not fail, given a text argument", function() {
  1849. expect`\colorbox{red}{a b}`.toParse();
  1850. expect`\colorbox{red}{x}^2`.toParse();
  1851. expect`\colorbox{red} x`.toParse();
  1852. });
  1853. it("should fail, given a math argument", function() {
  1854. expect`\colorbox{red}{\alpha}`.not.toParse();
  1855. expect`\colorbox{red}{\frac{a}{b}}`.not.toParse();
  1856. });
  1857. it("should parse a color", function() {
  1858. expect`\colorbox{red}{a b}`.toParse();
  1859. expect`\colorbox{#197}{a b}`.toParse();
  1860. expect`\colorbox{#1a9b7c}{a b}`.toParse();
  1861. });
  1862. it("should produce enclose", function() {
  1863. const parse = getParsed`\colorbox{red} x`[0];
  1864. expect(parse.type).toEqual("enclose");
  1865. });
  1866. });
  1867. describe("A colorbox builder", function() {
  1868. it("should not fail", function() {
  1869. expect`\colorbox{red}{a b}`.toBuild();
  1870. expect`\colorbox{red}{a b}^2`.toBuild();
  1871. expect`\colorbox{red} x`.toBuild();
  1872. });
  1873. it("should produce mords", function() {
  1874. expect(getBuilt`\colorbox{red}{a b}`[0].classes).toContain("mord");
  1875. });
  1876. });
  1877. describe("An fcolorbox parser", function() {
  1878. it("should not fail, given a text argument", function() {
  1879. expect`\fcolorbox{blue}{yellow}{a b}`.toParse();
  1880. expect`\fcolorbox{blue}{yellow}{x}^2`.toParse();
  1881. expect`\fcolorbox{blue}{yellow} x`.toParse();
  1882. });
  1883. it("should fail, given a math argument", function() {
  1884. expect`\fcolorbox{blue}{yellow}{\alpha}`.not.toParse();
  1885. expect`\fcolorbox{blue}{yellow}{\frac{a}{b}}`.not.toParse();
  1886. });
  1887. it("should parse a color", function() {
  1888. expect`\fcolorbox{blue}{yellow}{a b}`.toParse();
  1889. expect`\fcolorbox{blue}{#197}{a b}`.toParse();
  1890. expect`\fcolorbox{blue}{#1a9b7c}{a b}`.toParse();
  1891. });
  1892. it("should produce enclose", function() {
  1893. const parse = getParsed`\fcolorbox{blue}{yellow} x`[0];
  1894. expect(parse.type).toEqual("enclose");
  1895. });
  1896. });
  1897. describe("A fcolorbox builder", function() {
  1898. it("should not fail", function() {
  1899. expect`\fcolorbox{blue}{yellow}{a b}`.toBuild();
  1900. expect`\fcolorbox{blue}{yellow}{a b}^2`.toBuild();
  1901. expect`\fcolorbox{blue}{yellow} x`.toBuild();
  1902. });
  1903. it("should produce mords", function() {
  1904. expect(getBuilt`\colorbox{red}{a b}`[0].classes).toContain("mord");
  1905. });
  1906. });
  1907. describe("A strike-through parser", function() {
  1908. it("should not fail", function() {
  1909. expect`\cancel{x}`.toParse();
  1910. expect`\cancel{x^2}`.toParse();
  1911. expect`\cancel{x}^2`.toParse();
  1912. expect`\cancel x`.toParse();
  1913. });
  1914. it("should produce enclose", function() {
  1915. const parse = getParsed`\cancel x`[0];
  1916. expect(parse.type).toEqual("enclose");
  1917. });
  1918. it("should be grouped more tightly than supsubs", function() {
  1919. const parse = getParsed`\cancel x^2`[0];
  1920. expect(parse.type).toEqual("supsub");
  1921. });
  1922. });
  1923. describe("A strike-through builder", function() {
  1924. it("should not fail", function() {
  1925. expect`\cancel{x}`.toBuild();
  1926. expect`\cancel{x}^2`.toBuild();
  1927. expect`\cancel{x}_2`.toBuild();
  1928. expect`\cancel{x}_2^2`.toBuild();
  1929. expect`\sout{x}`.toBuild();
  1930. expect`\sout{x}^2`.toBuild();
  1931. expect`\sout{x}_2`.toBuild();
  1932. expect`\sout{x}_2^2`.toBuild();
  1933. });
  1934. it("should produce mords", function() {
  1935. expect(getBuilt`\cancel x`[0].classes).toContain("mord");
  1936. expect(getBuilt`\cancel +`[0].classes).toContain("mord");
  1937. expect(getBuilt`\cancel +`[0].classes).not.toContain("mbin");
  1938. expect(getBuilt`\cancel )^2`[0].classes).toContain("mord");
  1939. expect(getBuilt`\cancel )^2`[0].classes).not.toContain("mclose");
  1940. });
  1941. });
  1942. describe("A phantom parser", function() {
  1943. it("should not fail", function() {
  1944. expect`\phantom{x}`.toParse();
  1945. expect`\phantom{x^2}`.toParse();
  1946. expect`\phantom{x}^2`.toParse();
  1947. expect`\phantom x`.toParse();
  1948. expect`\hphantom{x}`.toParse();
  1949. expect`\hphantom{x^2}`.toParse();
  1950. expect`\hphantom{x}^2`.toParse();
  1951. expect`\hphantom x`.toParse();
  1952. });
  1953. it("should build a phantom node", function() {
  1954. const parse = getParsed`\phantom{x}`[0];
  1955. expect(parse.type).toEqual("phantom");
  1956. expect(parse.body).toBeDefined();
  1957. });
  1958. });
  1959. describe("A phantom builder", function() {
  1960. it("should not fail", function() {
  1961. expect`\phantom{x}`.toBuild();
  1962. expect`\phantom{x^2}`.toBuild();
  1963. expect`\phantom{x}^2`.toBuild();
  1964. expect`\phantom x`.toBuild();
  1965. expect`\hphantom{x}`.toBuild();
  1966. expect`\hphantom{x^2}`.toBuild();
  1967. expect`\hphantom{x}^2`.toBuild();
  1968. expect`\hphantom x`.toBuild();
  1969. });
  1970. it("should make the children transparent", function() {
  1971. const children = getBuilt`\phantom{x+1}`;
  1972. expect(children[0].style.color).toBe("transparent");
  1973. expect(children[2].style.color).toBe("transparent");
  1974. expect(children[4].style.color).toBe("transparent");
  1975. });
  1976. it("should make all descendants transparent", function() {
  1977. const children = getBuilt`\phantom{x+\blue{1}}`;
  1978. expect(children[0].style.color).toBe("transparent");
  1979. expect(children[2].style.color).toBe("transparent");
  1980. expect(children[4].style.color).toBe("transparent");
  1981. });
  1982. });
  1983. describe("A smash parser", function() {
  1984. it("should not fail", function() {
  1985. expect`\smash{x}`.toParse();
  1986. expect`\smash{x^2}`.toParse();
  1987. expect`\smash{x}^2`.toParse();
  1988. expect`\smash x`.toParse();
  1989. expect`\smash[b]{x}`.toParse();
  1990. expect`\smash[b]{x^2}`.toParse();
  1991. expect`\smash[b]{x}^2`.toParse();
  1992. expect`\smash[b] x`.toParse();
  1993. expect`\smash[]{x}`.toParse();
  1994. expect`\smash[]{x^2}`.toParse();
  1995. expect`\smash[]{x}^2`.toParse();
  1996. expect`\smash[] x`.toParse();
  1997. });
  1998. it("should build a smash node", function() {
  1999. const parse = getParsed`\smash{x}`[0];
  2000. expect(parse.type).toEqual("smash");
  2001. });
  2002. });
  2003. describe("A smash builder", function() {
  2004. it("should not fail", function() {
  2005. expect`\smash{x}`.toBuild();
  2006. expect`\smash{x^2}`.toBuild();
  2007. expect`\smash{x}^2`.toBuild();
  2008. expect`\smash x`.toBuild();
  2009. expect`\smash[b]{x}`.toBuild();
  2010. expect`\smash[b]{x^2}`.toBuild();
  2011. expect`\smash[b]{x}^2`.toBuild();
  2012. expect`\smash[b] x`.toBuild();
  2013. });
  2014. });
  2015. describe("A document fragment", function() {
  2016. it("should have paddings applied inside an extensible arrow", function() {
  2017. const markup = katex.renderToString("\\tiny\\xrightarrow\\textcolor{red}{x}");
  2018. expect(markup).toContain("x-arrow-pad");
  2019. });
  2020. it("should have paddings applied inside an enclose", function() {
  2021. const markup = katex.renderToString(r`\fbox\textcolor{red}{x}`);
  2022. expect(markup).toContain("boxpad");
  2023. });
  2024. it("should have paddings applied inside a square root", function() {
  2025. const markup = katex.renderToString(r`\sqrt\textcolor{red}{x}`);
  2026. expect(markup).toContain("padding-left");
  2027. });
  2028. });
  2029. describe("A parser error", function() {
  2030. it("should report the position of an error", function() {
  2031. try {
  2032. parseTree(r`\sqrt}`, new Settings());
  2033. } catch (e) {
  2034. expect(e.position).toEqual(5);
  2035. }
  2036. });
  2037. });
  2038. describe("An optional argument parser", function() {
  2039. it("should not fail", function() {
  2040. // Note this doesn't actually make an optional argument, but still
  2041. // should work
  2042. expect`\frac[1]{2}{3}`.toParse();
  2043. expect`\rule[0.2em]{1em}{1em}`.toParse();
  2044. });
  2045. it("should work with sqrts with optional arguments", function() {
  2046. expect`\sqrt[3]{2}`.toParse();
  2047. });
  2048. it("should work when the optional argument is missing", function() {
  2049. expect`\sqrt{2}`.toParse();
  2050. expect`\rule{1em}{2em}`.toParse();
  2051. });
  2052. it("should fail when the optional argument is malformed", function() {
  2053. expect`\rule[1]{2em}{3em}`.not.toParse();
  2054. });
  2055. it("should not work if the optional argument isn't closed", function() {
  2056. expect`\sqrt[`.not.toParse();
  2057. });
  2058. });
  2059. describe("An array environment", function() {
  2060. it("should accept a single alignment character", function() {
  2061. const parse = getParsed`\begin{array}r1\\20\end{array}`;
  2062. expect(parse[0].type).toBe("array");
  2063. expect(parse[0].cols).toEqual([
  2064. {type: "align", align: "r"},
  2065. ]);
  2066. });
  2067. it("should accept vertical separators", function() {
  2068. const parse = getParsed`\begin{array}{|l||c:r::}\end{array}`;
  2069. expect(parse[0].type).toBe("array");
  2070. expect(parse[0].cols).toEqual([
  2071. {type: "separator", separator: "|"},
  2072. {type: "align", align: "l"},
  2073. {type: "separator", separator: "|"},
  2074. {type: "separator", separator: "|"},
  2075. {type: "align", align: "c"},
  2076. {type: "separator", separator: ":"},
  2077. {type: "align", align: "r"},
  2078. {type: "separator", separator: ":"},
  2079. {type: "separator", separator: ":"},
  2080. ]);
  2081. });
  2082. });
  2083. describe("A cases environment", function() {
  2084. it("should parse its input", function() {
  2085. expect`f(a,b)=\begin{cases}a+1&\text{if }b\text{ is odd}\\a&\text{if }b=0\\a-1&\text{otherwise}\end{cases}`
  2086. .toParse();
  2087. });
  2088. });
  2089. describe("An aligned environment", function() {
  2090. it("should parse its input", function() {
  2091. expect`\begin{aligned}a&=b&c&=d\\e&=f\end{aligned}`.toParse();
  2092. });
  2093. it("should allow cells in brackets", function() {
  2094. expect`\begin{aligned}[a]&[b]\\ [c]&[d]\end{aligned}`.toParse();
  2095. });
  2096. it("should forbid cells in brackets without space", function() {
  2097. expect`\begin{aligned}[a]&[b]\\[c]&[d]\end{aligned}`.not.toParse();
  2098. });
  2099. it("should not eat the last row when its first cell is empty", function() {
  2100. const ae = getParsed`\begin{aligned}&E_1 & (1)\\&E_2 & (2)\\&E_3 & (3)\end{aligned}`[0];
  2101. expect(ae.body).toHaveLength(3);
  2102. });
  2103. });
  2104. describe("operatorname support", function() {
  2105. it("should not fail", function() {
  2106. expect("\\operatorname{x*Π∑\\Pi\\sum\\frac a b}").toBuild();
  2107. });
  2108. });
  2109. describe("href and url commands", function() {
  2110. // We can't use raw strings for \url because \u is for Unicode escapes.
  2111. it("should parse its input", function() {
  2112. expect`\href{http://example.com/}{\sin}`.toBuild();
  2113. expect("\\url{http://example.com/}").toBuild();
  2114. });
  2115. it("should allow empty URLs", function() {
  2116. expect`\href{}{example here}`.toBuild();
  2117. expect("\\url{}").toBuild();
  2118. });
  2119. it("should allow single-character URLs", () => {
  2120. expect`\href%end`.toParseLike("\\href{%}end");
  2121. expect("\\url%end").toParseLike("\\url{%}end");
  2122. expect("\\url%%end\n").toParseLike("\\url{%}");
  2123. expect("\\url end").toParseLike("\\url{e}nd");
  2124. expect("\\url%end").toParseLike("\\url {%}end");
  2125. });
  2126. it("should allow spaces single-character URLs", () => {
  2127. expect`\href %end`.toParseLike("\\href{%}end");
  2128. expect("\\url %end").toParseLike("\\url{%}end");
  2129. });
  2130. it("should allow letters [#$%&~_^] without escaping", function() {
  2131. const url = "http://example.org/~bar/#top?foo=$foo&bar=ba^r_boo%20baz";
  2132. const parsed1 = getParsed(`\\href{${url}}{\\alpha}`)[0];
  2133. expect(parsed1.href).toBe(url);
  2134. const parsed2 = getParsed(`\\url{${url}}`)[0];
  2135. expect(parsed2.href).toBe(url);
  2136. });
  2137. it("should allow balanced braces in url", function() {
  2138. const url = "http://example.org/{{}t{oo}}";
  2139. const parsed1 = getParsed(`\\href{${url}}{\\alpha}`)[0];
  2140. expect(parsed1.href).toBe(url);
  2141. const parsed2 = getParsed(`\\url{${url}}`)[0];
  2142. expect(parsed2.href).toBe(url);
  2143. });
  2144. it("should not allow unbalanced brace(s) in url", function() {
  2145. expect`\href{http://example.com/{a}{bar}`.not.toParse();
  2146. expect`\href{http://example.com/}a}{bar}`.not.toParse();
  2147. expect`\\url{http://example.com/{a}`.not.toParse();
  2148. expect`\\url{http://example.com/}a}`.not.toParse();
  2149. });
  2150. it("should allow escape for letters [#$%&~_^{}]", function() {
  2151. const url = "http://example.org/~bar/#top?foo=$}foo{&bar=bar^r_boo%20baz";
  2152. const input = url.replace(/([#$%&~_^{}])/g, '\\$1');
  2153. const parsed1 = getParsed(`\\href{${input}}{\\alpha}`)[0];
  2154. expect(parsed1.href).toBe(url);
  2155. const parsed2 = getParsed(`\\url{${input}}`)[0];
  2156. expect(parsed2.href).toBe(url);
  2157. });
  2158. it("should allow comments after URLs", function() {
  2159. expect("\\url{http://example.com/}%comment\n").toBuild();
  2160. });
  2161. it("should be marked up correctly", function() {
  2162. const markup = katex.renderToString(r`\href{http://example.com/}{example here}`);
  2163. expect(markup).toContain("<a href=\"http://example.com/\">");
  2164. });
  2165. it("should allow protocols in allowedProtocols", function() {
  2166. expect("\\href{relative}{foo}").toParse();
  2167. expect("\\href{ftp://x}{foo}").toParse(new Settings({
  2168. allowedProtocols: ["ftp"],
  2169. }));
  2170. expect("\\href{ftp://x}{foo}").toParse(new Settings({
  2171. allowedProtocols: ["*"],
  2172. }));
  2173. });
  2174. it("should not allow protocols not in allowedProtocols", function() {
  2175. expect("\\href{javascript:alert('x')}{foo}").not.toParse();
  2176. expect("\\href{relative}{foo}").not.toParse(new Settings({
  2177. allowedProtocols: [],
  2178. }));
  2179. });
  2180. it("should not affect spacing around", function() {
  2181. const built = getBuilt`a\href{http://example.com/}{+b}`;
  2182. expect(built).toMatchSnapshot();
  2183. });
  2184. });
  2185. describe("A raw text parser", function() {
  2186. it("should not not parse a mal-formed string", function() {
  2187. // In the next line, the first character passed to \includegraphics is a
  2188. // Unicode combining character. So this is a test that the parser will catch a bad string.
  2189. expect("\\includegraphics[\u030aheight=0.8em, totalheight=0.9em, width=0.9em]{" + "https://cdn.kastatic.org/images/apple-touch-icon-57x57-precomposed.new.png}").not.toParse();
  2190. });
  2191. });
  2192. describe("A parser that does not throw on unsupported commands", function() {
  2193. // The parser breaks on unsupported commands unless it is explicitly
  2194. // told not to
  2195. const errorColor = "#933";
  2196. const noThrowSettings = new Settings({
  2197. throwOnError: false,
  2198. errorColor: errorColor,
  2199. });
  2200. it("should still parse on unrecognized control sequences", function() {
  2201. expect`\error`.toParse(noThrowSettings);
  2202. });
  2203. describe("should allow unrecognized controls sequences anywhere, including", function() {
  2204. it("in superscripts and subscripts", function() {
  2205. expect`2_\error`.toBuild(noThrowSettings);
  2206. expect`3^{\error}_\error`.toBuild(noThrowSettings);
  2207. expect`\int\nolimits^\error_\error`.toBuild(noThrowSettings);
  2208. });
  2209. it("in fractions", function() {
  2210. expect`\frac{345}{\error}`.toBuild(noThrowSettings);
  2211. expect`\frac\error{\error}`.toBuild(noThrowSettings);
  2212. });
  2213. it("in square roots", function() {
  2214. expect`\sqrt\error`.toBuild(noThrowSettings);
  2215. expect`\sqrt{234\error}`.toBuild(noThrowSettings);
  2216. });
  2217. it("in text boxes", function() {
  2218. expect`\text{\error}`.toBuild(noThrowSettings);
  2219. });
  2220. });
  2221. it("should produce color nodes with a color value given by errorColor", function() {
  2222. const parsedInput = getParsed(r`\error`, noThrowSettings);
  2223. expect(parsedInput[0].type).toBe("color");
  2224. expect(parsedInput[0].color).toBe(errorColor);
  2225. });
  2226. it("should build katex-error span for other type of KaTeX error", function() {
  2227. const built = getBuilt("2^2^2", noThrowSettings);
  2228. expect(built).toMatchSnapshot();
  2229. });
  2230. it("should properly escape LaTeX in errors", function() {
  2231. const html = katex.renderToString("2^&\"<>", noThrowSettings);
  2232. expect(html).toMatchSnapshot();
  2233. });
  2234. });
  2235. describe("The symbol table integrity", function() {
  2236. it("should treat certain symbols as synonyms", function() {
  2237. expect`<`.toBuildLike`\lt`;
  2238. expect`>`.toBuildLike`\gt`;
  2239. expect`\left<\frac{1}{x}\right>`.toBuildLike`\left\lt\frac{1}{x}\right\gt`;
  2240. });
  2241. });
  2242. describe("Symbols", function() {
  2243. it("should support AMS symbols in both text and math mode", function() {
  2244. // These text+math symbols are from Section 6 of
  2245. // http://mirrors.ctan.org/fonts/amsfonts/doc/amsfonts.pdf
  2246. const symbols = r`\yen\checkmark\circledR\maltese`;
  2247. expect(symbols).toBuild();
  2248. expect(`\\text{${symbols}}`).toBuild(strictSettings);
  2249. });
  2250. });
  2251. describe("A macro expander", function() {
  2252. it("should produce individual tokens", function() {
  2253. expect`e^\foo`.toParseLike("e^1 23",
  2254. new Settings({macros: {"\\foo": "123"}}));
  2255. });
  2256. it("should preserve leading spaces inside macro definition", function() {
  2257. expect`\text{\foo}`.toParseLike(r`\text{ x}`,
  2258. new Settings({macros: {"\\foo": " x"}}));
  2259. });
  2260. it("should preserve leading spaces inside macro argument", function() {
  2261. expect`\text{\foo{ x}}`.toParseLike(r`\text{ x}`,
  2262. new Settings({macros: {"\\foo": "#1"}}));
  2263. });
  2264. it("should ignore expanded spaces in math mode", function() {
  2265. expect`\foo`.toParseLike("x", new Settings({macros: {"\\foo": " x"}}));
  2266. });
  2267. it("should consume spaces after control-word macro", function() {
  2268. expect`\text{\foo }`.toParseLike(r`\text{x}`,
  2269. new Settings({macros: {"\\foo": "x"}}));
  2270. });
  2271. it("should consume spaces after macro with \\relax", function() {
  2272. expect`\text{\foo }`.toParseLike(r`\text{}`,
  2273. new Settings({macros: {"\\foo": "\\relax"}}));
  2274. });
  2275. it("should not consume spaces after control-word expansion", function() {
  2276. expect`\text{\\ }`.toParseLike(r`\text{ }`,
  2277. new Settings({macros: {"\\\\": "\\relax"}}));
  2278. });
  2279. it("should consume spaces after \\relax", function() {
  2280. expect`\text{\relax }`.toParseLike`\text{}`;
  2281. });
  2282. it("should consume spaces after control-word function", function() {
  2283. expect`\text{\KaTeX }`.toParseLike`\text{\KaTeX}`;
  2284. });
  2285. it("should preserve spaces after control-symbol macro", function() {
  2286. expect`\text{\% y}`.toParseLike(r`\text{x y}`,
  2287. new Settings({macros: {"\\%": "x"}}));
  2288. });
  2289. it("should preserve spaces after control-symbol function", function() {
  2290. expect`\text{\' }`.toParse();
  2291. });
  2292. it("should consume spaces between arguments", function() {
  2293. expect`\text{\foo 1 2}`.toParseLike(r`\text{12end}`,
  2294. new Settings({macros: {"\\foo": "#1#2end"}}));
  2295. expect`\text{\foo {1} {2}}`.toParseLike(r`\text{12end}`,
  2296. new Settings({macros: {"\\foo": "#1#2end"}}));
  2297. });
  2298. it("should allow for multiple expansion", function() {
  2299. expect`1\foo2`.toParseLike("1aa2", new Settings({macros: {
  2300. "\\foo": "\\bar\\bar",
  2301. "\\bar": "a",
  2302. }}));
  2303. });
  2304. it("should allow for multiple expansion with argument", function() {
  2305. expect`1\foo2`.toParseLike("12222", new Settings({macros: {
  2306. "\\foo": "\\bar{#1}\\bar{#1}",
  2307. "\\bar": "#1#1",
  2308. }}));
  2309. });
  2310. it("should allow for macro argument", function() {
  2311. expect`\foo\bar`.toParseLike("(x)", new Settings({macros: {
  2312. "\\foo": "(#1)",
  2313. "\\bar": "x",
  2314. }}));
  2315. });
  2316. it("should allow for space macro argument (text version)", function() {
  2317. expect`\text{\foo\bar}`.toParseLike(r`\text{( )}`, new Settings({macros: {
  2318. "\\foo": "(#1)",
  2319. "\\bar": " ",
  2320. }}));
  2321. });
  2322. it("should allow for space macro argument (math version)", function() {
  2323. expect`\foo\bar`.toParseLike("()", new Settings({macros: {
  2324. "\\foo": "(#1)",
  2325. "\\bar": " ",
  2326. }}));
  2327. });
  2328. it("should allow for space second argument (text version)", function() {
  2329. expect`\text{\foo\bar\bar}`.toParseLike(r`\text{( , )}`, new Settings({macros: {
  2330. "\\foo": "(#1,#2)",
  2331. "\\bar": " ",
  2332. }}));
  2333. });
  2334. it("should allow for space second argument (math version)", function() {
  2335. expect`\foo\bar\bar`.toParseLike("(,)", new Settings({macros: {
  2336. "\\foo": "(#1,#2)",
  2337. "\\bar": " ",
  2338. }}));
  2339. });
  2340. it("should allow for empty macro argument", function() {
  2341. expect`\foo\bar`.toParseLike("()", new Settings({macros: {
  2342. "\\foo": "(#1)",
  2343. "\\bar": "",
  2344. }}));
  2345. });
  2346. // TODO: The following is not currently possible to get working, given that
  2347. // functions and macros are dealt with separately.
  2348. /*
  2349. it("should allow for space function arguments", function() {
  2350. expect`\frac\bar\bar`.toParseLike(r`\frac{}{}`, new Settings({macros: {
  2351. "\\bar": " ",
  2352. }}));
  2353. });
  2354. */
  2355. it("should build \\overset and \\underset", function() {
  2356. expect`\overset{f}{\rightarrow} Y`.toBuild();
  2357. expect("\\underset{f}{\\rightarrow} Y").toBuild();
  2358. });
  2359. it("should build \\iff, \\implies, \\impliedby", function() {
  2360. expect`X \iff Y`.toBuild();
  2361. expect`X \implies Y`.toBuild();
  2362. expect`X \impliedby Y`.toBuild();
  2363. });
  2364. it("should allow aliasing characters", function() {
  2365. expect`x’=c`.toParseLike("x'=c", new Settings({macros: {
  2366. "’": "'",
  2367. }}));
  2368. });
  2369. it("\\@firstoftwo should consume both, and avoid errors", function() {
  2370. expect`\@firstoftwo{yes}{no}`.toParseLike`yes`;
  2371. expect`\@firstoftwo{yes}{1'_2^3}`.toParseLike`yes`;
  2372. });
  2373. it("\\@ifstar should consume star but nothing else", function() {
  2374. expect`\@ifstar{yes}{no}*!`.toParseLike`yes!`;
  2375. expect`\@ifstar{yes}{no}?!`.toParseLike`no?!`;
  2376. });
  2377. it("\\@ifnextchar should not consume anything", function() {
  2378. expect`\@ifnextchar!{yes}{no}!!`.toParseLike`yes!!`;
  2379. expect`\@ifnextchar!{yes}{no}?!`.toParseLike`no?!`;
  2380. });
  2381. it("\\@ifstar should consume star but nothing else", function() {
  2382. expect`\@ifstar{yes}{no}*!`.toParseLike`yes!`;
  2383. expect`\@ifstar{yes}{no}?!`.toParseLike`no?!`;
  2384. });
  2385. it("\\TextOrMath should work immediately", function() {
  2386. expect`\TextOrMath{text}{math}`.toParseLike`math`;
  2387. });
  2388. it("\\TextOrMath should work after other math", function() {
  2389. expect`x+\TextOrMath{text}{math}`.toParseLike`x+math`;
  2390. });
  2391. it("\\TextOrMath should work immediately after \\text", function() {
  2392. expect`\text{\TextOrMath{text}{math}}`.toParseLike`\text{text}`;
  2393. });
  2394. it("\\TextOrMath should work later after \\text", function() {
  2395. expect`\text{hello \TextOrMath{text}{math}}`.toParseLike`\text{hello text}`;
  2396. });
  2397. it("\\TextOrMath should work immediately after \\text ends", function() {
  2398. expect`\text{\TextOrMath{text}{math}}\TextOrMath{text}{math}`
  2399. .toParseLike`\text{text}math`;
  2400. });
  2401. it("\\TextOrMath should work immediately after $", function() {
  2402. expect`\text{$\TextOrMath{text}{math}$}`.toParseLike`\text{$math$}`;
  2403. });
  2404. it("\\TextOrMath should work later after $", function() {
  2405. expect`\text{$x+\TextOrMath{text}{math}$}`.toParseLike`\text{$x+math$}`;
  2406. });
  2407. it("\\TextOrMath should work immediately after $ ends", function() {
  2408. expect`\text{$\TextOrMath{text}{math}$\TextOrMath{text}{math}}`
  2409. .toParseLike`\text{$math$text}`;
  2410. });
  2411. it("\\TextOrMath should work in a macro", function() {
  2412. expect`\mode\text{\mode$\mode$\mode}\mode`
  2413. .toParseLike(r`math\text{text$math$text}math`, new Settings({macros: {
  2414. "\\mode": "\\TextOrMath{text}{math}",
  2415. }}));
  2416. });
  2417. it("\\TextOrMath should work in a macro passed to \\text", function() {
  2418. expect`\text\mode`.toParseLike(r`\text t`, new Settings({macros:
  2419. {"\\mode": "\\TextOrMath{t}{m}"}}));
  2420. });
  2421. it("\\char produces literal characters", () => {
  2422. expect("\\char`a").toParseLike("\\char`\\a");
  2423. expect("\\char`\\%").toParseLike("\\char37");
  2424. expect("\\char`\\%").toParseLike("\\char'45");
  2425. expect("\\char`\\%").toParseLike('\\char"25');
  2426. expect("\\char").not.toParse();
  2427. expect("\\char`").not.toParse();
  2428. expect("\\char'").not.toParse();
  2429. expect('\\char"').not.toParse();
  2430. expect("\\char'a").not.toParse();
  2431. expect('\\char"g').not.toParse();
  2432. expect('\\char"g').not.toParse();
  2433. });
  2434. // TODO(edemaine): This doesn't work yet. Parses like `\text text`,
  2435. // which doesn't treat all four letters as an argument.
  2436. //it("\\TextOrMath should work in a macro passed to \\text", function() {
  2437. // expect`\text\mode`.toParseLike(r`\text{text}`, new Settings({macros:
  2438. // {"\\mode": "\\TextOrMath{text}{math}"});
  2439. //});
  2440. it("\\gdef defines macros", function() {
  2441. expect`\gdef\foo{x^2}\foo+\foo`.toParseLike`x^2+x^2`;
  2442. expect`\gdef{\foo}{x^2}\foo+\foo`.toParseLike`x^2+x^2`;
  2443. expect`\gdef\foo{hi}\foo+\text{\foo}`.toParseLike`hi+\text{hi}`;
  2444. expect`\gdef\foo#1{hi #1}\text{\foo{Alice}, \foo{Bob}}`
  2445. .toParseLike`\text{hi Alice, hi Bob}`;
  2446. expect`\gdef\foo#1#2{(#1,#2)}\foo 1 2+\foo 3 4`.toParseLike`(1,2)+(3,4)`;
  2447. expect`\gdef\foo#2{}`.not.toParse();
  2448. expect`\gdef\foo#1#3{}`.not.toParse();
  2449. expect`\gdef\foo#1#2#3#4#5#6#7#8#9{}`.toParse();
  2450. expect`\gdef\foo#1#2#3#4#5#6#7#8#9#10{}`.not.toParse();
  2451. expect`\gdef\foo#{}`.not.toParse();
  2452. expect`\gdef\foo\bar`.toParse();
  2453. expect`\gdef{\foo\bar}{}`.not.toParse();
  2454. expect`\gdef{}{}`.not.toParse();
  2455. // TODO: These shouldn't work, but `1` and `{1}` are currently treated
  2456. // the same, as are `\foo` and `{\foo}`.
  2457. //expect`\gdef\foo1`.not.toParse();
  2458. //expect`\gdef{\foo}{}`.not.toParse();
  2459. });
  2460. it("\\def works locally", () => {
  2461. expect("\\def\\x{1}\\x{\\def\\x{2}\\x{\\def\\x{3}\\x}\\x}\\x")
  2462. .toParseLike`1{2{3}2}1`;
  2463. expect("\\def\\x{1}\\x\\def\\x{2}\\x{\\def\\x{3}\\x\\def\\x{4}\\x}\\x")
  2464. .toParseLike`12{34}2`;
  2465. });
  2466. it("\\gdef overrides at all levels", () => {
  2467. expect("\\def\\x{1}\\x{\\def\\x{2}\\x{\\gdef\\x{3}\\x}\\x}\\x")
  2468. .toParseLike`1{2{3}3}3`;
  2469. expect("\\def\\x{1}\\x{\\def\\x{2}\\x{\\global\\def\\x{3}\\x}\\x}\\x")
  2470. .toParseLike`1{2{3}3}3`;
  2471. expect("\\def\\x{1}\\x{\\def\\x{2}\\x{\\gdef\\x{3}\\x\\def\\x{4}\\x}" +
  2472. "\\x\\def\\x{5}\\x}\\x").toParseLike`1{2{34}35}3`;
  2473. });
  2474. it("\\global needs to followed by \\def", () => {
  2475. expect`\global\def\foo{}\foo`.toParseLike``;
  2476. // TODO: This doesn't work yet; \global needs to expand argument.
  2477. //expect`\def\DEF{\def}\global\DEF\foo{}\foo`.toParseLike``;
  2478. expect`\global\foo`.not.toParse();
  2479. expect`\global\bar x`.not.toParse();
  2480. });
  2481. it("Macro arguments do not generate groups", () => {
  2482. expect("\\def\\x{1}\\x\\def\\foo#1{#1}\\foo{\\x\\def\\x{2}\\x}\\x")
  2483. .toParseLike`1122`;
  2484. });
  2485. it("\\textbf arguments do generate groups", () => {
  2486. expect("\\def\\x{1}\\x\\textbf{\\x\\def\\x{2}\\x}\\x")
  2487. .toParseLike`1\textbf{12}1`;
  2488. });
  2489. it("\\sqrt optional arguments generate groups", () => {
  2490. expect("\\def\\x{1}\\def\\y{1}\\x\\y" +
  2491. "\\sqrt[\\def\\x{2}\\x]{\\def\\y{2}\\y}\\x\\y")
  2492. .toParseLike`11\sqrt[2]{2}11`;
  2493. });
  2494. it("\\gdef changes settings.macros", () => {
  2495. const macros = {};
  2496. expect`\gdef\foo{1}`.toParse(new Settings({macros}));
  2497. expect(macros["\\foo"]).toBeTruthy();
  2498. });
  2499. it("\\def doesn't change settings.macros", () => {
  2500. const macros = {};
  2501. expect`\def\foo{1}`.toParse(new Settings({macros}));
  2502. expect(macros["\\foo"]).toBeFalsy();
  2503. });
  2504. it("\\newcommand defines new macros", () => {
  2505. expect`\newcommand\foo{x^2}\foo+\foo`.toParseLike`x^2+x^2`;
  2506. expect`\newcommand{\foo}{x^2}\foo+\foo`.toParseLike`x^2+x^2`;
  2507. // Function detection
  2508. expect`\newcommand\bar{x^2}\bar+\bar`.not.toParse();
  2509. expect`\newcommand{\bar}{x^2}\bar+\bar`.not.toParse();
  2510. // Symbol detection
  2511. expect`\newcommand\lambda{x^2}\lambda`.not.toParse();
  2512. expect`\newcommand\textdollar{x^2}\textdollar`.not.toParse();
  2513. // Macro detection
  2514. expect`\newcommand{\foo}{1}\foo\newcommand{\foo}{2}\foo`.not.toParse();
  2515. // Implicit detection
  2516. expect`\newcommand\limits{}`.not.toParse();
  2517. });
  2518. it("\\renewcommand redefines macros", () => {
  2519. expect`\renewcommand\foo{x^2}\foo+\foo`.not.toParse();
  2520. expect`\renewcommand{\foo}{x^2}\foo+\foo`.not.toParse();
  2521. expect`\renewcommand\bar{x^2}\bar+\bar`.toParseLike`x^2+x^2`;
  2522. expect`\renewcommand{\bar}{x^2}\bar+\bar`.toParseLike`x^2+x^2`;
  2523. expect`\newcommand{\foo}{1}\foo\renewcommand{\foo}{2}\foo`.toParseLike`12`;
  2524. });
  2525. it("\\providecommand (re)defines macros", () => {
  2526. expect`\providecommand\foo{x^2}\foo+\foo`.toParseLike`x^2+x^2`;
  2527. expect`\providecommand{\foo}{x^2}\foo+\foo`.toParseLike`x^2+x^2`;
  2528. expect`\providecommand\bar{x^2}\bar+\bar`.toParseLike`x^2+x^2`;
  2529. expect`\providecommand{\bar}{x^2}\bar+\bar`.toParseLike`x^2+x^2`;
  2530. expect`\newcommand{\foo}{1}\foo\providecommand{\foo}{2}\foo`
  2531. .toParseLike`12`;
  2532. expect`\providecommand{\foo}{1}\foo\renewcommand{\foo}{2}\foo`
  2533. .toParseLike`12`;
  2534. expect`\providecommand{\foo}{1}\foo\providecommand{\foo}{2}\foo`
  2535. .toParseLike`12`;
  2536. });
  2537. it("\\newcommand is local", () => {
  2538. expect`\newcommand\foo{1}\foo{\renewcommand\foo{2}\foo}\foo`
  2539. .toParseLike`1{2}1`;
  2540. });
  2541. it("\\newcommand accepts number of arguments", () => {
  2542. expect`\newcommand\foo[1]{#1^2}\foo x+\foo{y}`.toParseLike`x^2+y^2`;
  2543. expect`\newcommand\foo[10]{#1^2}\foo 0123456789`.toParseLike`0^2`;
  2544. expect`\newcommand\foo[x]{}`.not.toParse();
  2545. expect`\newcommand\foo[1.5]{}`.not.toParse();
  2546. });
  2547. // This may change in the future, if we support the extra features of
  2548. // \hspace.
  2549. it("should treat \\hspace, \\hskip like \\kern", function() {
  2550. expect`\hspace{1em}`.toParseLike`\kern1em`;
  2551. expect`\hskip{1em}`.toParseLike`\kern1em`;
  2552. });
  2553. it("should expand \\limsup as expected", () => {
  2554. expect`\limsup`.toParseLike`\mathop{\operatorname{lim\,sup}}\limits`;
  2555. });
  2556. it("should expand \\liminf as expected", () => {
  2557. expect`\liminf`.toParseLike`\mathop{\operatorname{lim\,inf}}\limits`;
  2558. });
  2559. });
  2560. describe("\\tag support", function() {
  2561. const displayMode = new Settings({displayMode: true});
  2562. it("should fail outside display mode", () => {
  2563. expect`\tag{hi}x+y`.not.toParse();
  2564. });
  2565. it("should fail with multiple tags", () => {
  2566. expect`\tag{1}\tag{2}x+y`.not.toParse(displayMode);
  2567. });
  2568. it("should build", () => {
  2569. expect`\tag{hi}x+y`.toBuild(displayMode);
  2570. });
  2571. it("should ignore location of \\tag", () => {
  2572. expect`\tag{hi}x+y`.toParseLike(r`x+y\tag{hi}`, displayMode);
  2573. });
  2574. it("should handle \\tag* like \\tag", () => {
  2575. expect`\tag{hi}x+y`.toParseLike(r`\tag*{({hi})}x+y`, displayMode);
  2576. });
  2577. });
  2578. describe("\\@binrel automatic bin/rel/ord", () => {
  2579. it("should generate proper class", () => {
  2580. expect("L\\@binrel+xR").toParseLike("L\\mathbin xR");
  2581. expect("L\\@binrel=xR").toParseLike("L\\mathrel xR");
  2582. expect("L\\@binrel xxR").toParseLike("L\\mathord xR");
  2583. expect("L\\@binrel{+}{x}R").toParseLike("L\\mathbin{{x}}R");
  2584. expect("L\\@binrel{=}{x}R").toParseLike("L\\mathrel{{x}}R");
  2585. expect("L\\@binrel{x}{x}R").toParseLike("L\\mathord{{x}}R");
  2586. });
  2587. it("should base on just first character in group", () => {
  2588. expect("L\\@binrel{+x}xR").toParseLike("L\\mathbin xR");
  2589. expect("L\\@binrel{=x}xR").toParseLike("L\\mathrel xR");
  2590. expect("L\\@binrel{xx}xR").toParseLike("L\\mathord xR");
  2591. });
  2592. });
  2593. describe("A parser taking String objects", function() {
  2594. it("should not fail on an empty String object", function() {
  2595. expect(new String("")).toParse();
  2596. });
  2597. it("should parse the same as a regular string", function() {
  2598. expect(new String("xy")).toParseLike`xy`;
  2599. expect(new String(r`\div`)).toParseLike`\div`;
  2600. expect(new String(r`\frac 1 2`)).toParseLike`\frac 1 2`;
  2601. });
  2602. });
  2603. describe("Unicode accents", function() {
  2604. it("should parse Latin-1 letters in math mode", function() {
  2605. // TODO(edemaine): Unsupported Latin-1 letters in math: ÇÐÞçðþ
  2606. expect`ÀÁÂÃÄÅÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝàáâãäåèéêëìíîïñòóôõöùúûüýÿ`
  2607. .toParseLike(
  2608. r`\grave A\acute A\hat A\tilde A\ddot A\mathring A` +
  2609. r`\grave E\acute E\hat E\ddot E` +
  2610. r`\grave I\acute I\hat I\ddot I` +
  2611. r`\tilde N` +
  2612. r`\grave O\acute O\hat O\tilde O\ddot O` +
  2613. r`\grave U\acute U\hat U\ddot U` +
  2614. r`\acute Y` +
  2615. r`\grave a\acute a\hat a\tilde a\ddot a\mathring a` +
  2616. r`\grave e\acute e\hat e\ddot e` +
  2617. r`\grave ı\acute ı\hat ı\ddot ı` +
  2618. r`\tilde n` +
  2619. r`\grave o\acute o\hat o\tilde o\ddot o` +
  2620. r`\grave u\acute u\hat u\ddot u` +
  2621. r`\acute y\ddot y`, nonstrictSettings);
  2622. });
  2623. it("should parse Latin-1 letters in text mode", function() {
  2624. // TODO(edemaine): Unsupported Latin-1 letters in text: ÇÐÞçðþ
  2625. expect`\text{ÀÁÂÃÄÅÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝàáâãäåèéêëìíîïñòóôõöùúûüýÿ}`
  2626. .toParseLike(
  2627. r`\text{\`A\'A\^A\~A\"A\r A` +
  2628. r`\`E\'E\^E\"E` +
  2629. r`\`I\'I\^I\"I` +
  2630. r`\~N` +
  2631. r`\`O\'O\^O\~O\"O` +
  2632. r`\`U\'U\^U\"U` +
  2633. r`\'Y` +
  2634. r`\`a\'a\^a\~a\"a\r a` +
  2635. r`\`e\'e\^e\"e` +
  2636. r`\`ı\'ı\^ı\"ı` +
  2637. r`\~n` +
  2638. r`\`o\'o\^o\~o\"o` +
  2639. r`\`u\'u\^u\"u` +
  2640. r`\'y\"y}`, strictSettings);
  2641. });
  2642. it("should support \\aa in text mode", function() {
  2643. expect`\text{\aa\AA}`.toParseLike(r`\text{\r a\r A}`, strictSettings);
  2644. expect`\aa`.not.toParse(strictSettings);
  2645. expect`\Aa`.not.toParse(strictSettings);
  2646. });
  2647. it("should parse combining characters", function() {
  2648. expect("A\u0301C\u0301").toParseLike(r`Á\acute C`, nonstrictSettings);
  2649. expect("\\text{A\u0301C\u0301}").toParseLike(r`\text{Á\'C}`, strictSettings);
  2650. });
  2651. it("should parse multi-accented characters", function() {
  2652. expect`ấā́ắ\text{ấā́ắ}`.toParse(nonstrictSettings);
  2653. // Doesn't parse quite the same as
  2654. // "\\text{\\'{\\^a}\\'{\\=a}\\'{\\u a}}" because of the ordgroups.
  2655. });
  2656. it("should parse accented i's and j's", function() {
  2657. expect`íȷ́`.toParseLike(r`\acute ı\acute ȷ`, nonstrictSettings);
  2658. expect`ấā́ắ\text{ấā́ắ}`.toParse(nonstrictSettings);
  2659. });
  2660. });
  2661. describe("Unicode", function() {
  2662. it("should parse negated relations", function() {
  2663. expect`∉∤∦≁≆≠≨≩≮≯≰≱⊀⊁⊈⊉⊊⊋⊬⊭⊮⊯⋠⋡⋦⋧⋨⋩⋬⋭⪇⪈⪉⪊⪵⪶⪹⪺⫋⫌`.toParse(strictSettings);
  2664. });
  2665. it("should build relations", function() {
  2666. expect`∈∋∝∼∽≂≃≅≈≊≍≎≏≐≑≒≓≖≗≜≡≤≥≦≧≪≫≬≳≷≺≻≼≽≾≿∴∵∣≔≕⩴⋘⋙⟂⊨∌`.toBuild(strictSettings);
  2667. });
  2668. it("should build big operators", function() {
  2669. expect`∏∐∑∫∬∭∮⋀⋁⋂⋃⨀⨁⨂⨄⨆`.toBuild(strictSettings);
  2670. });
  2671. it("should build more relations", function() {
  2672. expect`⊂⊃⊆⊇⊏⊐⊑⊒⊢⊣⊩⊪⊸⋈⋍⋐⋑⋔⋛⋞⋟⌢⌣⩾⪆⪌⪕⪖⪯⪰⪷⪸⫅⫆≘≙≚≛≝≞≟≲⩽⪅≶⋚⪋`.toBuild(strictSettings);
  2673. });
  2674. it("should parse symbols", function() {
  2675. expect("ð").toParse(); // warns about lacking character metrics
  2676. expect("£¥ℂℍℑℎℓℕ℘ℙℚℜℝℤℲℵℶℷℸ⅁∀∁∂∃∇∞∠∡∢♠♡♢♣♭♮♯✓°¬‼⋮\u00B7\u00A9").toBuild(strictSettings);
  2677. expect("\\text{£¥ℂℍℎ\u00A9\u00AE\uFE0F}").toBuild(strictSettings);
  2678. });
  2679. it("should build Greek capital letters", function() {
  2680. expect("\u0391\u0392\u0395\u0396\u0397\u0399\u039A\u039C\u039D" +
  2681. "\u039F\u03A1\u03A4\u03A7").toBuild(strictSettings);
  2682. });
  2683. it("should build arrows", function() {
  2684. expect`←↑→↓↔↕↖↗↘↙↚↛↞↠↢↣↦↩↪↫↬↭↮↰↱↶↷↼↽↾↾↿⇀⇁⇂⇃⇄⇆⇇⇈⇉`.toBuild(strictSettings);
  2685. });
  2686. it("should build more arrows", function() {
  2687. expect`⇊⇋⇌⇍⇎⇏⇐⇑⇒⇓⇔⇕⇚⇛⇝⟵⟶⟷⟸⟹⟺⟼`.toBuild(strictSettings);
  2688. });
  2689. it("should build binary operators", function() {
  2690. expect("±×÷∓∔∧∨∩∪≀⊎⊓⊔⊕⊖⊗⊘⊙⊚⊛⊝⊞⊟⊠⊡⊺⊻⊼⋇⋉⋊⋋⋌⋎⋏⋒⋓⩞\u22C5").toBuild(strictSettings);
  2691. });
  2692. it("should build delimiters", function() {
  2693. expect("\\left\u230A\\frac{a}{b}\\right\u230B").toBuild();
  2694. expect("\\left\u2308\\frac{a}{b}\\right\u2308").toBuild();
  2695. expect("\\left\u27ee\\frac{a}{b}\\right\u27ef").toBuild();
  2696. expect("\\left\u27e8\\frac{a}{b}\\right\u27e9").toBuild();
  2697. expect("\\left\u23b0\\frac{a}{b}\\right\u23b1").toBuild();
  2698. expect`┌x┐ └x┘`.toBuild();
  2699. expect("\u231Cx\u231D \u231Ex\u231F").toBuild();
  2700. expect("\u27E6x\u27E7").toBuild();
  2701. });
  2702. it("should build some surrogate pairs", function() {
  2703. let wideCharStr = "";
  2704. wideCharStr += String.fromCharCode(0xD835, 0xDC00); // bold A
  2705. wideCharStr += String.fromCharCode(0xD835, 0xDC68); // bold italic A
  2706. wideCharStr += String.fromCharCode(0xD835, 0xDD04); // Fraktur A
  2707. wideCharStr += String.fromCharCode(0xD835, 0xDD38); // double-struck
  2708. wideCharStr += String.fromCharCode(0xD835, 0xDC9C); // script A
  2709. wideCharStr += String.fromCharCode(0xD835, 0xDDA0); // sans serif A
  2710. wideCharStr += String.fromCharCode(0xD835, 0xDDD4); // bold sans A
  2711. wideCharStr += String.fromCharCode(0xD835, 0xDE08); // italic sans A
  2712. wideCharStr += String.fromCharCode(0xD835, 0xDE70); // monospace A
  2713. wideCharStr += String.fromCharCode(0xD835, 0xDFCE); // bold zero
  2714. wideCharStr += String.fromCharCode(0xD835, 0xDFE2); // sans serif zero
  2715. wideCharStr += String.fromCharCode(0xD835, 0xDFEC); // bold sans zero
  2716. wideCharStr += String.fromCharCode(0xD835, 0xDFF6); // monospace zero
  2717. expect(wideCharStr).toBuild(strictSettings);
  2718. let wideCharText = "\text{";
  2719. wideCharText += String.fromCharCode(0xD835, 0xDC00); // bold A
  2720. wideCharText += String.fromCharCode(0xD835, 0xDC68); // bold italic A
  2721. wideCharText += String.fromCharCode(0xD835, 0xDD04); // Fraktur A
  2722. wideCharText += String.fromCharCode(0xD835, 0xDD38); // double-struck
  2723. wideCharText += String.fromCharCode(0xD835, 0xDC9C); // script A
  2724. wideCharText += String.fromCharCode(0xD835, 0xDDA0); // sans serif A
  2725. wideCharText += String.fromCharCode(0xD835, 0xDDD4); // bold sans A
  2726. wideCharText += String.fromCharCode(0xD835, 0xDE08); // italic sans A
  2727. wideCharText += String.fromCharCode(0xD835, 0xDE70); // monospace A
  2728. wideCharText += String.fromCharCode(0xD835, 0xDFCE); // bold zero
  2729. wideCharText += String.fromCharCode(0xD835, 0xDFE2); // sans serif zero
  2730. wideCharText += String.fromCharCode(0xD835, 0xDFEC); // bold sans zero
  2731. wideCharText += String.fromCharCode(0xD835, 0xDFF6); // monospace zero
  2732. wideCharText += "}";
  2733. expect(wideCharText).toBuild(strictSettings);
  2734. });
  2735. });
  2736. describe("The maxSize setting", function() {
  2737. const rule = r`\rule{999em}{999em}`;
  2738. it("should clamp size when set", function() {
  2739. const built = getBuilt(rule, new Settings({maxSize: 5}))[0];
  2740. expect(built.style.borderRightWidth).toEqual("5em");
  2741. expect(built.style.borderTopWidth).toEqual("5em");
  2742. });
  2743. it("should not clamp size when not set", function() {
  2744. const built = getBuilt(rule)[0];
  2745. expect(built.style.borderRightWidth).toEqual("999em");
  2746. expect(built.style.borderTopWidth).toEqual("999em");
  2747. });
  2748. it("should make zero-width rules if a negative maxSize is passed", function() {
  2749. const built = getBuilt(rule, new Settings({maxSize: -5}))[0];
  2750. expect(built.style.borderRightWidth).toEqual("0em");
  2751. expect(built.style.borderTopWidth).toEqual("0em");
  2752. });
  2753. });
  2754. describe("The maxExpand setting", () => {
  2755. it("should prevent expansion", () => {
  2756. expect`\gdef\foo{1}\foo`.toParse();
  2757. expect`\gdef\foo{1}\foo`.toParse(new Settings({maxExpand: 2}));
  2758. expect`\gdef\foo{1}\foo`.not.toParse(new Settings({maxExpand: 1}));
  2759. expect`\gdef\foo{1}\foo`.not.toParse(new Settings({maxExpand: 0}));
  2760. });
  2761. it("should prevent infinite loops", () => {
  2762. expect`\gdef\foo{\foo}\foo`.not.toParse(
  2763. new Settings({maxExpand: 10}));
  2764. });
  2765. });
  2766. describe("The \\mathchoice function", function() {
  2767. const cmd = r`\sum_{k = 0}^{\infty} x^k`;
  2768. it("should render as if there is nothing other in display math", function() {
  2769. expect(`\\displaystyle\\mathchoice{${cmd}}{T}{S}{SS}`)
  2770. .toBuildLike(`\\displaystyle${cmd}`);
  2771. });
  2772. it("should render as if there is nothing other in text", function() {
  2773. expect(`\\mathchoice{D}{${cmd}}{S}{SS}`).toBuildLike(cmd);
  2774. });
  2775. it("should render as if there is nothing other in scriptstyle", function() {
  2776. expect(`x_{\\mathchoice{D}{T}{${cmd}}{SS}}`).toBuildLike(`x_{${cmd}}`);
  2777. });
  2778. it("should render as if there is nothing other in scriptscriptstyle", function() {
  2779. expect(`x_{y_{\\mathchoice{D}{T}{S}{${cmd}}}}`).toBuildLike(`x_{y_{${cmd}}}`);
  2780. });
  2781. });
  2782. describe("Newlines via \\\\ and \\newline", function() {
  2783. it("should build \\\\ and \\newline the same", () => {
  2784. expect`hello \\ world`.toBuildLike`hello \newline world`;
  2785. expect`hello \\[1ex] world`.toBuildLike(
  2786. "hello \\newline[1ex] world");
  2787. });
  2788. it("should not allow \\cr at top level", () => {
  2789. expect`hello \cr world`.not.toBuild();
  2790. });
  2791. it("array redefines and resets \\\\", () => {
  2792. expect`a\\b\begin{matrix}x&y\\z&w\end{matrix}\\c`
  2793. .toParseLike`a\newline b\begin{matrix}x&y\cr z&w\end{matrix}\newline c`;
  2794. });
  2795. });
  2796. describe("Symbols", function() {
  2797. it("should parse \\text{\\i\\j}", () => {
  2798. expect`\text{\i\j}`.toBuild(strictSettings);
  2799. });
  2800. it("should parse spacing functions in math or text mode", () => {
  2801. expect`A\;B\,C\nobreakspace \text{A\;B\,C\nobreakspace}`.toBuild(strictSettings);
  2802. });
  2803. it("should render ligature commands like their unicode characters", () => {
  2804. expect`\text{\ae\AE\oe\OE\o\O\ss}`.toBuildLike(r`\text{æÆœŒøØß}`, strictSettings);
  2805. });
  2806. });
  2807. describe("strict setting", function() {
  2808. it("should allow unicode text when not strict", () => {
  2809. expect`é`.toParse(new Settings(nonstrictSettings));
  2810. expect`試`.toParse(new Settings(nonstrictSettings));
  2811. expect`é`.toParse(new Settings({strict: "ignore"}));
  2812. expect`試`.toParse(new Settings({strict: "ignore"}));
  2813. expect`é`.toParse(new Settings({strict: () => false}));
  2814. expect`試`.toParse(new Settings({strict: () => false}));
  2815. expect`é`.toParse(new Settings({strict: () => "ignore"}));
  2816. expect`試`.toParse(new Settings({strict: () => "ignore"}));
  2817. });
  2818. it("should forbid unicode text when strict", () => {
  2819. expect`é`.not.toParse(new Settings({strict: true}));
  2820. expect`試`.not.toParse(new Settings({strict: true}));
  2821. expect`é`.not.toParse(new Settings({strict: "error"}));
  2822. expect`試`.not.toParse(new Settings({strict: "error"}));
  2823. expect`é`.not.toParse(new Settings({strict: () => true}));
  2824. expect`試`.not.toParse(new Settings({strict: () => true}));
  2825. expect`é`.not.toParse(new Settings({strict: () => "error"}));
  2826. expect`試`.not.toParse(new Settings({strict: () => "error"}));
  2827. });
  2828. it("should warn about unicode text when default", () => {
  2829. expect`é`.toWarn(new Settings());
  2830. expect`試`.toWarn(new Settings());
  2831. });
  2832. it("should always allow unicode text in text mode", () => {
  2833. expect`\text{é試}`.toParse(nonstrictSettings);
  2834. expect`\text{é試}`.toParse(strictSettings);
  2835. expect`\text{é試}`.toParse();
  2836. });
  2837. it("should warn about top-level \\newline in display mode", () => {
  2838. expect`x\\y`.toWarn(new Settings({displayMode: true}));
  2839. expect`x\\y`.toParse(new Settings({displayMode: false}));
  2840. });
  2841. });
  2842. describe("Internal __* interface", function() {
  2843. const latex = r`\sum_{k = 0}^{\infty} x^k`;
  2844. const rendered = katex.renderToString(latex);
  2845. it("__parse renders same as renderToString", () => {
  2846. const parsed = katex.__parse(latex);
  2847. expect(buildTree(parsed, latex, new Settings()).toMarkup()).toEqual(rendered);
  2848. });
  2849. it("__renderToDomTree renders same as renderToString", () => {
  2850. const tree = katex.__renderToDomTree(latex);
  2851. expect(tree.toMarkup()).toEqual(rendered);
  2852. });
  2853. it("__renderToHTMLTree renders same as renderToString sans MathML", () => {
  2854. const tree = katex.__renderToHTMLTree(latex);
  2855. const renderedSansMathML = rendered.replace(
  2856. /<span class="katex-mathml">.*?<\/span>/, '');
  2857. expect(tree.toMarkup()).toEqual(renderedSansMathML);
  2858. });
  2859. });
  2860. describe("Extending katex by new fonts and symbols", function() {
  2861. beforeAll(() => {
  2862. const fontName = "mockEasternArabicFont";
  2863. // add eastern arabic numbers to symbols table
  2864. // these symbols are ۰۱۲۳۴۵۶۷۸۹ and ٠١٢٣٤٥٦٧٨٩
  2865. for (let number = 0; number <= 9; number++) {
  2866. const persianNum = String.fromCharCode(0x0660 + number);
  2867. katex.__defineSymbol(
  2868. "math", fontName, "textord", persianNum, persianNum);
  2869. const arabicNum = String.fromCharCode(0x06F0 + number);
  2870. katex.__defineSymbol(
  2871. "math", fontName, "textord", arabicNum, arabicNum);
  2872. }
  2873. });
  2874. it("should throw on rendering new symbols with no font metrics", () => {
  2875. // Lets parse 99^11 in eastern arabic
  2876. const errorMessage = "Font metrics not found for font: mockEasternArabicFont-Regular.";
  2877. expect(() => {
  2878. katex.__renderToDomTree("۹۹^{۱۱}", strictSettings);
  2879. }).toThrow(errorMessage);
  2880. });
  2881. it("should add font metrics to metrics map and render successfully", () => {
  2882. const mockMetrics = {};
  2883. // mock font metrics for the symbols that we added previously
  2884. for (let number = 0; number <= 9; number++) {
  2885. mockMetrics[0x0660 + number] = [-0.00244140625, 0.6875, 0, 0];
  2886. mockMetrics[0x06F0 + number] = [-0.00244140625, 0.6875, 0, 0];
  2887. }
  2888. katex.__setFontMetrics('mockEasternArabicFont-Regular', mockMetrics);
  2889. expect`۹۹^{۱۱}`.toBuild();
  2890. });
  2891. it("Add new font class to new extended symbols", () => {
  2892. expect(katex.renderToString("۹۹^{۱۱}")).toMatchSnapshot();
  2893. });
  2894. });