Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
409
apps/ios/Tests/OpenClawTypographyTests.swift
Normal file
409
apps/ios/Tests/OpenClawTypographyTests.swift
Normal file
@@ -0,0 +1,409 @@
|
||||
import CoreText
|
||||
import Foundation
|
||||
import Testing
|
||||
import UIKit
|
||||
@testable import OpenClaw
|
||||
|
||||
struct OpenClawTypographyTests {
|
||||
@Test func `bundled fonts load from app bundle`() {
|
||||
for name in OpenClawType.registeredPostScriptNames {
|
||||
#expect(UIFont(name: name, size: 12) != nil, "Missing bundled font: \(name)")
|
||||
}
|
||||
}
|
||||
|
||||
@Test func `dynamic type scales display fonts`() {
|
||||
guard let base = UIFont(name: "RedHatDisplay-Regular", size: 34) else {
|
||||
Issue.record("RedHatDisplay-Regular should be bundled")
|
||||
return
|
||||
}
|
||||
|
||||
let defaultTraits = UITraitCollection(preferredContentSizeCategory: .large)
|
||||
let largeTraits = UITraitCollection(preferredContentSizeCategory: .accessibilityExtraExtraExtraLarge)
|
||||
let metrics = UIFontMetrics(forTextStyle: .largeTitle)
|
||||
|
||||
let defaultSize = metrics.scaledFont(for: base, compatibleWith: defaultTraits).pointSize
|
||||
let largeSize = metrics.scaledFont(for: base, compatibleWith: largeTraits).pointSize
|
||||
|
||||
#expect(largeSize > defaultSize)
|
||||
}
|
||||
|
||||
@Test func `display variable weight axis instantiates heavy weights`() {
|
||||
guard UIFont(name: "RedHatDisplay-Regular", size: 15) != nil else {
|
||||
Issue.record("RedHatDisplay-Regular should be bundled")
|
||||
return
|
||||
}
|
||||
|
||||
let weightAxis = NSNumber(value: 2_003_265_652) // "wght"
|
||||
let descriptor = UIFontDescriptor(fontAttributes: [
|
||||
.name: "RedHatDisplay-Regular",
|
||||
kCTFontVariationAttribute as UIFontDescriptor.AttributeName: [weightAxis: 900],
|
||||
])
|
||||
let font = UIFont(descriptor: descriptor, size: 15)
|
||||
let variations = font.fontDescriptor.object(
|
||||
forKey: kCTFontVariationAttribute as UIFontDescriptor.AttributeName) as? [NSNumber: Any]
|
||||
let weightValue = variations?[weightAxis] as? NSNumber
|
||||
|
||||
#expect(weightValue?.doubleValue == 900)
|
||||
}
|
||||
|
||||
@Test func `app extensions register bundled branded fonts`() throws {
|
||||
let project = try String(contentsOf: Self.projectYmlURL(), encoding: .utf8)
|
||||
let activityPlist = try String(contentsOf: Self.activityWidgetInfoPlistURL(), encoding: .utf8)
|
||||
let watchPlist = try String(contentsOf: Self.watchInfoPlistURL(), encoding: .utf8)
|
||||
|
||||
for targetName in ["OpenClawActivityWidget", "OpenClawWatchApp"] {
|
||||
let target = try Self.extract(
|
||||
project,
|
||||
from: " \(targetName):",
|
||||
to: targetName == "OpenClawActivityWidget" ? " OpenClawWatchApp:" : " OpenClawTests:")
|
||||
#expect(target.contains("- path: Sources/Fonts"))
|
||||
#expect(target.contains("UIAppFonts:"))
|
||||
for font in Self.bundledFontFiles {
|
||||
#expect(target.contains("- \(font)"))
|
||||
}
|
||||
}
|
||||
|
||||
for plist in [activityPlist, watchPlist] {
|
||||
#expect(plist.contains("<key>UIAppFonts</key>"))
|
||||
for font in Self.bundledFontFiles {
|
||||
#expect(plist.contains("<string>\(font)</string>"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test func `extension text surfaces use branded typography helpers`() throws {
|
||||
let activityTypeSource = try String(contentsOf: Self.activityWidgetTypographySourceURL(), encoding: .utf8)
|
||||
let activitySource = try String(contentsOf: Self.activityWidgetSourceURL(), encoding: .utf8)
|
||||
let watchTypeSource = try String(contentsOf: Self.watchTypographySourceURL(), encoding: .utf8)
|
||||
let watchSource = try String(contentsOf: Self.watchInboxSourceURL(), encoding: .utf8)
|
||||
|
||||
#expect(activityTypeSource.contains("relativeTo: .subheadline"))
|
||||
#expect(activityTypeSource.contains("relativeTo: .caption"))
|
||||
#expect(!activityTypeSource.contains(".custom(\"RedHatDisplay-Regular\", size: size).weight"))
|
||||
#expect(!activityTypeSource.contains(".custom(\"Inter-Regular\", size: size)"))
|
||||
#expect(activitySource.contains("OpenClawActivityType.subheadSemiBold"))
|
||||
#expect(activitySource.contains("OpenClawActivityType.subheadBold"))
|
||||
#expect(activitySource.contains("OpenClawActivityType.caption"))
|
||||
#expect(!activitySource.contains(".font(.subheadline"))
|
||||
#expect(!activitySource.contains(".font(.caption"))
|
||||
|
||||
#expect(watchTypeSource.contains("relativeTo textStyle: Font.TextStyle"))
|
||||
#expect(watchTypeSource.contains("body(size: 12, weight: .semibold, relativeTo: .caption)"))
|
||||
#expect(watchTypeSource.contains("body(size: 12, weight: .bold, relativeTo: .caption)"))
|
||||
#expect(watchTypeSource.contains("body(size: 11, relativeTo: .caption2)"))
|
||||
#expect(watchTypeSource.contains("relativeTo: .caption2"))
|
||||
#expect(watchTypeSource.contains("relativeTo: .headline"))
|
||||
#expect(!watchTypeSource.contains(".custom(\"RedHatDisplay-Regular\", size: size).weight"))
|
||||
#expect(!watchTypeSource.contains(".custom(\"Inter-Regular\", size: size).weight"))
|
||||
#expect(watchSource.contains("WatchClawType.title"))
|
||||
#expect(watchSource.contains("WatchClawType.body"))
|
||||
#expect(watchSource.contains("WatchClawType.caption"))
|
||||
#expect(!watchSource.contains(".font(.system"))
|
||||
#expect(!watchSource.contains(".font(.caption"))
|
||||
#expect(!watchSource.contains(".font(.title"))
|
||||
}
|
||||
|
||||
@Test func `UIKit typography refreshes when Dynamic Type changes`() throws {
|
||||
let appSource = try String(contentsOf: Self.appSourceURL(), encoding: .utf8)
|
||||
let typographySource = try String(
|
||||
contentsOf: Self.sourceURL("Design/OpenClawTypography.swift"),
|
||||
encoding: .utf8)
|
||||
|
||||
#expect(appSource.contains("UIContentSizeCategory.didChangeNotification"))
|
||||
#expect(appSource.contains("OpenClawType.refreshUIKitAppearance(in: Self.connectedWindows())"))
|
||||
#expect(typographySource.contains("static func refreshUIKitAppearance(in windows: [UIWindow])"))
|
||||
#expect(typographySource.contains("applyUIKitTypography(fonts, to: window)"))
|
||||
#expect(typographySource.contains("maximumPointSize: 13"))
|
||||
#expect(typographySource.contains("maximumPointSize: 16"))
|
||||
#expect(typographySource.contains("maximumPointSize: 22"))
|
||||
#expect(typographySource.contains("maximumPointSize: 44"))
|
||||
#expect(typographySource.contains("scaledFont(for: base, maximumPointSize: maximumPointSize)"))
|
||||
#expect(typographySource.contains("case let searchTextField as UISearchTextField"))
|
||||
#expect(!typographySource.contains("case let textField as UITextField"))
|
||||
#expect(!typographySource.contains("case let textView as UITextView"))
|
||||
}
|
||||
|
||||
@Test func `listed iOS app surfaces enforce branded control typography`() throws {
|
||||
let proComponents = try String(
|
||||
contentsOf: Self.sourceURL("Design/OpenClawProComponents.swift"),
|
||||
encoding: .utf8)
|
||||
let quickSetup = try String(contentsOf: Self.sourceURL("Gateway/GatewayQuickSetupSheet.swift"), encoding: .utf8)
|
||||
let gatewayProblem = try String(contentsOf: Self.sourceURL("Gateway/GatewayProblemView.swift"), encoding: .utf8)
|
||||
let onboardingSteps = try String(
|
||||
contentsOf: Self.sourceURL("Onboarding/OnboardingWizardSteps.swift"),
|
||||
encoding: .utf8)
|
||||
let onboardingWizard = try String(
|
||||
contentsOf: Self.sourceURL("Onboarding/OnboardingWizardView.swift"),
|
||||
encoding: .utf8)
|
||||
let settingsSections = try String(
|
||||
contentsOf: Self.sourceURL("Design/SettingsProTabSections.swift"),
|
||||
encoding: .utf8)
|
||||
let privacyAccess = try String(
|
||||
contentsOf: Self.sourceURL("Settings/PrivacyAccessSectionView.swift"),
|
||||
encoding: .utf8)
|
||||
let skillWorkshop = try String(
|
||||
contentsOf: Self.sourceURL("Design/IPadSkillWorkshopScreen.swift"),
|
||||
encoding: .utf8)
|
||||
let agentDestinations = try String(
|
||||
contentsOf: Self.sourceURL("Design/AgentProTab+Destinations.swift"),
|
||||
encoding: .utf8)
|
||||
let dreaming = try String(
|
||||
contentsOf: Self.sourceURL("Design/AgentProDreamingDestination.swift"),
|
||||
encoding: .utf8)
|
||||
let instances = try String(contentsOf: Self.sourceURL("Design/AgentProNodesDestination.swift"), encoding: .utf8)
|
||||
let channels = try String(
|
||||
contentsOf: Self.sourceURL("Design/SettingsChannelsDestination.swift"),
|
||||
encoding: .utf8)
|
||||
let docs = try String(contentsOf: Self.sourceURL("Design/OpenClawDocsScreen.swift"), encoding: .utf8)
|
||||
let chatTypography = try String(
|
||||
contentsOf: Self.iosRootURL()
|
||||
.deletingLastPathComponent()
|
||||
.appendingPathComponent("shared/OpenClawKit/Sources/OpenClawChatUI/ChatTypography.swift"),
|
||||
encoding: .utf8)
|
||||
let chatMessageViews = try String(
|
||||
contentsOf: Self.iosRootURL()
|
||||
.deletingLastPathComponent()
|
||||
.appendingPathComponent("shared/OpenClawKit/Sources/OpenClawChatUI/ChatMessageViews.swift"),
|
||||
encoding: .utf8)
|
||||
|
||||
#expect(proComponents.contains(".font(OpenClawType.subheadSemiBold)"))
|
||||
#expect(proComponents.contains("Text(primaryActionTitle)"))
|
||||
#expect(proComponents.contains("Text(secondaryActionTitle)"))
|
||||
#expect(proComponents.contains("@Environment(\\.isEnabled)"))
|
||||
#expect(proComponents.contains(".animation(.easeOut(duration: 0.15), value: self.isEnabled)"))
|
||||
|
||||
#expect(!quickSetup.contains("Button(\"Close\")"))
|
||||
#expect(quickSetup.contains("Text(\"Quick Setup\")"))
|
||||
#expect(quickSetup.contains("Text(\"Close\")"))
|
||||
#expect(quickSetup.contains(".font(OpenClawType.subheadSemiBold)"))
|
||||
|
||||
#expect(gatewayProblem.contains("Text(\"Connection problem\")"))
|
||||
#expect(gatewayProblem.contains("Text(\"Copy request ID\")"))
|
||||
#expect(gatewayProblem.contains("Text(\"Copy command\")"))
|
||||
#expect(gatewayProblem.contains(".font(OpenClawType.subheadSemiBold)"))
|
||||
|
||||
#expect(onboardingSteps.contains("Text(\"Connect Gateway\")"))
|
||||
#expect(onboardingSteps.contains("Label(\"Scan QR Code\", systemImage: \"qrcode\")"))
|
||||
#expect(onboardingSteps.contains("Text(\"Set Up Manually\")"))
|
||||
#expect(onboardingSteps.contains(".font(OpenClawType.headline)"))
|
||||
|
||||
#expect(onboardingWizard.contains("Text(\"Scan QR Code\")"))
|
||||
#expect(onboardingWizard.contains("Label(\"Resume After Approval\", systemImage: \"arrow.clockwise\")"))
|
||||
#expect(onboardingWizard.contains("Label(\"Scan QR Code Again\", systemImage: \"qrcode.viewfinder\")"))
|
||||
#expect(onboardingWizard.contains("Text(\"Apply Setup Code\")"))
|
||||
#expect(onboardingWizard.contains(".font(OpenClawType.subheadSemiBold)"))
|
||||
|
||||
#expect(settingsSections.contains(".font(OpenClawType.body)"))
|
||||
#expect(settingsSections.contains("Toggle(isOn: self.$talkButtonEnabled)"))
|
||||
#expect(settingsSections.contains("Text(\"Show Talk Control\")"))
|
||||
#expect(settingsSections.contains("TextField(\"Default Share Instruction\""))
|
||||
#expect(settingsSections.contains(".font(OpenClawType.subhead)"))
|
||||
#expect(settingsSections.contains("private struct AppearanceSettingsScreen"))
|
||||
#expect(settingsSections.contains("Section(\"Gateway\")"))
|
||||
#expect(settingsSections.contains("LabeledContent(\"Address\", value: self.gatewayAddress)"))
|
||||
#expect(settingsSections.contains("func gatewayActionButton"))
|
||||
#expect(settingsSections.contains("func settingsToggle"))
|
||||
#expect(settingsSections.contains(".font(OpenClawType.subheadSemiBold)"))
|
||||
#expect(settingsSections.contains("Text(\"Use Manual Gateway\")")
|
||||
|| settingsSections.contains("\"Use Manual Gateway\""))
|
||||
#expect(settingsSections.contains("func gatewaySecureField"))
|
||||
#expect(settingsSections.contains("self.gatewaySecureField(\"Gateway Auth Token\""))
|
||||
#expect(settingsSections.contains("self.gatewaySecureField(\"Gateway Password\""))
|
||||
let gatewaySecureField = try Self.extract(
|
||||
settingsSections,
|
||||
from: "func gatewaySecureField",
|
||||
to: " var voiceFeatureCard")
|
||||
#expect(gatewaySecureField.contains(".accessibilityLabel(placeholder)"))
|
||||
#expect(gatewaySecureField.contains(".accessibilityHidden(true)"))
|
||||
#expect(gatewaySecureField.contains(".textInputAutocapitalization(.never)"))
|
||||
#expect(gatewaySecureField.contains(".autocorrectionDisabled()"))
|
||||
#expect(settingsSections.contains("Picker(\"Default Agent\", selection: self.$selectedAgentPickerId)"))
|
||||
#expect(settingsSections.contains("Text(\"Default\")"))
|
||||
|
||||
#expect(!privacyAccess.contains("DisclosureGroup(\"Privacy & Access\")"))
|
||||
#expect(privacyAccess.contains("Text(\"Privacy & Access\")"))
|
||||
#expect(privacyAccess.contains("Text(actionTitle)"))
|
||||
#expect(privacyAccess.contains(".font(OpenClawType.footnoteSemiBold)"))
|
||||
|
||||
#expect(!skillWorkshop.contains("Button(\"Done\")"))
|
||||
#expect(skillWorkshop.contains("Label(\"Refresh\", systemImage: \"arrow.clockwise\")"))
|
||||
#expect(skillWorkshop.contains("Text(\"Default agent\")"))
|
||||
#expect(skillWorkshop.contains("Text(\"Inspect\")"))
|
||||
#expect(skillWorkshop.contains("Text(\"Apply\")"))
|
||||
#expect(skillWorkshop.contains("Text(\"Reject\")"))
|
||||
|
||||
for source in [agentDestinations, dreaming, instances, channels, docs] {
|
||||
#expect(source.contains(".font(OpenClawType.body)"))
|
||||
}
|
||||
|
||||
#expect(chatMessageViews.contains("font: OpenClawChatTypography.body"))
|
||||
#expect(chatMessageViews.contains("OpenClawChatTypography.callout.italic()"))
|
||||
#expect(!chatMessageViews.contains("font: .body"))
|
||||
#expect(!chatMessageViews.contains("Font.body"))
|
||||
#expect(!chatMessageViews.contains("Font.callout"))
|
||||
#expect(chatTypography
|
||||
.contains("Font.custom(self.macSystemFontName(size: size), size: size, relativeTo: textStyle)"))
|
||||
#expect(chatTypography.contains(
|
||||
"Font.custom(self.macMonospacedSystemFontName(size: size), size: size, relativeTo: textStyle)"))
|
||||
#expect(!chatTypography.contains("Font.system(textStyle, design: .default)"))
|
||||
#expect(!chatTypography.contains("Font.system(textStyle, design: .monospaced)"))
|
||||
}
|
||||
|
||||
@Test func `iOS app text and control calls keep branded font boundaries`() throws {
|
||||
let offenders = try Self.unbrandedTextCallOffenders()
|
||||
#expect(offenders.isEmpty, Comment(rawValue: offenders.joined(separator: "\n")))
|
||||
}
|
||||
|
||||
@Test func `secure fields do not use platform placeholder text`() throws {
|
||||
let offenders = try Self.swiftSourcesForTypographyAudit().flatMap { url -> [String] in
|
||||
let source = try String(contentsOf: url, encoding: .utf8)
|
||||
return source
|
||||
.components(separatedBy: .newlines)
|
||||
.enumerated()
|
||||
.compactMap { offset, line in
|
||||
guard line.range(
|
||||
of: #"\bSecureField\("[^"]+""#,
|
||||
options: .regularExpression) != nil
|
||||
else { return nil }
|
||||
return "\(Self.relativePath(url)):\(offset + 1): \(line.trimmingCharacters(in: .whitespaces))"
|
||||
}
|
||||
}
|
||||
#expect(offenders.isEmpty, Comment(rawValue: offenders.joined(separator: "\n")))
|
||||
}
|
||||
|
||||
private static let bundledFontFiles = [
|
||||
"RedHatDisplay[wght].ttf",
|
||||
"Inter[opsz,wght].ttf",
|
||||
"Inter-Italic[opsz,wght].ttf",
|
||||
"JetBrainsMono-Regular.ttf",
|
||||
"JetBrainsMono-Medium.ttf",
|
||||
"JetBrainsMono-SemiBold.ttf",
|
||||
]
|
||||
|
||||
private static func projectYmlURL() -> URL {
|
||||
self.iosRootURL().appendingPathComponent("project.yml")
|
||||
}
|
||||
|
||||
private static func activityWidgetInfoPlistURL() -> URL {
|
||||
self.iosRootURL().appendingPathComponent("ActivityWidget/Info.plist")
|
||||
}
|
||||
|
||||
private static func watchInfoPlistURL() -> URL {
|
||||
self.iosRootURL().appendingPathComponent("WatchApp/Info.plist")
|
||||
}
|
||||
|
||||
private static func activityWidgetSourceURL() -> URL {
|
||||
self.iosRootURL().appendingPathComponent("ActivityWidget/OpenClawLiveActivity.swift")
|
||||
}
|
||||
|
||||
private static func activityWidgetTypographySourceURL() -> URL {
|
||||
self.iosRootURL().appendingPathComponent("ActivityWidget/OpenClawActivityTypography.swift")
|
||||
}
|
||||
|
||||
private static func watchInboxSourceURL() -> URL {
|
||||
self.iosRootURL().appendingPathComponent("WatchApp/Sources/WatchInboxView.swift")
|
||||
}
|
||||
|
||||
private static func watchTypographySourceURL() -> URL {
|
||||
self.iosRootURL().appendingPathComponent("WatchApp/Sources/WatchClawTypography.swift")
|
||||
}
|
||||
|
||||
private static func appSourceURL() -> URL {
|
||||
self.sourceURL("OpenClawApp.swift")
|
||||
}
|
||||
|
||||
private static func sourceURL(_ relativePath: String) -> URL {
|
||||
self.iosRootURL().appendingPathComponent("Sources/\(relativePath)")
|
||||
}
|
||||
|
||||
private static func swiftSourcesForTypographyAudit() throws -> [URL] {
|
||||
let roots = [
|
||||
self.sourceURL(""),
|
||||
self.iosRootURL()
|
||||
.deletingLastPathComponent()
|
||||
.appendingPathComponent("shared/OpenClawKit/Sources/OpenClawChatUI"),
|
||||
]
|
||||
return roots.flatMap { root -> [URL] in
|
||||
guard let enumerator = FileManager.default.enumerator(
|
||||
at: root,
|
||||
includingPropertiesForKeys: nil)
|
||||
else { return [] }
|
||||
return enumerator.compactMap { item -> URL? in
|
||||
guard let url = item as? URL, url.pathExtension == "swift" else { return nil }
|
||||
return url
|
||||
}
|
||||
}
|
||||
.sorted { $0.path < $1.path }
|
||||
}
|
||||
|
||||
private static func unbrandedTextCallOffenders() throws -> [String] {
|
||||
let fontTokens = ["OpenClawType", "OpenClawChatTypography"]
|
||||
let allowedFragments = [".navigationTitle(", ".alert(\"", ".tabItem { Label("]
|
||||
return try self.swiftSourcesForTypographyAudit().flatMap { url -> [String] in
|
||||
let source = try String(contentsOf: url, encoding: .utf8)
|
||||
let lines = source.components(separatedBy: .newlines)
|
||||
return lines.indices.compactMap { idx -> String? in
|
||||
let rawLine = lines[idx]
|
||||
let line = rawLine.trimmingCharacters(in: .whitespaces)
|
||||
guard !line.hasPrefix("//") else { return nil }
|
||||
guard !allowedFragments.contains(where: rawLine.contains) else { return nil }
|
||||
|
||||
let window = lines[idx..<min(lines.count, idx + 12)].joined(separator: "\n")
|
||||
let hasLocalFont = fontTokens.contains { window.contains($0) }
|
||||
|| self.hasAllowedBrandedFontParameter(window, in: url)
|
||||
|
||||
if self.isTextOrLabelCall(rawLine), !hasLocalFont {
|
||||
return "\(self.relativePath(url)):\(idx + 1): \(line)"
|
||||
}
|
||||
|
||||
if self.isShorthandControlCall(rawLine), !hasLocalFont {
|
||||
return "\(self.relativePath(url)):\(idx + 1): \(line)"
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func isTextOrLabelCall(_ line: String) -> Bool {
|
||||
line.range(of: #"\b(Text|Label)\s*\("#, options: .regularExpression) != nil
|
||||
}
|
||||
|
||||
private static func isShorthandControlCall(_ line: String) -> Bool {
|
||||
line.range(
|
||||
of: #"\b(Button|Link|Picker|Toggle|TextField|SecureField|Menu|DisclosureGroup)\s*\(""#,
|
||||
options: .regularExpression) != nil
|
||||
}
|
||||
|
||||
private static func hasAllowedBrandedFontParameter(_ window: String, in url: URL) -> Bool {
|
||||
switch self.relativePath(url) {
|
||||
case "apps/ios/Sources/Design/OpenClawProComponents.swift":
|
||||
window.contains(".font(self.titleFont)") || window.contains(".font(self.subtitleFont)")
|
||||
case "apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatMarkdownRenderer.swift":
|
||||
window.contains(".font(self.font)")
|
||||
default:
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private static func relativePath(_ url: URL) -> String {
|
||||
let rootPath = self.iosRootURL()
|
||||
.deletingLastPathComponent()
|
||||
.deletingLastPathComponent()
|
||||
.path + "/"
|
||||
return url.path.hasPrefix(rootPath) ? String(url.path.dropFirst(rootPath.count)) : url.path
|
||||
}
|
||||
|
||||
private static func iosRootURL() -> URL {
|
||||
URL(fileURLWithPath: #filePath)
|
||||
.deletingLastPathComponent()
|
||||
.deletingLastPathComponent()
|
||||
}
|
||||
|
||||
private static func extract(_ source: String, from start: String, to end: String) throws -> String {
|
||||
let startRange = try #require(source.range(of: start))
|
||||
let tail = source[startRange.lowerBound...]
|
||||
let endRange = try #require(tail.range(of: end))
|
||||
return String(tail[..<endRange.lowerBound])
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user