Skip to content
Development

Seven small Deep Cut Atlas fixes, and a test I almost deleted

By Victor Da Luz
swiftiostestingdev-logdeep-cut-atlas

I spent a session working through the Deep Cut Atlas backlog in order of size, smallest and safest first, merging each one to main before starting the next. Seven issues landed: a wrong icon on an empty state, some leftover chevrons that didn’t go anywhere, artwork that didn’t respect Dynamic Type, a Settings section in the wrong order, a filter chip that didn’t look tappable, three small copy inconsistencies, and one real bug in how a detail sheet checked whether you already owned an album. Most of it was mechanical. Two of them taught me something worth writing down.

Fixed-size artwork doesn’t scale with text

Eight views in this app show album artwork next to a title and artist name. All eight used a plain .frame(width: 56, height: 56) (or 48, or 72, depending on the view). That’s fine at the default text size. Turn on a larger Dynamic Type setting in Accessibility and the text grows, but the artwork stays exactly the same size, so a wall of text ends up next to a postage stamp.

The fix is a one-line change per view: swap the frame’s literal for a @ScaledMetric property seeded with that same literal.

@ScaledMetric private var artworkSize: CGFloat = 56
...
.frame(width: artworkSize, height: artworkSize)

@ScaledMetric reads the current Dynamic Type category and scales the value automatically, so at the default size it renders exactly as before (no regression risk) and at larger sizes it grows the artwork proportionally with the text next to it. I verified this with xcrun simctl ui <udid> content_size accessibility-extra-extra-extra-large, which flips the simulator to the largest accessibility text size without touching the Settings app UI, then took a screenshot. Artwork visibly scaled up alongside the title and artist text in every one of the eight views.

The test that was quietly protecting the wrong thing

This one took the rest of the session. A track’s detail sheet shows “already in your library” when you’ve already added that album, and disables the add button. The check that decided this, isAlbumInLibrary, was a stored let passed into the view model’s initializer, frozen at the moment the sheet was built. If the app’s one-time whole-library scan was still running when you opened the sheet, the sheet permanently believed you didn’t own the album, even after the scan finished and said otherwise. Same bug shape as an earlier one I’d fixed in the suggestions list, just in a different property.

The fix follows the same pattern: turn the stored value into a computed property that reads the parent view model live.

var isAlbumInLibrary: Bool {
    history.isAlbumInLibrary(track)
}

No init parameter, no snapshot, no way for it to go stale. Since it’s computed and reads an @Observable property, SwiftUI re-renders the sheet automatically whenever the parent’s answer changes.

Making that change broke an existing test. That test constructed the view model directly and passed isAlbumInLibrary: false by hand to fake a specific scenario, one where the album turns out to be owned partway through an in-progress add. Once the property stopped being a constructor argument, that shortcut had nowhere to go, and the obvious move was to just delete the assertion, since the code it was testing didn’t exist in that shape anymore.

I read the test’s own comment before doing that, and it was describing a real race that still existed after my fix, just narrower: the library scan finishes in the exact window between the add button deciding it’s safe to tap and the add operation’s own re-check of the same cached data a few lines later, deeper in an async call chain. The fix changed how you could reach that window. It didn’t close it.

So instead of deleting the test, I rebuilt it. It needed two chokepoints held open at once instead of one: the library scan (already mockable) and the add flow’s own playlist lookup, which had no hook to pause it, so I added one. With both gated, the test parks the scan mid-flight, opens the sheet and confirms the button reads as addable, starts the add, parks it at its own await, releases the scan so the real answer becomes available, then releases the add and checks it reports “already in your library,” not “added.” Deterministic, not a sleep-and-hope test.

The lesson I took from this, and wrote into a knowledge-base pattern note: when a fix breaks a test, read what that test’s comment says it’s protecting before you touch it. If the scenario it names is still reachable, the fix changed the path to get there, not whether the bug is still possible. Rebuild the repro against the new mechanism. Don’t quietly lower the coverage because the old way of triggering it is gone.

The rest, briefly

The “Not Interested” empty state used a thumbs-up icon; thumbs-down now. Dropped a chevron from two list rows that didn’t navigate anywhere when tapped. Moved the filter-defaults section in Settings below the sections people actually change day to day. Filter chips looked the same whether they were on or off - added a checkmark for the active state, and gave the “Compilation” filter its full name instead of showing the same “Comp” abbreviation used for the badge. And three small copy fixes: an empty playlist message that now says something specific instead of a generic “nothing here,” and two “Add to Playlist” buttons renamed to match the feature’s actual name, “To Check Out.”

Related reading