Skip to content

Commit cf661cf

Browse files
authored
Merge pull request #294 from YousefED/engine-tests
Setup and add first engine tests
2 parents 54835ad + ba41ba7 commit cf661cf

File tree

11 files changed

+227
-21
lines changed

11 files changed

+227
-21
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"install-lerna": "npm install --no-package-lock",
1414
"bootstrap": "lerna bootstrap --hoist --ci && patch-package",
1515
"install-new-packages": "lerna bootstrap --hoist && patch-package",
16-
"test": "lerna run test-no-watch",
16+
"test": "jest --coverage=true --config=jest.config.js && lerna run test-no-watch",
1717
"build": "lerna run build --concurrency 1",
1818
"build-react": "lerna run build-react --concurrency 1 --stream",
1919
"lint": "lerna run lint --concurrency 1",

packages/engine/jest.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@ module.exports = {
1515
"^y-protocols/([a-zA-Z-]*)$":
1616
"<rootDir>/../../node_modules/y-protocols/dist/$1.cjs",
1717
},
18-
setupFiles: ["<rootDir>/src/setupTests.ts"],
18+
setupFiles: ["<rootDir>/src/tests/setupTests.ts"],
1919
};

packages/engine/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"vscode-lib": "^0.1.0"
1111
},
1212
"devDependencies": {
13+
"jest": "26.6.0",
1314
"rimraf": "^3.0.2",
1415
"typescript": "4.3.2"
1516
},
@@ -19,7 +20,8 @@
1920
"scripts": {
2021
"clean": "rimraf dist && rimraf types",
2122
"build": "npm run clean && tsc -p tsconfig.json",
22-
"watch": "tsc --watch"
23+
"watch": "tsc --watch",
24+
"test": "jest --coverage=true --config=jest.config.js"
2325
},
2426
"jest": {
2527
"transformIgnorePatterns": [

packages/engine/src/Engine.test.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { CodeModel } from "./CodeModel";
2+
import { Engine } from "./Engine";
3+
import { event } from "vscode-lib";
4+
import {
5+
buildMockedModel,
6+
importResolver,
7+
toAMDFormat,
8+
waitTillEvent,
9+
} from "./tests/util/helpers";
10+
11+
const getModel1 = () =>
12+
buildMockedModel(
13+
"model1",
14+
`let x = 4;
15+
let y = 6;
16+
let sum = x + y;
17+
exports.sum = sum;
18+
exports.default = sum;`
19+
);
20+
21+
const getModel2 = () =>
22+
buildMockedModel("model2", `exports.default = $.sum - 5;`);
23+
24+
describe("engine class", () => {
25+
it("should execute a single model", async () => {
26+
const engine = new Engine<CodeModel>(importResolver);
27+
engine.registerModel(getModel1());
28+
29+
const { model, output } = await event.Event.toPromise(engine.onOutput);
30+
31+
expect(model.path).toBe("model1");
32+
expect(output.sum).toBe(10);
33+
expect(output.default).toBe(10);
34+
});
35+
36+
it("should read exported variables from other models", async () => {
37+
const engine = new Engine<CodeModel>(importResolver);
38+
engine.registerModel(getModel1());
39+
await event.Event.toPromise(engine.onOutput);
40+
41+
engine.registerModel(getModel2());
42+
const { model, output } = await event.Event.toPromise(engine.onOutput);
43+
44+
expect(model.path).toBe("model2");
45+
expect(output.default).toBe(5);
46+
});
47+
48+
it("should re-evaluate code after change", async () => {
49+
const engine = new Engine<CodeModel>(importResolver);
50+
const model1 = getModel1();
51+
52+
engine.registerModel(model1);
53+
await event.Event.toPromise(engine.onOutput);
54+
55+
model1.updateCode(
56+
toAMDFormat(`let x = 0;
57+
let y = 6;
58+
let sum = x + y;
59+
exports.sum = sum;
60+
exports.default = sum;`)
61+
);
62+
63+
const { output } = await event.Event.toPromise(engine.onOutput);
64+
65+
expect(output.sum).toBe(6);
66+
expect(output.default).toBe(6);
67+
});
68+
69+
it("should re-evaluate other models when global variable changes", async () => {
70+
const engine = new Engine<CodeModel>(importResolver);
71+
// TODO: Expected 4 events. Figure out why model 2 re-evaluates.
72+
const eventsPromise = waitTillEvent(engine.onOutput, 5);
73+
const model1 = getModel1();
74+
const model2 = getModel2();
75+
76+
engine.registerModel(model1);
77+
engine.registerModel(model2);
78+
79+
model1.updateCode(
80+
toAMDFormat(`let x = 0;
81+
let y = 6;
82+
let sum = x + y;
83+
exports.sum = sum;
84+
exports.default = sum;`)
85+
);
86+
87+
const events = await eventsPromise;
88+
const eventsSnapshot = events.map((event) => ({
89+
path: event.model.path,
90+
output: event.output,
91+
}));
92+
const finalEvent = eventsSnapshot[eventsSnapshot.length - 1];
93+
94+
expect(finalEvent.path).toBe("model2");
95+
expect(finalEvent.output.default).toBe(1);
96+
expect(eventsSnapshot).toMatchSnapshot();
97+
});
98+
});
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`engine class should re-evaluate other models when global variable changes 1`] = `
4+
Array [
5+
Object {
6+
"output": Object {
7+
"default": 10,
8+
},
9+
"path": "model1",
10+
},
11+
Object {
12+
"output": Object {
13+
"default": 5,
14+
},
15+
"path": "model2",
16+
},
17+
Object {
18+
"output": Object {
19+
"default": 5,
20+
},
21+
"path": "model2",
22+
},
23+
Object {
24+
"output": Object {
25+
"default": 6,
26+
},
27+
"path": "model1",
28+
},
29+
Object {
30+
"output": Object {
31+
"default": 1,
32+
},
33+
"path": "model2",
34+
},
35+
]
36+
`;

packages/engine/src/modules.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,10 @@ export function createExecutionScope(context: TypeCellContext<any>) {
8787
}
8888

8989
export function getModulesFromTypeCellCode(compiledCode: string, scope: any) {
90-
if (!compiledCode.match(/^(define\((".*", )?\[.*\], )function/gm)) {
90+
// Checks if define([], function) like code is already present
91+
if (!compiledCode.match(/(define\((".*", )?\[.*\], )function/gm)) {
9192
// file is not a module (no exports). Create module-like code manually
92-
compiledCode = `define([], function() {
93-
${compiledCode};
94-
});
95-
`;
93+
compiledCode = `define([], function() { ${compiledCode}; });`;
9694
}
9795

9896
if (Object.keys(scope).find((key) => !/^[a-zA-Z0-9_$]+$/.test(key))) {

packages/engine/src/setupTests.ts

Lines changed: 0 additions & 4 deletions
This file was deleted.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// jest-dom adds custom jest matchers for asserting on DOM nodes.
2+
// allows you to do things like:
3+
// expect(element).toHaveTextContent(/react/i)
4+
// learn more: https://github.com/testing-library/jest-dom
5+
// import "@testing-library/jest-dom";
6+
// https://github.com/developit/microbundle/issues/708, otherwise vscode-lib fails
7+
import "regenerator-runtime/runtime.js";
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { CodeModel } from "../../CodeModel";
2+
import { event } from "vscode-lib";
3+
4+
export class CodeModelMock implements CodeModel {
5+
public contentChangeEmitter = new event.Emitter<void>();
6+
7+
public onWillDispose() {
8+
return {
9+
dispose: () => {},
10+
};
11+
}
12+
public onDidChangeContent = this.contentChangeEmitter.event;
13+
14+
constructor(
15+
public readonly language: string,
16+
public readonly path: string,
17+
public code: string
18+
) {}
19+
20+
public getValue(): string {
21+
return this.code;
22+
}
23+
24+
public updateCode(code: string) {
25+
this.code = code;
26+
this.contentChangeEmitter.fire();
27+
}
28+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { CodeModel } from "../../CodeModel";
2+
import { ResolvedImport } from "../../Engine";
3+
import { CodeModelMock } from "./CodeModelMock";
4+
5+
export function waitTillEvent<T>(
6+
e: (listener: (arg0: T) => void) => void,
7+
expected: number
8+
): Promise<T[]> {
9+
const captured: T[] = [];
10+
return new Promise((resolve) => {
11+
e(({ ...args }) => {
12+
if (captured.length < expected) {
13+
captured.push(args);
14+
}
15+
if (captured.length === expected) {
16+
return resolve(captured);
17+
}
18+
});
19+
});
20+
}
21+
22+
export async function importResolver(
23+
_module: string,
24+
_forModel: CodeModel
25+
): Promise<ResolvedImport> {
26+
const res = async () => {
27+
return {
28+
module: {
29+
default: {},
30+
},
31+
dispose: () => {},
32+
};
33+
};
34+
35+
return res();
36+
}
37+
38+
export function toAMDFormat(code: string) {
39+
return `define(["require", "exports"], function(require, exports) {
40+
"use strict";
41+
Object.defineProperty(exports, "__esModule", { value: true });
42+
${code}
43+
});
44+
`;
45+
}
46+
47+
export function buildMockedModel(name: string, code: string) {
48+
return new CodeModelMock("javascript", name, toAMDFormat(code));
49+
}

0 commit comments

Comments
 (0)