This commit increases strictnes of tests by failing on tests (even though they pass) if `console.warn` or `console.error` is used. This is used to fix warning outputs from Vue, cleaning up test output and preventing potential issues with tests. This commit fixes all of the failing tests, including refactoring in code to make them more testable through injecting Vue lifecycle hook function stubs. This removes `shallowMount`ing done on places, improving the speed of executing unit tests. It also reduces complexity and increases maintainability by removing `@vue/test-utils` dependency for these tests. Changes: - Register global hook for all tests to fail if console.error or console.warn is being used. - Fix all issues with failing tests. - Create test helper function for running code in a wrapper component to run code in reliable/unified way to surpress Vue warnings about code not running inside `setup`.
298 lines
11 KiB
TypeScript
298 lines
11 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { ref, type Ref } from 'vue';
|
|
import { useDragHandler, type DragDomModifier } from '@/presentation/components/Scripts/Slider/UseDragHandler';
|
|
import { ThrottleStub } from '@tests/unit/shared/Stubs/ThrottleStub';
|
|
import { type ThrottleFunction } from '@/application/Common/Timing/Throttle';
|
|
import type { ConstructorArguments } from '@/TypeHelpers';
|
|
import type { LifecycleHook } from '@/presentation/components/Shared/Hooks/Common/LifecycleHook';
|
|
import { LifecycleHookStub } from '@tests/unit/shared/Stubs/LifecycleHookStub';
|
|
|
|
describe('useDragHandler', () => {
|
|
describe('initially', () => {
|
|
it('sets displacement X to 0', () => {
|
|
// arrange
|
|
const expectedValue = 0;
|
|
|
|
// act
|
|
const { displacementX } = initializeDragHandlerWithMocks();
|
|
|
|
// assert
|
|
expect(displacementX.value).to.equal(expectedValue);
|
|
});
|
|
it('sets dragging state to false', () => {
|
|
// arrange
|
|
const expectedValue = false;
|
|
|
|
// act
|
|
const { isDragging } = initializeDragHandlerWithMocks();
|
|
|
|
// assert
|
|
expect(isDragging.value).to.equal(expectedValue);
|
|
});
|
|
it('disables element touch action', () => {
|
|
// arrange
|
|
const expectedTouchAction = 'none';
|
|
const mockElement = document.createElement('div');
|
|
|
|
// act
|
|
initializeDragHandlerWithMocks({
|
|
draggableElementRef: ref(mockElement),
|
|
});
|
|
|
|
// assert
|
|
const actualTouchAction = mockElement.style.touchAction;
|
|
expect(actualTouchAction).to.equal(expectedTouchAction);
|
|
});
|
|
it('attaches event listener for drag start', () => {
|
|
// arrange
|
|
const expectedEventName: keyof HTMLElementEventMap = 'pointerdown';
|
|
let actualEventName: keyof HTMLElementEventMap | undefined;
|
|
const mockElement = document.createElement('div');
|
|
mockElement.addEventListener = (eventName: keyof HTMLElementEventMap) => {
|
|
actualEventName = eventName;
|
|
};
|
|
|
|
// act
|
|
initializeDragHandlerWithMocks({
|
|
draggableElementRef: ref(mockElement),
|
|
});
|
|
|
|
// assert
|
|
expect(expectedEventName).to.equal(actualEventName);
|
|
});
|
|
});
|
|
describe('on drag', () => {
|
|
it('activates dragging on start of drag', () => {
|
|
// arrange
|
|
const expectedDragState = true;
|
|
const mockElement = document.createElement('div');
|
|
|
|
// act
|
|
const { isDragging } = initializeDragHandlerWithMocks({
|
|
draggableElementRef: ref(mockElement),
|
|
});
|
|
mockElement.dispatchEvent(createMockPointerEvent('pointerdown', { clientX: 100 }));
|
|
|
|
// assert
|
|
expect(isDragging.value).to.equal(expectedDragState);
|
|
});
|
|
it('updates displacement during dragging', () => {
|
|
// arrange
|
|
const initialDragX = 100;
|
|
const finalDragX = 150;
|
|
const expectedDisplacementX = finalDragX - initialDragX;
|
|
const mockElement = document.createElement('div');
|
|
const dragDomModifierMock = createDragDomModifierMock();
|
|
|
|
// act
|
|
const { displacementX } = initializeDragHandlerWithMocks({
|
|
draggableElementRef: ref(mockElement),
|
|
dragDomModifier: dragDomModifierMock,
|
|
});
|
|
mockElement.dispatchEvent(createMockPointerEvent('pointerdown', { clientX: 100 }));
|
|
dragDomModifierMock.simulateEvent('pointermove', createMockPointerEvent('pointermove', { clientX: finalDragX }));
|
|
|
|
// assert
|
|
expect(displacementX.value).to.equal(expectedDisplacementX);
|
|
});
|
|
it('attaches event listeners', () => {
|
|
// arrange
|
|
const expectedEventNames: ReadonlyArray<keyof HTMLElementEventMap> = [
|
|
'pointerup',
|
|
'pointermove',
|
|
];
|
|
const mockElement = document.createElement('div');
|
|
const dragDomModifierMock = createDragDomModifierMock();
|
|
|
|
// act
|
|
initializeDragHandlerWithMocks({
|
|
draggableElementRef: ref(mockElement),
|
|
dragDomModifier: dragDomModifierMock,
|
|
});
|
|
mockElement.dispatchEvent(createMockPointerEvent('pointerdown', { clientX: 100 }));
|
|
|
|
// assert
|
|
const actualEventNames = [...dragDomModifierMock.events].map(([eventName]) => eventName);
|
|
expect(expectedEventNames).to.have.lengthOf(actualEventNames.length);
|
|
expect(expectedEventNames).to.have.members(actualEventNames);
|
|
});
|
|
describe('throttling', () => {
|
|
it('initializes event throttling', () => {
|
|
// arrange
|
|
const throttleStub = new ThrottleStub()
|
|
.withImmediateExecution(false);
|
|
const mockElement = document.createElement('div');
|
|
|
|
// act
|
|
initializeDragHandlerWithMocks({
|
|
draggableElementRef: ref(mockElement),
|
|
throttler: throttleStub.func,
|
|
});
|
|
|
|
// assert
|
|
expect(throttleStub.throttleInitializationCallArgs.length).to.equal(1);
|
|
});
|
|
it('sets a specific throttle time interval', () => {
|
|
// arrange
|
|
const expectedThrottleInMs = 15;
|
|
const throttleStub = new ThrottleStub()
|
|
.withImmediateExecution(false);
|
|
const mockElement = document.createElement('div');
|
|
|
|
// act
|
|
initializeDragHandlerWithMocks({
|
|
draggableElementRef: ref(mockElement),
|
|
throttler: throttleStub.func,
|
|
});
|
|
|
|
// assert
|
|
expect(throttleStub.throttleInitializationCallArgs.length).to.equal(1);
|
|
const [, actualThrottleInMs] = throttleStub.throttleInitializationCallArgs[0];
|
|
expect(expectedThrottleInMs).to.equal(actualThrottleInMs);
|
|
});
|
|
it('limits frequency of drag movement updates', () => {
|
|
// arrange
|
|
const expectedTotalThrottledEvents = 3;
|
|
const throttleStub = new ThrottleStub();
|
|
const mockElement = document.createElement('div');
|
|
const dragDomModifierMock = createDragDomModifierMock();
|
|
|
|
// act
|
|
initializeDragHandlerWithMocks({
|
|
draggableElementRef: ref(mockElement),
|
|
throttler: throttleStub.func,
|
|
dragDomModifier: dragDomModifierMock,
|
|
});
|
|
mockElement.dispatchEvent(createMockPointerEvent('pointerdown', { clientX: 100 }));
|
|
for (let i = 0; i < expectedTotalThrottledEvents; i++) {
|
|
dragDomModifierMock.simulateEvent('pointermove', createMockPointerEvent('pointermove', { clientX: 110 }));
|
|
}
|
|
|
|
// assert
|
|
const actualTotalThrottledEvents = throttleStub.throttledFunctionCallArgs.length;
|
|
expect(actualTotalThrottledEvents).to.equal(expectedTotalThrottledEvents);
|
|
});
|
|
it('calculates displacement considering throttling', () => {
|
|
// arrange
|
|
const throttleStub = new ThrottleStub().withImmediateExecution(true);
|
|
const mockElement = document.createElement('div');
|
|
const initialDragX = 100;
|
|
const firstDisplacementX = 10;
|
|
const dragDomModifierMock = createDragDomModifierMock();
|
|
|
|
// act
|
|
const { displacementX } = initializeDragHandlerWithMocks({
|
|
draggableElementRef: ref(mockElement),
|
|
throttler: throttleStub.func,
|
|
dragDomModifier: dragDomModifierMock,
|
|
});
|
|
mockElement.dispatchEvent(createMockPointerEvent('pointerdown', { clientX: initialDragX }));
|
|
dragDomModifierMock.simulateEvent('pointermove', createMockPointerEvent('pointermove', { clientX: initialDragX - firstDisplacementX }));
|
|
dragDomModifierMock.simulateEvent('pointermove', createMockPointerEvent('pointermove', { clientX: initialDragX - firstDisplacementX * 2 }));
|
|
throttleStub.executeFirst();
|
|
|
|
// assert
|
|
expect(displacementX.value).to.equal(firstDisplacementX);
|
|
});
|
|
});
|
|
});
|
|
describe('on drag end', () => {
|
|
it('deactivates dragging on drag end', () => {
|
|
// arrange
|
|
const expectedDraggingState = false;
|
|
const mockElement = document.createElement('div');
|
|
const dragDomModifierMock = createDragDomModifierMock();
|
|
|
|
// act
|
|
const { isDragging } = initializeDragHandlerWithMocks({
|
|
draggableElementRef: ref(mockElement),
|
|
dragDomModifier: dragDomModifierMock,
|
|
});
|
|
mockElement.dispatchEvent(createMockPointerEvent('pointerdown', { clientX: 100 }));
|
|
dragDomModifierMock.simulateEvent('pointerup', createMockPointerEvent('pointerup'));
|
|
|
|
// assert
|
|
expect(isDragging.value).to.equal(expectedDraggingState);
|
|
});
|
|
it('removes event listeners', () => {
|
|
// arrange
|
|
const mockElement = document.createElement('div');
|
|
const dragDomModifierMock = createDragDomModifierMock();
|
|
|
|
// act
|
|
initializeDragHandlerWithMocks({
|
|
draggableElementRef: ref(mockElement),
|
|
dragDomModifier: dragDomModifierMock,
|
|
});
|
|
mockElement.dispatchEvent(createMockPointerEvent('pointerdown', { clientX: 100 }));
|
|
dragDomModifierMock.simulateEvent('pointerup', createMockPointerEvent('pointerup'));
|
|
|
|
// assert
|
|
const actualEvents = [...dragDomModifierMock.events];
|
|
expect(actualEvents).to.have.lengthOf(0);
|
|
});
|
|
});
|
|
describe('on teardown', () => {
|
|
it('removes event listeners', () => {
|
|
// arrange
|
|
const teardownHook = new LifecycleHookStub();
|
|
const mockElement = document.createElement('div');
|
|
const dragDomModifierMock = createDragDomModifierMock();
|
|
|
|
// act
|
|
initializeDragHandlerWithMocks({
|
|
draggableElementRef: ref(mockElement),
|
|
dragDomModifier: dragDomModifierMock,
|
|
onTeardown: teardownHook.getHook(),
|
|
});
|
|
mockElement.dispatchEvent(createMockPointerEvent('pointerdown', { clientX: 100 }));
|
|
teardownHook.executeAllCallbacks();
|
|
|
|
// assert
|
|
const actualEvents = [...dragDomModifierMock.events];
|
|
expect(actualEvents).to.have.lengthOf(0);
|
|
});
|
|
});
|
|
});
|
|
|
|
function initializeDragHandlerWithMocks(mocks?: {
|
|
readonly dragDomModifier?: DragDomModifier;
|
|
readonly draggableElementRef?: Ref<HTMLElement>;
|
|
readonly throttler?: ThrottleFunction,
|
|
readonly onTeardown?: LifecycleHook,
|
|
}) {
|
|
return useDragHandler(
|
|
mocks?.draggableElementRef ?? ref(document.createElement('div')),
|
|
mocks?.dragDomModifier ?? createDragDomModifierMock(),
|
|
mocks?.throttler ?? new ThrottleStub().withImmediateExecution(true).func,
|
|
mocks?.onTeardown ?? new LifecycleHookStub().getHook(),
|
|
);
|
|
}
|
|
|
|
function createMockPointerEvent(...args: ConstructorArguments<typeof PointerEvent>): PointerEvent {
|
|
return new MouseEvent(...args) as PointerEvent; // jsdom does not support `PointerEvent` constructor, https://github.com/jsdom/jsdom/issues/2527
|
|
}
|
|
|
|
function createDragDomModifierMock(): DragDomModifier & {
|
|
simulateEvent(type: keyof DocumentEventMap, event: Event): void;
|
|
readonly events: Map<keyof DocumentEventMap, EventListener>;
|
|
} {
|
|
const events = new Map<keyof DocumentEventMap, EventListener>();
|
|
return {
|
|
addEventListenerToDocument: (type, handler) => {
|
|
events.set(type, handler);
|
|
},
|
|
removeEventListenerFromDocument: (type) => {
|
|
events.delete(type);
|
|
},
|
|
simulateEvent: (type, event) => {
|
|
const handler = events.get(type);
|
|
if (!handler) {
|
|
throw new Error(`No event handler registered for: ${type}`);
|
|
}
|
|
handler(event);
|
|
},
|
|
events,
|
|
};
|
|
}
|