Introduce GlobalLoadingCenter with reference counting, wire it through RootView and major auth/order/profile screens, and add the loading animation asset plus unit tests. Co-authored-by: Cursor <cursoragent@cursor.com>
114 lines
4.2 KiB
Swift
114 lines
4.2 KiB
Swift
//
|
||
// GlobalLoadingCenterTests.swift
|
||
// suixinkanTests
|
||
//
|
||
// Created by Codex on 2026/6/23.
|
||
//
|
||
|
||
import XCTest
|
||
@testable import suixinkan
|
||
|
||
@MainActor
|
||
/// 全局 Loading 命令中心测试,验证展示状态不会泄漏给业务页面使用。
|
||
final class GlobalLoadingCenterTests: XCTestCase {
|
||
/// 测试多次 show/hide 会按引用计数控制最终显隐。
|
||
func testShowHideUsesReferenceCount() {
|
||
let center = GlobalLoadingCenter()
|
||
|
||
center.show(message: "加载账号")
|
||
center.show(message: "加载权限")
|
||
|
||
XCTAssertEqual(center.snapshotForTests, GlobalLoadingSnapshot(isVisible: true, message: "加载权限", activeCount: 2))
|
||
|
||
center.hide()
|
||
XCTAssertEqual(center.snapshotForTests, GlobalLoadingSnapshot(isVisible: true, message: "加载权限", activeCount: 1))
|
||
|
||
center.hide()
|
||
XCTAssertEqual(center.snapshotForTests, GlobalLoadingSnapshot(isVisible: false, message: "", activeCount: 0))
|
||
}
|
||
|
||
/// 测试 hide 调用次数超过 show 时不会产生负数计数。
|
||
func testHideNeverDropsReferenceCountBelowZero() {
|
||
let center = GlobalLoadingCenter()
|
||
|
||
center.hide()
|
||
|
||
XCTAssertEqual(center.snapshotForTests, GlobalLoadingSnapshot(isVisible: false, message: "", activeCount: 0))
|
||
}
|
||
|
||
/// 测试更新文案只影响当前展示内容,不改变引用计数。
|
||
func testUpdateMessageOnlyChangesVisibleMessage() {
|
||
let center = GlobalLoadingCenter()
|
||
|
||
center.updateMessage("不会展示")
|
||
XCTAssertEqual(center.snapshotForTests, GlobalLoadingSnapshot(isVisible: false, message: "", activeCount: 0))
|
||
|
||
center.show(message: "加载中")
|
||
center.updateMessage("即将完成")
|
||
|
||
XCTAssertEqual(center.snapshotForTests, GlobalLoadingSnapshot(isVisible: true, message: "即将完成", activeCount: 1))
|
||
}
|
||
|
||
/// 测试 withLoading 成功完成后自动隐藏 Loading。
|
||
func testWithLoadingHidesAfterSuccess() async throws {
|
||
let center = GlobalLoadingCenter()
|
||
|
||
let value = try await center.withLoading(message: "提交中") {
|
||
XCTAssertEqual(center.snapshotForTests, GlobalLoadingSnapshot(isVisible: true, message: "提交中", activeCount: 1))
|
||
return 42
|
||
}
|
||
|
||
XCTAssertEqual(value, 42)
|
||
XCTAssertEqual(center.snapshotForTests, GlobalLoadingSnapshot(isVisible: false, message: "", activeCount: 0))
|
||
}
|
||
|
||
/// 测试 withLoading 抛错时也会自动隐藏 Loading。
|
||
func testWithLoadingHidesAfterFailure() async {
|
||
let center = GlobalLoadingCenter()
|
||
|
||
do {
|
||
_ = try await center.withLoading(message: "提交中") {
|
||
throw TestFailure.expected
|
||
} as Int
|
||
XCTFail("withLoading 应该向外抛出业务错误")
|
||
} catch {
|
||
XCTAssertEqual(error as? TestFailure, .expected)
|
||
}
|
||
|
||
XCTAssertEqual(center.snapshotForTests, GlobalLoadingSnapshot(isVisible: false, message: "", activeCount: 0))
|
||
}
|
||
|
||
/// 测试 withOptionalLoading 关闭时不会改变展示状态。
|
||
func testWithOptionalLoadingDisabledDoesNotChangeState() async throws {
|
||
let center = GlobalLoadingCenter()
|
||
|
||
let value = try await center.withOptionalLoading(false, message: "不展示") {
|
||
XCTAssertEqual(center.snapshotForTests, GlobalLoadingSnapshot(isVisible: false, message: "", activeCount: 0))
|
||
return "done"
|
||
}
|
||
|
||
XCTAssertEqual(value, "done")
|
||
XCTAssertEqual(center.snapshotForTests, GlobalLoadingSnapshot(isVisible: false, message: "", activeCount: 0))
|
||
}
|
||
|
||
/// 测试使用 Loading 不需要读取可观察展示状态,业务侧只能拿到命令中心类型。
|
||
func testBusinessCodeOnlyDependsOnCommandCenter() {
|
||
let center = GlobalLoadingCenter()
|
||
|
||
issueLoadingCommand(center)
|
||
|
||
XCTAssertEqual(center.snapshotForTests, GlobalLoadingSnapshot(isVisible: true, message: "加载中", activeCount: 1))
|
||
center.hide()
|
||
}
|
||
|
||
/// 模拟业务代码只接收命令中心并发出展示指令。
|
||
private func issueLoadingCommand(_ center: GlobalLoadingCenter) {
|
||
center.show(message: "加载中")
|
||
}
|
||
|
||
/// 测试用错误类型。
|
||
private enum TestFailure: Error, Equatable {
|
||
case expected
|
||
}
|
||
}
|