From 2a0a5999b4f30b3d7529bfcb8adcd3121b38f301 Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Tue, 10 Feb 2026 11:46:13 +0000 Subject: [PATCH 01/12] add document actions to data browsing - edit, copy, clone, delete --- src/documentSource.ts | 1 + src/editors/playgroundController.ts | 16 +- src/explorer/documentTreeItem.ts | 6 +- src/mdbExtensionController.ts | 3 + .../views/dataBrowsingController.test.ts | 3 + .../extension-app-message-constants.ts | 31 +++- src/views/data-browsing-app/monaco-viewer.tsx | 101 ++++++++++++ .../data-browsing-app/store/messageHandler.ts | 5 + src/views/data-browsing-app/vscode-api.ts | 21 +++ src/views/dataBrowsingController.ts | 148 ++++++++++++++++++ 10 files changed, 327 insertions(+), 8 deletions(-) diff --git a/src/documentSource.ts b/src/documentSource.ts index dc7b52196..b3846e599 100644 --- a/src/documentSource.ts +++ b/src/documentSource.ts @@ -3,6 +3,7 @@ export const DocumentSource = { playground: 'playground', collectionview: 'collectionview', codelens: 'codelens', + databrowser: 'databrowser', } as const; export type DocumentSource = diff --git a/src/editors/playgroundController.ts b/src/editors/playgroundController.ts index 6ddbd34e5..6b95f9c6b 100644 --- a/src/editors/playgroundController.ts +++ b/src/editors/playgroundController.ts @@ -507,8 +507,12 @@ export default class PlaygroundController { if (shouldConfirmRunCopilotCode === true) { const name = this._connectionController.getActiveConnectionName(); const confirmRunCopilotCode = await vscode.window.showInformationMessage( - `Are you sure you want to run this code generated by the MongoDB participant against ${name}? This confirmation can be disabled in the extension settings.`, - { modal: true }, + `Are you sure you want to run this code generated by the MongoDB participant against ${name}?`, + { + modal: true, + detail: + 'This confirmation can be disabled in the extension settings.', + }, 'Yes', ); @@ -576,8 +580,12 @@ export default class PlaygroundController { if (shouldConfirmRunAll === true) { const name = this._connectionController.getActiveConnectionName(); const confirmRunAll = await vscode.window.showInformationMessage( - `Are you sure you want to run this playground against ${name}? This confirmation can be disabled in the extension settings.`, - { modal: true }, + `Are you sure you want to run this playground against ${name}?`, + { + modal: true, + detail: + 'This confirmation can be disabled in the extension settings.', + }, 'Yes', ); diff --git a/src/explorer/documentTreeItem.ts b/src/explorer/documentTreeItem.ts index b0b86c396..6bda09c20 100644 --- a/src/explorer/documentTreeItem.ts +++ b/src/explorer/documentTreeItem.ts @@ -57,7 +57,7 @@ export default class DocumentTreeItem const documentLabel = document._id ? JSON.stringify(document._id) - : `Document ${documentIndexInTree + 1}`; + : `"Document ${documentIndexInTree + 1}"`; this.dataService = dataService; this.document = document; @@ -112,9 +112,11 @@ export default class DocumentTreeItem if (shouldConfirmDeleteDocument === true) { const confirmationResult = await vscode.window.showInformationMessage( - `Are you sure you wish to drop this document${this.tooltip ? ` "${this.tooltip}"` : ''}? This confirmation can be disabled in the extension settings.`, + `Are you sure you wish to drop this document${this.tooltip ? ` ${this.tooltip}` : ''}?`, { modal: true, + detail: + 'This confirmation can be disabled in the extension settings.', }, 'Yes', ); diff --git a/src/mdbExtensionController.ts b/src/mdbExtensionController.ts index 13a7eefb1..af01a032e 100644 --- a/src/mdbExtensionController.ts +++ b/src/mdbExtensionController.ts @@ -264,6 +264,9 @@ export default class MDBExtensionController implements vscode.Disposable { }); this._dataBrowsingController = new DataBrowsingController({ connectionController: this._connectionController, + editorsController: this._editorsController, + playgroundController: this._playgroundController, + explorerController: this._explorerController, telemetryService: this._telemetryService, }); this._editorsController.registerProviders(); diff --git a/src/test/suite/views/dataBrowsingController.test.ts b/src/test/suite/views/dataBrowsingController.test.ts index e11946093..6c5c90f2c 100644 --- a/src/test/suite/views/dataBrowsingController.test.ts +++ b/src/test/suite/views/dataBrowsingController.test.ts @@ -55,6 +55,9 @@ suite('DataBrowsingController Test Suite', function () { }; testController = new DataBrowsingController({ connectionController: mockConnectionController as any, + editorsController: {} as any, + playgroundController: {} as any, + explorerController: {} as any, telemetryService: {} as any, }); mockPanel = createMockPanel(); diff --git a/src/views/data-browsing-app/extension-app-message-constants.ts b/src/views/data-browsing-app/extension-app-message-constants.ts index 83d4b50a8..94a07bb9e 100644 --- a/src/views/data-browsing-app/extension-app-message-constants.ts +++ b/src/views/data-browsing-app/extension-app-message-constants.ts @@ -4,6 +4,9 @@ export const PreviewMessageType = { getTotalCount: 'GET_TOTAL_COUNT', cancelRequest: 'CANCEL_REQUEST', getThemeColors: 'GET_THEME_COLORS', + editDocument: 'EDIT_DOCUMENT', + cloneDocument: 'CLONE_DOCUMENT', + deleteDocument: 'DELETE_DOCUMENT', // Messages from extension to webview loadPage: 'LOAD_PAGE', @@ -12,6 +15,7 @@ export const PreviewMessageType = { updateTotalCount: 'UPDATE_TOTAL_COUNT', updateTotalCountError: 'UPDATE_TOTAL_COUNT_ERROR', updateThemeColors: 'UPDATE_THEME_COLORS', + documentDeleted: 'DOCUMENT_DELETED', } as const; export interface TokenColors { @@ -53,6 +57,21 @@ export interface GetThemeColorsMessage extends BasicWebviewMessage { command: typeof PreviewMessageType.getThemeColors; } +export interface EditDocumentMessage extends BasicWebviewMessage { + command: typeof PreviewMessageType.editDocument; + documentId: any; +} + +export interface CloneDocumentMessage extends BasicWebviewMessage { + command: typeof PreviewMessageType.cloneDocument; + document: Record; +} + +export interface DeleteDocumentMessage extends BasicWebviewMessage { + command: typeof PreviewMessageType.deleteDocument; + documentId: any; +} + // Messages from extension to webview export interface LoadPageMessage extends BasicWebviewMessage { command: typeof PreviewMessageType.loadPage; @@ -84,11 +103,18 @@ export interface UpdateThemeColorsMessage extends BasicWebviewMessage { themeKind: MonacoBaseTheme; } +export interface DocumentDeletedMessage extends BasicWebviewMessage { + command: typeof PreviewMessageType.documentDeleted; +} + export type MessageFromWebviewToExtension = | GetDocumentsMessage | GetTotalCountMessage | CancelRequestMessage - | GetThemeColorsMessage; + | GetThemeColorsMessage + | EditDocumentMessage + | CloneDocumentMessage + | DeleteDocumentMessage; export type MessageFromExtensionToWebview = | LoadPageMessage @@ -96,4 +122,5 @@ export type MessageFromExtensionToWebview = | RequestCancelledMessage | UpdateTotalCountMessage | UpdateTotalCountErrorMessage - | UpdateThemeColorsMessage; + | UpdateThemeColorsMessage + | DocumentDeletedMessage; diff --git a/src/views/data-browsing-app/monaco-viewer.tsx b/src/views/data-browsing-app/monaco-viewer.tsx index 0171d9c01..b8f3cd5a5 100644 --- a/src/views/data-browsing-app/monaco-viewer.tsx +++ b/src/views/data-browsing-app/monaco-viewer.tsx @@ -15,6 +15,11 @@ import type { } from './extension-app-message-constants'; import { toJSString } from 'mongodb-query-parser'; import { EJSON } from 'bson'; +import { + sendEditDocument, + sendCloneDocument, + sendDeleteDocument, +} from './vscode-api'; // Configure Monaco Editor loader to use local files instead of CDN declare global { @@ -75,6 +80,7 @@ const monacoWrapperStyles = css({ }); const cardStyles = css({ + position: 'relative', backgroundColor: 'var(--vscode-editorWidget-background, var(--vscode-editor-background))', border: @@ -82,6 +88,47 @@ const cardStyles = css({ borderRadius: '6px', marginBottom: spacing[200], padding: spacing[300], + + '.action-bar': { + position: 'absolute', + top: spacing[200], + right: spacing[200], + display: 'flex', + gap: spacing[100], + zIndex: 1000, + + opacity: 0, + transition: 'opacity 0.2s', + pointerEvents: 'none', + }, + + '&:hover .action-bar': { + opacity: 1, + pointerEvents: 'auto', + }, +}); + +const actionButtonStyles = css({ + background: 'var(--vscode-button-background)', + border: '1px solid var(--vscode-button-border, transparent)', + color: 'var(--vscode-button-foreground)', + borderRadius: '4px', + padding: `${spacing[100]}px ${spacing[200]}px`, + cursor: 'pointer', + fontSize: '12px', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + minWidth: '24px', + minHeight: '24px', + + '&:hover': { + background: 'var(--vscode-button-hoverBackground)', + }, + + '&:active': { + background: 'var(--vscode-button-background)', + }, }); const viewerOptions: Monaco.editor.IStandaloneEditorConstructionOptions = { @@ -258,8 +305,62 @@ const MonacoViewer: React.FC = ({ }; }, []); + const handleEdit = useCallback(() => { + if (document._id) { + sendEditDocument(document._id); + } + }, [document]); + + const handleCopy = useCallback(() => { + void navigator.clipboard.writeText(documentString); + }, [documentString]); + + const handleClone = useCallback(() => { + sendCloneDocument(document); + }, [document]); + + const handleDelete = useCallback(() => { + if (document._id) { + sendDeleteDocument(document._id); + } + }, [document]); + return (
+
+ + + + +
{ command: PreviewMessageType.getThemeColors, }); }; + +export const sendEditDocument = (documentId: any): void => { + getVSCodeApi().postMessage({ + command: PreviewMessageType.editDocument, + documentId, + }); +}; + +export const sendCloneDocument = (document: Record): void => { + getVSCodeApi().postMessage({ + command: PreviewMessageType.cloneDocument, + document, + }); +}; + +export const sendDeleteDocument = (documentId: any): void => { + getVSCodeApi().postMessage({ + command: PreviewMessageType.deleteDocument, + documentId, + }); +}; diff --git a/src/views/dataBrowsingController.ts b/src/views/dataBrowsingController.ts index c6341a78a..ae043f098 100644 --- a/src/views/dataBrowsingController.ts +++ b/src/views/dataBrowsingController.ts @@ -1,6 +1,7 @@ import * as vscode from 'vscode'; import { EJSON, type Document } from 'bson'; import path from 'path'; +import { toJSString } from 'mongodb-query-parser'; import type ConnectionController from '../connectionController'; import { createLogger } from '../logging'; import { PreviewMessageType } from './data-browsing-app/extension-app-message-constants'; @@ -13,6 +14,11 @@ import { getThemeTokenColors, getMonacoBaseTheme, } from '../utils/themeColorReader'; +import type EditorsController from '../editors/editorsController'; +import type PlaygroundController from '../editors/playgroundController'; +import { DocumentSource } from '../documentSource'; +import { getDocumentViewAndEditFormat } from '../editors/types'; +import type ExplorerController from '../explorer/explorerController'; const log = createLogger('data browsing controller'); @@ -63,6 +69,9 @@ interface PanelAbortControllers { export default class DataBrowsingController { _connectionController: ConnectionController; + _editorsController: EditorsController; + _playgroundController: PlaygroundController; + _explorerController: ExplorerController; _telemetryService: TelemetryService; _activeWebviewPanels: vscode.WebviewPanel[] = []; _configChangedSubscription: vscode.Disposable; @@ -72,12 +81,21 @@ export default class DataBrowsingController { constructor({ connectionController, + editorsController, + playgroundController, + explorerController, telemetryService, }: { connectionController: ConnectionController; + editorsController: EditorsController; + playgroundController: PlaygroundController; + explorerController: ExplorerController; telemetryService: TelemetryService; }) { this._connectionController = connectionController; + this._editorsController = editorsController; + this._playgroundController = playgroundController; + this._explorerController = explorerController; this._telemetryService = telemetryService; this._configChangedSubscription = vscode.workspace.onDidChangeConfiguration( this.onConfigurationChanged, @@ -142,6 +160,27 @@ export default class DataBrowsingController { case PreviewMessageType.getThemeColors: this._sendThemeColors(panel); return; + case PreviewMessageType.editDocument: + await this.handleEditDocument( + panel, + options, + EJSON.deserialize(message.documentId, { relaxed: false }), + ); + return; + case PreviewMessageType.cloneDocument: + await this.handleCloneDocument( + panel, + options, + EJSON.deserialize(message.document, { relaxed: false }), + ); + return; + case PreviewMessageType.deleteDocument: + await this.handleDeleteDocument( + panel, + options, + EJSON.deserialize(message.documentId, { relaxed: false }), + ); + return; default: // no-op. return; @@ -234,6 +273,115 @@ export default class DataBrowsingController { } }; + handleEditDocument = async ( + panel: vscode.WebviewPanel, + options: DataBrowsingOptions, + documentId: any, + ): Promise => { + try { + await this._editorsController.openMongoDBDocument({ + source: DocumentSource.databrowser, + documentId, + namespace: options.namespace, + format: getDocumentViewAndEditFormat(), + connectionId: this._connectionController.getActiveConnectionId(), + line: 1, + }); + } catch (error) { + log.error('Error opening document for editing', error); + void vscode.window.showErrorMessage( + `Failed to open document: ${formatError(error).message}`, + ); + } + }; + + handleCloneDocument = async ( + panel: vscode.WebviewPanel, + options: DataBrowsingOptions, + document: Record, + ): Promise => { + try { + const deserialized = EJSON.deserialize(document, { relaxed: false }); + const documentContents = toJSString(deserialized) ?? ''; + + const [databaseName, collectionName] = options.namespace.split(/\.(.*)/s); + + await this._playgroundController.createPlaygroundForCloneDocument( + documentContents, + databaseName, + collectionName, + ); + } catch (error) { + log.error('Error cloning document', error); + void vscode.window.showErrorMessage( + `Failed to clone document: ${formatError(error).message}`, + ); + } + }; + + handleDeleteDocument = async ( + panel: vscode.WebviewPanel, + options: DataBrowsingOptions, + documentId: any, + ): Promise => { + try { + const shouldConfirmDeleteDocument = vscode.workspace + .getConfiguration('mdb') + .get('confirmDeleteDocument'); + + if (shouldConfirmDeleteDocument === true) { + const documentIdString = JSON.stringify( + EJSON.deserialize(documentId, { relaxed: false }), + ); + const confirmationResult = await vscode.window.showInformationMessage( + `Are you sure you wish to drop this document${documentIdString ? ` ${documentIdString}` : ''}?`, + { + modal: true, + detail: + 'This confirmation can be disabled in the extension settings.', + }, + 'Yes', + ); + + if (confirmationResult !== 'Yes') { + return; + } + } + + const dataService = this._connectionController.getActiveDataService(); + if (!dataService) { + throw new Error('No active database connection'); + } + + const deleteResult = await dataService.deleteOne( + options.namespace, + { _id: documentId }, + {}, + ); + + if (deleteResult.deletedCount !== 1) { + throw new Error('document not found'); + } + + void vscode.window.showInformationMessage( + 'Document successfully deleted.', + ); + + // Refresh the explorer view + this._explorerController.refresh(); + + // Notify the webview that the document was deleted + void panel.webview.postMessage({ + command: PreviewMessageType.documentDeleted, + }); + } catch (error) { + log.error('Error deleting document', error); + void vscode.window.showErrorMessage( + `Failed to delete document: ${formatError(error).message}`, + ); + } + }; + private async _fetchDocuments( namespace: string, signal?: AbortSignal, From 625d636c5fc2f4efc51157d73a38e6dc4e104bfc Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Tue, 10 Feb 2026 13:06:38 +0000 Subject: [PATCH 02/12] clone fixes --- src/views/data-browsing-app/vscode-api.ts | 3 ++- src/views/dataBrowsingController.ts | 7 ++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/views/data-browsing-app/vscode-api.ts b/src/views/data-browsing-app/vscode-api.ts index dc11038da..b4136ad6a 100644 --- a/src/views/data-browsing-app/vscode-api.ts +++ b/src/views/data-browsing-app/vscode-api.ts @@ -1,3 +1,4 @@ +import { EJSON } from 'bson'; import { PreviewMessageType, type MessageFromWebviewToExtension, @@ -56,7 +57,7 @@ export const sendEditDocument = (documentId: any): void => { export const sendCloneDocument = (document: Record): void => { getVSCodeApi().postMessage({ command: PreviewMessageType.cloneDocument, - document, + document: EJSON.serialize(document, { relaxed: false }), }); }; diff --git a/src/views/dataBrowsingController.ts b/src/views/dataBrowsingController.ts index ae043f098..ff9fd3b09 100644 --- a/src/views/dataBrowsingController.ts +++ b/src/views/dataBrowsingController.ts @@ -168,11 +168,7 @@ export default class DataBrowsingController { ); return; case PreviewMessageType.cloneDocument: - await this.handleCloneDocument( - panel, - options, - EJSON.deserialize(message.document, { relaxed: false }), - ); + await this.handleCloneDocument(panel, options, message.document); return; case PreviewMessageType.deleteDocument: await this.handleDeleteDocument( @@ -302,6 +298,7 @@ export default class DataBrowsingController { ): Promise => { try { const deserialized = EJSON.deserialize(document, { relaxed: false }); + delete deserialized._id; const documentContents = toJSString(deserialized) ?? ''; const [databaseName, collectionName] = options.namespace.split(/\.(.*)/s); From 7488f0194815321b130130e93e008baf3bf2c828 Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Tue, 10 Feb 2026 14:42:32 +0000 Subject: [PATCH 03/12] most buttons should only show up when there's an id --- src/views/data-browsing-app/monaco-viewer.tsx | 54 ++++++++++--------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/src/views/data-browsing-app/monaco-viewer.tsx b/src/views/data-browsing-app/monaco-viewer.tsx index b8f3cd5a5..dd96eddbc 100644 --- a/src/views/data-browsing-app/monaco-viewer.tsx +++ b/src/views/data-browsing-app/monaco-viewer.tsx @@ -328,14 +328,16 @@ const MonacoViewer: React.FC = ({ return (
- + {document.id && ( + + )} - - + {document.id && ( + + )} + {document.id && ( + + )}
Date: Tue, 10 Feb 2026 14:58:06 +0000 Subject: [PATCH 04/12] tests for action bar --- .../data-browsing-app/monaco-viewer.test.tsx | 177 +++++++++++++++++- src/views/data-browsing-app/monaco-viewer.tsx | 6 +- 2 files changed, 179 insertions(+), 4 deletions(-) diff --git a/src/test/suite/views/data-browsing-app/monaco-viewer.test.tsx b/src/test/suite/views/data-browsing-app/monaco-viewer.test.tsx index 46bdc48ac..0fb5b408a 100644 --- a/src/test/suite/views/data-browsing-app/monaco-viewer.test.tsx +++ b/src/test/suite/views/data-browsing-app/monaco-viewer.test.tsx @@ -1,9 +1,16 @@ import React from 'react'; import { expect } from 'chai'; import sinon from 'sinon'; -import { render, screen, cleanup, waitFor } from '@testing-library/react'; +import { + render, + screen, + cleanup, + waitFor, + fireEvent, +} from '@testing-library/react'; import MonacoViewer from '../../../../views/data-browsing-app/monaco-viewer'; +import * as vscodeApi from '../../../../views/data-browsing-app/vscode-api'; // Mock the Monaco Editor component let mockEditorValue = ''; @@ -314,4 +321,172 @@ describe('MonacoViewer test suite', function () { expect(container).to.exist; }); }); + + describe('Action bar', function () { + let sendEditDocumentStub: sinon.SinonStub; + let sendCloneDocumentStub: sinon.SinonStub; + let sendDeleteDocumentStub: sinon.SinonStub; + let clipboardWriteTextStub: sinon.SinonStub; + + beforeEach(function () { + sendEditDocumentStub = sinon.stub(vscodeApi, 'sendEditDocument'); + sendCloneDocumentStub = sinon.stub(vscodeApi, 'sendCloneDocument'); + sendDeleteDocumentStub = sinon.stub(vscodeApi, 'sendDeleteDocument'); + + if (!navigator.clipboard) { + (navigator as any).clipboard = { + writeText: async (): Promise => { + return Promise.resolve(); + }, + }; + } else if (!navigator.clipboard.writeText) { + (navigator.clipboard as any).writeText = async (): Promise => { + return Promise.resolve(); + }; + } + clipboardWriteTextStub = sinon.stub(navigator.clipboard, 'writeText'); + }); + + afterEach(function () { + sendEditDocumentStub.restore(); + sendCloneDocumentStub.restore(); + sendDeleteDocumentStub.restore(); + clipboardWriteTextStub.restore(); + }); + + it('should render all action buttons when document has _id', function () { + const document = { _id: '123', name: 'Test' }; + + render(); + + const editButton = screen.queryByTitle('Edit'); + const copyButton = screen.queryByTitle('Copy'); + const cloneButton = screen.queryByTitle('Clone'); + const deleteButton = screen.queryByTitle('Delete'); + + expect(editButton).to.exist; + expect(copyButton).to.exist; + expect(cloneButton).to.exist; + expect(deleteButton).to.exist; + }); + + it('should only render copy button when document has no _id', function () { + const document = { name: 'Test' }; + + render(); + + const editButton = screen.queryByTitle('Edit'); + const copyButton = screen.queryByTitle('Copy'); + const cloneButton = screen.queryByTitle('Clone'); + const deleteButton = screen.queryByTitle('Delete'); + + expect(editButton).to.not.exist; + expect(copyButton).to.exist; + expect(cloneButton).to.not.exist; + expect(deleteButton).to.not.exist; + }); + + it('should call sendEditDocument when edit button is clicked', function () { + const document = { _id: '123', name: 'Test' }; + + render(); + + const editButton = screen.getByTitle('Edit'); + fireEvent.click(editButton); + + expect(sendEditDocumentStub.calledOnce).to.be.true; + expect(sendEditDocumentStub.calledWith('123')).to.be.true; + }); + + it('should call clipboard.writeText when copy button is clicked', async function () { + const document = { _id: '123', name: 'Test' }; + + render(); + + const copyButton = screen.getByTitle('Copy'); + fireEvent.click(copyButton); + + await waitFor(() => { + expect(clipboardWriteTextStub.calledOnce).to.be.true; + const calledWith = clipboardWriteTextStub.firstCall.args[0]; + expect(calledWith).to.be.a('string'); + expect(calledWith).to.include('_id:'); + expect(calledWith).to.include('123'); + }); + }); + + it('should call sendCloneDocument when clone button is clicked', function () { + const document = { _id: '123', name: 'Test' }; + + render(); + + const cloneButton = screen.getByTitle('Clone'); + fireEvent.click(cloneButton); + + expect(sendCloneDocumentStub.calledOnce).to.be.true; + expect(sendCloneDocumentStub.calledWith(document)).to.be.true; + }); + + it('should call sendDeleteDocument when delete button is clicked', function () { + const document = { _id: '123', name: 'Test' }; + + render(); + + const deleteButton = screen.getByTitle('Delete'); + fireEvent.click(deleteButton); + + expect(sendDeleteDocumentStub.calledOnce).to.be.true; + expect(sendDeleteDocumentStub.calledWith('123')).to.be.true; + }); + + it('should handle ObjectId _id correctly', function () { + const document = { + _id: { $oid: '507f1f77bcf86cd799439011' }, + name: 'Test', + }; + + render(); + + const editButton = screen.getByTitle('Edit'); + fireEvent.click(editButton); + + expect(sendEditDocumentStub.calledOnce).to.be.true; + expect(sendEditDocumentStub.firstCall.args[0]).to.deep.equal({ + $oid: '507f1f77bcf86cd799439011', + }); + }); + + it('should handle numeric _id correctly', function () { + const document = { _id: 42, name: 'Test' }; + + render(); + + const deleteButton = screen.getByTitle('Delete'); + fireEvent.click(deleteButton); + + expect(sendDeleteDocumentStub.calledOnce).to.be.true; + expect(sendDeleteDocumentStub.calledWith(42)).to.be.true; + }); + + it('should have correct codicon icons for each button', function () { + const document = { _id: '123', name: 'Test' }; + + render(); + + const editButton = screen.getByTitle('Edit'); + const copyButton = screen.getByTitle('Copy'); + const cloneButton = screen.getByTitle('Clone'); + const deleteButton = screen.getByTitle('Delete'); + + const editIcon = editButton.querySelector('.codicon-edit'); + const copyIcon = copyButton.querySelector('.codicon-copy'); + const cloneIcon = cloneButton.querySelector('.codicon-files'); + const deleteIcon = deleteButton.querySelector('.codicon-trash'); + + expect(editIcon).to.exist; + expect(copyIcon).to.exist; + expect(cloneIcon).to.exist; + expect(deleteIcon).to.exist; + }); + }); }); diff --git a/src/views/data-browsing-app/monaco-viewer.tsx b/src/views/data-browsing-app/monaco-viewer.tsx index dd96eddbc..a1268bb52 100644 --- a/src/views/data-browsing-app/monaco-viewer.tsx +++ b/src/views/data-browsing-app/monaco-viewer.tsx @@ -328,7 +328,7 @@ const MonacoViewer: React.FC = ({ return (
- {document.id && ( + {document._id && ( - {document.id && ( + {document._id && ( )} - {document.id && ( + {document._id && (