Files
legado-ios/Tests/LegadoReaderTests/AppearanceContrastTests.swift
T
Aminark 45cf5b5482 fix(appearance): 完善深色模式和阅读页面对比度
- 区分按钮填充与图标颜色,统一书架、书源、搜索和阅读设置的选中态
- 调整编辑器语法高亮、键盘工具栏及统计卡片的浅深色显示
- 适配 RSS 正文和登录网页背景,保留书源自定义样式和页面状态
- 补充外观切换、页面对比度和管理页面的回归测试
2026-10-22 10:53:19 +08:00

87 lines
4.2 KiB
Swift

import SwiftUI
import UIKit
import WebKit
import XCTest
@testable import Legado
@MainActor
final class AppearanceContrastTests: XCTestCase {
func testLoginWebViewUsesAdaptiveBackgroundBeforeContentArrives() async throws {
var source = BookSource(bookSourceUrl: "https://appearance.invalid", bookSourceName: "外观测试")
source.loginUrl = "about:blank"
let controller = UIHostingController(rootView: SourceLoginView(source: source))
let window = UIWindow(frame: CGRect(x: 0, y: 0, width: 390, height: 844))
window.overrideUserInterfaceStyle = .dark
window.rootViewController = controller
window.makeKeyAndVisible()
defer { window.isHidden = true }
func webView(in view: UIView) -> WKWebView? {
if let view = view as? WKWebView { return view }
return view.subviews.lazy.compactMap { webView(in: $0) }.first
}
window.layoutIfNeeded()
for _ in 0..<20 {
if webView(in: controller.view) != nil { break }
try await Task.sleep(nanoseconds: 50_000_000)
}
let web = try XCTUnwrap(webView(in: controller.view))
XCTAssertFalse(web.isOpaque)
for style in [UIUserInterfaceStyle.dark, .light] {
window.overrideUserInterfaceStyle = style
window.layoutIfNeeded()
let traits = UITraitCollection(userInterfaceStyle: style)
let background = try XCTUnwrap(web.backgroundColor).resolvedColor(with: traits)
let scroll = try XCTUnwrap(web.scrollView.backgroundColor).resolvedColor(with: traits)
if style == .dark {
XCTAssertLessThan(luminance(background), 0.15)
XCTAssertLessThan(luminance(scroll), 0.15)
} else {
XCTAssertGreaterThan(luminance(background), 0.6)
XCTAssertGreaterThan(luminance(scroll), 0.6)
}
}
}
func testSyntaxTokensHaveReadableContrastInBaseAndElevatedAppearances() {
let samples: [(SyntaxCodeEditor.Language, String)] = [
(.json, #"{"title":"正文","count":12,"enabled":true,"empty":null}"#),
(.rule, "@css:div && $.title {{key}} ##"),
(.regex, #"\d+.*(word){2,4}"#),
(.javascript, "// comment\nconst value = 'text'; return 123; /* comment */")
]
for style in [UIUserInterfaceStyle.light, .dark] {
for level in [UIUserInterfaceLevel.base, .elevated] {
let traits = UITraitCollection(traitsFrom: [
UITraitCollection(userInterfaceStyle: style),
UITraitCollection(userInterfaceLevel: level)
])
for (language, text) in samples {
let editor = SyntaxCodeEditor(text: .constant(text), language: language)
let view = UITextView()
view.overrideUserInterfaceStyle = style
view.text = text
editor.makeCoordinator().highlight(view)
let background = UIColor.secondarySystemGroupedBackground.resolvedColor(with: traits)
view.textStorage.enumerateAttribute(.foregroundColor, in: NSRange(location: 0, length: view.textStorage.length)) { color, _, _ in
guard let color = color as? UIColor else { return XCTFail("Missing syntax color") }
let foreground = color.resolvedColor(with: traits)
let values = [luminance(foreground), luminance(background)].sorted()
XCTAssertGreaterThanOrEqual((values[1] + 0.05) / (values[0] + 0.05), 4.5,
"Low contrast for \(language), \(style), \(level)")
}
}
}
}
}
private func luminance(_ color: UIColor) -> Double {
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
XCTAssertTrue(color.getRed(&r, green: &g, blue: &b, alpha: &a))
func linear(_ value: CGFloat) -> Double {
let value = Double(value)
return value <= 0.04045 ? value / 12.92 : pow((value + 0.055) / 1.055, 2.4)
}
return 0.2126 * linear(r) + 0.7152 * linear(g) + 0.0722 * linear(b)
}
}