Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181import { expect, test, type Page } from '@playwright/test'import { readFile } from 'node:fs/promises'import { mockOAuth } from './oauthFixture'
const owner = 'did:plc:nuxt-shell'const created = `at://${owner}/space/community.lexicon.calendar/created`const seeded = `at://${owner}/space/community.lexicon.calendar/seeded`
async function setup(page: Page, options: { populated?: boolean; path?: string } = {}) { const populated = options.populated ?? false await page.clock.setFixedTime(new Date('2026-09-11T12:00:00Z')) await mockOAuth(page, owner, 'https://nuxt-shell.invalid') await page.route('**/xrpc/**', async route => { const request = route.request() const endpoint = new URL(request.url()).pathname.split('/').pop()! const body = request.method() === 'POST' ? request.postDataJSON() || {} : {} const reply = (data: unknown, status = 200) => route.fulfill({ status, contentType: 'application/json', body: JSON.stringify(data) }) if (endpoint.endsWith('refreshSession') || endpoint.endsWith('getSession')) return reply({ did: owner, handle: 'shell.test', accessJwt: 'x', refreshJwt: 'y', active: true, didDoc: { id: owner } }) if (endpoint.endsWith('listSpaces')) return reply({ spaces: populated ? [{ uri: seeded }] : [] }) if (endpoint.endsWith('getProfile')) return reply({ did: owner, handle: 'shell.test', displayName: 'Shell User' }) if (endpoint.endsWith('listMembers')) return reply({ members: [] }) if (endpoint.endsWith('listRecords')) return reply({ records: populated ? [{ rkey: 'seed', cid: 'bafyreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku', collection: 'community.lexicon.calendar.event', value: { $type: 'community.lexicon.calendar.event', name: 'Seed event', startsAt: '2026-09-11T09:00:00Z', endsAt: '2026-09-11T10:00:00Z', createdAt: '2026-09-01T00:00:00Z' } }] : [] }) if (endpoint.endsWith('getRecord') && new URL(request.url()).searchParams.get('collection')?.endsWith('.meta')) return reply({ uri: seeded, cid: 'bafyreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku', value: { name: 'Seed calendar', color: '#0284c7', createdAt: '2026-09-01T00:00:00Z' } }) if (endpoint.endsWith('createSpace')) return reply({ uri: created }) if (endpoint.endsWith('putRecord')) return reply({ uri: `${created}/self` }) if (endpoint.endsWith('getRecord')) return reply({ error: 'RecordNotFound' }, 400) if (request.method() === 'POST') return reply({ uri: created }) return reply({}) }) await page.goto(options.path ?? '/') await expect(page.locator('aside')).toHaveCount(1) await expect(page.getByRole('alert')).toHaveCount(0) if (populated) await expect(page.getByText('Seed event', { exact: true })).toBeVisible()}
test('Nuxt shell logs in, stays on month route, and has no demo calendars', async ({ page }) => { await setup(page) await expect(page).toHaveURL(/\/month\/2026-09-11$/) await expect(page.getByText('No calendar spaces found.')).toBeVisible() await expect(page.getByText(/demo|sample/i)).toHaveCount(0)})
test('keeps calendar visibility after refreshing the page', async ({ page }) => { await setup(page, { populated: true }) await page.getByRole('button', { name: 'Hide Seed calendar', exact: true }).click() await page.reload() await expect(page.getByRole('button', { name: 'Show Seed calendar', exact: true })).toBeVisible() await expect(page.getByText('Seed event', { exact: true })).toHaveCount(0) await page.getByRole('button', { name: 'Show Seed calendar', exact: true }).click() await page.reload() await expect(page.getByRole('button', { name: 'Hide Seed calendar', exact: true })).toBeVisible() await expect(page.getByText('Seed event', { exact: true })).toBeVisible()})
test('startup keeps network requests bounded', async ({ page }) => { const counts: Record<string, number> = {} const packages: Record<string, number> = {} page.on('request', request => { const path = new URL(request.url()).pathname const kind = path.includes('/xrpc/') ? 'api' : path.includes('/node_modules/') ? 'modules' : path.includes('/_nuxt/') ? 'nuxt' : 'other' counts[kind] = (counts[kind] || 0) + 1 if (kind === 'modules') { const parts = path.split('/node_modules/').pop()!.split('/') const name = parts[0]!.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0]! packages[name] = (packages[name] || 0) + 1 } }) await setup(page, { populated: true }) await page.waitForLoadState('networkidle') console.log('Startup request counts:', counts) // Nuxt serves application source files separately in development. expect(Object.values(counts).reduce((sum, count) => sum + count, 0)).toBeLessThan(450) expect(packages['@touch4it/ical-timezones'] || 0).toBe(0) expect(packages['reka-ui'] || 0).toBeLessThan(10)})
test('loads timezone export data only when exporting', async ({ page }) => { const timezoneRequests: string[] = [] page.on('request', request => { if (request.url().includes('/ical-timezones/')) timezoneRequests.push(request.url()) }) await setup(page, { populated: true }) expect(timezoneRequests).toHaveLength(0) await page.getByRole('button', { name: 'Calendar actions' }).click() const downloaded = page.waitForEvent('download') await page.getByRole('menuitem', { name: 'Export calendar', exact: true }).click() const download = await downloaded const contents = await readFile((await download.path())!, 'utf8') expect(contents).toContain('BEGIN:VCALENDAR') expect(contents).toContain('SUMMARY:Seed event') expect(contents).toContain('END:VCALENDAR')})
test('Nuxt shell resizes and hides the desktop sidebar without leaving a rail', async ({ page }) => { await setup(page, { populated: true }) const sidebar = page.locator('aside:visible') await expect(sidebar).toHaveCSS('width', '232px') const separator = page.getByRole('separator', { name: 'Resize sidebar' }) await separator.focus() await page.keyboard.press('Home') await expect(sidebar).toHaveCSS('width', '200px') const bounds = await separator.boundingBox() await page.mouse.move(bounds!.x + 1, bounds!.y + 100) await page.mouse.down() await page.mouse.move(300, bounds!.y + 100) await page.mouse.up() await expect(sidebar).toHaveCSS('width', '300px') await page.getByRole('button', { name: 'Close calendar navigation' }).click() await expect(sidebar).toBeHidden() const menu = page.getByRole('button', { name: 'Open menu' }) await expect(menu).toBeVisible() await expect(page.getByLabel('Calendar for new events')).toHaveCount(0) await expect(page.getByLabel(/^Today:/)).toHaveCount(0) const menuBox = (await menu.boundingBox())! const heading = (await page.locator('h1').boundingBox())! expect(menuBox.x).toBeLessThan(30) expect(menuBox.x + menuBox.width).toBeLessThan(heading.x) await page.screenshot({ path: '/tmp/calendat-sidebar-hidden.png' }) await menu.click() await expect(sidebar).toHaveCSS('width', '300px') await page.screenshot({ path: '/tmp/calendat-desktop.png', fullPage: true })})
test('Nuxt OAuth callback completes and redirects to the month view', async ({ page }) => { await setup(page, { path: '/oauth/callback?code=test&state=test' }) await expect(page).toHaveURL(/\/month\/2026-09-11$/)})
test('Nuxt shell creates a calendar from the sidebar', async ({ page }) => { await setup(page) await page.getByRole('button', { name: 'Create new calendar space' }).click() await page.getByLabel('Calendar Name *').fill('Nuxt Calendar') await page.getByRole('button', { name: 'Create Space' }).click() await expect(page.getByLabel('Toggle Nuxt Calendar')).toBeVisible()})
test('search palette shows real calendar colors and Agenda navigation', async ({ page }) => { await setup(page, { populated: true }) await page.getByRole('button', { name: 'Search events' }).click() const dialog = page.getByRole('dialog') await expect(dialog).toBeVisible() const calendarOption = dialog.getByRole('option', { name: /Seed calendar/ }) await expect(calendarOption).toBeVisible() await expect(calendarOption.locator('span[style*="background-color"]').first()).toHaveCSS('background-color', 'rgb(2, 132, 199)') const dot = calendarOption.locator('span[style*="background-color"]').first() await expect(dot).toHaveCSS('width', '8px') const dotBox = (await dot.boundingBox())! const slotBox = (await dot.locator('..').boundingBox())! expect(Math.abs(dotBox.x + dotBox.width / 2 - slotBox.x - slotBox.width / 2)).toBeLessThan(1) expect(Math.abs(dotBox.y + dotBox.height / 2 - slotBox.y - slotBox.height / 2)).toBeLessThan(1) await page.screenshot({ path: '/tmp/calendat-palette.png' }) await expect(dialog.getByText('Agenda', { exact: true })).toBeVisible() await dialog.getByText('Agenda', { exact: true }).click() await expect(page).toHaveURL(/\/agenda\/2026-09-11$/)})
test('theme selection persists across reloads', async ({ page }) => { await setup(page, { populated: true }) await page.getByRole('button', { name: 'Calendar actions' }).click() await page.getByRole('menuitem', { name: 'Theme', exact: true }).hover() await page.getByRole('menuitemcheckbox', { name: 'Dark', exact: true }).click() await expect(page.locator('html')).toHaveClass(/dark/) await page.reload() await expect(page.locator('html')).toHaveClass(/dark/) await page.getByRole('button', { name: 'Calendar actions' }).click() await page.getByRole('menuitem', { name: 'Theme', exact: true }).hover() await page.getByRole('menuitemcheckbox', { name: 'Light', exact: true }).click() await expect(page.locator('html')).not.toHaveClass(/dark/)})
for (const width of [320, 375, 414, 768]) { test(`Nuxt shell has no horizontal overflow at ${width}px`, async ({ page }) => { await page.setViewportSize({ width, height: 844 }) await setup(page, { populated: true }) await expect(page.locator('h1')).toHaveCount(1) if (width === 375) await page.screenshot({ path: '/tmp/calendat-mobile-375.png', fullPage: true }) expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)).toBe(true) await page.screenshot({ path: `test-results/nuxt-shell-${width}.png`, fullPage: true }) })}