UNPKG

fabric

Version:

Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.

228 lines (210 loc) 6.72 kB
import { roundSnapshotOptions } from '../../../jest.extend'; import { IText } from './IText'; import { ValueAnimation } from '../../util/animation/ValueAnimation'; export function matchTextStateSnapshot(text: IText) { const { styles, _text: t, _textLines: lines, __charBounds: charBounds, } = text; expect({ styles, text: t, lines, charBounds, }).toMatchSnapshot(roundSnapshotOptions); expect(text).toMatchObjectSnapshot({ includeDefaultValues: false }); } function create() { return IText.fromObject<any, IText>({ text: 'test', fontSize: 25, styles: [ { fill: 'red' }, { fill: 'yellow' }, { fill: 'blue' }, { fill: 'green' }, ].map((style, index) => ({ style, start: index, end: index + 1 })), }); } describe('text imperative changes', () => { it('removeChars', async () => { const iText = await create(); iText.removeChars(1, 3); expect(iText.text).toBe('tt'); matchTextStateSnapshot(iText); }); it('insertChars', async () => { const iText = await create(); iText.insertChars('ab', undefined, 1); expect(iText.text).toBe('tabest'); matchTextStateSnapshot(iText); }); it('insertChars and removes chars', async () => { const iText = await create(); iText.insertChars('ab', undefined, 1, 2); expect(iText.text).toBe('tabst'); matchTextStateSnapshot(iText); }); it('insertChars and removes chars', async () => { const iText = await create(); iText.insertChars('ab', undefined, 1, 4); expect(iText.text).toBe('tab'); matchTextStateSnapshot(iText); }); it('insertChars handles new lines correctly', async () => { const iText = await create(); iText.insertChars('ab\n\n', undefined, 1); matchTextStateSnapshot(iText); }); it('insertChars can accept some style for the new text', async () => { const iText = await create(); iText.insertChars( 'ab\n\na', [ { fill: 'col1' }, { fill: 'col2' }, { fill: 'col3' }, { fill: 'col4' }, { fill: 'col5' }, ], 1, ); matchTextStateSnapshot(iText); }); it('missingNewlineOffset', () => { const iText = new IText( '由石墨\n分裂的石墨分\n裂\n由石墨分裂由石墨分裂的石\n墨分裂', ); expect(iText.missingNewlineOffset(0)).toBe(1); }); }); describe('IText cursor animation snapshot', () => { let currentAnimation: string[] = []; const origCalculate = ValueAnimation.prototype.calculate; beforeAll(() => { jest .spyOn(ValueAnimation.prototype, 'calculate') .mockImplementation(function (timeElapsed: number) { const value = origCalculate.call(this, timeElapsed); currentAnimation.push(value.value.toFixed(3)); return value; }); jest.useFakeTimers(); }); afterAll(() => { ValueAnimation.prototype.calculate = origCalculate; }); beforeEach(() => { jest.runAllTimers(); currentAnimation = []; }); afterAll(() => { jest.resetAllMocks(); jest.useRealTimers(); }); test('initDelayedCursor false - with delay', () => { const iText = new IText('', { canvas: {} }); iText.initDelayedCursor(); jest.advanceTimersByTime(2000); expect(currentAnimation).toMatchSnapshot(); iText.abortCursorAnimation(); }); test('initDelayedCursor true - with NO delay', () => { const iText = new IText('', { canvas: {} }); iText.initDelayedCursor(true); jest.advanceTimersByTime(2000); expect(currentAnimation).toMatchSnapshot(); iText.abortCursorAnimation(); }); test('selectionStart/selection end will abort animation', () => { const iText = new IText('asd', { canvas: {} }); iText.initDelayedCursor(true); jest.advanceTimersByTime(160); iText.selectionStart = 0; iText.selectionEnd = 3; jest.advanceTimersByTime(2000); expect(currentAnimation).toMatchSnapshot(); iText.abortCursorAnimation(); }); test('exiting from a canvas will abort animation', () => { const iText = new IText('asd', { canvas: {} }); iText.initDelayedCursor(true); jest.advanceTimersByTime(160); iText.canvas = undefined; jest.advanceTimersByTime(2000); expect(currentAnimation).toMatchSnapshot(); iText.abortCursorAnimation(); }); test('Animation is configurable - fast cursor with delay', () => { const iText = new IText('', { canvas: {} }); iText.cursorDelay = 200; iText.cursorDuration = 80; iText.initDelayedCursor(); jest.advanceTimersByTime(1000); expect(currentAnimation).toMatchSnapshot(); iText.abortCursorAnimation(); }); test('Animation is configurable - fast cursor with no delay', () => { const iText = new IText('', { canvas: {} }); iText.cursorDelay = 200; iText.cursorDuration = 80; iText.initDelayedCursor(true); jest.advanceTimersByTime(1000); expect(currentAnimation).toMatchSnapshot(); iText.abortCursorAnimation(); }); }); describe('IText _tick', () => { const _tickMock = jest.fn(); beforeEach(() => { _tickMock.mockClear(); }); test('enter Editing will call _tick', () => { const iText = new IText('hello\nhello'); jest.spyOn(iText, '_tick').mockImplementation(_tickMock); iText.enterEditing(); expect(_tickMock).toHaveBeenCalledWith(); }); test('mouse up will fire an animation restart with 0 delay if is a click', () => { const iText = new IText('hello\nhello'); jest.spyOn(iText, '_tick').mockImplementation(_tickMock); iText.enterEditing(); expect(_tickMock).toHaveBeenCalledWith(); _tickMock.mockClear(); iText.selected = true; iText.mouseUpHandler({ e: { button: 0, }, }); expect(_tickMock).toHaveBeenCalledWith(0); }); }); describe('Itext enterEditing and exitEditing', () => { const enterMock = jest.fn(); const exitMock = jest.fn(); afterEach(() => { enterMock.mockClear(); exitMock.mockClear(); }); test('Entering and leaving edit triggers the listener', () => { const iText = new IText('some word'); iText.on('editing:entered', enterMock); iText.on('editing:exited', exitMock); iText.enterEditing(); expect(enterMock).toHaveBeenCalledTimes(1); iText.exitEditing(); expect(exitMock).toHaveBeenCalledTimes(1); }); test('Entering and leaving edit does not trigger the listener', () => { const iText = new IText('some word'); iText.on('editing:entered', enterMock); iText.on('editing:exited', exitMock); iText.enterEditingImpl(); expect(enterMock).toHaveBeenCalledTimes(0); iText.exitEditingImpl(); expect(exitMock).toHaveBeenCalledTimes(0); }); });