资讯详情

Plate 仓库 Yjs 协同编辑测试资产索引解读:从上游 CRDT 测试清单到本地协同收敛验证

📅 2026/9/15 12:35:30 | 华诺云谱 👁 阅读
Plate 仓库 Yjs 协同编辑测试资产索引解读:从上游 CRDT 测试清单到本地协同收敛验证
Plate 仓库 Yjs 协同编辑测试资产索引解读从上游 CRDT 测试清单到本地协同收敛验证【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate本篇技术指南以 docs/editor-test-harvester/yjs-collaboration/test-index.md 为核心骨架解读该仓库为Yjs 协同编辑建立的测试资产索引Harvest Test Index它盘点并归类了 slate-yjs、lexical-yjs、y-prosemirror、yjs 四个上游代码库中与协同编辑相关的全部可运行测试同时结合本仓库 packages/yjs 内实际落地的协同编辑测试多端内存连接器 收敛性 fixture进行交叉印证。读完本文你将理解这张索引的字段含义、四大测试域各自覆盖的协同不变式、portable/portable-mixed/harness/skip等分类如何指导测试资产复用以及如何在 Plate 的 Yjs 插件测试中复现本地编辑 远端镜像收敛这一核心验证模式。文档定位测试收割体系的协同编辑分册在进入具体条目之前先厘清这份文档在仓库中的位置。docs/editor-test-harvester/目录是仓库为编辑器行为测试收割test harvester建立的专题档案区按编辑器/协议分为多个分册docs/editor-test-harvester/lexicaldocs/editor-test-harvester/prosemirrordocs/editor-test-harvester/tiptapdocs/editor-test-harvester/portabletextdocs/editor-test-harvester/yjs-collaborationyjs-collaboration分册下有两份配套文档本次主题是其中之一的test-index.md另一份 inventory.md 则是它的总账给出每个文件的类别、理由与收割命令。两者配合使用inventory 回答这些测试是什么、是否可运行test-index 回答具体有哪些测试函数、在哪个文件哪一行。test-index.md的元信息非常简洁status: done license_mode: permissive这两行说明该分册的索引工作已完成done且所索引的上游测试全部采用宽松许可permissive因此可以合法地作为测试资产被参考或移植。四大测试域总览test-index.md将协同编辑相关测试资产划分为四个上游域下表为完整清单文件、可运行性、测试导出数量均以文档逐行统计为准上游域测试文件可运行测试导出数索引行号范围slate-yjscore/test/index.test.tsadapter suite✅1 套L63slate-yjscore/test/collaboration/{addMark,insertNode,insertText,mergeNode,moveNode,removeMark,removeNode,removeText,setNode,splitNode}/...共 54 个 fixture✅54 fixtureL9-L62lexical-yjslexical/packages/lexical-yjs❌无L66y-prosemirrortests/delta.test.js✅12L76-L88y-prosemirrortests/positions.test.js✅22L96-L118y-prosemirrortests/suggestion-simulation.test.js✅4L120-L124y-prosemirrortests/suggestions.test.js✅21L126-L147y-prosemirrortests/tr.test.js⚠️ manual1L149-L150y-prosemirrortests/undo.test.js✅33L152-L185y-prosemirrortests/y-prosemirror.test.js⚠️ manual23L187-L210y-prosemirrortests/{cohort,complexSchema,index,index.node}.js❌0harness/支持文件L70-L94yjstests/IdMap.tests.js✅7L214-L221yjstests/IdSet.tests.js✅7L223-L230yjstests/attribution.tests.js✅7L232-L239yjstests/compatibility.tests.js✅3L241-L244yjstests/delta.tests.js✅5L246-L251yjstests/doc.tests.js✅11L253-L264yjstests/encoding.tests.js✅3L266-L269yjstests/relativePositions.tests.js✅9L274-L283yjstests/snapshot.tests.js✅12L285-L297yjstests/undo-redo.tests.js✅25L302-L327yjstests/updates.tests.js✅8L329-L337yjstests/y-array.tests.js✅41L339-L380yjstests/y-map.tests.js✅40L382-L422yjstests/y-text.tests.js✅47L424-L471yjstests/y-xml.tests.js✅12L473-L485yjstests/{index,testHelper}.js❌0runner/支持文件L271-L299需要说明原文档中../slate-yjs/...、../y-prosemirror/...、../yjs/...等前缀是索引作者对上游仓库当前仓库之外的兄弟目录的相对引用这些路径在本仓库内不存在上表与下文的路径标识仅用于与原始索引一一对应。当前仓库内可验证的对应实现位于 packages/yjs后文第 4 节详述。分类体系如何判断一份测试资产值不值得移植inventory.md 为每个文件标注了Category字段这是整个收割体系的决策核心共五类类别含义典型例子portable纯协同不变式与具体编辑器视图解耦可直接移植yjs 的relativePositions、snapshot、updates、undo-redoslate-yjs 的全部 54 个操作 fixtureportable-mixed协同不变式有价值但与编辑器视图/插件策略耦合需甄别后移植y-prosemirror 的suggestions、undoyjs 的y-array、y-map、y-xml、attributionharness无行为断言仅是运行器或辅助设施y-prosemirror 的index.js/cohort.js/complexSchema.jsyjs 的index.js/testHelper.jsslate-yjs 的withTestingElements.tsskip是 Yjs 内部存储/编码兼容性测试对编辑器行为非直接目标IdMap、IdSet、compatibility、encodingmanual有价值但需人工介入如依赖浏览器视图y-prosemirror 的tr.test.js、y-prosemirror.test.js这一分类直接回答了该不该把这份上游测试搬进 Plate 的测试体系优先收割portable它们验证的是 CRDT 层面最基础的收敛不变式与框架无关谨慎处理portable-mixed其断言逻辑可复用但其中混入的 ProseMirror 视图/插件代码需要剥离harness文件本身不产出行为断言但其中的技术手法如 testHelper.js 对应的随机多端连接器与比较器值得借鉴——这正是本仓库 packages/yjs/src/lib/tests/collaboration/harness.ts 所采用的做法见第 4 节。分域详解slate-yjs54 个操作级回放fixtureslate-yjs 域贡献了索引中结构化程度最高的一块packages/core/test/collaboration/下按 Slate 原生操作命名的 54 个 fixture外加一个入口 suiteindex.test.ts:63。每个 fixture 的验证模式摘自 inventory 的 Reason 列是Slate operation fixture is replayed through slate-yjs and checked against a remote Y.Doc mirror.即用本地 Slate 编辑器重放一个操作 → 观察该操作如何被 slate-yjs 转录到共享 Y.XmlText → 在远端 Y.Doc 镜像上核对最终文档状态是否一致。这正是协同适配器测试的黄金路径不关心具体 UI只关心操作 → CRDT 变更的映射正确性。54 个 fixture 按操作族分布行号区间见 test-index.md操作族数量覆盖的关键场景addMark5跨多 mark、与既有 mark 共存、文档首/尾、withOtherMarksinsertNode3文档首/尾、段落中间插入块节点insertText11块首/块尾/文档首/文档尾、嵌套块中间、insideMarks、空字符串、HTML 实体、带 mark 文本、UnicodemergeNode5删除后退格触发合并、同父节点、混合嵌套/混合类型节点、UnicodemoveNode8上/下移动 × 是否变成嵌套 × 是否保持嵌套四种排列组合removeMark3文本中间、先 add 再 remove、与其他 mark 共存removeNode4文档首/尾、嵌套块、wrapper 块removeText3文档首/尾、UnicodesetNode6文档首/尾、onDataChange、内联节点数据变更、onResetBlock、类型切换splitNode6文档首/块尾/文档尾、非默认块、多子节点、Unicode从命名可以提炼出 slate-yjs 测试的两个设计原则边界全覆盖atBeginningOfDocument、atEndOfBlock、inTheMiddle与不变式覆盖Unicode、实体、空字符串、嵌套结构这些正是协同适配器最容易出偏差的地方——尤其是 Unicode 拆分涉及代理对与嵌套块移动后的结构一致性。lexical-yjssource-only 扫描无运行资产索引对 lexical-yjs 域只记录了一行结论L66No runnable test files found in../lexical/packages/lexical-yjs; source-only scan recorded in the report.也就是说上游 Lexical 的lexical-yjs包在索引采集时不存在符合可运行模式__tests__/test/tests/spec/e2e等的测试文件索引仅对其源码做过协同概念扫描未产出可收割条目。这个空目标本身就是收割工作的有效产出避免后续重复扫描该路径。inventory 的 Empty Target Notes 一节给出了相同结论。y-prosemirror覆盖 delta 映射、位置映射、建议模式与撤销y-prosemirror 域是功能面最宽的一块六个可运行测试文件各自盯住协同适配器的一个子系统delta.test.js12 个导出testBase、testDeleteRangeOverPartialNodes、testFormatting、testBaseInsert、testReplaceAround、testAttrStep、testWrapping、testFilledBlockquote等——验证 ProseMirror Step 与 Yjs delta 之间的双向映射不变式positions.test.js22 个导出testPositionsSingleParagraph一路覆盖到testPositionsDeeplyNested、testPositionsHorizontalRule、testStoreMappingRoundTrip、testStoreMappingBookmarkTextSelection、testStoreMappingBookmarkNodeSelection——验证协同位置RelativePosition在各类文档结构下的映射与书签往返suggestion-simulation.test.js4 个导出testSimSetupConverges、testSimSingleSuggestionEditConverges、testRepeatGeneratingSuggestionEdits、testSimLongRunningFuzz——用仿真/模糊手段验证建议suggestion模式下的收敛suggestions.test.js21 个导出testSuggestionSyncAndMarks、testSequentialTypingMarks、testBlockInsertionMarks、testImageInsertionMarks、testDeletionOfSuggestedContent、testTwoViewSuggestionsUsersDivergeOnSplit、testCohortReplayConvergesAfterSplitDeleteInterleave等——覆盖建议内容与 mark、删除、回车/退格合并、双视图分歧、cohort 重放收敛undo.test.js33 个导出testBasicUndoRedo、testAddToHistory、testCursorPositionAfterUndo、testUndoDeleteRestoresContent、testUndoManagerSurvivesViewDestroy、testMultipleEditorsOnSameYType、testRemoteChangesNotUndoable、testRedoClearedByRemoteChanges、testUndoManagerWithCustomCaptureTimeout——覆盖协同场景下撤销/重做与视图生命周期、远端变更的交互y-prosemirror.test.js23 个导出testPluginIntegrity、testOverlappingMarks、testDocTransformation、testChangeOrigin、testEmptyNotSync、testInsertDuplication、testVersioning、testRepeatGenerateProsemirrorChanges{2,3,30,40,70,100,300}——前段是插件集成与版本化不变式后段是重复生成变更的规模化压力测试。其中tr.test.js与y-prosemirror.test.js被 inventory 标记为manual需人工运行其余四个为可直接运行的portable/portable-mixed。yjsCRDT 基座测试协同正确性的最底层证据yjs 域直接收割 Yjs 核心仓库自身的测试它们是上面所有适配器测试的地基。按其主题可归为几组存储与编码兼容skip类IdMap、IdSet随机合并/差分/删除/交集、compatibilityV1 数组/Map/文本解码兼容、encoding状态向量差分——这些验证 Yjs 内部数据结构对编辑器行为是间接的文本与共享类型portable/portable-mixed类y-text47 个导出含testDeltaBug、testSplitSurrogateCharacter、testLargeFragmentedDocument、testAttributedContent、testIncrementalUpdatesPerformanceOnLargeFragmentedDocument、批量testRepeatGenerateTextChanges*、y-array41 个含并发插入冲突、迟到同步、观察者事件、GC、testRepeatGeneratingYarrayTests*直至 30000 次、y-map40 个含嵌套事件、三端冲突、十万次规模压测、y-xml12 个含属性/兄弟/克隆/attributed content协同语义portable类relativePositions9 个7 个位置用例 关联差异 undo 交互、snapshot12 个恢复快照、删除项恢复、依赖变更、testContainsUpdate、updates8 个更新合并、键编码、混淆、testIntersectDoc、undo-redo25 个testUndoText、testGlobalScope、testDoubleUndo、testUndoInEmbed、testConsecutiveRedoBug、testIgnoreRemoteMapChangesProperty等、doc11 个事务递归、子文档、客户端 ID 冲突、testToJSON、delta5 个、attribution7 个相对位置、attributed events、session。这批测试对 Plate 的意义在于Plate 的 Yjs 插件建立在slate-yjs/core之上而后者又建立在 Yjs 之上——当协同测试失败时先回到 yjs 域的portable测试确认基座是否健康可以快速区分是适配器问题还是 CRDT 基座问题。本地落地packages/yjs 中的协同收敛测试索引文档本身是资产清单而本仓库的 packages/yjs/src/lib/tests/collaboration 则是这套思路在 Plate 内的直接落地包含三个文件harness.ts多端内存协同连接器fixtures.ts10 个收敛性 fixtureindex.slow.ts慢速测试入口逐个执行 fixture。多端内存连接器模拟网络但保留 CRDT 语义harness.ts中CollaborationConnector的设计值得细读它复刻了上游testHelper.jsyjs 域harness 类的多端随机连接手法但针对编辑器协同做了精简每个 peer 是一个TestCollaborationProvider通过registerProviderType(PROVIDER_TYPE, ...)注册为插件可识别的自定义 provider 类型对应 providers/registry 的扩展点connect(peer)建立连接时把已在线 peer 的完整状态Y.encodeStateAsUpdate(...)排入队列L124-L132模拟新用户加入房间先拉全量快照本地document.on(update)捕获的增量更新会广播给所有在线 peerL190-L198flushAll({ order: fifo | reverse })决定投递顺序——reverse模式用于模拟乱序到达对应上游 y-prosemirror 的乱序/迟到场景远端更新统一以REMOTE_ORIGINSymbol为 origin 应用L91这样测试可以区分本地编辑与远端回放。10 个 fixture收敛性不变式清单fixtures.ts中的collaborationFixtures数组在 index.slow.ts 中被逐条执行覆盖的协同不变式与上游索引形成清晰对照fixture 名验证的协同不变式对应上游资产seed_once_from_empty_doc首个进房者负责播种后进者不重复播种slate-yjs 播种语义 / y-prosemirrortestEmptyNotSyncserver_content_wins_over_local_value服务端既有内容优先于本地 drafty-prosemirrortestVersioningstring_value_deserializes_onceHTML 字符串只在首个播种者处反序列化一次slate-yjs fixture 回放async_value_waits_then_converges异步 value 就绪前阻塞播种随后收敛y-prosemirror 异步变更组custom_shared_type_nested_doc自定义sharedType嵌套Y.XmlText下多端收敛且顶层content不被污染y-prosemirrortestDocTransformation/ yjsdoc子文档reconnect_eventually_converges断线重连后拉取期间缺失的更新yjsupdates合并 / y-arraytestDeletionsInLateSyncconcurrent_local_edits_while_disconnected_eventually_converge双端离线各自编辑重连后合并无丢失yjs 并发冲突族y-array/y-map三端冲突out_of_order_updates_eventually_convergereverse顺序投递下仍收敛且顺序正确yjs 状态向量去重/乱序恢复timeout_then_late_sync_does_not_reseed同步超时后不重复播种幂等播种y-prosemirrortestEmptyNotSyncmixed_provider_inputs同文档挂多个 provider 时每个都连接一次y-prosemirrortestMultipleEditorsOnSameYType其中out_of_order_updates_eventually_converge直接使用了connector.flushAll({ order: reverse })fixtures.ts L441把上游 yjs 的乱序收敛能力变成 Plate 插件层可验证的契约。与 BaseYjsPlugin 的对应关系这些 fixture 断言的其实是 BaseYjsPlugin.ts 的初始化契约关键逻辑在init()L176-L375先建 provider后连编辑器init()先遍历providers配置/实例用createProvider实例化并注入onConnect/onDisconnect/onError/onSyncChange回调L233-L294首同步等待 5 秒超时autoConnect为 true 时连接全部 provider并通过syncPromise与Promise.race等待首次同步超时SYNC_TIMEOUT 5000msL299-L322——这正是timeout_then_late_sync_does_not_reseedfixture 所验证的路径仅空文档才播种hasSharedRootState()通过检查共享Y.XmlText长度与内部_start字段判断是否已有历史L30-L35配合hasPendingLocalPersistence()等待本地持久化完成决定是否把value写入——string_value_deserializes_once、server_content_wins_over_local_value即围绕该分支设计播种后再连接编辑器YjsEditor.connect(editor)、editor.tf.init(...)、editor.api.onChange()依次执行L361-L371保证编辑器看到的初始状态就是收敛后的共享状态。值得一提的是hasSharedRootState中对_start的利用Y.XmlText 在内容被删空后仍保留 tombstone 历史_start非空即可区分从未播种的空文档与已持久化的空文档从而避免重复播种——这是 fixture 语义seed_once、does_not_reseed在源码层的直接映射。如何基于索引扩充 Plate 的协同测试综合test-index.md与inventory.md给出的信息后续扩充协同测试可遵循以下路径以仓库内现有资产为起点基座回归把 yjs 域的portable族relativePositions、snapshot、updates、undo-redo、delta、doc作为 CRDT 基座的冒烟集任何协同失败先跑这批操作矩阵移植以 slate-yjs 的 54 个 fixture 为模板边界位置 × 结构场景 × Unicode/实体对照 packages/yjs/src/lib/tests/collaboration/fixtures.ts 现有的 10 个 fixture把缺失的moveNode/splitNode/mergeNode嵌套场景补充为同类 fixture建议与撤销场景从 y-prosemirror 的portable-mixed族suggestions、undo中剥离 ProseMirror 视图层保留其不变式如远端变更不可撤销撤销后光标归位映射到 Plate 的 withTYHistory.ts 等本地实现规模化压测借鉴 yjs 域y-text/y-array/y-map中testRepeatGenerating*系列的做法在 index.slow.ts 的慢速通道中加入随机模糊生成的收敛断言。小结test-index.md不是一份普通的测试清单而是一张可执行的协同测试资产地图它以行号为锚点精确指向上游 300 余个测试函数以portable/portable-mixed/harness/skip/manual五级分类标明每份资产的复用价值并给出空目标lexical-yjs等反面记录。配合本仓库 packages/yjs 的本地实现——CollaborationConnector多端内存连接器、10 个收敛性 fixture、BaseYjsPlugin.init的播种与首同步契约——读者既能追溯每个协同不变式的上游出处也能看到它们在 Plate 插件层如何被验证从而为自己的协同编辑功能建立同样的操作回放 远端镜像收敛测试闭环。【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。