diff --git a/oxlint.json b/oxlint.json index 0b9dcaf57..4b05b1910 100644 --- a/oxlint.json +++ b/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": { diff --git a/tests/e2e/color-picker/demo-card.spec.ts b/tests/e2e/color-picker/demo-card.spec.ts index 2620764d4..30aa1670f 100644 --- a/tests/e2e/color-picker/demo-card.spec.ts +++ b/tests/e2e/color-picker/demo-card.spec.ts @@ -65,25 +65,37 @@ async function getSelectedFill(page: Parameters[0]['page']) { }) } +async function getSelectedFillOrThrow(page: Parameters[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>, + after: Awaited> +) { + 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) }) diff --git a/tests/engine/editor/clipboard/derived-text.test.ts b/tests/engine/editor/clipboard/derived-text.test.ts index 2c7507e4e..50aee6a24 100644 --- a/tests/engine/editor/clipboard/derived-text.test.ts +++ b/tests/engine/editor/clipboard/derived-text.test.ts @@ -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) }) }) diff --git a/tests/engine/editor/effects-and-resize.test.ts b/tests/engine/editor/effects-and-resize.test.ts index a5d6569f3..06889debc 100644 --- a/tests/engine/editor/effects-and-resize.test.ts +++ b/tests/engine/editor/effects-and-resize.test.ts @@ -180,6 +180,72 @@ describe('Effect types on scene graph', () => { }) describe('Resize logic', () => { + type ResizeState = Pick + + 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)) } }