mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-15 03:41:10 +02:00
test(calendar): add composable unit tests and WeekView E2E interaction tests
Unit tests (vitest): - useEventDragDrop.test.ts: 7 tests (idle state, drag start, resize start, cancel, hasMoved reset, getResizePreviewTime, preview positions) - useTaskDragDrop.test.ts: 5 tests (idle state, drag start, resize start, cancel, preview position calculation) - useDragToCreate.test.ts: 4 tests (idle state, guard when other op active, cancel, preview time format) E2E tests (playwright): - week-view-interactions.spec.ts: 7 tests covering drag-to-create (click, drag with time range, escape cancel), event card positioning, current time indicator, day headers, and today highlighting All 116 unit tests passing across 12 test files. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5832326010
commit
2eb280cc07
4 changed files with 591 additions and 0 deletions
184
apps/calendar/apps/web/e2e/week-view-interactions.spec.ts
Normal file
184
apps/calendar/apps/web/e2e/week-view-interactions.spec.ts
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
import { test, expect, dismissOnboarding } from './fixtures/auth';
|
||||
|
||||
const BACKEND_URL = process.env.PUBLIC_BACKEND_URL || 'http://localhost:3014';
|
||||
|
||||
test.describe('WeekView Interactions', () => {
|
||||
test.beforeAll(async () => {
|
||||
try {
|
||||
const res = await fetch(`${BACKEND_URL}/api/v1/health`, {
|
||||
signal: AbortSignal.timeout(3000),
|
||||
});
|
||||
if (!res.ok) test.skip(true, 'Calendar backend is not running');
|
||||
} catch {
|
||||
test.skip(true, 'Calendar backend is not reachable');
|
||||
}
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await dismissOnboarding(page);
|
||||
await expect(page.locator('main[aria-label="Kalender"]')).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
|
||||
test('drag-to-create: clicking on empty time slot opens quick create overlay', async ({
|
||||
page,
|
||||
}) => {
|
||||
// Find a day column in the week view
|
||||
const dayColumn = page.locator('.day-column').first();
|
||||
await expect(dayColumn).toBeVisible();
|
||||
|
||||
const box = await dayColumn.boundingBox();
|
||||
if (!box) return;
|
||||
|
||||
// Click in the middle of the day column (should open quick create)
|
||||
await dayColumn.click({ position: { x: box.width / 2, y: box.height * 0.4 } });
|
||||
|
||||
// Quick event overlay should appear
|
||||
const overlay = page.locator('.quick-event-overlay');
|
||||
await expect(overlay).toBeVisible({ timeout: 5000 });
|
||||
|
||||
// Close it
|
||||
await page.keyboard.press('Escape');
|
||||
});
|
||||
|
||||
test('drag-to-create: drag creates event with correct time range', async ({ page }) => {
|
||||
const dayColumn = page.locator('.day-column').first();
|
||||
await expect(dayColumn).toBeVisible();
|
||||
|
||||
const box = await dayColumn.boundingBox();
|
||||
if (!box) return;
|
||||
|
||||
// Drag from ~10am to ~12pm area
|
||||
const startY = box.y + box.height * 0.35;
|
||||
const endY = box.y + box.height * 0.5;
|
||||
const centerX = box.x + box.width / 2;
|
||||
|
||||
await page.mouse.move(centerX, startY);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(centerX, endY, { steps: 5 });
|
||||
await page.mouse.up();
|
||||
|
||||
// Quick event overlay should appear
|
||||
const overlay = page.locator('.quick-event-overlay');
|
||||
await expect(overlay).toBeVisible({ timeout: 5000 });
|
||||
|
||||
// Type a title and save
|
||||
const uniqueTitle = `Drag Create ${Date.now()}`;
|
||||
await page.keyboard.type(uniqueTitle);
|
||||
await overlay.getByRole('button', { name: /speichern/i }).click();
|
||||
await expect(overlay).not.toBeVisible({ timeout: 5000 });
|
||||
|
||||
// Event should appear in the grid
|
||||
const eventCard = page.locator('.event-card').filter({ hasText: uniqueTitle });
|
||||
await expect(eventCard).toBeVisible({ timeout: 5000 });
|
||||
|
||||
// Cleanup: delete the event
|
||||
await eventCard.click();
|
||||
const editOverlay = page.locator('.quick-event-overlay');
|
||||
await expect(editOverlay).toBeVisible({ timeout: 5000 });
|
||||
const deleteBtn = editOverlay.getByRole('button', { name: /löschen/i });
|
||||
if (await deleteBtn.isVisible()) {
|
||||
await deleteBtn.click();
|
||||
const confirmBtn = page.getByRole('button', { name: /löschen|ja|bestätigen/i });
|
||||
if (await confirmBtn.isVisible({ timeout: 2000 }).catch(() => false)) {
|
||||
await confirmBtn.click();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('escape cancels drag-to-create', async ({ page }) => {
|
||||
const dayColumn = page.locator('.day-column').first();
|
||||
await expect(dayColumn).toBeVisible();
|
||||
|
||||
const box = await dayColumn.boundingBox();
|
||||
if (!box) return;
|
||||
|
||||
const startY = box.y + box.height * 0.3;
|
||||
const centerX = box.x + box.width / 2;
|
||||
|
||||
// Start dragging
|
||||
await page.mouse.move(centerX, startY);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(centerX, startY + 50, { steps: 3 });
|
||||
|
||||
// Press escape to cancel
|
||||
await page.keyboard.press('Escape');
|
||||
await page.mouse.up();
|
||||
|
||||
// No overlay should appear
|
||||
const overlay = page.locator('.quick-event-overlay');
|
||||
await expect(overlay).not.toBeVisible({ timeout: 1000 });
|
||||
});
|
||||
|
||||
test('event card shows in correct position within time grid', async ({ page }) => {
|
||||
const uniqueTitle = `Position Test ${Date.now()}`;
|
||||
|
||||
// Create an event by clicking on the grid
|
||||
const dayColumn = page.locator('.day-column').first();
|
||||
await expect(dayColumn).toBeVisible();
|
||||
const box = await dayColumn.boundingBox();
|
||||
if (!box) return;
|
||||
|
||||
await dayColumn.click({ position: { x: box.width / 2, y: box.height * 0.5 } });
|
||||
|
||||
const overlay = page.locator('.quick-event-overlay');
|
||||
await expect(overlay).toBeVisible({ timeout: 5000 });
|
||||
await page.keyboard.type(uniqueTitle);
|
||||
await overlay.getByRole('button', { name: /speichern/i }).click();
|
||||
await expect(overlay).not.toBeVisible({ timeout: 5000 });
|
||||
|
||||
// Verify the event card exists and has a top style (positioned in grid)
|
||||
const eventCard = page.locator('.event-card').filter({ hasText: uniqueTitle });
|
||||
await expect(eventCard).toBeVisible({ timeout: 5000 });
|
||||
|
||||
const style = await eventCard.getAttribute('style');
|
||||
expect(style).toContain('top:');
|
||||
expect(style).toContain('height:');
|
||||
|
||||
// Cleanup
|
||||
await eventCard.click();
|
||||
const editOverlay = page.locator('.quick-event-overlay');
|
||||
const deleteBtn = editOverlay.getByRole('button', { name: /löschen/i });
|
||||
if (await deleteBtn.isVisible()) {
|
||||
await deleteBtn.click();
|
||||
const confirmBtn = page.getByRole('button', { name: /löschen|ja|bestätigen/i });
|
||||
if (await confirmBtn.isVisible({ timeout: 2000 }).catch(() => false)) {
|
||||
await confirmBtn.click();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('week view shows current time indicator on today', async ({ page }) => {
|
||||
const timeIndicator = page.locator('.time-indicator');
|
||||
// There should be at least one time indicator (on today's column)
|
||||
await expect(timeIndicator.first()).toBeVisible({ timeout: 5000 });
|
||||
|
||||
// It should have a top percentage style
|
||||
const style = await timeIndicator.first().getAttribute('style');
|
||||
expect(style).toContain('top:');
|
||||
});
|
||||
|
||||
test('week view shows correct day headers', async ({ page }) => {
|
||||
const dayHeaders = page.locator('.day-header');
|
||||
const count = await dayHeaders.count();
|
||||
|
||||
// Should have 5 (weekdays only) or 7 (full week) day headers
|
||||
expect(count === 5 || count === 7).toBe(true);
|
||||
|
||||
// Each header should have a day name and number
|
||||
for (let i = 0; i < count; i++) {
|
||||
const dayName = dayHeaders.nth(i).locator('.day-name');
|
||||
const dayNumber = dayHeaders.nth(i).locator('.day-number');
|
||||
await expect(dayName).toBeVisible();
|
||||
await expect(dayNumber).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('today column is highlighted', async ({ page }) => {
|
||||
const todayColumn = page.locator('.day-column.today');
|
||||
await expect(todayColumn).toBeVisible({ timeout: 5000 });
|
||||
|
||||
const todayHeader = page.locator('.day-header.today');
|
||||
await expect(todayHeader).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
|
||||
// Polyfill PointerEvent for jsdom
|
||||
if (typeof globalThis.PointerEvent === 'undefined') {
|
||||
globalThis.PointerEvent = class PointerEvent extends MouseEvent {
|
||||
constructor(type: string, params: PointerEventInit = {}) {
|
||||
super(type, params);
|
||||
}
|
||||
} as unknown as typeof PointerEvent;
|
||||
}
|
||||
|
||||
vi.mock('$lib/utils/calendarConstants', () => ({
|
||||
SNAP_INTERVAL_MINUTES: 15,
|
||||
}));
|
||||
|
||||
import { useDragToCreate } from './useDragToCreate.svelte';
|
||||
|
||||
function createMockContainer() {
|
||||
return {
|
||||
getBoundingClientRect: () => ({
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 700,
|
||||
bottom: 960,
|
||||
width: 700,
|
||||
height: 960,
|
||||
}),
|
||||
parentElement: { scrollTop: 0 },
|
||||
} as unknown as HTMLElement;
|
||||
}
|
||||
|
||||
function makeDays(): Date[] {
|
||||
return Array.from({ length: 7 }, (_, i) => {
|
||||
const d = new Date('2026-03-02');
|
||||
d.setDate(d.getDate() + i);
|
||||
return d;
|
||||
});
|
||||
}
|
||||
|
||||
function minutesToPercent(minutes: number): number {
|
||||
const firstHour = 0;
|
||||
const totalHours = 24;
|
||||
const adjusted = minutes - firstHour * 60;
|
||||
return (adjusted / (totalHours * 60)) * 100;
|
||||
}
|
||||
|
||||
describe('useDragToCreate', () => {
|
||||
let onCreateEnd: ReturnType<typeof vi.fn>;
|
||||
|
||||
function createInstance(overrides = {}) {
|
||||
const container = createMockContainer();
|
||||
onCreateEnd = vi.fn();
|
||||
return useDragToCreate(() => ({
|
||||
containerEl: container,
|
||||
days: makeDays(),
|
||||
firstVisibleHour: 0,
|
||||
lastVisibleHour: 24,
|
||||
totalVisibleHours: 24,
|
||||
hourHeight: 40,
|
||||
minutesToPercent,
|
||||
isOtherOperationActive: () => false,
|
||||
onCreateEnd,
|
||||
...overrides,
|
||||
}));
|
||||
}
|
||||
|
||||
it('should start in idle state', () => {
|
||||
const dtc = createInstance();
|
||||
expect(dtc.isCreating).toBe(false);
|
||||
expect(dtc.createTargetDay).toBeNull();
|
||||
});
|
||||
|
||||
it('should not start create if other operation is active', () => {
|
||||
const dtc = createInstance({ isOtherOperationActive: () => true });
|
||||
const target = document.createElement('div');
|
||||
const event = new PointerEvent('pointerdown', { clientX: 100, clientY: 200 });
|
||||
Object.defineProperty(event, 'target', { value: target });
|
||||
dtc.startCreate(event);
|
||||
expect(dtc.isCreating).toBe(false);
|
||||
});
|
||||
|
||||
it('should cancel on cancel()', () => {
|
||||
const dtc = createInstance();
|
||||
const target = document.createElement('div');
|
||||
const event = new PointerEvent('pointerdown', { clientX: 100, clientY: 200 });
|
||||
Object.defineProperty(event, 'target', { value: target });
|
||||
dtc.startCreate(event);
|
||||
dtc.cancel();
|
||||
expect(dtc.isCreating).toBe(false);
|
||||
expect(dtc.createTargetDay).toBeNull();
|
||||
});
|
||||
|
||||
it('should generate correct preview time format', () => {
|
||||
const dtc = createInstance();
|
||||
// getCreatePreviewTime uses internal state, returns HH:MM - HH:MM format
|
||||
const time = dtc.getCreatePreviewTime();
|
||||
expect(time).toMatch(/^\d{2}:\d{2} - \d{2}:\d{2}$/);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
|
||||
// Polyfill PointerEvent for jsdom
|
||||
if (typeof globalThis.PointerEvent === 'undefined') {
|
||||
globalThis.PointerEvent = class PointerEvent extends MouseEvent {
|
||||
constructor(type: string, params: PointerEventInit = {}) {
|
||||
super(type, params);
|
||||
}
|
||||
} as unknown as typeof PointerEvent;
|
||||
}
|
||||
|
||||
vi.mock('$lib/utils/eventDateHelpers', () => ({
|
||||
toDate: (d: string | Date) => new Date(d),
|
||||
}));
|
||||
|
||||
vi.mock('$lib/stores/events.svelte', () => ({
|
||||
eventsStore: {
|
||||
isDraftEvent: vi.fn(() => false),
|
||||
updateDraftEvent: vi.fn(),
|
||||
updateEvent: vi.fn().mockResolvedValue({ data: {}, error: null }),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('$lib/utils/calendarConstants', () => ({
|
||||
SNAP_INTERVAL_MINUTES: 15,
|
||||
}));
|
||||
|
||||
import { useEventDragDrop } from './useEventDragDrop.svelte';
|
||||
|
||||
function createMockContainer() {
|
||||
return {
|
||||
getBoundingClientRect: () => ({
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 700,
|
||||
bottom: 960,
|
||||
width: 700,
|
||||
height: 960,
|
||||
}),
|
||||
parentElement: { scrollTop: 0 },
|
||||
} as unknown as HTMLElement;
|
||||
}
|
||||
|
||||
function makeDays(): Date[] {
|
||||
return Array.from({ length: 7 }, (_, i) => {
|
||||
const d = new Date('2026-03-02');
|
||||
d.setDate(d.getDate() + i);
|
||||
return d;
|
||||
});
|
||||
}
|
||||
|
||||
function minutesToPercent(minutes: number): number {
|
||||
return (minutes / (24 * 60)) * 100;
|
||||
}
|
||||
|
||||
function makeConfig() {
|
||||
return {
|
||||
containerEl: createMockContainer(),
|
||||
days: makeDays(),
|
||||
firstVisibleHour: 0,
|
||||
lastVisibleHour: 24,
|
||||
totalVisibleHours: 24,
|
||||
hourHeight: 40,
|
||||
minutesToPercent,
|
||||
};
|
||||
}
|
||||
|
||||
describe('useEventDragDrop', () => {
|
||||
it('should start in idle state', () => {
|
||||
const dd = useEventDragDrop(() => makeConfig());
|
||||
expect(dd.isDragging).toBe(false);
|
||||
expect(dd.isResizing).toBe(false);
|
||||
expect(dd.draggedEvent).toBeNull();
|
||||
expect(dd.resizeEvent).toBeNull();
|
||||
expect(dd.hasMoved).toBe(false);
|
||||
});
|
||||
|
||||
it('should set isDragging when startDrag is called', () => {
|
||||
const dd = useEventDragDrop(() => makeConfig());
|
||||
const event = {
|
||||
id: 'evt-1',
|
||||
startTime: '2026-03-02T10:00:00',
|
||||
endTime: '2026-03-02T11:00:00',
|
||||
};
|
||||
const pointerEvent = new PointerEvent('pointerdown', {
|
||||
clientX: 100,
|
||||
clientY: 200,
|
||||
});
|
||||
|
||||
dd.startDrag(event as any, pointerEvent);
|
||||
|
||||
expect(dd.isDragging).toBe(true);
|
||||
expect(dd.draggedEvent).toBeTruthy();
|
||||
expect(dd.draggedEvent!.id).toBe('evt-1');
|
||||
|
||||
// Cleanup
|
||||
dd.cancel();
|
||||
});
|
||||
|
||||
it('should set isResizing when startResize is called', () => {
|
||||
const dd = useEventDragDrop(() => makeConfig());
|
||||
const event = {
|
||||
id: 'evt-1',
|
||||
startTime: '2026-03-02T10:00:00',
|
||||
endTime: '2026-03-02T11:00:00',
|
||||
};
|
||||
const pointerEvent = new PointerEvent('pointerdown', {
|
||||
clientX: 100,
|
||||
clientY: 200,
|
||||
});
|
||||
|
||||
dd.startResize(event as any, 'bottom', pointerEvent);
|
||||
|
||||
expect(dd.isResizing).toBe(true);
|
||||
expect(dd.resizeEvent).toBeTruthy();
|
||||
|
||||
dd.cancel();
|
||||
});
|
||||
|
||||
it('should reset all state on cancel', () => {
|
||||
const dd = useEventDragDrop(() => makeConfig());
|
||||
const event = {
|
||||
id: 'evt-1',
|
||||
startTime: '2026-03-02T10:00:00',
|
||||
endTime: '2026-03-02T11:00:00',
|
||||
};
|
||||
const pointerEvent = new PointerEvent('pointerdown', {
|
||||
clientX: 100,
|
||||
clientY: 200,
|
||||
});
|
||||
|
||||
dd.startDrag(event as any, pointerEvent);
|
||||
expect(dd.isDragging).toBe(true);
|
||||
|
||||
dd.cancel();
|
||||
expect(dd.isDragging).toBe(false);
|
||||
expect(dd.draggedEvent).toBeNull();
|
||||
});
|
||||
|
||||
it('should reset hasMoved on resetHasMoved', () => {
|
||||
const dd = useEventDragDrop(() => makeConfig());
|
||||
// hasMoved starts false
|
||||
expect(dd.hasMoved).toBe(false);
|
||||
dd.resetHasMoved();
|
||||
expect(dd.hasMoved).toBe(false);
|
||||
});
|
||||
|
||||
it('should return empty string for getResizePreviewTime when not resizing', () => {
|
||||
const dd = useEventDragDrop(() => makeConfig());
|
||||
expect(dd.getResizePreviewTime()).toBe('');
|
||||
});
|
||||
|
||||
it('should calculate preview positions on drag start', () => {
|
||||
const dd = useEventDragDrop(() => makeConfig());
|
||||
const event = {
|
||||
id: 'evt-1',
|
||||
startTime: '2026-03-02T12:00:00', // noon
|
||||
endTime: '2026-03-02T13:00:00', // 1pm
|
||||
};
|
||||
const pointerEvent = new PointerEvent('pointerdown', {
|
||||
clientX: 100,
|
||||
clientY: 480, // middle of container
|
||||
});
|
||||
|
||||
dd.startDrag(event as any, pointerEvent);
|
||||
|
||||
// Preview top should be around 50% (12:00 = 720 min / 1440 min)
|
||||
expect(dd.dragPreviewTop).toBeCloseTo(50, 0);
|
||||
// Height should be ~4.17% (60 min / 1440 min)
|
||||
expect(dd.dragPreviewHeight).toBeCloseTo(4.17, 0);
|
||||
|
||||
dd.cancel();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
import { describe, it, expect, vi } from 'vitest';
|
||||
|
||||
// Polyfill PointerEvent for jsdom
|
||||
if (typeof globalThis.PointerEvent === 'undefined') {
|
||||
globalThis.PointerEvent = class PointerEvent extends MouseEvent {
|
||||
constructor(type: string, params: PointerEventInit = {}) {
|
||||
super(type, params);
|
||||
}
|
||||
} as unknown as typeof PointerEvent;
|
||||
}
|
||||
|
||||
vi.mock('$lib/stores/todos.svelte', () => ({
|
||||
todosStore: {
|
||||
updateTodo: vi.fn().mockResolvedValue(undefined),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('$lib/utils/calendarConstants', () => ({
|
||||
SNAP_INTERVAL_MINUTES: 15,
|
||||
}));
|
||||
|
||||
import { useTaskDragDrop } from './useTaskDragDrop.svelte';
|
||||
|
||||
function createMockContainer() {
|
||||
const el = {
|
||||
getBoundingClientRect: () => ({
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 700,
|
||||
bottom: 960,
|
||||
width: 700,
|
||||
height: 960,
|
||||
}),
|
||||
querySelectorAll: () => [],
|
||||
querySelector: () => null,
|
||||
parentElement: { scrollTop: 0 },
|
||||
} as unknown as HTMLElement;
|
||||
return el;
|
||||
}
|
||||
|
||||
function makeDays(): Date[] {
|
||||
return Array.from({ length: 7 }, (_, i) => {
|
||||
const d = new Date('2026-03-02');
|
||||
d.setDate(d.getDate() + i);
|
||||
return d;
|
||||
});
|
||||
}
|
||||
|
||||
describe('useTaskDragDrop', () => {
|
||||
function createInstance() {
|
||||
return useTaskDragDrop(() => ({
|
||||
containerEl: createMockContainer(),
|
||||
days: makeDays(),
|
||||
firstVisibleHour: 0,
|
||||
totalVisibleHours: 24,
|
||||
}));
|
||||
}
|
||||
|
||||
it('should start in idle state', () => {
|
||||
const td = createInstance();
|
||||
expect(td.isTaskDragging).toBe(false);
|
||||
expect(td.isTaskResizing).toBe(false);
|
||||
expect(td.draggedTask).toBeNull();
|
||||
expect(td.resizeTask).toBeNull();
|
||||
expect(td.hasMoved).toBe(false);
|
||||
});
|
||||
|
||||
it('should set isTaskDragging when startDrag is called', () => {
|
||||
const td = createInstance();
|
||||
const task = {
|
||||
id: 'task-1',
|
||||
scheduledStartTime: '10:00',
|
||||
estimatedDuration: 30,
|
||||
};
|
||||
const event = new PointerEvent('pointerdown', { clientX: 100, clientY: 200 });
|
||||
|
||||
td.startDrag(task as any, event);
|
||||
|
||||
expect(td.isTaskDragging).toBe(true);
|
||||
expect(td.draggedTask).toBeTruthy();
|
||||
expect(td.draggedTask!.id).toBe('task-1');
|
||||
|
||||
td.cancel();
|
||||
});
|
||||
|
||||
it('should set isTaskResizing when startResize is called', () => {
|
||||
const td = createInstance();
|
||||
const task = {
|
||||
id: 'task-1',
|
||||
scheduledStartTime: '10:00',
|
||||
estimatedDuration: 30,
|
||||
};
|
||||
const event = new PointerEvent('pointerdown', { clientX: 100, clientY: 200 });
|
||||
|
||||
td.startResize(task as any, 'bottom', event);
|
||||
|
||||
expect(td.isTaskResizing).toBe(true);
|
||||
expect(td.resizeTask).toBeTruthy();
|
||||
|
||||
td.cancel();
|
||||
});
|
||||
|
||||
it('should reset state on cancel', () => {
|
||||
const td = createInstance();
|
||||
const task = { id: 'task-1', scheduledStartTime: '10:00', estimatedDuration: 30 };
|
||||
const event = new PointerEvent('pointerdown', { clientX: 100, clientY: 200 });
|
||||
|
||||
td.startDrag(task as any, event);
|
||||
expect(td.isTaskDragging).toBe(true);
|
||||
|
||||
td.cancel();
|
||||
expect(td.isTaskDragging).toBe(false);
|
||||
expect(td.draggedTask).toBeNull();
|
||||
});
|
||||
|
||||
it('should calculate preview position from task time', () => {
|
||||
const td = createInstance();
|
||||
const task = {
|
||||
id: 'task-1',
|
||||
scheduledStartTime: '12:00', // noon
|
||||
estimatedDuration: 60,
|
||||
};
|
||||
const event = new PointerEvent('pointerdown', { clientX: 100, clientY: 480 });
|
||||
|
||||
td.startDrag(task as any, event);
|
||||
|
||||
// Preview top should be around 50% (12:00 / 24:00)
|
||||
expect(td.taskDragPreviewTop).toBeCloseTo(50, 0);
|
||||
// Height should be ~4.17% (60 min / 1440 min)
|
||||
expect(td.taskDragPreviewHeight).toBeCloseTo(4.17, 0);
|
||||
|
||||
td.cancel();
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue