diff --git a/__mocks__/vscode.js b/__mocks__/vscode.js index bf4fb789..03b5158f 100644 --- a/__mocks__/vscode.js +++ b/__mocks__/vscode.js @@ -89,6 +89,7 @@ module.exports = { showErrorMessage: jest.fn(), showInformationMessage: jest.fn(() => Promise.resolve(undefined)), showWarningMessage: jest.fn(), + showInputBox: jest.fn(), showQuickPick: jest.fn(), createInputBox: jest.fn(() => { const handlers = { onDidChangeValue: [], onDidAccept: [], onDidHide: [] }; diff --git a/package.json b/package.json index d27b3e67..ba852bc8 100644 --- a/package.json +++ b/package.json @@ -163,6 +163,11 @@ "title": "Show in Memory Inspector", "category": "Live Watch" }, + { + "command": "vscode-cmsis-debugger.liveWatch.setValue", + "title": "Set Value", + "category": "Live Watch" + }, { "command": "vscode-cmsis-debugger.componentViewer.lockComponent", "title": "Lock Component", @@ -326,6 +331,10 @@ "command": "vscode-cmsis-debugger.liveWatch.showInMemoryInspector", "when": "false" }, + { + "command": "vscode-cmsis-debugger.liveWatch.setValue", + "when": "false" + }, { "command": "vscode-cmsis-debugger.componentViewer.lockComponent", "when": "false" @@ -473,6 +482,11 @@ "when": "view == cmsis-debugger.liveWatch && viewItem == parentExpression", "group": "contextMenuG1@4" }, + { + "command": "vscode-cmsis-debugger.liveWatch.setValue", + "when": "view == cmsis-debugger.liveWatch && inDebugMode", + "group": "contextMenuG1@5" + }, { "command": "vscode-cmsis-debugger.liveWatch.deleteAll", "when": "view == cmsis-debugger.liveWatch", diff --git a/src/views/live-watch/live-watch.test.ts b/src/views/live-watch/live-watch.test.ts index 08ff7756..a9b9ceaf 100644 --- a/src/views/live-watch/live-watch.test.ts +++ b/src/views/live-watch/live-watch.test.ts @@ -240,6 +240,45 @@ describe('LiveWatchTreeDataProvider', () => { expect(vscode.env.clipboard.writeText).toHaveBeenCalledWith('myExpression'); }); + it('set root value and send a setExpression request to session and calls refresh right after', async () => { + const node = makeNode('myVar', { result: '1', variablesReference: 0 }, 1); + const refreshSpy = jest.spyOn(liveWatchTreeDataProvider as any, 'refresh').mockResolvedValue(''); + (liveWatchTreeDataProvider as any)._activeSession = { + session: { + customRequest: jest.fn().mockResolvedValue({ result: '2', variablesReference: 0 }) + }, + evaluateGlobalExpression: jest.fn() + }; + (vscode.window as any).showInputBox = jest.fn().mockResolvedValue('2'); + await (liveWatchTreeDataProvider as any).handleSetValueCommand(node); + expect((liveWatchTreeDataProvider as any)._activeSession.session.customRequest).toHaveBeenCalledWith('setExpression', { expression: 'myVar', frameId: 0, value: '2' }); + expect(refreshSpy).toHaveBeenCalled(); + }); + + it('set child node value and send a setVariable request to session', async () => { + const parent = makeNode('parentVar', { result: '1', variablesReference: 123 }, 1); + const child = makeNode('childVar', { result: '1', variablesReference: 0 }, 2, parent); + const refreshSpy = jest.spyOn(liveWatchTreeDataProvider as any, 'refresh').mockResolvedValue(''); + (liveWatchTreeDataProvider as any)._activeSession = { + session: { + customRequest: jest.fn().mockResolvedValue({ result: '2', variablesReference: 0 }) + }, + evaluateGlobalExpression: jest.fn() + }; + (vscode.window as any).showInputBox = jest.fn().mockResolvedValue('2'); + await (liveWatchTreeDataProvider as any).handleSetValueCommand(child); + expect((liveWatchTreeDataProvider as any)._activeSession.session.customRequest).toHaveBeenCalledWith('setVariable', { name: 'childVar', value: '2', variablesReference: 123 }); + expect(refreshSpy).toHaveBeenCalled(); + }); + + it('should show error message when trying to set value with no active session', async () => { + const node = makeNode('node', { result: '1', variablesReference: 0 }, 1); + (liveWatchTreeDataProvider as any)._activeSession = undefined; + const showErrorMessageSpy = jest.spyOn(vscode.window, 'showErrorMessage').mockImplementation(undefined); + await (liveWatchTreeDataProvider as any).handleSetValueCommand(node); + expect(showErrorMessageSpy).toHaveBeenCalledWith('No active debug session'); + }); + it('AddFromSelection adds selected text as new live watch expression to roots', async () => { jest.spyOn(liveWatchTreeDataProvider as any, 'evaluate').mockResolvedValue({ result: '5678', variablesReference: 0 }); // Mock the active text editor with fake range @@ -333,18 +372,19 @@ describe('LiveWatchTreeDataProvider', () => { it('registers all live watch commands on activate', async () => { await liveWatchTreeDataProvider.activate(tracker); const calls = (vscode.commands.registerCommand as jest.Mock).mock.calls.map(call => call[0]); - expect(calls).toEqual(expect.arrayContaining([ + expect(calls).toEqual([ 'vscode-cmsis-debugger.liveWatch.add', 'vscode-cmsis-debugger.liveWatch.deleteAll', 'vscode-cmsis-debugger.liveWatch.delete', 'vscode-cmsis-debugger.liveWatch.refresh', 'vscode-cmsis-debugger.liveWatch.modify', 'vscode-cmsis-debugger.liveWatch.copy', + 'vscode-cmsis-debugger.liveWatch.setValue', 'vscode-cmsis-debugger.liveWatch.addToLiveWatchFromTextEditor', 'vscode-cmsis-debugger.liveWatch.addToLiveWatchFromWatchWindow', 'vscode-cmsis-debugger.liveWatch.addToLiveWatchFromVariablesView', 'vscode-cmsis-debugger.liveWatch.showInMemoryInspector' - ])); + ]); }); it('add command adds a node when expression provided', async () => { @@ -458,6 +498,37 @@ describe('LiveWatchTreeDataProvider', () => { expect((liveWatchTreeDataProvider as any).roots.length).toBe(0); }); + it('set value command calls handleSetValueCommand with node', async () => { + const node = makeNode('node', { result: '1', variablesReference: 0 }, 1); + const handleSetValueSpy = jest.spyOn(liveWatchTreeDataProvider as any, 'handleSetValueCommand').mockResolvedValue(''); + await liveWatchTreeDataProvider.activate(tracker); + // Simulate command invocation with node argument + const handler = getRegisteredHandler('vscode-cmsis-debugger.liveWatch.setValue'); + expect(handler).toBeDefined(); + await handler(node); + expect(handleSetValueSpy).toHaveBeenCalledWith(node); + }); + + it('set value command does nothing when node is undefined', async () => { + const handleSetValueSpy = jest.spyOn(liveWatchTreeDataProvider as any, 'handleSetValueCommand').mockResolvedValue(''); + await liveWatchTreeDataProvider.activate(tracker); + const handler = getRegisteredHandler('vscode-cmsis-debugger.liveWatch.setValue'); + expect(handler).toBeDefined(); + await handler(undefined); + expect(handleSetValueSpy).toHaveBeenCalled(); + }); + + it('set value command does nothing when no new value provided', async () => { + const node = makeNode('node', { result: '1', variablesReference: 0 }, 1); + const handleSetValueSpy = jest.spyOn(liveWatchTreeDataProvider as any, 'handleSetValueCommand').mockResolvedValue(''); + (vscode.window as any).showInputBox = jest.fn().mockResolvedValue(undefined); + await liveWatchTreeDataProvider.activate(tracker); + const handler = getRegisteredHandler('vscode-cmsis-debugger.liveWatch.setValue'); + expect(handler).toBeDefined(); + await handler(node); + expect(handleSetValueSpy).toHaveBeenCalled(); + }); + it('showInMemoryInspector command does nothing when node is undefined', async () => { await liveWatchTreeDataProvider.activate(tracker); const handler = getRegisteredHandler('vscode-cmsis-debugger.liveWatch.showInMemoryInspector'); diff --git a/src/views/live-watch/live-watch.ts b/src/views/live-watch/live-watch.ts index cd21284a..5132e0d0 100644 --- a/src/views/live-watch/live-watch.ts +++ b/src/views/live-watch/live-watch.ts @@ -152,6 +152,9 @@ export class LiveWatchTreeDataProvider implements vscode.TreeDataProvider await this.refresh()); const modifyCommand = vscode.commands.registerCommand('vscode-cmsis-debugger.liveWatch.modify', async (node) => await this.handleRenameCommand(node)); const copyCommand = vscode.commands.registerCommand('vscode-cmsis-debugger.liveWatch.copy', async (node) => await this.handleCopyCommand(node)); + const setValueCommand = vscode.commands.registerCommand('vscode-cmsis-debugger.liveWatch.setValue', async (node) => { + await this.handleSetValueCommand(node); + }); const addToLiveWatchCommand = vscode.commands.registerCommand('vscode-cmsis-debugger.liveWatch.addToLiveWatchFromTextEditor', async () => await this.handleAddFromSelectionCommand()); /* omarArm: I am using the same callback function for both watch window and variables view, as they have the same payload structure for now. @@ -174,6 +177,7 @@ export class LiveWatchTreeDataProvider implements vscode.TreeDataProvider