auto-interpretation.test.mjs (3803B)
1 import test from 'node:test'; 2 import assert from 'node:assert/strict'; 3 import {Graph} from '../dist/graph.js'; 4 import {makeLiteral,readableText,valueText,view,outputType,natural,list,DECODED_LIST_LIMIT} from '../dist/codecs.js'; 5 import {autoInterpretation} from '../dist/auto-interpretation.js'; 6 const n=value=>({type:'number',value:String(value)}),s=value=>({type:'string',value}); 7 const auto=literal=>{const g=new Graph(),h=makeLiteral(g,literal);return autoInterpretation(type=>Promise.resolve(view(g,h,type)),()=>false);}; 8 test('Auto chooses readable text, naturals, decoded lists, Boolean and source',async()=>{ 9 for(const text of ['hello','ฮป้ช๐','a\nb\t','๐จโ๐ฉโ๐งโ๐ฆ'])assert.deepEqual(await auto(s(text)),{type:'string',value:text}); 10 assert.deepEqual(await auto(n(42)),{type:'number',value:'42'}); 11 assert.deepEqual(await auto(s('')),{type:'number',value:'0'}); 12 assert.deepEqual(await auto({type:'bool',value:false}),{type:'number',value:'0'}); 13 assert.deepEqual(await auto({type:'bool',value:true}),{type:'bool',value:true}); 14 assert.deepEqual(await auto({type:'list',value:[s('hello'),n(42),n(2n**100n),s('')]}),{type:'values',value:[s('hello'),n(42),n(2n**100n),n(0)]}); 15 const bytes=Array(33).fill(128);assert.deepEqual(await auto({type:'bytes',value:bytes}),{type:'values',value:bytes.map(n)}); 16 assert.equal((await auto({type:'tree',value:'21100'})).type,'tricu'); 17 }); 18 test('readability is a preference, not a claim about types; explicit Text stays available',async()=>{ 19 for(const text of ['', '\0', '\x01', '\u202e', '\ue000','\u0378'])assert.equal(readableText(text),false,JSON.stringify(text)); 20 // The same list admits Unicode, byte and number-list views. Prefer readable text. 21 assert.deepEqual(await auto({type:'list',value:[n(65),n(66)]}),{type:'string',value:'AB'}); 22 const g=new Graph(),h=makeLiteral(g,s('\0'));assert.deepEqual(view(g,h,'string'),s('\0')); 23 assert.notEqual((await auto(s('\0'))).type,'string'); 24 }); 25 test('decoded lists preserve large natural values and fail closed on unsupported items/bounds',()=>{ 26 const g=new Graph(); 27 const h=makeLiteral(g,{type:'list',value:[s('a'),n(2n**100n)]}); 28 assert.equal(valueText(view(g,h,'values')),`["a" ${2n**100n}]`); 29 assert.throws(()=>view(g,makeLiteral(g,{type:'list',value:[{type:'bool',value:true}]}),'values'),/number/); 30 const boundary=list(g,Array(DECODED_LIST_LIMIT).fill(natural(g,42n))); 31 assert.equal(view(g,boundary,'values').value.length,4096); 32 assert.throws(()=>view(g,g.fork(g.leaf,boundary),'values'),/limit/); 33 assert.throws(()=>view(g,boundary,'list'),/limit/); // Tiny graph previews stay bounded. 34 assert.throws(()=>view(g,list(g,[list(g,Array(4097).fill(natural(g,65n)))]),'values'),/number bit/); 35 assert.throws(()=>outputType('record'),/Unsupported/); 36 }); 37 test('Auto stops on fatal errors, falls back once, and does not hide a failed tree observation',async()=>{ 38 const calls=[],fatal=Error('disposed'); 39 await assert.rejects(autoInterpretation(async type=>{calls.push(type);throw fatal;},()=>true),/disposed/); 40 assert.deepEqual(calls,['string']); 41 const attempted=[]; 42 await assert.rejects(autoInterpretation(async type=>{attempted.push(type);throw Error('observation failed');},()=>false),/observation failed/); 43 assert.deepEqual(attempted,['string','number','values','bool','bytes','tricu']); 44 }); 45 46 test('100 mixed FizzBuzz results decode explicitly and through Auto',async()=>{ 47 const expected=Array.from({length:100},(_,i)=>{const n=i+1;return n%15===0?s('fizzbuzz'):n%5===0?s('buzz'):n%3===0?s('fizz'):{type:'number',value:String(n)};}); 48 const literal={type:'list',value:expected},g=new Graph(),root=makeLiteral(g,literal); 49 assert.deepEqual(view(g,root,'values'),{type:'values',value:expected}); 50 assert.deepEqual(await auto(literal),{type:'values',value:expected}); 51 });