create-elit 3.3.3 → 3.3.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/templates/README.md +1 -0
- package/dist/templates/elit.config.ts +16 -0
- package/dist/templates/package.json +4 -1
- package/dist/templates/src/client.test.ts +292 -0
- package/dist/templates/src/components/Footer.test.ts +226 -0
- package/dist/templates/src/components/Header.test.ts +493 -0
- package/dist/templates/src/pages/ChatListPage.test.ts +603 -0
- package/dist/templates/src/pages/ChatPage.test.ts +530 -0
- package/dist/templates/src/pages/ForgotPasswordPage.test.ts +484 -0
- package/dist/templates/src/pages/HomePage.test.ts +601 -0
- package/dist/templates/src/pages/LoginPage.test.ts +619 -0
- package/dist/templates/src/pages/PrivateChatPage.test.ts +556 -0
- package/dist/templates/src/pages/ProfilePage.test.ts +628 -0
- package/dist/templates/src/pages/RegisterPage.test.ts +661 -0
- package/package.json +1 -1
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PrivateChatPage Component Unit Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// CRITICAL: Set up ALL mocks BEFORE importing PrivateChatPage component
|
|
6
|
+
// The component reads localStorage during import, so mocks must be set up first
|
|
7
|
+
|
|
8
|
+
// Simple mock function to track calls
|
|
9
|
+
function mockFn() {
|
|
10
|
+
const calls: any[] = [];
|
|
11
|
+
const fn = (...args: any[]) => {
|
|
12
|
+
calls.push(args);
|
|
13
|
+
return undefined;
|
|
14
|
+
};
|
|
15
|
+
fn.calls = calls;
|
|
16
|
+
fn.mockClear = () => {
|
|
17
|
+
calls.length = 0;
|
|
18
|
+
};
|
|
19
|
+
return fn;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Mock localStorage
|
|
23
|
+
const localStorageMock = (() => {
|
|
24
|
+
let store: Record<string, string> = {};
|
|
25
|
+
return {
|
|
26
|
+
getItem: (key: string): string | null => store[key] || null,
|
|
27
|
+
setItem: (key: string, value: string): void => {
|
|
28
|
+
store[key] = value;
|
|
29
|
+
},
|
|
30
|
+
removeItem: (key: string): void => {
|
|
31
|
+
delete store[key];
|
|
32
|
+
},
|
|
33
|
+
clear: (): void => {
|
|
34
|
+
store = {};
|
|
35
|
+
},
|
|
36
|
+
get length(): number {
|
|
37
|
+
return Object.keys(store).length;
|
|
38
|
+
},
|
|
39
|
+
key: (index: number): string | null => {
|
|
40
|
+
return Object.keys(store)[index] || null;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
})();
|
|
44
|
+
|
|
45
|
+
// Mock fetch
|
|
46
|
+
let mockFetchResponse: any = {
|
|
47
|
+
ok: true,
|
|
48
|
+
json: async () => ({ user: { id: '456', name: 'Other User', email: 'other@example.com', bio: '', avatar: '' } })
|
|
49
|
+
};
|
|
50
|
+
const mockFetch = async () => mockFetchResponse;
|
|
51
|
+
|
|
52
|
+
// Mock EventSource
|
|
53
|
+
class MockEventSource {
|
|
54
|
+
url: string;
|
|
55
|
+
readyState: number = 1; // CONNECTING
|
|
56
|
+
onmessage: ((event: MessageEvent) => void) | null = null;
|
|
57
|
+
onopen: ((event: Event) => void) | null = null;
|
|
58
|
+
onerror: ((event: Event) => void) | null = null;
|
|
59
|
+
|
|
60
|
+
constructor(url: string) {
|
|
61
|
+
this.url = url;
|
|
62
|
+
// Simulate connection
|
|
63
|
+
setTimeout(() => {
|
|
64
|
+
this.readyState = 1; // OPEN
|
|
65
|
+
if (this.onopen) {
|
|
66
|
+
this.onopen(new Event('open'));
|
|
67
|
+
}
|
|
68
|
+
}, 0);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
addEventListener(_type: string, _listener: any) {
|
|
72
|
+
// Do nothing
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
removeEventListener(_type: string, _listener: any) {
|
|
76
|
+
// Do nothing
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
close() {
|
|
80
|
+
this.readyState = 2; // CLOSED
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Mock document.querySelector for button state management
|
|
85
|
+
const querySelectorMock = (selector: string) => {
|
|
86
|
+
if (selector === '.chat-send-button') {
|
|
87
|
+
return {
|
|
88
|
+
disabled: false,
|
|
89
|
+
textContent: 'Send'
|
|
90
|
+
} as any;
|
|
91
|
+
}
|
|
92
|
+
return null as any;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// Mock window.addEventListener and dispatchEvent
|
|
96
|
+
const eventListeners: Record<string, Function[]> = {};
|
|
97
|
+
const addEventListenerMock = (event: string, handler: Function) => {
|
|
98
|
+
if (!eventListeners[event]) {
|
|
99
|
+
eventListeners[event] = [];
|
|
100
|
+
}
|
|
101
|
+
eventListeners[event].push(handler);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// Mock requestAnimationFrame
|
|
105
|
+
let rafId = 0;
|
|
106
|
+
const rafCallbacks: Map<number, FrameRequestCallback> = new Map();
|
|
107
|
+
const mockRequestAnimationFrame = (callback: FrameRequestCallback) => {
|
|
108
|
+
const id = ++rafId;
|
|
109
|
+
rafCallbacks.set(id, callback);
|
|
110
|
+
return id;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const mockCancelAnimationFrame = (id: number) => {
|
|
114
|
+
rafCallbacks.delete(id);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// Mock setTimeout for setupButtonState
|
|
118
|
+
let timeoutId = 0;
|
|
119
|
+
const timeoutCallbacks: Map<number, Function> = new Map();
|
|
120
|
+
const mockSetTimeout = (callback: Function, _delay: number) => {
|
|
121
|
+
const id = ++timeoutId;
|
|
122
|
+
timeoutCallbacks.set(id, callback);
|
|
123
|
+
return id;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const mockClearTimeout = (id: number) => {
|
|
127
|
+
timeoutCallbacks.delete(id);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// Mock router
|
|
131
|
+
const mockRouter = {
|
|
132
|
+
push: mockFn() as any,
|
|
133
|
+
replace: mockFn() as any,
|
|
134
|
+
go: mockFn() as any,
|
|
135
|
+
back: mockFn() as any,
|
|
136
|
+
forward: mockFn() as any,
|
|
137
|
+
currentPath: '/',
|
|
138
|
+
currentState: null
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// SETUP GLOBALS BEFORE IMPORT
|
|
142
|
+
// Clear real localStorage if it exists
|
|
143
|
+
if (typeof localStorage !== 'undefined') {
|
|
144
|
+
localStorage.clear();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Set up global mocks
|
|
148
|
+
(global as any).localStorage = localStorageMock;
|
|
149
|
+
(globalThis as any).localStorage = localStorageMock;
|
|
150
|
+
(global as any).fetch = mockFetch;
|
|
151
|
+
(globalThis as any).fetch = mockFetch;
|
|
152
|
+
(global as any).EventSource = MockEventSource;
|
|
153
|
+
(globalThis as any).EventSource = MockEventSource;
|
|
154
|
+
(global as any).window = {
|
|
155
|
+
addEventListener: addEventListenerMock,
|
|
156
|
+
removeEventListener: mockFn(),
|
|
157
|
+
dispatchEvent: mockFn(),
|
|
158
|
+
localStorage: localStorageMock
|
|
159
|
+
};
|
|
160
|
+
(globalThis as any).window = (global as any).window;
|
|
161
|
+
(global as any).document = {
|
|
162
|
+
querySelector: querySelectorMock
|
|
163
|
+
};
|
|
164
|
+
(globalThis as any).document = (global as any).document;
|
|
165
|
+
(global as any).setTimeout = mockSetTimeout;
|
|
166
|
+
(globalThis as any).clearTimeout = mockClearTimeout;
|
|
167
|
+
(globalThis as any).setTimeout = mockSetTimeout;
|
|
168
|
+
(globalThis as any).clearTimeout = mockClearTimeout;
|
|
169
|
+
(global as any).requestAnimationFrame = mockRequestAnimationFrame;
|
|
170
|
+
(global as any).cancelAnimationFrame = mockCancelAnimationFrame;
|
|
171
|
+
(globalThis as any).requestAnimationFrame = mockRequestAnimationFrame;
|
|
172
|
+
(globalThis as any).cancelAnimationFrame = mockCancelAnimationFrame;
|
|
173
|
+
|
|
174
|
+
// NOW import the component (after mocks are set up)
|
|
175
|
+
import { PrivateChatPage } from './PrivateChatPage';
|
|
176
|
+
import type { VNode } from 'elit/types';
|
|
177
|
+
|
|
178
|
+
// Helper function to render VNode to HTML string
|
|
179
|
+
function renderToString(vNode: VNode | string | number | undefined | null): string {
|
|
180
|
+
if (vNode == null || vNode === false) return '';
|
|
181
|
+
if (typeof vNode !== 'object') return String(vNode);
|
|
182
|
+
|
|
183
|
+
const { tagName, props, children } = vNode;
|
|
184
|
+
const attrs = props ? Object.entries(props)
|
|
185
|
+
.filter(([k, v]) => v != null && v !== false && k !== 'children' && k !== 'ref' && !k.startsWith('on'))
|
|
186
|
+
.map(([k, v]) => {
|
|
187
|
+
if (k === 'className' || k === 'class') return `class="${Array.isArray(v) ? v.join(' ') : v}"`;
|
|
188
|
+
if (k === 'style') return `style="${typeof v === 'string' ? v : Object.entries(v).map(([sk, sv]) => `${sk.replace(/([A-Z])/g, '-$1').toLowerCase()}:${sv}`).join(';')}"`;
|
|
189
|
+
if (v === true) return k;
|
|
190
|
+
return `${k}="${v}"`;
|
|
191
|
+
})
|
|
192
|
+
.join(' ') : '';
|
|
193
|
+
|
|
194
|
+
const childrenStr = children && children.length > 0
|
|
195
|
+
? children.map(c => renderToString(c as any)).join('')
|
|
196
|
+
: '';
|
|
197
|
+
|
|
198
|
+
return `<${tagName}${attrs ? ' ' + attrs : ''}>${childrenStr}</${tagName}>`;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Helper function to find child by tag name
|
|
202
|
+
function findChildByTagName(vNode: VNode, tagName: string): VNode | null {
|
|
203
|
+
if (vNode && typeof vNode === 'object' && 'tagName' in vNode) {
|
|
204
|
+
if (vNode.tagName === tagName) {
|
|
205
|
+
return vNode;
|
|
206
|
+
}
|
|
207
|
+
if (vNode.children) {
|
|
208
|
+
for (const child of vNode.children) {
|
|
209
|
+
if (typeof child === 'object' && child !== null) {
|
|
210
|
+
const found = findChildByTagName(child as VNode, tagName);
|
|
211
|
+
if (found) return found;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Helper function to find children by tag name
|
|
220
|
+
function findChildrenByTagName(vNode: VNode, tagName: string): VNode[] {
|
|
221
|
+
const results: VNode[] = [];
|
|
222
|
+
|
|
223
|
+
function search(node: VNode | string | number | null | undefined) {
|
|
224
|
+
if (node && typeof node === 'object' && 'tagName' in node) {
|
|
225
|
+
if (node.tagName === tagName) {
|
|
226
|
+
results.push(node);
|
|
227
|
+
}
|
|
228
|
+
if (node.children) {
|
|
229
|
+
for (const child of node.children) {
|
|
230
|
+
search(child as any);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
search(vNode);
|
|
237
|
+
return results;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
describe('PrivateChatPage Component', () => {
|
|
241
|
+
const otherUserId = '456';
|
|
242
|
+
const testUserId = '123';
|
|
243
|
+
|
|
244
|
+
// Clear REAL browser localStorage before any tests run
|
|
245
|
+
beforeAll(() => {
|
|
246
|
+
if (typeof localStorage !== 'undefined') {
|
|
247
|
+
localStorage.clear();
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
beforeEach(() => {
|
|
252
|
+
// Clear RAF callbacks FIRST
|
|
253
|
+
rafCallbacks.clear();
|
|
254
|
+
rafId = 0;
|
|
255
|
+
timeoutCallbacks.clear();
|
|
256
|
+
timeoutId = 0;
|
|
257
|
+
|
|
258
|
+
// Clear localStorage before each test
|
|
259
|
+
localStorageMock.clear();
|
|
260
|
+
|
|
261
|
+
// Also clear REAL browser localStorage
|
|
262
|
+
if (typeof localStorage !== 'undefined') {
|
|
263
|
+
localStorage.clear();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Set up authenticated user
|
|
267
|
+
localStorageMock.setItem('token', 'fake-token');
|
|
268
|
+
localStorageMock.setItem('user', JSON.stringify({ id: testUserId, name: 'Test User', email: 'test@example.com', bio: 'Test bio', avatar: '' }));
|
|
269
|
+
|
|
270
|
+
// Reset mock router
|
|
271
|
+
(mockRouter.push as any).mockClear();
|
|
272
|
+
(mockRouter.replace as any).mockClear();
|
|
273
|
+
|
|
274
|
+
// Reset fetch mock
|
|
275
|
+
mockFetchResponse = {
|
|
276
|
+
ok: true,
|
|
277
|
+
json: async () => ({ user: { id: '456', name: 'Other User', email: 'other@example.com', bio: '', avatar: '' } })
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// Clear event listeners
|
|
281
|
+
Object.keys(eventListeners).forEach(key => {
|
|
282
|
+
delete eventListeners[key];
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
afterEach(() => {
|
|
287
|
+
// Clean up event listeners after each test
|
|
288
|
+
Object.keys(eventListeners).forEach(key => {
|
|
289
|
+
delete eventListeners[key];
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// Clear RAF callbacks after each test
|
|
293
|
+
rafCallbacks.clear();
|
|
294
|
+
rafId = 0;
|
|
295
|
+
|
|
296
|
+
// Clear timeouts
|
|
297
|
+
timeoutCallbacks.clear();
|
|
298
|
+
timeoutId = 0;
|
|
299
|
+
|
|
300
|
+
// Clear REAL browser localStorage after each test
|
|
301
|
+
if (typeof localStorage !== 'undefined') {
|
|
302
|
+
localStorage.clear();
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
describe('authentication', () => {
|
|
307
|
+
it('should redirect to login if not authenticated', () => {
|
|
308
|
+
localStorageMock.clear();
|
|
309
|
+
const page = PrivateChatPage(mockRouter as any, otherUserId);
|
|
310
|
+
|
|
311
|
+
expect(mockRouter.push.calls.length).toBeGreaterThan(0);
|
|
312
|
+
expect(mockRouter.push.calls[0][0]).toBe('/login');
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('should render page when authenticated', () => {
|
|
316
|
+
const page = PrivateChatPage(mockRouter as any, otherUserId);
|
|
317
|
+
|
|
318
|
+
expect(page).toBeDefined();
|
|
319
|
+
expect(page.tagName).toBe('div');
|
|
320
|
+
expect(page.props?.className).toBe('chat-page');
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('should not redirect when authenticated', () => {
|
|
324
|
+
PrivateChatPage(mockRouter as any, otherUserId);
|
|
325
|
+
|
|
326
|
+
// Should not redirect (only the initial redirect check happens)
|
|
327
|
+
expect(mockRouter.push.calls.length).toBe(0);
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
describe('page structure', () => {
|
|
332
|
+
it('should render chat-page', () => {
|
|
333
|
+
const page = PrivateChatPage(mockRouter as any, otherUserId);
|
|
334
|
+
|
|
335
|
+
expect(page).toBeDefined();
|
|
336
|
+
expect(page.tagName).toBe('div');
|
|
337
|
+
expect(page.props?.className).toBe('chat-page');
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('should render chat-container', () => {
|
|
341
|
+
const page = PrivateChatPage(mockRouter as any, otherUserId);
|
|
342
|
+
|
|
343
|
+
expect(page.props?.className).toBe('chat-page');
|
|
344
|
+
|
|
345
|
+
const children = page.children as VNode[];
|
|
346
|
+
const container = children[0];
|
|
347
|
+
expect(container?.props?.className).toBe('chat-container');
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('should render chat-header', () => {
|
|
351
|
+
const page = PrivateChatPage(mockRouter as any, otherUserId);
|
|
352
|
+
const html = renderToString(page);
|
|
353
|
+
|
|
354
|
+
expect(html).toContain('chat-header');
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it('should render chat-messages', () => {
|
|
358
|
+
const page = PrivateChatPage(mockRouter as any, otherUserId);
|
|
359
|
+
const html = renderToString(page);
|
|
360
|
+
|
|
361
|
+
expect(html).toContain('chat-messages');
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('should render chat-input-area', () => {
|
|
365
|
+
const page = PrivateChatPage(mockRouter as any, otherUserId);
|
|
366
|
+
const html = renderToString(page);
|
|
367
|
+
|
|
368
|
+
expect(html).toContain('chat-input-area');
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
describe('header section', () => {
|
|
373
|
+
it('should render back button', () => {
|
|
374
|
+
const page = PrivateChatPage(mockRouter as any, otherUserId);
|
|
375
|
+
const html = renderToString(page);
|
|
376
|
+
|
|
377
|
+
expect(html).toContain('← Back');
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it('should render chat-title', () => {
|
|
381
|
+
const page = PrivateChatPage(mockRouter as any, otherUserId);
|
|
382
|
+
const html = renderToString(page);
|
|
383
|
+
|
|
384
|
+
expect(html).toContain('Chat');
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it('should render chat-subtitle', () => {
|
|
388
|
+
const page = PrivateChatPage(mockRouter as any, otherUserId);
|
|
389
|
+
const html = renderToString(page);
|
|
390
|
+
|
|
391
|
+
expect(html).toContain('Loading...');
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
describe('messages area', () => {
|
|
396
|
+
it('should render empty state when no messages', () => {
|
|
397
|
+
const page = PrivateChatPage(mockRouter as any, otherUserId);
|
|
398
|
+
const html = renderToString(page);
|
|
399
|
+
|
|
400
|
+
expect(html).toContain('chat-empty');
|
|
401
|
+
expect(html).toContain('No messages yet');
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it('should render chat-messages-list capability', () => {
|
|
405
|
+
const page = PrivateChatPage(mockRouter as any, otherUserId);
|
|
406
|
+
// chat-messages-list is conditionally rendered based on messages state
|
|
407
|
+
// When empty, it shows chat-empty instead
|
|
408
|
+
expect(page).toBeDefined();
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
describe('input area', () => {
|
|
413
|
+
it('should render chat input', () => {
|
|
414
|
+
const page = PrivateChatPage(mockRouter as any, otherUserId);
|
|
415
|
+
const html = renderToString(page);
|
|
416
|
+
|
|
417
|
+
expect(html).toContain('type="text"');
|
|
418
|
+
expect(html).toContain('chat-input');
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it('should render send button', () => {
|
|
422
|
+
const page = PrivateChatPage(mockRouter as any, otherUserId);
|
|
423
|
+
const html = renderToString(page);
|
|
424
|
+
|
|
425
|
+
expect(html).toContain('Send');
|
|
426
|
+
expect(html).toContain('chat-send-button');
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
it('should have correct placeholder', () => {
|
|
430
|
+
const page = PrivateChatPage(mockRouter as any, otherUserId);
|
|
431
|
+
const html = renderToString(page);
|
|
432
|
+
|
|
433
|
+
expect(html).toContain('Type your message...');
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
describe('error handling', () => {
|
|
438
|
+
it('should have error state', () => {
|
|
439
|
+
const page = PrivateChatPage(mockRouter as any, otherUserId);
|
|
440
|
+
// Error state is reactive
|
|
441
|
+
expect(page).toBeDefined();
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
it('should render auth-error when error exists', () => {
|
|
445
|
+
const page = PrivateChatPage(mockRouter as any, otherUserId);
|
|
446
|
+
// Error is reactive and only shows when error.value is set
|
|
447
|
+
expect(page).toBeDefined();
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
describe('loading state', () => {
|
|
452
|
+
it('should have loading state', () => {
|
|
453
|
+
const page = PrivateChatPage(mockRouter as any, otherUserId);
|
|
454
|
+
// Loading state is reactive
|
|
455
|
+
expect(page).toBeDefined();
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
it('should render chat-typing when loading', () => {
|
|
459
|
+
const page = PrivateChatPage(mockRouter as any, otherUserId);
|
|
460
|
+
// Typing indicator is reactive
|
|
461
|
+
expect(page).toBeDefined();
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
describe('CSS classes', () => {
|
|
466
|
+
it('should have correct CSS classes', () => {
|
|
467
|
+
const page = PrivateChatPage(mockRouter as any, otherUserId);
|
|
468
|
+
const html = renderToString(page);
|
|
469
|
+
|
|
470
|
+
expect(html).toContain('chat-page');
|
|
471
|
+
expect(html).toContain('chat-container');
|
|
472
|
+
expect(html).toContain('chat-header');
|
|
473
|
+
expect(html).toContain('chat-title');
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
describe('component consistency', () => {
|
|
478
|
+
it('should always return the same structure', () => {
|
|
479
|
+
const page1 = PrivateChatPage(mockRouter as any, otherUserId);
|
|
480
|
+
const page2 = PrivateChatPage(mockRouter as any, otherUserId);
|
|
481
|
+
|
|
482
|
+
expect(page1.tagName).toBe(page2.tagName);
|
|
483
|
+
expect(page1.props?.className).toBe(page2.props?.className);
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
it('should render without errors', () => {
|
|
487
|
+
expect(() => {
|
|
488
|
+
const page = PrivateChatPage(mockRouter as any, otherUserId);
|
|
489
|
+
renderToString(page);
|
|
490
|
+
}).not.toThrow();
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
it('should have h2 element', () => {
|
|
494
|
+
const page = PrivateChatPage(mockRouter as any, otherUserId);
|
|
495
|
+
const h2Elements = findChildrenByTagName(page, 'h2');
|
|
496
|
+
|
|
497
|
+
expect(h2Elements.length).toBeGreaterThan(0);
|
|
498
|
+
});
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
describe('reactive state', () => {
|
|
502
|
+
it('should have messages state', () => {
|
|
503
|
+
const page = PrivateChatPage(mockRouter as any, otherUserId);
|
|
504
|
+
expect(page).toBeDefined();
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
it('should have otherUser state', () => {
|
|
508
|
+
const page = PrivateChatPage(mockRouter as any, otherUserId);
|
|
509
|
+
expect(page).toBeDefined();
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
it('should have newMessage state', () => {
|
|
513
|
+
const page = PrivateChatPage(mockRouter as any, otherUserId);
|
|
514
|
+
expect(page).toBeDefined();
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it('should have error state', () => {
|
|
518
|
+
const page = PrivateChatPage(mockRouter as any, otherUserId);
|
|
519
|
+
expect(page).toBeDefined();
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
it('should have isLoading state', () => {
|
|
523
|
+
const page = PrivateChatPage(mockRouter as any, otherUserId);
|
|
524
|
+
expect(page).toBeDefined();
|
|
525
|
+
});
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
describe('EventSource integration', () => {
|
|
529
|
+
it('should initialize EventSource', () => {
|
|
530
|
+
const page = PrivateChatPage(mockRouter as any, otherUserId);
|
|
531
|
+
// EventSource is mocked, so just verify component renders
|
|
532
|
+
expect(page).toBeDefined();
|
|
533
|
+
});
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
describe('navigation', () => {
|
|
537
|
+
it('should have back button', () => {
|
|
538
|
+
const page = PrivateChatPage(mockRouter as any, otherUserId);
|
|
539
|
+
const html = renderToString(page);
|
|
540
|
+
|
|
541
|
+
expect(html).toContain('← Back');
|
|
542
|
+
});
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
describe('room ID generation', () => {
|
|
546
|
+
it('should accept otherUserId parameter', () => {
|
|
547
|
+
const page = PrivateChatPage(mockRouter as any, otherUserId);
|
|
548
|
+
expect(page).toBeDefined();
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
it('should handle different otherUserId', () => {
|
|
552
|
+
const page = PrivateChatPage(mockRouter as any, '789');
|
|
553
|
+
expect(page).toBeDefined();
|
|
554
|
+
});
|
|
555
|
+
});
|
|
556
|
+
});
|