@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.
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +16 -13
- package/dist/commands/create.js.map +1 -1
- package/package.json +3 -2
- package/template/.env.example +103 -0
- package/template/components.json +22 -0
- package/template/docs/framework/01-overview.md +289 -0
- package/template/docs/framework/02-techstack.md +503 -0
- package/template/docs/framework/api-layer.md +681 -0
- package/template/docs/framework/clerk-authentication.md +649 -0
- package/template/docs/framework/cli-installation.md +564 -0
- package/template/docs/framework/deployment/ci-cd.md +907 -0
- package/template/docs/framework/deployment/digitalocean.md +991 -0
- package/template/docs/framework/deployment/domain-setup.md +972 -0
- package/template/docs/framework/deployment/environment-variables.md +863 -0
- package/template/docs/framework/deployment/monitoring.md +927 -0
- package/template/docs/framework/deployment/production-checklist.md +649 -0
- package/template/docs/framework/deployment/vercel.md +791 -0
- package/template/docs/framework/environment-variables.md +658 -0
- package/template/docs/framework/health-check-system.md +582 -0
- package/template/docs/framework/implementation.md +559 -0
- package/template/docs/framework/snowflake-integration.md +591 -0
- package/template/docs/framework/state-management.md +615 -0
- package/template/docs/framework/supabase-integration.md +581 -0
- package/template/docs/framework/testing-guide.md +544 -0
- package/template/docs/framework/what-did-i-miss.md +526 -0
- package/template/drizzle.config.ts +12 -0
- package/template/next.config.js +21 -0
- package/template/postcss.config.js +5 -0
- package/template/prettier.config.js +4 -0
- package/template/public/favicon.ico +0 -0
- package/template/src/app/(auth)/layout.tsx +4 -0
- package/template/src/app/(auth)/sign-in/[[...sign-in]]/page.tsx +10 -0
- package/template/src/app/(auth)/sign-up/[[...sign-up]]/page.tsx +10 -0
- package/template/src/app/(dashboard)/dashboard/page.tsx +8 -0
- package/template/src/app/(dashboard)/health/page.tsx +16 -0
- package/template/src/app/(dashboard)/layout.tsx +17 -0
- package/template/src/app/api/[[...route]]/route.ts +11 -0
- package/template/src/app/api/debug-files/route.ts +33 -0
- package/template/src/app/api/webhooks/clerk/route.ts +112 -0
- package/template/src/app/layout.tsx +28 -0
- package/template/src/app/page.tsx +12 -0
- package/template/src/app/providers.tsx +20 -0
- package/template/src/components/layouts/navbar.tsx +14 -0
- package/template/src/components/shared/loading-spinner.tsx +6 -0
- package/template/src/components/ui/badge.tsx +46 -0
- package/template/src/components/ui/button.tsx +62 -0
- package/template/src/components/ui/card.tsx +92 -0
- package/template/src/components/ui/collapsible.tsx +33 -0
- package/template/src/components/ui/scroll-area.tsx +58 -0
- package/template/src/components/ui/sheet.tsx +139 -0
- package/template/src/config/__tests__/env.test.ts +166 -0
- package/template/src/config/__tests__/site.test.ts +46 -0
- package/template/src/config/env.ts +36 -0
- package/template/src/config/site.ts +10 -0
- package/template/src/env.js +44 -0
- package/template/src/features/__tests__/health-check-config.test.ts +142 -0
- package/template/src/features/__tests__/health-check-types.test.ts +201 -0
- package/template/src/features/documentation/components/doc-sidebar.tsx +109 -0
- package/template/src/features/documentation/components/doc-viewer.tsx +70 -0
- package/template/src/features/documentation/index.tsx +92 -0
- package/template/src/features/documentation/utils/doc-loader.ts +177 -0
- package/template/src/features/health-check/components/health-dashboard.tsx +363 -0
- package/template/src/features/health-check/config.ts +72 -0
- package/template/src/features/health-check/index.ts +4 -0
- package/template/src/features/health-check/stores/health-store.ts +14 -0
- package/template/src/features/health-check/types.ts +18 -0
- package/template/src/hooks/__tests__/use-debounce.test.tsx +28 -0
- package/template/src/hooks/queries/use-health-checks.ts +16 -0
- package/template/src/hooks/utils/use-debounce.ts +20 -0
- package/template/src/lib/__tests__/utils.test.ts +52 -0
- package/template/src/lib/__tests__/validators.test.ts +114 -0
- package/template/src/lib/nextbank/client.ts +37 -0
- package/template/src/lib/snowflake/client.ts +53 -0
- package/template/src/lib/supabase/admin.ts +7 -0
- package/template/src/lib/supabase/client.ts +7 -0
- package/template/src/lib/supabase/server.ts +23 -0
- package/template/src/lib/utils.ts +6 -0
- package/template/src/lib/validators.ts +9 -0
- package/template/src/middleware.ts +22 -0
- package/template/src/server/api/index.ts +22 -0
- package/template/src/server/api/middleware/auth.ts +19 -0
- package/template/src/server/api/middleware/logger.ts +4 -0
- package/template/src/server/api/routes/health/clerk.ts +214 -0
- package/template/src/server/api/routes/health/database.ts +117 -0
- package/template/src/server/api/routes/health/edge-functions.ts +75 -0
- package/template/src/server/api/routes/health/framework.ts +45 -0
- package/template/src/server/api/routes/health/index.ts +102 -0
- package/template/src/server/api/routes/health/nextbank.ts +67 -0
- package/template/src/server/api/routes/health/snowflake.ts +83 -0
- package/template/src/server/api/routes/health/storage.ts +163 -0
- package/template/src/server/api/routes/users.ts +95 -0
- package/template/src/server/db/index.ts +17 -0
- package/template/src/server/db/queries/users.ts +8 -0
- package/template/src/server/db/schema/__tests__/health-checks.test.ts +31 -0
- package/template/src/server/db/schema/__tests__/users.test.ts +46 -0
- package/template/src/server/db/schema/health-checks.ts +11 -0
- package/template/src/server/db/schema/index.ts +2 -0
- package/template/src/server/db/schema/users.ts +16 -0
- package/template/src/server/db/schema.ts +26 -0
- package/template/src/stores/__tests__/ui-store.test.ts +87 -0
- package/template/src/stores/ui-store.ts +14 -0
- package/template/src/styles/globals.css +129 -0
- package/template/src/test/mocks/clerk.ts +35 -0
- package/template/src/test/mocks/snowflake.ts +28 -0
- package/template/src/test/mocks/supabase.ts +37 -0
- package/template/src/test/setup.ts +69 -0
- package/template/src/test/utils/test-helpers.ts +158 -0
- package/template/src/types/index.ts +14 -0
- package/template/tsconfig.json +43 -0
- 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,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'
|