Seven descriptors written from their pages replace seven stubs. All are version 3; 1.5.2 announced Speaker, StepSpeaker, EqualizerController and PlaybackStateReporter as version 1, and all but ChannelController without a property. StepSpeaker has no properties object in discovery and PlaybackController one without fields, next to supportedOperations; a directive for an operation the device did not list is answered with INVALID_DIRECTIVE. The equalizer declares bands, modes or both under "configurations", a TV its inputs from the 61 names of the page. Not described: the lineup of ChannelController, whose discovery example the page folds away. The discovery examples of InputController spell one key "friendNames"; the fixtures have friendlyNames, as its table does. 62 examples of the six pages are saved as fixtures. 241 tests pass, 215 before. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
108 lines
6.5 KiB
JavaScript
108 lines
6.5 KiB
JavaScript
"use strict";
|
|
// The interfaces of a TV and a speaker: what the examples of their pages do not show. The examples themselves are
|
|
// run by descriptors.test.js, device/discovery.test.js and dispatch/interfaces.test.js.
|
|
const { test } = require("node:test");
|
|
const assert = require("node:assert/strict");
|
|
const {
|
|
Alex2MQTT, AlexaInterfaceType, DeclarationError, EqualizerController, InputController, Inputs, MemoryPublisher,
|
|
PlaybackController, PlaybackStateReporter, Speaker, StepSpeaker,
|
|
} = require("alex2node");
|
|
const { endpoint } = require("../helpers/endpoint.js");
|
|
const { directive } = require("../helpers/harness.js");
|
|
|
|
const tv = () => endpoint("tv-1", "TV", ["TV"]);
|
|
const refused = (descriptor, options, message) => assert.throws(
|
|
() => tv().add(descriptor, options),
|
|
(err) => err instanceof DeclarationError && err.message === `tv-1: ${descriptor.namespace}: ${message}`
|
|
);
|
|
|
|
test("1.x declarations: the version and the properties are those of the pages, where 1.5.2 had version 1 and no property", () => {
|
|
const T = AlexaInterfaceType;
|
|
const announced = (type) => {
|
|
const { version, properties } = tv().addCapability(type).getJSON();
|
|
return [version, properties && properties.supported];
|
|
};
|
|
assert.deepEqual(announced(T.SPEAKER), ["3", [{ name: "volume" }, { name: "muted" }]]);
|
|
assert.deepEqual(announced(T.STEP_SPEAKER), ["3", undefined]);
|
|
assert.deepEqual(announced(T.EQUALIZER_CONTROLLER), ["3", [{ name: "bands" }, { name: "mode" }]]);
|
|
assert.deepEqual(announced(T.PLAYBACK_CONTROLLER), ["3", undefined]);
|
|
assert.deepEqual(announced(T.PLAYBACK_STATE_REPORTER), ["3", [{ name: "playbackState" }]]);
|
|
assert.deepEqual(announced(T.INPUT_CONTROLLER), ["3", [{ name: "input" }]]);
|
|
assert.deepEqual(announced(T.CHANNEL_CONTROLLER), ["3", [{ name: "channel" }]]);
|
|
});
|
|
|
|
test("1.x declarations of an equalizer and of inputs have no way to say what the device has: check() says it", () => {
|
|
const device = tv();
|
|
device.addCapability(AlexaInterfaceType.EQUALIZER_CONTROLLER);
|
|
device.addCapability(AlexaInterfaceType.INPUT_CONTROLLER);
|
|
device.addCapability(AlexaInterfaceType.SPEAKER);
|
|
assert.deepEqual(device.check(), [
|
|
"tv-1: Alexa.EqualizerController: needs bands, modes or both: declare what the equalizer has",
|
|
"tv-1: Alexa.InputController: inputs: expected a list with 1 or more entries, got nothing",
|
|
]);
|
|
});
|
|
|
|
test("Speaker and StepSpeaker: neither has options, and the StepSpeaker has no properties object", () => {
|
|
assert.deepEqual(tv().add(StepSpeaker).toJSON(), { type: "AlexaInterface", interface: "Alexa.StepSpeaker", version: "3" });
|
|
refused(Speaker, { maximum: 11 }, "maximum: not an option of the interface");
|
|
assert.deepEqual(Object.keys(PlaybackStateReporter.directives), []);
|
|
});
|
|
|
|
test("EqualizerController: a device with bands, with modes or with both lists the property of what it has", () => {
|
|
const bands = { supported: ["BASS", "TREBLE"], range: { minimum: -10, maximum: 10 } };
|
|
const onlyBands = tv().add(EqualizerController, { bands }).toJSON();
|
|
assert.deepEqual(onlyBands.properties.supported, [{ name: "bands" }]);
|
|
assert.deepEqual(onlyBands.configurations, { bands: { supported: [{ name: "BASS" }, { name: "TREBLE" }], range: bands.range } });
|
|
const onlyModes = tv().add(EqualizerController, { modes: ["NIGHT", "TV"] }).toJSON();
|
|
assert.deepEqual(onlyModes.properties.supported, [{ name: "mode" }]);
|
|
assert.deepEqual(onlyModes.configurations, { modes: { supported: [{ name: "NIGHT" }, { name: "TV" }] } });
|
|
assert.equal("configuration" in onlyModes, false, "the interface spells it configurations");
|
|
|
|
refused(EqualizerController, {}, "needs bands, modes or both: declare what the equalizer has");
|
|
refused(EqualizerController, { bands: { ...bands, range: { minimum: 6, maximum: -6 } } }, "bands.range.maximum -6 is not above bands.range.minimum 6");
|
|
refused(EqualizerController, { bands: { ...bands, supported: ["BASS", "BASS"] } }, "BASS is listed twice");
|
|
refused(EqualizerController, { modes: ["CINEMA"] }, 'modes[0]: expected MOVIE | MUSIC | NIGHT | SPORT | TV, got "CINEMA"');
|
|
});
|
|
|
|
test("PlaybackController: all eight operations unless the declaration lists some, and only those reach the handler", async () => {
|
|
assert.deepEqual(tv().add(PlaybackController).toJSON().supportedOperations, Object.keys(PlaybackController.directives));
|
|
refused(
|
|
PlaybackController,
|
|
{ supportedOperations: ["Eject"] },
|
|
'supportedOperations[0]: expected Play | Pause | Stop | Next | Previous | FastForward | Rewind | StartOver, got "Eject"'
|
|
);
|
|
|
|
const sent = new MemoryPublisher();
|
|
const bridge = new Alex2MQTT("u", "p", "root", false, { publisher: sent, log: () => {} });
|
|
const player = bridge.addDevice({ endpointId: "player-1", name: "Player", categories: ["STREAMING_DEVICE"] });
|
|
const state = player.add(PlaybackStateReporter);
|
|
const seen = [];
|
|
player.add(PlaybackController, { supportedOperations: ["Play", "Pause"] }).on("*", (ctx) => {
|
|
seen.push(ctx.name);
|
|
return ctx.respond((s) => s.set(state, "playbackState", { state: ctx.name === "Play" ? "PLAYING" : "PAUSED" }));
|
|
});
|
|
for (const [name, token] of [["Play", "ct-1"], ["Rewind", "ct-2"], ["Pause", "ct-3"]]) {
|
|
await bridge.receive("root/player-1/alexaDirective", JSON.stringify(directive("Alexa.PlaybackController", name, "player-1", token)));
|
|
}
|
|
assert.deepEqual(seen, ["Play", "Pause"]);
|
|
const answers = sent.published.map(({ message: { event, context } }) => [
|
|
event.header.name,
|
|
context ? context.properties.filter(({ name }) => name === "playbackState").map(({ value }) => value.state) : event.payload.type,
|
|
]);
|
|
assert.deepEqual(answers, [["Response", ["PLAYING"]], ["ErrorResponse", "INVALID_DIRECTIVE"], ["Response", ["PAUSED"]]]);
|
|
});
|
|
|
|
test("InputController: the inputs are on the capability, each name and friendly name stands for one input", () => {
|
|
assert.equal(Inputs.length, 61);
|
|
assert.equal(new Set(Inputs).size, 61);
|
|
const inputs = [{ name: "HDMI 1", friendlyNames: ["Console"] }, { name: "TUNER" }];
|
|
assert.deepEqual(tv().add(InputController, { inputs }).toJSON().inputs, inputs);
|
|
refused(InputController, { inputs: [] }, "inputs: expected a list with 1 or more entries, got []");
|
|
refused(InputController, { inputs: [{ name: "SCART" }] }, 'inputs[0].name: expected an input like HDMI 1, got "SCART"');
|
|
refused(InputController, { inputs: [{ name: "TV" }, { name: "TV" }] }, "TV is listed twice, a name stands for one input");
|
|
refused(
|
|
InputController,
|
|
{ inputs: [{ name: "HDMI 1", friendlyNames: ["Console"] }, { name: "HDMI 2", friendlyNames: ["Console"] }] },
|
|
"Console is listed twice, a name stands for one input"
|
|
);
|
|
});
|