tennu-plugins
Version:
Tennu plugin subsystem
368 lines (303 loc) • 15.3 kB
JavaScript
const sinon = require("sinon");
const assert = require("better-assert");
const equal = require("deep-eql");
const inspect = require("util").inspect;
const format = require("util").format;
const debug = Boolean(false || process.env.VERBOSE);
const logfn = debug ? console.log.bind(console) : function () {};
const PluginSystem = require("../lib/plugins.js");
const failures = require("../lib/failures.js");
const examples = require("../examples/index.js");
describe("Plugin System", function () {
var context, system;
beforeEach(function () {
logfn(/* newline */);
context = {};
system = PluginSystem(context);
});
describe("Initialization", function () {
describe.skip("Failure: PluginFactory is not an object", function () {});
// May end up making this one part of the previous when implemented.
describe.skip("Failure: PluginFactory is null", function () {});
it("of a single bare plugin", function () {
const initializationResult = system.initialize(examples["bare"]);
assert(initializationResult.isOk());
assert(system.hasPlugin("bare"));
assert(equal(system.loadedNames(), ["bare"]));
});
it("of multiple bare plugins", function () {
system.initialize(examples["bare"]);
system.initialize(examples["bare-alt-name"]);
assert(system.hasPlugin("bare"));
assert(system.hasPlugin("bare-alt-name"));
});
describe("of plugins with dependencies;", function () {
describe.skip("Failure: PluginFactory requires property not an array", function () {});
it("Plugin's single required plugin is loaded", function () {
const imports = {
name: "requires-exports-true",
requires: ["exports-true"],
init: function (context, imports) {
assert(imports["exports-true"] === true);
return {};
},
};
const exportsResult = system.initialize(examples["exports-true"]);
assert(exportsResult.isOk());
const importsResult = system.initialize(imports);
importsResult.mapFail((fail) => logfn(inspect()));
assert(importsResult.isOk());
});
it.skip("isInitializableWith/2 Plugin's single required plugin is assumed loaded", function () {});
it.skip("Failure: isInitializable/1 Plugin's single required plugin is not loaded. No other plugins loaded", function () {});
it.skip("Failure: isInitializable/1 Plugin's single required plugin is not loaded. Other plugins loaded", function () {});
it.skip("Failure: isInitializableWith/2 Plugin's single required plugin is not loaded. No other plugins loaded or assumed loaded", function () {});
it.skip("Failure: isInitializableWith/2 Plugin's single required plugin is not loaded. No other plugins loaded. Other plugins assumed loaded", function () {});
it.skip("isInitializable/1 Plugin's multiple required plugins are loaded", function () {});
it.skip("isInitializableWith/2 Plugin's multiple required plugins are all assumed loaded", function () {});
it.skip("isInitializableWith/2 Plugin's multiple required plugins are all either loaded or assumed loaded", function () {});
it.skip("Failure: isInitializable/1 Plugin's multiple required plugins where none are loaded", function () {});
it.skip("Failure: isInitializable/1 Plugin's multiple required plugins where some are loaded", function () {});
it.skip("Failure: isInitializableWith/2 Plugin's multiple required plugins where none are loaded or assumed loaded", function () {});
it.skip("Failure: isInitializableWith/2 Plugin's multiple required plugins where some are assumed loaded", function () {});
});
describe("of plugins with role dependencies;", function () {
it("that requires a single plugin of a specific role", function (done) {
const role = {
name: "has-role",
role: "role",
init: function () {
return {
exports: true
};
}
};
const usesRole = {
name: "uses-role",
requiresRoles: ["role"],
init: function (_context, imports) {
assert(imports.role === true);
done();
return {};
}
};
const roleResult = system.initialize(role);
assert(roleResult.isOk());
const usesRoleResult = system.initialize(usesRole);
usesRoleResult.mapFail((fail) => logfn(inspect(fail)));
assert(usesRoleResult.isOk());
});
it.skip("isInitializableWith/2 Plugin's single required role is assumed loaded", function () {});
it.skip("Failure: isInitializable/1 Plugin's single required role is not loaded. No other plugins loaded", function () {});
it.skip("Failure: isInitializable/1 Plugin's single required role is not loaded. Other plugins loaded", function () {});
it.skip("Failure: isInitializableWith/2 Plugin's single required role is not loaded. No other plugins loaded or assumed loaded", function () {});
it.skip("Failure: isInitializableWith/2 Plugin's single required role is not loaded. No other plugins loaded. Other plugins assumed loaded", function () {});
it.skip("isInitializable/1 Plugin's multiple required roles are loaded", function () {});
it.skip("isInitializableWith/2 Plugin's multiple required roles are all assumed loaded", function () {});
it.skip("isInitializableWith/2 Plugin's multiple required roles are all either loaded or assumed loaded", function () {});
it.skip("Failure: isInitializable/1 Plugin's multiple required roles where none are loaded", function () {});
it.skip("Failure: isInitializable/1 Plugin's multiple required roles where some are loaded", function () {});
it.skip("Failure: isInitializableWith/2 Plugin's multiple required roles where none are loaded or assumed loaded", function () {});
it.skip("Failure: isInitializableWith/2 Plugin's multiple required roles where some are assumed loaded", function () {});
});
it("function gets a reference to the context", function () {
const plugin = {
init: function (_context) {
assert(context === _context);
return {};
},
name: "context-test"
};
const result = system.initialize(plugin);
assert(result.isOk());
});
it("of a plugin with a role", function () {
const result = system.initialize(examples["waltz"]);
if (result.isFail()) {
logfn(inspect(result.fail()));
}
assert(result.isOk());
assert(system.hasPlugin("waltz"));
assert(system.hasRole("dancer"));
});
it("of a plugin with custom hooks", function (done) {
const addsTestHook = {
name: "adds-test-hook",
init: function () {
return {
hooks: {
"test": function (name, value) {
assert(name === "has-test-hook");
assert(value === true);
done();
}
}
}
},
};
system.initialize(addsTestHook);
system.initialize(examples["has-test-hook"]);
});
describe("Failure: [PluginAlreadyExists] Initialization of two plugins with the same name", function () {
beforeEach(function () {
const result = system.initialize(examples["bare"]);
assert(result.isOk());
});
const initializationValidationExpected = {
canInitialize: false,
failureReason: failures.validation.PluginAlreadyExists,
reason: "A plugin with the name 'bare' has already been installed.",
pluginFactory: examples["bare"]
};
it("via isInitializable()", function () {
const validation = system.isInitializable(examples["bare"]);
assert(equal(validation, initializationValidationExpected));
});
it("via initialize()", function () {
const result = system.initialize(examples["bare"]);
assert(result.isFail());
const validationFailure = result.fail();
logfn(validationFailure);
assert(equal(validationFailure, {
failureReason: failures.CannotInitialize,
message: "The plugin cannot be initialized. For why, check the validationFailure.",
validationFailure: initializationValidationExpected,
validationFailureTypes: failures.validation,
pluginFactory: examples["bare"]
}));
});
});
describe("Failure: [RoleAlreadyExists] Initialization of two plugins with the same role", function () {
beforeEach(function () {
const result = system.initialize(examples["waltz"]);
assert(result.isOk());
});
const initializationValidationExpected = {
canInitialize: false,
failureReason: failures.validation.RoleAlreadyExists,
reason: "A plugin with the role 'dancer' has already been installed.",
pluginFactory: examples["tango"]
};
it("via isInitializable", function () {
const validation = system.isInitializable(examples["tango"]);
assert(equal(validation, initializationValidationExpected));
});
});
describe.skip("Failure: isInitializable/1 Plugin's name property is not a string", function () {});
describe.skip("Failure: isInitializable/1 Plugin's name collides with an already loaded plugin", function () {});
describe.skip("Failure: isInitializableWith/2 Plugin's name collides with already assumed plugin's name", function () {});
});
describe("Instance Hooks", function () {
var spy;
beforeEach(function () {
spy = sinon.spy();
const result = system.addInstanceHook("test", spy);
assert(result.isOk());
});
it("hooks into every loaded module", function () {
const result = system.initialize(examples["has-test-hook"]);
assert(result.isOk());
assert(spy.calledOnce);
assert(spy.calledWith("has-test-hook", true));
});
it("skips plugins not using the hook", function () {
const result = system.initialize(examples["bare"]);
assert(result.isOk());
assert(!spy.called);
});
it("Failure: Adding the same hook name twice", function () {
const result = system.addInstanceHook("test", function () {});
assert(result.isFail());
const fail = result.fail();
assert(equal(fail, {
failureReason: failures.InstanceHookAlreadyExists,
message: "Tried to set instance hook 'test', but a plugin already has that instance hook.",
hook: "test"
}));
});
it.skip("can have a symbol for the hook name", function () {});
it.skip("having multiple hooks", function () {});
it.skip("Error: Hook name not string or symbol", function () {});
it.skip("Error: Hook function not a function.", function () {});
});
describe("Static Hooks", function () {
var spy, setinel;
beforeEach(function () {
spy = sinon.spy();
setinel = {};
system.addStaticHook("test", spy);
});
it("hooks into every loaded module", function () {
system.initialize({
test: setinel,
name: "TEST",
init: function () {
assert(spy.calledOnce);
assert(spy.calledWith("TEST", setinel));
return {};
}
});
});
it("skips plugins not using the hook", function () {
system.initialize({
name: "TEST",
init: function () {
assert(!spy.called);
return {};
}
});
});
it.skip("Failure: Adding the same hook name twice", function () {});
it.skip("can have a symbol for the hook name", function () {});
it.skip("having multiple hooks", function () {});
it.skip("Error: Hook name not string or symbol", function () {});
it.skip("Error: Hook function not a function.", function () {});
});
describe("Has", function () {
it("for plugins", function () {
system.initialize({
name: "module",
init: function () { return {}; },
});
assert(system.hasPlugin("module"));
});
it("for roles", function () {
system.initialize({
init: function () { return {}; },
name: "provides-role",
role: "role"
});
assert(system.hasRole("role"));
});
});
describe("Exports Getters", function () {
it("for plugins", function () {
const result = system.initialize(examples["exports-true"]);
if (result.isFail()) {
logfn(inspect(result.fail()));
}
assert(result.isOk());
assert(system.pluginExportsOf("exports-true").ok() === true);
});
it("Failure: PluginNotInstalled for non-existent plugin", function () {
assert(system.pluginExportsOf("dne").isFail());
});
it("for roles", function () {
const exports = {};
const roleWithExports = {
init: function () {
return {
exports: exports
}
},
name: "role-with-exports",
role: "exports"
};
system.initialize(roleWithExports);
assert(system.roleExportsOf("exports").ok() === exports);
});
it("Failure: RoleNotInstalled for non-existent role", function () {
assert(system.roleExportsOf("dne").isFail());
});
});
});