import { describe, it, expect, afterEach, vi } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import { page } from 'vitest/browser';
import { tick } from 'svelte';
import TimelineDensityFilter from './TimelineDensityFilter.svelte';
import { formatTickLabel } from '$lib/shared/utils/monthBuckets';
import { getLocale } from '$lib/paraglide/runtime';
import type { components } from '$lib/generated/api';

type MonthBucket = components['schemas']['MonthBucket'];

afterEach(() => cleanup());

const NOOP = () => undefined;

function makeProps(overrides: Record<string, unknown> = {}) {
	return {
		density: [
			{ month: '1915-08', count: 5 },
			{ month: '1915-09', count: 2 },
			{ month: '1915-10', count: 8 }
		] satisfies MonthBucket[],
		minDate: '1915-08-01',
		maxDate: '1915-10-31',
		from: '',
		to: '',
		onchange: NOOP,
		...overrides
	};
}

describe('TimelineDensityFilter — visibility', () => {
	it('renders nothing when density is null', async () => {
		render(TimelineDensityFilter, makeProps({ density: null, minDate: null, maxDate: null }));
		expect(document.querySelector('[data-testid="timeline-density-filter"]')).toBeNull();
	});

	it('renders the widget when density is populated', async () => {
		render(TimelineDensityFilter, makeProps());
		await expect.element(page.getByTestId('timeline-density-filter')).toBeInTheDocument();
	});

	it('exposes an accessible group label on the widget', async () => {
		render(TimelineDensityFilter, makeProps());
		const widget = document.querySelector('[data-testid="timeline-density-filter"]') as HTMLElement;
		expect(widget.getAttribute('role')).toBe('group');
		expect(widget.getAttribute('aria-label')).toBeTruthy();
	});
});

describe('TimelineDensityFilter — axes', () => {
	it('renders a Y-axis showing the maximum bar count and zero', async () => {
		render(
			TimelineDensityFilter,
			makeProps({
				density: [
					{ month: '1915-08', count: 5 },
					{ month: '1915-09', count: 12 },
					{ month: '1915-10', count: 8 }
				],
				minDate: '1915-08-01',
				maxDate: '1915-10-31'
			})
		);

		const yAxis = document.querySelector('[data-testid="timeline-y-axis"]') as HTMLElement;
		expect(yAxis).not.toBeNull();
		expect(yAxis.textContent).toContain('12');
		expect(yAxis.textContent).toContain('0');
	});

	it('renders X-axis ticks at January boundaries for long month ranges', async () => {
		const buckets: MonthBucket[] = [];
		for (let m = 8; m <= 12; m++)
			buckets.push({ month: `1914-${String(m).padStart(2, '0')}`, count: 1 });
		for (let m = 1; m <= 12; m++)
			buckets.push({ month: `1915-${String(m).padStart(2, '0')}`, count: 1 });
		for (let m = 1; m <= 2; m++)
			buckets.push({ month: `1916-${String(m).padStart(2, '0')}`, count: 1 });

		render(
			TimelineDensityFilter,
			makeProps({ density: buckets, minDate: '1914-08-01', maxDate: '1916-02-29' })
		);

		const ticks = document.querySelectorAll('[data-testid="timeline-x-tick"]');
		expect(ticks.length).toBe(2);
		expect(Array.from(ticks).map((t) => t.textContent?.trim())).toEqual(
			expect.arrayContaining([expect.stringContaining('1915'), expect.stringContaining('1916')])
		);
	});

	it('renders X-axis ticks for year-aggregated bars (every 10 years for ~50yr range)', async () => {
		const buckets: MonthBucket[] = [];
		for (let year = 1900; year <= 1949; year++) {
			for (let month = 1; month <= 12; month++) {
				buckets.push({ month: `${year}-${String(month).padStart(2, '0')}`, count: 1 });
			}
		}
		render(
			TimelineDensityFilter,
			makeProps({ density: buckets, minDate: '1900-01-01', maxDate: '1949-12-31' })
		);

		const ticks = document.querySelectorAll('[data-testid="timeline-x-tick"]');
		const labels = Array.from(ticks).map((t) => t.textContent?.trim());
		expect(labels).toEqual(['1900', '1910', '1920', '1930', '1940']);
	});
});

describe('TimelineDensityFilter — bars', () => {
	it('renders one bar per month within the range, including zero-count gaps', async () => {
		render(
			TimelineDensityFilter,
			makeProps({
				density: [
					{ month: '1915-08', count: 5 },
					{ month: '1915-10', count: 8 }
				],
				minDate: '1915-08-01',
				maxDate: '1915-10-31'
			})
		);

		const bars = document.querySelectorAll('[data-testid="timeline-bar"]');
		expect(bars.length).toBe(3);
	});

	it('zero-count months get the minimum visible bar height of 2px', async () => {
		render(
			TimelineDensityFilter,
			makeProps({
				density: [{ month: '1915-08', count: 4 }],
				minDate: '1915-08-01',
				maxDate: '1915-09-30'
			})
		);

		const bars = document.querySelectorAll(
			'[data-testid="timeline-bar"] .bar-fill'
		) as NodeListOf<HTMLElement>;
		expect(bars.length).toBe(2);
		expect(bars[1].style.height).toBe('2px');
	});

	it('renders an empty widget without crashing when density is empty array and no range', async () => {
		render(TimelineDensityFilter, makeProps({ density: [], minDate: null, maxDate: null }));
		await expect.element(page.getByTestId('timeline-density-filter')).toBeInTheDocument();
		expect(document.querySelectorAll('[data-testid="timeline-bar"]').length).toBe(0);
	});
});

describe('TimelineDensityFilter — selection', () => {
	it('clicking a bar emits the boundary dates of that month via onchange', async () => {
		const onchange = vi.fn();
		render(TimelineDensityFilter, makeProps({ onchange }));

		const bars = document.querySelectorAll(
			'[data-testid="timeline-bar"]'
		) as NodeListOf<HTMLElement>;
		bars[0].dispatchEvent(new MouseEvent('click', { bubbles: true }));

		expect(onchange).toHaveBeenCalledTimes(1);
		expect(onchange).toHaveBeenCalledWith({ from: '1915-08-01', to: '1915-08-31' });
	});

	it('shows a clear button when from/to are set', async () => {
		render(TimelineDensityFilter, makeProps({ from: '1915-08-01', to: '1915-09-30' }));
		await expect.element(page.getByTestId('timeline-clear')).toBeInTheDocument();
	});

	it('does not show the clear button when from/to are empty', async () => {
		render(TimelineDensityFilter, makeProps());
		expect(document.querySelector('[data-testid="timeline-clear"]')).toBeNull();
	});

	it('clicking the clear button emits empty dates via onchange', async () => {
		const onchange = vi.fn();
		render(TimelineDensityFilter, makeProps({ from: '1915-08-01', to: '1915-09-30', onchange }));

		const clearBtn = document.querySelector('[data-testid="timeline-clear"]') as HTMLButtonElement;
		clearBtn.dispatchEvent(new MouseEvent('click', { bubbles: true }));

		expect(onchange).toHaveBeenCalledTimes(1);
		expect(onchange).toHaveBeenCalledWith({ from: '', to: '' });
	});

	it('clear button is a real <button> with aria-label (Nora a11y review)', async () => {
		render(TimelineDensityFilter, makeProps({ from: '1915-08-01', to: '1915-09-30' }));

		const clearBtn = document.querySelector('[data-testid="timeline-clear"]') as HTMLElement;
		expect(clearBtn.tagName).toBe('BUTTON');
		expect(clearBtn.getAttribute('aria-label')).toBeTruthy();
	});
});

describe('TimelineDensityFilter — year-granularity fallback', () => {
	it('collapses to year buckets when the month sequence exceeds the limit', async () => {
		// 21 years × 12 months = 252 entries — above the 240 month limit.
		const buckets: MonthBucket[] = [];
		for (let year = 1900; year <= 1920; year++) {
			buckets.push({ month: `${year}-06`, count: year - 1899 });
		}
		render(
			TimelineDensityFilter,
			makeProps({ density: buckets, minDate: '1900-01-01', maxDate: '1920-12-31' })
		);

		const bars = document.querySelectorAll('[data-testid="timeline-bar"]');
		expect(bars.length).toBe(21);
		const firstLabel = bars[0].getAttribute('aria-label') ?? '';
		// Localised, not the raw machine string "1900 · 1".
		expect(firstLabel).not.toMatch(/^\d{4} · \d+$/);
		expect(firstLabel).toContain('1900');
		expect(firstLabel).toContain('1');
	});

	it('clicking a year bar zooms into that year (filter + zoom atomic)', async () => {
		const onchange = vi.fn();
		const buckets: MonthBucket[] = [];
		for (let year = 1900; year <= 1920; year++) {
			buckets.push({ month: `${year}-06`, count: 5 });
		}
		render(
			TimelineDensityFilter,
			makeProps({
				density: buckets,
				minDate: '1900-01-01',
				maxDate: '1920-12-31',
				onchange
			})
		);

		const bars = document.querySelectorAll(
			'[data-testid="timeline-bar"]'
		) as NodeListOf<HTMLElement>;
		bars[5].dispatchEvent(new MouseEvent('click', { bubbles: true }));

		expect(onchange).toHaveBeenCalledWith({
			from: '1905-01-01',
			to: '1905-12-31',
			zoomFrom: '1905-01-01',
			zoomTo: '1905-12-31'
		});
	});
});

describe('TimelineDensityFilter — zoom', () => {
	it('does not show the zoom-in button (drag replaces it as the zoom gesture)', async () => {
		render(TimelineDensityFilter, makeProps({ from: '1915-08-01', to: '1915-09-30' }));
		expect(document.querySelector('[data-testid="timeline-zoom-in"]')).toBeNull();
	});

	it('shows the reset-zoom button only when zoomed', async () => {
		render(TimelineDensityFilter, makeProps({ zoomFrom: '1915-08-01', zoomTo: '1915-09-30' }));
		await expect.element(page.getByTestId('timeline-zoom-reset')).toBeInTheDocument();
	});

	it('clicking reset-zoom emits onzoomchange(null)', async () => {
		const onzoomchange = vi.fn();
		render(
			TimelineDensityFilter,
			makeProps({ zoomFrom: '1915-08-01', zoomTo: '1915-09-30', onzoomchange })
		);

		const resetBtn = document.querySelector(
			'[data-testid="timeline-zoom-reset"]'
		) as HTMLButtonElement;
		resetBtn.dispatchEvent(new MouseEvent('click', { bubbles: true }));

		expect(onzoomchange).toHaveBeenCalledWith(null);
	});

	it('when zoomed, only bars within the zoom range are rendered', async () => {
		// 21-year span normally collapses to year mode (>240 months handled
		// elsewhere). Zooming in to a 3-month window should restore month bars.
		const buckets: MonthBucket[] = [];
		for (let year = 1900; year <= 1920; year++) {
			for (let month = 1; month <= 12; month++) {
				buckets.push({
					month: `${year}-${String(month).padStart(2, '0')}`,
					count: 1
				});
			}
		}

		render(
			TimelineDensityFilter,
			makeProps({
				density: buckets,
				minDate: '1900-01-01',
				maxDate: '1920-12-31',
				zoomFrom: '1910-06-01',
				zoomTo: '1910-08-31'
			})
		);

		const bars = document.querySelectorAll('[data-testid="timeline-bar"]');
		// 3 months in zoom range
		expect(bars.length).toBe(3);
		const firstLabel = bars[0].getAttribute('aria-label') ?? '';
		const lastLabel = bars[2].getAttribute('aria-label') ?? '';
		// Localised — must NOT contain the raw "YYYY-MM" machine string.
		expect(firstLabel).not.toMatch(/\d{4}-\d{2}/);
		expect(lastLabel).not.toMatch(/\d{4}-\d{2}/);
		expect(firstLabel).toContain(formatTickLabel('1910-06', getLocale()));
		expect(lastLabel).toContain(formatTickLabel('1910-08', getLocale()));
	});
});

describe('TimelineDensityFilter — touch targets', () => {
	it('reset-zoom button is at least 44×44 (WCAG 2.5.8)', async () => {
		render(TimelineDensityFilter, makeProps({ zoomFrom: '1915-08-01', zoomTo: '1915-09-30' }));
		const resetBtn = document.querySelector('[data-testid="timeline-zoom-reset"]') as HTMLElement;
		expect(resetBtn.classList.contains('h-11')).toBe(true);
		expect(resetBtn.className).toMatch(/min-w-\[44px\]/);
		expect(resetBtn.className).not.toMatch(/(?:^|\s)h-6(?:$|\s)/);
	});

	it('clear-selection button is at least 44×44 (WCAG 2.5.8)', async () => {
		render(TimelineDensityFilter, makeProps({ from: '1915-08-01', to: '1915-09-30' }));
		const clearBtn = document.querySelector('[data-testid="timeline-clear"]') as HTMLElement;
		expect(clearBtn.classList.contains('h-11')).toBe(true);
		expect(clearBtn.classList.contains('w-11')).toBe(true);
		expect(clearBtn.className).not.toMatch(/(?:^|\s)h-6(?:$|\s)/);
	});

	it('bar buttons render a focus-visible ring so keyboard users can see focus', async () => {
		render(TimelineDensityFilter, makeProps());
		const bar = document.querySelector('[data-testid="timeline-bar"]') as HTMLElement;
		expect(bar.className).toMatch(/focus-visible:ring-2/);
		expect(bar.className).toMatch(/focus-visible:ring-brand-navy/);
		expect(bar.className).toMatch(/focus-visible:ring-offset-2/);
	});

	it('reset-zoom button renders the same focus-visible ring as the bars', async () => {
		render(TimelineDensityFilter, makeProps({ zoomFrom: '1915-08-01', zoomTo: '1915-09-30' }));
		const resetBtn = document.querySelector('[data-testid="timeline-zoom-reset"]') as HTMLElement;
		expect(resetBtn.className).toMatch(/focus-visible:ring-2/);
		expect(resetBtn.className).toMatch(/focus-visible:ring-brand-navy/);
		expect(resetBtn.className).toMatch(/focus-visible:ring-offset-2/);
	});

	it('clear-selection button renders the same focus-visible ring as the bars', async () => {
		render(TimelineDensityFilter, makeProps({ from: '1915-08-01', to: '1915-09-30' }));
		const clearBtn = document.querySelector('[data-testid="timeline-clear"]') as HTMLElement;
		expect(clearBtn.className).toMatch(/focus-visible:ring-2/);
		expect(clearBtn.className).toMatch(/focus-visible:ring-brand-navy/);
		expect(clearBtn.className).toMatch(/focus-visible:ring-offset-2/);
	});

	it('bar hover style is gated by @media (hover: hover) to avoid touch-device hover-stick', async () => {
		render(TimelineDensityFilter, makeProps());
		const cssText = Array.from(document.styleSheets)
			.flatMap((sheet) => {
				try {
					return Array.from(sheet.cssRules);
				} catch {
					return [];
				}
			})
			.map((rule) => rule.cssText)
			.join('\n');
		expect(cssText).toMatch(/@media \(hover: hover\)/);
	});
});

describe('TimelineDensityFilter — accessibility', () => {
	it('Y-axis labels meet the 12px minimum font floor (Tailwind text-xs)', async () => {
		render(TimelineDensityFilter, makeProps());
		const yAxis = document.querySelector('[data-testid="timeline-y-axis"]') as HTMLElement;
		expect(yAxis.classList.contains('text-xs')).toBe(true);
		expect(yAxis.className).not.toMatch(/text-\[10px\]/);
	});

	it('X-axis row uses text-xs and h-4 to fit the 12px line height', async () => {
		render(TimelineDensityFilter, makeProps());
		const xAxis = document.querySelector('[data-testid="timeline-x-axis"]') as HTMLElement;
		expect(xAxis.classList.contains('text-xs')).toBe(true);
		expect(xAxis.classList.contains('h-4')).toBe(true);
		expect(xAxis.className).not.toMatch(/text-\[10px\]/);
	});

	it('bar aria-label uses the singular noun form when count is 1', async () => {
		render(
			TimelineDensityFilter,
			makeProps({
				density: [{ month: '1915-08', count: 1 }],
				minDate: '1915-08-01',
				maxDate: '1915-08-31'
			})
		);
		const bar = document.querySelector('[data-testid="timeline-bar"]') as HTMLElement;
		const label = bar.getAttribute('aria-label') ?? '';
		// "documents", "Dokumente", "documentos" are the plural-only forms in our 3 locales.
		expect(label).not.toMatch(/\b(?:documents|Dokumente|documentos)\b/);
		expect(label).toMatch(/\b1 (?:document|Dokument|documento)\b/);
	});

	it('bar aria-label uses the plural noun form when count is not 1', async () => {
		render(
			TimelineDensityFilter,
			makeProps({
				density: [{ month: '1915-08', count: 5 }],
				minDate: '1915-08-01',
				maxDate: '1915-08-31'
			})
		);
		const bar = document.querySelector('[data-testid="timeline-bar"]') as HTMLElement;
		const label = bar.getAttribute('aria-label') ?? '';
		expect(label).toMatch(/\b5 (?:documents|Dokumente|documentos)\b/);
	});

	it('bar aria-label is built from a localised template, never the raw YYYY-MM', async () => {
		render(
			TimelineDensityFilter,
			makeProps({
				density: [{ month: '1915-08', count: 5 }],
				minDate: '1915-08-01',
				maxDate: '1915-08-31'
			})
		);

		const bar = document.querySelector('[data-testid="timeline-bar"]') as HTMLElement;
		const label = bar.getAttribute('aria-label') ?? '';
		expect(label).not.toMatch(/1915-08/);
		expect(label).toContain(formatTickLabel('1915-08', getLocale()));
		expect(label).toContain('5');
	});
});

describe('TimelineDensityFilter — aria-live during drag', () => {
	function pointerDown(el: HTMLElement) {
		el.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, pointerId: 1, button: 0 }));
	}
	function pointerEnter(el: HTMLElement) {
		el.dispatchEvent(new PointerEvent('pointerenter', { bubbles: true, pointerId: 1 }));
	}
	function pointerUp(el: HTMLElement) {
		el.dispatchEvent(new PointerEvent('pointerup', { bubbles: true, pointerId: 1, button: 0 }));
	}

	it('renders a polite aria-live region whose text reflects the dragged range', async () => {
		render(
			TimelineDensityFilter,
			makeProps({
				density: [
					{ month: '1915-08', count: 1 },
					{ month: '1915-09', count: 2 },
					{ month: '1915-10', count: 3 }
				],
				minDate: '1915-08-01',
				maxDate: '1915-10-31'
			})
		);

		const live = document.querySelector('[data-testid="timeline-aria-live"]') as HTMLElement;
		expect(live).not.toBeNull();
		expect(live.getAttribute('aria-live')).toBe('polite');
		expect(live.textContent?.trim() ?? '').toBe('');

		const bars = document.querySelectorAll(
			'[data-testid="timeline-bar"]'
		) as NodeListOf<HTMLElement>;
		pointerDown(bars[0]);
		pointerEnter(bars[2]);
		await tick();

		const text = live.textContent ?? '';
		expect(text).toContain(formatTickLabel('1915-08', getLocale()));
		expect(text).toContain(formatTickLabel('1915-10', getLocale()));

		pointerUp(bars[2]);
		await tick();
		expect(live.textContent?.trim() ?? '').toBe('');
	});
});

describe('TimelineDensityFilter — listener cleanup on unmount', () => {
	it('removes document pointer listeners when unmounted mid-drag', async () => {
		const removed: string[] = [];
		const realRemove = document.removeEventListener.bind(document);
		const removeSpy = vi
			.spyOn(document, 'removeEventListener')
			.mockImplementation((type: string, listener, options) => {
				removed.push(type);
				return realRemove(type, listener as EventListener, options);
			});

		render(TimelineDensityFilter, makeProps());
		const bar = document.querySelector('[data-testid="timeline-bar"]') as HTMLElement;
		bar.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, pointerId: 1, button: 0 }));

		cleanup();

		expect(removed).toContain('pointermove');
		expect(removed).toContain('pointerup');
		expect(removed).toContain('pointercancel');
		removeSpy.mockRestore();
	});
});

describe('TimelineDensityFilter — drag-to-select-range', () => {
	function pointerDown(el: HTMLElement) {
		const event = new PointerEvent('pointerdown', { bubbles: true, pointerId: 1, button: 0 });
		// jsdom-style stub for setPointerCapture (the real DOM has it but vitest-browser
		// uses Playwright-driven Chromium so it works natively too).
		el.dispatchEvent(event);
	}
	function pointerEnter(el: HTMLElement) {
		el.dispatchEvent(new PointerEvent('pointerenter', { bubbles: true, pointerId: 1 }));
	}
	function pointerUp(el: HTMLElement) {
		el.dispatchEvent(new PointerEvent('pointerup', { bubbles: true, pointerId: 1, button: 0 }));
	}

	it('dragging from bar A to bar B emits a single onchange with filter + zoom (atomic)', async () => {
		const onchange = vi.fn();
		render(
			TimelineDensityFilter,
			makeProps({
				density: [
					{ month: '1915-08', count: 1 },
					{ month: '1915-09', count: 2 },
					{ month: '1915-10', count: 3 }
				],
				minDate: '1915-08-01',
				maxDate: '1915-10-31',
				onchange
			})
		);

		const bars = document.querySelectorAll(
			'[data-testid="timeline-bar"]'
		) as NodeListOf<HTMLElement>;
		pointerDown(bars[0]);
		pointerEnter(bars[2]);
		pointerUp(bars[2]);

		expect(onchange).toHaveBeenCalledTimes(1);
		expect(onchange).toHaveBeenCalledWith({
			from: '1915-08-01',
			to: '1915-10-31',
			zoomFrom: '1915-08-01',
			zoomTo: '1915-10-31'
		});
	});

	it('dragging from a later bar to an earlier bar still emits ascending boundaries', async () => {
		const onchange = vi.fn();
		render(
			TimelineDensityFilter,
			makeProps({
				density: [
					{ month: '1915-08', count: 1 },
					{ month: '1915-09', count: 2 },
					{ month: '1915-10', count: 3 }
				],
				minDate: '1915-08-01',
				maxDate: '1915-10-31',
				onchange
			})
		);

		const bars = document.querySelectorAll(
			'[data-testid="timeline-bar"]'
		) as NodeListOf<HTMLElement>;
		pointerDown(bars[2]);
		pointerEnter(bars[0]);
		pointerUp(bars[0]);

		expect(onchange).toHaveBeenCalledWith({
			from: '1915-08-01',
			to: '1915-10-31',
			zoomFrom: '1915-08-01',
			zoomTo: '1915-10-31'
		});
	});

	it('pressing+releasing on the same bar selects that single month without zoom', async () => {
		const onchange = vi.fn();
		render(TimelineDensityFilter, makeProps({ onchange }));

		const bars = document.querySelectorAll(
			'[data-testid="timeline-bar"]'
		) as NodeListOf<HTMLElement>;
		pointerDown(bars[1]);
		pointerUp(bars[1]);

		expect(onchange).toHaveBeenCalledTimes(1);
		expect(onchange).toHaveBeenCalledWith({ from: '1915-09-01', to: '1915-09-30' });
	});

	it('renders a drag-window overlay between drag start and current position', async () => {
		render(
			TimelineDensityFilter,
			makeProps({
				density: [
					{ month: '1915-08', count: 1 },
					{ month: '1915-09', count: 2 },
					{ month: '1915-10', count: 3 },
					{ month: '1915-11', count: 4 }
				],
				minDate: '1915-08-01',
				maxDate: '1915-11-30'
			})
		);

		const bars = document.querySelectorAll(
			'[data-testid="timeline-bar"]'
		) as NodeListOf<HTMLElement>;

		expect(document.querySelector('[data-testid="timeline-drag-window"]')).toBeNull();

		pointerDown(bars[0]);
		pointerEnter(bars[2]);
		await tick();

		const win = document.querySelector('[data-testid="timeline-drag-window"]') as HTMLElement;
		expect(win).not.toBeNull();
		// 4 bars total, drag covers indices 0..2 → left 0%, right 25%.
		expect(win.style.left).toBe('0%');
		expect(win.style.right).toBe('25%');

		pointerUp(bars[2]);
		await tick();
		expect(document.querySelector('[data-testid="timeline-drag-window"]')).toBeNull();
	});

	it('marks bars in the active drag range with the in-drag-preview class', async () => {
		render(
			TimelineDensityFilter,
			makeProps({
				density: [
					{ month: '1915-08', count: 1 },
					{ month: '1915-09', count: 2 },
					{ month: '1915-10', count: 3 },
					{ month: '1915-11', count: 4 }
				],
				minDate: '1915-08-01',
				maxDate: '1915-11-30'
			})
		);

		const bars = document.querySelectorAll(
			'[data-testid="timeline-bar"]'
		) as NodeListOf<HTMLElement>;
		pointerDown(bars[0]);
		pointerEnter(bars[2]);
		await tick();

		expect(bars[0].classList.contains('in-drag-preview')).toBe(true);
		expect(bars[1].classList.contains('in-drag-preview')).toBe(true);
		expect(bars[2].classList.contains('in-drag-preview')).toBe(true);
		expect(bars[3].classList.contains('in-drag-preview')).toBe(false);

		pointerUp(bars[2]);
	});
});
