mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-13 07:46:17 -08:00
test(chapter): PKG-CHAPTER-02 cover behavior and optional factory types
This commit is contained in:
1 parent
701749520b
commit
12b047f9a6
17 files changed
+624
-15
No files matched your search
+1
-1
@@ -13,7 +13,7 @@ interface Result {
|
||||
update: (option: Option) => void
|
||||
}
|
||||
|
||||
declare const artplayerPluginChapter: (option: Option) => (art: Artplayer) => Result
|
||||
declare const artplayerPluginChapter: (option?: Option) => (art: Artplayer) => Result
|
||||
|
||||
export default artplayerPluginChapter
|
||||
|
||||
|
||||
@@ -6,6 +6,36 @@ chapter plugin for ArtPlayer
|
||||
|
||||
[https://artplayer.org](https://artplayer.org/?libs=./uncompiled/artplayer-plugin-chapter/index.js&example=chapter)
|
||||
|
||||
## API
|
||||
|
||||
```js
|
||||
import artplayerPluginChapter from 'artplayer-plugin-chapter'
|
||||
|
||||
const plugin = artplayerPluginChapter({
|
||||
chapters: [{ start: 0, end: Infinity, title: 'Introduction' }],
|
||||
})
|
||||
// Pass plugin to Artplayer's plugins array. Options may also be omitted.
|
||||
```
|
||||
|
||||
The registered name is `artplayerPluginChapter`. After metadata loads, use
|
||||
`art.plugins.artplayerPluginChapter.update({ chapters })` to replace the chapters, or
|
||||
`update({})` to clear them. Update is synchronous and requires an options object.
|
||||
Chapter times are seconds; ranges must not overlap. `Infinity` as an end value uses the current duration.
|
||||
|
||||
The existing implementation sorts and fills the supplied array in place and replaces its Infinity end
|
||||
values. Pass a fresh array when you need to retain the original configuration. After switching media,
|
||||
call update with chapters appropriate to the new duration; initialization listens to metadata once.
|
||||
|
||||
## Maintenance
|
||||
|
||||
The implementation is currently in `src/index.js`, styles in `src/style.less`, and public declarations
|
||||
in `types/artplayer-plugin-chapter.d.ts`. Its migration contracts and known defects are recorded in
|
||||
[the chapter contract](../../refactor/baselines/chapter-contract.md).
|
||||
|
||||
Run `yarn test:browser chapter.spec.js` for published/current plugin behavior on the published core.
|
||||
Run `yarn typecheck` and `yarn test:baseline` for strict consumer checks, including optional factory
|
||||
arguments and invalid data. `yarn build:ts` regenerates the online editor declarations.
|
||||
|
||||
## License
|
||||
|
||||
MIT © Harvey Zhao
|
||||
@@ -15,6 +15,6 @@ interface Result {
|
||||
update: (option: Option) => void
|
||||
}
|
||||
|
||||
declare const artplayerPluginChapter: (option: Option) => (art: Artplayer) => Result
|
||||
declare const artplayerPluginChapter: (option?: Option) => (art: Artplayer) => Result
|
||||
|
||||
export default artplayerPluginChapter
|
||||
@@ -0,0 +1,329 @@
|
||||
{
|
||||
"task": "PKG-CHAPTER-02",
|
||||
"capturedAt": "2026-09-10T14:39:42.386Z",
|
||||
"node": "24.21.0",
|
||||
"platform": "win32",
|
||||
"sourceHashAlgorithm": "sha256-lf",
|
||||
"hashes": {
|
||||
"packages/artplayer-plugin-chapter/src/index.js": "fcd732e4257597a8cbb192fa4d715488b344df361a3a5b7d0e7c70f7554a73e6",
|
||||
"packages/artplayer-plugin-chapter/types/artplayer-plugin-chapter.d.ts": "15856abc93ef140cf0c589c30f80335e5395b30b4604bc040e7e241281882127",
|
||||
"docs/assets/ts/artplayer-plugin-chapter.d.ts": "2931630e9fca67ddf3b3b4018b7cce8df880c6528a4d34140bbacc15abab54d8",
|
||||
"test/browser/chapter.spec.js": "a13aca482cf25eea4c240782262c597192781b763425d9da233275bd1d02c3c2",
|
||||
"test/browser/player.html": "4037c6eadc5da176f9dd67b2b517acb06c8cb2246c841316df26c660e55584b4",
|
||||
"test/browser/playback.spec.js": "5cc5df6f3968f3e834a6b2bc2c76c3c1c4ec5e7bda5ee3e99aa7394d24ac7e18",
|
||||
"test/types/chapter-options.ts": "8ae16ff704befdcf5303012d15d108bad4aa7dab4d62da9c02cff4776680fe60",
|
||||
"refactor/scripts/types.test.mjs": "17a4c9a2c2ae7cebe9c3007aad090f01371d8c7f65feb98662db08fc7cf619d8"
|
||||
},
|
||||
"resources": {
|
||||
"/candidate/artplayer.js": {
|
||||
"source": {
|
||||
"kind": "workspace-build",
|
||||
"entry": "packages\\artplayer\\src\\index.js"
|
||||
},
|
||||
"sha256": "c6f564431e03a198c81fc026af75ead9f7c624e5c7f2f23cba5ce984ec045290",
|
||||
"bytes": 220281
|
||||
},
|
||||
"/compiled/artplayer.js": {
|
||||
"source": {
|
||||
"kind": "workspace-build",
|
||||
"entry": "packages\\artplayer\\src\\index.js"
|
||||
},
|
||||
"sha256": "c6f564431e03a198c81fc026af75ead9f7c624e5c7f2f23cba5ce984ec045290",
|
||||
"bytes": 220281,
|
||||
"aliasOf": "/candidate/artplayer.js"
|
||||
},
|
||||
"/uncompiled/artplayer/index.js": {
|
||||
"source": {
|
||||
"kind": "workspace-build",
|
||||
"entry": "packages\\artplayer\\src\\index.js"
|
||||
},
|
||||
"sha256": "c6f564431e03a198c81fc026af75ead9f7c624e5c7f2f23cba5ce984ec045290",
|
||||
"bytes": 220281,
|
||||
"aliasOf": "/candidate/artplayer.js"
|
||||
},
|
||||
"/candidate/artplayer-plugin-chapter.js": {
|
||||
"source": {
|
||||
"kind": "workspace-build",
|
||||
"entry": "packages\\artplayer-plugin-chapter\\src\\index.js"
|
||||
},
|
||||
"sha256": "833b81bf3334443531ed1822e28a38a241484056c6dbbdd50cd1ec093303ab94",
|
||||
"bytes": 8178
|
||||
},
|
||||
"/compiled/artplayer-plugin-chapter.js": {
|
||||
"source": {
|
||||
"kind": "workspace-build",
|
||||
"entry": "packages\\artplayer-plugin-chapter\\src\\index.js"
|
||||
},
|
||||
"sha256": "833b81bf3334443531ed1822e28a38a241484056c6dbbdd50cd1ec093303ab94",
|
||||
"bytes": 8178,
|
||||
"aliasOf": "/candidate/artplayer-plugin-chapter.js"
|
||||
},
|
||||
"/uncompiled/artplayer-plugin-chapter/index.js": {
|
||||
"source": {
|
||||
"kind": "workspace-build",
|
||||
"entry": "packages\\artplayer-plugin-chapter\\src\\index.js"
|
||||
},
|
||||
"sha256": "833b81bf3334443531ed1822e28a38a241484056c6dbbdd50cd1ec093303ab94",
|
||||
"bytes": 8178,
|
||||
"aliasOf": "/candidate/artplayer-plugin-chapter.js"
|
||||
},
|
||||
"/published/artplayer.js": {
|
||||
"source": {
|
||||
"kind": "npm-release",
|
||||
"version": "5.4.0",
|
||||
"integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==",
|
||||
"member": "package/dist/artplayer.js"
|
||||
},
|
||||
"sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f",
|
||||
"bytes": 134037
|
||||
},
|
||||
"/published/artplayer-plugin-chapter.js": {
|
||||
"source": {
|
||||
"kind": "npm-release",
|
||||
"version": "1.1.0",
|
||||
"integrity": "sha512-ji66Lr2K2S+OjFMuMby3yq+pefWlOC51Sbpop/DIr2N6w3F0smrF+YZbshN1Dy8/Tx1TYpT7N6WkkZWy5Jix1w==",
|
||||
"member": "package/dist/artplayer-plugin-chapter.js"
|
||||
},
|
||||
"sha256": "c2aaee11bf1db765433b01d9f8dd3978e41fa4d39655db00ebd03fc0be3e35e0",
|
||||
"bytes": 4973
|
||||
},
|
||||
"/test/pattern.mp4": {
|
||||
"source": {
|
||||
"kind": "generated-media",
|
||||
"file": "test/browser/media/pattern.mp4"
|
||||
},
|
||||
"sha256": "0ad1d7ad286aec6e0fa9e27fc1e487e7277df2e603e30040fb942365763d0233",
|
||||
"bytes": 251538
|
||||
},
|
||||
"/test/pattern.webm": {
|
||||
"source": {
|
||||
"kind": "generated-media",
|
||||
"file": "test/browser/media/pattern.webm"
|
||||
},
|
||||
"sha256": "9d3c2b3dec9581a599de2d6732992ef7846790e0d1368e6e131bd339b39e984f",
|
||||
"bytes": 162073
|
||||
},
|
||||
"/assets/sample/video.mp4": {
|
||||
"source": {
|
||||
"kind": "docs-media",
|
||||
"file": "docs/assets/sample/video.mp4"
|
||||
},
|
||||
"sha256": "30a2de667ba267b6bc605edcee1c4bd728c048ed76f500ea0e73d1f1a6f8f980",
|
||||
"bytes": 5818706
|
||||
}
|
||||
},
|
||||
"fullBrowserSuite": {
|
||||
"startTime": "2026-09-10T14:39:42.386Z",
|
||||
"duration": 21337.515,
|
||||
"expected": 39,
|
||||
"skipped": 0,
|
||||
"unexpected": 0,
|
||||
"flaky": 0
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"scope": "chapter.spec.js / published-only observations: blank title, NaN and once-only metadata are historical defects",
|
||||
"project": "chromium",
|
||||
"status": "passed",
|
||||
"browser": "153.0.8010.12",
|
||||
"errors": [],
|
||||
"observation": {
|
||||
"staleTitle": "Previous",
|
||||
"endAfterSourceChange": 8,
|
||||
"acceptsNaN": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "chapter.spec.js / published-only observations: blank title, NaN and once-only metadata are historical defects",
|
||||
"project": "firefox",
|
||||
"status": "passed",
|
||||
"browser": "155.0",
|
||||
"errors": [],
|
||||
"observation": {
|
||||
"staleTitle": "Previous",
|
||||
"endAfterSourceChange": 8,
|
||||
"acceptsNaN": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "chapter.spec.js / published-only observations: blank title, NaN and once-only metadata are historical defects",
|
||||
"project": "webkit",
|
||||
"status": "passed",
|
||||
"browser": "26.6",
|
||||
"errors": [],
|
||||
"observation": {
|
||||
"staleTitle": "Previous",
|
||||
"endAfterSourceChange": 8,
|
||||
"acceptsNaN": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "chapter.spec.js / published chapter on published core / factory, registration, input normalization and safe title text",
|
||||
"project": "chromium",
|
||||
"status": "passed",
|
||||
"browser": "153.0.8010.12",
|
||||
"errors": []
|
||||
},
|
||||
{
|
||||
"scope": "chapter.spec.js / published chapter on published core / progress bars, real hover, title edges and boundary seek",
|
||||
"project": "chromium",
|
||||
"status": "passed",
|
||||
"browser": "153.0.8010.12",
|
||||
"errors": []
|
||||
},
|
||||
{
|
||||
"scope": "chapter.spec.js / published chapter on published core / sync update, Infinity, object identity, empty lists and invalid data",
|
||||
"project": "chromium",
|
||||
"status": "passed",
|
||||
"browser": "153.0.8010.12",
|
||||
"errors": []
|
||||
},
|
||||
{
|
||||
"scope": "chapter.spec.js / published chapter on published core / explicit source update and core-owned destruction",
|
||||
"project": "chromium",
|
||||
"status": "passed",
|
||||
"browser": "153.0.8010.12",
|
||||
"errors": []
|
||||
},
|
||||
{
|
||||
"scope": "chapter.spec.js / published chapter on published core / factory, registration, input normalization and safe title text",
|
||||
"project": "firefox",
|
||||
"status": "passed",
|
||||
"browser": "155.0",
|
||||
"errors": []
|
||||
},
|
||||
{
|
||||
"scope": "chapter.spec.js / published chapter on published core / progress bars, real hover, title edges and boundary seek",
|
||||
"project": "firefox",
|
||||
"status": "passed",
|
||||
"browser": "155.0",
|
||||
"errors": []
|
||||
},
|
||||
{
|
||||
"scope": "chapter.spec.js / published chapter on published core / sync update, Infinity, object identity, empty lists and invalid data",
|
||||
"project": "firefox",
|
||||
"status": "passed",
|
||||
"browser": "155.0",
|
||||
"errors": []
|
||||
},
|
||||
{
|
||||
"scope": "chapter.spec.js / published chapter on published core / explicit source update and core-owned destruction",
|
||||
"project": "firefox",
|
||||
"status": "passed",
|
||||
"browser": "155.0",
|
||||
"errors": []
|
||||
},
|
||||
{
|
||||
"scope": "chapter.spec.js / published chapter on published core / factory, registration, input normalization and safe title text",
|
||||
"project": "webkit",
|
||||
"status": "passed",
|
||||
"browser": "26.6",
|
||||
"errors": []
|
||||
},
|
||||
{
|
||||
"scope": "chapter.spec.js / published chapter on published core / progress bars, real hover, title edges and boundary seek",
|
||||
"project": "webkit",
|
||||
"status": "passed",
|
||||
"browser": "26.6",
|
||||
"errors": []
|
||||
},
|
||||
{
|
||||
"scope": "chapter.spec.js / published chapter on published core / sync update, Infinity, object identity, empty lists and invalid data",
|
||||
"project": "webkit",
|
||||
"status": "passed",
|
||||
"browser": "26.6",
|
||||
"errors": []
|
||||
},
|
||||
{
|
||||
"scope": "chapter.spec.js / published chapter on published core / explicit source update and core-owned destruction",
|
||||
"project": "webkit",
|
||||
"status": "passed",
|
||||
"browser": "26.6",
|
||||
"errors": []
|
||||
},
|
||||
{
|
||||
"scope": "chapter.spec.js / candidate chapter on published core / factory, registration, input normalization and safe title text",
|
||||
"project": "chromium",
|
||||
"status": "passed",
|
||||
"browser": "153.0.8010.12",
|
||||
"errors": []
|
||||
},
|
||||
{
|
||||
"scope": "chapter.spec.js / candidate chapter on published core / progress bars, real hover, title edges and boundary seek",
|
||||
"project": "chromium",
|
||||
"status": "passed",
|
||||
"browser": "153.0.8010.12",
|
||||
"errors": []
|
||||
},
|
||||
{
|
||||
"scope": "chapter.spec.js / candidate chapter on published core / sync update, Infinity, object identity, empty lists and invalid data",
|
||||
"project": "chromium",
|
||||
"status": "passed",
|
||||
"browser": "153.0.8010.12",
|
||||
"errors": []
|
||||
},
|
||||
{
|
||||
"scope": "chapter.spec.js / candidate chapter on published core / explicit source update and core-owned destruction",
|
||||
"project": "chromium",
|
||||
"status": "passed",
|
||||
"browser": "153.0.8010.12",
|
||||
"errors": []
|
||||
},
|
||||
{
|
||||
"scope": "chapter.spec.js / candidate chapter on published core / factory, registration, input normalization and safe title text",
|
||||
"project": "firefox",
|
||||
"status": "passed",
|
||||
"browser": "155.0",
|
||||
"errors": []
|
||||
},
|
||||
{
|
||||
"scope": "chapter.spec.js / candidate chapter on published core / progress bars, real hover, title edges and boundary seek",
|
||||
"project": "firefox",
|
||||
"status": "passed",
|
||||
"browser": "155.0",
|
||||
"errors": []
|
||||
},
|
||||
{
|
||||
"scope": "chapter.spec.js / candidate chapter on published core / sync update, Infinity, object identity, empty lists and invalid data",
|
||||
"project": "firefox",
|
||||
"status": "passed",
|
||||
"browser": "155.0",
|
||||
"errors": []
|
||||
},
|
||||
{
|
||||
"scope": "chapter.spec.js / candidate chapter on published core / explicit source update and core-owned destruction",
|
||||
"project": "firefox",
|
||||
"status": "passed",
|
||||
"browser": "155.0",
|
||||
"errors": []
|
||||
},
|
||||
{
|
||||
"scope": "chapter.spec.js / candidate chapter on published core / factory, registration, input normalization and safe title text",
|
||||
"project": "webkit",
|
||||
"status": "passed",
|
||||
"browser": "26.6",
|
||||
"errors": []
|
||||
},
|
||||
{
|
||||
"scope": "chapter.spec.js / candidate chapter on published core / progress bars, real hover, title edges and boundary seek",
|
||||
"project": "webkit",
|
||||
"status": "passed",
|
||||
"browser": "26.6",
|
||||
"errors": []
|
||||
},
|
||||
{
|
||||
"scope": "chapter.spec.js / candidate chapter on published core / sync update, Infinity, object identity, empty lists and invalid data",
|
||||
"project": "webkit",
|
||||
"status": "passed",
|
||||
"browser": "26.6",
|
||||
"errors": []
|
||||
},
|
||||
{
|
||||
"scope": "chapter.spec.js / candidate chapter on published core / explicit source update and core-owned destruction",
|
||||
"project": "webkit",
|
||||
"status": "passed",
|
||||
"browser": "26.6",
|
||||
"errors": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
# PKG-CHAPTER-02:章节回归与可选参数声明
|
||||
|
||||
## 改动
|
||||
|
||||
新增 [chapter.spec.js](../../test/browser/chapter.spec.js),在已发布核心 5.4.0 上分别运行发布与当前
|
||||
chapter:每浏览器 8 项正常/边界/失败/生命周期测试,另加 1 项只针对发布包的历史缺陷观察。
|
||||
实际鼠标 hover、进度点击 seek、metadata 与切源由真实浏览器执行;没有替换 HTMLMediaElement。
|
||||
|
||||
覆盖无参工厂完整注册、name/结果 key、同步 update、数组原地排序/补齐/对象引用、Infinity、
|
||||
标题 trim 和 HTML 纯文本、gap、三类进度条、标题两侧夹紧、边界 seek、空配置、六类非法数据、
|
||||
无参 update 错误、切源后显式 update、destroy 和全局 style 保留。
|
||||
|
||||
生产声明的工厂参数从必填扩展为可选,运行时已有默认值,不改变旧调用;update 的对象仍必填。
|
||||
[消费者夹具](../../test/types/chapter-options.ts) 验证无参/undefined/空对象/正常调用及三个反例。
|
||||
运行 build:ts 再生成编辑器声明,仅 chapter 文件有实际差异。README 补调用说明与测试入口。
|
||||
没有迁移或修改插件 JS 运行时;生产源码 TS 迁移仍为零。
|
||||
|
||||
## 验证和发现
|
||||
|
||||
- Chromium 153.0.8010.12、Firefox 155.0、WebKit 26.6:chapter 27 项通过;连同原 smoke 总计 39 项通过。
|
||||
- TS 5.9.3 的 Node10 CommonJS / NodeNext CommonJS / Bundler ESM 与 TS 4.3.5 旧消费通过,
|
||||
错误 update/标题/null 参数仍被拒绝。关闭 BASE-TYPE-02;BASE-TYPE-01/03 不受本修复影响。
|
||||
- `yarn ci:check` 55 项检查通过;build:ts、计划/风险台账、Git 差异检查通过。
|
||||
- [运行记录](../baselines/chapter-validation.json) 保存源码/声明指纹、浏览器版本、结果及历史观察。
|
||||
|
||||
实际复现空白 gap 沿用上一标题、NaN 区间进入 DOM、切源后保留旧区间。历史观察仅对发布版本
|
||||
固定这些结果,不要求候选永久保持缺陷。PKG-CHAPTER-03 负责标题/非法时间修正、生命周期与
|
||||
模块拆分;切源后的显式更新责任和已有调用方数组变化保持可解释,不能意外改变公开语义。
|
||||
|
||||
测试使用真实鼠标来提供核心 hover 事件需要的原生 event;整数坐标决定实际百分比。同步
|
||||
setBar 断言在一次 page.evaluate 中发出并读取,避免与原生 timeupdate 交错导致测试误报。
|
||||
完整套件另复现 Firefox 已缓存媒体 Range 的 NS_ERROR_PARSED_DATA_CACHED;按
|
||||
[Mozilla 的说明](https://bugzilla.mozilla.org/show_bug.cgi?id=1347174#c4) 增加精确引擎/媒体类型/同源/
|
||||
用例 URL 限定,保留原始记录和全部播放断言,不全局忽略请求失败。
|
||||
|
||||
## 边界和下一步
|
||||
|
||||
仍未验收完整编辑器、移动真机、所有核心历史版本或最终 tarball;旧核心/新插件的范围以当前
|
||||
基线为准。类型消费者的 NodeNext ESM/legacy 解析归 PKG-CHAPTER-04。先完成 ENG-07 候选消费,
|
||||
再进入 PKG-CHAPTER-03 源码拆分;可直接用 TS 实现模块,04 继续完成公开类型/分发消费闭环。
|
||||
|
||||
回退本提交恢复原必填声明及编辑器输出,并移除新增测试/文档;旧合法调用和运行时代码不变。
|
||||
+3
-2
@@ -4,7 +4,7 @@
|
||||
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 214 项,范围 22 个包及工作区/示例。
|
||||
|
||||
状态:todo 184 / doing 0 / blocked 0 / done 30 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
状态:todo 183 / doing 0 / blocked 0 / done 31 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
|
||||
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
|
||||
|
||||
@@ -133,7 +133,7 @@
|
||||
| ID | 范围 / 步骤 | 前置依赖 | 交付物 | 验收条件 | 风险 | 状态 |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| PKG-CHAPTER-01 | artplayer-plugin-chapter<br>核对包契约与历史用法 | BASE-05 | chapters 时间区间、update、name 和进度 DOM 清单 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结 | M | done |
|
||||
| PKG-CHAPTER-02 | artplayer-plugin-chapter<br>建立特有行为与错误测试 | PKG-CHAPTER-01, ENG-03, ENG-05 | 区间重叠/空列表/边界 seek、hover 标题和 update 用例 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言;修正 BASE-TYPE-02 的可选参数声明并补正反例 | M | todo |
|
||||
| PKG-CHAPTER-02 | artplayer-plugin-chapter<br>建立特有行为与错误测试 | PKG-CHAPTER-01, ENG-03, ENG-05 | 区间重叠/空列表/边界 seek、hover 标题和 update 用例 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言;修正 BASE-TYPE-02 的可选参数声明并补正反例 | M | done |
|
||||
| PKG-CHAPTER-03 | artplayer-plugin-chapter<br>整理内部职责与资源 | PKG-CHAPTER-02, ENG-07 | 在已有核心上整理时间区间计算、进度 DOM、事件/布局,合理简单代码可保持 | 不依赖新核心方法或大范围设置重构;旧 API/事件和资源通过,必要适配限包内 | M | todo |
|
||||
| PKG-CHAPTER-04 | artplayer-plugin-chapter<br>迁移自有源码和公开类型 | PKG-CHAPTER-03, ENG-04, ENG-06 | 基于既有公开核心类型的 Chapters/Option/Result 与 update 推导;JS/TS 工具链试点 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容;处理 BASE-TYPE-01/03,保持运行时和新旧 TS 模块解析消费 | M | todo |
|
||||
| PKG-CHAPTER-05 | artplayer-plugin-chapter<br>验证新旧核心和组合 | PKG-CHAPTER-04, CORE-22 | chapter + quality/thumbnail、移动和全屏进度场景 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | M | todo |
|
||||
@@ -433,3 +433,4 @@
|
||||
- ENG-05: [记录](changes/2026-09-10-ENG-05-browser-tests.md) [记录](baselines/browser-validation.json)
|
||||
- ENG-06: [记录](changes/2026-09-10-ENG-06-build-development.md) [记录](build-development.md) [记录](baselines/build-validation.json)
|
||||
- PKG-CHAPTER-01: [记录](changes/2026-09-10-PKG-CHAPTER-01-contract.md) [记录](baselines/chapter-contract.md)
|
||||
- PKG-CHAPTER-02: [记录](changes/2026-09-10-PKG-CHAPTER-02-tests.md) [记录](baselines/chapter-validation.json)
|
||||
@@ -1,5 +1,9 @@
|
||||
# 进度与证据
|
||||
|
||||
## 当前实施:PKG-CHAPTER-02 测试与声明修正完成
|
||||
|
||||
chapter 27 项真实浏览器回归通过,合并原 smoke 共 39 项;工厂 option 声明已扩展为可选并重新生成编辑器声明,新旧 TS 正反例通过,BASE-TYPE-02 关闭。见 [交付](changes/2026-09-10-PKG-CHAPTER-02-tests.md)。当前 214 项,31 完成、183 待办。PKG-CHAPTER-01 提交 70174952,本任务单独提交;下一项 ENG-07,随后直接以 TS 拆分 chapter 源码。运行时源码尚未迁移。
|
||||
|
||||
## 当前实施:PKG-CHAPTER-01 契约完成
|
||||
|
||||
源码/发布插件逻辑和元数据对照完成,冻结 chapter 调用、数组变更、DOM/事件和入口,登记待修问题;见 [契约](baselines/chapter-contract.md)。当前 214 项,30 完成、184 待办。ENG-05 提交 7318d338,下一项 PKG-CHAPTER-02 包特有测试与声明修正。生产源码 TS 迁移仍为零。
|
||||
|
||||
@@ -4,7 +4,7 @@ BASE-07 将先前基线发现和第三方来源缺口纳入同一份责任台账
|
||||
|
||||
## 如何读取状态
|
||||
|
||||
BASE-07 初始登记 39 项,目前仍为 open;ENG-06 新增并修复 BUILD-AMD-01,共 40 项。reproduced 表示已保存复现结果;source-observed 表示在源码、声明或产物中看到事实;unverified 表示来源、环境或行为仍需取证。证据等级与处理状态互相独立,不能把全部事项称为缺陷,也不能因建立了测试基线而自动接受旧缺陷。
|
||||
BASE-07 初始登记 39 项;后续新增和关闭状态以 risks.json 为准。ENG-06 修复 BUILD-AMD-01,PKG-CHAPTER-02 修复 BASE-TYPE-02,均有独立回归证据。reproduced 表示已保存复现结果;source-observed 表示在源码、声明或产物中看到事实;unverified 表示来源、环境或行为仍需取证。证据等级与处理状态互相独立,不能把全部事项称为缺陷,也不能因建立了测试基线而自动接受旧缺陷。
|
||||
|
||||
其中 15 项来自生命周期、DOM/示例、消费者类型/分发和性能基线;其余包含发布来源、站点分发、素材来源、8 组内嵌资源和 12 个集成边界。每项都列出负责的任务 ID、证据路径、兼容处理路线与关闭条件。具体复现仍以原基线报告为准。
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
| BASE-DOM-02 | open / 已复现 | 窄容器中控件裁切 | CORE-13, CORE-14 |
|
||||
| BASE-DEMO-01 | open / 源码/产物事实 | 旧 thumbnail 插件示例不对应当前 workspace 包 | SITE-01, EX-03 |
|
||||
| BASE-TYPE-01 | open / 已复现 | NodeNext ESM 默认导出与声明互操作失配 | ENG-04, CORE-07, PKG-CHAPTER-04 |
|
||||
| BASE-TYPE-02 | open / 已复现 | chapter 可选运行时参数被声明为必填 | PKG-CHAPTER-02 |
|
||||
| BASE-TYPE-02 | resolved / 已复现 | chapter 可选运行时参数被声明为必填 | PKG-CHAPTER-02 |
|
||||
| BASE-TYPE-03 | open / 已复现 | chapter legacy 缺旧模块解析的声明回退 | ENG-04, PKG-CHAPTER-04 |
|
||||
| BASE-TYPE-04 | open / 源码/产物事实 | plugins.add / toggle 的旧声明返回值与运行时不同 | CORE-07 |
|
||||
| BASE-DIST-01 | open / 源码/产物事实 | thumbnail tool 分发入口与类型文件缺失 | PKG-TOOL-THUMB-01, PKG-TOOL-THUMB-04 |
|
||||
|
||||
+11
-3
@@ -2,7 +2,7 @@
|
||||
"schemaVersion": 1,
|
||||
"task": "BASE-07",
|
||||
"sourceCommit": "e01f39ff",
|
||||
"updatedAt": "2026-09-10T13:46:01.011Z",
|
||||
"updatedAt": "2026-09-10T14:41:52.219Z",
|
||||
"note": "Open means closure evidence is outstanding; source-observed and unverified are not reproduced runtime bugs. A source issue already mitigated in the workspace still requires candidate regression evidence. No accepted-with-scope item is implied by baseline task completion.",
|
||||
"items": [
|
||||
{
|
||||
@@ -163,7 +163,7 @@
|
||||
"id": "BASE-TYPE-02",
|
||||
"title": "chapter 可选运行时参数被声明为必填",
|
||||
"confirmation": "reproduced",
|
||||
"status": "open",
|
||||
"status": "resolved",
|
||||
"owners": [
|
||||
"PKG-CHAPTER-02"
|
||||
],
|
||||
@@ -172,7 +172,15 @@
|
||||
"refactor/fixtures/api.js"
|
||||
],
|
||||
"compatibleResolution": "补充兼容可选参数,不删除已接受的配置。",
|
||||
"closureCriteria": "无参数和正确参数编译,错误参数依旧被拒绝,运行时一致。"
|
||||
"closureCriteria": "无参数和正确参数编译,错误参数依旧被拒绝,运行时一致。",
|
||||
"resolutionEvidence": [
|
||||
"packages/artplayer-plugin-chapter/types/artplayer-plugin-chapter.d.ts",
|
||||
"test/types/chapter-options.ts",
|
||||
"refactor/scripts/types.test.mjs",
|
||||
"test/browser/chapter.spec.js",
|
||||
"refactor/changes/2026-09-10-PKG-CHAPTER-02-tests.md"
|
||||
],
|
||||
"resolutionRationale": "工厂 option 扩展为可选,旧/新 TS 消费与实际无参插件注册通过;update 及非法输入仍受严格约束。编辑器声明已重新生成。NodeNext ESM 与 legacy 分发问题仍由其他条目跟踪。"
|
||||
},
|
||||
{
|
||||
"id": "BASE-TYPE-03",
|
||||
|
||||
@@ -35,3 +35,14 @@ test('Browser implementation configuration catches source errors without emittin
|
||||
fs.rmSync(directory, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
test('Chapter optional factory parameters preserve strict update and data types in new and old compilers', () => {
|
||||
const source = fs.readFileSync(path.join(refactorDir, '../test/types/chapter-options.ts'), 'utf8')
|
||||
for (const mode of ['node10-commonjs', 'nodenext-cjs', 'bundler-esm']) {
|
||||
assert.deepEqual(checkConsumer(ts, mode, source), [])
|
||||
}
|
||||
assert.deepEqual(checkConsumer(compat, 'node10-commonjs', source), [])
|
||||
const unguarded = source.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, '')
|
||||
const diagnostics = checkConsumer(ts, 'bundler-esm', unguarded)
|
||||
assert.equal(diagnostics.length, 3, 'Invalid update, title and null option must each fail')
|
||||
})
|
||||
+5
-2
@@ -1161,11 +1161,14 @@
|
||||
"ENG-03",
|
||||
"ENG-05"
|
||||
],
|
||||
"status": "todo",
|
||||
"status": "done",
|
||||
"risk": "M",
|
||||
"deliverable": "区间重叠/空列表/边界 seek、hover 标题和 update 用例",
|
||||
"acceptance": "旧版本行为可重跑,成功/失败/切源/销毁有必要断言;修正 BASE-TYPE-02 的可选参数声明并补正反例",
|
||||
"evidence": []
|
||||
"evidence": [
|
||||
"changes/2026-09-10-PKG-CHAPTER-02-tests.md",
|
||||
"baselines/chapter-validation.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "PKG-CHAPTER-03",
|
||||
|
||||
@@ -23,6 +23,10 @@ failed requests and final state. Tests attach decoded pixels and server-side med
|
||||
Windows WebKit media requests are not reliably visible to Playwright's page network events;
|
||||
use the loopback request log to prove that media was fetched. Its `videoWidth` can reflect layout
|
||||
size, so intrinsic-size compatibility is a separate test, not the playback-success predicate.
|
||||
Chromium's `net::ERR_ABORTED` and Firefox's `NS_ERROR_PARSED_DATA_CACHED` may cancel media range
|
||||
requests after buffering. The successful-playback tests allow only the exact engine-specific code,
|
||||
same-origin media type and two URLs bearing the current test ID; raw failures remain in reports.
|
||||
See [Mozilla's media-cache explanation](https://bugzilla.mozilla.org/show_bug.cgi?id=1347174#c4).
|
||||
|
||||
Reports, failure screenshots and traces are under `refactor/.cache/browser/`. They contain local
|
||||
test URLs and paths. JSON and HTML are regenerated each run; archive them before another run
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
import { expect, test } from './fixtures.js'
|
||||
|
||||
async function openChapter(page, chapter, chapters = []) {
|
||||
await page.goto(`/test/player.html?core=published&chapter=${chapter}`)
|
||||
await page.evaluate((chapters) => {
|
||||
window.chapterInput = chapters
|
||||
window.createPlayer('/test/pattern.mp4', { chapters })
|
||||
}, chapters)
|
||||
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
|
||||
}
|
||||
|
||||
function segments(page) {
|
||||
return page.locator('.art-chapter').evaluateAll(nodes => nodes.map(node => ({
|
||||
start: node.dataset.start,
|
||||
end: node.dataset.end,
|
||||
title: node.dataset.title,
|
||||
width: node.style.width,
|
||||
})))
|
||||
}
|
||||
|
||||
async function hover(page, percentage) {
|
||||
const box = await page.locator('.art-control-progress-inner').boundingBox()
|
||||
const x = Math.round(box.x + box.width * percentage)
|
||||
await page.mouse.move(x, box.y + box.height / 2)
|
||||
return (x - box.x) / box.width
|
||||
}
|
||||
|
||||
for (const chapter of ['published', 'candidate']) {
|
||||
test.describe(`${chapter} chapter on published core`, () => {
|
||||
test('factory, registration, input normalization and safe title text', async ({ page }) => {
|
||||
await openChapter(page, chapter, [{ start: 4, end: 6, title: ' Second ' }, { start: 1, end: 2, title: '<b>First</b>' }])
|
||||
expect(await page.evaluate(() => {
|
||||
const plugin = window.art.plugins.artplayerPluginChapter
|
||||
return { factory: typeof window.artplayerPluginChapter(), name: plugin.name, keys: Object.keys(plugin).sort(), input: window.chapterInput }
|
||||
})).toEqual({
|
||||
factory: 'function',
|
||||
name: 'artplayerPluginChapter',
|
||||
keys: ['name', 'update'],
|
||||
input: [{ start: 0, end: 1, title: '' }, { start: 1, end: 2, title: '<b>First</b>' }, { start: 2, end: 4, title: '' }, { start: 4, end: 6, title: ' Second ' }, { start: 6, end: 8, title: '' }],
|
||||
})
|
||||
expect(await segments(page)).toEqual([
|
||||
{ start: '0', end: '1', title: '', width: '12.5%' },
|
||||
{ start: '1', end: '2', title: '<b>First</b>', width: '12.5%' },
|
||||
{ start: '2', end: '4', title: '', width: '25%' },
|
||||
{ start: '4', end: '6', title: 'Second', width: '25%' },
|
||||
{ start: '6', end: '8', title: '', width: '25%' },
|
||||
])
|
||||
await hover(page, 0.2)
|
||||
await expect(page.locator('.art-chapter-title')).toHaveText('<b>First</b>')
|
||||
await expect(page.locator('.art-chapter-title b')).toHaveCount(0)
|
||||
await expect(page.locator('style#artplayer-plugin-chapter')).toHaveCount(1)
|
||||
await page.evaluate(() => {
|
||||
window.art.destroy()
|
||||
window.art = new window.Artplayer({ container: '.player', url: '/test/pattern.mp4', muted: true, plugins: [window.artplayerPluginChapter()] })
|
||||
})
|
||||
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
|
||||
await expect(page.locator('.art-chapter')).toHaveCount(0)
|
||||
})
|
||||
|
||||
test('progress bars, real hover, title edges and boundary seek', async ({ page }) => {
|
||||
await openChapter(page, chapter, [{ start: 0, end: 2, title: 'First chapter' }, { start: 2, end: 6, title: 'Middle' }, { start: 6, end: 8, title: 'Last chapter' }])
|
||||
for (const type of ['played', 'loaded']) {
|
||||
const widths = await page.evaluate((type) => {
|
||||
window.art.emit('setBar', type, 0.375)
|
||||
return [...document.querySelectorAll(`.art-chapter .art-progress-${type}`)].map(node => node.style.width)
|
||||
}, type)
|
||||
expect(widths).toEqual(['100%', '25%', '0px'])
|
||||
}
|
||||
const percentage = await hover(page, 0.375)
|
||||
const widths = await page.locator('.art-chapter .art-progress-hover').evaluateAll(nodes => nodes.map(node => Number.parseFloat(node.style.width)))
|
||||
for (const [index, expected] of [100, (percentage * 8 - 2) / 4 * 100, 0].entries())
|
||||
expect(widths[index]).toBeCloseTo(expected, 1)
|
||||
const box = await page.locator('.art-control-progress-inner').boundingBox()
|
||||
for (const [percentage, title] of [[0.01, 'First chapter'], [0.5, 'Middle'], [0.99, 'Last chapter']]) {
|
||||
await page.mouse.move(box.x + box.width * percentage, box.y + box.height / 2)
|
||||
await expect(page.locator('.art-chapter-title')).toHaveText(title)
|
||||
await expect(page.locator('.art-video-player')).toHaveClass(/art-progress-hover/)
|
||||
const layout = await page.locator('.art-chapter-title').evaluate(title => ({ left: Number.parseFloat(title.style.left), width: title.clientWidth, container: title.parentElement.clientWidth }))
|
||||
expect(layout.left).toBeGreaterThanOrEqual(0)
|
||||
expect(layout.left + layout.width).toBeLessThanOrEqual(layout.container)
|
||||
}
|
||||
await page.mouse.click(box.x + box.width / 4, box.y + box.height / 2)
|
||||
await expect.poll(() => page.locator('video').evaluate(video => video.currentTime)).toBeCloseTo(2, 1)
|
||||
expect(await page.evaluate(() => {
|
||||
window.art.emit('setBar', 'played', 1)
|
||||
return [...document.querySelectorAll('.art-chapter .art-progress-played')].map(node => node.style.width)
|
||||
})).toEqual(['100%', '100%', '100%'])
|
||||
})
|
||||
|
||||
test('sync update, Infinity, object identity, empty lists and invalid data', async ({ page }) => {
|
||||
await openChapter(page, chapter)
|
||||
await expect(page.locator('.art-chapter')).toHaveCount(0)
|
||||
expect(await page.evaluate(() => {
|
||||
const entry = { start: 0, end: Infinity, title: 'All' }
|
||||
const chapters = [entry]
|
||||
const result = window.art.plugins.artplayerPluginChapter.update({ chapters })
|
||||
return { result, same: chapters[0] === entry, end: entry.end }
|
||||
})).toEqual({ result: undefined, same: true, end: 8 })
|
||||
await expect(page.locator('.art-chapter')).toHaveCount(1)
|
||||
await page.evaluate(() => window.art.plugins.artplayerPluginChapter.update({}))
|
||||
await expect(page.locator('.art-chapter')).toHaveCount(0)
|
||||
await expect(page.locator('.art-video-player')).not.toHaveClass(/artplayer-plugin-chapter/)
|
||||
const cases = [
|
||||
[[{ start: '0', end: 2, title: 'Bad' }], 'TypeError', 'Illegal chapter data type'],
|
||||
[[{ start: 0, end: 2, title: 12 }], 'TypeError', 'Illegal chapter data type'],
|
||||
[[{ start: -1, end: 2, title: 'Bad' }], 'Error', 'Illegal chapter time point'],
|
||||
[[{ start: 2, end: 2, title: 'Bad' }], 'Error', 'Illegal chapter time point'],
|
||||
[[{ start: 0, end: 9, title: 'Bad' }], 'Error', 'Illegal chapter time point'],
|
||||
[[{ start: 0, end: 4, title: 'A' }, { start: 3, end: 6, title: 'B' }], 'Error', 'Illegal chapter time point'],
|
||||
]
|
||||
for (const [chapters, name, message] of cases) {
|
||||
expect(await page.evaluate((chapters) => {
|
||||
const update = window.art.plugins.artplayerPluginChapter.update
|
||||
update({ chapters: [{ start: 0, end: Infinity, title: 'Valid before failure' }] })
|
||||
try {
|
||||
update({ chapters })
|
||||
}
|
||||
catch (error) { return { name: error.name, message: error.message } }
|
||||
}, chapters)).toEqual({ name, message })
|
||||
await expect(page.locator('.art-chapter')).toHaveCount(0)
|
||||
}
|
||||
expect(await page.evaluate(() => {
|
||||
try {
|
||||
window.art.plugins.artplayerPluginChapter.update()
|
||||
}
|
||||
catch (error) { return error.name }
|
||||
})).toBe('TypeError')
|
||||
})
|
||||
|
||||
test('explicit source update and core-owned destruction', async ({ page }) => {
|
||||
await openChapter(page, chapter, [{ start: 0, end: 8, title: 'Initial' }])
|
||||
await page.evaluate(() => window.art.switchUrl('/assets/sample/video.mp4'))
|
||||
expect(await page.locator('video').evaluate(video => video.duration)).toBeGreaterThan(90)
|
||||
await page.evaluate(() => window.art.plugins.artplayerPluginChapter.update({ chapters: [{ start: 0, end: Infinity, title: 'New source' }] }))
|
||||
expect(await page.locator('.art-chapter').evaluate(node => Number(node.dataset.end))).toBeGreaterThan(90)
|
||||
await page.evaluate(() => window.art.destroy())
|
||||
await expect(page.locator('.player')).toBeEmpty()
|
||||
expect(await page.evaluate(() => window.Artplayer.instances.length)).toBe(0)
|
||||
await expect(page.locator('style#artplayer-plugin-chapter')).toHaveCount(1)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
test('published-only observations: blank title, NaN and once-only metadata are historical defects', async ({ page }, testInfo) => {
|
||||
await openChapter(page, 'published', [{ start: 0, end: 2, title: 'Previous' }, { start: 4, end: 8, title: 'Next' }])
|
||||
await hover(page, 0.1)
|
||||
await hover(page, 0.375)
|
||||
const staleTitle = await page.locator('.art-chapter-title').textContent()
|
||||
expect(staleTitle).toBe('Previous')
|
||||
await page.evaluate(() => window.art.switchUrl('/assets/sample/video.mp4'))
|
||||
expect(await page.locator('.art-chapter').last().evaluate(node => node.dataset.end)).toBe('8')
|
||||
await page.evaluate(() => window.art.plugins.artplayerPluginChapter.update({ chapters: [{ start: Number.NaN, end: 3, title: 'Invalid' }] }))
|
||||
expect(await page.locator('.art-chapter').first().evaluate(node => node.dataset.start)).toBe('NaN')
|
||||
await testInfo.attach('chapter-known-defects', { body: JSON.stringify({ staleTitle, endAfterSourceChange: 8, acceptsNaN: true }), contentType: 'application/json' })
|
||||
})
|
||||
@@ -1,7 +1,7 @@
|
||||
import { expect, test } from './fixtures.js'
|
||||
|
||||
for (const core of ['published', 'candidate']) {
|
||||
test(`${core}: real playback, pause, seek, switch and destroy`, async ({ page, request, diagnostics }, testInfo) => {
|
||||
test(`${core}: real playback, pause, seek, switch and destroy`, async ({ page, request, diagnostics, browserName }, testInfo) => {
|
||||
const caseId = encodeURIComponent(testInfo.testId)
|
||||
await page.goto(`/test/player.html?core=${core}`)
|
||||
await page.evaluate(url => window.createPlayer(url, true), `/test/pattern.mp4?case=${caseId}`)
|
||||
@@ -55,11 +55,13 @@ for (const core of ['published', 'candidate']) {
|
||||
await expect(page.locator('.player')).toBeEmpty()
|
||||
expect(await page.evaluate(() => window.Artplayer.instances.length)).toBe(0)
|
||||
expect(diagnostics.consoleErrors).toEqual([])
|
||||
// Chromium cancels media range requests as buffering, seeking and source ownership change.
|
||||
// Engines cancel media range requests as buffering, seeking and source ownership change.
|
||||
// Only exact cancellations of this test's two media URLs are expected; retain all in evidence.
|
||||
const unexpectedRequests = diagnostics.failedRequests.filter((item) => {
|
||||
const url = new URL(item.url)
|
||||
return !(item.resourceType === 'media' && item.failure?.errorText === 'net::ERR_ABORTED'
|
||||
const expectedCancellation = (browserName === 'chromium' && item.failure?.errorText === 'net::ERR_ABORTED')
|
||||
|| (browserName === 'firefox' && item.failure?.errorText === 'NS_ERROR_PARSED_DATA_CACHED')
|
||||
return !(item.resourceType === 'media' && expectedCancellation
|
||||
&& url.origin === new URL(page.url()).origin
|
||||
&& url.searchParams.get('case') === testInfo.testId
|
||||
&& ['/test/pattern.mp4', '/assets/sample/video.mp4'].includes(url.pathname))
|
||||
|
||||
@@ -24,7 +24,11 @@
|
||||
url,
|
||||
muted: true,
|
||||
plugins: chapter
|
||||
? [artplayerPluginChapter({ chapters: [{ start: 0, end: Infinity, title: 'Pattern' }] })]
|
||||
? [
|
||||
artplayerPluginChapter(
|
||||
chapter === true ? { chapters: [{ start: 0, end: Infinity, title: 'Pattern' }] } : chapter,
|
||||
),
|
||||
]
|
||||
: [],
|
||||
})
|
||||
for (const name of [
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import type Artplayer from 'artplayer'
|
||||
import chapter from 'artplayer-plugin-chapter'
|
||||
|
||||
declare const art: Artplayer
|
||||
chapter()
|
||||
chapter(undefined)
|
||||
chapter({})
|
||||
chapter({ chapters: [] })(art).update({})
|
||||
const name: 'artplayerPluginChapter' = chapter()(art).name
|
||||
void name
|
||||
// @ts-expect-error The update object remains required.
|
||||
chapter()(art).update()
|
||||
// @ts-expect-error A chapter title remains a string.
|
||||
chapter({ chapters: [{ start: 0, end: 1, title: 12 }] })
|
||||
// @ts-expect-error null is not an options object.
|
||||
chapter(null)
|
||||
Reference in new issue
Block a user