diff --git a/src/__mocks__/response/realEvents.json b/src/__mocks__/response/realEvents.json index 5ab618a0..8736d3bf 100644 --- a/src/__mocks__/response/realEvents.json +++ b/src/__mocks__/response/realEvents.json @@ -1,64 +1 @@ -{ - "events": [ - { - "id": "2b7545a6-ebee-426c-b906-2329bc8d62bd", - "title": "팀 회의", - "date": "2025-08-20", - "startTime": "10:00", - "endTime": "11:00", - "description": "주간 팀 미팅", - "location": "회의실 A", - "category": "업무", - "repeat": { "type": "none", "interval": 0 }, - "notificationTime": 1 - }, - { - "id": "09702fb3-a478-40b3-905e-9ab3c8849dcd", - "title": "점심 약속", - "date": "2025-08-21", - "startTime": "12:30", - "endTime": "13:30", - "description": "동료와 점심 식사", - "location": "회사 근처 식당", - "category": "개인", - "repeat": { "type": "none", "interval": 0 }, - "notificationTime": 1 - }, - { - "id": "da3ca408-836a-4d98-b67a-ca389d07552b", - "title": "프로젝트 마감", - "date": "2025-08-25", - "startTime": "09:00", - "endTime": "18:00", - "description": "분기별 프로젝트 마감", - "location": "사무실", - "category": "업무", - "repeat": { "type": "none", "interval": 0 }, - "notificationTime": 1 - }, - { - "id": "dac62941-69e5-4ec0-98cc-24c2a79a7f81", - "title": "생일 파티", - "date": "2025-08-28", - "startTime": "19:00", - "endTime": "22:00", - "description": "친구 생일 축하", - "location": "친구 집", - "category": "개인", - "repeat": { "type": "none", "interval": 0 }, - "notificationTime": 1 - }, - { - "id": "80d85368-b4a4-47b3-b959-25171d49371f", - "title": "운동", - "date": "2025-08-22", - "startTime": "18:00", - "endTime": "19:00", - "description": "주간 운동", - "location": "헬스장", - "category": "개인", - "repeat": { "type": "none", "interval": 0 }, - "notificationTime": 1 - } - ] -} +{"events":[{"id":"09702fb3-a478-40b3-905e-9ab3c8849dcd","title":"점심 약속","date":"2025-08-21","startTime":"12:30","endTime":"13:30","description":"동료와 점심 식사","location":"회사 근처 식당","category":"개인","repeat":{"type":"none","interval":0},"notificationTime":1},{"id":"da3ca408-836a-4d98-b67a-ca389d07552b","title":"프로젝트 마감","date":"2025-08-25","startTime":"09:00","endTime":"18:00","description":"분기별 프로젝트 마감","location":"사무실","category":"업무","repeat":{"type":"none","interval":0},"notificationTime":1},{"id":"dac62941-69e5-4ec0-98cc-24c2a79a7f81","title":"생일 파티","date":"2025-08-28","startTime":"19:00","endTime":"22:00","description":"친구 생일 축하","location":"친구 집","category":"개인","repeat":{"type":"none","interval":0},"notificationTime":1},{"id":"80d85368-b4a4-47b3-b959-25171d49371f","title":"운동","date":"2025-08-22","startTime":"18:00","endTime":"19:00","description":"주간 운동","location":"헬스장","category":"개인","repeat":{"type":"none","interval":0},"notificationTime":1}]} \ No newline at end of file diff --git a/src/__tests__/hooks/medium.useNotifications.spec.ts b/src/__tests__/hooks/medium.useNotifications.spec.ts index 7f585ea8..5184a15e 100644 --- a/src/__tests__/hooks/medium.useNotifications.spec.ts +++ b/src/__tests__/hooks/medium.useNotifications.spec.ts @@ -5,10 +5,111 @@ import { Event } from '../../types.ts'; import { formatDate } from '../../utils/dateUtils.ts'; import { parseHM } from '../utils.ts'; -it('초기 상태에서는 알림이 없어야 한다', () => {}); +// 각 테스트 전후에 타이머 완전히 리셋 +beforeEach(() => { + vi.useRealTimers(); +}); -it('지정된 시간이 된 경우 알림이 새롭게 생성되어 추가된다', () => {}); +afterEach(() => { + vi.useRealTimers(); +}); -it('index를 기준으로 알림을 적절하게 제거할 수 있다', () => {}); +it('초기 상태에서는 알림이 없어야 한다', () => { + const { result } = renderHook(() => useNotifications([] as Event[])); + expect(result.current.notifications).toEqual([]); + expect(result.current.notifications.length).toBe(0); + expect(result.current.notifiedEvents).toEqual([]); +}); -it('이미 알림이 발생한 이벤트에 대해서는 중복 알림이 발생하지 않아야 한다', () => {}); +it('지정된 시간이 된 경우 알림이 새롭게 생성되어 추가된다', () => { + vi.useFakeTimers(); + const fixedNow = new Date('2025-08-20T09:55:00'); + vi.setSystemTime(fixedNow); + + const events: Event[] = [ + { + id: '1', + title: '회의', + date: formatDate(fixedNow), + startTime: parseHM(fixedNow.getTime() + 5 * 60 * 1000), + notificationTime: 5, + }, + ] as Event[]; + + const { result } = renderHook(() => useNotifications(events)); + expect(result.current.notifications).toEqual([]); + + act(() => { + vi.advanceTimersByTime(1000); + }); + + expect(result.current.notifications.length).toBe(1); + expect(result.current.notifications[0].message).toContain('5분'); + expect(result.current.notifications[0].id).toEqual('1'); +}); + +it('index를 기준으로 알림을 적절하게 제거할 수 있다', () => { + vi.useFakeTimers(); + const fixedNow = new Date('2025-08-20T10:00:00'); + vi.setSystemTime(fixedNow); + + const events: Event[] = [ + { + id: '1', + title: '테스트', + date: formatDate(fixedNow), + startTime: parseHM(fixedNow.getTime() + 5 * 60 * 1000), + notificationTime: 5, + }, + ] as Event[]; + + const { result } = renderHook(() => useNotifications(events)); + + expect(result.current.notifications).toEqual([]); + + act(() => { + vi.advanceTimersByTime(1000); + }); + + expect(result.current.notifications.length).toBe(1); + + act(() => { + result.current.removeNotification(0); + }); + + expect(result.current.notifications.length).toEqual(0); +}); + +it('이미 알림이 발생한 이벤트에 대해서는 중복 알림이 발생하지 않아야 한다', () => { + vi.useFakeTimers(); + const fixedNow = new Date('2025-08-20T11:00:00'); + vi.setSystemTime(fixedNow); + + const events: Event[] = [ + { + id: '1', + title: '중복 테스트', + date: formatDate(fixedNow), + startTime: parseHM(fixedNow.getTime() + 5 * 60 * 1000), + notificationTime: 5, + }, + ] as Event[]; + + const { result } = renderHook(() => useNotifications(events)); + + expect(result.current.notifications).toEqual([]); + + act(() => { + vi.advanceTimersByTime(1000); + }); + + expect(result.current.notifications.length).toBe(1); + expect(result.current.notifiedEvents.length).toBe(1); + + act(() => { + vi.advanceTimersByTime(2000); + }); + + expect(result.current.notifications.length).toBe(1); + expect(result.current.notifiedEvents.length).toBe(1); +}); diff --git a/src/__tests__/unit/easy.dateUtils.spec.ts b/src/__tests__/unit/easy.dateUtils.spec.ts index 967bfacd..d73edbe0 100644 --- a/src/__tests__/unit/easy.dateUtils.spec.ts +++ b/src/__tests__/unit/easy.dateUtils.spec.ts @@ -10,107 +10,251 @@ import { getWeeksAtMonth, isDateInRange, } from '../../utils/dateUtils'; - describe('getDaysInMonth', () => { - it('1월은 31일 수를 반환한다', () => {}); - - it('4월은 30일 일수를 반환한다', () => {}); - - it('윤년의 2월에 대해 29일을 반환한다', () => {}); - - it('평년의 2월에 대해 28일을 반환한다', () => {}); - - it('유효하지 않은 월에 대해 적절히 처리한다', () => {}); + it('1월은 31일 수를 반환한다', () => { + const days = getDaysInMonth(2025, 1); + expect(days).toBe(31); + }); + + it('4월은 30일 일수를 반환한다', () => { + const days = getDaysInMonth(2025, 4); + expect(days).toBe(30); + }); + + it('윤년의 2월에 대해 29일을 반환한다', () => { + const days = getDaysInMonth(2024, 2); + expect(days).toBe(29); + }); + + it('평년의 2월에 대해 28일을 반환한다', () => { + const days = getDaysInMonth(2025, 2); + expect(days).toBe(28); + }); + // JS Date는 month가 1~12 범위를 넘어도 자동 보정 + it('유효하지 않은 월에 대해 적절히 처리한다', () => { + expect(getDaysInMonth(2025, 13)).toBe(31); // 12월 마지막 날 + expect(getDaysInMonth(2025, 0)).toBe(31); // 1월 0일 → 12월 마지막 날 + }); }); describe('getWeekDates', () => { - it('주중의 날짜(수요일)에 대해 올바른 주의 날짜들을 반환한다', () => {}); - - it('주의 시작(월요일)에 대해 올바른 주의 날짜들을 반환한다', () => {}); - - it('주의 끝(일요일)에 대해 올바른 주의 날짜들을 반환한다', () => {}); - - it('연도를 넘어가는 주의 날짜를 정확히 처리한다 (연말)', () => {}); - - it('연도를 넘어가는 주의 날짜를 정확히 처리한다 (연초)', () => {}); - - it('윤년의 2월 29일을 포함한 주를 올바르게 처리한다', () => {}); - - it('월의 마지막 날짜를 포함한 주를 올바르게 처리한다', () => {}); + const expectWeekContains = (inputDate: Date) => { + const weekDates = getWeekDates(inputDate); + expect(weekDates).toHaveLength(7); + const contains = weekDates.some( + (d) => + d.getFullYear() === inputDate.getFullYear() && + d.getMonth() === inputDate.getMonth() && + d.getDate() === inputDate.getDate() + ); + expect(contains).toBe(true); + }; + it('주중의 날짜(수요일)에 대해 올바른 주의 날짜들을 반환한다', () => { + expectWeekContains(new Date('2025-08-20')); + }); + + it('주의 시작(월요일)에 대해 올바른 주의 날짜들을 반환한다', () => { + expectWeekContains(new Date('2025-08-18')); + }); + + it('주의 끝(일요일)에 대해 올바른 주의 날짜들을 반환한다', () => { + expectWeekContains(new Date('2025-08-18')); + }); + + it('연도를 넘어가는 주의 날짜를 정확히 처리한다 (연말)', () => { + expectWeekContains(new Date('2025-08-18')); + }); + + it('연도를 넘어가는 주의 날짜를 정확히 처리한다 (연초)', () => { + expectWeekContains(new Date('2025-08-18')); + }); + + it('윤년의 2월 29일을 포함한 주를 올바르게 처리한다', () => { + expectWeekContains(new Date('2024-02-29')); + }); + + it('월의 마지막 날짜를 포함한 주를 올바르게 처리한다', () => { + expectWeekContains(new Date('2025-04-30')); + }); }); describe('getWeeksAtMonth', () => { - it('2025년 7월 1일의 올바른 주 정보를 반환해야 한다', () => {}); + it('2025년 7월 1일의 올바른 주 정보를 반환해야 한다', () => { + const testWeeksAtMonth = getWeeksAtMonth(new Date('2025-07-01')); + expect(testWeeksAtMonth[0]).toEqual([null, null, 1, 2, 3, 4, 5]); + expect(testWeeksAtMonth[testWeeksAtMonth.length - 1]).toEqual([27, 28, 29, 30, 31, null, null]); + }); }); describe('getEventsForDay', () => { - it('특정 날짜(1일)에 해당하는 이벤트만 정확히 반환한다', () => {}); - - it('해당 날짜에 이벤트가 없을 경우 빈 배열을 반환한다', () => {}); - - it('날짜가 0일 경우 빈 배열을 반환한다', () => {}); - - it('날짜가 32일 이상인 경우 빈 배열을 반환한다', () => {}); + it('특정 날짜(1일)에 해당하는 이벤트만 정확히 반환한다', () => { + const events = [ + { id: '1', title: '회의', date: '2025-08-01' }, + { id: '2', title: '점심', date: '2025-08-01' }, + { id: '3', title: '회의2', date: '2025-08-02' }, + ] as Event[]; + + const result = getEventsForDay(events, 1); + + expect(result).toHaveLength(2); + expect(result.map((e) => e.id)).toEqual(['1', '2']); + }); + it('해당 날짜에 이벤트가 없을 경우 빈 배열을 반환한다', () => { + const events = [{ id: '1', title: '회의', date: '2025-08-01' }] as Event[]; + const result = getEventsForDay(events, 2); + expect(result).toEqual([]); + }); + + it('날짜가 0일 경우 빈 배열을 반환한다', () => { + const events = [{ id: '1', title: '회의', date: '2025-08-01' }] as Event[]; + const result = getEventsForDay(events, 0); + expect(result).toEqual([]); + }); + + it('날짜가 32일 이상인 경우 빈 배열을 반환한다', () => { + const events = [{ id: '1', title: '회의', date: '2025-08-01' }] as Event[]; + const result = getEventsForDay(events, 32); + expect(result).toEqual([]); + }); }); describe('formatWeek', () => { - it('월의 중간 날짜에 대해 올바른 주 정보를 반환한다', () => {}); - - it('월의 첫 주에 대해 올바른 주 정보를 반환한다', () => {}); - - it('월의 마지막 주에 대해 올바른 주 정보를 반환한다', () => {}); - - it('연도가 바뀌는 주에 대해 올바른 주 정보를 반환한다', () => {}); - - it('윤년 2월의 마지막 주에 대해 올바른 주 정보를 반환한다', () => {}); - - it('평년 2월의 마지막 주에 대해 올바른 주 정보를 반환한다', () => {}); + // 목요일 기준으로 해당 월의 첫 목요일을 찾음 + it('월의 중간 날짜에 대해 올바른 주 정보를 반환한다', () => { + expect(formatWeek(new Date('2025-08-21'))).toBe('2025년 8월 3주'); + }); + + it('월의 첫 주에 대해 올바른 주 정보를 반환한다', () => { + expect(formatWeek(new Date('2025-08-01'))).toBe('2025년 7월 5주'); + expect(formatWeek(new Date('2025-08-02'))).toBe('2025년 7월 5주'); + expect(formatWeek(new Date('2025-08-01'))).not.toBe('2025년 8월 1주'); + expect(formatWeek(new Date('2025-08-02'))).not.toBe('2025년 8월 1주'); + }); + + it('월의 마지막 주에 대해 올바른 주 정보를 반환한다', () => { + expect(formatWeek(new Date('2025-09-01'))).toBe('2025년 9월 1주'); + expect(formatWeek(new Date('2025-09-01'))).toBe('2025년 9월 1주'); + }); + + it('연도가 바뀌는 주에 대해 올바른 주 정보를 반환한다', () => { + const date = new Date('2024-12-30'); + const week = formatWeek(date); + expect(typeof week).toBe('string'); + }); + + it('윤년 2월의 마지막 주에 대해 올바른 주 정보를 반환한다', () => { + expect(formatWeek(new Date('2024-02-29'))).toBe('2024년 2월 5주'); + }); + + it('평년 2월의 마지막 주에 대해 올바른 주 정보를 반환한다', () => { + expect(formatWeek(new Date('2025-02-28'))).toBe('2025년 2월 4주'); + }); }); describe('formatMonth', () => { - it("2025년 7월 10일을 '2025년 7월'로 반환한다", () => {}); + it("2025년 7월 10일을 '2025년 7월'로 반환한다", () => { + expect(formatMonth(new Date('2025-07-10'))).toBe('2025년 7월'); + }); }); describe('isDateInRange', () => { - it('범위 내의 날짜 2025-07-10에 대해 true를 반환한다', () => {}); - - it('범위의 시작일 2025-07-01에 대해 true를 반환한다', () => {}); - - it('범위의 종료일 2025-07-31에 대해 true를 반환한다', () => {}); - - it('범위 이전의 날짜 2025-06-30에 대해 false를 반환한다', () => {}); - - it('범위 이후의 날짜 2025-08-01에 대해 false를 반환한다', () => {}); - - it('시작일이 종료일보다 늦은 경우 모든 날짜에 대해 false를 반환한다', () => {}); + it('범위 내의 날짜 2025-07-10에 대해 true를 반환한다', () => { + expect( + isDateInRange(new Date('2025-07-10'), new Date('2025-07-09'), new Date('2025-07-11')) + ).toBe(true); + }); + + it('범위의 시작일 2025-07-01에 대해 true를 반환한다', () => { + expect( + isDateInRange(new Date('2025-07-01'), new Date('2025-07-01'), new Date('2025-07-11')) + ).toBe(true); + }); + + it('범위의 종료일 2025-07-31에 대해 true를 반환한다', () => { + expect( + isDateInRange(new Date('2025-07-10'), new Date('2025-07-09'), new Date('2025-07-31')) + ).toBe(true); + }); + + it('범위 이전의 날짜 2025-06-30에 대해 false를 반환한다', () => { + expect( + isDateInRange(new Date('2025-06-30'), new Date('2025-07-09'), new Date('2025-07-11')) + ).toBe(false); + }); + + it('범위 이후의 날짜 2025-08-01에 대해 false를 반환한다', () => { + expect( + isDateInRange(new Date('2025-07-08'), new Date('2025-07-09'), new Date('2025-08-01')) + ).toBe(false); + }); + + it('시작일이 종료일보다 늦은 경우 모든 날짜에 대해 false를 반환한다', () => { + expect( + isDateInRange(new Date('2025-07-12'), new Date('2025-07-09'), new Date('2025-07-11')) + ).toBe(false); + expect( + isDateInRange(new Date('2025-07-01'), new Date('2025-07-09'), new Date('2025-06-30')) + ).toBe(false); + }); }); describe('fillZero', () => { - it("5를 2자리로 변환하면 '05'를 반환한다", () => {}); - - it("10을 2자리로 변환하면 '10'을 반환한다", () => {}); - - it("3을 3자리로 변환하면 '003'을 반환한다", () => {}); - - it("100을 2자리로 변환하면 '100'을 반환한다", () => {}); - - it("0을 2자리로 변환하면 '00'을 반환한다", () => {}); - - it("1을 5자리로 변환하면 '00001'을 반환한다", () => {}); - - it("소수점이 있는 3.14를 5자리로 변환하면 '03.14'를 반환한다", () => {}); - - it('size 파라미터를 생략하면 기본값 2를 사용한다', () => {}); - - it('value가 지정된 size보다 큰 자릿수를 가지면 원래 값을 그대로 반환한다', () => {}); + it("5를 2자리로 변환하면 '05'를 반환한다", () => { + expect(fillZero(5, 2)).toBe('05'); + }); + + it("10을 2자리로 변환하면 '10'을 반환한다", () => { + expect(fillZero(10, 2)).toBe('10'); + }); + + it("3을 3자리로 변환하면 '003'을 반환한다", () => { + expect(fillZero(3, 3)).toBe('003'); + }); + + it("100을 2자리로 변환하면 '100'을 반환한다", () => { + expect(fillZero(100, 2)).toBe('100'); + }); + + it("0을 2자리로 변환하면 '00'을 반환한다", () => { + expect(fillZero(0, 2)).toBe('00'); + }); + + it("1을 5자리로 변환하면 '00001'을 반환한다", () => { + expect(fillZero(1, 5)).toBe('00001'); + }); + + it("소수점이 있는 3.14를 5자리로 변환하면 '03.14'를 반환한다", () => { + expect(fillZero(3.14, 5)).toBe('03.14'); + }); + + it('size 파라미터를 생략하면 기본값 2를 사용한다', () => { + expect(fillZero(1)).toBe('01'); + expect(fillZero(10)).toBe('10'); + expect(fillZero(100)).toBe('100'); + }); + + it('value가 지정된 size보다 큰 자릿수를 가지면 원래 값을 그대로 반환한다', () => { + expect(fillZero(100, 1)).toBe('100'); + expect(fillZero(10, 1)).toBe('10'); + }); }); describe('formatDate', () => { - it('날짜를 YYYY-MM-DD 형식으로 포맷팅한다', () => {}); - - it('day 파라미터가 제공되면 해당 일자로 포맷팅한다', () => {}); - - it('월이 한 자리 수일 때 앞에 0을 붙여 포맷팅한다', () => {}); - - it('일이 한 자리 수일 때 앞에 0을 붙여 포맷팅한다', () => {}); + it('날짜를 YYYY-MM-DD 형식으로 포맷팅한다', () => { + expect(formatDate(new Date('2025-08-21'))).toBe('2025-08-21'); + }); + + it('day 파라미터가 제공되면 해당 일자로 포맷팅한다', () => { + expect(formatDate(new Date('2025-08-21'), 31)).toBe('2025-08-31'); + }); + + it('월이 한 자리 수일 때 앞에 0을 붙여 포맷팅한다', () => { + expect(formatDate(new Date('2025-08-03'))).toBe('2025-08-03'); + expect(formatDate(new Date('2025-12-03'))).toBe('2025-12-03'); + }); + + it('일이 한 자리 수일 때 앞에 0을 붙여 포맷팅한다', () => { + expect(formatDate(new Date('2025-08-03'))).toBe('2025-08-03'); + expect(formatDate(new Date('2025-08-13'))).toBe('2025-08-13'); + }); }); diff --git a/src/__tests__/unit/easy.eventOverlap.spec.ts b/src/__tests__/unit/easy.eventOverlap.spec.ts index 5e5f6497..a4c23f0c 100644 --- a/src/__tests__/unit/easy.eventOverlap.spec.ts +++ b/src/__tests__/unit/easy.eventOverlap.spec.ts @@ -6,31 +6,235 @@ import { parseDateTime, } from '../../utils/eventOverlap'; describe('parseDateTime', () => { - it('2025-07-01 14:30을 정확한 Date 객체로 변환한다', () => {}); + it('2025-07-01 14:30을 정확한 Date 객체로 변환한다', () => { + const date = parseDateTime('2025-07-01', '14:30'); + expect(date).toBeInstanceOf(Date); // Date 객체인지 확인 + expect(date.getFullYear()).toBe(2025); + expect(date.getMonth()).toBe(6); // 0부터 시작하므로 7월 = 6 + expect(date.getDate()).toBe(1); + expect(date.getHours()).toBe(14); + expect(date.getMinutes()).toBe(30); + }); - it('잘못된 날짜 형식에 대해 Invalid Date를 반환한다', () => {}); + it('잘못된 날짜 형식에 대해 Invalid Date를 반환한다', () => { + const date = parseDateTime('20225-07-01', '14:30'); + expect(date).toBeInstanceOf(Date); + expect(isNaN(date.getFullYear())).toBe(true); + }); - it('잘못된 시간 형식에 대해 Invalid Date를 반환한다', () => {}); + it('잘못된 시간 형식에 대해 Invalid Date를 반환한다', () => { + const date = parseDateTime('2025-07-01', '124:30'); + expect(date).toBeInstanceOf(Date); + expect(isNaN(date.getTime())).toBe(true); + }); - it('날짜 문자열이 비어있을 때 Invalid Date를 반환한다', () => {}); + it('날짜 문자열이 비어있을 때 Invalid Date를 반환한다', () => { + const date = parseDateTime('', '124:30'); + expect(date).toBeInstanceOf(Date); + expect(isNaN(date.getTime())).toBe(true); + }); }); describe('convertEventToDateRange', () => { - it('일반적인 이벤트를 올바른 시작 및 종료 시간을 가진 객체로 변환한다', () => {}); + it('일반적인 이벤트를 올바른 시작 및 종료 시간을 가진 객체로 변환한다', () => { + const testEvent: Event = { + id: '1', + title: '테스트 이벤트', + date: '2025-08-21', + startTime: '10:00', + endTime: '12:00', + description: '테스트 설명', + location: '회의실', + category: '업무', + notificationTime: 5, + } as Event; - it('잘못된 날짜 형식의 이벤트에 대해 Invalid Date를 반환한다', () => {}); + const result = convertEventToDateRange(testEvent); - it('잘못된 시간 형식의 이벤트에 대해 Invalid Date를 반환한다', () => {}); + expect(result.start).toBeInstanceOf(Date); + expect(result.end).toBeInstanceOf(Date); + + expect(result.start.getFullYear()).toBe(2025); + expect(result.start.getMonth()).toBe(7); + expect(result.start.getDate()).toBe(21); + expect(result.start.getHours()).toBe(10); + expect(result.start.getMinutes()).toBe(0); + + expect(result.end.getHours()).toBe(12); + expect(result.end.getMinutes()).toBe(0); + }); + + it('잘못된 날짜 형식의 이벤트에 대해 Invalid Date를 반환한다', () => { + const testEvent: Event = { + id: '1', + title: '테스트 이벤트', + date: '20225-08-21', + startTime: '10:00', + endTime: '12:00', + description: '테스트 설명', + location: '회의실', + category: '업무', + notificationTime: 5, + } as Event; + const result = convertEventToDateRange(testEvent); + expect(result.start).toBeInstanceOf(Date); + expect(result.end).toBeInstanceOf(Date); + expect(isNaN(result.start.getTime())).toBe(true); + expect(isNaN(result.end.getTime())).toBe(true); + }); + + it('잘못된 시간 형식의 이벤트에 대해 Invalid Date를 반환한다', () => { + const testEvent: Event = { + id: '1', + title: '테스트 이벤트', + date: '2025-08-21', + startTime: '101:00', + endTime: '122:00', + description: '테스트 설명', + location: '회의실', + category: '업무', + notificationTime: 5, + } as Event; + const result = convertEventToDateRange(testEvent); + expect(result.start).toBeInstanceOf(Date); + expect(result.end).toBeInstanceOf(Date); + expect(isNaN(result.start.getTime())).toBe(true); + expect(isNaN(result.end.getTime())).toBe(true); + }); }); describe('isOverlapping', () => { - it('두 이벤트가 겹치는 경우 true를 반환한다', () => {}); + it('두 이벤트가 겹치는 경우 true를 반환한다', () => { + const event1 = { + id: '1', + title: '회의 1', + date: '2025-08-21', + startTime: '10:00', + endTime: '12:00', + description: '', + location: '', + category: '', + notificationTime: 5, + } as Event; + + const event2 = { + id: '2', + title: '회의 2', + date: '2025-08-21', + startTime: '11:00', + endTime: '13:00', + description: '', + location: '', + category: '', + notificationTime: 5, + } as Event; + + expect(isOverlapping(event1, event2)).toBe(true); + }); - it('두 이벤트가 겹치지 않는 경우 false를 반환한다', () => {}); + it('두 이벤트가 겹치지 않는 경우 false를 반환한다', () => { + const event1 = { + id: '1', + title: '회의 1', + date: '2025-08-21', + startTime: '10:00', + endTime: '12:00', + description: '', + location: '', + category: '', + notificationTime: 5, + } as Event; + + const event2 = { + id: '2', + title: '회의 2', + date: '2025-08-21', + startTime: '12:00', + endTime: '13:00', + description: '', + location: '', + category: '', + notificationTime: 5, + } as Event; + + expect(isOverlapping(event1, event2)).toBe(false); + }); }); describe('findOverlappingEvents', () => { - it('새 이벤트와 겹치는 모든 이벤트를 반환한다', () => {}); + it('새 이벤트와 겹치는 모든 이벤트를 반환한다', () => { + const existingEvents = [ + { + id: '1', + title: '회의 1', + date: '2025-08-21', + startTime: '10:00', + endTime: '12:00', + description: '', + location: '', + category: '', + notificationTime: 5, + }, + { + id: '2', + title: '회의 2', + date: '2025-08-21', + startTime: '13:00', + endTime: '14:00', + description: '', + location: '', + category: '', + notificationTime: 5, + }, + ] as Event[]; + + const newEvent = { + id: '3', + title: '새 회의', + date: '2025-08-21', + startTime: '11:00', + endTime: '13:30', + description: '', + location: '', + category: '', + repeat: { type: 'none' }, + notificationTime: 5, + } as Event; + + const overlapping = findOverlappingEvents(newEvent, existingEvents); + expect(overlapping).toHaveLength(2); + expect(overlapping.map((e) => e.id)).toContain('1'); + expect(overlapping.map((e) => e.id)).toContain('2'); + }); + + it('겹치는 이벤트가 없으면 빈 배열을 반환한다', () => { + const existingEvents = [ + { + id: '1', + title: '회의 1', + date: '2025-08-21', + startTime: '10:00', + endTime: '12:00', + description: '', + location: '', + category: '', + notificationTime: 5, + }, + ] as Event[]; + + const newEvent = { + id: '2', + title: '새 회의', + date: '2025-08-21', + startTime: '12:00', + endTime: '13:00', + description: '', + location: '', + category: '', + notificationTime: 5, + } as Event; - it('겹치는 이벤트가 없으면 빈 배열을 반환한다', () => {}); + const overlapping = findOverlappingEvents(newEvent, existingEvents); + expect(overlapping).toHaveLength(0); + }); });