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:
parent
13a3475026
commit
b9da0dcb76
10
oxlint.json
10
oxlint.json
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue