Skip to content

feat: Test Mode — test store products, purchase/restore overrides, dark mode UI - #425

Merged
yusuftor merged 35 commits into
developfrom
feature/test-store
Feb 18, 2026
Merged

yusuftor merged 35 commits into
developfrom
feature/test-store

Conversation

@yusuftor

@yusuftor yusuftor commented Jan 27, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Core Test Mode

  • TestModeManager: Detects test mode users from config (user ID, alias ID match) or bundle ID mismatch, fetches products from /v1/products endpoint, caches as StoreProduct
  • StoreKitManager: In test mode, uses cached test products instead of fetching from StoreKit
  • TransactionManager: Intercepts purchase and restore flows in test mode — shows test mode drawer/entitlement picker instead of real StoreKit transactions
  • DeviceHelper: Forces isSandbox = "true" when test mode active so all events route to sandbox
  • DependencyContainer: Wires TestModeManager to StoreKitManager and DeviceHelper after initialization
  • ConfigManager: Triggers test product fetching after config load when test mode detected

TestModeBehavior Option

  • Adds TestModeBehavior enum (.automatic, .whenEnabledForUser, .never, .always) to SuperwallOptions for explicit control over when test mode activates
  • isTestEnvironment detects test runners via NSClassFromString("XCTestCase") but bypasses when SUPERWALL_UNIT_TESTS launch arg is present for internal unit tests

Dark Mode UI

  • Adds dark branded header with Superwall logo to TestModePurchaseDrawer
  • Replaces all hardcoded colors with semantic UIKit colors (.label, .secondaryLabel, .separator, etc.) for proper light/dark mode support
  • Close button uses white tint on dark header background

Misc

  • Fixes all SwiftLint violations in TestMode files
  • Adds comprehensive test coverage for TestModeManager and all TestModeBehavior cases

Checklist

  • All unit tests pass.
  • All UI tests pass.
  • Demo project builds and runs on iOS.
  • Demo project builds and runs on Mac Catalyst.
  • Demo project builds and runs on visionOS.
  • I added/updated tests or detailed why my change isn't tested.
  • I added an entry to the CHANGELOG.md for any breaking changes, enhancements, or bug fixes.
  • I have run swiftlint in the main directory and fixed any issues.
  • I have updated the SDK documentation as well as the online docs.
  • I have reviewed the contributing guide

🤖 Generated with Claude Code

yusuftor and others added 5 commits January 27, 2026 13:52
Add CodingKeys for snake_case decoding since Web2App decoder
doesn't auto-convert from snake case.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Maps V2Product API response data into a StoreProductType for use
as a test store product backed by the Superwall API.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add TestModeManager for detecting test mode users via config
- Add TestStoreUser model parsed from config testStoreUsers
- Add TestModeColdLaunchAlert for showing test mode status on launch
- Add TestModePurchaseDrawer for simulating purchases (purchase/abandon/fail)
- Add TestModeTransactionHandler to intercept purchase and restore flows
- Override StoreKitManager.getProducts to use cached test products
- Override TransactionManager.purchase to show test mode drawer
- Override TransactionManager.tryToRestore to show entitlement picker
- Override DeviceHelper.isSandbox to return true in test mode
- Rename V2Product to SuperwallProduct across codebase
- Fetch products from /v1/products endpoint on config load when test mode

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…tsById

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment thread Sources/SuperwallKit/TestMode/TestModeTransactionHandler.swift
Comment thread Sources/SuperwallKit/StoreKit/Transactions/TransactionManager.swift Outdated
yusuftor and others added 11 commits January 27, 2026 17:15
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add bundleId to config endpoint application query
- Include bundle_id_config in config JSON response
- Decode bundleIdConfig in Swift Config model
- Check bundle ID mismatch in evaluateTestMode — activates test mode
  when app bundle ID doesn't match config

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

10 files reviewed, 5 comments

Edit Code Review Agent Settings | Greptile

Comment thread Sources/SuperwallKit/Superwall.swift Outdated
Comment thread Sources/SuperwallKit/Config/ConfigManager.swift
Comment thread Sources/SuperwallKit/StoreKit/StoreKitManager.swift Outdated
@greptile-apps

greptile-apps Bot commented Feb 6, 2026

Copy link
Copy Markdown
Additional Comments (2)

Sources/SuperwallKit/Config/Models/Config.swift
Config.encode changed semantics

Config.encode(to:) now always encodes web2appConfig, iosAppId, bundleIdConfig, and testModeUserIds (if present), whereas previously those fields were not encoded at all. If this Codable type is used for caching/round-tripping configs (e.g. persisted LatestConfig), this changes the on-disk JSON shape and can break decode expectations in older versions or any code assuming a server-shaped payload. If the intent is “cache the server response as-is”, consider keeping encoding symmetric with what the server sends, or isolating persistence to a dedicated type instead of changing Config’s encoding behavior.

Prompt To Fix With AI
This is a comment left during a code review.
Path: Sources/SuperwallKit/Config/Models/Config.swift
Line: 118:142

Comment:
**Config.encode changed semantics**

`Config.encode(to:)` now always encodes `web2appConfig`, `iosAppId`, `bundleIdConfig`, and `testModeUserIds` (if present), whereas previously those fields were not encoded at all. If this `Codable` type is used for caching/round-tripping configs (e.g. persisted `LatestConfig`), this changes the on-disk JSON shape and can break decode expectations in older versions or any code assuming a server-shaped payload. If the intent is “cache the server response as-is”, consider keeping encoding symmetric with what the server sends, or isolating persistence to a dedicated type instead of changing `Config`’s encoding behavior.

How can I resolve this? If you propose a fix, please make it concise.

Sources/SuperwallKit/Config/ConfigManager.swift
MainActor isolation mismatch

presentTestModeModal is @MainActor, but it’s awaited directly from processConfig (which is not @MainActor). This will fail under strict concurrency checking / actor isolation rules. Wrap the call with await MainActor.run { ... } / Task { @MainActor in ... }, or make the caller main-actor isolated.

(See presentTestModeModal declaration later in this file.)

Prompt To Fix With AI
This is a comment left during a code review.
Path: Sources/SuperwallKit/Config/ConfigManager.swift
Line: 438:446

Comment:
**MainActor isolation mismatch**

`presentTestModeModal` is `@MainActor`, but it’s awaited directly from `processConfig` (which is not `@MainActor`). This will fail under strict concurrency checking / actor isolation rules. Wrap the call with `await MainActor.run { ... }` / `Task { @MainActor in ... }`, or make the caller main-actor isolated.

(See `presentTestModeModal` declaration later in this file.)

How can I resolve this? If you propose a fix, please make it concise.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

5 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment thread Sources/SuperwallKit/StoreKit/StoreKitManager.swift
Comment thread Sources/SuperwallKit/Config/ConfigManager.swift
yusuftor and others added 9 commits February 17, 2026 17:03
Adds a new `debugModeBehavior` property to `SuperwallOptions` that gives
developers explicit control over when test mode activates:

- `.automatic` (default): activates on dashboard match or bundle ID
  mismatch, but never during UI tests
- `.whenEnabledForUser`: only activates on dashboard user match
- `.never`: test mode is never activated
- `.always`: test mode is always activated

Also forces `shouldBypassAppTransactionCheck = true` in UI test
environments so StoreKit checks don't block test runners.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Added superwall logo to test payment sheet
… detection

- Rename DebugModeBehavior -> TestModeBehavior, debugModeBehavior -> testModeBehavior
- Rename SWKDebugModeBehavior -> SWKTestModeBehavior (ObjC)
- Rename debugOption -> testModeOption
- Rename isUITestEnvironment -> isTestEnvironment
- Skip test mode in test environments unless SUPERWALL_UNIT_TESTS launch arg is set
- Update tests to work correctly with the new environment detection

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use where clause instead of if inside for loop
- Remove superfluous swiftlint disable commands
- Remove extra blank line before closing brace

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add testModeBehavior option to SuperwallOptions
@yusuftor yusuftor changed the title feat: Test Mode — test store products, purchase/restore overrides, sandbox detection feat: Test Mode — test store products, purchase/restore overrides, dark mode UI Feb 18, 2026
@yusuftor
yusuftor merged commit 899f553 into develop Feb 18, 2026
2 checks passed
@yusuftor
yusuftor deleted the feature/test-store branch February 18, 2026 14:28
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.

2 participants