@miliastry/quasar 1.0.6 → 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 +117 -6
- package/dist/Visuals/osu.css +36 -5
- package/dist/index.js +9664 -3282
- package/dist/index.mjs +9535 -3283
- 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 +18 -12
- 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 +129 -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 +374 -151
- 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 +117 -6
- package/src/Visuals/osu.css +36 -5
- package/src/index.ts +172 -5
- package/dist/index.d.mts +0 -4612
- package/dist/index.d.ts +0 -4612
- 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,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Debug — dump the windowed reconcile decisions for the minimal delete-20k
|
|
3
|
-
* divergence: change range, old/new window keys, orphan decisions, walk.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { describe, it } from 'vitest'
|
|
7
|
-
import { readFileSync, existsSync } from 'node:fs'
|
|
8
|
-
import { join } from 'node:path'
|
|
9
|
-
import { BBCodeDocumentModel } from '../BBCode/BBCodeDocumentModel'
|
|
10
|
-
import { HTMLRenderer } from '../Visitors/HTMLRenderer'
|
|
11
|
-
import { patchBlocksInto } from '../Visitors/BlockPatcher'
|
|
12
|
-
|
|
13
|
-
const renderer = new HTMLRenderer()
|
|
14
|
-
|
|
15
|
-
function loadFixture(): string {
|
|
16
|
-
const candidates = [
|
|
17
|
-
join(process.cwd(), 'packages', 'quasar', '500KCharsTest'),
|
|
18
|
-
join(process.cwd(), '500KCharsTest'),
|
|
19
|
-
join(__dirname, '..', '..', '500KCharsTest'),
|
|
20
|
-
]
|
|
21
|
-
const p = candidates.find(c => existsSync(c))
|
|
22
|
-
if (!p) throw new Error(`Fixture not found in: ${candidates.join(', ')}`)
|
|
23
|
-
return readFileSync(p, 'utf-8')
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const insertAt = (s: string, pos: number, text: string): string => s.slice(0, pos) + text + s.slice(pos)
|
|
27
|
-
function afterBlank(s: string, pos: number): number {
|
|
28
|
-
const i = s.indexOf('\n\n', pos)
|
|
29
|
-
return i === -1 ? s.length : i + 2
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
describe('windowed debug: minimal delete-20k', () => {
|
|
33
|
-
it('dumps orphan + walk decisions', () => {
|
|
34
|
-
;(globalThis as { __BP_DEBUG__?: boolean }).__BP_DEBUG__ = true
|
|
35
|
-
let src = loadFixture()
|
|
36
|
-
let model = new BBCodeDocumentModel({ source: src })
|
|
37
|
-
const el = document.createElement('div')
|
|
38
|
-
patchBlocksInto(el, model.redRoot!, { renderer, minWindowedBlocks: 0 })
|
|
39
|
-
|
|
40
|
-
// Step 1: insert box at 50%
|
|
41
|
-
src = insertAt(src, afterBlank(src, Math.floor(src.length * 0.5)), '[box]caja nueva[/box]\n\n')
|
|
42
|
-
model.applyTextUpdate(src)
|
|
43
|
-
model.ensureAnalyzed()
|
|
44
|
-
patchBlocksInto(el, model.redRoot!, { renderer, minWindowedBlocks: 0 })
|
|
45
|
-
|
|
46
|
-
// Step 2: insert quote at 60%
|
|
47
|
-
src = insertAt(src, afterBlank(src, Math.floor(src.length * 0.6)), '[quote]cita nueva[/quote]\n\n')
|
|
48
|
-
model.applyTextUpdate(src)
|
|
49
|
-
model.ensureAnalyzed()
|
|
50
|
-
patchBlocksInto(el, model.redRoot!, { renderer, minWindowedBlocks: 0 })
|
|
51
|
-
|
|
52
|
-
// Step 3: delete 20k at 50% — the diverging edit
|
|
53
|
-
console.log('[dbg] --- STEP 3: delete 20k at 50% ---')
|
|
54
|
-
const pos = Math.floor(src.length * 0.5)
|
|
55
|
-
const next = src.slice(0, pos) + src.slice(pos + 20_000)
|
|
56
|
-
model.applyTextUpdate(next)
|
|
57
|
-
model.ensureAnalyzed()
|
|
58
|
-
console.log('[dbg] change source pos', pos)
|
|
59
|
-
patchBlocksInto(el, model.redRoot!, { renderer, minWindowedBlocks: 0 })
|
|
60
|
-
;(globalThis as { __BP_DEBUG__?: boolean }).__BP_DEBUG__ = false
|
|
61
|
-
}, 300_000)
|
|
62
|
-
})
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { REFERENCE_DOCUMENT_WITH_GRADIENT } from './referenceDocument'
|
|
3
|
-
import { BBCodeDocumentModel } from '../BBCode/BBCodeDocumentModel'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Regression: the built-in validators must actually fire.
|
|
7
|
-
*
|
|
8
|
-
* Three of the four were unreachable, so `analyze()` returned zero diagnostics
|
|
9
|
-
* on *any* document and `ErrorCheckerWindow` — a whole window whose job is
|
|
10
|
-
* listing problems — was permanently empty. None of them set `range` either, so
|
|
11
|
-
* even a firing diagnostic could not be clicked through to its position.
|
|
12
|
-
*
|
|
13
|
-
* unknown-tag required kind 'custom', but the parser turns unknown tags
|
|
14
|
-
* into `text` nodes before the analyzer sees them
|
|
15
|
-
* deprecated-tag looked up `deprecated[node.text]`, but `text` holds the
|
|
16
|
-
* tag's attributes, not its name
|
|
17
|
-
* nested-structure required non-text children of [code], but the parser
|
|
18
|
-
* keeps code content as raw text, so there never are any
|
|
19
|
-
*
|
|
20
|
-
* `unknown-tag` is still unreachable by design — see the note at the bottom.
|
|
21
|
-
*/
|
|
22
|
-
function diagnose(source: string) {
|
|
23
|
-
const model = new BBCodeDocumentModel({ source })
|
|
24
|
-
const result = model.analyze()
|
|
25
|
-
return result.diagnostics.items
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function codes(source: string): string[] {
|
|
29
|
-
return diagnose(source).map(d => d.code)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
describe('SemanticAnalyzer — built-in validators', () => {
|
|
33
|
-
it('flags a deprecated tag exactly once, with its range', () => {
|
|
34
|
-
const source = '[strike]x[/strike]'
|
|
35
|
-
const found = diagnose(source)
|
|
36
|
-
|
|
37
|
-
expect(found.map(d => d.code)).toEqual(['deprecated-tag'])
|
|
38
|
-
// Several nodes share offset 0 here (document, paragraph, the tag itself),
|
|
39
|
-
// so an offset-only check reported this three times.
|
|
40
|
-
expect(found[0].range).toEqual({ start: 0, end: source.length })
|
|
41
|
-
expect(found[0].message).toContain('[s]')
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
it('does not flag the modern spelling', () => {
|
|
45
|
-
expect(codes('[s]x[/s]')).toEqual([])
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
it('flags BBCode tags inside a code block', () => {
|
|
49
|
-
expect(codes('[code][b]x[/b][/code]')).toEqual(['nested-tags-in-code', 'nested-tags-in-code'])
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
it('does not flag a code block without tags', () => {
|
|
53
|
-
expect(codes('[code]const a = 1[/code]')).toEqual([])
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
it('flags an empty tag', () => {
|
|
57
|
-
const found = diagnose('[b][/b]')
|
|
58
|
-
expect(found.map(d => d.code)).toEqual(['empty-tag'])
|
|
59
|
-
expect(found[0].range).not.toBeNull()
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
it('every diagnostic carries a usable range', () => {
|
|
63
|
-
// ErrorCheckerWindow guards on `if (err.range)` to jump to the problem, so
|
|
64
|
-
// a diagnostic without one is invisible in practice.
|
|
65
|
-
for (const source of ['[strike]x[/strike]', '[code][b]x[/b][/code]', '[b][/b]']) {
|
|
66
|
-
for (const d of diagnose(source)) {
|
|
67
|
-
expect(d.range).not.toBeNull()
|
|
68
|
-
expect(d.range!.start).toBeGreaterThanOrEqual(0)
|
|
69
|
-
expect(d.range!.end).toBeGreaterThan(d.range!.start)
|
|
70
|
-
expect(d.range!.end).toBeLessThanOrEqual(source.length)
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
describe('unclosed tags', () => {
|
|
76
|
-
// The one BBCode mistake people actually make. The legacy parser closes
|
|
77
|
-
// these silently by design, so without this nothing tells the author.
|
|
78
|
-
it('flags a tag never closed', () => {
|
|
79
|
-
expect(codes('[b]hola')).toEqual(['unclosed-tag'])
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
it('flags each unclosed tag separately', () => {
|
|
83
|
-
expect(codes('[b][i]hola')).toEqual(['unclosed-tag', 'unclosed-tag'])
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
it('flags a tag auto-closed by a mismatched close', () => {
|
|
87
|
-
// [i] is never closed; the legacy rules close it when [/b] arrives.
|
|
88
|
-
expect(codes('[b][i]x[/b]')).toEqual(['unclosed-tag'])
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
it('says nothing about properly closed tags', () => {
|
|
92
|
-
expect(codes('[b]hola[/b]')).toEqual([])
|
|
93
|
-
expect(codes('[b][i]x[/i][/b]')).toEqual([])
|
|
94
|
-
expect(codes('[color=red]x[/color]')).toEqual([])
|
|
95
|
-
expect(codes('[url=https://osu.ppy.sh]x[/url]')).toEqual([])
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
it('is case-insensitive about the closing tag', () => {
|
|
99
|
-
expect(codes('[B]x[/B]')).toEqual([])
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
it('does not flag [*], which has no closing form', () => {
|
|
103
|
-
expect(codes('[list]\n[*]uno\n[*]dos\n[/list]')).toEqual([])
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
it('does not flag brackets in ordinary prose', () => {
|
|
107
|
-
expect(codes('Combo de [90 misses] con teclado [Gateron]')).toEqual([])
|
|
108
|
-
expect(codes('hola[/b]')).toEqual([])
|
|
109
|
-
})
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
it('stays quiet on well-formed documents', () => {
|
|
113
|
-
// False positives are worse than silence: a checker that cries wolf on
|
|
114
|
-
// ordinary text gets ignored.
|
|
115
|
-
expect(codes('[b]hola[/b] [i]mundo[/i]')).toEqual([])
|
|
116
|
-
expect(codes('texto sin ninguna etiqueta')).toEqual([])
|
|
117
|
-
expect(codes('[url=https://osu.ppy.sh]enlace[/url]')).toEqual([])
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
it('produces no noise on the real reference document', () => {
|
|
121
|
-
const found = diagnose(REFERENCE_DOCUMENT_WITH_GRADIENT)
|
|
122
|
-
// This is a real, valid document: anything reported here is a false
|
|
123
|
-
// positive that would bury genuine problems in the checker window.
|
|
124
|
-
expect(found.filter(d => d.severity === 'error')).toEqual([])
|
|
125
|
-
expect(found.filter(d => d.severity === 'warning')).toEqual([])
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
// NOTE: `unknown-tag` is deliberately still dormant. The parser renders
|
|
129
|
-
// unrecognised tags as literal text on purpose — `[Gateron]`, `[90 misses]`
|
|
130
|
-
// are ordinary prose, not typos — so warning on them would be pure noise.
|
|
131
|
-
// Reviving it is a product decision, tracked as roadmap point 15.
|
|
132
|
-
it('does not warn about unknown tags (parser renders them as text)', () => {
|
|
133
|
-
expect(codes('[unknowntag]hola[/unknowntag]')).toEqual([])
|
|
134
|
-
expect(codes('Combo de [90 misses] con teclado [Gateron]')).toEqual([])
|
|
135
|
-
})
|
|
136
|
-
})
|
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { BBCodeDocumentModel } from '../BBCode/BBCodeDocumentModel'
|
|
3
|
-
import { HTMLRenderer } from '../Visitors/HTMLRenderer'
|
|
4
|
-
import { morphHTML } from '../Visitors/DOMMorpher'
|
|
5
|
-
import type { RedNode } from '../Syntax/RedNode'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Node ids must survive a reparse for every node that did not change.
|
|
9
|
-
*
|
|
10
|
-
* Ids are embedded in the rendered HTML as `data-node-id` (the preview→Monaco
|
|
11
|
-
* click mapping reads them), so regenerating them on every parse made the HTML
|
|
12
|
-
* of *unchanged* subtrees differ between keystrokes — which defeated the
|
|
13
|
-
* DOMMorpher's isEqualNode fast path and rewrote DOM the user was looking at.
|
|
14
|
-
*
|
|
15
|
-
* The last test is the end-to-end payoff: after a one-character edit, the DOM
|
|
16
|
-
* elements of untouched content must keep their object identity through a
|
|
17
|
-
* morph, because their HTML is now byte-identical.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
/** Map every text node's content to its id (text content identifies the leaf). */
|
|
21
|
-
function textIds(root: RedNode): Map<string, string> {
|
|
22
|
-
const out = new Map<string, string>()
|
|
23
|
-
root.walk(n => {
|
|
24
|
-
if (n.kind === 'text' && n.text.trim() !== '') out.set(n.text, String(n.id))
|
|
25
|
-
})
|
|
26
|
-
return out
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function subtreeText(node: RedNode): string {
|
|
30
|
-
let out = ''
|
|
31
|
-
node.walk(n => { if (n.kind === 'text') out += n.text })
|
|
32
|
-
return out
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function allIds(root: RedNode): string[] {
|
|
36
|
-
const out: string[] = []
|
|
37
|
-
root.walk(n => { out.push(String(n.id)) })
|
|
38
|
-
return out
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function mulberry32(seed: number): () => number {
|
|
42
|
-
let a = seed >>> 0
|
|
43
|
-
return () => {
|
|
44
|
-
a |= 0; a = (a + 0x6D2B79F5) | 0
|
|
45
|
-
let t = Math.imul(a ^ (a >>> 15), 1 | a)
|
|
46
|
-
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t
|
|
47
|
-
return ((t ^ (t >>> 14)) >>> 0) / 4294967296
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
describe('stable node ids across reparses', () => {
|
|
52
|
-
it('an edit in the middle keeps the ids of untouched content (incremental path)', () => {
|
|
53
|
-
const model = new BBCodeDocumentModel({
|
|
54
|
-
source: 'uno\n\ndos\n\ntres\n\n[b]cuatro[/b]',
|
|
55
|
-
})
|
|
56
|
-
const before = textIds(model.redRoot!)
|
|
57
|
-
|
|
58
|
-
model.applyTextUpdate('uno\n\ndos EDITADO\n\ntres\n\n[b]cuatro[/b]')
|
|
59
|
-
const after = textIds(model.redRoot!)
|
|
60
|
-
|
|
61
|
-
expect(after.get('uno')).toBe(before.get('uno'))
|
|
62
|
-
expect(after.get('tres')).toBe(before.get('tres'))
|
|
63
|
-
expect(after.get('cuatro')).toBe(before.get('cuatro'))
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
it('typing at the end keeps every previous id', () => {
|
|
67
|
-
const model = new BBCodeDocumentModel({
|
|
68
|
-
source: 'uno\n\n[i]dos[/i]\n\ntres',
|
|
69
|
-
})
|
|
70
|
-
const before = textIds(model.redRoot!)
|
|
71
|
-
|
|
72
|
-
model.applyTextUpdate('uno\n\n[i]dos[/i]\n\ntres y algo mas')
|
|
73
|
-
const after = textIds(model.redRoot!)
|
|
74
|
-
|
|
75
|
-
expect(after.get('uno')).toBe(before.get('uno'))
|
|
76
|
-
expect(after.get('dos')).toBe(before.get('dos'))
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
it('a full rebuild also preserves ids, via hash matching', () => {
|
|
80
|
-
const model = new BBCodeDocumentModel({
|
|
81
|
-
source: 'uno\n\ndos\n\n[b]tres[/b]',
|
|
82
|
-
})
|
|
83
|
-
const before = textIds(model.redRoot!)
|
|
84
|
-
|
|
85
|
-
// rebuild() reparses everything: no green is shared by reference, so this
|
|
86
|
-
// exercises the hash-verified tier.
|
|
87
|
-
model.rebuild('uno\n\ndos!\n\n[b]tres[/b]')
|
|
88
|
-
const after = textIds(model.redRoot!)
|
|
89
|
-
|
|
90
|
-
expect(after.get('uno')).toBe(before.get('uno'))
|
|
91
|
-
expect(after.get('tres')).toBe(before.get('tres'))
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
it('ids stay unique through seeded random edit sequences', () => {
|
|
95
|
-
const rand = mulberry32(1234)
|
|
96
|
-
const alphabet = ['a', ' ', '\n', '[', ']', 'b', '[b]', '[/b]', '[i]', '[/i]', '[list]', '[*]']
|
|
97
|
-
|
|
98
|
-
for (let doc = 0; doc < 8; doc++) {
|
|
99
|
-
let source = ''
|
|
100
|
-
const parts = 40 + Math.floor(rand() * 120)
|
|
101
|
-
for (let i = 0; i < parts; i++) {
|
|
102
|
-
source += alphabet[Math.floor(rand() * alphabet.length)]
|
|
103
|
-
}
|
|
104
|
-
const model = new BBCodeDocumentModel({ source })
|
|
105
|
-
|
|
106
|
-
for (let edit = 0; edit < 25; edit++) {
|
|
107
|
-
const pos = Math.floor(rand() * (model.source.length + 1))
|
|
108
|
-
const insert = alphabet[Math.floor(rand() * alphabet.length)]
|
|
109
|
-
const next = rand() < 0.75
|
|
110
|
-
? model.source.slice(0, pos) + insert + model.source.slice(pos)
|
|
111
|
-
: model.source.slice(0, pos) + model.source.slice(Math.min(pos + 1, model.source.length))
|
|
112
|
-
model.applyTextUpdate(next)
|
|
113
|
-
|
|
114
|
-
const ids = allIds(model.redRoot!)
|
|
115
|
-
expect(new Set(ids).size).toBe(ids.length)
|
|
116
|
-
expect(model.redRoot!.green).toBe(model.greenRoot)
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
it('inserting a block does not renumber the blocks that survive it', () => {
|
|
122
|
-
// Regression. Identity is carried either by red-subtree REUSE (the nodes
|
|
123
|
-
// are the previous objects) or by the id walk (every node is fresh) —
|
|
124
|
-
// never both. When both ran, the walk paired positionally, so inserting a
|
|
125
|
-
// block made each survivor pair with its NEIGHBOUR: it overwrote a live
|
|
126
|
-
// node's id with a different node's, which both duplicated ids and
|
|
127
|
-
// changed `data-node-id` on blocks that had not changed — defeating the
|
|
128
|
-
// morpher's fast path exactly when it matters most.
|
|
129
|
-
let src = ''
|
|
130
|
-
for (let i = 0; i < 40; i++) src += `[notice]bloque ${i}[/notice]\n\n`
|
|
131
|
-
const model = new BBCodeDocumentModel({ source: src })
|
|
132
|
-
|
|
133
|
-
const idOf = (root: RedNode, text: string): string | null => {
|
|
134
|
-
let found: string | null = null
|
|
135
|
-
root.walk(n => {
|
|
136
|
-
if (!found && n.kind === 'notice' && n.text === '' && subtreeText(n).includes(text)) {
|
|
137
|
-
found = String(n.id)
|
|
138
|
-
}
|
|
139
|
-
})
|
|
140
|
-
return found
|
|
141
|
-
}
|
|
142
|
-
const before10 = idOf(model.redRoot!, 'bloque 10')
|
|
143
|
-
const before39 = idOf(model.redRoot!, 'bloque 39')
|
|
144
|
-
expect(before10).toBeTruthy()
|
|
145
|
-
|
|
146
|
-
// Insert a whole new block near the start: everything after it shifts.
|
|
147
|
-
model.applyTextUpdate('[notice]NUEVO[/notice]\n\n' + src)
|
|
148
|
-
|
|
149
|
-
expect(idOf(model.redRoot!, 'bloque 10')).toBe(before10)
|
|
150
|
-
expect(idOf(model.redRoot!, 'bloque 39')).toBe(before39)
|
|
151
|
-
|
|
152
|
-
const ids = allIds(model.redRoot!)
|
|
153
|
-
expect(new Set(ids).size).toBe(ids.length)
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
it('end to end: untouched id-bearing blocks keep DOM identity through a morph', () => {
|
|
157
|
-
// Only block containers carry data-node-id (see HTMLRenderer.ID_BEARING_KINDS),
|
|
158
|
-
// so blocks are where id churn used to break the morpher's fast path.
|
|
159
|
-
const model = new BBCodeDocumentModel({
|
|
160
|
-
source: '[notice]uno[/notice]\n\n[quote]dos[/quote]\n\n[notice]tres[/notice]',
|
|
161
|
-
})
|
|
162
|
-
const renderer = new HTMLRenderer()
|
|
163
|
-
const el = document.createElement('div')
|
|
164
|
-
el.innerHTML = renderer.render(model.redRoot!)
|
|
165
|
-
|
|
166
|
-
const findBlock = (text: string): Element | null => {
|
|
167
|
-
for (const block of Array.from(el.querySelectorAll('[data-node-id]'))) {
|
|
168
|
-
if (block.textContent?.includes(text)) return block
|
|
169
|
-
}
|
|
170
|
-
return null
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const unoBefore = findBlock('uno')
|
|
174
|
-
const tresBefore = findBlock('tres')
|
|
175
|
-
expect(unoBefore).toBeTruthy()
|
|
176
|
-
expect(tresBefore).toBeTruthy()
|
|
177
|
-
const unoId = unoBefore!.getAttribute('data-node-id')
|
|
178
|
-
const tresId = tresBefore!.getAttribute('data-node-id')
|
|
179
|
-
|
|
180
|
-
model.applyTextUpdate('[notice]uno[/notice]\n\n[quote]dos EDITADO[/quote]\n\n[notice]tres[/notice]')
|
|
181
|
-
morphHTML(el, renderer.render(model.redRoot!))
|
|
182
|
-
|
|
183
|
-
// Same DOM objects, not equivalent ones: their HTML — ids included — did
|
|
184
|
-
// not change, so the morpher's isEqualNode fast path skipped them.
|
|
185
|
-
expect(findBlock('uno')).toBe(unoBefore)
|
|
186
|
-
expect(findBlock('tres')).toBe(tresBefore)
|
|
187
|
-
expect(unoBefore!.getAttribute('data-node-id')).toBe(unoId)
|
|
188
|
-
expect(tresBefore!.getAttribute('data-node-id')).toBe(tresId)
|
|
189
|
-
// And the edited block is actually updated.
|
|
190
|
-
expect(findBlock('dos EDITADO')).toBeTruthy()
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
it('every data-node-id in fresh HTML resolves to a node in the current tree', () => {
|
|
194
|
-
const model = new BBCodeDocumentModel({
|
|
195
|
-
source: '[notice]uno[/notice]\n\n[list][*]dos[*]tres[/list]',
|
|
196
|
-
})
|
|
197
|
-
model.applyTextUpdate('[notice]uno![/notice]\n\n[list][*]dos[*]tres[/list]')
|
|
198
|
-
|
|
199
|
-
const html = new HTMLRenderer().render(model.redRoot!)
|
|
200
|
-
const el = document.createElement('div')
|
|
201
|
-
el.innerHTML = html
|
|
202
|
-
|
|
203
|
-
const withIds = Array.from(el.querySelectorAll('[data-node-id]'))
|
|
204
|
-
expect(withIds.length).toBeGreaterThan(0)
|
|
205
|
-
for (const node of withIds) {
|
|
206
|
-
const id = node.getAttribute('data-node-id')!
|
|
207
|
-
expect(model.findNode(id as never)).toBeTruthy()
|
|
208
|
-
}
|
|
209
|
-
})
|
|
210
|
-
})
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { TagRegistry } from '../Model/TagRegistry'
|
|
3
|
-
import { BBCodeExporter } from '../Visitors/BBCodeExporter'
|
|
4
|
-
import { processStudioAST } from '@miliastry/quasar-studio'
|
|
5
|
-
|
|
6
|
-
describe('Studio Effects Color Bloat', () => {
|
|
7
|
-
it('should remove redundant outer color tags when a gradient is applied', () => {
|
|
8
|
-
const text = "[color=#000000]Hello[/color]"
|
|
9
|
-
|
|
10
|
-
const layers = [
|
|
11
|
-
{ id: 'grad', type: 'gradient', enabled: true, value: 100, properties: { color1: '#ff0000', color2: '#0000ff' }, colors: '#ff0000,#0000ff', opacity: 1.0, easing: 'linear' }
|
|
12
|
-
]
|
|
13
|
-
|
|
14
|
-
const registry = new TagRegistry()
|
|
15
|
-
const { redRoot } = processStudioAST(text, layers as any, {})
|
|
16
|
-
|
|
17
|
-
const exporter = new BBCodeExporter(registry)
|
|
18
|
-
const exportedBBCode = exporter.export(redRoot)
|
|
19
|
-
|
|
20
|
-
// The output should NOT contain [color=#000000]
|
|
21
|
-
expect(exportedBBCode).not.toContain('#000000')
|
|
22
|
-
// It should ONLY contain the gradient colors
|
|
23
|
-
expect(exportedBBCode).toContain('#ff0000')
|
|
24
|
-
})
|
|
25
|
-
})
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { describe, it } from 'vitest'
|
|
2
|
-
import { TagRegistry } from '../Model/TagRegistry'
|
|
3
|
-
import { BBCodeExporter } from '../Visitors/BBCodeExporter'
|
|
4
|
-
import { processStudioAST } from '@miliastry/quasar-studio'
|
|
5
|
-
|
|
6
|
-
describe('Studio Effects Debug Text', () => {
|
|
7
|
-
it('should print exported BBCode for debugging', () => {
|
|
8
|
-
const text = "Hello World\nTest\n\nDouble newline."
|
|
9
|
-
|
|
10
|
-
const layers = [
|
|
11
|
-
{ id: 'grad', type: 'gradient', active: true, value: 100, properties: { color1: '#ff0000', color2: '#0000ff' } }
|
|
12
|
-
]
|
|
13
|
-
|
|
14
|
-
const registry = new TagRegistry()
|
|
15
|
-
const { redRoot } = processStudioAST(text, layers as any, {})
|
|
16
|
-
|
|
17
|
-
const exporter = new BBCodeExporter(registry)
|
|
18
|
-
const exportedBBCode = exporter.export(redRoot)
|
|
19
|
-
|
|
20
|
-
console.log("================================")
|
|
21
|
-
console.log("ORIGINAL TEXT:")
|
|
22
|
-
console.log(JSON.stringify(text))
|
|
23
|
-
console.log("EXPORTED BBCODE:")
|
|
24
|
-
console.log(JSON.stringify(exportedBBCode))
|
|
25
|
-
console.log("================================")
|
|
26
|
-
})
|
|
27
|
-
})
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { TagRegistry } from '../Model/TagRegistry'
|
|
3
|
-
import { BBCodeExporter } from '../Visitors/BBCodeExporter'
|
|
4
|
-
import { processStudioAST } from '@miliastry/quasar-studio'
|
|
5
|
-
|
|
6
|
-
describe('Studio Effects Trailing Char Bug', () => {
|
|
7
|
-
it('should not duplicate trailing braille blanks', () => {
|
|
8
|
-
const text = "[notice]Hello⠀[/notice]"
|
|
9
|
-
|
|
10
|
-
const layers = [
|
|
11
|
-
{ id: 'grad', type: 'gradient', enabled: true, value: 100, properties: { color1: '#ff0000', color2: '#0000ff' }, colors: '#ff0000,#0000ff', opacity: 1.0, easing: 'linear' }
|
|
12
|
-
]
|
|
13
|
-
|
|
14
|
-
const registry = new TagRegistry()
|
|
15
|
-
const { redRoot } = processStudioAST(text, layers as any, {})
|
|
16
|
-
|
|
17
|
-
const exporter = new BBCodeExporter(registry)
|
|
18
|
-
const exportedBBCode = exporter.export(redRoot)
|
|
19
|
-
|
|
20
|
-
console.log("EXPORTED:", JSON.stringify(exportedBBCode))
|
|
21
|
-
|
|
22
|
-
// Ensure we don't have an extra braille blank at the end
|
|
23
|
-
expect(exportedBBCode.match(/⠀/g)?.length).toBe(1)
|
|
24
|
-
})
|
|
25
|
-
})
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { TagRegistry } from '../Model/TagRegistry'
|
|
3
|
-
import { BBCodeExporter } from '../Visitors/BBCodeExporter'
|
|
4
|
-
import { processStudioAST } from '@miliastry/quasar-studio'
|
|
5
|
-
|
|
6
|
-
describe('Studio Effects Valid Structure', () => {
|
|
7
|
-
it('should not alter structure of valid bbcode', () => {
|
|
8
|
-
const text = "[notice]\n [box=Hello]\n [centre]Content[/centre]\n [/box]\n[/notice]"
|
|
9
|
-
|
|
10
|
-
const layers = [
|
|
11
|
-
{ id: 'grad', type: 'gradient', active: true, value: 100, properties: { color1: '#ff0000', color2: '#0000ff' } }
|
|
12
|
-
]
|
|
13
|
-
|
|
14
|
-
const registry = new TagRegistry()
|
|
15
|
-
const { redRoot } = processStudioAST(text, layers as any, {})
|
|
16
|
-
|
|
17
|
-
const exporter = new BBCodeExporter(registry)
|
|
18
|
-
const exportedBBCode = exporter.export(redRoot)
|
|
19
|
-
|
|
20
|
-
// Strip colors to verify exact match
|
|
21
|
-
const stripped = exportedBBCode.replace(/\[\/?color[^\]]*\]/g, '')
|
|
22
|
-
|
|
23
|
-
expect(stripped).toEqual(text)
|
|
24
|
-
})
|
|
25
|
-
})
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { TagRegistry } from '../Model/TagRegistry'
|
|
3
|
-
import { HTMLRenderer } from '../Visitors/HTMLRenderer'
|
|
4
|
-
import { processStudioAST } from '@miliastry/quasar-studio'
|
|
5
|
-
import { BBCodeExporter } from '../Visitors/BBCodeExporter'
|
|
6
|
-
import fs from 'fs'
|
|
7
|
-
|
|
8
|
-
describe('URL IMG bug', () => {
|
|
9
|
-
it('should render URL with IMG inside correctly', () => {
|
|
10
|
-
const text = '[notice][centre][url=https://osekai.net/profiles?user=11624101][img]https://osekai.net/profiles/img/banner.svg?id=11624101[/img][/url][/centre][/notice]'
|
|
11
|
-
|
|
12
|
-
const registry = new TagRegistry()
|
|
13
|
-
const htmlRenderer = new HTMLRenderer({ registry })
|
|
14
|
-
const exporter = new BBCodeExporter(registry)
|
|
15
|
-
|
|
16
|
-
const { redRoot } = processStudioAST(text, [], {})
|
|
17
|
-
const html = htmlRenderer.render(redRoot)
|
|
18
|
-
const exported = exporter.export(redRoot)
|
|
19
|
-
|
|
20
|
-
console.log('HTML:\n', html)
|
|
21
|
-
console.log('Exported BBCode:\n', exported)
|
|
22
|
-
})
|
|
23
|
-
})
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { TagRegistry } from '../Model/TagRegistry'
|
|
3
|
-
import { HTMLRenderer } from '../Visitors/HTMLRenderer'
|
|
4
|
-
import { processStudioAST } from '@miliastry/quasar-studio'
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Every tag the visual builder can produce must reach the HTML with the
|
|
8
|
-
* element the preview's stylesheet expects.
|
|
9
|
-
*
|
|
10
|
-
* This file used to hold a 6 KB hardcoded "expected HTML" that was never
|
|
11
|
-
* compared against anything — it was written to `visual-builder-expected.html`
|
|
12
|
-
* and `visual-builder-actual.html` in the repo root (tracked files, dirtied on
|
|
13
|
-
* every run) for a human to eyeball. The only assertions were that the output
|
|
14
|
-
* contained `class="bb-text"` and `data-node-id`. The first stopped being true
|
|
15
|
-
* when text leaves lost their wrapper span, which is what prompted writing
|
|
16
|
-
* real assertions instead.
|
|
17
|
-
*/
|
|
18
|
-
describe('Visual Builder HTML Fidelity', () => {
|
|
19
|
-
const bbcode = `[heading]Quasar Engine Test[/heading]
|
|
20
|
-
|
|
21
|
-
[b]Bold[/b], [i]Italic[/i], [u]Underline[/u], [s]Strikethrough[/s]
|
|
22
|
-
|
|
23
|
-
[color=#61afef]Colored text[/color] and [size=150]Large text[/size]
|
|
24
|
-
|
|
25
|
-
[quote="Author"]This is a quote block with [b]formatting[/b] inside[/quote]
|
|
26
|
-
|
|
27
|
-
[code]
|
|
28
|
-
function hello() {
|
|
29
|
-
console.log("Hello World!")
|
|
30
|
-
}
|
|
31
|
-
[/code]
|
|
32
|
-
|
|
33
|
-
[list]
|
|
34
|
-
[*]Item one
|
|
35
|
-
[*]Item two
|
|
36
|
-
[*]Item three
|
|
37
|
-
[/list]
|
|
38
|
-
|
|
39
|
-
[centre][b]Centered content[/b][/centre]
|
|
40
|
-
|
|
41
|
-
[notice]This is an important notice![/notice]
|
|
42
|
-
|
|
43
|
-
[spoiler]Hidden content revealed on hover[/spoiler]
|
|
44
|
-
|
|
45
|
-
[url=https://osu.ppy.sh]osu! website[/url]
|
|
46
|
-
|
|
47
|
-
Esto
|
|
48
|
-
|
|
49
|
-
Y esto`
|
|
50
|
-
|
|
51
|
-
const render = () => {
|
|
52
|
-
const registry = new TagRegistry()
|
|
53
|
-
const renderer = new HTMLRenderer({ registry })
|
|
54
|
-
const { redRoot } = processStudioAST(bbcode, [], {})
|
|
55
|
-
return renderer.render(redRoot)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
it('maps every tag to its expected element', () => {
|
|
59
|
-
const html = render()
|
|
60
|
-
|
|
61
|
-
const cases: Array<[string, RegExp]> = [
|
|
62
|
-
['heading', /<h2[^>]*>Quasar Engine Test<\/h2>/],
|
|
63
|
-
['bold', /<strong[^>]*>Bold<\/strong>/],
|
|
64
|
-
['italic', /<em[^>]*>Italic<\/em>/],
|
|
65
|
-
['underline', /<u[^>]*>Underline<\/u>/],
|
|
66
|
-
['strikethrough', /<s[^>]*>Strikethrough<\/s>/],
|
|
67
|
-
['color', /<span[^>]*style="color:#61afef;"[^>]*>Colored text<\/span>/],
|
|
68
|
-
['size', /<span[^>]*style="font-size:150%;"[^>]*>Large text<\/span>/],
|
|
69
|
-
['quote', /<blockquote[^>]*>/],
|
|
70
|
-
['code', /<pre[^>]*><code>/],
|
|
71
|
-
['list', /<ul[^>]*>/],
|
|
72
|
-
['list item', /<li[^>]*>Item one/],
|
|
73
|
-
['centre', /<div[^>]*style="text-align:center;"[^>]*>/],
|
|
74
|
-
['notice', /<div[^>]*class="notice"[^>]*>/],
|
|
75
|
-
['spoiler', /<span[^>]*class="spoiler"[^>]*>/],
|
|
76
|
-
['url', /<a[^>]*href="https:\/\/osu\.ppy\.sh"[^>]*>osu! website<\/a>/],
|
|
77
|
-
]
|
|
78
|
-
|
|
79
|
-
for (const [label, pattern] of cases) {
|
|
80
|
-
expect(pattern.test(html), `${label} — no encontrado en el HTML`).toBe(true)
|
|
81
|
-
}
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
it('keeps the quote author and the raw code content', () => {
|
|
85
|
-
const html = render()
|
|
86
|
-
expect(html).toContain('Author')
|
|
87
|
-
expect(html).toContain('function hello()')
|
|
88
|
-
// Code content is raw text, so its quotes must be escaped, not parsed.
|
|
89
|
-
expect(html).toContain('"Hello World!"')
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
it('carries data-node-id on blocks, so preview clicks map back to nodes', () => {
|
|
93
|
-
const html = render()
|
|
94
|
-
// Blocks are id-bearing; text leaves deliberately are not.
|
|
95
|
-
expect(html).toMatch(/<h2 data-node-id="[^"]+"/)
|
|
96
|
-
expect(html).toMatch(/<blockquote data-node-id="[^"]+"/)
|
|
97
|
-
expect(html).toMatch(/<div data-node-id="[^"]+" class="notice"/)
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
it('renders trailing prose after the last tag', () => {
|
|
101
|
-
const html = render()
|
|
102
|
-
expect(html).toContain('Esto')
|
|
103
|
-
expect(html).toContain('Y esto')
|
|
104
|
-
})
|
|
105
|
-
})
|