@sketch-hq/sketch-assistant-utils
Version:
Utility functions and types for Sketch Assistants.
257 lines (256 loc) • 13.6 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const sketch_assistant_types_1 = require("@sketch-hq/sketch-assistant-types");
const sketch_file_1 = require("@sketch-hq/sketch-file");
const path_1 = require("path");
const __1 = require("..");
const get_image_metadata_1 = require("../../get-image-metadata");
const process_1 = require("../../process");
const test_helpers_1 = require("../../test-helpers");
/**
* Test helper function for creating a utils object.
*/
const createUtils = (filepath = './empty.sketch', assistant = test_helpers_1.createAssistantDefinition({
config: test_helpers_1.createAssistantConfig({ rules: { foo: { active: true } } }),
rules: [test_helpers_1.createRule({ name: 'foo' })],
}), ruleName = 'foo', violations = [], ignoreConfig = { pages: [], assistants: {} }, timeoutToken = { timedOut: false }) => __awaiter(void 0, void 0, void 0, function* () {
const op = { cancelled: false };
const file = yield sketch_file_1.fromFile(path_1.resolve(__dirname, filepath));
const processedFile = yield process_1.process(file, op);
return {
processedFile,
violations,
utils: __1.createRuleUtilsCreator(processedFile, violations, assistant, op, get_image_metadata_1.getImageMetadata, ignoreConfig)(ruleName, timeoutToken),
};
});
describe('createIterable', () => {
test('can yield file format objects', () => {
const iterable = __1.createIterable([test_helpers_1.createDummyRect(), test_helpers_1.createDummyRect()], {
cancelled: false,
}, { timedOut: false }, []);
expect([...iterable].map((item) => item._class)).toMatchInlineSnapshot(`
Array [
"rect",
"rect",
]
`);
});
test('can short-circuit when operation is cancelled', () => {
const iterable = __1.createIterable([test_helpers_1.createDummyRect(), test_helpers_1.createDummyRect()], {
cancelled: true,
}, { timedOut: false }, []);
expect([...iterable]).toHaveLength(0);
});
});
describe('createIterableObjectCache', () => {
test('works with an empty cache', () => {
const cache = process_1.createEmptyObjectCache();
const iterableCache = __1.createIterableObjectCache(cache, { cancelled: false }, { timedOut: false }, []);
expect([...iterableCache.text]).toHaveLength(0);
});
test('for..of loops work with type safety', () => {
const cache = process_1.createEmptyObjectCache();
cache[sketch_assistant_types_1.FileFormat.ClassValue.Rect] = [test_helpers_1.createDummyRect(), test_helpers_1.createDummyRect()];
const iterableCache = __1.createIterableObjectCache(cache, { cancelled: false }, { timedOut: false }, []);
for (const rect of iterableCache.rect) {
expect(rect._class).toBe('rect');
}
});
test('objects can be ignored by id', () => {
const cache = process_1.createEmptyObjectCache();
cache[sketch_assistant_types_1.FileFormat.ClassValue.Swatch] = [test_helpers_1.createDummySwatch('1'), test_helpers_1.createDummySwatch('2')];
const iterableCache = __1.createIterableObjectCache(cache, { cancelled: false }, { timedOut: false }, ['2']);
const result = [...iterableCache.swatch];
expect(result).toHaveLength(1);
expect(result[0]).toHaveProperty('do_objectID', '1');
});
});
describe('createRuleUtilsCreator', () => {
test('throws when named rule not present in the assistant', () => __awaiter(void 0, void 0, void 0, function* () {
try {
yield createUtils('./empty.sketch', test_helpers_1.createAssistantDefinition({
config: test_helpers_1.createAssistantConfig({ rules: { foo: { active: true } } }),
rules: [],
}), 'foo');
}
catch (error) {
expect(error).toMatchInlineSnapshot(`[RuleNotFoundError: Rule "foo" not found on assistant "dummy-assistant"]`);
}
}));
});
describe('getOption', () => {
test('can get an option', () => __awaiter(void 0, void 0, void 0, function* () {
expect.assertions(1);
const { utils } = yield createUtils('./empty.sketch', test_helpers_1.createAssistantDefinition({
config: test_helpers_1.createAssistantConfig({ rules: { foo: { active: true } } }),
rules: [test_helpers_1.createRule({ name: 'foo' })],
}), 'foo');
expect(utils.getOption('active')).toBeTruthy();
}));
test('throws when option missing in config', () => __awaiter(void 0, void 0, void 0, function* () {
expect.assertions(1);
const { utils } = yield createUtils('./empty.sketch', test_helpers_1.createAssistantDefinition({
config: test_helpers_1.createAssistantConfig({ rules: { foo: { active: true } } }),
rules: [test_helpers_1.createRule({ name: 'foo' })],
}), 'foo');
try {
utils.getOption('fluxCapacitance');
}
catch (error) {
expect(error).toMatchInlineSnapshot(`[InvalidRuleConfigError: Invalid configuration found for rule "foo" on assistant "dummy-assistant": Option "fluxCapacitance" not found in assistant configuration]`);
}
}));
test("throws when option in config doesn't match rule's schema", () => __awaiter(void 0, void 0, void 0, function* () {
expect.assertions(1);
const { utils } = yield createUtils('./empty.sketch', test_helpers_1.createAssistantDefinition({
config: test_helpers_1.createAssistantConfig({ rules: { foo: { active: true, custom: '1' } } }),
rules: [
test_helpers_1.createRule({
name: 'foo',
getOptions: (helpers) => [
helpers.numberOption({ name: 'custom', title: '', description: '' }),
],
}),
],
}), 'foo');
try {
utils.getOption('custom');
}
catch (error) {
expect(error).toMatchInlineSnapshot(`[InvalidRuleConfigError: Invalid configuration found for rule "foo" on assistant "dummy-assistant": "/custom" must be number]`);
}
}));
});
describe('getObjectParents', () => {
test('returns parent objects to the root', () => __awaiter(void 0, void 0, void 0, function* () {
const { utils, processedFile } = yield createUtils('./empty.sketch', test_helpers_1.createAssistantDefinition({
config: test_helpers_1.createAssistantConfig({ rules: { foo: { active: true } } }),
rules: [test_helpers_1.createRule({ name: 'foo' })],
}), 'foo');
const style = processedFile.original.contents.document.pages[0].style;
const parents = utils.getObjectParents(style);
expect(parents[0]).toBe(processedFile.original.contents);
expect(parents[1]).toBe(processedFile.original.contents.document);
expect(parents[2]).toBe(processedFile.original.contents.document.pages);
expect(parents[3]).toBe(processedFile.original.contents.document.pages[0]);
}));
});
describe('evalPointer', () => {
test('can resolve file objects by json pointer', () => __awaiter(void 0, void 0, void 0, function* () {
const { utils, processedFile } = yield createUtils('./empty.sketch', test_helpers_1.createAssistantDefinition({
config: test_helpers_1.createAssistantConfig({ rules: { foo: { active: true, custom: '1' } } }),
rules: [test_helpers_1.createRule({ name: 'foo' })],
}), 'foo');
const page = utils.evalPointer('/document/pages/0');
expect(page).toBe(processedFile.original.contents.document.pages[0]);
}));
});
describe('getObjectParent', () => {
test('can resolve file object parents by json pointer', () => __awaiter(void 0, void 0, void 0, function* () {
const { utils, processedFile } = yield createUtils();
const parent = utils.getObjectParent(processedFile.original.contents.document);
expect(parent).toBe(processedFile.original.contents);
}));
});
describe('objects', () => {
test('exposes iterators for file objects', () => __awaiter(void 0, void 0, void 0, function* () {
const { utils } = yield createUtils();
const pages = [...utils.objects.page];
expect(pages).toHaveLength(1);
expect(pages[0]._class).toBe('page');
}));
});
describe('isObjectIgnored', () => {
test('returns ignore status of object and rule combinations', () => __awaiter(void 0, void 0, void 0, function* () {
const swatch1 = test_helpers_1.createDummySwatch('1');
const swatch2 = test_helpers_1.createDummySwatch('2');
const { utils } = yield createUtils('./empty.sketch', test_helpers_1.createAssistantDefinition({
name: 'foo',
rules: [test_helpers_1.createRule({ name: 'bar' })],
}), 'bar', [], { pages: [], assistants: { foo: { rules: { bar: { objects: ['1'] } } } } });
expect(utils.isObjectIgnored(swatch1)).toBe(true);
expect(utils.isObjectIgnored(swatch2)).toBe(false);
}));
});
describe('foreignObjects', () => {
test('exposes iterators for foreign file objects', () => __awaiter(void 0, void 0, void 0, function* () {
const { utils } = yield createUtils('./foreign-symbol.sketch');
const symbols = [...utils.foreignObjects.symbolMaster];
expect(symbols).toHaveLength(2);
expect(symbols[0]._class).toBe(sketch_assistant_types_1.FileFormat.ClassValue.SymbolMaster);
}));
});
describe('report', () => {
test('can report violations', () => __awaiter(void 0, void 0, void 0, function* () {
const { utils, violations } = yield createUtils();
utils.report("Something isn't right", ...utils.objects.page);
expect(violations).toHaveLength(1);
expect(violations[0]).toMatchInlineSnapshot(`
Object {
"assistantName": "dummy-assistant",
"message": "Something isn't right",
"objects": Array [
Object {
"class": "page",
"id": "9AD22B94-A05B-4F49-8EDD-A38D62BD6181",
"name": "Page 1",
"pointer": "/document/pages/0",
},
],
"ruleName": "foo",
"severity": 3,
}
`);
}));
});
describe('objectHash', () => {
test('objects with the same contents produce the same hash', () => __awaiter(void 0, void 0, void 0, function* () {
const { utils } = yield createUtils();
expect(utils.objectHash({ foo: 'bar' })).toBe(utils.objectHash({ foo: 'bar' }));
}));
test('property order does not matter', () => __awaiter(void 0, void 0, void 0, function* () {
const { utils } = yield createUtils();
expect(utils.objectHash({ foo: 'bar', baz: 'qux' })).toBe(utils.objectHash({ baz: 'qux', foo: 'bar' }));
}));
test('do_objectID values are ignored by default', () => __awaiter(void 0, void 0, void 0, function* () {
const { utils } = yield createUtils();
expect(utils.objectHash({ foo: 'bar', do_objectID: '1' })).toBe(utils.objectHash({ foo: 'bar', do_objectID: '2' }));
}));
});
describe('styleEq', () => {
test('can test style objects for equality', () => __awaiter(void 0, void 0, void 0, function* () {
const { utils } = yield createUtils('./empty.sketch', test_helpers_1.createAssistantDefinition({
config: test_helpers_1.createAssistantConfig({ rules: { foo: { active: true } } }),
rules: [test_helpers_1.createRule({ name: 'foo' })],
}), 'foo');
const file1 = yield sketch_file_1.fromFile(path_1.resolve(__dirname, './simple-style.sketch'));
const file2 = yield sketch_file_1.fromFile(path_1.resolve(__dirname, './simple-style.sketch'));
const style1 = file1.contents.document.pages[0].layers[0].style;
const style2 = file2.contents.document.pages[0].layers[0].style;
if (style2 && style2.blur)
style2.blur.center = '{0.6, 0.5}';
expect(utils.styleEq(style1, style1)).toBe(true);
expect(utils.styleEq(style1, style2)).toBe(false);
}));
test('can test text style objects for equality', () => __awaiter(void 0, void 0, void 0, function* () {
const { utils } = yield createUtils();
const file1 = yield sketch_file_1.fromFile(path_1.resolve(__dirname, './simple-textstyle.sketch'));
const file2 = yield sketch_file_1.fromFile(path_1.resolve(__dirname, './simple-textstyle.sketch'));
const style1 = file1.contents.document.pages[0].layers[0].style;
const style2 = file2.contents.document.pages[0].layers[0].style;
expect(utils.textStyleEq(style1, style1)).toBe(true);
if (style2 && style2.textStyle) {
style2.textStyle.encodedAttributes.underlineStyle = 1;
}
expect(utils.textStyleEq(style1, style2)).toBe(false);
}));
});