Skip to content

Commit

Permalink
feat: implement task system
Browse files Browse the repository at this point in the history
  • Loading branch information
PainterPuppets committed Nov 23, 2022
1 parent 989f4eb commit 463e7de
Show file tree
Hide file tree
Showing 16 changed files with 5,390 additions and 1,998 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ node_modules/
**/lib

.idea
yarn.lock
6,636 changes: 4,641 additions & 1,995 deletions package-lock.json

Large diffs are not rendered by default.

118 changes: 118 additions & 0 deletions packages/core/__tests__/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { describe, it } from '@jest/globals';

import { applyErrorMsgTemplate } from '../src/errors';
import { ERRORS } from '../src/errors-list';
import { expect } from '@jest/globals';
import { KuaiError } from '../src/errors';
import { ErrorDescriptor } from '../src/errors-list';

describe('applyErrorMessageTemplate', () => {
describe('Variable names', () => {
it('Should reject invalid variable names', () => {
expectKuaiError(() => applyErrorMsgTemplate('', { '1': 1 }), ERRORS.INTERNAL.TEMPLATE_INVALID_VARIABLE_NAME);

expectKuaiError(() => applyErrorMsgTemplate('', { 'asd%': 1 }), ERRORS.INTERNAL.TEMPLATE_INVALID_VARIABLE_NAME);

expectKuaiError(
() => applyErrorMsgTemplate('', { 'asd asd': 1 }),
ERRORS.INTERNAL.TEMPLATE_INVALID_VARIABLE_NAME,
);
});
});

describe('Values', () => {
it("Should't contain variable tags", () => {
expectKuaiError(
() => applyErrorMsgTemplate('%tag%', { tag: '%value%' }),
ERRORS.INTERNAL.TEMPLATE_VALUE_CONTAINS_VARIABLE_TAG,
);

expectKuaiError(
() => applyErrorMsgTemplate('%tag%', { tag: '%q1%' }),
ERRORS.INTERNAL.TEMPLATE_VALUE_CONTAINS_VARIABLE_TAG,
);

expectKuaiError(
() => applyErrorMsgTemplate('%tag%', { tag: '%%' }),
ERRORS.INTERNAL.TEMPLATE_VALUE_CONTAINS_VARIABLE_TAG,
);
});
});

describe('Replacements', () => {
describe('Missing variable tag', () => {
it('Should fail if a viable tag is missing', () => {
expectKuaiError(() => applyErrorMsgTemplate('', { asd: '123' }), ERRORS.INTERNAL.TEMPLATE_VARIABLE_TAG_MISSING);
});
});

describe('Missing variable', () => {
it('Should work, leaving the variable tag', () => {
expect(applyErrorMsgTemplate('%asd% %fgh%', { asd: '123' })).toBe('123 %fgh%');
});
});

describe('String values', () => {
it('Should replace variable tags for the values', () => {
expect(applyErrorMsgTemplate('asd %asd% 123 %asd%', { asd: 'r' })).toBe('asd r 123 r');
});
});

describe('Non-string values', () => {
it('Should replace undefined values for undefined', () => {
expect(applyErrorMsgTemplate('asd %asd% 123 %asd%', { asd: undefined })).toBe('asd undefined 123 undefined');
});

it('Should replace null values for null', () => {
expect(applyErrorMsgTemplate('asd %asd% 123 %asd%', { asd: null })).toBe('asd null 123 null');
});
});
});
});

export function expectKuaiError(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
f: () => any,
errorDescriptor: ErrorDescriptor,
errorMessage?: string | RegExp,
): void {
try {
f();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
expect(error).toBeInstanceOf(KuaiError);
_assertKuaiError(error, errorDescriptor, errorMessage);
return;
}

throw new Error(`KuaiError code ${errorDescriptor.code} expected, but no Error was thrown`);
}

export async function expectKuaiErrorAsync(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
f: () => Promise<any>,
errorDescriptor: ErrorDescriptor,
errorMessage?: string | RegExp,
): Promise<void> {
try {
await f();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
expect(error).toBeInstanceOf(KuaiError);
_assertKuaiError(error, errorDescriptor, errorMessage);
return;
}

throw new Error(`KuaiError code ${errorDescriptor.code} expected, but no Error was thrown`);
}

function _assertKuaiError(error: KuaiError, errorDescriptor: ErrorDescriptor, errorMessage?: string | RegExp): void {
expect(error.code).toBe(errorDescriptor.code);
expect(error.message).not.toMatch(/%[a-zA-Z][a-zA-Z0-9]*%/);

if (typeof errorMessage == 'string') {
expect(error.message).toContain(errorMessage);
} else if (errorMessage != undefined) {
expect(error.message).toMatch(errorMessage);
}
}
114 changes: 114 additions & 0 deletions packages/core/__tests__/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { describe, expect, test } from '@jest/globals';

import { RuntimeEnvironment, Task, TaskMap, KuaiRuntimeEnvironment, paramTypes, ERRORS } from '../src';
import { SimpleTask } from '../src/task';
import { expectKuaiErrorAsync } from './errors';

declare module '../src/type/runtime' {
export interface RuntimeEnvironment {
wheel?: {
type: string;
};
engine?: {
power: number;
type: string;
};
}
}

const makeTaskMap = (tasks: Task[]): TaskMap => tasks.reduce((prev, task) => ({ ...prev, [task.name]: task }), {});

describe('kuai task system', () => {
const buildWheelTask = new SimpleTask('BUILD_WHEEL')
.addParam('wheelType', 'Type of wheel to build', 'steel', paramTypes.string)
.setAction<{ wheelType: string }>(async ({ wheelType }, env, runSuper) => {
if (wheelType === 'steel') {
env.wheel = {
type: wheelType,
};
}

if (runSuper.isDefined) {
await runSuper();
}
});

const buildEngineTask = new SimpleTask('BUILD_ENGINE')
.addParam('engineType', 'Type of engine to build', 'v8', paramTypes.string)
.addParam('power', 'Power of engine to build', 1000, paramTypes.number)
.setAction<{ engineType: string; power: number }>(async ({ engineType, power }, env, runSuper) => {
env.engine = {
power,
type: engineType,
};

if (runSuper.isDefined) {
await runSuper();
}
});

const buildCarTask = new SimpleTask('BUILD_CAR')
.addParam('wheelType', 'Type of wheel to build', 'steel', paramTypes.string)
.addParam('engineType', 'Type of engine to build', 'v8', paramTypes.string)
.setAction<{ engineType: string; wheelType: string }>(async (args, env) => {
env.run('BUILD_WHEEL', { wheelType: args.wheelType });
env.run('BUILD_ENGINE', { engineType: args.engineType });
});

const buildSkidProofWheelTask = new SimpleTask('BUILD_WHEEL').setAction<{ wheelType: string }>(
async ({ wheelType }, env, runSuper) => {
if (wheelType === 'skid_proof') {
env.wheel = {
type: wheelType,
};
}

if (runSuper.isDefined) {
await runSuper();
}
},
);

const environment = new KuaiRuntimeEnvironment(
{},
makeTaskMap([buildWheelTask, buildEngineTask, buildCarTask, buildSkidProofWheelTask]),
[],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) as any as RuntimeEnvironment;

test('should build car with skid proof wheel', () => {
environment.run('BUILD_CAR', { wheelType: 'skid_proof', engineType: 'v8' });
expect(environment.wheel?.type).toBe('skid_proof');
});

describe('invalid task', () => {
const buildWheelTask = new SimpleTask('BUILD_WHEEL')
.addParam('wheelType', 'Type of wheel to build')
.setAction<{ wheelType: string }>(async ({ wheelType }, env, runSuper) => {
if (wheelType === 'steel') {
env.wheel = {
type: wheelType,
};
}

if (runSuper.isDefined) {
await runSuper();
}
});

const environment = new KuaiRuntimeEnvironment(
{},
makeTaskMap([buildWheelTask]),
[],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) as any as RuntimeEnvironment;

test('invalid task name', async () => {
expectKuaiErrorAsync(() => environment.run('BUILD_WHEEL1', {}), ERRORS.ARGUMENTS.UNRECOGNIZED_TASK);
});

test('missing task arguments', async () => {
expectKuaiErrorAsync(() => environment.run('BUILD_WHEEL', {}), ERRORS.ARGUMENTS.MISSING_TASK_ARGUMENT);
});
});
});
6 changes: 6 additions & 0 deletions packages/core/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testPathIgnorePatterns: ['/__fixtures__/', '/__utils__/'],
};
10 changes: 10 additions & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "@kuai/core",
"version": "0.0.1",
"license": "MIT",
"main": "lib/index.js",
"scripts": {
"build": "tsc",
"test": "jest"
}
}
61 changes: 61 additions & 0 deletions packages/core/src/errors-list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
export interface ErrorDescriptor {
code: string;
message: string;
}

export const ERRORS = {
ARGUMENTS: {
UNRECOGNIZED_TASK: {
code: 'UNRECOGNIZED_TASK',
message: 'Unrecognized task %task%',
},
MISSING_TASK_ARGUMENT: {
code: 'MISSING_TASK_ARGUMENT',
message: "The '%param%' parameter expects a value, but none was passed.",
},
},
INTERNAL: {
TEMPLATE_INVALID_VARIABLE_NAME: {
code: 'TEMPLATE_INVALID_VARIABLE_NAME',
message: 'Variable names can only include ascii letters and numbers, and start with a letter, but got %var%',
},
TEMPLATE_VARIABLE_TAG_MISSING: {
code: 'TEMPLATE_VARIABLE_TAG_MISSING',
message: "Variable %var%'s tag not present in the template",
},
TEMPLATE_VALUE_CONTAINS_VARIABLE_TAG: {
code: 'TEMPLATE_VALUE_CONTAINS_VARIABLE_TAG',
message: "Template values can't include variable tags, but %var%'s value includes one",
},
},
GENERAL: {
CONTEXT_ALREADY_CREATED: {
code: 'CONTEXT_ALREADY_CREATED',
message: 'KuaiContext is already created.',
},
CONTEXT_NOT_CREATED: {
code: 'CONTEXT_NOT_CREATED',
message: 'KuaiContext is not created.',
},
RUNTIME_NOT_DEFINED: {
code: 'RUNTIME_NOT_DEFINED',
message: 'Kuai Runtime Environment is not defined in the KuaiContext.',
},
RUNTIME_ALREADY_DEFINED: {
code: 'RUNTIME_ALREADY_DEFINED',
message: 'Kuai Runtime Environment is already defined in the KuaiContext',
},
TS_NODE_NOT_INSTALLED: {
code: 'TS_NODE_NOT_INSTALLED',
message: `Your project uses typescript, but ts-node is not installed.
Please run: npm install --save-dev ts-node`,
},
TYPESCRIPT_NOT_INSTALLED: {
code: 'TYPESCRIPT_NOT_INSTALLED',
message: `Your project uses typescript, but it's not installed.
Please run: npm install --save-dev typescript`,
},
},
};
Loading

0 comments on commit 463e7de

Please sign in to comment.