test(review): add the 4 new test-coverage points from PR #185 re-review
The re-review's blocking/structural points (lease leak, dup-id guard test, body-before-title test, CHANGELOG, pg18, shared jsonb decoder) were already addressed in commit 24264ef; this adds the 4 genuinely-new coverage requests: - pt 6: `scrollToReference(id, index?)` exercised against a live editor DOM — selects the index-th `sup[data-footnote-ref][data-id]` occurrence, falls back to the first for out-of-range, returns false for an empty id (scrollIntoView stubbed). (#168) - pt 7: export `backlinkLabel` and pin the base-26 carry boundary (25->z, 26->aa, 27->ab, 51->az, 52->ba). (#168) - pt 8: integration fail-open — a PRESENT-but-corrupt tool_allowlist (jsonb string scalar holding non-array JSON) reads back as null ("no restriction"), covering normalizeRow's degrade branch. (#159 #172/#173) - pt 9: getFootnoteRefCount cache invalidation — adding a `[^a]` reference bumps the cached count 2 -> 3. (#168) Verified: editor-ext footnote 23; client structure 7 + tsc; server int 8. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -162,6 +162,111 @@ describe('getFootnoteRefCount (cached, live editor)', () => {
|
||||
expect(getFootnoteRefCount(editor.state, 'nope')).toBe(0);
|
||||
editor.destroy();
|
||||
});
|
||||
|
||||
// #185 re-review pt 9: the cached count must update on a doc change (mirror of
|
||||
// the number-cache invalidation test) — add another `[^a]` reference and the
|
||||
// count goes 2 -> 3.
|
||||
it('recomputes the cached ref count when a reference is added', () => {
|
||||
const editor = makeEditor({
|
||||
type: 'doc',
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{ type: FOOTNOTE_REFERENCE_NAME, attrs: { id: 'a' } },
|
||||
{ type: 'text', text: ' and ' },
|
||||
{ type: FOOTNOTE_REFERENCE_NAME, attrs: { id: 'a' } },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: FOOTNOTES_LIST_NAME,
|
||||
content: [
|
||||
{
|
||||
type: FOOTNOTE_DEFINITION_NAME,
|
||||
attrs: { id: 'a' },
|
||||
content: [{ type: 'paragraph' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(getFootnoteRefCount(editor.state, 'a')).toBe(2);
|
||||
|
||||
// Insert a THIRD reference to `a` at the start of the first paragraph.
|
||||
const refType = editor.schema.nodes[FOOTNOTE_REFERENCE_NAME];
|
||||
editor.view.dispatch(
|
||||
editor.state.tr.insert(1, refType.create({ id: 'a' })),
|
||||
);
|
||||
|
||||
expect(getFootnoteRefCount(editor.state, 'a')).toBe(3);
|
||||
editor.destroy();
|
||||
});
|
||||
});
|
||||
|
||||
// #185 re-review pt 6: scrollToReference picks the index-th occurrence among the
|
||||
// reused references, falls back to the first for an out-of-range index, and is a
|
||||
// no-op (false) for an empty id. Runs the REAL command against the editor's DOM
|
||||
// (scrollIntoView is stubbed — jsdom does not implement it).
|
||||
describe('scrollToReference command (occurrence selection + fallback)', () => {
|
||||
it('selects the index-th occurrence, falls back to the first, false for empty id', () => {
|
||||
const scrolled: Element[] = [];
|
||||
const original = (Element.prototype as any).scrollIntoView;
|
||||
(Element.prototype as any).scrollIntoView = function () {
|
||||
scrolled.push(this as Element);
|
||||
};
|
||||
try {
|
||||
const editor = makeEditor({
|
||||
type: 'doc',
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{ type: FOOTNOTE_REFERENCE_NAME, attrs: { id: 'a' } },
|
||||
{ type: 'text', text: ' x ' },
|
||||
{ type: FOOTNOTE_REFERENCE_NAME, attrs: { id: 'a' } },
|
||||
{ type: 'text', text: ' y ' },
|
||||
{ type: FOOTNOTE_REFERENCE_NAME, attrs: { id: 'a' } },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: FOOTNOTES_LIST_NAME,
|
||||
content: [
|
||||
{
|
||||
type: FOOTNOTE_DEFINITION_NAME,
|
||||
attrs: { id: 'a' },
|
||||
content: [{ type: 'paragraph' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
const sups = editor.view.dom.querySelectorAll(
|
||||
'sup[data-footnote-ref][data-id="a"]',
|
||||
);
|
||||
expect(sups.length).toBe(3);
|
||||
|
||||
// index 1 -> the SECOND occurrence.
|
||||
expect(editor.commands.scrollToReference('a', 1)).toBe(true);
|
||||
expect(scrolled[scrolled.length - 1]).toBe(sups[1]);
|
||||
|
||||
// out-of-range index -> falls back to the FIRST occurrence.
|
||||
expect(editor.commands.scrollToReference('a', 99)).toBe(true);
|
||||
expect(scrolled[scrolled.length - 1]).toBe(sups[0]);
|
||||
|
||||
// default index (0) -> first.
|
||||
expect(editor.commands.scrollToReference('a')).toBe(true);
|
||||
expect(scrolled[scrolled.length - 1]).toBe(sups[0]);
|
||||
|
||||
// empty id -> false, no scroll.
|
||||
const before = scrolled.length;
|
||||
expect(editor.commands.scrollToReference('')).toBe(false);
|
||||
expect(scrolled.length).toBe(before);
|
||||
|
||||
editor.destroy();
|
||||
} finally {
|
||||
(Element.prototype as any).scrollIntoView = original;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('setFootnote command', () => {
|
||||
|
||||
Reference in New Issue
Block a user