Skip to content

Latest commit

 

History

History
58 lines (50 loc) · 14.7 KB

README.md

File metadata and controls

58 lines (50 loc) · 14.7 KB

TaskMenuTests

Unit tests cover model behavior, app state, services, pure view helpers, and app lifecycle seams. Prefer small focused test slices while iterating.

Test Map

  • AppStateTests.swift - initial state, preferences (including the sort-order and menu-bar counter preferences and their persistence across sign-out), menuBarPendingCount guards (signed out, Off, live selected-list tasks), version/build-commit display, selected list helpers, ordering (Google position and due-date sort), created-task placement (tasksWithCreatedTask), sign-out/disconnect reset, and basic guarded actions.
  • AppStateBehaviorTests.swift - task mutations (including subtask creation landing first despite stale sibling positions, and drag moves being ignored while sorted by due date), caching, stale response protection, selection changes, refresh behavior, errors (including missing-scope bootstrap recovery and preserving sessions on unrelated 403s), notification sync, task-list creation (append/select, blank titles, failure, sign-out during the request), cross-list moveTask(_:toList:) optimistic updates, rollback, subtask/same-list guards, reminder removal and per-list syncs, and the menu-bar counter's background count sweep, periodic refresh, stale-snapshot discard, and error swallowing (DelayedTasksAPI records listTasks calls per list, can throw per list, and with setMoveTaskSuccess() applies same-list and cross-list moves to its own lists while logging moveCalls).
  • DemoModeTests.swift - entering/leaving demo mode, sample-list seeding without network access, suppressed notification syncing, credential-preserving sign-out, restoration of the account-backed API on exit, account-wide demo counts for the menu-bar counter, and creating a demo list in memory.
  • MenuBarCounterTests.swift - pure menu-bar counting (open vs due-today, day boundaries, subtasks) and the status item title/length presentation helper.
  • SideBySidePanesTests.swift - two-pane state: preference persistence, secondary default list, per-pane loads and stale-load protection, per-list refresh dedupe, mutation fan-out to every pane showing a list, per-pane mutation targeting, hidden-pane re-seeding, sign-out/demo reset, notification sync per visible list (and never from a pane's empty placeholder before its first fetch lands), the primary-pane forwarders, and moving a task into the other pane's list (uses a recording wrapper around DemoTasksAPI).
  • SearchFilterTests.swift - title/notes search, parent context inclusion, root/subtask filtered accessors, and the sort order of filtered roots.
  • TaskItemModelTests.swift and GoogleTasksAPITests.swift - model Codable round trips, completion helpers, parent/subtask fields, and due-date accessors.
  • GoogleTasksAPIBehaviorTests.swift - REST request methods, query parameters (including the move endpoint's destinationTasklist), pagination, auth headers, create/update bodies, the task-list creation request, error mapping (including modern/legacy scope failures and unrelated 403 preservation), setTaskCompleted's status-only PATCH (URL, method, auth header, body, decode, realistic Google IDs, and 401/404/429/500/network error mapping), and GoogleTasksAPI working against a fake AccessTokenProviding instead of the concrete GoogleAuthService.
  • GoogleTasksAPILiveTests.swift - opt-in contract checks against the real Google Tasks API: the first-child insert semantics AppState.addSubtask relies on (positions come back 00000000000000000000 and siblings are renumbered), AppState.addSubtask itself showing the server's order before any refresh, and whether tasks.move with destinationTasklist carries a parent's subtasks along (the assumption behind AppState.moveTask(_:toList:)). Skipped unless TASKMENU_LIVE_GOOGLE_TESTS=1 is set (pass it as TEST_RUNNER_TASKMENU_LIVE_GOOGLE_TESTS=1 to xcodebuild) and the test host can read a sign-in, which means signing in once from a Debug run of the app; it works in a throwaway task list that it deletes afterwards, or inside TASKMENU_LIVE_GOOGLE_LIST_ID (tasks left in place) for comparing with the Google Tasks website.
  • GoogleAuthServiceTests.swift - token loading, refresh/sign-in callback behavior, token exchange errors, partial-consent rejection and granted/omitted scope compatibility, revocation, web-auth test doubles, sign-out clearing both the current and injected legacy Keychain, init invoking a provided KeychainMigrationCoordinating, and AccessTokenProviding conformance.
  • KeychainServiceTests.swift - production wrapper behavior, XCTest in-memory isolation, and access-group isolation/round-trip/delete behavior (a shared-group instance and a legacy no-group instance sharing the same service string never see each other's data).
  • WidgetKeychainMigrationTests.swift - KeychainMigration's per-key KeychainItemMigrationState outcomes against InMemoryKeychainService doubles: no-op when the shared group is unresolved, fresh shared save/read/delete, a legacy-only install migrating every known key (and the resulting GoogleAuthService staying signed in), shared-copy-wins when both exist, resuming a partial migration safely, a failed shared write leaving the legacy item intact, an unverifiable shared write leaving the legacy item intact, migration touching only the exact known keys, and sign-out removing both locations. Includes a local ScriptedKeychainService double for scripting write/verification failures InMemoryKeychainService cannot express. Three signed-host regressions bypass the in-memory path and use unique disposable Keychain services to verify repeated startup, legacy upgrade, and shared-copy-wins cleanup against real Security framework access-group semantics. They skip when the host lacks the shared entitlement and never use the production OAuth service.
  • WidgetGoogleAccessTokenProviderTests.swift - WidgetGoogleAccessTokenProvider against a shared-group InMemoryKeychainService and MockURLProtocol: no-credentials/no-network-call, cached-valid-token reuse, expired-token refresh writing shared access/expiration, an omitted vs. rotated replacement refresh token, transient 429/400-undecodable/network failures preserving credentials, definitive invalid_grant/invalid_client clearing shared credentials, same-process concurrent-caller coalescing onto one refresh, and that the provider (never given a legacy-location double) cannot read legacy credentials.
  • DueDateNotificationServiceTests.swift - authorization, scheduling, stale-removal, and identifier targeting.
  • GitHubUpdateCheckerTests.swift - semantic-version parsing, GitHub release decoding, update-check persistence, throttling, launch-alert suppression, and AppState update outcomes.
  • DateFormattingTests.swift - RFC 3339, Google due-date, display, and relative-date behavior.
  • TaskWidgetSnapshotStoreTests.swift - TaskWidgetSnapshotStore's coordinated App Group reads/writes: missing/corrupt/future-schema files, atomic replacement, concurrent app/extension-style writers, replaceList(_:requestStartedAt:) stale-write rejection and acceptance, unrelated-list preservation, pruneLists(keeping:) deleted-list removal, clear() sign-out behavior, an unavailable container, and a check that the serialized JSON never contains a notes/email/token key. Every test uses an injected temporary directory, never the real App Group.
  • TaskWidgetProjectionTests.swift - TaskWidgetProjection's open-only filtering, Google-position/due-date root ordering (including position ties), direct-subtask placement and the orphan-subtask-becomes-root policy, row-budget truncation and +N remainder counting, and due-treatment bucketing (overdue/today/tomorrow/dated/undated) across multiple time zones and DST boundaries.
  • TaskWidgetProviderTests.swift - TaskWidgetTimelineLoader and TaskWidgetListCatalog, the Foundation-only decision logic behind the extension's TaskListWidgetProvider/TaskListEntityQuery (which live only in TaskMenuWidget and are not visible to this test host — see TaskMenuWidget/README.md's "Testability boundary"): placeholder sample content performing zero I/O and honoring the row budget, cache-only reads for signed-out/no-cache/populated-stale gallery content, live success writing and returning fresh tasks, offline/cancellation falling back to cache without persisting an error, no-cache-and-offline rendering "no cache" rather than crashing, definitive auth rejection clearing credentials and cached task titles, a deleted configured list staying unavailable, and the entity catalog's identity/order/default-list/renamed-title resolution. Also hosts the shared FakeTasksAPI double used by CompleteTaskIntentTests.swift.
  • TaskWidgetDeepLinkTests.swift - TaskWidgetDeepLink construction/parsing only: valid round trip, percent-encoded ids, wrong scheme/host/path, missing/empty/duplicate/oversized id. App-side routing (popover presentation, cold-start buffering) is out of this suite's scope.
  • TaskWidgetDeepLinkRoutingTests.swift - the widget deep-link routing decisions against fakes (no NSAppleEventManager, AppKit event plumbing, or live status item): TaskWidgetDeepLinkRouter malformed/wrong-scheme URLs having no side effects, signed-out routing straight to the popover, signed-in-but-not-loaded bootstrapping before selecting, a valid list selecting in the primary pane, and a deleted/unknown list presenting the popover without switching lists; plus DeepLinkColdStartBufferTests covering the "hold at most one link, replace rather than queue a second one, flush exactly once on markReady()" contract.
  • CompleteTaskIntentTests.swift - TaskWidgetCompletionCoordinator, the Foundation-only cascade/refetch/publish logic behind the extension's CompleteTaskIntent (not visible to this test host — see TaskMenuWidget/README.md): single-task success, parent-to-open-children cascade, subtask completion never affecting siblings, total transient failure leaving the cache unchanged, partial child failure publishing authoritative server truth instead of claiming full success, a stale in-flight app fetch unable to resurrect a task the widget already completed, definitive auth failure clearing credentials and the cache, and refusing to mutate a task id the cached list never confirmed.
  • AppStateWidgetPublishingTests.swift - AppState's widget-snapshot publishing at every convergence point (bootstrap/list load, selected-list load, account-wide sweep, add/edit/delete/reorder, completion and its rollback, cross-list move and its rollback, list creation, deleted-list recovery, selection change, sign-out/disconnect), demo mode never reaching a production publisher, and the cross-process stale-write protection TaskWidgetSnapshotPublisher provides against a slow in-flight fetch overwriting a newer cross-process write. Most tests inject the fast in-memory RecordingTaskWidgetSnapshotPublisher; two tests use a real store-backed TaskWidgetSnapshotPublisher pointed at a temporary directory (never the App Group) to prove store-level behavior, including that the published JSON contains no notes/email/token content.
  • TaskListViewTests.swift, TaskDetailViewTests.swift, and MenuBarWindowChromeTests.swift - shared task presentation helpers (including the pane-aware TaskListPresentation variants), the inline add-subtask field and where a committed subtask renders (end to end through AppState and DemoTasksAPI), the header's sort menu and side-by-side toggle button, drag gating under a due-date sort, the list picker's "New List…" item and inline field, the "Move to" context submenu, the content view rendering the pane it is handed, the task editor's list picker (ID-based selection, enabled/disabled rules, and a move that saves pending edits first), the signed-in popover rendering one or two pane controllers at 320 or 641 wide with the hairline divider, and AppKit glass/window behavior, including the Reduce transparency Settings switch, preference persistence, and live surface updates without rebuilding controls.
  • TaskMenuActionButtonTests.swift - pointing-hand cursor opt-in on TaskMenuActionButton (cursor tracking areas and cursorUpdate behavior).
  • MetricKitPayloadStoreTests.swift - local payload persistence.
  • TaskMenuAppTests.swift - app/app-delegate construction seams, launch UI mode parsing, the automatic update-check loop, and update-alert choice mapping.
  • StatusBarControllerPopoverTests.swift - PopoverPresentationDecision.shouldPresent, the pure idempotency guard behind StatusBarController.showPopover() (the widget deep-link entry point): presents when not already shown, does nothing when already shown so a second call never toggles the popover closed. StatusBarController itself keeps its NSStatusItem/NSPopover private and is not constructed live in this suite; see the file's header comment.
  • TaskMenuMainMenuTests.swift - main-menu structure and ordering, first-responder selectors and nil targets, key-equivalent resolution for every shortcut, autoenabling, responder-chain resolution of ⌘N/⌘F from a focused text field, and the unconditional launch install. Never performs a real copy: — that would clobber the developer's clipboard.
  • SettingsLaunchAtLoginTests.swift - the pure launch-at-login status decision (requiresApproval notice) behind the Settings toggle.
  • SettingsVersionRowTests.swift - the rendered Settings version row, covering the Version <version> (<commit>) text and the dev fallback for unstamped builds.
  • SettingsSideBySideRowTests.swift - the "Show two lists side by side" switch in Settings › General: present, mirrors AppState.sideBySideListsEnabled, and writes it back.

Test Doubles

  • MockURLProtocol.swift provides a URLSession that records requests and returns stubbed responses.
  • InMemoryKeychainService.swift provides keychain success/failure doubles. InMemoryKeychainService takes an optional accessGroup label (each instance is already an independent store, so construct one per location — e.g. one for "legacy" and one for "shared" — to test access-group-aware code without the real Keychain).
  • TestDueDateNotificationService.swift records notification sync and removal calls for AppState.

Running Focused Tests

xcodebuild test -project TaskMenu.xcodeproj -scheme TaskMenu \
  -configuration Debug \
  -only-testing:TaskMenuTests/GoogleTasksAPIBehaviorTests
  • Use -only-testing: for the nearest suite first.
  • Mark test classes @MainActor when they touch AppState, GoogleAuthService, AppKit, or other main-actor helpers.
  • Keychain tests must use unique service names or the in-memory XCTest path to avoid cross-test contamination.
  • The default widget publisher isolation test must pass with signed and unsigned hosts; a resolved App Group identifier is valid, but the publisher's store must remain container-less under XCTest.
  • When changing source file membership, regenerate with xcodegen generate before running tests.