Files
FlowPilot/tests/sidepanel-settings-autosave.test.js

145 lines
3.6 KiB
JavaScript

const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('sidepanel/sidepanel.js', 'utf8');
function extractFunction(name) {
const markers = [`async function ${name}(`, `function ${name}(`];
const start = markers
.map((marker) => source.indexOf(marker))
.find((index) => index >= 0);
assert.notEqual(start, -1, `missing ${name}`);
let parenDepth = 0;
let signatureEnded = false;
let braceStart = -1;
for (let i = start; i < source.length; i += 1) {
const ch = source[i];
if (ch === '(') parenDepth += 1;
if (ch === ')') {
parenDepth -= 1;
if (parenDepth === 0) signatureEnded = true;
}
if (ch === '{' && signatureEnded) {
braceStart = i;
break;
}
}
let depth = 0;
for (let i = braceStart; i < source.length; i += 1) {
const ch = source[i];
if (ch === '{') depth += 1;
if (ch === '}') {
depth -= 1;
if (depth === 0) {
return source.slice(start, i + 1);
}
}
}
throw new Error(`unterminated ${name}`);
}
test('focused settings editor still autosaves without repainting the focused field', async () => {
const bundle = [
extractFunction('isEditableElementInSettingsCard'),
extractFunction('scheduleSettingsAutoSave'),
extractFunction('saveSettings'),
].join('\n');
const api = new Function(`
class MockElement {
constructor(tagName, type = '') {
this.tagName = tagName;
this.type = type;
this.isContentEditable = false;
}
}
const Element = MockElement;
const focusedInput = new MockElement('input', 'text');
const settingsCard = { contains: (element) => element === focusedInput };
const document = { activeElement: focusedInput };
let settingsAutoSaveTimer = null;
let scheduled = null;
let clearedTimer = null;
let settingsDirty = true;
let settingsSaveInFlight = false;
let settingsSaveRevision = 1;
const messages = [];
const syncedStates = [];
const dirtyMarks = [];
let applyCalls = 0;
function setTimeout(fn, delay) {
scheduled = { fn, delay };
return 71;
}
function clearTimeout(value) {
clearedTimer = value;
}
function collectSettingsPayload() {
return { vpsUrl: 'typed-value' };
}
function syncLatestState(state) {
syncedStates.push(state);
}
function markSettingsDirty(value = true) {
settingsDirty = value;
if (value) settingsSaveRevision += 1;
dirtyMarks.push(value);
}
function applySettingsState() {
applyCalls += 1;
}
function updateSettingsSaveState() {}
function updatePanelModeUI() {}
function updateMailProviderUI() {}
function updateButtonStates() {}
const chrome = {
runtime: {
async sendMessage(message) {
messages.push(message);
return { state: { vpsUrl: message.payload.vpsUrl } };
},
},
};
${bundle}
return {
scheduleSettingsAutoSave,
async fireScheduled() {
await scheduled.fn();
},
getSnapshot() {
return {
applyCalls,
clearedTimer,
dirtyMarks,
messages,
scheduledDelay: scheduled?.delay,
syncedStates,
};
},
};
`)();
api.scheduleSettingsAutoSave();
let snapshot = api.getSnapshot();
assert.equal(snapshot.scheduledDelay, 1200);
assert.equal(snapshot.messages.length, 0);
await api.fireScheduled();
snapshot = api.getSnapshot();
assert.equal(snapshot.clearedTimer, 71);
assert.equal(snapshot.applyCalls, 0);
assert.deepEqual(snapshot.messages, [
{
type: 'SAVE_SETTING',
source: 'sidepanel',
payload: { vpsUrl: 'typed-value' },
},
]);
assert.deepEqual(snapshot.syncedStates, [{ vpsUrl: 'typed-value' }]);
assert.deepEqual(snapshot.dirtyMarks, [false]);
});