import { describe, expect, test } from 'vitest'; import { clampResourceCanvasGenerationPanelAnchorX, resolveResourceCanvasGenerationPanelPlacement, RESOURCE_CANVAS_GENERATION_PANEL_MAX_WIDTH, RESOURCE_CANVAS_GENERATION_PANEL_VIEWPORT_INSET, resourceCanvasGenerationPanelWidth, revealResourceCanvasGenerationContent, } from '../src/features/resource-canvas/resourceCanvasGenerationVisibilityModel'; const canvasSize = { width: 800, height: 600 }; const insets = { top: 60, bottom: 90 }; describe('生成浮层与占位的可见性', () => { test('内容已经在安全区内时坐标不变(宿主据此跳过写回)', () => { const viewport = { x: 0, y: 0, scale: 1 }; const next = revealResourceCanvasGenerationContent({ viewport, content: { x: 40, y: 80, width: 180, height: 200 }, canvasSize, insets, }); expect(next).toEqual(viewport); }); test('内容落到视口下方时做最小上移,底边贴住底栏安全区', () => { const viewport = { x: 0, y: 0, scale: 1 }; // 占位在 y=550、加上浮层后整块落到视口外——真实浏览器上复现过的那一档。 const next = revealResourceCanvasGenerationContent({ viewport, content: { x: 40, y: 550, width: 180, height: 320 }, canvasSize, insets, }); const safeBottom = canvasSize.height - insets.bottom; expect(viewport.y + (550 + 320)).toBeGreaterThan(safeBottom); expect(next.y + 550 + 320).toBe(safeBottom); expect(next.x).toBe(0); expect(next.scale).toBe(1); }); test('内容顶出顶栏时下移,内容越出左右边时做最小横移', () => { const next = revealResourceCanvasGenerationContent({ viewport: { x: 0, y: 0, scale: 1 }, content: { x: -300, y: -40, width: 180, height: 120 }, canvasSize, insets, }); expect(next.y).toBe(insets.top + 40); expect(next.x).toBe(12 + 300); }); test('缩放参与换算:缩小后的越界按同一口径平移,比例保持不变', () => { const next = revealResourceCanvasGenerationContent({ viewport: { x: 0, y: 0, scale: 0.5 }, content: { x: 40, y: 900, width: 180, height: 320 }, canvasSize, insets, }); expect(next.scale).toBe(0.5); const safeBottom = canvasSize.height - insets.bottom; expect(next.y + (900 + 320) * 0.5).toBe(safeBottom); }); test('内容比安全区还大时以顶边 / 左边对齐,不来回抖', () => { const next = revealResourceCanvasGenerationContent({ viewport: { x: 0, y: 0, scale: 1 }, content: { x: 0, y: 0, width: 2000, height: 1200 }, canvasSize, insets, }); expect(next).toEqual({ scale: 1, x: 12, y: insets.top }); }); test('尺寸非法或画布未就绪时原样返回', () => { const viewport = { x: 5, y: 6, scale: 1 }; expect( revealResourceCanvasGenerationContent({ viewport, content: { x: 0, y: 0, width: 0, height: 0 }, canvasSize, insets, }), ).toEqual(viewport); expect( revealResourceCanvasGenerationContent({ viewport, content: { x: 0, y: 0, width: 10, height: 10 }, canvasSize: { width: 0, height: 0 }, insets, }), ).toEqual(viewport); }); }); describe('浮层顶边与高度按占位卡的真实屏幕位置算', () => { const placement = (input: { canvasHeight: number; topInset: number; bottomInset: number; anchorTop: number; anchorHeight: number; }) => resolveResourceCanvasGenerationPanelPlacement(input); test('空间够时挂在卡下面:顶边贴卡下沿,高度 = 卡下沿到安全底边', () => { const result = placement({ canvasHeight: 568, topInset: 54, bottomInset: 84, anchorTop: 54, anchorHeight: 128, }); expect(result.overlaysAnchor).toBe(false); // 安全底边 484 - 卡下沿(54 + 128) - 间隙 12 = 290。 expect(result.top).toBe(54 + 128 + 12); expect(result.availableHeight).toBe(290); expect(result.top + result.availableHeight).toBe(568 - 84); }); test('安全带给不出可编辑高度时改为盖住占位,拿「卡顶边到安全底边」', () => { // 缩放 1.5:占位卡视觉 192px,卡下面只剩 234 (< 280) → 盖住占位。 const result = placement({ canvasHeight: 568, topInset: 58, bottomInset: 72, anchorTop: 58, anchorHeight: 192, }); expect(result.overlaysAnchor).toBe(true); expect(result.top).toBe(58); // 568 - 72 - 58 = 438:远大于「一百多像素」的旧表现。 expect(result.availableHeight).toBe(438); expect(result.availableHeight).toBeGreaterThanOrEqual(260); }); /** * 验收现场那条「生成窗口和画布边缘碰撞」的回归:占位卡是追加在栏目内容最下方的, * 落到画布下半屏是常态。旧口径只吃「安全带高度 − 卡高」,等于假设卡永远贴在安全带上沿, * 于是浮层底边越过画布下沿被 `overflow: hidden` 切掉。 */ test('占位卡落到下半屏时浮层底边仍收在安全底边以内', () => { const result = placement({ canvasHeight: 600, topInset: 60, bottomInset: 90, anchorTop: 470, anchorHeight: 128, }); expect(result.overlaysAnchor).toBe(true); // 卡顶边 470 会顶出安全底边(510 - 160 = 350),所以顶边先收到 350,再给一整段可编辑高度。 expect(result.top).toBe(350); expect(result.top + result.availableHeight).toBeLessThanOrEqual(600 - 90); // 中间位置同理:顶边贴卡顶,底边贴安全底边。 const middle = placement({ canvasHeight: 600, topInset: 60, bottomInset: 90, anchorTop: 260, anchorHeight: 128, }); expect(middle.overlaysAnchor).toBe(true); expect(middle.top).toBe(260); expect(middle.top + middle.availableHeight).toBe(600 - 90); }); test('空间充足时收到上限,几何非法时回退到最小编辑高度', () => { expect( placement({ canvasHeight: 1400, topInset: 54, bottomInset: 84, anchorTop: 54, anchorHeight: 128, }).availableHeight, ).toBe(520); expect( placement({ canvasHeight: 0, topInset: 54, bottomInset: 84, anchorTop: 54, anchorHeight: 128, }), ).toEqual({ overlaysAnchor: false, top: 54, availableHeight: 280 }); // 画布小到连安全带都装不下:退到安全带能给的量,但仍有可编辑高度。 const tiny = placement({ canvasHeight: 240, topInset: 40, bottomInset: 40, anchorTop: 40, anchorHeight: 128, }); expect(tiny.overlaysAnchor).toBe(true); expect(tiny.availableHeight).toBe(160); expect(tiny.top + tiny.availableHeight).toBeLessThanOrEqual(240 - 40); }); }); describe('浮层水平收口:贴着卡片,但恒定留在画布内', () => { const canvasWidth = 900; const panelWidth = resourceCanvasGenerationPanelWidth(canvasWidth); test('宽度口径与浮层 CSS 同源:min(560, 画布宽 - 24)', () => { expect(panelWidth).toBe(RESOURCE_CANVAS_GENERATION_PANEL_MAX_WIDTH); expect(resourceCanvasGenerationPanelWidth(500)).toBe( 500 - RESOURCE_CANVAS_GENERATION_PANEL_VIEWPORT_INSET * 2, ); // 画布未就绪(量不到宽)时退回最大宽度,交给 CSS 收。 expect(resourceCanvasGenerationPanelWidth(0)).toBe( RESOURCE_CANVAS_GENERATION_PANEL_MAX_WIDTH, ); }); test('卡片贴左边 / 贴右边时锚点收进来,浮层两边各留 12px', () => { const left = clampResourceCanvasGenerationPanelAnchorX({ centerX: 20, canvasWidth, panelWidth, }); expect(left - panelWidth / 2).toBe( RESOURCE_CANVAS_GENERATION_PANEL_VIEWPORT_INSET, ); const right = clampResourceCanvasGenerationPanelAnchorX({ centerX: canvasWidth - 5, canvasWidth, panelWidth, }); expect(right + panelWidth / 2).toBe( canvasWidth - RESOURCE_CANVAS_GENERATION_PANEL_VIEWPORT_INSET, ); // 卡片在中间时不动:收口只在真的会越界时才生效。 expect( clampResourceCanvasGenerationPanelAnchorX({ centerX: canvasWidth / 2, canvasWidth, panelWidth, }), ).toBe(canvasWidth / 2); }); test('画布比浮层还窄时居中(宽度收口交给 CSS),几何非法时原样返回', () => { const narrowCanvas = 400; const narrowPanel = resourceCanvasGenerationPanelWidth(narrowCanvas); expect( clampResourceCanvasGenerationPanelAnchorX({ centerX: 10, canvasWidth: narrowCanvas, panelWidth: narrowPanel, }), ).toBe(narrowCanvas / 2); expect( clampResourceCanvasGenerationPanelAnchorX({ centerX: 33, canvasWidth: Number.NaN, panelWidth: 560, }), ).toBe(33); expect( clampResourceCanvasGenerationPanelAnchorX({ centerX: 33, canvasWidth: 800, panelWidth: 0, }), ).toBe(33); }); });