1+ // Copyright (c) Deepnote
2+ // Distributed under the terms of the Modified BSD License.
3+
4+ import { NotebookPicker } from '../../src/components/NotebookPicker' ;
5+ import { framePromise } from '@jupyterlab/testing' ;
6+ import { NotebookPanel } from '@jupyterlab/notebook' ;
7+ import { INotebookModel } from '@jupyterlab/notebook' ;
8+ import { Widget } from '@lumino/widgets' ;
9+ import { simulate } from 'simulate-event' ;
10+
11+ describe ( 'NotebookPicker' , ( ) => {
12+ let panel : NotebookPanel ;
13+ let model : INotebookModel ;
14+
15+ beforeEach ( async ( ) => {
16+ // Mock model + metadata
17+ model = {
18+ fromJSON : jest . fn ( ) ,
19+ get cells ( ) {
20+ return [ ] ;
21+ } ,
22+ dirty : true
23+ } as any ;
24+
25+ panel = {
26+ context : {
27+ ready : Promise . resolve ( ) ,
28+ model : {
29+ getMetadata : jest . fn ( ) . mockReturnValue ( {
30+ notebooks : {
31+ nb1 : { id : 'nb1' , name : 'nb1' , cells : [ { source : 'code' } ] } ,
32+ nb2 : { id : 'nb2' , name : 'nb2' , cells : [ ] }
33+ } ,
34+ notebook_names : [ 'nb1' , 'nb2' ]
35+ } )
36+ }
37+ } ,
38+ model
39+ } as any ;
40+
41+ // Attach to DOM
42+ const widget = new NotebookPicker ( panel ) ;
43+ // Override onAfterAttach to avoid errors from this.parent being null
44+ ( widget as any ) . onAfterAttach = jest . fn ( ) ;
45+ Widget . attach ( widget , document . body ) ;
46+ await framePromise ( ) ;
47+ } ) ;
48+
49+ afterEach ( ( ) => {
50+ document . body . innerHTML = '' ;
51+ jest . restoreAllMocks ( ) ;
52+ } ) ;
53+
54+ it ( 'should render a select element' , async ( ) => {
55+ await framePromise ( ) ; // wait for rendering
56+ const select = document . querySelector ( 'select' ) as HTMLSelectElement ;
57+ expect ( select ) . not . toBeNull ( ) ;
58+ expect ( select . options . length ) . toBe ( 2 ) ;
59+ expect ( select . options [ 0 ] && select . options [ 0 ] . value ) . toBe ( 'nb1' ) ;
60+ } ) ;
61+
62+ it ( 'should call fromJSON when selecting a notebook' , async ( ) => {
63+ const select = document . querySelector ( 'select' ) as HTMLSelectElement ;
64+ simulate ( select , 'change' , { target : { value : 'nb2' } } ) ;
65+ await framePromise ( ) ;
66+ expect ( model . fromJSON ) . toHaveBeenCalledWith (
67+ expect . objectContaining ( {
68+ cells : expect . any ( Array ) ,
69+ metadata : expect . objectContaining ( {
70+ deepnote : expect . objectContaining ( {
71+ notebooks : expect . any ( Object )
72+ } )
73+ } )
74+ } )
75+ ) ;
76+ } ) ;
77+
78+ it ( 'should not call fromJSON if selected notebook is invalid' , async ( ) => {
79+ const getMetadata = panel . context . model . getMetadata as jest . Mock ;
80+ getMetadata . mockReturnValue ( { notebooks : { } } ) ;
81+
82+ const select = document . querySelector ( 'select' ) as HTMLSelectElement ;
83+ simulate ( select , 'change' , { target : { value : 'nonexistent' } } ) ;
84+ await framePromise ( ) ;
85+ expect ( model . fromJSON ) . not . toHaveBeenCalled ( ) ;
86+ } ) ;
87+
88+ it ( 'should update UI after selection' , async ( ) => {
89+ const select = document . querySelector ( 'select' ) as HTMLSelectElement ;
90+ select . value = 'nb2' ;
91+ simulate ( select , 'change' ) ;
92+ await framePromise ( ) ;
93+ expect ( select . value ) . toBe ( 'nb2' ) ;
94+ } ) ;
95+
96+ it ( 'should handle empty metadata gracefully' , async ( ) => {
97+ const getMetadata = panel . context . model . getMetadata as jest . Mock ;
98+ getMetadata . mockReturnValue ( { notebooks : { } , notebook_names : [ ] } ) ;
99+
100+ document . body . innerHTML = '' ;
101+ const widget = new NotebookPicker ( panel ) ;
102+ // Override onAfterAttach to avoid errors from this.parent being null
103+ ( widget as any ) . onAfterAttach = jest . fn ( ) ;
104+ Widget . attach ( widget , document . body ) ;
105+ await framePromise ( ) ;
106+
107+ const select = document . querySelector ( 'select' ) as HTMLSelectElement ;
108+ expect ( select . options . length ) . toBeGreaterThanOrEqual ( 1 ) ;
109+ expect ( select . options [ 0 ] && select . options [ 0 ] . value ) . toBe ( '-' ) ;
110+ } ) ;
111+ } ) ;
0 commit comments