diff --git a/apps/calendar-macos/Compass/AppDelegate.swift b/apps/calendar-macos/Compass/AppDelegate.swift index 6eb7fd8e0c..1ea3968bd9 100644 --- a/apps/calendar-macos/Compass/AppDelegate.swift +++ b/apps/calendar-macos/Compass/AppDelegate.swift @@ -1,4 +1,5 @@ import AppKit +import CompassData import CompassKit import CompassUI @@ -55,9 +56,15 @@ final class AppDelegate: NSObject, NSApplicationDelegate { let window = makeMainWindow() self.window = window installRootContent(on: window) - // Installing the content controller resizes the window, so restore the saved frame afterwards. - window.setFrameUsingName(Self.windowFrameName) - window.setFrameAutosaveName(Self.windowFrameName) + if UITestLaunchPolicy.isParityCapture { + // Parity XCUITest must match web 1280×820; never restore a narrow autosaved frame. + window.setContentSize(NSSize(width: 1280, height: 820)) + window.center() + } else { + // Installing the content controller resizes the window, so restore the saved frame afterwards. + window.setFrameUsingName(Self.windowFrameName) + window.setFrameAutosaveName(Self.windowFrameName) + } if let launchDeepLink = UserDefaults.standard.string(forKey: "COMPASS_LAUNCH_DEEP_LINK") { nativeRootController?.receiveDeepLink(urlString: launchDeepLink) diff --git a/apps/calendar-macos/CompassData/Sources/CompassData/NativeCalendarRootModel.swift b/apps/calendar-macos/CompassData/Sources/CompassData/NativeCalendarRootModel.swift index 755f0715d0..b86eebf5de 100644 --- a/apps/calendar-macos/CompassData/Sources/CompassData/NativeCalendarRootModel.swift +++ b/apps/calendar-macos/CompassData/Sources/CompassData/NativeCalendarRootModel.swift @@ -357,11 +357,30 @@ public final class NativeCalendarRootModel { await reloadCalendars() await refreshVisibleRange() await refreshSideband() + applyParityCapturePresentationIfNeeded() if isSignedIn { startEventStream() } } + /// Match web parity captures: five day columns @ 1280×820 and demo-seed scroll position. + func applyParityCapturePresentationIfNeeded() { + guard UITestLaunchPolicy.isParityCapture else { return } + viewStore.view = .week + isSidebarOpen = true + sidebarWidth = CGFloat(ParityCaptureGeometry.defaultSidebarWidth) + contentTrackWidth = CGFloat(ParityCaptureGeometry.contentTrackWidth) + viewStore.setVisibleDayCount(ParityCaptureGeometry.visibleDayCount) + let today = referenceNow + let calendar = EffectiveTimeZone.calendar + let weekStart = calendar.dateInterval(of: .weekOfYear, for: today)?.start ?? today + viewStore.setAnchorDate(weekStart) + monthPickerMonth = today + pendingScroll = .scrollToNowOrPulse + rebuildPresentation() + scheduleRefreshVisibleRange() + } + public func dismissDemoEventsBanner() { try? DemoEventsBannerState.dismiss(repository: environment.userMetadataRepository) } @@ -480,6 +499,10 @@ public final class NativeCalendarRootModel { } public func updateContentTrackWidth(_ width: CGFloat) { + if UITestLaunchPolicy.isParityCapture { + applyParityCapturePresentationIfNeeded() + return + } let resolved = max(UITestLaunchPolicy.pinnedWeekGridTrackWidth ?? width, 320) if abs(resolved - contentTrackWidth) > 0.5 { contentTrackWidth = resolved diff --git a/apps/calendar-macos/CompassData/Sources/CompassData/UITestLaunchPolicy.swift b/apps/calendar-macos/CompassData/Sources/CompassData/UITestLaunchPolicy.swift index 115d8ac87f..a62ca8c52c 100644 --- a/apps/calendar-macos/CompassData/Sources/CompassData/UITestLaunchPolicy.swift +++ b/apps/calendar-macos/CompassData/Sources/CompassData/UITestLaunchPolicy.swift @@ -65,11 +65,16 @@ public enum UITestLaunchPolicy { arguments.contains("-COMPASS_UI_TEST_OPEN_FOCUSED_EVENT_FORM") } + /// Desktop ↔ web screenshot capture (`NativeParityCaptureTests`). + public static var isParityCapture: Bool { + ProcessInfo.processInfo.environment["PARITY_CAPTURE"] == "1" + || ProcessInfo.processInfo.arguments.contains("-COMPASS_UI_TEST_PARITY_CAPTURE") + || ProcessInfo.processInfo.arguments.contains("-PARITY_CAPTURE") + } + /// Full week column count so demo fixture events stay in the focus layout during XCUITest. public static var pinnedWeekGridTrackWidth: CGFloat? { - if ProcessInfo.processInfo.environment["PARITY_CAPTURE"] == "1" - || ProcessInfo.processInfo.arguments.contains("-COMPASS_UI_TEST_PARITY_CAPTURE") - { + if isParityCapture { return CGFloat(ParityCaptureGeometry.contentTrackWidth) } if initialGridFocusEventId != nil diff --git a/apps/calendar-macos/CompassUI/Sources/CompassUI/TimeGrid/TimeGridView.swift b/apps/calendar-macos/CompassUI/Sources/CompassUI/TimeGrid/TimeGridView.swift index 0d9c57d687..b52a8c04aa 100644 --- a/apps/calendar-macos/CompassUI/Sources/CompassUI/TimeGrid/TimeGridView.swift +++ b/apps/calendar-macos/CompassUI/Sources/CompassUI/TimeGrid/TimeGridView.swift @@ -430,6 +430,14 @@ public final class TimeGridView: NSView { header.frame = NSRect(x: column.left, y: 6, width: column.width, height: headerHeight - 8) header.alignment = .center header.setAccessibilityIdentifier(column.accessibilityIdentifier) + if let date = CompassDateParsing.calendarDateInEffectiveTimeZone(column.key) { + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.calendar = EffectiveTimeZone.calendar + formatter.timeZone = EffectiveTimeZone.timeZone + formatter.dateFormat = "d EEE" + header.setAccessibilityLabel(formatter.string(from: date)) + } headerRowView.addSubview(header) } } diff --git a/apps/calendar-macos/CompassUITests/NativeParityCaptureTests.swift b/apps/calendar-macos/CompassUITests/NativeParityCaptureTests.swift index d3b892e609..0075506e99 100644 --- a/apps/calendar-macos/CompassUITests/NativeParityCaptureTests.swift +++ b/apps/calendar-macos/CompassUITests/NativeParityCaptureTests.swift @@ -9,6 +9,7 @@ final class NativeParityCaptureTests: XCTestCase { "-COMPASS_FIXTURE", "demo", "-compass.theme", "light-beach", "-COMPASS_UI_TEST_PARITY_CAPTURE", + "-PARITY_CAPTURE", "-\(OnboardingStorageKeys.hasSeenWelcome)", "true", ] @@ -89,6 +90,7 @@ final class NativeParityCaptureTests: XCTestCase { private func launchDemoWindow() -> XCUIElement { let app = XCUIApplication.compassUITest() app.launchArguments += Self.demoLaunchArgs + app.launchEnvironment["PARITY_CAPTURE"] = "1" app.launch() let window = app.windows["Compass"] XCTAssertTrue(window.waitForExistence(timeout: 15)) @@ -102,8 +104,36 @@ final class NativeParityCaptureTests: XCTestCase { if okay.waitForExistence(timeout: 4) { okay.tap() } + let wednesdayHeader = window.staticTexts.matching( + NSPredicate(format: "label ENDSWITH[c] %@", " Wed") + ).firstMatch + XCTAssertTrue( + wednesdayHeader.waitForExistence(timeout: 20), + "Expected Wednesday column in parity week grid") + XCTAssertTrue( + waitForGridColumnCount(window, atLeast: 5, timeout: 20), + "Expected five day columns in parity week grid") + window.typeKey("t", modifierFlags: []) XCTAssertTrue( window.buttons["Morning standup"].waitForExistence(timeout: 20), "Expected demo-seed timed event on the week grid") + sleep(1) + } + + @MainActor + private func waitForGridColumnCount( + _ window: XCUIElement, + atLeast count: Int, + timeout: TimeInterval + ) -> Bool { + let columns = window.descendants(matching: .any).matching( + NSPredicate(format: "identifier BEGINSWITH %@", "compass-grid-column-") + ) + let deadline = Date().addingTimeInterval(timeout) + while Date() < deadline { + if columns.count >= count { return true } + RunLoop.current.run(until: Date().addingTimeInterval(0.15)) + } + return columns.count >= count } } diff --git a/e2e/parity/seed-demo-fixture.util.ts b/e2e/parity/seed-demo-fixture.util.ts index b08e24ef08..4ef5b1397e 100644 --- a/e2e/parity/seed-demo-fixture.util.ts +++ b/e2e/parity/seed-demo-fixture.util.ts @@ -65,6 +65,11 @@ export async function prepareParityDemoWeek(page: Page) { await seedParityDemoEvents(page); await page.reload({ waitUntil: "domcontentloaded" }); await expectDemoGrid(page); + await page.locator("#mainGrid").focus(); + await page.keyboard.press("t"); + await page + .getByRole("button", { name: /Exercise/i }) + .waitFor({ timeout: 15_000 }); } export async function expectDemoGrid(page: Page) {