const { describe, test } = require('node:test'); const assert = require('node:assert'); const { BB } = require('./setup'); // settings-sync reads BB.api.sync and BB.ui at load time, so both are stood up // before the module is required. let syncStatusResult = {}; let syncToasts = []; BB.ui.showToast = (msg, type) => { syncToasts.push({ msg, type }); }; BB.ui.openModal = () => {}; BB.api.sync = { status: async () => syncStatusResult, startAuth: async () => ({ authUrl: 'https://test.com', state: 's', codeVerifier: 'cv', port: 8080 }), completeAuth: async () => {}, setupEncryptionNew: async () => {}, setupEncryptionExisting: async () => {}, now: async () => ({ pushed: 1, pulled: 2 }), updateSettings: async () => {}, disconnect: async () => {}, }; require('../settings-sync'); // Clear the modal body between renders: openSettings appends into it. function freshModalBody() { const body = document.getElementById('modal-body'); body.innerHTML = ''; body.children = []; return body; } describe('BB.sync.openSettings: renderState routing', () => { test('not configured shows Connect button', async () => { syncStatusResult = { configured: false, authenticated: false }; const body = freshModalBody(); await BB.sync.openSettings(); // renderConnect adds a div with class sync-connect assert.ok( body.children.some(c => c.className === 'sync-connect'), 'Should render connect state' ); }); test('configured but not authenticated shows Connect button', async () => { syncStatusResult = { configured: true, authenticated: false }; const body = freshModalBody(); await BB.sync.openSettings(); assert.ok( body.children.some(c => c.className === 'sync-connect'), 'Should render connect state when not authenticated' ); }); test('authenticated but no encryption and no server key shows Set Password', async () => { syncStatusResult = { configured: true, authenticated: true, encryptionReady: false, hasServerKey: false }; const body = freshModalBody(); await BB.sync.openSettings(); // renderEncryption adds a div, check for Set Password button text const hasForm = body.children.some(c => { // The child div contains a form with submit button return c.children && c.children.some(f => f.children && f.children.some(a => a.children && a.children.some(b => b._text === 'Set Password') ) ); }); assert.ok(hasForm, 'Should show Set Password button'); }); test('authenticated but no encryption with server key shows Unlock', async () => { syncStatusResult = { configured: true, authenticated: true, encryptionReady: false, hasServerKey: true }; const body = freshModalBody(); await BB.sync.openSettings(); const hasUnlock = body.children.some(c => { return c.children && c.children.some(f => f.children && f.children.some(a => a.children && a.children.some(b => b._text === 'Unlock') ) ); }); assert.ok(hasUnlock, 'Should show Unlock button'); }); test('fully ready shows sync-ready with Sync Now button', async () => { syncStatusResult = { configured: true, authenticated: true, encryptionReady: true, lastSyncAt: null, pendingChanges: 0, autoSyncEnabled: false, syncIntervalMinutes: 15, }; const body = freshModalBody(); await BB.sync.openSettings(); assert.ok( body.children.some(c => c.className === 'sync-ready'), 'Should render ready state' ); }); test('ready state shows Never for null lastSyncAt', async () => { syncStatusResult = { configured: true, authenticated: true, encryptionReady: true, lastSyncAt: null, pendingChanges: 0, autoSyncEnabled: true, syncIntervalMinutes: 15, }; const body = freshModalBody(); await BB.sync.openSettings(); const readyDiv = body.children.find(c => c.className === 'sync-ready'); assert.ok(readyDiv, 'Should have sync-ready div'); // The info div should contain "Never" const infoDiv = readyDiv.children.find(c => c.className === 'sync-info'); assert.ok(infoDiv && infoDiv.innerHTML.includes('Never'), 'Should show Never for null lastSyncAt'); }); test('openSettings shows error toast on API failure', async () => { syncToasts = []; BB.api.sync.status = async () => { throw new Error('network error'); }; await BB.sync.openSettings(); assert.ok(syncToasts.some(t => t.type === 'error'), 'Should show error toast'); // Restore status mock BB.api.sync.status = async () => syncStatusResult; }); test('ready state has disconnect button', async () => { syncStatusResult = { configured: true, authenticated: true, encryptionReady: true, lastSyncAt: '2026-01-01T00:00:00Z', pendingChanges: 3, autoSyncEnabled: true, syncIntervalMinutes: 30, }; const body = freshModalBody(); await BB.sync.openSettings(); const readyDiv = body.children.find(c => c.className === 'sync-ready'); assert.ok(readyDiv, 'Should have sync-ready div'); const hasDisconnect = readyDiv.children.some(c => c.className === 'button sync-disconnect' && c._text === 'Disconnect' ); assert.ok(hasDisconnect, 'Should have Disconnect button'); }); });