@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.
- package/dist/Visuals/lyne.css +191 -21
- package/dist/Visuals/osu.css +36 -5
- package/dist/index.js +9619 -3135
- package/dist/index.mjs +9490 -3136
- package/dist/types/Analysis/Contracts/AnalysisReport.d.ts +22 -0
- package/dist/types/Analysis/Contracts/Contribution.d.ts +68 -0
- package/dist/types/Analysis/Contracts/Pass.d.ts +59 -0
- package/dist/types/Analysis/Contracts/PipelineContext.d.ts +34 -0
- package/dist/types/Analysis/Passes/Analysis/ColorUsageAnalyzer.d.ts +56 -0
- package/dist/types/Analysis/Passes/Analysis/GradientAnalyzer.d.ts +111 -0
- package/dist/types/Analysis/Passes/Analysis/MergeableColorAnalyzer.d.ts +26 -0
- package/dist/types/Analysis/Passes/Analysis/RainbowAnalyzer.d.ts +47 -0
- package/dist/types/Analysis/Passes/Analysis/SymbolAnalyzer.d.ts +89 -0
- package/dist/types/Analysis/Passes/Analysis/WaveAnalyzer.d.ts +44 -0
- package/dist/types/Analysis/Passes/Analysis/__tests__/GradientAnalyzer.test.d.ts +4 -0
- package/dist/types/Analysis/Passes/Analysis/__tests__/MergeableColorAnalyzer.test.d.ts +4 -0
- package/dist/types/Analysis/Passes/Analysis/__tests__/RainbowAnalyzer.test.d.ts +4 -0
- package/dist/types/Analysis/Passes/Analysis/__tests__/WaveAnalyzer.test.d.ts +4 -0
- package/dist/types/Analysis/Passes/Decision/DefaultDecision.d.ts +30 -0
- package/dist/types/Analysis/Passes/Decision/PaletteRemapDecision.d.ts +169 -0
- package/dist/types/Analysis/Passes/Decision/__tests__/DefaultDecision.test.d.ts +4 -0
- package/dist/types/Analysis/Passes/Transform/CollapseGradientTransform.d.ts +37 -0
- package/dist/types/Analysis/Passes/Transform/MergeColorsTransform.d.ts +28 -0
- package/dist/types/Analysis/Passes/Transform/RainbowCollapseTransform.d.ts +19 -0
- package/dist/types/Analysis/Passes/Transform/WaveCollapseTransform.d.ts +20 -0
- package/dist/types/Analysis/Passes/Utility/CharacterCountAnalyzer.d.ts +19 -0
- package/dist/types/Analysis/Pipeline/Pipeline.d.ts +69 -0
- package/dist/types/Analysis/Pipeline/PipelineBuilder.d.ts +31 -0
- package/dist/types/Analysis/Pipeline/PipelineStage.d.ts +17 -0
- package/dist/types/Analysis/Utils/color-utils.d.ts +60 -0
- package/dist/types/Analysis/__tests__/Integration.test.d.ts +8 -0
- package/dist/types/Analysis/__tests__/PaletteRemap.test.d.ts +14 -0
- package/dist/types/Analysis/__tests__/Pipeline.test.d.ts +10 -0
- package/dist/types/Analysis/__tests__/RoundTrip.test.d.ts +30 -0
- package/dist/types/Analysis/__tests__/SymbolAnalyzer.test.d.ts +11 -0
- package/dist/types/Analysis/index.d.ts +40 -0
- package/dist/types/BBCode/BBCodeDocumentModel.d.ts +92 -0
- package/dist/types/BBCode/BBCodeToGreenNode.d.ts +111 -0
- package/dist/types/BBCode/Parser.d.ts +78 -0
- package/dist/types/BBCode/index.d.ts +4 -0
- package/dist/types/Collab/positions.d.ts +55 -0
- package/dist/types/Commands/Command.d.ts +37 -0
- package/dist/types/Commands/CommandRegistry.d.ts +48 -0
- package/dist/types/Commands/DeleteNode.d.ts +7 -0
- package/dist/types/Commands/InsertText.d.ts +8 -0
- package/dist/types/Commands/SplitMerge.d.ts +9 -0
- package/dist/types/Commands/WrapInTag.d.ts +8 -0
- package/dist/types/Commands/index.d.ts +6 -0
- package/dist/types/Diff/TreeDiffer.d.ts +52 -0
- package/dist/types/Diff/__tests__/TreeDiffer.test.d.ts +1 -0
- package/dist/types/Diff/index.d.ts +2 -0
- package/dist/types/Edits/EditPlan.d.ts +144 -0
- package/dist/types/Edits/Optimizer.d.ts +78 -0
- package/dist/types/Edits/Rules/Rule.d.ts +91 -0
- package/dist/types/Edits/Rules/dropEmptyTags.d.ts +40 -0
- package/dist/types/Edits/Rules/dropRedundantNesting.d.ts +59 -0
- package/dist/types/Edits/Rules/index.d.ts +9 -0
- package/dist/types/Edits/Rules/mergeAdjacent.d.ts +93 -0
- package/dist/types/Edits/Rules/reorderWrappers.d.ts +42 -0
- package/dist/types/Edits/Rules/shortenHex.d.ts +30 -0
- package/dist/types/Edits/Rules/tagValue.d.ts +56 -0
- package/dist/types/Edits/Rules/unwrapInvisibleColor.d.ts +47 -0
- package/dist/types/Edits/applyEdits.d.ts +36 -0
- package/dist/types/Edits/index.d.ts +6 -0
- package/dist/types/Events/EventBus.d.ts +103 -0
- package/dist/types/Events/index.d.ts +2 -0
- package/dist/types/Formatter/Formatter.d.ts +32 -0
- package/dist/types/Formatter/index.d.ts +2 -0
- package/dist/types/HTML/HTMLDocumentModel.d.ts +13 -0
- package/dist/types/HTML/HTMLToGreenNode.d.ts +4 -0
- package/dist/types/Incremental/BracketIndex.d.ts +101 -0
- package/dist/types/Incremental/ChangeTracker.d.ts +69 -0
- package/dist/types/Incremental/IncrementalParser.d.ts +246 -0
- package/dist/types/Incremental/__tests__/BracketIndex.test.d.ts +1 -0
- package/dist/types/Incremental/__tests__/IncrementalParser.test.d.ts +1 -0
- package/dist/types/Incremental/index.d.ts +4 -0
- package/dist/types/Lexer/BBCodeLexer.d.ts +74 -0
- package/dist/types/Lexer/Lexer.d.ts +54 -0
- package/dist/types/Lexer/index.d.ts +4 -0
- package/dist/types/Linter/Linter.d.ts +58 -0
- package/dist/types/Linter/index.d.ts +2 -0
- package/dist/types/Markdown/MarkdownAST.d.ts +122 -0
- package/dist/types/Markdown/MarkdownDocumentModel.d.ts +15 -0
- package/dist/types/Markdown/MarkdownLexer.d.ts +155 -0
- package/dist/types/Markdown/MarkdownParser.d.ts +42 -0
- package/dist/types/Markdown/MarkdownToGreenNode.d.ts +6 -0
- package/dist/types/MilHibri/MilHibriDocumentModel.d.ts +21 -0
- package/dist/types/Model/DocumentModel.d.ts +318 -0
- package/dist/types/Model/NodeFactory.d.ts +29 -0
- package/dist/types/Model/TagRegistry.d.ts +165 -0
- package/dist/types/Model/index.d.ts +5 -0
- package/dist/types/Plugins/PluginAPI.d.ts +44 -0
- package/dist/types/Plugins/PluginRegistry.d.ts +81 -0
- package/dist/types/Plugins/index.d.ts +3 -0
- package/dist/types/Queries/QueryEngine.d.ts +32 -0
- package/dist/types/Queries/index.d.ts +1 -0
- package/dist/types/Reconciler/SurgicalReconciler.d.ts +23 -0
- package/dist/types/RenderPipeline/RenderPipeline.d.ts +46 -0
- package/dist/types/RenderPipeline/RenderTree.d.ts +55 -0
- package/dist/types/RenderPipeline/index.d.ts +4 -0
- package/dist/types/Repair/NestingRepair.d.ts +48 -0
- package/dist/types/Repair/index.d.ts +1 -0
- package/dist/types/Semantic/SemanticAnalyzer.d.ts +321 -0
- package/dist/types/Semantic/index.d.ts +2 -0
- package/dist/types/Symbols/SymbolTable.d.ts +47 -0
- package/dist/types/Symbols/index.d.ts +1 -0
- package/dist/types/Syntax/GreenNode.d.ts +154 -0
- package/dist/types/Syntax/GreenNodePool.d.ts +134 -0
- package/dist/types/Syntax/NodeMatcher.d.ts +75 -0
- package/dist/types/Syntax/RedNode.d.ts +280 -0
- package/dist/types/Syntax/RedNodeStore.d.ts +116 -0
- package/dist/types/Syntax/TreeBuilder.d.ts +54 -0
- package/dist/types/Syntax/__tests__/GreenNode.test.d.ts +1 -0
- package/dist/types/Syntax/__tests__/RedNode.test.d.ts +1 -0
- package/dist/types/Syntax/__tests__/RedNodeStore.test.d.ts +1 -0
- package/dist/types/Syntax/greenEdit.d.ts +64 -0
- package/dist/types/Syntax/hash.d.ts +17 -0
- package/dist/types/Syntax/index.d.ts +8 -0
- package/dist/types/Syntax/nodeAttr.d.ts +63 -0
- package/dist/types/Syntax/partition.d.ts +67 -0
- package/dist/types/Syntax/preserveNodeIds.d.ts +47 -0
- package/dist/types/Tokens/TokenResolver.d.ts +25 -0
- package/dist/types/Tokens/index.d.ts +1 -0
- package/dist/types/Transactions/Transaction.d.ts +36 -0
- package/dist/types/Transactions/UndoManager.d.ts +63 -0
- package/dist/types/Transactions/index.d.ts +3 -0
- package/dist/types/Transformers/ASTOptimizer.d.ts +34 -0
- package/dist/types/Transformers/GradientTransformer.d.ts +18 -0
- package/dist/types/Transformers/GrowTransformer.d.ts +16 -0
- package/dist/types/Transformers/RainbowTransformer.d.ts +17 -0
- package/dist/types/Transformers/SineWaveTransformer.d.ts +17 -0
- package/dist/types/Transformers/Transformer.d.ts +17 -0
- package/dist/types/Types/core.d.ts +104 -0
- package/dist/types/Types/diagnostics.d.ts +85 -0
- package/dist/types/Types/index.d.ts +11 -0
- package/dist/types/Types/operations.d.ts +109 -0
- package/dist/types/Types/queries.d.ts +67 -0
- package/dist/types/Types/symbols.d.ts +42 -0
- package/dist/types/Types/tokens.d.ts +75 -0
- package/dist/types/Utils/BBCodeGenerator.d.ts +21 -0
- package/dist/types/Utils/ColorMath.d.ts +92 -0
- package/dist/types/Utils/EffectMath.d.ts +643 -0
- package/dist/types/Utils/TagVocabulary.d.ts +104 -0
- package/dist/types/Utils/TagVocabulary.test.d.ts +1 -0
- package/dist/types/Utils/color.d.ts +29 -0
- package/dist/types/Utils/dom-to-svg.d.ts +28 -0
- package/dist/types/Utils/dom-to-svg.test.d.ts +1 -0
- package/dist/types/Utils/treeTransformers.d.ts +160 -0
- package/dist/types/Visitors/BBBlocksExporter.d.ts +27 -0
- package/dist/types/Visitors/BBCodeExporter.d.ts +90 -0
- package/dist/types/Visitors/BlockPatcher.d.ts +91 -0
- package/dist/types/Visitors/DOMMorpher.d.ts +13 -0
- package/dist/types/Visitors/HTMLRenderer.d.ts +380 -0
- package/dist/types/Visitors/JSONExporter.d.ts +42 -0
- package/dist/types/Visitors/MarkdownExporter.d.ts +28 -0
- package/dist/types/Visitors/SVGRenderer.d.ts +16 -0
- package/dist/types/Visitors/TiptapExporter.d.ts +40 -0
- package/dist/types/Visitors/Visitor.d.ts +35 -0
- package/dist/types/Visitors/index.d.ts +9 -0
- package/dist/types/Visuals/BoxDrawer.d.ts +49 -0
- package/dist/types/Visuals/index.d.ts +38 -0
- package/dist/types/index.d.ts +154 -0
- package/package.json +14 -10
- package/src/Analysis/Passes/Analysis/ColorUsageAnalyzer.ts +103 -0
- package/src/Analysis/Passes/Analysis/GradientAnalyzer.ts +294 -20
- package/src/Analysis/Passes/Analysis/SymbolAnalyzer.ts +257 -0
- package/src/Analysis/Passes/Decision/PaletteRemapDecision.ts +425 -0
- package/src/Analysis/Pipeline/Pipeline.ts +15 -2
- package/src/Analysis/index.ts +8 -2
- package/src/BBCode/BBCodeDocumentModel.ts +3 -0
- package/src/BBCode/BBCodeToGreenNode.ts +34 -11
- package/src/BBCode/Parser.ts +38 -1
- package/src/Edits/EditPlan.ts +290 -0
- package/src/Edits/Optimizer.ts +185 -0
- package/src/Edits/Rules/Rule.ts +145 -0
- package/src/Edits/Rules/dropEmptyTags.ts +110 -0
- package/src/Edits/Rules/dropRedundantNesting.ts +120 -0
- package/src/Edits/Rules/index.ts +28 -0
- package/src/Edits/Rules/mergeAdjacent.ts +224 -0
- package/src/Edits/Rules/reorderWrappers.ts +154 -0
- package/src/Edits/Rules/shortenHex.ts +79 -0
- package/src/Edits/Rules/tagValue.ts +105 -0
- package/src/Edits/Rules/unwrapInvisibleColor.ts +93 -0
- package/src/Edits/applyEdits.ts +54 -0
- package/src/Edits/index.ts +22 -0
- package/src/Events/EventBus.ts +18 -0
- package/src/HTML/HTMLToGreenNode.ts +185 -37
- package/src/Incremental/BracketIndex.ts +249 -0
- package/src/Incremental/IncrementalParser.ts +464 -41
- package/src/Lexer/BBCodeLexer.ts +25 -3
- package/src/Lexer/index.ts +1 -1
- package/src/Markdown/MarkdownAST.ts +65 -2
- package/src/Markdown/MarkdownLexer.ts +69 -14
- package/src/Markdown/MarkdownParser.ts +749 -62
- package/src/Markdown/MarkdownToGreenNode.ts +148 -27
- package/src/MilHibri/MilHibriDocumentModel.ts +56 -0
- package/src/Model/DocumentModel.ts +138 -5
- package/src/Model/TagRegistry.ts +132 -134
- package/src/Reconciler/SurgicalReconciler.ts +460 -0
- package/src/Repair/NestingRepair.ts +116 -0
- package/src/Repair/index.ts +1 -0
- package/src/Semantic/SemanticAnalyzer.ts +1802 -65
- package/src/Syntax/GreenNodePool.ts +12 -12
- package/src/Syntax/RedNode.ts +17 -3
- package/src/Syntax/RedNodeStore.ts +9 -1
- package/src/Syntax/nodeAttr.ts +172 -0
- package/src/Tokens/TokenResolver.ts +55 -0
- package/src/Tokens/index.ts +1 -0
- package/src/Types/core.ts +15 -0
- package/src/Utils/ColorMath.ts +276 -55
- package/src/Utils/EffectMath.ts +2462 -0
- package/src/Utils/TagVocabulary.ts +141 -0
- package/src/Utils/dom-to-svg.ts +28 -12
- package/src/Visitors/BBBlocksExporter.ts +15 -1
- package/src/Visitors/BBCodeExporter.ts +319 -32
- package/src/Visitors/BlockPatcher.ts +87 -11
- package/src/Visitors/DOMMorpher.ts +15 -5
- package/src/Visitors/HTMLRenderer.ts +452 -142
- package/src/Visitors/MarkdownExporter.ts +98 -41
- package/src/Visitors/TiptapExporter.ts +1 -0
- package/src/Visuals/index.ts +7 -4
- package/src/Visuals/lyne.css +191 -21
- package/src/Visuals/osu.css +36 -5
- package/src/index.ts +172 -5
- package/dist/index.d.mts +0 -4609
- package/dist/index.d.ts +0 -4609
- package/src/Analysis/Passes/Analysis/__tests__/GradientAnalyzer.test.ts +0 -135
- package/src/Analysis/Passes/Analysis/__tests__/MergeableColorAnalyzer.test.ts +0 -84
- package/src/Analysis/Passes/Analysis/__tests__/RainbowAnalyzer.test.ts +0 -99
- package/src/Analysis/Passes/Analysis/__tests__/WaveAnalyzer.test.ts +0 -119
- package/src/Analysis/Passes/Decision/__tests__/DefaultDecision.test.ts +0 -179
- package/src/Analysis/__tests__/Integration.test.ts +0 -162
- package/src/Analysis/__tests__/Pipeline.test.ts +0 -133
- package/src/Diff/__tests__/TreeDiffer.test.ts +0 -65
- package/src/Incremental/__tests__/IncrementalParser.test.ts +0 -164
- package/src/Syntax/__tests__/GreenNode.test.ts +0 -33
- package/src/Syntax/__tests__/RedNode.test.ts +0 -81
- package/src/Syntax/__tests__/RedNodeStore.test.ts +0 -104
- package/src/Tests/ASTOptimizerIdempotence.test.ts +0 -77
- package/src/Tests/BlockPatcher.test.ts +0 -437
- package/src/Tests/BlockPatcherWindowed.test.ts +0 -364
- package/src/Tests/BoxDrawer.test.ts +0 -217
- package/src/Tests/BoxRichTitle.test.ts +0 -105
- package/src/Tests/Chars500kBenchmark.test.ts +0 -151
- package/src/Tests/Chars500kEdits.test.ts +0 -321
- package/src/Tests/CollabPositions.test.ts +0 -146
- package/src/Tests/CompilerPathProfiling.test.ts +0 -186
- package/src/Tests/DOMMorpher.test.ts +0 -142
- package/src/Tests/DomPatchPerf.test.ts +0 -60
- package/src/Tests/EffectSegments.snapshot.json +0 -616
- package/src/Tests/EffectSegments.test.ts +0 -68
- package/src/Tests/FindNodeAtOffset.test.ts +0 -65
- package/src/Tests/Fuzzer.test.ts +0 -166
- package/src/Tests/GreenNodePool.test.ts +0 -153
- package/src/Tests/Lexer.test.ts +0 -238
- package/src/Tests/LyneMode.test.ts +0 -291
- package/src/Tests/ModelCoherence.test.ts +0 -180
- package/src/Tests/Partition.test.ts +0 -238
- package/src/Tests/PluginTags.test.ts +0 -150
- package/src/Tests/ProblematicSection.test.ts +0 -46
- package/src/Tests/ProblematicSectionHTML.test.ts +0 -58
- package/src/Tests/RedReuse.test.ts +0 -134
- package/src/Tests/ReproDelete20k.test.ts +0 -62
- package/src/Tests/SemanticValidators.test.ts +0 -136
- package/src/Tests/StableNodeIds.test.ts +0 -210
- package/src/Tests/StudioColorBloat.test.ts +0 -25
- package/src/Tests/StudioDebugText.test.ts +0 -27
- package/src/Tests/StudioTrailingChar.test.ts +0 -25
- package/src/Tests/StudioValidText.test.ts +0 -25
- package/src/Tests/UrlImgBug.test.ts +0 -23
- package/src/Tests/VisualBuilderFidelity.test.ts +0 -105
- package/src/Tests/referenceDocument.ts +0 -119
- 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
|
-
})
|