diff --git a/.gitignore b/.gitignore index cc4df33..a23d889 100644 --- a/.gitignore +++ b/.gitignore @@ -34,4 +34,4 @@ node_modules/ playwright/.auth -tests/auth/ \ No newline at end of file +e2e-tests/auth/ \ No newline at end of file diff --git a/tests/auth.setup.ts b/e2e-tests/auth.setup.ts similarity index 100% rename from tests/auth.setup.ts rename to e2e-tests/auth.setup.ts diff --git a/e2e-tests/excel/excelDownload.spec.ts b/e2e-tests/excel/excelDownload.spec.ts new file mode 100644 index 0000000..fe18585 --- /dev/null +++ b/e2e-tests/excel/excelDownload.spec.ts @@ -0,0 +1,36 @@ +import { paths } from '@/const/paths'; +import test, { expect } from '@playwright/test'; + +test.use({ storageState: 'e2e-tests/auth/admin.json' }); + +test.describe('엑셀 다운로드 테스트', () => { + test('스터디 관리 페이지 (manage-study) 엑셀 다운로드 테스트', async ({ page }) => { + await page.goto(paths.admin.manageStudy); + + await page.waitForTimeout(3000); + const [download] = await Promise.all([ + page.waitForEvent('download'), + page.getByRole('button', { name: '그룹 활동 목록 엑셀 다운' }).click(), + ]); + + expect(download.suggestedFilename()).toBe('스터디그룹활동.xlsx'); + + const failure = await download.failure(); + expect(failure).toBeNull(); + }); + + test('스터디 신청자 목록 페이지 (manage-student) 엑셀 다운로드 테스트', async ({ page }) => { + await page.goto(paths.admin.manageStudent); + await page.waitForTimeout(3000); + + const [download] = await Promise.all([ + page.waitForEvent('download'), + page.getByRole('button', { name: '신청자 목록 다운로드' }).click(), + ]); + + expect(download.suggestedFilename()).toBe('스터디신청자목록.xlsx'); + + const failure = await download.failure(); + expect(failure).toBeNull(); + }); +}); diff --git a/tests/reports/report.member.spec.ts b/e2e-tests/reports/report.member.spec.ts similarity index 99% rename from tests/reports/report.member.spec.ts rename to e2e-tests/reports/report.member.spec.ts index 8937ad5..ac0e43a 100644 --- a/tests/reports/report.member.spec.ts +++ b/e2e-tests/reports/report.member.spec.ts @@ -28,7 +28,7 @@ export function formatMinutesToHoursAndMinutes(totalMinutes: string | number): s return `${hours}시간 ${remainingMinutes}분`; } -test.use({ storageState: 'tests/auth/member.json' }); +test.use({ storageState: 'e2e-tests/auth/member.json' }); test.describe('스터디원 리포트 테스트', () => { // 업로드한 이미지와 실제 올라간 이미지를 구분 못하는 이슈 존재 diff --git a/playwright.config.ts b/playwright.config.ts index 73da6df..9e2fedc 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -17,7 +17,8 @@ if (!isCI) { * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ - testDir: './tests', + timeout: 120_000, + testDir: './e2e-tests', /* Run tests in files in parallel */ fullyParallel: true, /* Fail the build on CI if you accidentally left test.only in the source code. */ diff --git a/src/pages/Admin/ManageStudent/Page.tsx b/src/pages/Admin/ManageStudent/Page.tsx index 1dd7041..ca49bc4 100644 --- a/src/pages/Admin/ManageStudent/Page.tsx +++ b/src/pages/Admin/ManageStudent/Page.tsx @@ -9,6 +9,7 @@ import { useQuery } from 'react-query'; import { StudyApplyUser } from '@/interface/user'; import SpinnerLoading from '@/components/SpinnerLoading'; import { toast } from 'sonner'; +import { downloadExcelFromSheetData } from '@/utils/excel'; // Helper function to clean course names const cleanCourseName = (name: string) => name.replace(/\n/g, ' '); @@ -86,8 +87,12 @@ export default function ManageStudentPage() { }, [applicants, searchTerm]); const handleExcelDownload = () => { - if (applicants) { - const sheetData = applicants.map((student) => ({ + if (!applicants) return; + + downloadExcelFromSheetData(buildApplicantsSheetData(applicants), '스터디신청자목록.xlsx'); + + function buildApplicantsSheetData(applicants: StudyApplyUser[]) { + return applicants.map((student) => ({ ID: student.id, Name: student.name, StudentId: student.sid, @@ -96,12 +101,6 @@ export default function ManageStudentPage() { Courses: student.courses.map((subject) => subject.name + `(${subject.prof})`).join(', '), Friends: student.friends.map((friend) => friend.name).join(', '), })); - - const ws = xlsx.utils.json_to_sheet([...sheetData]); - const wb = xlsx.utils.book_new(); - xlsx.utils.book_append_sheet(wb, ws, 'Sheet1'); - - xlsx.writeFile(wb, '스터디신청자목록.xlsx'); } }; diff --git a/src/pages/Admin/ManageStudy/Page.tsx b/src/pages/Admin/ManageStudy/Page.tsx index f13a890..3eaf863 100644 --- a/src/pages/Admin/ManageStudy/Page.tsx +++ b/src/pages/Admin/ManageStudy/Page.tsx @@ -1,4 +1,3 @@ -import * as xlsx from 'xlsx'; import * as React from 'react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; @@ -10,6 +9,8 @@ import { useQuery } from 'react-query'; import SpinnerLoading from '@/components/SpinnerLoading'; import { Link } from 'react-router-dom'; import { paths } from '@/const/paths'; +import { downloadExcelFromSheetData } from '@/utils/excel'; +import { Group } from '@/interface/group'; export default function ManageStudyPage() { const { data: activities, isLoading } = useQuery(['courses'], readAllGroups, { @@ -33,9 +34,13 @@ export default function ManageStudyPage() { }, [activities, searchTerm]); const handleExcelDownload = () => { - if (activities) { - const sheetData = activities.flatMap((group) => - group.members.map((member) => ({ + if (!activities) return; + + downloadExcelFromSheetData(buildStudySheetData(activities), '스터디그룹활동.xlsx'); + + function buildStudySheetData(activities: Group[]) { + return activities.flatMap((group) => { + const sheetRow = group.members.map((member) => ({ Group: group.group, MemberID: member.id, MemberName: member.name, @@ -44,15 +49,10 @@ export default function ManageStudyPage() { Subjects: member.courses.map((subject) => subject.name).join(', '), Reports: group.reports, Times: group.times, - })), - ); - const ws = xlsx.utils.json_to_sheet([...sheetData]); - const wb = xlsx.utils.book_new(); - xlsx.utils.book_append_sheet(wb, ws, 'Sheet1'); + })); - xlsx.writeFile(wb, '스터디그룹활동.xlsx'); - } else { - console.log('데이터가 비어있습니다.'); + return sheetRow; + }); } }; @@ -71,7 +71,7 @@ export default function ManageStudyPage() { onChange={(e) => setSearchTerm(e.target.value)} /> - diff --git a/src/utils/excel.ts b/src/utils/excel.ts new file mode 100644 index 0000000..65eec80 --- /dev/null +++ b/src/utils/excel.ts @@ -0,0 +1,16 @@ +import * as xlsx from 'xlsx'; + +export function downloadExcelFromSheetData(sheetData: T[], filename: string) { + const workBook = buildWorkBook(sheetData); + xlsx.writeFile(workBook, filename); + + function buildWorkBook(sheetData: T[]) { + const workBook = xlsx.utils.book_new(); + + const workSheet = xlsx.utils.json_to_sheet(sheetData); + + xlsx.utils.book_append_sheet(workBook, workSheet); + + return workBook; + } +}