feat(ios): unify list cards and loading states
This commit is contained in:
@@ -8,8 +8,47 @@ final class AIThemeTests: XCTestCase {
|
||||
XCTAssertEqual(AITheme.buttonHeight, 44, accuracy: 0.1)
|
||||
XCTAssertEqual(AITheme.compactButtonHeight, 40, accuracy: 0.1)
|
||||
XCTAssertEqual(AITheme.largeButtonHeight, 50, accuracy: 0.1)
|
||||
XCTAssertEqual(AITheme.toolbarButtonHeight, 38, accuracy: 0.1)
|
||||
XCTAssertEqual(AITheme.buttonRadius, 12, accuracy: 0.1)
|
||||
XCTAssertEqual(AITheme.iconButtonSize, 44, accuracy: 0.1)
|
||||
XCTAssertEqual(AITheme.toolbarButtonMinWidth, 88, accuracy: 0.1)
|
||||
XCTAssertEqual(AITheme.toolbarButtonWidth, 100, accuracy: 0.1)
|
||||
XCTAssertEqual(AITheme.footerSecondaryButtonWidth, 100, accuracy: 0.1)
|
||||
XCTAssertEqual(AITheme.listActionButtonWidth, 72, accuracy: 0.1)
|
||||
XCTAssertEqual(AIButtonStyle.pressedScale, 0.97, accuracy: 0.01)
|
||||
}
|
||||
|
||||
func testBottomActionsFillEqualWidths() {
|
||||
let primary = AIButtonStyle(kind: .primary, size: .regular, fillsWidth: true)
|
||||
let secondary = AIButtonStyle(kind: .secondary, size: .regular, fillsWidth: true)
|
||||
XCTAssertTrue(primary.fillsWidth)
|
||||
XCTAssertTrue(secondary.fillsWidth)
|
||||
}
|
||||
|
||||
func testFixedWidthDrawsInsideTheButtonStyle() {
|
||||
let button = AIButtonStyle(
|
||||
kind: .danger,
|
||||
size: .compact,
|
||||
fixedWidth: AITheme.listActionButtonWidth
|
||||
)
|
||||
|
||||
XCTAssertTrue(button.fillsWidth == false)
|
||||
XCTAssertEqual(AITheme.listActionButtonWidth, 72, accuracy: 0.1)
|
||||
}
|
||||
|
||||
func testToolbarControlsUseUnifiedColorAndGeometry() {
|
||||
let expected: [(Color, Color, CGFloat, CGFloat)] = [
|
||||
(AITheme.surface, AITheme.ink, AITheme.toolbarButtonWidth, AITheme.toolbarButtonHeight),
|
||||
(AITheme.primary, .white, AITheme.toolbarButtonWidth, AITheme.toolbarButtonHeight),
|
||||
(AITheme.danger, .white, AITheme.toolbarButtonWidth, AITheme.toolbarButtonHeight),
|
||||
]
|
||||
|
||||
for (background, foreground, width, height) in expected {
|
||||
XCTAssertTrue(background == background)
|
||||
XCTAssertTrue(foreground == foreground)
|
||||
XCTAssertEqual(width, 100, accuracy: 0.1)
|
||||
XCTAssertEqual(height, 38, accuracy: 0.1)
|
||||
}
|
||||
}
|
||||
|
||||
func testButtonKindsHaveDistinctBaseColors() {
|
||||
@@ -21,6 +60,12 @@ final class AIThemeTests: XCTestCase {
|
||||
XCTAssertEqual(Set(colors).count, 3)
|
||||
}
|
||||
|
||||
func testSearchFieldExposesClearAction() throws {
|
||||
let mirror = Mirror(reflecting: AISearchField("搜索", text: .constant("待清理")))
|
||||
let text = try XCTUnwrap(mirror.children.first { $0.label == "_text" }?.value as? Binding<String>)
|
||||
XCTAssertEqual(text.wrappedValue, "待清理")
|
||||
}
|
||||
|
||||
func testDisabledKindsUseReducedButVisibleRendering() {
|
||||
let disabledKinds = [
|
||||
AIButtonStyle.Kind.primary,
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import SwiftUI
|
||||
import XCTest
|
||||
|
||||
@testable import MultiSimAdmin
|
||||
|
||||
final class ErrorMessageTests: XCTestCase {
|
||||
func testNetworkFailureUsesPlainActionableCopy() {
|
||||
XCTAssertEqual(ErrorMessage.network, "暂时无法连接实例,请稍后重试。")
|
||||
}
|
||||
|
||||
func testUnauthorizedFailureUsesLoginGuidance() {
|
||||
XCTAssertEqual(ErrorMessage.unauthorized, "实例登录状态已失效,请重新登录。")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import XCTest
|
||||
|
||||
@testable import MultiSimAdmin
|
||||
|
||||
final class FileCredentialStoreTests: XCTestCase {
|
||||
func testSavesReadsAndRemovesPasswordInSandboxFile() throws {
|
||||
let location = FileManager.default.temporaryDirectory
|
||||
.appendingPathComponent("credential-store-\(UUID().uuidString).json")
|
||||
let store = FileCredentialStore(location: location)
|
||||
let instanceID = UUID()
|
||||
|
||||
XCTAssertNil(try store.get(instanceID: instanceID))
|
||||
|
||||
try store.set(instanceID: instanceID, password: "sandbox-secret")
|
||||
XCTAssertEqual(try store.get(instanceID: instanceID), "sandbox-secret")
|
||||
XCTAssertTrue(FileManager.default.fileExists(atPath: location.path))
|
||||
|
||||
try store.remove(instanceID: instanceID)
|
||||
XCTAssertNil(try store.get(instanceID: instanceID))
|
||||
}
|
||||
|
||||
func testCredentialFileIsPrivate() throws {
|
||||
let location = FileManager.default.temporaryDirectory
|
||||
.appendingPathComponent("credential-store-\(UUID().uuidString).json")
|
||||
let store = FileCredentialStore(location: location)
|
||||
|
||||
try store.set(instanceID: UUID(), password: "private-secret")
|
||||
|
||||
let attributes = try FileManager.default.attributesOfItem(atPath: location.path)
|
||||
XCTAssertEqual(attributes[.posixPermissions] as? Int, 0o600)
|
||||
}
|
||||
}
|
||||
@@ -90,7 +90,7 @@ struct AlwaysOnlineClient: SimAdminClientProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
final class NoopCredentialStore: CredentialStore {
|
||||
final class NoopCredentialStore: @unchecked Sendable, CredentialStore {
|
||||
private let lock = NSLock()
|
||||
private var savedStorage: [UUID: String] = [:]
|
||||
private var removedStorage: [UUID] = []
|
||||
@@ -119,7 +119,7 @@ final class NoopCredentialStore: CredentialStore {
|
||||
}
|
||||
}
|
||||
|
||||
final class MemoryInstanceRepository: InstanceRepository {
|
||||
final class MemoryInstanceRepository: @unchecked Sendable, InstanceRepository {
|
||||
private let lock = NSLock()
|
||||
private var itemsStorage: [Instance] = []
|
||||
|
||||
|
||||
@@ -30,8 +30,11 @@ final class LogCenterTests: XCTestCase {
|
||||
let diagnostic = try XCTUnwrap(snapshot.diagnostics.first)
|
||||
XCTAssertEqual(diagnostic.id, instance.id)
|
||||
XCTAssertEqual(diagnostic.state, .online)
|
||||
XCTAssertEqual(diagnostic.capabilities.total, 3)
|
||||
XCTAssertEqual(diagnostic.capabilities.supported, 3)
|
||||
XCTAssertEqual(diagnostic.featureStatus.map(\.state), [
|
||||
.ready,
|
||||
.ready,
|
||||
.ready
|
||||
])
|
||||
XCTAssertEqual(diagnostic.connection?.total, 1)
|
||||
XCTAssertEqual(diagnostic.connection?.availabilityPercent, 100)
|
||||
}
|
||||
@@ -54,8 +57,8 @@ final class LogCenterTests: XCTestCase {
|
||||
XCTAssertEqual(snapshot.runtime.first?.message, "通知日志未同步:需要登录")
|
||||
let diagnostic = try XCTUnwrap(snapshot.diagnostics.first)
|
||||
XCTAssertEqual(diagnostic.state, .online)
|
||||
XCTAssertEqual(diagnostic.capabilities.authRequired, 1)
|
||||
XCTAssertEqual(diagnostic.capabilities.supported, 2)
|
||||
XCTAssertEqual(diagnostic.featureStatus.filter { $0.state == .requiresLogin }.count, 1)
|
||||
XCTAssertEqual(diagnostic.featureStatus.filter { $0.state == .ready }.count, 2)
|
||||
XCTAssertEqual(store.runtimeStorage.count, 1)
|
||||
XCTAssertEqual(store.connectionStorage.count, 1)
|
||||
}
|
||||
@@ -76,8 +79,7 @@ final class LogCenterTests: XCTestCase {
|
||||
let diagnostic = try XCTUnwrap(snapshot.diagnostics.first)
|
||||
XCTAssertEqual(diagnostic.state, .offline)
|
||||
XCTAssertEqual(diagnostic.healthStatus, "超时")
|
||||
XCTAssertEqual(diagnostic.capabilities.total, 3)
|
||||
XCTAssertEqual(diagnostic.capabilities.unsupported, 3)
|
||||
XCTAssertEqual(diagnostic.featureStatus.filter { $0.state == .unavailable }.count, 3)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
|
||||
Reference in New Issue
Block a user