Alonso Torres 7655f854a4
✨ Improved api test suite (#10533)
* 🐛 Fix plugins api issues

* 🐛 Fix problem with text weight

* 🐛 Fix api problems

* ✨ Updated plugins test suite

* 🐛 Referential integrity tests

* ✨ Add resetOverrides method

* 🐛 Fix plugins components structure poluting
2026-07-07 16:12:12 +02:00

169 lines
5.8 KiB
TypeScript

import { expect } from '../framework/expect';
import { describe, test } from '../framework/registry';
// Platform: user/session, context info, history, utils and markup.
describe('Platform', () => {
describe('User', () => {
test('currentUser exposes profile fields', (ctx) => {
const user = ctx.penpot.currentUser;
expect(typeof user.id).toBe('string');
expect(typeof user.name).toBe('string');
expect(typeof user.sessionId).toBe('string');
// avatarUrl and color may be undefined depending on the profile.
void user.avatarUrl;
void user.color;
});
test('activeUsers is an array', (ctx) => {
const users = ctx.penpot.activeUsers;
expect(Array.isArray(users)).toBe(true);
if (users.length > 0) {
expect(typeof users[0].id).toBe('string');
void users[0].position;
void users[0].zoom;
}
});
});
describe('Context info', () => {
test('version is a string', (ctx) => {
expect(typeof ctx.penpot.version).toBe('string');
});
test('theme is light or dark', (ctx) => {
expect(['light', 'dark']).toContain(ctx.penpot.theme);
});
// The whole suite is written against both flags being on (README "Runtime
// details"): invalid input throws and `children` is z-index ordered. Pin
// them so a runner-setup regression fails here instead of silently
// changing what dozens of tests mean.
test('runner pins naturalChildOrdering and throwValidationErrors on', (ctx) => {
expect(ctx.penpot.flags.naturalChildOrdering).toBe(true);
expect(ctx.penpot.flags.throwValidationErrors).toBe(true);
});
});
describe('History', () => {
test('undo block begin and finish wrap operations', (ctx) => {
const block = ctx.penpot.history.undoBlockBegin();
const rect = ctx.penpot.createRectangle();
ctx.board.appendChild(rect);
rect.name = 'in-undo-block';
ctx.penpot.history.undoBlockFinish(block);
expect(rect.name).toBe('in-undo-block');
});
// Edge cases.
test('finishing an unknown undo block is a no-op (not rejected)', (ctx) => {
// undoBlockFinish does not validate the block id; an unknown id is ignored
// rather than rejected.
expect(() =>
ctx.penpot.history.undoBlockFinish(Symbol('not-a-real-block')),
).not.toThrow();
});
test('nested undo blocks begin and finish in order', (ctx) => {
const outer = ctx.penpot.history.undoBlockBegin();
const rect = ctx.penpot.createRectangle();
ctx.board.appendChild(rect);
const inner = ctx.penpot.history.undoBlockBegin();
rect.name = 'nested';
ctx.penpot.history.undoBlockFinish(inner);
ctx.penpot.history.undoBlockFinish(outer);
expect(rect.name).toBe('nested');
});
});
describe('Utils', () => {
test('geometry center returns a point', (ctx) => {
const rect = ctx.penpot.createRectangle();
ctx.board.appendChild(rect);
rect.x = 0;
rect.y = 0;
rect.resize(100, 100);
const center = ctx.penpot.utils.geometry.center([rect]);
expect(center).not.toBeNull();
if (center) {
expect(center.x).toBeCloseTo(50, 0);
expect(center.y).toBeCloseTo(50, 0);
}
});
// Edge cases.
test('center of an empty array returns null', (ctx) => {
expect(ctx.penpot.utils.geometry.center([])).toBeNull();
});
test('center of two shapes sits at their midpoint', (ctx) => {
const a = ctx.penpot.createRectangle();
const b = ctx.penpot.createRectangle();
ctx.board.appendChild(a);
ctx.board.appendChild(b);
a.x = 0;
a.y = 0;
a.resize(100, 100);
b.x = 200;
b.y = 100;
b.resize(100, 100);
const center = ctx.penpot.utils.geometry.center([a, b]);
expect(center).not.toBeNull();
if (center) {
// a spans 0..100, b spans 200..300 → combined bounds 0..300 → centre 150.
expect(center.x).toBeCloseTo(150, 0);
expect(center.y).toBeCloseTo(100, 0);
}
});
test('types predicates identify shapes', (ctx) => {
const types = ctx.penpot.utils.types;
const rect = ctx.penpot.createRectangle();
const ellipse = ctx.penpot.createEllipse();
const text = ctx.penpot.createText('hi');
const path = ctx.penpot.createPath();
ctx.board.appendChild(rect);
ctx.board.appendChild(ellipse);
ctx.board.appendChild(path);
if (text) ctx.board.appendChild(text);
expect(types.isRectangle(rect)).toBe(true);
expect(types.isEllipse(ellipse)).toBe(true);
expect(types.isPath(path)).toBe(true);
expect(types.isBoard(ctx.board)).toBe(true);
if (text) {
expect(types.isText(text)).toBe(true);
}
// Non-matching predicates should be falsy.
expect(types.isGroup(rect)).toBeFalsy();
expect(types.isBool(rect)).toBeFalsy();
expect(types.isMask(rect)).toBeFalsy();
expect(types.isSVG(rect)).toBeFalsy();
expect(types.isVariantContainer(rect)).toBeFalsy();
});
});
describe('Markup', () => {
test('generateMarkup returns html', (ctx) => {
const rect = ctx.penpot.createRectangle();
ctx.board.appendChild(rect);
const markup = ctx.penpot.generateMarkup([rect]);
expect(typeof markup).toBe('string');
});
test('generateMarkup can target svg', (ctx) => {
const rect = ctx.penpot.createRectangle();
ctx.board.appendChild(rect);
const svg = ctx.penpot.generateMarkup([rect], { type: 'svg' });
expect(typeof svg).toBe('string');
});
test('generateStyle returns css', (ctx) => {
const rect = ctx.penpot.createRectangle();
ctx.board.appendChild(rect);
const styles = ctx.penpot.generateStyle([rect]);
expect(typeof styles).toBe('string');
});
});
});