@odvi/create-dtt-framework 0.1.3 → 0.1.5

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 (111) hide show
  1. package/dist/commands/create.d.ts.map +1 -1
  2. package/dist/commands/create.js +16 -13
  3. package/dist/commands/create.js.map +1 -1
  4. package/package.json +3 -2
  5. package/template/.env.example +103 -0
  6. package/template/components.json +22 -0
  7. package/template/docs/framework/01-overview.md +289 -0
  8. package/template/docs/framework/02-techstack.md +503 -0
  9. package/template/docs/framework/api-layer.md +681 -0
  10. package/template/docs/framework/clerk-authentication.md +649 -0
  11. package/template/docs/framework/cli-installation.md +564 -0
  12. package/template/docs/framework/deployment/ci-cd.md +907 -0
  13. package/template/docs/framework/deployment/digitalocean.md +991 -0
  14. package/template/docs/framework/deployment/domain-setup.md +972 -0
  15. package/template/docs/framework/deployment/environment-variables.md +863 -0
  16. package/template/docs/framework/deployment/monitoring.md +927 -0
  17. package/template/docs/framework/deployment/production-checklist.md +649 -0
  18. package/template/docs/framework/deployment/vercel.md +791 -0
  19. package/template/docs/framework/environment-variables.md +658 -0
  20. package/template/docs/framework/health-check-system.md +582 -0
  21. package/template/docs/framework/implementation.md +559 -0
  22. package/template/docs/framework/snowflake-integration.md +591 -0
  23. package/template/docs/framework/state-management.md +615 -0
  24. package/template/docs/framework/supabase-integration.md +581 -0
  25. package/template/docs/framework/testing-guide.md +544 -0
  26. package/template/docs/framework/what-did-i-miss.md +526 -0
  27. package/template/drizzle.config.ts +12 -0
  28. package/template/next.config.js +21 -0
  29. package/template/postcss.config.js +5 -0
  30. package/template/prettier.config.js +4 -0
  31. package/template/public/favicon.ico +0 -0
  32. package/template/src/app/(auth)/layout.tsx +4 -0
  33. package/template/src/app/(auth)/sign-in/[[...sign-in]]/page.tsx +10 -0
  34. package/template/src/app/(auth)/sign-up/[[...sign-up]]/page.tsx +10 -0
  35. package/template/src/app/(dashboard)/dashboard/page.tsx +8 -0
  36. package/template/src/app/(dashboard)/health/page.tsx +16 -0
  37. package/template/src/app/(dashboard)/layout.tsx +17 -0
  38. package/template/src/app/api/[[...route]]/route.ts +11 -0
  39. package/template/src/app/api/debug-files/route.ts +33 -0
  40. package/template/src/app/api/webhooks/clerk/route.ts +112 -0
  41. package/template/src/app/layout.tsx +28 -0
  42. package/template/src/app/page.tsx +12 -0
  43. package/template/src/app/providers.tsx +20 -0
  44. package/template/src/components/layouts/navbar.tsx +14 -0
  45. package/template/src/components/shared/loading-spinner.tsx +6 -0
  46. package/template/src/components/ui/badge.tsx +46 -0
  47. package/template/src/components/ui/button.tsx +62 -0
  48. package/template/src/components/ui/card.tsx +92 -0
  49. package/template/src/components/ui/collapsible.tsx +33 -0
  50. package/template/src/components/ui/scroll-area.tsx +58 -0
  51. package/template/src/components/ui/sheet.tsx +139 -0
  52. package/template/src/config/__tests__/env.test.ts +166 -0
  53. package/template/src/config/__tests__/site.test.ts +46 -0
  54. package/template/src/config/env.ts +36 -0
  55. package/template/src/config/site.ts +10 -0
  56. package/template/src/env.js +44 -0
  57. package/template/src/features/__tests__/health-check-config.test.ts +142 -0
  58. package/template/src/features/__tests__/health-check-types.test.ts +201 -0
  59. package/template/src/features/documentation/components/doc-sidebar.tsx +109 -0
  60. package/template/src/features/documentation/components/doc-viewer.tsx +70 -0
  61. package/template/src/features/documentation/index.tsx +92 -0
  62. package/template/src/features/documentation/utils/doc-loader.ts +177 -0
  63. package/template/src/features/health-check/components/health-dashboard.tsx +363 -0
  64. package/template/src/features/health-check/config.ts +72 -0
  65. package/template/src/features/health-check/index.ts +4 -0
  66. package/template/src/features/health-check/stores/health-store.ts +14 -0
  67. package/template/src/features/health-check/types.ts +18 -0
  68. package/template/src/hooks/__tests__/use-debounce.test.tsx +28 -0
  69. package/template/src/hooks/queries/use-health-checks.ts +16 -0
  70. package/template/src/hooks/utils/use-debounce.ts +20 -0
  71. package/template/src/lib/__tests__/utils.test.ts +52 -0
  72. package/template/src/lib/__tests__/validators.test.ts +114 -0
  73. package/template/src/lib/nextbank/client.ts +37 -0
  74. package/template/src/lib/snowflake/client.ts +53 -0
  75. package/template/src/lib/supabase/admin.ts +7 -0
  76. package/template/src/lib/supabase/client.ts +7 -0
  77. package/template/src/lib/supabase/server.ts +23 -0
  78. package/template/src/lib/utils.ts +6 -0
  79. package/template/src/lib/validators.ts +9 -0
  80. package/template/src/middleware.ts +22 -0
  81. package/template/src/server/api/index.ts +22 -0
  82. package/template/src/server/api/middleware/auth.ts +19 -0
  83. package/template/src/server/api/middleware/logger.ts +4 -0
  84. package/template/src/server/api/routes/health/clerk.ts +214 -0
  85. package/template/src/server/api/routes/health/database.ts +117 -0
  86. package/template/src/server/api/routes/health/edge-functions.ts +75 -0
  87. package/template/src/server/api/routes/health/framework.ts +45 -0
  88. package/template/src/server/api/routes/health/index.ts +102 -0
  89. package/template/src/server/api/routes/health/nextbank.ts +67 -0
  90. package/template/src/server/api/routes/health/snowflake.ts +83 -0
  91. package/template/src/server/api/routes/health/storage.ts +163 -0
  92. package/template/src/server/api/routes/users.ts +95 -0
  93. package/template/src/server/db/index.ts +17 -0
  94. package/template/src/server/db/queries/users.ts +8 -0
  95. package/template/src/server/db/schema/__tests__/health-checks.test.ts +31 -0
  96. package/template/src/server/db/schema/__tests__/users.test.ts +46 -0
  97. package/template/src/server/db/schema/health-checks.ts +11 -0
  98. package/template/src/server/db/schema/index.ts +2 -0
  99. package/template/src/server/db/schema/users.ts +16 -0
  100. package/template/src/server/db/schema.ts +26 -0
  101. package/template/src/stores/__tests__/ui-store.test.ts +87 -0
  102. package/template/src/stores/ui-store.ts +14 -0
  103. package/template/src/styles/globals.css +129 -0
  104. package/template/src/test/mocks/clerk.ts +35 -0
  105. package/template/src/test/mocks/snowflake.ts +28 -0
  106. package/template/src/test/mocks/supabase.ts +37 -0
  107. package/template/src/test/setup.ts +69 -0
  108. package/template/src/test/utils/test-helpers.ts +158 -0
  109. package/template/src/types/index.ts +14 -0
  110. package/template/tsconfig.json +43 -0
  111. package/template/vitest.config.ts +44 -0
@@ -0,0 +1,46 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { users, type User, type NewUser } from '../users';
3
+
4
+ describe('users schema', () => {
5
+ it('should have users table defined', () => {
6
+ expect(users).toBeDefined();
7
+ });
8
+
9
+ it('should export User type', () => {
10
+ const user: User = {
11
+ id: 'test-id',
12
+ email: 'test@example.com',
13
+ firstName: 'Test',
14
+ lastName: 'User',
15
+ imageUrl: 'https://example.com/image.jpg',
16
+ clerkOrgId: 'org-123',
17
+ createdAt: new Date(),
18
+ updatedAt: new Date(),
19
+ };
20
+ expect(user).toBeDefined();
21
+ expect(user.id).toBe('test-id');
22
+ });
23
+
24
+ it('should export NewUser type', () => {
25
+ const newUser: NewUser = {
26
+ id: 'test-id',
27
+ email: 'test@example.com',
28
+ firstName: 'Test',
29
+ lastName: 'User',
30
+ imageUrl: 'https://example.com/image.jpg',
31
+ clerkOrgId: 'org-123',
32
+ };
33
+ expect(newUser).toBeDefined();
34
+ expect(newUser.email).toBe('test@example.com');
35
+ });
36
+
37
+ it('should allow NewUser without optional fields', () => {
38
+ const newUser: NewUser = {
39
+ id: 'test-id',
40
+ email: 'test@example.com',
41
+ };
42
+ expect(newUser).toBeDefined();
43
+ expect(newUser.id).toBe('test-id');
44
+ expect(newUser.email).toBe('test@example.com');
45
+ });
46
+ });
@@ -0,0 +1,11 @@
1
+ // Health checks table schema placeholder
2
+ import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'
3
+
4
+ export const healthCheckTests = pgTable('health_check_tests', {
5
+ id: uuid('id').primaryKey().defaultRandom(),
6
+ testKey: text('test_key').notNull(),
7
+ testValue: text('test_value'),
8
+ createdAt: timestamp('created_at').defaultNow().notNull(),
9
+ })
10
+
11
+ export type HealthCheckTest = typeof healthCheckTests.$inferSelect
@@ -0,0 +1,2 @@
1
+ export * from './users'
2
+ export * from './health-checks'
@@ -0,0 +1,16 @@
1
+ // Users table schema placeholder
2
+ import { pgTable, text, timestamp, varchar } from 'drizzle-orm/pg-core'
3
+
4
+ export const users = pgTable('users', {
5
+ id: text('id').primaryKey(),
6
+ email: varchar('email', { length: 255 }).notNull().unique(),
7
+ firstName: varchar('first_name', { length: 255 }),
8
+ lastName: varchar('last_name', { length: 255 }),
9
+ imageUrl: text('image_url'),
10
+ clerkOrgId: text('clerk_org_id'),
11
+ createdAt: timestamp('created_at').defaultNow().notNull(),
12
+ updatedAt: timestamp('updated_at').defaultNow().notNull(),
13
+ })
14
+
15
+ export type User = typeof users.$inferSelect
16
+ export type NewUser = typeof users.$inferInsert
@@ -0,0 +1,26 @@
1
+ // Example model schema from the Drizzle docs
2
+ // https://orm.drizzle.team/docs/sql-schema-declaration
3
+
4
+ import { index, pgTableCreator } from "drizzle-orm/pg-core";
5
+
6
+ /**
7
+ * This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same
8
+ * database instance for multiple projects.
9
+ *
10
+ * @see https://orm.drizzle.team/docs/goodies#multi-project-schema
11
+ */
12
+ export const createTable = pgTableCreator((name) => `dtt-framework_${name}`);
13
+
14
+ export const posts = createTable(
15
+ "post",
16
+ (d) => ({
17
+ id: d.integer().primaryKey().generatedByDefaultAsIdentity(),
18
+ name: d.varchar({ length: 256 }),
19
+ createdAt: d
20
+ .timestamp({ withTimezone: true })
21
+ .$defaultFn(() => /* @__PURE__ */ new Date())
22
+ .notNull(),
23
+ updatedAt: d.timestamp({ withTimezone: true }).$onUpdate(() => new Date()),
24
+ }),
25
+ (t) => [index("name_idx").on(t.name)],
26
+ );
@@ -0,0 +1,87 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { useUIStore } from '../ui-store';
3
+
4
+ describe('useUIStore', () => {
5
+ beforeEach(() => {
6
+ // Reset store state before each test
7
+ useUIStore.setState({ sidebarOpen: false });
8
+ });
9
+
10
+ it('should have initial state with sidebar closed', () => {
11
+ const state = useUIStore.getState();
12
+ expect(state.sidebarOpen).toBe(false);
13
+ });
14
+
15
+ it('should toggle sidebar from closed to open', () => {
16
+ const { toggleSidebar } = useUIStore.getState();
17
+ toggleSidebar();
18
+
19
+ const state = useUIStore.getState();
20
+ expect(state.sidebarOpen).toBe(true);
21
+ });
22
+
23
+ it('should toggle sidebar from open to closed', () => {
24
+ useUIStore.setState({ sidebarOpen: true });
25
+ const { toggleSidebar } = useUIStore.getState();
26
+ toggleSidebar();
27
+
28
+ const state = useUIStore.getState();
29
+ expect(state.sidebarOpen).toBe(false);
30
+ });
31
+
32
+ it('should toggle sidebar multiple times', () => {
33
+ const { toggleSidebar } = useUIStore.getState();
34
+
35
+ toggleSidebar();
36
+ expect(useUIStore.getState().sidebarOpen).toBe(true);
37
+
38
+ toggleSidebar();
39
+ expect(useUIStore.getState().sidebarOpen).toBe(false);
40
+
41
+ toggleSidebar();
42
+ expect(useUIStore.getState().sidebarOpen).toBe(true);
43
+
44
+ toggleSidebar();
45
+ expect(useUIStore.getState().sidebarOpen).toBe(false);
46
+ });
47
+
48
+ it('should provide toggleSidebar function', () => {
49
+ const state = useUIStore.getState();
50
+ expect(state.toggleSidebar).toBeDefined();
51
+ expect(typeof state.toggleSidebar).toBe('function');
52
+ });
53
+
54
+ it('should maintain state consistency across multiple store accesses', () => {
55
+ const state1 = useUIStore.getState();
56
+ const state2 = useUIStore.getState();
57
+
58
+ expect(state1.sidebarOpen).toBe(state2.sidebarOpen);
59
+ expect(state1.toggleSidebar).toBe(state2.toggleSidebar);
60
+ });
61
+
62
+ it('should allow direct state setting', () => {
63
+ useUIStore.setState({ sidebarOpen: true });
64
+
65
+ const state = useUIStore.getState();
66
+ expect(state.sidebarOpen).toBe(true);
67
+ });
68
+
69
+ it('should handle rapid state changes', () => {
70
+ const { toggleSidebar } = useUIStore.getState();
71
+
72
+ for (let i = 0; i < 10; i++) {
73
+ toggleSidebar();
74
+ }
75
+
76
+ const state = useUIStore.getState();
77
+ expect(state.sidebarOpen).toBe(false); // 10 toggles from false = false
78
+ });
79
+
80
+ it('should reset to initial state when explicitly set', () => {
81
+ useUIStore.setState({ sidebarOpen: true });
82
+ expect(useUIStore.getState().sidebarOpen).toBe(true);
83
+
84
+ useUIStore.setState({ sidebarOpen: false });
85
+ expect(useUIStore.getState().sidebarOpen).toBe(false);
86
+ });
87
+ });
@@ -0,0 +1,14 @@
1
+ // UI store placeholder
2
+ 'use client'
3
+
4
+ import { create } from 'zustand'
5
+
6
+ interface UIState {
7
+ sidebarOpen: boolean
8
+ toggleSidebar: () => void
9
+ }
10
+
11
+ export const useUIStore = create<UIState>((set) => ({
12
+ sidebarOpen: false,
13
+ toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),
14
+ }))
@@ -0,0 +1,129 @@
1
+ @import "tailwindcss";
2
+ @plugin "@tailwindcss/typography";
3
+ @import "tw-animate-css";
4
+
5
+ @custom-variant dark (&:is(.dark *));
6
+
7
+ @theme {
8
+ --font-sans: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif,
9
+ "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
10
+ }
11
+
12
+ @theme inline {
13
+ --radius-sm: calc(var(--radius) - 4px);
14
+ --radius-md: calc(var(--radius) - 2px);
15
+ --radius-lg: var(--radius);
16
+ --radius-xl: calc(var(--radius) + 4px);
17
+ --radius-2xl: calc(var(--radius) + 8px);
18
+ --radius-3xl: calc(var(--radius) + 12px);
19
+ --radius-4xl: calc(var(--radius) + 16px);
20
+ --color-background: var(--background);
21
+ --color-foreground: var(--foreground);
22
+ --color-card: var(--card);
23
+ --color-card-foreground: var(--card-foreground);
24
+ --color-popover: var(--popover);
25
+ --color-popover-foreground: var(--popover-foreground);
26
+ --color-primary: var(--primary);
27
+ --color-primary-foreground: var(--primary-foreground);
28
+ --color-secondary: var(--secondary);
29
+ --color-secondary-foreground: var(--secondary-foreground);
30
+ --color-muted: var(--muted);
31
+ --color-muted-foreground: var(--muted-foreground);
32
+ --color-accent: var(--accent);
33
+ --color-accent-foreground: var(--accent-foreground);
34
+ --color-destructive: var(--destructive);
35
+ --color-border: var(--border);
36
+ --color-input: var(--input);
37
+ --color-ring: var(--ring);
38
+ --color-chart-1: var(--chart-1);
39
+ --color-chart-2: var(--chart-2);
40
+ --color-chart-3: var(--chart-3);
41
+ --color-chart-4: var(--chart-4);
42
+ --color-chart-5: var(--chart-5);
43
+ --color-sidebar: var(--sidebar);
44
+ --color-sidebar-foreground: var(--sidebar-foreground);
45
+ --color-sidebar-primary: var(--sidebar-primary);
46
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
47
+ --color-sidebar-accent: var(--sidebar-accent);
48
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
49
+ --color-sidebar-border: var(--sidebar-border);
50
+ --color-sidebar-ring: var(--sidebar-ring);
51
+ }
52
+
53
+ :root {
54
+ --radius: 0.625rem;
55
+ --background: oklch(1 0 0);
56
+ --foreground: oklch(0.145 0 0);
57
+ --card: oklch(1 0 0);
58
+ --card-foreground: oklch(0.145 0 0);
59
+ --popover: oklch(1 0 0);
60
+ --popover-foreground: oklch(0.145 0 0);
61
+ --primary: oklch(0.205 0 0);
62
+ --primary-foreground: oklch(0.985 0 0);
63
+ --secondary: oklch(0.97 0 0);
64
+ --secondary-foreground: oklch(0.205 0 0);
65
+ --muted: oklch(0.97 0 0);
66
+ --muted-foreground: oklch(0.556 0 0);
67
+ --accent: oklch(0.97 0 0);
68
+ --accent-foreground: oklch(0.205 0 0);
69
+ --destructive: oklch(0.577 0.245 27.325);
70
+ --border: oklch(0.922 0 0);
71
+ --input: oklch(0.922 0 0);
72
+ --ring: oklch(0.708 0 0);
73
+ --chart-1: oklch(0.646 0.222 41.116);
74
+ --chart-2: oklch(0.6 0.118 184.704);
75
+ --chart-3: oklch(0.398 0.07 227.392);
76
+ --chart-4: oklch(0.828 0.189 84.429);
77
+ --chart-5: oklch(0.769 0.188 70.08);
78
+ --sidebar: oklch(0.985 0 0);
79
+ --sidebar-foreground: oklch(0.145 0 0);
80
+ --sidebar-primary: oklch(0.205 0 0);
81
+ --sidebar-primary-foreground: oklch(0.985 0 0);
82
+ --sidebar-accent: oklch(0.97 0 0);
83
+ --sidebar-accent-foreground: oklch(0.205 0 0);
84
+ --sidebar-border: oklch(0.922 0 0);
85
+ --sidebar-ring: oklch(0.708 0 0);
86
+ }
87
+
88
+ .dark {
89
+ --background: oklch(0.145 0 0);
90
+ --foreground: oklch(0.985 0 0);
91
+ --card: oklch(0.205 0 0);
92
+ --card-foreground: oklch(0.985 0 0);
93
+ --popover: oklch(0.205 0 0);
94
+ --popover-foreground: oklch(0.985 0 0);
95
+ --primary: oklch(0.922 0 0);
96
+ --primary-foreground: oklch(0.205 0 0);
97
+ --secondary: oklch(0.269 0 0);
98
+ --secondary-foreground: oklch(0.985 0 0);
99
+ --muted: oklch(0.269 0 0);
100
+ --muted-foreground: oklch(0.708 0 0);
101
+ --accent: oklch(0.269 0 0);
102
+ --accent-foreground: oklch(0.985 0 0);
103
+ --destructive: oklch(0.704 0.191 22.216);
104
+ --border: oklch(1 0 0 / 10%);
105
+ --input: oklch(1 0 0 / 15%);
106
+ --ring: oklch(0.556 0 0);
107
+ --chart-1: oklch(0.488 0.243 264.376);
108
+ --chart-2: oklch(0.696 0.17 162.48);
109
+ --chart-3: oklch(0.769 0.188 70.08);
110
+ --chart-4: oklch(0.627 0.265 303.9);
111
+ --chart-5: oklch(0.645 0.246 16.439);
112
+ --sidebar: oklch(0.205 0 0);
113
+ --sidebar-foreground: oklch(0.985 0 0);
114
+ --sidebar-primary: oklch(0.488 0.243 264.376);
115
+ --sidebar-primary-foreground: oklch(0.985 0 0);
116
+ --sidebar-accent: oklch(0.269 0 0);
117
+ --sidebar-accent-foreground: oklch(0.985 0 0);
118
+ --sidebar-border: oklch(1 0 0 / 10%);
119
+ --sidebar-ring: oklch(0.556 0 0);
120
+ }
121
+
122
+ @layer base {
123
+ * {
124
+ @apply border-border outline-ring/50;
125
+ }
126
+ body {
127
+ @apply bg-background text-foreground;
128
+ }
129
+ }
@@ -0,0 +1,35 @@
1
+ import { vi } from 'vitest';
2
+
3
+ // Mock Clerk
4
+ export const mockClerkClient = {
5
+ users: {
6
+ getUser: vi.fn(),
7
+ getUserList: vi.fn(),
8
+ },
9
+ organizations: {
10
+ getOrganizationMembershipList: vi.fn(),
11
+ getOrganizationList: vi.fn(),
12
+ },
13
+ };
14
+
15
+ vi.mock('@clerk/nextjs', () => ({
16
+ auth: vi.fn(),
17
+ currentUser: vi.fn(),
18
+ ClerkProvider: ({ children }: { children: React.ReactNode }) => children,
19
+ SignedIn: ({ children }: { children: React.ReactNode }) => children,
20
+ SignedOut: ({ children }: { children: React.ReactNode }) => null,
21
+ UserButton: vi.fn(() => null),
22
+ SignIn: vi.fn(() => null),
23
+ SignUp: vi.fn(() => null),
24
+ useUser: vi.fn(() => ({
25
+ user: null,
26
+ isLoaded: true,
27
+ })),
28
+ useAuth: vi.fn(() => ({
29
+ userId: null,
30
+ sessionId: null,
31
+ isLoaded: true,
32
+ })),
33
+ }));
34
+
35
+ export default mockClerkClient;
@@ -0,0 +1,28 @@
1
+ import { vi } from 'vitest';
2
+
3
+ // Mock Snowflake connection
4
+ export const mockSnowflakeConnection = {
5
+ connect: vi.fn((callback) => {
6
+ callback(null, mockSnowflakeConnection);
7
+ }),
8
+ execute: vi.fn((options: any) => {
9
+ if (options.complete) {
10
+ options.complete(null, null, []);
11
+ }
12
+ }),
13
+ destroy: vi.fn((callback) => {
14
+ callback(null);
15
+ }),
16
+ isUp: vi.fn(() => true),
17
+ getId: vi.fn(() => 'test-connection-id'),
18
+ };
19
+
20
+ // Mock Snowflake SDK
21
+ export const mockSnowflakeSDK = {
22
+ createConnection: vi.fn(() => mockSnowflakeConnection),
23
+ createConnector: vi.fn(),
24
+ };
25
+
26
+ vi.mock('snowflake-sdk', () => mockSnowflakeSDK);
27
+
28
+ export default mockSnowflakeConnection;
@@ -0,0 +1,37 @@
1
+ import { vi } from 'vitest';
2
+
3
+ // Mock Supabase client
4
+ export const mockSupabaseClient = {
5
+ from: vi.fn(() => mockSupabaseClient),
6
+ select: vi.fn(() => mockSupabaseClient),
7
+ insert: vi.fn(() => mockSupabaseClient),
8
+ update: vi.fn(() => mockSupabaseClient),
9
+ delete: vi.fn(() => mockSupabaseClient),
10
+ eq: vi.fn(() => mockSupabaseClient),
11
+ order: vi.fn(() => mockSupabaseClient),
12
+ limit: vi.fn(() => mockSupabaseClient),
13
+ single: vi.fn(),
14
+ maybeSingle: vi.fn(),
15
+ rpc: vi.fn(),
16
+ auth: {
17
+ getUser: vi.fn(),
18
+ signInWithPassword: vi.fn(),
19
+ signOut: vi.fn(),
20
+ onAuthStateChange: vi.fn(),
21
+ },
22
+ storage: {
23
+ from: vi.fn(() => ({
24
+ upload: vi.fn(),
25
+ download: vi.fn(),
26
+ remove: vi.fn(),
27
+ list: vi.fn(),
28
+ getPublicUrl: vi.fn(),
29
+ })),
30
+ },
31
+ };
32
+
33
+ vi.mock('@supabase/supabase-js', () => ({
34
+ createClient: vi.fn(() => mockSupabaseClient),
35
+ }));
36
+
37
+ export default mockSupabaseClient;
@@ -0,0 +1,69 @@
1
+ import '@testing-library/jest-dom';
2
+ import { cleanup } from '@testing-library/react';
3
+ import { afterEach, vi } from 'vitest';
4
+
5
+ // Cleanup after each test
6
+ afterEach(() => {
7
+ cleanup();
8
+ });
9
+
10
+ // Mock window.matchMedia
11
+ Object.defineProperty(window, 'matchMedia', {
12
+ writable: true,
13
+ value: vi.fn().mockImplementation((query) => ({
14
+ matches: false,
15
+ media: query,
16
+ onchange: null,
17
+ addListener: vi.fn(),
18
+ removeListener: vi.fn(),
19
+ addEventListener: vi.fn(),
20
+ removeEventListener: vi.fn(),
21
+ dispatchEvent: vi.fn(),
22
+ })),
23
+ });
24
+
25
+ // Mock IntersectionObserver
26
+ global.IntersectionObserver = class IntersectionObserver {
27
+ constructor() {}
28
+ disconnect() {}
29
+ observe() {}
30
+ takeRecords() {
31
+ return [];
32
+ }
33
+ unobserve() {}
34
+ } as any;
35
+
36
+ // Mock ResizeObserver
37
+ global.ResizeObserver = class ResizeObserver {
38
+ constructor() {}
39
+ disconnect() {}
40
+ observe() {}
41
+ unobserve() {}
42
+ } as any;
43
+
44
+ // Mock requestAnimationFrame
45
+ global.requestAnimationFrame = (callback: FrameRequestCallback) => {
46
+ return setTimeout(callback, 0) as unknown as number;
47
+ };
48
+
49
+ global.cancelAnimationFrame = (id: number) => {
50
+ clearTimeout(id);
51
+ };
52
+
53
+ // Suppress console errors in tests (optional, can be removed for debugging)
54
+ const originalError = console.error;
55
+ beforeAll(() => {
56
+ console.error = (...args: any[]) => {
57
+ if (
58
+ typeof args[0] === 'string' &&
59
+ args[0].includes('Warning: ReactDOM.render')
60
+ ) {
61
+ return;
62
+ }
63
+ originalError.call(console, ...args);
64
+ };
65
+ });
66
+
67
+ afterAll(() => {
68
+ console.error = originalError;
69
+ });
@@ -0,0 +1,158 @@
1
+ import type { RenderOptions } from '@testing-library/react';
2
+ import { render } from '@testing-library/react';
3
+ import type { ReactElement } from 'react';
4
+ import { vi, beforeEach, afterEach } from 'vitest';
5
+
6
+ /**
7
+ * Custom render function that includes providers if needed
8
+ */
9
+ export function renderWithProviders(
10
+ ui: ReactElement,
11
+ options?: Omit<RenderOptions, 'wrapper'>
12
+ ) {
13
+ return render(ui, options);
14
+ }
15
+
16
+ /**
17
+ * Create a mock function that returns a resolved value
18
+ */
19
+ export function mockResolved<T>(value: T) {
20
+ return vi.fn().mockResolvedValue(value);
21
+ }
22
+
23
+ /**
24
+ * Create a mock function that returns a rejected value
25
+ */
26
+ export function mockRejected(error: Error | string) {
27
+ return vi.fn().mockRejectedValue(error instanceof Error ? error : new Error(error));
28
+ }
29
+
30
+ /**
31
+ * Wait for a specified amount of time
32
+ */
33
+ export function wait(ms: number): Promise<void> {
34
+ return new Promise((resolve) => setTimeout(resolve, ms));
35
+ }
36
+
37
+ /**
38
+ * Create a mock user object
39
+ */
40
+ export function createMockUser(overrides: Partial<any> = {}) {
41
+ return {
42
+ id: 'user-123',
43
+ email: 'test@example.com',
44
+ firstName: 'Test',
45
+ lastName: 'User',
46
+ imageUrl: 'https://example.com/avatar.jpg',
47
+ clerkOrgId: 'org-123',
48
+ createdAt: new Date(),
49
+ updatedAt: new Date(),
50
+ ...overrides,
51
+ };
52
+ }
53
+
54
+ /**
55
+ * Create a mock health check result
56
+ */
57
+ export function createMockHealthCheck(overrides: Partial<any> = {}) {
58
+ return {
59
+ status: 'healthy',
60
+ responseTimeMs: 100,
61
+ message: 'Service is healthy',
62
+ ...overrides,
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Create a mock service health object
68
+ */
69
+ export function createMockServiceHealth(overrides: Partial<any> = {}) {
70
+ return {
71
+ name: 'Test Service',
72
+ icon: 'test-icon',
73
+ status: 'healthy',
74
+ responseTimeMs: 100,
75
+ checks: [
76
+ {
77
+ name: 'Test Check',
78
+ endpoint: '/test/endpoint',
79
+ status: 'healthy',
80
+ responseTimeMs: 50,
81
+ },
82
+ ],
83
+ ...overrides,
84
+ };
85
+ }
86
+
87
+ /**
88
+ * Mock console methods to suppress warnings in tests
89
+ */
90
+ export function suppressConsoleWarnings() {
91
+ const originalWarn = console.warn;
92
+ const originalError = console.error;
93
+
94
+ beforeEach(() => {
95
+ console.warn = vi.fn();
96
+ console.error = vi.fn();
97
+ });
98
+
99
+ afterEach(() => {
100
+ console.warn = originalWarn;
101
+ console.error = originalError;
102
+ });
103
+ }
104
+
105
+ /**
106
+ * Create a mock fetch response
107
+ */
108
+ export function createMockResponse(
109
+ data: any,
110
+ status: number = 200,
111
+ ok: boolean = true
112
+ ) {
113
+ return {
114
+ ok,
115
+ status,
116
+ json: vi.fn().mockResolvedValue(data),
117
+ text: vi.fn().mockResolvedValue(JSON.stringify(data)),
118
+ };
119
+ }
120
+
121
+ /**
122
+ * Mock localStorage
123
+ */
124
+ export function mockLocalStorage() {
125
+ const store: Record<string, string> = {};
126
+
127
+ return {
128
+ getItem: (key: string) => store[key] ?? null,
129
+ setItem: (key: string, value: string) => {
130
+ store[key] = value;
131
+ },
132
+ removeItem: (key: string) => {
133
+ delete store[key];
134
+ },
135
+ clear: () => {
136
+ Object.keys(store).forEach((key) => delete store[key]);
137
+ },
138
+ get length() {
139
+ return Object.keys(store).length;
140
+ },
141
+ key: (index: number) => Object.keys(store)[index] ?? null,
142
+ };
143
+ }
144
+
145
+ /**
146
+ * Create a mock router
147
+ */
148
+ export function createMockRouter() {
149
+ return {
150
+ push: vi.fn(),
151
+ replace: vi.fn(),
152
+ prefetch: vi.fn(),
153
+ back: vi.fn(),
154
+ pathname: '/',
155
+ query: {},
156
+ asPath: '/',
157
+ };
158
+ }
@@ -0,0 +1,14 @@
1
+ // Global type definitions placeholder
2
+
3
+ export interface User {
4
+ id: string
5
+ email: string
6
+ firstName?: string
7
+ lastName?: string
8
+ imageUrl?: string
9
+ clerkOrgId?: string
10
+ createdAt: Date
11
+ updatedAt: Date
12
+ }
13
+
14
+ export type HealthStatus = 'healthy' | 'unhealthy' | 'error' | 'pending' | 'unconfigured'