Compare commits

...

3 Commits

Author SHA1 Message Date
LyAhn 7020a6b6cf style(whats-new): reformat WhatsNewModal via pnpm format
github/actions/ci GitHub Actions CI finished: success
2026-07-05 15:02:27 +01:00
LyAhn bf38fac30d test(e2e): wire Playwright to the UI Lab with scenario smoke tests 2026-07-05 15:02:26 +01:00
LyAhn 79e2e28979 refactor(dev): move DemoPanel into src/dev
DemoPanel is dev-only tooling like the rest of the UI Lab mock code, so it
lives with it now instead of among the real UI components. Import path in
App.tsx updated; the DEV gating (and production tree-shaking) is unchanged.
2026-07-05 13:38:52 +01:00
8 changed files with 108 additions and 38 deletions
+1 -1
View File
@@ -22,7 +22,7 @@ http://127.0.0.1:1422
The script runs Vite in the custom `ui` mode:
```bash
vite --mode ui --host 127.0.0.1 --port 1422 --strictPort --open
vite --mode ui --host 127.0.0.1 --port 1422 --strictPort
```
The normal app development commands are unchanged:
+3 -2
View File
@@ -13,7 +13,7 @@
"clean:app": "cd src-tauri && cargo clean",
"dev:app": "tauri dev",
"dev:app:cpu": "tauri dev -- --no-default-features",
"dev:ui": "vite --mode ui --host 127.0.0.1 --port 1422 --strictPort --open",
"dev:ui": "vite --mode ui --host 127.0.0.1 --port 1422 --strictPort",
"dev:vite": "vite",
"dev:web": "cd website && pnpm dev",
"format": "prettier --write .",
@@ -22,7 +22,8 @@
"format:rust": "cd src-tauri && cargo fmt",
"format:rust:check": "cd src-tauri && cargo fmt --check",
"preview": "vite preview",
"tauri": "tauri"
"tauri": "tauri",
"test:e2e": "playwright test"
},
"dependencies": {
"@tanstack/react-virtual": "^3.13.23",
+9 -13
View File
@@ -20,13 +20,13 @@ export default defineConfig({
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
workers: process.env.CI ? 1 : 4,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('')`. */
// baseURL: 'http://localhost:3000',
baseURL: 'http://127.0.0.1:1422',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
@@ -39,11 +39,6 @@ export default defineConfig({
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
@@ -70,10 +65,11 @@ export default defineConfig({
// },
],
/* Run your local dev server before starting the tests */
// webServer: {
// command: 'npm run start',
// url: 'http://localhost:3000',
// reuseExistingServer: !process.env.CI,
// },
/* Run the Phokus UI Lab (browser-only dev mode, see docs/ui-lab.md) before starting the tests */
webServer: {
command: 'pnpm exec vite --mode ui --host 127.0.0.1 --port 1422 --strictPort',
url: 'http://127.0.0.1:1422',
reuseExistingServer: !process.env.CI,
timeout: 120_000,
},
})
+1 -1
View File
@@ -15,7 +15,7 @@ import { UpdateToast } from './components/UpdateToast'
import { WhatsNewToast } from './components/WhatsNewToast'
import { WhatsNewModal } from './components/WhatsNewModal'
import { OnboardingOverlay } from './components/onboarding/OnboardingOverlay'
import { DemoPanel } from './components/DemoPanel'
import { DemoPanel } from './dev/DemoPanel'
import { initializeNotifications } from './notifications'
export default function App() {
+1 -3
View File
@@ -188,9 +188,7 @@ function RailSections({ sections }: { sections: ChangelogSection[] }) {
})}
</nav>
<div className="min-h-0 flex-1 overflow-y-auto px-6 py-5">
<p
className={`mb-3 text-[11px] font-semibold tracking-[0.1em] uppercase ${style.label}`}
>
<p className={`mb-3 text-[11px] font-semibold tracking-[0.1em] uppercase ${style.label}`}>
{section.title}
</p>
<ItemList items={section.items} dot={style.dot} />
-18
View File
@@ -1,18 +0,0 @@
import { test, expect } from '@playwright/test'
test('has title', async ({ page }) => {
await page.goto('https://playwright.dev/')
// Expect a title "to contain" a substring.
await expect(page).toHaveTitle(/Playwright/)
})
test('get started link', async ({ page }) => {
await page.goto('https://playwright.dev/')
// Click the get started link.
await page.getByRole('link', { name: 'Get started' }).click()
// Expects page to have a heading with the name of Installation.
await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible()
})
+93
View File
@@ -0,0 +1,93 @@
import { test, expect, type Page } from '@playwright/test'
// Smoke tests against the Phokus UI Lab (docs/ui-lab.md) — a browser-only dev
// mode that runs the real App.tsx/store/components against seeded, in-memory
// mock fixtures selected via the `?scenario=` query param.
const browserErrors = new WeakMap<Page, string[]>()
test.beforeEach(async ({ page }) => {
const errors: string[] = []
browserErrors.set(page, errors)
page.on('console', (message) => {
if (message.type() === 'error') errors.push(message.text())
})
page.on('pageerror', (error) => errors.push(error.message))
})
test.afterEach(async ({ page }) => {
expect(browserErrors.get(page) ?? []).toEqual([])
})
test.describe('gallery scenarios', () => {
test('rich scenario renders the gallery grid with folders in the sidebar', async ({ page }) => {
await page.goto('/?scenario=rich')
await expect(page.getByText('Camera Roll', { exact: true })).toBeVisible()
await expect(page.getByText('Client Selects', { exact: true })).toBeVisible()
await expect(page.getByText('Video Archive', { exact: true })).toBeVisible()
await expect(page.getByRole('button', { name: /^Open / }).first()).toBeVisible()
})
test('filename search filters the visible gallery results', async ({ page }) => {
await page.goto('/?scenario=rich')
const search = page.getByPlaceholder('Search files, or use /s /t')
await search.fill('coastal-walk-003')
await expect(page.getByText('Filename')).toBeVisible()
await expect(page.getByRole('button', { name: 'Open coastal-walk-003.jpg' })).toBeVisible()
await expect(page.getByRole('button', { name: /^Open / })).toHaveCount(1)
})
test('empty scenario shows the empty library without the onboarding tour', async ({ page }) => {
await page.goto('/?scenario=empty')
await expect(page.getByText('No media found')).toBeVisible()
await expect(page.getByRole('heading', { name: 'Welcome' })).not.toBeVisible()
})
test('new-user scenario opens the onboarding tour on step 1', async ({ page }) => {
await page.goto('/?scenario=new-user')
await expect(page.getByRole('heading', { name: 'Welcome' })).toBeVisible()
await expect(page.getByText('Step 1 of 8')).toBeVisible()
})
test('duplicates fixture data shows cached groups in Duplicate Finder', async ({ page }) => {
await page.goto('/?scenario=duplicates')
await page.getByText('Duplicates', { exact: true }).click()
await expect(page.getByRole('heading', { name: 'Duplicate Finder' })).toBeVisible()
await expect(page.getByText(/\d+ groups? · .* reclaimable/)).toBeVisible()
await expect(page.getByRole('button', { name: 'Select all duplicates' })).toBeVisible()
})
})
test.describe("what's new", () => {
test('just-updated scenario shows the toast and opens the modal on click', async ({ page }) => {
await page.goto('/?scenario=just-updated')
const toastButton = page.getByRole('button', { name: "What's new" })
await expect(toastButton).toBeVisible()
await toastButton.click()
await expect(page.getByRole('heading', { name: /^Phokus v/ })).toBeVisible()
})
test('just-updated + unreleased changelog opens the modal with the section nav rail', async ({
page,
}) => {
await page.goto('/?scenario=just-updated&changelog=unreleased')
await page.getByRole('button', { name: "What's new" }).click()
await expect(page.getByRole('heading', { name: /^Phokus v/ })).toBeVisible()
const sectionRail = page.getByRole('navigation')
await expect(sectionRail.getByRole('button', { name: /Added/ })).toBeVisible()
await expect(sectionRail.getByRole('button', { name: /Changed/ })).toBeVisible()
})
})