Skip to content
Merged
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
45 changes: 42 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,49 @@ jobs:
run: npm run type-check --if-present

- name: Build
run: npm run build --if-present
run: npm run build

- name: Test
run: npm test --if-present
- name: Unit & Component tests
run: npm test

- name: Upload coverage
if: matrix.node-version == '22.x'
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage/

e2e:
runs-on: ubuntu-latest
needs: build-and-lint
steps:
- uses: actions/checkout@v6

- name: Node.js
uses: actions/setup-node@v6
with:
node-version: 22.x
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Install Playwright browsers
run: npx playwright install --with-deps chromium

- name: Build
run: npm run build

- name: Run E2E tests
run: npx playwright test --project=chromium

- name: Upload Playwright report
uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 14

commitlint:
runs-on: ubuntu-latest
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@

# testing
/coverage
/playwright-report/
/e2e/test-results/
/test-results/

# next.js
/.next/
Expand Down
29 changes: 29 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Next.js frontend projesi. Bu dosya Claude Code icin proje kurallarini tanimlar.
- **Dil:** TypeScript 5 (strict mode)
- **Styling:** Tailwind CSS 4
- **Linting:** ESLint 9 (flat config) + Prettier
- **Test:** Vitest + React Testing Library (unit/component), Playwright (E2E)
- **Font:** Geist Sans / Geist Mono (`next/font/google`)

## Kod Kurallari
Expand Down Expand Up @@ -43,6 +44,32 @@ Next.js frontend projesi. Bu dosya Claude Code icin proje kurallarini tanimlar.
- Dosya adi: camelCase, `use` prefix (`useAuth.ts`, `useDebounce.ts`)
- Named export kullan

## Test Kurallari

### Unit & Component Testleri (Vitest + React Testing Library)
- Test dosyalari: `__tests__/` klasorunde, `.test.tsx` uzantisi
- Component testlerinde `@testing-library/react` kullan — DOM query'leri icin `screen` kullan
- Kullanici etkilesimi icin `@testing-library/user-event` kullan (`fireEvent` degil)
- Her component icin en az: render, interaction ve edge case testleri yaz
- Mock'lar icin `vi.fn()` ve `vi.mock()` kullan
- `npm test` ile calistir, `npm run test:watch` ile izleme modunda calistir

### E2E Testleri (Playwright)
- Test dosyalari: `e2e/` klasorunde, `.spec.ts` uzantisi
- Locator'lar icin `getByRole`, `getByText` tercih et (CSS selector yerine)
- Her kritik kullanici akisi icin E2E testi yaz (navigasyon, form submit, auth)
- `npm run test:e2e` ile calistir, `npm run test:e2e:ui` ile gorsel modda calistir

### Test Dosya Yapisi
```
__tests__/ # Unit & component testleri
page.test.tsx # Sayfa testleri
components/ # Component testleri
Button.test.tsx
e2e/ # E2E testleri
home.spec.ts
```

## Klasor Yapisi

```
Expand All @@ -53,6 +80,8 @@ hooks/ # Custom React hook'lari
lib/ # Yardimci fonksiyonlar, config
types/ # Paylasilan TypeScript tipleri
public/ # Statik dosyalar
__tests__/ # Unit & component testleri (Vitest)
e2e/ # E2E testleri (Playwright)
```

## Kod Uretme Komutlari
Expand Down
43 changes: 43 additions & 0 deletions __tests__/components/Button.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
import Button from "@/components/Button";

describe("Button component", () => {
it("renders children text", () => {
render(<Button>Click me</Button>);
expect(screen.getByRole("button", { name: "Click me" })).toBeInTheDocument();
});

it("calls onClick when clicked", async () => {
const user = userEvent.setup();
const handleClick = vi.fn();

render(<Button onClick={handleClick}>Click me</Button>);
await user.click(screen.getByRole("button"));

expect(handleClick).toHaveBeenCalledOnce();
});

it("does not call onClick when disabled", async () => {
const user = userEvent.setup();
const handleClick = vi.fn();

render(<Button onClick={handleClick} disabled>Click me</Button>);
await user.click(screen.getByRole("button"));

expect(handleClick).not.toHaveBeenCalled();
});

it("applies primary variant by default", () => {
render(<Button>Primary</Button>);
const button = screen.getByRole("button");
expect(button.className).toContain("bg-foreground");
});

it("applies secondary variant when specified", () => {
render(<Button variant="secondary">Secondary</Button>);
const button = screen.getByRole("button");
expect(button.className).toContain("border");
});
});
21 changes: 21 additions & 0 deletions __tests__/page.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import Home from "@/app/page";

describe("Home page", () => {
it("renders the heading", () => {
render(<Home />);
expect(screen.getByRole("heading", { level: 1 })).toBeInTheDocument();
});

it("renders the Next.js logo", () => {
render(<Home />);
expect(screen.getByAltText("Next.js logo")).toBeInTheDocument();
});

it("renders Deploy Now and Documentation links", () => {
render(<Home />);
expect(screen.getByText("Deploy Now")).toBeInTheDocument();
expect(screen.getByText("Documentation")).toBeInTheDocument();
});
});
32 changes: 32 additions & 0 deletions components/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"use client";

interface ButtonProps {
children: React.ReactNode;
onClick?: () => void;
variant?: "primary" | "secondary";
disabled?: boolean;
}

export default function Button({
children,
onClick,
variant = "primary",
disabled = false,
}: ButtonProps) {
const base = "flex h-12 items-center justify-center rounded-full px-5 font-medium transition-colors";
const variants = {
primary: "bg-foreground text-background hover:bg-[#383838] dark:hover:bg-[#ccc]",
secondary:
"border border-black/[.08] hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a]",
};

return (
<button
className={`${base} ${variants[variant]}`}
onClick={onClick}
disabled={disabled}
>
{children}
</button>
);
}
27 changes: 27 additions & 0 deletions e2e/home.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { test, expect } from "@playwright/test";

test.describe("Home page", () => {
test("should load and display heading", async ({ page }) => {
await page.goto("/");
await expect(page.getByRole("heading", { level: 1 })).toBeVisible();
});

test("should have correct page title", async ({ page }) => {
await page.goto("/");
await expect(page).toHaveTitle(/Create Next App/);
});

test("should render Deploy Now link pointing to Vercel", async ({ page }) => {
await page.goto("/");
const deployLink = page.getByRole("link", { name: /Deploy Now/ });
await expect(deployLink).toBeVisible();
await expect(deployLink).toHaveAttribute("href", /vercel\.com/);
});

test("should render Documentation link pointing to Next.js docs", async ({ page }) => {
await page.goto("/");
const docsLink = page.getByRole("link", { name: "Documentation" });
await expect(docsLink).toBeVisible();
await expect(docsLink).toHaveAttribute("href", /nextjs\.org\/docs/);
});
});
Loading
Loading