mirror of
https://github.com/nexu-io/open-design.git
synced 2026-09-20 06:15:06 +08:00
973e868ced
* fix(web): make deck thumbnail navigation immediate * feat(deck): standardize viewer navigation protocol * test(daemon): update deck prompt matrix snapshot * test(daemon): assert versioned deck fallback state * test(deck): strengthen navigation regression contract * fix(deck): reject unsupported fallback protocol versions * fix(deck): reject unsupported bridge protocol versions * fix(deck): retry keyboard navigation through transition locks
108 lines
3.1 KiB
TypeScript
108 lines
3.1 KiB
TypeScript
// @vitest-environment node
|
|
|
|
import { JSDOM } from 'jsdom';
|
|
import { describe, expect, it, vi } from 'vitest';
|
|
|
|
import {
|
|
DECK_PROTOCOL_V1_INLINE_RUNTIME,
|
|
DECK_PROTOCOL_VERSION,
|
|
} from '@open-design/contracts/runtime/deck-protocol';
|
|
|
|
function setupCanonicalProtocolDeck() {
|
|
const dom = new JSDOM(`<!doctype html><html data-od-deck-protocol="1"><body>
|
|
<section class="slide active">One</section>
|
|
<section class="slide">Two</section>
|
|
<section class="slide">Three</section>
|
|
<section class="slide">Four</section>
|
|
</body></html>`, {
|
|
runScripts: 'outside-only',
|
|
pretendToBeVisual: true,
|
|
});
|
|
const win = dom.window;
|
|
const postMessage = vi.fn();
|
|
Object.defineProperty(win, 'parent', {
|
|
configurable: true,
|
|
value: { postMessage },
|
|
});
|
|
|
|
const script = `
|
|
var slides = Array.prototype.slice.call(document.querySelectorAll('.slide'));
|
|
var idx = 0;
|
|
function paint() {
|
|
slides.forEach(function (slide, index) {
|
|
slide.classList.toggle('active', index === idx);
|
|
});
|
|
postDeckState();
|
|
}
|
|
function go(index) {
|
|
idx = Math.max(0, Math.min(slides.length - 1, index));
|
|
paint();
|
|
}
|
|
${DECK_PROTOCOL_V1_INLINE_RUNTIME}
|
|
announceDeckProtocol();
|
|
paint();
|
|
`;
|
|
new win.Function(script).call(win);
|
|
|
|
return {
|
|
activeIndex: () => Array.from(win.document.querySelectorAll('.slide'))
|
|
.findIndex((slide) => slide.classList.contains('active')),
|
|
postMessage,
|
|
win,
|
|
};
|
|
}
|
|
|
|
describe('OD Deck Protocol v1', () => {
|
|
it('announces absolute navigation and state-event capabilities', () => {
|
|
const { postMessage, win } = setupCanonicalProtocolDeck();
|
|
|
|
expect(postMessage).toHaveBeenCalledWith({
|
|
type: 'od:deck-ready',
|
|
protocolVersion: DECK_PROTOCOL_VERSION,
|
|
capabilities: ['absolute-navigation', 'state-events'],
|
|
}, '*');
|
|
expect(postMessage).toHaveBeenCalledWith({
|
|
type: 'od:slide-state',
|
|
protocolVersion: DECK_PROTOCOL_VERSION,
|
|
active: 0,
|
|
count: 4,
|
|
}, '*');
|
|
win.close();
|
|
});
|
|
|
|
it('jumps directly to a thumbnail target without intermediate key events', () => {
|
|
const { activeIndex, postMessage, win } = setupCanonicalProtocolDeck();
|
|
const keydown = vi.fn();
|
|
win.addEventListener('keydown', keydown);
|
|
|
|
win.dispatchEvent(new win.MessageEvent('message', {
|
|
data: { type: 'od:slide', action: 'go', index: 3, protocolVersion: 1 },
|
|
}));
|
|
|
|
expect(activeIndex()).toBe(3);
|
|
expect(keydown).not.toHaveBeenCalled();
|
|
expect(postMessage).toHaveBeenLastCalledWith({
|
|
type: 'od:slide-state',
|
|
protocolVersion: DECK_PROTOCOL_VERSION,
|
|
active: 3,
|
|
count: 4,
|
|
}, '*');
|
|
win.close();
|
|
});
|
|
|
|
it('keeps unversioned legacy host commands compatible', () => {
|
|
const { activeIndex, win } = setupCanonicalProtocolDeck();
|
|
|
|
win.dispatchEvent(new win.MessageEvent('message', {
|
|
data: { type: 'od:slide', action: 'last' },
|
|
}));
|
|
expect(activeIndex()).toBe(3);
|
|
|
|
win.dispatchEvent(new win.MessageEvent('message', {
|
|
data: { type: 'od:slide', action: 'first' },
|
|
}));
|
|
expect(activeIndex()).toBe(0);
|
|
win.close();
|
|
});
|
|
});
|