Some checks failed
CI / Windows build (push) Has been cancelled
The SpeedyNote-style page binding the user asked for: a notebook is an ordered list of logical pages, each a SOURCE page (renders PDF page N, vector preserved) or a BLANK inserted page. Crucially, inserting/reordering logical pages does NOT renumber the PDF underlay — each page carries its source index. Copy-on-write edits (insertBlankAt/After, removeAt, move) return a new immutable PageMap; out-of-range edits throw RangeError; value equality + unmodifiable page list. Pure model (no DB/widget) so it's fully unit-tested; the notebook_pages table + viewport wire it later. flutter analyze lib/editor clean; 156/156 tests (+11). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
90 lines
3.0 KiB
Dart
90 lines
3.0 KiB
Dart
// Tests for the one-notebook-per-PDF logical page model (F6).
|
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
import 'package:badnote/editor/notebook/page_map.dart';
|
|
|
|
void main() {
|
|
test('fromSource is the identity map (one source page per PDF page)', () {
|
|
final m = PageMap.fromSource(3);
|
|
expect(m.length, 3);
|
|
expect(m.sourceCount, 3);
|
|
expect(m.blankCount, 0);
|
|
expect([m.sourceIndexAt(0), m.sourceIndexAt(1), m.sourceIndexAt(2)],
|
|
[0, 1, 2]);
|
|
});
|
|
|
|
test('fromSource(0) is empty', () {
|
|
final m = PageMap.fromSource(0);
|
|
expect(m.isEmpty, isTrue);
|
|
expect(m.length, 0);
|
|
});
|
|
|
|
test('insertBlankAt inserts a blank and shifts logical order, NOT source idx',
|
|
() {
|
|
final m = PageMap.fromSource(2).insertBlankAt(1);
|
|
expect(m.length, 3);
|
|
expect(m[0], const NotebookPage.source(0));
|
|
expect(m[1].isBlank, isTrue);
|
|
expect(m[2], const NotebookPage.source(1)); // source index UNCHANGED
|
|
expect(m.sourceIndexAt(1), isNull);
|
|
expect(m.blankCount, 1);
|
|
});
|
|
|
|
test('insertBlankAfter(-1) prepends; after(last) appends', () {
|
|
final m = PageMap.fromSource(2);
|
|
expect(m.insertBlankAfter(-1)[0].isBlank, isTrue);
|
|
final appended = m.insertBlankAfter(1);
|
|
expect(appended[2].isBlank, isTrue);
|
|
expect(appended.length, 3);
|
|
});
|
|
|
|
test('insertBlankAt at length appends (boundary)', () {
|
|
final m = PageMap.fromSource(2).insertBlankAt(2);
|
|
expect(m.length, 3);
|
|
expect(m[2].isBlank, isTrue);
|
|
});
|
|
|
|
test('removeAt drops the logical page', () {
|
|
final m = PageMap.fromSource(3).removeAt(1);
|
|
expect(m.length, 2);
|
|
expect([m.sourceIndexAt(0), m.sourceIndexAt(1)], [0, 2]);
|
|
});
|
|
|
|
test('move reorders (drag-reorder thumbnails)', () {
|
|
final m = PageMap.fromSource(3).move(0, 2); // [1,2,0]
|
|
expect([m.sourceIndexAt(0), m.sourceIndexAt(1), m.sourceIndexAt(2)],
|
|
[1, 2, 0]);
|
|
});
|
|
|
|
test('move to same index is a no-op (returns identical instance)', () {
|
|
final m = PageMap.fromSource(3);
|
|
expect(identical(m.move(1, 1), m), isTrue);
|
|
});
|
|
|
|
test('out-of-range edits throw RangeError', () {
|
|
final m = PageMap.fromSource(2);
|
|
expect(() => m.insertBlankAt(3), throwsRangeError);
|
|
expect(() => m.removeAt(2), throwsRangeError);
|
|
expect(() => m.move(0, 5), throwsRangeError);
|
|
expect(() => m.insertBlankAfter(2), throwsRangeError);
|
|
});
|
|
|
|
test('value equality + immutability of the page list', () {
|
|
expect(PageMap.fromSource(2), PageMap.fromSource(2));
|
|
expect(PageMap.fromSource(2).insertBlankAt(0) == PageMap.fromSource(2),
|
|
isFalse);
|
|
final m = PageMap.fromSource(1);
|
|
expect(() => m.pages.add(const NotebookPage.blank()),
|
|
throwsUnsupportedError);
|
|
});
|
|
|
|
test('NotebookPage equality distinguishes kind + source index', () {
|
|
expect(const NotebookPage.source(1), const NotebookPage.source(1));
|
|
expect(const NotebookPage.source(1) == const NotebookPage.source(2),
|
|
isFalse);
|
|
expect(const NotebookPage.blank() == const NotebookPage.source(0), isFalse);
|
|
expect(const NotebookPage.blank(), const NotebookPage.blank());
|
|
});
|
|
}
|