feat: Setup projet Classeo avec infrastructure Docker et architecture DDD
Configure l'environnement de développement complet avec Docker Compose, structure DDD 4 Bounded Contexts, et pipeline CI/CD GitHub Actions. Corrections compatibilité CI: - Symfony 8 nécessite monolog-bundle ^4.0 (la v3.x ne supporte que jusqu'à Symfony 7) - ESLint v9 nécessite flat config (eslint.config.js) - le format .eslintrc.cjs est obsolète
This commit is contained in:
37
frontend/.gitignore
vendored
Normal file
37
frontend/.gitignore
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
# =============================================================================
|
||||
# Dependencies
|
||||
# =============================================================================
|
||||
node_modules/
|
||||
.pnpm-store/
|
||||
|
||||
# =============================================================================
|
||||
# Build output
|
||||
# =============================================================================
|
||||
/.svelte-kit/
|
||||
/build/
|
||||
dist/
|
||||
|
||||
# =============================================================================
|
||||
# Environment files
|
||||
# =============================================================================
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# =============================================================================
|
||||
# Testing
|
||||
# =============================================================================
|
||||
/coverage/
|
||||
/playwright-report/
|
||||
/test-results/
|
||||
|
||||
# =============================================================================
|
||||
# PWA
|
||||
# =============================================================================
|
||||
dev-dist/
|
||||
|
||||
# =============================================================================
|
||||
# Misc
|
||||
# =============================================================================
|
||||
*.local
|
||||
*.tsbuildinfo
|
||||
15
frontend/.prettierrc
Normal file
15
frontend/.prettierrc
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.svelte",
|
||||
"options": {
|
||||
"parser": "svelte"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
61
frontend/Dockerfile
Normal file
61
frontend/Dockerfile
Normal file
@@ -0,0 +1,61 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
# =============================================================================
|
||||
# Node.js 22 - Frontend Classeo (SvelteKit)
|
||||
# =============================================================================
|
||||
|
||||
FROM node:22-alpine AS base
|
||||
|
||||
# Install pnpm
|
||||
RUN corepack enable && corepack prepare pnpm@10.28.2 --activate
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# =============================================================================
|
||||
# Development stage
|
||||
# =============================================================================
|
||||
FROM base AS dev
|
||||
|
||||
# Create entrypoint script for dev (installs deps if needed)
|
||||
RUN echo '#!/bin/sh' > /usr/local/bin/docker-entrypoint.sh && \
|
||||
echo 'set -e' >> /usr/local/bin/docker-entrypoint.sh && \
|
||||
echo 'if [ ! -d /app/node_modules ] || [ ! -f /app/node_modules/.pnpm/lock.yaml ]; then' >> /usr/local/bin/docker-entrypoint.sh && \
|
||||
echo ' echo "Installing pnpm dependencies..."' >> /usr/local/bin/docker-entrypoint.sh && \
|
||||
echo ' pnpm install' >> /usr/local/bin/docker-entrypoint.sh && \
|
||||
echo 'fi' >> /usr/local/bin/docker-entrypoint.sh && \
|
||||
echo 'exec "$@"' >> /usr/local/bin/docker-entrypoint.sh && \
|
||||
chmod +x /usr/local/bin/docker-entrypoint.sh
|
||||
|
||||
EXPOSE 5173
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
||||
CMD ["pnpm", "run", "dev", "--host", "0.0.0.0"]
|
||||
|
||||
# =============================================================================
|
||||
# Build stage
|
||||
# =============================================================================
|
||||
FROM base AS builder
|
||||
|
||||
COPY package.json pnpm-lock.yaml* ./
|
||||
RUN if [ -f pnpm-lock.yaml ]; then pnpm install --frozen-lockfile; else pnpm install; fi
|
||||
|
||||
COPY . .
|
||||
RUN pnpm run build
|
||||
|
||||
# =============================================================================
|
||||
# Production stage
|
||||
# =============================================================================
|
||||
FROM base AS prod
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
COPY package.json pnpm-lock.yaml* ./
|
||||
RUN if [ -f pnpm-lock.yaml ]; then pnpm install --frozen-lockfile --prod; else pnpm install --prod; fi
|
||||
|
||||
COPY --from=builder /app/build build/
|
||||
COPY --from=builder /app/package.json .
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["node", "build"]
|
||||
21
frontend/e2e/home.test.ts
Normal file
21
frontend/e2e/home.test.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
test('home page has correct title and content', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
await expect(page).toHaveTitle('Classeo');
|
||||
await expect(page.getByRole('heading', { name: 'Bienvenue sur Classeo' })).toBeVisible();
|
||||
await expect(page.getByText('Application de gestion scolaire')).toBeVisible();
|
||||
});
|
||||
|
||||
test('counter increments when button is clicked', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
await expect(page.getByText('Compteur: 0')).toBeVisible();
|
||||
|
||||
await page.getByRole('button', { name: 'Incrementer' }).click();
|
||||
await expect(page.getByText('Compteur: 1')).toBeVisible();
|
||||
|
||||
await page.getByRole('button', { name: 'Incrementer' }).click();
|
||||
await expect(page.getByText('Compteur: 2')).toBeVisible();
|
||||
});
|
||||
96
frontend/eslint.config.js
Normal file
96
frontend/eslint.config.js
Normal file
@@ -0,0 +1,96 @@
|
||||
import js from '@eslint/js';
|
||||
import tseslint from 'typescript-eslint';
|
||||
import sveltePlugin from 'eslint-plugin-svelte';
|
||||
import svelteParser from 'svelte-eslint-parser';
|
||||
import prettier from 'eslint-config-prettier';
|
||||
|
||||
export default tseslint.config(
|
||||
// Base JavaScript recommended rules
|
||||
js.configs.recommended,
|
||||
|
||||
// TypeScript recommended rules
|
||||
...tseslint.configs.recommended,
|
||||
|
||||
// Global ignores
|
||||
{
|
||||
ignores: [
|
||||
'.svelte-kit/**',
|
||||
'build/**',
|
||||
'dist/**',
|
||||
'node_modules/**',
|
||||
'*.config.js',
|
||||
'*.config.ts'
|
||||
]
|
||||
},
|
||||
|
||||
// TypeScript files
|
||||
{
|
||||
files: ['**/*.ts'],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 2020
|
||||
},
|
||||
globals: {
|
||||
window: 'readonly',
|
||||
document: 'readonly',
|
||||
console: 'readonly',
|
||||
process: 'readonly',
|
||||
__dirname: 'readonly',
|
||||
__filename: 'readonly',
|
||||
Promise: 'readonly',
|
||||
Set: 'readonly',
|
||||
Map: 'readonly'
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_'
|
||||
}
|
||||
],
|
||||
'no-console': ['warn', { allow: ['warn', 'error'] }]
|
||||
}
|
||||
},
|
||||
|
||||
// Svelte files
|
||||
{
|
||||
files: ['**/*.svelte'],
|
||||
languageOptions: {
|
||||
parser: svelteParser,
|
||||
parserOptions: {
|
||||
parser: tseslint.parser,
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 2020,
|
||||
extraFileExtensions: ['.svelte']
|
||||
},
|
||||
globals: {
|
||||
window: 'readonly',
|
||||
document: 'readonly',
|
||||
console: 'readonly',
|
||||
process: 'readonly',
|
||||
Promise: 'readonly',
|
||||
Set: 'readonly',
|
||||
Map: 'readonly'
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
svelte: sveltePlugin
|
||||
},
|
||||
rules: {
|
||||
...sveltePlugin.configs.recommended.rules,
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
// Prettier (disable conflicting rules)
|
||||
prettier
|
||||
);
|
||||
55
frontend/package.json
Normal file
55
frontend/package.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "classeo-frontend",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"test:coverage": "vitest run --coverage",
|
||||
"test:e2e": "playwright test",
|
||||
"lint": "eslint .",
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.50.0",
|
||||
"@sveltejs/adapter-auto": "^4.0.0",
|
||||
"@sveltejs/adapter-node": "^5.0.0",
|
||||
"@sveltejs/kit": "^2.50.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@tailwindcss/typography": "^0.5.10",
|
||||
"@testing-library/svelte": "^5.2.0",
|
||||
"@types/node": "^22.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||
"@typescript-eslint/parser": "^8.0.0",
|
||||
"@vitest/coverage-v8": "^2.1.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^9.0.0",
|
||||
"eslint-config-prettier": "^10.0.0",
|
||||
"eslint-plugin-svelte": "^3.0.0",
|
||||
"jsdom": "^27.4.0",
|
||||
"postcss": "^8.4.47",
|
||||
"prettier": "^3.4.0",
|
||||
"prettier-plugin-svelte": "^3.3.0",
|
||||
"prettier-plugin-tailwindcss": "^0.6.0",
|
||||
"svelte": "^5.15.0",
|
||||
"svelte-check": "^4.1.0",
|
||||
"tailwindcss": "^3.4.16",
|
||||
"typescript": "^5.7.0",
|
||||
"typescript-eslint": "^8.54.0",
|
||||
"vite": "^6.0.0",
|
||||
"vitest": "^2.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/svelte-query": "^5.66.0",
|
||||
"@vite-pwa/sveltekit": "^0.6.8",
|
||||
"workbox-window": "^7.3.0"
|
||||
},
|
||||
"packageManager": "pnpm@10.28.2"
|
||||
}
|
||||
41
frontend/playwright.config.ts
Normal file
41
frontend/playwright.config.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import type { PlaywrightTestConfig } from '@playwright/test';
|
||||
|
||||
const config: PlaywrightTestConfig = {
|
||||
webServer: {
|
||||
command: 'pnpm run build && pnpm run preview',
|
||||
port: 4173,
|
||||
reuseExistingServer: !process.env.CI
|
||||
},
|
||||
testDir: 'e2e',
|
||||
testMatch: /(.+\.)?(test|spec)\.[jt]s/,
|
||||
use: {
|
||||
baseURL: 'http://localhost:4173',
|
||||
trace: 'on-first-retry',
|
||||
screenshot: 'only-on-failure',
|
||||
video: 'retain-on-failure'
|
||||
},
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
reporter: process.env.CI ? 'github' : 'html',
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: {
|
||||
browserName: 'chromium'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'firefox',
|
||||
use: {
|
||||
browserName: 'firefox'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'webkit',
|
||||
use: {
|
||||
browserName: 'webkit'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export default config;
|
||||
7144
frontend/pnpm-lock.yaml
generated
Normal file
7144
frontend/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
6
frontend/postcss.config.js
Normal file
6
frontend/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {}
|
||||
}
|
||||
};
|
||||
62
frontend/src/app.css
Normal file
62
frontend/src/app.css
Normal file
@@ -0,0 +1,62 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 84% 4.9%;
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
--primary: 221.2 83.2% 53.3%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--ring: 221.2 83.2% 53.3%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
--popover: 222.2 84% 4.9%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--primary: 217.2 91.2% 59.8%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
--accent: 217.2 32.6% 17.5%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--ring: 224.3 76.3% 48%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
font-feature-settings:
|
||||
'rlig' 1,
|
||||
'calt' 1;
|
||||
}
|
||||
}
|
||||
16
frontend/src/app.html
Normal file
16
frontend/src/app.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!doctype html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#3b82f6" />
|
||||
<meta name="description" content="Classeo - Application de gestion scolaire" />
|
||||
<link rel="manifest" href="%sveltekit.assets%/manifest.webmanifest" />
|
||||
<link rel="apple-touch-icon" href="%sveltekit.assets%/apple-touch-icon.png" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
2
frontend/src/lib/index.ts
Normal file
2
frontend/src/lib/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
// place files you want to import through the `$lib` alias in this folder.
|
||||
export * from './types';
|
||||
35
frontend/src/lib/types/api.ts
Normal file
35
frontend/src/lib/types/api.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
// API response types
|
||||
export interface ApiError {
|
||||
code: string;
|
||||
message: string;
|
||||
violations?: Array<{
|
||||
propertyPath: string;
|
||||
message: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface PaginatedResponse<T> {
|
||||
data: T[];
|
||||
meta: {
|
||||
total: number;
|
||||
page: number;
|
||||
itemsPerPage: number;
|
||||
lastPage: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface HydraCollection<T> {
|
||||
'@context': string;
|
||||
'@id': string;
|
||||
'@type': string;
|
||||
'hydra:totalItems': number;
|
||||
'hydra:member': T[];
|
||||
'hydra:view'?: {
|
||||
'@id': string;
|
||||
'@type': string;
|
||||
'hydra:first'?: string;
|
||||
'hydra:last'?: string;
|
||||
'hydra:next'?: string;
|
||||
'hydra:previous'?: string;
|
||||
};
|
||||
}
|
||||
2
frontend/src/lib/types/index.ts
Normal file
2
frontend/src/lib/types/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './shared';
|
||||
export * from './api';
|
||||
27
frontend/src/lib/types/shared.ts
Normal file
27
frontend/src/lib/types/shared.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
// Branded types for type safety
|
||||
export type TenantId = string & { readonly brand: unique symbol };
|
||||
export type UserId = string & { readonly brand: unique symbol };
|
||||
export type NoteId = string & { readonly brand: unique symbol };
|
||||
export type ClasseId = string & { readonly brand: unique symbol };
|
||||
export type EleveId = string & { readonly brand: unique symbol };
|
||||
|
||||
// Helper functions for branded types
|
||||
export function createTenantId(id: string): TenantId {
|
||||
return id as TenantId;
|
||||
}
|
||||
|
||||
export function createUserId(id: string): UserId {
|
||||
return id as UserId;
|
||||
}
|
||||
|
||||
export function createNoteId(id: string): NoteId {
|
||||
return id as NoteId;
|
||||
}
|
||||
|
||||
export function createClasseId(id: string): ClasseId {
|
||||
return id as ClasseId;
|
||||
}
|
||||
|
||||
export function createEleveId(id: string): EleveId {
|
||||
return id as EleveId;
|
||||
}
|
||||
23
frontend/src/routes/+layout.svelte
Normal file
23
frontend/src/routes/+layout.svelte
Normal file
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import '../app.css';
|
||||
import { browser } from '$app/environment';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/svelte-query';
|
||||
|
||||
let { children } = $props();
|
||||
|
||||
const queryClient = $state(
|
||||
new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
enabled: browser,
|
||||
staleTime: 1000 * 60 * 5, // 5 minutes
|
||||
retry: 1
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
</script>
|
||||
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{@render children()}
|
||||
</QueryClientProvider>
|
||||
28
frontend/src/routes/+page.svelte
Normal file
28
frontend/src/routes/+page.svelte
Normal file
@@ -0,0 +1,28 @@
|
||||
<script lang="ts">
|
||||
let count = $state(0);
|
||||
|
||||
function increment() {
|
||||
count++;
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Classeo</title>
|
||||
</svelte:head>
|
||||
|
||||
<main class="flex min-h-screen flex-col items-center justify-center bg-gray-50">
|
||||
<div class="text-center">
|
||||
<h1 class="mb-4 text-4xl font-bold text-primary">Bienvenue sur Classeo</h1>
|
||||
<p class="mb-8 text-gray-600">Application de gestion scolaire</p>
|
||||
|
||||
<div class="rounded-lg bg-white p-8 shadow-md">
|
||||
<p class="mb-4 text-2xl font-semibold text-gray-800">Compteur: {count}</p>
|
||||
<button
|
||||
onclick={increment}
|
||||
class="rounded-md bg-primary px-6 py-2 text-primary-foreground transition-colors hover:bg-primary/90"
|
||||
>
|
||||
Incrementer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
BIN
frontend/static/pwa-192x192.png
Normal file
BIN
frontend/static/pwa-192x192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
BIN
frontend/static/pwa-512x512.png
Normal file
BIN
frontend/static/pwa-512x512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 117 KiB |
21
frontend/svelte.config.js
Normal file
21
frontend/svelte.config.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import adapter from '@sveltejs/adapter-node';
|
||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
preprocess: vitePreprocess(),
|
||||
|
||||
kit: {
|
||||
adapter: adapter(),
|
||||
alias: {
|
||||
$components: 'src/lib/components',
|
||||
$features: 'src/lib/features',
|
||||
$stores: 'src/lib/stores',
|
||||
$api: 'src/lib/api',
|
||||
$utils: 'src/lib/utils',
|
||||
$types: 'src/lib/types'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
53
frontend/tailwind.config.js
Normal file
53
frontend/tailwind.config.js
Normal file
@@ -0,0 +1,53 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
darkMode: 'class',
|
||||
content: ['./src/**/*.{html,js,svelte,ts}'],
|
||||
theme: {
|
||||
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)'
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'system-ui', 'sans-serif']
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [require('@tailwindcss/forms'), require('@tailwindcss/typography')]
|
||||
};
|
||||
21
frontend/tests/unit/example.test.ts
Normal file
21
frontend/tests/unit/example.test.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
describe('Math operations', () => {
|
||||
it('should add two numbers correctly', () => {
|
||||
expect(1 + 1).toBe(2);
|
||||
});
|
||||
|
||||
it('should multiply two numbers correctly', () => {
|
||||
expect(2 * 3).toBe(6);
|
||||
});
|
||||
});
|
||||
|
||||
describe('String operations', () => {
|
||||
it('should concatenate strings', () => {
|
||||
expect('Hello' + ' ' + 'World').toBe('Hello World');
|
||||
});
|
||||
|
||||
it('should convert to uppercase', () => {
|
||||
expect('classeo'.toUpperCase()).toBe('CLASSEO');
|
||||
});
|
||||
});
|
||||
23
frontend/tests/unit/page.test.ts
Normal file
23
frontend/tests/unit/page.test.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { render, screen } from '@testing-library/svelte';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import Page from '../../src/routes/+page.svelte';
|
||||
|
||||
describe('Home Page', () => {
|
||||
it('renders the welcome message', () => {
|
||||
render(Page);
|
||||
|
||||
expect(screen.getByRole('heading', { name: 'Bienvenue sur Classeo' })).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders the description', () => {
|
||||
render(Page);
|
||||
|
||||
expect(screen.getByText('Application de gestion scolaire')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('starts counter at 0', () => {
|
||||
render(Page);
|
||||
|
||||
expect(screen.getByText('Compteur: 0')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
18
frontend/tsconfig.json
Normal file
18
frontend/tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"moduleResolution": "bundler"
|
||||
}
|
||||
}
|
||||
74
frontend/vite.config.ts
Normal file
74
frontend/vite.config.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { SvelteKitPWA } from '@vite-pwa/sveltekit';
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
sveltekit(),
|
||||
SvelteKitPWA({
|
||||
srcDir: 'src',
|
||||
mode: 'development',
|
||||
strategies: 'generateSW',
|
||||
scope: '/',
|
||||
base: '/',
|
||||
manifest: {
|
||||
name: 'Classeo',
|
||||
short_name: 'Classeo',
|
||||
description: 'Application de gestion scolaire',
|
||||
theme_color: '#3b82f6',
|
||||
background_color: '#ffffff',
|
||||
display: 'standalone',
|
||||
start_url: '/',
|
||||
icons: [
|
||||
{
|
||||
src: 'pwa-192x192.png',
|
||||
sizes: '192x192',
|
||||
type: 'image/png'
|
||||
},
|
||||
{
|
||||
src: 'pwa-512x512.png',
|
||||
sizes: '512x512',
|
||||
type: 'image/png'
|
||||
},
|
||||
{
|
||||
src: 'pwa-512x512.png',
|
||||
sizes: '512x512',
|
||||
type: 'image/png',
|
||||
purpose: 'any maskable'
|
||||
}
|
||||
]
|
||||
},
|
||||
workbox: {
|
||||
globPatterns: ['**/*.{js,css,html,ico,png,svg,webp,woff,woff2}']
|
||||
},
|
||||
devOptions: {
|
||||
enabled: false,
|
||||
type: 'module',
|
||||
navigateFallback: '/'
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
}) as any
|
||||
],
|
||||
test: {
|
||||
include: ['src/**/*.{test,spec}.{js,ts}', 'tests/**/*.{test,spec}.{js,ts}'],
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
server: {
|
||||
deps: {
|
||||
inline: [/svelte/]
|
||||
}
|
||||
},
|
||||
alias: {
|
||||
$lib: '/src/lib',
|
||||
$app: '/node_modules/@sveltejs/kit/src/runtime/app'
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
conditions: ['browser']
|
||||
},
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 5173,
|
||||
strictPort: true
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user