ML-182: convert 4 test files to PhoenixTest only

Waves 1-2: scrobble_live/index, scrobbled_tracks_live/index, release_group_show, release_show. Dropped unwrap/2 bridges and page_title/1 usage. Search form tests use visit with query params. page_title tests use assert_has("title", ...).
This commit is contained in:
Claudio Ortolina
2026-05-14 23:20:52 +01:00
parent 8843f0ad4f
commit dda700ad96
7 changed files with 89 additions and 166 deletions
@@ -1,10 +1,10 @@
---
id: ML-182
title: Analyze and plan elimination of LiveViewTest usage in favor of PhoenixTest
status: To Do
status: In Progress
assignee: []
created_date: "2026-05-14 21:40"
updated_date: "2026-05-14 21:41"
updated_date: "2026-05-14 22:15"
labels:
- testing
- refactoring
@@ -43,9 +43,9 @@ ordinal: 10000
<!-- 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.
Audit all LiveView and component tests that mix LiveViewTest and PhoenixTest, and eliminate LiveViewTest usage where possible. The project's ConnCase auto-imports both frameworks, creating unnecessary dual-framework complexity.
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.
The work is broken into 6 waves by difficulty, each tracked as a subtask.
<!-- SECTION:DESCRIPTION:END -->
@@ -53,100 +53,25 @@ An initial analysis of 17 files has been completed. The task is to refine this a
<!-- 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
- [x] #1 All 17 files are classified as fully eliminable, partially eliminable, or blocked with clear rationale for each
- [x] #2 Three blocking patterns (send_update/3, send(view.pid), live_isolated) are documented with suggested workarounds
- [x] #3 A concrete migration order is proposed, prioritizing low-risk fully-eliminable files first
- [x] #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)
## Key challenges discovered during execution
### Context
1. **PhoenixTest can't click `<span>` elements** (Fluxon `.badge`) — only `<a>` (`click_link`) and `<button>` (`click_button`). HTML changes needed for badge-based interactions.
`ConnCase` auto-imports both frameworks:
2. **Fluxon input labels** using `data-part="field"` wrapper without separate visible text labels don't match `fill_in`. Workaround: visit with query params to trigger `handle_params` search.
- `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`
3. **LiveComponent modals with `phx-target`** don't respond to URL query params — need actual form interaction, which is harder with PhoenixTest when labels are missing.
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 |
4. **`trigger_hook` in PhoenixTest** expects JSON-encoded values, not Elixir maps. Different API from LiveViewTest's `render_hook`.
5. **`render_async/1` is auto-imported by ConnCase** — can use `unwrap(&render_async/1)` without any explicit import. This is the only LiveViewTest function that remains needed for async data loading.
<!-- SECTION:NOTES:END -->
@@ -0,0 +1,29 @@
---
id: ML-182.1
title: >-
Wave 1: Drop unwrap/2 bridges in scrobble_live and scrobbled_tracks_live index
tests
status: Done
assignee: []
created_date: "2026-05-14 22:13"
labels:
- testing
- refactoring
dependencies: []
modified_files:
- test/music_library_web/live/scrobble_live/index_test.exs
- test/music_library_web/live/scrobbled_tracks_live/index_test.exs
parent_task_id: ML-182
priority: medium
ordinal: 11000
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Remove `import Phoenix.LiveViewTest` and eliminate `unwrap/2` bridge patterns in these two files. Both files used `unwrap(fn view -> form(...) |> render_submit(); render(view) end)` to drive search forms. Conversion strategy: visit with query params directly, which triggers `handle_params` and the search — same effect as form submission and simpler.
Also clean up unused module attributes and convert validation-error tests to `fill_in` + `click_button` + `assert_has`.
<!-- SECTION:DESCRIPTION:END -->
@@ -0,0 +1,25 @@
---
id: ML-182.2
title: "Wave 2: Convert page_title/1 tests in release_group_show and release_show"
status: Done
assignee: []
created_date: "2026-05-14 22:14"
labels:
- testing
- refactoring
dependencies: []
modified_files:
- test/music_library_web/live/scrobble_live/release_group_show_test.exs
- test/music_library_web/live/scrobble_live/release_show_test.exs
parent_task_id: ML-182
priority: medium
ordinal: 12000
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Replace `Phoenix.LiveViewTest.live/2` + `render_async/1` + `page_title/1` with `visit/2` + `assert_has("title", text: "...")`. Keep `unwrap(&render_async/1)` where needed for async data loading (auto-imported by ConnCase).
<!-- SECTION:DESCRIPTION:END -->