Fix first click on detected update pill (#2117)

This commit is contained in:
Austin Wang 2026-03-25 00:51:34 -07:00 committed by GitHub
parent 57237d9faa
commit ffb660dee8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 77 additions and 3 deletions

View file

@ -5910,6 +5910,11 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
updateController.checkForUpdates()
}
func checkForUpdatesInCustomUI() {
updateViewModel.overrideState = nil
updateController.checkForUpdatesInCustomUI()
}
func openWelcomeWorkspace() {
guard let context = preferredMainWindowContextForWorkspaceCreation(event: nil, debugSource: "welcome") else {
return

View file

@ -208,6 +208,11 @@ class UpdateController {
checkForUpdatesWhenReady(retries: readyRetryCount)
}
/// Check for updates using the custom popover-based UI.
func checkForUpdatesInCustomUI() {
checkForUpdatesWhenReady(retries: readyRetryCount)
}
private func performCheckForUpdates() {
startUpdaterIfNeeded()
ensureSparkleInstallationCache()

View file

@ -28,8 +28,12 @@ struct UpdatePill: View {
private var pillButton: some View {
Button(action: {
if model.showsDetectedBackgroundUpdate {
showPopover = false
AppDelegate.shared?.checkForUpdates(nil)
if showPopover {
showPopover = false
} else {
showPopover = true
AppDelegate.shared?.checkForUpdatesInCustomUI()
}
return
}
if case .notFound(let notFound) = model.state {

View file

@ -11,7 +11,12 @@ struct UpdatePopoverView: View {
VStack(alignment: .leading, spacing: 0) {
switch model.effectiveState {
case .idle:
EmptyView()
if let detectedVersion = model.detectedUpdateVersion,
model.showsDetectedBackgroundUpdate {
DetectedBackgroundUpdateView(version: detectedVersion)
} else {
EmptyView()
}
case .permissionRequest(let request):
PermissionRequestView(request: request, dismiss: dismiss)
@ -42,6 +47,35 @@ struct UpdatePopoverView: View {
}
}
fileprivate struct DetectedBackgroundUpdateView: View {
let version: String
var body: some View {
VStack(alignment: .leading, spacing: 16) {
VStack(alignment: .leading, spacing: 8) {
Text(String(localized: "update.popover.updateAvailable", defaultValue: "Update Available"))
.font(.system(size: 13, weight: .semibold))
HStack(spacing: 6) {
Text(String(localized: "update.popover.version", defaultValue: "Version:"))
.foregroundColor(.secondary)
.frame(width: 60, alignment: .trailing)
Text(version)
}
.font(.system(size: 11))
}
HStack(spacing: 10) {
ProgressView()
.controlSize(.small)
Text(String(localized: "update.popover.checking", defaultValue: "Checking for updates…"))
.font(.system(size: 13))
}
}
.padding(16)
}
}
fileprivate struct PermissionRequestView: View {
let request: UpdateState.PermissionRequest
let dismiss: DismissAction

View file

@ -59,6 +59,32 @@ final class UpdatePillUITests: XCTestCase {
attachScreenshot(name: "background-detected-update-available")
}
func testDetectedBackgroundUpdateFirstClickOpensPopover() {
let systemSettings = XCUIApplication(bundleIdentifier: "com.apple.systempreferences")
systemSettings.terminate()
let app = XCUIApplication()
app.launchEnvironment["CMUX_UI_TEST_MODE"] = "1"
app.launchEnvironment["CMUX_UI_TEST_DETECTED_UPDATE_VERSION"] = "9.9.9"
app.launchEnvironment["CMUX_UI_TEST_FEED_URL"] = "https://cmux.test/appcast.xml"
app.launchEnvironment["CMUX_UI_TEST_FEED_MODE"] = "available"
app.launchEnvironment["CMUX_UI_TEST_UPDATE_VERSION"] = "9.9.9"
app.launchEnvironment["CMUX_UI_TEST_AUTO_ALLOW_PERMISSION"] = "1"
launchAndActivate(app)
let pill = pillButton(app: app, expectedLabel: "Update Available: 9.9.9")
XCTAssertTrue(pill.waitForExistence(timeout: 6.0))
assertVisibleSize(pill)
pill.click()
XCTAssertTrue(
app.staticTexts["Update Available"].waitForExistence(timeout: 8.0),
"Expected the first click on a background-detected update pill to open the popover"
)
XCTAssertTrue(app.buttons["Install and Relaunch"].waitForExistence(timeout: 2.0))
}
func testUpdatePillShowsForNoUpdateThenDismisses() {
let systemSettings = XCUIApplication(bundleIdentifier: "com.apple.systempreferences")
systemSettings.terminate()