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>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
09e120221d
commit
d6fd9aea60
@@ -0,0 +1,410 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user