Some checks failed
CI / Windows build (push) Has been cancelled
Book-like reading the user explicitly wanted: pairIntoRows groups pages into two-up spread rows (optional coverAlone for a title page; trailing odd page sits alone), spreadRowHeights fits each page to half the column and takes the tallest per row (common baseline), and spreadStackMetrics stacks the rows so the existing PageStackMetrics.visibleRange windows continuous-DOUBLE by ROW. Pure geometry reusing the continuous-single layer; the row-mounting widget is device-gated. Zero-rework-risk (not rendering). flutter analyze lib/editor clean; 172/172 tests (+8). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
97 lines
2.9 KiB
Dart
97 lines
2.9 KiB
Dart
// Tests for the two-up spread layout foundation (F2/F3).
|
|
|
|
import 'dart:ui' show Size;
|
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
import 'package:badnote/editor/layout/double_page_layout.dart';
|
|
import 'package:badnote/editor/pdf/pdf_document_source.dart';
|
|
|
|
class _Src implements PageDocumentSource {
|
|
_Src(this._sizes);
|
|
final List<Size> _sizes;
|
|
@override
|
|
int get pageCount => _sizes.length;
|
|
@override
|
|
Size pageSize(int index) => _sizes[index];
|
|
}
|
|
|
|
void main() {
|
|
group('pairIntoRows', () {
|
|
test('pairs from 0 by default', () {
|
|
expect(pairIntoRows(4), [
|
|
[0, 1],
|
|
[2, 3],
|
|
]);
|
|
});
|
|
|
|
test('trailing odd page gets a single-page row', () {
|
|
expect(pairIntoRows(5), [
|
|
[0, 1],
|
|
[2, 3],
|
|
[4],
|
|
]);
|
|
});
|
|
|
|
test('coverAlone puts page 0 alone, then pairs', () {
|
|
expect(pairIntoRows(5, coverAlone: true), [
|
|
[0],
|
|
[1, 2],
|
|
[3, 4],
|
|
]);
|
|
});
|
|
|
|
test('edge counts', () {
|
|
expect(pairIntoRows(0), isEmpty);
|
|
expect(pairIntoRows(1), [
|
|
[0],
|
|
]);
|
|
expect(pairIntoRows(1, coverAlone: true), [
|
|
[0],
|
|
]);
|
|
expect(pairIntoRows(2, coverAlone: true), [
|
|
[0],
|
|
[1],
|
|
]);
|
|
});
|
|
});
|
|
|
|
group('spreadRowHeights', () {
|
|
test('row height is the tallest page at half column width', () {
|
|
// page0 600x800 (aspect 800/600), page1 600x400. At column 600 → half 300.
|
|
// page0 fit = 300 * 800/600 = 400; page1 fit = 300 * 400/600 = 200.
|
|
final src = _Src([const Size(600, 800), const Size(600, 400)]);
|
|
final rows = pairIntoRows(2);
|
|
expect(spreadRowHeights(src, 600, rows), [400.0]); // max(400,200)
|
|
});
|
|
|
|
test('single-page row uses that page only', () {
|
|
final src = _Src([const Size(400, 800)]);
|
|
final rows = pairIntoRows(1);
|
|
// half = 150; fit = 150 * 800/400 = 300.
|
|
expect(spreadRowHeights(src, 300, rows), [300.0]);
|
|
});
|
|
|
|
test('non-positive width contributes 0', () {
|
|
final src = _Src([const Size(0, 800), const Size(400, 400)]);
|
|
final rows = pairIntoRows(2); // [[0,1]]
|
|
// page0 → 0; page1 half=150 fit=150*400/400=150 → max 150.
|
|
expect(spreadRowHeights(src, 300, rows), [150.0]);
|
|
});
|
|
});
|
|
|
|
group('spreadStackMetrics composes with windowing', () {
|
|
test('rows stack vertically; visibleRange selects visible rows', () {
|
|
// 6 portrait pages 600x800 → 3 rows; at column 600 each row 400 tall.
|
|
final src = _Src(List.filled(6, const Size(600, 800)));
|
|
final m = spreadStackMetrics(src, 600, gap: 0);
|
|
expect(m.pageCount, 3); // 3 ROWS
|
|
expect(m.totalExtent, closeTo(1200, 1e-9)); // 3 * 400
|
|
// viewport [350,850): row0 [0,400), row1 [400,800), row2 [800,1200).
|
|
// first bottom>350 → row0; last top<850 → row2.
|
|
expect(m.visibleRange(350, 500).first, 0);
|
|
expect(m.visibleRange(350, 500).last, 2);
|
|
});
|
|
});
|
|
}
|