Skip to content

Commit 50aa64d

Browse files
author
ca.mathieu
committed
test(webapp): stats end-to-end coverage
Covers owner-vs-viewer stat visibility, live sidebar download counts on preview, the activity metric selector and chart tooltips, trending window chips and metric toggle, list sorts, localized rendering, and the Home self-scoped trending panel.
1 parent 1ab53c3 commit 50aa64d

7 files changed

Lines changed: 514 additions & 26 deletions

File tree

webapp/e2e/admin.spec.js

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ test.describe('Admin view', () => {
1515
await page.waitForLoadState('networkidle')
1616

1717
// Click Uploads sidebar nav button
18-
await page.getByRole('button', { name: 'Uploads', exact: true }).click()
18+
await page.locator('aside').getByRole('button', { name: 'Uploads', exact: true }).click()
1919
await page.waitForLoadState('networkidle')
2020

2121
// Should show upload list (may be empty with "No uploads" or have entries)
@@ -177,9 +177,16 @@ test.describe('Admin server statistics', () => {
177177
const statsPanel = page.locator('.glass-card').filter({ hasText: 'Server Statistics' })
178178
await expect(statsPanel).toBeVisible({ timeout: 5_000 })
179179

180-
for (const label of ['Users', 'Uploads', 'Anonymous Uploads', 'Files', 'Total Size', 'Anonymous Size']) {
180+
for (const label of ['Current Usage', 'Users', 'Uploads', 'Files', 'Total Size', 'Lifetime Users', 'Lifetime Uploads', 'Lifetime Files', 'Lifetime Size']) {
181181
await expect(statsPanel.getByText(label, { exact: true })).toBeVisible()
182182
}
183+
await expect(statsPanel.getByText(/^Lifetime Usage \(since /)).toBeVisible()
184+
185+
const activityPanel = page.locator('.glass-card').filter({ hasText: 'Activity' }).filter({ hasText: 'Lifetime' })
186+
await expect(activityPanel).toBeVisible()
187+
for (const label of ['Today', '7 days', '30 days', 'Lifetime']) {
188+
await expect(activityPanel.getByText(label, { exact: true })).toBeVisible()
189+
}
183190
})
184191

185192
test('stat values are not empty or NaN', async ({ authenticatedPage: page }) => {
@@ -189,16 +196,25 @@ test.describe('Admin server statistics', () => {
189196
const statsPanel = page.locator('.glass-card').filter({ hasText: 'Server Statistics' })
190197
await expect(statsPanel).toBeVisible({ timeout: 5_000 })
191198

192-
// Check that the bold stat values exist and aren't NaN
199+
// Check that the bold stat values exist and aren't NaN.
193200
const values = statsPanel.locator('.text-2xl.font-bold')
194201
const count = await values.count()
195-
expect(count).toBe(6)
202+
expect(count).toBeGreaterThanOrEqual(8)
196203

197204
for (let i = 0; i < count; i++) {
198205
const text = await values.nth(i).textContent()
199206
expect(text).toBeTruthy()
200207
expect(text).not.toBe('NaN')
201208
}
209+
210+
const activityPanel = page.locator('.glass-card').filter({ hasText: 'Activity' }).filter({ hasText: 'Lifetime' })
211+
const windowValues = activityPanel.locator('.text-2xl.font-bold')
212+
await expect(windowValues).toHaveCount(4)
213+
for (let i = 0; i < 4; i++) {
214+
const text = await windowValues.nth(i).textContent()
215+
expect(text).toBeTruthy()
216+
expect(text).not.toBe('NaN')
217+
}
202218
})
203219
})
204220

@@ -450,9 +466,8 @@ test.describe('Admin result counts', () => {
450466

451467
test('uploads view shows result count', async ({ authenticatedPage: page }) => {
452468
await page.goto('/#/admin')
453-
await page.getByRole('button', { name: 'Uploads', exact: true }).click()
469+
await page.locator('aside').getByRole('button', { name: 'Uploads', exact: true }).click()
454470
const mainContent = page.locator('main')
455471
await expect(mainContent.getByText(/Showing \d+ of \d+ uploads/)).toBeVisible({ timeout: 5_000 })
456472
})
457473
})
458-

webapp/e2e/badge-filters.spec.js

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ async function apiDeleteUpload(page, uploadId) {
5050
async function goToAdminUploads(page) {
5151
await page.goto('/#/admin')
5252
await page.waitForLoadState('networkidle')
53-
await page.getByRole('button', { name: 'Uploads', exact: true }).click()
53+
await page.locator('aside').getByRole('button', { name: 'Uploads', exact: true }).click()
5454
await page.waitForLoadState('networkidle')
5555
}
5656

@@ -328,6 +328,7 @@ test.describe('Sort controls', () => {
328328
// Sort and Order buttons should be visible
329329
await expect(main.getByRole('button', { name: 'Date' })).toBeVisible({ timeout: 5_000 })
330330
await expect(main.getByRole('button', { name: 'Size' })).toBeVisible()
331+
await expect(main.getByRole('button', { name: 'Downloads' })).toBeVisible()
331332
await expect(main.getByRole('button', { name: 'Desc' })).toBeVisible()
332333
await expect(main.getByRole('button', { name: 'Asc' })).toBeVisible()
333334

@@ -339,14 +340,27 @@ test.describe('Sort controls', () => {
339340
await page.waitForLoadState('networkidle')
340341
await expect(main.getByRole('button', { name: 'Size' })).toHaveClass(/text-accent-400/)
341342
expect(page.url()).toContain('sort=size')
343+
344+
// Click Downloads — should become active and persist in URL
345+
await main.getByRole('button', { name: 'Downloads' }).click()
346+
await page.waitForLoadState('networkidle')
347+
await expect(main.getByRole('button', { name: 'Downloads' })).toHaveClass(/text-accent-400/)
348+
expect(page.url()).toContain('sort=downloads')
342349
})
343350

344351
test('home: sort buttons are visible and functional', async ({ authenticatedPage: page }) => {
345352
await goToHomeUploads(page)
346353
const main = page.locator('main')
354+
const badTokenResponses = []
355+
page.on('response', response => {
356+
if (response.url().includes('/me/token') && response.status() >= 400) {
357+
badTokenResponses.push(response.status())
358+
}
359+
})
347360

348361
await expect(main.getByRole('button', { name: 'Date' })).toBeVisible({ timeout: 5_000 })
349362
await expect(main.getByRole('button', { name: 'Size' })).toBeVisible()
363+
await expect(main.getByRole('button', { name: 'Downloads' })).toBeVisible()
350364
await expect(main.getByRole('button', { name: 'Desc' })).toBeVisible()
351365
await expect(main.getByRole('button', { name: 'Asc' })).toBeVisible()
352366

@@ -356,9 +370,16 @@ test.describe('Sort controls', () => {
356370
await expect(main.getByRole('button', { name: 'Size' })).toHaveClass(/text-accent-400/)
357371
expect(page.url()).toContain('sort=size')
358372

373+
// Click Downloads
374+
await main.getByRole('button', { name: 'Downloads' }).click()
375+
await page.waitForLoadState('networkidle')
376+
await expect(main.getByRole('button', { name: 'Downloads' })).toHaveClass(/text-accent-400/)
377+
expect(page.url()).toContain('sort=downloads')
378+
359379
// Reload — sort should persist
360380
await page.reload({ waitUntil: 'networkidle' })
361-
await expect(main.getByRole('button', { name: 'Size' })).toHaveClass(/text-accent-400/, { timeout: 5_000 })
381+
await expect(main.getByRole('button', { name: 'Downloads' })).toHaveClass(/text-accent-400/, { timeout: 5_000 })
382+
expect(badTokenResponses).toEqual([])
362383
})
363384

364385
})
@@ -368,12 +389,12 @@ test.describe('Sort controls', () => {
368389
test.describe('Direct URL with combined filters', () => {
369390
test('admin: navigating to URL with sort + badge filters applies all state', async ({ authenticatedPage: page }) => {
370391
// Navigate directly with multiple params — simulates paste-in-new-tab
371-
await page.goto('/#/admin/uploads?sort=size&oneShot=true&removable=true')
392+
await page.goto('/#/admin/uploads?sort=downloads&oneShot=true&removable=true')
372393
await page.waitForLoadState('networkidle')
373394
const main = page.locator('main')
374395

375-
// Sort should be "Size"
376-
await expect(main.getByRole('button', { name: 'Size' })).toHaveClass(/text-accent-400/, { timeout: 5_000 })
396+
// Sort should be "Downloads"
397+
await expect(main.getByRole('button', { name: 'Downloads' })).toHaveClass(/text-accent-400/, { timeout: 5_000 })
377398
await expect(main.getByRole('button', { name: 'Date' })).not.toHaveClass(/text-accent-400/)
378399

379400
// Badge filters should be active
@@ -386,12 +407,19 @@ test.describe('Direct URL with combined filters', () => {
386407
})
387408

388409
test('home: navigating to URL with sort + badge filters applies all state', async ({ authenticatedPage: page }) => {
389-
await page.goto('/#/home/uploads?sort=size&stream=true&password=true')
410+
const badTokenResponses = []
411+
page.on('response', response => {
412+
if (response.url().includes('/me/token') && response.status() >= 400) {
413+
badTokenResponses.push(response.status())
414+
}
415+
})
416+
417+
await page.goto('/#/home/uploads?sort=downloads&stream=true&password=true')
390418
await page.waitForLoadState('networkidle')
391419
const main = page.locator('main')
392420

393-
// Sort should be "Size"
394-
await expect(main.getByRole('button', { name: 'Size' })).toHaveClass(/text-accent-400/, { timeout: 5_000 })
421+
// Sort should be "Downloads"
422+
await expect(main.getByRole('button', { name: 'Downloads' })).toHaveClass(/text-accent-400/, { timeout: 5_000 })
395423
await expect(main.getByRole('button', { name: 'Date' })).not.toHaveClass(/text-accent-400/)
396424

397425
// Badge filters should be active
@@ -401,5 +429,6 @@ test.describe('Direct URL with combined filters', () => {
401429
// Other filters should NOT be active
402430
await expect(main.getByRole('button', { name: 'one-shot' })).not.toHaveClass(/ring-1/)
403431
await expect(main.getByRole('button', { name: 'removable' })).not.toHaveClass(/ring-1/)
432+
expect(badTokenResponses).toEqual([])
404433
})
405434
})

webapp/e2e/error-display.spec.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ test.describe('Error display components', () => {
5252
await page.waitForLoadState('networkidle')
5353

5454
// Click Uploads tab to trigger the intercepted call
55-
await page.getByRole('button', { name: 'Uploads', exact: true }).click()
55+
await page.locator('aside').getByRole('button', { name: 'Uploads', exact: true }).click()
5656
await page.waitForLoadState('networkidle')
5757

5858
// ErrorBanner should be visible
@@ -89,7 +89,7 @@ test.describe('Error display components', () => {
8989
await page.waitForLoadState('networkidle')
9090

9191
// Click Uploads tab to trigger the intercepted call
92-
await page.getByRole('button', { name: 'Uploads', exact: true }).click()
92+
await page.locator('aside').getByRole('button', { name: 'Uploads', exact: true }).click()
9393
await page.waitForLoadState('networkidle')
9494

9595
// ErrorBanner should be visible

webapp/e2e/home.spec.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ test.describe('Home view', () => {
1818
await page.waitForLoadState('networkidle')
1919

2020
// Click Uploads sidebar nav button
21-
await page.getByRole('button', { name: 'Uploads', exact: true }).click()
21+
await page.locator('aside').getByRole('button', { name: 'Uploads', exact: true }).click()
2222
await page.waitForLoadState('networkidle')
2323

2424
// Should show at least one upload card with the file name
@@ -232,14 +232,16 @@ test.describe('User statistics panel', () => {
232232
await expect(statsPanel).toBeVisible({ timeout: 5_000 })
233233

234234
// Check labels
235-
await expect(statsPanel.getByText('Uploads', { exact: true })).toBeVisible()
236-
await expect(statsPanel.getByText('Files', { exact: true })).toBeVisible()
237-
await expect(statsPanel.getByText('Total Size', { exact: true })).toBeVisible()
235+
await expect(statsPanel.getByText('Current Usage', { exact: true })).toBeVisible()
236+
await expect(statsPanel.getByText(/^Lifetime Usage \(since /)).toBeVisible()
237+
await expect(statsPanel.getByText('Uploads', { exact: true })).toHaveCount(2)
238+
await expect(statsPanel.getByText('Files', { exact: true })).toHaveCount(2)
239+
await expect(statsPanel.getByText('Total Size', { exact: true })).toHaveCount(2)
238240

239241
// Check that stat values are present and not NaN
240242
const values = statsPanel.locator('.text-2xl.font-bold')
241243
const count = await values.count()
242-
expect(count).toBe(3)
244+
expect(count).toBeGreaterThanOrEqual(6)
243245

244246
for (let i = 0; i < count; i++) {
245247
const text = await values.nth(i).textContent()

webapp/e2e/navigation.spec.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ test.describe('Home view tab routing', () => {
5858
await page.waitForLoadState('networkidle')
5959

6060
// Click Uploads sidebar button
61-
await page.getByRole('button', { name: 'Uploads', exact: true }).click()
61+
await page.locator('aside').getByRole('button', { name: 'Uploads', exact: true }).click()
6262
await page.waitForURL(/home\/uploads/)
6363

6464
// Click Tokens sidebar button
@@ -75,7 +75,7 @@ test.describe('Home view tab routing', () => {
7575
await page.waitForLoadState('networkidle')
7676

7777
// Navigate: stats → uploads → tokens (creates history entries)
78-
await page.getByRole('button', { name: 'Uploads', exact: true }).click()
78+
await page.locator('aside').getByRole('button', { name: 'Uploads', exact: true }).click()
7979
await page.waitForURL(/home\/uploads/)
8080
await page.getByRole('button', { name: 'Tokens', exact: true }).click()
8181
await page.waitForURL(/home\/tokens/)
@@ -141,7 +141,7 @@ test.describe('Admin view tab routing', () => {
141141
await page.waitForURL(/admin\/users/)
142142

143143
// Click Uploads sidebar button
144-
await page.getByRole('button', { name: 'Uploads', exact: true }).click()
144+
await page.locator('aside').getByRole('button', { name: 'Uploads', exact: true }).click()
145145
await page.waitForURL(/admin\/uploads/)
146146

147147
// Click Stats sidebar button
@@ -167,7 +167,7 @@ test.describe('Admin view tab routing', () => {
167167
await page.waitForURL(/admin\/users/)
168168
await expect(page.getByPlaceholder(/Search users/)).toBeVisible({ timeout: 5_000 })
169169

170-
await page.getByRole('button', { name: 'Uploads', exact: true }).click()
170+
await page.locator('aside').getByRole('button', { name: 'Uploads', exact: true }).click()
171171
await page.waitForURL(/admin\/uploads/)
172172

173173
// Back: uploads → users
@@ -199,7 +199,7 @@ test.describe('Admin view tab routing', () => {
199199
.toHaveClass(/text-accent/, { timeout: 5_000 })
200200

201201
// Navigate to uploads tab
202-
await page.getByRole('button', { name: 'Uploads', exact: true }).click()
202+
await page.locator('aside').getByRole('button', { name: 'Uploads', exact: true }).click()
203203
await page.waitForURL(/admin\/uploads/)
204204

205205
// Back: should return to users with provider=local still in URL

0 commit comments

Comments
 (0)