fix: left typescript errors

This commit is contained in:
JackUait 2025-11-15 03:33:20 +03:00
commit 1d6d0a477e
4 changed files with 350 additions and 18 deletions

View file

@ -37,7 +37,7 @@ export default class MoveDownTune implements BlockTune {
*
* @param {API} api Editor's API
*/
constructor({ api }) {
constructor({ api }: { api: API }) {
this.api = api;
}
@ -68,15 +68,9 @@ export default class MoveDownTune implements BlockTune {
const nextBlockElement = nextBlock.holder;
const nextBlockCoords = nextBlockElement.getBoundingClientRect();
let scrollOffset = Math.abs(window.innerHeight - nextBlockElement.offsetHeight);
/**
* Next block ends on screen.
* Increment scroll by next block's height to save element onscreen-position
*/
if (nextBlockCoords.top < window.innerHeight) {
scrollOffset = window.scrollY + nextBlockElement.offsetHeight;
}
const scrollOffset = nextBlockCoords.top < window.innerHeight
? window.scrollY + nextBlockElement.offsetHeight
: Math.abs(window.innerHeight - nextBlockElement.offsetHeight);
window.scrollTo(0, scrollOffset);

View file

@ -35,7 +35,7 @@ export default class MoveUpTune implements BlockTune {
*
* @param {API} api - Editor's API
*/
constructor({ api }) {
constructor({ api }: { api: API }) {
this.api = api;
}
@ -77,13 +77,9 @@ export default class MoveUpTune implements BlockTune {
const currentBlockCoords = currentBlockElement.getBoundingClientRect();
const previousBlockCoords = previousBlockElement.getBoundingClientRect();
let scrollUpOffset;
if (previousBlockCoords.top > 0) {
scrollUpOffset = Math.abs(currentBlockCoords.top) - Math.abs(previousBlockCoords.top);
} else {
scrollUpOffset = Math.abs(currentBlockCoords.top) + previousBlockCoords.height;
}
const scrollUpOffset = previousBlockCoords.top > 0
? Math.abs(currentBlockCoords.top) - Math.abs(previousBlockCoords.top)
: Math.abs(currentBlockCoords.top) + previousBlockCoords.height;
window.scrollBy(0, -1 * scrollUpOffset);

View file

@ -0,0 +1,166 @@
import { IconChevronDown } from '@codexteam/icons';
import type { Mock } from 'vitest';
import { afterEach, describe, expect, it, vi } from 'vitest';
import MoveDownTune from '../../../../src/components/block-tunes/block-tune-move-down';
import type { API } from '../../../../types';
import type { PopoverItemDefaultBaseParams } from '../../../../types/utils/popover';
type BlocksMocks = {
getCurrentBlockIndex: Mock<[], number>;
getBlockByIndex: Mock<[number], { holder: HTMLElement } | undefined>;
move: Mock<[number], void>;
};
type ToolbarMocks = {
toggleBlockSettings: Mock<[boolean], void>;
};
type I18nMocks = {
t: Mock<[string], string>;
};
const createApiMocks = (): {
api: API;
blocks: BlocksMocks;
toolbar: ToolbarMocks;
i18n: I18nMocks;
} => {
const blocks: BlocksMocks = {
getCurrentBlockIndex: vi.fn<[], number>().mockReturnValue(0),
getBlockByIndex: vi.fn<[number], { holder: HTMLElement } | undefined>(),
move: vi.fn<[number], void>(),
};
const toolbar: ToolbarMocks = {
toggleBlockSettings: vi.fn<[boolean], void>(),
};
const i18n: I18nMocks = {
t: vi.fn<[string], string>().mockImplementation((text) => text),
};
const api = {
blocks: blocks as unknown as API['blocks'],
toolbar: toolbar as unknown as API['toolbar'],
i18n: i18n as unknown as API['i18n'],
} as unknown as API;
return {
api,
blocks,
toolbar,
i18n,
};
};
const createBlockElement = (top: number, height: number): HTMLDivElement => {
const element = document.createElement('div');
Object.defineProperty(element, 'offsetHeight', {
configurable: true,
value: height,
});
vi.spyOn(element, 'getBoundingClientRect').mockReturnValue({
bottom: top + height,
height,
left: 0,
right: 0,
top,
width: 0,
x: 0,
y: top,
toJSON: () => ({}),
} as DOMRect);
return element;
};
describe('MoveDownTune', () => {
afterEach(() => {
vi.restoreAllMocks();
});
it('renders block tune config with translated title and handler', () => {
const { api, i18n } = createApiMocks();
const tune = new MoveDownTune({ api });
const handleClickSpy = vi.spyOn(tune, 'handleClick').mockImplementation(() => {});
const config = tune.render() as PopoverItemDefaultBaseParams;
expect(i18n.t).toHaveBeenCalledWith('Move down');
expect(config.icon).toBe(IconChevronDown);
expect(config.title).toBe('Move down');
expect(config.name).toBe('move-down');
config.onActivate(config);
expect(handleClickSpy).toHaveBeenCalledTimes(1);
});
it('moves the block down and scrolls relative to current position when next block is visible', () => {
const { api, blocks, toolbar } = createApiMocks();
const tune = new MoveDownTune({ api });
blocks.getCurrentBlockIndex.mockReturnValue(2);
const nextBlockElement = createBlockElement(400, 180);
blocks.getBlockByIndex.mockReturnValue({
holder: nextBlockElement,
});
vi.spyOn(window, 'innerHeight', 'get').mockReturnValue(800);
vi.spyOn(window, 'scrollY', 'get').mockReturnValue(150);
const scrollToSpy = vi.spyOn(window, 'scrollTo').mockImplementation(() => {});
tune.handleClick();
expect(blocks.getBlockByIndex).toHaveBeenCalledWith(3);
expect(scrollToSpy).toHaveBeenCalledWith(0, 330);
expect(blocks.move).toHaveBeenCalledWith(3);
expect(toolbar.toggleBlockSettings).toHaveBeenCalledWith(true);
});
it('moves the block down and scrolls by viewport delta when next block is below the fold', () => {
const { api, blocks, toolbar } = createApiMocks();
const tune = new MoveDownTune({ api });
blocks.getCurrentBlockIndex.mockReturnValue(0);
const nextBlockElement = createBlockElement(1_000, 200);
blocks.getBlockByIndex.mockReturnValue({
holder: nextBlockElement,
});
vi.spyOn(window, 'innerHeight', 'get').mockReturnValue(600);
vi.spyOn(window, 'scrollY', 'get').mockReturnValue(50);
const scrollToSpy = vi.spyOn(window, 'scrollTo').mockImplementation(() => {});
tune.handleClick();
expect(blocks.getBlockByIndex).toHaveBeenCalledWith(1);
expect(scrollToSpy).toHaveBeenCalledWith(0, 400);
expect(blocks.move).toHaveBeenCalledWith(1);
expect(toolbar.toggleBlockSettings).toHaveBeenCalledWith(true);
});
it('throws when there is no block below the current one', () => {
const { api, blocks, toolbar } = createApiMocks();
const tune = new MoveDownTune({ api });
blocks.getCurrentBlockIndex.mockReturnValue(5);
blocks.getBlockByIndex.mockReturnValue(undefined);
const scrollToSpy = vi.spyOn(window, 'scrollTo').mockImplementation(() => {});
expect(() => tune.handleClick()).toThrowError('Unable to move Block down since it is already the last');
expect(blocks.move).not.toHaveBeenCalled();
expect(toolbar.toggleBlockSettings).not.toHaveBeenCalled();
expect(scrollToSpy).not.toHaveBeenCalled();
});
});

View file

@ -0,0 +1,176 @@
import { IconChevronUp } from '@codexteam/icons';
import type { Mock } from 'vitest';
import { afterEach, describe, expect, it, vi } from 'vitest';
import MoveUpTune from '../../../../src/components/block-tunes/block-tune-move-up';
import type { API } from '../../../../types';
import type { PopoverItemDefaultBaseParams } from '../../../../types/utils/popover';
type BlocksMocks = {
getCurrentBlockIndex: Mock<[], number>;
getBlockByIndex: Mock<[number], { holder: HTMLElement } | undefined>;
move: Mock<[number], void>;
};
type ToolbarMocks = {
toggleBlockSettings: Mock<[boolean], void>;
};
type I18nMocks = {
t: Mock<[string], string>;
};
const createApiMocks = (): {
api: API;
blocks: BlocksMocks;
toolbar: ToolbarMocks;
i18n: I18nMocks;
} => {
const blocks: BlocksMocks = {
getCurrentBlockIndex: vi.fn<[], number>().mockReturnValue(0),
getBlockByIndex: vi.fn<[number], { holder: HTMLElement } | undefined>(),
move: vi.fn<[number], void>(),
};
const toolbar: ToolbarMocks = {
toggleBlockSettings: vi.fn<[boolean], void>(),
};
const i18n: I18nMocks = {
t: vi.fn<[string], string>().mockImplementation((text) => text),
};
const api = {
blocks: blocks as unknown as API['blocks'],
toolbar: toolbar as unknown as API['toolbar'],
i18n: i18n as unknown as API['i18n'],
} as unknown as API;
return {
api,
blocks,
toolbar,
i18n,
};
};
const createBlockElement = (top: number, height: number): HTMLDivElement => {
const element = document.createElement('div');
vi.spyOn(element, 'getBoundingClientRect').mockReturnValue({
bottom: top + height,
height,
left: 0,
right: 0,
top,
width: 0,
x: 0,
y: top,
toJSON: () => ({}),
} as DOMRect);
return element;
};
describe('MoveUpTune', () => {
afterEach(() => {
vi.restoreAllMocks();
});
it('renders block tune config with translated title and handler', () => {
const { api, i18n } = createApiMocks();
const tune = new MoveUpTune({ api });
const handleClickSpy = vi.spyOn(tune, 'handleClick').mockImplementation(() => {});
const config = tune.render() as PopoverItemDefaultBaseParams;
expect(i18n.t).toHaveBeenCalledWith('Move up');
expect(config.icon).toBe(IconChevronUp);
expect(config.title).toBe('Move up');
expect(config.name).toBe('move-up');
config.onActivate(config);
expect(handleClickSpy).toHaveBeenCalledTimes(1);
});
it('moves the block up and scrolls relative to blocks when previous block is visible', () => {
const { api, blocks, toolbar } = createApiMocks();
const tune = new MoveUpTune({ api });
blocks.getCurrentBlockIndex.mockReturnValue(2);
const currentBlockElement = createBlockElement(500, 180);
const previousBlockElement = createBlockElement(200, 160);
blocks.getBlockByIndex.mockImplementation((index) => {
if (index === 2) {
return { holder: currentBlockElement };
}
if (index === 1) {
return { holder: previousBlockElement };
}
return undefined;
});
const scrollBySpy = vi.spyOn(window, 'scrollBy').mockImplementation(() => {});
tune.handleClick();
expect(blocks.getBlockByIndex).toHaveBeenCalledWith(1);
expect(scrollBySpy).toHaveBeenCalledWith(0, -(Math.abs(500) - Math.abs(200)));
expect(blocks.move).toHaveBeenCalledWith(1);
expect(toolbar.toggleBlockSettings).toHaveBeenCalledWith(true);
});
it('moves the block up and scrolls by delta when previous block is above the viewport', () => {
const { api, blocks, toolbar } = createApiMocks();
const tune = new MoveUpTune({ api });
blocks.getCurrentBlockIndex.mockReturnValue(3);
const currentBlockElement = createBlockElement(400, 160);
const previousBlockElement = createBlockElement(-150, 120);
blocks.getBlockByIndex.mockImplementation((index) => {
if (index === 3) {
return { holder: currentBlockElement };
}
if (index === 2) {
return { holder: previousBlockElement };
}
return undefined;
});
const scrollBySpy = vi.spyOn(window, 'scrollBy').mockImplementation(() => {});
tune.handleClick();
expect(blocks.getBlockByIndex).toHaveBeenCalledWith(2);
expect(scrollBySpy).toHaveBeenCalledWith(0, -(Math.abs(400) + 120));
expect(blocks.move).toHaveBeenCalledWith(2);
expect(toolbar.toggleBlockSettings).toHaveBeenCalledWith(true);
});
it('throws when block is already the first or previous block is missing', () => {
const { api, blocks, toolbar } = createApiMocks();
const tune = new MoveUpTune({ api });
blocks.getCurrentBlockIndex.mockReturnValue(0);
blocks.getBlockByIndex.mockReturnValue(undefined);
const scrollBySpy = vi.spyOn(window, 'scrollBy').mockImplementation(() => {});
expect(() => tune.handleClick()).toThrowError(
'Unable to move Block up since it is already the first'
);
expect(blocks.move).not.toHaveBeenCalled();
expect(toolbar.toggleBlockSettings).not.toHaveBeenCalled();
expect(scrollBySpy).not.toHaveBeenCalled();
});
});