diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 61bd535..d856a5d 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -23,6 +23,7 @@ jobs: - name: Checkout uses: actions/checkout@v4 with: + persist-credentials: false ref: ${{ github.event.workflow_run.head_sha || github.sha }} - name: Setup pnpm diff --git a/.github/workflows/skill-examples.yml b/.github/workflows/skill-examples.yml index 60ec46d..f689e2b 100644 --- a/.github/workflows/skill-examples.yml +++ b/.github/workflows/skill-examples.yml @@ -14,6 +14,8 @@ jobs: VITEPRESS_BASE: /datav-kit/ steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: pnpm/action-setup@v6 with: version: 11.6.0 diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index d5348fa..6721824 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -9,6 +9,7 @@ export default defineConfig({ title: 'DataV Kit', description: 'Framework-agnostic Web Components for data dashboard decoration.', base, + head: [['link', { rel: 'icon', href: 'data:,' }]], buildEnd: copySkillExamples, cleanUrls: true, vite: { diff --git a/playwright.config.ts b/playwright.config.ts index 2d57105..2571563 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -3,6 +3,7 @@ import { defineConfig } from '@playwright/test' export default defineConfig({ testDir: './tests/examples', + forbidOnly: !!process.env.CI, timeout: 90_000, expect: { timeout: 15_000 }, workers: 1, diff --git a/skills/datav-kit/assets/examples/business.html b/skills/datav-kit/assets/examples/business.html index 860780d..f9e8ab7 100644 --- a/skills/datav-kit/assets/examples/business.html +++ b/skills/datav-kit/assets/examples/business.html @@ -3,6 +3,7 @@ + + + + + DataV Kit · Minimal Screen diff --git a/tests/examples/screens.spec.ts b/tests/examples/screens.spec.ts index e8ced01..c1c0ccb 100644 --- a/tests/examples/screens.spec.ts +++ b/tests/examples/screens.spec.ts @@ -11,7 +11,6 @@ const directory = path.resolve('skills/datav-kit/assets/examples') const examples = (await readdir(directory)).filter(name => name.endsWith('.html')) const base = process.env.VITEPRESS_BASE || '/' const previewBase = `http://127.0.0.1:4173${base}examples/` -const chartLibrary = 'https://cdn.jsdelivr.net/npm/echarts@6.1.0/dist/echarts.esm.min.js' async function openExample(page: Page, url: string) { const errors: string[] = [] @@ -35,7 +34,8 @@ async function expectRegistered(page: Page) { .map(element => element.localName)) expect(missing).toEqual([]) const invalid = await page.evaluate(async () => { - const { elementMetadata } = await import('https://cdn.jsdelivr.net/npm/@datav-kit/elements@0.0.5/+esm') + const imports = JSON.parse(document.querySelector('script[type="importmap"]')!.textContent!).imports + const { elementMetadata } = await import(imports['@datav-kit/elements']) const result: string[] = [] for (const element of document.querySelectorAll('*')) { if (!element.localName.startsWith('dvk-')) @@ -56,8 +56,9 @@ async function expectRegistered(page: Page) { } async function expectCharts(page: Page) { - const chartResults = await page.evaluate(async (url) => { - const echarts = await import(/* @vite-ignore */ url) + const chartResults = await page.evaluate(async () => { + const imports = JSON.parse(document.querySelector('script[type="importmap"]')!.textContent!).imports + const echarts = await import(imports.echarts) return [...document.querySelectorAll('[data-chart]')].map((element) => { const chart = echarts.getInstanceByDom(element) const options = chart.getOption() @@ -68,7 +69,7 @@ async function expectCharts(page: Page) { .some(attribute => roles.includes((mark.getAttribute(attribute) || '').toLowerCase()))) return { id: element.id, width: chart.getWidth(), height: chart.getHeight(), series: options.series.length, coloredMarks: marks.length } }) - }, chartLibrary) + }) for (const result of chartResults) { expect(result.width, result.id).toBeGreaterThan(100) expect(result.height, result.id).toBeGreaterThan(80) @@ -173,10 +174,11 @@ for (const name of examples) { await page.screenshot({ path: path.join(directory, 'previews', name.replace('.html', '.png')) }) } } - const animation = await page.evaluate(async (url) => { - const echarts = await import(/* @vite-ignore */ url) + const animation = await page.evaluate(async () => { + const imports = JSON.parse(document.querySelector('script[type="importmap"]')!.textContent!).imports + const echarts = await import(imports.echarts) return [...document.querySelectorAll('[data-chart]')].map(element => echarts.getInstanceByDom(element).getOption().animation) - }, chartLibrary) + }) expect(animation.every(value => value === false)).toBe(true) if (name === 'industrial.html') { await expect(page.locator('#screen')).toHaveAttribute('data-paused', 'true') @@ -285,6 +287,10 @@ test('minimal starter registers and scales', async ({ page }) => { await expect(page.locator('#screen')).toHaveAttribute('data-ready', 'true') await expectRegistered(page) await expectCanvasFits(page) + await page.route('https://cdn.jsdelivr.net/**', route => route.abort()) + await page.reload() + await expect(page.locator('#screen')).toHaveAttribute('data-ready', 'error') + await expect(page.getByRole('status')).toContainText('依赖加载失败') }) test('gallery serves screenshots and downloads the maintained HTML', async ({ page }) => { diff --git a/tests/examples/serve.mjs b/tests/examples/serve.mjs index 271a758..dfbd533 100644 --- a/tests/examples/serve.mjs +++ b/tests/examples/serve.mjs @@ -1,16 +1,15 @@ import { readFile } from 'node:fs/promises' import http from 'node:http' import path from 'node:path' +import process from 'node:process' const root = path.resolve('docs/.vitepress/dist') +const base = (process.env.VITEPRESS_BASE || '/').replace(/\/$/, '') const types = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript', '.css': 'text/css', '.png': 'image/png', '.svg': 'image/svg+xml' } http.createServer(async (request, response) => { const url = new URL(request.url, 'http://localhost') - const pathname = decodeURIComponent(url.pathname).replace(/^\/datav-kit(?=\/)/, '') - if (pathname === '/favicon.ico') { - response.writeHead(204).end() - return - } + const decoded = decodeURIComponent(url.pathname) + const pathname = base && decoded.startsWith(`${base}/`) ? decoded.slice(base.length) : decoded const filename = pathname.endsWith('/') ? `${pathname}index.html` : path.extname(pathname) ? pathname : `${pathname}.html` const file = path.resolve(root, `.${filename}`) if (!file.startsWith(`${root}${path.sep}`)) {