diff --git a/docs/devLog.md b/docs/devLog.md
new file mode 100644
index 00000000..f0d32d93
--- /dev/null
+++ b/docs/devLog.md
@@ -0,0 +1,35 @@
+## easy.useSearch.spec
+
+1. new Date() 시스템 시간 값이 이상하다
+ 지금 8월인데 10월로 나온다
+ 이것때문에 dummy event 8월로 만들어두고 하루종일 테스트 통과 안되다가
+ 설마하고 new Date() 콘솔에 찍어보니까 10월이라 열받는다.
+
+ ```ts
+ beforeEach(() => {
+ expect.hasAssertions(); // ? Med: 이걸 왜 써야하는지 물어보자
+
+ vi.setSystemTime(new Date('2025-10-01')); // ? Med: 이걸 왜 써야하는지 물어보자
+ });
+ ```
+
+ 이것 때문이네!!
+
+## easy.dateUtils.spec
+
+1. getDaysInMonth
+
+ ```ts
+ function getDaysInMonth(year: number, month: number): number {
+ return new Date(year, month, 0).getDate();
+ }
+ ```
+
+ day에 0을 넣음으로써 monthIndex에 넣은 정수의 -1월을 계산한다.
+
+ > new Date(2025, 8, 0) -> 여기서 `8`은 `9월`,
+ > day가 `0`으로 `9월`에서 `-1`을 한 `8월`로 계산됨.
+ > month param에 `8`을 넣고 결과값도 `8월`로 계산됨.
+
+ monthIndex는 0~11로 월을 판단하는데, 우리가 쓰는 1~12월과는 -1씩 차이나기 때문에 moth parameter가 우리가 실사용하는 월로 계산될 수 있다.
+ 함수 제대로 안 보고 막 작성했다가 꽤나 꼬였었음.
diff --git a/package.json b/package.json
index b01b2b4b..68a7441b 100644
--- a/package.json
+++ b/package.json
@@ -29,6 +29,7 @@
"react-dom": "19.1.0"
},
"devDependencies": {
+ "@eslint/js": "^9.33.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.5.2",
@@ -51,6 +52,7 @@
"eslint-plugin-vitest": "^0.5.4",
"globals": "16.3.0",
"jsdom": "^26.1.0",
+ "prettier": "^3.6.2",
"typescript": "^5.2.2",
"vite": "^7.0.2",
"vite-plugin-eslint": "^1.8.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 093f3ec7..5292986e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -39,6 +39,9 @@ importers:
specifier: 19.1.0
version: 19.1.0(react@19.1.0)
devDependencies:
+ '@eslint/js':
+ specifier: ^9.33.0
+ version: 9.33.0
'@testing-library/jest-dom':
specifier: ^6.6.3
version: 6.6.3
@@ -86,7 +89,7 @@ importers:
version: 2.32.0(@typescript-eslint/parser@8.35.0(eslint@9.30.0)(typescript@5.6.3))(eslint@9.30.0)
eslint-plugin-prettier:
specifier: ^5.5.1
- version: 5.5.1(@types/eslint@8.56.12)(eslint-config-prettier@10.1.5(eslint@9.30.0))(eslint@9.30.0)(prettier@3.3.3)
+ version: 5.5.1(@types/eslint@8.56.12)(eslint-config-prettier@10.1.5(eslint@9.30.0))(eslint@9.30.0)(prettier@3.6.2)
eslint-plugin-react:
specifier: ^7.37.0
version: 7.37.2(eslint@9.30.0)
@@ -95,7 +98,7 @@ importers:
version: 5.2.0(eslint@9.30.0)
eslint-plugin-storybook:
specifier: ^9.0.14
- version: 9.0.14(eslint@9.30.0)(storybook@9.0.14(@testing-library/dom@10.4.0)(prettier@3.3.3))(typescript@5.6.3)
+ version: 9.0.14(eslint@9.30.0)(storybook@9.0.14(@testing-library/dom@10.4.0)(prettier@3.6.2))(typescript@5.6.3)
eslint-plugin-vitest:
specifier: ^0.5.4
version: 0.5.4(@typescript-eslint/eslint-plugin@8.35.0(@typescript-eslint/parser@8.35.0(eslint@9.30.0)(typescript@5.6.3))(eslint@9.30.0)(typescript@5.6.3))(eslint@9.30.0)(typescript@5.6.3)(vitest@3.2.4)
@@ -105,6 +108,9 @@ importers:
jsdom:
specifier: ^26.1.0
version: 26.1.0
+ prettier:
+ specifier: ^3.6.2
+ version: 3.6.2
typescript:
specifier: ^5.2.2
version: 5.6.3
@@ -468,6 +474,10 @@ packages:
resolution: {integrity: sha512-Wzw3wQwPvc9sHM+NjakWTcPx11mbZyiYHuwWa/QfZ7cIRX7WK54PSk7bdyXDaoaopUcMatv1zaQvOAAO8hCdww==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@eslint/js@9.33.0':
+ resolution: {integrity: sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
'@eslint/object-schema@2.1.6':
resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -2562,8 +2572,8 @@ packages:
resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==}
engines: {node: '>=6.0.0'}
- prettier@3.3.3:
- resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==}
+ prettier@3.6.2:
+ resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==}
engines: {node: '>=14'}
hasBin: true
@@ -3628,6 +3638,8 @@ snapshots:
'@eslint/js@9.30.0': {}
+ '@eslint/js@9.33.0': {}
+
'@eslint/object-schema@2.1.6': {}
'@eslint/plugin-kit@0.3.3':
@@ -4958,10 +4970,10 @@ snapshots:
- eslint-import-resolver-webpack
- supports-color
- eslint-plugin-prettier@5.5.1(@types/eslint@8.56.12)(eslint-config-prettier@10.1.5(eslint@9.30.0))(eslint@9.30.0)(prettier@3.3.3):
+ eslint-plugin-prettier@5.5.1(@types/eslint@8.56.12)(eslint-config-prettier@10.1.5(eslint@9.30.0))(eslint@9.30.0)(prettier@3.6.2):
dependencies:
eslint: 9.30.0
- prettier: 3.3.3
+ prettier: 3.6.2
prettier-linter-helpers: 1.0.0
synckit: 0.11.8
optionalDependencies:
@@ -4994,11 +5006,11 @@ snapshots:
string.prototype.matchall: 4.0.11
string.prototype.repeat: 1.0.0
- eslint-plugin-storybook@9.0.14(eslint@9.30.0)(storybook@9.0.14(@testing-library/dom@10.4.0)(prettier@3.3.3))(typescript@5.6.3):
+ eslint-plugin-storybook@9.0.14(eslint@9.30.0)(storybook@9.0.14(@testing-library/dom@10.4.0)(prettier@3.6.2))(typescript@5.6.3):
dependencies:
'@typescript-eslint/utils': 8.35.0(eslint@9.30.0)(typescript@5.6.3)
eslint: 9.30.0
- storybook: 9.0.14(@testing-library/dom@10.4.0)(prettier@3.3.3)
+ storybook: 9.0.14(@testing-library/dom@10.4.0)(prettier@3.6.2)
transitivePeerDependencies:
- supports-color
- typescript
@@ -5984,7 +5996,7 @@ snapshots:
dependencies:
fast-diff: 1.3.0
- prettier@3.3.3: {}
+ prettier@3.6.2: {}
pretty-format@27.5.1:
dependencies:
@@ -6331,7 +6343,7 @@ snapshots:
es-errors: 1.3.0
internal-slot: 1.1.0
- storybook@9.0.14(@testing-library/dom@10.4.0)(prettier@3.3.3):
+ storybook@9.0.14(@testing-library/dom@10.4.0)(prettier@3.6.2):
dependencies:
'@storybook/global': 5.0.0
'@testing-library/jest-dom': 6.6.3
@@ -6345,7 +6357,7 @@ snapshots:
semver: 7.6.3
ws: 8.18.0
optionalDependencies:
- prettier: 3.3.3
+ prettier: 3.6.2
transitivePeerDependencies:
- '@testing-library/dom'
- bufferutil
diff --git a/src/__mocks__/handlersUtils.ts b/src/__mocks__/handlersUtils.ts
index 0263c669..aa66b582 100644
--- a/src/__mocks__/handlersUtils.ts
+++ b/src/__mocks__/handlersUtils.ts
@@ -57,6 +57,10 @@ export const setupMockHandlerUpdating = () => {
const updatedEvent = (await request.json()) as Event;
const index = mockEvents.findIndex((event) => event.id === id);
+ if (index === -1) {
+ return HttpResponse.json({ error: 'Event not found' }, { status: 404 });
+ }
+
mockEvents[index] = { ...mockEvents[index], ...updatedEvent };
return HttpResponse.json(mockEvents[index]);
})
@@ -92,3 +96,40 @@ export const setupMockHandlerDeletion = () => {
})
);
};
+
+export const setupMockHandlerCreationFailure = () => {
+ server.use(
+ http.get('/api/events', () => {
+ return HttpResponse.json(null, { status: 500 });
+ }),
+ http.post('/api/events', async () => {
+ return HttpResponse.json(null, { status: 500 });
+ })
+ );
+};
+
+export const setupMockHandlerDeletionFailure = () => {
+ const mockEvents: Event[] = [
+ {
+ id: '1',
+ title: '삭제할 이벤트',
+ date: '2025-10-15',
+ startTime: '09:00',
+ endTime: '10:00',
+ description: '삭제할 이벤트입니다',
+ location: '어딘가',
+ category: '기타',
+ repeat: { type: 'none', interval: 0 },
+ notificationTime: 10,
+ },
+ ];
+
+ server.use(
+ http.get('/api/events', () => {
+ return HttpResponse.json({ events: mockEvents });
+ }),
+ http.delete('/api/events/:id', () => {
+ return new HttpResponse(null, { status: 500 });
+ })
+ );
+};
diff --git a/src/__mocks__/response/realEvents.json b/src/__mocks__/response/realEvents.json
index 5ab618a0..f195be5f 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":"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},{"id":"b567e5b4-24dc-42e1-b644-9be03a4d738c","title":"123","date":"2025-08-20","startTime":"10:00","endTime":"11:00","description":"123","location":"123","category":"개인","repeat":{"type":"none","interval":1},"notificationTime":10}]}
\ No newline at end of file
diff --git a/src/__tests__/hooks/easy.useCalendarView.spec.ts b/src/__tests__/hooks/easy.useCalendarView.spec.ts
index 93b57f0e..8a751e75 100644
--- a/src/__tests__/hooks/easy.useCalendarView.spec.ts
+++ b/src/__tests__/hooks/easy.useCalendarView.spec.ts
@@ -1,24 +1,90 @@
import { act, renderHook } from '@testing-library/react';
import { useCalendarView } from '../../hooks/useCalendarView.ts';
-import { assertDate } from '../utils.ts';
describe('초기 상태', () => {
- it('view는 "month"이어야 한다', () => {});
+ it('view는 "month"이어야 한다', () => {
+ const { result } = renderHook(useCalendarView);
+ expect(result.current.view).toBe('month');
+ });
- it('currentDate는 오늘 날짜인 "2025-10-01"이어야 한다', () => {});
+ it('currentDate는 오늘 날짜인 "2025-10-01"이어야 한다', () => {
+ const { result } = renderHook(useCalendarView);
+ expect(result.current.currentDate).toEqual(new Date('2025-10-01'));
+ });
- it('holidays는 10월 휴일인 개천절, 한글날, 추석이 지정되어 있어야 한다', () => {});
+ it('holidays는 10월 휴일인 개천절, 한글날, 추석이 지정되어 있어야 한다', () => {
+ const { result } = renderHook(useCalendarView);
+ expect(result.current.holidays).toHaveProperty('2025-10-05', '추석');
+ expect(result.current.holidays).toHaveProperty('2025-10-03', '개천절');
+ expect(result.current.holidays).toHaveProperty('2025-10-09', '한글날');
+ });
});
-it("view를 'week'으로 변경 시 적절하게 반영된다", () => {});
+it("view를 'week'으로 변경 시 적절하게 반영된다", () => {
+ const { result } = renderHook(useCalendarView);
-it("주간 뷰에서 다음으로 navigate시 7일 후 '2025-10-08' 날짜로 지정이 된다", () => {});
+ act(() => {
+ result.current.setView('week');
+ });
-it("주간 뷰에서 이전으로 navigate시 7일 후 '2025-09-24' 날짜로 지정이 된다", () => {});
+ expect(result.current.view).toBe('week');
+});
+
+it("주간 뷰에서 다음으로 navigate시 7일 후 '2025-10-08' 날짜로 지정이 된다", () => {
+ const { result } = renderHook(useCalendarView);
+
+ act(() => {
+ result.current.setView('week');
+ });
+
+ act(() => {
+ result.current.navigate('next');
+ });
+
+ expect(result.current.currentDate).toEqual(new Date('2025-10-08'));
+});
+
+it("주간 뷰에서 이전으로 navigate시 7일 전 '2025-09-24' 날짜로 지정이 된다", () => {
+ const { result } = renderHook(useCalendarView);
+
+ act(() => {
+ result.current.setView('week');
+ });
+
+ act(() => {
+ result.current.navigate('prev');
+ });
+
+ expect(result.current.currentDate).toEqual(new Date('2025-09-24'));
+});
+
+it("월간 뷰에서 다음으로 navigate시 한 달 후 '2025-11-01' 날짜여야 한다", () => {
+ const { result } = renderHook(useCalendarView);
+
+ act(() => {
+ result.current.navigate('next');
+ });
+
+ expect(result.current.currentDate).toEqual(new Date('2025-11-01'));
+});
-it("월간 뷰에서 다음으로 navigate시 한 달 후 '2025-11-01' 날짜여야 한다", () => {});
+it("월간 뷰에서 이전으로 navigate시 한 달 전 '2025-09-01' 날짜여야 한다", () => {
+ const { result } = renderHook(useCalendarView);
-it("월간 뷰에서 이전으로 navigate시 한 달 전 '2025-09-01' 날짜여야 한다", () => {});
+ act(() => {
+ result.current.navigate('prev');
+ });
-it("currentDate가 '2025-03-01' 변경되면 3월 휴일 '삼일절'로 업데이트되어야 한다", async () => {});
+ expect(result.current.currentDate).toEqual(new Date('2025-09-01'));
+});
+
+it("currentDate가 '2025-03-01' 변경되면 3월 휴일 '삼일절'로 업데이트되어야 한다", async () => {
+ const { result } = renderHook(useCalendarView);
+
+ act(() => {
+ result.current.setCurrentDate(new Date('2025-03-01'));
+ });
+
+ expect(result.current.holidays).toHaveProperty('2025-03-01', '삼일절');
+});
diff --git a/src/__tests__/hooks/easy.useSearch.spec.ts b/src/__tests__/hooks/easy.useSearch.spec.ts
index 80f57fa3..7d3f3694 100644
--- a/src/__tests__/hooks/easy.useSearch.spec.ts
+++ b/src/__tests__/hooks/easy.useSearch.spec.ts
@@ -1,14 +1,65 @@
import { act, renderHook } from '@testing-library/react';
+import dummyEvents from '../../__mocks__/response/events.json';
import { useSearch } from '../../hooks/useSearch.ts';
import { Event } from '../../types.ts';
-it('검색어가 비어있을 때 모든 이벤트를 반환해야 한다', () => {});
+const events = dummyEvents.events as Event[];
-it('검색어에 맞는 이벤트만 필터링해야 한다', () => {});
+it('검색어가 비어있을 때 모든 이벤트를 반환해야 한다', () => {
+ const { result } = renderHook(() => useSearch(events, new Date(), 'month'));
-it('검색어가 제목, 설명, 위치 중 하나라도 일치하면 해당 이벤트를 반환해야 한다', () => {});
+ act(() => {
+ result.current.setSearchTerm('');
+ });
-it('현재 뷰(주간/월간)에 해당하는 이벤트만 반환해야 한다', () => {});
+ expect(result.current.filteredEvents).toEqual(events);
+});
-it("검색어를 '회의'에서 '점심'으로 변경하면 필터링된 결과가 즉시 업데이트되어야 한다", () => {});
+it('검색어에 맞는 이벤트만 필터링해야 한다', () => {
+ const { result } = renderHook(() => useSearch(events, new Date(), 'month'));
+
+ act(() => {
+ result.current.setSearchTerm('회의');
+ });
+
+ expect(result.current.filteredEvents).toEqual([events[0]]);
+});
+
+it('검색어가 제목, 설명, 위치 중 하나라도 일치하면 해당 이벤트를 반환해야 한다', () => {
+ const { result } = renderHook(() => useSearch(events, new Date(), 'month'));
+
+ act(() => {
+ result.current.setSearchTerm('회의실 B');
+ });
+
+ console.log(result.current.filteredEvents);
+
+ expect(result.current.filteredEvents).toEqual([events[0]]);
+});
+
+it('현재 뷰(주간/월간)에 해당하는 이벤트만 반환해야 한다', () => {
+ const { result } = renderHook(() => useSearch(events, new Date(), 'week'));
+
+ act(() => {
+ result.current.setSearchTerm('기존 회의');
+ });
+
+ expect(result.current.filteredEvents).toEqual([]);
+});
+
+it("검색어를 '회의'에서 '점심'으로 변경하면 필터링된 결과가 즉시 업데이트되어야 한다", () => {
+ const { result } = renderHook(() => useSearch(events, new Date(), 'month'));
+
+ act(() => {
+ result.current.setSearchTerm('회의');
+ });
+
+ expect(result.current.filteredEvents).toEqual([events[0]]);
+
+ act(() => {
+ result.current.setSearchTerm('점심');
+ });
+
+ expect(result.current.filteredEvents).toEqual([]);
+});
diff --git a/src/__tests__/hooks/medium.useEventOperations.spec.ts b/src/__tests__/hooks/medium.useEventOperations.spec.ts
index 566ecbb0..8c0829c8 100644
--- a/src/__tests__/hooks/medium.useEventOperations.spec.ts
+++ b/src/__tests__/hooks/medium.useEventOperations.spec.ts
@@ -1,13 +1,13 @@
import { act, renderHook } from '@testing-library/react';
-import { http, HttpResponse } from 'msw';
import {
setupMockHandlerCreation,
+ setupMockHandlerCreationFailure,
setupMockHandlerDeletion,
+ setupMockHandlerDeletionFailure,
setupMockHandlerUpdating,
} from '../../__mocks__/handlersUtils.ts';
import { useEventOperations } from '../../hooks/useEventOperations.ts';
-import { server } from '../../setupTests.ts';
import { Event } from '../../types.ts';
const enqueueSnackbarFn = vi.fn();
@@ -22,16 +22,172 @@ vi.mock('notistack', async () => {
};
});
-it('저장되어있는 초기 이벤트 데이터를 적절하게 불러온다', async () => {});
+const events = [
+ {
+ id: '1',
+ title: '이벤트 1',
+ date: '2025-07-01',
+ startTime: '14:00',
+ endTime: '15:00',
+ description: '동료와 점심 식사',
+ location: '회사 근처 식당',
+ category: '개인',
+ repeat: { type: 'none', interval: 0 },
+ notificationTime: 1,
+ },
+ {
+ id: '2',
+ title: '이벤트 2',
+ date: '2025-07-02',
+ startTime: '14:00',
+ endTime: '15:00',
+ description: '동료와 점심 식사',
+ location: '회사 근처 식당',
+ category: '개인',
+ repeat: { type: 'none', interval: 0 },
+ notificationTime: 1,
+ },
+] as Event[];
-it('정의된 이벤트 정보를 기준으로 적절하게 저장이 된다', async () => {});
+it('저장되어있는 초기 이벤트 데이터를 적절하게 불러온다', async () => {
+ setupMockHandlerCreation(events);
-it("새로 정의된 'title', 'endTime' 기준으로 적절하게 일정이 업데이트 된다", async () => {});
+ const { result } = renderHook(() => useEventOperations(true));
-it('존재하는 이벤트 삭제 시 에러없이 아이템이 삭제된다.', async () => {});
+ await act(async () => {
+ await result.current.fetchEvents();
+ });
-it("이벤트 로딩 실패 시 '이벤트 로딩 실패'라는 텍스트와 함께 에러 토스트가 표시되어야 한다", async () => {});
+ expect(result.current.events).toEqual(events);
+});
+
+it('정의된 이벤트 정보를 기준으로 적절하게 저장이 된다', async () => {
+ setupMockHandlerCreation(events);
+
+ const { result } = renderHook(() => useEventOperations(false));
+
+ const newEvent = {
+ id: '3',
+ title: '이벤트 3',
+ date: '2025-07-02',
+ startTime: '14:00',
+ endTime: '15:00',
+ description: '동료와 점심 식사',
+ location: '회사 근처 식당',
+ category: '개인',
+ repeat: { type: 'none', interval: 0 },
+ notificationTime: 1,
+ } as Event;
+
+ await act(async () => {
+ await result.current.saveEvent(newEvent);
+ });
+
+ expect(result.current.events).toContainEqual(newEvent);
+});
+
+it("새로 정의된 'title', 'endTime' 기준으로 적절하게 일정이 업데이트 된다", async () => {
+ setupMockHandlerUpdating();
+
+ const { result } = renderHook(() => useEventOperations(true));
+
+ await act(async () => {
+ await result.current.fetchEvents();
+ });
+
+ const eventToUpdate = {
+ id: '2',
+ title: '이벤트 2 new',
+ date: '2025-07-03',
+ startTime: '15:00',
+ endTime: '16:00',
+ description: '동료와 점심 식사',
+ location: '회사 근처 식당',
+ category: '개인',
+ repeat: { type: 'none', interval: 0 },
+ notificationTime: 1,
+ } as Event;
+
+ await act(async () => {
+ await result.current.saveEvent(eventToUpdate);
+ });
+
+ expect(result.current.events).toContainEqual(eventToUpdate);
+});
+
+it('존재하는 이벤트 삭제 시 에러없이 아이템이 삭제된다.', async () => {
+ setupMockHandlerDeletion();
+
+ const { result } = renderHook(() => useEventOperations(false));
+
+ await act(async () => {
+ await result.current.fetchEvents();
+ });
+
+ const eventToDelete = result.current.events[0];
+
+ await act(async () => {
+ await result.current.deleteEvent(eventToDelete.id);
+ });
-it("존재하지 않는 이벤트 수정 시 '일정 저장 실패'라는 토스트가 노출되며 에러 처리가 되어야 한다", async () => {});
+ expect(result.current.events).not.toContainEqual(eventToDelete);
+});
+
+it("이벤트 로딩 실패 시 '이벤트 로딩 실패'라는 텍스트와 함께 에러 토스트가 표시되어야 한다", async () => {
+ setupMockHandlerCreationFailure();
+
+ const { result } = renderHook(() => useEventOperations(false));
+
+ await act(async () => {
+ await result.current.fetchEvents();
+ });
+
+ expect(enqueueSnackbarFn).toHaveBeenCalledWith('이벤트 로딩 실패', { variant: 'error' });
+});
+
+it("존재하지 않는 이벤트 수정 시 '일정 저장 실패'라는 토스트가 노출되며 에러 처리가 되어야 한다", async () => {
+ setupMockHandlerUpdating();
+
+ const { result } = renderHook(() => useEventOperations(true));
+
+ await act(async () => {
+ await result.current.fetchEvents();
+ });
+
+ const invalidEventToUpdate = {
+ id: '999',
+ title: '이벤트 2 new',
+ date: '2025-07-03',
+ startTime: '15:00',
+ endTime: '16:00',
+ description: '동료와 점심 식사',
+ location: '회사 근처 식당',
+ category: '개인',
+ repeat: { type: 'none', interval: 0 },
+ notificationTime: 1,
+ } as Event;
-it("네트워크 오류 시 '일정 삭제 실패'라는 텍스트가 노출되며 이벤트 삭제가 실패해야 한다", async () => {});
+ await act(async () => {
+ await result.current.saveEvent(invalidEventToUpdate);
+ });
+
+ expect(enqueueSnackbarFn).toHaveBeenCalledWith('일정 저장 실패', { variant: 'error' });
+});
+
+it("네트워크 오류 시 '일정 삭제 실패'라는 텍스트가 노출되며 이벤트 삭제가 실패해야 한다", async () => {
+ setupMockHandlerDeletionFailure();
+
+ const { result } = renderHook(() => useEventOperations(false));
+
+ await act(async () => {
+ await result.current.fetchEvents();
+ });
+
+ const eventToDelete = result.current.events[0];
+
+ await act(async () => {
+ await result.current.deleteEvent(eventToDelete.id);
+ });
+
+ expect(enqueueSnackbarFn).toHaveBeenCalledWith('일정 삭제 실패', { variant: 'error' });
+});
diff --git a/src/__tests__/hooks/medium.useNotifications.spec.ts b/src/__tests__/hooks/medium.useNotifications.spec.ts
index 7f585ea8..e75d803f 100644
--- a/src/__tests__/hooks/medium.useNotifications.spec.ts
+++ b/src/__tests__/hooks/medium.useNotifications.spec.ts
@@ -2,13 +2,93 @@ import { act, renderHook } from '@testing-library/react';
import { useNotifications } from '../../hooks/useNotifications.ts';
import { Event } from '../../types.ts';
-import { formatDate } from '../../utils/dateUtils.ts';
-import { parseHM } from '../utils.ts';
-it('초기 상태에서는 알림이 없어야 한다', () => {});
+const events = [
+ {
+ id: '1',
+ title: '이벤트 1',
+ date: '2025-10-01',
+ startTime: '14:00',
+ endTime: '15:00',
+ description: '동료와 점심 식사',
+ location: '회사 근처 식당',
+ category: '개인',
+ repeat: { type: 'none', interval: 0 },
+ notificationTime: 10,
+ },
+ {
+ id: '2',
+ title: '이벤트 2',
+ date: '2025-10-02',
+ startTime: '14:00',
+ endTime: '15:00',
+ description: '동료와 점심 식사',
+ location: '회사 근처 식당',
+ category: '개인',
+ repeat: { type: 'none', interval: 0 },
+ notificationTime: 10,
+ },
+] as Event[];
-it('지정된 시간이 된 경우 알림이 새롭게 생성되어 추가된다', () => {});
+it('초기 상태에서는 알림이 없어야 한다', () => {
+ const { result } = renderHook(() => useNotifications(events));
-it('index를 기준으로 알림을 적절하게 제거할 수 있다', () => {});
+ expect(result.current.notifications).toHaveLength(0);
+ expect(result.current.notifiedEvents).toHaveLength(0);
+});
-it('이미 알림이 발생한 이벤트에 대해서는 중복 알림이 발생하지 않아야 한다', () => {});
+it('지정된 시간이 된 경우 알림이 새롭게 생성되어 추가된다', () => {
+ const now = new Date('2025-10-01T13:50:00'); // 이벤트 시작 10분 전
+ vi.setSystemTime(now);
+
+ const { result } = renderHook(() => useNotifications(events));
+
+ act(() => {
+ vi.advanceTimersByTime(1000);
+ });
+
+ expect(result.current.notifications).toEqual([
+ { id: '1', message: '10분 후 이벤트 1 일정이 시작됩니다.' },
+ ]);
+});
+
+it('index를 기준으로 알림을 적절하게 제거할 수 있다', () => {
+ const now = new Date('2025-10-01T13:50:00'); // 이벤트 시작 10분 전
+ vi.setSystemTime(now);
+
+ const { result } = renderHook(() => useNotifications(events));
+
+ act(() => {
+ vi.advanceTimersByTime(1000);
+ });
+
+ expect(result.current.notifications).toHaveLength(1);
+
+ act(() => {
+ result.current.removeNotification(0);
+ });
+
+ expect(result.current.notifications).toHaveLength(0);
+});
+
+it('이미 알림이 발생한 이벤트에 대해서는 중복 알림이 발생하지 않아야 한다', () => {
+ // 이벤트1의 알림 발생 시점(2025-10-01T13:50:00)으로 시간 설정
+ const now = new Date('2025-10-01T13:50:00');
+ vi.setSystemTime(now);
+
+ const { result } = renderHook(() => useNotifications(events));
+
+ // 첫 번째 알림 발생
+ act(() => {
+ vi.advanceTimersByTime(1000);
+ });
+
+ expect(result.current.notifications).toHaveLength(1);
+
+ // 타이머를 여러 번 더 진행해도 중복 알림이 추가되지 않아야 함
+ act(() => {
+ vi.advanceTimersByTime(1000 * 60 * 10);
+ });
+
+ expect(result.current.notifications).toHaveLength(1);
+});
diff --git a/src/__tests__/medium.integration.spec.tsx b/src/__tests__/medium.integration.spec.tsx
index 0b559b44..ae3e7d73 100644
--- a/src/__tests__/medium.integration.spec.tsx
+++ b/src/__tests__/medium.integration.spec.tsx
@@ -2,7 +2,6 @@ import CssBaseline from '@mui/material/CssBaseline';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import { render, screen, within, act } from '@testing-library/react';
import { UserEvent, userEvent } from '@testing-library/user-event';
-import { http, HttpResponse } from 'msw';
import { SnackbarProvider } from 'notistack';
import { ReactElement } from 'react';
@@ -12,7 +11,6 @@ import {
setupMockHandlerUpdating,
} from '../__mocks__/handlersUtils';
import App from '../App';
-import { server } from '../setupTests';
import { Event } from '../types';
const theme = createTheme();
@@ -58,38 +56,259 @@ const saveSchedule = async (
// ! HINT. "검색 결과가 없습니다"는 초기에 노출되는데요. 그럼 검증하고자 하는 액션이 실행되기 전에 검증해버리지 않을까요? 이 테스트를 신뢰성있게 만드려면 어떻게 할까요?
describe('일정 CRUD 및 기본 기능', () => {
it('입력한 새로운 일정 정보에 맞춰 모든 필드가 이벤트 리스트에 정확히 저장된다.', async () => {
- // ! HINT. event를 추가 제거하고 저장하는 로직을 잘 살펴보고, 만약 그대로 구현한다면 어떤 문제가 있을 지 고민해보세요.
+ setupMockHandlerCreation();
+
+ const { user } = setup();
+
+ await saveSchedule(user, {
+ title: '테스트 일정',
+ date: '2025-10-01',
+ startTime: '10:00',
+ endTime: '11:00',
+ description: '새로운 일정 설명',
+ location: '새로운 일정 위치',
+ category: '개인',
+ });
+
+ const eventList = within(screen.getByTestId('event-list'));
+ expect(eventList.getByText('테스트 일정')).toBeInTheDocument();
+ });
+
+ it('기존 일정의 세부 정보를 수정하고 변경사항이 정확히 반영된다', async () => {
+ setupMockHandlerUpdating();
+
+ const { user } = setup();
+
+ const editButton = await screen.findAllByLabelText('Edit event');
+
+ await user.click(editButton[0]);
+
+ await user.clear(screen.getByLabelText('제목'));
+ await user.type(screen.getByLabelText('제목'), '수정된 테스트 일정');
+
+ await user.click(screen.getByTestId('event-submit-button'));
+
+ const eventList = within(screen.getByTestId('event-list'));
+ expect(eventList.getByText('수정된 테스트 일정')).toBeInTheDocument();
});
- it('기존 일정의 세부 정보를 수정하고 변경사항이 정확히 반영된다', async () => {});
+ it('일정을 삭제하고 더 이상 조회되지 않는지 확인한다', async () => {
+ setupMockHandlerDeletion();
+
+ const { user } = setup();
+ const eventList = within(screen.getByTestId('event-list'));
+ expect(await eventList.findByText('삭제할 이벤트')).toBeInTheDocument();
- it('일정을 삭제하고 더 이상 조회되지 않는지 확인한다', async () => {});
+ // 삭제 버튼 클릭
+ const allDeleteButton = await screen.findAllByLabelText('Delete event');
+ await user.click(allDeleteButton[0]);
+
+ expect(eventList.queryByText('삭제할 이벤트')).not.toBeInTheDocument();
+ });
});
describe('일정 뷰', () => {
- it('주별 뷰를 선택 후 해당 주에 일정이 없으면, 일정이 표시되지 않는다.', async () => {});
+ it('주별 뷰를 선택 후 해당 주에 일정이 없으면, 일정이 표시되지 않는다.', async () => {
+ const { user } = setup();
+
+ await user.click(within(screen.getByLabelText('뷰 타입 선택')).getByRole('combobox'));
+ await user.click(screen.getByRole('option', { name: 'week-option' }));
+
+ const eventList = within(screen.getByTestId('event-list'));
+ expect(eventList.getByText('검색 결과가 없습니다.')).toBeInTheDocument();
+ });
+
+ it('주별 뷰 선택 후 해당 일자에 일정이 존재한다면 해당 일정이 정확히 표시된다', async () => {
+ setupMockHandlerCreation();
+
+ const { user } = setup();
+
+ await saveSchedule(user, {
+ title: '주별 테스트 일정',
+ date: '2025-10-01',
+ startTime: '10:00',
+ endTime: '11:00',
+ description: '주별 테스트 일정 설명',
+ location: '주별 테스트 위치',
+ category: '개인',
+ });
+
+ await user.click(within(screen.getByLabelText('뷰 타입 선택')).getByRole('combobox'));
+ await user.click(screen.getByRole('option', { name: 'week-option' }));
+
+ const eventList = within(screen.getByTestId('event-list'));
+ expect(eventList.getByText('주별 테스트 일정')).toBeInTheDocument();
+ });
+
+ it('월별 뷰에 일정이 없으면, 일정이 표시되지 않아야 한다.', async () => {
+ setupMockHandlerCreation();
- it('주별 뷰 선택 후 해당 일자에 일정이 존재한다면 해당 일정이 정확히 표시된다', async () => {});
+ setup();
+
+ const eventList = within(screen.getByTestId('event-list'));
+ expect(eventList.getByText('검색 결과가 없습니다.')).toBeInTheDocument();
+ });
+
+ it('월별 뷰에 일정이 정확히 표시되는지 확인한다', async () => {
+ setupMockHandlerCreation();
+
+ const { user } = setup();
+
+ await saveSchedule(user, {
+ title: '월별 테스트 일정',
+ date: '2025-10-01',
+ startTime: '10:00',
+ endTime: '11:00',
+ description: '월별 테스트 일정 설명',
+ location: '월별 테스트 위치',
+ category: '개인',
+ });
+
+ await user.click(within(screen.getByLabelText('뷰 타입 선택')).getByRole('combobox'));
+ await user.click(screen.getByRole('option', { name: 'month-option' }));
+
+ const eventList = within(screen.getByTestId('event-list'));
+ expect(eventList.getByText('월별 테스트 일정')).toBeInTheDocument();
+ });
- it('월별 뷰에 일정이 없으면, 일정이 표시되지 않아야 한다.', async () => {});
+ it('달력에 1월 1일(신정)이 공휴일로 표시되는지 확인한다', async () => {
+ vi.setSystemTime(new Date('2025-01-01'));
- it('월별 뷰에 일정이 정확히 표시되는지 확인한다', async () => {});
+ setup();
- it('달력에 1월 1일(신정)이 공휴일로 표시되는지 확인한다', async () => {});
+ const calendar = screen.getByTestId('month-view');
+
+ expect(within(calendar).getByText('신정')).toBeInTheDocument();
+ });
});
describe('검색 기능', () => {
- it('검색 결과가 없으면, "검색 결과가 없습니다."가 표시되어야 한다.', async () => {});
+ beforeEach(() => {
+ setupMockHandlerCreation([
+ {
+ id: '2b7545a6-ebee-426c-b906-2329bc8d62bd',
+ title: '팀 회의',
+ date: '2025-10-01',
+ startTime: '10:00',
+ endTime: '11:00',
+ description: '주간 팀 미팅',
+ location: '회의실 A',
+ category: '업무',
+ repeat: { type: 'none', interval: 0 },
+ notificationTime: 1,
+ },
+ ]);
+ });
+
+ it('검색 결과가 없으면, "검색 결과가 없습니다."가 표시되어야 한다.', async () => {
+ const { user } = setup();
+
+ const searchInput = screen.getByPlaceholderText('검색어를 입력하세요');
+ await user.type(searchInput, '없는 일정');
+
+ const eventList = within(screen.getByTestId('event-list'));
+ expect(eventList.getByText('검색 결과가 없습니다.')).toBeInTheDocument();
+ });
+
+ it("'팀 회의'를 검색하면 해당 제목을 가진 일정이 리스트에 노출된다", async () => {
+ const { user } = setup();
- it("'팀 회의'를 검색하면 해당 제목을 가진 일정이 리스트에 노출된다", async () => {});
+ const searchInput = screen.getByPlaceholderText('검색어를 입력하세요');
+ await user.type(searchInput, '팀 회의');
- it('검색어를 지우면 모든 일정이 다시 표시되어야 한다', async () => {});
+ const eventList = within(screen.getByTestId('event-list'));
+ expect(eventList.getByText('팀 회의')).toBeInTheDocument();
+ });
+
+ it('검색어를 지우면 모든 일정이 다시 표시되어야 한다', async () => {
+ const { user } = setup();
+
+ const searchInput = screen.getByPlaceholderText('검색어를 입력하세요');
+ await user.clear(searchInput);
+
+ const eventList = within(screen.getByTestId('event-list'));
+ expect(eventList.getByText('팀 회의')).toBeInTheDocument();
+ });
});
describe('일정 충돌', () => {
- it('겹치는 시간에 새 일정을 추가할 때 경고가 표시된다', async () => {});
+ beforeEach(() => {
+ setupMockHandlerCreation([
+ {
+ id: '2b7545a6-ebee-426c-b906-2329bc8d62bd',
+ title: '팀 회의',
+ date: '2025-10-01',
+ startTime: '10:00',
+ endTime: '11:00',
+ description: '주간 팀 미팅',
+ location: '회의실 A',
+ category: '업무',
+ repeat: { type: 'none', interval: 0 },
+ notificationTime: 1,
+ },
+ {
+ id: '2b7545a6-ebee-426c-b906-2329bc8d62bd',
+ title: '팀 회의2',
+ date: '2025-10-01',
+ startTime: '11:00',
+ endTime: '12:00',
+ description: '주간 팀 미팅',
+ location: '회의실 A',
+ category: '업무',
+ repeat: { type: 'none', interval: 0 },
+ notificationTime: 1,
+ },
+ ]);
+ });
+
+ it('겹치는 시간에 새 일정을 추가할 때 경고가 표시된다', async () => {
+ const { user } = setup();
+
+ await saveSchedule(user, {
+ title: '새 회의',
+ date: '2025-10-01',
+ startTime: '10:00',
+ endTime: '11:00',
+ description: '설명',
+ location: '회의실 A',
+ category: '업무',
+ });
- it('기존 일정의 시간을 수정하여 충돌이 발생하면 경고가 노출된다', async () => {});
+ expect(screen.getByText('일정 겹침 경고')).toBeInTheDocument();
+ });
+
+ it('기존 일정의 시간을 수정하여 충돌이 발생하면 경고가 노출된다', async () => {
+ setupMockHandlerUpdating();
+
+ const { user } = setup();
+
+ const editButton = (await screen.findAllByLabelText('Edit event'))[1];
+ await user.click(editButton);
+
+ // 시간 수정하여 다른 일정과 충돌 발생
+ await user.clear(screen.getByLabelText('시작 시간'));
+ await user.type(screen.getByLabelText('시작 시간'), '09:00');
+ await user.clear(screen.getByLabelText('종료 시간'));
+ await user.type(screen.getByLabelText('종료 시간'), '10:00');
+
+ await user.click(screen.getByTestId('event-submit-button'));
+
+ expect(screen.getByText('일정 겹침 경고')).toBeInTheDocument();
+ });
});
-it('notificationTime을 10으로 하면 지정 시간 10분 전 알람 텍스트가 노출된다', async () => {});
+it('notificationTime을 10으로 하면 지정 시간 10분 전 알람 텍스트가 노출된다', async () => {
+ vi.setSystemTime(new Date('2025-10-15 08:49:59'));
+
+ setup();
+
+ await screen.findByText('일정 로딩 완료!');
+
+ expect(screen.queryByText('10분 후 기존 회의 일정이 시작됩니다.')).not.toBeInTheDocument();
+
+ act(() => {
+ vi.advanceTimersByTime(1000);
+ });
+
+ expect(screen.getByText('10분 후 기존 회의 일정이 시작됩니다.')).toBeInTheDocument();
+});
diff --git a/src/__tests__/unit/easy.dateUtils.spec.ts b/src/__tests__/unit/easy.dateUtils.spec.ts
index 967bfacd..46f21fbd 100644
--- a/src/__tests__/unit/easy.dateUtils.spec.ts
+++ b/src/__tests__/unit/easy.dateUtils.spec.ts
@@ -12,105 +12,364 @@ import {
} from '../../utils/dateUtils';
describe('getDaysInMonth', () => {
- it('1월은 31일 수를 반환한다', () => {});
-
- it('4월은 30일 일수를 반환한다', () => {});
+ it('1월은 31일 수를 반환한다', () => {
+ expect(getDaysInMonth(2025, 1)).toBe(31);
+ });
+
+ it('4월은 30일 일수를 반환한다', () => {
+ expect(getDaysInMonth(2025, 4)).toBe(30);
+ });
+
+ it('윤년의 2월에 대해 29일을 반환한다', () => {
+ expect(getDaysInMonth(2024, 2)).toBe(29);
+ });
+
+ it('평년의 2월에 대해 28일을 반환한다', () => {
+ expect(getDaysInMonth(2025, 2)).toBe(28);
+ });
+
+ // 무의미한 테스트 삭제
+ it('유효하지 않은 월에 대해 적절히 처리한다', () => {
+ expect(getDaysInMonth(2025, 0)).toBe(31); // 작년 11월로 계산
+ expect(getDaysInMonth(2025, 13)).toBe(31); // 익년 1월로 간주
+ });
+});
- it('윤년의 2월에 대해 29일을 반환한다', () => {});
+describe('getWeekDates', () => {
+ it('주중의 날짜(수요일)에 대해 올바른 주의 날짜들을 반환한다', () => {
+ const currentDate = new Date(); // 2025년 10월 1일 (수요일)
+ const weekDates = getWeekDates(currentDate);
+
+ expect(weekDates).toEqual([
+ new Date(2025, 8, 28), // 일요일
+ new Date(2025, 8, 29), // 월요일
+ new Date(2025, 8, 30), // 화요일
+ new Date(2025, 9, 1), // 수요일
+ new Date(2025, 9, 2), // 목요일
+ new Date(2025, 9, 3), // 금요일
+ new Date(2025, 9, 4), // 토요일
+ ]);
+ });
+
+ it('주의 시작(월요일)에 대해 올바른 주의 날짜들을 반환한다', () => {
+ const currentDate = new Date(2025, 8, 29); // 월요일
+ const weekDates = getWeekDates(currentDate);
+
+ expect(weekDates).toEqual([
+ new Date(2025, 8, 28), // 일요일
+ new Date(2025, 8, 29), // 월요일
+ new Date(2025, 8, 30), // 화요일
+ new Date(2025, 9, 1), // 수요일
+ new Date(2025, 9, 2), // 목요일
+ new Date(2025, 9, 3), // 금요일
+ new Date(2025, 9, 4), // 토요일
+ ]);
+ });
+
+ it('주의 끝(일요일)에 대해 올바른 주의 날짜들을 반환한다', () => {
+ const currentDate = new Date(2025, 8, 28); // 일요일
+ const weekDates = getWeekDates(currentDate);
+
+ expect(weekDates).toEqual([
+ new Date(2025, 8, 28), // 일요일
+ new Date(2025, 8, 29), // 월요일
+ new Date(2025, 8, 30), // 화요일
+ new Date(2025, 9, 1), // 수요일
+ new Date(2025, 9, 2), // 목요일
+ new Date(2025, 9, 3), // 금요일
+ new Date(2025, 9, 4), // 토요일
+ ]);
+ });
+
+ it('연도를 넘어가는 주의 날짜를 정확히 처리한다 (연말)', () => {
+ const currentDate = new Date(2025, 11, 31); // 2025년 12월 31일 (수요일)
+ const weekDates = getWeekDates(currentDate);
+ console.log(weekDates);
+
+ expect(weekDates).toEqual([
+ new Date(2025, 11, 28), // 일요일
+ new Date(2025, 11, 29), // 월요일
+ new Date(2025, 11, 30), // 화요일
+ new Date(2025, 11, 31), // 수요일
+ new Date(2026, 0, 1), // 목요일
+ new Date(2026, 0, 2), // 금요일
+ new Date(2026, 0, 3), // 토요일
+ ]);
+ });
+
+ it('연도를 넘어가는 주의 날짜를 정확히 처리한다 (연초)', () => {
+ const currentDate = new Date(2026, 0, 1); // 2026년 1월 1일 (목요일)
+ const weekDates = getWeekDates(currentDate);
+ console.log(weekDates);
+
+ expect(weekDates).toEqual([
+ new Date(2025, 11, 28), // 일요일
+ new Date(2025, 11, 29), // 월요일
+ new Date(2025, 11, 30), // 화요일
+ new Date(2025, 11, 31), // 수요일
+ new Date(2026, 0, 1), // 목요일
+ new Date(2026, 0, 2), // 금요일
+ new Date(2026, 0, 3), // 토요일
+ ]);
+ });
+
+ it('윤년의 2월 29일을 포함한 주를 올바르게 처리한다', () => {
+ const currentDate = new Date(2024, 1, 29); // 2024년 2월 29일 (목요일)
+ const weekDates = getWeekDates(currentDate);
+ console.log(weekDates);
+
+ expect(weekDates).toEqual([
+ new Date(2024, 1, 25), // 일요일
+ new Date(2024, 1, 26), // 월요일
+ new Date(2024, 1, 27), // 화요일
+ new Date(2024, 1, 28), // 수요일
+ new Date(2024, 1, 29), // 목요일
+ new Date(2024, 2, 1), // 금요일
+ new Date(2024, 2, 2), // 토요일
+ ]);
+ });
+
+ it('월의 마지막 날짜를 포함한 주를 올바르게 처리한다', () => {
+ const currentDate = new Date(2025, 11, 31); // 2025년 12월 31일 (수요일)
+ const weekDates = getWeekDates(currentDate);
+ console.log(weekDates);
+
+ expect(weekDates).toEqual([
+ new Date(2025, 11, 28), // 일요일
+ new Date(2025, 11, 29), // 월요일
+ new Date(2025, 11, 30), // 화요일
+ new Date(2025, 11, 31), // 수요일
+ new Date(2026, 0, 1), // 목요일
+ new Date(2026, 0, 2), // 금요일
+ new Date(2026, 0, 3), // 토요일
+ ]);
+ });
+});
- it('평년의 2월에 대해 28일을 반환한다', () => {});
+describe('getWeeksAtMonth', () => {
+ it('2025년 7월 1일의 올바른 주 정보를 반환해야 한다', () => {
+ const currentDate = new Date(2025, 6, 1); // 2025년 7월 1일 (화요일)
+ const weeks = getWeeksAtMonth(currentDate);
+ console.log(weeks);
+
+ expect(weeks).toEqual([
+ [null, null, 1, 2, 3, 4, 5],
+ [6, 7, 8, 9, 10, 11, 12],
+ [13, 14, 15, 16, 17, 18, 19],
+ [20, 21, 22, 23, 24, 25, 26],
+ [27, 28, 29, 30, 31, null, null],
+ ]);
+ });
+});
- it('유효하지 않은 월에 대해 적절히 처리한다', () => {});
+describe('getEventsForDay', () => {
+ const dummyEvents = [
+ {
+ id: '1',
+ title: '기존 회의',
+ date: '2025-10-1',
+ startTime: '09:00',
+ endTime: '10:00',
+ description: '기존 팀 미팅',
+ location: '회의실 B',
+ category: '업무',
+ repeat: { type: 'none', interval: 0 },
+ notificationTime: 10,
+ },
+ ] as Event[];
+
+ it('특정 날짜(1일)에 해당하는 이벤트만 정확히 반환한다', () => {
+ const events = getEventsForDay(dummyEvents, 1);
+
+ expect(events).toEqual(dummyEvents);
+ });
+
+ it('해당 날짜에 이벤트가 없을 경우 빈 배열을 반환한다', () => {
+ const events = getEventsForDay(dummyEvents, 2);
+ console.log(events);
+
+ expect(events).toEqual([]);
+ });
+
+ it('날짜가 0일 경우 빈 배열을 반환한다', () => {
+ const events = getEventsForDay(dummyEvents, 0);
+ console.log(events);
+
+ expect(events).toEqual([]);
+ });
+
+ it('날짜가 32일 이상인 경우 빈 배열을 반환한다', () => {
+ const events = getEventsForDay(dummyEvents, 32);
+ console.log(events);
+
+ expect(events).toEqual([]);
+ });
});
-describe('getWeekDates', () => {
- it('주중의 날짜(수요일)에 대해 올바른 주의 날짜들을 반환한다', () => {});
+describe('formatWeek', () => {
+ it('월의 중간 날짜에 대해 올바른 주 정보를 반환한다', () => {
+ const currentDate = new Date();
- it('주의 시작(월요일)에 대해 올바른 주의 날짜들을 반환한다', () => {});
+ expect(formatWeek(currentDate)).toBe('2025년 10월 1주');
+ });
- it('주의 끝(일요일)에 대해 올바른 주의 날짜들을 반환한다', () => {});
+ it('월의 첫 주에 대해 올바른 주 정보를 반환한다', () => {
+ const currentDate = new Date(2025, 9, 1); // 2025년 10월 1일 (수요일)
- it('연도를 넘어가는 주의 날짜를 정확히 처리한다 (연말)', () => {});
+ expect(formatWeek(currentDate)).toBe('2025년 10월 1주');
+ });
- it('연도를 넘어가는 주의 날짜를 정확히 처리한다 (연초)', () => {});
+ it('월의 마지막 주에 대해 올바른 주 정보를 반환한다', () => {
+ const currentDate = new Date(2025, 9, 31); // 2025년 10월 31일 (금요일)
- it('윤년의 2월 29일을 포함한 주를 올바르게 처리한다', () => {});
+ expect(formatWeek(currentDate)).toBe('2025년 10월 5주');
+ });
- it('월의 마지막 날짜를 포함한 주를 올바르게 처리한다', () => {});
-});
+ it('연도가 바뀌는 주에 대해 올바른 주 정보를 반환한다', () => {
+ const currentDate = new Date(2025, 11, 31); // 2025년 12월 31일 (수요일)
+ const weekInfo = formatWeek(currentDate);
-describe('getWeeksAtMonth', () => {
- it('2025년 7월 1일의 올바른 주 정보를 반환해야 한다', () => {});
-});
+ expect(weekInfo).toBe('2026년 1월 1주');
+ });
-describe('getEventsForDay', () => {
- it('특정 날짜(1일)에 해당하는 이벤트만 정확히 반환한다', () => {});
+ it('윤년 2월의 마지막 주에 대해 올바른 주 정보를 반환한다', () => {
+ const currentDate = new Date(2024, 1, 29); // 2024년 2월 29일 (목요일)
+ const weekInfo = formatWeek(currentDate);
- it('해당 날짜에 이벤트가 없을 경우 빈 배열을 반환한다', () => {});
+ expect(weekInfo).toBe('2024년 2월 5주');
+ });
- it('날짜가 0일 경우 빈 배열을 반환한다', () => {});
+ it('평년 2월의 마지막 주에 대해 올바른 주 정보를 반환한다', () => {
+ const currentDate = new Date(2025, 1, 28); // 2025년 2월 28일 (화요일)
+ const weekInfo = formatWeek(currentDate);
- it('날짜가 32일 이상인 경우 빈 배열을 반환한다', () => {});
+ expect(weekInfo).toBe('2025년 2월 4주');
+ });
});
-describe('formatWeek', () => {
- it('월의 중간 날짜에 대해 올바른 주 정보를 반환한다', () => {});
+describe('formatMonth', () => {
+ it("2025년 7월 10일을 '2025년 7월'로 반환한다", () => {
+ const currentDate = new Date(2025, 6, 10); // 2025년 7월 10일 (목요일)
+ const monthInfo = formatMonth(currentDate);
- it('월의 첫 주에 대해 올바른 주 정보를 반환한다', () => {});
+ expect(monthInfo).toBe('2025년 7월');
+ });
+});
- it('월의 마지막 주에 대해 올바른 주 정보를 반환한다', () => {});
+describe('isDateInRange', () => {
+ it('범위 내의 날짜 2025-07-10에 대해 true를 반환한다', () => {
+ const startDate = new Date(2025, 6, 1);
+ const endDate = new Date(2025, 6, 31);
+ const testDate = new Date(2025, 6, 10);
- it('연도가 바뀌는 주에 대해 올바른 주 정보를 반환한다', () => {});
+ expect(isDateInRange(testDate, startDate, endDate)).toBe(true);
+ });
- it('윤년 2월의 마지막 주에 대해 올바른 주 정보를 반환한다', () => {});
+ it('범위의 시작일 2025-07-01에 대해 true를 반환한다', () => {
+ const startDate = new Date(2025, 6, 1);
+ const endDate = new Date(2025, 6, 31);
+ const testDate = new Date(2025, 6, 1);
- it('평년 2월의 마지막 주에 대해 올바른 주 정보를 반환한다', () => {});
-});
+ expect(isDateInRange(testDate, startDate, endDate)).toBe(true);
+ });
-describe('formatMonth', () => {
- it("2025년 7월 10일을 '2025년 7월'로 반환한다", () => {});
-});
+ it('범위의 종료일 2025-07-31에 대해 true를 반환한다', () => {
+ const startDate = new Date(2025, 6, 1);
+ const endDate = new Date(2025, 6, 31);
+ const testDate = new Date(2025, 6, 31);
-describe('isDateInRange', () => {
- it('범위 내의 날짜 2025-07-10에 대해 true를 반환한다', () => {});
+ expect(isDateInRange(testDate, startDate, endDate)).toBe(true);
+ });
+
+ it('범위 이전의 날짜 2025-06-30에 대해 false를 반환한다', () => {
+ const startDate = new Date(2025, 6, 1);
+ const endDate = new Date(2025, 6, 31);
+ const testDate = new Date(2025, 5, 30);
- it('범위의 시작일 2025-07-01에 대해 true를 반환한다', () => {});
+ expect(isDateInRange(testDate, startDate, endDate)).toBe(false);
+ });
- it('범위의 종료일 2025-07-31에 대해 true를 반환한다', () => {});
+ it('범위 이후의 날짜 2025-08-01에 대해 false를 반환한다', () => {
+ const startDate = new Date(2025, 6, 1);
+ const endDate = new Date(2025, 6, 31);
+ const testDate = new Date(2025, 7, 1);
- it('범위 이전의 날짜 2025-06-30에 대해 false를 반환한다', () => {});
+ expect(isDateInRange(testDate, startDate, endDate)).toBe(false);
+ });
- it('범위 이후의 날짜 2025-08-01에 대해 false를 반환한다', () => {});
+ it('시작일이 종료일보다 늦은 경우 모든 날짜에 대해 false를 반환한다', () => {
+ const startDate = new Date(2025, 6, 31);
+ const endDate = new Date(2025, 6, 1);
+ const testDate = new Date(2025, 6, 15);
- it('시작일이 종료일보다 늦은 경우 모든 날짜에 대해 false를 반환한다', () => {});
+ expect(isDateInRange(testDate, startDate, endDate)).toBe(false);
+ });
});
describe('fillZero', () => {
- it("5를 2자리로 변환하면 '05'를 반환한다", () => {});
+ it("5를 2자리로 변환하면 '05'를 반환한다", () => {
+ expect(fillZero(5, 2)).toBe('05');
+ });
- it("10을 2자리로 변환하면 '10'을 반환한다", () => {});
+ it("10을 2자리로 변환하면 '10'을 반환한다", () => {
+ expect(fillZero(10, 2)).toBe('10');
+ });
- it("3을 3자리로 변환하면 '003'을 반환한다", () => {});
+ it("3을 3자리로 변환하면 '003'을 반환한다", () => {
+ expect(fillZero(3, 3)).toBe('003');
+ });
- it("100을 2자리로 변환하면 '100'을 반환한다", () => {});
+ it("100을 2자리로 변환하면 '100'을 반환한다", () => {
+ expect(fillZero(100, 2)).toBe('100');
+ });
- it("0을 2자리로 변환하면 '00'을 반환한다", () => {});
+ it("0을 2자리로 변환하면 '00'을 반환한다", () => {
+ expect(fillZero(0, 2)).toBe('00');
+ });
- it("1을 5자리로 변환하면 '00001'을 반환한다", () => {});
+ it("1을 5자리로 변환하면 '00001'을 반환한다", () => {
+ expect(fillZero(1, 5)).toBe('00001');
+ });
- it("소수점이 있는 3.14를 5자리로 변환하면 '03.14'를 반환한다", () => {});
+ it("소수점이 있는 3.14를 5자리로 변환하면 '03.14'를 반환한다", () => {
+ expect(fillZero(3.14, 5)).toBe('03.14');
+ });
- it('size 파라미터를 생략하면 기본값 2를 사용한다', () => {});
+ it('size 파라미터를 생략하면 기본값 2를 사용한다', () => {
+ expect(fillZero(5)).toBe('05');
+ });
- it('value가 지정된 size보다 큰 자릿수를 가지면 원래 값을 그대로 반환한다', () => {});
+ it('value가 지정된 size보다 큰 자릿수를 가지면 원래 값을 그대로 반환한다', () => {
+ expect(fillZero(100, 2)).toBe('100');
+ });
});
describe('formatDate', () => {
- it('날짜를 YYYY-MM-DD 형식으로 포맷팅한다', () => {});
+ it('날짜를 YYYY-MM-DD 형식으로 포맷팅한다', () => {
+ const currentDate = new Date();
+ const formattedDate = formatDate(currentDate);
+
+ expect(formattedDate).toBe('2025-10-01');
+ });
+
+ it('day 파라미터가 제공되면 해당 일자로 포맷팅한다', () => {
+ const currentDate = new Date();
+ const formattedDate = formatDate(currentDate, 15);
+ expect(formattedDate).toBe('2025-10-15');
+ });
+
+ it('월이 한 자리 수일 때 앞에 0을 붙여 포맷팅한다', () => {
+ const currentDate = new Date(2025, 4, 1);
+ const formattedDate = formatDate(currentDate);
- it('day 파라미터가 제공되면 해당 일자로 포맷팅한다', () => {});
+ console.log(formattedDate);
+ expect(formattedDate).toBe('2025-05-01');
+ });
- it('월이 한 자리 수일 때 앞에 0을 붙여 포맷팅한다', () => {});
+ it('일이 한 자리 수일 때 앞에 0을 붙여 포맷팅한다', () => {
+ const currentDate = new Date();
+ const formattedDate = formatDate(currentDate, 5);
- it('일이 한 자리 수일 때 앞에 0을 붙여 포맷팅한다', () => {});
+ expect(formattedDate).toBe('2025-10-05');
+ });
});
diff --git a/src/__tests__/unit/easy.eventOverlap.spec.ts b/src/__tests__/unit/easy.eventOverlap.spec.ts
index 5e5f6497..ed865e71 100644
--- a/src/__tests__/unit/easy.eventOverlap.spec.ts
+++ b/src/__tests__/unit/easy.eventOverlap.spec.ts
@@ -5,32 +5,191 @@ import {
isOverlapping,
parseDateTime,
} from '../../utils/eventOverlap';
+
describe('parseDateTime', () => {
- it('2025-07-01 14:30을 정확한 Date 객체로 변환한다', () => {});
+ it('2025-07-01 14:30을 정확한 Date 객체로 변환한다', () => {
+ const date = '2025-07-01';
+ const time = '14:30';
+ const expectedDate = new Date('2025-07-01T14:30');
+
+ expect(parseDateTime(date, time)).toEqual(expectedDate);
+ });
+
+ it('잘못된 날짜 형식에 대해 Invalid Date를 반환한다', () => {
+ const date = '2025-07-32';
+ const time = '14:30';
+ const result = parseDateTime(date, time);
+
+ expect(result).toEqual(new Date('Invalid Date'));
+ });
+
+ it('잘못된 시간 형식에 대해 Invalid Date를 반환한다', () => {
+ const date = '2025-07-01';
+ const time = '25:30';
+ const result = parseDateTime(date, time);
- it('잘못된 날짜 형식에 대해 Invalid Date를 반환한다', () => {});
+ expect(result).toEqual(new Date('Invalid Date'));
+ });
- it('잘못된 시간 형식에 대해 Invalid Date를 반환한다', () => {});
+ it('날짜 문자열이 비어있을 때 Invalid Date를 반환한다', () => {
+ const date = '';
+ const time = '14:30';
+ const result = parseDateTime(date, time);
- it('날짜 문자열이 비어있을 때 Invalid Date를 반환한다', () => {});
+ expect(result).toEqual(new Date('Invalid Date'));
+ });
});
describe('convertEventToDateRange', () => {
- it('일반적인 이벤트를 올바른 시작 및 종료 시간을 가진 객체로 변환한다', () => {});
+ it('일반적인 이벤트를 올바른 시작 및 종료 시간을 가진 객체로 변환한다', () => {
+ const event = {
+ id: '1',
+ title: 'Test Event',
+ date: '2025-07-01',
+ startTime: '14:30',
+ endTime: '16:00',
+ description: '',
+ location: '',
+ } as Event;
+
+ const expectedRange = {
+ start: new Date('2025-07-01T14:30'),
+ end: new Date('2025-07-01T16:00'),
+ };
+
+ expect(convertEventToDateRange(event)).toEqual(expectedRange);
+ });
+
+ it('잘못된 날짜 형식의 이벤트에 대해 Invalid Date를 반환한다', () => {
+ const event = {
+ id: '2',
+ title: 'Invalid Date Event',
+ date: '2025-07-32',
+ startTime: '14:30',
+ endTime: '16:00',
+ description: '',
+ location: '',
+ } as Event;
+
+ const expectedRange = {
+ start: new Date('Invalid Date'),
+ end: new Date('Invalid Date'),
+ };
+
+ expect(convertEventToDateRange(event)).toEqual(expectedRange);
+ });
- it('잘못된 날짜 형식의 이벤트에 대해 Invalid Date를 반환한다', () => {});
+ it('잘못된 시간 형식의 이벤트에 대해 Invalid Date를 반환한다', () => {
+ const event = {
+ id: '3',
+ title: 'Invalid Time Event',
+ date: '2025-07-01',
+ startTime: '25:30',
+ endTime: '26:00',
+ description: '',
+ location: '',
+ } as Event;
- it('잘못된 시간 형식의 이벤트에 대해 Invalid Date를 반환한다', () => {});
+ const expectedRange = {
+ start: new Date('Invalid Date'),
+ end: new Date('Invalid Date'),
+ };
+
+ expect(convertEventToDateRange(event)).toEqual(expectedRange);
+ });
});
describe('isOverlapping', () => {
- it('두 이벤트가 겹치는 경우 true를 반환한다', () => {});
+ it('두 이벤트가 겹치는 경우 true를 반환한다', () => {
+ const event1 = {
+ id: '1',
+ date: '2025-07-01',
+ startTime: '14:00',
+ endTime: '15:00',
+ } as Event;
+
+ const event2 = {
+ id: '2',
+ date: '2025-07-01',
+ startTime: '14:30',
+ endTime: '15:30',
+ } as Event;
+
+ expect(isOverlapping(event1, event2)).toBe(true);
+ });
+
+ it('두 이벤트가 겹치지 않는 경우 false를 반환한다', () => {
+ const event1 = {
+ id: '1',
+ date: '2025-07-01',
+ startTime: '14:00',
+ endTime: '15:00',
+ } as Event;
+
+ const event2 = {
+ id: '2',
+ date: '2025-07-01',
+ startTime: '15:30',
+ endTime: '16:30',
+ } as Event;
- it('두 이벤트가 겹치지 않는 경우 false를 반환한다', () => {});
+ expect(isOverlapping(event1, event2)).toBe(false);
+ });
});
describe('findOverlappingEvents', () => {
- it('새 이벤트와 겹치는 모든 이벤트를 반환한다', () => {});
+ it('새 이벤트와 겹치는 모든 이벤트를 반환한다', () => {
+ const newEvent = {
+ id: '1',
+ date: '2025-07-01',
+ startTime: '14:30',
+ endTime: '15:30',
+ } as Event;
+
+ const existingEvents = [
+ {
+ id: '2',
+ date: '2025-07-01',
+ startTime: '14:00',
+ endTime: '15:00',
+ } as Event,
+ {
+ id: '3',
+ date: '2025-07-01',
+ startTime: '15:30',
+ endTime: '16:30',
+ } as Event,
+ ];
+
+ const overlappingEvents = findOverlappingEvents(newEvent, existingEvents);
+ console.log(overlappingEvents, existingEvents);
+ expect(overlappingEvents).toEqual([existingEvents[0]]);
+ });
+
+ it('겹치는 이벤트가 없으면 빈 배열을 반환한다', () => {
+ const newEvent = {
+ id: '4',
+ date: '2025-07-01',
+ startTime: '16:30',
+ endTime: '17:30',
+ } as Event;
+
+ const existingEvents = [
+ {
+ id: '2',
+ date: '2025-07-01',
+ startTime: '14:00',
+ endTime: '15:00',
+ } as Event,
+ {
+ id: '3',
+ date: '2025-07-01',
+ startTime: '15:30',
+ endTime: '16:30',
+ } as Event,
+ ];
- it('겹치는 이벤트가 없으면 빈 배열을 반환한다', () => {});
+ const overlappingEvents = findOverlappingEvents(newEvent, existingEvents);
+ expect(overlappingEvents).toEqual([]);
+ });
});
diff --git a/src/__tests__/unit/easy.eventUtils.spec.ts b/src/__tests__/unit/easy.eventUtils.spec.ts
index 8eef6371..ab04e6a4 100644
--- a/src/__tests__/unit/easy.eventUtils.spec.ts
+++ b/src/__tests__/unit/easy.eventUtils.spec.ts
@@ -1,20 +1,115 @@
import { Event } from '../../types';
import { getFilteredEvents } from '../../utils/eventUtils';
+const events = [
+ {
+ id: '1',
+ title: '이벤트 1',
+ date: '2025-07-01',
+ startTime: '14:00',
+ endTime: '15:00',
+ description: '동료와 점심 식사',
+ location: '회사 근처 식당',
+ category: '개인',
+ repeat: { type: 'none', interval: 0 },
+ notificationTime: 1,
+ },
+ {
+ id: '2',
+ title: '이벤트 2',
+ date: '2025-07-02',
+ startTime: '14:00',
+ endTime: '15:00',
+ description: '동료와 점심 식사',
+ location: '회사 근처 식당',
+ category: '개인',
+ repeat: { type: 'none', interval: 0 },
+ notificationTime: 1,
+ },
+ {
+ id: '3',
+ title: '이벤트 3',
+ date: '2025-07-31',
+ startTime: '14:00',
+ endTime: '15:00',
+ description: '동료와 점심 식사',
+ location: '회사 근처 식당',
+ category: '개인',
+ repeat: { type: 'none', interval: 0 },
+ notificationTime: 1,
+ },
+ {
+ id: '4',
+ title: 'EvEnT 4',
+ date: '2025-07-31',
+ startTime: '14:00',
+ endTime: '15:00',
+ description: '동료와 점심 식사',
+ location: '회사 근처 식당',
+ category: '개인',
+ repeat: { type: 'none', interval: 0 },
+ notificationTime: 1,
+ },
+ {
+ id: '5',
+ title: 'EvEnT 5',
+ date: '2025-08-01',
+ startTime: '14:00',
+ endTime: '15:00',
+ description: '동료와 점심 식사',
+ location: '회사 근처 식당',
+ category: '개인',
+ repeat: { type: 'none', interval: 0 },
+ notificationTime: 1,
+ },
+] as Event[];
+
describe('getFilteredEvents', () => {
- it("검색어 '이벤트 2'에 맞는 이벤트만 반환한다", () => {});
+ it("검색어 '이벤트 2'에 맞는 이벤트만 반환한다", () => {
+ const filteredEvents = getFilteredEvents(events, '이벤트 2', new Date('2025-07-01'), 'month');
+
+ expect(filteredEvents).toEqual([events[1]]);
+ });
+
+ it('주간 뷰에서 2025-07-01 주의 이벤트만 반환한다', () => {
+ const filteredEvents = getFilteredEvents(events, '', new Date('2025-07-01'), 'week');
+
+ expect(filteredEvents).toEqual([events[0], events[1]]);
+ });
+
+ it('월간 뷰에서 2025년 7월의 모든 이벤트를 반환한다', () => {
+ const filteredEvents = getFilteredEvents(events, '', new Date('2025-07-01'), 'month');
+
+ expect(filteredEvents).toEqual([events[0], events[1], events[2], events[3]]);
+ });
+
+ it("검색어 '이벤트'와 주간 뷰 필터링을 동시에 적용한다", () => {
+ const filteredEvents = getFilteredEvents(events, '이벤트', new Date('2025-07-01'), 'week');
+
+ expect(filteredEvents).toEqual([events[0], events[1]]);
+ });
+
+ it('검색어가 없을 때 모든 이벤트를 반환한다', () => {
+ const filteredEvents = getFilteredEvents(events, '', new Date('2025-07-01'), 'month');
- it('주간 뷰에서 2025-07-01 주의 이벤트만 반환한다', () => {});
+ expect(filteredEvents).toEqual([events[0], events[1], events[2], events[3]]);
+ });
- it('월간 뷰에서 2025년 7월의 모든 이벤트를 반환한다', () => {});
+ it('검색어가 대소문자를 구분하지 않고 작동한다', () => {
+ const filteredEvents = getFilteredEvents(events, 'event', new Date('2025-07-01'), 'month');
- it("검색어 '이벤트'와 주간 뷰 필터링을 동시에 적용한다", () => {});
+ expect(filteredEvents).toEqual([events[3]]);
+ });
- it('검색어가 없을 때 모든 이벤트를 반환한다', () => {});
+ it('월의 경계에 있는 이벤트를 올바르게 필터링한다', () => {
+ const filteredEvents = getFilteredEvents(events, '', new Date('2025-07-31'), 'month');
- it('검색어가 대소문자를 구분하지 않고 작동한다', () => {});
+ expect(filteredEvents).toEqual([events[0], events[1], events[2], events[3]]);
+ });
- it('월의 경계에 있는 이벤트를 올바르게 필터링한다', () => {});
+ it('빈 이벤트 리스트에 대해 빈 배열을 반환한다', () => {
+ const filteredEvents = getFilteredEvents([], '', new Date('2025-07-01'), 'month');
- it('빈 이벤트 리스트에 대해 빈 배열을 반환한다', () => {});
+ expect(filteredEvents).toEqual([]);
+ });
});
diff --git a/src/__tests__/unit/easy.fetchHolidays.spec.ts b/src/__tests__/unit/easy.fetchHolidays.spec.ts
index 013e87f0..a777c5fe 100644
--- a/src/__tests__/unit/easy.fetchHolidays.spec.ts
+++ b/src/__tests__/unit/easy.fetchHolidays.spec.ts
@@ -1,8 +1,35 @@
import { fetchHolidays } from '../../apis/fetchHolidays';
+
describe('fetchHolidays', () => {
- it('주어진 월의 공휴일만 반환한다', () => {});
+ it('주어진 월의 공휴일만 반환한다', () => {
+ const result = fetchHolidays(new Date('2025-10-01'));
+
+ console.log(result);
+
+ expect(result).toEqual({
+ '2025-10-05': '추석',
+ '2025-10-06': '추석',
+ '2025-10-07': '추석',
+ '2025-10-03': '개천절',
+ '2025-10-09': '한글날',
+ });
+ });
+
+ it('공휴일이 없는 월에 대해 빈 객체를 반환한다', () => {
+ const result = fetchHolidays(new Date('2025-11-01'));
+
+ expect(result).toEqual({});
+ });
- it('공휴일이 없는 월에 대해 빈 객체를 반환한다', () => {});
+ it('여러 공휴일이 있는 월에 대해 모든 공휴일을 반환한다', () => {
+ const result = fetchHolidays(new Date('2025-10-01'));
- it('여러 공휴일이 있는 월에 대해 모든 공휴일을 반환한다', () => {});
+ expect(result).toEqual({
+ '2025-10-05': '추석',
+ '2025-10-06': '추석',
+ '2025-10-07': '추석',
+ '2025-10-03': '개천절',
+ '2025-10-09': '한글날',
+ });
+ });
});
diff --git a/src/__tests__/unit/easy.notificationUtils.spec.ts b/src/__tests__/unit/easy.notificationUtils.spec.ts
index 2fe10360..d2c135e4 100644
--- a/src/__tests__/unit/easy.notificationUtils.spec.ts
+++ b/src/__tests__/unit/easy.notificationUtils.spec.ts
@@ -1,16 +1,113 @@
import { Event } from '../../types';
import { createNotificationMessage, getUpcomingEvents } from '../../utils/notificationUtils';
+const events = [
+ {
+ id: '1',
+ title: '이벤트 1',
+ date: '2025-07-01',
+ startTime: '14:00',
+ endTime: '15:00',
+ description: '동료와 점심 식사',
+ location: '회사 근처 식당',
+ category: '개인',
+ repeat: { type: 'none', interval: 0 },
+ notificationTime: 1,
+ },
+ {
+ id: '2',
+ title: '이벤트 2',
+ date: '2025-07-02',
+ startTime: '14:00',
+ endTime: '15:00',
+ description: '동료와 점심 식사',
+ location: '회사 근처 식당',
+ category: '개인',
+ repeat: { type: 'none', interval: 0 },
+ notificationTime: 1,
+ },
+ {
+ id: '3',
+ title: '이벤트 3',
+ date: '2025-07-31',
+ startTime: '14:00',
+ endTime: '15:00',
+ description: '동료와 점심 식사',
+ location: '회사 근처 식당',
+ category: '개인',
+ repeat: { type: 'none', interval: 0 },
+ notificationTime: 1,
+ },
+ {
+ id: '4',
+ title: 'EvEnT 4',
+ date: '2025-07-31',
+ startTime: '14:00',
+ endTime: '15:00',
+ description: '동료와 점심 식사',
+ location: '회사 근처 식당',
+ category: '개인',
+ repeat: { type: 'none', interval: 0 },
+ notificationTime: 1,
+ },
+ {
+ id: '5',
+ title: 'EvEnT 5',
+ date: '2025-08-01',
+ startTime: '14:00',
+ endTime: '15:00',
+ description: '동료와 점심 식사',
+ location: '회사 근처 식당',
+ category: '개인',
+ repeat: { type: 'none', interval: 0 },
+ notificationTime: 1,
+ },
+] as Event[];
+
describe('getUpcomingEvents', () => {
- it('알림 시간이 정확히 도래한 이벤트를 반환한다', () => {});
+ it('알림 시간이 정확히 도래한 이벤트를 반환한다', () => {
+ const result = getUpcomingEvents(events, new Date('2025-07-01T13:59:00'), []);
+
+ console.log(result);
+
+ expect(result).toEqual([events[0]]);
+ });
+
+ it('이미 알림이 간 이벤트는 제외한다', () => {
+ const result = getUpcomingEvents(events, new Date('2025-07-01T13:59:00'), ['1']);
+
+ console.log(result);
+
+ expect(result).toEqual([]);
+ });
+
+ it('알림 시간이 아직 도래하지 않은 이벤트는 반환하지 않는다', () => {
+ const result = getUpcomingEvents(events, new Date('2025-07-01T13:00:00'), []);
- it('이미 알림이 간 이벤트는 제외한다', () => {});
+ console.log(result);
- it('알림 시간이 아직 도래하지 않은 이벤트는 반환하지 않는다', () => {});
+ expect(result).toEqual([]);
+ });
- it('알림 시간이 지난 이벤트는 반환하지 않는다', () => {});
+ it('알림 시간이 지난 이벤트는 반환하지 않는다', () => {
+ const result = getUpcomingEvents(events, new Date('2025-07-01T14:01:00'), []);
+
+ console.log(result);
+
+ expect(result).toEqual([]);
+ });
});
describe('createNotificationMessage', () => {
- it('올바른 알림 메시지를 생성해야 한다', () => {});
+ it('올바른 알림 메시지를 생성해야 한다', () => {
+ const event = events[0];
+
+ const { notificationTime, title } = event;
+
+ const result = createNotificationMessage(event);
+
+ console.log(result);
+
+ expect(result).toBe(`${notificationTime}분 후 ${title} 일정이 시작됩니다.`);
+ });
});
diff --git a/src/__tests__/unit/easy.timeValidation.spec.ts b/src/__tests__/unit/easy.timeValidation.spec.ts
index 9dda1954..a61c8fa7 100644
--- a/src/__tests__/unit/easy.timeValidation.spec.ts
+++ b/src/__tests__/unit/easy.timeValidation.spec.ts
@@ -1,15 +1,45 @@
import { getTimeErrorMessage } from '../../utils/timeValidation';
describe('getTimeErrorMessage >', () => {
- it('시작 시간이 종료 시간보다 늦을 때 에러 메시지를 반환한다', () => {});
+ it('시작 시간이 종료 시간보다 늦을 때 에러 메시지를 반환한다', () => {
+ const result = getTimeErrorMessage('12:00', '11:00');
- it('시작 시간과 종료 시간이 같을 때 에러 메시지를 반환한다', () => {});
+ expect(result.startTimeError).toBe('시작 시간은 종료 시간보다 빨라야 합니다.');
+ expect(result.endTimeError).toBe('종료 시간은 시작 시간보다 늦어야 합니다.');
+ });
- it('시작 시간이 종료 시간보다 빠를 때 null을 반환한다', () => {});
+ it('시작 시간과 종료 시간이 같을 때 에러 메시지를 반환한다', () => {
+ const result = getTimeErrorMessage('10:00', '10:00');
- it('시작 시간이 비어있을 때 null을 반환한다', () => {});
+ expect(result.startTimeError).toBe('시작 시간은 종료 시간보다 빨라야 합니다.');
+ expect(result.endTimeError).toBe('종료 시간은 시작 시간보다 늦어야 합니다.');
+ });
- it('종료 시간이 비어있을 때 null을 반환한다', () => {});
+ it('시작 시간이 종료 시간보다 빠를 때 null을 반환한다', () => {
+ const result = getTimeErrorMessage('09:00', '10:00');
- it('시작 시간과 종료 시간이 모두 비어있을 때 null을 반환한다', () => {});
+ expect(result.startTimeError).toBeNull();
+ expect(result.endTimeError).toBeNull();
+ });
+
+ it('시작 시간이 비어있을 때 null을 반환한다', () => {
+ const result = getTimeErrorMessage('', '10:00');
+
+ expect(result.startTimeError).toBeNull();
+ expect(result.endTimeError).toBeNull();
+ });
+
+ it('종료 시간이 비어있을 때 null을 반환한다', () => {
+ const result = getTimeErrorMessage('10:00', '');
+
+ expect(result.startTimeError).toBeNull();
+ expect(result.endTimeError).toBeNull();
+ });
+
+ it('시작 시간과 종료 시간이 모두 비어있을 때 null을 반환한다', () => {
+ const result = getTimeErrorMessage('', '');
+
+ expect(result.startTimeError).toBeNull();
+ expect(result.endTimeError).toBeNull();
+ });
});