From 6907afc9773574640b2de5fdb39b80c266c336ab Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sat, 10 Oct 2026 10:30:15 +0300 Subject: [PATCH] test(table): close the acceptance gaps of #1149 - SQLite joins PostgreSQL and MySQL in the left/right layout check. - A double click on a neighbour without a custom callback opens it through QueryaCommandHost.onOpenSchemaObject (orders in public, a table) and asks for its tab to start in Relations. - Data, Relations, Data keeps the quick filter, not only staged edits and the page. - A materialized view does not offer the switch, like a view. The table view harness takes isMaterializedView and onOpenSchemaObject. --- .../generic_table_view_relations_test.dart | 60 ++++++++++++++++++- test/support/generic_table_view_harness.dart | 40 ++++++++----- 2 files changed, 85 insertions(+), 15 deletions(-) diff --git a/test/features/workspace/generic_table_view_relations_test.dart b/test/features/workspace/generic_table_view_relations_test.dart index a25f41d1..2e935946 100644 --- a/test/features/workspace/generic_table_view_relations_test.dart +++ b/test/features/workspace/generic_table_view_relations_test.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.dart' as material; import 'package:flutter_test/flutter_test.dart'; +import 'package:querya_desktop/core/actions/querya_schema_object.dart'; import 'package:querya_desktop/core/actions/table_view_command_bridge.dart'; import 'package:querya_desktop/core/database/table_mutation_engine.dart'; import 'package:querya_desktop/core/erd/erd_catalog.dart'; import 'package:querya_desktop/features/erd/erd_view.dart'; +import 'package:querya_desktop/features/workspace/data_grid_filter_bar.dart'; import 'package:querya_desktop/features/workspace/results_tab.dart'; import 'package:querya_desktop/features/workspace/table_data_delegate.dart'; @@ -13,7 +15,7 @@ import '../../support/generic_table_view_harness.dart'; /// A catalog around `users`: it references `orgs`, `orders` references it, /// `orgs` references `regions` (two keys away), `unrelated` has no keys. TableDataPage _catalog(String sql) { - for (final d in [SqlDialect.postgres, SqlDialect.mysql]) { + for (final d in SqlDialect.values) { if (sql == ErdCatalog.foreignKeysSql(d)) { return const TableDataPage(columns: ['t', 'c', 'rt', 'rc'], rows: [ ['orders', 'user_id', 'users', 'id'], @@ -60,6 +62,7 @@ void main() { (SqlDialect.postgres, 'public'), // MySQL passes the database as the schema; its catalog names are bare. (SqlDialect.mysql, 'shop'), + (SqlDialect.sqlite, null), ]) { testWidgets( '${dialect.name}: referenced tables sit left, referencing ones right', @@ -120,6 +123,29 @@ void main() { expect(opened, ['orders']); }); + testWidgets( + 'a double click on a neighbour opens it through the host, in Relations', + (t) async { + final opened = []; + final state = await pumpGenericTableView( + t, + FakeTableDataDelegate(onCustomSql: _catalog), + onOpenSchemaObject: opened.add, + ); + state.selectView(1); + await _settleDiagram(t); + + await t.tap(_card('orders')); + await t.pump(const Duration(milliseconds: 50)); + await t.tap(_card('orders')); + await t.pump(const Duration(milliseconds: 400)); + + expect(opened.map((o) => (o.name, o.schema, o.kind)), + [('orders', 'public', QueryaSchemaObjectKind.table)]); + // The neighbour's tab starts in Relations, like the table it came from. + expect(TableViewCommandBridge.instance.takePendingView(), 1); + }); + testWidgets('Data, Relations, Data keeps staged edits', (t) async { final delegate = FakeTableDataDelegate(onCustomSql: _catalog); final state = await pumpGenericTableView(t, delegate); @@ -158,6 +184,32 @@ void main() { expect(delegate.pagesLoaded.length, loads); }); + testWidgets('Data, Relations, Data keeps the quick filter', (t) async { + final state = await pumpGenericTableView( + t, FakeTableDataDelegate(onCustomSql: _catalog)); + await t.tap(find.byTooltip('Toggle Quick Filter')); + await t.pumpAndSettle(); + final field = find.descendant( + of: find.byType(DataGridFilterBar), + matching: find.byType(material.TextField), + ); + await t.enterText(field, 'Bob'); + // Let the filter bar's debounce fire. + await t.pump(const Duration(milliseconds: 400)); + await t.pumpAndSettle(); + expect(find.text('Alice'), findsNothing); + expect(find.text('Bob'), findsWidgets); + + state.selectView(1); + await _settleDiagram(t); + state.selectView(0); + await t.pumpAndSettle(); + + expect(t.widget(field).controller!.text, 'Bob'); + expect(find.text('Alice'), findsNothing); + expect(find.text('Bob'), findsWidgets); + }); + testWidgets('a table offers the Data | Relations switch', (t) async { await pumpGenericTableView(t, FakeTableDataDelegate()); expect(find.text('Relations'), findsOneWidget); @@ -168,6 +220,12 @@ void main() { expect(find.text('Relations'), findsNothing); }); + testWidgets('a materialized view does not offer the switch', (t) async { + await pumpGenericTableView(t, FakeTableDataDelegate(), + isMaterializedView: true); + expect(find.text('Relations'), findsNothing); + }); + testWidgets('extension tables do not offer the switch', (t) async { await pumpGenericTableView(t, FakeTableDataDelegate(), showRelations: false); diff --git a/test/support/generic_table_view_harness.dart b/test/support/generic_table_view_harness.dart index 82804724..ebc08e4b 100644 --- a/test/support/generic_table_view_harness.dart +++ b/test/support/generic_table_view_harness.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart' as material; import 'package:flutter_test/flutter_test.dart'; +import 'package:querya_desktop/core/actions/querya_command_host.dart'; +import 'package:querya_desktop/core/actions/querya_schema_object.dart'; import 'package:querya_desktop/core/database/table_mutation_engine.dart'; import 'package:querya_desktop/features/workspace/generic_table_view.dart'; @@ -14,30 +16,40 @@ Future pumpGenericTableView( SqlDialect dialect = SqlDialect.postgres, bool isReadOnly = false, bool isView = false, + bool isMaterializedView = false, bool showRelations = true, int limit = 200, String? schema = 'public', void Function(String table)? onOpenNeighbour, + // Wraps the view in a [QueryaCommandHost] that opens schema objects, the way + // the main screen does. + void Function(QueryaSchemaObject object)? onOpenSchemaObject, }) async { await tester.binding.setSurfaceSize(const material.Size(1300, 800)); addTearDown(() => tester.binding.setSurfaceSize(null)); + material.Widget view = GenericTableView( + delegate: delegate, + title: 'users', + dialect: dialect, + tableName: 'users', + schema: schema, + onOpenNeighbour: onOpenNeighbour, + isReadOnly: isReadOnly, + isView: isView, + isMaterializedView: isMaterializedView, + showRelations: showRelations, + limit: limit, + ); + if (onOpenSchemaObject != null) { + view = QueryaCommandHost( + onOpenSchemaObject: onOpenSchemaObject, + child: view, + ); + } await tester.pumpWidget( queryaThemeTestShell( child: material.Scaffold( - body: material.SizedBox.expand( - child: GenericTableView( - delegate: delegate, - title: 'users', - dialect: dialect, - tableName: 'users', - schema: schema, - onOpenNeighbour: onOpenNeighbour, - isReadOnly: isReadOnly, - isView: isView, - showRelations: showRelations, - limit: limit, - ), - ), + body: material.SizedBox.expand(child: view), ), ), );