mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-07-23 13:21:21 +00:00
* Chart-transform plugin capability * PR comments * Cleanup * Fix markdown * CodeRabbit feedback Signed-off-by: Joe <jphinspace@gmail.com> --------- Signed-off-by: Joe <jphinspace@gmail.com> Co-authored-by: Byron Gamatos <xasiklas@gmail.com>
336 lines
15 KiB
JavaScript
336 lines
15 KiB
JavaScript
const { test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const vm = require('node:vm');
|
|
const { createWindow, ROOT } = require('./capabilities_test_harness');
|
|
|
|
const CAPABILITIES_JS = path.join(ROOT, 'static', 'capabilities.js');
|
|
const CHART_TRANSFORM_JS = path.join(ROOT, 'static', 'capabilities', 'chart-transform.js');
|
|
|
|
const STORAGE_KEY = 'feedBack.chartTransform.selectedProviderId';
|
|
const PLUGIN_ID = 'example_plugin';
|
|
const PROVIDER_ID = 'example-transform';
|
|
const PROVIDER_LABEL = 'Example Transform';
|
|
|
|
function makeFakeHighway() {
|
|
const calls = { set: [], refresh: 0 };
|
|
return {
|
|
calls,
|
|
setChartTransform(p) { calls.set.push(p); },
|
|
refreshChartTransform() { calls.refresh += 1; },
|
|
getChartTransform() { return calls.set.length ? calls.set[calls.set.length - 1] : null; },
|
|
};
|
|
}
|
|
|
|
function loadChartTransform(options = {}) {
|
|
const window = createWindow(options);
|
|
// The real bus provides feedBack.on; the harness only has emit →
|
|
// dispatchEvent. Shim `on` the same way app.js implements it so the
|
|
// module's bus mirroring (song:ready, chart-transform-failed) is live.
|
|
window.feedBack.on = (type, handler) => window.addEventListener(type, handler);
|
|
if (options.highway) window.highway = options.highway;
|
|
if (options.persistedSelection) window.localStorage.setItem(STORAGE_KEY, options.persistedSelection);
|
|
const context = vm.createContext(window);
|
|
vm.runInContext(fs.readFileSync(CAPABILITIES_JS, 'utf8'), context, { filename: CAPABILITIES_JS });
|
|
vm.runInContext(fs.readFileSync(CHART_TRANSFORM_JS, 'utf8'), context, { filename: CHART_TRANSFORM_JS });
|
|
return window;
|
|
}
|
|
|
|
function captureEvents(api, eventNames) {
|
|
const events = [];
|
|
for (const name of eventNames) {
|
|
api.subscribe(name, (detail) => events.push(detail));
|
|
}
|
|
return events;
|
|
}
|
|
|
|
async function registerProvider(api, overrides = {}) {
|
|
return api.dispatch({
|
|
capability: 'chart-transform', command: 'register-provider',
|
|
source: overrides.source || PLUGIN_ID,
|
|
payload: {
|
|
providerId: overrides.providerId || PROVIDER_ID,
|
|
label: overrides.label || PROVIDER_LABEL,
|
|
transform: overrides.transform || ((input) => ({ notes: input.notes })),
|
|
},
|
|
});
|
|
}
|
|
|
|
test('chart-transform domain registers a safe provider-coordinator owner', () => {
|
|
const window = loadChartTransform();
|
|
const api = window.feedBack.capabilities;
|
|
const pipeline = api.inspect('chart-transform');
|
|
assert.ok(pipeline, 'chart-transform pipeline exists');
|
|
const owner = (pipeline.participants || []).find(p => p.pluginId === 'core.chart-transform');
|
|
assert.ok(owner, 'core.chart-transform owner registered');
|
|
assert.equal(owner.safety, 'safe');
|
|
assert.ok(owner.commands.includes('select-provider'));
|
|
assert.ok(owner.commands.includes('refresh'));
|
|
assert.equal(window.feedBack.chartTransformDomain.version, 1);
|
|
});
|
|
|
|
test('register-provider requires a transform function', async () => {
|
|
const window = loadChartTransform();
|
|
const api = window.feedBack.capabilities;
|
|
const result = await api.dispatch({
|
|
capability: 'chart-transform', command: 'register-provider',
|
|
source: PLUGIN_ID, payload: { providerId: PROVIDER_ID },
|
|
});
|
|
assert.equal(result.outcome, 'degraded');
|
|
assert.match(result.reason, /transform\(input\) function/);
|
|
});
|
|
|
|
test('register + select installs the provider on the highway and persists', async () => {
|
|
const highway = makeFakeHighway();
|
|
const window = loadChartTransform({ highway });
|
|
const api = window.feedBack.capabilities;
|
|
const events = captureEvents(api, [
|
|
'chart-transform:provider-registered',
|
|
'chart-transform:transform-changed',
|
|
]);
|
|
|
|
const reg = await registerProvider(api);
|
|
assert.equal(reg.outcome, 'handled');
|
|
assert.ok(api.inspect('chart-transform').participants.some(p => p.pluginId === PLUGIN_ID));
|
|
|
|
const sel = await api.dispatch({
|
|
capability: 'chart-transform', command: 'select-provider',
|
|
source: 'settings_ui', payload: { providerId: PROVIDER_ID },
|
|
});
|
|
assert.equal(sel.outcome, 'handled');
|
|
assert.equal(sel.payload.active, PROVIDER_ID);
|
|
assert.equal(sel.payload.installed, true);
|
|
assert.equal(highway.calls.set.length, 1);
|
|
assert.equal(highway.calls.set[0].id, PROVIDER_ID);
|
|
assert.equal(typeof highway.calls.set[0].transform, 'function');
|
|
assert.equal(window.localStorage.getItem(STORAGE_KEY), PROVIDER_ID);
|
|
|
|
const names = events.map(e => e.event);
|
|
assert.ok(names.includes('provider-registered'));
|
|
assert.ok(names.includes('transform-changed'));
|
|
});
|
|
|
|
test('select-provider with an unknown id degrades', async () => {
|
|
const window = loadChartTransform();
|
|
const api = window.feedBack.capabilities;
|
|
const result = await api.dispatch({
|
|
capability: 'chart-transform', command: 'select-provider',
|
|
source: 'settings_ui', payload: { providerId: 'nope' },
|
|
});
|
|
assert.equal(result.outcome, 'degraded');
|
|
assert.match(result.reason, /Unknown chart-transform provider/);
|
|
});
|
|
|
|
test('selection without a highway is kept and installed on song:ready', async () => {
|
|
const window = loadChartTransform();
|
|
const api = window.feedBack.capabilities;
|
|
await registerProvider(api);
|
|
const sel = await api.dispatch({
|
|
capability: 'chart-transform', command: 'select-provider',
|
|
source: 'settings_ui', payload: { providerId: PROVIDER_ID },
|
|
});
|
|
assert.equal(sel.outcome, 'handled');
|
|
assert.equal(sel.payload.installed, false, 'no highway yet');
|
|
|
|
const highway = makeFakeHighway();
|
|
window.highway = highway;
|
|
window.feedBack.emit('song:ready', {});
|
|
assert.equal(highway.calls.set.length, 1);
|
|
assert.equal(highway.calls.set[0].id, PROVIDER_ID);
|
|
assert.equal(window.feedBack.chartTransformDomain.snapshot().installed, true);
|
|
});
|
|
|
|
test('a persisted selection restores when its provider registers', async () => {
|
|
const highway = makeFakeHighway();
|
|
const window = loadChartTransform({ highway, persistedSelection: PROVIDER_ID });
|
|
const api = window.feedBack.capabilities;
|
|
await registerProvider(api);
|
|
const snapshot = window.feedBack.chartTransformDomain.snapshot();
|
|
assert.equal(snapshot.active, PROVIDER_ID);
|
|
assert.equal(snapshot.activeSource, 'restore-selection');
|
|
assert.equal(highway.calls.set.length, 1);
|
|
});
|
|
|
|
test('unregister is registrant-only and detaches the active provider', async () => {
|
|
const highway = makeFakeHighway();
|
|
const window = loadChartTransform({ highway });
|
|
const api = window.feedBack.capabilities;
|
|
await registerProvider(api);
|
|
await api.dispatch({
|
|
capability: 'chart-transform', command: 'select-provider',
|
|
source: 'settings_ui', payload: { providerId: PROVIDER_ID },
|
|
});
|
|
|
|
const denied = await api.dispatch({
|
|
capability: 'chart-transform', command: 'unregister-provider',
|
|
source: 'someone_else', payload: { providerId: PROVIDER_ID },
|
|
});
|
|
assert.equal(denied.outcome, 'degraded');
|
|
assert.match(denied.reason, /original registrant/);
|
|
|
|
const ok = await api.dispatch({
|
|
capability: 'chart-transform', command: 'unregister-provider',
|
|
source: PLUGIN_ID, payload: { providerId: PROVIDER_ID },
|
|
});
|
|
assert.equal(ok.outcome, 'handled');
|
|
const snapshot = window.feedBack.chartTransformDomain.snapshot();
|
|
assert.equal(snapshot.active, null);
|
|
assert.equal(snapshot.providers.length, 0);
|
|
// Detach = a trailing setChartTransform(null) on the highway.
|
|
assert.equal(highway.calls.set[highway.calls.set.length - 1], null);
|
|
// Persisted selection survives so re-registration re-activates.
|
|
assert.equal(window.localStorage.getItem(STORAGE_KEY), PROVIDER_ID);
|
|
});
|
|
|
|
test('unregister keeps a participant while another provider still references it', async () => {
|
|
const window = loadChartTransform();
|
|
const api = window.feedBack.capabilities;
|
|
await registerProvider(api, { providerId: 'provider-a', label: 'Provider A' });
|
|
await registerProvider(api, { providerId: 'provider-b', label: 'Provider B' });
|
|
|
|
let participant = api.inspect('chart-transform').participants
|
|
.find(p => p.pluginId === PLUGIN_ID);
|
|
assert.deepEqual(Array.from(participant.providerPolicy.providerIds), ['provider-a', 'provider-b']);
|
|
assert.deepEqual(
|
|
Array.from(participant.providerPolicy.providers, p => ({ id: p.id, label: p.label })),
|
|
[{ id: 'provider-a', label: 'Provider A' }, { id: 'provider-b', label: 'Provider B' }],
|
|
);
|
|
|
|
await api.dispatch({
|
|
capability: 'chart-transform', command: 'unregister-provider',
|
|
source: PLUGIN_ID, payload: { providerId: 'provider-b' },
|
|
});
|
|
|
|
participant = api.inspect('chart-transform').participants
|
|
.find(p => p.pluginId === PLUGIN_ID);
|
|
assert.ok(participant, 'the shared participant remains registered');
|
|
assert.deepEqual(Array.from(participant.providerPolicy.providerIds), ['provider-a']);
|
|
assert.deepEqual(
|
|
Array.from(participant.providerPolicy.providers, p => ({ id: p.id, label: p.label })),
|
|
[{ id: 'provider-a', label: 'Provider A' }],
|
|
);
|
|
assert.deepEqual(
|
|
Array.from(window.feedBack.chartTransformDomain.snapshot().providers, p => p.id),
|
|
['provider-a'],
|
|
);
|
|
|
|
await api.dispatch({
|
|
capability: 'chart-transform', command: 'unregister-provider',
|
|
source: PLUGIN_ID, payload: { providerId: 'provider-a' },
|
|
});
|
|
assert.ok(!api.inspect('chart-transform').participants
|
|
.some(p => p.pluginId === PLUGIN_ID), 'the final removal unregisters the participant');
|
|
});
|
|
|
|
test('clear-provider clears the highway hook and the persisted selection', async () => {
|
|
const highway = makeFakeHighway();
|
|
const window = loadChartTransform({ highway });
|
|
const api = window.feedBack.capabilities;
|
|
await registerProvider(api);
|
|
await api.dispatch({
|
|
capability: 'chart-transform', command: 'select-provider',
|
|
source: 'settings_ui', payload: { providerId: PROVIDER_ID },
|
|
});
|
|
const result = await api.dispatch({
|
|
capability: 'chart-transform', command: 'clear-provider', source: 'settings_ui',
|
|
});
|
|
assert.equal(result.outcome, 'handled');
|
|
assert.equal(highway.calls.set[highway.calls.set.length - 1], null);
|
|
assert.equal(window.localStorage.getItem(STORAGE_KEY), null);
|
|
});
|
|
|
|
test('refresh re-runs the installed transform', async () => {
|
|
const highway = makeFakeHighway();
|
|
const window = loadChartTransform({ highway });
|
|
const api = window.feedBack.capabilities;
|
|
await registerProvider(api);
|
|
await api.dispatch({
|
|
capability: 'chart-transform', command: 'select-provider',
|
|
source: 'settings_ui', payload: { providerId: PROVIDER_ID },
|
|
});
|
|
const result = await api.dispatch({ capability: 'chart-transform', command: 'refresh', source: PLUGIN_ID });
|
|
assert.equal(result.outcome, 'handled');
|
|
assert.equal(result.payload.refreshed, true);
|
|
assert.equal(highway.calls.refresh, 1);
|
|
});
|
|
|
|
test('announced highway instances (splitscreen panels) get the active transform', async () => {
|
|
const primary = makeFakeHighway();
|
|
const window = loadChartTransform({ highway: primary });
|
|
const api = window.feedBack.capabilities;
|
|
await registerProvider(api);
|
|
await api.dispatch({
|
|
capability: 'chart-transform', command: 'select-provider',
|
|
source: 'settings_ui', payload: { providerId: PROVIDER_ID },
|
|
});
|
|
assert.equal(window.feedBack.chartTransformDomain.snapshot().surfaces, 1);
|
|
|
|
// A splitscreen panel announces its own createHighway() instance.
|
|
const panel = makeFakeHighway();
|
|
window.feedBack.emit('highway:created', { highway: panel });
|
|
assert.equal(panel.calls.set.length, 1, 'panel receives the active transform');
|
|
assert.equal(panel.calls.set[0].id, PROVIDER_ID);
|
|
assert.equal(window.feedBack.chartTransformDomain.snapshot().surfaces, 2);
|
|
|
|
// Refresh reaches every surface.
|
|
await api.dispatch({ capability: 'chart-transform', command: 'refresh', source: PLUGIN_ID });
|
|
assert.equal(primary.calls.refresh, 1);
|
|
assert.equal(panel.calls.refresh, 1);
|
|
|
|
// Clearing detaches every surface.
|
|
await api.dispatch({ capability: 'chart-transform', command: 'clear-provider', source: 'settings_ui' });
|
|
assert.equal(primary.calls.set[primary.calls.set.length - 1], null);
|
|
assert.equal(panel.calls.set[panel.calls.set.length - 1], null);
|
|
});
|
|
|
|
test('a panel announced before any selection installs on later select', async () => {
|
|
const window = loadChartTransform();
|
|
const api = window.feedBack.capabilities;
|
|
const panel = makeFakeHighway();
|
|
window.feedBack.emit('highway:created', { highway: panel });
|
|
await registerProvider(api);
|
|
const sel = await api.dispatch({
|
|
capability: 'chart-transform', command: 'select-provider',
|
|
source: 'settings_ui', payload: { providerId: PROVIDER_ID },
|
|
});
|
|
assert.equal(sel.outcome, 'handled');
|
|
assert.equal(panel.calls.set.length, 1);
|
|
assert.equal(panel.calls.set[0].id, PROVIDER_ID);
|
|
});
|
|
|
|
test('highway failure events expose a fixed public reason', async () => {
|
|
const highway = makeFakeHighway();
|
|
const window = loadChartTransform({ highway });
|
|
const api = window.feedBack.capabilities;
|
|
const events = captureEvents(api, ['chart-transform:transform-failed']);
|
|
await registerProvider(api);
|
|
await api.dispatch({
|
|
capability: 'chart-transform', command: 'select-provider',
|
|
source: 'settings_ui', payload: { providerId: PROVIDER_ID },
|
|
});
|
|
|
|
window.feedBack.emit('highway:chart-transform-failed', {
|
|
id: PROVIDER_ID,
|
|
reason: 'token=secret https://example.test/private chart={notes:[...]}',
|
|
});
|
|
|
|
const snapshot = window.feedBack.chartTransformDomain.snapshot();
|
|
assert.equal(snapshot.lastFailure.providerId, PROVIDER_ID);
|
|
assert.equal(snapshot.lastFailure.reason, 'Chart transform provider failed');
|
|
assert.equal(events.length, 1);
|
|
assert.equal(events[0].payload.reason, 'Chart transform provider failed');
|
|
});
|
|
|
|
test('diagnostics contribution carries the schema and no song identity fields', async () => {
|
|
const window = loadChartTransform();
|
|
const api = window.feedBack.capabilities;
|
|
await registerProvider(api);
|
|
const contributions = window.feedBack.diagnostics.snapshotContributions();
|
|
const diag = contributions['chart-transform-capability'];
|
|
assert.ok(diag, 'diagnostics contributed');
|
|
assert.equal(diag.schema, 'feedBack.chart_transform.diagnostics.v1');
|
|
const flat = JSON.stringify(diag);
|
|
assert.ok(!/filename|title|artist|arrangement/.test(flat), 'no song identity in diagnostics');
|
|
});
|