Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1190,3 +1190,15 @@ public final class NativeCalendarRootModel {
}
}
}

extension NativeCalendarRootModel {
/// Tests drive calendar-hint sync without mocking the remote list API.
internal func applyCalendarsForPresentationTesting(_ updated: [CompassCalendar]) {
calendars = updated
rebuildPresentation()
}

internal var testingVisibleRangeRefreshIsScheduled: Bool {
refreshTask != nil
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import CompassData
import CompassKit
import XCTest
@testable import CompassData

@MainActor
final class NativeCalendarRootModelCalendarHintSyncTests: XCTestCase {
func testCalendarHintChangeReanchorsWeekAndSchedulesRangeRefresh() async throws {
let priorDefault = NSTimeZone.default
defer { NSTimeZone.default = priorDefault }
NSTimeZone.default = try XCTUnwrap(TimeZone(identifier: "UTC"))

let referenceNow = try XCTUnwrap(
CompassDateParsing.parseGridDateTime(
"2026-11-12T00:23:00.000",
timeZoneIdentifier: "UTC"
)
)
let environment = try NativeCalendarEnvironment(inMemoryDatabase: true)
let model = NativeCalendarRootModel(environment: environment, now: { referenceNow })
model.viewStore.setCalendarTimeZoneHint("America/New_York")

let nycOnly = [
compassCalendar(id: "nyc", name: "NYC", timeZone: "America/New_York", isPrimary: true, isVisible: true),
]
model.applyCalendarsForPresentationTesting(nycOnly)
let anchorInNewYork = model.viewStore.anchorDate
XCTAssertEqual(EffectiveTimeZone.identifier, "America/New_York")

let hiddenDenverPrimary = [
compassCalendar(
id: "denver-primary", name: "Primary", timeZone: "America/Denver", isPrimary: true, isVisible: false
),
compassCalendar(id: "nyc", name: "NYC", timeZone: "America/New_York", isPrimary: false, isVisible: true),
]
model.applyCalendarsForPresentationTesting(hiddenDenverPrimary)

XCTAssertEqual(EffectiveTimeZone.identifier, "America/Denver")
XCTAssertNotEqual(model.viewStore.anchorDate, anchorInNewYork)
let expected = CalendarWindowMath.sundayWeekViewStart(containing: referenceNow)
XCTAssertEqual(model.viewStore.anchorDate, expected)
XCTAssertTrue(model.testingVisibleRangeRefreshIsScheduled)
}

func testHiddenPrimaryDenverWinsOverVisibleNewYorkOnFullList() async throws {
let environment = try NativeCalendarEnvironment(inMemoryDatabase: true)
let model = NativeCalendarRootModel(environment: environment)
model.applyCalendarsForPresentationTesting([
compassCalendar(
id: "denver-primary", name: "Primary", timeZone: "America/Denver", isPrimary: true, isVisible: false
),
compassCalendar(id: "nyc", name: "NYC", timeZone: "America/New_York", isPrimary: false, isVisible: true),
])
XCTAssertEqual(EffectiveTimeZone.identifier, "America/Denver")
}

private func compassCalendar(
id: String,
name: String,
timeZone: String,
isPrimary: Bool,
isVisible: Bool
) -> CompassCalendar {
let capabilities = CompassCalendarCapabilities(
canInviteAttendees: false,
canManage: false,
canReadAvailability: true,
canReadDetails: true,
canWatchEvents: false,
canWrite: true,
conferenceKinds: []
)
return CompassCalendar(
access: .owner,
accountEmail: "a@example.com",
backgroundColor: "#ffffff",
capabilities: capabilities,
conference: nil,
createsGoogleMeet: false,
description: "",
foregroundColor: "#000000",
id: id,
isActive: true,
isPrimary: isPrimary,
isVisible: isVisible,
name: name,
provider: "google",
timeZone: IANATimeZone(rawValue: timeZone)
)
}
}
74 changes: 74 additions & 0 deletions apps/calendar-web/src/calendars/calendar.query.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { type QueryFunctionContext } from "@tanstack/react-query";
import { TimeZoneSchema } from "@core/types/domain-primitives";
import { createMockCalendar } from "@web/__tests__/utils/factories/calendar.factory";
import { CalendarApi } from "@web/api/calendar.api";
import { queryClient } from "@web/api/query-client";
import {
calendarQueryKeys,
calendarsQueryOptions,
} from "@web/calendars/calendar.query";
import * as calendarTimeZoneHint from "@web/timezone/calendar-timezone-hint";
import {
resetEffectiveTimeZoneStoreForTests,
setBrowserTimeZoneForTests,
} from "@web/timezone/effective-timezone.store";
import { afterEach, describe, expect, it, spyOn } from "bun:test";

function calendarsQueryContext(): QueryFunctionContext<
typeof calendarQueryKeys.all
> {
return {
client: queryClient,
queryKey: calendarQueryKeys.all,
signal: new AbortController().signal,
meta: undefined,
};
}

describe("calendarsQueryOptions", () => {
afterEach(() => {
resetEffectiveTimeZoneStoreForTests();
});

it("syncs the calendar timezone hint when the remote list resolves", async () => {
const calendars = [
createMockCalendar({
name: "Work",
timeZone: TimeZoneSchema.parse("America/Denver"),
isPrimary: true,
}),
];
const list = spyOn(CalendarApi, "list").mockResolvedValue(calendars);
const sync = spyOn(
calendarTimeZoneHint,
"syncCalendarTimeZoneHintFromCalendars",
);

await calendarsQueryOptions(true).queryFn!(calendarsQueryContext());

expect(list).toHaveBeenCalledTimes(1);
expect(sync).toHaveBeenCalledTimes(1);
expect(sync).toHaveBeenCalledWith(calendars);

list.mockRestore();
sync.mockRestore();
});

it("syncs the calendar timezone hint for the anonymous sentinel path", async () => {
setBrowserTimeZoneForTests("UTC");
const sync = spyOn(
calendarTimeZoneHint,
"syncCalendarTimeZoneHintFromCalendars",
);

await calendarsQueryOptions(false).queryFn!(calendarsQueryContext());

expect(sync).toHaveBeenCalledTimes(1);
const synced = sync.mock.calls[0]?.[0];
expect(synced).toBeDefined();
expect(synced).toHaveLength(1);
expect(synced![0]!.isVisible).toBe(true);

sync.mockRestore();
});
});
8 changes: 8 additions & 0 deletions apps/calendar-web/src/routers/loaders.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,14 @@ describe("loadTodayData", () => {

expect(loadTodayData().dateString).toBe("2026-11-11");
});

it("uses the browser timezone for today when the calendars cache is empty", () => {
setBrowserTimeZoneForTests("America/Chicago");
setSystemTime(new Date("2026-06-01T18:00:00.000Z"));
queryClient.removeQueries({ queryKey: calendarQueryKeys.all });

expect(loadTodayData().dateString).toBe("2026-06-01");
});
});

describe("loadDateParam", () => {
Expand Down
Loading