From f4c01c265683282c983ebe2ab82254c6f8ab1581 Mon Sep 17 00:00:00 2001 From: nmemmert <67961292+nmemmert@users.noreply.github.com> Date: Mon, 26 Oct 2026 14:17:48 -0400 Subject: [PATCH] Fix Chores page stuck on Loading; add a test that draws the page - Remove a leftover reference to the old points summary that threw after load - New chores-view test renders the page from real server data Co-Authored-By: Claude Sonnet 5.5 --- package.json | 2 +- public/chores-view.js | 1 - public/sw.js | 2 +- tools/chores-view-test.js | 19 +++++++++++++++++++ 4 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 tools/chores-view-test.js diff --git a/package.json b/package.json index 8458988..8526bb0 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "server.js", "scripts": { "start": "node server.js", - "test": "for t in smoke tasks push users cal plex player lyrics signal mail mail-imap files podcast abapi abdirect books store librivox radio vbv savefiles bible lrclib visualizer body transcript recipes contacts mystore caldav musicdata screentime chat notes chores kidlimits search inbox family board; do node tools/$t-test.js || exit 1; done", + "test": "for t in smoke tasks push users cal plex player lyrics signal mail mail-imap files podcast abapi abdirect books store librivox radio vbv savefiles bible lrclib visualizer body transcript recipes contacts mystore caldav musicdata screentime chat notes chores kidlimits search inbox family board chores-view; do node tools/$t-test.js || exit 1; done", "audit": "npm audit --omit=dev --audit-level=high" }, "keywords": [], diff --git a/public/chores-view.js b/public/chores-view.js index 94a4a81..abc4c4c 100644 --- a/public/chores-view.js +++ b/public/chores-view.js @@ -29,7 +29,6 @@ async function viewChores() { const d = cr.data, me = d.me.id, mine = d.board.filter(i => i.user === me), anyone = d.board.filter(i => i.user === null), others = d.people.filter(p => p.id !== me); const item = i => ``; const person = p => { const l = d.board.filter(i => i.user === p.id), done = l.filter(i => i.done).length; return `
${esc(sgInitial(p.name))}${esc(p.name)}${l.length ? done + ' of ' + l.length : 'nothing today'}
${l.length ? `
` : ''}
${l.map(item).join('')}
`; }; - const max = Math.max(1, ...cr.sum.people.map(p => p.points)); v.innerHTML = `

Chores

${d.me.admin ? `` : ''}
${esc(crDayLabel(cr.day))}${cr.day !== crYmd(new Date()) ? '' : ''}

Mine

${mine.map(item).join('') || '

Nothing for you ' + (cr.day === crYmd(new Date()) ? 'today' : 'on this day') + '. 🎉

'}
diff --git a/public/sw.js b/public/sw.js index 9a60e26..ffcd266 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,5 +1,5 @@ // App-shell cache only; Seafile/API traffic always goes to the network. -const CACHE = 'hub-v79'; +const CACHE = 'hub-v80'; const SHARE = 'hub-share'; // files shared into the app from other apps, held until the person picks a folder const SHELL = ['/icon.svg', '/manifest.webmanifest', '/icons/icon-192.png', '/icons/apple-touch-icon.png']; diff --git a/tools/chores-view-test.js b/tools/chores-view-test.js new file mode 100644 index 0000000..8545d8a --- /dev/null +++ b/tools/chores-view-test.js @@ -0,0 +1,19 @@ +// The Chores page script: it draws the page from real server data without throwing (a thrown error here leaves the page on "Loading…"). +const fs = require('fs'), vm = require('vm'), path = require('path'), assert = require('assert'), { Readable } = require('stream'); +process.env.DATA_DIR = fs.mkdtempSync(path.join(require('os').tmpdir(), 'nehub-crview-')); +const users = require('../users'), chores = require('../chores'); +const ann = users.create({ name: 'Ann', role: 'admin', handle: 'YQ' }), bob = users.create({ name: 'Bob', role: 'user', tabs: ['chores'], handle: 'Yg' }); +const run = (url, method = 'GET', body) => new Promise(ok => { const q = Readable.from(body ? [Buffer.from(JSON.stringify(body))] : []); q.url = url; q.method = method; q.headers = {}; users.runAs(ann, () => chores.handle(q, { writeHead() {}, end: d => ok(JSON.parse(d)) })); }); +(async () => { + try { + await run('/api/chores', 'POST', { title: 'Dishes', repeat: 'daily', assign: [bob.id], points: 3 }); await run('/api/chores', 'POST', { title: 'Trash', repeat: 'daily', assign: 'anyone', points: 2 }); await run('/api/chores/reward', 'POST', { title: 'Movie', points: 3 }); + const today = new Date().toISOString().slice(0, 10), data = await run('/api/chores?date=' + today), week = await run('/api/chores/week?date=' + today); + let html = ''; const el = { set innerHTML(v) { html = v; }, get innerHTML() { return html; }, querySelectorAll: () => ({ forEach() {} }), querySelector: () => null }; + const ctx = { ME: { id: ann.id, role: 'admin' }, location: { hash: '#/chores' }, document: {}, console, Intl, $: s => s === '#view' ? el : new Proxy({}, { get: () => () => {}, set: () => true }), esc: s => String(s), icon: () => '', sgColor: () => '#000', sgInitial: s => s[0], jfetch: async u => u.includes('/week') ? week : data, toast() {}, openSheet: () => el, closeSheet() {}, confirmSheet() {}, allowed: () => true }; + vm.createContext(ctx); vm.runInContext(fs.readFileSync(path.join(__dirname, '../public/chores-view.js'), 'utf8'), ctx); + await vm.runInContext('viewChores()', ctx); + assert.ok(/Weekly goals/.test(html) && /Dishes/.test(html) && /Take it/.test(html), 'the page is drawn with chores, a Take it button and the weekly goals'); + console.log('chores view ok'); + } catch (e) { console.error(e); process.exitCode = 1; } + process.exit(process.exitCode || 0); +})();