textWysiwyg.test.tsx 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385
  1. import ReactDOM from "react-dom";
  2. import ExcalidrawApp from "../excalidraw-app";
  3. import { GlobalTestState, render, screen } from "../tests/test-utils";
  4. import { Keyboard, Pointer, UI } from "../tests/helpers/ui";
  5. import { CODES, KEYS } from "../keys";
  6. import {
  7. fireEvent,
  8. mockBoundingClientRect,
  9. restoreOriginalGetBoundingClientRect,
  10. } from "../tests/test-utils";
  11. import { queryByText } from "@testing-library/react";
  12. import { FONT_FAMILY } from "../constants";
  13. import {
  14. ExcalidrawTextElement,
  15. ExcalidrawTextElementWithContainer,
  16. } from "./types";
  17. import { API } from "../tests/helpers/api";
  18. import { mutateElement } from "./mutateElement";
  19. import { resize } from "../tests/utils";
  20. import { getOriginalContainerHeightFromCache } from "./textWysiwyg";
  21. // Unmount ReactDOM from root
  22. ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
  23. const tab = " ";
  24. const mouse = new Pointer("mouse");
  25. describe("textWysiwyg", () => {
  26. describe("start text editing", () => {
  27. const { h } = window;
  28. beforeEach(async () => {
  29. await render(<ExcalidrawApp />);
  30. h.elements = [];
  31. });
  32. it("should prefer editing selected text element (non-bindable container present)", async () => {
  33. const line = API.createElement({
  34. type: "line",
  35. width: 100,
  36. height: 0,
  37. points: [
  38. [0, 0],
  39. [100, 0],
  40. ],
  41. });
  42. const textSize = 20;
  43. const text = API.createElement({
  44. type: "text",
  45. text: "ola",
  46. x: line.width / 2 - textSize / 2,
  47. y: -textSize / 2,
  48. width: textSize,
  49. height: textSize,
  50. });
  51. h.elements = [text, line];
  52. API.setSelectedElements([text]);
  53. Keyboard.keyPress(KEYS.ENTER);
  54. expect(h.state.editingElement?.id).toBe(text.id);
  55. expect(
  56. (h.state.editingElement as ExcalidrawTextElement).containerId,
  57. ).toBe(null);
  58. });
  59. it("should prefer editing selected text element (bindable container present)", async () => {
  60. const container = API.createElement({
  61. type: "rectangle",
  62. width: 100,
  63. boundElements: [],
  64. });
  65. const textSize = 20;
  66. const boundText = API.createElement({
  67. type: "text",
  68. text: "ola",
  69. x: container.width / 2 - textSize / 2,
  70. y: container.height / 2 - textSize / 2,
  71. width: textSize,
  72. height: textSize,
  73. containerId: container.id,
  74. });
  75. const boundText2 = API.createElement({
  76. type: "text",
  77. text: "ola",
  78. x: container.width / 2 - textSize / 2,
  79. y: container.height / 2 - textSize / 2,
  80. width: textSize,
  81. height: textSize,
  82. containerId: container.id,
  83. });
  84. h.elements = [container, boundText, boundText2];
  85. mutateElement(container, {
  86. boundElements: [{ type: "text", id: boundText.id }],
  87. });
  88. API.setSelectedElements([boundText2]);
  89. Keyboard.keyPress(KEYS.ENTER);
  90. expect(h.state.editingElement?.id).toBe(boundText2.id);
  91. });
  92. it("should not create bound text on ENTER if text exists at container center", () => {
  93. const container = API.createElement({
  94. type: "rectangle",
  95. width: 100,
  96. });
  97. const textSize = 20;
  98. const text = API.createElement({
  99. type: "text",
  100. text: "ola",
  101. x: container.width / 2 - textSize / 2,
  102. y: container.height / 2 - textSize / 2,
  103. width: textSize,
  104. height: textSize,
  105. containerId: container.id,
  106. });
  107. mutateElement(container, {
  108. boundElements: [{ type: "text", id: text.id }],
  109. });
  110. h.elements = [container, text];
  111. API.setSelectedElements([container]);
  112. Keyboard.keyPress(KEYS.ENTER);
  113. expect(h.state.editingElement?.id).toBe(text.id);
  114. });
  115. it("should edit existing bound text on ENTER even if higher z-index unbound text exists at container center", () => {
  116. const container = API.createElement({
  117. type: "rectangle",
  118. width: 100,
  119. boundElements: [],
  120. });
  121. const textSize = 20;
  122. const boundText = API.createElement({
  123. type: "text",
  124. text: "ola",
  125. x: container.width / 2 - textSize / 2,
  126. y: container.height / 2 - textSize / 2,
  127. width: textSize,
  128. height: textSize,
  129. containerId: container.id,
  130. });
  131. const boundText2 = API.createElement({
  132. type: "text",
  133. text: "ola",
  134. x: container.width / 2 - textSize / 2,
  135. y: container.height / 2 - textSize / 2,
  136. width: textSize,
  137. height: textSize,
  138. containerId: container.id,
  139. });
  140. h.elements = [container, boundText, boundText2];
  141. mutateElement(container, {
  142. boundElements: [{ type: "text", id: boundText.id }],
  143. });
  144. API.setSelectedElements([container]);
  145. Keyboard.keyPress(KEYS.ENTER);
  146. expect(h.state.editingElement?.id).toBe(boundText.id);
  147. });
  148. it("should edit text under cursor when clicked with text tool", () => {
  149. const text = API.createElement({
  150. type: "text",
  151. text: "ola",
  152. x: 60,
  153. y: 0,
  154. width: 100,
  155. height: 100,
  156. });
  157. h.elements = [text];
  158. UI.clickTool("text");
  159. mouse.clickAt(text.x + 50, text.y + 50);
  160. const editor = document.querySelector(
  161. ".excalidraw-textEditorContainer > textarea",
  162. ) as HTMLTextAreaElement;
  163. expect(editor).not.toBe(null);
  164. expect(h.state.editingElement?.id).toBe(text.id);
  165. expect(h.elements.length).toBe(1);
  166. });
  167. it("should edit text under cursor when double-clicked with selection tool", () => {
  168. const text = API.createElement({
  169. type: "text",
  170. text: "ola",
  171. x: 60,
  172. y: 0,
  173. width: 100,
  174. height: 100,
  175. });
  176. h.elements = [text];
  177. UI.clickTool("selection");
  178. mouse.doubleClickAt(text.x + 50, text.y + 50);
  179. const editor = document.querySelector(
  180. ".excalidraw-textEditorContainer > textarea",
  181. ) as HTMLTextAreaElement;
  182. expect(editor).not.toBe(null);
  183. expect(h.state.editingElement?.id).toBe(text.id);
  184. expect(h.elements.length).toBe(1);
  185. });
  186. });
  187. describe("Test container-unbound text", () => {
  188. const { h } = window;
  189. const dimensions = { height: 400, width: 800 };
  190. let textarea: HTMLTextAreaElement;
  191. let textElement: ExcalidrawTextElement;
  192. beforeAll(() => {
  193. mockBoundingClientRect(dimensions);
  194. });
  195. beforeEach(async () => {
  196. await render(<ExcalidrawApp />);
  197. //@ts-ignore
  198. h.app.refreshDeviceState(h.app.excalidrawContainerRef.current!);
  199. textElement = UI.createElement("text");
  200. mouse.clickOn(textElement);
  201. textarea = document.querySelector(
  202. ".excalidraw-textEditorContainer > textarea",
  203. )!;
  204. });
  205. afterAll(() => {
  206. restoreOriginalGetBoundingClientRect();
  207. });
  208. it("should add a tab at the start of the first line", () => {
  209. const event = new KeyboardEvent("keydown", { key: KEYS.TAB });
  210. textarea.value = "Line#1\nLine#2";
  211. // cursor: "|Line#1\nLine#2"
  212. textarea.selectionStart = 0;
  213. textarea.selectionEnd = 0;
  214. textarea.dispatchEvent(event);
  215. expect(textarea.value).toEqual(`${tab}Line#1\nLine#2`);
  216. // cursor: " |Line#1\nLine#2"
  217. expect(textarea.selectionStart).toEqual(4);
  218. expect(textarea.selectionEnd).toEqual(4);
  219. });
  220. it("should add a tab at the start of the second line", () => {
  221. const event = new KeyboardEvent("keydown", { key: KEYS.TAB });
  222. textarea.value = "Line#1\nLine#2";
  223. // cursor: "Line#1\nLin|e#2"
  224. textarea.selectionStart = 10;
  225. textarea.selectionEnd = 10;
  226. textarea.dispatchEvent(event);
  227. expect(textarea.value).toEqual(`Line#1\n${tab}Line#2`);
  228. // cursor: "Line#1\n Lin|e#2"
  229. expect(textarea.selectionStart).toEqual(14);
  230. expect(textarea.selectionEnd).toEqual(14);
  231. });
  232. it("should add a tab at the start of the first and second line", () => {
  233. const event = new KeyboardEvent("keydown", { key: KEYS.TAB });
  234. textarea.value = "Line#1\nLine#2\nLine#3";
  235. // cursor: "Li|ne#1\nLi|ne#2\nLine#3"
  236. textarea.selectionStart = 2;
  237. textarea.selectionEnd = 9;
  238. textarea.dispatchEvent(event);
  239. expect(textarea.value).toEqual(`${tab}Line#1\n${tab}Line#2\nLine#3`);
  240. // cursor: " Li|ne#1\n Li|ne#2\nLine#3"
  241. expect(textarea.selectionStart).toEqual(6);
  242. expect(textarea.selectionEnd).toEqual(17);
  243. });
  244. it("should remove a tab at the start of the first line", () => {
  245. const event = new KeyboardEvent("keydown", {
  246. key: KEYS.TAB,
  247. shiftKey: true,
  248. });
  249. textarea.value = `${tab}Line#1\nLine#2`;
  250. // cursor: "| Line#1\nLine#2"
  251. textarea.selectionStart = 0;
  252. textarea.selectionEnd = 0;
  253. textarea.dispatchEvent(event);
  254. expect(textarea.value).toEqual(`Line#1\nLine#2`);
  255. // cursor: "|Line#1\nLine#2"
  256. expect(textarea.selectionStart).toEqual(0);
  257. expect(textarea.selectionEnd).toEqual(0);
  258. });
  259. it("should remove a tab at the start of the second line", () => {
  260. const event = new KeyboardEvent("keydown", {
  261. key: KEYS.TAB,
  262. shiftKey: true,
  263. });
  264. // cursor: "Line#1\n Lin|e#2"
  265. textarea.value = `Line#1\n${tab}Line#2`;
  266. textarea.selectionStart = 15;
  267. textarea.selectionEnd = 15;
  268. textarea.dispatchEvent(event);
  269. expect(textarea.value).toEqual(`Line#1\nLine#2`);
  270. // cursor: "Line#1\nLin|e#2"
  271. expect(textarea.selectionStart).toEqual(11);
  272. expect(textarea.selectionEnd).toEqual(11);
  273. });
  274. it("should remove a tab at the start of the first and second line", () => {
  275. const event = new KeyboardEvent("keydown", {
  276. key: KEYS.TAB,
  277. shiftKey: true,
  278. });
  279. // cursor: " Li|ne#1\n Li|ne#2\nLine#3"
  280. textarea.value = `${tab}Line#1\n${tab}Line#2\nLine#3`;
  281. textarea.selectionStart = 6;
  282. textarea.selectionEnd = 17;
  283. textarea.dispatchEvent(event);
  284. expect(textarea.value).toEqual(`Line#1\nLine#2\nLine#3`);
  285. // cursor: "Li|ne#1\nLi|ne#2\nLine#3"
  286. expect(textarea.selectionStart).toEqual(2);
  287. expect(textarea.selectionEnd).toEqual(9);
  288. });
  289. it("should remove a tab at the start of the second line and cursor stay on this line", () => {
  290. const event = new KeyboardEvent("keydown", {
  291. key: KEYS.TAB,
  292. shiftKey: true,
  293. });
  294. // cursor: "Line#1\n | Line#2"
  295. textarea.value = `Line#1\n${tab}Line#2`;
  296. textarea.selectionStart = 9;
  297. textarea.selectionEnd = 9;
  298. textarea.dispatchEvent(event);
  299. // cursor: "Line#1\n|Line#2"
  300. expect(textarea.selectionStart).toEqual(7);
  301. // expect(textarea.selectionEnd).toEqual(7);
  302. });
  303. it("should remove partial tabs", () => {
  304. const event = new KeyboardEvent("keydown", {
  305. key: KEYS.TAB,
  306. shiftKey: true,
  307. });
  308. // cursor: "Line#1\n Line#|2"
  309. textarea.value = `Line#1\n Line#2`;
  310. textarea.selectionStart = 15;
  311. textarea.selectionEnd = 15;
  312. textarea.dispatchEvent(event);
  313. expect(textarea.value).toEqual(`Line#1\nLine#2`);
  314. });
  315. it("should remove nothing", () => {
  316. const event = new KeyboardEvent("keydown", {
  317. key: KEYS.TAB,
  318. shiftKey: true,
  319. });
  320. // cursor: "Line#1\n Li|ne#2"
  321. textarea.value = `Line#1\nLine#2`;
  322. textarea.selectionStart = 9;
  323. textarea.selectionEnd = 9;
  324. textarea.dispatchEvent(event);
  325. expect(textarea.value).toEqual(`Line#1\nLine#2`);
  326. });
  327. it("should resize text via shortcuts while in wysiwyg", () => {
  328. textarea.value = "abc def";
  329. const origFontSize = textElement.fontSize;
  330. textarea.dispatchEvent(
  331. new KeyboardEvent("keydown", {
  332. key: KEYS.CHEVRON_RIGHT,
  333. ctrlKey: true,
  334. shiftKey: true,
  335. }),
  336. );
  337. expect(textElement.fontSize).toBe(origFontSize * 1.1);
  338. textarea.dispatchEvent(
  339. new KeyboardEvent("keydown", {
  340. key: KEYS.CHEVRON_LEFT,
  341. ctrlKey: true,
  342. shiftKey: true,
  343. }),
  344. );
  345. expect(textElement.fontSize).toBe(origFontSize);
  346. });
  347. it("zooming via keyboard should zoom canvas", () => {
  348. expect(h.state.zoom.value).toBe(1);
  349. textarea.dispatchEvent(
  350. new KeyboardEvent("keydown", {
  351. code: CODES.MINUS,
  352. ctrlKey: true,
  353. }),
  354. );
  355. expect(h.state.zoom.value).toBe(0.9);
  356. textarea.dispatchEvent(
  357. new KeyboardEvent("keydown", {
  358. code: CODES.NUM_SUBTRACT,
  359. ctrlKey: true,
  360. }),
  361. );
  362. expect(h.state.zoom.value).toBe(0.8);
  363. textarea.dispatchEvent(
  364. new KeyboardEvent("keydown", {
  365. code: CODES.NUM_ADD,
  366. ctrlKey: true,
  367. }),
  368. );
  369. expect(h.state.zoom.value).toBe(0.9);
  370. textarea.dispatchEvent(
  371. new KeyboardEvent("keydown", {
  372. code: CODES.EQUAL,
  373. ctrlKey: true,
  374. }),
  375. );
  376. expect(h.state.zoom.value).toBe(1);
  377. });
  378. it("text should never go beyond max width", async () => {
  379. UI.clickTool("text");
  380. mouse.clickAt(750, 300);
  381. textarea = document.querySelector(
  382. ".excalidraw-textEditorContainer > textarea",
  383. )!;
  384. fireEvent.change(textarea, {
  385. target: {
  386. value:
  387. "Excalidraw is an opensource virtual collaborative whiteboard for sketching hand-drawn like diagrams!",
  388. },
  389. });
  390. textarea.dispatchEvent(new Event("input"));
  391. await new Promise((cb) => setTimeout(cb, 0));
  392. textarea.blur();
  393. expect(textarea.style.width).toBe("792px");
  394. expect(h.elements[0].width).toBe(1000);
  395. });
  396. });
  397. describe("Test container-bound text", () => {
  398. let rectangle: any;
  399. const { h } = window;
  400. beforeEach(async () => {
  401. await render(<ExcalidrawApp />);
  402. h.elements = [];
  403. rectangle = UI.createElement("rectangle", {
  404. x: 10,
  405. y: 20,
  406. width: 90,
  407. height: 75,
  408. });
  409. });
  410. it("should bind text to container when double clicked inside filled container", async () => {
  411. const rectangle = API.createElement({
  412. type: "rectangle",
  413. x: 10,
  414. y: 20,
  415. width: 90,
  416. height: 75,
  417. backgroundColor: "red",
  418. });
  419. h.elements = [rectangle];
  420. expect(h.elements.length).toBe(1);
  421. expect(h.elements[0].id).toBe(rectangle.id);
  422. mouse.doubleClickAt(rectangle.x + 10, rectangle.y + 10);
  423. expect(h.elements.length).toBe(2);
  424. const text = h.elements[1] as ExcalidrawTextElementWithContainer;
  425. expect(text.type).toBe("text");
  426. expect(text.containerId).toBe(rectangle.id);
  427. expect(rectangle.boundElements).toStrictEqual([
  428. { id: text.id, type: "text" },
  429. ]);
  430. mouse.down();
  431. const editor = document.querySelector(
  432. ".excalidraw-textEditorContainer > textarea",
  433. ) as HTMLTextAreaElement;
  434. fireEvent.change(editor, { target: { value: "Hello World!" } });
  435. await new Promise((r) => setTimeout(r, 0));
  436. editor.blur();
  437. expect(rectangle.boundElements).toStrictEqual([
  438. { id: text.id, type: "text" },
  439. ]);
  440. });
  441. it("should bind text to container when double clicked on center of transparent container", async () => {
  442. const rectangle = API.createElement({
  443. type: "rectangle",
  444. x: 10,
  445. y: 20,
  446. width: 90,
  447. height: 75,
  448. backgroundColor: "transparent",
  449. });
  450. h.elements = [rectangle];
  451. mouse.doubleClickAt(rectangle.x + 10, rectangle.y + 10);
  452. expect(h.elements.length).toBe(2);
  453. let text = h.elements[1] as ExcalidrawTextElementWithContainer;
  454. expect(text.type).toBe("text");
  455. expect(text.containerId).toBe(null);
  456. mouse.down();
  457. let editor = document.querySelector(
  458. ".excalidraw-textEditorContainer > textarea",
  459. ) as HTMLTextAreaElement;
  460. await new Promise((r) => setTimeout(r, 0));
  461. editor.blur();
  462. mouse.doubleClickAt(
  463. rectangle.x + rectangle.width / 2,
  464. rectangle.y + rectangle.height / 2,
  465. );
  466. expect(h.elements.length).toBe(3);
  467. text = h.elements[1] as ExcalidrawTextElementWithContainer;
  468. expect(text.type).toBe("text");
  469. expect(text.containerId).toBe(rectangle.id);
  470. mouse.down();
  471. editor = document.querySelector(
  472. ".excalidraw-textEditorContainer > textarea",
  473. ) as HTMLTextAreaElement;
  474. fireEvent.change(editor, { target: { value: "Hello World!" } });
  475. await new Promise((r) => setTimeout(r, 0));
  476. editor.blur();
  477. expect(rectangle.boundElements).toStrictEqual([
  478. { id: text.id, type: "text" },
  479. ]);
  480. });
  481. it("should bind text to container when clicked on container and enter pressed", async () => {
  482. expect(h.elements.length).toBe(1);
  483. expect(h.elements[0].id).toBe(rectangle.id);
  484. Keyboard.keyPress(KEYS.ENTER);
  485. expect(h.elements.length).toBe(2);
  486. const text = h.elements[1] as ExcalidrawTextElementWithContainer;
  487. expect(text.type).toBe("text");
  488. expect(text.containerId).toBe(rectangle.id);
  489. const editor = document.querySelector(
  490. ".excalidraw-textEditorContainer > textarea",
  491. ) as HTMLTextAreaElement;
  492. await new Promise((r) => setTimeout(r, 0));
  493. fireEvent.change(editor, { target: { value: "Hello World!" } });
  494. editor.blur();
  495. expect(rectangle.boundElements).toStrictEqual([
  496. { id: text.id, type: "text" },
  497. ]);
  498. });
  499. it("should bind text to container when double clicked on container stroke", async () => {
  500. const rectangle = API.createElement({
  501. type: "rectangle",
  502. x: 10,
  503. y: 20,
  504. width: 90,
  505. height: 75,
  506. strokeWidth: 4,
  507. });
  508. h.elements = [rectangle];
  509. expect(h.elements.length).toBe(1);
  510. expect(h.elements[0].id).toBe(rectangle.id);
  511. mouse.doubleClickAt(rectangle.x + 2, rectangle.y + 2);
  512. expect(h.elements.length).toBe(2);
  513. const text = h.elements[1] as ExcalidrawTextElementWithContainer;
  514. expect(text.type).toBe("text");
  515. expect(text.containerId).toBe(rectangle.id);
  516. expect(rectangle.boundElements).toStrictEqual([
  517. { id: text.id, type: "text" },
  518. ]);
  519. mouse.down();
  520. const editor = document.querySelector(
  521. ".excalidraw-textEditorContainer > textarea",
  522. ) as HTMLTextAreaElement;
  523. fireEvent.change(editor, { target: { value: "Hello World!" } });
  524. await new Promise((r) => setTimeout(r, 0));
  525. editor.blur();
  526. expect(rectangle.boundElements).toStrictEqual([
  527. { id: text.id, type: "text" },
  528. ]);
  529. });
  530. it("shouldn't bind to non-text-bindable containers", async () => {
  531. const freedraw = API.createElement({
  532. type: "freedraw",
  533. width: 100,
  534. height: 0,
  535. });
  536. h.elements = [freedraw];
  537. UI.clickTool("text");
  538. mouse.clickAt(
  539. freedraw.x + freedraw.width / 2,
  540. freedraw.y + freedraw.height / 2,
  541. );
  542. const editor = document.querySelector(
  543. ".excalidraw-textEditorContainer > textarea",
  544. ) as HTMLTextAreaElement;
  545. fireEvent.change(editor, {
  546. target: {
  547. value: "Hello World!",
  548. },
  549. });
  550. fireEvent.keyDown(editor, { key: KEYS.ESCAPE });
  551. editor.dispatchEvent(new Event("input"));
  552. expect(freedraw.boundElements).toBe(null);
  553. expect(h.elements[1].type).toBe("text");
  554. expect((h.elements[1] as ExcalidrawTextElement).containerId).toBe(null);
  555. });
  556. ["freedraw", "line"].forEach((type: any) => {
  557. it(`shouldn't create text element when pressing 'Enter' key on ${type} `, async () => {
  558. h.elements = [];
  559. const element = UI.createElement(type, {
  560. width: 100,
  561. height: 50,
  562. });
  563. API.setSelectedElements([element]);
  564. Keyboard.keyPress(KEYS.ENTER);
  565. expect(h.elements.length).toBe(1);
  566. });
  567. });
  568. it("should'nt bind text to container when not double clicked on center", async () => {
  569. expect(h.elements.length).toBe(1);
  570. expect(h.elements[0].id).toBe(rectangle.id);
  571. // clicking somewhere on top left
  572. mouse.doubleClickAt(rectangle.x + 20, rectangle.y + 20);
  573. expect(h.elements.length).toBe(2);
  574. const text = h.elements[1] as ExcalidrawTextElementWithContainer;
  575. expect(text.type).toBe("text");
  576. expect(text.containerId).toBe(null);
  577. mouse.down();
  578. const editor = document.querySelector(
  579. ".excalidraw-textEditorContainer > textarea",
  580. ) as HTMLTextAreaElement;
  581. fireEvent.change(editor, { target: { value: "Hello World!" } });
  582. await new Promise((r) => setTimeout(r, 0));
  583. editor.blur();
  584. expect(rectangle.boundElements).toBe(null);
  585. });
  586. it("should update font family correctly on undo/redo by selecting bounded text when font family was updated", async () => {
  587. expect(h.elements.length).toBe(1);
  588. mouse.doubleClickAt(
  589. rectangle.x + rectangle.width / 2,
  590. rectangle.y + rectangle.height / 2,
  591. );
  592. mouse.down();
  593. const text = h.elements[1] as ExcalidrawTextElementWithContainer;
  594. let editor = document.querySelector(
  595. ".excalidraw-textEditorContainer > textarea",
  596. ) as HTMLTextAreaElement;
  597. await new Promise((r) => setTimeout(r, 0));
  598. fireEvent.change(editor, { target: { value: "Hello World!" } });
  599. editor.blur();
  600. expect(text.fontFamily).toEqual(FONT_FAMILY.Virgil);
  601. UI.clickTool("text");
  602. mouse.clickAt(
  603. rectangle.x + rectangle.width / 2,
  604. rectangle.y + rectangle.height / 2,
  605. );
  606. mouse.down();
  607. editor = document.querySelector(
  608. ".excalidraw-textEditorContainer > textarea",
  609. ) as HTMLTextAreaElement;
  610. editor.select();
  611. fireEvent.click(screen.getByTitle(/code/i));
  612. await new Promise((r) => setTimeout(r, 0));
  613. editor.blur();
  614. expect(
  615. (h.elements[1] as ExcalidrawTextElementWithContainer).fontFamily,
  616. ).toEqual(FONT_FAMILY.Cascadia);
  617. //undo
  618. Keyboard.withModifierKeys({ ctrl: true }, () => {
  619. Keyboard.keyPress(KEYS.Z);
  620. });
  621. expect(
  622. (h.elements[1] as ExcalidrawTextElementWithContainer).fontFamily,
  623. ).toEqual(FONT_FAMILY.Virgil);
  624. //redo
  625. Keyboard.withModifierKeys({ ctrl: true, shift: true }, () => {
  626. Keyboard.keyPress(KEYS.Z);
  627. });
  628. expect(
  629. (h.elements[1] as ExcalidrawTextElementWithContainer).fontFamily,
  630. ).toEqual(FONT_FAMILY.Cascadia);
  631. });
  632. it("should wrap text and vertcially center align once text submitted", async () => {
  633. expect(h.elements.length).toBe(1);
  634. Keyboard.keyDown(KEYS.ENTER);
  635. let text = h.elements[1] as ExcalidrawTextElementWithContainer;
  636. let editor = document.querySelector(
  637. ".excalidraw-textEditorContainer > textarea",
  638. ) as HTMLTextAreaElement;
  639. fireEvent.change(editor, {
  640. target: {
  641. value: "Hello World!",
  642. },
  643. });
  644. editor.dispatchEvent(new Event("input"));
  645. await new Promise((cb) => setTimeout(cb, 0));
  646. editor.blur();
  647. text = h.elements[1] as ExcalidrawTextElementWithContainer;
  648. expect(text.text).toBe("Hello \nWorld!");
  649. expect(text.originalText).toBe("Hello World!");
  650. expect(text.y).toBe(
  651. rectangle.y + h.elements[0].height / 2 - text.height / 2,
  652. );
  653. expect(text.x).toBe(25);
  654. expect(text.height).toBe(48);
  655. expect(text.width).toBe(60);
  656. // Edit and text by removing second line and it should
  657. // still vertically align correctly
  658. mouse.select(rectangle);
  659. Keyboard.keyPress(KEYS.ENTER);
  660. editor = document.querySelector(
  661. ".excalidraw-textEditorContainer > textarea",
  662. ) as HTMLTextAreaElement;
  663. fireEvent.change(editor, {
  664. target: {
  665. value: "Hello",
  666. },
  667. });
  668. editor.dispatchEvent(new Event("input"));
  669. await new Promise((r) => setTimeout(r, 0));
  670. editor.blur();
  671. text = h.elements[1] as ExcalidrawTextElementWithContainer;
  672. expect(text.text).toBe("Hello");
  673. expect(text.originalText).toBe("Hello");
  674. expect(text.height).toBe(24);
  675. expect(text.width).toBe(50);
  676. expect(text.y).toBe(
  677. rectangle.y + h.elements[0].height / 2 - text.height / 2,
  678. );
  679. expect(text.x).toBe(30);
  680. });
  681. it("should unbind bound text when unbind action from context menu is triggered", async () => {
  682. expect(h.elements.length).toBe(1);
  683. expect(h.elements[0].id).toBe(rectangle.id);
  684. Keyboard.keyPress(KEYS.ENTER);
  685. expect(h.elements.length).toBe(2);
  686. const text = h.elements[1] as ExcalidrawTextElementWithContainer;
  687. expect(text.containerId).toBe(rectangle.id);
  688. const editor = document.querySelector(
  689. ".excalidraw-textEditorContainer > textarea",
  690. ) as HTMLTextAreaElement;
  691. await new Promise((r) => setTimeout(r, 0));
  692. fireEvent.change(editor, { target: { value: "Hello World!" } });
  693. editor.blur();
  694. expect(rectangle.boundElements).toStrictEqual([
  695. { id: text.id, type: "text" },
  696. ]);
  697. mouse.reset();
  698. UI.clickTool("selection");
  699. mouse.clickAt(10, 20);
  700. mouse.down();
  701. mouse.up();
  702. fireEvent.contextMenu(GlobalTestState.canvas, {
  703. button: 2,
  704. clientX: 20,
  705. clientY: 30,
  706. });
  707. const contextMenu = document.querySelector(".context-menu");
  708. fireEvent.click(queryByText(contextMenu as HTMLElement, "Unbind text")!);
  709. expect(h.elements[0].boundElements).toEqual([]);
  710. expect((h.elements[1] as ExcalidrawTextElement).containerId).toEqual(
  711. null,
  712. );
  713. });
  714. it("shouldn't bind to container if container has bound text", async () => {
  715. expect(h.elements.length).toBe(1);
  716. Keyboard.keyPress(KEYS.ENTER);
  717. expect(h.elements.length).toBe(2);
  718. // Bind first text
  719. const text = h.elements[1] as ExcalidrawTextElementWithContainer;
  720. expect(text.containerId).toBe(rectangle.id);
  721. const editor = document.querySelector(
  722. ".excalidraw-textEditorContainer > textarea",
  723. ) as HTMLTextAreaElement;
  724. await new Promise((r) => setTimeout(r, 0));
  725. fireEvent.change(editor, { target: { value: "Hello World!" } });
  726. editor.blur();
  727. expect(rectangle.boundElements).toStrictEqual([
  728. { id: text.id, type: "text" },
  729. ]);
  730. mouse.select(rectangle);
  731. Keyboard.keyPress(KEYS.ENTER);
  732. expect(h.elements.length).toBe(2);
  733. expect(rectangle.boundElements).toStrictEqual([
  734. { id: h.elements[1].id, type: "text" },
  735. ]);
  736. expect(text.containerId).toBe(rectangle.id);
  737. });
  738. it("should respect text alignment when resizing", async () => {
  739. Keyboard.keyPress(KEYS.ENTER);
  740. let editor = document.querySelector(
  741. ".excalidraw-textEditorContainer > textarea",
  742. ) as HTMLTextAreaElement;
  743. await new Promise((r) => setTimeout(r, 0));
  744. fireEvent.change(editor, { target: { value: "Hello" } });
  745. editor.blur();
  746. // should center align horizontally and vertically by default
  747. resize(rectangle, "ne", [rectangle.x + 100, rectangle.y - 100]);
  748. expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(`
  749. Array [
  750. 85,
  751. 5,
  752. ]
  753. `);
  754. mouse.select(rectangle);
  755. Keyboard.keyPress(KEYS.ENTER);
  756. editor = document.querySelector(
  757. ".excalidraw-textEditorContainer > textarea",
  758. ) as HTMLTextAreaElement;
  759. editor.select();
  760. fireEvent.click(screen.getByTitle("Left"));
  761. await new Promise((r) => setTimeout(r, 0));
  762. fireEvent.click(screen.getByTitle("Align bottom"));
  763. await new Promise((r) => setTimeout(r, 0));
  764. editor.blur();
  765. // should left align horizontally and bottom vertically after resize
  766. resize(rectangle, "ne", [rectangle.x + 100, rectangle.y - 100]);
  767. expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(`
  768. Array [
  769. 15,
  770. 66,
  771. ]
  772. `);
  773. mouse.select(rectangle);
  774. Keyboard.keyPress(KEYS.ENTER);
  775. editor = document.querySelector(
  776. ".excalidraw-textEditorContainer > textarea",
  777. ) as HTMLTextAreaElement;
  778. editor.select();
  779. fireEvent.click(screen.getByTitle("Right"));
  780. fireEvent.click(screen.getByTitle("Align top"));
  781. await new Promise((r) => setTimeout(r, 0));
  782. editor.blur();
  783. // should right align horizontally and top vertically after resize
  784. resize(rectangle, "ne", [rectangle.x + 100, rectangle.y - 100]);
  785. expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(`
  786. Array [
  787. 375,
  788. -539,
  789. ]
  790. `);
  791. });
  792. it("should always bind to selected container and insert it in correct position", async () => {
  793. const rectangle2 = UI.createElement("rectangle", {
  794. x: 5,
  795. y: 10,
  796. width: 120,
  797. height: 100,
  798. });
  799. API.setSelectedElements([rectangle]);
  800. Keyboard.keyPress(KEYS.ENTER);
  801. expect(h.elements.length).toBe(3);
  802. expect(h.elements[1].type).toBe("text");
  803. const text = h.elements[1] as ExcalidrawTextElementWithContainer;
  804. expect(text.type).toBe("text");
  805. expect(text.containerId).toBe(rectangle.id);
  806. mouse.down();
  807. const editor = document.querySelector(
  808. ".excalidraw-textEditorContainer > textarea",
  809. ) as HTMLTextAreaElement;
  810. fireEvent.change(editor, { target: { value: "Hello World!" } });
  811. await new Promise((r) => setTimeout(r, 0));
  812. editor.blur();
  813. expect(rectangle2.boundElements).toBeNull();
  814. expect(rectangle.boundElements).toStrictEqual([
  815. { id: text.id, type: "text" },
  816. ]);
  817. });
  818. it("should scale font size correctly when resizing using shift", async () => {
  819. Keyboard.keyPress(KEYS.ENTER);
  820. const editor = document.querySelector(
  821. ".excalidraw-textEditorContainer > textarea",
  822. ) as HTMLTextAreaElement;
  823. await new Promise((r) => setTimeout(r, 0));
  824. fireEvent.change(editor, { target: { value: "Hello" } });
  825. editor.blur();
  826. const textElement = h.elements[1] as ExcalidrawTextElement;
  827. expect(rectangle.width).toBe(90);
  828. expect(rectangle.height).toBe(75);
  829. expect(textElement.fontSize).toBe(20);
  830. resize(rectangle, "ne", [rectangle.x + 100, rectangle.y - 50], {
  831. shift: true,
  832. });
  833. expect(rectangle.width).toBe(200);
  834. expect(rectangle.height).toBe(166.66666666666669);
  835. expect(textElement.fontSize).toBe(47.5);
  836. });
  837. it("should bind text correctly when container duplicated with alt-drag", async () => {
  838. Keyboard.keyPress(KEYS.ENTER);
  839. const editor = document.querySelector(
  840. ".excalidraw-textEditorContainer > textarea",
  841. ) as HTMLTextAreaElement;
  842. await new Promise((r) => setTimeout(r, 0));
  843. fireEvent.change(editor, { target: { value: "Hello" } });
  844. editor.blur();
  845. expect(h.elements.length).toBe(2);
  846. mouse.select(rectangle);
  847. Keyboard.withModifierKeys({ alt: true }, () => {
  848. mouse.down(rectangle.x + 10, rectangle.y + 10);
  849. mouse.up(rectangle.x + 10, rectangle.y + 10);
  850. });
  851. expect(h.elements.length).toBe(4);
  852. const duplicatedRectangle = h.elements[0];
  853. const duplicatedText = h
  854. .elements[1] as ExcalidrawTextElementWithContainer;
  855. const originalRect = h.elements[2];
  856. const originalText = h.elements[3] as ExcalidrawTextElementWithContainer;
  857. expect(originalRect.boundElements).toStrictEqual([
  858. { id: originalText.id, type: "text" },
  859. ]);
  860. expect(originalText.containerId).toBe(originalRect.id);
  861. expect(duplicatedRectangle.boundElements).toStrictEqual([
  862. { id: duplicatedText.id, type: "text" },
  863. ]);
  864. expect(duplicatedText.containerId).toBe(duplicatedRectangle.id);
  865. });
  866. it("undo should work", async () => {
  867. Keyboard.keyPress(KEYS.ENTER);
  868. const editor = document.querySelector(
  869. ".excalidraw-textEditorContainer > textarea",
  870. ) as HTMLTextAreaElement;
  871. await new Promise((r) => setTimeout(r, 0));
  872. fireEvent.change(editor, { target: { value: "Hello" } });
  873. editor.blur();
  874. expect(rectangle.boundElements).toStrictEqual([
  875. { id: h.elements[1].id, type: "text" },
  876. ]);
  877. let text = h.elements[1] as ExcalidrawTextElementWithContainer;
  878. const originalRectX = rectangle.x;
  879. const originalRectY = rectangle.y;
  880. const originalTextX = text.x;
  881. const originalTextY = text.y;
  882. mouse.select(rectangle);
  883. mouse.downAt(rectangle.x, rectangle.y);
  884. mouse.moveTo(rectangle.x + 100, rectangle.y + 50);
  885. mouse.up(rectangle.x + 100, rectangle.y + 50);
  886. expect(rectangle.x).toBe(80);
  887. expect(rectangle.y).toBe(-35);
  888. expect(text.x).toBe(85);
  889. expect(text.y).toBe(-30);
  890. Keyboard.withModifierKeys({ ctrl: true }, () => {
  891. Keyboard.keyPress(KEYS.Z);
  892. });
  893. expect(rectangle.x).toBe(originalRectX);
  894. expect(rectangle.y).toBe(originalRectY);
  895. text = h.elements[1] as ExcalidrawTextElementWithContainer;
  896. expect(text.x).toBe(originalTextX);
  897. expect(text.y).toBe(originalTextY);
  898. expect(rectangle.boundElements).toStrictEqual([
  899. { id: text.id, type: "text" },
  900. ]);
  901. expect(text.containerId).toBe(rectangle.id);
  902. });
  903. it("should not allow bound text with only whitespaces", async () => {
  904. Keyboard.keyPress(KEYS.ENTER);
  905. const editor = document.querySelector(
  906. ".excalidraw-textEditorContainer > textarea",
  907. ) as HTMLTextAreaElement;
  908. await new Promise((r) => setTimeout(r, 0));
  909. fireEvent.change(editor, { target: { value: " " } });
  910. editor.blur();
  911. expect(rectangle.boundElements).toStrictEqual([]);
  912. expect(h.elements[1].isDeleted).toBe(true);
  913. });
  914. it("should restore original container height and clear cache once text is unbind", async () => {
  915. const originalRectHeight = rectangle.height;
  916. expect(rectangle.height).toBe(originalRectHeight);
  917. Keyboard.keyPress(KEYS.ENTER);
  918. const editor = document.querySelector(
  919. ".excalidraw-textEditorContainer > textarea",
  920. ) as HTMLTextAreaElement;
  921. await new Promise((r) => setTimeout(r, 0));
  922. fireEvent.change(editor, {
  923. target: { value: "Online whiteboard collaboration made easy" },
  924. });
  925. editor.blur();
  926. expect(rectangle.height).toBe(178);
  927. mouse.select(rectangle);
  928. fireEvent.contextMenu(GlobalTestState.canvas, {
  929. button: 2,
  930. clientX: 20,
  931. clientY: 30,
  932. });
  933. const contextMenu = document.querySelector(".context-menu");
  934. fireEvent.click(queryByText(contextMenu as HTMLElement, "Unbind text")!);
  935. expect(h.elements[0].boundElements).toEqual([]);
  936. expect(getOriginalContainerHeightFromCache(rectangle.id)).toBe(null);
  937. expect(rectangle.height).toBe(originalRectHeight);
  938. });
  939. it("should reset the container height cache when resizing", async () => {
  940. Keyboard.keyPress(KEYS.ENTER);
  941. expect(getOriginalContainerHeightFromCache(rectangle.id)).toBe(75);
  942. let editor = document.querySelector(
  943. ".excalidraw-textEditorContainer > textarea",
  944. ) as HTMLTextAreaElement;
  945. await new Promise((r) => setTimeout(r, 0));
  946. fireEvent.change(editor, { target: { value: "Hello" } });
  947. editor.blur();
  948. resize(rectangle, "ne", [rectangle.x + 100, rectangle.y - 100]);
  949. expect(rectangle.height).toBe(156);
  950. expect(getOriginalContainerHeightFromCache(rectangle.id)).toBe(null);
  951. mouse.select(rectangle);
  952. Keyboard.keyPress(KEYS.ENTER);
  953. editor = document.querySelector(
  954. ".excalidraw-textEditorContainer > textarea",
  955. ) as HTMLTextAreaElement;
  956. await new Promise((r) => setTimeout(r, 0));
  957. editor.blur();
  958. expect(rectangle.height).toBe(156);
  959. // cache updated again
  960. expect(getOriginalContainerHeightFromCache(rectangle.id)).toBe(156);
  961. });
  962. it("should reset the container height cache when font properties updated", async () => {
  963. Keyboard.keyPress(KEYS.ENTER);
  964. expect(getOriginalContainerHeightFromCache(rectangle.id)).toBe(75);
  965. const editor = document.querySelector(
  966. ".excalidraw-textEditorContainer > textarea",
  967. ) as HTMLTextAreaElement;
  968. await new Promise((r) => setTimeout(r, 0));
  969. fireEvent.change(editor, { target: { value: "Hello World!" } });
  970. editor.blur();
  971. mouse.select(rectangle);
  972. Keyboard.keyPress(KEYS.ENTER);
  973. fireEvent.click(screen.getByTitle(/code/i));
  974. expect(
  975. (h.elements[1] as ExcalidrawTextElementWithContainer).fontFamily,
  976. ).toEqual(FONT_FAMILY.Cascadia);
  977. expect(getOriginalContainerHeightFromCache(rectangle.id)).toBe(75);
  978. fireEvent.click(screen.getByTitle(/Very large/i));
  979. expect(
  980. (h.elements[1] as ExcalidrawTextElementWithContainer).fontSize,
  981. ).toEqual(36);
  982. expect(getOriginalContainerHeightFromCache(rectangle.id)).toBe(
  983. 96.39999999999999,
  984. );
  985. });
  986. describe("should align correctly", () => {
  987. let editor: HTMLTextAreaElement;
  988. beforeEach(async () => {
  989. Keyboard.keyPress(KEYS.ENTER);
  990. editor = document.querySelector(
  991. ".excalidraw-textEditorContainer > textarea",
  992. ) as HTMLTextAreaElement;
  993. await new Promise((r) => setTimeout(r, 0));
  994. fireEvent.change(editor, { target: { value: "Hello" } });
  995. editor.blur();
  996. mouse.select(rectangle);
  997. Keyboard.keyPress(KEYS.ENTER);
  998. editor = document.querySelector(
  999. ".excalidraw-textEditorContainer > textarea",
  1000. ) as HTMLTextAreaElement;
  1001. editor.select();
  1002. });
  1003. it("when top left", async () => {
  1004. fireEvent.click(screen.getByTitle("Left"));
  1005. fireEvent.click(screen.getByTitle("Align top"));
  1006. expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(`
  1007. Array [
  1008. 15,
  1009. 25,
  1010. ]
  1011. `);
  1012. });
  1013. it("when top center", async () => {
  1014. fireEvent.click(screen.getByTitle("Center"));
  1015. fireEvent.click(screen.getByTitle("Align top"));
  1016. expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(`
  1017. Array [
  1018. 30,
  1019. 25,
  1020. ]
  1021. `);
  1022. });
  1023. it("when top right", async () => {
  1024. fireEvent.click(screen.getByTitle("Right"));
  1025. fireEvent.click(screen.getByTitle("Align top"));
  1026. expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(`
  1027. Array [
  1028. 45,
  1029. 25,
  1030. ]
  1031. `);
  1032. });
  1033. it("when center left", async () => {
  1034. fireEvent.click(screen.getByTitle("Center vertically"));
  1035. fireEvent.click(screen.getByTitle("Left"));
  1036. expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(`
  1037. Array [
  1038. 15,
  1039. 45.5,
  1040. ]
  1041. `);
  1042. });
  1043. it("when center center", async () => {
  1044. fireEvent.click(screen.getByTitle("Center"));
  1045. fireEvent.click(screen.getByTitle("Center vertically"));
  1046. expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(`
  1047. Array [
  1048. 30,
  1049. 45.5,
  1050. ]
  1051. `);
  1052. });
  1053. it("when center right", async () => {
  1054. fireEvent.click(screen.getByTitle("Right"));
  1055. fireEvent.click(screen.getByTitle("Center vertically"));
  1056. expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(`
  1057. Array [
  1058. 45,
  1059. 45.5,
  1060. ]
  1061. `);
  1062. });
  1063. it("when bottom left", async () => {
  1064. fireEvent.click(screen.getByTitle("Left"));
  1065. fireEvent.click(screen.getByTitle("Align bottom"));
  1066. expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(`
  1067. Array [
  1068. 15,
  1069. 66,
  1070. ]
  1071. `);
  1072. });
  1073. it("when bottom center", async () => {
  1074. fireEvent.click(screen.getByTitle("Center"));
  1075. fireEvent.click(screen.getByTitle("Align bottom"));
  1076. expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(`
  1077. Array [
  1078. 30,
  1079. 66,
  1080. ]
  1081. `);
  1082. });
  1083. it("when bottom right", async () => {
  1084. fireEvent.click(screen.getByTitle("Right"));
  1085. fireEvent.click(screen.getByTitle("Align bottom"));
  1086. expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(`
  1087. Array [
  1088. 45,
  1089. 66,
  1090. ]
  1091. `);
  1092. });
  1093. });
  1094. it("should wrap text in a container when wrap text in container triggered from context menu", async () => {
  1095. UI.clickTool("text");
  1096. mouse.clickAt(20, 30);
  1097. const editor = document.querySelector(
  1098. ".excalidraw-textEditorContainer > textarea",
  1099. ) as HTMLTextAreaElement;
  1100. fireEvent.change(editor, {
  1101. target: {
  1102. value: "Excalidraw is an opensource virtual collaborative whiteboard",
  1103. },
  1104. });
  1105. editor.dispatchEvent(new Event("input"));
  1106. await new Promise((cb) => setTimeout(cb, 0));
  1107. editor.blur();
  1108. expect(h.elements[1].width).toBe(600);
  1109. expect(h.elements[1].height).toBe(24);
  1110. expect((h.elements[1] as ExcalidrawTextElement).text).toBe(
  1111. "Excalidraw is an opensource virtual collaborative whiteboard",
  1112. );
  1113. API.setSelectedElements([h.elements[1]]);
  1114. fireEvent.contextMenu(GlobalTestState.canvas, {
  1115. button: 2,
  1116. clientX: 20,
  1117. clientY: 30,
  1118. });
  1119. const contextMenu = document.querySelector(".context-menu");
  1120. fireEvent.click(
  1121. queryByText(contextMenu as HTMLElement, "Wrap text in a container")!,
  1122. );
  1123. expect(h.elements.length).toBe(3);
  1124. expect(h.elements[1]).toEqual(
  1125. expect.objectContaining({
  1126. angle: 0,
  1127. backgroundColor: "transparent",
  1128. boundElements: [
  1129. {
  1130. id: h.elements[2].id,
  1131. type: "text",
  1132. },
  1133. ],
  1134. fillStyle: "hachure",
  1135. groupIds: [],
  1136. height: 34,
  1137. isDeleted: false,
  1138. link: null,
  1139. locked: false,
  1140. opacity: 100,
  1141. roughness: 1,
  1142. roundness: {
  1143. type: 3,
  1144. },
  1145. strokeColor: "#000000",
  1146. strokeStyle: "solid",
  1147. strokeWidth: 1,
  1148. type: "rectangle",
  1149. updated: 1,
  1150. version: 1,
  1151. width: 610,
  1152. x: 15,
  1153. y: 25,
  1154. }),
  1155. );
  1156. expect((h.elements[2] as ExcalidrawTextElement).text).toBe(
  1157. "Excalidraw is an opensource virtual collaborative whiteboard",
  1158. );
  1159. });
  1160. });
  1161. });