diff --git a/android/app/src/main/java/com/fabledsword/inkwell/MainActivity.kt b/android/app/src/main/java/com/fabledsword/inkwell/MainActivity.kt index 20a7fa4..c687de1 100644 --- a/android/app/src/main/java/com/fabledsword/inkwell/MainActivity.kt +++ b/android/app/src/main/java/com/fabledsword/inkwell/MainActivity.kt @@ -219,15 +219,16 @@ private fun App( ) { val context = LocalContext.current val board: BoardViewModel = - viewModel( - factory = - BoardViewModel.factory(core, readFile = files::read) { - // Any store write can have moved the next reminder. Called on - // the IO dispatcher by the view model, which is where it has to - // be — this reads every note carrying a reminder. - Reminders.refresh(context, core) - }, - ) + viewModel { + BoardViewModel( + core, + // Any store write can have moved the next reminder. Called on the IO + // dispatcher by the view model, which is where it has to be — this + // reads every note carrying a reminder. + onRemindersChanged = { Reminders.refresh(context, core) }, + readFile = files::read, + ) + } // Consumed, not just read: without clearing it, every later recomposition // would reopen the same note and make the editor impossible to leave. @@ -251,7 +252,7 @@ private fun App( // A pull can rewrite every note the board is holding, so a sync that changed // anything tells it to reload. Wired here, at the one place that owns both. val sync: SyncViewModel = - viewModel(factory = SyncViewModel.factory(core, onStoreChanged = board::refresh)) + viewModel { SyncViewModel(core, onStoreChanged = board::refresh) } // Screen visibility is view STATE, not view-model state: it is about what is on // the display, and nothing in the store cares. Saveable so a rotation does not @@ -266,14 +267,14 @@ private fun App( // board may be LOOKING at one that a delete or a merge just removed. Both are // `refreshLabels`, which also leaves a lens whose tag stopped existing. val tags: TagsViewModel = - viewModel(factory = TagsViewModel.factory(core, onStoreChanged = board::refreshLabels)) + viewModel { TagsViewModel(core, onStoreChanged = board::refreshLabels) } // A share landing changes the note's `shared` flag, which the board's card chip // reads, so the board reloads after one. val share: ShareViewModel = - viewModel(factory = ShareViewModel.factory(core, onStoreChanged = board::refresh)) + viewModel { ShareViewModel(core, onStoreChanged = board::refresh) } - val update: UpdateViewModel = viewModel(factory = UpdateViewModel.factory(core, context)) + val update: UpdateViewModel = viewModel { UpdateViewModel(core, context.applicationContext) } val settings = remember(context) { SyncSettings(context) } var automatic by remember { mutableStateOf(settings.automatic) } diff --git a/android/app/src/main/java/com/fabledsword/inkwell/ui/BoardViewModel.kt b/android/app/src/main/java/com/fabledsword/inkwell/ui/BoardViewModel.kt index dd29c98..cd52c9a 100644 --- a/android/app/src/main/java/com/fabledsword/inkwell/ui/BoardViewModel.kt +++ b/android/app/src/main/java/com/fabledsword/inkwell/ui/BoardViewModel.kt @@ -5,10 +5,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope -import androidx.lifecycle.viewmodel.initializer -import androidx.lifecycle.viewmodel.viewModelFactory import com.fabledsword.inkwell.core.Inkwell import com.fabledsword.inkwell.core.Label import com.fabledsword.inkwell.core.Note @@ -660,13 +657,6 @@ class BoardViewModel( /** The board and the Archive together — everything not in Trash. */ private const val VIEW_ALL = "all" - - fun factory( - core: Inkwell, - readFile: (Uri) -> PickedFile, - onRemindersChanged: () -> Unit, - ): ViewModelProvider.Factory = - viewModelFactory { initializer { BoardViewModel(core, onRemindersChanged, readFile) } } } } diff --git a/android/app/src/main/java/com/fabledsword/inkwell/ui/ShareViewModel.kt b/android/app/src/main/java/com/fabledsword/inkwell/ui/ShareViewModel.kt index cc7fcac..b0276cc 100644 --- a/android/app/src/main/java/com/fabledsword/inkwell/ui/ShareViewModel.kt +++ b/android/app/src/main/java/com/fabledsword/inkwell/ui/ShareViewModel.kt @@ -4,10 +4,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope -import androidx.lifecycle.viewmodel.initializer -import androidx.lifecycle.viewmodel.viewModelFactory import com.fabledsword.inkwell.core.CoreException import com.fabledsword.inkwell.core.Inkwell import com.fabledsword.inkwell.core.Member @@ -132,11 +129,4 @@ class ShareViewModel( } } } - - companion object { - fun factory( - core: Inkwell, - onStoreChanged: () -> Unit, - ): ViewModelProvider.Factory = viewModelFactory { initializer { ShareViewModel(core, onStoreChanged) } } - } } diff --git a/android/app/src/main/java/com/fabledsword/inkwell/ui/SyncViewModel.kt b/android/app/src/main/java/com/fabledsword/inkwell/ui/SyncViewModel.kt index 6ea9421..8d918b1 100644 --- a/android/app/src/main/java/com/fabledsword/inkwell/ui/SyncViewModel.kt +++ b/android/app/src/main/java/com/fabledsword/inkwell/ui/SyncViewModel.kt @@ -4,10 +4,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope -import androidx.lifecycle.viewmodel.initializer -import androidx.lifecycle.viewmodel.viewModelFactory import com.fabledsword.inkwell.core.Compatibility import com.fabledsword.inkwell.core.Inkwell import com.fabledsword.inkwell.core.ProbeResult @@ -315,13 +312,6 @@ class SyncViewModel( fun dismissSyncError() { state = state.copy(syncError = null) } - - companion object { - fun factory( - core: Inkwell, - onStoreChanged: () -> Unit, - ): ViewModelProvider.Factory = viewModelFactory { initializer { SyncViewModel(core, onStoreChanged) } } - } } /** diff --git a/android/app/src/main/java/com/fabledsword/inkwell/ui/TagsViewModel.kt b/android/app/src/main/java/com/fabledsword/inkwell/ui/TagsViewModel.kt index 70e04d4..a850424 100644 --- a/android/app/src/main/java/com/fabledsword/inkwell/ui/TagsViewModel.kt +++ b/android/app/src/main/java/com/fabledsword/inkwell/ui/TagsViewModel.kt @@ -4,10 +4,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope -import androidx.lifecycle.viewmodel.initializer -import androidx.lifecycle.viewmodel.viewModelFactory import com.fabledsword.inkwell.core.Inkwell import com.fabledsword.inkwell.core.Label import kotlinx.coroutines.Dispatchers @@ -163,11 +160,4 @@ class TagsViewModel( onStoreChanged() } } - - companion object { - fun factory( - core: Inkwell, - onStoreChanged: () -> Unit, - ): ViewModelProvider.Factory = viewModelFactory { initializer { TagsViewModel(core, onStoreChanged) } } - } } diff --git a/android/app/src/main/java/com/fabledsword/inkwell/ui/UpdateViewModel.kt b/android/app/src/main/java/com/fabledsword/inkwell/ui/UpdateViewModel.kt index f2aa08f..f6fd6b7 100644 --- a/android/app/src/main/java/com/fabledsword/inkwell/ui/UpdateViewModel.kt +++ b/android/app/src/main/java/com/fabledsword/inkwell/ui/UpdateViewModel.kt @@ -5,10 +5,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope -import androidx.lifecycle.viewmodel.initializer -import androidx.lifecycle.viewmodel.viewModelFactory import com.fabledsword.inkwell.AppUpdate import com.fabledsword.inkwell.UpdateOutcome import com.fabledsword.inkwell.core.ClientUpdate @@ -214,14 +211,6 @@ class UpdateViewModel( fun dismissError() { state = state.copy(error = null) } - - companion object { - fun factory( - core: Inkwell, - context: Context, - ): ViewModelProvider.Factory = - viewModelFactory { initializer { UpdateViewModel(core, context.applicationContext) } } - } } private const val FALLBACK = "The update couldn't be checked." diff --git a/tests/conftest.py b/tests/conftest.py index 79144c4..1454b0b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,6 @@ import pytest +from inkwell.app import create_app from inkwell.config import Config @@ -10,3 +11,9 @@ def _isolated_data_dir(tmp_path, monkeypatch): signing key from env-or-random, not a file), but this stays defensive.""" monkeypatch.setattr(Config, "DATA_DIR", str(tmp_path / "data")) yield + + +@pytest.fixture +def app(): + """A fresh app for a test that drives routes through `app.test_client()`.""" + return create_app() diff --git a/tests/test_admin.py b/tests/test_admin.py index 3c53f77..e6de841 100644 --- a/tests/test_admin.py +++ b/tests/test_admin.py @@ -1,13 +1,3 @@ -import pytest - -from inkwell.app import create_app - - -@pytest.fixture -def app(): - return create_app() - - async def test_settings_requires_auth(app): # require_admin returns 401 before any DB access when unauthenticated. client = app.test_client() diff --git a/tests/test_app.py b/tests/test_app.py index bf30e95..175077a 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -1,13 +1,6 @@ -import pytest - from inkwell.app import create_app -@pytest.fixture -def app(): - return create_app() - - async def test_health_ok(app): client = app.test_client() resp = await client.get("/api/health") diff --git a/tests/test_client_dist.py b/tests/test_client_dist.py index 90a67ef..a76adf2 100644 --- a/tests/test_client_dist.py +++ b/tests/test_client_dist.py @@ -4,7 +4,6 @@ from pathlib import Path import pytest from inkwell import client_dist -from inkwell.app import create_app from inkwell.client_dist import ( BY_ID, PLATFORMS, @@ -54,11 +53,6 @@ def _empty_baked_client(tmp_path, monkeypatch): yield -@pytest.fixture -def app(): - return create_app() - - def place( platform_id: str = "android", payload: bytes = PAYLOAD, diff --git a/tests/test_devices.py b/tests/test_devices.py index 5b5062a..eadc489 100644 --- a/tests/test_devices.py +++ b/tests/test_devices.py @@ -1,13 +1,3 @@ -import pytest - -from inkwell.app import create_app - - -@pytest.fixture -def app(): - return create_app() - - async def test_create_device_requires_auth(app): # No session cookie and no bearer header → 401 before any DB access. client = app.test_client() diff --git a/tests/test_installer.py b/tests/test_installer.py index f7abf06..796db2f 100644 --- a/tests/test_installer.py +++ b/tests/test_installer.py @@ -1,7 +1,6 @@ import pytest from inkwell import installer, settings -from inkwell.app import create_app from inkwell.installer import PLACEHOLDER, SOURCE_SCRIPT, render, server_address # DB-free, like test_client_dist: the route reads `public_url` from the live cache @@ -17,11 +16,6 @@ def _defaults(): settings.reset_live() -@pytest.fixture -def app(): - return create_app() - - def test_the_published_script_carries_the_line_the_server_rewrites_exactly_once(): """The forge-served script and the server-served one are the same file; this is the one line that differs. Edit it and the server stops serving rather than diff --git a/tests/test_labels.py b/tests/test_labels.py index 10e0085..cb3d7bb 100644 --- a/tests/test_labels.py +++ b/tests/test_labels.py @@ -1,17 +1,9 @@ import uuid -import pytest - -from inkwell.app import create_app from inkwell.models.label import Label from inkwell.serialize import serialize_label -@pytest.fixture -def app(): - return create_app() - - def test_serialize_label_shape(): # The base {id, name, color} shape the labels API and (S4) sync deltas both build on. lid = uuid.uuid4() diff --git a/tests/test_notes.py b/tests/test_notes.py index d362e5a..357efe9 100644 --- a/tests/test_notes.py +++ b/tests/test_notes.py @@ -5,7 +5,6 @@ from datetime import datetime, timezone import pytest -from inkwell.app import create_app from inkwell.common import coerce_bool, parse_dt from inkwell.colors import NOTE_COLORS from inkwell.models.note import Note @@ -34,11 +33,6 @@ from inkwell.notes.recurrence import next_occurrence, normalize_recurrence from inkwell.notes.tags import parse_tags, split_body_tags -@pytest.fixture -def app(): - return create_app() - - def test_all_note_routes_registered(app): # Guards the notes package split: every route handler must still be attached to the # blueprint. A route whose module isn't imported by notes/__init__ would silently diff --git a/tests/test_ratelimit.py b/tests/test_ratelimit.py index 6cf79e7..9849e2f 100644 --- a/tests/test_ratelimit.py +++ b/tests/test_ratelimit.py @@ -10,7 +10,6 @@ import pytest from inkwell import ratelimit from inkwell.settings import live -from inkwell.app import create_app from inkwell.ratelimit import SlidingWindow @@ -28,11 +27,6 @@ def _clean_counters(): ratelimit.reset_all() -@pytest.fixture -def app(): - return create_app() - - def test_under_the_limit_is_not_blocked(): w = window(3, 60) for i in range(3): diff --git a/tests/test_security_headers.py b/tests/test_security_headers.py index 938ebcb..a44501e 100644 --- a/tests/test_security_headers.py +++ b/tests/test_security_headers.py @@ -4,14 +4,6 @@ Asserted on /api/health because it is the one route that needs no database and n session — the headers are set in an after_request hook, so any response proves the hook, and this suite has no Postgres. """ -import pytest - -from inkwell.app import create_app - - -@pytest.fixture -def app(): - return create_app() async def test_content_security_policy_locks_scripts_to_self(app): diff --git a/tests/test_sync.py b/tests/test_sync.py index 0569c4b..be4d55c 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -1,8 +1,5 @@ from datetime import datetime, timezone -import pytest - -from inkwell.app import create_app from inkwell.sync import ( DEFAULT_LIMIT, MAX_LIMIT, @@ -17,11 +14,6 @@ from inkwell.sync import ( ) -@pytest.fixture -def app(): - return create_app() - - async def test_changes_requires_auth(app): client = app.test_client() resp = await client.get("/api/sync/changes") diff --git a/tests/test_unfurl.py b/tests/test_unfurl.py index 0b20be2..c9e4555 100644 --- a/tests/test_unfurl.py +++ b/tests/test_unfurl.py @@ -2,15 +2,9 @@ import ipaddress import pytest -from inkwell.app import create_app from inkwell.unfurl import UnfurlError, extract_preview, is_public_ip, validate_url -@pytest.fixture -def app(): - return create_app() - - def test_is_public_ip_blocks_internal_ranges(): assert is_public_ip(ipaddress.ip_address("8.8.8.8")) assert is_public_ip(ipaddress.ip_address("2606:4700:4700::1111"))