- Fix router import path in main.js - Handle Django REST Framework pagination format in API calls - Add getTemplates function to project API - Restart frontend development server
977 lines
29 KiB
TypeScript
977 lines
29 KiB
TypeScript
import '../../vitest.extend';
|
|
import { Textbox } from './Textbox';
|
|
|
|
import { afterEach, beforeAll, describe, expect, it } from 'vitest';
|
|
import { Canvas } from '../canvas/Canvas';
|
|
import { stylesFromArray } from '../util';
|
|
import { FabricText } from './Text/Text';
|
|
import { IText } from './IText/IText';
|
|
import { Point } from '../Point';
|
|
import { createPointerEvent } from '../../test/utils';
|
|
|
|
describe('Textbox', () => {
|
|
let canvas: Canvas;
|
|
|
|
beforeAll(() => {
|
|
canvas = new Canvas();
|
|
});
|
|
|
|
afterEach(() => {
|
|
canvas.clear();
|
|
});
|
|
|
|
it('fromObject', async () => {
|
|
const textbox = await Textbox.fromObject({
|
|
text: 'The quick \nbrown \nfox',
|
|
});
|
|
expect(textbox).toMatchObjectSnapshot();
|
|
expect(textbox).toMatchObjectSnapshot({ includeDefaultValues: false });
|
|
});
|
|
|
|
it('toObject with styles', () => {
|
|
const textbox = new Textbox('The quick \nbrown \nfox', {
|
|
width: 120,
|
|
styles: {
|
|
'0': {
|
|
'5': { fill: 'red' },
|
|
'6': { fill: 'red' },
|
|
'7': { fill: 'red' },
|
|
'8': { fill: 'red' },
|
|
},
|
|
'1': {
|
|
'3': { underline: true },
|
|
'4': { underline: true },
|
|
'5': { underline: true },
|
|
},
|
|
'2': {
|
|
'0': { underline: true },
|
|
'1': { underline: true },
|
|
},
|
|
},
|
|
});
|
|
expect(textbox).toMatchObjectSnapshot();
|
|
});
|
|
|
|
it('stylesToArray edge case', () => {
|
|
const textbox = new Textbox('The quick \nbrown \nfox', {
|
|
width: 120,
|
|
styles: {
|
|
'0': {
|
|
'5': { fill: 'red' },
|
|
'6': { fill: 'red' },
|
|
'7': { fill: 'red' },
|
|
'8': { fill: 'red' },
|
|
'9': { fill: 'red' },
|
|
'10': { fill: 'red' },
|
|
},
|
|
'2': {
|
|
'0': { fill: 'red' },
|
|
},
|
|
},
|
|
});
|
|
expect(textbox.toObject().styles).toMatchSnapshot();
|
|
});
|
|
|
|
it('fromObject with styles', async () => {
|
|
const textbox = new Textbox('The quick \nbrown \nfox', {
|
|
width: 120,
|
|
styles: {
|
|
'0': {
|
|
'5': { fill: 'red' },
|
|
'6': { fill: 'red' },
|
|
'7': { fill: 'red' },
|
|
'8': { fill: 'red' },
|
|
},
|
|
'1': {
|
|
'3': { underline: true },
|
|
'4': { underline: true },
|
|
'5': { underline: true },
|
|
},
|
|
'2': {
|
|
'0': { underline: true },
|
|
'1': { underline: true },
|
|
},
|
|
},
|
|
});
|
|
const textbox2 = await Textbox.fromObject(textbox.toObject());
|
|
expect(textbox2.toObject()).toEqual(textbox.toObject());
|
|
expect(textbox2.styles !== textbox.styles).toBeTruthy();
|
|
for (const a in textbox2.styles) {
|
|
for (const b in textbox2.styles[a]) {
|
|
expect(textbox2.styles[a][b] !== textbox.styles[a][b]).toBeTruthy();
|
|
expect(textbox2.styles[a][b]).toEqual(textbox.styles[a][b]);
|
|
}
|
|
}
|
|
});
|
|
|
|
it('constructor', () => {
|
|
const textbox = new Textbox('test');
|
|
|
|
expect(textbox, 'should be instance of Textbox').toBeInstanceOf(Textbox);
|
|
expect(textbox, 'should be instance of IText').toBeInstanceOf(IText);
|
|
expect(textbox, 'should be instance of FabricText').toBeInstanceOf(
|
|
FabricText,
|
|
);
|
|
});
|
|
|
|
it('constructor with width', () => {
|
|
const textbox = new Textbox('test', { width: 400 });
|
|
|
|
expect(textbox.width, 'width is taken by contstructor').toBe(400);
|
|
});
|
|
|
|
it('constructor with width too small', () => {
|
|
const textbox = new Textbox('test', { width: 5 });
|
|
|
|
expect(
|
|
Math.round(textbox.width),
|
|
'width is calculated by constructor',
|
|
).toBe(56);
|
|
});
|
|
|
|
it('initial properties', () => {
|
|
const textbox = new Textbox('test');
|
|
|
|
expect(textbox.text, 'text value should be set').toBe('test');
|
|
expect(
|
|
textbox.constructor,
|
|
'constructor type should be Textbox',
|
|
).toHaveProperty('type', 'Textbox');
|
|
expect(textbox.styles, 'styles should be empty object').toEqual({});
|
|
expect(
|
|
Textbox.cacheProperties.indexOf('width') > -1,
|
|
'width is in cacheProperties',
|
|
).toBeTruthy();
|
|
});
|
|
|
|
it('isEndOfWrapping', () => {
|
|
const textbox = new Textbox('a q o m s g\np q r s t w', {
|
|
width: 70,
|
|
});
|
|
|
|
expect(
|
|
textbox.isEndOfWrapping(0),
|
|
'first line is not end of wrapping',
|
|
).toBe(false);
|
|
expect(
|
|
textbox.isEndOfWrapping(1),
|
|
'second line is not end of wrapping',
|
|
).toBe(false);
|
|
expect(
|
|
textbox.isEndOfWrapping(2),
|
|
'line before an hard break is end of wrapping',
|
|
).toBe(true);
|
|
expect(textbox.isEndOfWrapping(3), 'line 3 is not end of wrapping').toBe(
|
|
false,
|
|
);
|
|
expect(textbox.isEndOfWrapping(4), 'line 4 is not end of wrapping').toBe(
|
|
false,
|
|
);
|
|
expect(textbox.isEndOfWrapping(5), 'last line is end of wrapping').toBe(
|
|
true,
|
|
);
|
|
});
|
|
|
|
it('_removeExtraneousStyles', () => {
|
|
const textbox = new Textbox('a q o m s g\np q r s t w', {
|
|
width: 40,
|
|
styles: {
|
|
0: { 0: { fontSize: 4 } },
|
|
1: { 0: { fontSize: 4 } },
|
|
2: { 0: { fontSize: 4 } },
|
|
3: { 0: { fontSize: 4 } },
|
|
4: { 0: { fontSize: 4 } },
|
|
5: { 0: { fontSize: 4 } },
|
|
},
|
|
});
|
|
|
|
expect(textbox.styles[3], 'style line 3 exists').toEqual({
|
|
0: { fontSize: 4 },
|
|
});
|
|
expect(textbox.styles[4], 'style line 4 exists').toEqual({
|
|
0: { fontSize: 4 },
|
|
});
|
|
expect(textbox.styles[5], 'style line 5 exists').toEqual({
|
|
0: { fontSize: 4 },
|
|
});
|
|
|
|
textbox._removeExtraneousStyles();
|
|
|
|
expect(textbox.styles[2], 'style line 2 has been removed').toBeUndefined();
|
|
expect(textbox.styles[3], 'style line 3 has been removed').toBeUndefined();
|
|
expect(textbox.styles[4], 'style line 4 has been removed').toBeUndefined();
|
|
expect(textbox.styles[5], 'style line 5 has been removed').toBeUndefined();
|
|
});
|
|
|
|
it('isEmptyStyles', () => {
|
|
const textbox = new Textbox('x x', {
|
|
width: 5,
|
|
styles: { 0: { 0: { fill: 'red' } } },
|
|
});
|
|
|
|
expect(textbox._textLines.length, 'lines are wrapped').toBe(2);
|
|
expect(
|
|
textbox._unwrappedTextLines.length,
|
|
'there is only one text line',
|
|
).toBe(1);
|
|
// @ts-expect-error -- TODO: check if lineIndex should be optional?
|
|
expect(textbox.isEmptyStyles(), 'style is not empty').toBe(false);
|
|
expect(textbox.isEmptyStyles(0), 'style is not empty at line 0').toBe(
|
|
false,
|
|
);
|
|
expect(textbox.isEmptyStyles(1), 'style is empty at line 1').toBe(true);
|
|
});
|
|
|
|
it('isEmptyStyles does not crash on null styles', () => {
|
|
const textbox = new Textbox('x x', { width: 5 });
|
|
|
|
textbox.styles = {};
|
|
|
|
expect(textbox._textLines.length, 'lines are wrapped').toBe(2);
|
|
expect(
|
|
textbox._unwrappedTextLines.length,
|
|
'there is only one text line',
|
|
).toBe(1);
|
|
expect(textbox.isEmptyStyles(1), 'style is empty').toBe(true);
|
|
});
|
|
|
|
it('isEmptyStyles alternate lines', () => {
|
|
const textbox = new Textbox('xa xb xc xd xe\nya yb', {
|
|
width: 5,
|
|
styles: {
|
|
0: {
|
|
0: { fill: 'red' },
|
|
1: { fill: 'blue' },
|
|
9: { fill: 'red' },
|
|
10: { fill: 'blue' },
|
|
},
|
|
1: { 3: { fill: 'red' }, 4: { fill: 'blue' } },
|
|
},
|
|
});
|
|
|
|
expect(textbox._textLines.length, 'lines are wrapped').toBe(7);
|
|
expect(
|
|
textbox._unwrappedTextLines.length,
|
|
'there is only one text line',
|
|
).toBe(2);
|
|
// @ts-expect-error -- TODO: check why lineIndex is mandatory but test doesn't provide it
|
|
expect(textbox.isEmptyStyles(), 'style is not empty').toBe(false);
|
|
expect(textbox.isEmptyStyles(0), 'style is not empty at line 0').toBe(
|
|
false,
|
|
);
|
|
expect(textbox.isEmptyStyles(1), 'style is empty at line 1').toBe(true);
|
|
expect(textbox.isEmptyStyles(2), 'style is empty at line 2').toBe(true);
|
|
expect(textbox.isEmptyStyles(3), 'style is empty at line 3').toBe(false);
|
|
expect(textbox.isEmptyStyles(4), 'style is empty at line 4').toBe(true);
|
|
expect(textbox.isEmptyStyles(5), 'style is empty at line 5').toBe(true);
|
|
expect(textbox.isEmptyStyles(6), 'style is empty at line 6').toBe(false);
|
|
});
|
|
|
|
it('wrapping with charspacing', () => {
|
|
const textbox = new Textbox('xa xb xc xd xe ya yb id', {
|
|
width: 190,
|
|
});
|
|
|
|
expect(textbox.textLines[0], 'first line match expectations').toBe(
|
|
'xa xb xc xd',
|
|
);
|
|
|
|
textbox.charSpacing = 100;
|
|
textbox.initDimensions();
|
|
|
|
expect(
|
|
textbox.textLines[0],
|
|
'first line match expectations spacing 100',
|
|
).toBe('xa xb xc');
|
|
|
|
textbox.charSpacing = 300;
|
|
textbox.initDimensions();
|
|
|
|
expect(
|
|
textbox.textLines[0],
|
|
'first line match expectations spacing 300',
|
|
).toBe('xa xb');
|
|
|
|
textbox.charSpacing = 800;
|
|
textbox.initDimensions();
|
|
|
|
expect(
|
|
textbox.textLines[0],
|
|
'first line match expectations spacing 800',
|
|
).toBe('xa');
|
|
});
|
|
|
|
it('wrapping with splitByGrapheme and styles', () => {
|
|
const value = 'xaxbxcxdeyaybid';
|
|
const textbox = new Textbox(value, {
|
|
width: 190,
|
|
splitByGrapheme: true,
|
|
styles: stylesFromArray(
|
|
[
|
|
{
|
|
style: {
|
|
fontWeight: 'bold',
|
|
fontSize: 64,
|
|
},
|
|
start: 0,
|
|
end: 9,
|
|
},
|
|
],
|
|
value,
|
|
),
|
|
});
|
|
|
|
expect(
|
|
textbox.textLines,
|
|
'lines match splitByGrapheme with styles',
|
|
).toEqual(['xaxbx', 'cxdeyay', 'bid']);
|
|
});
|
|
|
|
it('wrapping with largestWordWidth and styles', () => {
|
|
const value = 'xaxbxc xdeyayb id sdgjhgsdg';
|
|
const textbox = new Textbox(value, {
|
|
width: 190,
|
|
styles: stylesFromArray(
|
|
[
|
|
{
|
|
style: {
|
|
fontWeight: 'bold',
|
|
fontSize: 64,
|
|
},
|
|
start: 0,
|
|
end: 10,
|
|
},
|
|
],
|
|
value,
|
|
),
|
|
});
|
|
|
|
expect(
|
|
textbox.textLines,
|
|
'lines match largestWordWidth with styles',
|
|
).toEqual(['xaxbxc', 'xdeyayb', 'id', 'sdgjhgsdg']);
|
|
});
|
|
|
|
it('wrapping with charspacing and splitByGrapheme positive', () => {
|
|
const textbox = new Textbox('xaxbxcxdeyaybid', {
|
|
width: 190,
|
|
splitByGrapheme: true,
|
|
charSpacing: 400,
|
|
});
|
|
|
|
expect(
|
|
textbox.textLines,
|
|
'lines match splitByGrapheme charSpacing 400',
|
|
).toEqual(['xaxbx', 'cxdey', 'aybid']);
|
|
});
|
|
|
|
it('wrapping with charspacing and splitByGrapheme negative', () => {
|
|
const textbox = new Textbox('xaxbxcxdeyaybid', {
|
|
width: 190,
|
|
splitByGrapheme: true,
|
|
charSpacing: -100,
|
|
});
|
|
|
|
expect(
|
|
textbox.textLines,
|
|
'lines match splitByGrapheme charSpacing -100',
|
|
).toEqual(['xaxbxcxdeyay', 'bid']);
|
|
});
|
|
|
|
it('Measure words', () => {
|
|
const textbox = new Textbox('word word\nword\nword', { width: 300 });
|
|
const { wordsData, largestWordWidth } = textbox.getGraphemeDataForRender(
|
|
textbox.textLines,
|
|
);
|
|
|
|
expect(wordsData[0], 'All words have the same length line 0').toEqual([
|
|
{ word: ['w', 'o', 'r', 'd'], width: largestWordWidth },
|
|
{ word: ['w', 'o', 'r', 'd'], width: largestWordWidth },
|
|
]);
|
|
expect(wordsData[1], 'All words have the same length line1').toEqual([
|
|
{ word: ['w', 'o', 'r', 'd'], width: largestWordWidth },
|
|
]);
|
|
expect(Math.round(largestWordWidth), 'largest word is 82').toBe(82);
|
|
});
|
|
|
|
it('Measure words with styles', () => {
|
|
const textbox = new Textbox('word word\nword\nword', { width: 300 });
|
|
|
|
textbox.styles = {
|
|
0: {
|
|
5: {
|
|
fontSize: 100,
|
|
},
|
|
6: {
|
|
fontSize: 100,
|
|
},
|
|
7: {
|
|
fontSize: 100,
|
|
},
|
|
8: {
|
|
fontSize: 100,
|
|
},
|
|
},
|
|
2: {
|
|
0: {
|
|
fontSize: 200,
|
|
},
|
|
1: {
|
|
fontSize: 200,
|
|
},
|
|
2: {
|
|
fontSize: 200,
|
|
},
|
|
3: {
|
|
fontSize: 200,
|
|
},
|
|
},
|
|
};
|
|
|
|
const { wordsData, largestWordWidth } = textbox.getGraphemeDataForRender(
|
|
textbox.textLines,
|
|
);
|
|
|
|
expect(Math.round(wordsData[0][0].width), 'unstyle word is 82 wide').toBe(
|
|
82,
|
|
);
|
|
expect(Math.round(wordsData[0][1].width), 'unstyle word is 206 wide').toBe(
|
|
206,
|
|
);
|
|
expect(wordsData[2], 'All words have the same length line1').toEqual([
|
|
{ word: ['w', 'o', 'r', 'd'], width: largestWordWidth },
|
|
]);
|
|
expect(Math.round(largestWordWidth), 'largest word is 411').toBe(411);
|
|
});
|
|
|
|
it('wrapping with different things', () => {
|
|
const textbox = new Textbox('xa xb\txc\rxd xe ya yb id', {
|
|
width: 16,
|
|
});
|
|
|
|
expect(textbox.textLines[0], '0 line match expectations').toBe('xa');
|
|
expect(textbox.textLines[1], '1 line match expectations').toBe('xb');
|
|
expect(textbox.textLines[2], '2 line match expectations').toBe('xc');
|
|
expect(textbox.textLines[3], '3 line match expectations').toBe('xd');
|
|
expect(textbox.textLines[4], '4 line match expectations').toBe('xe');
|
|
expect(textbox.textLines[5], '5 line match expectations').toBe('ya');
|
|
expect(textbox.textLines[6], '6 line match expectations').toBe('yb');
|
|
});
|
|
|
|
it('wrapping with splitByGrapheme', () => {
|
|
const textbox = new Textbox('xaxbxcxdxeyaybid', {
|
|
width: 1,
|
|
splitByGrapheme: true,
|
|
});
|
|
|
|
expect(
|
|
textbox.textLines[0],
|
|
'0 line match expectations splitByGrapheme',
|
|
).toBe('x');
|
|
expect(
|
|
textbox.textLines[1],
|
|
'1 line match expectations splitByGrapheme',
|
|
).toBe('a');
|
|
expect(
|
|
textbox.textLines[2],
|
|
'2 line match expectations splitByGrapheme',
|
|
).toBe('x');
|
|
expect(
|
|
textbox.textLines[3],
|
|
'3 line match expectations splitByGrapheme',
|
|
).toBe('b');
|
|
expect(
|
|
textbox.textLines[4],
|
|
'4 line match expectations splitByGrapheme',
|
|
).toBe('x');
|
|
expect(
|
|
textbox.textLines[5],
|
|
'5 line match expectations splitByGrapheme',
|
|
).toBe('c');
|
|
});
|
|
|
|
it('wrapping with custom space', () => {
|
|
const textbox = new Textbox('xa xb xc xd xe ya yb id', {
|
|
width: 2000,
|
|
});
|
|
|
|
const wordsData = textbox.getGraphemeDataForRender([
|
|
'xa xb xc xd xe ya yb id',
|
|
]);
|
|
const line1 = textbox._wrapLine(0, 100, wordsData, 0);
|
|
const expected1 = [
|
|
['x', 'a', ' ', 'x', 'b'],
|
|
['x', 'c', ' ', 'x', 'd'],
|
|
['x', 'e', ' ', 'y', 'a'],
|
|
['y', 'b', ' ', 'i', 'd'],
|
|
];
|
|
|
|
expect(line1, 'line1 match expected').toEqual(expected1);
|
|
expect(textbox.dynamicMinWidth, 'texbox width is 40').toBe(40);
|
|
|
|
const line2 = textbox._wrapLine(0, 100, wordsData, 50);
|
|
const expected2 = [
|
|
['x', 'a'],
|
|
['x', 'b'],
|
|
['x', 'c'],
|
|
['x', 'd'],
|
|
['x', 'e'],
|
|
['y', 'a'],
|
|
['y', 'b'],
|
|
['i', 'd'],
|
|
];
|
|
|
|
expect(line2, 'line2 match expected').toEqual(expected2);
|
|
expect(textbox.dynamicMinWidth, 'texbox width is 90').toBe(90);
|
|
});
|
|
|
|
it('wrapping an empty line', () => {
|
|
const textbox = new Textbox('', {
|
|
width: 10,
|
|
});
|
|
|
|
const wordsData = textbox.getGraphemeDataForRender(['']);
|
|
const line1 = textbox._wrapLine(0, 100, wordsData, 0);
|
|
|
|
expect(line1, 'wrapping without splitByGrapheme').toEqual([[]]);
|
|
|
|
textbox.splitByGrapheme = true;
|
|
const line2 = textbox._wrapLine(0, 100, wordsData, 0);
|
|
|
|
expect(line2, 'wrapping with splitByGrapheme').toEqual([[]]);
|
|
});
|
|
|
|
it('wrapping respects max line width', () => {
|
|
const a = 'xaxbxc xdxeyaybid xaxbxc';
|
|
const b = 'xaxbxcxdxeyaybidxaxbxcxdxeyaybid';
|
|
|
|
[true, false].forEach((order) => {
|
|
[true, false].forEach((space) => {
|
|
const ordered = order ? [a, b] : [b, a];
|
|
const text = ordered.join(space ? ' ' : '\n');
|
|
const { _textLines: lines } = new Textbox(text);
|
|
|
|
expect(lines, `max line width should be respected for ${text}`).toEqual(
|
|
ordered.map((line) => line.split('')),
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
it('texbox will change width from the mr corner', () => {
|
|
const text = new Textbox('xa xb xc xd xe ya yb id', { strokeWidth: 0 });
|
|
text.setPositionByOrigin(new Point(0, 0), 'left', 'top');
|
|
canvas.add(text);
|
|
canvas.setActiveObject(text);
|
|
|
|
const eventStub = createPointerEvent({
|
|
clientX: text.width,
|
|
clientY: text.oCoords.mr.corner.tl.y + 1,
|
|
type: 'mousedown',
|
|
target: canvas.upperCanvasEl,
|
|
});
|
|
|
|
const originalWidth = text.width;
|
|
|
|
canvas._onMouseDown(eventStub);
|
|
canvas._onMouseMove({
|
|
...eventStub,
|
|
clientX: eventStub.clientX + 20,
|
|
clientY: eventStub.clientY,
|
|
type: 'mousemove',
|
|
});
|
|
canvas._onMouseUp({
|
|
...eventStub,
|
|
clientX: eventStub.clientX + 20,
|
|
clientY: eventStub.clientY,
|
|
type: 'mouseup',
|
|
});
|
|
|
|
expect(text.width, 'width increased').toBe(originalWidth + 20);
|
|
});
|
|
|
|
it('texbox will change width from the ml corner', () => {
|
|
const text = new Textbox('xa xb xc xd xe ya yb id', {
|
|
strokeWidth: 0,
|
|
left: 40,
|
|
});
|
|
text.setPositionByOrigin(new Point(40, 0), 'left', 'top');
|
|
canvas.add(text);
|
|
canvas.setActiveObject(text);
|
|
|
|
const eventStub = createPointerEvent({
|
|
clientX: text.left - text.width / 2,
|
|
clientY: text.oCoords.ml.corner.tl.y + 2,
|
|
type: 'mousedown',
|
|
target: canvas.upperCanvasEl,
|
|
});
|
|
|
|
const originalWidth = text.width;
|
|
|
|
canvas._onMouseDown(eventStub);
|
|
canvas._onMouseMove({
|
|
...eventStub,
|
|
clientX: eventStub.clientX - 20,
|
|
clientY: eventStub.clientY,
|
|
type: 'mousemove',
|
|
});
|
|
canvas._onMouseUp({
|
|
...eventStub,
|
|
clientX: eventStub.clientX + 20,
|
|
clientY: eventStub.clientY,
|
|
type: 'mouseup',
|
|
});
|
|
|
|
expect(text.width, 'width increased').toBe(originalWidth + 20);
|
|
});
|
|
|
|
it('_removeExtraneousStyles for textbox', () => {
|
|
const iText = new Textbox('a\nqqo', {
|
|
styles: {
|
|
0: { 0: { fontSize: 4 } },
|
|
1: { 0: { fontSize: 4 } },
|
|
2: { 0: { fontSize: 4 } },
|
|
3: { 0: { fontSize: 4 } },
|
|
4: { 0: { fontSize: 4 } },
|
|
},
|
|
});
|
|
|
|
expect(iText.styles[3], 'style line 3 exists').toEqual({
|
|
0: { fontSize: 4 },
|
|
});
|
|
expect(iText.styles[4], 'style line 4 exists').toEqual({
|
|
0: { fontSize: 4 },
|
|
});
|
|
|
|
iText._removeExtraneousStyles();
|
|
|
|
expect(iText.styles[3], 'style line 3 has been removed').toBeUndefined();
|
|
expect(iText.styles[4], 'style line 4 has been removed').toBeUndefined();
|
|
});
|
|
|
|
it('get2DCursorLocation with splitByGrapheme', () => {
|
|
const iText = new Textbox('aaaaaaaaaaaaaaaaaaaaaaaa', {
|
|
width: 60,
|
|
splitByGrapheme: true,
|
|
});
|
|
|
|
let loc = iText.get2DCursorLocation();
|
|
|
|
expect(loc.lineIndex, 'initial cursor line should be 0').toBe(0);
|
|
expect(loc.charIndex, 'initial cursor position should be 0').toBe(0);
|
|
|
|
iText.selectionStart = iText.selectionEnd = 4;
|
|
loc = iText.get2DCursorLocation();
|
|
|
|
expect(loc.lineIndex, 'selection end 4 line 1').toBe(1);
|
|
expect(loc.charIndex, 'selection end 4 char 1').toBe(1);
|
|
|
|
iText.selectionStart = iText.selectionEnd = 7;
|
|
loc = iText.get2DCursorLocation();
|
|
|
|
expect(loc.lineIndex, 'selection end 7 line 2').toBe(2);
|
|
expect(loc.charIndex, 'selection end 7 char 1').toBe(1);
|
|
|
|
iText.selectionStart = iText.selectionEnd = 14;
|
|
loc = iText.get2DCursorLocation();
|
|
|
|
expect(loc.lineIndex, 'selection end 14 line 4').toBe(4);
|
|
expect(loc.charIndex, 'selection end 14 char 2').toBe(2);
|
|
});
|
|
|
|
it('missingNewlineOffset with splitByGrapheme', () => {
|
|
const textbox = new Textbox('aaa\naaaaaa\na\naaaaaaaaaaaa\n aaa', {
|
|
width: 80,
|
|
splitByGrapheme: true,
|
|
});
|
|
|
|
const expected = {
|
|
lines: ['aaa', 'aaaa', 'aa', 'a', 'aaaa', 'aaaa', 'aaaa', ' aaa'],
|
|
hardBreaks: [1, 0, 1, 1, 0, 0, 1, 1],
|
|
cursor: [
|
|
{ selection: 1, lineIndex: 0, charIndex: 1 }, // a|aa
|
|
{ selection: 4, lineIndex: 1, charIndex: 0 }, // |aaaa
|
|
{ selection: 9, lineIndex: 2, charIndex: 1 }, // a|a
|
|
{ selection: 11, lineIndex: 3, charIndex: 0 }, // |a
|
|
{ selection: 14, lineIndex: 4, charIndex: 1 }, // a|aaa
|
|
{ selection: 20, lineIndex: 5, charIndex: 3 }, // aaa|a
|
|
{ selection: 22, lineIndex: 6, charIndex: 1 }, // a|aaa
|
|
{ selection: 29, lineIndex: 7, charIndex: 3 }, // aa|a
|
|
],
|
|
};
|
|
|
|
expect(textbox.textLines, 'wrap line by width').toEqual(expected.lines);
|
|
|
|
for (let i = 0; i < expected.hardBreaks.length; i++) {
|
|
const offset = textbox.missingNewlineOffset(i);
|
|
expect(
|
|
offset,
|
|
`line ${i} expect missingNewlineOffset: ${expected.hardBreaks[i]}`,
|
|
).toBe(expected.hardBreaks[i]);
|
|
}
|
|
|
|
let loc = textbox.get2DCursorLocation();
|
|
expect(loc.lineIndex, 'initial cursor line should be 0').toBe(0);
|
|
expect(loc.charIndex, 'initial cursor position should be 0').toBe(0);
|
|
|
|
for (let i = 0; i < expected.cursor.length; i++) {
|
|
const { selection, lineIndex, charIndex } = expected.cursor[i];
|
|
textbox.selectionStart = textbox.selectionEnd = selection;
|
|
loc = textbox.get2DCursorLocation();
|
|
expect(
|
|
loc.lineIndex,
|
|
`selection end ${selection} line ${lineIndex}`,
|
|
).toBe(lineIndex);
|
|
expect(
|
|
loc.charIndex,
|
|
`selection end ${selection} char ${charIndex}`,
|
|
).toBe(charIndex);
|
|
}
|
|
});
|
|
|
|
it('missingNewlineOffset with normal split 1', () => {
|
|
const textbox = new Textbox('aaa\naaaaaa\na\naaaaaaaaaaaa\n aaa', {
|
|
width: 80,
|
|
});
|
|
|
|
const expected = {
|
|
lines: ['aaa', 'aaaaaa', 'a', 'aaaaaaaaaaaa', ' aaa'],
|
|
hardBreaks: [1, 1, 1, 1, 1], // it has to always return 1
|
|
cursor: [
|
|
{ selection: 1, lineIndex: 0, charIndex: 1 }, // a|aa
|
|
{ selection: 4, lineIndex: 1, charIndex: 0 }, // |aaaaaa
|
|
{ selection: 12, lineIndex: 2, charIndex: 1 }, // a|
|
|
{ selection: 22, lineIndex: 3, charIndex: 9 }, // aaaaaaaaa|aaa
|
|
{ selection: 28, lineIndex: 4, charIndex: 2 }, // a|aa
|
|
],
|
|
};
|
|
|
|
expect(textbox.textLines, 'wrap by largestWordWidth').toEqual(
|
|
expected.lines,
|
|
);
|
|
for (let i = 0; i < expected.hardBreaks.length; i++) {
|
|
const offset = textbox.missingNewlineOffset(i);
|
|
expect(offset, `line ${i} expect ${expected.hardBreaks[i]}`).toBe(
|
|
expected.hardBreaks[i],
|
|
);
|
|
}
|
|
|
|
let loc = textbox.get2DCursorLocation();
|
|
expect(loc.lineIndex, 'initial cursor line should be 0').toBe(0);
|
|
expect(loc.charIndex, 'initial cursor position should be 0').toBe(0);
|
|
|
|
for (let i = 0; i < expected.cursor.length; i++) {
|
|
const { selection, lineIndex, charIndex } = expected.cursor[i];
|
|
textbox.selectionStart = textbox.selectionEnd = selection;
|
|
loc = textbox.get2DCursorLocation();
|
|
expect(
|
|
loc.lineIndex,
|
|
`selection end ${selection} line ${lineIndex}`,
|
|
).toBe(lineIndex);
|
|
expect(
|
|
loc.charIndex,
|
|
`selection end ${selection} char ${charIndex}`,
|
|
).toBe(charIndex);
|
|
}
|
|
});
|
|
|
|
it('missingNewlineOffset with normal split and short word', () => {
|
|
const textbox = new Textbox(
|
|
'aaa\naaaaaa \na\naaaaaaa aaaaa\n aaa',
|
|
{
|
|
width: 80,
|
|
},
|
|
);
|
|
|
|
const expected = {
|
|
lines: ['aaa', 'aaaaaa ', ' ', 'a', 'aaaaaaa', 'aaaaa', ' aaa'],
|
|
hardBreaks: [1, 1, 1, 1, 1, 1, 1], // Note: currently, lineIndex 2 and 4 no hardBreak but still removed a space
|
|
cursor: [
|
|
{ selection: 1, lineIndex: 0, charIndex: 1 }, // a|aa
|
|
{ selection: 4, lineIndex: 1, charIndex: 0 }, // |aaaaaa
|
|
{ selection: 13, lineIndex: 2, charIndex: 1 }, // 8 space
|
|
{ selection: 22, lineIndex: 3, charIndex: 1 }, // a|
|
|
{ selection: 29, lineIndex: 4, charIndex: 6 }, // aaaaaa|a
|
|
{ selection: 32, lineIndex: 5, charIndex: 1 }, // a|aaaa
|
|
{ selection: 38, lineIndex: 6, charIndex: 1 }, // |aaa
|
|
],
|
|
};
|
|
|
|
expect(textbox.textLines, 'wrap by largestWordWidth').toEqual(
|
|
expected.lines,
|
|
);
|
|
for (let i = 0; i < expected.hardBreaks.length; i++) {
|
|
const offset = textbox.missingNewlineOffset(i);
|
|
expect(offset, `line ${i} expect ${expected.hardBreaks[i]}`).toBe(
|
|
expected.hardBreaks[i],
|
|
);
|
|
}
|
|
|
|
let loc = textbox.get2DCursorLocation();
|
|
expect(loc.lineIndex, 'initial cursor line should be 0').toBe(0);
|
|
expect(loc.charIndex, 'initial cursor position should be 0').toBe(0);
|
|
|
|
for (let i = 0; i < expected.cursor.length; i++) {
|
|
const { selection, lineIndex, charIndex } = expected.cursor[i];
|
|
textbox.selectionStart = textbox.selectionEnd = selection;
|
|
loc = textbox.get2DCursorLocation();
|
|
expect(
|
|
loc.lineIndex,
|
|
`selection end ${selection} line ${lineIndex}`,
|
|
).toBe(lineIndex);
|
|
expect(
|
|
loc.charIndex,
|
|
`selection end ${selection} char ${charIndex}`,
|
|
).toBe(charIndex);
|
|
}
|
|
});
|
|
|
|
it('_getLineStyle', () => {
|
|
const textbox = new Textbox('aaa aaq ggg gg\noee eee', {
|
|
styles: {
|
|
1: { 0: { fontSize: 4 } },
|
|
},
|
|
width: 80,
|
|
});
|
|
|
|
// @ts-expect-error -- protected member
|
|
expect(textbox._getLineStyle(0), 'wrapped line 0 has no style').toBe(false);
|
|
// @ts-expect-error -- protected member
|
|
expect(textbox._getLineStyle(1), 'wrapped line 1 has no style').toBe(false);
|
|
// @ts-expect-error -- protected member
|
|
expect(textbox._getLineStyle(4), 'wrapped line 2 has style').toBe(true);
|
|
});
|
|
|
|
it('_setLineStyle', () => {
|
|
const textbox = new Textbox('aaa aaq ggg gg\noee eee', {
|
|
styles: {
|
|
1: { 0: { fontSize: 4 } },
|
|
},
|
|
width: 80,
|
|
});
|
|
|
|
// @ts-expect-error -- protected member
|
|
expect(textbox._getLineStyle(0), 'wrapped line 0 has no style').toBe(false);
|
|
// @ts-expect-error -- protected member
|
|
expect(textbox._getLineStyle(1), 'wrapped line 1 has no style').toBe(false);
|
|
// @ts-expect-error -- protected member
|
|
expect(textbox._getLineStyle(2), 'wrapped line 2 has no style').toBe(false);
|
|
// @ts-expect-error -- protected member
|
|
expect(textbox._getLineStyle(3), 'wrapped line 3 has no style').toBe(false);
|
|
|
|
expect(textbox.styles[0], 'style is undefined').toBeUndefined();
|
|
|
|
// @ts-expect-error -- protected member
|
|
textbox._setLineStyle(0);
|
|
|
|
// @ts-expect-error -- protected member
|
|
expect(textbox._getLineStyle(0), 'wrapped line 0 has style').toBe(true);
|
|
// @ts-expect-error -- protected member
|
|
expect(textbox._getLineStyle(1), 'wrapped line 1 has style').toBe(true);
|
|
// @ts-expect-error -- protected member
|
|
expect(textbox._getLineStyle(2), 'wrapped line 2 has style').toBe(true);
|
|
// @ts-expect-error -- protected member
|
|
expect(textbox._getLineStyle(3), 'wrapped line 3 has style').toBe(true);
|
|
|
|
expect(textbox.styles[0], 'style is an empty object').toEqual({});
|
|
});
|
|
|
|
it('_deleteStyleDeclaration', () => {
|
|
const text = 'aaa aaq ggg gg oee eee';
|
|
const styles: Record<PropertyKey, any> = {};
|
|
|
|
for (let index = 0; index < text.length; index++) {
|
|
styles[index] = { fontSize: 4 };
|
|
}
|
|
|
|
const textbox = new Textbox(text, {
|
|
styles: { 0: styles },
|
|
width: 5,
|
|
});
|
|
|
|
// @ts-expect-error -- protected member
|
|
expect(textbox._deleteStyleDeclaration, 'function exists').toBeTypeOf(
|
|
'function',
|
|
);
|
|
|
|
// @ts-expect-error -- protected member
|
|
textbox._deleteStyleDeclaration(2, 2);
|
|
|
|
expect(textbox.styles[0][10], 'style has been removed').toBeUndefined();
|
|
});
|
|
|
|
it('_setStyleDeclaration', () => {
|
|
const text = 'aaa aaq ggg gg oee eee';
|
|
const styles: Record<PropertyKey, any> = {};
|
|
|
|
for (let index = 0; index < text.length; index++) {
|
|
styles[index] = { fontSize: 4 };
|
|
}
|
|
|
|
const textbox = new Textbox(text, {
|
|
styles: { 0: styles },
|
|
width: 5,
|
|
});
|
|
|
|
// @ts-expect-error -- protected member
|
|
expect(textbox._setStyleDeclaration, 'function exists').toBeTypeOf(
|
|
'function',
|
|
);
|
|
|
|
const newStyle = { fontSize: 10 };
|
|
|
|
// @ts-expect-error -- protected member
|
|
textbox._setStyleDeclaration(2, 2, newStyle);
|
|
|
|
expect(textbox.styles[0][10], 'style has been changed').toBe(newStyle);
|
|
});
|
|
|
|
it('styleHas', () => {
|
|
const textbox = new Textbox('aaa aaq ggg gg oee eee', {
|
|
styles: {
|
|
0: {
|
|
0: { fontSize: 4 },
|
|
1: { fontSize: 4 },
|
|
2: { fontSize: 4 },
|
|
4: { fontFamily: 'Arial' },
|
|
5: { fontFamily: 'Arial' },
|
|
6: { fontFamily: 'Arial' },
|
|
},
|
|
},
|
|
width: 5,
|
|
});
|
|
|
|
// @ts-expect-error -- TODO: check why lineIndex is mandatory but test doesn't provide it
|
|
expect(textbox.styleHas('fontSize'), 'style has fontSize').toBe(true);
|
|
expect(
|
|
textbox.styleHas('fontSize', 0),
|
|
'style has fontSize on line 0',
|
|
).toBe(true);
|
|
// @ts-expect-error -- TODO: check why lineIndex is mandatory but test doesn't provide it
|
|
expect(textbox.styleHas('fontFamily'), 'style has fontFamily').toBe(true);
|
|
expect(
|
|
textbox.styleHas('fontFamily', 1),
|
|
'style has fontFamily on line 1',
|
|
).toBe(true);
|
|
});
|
|
|
|
it('The same text does not need to be wrapped.', () => {
|
|
const str = '0123456789';
|
|
|
|
const measureTextbox = new Textbox(str, {
|
|
fontSize: 20,
|
|
splitByGrapheme: false,
|
|
});
|
|
|
|
const newTextbox = new Textbox(str, {
|
|
width: measureTextbox.width,
|
|
fontSize: 20,
|
|
splitByGrapheme: true,
|
|
});
|
|
|
|
expect(newTextbox.textLines.length, 'The same text is not wrapped').toBe(
|
|
measureTextbox.textLines.length,
|
|
);
|
|
});
|
|
});
|