From cd765c120073158eb19dfe9a37229fe205bc973c Mon Sep 17 00:00:00 2001 From: Kaede Akino Date: Fri, 18 Sep 2026 18:26:43 +0800 Subject: [PATCH] Fix Kownlege card & Metrics Tests --- .../AssistantCompactToolDetails.swift | 1 + .../MobileKnowledgeToolRenderingTests.swift | 53 ++++++++++++++++++- TFMTests/KnowledgeToolRenderingTests.swift | 16 +++++- TFMTests/ServerMetricsTests.swift | 10 +++- 4 files changed, 74 insertions(+), 6 deletions(-) diff --git a/Frameworks/TermindPresentation/AssistantCompactToolDetails.swift b/Frameworks/TermindPresentation/AssistantCompactToolDetails.swift index 44ccee99..33738263 100644 --- a/Frameworks/TermindPresentation/AssistantCompactToolDetails.swift +++ b/Frameworks/TermindPresentation/AssistantCompactToolDetails.swift @@ -28,6 +28,7 @@ public struct AssistantCompactToolDetails: View { contentHeight = $0 } .frame(maxHeight: expanded ? nil : previewHeight, alignment: .top) + .fixedSize(horizontal: false, vertical: true) .clipped() if expanded || preview.isShortened || contentHeight > previewHeight { HStack(spacing: 16) { diff --git a/TFITests/MobileKnowledgeToolRenderingTests.swift b/TFITests/MobileKnowledgeToolRenderingTests.swift index c6dc657a..1c29d5c4 100644 --- a/TFITests/MobileKnowledgeToolRenderingTests.swift +++ b/TFITests/MobileKnowledgeToolRenderingTests.swift @@ -31,18 +31,61 @@ struct MobileKnowledgeToolRenderingTests { if ProcessInfo.processInfo.environment["TERMIND_KNOWLEDGE_SCREENSHOTS"] == "1" { try snapshot(controller.view, name: "ios-\(language.rawValue)-\(card.descriptor.name)") } + let detail = try detail(for: card, language: language) + guard AgentToolDetailPreview(detail).isShortened else { + #expect(!state.isExpanded(.toolDetail(card.id))) + continue + } state.toggleDisclosure(.toolDetail(card.id)) try await Task.sleep(for: .milliseconds(20)) controller.view.setNeedsLayout() controller.view.layoutIfNeeded() let expanded = controller.sizeThatFits(in: size) - #expect(expanded.height >= compact.height) - if card.descriptor.name != "skill_install" { #expect(expanded.height > compact.height) } + #expect(expanded.height > compact.height, "\(language.rawValue): \(card.descriptor.name)") + controller.view.setNeedsLayout() + controller.view.layoutIfNeeded() + #expect(abs(controller.sizeThatFits(in: size).height - expanded.height) < 0.5) #expect(state.isExpanded(.toolDetail(card.id))) + state.toggleDisclosure(.toolDetail(card.id)) + try await Task.sleep(for: .milliseconds(20)) + controller.view.setNeedsLayout() + controller.view.layoutIfNeeded() + #expect(abs(controller.sizeThatFits(in: size).height - compact.height) < 0.5) + #expect(!state.isExpanded(.toolDetail(card.id))) } } } + @Test func shortKnowledgeDetailsKeepTheirNaturalHeight() throws { + for language in [AppLanguage.english, .simplifiedChinese, .traditionalChinese] { + var shortCards = 0 + for card in AssistantTranscriptPreview.knowledgeCards(language: language) { + let detail = try detail(for: card, language: language) + guard !AgentToolDetailPreview(detail).isShortened else { continue } + shortCards += 1 + let natural = UIHostingController( + rootView: + AssistantToolDetails(items: detail.items) + .frame(maxWidth: .infinity, alignment: .leading) + .fixedSize(horizontal: false, vertical: true) + .environment(\.locale, language.locale)) + let compact = UIHostingController( + rootView: + AssistantCompactToolDetails( + detail: detail, isError: false, expanded: .constant(false), copy: { _ in } + ) + .environment(\.locale, language.locale)) + let proposal = CGSize(width: 334, height: 30_000) + let naturalHeight = natural.sizeThatFits(in: proposal).height + #expect(naturalHeight > 0 && naturalHeight < 180) + #expect( + abs(compact.sizeThatFits(in: proposal).height - naturalHeight) < 0.5, + "\(language.rawValue): \(card.descriptor.name)") + } + #expect(shortCards > 0) + } + } + @Test func largerTextScalesThePreviewRatherThanHidingItsControls() { let detail = AgentToolDetail(items: [ .text(.init(text: String(repeating: "Readable memory\n", count: 100), style: .plain)) @@ -56,6 +99,12 @@ struct MobileKnowledgeToolRenderingTests { #expect(size.height < 800) } + private func detail(for card: AgentToolCard, language: AppLanguage) throws -> AgentToolDetail { + let presenter = try #require( + AgentToolCatalog.builtInPresenters.first { $0.id == card.descriptor.presentation?.presenterID }) + return try #require(AgentToolPresentation.detail(for: card, presenter: presenter, locale: language.locale)) + } + private func snapshot(_ view: UIView, name: String) throws { let image = UIGraphicsImageRenderer(bounds: view.bounds).image { context in view.layer.render(in: context.cgContext) diff --git a/TFMTests/KnowledgeToolRenderingTests.swift b/TFMTests/KnowledgeToolRenderingTests.swift index 1d948d99..f3523334 100644 --- a/TFMTests/KnowledgeToolRenderingTests.swift +++ b/TFMTests/KnowledgeToolRenderingTests.swift @@ -34,13 +34,25 @@ struct KnowledgeToolRenderingTests { try snapshot( row, width: width, height: compact, name: "mac-\(language.rawValue)-\(card.descriptor.name)") } + let presenter = try #require( + AgentToolCatalog.builtInPresenters.first { $0.id == card.descriptor.presentation?.presenterID }) + let detail = try #require( + AgentToolPresentation.detail(for: card, presenter: presenter, locale: language.locale)) + guard AgentToolDetailPreview(detail).isShortened else { + #expect(!state.disclosures.isExpanded(.toolDetail(card.id))) + continue + } state.disclosures.toggleDisclosure(.toolDetail(card.id)) configure() let expanded = row.measuredHeight(for: width) - #expect(expanded >= compact) - if card.descriptor.name != "skill_install" { #expect(expanded > compact) } + #expect(expanded > compact, "\(language.rawValue): \(card.descriptor.name)") configure() + #expect(abs(row.measuredHeight(for: width) - expanded) < 0.5) #expect(state.disclosures.isExpanded(.toolDetail(card.id))) + state.disclosures.toggleDisclosure(.toolDetail(card.id)) + configure() + #expect(abs(row.measuredHeight(for: width) - compact) < 0.5) + #expect(!state.disclosures.isExpanded(.toolDetail(card.id))) } } } diff --git a/TFMTests/ServerMetricsTests.swift b/TFMTests/ServerMetricsTests.swift index ed2b03d0..84af9d28 100644 --- a/TFMTests/ServerMetricsTests.swift +++ b/TFMTests/ServerMetricsTests.swift @@ -348,6 +348,9 @@ struct ServerMetricsLifecycleTests { #expect(fixture.model.servers.count == 1) #expect(fixture.model.snapshot(for: fixture.host.id)?.state == .live) #expect(await fixture.sampler.connections == 1) + // Base metrics appear before GPU collection finishes and the sampling sleep is registered. + // Both the maintenance loop and the sampler must be waiting before advancing this clock. + await eventually { fixture.time.pendingSleeps == 2 } let first = server.point?.date fixture.time.advance(2) await eventually { server.point?.date != first } @@ -534,13 +537,15 @@ struct ServerMetricsLifecycleTests { await eventually { await fixture.sampler.connections == 2 } } - private func eventually(_ condition: () async -> Bool) async { + private func eventually( + sourceLocation: SourceLocation = #_sourceLocation, _ condition: () async -> Bool + ) async { for _ in 0..<2000 { if await condition() { return } await Task.yield() try? await Task.sleep(for: .milliseconds(1)) } - #expect(await condition()) + #expect(await condition(), sourceLocation: sourceLocation) } private struct Fixture { @@ -659,6 +664,7 @@ private nonisolated final class ManualMetricsTime: @unchecked Sendable { private var waiters: [UUID: (Date, CheckedContinuation)] = [:] private var requested: [Double] = [] var delays: [Double] { lock.withLock { requested } } + var pendingSleeps: Int { lock.withLock { waiters.count } } var clock: MetricsClock { MetricsClock(now: { self.lock.withLock { self.date } }, sleep: { try await self.sleep($0) }) }