Skip to content
Merged
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
22 changes: 22 additions & 0 deletions lib/features/connections/connections_panel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -902,6 +902,9 @@ class ConnectionsPanelState extends State<ConnectionsPanel> {
if (id == null) return tile;
final colors = context.colors;
return material.Draggable<int>(
// Keyed by the connection: a row keeps its own expanded branches when
// another connection is inserted above it (#1821).
key: material.ValueKey('connection-$id'),
data: id,
maxSimultaneousDrags: 1,
onDragStarted: () {
Expand Down Expand Up @@ -1309,6 +1312,24 @@ class ConnectionsPanelState extends State<ConnectionsPanel> {
horizontal: 12, vertical: 12),
sliver: material.SliverList(
delegate: material.SliverChildBuilderDelegate(
findChildIndexCallback: (key) {
if (key is! material.ValueKey<String>) return null;
final v = key.value;
final offset = showEmptyState ? 1 : 0;
if (v.startsWith('folder-')) {
final i = filteredFolders
.indexOf(v.substring('folder-'.length));
return i < 0 ? null : i + offset;
}
if (v.startsWith('connection-')) {
final i = rootConnections.indexWhere(
(c) => 'connection-${c.id}' == v);
return i < 0
? null
: i + offset + filteredFolders.length;
}
return null;
},
(context, index) {
if (showEmptyState && index == 0) {
return material.Padding(
Expand All @@ -1325,6 +1346,7 @@ class ConnectionsPanelState extends State<ConnectionsPanel> {
if (folderIndex < filteredFolders.length) {
final name = filteredFolders[folderIndex];
return _FolderTile(
key: material.ValueKey('folder-$name'),
name: name,
initiallyExpanded: q.isNotEmpty ||
_expandedFolders.contains(name),
Expand Down
1 change: 1 addition & 0 deletions lib/features/connections/connections_panel_sidebar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ class _ConnectionTile extends StatelessWidget {

class _FolderTile extends StatefulWidget {
const _FolderTile({
super.key,
required this.name,
required this.initiallyExpanded,
required this.onExpansionCommitted,
Expand Down
106 changes: 106 additions & 0 deletions test/features/connections/connections_panel_keeps_rows_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import 'dart:io';

import 'package:flutter/material.dart' as material;
import 'package:flutter/material.dart' show MaterialApp;
import 'package:flutter_test/flutter_test.dart';
import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
import 'package:querya_desktop/core/storage/folders_storage.dart';
import 'package:querya_desktop/core/storage/local_db.dart';
import 'package:querya_desktop/features/connections/connections_panel.dart';

import '../../support/querya_theme_test_shell.dart';

class _FakePathProvider extends PathProviderPlatform {
_FakePathProvider(this._root);
final String _root;

@override
Future<String?> getApplicationSupportPath() async => _root;

@override
Future<String?> getTemporaryPath() async => _root;

@override
Future<String?> getApplicationDocumentsPath() async => _root;
}

ConnectionRow _row(String name) => ConnectionRow(
type: 'postgresql',
name: name,
host: '127.0.0.1',
port: 5432,
createdAt: DateTime.now().toUtc().toIso8601String(),
);

void main() {
TestWidgetsFlutterBinding.ensureInitialized();

late Directory tempDir;

setUpAll(() async {
tempDir = await Directory.systemTemp.createTemp('querya_rows_keep_test_');
PathProviderPlatform.instance = _FakePathProvider(tempDir.path);
await LocalDb.initFfi();
await LocalDb.instance.close();
await FoldersStorage.instance.reload();
});

tearDownAll(() async {
await LocalDb.instance.close();
if (await tempDir.exists()) await tempDir.delete(recursive: true);
});

testWidgets(
'a connection added above another leaves the other row\'s state alone '
'(#1821)', (tester) async {
late int zetaId;
await tester.runAsync(() async {
zetaId = await LocalDb.instance.addConnection(_row('Zeta'));
});

await tester.binding.setSurfaceSize(const material.Size(400, 720));
addTearDown(() => tester.binding.setSurfaceSize(null));
await tester.pumpWidget(
MaterialApp(
theme: queryaDarkMaterialTheme,
darkTheme: queryaDarkMaterialTheme,
themeMode: material.ThemeMode.dark,
builder: (context, child) => material.Material(child: child),
home: material.SizedBox.expand(
child: ConnectionsPanel(
skipInitialDbLoadForTest: true,
onPostgresOpenSqlWorkspace: (_, {database, schema, name, kind}) {},
),
),
),
);
final panel =
tester.state<ConnectionsPanelState>(find.byType(ConnectionsPanel));
Future<void> reload() async {
await tester.runAsync(() async {
await panel.reloadConnectionsFromDb();
await Future<void>.delayed(const Duration(milliseconds: 1));
});
await tester.pump(const Duration(milliseconds: 16));
}

await reload();
final zeta = find.byKey(material.ValueKey('connection-$zetaId'));
final before = tester.state(zeta);

await tester.runAsync(() async {
await LocalDb.instance.addConnection(_row('Alpha'));
});
await reload();

expect(find.text('Alpha'), findsOneWidget);
expect(identical(tester.state(zeta), before), isTrue,
reason: 'the Zeta row was rebuilt as a new element');

await tester.runAsync(() async {
for (final c in await LocalDb.instance.getConnections()) {
await LocalDb.instance.removeConnection(c.id!);
}
});
});
}
Loading