import { expect, it } from 'vitest'
// WebKit can report a Mac user agent while running on Linux.
const historyModifier = process.platform === 'darwin' ? 'Meta' : 'Control'
async function verifyCodeBlockSelections({
browser,
openPage,
setContent,
select,
press,
readSurface
}) {
const cases = [
{ markdown: 'selected text', html: '
selected text
', literal: 'selected text' },
{ markdown: 'first\nsecond', html: 'first
second
', literal: 'first\nsecond' },
{
markdown: 'first\n\nsecond',
html: 'first
\nsecond
',
literal: 'first\nsecond'
},
{
markdown: '**bold** and *italic*',
html: 'bold and italic
',
literal: 'bold and italic'
},
{
markdown: '```ts\nfirst\nsecond\n```',
html: 'first\nsecond
',
literal: 'first\nsecond',
language: 'ts'
},
{
markdown: 'Prefix first\n\nMiddle text\n\nlast suffix',
html: 'Prefix first
\nMiddle text
\nlast suffix
',
literal: 'Prefix first\nMiddle text\nlast suffix',
partial: true
},
{
markdown: 'Before\n\nAfter',
html: 'Before
\nAfter
',
literal: 'code',
collapsed: true,
saved: 'Before\n\n```\ncode\n```\n\nAfter',
cleanBefore: true
},
{
markdown: 'First\n\nAfter',
html: 'First
\nAfter
',
literal: 'code',
caret: { selector: 'p', offset: 2 },
saved: 'Fi\n\n```\ncode\n```\n\nrst\n\nAfter'
},
{
markdown: 'First\n\nAfter',
html: 'First
\nAfter
',
literal: 'code',
caret: { selector: 'p', offset: 'end' },
saved: 'First\n\n```\ncode\n```\n\nAfter'
},
{
markdown: '- First\n- Next',
html: '',
literal: 'code',
caret: { selector: 'li p', offset: 'end' },
saved: '- First\n ```\n code\n ```\n- Next'
},
{
markdown: '',
html: '
',
literal: 'code',
caret: { selector: 'p', offset: 0 },
saved: '```\ncode\n```',
cleanBefore: true
},
{
markdown: 'Before\n\nSelected text\n\n- after',
html: 'Before
\nSelected text
\n',
literal: 'Selected text',
paragraph: true,
saved: 'Before\n\n```\nSelected text\n```\n\n- after'
}
]
for (const sample of cases) {
const { page, consoleErrors } = await openPage(browser)
try {
await setContent(page, sample.markdown, sample.html)
await (sample.partial || sample.collapsed || sample.paragraph || sample.caret
? page.evaluate(
({ partial, paragraph, caret }) => {
const editor = document.querySelector('#first-surface #editor')
const paragraphs = editor.querySelectorAll('p')
editor.focus()
const range = document.createRange()
if (caret) {
const target = editor.querySelector(caret.selector)
if (caret.offset === 'end') {
range.selectNodeContents(target)
range.collapse(false)
} else {
range.setStart(target.firstChild ?? target, caret.offset)
range.collapse(true)
}
} else if (partial) {
range.setStart(paragraphs[0].firstChild, 7)
range.setEnd(paragraphs[2].firstChild, 4)
} else if (paragraph) {
range.selectNodeContents(paragraphs[1])
} else {
range.setStart(paragraphs[1].firstChild, 0)
range.collapse(true)
}
window.getSelection().removeAllRanges()
window.getSelection().addRange(range)
},
{
partial: sample.partial === true,
paragraph: sample.paragraph === true,
caret: sample.caret ?? null
}
)
: select(page, 'all'))
const selected = await page.evaluate(() => window.getSelection().toString())
await press(page, 'Code block')
const saved = sample.saved ?? `\`\`\`${sample.language ?? ''}\n${sample.literal}\n\`\`\``
await expect
.poll(() => page.evaluate(() => globalThis.__orcaEditor.changes.at(-1)))
.toBe(saved)
if (sample.cleanBefore) {
// No blank line may appear above a code block inserted at an empty line or a block start.
expect(
await page.evaluate(() => {
const before = document.querySelector('#first-surface #editor pre').previousSibling
return before === null || (before.nodeName !== 'BR' && before.textContent !== '')
})
).toBe(true)
}
await page.keyboard.press(`${historyModifier}+z`)
await expect.poll(() => readSurface(page)).toBe(sample.html)
expect(await page.evaluate(() => window.getSelection().toString())).toBe(selected)
await page.keyboard.press(`${historyModifier}+Shift+z`)
await expect
.poll(() => page.evaluate(() => globalThis.__orcaEditor.changes.at(-1)))
.toBe(saved)
await page.evaluate(
(content) => globalThis.__orcaEditor.set({ content, generation: 1 }),
saved
)
await expect
.poll(() => page.locator('#first-surface #editor pre code').textContent())
.toBe(sample.literal)
expect(await page.evaluate(() => globalThis.__orcaCspViolations)).toEqual([])
expect(consoleErrors).toEqual([])
} finally {
await page.close()
}
}
}
async function verifyStructuredSelections({
browser,
openPage,
setContent,
select,
press,
readSurface
}) {
const samples = [
{ markdown: 'Before\n\n- first\n- second\n - nested\n\nAfter', selector: 'ul' },
{ markdown: '| one | two |\n| --- | --- |\n| a | b |', selector: 'table' },
{
markdown: 'left  right',
html: 'left
right
'
}
]
for (const sample of samples) {
const { page, consoleErrors } = await openPage(browser)
try {
if (sample.html) {
await setContent(page, sample.markdown, sample.html)
} else {
await page.evaluate((content) => globalThis.__orcaEditor.set({ content }), sample.markdown)
await page.waitForSelector(`#first-surface #editor ${sample.selector}`)
}
await select(page, 'all')
const before = await readSurface(page)
const selected = await page.evaluate(() => window.getSelection().toString())
await page.evaluate(() => {
globalThis.__orcaEditor.changes.length = 0
})
await press(page, 'Code block')
expect(await readSurface(page)).toBe(before)
expect(await page.evaluate(() => window.getSelection().toString())).toBe(selected)
expect(await page.evaluate(() => globalThis.__orcaEditor.changes)).toEqual([])
expect(await page.evaluate(() => globalThis.__orcaCspViolations)).toEqual([])
expect(consoleErrors).toEqual([])
} finally {
await page.close()
}
}
}
export function codeBlockSelectionTest(
getBrowser,
openPage,
setContent,
select,
press,
readSurface
) {
it(
'preserves selected text through code-block formatting, undo and reload',
() =>
verifyCodeBlockSelections({
browser: getBrowser(),
openPage,
setContent,
select,
press,
readSurface
}),
600_000
)
structuredCodeBlockSelectionTest(getBrowser, openPage, setContent, select, press, readSurface)
}
function structuredCodeBlockSelectionTest(
getBrowser,
openPage,
setContent,
select,
press,
readSurface
) {
it(
'leaves structured selections unchanged instead of formatting them as code',
() =>
verifyStructuredSelections({
browser: getBrowser(),
openPage,
setContent,
select,
press,
readSurface
}),
600_000
)
}