Support nested toggles as editable blocks with recursion guards

Previously nested details blocks were preserved as inert passthrough HTML.
Now, nested details that themselves meet editability criteria are opened as
editable toggle nodes. Recursive validation includes a 16-level nesting limit
to prevent stack exhaustion on pathological input. Refactors common markdown
editor test helpers into a reusable fixture module.
This commit is contained in:
Jinjing
2026-08-27 16:38:58 -07:00
parent 551fbb9ac7
commit e1a7b0b022
7 changed files with 585 additions and 137 deletions
@@ -2,11 +2,25 @@ import { afterEach, describe, expect, it, vi } from 'vitest'
import {
extractDetailsSummaryHtml,
isEditableDetailsHtmlBlock,
matchDetailsHtmlBlock,
parseDetailsAttributes,
parseToggleHeadingVariant,
type DetailsHtmlBlock
} from './details-markdown-html'
function nestedToggles(depth: number): string {
let html = '<details class="orca-details" open>\n<summary>leaf</summary>\n\nBody\n\n</details>'
for (let level = depth - 1; level > 0; level -= 1) {
html = `<details class="orca-details" open>\n<summary>level ${level}</summary>\n\n${html}\n\n</details>`
}
return html
}
function isEditableHtml(html: string): boolean {
const block = matchDetailsHtmlBlock(html, 0)
return block !== null && isEditableDetailsHtmlBlock(block)
}
afterEach(() => {
vi.restoreAllMocks()
})
@@ -34,8 +48,7 @@ describe('details markdown html', () => {
const block: DetailsHtmlBlock = {
raw: '',
openingAttributes: '',
inner: `<summary>${'Heading line\n'.repeat(1_000)}</summary><p>Body</p>`,
hasNestedDetails: false
inner: `<summary>${'Heading line\n'.repeat(1_000)}</summary><p>Body</p>`
}
expect(isEditableDetailsHtmlBlock(block)).toBe(true)
@@ -61,17 +74,51 @@ describe('details markdown html', () => {
const editableHeading5: DetailsHtmlBlock = {
raw: '',
openingAttributes: ' data-orca-toggle="heading-5"',
inner: '<summary>Toggle</summary><p>Body</p>',
hasNestedDetails: false
inner: '<summary>Toggle</summary><p>Body</p>'
}
const unsupportedHeading6: DetailsHtmlBlock = {
raw: '',
openingAttributes: ' data-orca-toggle="heading-6"',
inner: '<summary>Toggle</summary><p>Body</p>',
hasNestedDetails: false
inner: '<summary>Toggle</summary><p>Body</p>'
}
expect(isEditableDetailsHtmlBlock(editableHeading5)).toBe(true)
expect(isEditableDetailsHtmlBlock(unsupportedHeading6)).toBe(false)
})
it('accepts nested toggles whose descendants are all editable', () => {
expect(isEditableHtml(nestedToggles(2))).toBe(true)
expect(isEditableHtml(nestedToggles(16))).toBe(true)
})
it('rejects a toggle whose nested toggle is not itself editable', () => {
const block: DetailsHtmlBlock = {
raw: '',
openingAttributes: ' class="orca-details"',
inner: '<summary>Outer</summary><details id="x"><summary>Inner</summary><p>Body</p></details>'
}
expect(isEditableDetailsHtmlBlock(block)).toBe(false)
})
it('rejects nesting past the recursion guard instead of recursing without bound', () => {
expect(isEditableHtml(nestedToggles(17))).toBe(false)
expect(isEditableHtml(nestedToggles(400))).toBe(false)
})
it('rejects unbalanced and non-toggle tags that start with details', () => {
const unbalanced: DetailsHtmlBlock = {
raw: '',
openingAttributes: '',
inner: '<summary>Outer</summary><details><summary>Inner</summary>'
}
const lookalike: DetailsHtmlBlock = {
raw: '',
openingAttributes: '',
inner: '<summary>Outer</summary><detailsish>Body</detailsish>'
}
expect(isEditableDetailsHtmlBlock(unbalanced)).toBe(false)
expect(isEditableDetailsHtmlBlock(lookalike)).toBe(false)
})
})
@@ -34,7 +34,6 @@ export type DetailsHtmlBlock = {
raw: string
openingAttributes: string
inner: string
hasNestedDetails: boolean
}
export type DetailsSummaryHtml = {
@@ -145,7 +144,6 @@ export function matchDetailsHtmlBlock(content: string, start: number): DetailsHt
const fenceRanges = markdownFenceRanges(content)
let depth = 0
let hasNestedDetails = false
for (;;) {
const tagMatch = detailsTagPattern.exec(content)
@@ -167,14 +165,10 @@ export function matchDetailsHtmlBlock(content: string, start: number): DetailsHt
return {
raw: content.slice(start, closingEnd),
openingAttributes: openingMatch[0].replace(/^<details\b/i, '').replace(/>$/u, ''),
inner: content.slice(start + openingMatch[0].length, tagMatch.index),
hasNestedDetails
inner: content.slice(start + openingMatch[0].length, tagMatch.index)
}
}
} else {
if (depth > 0) {
hasNestedDetails = true
}
depth += 1
}
}
@@ -267,11 +261,37 @@ function isHtmlTagNamePart(code: number): boolean {
)
}
export function isEditableDetailsHtmlBlock(block: DetailsHtmlBlock): boolean {
if (block.hasNestedDetails) {
return false
}
// Why: nested toggles validate recursively and each level rescans its own body,
// so a pathological file would otherwise blow the stack or go quadratic.
const MAX_DETAILS_NESTING_LEVELS = 16
// Removes nested toggles that are themselves editable, so the caller's raw-tag
// scan sees only the body's own markup. Null when a nested toggle can't be one.
function stripEditableNestedDetails(bodyHtml: string, nestingLevel: number): string | null {
let result = ''
let index = 0
for (;;) {
const nestedStart = indexOfAsciiIgnoreCase(bodyHtml, '<details', index)
if (nestedStart === -1) {
return result + bodyHtml.slice(index)
}
if (nestingLevel >= MAX_DETAILS_NESTING_LEVELS) {
return null
}
const nested = matchDetailsHtmlBlock(bodyHtml, nestedStart)
if (!nested || !isEditableDetailsHtmlBlock(nested, nestingLevel + 1)) {
return null
}
result += bodyHtml.slice(index, nestedStart)
index = nestedStart + nested.raw.length
}
}
export function isEditableDetailsHtmlBlock(block: DetailsHtmlBlock, nestingLevel = 1): boolean {
if (!hasOnlySupportedDetailsAttributes(block.openingAttributes)) {
return false
}
@@ -289,7 +309,11 @@ export function isEditableDetailsHtmlBlock(block: DetailsHtmlBlock): boolean {
return false
}
const bodyHtml = block.inner.slice(summary.rawLength)
const bodyHtml = stripEditableNestedDetails(block.inner.slice(summary.rawLength), nestingLevel)
if (bodyHtml === null) {
return false
}
if (!hasOnlyPlainParagraphAndBreakTags(bodyHtml)) {
return false
}
@@ -186,9 +186,74 @@ describe('rich markdown round trip', () => {
expect(roundTripMarkdown(input)).toBe(input.trimEnd())
})
it('preserves nested details blocks as passthrough html', () => {
it('reopens nested toggles as editable details blocks', () => {
expect(
roundTripMarkdown(
'<details><summary>Outer</summary><details><summary>Inner</summary><p>Body</p></details></details>\n'
)
).toBe(
[
'<details class="orca-details">',
'<summary>Outer</summary>',
'',
'<details class="orca-details">',
'<summary>Inner</summary>',
'',
'Body',
'',
'</details>',
'',
'</details>'
].join('\n')
)
})
it('round-trips an orca-authored nested toggle unchanged', () => {
const input = [
'<details class="orca-details" data-orca-toggle="heading-3" open>',
'<summary>08/26/2026</summary>',
'',
'<details class="orca-details" open>',
'<summary>goals</summary>',
'',
'- Read X post',
' - Collab',
'',
'</details>',
'',
'- after inner',
'',
'</details>',
''
].join('\n')
expect(roundTripMarkdown(input)).toBe(input.trimEnd())
})
it('keeps nested toggle bodies editable rather than inert raw html', () => {
const markdown = markdownAfterTextReplace(
[
'<details class="orca-details" open>',
'<summary>Outer</summary>',
'',
'<details class="orca-details" open>',
'<summary>Inner</summary>',
'',
'Body',
'',
'</details>',
'',
'</details>',
''
].join('\n'),
'Body',
'Edited body'
)
expect(markdown).toContain('Edited body')
})
it('preserves a nested toggle that is not itself editable as passthrough html', () => {
const input =
'<details><summary>Outer</summary><details><summary>Inner</summary><p>Body</p></details></details>\n'
'<details><summary>Outer</summary><details id="x"><summary>Inner</summary><p>Body</p></details></details>\n'
expect(roundTripMarkdown(input)).toBe(input.trimEnd())
})
@@ -0,0 +1,139 @@
import { randomUUID } from 'node:crypto'
import { mkdir, rm, writeFile } from 'node:fs/promises'
import path from 'node:path'
import type { Locator, Page } from '@stablyai/playwright-test'
import { expect } from '@stablyai/playwright-test'
const MARKDOWN_HYDRATION_TIMEOUT_MS = 25_000
export type ActiveWorktreeContext = {
worktreeId: string
rootPath: string
}
export type ActiveEditorFile = {
filePath: string
}
export async function getActiveWorktreeContext(page: Page): Promise<ActiveWorktreeContext> {
return page.evaluate(() => {
const store = window.__store
if (!store) {
throw new Error('window.__store is not available')
}
const state = store.getState()
const worktreeId = state.activeWorktreeId
if (!worktreeId) {
throw new Error('No active worktree is selected')
}
const worktree = Object.values(state.worktreesByRepo)
.flat()
.find((entry) => entry.id === worktreeId)
if (!worktree) {
throw new Error(`Active worktree was not found in store: ${worktreeId}`)
}
return { worktreeId, rootPath: worktree.path }
})
}
export async function createMarkdownFixture(
context: ActiveWorktreeContext,
directoryName: string,
slug: string,
workerIndex: number,
initialMarkdown: string
): Promise<string> {
const directory = path.join(context.rootPath, directoryName)
await mkdir(directory, { recursive: true })
const filePath = path.join(directory, `${slug}-${workerIndex}-${Date.now()}-${randomUUID()}.md`)
await writeFile(filePath, initialMarkdown, 'utf8')
return filePath
}
export async function cleanupMarkdownFixture(filePath: string | null): Promise<void> {
if (!filePath) {
return
}
try {
await rm(filePath, { force: true })
} catch {
// Best-effort cleanup must not hide the editor regression assertion.
}
}
export async function openMarkdownFixture(
page: Page,
context: ActiveWorktreeContext,
filePath: string
): Promise<ActiveEditorFile> {
const relativePath = path.relative(context.rootPath, filePath)
await page.evaluate(
({ filePath, relativePath, worktreeId }) => {
const store = window.__store
if (!store) {
throw new Error('window.__store is not available')
}
store.getState().openFile({
filePath,
relativePath,
worktreeId,
language: 'markdown',
mode: 'edit'
})
},
{ filePath, relativePath, worktreeId: context.worktreeId }
)
let activeFile: ActiveEditorFile | null = null
await expect
.poll(
async () => {
activeFile = await page.evaluate(() => {
const store = window.__store
if (!store) {
return null
}
const state = store.getState()
const file = state.openFiles.find((entry) => entry.id === state.activeFileId)
return file ? { filePath: file.filePath } : null
})
return activeFile?.filePath ?? null
},
{
timeout: 5_000,
message: `Active editor file did not become ${filePath}`
}
)
.toBe(filePath)
if (!activeFile) {
throw new Error(`Active editor file was not available after opening ${filePath}`)
}
return activeFile
}
export async function waitForRichMarkdownEditor(page: Page): Promise<Locator> {
const editor = page.locator('.rich-markdown-editor')
await expect(editor).toBeVisible({ timeout: MARKDOWN_HYDRATION_TIMEOUT_MS })
return editor
}
export async function closeActiveEditorTab(page: Page, filePath: string): Promise<void> {
const fileName = path.basename(filePath)
const tab = page.locator('[data-tab-id]').filter({ hasText: fileName }).last()
await tab.getByRole('button', { name: 'Close tab' }).click()
await expect(page.locator('.editor-header-path').filter({ hasText: fileName })).toHaveCount(0, {
timeout: 10_000
})
}
+163
View File
@@ -0,0 +1,163 @@
import type { Page } from '@stablyai/playwright-test'
import { expect } from '@stablyai/playwright-test'
const TOGGLE_RENDER_TIMEOUT_MS = 10_000
export const NESTED_TOGGLE_FIXTURE_DIRECTORY = '.orca-e2e-markdown-nested-toggle'
export const NESTED_TOGGLE_BODY_TEXT = 'Get back to ppl'
export const NESTED_TOGGLE_MARKDOWN = [
'# 08/27/2026',
'',
'<details class="orca-details" data-orca-toggle="heading-3" open>',
'<summary>08/26/2026</summary>',
'',
'<details class="orca-details" open>',
'<summary>goals</summary>',
'',
'- Get back to ppl',
'',
'</details>',
'',
'- after inner toggle',
'',
'</details>',
''
].join('\n')
// An inner toggle carrying unsupported attributes cannot become an editable
// node, so the whole block must still fall back to byte-preserving passthrough.
export const UNSUPPORTED_NESTED_TOGGLE_MARKDOWN = [
'<details class="orca-details" open>',
'<summary>outer</summary>',
'',
'<details id="not-a-toggle">',
'<summary>inner</summary>',
'',
'Body',
'',
'</details>',
'',
'</details>',
''
].join('\n')
export type RenderedToggles = {
toggleCount: number
nestedToggleCount: number
summaries: string[]
variants: (string | null)[]
passthroughBlockCount: number
}
export async function readRenderedToggles(page: Page): Promise<RenderedToggles> {
return page.evaluate(() => {
const editor = document.querySelector('.rich-markdown-editor')
const toggles = Array.from(editor?.querySelectorAll('[data-type="details"]') ?? [])
return {
toggleCount: toggles.length,
nestedToggleCount:
editor?.querySelectorAll('[data-type="details"] [data-type="details"]').length ?? 0,
summaries: toggles.map(
(toggle) => toggle.querySelector('summary')?.textContent?.trim() ?? ''
),
variants: toggles.map((toggle) => toggle.getAttribute('data-orca-toggle')),
passthroughBlockCount: editor?.querySelectorAll('[data-raw-markdown-html-block]').length ?? 0
}
})
}
export async function expectEditableNestedToggles(page: Page): Promise<void> {
await expect
.poll(async () => readRenderedToggles(page), {
timeout: TOGGLE_RENDER_TIMEOUT_MS,
message: 'Nested toggles did not render as editable toggle nodes'
})
.toEqual({
toggleCount: 2,
nestedToggleCount: 1,
summaries: ['08/26/2026', 'goals'],
variants: ['heading-3', null],
passthroughBlockCount: 0
})
}
export async function expectPassthroughFallback(page: Page): Promise<void> {
const passthrough = page.locator('.rich-markdown-editor [data-raw-markdown-html-block]')
await expect(passthrough).toHaveCount(1, { timeout: TOGGLE_RENDER_TIMEOUT_MS })
await expect(passthrough).toContainText('<details id="not-a-toggle">')
}
async function selectionIsInsideNestedToggleBody(page: Page): Promise<boolean> {
return page.evaluate(() => {
const editor = document.querySelector('.rich-markdown-editor') as
| (Element & { editor?: { state?: { selection?: { from?: number; empty?: boolean } } } })
| null
const paragraph = editor?.querySelector(
'[data-type="details"] [data-type="details"] [data-type="detailsContent"] p'
) as (Element & { pmViewDesc?: { posAtStart?: number; posAtEnd?: number } }) | null
const selection = editor?.editor?.state?.selection
const from = selection?.from
const start = paragraph?.pmViewDesc?.posAtStart
const end = paragraph?.pmViewDesc?.posAtEnd
if (
!selection?.empty ||
typeof from !== 'number' ||
typeof start !== 'number' ||
typeof end !== 'number'
) {
return false
}
return from >= start && from <= end
})
}
export async function placeCaretInNestedToggleBody(page: Page): Promise<void> {
// Real input only: click the body text itself, then extend to end of line.
await page
.locator('.rich-markdown-editor')
.getByText(NESTED_TOGGLE_BODY_TEXT, { exact: true })
.click()
await page.keyboard.press('End')
await expect
.poll(async () => selectionIsInsideNestedToggleBody(page), {
timeout: 3_000,
message: 'Clicking the nested toggle body text did not place the caret inside it'
})
.toBe(true)
}
export async function expectSentinelInsideNestedToggle(
page: Page,
sentinel: string
): Promise<void> {
const nestedBody = page.locator(
'.rich-markdown-editor [data-type="details"] [data-type="details"] [data-type="detailsContent"]'
)
await expect(nestedBody).toContainText(sentinel, { timeout: TOGGLE_RENDER_TIMEOUT_MS })
}
export function expectFileKeepsNesting(fileContents: string, sentinel: string): void {
const outerOpen = fileContents.indexOf('<details class="orca-details" data-orca-toggle=')
const innerOpen = fileContents.indexOf('<details class="orca-details" open>')
const firstClose = fileContents.indexOf('</details>')
const sentinelAt = fileContents.indexOf(sentinel)
expect({
openTagCount: fileContents.split('<details').length - 1,
closeTagCount: fileContents.split('</details>').length - 1,
innerIsNestedInOuter: outerOpen !== -1 && innerOpen > outerOpen,
// The edited line has to stay inside the inner toggle, before its close.
sentinelInsideInner: sentinelAt !== -1 && sentinelAt > innerOpen && sentinelAt < firstClose
}).toEqual({
openTagCount: 2,
closeTagCount: 2,
innerIsNestedInOuter: true,
sentinelInsideInner: true
})
}
+14 -117
View File
@@ -1,16 +1,20 @@
import { randomUUID } from 'node:crypto'
import { mkdir, rm, writeFile } from 'node:fs/promises'
import path from 'node:path'
import type { Locator, Page } from '@stablyai/playwright-test'
import type { Page } from '@stablyai/playwright-test'
import { expect } from '@stablyai/playwright-test'
import {
createMarkdownFixture as createMarkdownFixtureIn,
type ActiveWorktreeContext
} from './markdown-editor-fixture'
const MARKDOWN_HYDRATION_TIMEOUT_MS = 25_000
const DRAFT_SERIALIZATION_TIMEOUT_MS = 10_000
const FIXTURE_DIRECTORY = '.orca-e2e-markdown-ordered-list'
export type ActiveWorktreeContext = {
worktreeId: string
rootPath: string
}
export {
cleanupMarkdownFixture,
getActiveWorktreeContext,
openMarkdownFixture,
waitForRichMarkdownEditor,
type ActiveWorktreeContext
} from './markdown-editor-fixture'
export type MatrixRow = {
name: string
@@ -20,120 +24,13 @@ export type MatrixRow = {
run: (page: Page, sentinel: string) => Promise<void>
}
type ActiveEditorFile = {
filePath: string
}
export async function getActiveWorktreeContext(page: Page): Promise<ActiveWorktreeContext> {
return page.evaluate(() => {
const store = window.__store
if (!store) {
throw new Error('window.__store is not available')
}
const state = store.getState()
const worktreeId = state.activeWorktreeId
if (!worktreeId) {
throw new Error('No active worktree is selected')
}
const worktree = Object.values(state.worktreesByRepo)
.flat()
.find((entry) => entry.id === worktreeId)
if (!worktree) {
throw new Error(`Active worktree was not found in store: ${worktreeId}`)
}
return { worktreeId, rootPath: worktree.path }
})
}
export async function createMarkdownFixture(
context: ActiveWorktreeContext,
slug: string,
workerIndex: number,
initialMarkdown: string
): Promise<string> {
const directory = path.join(context.rootPath, '.orca-e2e-markdown-ordered-list')
await mkdir(directory, { recursive: true })
const filePath = path.join(directory, `${slug}-${workerIndex}-${Date.now()}-${randomUUID()}.md`)
await writeFile(filePath, initialMarkdown, 'utf8')
return filePath
}
export async function cleanupMarkdownFixture(filePath: string | null): Promise<void> {
if (!filePath) {
return
}
try {
await rm(filePath, { force: true })
} catch {
// Best-effort cleanup must not hide the editor regression assertion.
}
}
export async function openMarkdownFixture(
page: Page,
context: ActiveWorktreeContext,
filePath: string
): Promise<ActiveEditorFile> {
const relativePath = path.relative(context.rootPath, filePath)
await page.evaluate(
({ filePath, relativePath, worktreeId }) => {
const store = window.__store
if (!store) {
throw new Error('window.__store is not available')
}
store.getState().openFile({
filePath,
relativePath,
worktreeId,
language: 'markdown',
mode: 'edit'
})
},
{ filePath, relativePath, worktreeId: context.worktreeId }
)
let activeFile: ActiveEditorFile | null = null
await expect
.poll(
async () => {
activeFile = await page.evaluate(() => {
const store = window.__store
if (!store) {
return null
}
const state = store.getState()
const file = state.openFiles.find((entry) => entry.id === state.activeFileId)
return file ? { filePath: file.filePath } : null
})
return activeFile?.filePath ?? null
},
{
timeout: 5_000,
message: `Active editor file did not become ${filePath}`
}
)
.toBe(filePath)
if (!activeFile) {
throw new Error(`Active editor file was not available after opening ${filePath}`)
}
return activeFile
}
export async function waitForRichMarkdownEditor(page: Page): Promise<Locator> {
const editor = page.locator('.rich-markdown-editor')
await expect(editor).toBeVisible({ timeout: MARKDOWN_HYDRATION_TIMEOUT_MS })
return editor
return createMarkdownFixtureIn(context, FIXTURE_DIRECTORY, slug, workerIndex, initialMarkdown)
}
export async function expectSentinelParagraphOutsideOrderedList(
+113
View File
@@ -0,0 +1,113 @@
import { readFileSync } from 'node:fs'
import { test, expect } from './helpers/orca-app'
import {
cleanupMarkdownFixture,
closeActiveEditorTab,
createMarkdownFixture,
getActiveWorktreeContext,
openMarkdownFixture,
waitForRichMarkdownEditor
} from './helpers/markdown-editor-fixture'
import {
expectEditableNestedToggles,
expectFileKeepsNesting,
expectPassthroughFallback,
expectSentinelInsideNestedToggle,
NESTED_TOGGLE_FIXTURE_DIRECTORY,
NESTED_TOGGLE_MARKDOWN,
placeCaretInNestedToggleBody,
UNSUPPORTED_NESTED_TOGGLE_MARKDOWN
} from './helpers/markdown-nested-toggle'
import { waitForActiveWorktree, waitForSessionReady } from './helpers/store'
test.describe('Markdown nested toggle regression', () => {
test.beforeEach(async ({ orcaPage }) => {
await waitForSessionReady(orcaPage)
await waitForActiveWorktree(orcaPage)
})
test('a nested toggle on disk reopens as editable toggles', async ({ orcaPage }, testInfo) => {
const context = await getActiveWorktreeContext(orcaPage)
let filePath: string | null = null
try {
filePath = await createMarkdownFixture(
context,
NESTED_TOGGLE_FIXTURE_DIRECTORY,
'nested-toggle-reopen',
testInfo.workerIndex,
NESTED_TOGGLE_MARKDOWN
)
await openMarkdownFixture(orcaPage, context, filePath)
await waitForRichMarkdownEditor(orcaPage)
await expectEditableNestedToggles(orcaPage)
} finally {
await cleanupMarkdownFixture(filePath)
}
})
test('editing a nested toggle survives save and reopen', async ({ orcaPage }, testInfo) => {
const context = await getActiveWorktreeContext(orcaPage)
const sentinel = `editedInsideNestedToggle${Date.now()}`
let filePath: string | null = null
try {
filePath = await createMarkdownFixture(
context,
NESTED_TOGGLE_FIXTURE_DIRECTORY,
'nested-toggle-edit',
testInfo.workerIndex,
NESTED_TOGGLE_MARKDOWN
)
await openMarkdownFixture(orcaPage, context, filePath)
await waitForRichMarkdownEditor(orcaPage)
await expectEditableNestedToggles(orcaPage)
await placeCaretInNestedToggleBody(orcaPage)
await orcaPage.keyboard.type(` ${sentinel}`)
await expectSentinelInsideNestedToggle(orcaPage, sentinel)
// Save through the real shortcut and assert the bytes that landed on disk.
await orcaPage.keyboard.press('ControlOrMeta+S')
const savedPath = filePath
await expect
.poll(() => readFileSync(savedPath, 'utf8'), { timeout: 10_000 })
.toContain(sentinel)
expectFileKeepsNesting(readFileSync(savedPath, 'utf8'), sentinel)
// The reported bug only appeared on reopen, so close the tab and parse
// the saved file again from scratch.
await closeActiveEditorTab(orcaPage, savedPath)
await openMarkdownFixture(orcaPage, context, savedPath)
await waitForRichMarkdownEditor(orcaPage)
await expectEditableNestedToggles(orcaPage)
await expectSentinelInsideNestedToggle(orcaPage, sentinel)
} finally {
await cleanupMarkdownFixture(filePath)
}
})
test('a nested toggle that cannot be represented stays raw passthrough', async ({
orcaPage
}, testInfo) => {
const context = await getActiveWorktreeContext(orcaPage)
let filePath: string | null = null
try {
filePath = await createMarkdownFixture(
context,
NESTED_TOGGLE_FIXTURE_DIRECTORY,
'nested-toggle-unsupported',
testInfo.workerIndex,
UNSUPPORTED_NESTED_TOGGLE_MARKDOWN
)
await openMarkdownFixture(orcaPage, context, filePath)
await waitForRichMarkdownEditor(orcaPage)
await expectPassthroughFallback(orcaPage)
} finally {
await cleanupMarkdownFixture(filePath)
}
})
})