readme: quick start, generated capability table, recipes, bridge contract, migration
The readme is rewritten for 2.0: what the library needs, install, the lamp as quick start, concepts, the table of interfaces, recipes, the bridge contract, errors, testing without Alexa, migration from 1.x and limits. scripts/capability-table.js (npm run docs) writes the table from the registry and the text of three example files into their code blocks; "Through Alexa" names only the runs of 2026-09-28, made with 1.5.2. test/readme.test.js fails on a stale table or file, runs examples/testing/lamp.test.js (MemoryPublisher, no broker) and compiles the other code blocks. package.json: description, keywords, CHANGELOG.md in files. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
a59de926a2
commit
785944b2da
6 changed files with 848 additions and 174 deletions
86
test/readme.test.js
Normal file
86
test/readme.test.js
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
"use strict";
|
||||
// readme.md says what the code does: its table of interfaces is the one the registry gives, a program it shows is
|
||||
// the text of a file that a test runs, and the other code blocks compile.
|
||||
const { test } = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const { run, ROOT } = require("./helpers/examples.js");
|
||||
const { table, update, shown, BEGIN, END, DRIVEN, README } = require("../scripts/capability-table.js");
|
||||
const alex2node = require("..");
|
||||
|
||||
const readme = fs.readFileSync(README, "utf8");
|
||||
const AsyncFunction = (async () => {}).constructor;
|
||||
|
||||
// The js code blocks, each with the file its marker line names
|
||||
const blocks = [...readme.matchAll(/(?:<!-- file: (\S+) -->\n)?```js\n([\s\S]*?)\n```/g)]
|
||||
.map(([, file, code]) => ({ file, code }));
|
||||
const loads = (code) => /require\("alex2node"\)|from "alex2node"/.test(code);
|
||||
|
||||
test("the table of interfaces and the files in the readme are the ones of the code: npm run docs", () => {
|
||||
assert.equal(update(readme), readme);
|
||||
});
|
||||
|
||||
test("the table has a row for every described interface, and a line for the others", () => {
|
||||
const text = table();
|
||||
for (const { namespace, tier } of alex2node.registry.list()) {
|
||||
const row = text.split("\n").filter((line) => line.startsWith(`| [${namespace}](`));
|
||||
assert.equal(row.length, tier === 3 ? 0 : 1, namespace);
|
||||
assert.ok(text.includes(`[${namespace}](`), namespace);
|
||||
}
|
||||
assert.ok(readme.indexOf(BEGIN) < readme.indexOf(END));
|
||||
});
|
||||
|
||||
test("an interface is said to be driven through Alexa only when a run is recorded for it", () => {
|
||||
// The runs of 2026-09-28, with alex2node 1.5.2. A name is added here with the record of its run.
|
||||
assert.deepEqual(Object.keys(DRIVEN).sort(), [
|
||||
"Alexa", "Alexa.BrightnessController", "Alexa.ColorController", "Alexa.ColorTemperatureController",
|
||||
"Alexa.ContactSensor", "Alexa.LockController", "Alexa.ModeController", "Alexa.MotionSensor",
|
||||
"Alexa.PercentageController", "Alexa.PowerController", "Alexa.PowerLevelController", "Alexa.RangeController",
|
||||
"Alexa.SceneController", "Alexa.TemperatureSensor", "Alexa.ThermostatController", "Alexa.ToggleController",
|
||||
]);
|
||||
const rows = table().split("\n").filter((line) => line.startsWith("| [Alexa"));
|
||||
for (const line of rows) {
|
||||
const cells = line.split(" | ");
|
||||
const namespace = cells[0].slice(3, cells[0].indexOf("]"));
|
||||
assert.equal(cells[cells.length - 1].replace(/ \|$/, ""), DRIVEN[namespace] ?? "-", namespace);
|
||||
}
|
||||
});
|
||||
|
||||
test("a program in the readme is a file, and a test runs the file", () => {
|
||||
assert.deepEqual(shown(readme), ["examples/lamp.js", "examples/testing/lamp.test.js", "examples/legacy/ExamplePowerController.js"]);
|
||||
// More than the line that loads the library: a program, which has to be one of the files
|
||||
const programs = blocks.filter(({ code }) => loads(code) && code.split("\n").length > 1);
|
||||
assert.deepEqual(programs.map(({ file }) => file), shown(readme));
|
||||
|
||||
const examples = fs.readFileSync(path.join(__dirname, "examples.test.js"), "utf8");
|
||||
assert.ok(examples.includes('start("lamp.js"'));
|
||||
assert.ok("ExamplePowerController.js" in require("./fixtures/legacy-examples/discovery-1.5.2.json"));
|
||||
// examples/testing/lamp.test.js: the next test
|
||||
});
|
||||
|
||||
test("examples/testing/lamp.test.js passes, without a broker", async () => {
|
||||
// LOOPBACK_PORT 1, where nothing listens: a connection would fail
|
||||
const example = await run(path.join("testing", "lamp.test.js"));
|
||||
assert.equal(await example.exited, 0, example.output());
|
||||
assert.match(example.output(), /pass 4\n/);
|
||||
assert.match(example.output(), /fail 0\n/);
|
||||
});
|
||||
|
||||
test("the other code blocks of the readme compile, and load what the library exports", () => {
|
||||
const fragments = blocks.filter(({ file }) => !file);
|
||||
assert.ok(fragments.length >= 8);
|
||||
for (const { code } of fragments) {
|
||||
for (const [, names] of code.matchAll(/(?:const|import) \{([^}]+)\} (?:= require\("alex2node"\)|from "alex2node")/g)) {
|
||||
for (const name of names.split(",").map((part) => part.trim())) assert.ok(name in alex2node, `${name} is not an export`);
|
||||
}
|
||||
const body = code.split("\n").filter((line) => !/^import .* from "alex2node";$/.test(line)).join("\n");
|
||||
assert.doesNotThrow(() => new AsyncFunction(body), `this block does not compile:\n${code}`);
|
||||
}
|
||||
});
|
||||
|
||||
test("what the readme calls on AlexaErrors and on the registry is there", () => {
|
||||
for (const [, helper] of readme.matchAll(/AlexaErrors\.(\w+)\(/g)) assert.equal(typeof alex2node.AlexaErrors[helper], "function", helper);
|
||||
for (const [, namespace] of readme.matchAll(/registry\.get\("([^"]+)"\)/g)) assert.ok(alex2node.registry.has(namespace), namespace);
|
||||
assert.equal(Object.keys(require("../dist/cjs/registry/catalog.js").ERROR_TYPES).length, 73);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue