Files
tatlock-ui/test/shared/components/data_grid/data_grid_test.dart
T
Jeroen SchweitzerandClaude Opus 4.5 d6fd9aea60 test: add test harness and widget tests for DataGrid pages
Add comprehensive testing infrastructure:
- Test harness with mock auth, API client, and fixtures
- Mock Dio interceptor for canned API responses
- Fixtures for containers, domains, users, groups

Add widget tests for all DataGrid pages:
- ContainersListPage (21 tests)
- ProxyHostsPage (16 tests)
- UsersListPage (21 tests)
- GroupsListPage (18 tests)

Add unit tests:
- RoomRegistry (28 tests)
- DataGrid components (35 tests)

Test count: 282 -> 357 (+75 tests)
Coverage: 5.7% -> 19.1% (+504 lines)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 21:18:34 +01:00

411 lines
11 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:tatlock_ui/shared/components/data_grid/data_grid_column.dart';
import 'package:tatlock_ui/shared/components/data_grid/data_grid_config.dart';
import 'package:tatlock_ui/shared/components/data_grid/data_grid_state.dart';
import '../../../harness/test_harness.dart';
/// Test data class for DataGrid tests.
class TestItem {
const TestItem({
required this.id,
required this.name,
required this.value,
this.isActive = true,
});
final String id;
final String name;
final int value;
final bool isActive;
}
void main() {
final harness = TestHarness();
setUp(() => harness.setUp());
tearDown(() => harness.tearDown());
group('DataGridColumn', () {
test('creates column with required fields', () {
final column = DataGridColumn<TestItem>(
id: 'name',
header: 'Name',
valueBuilder: (item) => item.name,
);
expect(column.id, 'name');
expect(column.header, 'Name');
expect(column.sortable, isFalse);
expect(column.searchable, isFalse);
});
test('creates sortable column', () {
final column = DataGridColumn<TestItem>(
id: 'name',
header: 'Name',
valueBuilder: (item) => item.name,
sortable: true,
);
expect(column.sortable, isTrue);
});
test('creates searchable column', () {
final column = DataGridColumn<TestItem>(
id: 'name',
header: 'Name',
valueBuilder: (item) => item.name,
searchable: true,
);
expect(column.searchable, isTrue);
});
test('valueBuilder extracts correct value', () {
final column = DataGridColumn<TestItem>(
id: 'name',
header: 'Name',
valueBuilder: (item) => item.name,
);
final item = TestItem(id: '1', name: 'Test Item', value: 42);
expect(column.valueBuilder(item), 'Test Item');
});
test('supports fixed width', () {
final column = DataGridColumn<TestItem>(
id: 'id',
header: 'ID',
valueBuilder: (item) => item.id,
width: const DataGridColumnWidth.fixed(100),
);
expect(column.width, isA<GridFixedWidth>());
expect((column.width as GridFixedWidth).width, 100);
});
test('supports flex width', () {
final column = DataGridColumn<TestItem>(
id: 'name',
header: 'Name',
valueBuilder: (item) => item.name,
width: const DataGridColumnWidth.flex(2),
);
expect(column.width, isA<GridFlexWidth>());
expect((column.width as GridFlexWidth).flex, 2);
});
test('default width is flex(1)', () {
final column = DataGridColumn<TestItem>(
id: 'name',
header: 'Name',
valueBuilder: (item) => item.name,
);
expect(column.width, isA<GridFlexWidth>());
expect((column.width as GridFlexWidth).flex, 1);
});
test('supports custom alignment', () {
final column = DataGridColumn<TestItem>(
id: 'value',
header: 'Value',
valueBuilder: (item) => item.value.toString(),
alignment: DataGridColumnAlignment.end,
);
expect(column.alignment, DataGridColumnAlignment.end);
expect(column.textAlign, TextAlign.end);
expect(column.crossAxisAlignment, CrossAxisAlignment.end);
});
test('effectiveSortField defaults to lowercase header', () {
final column = DataGridColumn<TestItem>(
id: 'name',
header: 'Full Name',
valueBuilder: (item) => item.name,
);
expect(column.effectiveSortField, 'full name');
});
test('effectiveSortField uses sortField when provided', () {
final column = DataGridColumn<TestItem>(
id: 'name',
header: 'Full Name',
valueBuilder: (item) => item.name,
sortField: 'name',
);
expect(column.effectiveSortField, 'name');
});
});
group('DataGridConfig', () {
test('creates config with columns', () {
final config = DataGridConfig<TestItem>(
columns: [
DataGridColumn<TestItem>(
id: 'id',
header: 'ID',
valueBuilder: (item) => item.id,
),
DataGridColumn<TestItem>(
id: 'name',
header: 'Name',
valueBuilder: (item) => item.name,
),
],
);
expect(config.columns.length, 2);
});
test('enableSearch defaults to false', () {
final config = DataGridConfig<TestItem>(
columns: [],
);
expect(config.enableSearch, isFalse);
});
test('enables search when specified', () {
final config = DataGridConfig<TestItem>(
columns: [
DataGridColumn<TestItem>(
id: 'name',
header: 'Name',
valueBuilder: (item) => item.name,
searchable: true,
),
],
enableSearch: true,
);
expect(config.enableSearch, isTrue);
});
test('supports custom search hint', () {
final config = DataGridConfig<TestItem>(
columns: [],
enableSearch: true,
searchHint: 'Search items...',
);
expect(config.searchHint, 'Search items...');
});
test('supports row selection mode', () {
final config = DataGridConfig<TestItem>(
columns: [],
rowsSelectable: true,
);
expect(config.rowsSelectable, isTrue);
});
test('visibleColumns filters hidden columns', () {
final config = DataGridConfig<TestItem>(
columns: [
DataGridColumn<TestItem>(
id: 'id',
header: 'ID',
valueBuilder: (item) => item.id,
visible: false,
),
DataGridColumn<TestItem>(
id: 'name',
header: 'Name',
valueBuilder: (item) => item.name,
),
],
);
expect(config.visibleColumns.length, 1);
expect(config.visibleColumns.first.id, 'name');
});
test('searchableColumnIndices returns correct indices', () {
final config = DataGridConfig<TestItem>(
columns: [
DataGridColumn<TestItem>(
id: 'id',
header: 'ID',
valueBuilder: (item) => item.id,
),
DataGridColumn<TestItem>(
id: 'name',
header: 'Name',
valueBuilder: (item) => item.name,
searchable: true,
),
DataGridColumn<TestItem>(
id: 'value',
header: 'Value',
valueBuilder: (item) => item.value.toString(),
searchable: true,
),
],
);
expect(config.searchableColumnIndices, [1, 2]);
});
});
group('DataGridState', () {
test('creates initial state with defaults', () {
const state = DataGridState<TestItem>();
expect(state.items, isEmpty);
expect(state.isLoading, isFalse);
expect(state.selectedIds, isEmpty);
expect(state.searchQuery, isEmpty);
expect(state.isInitialLoad, isTrue);
});
test('creates loading state', () {
const state = DataGridState<TestItem>(
isLoading: true,
);
expect(state.isLoading, isTrue);
});
test('stores items', () {
final items = [
const TestItem(id: '1', name: 'Item 1', value: 10),
const TestItem(id: '2', name: 'Item 2', value: 20),
];
final state = DataGridState<TestItem>(
items: items,
isLoading: false,
);
expect(state.items.length, 2);
expect(state.items[0].name, 'Item 1');
});
test('stores selected IDs', () {
const state = DataGridState<TestItem>(
selectedIds: {'1', '2', '3'},
);
expect(state.selectedIds.length, 3);
expect(state.selectedIds.contains('1'), isTrue);
});
test('stores search query', () {
const state = DataGridState<TestItem>(
searchQuery: 'test query',
);
expect(state.searchQuery, 'test query');
});
test('stores sort column index', () {
const state = DataGridState<TestItem>(
sortColumnIndex: 1,
sortDescending: true,
);
expect(state.sortColumnIndex, 1);
expect(state.sortDescending, isTrue);
});
test('stores error', () {
const state = DataGridState<TestItem>(
error: 'Something went wrong',
);
expect(state.error, 'Something went wrong');
});
test('stores pagination info', () {
const state = DataGridState<TestItem>(
currentPage: 2,
totalCount: 100,
);
expect(state.currentPage, 2);
expect(state.totalCount, 100);
});
});
group('DataGridColumnWidth', () {
test('fixed width creates GridFixedWidth', () {
const width = DataGridColumnWidth.fixed(150);
expect(width, isA<GridFixedWidth>());
expect((width as GridFixedWidth).width, 150);
});
test('flex width creates GridFlexWidth', () {
const width = DataGridColumnWidth.flex(2);
expect(width, isA<GridFlexWidth>());
expect((width as GridFlexWidth).flex, 2);
});
test('default flex is 1', () {
const width = DataGridColumnWidth.flex();
expect((width as GridFlexWidth).flex, 1);
});
test('fraction width creates GridFractionWidth', () {
const width = DataGridColumnWidth.fraction(0.5);
expect(width, isA<GridFractionWidth>());
expect((width as GridFractionWidth).fraction, 0.5);
});
});
group('DataGridColumnAlignment', () {
test('has start alignment', () {
expect(DataGridColumnAlignment.start, isNotNull);
});
test('has center alignment', () {
expect(DataGridColumnAlignment.center, isNotNull);
});
test('has end alignment', () {
expect(DataGridColumnAlignment.end, isNotNull);
});
});
group('DataGridDataMode', () {
test('all mode', () {
const mode = DataGridDataMode.all();
expect(mode, isA<AllDataMode>());
});
test('paginated mode with default page size', () {
const mode = DataGridDataMode.paginated();
expect(mode, isA<PaginatedDataMode>());
expect((mode as PaginatedDataMode).pageSize, 25);
});
test('paginated mode with custom page size', () {
const mode = DataGridDataMode.paginated(pageSize: 50);
expect((mode as PaginatedDataMode).pageSize, 50);
});
test('infinite mode with defaults', () {
const mode = DataGridDataMode.infinite();
expect(mode, isA<InfiniteDataMode>());
expect((mode as InfiniteDataMode).initialLoad, 50);
expect((mode as InfiniteDataMode).loadMoreThreshold, 10);
});
test('infinite mode with custom values', () {
const mode = DataGridDataMode.infinite(
initialLoad: 100,
loadMoreThreshold: 20,
);
expect((mode as InfiniteDataMode).initialLoad, 100);
expect((mode as InfiniteDataMode).loadMoreThreshold, 20);
});
});
}