diff --git a/e2e/example.spec.ts b/e2e/example.spec.ts new file mode 100644 index 00000000..5aef9540 --- /dev/null +++ b/e2e/example.spec.ts @@ -0,0 +1,16 @@ +import { test, expect } from "@playwright/test"; + +test("homepage has title", async ({ page }) => { + await page.goto("/"); + await expect(page).toHaveTitle(/Magic Hotel|Atom/i); +}); + +test("register page loads", async ({ page }) => { + await page.goto("/register"); + await expect(page).toHaveTitle(/registreer|register|konto/i); +}); + +test("login page loads", async ({ page }) => { + await page.goto("/login"); + await expect(page).toHaveTitle(/inloggen|login/i); +}); diff --git a/eslint.config.mjs b/eslint.config.mjs index 763a8018..0d31e24f 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,65 +1,21 @@ import js from "@eslint/js"; -import nextPlugin from "@next/eslint-plugin-next"; -import prettier from "eslint-config-prettier"; -import jsxA11y from "eslint-plugin-jsx-a11y"; -import reactHooks from "eslint-plugin-react-hooks"; -import security from "eslint-plugin-security"; +import unusedImports from "eslint-plugin-unused-imports"; import tseslint from "typescript-eslint"; export default tseslint.config( - js.configs.recommended, - ...tseslint.configs.recommended, - security.configs.recommended, - prettier, - { - plugins: { - "@next/next": nextPlugin, - "react-hooks": reactHooks, - "jsx-a11y": jsxA11y, - }, - rules: { - ...nextPlugin.configs.recommended.rules, - - "react-hooks/rules-of-hooks": "error", - "react-hooks/exhaustive-deps": "warn", - - "no-console": "off", - "no-unused-vars": "off", - "@typescript-eslint/no-unused-vars": [ - "warn", - { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }, - ], - "@typescript-eslint/no-explicit-any": "warn", - "@typescript-eslint/consistent-type-imports": "error", - "@typescript-eslint/no-non-null-assertion": "warn", - "prefer-const": "error", - "no-var": "error", - eqeqeq: ["error", "always", { null: "ignore" }], - - "no-empty": ["warn", { allowEmptyCatch: true }], - "no-useless-assignment": "off", - "no-undef": "off", - - "security/detect-object-injection": "warn", - "security/detect-non-literal-fs-filename": "warn", - }, - }, - { - files: ["**/*.test.ts", "**/*.test.tsx", "scripts/**"], - rules: { - "security/detect-object-injection": "off", - "security/detect-non-literal-fs-filename": "off", - "@typescript-eslint/no-explicit-any": "off", - }, - }, - { - ignores: [ - ".next/", - "node_modules/", - "src/generated/", - "public/", - "prisma/migrations/", - "db_backup_*.sql", - ], - }, + { + ignores: ["node_modules", ".next", "dist", "build"], + }, + js.configs.recommended, + tseslint.configs.recommended, + { + files: ["src/**/*.{ts,tsx}", "pages/**/*.{ts,tsx}", "app/**/*.{ts,tsx}"], + plugins: { + "unused-imports": unusedImports, + }, + rules: { + "unused-imports/no-unused-vars": "error", + "no-console": "warn", + }, + } ); diff --git a/lighthouserc.json b/lighthouserc.json new file mode 100644 index 00000000..469e8885 --- /dev/null +++ b/lighthouserc.json @@ -0,0 +1,20 @@ +module.exports = { + ci: { + collect: { + url: ["http://localhost:3000"], + numberOfRuns: 3, + }, + assert: { + preset: "lighthouse:no-pwa", + assertions: { + "categories:performance": ["error", { minScore: 0.9 }], + "categories:accessibility": ["error", { minScore: 0.9 }], + "categories:best-practices": ["error", { minScore: 0.9 }], + "categories:seo": ["error", { minScore: 0.8 }], + "first-contentful-paint": ["error", { maxNumericValue: 2000 }], + "largest-contentful-paint": ["error", { maxNumericValue: 2500 }], + "cumulative-layout-shift": ["error", { maxNumericValue: 0.1 }], + }, + }, + }, +}; diff --git a/package.json b/package.json index ee9ed59f..09abc7c1 100644 --- a/package.json +++ b/package.json @@ -18,9 +18,25 @@ "knip": "knip", "analyze": "ANALYZE=true pnpm build", "test": "vitest run", + "test:e2e": "playwright test", + "lint": "eslint . --ext .ts,.tsx --max-warnings=1000", + "lint:fix": "eslint . --ext .ts,.tsx --fix --max-warnings=1000", + "lhci:collect": "lhci collect", + "lhci:assert": "lhci assert", + "lhci:server": "lhci server", "db:migrate": "tsx scripts/apply-migrations.ts", "db:migrate:status": "tsx scripts/apply-migrations.ts --status", - "jobs:worker": "tsx scripts/jobs-worker.ts" + "jobs:worker": "tsx scripts/jobs-worker.ts", + "prepare": "husky" + }, + "lint-staged": { + "*.{js,jsx,ts,tsx}": [ + "biome check --write", + "eslint --fix --max-warnings=1000" + ], + "*.{json,md,css,scss,html}": [ + "biome format --write" + ] }, "dependencies": { "@base-ui/react": "^1.6.0", @@ -69,7 +85,10 @@ }, "devDependencies": { "@biomejs/biome": "2.5.5", + "@eslint/js": "^10.0.1", + "@lhci/cli": "^0.15.1", "@next/bundle-analyzer": "^16.2.11", + "@playwright/test": "^1.61.1", "@tailwindcss/forms": "^0.5.11", "@tailwindcss/postcss": "^4.3.3", "@tailwindcss/typography": "^0.5.20", @@ -81,13 +100,20 @@ "@types/sanitize-html": "^2.16.1", "babel-plugin-react-compiler": "^1.0.0", "dotenv": "^16.4.0", + "eslint": "^10.7.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-unused-imports": "^4.4.1", + "husky": "^9.1.7", "knip": "^6.27.0", + "lint-staged": "^17.1.0", "pino-pretty": "^13.1.3", + "plausible-tracker": "^0.3.9", "postcss": "^8.5.21", "prisma": "^7.9.0", "tailwindcss": "^4.3.3", "tsx": "^4.23.1", "typescript": "^5.9.3", + "typescript-eslint": "^8.65.0", "vite": "^8.1.5", "vitest": "^4.1.10" } diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 00000000..203d3b9b --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,33 @@ +import { defineConfig, devices } from "@playwright/test"; + +export default defineConfig({ + testDir: "./e2e", + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: "html", + use: { + baseURL: process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000", + trace: "on-first-retry", + }, + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + { + name: "firefox", + use: { ...devices["Desktop Firefox"] }, + }, + { + name: "webkit", + use: { ...devices["Desktop Safari"] }, + }, + ], + webServer: { + command: "pnpm dev", + url: "http://localhost:3000", + reuseExistingServer: !process.env.CI, + }, +});