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
2 changes: 1 addition & 1 deletion src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 13 additions & 2 deletions src/contexts/DatabaseProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -678,7 +678,13 @@ export const DatabaseProvider = ({ children }: { children: ReactNode }) => {
// comes from the server on every connect, so databases created/dropped
// outside the app show up without editing the connection.
const allDatabasesMode = isMultiDatabaseCapable(capabilities) && savedDbList.length === 0;
let isMultiDb = savedDbList.length > 1;
// `>= 1`, not `> 1`: a connection saved with a single database is still a
// database *selection*, and `usesMultiDatabaseLayout` already accepts one.
// Gating here at `> 1` left `selectedDatabases` empty for that case, so
// the layout function's `>= 1` was unreachable and the sidebar lost the
// manage/refresh controls — leaving no way to pick another database
// without reopening the connection settings.
let isMultiDb = savedDbList.length >= 1;
let dbList = isMultiDb ? savedDbList : [];

if (allDatabasesMode) {
Expand Down Expand Up @@ -713,7 +719,7 @@ export const DatabaseProvider = ({ children }: { children: ReactNode }) => {
const { selection, removed } = reconcileDatabaseSelection(dbList, available);
if (removed.length > 0) {
dbList = selection;
isMultiDb = selection.length > 1;
isMultiDb = selection.length >= 1;
invoke('set_selected_databases', {
connectionId,
databases: selection,
Expand Down Expand Up @@ -769,6 +775,11 @@ export const DatabaseProvider = ({ children }: { children: ReactNode }) => {
databaseDataMap: initialDbMap,
allDatabasesMode,
...(allDatabasesMode ? { databaseName: firstDb } : {}),
// A lone database has nothing to choose between, so mark it active:
// the tree expands the active database, and leaving it collapsed
// would put its tables one click further away than the flat
// single-database layout this replaces.
...(dbList.length === 1 ? { activeSchema: firstDb } : {}),
isLoadingTables: false,
isLoadingViews: false,
isLoadingRoutines: false,
Expand Down
102 changes: 102 additions & 0 deletions tests/contexts/DatabaseProvider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,24 @@ const mockPostgresManifest = {
},
};

const mockMysqlManifest = {
id: 'mysql',
name: 'MySQL',
version: '1.0.0',
description: '',
default_port: 3306,
capabilities: {
schemas: false,
single_database: false,
file_based: false,
folder_based: false,
no_connection_required: false,
views: true,
routines: true,
identifier_quote: '`',
},
};

describe('DatabaseProvider', () => {
const mockConnections = [
{
Expand Down Expand Up @@ -175,6 +193,90 @@ describe('DatabaseProvider', () => {
expect(invoke).toHaveBeenCalledWith('get_views', { connectionId: 'conn-123' });
});

it('exposes a single saved database as a selection, not as a flat connection', async () => {
// A connection saved with one database is still a database *selection*:
// `usesMultiDatabaseLayout` accepts one, so `selectedDatabases` must be
// populated for it, otherwise the sidebar drops the manage/refresh controls
// and there is no way to pick another database without editing the
// connection.
const singleDbConnection = [
{
id: 'conn-single',
name: 'Single DB MySQL',
params: { driver: 'mysql', host: 'localhost', database: 'onlydb' },
},
];

vi.mocked(invoke).mockImplementation((cmd: string) => {
if (cmd === 'get_connections') return Promise.resolve(singleDbConnection);
if (cmd === 'get_driver_manifest') return Promise.resolve(mockMysqlManifest);
if (cmd === 'test_connection') return Promise.resolve('Connection successful!');
if (cmd === 'register_active_connection') return Promise.resolve(undefined);
if (cmd === 'get_available_databases') return Promise.resolve(['onlydb', 'otherdb']);
if (cmd === 'get_tables') return Promise.resolve(mockTables);
if (cmd === 'get_views') return Promise.resolve(mockViews);
if (cmd === 'get_routines') return Promise.resolve(mockRoutines);
if (cmd === 'get_triggers') return Promise.resolve([]);
if (cmd === 'set_window_title') return Promise.resolve(undefined);
return Promise.reject(new Error(`Unexpected command: ${cmd}`));
});

const wrapper = ({ children }: { children: React.ReactNode }) =>
React.createElement(DatabaseProvider, null, children);

const { result } = renderHook(() => useDatabase(), { wrapper });

await act(async () => {
await result.current.connect('conn-single');
});

await waitFor(() => {
expect(result.current.selectedDatabases).toEqual(['onlydb']);
// Marked active so the tree expands it: a lone database should not sit
// collapsed behind an extra click.
expect(result.current.activeSchema).toBe('onlydb');
});
});

it('leaves no database active when several are selected', async () => {
// With a choice to make, none is picked for the user.
const twoDbConnection = [
{
id: 'conn-two',
name: 'Two DB MySQL',
params: { driver: 'mysql', host: 'localhost', database: ['firstdb', 'seconddb'] },
},
];

vi.mocked(invoke).mockImplementation((cmd: string) => {
if (cmd === 'get_connections') return Promise.resolve(twoDbConnection);
if (cmd === 'get_driver_manifest') return Promise.resolve(mockMysqlManifest);
if (cmd === 'test_connection') return Promise.resolve('Connection successful!');
if (cmd === 'register_active_connection') return Promise.resolve(undefined);
if (cmd === 'get_available_databases') return Promise.resolve(['firstdb', 'seconddb']);
if (cmd === 'get_tables') return Promise.resolve(mockTables);
if (cmd === 'get_views') return Promise.resolve(mockViews);
if (cmd === 'get_routines') return Promise.resolve(mockRoutines);
if (cmd === 'get_triggers') return Promise.resolve([]);
if (cmd === 'set_window_title') return Promise.resolve(undefined);
return Promise.reject(new Error(`Unexpected command: ${cmd}`));
});

const wrapper = ({ children }: { children: React.ReactNode }) =>
React.createElement(DatabaseProvider, null, children);

const { result } = renderHook(() => useDatabase(), { wrapper });

await act(async () => {
await result.current.connect('conn-two');
});

await waitFor(() => {
expect(result.current.selectedDatabases).toEqual(['firstdb', 'seconddb']);
expect(result.current.activeSchema).toBeNull();
});
});

it('should handle connection failure', async () => {
vi.mocked(invoke).mockRejectedValue(new Error('Connection failed'));

Expand Down