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}`)) {