@@ -325,6 +325,78 @@ describe('in-page channel over bring-your-own ports', () => {
325325} )
326326
327327describe ( 'in-page channel shared state' , ( ) => {
328+ it . each ( [ 'patch' , 'full state' ] as const ) ( 'round-trips %s updates through both endpoint codecs' , async ( mode ) => {
329+ function codec ( sender : string , receiver : string ) {
330+ return {
331+ serialize : vi . fn ( value => ( { encodedBy : sender , value } ) ) ,
332+ deserialize : vi . fn ( ( wire : unknown ) => {
333+ expect ( wire ) . toHaveProperty ( 'encodedBy' , receiver )
334+ return ( wire as { value : unknown } ) . value
335+ } ) ,
336+ }
337+ }
338+ const pageCodec = codec ( 'page-script' , 'panel' )
339+ const panelCodec = codec ( 'panel' , 'page-script' )
340+ const { pageScript, panel, dispose } = createLinkedPair ( { pageScript : pageCodec , panel : panelCodec } )
341+ try {
342+ const authority = await pageScript . sharedState . get ( 'doc' , { initialValue : { count : 1 } } )
343+ const mirror = await panel . sharedState . get ( 'doc' )
344+ expect ( mirror . value ( ) ) . toEqual ( { count : 1 } )
345+ expect ( pageCodec . serialize ) . toHaveBeenCalledWith ( { count : 1 } )
346+ expect ( panelCodec . serialize . mock . calls [ 0 ] ?. [ 0 ] ) . toBe ( 'doc' )
347+
348+ function update ( state : typeof authority , count : number ) {
349+ // With enablePatches=true, mutate() emits patch arrays, while patch() emits an update with patches=undefined (full-state path).
350+ if ( mode === 'patch' ) {
351+ state . mutate ( ( draft ) => {
352+ draft . count = count
353+ } )
354+ }
355+ else {
356+ state . patch ( [ { op : 'replace' , path : [ 'count' ] , value : count } ] )
357+ }
358+ }
359+
360+ update ( authority , 2 )
361+ await until ( ( ) => mirror . value ( ) . count === 2 )
362+ update ( mirror , 3 )
363+ await until ( ( ) => authority . value ( ) . count === 3 )
364+ await panel . call ( 'echo' , 'flushed' )
365+ expect ( authority . value ( ) ) . toEqual ( { count : 3 } )
366+ expect ( mirror . value ( ) ) . toEqual ( { count : 3 } )
367+ }
368+ finally {
369+ dispose ( )
370+ }
371+ } )
372+
373+ it ( 'deserializes both subscription snapshots and subsequent notifications' , async ( ) => {
374+ function restore ( value : unknown ) : unknown {
375+ if ( Array . isArray ( value ) )
376+ return value . map ( restore )
377+ if ( value && typeof value === 'object' ) {
378+ const restored = Object . fromEntries ( Object . entries ( value ) . map ( ( [ key , item ] ) => [ key , restore ( item ) ] ) )
379+ return 'count' in restored ? { ...restored , label : 'restored' } : restored
380+ }
381+ return value
382+ }
383+ const { pageScript, panel, dispose } = createLinkedPair ( {
384+ panel : { deserialize : restore } ,
385+ } )
386+ try {
387+ const authority = await pageScript . sharedState . get ( 'doc' , { initialValue : { count : 1 } } )
388+ const mirror = await panel . sharedState . get ( 'doc' )
389+ expect ( mirror . value ( ) ) . toEqual ( { count : 1 , label : 'restored' } )
390+
391+ authority . mutate ( ( ) => ( { count : 2 } ) )
392+ await until ( ( ) => mirror . value ( ) . count === 2 )
393+ expect ( mirror . value ( ) ) . toEqual ( { count : 2 , label : 'restored' } )
394+ }
395+ finally {
396+ dispose ( )
397+ }
398+ } )
399+
328400 it ( 'seeds an equal snapshot and skips unchanged writes on both endpoints' , async ( ) => {
329401 const { pageScript, panel, dispose } = createLinkedPair ( )
330402 try {
0 commit comments