diff options
| author | Craig Jennings <c@cjennings.net> | 2026-05-26 14:05:40 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-05-26 14:05:40 -0500 |
| commit | 70e89e946cbdff307284d11a46558161f713607c (patch) | |
| tree | c305ce9249fac0aef1318f5caabae0df31be7e95 /pocketbook/tests | |
| parent | 92f4a9394ae1b662d037a3016e94058a3881bdb8 (diff) | |
| download | archsetup-70e89e946cbdff307284d11a46558161f713607c.tar.gz archsetup-70e89e946cbdff307284d11a46558161f713607c.zip | |
refactor: fold pocketbook in-tree and drop its install steps
Pocketbook is nowhere near ready, so I pulled it back from publication: deleted the github mirror and the cjennings.net repo, removed the server mirror hook, and copied the package into pocketbook/ here until it's ready to spin back out.
Dropped the steps that provisioned it on a fresh install: the gtk4-layer-shell dep and the pip install in archsetup, and the clone in post-install.sh. That clone pointed at the now-deleted github repo, so it would have failed a fresh run regardless. Re-wiring the install is tracked in the pocketbook backlog.
Diffstat (limited to 'pocketbook/tests')
| -rw-r--r-- | pocketbook/tests/__init__.py | 0 | ||||
| -rw-r--r-- | pocketbook/tests/conftest.py | 17 | ||||
| -rw-r--r-- | pocketbook/tests/test_app_toggle.py | 96 | ||||
| -rw-r--r-- | pocketbook/tests/test_note.py | 69 | ||||
| -rw-r--r-- | pocketbook/tests/test_panel.py | 40 | ||||
| -rw-r--r-- | pocketbook/tests/test_store.py | 103 |
6 files changed, 325 insertions, 0 deletions
diff --git a/pocketbook/tests/__init__.py b/pocketbook/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/pocketbook/tests/__init__.py diff --git a/pocketbook/tests/conftest.py b/pocketbook/tests/conftest.py new file mode 100644 index 0000000..db04f88 --- /dev/null +++ b/pocketbook/tests/conftest.py @@ -0,0 +1,17 @@ +import pytest + + +@pytest.fixture +def notes_dir(tmp_path): + """Temporary directory for note storage.""" + d = tmp_path / "quicknotes" + d.mkdir() + return d + + +@pytest.fixture +def sample_note_file(notes_dir): + """A sample note file on disk.""" + path = notes_dir / "0001-20260101-120000-abc12.txt" + path.write_text("Shopping List\n\nMilk\nEggs\nBread\n") + return path diff --git a/pocketbook/tests/test_app_toggle.py b/pocketbook/tests/test_app_toggle.py new file mode 100644 index 0000000..cb5ab89 --- /dev/null +++ b/pocketbook/tests/test_app_toggle.py @@ -0,0 +1,96 @@ +from pocketbook.app import ToggleStateMachine, EscapeStateMachine, navigate + + +class TestToggleStateMachine: + def test_initial_state_visible(self): + sm = ToggleStateMachine(start_hidden=False) + assert sm.visible is True + + def test_initial_state_hidden(self): + sm = ToggleStateMachine(start_hidden=True) + assert sm.visible is False + + def test_toggle_alternates(self): + sm = ToggleStateMachine(start_hidden=False) + assert sm.visible is True + sm.toggle() + assert sm.visible is False + sm.toggle() + assert sm.visible is True + + def test_toggle_from_hidden(self): + sm = ToggleStateMachine(start_hidden=True) + assert sm.visible is False + sm.toggle() + assert sm.visible is True + sm.toggle() + assert sm.visible is False + + +class TestEscapeStateMachine: + def test_escape_while_editing_returns_exit_edit(self): + esc = EscapeStateMachine() + toggle = ToggleStateMachine(start_hidden=False) + action = esc.escape(is_editing=True, toggle=toggle) + assert action == "exit_edit" + # Toggle state should not change + assert toggle.visible is True + + def test_escape_while_browsing_returns_hide(self): + esc = EscapeStateMachine() + toggle = ToggleStateMachine(start_hidden=False) + action = esc.escape(is_editing=False, toggle=toggle) + assert action == "hide" + assert toggle.visible is False + + def test_escape_edit_then_browse_hides(self): + """Simulates: editing → Escape (exit edit) → Escape (hide).""" + esc = EscapeStateMachine() + toggle = ToggleStateMachine(start_hidden=False) + + # First escape: exit edit mode + action1 = esc.escape(is_editing=True, toggle=toggle) + assert action1 == "exit_edit" + assert toggle.visible is True + + # Second escape: now in browse mode, hide panel + action2 = esc.escape(is_editing=False, toggle=toggle) + assert action2 == "hide" + assert toggle.visible is False + + def test_escape_hide_does_not_change_when_already_hidden(self): + esc = EscapeStateMachine() + toggle = ToggleStateMachine(start_hidden=True) + assert toggle.visible is False + action = esc.escape(is_editing=False, toggle=toggle) + assert action == "hide" + # Toggled again — now visible (edge case if called when hidden) + assert toggle.visible is True + + +class TestNavigate: + def test_no_notes(self): + assert navigate(None, 0, 1) is None + assert navigate(None, 0, -1) is None + + def test_no_focus_next_goes_to_first(self): + assert navigate(None, 3, 1) == 0 + + def test_no_focus_prev_goes_to_last(self): + assert navigate(None, 3, -1) == 2 + + def test_next_from_middle(self): + assert navigate(1, 3, 1) == 2 + + def test_prev_from_middle(self): + assert navigate(1, 3, -1) == 0 + + def test_next_clamps_at_end(self): + assert navigate(2, 3, 1) == 2 + + def test_prev_clamps_at_start(self): + assert navigate(0, 3, -1) == 0 + + def test_single_note(self): + assert navigate(0, 1, 1) == 0 + assert navigate(0, 1, -1) == 0 diff --git a/pocketbook/tests/test_note.py b/pocketbook/tests/test_note.py new file mode 100644 index 0000000..539451a --- /dev/null +++ b/pocketbook/tests/test_note.py @@ -0,0 +1,69 @@ +from pocketbook.note import Note + + +class TestNoteSerialisation: + def test_round_trip(self): + note = Note(title="Shopping", body="Milk\nEggs\nBread") + content = note.to_file_content() + restored = Note.from_file_content(content) + assert restored.title == note.title + assert restored.body == note.body + + def test_empty_body(self): + note = Note(title="Empty", body="") + content = note.to_file_content() + restored = Note.from_file_content(content) + assert restored.title == "Empty" + assert restored.body == "" + + def test_empty_title(self): + note = Note(title="", body="some body") + content = note.to_file_content() + restored = Note.from_file_content(content) + assert restored.title == "" + assert restored.body == "some body" + + def test_unicode(self): + note = Note(title="日本語タイトル", body="Ünïcödé bödý 🎉") + content = note.to_file_content() + restored = Note.from_file_content(content) + assert restored.title == "日本語タイトル" + assert restored.body == "Ünïcödé bödý 🎉" + + def test_multiline_body(self): + body = "Line 1\nLine 2\n\nLine 4\n" + note = Note(title="Multi", body=body) + content = note.to_file_content() + restored = Note.from_file_content(content) + assert restored.body == body + + def test_file_content_format(self): + """Title on line 1, blank line, then body.""" + note = Note(title="Title", body="Body text") + content = note.to_file_content() + assert content == "Title\n\nBody text" + + def test_from_file_content_no_blank_line(self): + """Gracefully handle files without a blank separator.""" + restored = Note.from_file_content("JustTitle") + assert restored.title == "JustTitle" + assert restored.body == "" + + +class TestNoteFilename: + def test_generate_filename(self): + note = Note(title="Test", body="") + filename = note.generate_filename(order=1) + assert filename.startswith("0001-") + assert filename.endswith(".txt") + # Format: 0001-YYYYMMDD-HHMMSS-shortid.txt + parts = filename.split("-") + assert len(parts) == 4 + assert len(parts[0]) == 4 # order + assert len(parts[1]) == 8 # date + # parts[2] = HHMMSS + shortid.txt combined via split on - + # Actually: 0001-20260225-143012-abc12.txt has 4 parts + + def test_parse_order_from_filename(self): + assert Note.parse_order("0005-20260101-120000-abc12.txt") == 5 + assert Note.parse_order("0001-20260101-120000-xyz99.txt") == 1 diff --git a/pocketbook/tests/test_panel.py b/pocketbook/tests/test_panel.py new file mode 100644 index 0000000..92f8648 --- /dev/null +++ b/pocketbook/tests/test_panel.py @@ -0,0 +1,40 @@ +from unittest.mock import MagicMock +from pocketbook.note import Note + + +class TestPanelController: + """Test panel controller logic with a mocked store.""" + + def _make_controller(self): + from pocketbook.panel import PanelController + store = MagicMock() + controller = PanelController(store) + return controller, store + + def test_add_note_calls_store_create(self): + controller, store = self._make_controller() + store.create.return_value = "0001-20260101-120000-abc12.txt" + controller.add_note() + store.create.assert_called_once_with("New Note", "") + + def test_delete_note_calls_store_delete(self): + controller, store = self._make_controller() + controller.delete_note("0001-20260101-120000-abc12.txt") + store.delete.assert_called_once_with("0001-20260101-120000-abc12.txt") + + def test_update_note_calls_store_update(self): + controller, store = self._make_controller() + controller.update_note("0001-20260101-120000-abc12.txt", "New Title", "New Body") + store.update.assert_called_once_with( + "0001-20260101-120000-abc12.txt", "New Title", "New Body" + ) + + def test_get_notes_calls_store_list(self): + controller, store = self._make_controller() + store.list_notes.return_value = [ + ("0001-20260101-120000-abc12.txt", Note(title="A", body="a")), + ] + notes = controller.get_notes() + store.list_notes.assert_called_once() + assert len(notes) == 1 + assert notes[0][1].title == "A" diff --git a/pocketbook/tests/test_store.py b/pocketbook/tests/test_store.py new file mode 100644 index 0000000..fab5bd6 --- /dev/null +++ b/pocketbook/tests/test_store.py @@ -0,0 +1,103 @@ +import pytest +from pocketbook.store import NoteStore +from pocketbook.note import Note + + +class TestNoteStoreCreate: + def test_create_note(self, notes_dir): + store = NoteStore(notes_dir) + filename = store.create("My Title", "My Body") + assert (notes_dir / filename).exists() + content = (notes_dir / filename).read_text() + assert content == "My Title\n\nMy Body" + + def test_create_assigns_incrementing_order(self, notes_dir): + store = NoteStore(notes_dir) + f1 = store.create("First", "") + f2 = store.create("Second", "") + assert Note.parse_order(f1) == 1 + assert Note.parse_order(f2) == 2 + + def test_create_auto_creates_directory(self, tmp_path): + d = tmp_path / "nonexistent" / "pocketbook" + store = NoteStore(d) + filename = store.create("Test", "body") + assert d.exists() + assert (d / filename).exists() + + +class TestNoteStoreList: + def test_list_empty(self, notes_dir): + # Remove the sample file if any fixture created one + for f in notes_dir.iterdir(): + f.unlink() + store = NoteStore(notes_dir) + assert store.list_notes() == [] + + def test_list_returns_sorted_by_order(self, notes_dir): + (notes_dir / "0002-20260101-120000-abc12.txt").write_text("B\n\nbody b") + (notes_dir / "0001-20260101-120000-def34.txt").write_text("A\n\nbody a") + (notes_dir / "0003-20260101-120000-ghi56.txt").write_text("C\n\nbody c") + store = NoteStore(notes_dir) + notes = store.list_notes() + assert len(notes) == 3 + assert notes[0][1].title == "A" + assert notes[1][1].title == "B" + assert notes[2][1].title == "C" + + def test_list_skips_non_txt_files(self, notes_dir): + (notes_dir / "0001-20260101-120000-abc12.txt").write_text("Note\n\nbody") + (notes_dir / "README.md").write_text("not a note") + store = NoteStore(notes_dir) + assert len(store.list_notes()) == 1 + + def test_list_skips_corrupted_filenames(self, notes_dir): + (notes_dir / "0001-20260101-120000-abc12.txt").write_text("Good\n\nbody") + (notes_dir / "bad-name.txt").write_text("Bad\n\nbody") + store = NoteStore(notes_dir) + notes = store.list_notes() + assert len(notes) == 1 + assert notes[0][1].title == "Good" + + +class TestNoteStoreUpdate: + def test_update_note(self, notes_dir): + fname = "0001-20260101-120000-abc12.txt" + (notes_dir / fname).write_text("Old Title\n\nOld body") + store = NoteStore(notes_dir) + store.update(fname, "New Title", "New body") + content = (notes_dir / fname).read_text() + assert content == "New Title\n\nNew body" + + def test_update_nonexistent_raises(self, notes_dir): + store = NoteStore(notes_dir) + with pytest.raises(FileNotFoundError): + store.update("0099-20260101-120000-nope0.txt", "T", "B") + + +class TestNoteStoreDelete: + def test_delete_note(self, notes_dir): + fname = "0001-20260101-120000-abc12.txt" + (notes_dir / fname).write_text("Delete me\n\nbody") + store = NoteStore(notes_dir) + store.delete(fname) + assert not (notes_dir / fname).exists() + + def test_delete_nonexistent_raises(self, notes_dir): + store = NoteStore(notes_dir) + with pytest.raises(FileNotFoundError): + store.delete("0099-20260101-120000-nope0.txt") + + +class TestNoteStoreReorder: + def test_reorder_renumbers_files(self, notes_dir): + # Create files with gaps in ordering + (notes_dir / "0001-20260101-120000-aaa11.txt").write_text("A\n\na") + (notes_dir / "0005-20260101-120000-bbb22.txt").write_text("B\n\nb") + (notes_dir / "0010-20260101-120000-ccc33.txt").write_text("C\n\nc") + store = NoteStore(notes_dir) + store.reorder() + notes = store.list_notes() + assert Note.parse_order(notes[0][0]) == 1 + assert Note.parse_order(notes[1][0]) == 2 + assert Note.parse_order(notes[2][0]) == 3 |
