From f1426cb3bb4e56701675cc8d098a2ff3551f21aa Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 1 Jul 2026 11:43:24 +0200 Subject: [PATCH] :bug: Add test of fixes in the plugins api suite --- .../src/tests/comments.test.ts | 8 +-- .../src/tests/interactions.test.ts | 59 +++++++++++++++++++ .../src/tests/layout.test.ts | 34 +++++++++++ .../src/tests/shapes-geometry.test.ts | 15 +++++ .../src/tests/tokens.test.ts | 13 ++++ 5 files changed, 125 insertions(+), 4 deletions(-) diff --git a/plugins/apps/plugin-api-test-suite/src/tests/comments.test.ts b/plugins/apps/plugin-api-test-suite/src/tests/comments.test.ts index b6ba1745ad..bc53f49d26 100644 --- a/plugins/apps/plugin-api-test-suite/src/tests/comments.test.ts +++ b/plugins/apps/plugin-api-test-suite/src/tests/comments.test.ts @@ -4,9 +4,9 @@ import type { CommentThread, Page } from '@penpot/plugin-types'; import type { TestContext } from '../framework/types'; // Comments. -// Comment threads are created on the current page. Both thread removal APIs are -// currently broken (see the dedicated red tests), so cleanup is best-effort to -// keep the other assertions meaningful. +// Comment threads are created on the current page. Thread/comment removal is +// asynchronous (it resolves once the backend delete RPC completes), so callers +// must await it; cleanup is best-effort and swallows errors. function page(ctx: TestContext): Page { const p = ctx.penpot.currentPage; @@ -124,7 +124,7 @@ describe.skipIfMocked('Comments', () => { x: 8, y: 8, }); - thread.remove(); + await thread.remove(); const threads = await p.findCommentThreads(); expect(threads.every((t) => t.seqNumber !== thread.seqNumber)).toBe(true); }); diff --git a/plugins/apps/plugin-api-test-suite/src/tests/interactions.test.ts b/plugins/apps/plugin-api-test-suite/src/tests/interactions.test.ts index 91f26d3dce..842a395031 100644 --- a/plugins/apps/plugin-api-test-suite/src/tests/interactions.test.ts +++ b/plugins/apps/plugin-api-test-suite/src/tests/interactions.test.ts @@ -83,6 +83,21 @@ describe('Interactions', () => { expect(interaction.action.type).toBe('open-overlay'); }); + // position is optional; when omitted the overlay defaults to 'center'. + test('open-overlay without a position defaults to center', (ctx) => { + const overlay = board(ctx); + const r = rect(ctx); + const interaction = r.addInteraction('click', { + type: 'open-overlay', + destination: overlay, + }); + expect(interaction.action.type).toBe('open-overlay'); + if (interaction.action.type === 'open-overlay') { + expect(interaction.action.destination.id).toBe(overlay.id); + expect(interaction.action.position).toBe('center'); + } + }); + test('toggle-overlay interaction round-trips', (ctx) => { const overlay = board(ctx); const r = rect(ctx); @@ -116,6 +131,23 @@ describe('Interactions', () => { } }); + // animation is optional on close-overlay; omitting it closes with no transition. + test('close-overlay without an animation round-trips', (ctx) => { + const overlay = board(ctx); + const r = rect(ctx); + const interaction = r.addInteraction('click', { + type: 'close-overlay', + destination: overlay, + }); + expect(interaction.action.type).toBe('close-overlay'); + if (interaction.action.type === 'close-overlay') { + expect( + interaction.action.destination && interaction.action.destination.id, + ).toBe(overlay.id); + expect(interaction.action.animation).toBeUndefined(); + } + }); + test('previous-screen interaction round-trips', (ctx) => { const r = rect(ctx); const interaction = r.addInteraction('click', { type: 'previous-screen' }); @@ -134,6 +166,19 @@ describe('Interactions', () => { expect(interaction.delay).toBeCloseTo(1000, 0); }); + // A zero delay is a valid value (fires immediately), not an error. + test('after-delay accepts a zero delay', (ctx) => { + const dest = board(ctx); + const r = rect(ctx); + const interaction = r.addInteraction( + 'after-delay', + { type: 'navigate-to', destination: dest }, + 0, + ); + expect(interaction.trigger).toBe('after-delay'); + expect(interaction.delay).toBeCloseTo(0, 0); + }); + test('mouse-leave trigger is recorded', (ctx) => { // click / mouse-enter / after-delay are covered above; mouse-leave is the // remaining trigger. @@ -167,6 +212,20 @@ describe('Interactions', () => { expect(interaction.action.type).toBe('previous-screen'); }); + // The delay setter accepts zero (fires immediately) as a valid value. + test('delay setter accepts a zero value', (ctx) => { + const dest = board(ctx); + const r = rect(ctx); + const interaction = r.addInteraction( + 'after-delay', + { type: 'navigate-to', destination: dest }, + 1000, + ); + + interaction.delay = 0; + expect(interaction.delay).toBeCloseTo(0, 0); + }); + describe('Animations', () => { test('dissolve animation round-trips', (ctx) => { const dest = board(ctx); diff --git a/plugins/apps/plugin-api-test-suite/src/tests/layout.test.ts b/plugins/apps/plugin-api-test-suite/src/tests/layout.test.ts index f4f8aacc6e..21471365bc 100644 --- a/plugins/apps/plugin-api-test-suite/src/tests/layout.test.ts +++ b/plugins/apps/plugin-api-test-suite/src/tests/layout.test.ts @@ -65,6 +65,23 @@ describe('Layout', () => { expect(flex.leftPadding).toBeCloseTo(4, 0); }); + // Gap and padding setters accept fractional numbers, not just integers. + test('gaps and padding accept fractional values', (ctx) => { + const flex = board(ctx).addFlexLayout(); + flex.rowGap = 5.5; + flex.columnGap = 10.25; + flex.topPadding = 1.5; + flex.rightPadding = 2.25; + flex.bottomPadding = 3.75; + flex.leftPadding = 4.5; + expect(flex.rowGap).toBeCloseTo(5.5, 2); + expect(flex.columnGap).toBeCloseTo(10.25, 2); + expect(flex.topPadding).toBeCloseTo(1.5, 2); + expect(flex.rightPadding).toBeCloseTo(2.25, 2); + expect(flex.bottomPadding).toBeCloseTo(3.75, 2); + expect(flex.leftPadding).toBeCloseTo(4.5, 2); + }); + test('sizing round-trips', (ctx) => { const flex = board(ctx).addFlexLayout(); flex.horizontalSizing = 'fix'; @@ -176,6 +193,23 @@ describe('Layout', () => { expect(grid.columnGap).toBeCloseTo(9, 0); }); + // Gap and padding setters accept fractional numbers, not just integers. + test('gaps and padding accept fractional values', (ctx) => { + const grid = board(ctx).addGridLayout(); + grid.rowGap = 7.5; + grid.columnGap = 9.25; + grid.topPadding = 1.5; + grid.rightPadding = 2.75; + grid.bottomPadding = 3.25; + grid.leftPadding = 4.5; + expect(grid.rowGap).toBeCloseTo(7.5, 2); + expect(grid.columnGap).toBeCloseTo(9.25, 2); + expect(grid.topPadding).toBeCloseTo(1.5, 2); + expect(grid.rightPadding).toBeCloseTo(2.75, 2); + expect(grid.bottomPadding).toBeCloseTo(3.25, 2); + expect(grid.leftPadding).toBeCloseTo(4.5, 2); + }); + // Index boundaries — invalid indices must be rejected. test('addRowAtIndex with a negative index throws', (ctx) => { const grid = board(ctx).addGridLayout(); diff --git a/plugins/apps/plugin-api-test-suite/src/tests/shapes-geometry.test.ts b/plugins/apps/plugin-api-test-suite/src/tests/shapes-geometry.test.ts index 2402f29035..dfeae815f6 100644 --- a/plugins/apps/plugin-api-test-suite/src/tests/shapes-geometry.test.ts +++ b/plugins/apps/plugin-api-test-suite/src/tests/shapes-geometry.test.ts @@ -237,6 +237,21 @@ describe('Shapes', () => { expect(r.borderRadiusBottomRight).toBeCloseTo(3, 0); expect(r.borderRadiusBottomLeft).toBeCloseTo(4, 0); }); + + // Border radius setters accept fractional numbers, not just integers. + test('border radius accepts fractional values', (ctx) => { + const r = rect(ctx); + r.borderRadius = 4.5; + expect(r.borderRadius).toBeCloseTo(4.5, 2); + r.borderRadiusTopLeft = 1.25; + r.borderRadiusTopRight = 2.5; + r.borderRadiusBottomRight = 3.75; + r.borderRadiusBottomLeft = 0.5; + expect(r.borderRadiusTopLeft).toBeCloseTo(1.25, 2); + expect(r.borderRadiusTopRight).toBeCloseTo(2.5, 2); + expect(r.borderRadiusBottomRight).toBeCloseTo(3.75, 2); + expect(r.borderRadiusBottomLeft).toBeCloseTo(0.5, 2); + }); }); describe('Ordering', () => { diff --git a/plugins/apps/plugin-api-test-suite/src/tests/tokens.test.ts b/plugins/apps/plugin-api-test-suite/src/tests/tokens.test.ts index 1bfc15003e..8bd2e0f790 100644 --- a/plugins/apps/plugin-api-test-suite/src/tests/tokens.test.ts +++ b/plugins/apps/plugin-api-test-suite/src/tests/tokens.test.ts @@ -148,6 +148,19 @@ describe('Tokens', () => { theme.removeSet(set); }); + // addSet/removeSet also accept a token set id (string), not just a TokenSet. + test('addSet and removeSet accept a set id', async (ctx) => { + const cat = catalog(ctx); + const theme = cat.addTheme({ group: '', name: unique('theme') }); + const set = cat.addSet({ name: unique('set'), active: false }); + theme.addSet(set.id); + await sleep(300); + expect(theme.activeSets.length).toBeGreaterThan(0); + theme.removeSet(set.id); + await sleep(300); + expect(theme.activeSets.length).toBe(0); + }); + test('duplicate and remove a theme', (ctx) => { const theme = catalog(ctx).addTheme({ group: '', name: unique('theme') }); const dup = theme.duplicate();