Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
1cc66ae
feat(web): add tests for onboarding improvements
tyler-dane Jan 2, 2026
bcd9704
fix(web): skip task seeding in test environment
tyler-dane Jan 2, 2026
6cf674d
fix(web): fix test for cmd+k keyboard event handling
tyler-dane Jan 2, 2026
367fd91
fix(backend): prevent duplicate instance when COUNT is specified in r…
tyler-dane Jan 2, 2026
c714c57
refactor(web): extract onboarding useEffects into separate custom hooks
tyler-dane Jan 2, 2026
889f94a
fix(web): fix failing onboarding tests
tyler-dane Jan 2, 2026
82e4fe2
feat(web): implement command palette guide for onboarding
tyler-dane Jan 2, 2026
64a29d8
feat(web): enhance onboarding with command palette guide tests
tyler-dane Jan 2, 2026
92b182a
refactor(web): remove OnboardingOverlay component and integrate CmdPa…
tyler-dane Jan 2, 2026
b94777b
feat(web): enhance onboarding guide with step completion tracking
tyler-dane Jan 2, 2026
f151324
feat(web): refactor onboarding storage management and enhance progres…
tyler-dane Jan 2, 2026
bb54bf1
feat(web): update CmdPaletteGuide to display contextual welcome messages
tyler-dane Jan 2, 2026
1559e68
feat(web): extend CmdPaletteGuide with step 4 and enhance onboarding …
tyler-dane Jan 2, 2026
e5b122c
feat(web): refactor onboarding steps to use constants for improved cl…
tyler-dane Jan 2, 2026
d5cf873
feat(web): enhance onboarding experience with new steps and improved …
tyler-dane Jan 2, 2026
410794c
refactor(web): standardize onboarding storage utilities and improve s…
tyler-dane Jan 3, 2026
c4fd05c
fix(web): update CmdPaletteGuide step counts and remove deprecated step
tyler-dane Jan 3, 2026
b972901
feat(web): integrate CmdPaletteGuide into CalendarView and enhance su…
tyler-dane Jan 3, 2026
5ac651d
feat(web): integrate CmdPaletteGuide into AuthenticatedLayout and enh…
tyler-dane Jan 3, 2026
5cc9ad4
Merge branch 'main' into feat/onboarding
tyler-dane Jan 3, 2026
77b0ba9
Update packages/web/src/auth/UserProvider.tsx
tyler-dane Jan 3, 2026
3601507
fix(web): address PR review comments for onboarding flow
tyler-dane Jan 3, 2026
811ab9f
refactor(web): improve useAuthPrompt and extract useUser hook
tyler-dane Jan 3, 2026
701a39a
chore: revert `gcal.event.rrule.ts`
tyler-dane Jan 3, 2026
895c81f
chore: add dexie and dexie-react-hooks dependencies to package.json
tyler-dane Jan 3, 2026
64d91b2
chore: add baseline-browser-mapping and fake-indexeddb dependencies t…
tyler-dane Jan 3, 2026
5ba256a
feat(web): enhance event creation saga for unauthenticated users
tyler-dane Jan 4, 2026
9e083f0
feat(web): add unit tests for getUserId function in auth.util
tyler-dane Jan 4, 2026
eeb824f
feat(web): implement IndexedDB storage for events and enhance saga fo…
tyler-dane Jan 4, 2026
5f6205d
test(web): enhance tests for Sidebar interactions and session handling
tyler-dane Jan 4, 2026
bac8b27
feat(web): implement EventRepository with local and remote storage ha…
tyler-dane Jan 4, 2026
eb7ed12
feat(web): add event repository utilities for local and remote storage
tyler-dane Jan 4, 2026
18b7a52
refactor(tests): remove Calendar.render.test.tsx file
tyler-dane Jan 4, 2026
f1adaa8
feat(tests): add unit tests for LocalEventRepository and LocalTaskRep…
tyler-dane Jan 4, 2026
d322254
Update packages/web/src/routers/loaders.ts
tyler-dane Jan 4, 2026
5351e68
fix(tests): remove showCmdPaletteTutorial from useAuthPrompt test
tyler-dane Jan 4, 2026
cac83ab
Update packages/web/src/common/repositories/event/event.repository.in…
tyler-dane Jan 4, 2026
d8b7a96
feat(tests): refactor Jest configuration to support multiple projects
tyler-dane Jan 4, 2026
e1eebd3
feat(onboarding): implement DayOnboardingOverlays component and relat…
tyler-dane Jan 4, 2026
92c5ef6
feat(onboarding): integrate onboarding overlays and related components
tyler-dane Jan 4, 2026
83ada8a
feat(onboarding): enhance CmdPaletteGuide with dynamic instructions a…
tyler-dane Jan 4, 2026
38139f8
feat(onboarding): introduce NAVIGATE_TO_DAY step and update onboardin…
tyler-dane Jan 5, 2026
7148725
refactor(onboarding): streamline CmdPaletteGuide and onboarding step …
tyler-dane Jan 5, 2026
07a4d82
refactor(event): remove isOptimistic flag from event handling
tyler-dane Jan 5, 2026
cd921a6
refactor(event): simplify event saga by removing unauthenticated user…
tyler-dane Jan 5, 2026
739fa8a
feat(auth): add AUTH_PROMPT_DISMISSED key to local storage management
tyler-dane Jan 5, 2026
6c7464a
feat(auth): refactor local storage management for authentication states
tyler-dane Jan 5, 2026
cf2f477
feat(auth): implement useIsSignupComplete hook and refactor onboardin…
tyler-dane Jan 5, 2026
5a9d041
Merge branch 'main' into feat/onboarding
tyler-dane Jan 5, 2026
e40ca95
feat(auth): update authentication flow to redirect to Day view
tyler-dane Jan 5, 2026
76cb0fe
feat(event): refactor event editing to utilize session and repository
tyler-dane Jan 5, 2026
9a612d4
refactor(event): update event repository methods and remove unused Re…
tyler-dane Jan 5, 2026
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
161 changes: 87 additions & 74 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,90 @@ const backendProject = {
preset: "@shelf/jest-mongodb", // https://jestjs.io/docs/mongodb,
};

/** @type { Exclude<Exclude<import("jest").Config["projects"], undefined>[number], string>} */
const coreProject = {
displayName: "core",
moduleNameMapper: {
"^@core(/(.*)$)?": "<rootDir>/packages/core/src/$1",
},
testEnvironment: "node",
testMatch: ["<rootDir>/packages/core/**/?(*.)+(spec|test).[tj]s?(x)"],
setupFiles: ["<rootDir>/packages/core/src/__tests__/core.test.init.ts"],
setupFilesAfterEnv: [
"<rootDir>/packages/core/src/__tests__/core.test.start.ts",
],
};

/** @type { Exclude<Exclude<import("jest").Config["projects"], undefined>[number], string>} */
const webProject = {
displayName: "web",
moduleNameMapper: {
"\\.(jpg|jpeg|png|gif)$":
"<rootDir>/packages/web/src/__tests__/__mocks__/file.stub.js",
"^@core(/(.*)$)?": "<rootDir>/packages/core/src/$1",
"^@web/__tests__(/(.*)$)?": "<rootDir>/packages/web/src/__tests__/$1",
"^@web/assets(/(.*)$)?": "<rootDir>/packages/web/src/assets/$1",
"^@web/auth(/(.*)$)?": "<rootDir>/packages/web/src/auth/$1",
"^@web/common(/(.*)$)?": "<rootDir>/packages/web/src/common/$1",
"^@web/components(/(.*)$)?": "<rootDir>/packages/web/src/components/$1",
"^@web/ducks(/(.*)$)?": "<rootDir>/packages/web/src/ducks/$1",
"^@web/public(/(.*)$)?": "<rootDir>/packages/web/src/public/$1",
"^@web/routers(/(.*)$)?": "<rootDir>/packages/web/src/routers/$1",
"^@web/socket(/(.*)$)?": "<rootDir>/packages/web/src/socket/$1",
"^@web/store((/(.*)$)?)?": "<rootDir>/packages/web/src/store/$1",
"^@web/views(/(.*)$)?": "<rootDir>/packages/web/src/views/$1",
"^.+\\.(css|less)$":
"<rootDir>/packages/web/src/__tests__/__mocks__/css.stub.js",
"\\.(svg)$": "<rootDir>/packages/web/src/__tests__/__mocks__/svg.stub.js",
"^uuid$": "uuid",
},
setupFiles: [
"<rootDir>/packages/core/src/__tests__/core.test.init.ts",
"<rootDir>/packages/core/src/__tests__/core.test.start.ts",
"<rootDir>/packages/web/src/__tests__/web.test.init.ts",
"jest-canvas-mock",
],
setupFilesAfterEnv: [
"<rootDir>/packages/web/src/__tests__/web.test.start.ts",
],
testEnvironment: "<rootDir>/packages/web/src/__tests__/jsdom.ts",
testMatch: ["<rootDir>/packages/web/**/*.(test|spec).[jt]s?(x)"],
transformIgnorePatterns: [
//https://github.com/react-dnd/react-dnd/issues/3443
"/node_modules/(?!react-dnd|dnd-core|@react-dnd)",
],
};

/** @type { Exclude<Exclude<import("jest").Config["projects"], undefined>[number], string>} */
const scriptsProject = {
displayName: "scripts",
moduleNameMapper: {
...backendProject.moduleNameMapper,
"^@scripts(/(.*)$)?": "<rootDir>/packages/scripts/src/$1",
"^@scripts/commands(/(.*)$)?": "<rootDir>/packages/scripts/src/commands/$1",
"^@scripts/common(/(.*)$)?": "<rootDir>/packages/scripts/src/common/$1",
"^@scripts/migrations(/(.*)$)?":
"<rootDir>/packages/scripts/src/migrations/$1",
"^@scripts/seeders(/(.*)$)?": "<rootDir>/packages/scripts/src/seeders/$1",
},
setupFiles: [...backendProject.setupFiles],
setupFilesAfterEnv: [...backendProject.setupFilesAfterEnv],
testMatch: ["<rootDir>/packages/scripts/**/?(*.)+(spec|test).[tj]s?(x)"],
// A preset that is used as a base for Jest's configuration
preset: "@shelf/jest-mongodb", // https://jestjs.io/docs/mongodb,
};

const projectMap = {
core: coreProject,
web: webProject,
backend: backendProject,
scripts: scriptsProject,
};

const requestedProject = process.argv.find((arg) =>
Object.prototype.hasOwnProperty.call(projectMap, arg),
);

/** @type { import("jest").Config } */
const config = {
// All imported modules in your tests should be mocked automatically
Expand Down Expand Up @@ -126,80 +210,9 @@ const config = {
// An enum that specifies notification mode. Requires { notify: true }
// notifyMode: "failure-change",

projects: [
{
displayName: "core",
moduleNameMapper: {
"^@core(/(.*)$)?": "<rootDir>/packages/core/src/$1",
},
testEnvironment: "node",
testMatch: ["<rootDir>/packages/core/**/?(*.)+(spec|test).[tj]s?(x)"],
setupFiles: ["<rootDir>/packages/core/src/__tests__/core.test.init.ts"],
setupFilesAfterEnv: [
"<rootDir>/packages/core/src/__tests__/core.test.start.ts",
],
},
{
displayName: "web",
moduleNameMapper: {
"\\.(jpg|jpeg|png|gif)$":
"<rootDir>/packages/web/src/__tests__/__mocks__/file.stub.js",
"^@core(/(.*)$)?": "<rootDir>/packages/core/src/$1",
"^@web/__tests__(/(.*)$)?": "<rootDir>/packages/web/src/__tests__/$1",
"^@web/assets(/(.*)$)?": "<rootDir>/packages/web/src/assets/$1",
"^@web/auth(/(.*)$)?": "<rootDir>/packages/web/src/auth/$1",
"^@web/common(/(.*)$)?": "<rootDir>/packages/web/src/common/$1",
"^@web/components(/(.*)$)?": "<rootDir>/packages/web/src/components/$1",
"^@web/ducks(/(.*)$)?": "<rootDir>/packages/web/src/ducks/$1",
"^@web/public(/(.*)$)?": "<rootDir>/packages/web/src/public/$1",
"^@web/routers(/(.*)$)?": "<rootDir>/packages/web/src/routers/$1",
"^@web/socket(/(.*)$)?": "<rootDir>/packages/web/src/socket/$1",
"^@web/store((/(.*)$)?)?": "<rootDir>/packages/web/src/store/$1",
"^@web/views(/(.*)$)?": "<rootDir>/packages/web/src/views/$1",
"^.+\\.(css|less)$":
"<rootDir>/packages/web/src/__tests__/__mocks__/css.stub.js",
"\\.(svg)$":
"<rootDir>/packages/web/src/__tests__/__mocks__/svg.stub.js",
"^uuid$": "uuid",
},
setupFiles: [
"<rootDir>/packages/core/src/__tests__/core.test.init.ts",
"<rootDir>/packages/core/src/__tests__/core.test.start.ts",
"<rootDir>/packages/web/src/__tests__/web.test.init.ts",
"jest-canvas-mock",
],
setupFilesAfterEnv: [
"<rootDir>/packages/web/src/__tests__/web.test.start.ts",
],
testEnvironment: "<rootDir>/packages/web/src/__tests__/jsdom.ts",
testMatch: ["<rootDir>/packages/web/**/*.(test|spec).[jt]s?(x)"],
transformIgnorePatterns: [
//https://github.com/react-dnd/react-dnd/issues/3443
"/node_modules/(?!react-dnd|dnd-core|@react-dnd)",
],
},
backendProject,
{
displayName: "scripts",
moduleNameMapper: {
...backendProject.moduleNameMapper,
"^@scripts(/(.*)$)?": "<rootDir>/packages/scripts/src/$1",
"^@scripts/commands(/(.*)$)?":
"<rootDir>/packages/scripts/src/commands/$1",
"^@scripts/common(/(.*)$)?": "<rootDir>/packages/scripts/src/common/$1",
"^@scripts/migrations(/(.*)$)?":
"<rootDir>/packages/scripts/src/migrations/$1",
"^@scripts/seeders(/(.*)$)?":
"<rootDir>/packages/scripts/src/seeders/$1",
},

setupFiles: [...backendProject.setupFiles],
setupFilesAfterEnv: [...backendProject.setupFilesAfterEnv],
testMatch: ["<rootDir>/packages/scripts/**/?(*.)+(spec|test).[tj]s?(x)"],
// A preset that is used as a base for Jest's configuration
preset: "@shelf/jest-mongodb", // https://jestjs.io/docs/mongodb,
},
],
projects: requestedProject
? [projectMap[requestedProject]]
: [coreProject, webProject, backendProject, scriptsProject],
// Use this configuration option to add custom reporters to Jest
// reporters: undefined,

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.0.0",
"babel-loader": "^9.1.0",
"baseline-browser-mapping": "^2.9.11",
"buffer": "^6.0.3",
"concurrently": "^8.0.1",
"cross-env": "^7.0.3",
Expand Down
3 changes: 3 additions & 0 deletions packages/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
"classnames": "^2.3.1",
"css-loader": "^6.3.0",
"dayjs": "^1.10.7",
"dexie": "^4.2.1",
"dexie-react-hooks": "^4.2.0",
"fast-deep-equal": "^3.1.3",
"html-webpack-plugin": "^5.6.4",
"mini-css-extract-plugin": "^2.3.0",
Expand Down Expand Up @@ -78,6 +80,7 @@
"eslint-plugin-react": "^7.28.0",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-testing-library": "^5.0.5",
"fake-indexeddb": "^6.2.5",
"jest": "^29.0.3",
"jest-canvas-mock": "^2.5.2",
"jest-environment-jsdom": "^29.7.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { Origin, Priorities } from "@core/constants/core.constants";
import { Event_Core } from "@core/types/event.types";
import dayjs from "@core/util/date/dayjs";
import { createMockStandaloneEvent } from "@core/util/test/ccal.event.factory";
import { Task } from "@web/common/types/task.types";

/**
* Factory function to create a test Event_Core with sensible defaults.
* @param overrides - Partial event properties to override defaults
* @returns A complete Event_Core object
*/
export const createTestEvent = (
overrides: Partial<Event_Core & { order?: number }> = {},
): Event_Core & { order?: number } => {
const dateStr = dayjs().format(dayjs.DateFormat.YEAR_MONTH_DAY_FORMAT);
return {
_id: `event-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`,
title: "Test Event",
startDate: dateStr,
endDate: dateStr,
origin: Origin.COMPASS,
priority: Priorities.UNASSIGNED,
user: "user-1",
...overrides,
};
};

/**
* Factory function to create multiple test events.
* @param count - Number of events to create
* @param overrides - Partial event properties to override defaults (applied to all events)
* @returns Array of Event_Core objects
*/
export const createTestEvents = (
count: number,
overrides: Partial<Event_Core> = {},
): Event_Core[] => {
return Array.from({ length: count }, (_, index) =>
createTestEvent({
...overrides,
_id: overrides._id || `event-${index + 1}`,
title: overrides.title || `Event ${index + 1}`,
}),
);
};

/**
* Factory function to create a test CompassCoreEvent (for edit operations).
* Uses the existing factory from @core/util/test/ccal.event.factory.
* @param overrides - Partial event properties to override defaults
* @returns A complete event object compatible with CompassCoreEvent
*/
export const createTestCompassEvent = (
overrides: Parameters<typeof createMockStandaloneEvent>[0] = {},
) => {
return createMockStandaloneEvent(overrides);
};

/**
* Factory function to create a test Task with sensible defaults.
* @param overrides - Partial task properties to override defaults
* @returns A complete Task object
*/
export const createTestTask = (overrides: Partial<Task> = {}): Task => {
return {
id: `task-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`,
title: "Test Task",
status: "todo",
order: 0,
createdAt: new Date().toISOString(),
...overrides,
};
};

/**
* Factory function to create multiple test tasks.
* @param count - Number of tasks to create
* @param overrides - Partial task properties to override defaults (applied to all tasks)
* @returns Array of Task objects
*/
export const createTestTasks = (
count: number,
overrides: Partial<Task> = {},
): Task[] => {
return Array.from({ length: count }, (_, index) =>
createTestTask({
...overrides,
id: overrides.id || `task-${index + 1}`,
title: overrides.title || `Task ${index + 1}`,
order: overrides.order !== undefined ? overrides.order : index,
}),
);
};
16 changes: 15 additions & 1 deletion packages/web/src/__tests__/web.test.start.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
import "fake-indexeddb/auto";
import "@testing-library/jest-dom";
import { mockNodeModules } from "@web/__tests__/__mocks__/mock.setup";
import { server } from "@web/__tests__/__mocks__/server/mock.server";

// Polyfill structuredClone for fake-indexeddb
if (typeof global.structuredClone === "undefined") {
global.structuredClone = (obj: unknown) => {
return JSON.parse(JSON.stringify(obj));
};
}

mockNodeModules();

beforeEach(() => jest.clearAllMocks());
beforeEach(() => {
jest.clearAllMocks();
const sessionModule = jest.requireMock(
"supertokens-web-js/recipe/session",
) as { doesSessionExist?: jest.Mock };
sessionModule.doesSessionExist?.mockResolvedValue(true);
});
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
Expand Down
9 changes: 9 additions & 0 deletions packages/web/src/auth/UserContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createContext } from "react";
import { UserProfile } from "@core/types/user.types";

export const UserContext = createContext<
| Partial<
{ isLoadingUser: boolean; userId: string } & Omit<UserProfile, "_id">
>
| undefined
>(undefined);
32 changes: 15 additions & 17 deletions packages/web/src/auth/UserProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,9 @@
import { usePostHog } from "posthog-js/react";
import {
ReactNode,
createContext,
useEffect,
useLayoutEffect,
useRef,
useState,
} from "react";
import { ReactNode, useEffect, useLayoutEffect, useRef, useState } from "react";
import { UserProfile } from "@core/types/user.types";
import { UserApi } from "@web/common/apis/user.api";
import { AbsoluteOverflowLoader } from "@web/components/AbsoluteOverflowLoader";

const UserContext = createContext<
| Partial<
{ isLoadingUser: boolean; userId: string } & Omit<UserProfile, "_id">
>
| undefined
>(undefined);
import { UserContext } from "./UserContext";

export const UserProvider = ({ children }: { children: ReactNode }) => {
const profile = useRef<UserProfile | null>(null);
Expand All @@ -35,7 +22,13 @@ export const UserProvider = ({ children }: { children: ReactNode }) => {
profile.current = userProfile;
})
.catch((e) => {
console.error("Failed to get user profile", e);
// For unauthenticated users, this is expected - don't show error
// Only log if it's not a 401/403 (unauthorized) error
const status = (e as { response?: { status?: number } })?.response
?.status;
if (status !== 401 && status !== 403) {
console.error("Failed to get user profile", e);
}
})
.finally(() => {
setIsLoadingUser(false);
Expand All @@ -50,7 +43,12 @@ export const UserProvider = ({ children }: { children: ReactNode }) => {
}
}, [userId, email, posthog]);

if (isLoadingUser || userId === null) {
// Allow unauthenticated users to proceed without blocking
// Only show loader briefly while checking auth status
// Unauthenticated users will have profile.current === null, which is fine
if (isLoadingUser && userId === null) {
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The UserProvider now allows unauthenticated users to proceed (line 49-52), but the conditional check if (isLoadingUser && userId === null) might cause issues. When a user is authenticated, userId will be truthy, so userId === null would be false, meaning the loader won't show even during initial loading when isLoadingUser is true. Consider changing the condition to if (isLoadingUser) or adding an additional check to handle the authenticated loading state.

Suggested change
if (isLoadingUser && userId === null) {
if (isLoadingUser) {

Copilot uses AI. Check for mistakes.
// Brief loading state - but don't block indefinitely
// The route loader handles auth redirects
return <AbsoluteOverflowLoader />;
}

Expand Down
Loading