diff --git a/packages/core/src/__mocks__/v1/events/events.22mar.ts b/packages/core/src/__mocks__/v1/events/events.22mar.ts index 8ab57c50a..f6a1d1519 100644 --- a/packages/core/src/__mocks__/v1/events/events.22mar.ts +++ b/packages/core/src/__mocks__/v1/events/events.22mar.ts @@ -1,8 +1,10 @@ -import { ObjectId, WithId } from "mongodb"; -import { Priorities } from "../../../constants/core.constants"; -import { Schema_Event } from "../../../types/event.types"; +import { ObjectId } from "bson"; +import { Priorities } from "@core/constants/core.constants"; +import { Schema_Event, WithMongoId } from "@core/types/event.types"; -const allDayEventsThatShouldMatch: Array>> = +const allDayEventsThatShouldMatch: Array< + WithMongoId> +> = // ordered by start date [ { @@ -43,49 +45,50 @@ const allDayEventsThatShouldMatch: Array>> = }, ]; -const allDayEventsThatShouldNotMatch: Array>> = - [ - { - _id: new ObjectId(), - user: "user1", - title: "Feb 28 - Mar 5", - isAllDay: true, - isSomeday: false, - startDate: "2022-02-28", - endDate: "2022-03-05", - priority: Priorities.WORK, - }, - { - _id: new ObjectId(), - user: "user1", - title: "Mar 5", - isAllDay: true, - isSomeday: false, - startDate: "2022-03-05", - endDate: "2022-03-06", - priority: Priorities.WORK, - }, - { - _id: new ObjectId(), - user: "user1", - title: "Mar 13", - isAllDay: true, - isSomeday: false, - startDate: "2022-03-13", - endDate: "2022-03-14", - priority: Priorities.WORK, - }, - { - _id: new ObjectId(), - user: "user1", - title: "Mar 13 - 16", - isAllDay: true, - isSomeday: false, - startDate: "2022-03-13", - endDate: "2022-03-17", - priority: Priorities.WORK, - }, - ]; +const allDayEventsThatShouldNotMatch: Array< + WithMongoId> +> = [ + { + _id: new ObjectId(), + user: "user1", + title: "Feb 28 - Mar 5", + isAllDay: true, + isSomeday: false, + startDate: "2022-02-28", + endDate: "2022-03-05", + priority: Priorities.WORK, + }, + { + _id: new ObjectId(), + user: "user1", + title: "Mar 5", + isAllDay: true, + isSomeday: false, + startDate: "2022-03-05", + endDate: "2022-03-06", + priority: Priorities.WORK, + }, + { + _id: new ObjectId(), + user: "user1", + title: "Mar 13", + isAllDay: true, + isSomeday: false, + startDate: "2022-03-13", + endDate: "2022-03-14", + priority: Priorities.WORK, + }, + { + _id: new ObjectId(), + user: "user1", + title: "Mar 13 - 16", + isAllDay: true, + isSomeday: false, + startDate: "2022-03-13", + endDate: "2022-03-17", + priority: Priorities.WORK, + }, +]; export const mockEventSetMar22: Array> = [ ...allDayEventsThatShouldMatch, diff --git a/packages/core/src/mappers/map.event.ts b/packages/core/src/mappers/map.event.ts index 2ab298635..31701c947 100644 --- a/packages/core/src/mappers/map.event.ts +++ b/packages/core/src/mappers/map.event.ts @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/no-namespace */ import mergeWith from "lodash.mergewith"; -import { WithId } from "mongodb"; import { Origin, Priorities } from "@core/constants/core.constants"; import { BaseError } from "@core/errors/errors.base"; import { @@ -11,6 +10,7 @@ import { Schema_Event_Recur_Instance, Schema_Event_Regular, WithCompassId, + WithMongoId, WithoutCompassId, } from "@core/types/event.types"; import { WithGcalId, gSchema$Event } from "@core/types/gcal"; @@ -37,9 +37,9 @@ export namespace MapEvent { }; export const removeProviderData = ( - event: WithId> | Schema_Event, + event: WithMongoId> | Schema_Event, ): Omit< - WithId> | Schema_Event, + WithMongoId> | Schema_Event, "gEventId" | "gRecurringEventId" > => { const { @@ -57,7 +57,7 @@ export namespace MapEvent { }; export const removeIdentifyingData = ( - event: WithId> | Schema_Event, + event: WithMongoId> | Schema_Event, ): Omit< Schema_Event, | "_id" @@ -128,7 +128,7 @@ export namespace MapEvent { export const toGcalSingleProviderData = ( base: - | WithId> + | WithMongoId> | WithCompassId< Omit >, @@ -140,11 +140,11 @@ export namespace MapEvent { export const toProviderData = ( event: - | WithId> + | WithMongoId> | WithCompassId>, provider?: CalendarProvider, base?: - | WithId> + | WithMongoId> | WithCompassId>, ) => { const isCInstance = isInstance(event); @@ -153,7 +153,7 @@ export namespace MapEvent { case CalendarProvider.GOOGLE: { return isCInstance ? MapEvent.toGcalInstanceProviderData( - event as WithId>, + event as WithMongoId>, base, ) : MapEvent.toGcalSingleProviderData(event); diff --git a/packages/core/src/types/mongo.types.ts b/packages/core/src/types/mongo.types.ts index 1f6e9060b..a21f5e548 100644 --- a/packages/core/src/types/mongo.types.ts +++ b/packages/core/src/types/mongo.types.ts @@ -1,3 +1,3 @@ -import { ObjectId } from "mongodb"; +import { ObjectId } from "bson"; export type InsertedIds = { [key: number]: ObjectId }; diff --git a/packages/core/src/types/sync.types.ts b/packages/core/src/types/sync.types.ts index 65bb08228..1761c6c18 100644 --- a/packages/core/src/types/sync.types.ts +++ b/packages/core/src/types/sync.types.ts @@ -1,4 +1,4 @@ -import { AnyBulkWriteOperation, BulkWriteResult } from "mongodb"; +import type { AnyBulkWriteOperation, BulkWriteResult } from "mongodb"; import { BaseError } from "@core/errors/errors.base"; export interface Params_Sync_Gcal extends Payload_Sync_Notif { diff --git a/packages/core/src/util/event/compass.event.rrule.test.ts b/packages/core/src/util/event/compass.event.rrule.test.ts index 87144071f..4a2f09996 100644 --- a/packages/core/src/util/event/compass.event.rrule.test.ts +++ b/packages/core/src/util/event/compass.event.rrule.test.ts @@ -1,4 +1,4 @@ -import { ObjectId, type WithId } from "mongodb"; +import { ObjectId } from "bson"; import { RRule } from "rrule"; import { faker } from "@faker-js/faker"; import { recurring } from "@core/__mocks__/v1/events/gcal/gcal.recurring"; @@ -7,6 +7,7 @@ import { gEventToCompassEvent } from "@core/mappers/map.event"; import { CalendarProvider, type Schema_Event_Recur_Base, + WithMongoId, } from "@core/types/event.types"; import dayjs from "@core/util/date/dayjs"; import { CompassEventRRule } from "@core/util/event/compass.event.rrule"; @@ -390,7 +391,7 @@ describe("CompassEventRRule: ", () => { return event; }); - const baseEvent = compassEvents.find(isBase) as WithId< + const baseEvent = compassEvents.find(isBase) as WithMongoId< Omit >; diff --git a/packages/core/src/util/event/compass.event.rrule.ts b/packages/core/src/util/event/compass.event.rrule.ts index a3af7be33..087a81ef5 100644 --- a/packages/core/src/util/event/compass.event.rrule.ts +++ b/packages/core/src/util/event/compass.event.rrule.ts @@ -1,5 +1,4 @@ import { ObjectId } from "bson"; -import type { WithId } from "mongodb"; import type { Options, RRuleStrOptions } from "rrule"; import { RRule, rrulestr } from "rrule"; import type { ParsedOptions } from "rrule/dist/esm/types"; @@ -9,6 +8,7 @@ import type { CalendarProvider, Schema_Event_Recur_Base, Schema_Event_Recur_Instance, + WithMongoId, } from "@core/types/event.types"; import dayjs from "@core/util/date/dayjs"; import { @@ -18,13 +18,13 @@ import { } from "@core/util/event/event.util"; export class CompassEventRRule extends RRule { - #event: WithId>; + #event: WithMongoId>; #dateFormat: string; #durationMs!: number; constructor( event: Pick< - WithId>, + WithMongoId>, "startDate" | "endDate" | "_id" | "recurrence" >, options: Partial = {}, @@ -41,7 +41,7 @@ export class CompassEventRRule extends RRule { } static #initOptions( - event: WithId>, + event: WithMongoId>, _options: Partial = {}, ): Partial { const startDate = parseCompassEventDate(event.startDate!); @@ -111,7 +111,7 @@ export class CompassEventRRule extends RRule { base( provider?: CalendarProvider, - ): WithId> { + ): WithMongoId> { const _id = this.#event._id ?? new ObjectId(); const recurrence = { rule: this.toRecurrence() }; const event = { ...this.#event, _id, recurrence }; @@ -129,7 +129,7 @@ export class CompassEventRRule extends RRule { */ instances( provider?: CalendarProvider, - ): WithId>[] { + ): WithMongoId>[] { const base = this.base(); const baseData = MapEvent.removeIdentifyingData(base); const baseEventId = base._id.toString(); diff --git a/packages/core/src/util/test/ccal.event.factory.ts b/packages/core/src/util/test/ccal.event.factory.ts index df8a0d6b4..90d175935 100644 --- a/packages/core/src/util/test/ccal.event.factory.ts +++ b/packages/core/src/util/test/ccal.event.factory.ts @@ -1,4 +1,4 @@ -import { ObjectId } from "mongodb"; +import { ObjectId } from "bson"; import { faker } from "@faker-js/faker"; import { Origin, Priorities } from "@core/constants/core.constants"; import { @@ -8,7 +8,7 @@ import { WithCompassId, } from "@core/types/event.types"; import dayjs from "@core/util/date/dayjs"; -import { isAllDay, parseCompassEventDate } from "../event/event.util"; +import { isAllDay, parseCompassEventDate } from "@core/util/event/event.util"; export const createMockStandaloneEvent = ( overrides: Partial = {}, diff --git a/packages/web/src/common/types/web.event.types.ts b/packages/web/src/common/types/web.event.types.ts index 56a28f853..5c0c79ef7 100644 --- a/packages/web/src/common/types/web.event.types.ts +++ b/packages/web/src/common/types/web.event.types.ts @@ -24,7 +24,7 @@ const WebEventRecurrence = z.union([ ]); const WebCoreEventSchema = CompassCoreEventSchema.extend({ - _id: z.union([idSchema, optimisticIdSchema]), + _id: z.union([idSchema, optimisticIdSchema]).optional(), recurrence: WebEventRecurrence, order: z.number().optional(), }); diff --git a/packages/web/src/common/utils/event.util.test.ts b/packages/web/src/common/utils/event.util.test.ts index 02d23596c..4d9ede881 100644 --- a/packages/web/src/common/utils/event.util.test.ts +++ b/packages/web/src/common/utils/event.util.test.ts @@ -1,4 +1,10 @@ -import { isEventInRange } from "./event.util"; +import { createMockStandaloneEvent } from "@core/util/test/ccal.event.factory"; +import { + Schema_GridEvent, + Schema_WebEvent, +} from "@web/common/types/web.event.types"; +import { isEventInRange } from "@web/common/utils/event.util"; +import { _assembleGridEvent } from "@web/ducks/events/sagas/saga.util"; describe("isEventInRange", () => { it("returns true if event is in range", () => { @@ -9,6 +15,7 @@ describe("isEventInRange", () => { }; expect(isEventInRange(event, dates)).toBe(true); }); + it("returns false if event is not in range", () => { const event = { start: "2022-03-15", end: "2022-03-15" }; const dates = { @@ -18,3 +25,31 @@ describe("isEventInRange", () => { expect(isEventInRange(event, dates)).toBe(false); }); }); + +describe("_assembleGridEvent", () => { + it("should successfully convert Someday event to Grid event by adding position field", () => { + // Create a mock Someday event (without position field) + const somedayEvent = createMockStandaloneEvent({ + isSomeday: true, + }) as Schema_WebEvent; + + const generator = _assembleGridEvent(somedayEvent); + + // First, it calls getEventById + const getEventStep = generator.next(); + expect(getEventStep.done).toBe(false); + + // Mock returning the Someday event + const validateStep = generator.next({ ...somedayEvent, isSomeday: false }); + + // This should now succeed because the fix adds the required position field + expect(validateStep.done).toBe(true); + const result = validateStep.value as Schema_GridEvent; + + // Verify that position field is now present + expect(result.position).toBeDefined(); + expect(result.position.isOverlapping).toBe(false); + expect(result.position.widthMultiplier).toBe(1); + expect(result.position.horizontalOrder).toBe(1); + }); +}); diff --git a/packages/web/src/common/utils/event.util.ts b/packages/web/src/common/utils/event.util.ts index a078c7477..4097ccb65 100644 --- a/packages/web/src/common/utils/event.util.ts +++ b/packages/web/src/common/utils/event.util.ts @@ -21,10 +21,10 @@ import { Schema_OptimisticEvent, Schema_WebEvent, } from "@web/common/types/web.event.types"; +import { validateGridEvent } from "@web/common/validators/grid.event.validator"; import { validateSomedayEvent } from "@web/common/validators/someday.event.validator"; -import { validateGridEvent } from "../validators/grid.event.validator"; -const gridEventDefaultPosition = { +export const gridEventDefaultPosition = { isOverlapping: false, widthMultiplier: 1, horizontalOrder: 1, @@ -87,7 +87,7 @@ export const assembleDefaultEvent = async ( } }; -export const assembleGridEvent = (event: Schema_Event): Schema_GridEvent => { +export const assembleGridEvent = (event: Schema_WebEvent): Schema_GridEvent => { const gridEvent: Schema_GridEvent = { ...event, position: gridEventDefaultPosition, @@ -97,7 +97,7 @@ export const assembleGridEvent = (event: Schema_Event): Schema_GridEvent => { origin: event.origin ?? Origin.COMPASS, priority: event.priority ?? Priorities.UNASSIGNED, user: event.user!, - recurrence: event.recurrence as Schema_Event_Recur_Base["recurrence"], + recurrence: event.recurrence, }; return gridEvent; @@ -203,13 +203,10 @@ export const prepEvtAfterDraftDrop = ( return event; }; -export const prepEvtBeforeSubmit = ( - draft: Schema_Event | Schema_GridEvent, - userId: string, -) => { - const _event: Omit = { +export const prepEvtBeforeSubmit = (draft: Schema_WebEvent, userId: string) => { + const _event = { ...draft, - origin: Origin.COMPASS, + origin: draft.origin ?? Origin.COMPASS, user: userId, }; diff --git a/packages/web/src/common/validators/grid.event.validator.ts b/packages/web/src/common/validators/grid.event.validator.ts index 1cf6e9497..6a54b620a 100644 --- a/packages/web/src/common/validators/grid.event.validator.ts +++ b/packages/web/src/common/validators/grid.event.validator.ts @@ -1,7 +1,10 @@ -import { Schema_Event } from "@core/types/event.types"; -import { GridEventSchema, Schema_GridEvent } from "../types/web.event.types"; +import { + GridEventSchema, + Schema_GridEvent, + Schema_WebEvent, +} from "@web/common/types/web.event.types"; -export const validateGridEvent = (event: Schema_Event): Schema_GridEvent => { +export const validateGridEvent = (event: Schema_WebEvent): Schema_GridEvent => { const result = GridEventSchema.parse(event) as Schema_GridEvent; return result; diff --git a/packages/web/src/ducks/events/sagas/saga.util.ts b/packages/web/src/ducks/events/sagas/saga.util.ts index cd59b99e2..550feae04 100644 --- a/packages/web/src/ducks/events/sagas/saga.util.ts +++ b/packages/web/src/ducks/events/sagas/saga.util.ts @@ -1,14 +1,20 @@ import dayjs from "dayjs"; import { normalize, schema } from "normalizr"; -import { call, put, select } from "redux-saga/effects"; +import { SelectEffect, call, put, select } from "redux-saga/effects"; import { ID_OPTIMISTIC_PREFIX } from "@core/constants/core.constants"; import { Params_Events, RecurringEventUpdateScope, Schema_Event, } from "@core/types/event.types"; -import { Schema_GridEvent } from "@web/common/types/web.event.types"; -import { replaceIdWithOptimisticId } from "@web/common/utils/event.util"; +import { + Schema_GridEvent, + Schema_WebEvent, +} from "@web/common/types/web.event.types"; +import { + assembleGridEvent, + replaceIdWithOptimisticId, +} from "@web/common/utils/event.util"; import { validateGridEvent } from "@web/common/validators/grid.event.validator"; import { EventApi } from "@web/ducks/events/event.api"; import { Payload_ConvertEvent } from "@web/ducks/events/event.types"; @@ -22,8 +28,8 @@ export function* getEventById( _id: string, ): Generator< ReturnType, - Schema_GridEvent | undefined, - Schema_GridEvent | undefined + Schema_GridEvent | Schema_WebEvent, + Schema_GridEvent | Schema_WebEvent > { const currEvent = yield select((state: RootState) => selectEventById(state, _id), @@ -36,7 +42,7 @@ export function* _editEvent( gridEvent: Schema_GridEvent, params: { applyTo?: RecurringEventUpdateScope } = {}, ) { - yield call(EventApi.edit, gridEvent._id as string, gridEvent, params); + yield call(EventApi.edit, gridEvent._id, gridEvent, params); } export function* insertOptimisticEvent( @@ -58,11 +64,22 @@ export function* insertOptimisticEvent( export function* _assembleGridEvent({ _id, ...updatedFields -}: Payload_ConvertEvent["event"]) { +}: Payload_ConvertEvent["event"]): Generator< + SelectEffect, + Schema_GridEvent, + Schema_WebEvent +> { const currEvent = yield* getEventById(_id); - const _gridEvent = { ...currEvent, ...updatedFields }; - const gridEvent = validateGridEvent(_gridEvent); + // First merge the current event with updated fields + const eventWithUpdates = { ...currEvent, ...updatedFields, _id }; + + // Use assembleGridEvent to ensure position field is properly set + const gridEventWithDefaults = assembleGridEvent(eventWithUpdates); + + // Validate the result + const gridEvent = validateGridEvent(gridEventWithDefaults); + return gridEvent; }