|
| 1 | +/** |
| 2 | + * E2E tests for issue #23: bar misalignment at non-100% browser zoom. |
| 3 | + * |
| 4 | + * Root cause: table-layout:auto gives columns fractional pixel widths at |
| 5 | + * non-100% zoom. As columns accumulate, bars drift away from their date. |
| 6 | + * |
| 7 | + * Fix: table-layout:fixed + explicit JS cell widths (already applied). |
| 8 | + * |
| 9 | + * Test strategy: |
| 10 | + * - Apply CSS zoom to #embedded-Gantt (equivalent to browser-level zoom) |
| 11 | + * - At each zoom level, assert bars scale proportionally with zoom |
| 12 | + * (viewportLeft ≈ styleLeft × zoom ± tolerance) |
| 13 | + * - Assert column widths are uniform within each zoom level |
| 14 | + * (no fractional-px spread from table-layout:auto) |
| 15 | + */ |
| 16 | + |
| 17 | +import { test, expect } from '@playwright/test'; |
| 18 | +import { GanttPage, DEMO_URL } from './gantt.page'; |
| 19 | + |
| 20 | +const ZOOM_LEVELS = [0.75, 0.8, 1.0, 1.25, 1.5]; |
| 21 | + |
| 22 | +/** Maximum allowed drift between expected and actual bar position (px). */ |
| 23 | +const DRIFT_TOLERANCE_PX = 3; |
| 24 | + |
| 25 | +/** Maximum allowed spread between widest and narrowest column at the same zoom (px). */ |
| 26 | +const COL_SPREAD_TOLERANCE_PX = 1; |
| 27 | + |
| 28 | +test.describe('Issue #23 — bar alignment at non-100% zoom', () => { |
| 29 | + for (const zoom of ZOOM_LEVELS) { |
| 30 | + const label = `${Math.round(zoom * 100)}%`; |
| 31 | + |
| 32 | + test(`bars are correctly aligned at ${label} zoom`, async ({ page }) => { |
| 33 | + const gantt = new GanttPage(page); |
| 34 | + await gantt.goto(DEMO_URL); |
| 35 | + await gantt.applyZoom(zoom); |
| 36 | + |
| 37 | + const bars = await gantt.getBarMetrics(8); |
| 38 | + expect(bars.length, 'should find rendered task bars').toBeGreaterThan(0); |
| 39 | + |
| 40 | + for (const bar of bars) { |
| 41 | + const expectedLeft = bar.styleLeft * zoom; |
| 42 | + const drift = Math.abs(bar.viewportLeft - expectedLeft); |
| 43 | + expect( |
| 44 | + drift, |
| 45 | + `${bar.id} at zoom ${label}: viewportLeft=${bar.viewportLeft} expected≈${expectedLeft.toFixed(1)}`, |
| 46 | + ).toBeLessThanOrEqual(DRIFT_TOLERANCE_PX); |
| 47 | + } |
| 48 | + }); |
| 49 | + |
| 50 | + test(`column widths are uniform at ${label} zoom`, async ({ page }) => { |
| 51 | + const gantt = new GanttPage(page); |
| 52 | + await gantt.goto(DEMO_URL); |
| 53 | + |
| 54 | + // Capture baseline column width at 100% before applying zoom |
| 55 | + const baseCols = await gantt.getColumnMetrics(5); |
| 56 | + const baseColWidth = baseCols[0]?.viewportWidth ?? 39; |
| 57 | + |
| 58 | + await gantt.applyZoom(zoom); |
| 59 | + |
| 60 | + const cols = await gantt.getColumnMetrics(20); |
| 61 | + expect(cols.length, 'should find header columns').toBeGreaterThan(0); |
| 62 | + |
| 63 | + // All columns at the same zoom level should have the same width |
| 64 | + const widths = cols.map(c => c.viewportWidth); |
| 65 | + const spread = Math.max(...widths) - Math.min(...widths); |
| 66 | + expect( |
| 67 | + spread, |
| 68 | + `column width spread at ${label} zoom (min=${Math.min(...widths).toFixed(2)} max=${Math.max(...widths).toFixed(2)})`, |
| 69 | + ).toBeLessThanOrEqual(COL_SPREAD_TOLERANCE_PX); |
| 70 | + |
| 71 | + // Column widths should scale proportionally with the zoom factor |
| 72 | + const expectedWidth = baseColWidth * zoom; |
| 73 | + const avgWidth = widths.reduce((a, b) => a + b, 0) / widths.length; |
| 74 | + expect( |
| 75 | + Math.abs(avgWidth - expectedWidth), |
| 76 | + `avg col width ${avgWidth.toFixed(2)} should be ≈ baseCol(${baseColWidth}) × zoom(${zoom}) = ${expectedWidth.toFixed(2)}`, |
| 77 | + ).toBeLessThanOrEqual(1); |
| 78 | + }); |
| 79 | + } |
| 80 | +}); |
0 commit comments