mirror of
https://github.com/saltbo/zpan.git
synced 2026-09-17 16:39:27 +08:00
* feat(public): redesign profile and share pages * fix(openapi): regenerate Go client * test(share): cover README validation boundaries
234 lines
9.3 KiB
TypeScript
234 lines
9.3 KiB
TypeScript
import path from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
import { expect, type Page, test } from '@playwright/test'
|
|
import { signUpAndGoToFiles } from './helpers'
|
|
|
|
const fixturesDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), 'fixtures')
|
|
|
|
const folderShare = {
|
|
token: 'folder-share-e2e',
|
|
kind: 'landing',
|
|
status: 'active',
|
|
expiresAt: null,
|
|
downloadLimit: null,
|
|
matter: { name: 'Client Files', type: 'application/x-directory', size: 0, isFolder: true },
|
|
creatorName: 'E2E Admin',
|
|
creatorUsername: 'e2e-admin',
|
|
requiresPassword: false,
|
|
expired: false,
|
|
exhausted: false,
|
|
accessibleByUser: false,
|
|
downloads: 0,
|
|
views: 3,
|
|
rootRef: 'root-folder-ref',
|
|
}
|
|
|
|
const fileShare = {
|
|
token: 'file-share-e2e',
|
|
kind: 'landing',
|
|
status: 'active',
|
|
expiresAt: null,
|
|
downloadLimit: null,
|
|
matter: { name: 'Brief.txt', type: 'text/plain', size: 50, isFolder: false },
|
|
creatorName: 'E2E Admin',
|
|
creatorUsername: 'e2e-admin',
|
|
requiresPassword: false,
|
|
expired: false,
|
|
exhausted: false,
|
|
accessibleByUser: false,
|
|
downloads: 0,
|
|
views: 1,
|
|
rootRef: 'root-file-ref',
|
|
}
|
|
|
|
const officeShare = {
|
|
...fileShare,
|
|
token: 'office-share-e2e',
|
|
matter: {
|
|
name: 'Quarterly Report.docx',
|
|
type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
size: 4096,
|
|
isFolder: false,
|
|
},
|
|
rootRef: 'root-office-ref',
|
|
}
|
|
|
|
async function mockFolderShare(page: Page) {
|
|
await page.route(`**/api/shares/${folderShare.token}`, (route) => {
|
|
if (route.request().method() !== 'GET') return route.continue()
|
|
return route.fulfill({ json: folderShare })
|
|
})
|
|
|
|
await page.route(`**/api/shares/${folderShare.token}/objects?*`, async (route) => {
|
|
const url = new URL(route.request().url())
|
|
const parent = url.searchParams.get('parent') ?? ''
|
|
if (parent === 'Reports') {
|
|
return route.fulfill({
|
|
json: {
|
|
items: [{ ref: 'nested-notes-ref', name: 'notes.txt', type: 'text/plain', size: 50, isFolder: false }],
|
|
total: 1,
|
|
page: 1,
|
|
pageSize: 50,
|
|
breadcrumb: [
|
|
{ name: 'Client Files', path: '' },
|
|
{ name: 'Reports', path: 'Reports' },
|
|
],
|
|
},
|
|
})
|
|
}
|
|
|
|
return route.fulfill({
|
|
json: {
|
|
items: [
|
|
{ ref: 'reports-folder-ref', name: 'Reports', type: 'application/x-directory', size: 0, isFolder: true },
|
|
{ ref: 'overview-ref', name: 'overview.txt', type: 'text/plain', size: 50, isFolder: false },
|
|
],
|
|
total: 2,
|
|
page: 1,
|
|
pageSize: 50,
|
|
breadcrumb: [{ name: 'Client Files', path: '' }],
|
|
},
|
|
})
|
|
})
|
|
|
|
await page.route(`**/api/shares/${folderShare.token}/objects/*`, (route) =>
|
|
route.fulfill({ path: path.join(fixturesDir, 'sample.txt'), contentType: 'text/plain' }),
|
|
)
|
|
await page.route(`**/api/shares/${folderShare.token}/readme`, (route) =>
|
|
route.fulfill({ json: { content: '# Client files\n\nShared project documentation.' } }),
|
|
)
|
|
}
|
|
|
|
async function mockFileShare(page: Page) {
|
|
await page.route(`**/api/shares/${fileShare.token}`, (route) => {
|
|
if (route.request().method() !== 'GET') return route.continue()
|
|
return route.fulfill({ json: fileShare })
|
|
})
|
|
|
|
await page.route(`**/api/shares/${fileShare.token}/objects/${fileShare.rootRef}`, (route) =>
|
|
route.fulfill({ path: path.join(fixturesDir, 'sample.txt'), contentType: 'text/plain' }),
|
|
)
|
|
}
|
|
|
|
async function mockOfficeShare(page: Page) {
|
|
const resolvedDownloadUrl = 'https://files.example.test/Quarterly%20Report.docx'
|
|
|
|
await page.route(`**/api/shares/${officeShare.token}`, (route) => {
|
|
if (route.request().method() !== 'GET') return route.continue()
|
|
return route.fulfill({ json: officeShare })
|
|
})
|
|
|
|
await page.route(`**/api/shares/${officeShare.token}/objects/${officeShare.rootRef}*`, (route) => {
|
|
const url = new URL(route.request().url())
|
|
if (url.searchParams.get('downloadUrl') === '1') {
|
|
return route.fulfill({ json: { downloadUrl: resolvedDownloadUrl } })
|
|
}
|
|
return route.fulfill({ body: 'office-doc-placeholder', contentType: officeShare.matter.type })
|
|
})
|
|
}
|
|
|
|
async function expectNoHorizontalOverflow(page: Page) {
|
|
const hasHScroll = await page.evaluate(
|
|
() => document.documentElement.scrollWidth > document.documentElement.clientWidth,
|
|
)
|
|
expect(hasHScroll).toBe(false)
|
|
}
|
|
|
|
test.describe('Share access page shell', () => {
|
|
test('public folder share uses the standard page shell without promotional chrome @all', async ({ page }) => {
|
|
await mockFolderShare(page)
|
|
await page.goto(`/s/${folderShare.token}`)
|
|
|
|
await expect(page.locator('header')).toContainText('ZPan')
|
|
await expect(page.getByTestId('page-header')).toContainText('Client Files')
|
|
await expect(page.getByText('overview.txt')).toBeVisible()
|
|
await expect(page.getByText('Reports')).toBeVisible()
|
|
await expect(page.getByText('E2E Admin').first()).toBeVisible()
|
|
await expect(page.getByRole('link', { name: 'E2E Admin' }).first()).toHaveAttribute('href', '/u/e2e-admin')
|
|
await expect(page.getByRole('link', { name: /sign in/i })).toBeVisible()
|
|
await expect(page.getByRole('heading', { name: 'Client files', exact: true })).toBeVisible()
|
|
await expect(page.getByText('Shared project documentation.')).toBeVisible()
|
|
await expect(page.getByRole('heading', { name: 'Share information' })).toHaveCount(0)
|
|
|
|
await expect(page.getByText('Public access page')).toHaveCount(0)
|
|
await expect(page.getByText('Open workspace')).toHaveCount(0)
|
|
await expect(page.getByText('Shared Folder')).toHaveCount(0)
|
|
await expect(page.getByText(/Browse shared content/i)).toHaveCount(0)
|
|
await expect(page.getByText(/editing actions are disabled/i)).toHaveCount(0)
|
|
|
|
await expect(page.locator('footer')).toContainText('ZPan')
|
|
await expect(page.locator('footer')).toContainText(new Date().getFullYear().toString())
|
|
await expectNoHorizontalOverflow(page)
|
|
})
|
|
|
|
test('folder share keeps FileManager navigation integrated with the share header @desktop', async ({ page }) => {
|
|
await mockFolderShare(page)
|
|
await page.goto(`/s/${folderShare.token}`)
|
|
|
|
await page.getByRole('button', { name: 'Reports' }).dblclick()
|
|
|
|
await expect(page.getByTestId('page-header')).toContainText('Client Files')
|
|
await expect(page.getByTestId('page-header')).toContainText('Reports')
|
|
await expect(page.getByText('notes.txt')).toBeVisible()
|
|
await expect(page.getByText('overview.txt')).toHaveCount(0)
|
|
await expectNoHorizontalOverflow(page)
|
|
})
|
|
|
|
test('single-file share uses the same share surface and shows preview actions @all', async ({ page }) => {
|
|
await mockFileShare(page)
|
|
await page.goto(`/s/${fileShare.token}`)
|
|
|
|
await expect(page.getByRole('heading', { name: 'Brief.txt' })).toBeVisible()
|
|
await expect(page.getByRole('link', { name: 'E2E Admin' }).first()).toHaveAttribute('href', '/u/e2e-admin')
|
|
await expect(page.getByRole('link', { name: /download/i })).toBeVisible()
|
|
await expect(page.getByText('Hello, this is a plain text file')).toBeVisible({ timeout: 10000 })
|
|
|
|
await expect(page.getByText('Public access page')).toHaveCount(0)
|
|
await expect(page.getByText('Open workspace')).toHaveCount(0)
|
|
await expect(page.getByText(/Preview and download/i)).toHaveCount(0)
|
|
await expectNoHorizontalOverflow(page)
|
|
})
|
|
|
|
test('office file share embeds Microsoft Office Viewer with the existing download URL @desktop', async ({ page }) => {
|
|
await mockOfficeShare(page)
|
|
await page.goto(`/s/${officeShare.token}`)
|
|
|
|
await expect(page.getByRole('heading', { name: 'Quarterly Report.docx' })).toBeVisible()
|
|
const frame = page.frameLocator('iframe[title="Quarterly Report.docx"]')
|
|
await expect(frame.owner()).toBeVisible()
|
|
|
|
const src = await frame.owner().getAttribute('src')
|
|
expect(src).toContain('https://view.officeapps.live.com/op/embed.aspx?src=')
|
|
expect(decodeURIComponent(src ?? '')).toContain('https://files.example.test/Quarterly%20Report.docx')
|
|
await expect(page.getByText('Microsoft Office Viewer requires a public file URL')).toHaveCount(0)
|
|
await expectNoHorizontalOverflow(page)
|
|
})
|
|
|
|
test('signed-in visitor sees avatar dropdown in the topbar @desktop', async ({ page }) => {
|
|
await signUpAndGoToFiles(page)
|
|
await mockFolderShare(page)
|
|
await page.goto(`/s/${folderShare.token}`)
|
|
|
|
const accountMenu = page.locator('header').getByRole('button', { name: /e2e/i })
|
|
await expect(accountMenu).toBeVisible()
|
|
await accountMenu.click()
|
|
|
|
await expect(page.getByRole('menuitem', { name: /settings/i })).toBeVisible()
|
|
await expect(page.getByRole('menuitem', { name: /teams/i })).toHaveCount(0)
|
|
await expect(page.getByRole('menuitem', { name: /sign out/i })).toBeVisible()
|
|
await expect(page.locator('header').getByText('Public access page')).toHaveCount(0)
|
|
await expect(page.locator('header').getByText('Open workspace')).toHaveCount(0)
|
|
})
|
|
|
|
test('mobile share page keeps topbar, content, and footer within the viewport @mobile', async ({ page }) => {
|
|
await mockFolderShare(page)
|
|
await page.goto(`/s/${folderShare.token}`)
|
|
|
|
await expect(page.locator('header')).toBeVisible()
|
|
await expect(page.getByTestId('page-header')).toContainText('Client Files')
|
|
await expect(page.locator('footer')).toBeVisible()
|
|
await expect(page.getByText('overview.txt')).toBeVisible()
|
|
await expectNoHorizontalOverflow(page)
|
|
})
|
|
})
|