import { test, expect } from '@playwright/test'; import { createCharacter } from './helpers'; test.beforeEach(async ({ page }) => { await page.goto('/'); await page.evaluate(async () => { indexedDB.deleteDatabase('ttrpg-manager'); localStorage.clear(); }); await page.reload(); }); test('maps: upload, place token, reveal all, show to players', async ({ page }) => { await page.getByRole('button', { name: '+ New campaign' }).first().click(); await page.locator('input[data-autofocus]').fill('VTT'); await page.getByRole('button', { name: 'Create' }).click(); await page.getByRole('link', { name: 'Dashboard' }).click(); await page.getByRole('link', { name: 'Maps' }).click(); await page.locator('input[type="file"]').setInputFiles('public/pwa-512x512.png'); // Editor loads (Move tool present) await expect(page.getByRole('button', { name: 'move', exact: true })).toBeVisible(); // Add a token await page.getByRole('button', { name: '+ Token' }).click(); await expect(page.getByTestId('map-token')).toHaveCount(1); // Reveal tool exposes the contextual fog controls; reveal all await page.getByRole('button', { name: 'reveal', exact: true }).click(); await page.getByText('Fog', { exact: true }).locator('input[type=checkbox]').check(); await page.getByRole('button', { name: 'Reveal all' }).click(); // Show to players, then the player view renders the battle map await page.getByRole('button', { name: 'Show to players' }).click(); await page.getByRole('link', { name: 'Open player view ↗' }).click(); await expect(page.getByRole('heading', { name: 'Battle map' })).toBeVisible(); await expect(page.getByTestId('map-token')).toHaveCount(1); }); test('maps: import a Universal VTT (.dd2vtt) map', async ({ page }) => { await page.getByRole('button', { name: '+ New campaign' }).first().click(); await page.locator('input[data-autofocus]').fill('VTTimport'); await page.getByRole('button', { name: 'Create' }).click(); await page.getByRole('link', { name: 'Dashboard' }).click(); await page.getByRole('link', { name: 'Maps' }).click(); await page.locator('input[type="file"]').setInputFiles('e2e/fixtures/sample.dd2vtt'); // Imported map opens in the editor with the file's pixels_per_grid as the grid size. await expect(page.getByTestId('map-viewport')).toBeVisible(); await expect(page.getByLabel('Grid cell size')).toHaveValue('100'); await expect(page.getByRole('button', { name: 'Export .uvtt' })).toBeVisible(); }); test('maps: zoom controls scale the canvas and fit resets', async ({ page }) => { await page.getByRole('button', { name: '+ New campaign' }).first().click(); await page.locator('input[data-autofocus]').fill('Zoom'); await page.getByRole('button', { name: 'Create' }).click(); await page.getByRole('link', { name: 'Dashboard' }).click(); await page.getByRole('link', { name: 'Maps' }).click(); await page.locator('input[type="file"]').setInputFiles('public/pwa-512x512.png'); const viewport = page.getByTestId('map-viewport'); await expect(viewport).toBeVisible(); const scaleOf = async () => Number(await viewport.getAttribute('data-zoom')); // Let the initial fit settle, then zooming in must increase the zoom… await page.getByRole('button', { name: 'Fit map to view' }).click(); const fitZoom = await scaleOf(); expect(fitZoom).toBeGreaterThan(0); await page.getByRole('button', { name: 'Zoom in' }).click(); await page.getByRole('button', { name: 'Zoom in' }).click(); const zoomedIn = await scaleOf(); expect(zoomedIn).toBeGreaterThan(fitZoom); // …and Fit brings it back to the fit value. await page.getByRole('button', { name: 'Fit map to view' }).click(); expect(await scaleOf()).toBeCloseTo(fitZoom, 5); }); test('maps: place a token from a planned encounter via the palette', async ({ page }) => { await page.getByRole('button', { name: '+ New campaign' }).first().click(); await page.locator('input[data-autofocus]').fill('Enc'); await page.getByRole('button', { name: 'Create' }).click(); // A planned (not-yet-started) encounter with a monster. await page.getByRole('link', { name: 'Combat' }).click(); await page.getByRole('button', { name: '+ New encounter' }).first().click(); await page.locator('input[data-autofocus]').fill('Ambush'); await page.getByRole('button', { name: 'Create' }).click(); await page.getByLabel('Name').fill('Bandit'); await page.getByRole('button', { name: 'Add', exact: true }).click(); await page.getByRole('link', { name: 'Dashboard' }).click(); await page.getByRole('link', { name: 'Maps' }).click(); await page.locator('input[type="file"]').setInputFiles('public/pwa-512x512.png'); // The palette's Encounter section surfaces the planned encounter's combatant. await expect(page.getByText('Encounter', { exact: true })).toBeVisible(); await page.getByRole('button', { name: /Bandit/ }).click(); await expect(page.getByTestId('map-token')).toHaveCount(1); await expect(page.getByTestId('map-token')).toContainText('Bandit'); }); test('maps: a character portrait becomes the token icon', async ({ page }) => { await page.getByRole('button', { name: '+ New campaign' }).first().click(); await page.locator('input[data-autofocus]').fill('Art'); await page.getByRole('button', { name: 'Create' }).click(); await page.getByRole('link', { name: 'Characters' }).click(); await createCharacter(page, 'Aria'); // Upload a portrait on the sheet; it renders in the header avatar. await page.getByLabel('Portrait').setInputFiles('public/pwa-512x512.png'); await expect(page.locator('label img').first()).toBeVisible(); await page.getByRole('link', { name: 'Dashboard' }).click(); await page.getByRole('link', { name: 'Maps' }).click(); await page.locator('input[type="file"]').setInputFiles('public/pwa-512x512.png'); // Placing the PC drops a token that shows the portrait image. await page.getByRole('button', { name: /Aria/ }).click(); await expect(page.getByTestId('map-token').first().locator('img')).toBeVisible(); }); test('maps: place a party token from the palette', async ({ page }) => { await page.getByRole('button', { name: '+ New campaign' }).first().click(); await page.locator('input[data-autofocus]').fill('Party'); await page.getByRole('button', { name: 'Create' }).click(); await page.getByRole('link', { name: 'Characters' }).click(); await createCharacter(page, 'Aria'); await page.getByRole('link', { name: 'Dashboard' }).click(); await page.getByRole('link', { name: 'Maps' }).click(); await page.locator('input[type="file"]').setInputFiles('public/pwa-512x512.png'); // Palette lists the PC; clicking it drops a linked token. await page.getByRole('button', { name: /Aria/ }).click(); await expect(page.getByTestId('map-token')).toHaveCount(1); await expect(page.getByTestId('map-token')).toContainText('Aria'); });