Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
563cea2
test: getDaysInMonth 함수 유닛 테스트 작성
eveneul Aug 19, 2025
4bfdc6b
feat: MSW 핸들러 추가 및 수정 - 이벤트 조회, 생성, 업데이트, 삭제 기능 구현
eveneul Aug 21, 2025
d5d6959
feat: MSW 핸들러 수정 및 유닛 테스트 추가 - 이벤트 업데이트 및 삭제 기능 구현
eveneul Aug 21, 2025
77c8412
feat: MSW handlersUtils.ts -> server.use로 작성
eveneul Aug 21, 2025
8bba88c
feat: 이벤트 핸들러 수정 - 이벤트 상태 공유 및 카테고리 변경
eveneul Aug 21, 2025
e7be3fb
test: getWeekDates 함수에 대한 유닛 테스트 추가 - 다양한 날짜에 대한 주의 날짜 반환 검증
eveneul Aug 21, 2025
9ddd413
test: getWeeksAtMonth 함수에 대한 유닛 테스트 추가 - 2025년 7월의 주 정보 검증
eveneul Aug 21, 2025
fa5ebd2
test: getEventsForDay 함수에 대한 유닛 테스트 추가 - 특정 날짜의 이벤트 반환 검증 및 빈 배열 반환 검증
eveneul Aug 21, 2025
3063ac9
test: formatWeek 함수에 대한 유닛 테스트 추가 - 다양한 날짜에 대한 주 정보 검증
eveneul Aug 21, 2025
18b2668
test: formatMonth 함수에 대한 유닛 테스트 추가 - 2025년 7월 10일을 '2025년 7월'로 반환하는지 검증
eveneul Aug 21, 2025
fd092e4
test: isDateInRange 함수에 대한 유닛 테스트 추가 - 2025년 7월의 날짜 범위 검증
eveneul Aug 21, 2025
17118a5
test: fillZero 함수에 대한 유닛 테스트 추가 - 다양한 입력값에 대한 반환 검증
eveneul Aug 21, 2025
31f2308
test: formatDate 함수에 대한 유닛 테스트 추가 - 다양한 날짜 포맷팅 검증
eveneul Aug 21, 2025
7cde4c7
test: 이벤트 관련 함수에 대한 유닛 테스트 추가 - 날짜 및 시간 형식 검증, 이벤트 겹침 확인
eveneul Aug 21, 2025
66375b2
test: getFilteredEvents 함수에 대한 유닛 테스트 추가 - 다양한 필터링 조건 검증 및 빈 이벤트 리스트 처리
eveneul Aug 21, 2025
3014cb2
test: fetchHolidays 함수에 대한 유닛 테스트 추가 - 공휴일 반환 및 빈 객체 처리 검증
eveneul Aug 21, 2025
4d49fae
test: getUpcomingEvents 및 createNotificationMessage 함수에 대한 유닛 테스트 추가 …
eveneul Aug 21, 2025
1911f20
test: getUpcomingEvents 함수의 유닛 테스트 수정 - 알림 메시지 주석 내용 변경
eveneul Aug 21, 2025
3f0f310
test: getTimeErrorMessage 함수에 대한 유닛 테스트 추가 - 시작 및 종료 시간 검증 로직 구현
eveneul Aug 21, 2025
9705f40
test: useCalendarView 훅에 대한 유닛 테스트 추가 - 초기 상태 및 뷰 변경, 날짜 탐색 기능 검증
eveneul Aug 21, 2025
a9f526f
test: useSearch 훅에 대한 유닛 테스트 추가 - 검색어에 따른 이벤트 필터링 및 현재 뷰에 맞는 이벤트 반환 검증
eveneul Aug 21, 2025
472be20
test: useCalendarView 훅의 유닛 테스트 수정 - 불필요한 import 문 제거
eveneul Aug 21, 2025
16cfebc
cSpell.json 파일 추가 - "notistack" 단어 등록
eveneul Aug 21, 2025
6764b18
@eslint/js 설치
neul22 Aug 22, 2025
0e27b26
test: useSearch 훅의 유닛 테스트 수정 - 검색 기간 및 필터링 로직 개선
eveneul Aug 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cSpell.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"words": ["notistack"]
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 47 additions & 4 deletions src/__mocks__/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,54 @@
// ! HARD
// ! 각 응답에 대한 MSW 핸들러를 작성해주세요. GET 요청은 이미 작성되어 있는 events json을 활용해주세요.
export const handlers = [
http.get('/api/events', () => {}),
http.get('/api/events', () => {
// 조회
// status 값을 생략하면 명시적으로 200
return HttpResponse.json({ events });
}),

http.post('/api/events', async ({ request }) => {}),
http.post('/api/events', async ({ request }) => {
// 생성

http.put('/api/events/:id', async ({ params, request }) => {}),
try {
const newEvents = (await request.json()) as Event;
newEvents.id = String(events.length + 1); // id 타입이 string
events.push(newEvents);

http.delete('/api/events/:id', ({ params }) => {}),
return HttpResponse.json({ newEvents }, { status: 201 });
} catch (err) {

Check failure on line 24 in src/__mocks__/handlers.ts

View workflow job for this annotation

GitHub Actions / lint

'err' is defined but never used

Check warning on line 24 in src/__mocks__/handlers.ts

View workflow job for this annotation

GitHub Actions / lint

'err' is defined but never used
return HttpResponse.json({ error: '이벤트 생성에 실패했습니다.' }, { status: 400 });
}
}),

http.put('/api/events/:id', async ({ params, request }) => {
// 업데이트
const { id } = params;
const updateEvent = (await request.json()) as Event;
const index = events.findIndex((event) => event.id === id); // 업데이트할 이벤트 찾기

if (index !== -1) {
// 수정하고자 하는 이벤트가 (findIndex로) 잘 찾아졌으면
events[index] = { ...events[index], ...updateEvent };
return HttpResponse.json({ ...events[index], ...updateEvent }, { status: 200 });
}

// 수정하고자 하는 이벤트가 없으면
return HttpResponse.json(null, { status: 404 });
}),

http.delete('/api/events/:id', ({ params }) => {
// 삭제

const { id } = params;
const index = events.findIndex((event) => event.id === id); // 삭제할 이벤트 찾기
if (index !== -1) {
// 삭제하고자 하는 이벤트가 잘 찾아졌으면
events.splice(index, 1);
return HttpResponse.json(null, { status: 204 });
}

// 삭제하고자 하는 이벤트가 없으면
return HttpResponse.json(null, { status: 404 });
}),
];
149 changes: 144 additions & 5 deletions src/__mocks__/handlersUtils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,149 @@
import { http, HttpResponse } from 'msw';

Check failure on line 1 in src/__mocks__/handlersUtils.ts

View workflow job for this annotation

GitHub Actions / lint

There should be at least one empty line between import groups
import { Event } from '../types';
import { server } from '../setupTests';

Check failure on line 3 in src/__mocks__/handlersUtils.ts

View workflow job for this annotation

GitHub Actions / lint

`../setupTests` import should occur before import of `../types`

// ! Hard
// ! 이벤트는 생성, 수정 되면 fetch를 다시 해 상태를 업데이트 합니다. 이를 위한 제어가 필요할 것 같은데요. 어떻게 작성해야 테스트가 병렬로 돌아도 안정적이게 동작할까요?
// ! 아래 이름을 사용하지 않아도 되니, 독립적이게 테스트를 구동할 수 있는 방법을 찾아보세요. 그리고 이 로직을 PR에 설명해주세요.
export const setupMockHandlerCreation = (initEvents = [] as Event[]) => {};
// ! 이벤트는 생성, 수정 되면 fetch를 다시 해 상태를 업데이트 합니다.
// 이를 위한 제어가 필요할 것 같은데요.
// 어떻게 작성해야 테스트가 병렬로 돌아도 안정적이게 동작할까요?

export const setupMockHandlerUpdating = () => {};
// ! 아래 이름을 사용하지 않아도 되니,
// 독립적이게 테스트를 구동할 수 있는 방법을 찾아보세요.
// 그리고 이 로직을 PR에 설명해주세요.... (-> 제가요?)

export const setupMockHandlerDeletion = () => {};
// server.use -> handler.ts에 있는 http 함수들은 모든 테스트에 기본적으로 깔려 있는 msw
// 그런데 테스트를 하다 보면 다른 상황에서의 API 호출(fetch)이 필요하니
// 그때 server.use로 handler.ts에 적혀 있는 msw를 덮어 씌우는 역할을 함
// 특정 상황에서만 사용되니 export

// 같은 변수값 상태 공유를 위해 상단에서 초기화
let events: Event[] = [];

export const setupMockHandlerCreation = (initEvents = [] as Event[]) => {
// 이벤트 생성 -> 이벤트 배열이 우선적으로 필요하고, 거기에 id를 추가한 새 이벤트를 넣는다
// handlers.ts에 있는 http.get, http.post와 비슷...?
// initEvents가 필요한 이유: 테스트마다 다른 값들이 필요할 수 있어서..

events = [...initEvents];

server.use(
http.get('/api/events', () => {
return HttpResponse.json({ events });
}),
http.post('/api/events', async ({ request }) => {
try {
const newEvents = (await request.json()) as Event;
newEvents.id = String(events.length + 1);
events.push(newEvents);

return HttpResponse.json({ newEvents }, { status: 201 });
} catch (err) {

Check failure on line 40 in src/__mocks__/handlersUtils.ts

View workflow job for this annotation

GitHub Actions / lint

'err' is defined but never used

Check warning on line 40 in src/__mocks__/handlersUtils.ts

View workflow job for this annotation

GitHub Actions / lint

'err' is defined but never used
return HttpResponse.json({ error: '이벤트 생성에 실패했습니다.' }, { status: 400 });
}
})
);
};

export const setupMockHandlerUpdating = () => {
// update면 http.put과 비슷하게 해도 되지 않을까?
// 그런데 왜 인자 request가 없을까.. -> 기본적인 목업 데이터를 만들어라?

events = [
{
id: '1',
title: '소희랑 밥 먹기',
date: '2025-08-22',
startTime: '21:00',
endTime: '23:00',
description: '수민지혜유진송이 초대하기',
location: '강남역',
category: '개인',
repeat: { type: 'none', interval: 0 },
notificationTime: 10,
},
{
id: '2',
title: '병준이 집들이 가기',
date: '2025-08-27',
startTime: '18:00',
endTime: '23:00',
description: '4팀 7팀 다 모여',
location: '병준이네 집',
category: '개인',
repeat: { type: 'none', interval: 0 },
notificationTime: 10,
},
];

// 먼저 기존 데이터(위에 작성한 event)를 get으로 불러오고, put으로 추가
server.use(
http.get('/api/events', () => {
return HttpResponse.json({ events });
}),

http.put('/api/events/:id', async ({ params, request }) => {
// 업데이트
const { id } = params;
const updateEvent = (await request.json()) as Event;
const index = events.findIndex((event) => event.id === id); // 업데이트할 이벤트 찾기

if (index !== -1) {
// 수정하고자 하는 이벤트가 (findIndex로) 잘 찾아졌으면
events[index] = { ...events[index], ...updateEvent };
return HttpResponse.json({ ...events[index], ...updateEvent }, { status: 200 });
}

// 수정하고자 하는 이벤트가 없으면
return HttpResponse.json(null, { status: 404 });
})
);
};

export const setupMockHandlerDeletion = () => {
events = [
{
id: '1',
title: '소희랑 밥 먹기',
date: '2025-08-22',
startTime: '21:00',
endTime: '23:00',
description: '수민지혜유진송이 초대하기',
location: '강남역',
category: '개인',
repeat: { type: 'none', interval: 0 },
notificationTime: 10,
},
{
id: '2',
title: '병준이 집들이 가기',
date: '2025-08-27',
startTime: '18:00',
endTime: '23:00',
description: '4팀 7팀 다 모여',
location: '병준이네 집',
category: '개인',
repeat: { type: 'none', interval: 0 },
notificationTime: 10,
},
];

server.use(
http.get('/api/events', () => {
return HttpResponse.json({ events });
}),
http.delete('/api/events/:id', ({ params }) => {
// 삭제

const { id } = params;
const index = events.findIndex((event) => event.id === id); // 삭제할 이벤트 찾기
if (index !== -1) {
// 삭제하고자 하는 이벤트가 잘 찾아졌으면
events.splice(index, 1);
return HttpResponse.json(null, { status: 204 });
}

// 삭제하고자 하는 이벤트가 없으면
return HttpResponse.json(null, { status: 404 });
})
);
};
120 changes: 110 additions & 10 deletions src/__tests__/hooks/easy.useCalendarView.spec.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,124 @@
import { act, renderHook } from '@testing-library/react';

import { useCalendarView } from '../../hooks/useCalendarView.ts';
import { assertDate } from '../utils.ts';

describe('초기 상태', () => {
it('view는 "month"이어야 한다', () => {});
// renderHook으로 감싸면 result.current로 return으로 접근해야 함
const { result } = renderHook(() => useCalendarView());

it('currentDate는 오늘 날짜인 "2025-10-01"이어야 한다', () => {});
it('view는 "month"이어야 한다', () => {
expect(result.current.view).toBe('month');
});

it('holidays는 10월 휴일인 개천절, 한글날, 추석이 지정되어 있어야 한다', () => {});
it('currentDate는 오늘 날짜이어야 한다', () => {
expect(result.current.currentDate.getFullYear()).toBe(new Date().getFullYear());
expect(result.current.currentDate.getMonth()).toBe(new Date().getMonth());
expect(result.current.currentDate.getDate()).toBe(new Date().getDate());
});

it('holidays는 10월 휴일인 개천절, 한글날, 추석이 지정되어 있어야 한다', () => {
act(() => {
result.current.setCurrentDate(new Date('2025-10-01'));
});

const holidays = {
'2025-10-05': '추석',
'2025-10-06': '추석',
'2025-10-07': '추석',
'2025-10-03': '개천절',
'2025-10-09': '한글날',
};

expect(result.current.holidays).toEqual(holidays);
});
});

it("view를 'week'으로 변경 시 적절하게 반영된다", () => {
const { result } = renderHook(() => useCalendarView());
act(() => {
result.current.setView('week');
});

expect(result.current.view).toBe('week');
});

it("주간 뷰에서 다음으로 navigate시 7일 후 '2025-08-29' 날짜로 지정이 된다", () => {
const { result } = renderHook(() => useCalendarView());

// setCurrentDate를 오늘 날짜로 해야 할 것 같았는데 딱히 안 그래도 되는 느낌..
act(() => {
result.current.setCurrentDate(new Date('2025-08-22'));
result.current.setView('week');
});

act(() => {
result.current.navigate('next');
});

const expectedDate = new Date('2025-08-29');

expect(result.current.currentDate).toEqual(expectedDate);
});

it("view를 'week'으로 변경 시 적절하게 반영된다", () => {});
it("주간 뷰에서 이전으로 navigate시 7일 전인 '2025-09-24' 날짜로 지정이 된다", () => {
const { result } = renderHook(() => useCalendarView());

act(() => {
result.current.setCurrentDate(new Date('2025-10-01'));
result.current.setView('week');
});

it("주간 뷰에서 다음으로 navigate시 7일 후 '2025-10-08' 날짜로 지정이 된다", () => {});
act(() => {
result.current.navigate('prev');
});

it("주간 뷰에서 이전으로 navigate시 7일 후 '2025-09-24' 날짜로 지정이 된다", () => {});
const expectedDate = new Date('2025-09-24');

it("월간 뷰에서 다음으로 navigate시 한 달 후 '2025-11-01' 날짜여야 한다", () => {});
expect(result.current.currentDate).toEqual(expectedDate);
});

it("월간 뷰에서 이전으로 navigate시 한 달 전 '2025-09-01' 날짜여야 한다", () => {});
it("월간 뷰에서 다음으로 navigate시 한 달 후 '2025-11-01' 날짜여야 한다", () => {
const { result } = renderHook(() => useCalendarView());

it("currentDate가 '2025-03-01' 변경되면 3월 휴일 '삼일절'로 업데이트되어야 한다", async () => {});
act(() => {
result.current.setCurrentDate(new Date('2025-10-01'));
result.current.setView('month');
});

act(() => {
result.current.navigate('next');
});

const expectedDate = new Date('2025-11-01');

expect(result.current.currentDate).toEqual(expectedDate);
});

it("월간 뷰에서 이전으로 navigate시 한 달 전 '2025-09-01' 날짜여야 한다", () => {
const { result } = renderHook(() => useCalendarView());

act(() => {
result.current.setCurrentDate(new Date('2025-10-01'));
result.current.setView('month');
});

act(() => {
result.current.navigate('prev');
});

const expectedDate = new Date('2025-09-01');

expect(result.current.currentDate).toEqual(expectedDate);
});

it("currentDate가 '2025-03-01' 변경되면 3월 휴일 '삼일절'로 업데이트되어야 한다", async () => {
const { result } = renderHook(() => useCalendarView());
const holidays = {
'2025-03-01': '삼일절',
};

act(() => {
result.current.setCurrentDate(new Date('2025-03-01'));
});
expect(result.current.holidays).toEqual(holidays);
});
Loading
Loading