Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions tests/behavior/tests/sdt/sdt-right-arrow-trailing.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import path from 'path';
import { fileURLToPath } from 'url';
import { test, expect, type SuperDocFixture } from '../../fixtures/superdoc.js';
import { getInlineSdtRange, getInlineSdtSnapshot, caretLocation } from '../../helpers/sdt.js';

/**
* Right-arrow at the inline SDT trailing edge. Word parity contract
* sdt/inline-right-arrow-trailing, Word 16.0 (lock-invariant)
* One press moves the caret from inside the control to just after it
* (inside-cc -> after-cc); navigation does not depend on lock mode.
*
* First consumer of the parity axis helper caretLocation().
*/

test.use({ config: { toolbar: 'full', showSelection: true } });

const DIR = path.dirname(fileURLToPath(import.meta.url));
const FIXTURE = {
unlocked: path.resolve(DIR, 'fixtures/sd3237-inline-unlocked.docx'),
contentLocked: path.resolve(DIR, 'fixtures/sd3237-inline-contentlocked.docx'),
} as const;

test.describe('SDT Right-arrow at the trailing edge - Word parity', () => {
for (const mode of ['unlocked', 'contentLocked'] as const) {
test(`${mode}: one Right-arrow moves the caret from inside the SDT to after it`, async ({ superdoc }) => {
await superdoc.loadDocument(FIXTURE[mode]);
await superdoc.waitForStable();
const sdt = await getInlineSdtRange(superdoc.page);
expect(sdt).not.toBeNull();

await superdoc.setTextSelection(sdt!.end); // last position inside the content
await superdoc.page.evaluate(() => (window as any).editor.view.focus());
await superdoc.waitForStable();
expect(caretLocation(await getInlineSdtSnapshot(superdoc.page, sdt!.id), sdt!)).toBe('inside-cc');

await superdoc.press('ArrowRight');
await superdoc.waitForStable();

const after = await getInlineSdtSnapshot(superdoc.page, sdt!.id);
expect(caretLocation(after, sdt!)).toBe('after-cc'); // exited in one press
expect(after.sdtExists).toBe(true); // navigation does not mutate
});
}
});
55 changes: 55 additions & 0 deletions tests/behavior/tests/sdt/sdt-shift-right-boundary.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import path from 'path';
import { fileURLToPath } from 'url';
import { test, expect, type SuperDocFixture } from '../../fixtures/superdoc.js';
import { getInlineSdtRange, getInlineSdtSnapshot, selectionScope, type SelectionScope } from '../../helpers/sdt.js';

/**
* Shift+Right extending a selection across the inline SDT leading boundary.
* Word parity contract sdt/inline-shift-right-boundary, Word 16.0 (lock-invariant).
*
* The parity fact: selection crosses into the control character-by-character
* (the scope progresses outside-cc -> cc-and-beyond), it never snaps to the
* whole control as a unit (whole-content-control). Lock-invariant.
*
* Consumes the parity axis helper selectionScope().
*/

test.use({ config: { toolbar: 'full', showSelection: true } });

const DIR = path.dirname(fileURLToPath(import.meta.url));
const FIXTURE = {
unlocked: path.resolve(DIR, 'fixtures/sd3237-inline-unlocked.docx'),
contentLocked: path.resolve(DIR, 'fixtures/sd3237-inline-contentlocked.docx'),
} as const;

test.describe('SDT Shift+Right across the leading boundary - Word parity', () => {
for (const mode of ['unlocked', 'contentLocked'] as const) {
test(`${mode}: Shift+Right crosses into the SDT character-by-character, not atomically`, async ({ superdoc }) => {
await superdoc.loadDocument(FIXTURE[mode]);
await superdoc.waitForStable();
const sdt = await getInlineSdtRange(superdoc.page);
expect(sdt).not.toBeNull();

// Anchor in the text just before the SDT (mirrors the contract's
// outside-leading placement); a collapsed caret exactly on the node
// boundary steps into the node instead of extending.
await superdoc.setTextSelection(sdt!.pos - 2);
await superdoc.page.evaluate(() => (window as any).editor.view.focus());
await superdoc.waitForStable();

const scopes: SelectionScope[] = [];
for (let i = 0; i < 6; i++) {
await superdoc.press('Shift+ArrowRight');
await superdoc.waitForStable();
scopes.push(selectionScope(await getInlineSdtSnapshot(superdoc.page, sdt!.id), sdt!));
}

// Crossed into the content (overlaps + extends past the leading edge)...
expect(scopes).toContain('cc-and-beyond');
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Verify the first SDT crossing is not a full-control jump

When a regression makes Shift+ArrowRight jump-select the entire content control while the anchor remains before the SDT (from < range.pos and to === range.nodeEnd), selectionScope() classifies that selection as cc-and-beyond, so this assertion still passes even though the contract says the boundary is crossed character-by-character rather than atomically. Please assert the first cc-and-beyond snapshot stops inside the control, or otherwise capture per-step positions, before accepting this state.

Useful? React with 👍 / 👎.

// ...but never selected the whole control as a unit - character-granular.
expect(scopes).not.toContain('whole-content-control');
// non-destructive
expect((await getInlineSdtSnapshot(superdoc.page, sdt!.id)).sdtExists).toBe(true);
});
}
});
Loading