mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-24 05:40:52 +02:00
fix: snapshot -i auto-detects dropdown/popover interactive elements (#845)
* fix: snapshot -i auto-detects dropdown/popover interactive elements - Auto-enable cursor-interactive scan (-C) when -i flag is used - Add floating container detection (portals, popovers, dropdowns) - Detects position:fixed/absolute with high z-index - Recognizes data-floating-ui-portal, data-radix-* attributes - Recognizes role=listbox, role=menu containers - Elements inside floating containers bypass the hasRole skip - Catches dropdown items missed by the accessibility tree - Role=option/menuitem elements in floating containers captured even without cursor:pointer/onclick - Tag floating container items with 'popover-child' reason - Include role name in @c ref reasons when present - Add dropdown.html test fixture - Add dropdown/popover detection test suite (6 tests) - Add test: -i alone includes cursor-interactive elements Fixes: Bookface autocomplete, Radix UI combobox, React portals, and similar dynamic dropdown patterns where ariaSnapshot() misses the floating content. * chore: bump version and changelog (v0.15.12.0) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs: update snapshot -i/-C flag descriptions to mention auto-enable behavior * test: strengthen clickability test guard assertions The @c ref clickability test previously used if-guards that would silently pass when no Alice line was found in the snapshot output. Both Claude and Codex adversarial review flagged this as a test that could regress without CI noticing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: regenerate top-level SKILL.md with updated flag descriptions Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: root <root@localhost> Co-authored-by: gstack <ship@gstack.dev> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
root
gstack
parent
237ae2abbe
commit
a94a64f821
@@ -386,6 +386,75 @@ describe('Cursor-interactive', () => {
|
||||
// And cursor-interactive section
|
||||
expect(result).toContain('cursor-interactive');
|
||||
});
|
||||
|
||||
test('snapshot -i alone also includes cursor-interactive elements', async () => {
|
||||
await handleWriteCommand('goto', [baseUrl + '/cursor-interactive.html'], bm);
|
||||
const result = await handleMetaCommand('snapshot', ['-i'], bm, shutdown);
|
||||
// -i now auto-enables -C
|
||||
expect(result).toContain('[button]');
|
||||
expect(result).toContain('[link]');
|
||||
expect(result).toContain('cursor-interactive');
|
||||
expect(result).toContain('@c');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Dropdown/Popover Detection ─────────────────────────────────
|
||||
|
||||
describe('Dropdown/popover detection', () => {
|
||||
test('snapshot -i auto-enables cursor scan and finds dropdown items', async () => {
|
||||
await handleWriteCommand('goto', [baseUrl + '/dropdown.html'], bm);
|
||||
const result = await handleMetaCommand('snapshot', ['-i'], bm, shutdown);
|
||||
// Should find standard interactive elements
|
||||
expect(result).toContain('[button]');
|
||||
expect(result).toContain('[link]');
|
||||
expect(result).toContain('[textbox]');
|
||||
// Should also find cursor-interactive dropdown items
|
||||
expect(result).toContain('cursor-interactive');
|
||||
expect(result).toContain('@c');
|
||||
expect(result).toContain('Alice Johnson');
|
||||
expect(result).toContain('Bob Smith');
|
||||
});
|
||||
|
||||
test('dropdown items in floating container are tagged as popover-child', async () => {
|
||||
await handleWriteCommand('goto', [baseUrl + '/dropdown.html'], bm);
|
||||
const result = await handleMetaCommand('snapshot', ['-i'], bm, shutdown);
|
||||
expect(result).toContain('popover-child');
|
||||
});
|
||||
|
||||
test('dropdown items with role="option" in portal are captured', async () => {
|
||||
await handleWriteCommand('goto', [baseUrl + '/dropdown.html'], bm);
|
||||
const result = await handleMetaCommand('snapshot', ['-i'], bm, shutdown);
|
||||
// Dave Wilson has role="option" — should be captured even though it has a role
|
||||
expect(result).toContain('Dave Wilson');
|
||||
});
|
||||
|
||||
test('static text in dropdown without interactivity is NOT captured', async () => {
|
||||
await handleWriteCommand('goto', [baseUrl + '/dropdown.html'], bm);
|
||||
const result = await handleMetaCommand('snapshot', ['-i'], bm, shutdown);
|
||||
// "No results? Try a different search." has no cursor:pointer, no onclick, no tabindex
|
||||
expect(result).not.toContain('No results');
|
||||
});
|
||||
|
||||
test('@c ref from dropdown is clickable', async () => {
|
||||
await handleWriteCommand('goto', [baseUrl + '/dropdown.html'], bm);
|
||||
const snap = await handleMetaCommand('snapshot', ['-i'], bm, shutdown);
|
||||
// Find a @c ref for Alice
|
||||
const aliceLine = snap.split('\n').find(l => l.includes('@c') && l.includes('Alice'));
|
||||
expect(aliceLine).toBeTruthy();
|
||||
const refMatch = aliceLine!.match(/@(c\d+)/);
|
||||
expect(refMatch).toBeTruthy();
|
||||
const result = await handleWriteCommand('click', [`@${refMatch![1]}`], bm);
|
||||
expect(result).toContain('Clicked');
|
||||
});
|
||||
|
||||
test('snapshot -C still works standalone without -i', async () => {
|
||||
await handleWriteCommand('goto', [baseUrl + '/dropdown.html'], bm);
|
||||
const result = await handleMetaCommand('snapshot', ['-C'], bm, shutdown);
|
||||
expect(result).toContain('cursor-interactive');
|
||||
expect(result).toContain('Alice Johnson');
|
||||
// Without -i, should include non-interactive ARIA elements too
|
||||
expect(result).toContain('[heading]');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Snapshot Error Paths ───────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user