mirror of
https://github.com/kuhyx/todo-app.git
synced 2026-11-10 20:47:01 +01:00
Below 600dp the notes list is a compact tile grid (title + priority/status cues). Tile height is derived so whole rows exactly fill the viewport between the status and navigation bars; past a screenful it scrolls vertically (no paging). Body and dates moved into the long-press sheet. Wider windows keep the list. Not using design_system FitGrid: it is square-celled and pages horizontally. Pins: design_system v0.3.0 (newest tag); also bumped crdt_sync_dart 0.11.2, sync_settings_ui 0.4.2, file_picker 13.3.1, idb_shim 2.11.1, sqflite_common_ffi 2.4.5, web 1.1.2 because the freshness gate was already red on them at HEAD. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
162 lines
4.8 KiB
Dart
162 lines
4.8 KiB
Dart
/// What the narrow notes grid shows and does, apart from its geometry.
|
|
library;
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:todo/data/note.dart';
|
|
import 'package:todo/ui/note_grid_tile.dart';
|
|
|
|
import 'notes_list_harness.dart';
|
|
|
|
void _phone(WidgetTester t) {
|
|
t.view
|
|
..physicalSize = const Size(1080, 2400)
|
|
..devicePixelRatio = 2.625;
|
|
addTearDown(t.view.reset);
|
|
}
|
|
|
|
Future<void> _openSheet(WidgetTester t, String title) async {
|
|
await t.longPress(find.text(title));
|
|
await t.pump();
|
|
await t.pump(const Duration(milliseconds: 300));
|
|
}
|
|
|
|
void main() {
|
|
testWidgets('phone width shows tiles, desktop width keeps the list', (
|
|
t,
|
|
) async {
|
|
_phone(t);
|
|
await pumpList(t, seed: [note('a', 'Buy milk')]);
|
|
expect(find.byType(NoteGridTile), findsOneWidget);
|
|
expect(find.byType(ListTile), findsNothing);
|
|
|
|
t.view
|
|
..physicalSize = const Size(3840, 2160)
|
|
..devicePixelRatio = 1;
|
|
await t.pump();
|
|
expect(find.byType(NoteGridTile), findsNothing);
|
|
expect(find.byType(ListTile), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('a tile is title plus priority and status cues only', (t) async {
|
|
_phone(t);
|
|
await pumpList(
|
|
t,
|
|
seed: [
|
|
note(
|
|
'a',
|
|
'Buy milk\n\n## what\nTwo litres',
|
|
priority: Priority.high,
|
|
status: Status.inProgress,
|
|
),
|
|
],
|
|
);
|
|
|
|
expect(find.text('Buy milk'), findsOneWidget);
|
|
expect(find.text('High'), findsOneWidget);
|
|
expect(find.text('In progress'), findsOneWidget);
|
|
expect(find.textContaining('Two litres'), findsNothing);
|
|
expect(find.textContaining('edited'), findsNothing);
|
|
expect(find.byTooltip('Quick actions'), findsNothing);
|
|
});
|
|
|
|
testWidgets('priority and status each get their own cue', (t) async {
|
|
_phone(t);
|
|
await pumpList(
|
|
t,
|
|
seed: [
|
|
note('h', 'H', priority: Priority.high),
|
|
note('m', 'M'),
|
|
note('l', 'L', priority: Priority.low, status: Status.inProgress),
|
|
],
|
|
);
|
|
final scheme = Theme.of(t.element(find.byType(Scaffold))).colorScheme;
|
|
final accents = {
|
|
for (final id in ['h', 'm', 'l'])
|
|
t.widget<NoteGridTile>(find.byKey(ValueKey(id))).accentFor(scheme),
|
|
};
|
|
expect(accents, hasLength(3));
|
|
expect(find.byIcon(Icons.radio_button_unchecked), findsNWidgets(2));
|
|
expect(find.byIcon(Icons.timelapse), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('every status has an icon', (t) async {
|
|
_phone(t);
|
|
await pumpList(
|
|
t,
|
|
seed: [for (final s in Status.values) note(s.name, s.name, status: s)],
|
|
);
|
|
expect(find.byIcon(Icons.check_circle_outline), findsOneWidget);
|
|
expect(find.byIcon(Icons.cancel_outlined), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('an empty note reads (empty)', (t) async {
|
|
_phone(t);
|
|
await pumpList(t, seed: [note('a', '')]);
|
|
expect(find.text('(empty)'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('tap opens the note', (t) async {
|
|
_phone(t);
|
|
await pumpList(t, seed: [note('a', 'Open me')]);
|
|
|
|
await t.tap(find.text('Open me'));
|
|
await t.pumpAndSettle();
|
|
|
|
expect(find.text('Priority'), findsOneWidget);
|
|
expect(find.text('View'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('long-press opens the sheet with body, dates and actions', (
|
|
t,
|
|
) async {
|
|
_phone(t);
|
|
final repo = await pumpList(
|
|
t,
|
|
seed: [note('a', 'Buy milk\n\n## what\nTwo litres of oat')],
|
|
);
|
|
|
|
await _openSheet(t, 'Buy milk');
|
|
|
|
expect(find.textContaining('Two litres of oat'), findsOneWidget);
|
|
expect(find.textContaining('Edited'), findsOneWidget);
|
|
expect(find.textContaining('Created'), findsOneWidget);
|
|
await t.tap(find.text('In progress'));
|
|
await t.pump();
|
|
expect((await repo.listNotes()).single.status, Status.inProgress);
|
|
expect(find.text('Delete note'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('a title-only note adds no body to the sheet', (t) async {
|
|
_phone(t);
|
|
await pumpList(t, seed: [note('a', 'Just a title')]);
|
|
|
|
await _openSheet(t, 'Just a title');
|
|
|
|
expect(find.text('Just a title'), findsNWidgets(2));
|
|
expect(find.textContaining('Edited'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('the sheet can delete a grid note', (t) async {
|
|
_phone(t);
|
|
final repo = await pumpList(t, seed: [note('a', 'Delete me')]);
|
|
|
|
await _openSheet(t, 'Delete me');
|
|
await t.tap(find.text('Delete note'));
|
|
await t.pumpAndSettle();
|
|
await t.tap(find.text('Delete'));
|
|
await t.pumpAndSettle();
|
|
|
|
expect(await repo.listNotes(), isEmpty);
|
|
});
|
|
|
|
testWidgets('empty state and the default filter survive the grid', (t) async {
|
|
_phone(t);
|
|
final repo = await pumpList(t);
|
|
|
|
expect(find.textContaining('No notes'), findsOneWidget);
|
|
expect(repo.lastFilter!.statuses, {Status.todo, Status.inProgress});
|
|
expect(t.widget<Badge>(find.byType(Badge)).isLabelVisible, isFalse);
|
|
});
|
|
}
|