Home uses a custom top bar, so TabNavigationController now syncs system nav bar visibility with the stack top; remove the scan tab text label to show icon only. Co-authored-by: Cursor <cursoragent@cursor.com>
186 lines
7.7 KiB
Swift
186 lines
7.7 KiB
Swift
//
|
||
// NavigationRouterTests.swift
|
||
// suixinkanTests
|
||
//
|
||
// Created by Codex on 2026/6/22.
|
||
//
|
||
|
||
import UIKit
|
||
import XCTest
|
||
@testable import suixinkan_ios
|
||
|
||
/// UIKit 主导航器测试,覆盖 Tab 宿主、全局入口和当前栈 push 行为。
|
||
@MainActor
|
||
final class AppNavigatorTests: XCTestCase {
|
||
/// 测试当前栈 push 会增加选中导航栈层级,并隐藏底部 TabBar。
|
||
func testPushUsesCurrentNavigationStack() {
|
||
let services = AppServices(apiClient: APIClient(session: AppNavigatorRecordingURLSession()))
|
||
let host = AppNavigatorTestHost(services: services)
|
||
let navigator = AppNavigator()
|
||
navigator.attach(host: host)
|
||
|
||
navigator.push(.home(.paymentCollection), from: nil, animated: false)
|
||
|
||
let homeStack = host.navigationController(for: .home)!
|
||
XCTAssertEqual(homeStack.viewControllers.count, 2)
|
||
XCTAssertTrue(homeStack.viewControllers.last?.hidesBottomBarWhenPushed == true)
|
||
XCTAssertTrue(homeStack.viewControllers.last is PaymentCollectionViewController)
|
||
}
|
||
|
||
/// 测试首页隐藏系统导航栏,push 子页面后显示,返回首页后重新隐藏。
|
||
func testHomeNavigationBarVisibilityFollowsTopViewController() {
|
||
let services = AppServices(apiClient: APIClient(session: AppNavigatorRecordingURLSession()))
|
||
let navigationController = TabNavigationController(tab: .home, services: services)
|
||
|
||
XCTAssertTrue(navigationController.isNavigationBarHidden)
|
||
|
||
navigationController.push(route: .home(.paymentCollection), animated: false)
|
||
XCTAssertFalse(navigationController.isNavigationBarHidden)
|
||
|
||
navigationController.popToRootViewController(animated: false)
|
||
XCTAssertTrue(navigationController.isNavigationBarHidden)
|
||
}
|
||
|
||
/// 测试 tabRoot 会切换到指定 Tab、清空旧栈并打开目标页。
|
||
func testTabRootSelectsTabResetsStackAndPushesRoute() {
|
||
let services = AppServices(apiClient: APIClient(session: AppNavigatorRecordingURLSession()))
|
||
let host = AppNavigatorTestHost(services: services)
|
||
let navigator = AppNavigator()
|
||
navigator.attach(host: host)
|
||
host.navigationController(for: .home)?.push(route: .home(.wallet), animated: false)
|
||
|
||
navigator.openHome(.taskManagement, policy: .tabRoot(.home), animated: false)
|
||
|
||
let homeStack = host.navigationController(for: .home)!
|
||
XCTAssertEqual(host.selectedTab, .home)
|
||
XCTAssertEqual(homeStack.viewControllers.count, 2)
|
||
XCTAssertTrue(homeStack.viewControllers.last is TaskManagementViewController)
|
||
}
|
||
|
||
/// 测试 preserveTab 只切换 Tab 并保留旧栈。
|
||
func testPreserveTabKeepsExistingStack() {
|
||
let services = AppServices(apiClient: APIClient(session: AppNavigatorRecordingURLSession()))
|
||
let host = AppNavigatorTestHost(services: services)
|
||
let navigator = AppNavigator()
|
||
navigator.attach(host: host)
|
||
host.navigationController(for: .profile)?.push(route: .profile(.settings), animated: false)
|
||
|
||
navigator.openProfile(.realNameAuth, policy: .preserveTab(.profile), animated: false)
|
||
|
||
let profileStack = host.navigationController(for: .profile)!
|
||
XCTAssertEqual(host.selectedTab, .profile)
|
||
XCTAssertEqual(profileStack.viewControllers.count, 3)
|
||
XCTAssertTrue(profileStack.viewControllers[1] is SettingsViewController)
|
||
XCTAssertTrue(profileStack.viewControllers.last is RealNameAuthViewController)
|
||
}
|
||
|
||
/// 测试宿主未挂载时全局请求会排队,并在 attach 后按顺序执行。
|
||
func testPendingActionsRunAfterAttachInFIFOOrder() {
|
||
let services = AppServices(apiClient: APIClient(session: AppNavigatorRecordingURLSession()))
|
||
let host = AppNavigatorTestHost(services: services)
|
||
let navigator = AppNavigator()
|
||
|
||
navigator.openHome(.paymentCollection, policy: .tabRoot(.home), animated: false)
|
||
navigator.openHome(.messageCenter, policy: .preserveTab(.home), animated: false)
|
||
navigator.attach(host: host)
|
||
|
||
let homeStack = host.navigationController(for: .home)!
|
||
XCTAssertEqual(homeStack.viewControllers.count, 3)
|
||
XCTAssertTrue(homeStack.viewControllers[1] is PaymentCollectionViewController)
|
||
XCTAssertTrue(homeStack.viewControllers.last is MessageCenterViewController)
|
||
}
|
||
|
||
/// 测试 resetAllStacks 会清空暂存请求并重置已挂载宿主。
|
||
func testResetAllStacksClearsPendingAndMountedStacks() {
|
||
let services = AppServices(apiClient: APIClient(session: AppNavigatorRecordingURLSession()))
|
||
let host = AppNavigatorTestHost(services: services)
|
||
let navigator = AppNavigator()
|
||
|
||
navigator.openHome(.paymentCollection, policy: .tabRoot(.home), animated: false)
|
||
navigator.resetAllStacks()
|
||
navigator.attach(host: host)
|
||
XCTAssertEqual(host.navigationController(for: .home)?.viewControllers.count, 1)
|
||
|
||
navigator.openHome(.wallet, policy: .tabRoot(.home), animated: false)
|
||
navigator.resetAllStacks()
|
||
XCTAssertEqual(host.navigationController(for: .home)?.viewControllers.count, 1)
|
||
XCTAssertEqual(host.selectedTab, .home)
|
||
}
|
||
|
||
/// 测试打开订单入口会切到订单 Tab、回到根页面并传递扫码结果。
|
||
func testOpenOrdersEntryAppliesRootStateAndScanCode() {
|
||
let services = AppServices(apiClient: APIClient(session: AppNavigatorRecordingURLSession()))
|
||
let host = AppNavigatorTestHost(services: services)
|
||
let navigator = AppNavigator()
|
||
navigator.attach(host: host)
|
||
|
||
navigator.openOrdersEntry(.verificationOrders, scannedCode: "VERIFY001")
|
||
|
||
XCTAssertEqual(host.selectedTab, .orders)
|
||
XCTAssertEqual(host.appliedOrdersEntry, .verificationOrders)
|
||
XCTAssertEqual(host.appliedScanCode, "VERIFY001")
|
||
XCTAssertEqual(host.navigationController(for: .orders)?.viewControllers.count, 1)
|
||
}
|
||
}
|
||
|
||
/// AppNavigator 单测专用宿主,模拟主 Tab 容器能力。
|
||
@MainActor
|
||
private final class AppNavigatorTestHost: AppNavigationHost {
|
||
private let services: AppServices
|
||
private var controllers: [AppTab: TabNavigationController] = [:]
|
||
var selectedTab: AppTab = .home
|
||
var appliedOrdersEntry: OrdersEntry?
|
||
var appliedScanCode: String?
|
||
|
||
/// 初始化所有 Tab 导航栈。
|
||
init(services: AppServices) {
|
||
self.services = services
|
||
for tab in AppTab.allCases {
|
||
controllers[tab] = TabNavigationController(tab: tab, services: services)
|
||
}
|
||
}
|
||
|
||
/// 选中指定主 Tab。
|
||
func selectTab(_ tab: AppTab) {
|
||
selectedTab = tab
|
||
}
|
||
|
||
/// 返回指定 Tab 的导航控制器。
|
||
func navigationController(for tab: AppTab) -> TabNavigationController? {
|
||
controllers[tab]
|
||
}
|
||
|
||
/// 返回当前选中 Tab 的导航控制器。
|
||
func selectedNavigationController() -> TabNavigationController? {
|
||
controllers[selectedTab]
|
||
}
|
||
|
||
/// 重置全部 Tab 导航栈。
|
||
func resetAllStacks() {
|
||
for controller in controllers.values {
|
||
controller.popToRootViewController(animated: false)
|
||
}
|
||
selectedTab = .home
|
||
}
|
||
|
||
/// 记录订单根页面接收到的入口状态。
|
||
func applyOrdersEntry(_ entry: OrdersEntry, scannedCode: String?) {
|
||
appliedOrdersEntry = entry
|
||
appliedScanCode = scannedCode
|
||
}
|
||
}
|
||
|
||
/// 空网络会话,避免路由测试发起真实请求。
|
||
private final class AppNavigatorRecordingURLSession: URLSessionProtocol {
|
||
/// 返回空成功响应。
|
||
func data(for request: URLRequest) async throws -> (Data, URLResponse) {
|
||
let response = HTTPURLResponse(
|
||
url: request.url ?? URL(string: "https://example.com")!,
|
||
statusCode: 200,
|
||
httpVersion: nil,
|
||
headerFields: nil
|
||
)!
|
||
return (Data(), response)
|
||
}
|
||
}
|