Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions calm-plugins/vscode/src/extension/extension.clear-url.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { beforeEach, describe, expect, it } from 'vitest';
import * as vscode from 'vscode';
import { activate } from './extension';

/**
* Regression test for #3523: after "Clear URL" the saved `calm.hub.url` is
* gone, so the status bar must forget it too. Before the fix the tooltip kept
* saying "Disconnected from <old URL> / Click to reconnect" while the click
* opened an empty URL box.
*/

interface ConfigUpdate {
section: string;
key: string;
value: unknown;
}

type CommandCallback = (...args: unknown[]) => unknown;

const HUB_URL = 'https://calmhub.example.com';

const registeredCommands = new Map<string, CommandCallback>();
const configUpdates: ConfigUpdate[] = [];
let statusBarItem: vscode.StatusBarItem;

function context(): vscode.ExtensionContext {
return {
subscriptions: [],
extensionUri: vscode.Uri.file('/tmp/extension'),
secrets: {
// Reading the stored token fails, so connecting ends in the error
// state without any network access.
get: async () => {
throw new Error('secret storage unavailable');
},
store: async () => {},
delete: async () => {},
onDidChange: () => ({ dispose: () => {} }),
},
} as unknown as vscode.ExtensionContext;
}

beforeEach(() => {
registeredCommands.clear();
configUpdates.length = 0;

(vscode.commands as unknown as { registerCommand: unknown }).registerCommand = (
command: string,
callback: CommandCallback
) => {
registeredCommands.set(command, callback);
return { dispose: () => {} };
};

(vscode.window as unknown as { createStatusBarItem: unknown }).createStatusBarItem = () => {
statusBarItem = {
text: '',
tooltip: undefined,
command: undefined,
backgroundColor: undefined,
show: () => {},
hide: () => {},
dispose: () => {},
} as unknown as vscode.StatusBarItem;
return statusBarItem;
};

(vscode.workspace as unknown as { getConfiguration: unknown }).getConfiguration = (
section?: string
) => ({
get: <T>(key: string, defaultValue?: T): T | undefined => {
if (key === 'url') return HUB_URL as unknown as T;
if (key === 'autoConnect') return false as unknown as T;
return defaultValue;
},
update: async (key: string, value: unknown) => {
configUpdates.push({ section: section ?? '', key, value });
},
});

activate(context());
});

async function failToConnect(): Promise<void> {
const connect = registeredCommands.get('calm.connectToHub');
expect(connect).toBeDefined();
await connect!();
expect(statusBarItem.text).toBe('$(cloud) CALM Hub (error)');
expect(statusBarItem.tooltip).toBe(`Failed to connect to ${HUB_URL}\nClick to retry`);
}

describe('clearing the Hub URL after a failed connection', () => {
it('makes the status bar forget the URL along with the setting', async () => {
await failToConnect();

const disconnect = registeredCommands.get('calm.disconnectFromHub');
expect(disconnect).toBeDefined();
await disconnect!();

expect(configUpdates).toEqual([{ section: 'calm.hub', key: 'url', value: undefined }]);
expect(statusBarItem.text).toBe('$(cloud) CALM Hub');
expect(statusBarItem.tooltip).toBe('Click to connect to CalmHub');
});
});
1 change: 1 addition & 0 deletions calm-plugins/vscode/src/extension/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ async function disconnectFromHub(): Promise<void> {
hubStatusBar?.setState('disconnected');
void canvasPanel?.setHubConnection(undefined);
await vscode.workspace.getConfiguration('calm.hub').update('url', undefined, vscode.ConfigurationTarget.Global);
hubStatusBar?.clearHubUrl();
outputChannel.appendLine('[INFO] Disconnected from CalmHub');
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,39 @@ describe('HubStatusBar', () => {
});
});

describe('clearHubUrl', () => {
it('keeps the URL on a plain disconnect so the tooltip offers to reconnect', () => {
statusBar.setState('error', undefined, 'https://calmhub.example.com');
expect(mockItem.tooltip).toBe(
'Failed to connect to https://calmhub.example.com\nClick to retry'
);

statusBar.setState('disconnected');
expect(mockItem.tooltip).toBe(
'Disconnected from https://calmhub.example.com\nClick to reconnect'
);
});

it('forgets the URL so the tooltip offers to connect', () => {
statusBar.setState('error', undefined, 'https://calmhub.example.com');
statusBar.setState('disconnected');

statusBar.clearHubUrl();

expect(statusBar.state).toBe('disconnected');
expect(mockItem.text).toBe('$(cloud) CALM Hub');
expect(mockItem.tooltip).toBe('Click to connect to CalmHub');
});

it('does not bring the URL back on the next state change', () => {
statusBar.setState('error', undefined, 'https://calmhub.example.com');
statusBar.clearHubUrl();

statusBar.setState('connecting');
expect(mockItem.tooltip).toBe('Connecting...');
});
});

describe('dispose', () => {
it('disposes the underlying status bar item', () => {
statusBar.dispose();
Expand Down
10 changes: 10 additions & 0 deletions calm-plugins/vscode/src/extension/services/hub-status-bar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ export class HubStatusBar {
this.update();
}

/**
* Forget the Hub URL. Called when the saved `calm.hub.url` is cleared, so
* the tooltip offers to connect instead of reconnecting to a URL that no
* longer exists (#3523). A plain disconnect keeps the URL (#3494).
*/
clearHubUrl(): void {
this.hubUrl = undefined;
this.update();
}

setAvailableAndSelected(available: string[], selected: string[]): void {
this.availableNamespaces = available;
this.selectedNamespaces = selected;
Expand Down
Loading