mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-10 21:06:15 -08:00
refactor(docs): [SITE-SMOKE-01] verify example readiness and cleanup
This commit is contained in:
1 parent
d74ecc43cc
commit
1a5440a67f
25 files changed
+3510
-4770
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -2,7 +2,7 @@
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Mocha Tests</title>
|
||||
<title>Documentation Readiness Smoke</title>
|
||||
<link href="../assets/css/normalize.css" rel="stylesheet" />
|
||||
<link href="./mocha.css" rel="stylesheet" />
|
||||
<style>
|
||||
@@ -23,6 +23,7 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>These tests check example evaluation and player readiness. Interactive features require separate validation.</p>
|
||||
<div class="container">
|
||||
<div class="artplayer-app"></div>
|
||||
</div>
|
||||
@@ -48,4 +49,4 @@
|
||||
<script>mocha.run();</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
+595
-4606
File diff suppressed because it is too large.
Load diff
+10
-6
@@ -40,15 +40,15 @@
|
||||
"test:dash-control": "node --test test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js",
|
||||
"dev": "npx cross-env NODE_ENV=development node ./scripts/dev.js",
|
||||
"build": "npx cross-env NODE_ENV=production node ./scripts/build.js",
|
||||
"lint": "eslint \"packages/*/{src,public,types,package.json}\" \"scripts/*.{js,mjs}\" \"test/*\" \"docs/assets/ts/*\" \"types/*.d.ts\" \"playwright*.config.js\" --no-fix",
|
||||
"lint": "eslint \"packages/*/{src,public,types,package.json}\" \"scripts/*.{js,mjs}\" \"scripts/docs-smoke/**/*.ts\" \"test/*\" \"docs/assets/ts/*\" \"types/*.d.ts\" \"playwright*.config.js\" --no-fix",
|
||||
"build:all": "yarn ci:build && yarn lint",
|
||||
"check:toolchain": "node scripts/check-toolchain.mjs",
|
||||
"lint:fix": "eslint \"packages/*/{src,public,types,package.json}\" \"scripts/*.{js,mjs}\" \"test/*\" \"docs/assets/ts/*\" \"types/*.d.ts\" \"playwright*.config.js\" --fix",
|
||||
"lint:fix": "eslint \"packages/*/{src,public,types,package.json}\" \"scripts/*.{js,mjs}\" \"scripts/docs-smoke/**/*.ts\" \"test/*\" \"docs/assets/ts/*\" \"types/*.d.ts\" \"playwright*.config.js\" --fix",
|
||||
"check:plan": "node refactor/scripts/plan.mjs --check",
|
||||
"test:node": "yarn test:unit && node --test test/toolchain.test.js test/build-docs.test.js test/package-check.test.js test/declarations.test.js test/editor-types.test.js test/coverage.test.js test/performance-report.test.js test/media-gate.test.js test/ci-summary.test.js test/package-runtime.test.js test/library-build.test.js",
|
||||
"test:baseline": "node --test refactor/scripts/*.test.mjs",
|
||||
"ci:check": "yarn check:toolchain --strict && yarn check:commits --report && yarn check:impact --report && yarn check:ci && yarn test:contracts && yarn check:contracts --report && yarn check:plan && yarn lint && yarn check:types && yarn typecheck && yarn typecheck:react && yarn lint:react && yarn typecheck:vue && yarn lint:vue && yarn test",
|
||||
"ci:build": "yarn build:types && yarn build all && yarn build:i18n && yarn build:ts && yarn build:docs && yarn test:imports",
|
||||
"ci:check": "yarn check:toolchain --strict && yarn check:commits --report && yarn check:impact --report && yarn check:ci && yarn test:contracts && yarn check:contracts --report && yarn check:plan && yarn lint && yarn typecheck:docs-tools && yarn check:docs-smoke && yarn check:types && yarn typecheck && yarn typecheck:react && yarn lint:react && yarn typecheck:vue && yarn lint:vue && yarn test",
|
||||
"ci:build": "yarn build:types && yarn build all && yarn build:i18n && yarn build:ts && yarn build:test && yarn build:docs && yarn test:imports",
|
||||
"test:imports": "node --test test/esm.test.js test/i18n.test.js test/ssr.test.js test/asr-distribution.test.js",
|
||||
"typecheck": "node scripts/typecheck.mjs",
|
||||
"test:unit": "node --test test/danmuku-setting.test.js test/danmuku-heatmap.test.js test/danmuku-renderer.test.js test/danmuku-scheduler.test.js test/danmuku-worker-client.test.js test/danmuku-input.test.js test/danmuku-parser.test.js test/danmuku-failures.test.js test/danmuku-mask-failures.test.js test/danmuku-mask-lifecycle.test.js test/asr-encoding.test.js test/asr-lifecycle.test.js test/asr-audio.test.js test/asr.test.js test/jassub.test.js test/jassub-registration.test.js test/jassub-runtime.test.js test/jassub-vendor-lifecycle.test.js test/jassub-frame-polyfill.test.js test/jassub-offscreen.test.js test/multiple-subtitles-merge.test.js test/multiple-subtitles-lifecycle.test.js test/multiple-subtitles-failures.test.js test/multiple-subtitles.test.js test/multiple-subtitles-vendor.test.js test/vtt-thumbnail-parser.test.js test/vtt-thumbnail-lifecycle.test.js test/vtt-thumbnail.test.js test/auto-thumbnail-exports.test.js test/auto-thumbnail-canvas.test.js test/auto-thumbnail-frames.test.js test/auto-thumbnail-lifecycle.test.js test/auto-thumbnail.test.js test/thumbnail-emitter.test.js test/thumbnail-runtime.test.js test/thumbnail-vendor.test.js test/thumbnail-lifecycle.test.js test/thumbnail-input.test.js test/thumbnail.test.js test/iframe-navigation.test.js test/iframe-boundaries.test.js test/iframe-lifecycle.test.js test/iframe.test.js test/mediabunny.test.js test/mediabunny-shim.test.js test/mediabunny-coordination.test.js test/mediabunny-video.test.js test/mediabunny-audio.test.js test/mediabunny-hls.test.js test/mediabunny-entry.test.js test/mediabunny-capability.test.js test/mediabunny-load.test.js test/mediabunny-input.test.js test/dpip.test.js test/dpip-lifecycle.test.js test/canvas.test.js test/canvas-lifecycle.test.js test/ambilight.test.js test/ambilight-lifecycle.test.js test/ambilight-proxy.test.js test/vast.test.js test/vast-lifecycle.test.js test/ads.test.js test/ads-lifecycle.test.js test/playback.test.js test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js test/hls-control.test.js test/audio-track.test.js test/public-behavior.test.js test/helpers.test.js test/chapter.test.js test/utils.test.js test/resource-scope.test.js test/instance-lifecycle.test.js test/options.test.js test/media-hosts.test.js test/plugins.test.js test/source.test.js test/playback-properties.test.js test/media-events.test.js test/template-resources.test.js test/core-vendor.test.js test/component-resources.test.js test/setting-model.test.js test/setting-layout.test.js test/setting-resources.test.js test/subtitle.test.js test/display-native.test.js test/display-video-fullscreen.test.js test/display-pip.test.js test/display-mini.test.js test/display-sizing.test.js test/display-orientation.test.js test/hotkey.test.js test/listener-registry.test.js test/global-events.test.js test/pointer-events.test.js test/gesture.test.js test/event-scheduling.test.js test/notice.test.js test/fast-forward.test.js test/auto-playback.test.js test/builtin-layers.test.js test/prompt-components.test.js test/screenshot.test.js test/thumbnails.test.js test/progress.test.js test/environment.test.js test/storage.test.js test/facade-properties.test.js test/dom-boundaries.test.js test/initialization.test.js test/entry.test.js test/accessibility-button.test.js test/accessibility-focus.test.js test/accessibility-slider.test.js test/asr-fallback-routing.test.js test/asr-local-example.test.js test/asr-explicit-capture.test.js test/chromecast.test.js test/chromecast-failures.test.js test/chromecast-runtime.test.js",
|
||||
@@ -110,7 +110,7 @@
|
||||
"test:auto-thumbnail-types-package": "node refactor/scripts/auto-thumbnail-package-types.mjs",
|
||||
"probe:auto-thumbnail-rendering": "node refactor/scripts/auto-thumbnail-rendering-probe.mjs",
|
||||
"dev:react": "vite example/react.js --config example/react.js/vite.config.js",
|
||||
"typecheck:react": "tsc -p example/react.js/tsconfig.json --noEmit",
|
||||
"typecheck:react": "node node_modules/typescript/bin/tsc -p example/react.js/tsconfig.json --noEmit",
|
||||
"build:react": "yarn typecheck:react && vite build example/react.js --config example/react.js/vite.config.js --outDir ../../refactor/.cache/react-example-dist",
|
||||
"test:react-consumer": "node refactor/scripts/react-consumer.mjs",
|
||||
"lint:react": "eslint example/react.js --no-fix",
|
||||
@@ -118,12 +118,16 @@
|
||||
"typecheck:vue": "vue-tsc -p example/vue.js/tsconfig.json --noEmit",
|
||||
"build:vue": "yarn typecheck:vue && vite build example/vue.js --config example/vue.js/vite.config.js --outDir ../../refactor/.cache/vue-example-dist",
|
||||
"test:vue-consumer": "node refactor/scripts/vue-consumer.mjs",
|
||||
"lint:vue": "eslint example/vue.js --no-fix"
|
||||
"lint:vue": "eslint example/vue.js --no-fix",
|
||||
"typecheck:docs-tools": "node node_modules/typescript/bin/tsc -p scripts/tsconfig.docs.json --noEmit",
|
||||
"check:docs-smoke": "node scripts/build-test.js --check"
|
||||
},
|
||||
"browserslist": "last 1 Chrome version",
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "5.4.1",
|
||||
"@playwright/test": "1.63.0",
|
||||
"@types/markdown-it": "14.1.2",
|
||||
"@types/node": "24.10.0",
|
||||
"@types/react": "19.1.10",
|
||||
"@types/react-dom": "19.1.7",
|
||||
"@vitejs/plugin-react": "5.0.0",
|
||||
|
||||
@@ -43,8 +43,9 @@ The repository generators have different ownership:
|
||||
- `scripts/build-types.mjs`: core public declaration sources to existing `types/`.
|
||||
- `scripts/build-ts.js`: standalone editor declarations and the editor library list.
|
||||
- `scripts/build-test.js`: extracts Chinese Run Code blocks into `docs/test/test.js`.
|
||||
It is currently a smoke generator with a fixed 100 ms completion delay, not
|
||||
proof of asynchronous playback or plugin success. SITE-02 owns its replacement.
|
||||
The TS implementation in `scripts/docs-smoke/` produces deterministic readiness
|
||||
smoke cases with owned frames, error observation and cleanup. It does not prove
|
||||
complete playback or plugin behavior. See its maintenance README and SITE-SMOKE-01.
|
||||
- `scripts/build-i18n.js`: core language source bundles and copies to `docs/compiled/i18n/`.
|
||||
- `scripts/build-docs.js`: this VitePress build.
|
||||
- `scripts/build-llm.js` and `scripts/trans-docs.js`: explicit remote DeepSeek
|
||||
|
||||
@@ -0,0 +1,584 @@
|
||||
{
|
||||
"task": "SITE-SMOKE-01",
|
||||
"baseline": "d74ecc43ccee99dcd2e3090356a6fb761825e6af",
|
||||
"node": "24.21.0",
|
||||
"yarn": "1.22.22",
|
||||
"tools": {
|
||||
"primaryTypeScript": "5.9.3",
|
||||
"historicalSharedTscBin": "5.1.6",
|
||||
"addedRootDevDependencies": {
|
||||
"@types/node": "24.10.0",
|
||||
"@types/markdown-it": "14.1.2"
|
||||
},
|
||||
"productionTsFiles": 406,
|
||||
"workspaces": 22,
|
||||
"pinnedTools": 38,
|
||||
"dependencySelectors": 1470
|
||||
},
|
||||
"selectedExamples": {
|
||||
"examples": 233,
|
||||
"files": 11,
|
||||
"runtimeScripts": 11,
|
||||
"preservedCodeAfterLfNormalization": true,
|
||||
"fullBrowserExecution": false
|
||||
},
|
||||
"parserBefore": {
|
||||
"sourceCommit": "d74ecc43ccee99dcd2e3090356a6fb761825e6af",
|
||||
"reproduction": "Fixed old extractor VM ERR_SCRIPT_EXECUTION_TIMEOUT for malformed snippet; committed test verifies finite new parse and syntax rejection."
|
||||
},
|
||||
"validation": {
|
||||
"baseline": {
|
||||
"file": "refactor/.cache/docs-smoke-baseline.log",
|
||||
"sha256": "364e7e17a40399436e42eef2a9e6a895830fc9abc70f1067050a90df3a097fed",
|
||||
"exitCode": 0,
|
||||
"pass": 522
|
||||
},
|
||||
"targeted": {
|
||||
"file": "refactor/.cache/docs-smoke-tests.log",
|
||||
"sha256": "7170a7bb34967be812c4b20f57e35fa4bf1701e1b4ae5c5adb3c67a81003379f",
|
||||
"exitCode": 0,
|
||||
"pass": 5
|
||||
},
|
||||
"ci": {
|
||||
"file": "refactor/.cache/docs-smoke-ci-tests.log",
|
||||
"sha256": "18ef50db081f0106e31c322e2d836add7866c11f22d53ebf8770a1b4f91a707e",
|
||||
"exitCode": 0,
|
||||
"pass": 50
|
||||
},
|
||||
"productionTypes": {
|
||||
"file": "refactor/.cache/docs-smoke-production-types.log",
|
||||
"sha256": "a57e291b4168679e50533f35ab39b835c66a1dde22ad4b8a045d65db27e14e81",
|
||||
"exitCode": 0
|
||||
},
|
||||
"docsTypes": {
|
||||
"file": "refactor/.cache/docs-smoke-types-final.log",
|
||||
"sha256": "d082eef2f531b831e25fc38c50f2f0727b049b5f1c8c915c67b12fb8c42a80e7",
|
||||
"exitCode": 0
|
||||
},
|
||||
"reactTypes": {
|
||||
"file": "refactor/.cache/docs-smoke-react-types.log",
|
||||
"sha256": "076a3287d3c0eff3c331b25972a92239476928b31b83d84cb6fee512fc8d2ad5",
|
||||
"exitCode": 0
|
||||
},
|
||||
"frozen": {
|
||||
"file": "refactor/.cache/docs-smoke-frozen.log",
|
||||
"sha256": "002782fd8a6091b824463b26b2711b695f7154b6f3cd7b6c7ca4e86e775153e0",
|
||||
"exitCode": 0
|
||||
},
|
||||
"toolchain": {
|
||||
"file": "refactor/.cache/docs-smoke-toolchain.log",
|
||||
"sha256": "ff37a9bff5aeaac042303412efefbcbe0ffc01ae6c615b72e3be4b2bedc522a5",
|
||||
"exitCode": 0
|
||||
},
|
||||
"rootLint": {
|
||||
"file": "refactor/.cache/docs-smoke-root-lint.log",
|
||||
"sha256": "3cc99e4aad769bb1d791f64cfbe2691d46a65b67c1ae7de25ca09e7630033ee5",
|
||||
"exitCode": 0
|
||||
},
|
||||
"targetedLint": {
|
||||
"file": "refactor/.cache/docs-smoke-final-lint.log",
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"exitCode": 0
|
||||
},
|
||||
"readonlyCheck": {
|
||||
"file": "refactor/.cache/docs-smoke-check.log",
|
||||
"sha256": "c5f08a17633392d6e1680f92db95be60da5e7e3fb029adb69a25f0303a8558bb",
|
||||
"exitCode": 0
|
||||
},
|
||||
"compilerAliasBefore": {
|
||||
"file": "refactor/.cache/docs-smoke-tsc-alias-before.log",
|
||||
"sha256": "b0a1234a8a71fed3658891302f314f246f13c802423c4a16c1e2b802c9df8dea",
|
||||
"exitCode": 2
|
||||
},
|
||||
"browserBefore": {
|
||||
"file": "refactor/.cache/docs-smoke-browser-final.log",
|
||||
"sha256": "3cd781617dcc585c7cb7d5fdc8d3b9345cff9863ba81c2fc85f8d98995d2bb85",
|
||||
"exitCode": 1,
|
||||
"pass": 31
|
||||
},
|
||||
"browserAccepted": {
|
||||
"file": "refactor/.cache/docs-smoke-browser-verified.log",
|
||||
"sha256": "d4c01724c6d5960b33efc242b861553a65da0f7842e6def593e899b1f7387217",
|
||||
"exitCode": 0,
|
||||
"pass": 33
|
||||
}
|
||||
},
|
||||
"rootLintExistingWarnings": 1,
|
||||
"browser": {
|
||||
"file": "refactor/.cache/docs-smoke-browser-accepted/report.json",
|
||||
"sha256": "cc96afc1e1b53e78db3063679bb4505fb19524d720019ad856e270d6bd6397f0",
|
||||
"stats": {
|
||||
"startTime": "2026-09-13T22:43:58.881Z",
|
||||
"duration": 32387.1,
|
||||
"expected": 33,
|
||||
"skipped": 0,
|
||||
"unexpected": 0,
|
||||
"flaky": 0
|
||||
},
|
||||
"cases": [
|
||||
{
|
||||
"title": "readiness smoke uses actual core/media and releases each frame without touching a parent player",
|
||||
"project": "chromium",
|
||||
"status": "expected",
|
||||
"attempts": 1,
|
||||
"browser": "153.0.8010.12",
|
||||
"platform": "win32",
|
||||
"errors": [],
|
||||
"observations": [
|
||||
{
|
||||
"name": "readiness-0",
|
||||
"contentType": "application/json",
|
||||
"content": "{\"id\":\"fixture.md:1\",\"instances\":1,\"ready\":1,\"media\":[{\"readyState\":4,\"width\":320,\"height\":180,\"duration\":8}]}"
|
||||
},
|
||||
{
|
||||
"name": "readiness-1",
|
||||
"contentType": "application/json",
|
||||
"content": "{\"id\":\"fixture.md:1\",\"instances\":1,\"ready\":1,\"media\":[{\"readyState\":4,\"width\":320,\"height\":180,\"duration\":8}]}"
|
||||
},
|
||||
{
|
||||
"name": "readiness-2",
|
||||
"contentType": "application/json",
|
||||
"content": "{\"id\":\"fixture.md:1\",\"instances\":1,\"ready\":1,\"media\":[{\"readyState\":4,\"width\":320,\"height\":180,\"duration\":8}]}"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "smoke remains pending beyond 100ms until real media becomes ready",
|
||||
"project": "chromium",
|
||||
"status": "expected",
|
||||
"attempts": 1,
|
||||
"browser": "153.0.8010.12",
|
||||
"platform": "win32",
|
||||
"errors": [],
|
||||
"observations": []
|
||||
},
|
||||
{
|
||||
"title": "generated real documentation cases run with controlled native media",
|
||||
"project": "chromium",
|
||||
"status": "expected",
|
||||
"attempts": 1,
|
||||
"browser": "153.0.8010.12",
|
||||
"platform": "win32",
|
||||
"errors": [],
|
||||
"observations": []
|
||||
},
|
||||
{
|
||||
"title": "parallel calls are rejected while a controlled case is still active",
|
||||
"project": "chromium",
|
||||
"status": "expected",
|
||||
"attempts": 1,
|
||||
"browser": "153.0.8010.12",
|
||||
"platform": "win32",
|
||||
"errors": [],
|
||||
"observations": []
|
||||
},
|
||||
{
|
||||
"title": "smoke reports synchronous error and removes its frame",
|
||||
"project": "chromium",
|
||||
"status": "expected",
|
||||
"attempts": 1,
|
||||
"browser": "153.0.8010.12",
|
||||
"platform": "win32",
|
||||
"errors": [],
|
||||
"observations": []
|
||||
},
|
||||
{
|
||||
"title": "smoke reports returned rejection and removes its frame",
|
||||
"project": "chromium",
|
||||
"status": "expected",
|
||||
"attempts": 1,
|
||||
"browser": "153.0.8010.12",
|
||||
"platform": "win32",
|
||||
"errors": [],
|
||||
"observations": []
|
||||
},
|
||||
{
|
||||
"title": "smoke reports unhandled rejection and removes its frame",
|
||||
"project": "chromium",
|
||||
"status": "expected",
|
||||
"attempts": 1,
|
||||
"browser": "153.0.8010.12",
|
||||
"platform": "win32",
|
||||
"errors": [],
|
||||
"observations": []
|
||||
},
|
||||
{
|
||||
"title": "smoke reports readiness callback error and removes its frame",
|
||||
"project": "chromium",
|
||||
"status": "expected",
|
||||
"attempts": 1,
|
||||
"browser": "153.0.8010.12",
|
||||
"platform": "win32",
|
||||
"errors": [],
|
||||
"observations": []
|
||||
},
|
||||
{
|
||||
"title": "smoke reports cleanup error and removes its frame",
|
||||
"project": "chromium",
|
||||
"status": "expected",
|
||||
"attempts": 1,
|
||||
"browser": "153.0.8010.12",
|
||||
"platform": "win32",
|
||||
"errors": [],
|
||||
"observations": []
|
||||
},
|
||||
{
|
||||
"title": "missing script and never-ready examples fail instead of becoming green after a delay",
|
||||
"project": "chromium",
|
||||
"status": "expected",
|
||||
"attempts": 1,
|
||||
"browser": "153.0.8010.12",
|
||||
"platform": "win32",
|
||||
"errors": [],
|
||||
"observations": []
|
||||
},
|
||||
{
|
||||
"title": "frame-owned timers and globals do not survive into the next example",
|
||||
"project": "chromium",
|
||||
"status": "expected",
|
||||
"attempts": 1,
|
||||
"browser": "153.0.8010.12",
|
||||
"platform": "win32",
|
||||
"errors": [],
|
||||
"observations": []
|
||||
},
|
||||
{
|
||||
"title": "readiness smoke uses actual core/media and releases each frame without touching a parent player",
|
||||
"project": "firefox",
|
||||
"status": "expected",
|
||||
"attempts": 1,
|
||||
"browser": "155.0",
|
||||
"platform": "win32",
|
||||
"errors": [],
|
||||
"observations": [
|
||||
{
|
||||
"name": "readiness-0",
|
||||
"contentType": "application/json",
|
||||
"content": "{\"id\":\"fixture.md:1\",\"instances\":1,\"ready\":1,\"media\":[{\"readyState\":4,\"width\":320,\"height\":180,\"duration\":8}]}"
|
||||
},
|
||||
{
|
||||
"name": "readiness-1",
|
||||
"contentType": "application/json",
|
||||
"content": "{\"id\":\"fixture.md:1\",\"instances\":1,\"ready\":1,\"media\":[{\"readyState\":4,\"width\":320,\"height\":180,\"duration\":8}]}"
|
||||
},
|
||||
{
|
||||
"name": "readiness-2",
|
||||
"contentType": "application/json",
|
||||
"content": "{\"id\":\"fixture.md:1\",\"instances\":1,\"ready\":1,\"media\":[{\"readyState\":4,\"width\":320,\"height\":180,\"duration\":8}]}"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "smoke remains pending beyond 100ms until real media becomes ready",
|
||||
"project": "firefox",
|
||||
"status": "expected",
|
||||
"attempts": 1,
|
||||
"browser": "155.0",
|
||||
"platform": "win32",
|
||||
"errors": [],
|
||||
"observations": []
|
||||
},
|
||||
{
|
||||
"title": "generated real documentation cases run with controlled native media",
|
||||
"project": "firefox",
|
||||
"status": "expected",
|
||||
"attempts": 1,
|
||||
"browser": "155.0",
|
||||
"platform": "win32",
|
||||
"errors": [],
|
||||
"observations": []
|
||||
},
|
||||
{
|
||||
"title": "parallel calls are rejected while a controlled case is still active",
|
||||
"project": "firefox",
|
||||
"status": "expected",
|
||||
"attempts": 1,
|
||||
"browser": "155.0",
|
||||
"platform": "win32",
|
||||
"errors": [],
|
||||
"observations": []
|
||||
},
|
||||
{
|
||||
"title": "smoke reports synchronous error and removes its frame",
|
||||
"project": "firefox",
|
||||
"status": "expected",
|
||||
"attempts": 1,
|
||||
"browser": "155.0",
|
||||
"platform": "win32",
|
||||
"errors": [],
|
||||
"observations": []
|
||||
},
|
||||
{
|
||||
"title": "smoke reports returned rejection and removes its frame",
|
||||
"project": "firefox",
|
||||
"status": "expected",
|
||||
"attempts": 1,
|
||||
"browser": "155.0",
|
||||
"platform": "win32",
|
||||
"errors": [],
|
||||
"observations": []
|
||||
},
|
||||
{
|
||||
"title": "smoke reports unhandled rejection and removes its frame",
|
||||
"project": "firefox",
|
||||
"status": "expected",
|
||||
"attempts": 1,
|
||||
"browser": "155.0",
|
||||
"platform": "win32",
|
||||
"errors": [],
|
||||
"observations": []
|
||||
},
|
||||
{
|
||||
"title": "smoke reports readiness callback error and removes its frame",
|
||||
"project": "firefox",
|
||||
"status": "expected",
|
||||
"attempts": 1,
|
||||
"browser": "155.0",
|
||||
"platform": "win32",
|
||||
"errors": [],
|
||||
"observations": []
|
||||
},
|
||||
{
|
||||
"title": "smoke reports cleanup error and removes its frame",
|
||||
"project": "firefox",
|
||||
"status": "expected",
|
||||
"attempts": 1,
|
||||
"browser": "155.0",
|
||||
"platform": "win32",
|
||||
"errors": [],
|
||||
"observations": []
|
||||
},
|
||||
{
|
||||
"title": "missing script and never-ready examples fail instead of becoming green after a delay",
|
||||
"project": "firefox",
|
||||
"status": "expected",
|
||||
"attempts": 1,
|
||||
"browser": "155.0",
|
||||
"platform": "win32",
|
||||
"errors": [],
|
||||
"observations": []
|
||||
},
|
||||
{
|
||||
"title": "frame-owned timers and globals do not survive into the next example",
|
||||
"project": "firefox",
|
||||
"status": "expected",
|
||||
"attempts": 1,
|
||||
"browser": "155.0",
|
||||
"platform": "win32",
|
||||
"errors": [],
|
||||
"observations": []
|
||||
},
|
||||
{
|
||||
"title": "readiness smoke uses actual core/media and releases each frame without touching a parent player",
|
||||
"project": "webkit",
|
||||
"status": "expected",
|
||||
"attempts": 1,
|
||||
"browser": "26.6",
|
||||
"platform": "win32",
|
||||
"errors": [],
|
||||
"observations": [
|
||||
{
|
||||
"name": "readiness-0",
|
||||
"contentType": "application/json",
|
||||
"content": "{\"id\":\"fixture.md:1\",\"instances\":1,\"ready\":1,\"media\":[{\"readyState\":4,\"width\":320,\"height\":180,\"duration\":8}]}"
|
||||
},
|
||||
{
|
||||
"name": "readiness-1",
|
||||
"contentType": "application/json",
|
||||
"content": "{\"id\":\"fixture.md:1\",\"instances\":1,\"ready\":1,\"media\":[{\"readyState\":4,\"width\":320,\"height\":180,\"duration\":8}]}"
|
||||
},
|
||||
{
|
||||
"name": "readiness-2",
|
||||
"contentType": "application/json",
|
||||
"content": "{\"id\":\"fixture.md:1\",\"instances\":1,\"ready\":1,\"media\":[{\"readyState\":4,\"width\":320,\"height\":180,\"duration\":8}]}"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "smoke remains pending beyond 100ms until real media becomes ready",
|
||||
"project": "webkit",
|
||||
"status": "expected",
|
||||
"attempts": 1,
|
||||
"browser": "26.6",
|
||||
"platform": "win32",
|
||||
"errors": [],
|
||||
"observations": []
|
||||
},
|
||||
{
|
||||
"title": "generated real documentation cases run with controlled native media",
|
||||
"project": "webkit",
|
||||
"status": "expected",
|
||||
"attempts": 1,
|
||||
"browser": "26.6",
|
||||
"platform": "win32",
|
||||
"errors": [],
|
||||
"observations": []
|
||||
},
|
||||
{
|
||||
"title": "parallel calls are rejected while a controlled case is still active",
|
||||
"project": "webkit",
|
||||
"status": "expected",
|
||||
"attempts": 1,
|
||||
"browser": "26.6",
|
||||
"platform": "win32",
|
||||
"errors": [],
|
||||
"observations": []
|
||||
},
|
||||
{
|
||||
"title": "smoke reports synchronous error and removes its frame",
|
||||
"project": "webkit",
|
||||
"status": "expected",
|
||||
"attempts": 1,
|
||||
"browser": "26.6",
|
||||
"platform": "win32",
|
||||
"errors": [],
|
||||
"observations": []
|
||||
},
|
||||
{
|
||||
"title": "smoke reports returned rejection and removes its frame",
|
||||
"project": "webkit",
|
||||
"status": "expected",
|
||||
"attempts": 1,
|
||||
"browser": "26.6",
|
||||
"platform": "win32",
|
||||
"errors": [],
|
||||
"observations": []
|
||||
},
|
||||
{
|
||||
"title": "smoke reports unhandled rejection and removes its frame",
|
||||
"project": "webkit",
|
||||
"status": "expected",
|
||||
"attempts": 1,
|
||||
"browser": "26.6",
|
||||
"platform": "win32",
|
||||
"errors": [],
|
||||
"observations": []
|
||||
},
|
||||
{
|
||||
"title": "smoke reports readiness callback error and removes its frame",
|
||||
"project": "webkit",
|
||||
"status": "expected",
|
||||
"attempts": 1,
|
||||
"browser": "26.6",
|
||||
"platform": "win32",
|
||||
"errors": [],
|
||||
"observations": []
|
||||
},
|
||||
{
|
||||
"title": "smoke reports cleanup error and removes its frame",
|
||||
"project": "webkit",
|
||||
"status": "expected",
|
||||
"attempts": 1,
|
||||
"browser": "26.6",
|
||||
"platform": "win32",
|
||||
"errors": [],
|
||||
"observations": []
|
||||
},
|
||||
{
|
||||
"title": "missing script and never-ready examples fail instead of becoming green after a delay",
|
||||
"project": "webkit",
|
||||
"status": "expected",
|
||||
"attempts": 1,
|
||||
"browser": "26.6",
|
||||
"platform": "win32",
|
||||
"errors": [],
|
||||
"observations": []
|
||||
},
|
||||
{
|
||||
"title": "frame-owned timers and globals do not survive into the next example",
|
||||
"project": "webkit",
|
||||
"status": "expected",
|
||||
"attempts": 1,
|
||||
"browser": "26.6",
|
||||
"platform": "win32",
|
||||
"errors": [],
|
||||
"observations": []
|
||||
}
|
||||
],
|
||||
"before": {
|
||||
"pass": 31,
|
||||
"fail": 2,
|
||||
"reason": "Windows WebKit reports 640x360 layout dimensions for 320x180 media; existing DPIP-MEDIA-01. Exact pairs only, not closed.",
|
||||
"archive": "refactor/.cache/docs-smoke-browser-before-g6DmAA"
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"scripts/build-test.js": {
|
||||
"sha256Lf": "b032fc230d716f0f33650113a04564e64b827c7d637bbbf07afa990fa8755027"
|
||||
},
|
||||
"scripts/tsconfig.docs.json": {
|
||||
"sha256Lf": "8d2adc75cbe68f612cb83c3b4586b547cd33ddb724e0dba2471ea5d160fd20e5"
|
||||
},
|
||||
"scripts/docs-smoke/parser.ts": {
|
||||
"sha256Lf": "d1f19833124954922849d4f40f853380b0ddd7bbf232f7cdf9c07dd8d6403e1c"
|
||||
},
|
||||
"scripts/docs-smoke/generator.ts": {
|
||||
"sha256Lf": "2ae4144f27b3050869217e437ddca75ccc25897bf78f75efc91dd1f7d2ca3dc2"
|
||||
},
|
||||
"scripts/docs-smoke/runtime.ts": {
|
||||
"sha256Lf": "9fae3aa1bc6a6ef9728d9a677de9e283d40314022b710b2ecf19a9b951f6291d"
|
||||
},
|
||||
"refactor/scripts/docs-smoke.test.mjs": {
|
||||
"sha256Lf": "ddb417851665bfc2bd9e3a52c43b0fbc450fcd8eff7e0d63427bd6c2665704d5"
|
||||
},
|
||||
"test/browser/docs-smoke.spec.js": {
|
||||
"sha256Lf": "7e3efb737346da3f9da8daf29f5bd15ad8162bb906ced456002e4c8222d8ed9a"
|
||||
},
|
||||
"docs/test/index.html": {
|
||||
"sha256Lf": "5f525951d54590c358f060c8a4829abf322dcff89f31a25f3892d9030ad4e4c8"
|
||||
},
|
||||
"package.json": {
|
||||
"sha256Lf": "3ef07142a1b5a3141292694e23de7af26d25812ecd2863a7f2a3deb856ce7554"
|
||||
},
|
||||
"yarn.lock": {
|
||||
"sha256Lf": "8139d40cc1a40e4089c1bcf21f9edece6a8726256ba9246775680add45622512"
|
||||
},
|
||||
"packages/artplayer-vitepress/docs/advanced/built-in.md": {
|
||||
"sha256Lf": "332f7b86bdbbb2957fddfd94aa779f3b9c40c172695877d65b4edbdc0e67b39e"
|
||||
},
|
||||
"packages/artplayer-vitepress/docs/advanced/class.md": {
|
||||
"sha256Lf": "1d5c1c53beda9b5d6d40ab94587028e9d57a562b03604cb32e7d049058945ebf"
|
||||
},
|
||||
"packages/artplayer-vitepress/docs/advanced/event.md": {
|
||||
"sha256Lf": "a5b2bf5bba605914efaa76e7a21b16b52575e92e8fe1ec682f46ea0e4faf7a55"
|
||||
},
|
||||
"packages/artplayer-vitepress/docs/advanced/global.md": {
|
||||
"sha256Lf": "d9990b5c0293df1a8ded04044efbd59bdea6d9977bcf2bc0b0f8ee7d3df7d2ba"
|
||||
},
|
||||
"packages/artplayer-vitepress/docs/advanced/plugin.md": {
|
||||
"sha256Lf": "21b4dc45a245d7747fdf0bf0b86548d3bb945a037ac5493f290e81066ba65bb7"
|
||||
},
|
||||
"packages/artplayer-vitepress/docs/advanced/property.md": {
|
||||
"sha256Lf": "0c1a40d37bd3a87c31eb768acae3104286976b7feb0d560c187d31b0805421e6"
|
||||
},
|
||||
"packages/artplayer-vitepress/docs/component/contextmenu.md": {
|
||||
"sha256Lf": "50ee2ef72eca5f0ea4ef42283d98a0f5bd375d661b26f0ccd58d82c5d99aeb4e"
|
||||
},
|
||||
"packages/artplayer-vitepress/docs/component/controls.md": {
|
||||
"sha256Lf": "ef215c3e831835edc002f4e7e22fa1f619435936012684d23d251e78d4d08e79"
|
||||
},
|
||||
"packages/artplayer-vitepress/docs/component/layers.md": {
|
||||
"sha256Lf": "7bc9eea79ab23b1280aa34a7946ab4b98b0a52051e014378609f45fcd1f074ee"
|
||||
},
|
||||
"packages/artplayer-vitepress/docs/component/setting.md": {
|
||||
"sha256Lf": "4387c3b15e32ac3176d4c9279642262073c37df8fb213379eae8cee6d7718a1f"
|
||||
},
|
||||
"packages/artplayer-vitepress/docs/start/option.md": {
|
||||
"sha256Lf": "336c1d69cd484e18fbebd6a029069a0ab734af2a256bb696b6272003a1eadb15"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"docs/test/test.js": {
|
||||
"sha256Lf": "d56a24949b5b4c97b85abfb8233050b2e62c2869a0c15b1e3ad7c83d4987adfb"
|
||||
},
|
||||
"docs/test/examples.json": {
|
||||
"sha256Lf": "5af15520ebca3d84887391ea8b202fb0de7c2a2809dbe45cdad199f91f9cdf2a"
|
||||
}
|
||||
},
|
||||
"limitations": [
|
||||
"Readiness harness, not complete interaction/playback acceptance of 233 examples.",
|
||||
"No remote SDK endpoint, native Safari/device, manual Chrome/iab or remote CI validation.",
|
||||
"Same-origin iframe is not a security sandbox; storage restoration requires exclusive test origin.",
|
||||
"No push, publication or SITE-02 declaration-generator completion."
|
||||
]
|
||||
}
|
||||
@@ -17939,7 +17939,7 @@
|
||||
{
|
||||
"source": "docs/test/index.html",
|
||||
"route": "/test/",
|
||||
"sha256Lf": "67132147ebbb63867c05be182ec18fc6c3129df89808bf17819c92cee9a4769c",
|
||||
"sha256Lf": "5f525951d54590c358f060c8a4829abf322dcff89f31a25f3892d9030ad4e4c8",
|
||||
"kind": "demo-or-editor",
|
||||
"scriptSources": [
|
||||
"./chai.js",
|
||||
@@ -18113,7 +18113,7 @@
|
||||
},
|
||||
{
|
||||
"file": "scripts/build-test.js",
|
||||
"sha256Lf": "1e8ce7bce686b507bcf9f3fd02dfe03102b1940daa0fc14a487abadb909e93c7"
|
||||
"sha256Lf": "b032fc230d716f0f33650113a04564e64b827c7d637bbbf07afa990fa8755027"
|
||||
},
|
||||
{
|
||||
"file": "scripts/build-i18n.js",
|
||||
@@ -18154,6 +18154,18 @@
|
||||
{
|
||||
"file": "packages/artplayer-vitepress/docs/.vitepress/config.js",
|
||||
"sha256Lf": "9b2741347afb49d830dafa2a847625e6b1d6a820e930fb119c91b22dbbcc52d1"
|
||||
},
|
||||
{
|
||||
"file": "scripts/docs-smoke/generator.ts",
|
||||
"sha256Lf": "2ae4144f27b3050869217e437ddca75ccc25897bf78f75efc91dd1f7d2ca3dc2"
|
||||
},
|
||||
{
|
||||
"file": "scripts/docs-smoke/parser.ts",
|
||||
"sha256Lf": "d1f19833124954922849d4f40f853380b0ddd7bbf232f7cdf9c07dd8d6403e1c"
|
||||
},
|
||||
{
|
||||
"file": "scripts/docs-smoke/runtime.ts",
|
||||
"sha256Lf": "9fae3aa1bc6a6ef9728d9a677de9e283d40314022b710b2ecf19a9b951f6291d"
|
||||
}
|
||||
],
|
||||
"generationCommands": {
|
||||
@@ -18163,7 +18175,7 @@
|
||||
"build:i18n": "node ./scripts/build-i18n.js",
|
||||
"build:test": "node ./scripts/build-test.js",
|
||||
"build:all": "yarn ci:build && yarn lint",
|
||||
"ci:build": "yarn build:types && yarn build all && yarn build:i18n && yarn build:ts && yarn build:docs && yarn test:imports",
|
||||
"ci:build": "yarn build:types && yarn build all && yarn build:i18n && yarn build:ts && yarn build:test && yarn build:docs && yarn test:imports",
|
||||
"build:types": "node scripts/build-types.mjs --write"
|
||||
},
|
||||
"siteManifest": {
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
# SITE-SMOKE-01 文档示例生成与就绪检查
|
||||
|
||||
起点 `d74ecc43ccee99dcd2e3090356a6fb761825e6af`。从 SITE-02 拆出示例生成和运行器,
|
||||
保留原父任务负责编辑器声明生成。保留 `yarn build:test`、脚本路径、测试页面及
|
||||
`docs/test/test.js` URL;没有修改生产包 API、公开声明、播放器 DOM、事件或入口。
|
||||
|
||||
## 实现与缺陷
|
||||
|
||||
- `scripts/docs-smoke/parser.ts` 用现有 MarkdownIt tokens 替换共享正则游标。
|
||||
原生成器在异常围栏中反复 continue 且不推进,测试对固定旧提交使用 VM 超时
|
||||
复现;新解析有限结束,并在语法检查阶段带文件/行号失败。未闭合、空示例和
|
||||
marker 后不是 JavaScript 围栏也明确报错。只支持现有 Run Code 标记约定。
|
||||
- `generator.ts` 负责确定性枚举、语法编译、页面脚本依赖和 esbuild 打包;生成前
|
||||
验证全部输入。保留 11 个中文文件的 233 段旧代码(仅统一 CRLF 为 LF),新增
|
||||
examples.json 清单。重复生成一致,无时间戳;`--check` 只读拒绝漂移。
|
||||
- `runtime.ts` 管理每例独立 iframe、顺序依赖、返回 Promise、当前实例 ready、
|
||||
error/unhandledrejection 和清理。移除固定 100ms 无条件成功;4500ms 是失败
|
||||
deadline,ready 后仅多一个事件循环任务用于接收未处理拒绝。销毁失败仍移除
|
||||
frame、恢复 storage,并将原错误和清理错误一起报告。拒绝并发案例。
|
||||
- 原 JS CLI 是兼容入口,由严格 checkJs 覆盖;自有实现拆成 TS。维护地图和边界见
|
||||
`scripts/docs-smoke/README.md`,文档站、测试与 CI 说明同次更新。
|
||||
|
||||
## 工具链与 CI
|
||||
|
||||
根开发依赖新增精确 `@types/node` 24.10.0、`@types/markdown-it` 14.1.2,分别为
|
||||
Node 工具和已有 MarkdownIt 14.1.0 提供严格类型;没有播放器运行依赖变化。只维护
|
||||
根 yarn.lock,Node 24.21.0 原生类型擦除执行工具,Yarn Classic 1.22.22 frozen
|
||||
安装成功。新增 typecheck:docs-tools/check:docs-smoke 加入 ci:check,build:test
|
||||
加入 ci:build;没有把本地配置视为远端 CI 通过。
|
||||
|
||||
安装后发现共享 tsc bin 指向 `typescript-runtime-compat` 5.1.6,而主编译器是
|
||||
5.9.3。旧别名检查 Node 24 类型因 esnext.disposable/Symbol.dispose 等退出 2;
|
||||
新工具与根 React 检查改用显式 `node node_modules/typescript/bin/tsc`,保留旧
|
||||
编译器用于历史消费测试。严格检查不使用 skipLibCheck 掩盖错误。工具配置的
|
||||
ES2021 lib 用于 Node parser 的 replaceAll;浏览器产物 target 仍为 ES2020。
|
||||
|
||||
## 验证与限制
|
||||
|
||||
- 5 项生成器回归通过:固定旧代码比较、旧卡死复现、围栏反例、确定性与真实
|
||||
Mocha 注册、CLI 只读/失败不覆盖既有结果。完整 baseline 522 通过、0 跳过。
|
||||
- 三浏览器各 11 项,共 33 通过、0 跳过/重试通过:真实核心/受控媒体就绪、
|
||||
超过旧 100ms 后仍等待、连续挂载清理、三段未改原示例、并发拒绝、同步/返回/
|
||||
未处理/ready 回调/销毁错误、脚本失败、永不 ready、跨案例定时器与全局隔离。
|
||||
- 初次扩展测试有 31 通过/2 失败,均为 Windows WebKit 报告 640x360 而非媒体
|
||||
320x180。原报告保留。按既有 DPIP-MEDIA-01 记录精确两组尺寸,其他浏览器只
|
||||
接受 320x180;不扩大超时、不重试取绿,也未关闭原生尺寸问题或真实 Safari 门槛。
|
||||
- 主编译器的工具/React 检查、CI 50 项测试、根 lint(0 error/1 既有生成声明
|
||||
warning)、严格工具链(22 workspace/38 固定工具/1470 依赖选择器)通过。
|
||||
完整生产与消费者类型检查结果及输入/输出/日志指纹见验证 JSON。
|
||||
|
||||
iframe 是同源归属隔离,不是安全沙箱。需独占测试 origin;storage 恢复会覆盖
|
||||
其他同源并发写入,不为跨 tab 合并机制。案例交给其它窗口的资源不由本运行器
|
||||
接管。就绪不等于播放/seek/点击/延迟回调功能验收;完整 233 例、外部 SDK、
|
||||
所有语言/插件文档与真实设备仍由 EX-03、SITE-04 和包验收覆盖。本次没有手动
|
||||
连接 Chrome/iab、远端 CI、推送或发布。没有声称全部示例已通过浏览器执行。
|
||||
|
||||
## 状态与回退
|
||||
|
||||
SITE-SMOKE-01 单独完成提交;SITE-02 的声明生成器尚未完成。生成器生成资产,
|
||||
不得手改 test.js;解析/语法失败保证写入前退出,磁盘多文件写入不承诺事务性。
|
||||
回退本任务提交并执行 frozen 安装可恢复旧生成器和依赖;旧无限循环与 100ms
|
||||
假通过会随之恢复,因此优先修复具体回归。后续改动运行 README 对应命令并
|
||||
更新当前 site-inventory,历史验证 JSON 保持原证据。
|
||||
@@ -6,10 +6,11 @@
|
||||
|
||||
| 命令 | 行为 |
|
||||
| --- | --- |
|
||||
| `yarn lint` | 只读 ESLint,覆盖包源码/声明、JS/MJS 工具、测试和编辑器声明 |
|
||||
| `yarn lint` | 只读 ESLint,覆盖包源码/声明、JS/MJS 工具、docs-smoke TS 模块、测试和编辑器声明 |
|
||||
| `yarn lint:fix` | 显式自动修复相同范围 |
|
||||
| `yarn typecheck` | 根/迁移包严格检查、当前与兼容 TS 消费;历史 NodeNext ESM 错误单独核对,见 typechecking.md |
|
||||
| `yarn typecheck:react` / `yarn typecheck:vue` | 原 React TSX / Vue SFC 示例严格检查,ci:check 同时执行对应 lint |
|
||||
| `yarn typecheck:docs-tools` / `yarn check:docs-smoke` | 严格检查 TS 示例生成器及 JS 命令门面;只读核对确定性生成的 readiness smoke,ci:check 执行 |
|
||||
| `yarn test:react-consumer` / `yarn test:vue-consumer` | 仓库外 tarball 安装、原框架示例、开发/生产三引擎;browser-smoke 执行并上传独立目录,本地证据不代替远端矩阵 |
|
||||
| `yarn test:unit` | 原播放/DASH 回归、同夹具的新旧公共契约与 JS/TS loader 验证 |
|
||||
| `yarn test:node` | test:unit 加工具链/文档构建回归,保留原 test:playback/test:dash-control 入口 |
|
||||
@@ -21,9 +22,9 @@
|
||||
| `yarn check:contracts --report` | 校验12类契约/22包归属、版本及报告对应;--write更新静态表,详见 [维护说明](contract-coverage.md) |
|
||||
| `yarn ci:check` | 严格 Node/Yarn/锁检查、计划、只读 lint、类型、Node 和基线测试;允许写忽略缓存,不修改源码 |
|
||||
| `yarn check:ci` | 只读校验实际工作流的完整系统矩阵、安装、缓存、报告和最终检查;已接入 ci:check |
|
||||
| `yarn test:ci` | CI 汇总退出码、工作流/影响分析反例与隔离运行时校验,共 44 项 |
|
||||
| `yarn test:ci` | CI 汇总退出码、工作流/影响分析反例与隔离运行时校验;SITE-SMOKE-01 实测 50 项 |
|
||||
| `yarn test:package:runtime` | 标准 Node 重装同一已检查 tarball;须先运行 test:package,其他 Node 使用显式 --expected-node |
|
||||
| `yarn ci:build` | 21 库包、i18n、编辑器声明和文档站构建,以及构建后包导入 smoke;会生成 dist 和 docs 内容 |
|
||||
| `yarn ci:build` | 21 库包、i18n、编辑器声明、文档 readiness smoke 和文档站构建,以及构建后包导入 smoke;会生成 dist 和 docs 内容 |
|
||||
| `yarn check:impact --report` | 读取实际依赖/验证关系和Git变更,核对workflow必需命令,写CI影响报告;已接入ci:check,见[影响映射](impact-analysis.md) |
|
||||
| `yarn build:all` | 保留旧入口,执行 ci:build 后只读 lint |
|
||||
|
||||
|
||||
+6
-4
@@ -2,9 +2,9 @@
|
||||
|
||||
> 由 tasks.json 生成。请修改数据后运行 `node refactor/scripts/plan.mjs --write`,不要手改本表。
|
||||
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 239 项,范围 22 个包及工作区/示例。
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 240 项,范围 22 个包及工作区/示例。
|
||||
|
||||
状态:todo 60 / doing 15 / blocked 0 / done 164 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
状态:todo 60 / doing 15 / blocked 0 / done 165 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
|
||||
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
| artplayer-proxy-mediabunny | 1.2.0 | PKG-MB-01, PKG-MB-02, PKG-MB-03, PKG-MB-04, PKG-MB-05, PKG-MB-06, PKG-MB-07, PKG-MB-08, PKG-MB-09, PKG-MB-10 |
|
||||
| artplayer-tool-iframe | 1.1.0 | PKG-IFRAME-01, PKG-IFRAME-02, PKG-IFRAME-03, PKG-IFRAME-04, PKG-IFRAME-05, PKG-IFRAME-06 |
|
||||
| artplayer-tool-thumbnail | 4.4.0 | PKG-TOOL-THUMB-01, PKG-TOOL-THUMB-02, PKG-TOOL-THUMB-03, PKG-TOOL-THUMB-04, PKG-TOOL-THUMB-05, PKG-TOOL-THUMB-06 |
|
||||
| artplayer-vitepress | 1.1.0 | SITE-01, SITE-02, SITE-03, SITE-04, SITE-05, SITE-06, SITE-07 |
|
||||
| artplayer-vitepress | 1.1.0 | SITE-01, SITE-SMOKE-01, SITE-02, SITE-03, SITE-04, SITE-05, SITE-06, SITE-07 |
|
||||
|
||||
## 0 规划
|
||||
|
||||
@@ -381,7 +381,8 @@
|
||||
| ID | 范围 / 步骤 | 前置依赖 | 交付物 | 验收条件 | 风险 | 状态 |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| SITE-01 | artplayer-vitepress, workspace<br>清点文档/示例/生成链 | BASE-04, BASE-05 | 中英文文档、插件页面、demo URL、编辑器声明与生成目录清单;六类 HTML 入口、prod/libs/code/example 加载行为及移动重定向 | 所有公开 API/插件有对应页面或明确补充任务;核实 BASE-DEMO-01 的历史 thumbnail 插件来源及 29 示例/36 HTML 路径;明确 BASE-05 所记录站点实际分发与未设 private 的 manifest 意图;接续 VENDOR-05/06/07/08 和 BASE-SITE-01/BASE-MEDIA-01,核对字体、Monaco、vConsole、console bundle 与样本来源及分发范围 | M | done |
|
||||
| SITE-02 | artplayer-vitepress<br>整理声明与示例生成器 | SITE-01, ENG-04, ENG-06 | build-ts/build-test 生成链的可验证 TS 脚本 | 不靠字符串拼接掩盖声明错误,生成示例有真实断言或仅标 smoke;替换固定 100ms 成功判定,明确异步错误、清理和生成覆盖限制;坏代码块解析必须终止,覆盖 malformed 分支不推进 regexp 的回归 | M | todo |
|
||||
| SITE-SMOKE-01 | artplayer-vitepress, workspace<br>迁移示例生成器并验证真实就绪与清理 | SITE-01 | TS Markdown 解析/生成与浏览器运行模块;旧命令和 URL、稳定实例就绪 smoke、错误与资源清理 | 复现旧 malformed 死循环并保证有限失败;保留全部既有示例内容、生成确定且 check 只读;不再以 100ms 作为成功,验证原生就绪、异常、取消与清理;三浏览器检查,明确延迟交互与全部 233 示例仍需 EX-03 | M | done |
|
||||
| SITE-02 | artplayer-vitepress<br>整理声明与示例生成器 | SITE-01, ENG-04, ENG-06, SITE-SMOKE-01 | build-ts/build-test 生成链的可验证 TS 脚本 | 不靠字符串拼接掩盖声明错误,生成示例有真实断言或仅标 smoke;替换固定 100ms 成功判定,明确异步错误、清理和生成覆盖限制;坏代码块解析必须终止,覆盖 malformed 分支不推进 regexp 的回归 | M | todo |
|
||||
| SITE-03 | artplayer-vitepress<br>整理 i18n/文档/LLM 生成流程 | SITE-02 | build-i18n/build-docs/build-llm/trans-docs 的任务边界和错误处理 | 原命令兼容、生成可复现,翻译步骤不隐式运行远程服务;覆盖移动 loader 失败恢复 define、脚本依赖顺序、localhost/127.0.0.1 Run Code 目标和语言重定向 | M | todo |
|
||||
| SITE-04 | artplayer-vitepress<br>交叉核对逐包持续维护的文档 | CORE-21, SITE-03, PKG-CHAPTER-04, PKG-AMBILIGHT-04, PKG-AUDIO-04, PKG-AUTO-THUMB-04, PKG-VTT-THUMB-04, PKG-HLS-04, PKG-DASH-04, PKG-MULTI-SUB-04, PKG-JASSUB-04, PKG-MASK-04, PKG-ASR-04, PKG-ADS-04, PKG-VAST-04, PKG-CAST-04, PKG-DPIP-04, PKG-CANVAS-04, PKG-IFRAME-04, PKG-TOOL-THUMB-04, PKG-DANMUKU-06, PKG-MB-08 | 已随实现更新的中文/英文 API、包内实现地图、旧 JS 示例及已知能力限制的全包核对 | 未把缺环境的能力写成已验证,静态核对不等待设备任务;最终 demo 仍由 EX-03 验收;按 SITE-01 声明成员/候选标题清单逐项语义核对,补齐缺失的双语插件说明和 Danmuku 英文入口 | M | todo |
|
||||
| SITE-05 | artplayer-vitepress<br>构建文档站和验证链接/示例 | SITE-04, EX-01, EX-02, SITE-07 | VitePress 构建、链接与嵌入 demo 检查 | 文档构建、链接、嵌入路径与声明注入通过;真实完整 demo 保留 EX-03 独立门槛;核对 ENG-PM-01 登记的搜索 peer 范围和真实搜索行为 | M | todo |
|
||||
@@ -606,6 +607,7 @@
|
||||
- PKG-TOOL-THUMB-03: [记录](changes/2026-09-13-PKG-TOOL-THUMB-03-input-checkpoint.md) [记录](baselines/thumbnail-input-checkpoint.json) [记录](changes/2026-09-13-PKG-TOOL-THUMB-03-lifecycle.md) [记录](baselines/thumbnail-lifecycle-validation.json)
|
||||
- PKG-TOOL-THUMB-04: [记录](changes/2026-09-13-PKG-TOOL-THUMB-04-runtime-types.md) [记录](baselines/thumbnail-runtime-types-validation.json) [记录](changes/2026-09-13-PKG-TOOL-THUMB-04-public-types.md) [记录](baselines/thumbnail-public-types-validation.json) [记录](changes/2026-09-13-PKG-TOOL-THUMB-04-emitter.md) [记录](baselines/thumbnail-emitter-validation.json)
|
||||
- SITE-01: [记录](site-inventory.md) [记录](baselines/site-inventory.json) [记录](baselines/site-provenance.json) [记录](baselines/demo-additions.json) [记录](changes/2026-09-14-SITE-01-site-inventory.md) [记录](baselines/site-inventory-validation.json)
|
||||
- SITE-SMOKE-01: [记录](changes/2026-09-14-SITE-SMOKE-01-documentation-smoke.md) [记录](baselines/docs-smoke-validation.json) [记录](scripts/docs-smoke.test.mjs)
|
||||
- SITE-07: [记录](site-inventory.md) [记录](baselines/site-provenance.json)
|
||||
- EX-01: [记录](changes/2026-09-14-EX-01-react-consumer.md) [记录](baselines/react-consumer-validation.json) [记录](scripts/react-consumer.mjs)
|
||||
- EX-02: [记录](changes/2026-09-14-EX-02-vue-consumer.md) [记录](baselines/vue-consumer-validation.json) [记录](scripts/vue-consumer.mjs)
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# 进度与证据
|
||||
|
||||
## SITE-SMOKE-01 示例生成器与就绪检查完成
|
||||
|
||||
从 SITE-02 拆出文档示例工具,TS parser/generator/runtime 与严格检查的原 JS
|
||||
入口职责分开。固定旧版复现异常围栏无限循环,保留 11 文件 233 段旧示例;
|
||||
生成清单和浏览器 bundle 可重复、可只读检查。每例 iframe 等待真实 ready、
|
||||
收集同步/异步错误并清理,替换旧固定 100ms 成功。三浏览器共 33 项、生成器
|
||||
5 项、完整 baseline 522 项、CI 50 项及 406 生产 TS/消费者检查通过;frozen
|
||||
安装、工具链与 lint 通过(根 lint 1 既有 warning)。新增两项根开发类型依赖,
|
||||
显式使用主 TS 5.9.3,修复共享 tsc bin 指向兼容 5.1.6 的检查歧义。Windows
|
||||
WebKit 原尺寸差异仍保留 DPIP-MEDIA-01;完整 233 例交互/SDK/设备没有计为通过。
|
||||
见[变更](changes/2026-09-14-SITE-SMOKE-01-documentation-smoke.md)与
|
||||
[证据](baselines/docs-smoke-validation.json)。当前 240 项:165 done、15 doing、
|
||||
60 todo。下一步 SITE-02 编辑器声明生成器;VAST 兼容选择、Auto Thumbnail 首帧
|
||||
和各包最终验收仍开放。仅本地独立提交,无推送或发布。
|
||||
|
||||
## SITE-01 文档/示例/生成链清点完成
|
||||
|
||||
建立当前 22 包、27 Markdown、30 示例、36 HTML、963 声明成员和 204 资产
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { execFileSync, spawnSync } from 'node:child_process'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
// eslint-disable-next-line test/no-import-node-test -- Repository baseline test runner.
|
||||
import test from 'node:test'
|
||||
import vm from 'node:vm'
|
||||
import { collectExamples, generateDocumentationSmoke } from '../../scripts/docs-smoke/generator.ts'
|
||||
import { extractExamples } from '../../scripts/docs-smoke/parser.ts'
|
||||
|
||||
const root = process.cwd()
|
||||
const old = execFileSync('git', ['show', 'd74ecc43ccee99dcd2e3090356a6fb761825e6af:scripts/build-test.js'], {
|
||||
encoding: 'utf8',
|
||||
})
|
||||
const oldDefinitions = old.slice(0, old.indexOf('// Main execution')).replace(/^import .*$/gm, '')
|
||||
const marker = '<div className="run-code">Run Code</div>'
|
||||
const fence = code => `${marker}\n\n\`\`\`js{2}\n${code}\n\`\`\`\n`
|
||||
|
||||
test('Markdown tokens keep all 233 current examples identical after LF normalization', () => {
|
||||
const examples = collectExamples('packages/artplayer-vitepress/docs')
|
||||
const context = vm.createContext({ fs, path, console: { warn() {} } })
|
||||
vm.runInContext(oldDefinitions, context)
|
||||
for (const file of new Set(examples.map(example => example.file))) {
|
||||
context.content = fs.readFileSync(`packages/artplayer-vitepress/docs/${file}`, 'utf8').replaceAll('\r\n', '\n')
|
||||
const oldCodes = vm.runInContext(
|
||||
'JSON.stringify(extractCodeBlocks(content, "current.md").map(item => item.code))',
|
||||
context,
|
||||
{ timeout: 1000 },
|
||||
)
|
||||
assert.deepEqual(
|
||||
examples.filter(example => example.file === file).map(example => example.code),
|
||||
JSON.parse(oldCodes),
|
||||
)
|
||||
}
|
||||
assert.equal(examples.length, 233)
|
||||
assert.equal(new Set(examples.map(example => example.id)).size, examples.length)
|
||||
})
|
||||
|
||||
test('Malformed historical regex loops; new parsing terminates and syntax validation rejects it', () => {
|
||||
const content = fence('var valid = 1;\n## misplaced heading\nvar more = 2;')
|
||||
const context = vm.createContext({ fs, path, content, console: { warn() {} } })
|
||||
vm.runInContext(oldDefinitions, context)
|
||||
assert.throws(() => vm.runInContext('extractCodeBlocks(content, "broken.md")', context, { timeout: 100 }), {
|
||||
code: 'ERR_SCRIPT_EXECUTION_TIMEOUT',
|
||||
})
|
||||
const [example] = extractExamples(content, 'broken.md')
|
||||
assert.throws(() => new vm.Script(example.code, { filename: example.id }), SyntaxError)
|
||||
assert.throws(
|
||||
() => extractExamples(`${marker}\n\n\`\`\`js\nvar unfinished = 1;`, 'unclosed.md'),
|
||||
/unclosed.md:1: Unclosed/,
|
||||
)
|
||||
})
|
||||
|
||||
test('Markdown syntax handles CRLF and ignores markers inside ordinary code fences', () => {
|
||||
const content = `\`\`\`html\n${marker}\n\`\`\`\n${fence('const value = "\\n## inside a JS string";')}`
|
||||
const result = extractExamples(content.replaceAll('\n', '\r\n'), 'quoted.md')
|
||||
assert.equal(result.length, 1)
|
||||
assert.equal(result[0].line, 7)
|
||||
assert.throws(() => extractExamples(marker, 'missing.md'), /must be followed/)
|
||||
assert.throws(() => extractExamples(fence('').replace('js{2}', 'css'), 'wrong.md'), /JavaScript fence/)
|
||||
assert.throws(() => extractExamples(fence(''), 'empty.md'), /Empty/)
|
||||
})
|
||||
|
||||
test('Smoke output is deterministic and includes isolation instead of the old 100ms completion', async () => {
|
||||
const first = await generateDocumentationSmoke(root)
|
||||
assert.deepEqual(await generateDocumentationSmoke(root), first)
|
||||
assert(!first.get('docs/test/test.js').includes('Generated at:'))
|
||||
assert(!first.get('docs/test/test.js').includes('setTimeout(() => done(), 100)'))
|
||||
const data = JSON.parse(first.get('docs/test/examples.json'))
|
||||
assert.equal(data.examples.length, 233)
|
||||
assert.equal(data.scripts.length, 11)
|
||||
assert(data.scope.includes('Not complete feature'))
|
||||
const registered = []
|
||||
const context = vm.createContext({
|
||||
describe(_name, callback) {
|
||||
callback.call({ timeout() {} })
|
||||
},
|
||||
it(name, callback) {
|
||||
registered.push({ name, callback })
|
||||
},
|
||||
})
|
||||
vm.runInContext(first.get('docs/test/test.js'), context)
|
||||
assert.deepEqual(
|
||||
registered.map(item => item.name),
|
||||
data.examples.map(item => item.id),
|
||||
)
|
||||
assert(registered.every(item => typeof item.callback === 'function'))
|
||||
})
|
||||
|
||||
test('CLI checks drift and validates all source examples before changing any output', () => {
|
||||
const directory = fs.mkdtempSync(path.join(root, 'refactor/.cache/docs-smoke-test-'))
|
||||
try {
|
||||
fs.mkdirSync(path.join(directory, 'packages/artplayer-vitepress/docs'), { recursive: true })
|
||||
fs.mkdirSync(path.join(directory, 'docs/test'), { recursive: true })
|
||||
fs.cpSync(path.join(root, 'scripts/docs-smoke'), path.join(directory, 'scripts/docs-smoke'), { recursive: true })
|
||||
const source = path.join(directory, 'packages/artplayer-vitepress/docs/index.md')
|
||||
fs.writeFileSync(source, fence('var number = 1;'))
|
||||
fs.writeFileSync(path.join(directory, 'docs/test/index.html'), '<script src="../compiled/artplayer.js"></script>')
|
||||
const run = args =>
|
||||
spawnSync(process.execPath, [path.join(root, 'scripts/build-test.js'), ...args], {
|
||||
cwd: directory,
|
||||
encoding: 'utf8',
|
||||
timeout: 30000,
|
||||
})
|
||||
assert.equal(run([]).status, 0)
|
||||
assert.equal(run(['--check']).status, 0)
|
||||
const output = path.join(directory, 'docs/test/test.js')
|
||||
fs.appendFileSync(output, '// drift\n')
|
||||
const drift = run(['--check'])
|
||||
assert.notEqual(drift.status, 0)
|
||||
assert.match(drift.stderr, /Generated documentation smoke drift/)
|
||||
const preserved = fs.readFileSync(output)
|
||||
fs.writeFileSync(source, fence('const invalid = ;'))
|
||||
assert.notEqual(run([]).status, 0)
|
||||
assert.deepEqual(fs.readFileSync(output), preserved)
|
||||
}
|
||||
finally {
|
||||
assert(directory.startsWith(path.join(root, 'refactor/.cache/docs-smoke-test-')))
|
||||
fs.rmSync(directory, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
@@ -78,7 +78,7 @@ export function captureSiteInventory() {
|
||||
pages: demo.pages,
|
||||
baselinePaths: { examplesAdded: demo.examples.filter(row => !historical.examples.some(old => old.source === row.source)).map(row => row.source), examplesRemoved: historical.examples.filter(row => !demo.examples.some(now => now.source === row.source)).map(row => row.source), htmlAdded: demo.pages.filter(row => !historical.pages.some(old => old.source === row.source)).map(row => row.source), htmlRemoved: historical.pages.filter(row => !demo.pages.some(now => now.source === row.source)).map(row => row.source) },
|
||||
editorDeclarations: [...read('docs/assets/js/common.js').matchAll(/'\.\/assets\/ts\/([^']+\.d\.ts)'/g)].map(match => ({ file: `docs/assets/ts/${match[1]}`, exists: fs.existsSync(path.join(root, 'docs/assets/ts', match[1])), owner: 'SITE-02' })),
|
||||
scripts: scriptFiles.map(file => ({ file, sha256Lf: hash(read(file)) })),
|
||||
scripts: [...scriptFiles, ...files('scripts/docs-smoke').filter(file => file.endsWith('.ts'))].map(file => ({ file, sha256Lf: hash(read(file)) })),
|
||||
generationCommands: Object.fromEntries(Object.entries(json('package.json').scripts).filter(([name]) => /^(?:build:(?:types|ts|test|i18n|docs|llm|all)|ci:build)$/.test(name))),
|
||||
siteManifest: json('packages/artplayer-vitepress/package.json'),
|
||||
assets,
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# 文档站、示例与生成链清单
|
||||
|
||||
SITE-SMOKE-01 后续已将 build:test 拆为 TS 解析、生成和浏览器运行模块,新增
|
||||
确定性 examples.json 和 readonly check,替换 100ms done 为实例 ready/错误/清理。
|
||||
以下 SITE-01 的旧生成器问题属于历史发现;当前维护入口见
|
||||
[docs-smoke README](../scripts/docs-smoke/README.md)。声明生成仍归 SITE-02。
|
||||
|
||||
SITE-01 在 `d62ab13a35c756ea567c426d4fbe00d893dd7e2b` 后核对当前源码。
|
||||
可重跑清单见 [site-inventory.json](baselines/site-inventory.json);它登记
|
||||
22 包、27 Markdown、30 示例、36 HTML、963 条声明成员和 204 个资产指纹。
|
||||
|
||||
+23
-1
@@ -4104,6 +4104,27 @@
|
||||
"baselines/site-inventory-validation.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "SITE-SMOKE-01",
|
||||
"phase": "6 文档与消费者",
|
||||
"title": "迁移示例生成器并验证真实就绪与清理",
|
||||
"scope": [
|
||||
"artplayer-vitepress",
|
||||
"workspace"
|
||||
],
|
||||
"dependsOn": [
|
||||
"SITE-01"
|
||||
],
|
||||
"status": "done",
|
||||
"risk": "M",
|
||||
"deliverable": "TS Markdown 解析/生成与浏览器运行模块;旧命令和 URL、稳定实例就绪 smoke、错误与资源清理",
|
||||
"acceptance": "复现旧 malformed 死循环并保证有限失败;保留全部既有示例内容、生成确定且 check 只读;不再以 100ms 作为成功,验证原生就绪、异常、取消与清理;三浏览器检查,明确延迟交互与全部 233 示例仍需 EX-03",
|
||||
"evidence": [
|
||||
"changes/2026-09-14-SITE-SMOKE-01-documentation-smoke.md",
|
||||
"baselines/docs-smoke-validation.json",
|
||||
"scripts/docs-smoke.test.mjs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "SITE-02",
|
||||
"phase": "6 文档与消费者",
|
||||
@@ -4114,7 +4135,8 @@
|
||||
"dependsOn": [
|
||||
"SITE-01",
|
||||
"ENG-04",
|
||||
"ENG-06"
|
||||
"ENG-06",
|
||||
"SITE-SMOKE-01"
|
||||
],
|
||||
"status": "todo",
|
||||
"risk": "M",
|
||||
|
||||
+10
-134
@@ -1,137 +1,13 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import { generateDocumentationSmoke } from './docs-smoke/generator.ts'
|
||||
|
||||
// 使用中文文档作为源(中文是原始文档,英文是 AI 翻译的)
|
||||
const docsDir = path.resolve('packages/artplayer-vitepress/docs')
|
||||
const outputFile = path.resolve('docs/test/test.js')
|
||||
|
||||
// 排除的目录
|
||||
const excludeDirs = ['en', '.vitepress', 'public', 'plugin']
|
||||
|
||||
// Regex to match code blocks after "Run Code" markers
|
||||
// Match until we find a closing ``` that's on its own line
|
||||
const runCodePattern = /<div className="run-code">.*?<\/div>[\t\v\f\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]*\n\s*```js[^\n]*\n([\s\S]*?)\n```(?=\s*\n|$)/g
|
||||
|
||||
function extractCodeBlocks(content, filePath) {
|
||||
const blocks = []
|
||||
let match = runCodePattern.exec(content)
|
||||
|
||||
while (match !== null) {
|
||||
const code = match[1].trim()
|
||||
|
||||
// Skip if code contains markdown syntax (unclosed code block issue)
|
||||
if (code.includes('\n## ') || code.includes('\n:::') || code.includes('<div className="run-code">')) {
|
||||
console.warn(`⚠️ Skipping malformed code block in ${filePath}`)
|
||||
continue
|
||||
}
|
||||
|
||||
blocks.push({
|
||||
code,
|
||||
file: filePath,
|
||||
})
|
||||
match = runCodePattern.exec(content)
|
||||
}
|
||||
|
||||
return blocks
|
||||
assert(process.argv.slice(2).every(arg => arg === '--check'), 'Use yarn build:test [--check]')
|
||||
const outputs = await generateDocumentationSmoke(process.cwd())
|
||||
for (const [file, content] of outputs) {
|
||||
if (process.argv.includes('--check'))
|
||||
assert.equal(fs.readFileSync(file, 'utf8').replaceAll('\r\n', '\n'), content, `Generated documentation smoke drift: ${file}`)
|
||||
else fs.writeFileSync(file, content)
|
||||
}
|
||||
|
||||
function processMarkdownFiles(dir, relativePath = '') {
|
||||
const results = []
|
||||
const items = fs.readdirSync(dir)
|
||||
|
||||
for (const item of items) {
|
||||
// 跳过排除的目录
|
||||
if (excludeDirs.includes(item)) {
|
||||
continue
|
||||
}
|
||||
|
||||
const fullPath = path.join(dir, item)
|
||||
const relPath = path.join(relativePath, item)
|
||||
const stat = fs.statSync(fullPath)
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
results.push(...processMarkdownFiles(fullPath, relPath))
|
||||
}
|
||||
else if (item.endsWith('.md')) {
|
||||
const content = fs.readFileSync(fullPath, 'utf-8')
|
||||
const blocks = extractCodeBlocks(content, relPath)
|
||||
if (blocks.length > 0) {
|
||||
results.push({
|
||||
file: relPath,
|
||||
blocks,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
function generateTestCode(fileResults) {
|
||||
const lines = []
|
||||
|
||||
lines.push(`// Auto-generated smoke tests from documentation code blocks`)
|
||||
lines.push(`// Generated at: ${new Date().toISOString()}`)
|
||||
lines.push(`// Do not edit manually - run 'node scripts/build-test.js' to regenerate`)
|
||||
lines.push(``)
|
||||
lines.push(`describe('Documentation Code Examples', function () {`)
|
||||
lines.push(` this.timeout(5000);`)
|
||||
lines.push(``)
|
||||
lines.push(` beforeEach(function () {`)
|
||||
lines.push(` // Destroy any existing instances`)
|
||||
lines.push(` [...Artplayer.instances].forEach((art) => art.destroy());`)
|
||||
lines.push(` // Reset container`)
|
||||
lines.push(` const container = document.querySelector('.artplayer-app');`)
|
||||
lines.push(` if (container) {`)
|
||||
lines.push(` container.innerHTML = '';`)
|
||||
lines.push(` }`)
|
||||
lines.push(` });`)
|
||||
lines.push(``)
|
||||
lines.push(` afterEach(function () {`)
|
||||
lines.push(` [...Artplayer.instances].forEach((art) => art.destroy());`)
|
||||
lines.push(` });`)
|
||||
lines.push(``)
|
||||
|
||||
for (const { file, blocks } of fileResults) {
|
||||
const suiteName = file.replace(/\.md$/, '').replace(/\//g, ' > ')
|
||||
|
||||
lines.push(` describe('${suiteName}', function () {`)
|
||||
|
||||
blocks.forEach((block, index) => {
|
||||
const testName = `Example ${index + 1}`
|
||||
|
||||
lines.push(` it('${testName}', function (done) {`)
|
||||
lines.push(` try {`)
|
||||
lines.push(` ${block.code.split('\n').join('\n ')}`)
|
||||
lines.push(` // Wait a bit for async initialization`)
|
||||
lines.push(` setTimeout(() => done(), 100);`)
|
||||
lines.push(` } catch (err) {`)
|
||||
lines.push(` done(err);`)
|
||||
lines.push(` }`)
|
||||
lines.push(` });`)
|
||||
lines.push(``)
|
||||
})
|
||||
|
||||
lines.push(` });`)
|
||||
lines.push(``)
|
||||
}
|
||||
|
||||
lines.push(`});`)
|
||||
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
// Main execution
|
||||
const fileResults = processMarkdownFiles(docsDir)
|
||||
|
||||
let totalBlocks = 0
|
||||
for (const { file, blocks } of fileResults) {
|
||||
console.log(`📄 ${file}: ${blocks.length} code blocks`)
|
||||
totalBlocks += blocks.length
|
||||
}
|
||||
|
||||
const testCode = generateTestCode(fileResults)
|
||||
fs.writeFileSync(outputFile, testCode)
|
||||
|
||||
console.log(`\n✅ Generated ${totalBlocks} test cases from ${fileResults.length} files`)
|
||||
console.log(`📝 Output: ${outputFile}`)
|
||||
console.log(`Documentation readiness smoke ${process.argv.includes('--check') ? 'checked' : 'generated'}: ${outputs.size} outputs; complete demo acceptance remains separate`)
|
||||
@@ -0,0 +1,75 @@
|
||||
# Documentation readiness smoke
|
||||
|
||||
This generator keeps the existing `yarn build:test` / `node scripts/build-test.js`
|
||||
entrypoint and `docs/test/test.js` URL. Use the pinned Node 24.21.0 runtime and
|
||||
Yarn 1.22.22. Node runs the owned TypeScript modules using native type stripping;
|
||||
the command shim remains JavaScript and is included in strict `checkJs` checking.
|
||||
The root typecheck commands use `node node_modules/typescript/bin/tsc` explicitly:
|
||||
Yarn's shared `tsc` binary can point at the installed 5.1.6 compatibility alias.
|
||||
The main 5.9.3 compiler and older compatibility compilers retain distinct roles.
|
||||
|
||||
| File | Ownership |
|
||||
| ----------------------- | ---------------------------------------------------------------------------------------------------------- |
|
||||
| `parser.ts` | Markdown tokens, Run Code/fence adjacency, source locations and closing fences |
|
||||
| `generator.ts` | Deterministic file discovery, syntax compilation without execution, dependency list and browser bundle |
|
||||
| `runtime.ts` | One sequential example frame, script loading, error/readiness observation, teardown and Mocha registration |
|
||||
| `../build-test.js` | Original CLI path, argument validation and write/read-only check |
|
||||
| `../tsconfig.docs.json` | Strict Node/DOM tools check, including the JavaScript shim |
|
||||
|
||||
```sh
|
||||
yarn build:test
|
||||
yarn check:docs-smoke
|
||||
yarn typecheck:docs-tools
|
||||
node --test refactor/scripts/docs-smoke.test.mjs
|
||||
yarn test:browser test/browser/docs-smoke.spec.js
|
||||
```
|
||||
|
||||
`docs/test/examples.json` records stable source file/line IDs, original code and
|
||||
the ordered script list from `docs/test/index.html`. No timestamps enter generated
|
||||
content. Generation validates every selected example before writing either output;
|
||||
`--check` compares both files without modifying them. Parse/syntax errors fail the
|
||||
command with a source location. This is not a transactional multi-file filesystem
|
||||
write if the filesystem itself fails during output.
|
||||
|
||||
The scope stays Chinese JavaScript fences after Run Code markers, excluding
|
||||
`en`, `plugin`, `public` and `.vitepress`. There are 233 current cases in 11 files.
|
||||
CRLF is normalized to LF. New cases must be reviewed and the deliberate count
|
||||
assertion updated. JS syntax compilation does not execute snippets or validate
|
||||
their API semantics. TypeScript/editor declaration generation remains SITE-02.
|
||||
|
||||
The browser bundle registers cases with the existing Mocha BDD page. Each case:
|
||||
|
||||
1. Creates an iframe with its own ArtPlayer container and loads the page's runtime
|
||||
scripts in order, excluding Mocha, Chai and the generated test script itself.
|
||||
2. Evaluates the snippet as a classic script in that frame, awaits its returned
|
||||
promise if any, and waits for the currently created players' `ready` events.
|
||||
3. Observes thrown errors, unhandled rejections and media/player errors until that
|
||||
readiness boundary plus one task turn. A 4500 ms deadline produces failure,
|
||||
never success. Cases without players cover evaluation only.
|
||||
4. Detaches observer listeners, destroys frame players, removes the iframe and
|
||||
restores local/session storage. Cleanup errors are failures and retain an
|
||||
earlier failure in the error list. Concurrent calls are rejected.
|
||||
|
||||
The iframe is an owned browsing context, **not a security sandbox**. It shares
|
||||
the parent origin. Run the test page on an isolated test origin with no concurrent
|
||||
same-origin work: storage restoration does not merge writes from other tabs.
|
||||
Unchanged browser storage is restored after each case, while frame-owned timers
|
||||
and globals disappear when the frame is removed. Arbitrary external resources
|
||||
that a snippet deliberately hands to another window are outside this ownership.
|
||||
|
||||
Readiness is not playback, interaction or complete asynchronous feature acceptance.
|
||||
Delayed actions scheduled after this boundary are not verified; examples involving
|
||||
clicks, seek, SDKs, windows and full lifecycle behavior remain EX-03/package tests.
|
||||
The automated harness tests use actual core/media and three selected unchanged
|
||||
documentation snippets with controlled media. They do not certify all 233 examples
|
||||
or the remote SDK endpoints in the full manual page.
|
||||
|
||||
Windows WebKit has the existing DPIP-MEDIA-01 dimension discrepancy: the same
|
||||
320x180 media can report 640x360 layout dimensions. The harness retains exact raw
|
||||
readings and only these documented pairs; it does not close intrinsic-size or
|
||||
real Safari/device acceptance. See `refactor/site-inventory.md` for broader ownership.
|
||||
|
||||
Dependencies: root development-only `@types/node` 24.10.0 and
|
||||
`@types/markdown-it` 14.1.2 provide strict tools typing. Runtime parsing uses the
|
||||
existing MarkdownIt 14.1.0 and browser bundling uses the existing pinned esbuild.
|
||||
No player package runtime dependency changes.
|
||||
@@ -0,0 +1,41 @@
|
||||
import type { DocumentationExample } from './parser.ts'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { Script } from 'node:vm'
|
||||
import { build } from 'esbuild'
|
||||
import { extractExamples } from './parser.ts'
|
||||
|
||||
export function collectExamples(directory: string, prefix = ''): DocumentationExample[] {
|
||||
return fs.readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name, 'en')).flatMap((entry) => {
|
||||
if (['en', '.vitepress', 'public', 'plugin'].includes(entry.name))
|
||||
return []
|
||||
const relative = prefix ? `${prefix}/${entry.name}` : entry.name
|
||||
const file = path.join(directory, entry.name)
|
||||
if (entry.isDirectory())
|
||||
return collectExamples(file, relative)
|
||||
if (!entry.name.endsWith('.md'))
|
||||
return []
|
||||
const examples = extractExamples(fs.readFileSync(file, 'utf8'), relative)
|
||||
for (const example of examples) {
|
||||
// eslint-disable-next-line no-new -- Compile syntax without executing documentation code.
|
||||
new Script(example.code, { filename: example.id })
|
||||
}
|
||||
return examples
|
||||
})
|
||||
}
|
||||
|
||||
export async function generateDocumentationSmoke(root: string): Promise<Map<string, string>> {
|
||||
const examples = collectExamples(path.join(root, 'packages/artplayer-vitepress/docs'))
|
||||
const html = fs.readFileSync(path.join(root, 'docs/test/index.html'), 'utf8').replace(/<!--[\s\S]*?-->/g, '')
|
||||
const scripts = [...html.matchAll(/<script\b[^>]+\bsrc=["']([^"']+)["']/g)].map(match => match[1]!).filter(url => !['./chai.js', './mocha.js', './test.js'].includes(url))
|
||||
if (!scripts.some(url => /\/artplayer\.js$/.test(url)))
|
||||
throw new Error('Documentation smoke page must load the core')
|
||||
const bundle = await build({ stdin: { contents: `import { registerDocumentationSmoke } from './runtime.ts';\nregisterDocumentationSmoke(${JSON.stringify(examples)}, ${JSON.stringify(scripts)});`, resolveDir: path.join(root, 'scripts/docs-smoke'), sourcefile: 'documentation-smoke-entry.ts', loader: 'ts' }, bundle: true, write: false, platform: 'browser', format: 'iife', target: 'es2020', legalComments: 'none', logLevel: 'silent' })
|
||||
const script = bundle.outputFiles?.[0]?.text
|
||||
if (!script)
|
||||
throw new Error('Documentation smoke build produced no script')
|
||||
return new Map([
|
||||
['docs/test/test.js', `// Generated by yarn build:test. Readiness smoke only; do not edit.\n${script}`],
|
||||
['docs/test/examples.json', `${JSON.stringify({ scope: 'Chinese Run Code JavaScript; excludes en/plugin/public/.vitepress. Waits for created players to become ready, observes errors until that boundary, then destroys the frame. Not complete feature, delayed-callback or interaction acceptance.', examples, scripts }, null, 2)}\n`],
|
||||
])
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import MarkdownIt from 'markdown-it'
|
||||
|
||||
export interface DocumentationExample {
|
||||
id: string
|
||||
file: string
|
||||
line: number
|
||||
code: string
|
||||
}
|
||||
|
||||
const markdown = new MarkdownIt({ html: true })
|
||||
|
||||
export function extractExamples(content: string, file: string): DocumentationExample[] {
|
||||
const normalized = content.replaceAll('\r\n', '\n')
|
||||
const lines = normalized.split('\n')
|
||||
const tokens = markdown.parse(normalized, {})
|
||||
const examples: DocumentationExample[] = []
|
||||
for (const [index, marker] of tokens.entries()) {
|
||||
if (marker.type !== 'html_block' || !/<div\s+class(?:Name)?="run-code"[\s>]/.test(marker.content))
|
||||
continue
|
||||
const line = (marker.map?.[0] ?? 0) + 1
|
||||
const invalid = (message: string) => new Error(`${file}:${line}: ${message}`)
|
||||
const fence = tokens[index + 1]
|
||||
if (!fence || fence.type !== 'fence' || !/^js(?:\{|\s|$)/.test(fence.info))
|
||||
throw invalid('Run Code must be followed by a JavaScript fence')
|
||||
const range = fence.map
|
||||
if (!range)
|
||||
throw invalid('Missing code fence source range')
|
||||
const closing = lines[range[1] - 1]?.trim() || ''
|
||||
if (closing.length < fence.markup.length || [...closing].some(character => character !== fence.markup[0]))
|
||||
throw invalid('Unclosed Run Code fence')
|
||||
const code = fence.content.trim()
|
||||
if (!code)
|
||||
throw invalid('Empty Run Code example')
|
||||
examples.push({ id: `${file}:${range[0] + 2}`, file, line: range[0] + 2, code })
|
||||
}
|
||||
return examples
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
import type Artplayer from '../../packages/artplayer/public/artplayer.ts'
|
||||
import type { DocumentationExample } from './parser.ts'
|
||||
|
||||
interface FrameWindow extends Window {
|
||||
Artplayer?: typeof Artplayer
|
||||
eval: (code: string) => unknown
|
||||
}
|
||||
export interface SmokeResult {
|
||||
id: string
|
||||
instances: number
|
||||
ready: number
|
||||
media: { readyState: number, width: number, height: number, duration: number }[]
|
||||
}
|
||||
|
||||
let running = false
|
||||
|
||||
/** Evaluate one example in an owned browsing context; readiness is not feature acceptance. */
|
||||
export async function runExample(
|
||||
example: DocumentationExample,
|
||||
scripts: string[],
|
||||
timeoutMs = 4500,
|
||||
): Promise<SmokeResult> {
|
||||
if (running)
|
||||
throw new Error('Documentation smoke cases must run sequentially')
|
||||
const savedStorage = [localStorage, sessionStorage].map(storage => ({ storage, entries: Object.entries(storage) }))
|
||||
const iframe = document.createElement('iframe')
|
||||
iframe.dataset.documentationSmoke = example.id
|
||||
iframe.title = `Documentation smoke: ${example.id}`
|
||||
iframe.style.cssText = 'width:640px;height:360px;border:0;display:block'
|
||||
const context: { frame: FrameWindow | null } = { frame: null }
|
||||
let active = true
|
||||
const disposers: (() => void)[] = []
|
||||
let fail: (error: unknown) => void = () => {}
|
||||
const failure = new Promise<never>((_resolve, reject) => {
|
||||
fail = reject
|
||||
})
|
||||
const deadline = window.setTimeout(
|
||||
() => fail(new Error(`${example.id}: readiness timeout (${timeoutMs}ms)`)),
|
||||
timeoutMs,
|
||||
)
|
||||
running = true
|
||||
const ensureActive = () => {
|
||||
if (!active)
|
||||
throw new Error('Documentation context was disposed')
|
||||
}
|
||||
function loaded(target: EventTarget, action: () => void, description: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const done = () => {
|
||||
cleanup()
|
||||
resolve()
|
||||
}
|
||||
const error = () => {
|
||||
cleanup()
|
||||
reject(new Error(`Failed to load ${description}`))
|
||||
}
|
||||
function cleanup() {
|
||||
target.removeEventListener('load', done)
|
||||
target.removeEventListener('error', error)
|
||||
}
|
||||
target.addEventListener('load', done, { once: true })
|
||||
target.addEventListener('error', error, { once: true })
|
||||
disposers.push(() => {
|
||||
cleanup()
|
||||
reject(new Error(`Cancelled ${description}`))
|
||||
})
|
||||
action()
|
||||
})
|
||||
}
|
||||
const work = async (): Promise<SmokeResult> => {
|
||||
await loaded(
|
||||
iframe,
|
||||
() => {
|
||||
iframe.srcdoc
|
||||
= '<!doctype html><html><body style="margin:0"><div class="artplayer-app" style="width:640px;height:360px"></div></body></html>'
|
||||
document.body.appendChild(iframe)
|
||||
},
|
||||
'documentation frame',
|
||||
)
|
||||
ensureActive()
|
||||
const frame = iframe.contentWindow as FrameWindow | null
|
||||
context.frame = frame
|
||||
if (!frame)
|
||||
throw new Error('Documentation frame is unavailable')
|
||||
const onError = (event: ErrorEvent) => {
|
||||
event.preventDefault()
|
||||
fail(new Error(`${example.id}: ${event.message}`))
|
||||
}
|
||||
const onRejection = (event: PromiseRejectionEvent) => {
|
||||
event.preventDefault()
|
||||
fail(new Error(`${example.id}: ${String(event.reason)}`))
|
||||
}
|
||||
frame.addEventListener('error', onError)
|
||||
frame.addEventListener('unhandledrejection', onRejection)
|
||||
disposers.push(() => {
|
||||
frame?.removeEventListener('error', onError)
|
||||
frame?.removeEventListener('unhandledrejection', onRejection)
|
||||
})
|
||||
for (const url of scripts) {
|
||||
const script = frame.document.createElement('script')
|
||||
script.src = new URL(url, location.href).href
|
||||
await loaded(script, () => frame.document.head.appendChild(script), script.src)
|
||||
ensureActive()
|
||||
}
|
||||
if (!frame.Artplayer)
|
||||
throw new Error('Artplayer was not loaded in the documentation frame')
|
||||
// Preserve classic-script globals while keeping timers, DOM and listeners inside this frame.
|
||||
await frame.eval(`${example.code}\n//# sourceURL=artplayer-doc-example-${encodeURIComponent(example.id)}.js`)
|
||||
ensureActive()
|
||||
const instances = frame.Artplayer.instances.slice()
|
||||
await Promise.all(
|
||||
instances.map(
|
||||
instance =>
|
||||
new Promise<void>((resolve, reject) => {
|
||||
if (instance.isReady) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
const ready = () => {
|
||||
cleanup()
|
||||
resolve()
|
||||
}
|
||||
const error = () => {
|
||||
cleanup()
|
||||
reject(new Error(`${example.id}: player readiness failed`))
|
||||
}
|
||||
function cleanup() {
|
||||
instance.off('ready', ready)
|
||||
instance.off('error', error)
|
||||
instance.off('video:error', error)
|
||||
}
|
||||
instance.on('ready', ready)
|
||||
instance.on('error', error)
|
||||
instance.on('video:error', error)
|
||||
disposers.push(() => {
|
||||
cleanup()
|
||||
reject(new Error('Cancelled player readiness'))
|
||||
})
|
||||
}),
|
||||
),
|
||||
)
|
||||
// A task boundary delivers unhandled promise rejections from evaluation/ready callbacks.
|
||||
await new Promise<void>((resolve) => {
|
||||
const timer = window.setTimeout(resolve, 0)
|
||||
disposers.push(() => window.clearTimeout(timer))
|
||||
})
|
||||
ensureActive()
|
||||
return {
|
||||
id: example.id,
|
||||
instances: instances.length,
|
||||
ready: instances.filter(instance => instance.isReady).length,
|
||||
media: instances.map(instance => ({
|
||||
readyState: instance.video.readyState,
|
||||
width: instance.video.videoWidth,
|
||||
height: instance.video.videoHeight,
|
||||
duration: instance.video.duration,
|
||||
})),
|
||||
}
|
||||
}
|
||||
let result: SmokeResult | undefined
|
||||
let problem: unknown
|
||||
let failed = false
|
||||
try {
|
||||
result = await Promise.race([work(), failure])
|
||||
}
|
||||
catch (error) {
|
||||
failed = true
|
||||
problem = error
|
||||
}
|
||||
finally {
|
||||
active = false
|
||||
window.clearTimeout(deadline)
|
||||
for (const dispose of disposers) dispose()
|
||||
const cleanupErrors: unknown[] = []
|
||||
for (const instance of context.frame?.Artplayer?.instances.slice() || []) {
|
||||
try {
|
||||
instance.destroy(true)
|
||||
}
|
||||
catch (error) {
|
||||
cleanupErrors.push(error)
|
||||
}
|
||||
}
|
||||
iframe.remove()
|
||||
for (const { storage, entries } of savedStorage) {
|
||||
try {
|
||||
storage.clear()
|
||||
for (const [key, value] of entries) storage.setItem(key, value)
|
||||
}
|
||||
catch (error) {
|
||||
cleanupErrors.push(error)
|
||||
}
|
||||
}
|
||||
running = false
|
||||
if (cleanupErrors.length) {
|
||||
const errors = failed ? [problem, ...cleanupErrors] : cleanupErrors
|
||||
problem = Object.assign(new Error(`Documentation cleanup failed: ${errors.map(String).join('; ')}`), { errors })
|
||||
failed = true
|
||||
}
|
||||
}
|
||||
if (failed)
|
||||
throw problem
|
||||
if (!result)
|
||||
throw new Error('Documentation smoke did not produce a result')
|
||||
return result
|
||||
}
|
||||
|
||||
interface MochaHost {
|
||||
describe: (name: string, callback: (this: { timeout: (ms: number) => void }) => void) => void
|
||||
it: (name: string, callback: () => Promise<void>) => void
|
||||
}
|
||||
|
||||
export function registerDocumentationSmoke(examples: DocumentationExample[], scripts: string[]): void {
|
||||
const host = globalThis as unknown as MochaHost
|
||||
if (typeof host.describe !== 'function' || typeof host.it !== 'function')
|
||||
throw new Error('Documentation smoke requires the existing Mocha BDD page')
|
||||
host.describe('Documentation readiness smoke (not feature acceptance)', function () {
|
||||
this.timeout(6000)
|
||||
for (const example of examples) {
|
||||
host.it(example.id, async () => {
|
||||
await runExample(example, scripts)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"lib": ["ES2021", "DOM", "DOM.Iterable"],
|
||||
"types": ["node"],
|
||||
"allowImportingTsExtensions": true,
|
||||
"checkJs": true
|
||||
},
|
||||
"include": ["docs-smoke/**/*.ts", "build-test.js"]
|
||||
}
|
||||
@@ -2,6 +2,15 @@
|
||||
|
||||
Use the pinned Node/Yarn toolchain from `../refactor/toolchain-setup.md`.
|
||||
|
||||
`yarn build:test` generates deterministic documentation readiness smoke and its
|
||||
source manifest. `yarn check:docs-smoke` is read-only, and
|
||||
`yarn typecheck:docs-tools` checks the TS modules and legacy JS command shim.
|
||||
`refactor/scripts/docs-smoke.test.mjs` covers the historical malformed-parser loop,
|
||||
all 233 current source snippets, stable generation and CLI drift/error handling.
|
||||
`test/browser/docs-smoke.spec.js` uses actual core/media in three engines for
|
||||
readiness, failures, frame cleanup, storage restoration and repeated execution.
|
||||
See `scripts/docs-smoke/README.md` for the explicit limits of readiness smoke.
|
||||
|
||||
`yarn test:vue-consumer` checks the actual Vue example with packed core/Danmuku/
|
||||
Document PiP in an outside-workspace install. It also installs its pinned Vue
|
||||
compiler there, checks all declaration paths for isolation, and runs development
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# Browser regression entry
|
||||
|
||||
`docs-smoke.spec.js` verifies the documentation runner in three engines with
|
||||
actual core/media, controlled failures and three selected generated snippets.
|
||||
It checks pending readiness beyond the former 100ms timer, returned/unhandled
|
||||
rejections, ready/destroy errors, missing scripts, deadlines, frame/global/timer
|
||||
cleanup and storage restoration. It does not execute all 233 documentation cases
|
||||
or prove remote SDKs/features. DPIP-MEDIA-01's exact WebKit intrinsic/layout size
|
||||
pairs remain explicit observations; this suite does not close that risk.
|
||||
|
||||
`yarn test:browser test/browser/ads.spec.js` tests actual Ads 1.0.6 and candidate code
|
||||
with published 4.5.5/5.4.1 and candidate cores using local image/video media. It covers actual
|
||||
decoded pixels, countdown, skipping, content restoration, 404 and source replacement.
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
import fs from 'node:fs'
|
||||
import { build } from 'esbuild'
|
||||
import { expect, test } from './fixtures.js'
|
||||
|
||||
const compiled = await build({
|
||||
entryPoints: ['scripts/docs-smoke/runtime.ts'],
|
||||
bundle: true,
|
||||
write: false,
|
||||
format: 'iife',
|
||||
globalName: 'DocumentationSmoke',
|
||||
target: 'es2020',
|
||||
logLevel: 'silent',
|
||||
})
|
||||
const runtime = compiled.outputFiles[0].text
|
||||
const core = '/candidate/artplayer.js'
|
||||
const example = code => ({ id: 'fixture.md:1', file: 'fixture.md', line: 1, code })
|
||||
const player = 'var art = new Artplayer({ container: \'.artplayer-app\', url: \'/test/pattern.mp4\', muted: true });'
|
||||
const documented = JSON.parse(fs.readFileSync('docs/test/examples.json', 'utf8'))
|
||||
.examples
|
||||
.filter(item => item.file === 'start/option.md')
|
||||
.slice(0, 3)
|
||||
|
||||
function expectDimensions(media, browserName) {
|
||||
// DPIP-MEDIA-01: Windows WebKit can report either intrinsic or styled dimensions.
|
||||
// Keep exact observed pairs; readiness smoke does not close intrinsic-size compatibility.
|
||||
const sizes = browserName === 'webkit' ? [[320, 180], [640, 360]] : [[320, 180]]
|
||||
expect(sizes).toContainEqual([media.width, media.height])
|
||||
}
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/test/player.html')
|
||||
await page.addScriptTag({ content: runtime })
|
||||
})
|
||||
|
||||
test('readiness smoke uses actual core/media and releases each frame without touching a parent player', async ({
|
||||
page,
|
||||
browserName,
|
||||
}, testInfo) => {
|
||||
await page.evaluate(() => {
|
||||
window.createPlayer('/test/pattern.mp4')
|
||||
localStorage.setItem('smoke-existing', 'preserved')
|
||||
sessionStorage.setItem('smoke-session', 'preserved')
|
||||
})
|
||||
for (let index = 0; index < 3; index++) {
|
||||
const result = await page.evaluate(
|
||||
async ({ item, core }) => {
|
||||
return window.DocumentationSmoke.runExample(item, [core])
|
||||
},
|
||||
{
|
||||
item: example(
|
||||
`${player}\nlocalStorage.clear(); sessionStorage.clear(); localStorage.setItem('smoke-leak', 'bad');`,
|
||||
),
|
||||
core,
|
||||
},
|
||||
)
|
||||
expect(result.instances).toBe(1)
|
||||
expect(result.ready).toBe(1)
|
||||
expectDimensions(result.media[0], browserName)
|
||||
expect(result.media[0].duration).toBeGreaterThan(0)
|
||||
await testInfo.attach(`readiness-${index}`, { body: JSON.stringify(result), contentType: 'application/json' })
|
||||
expect(await page.locator('iframe[data-documentation-smoke]').count()).toBe(0)
|
||||
}
|
||||
expect(
|
||||
await page.evaluate(() => ({
|
||||
players: window.Artplayer.instances.length,
|
||||
kept: localStorage.getItem('smoke-existing'),
|
||||
session: sessionStorage.getItem('smoke-session'),
|
||||
leak: localStorage.getItem('smoke-leak'),
|
||||
})),
|
||||
).toEqual({ players: 1, kept: 'preserved', session: 'preserved', leak: null })
|
||||
await page.evaluate(() => window.art.destroy())
|
||||
})
|
||||
|
||||
test('smoke remains pending beyond 100ms until real media becomes ready', async ({ page }) => {
|
||||
let release
|
||||
const gate = new Promise((resolve) => {
|
||||
release = resolve
|
||||
})
|
||||
await page.route('**/test/pattern.mp4', async (route) => {
|
||||
await gate
|
||||
await route.continue()
|
||||
})
|
||||
try {
|
||||
await page.evaluate(
|
||||
({ item, core }) => {
|
||||
window.smokeState = { done: false }
|
||||
window.smokePromise = window.DocumentationSmoke.runExample(item, [core]).then((value) => {
|
||||
window.smokeState = { done: true, value }
|
||||
})
|
||||
window.oldCompletion = new Promise(resolve => setTimeout(resolve, 100))
|
||||
},
|
||||
{ item: example(player), core },
|
||||
)
|
||||
await page.evaluate(() => window.oldCompletion)
|
||||
expect(await page.evaluate(() => window.smokeState.done)).toBe(false)
|
||||
release()
|
||||
await page.evaluate(() => window.smokePromise)
|
||||
expect(await page.evaluate(() => window.smokeState.value.ready)).toBe(1)
|
||||
}
|
||||
finally {
|
||||
release()
|
||||
}
|
||||
})
|
||||
|
||||
test('generated real documentation cases run with controlled native media', async ({ page, browserName }) => {
|
||||
await page.route('**/assets/sample/video.mp4', route =>
|
||||
route.fulfill({ contentType: 'video/mp4', body: fs.readFileSync('test/browser/media/pattern.mp4') }))
|
||||
for (const item of documented) {
|
||||
const result = await page.evaluate(({ item, core }) => window.DocumentationSmoke.runExample(item, [core]), {
|
||||
item,
|
||||
core,
|
||||
})
|
||||
expect(result.ready).toBe(1)
|
||||
expectDimensions(result.media[0], browserName)
|
||||
}
|
||||
})
|
||||
|
||||
test('parallel calls are rejected while a controlled case is still active', async ({ page }) => {
|
||||
const state = await page.evaluate(
|
||||
async ({ item, core }) => {
|
||||
const first = window.DocumentationSmoke.runExample(item, [core])
|
||||
let rejected
|
||||
try {
|
||||
await window.DocumentationSmoke.runExample(item, [core])
|
||||
}
|
||||
catch (error) {
|
||||
rejected = String(error)
|
||||
}
|
||||
await first
|
||||
return rejected
|
||||
},
|
||||
{ item: example(player), core },
|
||||
)
|
||||
expect(state).toContain('must run sequentially')
|
||||
})
|
||||
|
||||
for (const [name, code, message] of [
|
||||
['synchronous error', `${player}\nthrow new Error('sync fixture');`, 'sync fixture'],
|
||||
['returned rejection', `${player}\nPromise.reject(new Error('returned fixture'));`, 'returned fixture'],
|
||||
['unhandled rejection', `${player}\nPromise.reject(new Error('unhandled fixture')); void 0;`, 'unhandled fixture'],
|
||||
[
|
||||
'readiness callback error',
|
||||
`${player}\nart.on('ready', () => { throw new Error('ready fixture'); });`,
|
||||
'ready fixture',
|
||||
],
|
||||
['cleanup error', `${player}\nart.on('destroy', () => { throw new Error('destroy fixture'); });`, 'destroy fixture'],
|
||||
]) {
|
||||
test(`smoke reports ${name} and removes its frame`, async ({ page }) => {
|
||||
const result = await page.evaluate(
|
||||
async ({ item, core }) => {
|
||||
try {
|
||||
await window.DocumentationSmoke.runExample(item, [core])
|
||||
return 'unexpected success'
|
||||
}
|
||||
catch (error) {
|
||||
return String(error)
|
||||
}
|
||||
},
|
||||
{ item: example(code), core },
|
||||
)
|
||||
expect(result).toContain(message)
|
||||
expect(await page.locator('iframe[data-documentation-smoke]').count()).toBe(0)
|
||||
expect(await page.evaluate(() => window.Artplayer.instances.length)).toBe(0)
|
||||
})
|
||||
}
|
||||
|
||||
test('missing script and never-ready examples fail instead of becoming green after a delay', async ({ page }) => {
|
||||
await page.route('**/missing-smoke.js', route => route.fulfill({ status: 404, body: 'missing fixture' }))
|
||||
const missing = await page.evaluate(async (item) => {
|
||||
try {
|
||||
await window.DocumentationSmoke.runExample(item, ['/missing-smoke.js'])
|
||||
return 'unexpected success'
|
||||
}
|
||||
catch (error) {
|
||||
return String(error)
|
||||
}
|
||||
}, example('void 0;'))
|
||||
expect(missing).toContain('Failed to load')
|
||||
const timeout = await page.evaluate(
|
||||
async ({ item, core }) => {
|
||||
try {
|
||||
await window.DocumentationSmoke.runExample(item, [core], 1000)
|
||||
return 'unexpected success'
|
||||
}
|
||||
catch (error) {
|
||||
return String(error)
|
||||
}
|
||||
},
|
||||
{ item: example('new Artplayer({ container: \'.artplayer-app\', url: \'\' });'), core },
|
||||
)
|
||||
expect(timeout).toContain('readiness timeout')
|
||||
expect(await page.locator('iframe[data-documentation-smoke]').count()).toBe(0)
|
||||
})
|
||||
|
||||
test('frame-owned timers and globals do not survive into the next example', async ({ page }) => {
|
||||
await page.evaluate(
|
||||
async ({ first, second, core }) => {
|
||||
await window.DocumentationSmoke.runExample(first, [core])
|
||||
await window.DocumentationSmoke.runExample(second, [core])
|
||||
},
|
||||
{
|
||||
first: example(
|
||||
`${player}\nwindow.smokeLeak = 1; setTimeout(() => { throw new Error('late frame callback'); }, 700);`,
|
||||
),
|
||||
second: example(
|
||||
`${player}\nif (window.smokeLeak) throw new Error('global leaked');\nnew Promise(resolve => setTimeout(resolve, 900));`,
|
||||
),
|
||||
core,
|
||||
},
|
||||
)
|
||||
expect(await page.locator('iframe[data-documentation-smoke]').count()).toBe(0)
|
||||
})
|
||||
@@ -1970,6 +1970,14 @@
|
||||
resolved "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz"
|
||||
integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==
|
||||
|
||||
"@types/markdown-it@14.1.2", "@types/markdown-it@^14.1.2":
|
||||
version "14.1.2"
|
||||
resolved "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz"
|
||||
integrity sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==
|
||||
dependencies:
|
||||
"@types/linkify-it" "^5"
|
||||
"@types/mdurl" "^2"
|
||||
|
||||
"@types/markdown-it@^12.2.3":
|
||||
version "12.2.3"
|
||||
resolved "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz"
|
||||
@@ -1978,14 +1986,6 @@
|
||||
"@types/linkify-it" "*"
|
||||
"@types/mdurl" "*"
|
||||
|
||||
"@types/markdown-it@^14.1.2":
|
||||
version "14.1.2"
|
||||
resolved "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz"
|
||||
integrity sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==
|
||||
dependencies:
|
||||
"@types/linkify-it" "^5"
|
||||
"@types/mdurl" "^2"
|
||||
|
||||
"@types/mdast@^4.0.0":
|
||||
version "4.0.4"
|
||||
resolved "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz"
|
||||
@@ -2013,6 +2013,13 @@
|
||||
resolved "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz"
|
||||
integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==
|
||||
|
||||
"@types/node@24.10.0":
|
||||
version "24.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-24.10.0.tgz#6b79086b0dfc54e775a34ba8114dcc4e0221f31f"
|
||||
integrity sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==
|
||||
dependencies:
|
||||
undici-types "~7.16.0"
|
||||
|
||||
"@types/normalize-package-data@^2.4.0":
|
||||
version "2.4.4"
|
||||
resolved "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz"
|
||||
@@ -8227,6 +8234,11 @@ uhyphen@^0.2.0:
|
||||
resolved "https://registry.yarnpkg.com/uhyphen/-/uhyphen-0.2.0.tgz#8fdf0623314486e020a3c00ee5cc7a12fe722b81"
|
||||
integrity sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==
|
||||
|
||||
undici-types@~7.16.0:
|
||||
version "7.16.0"
|
||||
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.16.0.tgz#ffccdff36aea4884cbfce9a750a0580224f58a46"
|
||||
integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==
|
||||
|
||||
unicorn-magic@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/unicorn-magic/-/unicorn-magic-0.4.0.tgz#78c6a090fd6d07abd2468b83b385603e00dfdb24"
|
||||
|
||||
Reference in new issue
Block a user