@@ -513,6 +513,54 @@ suite('DeepnoteDataConverter', () => {
513513 assert . strictEqual ( new TextDecoder ( ) . decode ( markdownItem ! . data ) , markdownContent ) ;
514514 assert . strictEqual ( new TextDecoder ( ) . decode ( plainItem ! . data ) , 'Result\n\nThis is formatted output.' ) ;
515515 } ) ;
516+
517+ test ( 'converts Plotly chart output' , ( ) => {
518+ const plotlyData = {
519+ data : [
520+ {
521+ type : 'bar' ,
522+ x : [ 'A' , 'B' , 'C' ] ,
523+ y : [ 10 , 20 , 15 ]
524+ }
525+ ] ,
526+ layout : {
527+ title : 'Sample Chart' ,
528+ xaxis : { title : 'Category' } ,
529+ yaxis : { title : 'Value' }
530+ }
531+ } ;
532+
533+ const deepnoteOutputs : DeepnoteOutput [ ] = [
534+ {
535+ output_type : 'execute_result' ,
536+ execution_count : 1 ,
537+ data : {
538+ 'application/vnd.plotly.v1+json' : plotlyData
539+ }
540+ }
541+ ] ;
542+
543+ const blocks : DeepnoteBlock [ ] = [
544+ {
545+ blockGroup : 'test-group' ,
546+ id : 'block1' ,
547+ type : 'code' ,
548+ content : 'fig.show()' ,
549+ sortingKey : 'a0' ,
550+ outputs : deepnoteOutputs
551+ }
552+ ] ;
553+
554+ const cells = converter . convertBlocksToCells ( blocks ) ;
555+ const outputs = cells [ 0 ] . outputs ! ;
556+
557+ assert . strictEqual ( outputs . length , 1 ) ;
558+ assert . strictEqual ( outputs [ 0 ] . items . length , 1 ) ;
559+ assert . strictEqual ( outputs [ 0 ] . items [ 0 ] . mime , 'application/vnd.plotly.v1+json' ) ;
560+
561+ const outputData = JSON . parse ( new TextDecoder ( ) . decode ( outputs [ 0 ] . items [ 0 ] . data ) ) ;
562+ assert . deepStrictEqual ( outputData , plotlyData ) ;
563+ } ) ;
516564 } ) ;
517565
518566 suite ( 'round trip conversion' , ( ) => {
@@ -597,6 +645,66 @@ suite('DeepnoteDataConverter', () => {
597645 assert . deepStrictEqual ( output . data ?. [ 'application/vnd.deepnote.sql-output-metadata+json' ] , sqlMetadata ) ;
598646 } ) ;
599647
648+ test ( 'Plotly chart output round-trips correctly' , ( ) => {
649+ const plotlyData = {
650+ data : [
651+ {
652+ type : 'histogram' ,
653+ x : [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ] ,
654+ nbinsx : 30 ,
655+ opacity : 0.75
656+ }
657+ ] ,
658+ layout : {
659+ title : 'Sessions per week by churn status' ,
660+ xaxis : { title : 'Sessions per week' } ,
661+ yaxis : { title : 'Users' } ,
662+ legend : {
663+ yanchor : 'top' ,
664+ y : 1 ,
665+ xanchor : 'left' ,
666+ x : 1.02
667+ }
668+ }
669+ } ;
670+
671+ const originalBlocks : DeepnoteBlock [ ] = [
672+ {
673+ blockGroup : 'test-group' ,
674+ id : 'plotly-block' ,
675+ type : 'code' ,
676+ content : 'fig = px.histogram(df)\nfig.show()' ,
677+ sortingKey : 'a0' ,
678+ executionCount : 1 ,
679+ metadata : { } ,
680+ outputs : [
681+ {
682+ output_type : 'execute_result' ,
683+ execution_count : 1 ,
684+ data : {
685+ 'application/vnd.plotly.v1+json' : plotlyData
686+ }
687+ }
688+ ]
689+ }
690+ ] ;
691+
692+ const cells = converter . convertBlocksToCells ( originalBlocks ) ;
693+ const roundTripBlocks = converter . convertCellsToBlocks ( cells ) ;
694+
695+ // The round-trip should preserve the Plotly chart output
696+ assert . strictEqual ( roundTripBlocks . length , 1 ) ;
697+ assert . strictEqual ( roundTripBlocks [ 0 ] . id , 'plotly-block' ) ;
698+ assert . strictEqual ( roundTripBlocks [ 0 ] . outputs ?. length , 1 ) ;
699+
700+ const output = roundTripBlocks [ 0 ] . outputs ! [ 0 ] as {
701+ output_type : string ;
702+ data ?: Record < string , unknown > ;
703+ } ;
704+ assert . strictEqual ( output . output_type , 'execute_result' ) ;
705+ assert . deepStrictEqual ( output . data ?. [ 'application/vnd.plotly.v1+json' ] , plotlyData ) ;
706+ } ) ;
707+
600708 test ( 'real deepnote notebook round-trips without losing data' , ( ) => {
601709 // Inline test data representing a real Deepnote notebook with various block types
602710 // blockGroup is an optional field not in the DeepnoteBlock interface, so we cast as any
0 commit comments