-
Notifications
You must be signed in to change notification settings - Fork 0
[feat/test deploy] - 1차 배포 테스트 pr #24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,107 @@ | ||||||
| name: CI & Release (pnpm, secure + optimized) | ||||||
|
|
||||||
| on: | ||||||
| push: | ||||||
| branches: [main] | ||||||
| tags: | ||||||
| - 'v*' # v로 시작하는 태그가 푸시될 때 (예: v1.0.0) | ||||||
| pull_request: | ||||||
|
|
||||||
| jobs: | ||||||
| test: | ||||||
| runs-on: ubuntu-latest | ||||||
| steps: | ||||||
| - uses: actions/checkout@v3 | ||||||
| - uses: actions/setup-node@v3 | ||||||
| with: | ||||||
| node-version: 18 | ||||||
| - run: corepack enable | ||||||
| - name: Cache pnpm store | ||||||
| uses: actions/cache@v3 | ||||||
| with: | ||||||
| path: ~/.pnpm-store | ||||||
| key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} | ||||||
| - run: pnpm install --frozen-lockfile | ||||||
| - run: pnpm test --if-present | ||||||
|
|
||||||
| release: | ||||||
| needs: test | ||||||
| if: startsWith(github.ref, 'refs/tags/v') | ||||||
| runs-on: ubuntu-latest | ||||||
| steps: | ||||||
| - uses: actions/checkout@v3 | ||||||
| - uses: actions/setup-node@v3 | ||||||
| with: | ||||||
| node-version: 18 | ||||||
| registry-url: 'https://registry.npmjs.org' | ||||||
| - run: corepack enable | ||||||
| - name: Cache pnpm store | ||||||
| uses: actions/cache@v3 | ||||||
| with: | ||||||
| path: ~/.pnpm-store | ||||||
| key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} | ||||||
| - run: pnpm install --frozen-lockfile | ||||||
| - run: pnpm build | ||||||
|
|
||||||
| - name: Configure npm auth | ||||||
| run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc | ||||||
|
|
||||||
| - name: Verify npm user | ||||||
| run: npm whoami | ||||||
| env: | ||||||
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | ||||||
|
|
||||||
| - name: Pack package for test | ||||||
| run: pnpm pack | ||||||
|
|
||||||
| - name: Test local install | ||||||
| run: npm install ./$(ls *.tgz | head -n 1) | ||||||
|
|
||||||
| - name: Publish to npm | ||||||
| run: pnpm publish --access public | ||||||
| env: | ||||||
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | ||||||
|
|
||||||
| # 배포 성공 후에만 Release 생성 | ||||||
| - name: Create GitHub Release | ||||||
| uses: actions/github-script@v7 | ||||||
| with: | ||||||
| script: | | ||||||
| const tagName = context.ref.replace('refs/tags/', ''); | ||||||
| try { | ||||||
| await github.rest.repos.createRelease({ | ||||||
| owner: context.repo.owner, | ||||||
| repo: context.repo.repo, | ||||||
| tag_name: tagName, | ||||||
| name: `Release ${tagName}`, | ||||||
| body: `🚀 Package successfully published to NPM!\n\n**Version:** ${tagName}\n**Package:** [recomponent](https://www.npmjs.com/package/recomponent)`, | ||||||
| draft: false, | ||||||
| prerelease: tagName.includes('-') | ||||||
| }); | ||||||
| console.log(`✅ Release ${tagName} created successfully!`); | ||||||
| } catch (error) { | ||||||
| if (error.status === 422) { | ||||||
| console.log(`⚠️ Release ${tagName} already exists`); | ||||||
| } else { | ||||||
| throw error; | ||||||
| } | ||||||
| } | ||||||
| # 이메일 알림 | ||||||
| - name: Send email | ||||||
| uses: dawidd6/action-send-mail@v3 | ||||||
| with: | ||||||
| server_address: smtp.gmail.com | ||||||
| server_port: 587 | ||||||
| secure: true | ||||||
| username: ${{ secrets.GMAIL_USERNAME }} | ||||||
| password: ${{ secrets.GMAIL_APP_PASSWORD }} | ||||||
| subject: 🚀 Release ${{ github.ref_name }} completed | ||||||
| body: | | ||||||
| Your package has been successfully published to NPM! | ||||||
| Version: ${{ github.ref_name }} | ||||||
| Package: recomponent | ||||||
|
||||||
| Package: recomponent | |
| Package: @ramong23/recomponent |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,39 @@ | ||
| { | ||
| "name": "xp-components", | ||
| "name": "@ramong23/recomponent", | ||
| "private": false, | ||
| "version": "0.0.0", | ||
| "version": "1.0.0", | ||
| "type": "module", | ||
| "main": "dist/index.js", | ||
| "module": "dist/index.mjs", | ||
| "types": "dist/index.d.ts", | ||
| "exports": { | ||
| ".": { | ||
| "import": "./dist/index.mjs", | ||
| "require": "./dist/index.js", | ||
| "types": "./dist/index.d.ts" | ||
| }, | ||
| "./styles": "./dist/index.css" | ||
| }, | ||
| "files": [ | ||
| "dist", | ||
| "README.md" | ||
| ], | ||
| "keywords": [ | ||
| "react", | ||
| "components", | ||
| "ui", | ||
| "typescript" | ||
| ], | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "https://github.com/ramong26/xp-components.git" | ||
| }, | ||
| "homepage": "https://github.com/ramong26/xp-components", | ||
| "bugs": { | ||
| "url": "https://github.com/ramong26/xp-components/issues" | ||
| }, | ||
|
Comment on lines
+27
to
+34
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. repository, homepage, bugs URL에 오타가 있는 것 같습니다. 사용자 이름이 "repository": {
"type": "git",
"url": "https://github.com/ramong23/xp-components.git"
},
"homepage": "https://github.com/ramong23/xp-components",
"bugs": {
"url": "https://github.com/ramong23/xp-components/issues"
} |
||
| "author": "ramong23", | ||
| "license": "MIT", | ||
| "scripts": { | ||
| "dev": "vite", | ||
| "build:lib": "tsup src/index.ts --dts --format esm,cjs --out-dir dist --clean", | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,4 +1,4 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { render, screen } from '@testing-library/react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { render, screen, act } from '@testing-library/react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import userEvent from '@testing-library/user-event'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { test, expect, vi } from 'vitest'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -30,34 +30,49 @@ const items = [ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| test('renders Carousel and 클릭', async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| render(<Carousel items={items} autoPlay={false} />); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const slide1 = screen.getByRole('heading', { name: /Slide 1/i }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const slide2 = screen.getByRole('heading', { name: /Slide 2/i }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const slide3 = screen.getByRole('heading', { name: /Slide 3/i }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await userEvent.click(slide1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const indicators = screen.getAllByRole('button'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(screen.getByRole('heading', { name: /Slide 1/i })).toBeInTheDocument(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await userEvent.click(screen.getByRole('heading', { name: /Slide 1/i })); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(items[0].onClick).toHaveBeenCalled(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await userEvent.click(slide2); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await userEvent.click(indicators[1]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(screen.getByRole('heading', { name: /Slide 2/i })).toBeInTheDocument(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await userEvent.click(screen.getByRole('heading', { name: /Slide 2/i })); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(items[1].onClick).toHaveBeenCalled(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await userEvent.click(slide3); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await userEvent.click(indicators[2]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(screen.getByRole('heading', { name: /Slide 3/i })).toBeInTheDocument(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await userEvent.click(screen.getByRole('heading', { name: /Slide 3/i })); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(items[2].onClick).toHaveBeenCalled(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(screen.getByRole('heading', { name: /Slide 1/i })).toBeInTheDocument(); | |
| await userEvent.click(screen.getByRole('heading', { name: /Slide 1/i })); | |
| expect(items[0].onClick).toHaveBeenCalled(); | |
| await userEvent.click(slide2); | |
| await userEvent.click(indicators[1]); | |
| expect(screen.getByRole('heading', { name: /Slide 2/i })).toBeInTheDocument(); | |
| await userEvent.click(screen.getByRole('heading', { name: /Slide 2/i })); | |
| expect(items[1].onClick).toHaveBeenCalled(); | |
| await userEvent.click(slide3); | |
| await userEvent.click(indicators[2]); | |
| expect(screen.getByRole('heading', { name: /Slide 3/i })).toBeInTheDocument(); | |
| await userEvent.click(screen.getByRole('heading', { name: /Slide 3/i })); | |
| expect(items[2].onClick).toHaveBeenCalled(); | |
| for (const [index, item] of items.entries()) { | |
| if (index > 0) { | |
| await userEvent.click(indicators[index]); | |
| } | |
| const slide = screen.getByRole('heading', { name: new RegExp(item.title, 'i') }); | |
| expect(slide).toBeInTheDocument(); | |
| await userEvent.click(slide); | |
| expect(item.onClick).toHaveBeenCalled(); | |
| } |
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
자동 슬라이드 동작을 검증하는 테스트 코드에 반복되는 로직이 있습니다. act와 expect 호출이 여러 번 반복되는데, 이를 루프로 리팩토링하면 코드가 더 간결해지고 유지보수하기 쉬워집니다. 예를 들어, 캐러셀 아이템 개수가 변경되어도 테스트 코드를 수정할 필요가 없어집니다.
| await act(async () => { | |
| vi.advanceTimersByTime(1000); | |
| }); | |
| expect(indicators[0]).not.toHaveClass('active'); | |
| expect(indicators[1]).toHaveClass('active'); | |
| vi.advanceTimersByTime(1000); | |
| await act(async () => { | |
| vi.advanceTimersByTime(1000); | |
| }); | |
| expect(indicators[1]).not.toHaveClass('active'); | |
| expect(indicators[2]).toHaveClass('active'); | |
| vi.advanceTimersByTime(1000); | |
| await act(async () => { | |
| vi.advanceTimersByTime(1000); | |
| }); | |
| expect(indicators[2]).not.toHaveClass('active'); | |
| expect(indicators[0]).toHaveClass('active'); | |
| for (let i = 0; i < items.length; i++) { | |
| const currentIndex = i; | |
| const nextIndex = (i + 1) % items.length; | |
| await act(async () => { | |
| vi.advanceTimersByTime(1000); | |
| }); | |
| expect(indicators[currentIndex]).not.toHaveClass('active'); | |
| expect(indicators[nextIndex]).toHaveClass('active'); | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The package name in the NPM link should be '@ramong23/recomponent' to match the actual package name defined in package.json.