@@ -291,6 +291,135 @@ describe("TypeTaggedSchema Tests", () => {
291291 expect ( encoded ) . toEqual ( "d87a80" )
292292 expect ( decoded ) . toBeNull ( )
293293 } )
294+
295+ it ( "should preserve field order in structs with NullOr fields (regression test)" , ( ) => {
296+ // Regression test for field ordering bug with NullOr/UndefinedOr
297+ const CredentialSchema = TSchema . Union (
298+ TSchema . Struct ( { pubKeyHash : TSchema . ByteArray } , { flatFields : true } ) ,
299+ TSchema . Struct ( { scriptHash : TSchema . ByteArray } , { flatFields : true } )
300+ )
301+
302+ const AddressSchema = TSchema . Struct ( {
303+ paymentCredential : CredentialSchema ,
304+ stakeCredential : TSchema . NullOr ( TSchema . Integer )
305+ } )
306+
307+ const input = {
308+ paymentCredential : { pubKeyHash : fromHex ( "deadbeef" ) } ,
309+ stakeCredential : null
310+ }
311+
312+ const encoded = Data . withSchema ( AddressSchema ) . toData ( input )
313+ const decoded = Data . withSchema ( AddressSchema ) . fromData ( encoded )
314+
315+ // Verify roundtrip
316+ expect ( decoded ) . toEqual ( input )
317+
318+ // Verify field order in CBOR: paymentCredential should be field 0, stakeCredential field 1
319+ expect ( encoded . fields . length ) . toBe ( 2 )
320+ expect ( encoded . fields [ 0 ] ) . toBeInstanceOf ( Data . Constr ) // paymentCredential
321+ expect ( encoded . fields [ 1 ] ) . toBeInstanceOf ( Data . Constr ) // stakeCredential (null)
322+ expect ( ( encoded . fields [ 1 ] as Data . Constr ) . index ) . toBe ( 1n ) // null is Constr(1, [])
323+ } )
324+
325+ it ( "should preserve field order with multiple NullOr fields" , ( ) => {
326+ const TestSchema = TSchema . Struct ( {
327+ first : TSchema . Integer ,
328+ second : TSchema . NullOr ( TSchema . ByteArray ) ,
329+ third : TSchema . Boolean ,
330+ fourth : TSchema . NullOr ( TSchema . Integer )
331+ } )
332+
333+ const input = {
334+ first : 100n ,
335+ second : fromHex ( "cafe" ) ,
336+ third : true ,
337+ fourth : null
338+ }
339+
340+ const encoded = Data . withSchema ( TestSchema ) . toData ( input )
341+ const decoded = Data . withSchema ( TestSchema ) . fromData ( encoded )
342+
343+ expect ( decoded ) . toEqual ( input )
344+
345+ // Verify field order
346+ expect ( encoded . fields [ 0 ] ) . toBe ( 100n ) // first
347+ expect ( encoded . fields [ 1 ] ) . toBeInstanceOf ( Data . Constr ) // second (NullOr with value)
348+ expect ( encoded . fields [ 2 ] ) . toBeInstanceOf ( Data . Constr ) // third (Boolean)
349+ expect ( encoded . fields [ 3 ] ) . toBeInstanceOf ( Data . Constr ) // fourth (null)
350+ expect ( ( encoded . fields [ 3 ] as Data . Constr ) . index ) . toBe ( 1n ) // null
351+ } )
352+
353+ it ( "should preserve field order with UndefinedOr fields" , ( ) => {
354+ const TestSchema = TSchema . Struct ( {
355+ first : TSchema . ByteArray ,
356+ second : TSchema . UndefinedOr ( TSchema . Integer ) ,
357+ third : TSchema . Boolean
358+ } )
359+
360+ const inputWithValue = {
361+ first : fromHex ( "deadbeef" ) ,
362+ second : 42n ,
363+ third : false
364+ }
365+
366+ const encodedWithValue = Data . withSchema ( TestSchema ) . toData ( inputWithValue )
367+ const decodedWithValue = Data . withSchema ( TestSchema ) . fromData ( encodedWithValue )
368+
369+ expect ( decodedWithValue ) . toEqual ( inputWithValue )
370+
371+ // Verify field order when value is present
372+ expect ( encodedWithValue . fields [ 0 ] ) . toBeInstanceOf ( Uint8Array ) // first
373+ expect ( encodedWithValue . fields [ 1 ] ) . toBeInstanceOf ( Data . Constr ) // second (UndefinedOr with value)
374+ expect ( encodedWithValue . fields [ 2 ] ) . toBeInstanceOf ( Data . Constr ) // third (Boolean)
375+
376+ const inputUndefined = {
377+ first : fromHex ( "cafebabe" ) ,
378+ second : undefined ,
379+ third : true
380+ }
381+
382+ const encodedUndefined = Data . withSchema ( TestSchema ) . toData ( inputUndefined )
383+ const decodedUndefined = Data . withSchema ( TestSchema ) . fromData ( encodedUndefined )
384+
385+ expect ( decodedUndefined ) . toEqual ( inputUndefined )
386+
387+ // Verify field order when value is undefined
388+ expect ( encodedUndefined . fields [ 0 ] ) . toBeInstanceOf ( Uint8Array ) // first
389+ expect ( encodedUndefined . fields [ 1 ] ) . toBeInstanceOf ( Data . Constr ) // second (undefined)
390+ expect ( ( encodedUndefined . fields [ 1 ] as Data . Constr ) . index ) . toBe ( 1n ) // undefined is Constr(1, [])
391+ expect ( encodedUndefined . fields [ 2 ] ) . toBeInstanceOf ( Data . Constr ) // third (Boolean)
392+ } )
393+
394+ it ( "should preserve field order with mixed NullOr and UndefinedOr fields" , ( ) => {
395+ const TestSchema = TSchema . Struct ( {
396+ a : TSchema . Integer ,
397+ b : TSchema . NullOr ( TSchema . ByteArray ) ,
398+ c : TSchema . UndefinedOr ( TSchema . Integer ) ,
399+ d : TSchema . Boolean
400+ } )
401+
402+ const input = {
403+ a : 999n ,
404+ b : null ,
405+ c : undefined ,
406+ d : true
407+ }
408+
409+ const encoded = Data . withSchema ( TestSchema ) . toData ( input )
410+ const decoded = Data . withSchema ( TestSchema ) . fromData ( encoded )
411+
412+ expect ( decoded ) . toEqual ( input )
413+
414+ // Verify all fields are in correct order
415+ expect ( encoded . fields [ 0 ] ) . toBe ( 999n ) // a
416+ expect ( encoded . fields [ 1 ] ) . toBeInstanceOf ( Data . Constr ) // b (null)
417+ expect ( ( encoded . fields [ 1 ] as Data . Constr ) . index ) . toBe ( 1n ) // null
418+ expect ( encoded . fields [ 2 ] ) . toBeInstanceOf ( Data . Constr ) // c (undefined)
419+ expect ( ( encoded . fields [ 2 ] as Data . Constr ) . index ) . toBe ( 1n ) // undefined
420+ expect ( encoded . fields [ 3 ] ) . toBeInstanceOf ( Data . Constr ) // d (Boolean)
421+ expect ( ( encoded . fields [ 3 ] as Data . Constr ) . index ) . toBe ( 1n ) // true
422+ } )
294423 } )
295424
296425 describe ( "Literal Schema" , ( ) => {
0 commit comments