Skip to content

Configuration Laravel 12 + React + Tests

1. Architecture d'ensemble

graph TB
    %% Frontend Layer
    subgraph Frontend["🎨 Frontend Layer"]
        React["React + TypeScript"]
        Shadcn["shadcn/ui Components"]
        TanStack["TanStack Query"]
        React --> Shadcn
        React --> TanStack
    end

    %% Backend Layer
    subgraph Backend["⚙️ Backend Layer"]
        Laravel["Laravel 12 API"]
        Models["Models + Eloquent"]
        DB[(Database)]
        Laravel --> Models
        Models --> DB
    end

    %% Testing Layer
    subgraph TestingPHP["🧪 Tests PHP (Pest)"]
        UnitPHP["Unit Tests<br/>Models/Services"]
        FeaturePHP["Feature Tests<br/>API + DB"]
        DuskTests["Dusk Tests<br/>E2E Browser"]
    end

    subgraph TestingJS["🧪 Tests JS (Vitest)"]
        UnitJS["Unit Tests<br/>Components"]
        Integration["Integration Tests<br/>Frontend ↔ API"]
    end

    %% Data Flow
    Frontend -->|HTTP API| Backend
    Backend -->|JSON| Frontend

    %% Testing Relationships
    UnitPHP -.->|Tests| Models
    FeaturePHP -.->|Tests| Laravel
    DuskTests -.->|Tests| Frontend
    DuskTests -.->|Tests| Backend
    UnitJS -.->|Tests| React
    Integration -.->|Tests| Frontend
    Integration -.->|Tests| Backend

    %% Build Tools
    subgraph BuildTools["🔧 Build & Dev Tools"]
        Vite["Vite<br/>Dev Server"]
        Tailwind["Tailwind CSS"]
        TypeScript["TypeScript"]
    end

    Frontend --> BuildTools

    %% Styling
    classDef frontend fill:#61dafb,stroke:#333,stroke-width:2px,color:#000
    classDef backend fill:#ff2d20,stroke:#333,stroke-width:2px,color:#fff
    classDef testing fill:#10b981,stroke:#333,stroke-width:2px,color:#fff
    classDef tools fill:#f59e0b,stroke:#333,stroke-width:2px,color:#000

    class Frontend,React,Shadcn,TanStack frontend
    class Backend,Laravel,Models,DB backend
    class TestingPHP,TestingJS,UnitPHP,FeaturePHP,DuskTests,UnitJS,Integration testing
    class BuildTools,Vite,Tailwind,TypeScript tools

2. Structure du projet avec tous les types de tests

mon-app/
├── app/                    # Laravel backend
├── resources/
│   └── js/                # React frontend
│       ├── components/
│       ├── pages/
│       ├── hooks/
│       ├── services/
│       ├── types/
│       ├── __tests__/     # Tests unitaires React/JS (Vitest)
│       │   ├── components/
│       │   ├── hooks/
│       │   └── utils/
│       └── test-setup.ts
├── tests/                 # Tests Laravel (Pest/PHP)
│   ├── Feature/           # Tests d'intégration Laravel (API + DB)
│   ├── Unit/              # Tests unitaires Laravel
│   ├── Browser/           # Tests E2E avec Laravel Dusk
│   │   ├── UserCrudTest.php
│   │   ├── DataTableTest.php
│   │   └── Pages/
│   │       └── UserPage.php
│   └── TestCase.php
├── e2e/                   # Alternative : Tests E2E (Playwright) - OPTIONNEL
│   ├── tests/
│   ├── playwright.config.ts
│   └── test-setup.ts
├── integration/           # Tests d'intégration frontend-backend
│   ├── api-integration.test.ts
│   ├── auth-flow.test.ts
│   └── data-sync.test.ts
├── database/
└── public/

Types de tests et leur emplacement

Tests unitaires : - Backend : /tests/Unit/ (Pest/PHP) - Frontend : /resources/js/__tests__/ (Vitest/JS)

Tests d'intégration : - Laravel : /tests/Feature/ (API + DB avec Pest) - Frontend-Backend : /integration/ (Vitest avec vraies API)

Tests E2E : - Playwright : /e2e/ (navigateur complet)

3. Installation et configuration

Backend Laravel + Dusk

# Créer le projet
composer create-project laravel/laravel mon-app
cd mon-app

# Installer Pest
composer require pestphp/pest --dev
composer require pestphp/pest-plugin-laravel --dev
./vendor/bin/pest --init

# Installer Laravel Dusk pour les tests E2E
composer require --dev laravel/dusk
php artisan dusk:install

# Configuration API
php artisan install:api

Frontend React + TypeScript

# Installer les dépendances React
npm install react react-dom @types/react @types/react-dom
npm install -D @vitejs/plugin-react typescript
npm install -D vitest @testing-library/react @testing-library/jest-dom
npm install -D @testing-library/user-event jsdom

# Gestion des données
npm install @tanstack/react-query axios
npm install -D @types/node

# UI avec shadcn/ui
npm install tailwindcss postcss autoprefixer
npm install class-variance-authority clsx tailwind-merge
npm install @radix-ui/react-slot
npm install lucide-react
npx tailwindcss init -p

# Tests d'intégration avec MSW (Mock Service Worker)
npm install -D msw

# Initialiser shadcn/ui
npx shadcn@latest init

# Composants essentiels pour applications d'entreprise
npx shadcn@latest add button input label table data-table form dialog select card badge skeleton toast

4. Configuration Vite (vite.config.js)

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import react from '@vitejs/plugin-react';

export default defineConfig({
    plugins: [
        laravel({
            input: ['resources/css/app.css', 'resources/js/app.tsx'],
            refresh: true,
        }),
        react(),
    ],
    test: {
        globals: true,
        environment: 'jsdom',
        setupFiles: ['./resources/js/test-setup.ts'],
        include: ['resources/js/**/*.{test,spec}.{js,ts,tsx}'],
        exclude: ['tests/**/*'], // Exclure les tests Laravel
    },
    resolve: {
        alias: {
            '@': resolve(__dirname, './resources/js'),
        },
    },
});

5. Test d'intégration Frontend-Backend

integration/user-api.test.ts
import { beforeAll, afterAll, describe, it, expect } from 'vitest';
import axios from 'axios';

// Configuration pour tests d'intégration avec vraie API
const api = axios.create({
  baseURL: 'http://localhost:8000/api',
  timeout: 5000,
});

describe('User API Integration', () => {
  beforeAll(async () => {
    // Setup : créer des données de test si nécessaire
    await api.post('/test/setup');
  });

  afterAll(async () => {
    // Cleanup : nettoyer les données de test
    await api.post('/test/cleanup');
  });

  it('should create and retrieve user via API', async () => {
    // Créer un utilisateur
    const userData = {
      name: 'Integration Test User',
      email: 'integration@test.com',
      password: 'password123'
    };

    const createResponse = await api.post('/users', userData);
    expect(createResponse.status).toBe(201);
    expect(createResponse.data.data.name).toBe(userData.name);

    // Récupérer l'utilisateur
    const userId = createResponse.data.data.id;
    const getResponse = await api.get(`/users/${userId}`);
    expect(getResponse.status).toBe(200);
    expect(getResponse.data.data.email).toBe(userData.email);
  });

  it('should handle pagination correctly', async () => {
    const response = await api.get('/users?page=1&per_page=10');

    expect(response.status).toBe(200);
    expect(response.data).toHaveProperty('data');
    expect(response.data).toHaveProperty('meta');
    expect(response.data.meta).toHaveProperty('current_page', 1);
    expect(response.data.meta).toHaveProperty('per_page', 10);
  });
});

5. Tests E2E avec Laravel Dusk

Page Object Pattern

tests/Browser/Pages/UserPage.php
<?php

namespace Tests\\Browser\\Pages;

use Laravel\\Dusk\\Browser;
use Laravel\\Dusk\\Page;

class UserPage extends Page
{
    public function url()
    {
        return '/users';
    }

    public function assert(Browser $browser)
    {
        $browser->assertPathIs($this->url())
                ->assertSee('Utilisateurs');
    }

    public function elements()
    {
        return [
            '@add-user-button' => 'button:contains(\"Ajouter un utilisateur\")',
            '@search-input' => 'input[placeholder=\"Rechercher...\"]',
            '@users-table' => 'table',
            '@name-input' => 'input[name=\"name\"]',
            '@email-input' => 'input[name=\"email\"]',
            '@password-input' => 'input[name=\"password\"]',
            '@submit-button' => 'button[type=\"submit\"]',
        ];
    }

    public function createUser(Browser $browser, string $name, string $email, string $password)
    {
        $browser->click('@add-user-button')
                ->waitFor('@name-input')
                ->type('@name-input', $name)
                ->type('@email-input', $email)
                ->type('@password-input', $password)
                ->click('@submit-button');
    }

    public function searchUser(Browser $browser, string $searchTerm)
    {
        $browser->type('@search-input', $searchTerm)
                ->pause(500); // Attendre la recherche
    }
}

Tests E2E complets

tests/Browser/UserCrudTest.php
<?php

use Laravel\\Dusk\\Browser;
use Tests\\Browser\\Pages\\UserPage;
use App\\Models\\User;

uses(Tests\\DuskTestCase::class);

it('can display users table', function () {
    // Créer quelques utilisateurs de test
    User::factory()->count(3)->create();

    $this->browse(function (Browser $browser) {
        $browser->visit(new UserPage)
                ->assertSee('Utilisateurs')
                ->assertPresent('@users-table')
                ->assertSeeIn('@users-table', 'Nom')
                ->assertSeeIn('@users-table', 'Email');
    });
});

it('can create new user via form', function () {
    $this->browse(function (Browser $browser) {
        $browser->visit(new UserPage)
                ->createUser('Nouvel Utilisateur', 'nouveau@test.com', 'password123')
                ->waitForText('Nouvel Utilisateur')
                ->assertSeeIn('@users-table', 'Nouvel Utilisateur')
                ->assertSeeIn('@users-table', 'nouveau@test.com');
    });
});

it('can search users', function () {
    // Créer des utilisateurs avec des noms différents
    User::factory()->create(['name' => 'John Doe']);
    User::factory()->create(['name' => 'Jane Smith']);
    User::factory()->create(['name' => 'Bob Wilson']);

    $this->browse(function (Browser $browser) {
        $browser->visit(new UserPage)
                ->searchUser('John')
                ->waitFor('@users-table')
                ->assertSeeIn('@users-table', 'John Doe')
                ->assertDontSee('Jane Smith')
                ->assertDontSee('Bob Wilson');
    });
});

it('can handle large datasets with pagination', function () {
    // Créer beaucoup d'utilisateurs
    User::factory()->count(50)->create();

    $this->browse(function (Browser $browser) {
        $browser->visit(new UserPage)
                ->waitFor('@users-table')
                ->assertSee('50 résultats')
                // Vérifier la pagination si elle existe
                ->whenAvailable('[data-testid=\"pagination\"]', function ($pagination) {
                    $pagination->click('[data-testid=\"next-page\"]');
                });
    });
});

it('can delete user', function () {
    $user = User::factory()->create(['name' => 'User à supprimer']);

    $this->browse(function (Browser $browser) use ($user) {
        $browser->visit(new UserPage)
                ->waitFor('@users-table')
                ->click(\"button[data-user-id='{$user->id}'][data-action='delete']\")
                ->waitForText('Êtes-vous sûr ?')
                ->click('button:contains(\"Confirmer\")')
                ->waitUntilMissingText($user->name)
                ->assertDontSee($user->name);
    });
});

6. Configuration Laravel Dusk

Configuration Dusk (.env.dusk.local)

APP_URL=http://localhost:8000
DB_CONNECTION=sqlite
DB_DATABASE=:memory:

7. Configuration Vitest pour tests d'intégration

vitest.integration.config.ts
import { defineConfig } from 'vite';
import { resolve } from 'path';

export default defineConfig({
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: ['./integration/test-setup.ts'],
    include: ['integration/**/*.{test,spec}.{js,ts,tsx}'],
  },
  resolve: {
    alias: {
      '@': resolve(__dirname, './resources/js'),
    },
  },
});

8. Configuration Tailwind

tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  darkMode: [\"class\"],
  content: [
    './pages/**/*.{ts,tsx}',
    './components/**/*.{ts,tsx}',
    './app/**/*.{ts,tsx}',
    './src/**/*.{ts,tsx}',
    './resources/**/*.{js,ts,jsx,tsx}',
  ],
  prefix: \"\",
  theme: {
    container: {
      center: true,
      padding: \"2rem\",
      screens: {
        \"2xl\": \"1400px\",
      },
    },
    extend: {
      colors: {
        border: \"hsl(var(--border))\",
        input: \"hsl(var(--input))\",
        ring: \"hsl(var(--ring))\",
        background: \"hsl(var(--background))\",
        foreground: \"hsl(var(--foreground))\",
        primary: {
          DEFAULT: \"hsl(var(--primary))\",
          foreground: \"hsl(var(--primary-foreground))\",
        },
        secondary: {
          DEFAULT: \"hsl(var(--secondary))\",
          foreground: \"hsl(var(--secondary-foreground))\",
        },
        destructive: {
          DEFAULT: \"hsl(var(--destructive))\",
          foreground: \"hsl(var(--destructive-foreground))\",
        },
        muted: {
          DEFAULT: \"hsl(var(--muted))\",
          foreground: \"hsl(var(--muted-foreground))\",
        },
        accent: {
          DEFAULT: \"hsl(var(--accent))\",
          foreground: \"hsl(var(--accent-foreground))\",
        },
        popover: {
          DEFAULT: \"hsl(var(--popover))\",
          foreground: \"hsl(var(--popover-foreground))\",
        },
        card: {
          DEFAULT: \"hsl(var(--card))\",
          foreground: \"hsl(var(--card-foreground))\",
        },
      },
      borderRadius: {
        lg: \"var(--radius)\",
        md: \"calc(var(--radius) - 2px)\",
        sm: \"calc(var(--radius) - 4px)\",
      },
      keyframes: {
        \"accordion-down\": {
          from: { height: \"0\" },
          to: { height: \"var(--radix-accordion-content-height)\" },
        },
        \"accordion-up\": {
          from: { height: \"var(--radix-accordion-content-height)\" },
          to: { height: \"0\" },
        },
      },
      animation: {
        \"accordion-down\": \"accordion-down 0.2s ease-out\",
        \"accordion-up\": \"accordion-up 0.2s ease-out\",
      },
    },
  },
  plugins: [require(\"tailwindcss-animate\")],
}

9. Configuration TypeScript

tsconfig.json
{
  \"compilerOptions\": {
    \"target\": \"ES2020\",
    \"useDefineForClassFields\": true,
    \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],
    \"module\": \"ESNext\",
    \"skipLibCheck\": true,
    \"moduleResolution\": \"bundler\",
    \"allowImportingTsExtensions\": true,
    \"resolveJsonModule\": true,
    \"isolatedModules\": true,
    \"noEmit\": true,
    \"jsx\": \"react-jsx\",
    \"strict\": true,
    \"noUnusedLocals\": true,
    \"noUnusedParameters\": true,
    \"noFallthroughCasesInSwitch\": true,
    \"baseUrl\": \".\",
    \"paths\": {
      \"@/*\": [\"./resources/js/*\"]
    },
    \"types\": [\"vitest/globals\", \"@testing-library/jest-dom\"]
  },
  \"include\": [\"resources/js/**/*\"],
  \"references\": [{ \"path\": \"./tsconfig.node.json\" }]
}

10. Utilitaires shadcn/ui

resources/js/lib/utils.ts
import { type ClassValue, clsx } from \"clsx\"
import { twMerge } from \"tailwind-merge\"

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

11. Setup des tests Frontend

resources/js/test-setup.ts
import '@testing-library/jest-dom';

12. Configuration package.json (scripts)

{
  \"scripts\": {
    \"dev\": \"vite\",
    \"build\": \"tsc && vite build\",

    \"test\": \"vitest\",
    \"test:ui\": \"vitest --ui\",
    \"test:coverage\": \"vitest --coverage\",
    \"test:unit\": \"vitest\",
    \"test:integration\": \"vitest --config vitest.integration.config.ts\",

    \"test:frontend\": \"npm run test:unit\",
    \"test:backend\": \"./vendor/bin/pest\",
    \"test:dusk\": \"php artisan dusk\",
    \"test:e2e\": \"npm run test:dusk\",

    \"test:all\": \"npm run test:backend && npm run test:frontend && npm run test:integration\",
    \"test:full\": \"npm run test:all && npm run test:dusk\",

    \"pest\": \"./vendor/bin/pest\"
  }
}

13. Structure React avec shadcn/ui

App principal

resources/js/app.tsx
import React from 'react';
import { createRoot } from 'react-dom/client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import App from './components/App';

const queryClient = new QueryClient();

const container = document.getElementById('app');
if (container) {
    const root = createRoot(container);
    root.render(
        <QueryClientProvider client={queryClient}>
            <App />
        </QueryClientProvider>
    );
}

Service API

resources/js/services/api.ts
import axios from 'axios';

const api = axios.create({
    baseURL: '/api',
    headers: {
        'Content-Type': 'application/json',
        'X-Requested-With': 'XMLHttpRequest',
    },
});

// Intercepteur pour le token CSRF
api.interceptors.request.use((config) => {
    const token = document.querySelector('meta[name=\"csrf-token\"]')?.getAttribute('content');
    if (token) {
        config.headers['X-CSRF-TOKEN'] = token;
    }
    return config;
});

export default api;

14. Composant DataTable avec shadcn/ui

resources/js/components/DataTable.tsx
import React from 'react';
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from '@/components/ui/table';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Badge } from '@/components/ui/badge';
import { Skeleton } from '@/components/ui/skeleton';

interface DataTableProps {
  data: any[];
  columns: Array<{
    key: string;
    label: string;
    render?: (value: any, row: any) => React.ReactNode;
  }>;
  isLoading?: boolean;
  onSearch?: (term: string) => void;
}

export default function DataTable({ data, columns, isLoading, onSearch }: DataTableProps) {
  const [searchTerm, setSearchTerm] = React.useState('');

  const handleSearch = (value: string) => {
    setSearchTerm(value);
    onSearch?.(value);
  };

  if (isLoading) {
    return (
      <div className=\"space-y-4\">
        <Skeleton className=\"h-10 w-full\" />
        <div className=\"space-y-2\">
          {Array.from({ length: 5 }).map((_, i) => (
            <Skeleton key={i} className=\"h-16 w-full\" />
          ))}
        </div>
      </div>
    );
  }

  return (
    <div className=\"space-y-4\">
      <div className=\"flex justify-between items-center\">
        <Input
          placeholder=\"Rechercher...\"
          value={searchTerm}
          onChange={(e) => handleSearch(e.target.value)}
          className=\"max-w-sm\"
        />
        <Badge variant=\"secondary\">
          {data.length} résultat{data.length > 1 ? 's' : ''}
        </Badge>
      </div>

      <div className=\"rounded-md border\">
        <Table>
          <TableHeader>
            <TableRow>
              {columns.map((column) => (
                <TableHead key={column.key}>{column.label}</TableHead>
              ))}
            </TableRow>
          </TableHeader>
          <TableBody>
            {data.length === 0 ? (
              <TableRow>
                <TableCell colSpan={columns.length} className=\"h-24 text-center\">
                  Aucune donnée trouvée
                </TableCell>
              </TableRow>
            ) : (
              data.map((row, index) => (
                <TableRow key={index}>
                  {columns.map((column) => (
                    <TableCell key={column.key}>
                      {column.render 
                        ? column.render(row[column.key], row)
                        : row[column.key]
                      }
                    </TableCell>
                  ))}
                </TableRow>
              ))
            )}
          </TableBody>
        </Table>
      </div>
    </div>
  );
}

15. Exemples de tous les types de tests

Test Backend

tests/Feature/UserApiTest.php
<?php

use App\\Models\\User;

it('can fetch users via API', function () {
    User::factory()->count(5)->create();

    $response = $this->getJson('/api/users');

    $response->assertStatus(200)
             ->assertJsonCount(5, 'data');
});

it('can create user via API', function () {
    $userData = [
        'name' => 'John Doe',
        'email' => 'john@example.com',
        'password' => 'password123'
    ];

    $response = $this->postJson('/api/users', $userData);

    $response->assertStatus(201)
             ->assertJson(['data' => ['name' => 'John Doe']]);
});

Test Frontend

resources/js/__tests__/UserList.test.tsx
import { render, screen, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import UserList from '../components/UserList';
import api from '../services/api';

// Mock API
vi.mock('../services/api');
const mockedApi = vi.mocked(api);

const renderWithQuery = (component: React.ReactElement) => {
    const queryClient = new QueryClient({
        defaultOptions: { queries: { retry: false } }
    });
    return render(
        <QueryClientProvider client={queryClient}>
            {component}
        </QueryClientProvider>
    );
};

describe('UserList', () => {
  it('displays users correctly', async () => {
    const mockUsers = [
      { id: 1, name: 'John Doe', email: 'john@example.com' },
      { id: 2, name: 'Jane Smith', email: 'jane@example.com' }
    ];

    mockedApi.get.mockResolvedValue({ data: { data: mockUsers } });

    renderWithQuery(<UserList />);

    await waitFor(() => {
      expect(screen.getByText('John Doe')).toBeInTheDocument();
      expect(screen.getByText('jane@example.com')).toBeInTheDocument();
    });
  });
});

Test Frontend avec shadcn/ui

resources/js/__tests__/DataTable.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import DataTable from '../components/DataTable';

const mockData = [
  { id: 1, name: 'John Doe', email: 'john@example.com', status: 'active' },
  { id: 2, name: 'Jane Smith', email: 'jane@example.com', status: 'inactive' },
];

const mockColumns = [
  { key: 'name', label: 'Nom' },
  { key: 'email', label: 'Email' },
  { 
    key: 'status', 
    label: 'Statut',
    render: (value: string) => <span data-testid=\"status\">{value}</span>
  },
];

describe('DataTable', () => {
  it('renders data correctly', () => {
    render(<DataTable data={mockData} columns={mockColumns} />);

    expect(screen.getByText('John Doe')).toBeInTheDocument();
    expect(screen.getByText('jane@example.com')).toBeInTheDocument();
    expect(screen.getByText('2 résultats')).toBeInTheDocument();
  });

  it('shows loading skeleton when loading', () => {
    render(<DataTable data={[]} columns={mockColumns} isLoading={true} />);

    // Note: Ajustez le selector selon votre implémentation
    expect(document.querySelector('.h-10')).toBeInTheDocument();
  });

  it('handles search functionality', () => {
    const mockOnSearch = vi.fn();
    render(
      <DataTable 
        data={mockData} 
        columns={mockColumns} 
        onSearch={mockOnSearch}
      />
    );

    const searchInput = screen.getByPlaceholderText('Rechercher...');
    fireEvent.change(searchInput, { target: { value: 'John' } });

    expect(mockOnSearch).toHaveBeenCalledWith('John');
  });

  it('shows empty state when no data', () => {
    render(<DataTable data={[]} columns={mockColumns} />);

    expect(screen.getByText('Aucune donnée trouvée')).toBeInTheDocument();
  });
});

16. Scripts utiles pour tous les tests

Lancer les tests

# Tests unitaires uniquement
npm run test:unit              # React components
./vendor/bin/pest tests/Unit   # Laravel models/services

# Tests d'intégration
npm run test:integration       # Frontend-Backend
./vendor/bin/pest tests/Feature # Laravel API + DB

# Tests E2E avec Dusk
php artisan dusk              # Tous les tests Dusk
php artisan dusk tests/Browser/UserCrudTest.php # Test spécifique
npm run test:dusk             # Via npm script

# Configuration Dusk
php artisan dusk:install      # Setup initial
php artisan dusk:chrome-driver # Installer ChromeDriver

Développement

# Backend
php artisan serve

# Frontend (dans un autre terminal)
npm run dev

17. Architecture pour gros volumes de données

graph TB
    %% User Layer
    subgraph Users["👥 Utilisateurs"]
        Admins["Administrateurs"]
        Employees["Employés"]
        Managers["Managers"]
    end

    %% Client Layer
    subgraph Client["💻 Client Web"]
        Browser["Navigateur Chrome"]
        ReactApp["Application React<br/>+ shadcn/ui"]
        StateManagement["TanStack Query<br/>Gestion d'État"]

        Browser --> ReactApp
        ReactApp --> StateManagement
    end

    %% API Gateway / Load Balancer
    subgraph Gateway["🌐 API Gateway"]
        LoadBalancer["Load Balancer<br/>(Nginx/Apache)"]
        RateLimiting["Rate Limiting"]
        CORS["CORS Policy"]

        LoadBalancer --> RateLimiting
        LoadBalancer --> CORS
    end

    %% Application Layer
    subgraph AppLayer["🏗️ Application Layer"]
        LaravelAPI["Laravel 12 API<br/>REST Endpoints"]
        Middleware["Middleware<br/>Auth/Validation"]
        Controllers["Controllers<br/>Business Logic"]

        LaravelAPI --> Middleware
        LaravelAPI --> Controllers
    end

    %% Business Logic Layer
    subgraph BusinessLayer["⚡ Business Logic"]
        Services["Services<br/>Domain Logic"]
        Jobs["Queue Jobs<br/>Background Tasks"]
        Events["Events & Listeners"]

        Services --> Jobs
        Services --> Events
    end

    %% Data Layer
    subgraph DataLayer["🗄️ Data Layer"]
        Models["Eloquent Models"]
        Repositories["Repository Pattern"]
        Cache["Redis Cache<br/>Session/Queue"]

        Models --> Repositories
        Models --> Cache
    end

    %% Database Layer
    subgraph Database["💾 Database"]
        MySQL["MySQL/PostgreSQL<br/>Primary Database"]
        Migrations["Migrations<br/>Schema Versioning"]
        Seeders["Seeders<br/>Test Data"]

        MySQL --> Migrations
        MySQL --> Seeders
    end

    %% External Services
    subgraph External["🔌 Services Externes"]
        Email["Service Email<br/>(Mailgun/SES)"]
        FileStorage["File Storage<br/>(S3/MinIO)"]
        Analytics["Analytics<br/>(Custom/3rd party)"]
    end

    %% Security Layer
    subgraph Security["🔒 Sécurité"]
        JWT["JWT Authentication"]
        Permissions["Permissions & Roles"]
        Encryption["Encryption at Rest"]

        JWT --> Permissions
    end

    %% Monitoring & Logging
    subgraph Monitoring["📊 Monitoring"]
        Logs["Application Logs"]
        Metrics["Performance Metrics"]
        Alerts["Error Alerts"]

        Logs --> Metrics
        Logs --> Alerts
    end

    %% Data Flow
    Users --> Client
    Client -->|HTTPS API Calls| Gateway
    Gateway --> AppLayer
    AppLayer --> BusinessLayer
    BusinessLayer --> DataLayer
    DataLayer --> Database

    AppLayer -.->|Auth| Security
    BusinessLayer -.->|Email/Files| External
    AppLayer -.->|Logs| Monitoring

    %% Data Processing Flow for Large Datasets
    subgraph DataProcessing["📈 Traitement Gros Volumes"]
        BatchJobs["Batch Jobs<br/>Import/Export"]
        Pagination["API Pagination"]
        Virtualization["Frontend Virtualization"]

        BatchJobs --> Pagination
        Pagination --> Virtualization
    end

    BusinessLayer --> DataProcessing
    DataProcessing --> Client

    %% Styling
    classDef user fill:#8b5cf6,stroke:#333,stroke-width:2px,color:#fff
    classDef client fill:#06b6d4,stroke:#333,stroke-width:2px,color:#fff
    classDef api fill:#10b981,stroke:#333,stroke-width:2px,color:#fff
    classDef business fill:#f59e0b,stroke:#333,stroke-width:2px,color:#000
    classDef data fill:#ef4444,stroke:#333,stroke-width:2px,color:#fff
    classDef external fill:#6b7280,stroke:#333,stroke-width:2px,color:#fff
    classDef security fill:#dc2626,stroke:#333,stroke-width:2px,color:#fff
    classDef monitoring fill:#7c3aed,stroke:#333,stroke-width:2px,color:#fff

    class Users,Admins,Employees,Managers user
    class Client,Browser,ReactApp,StateManagement client
    class Gateway,LoadBalancer,RateLimiting,CORS,AppLayer,LaravelAPI,Middleware,Controllers api
    class BusinessLayer,Services,Jobs,Events,DataProcessing,BatchJobs,Pagination,Virtualization business
    class DataLayer,Models,Repositories,Cache,Database,MySQL,Migrations,Seeders data
    class External,Email,FileStorage,Analytics external
    class Security,JWT,Permissions,Encryption security
    class Monitoring,Logs,Metrics,Alerts monitoring

18. Optimisations pour gros volumes avec shadcn/ui

DataTable avec pagination et tri

import { useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import {
  flexRender,
  getCoreRowModel,
  getFilteredRowModel,
  getPaginationRowModel,
  getSortedRowModel,
  useReactTable,
} from '@tanstack/react-table';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from '@/components/ui/table';

// Utiliser @tanstack/react-table avec shadcn/ui pour des tables performantes
// npm install @tanstack/react-table

Virtualisation avec React Window

npm install react-window react-window-infinite-loader
npm install -D @types/react-window

Composants shadcn/ui supplémentaires pour les données

npx shadcn@latest add pagination
npx shadcn@latest add command
npx shadcn@latest add popover
npx shadcn@latest add calendar
npx shadcn@latest add date-picker
npx shadcn@latest add checkbox
npx shadcn@latest add radio-group

19. Stack de test finale recommandée

Cette configuration vous donne une base solide pour votre application d'entreprise avec shadcn/ui, offrant :

  • Design system cohérent avec des composants prêts à l'emploi
  • DataTable performant pour vos gros volumes de données
  • Tests robustes pour les composants UI
  • Accessibilité intégrée par défaut
  • Customisation facile via les CSS variables

Avantages spécifiques de Dusk pour votre projet : - Intégration native avec votre base de données Laravel - Syntaxe Pest compatible - cohérent avec vos autres tests - Gestion automatique des migrations de test - Page Objects pour une meilleure organisation - Un seul navigateur (Chrome) - parfait pour vos besoins - Configuration minimale

Stack de test finale recommandée : - Tests unitaires : Pest (PHP) + Vitest (JS) - Tests d'intégration : Pest Feature + Vitest avec API réelles - Tests E2E : Laravel Dusk uniquement - Pas de Playwright - inutile sans multi-navigateurs


Status : 🟢 Configuration complète et testée
Dernière mise à jour : 31/07/2025
Utilisation : Template pour applications Laravel + React avec tests complets