Skip to content

Commit 8887ef7

Browse files
committed
refactor: fix test and format
1 parent 6659212 commit 8887ef7

File tree

1 file changed

+181
-154
lines changed

1 file changed

+181
-154
lines changed
Lines changed: 181 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -1,158 +1,185 @@
11
// Copyright (c) Deepnote
22
// Distributed under the terms of the Modified BSD License.
33

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: {}, notebook_names: [] });
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-
112-
it("should handle null model in handleChange", async () => {
113-
const nullModelPanel = {
114-
context: {
115-
ready: Promise.resolve(),
116-
model: {
117-
getMetadata: jest.fn().mockReturnValue({
118-
notebooks: {
119-
nb1: { id: "nb1", name: "nb1", cells: [] },
120-
},
121-
notebook_names: ["nb1"],
122-
}),
123-
},
124-
},
125-
model: null,
126-
} as any;
127-
128-
document.body.innerHTML = "";
129-
const widget = new NotebookPicker(nullModelPanel);
130-
(widget as any).onAfterAttach = jest.fn();
131-
Widget.attach(widget, document.body);
132-
await framePromise();
133-
134-
const select = document.querySelector("select") as HTMLSelectElement;
135-
simulate(select, "change", { target: { value: "nb1" } });
136-
await framePromise();
137-
});
138-
139-
it("should handle invalid metadata in handleChange", async () => {
140-
const consoleErrorSpy = jest
141-
.spyOn(console, "error")
142-
.mockImplementation(() => {});
143-
const getMetadata = panel.context.model.getMetadata as jest.Mock;
144-
getMetadata.mockReturnValue({ invalid: "metadata" });
145-
146-
const select = document.querySelector("select") as HTMLSelectElement;
147-
simulate(select, "change", { target: { value: "nb1" } });
148-
await framePromise();
149-
150-
expect(consoleErrorSpy).toHaveBeenCalledWith(
151-
"Invalid deepnote metadata:",
152-
expect.anything(),
153-
);
154-
expect(model.fromJSON).not.toHaveBeenCalled();
155-
156-
consoleErrorSpy.mockRestore();
157-
});
4+
import type { NotebookPanel } from '@jupyterlab/notebook';
5+
import { framePromise } from '@jupyterlab/testing';
6+
import { Widget } from '@lumino/widgets';
7+
import { simulate } from 'simulate-event';
8+
import { NotebookPicker } from '../../src/components/NotebookPicker';
9+
10+
// Mock types for testing
11+
interface MockNotebookModel {
12+
fromJSON: jest.Mock;
13+
cells: unknown[];
14+
dirty: boolean;
15+
}
16+
17+
interface MockNotebookPanel {
18+
context: {
19+
ready: Promise<void>;
20+
model: {
21+
getMetadata: jest.Mock;
22+
};
23+
};
24+
model: MockNotebookModel | null;
25+
}
26+
27+
// Type for widget with overridden protected method
28+
type WidgetWithMockOnAfterAttach = NotebookPicker & {
29+
onAfterAttach: jest.Mock;
30+
};
31+
32+
describe('NotebookPicker', () => {
33+
let panel: MockNotebookPanel;
34+
let model: MockNotebookModel;
35+
36+
beforeEach(async () => {
37+
// Mock model + metadata
38+
model = {
39+
fromJSON: jest.fn(),
40+
get cells() {
41+
return [];
42+
},
43+
dirty: true
44+
};
45+
46+
panel = {
47+
context: {
48+
ready: Promise.resolve(),
49+
model: {
50+
getMetadata: jest.fn().mockReturnValue({
51+
notebooks: {
52+
nb1: { id: 'nb1', name: 'nb1', cells: [{ source: 'code' }] },
53+
nb2: { id: 'nb2', name: 'nb2', cells: [] }
54+
},
55+
notebook_names: ['nb1', 'nb2']
56+
})
57+
}
58+
},
59+
model
60+
};
61+
62+
// Attach to DOM
63+
const widget = new NotebookPicker(
64+
panel as unknown as NotebookPanel
65+
) as WidgetWithMockOnAfterAttach;
66+
// Override onAfterAttach to avoid errors from this.parent being null
67+
widget.onAfterAttach = jest.fn();
68+
Widget.attach(widget, document.body);
69+
await framePromise();
70+
});
71+
72+
afterEach(() => {
73+
document.body.innerHTML = '';
74+
jest.restoreAllMocks();
75+
});
76+
77+
it('should render a select element', async () => {
78+
await framePromise(); // wait for rendering
79+
const select = document.querySelector('select') as HTMLSelectElement;
80+
expect(select).not.toBeNull();
81+
expect(select.options.length).toBe(2);
82+
expect(select.options[0]?.value).toBe('nb1');
83+
});
84+
85+
it('should call fromJSON when selecting a notebook', async () => {
86+
const select = document.querySelector('select') as HTMLSelectElement;
87+
simulate(select, 'change', { target: { value: 'nb2' } });
88+
await framePromise();
89+
expect(model.fromJSON).toHaveBeenCalledWith(
90+
expect.objectContaining({
91+
cells: expect.any(Array),
92+
metadata: expect.objectContaining({
93+
deepnote: expect.objectContaining({
94+
notebooks: expect.any(Object)
95+
})
96+
})
97+
})
98+
);
99+
});
100+
101+
it('should not call fromJSON if selected notebook is invalid', async () => {
102+
const getMetadata = panel.context.model.getMetadata as jest.Mock;
103+
getMetadata.mockReturnValue({ notebooks: {}, notebook_names: [] });
104+
105+
const select = document.querySelector('select') as HTMLSelectElement;
106+
simulate(select, 'change', { target: { value: 'nonexistent' } });
107+
await framePromise();
108+
expect(model.fromJSON).not.toHaveBeenCalled();
109+
});
110+
111+
it('should update UI after selection', async () => {
112+
const select = document.querySelector('select') as HTMLSelectElement;
113+
select.value = 'nb2';
114+
simulate(select, 'change');
115+
await framePromise();
116+
expect(select.value).toBe('nb2');
117+
});
118+
119+
it('should handle empty metadata gracefully', async () => {
120+
const getMetadata = panel.context.model.getMetadata as jest.Mock;
121+
getMetadata.mockReturnValue({ notebooks: {}, notebook_names: [] });
122+
123+
document.body.innerHTML = '';
124+
const widget = new NotebookPicker(
125+
panel as unknown as NotebookPanel
126+
) as WidgetWithMockOnAfterAttach;
127+
// Override onAfterAttach to avoid errors from this.parent being null
128+
widget.onAfterAttach = jest.fn();
129+
Widget.attach(widget, document.body);
130+
await framePromise();
131+
132+
const select = document.querySelector('select') as HTMLSelectElement;
133+
expect(select.options.length).toBeGreaterThanOrEqual(1);
134+
expect(select.options[0]?.value).toBe('-');
135+
});
136+
137+
it('should handle null model in handleChange', async () => {
138+
const nullModelPanel: MockNotebookPanel = {
139+
context: {
140+
ready: Promise.resolve(),
141+
model: {
142+
getMetadata: jest.fn().mockReturnValue({
143+
notebooks: {
144+
nb1: { id: 'nb1', name: 'nb1', cells: [] }
145+
},
146+
notebook_names: ['nb1']
147+
})
148+
}
149+
},
150+
model: null
151+
};
152+
153+
document.body.innerHTML = '';
154+
const widget = new NotebookPicker(
155+
nullModelPanel as unknown as NotebookPanel
156+
) as WidgetWithMockOnAfterAttach;
157+
widget.onAfterAttach = jest.fn();
158+
Widget.attach(widget, document.body);
159+
await framePromise();
160+
161+
const select = document.querySelector('select') as HTMLSelectElement;
162+
simulate(select, 'change', { target: { value: 'nb1' } });
163+
await framePromise();
164+
});
165+
166+
it('should handle invalid metadata in handleChange', async () => {
167+
const consoleErrorSpy = jest
168+
.spyOn(console, 'error')
169+
.mockImplementation(() => {});
170+
const getMetadata = panel.context.model.getMetadata as jest.Mock;
171+
getMetadata.mockReturnValue({ invalid: 'metadata' });
172+
173+
const select = document.querySelector('select') as HTMLSelectElement;
174+
simulate(select, 'change', { target: { value: 'nb1' } });
175+
await framePromise();
176+
177+
expect(consoleErrorSpy).toHaveBeenCalledWith(
178+
'Invalid deepnote metadata:',
179+
expect.anything()
180+
);
181+
expect(model.fromJSON).not.toHaveBeenCalled();
182+
183+
consoleErrorSpy.mockRestore();
184+
});
158185
});

0 commit comments

Comments
 (0)