Files
wehub-resource-sync 1d1286fadb
Build / Build and test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:18:38 +08:00

163 lines
5.7 KiB
Swift

import Carbon
import MacToolsPluginKit
import XCTest
@testable import TranslatorPlugin
@MainActor
final class TranslatorPluginTests: XCTestCase {
func testMetadataMatchesManifestContract() {
let plugin = makePlugin()
XCTAssertEqual(plugin.metadata.id, "translator")
XCTAssertEqual(plugin.metadata.title, "翻译")
XCTAssertEqual(plugin.metadata.defaultDescription, "划词与截图快捷键翻译")
XCTAssertNotNil(plugin.primaryPanel)
XCTAssertNotNil(plugin.configuration)
}
func testShortcutDefinitionsIncludeSelectAndScreenshotTranslation() {
let definitions = makePlugin().shortcutDefinitions
XCTAssertEqual(definitions.map(\.id), [
"translator.select-translation",
"translator.screenshot-translation",
])
XCTAssertEqual(definitions.map(\.actionID), [
"select-translation",
"screenshot-translation",
])
XCTAssertEqual(definitions.map(\.scope), [.global, .global])
XCTAssertEqual(definitions.first?.defaultBinding?.keyCode, UInt16(kVK_ANSI_D))
XCTAssertEqual(definitions.last?.defaultBinding?.keyCode, UInt16(kVK_ANSI_S))
}
func testDeclaresAccessibilityAutomationAndScreenRecordingPermissions() {
let requirements = makePlugin().permissionRequirements
XCTAssertEqual(requirements.map(\.id), ["accessibility", "automation", "screen-recording"])
XCTAssertEqual(requirements.map(\.kind), [.accessibility, .automation, .screenRecording])
}
func testPrimaryPanelReflectsPermissionAndSetupState() {
XCTAssertEqual(
makePlugin(accessibilityTrustProvider: { false }).primaryPanelState.subtitle,
"启用前需要辅助功能授权"
)
let storage = TranslatorInMemoryPluginStorage()
let plugin = makePlugin(storage: storage, secretStore: CountingTranslatorSecretStore(apiKey: nil))
let message = plugin.saveConfiguration(
OpenAICompatibleConfiguration(
baseURL: "https://gateway.example.com",
model: "gpt-test",
promptTemplate: "{{text}}"
),
apiKey: "",
languagePair: TranslatorLanguagePair(first: .english, second: .simplifiedChinese)
)
XCTAssertEqual(message, "API Key 不能为空。")
XCTAssertEqual(plugin.primaryPanelState.subtitle, "需要配置翻译服务")
}
func testSavingBlankAPIKeyPreservesExistingKey() {
let secretStore = CountingTranslatorSecretStore(apiKey: "sk-existing")
let plugin = makePlugin(secretStore: secretStore)
let message = plugin.saveConfiguration(
OpenAICompatibleConfiguration(
baseURL: "https://gateway.example.com",
model: "gpt-test",
promptTemplate: "{{text}}"
),
apiKey: "",
languagePair: TranslatorLanguagePair(first: .english, second: .simplifiedChinese)
)
XCTAssertNil(message)
XCTAssertEqual(secretStore.saveCount, 0)
XCTAssertEqual(plugin.primaryPanelState.subtitle, "按 ⌥D 划词,⌥S 截图")
}
func testPrimaryPanelTogglePersistsDisabledStateAndNotifies() {
let storage = TranslatorInMemoryPluginStorage()
let plugin = makePlugin(storage: storage)
var didNotify = false
plugin.onStateChange = { didNotify = true }
plugin.handleAction(.setSwitch(false))
XCTAssertEqual(storage.bool(forKey: "translator.shortcut.enabled"), false)
XCTAssertTrue(didNotify)
XCTAssertFalse(plugin.primaryPanelState.isOn)
}
private func makePlugin(
storage: TranslatorInMemoryPluginStorage? = nil,
accessibilityTrustProvider: @escaping () -> Bool = { true },
secretStore: (any TranslatorSecretStoring)? = nil
) -> TranslatorPlugin {
let storage = storage ?? TranslatorInMemoryPluginStorage()
return TranslatorPlugin(
context: PluginRuntimeContext(pluginID: "translator", storage: storage),
accessibilityTrustProvider: accessibilityTrustProvider,
accessibilityTrustRequester: { _ in true },
screenRecordingPermissionProvider: { true },
secretStore: secretStore ?? CountingTranslatorSecretStore(apiKey: "sk-test"),
panelController: RecordingTranslatorPanelController(),
selectedTextCapturePipeline: SelectedTextCapturePipeline(strategies: [])
)
}
}
private final class CountingTranslatorSecretStore: TranslatorSecretStoring, @unchecked Sendable {
private var apiKey: String?
private(set) var saveCount = 0
init(apiKey: String?) {
self.apiKey = apiKey
}
func containsAPIKey() throws -> Bool {
apiKey?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false
}
func containsAPIKey(forProfileID _: String) throws -> Bool {
try containsAPIKey()
}
func loadAPIKey() throws -> String? {
apiKey
}
func loadAPIKey(forProfileID _: String) throws -> String? {
apiKey
}
func saveAPIKey(_ apiKey: String) throws {
saveCount += 1
self.apiKey = apiKey
}
func saveAPIKey(_ apiKey: String, forProfileID _: String) throws {
try saveAPIKey(apiKey)
}
func deleteAPIKey() throws {
apiKey = nil
}
func deleteAPIKey(forProfileID _: String) throws {
try deleteAPIKey()
}
}
@MainActor
private final class RecordingTranslatorPanelController: TranslatorPanelControlling {
var onAction: ((TranslatorPanelAction) -> Void)?
func show(snapshot _: TranslatorPanelSnapshot) {}
func update(snapshot _: TranslatorPanelSnapshot) {}
func close() {}
}