create-react-forge 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/README.md +88 -61
  2. package/dist/cli/index.js +1 -1
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/cli/parser.js +1 -1
  5. package/dist/cli/parser.js.map +1 -1
  6. package/dist/docs/architecture-generator.js +1 -1
  7. package/dist/generator/index.js +1 -1
  8. package/dist/generator/index.js.map +1 -1
  9. package/package.json +2 -1
  10. package/src/templates/overlays/base/manifest.json +16 -0
  11. package/src/templates/overlays/base/src/components/ui/Button.tsx +66 -0
  12. package/src/templates/overlays/base/src/components/ui/Input.tsx +51 -0
  13. package/src/templates/overlays/base/src/components/ui/index.ts +3 -0
  14. package/src/templates/overlays/base/src/hooks/use-disclosure.ts +21 -0
  15. package/src/templates/overlays/base/src/hooks/use-local-storage.ts +80 -0
  16. package/src/templates/overlays/base/src/lib/api-client.ts +101 -0
  17. package/src/templates/overlays/base/src/lib/utils.ts +34 -0
  18. package/src/templates/overlays/base/src/types/api.ts +47 -0
  19. package/src/templates/overlays/features/tanstack-query/manifest.json +17 -0
  20. package/src/templates/overlays/features/tanstack-query/src/features/users/api/create-user.ts +41 -0
  21. package/src/templates/overlays/features/tanstack-query/src/features/users/api/get-user.ts +27 -0
  22. package/src/templates/overlays/features/tanstack-query/src/features/users/api/get-users.ts +39 -0
  23. package/src/templates/overlays/features/tanstack-query/src/hooks/use-query-config.ts +42 -0
  24. package/src/templates/overlays/features/tanstack-query/src/lib/QueryProvider.tsx +28 -0
  25. package/src/templates/overlays/features/tanstack-query/src/lib/react-query.ts +46 -0
  26. package/src/templates/overlays/runtime/nextjs/manifest.json +27 -0
  27. package/src/templates/overlays/runtime/nextjs/next-env.d.ts +6 -0
  28. package/src/templates/overlays/runtime/nextjs/next.config.js +12 -0
  29. package/src/templates/overlays/runtime/nextjs/src/app/error.tsx +34 -0
  30. package/src/templates/overlays/runtime/nextjs/src/app/layout.tsx +23 -0
  31. package/src/templates/overlays/runtime/nextjs/src/app/loading.tsx +12 -0
  32. package/src/templates/overlays/runtime/nextjs/src/app/not-found.tsx +26 -0
  33. package/src/templates/overlays/runtime/nextjs/src/app/page.tsx +33 -0
  34. package/src/templates/overlays/runtime/nextjs/src/app/providers.tsx +14 -0
  35. package/src/templates/overlays/runtime/nextjs/src/styles/globals.css +50 -0
  36. package/src/templates/overlays/runtime/nextjs/tsconfig.json +29 -0
  37. package/src/templates/overlays/runtime/vite/index.html +14 -0
  38. package/src/templates/overlays/runtime/vite/manifest.json +28 -0
  39. package/src/templates/overlays/runtime/vite/public/vite.svg +2 -0
  40. package/src/templates/overlays/runtime/vite/src/app/App.tsx +11 -0
  41. package/src/templates/overlays/runtime/vite/src/app/provider.tsx +20 -0
  42. package/src/templates/overlays/runtime/vite/src/app/router.tsx +19 -0
  43. package/src/templates/overlays/runtime/vite/src/components/errors/ErrorFallback.tsx +21 -0
  44. package/src/templates/overlays/runtime/vite/src/components/ui/LoadingSpinner.tsx +23 -0
  45. package/src/templates/overlays/runtime/vite/src/features/misc/routes/Landing.tsx +33 -0
  46. package/src/templates/overlays/runtime/vite/src/features/misc/routes/NotFound.tsx +26 -0
  47. package/src/templates/overlays/runtime/vite/src/main.tsx +11 -0
  48. package/src/templates/overlays/runtime/vite/src/styles/globals.css +55 -0
  49. package/src/templates/overlays/runtime/vite/tsconfig.json +32 -0
  50. package/src/templates/overlays/runtime/vite/tsconfig.node.json +23 -0
  51. package/src/templates/overlays/runtime/vite/vite.config.ts +22 -0
  52. package/src/templates/overlays/state/redux/manifest.json +17 -0
  53. package/src/templates/overlays/state/redux/src/stores/Provider.tsx +17 -0
  54. package/src/templates/overlays/state/redux/src/stores/hooks.ts +11 -0
  55. package/src/templates/overlays/state/redux/src/stores/index.ts +17 -0
  56. package/src/templates/overlays/state/redux/src/stores/slices/auth.ts +54 -0
  57. package/src/templates/overlays/state/redux/src/stores/slices/notifications.ts +58 -0
  58. package/src/templates/overlays/state/redux/src/stores/store.ts +27 -0
  59. package/src/templates/overlays/state/zustand/manifest.json +16 -0
  60. package/src/templates/overlays/state/zustand/src/stores/auth.ts +48 -0
  61. package/src/templates/overlays/state/zustand/src/stores/index.ts +3 -0
  62. package/src/templates/overlays/state/zustand/src/stores/notifications.ts +54 -0
  63. package/src/templates/overlays/styling/css-modules/manifest.json +14 -0
  64. package/src/templates/overlays/styling/css-modules/src/components/ui/Button.module.css +87 -0
  65. package/src/templates/overlays/styling/css-modules/src/styles/globals.css +91 -0
  66. package/src/templates/overlays/styling/tailwind/manifest.json +18 -0
  67. package/src/templates/overlays/styling/tailwind/postcss.config.js +7 -0
  68. package/src/templates/overlays/styling/tailwind/src/styles/globals.css +54 -0
  69. package/src/templates/overlays/styling/tailwind/tailwind.config.js +62 -0
  70. package/src/templates/overlays/testing/jest/jest.config.js +24 -0
  71. package/src/templates/overlays/testing/jest/manifest.json +27 -0
  72. package/src/templates/overlays/testing/jest/src/components/ui/__tests__/Button.test.tsx +33 -0
  73. package/src/templates/overlays/testing/jest/src/testing/mocks/browser.ts +17 -0
  74. package/src/templates/overlays/testing/jest/src/testing/mocks/handlers.ts +42 -0
  75. package/src/templates/overlays/testing/jest/src/testing/mocks/server.ts +8 -0
  76. package/src/templates/overlays/testing/jest/src/testing/setup.ts +20 -0
  77. package/src/templates/overlays/testing/jest/src/testing/test-utils.tsx +39 -0
  78. package/src/templates/overlays/testing/playwright/manifest.json +21 -0
  79. package/src/templates/overlays/testing/playwright/playwright.config.ts +53 -0
  80. package/src/templates/overlays/testing/playwright/tests/e2e/accessibility.spec.ts +41 -0
  81. package/src/templates/overlays/testing/playwright/tests/e2e/home.spec.ts +41 -0
  82. package/src/templates/overlays/testing/vitest/manifest.json +28 -0
  83. package/src/templates/overlays/testing/vitest/src/components/ui/__tests__/Button.test.tsx +34 -0
  84. package/src/templates/overlays/testing/vitest/src/testing/mocks/browser.ts +17 -0
  85. package/src/templates/overlays/testing/vitest/src/testing/mocks/handlers.ts +42 -0
  86. package/src/templates/overlays/testing/vitest/src/testing/mocks/server.ts +8 -0
  87. package/src/templates/overlays/testing/vitest/src/testing/setup.ts +21 -0
  88. package/src/templates/overlays/testing/vitest/src/testing/test-utils.tsx +39 -0
  89. package/src/templates/overlays/testing/vitest/vitest.config.ts +31 -0
@@ -0,0 +1,87 @@
1
+ .button {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ justify-content: center;
5
+ border-radius: var(--radius-md);
6
+ font-weight: 600;
7
+ transition: all 0.15s ease;
8
+ cursor: pointer;
9
+ border: none;
10
+ }
11
+
12
+ .button:disabled {
13
+ opacity: 0.5;
14
+ cursor: not-allowed;
15
+ }
16
+
17
+ /* Variants */
18
+ .primary {
19
+ background-color: var(--color-primary-600);
20
+ color: white;
21
+ }
22
+
23
+ .primary:hover:not(:disabled) {
24
+ background-color: var(--color-primary-500);
25
+ }
26
+
27
+ .secondary {
28
+ background-color: var(--color-gray-100);
29
+ color: var(--color-gray-900);
30
+ }
31
+
32
+ .secondary:hover:not(:disabled) {
33
+ background-color: var(--color-gray-200);
34
+ }
35
+
36
+ .outline {
37
+ background-color: transparent;
38
+ border: 1px solid var(--color-gray-300);
39
+ color: var(--color-gray-700);
40
+ }
41
+
42
+ .outline:hover:not(:disabled) {
43
+ background-color: var(--color-gray-50);
44
+ }
45
+
46
+ .danger {
47
+ background-color: var(--color-red-600);
48
+ color: white;
49
+ }
50
+
51
+ .danger:hover:not(:disabled) {
52
+ background-color: var(--color-red-500);
53
+ }
54
+
55
+ /* Sizes */
56
+ .sm {
57
+ padding: var(--spacing-1) var(--spacing-2);
58
+ font-size: 0.75rem;
59
+ }
60
+
61
+ .md {
62
+ padding: var(--spacing-2) var(--spacing-4);
63
+ font-size: 0.875rem;
64
+ }
65
+
66
+ .lg {
67
+ padding: var(--spacing-3) var(--spacing-4);
68
+ font-size: 1rem;
69
+ }
70
+
71
+ /* Loading spinner */
72
+ .spinner {
73
+ width: 1rem;
74
+ height: 1rem;
75
+ margin-right: var(--spacing-2);
76
+ border: 2px solid currentColor;
77
+ border-top-color: transparent;
78
+ border-radius: var(--radius-full);
79
+ animation: spin 0.6s linear infinite;
80
+ }
81
+
82
+ @keyframes spin {
83
+ to {
84
+ transform: rotate(360deg);
85
+ }
86
+ }
87
+
@@ -0,0 +1,91 @@
1
+ /* CSS Custom Properties (Design Tokens) */
2
+ :root {
3
+ /* Colors */
4
+ --color-primary-50: #eff6ff;
5
+ --color-primary-100: #dbeafe;
6
+ --color-primary-500: #3b82f6;
7
+ --color-primary-600: #2563eb;
8
+ --color-primary-700: #1d4ed8;
9
+
10
+ --color-gray-50: #f9fafb;
11
+ --color-gray-100: #f3f4f6;
12
+ --color-gray-200: #e5e7eb;
13
+ --color-gray-300: #d1d5db;
14
+ --color-gray-400: #9ca3af;
15
+ --color-gray-500: #6b7280;
16
+ --color-gray-600: #4b5563;
17
+ --color-gray-700: #374151;
18
+ --color-gray-800: #1f2937;
19
+ --color-gray-900: #111827;
20
+
21
+ --color-red-500: #ef4444;
22
+ --color-red-600: #dc2626;
23
+
24
+ /* Typography */
25
+ --font-family-sans: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
26
+ Roboto, 'Helvetica Neue', Arial, sans-serif;
27
+ --font-family-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
28
+ monospace;
29
+
30
+ /* Spacing */
31
+ --spacing-1: 0.25rem;
32
+ --spacing-2: 0.5rem;
33
+ --spacing-3: 0.75rem;
34
+ --spacing-4: 1rem;
35
+ --spacing-6: 1.5rem;
36
+ --spacing-8: 2rem;
37
+
38
+ /* Border Radius */
39
+ --radius-sm: 0.25rem;
40
+ --radius-md: 0.375rem;
41
+ --radius-lg: 0.5rem;
42
+ --radius-full: 9999px;
43
+
44
+ /* Shadows */
45
+ --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
46
+ --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
47
+ }
48
+
49
+ /* Reset */
50
+ *,
51
+ *::before,
52
+ *::after {
53
+ box-sizing: border-box;
54
+ margin: 0;
55
+ padding: 0;
56
+ }
57
+
58
+ html {
59
+ -webkit-font-smoothing: antialiased;
60
+ -moz-osx-font-smoothing: grayscale;
61
+ }
62
+
63
+ body {
64
+ font-family: var(--font-family-sans);
65
+ line-height: 1.5;
66
+ color: var(--color-gray-900);
67
+ background-color: white;
68
+ }
69
+
70
+ img,
71
+ picture,
72
+ video,
73
+ canvas,
74
+ svg {
75
+ display: block;
76
+ max-width: 100%;
77
+ }
78
+
79
+ input,
80
+ button,
81
+ textarea,
82
+ select {
83
+ font: inherit;
84
+ }
85
+
86
+ /* Focus styles */
87
+ :focus-visible {
88
+ outline: 2px solid var(--color-primary-500);
89
+ outline-offset: 2px;
90
+ }
91
+
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "styling-tailwind",
3
+ "version": "1.0.0",
4
+ "description": "Tailwind CSS styling setup with modern configuration",
5
+ "compatibleWith": ["runtime-vite", "runtime-nextjs"],
6
+ "dependencies": {},
7
+ "devDependencies": {
8
+ "tailwindcss": "^3.4.0",
9
+ "postcss": "^8.4.0",
10
+ "autoprefixer": "^10.4.0"
11
+ },
12
+ "scripts": {},
13
+ "filePatterns": {
14
+ "include": ["**/*"],
15
+ "exclude": ["manifest.json"]
16
+ }
17
+ }
18
+
@@ -0,0 +1,7 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ };
7
+
@@ -0,0 +1,54 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ /* Custom base styles */
6
+ @layer base {
7
+ html {
8
+ @apply antialiased;
9
+ }
10
+
11
+ body {
12
+ @apply bg-white text-gray-900;
13
+ }
14
+
15
+ /* Focus styles for accessibility */
16
+ :focus-visible {
17
+ @apply outline-none ring-2 ring-indigo-500 ring-offset-2;
18
+ }
19
+ }
20
+
21
+ /* Custom component classes */
22
+ @layer components {
23
+ .container-app {
24
+ @apply mx-auto max-w-7xl px-4 sm:px-6 lg:px-8;
25
+ }
26
+
27
+ .card {
28
+ @apply rounded-lg border border-gray-200 bg-white p-6 shadow-sm;
29
+ }
30
+
31
+ .link {
32
+ @apply text-indigo-600 hover:text-indigo-500 hover:underline;
33
+ }
34
+ }
35
+
36
+ /* Custom utilities */
37
+ @layer utilities {
38
+ .text-balance {
39
+ text-wrap: balance;
40
+ }
41
+
42
+ .animation-delay-100 {
43
+ animation-delay: 100ms;
44
+ }
45
+
46
+ .animation-delay-200 {
47
+ animation-delay: 200ms;
48
+ }
49
+
50
+ .animation-delay-300 {
51
+ animation-delay: 300ms;
52
+ }
53
+ }
54
+
@@ -0,0 +1,62 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ export default {
3
+ content: [
4
+ './index.html',
5
+ './src/**/*.{js,ts,jsx,tsx}',
6
+ './app/**/*.{js,ts,jsx,tsx}',
7
+ ],
8
+ theme: {
9
+ extend: {
10
+ colors: {
11
+ // Add custom colors here
12
+ brand: {
13
+ 50: '#eff6ff',
14
+ 100: '#dbeafe',
15
+ 200: '#bfdbfe',
16
+ 300: '#93c5fd',
17
+ 400: '#60a5fa',
18
+ 500: '#3b82f6',
19
+ 600: '#2563eb',
20
+ 700: '#1d4ed8',
21
+ 800: '#1e40af',
22
+ 900: '#1e3a8a',
23
+ 950: '#172554',
24
+ },
25
+ },
26
+ fontFamily: {
27
+ sans: [
28
+ 'Inter',
29
+ 'system-ui',
30
+ '-apple-system',
31
+ 'BlinkMacSystemFont',
32
+ 'Segoe UI',
33
+ 'Roboto',
34
+ 'Helvetica Neue',
35
+ 'Arial',
36
+ 'sans-serif',
37
+ ],
38
+ },
39
+ animation: {
40
+ 'fade-in': 'fade-in 0.3s ease-out',
41
+ 'slide-up': 'slide-up 0.3s ease-out',
42
+ 'slide-down': 'slide-down 0.3s ease-out',
43
+ },
44
+ keyframes: {
45
+ 'fade-in': {
46
+ '0%': { opacity: '0' },
47
+ '100%': { opacity: '1' },
48
+ },
49
+ 'slide-up': {
50
+ '0%': { transform: 'translateY(10px)', opacity: '0' },
51
+ '100%': { transform: 'translateY(0)', opacity: '1' },
52
+ },
53
+ 'slide-down': {
54
+ '0%': { transform: 'translateY(-10px)', opacity: '0' },
55
+ '100%': { transform: 'translateY(0)', opacity: '1' },
56
+ },
57
+ },
58
+ },
59
+ },
60
+ plugins: [],
61
+ };
62
+
@@ -0,0 +1,24 @@
1
+ /** @type {import('jest').Config} */
2
+ const config = {
3
+ testEnvironment: 'jsdom',
4
+ setupFilesAfterEnv: ['<rootDir>/src/testing/setup.ts'],
5
+ testMatch: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'],
6
+ moduleNameMapper: {
7
+ '^@/(.*)$': '<rootDir>/src/$1',
8
+ },
9
+ transform: {
10
+ '^.+\\.(ts|tsx)$': ['ts-jest', {
11
+ tsconfig: 'tsconfig.json',
12
+ }],
13
+ },
14
+ collectCoverageFrom: [
15
+ 'src/**/*.{js,jsx,ts,tsx}',
16
+ '!src/**/*.d.ts',
17
+ '!src/testing/**',
18
+ '!src/**/index.ts',
19
+ ],
20
+ coverageReporters: ['text', 'json', 'html'],
21
+ };
22
+
23
+ module.exports = config;
24
+
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "testing-jest",
3
+ "version": "1.0.0",
4
+ "description": "Jest unit testing setup with React Testing Library",
5
+ "compatibleWith": ["runtime-vite", "runtime-nextjs"],
6
+ "dependencies": {},
7
+ "devDependencies": {
8
+ "jest": "^29.7.0",
9
+ "jest-environment-jsdom": "^29.7.0",
10
+ "@types/jest": "^29.5.0",
11
+ "@testing-library/react": "^16.0.0",
12
+ "@testing-library/jest-dom": "^6.4.0",
13
+ "@testing-library/user-event": "^14.5.0",
14
+ "ts-jest": "^29.2.0",
15
+ "msw": "^2.4.0"
16
+ },
17
+ "scripts": {
18
+ "test": "jest",
19
+ "test:watch": "jest --watch",
20
+ "test:coverage": "jest --coverage"
21
+ },
22
+ "filePatterns": {
23
+ "include": ["**/*"],
24
+ "exclude": ["manifest.json"]
25
+ }
26
+ }
27
+
@@ -0,0 +1,33 @@
1
+ import { render, screen } from '@/testing/test-utils';
2
+ import { Button } from '../Button';
3
+
4
+ describe('Button', () => {
5
+ it('renders children correctly', () => {
6
+ render(<Button>Click me</Button>);
7
+ expect(screen.getByRole('button', { name: /click me/i })).toBeInTheDocument();
8
+ });
9
+
10
+ it('handles click events', async () => {
11
+ const handleClick = jest.fn();
12
+ const { user } = render(<Button onClick={handleClick}>Click me</Button>);
13
+
14
+ await user.click(screen.getByRole('button'));
15
+ expect(handleClick).toHaveBeenCalledTimes(1);
16
+ });
17
+
18
+ it('shows loading state', () => {
19
+ render(<Button isLoading>Loading</Button>);
20
+ expect(screen.getByRole('button')).toBeDisabled();
21
+ });
22
+
23
+ it('can be disabled', () => {
24
+ render(<Button disabled>Disabled</Button>);
25
+ expect(screen.getByRole('button')).toBeDisabled();
26
+ });
27
+
28
+ it('applies variant styles', () => {
29
+ render(<Button variant="danger">Delete</Button>);
30
+ expect(screen.getByRole('button')).toHaveClass('bg-red-600');
31
+ });
32
+ });
33
+
@@ -0,0 +1,17 @@
1
+ import { setupWorker } from 'msw/browser';
2
+ import { handlers } from './handlers';
3
+
4
+ /**
5
+ * MSW worker for browser environment (development)
6
+ * Import and start in your app entry point for API mocking during development
7
+ *
8
+ * Example:
9
+ * ```ts
10
+ * if (process.env.NODE_ENV === 'development') {
11
+ * const { worker } = await import('./testing/mocks/browser');
12
+ * await worker.start({ onUnhandledRequest: 'bypass' });
13
+ * }
14
+ * ```
15
+ */
16
+ export const worker = setupWorker(...handlers);
17
+
@@ -0,0 +1,42 @@
1
+ import { http, HttpResponse } from 'msw';
2
+
3
+ /**
4
+ * MSW request handlers for mocking API calls in tests
5
+ * Following bulletproof-react patterns
6
+ */
7
+
8
+ const API_URL = 'http://localhost:3001';
9
+
10
+ export const handlers = [
11
+ // Example: GET /api/users
12
+ http.get(`${API_URL}/api/users`, () => {
13
+ return HttpResponse.json([
14
+ { id: '1', name: 'John Doe', email: 'john@example.com' },
15
+ { id: '2', name: 'Jane Smith', email: 'jane@example.com' },
16
+ ]);
17
+ }),
18
+
19
+ // Example: POST /api/auth/login
20
+ http.post(`${API_URL}/api/auth/login`, async ({ request }) => {
21
+ const body = await request.json() as { email: string; password: string };
22
+
23
+ if (body.email === 'test@example.com' && body.password === 'password') {
24
+ return HttpResponse.json({
25
+ user: {
26
+ id: '1',
27
+ email: 'test@example.com',
28
+ name: 'Test User',
29
+ },
30
+ token: 'mock-jwt-token',
31
+ });
32
+ }
33
+
34
+ return HttpResponse.json(
35
+ { message: 'Invalid credentials' },
36
+ { status: 401 }
37
+ );
38
+ }),
39
+
40
+ // Add more handlers as needed
41
+ ];
42
+
@@ -0,0 +1,8 @@
1
+ import { setupServer } from 'msw/node';
2
+ import { handlers } from './handlers';
3
+
4
+ /**
5
+ * MSW server for Node.js environment (tests)
6
+ */
7
+ export const server = setupServer(...handlers);
8
+
@@ -0,0 +1,20 @@
1
+ import '@testing-library/jest-dom';
2
+ import { cleanup } from '@testing-library/react';
3
+ import { server } from './mocks/server';
4
+
5
+ // Establish API mocking before all tests
6
+ beforeAll(() => {
7
+ server.listen({ onUnhandledRequest: 'error' });
8
+ });
9
+
10
+ // Clean up after each test
11
+ afterEach(() => {
12
+ cleanup();
13
+ server.resetHandlers();
14
+ });
15
+
16
+ // Clean up after all tests are done
17
+ afterAll(() => {
18
+ server.close();
19
+ });
20
+
@@ -0,0 +1,39 @@
1
+ import { ReactElement, ReactNode } from 'react';
2
+ import { render, RenderOptions } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import { BrowserRouter } from 'react-router-dom';
5
+
6
+ /**
7
+ * Custom render function that includes providers
8
+ * Following bulletproof-react testing patterns
9
+ */
10
+
11
+ type WrapperProps = {
12
+ children: ReactNode;
13
+ };
14
+
15
+ function AllProviders({ children }: WrapperProps) {
16
+ return (
17
+ <BrowserRouter>
18
+ {/* Add other providers here (React Query, Theme, etc.) */}
19
+ {children}
20
+ </BrowserRouter>
21
+ );
22
+ }
23
+
24
+ function customRender(
25
+ ui: ReactElement,
26
+ options?: Omit<RenderOptions, 'wrapper'>
27
+ ) {
28
+ return {
29
+ user: userEvent.setup(),
30
+ ...render(ui, { wrapper: AllProviders, ...options }),
31
+ };
32
+ }
33
+
34
+ // Re-export everything from testing-library
35
+ export * from '@testing-library/react';
36
+
37
+ // Override render method
38
+ export { customRender as render };
39
+
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "testing-playwright",
3
+ "version": "1.0.0",
4
+ "description": "Playwright E2E testing setup",
5
+ "compatibleWith": ["runtime-vite", "runtime-nextjs"],
6
+ "dependencies": {},
7
+ "devDependencies": {
8
+ "@playwright/test": "^1.45.0"
9
+ },
10
+ "scripts": {
11
+ "test:e2e": "playwright test",
12
+ "test:e2e:ui": "playwright test --ui",
13
+ "test:e2e:debug": "playwright test --debug",
14
+ "test:e2e:report": "playwright show-report"
15
+ },
16
+ "filePatterns": {
17
+ "include": ["**/*"],
18
+ "exclude": ["manifest.json"]
19
+ }
20
+ }
21
+
@@ -0,0 +1,53 @@
1
+ import { defineConfig, devices } from '@playwright/test';
2
+
3
+ /**
4
+ * Playwright configuration
5
+ * @see https://playwright.dev/docs/test-configuration
6
+ */
7
+ export default defineConfig({
8
+ testDir: './tests/e2e',
9
+ fullyParallel: true,
10
+ forbidOnly: !!process.env.CI,
11
+ retries: process.env.CI ? 2 : 0,
12
+ workers: process.env.CI ? 1 : undefined,
13
+ reporter: 'html',
14
+
15
+ use: {
16
+ baseURL: 'http://localhost:3000',
17
+ trace: 'on-first-retry',
18
+ screenshot: 'only-on-failure',
19
+ },
20
+
21
+ projects: [
22
+ {
23
+ name: 'chromium',
24
+ use: { ...devices['Desktop Chrome'] },
25
+ },
26
+ {
27
+ name: 'firefox',
28
+ use: { ...devices['Desktop Firefox'] },
29
+ },
30
+ {
31
+ name: 'webkit',
32
+ use: { ...devices['Desktop Safari'] },
33
+ },
34
+ // Mobile viewports
35
+ {
36
+ name: 'Mobile Chrome',
37
+ use: { ...devices['Pixel 5'] },
38
+ },
39
+ {
40
+ name: 'Mobile Safari',
41
+ use: { ...devices['iPhone 12'] },
42
+ },
43
+ ],
44
+
45
+ // Run your local dev server before starting the tests
46
+ webServer: {
47
+ command: 'npm run dev',
48
+ url: 'http://localhost:3000',
49
+ reuseExistingServer: !process.env.CI,
50
+ timeout: 120 * 1000,
51
+ },
52
+ });
53
+
@@ -0,0 +1,41 @@
1
+ import { test, expect } from '@playwright/test';
2
+ import AxeBuilder from '@axe-core/playwright';
3
+
4
+ test.describe('Accessibility', () => {
5
+ test('home page should not have any automatically detectable accessibility issues', async ({
6
+ page,
7
+ }) => {
8
+ await page.goto('/');
9
+
10
+ const accessibilityScanResults = await new AxeBuilder({ page }).analyze();
11
+
12
+ expect(accessibilityScanResults.violations).toEqual([]);
13
+ });
14
+
15
+ test('should be keyboard navigable', async ({ page }) => {
16
+ await page.goto('/');
17
+
18
+ // Tab to first interactive element
19
+ await page.keyboard.press('Tab');
20
+
21
+ // Check that something is focused
22
+ const focusedElement = await page.evaluate(
23
+ () => document.activeElement?.tagName
24
+ );
25
+ expect(focusedElement).toBeTruthy();
26
+ });
27
+
28
+ test('interactive elements should have visible focus states', async ({
29
+ page,
30
+ }) => {
31
+ await page.goto('/');
32
+
33
+ // Find a button or link
34
+ const button = page.getByRole('link', { name: /get started/i });
35
+ await button.focus();
36
+
37
+ // Check that focus is visible (element should have focus-visible styles)
38
+ await expect(button).toBeFocused();
39
+ });
40
+ });
41
+