mirror of
https://github.com/zen-browser/www.git
synced 2025-07-07 17:05:32 +02:00
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:
parent
966da54a29
commit
133bbc20be
20 changed files with 2339 additions and 152 deletions
109
.github/workflows/ci-pipeline.yml
vendored
Normal file
109
.github/workflows/ci-pipeline.yml
vendored
Normal 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
|
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
|
1652
package-lock.json
generated
1652
package-lock.json
generated
File diff suppressed because it is too large
Load diff
19
package.json
19
package.json
|
@ -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
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://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,
|
||||
},
|
||||
});
|
|
@ -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}
|
||||
>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
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')
|
||||
})
|
||||
})
|
88
src/tests/components/PlatformDownload.test.ts
Normal file
88
src/tests/components/PlatformDownload.test.ts
Normal 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')
|
||||
})
|
||||
})
|
26
src/tests/components/release-data.test.ts
Normal file
26
src/tests/components/release-data.test.ts
Normal 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')
|
||||
})
|
||||
})
|
67
src/tests/pages/download.spec.ts
Normal file
67
src/tests/pages/download.spec.ts
Normal 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')
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
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,
|
||||
}))
|
|
@ -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',
|
||||
|
|
|
@ -5,7 +5,12 @@
|
|||
"jsxImportSource": "preact",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": ["./src/*"]
|
||||
}
|
||||
"~/*": [
|
||||
"./src/*"
|
||||
]
|
||||
},
|
||||
"types": [
|
||||
"@vitest/browser/providers/playwright"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
12
vitest.config.ts
Normal file
12
vitest.config.ts
Normal 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'],
|
||||
},
|
||||
},
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue