Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
162 lines
6.5 KiB
TypeScript
162 lines
6.5 KiB
TypeScript
import { expect } from '@playwright/test'
|
|
import mermaidDefinitions from '../../src/examples/use-cases/hundred-mermaids/mermaids'
|
|
import type { MermaidReadabilityFinding } from '../../src/misc/mermaidReadability'
|
|
import test from '../fixtures/fixtures'
|
|
import { setupPage } from '../shared-e2e'
|
|
|
|
// Converts every diagram in the "Hundreds of Mermaid diagrams" example and compares the result with
|
|
// mermaid's own rendering (see `src/misc/mermaidReadability.ts`). Not part of `yarn e2e`: CI runs it
|
|
// only when mermaid conversion changes (`.github/workflows/playwright-mermaid.yml`), and locally it
|
|
// is `yarn e2e-mermaid-readability` from `apps/examples`.
|
|
|
|
// `mermaids.ts` groups its definitions by diagram type, in this order.
|
|
const DIAGRAM_TYPES = ['flowchart', 'state', 'sequence', 'mindmap'] as const
|
|
type DiagramType = (typeof DIAGRAM_TYPES)[number]
|
|
|
|
// `index` is the diagram's position within its group in `mermaids.ts`.
|
|
type Finding = { diagram: DiagramType; index: number } & MermaidReadabilityFinding
|
|
|
|
function overlap(diagram: DiagramType, index: number, label: string, over: string): Finding {
|
|
return { diagram, index, check: 'overlap', label, over }
|
|
}
|
|
|
|
function midWordBreak(diagram: DiagramType, index: number, label: string, word: string): Finding {
|
|
return { diagram, index, check: 'mid-word break', label, word }
|
|
}
|
|
|
|
function missingText(diagram: DiagramType, index: number, text: string): Finding {
|
|
return { diagram, index, check: 'missing text', text }
|
|
}
|
|
|
|
interface KnownProblem {
|
|
reason: string
|
|
findings: Finding[]
|
|
}
|
|
|
|
// Problems that exist today. An entry that stops being found fails the check too, so the list
|
|
// shrinks as they are fixed. Like the e2e snapshots, it matches chromium on Linux, where CI runs:
|
|
// text metrics differ by a pixel elsewhere, so a local run on macOS can disagree on an entry that
|
|
// is a pixel from colliding.
|
|
const KNOWN_PROBLEMS: KnownProblem[] = [
|
|
{
|
|
// Not a bug: a label is part of its arrow, so a self-message's sits on its loop. Placing it
|
|
// above the loop, as mermaid does, would mean detaching it. Closed as intended in #10796.
|
|
reason: 'Sequence self-message labels are centered on their loop, over the lifeline',
|
|
findings: [
|
|
overlap('sequence', 6, 'Recompute backoff', 'line'),
|
|
overlap('sequence', 9, 'Recalculate cache', 'line'),
|
|
overlap('sequence', 11, 'Apply discount', 'opt [Promo code provided]'),
|
|
overlap('sequence', 24, '9 Apply discount rules', 'line'),
|
|
overlap('sequence', 24, '9 Apply discount rules', 'rectangle with no text'),
|
|
overlap('sequence', 24, '9 Apply discount rules', 'opt [Customer included coupon]'),
|
|
overlap('sequence', 25, 'Recompute eviction policy', 'line'),
|
|
overlap('sequence', 28, 'Retry parsing malformed input', 'line'),
|
|
overlap('sequence', 28, 'Retry parsing malformed input', 'rectangle with no text'),
|
|
overlap(
|
|
'sequence',
|
|
29,
|
|
'perform an unusually long internal bookkeeping step with a very wide label',
|
|
'line'
|
|
),
|
|
overlap(
|
|
'sequence',
|
|
32,
|
|
'parse chunk 3 with a suspiciously long status label for layout testing',
|
|
'line'
|
|
),
|
|
overlap(
|
|
'sequence',
|
|
32,
|
|
'parse chunk 3 with a suspiciously long status label for layout testing',
|
|
'rectangle with no text'
|
|
),
|
|
],
|
|
},
|
|
{
|
|
// Listed as a known limitation in #10773.
|
|
reason: 'A self-loop on the top or bottom of a narrow state node gets too little label width',
|
|
findings: [midWordBreak('state', 21, 'type character', 'chara/cter')],
|
|
},
|
|
{
|
|
// Mermaid lays edge labels out for a smaller font than the node size they are drawn at, so a
|
|
// label near a composite state's edge can reach past it. Accepted in #10804, which drew them at
|
|
// the node size so they read at the same size as the nodes.
|
|
reason: 'An edge label near a composite state reaches past its edge',
|
|
findings: [overlap('state', 19, 'manager rejects', 'Review')],
|
|
},
|
|
{
|
|
reason:
|
|
'Frame titles are drawn at the top left of the frame, where an activation bar runs through them',
|
|
findings: [
|
|
'par [Fetch product data]',
|
|
'[Check stock]',
|
|
'opt [Customer included coupon]',
|
|
'critical [Commit purchase]',
|
|
'[Payment failed]',
|
|
'par [Notify customer]',
|
|
'[Track analytics]',
|
|
].map((label) => overlap('sequence', 24, label, 'rectangle with no text')),
|
|
},
|
|
{
|
|
reason: 'A note in an `option` section is drawn over the section title',
|
|
findings: [overlap('sequence', 24, 'Stop flow before inventory mutation', '[Payment failed]')],
|
|
},
|
|
{
|
|
reason:
|
|
"A section title's box is taller than its text, so a frame nested right under it starts inside the box",
|
|
findings: [overlap('sequence', 22, 'opt [New device detected]', '[Optional alert]')],
|
|
},
|
|
{
|
|
// Not a conversion bug: inside a composite state, mermaid ignores a `<<fork>>` or `<<join>>`
|
|
// declared after the state is first used and draws a plain named state. The conversion draws
|
|
// the bar the source asks for.
|
|
reason: 'Mermaid draws these forks and joins as named states',
|
|
findings: [
|
|
missingText('state', 11, 'Fork'),
|
|
missingText('state', 11, 'Join'),
|
|
missingText('state', 20, 'F'),
|
|
missingText('state', 20, 'J'),
|
|
],
|
|
},
|
|
]
|
|
|
|
function getKey(finding: Finding) {
|
|
return JSON.stringify(finding, Object.keys(finding).sort())
|
|
}
|
|
|
|
test.describe('Mermaid readability', () => {
|
|
test.skip(({ isMobile }) => isMobile, 'Readability is measured at desktop size only')
|
|
|
|
DIAGRAM_TYPES.forEach((diagram, group) => {
|
|
test(`${diagram} diagrams`, async ({ page, api }) => {
|
|
test.setTimeout(120_000)
|
|
await setupPage(page)
|
|
await api.preloadFonts()
|
|
// A font that fails to load is swapped for a system one, whose metrics wrap text
|
|
// differently. That would surface below as breaks and overlaps, not as a font problem.
|
|
const failedFonts = await page.evaluate(() => [
|
|
...new Set(
|
|
[...document.fonts].filter((font) => font.status === 'error').map((font) => font.family)
|
|
),
|
|
])
|
|
expect(failedFonts).toEqual([])
|
|
|
|
const found: Finding[] = []
|
|
for (const [index, definition] of mermaidDefinitions[group].entries()) {
|
|
const findings = await api.checkMermaidReadability(definition)
|
|
found.push(...findings.map((finding) => ({ diagram, index, ...finding })))
|
|
}
|
|
|
|
const known = KNOWN_PROBLEMS.flatMap((problem) => problem.findings).filter(
|
|
(finding) => finding.diagram === diagram
|
|
)
|
|
const foundKeys = new Set(found.map(getKey))
|
|
const knownKeys = new Set(known.map(getKey))
|
|
expect({
|
|
unexpected: found.filter((finding) => !knownKeys.has(getKey(finding))),
|
|
noLongerFound: known.filter((finding) => !foundKeys.has(getKey(finding))),
|
|
}).toEqual({ unexpected: [], noLongerFound: [] })
|
|
})
|
|
})
|
|
})
|