itube-specs 0.0.712 → 0.0.713

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 (24) hide show
  1. package/package.json +1 -1
  2. package/runtime/utils/cleaners/clean-category-card.test.ts +45 -0
  3. package/runtime/utils/cleaners/clean-category-info.test.ts +42 -0
  4. package/runtime/utils/cleaners/clean-channel-card.test.ts +44 -0
  5. package/runtime/utils/cleaners/clean-channel-info.test.ts +41 -0
  6. package/runtime/utils/cleaners/clean-mini-category-card.test.ts +31 -0
  7. package/runtime/utils/cleaners/clean-model-card.test.ts +57 -0
  8. package/runtime/utils/cleaners/clean-playlist-card.test.ts +45 -0
  9. package/runtime/utils/cleaners/clean-playlist-data.test.ts +42 -0
  10. package/runtime/utils/cleaners/clean-playlist-video.test.ts +39 -0
  11. package/runtime/utils/cleaners/clean-profile-data.test.ts +30 -0
  12. package/runtime/utils/cleaners/clean-user-playlists-card.test.ts +40 -0
  13. package/runtime/utils/cleaners/clean-video-card.test.ts +57 -0
  14. package/runtime/utils/cleaners/clean-video-data.test.ts +57 -0
  15. package/runtime/utils/converters/convert-categories-to-chips.test.ts +26 -0
  16. package/runtime/utils/converters/convert-categories-to-footer.test.ts +20 -0
  17. package/runtime/utils/converters/convert-model-card-to-chips.test.ts +20 -0
  18. package/runtime/utils/converters/convert-query-categories.test.ts +22 -0
  19. package/runtime/utils/converters/convert-string-array-to-chips.test.ts +21 -0
  20. package/runtime/utils/converters/group-categories-by-first-letter.test.ts +46 -0
  21. package/runtime/utils/get-multiple-query.test.ts +32 -0
  22. package/runtime/utils/get-selected-query.test.ts +24 -0
  23. package/runtime/utils/is-mobile-device.test.ts +24 -0
  24. package/runtime/utils/video-data-add-model-icon.test.ts +32 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "itube-specs",
3
3
  "type": "module",
4
- "version": "0.0.712",
4
+ "version": "0.0.713",
5
5
  "main": "./nuxt.config.ts",
6
6
  "types": "./types/index.d.ts",
7
7
  "scripts": {
@@ -0,0 +1,45 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { cleanCategoryCard } from './clean-category-card';
3
+
4
+ describe('cleanCategoryCard', () => {
5
+ it('полные данные', () => {
6
+ const raw = {
7
+ name: 'teen',
8
+ title: 'Teen',
9
+ videosCount: 100,
10
+ video_thumb_urls: {
11
+ webp: { '320x180': 'https://img.com/small.webp' },
12
+ },
13
+ guid: 'abc-123',
14
+ description: 'Test desc',
15
+ icon: 'icon.svg',
16
+ };
17
+ const result = cleanCategoryCard(raw as any);
18
+ expect(result).toEqual({
19
+ name: 'teen',
20
+ title: 'Teen',
21
+ videosCount: 100,
22
+ thumbUrls: { webp: { '320x180': 'https://img.com/small.webp' } },
23
+ guid: 'abc-123',
24
+ description: 'Test desc',
25
+ icon: 'icon.svg',
26
+ });
27
+ });
28
+
29
+ it('пустые данные → дефолты', () => {
30
+ const result = cleanCategoryCard({} as any);
31
+ expect(result.name).toBe('');
32
+ expect(result.title).toBe('');
33
+ expect(result.videosCount).toBe(0);
34
+ expect(result.thumbUrls).toBeUndefined();
35
+ expect(result.guid).toBe('');
36
+ expect(result.description).toBe('');
37
+ expect(result.icon).toBe('');
38
+ });
39
+
40
+ it('thumbUrls без нужного размера', () => {
41
+ const raw = { video_thumb_urls: { webp: {} } };
42
+ const result = cleanCategoryCard(raw as any);
43
+ expect(result.thumbUrls).toEqual({ webp: { '320x180': '' } });
44
+ });
45
+ });
@@ -0,0 +1,42 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { cleanCategoryInfo } from './clean-category-info';
3
+
4
+ describe('cleanCategoryInfo', () => {
5
+ it('полные данные', () => {
6
+ const raw = {
7
+ guid: 'abc',
8
+ title: 'Teen',
9
+ videosCount: 50,
10
+ description: 'desc',
11
+ fullDescription: 'full desc',
12
+ related: [{ title: 'MILF', name: 'milf' }],
13
+ meta_description: 'meta desc',
14
+ meta_title: 'meta title',
15
+ header: 'h1 header',
16
+ };
17
+ const result = cleanCategoryInfo(raw as any);
18
+ expect(result.guid).toBe('abc');
19
+ expect(result.title).toBe('Teen');
20
+ expect(result.videosCount).toBe(50);
21
+ expect(result.fullDescription).toBe('full desc');
22
+ expect(result.metaDescription).toBe('meta desc');
23
+ expect(result.metaTitle).toBe('meta title');
24
+ expect(result.header).toBe('h1 header');
25
+ expect(result.related).toEqual([
26
+ { title: 'MILF', value: 'milf', prefix: 'categories' },
27
+ ]);
28
+ });
29
+
30
+ it('пустые данные → дефолты', () => {
31
+ const result = cleanCategoryInfo({} as any);
32
+ expect(result.guid).toBe('');
33
+ expect(result.title).toBe('');
34
+ expect(result.videosCount).toBe(0);
35
+ expect(result.description).toBe('');
36
+ expect(result.fullDescription).toBe('');
37
+ expect(result.related).toEqual([]);
38
+ expect(result.metaDescription).toBe('');
39
+ expect(result.metaTitle).toBe('');
40
+ expect(result.header).toBe('');
41
+ });
42
+ });
@@ -0,0 +1,44 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { cleanChannelCard } from './clean-channel-card';
3
+
4
+ describe('cleanChannelCard', () => {
5
+ it('полные данные', () => {
6
+ const raw = {
7
+ guid: 'ch-1',
8
+ name: 'Channel',
9
+ url: '/channel/1',
10
+ videos_count: 200,
11
+ is_network: true,
12
+ updated: 1700000000,
13
+ avatar_url: 'https://img.com/avatar.jpg',
14
+ video_thumb_urls: {
15
+ webp: {
16
+ '320x180': 'https://img.com/s.webp',
17
+ '480x270': 'https://img.com/m.webp',
18
+ },
19
+ },
20
+ description: 'Channel desc',
21
+ };
22
+ const result = cleanChannelCard(raw as any);
23
+ expect(result.guid).toBe('ch-1');
24
+ expect(result.name).toBe('Channel');
25
+ expect(result.videosCount).toBe(200);
26
+ expect(result.isNetwork).toBe(true);
27
+ expect(result.avatarUrl).toBe('https://img.com/avatar.jpg');
28
+ expect(result.thumbUrls).toEqual({
29
+ webp: {
30
+ '320x180': 'https://img.com/s.webp',
31
+ '480x270': 'https://img.com/m.webp',
32
+ },
33
+ });
34
+ });
35
+
36
+ it('пустые данные → дефолты', () => {
37
+ const result = cleanChannelCard({} as any);
38
+ expect(result.guid).toBe('');
39
+ expect(result.name).toBe('');
40
+ expect(result.videosCount).toBe(0);
41
+ expect(result.isNetwork).toBe(false);
42
+ expect(result.thumbUrls).toBeUndefined();
43
+ });
44
+ });
@@ -0,0 +1,41 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { cleanChannelInfo } from './clean-channel-info';
3
+
4
+ describe('cleanChannelInfo', () => {
5
+ it('полные данные', () => {
6
+ const raw = {
7
+ guid: 'ch-1',
8
+ name: 'Channel Name',
9
+ url: '/channel/1',
10
+ description: 'desc',
11
+ videos_count: 100,
12
+ is_network: false,
13
+ updated: 1700000000,
14
+ avatar_url: 'https://img.com/avatar.jpg',
15
+ video_thumb_urls: { webp: { '320x180': 'https://img.com/s.webp' } },
16
+ tags: ['tag1', 'tag2'],
17
+ };
18
+ const result = cleanChannelInfo(raw as any);
19
+ expect(result.title).toBe('Channel Name');
20
+ expect(result.img).toBe('https://img.com/avatar.jpg');
21
+ expect(result.tags).toEqual(['tag1', 'tag2']);
22
+ });
23
+
24
+ it('без avatar_url → берёт thumb', () => {
25
+ const raw = {
26
+ avatar_url: '',
27
+ video_thumb_urls: { webp: { '320x180': 'https://img.com/thumb.webp' } },
28
+ };
29
+ const result = cleanChannelInfo(raw as any);
30
+ expect(result.img).toBe('https://img.com/thumb.webp');
31
+ });
32
+
33
+ it('пустые данные с пустым webp → дефолты', () => {
34
+ const raw = { video_thumb_urls: { webp: {} } };
35
+ const result = cleanChannelInfo(raw as any);
36
+ expect(result.guid).toBe('');
37
+ expect(result.title).toBe('');
38
+ expect(result.videosCount).toBe(0);
39
+ expect(result.tags).toEqual([]);
40
+ });
41
+ });
@@ -0,0 +1,31 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { cleanMiniCategoryCard } from './clean-mini-category-card';
3
+
4
+ describe('cleanMiniCategoryCard', () => {
5
+ it('полные данные', () => {
6
+ const raw = {
7
+ name: 'teen',
8
+ title: 'Teen',
9
+ is_top: true,
10
+ videosCount: 50,
11
+ icon: 'icon.svg',
12
+ };
13
+ const result = cleanMiniCategoryCard(raw as any);
14
+ expect(result).toEqual({
15
+ name: 'teen',
16
+ title: 'Teen',
17
+ isTop: true,
18
+ videosCount: 50,
19
+ icon: 'icon.svg',
20
+ });
21
+ });
22
+
23
+ it('пустые данные → дефолты', () => {
24
+ const result = cleanMiniCategoryCard({} as any);
25
+ expect(result.name).toBe('');
26
+ expect(result.title).toBe('');
27
+ expect(result.isTop).toBe(false);
28
+ expect(result.videosCount).toBe(0);
29
+ expect(result.icon).toBeUndefined();
30
+ });
31
+ });
@@ -0,0 +1,57 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { cleanModelCard } from './clean-model-card';
3
+
4
+ const makeRaw = (overrides = {}) => ({
5
+ title: 'Model Name',
6
+ videos_count: 10,
7
+ video_thumb_urls: { webp: { '320x180': 'https://img.com/s.webp' } },
8
+ primary_image_url: 'https://img.com/primary.jpg',
9
+ guid: 'model-1',
10
+ parameters: [
11
+ { name: 'birthplace_country', values: [{ title: 'USA', name: 'usa' }] },
12
+ { name: 'age', values: [{ title: '25' }] },
13
+ { name: 'height_cm', values: [{ title: '170' }] },
14
+ { name: 'weight_kg', values: [{ title: '55' }] },
15
+ ],
16
+ ...overrides,
17
+ });
18
+
19
+ describe('cleanModelCard', () => {
20
+ it('полные данные', () => {
21
+ const result = cleanModelCard(makeRaw() as any);
22
+ expect(result.title).toBe('Model Name');
23
+ expect(result.videosCount).toBe(10);
24
+ expect(result.thumbUrl).toBe('https://img.com/s.webp');
25
+ expect(result.guid).toBe('model-1');
26
+ expect(result.country).toEqual({ title: 'USA', name: 'usa' });
27
+ });
28
+
29
+ it('параметры: age, height, weight', () => {
30
+ const result = cleanModelCard(makeRaw() as any);
31
+ expect(result.parameters).toEqual([
32
+ { title: 'age', value: '25' },
33
+ { title: 'height_cm', value: '170' },
34
+ { title: 'weight_kg', value: '55' },
35
+ ]);
36
+ });
37
+
38
+ it('без age → параметры без age', () => {
39
+ const raw = makeRaw({
40
+ parameters: [
41
+ { name: 'birthplace_country', values: [{ title: 'USA', name: 'usa' }] },
42
+ { name: 'height_cm', values: [{ title: '170' }] },
43
+ ],
44
+ });
45
+ const result = cleanModelCard(raw as any);
46
+ expect(result.parameters).toEqual([
47
+ { title: 'height_cm', value: '170' },
48
+ ]);
49
+ });
50
+
51
+ it('пустые параметры', () => {
52
+ const raw = makeRaw({ parameters: [] });
53
+ const result = cleanModelCard(raw as any);
54
+ expect(result.country).toEqual({ title: '', name: '' });
55
+ expect(result.parameters).toEqual([]);
56
+ });
57
+ });
@@ -0,0 +1,45 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { cleanPlaylistCard } from './clean-playlist-card';
3
+
4
+ describe('cleanPlaylistCard', () => {
5
+ it('полные данные', () => {
6
+ const raw = {
7
+ created: 1700000000,
8
+ id: 'pl-1',
9
+ name: 'My Playlist',
10
+ username: 'user1',
11
+ playlist_type: 'public',
12
+ thumbs: [
13
+ { urls: { webp: { '320x180': 'https://img.com/s1.webp', '480x270': 'https://img.com/m1.webp' } } },
14
+ ],
15
+ videos_count: 5,
16
+ first_video_id: 'v-1',
17
+ search_tags: 'tag1,tag2',
18
+ views: 100,
19
+ likes: 10,
20
+ dislikes: 2,
21
+ };
22
+ const result = cleanPlaylistCard(raw as any);
23
+ expect(result.id).toBe('pl-1');
24
+ expect(result.name).toBe('My Playlist');
25
+ expect(result.playlistType).toBe('public');
26
+ expect(result.videosCount).toBe(5);
27
+ expect(result.thumbUrls).toHaveLength(1);
28
+ expect(result.thumbUrls![0]).toEqual({
29
+ webp: {
30
+ '320x180': 'https://img.com/s1.webp',
31
+ '480x270': 'https://img.com/m1.webp',
32
+ },
33
+ });
34
+ });
35
+
36
+ it('пустые данные → дефолты', () => {
37
+ const result = cleanPlaylistCard({} as any);
38
+ expect(result.id).toBe('');
39
+ expect(result.name).toBe('');
40
+ expect(result.videosCount).toBe(0);
41
+ expect(result.views).toBe(0);
42
+ expect(result.likes).toBe(0);
43
+ expect(result.dislikes).toBe(0);
44
+ });
45
+ });
@@ -0,0 +1,42 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { cleanPlaylistData } from './clean-playlist-data';
3
+
4
+ describe('cleanPlaylistData', () => {
5
+ it('полные данные', () => {
6
+ const raw = {
7
+ updated: 1700000000,
8
+ id: 'pl-1',
9
+ name: 'Playlist',
10
+ username: 'user1',
11
+ playlist_type: 'private',
12
+ videos_count: 10,
13
+ first_video_id: 'v-1',
14
+ search_tags: 'tags',
15
+ views: 200,
16
+ likes: 20,
17
+ dislikes: 3,
18
+ };
19
+ const result = cleanPlaylistData(raw as any);
20
+ expect(result).toEqual({
21
+ updated: 1700000000,
22
+ id: 'pl-1',
23
+ name: 'Playlist',
24
+ username: 'user1',
25
+ playlistType: 'private',
26
+ videosCount: 10,
27
+ firstVideoId: 'v-1',
28
+ searchTags: 'tags',
29
+ views: 200,
30
+ likes: 20,
31
+ dislikes: 3,
32
+ });
33
+ });
34
+
35
+ it('пустые данные → дефолты', () => {
36
+ const result = cleanPlaylistData({} as any);
37
+ expect(result.updated).toBe(0);
38
+ expect(result.id).toBe('');
39
+ expect(result.videosCount).toBe(0);
40
+ expect(result.views).toBe(0);
41
+ });
42
+ });
@@ -0,0 +1,39 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { cleanPlaylistVideo } from './clean-playlist-video';
3
+
4
+ describe('cleanPlaylistVideo', () => {
5
+ it('полные данные', () => {
6
+ const raw = {
7
+ guid: 'v-1',
8
+ duration: 300,
9
+ title: 'Video Title',
10
+ id: 'id-1',
11
+ thumb_urls: {
12
+ webp: {
13
+ '320x180': 'https://img.com/s.webp',
14
+ '480x270': 'https://img.com/m.webp',
15
+ },
16
+ },
17
+ md5: 'abc123',
18
+ };
19
+ const result = cleanPlaylistVideo(raw as any);
20
+ expect(result.guid).toBe('v-1');
21
+ expect(result.duration).toBe(300);
22
+ expect(result.title).toBe('Video Title');
23
+ expect(result.thumbUrls).toEqual({
24
+ webp: {
25
+ '320x180': 'https://img.com/s.webp',
26
+ '480x270': 'https://img.com/m.webp',
27
+ },
28
+ });
29
+ });
30
+
31
+ it('пустые данные → дефолты', () => {
32
+ const result = cleanPlaylistVideo({} as any);
33
+ expect(result.guid).toBe('');
34
+ expect(result.duration).toBe(0);
35
+ expect(result.title).toBe('');
36
+ expect(result.thumbUrls).toBeUndefined();
37
+ expect(result.md5).toBe('');
38
+ });
39
+ });
@@ -0,0 +1,30 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { cleanProfileData } from './clean-profile-data';
3
+
4
+ describe('cleanProfileData', () => {
5
+ it('полные данные', () => {
6
+ const raw = {
7
+ username: 'john',
8
+ email: 'john@test.com',
9
+ avatar: 'https://img.com/avatar.jpg',
10
+ country: 'US',
11
+ city: 'NYC',
12
+ birthday: 946684800,
13
+ gender: 'male',
14
+ created: 1700000000,
15
+ };
16
+ const result = cleanProfileData(raw as any);
17
+ expect(result).toEqual(raw);
18
+ });
19
+
20
+ it('пустые данные → дефолты', () => {
21
+ const result = cleanProfileData({} as any);
22
+ expect(result.username).toBe('');
23
+ expect(result.email).toBe('');
24
+ expect(result.avatar).toBe('');
25
+ expect(result.country).toBe('');
26
+ expect(result.birthday).toBe(0);
27
+ expect(result.gender).toBe('');
28
+ expect(result.created).toBe(0);
29
+ });
30
+ });
@@ -0,0 +1,40 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { cleanUserPlaylistCard } from './clean-user-playlists-card';
3
+
4
+ describe('cleanUserPlaylistCard', () => {
5
+ it('полные данные', () => {
6
+ const raw = {
7
+ created: 1700000000,
8
+ id: 'pl-1',
9
+ name: 'My Playlist',
10
+ username: 'user1',
11
+ playlistType: 'public',
12
+ thumbs: [
13
+ 'https://img.com/1.webp',
14
+ 'https://img.com/2.webp',
15
+ 'https://img.com/3.webp',
16
+ 'https://img.com/4.webp',
17
+ 'https://img.com/5.webp',
18
+ ],
19
+ videoCount: 10,
20
+ firstVideoID: 'v-1',
21
+ };
22
+ const result = cleanUserPlaylistCard(raw as any);
23
+ expect(result.id).toBe('pl-1');
24
+ expect(result.playlistType).toBe('public');
25
+ expect(result.videosCount).toBe(10);
26
+ expect(result.firstVideoId).toBe('v-1');
27
+ expect(result.thumbUrls).toHaveLength(4); // slice(0, 4)
28
+ expect(result.thumbUrls![0]).toEqual({
29
+ webp: { '320x180': 'https://img.com/1.webp' },
30
+ });
31
+ });
32
+
33
+ it('пустые данные → дефолты', () => {
34
+ const result = cleanUserPlaylistCard({} as any);
35
+ expect(result.id).toBe('');
36
+ expect(result.name).toBe('');
37
+ expect(result.videosCount).toBe(0);
38
+ expect(result.thumbUrls).toEqual([]);
39
+ });
40
+ });
@@ -0,0 +1,57 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { cleanVideoCard } from './clean-video-card';
3
+
4
+ describe('cleanVideoCard', () => {
5
+ it('полные данные', () => {
6
+ const raw = {
7
+ guid: 'v-1',
8
+ quality: '1080p',
9
+ duration: 600,
10
+ title: 'Video Title',
11
+ views: 1000,
12
+ id: 'id-1',
13
+ tags: 'tag1,tag2',
14
+ is_new: true,
15
+ channel: { avatar_url: 'https://img.com/ch.jpg', name: 'Channel' },
16
+ url: '/video/1',
17
+ preview_url: 'https://img.com/preview.mp4',
18
+ thumb_urls: {
19
+ webp: {
20
+ '320x180': 'https://img.com/s.webp',
21
+ '480x270': 'https://img.com/m.webp',
22
+ },
23
+ },
24
+ md5: 'abc',
25
+ thumb_number: 3,
26
+ models: [{ title: 'Model A' }, { title: 'Model B' }],
27
+ };
28
+ const result = cleanVideoCard(raw as any);
29
+ expect(result.guid).toBe('v-1');
30
+ expect(result.quality).toBe('1080p');
31
+ expect(result.isNew).toBe(true);
32
+ expect(result.channelName).toBe('Channel');
33
+ expect(result.channelAvatar).toBe('https://img.com/ch.jpg');
34
+ expect(result.models).toEqual(['Model A', 'Model B']);
35
+ expect(result.thumbNum).toBe(3);
36
+ expect(result.thumbUrls).toEqual({
37
+ webp: {
38
+ '320x180': 'https://img.com/s.webp',
39
+ '480x270': 'https://img.com/m.webp',
40
+ },
41
+ });
42
+ });
43
+
44
+ it('пустые данные → дефолты', () => {
45
+ const result = cleanVideoCard({} as any);
46
+ expect(result.guid).toBe('');
47
+ expect(result.quality).toBe('');
48
+ expect(result.duration).toBe(0);
49
+ expect(result.views).toBe(0);
50
+ expect(result.isNew).toBe(false);
51
+ expect(result.channelAvatar).toBe('');
52
+ expect(result.channelName).toBe('');
53
+ expect(result.thumbUrls).toBeUndefined();
54
+ expect(result.models).toEqual([]);
55
+ expect(result.thumbNum).toBe(0);
56
+ });
57
+ });
@@ -0,0 +1,57 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { cleanVideoData } from './clean-video-data';
3
+
4
+ describe('cleanVideoData', () => {
5
+ it('полные данные', () => {
6
+ const raw = {
7
+ guid: 'v-1',
8
+ md5: 'abc',
9
+ quality: '1080p',
10
+ duration: 600,
11
+ title: 'Title',
12
+ header: 'Header',
13
+ views: 1000,
14
+ likes: 100,
15
+ id: 'id-1',
16
+ tags: 'tag1',
17
+ models: 'model1',
18
+ categories: 'cat1',
19
+ actions: { liked: true },
20
+ channel: { name: 'Channel', avatar_url: 'https://img.com/ch.jpg' },
21
+ url: '/video/1',
22
+ preview_url: 'https://img.com/preview.mp4',
23
+ thumb_urls: { webp: { '320x180': 'small' } },
24
+ vtt_url: '/vtt',
25
+ vtt_sprite_url: '/sprite',
26
+ created: 1700000000,
27
+ video_url: '/stream',
28
+ is_deleted: false,
29
+ thumb_number: 5,
30
+ description: 'desc',
31
+ dislikes: 3,
32
+ };
33
+ const result = cleanVideoData(raw as any);
34
+ expect(result.guid).toBe('v-1');
35
+ expect(result.channelName).toBe('Channel');
36
+ expect(result.thumbUrls).toEqual({ webp: { '320x180': 'small' } });
37
+ expect(result.vttUrl).toBe('/vtt');
38
+ expect(result.videoUrl).toBe('/stream');
39
+ expect(result.isDeleted).toBe(false);
40
+ expect(result.thumbNum).toBe(5);
41
+ });
42
+
43
+ it('пустые данные → дефолты', () => {
44
+ const result = cleanVideoData({} as any);
45
+ expect(result.guid).toBe('');
46
+ expect(result.duration).toBe(0);
47
+ expect(result.views).toBe(0);
48
+ expect(result.likes).toBe(0);
49
+ expect(result.channelName).toBe('');
50
+ expect(result.channelAvatar).toBe('');
51
+ expect(result.thumbUrls).toBeUndefined();
52
+ expect(result.isDeleted).toBe(false);
53
+ expect(result.created).toBe(0);
54
+ expect(result.actions).toBe(null);
55
+ expect(result.dislikes).toBe(0);
56
+ });
57
+ });
@@ -0,0 +1,26 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { convertCategoriesToChips } from './convert-categories-to-chips';
3
+
4
+ describe('convertCategoriesToChips', () => {
5
+ it('конвертирует массив категорий в чипсы', () => {
6
+ const items = [
7
+ { title: 'Teen', name: 'teen' },
8
+ { title: 'MILF', name: 'milf' },
9
+ ];
10
+ const result = convertCategoriesToChips(items as any, 'categories');
11
+ expect(result).toEqual([
12
+ { title: 'Teen', value: 'teen', prefix: 'categories' },
13
+ { title: 'MILF', value: 'milf', prefix: 'categories' },
14
+ ]);
15
+ });
16
+
17
+ it('пустой массив', () => {
18
+ expect(convertCategoriesToChips([], 'categories')).toEqual([]);
19
+ });
20
+
21
+ it('использует переданный prefix', () => {
22
+ const items = [{ title: 'A', name: 'a' }];
23
+ const result = convertCategoriesToChips(items as any, 'models');
24
+ expect(result[0].prefix).toBe('models');
25
+ });
26
+ });
@@ -0,0 +1,20 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { convertCategoriesToFooter } from './convert-categories-to-footer';
3
+
4
+ describe('convertCategoriesToFooter', () => {
5
+ it('конвертирует категории в футер', () => {
6
+ const items = [
7
+ { title: 'Teen', name: 'teen' },
8
+ { title: 'MILF', name: 'milf' },
9
+ ];
10
+ const result = convertCategoriesToFooter(items as any);
11
+ expect(result).toEqual([
12
+ { title: 'Teen', name: 'teen' },
13
+ { title: 'MILF', name: 'milf' },
14
+ ]);
15
+ });
16
+
17
+ it('пустой массив', () => {
18
+ expect(convertCategoriesToFooter([])).toEqual([]);
19
+ });
20
+ });
@@ -0,0 +1,20 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { convertModelCardToChips } from './convert-model-card-to-chips';
3
+
4
+ describe('convertModelCardToChips', () => {
5
+ it('конвертирует карточки моделей в чипсы', () => {
6
+ const items = [
7
+ { title: 'Model A', video_thumb_urls: { webp: { '320x180': 'https://img.com/a.webp' } } },
8
+ { title: 'Model B', video_thumb_urls: { webp: { '320x180': 'https://img.com/b.webp' } } },
9
+ ];
10
+ const result = convertModelCardToChips(items as any);
11
+ expect(result).toEqual([
12
+ { title: 'Model A', value: 'Model A', icon: 'https://img.com/a.webp', prefix: 'models' },
13
+ { title: 'Model B', value: 'Model B', icon: 'https://img.com/b.webp', prefix: 'models' },
14
+ ]);
15
+ });
16
+
17
+ it('пустой массив', () => {
18
+ expect(convertModelCardToChips([])).toEqual([]);
19
+ });
20
+ });
@@ -0,0 +1,22 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { convertQueryCategories } from './convert-query-categories';
3
+
4
+ describe('convertQueryCategories', () => {
5
+ it('конвертирует query категории', () => {
6
+ const route = { query: { categories: 'cat_teen,cat_milf' } };
7
+ const result = convertQueryCategories(route as any);
8
+ expect(result).toBe('Teen, Milf');
9
+ });
10
+
11
+ it('ограничивает до 3 категорий', () => {
12
+ const route = { query: { categories: 'cat_a,cat_b,cat_c,cat_d' } };
13
+ const result = convertQueryCategories(route as any);
14
+ expect(result).toBe('A, B, C');
15
+ });
16
+
17
+ it('одна категория', () => {
18
+ const route = { query: { categories: 'cat_teen' } };
19
+ const result = convertQueryCategories(route as any);
20
+ expect(result).toBe('Teen');
21
+ });
22
+ });
@@ -0,0 +1,21 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { convertStringArrayToChips } from './convert-string-array-to-chips';
3
+
4
+ describe('convertStringArrayToChips', () => {
5
+ it('конвертирует массив строк в чипсы', () => {
6
+ const result = convertStringArrayToChips(['teen', 'milf'], 'tags', '/videos');
7
+ expect(result).toEqual([
8
+ { title: 'teen', value: 'teen', prefix: 'tags', resetPath: '/videos' },
9
+ { title: 'milf', value: 'milf', prefix: 'tags', resetPath: '/videos' },
10
+ ]);
11
+ });
12
+
13
+ it('# → post', () => {
14
+ const result = convertStringArrayToChips(['#', 'teen'], 'tags', '/');
15
+ expect(result[0]).toEqual({ title: 'post', value: 'post', prefix: 'tags', resetPath: '/' });
16
+ });
17
+
18
+ it('пустой массив', () => {
19
+ expect(convertStringArrayToChips([], 'tags', '/')).toEqual([]);
20
+ });
21
+ });
@@ -0,0 +1,46 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { groupCategoriesByFirstLetter } from './group-categories-by-first-letter';
3
+
4
+ const items = [
5
+ { title: 'Amateur', name: 'amateur', videosCount: 10, guid: '1', icon: '', is_top: false },
6
+ { title: 'Anal', name: 'anal', videosCount: 5, guid: '2', icon: '', is_top: true },
7
+ { title: 'Big Tits', name: 'big-tits', videosCount: 20, guid: '3', icon: '', is_top: false },
8
+ { title: '69', name: '69', videosCount: 8, guid: '4', icon: '', is_top: false },
9
+ ];
10
+
11
+ describe('groupCategoriesByFirstLetter', () => {
12
+ it('группирует по первой букве title', () => {
13
+ const result = groupCategoriesByFirstLetter(items as any, 'title', 'en');
14
+ expect(Object.keys(result)).toContain('A');
15
+ expect(Object.keys(result)).toContain('B');
16
+ expect(result['A']).toHaveLength(2);
17
+ expect(result['B']).toHaveLength(1);
18
+ });
19
+
20
+ it('цифры попадают в POST', () => {
21
+ const result = groupCategoriesByFirstLetter(items as any, 'title', 'en');
22
+ expect(result['POST']).toHaveLength(1);
23
+ expect(result['POST'][0].name).toBe('69');
24
+ });
25
+
26
+ it('кастомный nonLetterKey', () => {
27
+ const result = groupCategoriesByFirstLetter(items as any, 'title', 'en', 'OTHER');
28
+ expect(result['OTHER']).toHaveLength(1);
29
+ });
30
+
31
+ it('POST ключ идёт последним', () => {
32
+ const result = groupCategoriesByFirstLetter(items as any, 'title', 'en');
33
+ const keys = Object.keys(result);
34
+ expect(keys[keys.length - 1]).toBe('POST');
35
+ });
36
+
37
+ it('пустой массив', () => {
38
+ const result = groupCategoriesByFirstLetter([], 'title', 'en');
39
+ expect(result).toEqual({});
40
+ });
41
+
42
+ it('не массив → пустой результат', () => {
43
+ const result = groupCategoriesByFirstLetter(null as any, 'title', 'en');
44
+ expect(result).toEqual({});
45
+ });
46
+ });
@@ -0,0 +1,32 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { getMultipleQuery } from './get-multiple-query';
3
+
4
+ describe('getMultipleQuery', () => {
5
+ const route = { query: { duration: '10-30' } };
6
+ const items = [
7
+ { value: '0-10', key: 'duration', query: [0, 10] },
8
+ { value: '10-30', key: 'duration', query: [10, 30] },
9
+ { value: '30+', key: 'duration', query: 30 },
10
+ ];
11
+
12
+ it('массив query → min и max', () => {
13
+ const result = getMultipleQuery(route as any, items as any, 'min', 'max');
14
+ expect(result).toEqual({ min: 10, max: 30 });
15
+ });
16
+
17
+ it('последний элемент → только min', () => {
18
+ const r = { query: { duration: '30+' } };
19
+ const result = getMultipleQuery(r as any, items as any, 'min', 'max');
20
+ expect(result).toEqual({ min: 30 });
21
+ });
22
+
23
+ it('не массив и не последний → только max', () => {
24
+ const r = { query: { duration: '0-10' } };
25
+ const shortItems = [
26
+ { value: '0-10', key: 'duration', query: 'ten' },
27
+ { value: '30+', key: 'duration', query: 30 },
28
+ ];
29
+ const result = getMultipleQuery(r as any, shortItems as any, 'min', 'max');
30
+ expect(result).toEqual({ max: 'ten' });
31
+ });
32
+ });
@@ -0,0 +1,24 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { getSelectedQuery } from './get-selected-query';
3
+
4
+ describe('getSelectedQuery', () => {
5
+ it('находит query по значению из route', () => {
6
+ const route = { query: { sort: 'popular' } };
7
+ const items = [
8
+ { value: 'popular', key: 'sort', query: 'popular-query' },
9
+ { value: 'newest', key: 'sort', query: 'newest-query' },
10
+ ];
11
+ expect(getSelectedQuery(route as any, items as any)).toBe('popular-query');
12
+ });
13
+
14
+ it('не найдено → undefined', () => {
15
+ const route = { query: { sort: 'unknown' } };
16
+ const items = [{ value: 'popular', key: 'sort', query: 'q' }];
17
+ expect(getSelectedQuery(route as any, items as any)).toBeUndefined();
18
+ });
19
+
20
+ it('пустой items → undefined', () => {
21
+ const route = { query: { sort: 'popular' } };
22
+ expect(getSelectedQuery(route as any, [])).toBeUndefined();
23
+ });
24
+ });
@@ -0,0 +1,24 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { isMobileDevice } from './is-mobile-device';
3
+
4
+ describe('isMobileDevice', () => {
5
+ it('Android', () => {
6
+ expect(isMobileDevice('Mozilla/5.0 (Linux; Android 10)')).toBe(true);
7
+ });
8
+
9
+ it('iPhone', () => {
10
+ expect(isMobileDevice('Mozilla/5.0 (iPhone; CPU iPhone OS 14_0)')).toBe(true);
11
+ });
12
+
13
+ it('iPad', () => {
14
+ expect(isMobileDevice('Mozilla/5.0 (iPad; CPU OS 14_0)')).toBe(true);
15
+ });
16
+
17
+ it('desktop → false', () => {
18
+ expect(isMobileDevice('Mozilla/5.0 (Windows NT 10.0; Win64; x64)')).toBe(false);
19
+ });
20
+
21
+ it('пустая строка → false', () => {
22
+ expect(isMobileDevice('')).toBe(false);
23
+ });
24
+ });
@@ -0,0 +1,32 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { videoDataAddModelIcon } from './video-data-add-model-icon';
3
+
4
+ describe('videoDataAddModelIcon', () => {
5
+ it('добавляет icon к matching модели', () => {
6
+ const videoData = {
7
+ models: [
8
+ { guid: 'm-1', title: 'Model A' },
9
+ { guid: 'm-2', title: 'Model B' },
10
+ ],
11
+ };
12
+ const modelsData = [
13
+ { guid: 'm-1', primary_image_url: 'https://img.com/a.jpg' },
14
+ ];
15
+ const result = videoDataAddModelIcon(videoData as any, modelsData as any);
16
+ expect(result.models[0].icon).toBe('https://img.com/a.jpg');
17
+ expect(result.models[1].icon).toBeUndefined();
18
+ });
19
+
20
+ it('не мутирует исходный объект', () => {
21
+ const videoData = { models: [{ guid: 'm-1', title: 'A' }] };
22
+ const modelsData = [{ guid: 'm-1', primary_image_url: 'url' }];
23
+ const result = videoDataAddModelIcon(videoData as any, modelsData as any);
24
+ expect(result).not.toBe(videoData);
25
+ });
26
+
27
+ it('пустой массив моделей', () => {
28
+ const videoData = { models: [{ guid: 'm-1', title: 'A' }] };
29
+ const result = videoDataAddModelIcon(videoData as any, []);
30
+ expect(result.models[0].icon).toBeUndefined();
31
+ });
32
+ });