Dagsplan: flytt/navn/deling + plan-badge, kategorier, feltmodus
Dagsplan kan navngis og flyttes til annen dato (slår sammen uten datatap).
Delbar som hovedregel (shared-flagg default true) med toggle for privat;
andres delte planer kan ses skrivebeskyttet via "Plan for"-velger.
getPlanFor håndhever tilgang. Per-oppgave plan-badge (dag + person) via
planIndex filtrert per viewer.
Egendefinerte kategorier (categories.json) i tillegg til de 12 innebygde;
dynamisk validering, deles via WS, addes fra kategori-nedtrekket.
Feltmodus (body.field-mode): større berøringsmål for mobil, auto på <640px.
plans.json migreres array -> {name,items,shared} ved oppstart (bakoverkompat).
26 integrasjonstester (6 nye), alle grønne. README + CLAUDE.md oppdatert.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
be166034de
commit
bdeb6254b9
8 changed files with 724 additions and 147 deletions
|
|
@ -265,7 +265,94 @@ test('dagsplan: ugyldig dato faller tilbake til i dag', async () => {
|
|||
assert.match(data.date, /^\d{4}-\d{2}-\d{2}$/);
|
||||
});
|
||||
|
||||
test('websocket: dagsplan-endring pushes kun til samme bruker', async () => {
|
||||
test('dagsplan: navn lagres og hentes', async () => {
|
||||
const a = (await (await req('POST', '/api/tasks', { title: 'Navngitt plan-task' })).json()).task;
|
||||
let res = await req('PUT', '/api/plan', { date: '2026-06-20', items: [a.id], name: 'Vedlikeholdsvindu DC-OSL' });
|
||||
assert.equal((await res.json()).name, 'Vedlikeholdsvindu DC-OSL');
|
||||
res = await req('GET', '/api/plan?date=2026-06-20');
|
||||
const data = await res.json();
|
||||
assert.equal(data.name, 'Vedlikeholdsvindu DC-OSL');
|
||||
assert.deepEqual(data.items, [a.id]);
|
||||
});
|
||||
|
||||
test('dagsplan: flytt slår sammen til måldato og tømmer kilden', async () => {
|
||||
const a = (await (await req('POST', '/api/tasks', { title: 'Flytt A' })).json()).task;
|
||||
const b = (await (await req('POST', '/api/tasks', { title: 'Flytt B' })).json()).task;
|
||||
await req('PUT', '/api/plan', { date: '2026-07-01', items: [a.id], name: 'Mandagsplan' });
|
||||
await req('PUT', '/api/plan', { date: '2026-07-02', items: [b.id] });
|
||||
|
||||
let res = await req('PUT', '/api/plan/move', { from: '2026-07-01', to: '2026-07-02' });
|
||||
assert.equal(res.status, 200);
|
||||
const moved = await res.json();
|
||||
assert.deepEqual(moved.items, [b.id, a.id]); // måldatoens eksisterende først, så kildens
|
||||
|
||||
// Kilden er tom nå
|
||||
res = await req('GET', '/api/plan?date=2026-07-01');
|
||||
assert.deepEqual((await res.json()).items, []);
|
||||
});
|
||||
|
||||
test('dagsplan: deling — privat plan er skjult for andre, delt er synlig', async () => {
|
||||
const a = (await (await req('POST', '/api/tasks', { title: 'Delt plan-task' })).json()).task;
|
||||
// jon lager en delt plan (default shared=true)
|
||||
await req('PUT', '/api/plan', { date: '2026-07-10', items: [a.id] }, { 'X-Remote-User': 'jon' });
|
||||
// kari kan se den
|
||||
let res = await req('GET', '/api/plan?date=2026-07-10&user=jon', null, { 'X-Remote-User': 'kari' });
|
||||
assert.equal(res.status, 200);
|
||||
assert.deepEqual((await res.json()).items, [a.id]);
|
||||
|
||||
// jon gjør den privat
|
||||
res = await req('PUT', '/api/plan/share', { date: '2026-07-10', shared: false }, { 'X-Remote-User': 'jon' });
|
||||
assert.equal((await res.json()).shared, false);
|
||||
// kari får 403
|
||||
res = await req('GET', '/api/plan?date=2026-07-10&user=jon', null, { 'X-Remote-User': 'kari' });
|
||||
assert.equal(res.status, 403);
|
||||
// jon ser fortsatt sin egen
|
||||
res = await req('GET', '/api/plan?date=2026-07-10', null, { 'X-Remote-User': 'jon' });
|
||||
assert.deepEqual((await res.json()).items, [a.id]);
|
||||
});
|
||||
|
||||
test('dagsplan: planIndex viser delte planer + egne private', async () => {
|
||||
const a = (await (await req('POST', '/api/tasks', { title: 'Index-task' })).json()).task;
|
||||
// jon: privat plan på en egen dato
|
||||
await req('PUT', '/api/plan', { date: '2026-07-15', items: [a.id] }, { 'X-Remote-User': 'jon' });
|
||||
await req('PUT', '/api/plan/share', { date: '2026-07-15', shared: false }, { 'X-Remote-User': 'jon' });
|
||||
|
||||
// jon ser sin private i sin planIndex
|
||||
let res = await req('GET', '/api/state', null, { 'X-Remote-User': 'jon' });
|
||||
let idx = (await res.json()).planIndex;
|
||||
assert.ok(idx[a.id] && idx[a.id].some(e => e.user === 'jon' && e.date === '2026-07-15'));
|
||||
|
||||
// kari ser den IKKE (privat, ikke hennes)
|
||||
res = await req('GET', '/api/state', null, { 'X-Remote-User': 'kari' });
|
||||
idx = (await res.json()).planIndex;
|
||||
assert.ok(!idx[a.id] || !idx[a.id].some(e => e.user === 'jon' && e.date === '2026-07-15'));
|
||||
});
|
||||
|
||||
test('kategorier: legg til egendefinert, og bruk den på en oppgave', async () => {
|
||||
let res = await req('POST', '/api/categories', { name: 'Trådløst' });
|
||||
assert.equal(res.status, 201);
|
||||
const { category } = await res.json();
|
||||
assert.match(category.id, /^[A-Z0-9]+$/);
|
||||
assert.equal(category.name, 'Trådløst');
|
||||
|
||||
// Kategorien er nå i /api/state
|
||||
res = await req('GET', '/api/state');
|
||||
const cats = (await res.json()).categories;
|
||||
assert.ok(cats.some(c => c.id === category.id && c.custom));
|
||||
|
||||
// Den kan brukes på en oppgave (godtas av validering)
|
||||
res = await req('POST', '/api/tasks', { title: 'Wifi-oppgave', category: category.id });
|
||||
assert.equal((await res.json()).task.category, category.id);
|
||||
});
|
||||
|
||||
test('kategorier: tomt navn avvises, ukjent kategori-id på oppgave faller til default', async () => {
|
||||
let res = await req('POST', '/api/categories', { name: ' ' });
|
||||
assert.equal(res.status, 400);
|
||||
res = await req('POST', '/api/tasks', { title: 'Ukjent kat', category: 'FINNESIKKE' });
|
||||
assert.equal((await res.json()).task.category, 'L3');
|
||||
});
|
||||
|
||||
test('websocket: delt plan broadcastes til alle, privat kun til eier', async () => {
|
||||
const wsUrl = baseUrl.replace('http', 'ws') + '/ws';
|
||||
const mk = user => new Promise((resolve, reject) => {
|
||||
const c = new WebSocket(wsUrl, { headers: { 'X-Remote-User': user } });
|
||||
|
|
@ -278,12 +365,21 @@ test('websocket: dagsplan-endring pushes kun til samme bruker', async () => {
|
|||
const kari = await mk('kari');
|
||||
await new Promise(r => setTimeout(r, 150));
|
||||
|
||||
const a = (await (await req('POST', '/api/tasks', { title: 'WS-plan' })).json()).task;
|
||||
await req('PUT', '/api/plan', { date: '2026-06-19', items: [a.id] });
|
||||
const a = (await (await req('POST', '/api/tasks', { title: 'WS-delt-plan' })).json()).task;
|
||||
// Delt plan (default) → begge får den
|
||||
await req('PUT', '/api/plan', { date: '2026-08-01', items: [a.id] });
|
||||
await new Promise(r => setTimeout(r, 150));
|
||||
assert.ok(jon.msgs.some(m => m.type === 'plan' && m.date === '2026-08-01' && m.items.includes(a.id)), 'jon fikk delt plan');
|
||||
assert.ok(kari.msgs.some(m => m.type === 'plan' && m.date === '2026-08-01' && m.items.includes(a.id)), 'kari fikk delt plan');
|
||||
|
||||
assert.ok(jon.msgs.some(m => m.type === 'plan' && m.items.includes(a.id)), 'jon fikk plan-push');
|
||||
assert.ok(!kari.msgs.some(m => m.type === 'plan'), 'kari skal ikke få plan-push');
|
||||
// Gjør privat → kari får tom melding (fjerner badge), jon får ekte
|
||||
jon.msgs.length = 0; kari.msgs.length = 0;
|
||||
await req('PUT', '/api/plan/share', { date: '2026-08-01', shared: false });
|
||||
await new Promise(r => setTimeout(r, 150));
|
||||
const jonMsg = jon.msgs.find(m => m.type === 'plan' && m.date === '2026-08-01');
|
||||
const kariMsg = kari.msgs.find(m => m.type === 'plan' && m.date === '2026-08-01');
|
||||
assert.ok(jonMsg && jonMsg.items.includes(a.id), 'jon ser fortsatt sin private plan');
|
||||
assert.ok(kariMsg && kariMsg.items.length === 0, 'kari får tom plan-melding for privat');
|
||||
|
||||
jon.close(); kari.close();
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue