trans1 / ui /tests /components /RenderControlsPanel.test.tsx
Mayo
fix: black color not apply
3cd559b unverified
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 />)
// Select node t1
useSelectionStore.getState().select('t1', false)
// Open font select
const trigger = await screen.findByTestId('render-font-select')
await userEvent.click(trigger)
// Pick "Roboto"
const option = await screen.findByText('Roboto')
await userEvent.click(option)
// Verify applyOp was called for t1
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 />)
// Select both nodes
useSelectionStore.getState().selectMany(['t1', 't2'])
// Open font select
const trigger = await screen.findByTestId('render-font-select')
await userEvent.click(trigger)
// Pick "Roboto"
const option = await screen.findByText('Roboto')
await userEvent.click(option)
// Verify applyOp was called with a batch
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 />)
// No selection
// Open font select
const trigger = await screen.findByTestId('render-font-select')
await userEvent.click(trigger)
// Pick "Custom"
const option = await screen.findByText('Custom')
await userEvent.click(option)
// Verify default font changed
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])
})
})