* #708 apply strictier linting rules and fix simple eslint bugs * #708 fix eslint errors relate to promise * #708 fix eslint import/no-extraneous-dependencies * #708 fix eslint errors of react-hook * #708 enable eslint check for typescript --------- Co-authored-by: lethemanh <lethemanh@lethemanhs-MacBook-Pro.local>
This commit is contained in:
@@ -2,72 +2,72 @@ import {
|
||||
calendarRangeManager,
|
||||
getDisplayedCalendarRange,
|
||||
getDisplayedDate,
|
||||
setDisplayedDateAndRange,
|
||||
} from "@/utils/CalendarRangeManager";
|
||||
setDisplayedDateAndRange
|
||||
} from '@/utils/CalendarRangeManager'
|
||||
|
||||
describe("CalendarRangeManager", () => {
|
||||
describe('CalendarRangeManager', () => {
|
||||
beforeEach(() => {
|
||||
setDisplayedDateAndRange(new Date());
|
||||
});
|
||||
setDisplayedDateAndRange(new Date())
|
||||
})
|
||||
|
||||
it("should return a singleton instance", () => {
|
||||
const instance1 = calendarRangeManager;
|
||||
const instance2 = calendarRangeManager;
|
||||
it('should return a singleton instance', () => {
|
||||
const instance1 = calendarRangeManager
|
||||
const instance2 = calendarRangeManager
|
||||
|
||||
expect(instance1).toStrictEqual(instance2);
|
||||
});
|
||||
expect(instance1).toStrictEqual(instance2)
|
||||
})
|
||||
|
||||
it("should compute and return calendar range when date is set", () => {
|
||||
const testDate = new Date("2025-06-15T10:00:00Z");
|
||||
setDisplayedDateAndRange(testDate);
|
||||
it('should compute and return calendar range when date is set', () => {
|
||||
const testDate = new Date('2025-06-15T10:00:00Z')
|
||||
setDisplayedDateAndRange(testDate)
|
||||
|
||||
const range = getDisplayedCalendarRange();
|
||||
expect(range.start).toBeInstanceOf(Date);
|
||||
expect(range.end).toBeInstanceOf(Date);
|
||||
expect(range.start.getTime()).toBeLessThanOrEqual(testDate.getTime());
|
||||
expect(range.end.getTime()).toBeGreaterThanOrEqual(testDate.getTime());
|
||||
});
|
||||
const range = getDisplayedCalendarRange()
|
||||
expect(range.start).toBeInstanceOf(Date)
|
||||
expect(range.end).toBeInstanceOf(Date)
|
||||
expect(range.start.getTime()).toBeLessThanOrEqual(testDate.getTime())
|
||||
expect(range.end.getTime()).toBeGreaterThanOrEqual(testDate.getTime())
|
||||
})
|
||||
|
||||
it("should get the default date", () => {
|
||||
const date = getDisplayedDate();
|
||||
expect(date).toBeInstanceOf(Date);
|
||||
});
|
||||
it('should get the default date', () => {
|
||||
const date = getDisplayedDate()
|
||||
expect(date).toBeInstanceOf(Date)
|
||||
})
|
||||
|
||||
it("should set and get a date", () => {
|
||||
const testDate = new Date("2025-01-15T10:00:00Z");
|
||||
setDisplayedDateAndRange(testDate);
|
||||
it('should set and get a date', () => {
|
||||
const testDate = new Date('2025-01-15T10:00:00Z')
|
||||
setDisplayedDateAndRange(testDate)
|
||||
|
||||
const retrievedDate = getDisplayedDate();
|
||||
expect(retrievedDate).toStrictEqual(testDate);
|
||||
});
|
||||
const retrievedDate = getDisplayedDate()
|
||||
expect(retrievedDate).toStrictEqual(testDate)
|
||||
})
|
||||
|
||||
it("should persist date across multiple calls", () => {
|
||||
const testDate = new Date("2025-06-20T15:30:00Z");
|
||||
setDisplayedDateAndRange(testDate);
|
||||
it('should persist date across multiple calls', () => {
|
||||
const testDate = new Date('2025-06-20T15:30:00Z')
|
||||
setDisplayedDateAndRange(testDate)
|
||||
|
||||
const date1 = getDisplayedDate();
|
||||
const date2 = getDisplayedDate();
|
||||
const date1 = getDisplayedDate()
|
||||
const date2 = getDisplayedDate()
|
||||
|
||||
expect(date1).toStrictEqual(date2);
|
||||
expect(date1).toStrictEqual(testDate);
|
||||
});
|
||||
expect(date1).toStrictEqual(date2)
|
||||
expect(date1).toStrictEqual(testDate)
|
||||
})
|
||||
|
||||
it("should update date when set multiple times", () => {
|
||||
const date1 = new Date("2025-01-01T00:00:00Z");
|
||||
const date2 = new Date("2025-12-31T23:59:59Z");
|
||||
it('should update date when set multiple times', () => {
|
||||
const date1 = new Date('2025-01-01T00:00:00Z')
|
||||
const date2 = new Date('2025-12-31T23:59:59Z')
|
||||
|
||||
setDisplayedDateAndRange(date1);
|
||||
expect(getDisplayedDate()).toStrictEqual(date1);
|
||||
setDisplayedDateAndRange(date1)
|
||||
expect(getDisplayedDate()).toStrictEqual(date1)
|
||||
|
||||
setDisplayedDateAndRange(date2);
|
||||
expect(getDisplayedDate()).toStrictEqual(date2);
|
||||
});
|
||||
setDisplayedDateAndRange(date2)
|
||||
expect(getDisplayedDate()).toStrictEqual(date2)
|
||||
})
|
||||
|
||||
it("should maintain shared state (singleton behavior)", () => {
|
||||
const testDate = new Date("2025-03-01");
|
||||
setDisplayedDateAndRange(testDate);
|
||||
it('should maintain shared state (singleton behavior)', () => {
|
||||
const testDate = new Date('2025-03-01')
|
||||
setDisplayedDateAndRange(testDate)
|
||||
|
||||
// Verify CalendarRangeManager reflects the same state
|
||||
expect(calendarRangeManager.getDate()).toStrictEqual(testDate);
|
||||
});
|
||||
});
|
||||
expect(calendarRangeManager.getDate()).toStrictEqual(testDate)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import type { AppStore, RootState } from "@/app/store";
|
||||
import { setupStore } from "@/app/store";
|
||||
import { TwakeMuiThemeProvider } from "@linagora/twake-mui";
|
||||
import type { RenderOptions } from "@testing-library/react";
|
||||
import { render } from "@testing-library/react";
|
||||
import React, { PropsWithChildren } from "react";
|
||||
import { Provider } from "react-redux";
|
||||
import { MemoryRouter } from "react-router-dom";
|
||||
import { I18nContext } from "twake-i18n";
|
||||
interface ExtendedRenderOptions extends Omit<RenderOptions, "queries"> {
|
||||
preloadedState?: Partial<RootState>;
|
||||
store?: AppStore;
|
||||
import type { AppStore, RootState } from '@/app/store'
|
||||
import { setupStore } from '@/app/store'
|
||||
import { TwakeMuiThemeProvider } from '@linagora/twake-mui'
|
||||
import type { RenderOptions } from '@testing-library/react'
|
||||
import { render } from '@testing-library/react'
|
||||
import React, { PropsWithChildren } from 'react'
|
||||
import { Provider } from 'react-redux'
|
||||
import { MemoryRouter } from 'react-router-dom'
|
||||
import { I18nContext } from 'twake-i18n'
|
||||
interface ExtendedRenderOptions extends Omit<RenderOptions, 'queries'> {
|
||||
preloadedState?: Partial<RootState>
|
||||
store?: AppStore
|
||||
}
|
||||
|
||||
export function renderWithProviders(
|
||||
@@ -18,7 +18,7 @@ export function renderWithProviders(
|
||||
extendedRenderOptions: ExtendedRenderOptions = {}
|
||||
) {
|
||||
const { store = setupStore(preloadedState), ...renderOptions } =
|
||||
extendedRenderOptions;
|
||||
extendedRenderOptions
|
||||
|
||||
const Wrapper = ({ children }: PropsWithChildren) => {
|
||||
return (
|
||||
@@ -26,31 +26,31 @@ export function renderWithProviders(
|
||||
<I18nContext.Provider
|
||||
value={{
|
||||
t: (key: string, vars?: Record<string, string>) => {
|
||||
if (key === "locale") {
|
||||
return "en";
|
||||
if (key === 'locale') {
|
||||
return 'en'
|
||||
}
|
||||
if (vars) {
|
||||
const params = Object.entries(vars)
|
||||
.map(([k, v]) => `${k}=${v}`)
|
||||
.join(",");
|
||||
return `${key}(${params})`;
|
||||
.join(',')
|
||||
return `${key}(${params})`
|
||||
}
|
||||
return key;
|
||||
return key
|
||||
},
|
||||
f: (date: Date, formatStr: string) => date.toString(),
|
||||
lang: "en",
|
||||
lang: 'en'
|
||||
}}
|
||||
>
|
||||
<MemoryRouter initialEntries={["/"]}>
|
||||
<MemoryRouter initialEntries={['/']}>
|
||||
<Provider store={store}>{children}</Provider>
|
||||
</MemoryRouter>
|
||||
</I18nContext.Provider>
|
||||
</TwakeMuiThemeProvider>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
store,
|
||||
...render(ui, { wrapper: Wrapper, ...renderOptions }),
|
||||
};
|
||||
...render(ui, { wrapper: Wrapper, ...renderOptions })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,113 +1,113 @@
|
||||
import { getInitials, stringToGradient } from "@/utils/avatarUtils";
|
||||
import { getInitials, stringToGradient } from '@/utils/avatarUtils'
|
||||
|
||||
jest.mock("@linagora/twake-mui", () => ({
|
||||
jest.mock('@linagora/twake-mui', () => ({
|
||||
nameToColor: jest.fn((name: string) => {
|
||||
if (!name) return undefined;
|
||||
if (!name) return undefined
|
||||
const colors = [
|
||||
"sunrise",
|
||||
"downy",
|
||||
"sugarCoral",
|
||||
"pinkBonnet",
|
||||
"blueMana",
|
||||
"nightBlue",
|
||||
"snowPea",
|
||||
"pluviophile",
|
||||
"cornflower",
|
||||
"paleGreen",
|
||||
"moonBlue",
|
||||
];
|
||||
'sunrise',
|
||||
'downy',
|
||||
'sugarCoral',
|
||||
'pinkBonnet',
|
||||
'blueMana',
|
||||
'nightBlue',
|
||||
'snowPea',
|
||||
'pluviophile',
|
||||
'cornflower',
|
||||
'paleGreen',
|
||||
'moonBlue'
|
||||
]
|
||||
const hash = Array.from(name.toUpperCase())
|
||||
.map((letter) => letter.charCodeAt(0))
|
||||
.reduce((sum, number) => sum + number, 0);
|
||||
return colors[hash % colors.length];
|
||||
}),
|
||||
}));
|
||||
.map(letter => letter.charCodeAt(0))
|
||||
.reduce((sum, number) => sum + number, 0)
|
||||
return colors[hash % colors.length]
|
||||
})
|
||||
}))
|
||||
|
||||
describe("avatarUtils", () => {
|
||||
describe("getInitials", () => {
|
||||
it("returns 2-letter initials for full name", () => {
|
||||
expect(getInitials("John Doe")).toBe("JD");
|
||||
expect(getInitials("Alice Smith")).toBe("AS");
|
||||
expect(getInitials("Nguyễn Văn")).toBe("NV");
|
||||
});
|
||||
describe('avatarUtils', () => {
|
||||
describe('getInitials', () => {
|
||||
it('returns 2-letter initials for full name', () => {
|
||||
expect(getInitials('John Doe')).toBe('JD')
|
||||
expect(getInitials('Alice Smith')).toBe('AS')
|
||||
expect(getInitials('Nguyễn Văn')).toBe('NV')
|
||||
})
|
||||
|
||||
it("returns single letter for single word", () => {
|
||||
expect(getInitials("Alice")).toBe("A");
|
||||
expect(getInitials("John")).toBe("J");
|
||||
});
|
||||
it('returns single letter for single word', () => {
|
||||
expect(getInitials('Alice')).toBe('A')
|
||||
expect(getInitials('John')).toBe('J')
|
||||
})
|
||||
|
||||
it("returns uppercase initials", () => {
|
||||
expect(getInitials("john doe")).toBe("JD");
|
||||
expect(getInitials("ALICE SMITH")).toBe("AS");
|
||||
expect(getInitials("a")).toBe("A");
|
||||
});
|
||||
it('returns uppercase initials', () => {
|
||||
expect(getInitials('john doe')).toBe('JD')
|
||||
expect(getInitials('ALICE SMITH')).toBe('AS')
|
||||
expect(getInitials('a')).toBe('A')
|
||||
})
|
||||
|
||||
it("handles multiple spaces between words", () => {
|
||||
expect(getInitials("John Doe")).toBe("JD");
|
||||
expect(getInitials("Alice Smith Brown")).toBe("AS");
|
||||
});
|
||||
it('handles multiple spaces between words', () => {
|
||||
expect(getInitials('John Doe')).toBe('JD')
|
||||
expect(getInitials('Alice Smith Brown')).toBe('AS')
|
||||
})
|
||||
|
||||
it("handles email addresses", () => {
|
||||
expect(getInitials("john.doe@email.com")).toBe("J");
|
||||
expect(getInitials("test@example.com")).toBe("T");
|
||||
});
|
||||
it('handles email addresses', () => {
|
||||
expect(getInitials('john.doe@email.com')).toBe('J')
|
||||
expect(getInitials('test@example.com')).toBe('T')
|
||||
})
|
||||
|
||||
it("handles empty string", () => {
|
||||
expect(getInitials("")).toBe("");
|
||||
});
|
||||
it('handles empty string', () => {
|
||||
expect(getInitials('')).toBe('')
|
||||
})
|
||||
|
||||
it("handles whitespace-only string", () => {
|
||||
expect(getInitials(" ")).toBe("");
|
||||
});
|
||||
it('handles whitespace-only string', () => {
|
||||
expect(getInitials(' ')).toBe('')
|
||||
})
|
||||
|
||||
it("handles single character", () => {
|
||||
expect(getInitials("A")).toBe("A");
|
||||
expect(getInitials("a")).toBe("A");
|
||||
});
|
||||
it('handles single character', () => {
|
||||
expect(getInitials('A')).toBe('A')
|
||||
expect(getInitials('a')).toBe('A')
|
||||
})
|
||||
|
||||
it("handles names with special characters", () => {
|
||||
expect(getInitials("Jean-Pierre")).toBe("J");
|
||||
expect(getInitials("Mary Jane Watson")).toBe("MJ");
|
||||
});
|
||||
it('handles names with special characters', () => {
|
||||
expect(getInitials('Jean-Pierre')).toBe('J')
|
||||
expect(getInitials('Mary Jane Watson')).toBe('MJ')
|
||||
})
|
||||
|
||||
it("handles unicode characters", () => {
|
||||
expect(getInitials("Nguyễn Văn")).toBe("NV");
|
||||
expect(getInitials("José María")).toBe("JM");
|
||||
});
|
||||
it('handles unicode characters', () => {
|
||||
expect(getInitials('Nguyễn Văn')).toBe('NV')
|
||||
expect(getInitials('José María')).toBe('JM')
|
||||
})
|
||||
|
||||
it("takes first two words for names with more than two words", () => {
|
||||
expect(getInitials("John Michael Smith")).toBe("JM");
|
||||
expect(getInitials("Alice Bob Charlie")).toBe("AB");
|
||||
});
|
||||
});
|
||||
it('takes first two words for names with more than two words', () => {
|
||||
expect(getInitials('John Michael Smith')).toBe('JM')
|
||||
expect(getInitials('Alice Bob Charlie')).toBe('AB')
|
||||
})
|
||||
})
|
||||
|
||||
describe("stringToGradient", () => {
|
||||
it("returns color for valid string", () => {
|
||||
const result = stringToGradient("John Doe");
|
||||
expect(result).toBeDefined();
|
||||
expect(typeof result).toBe("string");
|
||||
});
|
||||
describe('stringToGradient', () => {
|
||||
it('returns color for valid string', () => {
|
||||
const result = stringToGradient('John Doe')
|
||||
expect(result).toBeDefined()
|
||||
expect(typeof result).toBe('string')
|
||||
})
|
||||
|
||||
it("returns same color for same input", () => {
|
||||
const result1 = stringToGradient("John Doe");
|
||||
const result2 = stringToGradient("John Doe");
|
||||
expect(result1).toBe(result2);
|
||||
});
|
||||
it('returns same color for same input', () => {
|
||||
const result1 = stringToGradient('John Doe')
|
||||
const result2 = stringToGradient('John Doe')
|
||||
expect(result1).toBe(result2)
|
||||
})
|
||||
|
||||
it("returns undefined for empty string", () => {
|
||||
expect(stringToGradient("")).toBeUndefined();
|
||||
});
|
||||
it('returns undefined for empty string', () => {
|
||||
expect(stringToGradient('')).toBeUndefined()
|
||||
})
|
||||
|
||||
it("returns color for single character", () => {
|
||||
const result = stringToGradient("A");
|
||||
expect(result).toBeDefined();
|
||||
expect(typeof result).toBe("string");
|
||||
});
|
||||
it('returns color for single character', () => {
|
||||
const result = stringToGradient('A')
|
||||
expect(result).toBeDefined()
|
||||
expect(typeof result).toBe('string')
|
||||
})
|
||||
|
||||
it("returns color for email address", () => {
|
||||
const result = stringToGradient("test@example.com");
|
||||
expect(result).toBeDefined();
|
||||
expect(typeof result).toBe("string");
|
||||
});
|
||||
});
|
||||
});
|
||||
it('returns color for email address', () => {
|
||||
const result = stringToGradient('test@example.com')
|
||||
expect(result).toBeDefined()
|
||||
expect(typeof result).toBe('string')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,51 +1,51 @@
|
||||
import { Calendar } from "@/features/Calendars/CalendarTypes";
|
||||
import { buildDelegatedEventURL } from "@/features/Events/utils";
|
||||
import { Calendar } from '@/features/Calendars/CalendarTypes'
|
||||
import { buildDelegatedEventURL } from '@/features/Events/utils'
|
||||
|
||||
const makeCalendar = (link: string): Calendar =>
|
||||
({
|
||||
id: "user2/cal1",
|
||||
id: 'user2/cal1',
|
||||
delegated: true,
|
||||
link,
|
||||
owner: { emails: ["owner@example.com"] },
|
||||
}) as Calendar;
|
||||
owner: { emails: ['owner@example.com'] }
|
||||
}) as Calendar
|
||||
|
||||
describe("buildDelegatedEventURL", () => {
|
||||
it("rebases event filename onto calendar link base path", () => {
|
||||
const calendar = makeCalendar("/calendars/user2/cal1.json");
|
||||
const eventURL = "/calendars/someother/path/event-abc.ics";
|
||||
describe('buildDelegatedEventURL', () => {
|
||||
it('rebases event filename onto calendar link base path', () => {
|
||||
const calendar = makeCalendar('/calendars/user2/cal1.json')
|
||||
const eventURL = '/calendars/someother/path/event-abc.ics'
|
||||
expect(buildDelegatedEventURL(calendar, eventURL)).toBe(
|
||||
"/calendars/user2/cal1/event-abc.ics"
|
||||
);
|
||||
});
|
||||
'/calendars/user2/cal1/event-abc.ics'
|
||||
)
|
||||
})
|
||||
|
||||
it("strips .json suffix from calendar link", () => {
|
||||
const calendar = makeCalendar("/calendars/user2/cal1.json");
|
||||
const eventURL = "/calendars/user2/cal1/event-abc.ics";
|
||||
const result = buildDelegatedEventURL(calendar, eventURL);
|
||||
expect(result).not.toContain(".json");
|
||||
});
|
||||
it('strips .json suffix from calendar link', () => {
|
||||
const calendar = makeCalendar('/calendars/user2/cal1.json')
|
||||
const eventURL = '/calendars/user2/cal1/event-abc.ics'
|
||||
const result = buildDelegatedEventURL(calendar, eventURL)
|
||||
expect(result).not.toContain('.json')
|
||||
})
|
||||
|
||||
it("preserves the exact filename from the event URL", () => {
|
||||
const calendar = makeCalendar("/calendars/user2/cal1.json");
|
||||
const eventURL = "/calendars/user2/cal1/some-uid-with-dashes.ics";
|
||||
it('preserves the exact filename from the event URL', () => {
|
||||
const calendar = makeCalendar('/calendars/user2/cal1.json')
|
||||
const eventURL = '/calendars/user2/cal1/some-uid-with-dashes.ics'
|
||||
expect(buildDelegatedEventURL(calendar, eventURL)).toBe(
|
||||
"/calendars/user2/cal1/some-uid-with-dashes.ics"
|
||||
);
|
||||
});
|
||||
'/calendars/user2/cal1/some-uid-with-dashes.ics'
|
||||
)
|
||||
})
|
||||
|
||||
it("throws when event URL has no filename", () => {
|
||||
const calendar = makeCalendar("/calendars/user2/cal1.json");
|
||||
const eventURL = "";
|
||||
it('throws when event URL has no filename', () => {
|
||||
const calendar = makeCalendar('/calendars/user2/cal1.json')
|
||||
const eventURL = ''
|
||||
expect(() => buildDelegatedEventURL(calendar, eventURL)).toThrow(
|
||||
/Cannot extract filename from event URL/
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
|
||||
it("works with nested calendar link paths", () => {
|
||||
const calendar = makeCalendar("/dav/calendars/users/user2/cal1.json");
|
||||
const eventURL = "/other/path/event-xyz.ics";
|
||||
it('works with nested calendar link paths', () => {
|
||||
const calendar = makeCalendar('/dav/calendars/users/user2/cal1.json')
|
||||
const eventURL = '/other/path/event-xyz.ics'
|
||||
expect(buildDelegatedEventURL(calendar, eventURL)).toBe(
|
||||
"/dav/calendars/users/user2/cal1/event-xyz.ics"
|
||||
);
|
||||
});
|
||||
});
|
||||
'/dav/calendars/users/user2/cal1/event-xyz.ics'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,231 +1,231 @@
|
||||
import {
|
||||
eventToFullCalendarFormat,
|
||||
getCalendarVisibility,
|
||||
} from "@/components/Calendar/utils/calendarUtils";
|
||||
import { Calendar, DelegationAccess } from "@/features/Calendars/CalendarTypes";
|
||||
import { AclEntry } from "@/features/Calendars/types/CalendarData";
|
||||
import { CalendarEvent } from "@/features/Events/EventsTypes";
|
||||
import { userOrganiser } from "@/features/User/userDataTypes";
|
||||
getCalendarVisibility
|
||||
} from '@/components/Calendar/utils/calendarUtils'
|
||||
import { Calendar, DelegationAccess } from '@/features/Calendars/CalendarTypes'
|
||||
import { AclEntry } from '@/features/Calendars/types/CalendarData'
|
||||
import { CalendarEvent } from '@/features/Events/EventsTypes'
|
||||
import { userOrganiser } from '@/features/User/userDataTypes'
|
||||
|
||||
describe("getCalendarVisibility", () => {
|
||||
describe('getCalendarVisibility', () => {
|
||||
it("returns 'public' when {DAV:}authenticated has {DAV:}read", () => {
|
||||
const acl: AclEntry[] = [
|
||||
{
|
||||
privilege: "{DAV:}read",
|
||||
principal: "{DAV:}authenticated",
|
||||
protected: true,
|
||||
},
|
||||
];
|
||||
expect(getCalendarVisibility(acl)).toBe("public");
|
||||
});
|
||||
privilege: '{DAV:}read',
|
||||
principal: '{DAV:}authenticated',
|
||||
protected: true
|
||||
}
|
||||
]
|
||||
expect(getCalendarVisibility(acl)).toBe('public')
|
||||
})
|
||||
|
||||
it("returns 'private' when {DAV:}authenticated only has read-free-busy", () => {
|
||||
const acl: AclEntry[] = [
|
||||
{
|
||||
privilege: "{urn:ietf:params:xml:ns:caldav}read-free-busy",
|
||||
principal: "{DAV:}authenticated",
|
||||
protected: true,
|
||||
},
|
||||
];
|
||||
expect(getCalendarVisibility(acl)).toBe("private");
|
||||
});
|
||||
privilege: '{urn:ietf:params:xml:ns:caldav}read-free-busy',
|
||||
principal: '{DAV:}authenticated',
|
||||
protected: true
|
||||
}
|
||||
]
|
||||
expect(getCalendarVisibility(acl)).toBe('private')
|
||||
})
|
||||
|
||||
it("returns 'private' when {DAV:}authenticated is not present", () => {
|
||||
const acl: AclEntry[] = [
|
||||
{
|
||||
privilege: "{DAV:}read",
|
||||
principal: "principals/users/123",
|
||||
protected: true,
|
||||
},
|
||||
];
|
||||
expect(getCalendarVisibility(acl)).toBe("private");
|
||||
});
|
||||
privilege: '{DAV:}read',
|
||||
principal: 'principals/users/123',
|
||||
protected: true
|
||||
}
|
||||
]
|
||||
expect(getCalendarVisibility(acl)).toBe('private')
|
||||
})
|
||||
|
||||
it("ignores non-authenticated principals and still returns 'private'", () => {
|
||||
const acl: AclEntry[] = [
|
||||
{
|
||||
privilege: "{DAV:}read",
|
||||
principal: "principals/users/other",
|
||||
protected: true,
|
||||
privilege: '{DAV:}read',
|
||||
principal: 'principals/users/other',
|
||||
protected: true
|
||||
},
|
||||
{
|
||||
privilege: "{DAV:}write",
|
||||
principal: "principals/users/other",
|
||||
protected: true,
|
||||
},
|
||||
];
|
||||
expect(getCalendarVisibility(acl)).toBe("private");
|
||||
});
|
||||
privilege: '{DAV:}write',
|
||||
principal: 'principals/users/other',
|
||||
protected: true
|
||||
}
|
||||
]
|
||||
expect(getCalendarVisibility(acl)).toBe('private')
|
||||
})
|
||||
|
||||
it("stops early when it finds a {DAV:}read for {DAV:}authenticated", () => {
|
||||
it('stops early when it finds a {DAV:}read for {DAV:}authenticated', () => {
|
||||
const acl: AclEntry[] = [
|
||||
{
|
||||
privilege: "{DAV:}read",
|
||||
principal: "{DAV:}authenticated",
|
||||
protected: true,
|
||||
privilege: '{DAV:}read',
|
||||
principal: '{DAV:}authenticated',
|
||||
protected: true
|
||||
},
|
||||
{
|
||||
privilege: "{DAV:}write",
|
||||
principal: "{DAV:}authenticated",
|
||||
protected: true,
|
||||
},
|
||||
];
|
||||
expect(getCalendarVisibility(acl)).toBe("public");
|
||||
});
|
||||
});
|
||||
privilege: '{DAV:}write',
|
||||
principal: '{DAV:}authenticated',
|
||||
protected: true
|
||||
}
|
||||
]
|
||||
expect(getCalendarVisibility(acl)).toBe('public')
|
||||
})
|
||||
})
|
||||
|
||||
jest.mock("twake-i18n", () => ({
|
||||
useI18n: () => ({ t: (key: string) => key }),
|
||||
}));
|
||||
jest.mock('twake-i18n', () => ({
|
||||
useI18n: () => ({ t: (key: string) => key })
|
||||
}))
|
||||
|
||||
const baseEvent: CalendarEvent = {
|
||||
uid: "event-1",
|
||||
calId: "user1/cal1",
|
||||
title: "Test Event",
|
||||
start: "2024-01-15T10:00:00",
|
||||
end: "2024-01-15T11:00:00",
|
||||
uid: 'event-1',
|
||||
calId: 'user1/cal1',
|
||||
title: 'Test Event',
|
||||
start: '2024-01-15T10:00:00',
|
||||
end: '2024-01-15T11:00:00',
|
||||
allday: false,
|
||||
organizer: { cal_address: "owner@example.com" } as userOrganiser,
|
||||
color: { light: "#FF0000" },
|
||||
class: "PUBLIC",
|
||||
} as unknown as CalendarEvent;
|
||||
organizer: { cal_address: 'owner@example.com' } as userOrganiser,
|
||||
color: { light: '#FF0000' },
|
||||
class: 'PUBLIC'
|
||||
} as unknown as CalendarEvent
|
||||
|
||||
const makeCalendar = (overrides: Partial<Calendar> = {}): Calendar =>
|
||||
({
|
||||
id: "user1/cal1",
|
||||
name: "Test Calendar",
|
||||
id: 'user1/cal1',
|
||||
name: 'Test Calendar',
|
||||
delegated: false,
|
||||
access: {} as DelegationAccess,
|
||||
owner: { emails: ["alice@example.com"], firstname: "Alice" },
|
||||
color: { light: "#FF0000" },
|
||||
owner: { emails: ['alice@example.com'], firstname: 'Alice' },
|
||||
color: { light: '#FF0000' },
|
||||
events: {},
|
||||
...overrides,
|
||||
}) as Calendar;
|
||||
...overrides
|
||||
}) as Calendar
|
||||
|
||||
const callFormat = (
|
||||
event: CalendarEvent,
|
||||
calendars: Record<string, Calendar>,
|
||||
userAddress = "alice@example.com",
|
||||
userId = "user1"
|
||||
userAddress = 'alice@example.com',
|
||||
userId = 'user1'
|
||||
) =>
|
||||
eventToFullCalendarFormat([event], [], userId, userAddress, false, calendars);
|
||||
eventToFullCalendarFormat([event], [], userId, userAddress, false, calendars)
|
||||
|
||||
describe("eventToFullCalendarFormat - editable flag", () => {
|
||||
describe("personal calendar (non-delegated)", () => {
|
||||
const calendar = makeCalendar({ delegated: false });
|
||||
describe('eventToFullCalendarFormat - editable flag', () => {
|
||||
describe('personal calendar (non-delegated)', () => {
|
||||
const calendar = makeCalendar({ delegated: false })
|
||||
|
||||
it("is editable when user is organizer", () => {
|
||||
it('is editable when user is organizer', () => {
|
||||
const event = {
|
||||
...baseEvent,
|
||||
organizer: { cal_address: "alice@example.com" },
|
||||
} as CalendarEvent;
|
||||
const [result] = callFormat(event, { "user1/cal1": calendar });
|
||||
expect(result.editable).toBe(true);
|
||||
});
|
||||
organizer: { cal_address: 'alice@example.com' }
|
||||
} as CalendarEvent
|
||||
const [result] = callFormat(event, { 'user1/cal1': calendar })
|
||||
expect(result.editable).toBe(true)
|
||||
})
|
||||
|
||||
it("is not editable when user is not organizer", () => {
|
||||
it('is not editable when user is not organizer', () => {
|
||||
const event = {
|
||||
...baseEvent,
|
||||
organizer: { cal_address: "other@example.com" },
|
||||
} as CalendarEvent;
|
||||
const [result] = callFormat(event, { "user1/cal1": calendar });
|
||||
expect(result.editable).toBe(false);
|
||||
});
|
||||
organizer: { cal_address: 'other@example.com' }
|
||||
} as CalendarEvent
|
||||
const [result] = callFormat(event, { 'user1/cal1': calendar })
|
||||
expect(result.editable).toBe(false)
|
||||
})
|
||||
|
||||
it("is not editable when pending", () => {
|
||||
it('is not editable when pending', () => {
|
||||
const event = {
|
||||
...baseEvent,
|
||||
organizer: { cal_address: "alice@example.com" },
|
||||
} as CalendarEvent;
|
||||
organizer: { cal_address: 'alice@example.com' }
|
||||
} as CalendarEvent
|
||||
const [result] = eventToFullCalendarFormat(
|
||||
[event],
|
||||
[],
|
||||
"user1",
|
||||
"alice@example.com",
|
||||
'user1',
|
||||
'alice@example.com',
|
||||
true,
|
||||
{ "user1/cal1": calendar }
|
||||
);
|
||||
expect(result.editable).toBe(false);
|
||||
});
|
||||
});
|
||||
{ 'user1/cal1': calendar }
|
||||
)
|
||||
expect(result.editable).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("delegated calendar", () => {
|
||||
describe('delegated calendar', () => {
|
||||
const writeDelegatedCalendar = makeCalendar({
|
||||
id: "user2/cal1",
|
||||
id: 'user2/cal1',
|
||||
delegated: true,
|
||||
access: { write: true } as DelegationAccess,
|
||||
owner: { emails: ["owner@example.com"], firstname: "Owner" },
|
||||
});
|
||||
owner: { emails: ['owner@example.com'], firstname: 'Owner' }
|
||||
})
|
||||
const readDelegatedCalendar = makeCalendar({
|
||||
id: "user2/cal1",
|
||||
id: 'user2/cal1',
|
||||
delegated: true,
|
||||
access: { read: true } as DelegationAccess,
|
||||
owner: { emails: ["owner@example.com"], firstname: "Owner" },
|
||||
});
|
||||
const event = { ...baseEvent, calId: "user2/cal1" };
|
||||
owner: { emails: ['owner@example.com'], firstname: 'Owner' }
|
||||
})
|
||||
const event = { ...baseEvent, calId: 'user2/cal1' }
|
||||
|
||||
it("is editable when owner is organizer and delegated user has write rights", () => {
|
||||
it('is editable when owner is organizer and delegated user has write rights', () => {
|
||||
const [result] = callFormat(
|
||||
{
|
||||
...event,
|
||||
organizer: { cal_address: "owner@example.com" },
|
||||
organizer: { cal_address: 'owner@example.com' }
|
||||
} as CalendarEvent,
|
||||
{ "user2/cal1": writeDelegatedCalendar }
|
||||
);
|
||||
expect(result.editable).toBe(true);
|
||||
});
|
||||
{ 'user2/cal1': writeDelegatedCalendar }
|
||||
)
|
||||
expect(result.editable).toBe(true)
|
||||
})
|
||||
|
||||
it("is not editable when owner is organizer but delegated user only has read access", () => {
|
||||
it('is not editable when owner is organizer but delegated user only has read access', () => {
|
||||
const [result] = callFormat(
|
||||
{
|
||||
...event,
|
||||
organizer: { cal_address: "owner@example.com" },
|
||||
organizer: { cal_address: 'owner@example.com' }
|
||||
} as CalendarEvent,
|
||||
{ "user2/cal1": readDelegatedCalendar }
|
||||
);
|
||||
expect(result.editable).toBe(false);
|
||||
});
|
||||
{ 'user2/cal1': readDelegatedCalendar }
|
||||
)
|
||||
expect(result.editable).toBe(false)
|
||||
})
|
||||
|
||||
it("is not editable when owner is not organizer", () => {
|
||||
it('is not editable when owner is not organizer', () => {
|
||||
const [result] = callFormat(
|
||||
{
|
||||
...event,
|
||||
organizer: { cal_address: "someone-else@example.com" },
|
||||
organizer: { cal_address: 'someone-else@example.com' }
|
||||
} as CalendarEvent,
|
||||
{ "user2/cal1": writeDelegatedCalendar }
|
||||
);
|
||||
expect(result.editable).toBe(false);
|
||||
});
|
||||
{ 'user2/cal1': writeDelegatedCalendar }
|
||||
)
|
||||
expect(result.editable).toBe(false)
|
||||
})
|
||||
|
||||
it("is not editable when pending even if owner is organizer", () => {
|
||||
it('is not editable when pending even if owner is organizer', () => {
|
||||
const [result] = eventToFullCalendarFormat(
|
||||
[
|
||||
{
|
||||
...event,
|
||||
organizer: { cal_address: "owner@example.com" },
|
||||
} as CalendarEvent,
|
||||
organizer: { cal_address: 'owner@example.com' }
|
||||
} as CalendarEvent
|
||||
],
|
||||
[],
|
||||
"user1",
|
||||
"alice@example.com",
|
||||
'user1',
|
||||
'alice@example.com',
|
||||
true,
|
||||
{ "user2/cal1": writeDelegatedCalendar }
|
||||
);
|
||||
expect(result.editable).toBe(false);
|
||||
});
|
||||
{ 'user2/cal1': writeDelegatedCalendar }
|
||||
)
|
||||
expect(result.editable).toBe(false)
|
||||
})
|
||||
|
||||
it("uses owner email not logged-in user email for organizer check", () => {
|
||||
it('uses owner email not logged-in user email for organizer check', () => {
|
||||
// logged-in user is alice, owner is owner@example.com
|
||||
// organizer matches owner → editable
|
||||
const [result] = callFormat(
|
||||
{
|
||||
...event,
|
||||
organizer: { cal_address: "owner@example.com" },
|
||||
organizer: { cal_address: 'owner@example.com' }
|
||||
} as CalendarEvent,
|
||||
{ "user2/cal1": writeDelegatedCalendar },
|
||||
"alice@example.com" // logged-in user, should NOT be used for delegated
|
||||
);
|
||||
expect(result.editable).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
{ 'user2/cal1': writeDelegatedCalendar },
|
||||
'alice@example.com' // logged-in user, should NOT be used for delegated
|
||||
)
|
||||
expect(result.editable).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,120 +1,120 @@
|
||||
import { createEventContext } from "@/features/Events/createEventContext";
|
||||
import { Calendar } from "@/features/Calendars/CalendarTypes";
|
||||
import { CalendarEvent } from "@/features/Events/EventsTypes";
|
||||
import { userData } from "@/features/User/userDataTypes";
|
||||
import { userAttendee } from "@/features/User/models/attendee";
|
||||
import { createEventContext } from '@/features/Events/createEventContext'
|
||||
import { Calendar } from '@/features/Calendars/CalendarTypes'
|
||||
import { CalendarEvent } from '@/features/Events/EventsTypes'
|
||||
import { userData } from '@/features/User/userDataTypes'
|
||||
import { userAttendee } from '@/features/User/models/attendee'
|
||||
|
||||
const makeUser = (email: string): userData => ({
|
||||
email,
|
||||
given_name: "Alice",
|
||||
family_name: "User",
|
||||
name: "Alice User",
|
||||
sid: "user1",
|
||||
sub: "user1",
|
||||
});
|
||||
given_name: 'Alice',
|
||||
family_name: 'User',
|
||||
name: 'Alice User',
|
||||
sid: 'user1',
|
||||
sub: 'user1'
|
||||
})
|
||||
|
||||
const makeEvent = (overrides: Partial<CalendarEvent> = {}): CalendarEvent =>
|
||||
({
|
||||
uid: "event-1",
|
||||
calId: "user1/cal1",
|
||||
title: "Test Event",
|
||||
start: "2024-01-15T10:00:00",
|
||||
end: "2024-01-15T11:00:00",
|
||||
organizer: { cal_address: "alice@example.com" },
|
||||
uid: 'event-1',
|
||||
calId: 'user1/cal1',
|
||||
title: 'Test Event',
|
||||
start: '2024-01-15T10:00:00',
|
||||
end: '2024-01-15T11:00:00',
|
||||
organizer: { cal_address: 'alice@example.com' },
|
||||
attendee: [
|
||||
{ cal_address: "alice@example.com", partstat: "ACCEPTED" },
|
||||
{ cal_address: "owner@example.com", partstat: "ACCEPTED" },
|
||||
{ cal_address: "other@example.com", partstat: "NEEDS-ACTION" },
|
||||
{ cal_address: 'alice@example.com', partstat: 'ACCEPTED' },
|
||||
{ cal_address: 'owner@example.com', partstat: 'ACCEPTED' },
|
||||
{ cal_address: 'other@example.com', partstat: 'NEEDS-ACTION' }
|
||||
],
|
||||
...overrides,
|
||||
}) as CalendarEvent;
|
||||
...overrides
|
||||
}) as CalendarEvent
|
||||
|
||||
const makeCalendar = (overrides: Partial<Calendar> = {}): Calendar =>
|
||||
({
|
||||
id: "user1/cal1",
|
||||
name: "Test Calendar",
|
||||
id: 'user1/cal1',
|
||||
name: 'Test Calendar',
|
||||
delegated: false,
|
||||
owner: { emails: ["alice@example.com"], firstname: "Alice" },
|
||||
owner: { emails: ['alice@example.com'], firstname: 'Alice' },
|
||||
events: {},
|
||||
...overrides,
|
||||
}) as Calendar;
|
||||
...overrides
|
||||
}) as Calendar
|
||||
|
||||
describe("createEventContext", () => {
|
||||
describe("non-delegated calendar", () => {
|
||||
const calendar = makeCalendar({ delegated: false });
|
||||
const user = makeUser("alice@example.com");
|
||||
describe('createEventContext', () => {
|
||||
describe('non-delegated calendar', () => {
|
||||
const calendar = makeCalendar({ delegated: false })
|
||||
const user = makeUser('alice@example.com')
|
||||
|
||||
it("finds currentUserAttendee by logged-in user email", () => {
|
||||
const event = makeEvent();
|
||||
const ctx = createEventContext(event, calendar, user);
|
||||
expect(ctx.currentUserAttendee?.cal_address).toBe("alice@example.com");
|
||||
});
|
||||
it('finds currentUserAttendee by logged-in user email', () => {
|
||||
const event = makeEvent()
|
||||
const ctx = createEventContext(event, calendar, user)
|
||||
expect(ctx.currentUserAttendee?.cal_address).toBe('alice@example.com')
|
||||
})
|
||||
|
||||
it("returns null currentUserAttendee when user is not an attendee", () => {
|
||||
const event = makeEvent({ attendee: [] });
|
||||
const ctx = createEventContext(event, calendar, user);
|
||||
expect(ctx.currentUserAttendee).toBeUndefined();
|
||||
});
|
||||
it('returns null currentUserAttendee when user is not an attendee', () => {
|
||||
const event = makeEvent({ attendee: [] })
|
||||
const ctx = createEventContext(event, calendar, user)
|
||||
expect(ctx.currentUserAttendee).toBeUndefined()
|
||||
})
|
||||
|
||||
it("isOwn is true when user email is in owner emails", () => {
|
||||
const ctx = createEventContext(makeEvent(), calendar, user);
|
||||
expect(ctx.isOwn).toBe(true);
|
||||
});
|
||||
it('isOwn is true when user email is in owner emails', () => {
|
||||
const ctx = createEventContext(makeEvent(), calendar, user)
|
||||
expect(ctx.isOwn).toBe(true)
|
||||
})
|
||||
|
||||
it("isOwn is false when user email is not in owner emails", () => {
|
||||
const other = makeUser("other@example.com");
|
||||
const ctx = createEventContext(makeEvent(), calendar, other);
|
||||
expect(ctx.isOwn).toBe(false);
|
||||
});
|
||||
});
|
||||
it('isOwn is false when user email is not in owner emails', () => {
|
||||
const other = makeUser('other@example.com')
|
||||
const ctx = createEventContext(makeEvent(), calendar, other)
|
||||
expect(ctx.isOwn).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("delegated calendar", () => {
|
||||
describe('delegated calendar', () => {
|
||||
const calendar = makeCalendar({
|
||||
id: "user2/cal1",
|
||||
id: 'user2/cal1',
|
||||
delegated: true,
|
||||
owner: { emails: ["owner@example.com"], firstname: "Owner" },
|
||||
});
|
||||
const user = makeUser("alice@example.com"); // logged-in user, not the owner
|
||||
owner: { emails: ['owner@example.com'], firstname: 'Owner' }
|
||||
})
|
||||
const user = makeUser('alice@example.com') // logged-in user, not the owner
|
||||
|
||||
it("finds currentUserAttendee by owner email, not logged-in user email", () => {
|
||||
const event = makeEvent({ calId: "user2/cal1" });
|
||||
const ctx = createEventContext(event, calendar, user);
|
||||
expect(ctx.currentUserAttendee?.cal_address).toBe("owner@example.com");
|
||||
});
|
||||
it('finds currentUserAttendee by owner email, not logged-in user email', () => {
|
||||
const event = makeEvent({ calId: 'user2/cal1' })
|
||||
const ctx = createEventContext(event, calendar, user)
|
||||
expect(ctx.currentUserAttendee?.cal_address).toBe('owner@example.com')
|
||||
})
|
||||
|
||||
it("does not find logged-in user as attendee for delegated calendar", () => {
|
||||
it('does not find logged-in user as attendee for delegated calendar', () => {
|
||||
const event = makeEvent({
|
||||
calId: "user2/cal1",
|
||||
calId: 'user2/cal1',
|
||||
attendee: [
|
||||
{
|
||||
cal_address: "owner@example.com",
|
||||
partstat: "ACCEPTED",
|
||||
} as userAttendee,
|
||||
],
|
||||
});
|
||||
const ctx = createEventContext(event, calendar, user);
|
||||
cal_address: 'owner@example.com',
|
||||
partstat: 'ACCEPTED'
|
||||
} as userAttendee
|
||||
]
|
||||
})
|
||||
const ctx = createEventContext(event, calendar, user)
|
||||
// currentUserAttendee should be owner's, not alice's
|
||||
expect(ctx.currentUserAttendee?.cal_address).toBe("owner@example.com");
|
||||
});
|
||||
expect(ctx.currentUserAttendee?.cal_address).toBe('owner@example.com')
|
||||
})
|
||||
|
||||
it("returns undefined currentUserAttendee when owner is not an attendee", () => {
|
||||
it('returns undefined currentUserAttendee when owner is not an attendee', () => {
|
||||
const event = makeEvent({
|
||||
calId: "user2/cal1",
|
||||
calId: 'user2/cal1',
|
||||
attendee: [
|
||||
{
|
||||
cal_address: "alice@example.com",
|
||||
partstat: "ACCEPTED",
|
||||
} as userAttendee,
|
||||
],
|
||||
});
|
||||
const ctx = createEventContext(event, calendar, user);
|
||||
expect(ctx.currentUserAttendee).toBeUndefined();
|
||||
});
|
||||
cal_address: 'alice@example.com',
|
||||
partstat: 'ACCEPTED'
|
||||
} as userAttendee
|
||||
]
|
||||
})
|
||||
const ctx = createEventContext(event, calendar, user)
|
||||
expect(ctx.currentUserAttendee).toBeUndefined()
|
||||
})
|
||||
|
||||
it("passes event through unchanged", () => {
|
||||
const event = makeEvent({ calId: "user2/cal1" });
|
||||
const ctx = createEventContext(event, calendar, user);
|
||||
expect(ctx.event).toBe(event);
|
||||
});
|
||||
});
|
||||
});
|
||||
it('passes event through unchanged', () => {
|
||||
const event = makeEvent({ calId: 'user2/cal1' })
|
||||
const ctx = createEventContext(event, calendar, user)
|
||||
expect(ctx.event).toBe(event)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
import { getCalendarRange } from "@/utils/dateUtils";
|
||||
import { getCalendarRange } from '@/utils/dateUtils'
|
||||
|
||||
describe("getCalendarRange", () => {
|
||||
it("Nov 2025 (5 weeks): 2025-10-27 to 2025-11-30", () => {
|
||||
describe('getCalendarRange', () => {
|
||||
it('Nov 2025 (5 weeks): 2025-10-27 to 2025-11-30', () => {
|
||||
const { start, end } = getCalendarRange(
|
||||
new Date("2025-11-01T00:00:00.000Z")
|
||||
);
|
||||
expect(start.toISOString().slice(0, 10)).toBe("2025-10-27");
|
||||
expect(end.toISOString().slice(0, 10)).toBe("2025-11-30");
|
||||
});
|
||||
new Date('2025-11-01T00:00:00.000Z')
|
||||
)
|
||||
expect(start.toISOString().slice(0, 10)).toBe('2025-10-27')
|
||||
expect(end.toISOString().slice(0, 10)).toBe('2025-11-30')
|
||||
})
|
||||
|
||||
it("Dec 2025 (6 weeks): end Sunday 2026-01-04", () => {
|
||||
it('Dec 2025 (6 weeks): end Sunday 2026-01-04', () => {
|
||||
const { start, end } = getCalendarRange(
|
||||
new Date("2025-12-01T00:00:00.000Z")
|
||||
);
|
||||
expect(start.toISOString().slice(0, 10)).toBe("2025-12-01");
|
||||
expect(end.toISOString().slice(0, 10)).toBe("2026-01-04");
|
||||
});
|
||||
new Date('2025-12-01T00:00:00.000Z')
|
||||
)
|
||||
expect(start.toISOString().slice(0, 10)).toBe('2025-12-01')
|
||||
expect(end.toISOString().slice(0, 10)).toBe('2026-01-04')
|
||||
})
|
||||
|
||||
it("Feb 2025 (short month): end Sunday 2025-03-02", () => {
|
||||
it('Feb 2025 (short month): end Sunday 2025-03-02', () => {
|
||||
const { start, end } = getCalendarRange(
|
||||
new Date("2025-02-01T00:00:00.000Z")
|
||||
);
|
||||
expect(start.toISOString().slice(0, 10)).toBe("2025-01-27");
|
||||
expect(end.toISOString().slice(0, 10)).toBe("2025-03-02");
|
||||
});
|
||||
new Date('2025-02-01T00:00:00.000Z')
|
||||
)
|
||||
expect(start.toISOString().slice(0, 10)).toBe('2025-01-27')
|
||||
expect(end.toISOString().slice(0, 10)).toBe('2025-03-02')
|
||||
})
|
||||
|
||||
it("sets boundary times correctly (start 00:00, end 23:59)", () => {
|
||||
it('sets boundary times correctly (start 00:00, end 23:59)', () => {
|
||||
const { start, end } = getCalendarRange(
|
||||
new Date("2025-11-01T00:00:00.000Z")
|
||||
);
|
||||
expect(start.getHours()).toBe(0);
|
||||
expect(start.getMinutes()).toBe(0);
|
||||
expect(start.getSeconds()).toBe(0);
|
||||
expect(end.getHours()).toBe(23);
|
||||
expect(end.getMinutes()).toBe(59);
|
||||
});
|
||||
});
|
||||
new Date('2025-11-01T00:00:00.000Z')
|
||||
)
|
||||
expect(start.getHours()).toBe(0)
|
||||
expect(start.getMinutes()).toBe(0)
|
||||
expect(start.getSeconds()).toBe(0)
|
||||
expect(end.getHours()).toBe(23)
|
||||
expect(end.getMinutes()).toBe(59)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,99 +1,99 @@
|
||||
import { RootState } from "@/app/store";
|
||||
import { Calendar } from "@/features/Calendars/CalendarTypes";
|
||||
import { findCalendarById } from "@/utils";
|
||||
import { RootState } from '@/app/store'
|
||||
import { Calendar } from '@/features/Calendars/CalendarTypes'
|
||||
import { findCalendarById } from '@/utils'
|
||||
|
||||
describe("findCalendarById", () => {
|
||||
describe('findCalendarById', () => {
|
||||
const mockCalendar1: Calendar = {
|
||||
id: "cal1",
|
||||
name: "Personal",
|
||||
color: { light: "#FF0000" },
|
||||
} as unknown as Calendar;
|
||||
id: 'cal1',
|
||||
name: 'Personal',
|
||||
color: { light: '#FF0000' }
|
||||
} as unknown as Calendar
|
||||
|
||||
const mockCalendar2: Calendar = {
|
||||
id: "cal2",
|
||||
name: "Work",
|
||||
color: { light: "#0000FF" },
|
||||
} as unknown as Calendar;
|
||||
id: 'cal2',
|
||||
name: 'Work',
|
||||
color: { light: '#0000FF' }
|
||||
} as unknown as Calendar
|
||||
|
||||
const mockTempCalendar: Calendar = {
|
||||
id: "temp1",
|
||||
name: "Temporary",
|
||||
color: { light: "#00FF00" },
|
||||
} as unknown as Calendar;
|
||||
id: 'temp1',
|
||||
name: 'Temporary',
|
||||
color: { light: '#00FF00' }
|
||||
} as unknown as Calendar
|
||||
|
||||
const mockState = {
|
||||
calendars: {
|
||||
list: {
|
||||
cal1: mockCalendar1,
|
||||
cal2: mockCalendar2,
|
||||
cal2: mockCalendar2
|
||||
},
|
||||
templist: {
|
||||
temp1: mockTempCalendar,
|
||||
},
|
||||
},
|
||||
} as unknown as Partial<RootState>;
|
||||
temp1: mockTempCalendar
|
||||
}
|
||||
}
|
||||
} as unknown as Partial<RootState>
|
||||
|
||||
it("should find calendar in main list", () => {
|
||||
const result = findCalendarById(mockState, "cal1");
|
||||
it('should find calendar in main list', () => {
|
||||
const result = findCalendarById(mockState, 'cal1')
|
||||
|
||||
expect(result?.calendar).toEqual(mockCalendar1);
|
||||
expect(result?.type).toBeUndefined();
|
||||
});
|
||||
expect(result?.calendar).toEqual(mockCalendar1)
|
||||
expect(result?.type).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should find calendar in temp list", () => {
|
||||
const result = findCalendarById(mockState, "temp1");
|
||||
it('should find calendar in temp list', () => {
|
||||
const result = findCalendarById(mockState, 'temp1')
|
||||
|
||||
expect(result?.calendar).toEqual(mockTempCalendar);
|
||||
expect(result?.type).toBe("temp");
|
||||
});
|
||||
expect(result?.calendar).toEqual(mockTempCalendar)
|
||||
expect(result?.type).toBe('temp')
|
||||
})
|
||||
|
||||
it("should not return calendar for non-existent id", () => {
|
||||
const result = findCalendarById(mockState, "nonexistent");
|
||||
it('should not return calendar for non-existent id', () => {
|
||||
const result = findCalendarById(mockState, 'nonexistent')
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should not return calendar for empty string", () => {
|
||||
const result = findCalendarById(mockState, "");
|
||||
it('should not return calendar for empty string', () => {
|
||||
const result = findCalendarById(mockState, '')
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should prioritize temp list over main list", () => {
|
||||
it('should prioritize temp list over main list', () => {
|
||||
const stateWithDuplicate = {
|
||||
calendars: {
|
||||
list: {
|
||||
dup1: mockCalendar1,
|
||||
dup1: mockCalendar1
|
||||
},
|
||||
templist: {
|
||||
dup1: mockTempCalendar,
|
||||
},
|
||||
},
|
||||
} as unknown as Partial<RootState>;
|
||||
dup1: mockTempCalendar
|
||||
}
|
||||
}
|
||||
} as unknown as Partial<RootState>
|
||||
|
||||
const result = findCalendarById(stateWithDuplicate, "dup1");
|
||||
const result = findCalendarById(stateWithDuplicate, 'dup1')
|
||||
|
||||
expect(result?.calendar).toEqual(mockTempCalendar);
|
||||
expect(result?.type).toBe("temp");
|
||||
});
|
||||
expect(result?.calendar).toEqual(mockTempCalendar)
|
||||
expect(result?.type).toBe('temp')
|
||||
})
|
||||
|
||||
it("should handle undefined list or templist", () => {
|
||||
it('should handle undefined list or templist', () => {
|
||||
const stateWithPartialCalendars = {
|
||||
calendars: {
|
||||
list: undefined,
|
||||
templist: { temp1: mockTempCalendar },
|
||||
},
|
||||
} as unknown as Partial<RootState>;
|
||||
templist: { temp1: mockTempCalendar }
|
||||
}
|
||||
} as unknown as Partial<RootState>
|
||||
|
||||
const result = findCalendarById(stateWithPartialCalendars, "temp1");
|
||||
expect(result?.calendar).toEqual(mockTempCalendar);
|
||||
});
|
||||
const result = findCalendarById(stateWithPartialCalendars, 'temp1')
|
||||
expect(result?.calendar).toEqual(mockTempCalendar)
|
||||
})
|
||||
|
||||
it("should handle missing calendars state", () => {
|
||||
const emptyState = {};
|
||||
it('should handle missing calendars state', () => {
|
||||
const emptyState = {}
|
||||
|
||||
const result = findCalendarById(emptyState, "cal1");
|
||||
const result = findCalendarById(emptyState, 'cal1')
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,92 +1,92 @@
|
||||
import { userData } from "@/features/User/userDataTypes";
|
||||
import { getUserDisplayName } from "@/utils/userUtils";
|
||||
import { userData } from '@/features/User/userDataTypes'
|
||||
import { getUserDisplayName } from '@/utils/userUtils'
|
||||
|
||||
describe("userUtils", () => {
|
||||
describe("getUserDisplayName", () => {
|
||||
it("returns full name when user has name and family_name", () => {
|
||||
describe('userUtils', () => {
|
||||
describe('getUserDisplayName', () => {
|
||||
it('returns full name when user has name and family_name', () => {
|
||||
const user: userData = {
|
||||
sub: "test",
|
||||
email: "test@test.com",
|
||||
family_name: "Doe",
|
||||
given_name: "John",
|
||||
name: "John",
|
||||
sid: "mockSid",
|
||||
};
|
||||
sub: 'test',
|
||||
email: 'test@test.com',
|
||||
family_name: 'Doe',
|
||||
given_name: 'John',
|
||||
name: 'John',
|
||||
sid: 'mockSid'
|
||||
}
|
||||
|
||||
expect(getUserDisplayName(user)).toBe("John Doe");
|
||||
});
|
||||
expect(getUserDisplayName(user)).toBe('John Doe')
|
||||
})
|
||||
|
||||
it("returns email when user does not have name and family_name", () => {
|
||||
it('returns email when user does not have name and family_name', () => {
|
||||
const user: userData = {
|
||||
sub: "test",
|
||||
email: "test@test.com",
|
||||
family_name: "",
|
||||
given_name: "",
|
||||
name: "",
|
||||
sid: "mockSid",
|
||||
};
|
||||
sub: 'test',
|
||||
email: 'test@test.com',
|
||||
family_name: '',
|
||||
given_name: '',
|
||||
name: '',
|
||||
sid: 'mockSid'
|
||||
}
|
||||
|
||||
expect(getUserDisplayName(user)).toBe("test@test.com");
|
||||
});
|
||||
expect(getUserDisplayName(user)).toBe('test@test.com')
|
||||
})
|
||||
|
||||
it("returns email when user has only email", () => {
|
||||
it('returns email when user has only email', () => {
|
||||
const user: userData = {
|
||||
sub: "test",
|
||||
email: "user@example.com",
|
||||
family_name: "",
|
||||
given_name: "",
|
||||
name: "",
|
||||
sid: "mockSid",
|
||||
};
|
||||
sub: 'test',
|
||||
email: 'user@example.com',
|
||||
family_name: '',
|
||||
given_name: '',
|
||||
name: '',
|
||||
sid: 'mockSid'
|
||||
}
|
||||
|
||||
expect(getUserDisplayName(user)).toBe("user@example.com");
|
||||
});
|
||||
expect(getUserDisplayName(user)).toBe('user@example.com')
|
||||
})
|
||||
|
||||
it("returns empty string when user is null", () => {
|
||||
expect(getUserDisplayName(null)).toBe("");
|
||||
});
|
||||
it('returns empty string when user is null', () => {
|
||||
expect(getUserDisplayName(null)).toBe('')
|
||||
})
|
||||
|
||||
it("returns empty string when user is undefined", () => {
|
||||
expect(getUserDisplayName(undefined)).toBe("");
|
||||
});
|
||||
it('returns empty string when user is undefined', () => {
|
||||
expect(getUserDisplayName(undefined)).toBe('')
|
||||
})
|
||||
|
||||
it("returns empty string when user has no name, family_name, or email", () => {
|
||||
it('returns empty string when user has no name, family_name, or email', () => {
|
||||
const user: userData = {
|
||||
sub: "test",
|
||||
email: "",
|
||||
family_name: "",
|
||||
given_name: "",
|
||||
name: "",
|
||||
sid: "mockSid",
|
||||
};
|
||||
sub: 'test',
|
||||
email: '',
|
||||
family_name: '',
|
||||
given_name: '',
|
||||
name: '',
|
||||
sid: 'mockSid'
|
||||
}
|
||||
|
||||
expect(getUserDisplayName(user)).toBe("");
|
||||
});
|
||||
expect(getUserDisplayName(user)).toBe('')
|
||||
})
|
||||
|
||||
it("handles user with only name (no family_name)", () => {
|
||||
it('handles user with only name (no family_name)', () => {
|
||||
const user: userData = {
|
||||
sub: "test",
|
||||
email: "test@test.com",
|
||||
family_name: "",
|
||||
given_name: "John",
|
||||
name: "John",
|
||||
sid: "mockSid",
|
||||
};
|
||||
sub: 'test',
|
||||
email: 'test@test.com',
|
||||
family_name: '',
|
||||
given_name: 'John',
|
||||
name: 'John',
|
||||
sid: 'mockSid'
|
||||
}
|
||||
|
||||
expect(getUserDisplayName(user)).toBe("test@test.com");
|
||||
});
|
||||
expect(getUserDisplayName(user)).toBe('test@test.com')
|
||||
})
|
||||
|
||||
it("handles user with only family_name (no name)", () => {
|
||||
it('handles user with only family_name (no name)', () => {
|
||||
const user: userData = {
|
||||
sub: "test",
|
||||
email: "test@test.com",
|
||||
family_name: "Doe",
|
||||
given_name: "",
|
||||
name: "",
|
||||
sid: "mockSid",
|
||||
};
|
||||
sub: 'test',
|
||||
email: 'test@test.com',
|
||||
family_name: 'Doe',
|
||||
given_name: '',
|
||||
name: '',
|
||||
sid: 'mockSid'
|
||||
}
|
||||
|
||||
expect(getUserDisplayName(user)).toBe("test@test.com");
|
||||
});
|
||||
});
|
||||
});
|
||||
expect(getUserDisplayName(user)).toBe('test@test.com')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user