同步 Android 启动页到 iOS,并在冷启动期间静默恢复登录态。

新增 SplashView、SplashCoordinator 与 Launch Screen 资源,移除冷启动 Lottie;LoginView 加载 App 配置。同时将 token 存储迁移至 UserDefaults,并更新相关测试与文档。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-29 14:59:44 +08:00
parent 08492df6ce
commit 5bdf4a7dbf
29 changed files with 501 additions and 91 deletions

View File

@ -0,0 +1,87 @@
//
// AppConfigTests.swift
// suixinkanTests
//
// Created by Codex on 2026/6/29.
//
import XCTest
@testable import suixinkan
@MainActor
/// App API
final class AppConfigTests: XCTestCase {
/// App 使 enable_register
func testAppConfigAPIRequestsConfigEndpoint() async throws {
let response = """
{
"code": 100000,
"msg": "success",
"data": {
"enable_register": true
}
}
""".data(using: .utf8)!
let session = AppConfigURLSession(responses: [response])
let api = AppConfigAPI(client: APIClient(session: session))
let config = try await api.fetchAppConfig()
XCTAssertTrue(config.enableRegister)
let request = try XCTUnwrap(session.requests.first)
XCTAssertEqual(request.httpMethod, "GET")
XCTAssertEqual(request.url?.path, "/api/app/config")
}
/// App enableRegister
func testLoginViewModelLoadAppConfigUpdatesEnableRegister() async throws {
let response = """
{
"code": 100000,
"msg": "success",
"data": {
"enable_register": true
}
}
""".data(using: .utf8)!
let session = AppConfigURLSession(responses: [response])
let api = AppConfigAPI(client: APIClient(session: session))
let viewModel = LoginViewModel()
await viewModel.loadAppConfig(appConfigAPI: api)
XCTAssertTrue(viewModel.enableRegister)
}
/// App
func testLoginViewModelLoadAppConfigKeepsDefaultOnFailure() async {
let session = AppConfigURLSession(responses: [Data()])
let api = AppConfigAPI(client: APIClient(session: session))
let viewModel = LoginViewModel()
await viewModel.loadAppConfig(appConfigAPI: api)
XCTAssertFalse(viewModel.enableRegister)
}
}
/// App URLSession
private final class AppConfigURLSession: URLSessionProtocol {
private var responses: [Data]
private(set) var requests: [URLRequest] = []
///
init(responses: [Data]) {
self.responses = responses
}
///
func data(for request: URLRequest) async throws -> (Data, URLResponse) {
requests.append(request)
let data = responses.isEmpty ? Data() : responses.removeFirst()
return (
data,
HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
)
}
}