chore(lint): enforce test complexity limits

- Extract color picker fill comparison helpers
- Guard derived text assertions without optional chains
- Split resize math into smaller helper functions
- Remove the remaining complexity test exceptions
This commit is contained in:
Danila Poyarkov 2026-05-06 14:18:29 +03:00
parent 13a3475026
commit b9da0dcb76
4 changed files with 125 additions and 97 deletions

View file

@ -202,16 +202,6 @@
"no-console": "off"
}
},
{
"files": [
"tests/e2e/color-picker/demo-card.spec.ts",
"tests/engine/editor/clipboard/derived-text.test.ts",
"tests/engine/editor/effects-and-resize.test.ts"
],
"rules": {
"complexity": "off"
}
},
{
"files": ["scripts/**/*.ts"],
"rules": {

View file

@ -65,25 +65,37 @@ async function getSelectedFill(page: Parameters<typeof test>[0]['page']) {
})
}
async function getSelectedFillOrThrow(page: Parameters<typeof test>[0]['page']) {
const selectedFill = await getSelectedFill(page)
if (!selectedFill?.fill?.color) throw new Error('Selected node has no color fill')
return selectedFill
}
function expectFillColorChanged(
before: Awaited<ReturnType<typeof getSelectedFillOrThrow>>,
after: Awaited<ReturnType<typeof getSelectedFillOrThrow>>
) {
expect([
before.fill.color.r !== after.fill.color.r,
before.fill.color.g !== after.fill.color.g,
before.fill.color.b !== after.fill.color.b
]).toContain(true)
}
test('demo card fill changes through color picker', async ({ page }) => {
const canvas = new CanvasHelper(page)
await selectDemoCard(page, canvas)
const before = await getSelectedFill(page)
const before = await getSelectedFillOrThrow(page)
await page.locator('[data-test-id="fill-picker-swatch"]').first().click()
await expect(page.locator('[data-test-id="fill-picker-tab-solid"]')).toBeVisible()
await dragSlider(page, canvas, 'color-slider-hue', 0.7)
const after = await getSelectedFill(page)
const after = await getSelectedFillOrThrow(page)
expect(after).not.toBeNull()
expect(
before?.fill?.color.r !== after?.fill?.color.r ||
before?.fill?.color.g !== after?.fill?.color.g ||
before?.fill?.color.b !== after?.fill?.color.b
).toBe(true)
expectFillColorChanged(before, after)
})
test('demo card fill changes from hsb saturation and brightness sliders', async ({ page }) => {
@ -98,25 +110,15 @@ test('demo card fill changes from hsb saturation and brightness sliders', async
await page.getByRole('option', { name: 'HSB', exact: true }).click()
await canvas.waitForRender()
const beforeS = await getSelectedFill(page)
const beforeS = await getSelectedFillOrThrow(page)
await dragSlider(page, canvas, 'color-slider-hsb-s', 0.5)
const afterS = await getSelectedFill(page)
const afterS = await getSelectedFillOrThrow(page)
expect(afterS).not.toBeNull()
expect(
beforeS?.fill?.color.r !== afterS?.fill?.color.r ||
beforeS?.fill?.color.g !== afterS?.fill?.color.g ||
beforeS?.fill?.color.b !== afterS?.fill?.color.b
).toBe(true)
expectFillColorChanged(beforeS, afterS)
const beforeB = await getSelectedFill(page)
const beforeB = await getSelectedFillOrThrow(page)
await dragSlider(page, canvas, 'color-slider-hsb-b', 0.25)
const afterB = await getSelectedFill(page)
const afterB = await getSelectedFillOrThrow(page)
expect(afterB).not.toBeNull()
expect(
beforeB?.fill?.color.r !== afterB?.fill?.color.r ||
beforeB?.fill?.color.g !== afterB?.fill?.color.g ||
beforeB?.fill?.color.b !== afterB?.fill?.color.b
).toBe(true)
expectFillColorChanged(beforeB, afterB)
})

View file

@ -8,13 +8,16 @@ import {
initCodec
} from '@open-pencil/core'
import { expectDefined } from '#tests/helpers/assert'
describe('clipboard derived text export', () => {
test('builds richer v4 derivedTextData from shaped text + glyph outlines', async () => {
await initCodec()
const font = await fontManager.fetchBundledFont('/Inter-Regular.ttf')
expect(font).toBeTruthy()
if (!font) return
const font = expectDefined(
await fontManager.fetchBundledFont('/Inter-Regular.ttf'),
'bundled Inter font'
)
fontManager.markLoaded('Inter', 'Regular', font)
const graph = new SceneGraph()
@ -45,19 +48,25 @@ describe('clipboard derived text export', () => {
logicalIndexToCharacterOffsetMap: [0, 8, 16, 24, 32, 42]
})
expect(derived?.fontMetaData?.length).toBeGreaterThan(0)
expect(derived?.glyphs?.length).toBeGreaterThan(0)
expect(derived?.baselines?.length).toBeGreaterThan(0)
expect(derived?.logicalIndexToCharacterOffsetMap?.length).toBe(text.text.length + 1)
expect(derived?.logicalIndexToCharacterOffsetMap?.[5]).toBe(42)
expect(derived?.derivedLines?.[0]?.directionality).toBe('LTR')
expect(derived?.truncationStartIndex).toBe(-1)
expect(derived?.truncatedHeight).toBe(-1)
expect(derived?.glyphs?.[0]?.commands.length).toBeGreaterThan(0)
expect(derived?.glyphs?.[0]?.position.x).toBe(0)
expect(derived?.glyphs?.[4]?.position.x).toBe(32)
expect(derived?.baselines?.[0]?.lineHeight).toBe(20)
expect(derived?.baselines?.[0]?.lineAscent).toBe(15)
expect(derived?.baselines?.[0]?.width).toBe(42)
const derivedTextData = expectDefined(derived, 'derived text data')
const firstGlyph = expectDefined(derivedTextData.glyphs[0], 'first glyph')
const lastGlyph = expectDefined(derivedTextData.glyphs[4], 'last glyph')
const baseline = expectDefined(derivedTextData.baselines[0], 'first baseline')
const line = expectDefined(derivedTextData.derivedLines[0], 'first derived line')
expect(derivedTextData.fontMetaData.length).toBeGreaterThan(0)
expect(derivedTextData.glyphs.length).toBeGreaterThan(0)
expect(derivedTextData.baselines.length).toBeGreaterThan(0)
expect(derivedTextData.logicalIndexToCharacterOffsetMap.length).toBe(text.text.length + 1)
expect(derivedTextData.logicalIndexToCharacterOffsetMap[5]).toBe(42)
expect(line.directionality).toBe('LTR')
expect(derivedTextData.truncationStartIndex).toBe(-1)
expect(derivedTextData.truncatedHeight).toBe(-1)
expect(firstGlyph.commands.length).toBeGreaterThan(0)
expect(firstGlyph.position.x).toBe(0)
expect(lastGlyph.position.x).toBe(32)
expect(baseline.lineHeight).toBe(20)
expect(baseline.lineAscent).toBe(15)
expect(baseline.width).toBe(42)
})
})

View file

@ -180,6 +180,72 @@ describe('Effect types on scene graph', () => {
})
describe('Resize logic', () => {
type ResizeState = Pick<Rect, 'x' | 'y' | 'width' | 'height'>
function resizeFlags(handle: string) {
return {
moveLeft: handle.includes('w'),
moveRight: handle.includes('e'),
moveTop: handle === 'nw' || handle === 'n' || handle === 'ne',
moveBottom: handle === 'sw' || handle === 's' || handle === 'se'
}
}
function applyHandleDelta(handle: string, origRect: Rect, dx: number, dy: number): ResizeState {
const state: ResizeState = { ...origRect }
const flags = resizeFlags(handle)
if (flags.moveRight) state.width = origRect.width + dx
if (flags.moveLeft) {
state.x = origRect.x + dx
state.width = origRect.width - dx
}
if (flags.moveBottom) state.height = origRect.height + dy
if (flags.moveTop) {
state.y = origRect.y + dy
state.height = origRect.height - dy
}
return state
}
function applyAspectConstraint(
handle: string,
origRect: Rect,
state: ResizeState,
dx: number,
dy: number
) {
const aspect = origRect.width / origRect.height
const flags = resizeFlags(handle)
if (handle === 'n' || handle === 's') {
state.width = Math.abs(state.height) * aspect
state.x = origRect.x + (origRect.width - state.width) / 2
} else if (handle === 'e' || handle === 'w') {
state.height = Math.abs(state.width) / aspect
state.y = origRect.y + (origRect.height - state.height) / 2
} else if (Math.abs(dx) > Math.abs(dy)) {
state.height = (Math.abs(state.width) / aspect) * Math.sign(state.height || 1)
if (flags.moveTop) state.y = origRect.y + origRect.height - Math.abs(state.height)
} else {
state.width = Math.abs(state.height) * aspect * Math.sign(state.width || 1)
if (flags.moveLeft) state.x = origRect.x + origRect.width - Math.abs(state.width)
}
}
function normalizeResize(state: ResizeState): ResizeState {
if (state.width < 0) {
state.x += state.width
state.width = -state.width
}
if (state.height < 0) {
state.y += state.height
state.height = -state.height
}
return state
}
function applyResize(
handle: string,
origRect: Rect,
@ -189,59 +255,20 @@ describe('Resize logic', () => {
cy: number,
constrain = false
) {
let { x, y, width, height } = origRect
const dx = cx - startX
const dy = cy - startY
const moveLeft = handle.includes('w')
const moveRight = handle.includes('e')
const moveTop = handle === 'nw' || handle === 'n' || handle === 'ne'
const moveBottom = handle === 'sw' || handle === 's' || handle === 'se'
if (moveRight) width = origRect.width + dx
if (moveLeft) {
x = origRect.x + dx
width = origRect.width - dx
}
if (moveBottom) height = origRect.height + dy
if (moveTop) {
y = origRect.y + dy
height = origRect.height - dy
}
const state = applyHandleDelta(handle, origRect, dx, dy)
if (constrain && origRect.width > 0 && origRect.height > 0) {
const aspect = origRect.width / origRect.height
if (handle === 'n' || handle === 's') {
width = Math.abs(height) * aspect
x = origRect.x + (origRect.width - width) / 2
} else if (handle === 'e' || handle === 'w') {
height = Math.abs(width) / aspect
y = origRect.y + (origRect.height - height) / 2
} else {
if (Math.abs(dx) > Math.abs(dy)) {
height = (Math.abs(width) / aspect) * Math.sign(height || 1)
if (moveTop) y = origRect.y + origRect.height - Math.abs(height)
} else {
width = Math.abs(height) * aspect * Math.sign(width || 1)
if (moveLeft) x = origRect.x + origRect.width - Math.abs(width)
}
}
}
if (width < 0) {
x = x + width
width = -width
}
if (height < 0) {
y = y + height
height = -height
applyAspectConstraint(handle, origRect, state, dx, dy)
}
const normalized = normalizeResize(state)
return {
x: Math.round(x),
y: Math.round(y),
width: Math.round(Math.max(1, width)),
height: Math.round(Math.max(1, height))
x: Math.round(normalized.x),
y: Math.round(normalized.y),
width: Math.round(Math.max(1, normalized.width)),
height: Math.round(Math.max(1, normalized.height))
}
}