8주차 과제 체크포인트
기본 과제
필수
- 반복 유형 선택
- 일정 생성 또는 수정 시 반복 유형을 선택할 수 있다.
- 반복 유형은 다음과 같다: 매일, 매주, 매월, 매년
- 31일에 매월을 선택한다면 -> 매월 마지막이 아닌, 31일에만 생성하세요.
- 윤년 29일에 매년을 선택한다면 -> 29일에만 생성하세요!
- 반복 일정 표시
- 캘린더 뷰에서 반복 일정을 시각적으로 구분하여 표시한다.
- 아이콘을 넣든 태그를 넣든 자유롭게 해보세요!
- 캘린더 뷰에서 반복 일정을 시각적으로 구분하여 표시한다.
- 반복 종료
- 반복 종료 조건을 지정할 수 있다.
- 옵션: 특정 날짜까지, 특정 횟수만큼, 또는 종료 없음 (예제 특성상, 2025-06-30까지)
- 반복 일정 단일 수정
- 반복일정을 수정하면 단일 일정으로 변경됩니다.
- 반복일정 아이콘도 사라집니다.
- 반복 일정 단일 삭제
- 반복일정을 삭제하면 해당 일정만 삭제합니다.
선택
- 반복 간격 설정
- 각 반복 유형에 대해 간격을 설정할 수 있다.
- 예: 2일마다, 3주마다, 2개월마다 등
- 예외 날짜 처리:
- 반복 일정 중 특정 날짜를 제외할 수 있다.
- 반복 일정 중 특정 날짜의 일정을 수정할 수 있다.
- 요일 지정 (주간 반복의 경우):
- 주간 반복 시 특정 요일을 선택할 수 있다.
- 월간 반복 옵션:
- 매월 특정 날짜에 반복되도록 설정할 수 있다.
- 매월 특정 순서의 요일에 반복되도록 설정할 수 있다.
- 반복 일정 전체 수정 및 삭제
- 반복 일정의 모든 일정을 수정할 수 있다.
- 반복 일정의 모든 일정을 삭제할 수 있다.
심화 과제
- 이 앱에 적합한 테스트 전략을 만들었나요?
각 팀원들의 테스트 전략은?
피그잼을 통해 서로 원하는 방식을 공유하며 테스트 전략을 짜도록 진행하였습니다.
합의된 테스트 전략과 그 이유는 무엇인가요?
페어 2팀은 e2e 테스트에 대한 의문이 가장 컸고, 통합 테스트의 비율을 높이는데에 집중하였습니다.
추가로 작성된 테스트 코드는 어떤 것들이 있나요?
반복 일정에 대한 통합테스트 추가
과제 셀프회고
이번 주차 과제는 이해하고 파악하는 데 시간이 많이 걸렸던 주차였습니다. 이해를 하고 난 뒤에도 “어디서부터 시작해야 하지?”라는 고민이 들어 쉽게 손을 못 댔습니다. 태영님이 젭에서 *“이렇게까지 오래 걸릴 일이 아닌데”*라고 말씀하셨을 때, 저도 되돌아보니 정말 그랬던 것 같다는 생각이 들었습니다.
그래도 이번 과제를 통해 TDD 방식으로 개발하는 경험을 해볼 수 있었고, 그 과정에서 TDD의 필요성과 장점을 조금은 체감할 수 있었습니다. 확실히 테스트 코드를 작성하는 데 흥미가 붙었고, 이게 가장 큰 수확이었다고 생각합니다.
확실히 테스트 코드에 대한 재미가 붙었습니다. :)
기술적 성장
- 테스트 코드 작성 (getBy / getAllBy)
- getBy는 하나의 요소만 반환할 때 사용.
- getAllBy는 여러 개의 요소가 있을 때 사용.
let repeatIcons = within(monthView).getAllByTestId('repeat-icon');
expect(repeatIcons.length).toBe(5);
→ repeat-icon을 가진 요소가 여러 개이므로 getAllByTestId를 사용했고, 그 개수를 length로 확인할 수 있었습니다.
- closest("td") 사용
closest는 상위 요소에 접근하여 원하는 요소를 찾을 수 있도록 해줍니다.
for (let day of [25, 26, 27, 28, 29, 30]) {
const cell = within(monthView).getByText(String(day)).closest('td')!;
expect(within(cell).getByText('기존 회의')).toBeInTheDocument();
expect(within(cell).getByTestId('repeat-icon')).toBeInTheDocument();
}
이 코드를 통해 달력에서 해당 날짜 셀(td)을 찾아 그 안에 반복 아이콘과 일정이 있는지 확인할 수 있었습니다.
솔루션 코드에서 다음과 같은 예시를 보고 아이디어를 얻어 활용했습니다.
// 1월 1일 셀 확인
const januaryFirstCell = within(monthView).getByText('1').closest('td')!;
expect(within(januaryFirstCell).getByText('신정')).toBeInTheDocument();
- 뒤에 붙는 ! (Non-null assertion)
closest('td')의 반환 타입은 Element | null 입니다.
TypeScript에서는 null 가능성을 허용하지 않는 경우 에러가 발생합니다.
!는 "여기서는 절대 null이 될 리 없어!"라고 TypeScript에 확신을 주는 연산자입니다.
따라서 !는 요소가 무조건 존재한다는 확신이 있을 때만 사용하는 것이 안전합니다.
코드 품질
const saveEvent = async (eventData: Event | EventForm) => {
try {
let response;
if (editing) {
response = await fetch(`/api/events/${(eventData as Event).id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(eventData),
});
} else {
if (eventData.repeat.type !== 'none') {
const repeatedEvents = repeatingDates(eventData as EventForm);
response = await fetch('/api/events-list', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ events: repeatedEvents }),
});
} else {
response = await fetch('/api/events', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(eventData),
});
}
}
if (!response.ok) {
throw new Error('Failed to save event');
}
await fetchEvents();
onSave?.();
enqueueSnackbar(editing ? '일정이 수정되었습니다.' : '일정이 추가되었습니다.', {
variant: 'success',
});
} catch (error) {
console.error('Error saving event:', error);
enqueueSnackbar('일정 저장 실패', { variant: 'error' });
}
};
수정일 때 repeat에 대한 처리를 해주지 않아서, 수정할 때 반복 일정을 추가하면 반복 일정 수정이 안된다. 리팩토링을 통해 수정할 때 반복 일정 구현을 해보고 싶다.
학습 효과 분석
아직 mock에 대한 이해가 잘 가지 않습니다. 어떤식으로 동작을 하는지에 대한 이해와 적용이 필요하다고 생각합니다.
과제 피드백
TDD 방식을 통해 구현을 해볼 수 있어서 좋았습니다.
리뷰 받고 싶은 내용
const saveEvent = async (eventData: Event | EventForm) => {
try {
let response;
if (editing) {
response = await fetch(`/api/events/${(eventData as Event).id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(eventData),
});
} else {
if (eventData.repeat.type !== 'none') {
const repeatedEvents = repeatingDates(eventData as EventForm);
response = await fetch('/api/events-list', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ events: repeatedEvents }),
});
} else {
response = await fetch('/api/events', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(eventData),
});
}
}
여기서 editing일 때에도 repeat처리를 해줘야 수정할 때 반복 일정처리가 가능할까요?
과제 피드백
안녕하세요 수현님! 8주차 과제 잘 진행해주셨네요 ㅎㅎ 고생하셨습니다!!
태영님이 젭에서 “이렇게까지 오래 걸릴 일이 아닌데” 라고 말씀하셨을 때, 저도 되돌아보니 정말 그랬던 것 같다는 생각이 들었습니다.
고민을 많이 해보는건 좋다고 생각해요! 이런 과정이 있었기 때문에 다음에 시도할 때는 더 빠르게 진행할 수 있으리라 생각해요!
확실히 테스트 코드에 대한 재미가 붙었습니다. :)
제일 큰 수확이네요!! 사실 테스트에 대한 의미와 이를 해야겠다는 의지가 무엇보다 중요하다고 생각해요. 그래야 진행할 수 있으니까요!
editing일 때에도 repeat처리를 해줘야 수정할 때 반복 일정처리가 가능할까요?
현재 API에서는 기능을 제공하고 있지 않네요 ㅠㅠ 현재 과제의 구조를 보면.. 프론트에서 처리를 해야할 것 같아요 ㅋㅋ 사용자가 "전체 반복 일정 수정" 항목을 체크해서 수정한다면 전체 일정이 수정되도록 payload를 만들어서 전송해야겠죠!?
지금 보니까
app.put('/api/events-list', async (req, res) => {
const events = await getEvents();
let isUpdated = false;
const newEvents = [...events.events];
req.body.events.forEach((event) => {
const eventIndex = events.events.findIndex((target) => target.id === event.id);
if (eventIndex > -1) {
isUpdated = true;
newEvents[eventIndex] = { ...events.events[eventIndex], ...event };
}
});
if (isUpdated) {
fs.writeFileSync(
`${__dirname}/src/__mocks__/response/realEvents.json`,
JSON.stringify({
events: newEvents,
})
);
res.json(events.events);
} else {
res.status(404).send('Event not found');
}
});
이 API를 사용해야 할 것 같네요 ㅎㅎ 수정할 이벤트 목록을 한 번에 전송해야하는 구조랍니다.