firebase-os 1.1.4 → 1.1.6
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/FirebaseOS.d.ts +15 -0
- package/dist/firebase-os.cjs.js +5 -20
- package/dist/firebase-os.es.js +95 -90
- package/dist/index.d.ts +3 -0
- package/dist/lib/ConfigContext.d.ts +12 -0
- package/package.json +3 -2
- package/scripts/postinstall.js +86 -15
- package/src/App.css +184 -0
- package/src/App.tsx +214 -0
- package/src/FirebaseOS.tsx +81 -0
- package/src/assets/hero.png +0 -0
- package/src/assets/react.svg +1 -0
- package/src/assets/vite.svg +1 -0
- package/src/components/AdminNotifications.test.tsx +98 -0
- package/src/components/AdminNotifications.tsx +194 -0
- package/src/components/Button.test.tsx +22 -0
- package/src/components/Button.tsx +53 -0
- package/src/components/ConfirmModal.test.tsx +98 -0
- package/src/components/ConfirmModal.tsx +73 -0
- package/src/components/ContactPopup.test.tsx +98 -0
- package/src/components/ContactPopup.tsx +437 -0
- package/src/components/CustomSelect.test.tsx +47 -0
- package/src/components/CustomSelect.tsx +89 -0
- package/src/components/DashboardNav.test.tsx +98 -0
- package/src/components/DashboardNav.tsx +281 -0
- package/src/components/Input.test.tsx +33 -0
- package/src/components/Input.tsx +61 -0
- package/src/components/JsonEditor.tsx +579 -0
- package/src/components/Navbar.test.tsx +98 -0
- package/src/components/Navbar.tsx +563 -0
- package/src/configs/forms/contactForm.config.ts +15 -0
- package/src/configs/forms/index.ts +29 -0
- package/src/configs/forms/pubForm.config.ts +11 -0
- package/src/configs/forms/supportForm.config.ts +14 -0
- package/src/configs/forms/userForm.config.ts +11 -0
- package/src/configs/pages/admin.config.ts +29 -0
- package/src/configs/pages/contact.config.ts +6 -0
- package/src/configs/pages/home.config.ts +18 -0
- package/src/configs/pages/mem.config.ts +2 -0
- package/src/configs/pages/menuOrders.config.ts +11 -0
- package/src/configs/pages/pub.config.ts +11 -0
- package/src/configs/pages/shared.config.ts +29 -0
- package/src/configs/pages/support.config.ts +7 -0
- package/src/configs/pages/tabOrders.config.ts +33 -0
- package/src/configs/pages/user.config.ts +29 -0
- package/src/configs/theme.config.ts +93 -0
- package/src/index.css +403 -0
- package/src/index.ts +22 -0
- package/src/lib/AuthContext.test.tsx +88 -0
- package/src/lib/AuthContext.tsx +191 -0
- package/src/lib/ConfigContext.tsx +45 -0
- package/src/lib/ThemeContext.tsx +233 -0
- package/src/lib/firebase.ts +91 -0
- package/src/main.tsx +22 -0
- package/src/microcomponents/AdminExampleContent.tsx +44 -0
- package/src/microcomponents/PrivateExampleContent.tsx +39 -0
- package/src/microcomponents/Public.tsx +126 -0
- package/src/microcomponents/SharedExampleContent.tsx +53 -0
- package/src/pages/Dashboard.test.tsx +98 -0
- package/src/pages/Dashboard.tsx +60 -0
- package/src/pages/DynamicPage.tsx +237 -0
- package/src/pages/FormsAdmin.test.tsx +98 -0
- package/src/pages/FormsAdmin.tsx +459 -0
- package/src/pages/Home.test.tsx +98 -0
- package/src/pages/Home.tsx +144 -0
- package/src/pages/Login.test.tsx +98 -0
- package/src/pages/Login.tsx +108 -0
- package/src/pages/PagesAdmin.test.tsx +98 -0
- package/src/pages/PagesAdmin.tsx +1022 -0
- package/src/pages/Profile.test.tsx +98 -0
- package/src/pages/Profile.tsx +319 -0
- package/src/pages/Register.test.tsx +98 -0
- package/src/pages/Register.tsx +116 -0
- package/src/pages/Requests.test.tsx +95 -0
- package/src/pages/Requests.tsx +422 -0
- package/src/pages/ResetPassword.test.tsx +98 -0
- package/src/pages/ResetPassword.tsx +92 -0
- package/src/pages/Settings.test.tsx +98 -0
- package/src/pages/Settings.tsx +393 -0
- package/src/pages/Setup.tsx +407 -0
- package/src/pages/StorageAdmin.test.tsx +150 -0
- package/src/pages/StorageAdmin.tsx +769 -0
- package/src/pages/Submissions.test.tsx +95 -0
- package/src/pages/Submissions.tsx +378 -0
- package/src/pages/Templates.test.tsx +98 -0
- package/src/pages/Templates.tsx +103 -0
- package/src/pages/ThemeAdmin.test.tsx +144 -0
- package/src/pages/ThemeAdmin.tsx +1000 -0
- package/src/pages/Users.test.tsx +95 -0
- package/src/pages/Users.tsx +334 -0
- package/src/pages/Verify.test.tsx +98 -0
- package/src/pages/Verify.tsx +95 -0
- package/src/prompts/index.ts +13 -0
- package/src/prompts/pages/publicPage.ts +44 -0
- package/src/prompts/sharedConstants.ts +12 -0
- package/src/prompts/tabs/board/adminboard.ts +32 -0
- package/src/prompts/tabs/board/privateboard.ts +36 -0
- package/src/prompts/tabs/board/publicboard.ts +36 -0
- package/src/prompts/tabs/calendar/admincalendar.ts +32 -0
- package/src/prompts/tabs/calendar/privatecalendar.ts +36 -0
- package/src/prompts/tabs/calendar/publiccalendar.ts +36 -0
- package/src/prompts/tabs/crud/admin.ts +54 -0
- package/src/prompts/tabs/crud/private.ts +55 -0
- package/src/prompts/tabs/crud/shared.ts +53 -0
- package/src/prompts/tabs/table/admintable.ts +32 -0
- package/src/prompts/tabs/table/privatetable.ts +36 -0
- package/src/prompts/tabs/table/publictable.ts +36 -0
- package/src/setupTests.ts +1 -0
- package/src/templates/AdminPageTemplate.tsx +678 -0
- package/src/templates/PrivatePageTemplate.tsx +594 -0
- package/src/templates/PublicPageTemplate.tsx +92 -0
- package/src/templates/SharedPageTemplate.tsx +551 -0
- package/src/templates/TemplateBoard.test.tsx +106 -0
- package/src/templates/TemplateBoard.tsx +642 -0
- package/src/templates/TemplateCalendar.test.tsx +106 -0
- package/src/templates/TemplateCalendar.tsx +848 -0
- package/src/templates/TemplateConfirmation.test.tsx +106 -0
- package/src/templates/TemplateConfirmation.tsx +145 -0
- package/src/templates/TemplateInlineForm.test.tsx +106 -0
- package/src/templates/TemplateInlineForm.tsx +129 -0
- package/src/templates/TemplatePopupForm.test.tsx +106 -0
- package/src/templates/TemplatePopupForm.tsx +174 -0
- package/src/templates/TemplateTable.test.tsx +106 -0
- package/src/templates/TemplateTable.tsx +675 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { Navigate, useLocation } from 'react-router-dom';
|
|
3
|
+
import { collection, getDocs, doc, getDoc } from 'firebase/firestore';
|
|
4
|
+
import { db } from '../lib/firebase';
|
|
5
|
+
import { ContactPopup } from '../components/ContactPopup';
|
|
6
|
+
import { useAuth } from '../lib/AuthContext';
|
|
7
|
+
import { useConfig } from '../lib/ConfigContext';
|
|
8
|
+
import { PublicPageTemplate } from '../templates/PublicPageTemplate';
|
|
9
|
+
import { PrivatePageTemplate } from '../templates/PrivatePageTemplate';
|
|
10
|
+
import { AdminPageTemplate } from '../templates/AdminPageTemplate';
|
|
11
|
+
import { SharedPageTemplate } from '../templates/SharedPageTemplate';
|
|
12
|
+
import { TemplateBoard } from '../templates/TemplateBoard';
|
|
13
|
+
import { TemplateTable } from '../templates/TemplateTable';
|
|
14
|
+
import { TemplateCalendar } from '../templates/TemplateCalendar';
|
|
15
|
+
import { DashboardNav } from '../components/DashboardNav';
|
|
16
|
+
import { Loader2 } from 'lucide-react';
|
|
17
|
+
|
|
18
|
+
export function DynamicPage() {
|
|
19
|
+
const location = useLocation();
|
|
20
|
+
const { user } = useAuth();
|
|
21
|
+
const globalConfig = useConfig();
|
|
22
|
+
const [config, setConfig] = useState<any | null>(null);
|
|
23
|
+
const [loading, setLoading] = useState(true);
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const fetchConfigs = async () => {
|
|
27
|
+
setLoading(true);
|
|
28
|
+
try {
|
|
29
|
+
const [pubConfigsSnap, pubTabsSnap] = await Promise.all([
|
|
30
|
+
getDocs(collection(db, 'sys_pages')),
|
|
31
|
+
getDocs(collection(db, 'sys_tabs'))
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
let matchedConfig: any = null;
|
|
35
|
+
let matchedId: string | null = null;
|
|
36
|
+
|
|
37
|
+
const checkSnap = (snap: any, isPrivate: boolean) => {
|
|
38
|
+
snap.forEach((docSnap: any) => {
|
|
39
|
+
const d = docSnap.data();
|
|
40
|
+
let route = d.route;
|
|
41
|
+
if (route && !route.startsWith('/')) route = '/' + route;
|
|
42
|
+
|
|
43
|
+
// System Pages have strict document IDs tracking their canonical path (e.g. 'contact')
|
|
44
|
+
const pathMatchesSystemId = ('/' + docSnap.id) === location.pathname;
|
|
45
|
+
|
|
46
|
+
if (route === location.pathname || pathMatchesSystemId) {
|
|
47
|
+
matchedConfig = { ...d, isPrivate, isAdmin: d.pageType === 'admin', isShared: d.pageType === 'shared' };
|
|
48
|
+
matchedId = docSnap.id;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
checkSnap(pubConfigsSnap, false);
|
|
54
|
+
if (!matchedConfig) {
|
|
55
|
+
checkSnap(pubTabsSnap, true);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Fallbacks for core pages if not customized yet
|
|
59
|
+
if (!matchedConfig) {
|
|
60
|
+
if (location.pathname === '/contact') {
|
|
61
|
+
matchedConfig = { route: '/contact', template: 'popup_form', form: 'contact' };
|
|
62
|
+
matchedId = 'contact';
|
|
63
|
+
} else if (location.pathname === '/support') {
|
|
64
|
+
matchedConfig = { route: '/support', template: 'popup_form', form: 'support' };
|
|
65
|
+
matchedId = 'support';
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (matchedConfig) {
|
|
70
|
+
if ((matchedId === 'contact' || matchedId === 'support') && !matchedConfig.form) {
|
|
71
|
+
matchedConfig.form = matchedId;
|
|
72
|
+
}
|
|
73
|
+
matchedConfig.pageId = matchedId;
|
|
74
|
+
|
|
75
|
+
// Hydrate form config immediately to prevent secondary loaders flashing on transitions
|
|
76
|
+
if (matchedConfig.form) {
|
|
77
|
+
const formDoc = await getDoc(doc(db, 'sys_forms', matchedConfig.form));
|
|
78
|
+
if (formDoc.exists()) {
|
|
79
|
+
matchedConfig.formConfigOverride = { ...formDoc.data(), id: formDoc.id };
|
|
80
|
+
} else {
|
|
81
|
+
if (matchedConfig.form === 'contact') {
|
|
82
|
+
const { contactFormConfig } = await import('../configs/forms/contactForm.config');
|
|
83
|
+
matchedConfig.formConfigOverride = contactFormConfig;
|
|
84
|
+
} else if (matchedConfig.form === 'support') {
|
|
85
|
+
const { supportFormConfig } = await import('../configs/forms/supportForm.config');
|
|
86
|
+
matchedConfig.formConfigOverride = supportFormConfig;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Update Document Title matching pageName/tabName globally
|
|
92
|
+
const newTitle = matchedConfig.pageName || matchedConfig.tabName || matchedConfig.title;
|
|
93
|
+
if (newTitle) {
|
|
94
|
+
document.title = newTitle;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
setConfig(matchedConfig);
|
|
98
|
+
} else {
|
|
99
|
+
setConfig('not-found');
|
|
100
|
+
}
|
|
101
|
+
} catch (err) {
|
|
102
|
+
setConfig('not-found');
|
|
103
|
+
} finally {
|
|
104
|
+
setLoading(false);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
fetchConfigs();
|
|
108
|
+
}, [location.pathname]);
|
|
109
|
+
|
|
110
|
+
const [showLoader, setShowLoader] = useState(false);
|
|
111
|
+
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
let timer: any;
|
|
114
|
+
if (loading) timer = setTimeout(() => setShowLoader(true), 150);
|
|
115
|
+
else setShowLoader(false);
|
|
116
|
+
return () => clearTimeout(timer);
|
|
117
|
+
}, [loading]);
|
|
118
|
+
|
|
119
|
+
if (loading) {
|
|
120
|
+
if (!showLoader) return null;
|
|
121
|
+
if (user) {
|
|
122
|
+
return (
|
|
123
|
+
<main className="flex-1 w-full max-w-7xl mx-auto p-4 md:p-8 z-10 flex flex-col">
|
|
124
|
+
<div className="mb-6">
|
|
125
|
+
<div className="h-10 w-56 rounded-xl bg-foreground/5 animate-pulse mt-1" />
|
|
126
|
+
<div className="h-3 w-24 rounded bg-foreground/5 animate-pulse mt-3" />
|
|
127
|
+
</div>
|
|
128
|
+
<div className="flex flex-col gap-6 flex-1 pb-16">
|
|
129
|
+
<DashboardNav />
|
|
130
|
+
<div className="flex-1 glass-panel border border-[var(--panel-border)] rounded-3xl flex items-center justify-center min-h-[400px] bg-background shadow-2xl">
|
|
131
|
+
<Loader2 className="w-6 h-6 animate-spin text-accent" />
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
</main>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
return (
|
|
138
|
+
<div className="flex-1 flex items-center justify-center min-h-[60vh] z-10">
|
|
139
|
+
<Loader2 className="w-6 h-6 animate-spin text-accent" />
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (config === 'not-found') {
|
|
145
|
+
return (
|
|
146
|
+
<main className="flex-1 w-full max-w-7xl mx-auto p-4 md:p-8 z-10 flex flex-col pt-12 min-h-screen">
|
|
147
|
+
<div className="flex flex-col items-center justify-center flex-1 text-center">
|
|
148
|
+
<div className="w-16 h-16 bg-red-500/10 text-red-500 rounded-full flex items-center justify-center mb-6">
|
|
149
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" className="w-8 h-8"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>
|
|
150
|
+
</div>
|
|
151
|
+
<h2 className="text-3xl font-bold mb-3 text-foreground">Page Not Found</h2>
|
|
152
|
+
<p className="text-foreground/60 max-w-md">The route you are looking for does not exist or has been removed.</p>
|
|
153
|
+
</div>
|
|
154
|
+
</main>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Handle Support specific logic -> only signed in users can see private Support pages
|
|
159
|
+
if ((config.pageId === 'support' || config.isPrivate) && !user) {
|
|
160
|
+
return <Navigate to="/login" state={{ from: location }} />;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const parsedPageName = config.pageName?.toLowerCase().replace(/\s+/g, '_') || config.pageId || 'custom_page';
|
|
164
|
+
const prefix = `pub_${parsedPageName}`;
|
|
165
|
+
|
|
166
|
+
if (config.template === 'none') {
|
|
167
|
+
if (config.isAdmin) {
|
|
168
|
+
return <AdminPageTemplate config={config} />;
|
|
169
|
+
}
|
|
170
|
+
if (config.isShared) {
|
|
171
|
+
return <SharedPageTemplate config={config} />;
|
|
172
|
+
}
|
|
173
|
+
if (config.isPrivate) {
|
|
174
|
+
return <PrivatePageTemplate config={config} />;
|
|
175
|
+
}
|
|
176
|
+
return <PublicPageTemplate config={config} />;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (config.template === 'board') {
|
|
180
|
+
const boardPrefix = config.isAdmin ? `admin_${config.pageId || parsedPageName}`
|
|
181
|
+
: config.isShared ? `mem_${config.pageId || parsedPageName}`
|
|
182
|
+
: config.isPrivate ? `user_${config.pageId || parsedPageName}`
|
|
183
|
+
: prefix;
|
|
184
|
+
const TemplateBoardComponent = globalConfig.components?.TemplateBoard || TemplateBoard;
|
|
185
|
+
return <TemplateBoardComponent
|
|
186
|
+
title={config.tabTitle || config.tabName || config.title || config.pageName || 'Board'}
|
|
187
|
+
subtitle={config.route}
|
|
188
|
+
tasksCollection={`${boardPrefix}_tasks`}
|
|
189
|
+
categoriesCollection={`${boardPrefix}_categories`}
|
|
190
|
+
config={config}
|
|
191
|
+
/>;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (config.template === 'table') {
|
|
195
|
+
const tablePrefix = config.isAdmin ? `admin_${config.pageId || parsedPageName}`
|
|
196
|
+
: config.isShared ? `mem_${config.pageId || parsedPageName}`
|
|
197
|
+
: config.isPrivate ? `user_${config.pageId || parsedPageName}`
|
|
198
|
+
: prefix;
|
|
199
|
+
const TemplateTableComponent = globalConfig.components?.TemplateTable || TemplateTable;
|
|
200
|
+
return <TemplateTableComponent
|
|
201
|
+
title={config.tabTitle || config.tabName || config.title || config.pageName || 'Table'}
|
|
202
|
+
subtitle={config.route}
|
|
203
|
+
tableCollection={`${tablePrefix}_table`}
|
|
204
|
+
config={config}
|
|
205
|
+
/>;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (config.template === 'calendar') {
|
|
209
|
+
// Determine collection prefix based on page type
|
|
210
|
+
const calPrefix = config.isAdmin ? `admin_${config.pageId || parsedPageName}`
|
|
211
|
+
: config.isShared ? `mem_${config.pageId || parsedPageName}`
|
|
212
|
+
: config.isPrivate ? `user_${config.pageId || parsedPageName}`
|
|
213
|
+
: prefix;
|
|
214
|
+
const TemplateCalendarComponent = globalConfig.components?.TemplateCalendar || TemplateCalendar;
|
|
215
|
+
return <TemplateCalendarComponent
|
|
216
|
+
title={config.tabTitle || config.tabName || config.title || config.pageName || 'Calendar'}
|
|
217
|
+
subtitle={config.route}
|
|
218
|
+
eventsCollection={`${calPrefix}_events`}
|
|
219
|
+
defaultTimeFormat={config.defaultTimeFormat || config.defaulttimeformat || '12h'}
|
|
220
|
+
config={config}
|
|
221
|
+
/>;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (config.template === 'inline_form') {
|
|
225
|
+
return (
|
|
226
|
+
<div className="flex-1 flex flex-col items-center justify-start mt-8 relative z-10 w-full min-h-[60vh]">
|
|
227
|
+
<ContactPopup isInline={true} formId={config.form} formConfig={config.formConfigOverride} />
|
|
228
|
+
</div>
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return (
|
|
233
|
+
<div className="flex-1 flex flex-col items-center justify-center relative z-10 w-full min-h-[60vh]">
|
|
234
|
+
<ContactPopup formId={config.form} formConfig={config.formConfigOverride} onClose={() => window.history.back()} />
|
|
235
|
+
</div>
|
|
236
|
+
);
|
|
237
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// @generated-test
|
|
2
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
3
|
+
import { render, screen, act } from '@testing-library/react';
|
|
4
|
+
import { BrowserRouter } from 'react-router-dom';
|
|
5
|
+
import { AuthProvider } from '../lib/AuthContext';
|
|
6
|
+
import { ThemeProvider } from '../lib/ThemeContext';
|
|
7
|
+
import { FormsAdmin } from './FormsAdmin';
|
|
8
|
+
|
|
9
|
+
// Global mocks
|
|
10
|
+
Object.defineProperty(window, 'matchMedia', {
|
|
11
|
+
writable: true,
|
|
12
|
+
value: vi.fn().mockImplementation(query => ({
|
|
13
|
+
matches: false,
|
|
14
|
+
media: query,
|
|
15
|
+
onchange: null,
|
|
16
|
+
addListener: vi.fn(),
|
|
17
|
+
removeListener: vi.fn(),
|
|
18
|
+
addEventListener: vi.fn(),
|
|
19
|
+
removeEventListener: vi.fn(),
|
|
20
|
+
dispatchEvent: vi.fn(),
|
|
21
|
+
})),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
vi.mock('../lib/AuthContext', () => ({
|
|
25
|
+
AuthProvider: ({ children }: any) => <>{children}</>,
|
|
26
|
+
useAuth: () => ({
|
|
27
|
+
user: { uid: 'mock-user-123', email: 'test@example.com' },
|
|
28
|
+
userWorkspaces: [],
|
|
29
|
+
activeWorkspace: null,
|
|
30
|
+
activeOrg: null,
|
|
31
|
+
loading: false
|
|
32
|
+
})
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
vi.mock('../lib/ThemeContext', () => ({
|
|
36
|
+
ThemeProvider: ({ children }: any) => <>{children}</>,
|
|
37
|
+
useTheme: () => ({ themeMode: 'light', setThemeMode: vi.fn(), activeConfig: {} })
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
vi.mock('firebase/auth', () => ({
|
|
41
|
+
getAuth: vi.fn(() => ({})),
|
|
42
|
+
onAuthStateChanged: vi.fn((auth, cb) => { cb({ uid: 'mock-user-123', email: 'test@example.com', getIdToken: vi.fn(() => Promise.resolve('mock-token')) }); return () => {}; })
|
|
43
|
+
}));
|
|
44
|
+
vi.mock('firebase/firestore', () => ({
|
|
45
|
+
getFirestore: vi.fn(() => ({})),
|
|
46
|
+
collection: vi.fn(),
|
|
47
|
+
doc: vi.fn(),
|
|
48
|
+
setDoc: vi.fn(() => Promise.resolve()),
|
|
49
|
+
addDoc: vi.fn(() => Promise.resolve()),
|
|
50
|
+
updateDoc: vi.fn(() => Promise.resolve()),
|
|
51
|
+
deleteDoc: vi.fn(() => Promise.resolve()),
|
|
52
|
+
query: vi.fn(),
|
|
53
|
+
where: vi.fn(),
|
|
54
|
+
orderBy: vi.fn(),
|
|
55
|
+
limit: vi.fn(),
|
|
56
|
+
getDoc: vi.fn(() => Promise.resolve({ exists: () => true, data: () => ({ role: 'super_admin' }) })),
|
|
57
|
+
getDocs: vi.fn(() => Promise.resolve({ docs: [], forEach: vi.fn() })),
|
|
58
|
+
onSnapshot: vi.fn((...args: any[]) => {
|
|
59
|
+
let cb = args[1];
|
|
60
|
+
if (typeof args[2] === 'function') {
|
|
61
|
+
cb = args[2];
|
|
62
|
+
}
|
|
63
|
+
if (typeof cb === 'function') {
|
|
64
|
+
cb({docs: [], forEach: vi.fn(), data: () => ({}), exists: () => true});
|
|
65
|
+
}
|
|
66
|
+
return () => {};
|
|
67
|
+
})
|
|
68
|
+
}));
|
|
69
|
+
vi.mock('firebase/storage', () => ({
|
|
70
|
+
getStorage: vi.fn(() => ({})),
|
|
71
|
+
ref: vi.fn(),
|
|
72
|
+
listAll: vi.fn(() => Promise.resolve({ items: [], prefixes: [] })),
|
|
73
|
+
getDownloadURL: vi.fn(() => Promise.resolve('mock-url')),
|
|
74
|
+
getMetadata: vi.fn(() => Promise.resolve({ size: 1024, timeCreated: new Date().toISOString() }))
|
|
75
|
+
}));
|
|
76
|
+
|
|
77
|
+
describe('FormsAdmin Component', () => {
|
|
78
|
+
it('renders without crashing', async () => {
|
|
79
|
+
// Wrap in standard application providers inside act to process async side effects and prevent warnings
|
|
80
|
+
await act(async () => {
|
|
81
|
+
render(
|
|
82
|
+
<BrowserRouter>
|
|
83
|
+
<AuthProvider>
|
|
84
|
+
<ThemeProvider>
|
|
85
|
+
{/* @ts-ignore */}
|
|
86
|
+
<FormsAdmin />
|
|
87
|
+
</ThemeProvider>
|
|
88
|
+
</AuthProvider>
|
|
89
|
+
</BrowserRouter>
|
|
90
|
+
);
|
|
91
|
+
// Wait a tick to flush background state updates
|
|
92
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Check if the document has anything rendered without throwing
|
|
96
|
+
expect(document.body).toBeDefined();
|
|
97
|
+
});
|
|
98
|
+
});
|