From c24dad5c385f56594bb52b69bd51aec8ad7df469 Mon Sep 17 00:00:00 2001 From: Uichan Lee Date: Thu, 13 Jun 2024 16:28:24 +0900 Subject: [PATCH 01/31] =?UTF-8?q?feat:=20useFollow=20unit=20test=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../follow/test/useFollow.unit.test.tsx | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/features/follow/test/useFollow.unit.test.tsx diff --git a/src/features/follow/test/useFollow.unit.test.tsx b/src/features/follow/test/useFollow.unit.test.tsx new file mode 100644 index 0000000..67a0941 --- /dev/null +++ b/src/features/follow/test/useFollow.unit.test.tsx @@ -0,0 +1,62 @@ +import { renderHook, act, waitFor } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; + +import { createQueryClientWrapper } from '@/shared/tests'; + +import { useFollow } from '../api'; + +describe('Follow 기능 테스트', () => { + it('self 상태일 때 Follow 기능이 동작하지 않는다.', async () => { + // given + const { result } = renderHook(() => useFollow('self', false), { + wrapper: createQueryClientWrapper(), + }); + + // when + act(() => result.current.handleFollow()); + + // then + await waitFor(() => { + const { + data: { initialStatus }, + } = result.current.data; + + expect(initialStatus).toBe('self'); + }); + }); + + it('none 상태일 때 Follow 기능 실행 시, 공개 계정이라면 following 상태로 변경된다.', async () => { + //given + const { result } = renderHook(() => useFollow('none', false), { + wrapper: createQueryClientWrapper(), + }); + + //when + act(() => result.current.handleFollow()); + + //then + await waitFor(() => { + const { + data: { initialStatus }, + } = result.current.data; + + expect(initialStatus).toBe('following'); + }); + }); + + it('none 상태일 때 Follow 기능 실행 시, 비공개 계정이라면 pending 상태로 변경된다.', async () => { + const { result } = renderHook(() => useFollow('none', true), { + wrapper: createQueryClientWrapper(), + }); + + act(() => result.current.handleFollow()); + + await waitFor(() => { + const { + data: { initialStatus }, + } = result.current.data; + + expect(initialStatus).toBe('pending'); + }); + }); +}); From d15faf0109bfad64a0f825ecc1dc501c029bb943 Mon Sep 17 00:00:00 2001 From: Uichan Lee Date: Thu, 13 Jun 2024 16:29:46 +0900 Subject: [PATCH 02/31] =?UTF-8?q?feat:=20useUnfollow=20unit=20test=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../follow/test/useUnfollow.unit.test.tsx | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/features/follow/test/useUnfollow.unit.test.tsx diff --git a/src/features/follow/test/useUnfollow.unit.test.tsx b/src/features/follow/test/useUnfollow.unit.test.tsx new file mode 100644 index 0000000..2b96dde --- /dev/null +++ b/src/features/follow/test/useUnfollow.unit.test.tsx @@ -0,0 +1,65 @@ +import { renderHook, act, waitFor } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; + +import { createQueryClientWrapper } from '@/shared/tests'; + +import { useUnfollow } from '../api'; + +describe('Unfollow 기능 테스트', () => { + it('self 상태일 때 Unfollow 기능이 동작하지 않는다.', async () => { + // given + const { result } = renderHook(() => useUnfollow('self', false), { + wrapper: createQueryClientWrapper(), + }); + + // when + act(() => result.current.handleUnfollow()); + + // then + await waitFor(() => { + const { + data: { initialStatus }, + } = result.current.data; + + expect(initialStatus).toBe('self'); + }); + }); + + it('following 상태일 때 Unfollow 기능 실행 시, none 상태로 변경된다.', async () => { + // given + const { result } = renderHook(() => useUnfollow('following', false), { + wrapper: createQueryClientWrapper(), + }); + + // when + act(() => result.current.handleUnfollow()); + + // then + await waitFor(() => { + const { + data: { initialStatus }, + } = result.current.data; + + expect(initialStatus).toBe('none'); + }); + }); + + it('pending 상태일 때 Unfollow 기능 실행 시, none 상태로 변경된다.', async () => { + // given + const { result } = renderHook(() => useUnfollow('pending', true), { + wrapper: createQueryClientWrapper(), + }); + + //when + act(() => result.current.handleUnfollow()); + + //then + await waitFor(() => { + const { + data: { initialStatus }, + } = result.current.data; + + expect(initialStatus).toBe('none'); + }); + }); +}); From d2dda3c8c5434f7e4a1d902087e6315692422295 Mon Sep 17 00:00:00 2001 From: Uichan Lee Date: Sat, 15 Jun 2024 14:16:01 +0900 Subject: [PATCH 03/31] =?UTF-8?q?feat:=20follow=20axios=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/axios/follow/follow.ts | 29 +++++++++++++++++++++++++++++ src/shared/axios/follow/index.ts | 1 + src/shared/axios/index.ts | 1 + 3 files changed, 31 insertions(+) create mode 100644 src/shared/axios/follow/follow.ts create mode 100644 src/shared/axios/follow/index.ts diff --git a/src/shared/axios/follow/follow.ts b/src/shared/axios/follow/follow.ts new file mode 100644 index 0000000..4d3b473 --- /dev/null +++ b/src/shared/axios/follow/follow.ts @@ -0,0 +1,29 @@ +import { FetchRelationshipStatus } from '@/shared/consts'; + +import { axiosInstance } from '../config/instance'; + +/** + * 팔로우 API + * @param userId 유저 아이디 + * @returns 관계 상태 + */ +export async function requestFollow( + userId: number, +): Promise { + const { data } = await axiosInstance.post(`/users/${userId}/follow`); + + return data; +} + +/** + * 언팔로우/팔로우 취소 API + * @param userId 유저 아이디 + * @returns 관계 상태 + */ +export async function requestUnfollow( + userId: number, +): Promise { + const { data } = await axiosInstance.delete(`/users/${userId}/follow`); + + return data; +} diff --git a/src/shared/axios/follow/index.ts b/src/shared/axios/follow/index.ts new file mode 100644 index 0000000..8e22221 --- /dev/null +++ b/src/shared/axios/follow/index.ts @@ -0,0 +1 @@ +export * from './follow'; diff --git a/src/shared/axios/index.ts b/src/shared/axios/index.ts index 63b64b0..6ccdc3b 100644 --- a/src/shared/axios/index.ts +++ b/src/shared/axios/index.ts @@ -1,3 +1,4 @@ export { axiosInstance } from './config'; export * from './like'; export * from './bookmark'; +export * from './follow'; From 71192754b3b68c4a44490995eb5d36d356a44920 Mon Sep 17 00:00:00 2001 From: Uichan Lee Date: Sat, 15 Jun 2024 21:46:11 +0900 Subject: [PATCH 04/31] =?UTF-8?q?feat:=20follow=20query=20key=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/react-query/consts/keys.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/shared/react-query/consts/keys.ts b/src/shared/react-query/consts/keys.ts index ea3fc87..46578d5 100644 --- a/src/shared/react-query/consts/keys.ts +++ b/src/shared/react-query/consts/keys.ts @@ -1,4 +1,5 @@ export const QUERY_KEYS = Object.freeze({ feeds: 'feeds', users: 'users', + follow: 'follow', }); From dd1c88884fc519184d4105eb0b3846faa51ca43e Mon Sep 17 00:00:00 2001 From: Uichan Lee Date: Sat, 15 Jun 2024 21:52:25 +0900 Subject: [PATCH 05/31] =?UTF-8?q?feat:=20updateRelationshipStatus=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/follow/lib/index.ts | 1 + .../follow/lib/updateRelationshipStatus.ts | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 src/features/follow/lib/index.ts create mode 100644 src/features/follow/lib/updateRelationshipStatus.ts diff --git a/src/features/follow/lib/index.ts b/src/features/follow/lib/index.ts new file mode 100644 index 0000000..1b7847e --- /dev/null +++ b/src/features/follow/lib/index.ts @@ -0,0 +1 @@ +export { updateRelationshipStatus } from './updateRelationshipStatus'; diff --git a/src/features/follow/lib/updateRelationshipStatus.ts b/src/features/follow/lib/updateRelationshipStatus.ts new file mode 100644 index 0000000..a90a241 --- /dev/null +++ b/src/features/follow/lib/updateRelationshipStatus.ts @@ -0,0 +1,19 @@ +import { RelationshipStatus } from '@/shared/consts'; + +export function updateRelationshipStatus( + previousRelationshipStatus: RelationshipStatus, + isPrivate: boolean, +) { + switch (previousRelationshipStatus) { + case 'self': + return 'self'; + case 'following': + return 'none'; + case 'none': + return isPrivate ? 'pending' : 'following'; + case 'pending': + return 'none'; + default: + return previousRelationshipStatus; + } +} From b235c01541865484491e0908af0d7da1dfbf06a1 Mon Sep 17 00:00:00 2001 From: Uichan Lee Date: Sat, 15 Jun 2024 22:01:09 +0900 Subject: [PATCH 06/31] =?UTF-8?q?feat:=20useFollow=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/follow/api/index.ts | 1 + src/features/follow/api/useFollow.tsx | 59 +++++++++++++++++++++++++++ src/features/follow/index.ts | 1 + 3 files changed, 61 insertions(+) create mode 100644 src/features/follow/api/index.ts create mode 100644 src/features/follow/api/useFollow.tsx create mode 100644 src/features/follow/index.ts diff --git a/src/features/follow/api/index.ts b/src/features/follow/api/index.ts new file mode 100644 index 0000000..dc4600b --- /dev/null +++ b/src/features/follow/api/index.ts @@ -0,0 +1 @@ +export { useFollow } from './useFollow'; diff --git a/src/features/follow/api/useFollow.tsx b/src/features/follow/api/useFollow.tsx new file mode 100644 index 0000000..ed65868 --- /dev/null +++ b/src/features/follow/api/useFollow.tsx @@ -0,0 +1,59 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import { requestFollow } from '@/shared/axios'; +import { RelationshipStatus } from '@/shared/consts'; +import { QUERY_KEYS } from '@/shared/react-query'; +import { isErrorResponse } from '@/shared/utils'; + +import { updateRelationshipStatus } from '../lib/updateRelationshipStatus'; + +export const useFollow = (isPrivate: boolean, userId: number) => { + const queryClient = useQueryClient(); + + const { + data, + mutate: handleFollow, + isPending, + } = useMutation({ + mutationFn: () => requestFollow(userId), + onMutate: async () => { + await queryClient.cancelQueries({ queryKey: [QUERY_KEYS.follow] }); + + // 이전 쿼리값의 스냅샷 + const previousQueryData = queryClient.getQueryData([ + QUERY_KEYS.follow, + ]); + + if (!previousQueryData) return; + + // 업데이트 될 쿼리값 + const updatedQueryData = updateRelationshipStatus( + previousQueryData as RelationshipStatus, + isPrivate, + ); + + // setQueryData 함수를 사용해 Optimistic Update를 실시한다. + await queryClient.setQueryData([QUERY_KEYS.follow], updatedQueryData); + + return { previousQueryData }; + }, + onError: (_, __, context) => { + // Network Error일 경우 이전 쿼리값으로 롤백 + queryClient.setQueryData([QUERY_KEYS.follow], context?.previousQueryData); + }, + onSuccess: (response, _, context) => { + if (isErrorResponse(response)) { + // Server Error일 경우 이전 쿼리값으로 롤백 + queryClient.setQueryData( + [QUERY_KEYS.follow], + context.previousQueryData, + ); + } + }, + onSettled: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.follow] }); + }, + }); + + return { data, handleFollow, isPending }; +}; diff --git a/src/features/follow/index.ts b/src/features/follow/index.ts new file mode 100644 index 0000000..b1c13e7 --- /dev/null +++ b/src/features/follow/index.ts @@ -0,0 +1 @@ +export * from './api'; From 9d6a28d1d1b703500310efd7601588b6c397973c Mon Sep 17 00:00:00 2001 From: Uichan Lee Date: Wed, 26 Jun 2024 14:19:18 +0900 Subject: [PATCH 07/31] =?UTF-8?q?feat:=20useFollow=EC=9D=98=20isPrivate=20?= =?UTF-8?q?->=20locked=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/follow/api/useFollow.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/features/follow/api/useFollow.tsx b/src/features/follow/api/useFollow.tsx index ed65868..5482d17 100644 --- a/src/features/follow/api/useFollow.tsx +++ b/src/features/follow/api/useFollow.tsx @@ -7,16 +7,18 @@ import { isErrorResponse } from '@/shared/utils'; import { updateRelationshipStatus } from '../lib/updateRelationshipStatus'; -export const useFollow = (isPrivate: boolean, userId: number) => { +export const useFollow = (userId: number, locked: boolean) => { const queryClient = useQueryClient(); const { data, mutate: handleFollow, - isPending, + isPending: isPendingFollow, } = useMutation({ mutationFn: () => requestFollow(userId), + // mutate가 호출되면 ✨낙관적 업데이트를 위해 onMutate를 실행 onMutate: async () => { + // 진행중인 refetch가 있다면 취소 await queryClient.cancelQueries({ queryKey: [QUERY_KEYS.follow] }); // 이전 쿼리값의 스냅샷 @@ -29,7 +31,7 @@ export const useFollow = (isPrivate: boolean, userId: number) => { // 업데이트 될 쿼리값 const updatedQueryData = updateRelationshipStatus( previousQueryData as RelationshipStatus, - isPrivate, + locked, ); // setQueryData 함수를 사용해 Optimistic Update를 실시한다. @@ -55,5 +57,5 @@ export const useFollow = (isPrivate: boolean, userId: number) => { }, }); - return { data, handleFollow, isPending }; + return { data, handleFollow, isPendingFollow }; }; From 04881aa9667e99a910631f895cd5f852b3f04a89 Mon Sep 17 00:00:00 2001 From: Uichan Lee Date: Wed, 26 Jun 2024 14:20:02 +0900 Subject: [PATCH 08/31] =?UTF-8?q?feat:=20useUnfollow=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/follow/api/index.ts | 1 + src/features/follow/api/useUnfollow.tsx | 61 +++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 src/features/follow/api/useUnfollow.tsx diff --git a/src/features/follow/api/index.ts b/src/features/follow/api/index.ts index dc4600b..e02ebc0 100644 --- a/src/features/follow/api/index.ts +++ b/src/features/follow/api/index.ts @@ -1 +1,2 @@ export { useFollow } from './useFollow'; +export { useUnfollow } from './useUnfollow'; diff --git a/src/features/follow/api/useUnfollow.tsx b/src/features/follow/api/useUnfollow.tsx new file mode 100644 index 0000000..cd07a1e --- /dev/null +++ b/src/features/follow/api/useUnfollow.tsx @@ -0,0 +1,61 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import { requestUnfollow } from '@/shared/axios'; +import { RelationshipStatus } from '@/shared/consts'; +import { QUERY_KEYS } from '@/shared/react-query'; +import { isErrorResponse } from '@/shared/utils'; + +import { updateRelationshipStatus } from '../lib/updateRelationshipStatus'; + +export const useUnfollow = (userId: number, locked: boolean) => { + const queryClient = useQueryClient(); + + const { + data, + mutate: handleUnfollow, + isPending: isPendingUnfollow, + } = useMutation({ + mutationFn: () => requestUnfollow(userId), + // mutate가 호출되면 ✨낙관적 업데이트를 위해 onMutate를 실행 + onMutate: async () => { + // 진행중인 refetch가 있다면 취소 + await queryClient.cancelQueries({ queryKey: [QUERY_KEYS.follow] }); + + // 이전 쿼리값의 스냅샷 + const previousQueryData = queryClient.getQueryData([ + QUERY_KEYS.follow, + ]); + + if (!previousQueryData) return; + + // 업데이트 될 쿼리값 + const updatedQueryData = updateRelationshipStatus( + previousQueryData as RelationshipStatus, + locked, + ); + + // setQueryData 함수를 사용해 Optimistic Update를 실시한다. + await queryClient.setQueryData([QUERY_KEYS.follow], updatedQueryData); + + return { previousQueryData }; + }, + onError: (_, __, context) => { + // Network Error일 경우 이전 쿼리값으로 롤백 + queryClient.setQueryData([QUERY_KEYS.follow], context?.previousQueryData); + }, + onSuccess: (response, _, context) => { + if (isErrorResponse(response)) { + // Server Error일 경우 이전 쿼리값으로 롤백 + queryClient.setQueryData( + [QUERY_KEYS.follow], + context.previousQueryData, + ); + } + }, + onSettled: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.follow] }); + }, + }); + + return { data, handleUnfollow, isPendingUnfollow }; +}; From eacd133f92073707c2ce1c23ed14b398fd0eceae Mon Sep 17 00:00:00 2001 From: Uichan Lee Date: Wed, 26 Jun 2024 14:20:57 +0900 Subject: [PATCH 09/31] =?UTF-8?q?feat:=20updateRelationshipStatus=20isPriv?= =?UTF-8?q?ate=20->=20locked=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/follow/lib/updateRelationshipStatus.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/follow/lib/updateRelationshipStatus.ts b/src/features/follow/lib/updateRelationshipStatus.ts index a90a241..286691b 100644 --- a/src/features/follow/lib/updateRelationshipStatus.ts +++ b/src/features/follow/lib/updateRelationshipStatus.ts @@ -2,7 +2,7 @@ import { RelationshipStatus } from '@/shared/consts'; export function updateRelationshipStatus( previousRelationshipStatus: RelationshipStatus, - isPrivate: boolean, + locked: boolean, ) { switch (previousRelationshipStatus) { case 'self': @@ -10,7 +10,7 @@ export function updateRelationshipStatus( case 'following': return 'none'; case 'none': - return isPrivate ? 'pending' : 'following'; + return locked ? 'pending' : 'following'; case 'pending': return 'none'; default: From b01c820bab57905e63696ea81fd3305e8dabcfec Mon Sep 17 00:00:00 2001 From: Uichan Lee Date: Wed, 26 Jun 2024 14:23:04 +0900 Subject: [PATCH 10/31] =?UTF-8?q?feat:=20follow=20=EC=9A=94=EC=B2=AD?= =?UTF-8?q?=EC=9D=B4=20relationshipStatus=EB=A5=BC=20=EB=B0=98=ED=99=98?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/mocks/handler/follow.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/app/mocks/handler/follow.ts b/src/app/mocks/handler/follow.ts index 5bed3db..55b8d2e 100644 --- a/src/app/mocks/handler/follow.ts +++ b/src/app/mocks/handler/follow.ts @@ -26,8 +26,11 @@ export const followHandler = [ case 'none': if (users[formattedUserId].locked) { relationshipStatus[formattedUserId] = 'pending'; - } else relationshipStatus[formattedUserId] = 'following'; - break; + return createHttpSuccessResponse({ relationshipStatus: 'pending' }); + } else { + relationshipStatus[formattedUserId] = 'following'; + return createHttpSuccessResponse({ relationshipStatus: 'following' }); + } case 'following': return createHttpErrorResponse('4220'); case 'pending': @@ -35,8 +38,6 @@ export const followHandler = [ default: return createHttpErrorResponse('4040'); } - - return createHttpSuccessResponse({}); }), // 2️⃣ 언팔로우 & 팔로우 요청 취소 http.delete('/users/:user_id/follow', ({ params }) => { @@ -56,16 +57,13 @@ export const followHandler = [ return createHttpErrorResponse('4220'); case 'following': relationshipStatus[formattedUserId] = 'none'; - break; + return createHttpSuccessResponse({ relationshipStatus: 'none' }); case 'pending': relationshipStatus[formattedUserId] = 'none'; - break; - + return createHttpSuccessResponse({ relationshipStatus: 'none' }); default: return createHttpErrorResponse('4040'); } - - return createHttpSuccessResponse({}); }), // 3️⃣ 팔로우 확인 http.get('/users/:user_id/follow', ({ params }) => { From 56cae1d10279c3e91407669a145e8242b3750881 Mon Sep 17 00:00:00 2001 From: Uichan Lee Date: Wed, 26 Jun 2024 14:26:08 +0900 Subject: [PATCH 11/31] =?UTF-8?q?feat:=20follow=20=EA=B4=80=EB=A0=A8=20uni?= =?UTF-8?q?t=20test=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 테스트 값을 유효한 값으로 변경하고, 구조분해할당을 수정해 적절한 값으로 expect를 진행하도록 했습니다. 또한 유효하지 않은 test를 제거하고, 문구를 더 명확하게 했습니다. --- .../follow/test/useFollow.unit.test.tsx | 75 +++++++------------ .../follow/test/useUnfollow.unit.test.tsx | 31 ++------ 2 files changed, 35 insertions(+), 71 deletions(-) diff --git a/src/features/follow/test/useFollow.unit.test.tsx b/src/features/follow/test/useFollow.unit.test.tsx index 67a0941..c484caf 100644 --- a/src/features/follow/test/useFollow.unit.test.tsx +++ b/src/features/follow/test/useFollow.unit.test.tsx @@ -6,57 +6,40 @@ import { createQueryClientWrapper } from '@/shared/tests'; import { useFollow } from '../api'; describe('Follow 기능 테스트', () => { - it('self 상태일 때 Follow 기능이 동작하지 않는다.', async () => { - // given - const { result } = renderHook(() => useFollow('self', false), { - wrapper: createQueryClientWrapper(), + describe('none 상태일 때', () => { + it('공개 계정이라면 following 상태로 변경된다.', async () => { + //given + const { result } = renderHook(() => useFollow(9, false), { + wrapper: createQueryClientWrapper(), + }); + + //when + act(() => result.current.handleFollow()); + + //then + await waitFor(() => { + const { + data: { relationshipStatus: initialStatus }, + } = result.current.data; + + expect(initialStatus).toBe('following'); + }); }); - // when - act(() => result.current.handleFollow()); + it('비공개 계정이라면 pending 상태로 변경된다.', async () => { + const { result } = renderHook(() => useFollow(4, true), { + wrapper: createQueryClientWrapper(), + }); - // then - await waitFor(() => { - const { - data: { initialStatus }, - } = result.current.data; + act(() => result.current.handleFollow()); - expect(initialStatus).toBe('self'); - }); - }); - - it('none 상태일 때 Follow 기능 실행 시, 공개 계정이라면 following 상태로 변경된다.', async () => { - //given - const { result } = renderHook(() => useFollow('none', false), { - wrapper: createQueryClientWrapper(), - }); - - //when - act(() => result.current.handleFollow()); - - //then - await waitFor(() => { - const { - data: { initialStatus }, - } = result.current.data; - - expect(initialStatus).toBe('following'); - }); - }); - - it('none 상태일 때 Follow 기능 실행 시, 비공개 계정이라면 pending 상태로 변경된다.', async () => { - const { result } = renderHook(() => useFollow('none', true), { - wrapper: createQueryClientWrapper(), - }); - - act(() => result.current.handleFollow()); - - await waitFor(() => { - const { - data: { initialStatus }, - } = result.current.data; + await waitFor(() => { + const { + data: { relationshipStatus: initialStatus }, + } = result.current.data; - expect(initialStatus).toBe('pending'); + expect(initialStatus).toBe('pending'); + }); }); }); }); diff --git a/src/features/follow/test/useUnfollow.unit.test.tsx b/src/features/follow/test/useUnfollow.unit.test.tsx index 2b96dde..6bc1ec4 100644 --- a/src/features/follow/test/useUnfollow.unit.test.tsx +++ b/src/features/follow/test/useUnfollow.unit.test.tsx @@ -6,9 +6,9 @@ import { createQueryClientWrapper } from '@/shared/tests'; import { useUnfollow } from '../api'; describe('Unfollow 기능 테스트', () => { - it('self 상태일 때 Unfollow 기능이 동작하지 않는다.', async () => { + it('following 상태일 때, none 상태로 변경된다.', async () => { // given - const { result } = renderHook(() => useUnfollow('self', false), { + const { result } = renderHook(() => useUnfollow(2, false), { wrapper: createQueryClientWrapper(), }); @@ -18,35 +18,16 @@ describe('Unfollow 기능 테스트', () => { // then await waitFor(() => { const { - data: { initialStatus }, - } = result.current.data; - - expect(initialStatus).toBe('self'); - }); - }); - - it('following 상태일 때 Unfollow 기능 실행 시, none 상태로 변경된다.', async () => { - // given - const { result } = renderHook(() => useUnfollow('following', false), { - wrapper: createQueryClientWrapper(), - }); - - // when - act(() => result.current.handleUnfollow()); - - // then - await waitFor(() => { - const { - data: { initialStatus }, + data: { relationshipStatus: initialStatus }, } = result.current.data; expect(initialStatus).toBe('none'); }); }); - it('pending 상태일 때 Unfollow 기능 실행 시, none 상태로 변경된다.', async () => { + it('pending 상태일 때, none 상태로 변경된다.', async () => { // given - const { result } = renderHook(() => useUnfollow('pending', true), { + const { result } = renderHook(() => useUnfollow(3, true), { wrapper: createQueryClientWrapper(), }); @@ -56,7 +37,7 @@ describe('Unfollow 기능 테스트', () => { //then await waitFor(() => { const { - data: { initialStatus }, + data: { relationshipStatus: initialStatus }, } = result.current.data; expect(initialStatus).toBe('none'); From 7a52d9f54423c21cb4727d405d483b3c9a00e6c0 Mon Sep 17 00:00:00 2001 From: Uichan Lee Date: Wed, 26 Jun 2024 14:26:55 +0900 Subject: [PATCH 12/31] =?UTF-8?q?feat:=20fetchRelationshipStatus=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/axios/follow/follow.ts | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/shared/axios/follow/follow.ts b/src/shared/axios/follow/follow.ts index 4d3b473..294d182 100644 --- a/src/shared/axios/follow/follow.ts +++ b/src/shared/axios/follow/follow.ts @@ -1,5 +1,3 @@ -import { FetchRelationshipStatus } from '@/shared/consts'; - import { axiosInstance } from '../config/instance'; /** @@ -7,9 +5,7 @@ import { axiosInstance } from '../config/instance'; * @param userId 유저 아이디 * @returns 관계 상태 */ -export async function requestFollow( - userId: number, -): Promise { +export async function requestFollow(userId: number) { const { data } = await axiosInstance.post(`/users/${userId}/follow`); return data; @@ -20,10 +16,19 @@ export async function requestFollow( * @param userId 유저 아이디 * @returns 관계 상태 */ -export async function requestUnfollow( - userId: number, -): Promise { +export async function requestUnfollow(userId: number) { const { data } = await axiosInstance.delete(`/users/${userId}/follow`); return data; } + +/** + * 팔로우 확인 API + * @param userId 유저 아이디 + * @returns 관계 상태 + */ +export async function fetchRelationshipStatus(userId: number) { + const { data } = await axiosInstance.get(`/users/${userId}/follow`); + + return data; +} From b346fd1f0825acb041611e431d52aa89e76d1c8e Mon Sep 17 00:00:00 2001 From: Uichan Lee Date: Wed, 26 Jun 2024 15:50:02 +0900 Subject: [PATCH 13/31] =?UTF-8?q?feat:=20useGetRelationshipStatus=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/widgets/profile-user/api/index.ts | 1 + .../api/useGetRelationshipStatus.ts | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 src/widgets/profile-user/api/useGetRelationshipStatus.ts diff --git a/src/widgets/profile-user/api/index.ts b/src/widgets/profile-user/api/index.ts index d184899..b66b09c 100644 --- a/src/widgets/profile-user/api/index.ts +++ b/src/widgets/profile-user/api/index.ts @@ -1 +1,2 @@ export { useGetUser } from './useGetUser'; +export { useGetRelationshipStatus } from './useGetRelationshipStatus'; diff --git a/src/widgets/profile-user/api/useGetRelationshipStatus.ts b/src/widgets/profile-user/api/useGetRelationshipStatus.ts new file mode 100644 index 0000000..aedc6f7 --- /dev/null +++ b/src/widgets/profile-user/api/useGetRelationshipStatus.ts @@ -0,0 +1,23 @@ +import { useQuery } from '@tanstack/react-query'; + +import { fetchRelationshipStatus } from '@/shared/axios'; +import { QUERY_KEYS } from '@/shared/react-query'; + +export const useGetRelationshipStatus = (userId: number) => { + const { + data: relationshipStatusData, + isLoading: relationshipLoading, + isError: relationshipError, + refetch: refetchRelationshipStatus, + } = useQuery({ + queryKey: [QUERY_KEYS.follow, userId], + queryFn: () => fetchRelationshipStatus(userId), + }); + + return { + relationshipStatusData, + relationshipLoading, + relationshipError, + refetchRelationshipStatus, + }; +}; From c514f3ad02c3d5534bac8618914370e9551d44a4 Mon Sep 17 00:00:00 2001 From: Uichan Lee Date: Wed, 26 Jun 2024 15:51:39 +0900 Subject: [PATCH 14/31] =?UTF-8?q?feat:=20ProfileFollowButton=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../profile-user/ui/ProfileFollowButton.scss | 25 +++++++++ .../profile-user/ui/ProfileFollowButton.tsx | 54 +++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 src/widgets/profile-user/ui/ProfileFollowButton.scss create mode 100644 src/widgets/profile-user/ui/ProfileFollowButton.tsx diff --git a/src/widgets/profile-user/ui/ProfileFollowButton.scss b/src/widgets/profile-user/ui/ProfileFollowButton.scss new file mode 100644 index 0000000..ff9b525 --- /dev/null +++ b/src/widgets/profile-user/ui/ProfileFollowButton.scss @@ -0,0 +1,25 @@ +@mixin profileFollowButton() { + width: 46px; + height: 26px; + border-radius: 5px; +} + +.profile-follow-btn { + @include profileFollowButton(); + color: white; + background-color: $mint3; + + &:disabled { + color: white; + } +} + +.profile-unfollow-btn { + @include profileFollowButton(); + color: $gray4; + background-color: $gray1; + + &:disabled { + color: $gray4; + } +} diff --git a/src/widgets/profile-user/ui/ProfileFollowButton.tsx b/src/widgets/profile-user/ui/ProfileFollowButton.tsx new file mode 100644 index 0000000..ecdb4a1 --- /dev/null +++ b/src/widgets/profile-user/ui/ProfileFollowButton.tsx @@ -0,0 +1,54 @@ +import './ProfileFollowButton.scss'; + +import { useFollow, useUnfollow } from '@/features/follow'; +import { RelationshipStatus } from '@/shared/consts'; + +interface ProfileFollowButtonProps { + relationshipStatus: RelationshipStatus; + userId: number; + locked: boolean; +} + +export const ProfileFollowButton = ({ + relationshipStatus, + userId, + locked, +}: ProfileFollowButtonProps) => { + const { handleFollow, isPendingFollow } = useFollow(userId, locked); + const { handleUnfollow, isPendingUnfollow } = useUnfollow(userId, locked); + + switch (relationshipStatus) { + case 'none': + return ( + + ); + case 'pending': + return ( + + ); + case 'following': + return ( + + ); + default: + return null; + } +}; From 3f885187466d533bd36bf1cf6f8242a777249c47 Mon Sep 17 00:00:00 2001 From: Uichan Lee Date: Wed, 26 Jun 2024 15:55:53 +0900 Subject: [PATCH 15/31] =?UTF-8?q?feat:=20SkeletonProfileButton=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/widgets/profile-user/ui/SkeletonProfileButton.scss | 7 +++++++ src/widgets/profile-user/ui/SkeletonProfileButton.tsx | 3 +++ 2 files changed, 10 insertions(+) create mode 100644 src/widgets/profile-user/ui/SkeletonProfileButton.scss create mode 100644 src/widgets/profile-user/ui/SkeletonProfileButton.tsx diff --git a/src/widgets/profile-user/ui/SkeletonProfileButton.scss b/src/widgets/profile-user/ui/SkeletonProfileButton.scss new file mode 100644 index 0000000..f2db3f7 --- /dev/null +++ b/src/widgets/profile-user/ui/SkeletonProfileButton.scss @@ -0,0 +1,7 @@ +.skeleton-profile-btn { + @include skeletonAnimation(); + background-color: $gray2; + width: 68px; + height: 26px; + border-radius: 6px; +} diff --git a/src/widgets/profile-user/ui/SkeletonProfileButton.tsx b/src/widgets/profile-user/ui/SkeletonProfileButton.tsx new file mode 100644 index 0000000..76faf76 --- /dev/null +++ b/src/widgets/profile-user/ui/SkeletonProfileButton.tsx @@ -0,0 +1,3 @@ +export const SkeletonProfileButton = () => { + return
; +}; From 8177a6ef3ad6be0244d29de4d8bfd417845a0aa1 Mon Sep 17 00:00:00 2001 From: Uichan Lee Date: Wed, 26 Jun 2024 15:56:38 +0900 Subject: [PATCH 16/31] =?UTF-8?q?feat:=20SkeletonProfileUser=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/widgets/profile-user/ui/SkeletonProfileUser.scss | 10 +--------- src/widgets/profile-user/ui/SkeletonProfileUser.tsx | 5 +++-- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/widgets/profile-user/ui/SkeletonProfileUser.scss b/src/widgets/profile-user/ui/SkeletonProfileUser.scss index d122c21..8d35b0e 100644 --- a/src/widgets/profile-user/ui/SkeletonProfileUser.scss +++ b/src/widgets/profile-user/ui/SkeletonProfileUser.scss @@ -13,7 +13,7 @@ justify-content: space-between; align-items: center; - .skeleton-proile { + .skeleton-profile { @include skeletonAnimation(); background-color: $gray2; position: relative; @@ -30,14 +30,6 @@ height: 14px; border-radius: 4px; } - - .skeleton-profile-btn { - @include skeletonAnimation(); - background-color: $gray2; - width: 68px; - height: 26px; - border-radius: 6px; - } } .skeleton-profile-count-container { diff --git a/src/widgets/profile-user/ui/SkeletonProfileUser.tsx b/src/widgets/profile-user/ui/SkeletonProfileUser.tsx index 4593c71..e80d0ad 100644 --- a/src/widgets/profile-user/ui/SkeletonProfileUser.tsx +++ b/src/widgets/profile-user/ui/SkeletonProfileUser.tsx @@ -1,12 +1,13 @@ +import { SkeletonProfileButton } from './SkeletonProfileButton'; import './SkeletonProfileUser.scss'; export const SkeletonProfileUser = () => { return (
-
+
-
+
From b556f3c3b49b1f0b1967d789fd50c6c75940f637 Mon Sep 17 00:00:00 2001 From: Uichan Lee Date: Wed, 26 Jun 2024 15:59:47 +0900 Subject: [PATCH 17/31] =?UTF-8?q?feat:=20ProfileUser=EC=97=90=20ProfileFol?= =?UTF-8?q?lowButton=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/widgets/profile-user/ui/ProfileUser.tsx | 26 ++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/widgets/profile-user/ui/ProfileUser.tsx b/src/widgets/profile-user/ui/ProfileUser.tsx index bc9765f..69ca39f 100644 --- a/src/widgets/profile-user/ui/ProfileUser.tsx +++ b/src/widgets/profile-user/ui/ProfileUser.tsx @@ -1,10 +1,12 @@ import { NetworkError, PageHeader } from '@/shared/ui'; -import { useGetUser } from '../api'; +import { useGetUser, useGetRelationshipStatus } from '../api'; import { ProfileCount } from './ProfileCount'; import './ProfileUser.scss'; +import { ProfileFollowButton } from './ProfileFollowButton'; import { ProfileUserImage } from './ProfileUserImage'; +import { SkeletonProfileButton } from './SkeletonProfileButton'; import { SkeletonProfileUser } from './SkeletonProfileUser'; interface ProfileUserProps { @@ -14,6 +16,12 @@ interface ProfileUserProps { export const ProfileUser = ({ userId, isOwner }: ProfileUserProps) => { const { data, isLoading, isError, refetchUser } = useGetUser(userId); + const { + relationshipStatusData, + relationshipLoading, + relationshipError, + refetchRelationshipStatus, + } = useGetRelationshipStatus(userId); if (isLoading) { return ( @@ -27,16 +35,22 @@ export const ProfileUser = ({ userId, isOwner }: ProfileUserProps) => { if (isError || !data) { return ; } + if (relationshipError) { + return ; + } const { profileImage, name, username, + locked, feedCount, followerCount, followingCount, } = data.data.user; + const relationshipStatus = relationshipStatusData?.data.relationshipStatus; + return ( <> @@ -48,10 +62,16 @@ export const ProfileUser = ({ userId, isOwner }: ProfileUserProps) => { isOwner={isOwner} />

{username}

- {isOwner ? ( + {relationshipLoading ? ( + + ) : relationshipStatus === 'self' ? ( ) : ( - + )}
From 71fcd17cd0d875272d28882b1a3d424ea6a493ed Mon Sep 17 00:00:00 2001 From: Uichan Lee Date: Wed, 26 Jun 2024 16:00:58 +0900 Subject: [PATCH 18/31] =?UTF-8?q?feat:=20fetchUser=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/axios/index.ts | 1 + src/shared/axios/user/index.ts | 1 + src/shared/axios/user/user.ts | 14 ++++++++++++++ src/widgets/profile-user/api/useGetUser.ts | 8 +------- 4 files changed, 17 insertions(+), 7 deletions(-) create mode 100644 src/shared/axios/user/index.ts create mode 100644 src/shared/axios/user/user.ts diff --git a/src/shared/axios/index.ts b/src/shared/axios/index.ts index 6ccdc3b..b3b7102 100644 --- a/src/shared/axios/index.ts +++ b/src/shared/axios/index.ts @@ -2,3 +2,4 @@ export { axiosInstance } from './config'; export * from './like'; export * from './bookmark'; export * from './follow'; +export * from './user'; diff --git a/src/shared/axios/user/index.ts b/src/shared/axios/user/index.ts new file mode 100644 index 0000000..e5abc85 --- /dev/null +++ b/src/shared/axios/user/index.ts @@ -0,0 +1 @@ +export * from './user'; diff --git a/src/shared/axios/user/user.ts b/src/shared/axios/user/user.ts new file mode 100644 index 0000000..c465546 --- /dev/null +++ b/src/shared/axios/user/user.ts @@ -0,0 +1,14 @@ +import { FetchUser } from '@/shared/consts'; + +import { axiosInstance } from '../config/instance'; + +/** + * 유저 정보 API + * @param userId 유저 아이디 + * @returns 유저 정보 + */ +export async function fetchUser(userId: number): Promise { + const { data } = await axiosInstance.get(`/users/${userId}`); + + return data; +} diff --git a/src/widgets/profile-user/api/useGetUser.ts b/src/widgets/profile-user/api/useGetUser.ts index 825e1ef..1d85992 100644 --- a/src/widgets/profile-user/api/useGetUser.ts +++ b/src/widgets/profile-user/api/useGetUser.ts @@ -1,14 +1,8 @@ import { useQuery } from '@tanstack/react-query'; -import { axiosInstance } from '@/shared/axios'; -import { FetchUser } from '@/shared/consts'; +import { fetchUser } from '@/shared/axios'; import { QUERY_KEYS } from '@/shared/react-query'; -async function fetchUser(userId: number): Promise { - const { data } = await axiosInstance.get(`/users/${userId}`); - return data; -} - export const useGetUser = (userId: number) => { const { data, From ee43ded58fef9064ceb3ea313f485ca2648ce189 Mon Sep 17 00:00:00 2001 From: Uichan Lee Date: Wed, 26 Jun 2024 16:01:29 +0900 Subject: [PATCH 19/31] =?UTF-8?q?feat:=20IPhoneLayout=20defaultSize=20?= =?UTF-8?q?=ED=99=95=EB=8C=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/layout/RootLayout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/layout/RootLayout.tsx b/src/app/layout/RootLayout.tsx index 8490b3d..088f71f 100644 --- a/src/app/layout/RootLayout.tsx +++ b/src/app/layout/RootLayout.tsx @@ -11,7 +11,7 @@ import './RootLayout.scss'; */ export const RootLayout = () => { return ( - +
From 8ca244f09076dbe1eff165039a7bcd31e496656b Mon Sep 17 00:00:00 2001 From: Legitgoons Date: Wed, 26 Jun 2024 22:59:14 +0900 Subject: [PATCH 20/31] =?UTF-8?q?fix:=20useFollow,=20useUnfollow=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Querykey로 userId 추가, getQueryData 인터페이스 수정 --- src/features/follow/api/useFollow.tsx | 28 ++++++++++++++--------- src/features/follow/api/useUnfollow.tsx | 30 ++++++++++++++++--------- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/src/features/follow/api/useFollow.tsx b/src/features/follow/api/useFollow.tsx index 5482d17..82f67dc 100644 --- a/src/features/follow/api/useFollow.tsx +++ b/src/features/follow/api/useFollow.tsx @@ -1,7 +1,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; import { requestFollow } from '@/shared/axios'; -import { RelationshipStatus } from '@/shared/consts'; +import { FetchRelationshipStatus } from '@/shared/consts'; import { QUERY_KEYS } from '@/shared/react-query'; import { isErrorResponse } from '@/shared/utils'; @@ -19,41 +19,47 @@ export const useFollow = (userId: number, locked: boolean) => { // mutate가 호출되면 ✨낙관적 업데이트를 위해 onMutate를 실행 onMutate: async () => { // 진행중인 refetch가 있다면 취소 - await queryClient.cancelQueries({ queryKey: [QUERY_KEYS.follow] }); + await queryClient.cancelQueries({ + queryKey: [QUERY_KEYS.follow, userId], + }); // 이전 쿼리값의 스냅샷 - const previousQueryData = queryClient.getQueryData([ - QUERY_KEYS.follow, - ]); + const previousQueryData = + queryClient.getQueryData([ + QUERY_KEYS.follow, + userId, + ]); if (!previousQueryData) return; // 업데이트 될 쿼리값 const updatedQueryData = updateRelationshipStatus( - previousQueryData as RelationshipStatus, + previousQueryData, locked, ); - // setQueryData 함수를 사용해 Optimistic Update를 실시한다. - await queryClient.setQueryData([QUERY_KEYS.follow], updatedQueryData); + queryClient.setQueryData([QUERY_KEYS.follow, userId], updatedQueryData); return { previousQueryData }; }, onError: (_, __, context) => { // Network Error일 경우 이전 쿼리값으로 롤백 - queryClient.setQueryData([QUERY_KEYS.follow], context?.previousQueryData); + queryClient.setQueryData( + [QUERY_KEYS.follow, userId], + context?.previousQueryData, + ); }, onSuccess: (response, _, context) => { if (isErrorResponse(response)) { // Server Error일 경우 이전 쿼리값으로 롤백 queryClient.setQueryData( - [QUERY_KEYS.follow], + [QUERY_KEYS.follow, userId], context.previousQueryData, ); } }, onSettled: () => { - queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.follow] }); + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.follow, userId] }); }, }); diff --git a/src/features/follow/api/useUnfollow.tsx b/src/features/follow/api/useUnfollow.tsx index cd07a1e..83a4aa6 100644 --- a/src/features/follow/api/useUnfollow.tsx +++ b/src/features/follow/api/useUnfollow.tsx @@ -1,7 +1,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; import { requestUnfollow } from '@/shared/axios'; -import { RelationshipStatus } from '@/shared/consts'; +import { FetchRelationshipStatus } from '@/shared/consts'; import { QUERY_KEYS } from '@/shared/react-query'; import { isErrorResponse } from '@/shared/utils'; @@ -19,41 +19,51 @@ export const useUnfollow = (userId: number, locked: boolean) => { // mutate가 호출되면 ✨낙관적 업데이트를 위해 onMutate를 실행 onMutate: async () => { // 진행중인 refetch가 있다면 취소 - await queryClient.cancelQueries({ queryKey: [QUERY_KEYS.follow] }); + await queryClient.cancelQueries({ + queryKey: [QUERY_KEYS.follow, userId], + }); // 이전 쿼리값의 스냅샷 - const previousQueryData = queryClient.getQueryData([ - QUERY_KEYS.follow, - ]); + const previousQueryData = + queryClient.getQueryData([ + QUERY_KEYS.follow, + userId, + ]); if (!previousQueryData) return; // 업데이트 될 쿼리값 const updatedQueryData = updateRelationshipStatus( - previousQueryData as RelationshipStatus, + previousQueryData, locked, ); // setQueryData 함수를 사용해 Optimistic Update를 실시한다. - await queryClient.setQueryData([QUERY_KEYS.follow], updatedQueryData); + await queryClient.setQueryData( + [QUERY_KEYS.follow, userId], + updatedQueryData, + ); return { previousQueryData }; }, onError: (_, __, context) => { // Network Error일 경우 이전 쿼리값으로 롤백 - queryClient.setQueryData([QUERY_KEYS.follow], context?.previousQueryData); + queryClient.setQueryData( + [QUERY_KEYS.follow, userId], + context?.previousQueryData, + ); }, onSuccess: (response, _, context) => { if (isErrorResponse(response)) { // Server Error일 경우 이전 쿼리값으로 롤백 queryClient.setQueryData( - [QUERY_KEYS.follow], + [QUERY_KEYS.follow, userId], context.previousQueryData, ); } }, onSettled: () => { - queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.follow] }); + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.follow, userId] }); }, }); From d87ab55e664bd39292c611c5b1f9a6ba0968af49 Mon Sep 17 00:00:00 2001 From: Legitgoons Date: Wed, 26 Jun 2024 23:00:06 +0900 Subject: [PATCH 21/31] =?UTF-8?q?fix:=20updateRelationshipStatus=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updateRelationshipStatus가 올바른 data 형식을 return 하도록 수정했습니다. --- .../follow/lib/updateRelationshipStatus.ts | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/features/follow/lib/updateRelationshipStatus.ts b/src/features/follow/lib/updateRelationshipStatus.ts index 286691b..668914f 100644 --- a/src/features/follow/lib/updateRelationshipStatus.ts +++ b/src/features/follow/lib/updateRelationshipStatus.ts @@ -1,10 +1,10 @@ -import { RelationshipStatus } from '@/shared/consts'; +import { RelationshipStatus, FetchRelationshipStatus } from '@/shared/consts'; -export function updateRelationshipStatus( - previousRelationshipStatus: RelationshipStatus, +const RelationshipStatusCalculate = ( + relationshipStatus: RelationshipStatus, locked: boolean, -) { - switch (previousRelationshipStatus) { +) => { + switch (relationshipStatus) { case 'self': return 'self'; case 'following': @@ -14,6 +14,21 @@ export function updateRelationshipStatus( case 'pending': return 'none'; default: - return previousRelationshipStatus; + return relationshipStatus; } +}; + +export function updateRelationshipStatus( + previousRelationshipStatusData: FetchRelationshipStatus, + locked: boolean, +) { + return { + code: previousRelationshipStatusData.code, + data: { + relationshipStatus: RelationshipStatusCalculate( + previousRelationshipStatusData.data.relationshipStatus, + locked, + ), + }, + }; } From 7442ba2d0865705a829632e44b9f91e3a874efe2 Mon Sep 17 00:00:00 2001 From: Legitgoons Date: Wed, 26 Jun 2024 23:42:57 +0900 Subject: [PATCH 22/31] =?UTF-8?q?feat:=20useFollow,=20useUnfollow=20mutati?= =?UTF-8?q?onKey=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/follow/api/useFollow.tsx | 1 + src/features/follow/api/useUnfollow.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/src/features/follow/api/useFollow.tsx b/src/features/follow/api/useFollow.tsx index 82f67dc..2de5c68 100644 --- a/src/features/follow/api/useFollow.tsx +++ b/src/features/follow/api/useFollow.tsx @@ -15,6 +15,7 @@ export const useFollow = (userId: number, locked: boolean) => { mutate: handleFollow, isPending: isPendingFollow, } = useMutation({ + mutationKey: [QUERY_KEYS.follow], mutationFn: () => requestFollow(userId), // mutate가 호출되면 ✨낙관적 업데이트를 위해 onMutate를 실행 onMutate: async () => { diff --git a/src/features/follow/api/useUnfollow.tsx b/src/features/follow/api/useUnfollow.tsx index 83a4aa6..9c67beb 100644 --- a/src/features/follow/api/useUnfollow.tsx +++ b/src/features/follow/api/useUnfollow.tsx @@ -15,6 +15,7 @@ export const useUnfollow = (userId: number, locked: boolean) => { mutate: handleUnfollow, isPending: isPendingUnfollow, } = useMutation({ + mutationKey: [QUERY_KEYS.follow], mutationFn: () => requestUnfollow(userId), // mutate가 호출되면 ✨낙관적 업데이트를 위해 onMutate를 실행 onMutate: async () => { From ae66f541e2d7c069326363a621eaebf789cc3bc9 Mon Sep 17 00:00:00 2001 From: Legitgoons Date: Wed, 26 Jun 2024 23:43:49 +0900 Subject: [PATCH 23/31] =?UTF-8?q?feat:=20handleMutationError=EC=97=90=20mu?= =?UTF-8?q?tationKeys=20=EB=B0=B0=EC=97=B4=20=EC=83=9D=EC=84=B1,=20follow?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/react-query/dir/handleQuery.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/shared/react-query/dir/handleQuery.ts b/src/shared/react-query/dir/handleQuery.ts index f4b021c..a54afba 100644 --- a/src/shared/react-query/dir/handleQuery.ts +++ b/src/shared/react-query/dir/handleQuery.ts @@ -40,7 +40,8 @@ export function handleMutationError( mutation: Mutation, ) { const { options } = mutation; + const mutationKeys = ['feed-report', 'follow']; - if (options.mutationKey?.includes('feed-report')) + if (mutationKeys.some((key) => options.mutationKey?.includes(key))) showToastHandler('caution', '다시 시도해 주세요'); } From 3f135fd9775412f35165a0db5c056daa08db4a55 Mon Sep 17 00:00:00 2001 From: Legitgoons Date: Sat, 29 Jun 2024 22:45:29 +0900 Subject: [PATCH 24/31] =?UTF-8?q?fix:=20relationshipStatus=20mock=20data?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/mocks/consts/relationshipStatus.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/mocks/consts/relationshipStatus.ts b/src/app/mocks/consts/relationshipStatus.ts index 0a0751c..58f1788 100644 --- a/src/app/mocks/consts/relationshipStatus.ts +++ b/src/app/mocks/consts/relationshipStatus.ts @@ -11,7 +11,7 @@ export const relationshipStatus: Relationship = { 3: 'pending', 4: 'none', 5: 'following', - 6: 'pending', + 6: 'none', 7: 'following', 8: 'none', 9: 'none', From 86af6a0c8bd665f889fdb1fa14d6eb82b705a4c9 Mon Sep 17 00:00:00 2001 From: Legitgoons Date: Sat, 29 Jun 2024 22:46:09 +0900 Subject: [PATCH 25/31] =?UTF-8?q?feat:=20ProfileFollowButton=20switch?= =?UTF-8?q?=EB=AC=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/widgets/profile-user/ui/ProfileFollowButton.tsx | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/widgets/profile-user/ui/ProfileFollowButton.tsx b/src/widgets/profile-user/ui/ProfileFollowButton.tsx index ecdb4a1..6ee5707 100644 --- a/src/widgets/profile-user/ui/ProfileFollowButton.tsx +++ b/src/widgets/profile-user/ui/ProfileFollowButton.tsx @@ -29,15 +29,6 @@ export const ProfileFollowButton = ({ ); case 'pending': - return ( - - ); case 'following': return ( ); default: From aceb30b093126858310e6aca3853329648e3a993 Mon Sep 17 00:00:00 2001 From: Legitgoons Date: Mon, 1 Jul 2024 00:27:35 +0900 Subject: [PATCH 26/31] =?UTF-8?q?feat:=20followHandler=EC=97=90=20follower?= =?UTF-8?q?Count/folloingCount=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/mocks/handler/follow.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/app/mocks/handler/follow.ts b/src/app/mocks/handler/follow.ts index 55b8d2e..3be4935 100644 --- a/src/app/mocks/handler/follow.ts +++ b/src/app/mocks/handler/follow.ts @@ -28,6 +28,8 @@ export const followHandler = [ relationshipStatus[formattedUserId] = 'pending'; return createHttpSuccessResponse({ relationshipStatus: 'pending' }); } else { + users[formattedUserId].followerCount += 1; + users[1].followingCount += 1; relationshipStatus[formattedUserId] = 'following'; return createHttpSuccessResponse({ relationshipStatus: 'following' }); } @@ -57,6 +59,8 @@ export const followHandler = [ return createHttpErrorResponse('4220'); case 'following': relationshipStatus[formattedUserId] = 'none'; + users[formattedUserId].followerCount -= 1; + users[1].followingCount -= 1; return createHttpSuccessResponse({ relationshipStatus: 'none' }); case 'pending': relationshipStatus[formattedUserId] = 'none'; From 3e0c705e648ea741f0bfa78bd9e4fe570673ff87 Mon Sep 17 00:00:00 2001 From: Legitgoons Date: Mon, 1 Jul 2024 00:29:40 +0900 Subject: [PATCH 27/31] =?UTF-8?q?feat:=20useFollow,=20useUnfollow=EC=97=90?= =?UTF-8?q?=EC=84=9C=20user=20=EA=B0=80=EC=A0=B8=EC=98=A4=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/follow/api/useFollow.tsx | 19 ++++++++++--------- src/features/follow/api/useUnfollow.tsx | 13 +++++-------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/features/follow/api/useFollow.tsx b/src/features/follow/api/useFollow.tsx index 2de5c68..8fe8ef2 100644 --- a/src/features/follow/api/useFollow.tsx +++ b/src/features/follow/api/useFollow.tsx @@ -5,7 +5,7 @@ import { FetchRelationshipStatus } from '@/shared/consts'; import { QUERY_KEYS } from '@/shared/react-query'; import { isErrorResponse } from '@/shared/utils'; -import { updateRelationshipStatus } from '../lib/updateRelationshipStatus'; +import { updateRelationshipStatus } from '../lib'; export const useFollow = (userId: number, locked: boolean) => { const queryClient = useQueryClient(); @@ -17,14 +17,11 @@ export const useFollow = (userId: number, locked: boolean) => { } = useMutation({ mutationKey: [QUERY_KEYS.follow], mutationFn: () => requestFollow(userId), - // mutate가 호출되면 ✨낙관적 업데이트를 위해 onMutate를 실행 onMutate: async () => { - // 진행중인 refetch가 있다면 취소 await queryClient.cancelQueries({ queryKey: [QUERY_KEYS.follow, userId], }); - // 이전 쿼리값의 스냅샷 const previousQueryData = queryClient.getQueryData([ QUERY_KEYS.follow, @@ -33,18 +30,19 @@ export const useFollow = (userId: number, locked: boolean) => { if (!previousQueryData) return; - // 업데이트 될 쿼리값 const updatedQueryData = updateRelationshipStatus( previousQueryData, locked, ); - // setQueryData 함수를 사용해 Optimistic Update를 실시한다. - queryClient.setQueryData([QUERY_KEYS.follow, userId], updatedQueryData); + + await queryClient.setQueryData( + [QUERY_KEYS.follow, userId], + updatedQueryData, + ); return { previousQueryData }; }, onError: (_, __, context) => { - // Network Error일 경우 이전 쿼리값으로 롤백 queryClient.setQueryData( [QUERY_KEYS.follow, userId], context?.previousQueryData, @@ -52,7 +50,6 @@ export const useFollow = (userId: number, locked: boolean) => { }, onSuccess: (response, _, context) => { if (isErrorResponse(response)) { - // Server Error일 경우 이전 쿼리값으로 롤백 queryClient.setQueryData( [QUERY_KEYS.follow, userId], context.previousQueryData, @@ -61,6 +58,10 @@ export const useFollow = (userId: number, locked: boolean) => { }, onSettled: () => { queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.follow, userId] }); + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.users, userId], + }); + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.users, 1] }); }, }); diff --git a/src/features/follow/api/useUnfollow.tsx b/src/features/follow/api/useUnfollow.tsx index 9c67beb..e53c1c3 100644 --- a/src/features/follow/api/useUnfollow.tsx +++ b/src/features/follow/api/useUnfollow.tsx @@ -5,7 +5,7 @@ import { FetchRelationshipStatus } from '@/shared/consts'; import { QUERY_KEYS } from '@/shared/react-query'; import { isErrorResponse } from '@/shared/utils'; -import { updateRelationshipStatus } from '../lib/updateRelationshipStatus'; +import { updateRelationshipStatus } from '../lib'; export const useUnfollow = (userId: number, locked: boolean) => { const queryClient = useQueryClient(); @@ -17,14 +17,11 @@ export const useUnfollow = (userId: number, locked: boolean) => { } = useMutation({ mutationKey: [QUERY_KEYS.follow], mutationFn: () => requestUnfollow(userId), - // mutate가 호출되면 ✨낙관적 업데이트를 위해 onMutate를 실행 onMutate: async () => { - // 진행중인 refetch가 있다면 취소 await queryClient.cancelQueries({ queryKey: [QUERY_KEYS.follow, userId], }); - // 이전 쿼리값의 스냅샷 const previousQueryData = queryClient.getQueryData([ QUERY_KEYS.follow, @@ -33,13 +30,11 @@ export const useUnfollow = (userId: number, locked: boolean) => { if (!previousQueryData) return; - // 업데이트 될 쿼리값 const updatedQueryData = updateRelationshipStatus( previousQueryData, locked, ); - // setQueryData 함수를 사용해 Optimistic Update를 실시한다. await queryClient.setQueryData( [QUERY_KEYS.follow, userId], updatedQueryData, @@ -48,7 +43,6 @@ export const useUnfollow = (userId: number, locked: boolean) => { return { previousQueryData }; }, onError: (_, __, context) => { - // Network Error일 경우 이전 쿼리값으로 롤백 queryClient.setQueryData( [QUERY_KEYS.follow, userId], context?.previousQueryData, @@ -56,7 +50,6 @@ export const useUnfollow = (userId: number, locked: boolean) => { }, onSuccess: (response, _, context) => { if (isErrorResponse(response)) { - // Server Error일 경우 이전 쿼리값으로 롤백 queryClient.setQueryData( [QUERY_KEYS.follow, userId], context.previousQueryData, @@ -65,6 +58,10 @@ export const useUnfollow = (userId: number, locked: boolean) => { }, onSettled: () => { queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.follow, userId] }); + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.users, userId], + }); + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.users, 1] }); }, }); From 50c93cebed357c9792592624ac20b9a080c60acb Mon Sep 17 00:00:00 2001 From: Legitgoons Date: Mon, 1 Jul 2024 00:34:45 +0900 Subject: [PATCH 28/31] =?UTF-8?q?feat:=20mutitonKeys=20->=20mutationCautio?= =?UTF-8?q?nToastKeys=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/react-query/dir/handleQuery.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/shared/react-query/dir/handleQuery.ts b/src/shared/react-query/dir/handleQuery.ts index a54afba..71f46de 100644 --- a/src/shared/react-query/dir/handleQuery.ts +++ b/src/shared/react-query/dir/handleQuery.ts @@ -40,8 +40,10 @@ export function handleMutationError( mutation: Mutation, ) { const { options } = mutation; - const mutationKeys = ['feed-report', 'follow']; + const mutationCautionToastKeys = ['feed-report', 'follow']; - if (mutationKeys.some((key) => options.mutationKey?.includes(key))) + if ( + mutationCautionToastKeys.some((key) => options.mutationKey?.includes(key)) + ) showToastHandler('caution', '다시 시도해 주세요'); } From 58100933205987a5081918e32816f97fa09f221c Mon Sep 17 00:00:00 2001 From: Uichan Lee Date: Tue, 2 Jul 2024 14:25:34 +0900 Subject: [PATCH 29/31] =?UTF-8?q?feat:=20useFollow=EC=99=80=20useUnfollow?= =?UTF-8?q?=20hook=20=EB=B3=91=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/follow/api/index.ts | 1 - src/features/follow/api/useFollow.tsx | 11 ++- src/features/follow/api/useUnfollow.tsx | 69 ------------------- src/features/follow/index.ts | 2 +- .../follow/test/useFollow.unit.test.tsx | 44 +++++++++++- .../follow/test/useUnfollow.unit.test.tsx | 46 ------------- 6 files changed, 51 insertions(+), 122 deletions(-) delete mode 100644 src/features/follow/api/useUnfollow.tsx delete mode 100644 src/features/follow/test/useUnfollow.unit.test.tsx diff --git a/src/features/follow/api/index.ts b/src/features/follow/api/index.ts index e02ebc0..dc4600b 100644 --- a/src/features/follow/api/index.ts +++ b/src/features/follow/api/index.ts @@ -1,2 +1 @@ export { useFollow } from './useFollow'; -export { useUnfollow } from './useUnfollow'; diff --git a/src/features/follow/api/useFollow.tsx b/src/features/follow/api/useFollow.tsx index 8fe8ef2..0924a74 100644 --- a/src/features/follow/api/useFollow.tsx +++ b/src/features/follow/api/useFollow.tsx @@ -1,13 +1,17 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { requestFollow } from '@/shared/axios'; +import { requestFollow, requestUnfollow } from '@/shared/axios'; import { FetchRelationshipStatus } from '@/shared/consts'; import { QUERY_KEYS } from '@/shared/react-query'; import { isErrorResponse } from '@/shared/utils'; import { updateRelationshipStatus } from '../lib'; -export const useFollow = (userId: number, locked: boolean) => { +export const useFollow = ( + userId: number, + locked: boolean, + isFollow: boolean, +) => { const queryClient = useQueryClient(); const { @@ -16,7 +20,8 @@ export const useFollow = (userId: number, locked: boolean) => { isPending: isPendingFollow, } = useMutation({ mutationKey: [QUERY_KEYS.follow], - mutationFn: () => requestFollow(userId), + mutationFn: () => + isFollow ? requestFollow(userId) : requestUnfollow(userId), onMutate: async () => { await queryClient.cancelQueries({ queryKey: [QUERY_KEYS.follow, userId], diff --git a/src/features/follow/api/useUnfollow.tsx b/src/features/follow/api/useUnfollow.tsx deleted file mode 100644 index e53c1c3..0000000 --- a/src/features/follow/api/useUnfollow.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { useMutation, useQueryClient } from '@tanstack/react-query'; - -import { requestUnfollow } from '@/shared/axios'; -import { FetchRelationshipStatus } from '@/shared/consts'; -import { QUERY_KEYS } from '@/shared/react-query'; -import { isErrorResponse } from '@/shared/utils'; - -import { updateRelationshipStatus } from '../lib'; - -export const useUnfollow = (userId: number, locked: boolean) => { - const queryClient = useQueryClient(); - - const { - data, - mutate: handleUnfollow, - isPending: isPendingUnfollow, - } = useMutation({ - mutationKey: [QUERY_KEYS.follow], - mutationFn: () => requestUnfollow(userId), - onMutate: async () => { - await queryClient.cancelQueries({ - queryKey: [QUERY_KEYS.follow, userId], - }); - - const previousQueryData = - queryClient.getQueryData([ - QUERY_KEYS.follow, - userId, - ]); - - if (!previousQueryData) return; - - const updatedQueryData = updateRelationshipStatus( - previousQueryData, - locked, - ); - - await queryClient.setQueryData( - [QUERY_KEYS.follow, userId], - updatedQueryData, - ); - - return { previousQueryData }; - }, - onError: (_, __, context) => { - queryClient.setQueryData( - [QUERY_KEYS.follow, userId], - context?.previousQueryData, - ); - }, - onSuccess: (response, _, context) => { - if (isErrorResponse(response)) { - queryClient.setQueryData( - [QUERY_KEYS.follow, userId], - context.previousQueryData, - ); - } - }, - onSettled: () => { - queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.follow, userId] }); - queryClient.invalidateQueries({ - queryKey: [QUERY_KEYS.users, userId], - }); - queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.users, 1] }); - }, - }); - - return { data, handleUnfollow, isPendingUnfollow }; -}; diff --git a/src/features/follow/index.ts b/src/features/follow/index.ts index b1c13e7..ff55e9e 100644 --- a/src/features/follow/index.ts +++ b/src/features/follow/index.ts @@ -1 +1 @@ -export * from './api'; +export { useFollow } from './api'; diff --git a/src/features/follow/test/useFollow.unit.test.tsx b/src/features/follow/test/useFollow.unit.test.tsx index c484caf..07f9bca 100644 --- a/src/features/follow/test/useFollow.unit.test.tsx +++ b/src/features/follow/test/useFollow.unit.test.tsx @@ -9,7 +9,7 @@ describe('Follow 기능 테스트', () => { describe('none 상태일 때', () => { it('공개 계정이라면 following 상태로 변경된다.', async () => { //given - const { result } = renderHook(() => useFollow(9, false), { + const { result } = renderHook(() => useFollow(9, false, true), { wrapper: createQueryClientWrapper(), }); @@ -27,7 +27,7 @@ describe('Follow 기능 테스트', () => { }); it('비공개 계정이라면 pending 상태로 변경된다.', async () => { - const { result } = renderHook(() => useFollow(4, true), { + const { result } = renderHook(() => useFollow(4, true, true), { wrapper: createQueryClientWrapper(), }); @@ -43,3 +43,43 @@ describe('Follow 기능 테스트', () => { }); }); }); + +describe('Unfollow 기능 테스트', () => { + it('following 상태일 때, none 상태로 변경된다.', async () => { + // given + const { result } = renderHook(() => useFollow(2, false, false), { + wrapper: createQueryClientWrapper(), + }); + + // when + act(() => result.current.handleFollow()); + + // then + await waitFor(() => { + const { + data: { relationshipStatus: initialStatus }, + } = result.current.data; + + expect(initialStatus).toBe('none'); + }); + }); + + it('pending 상태일 때, none 상태로 변경된다.', async () => { + // given + const { result } = renderHook(() => useFollow(3, true, false), { + wrapper: createQueryClientWrapper(), + }); + + //when + act(() => result.current.handleFollow()); + + //then + await waitFor(() => { + const { + data: { relationshipStatus: initialStatus }, + } = result.current.data; + + expect(initialStatus).toBe('none'); + }); + }); +}); diff --git a/src/features/follow/test/useUnfollow.unit.test.tsx b/src/features/follow/test/useUnfollow.unit.test.tsx deleted file mode 100644 index 6bc1ec4..0000000 --- a/src/features/follow/test/useUnfollow.unit.test.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { renderHook, act, waitFor } from '@testing-library/react'; -import { describe, expect, it } from 'vitest'; - -import { createQueryClientWrapper } from '@/shared/tests'; - -import { useUnfollow } from '../api'; - -describe('Unfollow 기능 테스트', () => { - it('following 상태일 때, none 상태로 변경된다.', async () => { - // given - const { result } = renderHook(() => useUnfollow(2, false), { - wrapper: createQueryClientWrapper(), - }); - - // when - act(() => result.current.handleUnfollow()); - - // then - await waitFor(() => { - const { - data: { relationshipStatus: initialStatus }, - } = result.current.data; - - expect(initialStatus).toBe('none'); - }); - }); - - it('pending 상태일 때, none 상태로 변경된다.', async () => { - // given - const { result } = renderHook(() => useUnfollow(3, true), { - wrapper: createQueryClientWrapper(), - }); - - //when - act(() => result.current.handleUnfollow()); - - //then - await waitFor(() => { - const { - data: { relationshipStatus: initialStatus }, - } = result.current.data; - - expect(initialStatus).toBe('none'); - }); - }); -}); From c4de780311a079ad47c69f9018f9c6823f5ddb64 Mon Sep 17 00:00:00 2001 From: Uichan Lee Date: Tue, 2 Jul 2024 14:27:19 +0900 Subject: [PATCH 30/31] =?UTF-8?q?feat:=20hook=20=EB=B3=91=ED=95=A9?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=9D=BC=20ProfileFollowButton=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/widgets/profile-user/ui/ProfileFollowButton.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/widgets/profile-user/ui/ProfileFollowButton.tsx b/src/widgets/profile-user/ui/ProfileFollowButton.tsx index 6ee5707..9dfbbb4 100644 --- a/src/widgets/profile-user/ui/ProfileFollowButton.tsx +++ b/src/widgets/profile-user/ui/ProfileFollowButton.tsx @@ -1,6 +1,6 @@ import './ProfileFollowButton.scss'; -import { useFollow, useUnfollow } from '@/features/follow'; +import { useFollow } from '@/features/follow'; import { RelationshipStatus } from '@/shared/consts'; interface ProfileFollowButtonProps { @@ -14,8 +14,8 @@ export const ProfileFollowButton = ({ userId, locked, }: ProfileFollowButtonProps) => { - const { handleFollow, isPendingFollow } = useFollow(userId, locked); - const { handleUnfollow, isPendingUnfollow } = useUnfollow(userId, locked); + const isfollow = relationshipStatus === 'none' ? true : false; + const { handleFollow, isPendingFollow } = useFollow(userId, locked, isfollow); switch (relationshipStatus) { case 'none': @@ -32,8 +32,8 @@ export const ProfileFollowButton = ({ case 'following': return (