返回 oh-my-ppt
edit-mode-shape-classify.test.ts
根目录 / tests / unit / preview / edit-mode-shape-classify.test.ts
1 import { Window } from 'happy-dom'
2 import { describe, expect, it } from 'vitest'
3 import { buildEditModeInjectScript } from '@arcsin1/presentation-editor-runtime'
4 import {
5 buildIconElementHtml,
6 buildShapeElementHtml
7 } from '../../../src/renderer/src/components/session-detail/workspace/insert-shapes'
8
9 type Rect = Pick<DOMRect, 'left' | 'top' | 'right' | 'bottom' | 'width' | 'height'>
10
11 const rect = (left: number, top: number, width: number, height: number): Rect => ({
12 left,
13 top,
14 right: left + width,
15 bottom: top + height,
16 width,
17 height
18 })
19
20 interface EditWindow extends Window {
21 __pptEditModeReadSnapshot: (selector: string) => {
22 kind: string
23 capabilities: string[]
24 blockId?: string
25 computed: {
26 svgPaintColor?: string
27 }
28 } | null
29 __pptEditModeApplyProperties: (
30 selector: string,
31 patch: { style?: { backgroundColor?: string } }
32 ) => void
33 }
34
35 function setupEditWindow(innerHtml: string): EditWindow {
36 const window = new Window({ url: 'http://localhost/page.html' }) as unknown as EditWindow & {
37 ResizeObserver: typeof ResizeObserver
38 eval: (code: string) => void
39 }
40 const { document } = window
41
42 document.body.setAttribute('data-page-id', 'page')
43 document.body.innerHTML = innerHtml
44
45 const root = document.querySelector('.ppt-page-root') as HTMLElement
46 window.HTMLElement.prototype.getBoundingClientRect = function () {
47 if (this === root) return rect(0, 0, 1000, 600)
48 // any child element gets a non-zero rect so it passes the >=2px gate
49 return rect(100, 100, 240, 120)
50 }
51 window.HTMLElement.prototype.getClientRects = function () {
52 return [this.getBoundingClientRect()]
53 }
54 window.ResizeObserver = class {
55 observe() {}
56 disconnect() {}
57 } as unknown as typeof ResizeObserver
58
59 window.eval(buildEditModeInjectScript())
60 return window
61 }
62
63 const PAGE_SHELL = `
64 <main class="ppt-page-root" data-ppt-guard-root="1" data-ppt-width="1000" data-ppt-height="600">
65 <main data-block-id="content" data-role="content"></main>
66 </main>
67 `
68
69 describe('edit-mode classify for inserted shape/icon elements', () => {
70 it('classifies a shape div as kind=shape with appearance capabilities', () => {
71 const fragment = buildShapeElementHtml({
72 blockId: 'select-arcsin1-shape0001',
73 left: 100,
74 top: 100,
75 width: 240,
76 height: 120,
77 zIndex: 12,
78 type: 'rounded-rect'
79 })
80 const window = setupEditWindow(PAGE_SHELL.replace('</main>', `${fragment}</main>`))
81 const snapshot = window.__pptEditModeReadSnapshot(
82 'body[data-page-id="page"] [data-block-id="select-arcsin1-shape0001"]'
83 )
84 expect(snapshot).not.toBeNull()
85 expect(snapshot!.kind).toBe('shape')
86 expect(snapshot!.capabilities).toEqual(expect.arrayContaining(['appearance', 'border']))
87 expect(snapshot!.computed.svgPaintColor).toBe('#d4e4c1')
88 })
89
90 it('classifies an icon div as kind=shape (visual element) with appearance capabilities', () => {
91 const fragment = buildIconElementHtml({
92 blockId: 'select-arcsin1-icon00001',
93 iconId: 'lightbulb',
94 left: 100,
95 top: 100,
96 width: 96,
97 height: 96,
98 zIndex: 12
99 })
100 const window = setupEditWindow(PAGE_SHELL.replace('</main>', `${fragment}</main>`))
101 const snapshot = window.__pptEditModeReadSnapshot(
102 'body[data-page-id="page"] [data-block-id="select-arcsin1-icon00001"]'
103 )
104 expect(snapshot).not.toBeNull()
105 // icon is also a painted visual element; it must not fall back to "unknown"
106 expect(snapshot!.kind).not.toBe('unknown')
107 expect(snapshot!.capabilities).toEqual(expect.arrayContaining(['appearance']))
108 })
109
110 it('applies appearance color changes to inserted shape svg paint', () => {
111 const fragment = buildShapeElementHtml({
112 blockId: 'select-arcsin1-shape0002',
113 left: 100,
114 top: 100,
115 width: 240,
116 height: 120,
117 zIndex: 12,
118 type: 'rect'
119 })
120 const selector = 'body[data-page-id="page"] [data-block-id="select-arcsin1-shape0002"]'
121 const window = setupEditWindow(PAGE_SHELL.replace('</main>', `${fragment}</main>`))
122
123 window.__pptEditModeApplyProperties(selector, { style: { backgroundColor: '#ff3366' } })
124
125 const rect = window.document.querySelector(`${selector} svg rect`)
126 expect(rect?.getAttribute('fill')).toBe('#ff3366')
127 expect(rect?.getAttribute('stroke')).toBe('#ff3366')
128 })
129
130 it('applies appearance color changes to inserted icon currentColor', () => {
131 const fragment = buildIconElementHtml({
132 blockId: 'select-arcsin1-icon00002',
133 iconId: 'lightbulb',
134 left: 100,
135 top: 100,
136 width: 96,
137 height: 96,
138 zIndex: 12
139 })
140 const selector = 'body[data-page-id="page"] [data-block-id="select-arcsin1-icon00002"]'
141 const window = setupEditWindow(PAGE_SHELL.replace('</main>', `${fragment}</main>`))
142
143 window.__pptEditModeApplyProperties(selector, { style: { backgroundColor: '#3366ff' } })
144
145 const icon = window.document.querySelector(selector) as HTMLElement | null
146 expect(icon?.style.color).toBe('#3366ff')
147 })
148 })
149
149 lines TYPESCRIPT