Загрузка данных
import { CustomAxisView } from '@core/Drawings/axis/CustomAxisView';
import type { AxisLabel } from '@core/Drawings/types';
class TestCustomAxisView extends CustomAxisView {
protected getViewportSize(rect: DOMRect): number {
return rect.width;
}
}
function createViewport(
width = 400,
height = 300,
): HTMLElement {
const viewport = document.createElement('div');
jest.spyOn(
viewport,
'getBoundingClientRect',
).mockReturnValue({
width,
height,
x: 0,
y: 0,
top: 0,
left: 0,
right: width,
bottom: height,
toJSON: () => ({}),
});
return viewport;
}
function createLabel(
overrides: Partial<AxisLabel> = {},
): AxisLabel {
return {
coordinate: 100,
text: 'Label',
textColor: '#ffffff',
backgroundColor: '#000000',
...overrides,
};
}
describe('CustomAxisView', () => {
it('должен возвращать данные текущего label', () => {
const getAxisLabel = jest.fn(
() => createLabel(),
);
const view = new TestCustomAxisView({
getAxisLabel,
labelKind: 'start',
viewport: createViewport(),
});
expect(view.coordinate()).toBe(100);
expect(view.text()).toBe('Label');
expect(view.textColor()).toBe('#ffffff');
expect(view.backColor()).toBe('#000000');
expect(getAxisLabel).toHaveBeenCalledWith('start');
});
it('должен возвращать значения по умолчанию при отсутствии label', () => {
const view = new TestCustomAxisView({
getAxisLabel: () => null,
labelKind: 'start',
viewport: createViewport(),
});
expect(view.visible()).toBe(false);
expect(view.tickVisible()).toBe(false);
expect(view.coordinate()).toBe(0);
expect(view.text()).toBe('');
expect(view.textColor()).toBe('');
expect(view.backColor()).toBe('');
});
it('не должен показывать label с пустым текстом', () => {
const view = new TestCustomAxisView({
getAxisLabel: () =>
createLabel({
text: '',
}),
labelKind: 'start',
viewport: createViewport(),
});
expect(view.visible()).toBe(false);
});
it('должен показывать label внутри viewport', () => {
const view = new TestCustomAxisView({
getAxisLabel: () =>
createLabel({
coordinate: 200,
}),
labelKind: 'start',
viewport: createViewport(400),
});
expect(view.visible()).toBe(true);
});
it.each([
-1,
401,
])(
'не должен показывать label вне viewport: %s',
(coordinate) => {
const view = new TestCustomAxisView({
getAxisLabel: () =>
createLabel({
coordinate,
}),
labelKind: 'start',
viewport: createViewport(400),
});
expect(view.visible()).toBe(false);
},
);
it.each([
0,
400,
])(
'должен показывать label на границе viewport: %s',
(coordinate) => {
const view = new TestCustomAxisView({
getAxisLabel: () =>
createLabel({
coordinate,
}),
labelKind: 'start',
viewport: createViewport(400),
});
expect(view.visible()).toBe(true);
},
);
it.each([
Number.NaN,
Number.POSITIVE_INFINITY,
Number.NEGATIVE_INFINITY,
])(
'не должен показывать label с невалидной coordinate: %s',
(coordinate) => {
const view = new TestCustomAxisView({
getAxisLabel: () =>
createLabel({
coordinate,
}),
labelKind: 'start',
viewport: createViewport(),
});
expect(view.visible()).toBe(false);
},
);
it('tick не должен быть видимым', () => {
const view = new TestCustomAxisView({
getAxisLabel: () =>
createLabel(),
labelKind: 'start',
viewport: createViewport(),
});
expect(
view.tickVisible(),
).toBe(false);
});
it('update не должен изменять состояние view', () => {
const getAxisLabel = jest.fn(
() => createLabel(),
);
const view = new TestCustomAxisView({
getAxisLabel,
labelKind: 'start',
viewport: createViewport(),
});
view.update();
expect(view.visible()).toBe(true);
expect(view.coordinate()).toBe(100);
});
});
import {
CustomPriceAxisPaneView,
CustomPriceAxisView,
} from '@core/Drawings/axis';
import type { CanvasRenderingTarget2D } from 'fancy-canvas';
function createTarget(verticalPixelRatio = 2) {
const context = {
save: jest.fn(),
restore: jest.fn(),
fillRect: jest.fn(),
fillStyle: '',
} as unknown as CanvasRenderingContext2D;
const useBitmapCoordinateSpace = jest.fn(
(
callback: (scope: {
context: CanvasRenderingContext2D;
horizontalPixelRatio: number;
verticalPixelRatio: number;
bitmapSize: { width: number; height: number };
}) => void,
) => {
callback({
context,
horizontalPixelRatio: 2,
verticalPixelRatio,
bitmapSize: {
width: 300,
height: 200,
},
});
},
);
return {
context,
target: {
useBitmapCoordinateSpace,
} as unknown as CanvasRenderingTarget2D,
useBitmapCoordinateSpace,
};
}
function createViewport(width = 400, height = 300): HTMLElement {
const viewport = document.createElement('div');
jest.spyOn(viewport, 'getBoundingClientRect').mockReturnValue({
width,
height,
x: 0,
y: 0,
top: 0,
left: 0,
right: width,
bottom: height,
toJSON: () => ({}),
});
return viewport;
}
describe('CustomPriceAxisPaneView', () => {
it('должен использовать bottom zOrder по умолчанию', () => {
const view = new CustomPriceAxisPaneView({
getAxisSegments: () => [],
});
expect(view.zOrder()).toBe('bottom');
});
it('должен использовать переданный zOrder', () => {
const view = new CustomPriceAxisPaneView({
getAxisSegments: () => [],
zOrder: 'top',
});
expect(view.zOrder()).toBe('top');
});
it('должен возвращать один экземпляр renderer', () => {
const view = new CustomPriceAxisPaneView({
getAxisSegments: () => [],
});
expect(view.renderer()).toBe(view.renderer());
});
it('update не должен пересоздавать renderer', () => {
const view = new CustomPriceAxisPaneView({
getAxisSegments: () => [],
});
const renderer = view.renderer();
view.update();
expect(view.renderer()).toBe(renderer);
});
it('не должен рисовать при отсутствии сегментов', () => {
const { target, useBitmapCoordinateSpace } = createTarget();
const view = new CustomPriceAxisPaneView({
getAxisSegments: () => [],
});
view.renderer().draw(target);
expect(useBitmapCoordinateSpace).not.toHaveBeenCalled();
});
it('должен рисовать сегмент по вертикали', () => {
const { context, target } = createTarget();
const view = new CustomPriceAxisPaneView({
getAxisSegments: () => [
{
from: 10,
to: 30,
color: '#ff0000',
},
],
});
view.renderer().draw(target);
expect(context.fillStyle).toBe('#ff0000');
expect(context.fillRect).toHaveBeenCalledWith(
0,
20,
300,
40,
);
expect(context.save).toHaveBeenCalledTimes(1);
expect(context.restore).toHaveBeenCalledTimes(1);
});
it('должен учитывать verticalPixelRatio', () => {
const { context, target } = createTarget(3);
const view = new CustomPriceAxisPaneView({
getAxisSegments: () => [
{
from: 10,
to: 30,
color: '#ff0000',
},
],
});
view.renderer().draw(target);
expect(context.fillRect).toHaveBeenCalledWith(
0,
30,
300,
60,
);
});
it('должен корректно рисовать сегмент при обратном порядке координат', () => {
const { context, target } = createTarget();
const view = new CustomPriceAxisPaneView({
getAxisSegments: () => [
{
from: 30,
to: 10,
color: '#00ff00',
},
],
});
view.renderer().draw(target);
expect(context.fillRect).toHaveBeenCalledWith(
0,
20,
300,
40,
);
});
it('должен рисовать несколько сегментов', () => {
const { context, target } = createTarget();
const view = new CustomPriceAxisPaneView({
getAxisSegments: () => [
{
from: 10,
to: 20,
color: '#ff0000',
},
{
from: 30,
to: 50,
color: '#00ff00',
},
],
});
view.renderer().draw(target);
expect(context.fillRect).toHaveBeenNthCalledWith(
1,
0,
20,
300,
20,
);
expect(context.fillRect).toHaveBeenNthCalledWith(
2,
0,
60,
300,
40,
);
expect(context.save).toHaveBeenCalledTimes(1);
expect(context.restore).toHaveBeenCalledTimes(1);
});
it('должен получать актуальные segments при каждом draw', () => {
const { context, target } = createTarget();
const getAxisSegments = jest
.fn()
.mockReturnValueOnce([
{
from: 10,
to: 20,
color: '#ff0000',
},
])
.mockReturnValueOnce([
{
from: 30,
to: 50,
color: '#00ff00',
},
]);
const view = new CustomPriceAxisPaneView({
getAxisSegments,
});
view.renderer().draw(target);
view.renderer().draw(target);
expect(getAxisSegments).toHaveBeenCalledTimes(2);
expect(context.fillRect).toHaveBeenNthCalledWith(
1,
0,
20,
300,
20,
);
expect(context.fillRect).toHaveBeenNthCalledWith(
2,
0,
60,
300,
40,
);
});
});
describe('CustomPriceAxisView', () => {
it('должен использовать height viewport для проверки видимости', () => {
const view = new CustomPriceAxisView({
getAxisLabel: () => ({
coordinate: 250,
text: '100.50',
textColor: '#ffffff',
backgroundColor: '#000000',
}),
labelKind: 'entry',
viewport: createViewport(100, 300),
});
expect(view.visible()).toBe(true);
});
it('не должен использовать width viewport для проверки видимости', () => {
const view = new CustomPriceAxisView({
getAxisLabel: () => ({
coordinate: 150,
text: '100.50',
textColor: '#ffffff',
backgroundColor: '#000000',
}),
labelKind: 'entry',
viewport: createViewport(400, 100),
});
expect(view.visible()).toBe(false);
});
it('должен считать нижнюю границу height видимой', () => {
const view = new CustomPriceAxisView({
getAxisLabel: () => ({
coordinate: 300,
text: '100.50',
textColor: '#ffffff',
backgroundColor: '#000000',
}),
labelKind: 'entry',
viewport: createViewport(100, 300),
});
expect(view.visible()).toBe(true);
});
it('не должен показывать label ниже height viewport', () => {
const view = new CustomPriceAxisView({
getAxisLabel: () => ({
coordinate: 301,
text: '100.50',
textColor: '#ffffff',
backgroundColor: '#000000',
}),
labelKind: 'entry',
viewport: createViewport(1000, 300),
});
expect(view.visible()).toBe(false);
});
});
import {
CustomTimeAxisPaneView,
CustomTimeAxisView,
} from '@core/Drawings/axis';
import type { CanvasRenderingTarget2D } from 'fancy-canvas';
function createTarget(horizontalPixelRatio = 2) {
const context = {
save: jest.fn(),
restore: jest.fn(),
fillRect: jest.fn(),
fillStyle: '',
} as unknown as CanvasRenderingContext2D;
const useBitmapCoordinateSpace = jest.fn(
(
callback: (scope: {
context: CanvasRenderingContext2D;
horizontalPixelRatio: number;
verticalPixelRatio: number;
bitmapSize: { width: number; height: number };
}) => void,
) => {
callback({
context,
horizontalPixelRatio,
verticalPixelRatio: 2,
bitmapSize: {
width: 300,
height: 200,
},
});
},
);
return {
context,
target: {
useBitmapCoordinateSpace,
} as unknown as CanvasRenderingTarget2D,
useBitmapCoordinateSpace,
};
}
function createViewport(width = 400, height = 300): HTMLElement {
const viewport = document.createElement('div');
jest.spyOn(viewport, 'getBoundingClientRect').mockReturnValue({
width,
height,
x: 0,
y: 0,
top: 0,
left: 0,
right: width,
bottom: height,
toJSON: () => ({}),
});
return viewport;
}
describe('CustomTimeAxisPaneView', () => {
it('должен использовать bottom zOrder по умолчанию', () => {
const view = new CustomTimeAxisPaneView({
getAxisSegments: () => [],
});
expect(view.zOrder()).toBe('bottom');
});
it('должен использовать переданный zOrder', () => {
const view = new CustomTimeAxisPaneView({
getAxisSegments: () => [],
zOrder: 'normal',
});
expect(view.zOrder()).toBe('normal');
});
it('должен возвращать один экземпляр renderer', () => {
const view = new CustomTimeAxisPaneView({
getAxisSegments: () => [],
});
expect(view.renderer()).toBe(view.renderer());
});
it('update не должен пересоздавать renderer', () => {
const view = new CustomTimeAxisPaneView({
getAxisSegments: () => [],
});
const renderer = view.renderer();
view.update();
expect(view.renderer()).toBe(renderer);
});
it('не должен рисовать при отсутствии сегментов', () => {
const { target, useBitmapCoordinateSpace } = createTarget();
const view = new CustomTimeAxisPaneView({
getAxisSegments: () => [],
});
view.renderer().draw(target);
expect(useBitmapCoordinateSpace).not.toHaveBeenCalled();
});
it('должен рисовать сегмент по горизонтали', () => {
const { context, target } = createTarget();
const view = new CustomTimeAxisPaneView({
getAxisSegments: () => [
{
from: 10,
to: 30,
color: '#ff0000',
},
],
});
view.renderer().draw(target);
expect(context.fillStyle).toBe('#ff0000');
expect(context.fillRect).toHaveBeenCalledWith(
20,
0,
40,
200,
);
expect(context.save).toHaveBeenCalledTimes(1);
expect(context.restore).toHaveBeenCalledTimes(1);
});
it('должен учитывать horizontalPixelRatio', () => {
const { context, target } = createTarget(3);
const view = new CustomTimeAxisPaneView({
getAxisSegments: () => [
{
from: 10,
to: 30,
color: '#ff0000',
},
],
});
view.renderer().draw(target);
expect(context.fillRect).toHaveBeenCalledWith(
30,
0,
60,
200,
);
});
it('должен корректно рисовать сегмент при обратном порядке координат', () => {
const { context, target } = createTarget();
const view = new CustomTimeAxisPaneView({
getAxisSegments: () => [
{
from: 30,
to: 10,
color: '#00ff00',
},
],
});
view.renderer().draw(target);
expect(context.fillRect).toHaveBeenCalledWith(
20,
0,
40,
200,
);
});
it('должен рисовать несколько сегментов', () => {
const { context, target } = createTarget();
const view = new CustomTimeAxisPaneView({
getAxisSegments: () => [
{
from: 10,
to: 20,
color: '#ff0000',
},
{
from: 30,
to: 50,
color: '#00ff00',
},
],
});
view.renderer().draw(target);
expect(context.fillRect).toHaveBeenNthCalledWith(
1,
20,
0,
20,
200,
);
expect(context.fillRect).toHaveBeenNthCalledWith(
2,
60,
0,
40,
200,
);
expect(context.save).toHaveBeenCalledTimes(1);
expect(context.restore).toHaveBeenCalledTimes(1);
});
it('должен получать актуальные segments при каждом draw', () => {
const { context, target } = createTarget();
const getAxisSegments = jest
.fn()
.mockReturnValueOnce([
{
from: 10,
to: 20,
color: '#ff0000',
},
])
.mockReturnValueOnce([
{
from: 30,
to: 50,
color: '#00ff00',
},
]);
const view = new CustomTimeAxisPaneView({
getAxisSegments,
});
view.renderer().draw(target);
view.renderer().draw(target);
expect(getAxisSegments).toHaveBeenCalledTimes(2);
expect(context.fillRect).toHaveBeenNthCalledWith(
1,
20,
0,
20,
200,
);
expect(context.fillRect).toHaveBeenNthCalledWith(
2,
60,
0,
40,
200,
);
});
});
describe('CustomTimeAxisView', () => {
it('должен использовать width viewport для проверки видимости', () => {
const view = new CustomTimeAxisView({
getAxisLabel: () => ({
coordinate: 250,
text: '09:30',
textColor: '#ffffff',
backgroundColor: '#000000',
}),
labelKind: 'start',
viewport: createViewport(300, 100),
});
expect(view.visible()).toBe(true);
});
it('не должен использовать height viewport для проверки видимости', () => {
const view = new CustomTimeAxisView({
getAxisLabel: () => ({
coordinate: 150,
text: '09:30',
textColor: '#ffffff',
backgroundColor: '#000000',
}),
labelKind: 'start',
viewport: createViewport(100, 400),
});
expect(view.visible()).toBe(false);
});
it('должен считать правую границу width видимой', () => {
const view = new CustomTimeAxisView({
getAxisLabel: () => ({
coordinate: 300,
text: '09:30',
textColor: '#ffffff',
backgroundColor: '#000000',
}),
labelKind: 'start',
viewport: createViewport(300, 100),
});
expect(view.visible()).toBe(true);
});
it('не должен показывать label правее width viewport', () => {
const view = new CustomTimeAxisView({
getAxisLabel: () => ({
coordinate: 301,
text: '09:30',
textColor: '#ffffff',
backgroundColor: '#000000',
}),
labelKind: 'start',
viewport: createViewport(300, 1000),
});
expect(view.visible()).toBe(false);
});
});