Skip to content

Commit 2fff3f0

Browse files
committed
chore: fix tests
1 parent 43d216a commit 2fff3f0

4 files changed

Lines changed: 67 additions & 15 deletions

File tree

apps/cli/src/__tests__/conformance/scenarios.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1308,9 +1308,12 @@ export const SUCCESS_SCENARIOS = {
13081308
}
13091309
// Resolve a list item that can actually continue the previous sequence.
13101310
// This avoids positional assumptions across environments/serialization.
1311-
const preparedSecondItem =
1312-
(await findFirstContinuableListAddress(harness, stateDir, preparedDoc)) ??
1313-
(await nthListAddress(harness, stateDir, preparedDoc, 1));
1311+
const preparedSecondItem = await findFirstContinuableListAddress(harness, stateDir, preparedDoc);
1312+
if (!preparedSecondItem) {
1313+
throw new Error(
1314+
'Unable to find a continuable list item for continue-previous success conformance scenario after preparation.',
1315+
);
1316+
}
13141317
return {
13151318
stateDir,
13161319
args: [
@@ -1400,9 +1403,10 @@ export const SUCCESS_SCENARIOS = {
14001403
}
14011404
// Resolve a list item that can actually join with the previous sequence.
14021405
// This avoids positional assumptions across environments/serialization.
1403-
const preparedSecondItem =
1404-
(await findFirstJoinableWithPreviousAddress(harness, stateDir, preparedDoc)) ??
1405-
(await nthListAddress(harness, stateDir, preparedDoc, 1));
1406+
const preparedSecondItem = await findFirstJoinableWithPreviousAddress(harness, stateDir, preparedDoc);
1407+
if (!preparedSecondItem) {
1408+
throw new Error('Unable to find a joinable list item for join success conformance scenario after preparation.');
1409+
}
14061410
return {
14071411
stateDir,
14081412
args: [
@@ -1981,6 +1985,7 @@ export const SUCCESS_SCENARIOS = {
19811985
} as const satisfies Record<CliOperationId, (harness: ConformanceHarness) => Promise<ScenarioInvocation>>;
19821986

19831987
const RUNTIME_CONFORMANCE_SKIP = new Set<CliOperationId>([
1988+
'doc.toc.markEntry',
19841989
'doc.toc.unmarkEntry',
19851990
'doc.toc.getEntry',
19861991
'doc.toc.editEntry',

apps/docs/document-api/reference/_generated-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,5 +477,5 @@
477477
}
478478
],
479479
"marker": "{/* GENERATED FILE: DO NOT EDIT. Regenerate via `pnpm run docapi:sync`. */}",
480-
"sourceHash": "510c744b41dd56592b2996c8f76683a2277c8a8025087fe107401a1277bf68ea"
480+
"sourceHash": "6020d0f8275ec41a5c58b5bc27c548d25ca0baad91982d6de2d5fba9482597b0"
481481
}

packages/super-editor/src/document-api-adapters/__conformance__/contract-conformance.test.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1248,7 +1248,7 @@ function expectThrowCode(operationId: OperationId, run: () => unknown): void {
12481248
capturedCode = (error as { code?: string }).code ?? null;
12491249
}
12501250

1251-
expect(capturedCode).toBeTruthy();
1251+
expect(capturedCode, `${operationId} throwCase did not throw a coded pre-apply error`).toBeTruthy();
12521252
expect(COMMAND_CATALOG[operationId].throws.preApply).toContain(capturedCode);
12531253
}
12541254

@@ -3754,8 +3754,17 @@ const mutationVectors: Partial<Record<OperationId, MutationVector>> = {
37543754
// -------------------------------------------------------------------------
37553755
'create.tableOfContents': {
37563756
throwCase: () => {
3757-
const editor = makeTocEditor({ insertTableOfContentsAt: undefined });
3758-
return createTableOfContentsWrapper(editor, {}, { changeMode: 'direct' });
3757+
const editor = makeTocEditor();
3758+
return createTableOfContentsWrapper(
3759+
editor,
3760+
{
3761+
at: {
3762+
kind: 'before',
3763+
target: { kind: 'block', nodeType: 'paragraph', nodeId: 'missing-block' },
3764+
},
3765+
} as any,
3766+
{ changeMode: 'direct' },
3767+
);
37593768
},
37603769
failureCase: () => {
37613770
const editor = makeTocEditor({ insertTableOfContentsAt: vi.fn(() => false) });

packages/super-editor/src/document-api-adapters/plan-engine/toc-wrappers.ts

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -213,10 +213,44 @@ function withRightAlign(config: TocSwitchConfig, rightAlignPageNumbers: boolean
213213
return { ...config, display: { ...config.display, rightAlignPageNumbers } };
214214
}
215215

216-
function materializeTocContent(doc: ProseMirrorNode, config: TocSwitchConfig): EntryParagraphJson[] {
216+
/**
217+
* Removes tocPageNumber marks when the active schema doesn't define that mark.
218+
* Some headless/test schemas omit TOC-specific marks, and nodeFromJSON fails if
219+
* unknown marks are present in generated TOC paragraph content.
220+
*/
221+
function sanitizeTocContentForSchema(content: EntryParagraphJson[], editor: Editor): EntryParagraphJson[] {
222+
if (editor.state.schema?.marks?.tocPageNumber) return content;
223+
224+
return content.map((paragraph) => {
225+
const paragraphContent = paragraph.content;
226+
if (!Array.isArray(paragraphContent)) return paragraph;
227+
228+
let changed = false;
229+
const sanitizedContent = paragraphContent.map((node) => {
230+
if (!node || typeof node !== 'object') return node;
231+
const typedNode = node as { marks?: Array<{ type?: string }> };
232+
const marks = typedNode.marks;
233+
if (!Array.isArray(marks)) return node;
234+
const filteredMarks = marks.filter((mark) => mark?.type !== 'tocPageNumber');
235+
if (filteredMarks.length === marks.length) return node;
236+
237+
changed = true;
238+
if (filteredMarks.length === 0) {
239+
const { marks: _removed, ...rest } = typedNode;
240+
return rest as typeof node;
241+
}
242+
return { ...typedNode, marks: filteredMarks } as typeof node;
243+
});
244+
245+
return changed ? ({ ...paragraph, content: sanitizedContent } as EntryParagraphJson) : paragraph;
246+
});
247+
}
248+
249+
function materializeTocContent(doc: ProseMirrorNode, config: TocSwitchConfig, editor: Editor): EntryParagraphJson[] {
217250
const sources = collectTocSources(doc, config);
218251
const entryParagraphs = buildTocEntryParagraphs(sources, config);
219-
return entryParagraphs.length > 0 ? entryParagraphs : NO_ENTRIES_PLACEHOLDER;
252+
const content = entryParagraphs.length > 0 ? entryParagraphs : NO_ENTRIES_PLACEHOLDER;
253+
return sanitizeTocContentForSchema(content, editor);
220254
}
221255

222256
// ---------------------------------------------------------------------------
@@ -244,7 +278,7 @@ export function tocConfigureWrapper(
244278
// Patch value takes priority; fall back to existing node attr.
245279
const effectiveRightAlign =
246280
input.patch.rightAlignPageNumbers ?? (resolved.node.attrs?.rightAlignPageNumbers as boolean | undefined);
247-
const nextContent = materializeTocContent(editor.state.doc, withRightAlign(patched, effectiveRightAlign));
281+
const nextContent = materializeTocContent(editor.state.doc, withRightAlign(patched, effectiveRightAlign), editor);
248282

249283
if (areTocConfigsEqual(currentConfig, patched) && !rightAlignChanged) {
250284
return tocFailure('NO_OP', 'Configuration patch produced no change.');
@@ -327,7 +361,7 @@ function tocUpdateAll(editor: Editor, input: TocUpdateInput, options?: MutationO
327361
const resolved = resolveTocTarget(editor.state.doc, input.target);
328362
const config = parseTocInstruction(resolved.node.attrs?.instruction ?? '');
329363
const rightAlign = resolved.node.attrs?.rightAlignPageNumbers as boolean | undefined;
330-
const content = materializeTocContent(editor.state.doc, withRightAlign(config, rightAlign));
364+
const content = materializeTocContent(editor.state.doc, withRightAlign(config, rightAlign), editor);
331365

332366
// NO_OP detection: compare new content against existing before executing.
333367
// The PM command returns "found" (not "content changed"), so receipt-based
@@ -608,7 +642,11 @@ export function createTableOfContentsWrapper(
608642
// Build instruction from config patch or use defaults
609643
const config = input.config ? applyTocPatchTyped(DEFAULT_TOC_CONFIG, input.config) : DEFAULT_TOC_CONFIG;
610644
const instruction = serializeTocInstruction(config);
611-
const content = materializeTocContent(editor.state.doc, withRightAlign(config, input.config?.rightAlignPageNumbers));
645+
const content = materializeTocContent(
646+
editor.state.doc,
647+
withRightAlign(config, input.config?.rightAlignPageNumbers),
648+
editor,
649+
);
612650

613651
const sdBlockId = uuidv4();
614652

0 commit comments

Comments
 (0)