const {test} = require('node:test'); const assert = require('node:assert/strict'); const vm = require('node:vm'); const fs = require('node:fs'); function setup(responses = [{ok:true}], confirm = true) { const handlers = {}, calls = [], messages = []; let reloads = 0; const card = {dataset:{moveId:'8',version:'4',start:'2026-10-20T08:00:00',end:'2026-10-20T09:00:00',segmentStart:'2026-10-20T08:00:00'},classList:{add(){},remove(){}},getBoundingClientRect:()=>({top:196})}; const day = {dataset:{date:'2026-10-21'},classList:{add(){},remove(){}},getBoundingClientRect:()=>({top:100})}; const host = {dataset:{firstHour:'7'},addEventListener:(name,fn)=>{handlers[name]=fn;},querySelectorAll:()=>[]}; vm.runInNewContext(fs.readFileSync('static/js/case-slot-move.js','utf8'),{ document:{getElementById:()=>host}, window:{confirm:()=>confirm,showCaseFeedback:message=>messages.push(message),loadEmbeddedCasePlanner:async()=>{reloads++;}}, fetch:async(url,options)=>{calls.push({url,body:JSON.parse(options.body)});const r=responses.shift();return {...r,json:async()=>r.body||{}};}, }); const event = (target,y) => ({target:{closest:selector=>selector==='.case-slot-resize'?null:target},clientY:y,preventDefault(){},stopPropagation(){},dataTransfer:{setData(){}}}); return {handlers,calls,messages,card,day,event,reloads:()=>reloads}; } test('move to another day and time preserves duration and version',async()=>{ const s=setup(); s.handlers.dragstart(s.event(s.card,208)); // Grab 7.5 minutes below start. await s.handlers.drop(s.event(s.day,400)); assert.equal(s.calls[0].url,'/api/v1/planner/allocations/8'); assert.equal(s.calls[0].body.version,4); const start=new Date(s.calls[0].body.starts_at),end=new Date(s.calls[0].body.ends_at); assert.equal(start.getDate(),21); assert.equal(start.getHours(),10); assert.equal(start.getMinutes(),0); assert.equal(end-start,3600000); assert.equal(s.reloads(),1); }); test('cancelled drag and unrelated drops do not write',async()=>{ const s=setup(); s.handlers.dragstart(s.event(s.card,196)); s.handlers.dragend(); await s.handlers.drop(s.event(s.day,400)); assert.equal(s.calls.length,0); }); test('overlap can be confirmed; stale version is never forced',async()=>{ const s=setup([{ok:false,status:409,body:{detail:{message:'Overlap',conflict_status:'overlap'}}},{ok:true}]); s.handlers.dragstart(s.event(s.card,196)); await s.handlers.drop(s.event(s.day,400)); assert.equal(s.calls.length,2); assert.equal(s.calls[1].body.confirm_conflicts,true); const stale=setup([{ok:false,status:409,body:{detail:{message:'Ændret af anden bruger',current:{version:5}}}}]); stale.handlers.dragstart(stale.event(stale.card,196)); await stale.handlers.drop(stale.event(stale.day,400)); assert.equal(stale.calls.length,1); assert.equal(stale.messages[0],'Ændret af anden bruger'); }); test('declining overlap restores server state',async()=>{ const s=setup([{ok:false,status:409,body:{detail:{message:'Overlap',conflict_status:'overlap'}}}],false); s.handlers.dragstart(s.event(s.card,196)); await s.handlers.drop(s.event(s.day,400)); assert.equal(s.calls.length,1); assert.equal(s.reloads(),1); assert.equal(s.messages[0],'Overlap'); });