| 1 | import { Window } from 'happy-dom' |
| 2 | import { describe, expect, it } from 'vitest' |
| 3 | import { buildEditModeInjectScript } from '@arcsin1/presentation-editor-runtime' |
| 4 | |
| 5 | type EditorWindow = Window & { |
| 6 | __pptEditModeInspectElement: (selector: string) => { |
| 7 | attributes: Record<string, string> |
| 8 | inlineStyle: Record<string, { value: string; priority: string }> |
| 9 | computedStyle: Record<string, string> |
| 10 | } | null |
| 11 | __pptEditModeApplyOperations: ( |
| 12 | selector: string, |
| 13 | operations: unknown[] |
| 14 | ) => Array<{ ok: boolean; errorCode?: string }> |
| 15 | __pptEditModeRestoreSelection: (selector: string) => boolean |
| 16 | __pptEditModeClearSelection: () => void |
| 17 | } |
| 18 | |
| 19 | describe('presentation editor runtime bridge', () => { |
| 20 | it('keeps legacy edit-mode selection while exposing full inspection and controlled generic edits', () => { |
| 21 | const window = new Window({ url: 'http://localhost/page.html' }) as EditorWindow & { |
| 22 | ResizeObserver: typeof ResizeObserver |
| 23 | eval: (code: string) => void |
| 24 | } |
| 25 | const { document } = window |
| 26 | document.body.setAttribute('data-page-id', 'page') |
| 27 | document.body.innerHTML = ` |
| 28 | <main class="ppt-page-root" data-ppt-guard-root="1" data-ppt-width="1000" data-ppt-height="600"> |
| 29 | <main data-block-id="content" data-role="content"> |
| 30 | <p id="title" data-block-id="title" aria-label="Original" style="letter-spacing: 0.02em">Hello</p> |
| 31 | </main> |
| 32 | </main> |
| 33 | ` |
| 34 | const title = document.querySelector('#title') as HTMLElement |
| 35 | window.HTMLElement.prototype.getBoundingClientRect = function () { |
| 36 | return this === title |
| 37 | ? ({ left: 20, top: 30, width: 240, height: 60, right: 260, bottom: 90 } as DOMRect) |
| 38 | : ({ left: 0, top: 0, width: 1000, height: 600, right: 1000, bottom: 600 } as DOMRect) |
| 39 | } |
| 40 | window.HTMLElement.prototype.getClientRects = function () { |
| 41 | return [this.getBoundingClientRect()] |
| 42 | } |
| 43 | window.ResizeObserver = class { |
| 44 | observe() {} |
| 45 | disconnect() {} |
| 46 | } as unknown as typeof ResizeObserver |
| 47 | |
| 48 | window.eval(buildEditModeInjectScript()) |
| 49 | const selector = 'body[data-page-id="page"] [data-block-id="title"]' |
| 50 | |
| 51 | const inspector = window.__pptEditModeInspectElement(selector) |
| 52 | expect(inspector?.attributes).toMatchObject({ |
| 53 | id: 'title', |
| 54 | 'data-block-id': 'title', |
| 55 | 'aria-label': 'Original' |
| 56 | }) |
| 57 | expect(inspector?.inlineStyle['letter-spacing']).toEqual({ value: '0.02em', priority: '' }) |
| 58 | expect(inspector?.computedStyle.display).toBeTruthy() |
| 59 | |
| 60 | expect( |
| 61 | window.__pptEditModeApplyOperations(selector, [ |
| 62 | { type: 'set-attribute', name: 'data-theme', value: 'dark' }, |
| 63 | { type: 'set-style', property: 'letter-spacing', value: '0.08em', priority: 'important' }, |
| 64 | { type: 'set-attribute', name: 'onclick', value: 'alert(1)' } |
| 65 | ]) |
| 66 | ).toMatchObject([ |
| 67 | { ok: true }, |
| 68 | { ok: true }, |
| 69 | { ok: false, errorCode: 'PROTECTED_ATTRIBUTE' } |
| 70 | ]) |
| 71 | expect(title.getAttribute('data-theme')).toBe('dark') |
| 72 | expect(title.style.getPropertyValue('letter-spacing')).toBe('0.08em') |
| 73 | expect(title.style.getPropertyPriority('letter-spacing')).toBe('important') |
| 74 | |
| 75 | expect(window.__pptEditModeRestoreSelection(selector)).toBe(true) |
| 76 | expect(title.classList.contains('arcsin1-presentation-editor-selected')).toBe(true) |
| 77 | expect(title.getAttribute('data-arcsin1-presentation-editor-selected')).toBe('true') |
| 78 | |
| 79 | window.__pptEditModeClearSelection() |
| 80 | expect(title.hasAttribute('data-arcsin1-presentation-editor-selected')).toBe(false) |
| 81 | }) |
| 82 | }) |
| 83 |