diff --git a/.eslintrc.json b/.eslintrc.json
index df03548d..0f3f5255 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -71,6 +71,12 @@
"rules": {
"import/default": "off"
}
+ },
+ {
+ "files": ["*.stories.tsx"],
+ "rules": {
+ "react/display-name": "off"
+ }
}
]
}
diff --git a/.github/ISSUE_TEMPLATE/component-template.md b/.github/ISSUE_TEMPLATE/component-template.md
new file mode 100644
index 00000000..2b88aa24
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/component-template.md
@@ -0,0 +1,46 @@
+---
+name: Component Template
+about: Describe this issue template's purpose here.
+title: "컴포넌트 이슈 작성 템플릿"
+labels: "🎯 기능 구현"
+assignees: ""
+---
+
+### **용도 (Usage)**
+
+
+
+### **기능 (Props)**
+
+
+
+### **참고 (Reference)**
+
+
+
+### **마일스톤 (Milestones)**
+
+
diff --git a/.github/PULL_REQUEST_TEMPLATE/component_template.md b/.github/PULL_REQUEST_TEMPLATE/component_template.md
new file mode 100644
index 00000000..d84374e9
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE/component_template.md
@@ -0,0 +1,26 @@
+
+
+### 예상 동작 (Expected Behavior)
+
+
+
+### 고민했던 내용 (Considerations)
+
+
+
+### 관련 이슈
+
+- resolved #(issue_num)
diff --git a/.github/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md
similarity index 100%
rename from .github/pull_request_template.md
rename to .github/PULL_REQUEST_TEMPLATE/pull_request_template.md
diff --git a/.github/workflows/chromatic_auto_deploy.yml b/.github/workflows/chromatic_auto_deploy.yml
index e0072257..432e77d5 100644
--- a/.github/workflows/chromatic_auto_deploy.yml
+++ b/.github/workflows/chromatic_auto_deploy.yml
@@ -25,18 +25,25 @@ jobs:
with:
node-version: 20
- - name: Corepack 활성화
- run: corepack enable
-
- - name: pnpm 설치
- run: corepack prepare pnpm@latest --activate
+ - name: pnpm 8.15.6 설치
+ uses: pnpm/action-setup@v2
+ with:
+ version: 8.15.6
- name: 의존성 설치
run: pnpm install --no-frozen-lockfile
+ - name: Check if token exists
+ run: |
+ if [ -n "${{ secrets.PRIMITIVE_UI_CHROMATIC_TOKEN }}" ]; then
+ echo "PRIMITIVE_UI_CHROMATIC_TOKEN is set"
+ else
+ echo "PRIMITIVE_UI_CHROMATIC_TOKEN is not set"
+ fi
+
- name: chromatic에 배포
id: publish_chromatic
- uses: chromaui/action@v1
+ uses: chromaui/action@latest
with:
projectToken: ${{ secrets.PRIMITIVE_UI_CHROMATIC_TOKEN }}
token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/label-vrt-update.yml b/.github/workflows/label-vrt-update.yml
new file mode 100644
index 00000000..af61811a
--- /dev/null
+++ b/.github/workflows/label-vrt-update.yml
@@ -0,0 +1,73 @@
+name: VRT 스냅샷 업데이트
+on:
+ pull_request:
+ types: [labeled]
+
+jobs:
+ update-snapshots:
+ if: github.event.label.name == 'VRT'
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ - uses: pnpm/action-setup@v2
+ with:
+ version: 8.15.6
+
+ - name: 의존성 설치
+ run: pnpm install
+
+ - name: Playwright 설치
+ run: pnpm exec playwright install --with-deps
+
+ - name: 스토리북 빌드
+ run: pnpm run build-storybook
+
+ working-directory: packages/primitive
+ - name: 스토리북 실행
+ run: |
+ npx serve -l 6006 packages/primitive/storybook-static &
+ echo $! > .storybook-pid
+
+ - name: 스냅샷 업데이트
+ run: pnpm run e2e:update
+
+ - name: 스토리북 프로세스 종료
+ if: always()
+ run: kill $(cat .storybook-pid)
+
+ - name: 변경된 스냅샷 커밋 및 푸시
+ run: |
+ git config --local user.email "action@github.com"
+ git config --local user.name "GitHub Action"
+ git add .playwright
+ git diff --staged --quiet || git commit -m "Update VRT snapshots in .playwright folder"
+ git push origin HEAD:${{ github.head_ref }}
+
+ - name: PR 코멘트 작성
+ uses: actions/github-script@v6
+ with:
+ github-token: ${{secrets.GITHUB_TOKEN}}
+ script: |
+ github.rest.issues.createComment({
+ issue_number: context.issue.number,
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ body: '## VRT 스냅샷 업데이트 완료\n\n스냅샷이 성공적으로 업데이트되었습니다. 변경된 스냅샷이 이 PR에 포함되었습니다. 리뷰해주세요.'
+ })
+
+ - name: VRT 레이블 제거
+ uses: actions/github-script@v6
+ with:
+ github-token: ${{secrets.GITHUB_TOKEN}}
+ script: |
+ github.rest.issues.removeLabel({
+ issue_number: context.issue.number,
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ name: 'VRT'
+ })
diff --git a/.github/workflows/pr-dday-labeler.yml b/.github/workflows/pr-dday-labeler.yml
new file mode 100644
index 00000000..953a767f
--- /dev/null
+++ b/.github/workflows/pr-dday-labeler.yml
@@ -0,0 +1,22 @@
+name: PR D-day Labeler
+
+on:
+ schedule:
+ - cron: "0 15 * * *"
+ pull_request:
+ types: [opened, reopened]
+ workflow_dispatch:
+
+permissions:
+ contents: read
+ pull-requests: write
+
+jobs:
+ label-prs:
+ runs-on: ubuntu-latest
+ steps:
+ - name: PR D-day Labeler
+ uses: team-warrr/pr-dday-labeler@1.0.3
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
diff --git a/.github/workflows/pr-vrt.yml b/.github/workflows/pr-vrt.yml
new file mode 100644
index 00000000..3591fa76
--- /dev/null
+++ b/.github/workflows/pr-vrt.yml
@@ -0,0 +1,84 @@
+name: PR VRT 테스트
+on:
+ pull_request:
+ branches: [develop]
+
+permissions:
+ contents: read
+ pull-requests: write
+
+jobs:
+ vrt-test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ - uses: pnpm/action-setup@v2
+ with:
+ version: 8.15.6
+
+ - name: 의존성 설치
+ run: pnpm install --no-frozen-lockfile
+
+ - name: Playwright 설치
+ run: pnpm exec playwright install --with-deps
+
+ - name: 스토리북 빌드
+ run: pnpm run build-storybook
+ working-directory: packages/primitive
+
+ - name: 스토리북 실행
+ run: |
+ npx serve -l 6006 packages/primitive/storybook-static &
+ echo $! > .storybook-pid
+
+ - name: VRT 테스트 실행
+ id: vrt-test
+ run: |
+ if pnpm run e2e; then
+ echo "결과=성공" >> $GITHUB_OUTPUT
+ else
+ echo "결과=실패" >> $GITHUB_OUTPUT
+ fi
+
+ - name: 스토리북 프로세스 종료
+ if: always()
+ run: kill $(cat .storybook-pid)
+
+ - name: 테스트 결과 및 diff 이미지 업로드
+ uses: actions/upload-artifact@v4
+ if: failure()
+ with:
+ name: vrt-results
+ path: |
+ playwright-report/
+ .playwright/
+ retention-days: 7
+
+ - name: PR 코멘트 작성 (성공)
+ uses: actions/github-script@v6
+ if: success()
+ with:
+ github-token: ${{secrets.GITHUB_TOKEN}}
+ script: |
+ github.rest.issues.createComment({
+ issue_number: context.issue.number,
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ body: '## VRT 테스트 성공\n\nVRT 테스트가 성공적으로 완료되었습니다.'
+ })
+
+ - name: PR 코멘트 작성 (실패)
+ uses: actions/github-script@v6
+ if: failure()
+ with:
+ github-token: ${{secrets.GITHUB_TOKEN}}
+ script: |
+ github.rest.issues.createComment({
+ issue_number: context.issue.number,
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ body: '## VRT 테스트 실패\n\nVRT 테스트가 실패했습니다. 자세한 내용은 첨부된 테스트 결과와 diff 이미지를 확인해주세요.\n\n[테스트 결과 확인](https://github.com/${{github.repository}}/actions/runs/${{github.run_id}})\n\n스냅샷 업데이트가 필요한 경우, PR에 "VRT" 레이블을 추가해주세요.'
+ })
diff --git a/.gitignore b/.gitignore
index a341f14e..f8420906 100644
--- a/.gitignore
+++ b/.gitignore
@@ -37,4 +37,10 @@ yarn-error.log*
.DS_Store
*.pem
-*storybook.log
\ No newline at end of file
+*storybook.log
+.eslintcache
+local.secrets
+/test-results/
+/playwright-report/
+/blob-report/
+/playwright/.cache/
diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100644
index 00000000..9fc3832c
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1,10 @@
+#!/bin/sh
+. "$(dirname "$0")/_/husky.sh"
+
+pnpm lint
+
+pnpm test
+if [ $? -ne 0 ]; then
+ echo "테스트 실패"
+ exit 1
+fi
diff --git a/.husky/pre-push b/.husky/pre-push
new file mode 100644
index 00000000..80fabd3a
--- /dev/null
+++ b/.husky/pre-push
@@ -0,0 +1,4 @@
+#!/bin/sh
+. "$(dirname "$0")/_/husky.sh"
+
+pnpm format
diff --git a/.lintstagedrc.json b/.lintstagedrc.json
new file mode 100644
index 00000000..31a2100b
--- /dev/null
+++ b/.lintstagedrc.json
@@ -0,0 +1,3 @@
+{
+ "**.*.{ts,tsx}": ["pnpm lint", "pnpm prettier"]
+}
diff --git a/.npmrc b/.npmrc
index e69de29b..b6f27f13 100644
--- a/.npmrc
+++ b/.npmrc
@@ -0,0 +1 @@
+engine-strict=true
diff --git a/.playwright/report/index.html b/.playwright/report/index.html
new file mode 100644
index 00000000..dd196f23
--- /dev/null
+++ b/.playwright/report/index.html
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+ Playwright Test Report
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.playwright/results.json b/.playwright/results.json
new file mode 100644
index 00000000..4439599b
--- /dev/null
+++ b/.playwright/results.json
@@ -0,0 +1,320 @@
+{
+ "config": {
+ "configFile": "/home/runner/work/warrr-ui/warrr-ui/playwright.config.ts",
+ "rootDir": "/home/runner/work/warrr-ui/warrr-ui/e2e",
+ "forbidOnly": true,
+ "fullyParallel": true,
+ "globalSetup": null,
+ "globalTeardown": null,
+ "globalTimeout": 0,
+ "grep": {},
+ "grepInvert": null,
+ "maxFailures": 0,
+ "metadata": {
+ "actualWorkers": 1
+ },
+ "preserveOutput": "always",
+ "reporter": [
+ [
+ "line",
+ null
+ ],
+ [
+ "blob",
+ null
+ ],
+ [
+ "json",
+ {
+ "outputFile": "/home/runner/work/warrr-ui/warrr-ui/.playwright/results.json"
+ }
+ ]
+ ],
+ "reportSlowTests": {
+ "max": 5,
+ "threshold": 15000
+ },
+ "quiet": false,
+ "projects": [
+ {
+ "outputDir": "/home/runner/work/warrr-ui/warrr-ui/.playwright/results",
+ "repeatEach": 1,
+ "retries": 2,
+ "metadata": {},
+ "id": "chromium",
+ "name": "chromium",
+ "testDir": "/home/runner/work/warrr-ui/warrr-ui/e2e",
+ "testIgnore": [],
+ "testMatch": [
+ "**/*.@(spec|test).?(c|m)[jt]s?(x)"
+ ],
+ "timeout": 30000
+ },
+ {
+ "outputDir": "/home/runner/work/warrr-ui/warrr-ui/.playwright/results",
+ "repeatEach": 1,
+ "retries": 2,
+ "metadata": {},
+ "id": "firefox",
+ "name": "firefox",
+ "testDir": "/home/runner/work/warrr-ui/warrr-ui/e2e",
+ "testIgnore": [],
+ "testMatch": [
+ "**/*.@(spec|test).?(c|m)[jt]s?(x)"
+ ],
+ "timeout": 30000
+ },
+ {
+ "outputDir": "/home/runner/work/warrr-ui/warrr-ui/.playwright/results",
+ "repeatEach": 1,
+ "retries": 2,
+ "metadata": {},
+ "id": "webkit",
+ "name": "webkit",
+ "testDir": "/home/runner/work/warrr-ui/warrr-ui/e2e",
+ "testIgnore": [],
+ "testMatch": [
+ "**/*.@(spec|test).?(c|m)[jt]s?(x)"
+ ],
+ "timeout": 30000
+ }
+ ],
+ "shard": null,
+ "updateSnapshots": "all",
+ "version": "1.46.0",
+ "workers": 1,
+ "webServer": null
+ },
+ "suites": [
+ {
+ "title": "components/Button.test.ts",
+ "file": "components/Button.test.ts",
+ "column": 0,
+ "line": 0,
+ "specs": [],
+ "suites": [
+ {
+ "title": "Button 컴포넌트",
+ "file": "components/Button.test.ts",
+ "line": 6,
+ "column": 6,
+ "specs": [
+ {
+ "title": "primary 시각적 회귀 테스트",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 30000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "chromium",
+ "projectName": "chromium",
+ "results": [
+ {
+ "workerIndex": 0,
+ "status": "passed",
+ "duration": 1034,
+ "errors": [],
+ "stdout": [
+ {
+ "text": "/home/runner/work/warrr-ui/warrr-ui/.playwright/snapshots/components/Button.test.ts-snapshots/Button-컴포넌트-primary-시각적-회귀-테스트-1-chromium-linux.png is re-generated, writing actual.\n"
+ }
+ ],
+ "stderr": [],
+ "retry": 0,
+ "startTime": "2024-08-15T21:13:25.127Z",
+ "attachments": []
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "ae6d8ccd909362ca9a3d-f411095b4d7a7207787e",
+ "file": "components/Button.test.ts",
+ "line": 7,
+ "column": 7
+ },
+ {
+ "title": "axe를 사용하여 자동 접근성 테스트",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 30000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "chromium",
+ "projectName": "chromium",
+ "results": [
+ {
+ "workerIndex": 0,
+ "status": "passed",
+ "duration": 809,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "startTime": "2024-08-15T21:13:26.431Z",
+ "attachments": []
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "ae6d8ccd909362ca9a3d-88976050552a720a57fd",
+ "file": "components/Button.test.ts",
+ "line": 15,
+ "column": 7
+ },
+ {
+ "title": "primary 시각적 회귀 테스트",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 30000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "firefox",
+ "projectName": "firefox",
+ "results": [
+ {
+ "workerIndex": 1,
+ "status": "passed",
+ "duration": 1843,
+ "errors": [],
+ "stdout": [
+ {
+ "text": "/home/runner/work/warrr-ui/warrr-ui/.playwright/snapshots/components/Button.test.ts-snapshots/Button-컴포넌트-primary-시각적-회귀-테스트-1-firefox-linux.png is re-generated, writing actual.\n"
+ }
+ ],
+ "stderr": [],
+ "retry": 0,
+ "startTime": "2024-08-15T21:13:27.729Z",
+ "attachments": []
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "ae6d8ccd909362ca9a3d-9b41d4ab3bf8ed6fb360",
+ "file": "components/Button.test.ts",
+ "line": 7,
+ "column": 7
+ },
+ {
+ "title": "axe를 사용하여 자동 접근성 테스트",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 30000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "firefox",
+ "projectName": "firefox",
+ "results": [
+ {
+ "workerIndex": 1,
+ "status": "passed",
+ "duration": 917,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "startTime": "2024-08-15T21:13:30.511Z",
+ "attachments": []
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "ae6d8ccd909362ca9a3d-80d45ed3fa2dbcc14d45",
+ "file": "components/Button.test.ts",
+ "line": 15,
+ "column": 7
+ },
+ {
+ "title": "primary 시각적 회귀 테스트",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 30000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "webkit",
+ "projectName": "webkit",
+ "results": [
+ {
+ "workerIndex": 2,
+ "status": "passed",
+ "duration": 4167,
+ "errors": [],
+ "stdout": [
+ {
+ "text": "/home/runner/work/warrr-ui/warrr-ui/.playwright/snapshots/components/Button.test.ts-snapshots/Button-컴포넌트-primary-시각적-회귀-테스트-1-webkit-linux.png is re-generated, writing actual.\n"
+ }
+ ],
+ "stderr": [],
+ "retry": 0,
+ "startTime": "2024-08-15T21:13:32.377Z",
+ "attachments": []
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "ae6d8ccd909362ca9a3d-d425228141c78bf583f3",
+ "file": "components/Button.test.ts",
+ "line": 7,
+ "column": 7
+ },
+ {
+ "title": "axe를 사용하여 자동 접근성 테스트",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 30000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "webkit",
+ "projectName": "webkit",
+ "results": [
+ {
+ "workerIndex": 2,
+ "status": "passed",
+ "duration": 1028,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "startTime": "2024-08-15T21:13:38.735Z",
+ "attachments": []
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "ae6d8ccd909362ca9a3d-5120bb9063b0336c94ab",
+ "file": "components/Button.test.ts",
+ "line": 15,
+ "column": 7
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "errors": [],
+ "stats": {
+ "startTime": "2024-08-15T21:13:24.566Z",
+ "duration": 15243.547,
+ "expected": 6,
+ "skipped": 0,
+ "unexpected": 0,
+ "flaky": 0
+ }
+}
\ No newline at end of file
diff --git a/.playwright/results/.last-run.json b/.playwright/results/.last-run.json
new file mode 100644
index 00000000..cbcc1fba
--- /dev/null
+++ b/.playwright/results/.last-run.json
@@ -0,0 +1,4 @@
+{
+ "status": "passed",
+ "failedTests": []
+}
\ No newline at end of file
diff --git "a/.playwright/results/components-Button-Button-\354\273\264\355\217\254\353\204\214\355\212\270-primary-\354\213\234\352\260\201\354\240\201-\355\232\214\352\267\200-\355\205\214\354\212\244\355\212\270-chromium/Button-\354\273\264\355\217\254\353\204\214\355\212\270-primary-\354\213\234\352\260\201\354\240\201-\355\232\214\352\267\200-\355\205\214\354\212\244\355\212\270-1-actual.png" "b/.playwright/results/components-Button-Button-\354\273\264\355\217\254\353\204\214\355\212\270-primary-\354\213\234\352\260\201\354\240\201-\355\232\214\352\267\200-\355\205\214\354\212\244\355\212\270-chromium/Button-\354\273\264\355\217\254\353\204\214\355\212\270-primary-\354\213\234\352\260\201\354\240\201-\355\232\214\352\267\200-\355\205\214\354\212\244\355\212\270-1-actual.png"
new file mode 100644
index 00000000..5a25c681
Binary files /dev/null and "b/.playwright/results/components-Button-Button-\354\273\264\355\217\254\353\204\214\355\212\270-primary-\354\213\234\352\260\201\354\240\201-\355\232\214\352\267\200-\355\205\214\354\212\244\355\212\270-chromium/Button-\354\273\264\355\217\254\353\204\214\355\212\270-primary-\354\213\234\352\260\201\354\240\201-\355\232\214\352\267\200-\355\205\214\354\212\244\355\212\270-1-actual.png" differ
diff --git "a/.playwright/results/components-Button-Button-\354\273\264\355\217\254\353\204\214\355\212\270-primary-\354\213\234\352\260\201\354\240\201-\355\232\214\352\267\200-\355\205\214\354\212\244\355\212\270-firefox/Button-\354\273\264\355\217\254\353\204\214\355\212\270-primary-\354\213\234\352\260\201\354\240\201-\355\232\214\352\267\200-\355\205\214\354\212\244\355\212\270-1-actual.png" "b/.playwright/results/components-Button-Button-\354\273\264\355\217\254\353\204\214\355\212\270-primary-\354\213\234\352\260\201\354\240\201-\355\232\214\352\267\200-\355\205\214\354\212\244\355\212\270-firefox/Button-\354\273\264\355\217\254\353\204\214\355\212\270-primary-\354\213\234\352\260\201\354\240\201-\355\232\214\352\267\200-\355\205\214\354\212\244\355\212\270-1-actual.png"
new file mode 100644
index 00000000..ecd6556b
Binary files /dev/null and "b/.playwright/results/components-Button-Button-\354\273\264\355\217\254\353\204\214\355\212\270-primary-\354\213\234\352\260\201\354\240\201-\355\232\214\352\267\200-\355\205\214\354\212\244\355\212\270-firefox/Button-\354\273\264\355\217\254\353\204\214\355\212\270-primary-\354\213\234\352\260\201\354\240\201-\355\232\214\352\267\200-\355\205\214\354\212\244\355\212\270-1-actual.png" differ
diff --git "a/.playwright/results/components-Button-Button-\354\273\264\355\217\254\353\204\214\355\212\270-primary-\354\213\234\352\260\201\354\240\201-\355\232\214\352\267\200-\355\205\214\354\212\244\355\212\270-webkit/Button-\354\273\264\355\217\254\353\204\214\355\212\270-primary-\354\213\234\352\260\201\354\240\201-\355\232\214\352\267\200-\355\205\214\354\212\244\355\212\270-1-actual.png" "b/.playwright/results/components-Button-Button-\354\273\264\355\217\254\353\204\214\355\212\270-primary-\354\213\234\352\260\201\354\240\201-\355\232\214\352\267\200-\355\205\214\354\212\244\355\212\270-webkit/Button-\354\273\264\355\217\254\353\204\214\355\212\270-primary-\354\213\234\352\260\201\354\240\201-\355\232\214\352\267\200-\355\205\214\354\212\244\355\212\270-1-actual.png"
new file mode 100644
index 00000000..3321bdd3
Binary files /dev/null and "b/.playwright/results/components-Button-Button-\354\273\264\355\217\254\353\204\214\355\212\270-primary-\354\213\234\352\260\201\354\240\201-\355\232\214\352\267\200-\355\205\214\354\212\244\355\212\270-webkit/Button-\354\273\264\355\217\254\353\204\214\355\212\270-primary-\354\213\234\352\260\201\354\240\201-\355\232\214\352\267\200-\355\205\214\354\212\244\355\212\270-1-actual.png" differ
diff --git "a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-\354\273\264\355\217\254\353\204\214\355\212\270-primary-\354\213\234\352\260\201\354\240\201-\355\232\214\352\267\200-\355\205\214\354\212\244\355\212\270-1-chromium-linux.png" "b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-\354\273\264\355\217\254\353\204\214\355\212\270-primary-\354\213\234\352\260\201\354\240\201-\355\232\214\352\267\200-\355\205\214\354\212\244\355\212\270-1-chromium-linux.png"
new file mode 100644
index 00000000..5a25c681
Binary files /dev/null and "b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-\354\273\264\355\217\254\353\204\214\355\212\270-primary-\354\213\234\352\260\201\354\240\201-\355\232\214\352\267\200-\355\205\214\354\212\244\355\212\270-1-chromium-linux.png" differ
diff --git "a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-\354\273\264\355\217\254\353\204\214\355\212\270-primary-\354\213\234\352\260\201\354\240\201-\355\232\214\352\267\200-\355\205\214\354\212\244\355\212\270-1-firefox-linux.png" "b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-\354\273\264\355\217\254\353\204\214\355\212\270-primary-\354\213\234\352\260\201\354\240\201-\355\232\214\352\267\200-\355\205\214\354\212\244\355\212\270-1-firefox-linux.png"
new file mode 100644
index 00000000..ecd6556b
Binary files /dev/null and "b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-\354\273\264\355\217\254\353\204\214\355\212\270-primary-\354\213\234\352\260\201\354\240\201-\355\232\214\352\267\200-\355\205\214\354\212\244\355\212\270-1-firefox-linux.png" differ
diff --git "a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-\354\273\264\355\217\254\353\204\214\355\212\270-primary-\354\213\234\352\260\201\354\240\201-\355\232\214\352\267\200-\355\205\214\354\212\244\355\212\270-1-webkit-linux.png" "b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-\354\273\264\355\217\254\353\204\214\355\212\270-primary-\354\213\234\352\260\201\354\240\201-\355\232\214\352\267\200-\355\205\214\354\212\244\355\212\270-1-webkit-linux.png"
new file mode 100644
index 00000000..3321bdd3
Binary files /dev/null and "b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-\354\273\264\355\217\254\353\204\214\355\212\270-primary-\354\213\234\352\260\201\354\240\201-\355\232\214\352\267\200-\355\205\214\354\212\244\355\212\270-1-webkit-linux.png" differ
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 00000000..3250cb23
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,5 @@
+turbo/
+pnpm-lock.yaml
+.playwright/report/index.html
+.playwright/results.json
+.playwright/results/.last-run.json
diff --git a/.prettierrc.json b/.prettierrc.json
index 3e38326e..1554e0e3 100644
--- a/.prettierrc.json
+++ b/.prettierrc.json
@@ -7,7 +7,7 @@
"printWidth": 100,
"arrowParens": "always",
"bracketSpacing": true,
- "jsxBracketSameLine": false,
+ "bracketSameLine": false,
"jsxSingleQuote": false,
"quoteProps": "as-needed"
}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 1e0fc28e..51beef4e 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -11,5 +11,6 @@
"source.fixAll.eslint": "explicit",
"source.organizeImports": "explicit"
},
+ "prettier.requireConfig": true,
"typescript.tsdk": "node_modules/typescript/lib"
}
diff --git a/e2e/components/Button.test.ts b/e2e/components/Button.test.ts
new file mode 100644
index 00000000..32b086a4
--- /dev/null
+++ b/e2e/components/Button.test.ts
@@ -0,0 +1,23 @@
+import { Page, expect, test } from "@playwright/test";
+
+import { axeAccessibilityScan } from "../test-utils/a11y";
+import { visit } from "../test-utils/storybook";
+
+test.describe("Button 컴포넌트", () => {
+ test("primary 시각적 회귀 테스트", async ({ page }: { page: Page }) => {
+ await visit(page, {
+ id: "example-button--primary",
+ });
+
+ await expect(page).toHaveScreenshot();
+ });
+
+ test("axe를 사용하여 자동 접근성 테스트", async ({ page }: { page: Page }) => {
+ await visit(page, {
+ id: "example-button--primary",
+ });
+
+ const accessibilityScanResults = await axeAccessibilityScan(page);
+ expect(accessibilityScanResults.violations).toEqual([]);
+ });
+});
diff --git a/e2e/test-utils/a11y.ts b/e2e/test-utils/a11y.ts
new file mode 100644
index 00000000..6063a1ac
--- /dev/null
+++ b/e2e/test-utils/a11y.ts
@@ -0,0 +1,12 @@
+import AxeBuilder from "@axe-core/playwright";
+import { Page } from "@playwright/test";
+import { AxeResults } from "axe-core";
+
+export async function axeAccessibilityScan(page: Page): Promise {
+ const accessibilityScanResults = await new AxeBuilder({ page })
+ .withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"])
+ .disableRules(["color-contrast"])
+ .analyze();
+
+ return accessibilityScanResults;
+}
diff --git a/e2e/test-utils/storybook.ts b/e2e/test-utils/storybook.ts
new file mode 100644
index 00000000..d1241f9b
--- /dev/null
+++ b/e2e/test-utils/storybook.ts
@@ -0,0 +1,77 @@
+import { Page } from "@playwright/test";
+
+type Value =
+ | string
+ | boolean
+ | number
+ | {
+ [Key: string]: Value;
+ };
+
+interface Options {
+ id: string;
+ args?: Record;
+ globals?: Record;
+}
+
+const { STORYBOOK_URL = "http://localhost:6006" } = process.env;
+
+export async function visit(page: Page, options: Options) {
+ const { id, args, globals } = options;
+ const url = process.env.CI
+ ? new URL(`${STORYBOOK_URL}/iframe`)
+ : new URL(`${STORYBOOK_URL}/iframe.html`);
+
+ url.searchParams.set("id", id);
+ url.searchParams.set("viewMode", "story");
+
+ if (args) {
+ const serializedArgs = Object.entries(args)
+ .map(([key, value]) => `${key}:${value}`)
+ .join(";");
+ url.searchParams.set("args", serializedArgs);
+ }
+
+ if (globals) {
+ let params = "";
+ for (const [key, value] of Object.entries(globals)) {
+ if (params !== "") {
+ params += ";";
+ }
+ if (typeof value === "object") {
+ params += serializeObject(value, key);
+ } else {
+ params += `${key}:${value}`;
+ }
+ }
+ url.searchParams.set("globals", params);
+ }
+
+ await page.goto(url.toString());
+
+ // 스토리북이 로딩될 때까지 대기하여야 playwright 테스트 가능
+ await page.waitForSelector("body.sb-show-main:not(.sb-show-preparing-story)");
+ await page.waitForSelector("#storybook-root > *");
+}
+
+function serializeObject(
+ object: T,
+ parentPath: string
+): string {
+ return Object.entries(object)
+ .map(([key, value]) => {
+ if (typeof value === "object") {
+ return serializeObject(value, `${parentPath}.${key}`);
+ }
+ return `${parentPath}.${key}:${serialize(value)}`;
+ })
+ .join(";");
+}
+
+function serialize(value: Value): string {
+ if (typeof value === "boolean") {
+ return `!${value}`;
+ }
+
+ return `${value}`;
+}
diff --git a/jest.config.ts b/jest.config.ts
new file mode 100644
index 00000000..fc8044df
--- /dev/null
+++ b/jest.config.ts
@@ -0,0 +1,10 @@
+import type { Config } from "jest";
+
+const config: Config = {
+ testEnvironment: "jsdom",
+ preset: "ts-jest",
+ testMatch: ["/packages/**/?(*.)+(spec|test).[jt]s?(x)"],
+ setupFilesAfterEnv: ["/jest.setup.ts"],
+};
+
+export default config;
diff --git a/jest.setup.ts b/jest.setup.ts
new file mode 100644
index 00000000..8087db30
--- /dev/null
+++ b/jest.setup.ts
@@ -0,0 +1,2 @@
+import "@testing-library/dom";
+import "@testing-library/jest-dom";
diff --git a/package.json b/package.json
index 5b489b65..b5666479 100644
--- a/package.json
+++ b/package.json
@@ -1,29 +1,51 @@
{
- "name": "warrr-ui",
+ "name": "@warrr-ui",
"private": true,
"scripts": {
"build": "turbo build",
"dev": "turbo dev",
- "prettier": "exec prettier . --write",
- "lint": "eslint . --ext .js,.jsx,.ts,.tsx --fix"
+ "format": "prettier . --write --cache",
+ "lint": "eslint . --ext .ts,.tsx --fix --cache",
+ "turbo:gen": "turbo gen",
+ "prepare": "husky",
+ "e2e": "playwright test",
+ "e2e:update": "playwright test --update-snapshots",
+ "test": "jest --verbose --config ./jest.config.ts"
},
"devDependencies": {
+ "@axe-core/playwright": "^4.9.1",
+ "@playwright/test": "^1.46.0",
+ "@testing-library/dom": "^10.4.0",
+ "@testing-library/jest-dom": "^6.5.0",
+ "@testing-library/react": "^16.0.0",
+ "@testing-library/user-event": "^14.5.2",
+ "@turbo/gen": "^2.0.9",
+ "@types/jest": "^29.5.12",
+ "@types/node": "^20.14.9",
+ "@types/react": "^18.3.3",
+ "@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^6.12.0",
"@typescript-eslint/parser": "^6.12.0",
+ "axe-core": "^4.10.0",
"eslint": "8.57.0",
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-react": "^7.34.3",
"eslint-plugin-react-hooks": "^4.6.2",
+ "husky": "^9.1.3",
+ "jest": "^29.7.0",
+ "jest-environment-jsdom": "^29.7.0",
+ "lint-staged": "^15.2.7",
"prettier": "^3.2.5",
+ "ts-jest": "^29.2.3",
+ "ts-node": "^10.9.2",
+ "tsup": "^8.2.1",
"turbo": "^2.0.4",
- "typescript": "^5.4.5",
- "@types/node": "^20.14.9",
- "@types/react": "^18.3.3",
- "@types/react-dom": "^18.3.0"
+ "typescript": "^5.4.5"
},
"packageManager": "pnpm@8.15.6",
"engines": {
- "node": ">=18"
+ "node": ">=18",
+ "pnpm": "8.15.6"
}
}
diff --git a/packages/plugins/boilerplate/figma.manifest.ts b/packages/plugins/boilerplate/src/config/figma.manifest.ts
similarity index 100%
rename from packages/plugins/boilerplate/figma.manifest.ts
rename to packages/plugins/boilerplate/src/config/figma.manifest.ts
diff --git a/packages/plugins/boilerplate/injectBundledJsIntoHTML.ts b/packages/plugins/boilerplate/src/config/injectBundledJsIntoHTML.ts
similarity index 100%
rename from packages/plugins/boilerplate/injectBundledJsIntoHTML.ts
rename to packages/plugins/boilerplate/src/config/injectBundledJsIntoHTML.ts
diff --git a/packages/plugins/boilerplate/tsconfig.json b/packages/plugins/boilerplate/tsconfig.json
index 2d4a552d..c108e49b 100644
--- a/packages/plugins/boilerplate/tsconfig.json
+++ b/packages/plugins/boilerplate/tsconfig.json
@@ -5,5 +5,6 @@
"typeRoots": ["./node_modules/@types", "./node_modules/@figma"],
"skipLibCheck": true,
"allowSyntheticDefaultImports": true
- }
+ },
+ "include": ["src", "vite.config.ts"]
}
diff --git a/packages/plugins/boilerplate/vite.config.ts b/packages/plugins/boilerplate/vite.config.ts
index 2c2e4ab9..6a04c9e2 100644
--- a/packages/plugins/boilerplate/vite.config.ts
+++ b/packages/plugins/boilerplate/vite.config.ts
@@ -4,8 +4,8 @@ import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
import generateFile from "vite-plugin-generate-file";
-import manifest from "./figma.manifest";
-import { injectBundledJsIntoHTML } from "./injectBundledJsIntoHTML";
+import manifest from "./src/config/figma.manifest";
+import { injectBundledJsIntoHTML } from "./src/config/injectBundledJsIntoHTML";
export default defineConfig({
plugins: [
diff --git a/packages/primitive/components/Button.test.tsx b/packages/primitive/components/Button.test.tsx
new file mode 100644
index 00000000..15284344
--- /dev/null
+++ b/packages/primitive/components/Button.test.tsx
@@ -0,0 +1,17 @@
+import { render } from "@testing-library/react";
+
+import { Button } from "./Button";
+
+describe("버튼 컴포넌트 테스트", () => {
+ it("렌더링이 올바르게 되는지 확인합니다.", () => {
+ const wrapper = render();
+
+ expect(() => wrapper.unmount()).not.toThrow();
+ });
+
+ it("rtl을 사용해 렌더링이 올바르게 되는지 확인합니다.", () => {
+ const wrapper = render();
+
+ expect(wrapper.getByText("Button")).toBeInTheDocument();
+ });
+});
diff --git a/packages/primitive/components/Button.tsx b/packages/primitive/components/Button.tsx
index c6e4a837..8d61c627 100644
--- a/packages/primitive/components/Button.tsx
+++ b/packages/primitive/components/Button.tsx
@@ -1,5 +1,3 @@
-import "./button.css";
-
interface ButtonProps {
/**
* Is this the principal call to action on the page?
@@ -34,13 +32,14 @@ export const Button = ({
...props
}: ButtonProps) => {
const mode = primary ? "storybook-button--primary" : "storybook-button--secondary";
+
return (