diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index a9a7e84..d0e8f52 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -12,7 +12,7 @@ "name": "pdat-swift-practice", "source": "./pdat-swift-practice", "description": "Curated Apple platform findings with sources, plus practices learned from production incidents.", - "version": "0.3.0", + "version": "0.4.0", "author": { "name": "PDAT s.r.o." }, diff --git a/pdat-swift-practice/.claude-plugin/plugin.json b/pdat-swift-practice/.claude-plugin/plugin.json index 7b2d5ac..5ec5694 100644 --- a/pdat-swift-practice/.claude-plugin/plugin.json +++ b/pdat-swift-practice/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "pdat-swift-practice", - "version": "0.3.0", + "version": "0.4.0", "description": "Curated Apple platform findings with sources, plus practices learned from production incidents.", "author": { "name": "PDAT s.r.o.", diff --git a/pdat-swift-practice/skills/xcode-practice/SKILL.md b/pdat-swift-practice/skills/xcode-practice/SKILL.md index fdedc3f..7fb3c2d 100644 --- a/pdat-swift-practice/skills/xcode-practice/SKILL.md +++ b/pdat-swift-practice/skills/xcode-practice/SKILL.md @@ -152,10 +152,55 @@ written down afterwards: a note recording a false cause is worse than no note, because the next person believes it. Corollary for UI tests: when one goes red without a code change, suspect -what the test depends on but does not control — accumulated seed data, -`@AppStorage` surviving between launches, a simulator location and the -"nearby" sort order it drives, or an element scrolled out of a lazy -container and therefore missing from the accessibility tree entirely. +what the test depends on but does not control. In our case it was exactly +that, and the two causes are worth naming. + +## UI tests inherit whatever the last test left behind + +`@AppStorage` is UserDefaults, and the simulator keeps those between +tests, between runs, and between days. A test that switched the card list +to its List layout left it that way; a different test, written for the +grid, then failed — days later, with nobody having touched the code. + +Reset the state at launch. Do it with a **debug hook that writes to the +normal domain**, not with launch arguments: + +```swift +// in the app, under #if DEBUG +if ProcessInfo.processInfo.arguments.contains("-resetUserState") { + UserDefaults.standard.removeObject(forKey: "cardTypeFilter") + UserDefaults.standard.set("cards", forKey: "cardLayoutStyle") +} +``` + +Launch arguments look like the obvious answer and are a trap: they form +`NSArgumentDomain`, which outranks anything the app writes at runtime. The +setting can then be forced but never changed, so every test that toggles +that setting fails. Trying it turned one failing test into five. + +While resetting, prefer values that don't depend on the environment. A +"sort by distance" default makes card order a function of the simulator's +location. + +## An element scrolled out of a lazy container does not exist + +`List`, `LazyVStack` and `LazyVGrid` only build what is visible, so +`element.exists` is `false` for a row further down — indistinguishable +from a genuine bug. Anything below the fold needs a helper that swipes +until the element appears: + +```swift +func scrollToStaticText(_ label: String, maxSwipes: Int = 8) -> XCUIElement { + let element = staticTexts[label] + if element.waitForExistence(timeout: 5) { return element } + for _ in 0..