ML-182: migration to phoenix_test (plan)

This commit is contained in:
Claudio Ortolina
2026-05-14 22:42:42 +01:00
parent 7cf634a421
commit 8843f0ad4f
@@ -0,0 +1,152 @@
---
id: ML-182
title: Analyze and plan elimination of LiveViewTest usage in favor of PhoenixTest
status: To Do
assignee: []
created_date: "2026-05-14 21:40"
updated_date: "2026-05-14 21:41"
labels:
- testing
- refactoring
- analysis
dependencies: []
references:
- >-
test/support/conn_case.ex (shows auto-imported LiveViewTest subset and
PhoenixTest)
documentation:
- "https://hexdocs.pm/phoenix_test/PhoenixTest.html"
- "https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html"
modified_files:
- test/music_library_web/components/chat_test.exs
- test/music_library_web/components/release_test.exs
- test/music_library_web/live/artist_live/show_test.exs
- test/music_library_web/live/collection_live/index_test.exs
- test/music_library_web/live/maintenance_live/index_test.exs
- test/music_library_web/live/online_store_template_live/index_test.exs
- test/music_library_web/live/record_set_live/index_test.exs
- test/music_library_web/live/record_set_live/show_test.exs
- test/music_library_web/live/scrobble_live/index_test.exs
- test/music_library_web/live/scrobble_live/release_group_show_test.exs
- test/music_library_web/live/scrobble_live/release_show_test.exs
- test/music_library_web/live/scrobble_rules_live/index_test.exs
- test/music_library_web/live/scrobbled_tracks_live/index_test.exs
- test/music_library_web/live/scrobbled_tracks_live/rule_picker_test.exs
- test/music_library_web/live/stats_live/top_albums_test.exs
- test/music_library_web/live/wishlist_live/index_test.exs
- test/music_library_web/live_helpers/record_actions_test.exs
priority: medium
ordinal: 10000
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Audit all LiveView and component tests that use LiveViewTest (either exclusively or mixed with PhoenixTest) and determine which can be converted to use only PhoenixTest. The project's ConnCase auto-imports both PhoenixTest (full) and Phoenix.LiveViewTest (subset via `only:`), creating a dual-framework testing situation. The goal is to reduce complexity by eliminating LiveViewTest usage where possible.
An initial analysis of 17 files has been completed. The task is to refine this analysis and produce a concrete migration plan with cost estimates.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 All 17 files are classified as fully eliminable, partially eliminable, or blocked with clear rationale for each
- [ ] #2 Three blocking patterns (send_update/3, send(view.pid), live_isolated) are documented with suggested workarounds
- [ ] #3 A concrete migration order is proposed, prioritizing low-risk fully-eliminable files first
- [ ] #4 Cost estimates (effort level: low/medium/high) are assigned to each file or group of files
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
## Initial Analysis (completed 2026-05-14)
### Context
`ConnCase` auto-imports both frameworks:
- `PhoenixTest` (full) — provides `visit/2`, `click/2`, `fill_in/2`, `assert_has/3`, `refute_has/3`, `assert_path/2`, `trigger_hook/3`, `upload/2`, `unwrap/2`, etc.
- `Phoenix.LiveViewTest` (subset via `only:`) — provides `render_async/1`, `render_change/2`, `render_click/3`, `render_hook/3`, `element/2`, `form/3`
Files that need `live/2` (not auto-imported) add `import Phoenix.LiveViewTest` explicitly.
### Per-file classification
#### 🟢 Fully eliminable (11 files)
Pattern: `live/2``visit/2`, `render_click/3``click/2`, `render_submit/1``click_button/2`, `render_change/2``fill_in/2`, `render/1``assert_has/3`, `has_element?/2``assert_has/3`/`refute_has/3`, `assert_patch/2``assert_path/2`, `page_title/1``assert_has("title", text: "...")`
1. `live/artist_live/show_test.exs``live/2` for image edit modal; `file_input/3`+`render_upload/3` → PhoenixTest `upload/2`
2. `live/maintenance_live/index_test.exs` — All 11 tests use `live/2`+`render_click/3`. External redirect test needs `assert_redirect/2`.
3. `live/online_store_template_live/index_test.exs` — Mixed `visit/2` and `live/2`. `assert_patch/2``assert_path/2`.
4. `live/record_set_live/index_test.exs` — Mixed `visit/2` and `live/2`.
5. `live/record_set_live/show_test.exs` — Mixed. `render_hook/3` has PhoenixTest `trigger_hook/3` equivalent. `assert_redirect/2``assert_path/2`.
6. `live/scrobble_live/index_test.exs` — Uses `unwrap/2` bridge pattern, no `live/2`. Drop `unwrap`, use direct PhoenixTest.
7. `live/scrobble_live/release_group_show_test.exs` — One `live/2` for `page_title/1`. Replace with `assert_has("title", ...)`.
8. `live/scrobble_live/release_show_test.exs` — Same page_title pattern.
9. `live/scrobble_rules_live/index_test.exs` — All `live/2`. Standard conversion.
10. `live/scrobbled_tracks_live/index_test.exs``unwrap/2` bridge, no `live/2`. Drop `unwrap`.
11. `live/scrobbled_tracks_live/rule_picker_test.exs` — All `live/2`. Standard conversion.
12. `live/stats_live/top_albums_test.exs` — Mixed. `live/2` for cover URL/badge tests.
#### 🟡 Mostly eliminable (4 files)
Blocked by `send(view.pid, message)` pattern for testing internal `handle_info/2` callbacks.
13. `live/collection_live/index_test.exs` — 2 PubSub tests use `send(view.pid, :records_index_changed)`. 3 cart format tests use `live/2` for `render_change/2` with nested params.
14. `live/wishlist_live/index_test.exs` — Same PubSub pattern. Single-item import uses `live/2`.
15. `live_helpers/record_actions_test.exs` — One test uses `send(view.pid, {Chat, :chats_changed})`. Rest are standard `live/2`+`render_click/3`.
16. `components/release_test.exs``render_change/2` with nested LiveComponent form data. `ShowPrintTest` sub-module uses `live_isolated/3`.
#### 🔴 Hard to eliminate (1 file)
17. `components/chat_test.exs``Phoenix.LiveView.send_update(view.pid, Chat, [chunk: ..., done: ..., error: ...])` drives the Chat component's internal `update/2` callback directly. This is unit-level LiveComponent testing with no PhoenixTest equivalent. 3 tests in "update/2 streaming state transitions" describe block.
### Three blocking patterns
**Pattern 1: `Phoenix.LiveView.send_update/3`** (chat_test.exs)
- Used to test component's `update/2` callback with internal state tuples (`chunk:`, `done:`, `error:`)
- No PhoenixTest equivalent — PhoenixTest has no concept of LiveComponent PIDs
- Workaround: Rewrite as integration tests through the actual SSE streaming pipeline
- Effort: High (rewrite 3 tests with streaming stub setup)
**Pattern 2: `send(view.pid, message)`** (collection_live, wishlist_live, record_actions)
- Used to test `handle_info/2` callbacks for PubSub-driven updates
- PhoenixTest sessions don't expose LiveView PIDs
- Workaround: Trigger the actual side effects (create records, broadcast via PubSub, interact with Chat component) and use `assert_has/3` with timeout
- Effort: Medium per affected test (need to verify PhoenixTest sessions pick up out-of-band PubSub updates)
**Pattern 3: `live_isolated/3`** (components/release_test.exs ShowPrintTest)
- Mounts a LiveComponent in isolation to test a specific assign
- PhoenixTest requires full page navigation
- Workaround: Test through parent LiveView (`/collection/:id`) with `visit/2`
- Effort: Low (2 tests, simple assertion)
### Proposed migration order
1. **Wave 1 (low risk):** Drop `unwrap/2` bridges — `scrobble_live/index_test.exs`, `scrobbled_tracks_live/index_test.exs`
2. **Wave 2 (low risk):** Convert `page_title/1` tests — `scrobble_live/release_group_show_test.exs`, `scrobble_live/release_show_test.exs`
3. **Wave 3 (medium risk):** Convert `live/2``visit/2` for standard CRUD LiveViews — `scrobble_rules_live`, `online_store_template_live`, `record_set_live/index`, `record_set_live/show`, `scrobbled_tracks_live/rule_picker`, `maintenance_live`
4. **Wave 4 (medium risk):** Convert mixed-usage files — `artist_live/show`, `stats_live/top_albums`
5. **Wave 5 (higher risk):** Files with `send(view.pid)` blockers — `collection_live/index`, `wishlist_live/index`, `record_actions`, `components/release`
6. **Wave 6 (hardest):** Component streaming tests — `components/chat`
### Cost estimates
| Wave | Effort | Files |
| ---- | ------------------ | ------------------------------------------------------------------------- |
| 1 | Low (30 min) | 2 files — mechanical removal of `unwrap/2` |
| 2 | Low (15 min) | 2 files — replace `page_title/1` |
| 3 | Medium (2-3h) | 6 files — `live/2``visit/2`, selector adjustments |
| 4 | Medium (1-2h) | 2 files — `file_input/3`+`render_upload/3``upload/2`, `live/2``visit/2` |
| 5 | Medium-High (3-4h) | 4 files — PubSub workarounds, `live_isolated`→parent page |
| 6 | High (4-6h) | 1 file — rewrite streaming tests as integration tests |
<!-- SECTION:NOTES:END -->