fix: resolve merge conflict

This commit is contained in:
taroj1205 2025-05-16 18:15:12 +12:00
commit 9f4cab7f7d
No known key found for this signature in database
GPG key ID: 0FCB6CFFE0981AB7
23 changed files with 2365 additions and 2387 deletions

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

@ -0,0 +1,111 @@
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
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
- name: Verify npm installation
run: npm --version
- name: Install dependencies
run: npm ci
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
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
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
- name: Run Vitest tests
run: npx vitest run
build:
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
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
- name: Build project
run: npm run build
- name: Upload build output
uses: actions/upload-artifact@v4
with:
name: build
path: |
dist
playwright:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Restore node_modules from cache
uses: actions/cache@v4
with:
path: |
node_modules
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Download build output
uses: actions/download-artifact@v4
with:
name: build
path: dist
- name: Run Playwright tests
run: npx playwright test

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

3915
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -11,7 +11,10 @@
"astro": "astro",
"lint": "biome lint ./src",
"format": "biome format ./src",
"prepare": "husky"
"prepare": "husky",
"test": "npx vitest run",
"test:coverage": "npx vitest --coverage",
"test:playwright": "npx playwright test"
},
"dependencies": {
"@astrojs/check": "^0.9.4",
@ -42,8 +45,17 @@
},
"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",
"husky": "^9.1.7",
"jsdom": "^26.1.0",
"lint-staged": "^15.2.7",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.1.3",
"wrangler": "^3.94.0"
},
"lint-staged": {

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://localhost: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: process.env.CI ? 'npm run start' : 'npm run dev',
url: 'http://localhost: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

@ -9,7 +9,14 @@ interface PlatformReleases {
universal?: ReleaseInfo
all?: ReleaseInfo
tarball?: ReleaseInfo
x86_64?: { tarball: ReleaseInfo } | ReleaseInfo
x86_64?:
| {
tarball?: ReleaseInfo
}
| ReleaseInfo
aarch64?: {
tarball?: ReleaseInfo
}
arm64?: ReleaseInfo
flathub?: { all: ReleaseInfo }
}
@ -27,11 +34,15 @@ import { Image } from 'astro:assets'
import AppIconDark from '../../assets/app-icon-dark.png'
import AppIconLight from '../../assets/app-icon-light.png'
import DownloadCard from './ButtonCard.astro'
function isFlatReleaseInfo(obj: unknown): obj is ReleaseInfo {
return !!obj && typeof obj === 'object' && 'link' in obj
}
---
<div
id={`${platform}-downloads`}
data-active={platform === 'mac'}
data-active={platform === "mac"}
class="platform-section data-[active='false']:hidden"
>
<div class="items-center gap-8 md:flex">
@ -45,9 +56,10 @@ import DownloadCard from './ButtonCard.astro'
<p class="text-muted-foreground mb-6" set:html={description} />
<div class="space-y-6">
{
platform === 'linux' ? (
platform === "linux" ? (
<>
{releases.flathub && releases.flathub.all.label && <div>
{releases.flathub && releases.flathub.all.label && (
<div>
<h4 class="mb-3 text-lg font-medium">Package Managers</h4>
<div class="space-y-3">
<DownloadCard
@ -56,28 +68,64 @@ import DownloadCard from './ButtonCard.astro'
variant="flathub"
/>
</div>
</div>}
{releases.x86_64 && 'tarball' in releases.x86_64 && <div>
<h4 class="mb-3 text-lg font-medium">Tarball</h4>
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2">
</div>
)}
{releases.x86_64 &&
typeof releases.x86_64 === "object" &&
"tarball" in releases.x86_64 &&
(releases.x86_64.tarball) && (
<div>
<h4 class="mb-3 text-lg font-medium">x86_64</h4>
<div class="">
{releases.x86_64.tarball && (
<DownloadCard
label="x86_64"
href={releases.x86_64.tarball.link}
label={
releases.x86_64.tarball.label
? releases.x86_64.tarball.label
: ""
}
href={
releases.x86_64.tarball.link
? releases.x86_64.tarball.link
: ""
}
variant="x86_64"
checksum={releases.x86_64.tarball.checksum}
/>
<DownloadCard
label="ARM64"
href={releases.x86_64.tarball.link}
variant="aarch64"
checksum={releases.x86_64.tarball.checksum}
/>
)}
</div>
</div>}
</div>
)}
{releases.aarch64 &&
typeof releases.aarch64 === "object" &&
"tarball" in releases.aarch64 &&
(releases.aarch64.tarball) && (
<div>
<h4 class="mb-3 text-lg font-medium">ARM64</h4>
<div class="gap-3">
{releases.aarch64.tarball && (
<DownloadCard
label={
releases.aarch64.tarball.label
? releases.aarch64.tarball.label
: ""
}
href={
releases.aarch64.tarball.link
? releases.aarch64.tarball.link
: ""
}
variant="aarch64"
checksum={releases.aarch64.tarball.checksum}
/>
)}
</div>
</div>
)}
</>
) : (
<div class="space-y-4">
<div class="space-y-3">
<div class="flex flex-col gap-4">
<div class="flex flex-col gap-3">
{releases.universal && releases.universal.label && (
<DownloadCard
label={releases.universal.label}
@ -85,22 +133,14 @@ import DownloadCard from './ButtonCard.astro'
checksum={releases.universal.checksum}
/>
)}
{releases.x86_64 && (
'tarball' in releases.x86_64
? releases.x86_64.tarball.label && (
<DownloadCard
label={releases.x86_64.tarball.label}
href={releases.x86_64.tarball.link}
checksum={releases.x86_64.tarball.checksum}
/>
)
: releases.x86_64.label && (
{releases.x86_64 &&
isFlatReleaseInfo(releases.x86_64) &&
releases.x86_64.label && (
<DownloadCard
label={releases.x86_64.label}
href={releases.x86_64.link}
checksum={releases.x86_64.checksum}
/>
)
)}
{releases.arm64 && releases.arm64.label && (
<DownloadCard

View file

@ -1,4 +1,3 @@
---
/**
* Returns the releases object, injecting checksums dynamically.
* @param checksums Record<string, string> mapping filenames to SHA-256 hashes
@ -28,26 +27,16 @@ export function getReleasesWithChecksums(checksums: Record<string, string>) {
x86_64: {
tarball: {
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen.linux-x86_64.tar.xz',
label: 'Tarball x86_64',
label: 'Tarball',
checksum: checksums['zen.linux-x86_64.tar.xz'],
},
appImage: {
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen-x86_64.AppImage',
label: 'AppImage x86_64',
checksum: checksums['zen-x86_64.AppImage'],
},
},
aarch64: {
tarball: {
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen.linux-aarch64.tar.xz',
label: 'Tarball aarch64',
label: 'Tarball',
checksum: checksums['zen.linux-aarch64.tar.xz'],
},
appImage: {
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen-aarch64.AppImage',
label: 'AppImage aarch64',
checksum: checksums['zen-aarch64.AppImage'],
},
},
flathub: {
all: {
@ -58,4 +47,3 @@ export function getReleasesWithChecksums(checksums: Record<string, string>) {
},
}
}
---

View file

@ -0,0 +1,7 @@
export const CHECKSUMS = {
'zen.macos-universal.dmg': 'macsum',
'zen.installer.exe': 'winsum',
'zen.installer-arm64.exe': 'winarmsum',
'zen.linux-x86_64.tar.xz': 'linuxsum',
'zen.linux-aarch64.tar.xz': 'linuxarmsum',
}

View file

@ -1,5 +1,7 @@
import { CHECKSUMS } from './checksum'
import { I18N } from './i18n'
export const CONSTANT = {
I18N,
CHECKSUMS,
}

View file

@ -2,7 +2,7 @@
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 { getReleasesWithChecksums } from '~/components/download/release-data'
import Layout from '~/layouts/Layout.astro'
import { getChecksums } from '~/utils/githubChecksums'
import { getLocale, getUI } from '~/utils/i18n'

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,111 @@
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' },
},
aarch64: {
tarball: { label: 'Tarball ARM64', link: '/tarball-arm64', checksum: 'sha256-arm64' },
},
}
const result = await container.renderToString(PlatformDownload, {
props: {
platform: 'linux',
icon: mockIcon,
title: 'Linux Title',
description: 'Linux Desc',
releases: linuxReleases,
},
})
// Test basic content
expect(result).toContain('Linux Title')
expect(result).toContain('Linux Desc')
// Test Flathub section
expect(result).toContain('Flathub')
expect(result).toContain('/flathub')
// Test x86_64 section
expect(result).toContain('x86_64')
expect(result).toContain('Tarball x86_64')
expect(result).toContain('/tarball-x86_64')
expect(result).toContain('sha256')
// Test ARM64 section
expect(result).toContain('ARM64')
expect(result).toContain('Tarball ARM64')
expect(result).toContain('/tarball-arm64')
expect(result).toContain('sha256-arm64')
})
})

View file

@ -0,0 +1,22 @@
import { describe, expect, it } from 'vitest'
import { getReleasesWithChecksums } from '~/components/download/release-data'
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.linux-aarch64.tar.xz': 'linaarchsum',
}
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.aarch64.tarball.checksum).toBe('linaarchsum')
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,102 @@
import { expect, test } from '@playwright/test'
import type { BrowserContextOptions, Page } from '@playwright/test'
import { getReleasesWithChecksums } from '~/components/download/release-data'
import { CONSTANT } from '~/constants'
// 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}']`)
// Helper to get the platform download link
const getPlatformDownloadLink = (page: Page, platform: string, label: string) =>
page.locator(`#${platform}-downloads .download-link:has-text('${label}')`)
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',
},
]
test.describe('Download page default tab per platform', () => {
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')
}
}
})
})
test.describe('Download page download links', () => {
const releases = getReleasesWithChecksums(CONSTANT.CHECKSUMS)
function getPlatformLinks(releases: ReturnType<typeof getReleasesWithChecksums>) {
return {
mac: [releases.macos.universal],
windows: [releases.windows.x86_64, releases.windows.arm64],
linux: [releases.linux.x86_64.tarball, releases.linux.aarch64.tarball, releases.linux.flathub.all],
}
}
test('all platform download links are correct', async ({ page }) => {
const platforms = ['windows', 'mac', 'linux']
const platformLinkSelectors = getPlatformLinks(releases)
await page.goto('/download')
await page.waitForLoadState('domcontentloaded')
for (const platform of platforms) {
await getPlatformButton(page, platform).click()
for (const { label, link } of platformLinkSelectors[platform as keyof typeof platformLinkSelectors]) {
const downloadLink = page.locator(`#${platform}-downloads .download-link[href="${link}"]`)
await expect(downloadLink).toContainText(label)
await expect(downloadLink).toHaveAttribute('href', link)
}
}
})
})

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

@ -1,8 +1,13 @@
import { CONSTANT } from '~/constants'
/**
* Fetches the latest release notes from GitHub and parses the SHA-256 checksums.
* Returns a mapping from filename to checksum.
*/
export async function getChecksums() {
if (import.meta.env.DEV) {
return CONSTANT.CHECKSUMS
}
const res = await fetch('https://api.github.com/repos/zen-browser/desktop/releases/latest', {
headers: {
Accept: 'application/vnd.github+json',

View file

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

14
vitest.config.ts Normal file
View file

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