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>
411 lines
11 KiB
Dart
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);
|
|
});
|
|
});
|
|
}
|