Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha }}
- uses: pnpm/action-setup@v4
with:
version: latest
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
Expand All @@ -37,12 +37,30 @@ jobs:
ref: ${{ github.event.pull_request.head.sha }}
- uses: pnpm/action-setup@v4
with:
version: latest
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: test basic
run: |
pnpm install
pnpm run test
pnpm run test
e2e-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: test basic
run: |
pnpm install
pnpm run test:e2e
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,10 @@
.vscode
node_modules
.coverage
.vitest-preview/index.html

# Playwright
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
73 changes: 73 additions & 0 deletions e2e/calendar.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { expect, test } from '@playwright/test';

test.describe('월간 캘린더 테스트', () => {
test.beforeEach(async ({ page }) => {
await page.clock.setFixedTime(new Date('2025-08-15'));
await page.goto('http://localhost:5173/');
await page.getByLabel('뷰 타입 선택').click();
await page.getByRole('option', { name: 'Month' }).click();
await page.getByText('2025년 8월');
const headerTexts = await page.locator('thead th').allTextContents();
expect(headerTexts).toEqual(['일', '월', '화', '수', '목', '금', '토']);
});

test('Month 뷰 렌더링', async ({ page }) => {
await expect(page.locator('tbody')).toContainText('1');
await expect(page.locator('tbody')).toContainText('31');
});

test('휴일 렌더링', async ({ page }) => {
await page.waitForSelector('text=15');
await page.waitForSelector('text=광복절');
});

test('다음달로 이동', async ({ page }) => {
await page.getByTestId('ChevronRightIcon').click();
await page.waitForSelector('text=2025년 9월');
await page.getByTestId('ChevronRightIcon').click();
await page.waitForSelector('text=2025년 10월');
});

test('이전달로 이동', async ({ page }) => {
await page.getByTestId('ChevronLeftIcon').click();
await page.waitForSelector('text=2025년 7월');
await page.getByTestId('ChevronLeftIcon').click();
await page.waitForSelector('text=2025년 6월');
});
});

test.describe('주간 캘린더 테스트', () => {
test.beforeEach(async ({ page }) => {
await page.clock.setFixedTime(new Date('2025-08-15'));
await page.goto('http://localhost:5173/');
await page.getByLabel('뷰 타입 선택').click();
await page.getByRole('option', { name: 'Week' }).click();
await page.getByText('2025년 8월 2주');
const headerTexts = await page.locator('thead th').allTextContents();
expect(headerTexts).toEqual(['일', '월', '화', '수', '목', '금', '토']);
});

test('Week 뷰 렌더링', async ({ page }) => {
await expect(page.locator('tbody')).toContainText('10');
await expect(page.locator('tbody')).toContainText('16');
});

test('휴일 렌더링', async ({ page }) => {
await page.waitForSelector('text=15');
await page.waitForSelector('text=광복절');
});

test('다음주로 이동', async ({ page }) => {
await page.getByTestId('ChevronRightIcon').click();
await page.waitForSelector('text=2025년 8월 3주');
await page.getByTestId('ChevronRightIcon').click();
await page.waitForSelector('text=2025년 8월 4주');
});

test('이전주로 이동', async ({ page }) => {
await page.getByTestId('ChevronLeftIcon').click();
await page.waitForSelector('text=2025년 8월 1주');
await page.getByTestId('ChevronLeftIcon').click();
await page.waitForSelector('text=2025년 7월 5주');
});
});
90 changes: 90 additions & 0 deletions e2e/crud.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { test } from '@playwright/test';

test.describe('CRUD 테스트', () => {
test.beforeEach(async ({ page }) => {
await page.goto('http://localhost:5173/');
await page.waitForSelector('text=일정 로딩 완료!');
});

test('일정 추가', async ({ page }) => {
await page.getByRole('textbox', { name: '제목' }).fill('새 회의');
await page.getByRole('textbox', { name: '날짜' }).fill('2025-05-19');
await page.getByRole('textbox', { name: '시작 시간' }).fill('10:00');
await page.getByRole('textbox', { name: '종료 시간' }).fill('11:00');
await page.getByRole('textbox', { name: '설명' }).fill('설명');
await page.getByRole('textbox', { name: '위치' }).fill('위치');
await page.getByTestId('event-submit-button').click();
await page.waitForSelector('text=일정이 추가되었습니다.');
});

test('반복 일정 추가', async ({ page }) => {
await page.getByRole('textbox', { name: '제목' }).fill('새 회의');
await page.getByRole('textbox', { name: '날짜' }).fill('2025-05-20');
await page.getByRole('textbox', { name: '시작 시간' }).fill('10:00');
await page.getByRole('textbox', { name: '종료 시간' }).fill('11:00');
await page.getByRole('textbox', { name: '설명' }).fill('설명');
await page.getByRole('textbox', { name: '위치' }).fill('위치');
await page.getByLabel('반복 일정').check();
await page.getByTestId('repeat-type-select').click();
await page.getByRole('option', { name: '매일' }).click();
await page.getByRole('spinbutton', { name: '반복 간격' }).fill('2');
await page.getByRole('textbox', { name: '반복 종료일' }).fill('2025-05-25');
await page.getByTestId('event-submit-button').click();
await page.waitForSelector('text=일정이 추가되었습니다.');
});

test('일정 수정', async ({ page }) => {
await page.getByLabel('Edit event').first().click();
await page.getByRole('textbox', { name: '제목' }).fill('수정된 회의');
await page.getByRole('textbox', { name: '설명' }).fill('수정된 회의');
await page.getByRole('textbox', { name: '날짜' }).fill('2025-09-20');
await page.getByTestId('event-submit-button').click();
await page.waitForSelector('text=일정이 수정되었습니다.');
});

test('일정 삭제', async ({ page }) => {
await page.getByLabel('Delete event').last().click();
await page.waitForSelector('text=일정이 삭제되었습니다.');
});

test('필수 정보 누락', async ({ page }) => {
await page.getByRole('button', { name: '추가' }).click();
await page.waitForSelector('text=필수 정보를 모두 입력해주세요.');
});

test('시간 설정 오류', async ({ page }) => {
await page.getByRole('textbox', { name: '시작 시간' }).fill('11:00');
await page.getByRole('textbox', { name: '종료 시간' }).fill('10:00');
await page.getByTestId('event-submit-button').click();
await page.waitForSelector('text=시작 시간은 종료 시간보다 빨라야 합니다');
await page.waitForSelector('text=종료 시간은 시작 시간보다 늦어야 합니다');
});

test('중복 일정 -> 취소', async ({ page }) => {
await page.getByRole('textbox', { name: '제목' }).fill('새 회의');
await page.getByRole('textbox', { name: '날짜' }).fill('2025-08-28');
await page.getByRole('textbox', { name: '시작 시간' }).fill('19:00');
await page.getByRole('textbox', { name: '종료 시간' }).fill('22:00');
await page.getByRole('textbox', { name: '설명' }).fill('설명');
await page.getByRole('textbox', { name: '위치' }).fill('위치');
await page.getByTestId('event-submit-button').click();
await page.waitForSelector('text=일정 겹침 경고');
await page.getByRole('button', { name: '취소' }).click();
await page.waitForSelector('text=일정 겹침 경고', { state: 'hidden' });
await page.waitForSelector('text=일정이 추가되었습니다.', { state: 'hidden' });
});

test('중복 일정 -> 저장', async ({ page }) => {
await page.getByRole('textbox', { name: '제목' }).fill('새 회의');
await page.getByRole('textbox', { name: '날짜' }).fill('2025-08-28');
await page.getByRole('textbox', { name: '시작 시간' }).fill('19:00');
await page.getByRole('textbox', { name: '종료 시간' }).fill('22:00');
await page.getByRole('textbox', { name: '설명' }).fill('설명');
await page.getByRole('textbox', { name: '위치' }).fill('위치');
await page.getByTestId('event-submit-button').click();
await page.waitForSelector('text=일정 겹침 경고');
await page.getByRole('button', { name: '계속 진행' }).click();
await page.waitForSelector('text=일정 겹침 경고', { state: 'hidden' });
await page.waitForSelector('text=일정이 추가되었습니다.');
});
});
23 changes: 23 additions & 0 deletions e2e/filter.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { expect, test } from '@playwright/test';

test.describe('이벤트 필터링 테스트', () => {
test.beforeEach(async ({ page }) => {
await page.goto('http://localhost:5173/');
await page.waitForSelector('text=일정 로딩 완료!');
});

test('검색어 테스트', async ({ page }) => {
const eventList = await page.getByTestId('event-list');

await eventList.getByLabel('일정 검색').fill('아무런 키워드로 검색하기');
await expect(eventList.getByText('검색 결과가 없습니다.')).toBeVisible();

await eventList.getByLabel('일정 검색').fill('동료와 점심 식사');
await expect(eventList.getByText('점심 약속')).toBeVisible();
await expect(eventList.getByText('2025-08-21')).toBeVisible();
await expect(eventList.getByText('12:30 - 13:30')).toBeVisible();
await expect(eventList.getByText('동료와 점심 식사')).toBeVisible();
await expect(eventList.getByText('회사 근처 식당')).toBeVisible();
await expect(eventList.getByText('개인')).toBeVisible();
});
});
13 changes: 13 additions & 0 deletions e2e/notification.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { test } from '@playwright/test';

test.describe('알림 테스트', () => {
test.beforeEach(async ({ page }) => {
await page.goto('http://localhost:5173/');
await page.waitForSelector('text=일정 로딩 완료!');
});

test('알림 테스트', async ({ page }) => {
await page.clock.setFixedTime(new Date('2025-09-20T17:59:30'));
await page.waitForSelector('text=일정이 시작됩니다');
});
});
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
"test": "vitest",
"test:ui": "vitest --ui",
"test:coverage": "vitest run --coverage",
"test:e2e": "playwright test",
"build": "tsc -b && vite build",
"lint:eslint": "eslint . --ext ts,tsx --report-unused-disable-directives",
"lint:tsc": "tsc --pretty",
"lint": "pnpm lint:eslint && pnpm lint:tsc"
"lint": "pnpm lint:eslint && pnpm lint:tsc",
"vitest-preview": "vitest-preview"
},
"dependencies": {
"@emotion/react": "^11.11.4",
Expand All @@ -30,9 +32,11 @@
},
"devDependencies": {
"@eslint/js": "9.33.0",
"@playwright/test": "^1.55.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.5.2",
"@types/node": "^24.3.0",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@typescript-eslint/eslint-plugin": "^8.35.0",
Expand All @@ -55,6 +59,7 @@
"typescript": "^5.2.2",
"vite": "^7.0.2",
"vite-plugin-eslint": "^1.8.1",
"vitest": "^3.2.4"
"vitest": "^3.2.4",
"vitest-preview": "^0.0.1"
}
}
49 changes: 49 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { defineConfig, devices } from '@playwright/test';

/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// import dotenv from 'dotenv';
// import path from 'path';
// dotenv.config({ path: path.resolve(__dirname, '.env') });

/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './e2e',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
// baseURL: 'http://localhost:3000',

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},

/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],

/* Run your local dev server before starting the tests */
webServer: {
command: 'pnpm run dev',
url: 'http://localhost:5173/',
reuseExistingServer: !process.env.CI,
},
});
Loading
Loading