Skip to content

test: add 50 preferences persistence and migration tests#540

Merged
malpern merged 1 commit into
masterfrom
test/532-preferences-persistence
May 22, 2026
Merged

test: add 50 preferences persistence and migration tests#540
malpern merged 1 commit into
masterfrom
test/532-preferences-persistence

Conversation

@malpern
Copy link
Copy Markdown
Owner

@malpern malpern commented May 22, 2026

Summary

Closes #532

  • Value clamping (12 tests): HUD timeout [1.0-10.0s], hold delay [100-2000ms], TCP port [1024-65535] including boundaries, zero, and negative values
  • LeaderKeyPreference Codable (7 tests): round-trip for navigation/base/custom layers, default values, invalid JSON fallback, equatable
  • Overlay suppressed bundle IDs (3 tests): default Figma IDs, Set insert/remove operations, deduplication
  • Neovim reference topics (2 tests): defaults non-empty, all topics are valid categories
  • Enum raw value round-trips (5 tests): ContextHUDDisplayMode, TriggerMode, HoldDelayPreset, KindaVimLeaderHUDMode, CommunicationProtocol
  • Invalid raw values (5 tests): all preference enums return nil for unknown values
  • Hold delay computed property (2 tests): custom value used, preset overrides custom
  • Default values (3 tests): reasonable defaults, timeout in range, hold delay in range
  • RuleCollectionLayer Codable (3 tests): base/navigation/custom round-trips
  • Reset & config (2 tests): reset restores defaults, config description format
  • NeovimTerminalCategory (3 tests): titles, expected cases, invalid raw value

Test plan

  • All 50 new tests pass
  • Tests run in <1s
  • Build passes

Cover value clamping (HUD timeout 1-10s, hold delay 100-2000ms,
TCP port 1024-65535), LeaderKeyPreference Codable round-trips
(navigation, base, custom layers), overlay suppressed bundle IDs
Set operations and deduplication, Neovim reference topic validation,
enum raw value round-trips for all preference enums, invalid raw
value handling, RuleCollectionLayer Codable, default values, reset
behavior, and config description format.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@malpern malpern merged commit 3678964 into master May 22, 2026
@malpern malpern deleted the test/532-preferences-persistence branch May 22, 2026 15:37
@claude
Copy link
Copy Markdown

claude Bot commented May 22, 2026

Code Review — PR #540: 50 Preferences Persistence & Migration Tests

Overall this is a solid test addition. Good boundary coverage, good use of @MainActor, well-organized MARK sections, and the tests stay fast. A few issues worth addressing before the next similar batch:


Major

1. testOverlaySuppressedBundleIDs_DeduplicatesOnStorage doesn't test what it claims

prefs.overlaySuppressedBundleIDs = Set(["com.a.app", "com.a.app", "com.b.app"])
XCTAssertEqual(prefs.overlaySuppressedBundleIDs.count, 2)

Set(["com.a.app", "com.a.app", "com.b.app"]) deduplicates at the Swift call site before the service ever sees the value — you're testing Swift's Set initializer, not the service's storage behavior. To actually test deduplication-on-storage, you'd need to insert the duplicate via separate insert() calls, or assign an array and verify the service reduces it:

// Option A: test insert-based deduplication
prefs.overlaySuppressedBundleIDs.insert("com.a.app")
prefs.overlaySuppressedBundleIDs.insert("com.a.app")
XCTAssertEqual(prefs.overlaySuppressedBundleIDs.filter { $0 == "com.a.app" }.count, 1)

// Option B: if the setter accepts [String], assign duplicates

2. Misleading test name: testLeaderKeyPreference_InvalidJsonFallsBackToDefault

The name implies a graceful fallback to a default value, but the assertion is XCTAssertNil(decoded) — it's actually testing that decoding fails, not that it falls back. The name creates false confidence that the persistence layer handles invalid JSON gracefully. Rename to something like testLeaderKeyPreference_InvalidJsonFailsDecoding.


Minor

3. Hardcoded magic number 37001

func testResetCommunicationSettings_RestoresProtocolAndPort() {
    let defaultPort = 37001   // ← magic number
    ...
    XCTAssertEqual(prefs.tcpServerPort, defaultPort)
}

If the default port ever changes, this test fails for the wrong reason with an unhelpful message. Use the source-of-truth constant if one exists (PreferencesService.defaultTcpServerPort or equivalent), or at minimum add an inline comment explaining where 37001 comes from.


4. Hardcoded preset value 150

XCTAssertEqual(prefs.contextHUDHoldDelayMs, 150, "Preset value should win over custom")

Same issue as above: if .short's millisecond value changes, the failure message won't point at the change. Prefer ContextHUDHoldDelayPreset.short.milliseconds (or whatever the accessor is) so the assertion tracks the source value.


5. Test isolation: manual save/restore is fragile

Several tests manually save and restore shared state:

let originalPort = prefs.tcpServerPort
prefs.tcpServerPort = 8080
// ... test ...
prefs.tcpServerPort = originalPort

This works until a test throws or XCTAssertX fails early — then the restore line never runs and state leaks into subsequent tests. If PreferencesService reads/writes UserDefaults, use addTeardownBlock or setUp/tearDown to isolate:

override func tearDown() {
    super.tearDown()
    PreferencesService().reset() // or inject an in-memory store
}

6. @preconcurrency import XCTest — unexplained suppression

This suppresses Swift concurrency warnings. Fine if it's genuinely needed to avoid noise from XCTest's pre-concurrency API surface, but worth a one-line comment so future maintainers know it's intentional and not a stray import.


Nit

KeyLabelStyle appears in the invalid raw value tests but not in the round-trip tests. If it's not CaseIterable that's fine — just noting the asymmetry in case it was an oversight.


Good coverage of the happy paths and boundaries. Addressing the deduplication test and the misleading name would be the priority fixes.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0d44b87fdb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


func testContextHUDTimeout_ClampedToMinimum() {
let prefs = PreferencesService()
prefs.contextHUDTimeout = 0.5
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reset shared preferences after each persistence test

This test mutates PreferencesService, which writes to UserDefaults.standard, but the class does not isolate or clean up most of these mutations. Since PreferencesService.init() reads persisted defaults, later tests can observe prior test state instead of defaults (including tests in this file that assert default behavior), which makes the suite order-dependent and flaky when test order changes or when the defaults domain is already dirty. Add per-test setup/teardown to clear the KeyPath.* keys (or route tests through an isolated defaults suite).

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

test: expand preferences persistence and migration coverage

1 participant