feat: auto-update, тесты и CI/CD
Полная автоматизация релизного цикла. == Auto-update (electron-updater) == - src/main/updater.ts — обёртка над autoUpdater с дискриминированным UpdaterStatus union и broadcast через IPC. autoDownload=false, пользователь сам жмёт «Скачать». allowDowngrade=false. Проверка каждые 6 часов, первая через 5с после старта. - В dev-режиме (app.isPackaged=false) статус сразу становится 'unsupported' с пояснением — никаких exceptions из updater'а. - build.publish в package.json: provider=generic, url указывает на Gitea release assets конкретной версии. - src/main/ipc.ts: 4 новых канала — status/check/download/install. - src/preload: API window.api.updater* + onUpdaterStatus. - src/renderer/src/components/UpdaterCard.tsx: HUD-карточка в Settings с состояниями idle/checking/available/downloading/downloaded/error, прогресс-бар с скоростью в МБ/с. == Тесты (vitest) == - vitest.config.ts с алиасами @shared / @renderer - 23 теста, все зелёные: * format.test.ts — formatCountdown, formatInterval (8 cases) * vdf.test.ts — parseVdf / stringifyVdf / round-trip (11 cases) * types.test.ts — DEFAULT_SETTINGS, SAMPLE_EXERCISES sanity (4) - npm scripts: test (watch), test:run (CI) == CI/CD (Gitea Actions) == - .gitea/workflows/ci.yml — на push/PR: typecheck + тесты + smoke-сборка - .gitea/workflows/release.yml — на тег v*.*.*: сборка NSIS + Gitea release == Локальный релизный скрипт == - scripts/release.ps1 — один скрипт от бампа версии до публикации через Gitea API (params: -Bump patch/minor/major, -Version, -DryRun) - npm run release — обёртка - RELEASING.md — полная инструкция Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
43
src/shared/types.test.ts
Normal file
43
src/shared/types.test.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
DEFAULT_SETTINGS,
|
||||
GAME_STATS,
|
||||
SAMPLE_EXERCISES,
|
||||
STAT_LABELS,
|
||||
type GameStat
|
||||
} from './types'
|
||||
|
||||
describe('DEFAULT_SETTINGS', () => {
|
||||
it('uses safe defaults that do not surprise the user', () => {
|
||||
expect(DEFAULT_SETTINGS.globalEnabled).toBe(true)
|
||||
expect(DEFAULT_SETTINGS.notificationMode).toBe('modal')
|
||||
expect(DEFAULT_SETTINGS.minimizeToTray).toBe(true)
|
||||
expect(DEFAULT_SETTINGS.startWithWindows).toBe(false) // never auto-enroll
|
||||
expect(DEFAULT_SETTINGS.snoozeMinutes).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('SAMPLE_EXERCISES', () => {
|
||||
it('ships at least one enabled sample so the app is not empty on first launch', () => {
|
||||
expect(SAMPLE_EXERCISES.length).toBeGreaterThan(0)
|
||||
expect(SAMPLE_EXERCISES.some((e) => e.enabled)).toBe(true)
|
||||
})
|
||||
|
||||
it('all samples have positive reps and intervals', () => {
|
||||
for (const ex of SAMPLE_EXERCISES) {
|
||||
expect(ex.reps, `reps for ${ex.name}`).toBeGreaterThan(0)
|
||||
expect(ex.intervalMinutes, `interval for ${ex.name}`).toBeGreaterThan(0)
|
||||
expect(ex.icon.length, `icon set for ${ex.name}`).toBeGreaterThan(0)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('STAT_LABELS', () => {
|
||||
it('has a Russian label for every GameStat in every GAME_STATS bundle', () => {
|
||||
for (const stats of Object.values(GAME_STATS)) {
|
||||
for (const stat of stats as readonly GameStat[]) {
|
||||
expect(STAT_LABELS[stat], `label for ${stat}`).toBeTruthy()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user