2026-07-09 18:00:26 +02:00

188 lines
7.3 KiB
TypeScript

import { expect } from '../framework/expect';
import { describe, test } from '../framework/registry';
// Plugin data and local storage.
describe('Plugin data', () => {
test('plugin data round-trips on a shape', (ctx) => {
const rect = ctx.penpot.createRectangle();
ctx.board.appendChild(rect);
rect.setPluginData('exampleKey', 'exampleValue');
expect(rect.getPluginData('exampleKey')).toBe('exampleValue');
expect(rect.getPluginDataKeys()).toContain('exampleKey');
});
test('shared plugin data round-trips on a shape', (ctx) => {
const rect = ctx.penpot.createRectangle();
ctx.board.appendChild(rect);
rect.setSharedPluginData('ns', 'sharedKey', 'sharedValue');
expect(rect.getSharedPluginData('ns', 'sharedKey')).toBe('sharedValue');
expect(rect.getSharedPluginDataKeys('ns')).toContain('sharedKey');
});
test('plugin data round-trips on the file', (ctx) => {
const file = ctx.penpot.currentFile;
expect(file).not.toBeNull();
if (file) {
file.setPluginData('fileKey', 'fileValue');
expect(file.getPluginData('fileKey')).toBe('fileValue');
expect(file.getPluginDataKeys()).toContain('fileKey');
}
});
test('shared plugin data round-trips on the file', (ctx) => {
const file = ctx.penpot.currentFile;
expect(file).not.toBeNull();
if (file) {
file.setSharedPluginData('ns', 'fileShared', 'fileSharedValue');
expect(file.getSharedPluginData('ns', 'fileShared')).toBe(
'fileSharedValue',
);
expect(file.getSharedPluginDataKeys('ns')).toContain('fileShared');
}
});
test('plugin data round-trips on a page', (ctx) => {
const page = ctx.penpot.currentPage;
expect(page).not.toBeNull();
if (page) {
page.setPluginData('pageKey', 'pageValue');
expect(page.getPluginData('pageKey')).toBe('pageValue');
expect(page.getPluginDataKeys()).toContain('pageKey');
}
});
test('shared plugin data round-trips on a page', (ctx) => {
const page = ctx.penpot.currentPage;
expect(page).not.toBeNull();
if (page) {
page.setSharedPluginData('ns', 'pageShared', 'pageSharedValue');
expect(page.getSharedPluginData('ns', 'pageShared')).toBe(
'pageSharedValue',
);
expect(page.getSharedPluginDataKeys('ns')).toContain('pageShared');
}
});
// ---------------------------------------------------------------------------
// Edge cases. "fail" tests assert invalid keys/values are
// rejected; "success" tests cover multi-key listing, overwrite, large values,
// missing keys and local/shared isolation.
// ---------------------------------------------------------------------------
test('setPluginData with an empty key is accepted (currently unvalidated)', (ctx) => {
// An empty key is not rejected; this pins the current lenient behaviour
// (a candidate for future hardening): the value is stored and retrievable
// under the empty key like any other.
const rect = ctx.penpot.createRectangle();
ctx.board.appendChild(rect);
expect(() => rect.setPluginData('', 'value')).not.toThrow();
expect(rect.getPluginData('')).toBe('value');
expect(rect.getPluginDataKeys()).toContain('');
});
// Keys are opaque strings: no case normalization is applied. A camelCase key
// and its kebab-case spelling are distinct entries that each round-trip
// independently. Pins that behaviour so a future key-normalization regression
// (reported as camelCase keys "behaving incorrectly") would be caught here.
test('camelCase and kebab-case keys are distinct and both round-trip', (ctx) => {
const rect = ctx.penpot.createRectangle();
ctx.board.appendChild(rect);
rect.setPluginData('myKey', 'camel');
rect.setPluginData('my-key', 'kebab');
expect(rect.getPluginData('myKey')).toBe('camel');
expect(rect.getPluginData('my-key')).toBe('kebab');
const keys = rect.getPluginDataKeys();
expect(keys).toContain('myKey');
expect(keys).toContain('my-key');
});
// Same guarantee at the file-data storage location (a distinct code path from
// shape/page objects), covering a camelCase key on the file itself.
test('a camelCase key round-trips on the file', (ctx) => {
const file = ctx.penpot.currentFile;
expect(file).not.toBeNull();
if (file) {
file.setPluginData('camelCaseFileKey', 'value');
expect(file.getPluginData('camelCaseFileKey')).toBe('value');
expect(file.getPluginDataKeys()).toContain('camelCaseFileKey');
}
});
// camelCase is likewise preserved in the shared namespace and the shared key.
test('camelCase shared namespace and key round-trip', (ctx) => {
const rect = ctx.penpot.createRectangle();
ctx.board.appendChild(rect);
rect.setSharedPluginData('myNamespace', 'myKey', 'camel');
rect.setSharedPluginData('myNamespace', 'my-key', 'kebab');
expect(rect.getSharedPluginData('myNamespace', 'myKey')).toBe('camel');
expect(rect.getSharedPluginData('myNamespace', 'my-key')).toBe('kebab');
const keys = rect.getSharedPluginDataKeys('myNamespace');
expect(keys).toContain('myKey');
expect(keys).toContain('my-key');
});
test('setPluginData with a non-string value throws', (ctx) => {
const rect = ctx.penpot.createRectangle();
ctx.board.appendChild(rect);
expect(() => rect.setPluginData('key', 123 as unknown as string)).toThrow();
});
test('multiple keys round-trip and are all listed', (ctx) => {
const rect = ctx.penpot.createRectangle();
ctx.board.appendChild(rect);
rect.setPluginData('a', '1');
rect.setPluginData('b', '2');
rect.setPluginData('c', '3');
expect(rect.getPluginData('a')).toBe('1');
expect(rect.getPluginData('b')).toBe('2');
expect(rect.getPluginData('c')).toBe('3');
const keys = rect.getPluginDataKeys();
expect(keys).toContain('a');
expect(keys).toContain('b');
expect(keys).toContain('c');
});
test('overwriting a key replaces its value', (ctx) => {
const rect = ctx.penpot.createRectangle();
ctx.board.appendChild(rect);
rect.setPluginData('k', 'first');
rect.setPluginData('k', 'second');
expect(rect.getPluginData('k')).toBe('second');
});
test('a large value round-trips', (ctx) => {
const rect = ctx.penpot.createRectangle();
ctx.board.appendChild(rect);
const big = 'x'.repeat(10000);
rect.setPluginData('big', big);
expect(rect.getPluginData('big')).toBe(big);
});
test('reading a missing key is falsy', (ctx) => {
const rect = ctx.penpot.createRectangle();
ctx.board.appendChild(rect);
expect(rect.getPluginData('never-set')).toBeFalsy();
});
test('local and shared plugin data are isolated', (ctx) => {
const rect = ctx.penpot.createRectangle();
ctx.board.appendChild(rect);
rect.setPluginData('k', 'local');
rect.setSharedPluginData('ns', 'k', 'shared');
expect(rect.getPluginData('k')).toBe('local');
expect(rect.getSharedPluginData('ns', 'k')).toBe('shared');
});
});
describe('Local storage', () => {
test('set, get, keys and remove an item', (ctx) => {
const ls = ctx.penpot.localStorage;
ls.setItem('plugin-key', 'plugin-value');
expect(ls.getItem('plugin-key')).toBe('plugin-value');
expect(ls.getKeys()).toContain('plugin-key');
ls.removeItem('plugin-key');
expect(ls.getItem('plugin-key')).toBeFalsy();
});
});