From da8c8adc9066f7d51b2941f3743085962617d91a Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Fri, 9 Oct 2026 11:15:34 +0300 Subject: [PATCH 1/2] fix(erd): a diagram with every table hidden says so, the empty state names the database (#1157) Hiding every table from the diagram showed "No tables found: this schema has no tables, or this role cannot see them", which is wrong: the tables exist. That case now has its own state with Show all tables. The empty state for a schema without tables names the database, and the Export menu's Copy Mermaid is covered by a test. --- lib/features/erd/erd_view.dart | 22 +++++++++- test/features/erd/erd_test.dart | 72 +++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/lib/features/erd/erd_view.dart b/lib/features/erd/erd_view.dart index b20c89e1..b857f0d4 100644 --- a/lib/features/erd/erd_view.dart +++ b/lib/features/erd/erd_view.dart @@ -678,14 +678,32 @@ class _ErdViewState extends material.State { onAction: _load, ), ); + } else if (full != null && full.tables.isNotEmpty && schema!.isEmpty) { + // Every table was hidden from the diagram: they exist, so say so and + // offer them back instead of "No tables found". + body = material.Center( + child: QueryaEmptyState( + icon: material.Icon(material.Icons.visibility_off_outlined, + color: wb.mutedForeground), + title: 'All tables are hidden', + description: '${_hidden.length} ' + '${_hidden.length == 1 ? 'table is' : 'tables are'} hidden ' + 'from the diagram.', + actionLabel: 'Show all tables', + onAction: _showAllTables, + ), + ); } else if (schema == null || layout == null || schema.isEmpty) { + final db = widget.databaseName; body = material.Center( child: QueryaEmptyState( icon: material.Icon(material.Icons.table_chart_outlined, color: wb.mutedForeground), title: 'No tables found', - description: - 'This schema has no tables, or this role cannot see them.', + description: db.isEmpty + ? 'This schema has no tables, or this role cannot see them.' + : 'The current schema of $db has no tables, or this role ' + 'cannot see them.', ), ); } else if (widget.neighbourhoodDepth != null && diff --git a/test/features/erd/erd_test.dart b/test/features/erd/erd_test.dart index 28e2da1e..639862b9 100644 --- a/test/features/erd/erd_test.dart +++ b/test/features/erd/erd_test.dart @@ -749,6 +749,78 @@ void main() { closeTo(height, 0.5)); }); + testWidgets('Copy Mermaid puts the diagram on the clipboard', (t) async { + await t.binding.setSurfaceSize(const material.Size(1200, 800)); + String? copied; + t.binding.defaultBinaryMessenger.setMockMethodCallHandler( + SystemChannels.platform, (call) async { + if (call.method == 'Clipboard.setData') { + copied = (call.arguments as Map)['text'] as String?; + } + return null; + }); + addTearDown(() => t.binding.defaultBinaryMessenger + .setMockMethodCallHandler(SystemChannels.platform, null)); + await t.pumpWidget(queryaThemeTestShell( + child: ErdView( + source: SqlErdSource(delegate: delegate(), dialect: SqlDialect.sqlite)), + )); + await t.pump(); + await t.pump(); + + await t.tap(find.byKey(const material.ValueKey('erd_export'))); + await t.pump(); + await t.tap(find.text('Copy Mermaid')); + await t.pump(); + expect(copied, startsWith('erDiagram')); + expect(copied, contains('users ||--|{ orders')); + await t.pump(const Duration(seconds: 6)); + }); + + testWidgets('hiding every table says so and offers them back', + variant: desktop, (t) async { + await t.binding.setSurfaceSize(const material.Size(1200, 800)); + await t.pumpWidget(queryaThemeTestShell( + child: ErdView( + source: SqlErdSource(delegate: delegate(), dialect: SqlDialect.sqlite)), + )); + await t.pump(); + await t.pump(); + + for (final table in ['users', 'orders']) { + await t.tap(find.byKey(material.ValueKey('erd_table_$table')), + buttons: kSecondaryButton); + await t.pump(); + await t.pump(const Duration(milliseconds: 300)); + await t.tap(find.text('Hide from diagram')); + await t.pump(const Duration(milliseconds: 300)); + } + expect(find.text('All tables are hidden'), findsOneWidget); + expect(find.text('No tables found'), findsNothing); + + await t.tap(find.text('Show all tables')); + await t.pump(); + await t.pump(); + expect(find.byKey(const material.ValueKey('erd_table_users')), + findsOneWidget); + }); + + testWidgets('an empty schema names the database', (t) async { + await t.pumpWidget(queryaThemeTestShell( + child: ErdView( + source: SqlErdSource( + delegate: FakeSqlExecutionDelegate( + onExecute: (_) => const SqlExecutionResult()), + dialect: SqlDialect.postgres), + databaseName: 'shop', + ), + )); + await t.pump(); + await t.pump(); + expect(find.text('No tables found'), findsOneWidget); + expect(find.textContaining('shop'), findsOneWidget); + }); + testWidgets('a card can be dragged and Auto layout puts it back', (t) async { await t.binding.setSurfaceSize(const material.Size(1200, 800)); From c0990738aaeb7ea03cb240f79a2f1e963cfa3b22 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Fri, 9 Oct 2026 11:19:01 +0300 Subject: [PATCH 2/2] test(erd): let the toast and the card menu finish before the next step (#1157) The Copy Mermaid toast starts its 5 s timer after its entry animation, so one 6 s pump left the timer pending. The closing card menu took the tap meant for Show all tables. --- test/features/erd/erd_test.dart | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/test/features/erd/erd_test.dart b/test/features/erd/erd_test.dart index 639862b9..e6363bc1 100644 --- a/test/features/erd/erd_test.dart +++ b/test/features/erd/erd_test.dart @@ -774,7 +774,11 @@ void main() { await t.pump(); expect(copied, startsWith('erDiagram')); expect(copied, contains('users ||--|{ orders')); - await t.pump(const Duration(seconds: 6)); + // The toast's 5 s timer starts after its entry animation: step the + // clock frame by frame until it has gone. + for (var i = 0; i < 10; i++) { + await t.pump(const Duration(seconds: 1)); + } }); testWidgets('hiding every table says so and offers them back', @@ -793,7 +797,8 @@ void main() { await t.pump(); await t.pump(const Duration(milliseconds: 300)); await t.tap(find.text('Hide from diagram')); - await t.pump(const Duration(milliseconds: 300)); + // Let the menu popover close, or it takes the next tap. + await t.pumpAndSettle(); } expect(find.text('All tables are hidden'), findsOneWidget); expect(find.text('No tables found'), findsNothing);