const assert = require('node:assert/strict'); const fs = require('node:fs'); const vm = require('node:vm'); const nodes = {}; const get = id => nodes[id] ||= {value:'', hidden:false, className:'', innerHTML:'',textContent:'',focus(){},closest(){return this;}}; const requests=[]; let replies=[]; const ctx = {crypto:{randomUUID:()=> '11111111-1111-4111-8111-111111111111'}, document:{getElementById:get},URLSearchParams, fetch:async url=>{requests.push(url); return {ok:true,json:async()=>replies.shift()};}}; vm.createContext(ctx); vm.runInContext(fs.readFileSync('static/js/hardware-intake.js','utf8'),ctx); (async()=>{ vm.runInContext("hardwareIntake.mode='invoice'",ctx); ctx.showHardwareInvoicePreview({preview_id:'p',header:{invoice_number:'F123',currency:'DKK'},created_units:{'7':[10]}, lines:[{key:'7',quantity:'3',unit_price:'0',description:'PC '}]}); ctx.chooseHardwareInvoiceLine(0); assert.equal(get('intake-count').value,2); assert.equal(get('intake-unit-cost').value,'0'); assert.equal(get('model').value,'PC '); assert.equal(get('intake-serials-group').hidden,false); get('intake-serials').value='SNA\nSNB'; get('intake-currency').value='DKK'; const payload=ctx.hardwareIntakePayload({asset_type:'pc'}); assert.equal(payload.preview_id,'p'); assert.equal(payload.count,2); assert.deepEqual(Array.from(payload.serial_numbers),['SNA','SNB']); assert(get('intake-invoice-lines').innerHTML.includes('PC <safe>')); ctx.setHardwareIntakeMode('manual'); assert.equal(ctx.hardwareIntakePayload({asset_type:'pc'}).preview_id,null); vm.runInContext("hardwareIntake.mode='barcode'",ctx); get('intake-code').value='0012345678901'; replies=[[],{products:[{name:'Test PC',sku:'SKU',ean:'0012345678901',manufacturer:'Maker',price:100,currency:'DKK'}]}]; await ctx.lookupHardwareCode(); assert(requests[0].includes('0012345678901')); assert(requests[1].includes('/apigateway/search')); ctx.chooseHardwareProduct(0); assert.equal(get('brand').value,'Maker'); assert.equal(get('model').value,'Test PC'); assert.equal(ctx.hardwareIntakePayload({asset_type:'pc'}).code,'0012345678901'); assert.equal(get('serial_number').value,''); // Selected machine data and serial are retained until another source is selected. vm.runInContext("hardwareIntake.inventoryItems=[{name:'Mac <12>',brand:'Apple',model:'Mac mini',serial_number:'SN12',hardware_specs:{gateway_inventory:{id:12}}}]",ctx); ctx.chooseHardwareInventory(0); assert.equal(get('serial_number').value,'SN12'); get('intake-currency').value='DKK'; vm.runInContext("hardwareIntake.mode='manual'",ctx); assert.equal(ctx.hardwareIntakePayload({asset_type:'pc'}).hardware.hardware_specs.gateway_inventory.id,12); ctx.setHardwareIntakeMode('manual'); assert.equal(ctx.hardwareIntakePayload({asset_type:'pc'}).hardware.hardware_specs,undefined); vm.runInContext("hardwareIntake.pairHardwareId=4;hardwareIntake.inventoryItems=[{id:12,name:'PC12'}]",ctx); let reloaded = false; ctx.window = {location:{reload(){reloaded=true;}}}; const writes = []; ctx.fetch = async (url, options) => {writes.push({url,options});return {ok:true,json:async()=>({hardware_id:4})};}; await ctx.chooseHardwareInventory(0); assert.equal(writes[0].url,'/api/v1/hardware-intake/inventory/4/pair'); assert.equal(JSON.parse(writes[0].options.body).inventory_id,12); assert.equal(reloaded,true); await ctx.unpairHardwareInventory(); assert.equal(writes[1].options.method,'DELETE'); ctx.fetch = async () => ({ok:false,json:async()=>({detail:'Gateway utilgængelig'})}); reloaded = false; await ctx.chooseHardwareInventory(0); assert.equal(reloaded,false); assert.equal(get('intake-message').textContent,'Gateway utilgængelig'); console.log('Hardware intake UI OK: invoice quantities, serials, source reset, code lookup and EAN preservation'); })().catch(error=>{console.error(error);process.exitCode=1;});