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
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
<?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
<?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)
7. Configuration Vitest pour tests d'intégration
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
/** @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
{
\"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
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
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
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
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
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
<?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
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
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
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
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