diff --git a/.github/workflows/autobranch.yml b/.github/workflows/autobranch.yml index d460a975..2076b178 100644 --- a/.github/workflows/autobranch.yml +++ b/.github/workflows/autobranch.yml @@ -1,4 +1,4 @@ -name: 자동 브랜치 생성 +name: Auto Create Branch on: issues: @@ -47,11 +47,12 @@ jobs: echo "추출된 브랜치 타입: $BRANCH_TYPE" - # 이슈 제목 생성 ([TYPE] #번호 - 제목 형식) + # 이슈 제목 생성 ([Type] #번호 - 제목 형식) if [ -n "$BRANCH_TYPE" ] && [ -n "$ISSUE_TITLE" ]; then if [[ ! "$ISSUE_TITLE" =~ ^\[.*\] ]]; then - BRANCH_TYPE_UPPER=$(echo "$BRANCH_TYPE" | tr '[:lower:]' '[:upper:]') - NEW_ISSUE_TITLE="[${BRANCH_TYPE_UPPER}] #${{ github.event.issue.number }} - ${ISSUE_TITLE}" + # 파스칼케이스 변환 + BRANCH_TYPE_PASCAL=$(echo "$BRANCH_TYPE" | sed 's/\b\(.\)/\u\1/') + NEW_ISSUE_TITLE="[${BRANCH_TYPE_PASCAL}] #${{ github.event.issue.number }} - ${ISSUE_TITLE}" echo "새로운 이슈 제목: $NEW_ISSUE_TITLE" else NEW_ISSUE_TITLE="$ISSUE_TITLE" diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 0893a1bf..c3b4b7e6 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -18,15 +18,15 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - name: Enable Corepack + run: corepack enable + - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 22 cache: 'yarn' - - name: Enable Corepack - run: corepack enable - - name: Set Yarn version run: corepack prepare yarn@4.10.3 --activate @@ -63,6 +63,10 @@ jobs: run: yarn format:check continue-on-error: true + - name: Run tests + id: test + run: yarn test --run + - name: Build app id: build run: yarn vite build --mode development @@ -130,6 +134,14 @@ jobs: comment += '❌ **Prettier**: 포맷 필요\n'; } + // 테스트 결과 + const testStatus = '${{ steps.test.outcome }}'; + if (testStatus === 'success') { + comment += '✅ **Test**: 통과\n'; + } else { + comment += '❌ **Test**: 실패\n'; + } + // 빌드 결과 const buildStatus = '${{ steps.build.outcome }}'; if (buildStatus === 'success') { diff --git a/package.json b/package.json index 01a4e7ac..83029a79 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "private": true, "version": "0.0.0", "type": "module", + "packageManager": "yarn@4.10.3", "scripts": { "dev": "vite", "build": "tsc -b && vite build", diff --git a/src/shared/ui/Button/Button.stories.tsx b/src/shared/ui/Button/Button.stories.tsx new file mode 100644 index 00000000..7fde718a --- /dev/null +++ b/src/shared/ui/Button/Button.stories.tsx @@ -0,0 +1,51 @@ +import { Button } from '@shared/ui/Button/Button'; +import type { Meta, StoryObj } from '@storybook/react-vite'; + +const meta = { + title: 'Shared/UI/Button', + component: Button, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + variant: { + control: 'select', + options: ['fill', 'outline'], + description: 'Button variant style', + }, + children: { + control: 'text', + description: 'Button text content', + }, + disabled: { + control: 'boolean', + description: 'Disabled state', + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Fill: Story = { + args: { + variant: 'fill', + children: 'Button', + }, +}; + +export const Outline: Story = { + args: { + variant: 'outline', + children: 'Button', + }, +}; + +export const Disabled: Story = { + args: { + variant: 'fill', + children: 'Button', + disabled: true, + }, +}; diff --git a/src/shared/ui/Button/Button.tsx b/src/shared/ui/Button/Button.tsx new file mode 100644 index 00000000..600b59e2 --- /dev/null +++ b/src/shared/ui/Button/Button.tsx @@ -0,0 +1,13 @@ +import { buttonVariants } from '@shared/ui/Button/Button.variants'; +import type { ComponentPropsWithoutRef } from 'react'; +import type { VariantProps } from 'tailwind-variants'; + +export type ButtonProps = ComponentPropsWithoutRef<'button'> & VariantProps; + +export const Button = ({ children, variant, className, ...props }: ButtonProps) => { + return ( + + ); +}; diff --git a/src/shared/ui/Button/Button.variants.ts b/src/shared/ui/Button/Button.variants.ts new file mode 100644 index 00000000..105631b6 --- /dev/null +++ b/src/shared/ui/Button/Button.variants.ts @@ -0,0 +1,23 @@ +import { tv } from 'tailwind-variants'; + +export const buttonVariants = tv({ + base: [ + 'flex items-center justify-center', + 'h-[44px]', + 'px-[24px]', + 'py-[--padding-m]', + 'gap-[--spacing-xxs]', + 'rounded-[--radius-l]', + 'typo-body-1', + 'transition-colors', + ], + variants: { + variant: { + fill: ['bg-gray-900 text-white'], + outline: ['border-2 border-gray-900 text-gray-900'], + }, + }, + defaultVariants: { + variant: 'fill', + }, +}); diff --git a/src/shared/utils/.gitkeep b/src/shared/utils/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/src/shared/utils/cn.ts b/src/shared/utils/cn.ts new file mode 100644 index 00000000..37244999 --- /dev/null +++ b/src/shared/utils/cn.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +export const cn = (...inputs: ClassValue[]) => { + return twMerge(clsx(inputs)); +}; diff --git a/src/shared/utils/index.ts b/src/shared/utils/index.ts new file mode 100644 index 00000000..414c24df --- /dev/null +++ b/src/shared/utils/index.ts @@ -0,0 +1 @@ +export { cn } from './cn'; diff --git a/tests/unit/shared/ui/Button/Button.test.tsx b/tests/unit/shared/ui/Button/Button.test.tsx new file mode 100644 index 00000000..33d91bac --- /dev/null +++ b/tests/unit/shared/ui/Button/Button.test.tsx @@ -0,0 +1,27 @@ +import { Button } from '@shared/ui/Button/Button'; +import { render, screen } from '@testing-library/react'; + +describe('Button', () => { + it('renders children text', () => { + render(); + expect(screen.getByText('Click me')).toBeInTheDocument(); + }); + + it('renders with fill variant by default', () => { + render(); + const button = screen.getByRole('button'); + expect(button).toBeInTheDocument(); + }); + + it('renders with outline variant', () => { + render(); + const button = screen.getByRole('button'); + expect(button).toBeInTheDocument(); + }); + + it('handles disabled state', () => { + render(); + const button = screen.getByRole('button'); + expect(button).toBeDisabled(); + }); +}); diff --git a/tsconfig.app.json b/tsconfig.app.json index b4350817..f45bb476 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -1,6 +1,8 @@ { "compilerOptions": { + "composite": true, "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "noEmit": true, "target": "ES2022", "useDefineForClassFields": true, "lib": ["ES2022", "DOM", "DOM.Iterable"], @@ -11,7 +13,6 @@ "allowImportingTsExtensions": true, "verbatimModuleSyntax": true, "moduleDetection": "force", - "noEmit": true, "jsx": "react-jsx", "resolveJsonModule": true, "isolatedModules": true, diff --git a/tsconfig.json b/tsconfig.json index d32ff682..01490aab 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,4 +1,8 @@ { "files": [], - "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }] + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" }, + { "path": "./tsconfig.test.json" } + ] } diff --git a/tsconfig.test.json b/tsconfig.test.json new file mode 100644 index 00000000..e6f8b388 --- /dev/null +++ b/tsconfig.test.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.app.json", + "compilerOptions": { + "composite": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.test.tsbuildinfo", + "noEmit": true, + "types": ["vitest/globals", "@testing-library/jest-dom"] + }, + "include": ["tests/**/*", "src/**/*"] +} diff --git a/vitest.config.ts b/vitest.config.ts index 30c1377e..b17f9a38 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -15,6 +15,7 @@ export default defineConfig({ environment: 'jsdom', setupFiles: './src/setupTests.ts', css: true, + include: ['tests/**/*.{test,spec}.{js,ts,jsx,tsx}'], }, resolve: { alias: {