@@ -255,6 +255,70 @@ test('serializeBlocks serializes x and y for topLevel blocks with x,y of 0,0', t
255255 } ) ;
256256} ) ;
257257
258+ test ( 'serializeBlock does not serialize shadow blocks as top-level' , t => {
259+ const blocks = {
260+ hatBlock : {
261+ id : 'hatBlock' ,
262+ opcode : 'event_whenflagclicked' ,
263+ next : 'costumeBlock' ,
264+ parent : null ,
265+ inputs : { } ,
266+ fields : { } ,
267+ shadow : false ,
268+ topLevel : true ,
269+ x : 0 ,
270+ y : 0
271+ } ,
272+ costumeBlock : {
273+ id : 'costumeBlock' ,
274+ opcode : 'looks_switchcostumeto' ,
275+ next : null ,
276+ parent : 'hatBlock' ,
277+ inputs : {
278+ COSTUME : {
279+ name : 'COSTUME' ,
280+ block : 'varReporter' ,
281+ shadow : 'costumeShadow'
282+ }
283+ } ,
284+ fields : { } ,
285+ shadow : false ,
286+ topLevel : false
287+ } ,
288+ varReporter : {
289+ id : 'varReporter' ,
290+ opcode : 'data_variable' ,
291+ next : null ,
292+ parent : 'costumeBlock' ,
293+ inputs : { } ,
294+ fields : { VARIABLE : { name : 'VARIABLE' , value : 'my variable' , id : 'var1' } } ,
295+ shadow : false ,
296+ topLevel : false
297+ } ,
298+ costumeShadow : {
299+ id : 'costumeShadow' ,
300+ opcode : 'looks_costume' ,
301+ next : null ,
302+ parent : null ,
303+ inputs : { } ,
304+ fields : { COSTUME : { name : 'COSTUME' , value : 'costume1' } } ,
305+ shadow : true ,
306+ topLevel : true // bug: should not be serialized as top-level
307+ }
308+ } ;
309+
310+ const result = sb3 . serializeBlocks ( blocks ) ;
311+ const serialized = result [ 0 ] ;
312+
313+ t . equal ( serialized . costumeShadow . topLevel , false ,
314+ 'shadow block should not be serialized as top-level' ) ;
315+ t . notOk ( serialized . costumeShadow . x ,
316+ 'shadow block should not have x coordinate' ) ;
317+ t . notOk ( serialized . costumeShadow . y ,
318+ 'shadow block should not have y coordinate' ) ;
319+ t . end ( ) ;
320+ } ) ;
321+
258322test ( 'deserializeBlocks' , t => {
259323 const vm = new VirtualMachine ( ) ;
260324 vm . loadProject ( readFileToBuffer ( commentsSB3ProjectPath ) )
@@ -280,6 +344,45 @@ test('deserializeBlocks on already deserialized input', t => {
280344 } ) ;
281345} ) ;
282346
347+ test ( 'deserializeBlocks clears topLevel on shadow blocks' , t => {
348+ // Production Scratch can serialize obscured shadow blocks as top-level
349+ // (e.g. when a variable reporter covers a dropdown's shadow). Upstream
350+ // Blockly throws "Shadow block cannot be a top-level block" if these
351+ // survive into the XML, so deserialization must clear the flag.
352+ const blocks = {
353+ parentBlock : {
354+ opcode : 'looks_switchcostumeto' ,
355+ next : null ,
356+ parent : null ,
357+ inputs : {
358+ COSTUME : [ 3 , [ 12 , 'my variable' , 'var_id' ] , 'shadowBlock' ]
359+ } ,
360+ fields : { } ,
361+ shadow : false ,
362+ topLevel : true ,
363+ x : 0 ,
364+ y : 0
365+ } ,
366+ shadowBlock : {
367+ opcode : 'looks_costume' ,
368+ next : null ,
369+ parent : null ,
370+ inputs : { } ,
371+ fields : { COSTUME : [ 'costume1' , null ] } ,
372+ shadow : true ,
373+ topLevel : true ,
374+ x : 100 ,
375+ y : 100
376+ }
377+ } ;
378+
379+ sb3 . deserializeBlocks ( blocks ) ;
380+
381+ t . equal ( blocks . shadowBlock . topLevel , false ,
382+ 'shadow block should not be top-level after deserialization' ) ;
383+ t . end ( ) ;
384+ } ) ;
385+
283386test ( 'getExtensionIdForOpcode' , t => {
284387 t . equal ( sb3 . getExtensionIdForOpcode ( 'wedo_loopy' ) , 'wedo' ) ;
285388
0 commit comments