import { nbformat } from '@jupyterlab/coreutils' ; import * as assert from 'assert' ; import * as vscode from 'vscode' ; import { jupyterCellOutputToCellOutput , jupyterNotebookModelToNotebookData } from '../deserializers' ; function deepStripProperties ( obj , props ) { for ( let prop in obj ) { if ( obj [ prop ] ) { delete obj [ prop ] ; } else if ( typeof obj [ prop ] === 'object' ) { deepStripProperties ( obj [ prop ] , props ) ; } } } suite ( 'ipynb serializer' , ( ) => { const base64EncodedImage = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOUlZL6DwAB/wFSU1jVmgAAAABJRU5ErkJggg==' ; test ( 'Deserialize' , async ( ) => { const cells = [ { cell_type : 'code' , execution_count : 10 , outputs : [ ] , source : 'print(1)' , metadata : { } } , { cell_type : 'markdown' , source : '# HEAD' , metadata : { } } ] ; const notebook = jupyterNotebookModelToNotebookData ( { cells } , 'python' ) ; assert . ok ( notebook ) ; const expectedCodeCell = new vscode . NotebookCellData ( vscode . NotebookCellKind . Code , 'print(1)' , 'python' ) ; expectedCodeCell . outputs = [ ] ; expectedCodeCell . metadata = { custom : { metadata : { } } } ; expectedCodeCell . executionSummary = { executionOrder : 10 } ; const expectedMarkdownCell = new vscode . NotebookCellData ( vscode . NotebookCellKind . Markup , '# HEAD' , 'markdown' ) ; expectedMarkdownCell . outputs = [ ] ; expectedMarkdownCell . metadata = { custom : { metadata : { } } } ; assert . deepStrictEqual ( notebook . cells , [ expectedCodeCell , expectedMarkdownCell ] ) ; } ) ; suite ( 'Outputs' , ( ) => { function validateCellOutputTranslation ( outputs , expectedOutputs , propertiesToExcludeFromComparison = [ ] ) { const cells = [ { cell_type : 'code' , execution_count : 10 , outputs , source : 'print(1)' , metadata : { } } ] ; const notebook = jupyterNotebookModelToNotebookData ( { cells } , 'python' ) ; const propertiesToExclude = propertiesToExcludeFromComparison . concat ( [ 'id' ] ) ; const actualOuts = notebook . cells [ 0 ] . outputs ; deepStripProperties ( actualOuts , propertiesToExclude ) ; deepStripProperties ( expectedOutputs , propertiesToExclude ) ; assert . deepStrictEqual ( actualOuts , expectedOutputs ) ; } test ( 'Empty output' , ( ) => { validateCellOutputTranslation ( [ ] , [ ] ) ; } ) ; test ( 'Stream output' , ( ) => { validateCellOutputTranslation ( [ { output_type : 'stream' , name : 'stderr' , text : 'Error' } , { output_type : 'stream' , name : 'stdout' , text : 'NoError' } ] , [ new vscode . NotebookCellOutput ( [ vscode . NotebookCellOutputItem . stderr ( 'Error' ) ] , { outputType : 'stream' } ) , new vscode . NotebookCellOutput ( [ vscode . NotebookCellOutputItem . stdout ( 'NoError' ) ] , { outputType : 'stream' } ) ] ) ; } ) ; test ( 'Stream output and line endings' , ( ) => { validateCellOutputTranslation ( [ { output_type : 'stream' , name : 'stdout' , text : [ 'Line1\n' , '\n' , 'Line3\n' , 'Line4' ] } ] , [ new vscode . NotebookCellOutput ( [ vscode . NotebookCellOutputItem . stdout ( 'Line1\n\nLine3\nLine4' ) ] , { outputType : 'stream' } ) ] ) ; validateCellOutputTranslation ( [ { output_type : 'stream' , name : 'stdout' , text : [ 'Hello\n' , 'Hello\n' , 'Hello\n' , 'Hello\n' , 'Hello\n' , 'Hello\n' ] } ] , [ new vscode . NotebookCellOutput ( [ vscode . NotebookCellOutputItem . stdout ( 'Hello\nHello\nHello\nHello\nHello\nHello\n' ) ] , { outputType : 'stream' } ) ] ) ; } ) ; test ( 'Multi-line Stream output' , ( ) => { validateCellOutputTranslation ( [ { name : 'stdout' , output_type : 'stream' , text : [ 'Epoch 1/5\n' , '...\n' , 'Epoch 2/5\n' , '...\n' , 'Epoch 3/5\n' , '...\n' , 'Epoch 4/5\n' , '...\n' , 'Epoch 5/5\n' , '...\n' ] } ] , [ new vscode . NotebookCellOutput ( [ vscode . NotebookCellOutputItem . stdout ( [ 'Epoch 1/5\n' , '...\n' , 'Epoch 2/5\n' , '...\n' , 'Epoch 3/5\n' , '...\n' , 'Epoch 4/5\n' , '...\n' , 'Epoch 5/5\n' , '...\n' ] . join ( '' ) ) ] , { outputType : 'stream' } ) ] ) ; } ) ; test ( 'Multi-line Stream output (last empty line should not be saved in ipynb)' , ( ) => { validateCellOutputTranslation ( [ { name : 'stderr' , output_type : 'stream' , text : [ 'Epoch 1/5\n' , '...\n' , 'Epoch 2/5\n' , '...\n' , 'Epoch 3/5\n' , '...\n' , 'Epoch 4/5\n' , '...\n' , 'Epoch 5/5\n' , '...\n' ] } ] , [ new vscode . NotebookCellOutput ( [ vscode . NotebookCellOutputItem . stderr ( [ 'Epoch 1/5\n' , '...\n' , 'Epoch 2/5\n' , '...\n' , 'Epoch 3/5\n' , '...\n' , 'Epoch 4/5\n' , '...\n' , 'Epoch 5/5\n' , '...\n' , '\n' ] . join ( '' ) ) ] , { outputType : 'stream' } ) ] ) ; } ) ; test ( 'Streamed text with Ansi characters' , async ( ) => { validateCellOutputTranslation ( [ { name : 'stderr' , text : '\u001b[K\u001b[33m✅ \u001b[0m Loading\n' , output_type : 'stream' } ] , [ new vscode . NotebookCellOutput ( [ vscode . NotebookCellOutputItem . stderr ( '\u001b[K\u001b[33m✅ \u001b[0m Loading\n' ) ] , { outputType : 'stream' } ) ] ) ; } ) ; test ( 'Streamed text with angle bracket characters' , async ( ) => { validateCellOutputTranslation ( [ { name : 'stderr' , text : '1 is < 2' , output_type : 'stream' } ] , [ new vscode . NotebookCellOutput ( [ vscode . NotebookCellOutputItem . stderr ( '1 is < 2' ) ] , { outputType : 'stream' } ) ] ) ; } ) ; test ( 'Streamed text with angle bracket characters and ansi chars' , async ( ) => { validateCellOutputTranslation ( [ { name : 'stderr' , text : '1 is < 2\u001b[K\u001b[33m✅ \u001b[0m Loading\n' , output_type : 'stream' } ] , [ new vscode . NotebookCellOutput ( [ vscode . NotebookCellOutputItem . stderr ( '1 is < 2\u001b[K\u001b[33m✅ \u001b[0m Loading\n' ) ] , { outputType : 'stream' } ) ] ) ; } ) ; test ( 'Error' , async ( ) => { validateCellOutputTranslation ( [ { ename : 'Error Name' , evalue : 'Error Value' , traceback : [ 'stack1' , 'stack2' , 'stack3' ] , output_type : 'error' } ] , [ new vscode . NotebookCellOutput ( [ vscode . NotebookCellOutputItem . error ( { name : 'Error Name' , message : 'Error Value' , stack : [ 'stack1' , 'stack2' , 'stack3' ] . join ( '\n' ) } ) ] , { outputType : 'error' , originalError : { ename : 'Error Name' , evalue : 'Error Value' , traceback : [ 'stack1' , 'stack2' , 'stack3' ] , output_type : 'error' } } ) ] ) ; } ) ; [ 'display_data' , 'execute_result' ] . forEach ( output_type => { suite ( `Rich output for output_type = ${ output_type }` , ( ) => { let propertiesToExcludeFromComparison = [ ] ; setup ( ( ) => { if ( output_type === 'display_data' ) { propertiesToExcludeFromComparison = [ 'execution_count' , 'executionCount' ] ; } } ) ; test ( 'Text mimeType output' , async ( ) => { validateCellOutputTranslation ( [ { data : { 'text/plain' : 'Hello World!' } , output_type , metadata : { } , execution_count : 1 } ] , [ new vscode . NotebookCellOutput ( [ new vscode . NotebookCellOutputItem ( Buffer . from ( 'Hello World!' , 'utf8' ) , 'text/plain' ) ] , { outputType : output_type , metadata : { } , executionCount : 1 } ) ] , propertiesToExcludeFromComparison ) ; } ) ; test ( 'png,jpeg images' , async ( ) => { validateCellOutputTranslation ( [ { execution_count : 1 , data : { 'image/png' : base64EncodedImage , 'image/jpeg' : base64EncodedImage } , metadata : { } , output_type } ] , [ new vscode . NotebookCellOutput ( [ new vscode . NotebookCellOutputItem ( Buffer . from ( base64EncodedImage , 'base64' ) , 'image/png' ) , new vscode . NotebookCellOutputItem ( Buffer . from ( base64EncodedImage , 'base64' ) , 'image/jpeg' ) ] , { executionCount : 1 , outputType : output_type , metadata : { } } ) ] , propertiesToExcludeFromComparison ) ; } ) ; test ( 'png image with a light background' , async ( ) => { validateCellOutputTranslation ( [ { execution_count : 1 , data : { 'image/png' : base64EncodedImage } , metadata : { needs_background : 'light' } , output_type } ] , [ new vscode . NotebookCellOutput ( [ new vscode . NotebookCellOutputItem ( Buffer . from ( base64EncodedImage , 'base64' ) , 'image/png' ) ] , { executionCount : 1 , metadata : { needs_background : 'light' } , outputType : output_type } ) ] , propertiesToExcludeFromComparison ) ; } ) ; test ( 'png image with a dark background' , async ( ) => { validateCellOutputTranslation ( [ { execution_count : 1 , data : { 'image/png' : base64EncodedImage } , metadata : { needs_background : 'dark' } , output_type } ] , [ new vscode . NotebookCellOutput ( [ new vscode . NotebookCellOutputItem ( Buffer . from ( base64EncodedImage , 'base64' ) , 'image/png' ) ] , { executionCount : 1 , metadata : { needs_background : 'dark' } , outputType : output_type } ) ] , propertiesToExcludeFromComparison ) ; } ) ; test ( 'png image with custom dimensions' , async ( ) => { validateCellOutputTranslation ( [ { execution_count : 1 , data : { 'image/png' : base64EncodedImage } , metadata : { 'image/png' : { height : '111px' , width : '999px' } } , output_type } ] , [ new vscode . NotebookCellOutput ( [ new vscode . NotebookCellOutputItem ( Buffer . from ( base64EncodedImage , 'base64' ) , 'image/png' ) ] , { executionCount : 1 , metadata : { 'image/png' : { height : '111px' , width : '999px' } } , outputType : output_type } ) ] , propertiesToExcludeFromComparison ) ; } ) ; test ( 'png allowed to scroll' , async ( ) => { validateCellOutputTranslation ( [ { execution_count : 1 , data : { 'image/png' : base64EncodedImage } , metadata : { unconfined : true , 'image/png' : { width : '999px' } } , output_type } ] , [ new vscode . NotebookCellOutput ( [ new vscode . NotebookCellOutputItem ( Buffer . from ( base64EncodedImage , 'base64' ) , 'image/png' ) ] , { executionCount : 1 , metadata : { unconfined : true , 'image/png' : { width : '999px' } } , outputType : output_type } ) ] , propertiesToExcludeFromComparison ) ; } ) ; } ) ; } ) ; } ) ; suite ( 'Output Order' , ( ) => { test ( 'Verify order of outputs' , async ( ) => { const dataAndExpectedOrder = [ { output : { data : { 'application/vnd.vegalite.v4+json' : 'some json' , 'text/html' : '<a>Hello</a>' } , metadata : { } , output_type : 'display_data' } , expectedMimeTypesOrder : [ 'application/vnd.vegalite.v4+json' , 'text/html' ] } , { output : { data : { 'application/vnd.vegalite.v4+json' : 'some json' , 'application/javascript' : 'some js' , 'text/plain' : 'some text' , 'text/html' : '<a>Hello</a>' } , metadata : { } , output_type : 'display_data' } , expectedMimeTypesOrder : [ 'application/vnd.vegalite.v4+json' , 'text/html' , 'application/javascript' , 'text/plain' ] } , { output : { data : { 'application/vnd.vegalite.v4+json' : '' , 'application/javascript' : 'some js' , 'text/plain' : 'some text' , 'text/html' : '<a>Hello</a>' } , metadata : { } , output_type : 'display_data' } , expectedMimeTypesOrder : [ 'text/html' , 'application/javascript' , 'text/plain' , 'application/vnd.vegalite.v4+json' ] } , { output : { data : { 'text/plain' : 'some text' , 'text/html' : '<a>Hello</a>' } , metadata : { } , output_type : 'display_data' } , expectedMimeTypesOrder : [ 'text/html' , 'text/plain' ] } , { output : { data : { 'application/javascript' : 'some js' , 'text/plain' : 'some text' } , metadata : { } , output_type : 'display_data' } , expectedMimeTypesOrder : [ 'application/javascript' , 'text/plain' ] } , { output : { data : { 'image/svg+xml' : 'some svg' , 'text/plain' : 'some text' } , metadata : { } , output_type : 'display_data' } , expectedMimeTypesOrder : [ 'image/svg+xml' , 'text/plain' ] } , { output : { data : { 'text/latex' : 'some latex' , 'text/plain' : 'some text' } , metadata : { } , output_type : 'display_data' } , expectedMimeTypesOrder : [ 'text/latex' , 'text/plain' ] } , { output : { data : { 'application/vnd.jupyter.widget-view+json' : 'some widget' , 'text/plain' : 'some text' } , metadata : { } , output_type : 'display_data' } , expectedMimeTypesOrder : [ 'application/vnd.jupyter.widget-view+json' , 'text/plain' ] } , { output : { data : { 'text/plain' : 'some text' , 'image/svg+xml' : 'some svg' , 'image/png' : 'some png' } , metadata : { } , output_type : 'display_data' } , expectedMimeTypesOrder : [ 'image/png' , 'image/svg+xml' , 'text/plain' ] } ] ; dataAndExpectedOrder . forEach ( ( { output , expectedMimeTypesOrder } ) => { const sortedOutputs = jupyterCellOutputToCellOutput ( output ) ; const mimeTypes = sortedOutputs . items . map ( ( item ) => item . mime ) . join ( ',' ) ; assert . equal ( mimeTypes , expectedMimeTypesOrder . join ( ',' ) ) ; } ) ; } ) ; } ) ; } ) ;