feat(tests): integrate Playwright and Vitest for testing, add CI pipeline, and update dependencies

- Added Playwright configuration for end-to-end testing.
- Integrated Vitest for unit testing with setup files.
- Updated package.json scripts for testing commands.
- Created CI pipeline for automated testing and builds.
- Added various test cases for components and pages.
- Updated .gitignore to exclude Playwright and coverage.
- Enhanced ModsList component with additional class for styling.
This commit is contained in:
taroj1205 2025-05-16 11:15:14 +12:00
parent 966da54a29
commit 133bbc20be
No known key found for this signature in database
GPG key ID: 0FCB6CFFE0981AB7
20 changed files with 2339 additions and 152 deletions

109
.github/workflows/ci-pipeline.yml vendored Normal file
View file

@ -0,0 +1,109 @@
name: CI Pipeline
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
setup:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Cache node_modules
id: cache-deps
uses: actions/cache@v4
with:
path: |
node_modules
~/.cache/ms-playwright
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
build-check:
runs-on: ubuntu-latest
needs: setup
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
- name: Restore node_modules from cache
uses: actions/cache@v4
with:
path: |
node_modules
~/.cache/ms-playwright
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
- name: Verify npm installation
run: npm --version
- name: Build project
run: npm run build
biome:
runs-on: ubuntu-latest
needs: setup
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
- name: Restore node_modules from cache
uses: actions/cache@v4
with:
path: |
node_modules
~/.cache/ms-playwright
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
- name: Run Biome check
run: npx biome check ./src
vitest:
runs-on: ubuntu-latest
needs: setup
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Restore node_modules from cache
uses: actions/cache@v4
with:
path: |
node_modules
~/.cache/ms-playwright
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
- name: Run Vitest tests
run: npx vitest run
playwright:
runs-on: ubuntu-latest
needs: setup
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Restore node_modules from cache
uses: actions/cache@v4
with:
path: |
node_modules
~/.cache/ms-playwright
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
- name: Run Playwright tests
run: npx playwright test
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 30

View file

@ -1,28 +0,0 @@
name: PR Build Check
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
upload:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
- name: Verify npm installation
run: npm --version
- name: Install dependencies
run: npm install --no-frozen-lockfile
- name: Run Biome check
run: npx biome check ./src
- name: Build project
run: npm run build

8
.gitignore vendored
View file

@ -22,3 +22,11 @@ npm-debug.log*
# jetbrains setting folder
.idea/
# Playwright
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
/coverage

1652
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -11,7 +11,9 @@
"astro": "astro",
"lint": "biome lint ./src",
"format": "biome format ./src",
"prepare": "husky"
"prepare": "husky",
"test": "npx vitest run",
"test:coverage": "npx vitest --coverage"
},
"dependencies": {
"@astrojs/check": "^0.9.4",
@ -40,11 +42,24 @@
},
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"@playwright/test": "^1.52.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/user-event": "^14.6.1",
"@types/jsdom": "^21.1.7",
"@types/node": "^22.15.18",
"@vitest/coverage-istanbul": "^3.1.3",
"@vitest/ui": "^3.1.3",
"husky": "^9.1.7",
"jsdom": "^26.1.0",
"lint-staged": "^15.2.7",
"text-encoding": "^0.7.0",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.1.3",
"wrangler": "^3.94.0"
},
"lint-staged": {
"src/**/*.{ts,tsx,astro,js,jsx}": ["biome check --write ./src"]
"src/**/*.{ts,tsx,astro,js,jsx}": [
"biome check --write ./src"
]
}
}

72
playwright.config.ts Normal file
View file

@ -0,0 +1,72 @@
import { defineConfig, devices } from '@playwright/test';
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// import dotenv from 'dotenv';
// import path from 'path';
// dotenv.config({ path: path.resolve(__dirname, '.env') });
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './src/tests',
testIgnore: ['**.test.ts'],
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: 'http://127.0.0.1:3000',
trace: 'on-first-retry',
},
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],
/* Run your local dev server before starting the tests */
webServer: {
command: 'npm run dev',
url: 'http://127.0.0.1:3000',
reuseExistingServer: !process.env.CI,
},
});

View file

@ -196,7 +196,7 @@ export default function ModsList({ allMods, locale }: ModsListProps) {
{paginatedMods.length > 0 ? (
paginatedMods.map((mod) => (
<a
className="flex w-full flex-col gap-4 border-transparent transition-colors duration-100 hover:opacity-90"
className="mod-card flex w-full flex-col gap-4 border-transparent transition-colors duration-100 hover:opacity-90"
href={`/mods/${mod.id}`}
key={mod.id}
>

View file

@ -1,105 +0,0 @@
<script>
// Handle platform selection
const platformButtons = document.querySelectorAll('.platform-selector')
const platformSections = document.querySelectorAll('.platform-section')
// Function to detect OS and select appropriate platform
function detectOS() {
const userAgent = window.navigator.userAgent
let detectedOS = 'mac' // Default to macOS
if (userAgent.indexOf('Windows') !== -1) {
detectedOS = 'windows'
} else if (userAgent.indexOf('Linux') !== -1) {
detectedOS = 'linux'
}
return detectedOS
}
// Initialize platform based on user's OS
async function initializePlatform() {
const detectedOS = detectOS()
selectPlatform(detectedOS)
}
// Function to select a platform
async function selectPlatform(platform: string) {
// Update button styling
platformButtons.forEach((button) => {
const buttonPlatform = button.getAttribute('data-platform')
if (buttonPlatform === platform) {
button.setAttribute('data-active', 'true')
} else {
button.setAttribute('data-active', 'false')
}
})
// Show/hide platform sections
platformSections.forEach((section) => {
if (section.id === `${platform}-downloads`) {
section.setAttribute('data-active', 'true')
} else {
section.setAttribute('data-active', 'false')
}
})
}
// Handle platform button clicks
platformButtons.forEach((button) => {
button.addEventListener('click', () => {
const platform = button.getAttribute('data-platform') ?? ''
selectPlatform(platform)
})
})
// Check for twilight mode
async function checkTwilightMode() {
const urlParams = new URLSearchParams(window.location.search)
const isTwilight = urlParams.has('twilight')
if (isTwilight) {
const twilightInfoElem = document.getElementById('twilight-info')
twilightInfoElem?.setAttribute('data-twilight', 'true')
// Update UI to show twilight mode with animation
const titleElem = document.getElementById('download-title')
if (titleElem) {
const zenText = titleElem.innerHTML
titleElem.innerHTML = zenText.replace('Zen', 'Twilight')
}
const tags = document.querySelectorAll('.release-type-tag')
tags.forEach((tag) => {
tag.innerHTML = tag.innerHTML.replace('Beta', 'Twilight')
})
// Apply twilight mode to all relevant elements
const coralElements = document.querySelectorAll(
'.download-browser-logo, .release-type-tag, .decorative-gradient, .download-link, .download-arrow-icon, .download-card__icon, .checksum-icon-btn, .copy-btn',
)
coralElements.forEach((element) => {
element.setAttribute('data-twilight', 'true')
})
// Replace all download links with twilight versions
const downloadLinks = document.querySelectorAll('a.download-link')
downloadLinks.forEach((link) => {
if (!link.id.includes('beta')) {
const href = link.getAttribute('href')
if (href && href.includes('/latest/download/')) {
const twilightHref = href.replace(
'/latest/download/',
'/download/twilight/',
)
link.setAttribute('href', twilightHref)
}
}
})
}
}
// Initialize the page
initializePlatform()
checkTwilightMode()
</script>

View file

@ -1,6 +1,5 @@
---
import Description from '~/components/Description.astro'
import DownloadScript from '~/components/download/DownloadScript.astro'
import PlatformDownload from '~/components/download/PlatformDownload.astro'
import { getReleasesWithChecksums } from '~/components/download/release-data.astro'
import Layout from '~/layouts/Layout.astro'
@ -33,8 +32,6 @@ const platformNames = download.platformNames
const platformDescriptions = download.platformDescriptions
---
<DownloadScript />
<Layout title={layout.download.title} description={layout.download.description}>
<main class="flex min-h-screen flex-col px-6 data-[os='windows']:bg-zen-blue">
<div class="container relative mx-auto py-12">
@ -231,3 +228,109 @@ const platformDescriptions = download.platformDescriptions
width: 3rem;
}
</style>
<script>
// Handle platform selection
const platformButtons = document.querySelectorAll('.platform-selector')
const platformSections = document.querySelectorAll('.platform-section')
// Function to detect OS and select appropriate platform
function detectOS() {
const userAgent = window.navigator.userAgent
let detectedOS = 'mac' // Default to macOS
if (userAgent.indexOf('Windows') !== -1) {
detectedOS = 'windows'
} else if (userAgent.indexOf('Linux') !== -1) {
detectedOS = 'linux'
}
return detectedOS
}
// Initialize platform based on user's OS
async function initializePlatform() {
const detectedOS = detectOS()
selectPlatform(detectedOS)
}
// Function to select a platform
async function selectPlatform(platform: string) {
// Update button styling
platformButtons.forEach((button) => {
const buttonPlatform = button.getAttribute('data-platform')
if (buttonPlatform === platform) {
button.setAttribute('data-active', 'true')
} else {
button.setAttribute('data-active', 'false')
}
})
// Show/hide platform sections
platformSections.forEach((section) => {
if (section.id === `${platform}-downloads`) {
section.setAttribute('data-active', 'true')
} else {
section.setAttribute('data-active', 'false')
}
})
}
// Handle platform button clicks
platformButtons.forEach((button) => {
button.addEventListener('click', () => {
const platform = button.getAttribute('data-platform') ?? ''
selectPlatform(platform)
})
})
// Check for twilight mode
async function checkTwilightMode() {
const urlParams = new URLSearchParams(window.location.search)
const isTwilight = urlParams.has('twilight')
if (isTwilight) {
const twilightInfoElem = document.getElementById('twilight-info')
twilightInfoElem?.setAttribute('data-twilight', 'true')
// Update UI to show twilight mode with animation
const titleElem = document.getElementById('download-title')
if (titleElem) {
const zenText = titleElem.innerHTML
titleElem.innerHTML = zenText.replace('Zen', 'Twilight')
}
const tags = document.querySelectorAll('.release-type-tag')
tags.forEach((tag) => {
tag.innerHTML = tag.innerHTML.replace('Beta', 'Twilight')
})
// Apply twilight mode to all relevant elements
const coralElements = document.querySelectorAll(
'.download-browser-logo, .release-type-tag, .decorative-gradient, .download-link, .download-arrow-icon, .download-card__icon, .checksum-icon-btn, .copy-btn',
)
coralElements.forEach((element) => {
element.setAttribute('data-twilight', 'true')
})
// Replace all download links with twilight versions
const downloadLinks = document.querySelectorAll('a.download-link')
downloadLinks.forEach((link) => {
if (!link.id.includes('beta')) {
const href = link.getAttribute('href')
if (href && href.includes('/latest/download/')) {
const twilightHref = href.replace(
'/latest/download/',
'/download/twilight/',
)
link.setAttribute('href', twilightHref)
}
}
})
}
}
// Initialize the page
initializePlatform()
checkTwilightMode()
</script>

View file

@ -0,0 +1,101 @@
import { experimental_AstroContainer as AstroContainer } from 'astro/container'
import { beforeEach, describe, expect, it } from 'vitest'
import Button from '~/components/Button.astro'
describe('<Button />', () => {
let container: Awaited<ReturnType<typeof AstroContainer.create>>
beforeEach(async () => {
container = await AstroContainer.create()
})
describe('as <button>', () => {
it('renders default <button> with slot', async () => {
const result = await container.renderToString(Button, {
props: {},
slots: { default: 'Click me' },
})
expect(result).toContain('<button')
expect(result).toContain('Click me')
})
it.each([
['isPrimary', { isPrimary: true }, 'bg-dark'],
['isAlert', { isAlert: true }, 'bg-red-300'],
['isBordered', { isBordered: true }, 'border-2'],
])('applies %s style', async (_label, propObj, expectedClass) => {
const result = await container.renderToString(Button, {
props: { ...propObj },
slots: { default: 'Test' },
})
expect(result).toContain('<button')
expect(result).toContain(expectedClass)
})
it('applies id and extra props', async () => {
const result = await container.renderToString(Button, {
props: {
id: 'my-btn',
extra: { 'data-test': 'foo' },
},
slots: { default: 'Test' },
})
expect(result).toContain('id="my-btn"')
expect(result).toContain('data-test="foo"')
})
})
describe('as <a>', () => {
it('renders <a> with slot and href', async () => {
const result = await container.renderToString(Button, {
props: { href: '/link' },
slots: { default: 'Go' },
})
expect(result).toContain('<a')
expect(result).toContain('Go')
expect(result).toContain('href="/en/link"')
})
it.each([
['isPrimary', { isPrimary: true }, 'bg-dark'],
['isAlert', { isAlert: true }, 'bg-red-300'],
['isBordered', { isBordered: true }, 'border-2'],
])('applies %s style', async (_label, propObj, expectedClass) => {
const result = await container.renderToString(Button, {
props: { href: '/link', ...propObj },
slots: { default: 'Test' },
})
expect(result).toContain('<a')
expect(result).toContain(expectedClass)
})
it('applies id and extra props', async () => {
const result = await container.renderToString(Button, {
props: {
href: '/link',
id: 'my-link',
extra: { 'data-test': 'bar' },
},
slots: { default: 'Test' },
})
expect(result).toContain('id="my-link"')
expect(result).toContain('data-test="bar"')
})
})
it('applies custom className', async () => {
const result = await container.renderToString(Button, {
props: { class: 'custom-class' },
slots: { default: 'Test' },
})
expect(result).toContain('custom-class')
})
it('uses locale path for href', async () => {
const result = await container.renderToString(Button, {
props: { href: '/foo' },
slots: { default: 'Test' },
})
expect(result).toContain('href="/en/foo"')
})
})

View file

@ -0,0 +1,47 @@
import { experimental_AstroContainer as AstroContainer } from 'astro/container'
import { beforeEach, describe, expect, it } from 'vitest'
import ButtonCard from '~/components/download/ButtonCard.astro'
describe('<ButtonCard />', () => {
let container: Awaited<ReturnType<typeof AstroContainer.create>>
beforeEach(async () => {
container = await AstroContainer.create()
})
it('renders with required props', async () => {
const result = await container.renderToString(ButtonCard, {
props: {
label: 'Download',
href: '/download',
},
})
expect(result).toContain('Download')
expect(result).toContain('href="/download"')
expect(result).not.toContain('Show SHA-256')
})
it('renders with checksum', async () => {
const result = await container.renderToString(ButtonCard, {
props: {
label: 'Download',
href: '/download',
checksum: 'sha256sum',
},
})
expect(result).toContain('Show SHA-256')
expect(result).toContain('sha256sum')
expect(result).toContain('Copy')
})
it('renders with variant', async () => {
const result = await container.renderToString(ButtonCard, {
props: {
label: 'Download',
href: '/download',
variant: 'flathub',
},
})
expect(result).toContain('Download')
expect(result).toContain('Beta')
})
})

View file

@ -0,0 +1,88 @@
import { experimental_AstroContainer as AstroContainer } from 'astro/container'
import { beforeEach, describe, expect, it } from 'vitest'
import PlatformDownload from '~/components/download/PlatformDownload.astro'
const mockIcon = ['<svg></svg>']
const mockReleases = {
universal: { label: 'Universal', link: '/universal', checksum: 'abc123' },
x86_64: { label: 'x86_64', link: '/x86_64', checksum: 'def456' },
arm64: { label: 'ARM64', link: '/arm64', checksum: 'ghi789' },
flathub: { all: { label: 'Flathub', link: '/flathub' } },
}
describe('<PlatformDownload />', () => {
let container: Awaited<ReturnType<typeof AstroContainer.create>>
beforeEach(async () => {
container = await AstroContainer.create()
})
it('renders mac platform', async () => {
const result = await container.renderToString(PlatformDownload, {
props: {
platform: 'mac',
icon: mockIcon,
title: 'Mac Title',
description: 'Mac Desc',
releases: mockReleases,
},
})
expect(result).toContain('Mac Title')
expect(result).toContain('Mac Desc')
expect(result).toContain('Universal')
})
it('renders windows platform', async () => {
const result = await container.renderToString(PlatformDownload, {
props: {
platform: 'windows',
icon: mockIcon,
title: 'Win Title',
description: 'Win Desc',
releases: mockReleases,
},
})
expect(result).toContain('Win Title')
expect(result).toContain('Win Desc')
expect(result).toContain('x86_64')
expect(result).toContain('ARM64')
})
it('renders linux platform with flathub and tarball', async () => {
const linuxReleases = {
flathub: { all: { label: 'Flathub', link: '/flathub' } },
x86_64: { tarball: { label: 'Tarball x86_64', link: '/tarball-x86_64', checksum: 'sha256' } },
}
const result = await container.renderToString(PlatformDownload, {
props: {
platform: 'linux',
icon: mockIcon,
title: 'Linux Title',
description: 'Linux Desc',
releases: linuxReleases,
},
})
expect(result).toContain('Linux Title')
expect(result).toContain('Linux Desc')
expect(result).toContain('Flathub')
expect(result).toContain('Tarball')
expect(result).toContain('x86_64')
})
it('renders linux platform with all branches', async () => {
const linuxReleases = {
flathub: { all: { label: 'Flathub', link: '/flathub' } },
x86_64: { tarball: { label: 'Tarball x86_64', link: '/tarball-x86_64', checksum: 'sha256' } },
arm64: { label: 'ARM64', link: '/arm64', checksum: 'ghi789' },
}
const result = await container.renderToString(PlatformDownload, {
props: {
platform: 'linux',
icon: mockIcon,
title: 'Linux Title',
description: 'Linux Desc',
releases: linuxReleases,
},
})
expect(result).toContain('ARM64')
})
})

View file

@ -0,0 +1,26 @@
import { describe, expect, it } from 'vitest'
import { getReleasesWithChecksums } from '~/components/download/release-data.astro'
describe('getReleasesWithChecksums', () => {
it('returns correct structure with checksums', () => {
const checksums = {
'zen.macos-universal.dmg': 'macsum',
'zen.installer.exe': 'winsum',
'zen.installer-arm64.exe': 'winarmsum',
'zen.linux-x86_64.tar.xz': 'linx86sum',
'zen-x86_64.AppImage': 'linx86appsum',
'zen.linux-aarch64.tar.xz': 'linaarchsum',
'zen-aarch64.AppImage': 'linaarchappsum',
}
const releases = getReleasesWithChecksums(checksums)
expect(releases.macos.universal.checksum).toBe('macsum')
expect(releases.windows.x86_64.checksum).toBe('winsum')
expect(releases.windows.arm64.checksum).toBe('winarmsum')
expect(releases.linux.x86_64.tarball.checksum).toBe('linx86sum')
expect(releases.linux.x86_64.appImage.checksum).toBe('linx86appsum')
expect(releases.linux.aarch64.tarball.checksum).toBe('linaarchsum')
expect(releases.linux.aarch64.appImage.checksum).toBe('linaarchappsum')
expect(releases.linux.flathub.all.label).toBe('Flathub')
expect(releases.linux.flathub.all.link).toBe('https://flathub.org/apps/app.zen_browser.zen')
})
})

View file

@ -0,0 +1,67 @@
import { expect, test } from '@playwright/test'
import type { BrowserContextOptions, Page } from '@playwright/test'
// Helper to get the platform section by id
const getPlatformSection = (page: Page, platform: string) =>
page.locator(`#${platform}-downloads.platform-section[data-active='true']`)
// Helper to get the platform tab button
const getPlatformButton = (page: Page, platform: string) =>
page.locator(`button.platform-selector[data-platform='${platform}']`)
const platformConfigs: { name: string; userAgent: string; platform: string }[] = [
{
name: 'windows',
userAgent:
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
platform: 'Win32',
},
{
name: 'mac',
userAgent:
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Safari/605.1.15',
platform: 'MacIntel',
},
{
name: 'linux',
userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
platform: 'Linux x86_64',
},
]
for (const { name, userAgent, platform } of platformConfigs) {
test(`shows correct default tab for ${name} platform`, async ({ browser }) => {
const context = await browser.newContext({
userAgent,
locale: 'en-US',
platform,
} as BrowserContextOptions)
const page = await context.newPage()
await page.goto('/download')
await expect(getPlatformSection(page, name)).toBeVisible()
await expect(getPlatformButton(page, name)).toHaveAttribute('data-active', 'true')
// Other platforms should not be active
for (const other of platformConfigs.filter((p) => p.name !== name)) {
await expect(getPlatformSection(page, other.name)).toBeHidden()
await expect(getPlatformButton(page, other.name)).not.toHaveAttribute('data-active', 'true')
}
await context.close()
})
}
test.describe('Download page platform detection and tab switching', () => {
test('shows correct platform section and tab when switching platforms', async ({ page }) => {
await page.goto('/download')
const platforms = ['windows', 'mac', 'linux']
for (const platform of platforms) {
await getPlatformButton(page, platform).click()
await expect(getPlatformSection(page, platform)).toBeVisible()
await expect(getPlatformButton(page, platform)).toHaveAttribute('data-active', 'true')
// other platform sections should be hidden
for (const otherPlatform of platforms.filter((p) => p !== platform)) {
await expect(getPlatformSection(page, otherPlatform)).toBeHidden()
await expect(getPlatformButton(page, otherPlatform)).not.toHaveAttribute('data-active', 'true')
}
}
})
})

View file

@ -0,0 +1,11 @@
import { expect, test } from '@playwright/test'
test('clicking back button navigates to previous page', async ({ page }) => {
await page.goto('/mods?created=asc')
const currentUrl = page.url()
const modCards = await page.locator('.mod-card').all()
await modCards[0].click()
await page.getByRole('button', { name: 'Back' }).click()
await page.waitForURL(currentUrl)
expect(page.url()).toStrictEqual(currentUrl)
})

View file

@ -0,0 +1,9 @@
import { expect, test } from '@playwright/test'
test('all routes do not return 404', async ({ page }) => {
const routes = ['/', '/welcome', '/about', '/privacy-policy', '/download', '/donate', '/whatsnew']
for (const route of routes) {
const response = await page.goto(route)
expect(response?.status()).not.toBe(404)
}
})

View file

@ -0,0 +1,8 @@
import { vi } from 'vitest'
import translation from '~/i18n/en/translation.json'
vi.mock('~/utils/i18n', () => ({
getLocale: () => 'en',
getPath: () => (href: string) => `/en${href}`,
getUI: () => translation,
}))

View file

@ -3,6 +3,13 @@
* Returns a mapping from filename to checksum.
*/
export async function getChecksums() {
if (import.meta.env.DEV) {
return {
'zen.macos-universal.dmg': 'macsum',
'zen.installer.exe': 'winsum',
'zen.installer-arm64.exe': 'winarmsum',
}
}
const res = await fetch('https://api.github.com/repos/zen-browser/desktop/releases/latest', {
headers: {
Accept: 'application/vnd.github+json',

View file

@ -5,7 +5,12 @@
"jsxImportSource": "preact",
"baseUrl": ".",
"paths": {
"~/*": ["./src/*"]
}
"~/*": [
"./src/*"
]
},
"types": [
"@vitest/browser/providers/playwright"
]
}
}

12
vitest.config.ts Normal file
View file

@ -0,0 +1,12 @@
/// <reference types="vitest" />
import { getViteConfig } from 'astro/config';
export default getViteConfig({
test: {
setupFiles: ['./src/tests/vitest.setup.ts'],
coverage: {
provider: 'istanbul',
reporter: ['text', 'html'],
},
},
});