Ctrl K
ComunidadGuías…

test/context-view-preferences.test.js

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
var assert = require("assert");
var test = require("node:test");
var fs = require("fs");
var path = require("path");
var vm = require("vm");
var storageModule = require("../lib/users-context-view-preferences");
var serviceModule = require("../lib/server-context-view");

function storage(initial, multiUser, configInitial) {
  var data = JSON.parse(JSON.stringify(initial));
  var config = Object.assign({}, configInitial || {});
  var configModule = { loadConfig: function () { return Object.assign({}, config); }, saveConfig: function (next) { config = Object.assign({}, next); } };
  var preferences = storageModule.attachContextViewPreferences({
    loadUsers: function () { return JSON.parse(JSON.stringify(data)); },
    saveUsers: function (next) { data = JSON.parse(JSON.stringify(next)); },
    isMultiUser: function () { return multiUser; },
    configModule: configModule,
  });
  return { preferences: preferences, data: function () { return data; }, config: function () { return config; } };
}

test("context view persists across fresh single-user instances while preserving config", function () {
  var fixture = storage({ users: [] }, false, { port: 2633, tls: true });
  assert.deepEqual(fixture.preferences.get("default"), { present: false, mode: "off", source: null });
  assert.equal(fixture.preferences.set("default", "panel").ok, true);
  var reloaded = storage(fixture.data(), false, fixture.config());
  assert.equal(reloaded.preferences.get("default").mode, "panel");
  assert.equal(reloaded.config().port, 2633);
  assert.equal(reloaded.preferences.set("default", "invalid").ok, false);
});

test("context view isolates multi-user records and rejects missing users", function () {
  var fixture = storage({ users: [{ id: "u1" }, { id: "u2", contextViewPreference: "mini" }] }, true);
  assert.equal(fixture.preferences.set("u1", "panel").ok, true);
  assert.equal(fixture.preferences.get("u1").mode, "panel");
  assert.equal(fixture.preferences.get("u2").mode, "mini");
  assert.equal(fixture.preferences.set("missing", "off").ok, false);
});

function serviceFixture(options) {
  options = options || {};
  var records = { u1: { id: "u1" }, u2: { id: "u2" } };
  var persisted = storage({ users: [{ id: "u1" }, { id: "u2" }] }, true);
  var clients = [];
  var users = {
    isMultiUser: function () { return true; },
    findUserById: function (id) { return records[id] || null; },
    getContextViewPreference: function (id) { return persisted.preferences.get(id); },
    setContextViewPreference: function (id, mode) { return persisted.preferences.set(id, mode); },
  };
  var service = serviceModule.attachContextViewService({ users: users, forEachAppClient: function (fn) { clients.forEach(fn); }, serverEpoch: "context-test" });
  function ws(id) { var socket = { readyState: 1, _clayUser: { id: id }, sent: [] }; socket.send = function (message) { socket.sent.push(JSON.parse(message)); }; return socket; }
  return { service: service, ws: ws, clients: clients, records: records };
}

test("context view broadcasts across projects only to the same user", function () {
  var fixture = serviceFixture();
  var sourceProject = fixture.ws("u1");
  var otherProject = fixture.ws("u1");
  var otherUser = fixture.ws("u2");
  fixture.clients.push(otherProject, otherUser);
  fixture.service.handleMessage(sourceProject, { type: "context_view_set", requestId: "save", mode: "mini" });
  assert.equal(otherProject.sent[0].mode, "mini");
  assert.equal(otherUser.sent.length, 0);
  assert.equal(sourceProject.sent[0].requestId, "save");
});

test("context view service rejects invalid modes and revoked actors using real storage", function () {
  var fixture = serviceFixture();
  var invalid = fixture.ws("u1");
  fixture.service.handleMessage(invalid, { type: "context_view_set", requestId: "invalid", mode: "not-a-mode" });
  assert.equal(invalid.sent[0].ready, false);
  assert.match(invalid.sent[0].error, /invalid/i);
  var revoked = fixture.ws("u1");
  fixture.records.u1 = null;
  fixture.service.handleMessage(revoked, { type: "context_view_get", requestId: "revoked" });
  assert.equal(revoked.sent[0].accountAvailable, false);
  assert.equal(revoked.sent[0].accountId, null);
});

function loadClient() {
  var source = fs.readFileSync(path.join(__dirname, "../lib/public/modules/context-view-preference.js"), "utf8");
  source = source.replace(/^import .*;\n/gm, "").replace(/export function /g, "function ").replace(/export \{ validMode \};/g, "");
  source += "\nthis.api={getContextView:getContextView,getEffectiveContextView:getEffectiveContextView,beginContextViewConnection:beginContextViewConnection,requestContextView:requestContextView,setContextView:setContextView,handleContextViewMessage:handleContextViewMessage};";
  var state = { connected: true, myUserId: "u1", currentSlug: "project-a", contextViewOverride: null, contextViewPreferenceState: { mode: "off", canonicalMode: "off", preferencePresent: false, loading: false, saving: false, requestId: null, saveRequestId: null, pendingSaves: [], accountId: "u1", serverEpoch: "old", canonicalRevision: 2, error: "" } };
  var listeners = [];
  var sent = [];
  var context = {
    store: { get: function (key) { return state[key]; }, set: function (value) { var previous = state; state = Object.assign({}, state, value); listeners.forEach(function (listener) { listener(state, previous); }); }, subscribe: function (listener) { listeners.push(listener); } },
    getWs: function () { return { readyState: 1, send: function (message) { sent.push(JSON.parse(message)); } }; },
    Date: Date,
  };
  vm.runInNewContext(source, context);
  return { api: context.api, state: function () { return state; }, sent: sent, set: function (value) { context.store.set(value); } };
}

test("context view client confirms older rapid save before rolling back latest failure", function () {
  var fixture = loadClient();
  fixture.api.setContextView("mini");
  var first = fixture.state().contextViewPreferenceState.saveRequestId;
  fixture.api.setContextView("panel");
  var second = fixture.state().contextViewPreferenceState.saveRequestId;
  fixture.api.handleContextViewMessage({ type: "context_view_state", requestId: first, accountId: "u1", serverEpoch: "old", canonicalRevision: 3, mode: "mini", ready: true });
  assert.equal(fixture.state().contextViewPreferenceState.mode, "panel");
  assert.equal(fixture.state().contextViewPreferenceState.saveRequestId, second);
  fixture.api.handleContextViewMessage({ type: "context_view_state", requestId: second, accountId: "u1", serverEpoch: "old", canonicalRevision: 4, ready: false, error: "save failed" });
  assert.equal(fixture.state().contextViewPreferenceState.saving, false);
  assert.equal(fixture.api.getContextView(), "mini");
  assert.equal(fixture.state().contextViewPreferenceState.error, "save failed");
});

test("context view broadcast updates canonical mode without replacing pending optimistic mode", function () {
  var fixture = loadClient();
  fixture.api.setContextView("mini");
  var saveId = fixture.state().contextViewPreferenceState.saveRequestId;
  fixture.api.handleContextViewMessage({ type: "context_view_state", accountId: "u1", serverEpoch: "old", canonicalRevision: 3, mode: "panel", preferencePresent: true });
  assert.equal(fixture.api.getContextView(), "mini");
  assert.equal(fixture.state().contextViewPreferenceState.canonicalMode, "panel");
  assert.equal(fixture.state().contextViewPreferenceState.saveRequestId, saveId);
  fixture.api.handleContextViewMessage({ type: "context_view_state", requestId: saveId, accountId: "u1", serverEpoch: "old", canonicalRevision: 4, mode: "mini", ready: true });
  assert.equal(fixture.api.getContextView(), "mini");
  assert.equal(fixture.state().contextViewPreferenceState.saving, false);
});

test("context view reverse-order ACK keeps newer confirmed intent and ignores older revision", function () {
  var fixture = loadClient();
  fixture.api.setContextView("mini");
  var first = fixture.state().contextViewPreferenceState.saveRequestId;
  fixture.api.setContextView("panel");
  var second = fixture.state().contextViewPreferenceState.saveRequestId;
  fixture.api.handleContextViewMessage({ type: "context_view_state", requestId: second, accountId: "u1", serverEpoch: "old", canonicalRevision: 4, mode: "panel", ready: true, preferencePresent: true });
  assert.equal(fixture.api.getContextView(), "panel");
  assert.equal(fixture.state().contextViewPreferenceState.canonicalMode, "panel");
  assert.equal(fixture.state().contextViewPreferenceState.canonicalRevision, 4);
  assert.equal(fixture.state().contextViewPreferenceState.saving, false);
  fixture.api.handleContextViewMessage({ type: "context_view_state", requestId: first, accountId: "u1", serverEpoch: "old", canonicalRevision: 3, mode: "mini", ready: true });
  assert.equal(fixture.api.getContextView(), "panel");
  assert.equal(fixture.state().contextViewPreferenceState.canonicalMode, "panel");
  assert.equal(fixture.state().contextViewPreferenceState.canonicalRevision, 4);

  var ordered = loadClient();
  ordered.api.setContextView("mini");
  var older = ordered.state().contextViewPreferenceState.saveRequestId;
  ordered.api.setContextView("panel");
  var newer = ordered.state().contextViewPreferenceState.saveRequestId;
  ordered.api.handleContextViewMessage({ type: "context_view_state", accountId: "u1", serverEpoch: "old", canonicalRevision: 4, mode: "panel", ready: true, preferencePresent: true });
  ordered.api.handleContextViewMessage({ type: "context_view_state", requestId: older, accountId: "u1", serverEpoch: "old", canonicalRevision: 3, mode: "mini", ready: true });
  assert.equal(ordered.state().contextViewPreferenceState.canonicalRevision, 4);
  assert.equal(ordered.api.getContextView(), "panel");
  ordered.api.handleContextViewMessage({ type: "context_view_state", requestId: newer, accountId: "u1", serverEpoch: "old", canonicalRevision: 5, ready: false, error: "save failed" });
  assert.equal(ordered.api.getContextView(), "panel");
  assert.equal(ordered.state().contextViewPreferenceState.canonicalMode, "panel");
});

test("context view correlated account loss settles the pending save", function () {
  var fixture = loadClient();
  fixture.api.setContextView("panel");
  var saveId = fixture.state().contextViewPreferenceState.saveRequestId;
  fixture.api.handleContextViewMessage({ type: "context_view_state", requestId: saveId, accountId: null, accountAvailable: false, serverEpoch: "old", error: "Your account is no longer available." });
  assert.equal(fixture.state().contextViewPreferenceState.saving, false);
  assert.equal(fixture.state().contextViewPreferenceState.saveRequestId, null);
  assert.equal(fixture.api.getContextView(), "off");
});

test("context view reconnect drops lost save before the new GET reply", function () {
  var fixture = loadClient();
  fixture.api.setContextView("panel");
  fixture.api.beginContextViewConnection();
  assert.equal(fixture.api.getContextView(), "off");
  assert.equal(fixture.state().contextViewPreferenceState.saving, false);
  assert.equal(fixture.state().contextViewPreferenceState.saveRequestId, null);
  fixture.api.requestContextView();
  var requestId = fixture.state().contextViewPreferenceState.requestId;
  fixture.api.handleContextViewMessage({ type: "context_view_state", requestId: requestId, accountId: "u1", serverEpoch: "new", canonicalRevision: 0, mode: "mini", preferencePresent: true, ready: true });
  assert.equal(fixture.api.getContextView(), "mini");
  assert.equal(fixture.state().contextViewPreferenceState.loading, false);
});

test("app panels use server preference authority and pane override remains transient", function () {
  var panels = fs.readFileSync(path.join(__dirname, "../lib/public/modules/app-panels.js"), "utf8");
  var pane = fs.readFileSync(path.join(__dirname, "../lib/public/modules/pane-bridge.js"), "utf8");
  assert.equal(panels.indexOf("localStorage.getItem(\"clay-context-view\")"), -1);
  assert.equal(panels.indexOf("localStorage.setItem(\"clay-context-view\")"), -1);
  assert.ok(panels.indexOf("getStoredContextView") !== -1);
  assert.ok(panels.indexOf("store.subscribe") !== -1);
  assert.equal(panels.indexOf("clay-context-view-changed"), -1);
  assert.doesNotMatch(panels, /setContextView\("off"\)\) applyContextView/);
  assert.doesNotMatch(panels, /setContextView\("mini"\)\) applyContextView/);
  assert.doesNotMatch(panels, /setContextView\("panel"\)\) applyContextView/);
  assert.match(pane, /contextViewOverride/);
});

test("context view effective mode uses the transient store override without changing preference", function () {
  var fixture = loadClient();
  fixture.set({ contextViewOverride: "panel" });
  fixture.set({ paneMode: false });
  assert.equal(fixture.api.getEffectiveContextView(), "off");
  fixture.set({ paneMode: true });
  assert.equal(fixture.api.getEffectiveContextView(), "panel");
  fixture.set({ contextViewPreferenceState: Object.assign({}, fixture.state().contextViewPreferenceState, { mode: "mini", canonicalMode: "mini" }) });
  fixture.set({ contextViewOverride: "off" });
  assert.equal(fixture.api.getEffectiveContextView(), "off");
  fixture.set({ contextViewOverride: null });
  assert.equal(fixture.api.getEffectiveContextView(), "mini");
});

test("context view boundary reset clears the pane override", function () {
  var fixture = loadClient();
  fixture.set({ contextViewOverride: "panel" });
  fixture.set({ myUserId: "u2" });
  assert.equal(fixture.state().contextViewOverride, null);
  assert.equal(fixture.state().contextViewPreferenceState.mode, "off");
});