@miliastry/quasar 1.0.5 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (273) hide show
  1. package/dist/Visuals/lyne.css +191 -21
  2. package/dist/Visuals/osu.css +36 -5
  3. package/dist/index.js +9619 -3135
  4. package/dist/index.mjs +9490 -3136
  5. package/dist/types/Analysis/Contracts/AnalysisReport.d.ts +22 -0
  6. package/dist/types/Analysis/Contracts/Contribution.d.ts +68 -0
  7. package/dist/types/Analysis/Contracts/Pass.d.ts +59 -0
  8. package/dist/types/Analysis/Contracts/PipelineContext.d.ts +34 -0
  9. package/dist/types/Analysis/Passes/Analysis/ColorUsageAnalyzer.d.ts +56 -0
  10. package/dist/types/Analysis/Passes/Analysis/GradientAnalyzer.d.ts +111 -0
  11. package/dist/types/Analysis/Passes/Analysis/MergeableColorAnalyzer.d.ts +26 -0
  12. package/dist/types/Analysis/Passes/Analysis/RainbowAnalyzer.d.ts +47 -0
  13. package/dist/types/Analysis/Passes/Analysis/SymbolAnalyzer.d.ts +89 -0
  14. package/dist/types/Analysis/Passes/Analysis/WaveAnalyzer.d.ts +44 -0
  15. package/dist/types/Analysis/Passes/Analysis/__tests__/GradientAnalyzer.test.d.ts +4 -0
  16. package/dist/types/Analysis/Passes/Analysis/__tests__/MergeableColorAnalyzer.test.d.ts +4 -0
  17. package/dist/types/Analysis/Passes/Analysis/__tests__/RainbowAnalyzer.test.d.ts +4 -0
  18. package/dist/types/Analysis/Passes/Analysis/__tests__/WaveAnalyzer.test.d.ts +4 -0
  19. package/dist/types/Analysis/Passes/Decision/DefaultDecision.d.ts +30 -0
  20. package/dist/types/Analysis/Passes/Decision/PaletteRemapDecision.d.ts +169 -0
  21. package/dist/types/Analysis/Passes/Decision/__tests__/DefaultDecision.test.d.ts +4 -0
  22. package/dist/types/Analysis/Passes/Transform/CollapseGradientTransform.d.ts +37 -0
  23. package/dist/types/Analysis/Passes/Transform/MergeColorsTransform.d.ts +28 -0
  24. package/dist/types/Analysis/Passes/Transform/RainbowCollapseTransform.d.ts +19 -0
  25. package/dist/types/Analysis/Passes/Transform/WaveCollapseTransform.d.ts +20 -0
  26. package/dist/types/Analysis/Passes/Utility/CharacterCountAnalyzer.d.ts +19 -0
  27. package/dist/types/Analysis/Pipeline/Pipeline.d.ts +69 -0
  28. package/dist/types/Analysis/Pipeline/PipelineBuilder.d.ts +31 -0
  29. package/dist/types/Analysis/Pipeline/PipelineStage.d.ts +17 -0
  30. package/dist/types/Analysis/Utils/color-utils.d.ts +60 -0
  31. package/dist/types/Analysis/__tests__/Integration.test.d.ts +8 -0
  32. package/dist/types/Analysis/__tests__/PaletteRemap.test.d.ts +14 -0
  33. package/dist/types/Analysis/__tests__/Pipeline.test.d.ts +10 -0
  34. package/dist/types/Analysis/__tests__/RoundTrip.test.d.ts +30 -0
  35. package/dist/types/Analysis/__tests__/SymbolAnalyzer.test.d.ts +11 -0
  36. package/dist/types/Analysis/index.d.ts +40 -0
  37. package/dist/types/BBCode/BBCodeDocumentModel.d.ts +92 -0
  38. package/dist/types/BBCode/BBCodeToGreenNode.d.ts +111 -0
  39. package/dist/types/BBCode/Parser.d.ts +78 -0
  40. package/dist/types/BBCode/index.d.ts +4 -0
  41. package/dist/types/Collab/positions.d.ts +55 -0
  42. package/dist/types/Commands/Command.d.ts +37 -0
  43. package/dist/types/Commands/CommandRegistry.d.ts +48 -0
  44. package/dist/types/Commands/DeleteNode.d.ts +7 -0
  45. package/dist/types/Commands/InsertText.d.ts +8 -0
  46. package/dist/types/Commands/SplitMerge.d.ts +9 -0
  47. package/dist/types/Commands/WrapInTag.d.ts +8 -0
  48. package/dist/types/Commands/index.d.ts +6 -0
  49. package/dist/types/Diff/TreeDiffer.d.ts +52 -0
  50. package/dist/types/Diff/__tests__/TreeDiffer.test.d.ts +1 -0
  51. package/dist/types/Diff/index.d.ts +2 -0
  52. package/dist/types/Edits/EditPlan.d.ts +144 -0
  53. package/dist/types/Edits/Optimizer.d.ts +78 -0
  54. package/dist/types/Edits/Rules/Rule.d.ts +91 -0
  55. package/dist/types/Edits/Rules/dropEmptyTags.d.ts +40 -0
  56. package/dist/types/Edits/Rules/dropRedundantNesting.d.ts +59 -0
  57. package/dist/types/Edits/Rules/index.d.ts +9 -0
  58. package/dist/types/Edits/Rules/mergeAdjacent.d.ts +93 -0
  59. package/dist/types/Edits/Rules/reorderWrappers.d.ts +42 -0
  60. package/dist/types/Edits/Rules/shortenHex.d.ts +30 -0
  61. package/dist/types/Edits/Rules/tagValue.d.ts +56 -0
  62. package/dist/types/Edits/Rules/unwrapInvisibleColor.d.ts +47 -0
  63. package/dist/types/Edits/applyEdits.d.ts +36 -0
  64. package/dist/types/Edits/index.d.ts +6 -0
  65. package/dist/types/Events/EventBus.d.ts +103 -0
  66. package/dist/types/Events/index.d.ts +2 -0
  67. package/dist/types/Formatter/Formatter.d.ts +32 -0
  68. package/dist/types/Formatter/index.d.ts +2 -0
  69. package/dist/types/HTML/HTMLDocumentModel.d.ts +13 -0
  70. package/dist/types/HTML/HTMLToGreenNode.d.ts +4 -0
  71. package/dist/types/Incremental/BracketIndex.d.ts +101 -0
  72. package/dist/types/Incremental/ChangeTracker.d.ts +69 -0
  73. package/dist/types/Incremental/IncrementalParser.d.ts +246 -0
  74. package/dist/types/Incremental/__tests__/BracketIndex.test.d.ts +1 -0
  75. package/dist/types/Incremental/__tests__/IncrementalParser.test.d.ts +1 -0
  76. package/dist/types/Incremental/index.d.ts +4 -0
  77. package/dist/types/Lexer/BBCodeLexer.d.ts +74 -0
  78. package/dist/types/Lexer/Lexer.d.ts +54 -0
  79. package/dist/types/Lexer/index.d.ts +4 -0
  80. package/dist/types/Linter/Linter.d.ts +58 -0
  81. package/dist/types/Linter/index.d.ts +2 -0
  82. package/dist/types/Markdown/MarkdownAST.d.ts +122 -0
  83. package/dist/types/Markdown/MarkdownDocumentModel.d.ts +15 -0
  84. package/dist/types/Markdown/MarkdownLexer.d.ts +155 -0
  85. package/dist/types/Markdown/MarkdownParser.d.ts +42 -0
  86. package/dist/types/Markdown/MarkdownToGreenNode.d.ts +6 -0
  87. package/dist/types/MilHibri/MilHibriDocumentModel.d.ts +21 -0
  88. package/dist/types/Model/DocumentModel.d.ts +318 -0
  89. package/dist/types/Model/NodeFactory.d.ts +29 -0
  90. package/dist/types/Model/TagRegistry.d.ts +165 -0
  91. package/dist/types/Model/index.d.ts +5 -0
  92. package/dist/types/Plugins/PluginAPI.d.ts +44 -0
  93. package/dist/types/Plugins/PluginRegistry.d.ts +81 -0
  94. package/dist/types/Plugins/index.d.ts +3 -0
  95. package/dist/types/Queries/QueryEngine.d.ts +32 -0
  96. package/dist/types/Queries/index.d.ts +1 -0
  97. package/dist/types/Reconciler/SurgicalReconciler.d.ts +23 -0
  98. package/dist/types/RenderPipeline/RenderPipeline.d.ts +46 -0
  99. package/dist/types/RenderPipeline/RenderTree.d.ts +55 -0
  100. package/dist/types/RenderPipeline/index.d.ts +4 -0
  101. package/dist/types/Repair/NestingRepair.d.ts +48 -0
  102. package/dist/types/Repair/index.d.ts +1 -0
  103. package/dist/types/Semantic/SemanticAnalyzer.d.ts +321 -0
  104. package/dist/types/Semantic/index.d.ts +2 -0
  105. package/dist/types/Symbols/SymbolTable.d.ts +47 -0
  106. package/dist/types/Symbols/index.d.ts +1 -0
  107. package/dist/types/Syntax/GreenNode.d.ts +154 -0
  108. package/dist/types/Syntax/GreenNodePool.d.ts +134 -0
  109. package/dist/types/Syntax/NodeMatcher.d.ts +75 -0
  110. package/dist/types/Syntax/RedNode.d.ts +280 -0
  111. package/dist/types/Syntax/RedNodeStore.d.ts +116 -0
  112. package/dist/types/Syntax/TreeBuilder.d.ts +54 -0
  113. package/dist/types/Syntax/__tests__/GreenNode.test.d.ts +1 -0
  114. package/dist/types/Syntax/__tests__/RedNode.test.d.ts +1 -0
  115. package/dist/types/Syntax/__tests__/RedNodeStore.test.d.ts +1 -0
  116. package/dist/types/Syntax/greenEdit.d.ts +64 -0
  117. package/dist/types/Syntax/hash.d.ts +17 -0
  118. package/dist/types/Syntax/index.d.ts +8 -0
  119. package/dist/types/Syntax/nodeAttr.d.ts +63 -0
  120. package/dist/types/Syntax/partition.d.ts +67 -0
  121. package/dist/types/Syntax/preserveNodeIds.d.ts +47 -0
  122. package/dist/types/Tokens/TokenResolver.d.ts +25 -0
  123. package/dist/types/Tokens/index.d.ts +1 -0
  124. package/dist/types/Transactions/Transaction.d.ts +36 -0
  125. package/dist/types/Transactions/UndoManager.d.ts +63 -0
  126. package/dist/types/Transactions/index.d.ts +3 -0
  127. package/dist/types/Transformers/ASTOptimizer.d.ts +34 -0
  128. package/dist/types/Transformers/GradientTransformer.d.ts +18 -0
  129. package/dist/types/Transformers/GrowTransformer.d.ts +16 -0
  130. package/dist/types/Transformers/RainbowTransformer.d.ts +17 -0
  131. package/dist/types/Transformers/SineWaveTransformer.d.ts +17 -0
  132. package/dist/types/Transformers/Transformer.d.ts +17 -0
  133. package/dist/types/Types/core.d.ts +104 -0
  134. package/dist/types/Types/diagnostics.d.ts +85 -0
  135. package/dist/types/Types/index.d.ts +11 -0
  136. package/dist/types/Types/operations.d.ts +109 -0
  137. package/dist/types/Types/queries.d.ts +67 -0
  138. package/dist/types/Types/symbols.d.ts +42 -0
  139. package/dist/types/Types/tokens.d.ts +75 -0
  140. package/dist/types/Utils/BBCodeGenerator.d.ts +21 -0
  141. package/dist/types/Utils/ColorMath.d.ts +92 -0
  142. package/dist/types/Utils/EffectMath.d.ts +643 -0
  143. package/dist/types/Utils/TagVocabulary.d.ts +104 -0
  144. package/dist/types/Utils/TagVocabulary.test.d.ts +1 -0
  145. package/dist/types/Utils/color.d.ts +29 -0
  146. package/dist/types/Utils/dom-to-svg.d.ts +28 -0
  147. package/dist/types/Utils/dom-to-svg.test.d.ts +1 -0
  148. package/dist/types/Utils/treeTransformers.d.ts +160 -0
  149. package/dist/types/Visitors/BBBlocksExporter.d.ts +27 -0
  150. package/dist/types/Visitors/BBCodeExporter.d.ts +90 -0
  151. package/dist/types/Visitors/BlockPatcher.d.ts +91 -0
  152. package/dist/types/Visitors/DOMMorpher.d.ts +13 -0
  153. package/dist/types/Visitors/HTMLRenderer.d.ts +380 -0
  154. package/dist/types/Visitors/JSONExporter.d.ts +42 -0
  155. package/dist/types/Visitors/MarkdownExporter.d.ts +28 -0
  156. package/dist/types/Visitors/SVGRenderer.d.ts +16 -0
  157. package/dist/types/Visitors/TiptapExporter.d.ts +40 -0
  158. package/dist/types/Visitors/Visitor.d.ts +35 -0
  159. package/dist/types/Visitors/index.d.ts +9 -0
  160. package/dist/types/Visuals/BoxDrawer.d.ts +49 -0
  161. package/dist/types/Visuals/index.d.ts +38 -0
  162. package/dist/types/index.d.ts +154 -0
  163. package/package.json +14 -10
  164. package/src/Analysis/Passes/Analysis/ColorUsageAnalyzer.ts +103 -0
  165. package/src/Analysis/Passes/Analysis/GradientAnalyzer.ts +294 -20
  166. package/src/Analysis/Passes/Analysis/SymbolAnalyzer.ts +257 -0
  167. package/src/Analysis/Passes/Decision/PaletteRemapDecision.ts +425 -0
  168. package/src/Analysis/Pipeline/Pipeline.ts +15 -2
  169. package/src/Analysis/index.ts +8 -2
  170. package/src/BBCode/BBCodeDocumentModel.ts +3 -0
  171. package/src/BBCode/BBCodeToGreenNode.ts +34 -11
  172. package/src/BBCode/Parser.ts +38 -1
  173. package/src/Edits/EditPlan.ts +290 -0
  174. package/src/Edits/Optimizer.ts +185 -0
  175. package/src/Edits/Rules/Rule.ts +145 -0
  176. package/src/Edits/Rules/dropEmptyTags.ts +110 -0
  177. package/src/Edits/Rules/dropRedundantNesting.ts +120 -0
  178. package/src/Edits/Rules/index.ts +28 -0
  179. package/src/Edits/Rules/mergeAdjacent.ts +224 -0
  180. package/src/Edits/Rules/reorderWrappers.ts +154 -0
  181. package/src/Edits/Rules/shortenHex.ts +79 -0
  182. package/src/Edits/Rules/tagValue.ts +105 -0
  183. package/src/Edits/Rules/unwrapInvisibleColor.ts +93 -0
  184. package/src/Edits/applyEdits.ts +54 -0
  185. package/src/Edits/index.ts +22 -0
  186. package/src/Events/EventBus.ts +18 -0
  187. package/src/HTML/HTMLToGreenNode.ts +185 -37
  188. package/src/Incremental/BracketIndex.ts +249 -0
  189. package/src/Incremental/IncrementalParser.ts +464 -41
  190. package/src/Lexer/BBCodeLexer.ts +25 -3
  191. package/src/Lexer/index.ts +1 -1
  192. package/src/Markdown/MarkdownAST.ts +65 -2
  193. package/src/Markdown/MarkdownLexer.ts +69 -14
  194. package/src/Markdown/MarkdownParser.ts +749 -62
  195. package/src/Markdown/MarkdownToGreenNode.ts +148 -27
  196. package/src/MilHibri/MilHibriDocumentModel.ts +56 -0
  197. package/src/Model/DocumentModel.ts +138 -5
  198. package/src/Model/TagRegistry.ts +132 -134
  199. package/src/Reconciler/SurgicalReconciler.ts +460 -0
  200. package/src/Repair/NestingRepair.ts +116 -0
  201. package/src/Repair/index.ts +1 -0
  202. package/src/Semantic/SemanticAnalyzer.ts +1802 -65
  203. package/src/Syntax/GreenNodePool.ts +12 -12
  204. package/src/Syntax/RedNode.ts +17 -3
  205. package/src/Syntax/RedNodeStore.ts +9 -1
  206. package/src/Syntax/nodeAttr.ts +172 -0
  207. package/src/Tokens/TokenResolver.ts +55 -0
  208. package/src/Tokens/index.ts +1 -0
  209. package/src/Types/core.ts +15 -0
  210. package/src/Utils/ColorMath.ts +276 -55
  211. package/src/Utils/EffectMath.ts +2462 -0
  212. package/src/Utils/TagVocabulary.ts +141 -0
  213. package/src/Utils/dom-to-svg.ts +28 -12
  214. package/src/Visitors/BBBlocksExporter.ts +15 -1
  215. package/src/Visitors/BBCodeExporter.ts +319 -32
  216. package/src/Visitors/BlockPatcher.ts +87 -11
  217. package/src/Visitors/DOMMorpher.ts +15 -5
  218. package/src/Visitors/HTMLRenderer.ts +452 -142
  219. package/src/Visitors/MarkdownExporter.ts +98 -41
  220. package/src/Visitors/TiptapExporter.ts +1 -0
  221. package/src/Visuals/index.ts +7 -4
  222. package/src/Visuals/lyne.css +191 -21
  223. package/src/Visuals/osu.css +36 -5
  224. package/src/index.ts +172 -5
  225. package/dist/index.d.mts +0 -4609
  226. package/dist/index.d.ts +0 -4609
  227. package/src/Analysis/Passes/Analysis/__tests__/GradientAnalyzer.test.ts +0 -135
  228. package/src/Analysis/Passes/Analysis/__tests__/MergeableColorAnalyzer.test.ts +0 -84
  229. package/src/Analysis/Passes/Analysis/__tests__/RainbowAnalyzer.test.ts +0 -99
  230. package/src/Analysis/Passes/Analysis/__tests__/WaveAnalyzer.test.ts +0 -119
  231. package/src/Analysis/Passes/Decision/__tests__/DefaultDecision.test.ts +0 -179
  232. package/src/Analysis/__tests__/Integration.test.ts +0 -162
  233. package/src/Analysis/__tests__/Pipeline.test.ts +0 -133
  234. package/src/Diff/__tests__/TreeDiffer.test.ts +0 -65
  235. package/src/Incremental/__tests__/IncrementalParser.test.ts +0 -164
  236. package/src/Syntax/__tests__/GreenNode.test.ts +0 -33
  237. package/src/Syntax/__tests__/RedNode.test.ts +0 -81
  238. package/src/Syntax/__tests__/RedNodeStore.test.ts +0 -104
  239. package/src/Tests/ASTOptimizerIdempotence.test.ts +0 -77
  240. package/src/Tests/BlockPatcher.test.ts +0 -437
  241. package/src/Tests/BlockPatcherWindowed.test.ts +0 -364
  242. package/src/Tests/BoxDrawer.test.ts +0 -217
  243. package/src/Tests/BoxRichTitle.test.ts +0 -105
  244. package/src/Tests/Chars500kBenchmark.test.ts +0 -151
  245. package/src/Tests/Chars500kEdits.test.ts +0 -321
  246. package/src/Tests/CollabPositions.test.ts +0 -146
  247. package/src/Tests/CompilerPathProfiling.test.ts +0 -186
  248. package/src/Tests/DOMMorpher.test.ts +0 -142
  249. package/src/Tests/DomPatchPerf.test.ts +0 -60
  250. package/src/Tests/EffectSegments.snapshot.json +0 -616
  251. package/src/Tests/EffectSegments.test.ts +0 -68
  252. package/src/Tests/FindNodeAtOffset.test.ts +0 -65
  253. package/src/Tests/Fuzzer.test.ts +0 -166
  254. package/src/Tests/GreenNodePool.test.ts +0 -153
  255. package/src/Tests/Lexer.test.ts +0 -238
  256. package/src/Tests/LyneMode.test.ts +0 -291
  257. package/src/Tests/ModelCoherence.test.ts +0 -180
  258. package/src/Tests/Partition.test.ts +0 -238
  259. package/src/Tests/PluginTags.test.ts +0 -150
  260. package/src/Tests/ProblematicSection.test.ts +0 -46
  261. package/src/Tests/ProblematicSectionHTML.test.ts +0 -58
  262. package/src/Tests/RedReuse.test.ts +0 -134
  263. package/src/Tests/ReproDelete20k.test.ts +0 -62
  264. package/src/Tests/SemanticValidators.test.ts +0 -136
  265. package/src/Tests/StableNodeIds.test.ts +0 -210
  266. package/src/Tests/StudioColorBloat.test.ts +0 -25
  267. package/src/Tests/StudioDebugText.test.ts +0 -27
  268. package/src/Tests/StudioTrailingChar.test.ts +0 -25
  269. package/src/Tests/StudioValidText.test.ts +0 -25
  270. package/src/Tests/UrlImgBug.test.ts +0 -23
  271. package/src/Tests/VisualBuilderFidelity.test.ts +0 -105
  272. package/src/Tests/referenceDocument.ts +0 -119
  273. package/src/Utils/dom-to-svg.test.ts +0 -86
@@ -1,162 +0,0 @@
1
- /**
2
- * Quasar Analysis Framework — Integration Tests
3
- *
4
- * End-to-end tests that exercise the full pipeline with real passes:
5
- * GradientAnalyzer → DefaultDecision → CollapseGradientTransform
6
- * MergeableColorAnalyzer → DefaultDecision → MergeColorsTransform
7
- */
8
-
9
- import { describe, it, expect } from 'vitest'
10
- import { greenNode, greenLeaf } from '../../Syntax/GreenNode'
11
- import type { GreenNode } from '../../Syntax/GreenNode'
12
- import { Pipeline } from '../Pipeline/Pipeline'
13
- import { PipelineBuilder } from '../Pipeline/PipelineBuilder'
14
- import { GradientAnalyzer } from '../Passes/Analysis/GradientAnalyzer'
15
- import { MergeableColorAnalyzer } from '../Passes/Analysis/MergeableColorAnalyzer'
16
- import { DefaultDecision } from '../Passes/Decision/DefaultDecision'
17
- import { CollapseGradientTransform } from '../Passes/Transform/CollapseGradientTransform'
18
- import { MergeColorsTransform } from '../Passes/Transform/MergeColorsTransform'
19
- import type { PipelineContext } from '../Contracts/PipelineContext'
20
- import { PipelineMode, ExportTarget } from '../Contracts/PipelineContext'
21
-
22
- // ── Helpers ───────────────────────────────────────────────────────
23
-
24
- function colorNode(hex: string, text: string): GreenNode {
25
- // Widths of the real BBCode this stands for: `[color=#RRGGBB]` and `[/color]`.
26
- const textLeaf = greenLeaf('text', text)
27
- return greenNode('color', `=${hex}`, [textLeaf], 8 + hex.length, 8)
28
- }
29
-
30
- const interactiveContext: PipelineContext = {
31
- mode: PipelineMode.Interactive,
32
- target: ExportTarget.Miliastry,
33
- featureFlags: {},
34
- metadata: {},
35
- }
36
-
37
- const batchOsuContext: PipelineContext = {
38
- mode: PipelineMode.Batch,
39
- target: ExportTarget.Osu,
40
- featureFlags: {},
41
- metadata: {},
42
- }
43
-
44
- // ── Tests ─────────────────────────────────────────────────────────
45
-
46
- describe('Gradient pipeline (full integration)', () => {
47
- it('collapses a clear gradient with miliastry target', () => {
48
- // [color=#FF0000]H[/color][color=#EE1100]e[/color][color=#DD2200]l[/color][color=#CC3300]l[/color][color=#BB4400]o[/color]
49
- const tree = greenNode('document', '', [
50
- colorNode('#FF0000', 'H'),
51
- colorNode('#EE1100', 'e'),
52
- colorNode('#DD2200', 'l'),
53
- colorNode('#CC3300', 'l'),
54
- colorNode('#BB4400', 'o'),
55
- ])
56
-
57
- const pipeline = new PipelineBuilder()
58
- .analysis(new GradientAnalyzer())
59
- .decision(new DefaultDecision())
60
- .transform(new CollapseGradientTransform())
61
- .build()
62
-
63
- const result = pipeline.run(tree, interactiveContext)
64
- const treeChildren = result.tree.children as GreenNode[]
65
-
66
- // After collapsing, we should have a single gradient node
67
- const gradientNodes = treeChildren.filter(c => c.kind === 'gradient')
68
- expect(gradientNodes.length).toBeGreaterThanOrEqual(1)
69
- })
70
-
71
- it('preserves non-gradient color sequences with osu target', () => {
72
- // Random colors — low confidence, below osu threshold
73
- const tree = greenNode('document', '', [
74
- colorNode('#FF0000', 'A'),
75
- colorNode('#00FF00', 'B'),
76
- colorNode('#0000FF', 'C'),
77
- ])
78
-
79
- const pipeline = new PipelineBuilder()
80
- .analysis(new GradientAnalyzer())
81
- .decision(new DefaultDecision())
82
- .transform(new CollapseGradientTransform())
83
- .build()
84
-
85
- const result = pipeline.run(tree, batchOsuContext)
86
- const treeChildren = result.tree.children as GreenNode[]
87
-
88
- // With only 3 random colors, confidence should be below osu threshold
89
- const gradientNodes = treeChildren.filter(c => c.kind === 'gradient')
90
- expect(gradientNodes).toHaveLength(0)
91
- })
92
-
93
- it('produces an AnalysisReport with contributions', () => {
94
- const tree = greenNode('document', '', [
95
- colorNode('#FF0000', 'A'),
96
- colorNode('#EE1100', 'B'),
97
- colorNode('#DD2200', 'C'),
98
- colorNode('#CC3300', 'D'),
99
- ])
100
-
101
- const pipeline = new PipelineBuilder()
102
- .analysis(new GradientAnalyzer())
103
- .build()
104
-
105
- const result = pipeline.run(tree, interactiveContext)
106
-
107
- expect(result.report.contributions.length).toBeGreaterThanOrEqual(1)
108
- expect(result.report.elapsedMs).toBeGreaterThanOrEqual(0)
109
- expect(result.report.passCount).toBe(1)
110
- })
111
- })
112
-
113
- describe('Merge colors pipeline (full integration)', () => {
114
- it('merges identical consecutive color nodes', () => {
115
- // [color=#FF0000]H[/color][color=#FF0000]e[/color][color=#FF0000]l[/color]
116
- const tree = greenNode('document', '', [
117
- colorNode('#FF0000', 'H'),
118
- colorNode('#FF0000', 'e'),
119
- colorNode('#FF0000', 'l'),
120
- ])
121
-
122
- const pipeline = new PipelineBuilder()
123
- .analysis(new MergeableColorAnalyzer())
124
- .decision(new DefaultDecision())
125
- .transform(new MergeColorsTransform())
126
- .build()
127
-
128
- const result = pipeline.run(tree, interactiveContext)
129
- const treeChildren = result.tree.children as GreenNode[]
130
-
131
- // Should be 1 colour node (merged) instead of 3
132
- const colorNodes = treeChildren.filter(c => c.kind === 'color')
133
- expect(colorNodes).toHaveLength(1)
134
- })
135
- })
136
-
137
- describe('Combined pipeline', () => {
138
- it('runs multiple analysis passes and produces correct report', () => {
139
- const tree = greenNode('document', '', [
140
- colorNode('#FF0000', 'A'),
141
- colorNode('#EE1100', 'B'),
142
- colorNode('#DD2200', 'C'),
143
- colorNode('#FF0000', 'X'), // part of mergeable (different from gradient sequence)
144
- colorNode('#FF0000', 'Y'),
145
- ])
146
-
147
- const pipeline = new PipelineBuilder()
148
- .analysis(new GradientAnalyzer(), new MergeableColorAnalyzer())
149
- .decision(new DefaultDecision())
150
- .build()
151
-
152
- const result = pipeline.run(tree, interactiveContext)
153
-
154
- // Should have contributions from both analyzers
155
- const analysisContributions = result.report.contributions
156
- const gradientContributions = analysisContributions.filter(c => c.kind === 'semantic' && c.label === 'Gradient')
157
- const mergeContributions = analysisContributions.filter(c => c.kind === 'optimization' && c.label === 'Merge Colors')
158
-
159
- expect(gradientContributions.length).toBeGreaterThanOrEqual(1)
160
- expect(mergeContributions.length).toBeGreaterThanOrEqual(1)
161
- })
162
- })
@@ -1,133 +0,0 @@
1
- /**
2
- * Quasar Analysis Framework — Pipeline Tests
3
- *
4
- * Validates that the core pipeline infrastructure works:
5
- * - Pass interfaces are correctly typed
6
- * - PipelineBuilder assembles passes
7
- * - Pipeline executes them in order
8
- * - CharacterCountAnalyzer (PoC) produces expected metrics
9
- */
10
-
11
- import { describe, it, expect } from 'vitest'
12
- import { greenNode, greenLeaf } from '../../Syntax/GreenNode'
13
- import type { GreenNode } from '../../Syntax/GreenNode'
14
- import { Pipeline } from '../Pipeline/Pipeline'
15
- import { PipelineBuilder } from '../Pipeline/PipelineBuilder'
16
- import { CharacterCountAnalyzer } from '../Passes/Utility/CharacterCountAnalyzer'
17
- import type { PipelineContext } from '../Contracts/PipelineContext'
18
- import { PipelineMode, ExportTarget } from '../Contracts/PipelineContext'
19
-
20
- // ── Helpers ───────────────────────────────────────────────────────
21
-
22
- const mockContext: PipelineContext = {
23
- mode: PipelineMode.Batch,
24
- target: ExportTarget.Osu,
25
- featureFlags: {},
26
- metadata: {},
27
- }
28
-
29
- function buildSimpleTree(): GreenNode {
30
- // root
31
- // ├── heading "Hello"
32
- // └── text "World"
33
- const headingText = greenLeaf('text', 'Hello')
34
- const heading = greenNode('heading', '', [headingText])
35
- const worldText = greenLeaf('text', 'World')
36
- return greenNode('root', '', [heading, worldText])
37
- }
38
-
39
- // ── Tests ─────────────────────────────────────────────────────────
40
-
41
- describe('Pipeline Infrastructure', () => {
42
- describe('PipelineBuilder', () => {
43
- it('builds a pipeline from passes', () => {
44
- const pipeline = new PipelineBuilder()
45
- .analysis(new CharacterCountAnalyzer())
46
- .build()
47
-
48
- expect(pipeline).toBeInstanceOf(Pipeline)
49
- })
50
-
51
- it('accepts multiple analysis passes', () => {
52
- const pipeline = new PipelineBuilder()
53
- .analysis(new CharacterCountAnalyzer(), new CharacterCountAnalyzer())
54
- .build()
55
-
56
- expect(pipeline).toBeInstanceOf(Pipeline)
57
- })
58
-
59
- it('rejects analysis passes in decision/transform slots at type level', () => {
60
- // TypeScript enforces that builder.analysis() only accepts AnalyzerPass
61
- // instances. This is a compile-time check that can't be validated with
62
- // vitest. At runtime we just verify the builder doesn't throw for valid usage.
63
- const builder = new PipelineBuilder()
64
- expect(() => builder.analysis(new CharacterCountAnalyzer())).not.toThrow()
65
- })
66
- })
67
-
68
- describe('Pipeline execution', () => {
69
- it('executes analysis passes and produces a report', () => {
70
- const pipeline = new PipelineBuilder()
71
- .analysis(new CharacterCountAnalyzer())
72
- .build()
73
-
74
- const tree = buildSimpleTree()
75
- const result = pipeline.run(tree, mockContext)
76
-
77
- expect(result.report.contributions).toHaveLength(1)
78
- expect(result.report.passCount).toBe(1)
79
- expect(result.report.elapsedMs).toBeGreaterThanOrEqual(0)
80
- })
81
-
82
- it('preserves the original tree when no transforms are registered', () => {
83
- const pipeline = new PipelineBuilder()
84
- .analysis(new CharacterCountAnalyzer())
85
- .build()
86
-
87
- const tree = buildSimpleTree()
88
- const result = pipeline.run(tree, mockContext)
89
-
90
- expect(result.tree).toBe(tree) // same reference since no transforms
91
- })
92
-
93
- it('reports correct character count', () => {
94
- const pipeline = new PipelineBuilder()
95
- .analysis(new CharacterCountAnalyzer())
96
- .build()
97
-
98
- const tree = buildSimpleTree()
99
- const result = pipeline.run(tree, mockContext)
100
-
101
- const metricsContribution = result.report.contributions[0]
102
- expect(metricsContribution).toMatchObject({
103
- kind: 'metrics',
104
- metrics: { characters: 10 }, // "Hello" (5) + "World" (5)
105
- })
106
- })
107
-
108
- it('handles empty trees gracefully', () => {
109
- const pipeline = new PipelineBuilder()
110
- .analysis(new CharacterCountAnalyzer())
111
- .build()
112
-
113
- const emptyTree = greenNode('root', '', [])
114
- const result = pipeline.run(emptyTree, mockContext)
115
-
116
- const metricsContribution = result.report.contributions[0]
117
- expect(metricsContribution).toMatchObject({
118
- kind: 'metrics',
119
- metrics: { characters: 0 },
120
- })
121
- })
122
- })
123
-
124
- describe('PipelineBuilder direct usage', () => {
125
- it('works via new PipelineBuilder()', () => {
126
- const pipeline = new PipelineBuilder()
127
- .analysis(new CharacterCountAnalyzer())
128
- .build()
129
-
130
- expect(pipeline).toBeInstanceOf(Pipeline)
131
- })
132
- })
133
- })
@@ -1,65 +0,0 @@
1
- import { describe, it, expect } from 'vitest'
2
- import { TreeDiffer } from '../TreeDiffer'
3
- import { RedNode } from '../../Syntax/RedNode'
4
- import { GreenNode } from '../../Syntax/GreenNode'
5
- import type { NodeId } from '../../Types/core'
6
-
7
- /** Fixed, readable NodeIds. `NodeId` is branded, so literals need the cast. */
8
- const nid = (s: string) => s as unknown as NodeId
9
-
10
- describe('TreeDiffer', () => {
11
- it('should detect moves/updates by content when IDs differ', () => {
12
- // Create old tree
13
- const oldChild = new RedNode(new GreenNode('paragraph', 'hello', [], 0, 0, 5), { id: nid('old-id'), kind: 'paragraph' })
14
- const oldRoot = new RedNode(new GreenNode('document', '', [], 0, 0, 5), { id: nid('root'), kind: 'document' })
15
- oldRoot.children.push(oldChild)
16
- oldChild.parent = oldRoot
17
-
18
- // Create new tree with different ID but same content
19
- const newChild = new RedNode(new GreenNode('paragraph', 'hello', [], 0, 0, 5), { id: nid('new-id'), kind: 'paragraph' })
20
- const newRoot = new RedNode(new GreenNode('document', '', [], 0, 0, 5), { id: nid('root'), kind: 'document' })
21
- newRoot.children.push(newChild)
22
- newChild.parent = newRoot
23
-
24
- const differ = new TreeDiffer()
25
- const result = differ.diff(oldRoot, newRoot)
26
-
27
- // Should detect as 1 preserve/update since index is same, not a delete and insert
28
- // Because the position didn't change it's treated as update when IDs change but content is same.
29
- expect(result.stats.inserts).toBe(0)
30
- expect(result.stats.deletes).toBe(0)
31
- expect(result.stats.updates).toBe(1)
32
-
33
- expect(result.operations[0].kind).toBe('update')
34
- expect(result.operations[0].node.id).toBe(nid('new-id'))
35
- expect(result.operations[0].oldNode?.id).toBe(nid('old-id'))
36
- })
37
-
38
- it('should detect moves when content matches and index changes', () => {
39
- const oldChild1 = new RedNode(new GreenNode('paragraph', 'a', [], 0, 0, 1), { id: nid('id1'), kind: 'paragraph' })
40
- const oldChild2 = new RedNode(new GreenNode('paragraph', 'b', [], 0, 0, 1), { id: nid('id2'), kind: 'paragraph' })
41
- const oldRoot = new RedNode(new GreenNode('document', '', [], 0, 0, 2), { id: nid('root'), kind: 'document' })
42
- oldRoot.children.push(oldChild1, oldChild2)
43
- oldChild1.parent = oldRoot
44
- oldChild2.parent = oldRoot
45
-
46
- const newChild1 = new RedNode(new GreenNode('paragraph', 'a', [], 0, 0, 1), { id: nid('id1'), kind: 'paragraph' }) // same id
47
- const newChild2 = new RedNode(new GreenNode('paragraph', 'b', [], 0, 0, 1), { id: nid('id3'), kind: 'paragraph' }) // different id, same content
48
- const newRoot = new RedNode(new GreenNode('document', '', [], 0, 0, 2), { id: nid('root'), kind: 'document' })
49
- // Swap order to trigger move
50
- newRoot.children.push(newChild2, newChild1)
51
- newChild1.parent = newRoot
52
- newChild2.parent = newRoot
53
-
54
- const differ = new TreeDiffer()
55
- const result = differ.diff(oldRoot, newRoot)
56
-
57
- // id2 moved to index 0 (and changed id to id3), id1 moved to index 1
58
- expect(result.stats.inserts).toBe(0)
59
- expect(result.stats.deletes).toBe(0)
60
-
61
- // We should have ops for moves/updates
62
- const hasMoveOrUpdateId3 = result.operations.some(op => op.node.id === nid('id3') && (op.kind === 'move' || op.kind === 'update'))
63
- expect(hasMoveOrUpdateId3).toBe(true)
64
- })
65
- })
@@ -1,164 +0,0 @@
1
- import { describe, it, expect } from 'vitest'
2
- import { BBCodeDocumentModel } from '../../BBCode/BBCodeDocumentModel'
3
- import { GreenNode } from '../../Syntax/GreenNode'
4
- import { checkPartition } from '../../Syntax/partition'
5
- import { IncrementalParser } from '../IncrementalParser'
6
-
7
- /**
8
- * The contract of an incremental reparse is not "it is fast" — it is "you
9
- * cannot tell". Every test here compares the incrementally updated tree
10
- * against a full rebuild of the same final text and demands they be identical,
11
- * because the failure mode of the old implementation was precisely a tree that
12
- * looked plausible and rendered wrong.
13
- *
14
- * Note that RedNode identity is deliberately NOT preserved: the red tree is
15
- * derived from the new green root, exactly as a full rebuild would derive it.
16
- * The previous implementation kept ids for untouched siblings by splicing red
17
- * nodes in place, which is also how it ended up with ranges that no longer
18
- * described the text. Nothing depends on those ids — `DOMMorpher` matches on
19
- * structure (`isEqualNode`), not on `data-node-id`.
20
- */
21
-
22
- /** First structural difference between two green trees, or null if identical. */
23
- function firstDiff(a: GreenNode, b: GreenNode, path = a.kind): string | null {
24
- if (a.kind !== b.kind) return `${path}: kind ${a.kind} vs ${b.kind}`
25
- if (a.text !== b.text) return `${path}: text ${JSON.stringify(a.text)} vs ${JSON.stringify(b.text)}`
26
- if (a.width !== b.width) return `${path}: width ${a.width} vs ${b.width}`
27
- if (a.leadingWidth !== b.leadingWidth || a.trailingWidth !== b.trailingWidth) {
28
- return `${path}: widths ${a.leadingWidth}/${a.trailingWidth} vs ${b.leadingWidth}/${b.trailingWidth}`
29
- }
30
- if (a.children.length !== b.children.length) {
31
- return `${path}: ${a.children.length} children vs ${b.children.length}`
32
- }
33
- for (let i = 0; i < a.children.length; i++) {
34
- const child = a.children[i] as GreenNode
35
- const d = firstDiff(child, b.children[i] as GreenNode, `${path}/${child.kind}[${i}]`)
36
- if (d) return d
37
- }
38
- return null
39
- }
40
-
41
- /**
42
- * Apply `edits` incrementally and assert the result matches a full rebuild.
43
- *
44
- * The size thresholds are lifted so these documents stay short and readable:
45
- * they are performance tuning, and what is under test here is the splice.
46
- */
47
- function expectMatchesRebuild(source: string, edits: string[]): BBCodeDocumentModel {
48
- const model = new BBCodeDocumentModel({ source, autoAnalyze: false })
49
- ;(model as { incrementalParser: IncrementalParser }).incrementalParser =
50
- new IncrementalParser({ minSourceLength: 0, maxRegionFraction: 1 })
51
- for (const text of edits) model.applyTextUpdate(text)
52
-
53
- const final = edits[edits.length - 1]
54
- const truth = new BBCodeDocumentModel({ source: final, autoAnalyze: false })
55
-
56
- expect(firstDiff(model.greenRoot!, truth.greenRoot!)).toBeNull()
57
- expect(checkPartition(model.greenRoot!, final.length, { limit: 5 })).toEqual([])
58
- // The red tree must describe the same text as the green one it came from.
59
- expect(model.redRoot!.range).toEqual({ start: 0, end: final.length })
60
- return model
61
- }
62
-
63
- describe('IncrementalParser', () => {
64
- it('reparses only the affected container', () => {
65
- const source = '[quote]Hello[/quote][box]World[/box]'
66
- const model = expectMatchesRebuild(source, ['[quote]Hello![/quote][box]World[/box]'])
67
-
68
- expect(model.lastReparsePath).toBe('incremental')
69
- const quote = model.redRoot!.children.find(c => c.kind === 'quote')!
70
- expect(quote.children[0].text).toContain('Hello!')
71
- })
72
-
73
- it('shifts the ranges of everything after the edit', () => {
74
- // The defect this pins: siblings and ancestors used to keep stale ranges.
75
- const model = expectMatchesRebuild(
76
- '[quote]ab[/quote][box]cd[/box]',
77
- ['[quote]abXYZ[/quote][box]cd[/box]'],
78
- )
79
-
80
- expect(model.lastReparsePath).toBe('incremental')
81
- const box = model.redRoot!.children.find(c => c.kind === 'box')!
82
- // '[quote]abXYZ[/quote]' is 20 chars, so [box] must start at 20, not 17.
83
- expect(box.range.start).toBe(20)
84
- })
85
-
86
- it('keeps green and red describing the same tree', () => {
87
- // `reparse` used to return the OLD green root while returning a NEW red
88
- // one, leaving the model permanently desynchronised.
89
- const model = expectMatchesRebuild('[notice]uno[/notice]', ['[notice]uno dos[/notice]'])
90
-
91
- expect(model.greenRoot).toBe(model.redRoot!.green)
92
- expect(model.greenRoot!.width).toBe('[notice]uno dos[/notice]'.length)
93
- })
94
-
95
- it('survives a long sequence of edits in the same container', () => {
96
- const base = '[centre][b]hola[/b] mundo[/centre]'
97
- const edits: string[] = []
98
- let text = base
99
- for (const ch of 'abcdefghij') {
100
- text = text.slice(0, 15) + ch + text.slice(15)
101
- edits.push(text)
102
- }
103
- const model = expectMatchesRebuild(base, edits)
104
- expect(model.lastReparsePath).toBe('incremental')
105
- })
106
-
107
- describe('edita a nivel de raíz sin caer a rebuild', () => {
108
- // Escribir al final de un post es la posición de caret más común que hay, y
109
- // durante mucho tiempo fue la que peor se comportaba: ningún contenedor de
110
- // bloque la envolvía, así que cada pulsación reconstruía el documento
111
- // entero. Con la ventana de hermanos, el padre es el propio `document`.
112
- it('añadir al final', () => {
113
- const model = expectMatchesRebuild('[quote]hola[/quote]', ['[quote]hola[/quote]cola'])
114
- expect(model.lastReparsePath).toBe('incremental')
115
- })
116
-
117
- it('escribir dentro de un párrafo suelto', () => {
118
- const model = expectMatchesRebuild(
119
- 'primer parrafo\n\nsegundo parrafo',
120
- ['primer parrafo\n\nsegundo parrafoX'],
121
- )
122
- expect(model.lastReparsePath).toBe('incremental')
123
- })
124
-
125
- it('una línea en blanco nueva PARTE el párrafo', () => {
126
- // El caso que obliga a que `paragraph` sea opaco: la división solo es
127
- // visible reparseando a nivel de raíz, no dentro del párrafo.
128
- expectMatchesRebuild('uno dos tres', ['uno\n\ndos tres'])
129
- })
130
-
131
- it('borrar la línea en blanco FUNDE los párrafos', () => {
132
- // El caso que obliga a ensanchar la ventana: el cambio solo toca el nodo
133
- // de en medio, y la fusión ocurre entre sus dos vecinos.
134
- expectMatchesRebuild('uno\n\ndos', ['uno\ndos', 'unodos'])
135
- })
136
- })
137
-
138
- it('handles deletions as well as insertions', () => {
139
- expectMatchesRebuild('[quote]abcdefgh[/quote]tail', [
140
- '[quote]abcdefg[/quote]tail',
141
- '[quote]abcdef[/quote]tail',
142
- '[quote]abc[/quote]tail',
143
- ])
144
- })
145
-
146
- describe('declines to splice when it cannot be sure', () => {
147
- const cases: [string, string, string][] = [
148
- // A nested container of the same kind would steal the closing delimiter.
149
- ['un [centre] anidado', '[centre]hola[/centre]', '[centre]ho[centre]la[/centre]'],
150
- // A half-typed tag leaves a bracket the region cannot resolve alone.
151
- ['un [ a medias', '[quote]hola[/quote]', '[quote]ho[la[/quote]'],
152
- // An unclosed [code] would swallow past the region.
153
- ['un [code] sin cerrar', '[quote]hola[/quote]', '[quote]ho[code]la[/quote]'],
154
- ]
155
-
156
- for (const [name, source, edited] of cases) {
157
- it(name, () => {
158
- const model = expectMatchesRebuild(source, [edited])
159
- expect(model.lastReparsePath).toBe('full_rebuild')
160
- expect(model.lastReparseFallbackReason).not.toBeNull()
161
- })
162
- }
163
- })
164
- })
@@ -1,33 +0,0 @@
1
- import { describe, it, expect } from 'vitest'
2
- import { greenNode, greenLeaf } from '../GreenNode'
3
-
4
- describe('GreenNode', () => {
5
- it('creates a green node with correct properties', () => {
6
- const leaf1 = greenLeaf('Text', 'hello ')
7
- const leaf2 = greenLeaf('Text', 'world')
8
- const node = greenNode('Paragraph', '', [leaf1, leaf2])
9
-
10
- expect(node.kind).toBe('Paragraph')
11
- expect(node.text).toBe('')
12
- expect(node.width).toBe(11)
13
- expect(node.childCount).toBe(2)
14
- expect(node.isLeaf).toBe(false)
15
-
16
- expect(leaf1.isLeaf).toBe(true)
17
- expect(leaf1.text).toBe('hello ')
18
- expect(leaf1.width).toBe(6)
19
- })
20
-
21
- it('is immutable', () => {
22
- const leaf1 = greenLeaf('Text', 'hello')
23
- const leaf2 = greenLeaf('Text', 'world')
24
- const input = [leaf1]
25
- const node = greenNode('Paragraph', '', input)
26
-
27
- // Children should be a copy, not the same reference as input
28
- expect(node.children).not.toBe(input)
29
- // But should contain the same elements
30
- expect(node.children.length).toBe(1)
31
- expect(node.children[0]).toBe(leaf1)
32
- })
33
- })
@@ -1,81 +0,0 @@
1
- import { describe, it, expect } from 'vitest'
2
- import { RedNode } from '../RedNode'
3
- import { greenNode, greenLeaf } from '../GreenNode'
4
-
5
- describe('RedNode', () => {
6
- it('creates a red node from a green node', () => {
7
- const green = greenLeaf('Text', 'hello')
8
- const red = new RedNode(green)
9
-
10
- expect(red.kind).toBe('Text')
11
- expect(red.text).toBe('hello')
12
- expect(red.version).toBe(1)
13
- expect(red.id).toBeDefined()
14
- expect(red.parent).toBeNull()
15
- })
16
-
17
- it('supports tree navigation', () => {
18
- const rootGreen = greenNode('Root', '')
19
- const childGreen = greenLeaf('Text', 'hello')
20
-
21
- const root = new RedNode(rootGreen)
22
- const child = new RedNode(childGreen)
23
-
24
- RedNode.allowMutation(() => {
25
- root.appendChild(child)
26
- })
27
-
28
- expect(child.parent).toBe(root)
29
- expect(root.childCount).toBe(1)
30
- expect(root.childAt(0)).toBe(child)
31
- expect(child.root).toBe(root)
32
- expect(child.depth).toBe(1)
33
- expect(root.depth).toBe(0)
34
- })
35
-
36
- it('supports mutation and bumps version', () => {
37
- const rootGreen = greenNode('Root', '')
38
- const childGreen = greenLeaf('Text', 'hello')
39
-
40
- const root = new RedNode(rootGreen)
41
- const child = new RedNode(childGreen)
42
-
43
- expect(root.version).toBe(1)
44
-
45
- RedNode.allowMutation(() => {
46
- root.appendChild(child)
47
- })
48
- expect(root.version).toBe(2)
49
-
50
- RedNode.allowMutation(() => {
51
- root.removeChild(child.id)
52
- })
53
- expect(root.version).toBe(3)
54
- expect(root.childCount).toBe(0)
55
- expect(child.parent).toBeNull()
56
- })
57
-
58
- it('throws an error if mutation methods are called without an active transaction or lock', () => {
59
- const rootGreen = greenNode('Root', '')
60
- const childGreen = greenLeaf('Text', 'hello')
61
-
62
- const root = new RedNode(rootGreen)
63
- const child = new RedNode(childGreen)
64
-
65
- expect(() => {
66
- root.appendChild(child)
67
- }).toThrow(/mutation boundary/)
68
-
69
- RedNode.allowMutation(() => {
70
- root.appendChild(child)
71
- })
72
-
73
- expect(() => {
74
- root.removeChild(child.id)
75
- }).toThrow(/mutation boundary/)
76
-
77
- expect(() => {
78
- root.bumpVersion()
79
- }).toThrow(/mutation boundary/)
80
- })
81
- })