mirror of
https://github.com/zen-browser/www.git
synced 2025-07-07 08:55:32 +02:00
fix: resolve merge conflict
This commit is contained in:
commit
9f4cab7f7d
23 changed files with 2365 additions and 2387 deletions
111
.github/workflows/ci-pipeline.yml
vendored
Normal file
111
.github/workflows/ci-pipeline.yml
vendored
Normal 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
|
28
.github/workflows/prbuildcheck.yml
vendored
28
.github/workflows/prbuildcheck.yml
vendored
|
@ -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
8
.gitignore
vendored
|
@ -22,3 +22,11 @@ npm-debug.log*
|
|||
|
||||
# jetbrains setting folder
|
||||
.idea/
|
||||
|
||||
# Playwright
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
|
||||
/coverage
|
3921
package-lock.json
generated
3921
package-lock.json
generated
File diff suppressed because it is too large
Load diff
14
package.json
14
package.json
|
@ -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
72
playwright.config.ts
Normal 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,
|
||||
},
|
||||
})
|
|
@ -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}
|
||||
>
|
||||
|
|
|
@ -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,39 +56,76 @@ 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>
|
||||
<h4 class="mb-3 text-lg font-medium">Package Managers</h4>
|
||||
<div class="space-y-3">
|
||||
<DownloadCard
|
||||
label={releases.flathub.all.label}
|
||||
href={releases.flathub.all.link}
|
||||
variant="flathub"
|
||||
/>
|
||||
{releases.flathub && releases.flathub.all.label && (
|
||||
<div>
|
||||
<h4 class="mb-3 text-lg font-medium">Package Managers</h4>
|
||||
<div class="space-y-3">
|
||||
<DownloadCard
|
||||
label={releases.flathub.all.label}
|
||||
href={releases.flathub.all.link}
|
||||
variant="flathub"
|
||||
/>
|
||||
</div>
|
||||
</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">
|
||||
<DownloadCard
|
||||
label="x86_64"
|
||||
href={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>}
|
||||
)}
|
||||
{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={
|
||||
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}
|
||||
/>
|
||||
)}
|
||||
</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,23 +133,15 @@ 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 && (
|
||||
<DownloadCard
|
||||
label={releases.x86_64.label}
|
||||
href={releases.x86_64.link}
|
||||
checksum={releases.x86_64.checksum}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
{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
|
||||
label={releases.arm64.label}
|
||||
|
|
|
@ -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>) {
|
|||
},
|
||||
}
|
||||
}
|
||||
---
|
7
src/constants/checksum.ts
Normal file
7
src/constants/checksum.ts
Normal 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',
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
import { CHECKSUMS } from './checksum'
|
||||
import { I18N } from './i18n'
|
||||
|
||||
export const CONSTANT = {
|
||||
I18N,
|
||||
CHECKSUMS,
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
|
101
src/tests/components/Button.test.ts
Normal file
101
src/tests/components/Button.test.ts
Normal 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"')
|
||||
})
|
||||
})
|
47
src/tests/components/ButtonCard.test.ts
Normal file
47
src/tests/components/ButtonCard.test.ts
Normal 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')
|
||||
})
|
||||
})
|
111
src/tests/components/PlatformDownload.test.ts
Normal file
111
src/tests/components/PlatformDownload.test.ts
Normal 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')
|
||||
})
|
||||
})
|
22
src/tests/components/release-data.test.ts
Normal file
22
src/tests/components/release-data.test.ts
Normal 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')
|
||||
})
|
||||
})
|
102
src/tests/pages/download.spec.ts
Normal file
102
src/tests/pages/download.spec.ts
Normal 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)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
11
src/tests/pages/mods.spec.ts
Normal file
11
src/tests/pages/mods.spec.ts
Normal 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)
|
||||
})
|
9
src/tests/pages/routes.spec.ts
Normal file
9
src/tests/pages/routes.spec.ts
Normal 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)
|
||||
}
|
||||
})
|
8
src/tests/vitest.setup.ts
Normal file
8
src/tests/vitest.setup.ts
Normal 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,
|
||||
}))
|
|
@ -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',
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"types": ["@vitest/browser/providers/playwright"]
|
||||
}
|
||||
}
|
||||
|
|
14
vitest.config.ts
Normal file
14
vitest.config.ts
Normal 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'],
|
||||
},
|
||||
},
|
||||
})
|
Loading…
Add table
Add a link
Reference in a new issue