@lobehub/chat 1.103.2 → 1.104.1
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/.cursor/rules/code-review.mdc +2 -0
- package/.cursor/rules/typescript.mdc +3 -1
- package/CHANGELOG.md +50 -0
- package/apps/desktop/src/main/controllers/BrowserWindowsCtr.ts +1 -1
- package/apps/desktop/src/main/controllers/ShortcutCtr.ts +9 -1
- package/apps/desktop/src/main/controllers/TrayMenuCtr.ts +1 -5
- package/apps/desktop/src/main/controllers/__tests__/ShortcutCtr.test.ts +14 -11
- package/apps/desktop/src/main/core/ui/ShortcutManager.ts +71 -5
- package/apps/desktop/src/main/shortcuts/config.ts +4 -2
- package/changelog/v1.json +18 -0
- package/locales/ar/hotkey.json +10 -4
- package/locales/ar/setting.json +12 -1
- package/locales/bg-BG/hotkey.json +10 -4
- package/locales/bg-BG/setting.json +12 -1
- package/locales/de-DE/hotkey.json +10 -4
- package/locales/de-DE/setting.json +12 -1
- package/locales/en-US/hotkey.json +10 -4
- package/locales/en-US/setting.json +12 -1
- package/locales/es-ES/hotkey.json +10 -4
- package/locales/es-ES/setting.json +12 -1
- package/locales/fa-IR/hotkey.json +10 -4
- package/locales/fa-IR/setting.json +12 -1
- package/locales/fr-FR/hotkey.json +10 -4
- package/locales/fr-FR/setting.json +12 -1
- package/locales/it-IT/hotkey.json +10 -4
- package/locales/it-IT/setting.json +12 -1
- package/locales/ja-JP/hotkey.json +10 -4
- package/locales/ja-JP/setting.json +12 -1
- package/locales/ko-KR/hotkey.json +10 -4
- package/locales/ko-KR/setting.json +12 -1
- package/locales/nl-NL/hotkey.json +10 -4
- package/locales/nl-NL/setting.json +12 -1
- package/locales/pl-PL/hotkey.json +10 -4
- package/locales/pl-PL/setting.json +12 -1
- package/locales/pt-BR/hotkey.json +10 -4
- package/locales/pt-BR/setting.json +12 -1
- package/locales/ru-RU/hotkey.json +10 -4
- package/locales/ru-RU/setting.json +12 -1
- package/locales/tr-TR/hotkey.json +10 -4
- package/locales/tr-TR/setting.json +12 -1
- package/locales/vi-VN/hotkey.json +10 -4
- package/locales/vi-VN/setting.json +12 -1
- package/locales/zh-CN/hotkey.json +10 -4
- package/locales/zh-CN/setting.json +12 -1
- package/locales/zh-TW/hotkey.json +10 -4
- package/locales/zh-TW/setting.json +12 -1
- package/package.json +1 -1
- package/packages/electron-client-ipc/src/events/shortcut.ts +3 -1
- package/packages/electron-client-ipc/src/types/shortcut.ts +11 -0
- package/src/app/[variants]/(main)/image/features/GenerationFeed/BatchItem.tsx +6 -1
- package/src/app/[variants]/(main)/image/features/GenerationFeed/GenerationItem/ErrorState.tsx +3 -2
- package/src/app/[variants]/(main)/image/features/GenerationFeed/GenerationItem/LoadingState.tsx +27 -24
- package/src/app/[variants]/(main)/image/features/GenerationFeed/GenerationItem/SuccessState.tsx +14 -3
- package/src/app/[variants]/(main)/image/features/GenerationFeed/GenerationItem/index.tsx +4 -7
- package/src/app/[variants]/(main)/image/features/GenerationFeed/GenerationItem/types.ts +3 -0
- package/src/app/[variants]/(main)/image/features/GenerationFeed/GenerationItem/utils.test.ts +600 -0
- package/src/app/[variants]/(main)/image/features/GenerationFeed/GenerationItem/utils.ts +126 -7
- package/src/app/[variants]/(main)/settings/hotkey/features/Conversation.tsx +3 -11
- package/src/app/[variants]/(main)/settings/hotkey/features/Desktop.tsx +92 -0
- package/src/app/[variants]/(main)/settings/hotkey/features/Essential.tsx +3 -11
- package/src/app/[variants]/(main)/settings/hotkey/page.tsx +3 -0
- package/src/const/desktop.ts +9 -0
- package/src/const/hotkeys.ts +20 -16
- package/src/const/imageGeneration.ts +18 -0
- package/src/features/User/UserPanel/useMenu.tsx +2 -2
- package/src/libs/model-runtime/utils/openaiCompatibleFactory/index.test.ts +3 -0
- package/src/libs/model-runtime/utils/openaiCompatibleFactory/index.ts +7 -5
- package/src/libs/model-runtime/utils/streams/openai/openai.ts +8 -4
- package/src/libs/model-runtime/utils/usageConverter.test.ts +45 -1
- package/src/libs/model-runtime/utils/usageConverter.ts +6 -2
- package/src/locales/default/hotkey.ts +13 -5
- package/src/locales/default/setting.ts +11 -0
- package/src/server/services/generation/index.test.ts +848 -0
- package/src/server/services/generation/index.ts +90 -69
- package/src/services/electron/settings.ts +19 -1
- package/src/store/electron/actions/settings.ts +42 -1
- package/src/store/electron/initialState.ts +9 -1
- package/src/store/electron/selectors/__tests__/desktopState.test.ts +6 -17
- package/src/store/electron/selectors/hotkey.ts +11 -0
- package/src/store/electron/selectors/index.ts +1 -0
- package/src/types/hotkey.ts +18 -4
- package/src/utils/number.test.ts +101 -1
- package/src/utils/number.ts +42 -0
@@ -0,0 +1,600 @@
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
2
|
+
|
3
|
+
import type { Generation, GenerationBatch } from '@/types/generation';
|
4
|
+
|
5
|
+
// Import functions for testing
|
6
|
+
import {
|
7
|
+
DEFAULT_MAX_ITEM_WIDTH,
|
8
|
+
getAspectRatio,
|
9
|
+
getImageDimensions,
|
10
|
+
getThumbnailMaxWidth,
|
11
|
+
} from './utils';
|
12
|
+
|
13
|
+
describe('getImageDimensions', () => {
|
14
|
+
// Mock base generation object
|
15
|
+
const baseGeneration: Generation = {
|
16
|
+
id: 'test-gen-id',
|
17
|
+
seed: 12345,
|
18
|
+
createdAt: new Date(),
|
19
|
+
asyncTaskId: null,
|
20
|
+
task: {
|
21
|
+
id: 'task-id',
|
22
|
+
status: 'success' as any,
|
23
|
+
},
|
24
|
+
};
|
25
|
+
|
26
|
+
describe('with asset dimensions', () => {
|
27
|
+
it('should return width, height and aspect ratio from asset', () => {
|
28
|
+
const generation: Generation = {
|
29
|
+
...baseGeneration,
|
30
|
+
asset: {
|
31
|
+
type: 'image',
|
32
|
+
width: 1920,
|
33
|
+
height: 1080,
|
34
|
+
},
|
35
|
+
};
|
36
|
+
|
37
|
+
const result = getImageDimensions(generation);
|
38
|
+
expect(result).toEqual({
|
39
|
+
width: 1920,
|
40
|
+
height: 1080,
|
41
|
+
aspectRatio: '1920 / 1080',
|
42
|
+
});
|
43
|
+
});
|
44
|
+
|
45
|
+
it('should prioritize asset even when other sources exist', () => {
|
46
|
+
const generation: Generation = {
|
47
|
+
...baseGeneration,
|
48
|
+
asset: {
|
49
|
+
type: 'image',
|
50
|
+
width: 800,
|
51
|
+
height: 600,
|
52
|
+
},
|
53
|
+
};
|
54
|
+
|
55
|
+
const generationBatch: GenerationBatch = {
|
56
|
+
id: 'batch-id',
|
57
|
+
provider: 'test',
|
58
|
+
model: 'test-model',
|
59
|
+
prompt: 'test prompt',
|
60
|
+
width: 1024,
|
61
|
+
height: 1024,
|
62
|
+
config: {
|
63
|
+
prompt: 'test',
|
64
|
+
width: 512,
|
65
|
+
height: 512,
|
66
|
+
},
|
67
|
+
createdAt: new Date(),
|
68
|
+
generations: [],
|
69
|
+
};
|
70
|
+
|
71
|
+
const result = getImageDimensions(generation, generationBatch);
|
72
|
+
expect(result).toEqual({
|
73
|
+
width: 800,
|
74
|
+
height: 600,
|
75
|
+
aspectRatio: '800 / 600',
|
76
|
+
});
|
77
|
+
});
|
78
|
+
});
|
79
|
+
|
80
|
+
describe('with config dimensions', () => {
|
81
|
+
it('should return dimensions from config when asset is not available', () => {
|
82
|
+
const generation: Generation = {
|
83
|
+
...baseGeneration,
|
84
|
+
asset: null,
|
85
|
+
};
|
86
|
+
|
87
|
+
const generationBatch: GenerationBatch = {
|
88
|
+
id: 'batch-id',
|
89
|
+
provider: 'test',
|
90
|
+
model: 'test-model',
|
91
|
+
prompt: 'test prompt',
|
92
|
+
config: {
|
93
|
+
prompt: 'test',
|
94
|
+
width: 1024,
|
95
|
+
height: 768,
|
96
|
+
},
|
97
|
+
createdAt: new Date(),
|
98
|
+
generations: [],
|
99
|
+
};
|
100
|
+
|
101
|
+
const result = getImageDimensions(generation, generationBatch);
|
102
|
+
expect(result).toEqual({
|
103
|
+
width: 1024,
|
104
|
+
height: 768,
|
105
|
+
aspectRatio: '1024 / 768',
|
106
|
+
});
|
107
|
+
});
|
108
|
+
});
|
109
|
+
|
110
|
+
describe('with batch top-level dimensions', () => {
|
111
|
+
it('should return dimensions from batch when config is not available', () => {
|
112
|
+
const generation: Generation = {
|
113
|
+
...baseGeneration,
|
114
|
+
asset: null,
|
115
|
+
};
|
116
|
+
|
117
|
+
const generationBatch: GenerationBatch = {
|
118
|
+
id: 'batch-id',
|
119
|
+
provider: 'test',
|
120
|
+
model: 'test-model',
|
121
|
+
prompt: 'test prompt',
|
122
|
+
width: 1280,
|
123
|
+
height: 720,
|
124
|
+
createdAt: new Date(),
|
125
|
+
generations: [],
|
126
|
+
};
|
127
|
+
|
128
|
+
const result = getImageDimensions(generation, generationBatch);
|
129
|
+
expect(result).toEqual({
|
130
|
+
width: 1280,
|
131
|
+
height: 720,
|
132
|
+
aspectRatio: '1280 / 720',
|
133
|
+
});
|
134
|
+
});
|
135
|
+
});
|
136
|
+
|
137
|
+
describe('with size parameter', () => {
|
138
|
+
it('should parse dimensions from size parameter', () => {
|
139
|
+
const generation: Generation = {
|
140
|
+
...baseGeneration,
|
141
|
+
asset: null,
|
142
|
+
};
|
143
|
+
|
144
|
+
const generationBatch: GenerationBatch = {
|
145
|
+
id: 'batch-id',
|
146
|
+
provider: 'test',
|
147
|
+
model: 'test-model',
|
148
|
+
prompt: 'test prompt',
|
149
|
+
config: {
|
150
|
+
prompt: 'test',
|
151
|
+
size: '1920x1080',
|
152
|
+
},
|
153
|
+
createdAt: new Date(),
|
154
|
+
generations: [],
|
155
|
+
};
|
156
|
+
|
157
|
+
const result = getImageDimensions(generation, generationBatch);
|
158
|
+
expect(result).toEqual({
|
159
|
+
width: 1920,
|
160
|
+
height: 1080,
|
161
|
+
aspectRatio: '1920 / 1080',
|
162
|
+
});
|
163
|
+
});
|
164
|
+
|
165
|
+
it('should ignore size when it is "auto"', () => {
|
166
|
+
const generation: Generation = {
|
167
|
+
...baseGeneration,
|
168
|
+
asset: null,
|
169
|
+
};
|
170
|
+
|
171
|
+
const generationBatch: GenerationBatch = {
|
172
|
+
id: 'batch-id',
|
173
|
+
provider: 'test',
|
174
|
+
model: 'test-model',
|
175
|
+
prompt: 'test prompt',
|
176
|
+
config: {
|
177
|
+
prompt: 'test',
|
178
|
+
size: 'auto',
|
179
|
+
aspectRatio: '16:9',
|
180
|
+
},
|
181
|
+
createdAt: new Date(),
|
182
|
+
generations: [],
|
183
|
+
};
|
184
|
+
|
185
|
+
const result = getImageDimensions(generation, generationBatch);
|
186
|
+
expect(result).toEqual({
|
187
|
+
width: null,
|
188
|
+
height: null,
|
189
|
+
aspectRatio: '16 / 9',
|
190
|
+
});
|
191
|
+
});
|
192
|
+
});
|
193
|
+
|
194
|
+
describe('with aspectRatio parameter only', () => {
|
195
|
+
it('should return aspect ratio without dimensions', () => {
|
196
|
+
const generation: Generation = {
|
197
|
+
...baseGeneration,
|
198
|
+
asset: null,
|
199
|
+
};
|
200
|
+
|
201
|
+
const generationBatch: GenerationBatch = {
|
202
|
+
id: 'batch-id',
|
203
|
+
provider: 'test',
|
204
|
+
model: 'test-model',
|
205
|
+
prompt: 'test prompt',
|
206
|
+
config: {
|
207
|
+
prompt: 'test',
|
208
|
+
aspectRatio: '16:9',
|
209
|
+
},
|
210
|
+
createdAt: new Date(),
|
211
|
+
generations: [],
|
212
|
+
};
|
213
|
+
|
214
|
+
const result = getImageDimensions(generation, generationBatch);
|
215
|
+
expect(result).toEqual({
|
216
|
+
width: null,
|
217
|
+
height: null,
|
218
|
+
aspectRatio: '16 / 9',
|
219
|
+
});
|
220
|
+
});
|
221
|
+
|
222
|
+
it('should handle various aspect ratio formats', () => {
|
223
|
+
const testCases = [
|
224
|
+
{ aspectRatio: '1:1', expected: '1 / 1' },
|
225
|
+
{ aspectRatio: '4:3', expected: '4 / 3' },
|
226
|
+
{ aspectRatio: '16:9', expected: '16 / 9' },
|
227
|
+
{ aspectRatio: '21:9', expected: '21 / 9' },
|
228
|
+
];
|
229
|
+
|
230
|
+
testCases.forEach(({ aspectRatio, expected }) => {
|
231
|
+
const generation: Generation = {
|
232
|
+
...baseGeneration,
|
233
|
+
asset: null,
|
234
|
+
};
|
235
|
+
|
236
|
+
const generationBatch: GenerationBatch = {
|
237
|
+
id: 'batch-id',
|
238
|
+
provider: 'test',
|
239
|
+
model: 'test-model',
|
240
|
+
prompt: 'test prompt',
|
241
|
+
config: {
|
242
|
+
prompt: 'test',
|
243
|
+
aspectRatio,
|
244
|
+
},
|
245
|
+
createdAt: new Date(),
|
246
|
+
generations: [],
|
247
|
+
};
|
248
|
+
|
249
|
+
const result = getImageDimensions(generation, generationBatch);
|
250
|
+
expect(result.aspectRatio).toBe(expected);
|
251
|
+
});
|
252
|
+
});
|
253
|
+
});
|
254
|
+
|
255
|
+
describe('edge cases', () => {
|
256
|
+
it('should return all null when no dimensions are available', () => {
|
257
|
+
const generation: Generation = {
|
258
|
+
...baseGeneration,
|
259
|
+
asset: null,
|
260
|
+
};
|
261
|
+
|
262
|
+
const result = getImageDimensions(generation);
|
263
|
+
expect(result).toEqual({
|
264
|
+
width: null,
|
265
|
+
height: null,
|
266
|
+
aspectRatio: null,
|
267
|
+
});
|
268
|
+
});
|
269
|
+
|
270
|
+
it('should handle partial asset dimensions', () => {
|
271
|
+
const generation: Generation = {
|
272
|
+
...baseGeneration,
|
273
|
+
asset: {
|
274
|
+
type: 'image',
|
275
|
+
width: 1920,
|
276
|
+
// height is missing
|
277
|
+
},
|
278
|
+
};
|
279
|
+
|
280
|
+
const generationBatch: GenerationBatch = {
|
281
|
+
id: 'batch-id',
|
282
|
+
provider: 'test',
|
283
|
+
model: 'test-model',
|
284
|
+
prompt: 'test prompt',
|
285
|
+
config: {
|
286
|
+
prompt: 'test',
|
287
|
+
width: 1024,
|
288
|
+
height: 768,
|
289
|
+
},
|
290
|
+
createdAt: new Date(),
|
291
|
+
generations: [],
|
292
|
+
};
|
293
|
+
|
294
|
+
const result = getImageDimensions(generation, generationBatch);
|
295
|
+
expect(result).toEqual({
|
296
|
+
width: 1024,
|
297
|
+
height: 768,
|
298
|
+
aspectRatio: '1024 / 768',
|
299
|
+
});
|
300
|
+
});
|
301
|
+
|
302
|
+
it('should handle invalid size format', () => {
|
303
|
+
const generation: Generation = {
|
304
|
+
...baseGeneration,
|
305
|
+
asset: null,
|
306
|
+
};
|
307
|
+
|
308
|
+
const generationBatch: GenerationBatch = {
|
309
|
+
id: 'batch-id',
|
310
|
+
provider: 'test',
|
311
|
+
model: 'test-model',
|
312
|
+
prompt: 'test prompt',
|
313
|
+
config: {
|
314
|
+
prompt: 'test',
|
315
|
+
size: 'invalid-format',
|
316
|
+
},
|
317
|
+
createdAt: new Date(),
|
318
|
+
generations: [],
|
319
|
+
};
|
320
|
+
|
321
|
+
const result = getImageDimensions(generation, generationBatch);
|
322
|
+
expect(result).toEqual({
|
323
|
+
width: null,
|
324
|
+
height: null,
|
325
|
+
aspectRatio: null,
|
326
|
+
});
|
327
|
+
});
|
328
|
+
|
329
|
+
it('should handle invalid aspectRatio format', () => {
|
330
|
+
const generation: Generation = {
|
331
|
+
...baseGeneration,
|
332
|
+
asset: null,
|
333
|
+
};
|
334
|
+
|
335
|
+
const generationBatch: GenerationBatch = {
|
336
|
+
id: 'batch-id',
|
337
|
+
provider: 'test',
|
338
|
+
model: 'test-model',
|
339
|
+
prompt: 'test prompt',
|
340
|
+
config: {
|
341
|
+
prompt: 'test',
|
342
|
+
aspectRatio: 'invalid-format',
|
343
|
+
},
|
344
|
+
createdAt: new Date(),
|
345
|
+
generations: [],
|
346
|
+
};
|
347
|
+
|
348
|
+
const result = getImageDimensions(generation, generationBatch);
|
349
|
+
expect(result).toEqual({
|
350
|
+
width: null,
|
351
|
+
height: null,
|
352
|
+
aspectRatio: null,
|
353
|
+
});
|
354
|
+
});
|
355
|
+
|
356
|
+
it('should handle zero dimensions', () => {
|
357
|
+
const generation: Generation = {
|
358
|
+
...baseGeneration,
|
359
|
+
asset: {
|
360
|
+
type: 'image',
|
361
|
+
width: 0,
|
362
|
+
height: 0,
|
363
|
+
},
|
364
|
+
};
|
365
|
+
|
366
|
+
const result = getImageDimensions(generation);
|
367
|
+
expect(result).toEqual({
|
368
|
+
width: null,
|
369
|
+
height: null,
|
370
|
+
aspectRatio: null,
|
371
|
+
});
|
372
|
+
});
|
373
|
+
});
|
374
|
+
});
|
375
|
+
|
376
|
+
describe('getAspectRatio (isolated unit testing)', () => {
|
377
|
+
const mockGeneration: Generation = {
|
378
|
+
id: 'test-gen-id',
|
379
|
+
seed: 12345,
|
380
|
+
createdAt: new Date(),
|
381
|
+
asyncTaskId: null,
|
382
|
+
task: {
|
383
|
+
id: 'task-id',
|
384
|
+
status: 'success' as any,
|
385
|
+
},
|
386
|
+
};
|
387
|
+
const mockGenerationBatch: GenerationBatch = {
|
388
|
+
id: 'test-batch-id',
|
389
|
+
provider: 'test-provider',
|
390
|
+
model: 'test-model',
|
391
|
+
prompt: 'test prompt',
|
392
|
+
createdAt: new Date(),
|
393
|
+
generations: [],
|
394
|
+
};
|
395
|
+
|
396
|
+
beforeEach(() => {
|
397
|
+
vi.clearAllMocks();
|
398
|
+
});
|
399
|
+
|
400
|
+
it('should return aspectRatio from getImageDimensions when dimensions have aspectRatio', () => {
|
401
|
+
// Test the actual implementation directly with mock data
|
402
|
+
const mockGen: Generation = {
|
403
|
+
...mockGeneration,
|
404
|
+
asset: {
|
405
|
+
type: 'image',
|
406
|
+
width: 1920,
|
407
|
+
height: 1080,
|
408
|
+
},
|
409
|
+
};
|
410
|
+
|
411
|
+
const result = getAspectRatio(mockGen);
|
412
|
+
expect(result).toBe('1920 / 1080');
|
413
|
+
});
|
414
|
+
|
415
|
+
it('should return default "1 / 1" when no dimensions are available', () => {
|
416
|
+
const result = getAspectRatio(mockGeneration, mockGenerationBatch);
|
417
|
+
expect(result).toBe('1 / 1');
|
418
|
+
});
|
419
|
+
|
420
|
+
it('should work with different aspectRatio sources', () => {
|
421
|
+
const mockBatch: GenerationBatch = {
|
422
|
+
id: 'test-batch',
|
423
|
+
provider: 'test-provider',
|
424
|
+
model: 'test-model',
|
425
|
+
prompt: 'test prompt',
|
426
|
+
createdAt: new Date(),
|
427
|
+
generations: [],
|
428
|
+
config: {
|
429
|
+
prompt: 'test prompt',
|
430
|
+
aspectRatio: '16:9',
|
431
|
+
},
|
432
|
+
};
|
433
|
+
|
434
|
+
const result = getAspectRatio(mockGeneration, mockBatch);
|
435
|
+
expect(result).toBe('16 / 9');
|
436
|
+
});
|
437
|
+
});
|
438
|
+
|
439
|
+
describe('getThumbnailMaxWidth (isolated unit testing)', () => {
|
440
|
+
const mockGeneration: Generation = {
|
441
|
+
id: 'test-gen-id',
|
442
|
+
seed: 12345,
|
443
|
+
createdAt: new Date(),
|
444
|
+
asyncTaskId: null,
|
445
|
+
task: {
|
446
|
+
id: 'task-id',
|
447
|
+
status: 'success' as any,
|
448
|
+
},
|
449
|
+
};
|
450
|
+
const mockGenerationBatch: GenerationBatch = {
|
451
|
+
id: 'test-batch-id',
|
452
|
+
provider: 'test-provider',
|
453
|
+
model: 'test-model',
|
454
|
+
prompt: 'test prompt',
|
455
|
+
createdAt: new Date(),
|
456
|
+
generations: [],
|
457
|
+
};
|
458
|
+
|
459
|
+
// Mock window.innerHeight for tests
|
460
|
+
const originalWindow = global.window;
|
461
|
+
|
462
|
+
beforeEach(() => {
|
463
|
+
vi.clearAllMocks();
|
464
|
+
Object.defineProperty(global, 'window', {
|
465
|
+
writable: true,
|
466
|
+
value: {
|
467
|
+
innerHeight: 800,
|
468
|
+
},
|
469
|
+
});
|
470
|
+
});
|
471
|
+
|
472
|
+
afterEach(() => {
|
473
|
+
global.window = originalWindow;
|
474
|
+
});
|
475
|
+
|
476
|
+
it('should return DEFAULT_MAX_ITEM_WIDTH when no dimensions available', () => {
|
477
|
+
const result = getThumbnailMaxWidth(mockGeneration, mockGenerationBatch);
|
478
|
+
expect(result).toBe(DEFAULT_MAX_ITEM_WIDTH);
|
479
|
+
});
|
480
|
+
|
481
|
+
it('should return DEFAULT_MAX_ITEM_WIDTH when width is missing', () => {
|
482
|
+
const mockGen: Generation = {
|
483
|
+
...mockGeneration,
|
484
|
+
// No asset with width/height, should fall back to default
|
485
|
+
};
|
486
|
+
const result = getThumbnailMaxWidth(mockGen);
|
487
|
+
expect(result).toBe(DEFAULT_MAX_ITEM_WIDTH);
|
488
|
+
});
|
489
|
+
|
490
|
+
it('should return DEFAULT_MAX_ITEM_WIDTH when height is missing', () => {
|
491
|
+
const mockGen: Generation = {
|
492
|
+
...mockGeneration,
|
493
|
+
// No asset with valid dimensions
|
494
|
+
};
|
495
|
+
const result = getThumbnailMaxWidth(mockGen);
|
496
|
+
expect(result).toBe(DEFAULT_MAX_ITEM_WIDTH);
|
497
|
+
});
|
498
|
+
|
499
|
+
it('should calculate width based on screen height constraint', () => {
|
500
|
+
const mockGen: Generation = {
|
501
|
+
...mockGeneration,
|
502
|
+
asset: {
|
503
|
+
type: 'image',
|
504
|
+
width: 300,
|
505
|
+
height: 200,
|
506
|
+
},
|
507
|
+
};
|
508
|
+
|
509
|
+
// aspectRatio = 300/200 = 1.5
|
510
|
+
// maxScreenHeight = 800/2 = 400
|
511
|
+
// maxWidthFromHeight = 400 * 1.5 = 600
|
512
|
+
// maxReasonableWidth = 200 * 2 = 400
|
513
|
+
// min(600, 400) = 400
|
514
|
+
const result = getThumbnailMaxWidth(mockGen);
|
515
|
+
expect(result).toBe(400);
|
516
|
+
});
|
517
|
+
|
518
|
+
it('should apply maxReasonableWidth limit', () => {
|
519
|
+
const mockGen: Generation = {
|
520
|
+
...mockGeneration,
|
521
|
+
asset: {
|
522
|
+
type: 'image',
|
523
|
+
width: 600,
|
524
|
+
height: 200,
|
525
|
+
},
|
526
|
+
};
|
527
|
+
|
528
|
+
// aspectRatio = 600/200 = 3
|
529
|
+
// maxScreenHeight = 800/2 = 400
|
530
|
+
// maxWidthFromHeight = 400 * 3 = 1200
|
531
|
+
// maxReasonableWidth = 200 * 2 = 400
|
532
|
+
// min(1200, 400) = 400
|
533
|
+
const result = getThumbnailMaxWidth(mockGen);
|
534
|
+
expect(result).toBe(400);
|
535
|
+
});
|
536
|
+
|
537
|
+
it('should use screen height constraint when smaller', () => {
|
538
|
+
const mockGen: Generation = {
|
539
|
+
...mockGeneration,
|
540
|
+
asset: {
|
541
|
+
type: 'image',
|
542
|
+
width: 200,
|
543
|
+
height: 400,
|
544
|
+
},
|
545
|
+
};
|
546
|
+
|
547
|
+
// aspectRatio = 200/400 = 0.5
|
548
|
+
// maxScreenHeight = 800/2 = 400
|
549
|
+
// maxWidthFromHeight = 400 * 0.5 = 200
|
550
|
+
// maxReasonableWidth = 200 * 2 = 400
|
551
|
+
// min(200, 400) = 200
|
552
|
+
const result = getThumbnailMaxWidth(mockGen);
|
553
|
+
expect(result).toBe(200);
|
554
|
+
});
|
555
|
+
|
556
|
+
it('should handle different window.innerHeight values', () => {
|
557
|
+
Object.defineProperty(global, 'window', {
|
558
|
+
writable: true,
|
559
|
+
value: {
|
560
|
+
innerHeight: 600,
|
561
|
+
},
|
562
|
+
});
|
563
|
+
|
564
|
+
const mockGen: Generation = {
|
565
|
+
...mockGeneration,
|
566
|
+
asset: {
|
567
|
+
type: 'image',
|
568
|
+
width: 400,
|
569
|
+
height: 200,
|
570
|
+
},
|
571
|
+
};
|
572
|
+
|
573
|
+
// aspectRatio = 400/200 = 2
|
574
|
+
// maxScreenHeight = 600/2 = 300
|
575
|
+
// maxWidthFromHeight = 300 * 2 = 600
|
576
|
+
// maxReasonableWidth = 200 * 2 = 400
|
577
|
+
// min(600, 400) = 400
|
578
|
+
const result = getThumbnailMaxWidth(mockGen);
|
579
|
+
expect(result).toBe(400);
|
580
|
+
});
|
581
|
+
|
582
|
+
it('should round calculated width correctly', () => {
|
583
|
+
const mockGen: Generation = {
|
584
|
+
...mockGeneration,
|
585
|
+
asset: {
|
586
|
+
type: 'image',
|
587
|
+
width: 512,
|
588
|
+
height: 1000,
|
589
|
+
},
|
590
|
+
};
|
591
|
+
|
592
|
+
// aspectRatio = 512/1000 = 0.512
|
593
|
+
// maxScreenHeight = 800/2 = 400
|
594
|
+
// maxWidthFromHeight = Math.round(400 * 0.512) = Math.round(204.8) = 205
|
595
|
+
// maxReasonableWidth = 200 * 2 = 400
|
596
|
+
// min(205, 400) = 205
|
597
|
+
const result = getThumbnailMaxWidth(mockGen);
|
598
|
+
expect(result).toBe(205);
|
599
|
+
});
|
600
|
+
});
|