From 46117e965e91a2ab58a532b24d8c7151751f6d79 Mon Sep 17 00:00:00 2001 From: Alisson Nunes Date: Sun, 20 Sep 2026 21:11:52 -0300 Subject: [PATCH 1/3] fix: stabilize production CI tests and save drafts before navigation --- .github/workflows/pages.yml | 11 +++++++++ playwright.config.ts | 8 ++++++- src/editor/controller.ts | 16 +++++++------ tests/controller.test.ts | 47 +++++++++++++++++++++++++++++++++++++ tests/e2e/editor.spec.ts | 19 --------------- 5 files changed, 74 insertions(+), 27 deletions(-) create mode 100644 tests/controller.test.ts diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 4554f25..2ed1817 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -2,6 +2,8 @@ name: GitHub Pages on: push: branches: [main] + pull_request: + branches: [main] workflow_dispatch: permissions: contents: read @@ -50,11 +52,20 @@ jobs: if curl --fail --silent "$TEST_URL" > /dev/null; then break; fi sleep 1 done + curl --fail --silent "$TEST_URL" > /dev/null npm run test:e2e + - name: Save browser failure evidence + if: failure() + uses: actions/upload-artifact@v4 + with: + name: browser-test-results + path: test-results/ + if-no-files-found: ignore - uses: actions/upload-pages-artifact@v3 with: path: dist deploy: + if: github.ref == 'refs/heads/main' needs: build runs-on: ubuntu-latest permissions: diff --git a/playwright.config.ts b/playwright.config.ts index 9f17ab3..20e828e 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -2,5 +2,11 @@ import { defineConfig } from '@playwright/test'; export default defineConfig({ testDir: './tests/e2e', timeout: 60000, - use: { baseURL: process.env.TEST_URL ?? 'http://127.0.0.1:4321/', viewport: { width: 1300, height: 850 }, launchOptions: process.platform === 'win32' ? { channel: 'msedge' } : {} }, + use: { + baseURL: process.env.TEST_URL ?? 'http://127.0.0.1:4321/', + viewport: { width: 1300, height: 850 }, + launchOptions: process.platform === 'win32' ? { channel: 'msedge' } : {}, + trace: 'retain-on-failure', + screenshot: 'only-on-failure', + }, }); diff --git a/src/editor/controller.ts b/src/editor/controller.ts index 7b2e249..3d93269 100644 --- a/src/editor/controller.ts +++ b/src/editor/controller.ts @@ -24,14 +24,16 @@ let saveTimer: ReturnType; let renderTimer: ReturnType; function status(message = '') { $('status').textContent = message; } function error(reason: unknown) { status(reason instanceof Error ? reason.message : 'Não foi possível concluir a operação.'); } +function saveDraft() { + clearTimeout(saveTimer); + try { + localStorage.setItem(storageKey, JSON.stringify({ xml: toXml(dance), figure, direction })); + $('saved').textContent = 'Salvo neste navegador'; + } catch { $('saved').textContent = 'Exporte para salvar'; } +} function persist() { clearTimeout(saveTimer); - saveTimer = setTimeout(() => { - try { - localStorage.setItem(storageKey, JSON.stringify({ xml: toXml(dance), figure, direction })); - $('saved').textContent = 'Salvo neste navegador'; - } catch { $('saved').textContent = 'Exporte para salvar'; } - }, 250); + saveTimer = setTimeout(saveDraft, 250); } function pause() { clock.playing = false; syncPlay(); } function syncPlay() { $('play').textContent = clock.playing ? 'Ⅱ' : '▶'; $('play').setAttribute('aria-label', clock.playing ? 'Pausar' : 'Reproduzir'); } @@ -168,7 +170,7 @@ requestAnimationFrame(tick); window.addEventListener('keydown', event => { if (event.code === 'Space' && (event.target === document.body || event.target === $('stage'))) { event.preventDefault(); $('play').click(); } }); -window.addEventListener('pagehide', () => { lookup?.abort(); preview?.destroy(); }); +window.addEventListener('pagehide', () => { saveDraft(); lookup?.abort(); preview?.destroy(); }); async function start() { let restored = false; try { diff --git a/tests/controller.test.ts b/tests/controller.test.ts new file mode 100644 index 0000000..9f0fa55 --- /dev/null +++ b/tests/controller.test.ts @@ -0,0 +1,47 @@ +import { afterEach, beforeEach, expect, it, vi } from 'vitest'; +import { readFileSync } from 'node:fs'; +import { newDance, type DanceDocument } from '../src/dance/document'; +import { parseDance, toXml } from '../src/dance/codec'; + +const state = vi.hoisted(() => ({ factory: vi.fn() })); +vi.mock('../src/preview/renderer', () => ({ createPreview: state.factory })); +const captureListeners = () => vi.spyOn(window, 'addEventListener'); +let listeners: ReturnType; +beforeEach(() => { + vi.resetModules(); vi.useFakeTimers(); + vi.stubGlobal('requestAnimationFrame', () => 0); + const source = readFileSync('src/pages/index.astro', 'utf8'); + document.body.innerHTML = source.match(/([\s\S]*)<\/body>/)![1].replace(/