You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
433 lines
11 KiB
TypeScript
433 lines
11 KiB
TypeScript
import { BOUND_TEXT_PADDING, FONT_FAMILY } from "../constants";
|
|
import { getLineHeight } from "../fonts";
|
|
import { API } from "../tests/helpers/api";
|
|
import {
|
|
computeContainerDimensionForBoundText,
|
|
getContainerCoords,
|
|
getBoundTextMaxWidth,
|
|
getBoundTextMaxHeight,
|
|
wrapText,
|
|
detectLineHeight,
|
|
getLineHeightInPx,
|
|
parseTokens,
|
|
} from "./textElement";
|
|
import type { ExcalidrawTextElementWithContainer, FontString } from "./types";
|
|
|
|
describe("Test wrapText", () => {
|
|
const font = "20px Cascadia, width: Segoe UI Emoji" as FontString;
|
|
|
|
it("shouldn't add new lines for trailing spaces", () => {
|
|
const text = "Hello whats up ";
|
|
const maxWidth = 200 - BOUND_TEXT_PADDING * 2;
|
|
const res = wrapText(text, font, maxWidth);
|
|
expect(res).toBe(text);
|
|
});
|
|
|
|
it("should work with emojis", () => {
|
|
const text = "😀";
|
|
const maxWidth = 1;
|
|
const res = wrapText(text, font, maxWidth);
|
|
expect(res).toBe("😀");
|
|
});
|
|
|
|
it("should show the text correctly when max width reached", () => {
|
|
const text = "Hello😀";
|
|
const maxWidth = 10;
|
|
const res = wrapText(text, font, maxWidth);
|
|
expect(res).toBe("H\ne\nl\nl\no\n😀");
|
|
});
|
|
|
|
describe("When text doesn't contain new lines", () => {
|
|
const text = "Hello whats up";
|
|
|
|
[
|
|
{
|
|
desc: "break all words when width of each word is less than container width",
|
|
width: 80,
|
|
res: `Hello \nwhats \nup`,
|
|
},
|
|
{
|
|
desc: "break all characters when width of each character is less than container width",
|
|
width: 25,
|
|
res: `H
|
|
e
|
|
l
|
|
l
|
|
o
|
|
w
|
|
h
|
|
a
|
|
t
|
|
s
|
|
u
|
|
p`,
|
|
},
|
|
{
|
|
desc: "break words as per the width",
|
|
|
|
width: 140,
|
|
res: `Hello whats \nup`,
|
|
},
|
|
{
|
|
desc: "fit the container",
|
|
|
|
width: 250,
|
|
res: "Hello whats up",
|
|
},
|
|
{
|
|
desc: "should push the word if its equal to max width",
|
|
width: 60,
|
|
res: `Hello
|
|
whats
|
|
up`,
|
|
},
|
|
].forEach((data) => {
|
|
it(`should ${data.desc}`, () => {
|
|
const res = wrapText(text, font, data.width - BOUND_TEXT_PADDING * 2);
|
|
expect(res).toEqual(data.res);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("When text contain new lines", () => {
|
|
const text = `Hello
|
|
whats up`;
|
|
[
|
|
{
|
|
desc: "break all words when width of each word is less than container width",
|
|
width: 80,
|
|
res: `Hello\nwhats \nup`,
|
|
},
|
|
{
|
|
desc: "break all characters when width of each character is less than container width",
|
|
width: 25,
|
|
res: `H
|
|
e
|
|
l
|
|
l
|
|
o
|
|
w
|
|
h
|
|
a
|
|
t
|
|
s
|
|
u
|
|
p`,
|
|
},
|
|
{
|
|
desc: "break words as per the width",
|
|
|
|
width: 150,
|
|
res: `Hello
|
|
whats up`,
|
|
},
|
|
{
|
|
desc: "fit the container",
|
|
|
|
width: 250,
|
|
res: `Hello
|
|
whats up`,
|
|
},
|
|
].forEach((data) => {
|
|
it(`should respect new lines and ${data.desc}`, () => {
|
|
const res = wrapText(text, font, data.width - BOUND_TEXT_PADDING * 2);
|
|
expect(res).toEqual(data.res);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("When text is long", () => {
|
|
const text = `hellolongtextthisiswhatsupwithyouIamtypingggggandtypinggg break it now`;
|
|
[
|
|
{
|
|
desc: "fit characters of long string as per container width",
|
|
width: 170,
|
|
res: `hellolongtextth\nisiswhatsupwith\nyouIamtypingggg\ngandtypinggg \nbreak it now`,
|
|
},
|
|
|
|
{
|
|
desc: "fit characters of long string as per container width and break words as per the width",
|
|
|
|
width: 130,
|
|
res: `hellolongte
|
|
xtthisiswha
|
|
tsupwithyou
|
|
Iamtypinggg
|
|
ggandtyping
|
|
gg break it
|
|
now`,
|
|
},
|
|
{
|
|
desc: "fit the long text when container width is greater than text length and move the rest to next line",
|
|
|
|
width: 600,
|
|
res: `hellolongtextthisiswhatsupwithyouIamtypingggggandtypinggg \nbreak it now`,
|
|
},
|
|
].forEach((data) => {
|
|
it(`should ${data.desc}`, () => {
|
|
const res = wrapText(text, font, data.width - BOUND_TEXT_PADDING * 2);
|
|
expect(res).toEqual(data.res);
|
|
});
|
|
});
|
|
});
|
|
|
|
it("should wrap the text correctly when word length is exactly equal to max width", () => {
|
|
const text = "Hello Excalidraw";
|
|
// Length of "Excalidraw" is 100 and exacty equal to max width
|
|
const res = wrapText(text, font, 100);
|
|
expect(res).toEqual(`Hello \nExcalidraw`);
|
|
});
|
|
|
|
it("should return the text as is if max width is invalid", () => {
|
|
const text = "Hello Excalidraw";
|
|
expect(wrapText(text, font, NaN)).toEqual(text);
|
|
expect(wrapText(text, font, -1)).toEqual(text);
|
|
expect(wrapText(text, font, Infinity)).toEqual(text);
|
|
});
|
|
|
|
it("should wrap the text correctly when text contains hyphen", () => {
|
|
let text =
|
|
"Wikipedia is hosted by Wikimedia- Foundation, a non-profit organization that also hosts a range-of other projects";
|
|
const res = wrapText(text, font, 110);
|
|
expect(res).toBe(
|
|
`Wikipedia \nis hosted \nby \nWikimedia-\nFoundation,\na non-\nprofit \norganizati\non that \nalso hosts\na range-of\nother \nprojects`,
|
|
);
|
|
|
|
text = "Hello thereusing-now";
|
|
expect(wrapText(text, font, 100)).toEqual("Hello \nthereusin\ng-now");
|
|
});
|
|
});
|
|
|
|
describe("Test parseTokens", () => {
|
|
it("should split into tokens correctly", () => {
|
|
let text = "Excalidraw is a virtual collaborative whiteboard";
|
|
expect(parseTokens(text)).toEqual([
|
|
"Excalidraw",
|
|
"is",
|
|
"a",
|
|
"virtual",
|
|
"collaborative",
|
|
"whiteboard",
|
|
]);
|
|
|
|
text =
|
|
"Wikipedia is hosted by Wikimedia- Foundation, a non-profit organization that also hosts a range-of other projects";
|
|
expect(parseTokens(text)).toEqual([
|
|
"Wikipedia",
|
|
"is",
|
|
"hosted",
|
|
"by",
|
|
"Wikimedia-",
|
|
"",
|
|
"Foundation,",
|
|
"a",
|
|
"non-",
|
|
"profit",
|
|
"organization",
|
|
"that",
|
|
"also",
|
|
"hosts",
|
|
"a",
|
|
"range-",
|
|
"of",
|
|
"other",
|
|
"projects",
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe("Test measureText", () => {
|
|
describe("Test getContainerCoords", () => {
|
|
const params = { width: 200, height: 100, x: 10, y: 20 };
|
|
|
|
it("should compute coords correctly when ellipse", () => {
|
|
const element = API.createElement({
|
|
type: "ellipse",
|
|
...params,
|
|
});
|
|
expect(getContainerCoords(element)).toEqual({
|
|
x: 44.2893218813452455,
|
|
y: 39.64466094067262,
|
|
});
|
|
});
|
|
|
|
it("should compute coords correctly when rectangle", () => {
|
|
const element = API.createElement({
|
|
type: "rectangle",
|
|
...params,
|
|
});
|
|
expect(getContainerCoords(element)).toEqual({
|
|
x: 15,
|
|
y: 25,
|
|
});
|
|
});
|
|
|
|
it("should compute coords correctly when diamond", () => {
|
|
const element = API.createElement({
|
|
type: "diamond",
|
|
...params,
|
|
});
|
|
expect(getContainerCoords(element)).toEqual({
|
|
x: 65,
|
|
y: 50,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("Test computeContainerDimensionForBoundText", () => {
|
|
const params = {
|
|
width: 178,
|
|
height: 194,
|
|
};
|
|
|
|
it("should compute container height correctly for rectangle", () => {
|
|
const element = API.createElement({
|
|
type: "rectangle",
|
|
...params,
|
|
});
|
|
expect(computeContainerDimensionForBoundText(150, element.type)).toEqual(
|
|
160,
|
|
);
|
|
});
|
|
|
|
it("should compute container height correctly for ellipse", () => {
|
|
const element = API.createElement({
|
|
type: "ellipse",
|
|
...params,
|
|
});
|
|
expect(computeContainerDimensionForBoundText(150, element.type)).toEqual(
|
|
226,
|
|
);
|
|
});
|
|
|
|
it("should compute container height correctly for diamond", () => {
|
|
const element = API.createElement({
|
|
type: "diamond",
|
|
...params,
|
|
});
|
|
expect(computeContainerDimensionForBoundText(150, element.type)).toEqual(
|
|
320,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("Test getBoundTextMaxWidth", () => {
|
|
const params = {
|
|
width: 178,
|
|
height: 194,
|
|
};
|
|
|
|
it("should return max width when container is rectangle", () => {
|
|
const container = API.createElement({ type: "rectangle", ...params });
|
|
expect(getBoundTextMaxWidth(container, null)).toBe(168);
|
|
});
|
|
|
|
it("should return max width when container is ellipse", () => {
|
|
const container = API.createElement({ type: "ellipse", ...params });
|
|
expect(getBoundTextMaxWidth(container, null)).toBe(116);
|
|
});
|
|
|
|
it("should return max width when container is diamond", () => {
|
|
const container = API.createElement({ type: "diamond", ...params });
|
|
expect(getBoundTextMaxWidth(container, null)).toBe(79);
|
|
});
|
|
});
|
|
|
|
describe("Test getBoundTextMaxHeight", () => {
|
|
const params = {
|
|
width: 178,
|
|
height: 194,
|
|
id: '"container-id',
|
|
};
|
|
|
|
const boundTextElement = API.createElement({
|
|
type: "text",
|
|
id: "text-id",
|
|
x: 560.51171875,
|
|
y: 202.033203125,
|
|
width: 154,
|
|
height: 175,
|
|
fontSize: 20,
|
|
fontFamily: 1,
|
|
text: "Excalidraw is a\nvirtual \nopensource \nwhiteboard for \nsketching \nhand-drawn like\ndiagrams",
|
|
textAlign: "center",
|
|
verticalAlign: "middle",
|
|
containerId: params.id,
|
|
}) as ExcalidrawTextElementWithContainer;
|
|
|
|
it("should return max height when container is rectangle", () => {
|
|
const container = API.createElement({ type: "rectangle", ...params });
|
|
expect(getBoundTextMaxHeight(container, boundTextElement)).toBe(184);
|
|
});
|
|
|
|
it("should return max height when container is ellipse", () => {
|
|
const container = API.createElement({ type: "ellipse", ...params });
|
|
expect(getBoundTextMaxHeight(container, boundTextElement)).toBe(127);
|
|
});
|
|
|
|
it("should return max height when container is diamond", () => {
|
|
const container = API.createElement({ type: "diamond", ...params });
|
|
expect(getBoundTextMaxHeight(container, boundTextElement)).toBe(87);
|
|
});
|
|
|
|
it("should return max height when container is arrow", () => {
|
|
const container = API.createElement({
|
|
type: "arrow",
|
|
...params,
|
|
});
|
|
expect(getBoundTextMaxHeight(container, boundTextElement)).toBe(194);
|
|
});
|
|
|
|
it("should return max height when container is arrow and height is less than threshold", () => {
|
|
const container = API.createElement({
|
|
type: "arrow",
|
|
...params,
|
|
height: 70,
|
|
boundElements: [{ type: "text", id: "text-id" }],
|
|
});
|
|
|
|
expect(getBoundTextMaxHeight(container, boundTextElement)).toBe(
|
|
boundTextElement.height,
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
const textElement = API.createElement({
|
|
type: "text",
|
|
text: "Excalidraw is a\nvirtual \nopensource \nwhiteboard for \nsketching \nhand-drawn like\ndiagrams",
|
|
fontSize: 20,
|
|
fontFamily: 1,
|
|
height: 175,
|
|
});
|
|
|
|
describe("Test detectLineHeight", () => {
|
|
it("should return correct line height", () => {
|
|
expect(detectLineHeight(textElement)).toBe(1.25);
|
|
});
|
|
});
|
|
|
|
describe("Test getLineHeightInPx", () => {
|
|
it("should return correct line height", () => {
|
|
expect(
|
|
getLineHeightInPx(textElement.fontSize, textElement.lineHeight),
|
|
).toBe(25);
|
|
});
|
|
});
|
|
|
|
describe("Test getDefaultLineHeight", () => {
|
|
it("should return line height using default font family when not passed", () => {
|
|
//@ts-ignore
|
|
expect(getLineHeight()).toBe(1.25);
|
|
});
|
|
|
|
it("should return line height using default font family for unknown font", () => {
|
|
const UNKNOWN_FONT = 5;
|
|
expect(getLineHeight(UNKNOWN_FONT)).toBe(1.25);
|
|
});
|
|
|
|
it("should return correct line height", () => {
|
|
expect(getLineHeight(FONT_FAMILY.Cascadia)).toBe(1.2);
|
|
});
|
|
});
|