@qwickapps/react-framework 1.3.1 → 1.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +123 -1
  2. package/dist/components/AccessibilityProvider.d.ts +64 -0
  3. package/dist/components/AccessibilityProvider.d.ts.map +1 -0
  4. package/dist/components/Breadcrumbs.d.ts +39 -0
  5. package/dist/components/Breadcrumbs.d.ts.map +1 -0
  6. package/dist/components/ErrorBoundary.d.ts +39 -0
  7. package/dist/components/ErrorBoundary.d.ts.map +1 -0
  8. package/dist/components/QwickApp.d.ts.map +1 -1
  9. package/dist/components/index.d.ts +3 -0
  10. package/dist/components/index.d.ts.map +1 -1
  11. package/dist/index.bundled.css +12 -0
  12. package/dist/index.esm.js +910 -44
  13. package/dist/index.js +916 -47
  14. package/dist/templates/TemplateResolver.d.ts.map +1 -1
  15. package/dist/utils/htmlTransform.d.ts.map +1 -1
  16. package/dist/utils/logger.d.ts +15 -3
  17. package/dist/utils/logger.d.ts.map +1 -1
  18. package/package.json +4 -2
  19. package/src/components/AccessibilityProvider.tsx +466 -0
  20. package/src/components/Breadcrumbs.tsx +223 -0
  21. package/src/components/ErrorBoundary.tsx +216 -0
  22. package/src/components/QwickApp.tsx +17 -11
  23. package/src/components/__tests__/AccessibilityProvider.test.tsx +330 -0
  24. package/src/components/__tests__/Breadcrumbs.test.tsx +268 -0
  25. package/src/components/__tests__/ErrorBoundary.test.tsx +163 -0
  26. package/src/components/index.ts +3 -0
  27. package/src/stories/AccessibilityProvider.stories.tsx +284 -0
  28. package/src/stories/Breadcrumbs.stories.tsx +304 -0
  29. package/src/stories/ErrorBoundary.stories.tsx +159 -0
  30. package/src/stories/{form/FormComponents.stories.tsx → FormComponents.stories.tsx} +8 -8
  31. package/src/templates/TemplateResolver.ts +2 -6
  32. package/src/utils/__tests__/nested-dom-fix.test.tsx +53 -0
  33. package/src/utils/__tests__/optional-logging.test.ts +83 -0
  34. package/src/utils/htmlTransform.tsx +69 -3
  35. package/src/utils/logger.ts +60 -5
  36. package/dist/schemas/Builders.d.ts +0 -7
  37. package/dist/schemas/Builders.d.ts.map +0 -1
  38. package/dist/schemas/types.d.ts +0 -7
  39. package/dist/schemas/types.d.ts.map +0 -1
  40. package/dist/types/DataBinding.d.ts +0 -7
  41. package/dist/types/DataBinding.d.ts.map +0 -1
  42. package/dist/types/DataProvider.d.ts +0 -7
  43. package/dist/types/DataProvider.d.ts.map +0 -1
@@ -0,0 +1,304 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Breadcrumbs, useBreadcrumbs, type BreadcrumbItem } from '../components/Breadcrumbs';
3
+ import React from 'react';
4
+
5
+ // Icons for demo
6
+ const HomeIcon = () => (
7
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
8
+ <path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
9
+ </svg>
10
+ );
11
+
12
+ const FolderIcon = () => (
13
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
14
+ <path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/>
15
+ </svg>
16
+ );
17
+
18
+ const FileIcon = () => (
19
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
20
+ <path d="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z"/>
21
+ </svg>
22
+ );
23
+
24
+ // Demo component with navigation simulation
25
+ const BreadcrumbDemo = () => {
26
+ const { breadcrumbs, setBreadcrumbs, addBreadcrumb, removeBreadcrumb, clearBreadcrumbs } = useBreadcrumbs();
27
+ const [currentPath, setCurrentPath] = React.useState('Home > Documents > Projects');
28
+
29
+ const samplePaths = [
30
+ 'Home',
31
+ 'Home > Documents',
32
+ 'Home > Documents > Projects',
33
+ 'Home > Documents > Projects > QwickApps',
34
+ 'Home > Documents > Projects > QwickApps > Framework',
35
+ 'Home > Store > Products',
36
+ 'Home > Store > Products > Electronics > Computers',
37
+ ];
38
+
39
+ const pathToBreadcrumbs = (path: string): BreadcrumbItem[] => {
40
+ const parts = path.split(' > ');
41
+ return parts.map((part, index) => ({
42
+ label: part,
43
+ href: `/${part.toLowerCase().replace(/\s+/g, '-')}`,
44
+ icon: index === 0 ? <HomeIcon /> :
45
+ index === parts.length - 1 ? <FileIcon /> :
46
+ <FolderIcon />,
47
+ current: index === parts.length - 1
48
+ }));
49
+ };
50
+
51
+ React.useEffect(() => {
52
+ setBreadcrumbs(pathToBreadcrumbs(currentPath));
53
+ }, [currentPath, setBreadcrumbs]);
54
+
55
+ const handleNavigate = (item: BreadcrumbItem, index: number) => {
56
+ const newPath = breadcrumbs.slice(0, index + 1).map(b => b.label).join(' > ');
57
+ setCurrentPath(newPath);
58
+ alert(`Navigated to: ${item.label}`);
59
+ };
60
+
61
+ return (
62
+ <div style={{ padding: '2rem' }}>
63
+ <h2>Interactive Breadcrumbs Demo</h2>
64
+
65
+ <div style={{ marginBottom: '2rem' }}>
66
+ <h3>Current Navigation:</h3>
67
+ <Breadcrumbs items={breadcrumbs} onNavigate={handleNavigate} />
68
+ </div>
69
+
70
+ <div style={{ marginBottom: '2rem' }}>
71
+ <h3>Navigate to Different Paths:</h3>
72
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
73
+ {samplePaths.map((path) => (
74
+ <button
75
+ key={path}
76
+ onClick={() => setCurrentPath(path)}
77
+ style={{
78
+ padding: '0.5rem 1rem',
79
+ background: path === currentPath ? '#007cba' : '#f0f0f0',
80
+ color: path === currentPath ? 'white' : 'black',
81
+ border: '1px solid #ddd',
82
+ borderRadius: '4px',
83
+ textAlign: 'left',
84
+ cursor: 'pointer'
85
+ }}
86
+ >
87
+ {path}
88
+ </button>
89
+ ))}
90
+ </div>
91
+ </div>
92
+
93
+ <div>
94
+ <h3>Breadcrumb Controls:</h3>
95
+ <div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}>
96
+ <button
97
+ onClick={() => addBreadcrumb({ label: 'New Item', href: '/new' })}
98
+ style={{ padding: '0.5rem 1rem', background: '#28a745', color: 'white', border: 'none', borderRadius: '4px' }}
99
+ >
100
+ Add Item
101
+ </button>
102
+ <button
103
+ onClick={() => removeBreadcrumb(breadcrumbs.length - 1)}
104
+ style={{ padding: '0.5rem 1rem', background: '#dc3545', color: 'white', border: 'none', borderRadius: '4px' }}
105
+ >
106
+ Remove Last
107
+ </button>
108
+ <button
109
+ onClick={clearBreadcrumbs}
110
+ style={{ padding: '0.5rem 1rem', background: '#6c757d', color: 'white', border: 'none', borderRadius: '4px' }}
111
+ >
112
+ Clear All
113
+ </button>
114
+ </div>
115
+ </div>
116
+ </div>
117
+ );
118
+ };
119
+
120
+ const meta: Meta<typeof Breadcrumbs> = {
121
+ title: 'Components/Breadcrumbs',
122
+ component: Breadcrumbs,
123
+ parameters: {
124
+ layout: 'centered',
125
+ docs: {
126
+ description: {
127
+ component: 'A navigation breadcrumb component that shows the current page location within the application hierarchy. Supports accessibility, keyboard navigation, and customization.',
128
+ },
129
+ },
130
+ },
131
+ tags: ['autodocs'],
132
+ argTypes: {
133
+ items: {
134
+ description: 'Array of breadcrumb items to display',
135
+ },
136
+ separator: {
137
+ description: 'Custom separator between breadcrumb items',
138
+ control: 'text',
139
+ },
140
+ className: {
141
+ description: 'Additional CSS classes to apply',
142
+ control: 'text',
143
+ },
144
+ onNavigate: {
145
+ description: 'Callback function when a breadcrumb is clicked',
146
+ action: 'navigate',
147
+ },
148
+ maxItems: {
149
+ description: 'Maximum number of items to show (truncates with ellipsis)',
150
+ control: { type: 'number', min: 1, max: 10 },
151
+ },
152
+ showRoot: {
153
+ description: 'Whether to show the root (first) item',
154
+ control: 'boolean',
155
+ },
156
+ },
157
+ };
158
+
159
+ export default meta;
160
+ type Story = StoryObj<typeof meta>;
161
+
162
+ // Sample breadcrumb data
163
+ const sampleBreadcrumbs: BreadcrumbItem[] = [
164
+ { label: 'Home', href: '/', icon: <HomeIcon /> },
165
+ { label: 'Products', href: '/products', icon: <FolderIcon /> },
166
+ { label: 'Electronics', href: '/products/electronics', icon: <FolderIcon /> },
167
+ { label: 'Laptop', href: '/products/electronics/laptop', icon: <FileIcon />, current: true },
168
+ ];
169
+
170
+ // Default story
171
+ export const Default: Story = {
172
+ args: {
173
+ items: sampleBreadcrumbs,
174
+ },
175
+ };
176
+
177
+ // Story with custom separator
178
+ export const CustomSeparator: Story = {
179
+ args: {
180
+ items: sampleBreadcrumbs,
181
+ separator: '→',
182
+ },
183
+ parameters: {
184
+ docs: {
185
+ description: {
186
+ story: 'Breadcrumbs with a custom arrow separator instead of the default slash.',
187
+ },
188
+ },
189
+ },
190
+ };
191
+
192
+ // Story with emoji separator
193
+ export const EmojiSeparator: Story = {
194
+ args: {
195
+ items: sampleBreadcrumbs,
196
+ separator: '🏠',
197
+ },
198
+ parameters: {
199
+ docs: {
200
+ description: {
201
+ story: 'Breadcrumbs with an emoji separator for a more playful look.',
202
+ },
203
+ },
204
+ },
205
+ };
206
+
207
+ // Story with max items (truncation)
208
+ export const TruncatedItems: Story = {
209
+ args: {
210
+ items: [
211
+ { label: 'Home', href: '/' },
212
+ { label: 'Level 1', href: '/level1' },
213
+ { label: 'Level 2', href: '/level1/level2' },
214
+ { label: 'Level 3', href: '/level1/level2/level3' },
215
+ { label: 'Level 4', href: '/level1/level2/level3/level4' },
216
+ { label: 'Current Page', href: '/level1/level2/level3/level4/current', current: true },
217
+ ],
218
+ maxItems: 4,
219
+ },
220
+ parameters: {
221
+ docs: {
222
+ description: {
223
+ story: 'When there are too many items, middle items are replaced with ellipsis.',
224
+ },
225
+ },
226
+ },
227
+ };
228
+
229
+ // Story without root item
230
+ export const WithoutRoot: Story = {
231
+ args: {
232
+ items: sampleBreadcrumbs,
233
+ showRoot: false,
234
+ },
235
+ parameters: {
236
+ docs: {
237
+ description: {
238
+ story: 'Breadcrumbs without showing the root (first) item.',
239
+ },
240
+ },
241
+ },
242
+ };
243
+
244
+ // Story without icons
245
+ export const WithoutIcons: Story = {
246
+ args: {
247
+ items: [
248
+ { label: 'Home', href: '/' },
249
+ { label: 'Products', href: '/products' },
250
+ { label: 'Electronics', href: '/products/electronics' },
251
+ { label: 'Laptop', href: '/products/electronics/laptop', current: true },
252
+ ],
253
+ },
254
+ parameters: {
255
+ docs: {
256
+ description: {
257
+ story: 'Simple breadcrumbs without icons, just text navigation.',
258
+ },
259
+ },
260
+ },
261
+ };
262
+
263
+ // Story with navigation handler
264
+ export const WithNavigation: Story = {
265
+ args: {
266
+ items: sampleBreadcrumbs,
267
+ onNavigate: (item, index) => {
268
+ alert(`Clicked on "${item.label}" at index ${index}`);
269
+ },
270
+ },
271
+ parameters: {
272
+ docs: {
273
+ description: {
274
+ story: 'Breadcrumbs with a navigation handler that prevents default link behavior.',
275
+ },
276
+ },
277
+ },
278
+ };
279
+
280
+ // Interactive demo story
281
+ export const InteractiveDemo: Story = {
282
+ render: () => <BreadcrumbDemo />,
283
+ parameters: {
284
+ docs: {
285
+ description: {
286
+ story: 'Interactive demo showing breadcrumb navigation with the useBreadcrumbs hook.',
287
+ },
288
+ },
289
+ },
290
+ };
291
+
292
+ // Accessibility showcase
293
+ export const AccessibilityShowcase: Story = {
294
+ args: {
295
+ items: sampleBreadcrumbs,
296
+ },
297
+ parameters: {
298
+ docs: {
299
+ description: {
300
+ story: 'This breadcrumb component includes proper ARIA labels, keyboard navigation, and screen reader support. Try using Tab and Enter keys to navigate.',
301
+ },
302
+ },
303
+ },
304
+ };
@@ -0,0 +1,159 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { ErrorBoundary, withErrorBoundary } from '../components/ErrorBoundary';
3
+ import React from 'react';
4
+
5
+ // Test component that throws an error
6
+ const ThrowError = ({ shouldThrow = false }: { shouldThrow?: boolean }) => {
7
+ if (shouldThrow) {
8
+ throw new Error('Intentional error for testing ErrorBoundary');
9
+ }
10
+ return <div>This component is working fine!</div>;
11
+ };
12
+
13
+ const meta: Meta<typeof ErrorBoundary> = {
14
+ title: 'Framework/ErrorBoundary',
15
+ component: ErrorBoundary,
16
+ parameters: {
17
+ layout: 'centered',
18
+ docs: {
19
+ description: {
20
+ component: 'A React Error Boundary component that catches JavaScript errors and displays a user-friendly fallback UI with recovery options.',
21
+ },
22
+ },
23
+ },
24
+ tags: ['autodocs'],
25
+ argTypes: {
26
+ fallback: {
27
+ description: 'Custom fallback UI to display when an error occurs',
28
+ },
29
+ onError: {
30
+ description: 'Callback function called when an error is caught',
31
+ action: 'onError',
32
+ },
33
+ showErrorDetails: {
34
+ description: 'Whether to show error details (overrides development mode detection)',
35
+ control: 'boolean',
36
+ },
37
+ children: {
38
+ description: 'Child components to wrap with error boundary',
39
+ },
40
+ },
41
+ };
42
+
43
+ export default meta;
44
+ type Story = StoryObj<typeof meta>;
45
+
46
+ // Default story with working component
47
+ export const Default: Story = {
48
+ args: {
49
+ children: <div>This is a working component wrapped in ErrorBoundary</div>,
50
+ },
51
+ };
52
+
53
+ // Story that demonstrates error handling
54
+ export const WithError: Story = {
55
+ args: {
56
+ children: <ThrowError shouldThrow={true} />,
57
+ },
58
+ parameters: {
59
+ docs: {
60
+ description: {
61
+ story: 'Demonstrates ErrorBoundary catching an error and displaying the fallback UI.',
62
+ },
63
+ },
64
+ },
65
+ };
66
+
67
+ // Story with custom fallback UI
68
+ export const CustomFallback: Story = {
69
+ args: {
70
+ children: <ThrowError shouldThrow={true} />,
71
+ fallback: (
72
+ <div style={{
73
+ padding: '2rem',
74
+ textAlign: 'center',
75
+ background: '#ffe6e6',
76
+ border: '2px solid #ff9999',
77
+ borderRadius: '8px',
78
+ color: '#cc0000'
79
+ }}>
80
+ <h3>🚨 Custom Error UI</h3>
81
+ <p>Something went wrong, but we have a custom fallback!</p>
82
+ </div>
83
+ ),
84
+ },
85
+ parameters: {
86
+ docs: {
87
+ description: {
88
+ story: 'Shows how to provide a custom fallback UI instead of the default error message.',
89
+ },
90
+ },
91
+ },
92
+ };
93
+
94
+ // Story showing error details in development mode
95
+ export const WithErrorDetails: Story = {
96
+ args: {
97
+ children: <ThrowError shouldThrow={true} />,
98
+ showErrorDetails: true,
99
+ },
100
+ parameters: {
101
+ docs: {
102
+ description: {
103
+ story: 'Shows error details including stack trace (normally only shown in development mode).',
104
+ },
105
+ },
106
+ },
107
+ };
108
+
109
+ // Story demonstrating the HOC
110
+ const TestComponent = () => <div>Component enhanced with ErrorBoundary HOC</div>;
111
+ const EnhancedComponent = withErrorBoundary(TestComponent);
112
+
113
+ export const HigherOrderComponent: Story = {
114
+ render: () => <EnhancedComponent />,
115
+ parameters: {
116
+ docs: {
117
+ description: {
118
+ story: 'Demonstrates using the withErrorBoundary Higher-Order Component to wrap any component with error handling.',
119
+ },
120
+ },
121
+ },
122
+ };
123
+
124
+ // Interactive story for testing retry functionality
125
+ export const Interactive: Story = {
126
+ render: () => {
127
+ const [shouldThrow, setShouldThrow] = React.useState(false);
128
+
129
+ return (
130
+ <div style={{ padding: '2rem' }}>
131
+ <button
132
+ onClick={() => setShouldThrow(!shouldThrow)}
133
+ style={{
134
+ marginBottom: '1rem',
135
+ padding: '0.5rem 1rem',
136
+ background: shouldThrow ? '#ff4444' : '#44aa44',
137
+ color: 'white',
138
+ border: 'none',
139
+ borderRadius: '4px',
140
+ cursor: 'pointer'
141
+ }}
142
+ >
143
+ {shouldThrow ? 'Fix Component' : 'Break Component'}
144
+ </button>
145
+
146
+ <ErrorBoundary>
147
+ <ThrowError shouldThrow={shouldThrow} />
148
+ </ErrorBoundary>
149
+ </div>
150
+ );
151
+ },
152
+ parameters: {
153
+ docs: {
154
+ description: {
155
+ story: 'Interactive demo where you can break and fix the component to see ErrorBoundary in action.',
156
+ },
157
+ },
158
+ },
159
+ };
@@ -4,13 +4,13 @@
4
4
 
5
5
  import { Box, Paper, Typography } from '@mui/material';
6
6
  import type { Meta, StoryObj } from '@storybook/react';
7
- import { Button } from '../../components/buttons/Button';
8
- import { TextField } from '../../components/input/TextField';
9
- import { GridCell } from '../../components/layout/GridCell';
10
- import GridLayout from '../../components/layout/GridLayout';
7
+ import { Button } from '../components/buttons/Button';
8
+ import { TextField } from '../components/input/TextField';
9
+ import { GridCell } from '../components/layout/GridCell';
10
+ import GridLayout from '../components/layout/GridLayout';
11
11
 
12
12
  const meta: Meta = {
13
- title: 'Form/Enhanced Components',
13
+ title: 'Forms/Enhanced Components',
14
14
  parameters: {
15
15
  layout: 'padded',
16
16
  docs: {
@@ -43,14 +43,14 @@ export const BasicTextField: StoryObj = {
43
43
  <TextField
44
44
  label="Small Width"
45
45
  width="small"
46
- margin="medium"
46
+ margin="normal"
47
47
  padding="small"
48
48
  />
49
49
  <br />
50
50
  <TextField
51
51
  label="Large Width"
52
52
  width="large"
53
- margin="medium"
53
+ margin="normal"
54
54
  padding="large"
55
55
  background="primary.light"
56
56
  />
@@ -73,7 +73,7 @@ export const BasicButton: StoryObj = {
73
73
  <Button
74
74
  variant="contained"
75
75
  width="large"
76
- margin="medium"
76
+ margin="normal"
77
77
  padding="large"
78
78
  >
79
79
  Large Width
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Copyright (c) 2025 QwickApps.com. All rights reserved.
6
6
  */
7
- import { Logger } from '@qwickapps/logging';
7
+ import { Logger, createLogger } from '../utils/logger';
8
8
  import { CachedDataProvider, DataResponse, ICacheProvider, IDataProvider, MemoryCacheProvider, Model, MustacheTemplateProvider, SelectOptions } from '@qwickapps/schema';
9
9
  import {
10
10
  DataProxy,
@@ -51,11 +51,7 @@ export class TemplateResolver implements ITemplateResolver {
51
51
  private log: Logger;
52
52
 
53
53
  constructor(config: TemplateResolverConfig) {
54
- this.log = new Logger({
55
- namespace: 'ContentResolver',
56
- enabled: config.enableLogging || false
57
- });
58
-
54
+ this.log = createLogger('ContentResolver');
59
55
  this.enableLogging = config.enableLogging || false;
60
56
  this.templateResolver = config.templateResolver || new MustacheTemplateProvider();
61
57
  if (config.cacheProvider === true) {
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Test for DOM nesting fix in HTML transformation
3
+ */
4
+
5
+ import { transformHtmlToReact, defaultMarkdownRules } from '../htmlTransform';
6
+
7
+ describe('DOM nesting fix', () => {
8
+ it('should fix invalid paragraph nesting with block elements', () => {
9
+ // This is the problematic HTML that marked.js creates from mixed content
10
+ const invalidHtml = `
11
+ <p>You can mix HTML with Markdown when needed:</p>
12
+ <p><div style="padding: 16px; border: 1px solid #ccc;">
13
+ This is <strong>HTML with Markdown</strong> inside it!
14
+ </div></p>
15
+ <p>Back to regular content.</p>
16
+ `;
17
+
18
+ // Should not throw React error
19
+ expect(() => {
20
+ const result = transformHtmlToReact(invalidHtml, { rules: defaultMarkdownRules });
21
+ expect(result).toBeDefined();
22
+ expect(Array.isArray(result)).toBe(true);
23
+ }).not.toThrow();
24
+ });
25
+
26
+ it('should preserve valid nesting', () => {
27
+ const validHtml = `
28
+ <p>This is a valid paragraph with <strong>inline elements</strong>.</p>
29
+ <div>This is a valid div with block content.</div>
30
+ `;
31
+
32
+ const result = transformHtmlToReact(validHtml, { rules: defaultMarkdownRules });
33
+ expect(result).toBeDefined();
34
+ expect(result.length).toBeGreaterThan(0);
35
+ });
36
+
37
+ it('should handle deeply nested invalid structures', () => {
38
+ const deeplyNested = `
39
+ <p>
40
+ Outer paragraph
41
+ <div>
42
+ Block in paragraph
43
+ <p>Paragraph in div in paragraph</p>
44
+ </div>
45
+ </p>
46
+ `;
47
+
48
+ expect(() => {
49
+ const result = transformHtmlToReact(deeplyNested, { rules: defaultMarkdownRules });
50
+ expect(result).toBeDefined();
51
+ }).not.toThrow();
52
+ });
53
+ });
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Test for optional logging dependency fallback
3
+ */
4
+
5
+ import { createLogger, loggers } from '../logger';
6
+
7
+ // Mock the require function to simulate missing @qwickapps/logging package
8
+ const originalRequire = require;
9
+
10
+ describe('Optional logging dependency', () => {
11
+ beforeEach(() => {
12
+ // Reset console spy before each test
13
+ jest.clearAllMocks();
14
+ });
15
+
16
+ afterEach(() => {
17
+ // Restore original require
18
+ (global as any).require = originalRequire;
19
+ });
20
+
21
+ it('should use console fallback when @qwickapps/logging is not available', () => {
22
+ // Mock require to throw an error (simulating missing package)
23
+ (global as any).require = jest.fn().mockImplementation((module: string) => {
24
+ if (module === '@qwickapps/logging') {
25
+ throw new Error('Cannot find module');
26
+ }
27
+ return originalRequire(module);
28
+ });
29
+
30
+ // Spy on console methods
31
+ const consoleSpy = jest.spyOn(console, 'info').mockImplementation();
32
+
33
+ // Create a new logger (should use fallback)
34
+ const logger = createLogger('TestLogger');
35
+ logger.info('Test message');
36
+
37
+ expect(consoleSpy).toHaveBeenCalledWith('[TestLogger] Test message');
38
+ consoleSpy.mockRestore();
39
+ });
40
+
41
+ it('should have all framework loggers available', () => {
42
+ expect(loggers).toHaveProperty('scaffold');
43
+ expect(loggers).toHaveProperty('navigation');
44
+ expect(loggers).toHaveProperty('auth');
45
+ expect(loggers).toHaveProperty('theme');
46
+ expect(loggers).toHaveProperty('palette');
47
+ expect(loggers).toHaveProperty('form');
48
+ expect(loggers).toHaveProperty('layout');
49
+ expect(loggers).toHaveProperty('menu');
50
+ expect(loggers).toHaveProperty('router');
51
+
52
+ // Each logger should have the required methods
53
+ Object.values(loggers).forEach(logger => {
54
+ expect(typeof logger.debug).toBe('function');
55
+ expect(typeof logger.info).toBe('function');
56
+ expect(typeof logger.warn).toBe('function');
57
+ expect(typeof logger.error).toBe('function');
58
+ });
59
+ });
60
+
61
+ it('should suppress debug logs in production', () => {
62
+ const originalEnv = process.env.NODE_ENV;
63
+ process.env.NODE_ENV = 'production';
64
+
65
+ // Mock require to throw an error (use fallback)
66
+ (global as any).require = jest.fn().mockImplementation((module: string) => {
67
+ if (module === '@qwickapps/logging') {
68
+ throw new Error('Cannot find module');
69
+ }
70
+ return originalRequire(module);
71
+ });
72
+
73
+ const debugSpy = jest.spyOn(console, 'debug').mockImplementation();
74
+
75
+ const logger = createLogger('TestLogger');
76
+ logger.debug('Debug message');
77
+
78
+ expect(debugSpy).not.toHaveBeenCalled();
79
+
80
+ debugSpy.mockRestore();
81
+ process.env.NODE_ENV = originalEnv;
82
+ });
83
+ });