| import { screen, waitFor } from '@testing-library/react' |
| import userEvent from '@testing-library/user-event' |
| import { http, HttpResponse } from 'msw' |
| import { beforeEach, describe, expect, it, vi } from 'vitest' |
|
|
| import { RenderControlsPanel } from '@/components/panels/RenderControlsPanel' |
| import * as sceneActions from '@/lib/io/scene' |
| import { usePreferencesStore } from '@/lib/stores/preferencesStore' |
| import { useSelectionStore } from '@/lib/stores/selectionStore' |
|
|
| import { renderWithQuery } from '../helpers' |
| import { server } from '../msw/server' |
|
|
| vi.mock('@tanstack/react-virtual', () => ({ |
| useVirtualizer: ({ count }: { count: number }) => ({ |
| getTotalSize: () => count * 28, |
| getVirtualItems: () => |
| Array.from({ length: count }, (_, i) => ({ |
| index: i, |
| start: i * 28, |
| end: (i + 1) * 28, |
| size: 28, |
| key: i, |
| })), |
| measure: vi.fn(), |
| }), |
| })) |
|
|
| vi.mock('@/lib/io/scene', async () => { |
| const actual = await vi.importActual<any>('@/lib/io/scene') |
| return { |
| ...actual, |
| applyOp: vi.fn(), |
| queueAutoRender: vi.fn(), |
| } |
| }) |
|
|
| function sceneWithTextNodes(nodes: any[]) { |
| const nodeMap: any = {} |
| nodes.forEach((n) => { |
| nodeMap[n.id] = { |
| id: n.id, |
| transform: { x: 0, y: 0, width: 10, height: 10, rotationDeg: 0 }, |
| visible: true, |
| kind: { text: n.kind?.text ?? { style: { fontFamilies: ['Arial'] } } }, |
| } |
| }) |
| return { |
| epoch: 1, |
| scene: { |
| pages: { |
| p1: { id: 'p1', name: 'P1', nodes: nodeMap }, |
| }, |
| project: { name: 'Proj' }, |
| }, |
| } |
| } |
|
|
| describe('RenderControlsPanel Font Assignment', () => { |
| beforeEach(() => { |
| useSelectionStore.getState().setPage('p1') |
| useSelectionStore.getState().clear() |
| usePreferencesStore.getState().setDefaultFont('Arial') |
| vi.clearAllMocks() |
|
|
| server.use( |
| http.get('/api/v1/fonts', () => |
| HttpResponse.json([ |
| { familyName: 'Arial', postScriptName: 'Arial', source: 'system', cached: true }, |
| { familyName: 'Roboto', postScriptName: 'Roboto', source: 'system', cached: true }, |
| { familyName: 'Custom', postScriptName: 'Custom', source: 'system', cached: true }, |
| ]), |
| ), |
| http.get('/api/v1/scene.json', () => |
| HttpResponse.json( |
| sceneWithTextNodes([ |
| { id: 't1', kind: { text: { style: { fontFamilies: ['Arial'] } } } }, |
| { id: 't2', kind: { text: { style: { fontFamilies: ['Arial'] } } } }, |
| ]), |
| ), |
| ), |
| ) |
| }) |
|
|
| it('applying a font to a singular text box only updates that box', async () => { |
| renderWithQuery(<RenderControlsPanel />) |
|
|
| |
| useSelectionStore.getState().select('t1', false) |
|
|
| |
| const trigger = await screen.findByTestId('render-font-select') |
| await userEvent.click(trigger) |
|
|
| |
| const option = await screen.findByText('Roboto') |
| await userEvent.click(option) |
|
|
| |
| await waitFor(() => expect(sceneActions.applyOp).toHaveBeenCalled()) |
| const lastOp = (sceneActions.applyOp as any).mock.calls[0][0] |
| expect(lastOp).toHaveProperty('updateNode') |
| expect(lastOp.updateNode.id).toBe('t1') |
| expect(lastOp.updateNode.patch.data.text.style.fontFamilies).toEqual(['Roboto']) |
| }) |
|
|
| it('bulk applying a font change (with selection) updates all selected boxes', async () => { |
| renderWithQuery(<RenderControlsPanel />) |
|
|
| |
| useSelectionStore.getState().selectMany(['t1', 't2']) |
|
|
| |
| const trigger = await screen.findByTestId('render-font-select') |
| await userEvent.click(trigger) |
|
|
| |
| const option = await screen.findByText('Roboto') |
| await userEvent.click(option) |
|
|
| |
| await waitFor(() => expect(sceneActions.applyOp).toHaveBeenCalled()) |
| const lastOp = (sceneActions.applyOp as any).mock.calls[0][0] |
| expect(lastOp).toHaveProperty('batch') |
| expect(lastOp.batch.ops).toHaveLength(2) |
| }) |
|
|
| it('changing global font (no selection) updates defaultFont in preferences', async () => { |
| renderWithQuery(<RenderControlsPanel />) |
|
|
| |
|
|
| |
| const trigger = await screen.findByTestId('render-font-select') |
| await userEvent.click(trigger) |
|
|
| |
| const option = await screen.findByText('Custom') |
| await userEvent.click(option) |
|
|
| |
| expect(usePreferencesStore.getState().defaultFont).toBe('Custom') |
| }) |
|
|
| it('shows auto when a selected block has no manual font size override', async () => { |
| server.use( |
| http.get('/api/v1/scene.json', () => |
| HttpResponse.json( |
| sceneWithTextNodes([ |
| { |
| id: 't1', |
| kind: { |
| text: { |
| style: { fontFamilies: ['Arial'] }, |
| fontPrediction: { fontSizePx: 66, strokeWidthPx: 0, textColor: [0, 0, 0] }, |
| detectedFontSizePx: 30, |
| }, |
| }, |
| }, |
| ]), |
| ), |
| ), |
| ) |
|
|
| renderWithQuery(<RenderControlsPanel />) |
| useSelectionStore.getState().select('t1', false) |
|
|
| const input = (await screen.findByTestId('render-font-size')) as HTMLInputElement |
| await waitFor(() => expect(input.value).toBe('')) |
| expect(input).toHaveAttribute('placeholder', 'auto') |
| }) |
|
|
| it('opening the font color picker commits effective black as an explicit color', async () => { |
| server.use( |
| http.get('/api/v1/scene.json', () => |
| HttpResponse.json( |
| sceneWithTextNodes([ |
| { |
| id: 't1', |
| kind: { |
| text: { |
| fontPrediction: { fontSizePx: 66, strokeWidthPx: 0, textColor: [0, 0, 0] }, |
| }, |
| }, |
| }, |
| ]), |
| ), |
| ), |
| ) |
|
|
| renderWithQuery(<RenderControlsPanel />) |
| useSelectionStore.getState().select('t1', false) |
|
|
| const trigger = await screen.findByTestId('render-color-trigger') |
| await userEvent.click(trigger) |
|
|
| await waitFor(() => expect(sceneActions.applyOp).toHaveBeenCalled()) |
| const op = (sceneActions.applyOp as any).mock.calls[0][0] |
| expect(op.updateNode.id).toBe('t1') |
| expect(op.updateNode.patch.data.text.style.color).toEqual([0, 0, 0, 255]) |
| }) |
| }) |
|
|