Skip to content

Commit 1ee44eb

Browse files
authored
Merge pull request #3561 from superdoc-dev/caio/sdt-navigation-parity
test(behavior): SDT right-arrow + shift-right navigation parity (SD-3237/SD-3218)
2 parents 052410b + 6168021 commit 1ee44eb

2 files changed

Lines changed: 99 additions & 0 deletions

File tree

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import path from 'path';
2+
import { fileURLToPath } from 'url';
3+
import { test, expect, type SuperDocFixture } from '../../fixtures/superdoc.js';
4+
import { getInlineSdtRange, getInlineSdtSnapshot, caretLocation } from '../../helpers/sdt.js';
5+
6+
/**
7+
* Right-arrow at the inline SDT trailing edge. Word parity contract
8+
* sdt/inline-right-arrow-trailing, Word 16.0 (lock-invariant)
9+
* One press moves the caret from inside the control to just after it
10+
* (inside-cc -> after-cc); navigation does not depend on lock mode.
11+
*
12+
* First consumer of the parity axis helper caretLocation().
13+
*/
14+
15+
test.use({ config: { toolbar: 'full', showSelection: true } });
16+
17+
const DIR = path.dirname(fileURLToPath(import.meta.url));
18+
const FIXTURE = {
19+
unlocked: path.resolve(DIR, 'fixtures/sd3237-inline-unlocked.docx'),
20+
contentLocked: path.resolve(DIR, 'fixtures/sd3237-inline-contentlocked.docx'),
21+
} as const;
22+
23+
test.describe('SDT Right-arrow at the trailing edge - Word parity', () => {
24+
for (const mode of ['unlocked', 'contentLocked'] as const) {
25+
test(`${mode}: one Right-arrow moves the caret from inside the SDT to after it`, async ({ superdoc }) => {
26+
await superdoc.loadDocument(FIXTURE[mode]);
27+
await superdoc.waitForStable();
28+
const sdt = await getInlineSdtRange(superdoc.page);
29+
expect(sdt).not.toBeNull();
30+
31+
await superdoc.setTextSelection(sdt!.end); // last position inside the content
32+
await superdoc.page.evaluate(() => (window as any).editor.view.focus());
33+
await superdoc.waitForStable();
34+
expect(caretLocation(await getInlineSdtSnapshot(superdoc.page, sdt!.id), sdt!)).toBe('inside-cc');
35+
36+
await superdoc.press('ArrowRight');
37+
await superdoc.waitForStable();
38+
39+
const after = await getInlineSdtSnapshot(superdoc.page, sdt!.id);
40+
expect(caretLocation(after, sdt!)).toBe('after-cc'); // exited in one press
41+
expect(after.sdtExists).toBe(true); // navigation does not mutate
42+
});
43+
}
44+
});
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import path from 'path';
2+
import { fileURLToPath } from 'url';
3+
import { test, expect, type SuperDocFixture } from '../../fixtures/superdoc.js';
4+
import { getInlineSdtRange, getInlineSdtSnapshot, selectionScope, type SelectionScope } from '../../helpers/sdt.js';
5+
6+
/**
7+
* Shift+Right extending a selection across the inline SDT leading boundary.
8+
* Word parity contract sdt/inline-shift-right-boundary, Word 16.0 (lock-invariant).
9+
*
10+
* The parity fact: selection crosses into the control character-by-character
11+
* (the scope progresses outside-cc -> cc-and-beyond), it never snaps to the
12+
* whole control as a unit (whole-content-control). Lock-invariant.
13+
*
14+
* Consumes the parity axis helper selectionScope().
15+
*/
16+
17+
test.use({ config: { toolbar: 'full', showSelection: true } });
18+
19+
const DIR = path.dirname(fileURLToPath(import.meta.url));
20+
const FIXTURE = {
21+
unlocked: path.resolve(DIR, 'fixtures/sd3237-inline-unlocked.docx'),
22+
contentLocked: path.resolve(DIR, 'fixtures/sd3237-inline-contentlocked.docx'),
23+
} as const;
24+
25+
test.describe('SDT Shift+Right across the leading boundary - Word parity', () => {
26+
for (const mode of ['unlocked', 'contentLocked'] as const) {
27+
test(`${mode}: Shift+Right crosses into the SDT character-by-character, not atomically`, async ({ superdoc }) => {
28+
await superdoc.loadDocument(FIXTURE[mode]);
29+
await superdoc.waitForStable();
30+
const sdt = await getInlineSdtRange(superdoc.page);
31+
expect(sdt).not.toBeNull();
32+
33+
// Anchor in the text just before the SDT (mirrors the contract's
34+
// outside-leading placement); a collapsed caret exactly on the node
35+
// boundary steps into the node instead of extending.
36+
await superdoc.setTextSelection(sdt!.pos - 2);
37+
await superdoc.page.evaluate(() => (window as any).editor.view.focus());
38+
await superdoc.waitForStable();
39+
40+
const scopes: SelectionScope[] = [];
41+
for (let i = 0; i < 6; i++) {
42+
await superdoc.press('Shift+ArrowRight');
43+
await superdoc.waitForStable();
44+
scopes.push(selectionScope(await getInlineSdtSnapshot(superdoc.page, sdt!.id), sdt!));
45+
}
46+
47+
// Crossed into the content (overlaps + extends past the leading edge)...
48+
expect(scopes).toContain('cc-and-beyond');
49+
// ...but never selected the whole control as a unit - character-granular.
50+
expect(scopes).not.toContain('whole-content-control');
51+
// non-destructive
52+
expect((await getInlineSdtSnapshot(superdoc.page, sdt!.id)).sdtExists).toBe(true);
53+
});
54+
}
55+
});

0 commit comments

Comments
 (0)