diff --git a/desktop/src/preview-agent/metadata.test.ts b/desktop/src/preview-agent/metadata.test.ts new file mode 100644 index 00000000..fca41d8d --- /dev/null +++ b/desktop/src/preview-agent/metadata.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, it, beforeEach, vi } from 'vitest' +import { buildElementMetadata } from './metadata' + +beforeEach(() => { document.body.innerHTML = `

Hello World

` }) + +describe('buildElementMetadata', () => { + it('captures tag/id/classes/text/selector and styles', () => { + const el = document.getElementById('t')! + el.getBoundingClientRect = () => ({ x: 1, y: 2, width: 3, height: 4, top: 2, left: 1, right: 4, bottom: 6, toJSON: () => ({}) }) as DOMRect + vi.spyOn(window, 'getComputedStyle').mockReturnValue({ color: 'rgb(0,0,0)', backgroundColor: 'rgba(0,0,0,0)', opacity: '1', fontFamily: 'serif', fontSize: '40px' } as unknown as CSSStyleDeclaration) + const m = buildElementMetadata(el) + expect(m.tag).toBe('h1') + expect(m.id).toBe('t') + expect(m.classes).toEqual(['a', 'b']) + expect(m.text).toBe('Hello World') + expect(m.boundingBox).toEqual({ x: 1, y: 2, w: 3, h: 4 }) + expect(m.computedStyles.color).toBe('rgb(0,0,0)') + expect(m.selector).toBe('#t') + }) +}) diff --git a/desktop/src/preview-agent/metadata.ts b/desktop/src/preview-agent/metadata.ts new file mode 100644 index 00000000..fe98a244 --- /dev/null +++ b/desktop/src/preview-agent/metadata.ts @@ -0,0 +1,25 @@ +import { buildSelector, buildNthPath } from './selector' + +export type ElementMetadata = { + selector: string; nthPath: string; tag: string; id?: string; classes: string[] + text?: string; boundingBox: { x: number; y: number; w: number; h: number } + computedStyles: Record; outerHtmlSnippet?: string +} + +const STYLE_KEYS = ['color', 'backgroundColor', 'opacity', 'fontFamily', 'fontSize', 'fontWeight', 'textAlign', 'padding', 'margin'] as const + +export function buildElementMetadata(el: Element): ElementMetadata { + const cs = window.getComputedStyle(el) + const styles: Record = {} + for (const k of STYLE_KEYS) styles[k] = (cs as unknown as Record)[k] ?? '' + const r = el.getBoundingClientRect() + const sel = buildSelector(el) + return { + selector: sel, nthPath: buildNthPath(el), tag: el.tagName.toLowerCase(), + id: el.id || undefined, classes: Array.from(el.classList), + text: (el.textContent ?? '').trim().slice(0, 200) || undefined, + boundingBox: { x: r.x, y: r.y, w: r.width, h: r.height }, + computedStyles: styles, + outerHtmlSnippet: sel.includes(':nth') ? el.outerHTML.slice(0, 300) : undefined, + } +}