create-react-forge 1.0.1 → 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.
- package/README.md +88 -61
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/parser.js +1 -1
- package/dist/cli/parser.js.map +1 -1
- package/dist/docs/architecture-generator.js +1 -1
- package/dist/generator/index.js +1 -1
- package/dist/generator/index.js.map +1 -1
- package/package.json +3 -2
- package/src/templates/overlays/base/manifest.json +16 -0
- package/src/templates/overlays/base/src/components/ui/Button.tsx +66 -0
- package/src/templates/overlays/base/src/components/ui/Input.tsx +51 -0
- package/src/templates/overlays/base/src/components/ui/index.ts +3 -0
- package/src/templates/overlays/base/src/hooks/use-disclosure.ts +21 -0
- package/src/templates/overlays/base/src/hooks/use-local-storage.ts +80 -0
- package/src/templates/overlays/base/src/lib/api-client.ts +101 -0
- package/src/templates/overlays/base/src/lib/utils.ts +34 -0
- package/src/templates/overlays/base/src/types/api.ts +47 -0
- package/src/templates/overlays/features/tanstack-query/manifest.json +17 -0
- package/src/templates/overlays/features/tanstack-query/src/features/users/api/create-user.ts +41 -0
- package/src/templates/overlays/features/tanstack-query/src/features/users/api/get-user.ts +27 -0
- package/src/templates/overlays/features/tanstack-query/src/features/users/api/get-users.ts +39 -0
- package/src/templates/overlays/features/tanstack-query/src/hooks/use-query-config.ts +42 -0
- package/src/templates/overlays/features/tanstack-query/src/lib/QueryProvider.tsx +28 -0
- package/src/templates/overlays/features/tanstack-query/src/lib/react-query.ts +46 -0
- package/src/templates/overlays/runtime/nextjs/manifest.json +27 -0
- package/src/templates/overlays/runtime/nextjs/next-env.d.ts +6 -0
- package/src/templates/overlays/runtime/nextjs/next.config.js +12 -0
- package/src/templates/overlays/runtime/nextjs/src/app/error.tsx +34 -0
- package/src/templates/overlays/runtime/nextjs/src/app/layout.tsx +23 -0
- package/src/templates/overlays/runtime/nextjs/src/app/loading.tsx +12 -0
- package/src/templates/overlays/runtime/nextjs/src/app/not-found.tsx +26 -0
- package/src/templates/overlays/runtime/nextjs/src/app/page.tsx +33 -0
- package/src/templates/overlays/runtime/nextjs/src/app/providers.tsx +14 -0
- package/src/templates/overlays/runtime/nextjs/src/styles/globals.css +50 -0
- package/src/templates/overlays/runtime/nextjs/tsconfig.json +29 -0
- package/src/templates/overlays/runtime/vite/index.html +14 -0
- package/src/templates/overlays/runtime/vite/manifest.json +28 -0
- package/src/templates/overlays/runtime/vite/public/vite.svg +2 -0
- package/src/templates/overlays/runtime/vite/src/app/App.tsx +11 -0
- package/src/templates/overlays/runtime/vite/src/app/provider.tsx +20 -0
- package/src/templates/overlays/runtime/vite/src/app/router.tsx +19 -0
- package/src/templates/overlays/runtime/vite/src/components/errors/ErrorFallback.tsx +21 -0
- package/src/templates/overlays/runtime/vite/src/components/ui/LoadingSpinner.tsx +23 -0
- package/src/templates/overlays/runtime/vite/src/features/misc/routes/Landing.tsx +33 -0
- package/src/templates/overlays/runtime/vite/src/features/misc/routes/NotFound.tsx +26 -0
- package/src/templates/overlays/runtime/vite/src/main.tsx +11 -0
- package/src/templates/overlays/runtime/vite/src/styles/globals.css +55 -0
- package/src/templates/overlays/runtime/vite/tsconfig.json +32 -0
- package/src/templates/overlays/runtime/vite/tsconfig.node.json +23 -0
- package/src/templates/overlays/runtime/vite/vite.config.ts +22 -0
- package/src/templates/overlays/state/redux/manifest.json +17 -0
- package/src/templates/overlays/state/redux/src/stores/Provider.tsx +17 -0
- package/src/templates/overlays/state/redux/src/stores/hooks.ts +11 -0
- package/src/templates/overlays/state/redux/src/stores/index.ts +17 -0
- package/src/templates/overlays/state/redux/src/stores/slices/auth.ts +54 -0
- package/src/templates/overlays/state/redux/src/stores/slices/notifications.ts +58 -0
- package/src/templates/overlays/state/redux/src/stores/store.ts +27 -0
- package/src/templates/overlays/state/zustand/manifest.json +16 -0
- package/src/templates/overlays/state/zustand/src/stores/auth.ts +48 -0
- package/src/templates/overlays/state/zustand/src/stores/index.ts +3 -0
- package/src/templates/overlays/state/zustand/src/stores/notifications.ts +54 -0
- package/src/templates/overlays/styling/css-modules/manifest.json +14 -0
- package/src/templates/overlays/styling/css-modules/src/components/ui/Button.module.css +87 -0
- package/src/templates/overlays/styling/css-modules/src/styles/globals.css +91 -0
- package/src/templates/overlays/styling/tailwind/manifest.json +18 -0
- package/src/templates/overlays/styling/tailwind/postcss.config.js +7 -0
- package/src/templates/overlays/styling/tailwind/src/styles/globals.css +54 -0
- package/src/templates/overlays/styling/tailwind/tailwind.config.js +62 -0
- package/src/templates/overlays/testing/jest/jest.config.js +24 -0
- package/src/templates/overlays/testing/jest/manifest.json +27 -0
- package/src/templates/overlays/testing/jest/src/components/ui/__tests__/Button.test.tsx +33 -0
- package/src/templates/overlays/testing/jest/src/testing/mocks/browser.ts +17 -0
- package/src/templates/overlays/testing/jest/src/testing/mocks/handlers.ts +42 -0
- package/src/templates/overlays/testing/jest/src/testing/mocks/server.ts +8 -0
- package/src/templates/overlays/testing/jest/src/testing/setup.ts +20 -0
- package/src/templates/overlays/testing/jest/src/testing/test-utils.tsx +39 -0
- package/src/templates/overlays/testing/playwright/manifest.json +21 -0
- package/src/templates/overlays/testing/playwright/playwright.config.ts +53 -0
- package/src/templates/overlays/testing/playwright/tests/e2e/accessibility.spec.ts +41 -0
- package/src/templates/overlays/testing/playwright/tests/e2e/home.spec.ts +41 -0
- package/src/templates/overlays/testing/vitest/manifest.json +28 -0
- package/src/templates/overlays/testing/vitest/src/components/ui/__tests__/Button.test.tsx +34 -0
- package/src/templates/overlays/testing/vitest/src/testing/mocks/browser.ts +17 -0
- package/src/templates/overlays/testing/vitest/src/testing/mocks/handlers.ts +42 -0
- package/src/templates/overlays/testing/vitest/src/testing/mocks/server.ts +8 -0
- package/src/templates/overlays/testing/vitest/src/testing/setup.ts +21 -0
- package/src/templates/overlays/testing/vitest/src/testing/test-utils.tsx +39 -0
- package/src/templates/overlays/testing/vitest/vitest.config.ts +31 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { generateId } from '@/lib/utils';
|
|
2
|
+
import { create } from 'zustand';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Notification store following bulletproof-react patterns
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type NotificationType = 'info' | 'success' | 'warning' | 'error';
|
|
9
|
+
|
|
10
|
+
export type Notification = {
|
|
11
|
+
id: string;
|
|
12
|
+
type: NotificationType;
|
|
13
|
+
title: string;
|
|
14
|
+
message?: string;
|
|
15
|
+
duration?: number;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type NotificationsState = {
|
|
19
|
+
notifications: Notification[];
|
|
20
|
+
addNotification: (notification: Omit<Notification, 'id'>) => void;
|
|
21
|
+
removeNotification: (id: string) => void;
|
|
22
|
+
clearNotifications: () => void;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const useNotifications = create<NotificationsState>((set) => ({
|
|
26
|
+
notifications: [],
|
|
27
|
+
|
|
28
|
+
addNotification: (notification) => {
|
|
29
|
+
const id = generateId();
|
|
30
|
+
const newNotification = { ...notification, id };
|
|
31
|
+
|
|
32
|
+
set((state) => ({
|
|
33
|
+
notifications: [...state.notifications, newNotification],
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
// Auto-remove after duration (default 5 seconds)
|
|
37
|
+
const duration = notification.duration ?? 5000;
|
|
38
|
+
if (duration > 0) {
|
|
39
|
+
setTimeout(() => {
|
|
40
|
+
set((state) => ({
|
|
41
|
+
notifications: state.notifications.filter((n) => n.id !== id),
|
|
42
|
+
}));
|
|
43
|
+
}, duration);
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
removeNotification: (id) =>
|
|
48
|
+
set((state) => ({
|
|
49
|
+
notifications: state.notifications.filter((n) => n.id !== id),
|
|
50
|
+
})),
|
|
51
|
+
|
|
52
|
+
clearNotifications: () => set({ notifications: [] }),
|
|
53
|
+
}));
|
|
54
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "styling-css-modules",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CSS Modules styling with scoped CSS",
|
|
5
|
+
"compatibleWith": ["runtime-vite", "runtime-nextjs"],
|
|
6
|
+
"dependencies": {},
|
|
7
|
+
"devDependencies": {},
|
|
8
|
+
"scripts": {},
|
|
9
|
+
"filePatterns": {
|
|
10
|
+
"include": ["**/*"],
|
|
11
|
+
"exclude": ["manifest.json"]
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
@@ -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,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,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
|
+
|