webamp/modern/src/__tests__/objects.test.js
2020-04-06 19:50:20 -07:00

125 lines
4.2 KiB
JavaScript

import { getClass, getFormattedId, objects } from "../maki-interpreter/objects";
import runtime from "../runtime";
test("getFormattedId() is reversable", () => {
Object.keys(runtime).forEach((id) => {
const formattedId = getFormattedId(id);
const inverse = getFormattedId(formattedId);
expect(inverse).toBe(id);
});
Object.keys(objects)
.map((id) => id.toLowerCase())
.forEach((id) => {
const formattedId = getFormattedId(id);
const inverse = getFormattedId(formattedId);
expect(inverse).toBe(id);
});
});
const getMakiMethods = (obj) =>
Object.getOwnPropertyNames(obj).filter((name) => {
return (
typeof obj[name] === "function" &&
!name.startsWith("js_") &&
!name.startsWith("_") &&
name !== "constructor"
);
});
for (const [key, Klass] of Object.entries(runtime)) {
const obj = getClass(key);
describe(`${obj.name}`, () => {
test("implements getclassname()", () => {
expect(Klass.prototype.getclassname()).toBe(obj.name);
});
test("has the correct parent", () => {
const Parent = Object.getPrototypeOf(Klass);
if (Klass.prototype.getclassname() === "Object") {
expect(Parent.prototype).toBe(undefined);
return;
}
expect(Parent.prototype.getclassname()).toBe(obj.parent);
});
describe("methods have the correct arity", () => {
obj.functions.forEach((func) => {
const methodName = func.name.toLowerCase();
// Once all methods are implemented this check can be removed.
// For now we have a separate test which checks that we haven't
// regressed on the methods we've implemented.
const hasMethodOnSelf = Klass.prototype.hasOwnProperty(methodName);
test(`Has the method ${obj.name}.${func.name}`, () => {
expect(hasMethodOnSelf).toBe(true);
});
if (!hasMethodOnSelf) {
return;
}
const actual = Klass.prototype[func.name.toLowerCase()].length;
test(`${obj.name}.${func.name} has an arity of ${actual}`, () => {
expect(func.parameters.length).toBe(actual);
});
});
});
});
}
describe("Maki classes", () => {
const runtimeMethods = new Set();
const unimplementedRuntimeMethods = new Set();
const objectMethods = new Set();
for (const [key, Klass] of Object.entries(runtime)) {
const obj = getClass(key);
getMakiMethods(Klass.prototype).forEach((methodName) => {
runtimeMethods.add(`${obj.name}.${methodName}`);
const methodSource = Klass.prototype[methodName].toString();
if (methodSource.includes("unimplementedWarning")) {
unimplementedRuntimeMethods.add(`${obj.name}.${methodName}`);
}
});
obj.functions.forEach((func) => {
objectMethods.add(`${obj.name}.${func.name.toLowerCase()}`);
});
}
test("All classes are implemented", () => {
const getName = (Klass) => Klass.prototype.getclassname();
const actualNames = Object.keys(runtime).map(
(id) => `${getName(runtime[id])} (${id})`
);
const expectedNames = Object.keys(objects).map(
(id) => `${objects[id].name} (${getFormattedId(id)})`
);
expect(new Set(actualNames)).toEqual(new Set(expectedNames));
});
test("have no extra methods", () => {
// getclassname _should_ be implemented on Object and let each class inherit
// it. However it's far easier to implement it on each class directly, so
// we'll allow that.
function isntGetClassname(method) {
return !/\.getclassname$/.test(method);
}
function isntMakiMethod(method) {
return !objectMethods.has(method);
}
const extra = [...runtimeMethods]
.filter(isntMakiMethod)
.filter(isntGetClassname);
expect(extra).toEqual([]);
});
test("There are no missing methods", () => {
const missing = [...objectMethods].filter((x) => !runtimeMethods.has(x));
expect(missing).toEqual([]);
});
test("Track unimplemented methods", () => {
// Write this as a newline delineated string to make it easier to other
// tools to extract from the `.snap` file.
const expected = Array.from(unimplementedRuntimeMethods).join("\n");
expect(expected).toMatchSnapshot();
});
});