diff --git a/test/e2e/03_data_grid_and_mutations/e2e_dml_preview_and_commit_test.dart b/test/e2e/03_data_grid_and_mutations/e2e_dml_preview_and_commit_test.dart new file mode 100644 index 00000000..dd15ddab --- /dev/null +++ b/test/e2e/03_data_grid_and_mutations/e2e_dml_preview_and_commit_test.dart @@ -0,0 +1,94 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:querya_desktop/core/database/table_mutation_engine.dart'; + +import '../../support/fake_table_data_delegate.dart'; +import '../../support/generic_table_view_harness.dart'; +import '../helpers/e2e_grid_interactions.dart'; + +const _timeout = Timeout(Duration(seconds: 60)); + +/// `pumpAndSettle` never returns while a save runs: the spinner animates. +Future _frames(WidgetTester tester) async { + await tester.pump(); + await tester.pump(const Duration(milliseconds: 500)); +} + +void main() { + // dialect → (quoted table, quoted column) + const quoting = { + SqlDialect.postgres: ('"public"."users"', '"name"'), + SqlDialect.mysql: ('`public`.`users`', '`name`'), + SqlDialect.sqlite: ('"users"', '"name"'), + }; + + for (final entry in quoting.entries) { + final dialect = entry.key; + final (table, column) = entry.value; + + testWidgets('${dialect.name}: UPDATE, INSERT and DELETE are previewed ' + 'and committed in one plan', timeout: _timeout, (tester) async { + final delegate = FakeTableDataDelegate(); + final state = await pumpGenericTableView( + tester, + delegate, + dialect: dialect, + ); + await E2eGrid.shortcut(tester, LogicalKeyboardKey.keyE, ctrl: true); + await _frames(tester); + + await E2eGrid.doubleTapCell(tester, 'Alice'); + await E2eGrid.typeAndCommit(tester, 'Alicia'); + await _frames(tester); + await E2eGrid.tapCell(tester, 'Carol'); + await tester.tap(find.text('Delete Row')); + await _frames(tester); + await tester.tap(find.text('Add Row')); + await _frames(tester); + E2eGrid.expectStaged( + state.stagingBuffer!, + modifiedCells: 1, + inserted: 1, + deleted: 1, + ); + + await E2eGrid.shortcut(tester, LogicalKeyboardKey.keyS, ctrl: true); + await _frames(tester); + expect(find.text('Confirm Data Changes'), findsOneWidget); + expect(find.text('TRANSACTION SCRIPT'), findsOneWidget); + expect(delegate.appliedPlans, isEmpty, reason: 'nothing before confirm'); + + await tester.tap(find.text('Apply Changes')); + await _frames(tester); + await tester.pump(const Duration(seconds: 6)); // toast timer + + expect(delegate.appliedPlans, hasLength(1)); + final sql = delegate.appliedPlans.single.statements.map((s) => s.sql); + expect(sql.where((s) => s.startsWith('UPDATE $table SET $column = ')), + hasLength(1)); + expect(sql.where((s) => s.startsWith('INSERT INTO $table')), hasLength(1)); + expect(sql.where((s) => s.startsWith('DELETE FROM $table')), hasLength(1)); + expect(state.isDirty, isFalse); + }); + } + + testWidgets('Cancel in the preview keeps every pending edit', + timeout: _timeout, (tester) async { + final delegate = FakeTableDataDelegate(); + final state = await pumpGenericTableView(tester, delegate); + await E2eGrid.shortcut(tester, LogicalKeyboardKey.keyE, ctrl: true); + await _frames(tester); + await E2eGrid.doubleTapCell(tester, 'Alice'); + await E2eGrid.typeAndCommit(tester, 'Alicia'); + await _frames(tester); + + await E2eGrid.shortcut(tester, LogicalKeyboardKey.keyS, ctrl: true); + await _frames(tester); + await tester.tap(find.text('Cancel')); + await _frames(tester); + await tester.pump(const Duration(seconds: 6)); + + expect(delegate.appliedPlans, isEmpty); + E2eGrid.expectStaged(state.stagingBuffer!, modifiedCells: 1); + }); +} diff --git a/test/e2e/03_data_grid_and_mutations/e2e_grid_ast_filters_and_calc_test.dart b/test/e2e/03_data_grid_and_mutations/e2e_grid_ast_filters_and_calc_test.dart new file mode 100644 index 00000000..562454ec --- /dev/null +++ b/test/e2e/03_data_grid_and_mutations/e2e_grid_ast_filters_and_calc_test.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart' as material; +import 'package:flutter_test/flutter_test.dart'; +import 'package:querya_desktop/features/workspace/data_grid_filter_bar.dart'; + +import '../../support/fake_table_data_delegate.dart'; +import '../../support/generic_table_view_harness.dart'; +import '../helpers/e2e_grid_interactions.dart'; + +const _timeout = Timeout(Duration(seconds: 60)); + +FakeTableDataDelegate _accounts() => FakeTableDataDelegate( + columns: const ['id', 'name', 'balance'], + rows: const [ + ['1', 'alice', '150.00'], + ['2', 'bob', '50.00'], + ['3', 'charlie', '300.00'], + ['4', 'david', '0.00'], + ], + ); + +Future _filter(WidgetTester tester, String text) async { + await tester.enterText( + find.descendant( + of: find.byType(DataGridFilterBar), + matching: find.byType(material.TextField), + ), + text, + ); + await tester.pump(const Duration(milliseconds: 400)); + await tester.pumpAndSettle(); +} + +void main() { + testWidgets('balance > 100 keeps only the matching rows', timeout: _timeout, + (tester) async { + await pumpGenericTableView(tester, _accounts()); + await tester.tap(find.byTooltip('Toggle Quick Filter')); + await tester.pumpAndSettle(); + + await _filter(tester, 'balance > 100'); + + expect(find.text('alice'), findsOneWidget); + expect(find.text('charlie'), findsOneWidget); + expect(find.text('bob'), findsNothing); + expect(find.text('david'), findsNothing); + expect(find.text('2 / 4'), findsOneWidget); + + await _filter(tester, ''); + expect(find.text('bob'), findsOneWidget); + expect(find.text('david'), findsOneWidget); + }); + + testWidgets('a plain word filters by any cell', + timeout: _timeout, (tester) async { + await pumpGenericTableView(tester, _accounts()); + await tester.tap(find.byTooltip('Toggle Quick Filter')); + await tester.pumpAndSettle(); + + await _filter(tester, 'bob'); + expect(find.text('alice'), findsNothing); + expect(find.text('charlie'), findsNothing); + expect(find.text('1 / 4'), findsOneWidget); + }); + + testWidgets('selecting a numeric cell shows its Quick Calc statistics', + timeout: _timeout, (tester) async { + await pumpGenericTableView(tester, _accounts()); + + await E2eGrid.tapCell(tester, '300.00'); + await tester.pump(const Duration(milliseconds: 500)); + await tester.pumpAndSettle(); + + expect(find.text('Count: '), findsOneWidget); + expect(find.text('Sum: '), findsOneWidget); + expect(find.text('Max: '), findsOneWidget); + }); +} diff --git a/test/e2e/03_data_grid_and_mutations/e2e_grid_cell_editing_staging_test.dart b/test/e2e/03_data_grid_and_mutations/e2e_grid_cell_editing_staging_test.dart new file mode 100644 index 00000000..10601feb --- /dev/null +++ b/test/e2e/03_data_grid_and_mutations/e2e_grid_cell_editing_staging_test.dart @@ -0,0 +1,84 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../support/fake_table_data_delegate.dart'; +import '../../support/generic_table_view_harness.dart'; +import '../helpers/e2e_grid_interactions.dart'; + +const _timeout = Timeout(Duration(seconds: 60)); + +void main() { + testWidgets('double click edits a cell, Revert All restores it', + timeout: _timeout, (tester) async { + final state = await pumpGenericTableView(tester, FakeTableDataDelegate()); + + await E2eGrid.shortcut(tester, LogicalKeyboardKey.keyE, ctrl: true); + await tester.pumpAndSettle(); + expect(state.editMode, isTrue); + expect(find.text('No changes'), findsOneWidget); + + await E2eGrid.doubleTapCell(tester, 'Bob'); + await E2eGrid.typeAndCommit(tester, 'Bobby'); + await tester.pumpAndSettle(); + + E2eGrid.expectStaged(state.stagingBuffer!, modifiedCells: 1); + expect(find.text('Bobby'), findsOneWidget); + expect(find.text('No changes'), findsNothing); + + await tester.tap(find.text('Revert All')); + await tester.pumpAndSettle(); + + E2eGrid.expectStaged(state.stagingBuffer!); + expect(find.text('Bob'), findsOneWidget); + expect(find.text('Bobby'), findsNothing); + expect(find.text('No changes'), findsOneWidget); + }); + + testWidgets('Delete Row marks the selected row and Restore Row undoes it', + timeout: _timeout, (tester) async { + final state = await pumpGenericTableView(tester, FakeTableDataDelegate()); + await E2eGrid.shortcut(tester, LogicalKeyboardKey.keyE, ctrl: true); + await tester.pumpAndSettle(); + + await E2eGrid.tapCell(tester, 'Carol'); + await tester.tap(find.text('Delete Row')); + await tester.pumpAndSettle(); + E2eGrid.expectStaged(state.stagingBuffer!, deleted: 1); + expect(find.text('Restore Row'), findsOneWidget); + + await tester.tap(find.text('Restore Row')); + await tester.pumpAndSettle(); + E2eGrid.expectStaged(state.stagingBuffer!); + expect(find.text('Delete Row'), findsOneWidget); + }); + + testWidgets('Add Row stages an insert and Revert All drops it', + timeout: _timeout, (tester) async { + final state = await pumpGenericTableView(tester, FakeTableDataDelegate()); + await E2eGrid.shortcut(tester, LogicalKeyboardKey.keyE, ctrl: true); + await tester.pumpAndSettle(); + + await tester.tap(find.text('Add Row')); + await tester.pumpAndSettle(); + E2eGrid.expectStaged(state.stagingBuffer!, inserted: 1); + + await tester.tap(find.text('Revert All')); + await tester.pumpAndSettle(); + E2eGrid.expectStaged(state.stagingBuffer!); + }); + + testWidgets('a read-only session never offers editing', timeout: _timeout, + (tester) async { + final state = await pumpGenericTableView( + tester, + FakeTableDataDelegate(), + isReadOnly: true, + ); + + await E2eGrid.shortcut(tester, LogicalKeyboardKey.keyE, ctrl: true); + await tester.pumpAndSettle(); + + expect(state.editMode, isFalse); + expect(find.text('Add Row'), findsNothing); + }); +}