Files
agent-pet-companion/apps/macos/Tests/AgentPetCompanionTests/SharedProductComponentsTests.swift
T
xjxandgd-ops 1807246b7e Add portable skill management and harden protected delivery (#10)
## What changed

- add App and PetCore portable Agent Pet Maker skill management, with
fd-relative ownership and race-safe install/uninstall
- improve Pi no-model failure projection, session bubble message
selection, child-session filtering, onboarding rendering, and related
App behavior
- delete verified PR source branches with exact leases after merge while
dispatching exact-main CI first
- document direct/train multi-agent delivery, explicit
add/commit/clean/push checks, post-merge worktree audits, and exact-SHA
release proof

## Validation

- full `cargo test -p petcore`
- Swift package suite: 734 tests
- Pi connector privacy tests
- promotion/release Python tests and actionlint
- `./script/validate_pre_push.sh`
- independent specification and safety reviews: no remaining blockers

This PR changes the protected CI control plane, so it is intended for
full CI followed by trusted exact-head squash merge.

---------

Co-authored-by: gd-ops <gd-ops@xmfunny.com>
2026-08-12 01:49:51 +08:00

727 lines
27 KiB
Swift

import AppKit
import Foundation
import SwiftUI
import Testing
@testable import AgentPetCompanion
@testable import AgentPetCompanionCore
@Suite("Shared control-center product components")
struct SharedProductComponentsTests {
@Test
func statusAppearanceCoversEveryLifecycleAndConnectionState() {
#expect(
ProductLifecycleState.allCases.map(ProductStatusAppearance.init(lifecycle:))
== [
.normal,
.checking,
.checking,
.attention,
.normal,
.error,
]
)
#expect(
AgentConnectionHealthState.allCases.map(
ProductStatusAppearance.init(connectionHealth:)
) == [.neutral, .checking, .normal, .attention, .error]
)
#expect(Set(ProductStatusAppearance.allCases) == [
.neutral,
.normal,
.attention,
.error,
.checking,
])
}
@Test
func standaloneSessionIconsCoverOnlyAttentionAndTerminalStates() {
let eventTypes: [AgentEventKind?] = [
nil, .start, .thinking, .plan, .tool, .waiting, .done, .failed,
]
#expect(eventTypes.map(SessionBubbleRowStateIndicator.init(eventType:)) == [
nil, nil, nil, nil, nil, .needsInput, .done, .failed,
])
#expect(SessionBubbleRowStateIndicator.needsInput.systemImage
== "exclamationmark.circle.fill")
#expect(SessionBubbleRowStateIndicator.done.systemImage
== "checkmark.circle.fill")
#expect(SessionBubbleRowStateIndicator.failed.systemImage
== "xmark.circle.fill")
}
@Test
func actionPresentationKeepsMutationAuthoritySemantic() {
let sharedDisplayCopy = "Continue"
let repair = ProductActionPresentation(
action: AgentConnectionPrimaryAction.repair,
title: sharedDisplayCopy
)
let verify = ProductActionPresentation(
action: AgentConnectionPrimaryAction.verify,
title: sharedDisplayCopy
)
#expect(repair.title == verify.title)
#expect(repair.action == .repair)
#expect(verify.action == .verify)
#expect(repair != verify)
let connectionActions: [AgentConnectionPrimaryAction] = [
.connect,
.repair,
.verify,
.retry,
.unavailable,
]
let libraryActions: [PetLibraryPrimaryAction] = [
.usePet,
.createPet,
.importPet,
.unavailable,
]
let makerActions: [PetMakerPrimaryAction] = [
.createPet,
.sendReply,
.cancel,
.retry,
.reselectReferences,
.usePet,
.continueEditing,
.unavailable,
]
let diagnosticsActions: [ServiceDiagnosticsPrimaryAction] = [
.refresh,
.recover,
.retry,
.unavailable,
]
#expect(
connectionActions.map {
ProductActionPresentation(action: $0, title: sharedDisplayCopy).action
} == connectionActions
)
#expect(
libraryActions.map {
ProductActionPresentation(action: $0, title: sharedDisplayCopy).action
} == libraryActions
)
#expect(
makerActions.map {
ProductActionPresentation(action: $0, title: sharedDisplayCopy).action
} == makerActions
)
#expect(
diagnosticsActions.map {
ProductActionPresentation(action: $0, title: sharedDisplayCopy).action
} == diagnosticsActions
)
}
@Test
func componentAccessibilityIdentifiersAreStableAndUnique() {
let page = ProductComponentIdentity(scope: "connections")
let componentIdentifiers = SharedProductComponentKind.allCases.map {
page.accessibilityIdentifier(for: $0)
}
#expect(Set(componentIdentifiers).count == SharedProductComponentKind.allCases.count)
#expect(
page.accessibilityIdentifier(for: .pageHeader)
== "product.connections.page-header"
)
let codex = ProductComponentIdentity(scope: "connections", instance: "codex")
let pi = ProductComponentIdentity(scope: "connections", instance: "pi")
#expect(
codex.accessibilityIdentifier(for: .agentHealthRow)
== "product.connections.codex.agent-health-row"
)
#expect(
codex.accessibilityIdentifier(for: .agentHealthRow)
!= pi.accessibilityIdentifier(for: .agentHealthRow)
)
#expect(
codex.accessibilityIdentifier(
for: .agentHealthRow,
suffix: "primary-action"
) != codex.accessibilityIdentifier(for: .agentHealthRow)
)
}
@Test
func attentionPresetOptionsKeepCustomDerivedAndNonSelectableByDefault() {
let options = AttentionPreset.allCases.map {
AttentionPresetOption(
preset: $0,
title: $0.rawValue,
detail: "detail"
)
}
#expect(options.map(\.preset) == AttentionPreset.allCases)
#expect(options.filter(\.isSelectable).map(\.preset) == [
.onlyWhenNeeded,
.standard,
.allActivity,
])
#expect(options.first(where: { $0.preset == .custom })?.isSelectable == false)
}
@Test
func sessionDetailSuppressesOnlyEmptyOrRepeatedCopy() {
#expect(
SharedProductComponentText.distinctDetail(
"Working",
comparedTo: ["Session", "working"]
) == nil
)
#expect(
SharedProductComponentText.distinctDetail(
" ",
comparedTo: ["Session", "Working"]
) == nil
)
#expect(
SharedProductComponentText.distinctDetail(
"Running the test suite",
comparedTo: ["Session", "Working"]
) == "Running the test suite"
)
}
@Test
func overlayUsesTheSingleProductionSessionBubbleRow() throws {
let sharedSource = try String(
contentsOf: sharedComponentsURL,
encoding: .utf8
)
let overlaySource = try String(
contentsOf: overlayRootViewURL,
encoding: .utf8
)
#expect(occurrences(
of: "struct SessionBubbleRow: View",
in: sharedSource
) == 1)
#expect(!sharedSource.contains("ControlCenterSessionBubbleRow"))
#expect(!sharedSource.contains("SharedProductComponents.SessionBubbleRow"))
#expect(!overlaySource.contains("struct SessionBubbleRow: View"))
#expect(overlaySource.contains("SessionBubbleRow(\n session: session,"))
#expect(overlaySource.contains("presentation: .standaloneSummary"))
#expect(overlaySource.contains("agentName: content.agentName"))
#expect(overlaySource.contains("if content.showsStackDecoration {"))
#expect(overlaySource.contains(
"Every represented session remains a complete native lens"
))
#expect(occurrences(
of: "ConversationBubbleSurfaceStyle(",
in: overlaySource
) == 2)
#expect(overlaySource.contains("statusTone: content.statusTone"))
#expect(overlaySource.contains(
"no material plate,"
))
#expect(occurrences(
of: "action: { performPrimarySessionAction(session) }",
in: overlaySource
) == 2)
#expect(overlaySource.contains(
"case .expandSessions:\n onToggleGroup()\n case .activateSession:\n onActivateSession(session)"
))
#expect(!overlaySource.contains("agentIndicatorSource:"))
#expect(!overlaySource.contains("agentIndicatorTitle:"))
#expect(sharedSource.contains("Button(action: action) {\n rowContent"))
#expect(sharedSource.contains("case standaloneSummary"))
#expect(sharedSource.contains("private var standaloneIdentityLine: some View"))
#expect(sharedSource.contains("if let agentName = resolvedStandaloneAgentName"))
#expect(sharedSource.contains("Text(\"· \\(surfaceLabel)\")"))
#expect(sharedSource.contains(
"case .standaloneSummary:\n // The card already owns one full glass surface."
))
#expect(sharedSource.contains("Text(\" · \")"))
#expect(sharedSource.contains(
"OverlayGeometry.bubbleStandaloneSummaryLineLimit,\n reservesSpace: true"
))
let standaloneStart = try #require(
sharedSource.range(of: "private var standaloneSummaryContent")
)
let standaloneEnd = try #require(
sharedSource.range(of: "private var standaloneSummaryText")
)
let standaloneSource = String(
sharedSource[standaloneStart.lowerBound ..< standaloneEnd.lowerBound]
)
#expect(!standaloneSource.contains("destinationIndicator"))
#expect(standaloneSource.contains(
".padding(.trailing, reservedTrailingAccessoryWidth)"
))
#expect(standaloneSource.contains("if !session.statusText.isEmpty"))
#expect(standaloneSource.contains("Text(session.statusText)"))
#expect(standaloneSource.contains("if let stateIndicator"))
#expect(standaloneSource.contains(".help(session.statusText)"))
#expect(standaloneSource.contains(
".accessibilityIdentifier(standaloneStatusIdentifier)"
))
#expect(sharedSource.contains(
"\"overlay.session.status.\\(stateIndicator?.rawValue ?? \"running\")\""
))
#expect(!sharedSource.contains("if session.canOpen {"))
#expect(sharedSource.contains(".focused($focused)"))
#expect(sharedSource.contains(
"Image(systemName: session.canOpen\n ? \"arrow.up.forward\"\n : \"exclamationmark.circle\")"
))
#expect(sharedSource.contains(".opacity(hovered || focused ? 1 : 0)"))
#expect(sharedSource.contains("if hovered || focused {"))
#expect(!sharedSource.contains("Text(session.actionLabel)"))
#expect(sharedSource.contains(".accessibilityIdentifier(\"overlay.session.\\(session.id)\")"))
#expect(sharedSource.contains(".accessibilityLabel(session.accessibilityLabel)"))
#expect(sharedSource.contains(".fill((statusColor ?? .clear).opacity(0.12))"))
#expect(sharedSource.contains("case .start, .thinking, .plan, .tool, nil:"))
#expect(sharedSource.contains("return nil"))
#expect(!sharedSource.contains("case .start, .tool: .blue"))
#expect(sharedSource.contains(
".lineLimit(OverlayGeometry.bubbleDetailLineLimit, reservesSpace: true)"
))
#expect(sharedSource.contains(
"openLabel: primaryActionLabel ?? session.actionLabel"
))
}
@Test
func controlCenterComponentsUseSolidSurfacesAndSecondaryRowActions() throws {
let source = try String(
contentsOf: sharedComponentsURL,
encoding: .utf8
)
#expect(!source.contains(".background(.thinMaterial"))
#expect(!source.contains(".background(.regularMaterial"))
#expect(source.contains(
".background(Color(nsColor: .textBackgroundColor), in: previewShape)"
))
#expect(source.contains(".background(APCDesign.panel, in: shape)"))
#expect(source.contains("ProductSecondaryActionButton("))
}
@Test
func advancedDetailsDisclosureMakesTheWholeHeaderInteractive() throws {
let source = try String(
contentsOf: sharedComponentsURL,
encoding: .utf8
)
let start = try #require(
source.range(of: "struct AdvancedDetailsDisclosure<Content: View>: View")
)
let end = try #require(
source.range(of: "struct EmptyStateAction<Action: Hashable>: View")
)
let disclosureSource = String(
source[start.lowerBound ..< end.lowerBound]
)
#expect(disclosureSource.contains("Button {"))
#expect(disclosureSource.contains("isExpanded.toggle()"))
#expect(disclosureSource.contains(
".buttonStyle(.plain)\n"
+ " .frame(maxWidth: .infinity, alignment: .leading)\n"
+ " .contentShape(Rectangle())"
))
#expect(disclosureSource.contains(".focused($isHeaderFocused)"))
#expect(disclosureSource.contains(".onHover { isHeaderHovered = $0 }"))
#expect(disclosureSource.contains(".accessibilityLabel(title)"))
}
@MainActor
@Test
func productionSessionRowBuildsExactHostAndUnavailableAccessibilityStates() {
func session(
id: String,
navigation: AgentSessionNavigation
) -> OverlaySessionContent {
OverlaySessionContent(
id: id,
source: .codex,
sessionID: id,
eventType: .waiting,
sessionTitle: "Session \(id)",
messageText: "Needs a response",
statusText: "Needs You",
navigation: navigation
)
}
let exact = session(
id: "exact",
navigation: AgentSessionNavigation(
capability: .exactSession,
sessionOpen: true,
surface: "cli_terminal",
terminalApp: "warp",
openURL: "warp://session/A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4"
)
)
let host = session(
id: "host",
navigation: AgentSessionNavigation(
capability: .agentHost,
sessionOpen: true,
surface: "chatgpt_app"
)
)
let unavailable = session(
id: "unavailable",
navigation: AgentSessionNavigation(
capability: .exactSession,
sessionOpen: true,
surface: "chatgpt_app",
routableSessionID: "malformed"
)
)
#expect(exact.navigationCapability == .exactSession)
#expect(exact.canOpen)
#expect(host.navigationCapability == .agentHost)
#expect(host.canOpen)
#expect(unavailable.navigationCapability == .unavailable)
#expect(!unavailable.canOpen)
for item in [exact, host, unavailable] {
#expect(item.accessibilityLabel
== item.accessibilityReadingOrder.joined(separator: ", "))
#expect(item.accessibilityReadingOrder.first == "Codex")
#expect(item.accessibilityReadingOrder.last == item.actionLabel)
let hostingView = NSHostingView(
rootView: SessionBubbleRow(
session: item,
action: {},
dismissAction: nil
)
.frame(width: 320)
)
#expect(hostingView.fittingSize.width <= 320.5)
#expect(hostingView.fittingSize.height > 0)
}
}
@MainActor
@Test(arguments: SharedProductCopyFixture.all)
func completeComponentSetBuildsAtMinimumWidth(
copy: SharedProductCopyFixture
) throws {
let width = SharedProductComponentLayout.supportedMinimumContentWidth
let root = SharedProductComponentFixture(copy: copy)
.frame(width: width)
.padding(1)
.environment(\.colorScheme, .light)
let hostingView = NSHostingView(rootView: root)
let fittingSize = hostingView.fittingSize
#expect(fittingSize.width <= width + 2.5)
#expect(fittingSize.height > 0)
#expect(fittingSize.height.isFinite)
hostingView.frame = CGRect(
origin: .zero,
size: CGSize(width: width + 2, height: ceil(fittingSize.height))
)
hostingView.layoutSubtreeIfNeeded()
let bitmap = try #require(
hostingView.bitmapImageRepForCachingDisplay(in: hostingView.bounds)
)
hostingView.cacheDisplay(in: hostingView.bounds, to: bitmap)
#expect(bitmap.pixelsWide > 0)
#expect(bitmap.pixelsHigh > 0)
#expect(hasVisibleInk(in: bitmap))
}
@MainActor
@Test(arguments: SharedProductCopyFixture.all)
func longRequiredActionCopyWrapsInsteadOfUsingSingleLineTruncation(
copy: SharedProductCopyFixture
) {
let availableWidth: CGFloat = 150
let shortHeight = fittingHeight(
of: ProductActionLabel(title: "Retry", systemImage: "arrow.clockwise"),
width: availableWidth
)
let longHeight = fittingHeight(
of: ProductActionLabel(
title: copy.primaryAction,
systemImage: "arrow.clockwise"
),
width: availableWidth
)
#expect(longHeight > shortHeight)
}
@MainActor
private func fittingHeight<Content: View>(
of view: Content,
width: CGFloat
) -> CGFloat {
NSHostingView(rootView: view.frame(width: width)).fittingSize.height
}
private func hasVisibleInk(in bitmap: NSBitmapImageRep) -> Bool {
let stride = max(1, min(bitmap.pixelsWide, bitmap.pixelsHigh) / 40)
for x in Swift.stride(from: 0, to: bitmap.pixelsWide, by: stride) {
for y in Swift.stride(from: 0, to: bitmap.pixelsHigh, by: stride) {
guard let color = bitmap.colorAt(x: x, y: y) else { continue }
if color.alphaComponent > 0.05 { return true }
}
}
return false
}
private func occurrences(of needle: String, in source: String) -> Int {
source.components(separatedBy: needle).count - 1
}
private var sharedComponentsURL: URL {
packageRootURL.appendingPathComponent(
"Sources/AgentPetCompanion/Views/SharedProductComponents.swift"
)
}
private var overlayRootViewURL: URL {
packageRootURL.appendingPathComponent(
"Sources/AgentPetCompanion/Overlay/OverlayRootView.swift"
)
}
private var packageRootURL: URL {
URL(fileURLWithPath: #filePath)
.deletingLastPathComponent()
.deletingLastPathComponent()
.deletingLastPathComponent()
}
}
struct SharedProductCopyFixture: CustomTestStringConvertible, Sendable {
let language: String
let pageTitle: String
let summary: String
let status: String
let detail: String
let primaryAction: String
let advancedTitle: String
let presets: [AttentionPresetOption]
var testDescription: String { language }
static let all = [english, simplifiedChinese]
static let english = SharedProductCopyFixture(
language: "English",
pageTitle: "Agent Connections",
summary: "Connect or repair an Agent integration with one clear action and quiet technical details.",
status: "Needs Repair",
detail: "Event delivery needs attention before the desktop pet can notify you.",
primaryAction: "Reconnect this coding Agent and verify event delivery",
advancedTitle: "Technical Details and Managed Integration Information",
presets: [
AttentionPresetOption(
preset: .onlyWhenNeeded,
title: "Only When I Am Needed",
detail: "Show waiting and failed messages."
),
AttentionPresetOption(
preset: .standard,
title: "Standard",
detail: "Show important progress and attention messages."
),
AttentionPresetOption(
preset: .allActivity,
title: "All Activity",
detail: "Show all eight persisted event types."
),
AttentionPresetOption(
preset: .custom,
title: "Custom",
detail: "Your advanced event choices are active."
),
]
)
static let simplifiedChinese = SharedProductCopyFixture(
language: "简体中文",
pageTitle: "Agent 连接",
summary: "通过一个清晰操作连接或修复 Agent 集成,并按需展开技术详情。",
status: "需要修复",
detail: "桌宠向你发送通知之前,需要先恢复 Agent 的消息事件传递。",
primaryAction: "重新连接这个编码 Agent 并验证消息事件是否能够正常送达",
advancedTitle: "技术详情与受管理的 Agent 集成信息",
presets: [
AttentionPresetOption(
preset: .onlyWhenNeeded,
title: "只在需要我时",
detail: "显示等待处理、可以查看和遇到问题的消息。"
),
AttentionPresetOption(
preset: .standard,
title: "标准",
detail: "显示重要进展和需要注意的消息。"
),
AttentionPresetOption(
preset: .allActivity,
title: "全部活动",
detail: "显示全部八种持久化消息事件。"
),
AttentionPresetOption(
preset: .custom,
title: "自定义",
detail: "当前正在使用你在高级设置中的事件选择。"
),
]
)
}
private struct SharedProductComponentFixture: View {
let copy: SharedProductCopyFixture
var body: some View {
VStack(alignment: .leading, spacing: SharedProductComponentLayout.pageSpacing) {
ProductPageHeader(
identity: ProductComponentIdentity(scope: "fixture"),
title: copy.pageTitle,
summary: copy.summary
)
PrimaryExperienceCard(
identity: ProductComponentIdentity(scope: "fixture"),
title: copy.pageTitle,
summary: copy.summary,
status: ProductStatusPresentation(
appearance: .attention,
title: copy.status,
detail: copy.detail
),
primaryAction: ProductActionPresentation(
action: AgentConnectionPrimaryAction.repair,
title: copy.primaryAction,
systemImage: "wrench.and.screwdriver"
),
onPrimaryAction: { _ in }
) {
Text(copy.detail)
.fixedSize(horizontal: false, vertical: true)
}
PetPreviewStage(
identity: ProductComponentIdentity(scope: "fixture"),
accessibilityLabel: copy.pageTitle
) {
Image(systemName: "pawprint.fill")
.resizable()
.scaledToFit()
.frame(width: 72, height: 72)
.foregroundStyle(.secondary)
}
ForEach(
Array(AgentConnectionHealthState.allCases.enumerated()),
id: \.offset
) { index, health in
AgentHealthRow(
identity: ProductComponentIdentity(
scope: "fixture",
instance: "agent-\(index)"
),
agentTitle: "Codex",
agentSummary: copy.detail,
health: health,
healthTitle: copy.status,
primaryAction: ProductActionPresentation(
action: AgentConnectionPrimaryAction.repair,
title: copy.primaryAction,
systemImage: "wrench.and.screwdriver"
),
onPrimaryAction: { _ in }
)
}
SessionBubbleRow(
session: OverlaySessionContent(
id: "fixture-session-alpha",
source: .codex,
sessionID: "fixture-session-alpha",
eventType: .tool,
sessionTitle: copy.pageTitle,
messageText: copy.detail,
statusText: copy.status,
navigation: AgentSessionNavigation(
capability: .agentHost,
sessionOpen: true,
surface: "chatgpt_app"
)
),
action: {},
dismissAction: nil
)
AttentionPresetPicker(
identity: ProductComponentIdentity(scope: "fixture"),
title: copy.pageTitle,
selection: .onlyWhenNeeded,
options: copy.presets,
onSelection: { _ in }
)
AdvancedDetailsDisclosure(
identity: ProductComponentIdentity(scope: "fixture"),
title: copy.advancedTitle,
summary: copy.summary,
isExpanded: .constant(true)
) {
Text(copy.detail)
.fixedSize(horizontal: false, vertical: true)
}
EmptyStateAction(
identity: ProductComponentIdentity(scope: "fixture"),
status: ProductStatusPresentation(
appearance: .normal,
title: copy.status
),
message: copy.summary,
primaryAction: ProductActionPresentation(
action: PetLibraryPrimaryAction.createPet,
title: copy.primaryAction,
systemImage: "sparkles"
),
onPrimaryAction: { _ in }
)
ForEach(
Array(ProductStatusAppearance.allCases.enumerated()),
id: \.offset
) { index, appearance in
InlineRecoveryBanner(
identity: ProductComponentIdentity(
scope: "fixture",
instance: "banner-\(index)"
),
status: ProductStatusPresentation(
appearance: appearance,
title: copy.status,
detail: copy.detail
),
primaryAction: ProductActionPresentation(
action: ServiceDiagnosticsPrimaryAction.retry,
title: copy.primaryAction,
systemImage: "arrow.clockwise"
),
onPrimaryAction: { _ in }
)
}
}
}
}