From d2a354187d5b5ad6d843f5dca5938b8936c7bf3e Mon Sep 17 00:00:00 2001 From: a-honey Date: Wed, 25 Dec 2024 13:12:06 +0900 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20playwright=20=EC=9D=B8=EC=8B=9D?= =?UTF-8?q?=EC=9D=84=20=EC=9C=84=ED=95=B4=20href=20=EC=A7=81=EC=A0=91=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/load.spec.ts | 6 ++++-- src/api/fetchData.ts | 11 +++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/__tests__/load.spec.ts b/__tests__/load.spec.ts index 52184e5..a5c456c 100644 --- a/__tests__/load.spec.ts +++ b/__tests__/load.spec.ts @@ -54,14 +54,16 @@ test.describe("페이지 접근 리다이렉트 테스트", () => { localStorage.setItem("accessToken", "token"); }); + await page.goto("http://localhost:5173/"); + await page.route("https://linkedoutapp.com/api/admin-info/my", (route) => { route.fulfill({ status: 401, + contentType: "application/json", + body: JSON.stringify({ error: "Unauthorized" }), }); }); - await page.goto("http://localhost:5173/"); - await expect(page).toHaveURL("http://localhost:5173/auth/login"); }); }); diff --git a/src/api/fetchData.ts b/src/api/fetchData.ts index 936f1e9..ce98cb1 100644 --- a/src/api/fetchData.ts +++ b/src/api/fetchData.ts @@ -1,9 +1,16 @@ +import { AuthPaths } from "../router/paths"; + const apiUrl = "https://linkedoutapp.com/api"; +type ErrorBody = { + error: string; + message: string; +}; + type FetchDataProps = { url: string; method?: "GET" | "POST" | "PUT" | "DELETE"; - body?: RequestBodyType; + body?: RequestBodyType | ErrorBody; }; export default async function fetchData< @@ -44,7 +51,7 @@ export default async function fetchData< } if (!response.ok) { - throw new Error("API 요청에 실패했습니다."); + window.location.href = `/auth/${AuthPaths.LOGIN}`; } return response.json(); From 658c11fe09a1846eec1143ab2bf5c4e2ed597443 Mon Sep 17 00:00:00 2001 From: a-honey Date: Wed, 25 Dec 2024 13:15:33 +0900 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20loader=20=EB=8D=B0=EC=9D=B4=ED=84=B0?= =?UTF-8?q?=20=EC=9A=94=EC=B2=AD=20context=EC=97=90=EC=84=9C=20=EA=B0=80?= =?UTF-8?q?=EB=A1=9C=EC=B1=84=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __mocks__/permissionMock.ts | 17 +++++++++++++++++ __tests__/dashboard.spec.ts | 25 ++++++++----------------- 2 files changed, 25 insertions(+), 17 deletions(-) create mode 100644 __mocks__/permissionMock.ts diff --git a/__mocks__/permissionMock.ts b/__mocks__/permissionMock.ts new file mode 100644 index 0000000..cba5a36 --- /dev/null +++ b/__mocks__/permissionMock.ts @@ -0,0 +1,17 @@ +export const permissionAvailableMock = { + url: "https://linkedoutapp.com/api/admin-info/my", + apiResponse: { + status: 200, + contentType: "application/json", + body: JSON.stringify({ + data: { + id: 0, + email: "test@example.com", + name: "테스트", + profileImage: null, + activated: true, + info: null, + }, + }), + }, +}; diff --git a/__tests__/dashboard.spec.ts b/__tests__/dashboard.spec.ts index 8a95bd6..71cbb80 100644 --- a/__tests__/dashboard.spec.ts +++ b/__tests__/dashboard.spec.ts @@ -5,29 +5,20 @@ import { } from "../__mocks__/dashboard"; import { expect, test } from "@playwright/test"; +import { permissionAvailableMock } from "../__mocks__/permissionMock"; + test.describe("대시보드 페이지", () => { - test.beforeEach(async ({ page }) => { + test.beforeEach(async ({ context, page }) => { await page.addInitScript(() => { localStorage.setItem("accessToken", "token"); }); - await page.goto("http://localhost:5173/dashboard"); - await page.route("https://linkedoutapp.com/api/admin-info/my", (route) => - route.fulfill({ - status: 200, - contentType: "application/json", - body: JSON.stringify({ - data: { - id: 0, - email: "test@example.com", - name: "테스트", - profileImage: null, - activated: true, - info: null, - }, - }), - }) + await context.route(permissionAvailableMock.url, (route) => + route.fulfill(permissionAvailableMock.apiResponse) ); + + await page.goto("http://localhost:5173/dashboard"); + await page.route(dashboardGraphData.url, (route) => route.fulfill(dashboardGraphData.apiResponse) ); From 1f9d8ab7f9f086e7b392d442942753906e3962cc Mon Sep 17 00:00:00 2001 From: a-honey Date: Wed, 25 Dec 2024 13:41:42 +0900 Subject: [PATCH 3/3] =?UTF-8?q?chore:=20=EB=8B=A4=EB=A5=B8=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EC=97=90=EB=8F=84=20context=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 --- __mocks__/reportMock.ts | 216 ++++----------------------- __tests__/essay.spec.ts | 8 +- __tests__/geulroquis.spec.ts | 8 +- __tests__/manager.spec.ts | 15 +- __tests__/notice.spec.ts | 8 +- __tests__/query.spec.ts | 48 ------ __tests__/report.spec.ts | 21 ++- __tests__/user.spec.ts | 16 +- src/api/essays/getReportList.ts | 2 + src/components/List/Header/index.tsx | 10 +- src/pages/report/List.tsx | 6 +- 11 files changed, 109 insertions(+), 249 deletions(-) delete mode 100644 __tests__/query.spec.ts diff --git a/__mocks__/reportMock.ts b/__mocks__/reportMock.ts index 4fe4bf0..0ea67a3 100644 --- a/__mocks__/reportMock.ts +++ b/__mocks__/reportMock.ts @@ -1,5 +1,5 @@ export const reportListMock = { - url: "https://linkedoutapp.com/api/admin-task/reports?sort=most&page=1&limit=10", + url: "https://linkedoutapp.com/api/admin-task/reports?page=1&limit=10", apiResponse: { status: 200, contentType: "application/json", @@ -12,211 +12,51 @@ export const reportListMock = { reports: [ { id: 1, - title: "Unauthorized Access", - content: "This content violates terms.", - linkedOutGauge: 20, - createdDate: "2024-12-09T16:21:20.569Z", - updatedDate: "2024-12-10T10:05:30.000Z", - thumbnail: "image1.png", - bookmarks: true, - views: 150, - status: "Pending", - deviceOS: "iOS", - deviceType: "Smartphone", - deviceModel: "iPhone 14", - authorId: 101, - reports: [ - { - id: 1, - reason: "Hate speech", - processed: false, - processedDate: null, - createdDate: "2024-12-09T16:30:20.000Z", - reporterId: 301, - }, - ], + essayTitle: "Unauthorized Access", + reportCount: 20, + oldestReportDate: "2024-12-09T16:21:20.569Z", }, { id: 2, - title: "Spam Content", - content: "Buy this now!", - linkedOutGauge: 50, - createdDate: "2024-12-08T14:15:00.000Z", - updatedDate: "2024-12-09T08:10:45.000Z", - thumbnail: "image2.png", - bookmarks: false, - views: 80, - status: "Reviewed", - deviceOS: "Android", - deviceType: "Tablet", - deviceModel: "Galaxy Tab S7", - authorId: 102, - reports: [ - { - id: 2, - reason: "Spam", - processed: true, - processedDate: "2024-12-09T10:30:00.000Z", - createdDate: "2024-12-08T14:30:00.000Z", - reporterId: 302, - }, - ], + essayTitle: "Spam Content", + reportCount: 50, + oldestReportDate: "2024-12-08T14:15:00.000Z", }, { id: 3, - title: "Offensive Language", - content: "This post contains offensive words.", - linkedOutGauge: 10, - createdDate: "2024-12-07T18:00:00.000Z", - updatedDate: "2024-12-08T09:00:00.000Z", - thumbnail: "image3.png", - bookmarks: true, - views: 95, - status: "Pending", - deviceOS: "Windows", - deviceType: "Desktop", - deviceModel: "HP Pavilion", - authorId: 103, - reports: [ - { - id: 3, - reason: "Offensive Language", - processed: false, - processedDate: null, - createdDate: "2024-12-07T18:15:00.000Z", - reporterId: 303, - }, - ], + essayTitle: "Offensive Language", + reportCount: 10, + oldestReportDate: "2024-12-07T18:00:00.000Z", }, { id: 4, - title: "Misinformation", - content: "False information about current events.", - linkedOutGauge: 5, - createdDate: "2024-12-06T12:00:00.000Z", - updatedDate: "2024-12-07T15:00:00.000Z", - thumbnail: "image4.png", - bookmarks: false, - views: 120, - status: "Reviewed", - deviceOS: "MacOS", - deviceType: "Laptop", - deviceModel: "MacBook Pro", - authorId: 104, - reports: [ - { - id: 4, - reason: "Misinformation", - processed: true, - processedDate: "2024-12-07T16:00:00.000Z", - createdDate: "2024-12-06T12:15:00.000Z", - reporterId: 304, - }, - ], + essayTitle: "Misinformation", + reportCount: 5, + oldestReportDate: "2024-12-06T12:00:00.000Z", }, { id: 5, - title: "Copyright Infringement", - content: "Unauthorized use of copyrighted material.", - linkedOutGauge: 15, - createdDate: "2024-12-05T10:00:00.000Z", - updatedDate: "2024-12-06T11:00:00.000Z", - thumbnail: "image5.png", - bookmarks: true, - views: 65, - status: "Pending", - deviceOS: "Linux", - deviceType: "Server", - deviceModel: "Dell PowerEdge", - authorId: 105, - reports: [ - { - id: 5, - reason: "Copyright Violation", - processed: false, - processedDate: null, - createdDate: "2024-12-05T10:15:00.000Z", - reporterId: 305, - }, - ], + essayTitle: "Copyright Infringement", + reportCount: 15, + oldestReportDate: "2024-12-05T10:00:00.000Z", }, { id: 47, - title: "Harassment", - content: "Targeted harassment of an individual.", - linkedOutGauge: 30, - createdDate: "2024-12-04T16:00:00.000Z", - updatedDate: "2024-12-05T17:00:00.000Z", - thumbnail: "image6.png", - bookmarks: false, - views: 200, - status: "Reviewed", - deviceOS: "Android", - deviceType: "Smartphone", - deviceModel: "Pixel 7", - authorId: 106, - reports: [ - { - id: 6, - reason: "Harassment", - processed: true, - processedDate: "2024-12-05T18:00:00.000Z", - createdDate: "2024-12-04T16:15:00.000Z", - reporterId: 306, - }, - ], + essayTitle: "Harassment", + reportCount: 30, + oldestReportDate: "2024-12-04T16:00:00.000Z", }, { id: 7, - title: "Malicious Link", - content: "This post contains harmful links.", - linkedOutGauge: 45, - createdDate: "2024-12-03T08:00:00.000Z", - updatedDate: "2024-12-04T09:00:00.000Z", - thumbnail: "image7.png", - bookmarks: true, - views: 175, - status: "Pending", - deviceOS: "Windows", - deviceType: "Desktop", - deviceModel: "Lenovo ThinkPad", - authorId: 107, - reports: [ - { - id: 7, - reason: "Malicious Content", - processed: false, - processedDate: null, - createdDate: "2024-12-03T08:15:00.000Z", - reporterId: 307, - }, - ], + essayTitle: "Malicious Link", + reportCount: 45, + oldestReportDate: "2024-12-03T08:00:00.000Z", }, { id: 8, - title: "Fake Account", - content: "Suspected fake account activity.", - linkedOutGauge: 0, - createdDate: "2024-12-02T20:00:00.000Z", - updatedDate: "2024-12-03T21:00:00.000Z", - thumbnail: "image8.png", - bookmarks: false, - views: 50, - status: "Reviewed", - deviceOS: "iOS", - deviceType: "Smartphone", - deviceModel: "iPhone SE", - authorId: 108, - reports: [ - { - id: 8, - reason: "Impersonation", - processed: true, - processedDate: "2024-12-03T22:00:00.000Z", - createdDate: "2024-12-02T20:15:00.000Z", - reporterId: 308, - }, - ], + essayTitle: "Fake Account", + reportCount: 0, + oldestReportDate: "2024-12-02T20:00:00.000Z", }, ], }, @@ -232,10 +72,10 @@ export const reportDetailMock = { body: JSON.stringify({ data: { id: 11, - title: "제목 없음", + essayTitle: "제목 없음", content: "

제목없음

", - linkedOutGauge: 0, - createdDate: "2024-11-01T16:04:18.145+09:00", + reportCount: 0, + oldestReportDate: "2024-11-01T16:04:18.145+09:00", updatedDate: "2024-11-01T16:29:24.215+09:00", thumbnail: null, views: 0, diff --git a/__tests__/essay.spec.ts b/__tests__/essay.spec.ts index 329c244..768ed03 100644 --- a/__tests__/essay.spec.ts +++ b/__tests__/essay.spec.ts @@ -2,13 +2,19 @@ import { essayDetailMock, essayListMock } from "../__mocks__/essayMock"; import { expect, test } from "@chromatic-com/playwright"; import { authMock } from "../__mocks__/authMock"; +import { permissionAvailableMock } from "../__mocks__/permissionMock"; import { reportDetailMock } from "../__mocks__/reportMock"; test.describe("에세이 리스트 페이지", () => { - test.beforeEach(async ({ page }) => { + test.beforeEach(async ({ context, page }) => { await page.addInitScript(() => { localStorage.setItem("accessToken", "token"); }); + + await context.route(permissionAvailableMock.url, (route) => + route.fulfill(permissionAvailableMock.apiResponse) + ); + await page.goto("http://localhost:5173/essays"); await page.route(essayListMock.url, (route) => diff --git a/__tests__/geulroquis.spec.ts b/__tests__/geulroquis.spec.ts index 828f423..e56a62c 100644 --- a/__tests__/geulroquis.spec.ts +++ b/__tests__/geulroquis.spec.ts @@ -4,12 +4,18 @@ import { geulroquisListMock, } from "../__mocks__/geulroquis"; +import { permissionAvailableMock } from "../__mocks__/permissionMock"; + test.describe("글로키 리스트 페이지", () => { - test.beforeEach(async ({ page }) => { + test.beforeEach(async ({ context, page }) => { await page.addInitScript(() => { localStorage.setItem("accessToken", "token"); }); + await context.route(permissionAvailableMock.url, (route) => + route.fulfill(permissionAvailableMock.apiResponse) + ); + await page.goto("http://localhost:5173/geulroquis"); await page.route(geulroquisListMock.url, (route) => diff --git a/__tests__/manager.spec.ts b/__tests__/manager.spec.ts index b6213cd..61574ca 100644 --- a/__tests__/manager.spec.ts +++ b/__tests__/manager.spec.ts @@ -4,11 +4,18 @@ import { managerListMock, } from "../__mocks__/managerMock"; +import { permissionAvailableMock } from "../__mocks__/permissionMock"; + test.describe("관리자 목록 페이지", () => { - test.beforeEach(async ({ page }) => { + test.beforeEach(async ({ context, page }) => { await page.addInitScript(() => { localStorage.setItem("accessToken", "token"); }); + + await context.route(permissionAvailableMock.url, (route) => + route.fulfill(permissionAvailableMock.apiResponse) + ); + await page.goto("http://localhost:5173/managers"); await page.route(managerListMock.url, (route) => @@ -32,10 +39,14 @@ test.describe("관리자 목록 페이지", () => { }); test.describe("관리자 로그 리스트 페이지", () => { - test.beforeEach(async ({ page }) => { + test.beforeEach(async ({ context, page }) => { await page.addInitScript(() => { localStorage.setItem("accessToken", "token"); }); + + await context.route(permissionAvailableMock.url, (route) => + route.fulfill(permissionAvailableMock.apiResponse) + ); await page.goto("http://localhost:5173/manager-history"); await page.route(managerHistoryListMock.url, (route) => diff --git a/__tests__/notice.spec.ts b/__tests__/notice.spec.ts index d39ea9b..cd19713 100644 --- a/__tests__/notice.spec.ts +++ b/__tests__/notice.spec.ts @@ -1,12 +1,18 @@ import { expect, test } from "@chromatic-com/playwright"; import { noticeListMock } from "../__mocks__/noticeMock"; +import { permissionAvailableMock } from "../__mocks__/permissionMock"; test.describe("공지사항 리스트 페이지", () => { - test.beforeEach(async ({ page }) => { + test.beforeEach(async ({ context, page }) => { await page.addInitScript(() => { localStorage.setItem("accessToken", "token"); }); + + await context.route(permissionAvailableMock.url, (route) => + route.fulfill(permissionAvailableMock.apiResponse) + ); + await page.goto("http://localhost:5173/notices"); await page.route(noticeListMock.url, (route) => diff --git a/__tests__/query.spec.ts b/__tests__/query.spec.ts deleted file mode 100644 index 631c8c7..0000000 --- a/__tests__/query.spec.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { expect, test } from "@chromatic-com/playwright"; - -import { reportListMock } from "../__mocks__/reportMock"; - -test.describe("문의사항 리스트 페이지", () => { - test.beforeEach(async ({ page }) => { - await page.addInitScript(() => { - localStorage.setItem("accessToken", "token"); - }); - await page.goto("http://localhost:5173/query"); - - await page.route(reportListMock.url, (route) => - route.fulfill(reportListMock.apiResponse) - ); - }); - - test("문의사항 리스트 페이지를 보여준다.", async ({ page }) => { - await expect(page.getByText("총 에세이 수 6")).toBeVisible(); - await expect(page.getByText("총 레포트 수 6")).toBeVisible(); - }); - - test("문의사항를 클릭하면 에세이 해당 에세이의 페이지로 이동한다", async ({ - page, - }) => { - await page.click('a:has-text("6")'); - - await page.goto("http://localhost:5173/essays/47#report"); - - await expect(page).toHaveURL("http://localhost:5173/essay/47#report"); - }); -}); - -test.describe("문의사항 디테일 페이지", () => { - test.beforeEach(async ({ page }) => { - await page.addInitScript(() => { - localStorage.setItem("accessToken", "token"); - }); - await page.goto("http://localhost:5173/query"); - - await page.route(reportListMock.url, (route) => - route.fulfill(reportListMock.apiResponse) - ); - }); - - test("문의사항 디테일 리스트 페이지를 보여준다.", async ({ page }) => {}); - - test("문의사항를 클릭하면 답변 폼을 보여준다", async ({ page }) => {}); -}); diff --git a/__tests__/report.spec.ts b/__tests__/report.spec.ts index 9bde062..b186836 100644 --- a/__tests__/report.spec.ts +++ b/__tests__/report.spec.ts @@ -1,17 +1,32 @@ import { expect, test } from "@chromatic-com/playwright"; +import { reportDetailMock, reportListMock } from "../__mocks__/reportMock"; -import { reportListMock } from "../__mocks__/reportMock"; +import { essayDetailMock } from "../__mocks__/essayMock"; +import { permissionAvailableMock } from "../__mocks__/permissionMock"; test.describe("레포트 리스트 페이지", () => { - test.beforeEach(async ({ page }) => { + test.beforeEach(async ({ context, page }) => { await page.addInitScript(() => { localStorage.setItem("accessToken", "token"); }); + + await context.route(permissionAvailableMock.url, (route) => + route.fulfill(permissionAvailableMock.apiResponse) + ); + await page.goto("http://localhost:5173/reports"); await page.route(reportListMock.url, (route) => route.fulfill(reportListMock.apiResponse) ); + + await page.route(reportDetailMock.url, (route) => + route.fulfill(reportDetailMock.apiResponse) + ); + + await page.route(essayDetailMock.url, (route) => + route.fulfill(essayDetailMock.apiResponse) + ); }); test("레포트 리스트 페이지를 보여준다.", async ({ page }) => { @@ -26,6 +41,6 @@ test.describe("레포트 리스트 페이지", () => { await page.goto("http://localhost:5173/essays/47#report"); - await expect(page).toHaveURL("http://localhost:5173/essay/47#report"); + await expect(page).toHaveURL("http://localhost:5173/essays/47#report"); }); }); diff --git a/__tests__/user.spec.ts b/__tests__/user.spec.ts index a6b149e..1a7255f 100644 --- a/__tests__/user.spec.ts +++ b/__tests__/user.spec.ts @@ -1,11 +1,18 @@ import { expect, test } from "@chromatic-com/playwright"; import { userDetailMock, userListMock } from "../__mocks__/userMock"; +import { permissionAvailableMock } from "../__mocks__/permissionMock"; + test.describe("사용자 리스트 페이지", () => { - test.beforeEach(async ({ page }) => { + test.beforeEach(async ({ context, page }) => { await page.addInitScript(() => { localStorage.setItem("accessToken", "token"); }); + + await context.route(permissionAvailableMock.url, (route) => + route.fulfill(permissionAvailableMock.apiResponse) + ); + await page.goto("http://localhost:5173/users"); await page.route(userListMock.url, (route) => @@ -25,10 +32,15 @@ test.describe("사용자 리스트 페이지", () => { }); test.describe("사용자 디테일 페이지 테스트", () => { - test.beforeEach(async ({ page }) => { + test.beforeEach(async ({ context, page }) => { await page.addInitScript(() => { localStorage.setItem("accessToken", "token"); }); + + await context.route(permissionAvailableMock.url, (route) => + route.fulfill(permissionAvailableMock.apiResponse) + ); + await page.goto("http://localhost:5173/users/1"); await page.route(userDetailMock.url, (route) => diff --git a/src/api/essays/getReportList.ts b/src/api/essays/getReportList.ts index 471202b..6362f86 100644 --- a/src/api/essays/getReportList.ts +++ b/src/api/essays/getReportList.ts @@ -6,6 +6,8 @@ import fetchData from "../fetchData"; export type ReportListResponseType = { reports: ReportListType[]; + totalReports: number; + totalEssay: number; } & ResponsePaginationType; export default async function getReportList(params: ListParams) { diff --git a/src/components/List/Header/index.tsx b/src/components/List/Header/index.tsx index 8749503..e18d043 100644 --- a/src/components/List/Header/index.tsx +++ b/src/components/List/Header/index.tsx @@ -1,13 +1,19 @@ type HeaderProps = { label?: string; totalCount: number; + sub?: string; } & React.PropsWithChildren; -export default function Header({ totalCount, label, children }: HeaderProps) { +export default function Header({ + totalCount, + label, + children, + sub, +}: HeaderProps) { return (
- 총 {label} 수 {totalCount} + 총 {label} 수 {totalCount} {sub ? `/ ${sub}` : ""}
{children}
diff --git a/src/pages/report/List.tsx b/src/pages/report/List.tsx index ef18451..6bc7528 100644 --- a/src/pages/report/List.tsx +++ b/src/pages/report/List.tsx @@ -48,7 +48,11 @@ const ReportListContent = () => { return ( - + {data.reports.map((report) => (