const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
function createAccountPoolUiStub() {
return {
createAccountPoolFormController({
formShell,
toggleButton,
hiddenLabel = '添加账号',
visibleLabel = '取消添加',
onClear,
onFocus,
} = {}) {
let visible = false;
function sync() {
if (formShell) {
formShell.hidden = !visible;
}
if (toggleButton) {
toggleButton.textContent = visible ? visibleLabel : hiddenLabel;
toggleButton.setAttribute?.('aria-expanded', String(visible));
}
}
function setVisible(nextVisible, options = {}) {
visible = Boolean(nextVisible);
if (options.clearForm) {
onClear?.();
}
sync();
if (visible && options.focusField) {
onFocus?.();
}
}
sync();
return {
isVisible: () => visible,
setVisible,
sync,
};
},
};
}
test('sidepanel loads mail2925 manager before sidepanel bootstrap', () => {
const html = fs.readFileSync('sidepanel/sidepanel.html', 'utf8');
const helperIndex = html.indexOf('');
const managerIndex = html.indexOf('');
const sidepanelIndex = html.indexOf('');
assert.notEqual(helperIndex, -1);
assert.notEqual(managerIndex, -1);
assert.notEqual(sidepanelIndex, -1);
assert.ok(helperIndex < managerIndex);
assert.ok(managerIndex < sidepanelIndex);
});
test('sidepanel html contains mail2925 pool toggle and selector controls', () => {
const html = fs.readFileSync('sidepanel/sidepanel.html', 'utf8');
assert.match(html, /id="input-mail2925-use-account-pool"/);
assert.match(html, /id="select-mail2925-pool-account"/);
assert.match(html, /id="btn-toggle-mail2925-form"/);
assert.match(html, /id="mail2925-form-shell"/);
assert.doesNotMatch(html, /id="btn-cancel-mail2925-edit"/);
});
test('sidepanel css keeps collapsed shared mailbox list near a single card height', () => {
const css = fs.readFileSync('sidepanel/sidepanel.css', 'utf8');
assert.match(css, /\.hotmail-list-shell\.is-collapsed\s*\{\s*max-height:\s*176px;\s*\}/);
});
test('mail2925 manager exposes a factory and renders empty state', () => {
const source = fs.readFileSync('sidepanel/mail-2925-manager.js', 'utf8');
const windowObject = {
SidepanelAccountPoolUi: createAccountPoolUiStub(),
};
const localStorageMock = {
getItem() {
return null;
},
setItem() {},
};
const api = new Function('window', 'localStorage', `${source}; return window.SidepanelMail2925Manager;`)(
windowObject,
localStorageMock
);
assert.equal(typeof api?.createMail2925Manager, 'function');
const mail2925AccountsList = { innerHTML: '', addEventListener() {} };
const formToggleButton = {
textContent: '',
disabled: false,
setAttribute() {},
addEventListener() {},
};
const toggleButton = {
textContent: '',
disabled: false,
setAttribute() {},
addEventListener() {},
};
const noopClassList = { toggle() {} };
const manager = api.createMail2925Manager({
state: {
getLatestState: () => ({ currentMail2925AccountId: null, mail2925Accounts: [] }),
syncLatestState() {},
},
dom: {
btnAddMail2925Account: { disabled: false, addEventListener() {} },
btnDeleteAllMail2925Accounts: { textContent: '', disabled: false, addEventListener() {} },
btnImportMail2925Accounts: { disabled: false, addEventListener() {} },
btnToggleMail2925Form: formToggleButton,
btnToggleMail2925List: toggleButton,
inputMail2925Email: { value: '' },
inputMail2925Import: { value: '' },
inputMail2925Password: { value: '' },
mail2925AccountsList,
mail2925FormShell: { hidden: true },
mail2925ListShell: { classList: noopClassList },
},
helpers: {
getMail2925Accounts: () => [],
escapeHtml: (value) => String(value || ''),
showToast() {},
openConfirmModal: async () => true,
copyTextToClipboard: async () => {},
refreshManagedAliasBaseEmail() {},
},
runtime: {
sendMessage: async () => ({}),
},
constants: {
copyIcon: '',
displayTimeZone: 'Asia/Shanghai',
expandedStorageKey: 'multipage-mail2925-list-expanded',
},
mail2925Utils: {},
});
assert.equal(typeof manager.renderMail2925Accounts, 'function');
assert.equal(typeof manager.bindMail2925Events, 'function');
assert.equal(typeof manager.initMail2925ListExpandedState, 'function');
manager.renderMail2925Accounts();
assert.match(mail2925AccountsList.innerHTML, /还没有 2925 账号/);
});
test('mail2925 manager toggles form container from header button', () => {
const source = fs.readFileSync('sidepanel/mail-2925-manager.js', 'utf8');
const windowObject = {
SidepanelAccountPoolUi: createAccountPoolUiStub(),
};
const localStorageMock = {
getItem() {
return null;
},
setItem() {},
};
const api = new Function('window', 'localStorage', `${source}; return window.SidepanelMail2925Manager;`)(
windowObject,
localStorageMock
);
const clickHandlers = {};
const formToggleButton = {
textContent: '',
disabled: false,
setAttribute(name, value) {
this[name] = value;
},
addEventListener(type, handler) {
clickHandlers[type] = handler;
},
};
const formShell = { hidden: true };
const manager = api.createMail2925Manager({
state: {
getLatestState: () => ({ currentMail2925AccountId: null, mail2925Accounts: [] }),
syncLatestState() {},
},
dom: {
btnAddMail2925Account: { textContent: '', disabled: false, addEventListener() {} },
btnDeleteAllMail2925Accounts: { textContent: '', disabled: false, addEventListener() {} },
btnImportMail2925Accounts: { disabled: false, addEventListener() {} },
btnToggleMail2925Form: formToggleButton,
btnToggleMail2925List: { textContent: '', disabled: false, setAttribute() {}, addEventListener() {} },
inputMail2925Email: { value: '', focus() { this.focused = true; } },
inputMail2925Import: { value: '' },
inputMail2925Password: { value: '' },
mail2925AccountsList: { innerHTML: '', addEventListener() {} },
mail2925FormShell: formShell,
mail2925ListShell: { classList: { toggle() {} } },
},
helpers: {
getMail2925Accounts: () => [],
escapeHtml: (value) => String(value || ''),
showToast() {},
openConfirmModal: async () => true,
copyTextToClipboard: async () => {},
refreshManagedAliasBaseEmail() {},
},
runtime: {
sendMessage: async () => ({}),
},
constants: {
copyIcon: '',
displayTimeZone: 'Asia/Shanghai',
expandedStorageKey: 'multipage-mail2925-list-expanded',
},
mail2925Utils: {},
});
manager.bindMail2925Events();
assert.equal(formShell.hidden, true);
assert.equal(formToggleButton.textContent, '添加账号');
clickHandlers.click();
assert.equal(formShell.hidden, false);
assert.equal(formToggleButton.textContent, '取消添加');
clickHandlers.click();
assert.equal(formShell.hidden, true);
assert.equal(formToggleButton.textContent, '添加账号');
});
test('mail2925 manager hides form after save succeeds', async () => {
const source = fs.readFileSync('sidepanel/mail-2925-manager.js', 'utf8');
const windowObject = {
SidepanelAccountPoolUi: createAccountPoolUiStub(),
};
const localStorageMock = {
getItem() {
return null;
},
setItem() {},
};
const api = new Function('window', 'localStorage', `${source}; return window.SidepanelMail2925Manager;`)(
windowObject,
localStorageMock
);
let latestState = { currentMail2925AccountId: null, mail2925Accounts: [] };
const handlers = {};
const formToggleButton = {
textContent: '',
disabled: false,
setAttribute() {},
addEventListener(type, handler) {
if (type === 'click') handlers.toggle = handler;
},
};
const addButton = {
textContent: '',
disabled: false,
addEventListener(type, handler) {
if (type === 'click') handlers.add = handler;
},
};
const formShell = { hidden: true };
const inputMail2925Email = { value: '', focus() {} };
const inputMail2925Password = { value: '' };
const toastMessages = [];
const manager = api.createMail2925Manager({
state: {
getLatestState: () => latestState,
syncLatestState(patch) {
latestState = { ...latestState, ...patch };
},
},
dom: {
btnAddMail2925Account: addButton,
btnDeleteAllMail2925Accounts: { textContent: '', disabled: false, addEventListener() {} },
btnImportMail2925Accounts: { disabled: false, addEventListener() {} },
btnToggleMail2925Form: formToggleButton,
btnToggleMail2925List: { textContent: '', disabled: false, setAttribute() {}, addEventListener() {} },
inputMail2925Email,
inputMail2925Import: { value: '' },
inputMail2925Password,
mail2925AccountsList: { innerHTML: '', addEventListener() {} },
mail2925FormShell: formShell,
mail2925ListShell: { classList: { toggle() {} } },
},
helpers: {
getMail2925Accounts: (state) => state.mail2925Accounts || [],
escapeHtml: (value) => String(value || ''),
showToast(message) {
toastMessages.push(message);
},
openConfirmModal: async () => true,
copyTextToClipboard: async () => {},
refreshManagedAliasBaseEmail() {},
},
runtime: {
sendMessage: async () => ({
account: {
id: 'acc-1',
email: 'demo@2925.com',
password: 'secret',
enabled: true,
lastLoginAt: 0,
lastUsedAt: 0,
lastLimitAt: 0,
disabledUntil: 0,
lastError: '',
},
}),
},
constants: {
copyIcon: '',
displayTimeZone: 'Asia/Shanghai',
expandedStorageKey: 'multipage-mail2925-list-expanded',
},
mail2925Utils: {
upsertMail2925AccountInList: (accounts, nextAccount) => accounts.concat(nextAccount),
},
});
manager.bindMail2925Events();
handlers.toggle();
inputMail2925Email.value = 'demo@2925.com';
inputMail2925Password.value = 'secret';
await handlers.add();
assert.equal(formShell.hidden, true);
assert.equal(formToggleButton.textContent, '添加账号');
assert.equal(addButton.textContent, '添加账号');
assert.equal(inputMail2925Email.value, '');
assert.equal(inputMail2925Password.value, '');
assert.match(toastMessages.at(-1) || '', /已保存 2925 账号/);
});