mirror of https://github.com/buster-so/buster.git
Added some basic markdown conversion stuff
This commit is contained in:
parent
483a6f037a
commit
32abc2d939
|
@ -1555,3 +1555,384 @@ Nested
|
|||
expect(platejs[1].children[0].text).toBe('Nested');
|
||||
});
|
||||
});
|
||||
|
||||
describe('metric escaping bug tests', () => {
|
||||
it('should not add backslashes to metric tags during multiple conversions', async () => {
|
||||
const originalMarkdown = `# Sales Report
|
||||
|
||||
Our top performer this month shows impressive results.
|
||||
|
||||
<metric metricId="abc-123-def" width="100%" caption="Sales Performance"></metric>
|
||||
|
||||
## Summary
|
||||
Great performance across all metrics.`;
|
||||
|
||||
// First conversion cycle: markdown -> platejs -> markdown
|
||||
const platejs = await markdownToPlatejs(editor, originalMarkdown);
|
||||
const convertedMarkdown = await platejsToMarkdown(editor, platejs);
|
||||
|
||||
// Second conversion cycle: markdown -> platejs -> markdown (simulates save operation)
|
||||
const platejs2 = await markdownToPlatejs(editor, convertedMarkdown);
|
||||
const convertedMarkdown2 = await platejsToMarkdown(editor, platejs2);
|
||||
|
||||
// Third conversion cycle (simulates another save operation)
|
||||
const platejs3 = await markdownToPlatejs(editor, convertedMarkdown2);
|
||||
const convertedMarkdown3 = await platejsToMarkdown(editor, platejs3);
|
||||
|
||||
// Should not contain escaped metric tags
|
||||
expect(convertedMarkdown3).not.toContain('\\<metric');
|
||||
expect(convertedMarkdown3).not.toContain('\\<\\/metric');
|
||||
expect(convertedMarkdown3).toContain('<metric metricId="abc-123-def"');
|
||||
expect(convertedMarkdown3).toContain('</metric>');
|
||||
|
||||
// Verify the metric element is still properly parsed
|
||||
const finalPlatejs = await markdownToPlatejs(editor, convertedMarkdown3);
|
||||
const metricElement = finalPlatejs.find((el) => el.type === 'metric');
|
||||
expect(metricElement).toBeDefined();
|
||||
expect(metricElement?.metricId).toBe('abc-123-def');
|
||||
});
|
||||
|
||||
it('should handle metric tags with special characters in attributes', async () => {
|
||||
const markdown = `<metric metricId="test-123-with-&-special-chars" caption="Revenue & Profit Analysis" width="100%"></metric>`;
|
||||
|
||||
// Multiple conversion cycles
|
||||
const platejs = await markdownToPlatejs(editor, markdown);
|
||||
const convertedMarkdown = await platejsToMarkdown(editor, platejs);
|
||||
const platejs2 = await markdownToPlatejs(editor, convertedMarkdown);
|
||||
const convertedMarkdown2 = await platejsToMarkdown(editor, platejs2);
|
||||
|
||||
// Should not escape the metric tags themselves
|
||||
expect(convertedMarkdown2).not.toContain('\\<metric');
|
||||
expect(convertedMarkdown2).toContain('<metric metricId="test-123-with-&-special-chars"');
|
||||
});
|
||||
|
||||
it('should preserve metric tags mixed with escaped markdown content', async () => {
|
||||
const markdown = `# Report with Mixed Content
|
||||
|
||||
Here's some text with \\*escaped markdown\\* and **actual bold**.
|
||||
|
||||
<metric metricId="mixed-content-test" caption="Test Metric"></metric>
|
||||
|
||||
- List item with \\[escaped brackets\\]
|
||||
- Normal list item
|
||||
|
||||
\\<not-a-metric\\> but this should remain escaped.`;
|
||||
|
||||
const platejs = await markdownToPlatejs(editor, markdown);
|
||||
const convertedMarkdown = await platejsToMarkdown(editor, platejs);
|
||||
const platejs2 = await markdownToPlatejs(editor, convertedMarkdown);
|
||||
const convertedMarkdown2 = await platejsToMarkdown(editor, platejs2);
|
||||
|
||||
// Metric tags should not be escaped
|
||||
expect(convertedMarkdown2).toContain('<metric metricId="mixed-content-test"');
|
||||
expect(convertedMarkdown2).not.toContain('\\<metric metricId="mixed-content-test"');
|
||||
|
||||
console.log(convertedMarkdown2);
|
||||
|
||||
// But other escaped content should remain escaped
|
||||
expect(convertedMarkdown2).toContain('\\<not-a-metric\\>');
|
||||
});
|
||||
|
||||
it('should handle multiple metric tags in a single document', async () => {
|
||||
const markdown = `# Multi-Metric Report
|
||||
|
||||
<metric metricId="first-metric" caption="First Metric"></metric>
|
||||
|
||||
Some content between metrics.
|
||||
|
||||
<metric metricId="second-metric" width="50%" caption="Second Metric"></metric>
|
||||
|
||||
More content.
|
||||
|
||||
<metric metricId="third-metric" versionNumber="1.2" caption="Third Metric"></metric>`;
|
||||
|
||||
// Simulate multiple save operations
|
||||
let currentMarkdown = markdown;
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const platejs = await markdownToPlatejs(editor, currentMarkdown);
|
||||
currentMarkdown = await platejsToMarkdown(editor, platejs);
|
||||
}
|
||||
|
||||
// None of the metrics should be escaped
|
||||
expect(currentMarkdown).not.toContain('\\<metric');
|
||||
expect(currentMarkdown).not.toContain('\\</metric>');
|
||||
|
||||
// All metrics should still be present
|
||||
expect(currentMarkdown).toContain('<metric metricId="first-metric"');
|
||||
expect(currentMarkdown).toContain('<metric metricId="second-metric"');
|
||||
expect(currentMarkdown).toContain('<metric metricId="third-metric"');
|
||||
|
||||
// Final conversion should still work
|
||||
const finalPlatejs = await markdownToPlatejs(editor, currentMarkdown);
|
||||
const metricElements = finalPlatejs.filter((el) => el.type === 'metric');
|
||||
expect(metricElements).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('should handle metric tags in complex document structures', async () => {
|
||||
const markdown = `# Complex Document
|
||||
|
||||
## Section 1
|
||||
Here's a list:
|
||||
- Item 1
|
||||
- Item 2
|
||||
|
||||
<metric metricId="list-metric" caption="After List Metric"></metric>
|
||||
|
||||
> This is a blockquote with a metric inside:
|
||||
>
|
||||
> <metric metricId="blockquote-metric" caption="Blockquote Metric"></metric>
|
||||
>
|
||||
> End of quote.
|
||||
|
||||
\`\`\`
|
||||
// Code block
|
||||
function test() {
|
||||
return "not a <metric>";
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
<metric metricId="after-code-metric" caption="After Code Metric"></metric>`;
|
||||
|
||||
// Multiple conversion cycles
|
||||
let currentMarkdown = markdown;
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const platejs = await markdownToPlatejs(editor, currentMarkdown);
|
||||
currentMarkdown = await platejsToMarkdown(editor, platejs);
|
||||
}
|
||||
|
||||
// Metrics should not be escaped
|
||||
expect(currentMarkdown).not.toContain('\\<metric');
|
||||
expect(currentMarkdown).toContain('<metric metricId="list-metric"');
|
||||
expect(currentMarkdown).toContain('<metric metricId="blockquote-metric"');
|
||||
expect(currentMarkdown).toContain('<metric metricId="after-code-metric"');
|
||||
|
||||
// Code block content should remain unchanged
|
||||
expect(currentMarkdown).toContain('return "not a <metric>";');
|
||||
});
|
||||
|
||||
it('should preserve metric functionality after content edits and saves', async () => {
|
||||
// Simulate the workflow: create report -> edit -> save -> edit -> save
|
||||
const initialMarkdown = `# Initial Report
|
||||
|
||||
<metric metricId="initial-metric" caption="Initial Metric"></metric>`;
|
||||
|
||||
// First save cycle
|
||||
const platejs1 = await markdownToPlatejs(editor, initialMarkdown);
|
||||
const savedMarkdown1 = await platejsToMarkdown(editor, platejs1);
|
||||
|
||||
// Edit: add content
|
||||
const editedMarkdown = savedMarkdown1 + `\n\n## New Section\nAdded content after save.`;
|
||||
|
||||
// Second save cycle
|
||||
const platejs2 = await markdownToPlatejs(editor, editedMarkdown);
|
||||
const savedMarkdown2 = await platejsToMarkdown(editor, platejs2);
|
||||
|
||||
// Edit: add another metric
|
||||
const editedMarkdown2 = savedMarkdown2.replace(
|
||||
'## New Section',
|
||||
`<metric metricId="added-metric" caption="Added Later"></metric>\n\n## New Section`
|
||||
);
|
||||
|
||||
// Third save cycle
|
||||
const platejs3 = await markdownToPlatejs(editor, editedMarkdown2);
|
||||
const savedMarkdown3 = await platejsToMarkdown(editor, platejs3);
|
||||
|
||||
// Neither metric should be escaped
|
||||
expect(savedMarkdown3).not.toContain('\\<metric');
|
||||
expect(savedMarkdown3).toContain('<metric metricId="initial-metric"');
|
||||
expect(savedMarkdown3).toContain('<metric metricId="added-metric"');
|
||||
|
||||
// Both metrics should parse correctly
|
||||
const finalPlatejs = await markdownToPlatejs(editor, savedMarkdown3);
|
||||
const metrics = finalPlatejs.filter((el) => el.type === 'metric');
|
||||
expect(metrics).toHaveLength(2);
|
||||
expect(metrics[0].metricId).toBe('initial-metric');
|
||||
expect(metrics[1].metricId).toBe('added-metric');
|
||||
});
|
||||
|
||||
// Edge cases that might trigger the escaping bug
|
||||
it('should handle metric tags when markdown processor sees angle brackets as special', async () => {
|
||||
// This test specifically targets potential escaping during markdown processing
|
||||
const markdown = `# Test Report
|
||||
|
||||
Text before metric.
|
||||
|
||||
<metric metricId="edge-case-test" caption="Test < and > in caption"></metric>
|
||||
|
||||
Text after metric.`;
|
||||
|
||||
// Process through multiple serialization cycles to trigger any escaping behavior
|
||||
let result = markdown;
|
||||
for (let cycle = 0; cycle < 10; cycle++) {
|
||||
const platejs = await markdownToPlatejs(editor, result);
|
||||
result = await platejsToMarkdown(editor, platejs);
|
||||
|
||||
// After each cycle, metric tags should never be escaped
|
||||
expect(result).not.toContain('\\<metric');
|
||||
expect(result).not.toContain('\\</metric>');
|
||||
expect(result).toContain('<metric metricId="edge-case-test"');
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle edge case of metric tags with malformed HTML nearby', async () => {
|
||||
const markdown = `# Report with Malformed Content
|
||||
|
||||
<metric metricId="test-metric-1" caption="First Metric"></metric>
|
||||
|
||||
Some text with <unclosed-tag and mismatched </div> tags.
|
||||
|
||||
<metric metricId="test-metric-2" caption="Second Metric"></metric>
|
||||
|
||||
More content.`;
|
||||
|
||||
// Multiple conversion cycles
|
||||
let result = markdown;
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const platejs = await markdownToPlatejs(editor, result);
|
||||
result = await platejsToMarkdown(editor, platejs);
|
||||
}
|
||||
|
||||
// Metric tags should not be escaped even with malformed HTML nearby
|
||||
expect(result).not.toContain('\\<metric');
|
||||
expect(result).toContain('<metric metricId="test-metric-1"');
|
||||
expect(result).toContain('<metric metricId="test-metric-2"');
|
||||
});
|
||||
|
||||
it('should handle metric tags when content contains backslashes already', async () => {
|
||||
const markdown = `# Report with Existing Backslashes
|
||||
|
||||
This is \\*escaped\\* markdown text.
|
||||
|
||||
<metric metricId="backslash-test" caption="Test Metric"></metric>
|
||||
|
||||
Some code: \`console.log("test\\n");\`
|
||||
|
||||
Another escaped: \\<div\\>content\\</div\\>`;
|
||||
|
||||
// Process multiple times to see if existing backslashes interfere
|
||||
let result = markdown;
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const platejs = await markdownToPlatejs(editor, result);
|
||||
result = await platejsToMarkdown(editor, platejs);
|
||||
}
|
||||
|
||||
// Metric should not be escaped
|
||||
expect(result).not.toContain('\\<metric');
|
||||
expect(result).toContain('<metric metricId="backslash-test"');
|
||||
|
||||
// Existing escaped content should remain
|
||||
expect(result).toContain('\\*escaped\\*');
|
||||
expect(result).toContain('\\<div\\>');
|
||||
});
|
||||
|
||||
it('should reproduce the reported bug scenario: works initially but breaks after saves', async () => {
|
||||
// Initial report creation - this should work fine
|
||||
const initialContent = `# Sales Performance Report
|
||||
|
||||
<metric metricId="sales-overview" caption="Q4 Sales Overview"></metric>
|
||||
|
||||
## Analysis
|
||||
The data shows strong performance this quarter.`;
|
||||
|
||||
// Simulate initial save (first conversion)
|
||||
const initialPlatejs = await markdownToPlatejs(editor, initialContent);
|
||||
expect(initialPlatejs.find((el) => el.type === 'metric')).toBeDefined();
|
||||
|
||||
const firstSave = await platejsToMarkdown(editor, initialPlatejs);
|
||||
expect(firstSave).toContain('<metric metricId="sales-overview"');
|
||||
expect(firstSave).not.toContain('\\<metric');
|
||||
|
||||
// Simulate user editing and additional saves
|
||||
let currentContent = firstSave;
|
||||
|
||||
// Multiple edit/save cycles (this is where the bug reportedly occurs)
|
||||
for (let saveCount = 1; saveCount <= 10; saveCount++) {
|
||||
// Convert to editor format
|
||||
const platejs = await markdownToPlatejs(editor, currentContent);
|
||||
|
||||
// Simulate user adding some content (like what happens during editing)
|
||||
platejs.push({
|
||||
type: 'p',
|
||||
children: [{ text: `Edit from save cycle ${saveCount}` }],
|
||||
});
|
||||
|
||||
// Save back to markdown
|
||||
currentContent = await platejsToMarkdown(editor, platejs);
|
||||
|
||||
// At NO point should the metric tag be escaped
|
||||
expect(currentContent).not.toContain('\\<metric');
|
||||
expect(currentContent).not.toContain('\\</metric>');
|
||||
expect(currentContent).toContain('<metric metricId="sales-overview"');
|
||||
|
||||
// The metric should still be parseable
|
||||
const testPlatejs = await markdownToPlatejs(editor, currentContent);
|
||||
const metricEl = testPlatejs.find((el) => el.type === 'metric');
|
||||
expect(metricEl).toBeDefined();
|
||||
expect(metricEl?.metricId).toBe('sales-overview');
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle JSON serialization scenarios that might escape content', async () => {
|
||||
// This test targets potential escaping during JSON serialization/deserialization
|
||||
// which might happen when content is sent to/from the server
|
||||
const markdown = `# Report with JSON-Sensitive Content
|
||||
|
||||
<metric metricId="json-test-metric" caption='Caption with "quotes" and special chars'></metric>
|
||||
|
||||
Text with "quotes" and 'apostrophes'.`;
|
||||
|
||||
// Simulate JSON serialization/deserialization that might happen during API calls
|
||||
const platejs = await markdownToPlatejs(editor, markdown);
|
||||
const serializedPlateJS = JSON.parse(JSON.stringify(platejs));
|
||||
const backToMarkdown = await platejsToMarkdown(editor, serializedPlateJS);
|
||||
|
||||
// Convert back to PlateJS again (simulating another round trip)
|
||||
const platejs2 = await markdownToPlatejs(editor, backToMarkdown);
|
||||
const serializedPlateJS2 = JSON.parse(JSON.stringify(platejs2));
|
||||
const finalMarkdown = await platejsToMarkdown(editor, serializedPlateJS2);
|
||||
|
||||
// Metric tags should never be escaped during JSON round trips
|
||||
expect(finalMarkdown).not.toContain('\\<metric');
|
||||
expect(finalMarkdown).toContain('<metric metricId="json-test-metric"');
|
||||
});
|
||||
|
||||
it('should handle streaming content updates without escaping metrics', async () => {
|
||||
// This simulates the streaming content updates that happen during report generation
|
||||
const baseMarkdown = `# Streaming Report
|
||||
|
||||
Initial content.`;
|
||||
|
||||
const streamedAddition = `
|
||||
|
||||
<metric metricId="streamed-metric" caption="Added During Stream"></metric>
|
||||
|
||||
More streamed content.`;
|
||||
|
||||
// Simulate initial content
|
||||
let platejs = await markdownToPlatejs(editor, baseMarkdown);
|
||||
|
||||
// Simulate streaming addition (like what happens during AI content generation)
|
||||
const streamedPlatejs = await markdownToPlatejs(editor, streamedAddition);
|
||||
platejs = platejs.concat(streamedPlatejs);
|
||||
|
||||
// Convert back to markdown (simulating save)
|
||||
const result = await platejsToMarkdown(editor, platejs);
|
||||
|
||||
// Multiple conversion cycles to simulate additional streaming and saves
|
||||
let currentContent = result;
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const tempPlatejs = await markdownToPlatejs(editor, currentContent);
|
||||
// Add more streamed content
|
||||
tempPlatejs.push({
|
||||
type: 'p',
|
||||
children: [{ text: `Stream update ${i}` }],
|
||||
});
|
||||
currentContent = await platejsToMarkdown(editor, tempPlatejs);
|
||||
}
|
||||
|
||||
// Metric should not be escaped during streaming operations
|
||||
expect(currentContent).not.toContain('\\<metric');
|
||||
expect(currentContent).toContain('<metric metricId="streamed-metric"');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,7 +8,7 @@ const isProduction = import.meta.env.PROD;
|
|||
// Only create lazy components if we're in the browser
|
||||
const LazyTanstackDevtools = !import.meta.env.SSR
|
||||
? lazy(() =>
|
||||
import('@tanstack/react-devtools/production').then((mod) => ({
|
||||
import('@tanstack/react-devtools').then((mod) => ({
|
||||
default: mod.TanStackDevtools,
|
||||
}))
|
||||
)
|
||||
|
@ -16,7 +16,7 @@ const LazyTanstackDevtools = !import.meta.env.SSR
|
|||
|
||||
const LazyTanstackDevtoolsInProd = !import.meta.env.SSR
|
||||
? lazy(() =>
|
||||
import('@tanstack/react-devtools/production').then((mod) => ({
|
||||
import('@tanstack/react-devtools').then((mod) => ({
|
||||
default: mod.TanStackDevtools,
|
||||
}))
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue