feat: fully dynamic model picker in inline prompt
This commit is contained in:
@@ -1,12 +1,13 @@
|
||||
/**
|
||||
* Unit tests for OpenCodeCellActions (single AI button) and OpenCodeInlinePrompt
|
||||
* (inline input panel inside the cell).
|
||||
* (inline input panel inside the cell). v3-final: no settings — the model
|
||||
* picker is fully dynamic; default selection is the first provider/model.
|
||||
*
|
||||
* Mocks @jupyterlab/cells, @jupyterlab/apputils, and @lumino/widgets at the
|
||||
* test boundary so the real ESM packages are not loaded.
|
||||
*/
|
||||
jest.mock('@jupyterlab/cells', () => ({
|
||||
CodeCell: class CodeCell { public node = document.createElement('div'); }
|
||||
CodeCell: class CodeCell {}
|
||||
}));
|
||||
|
||||
jest.mock('@jupyterlab/apputils', () => ({
|
||||
@@ -32,21 +33,25 @@ jest.mock('@lumino/widgets', () => {
|
||||
}
|
||||
public dispose(): void {
|
||||
this._isDisposed = true;
|
||||
if (this.node.parentElement) {
|
||||
this.node.parentElement.removeChild(this.node);
|
||||
if (this.node.parentNode) {
|
||||
this.node.parentNode.removeChild(this.node);
|
||||
}
|
||||
}
|
||||
public onAfterAttach(msg: any): void {
|
||||
// no-op for tests
|
||||
}
|
||||
public static attach(widget: Widget, host: HTMLElement): void {
|
||||
host.appendChild(widget.node);
|
||||
}
|
||||
public onAfterAttach(_msg: any): void {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
return { Widget };
|
||||
});
|
||||
|
||||
import { OpenCodeCellActions } from '../components/opencode_cell_actions';
|
||||
import {
|
||||
OpenCodeCellActions,
|
||||
setOpenCodeProviders,
|
||||
setOpenCodeServerSettings
|
||||
} from '../components/opencode_cell_actions';
|
||||
import { OpenCodeInlinePrompt } from '../components/opencode_inline_prompt';
|
||||
import { CodeCell } from '@jupyterlab/cells';
|
||||
|
||||
@@ -81,6 +86,7 @@ function makeFakeCell(
|
||||
};
|
||||
const cell = new (CodeCell as unknown as new () => CodeCell)();
|
||||
(cell as any).model = model;
|
||||
(cell as any).node = document.createElement('div');
|
||||
|
||||
const notebook: any = { widgets: [] as CodeCell[] };
|
||||
const panel: any = {
|
||||
@@ -112,7 +118,11 @@ function makeFakeCell(
|
||||
}
|
||||
|
||||
describe('OpenCodeCellActions', () => {
|
||||
it('renders a single AI button (not 3 buttons)', () => {
|
||||
it('allows server settings to be injected', () => {
|
||||
setOpenCodeServerSettings({} as any);
|
||||
});
|
||||
|
||||
it('renders a single AI button', () => {
|
||||
const cell = makeFakeCell('x = 1');
|
||||
const actions = new OpenCodeCellActions(cell);
|
||||
const btns = actions.node.querySelectorAll('button');
|
||||
@@ -138,7 +148,6 @@ describe('OpenCodeCellActions', () => {
|
||||
it('clicking the button attaches an OpenCodeInlinePrompt to the cell', () => {
|
||||
const cell = makeFakeCell('x = 1');
|
||||
const actions = new OpenCodeCellActions(cell);
|
||||
// Attach actions to cell so it can resolve parent chain
|
||||
Object.defineProperty(actions, 'parent', { value: cell, configurable: true });
|
||||
(actions as any).onAfterAttach({} as any);
|
||||
|
||||
@@ -160,8 +169,7 @@ describe('OpenCodeCellActions', () => {
|
||||
const btn = actions.node.querySelector('button') as HTMLButtonElement;
|
||||
btn.click();
|
||||
btn.click();
|
||||
const prompt = cell.node.querySelector('.opencode-inline-prompt');
|
||||
expect(prompt).toBeNull();
|
||||
expect(cell.node.querySelector('.opencode-inline-prompt')).toBeNull();
|
||||
});
|
||||
|
||||
it('disconnects model signals on dispose', () => {
|
||||
@@ -172,15 +180,58 @@ describe('OpenCodeCellActions', () => {
|
||||
expect(model.contentChanged.disconnect).toHaveBeenCalled();
|
||||
expect(model.stateChanged.disconnect).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('renders a model selector with the first option selected when providers are cached', () => {
|
||||
setOpenCodeProviders({
|
||||
providers: [
|
||||
{ id: 'anthropic', models: [{ id: 'claude-sonnet-4-20250514' }] },
|
||||
{ id: 'openai', models: [{ id: 'gpt-5' }] }
|
||||
]
|
||||
});
|
||||
const cell = makeFakeCell('x = 1');
|
||||
const actions = new OpenCodeCellActions(cell);
|
||||
Object.defineProperty(actions, 'parent', { value: cell, configurable: true });
|
||||
(actions as any).onAfterAttach({} as any);
|
||||
|
||||
const btn = actions.node.querySelector('button') as HTMLButtonElement;
|
||||
btn.click();
|
||||
|
||||
const select = cell.node.querySelector(
|
||||
'.opencode-inline-prompt .opencode-model-select'
|
||||
) as HTMLSelectElement;
|
||||
expect(select).not.toBeNull();
|
||||
expect(select.options.length).toBe(2);
|
||||
expect(select.options[0].textContent).toBe('anthropic / claude-sonnet-4-20250514');
|
||||
expect(select.options[1].textContent).toBe('openai / gpt-5');
|
||||
// v3-final: default = first option (no settings).
|
||||
expect(select.value).toBe('anthropic|claude-sonnet-4-20250514');
|
||||
setOpenCodeProviders(null);
|
||||
});
|
||||
|
||||
it('does not render the selector when providers are not cached', () => {
|
||||
setOpenCodeProviders(null);
|
||||
const cell = makeFakeCell('x = 1');
|
||||
const actions = new OpenCodeCellActions(cell);
|
||||
Object.defineProperty(actions, 'parent', { value: cell, configurable: true });
|
||||
(actions as any).onAfterAttach({} as any);
|
||||
|
||||
const btn = actions.node.querySelector('button') as HTMLButtonElement;
|
||||
btn.click();
|
||||
|
||||
expect(
|
||||
cell.node.querySelector('.opencode-inline-prompt .opencode-model-select')
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('OpenCodeInlinePrompt', () => {
|
||||
it('renders a textarea, a send and a cancel button', () => {
|
||||
it('renders a textarea, a send and a cancel button (no selector when providers null)', () => {
|
||||
const cell = makeFakeCell('x = 1');
|
||||
const prompt = new OpenCodeInlinePrompt(cell, {
|
||||
onSubmit: jest.fn(),
|
||||
onCancel: jest.fn(),
|
||||
disabled: false
|
||||
disabled: false,
|
||||
providers: null
|
||||
});
|
||||
expect(prompt.node.querySelector('textarea')).not.toBeNull();
|
||||
expect(prompt.node.querySelectorAll('button').length).toBe(2);
|
||||
|
||||
Reference in New Issue
Block a user