@sketch-hq/sketch-assistant-utils
Version:
Utility functions and types for Sketch Assistants.
360 lines (359 loc) • 14.9 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.
*/
const testRunMultiple = ({ assistants = { 'dummy-assistant': test_helpers_1.createAssistant() }, cancelToken = { cancelled: false }, getImageMetadata = get_image_metadata_1.getImageMetadata, env = { locale: 'en', runtime: sketch_assistant_types_1.AssistantRuntime.Node }, ignore = { pages: [], assistants: {} }, timeBudgets = { totalMs: Infinity, minRuleTimeoutMs: 0, maxRuleTimeoutMs: Infinity }, }, filename = './empty.sketch') => __awaiter(void 0, void 0, void 0, function* () {
const file = yield sketch_file_1.fromFile(path_1.resolve(__dirname, filename));
const processedFile = yield process_1.process(file, cancelToken);
return yield __1.runMultipleAssistants({
assistants,
processedFile,
cancelToken,
getImageMetadata,
env,
ignore,
timeBudgets,
});
});
test('rejects when no assistants', () => __awaiter(void 0, void 0, void 0, function* () {
yield expect(testRunMultiple({ assistants: {} })).rejects.toHaveProperty('code', 'runError');
}));
test('outputs an error result for a malformed assistant package', () => __awaiter(void 0, void 0, void 0, function* () {
const promise = testRunMultiple({
assistants: {
// @ts-ignore
foo: {},
},
});
yield expect(promise).resolves.toHaveProperty('assistants.foo.code', 'error');
}));
test('outputs an error result for assistant name mismatch', () => __awaiter(void 0, void 0, void 0, function* () {
const promise = testRunMultiple({
assistants: {
foo: test_helpers_1.createAssistant({ name: 'bar' }),
},
});
yield expect(promise).resolves.toHaveProperty('assistants.foo.code', 'error');
}));
test('bails early if cancelled', () => __awaiter(void 0, void 0, void 0, function* () {
yield expect(testRunMultiple({ cancelToken: { cancelled: true } })).rejects.toHaveProperty('code', 'cancelled');
}));
test('works with a no-op assistant', () => __awaiter(void 0, void 0, void 0, function* () {
yield expect(testRunMultiple({})).resolves.toBeTruthy();
}));
test('works with assistants exported as ESM defaults', () => __awaiter(void 0, void 0, void 0, function* () {
const assistants = {
'assistant-2': {
__esModule: true,
default: [
{
__esModule: true,
default: test_helpers_1.createAssistant({ name: 'assistant-1' }),
},
{
__esModule: true,
default: test_helpers_1.createAssistant({ name: 'assistant-2' }),
},
],
},
};
const output = yield testRunMultiple({ assistants });
expect(output.assistants['assistant-2'].code).toBe('success');
}));
test('can generate violations', () => __awaiter(void 0, void 0, void 0, function* () {
const assistants = {
'dummy-assistant': test_helpers_1.createAssistant({
rules: [
test_helpers_1.createRule({
name: 'rule',
rule: (ruleContext) => __awaiter(void 0, void 0, void 0, function* () {
ruleContext.utils.report('Subspace anomaly detected');
}),
}),
],
config: test_helpers_1.createAssistantConfig({
rules: {
rule: { active: true },
},
}),
}),
};
const output = yield testRunMultiple({ assistants });
const res = output.assistants['dummy-assistant'];
expect.assertions(3);
expect(res).toHaveProperty('code', 'success');
expect(res).toHaveProperty('result.passed', false);
if (res.code === 'success') {
expect(res.result.violations).toHaveLength(1);
}
}));
test('will pass an assistant if violations not error-level', () => __awaiter(void 0, void 0, void 0, function* () {
const assistants = {
'dummy-assistant': test_helpers_1.createAssistant({
rules: [
test_helpers_1.createRule({
name: 'rule',
rule: (ruleContext) => __awaiter(void 0, void 0, void 0, function* () {
ruleContext.utils.report('Subspace anomaly detected');
}),
}),
],
config: test_helpers_1.createAssistantConfig({
rules: {
rule: { active: true, severity: sketch_assistant_types_1.ViolationSeverity.info },
},
}),
}),
};
const output = yield testRunMultiple({ assistants });
const res = output.assistants['dummy-assistant'];
expect.assertions(3);
expect(res).toHaveProperty('code', 'success');
expect(res).toHaveProperty('result.passed', true);
if (res.code === 'success') {
expect(res.result.violations).toHaveLength(1);
}
}));
test('can generate rule errors', () => __awaiter(void 0, void 0, void 0, function* () {
const assistants = {
'dummy-assistant': test_helpers_1.createAssistant({
rules: [
test_helpers_1.createRule({
name: 'rule',
rule: () => __awaiter(void 0, void 0, void 0, function* () {
throw new Error();
}),
}),
],
config: test_helpers_1.createAssistantConfig({
rules: {
rule: { active: true, severity: sketch_assistant_types_1.ViolationSeverity.warn },
},
}),
}),
};
const output = yield testRunMultiple({ assistants });
const res = output.assistants['dummy-assistant'];
expect.assertions(2);
if (res.code === 'success') {
expect(res.result.violations).toHaveLength(0);
expect(res.result.ruleErrors).toHaveLength(1);
}
}));
test('generates rule errors when ignored objects are reported', () => __awaiter(void 0, void 0, void 0, function* () {
const assistants = {
'dummy-assistant': test_helpers_1.createAssistant({
rules: [
test_helpers_1.createRule({
name: 'rule',
rule: (context) => __awaiter(void 0, void 0, void 0, function* () {
context.utils.report('', context.file.original.contents.document.pages[0]);
}),
}),
],
config: test_helpers_1.createAssistantConfig({
rules: {
rule: { active: true },
},
}),
}),
};
const output = yield testRunMultiple({
assistants,
ignore: {
pages: [],
assistants: {
'dummy-assistant': {
rules: { rule: { objects: ['9AD22B94-A05B-4F49-8EDD-A38D62BD6181'] } },
},
},
},
});
const res = output.assistants['dummy-assistant'];
expect.assertions(2);
if (res.code === 'success') {
expect(res.result.violations).toHaveLength(0);
expect(res.result.ruleErrors).toHaveLength(1);
}
}));
test.only('budget can be used to timeout rules', () => __awaiter(void 0, void 0, void 0, function* () {
const assistants = {
'dummy-assistant': test_helpers_1.createAssistant({
rules: [
test_helpers_1.createRule({
name: 'rule',
rule: () => __awaiter(void 0, void 0, void 0, function* () {
yield new Promise((resolve) => setTimeout(resolve, 100));
}),
}),
],
config: test_helpers_1.createAssistantConfig({
rules: {
rule: { active: true },
},
}),
}),
};
const output = yield testRunMultiple({
assistants,
timeBudgets: {
totalMs: Infinity,
minRuleTimeoutMs: 0,
maxRuleTimeoutMs: 10,
},
});
const res = output.assistants['dummy-assistant'];
expect.assertions(2);
if (res.code === 'success') {
expect(res.result.violations).toHaveLength(0);
expect(res.result.ruleErrors).toHaveLength(1);
}
}));
test('can run mulitple assistants', () => __awaiter(void 0, void 0, void 0, function* () {
const assistants = {
'dummy-assistant-1': test_helpers_1.createAssistant({
name: 'dummy-assistant-1',
rules: [
test_helpers_1.createRule({
name: 'dummy-assistant-1/rule',
rule: (ruleContext) => __awaiter(void 0, void 0, void 0, function* () {
ruleContext.utils.report('');
}),
}),
],
config: test_helpers_1.createAssistantConfig({
rules: {
'dummy-assistant-1/rule': { active: true },
},
}),
}),
'dummy-assistant-2': test_helpers_1.createAssistant({
name: 'dummy-assistant-2',
rules: [
test_helpers_1.createRule({
name: 'dummy-assistant-2/rule',
rule: (ruleContext) => __awaiter(void 0, void 0, void 0, function* () {
ruleContext.utils.report('');
}),
}),
],
config: test_helpers_1.createAssistantConfig({
rules: {
'dummy-assistant-2/rule': { active: true },
},
}),
}),
};
const output = yield testRunMultiple({ assistants });
const res1 = output.assistants['dummy-assistant-1'];
const res2 = output.assistants['dummy-assistant-2'];
expect.assertions(4);
if (res1.code === 'success') {
expect(res1.result.violations).toHaveLength(1);
expect(res1.result.ruleErrors).toHaveLength(0);
}
if (res2.code === 'success') {
expect(res2.result.violations).toHaveLength(1);
expect(res2.result.ruleErrors).toHaveLength(0);
}
}));
test('can be internationalized', () => __awaiter(void 0, void 0, void 0, function* () {
const assistant = (env) => __awaiter(void 0, void 0, void 0, function* () {
return ({
name: 'dummy-assistant',
rules: [
test_helpers_1.createRule({
name: 'rule',
rule: (ruleContext) => __awaiter(void 0, void 0, void 0, function* () {
ruleContext.utils.report(env.locale === 'zh-Hans' ? '你好世界' : 'Hello world');
}),
}),
],
config: {
rules: {
rule: { active: true },
},
},
});
});
const assistants = {
'dummy-assistant': assistant,
};
const zhOutput = yield testRunMultiple({
assistants,
env: { runtime: sketch_assistant_types_1.AssistantRuntime.Node, locale: 'zh-Hans' },
});
const zhRes = zhOutput.assistants['dummy-assistant'];
const enOutput = yield testRunMultiple({
assistants,
env: { runtime: sketch_assistant_types_1.AssistantRuntime.Node, locale: 'en' },
});
const enRes = enOutput.assistants['dummy-assistant'];
expect.assertions(2);
if (zhRes.code === 'success' && enRes.code === 'success') {
expect(zhRes.result.violations[0].message).toBe('你好世界');
expect(enRes.result.violations[0].message).toBe('Hello world');
}
}));
test('prunes missing assistants from ignore data', () => __awaiter(void 0, void 0, void 0, function* () {
const { ignore } = yield testRunMultiple({
ignore: {
pages: [],
assistants: { 'missing-assistant': { rules: {} }, 'dummy-assistant': { rules: {} } },
},
});
expect(ignore.assistants).not.toHaveProperty('missing-assistant');
expect(ignore.assistants).toHaveProperty('dummy-assistant');
}));
test('prunes missing rules from ignore data', () => __awaiter(void 0, void 0, void 0, function* () {
const { ignore } = yield testRunMultiple({
assistants: {
'dummy-assistant': test_helpers_1.createAssistant({ rules: [test_helpers_1.createRule({ name: 'rule' })] }),
},
ignore: {
pages: [],
assistants: {
'dummy-assistant': { rules: { rule: { allObjects: true }, missing: { allObjects: true } } },
},
},
});
expect(ignore.assistants['dummy-assistant'].rules).toHaveProperty('rule');
expect(ignore.assistants['dummy-assistant'].rules).not.toHaveProperty('missing');
}));
test('prunes missing objects from ignore data', () => __awaiter(void 0, void 0, void 0, function* () {
const { ignore } = yield testRunMultiple({
assistants: {
'dummy-assistant': test_helpers_1.createAssistant({ rules: [test_helpers_1.createRule({ name: 'rule' })] }),
},
ignore: {
pages: [],
assistants: {
'dummy-assistant': {
rules: { rule: { objects: ['9AD22B94-A05B-4F49-8EDD-A38D62BD6181', 'missing-id'] } },
},
},
},
});
expect.assertions(2);
if ('objects' in ignore.assistants['dummy-assistant'].rules.rule) {
expect(ignore.assistants['dummy-assistant'].rules.rule.objects).toContain('9AD22B94-A05B-4F49-8EDD-A38D62BD6181');
expect(ignore.assistants['dummy-assistant'].rules.rule.objects).not.toContain('missing-id');
}
}));