itube-specs 0.0.766 → 0.0.768
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/composables/__tests__/use-format-time-ago.test.ts +39 -0
- package/composables/__tests__/use-model-filter-chips.test.ts +19 -5
- package/composables/use-format-time-ago.ts +13 -21
- package/composables/use-model-filter-chips.ts +3 -2
- package/lib/report-forms-scheme.ts +5 -5
- package/package.json +1 -1
- package/runtime/index.ts +0 -2
- package/runtime/utils/__tests__/get-month.test.ts +10 -8
- package/runtime/utils/get-month.ts +5 -20
- package/runtime/utils/__tests__/format-number.test.ts +0 -24
- package/runtime/utils/__tests__/format-time-ago.test.ts +0 -49
- package/runtime/utils/format-number.ts +0 -12
- package/runtime/utils/format-time-ago.ts +0 -26
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { useFormatTimeAgo } from '../use-format-time-ago';
|
|
3
|
+
|
|
4
|
+
describe('useFormatTimeAgo', () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
vi.useFakeTimers();
|
|
7
|
+
vi.setSystemTime(new Date('2025-06-15T12:00:00Z'));
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
vi.useRealTimers();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const now = () => Math.floor(Date.now() / 1000);
|
|
15
|
+
|
|
16
|
+
it('сегодня', () => {
|
|
17
|
+
expect(useFormatTimeAgo(now(), 'en')).toBe('today');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('вчера', () => {
|
|
21
|
+
expect(useFormatTimeAgo(now() - 86400, 'en')).toBe('yesterday');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('5 дней назад', () => {
|
|
25
|
+
expect(useFormatTimeAgo(now() - 5 * 86400, 'en')).toBe('5 days ago');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('3 месяца назад', () => {
|
|
29
|
+
expect(useFormatTimeAgo(now() - 90 * 86400, 'en')).toBe('3 months ago');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('2 года назад', () => {
|
|
33
|
+
expect(useFormatTimeAgo(now() - 730 * 86400, 'en')).toBe('2 years ago');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('локализация (ru)', () => {
|
|
37
|
+
expect(useFormatTimeAgo(now() - 5 * 86400, 'ru')).toBe('5 дней назад');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -8,14 +8,14 @@ describe('useFilterChipsItems', () => {
|
|
|
8
8
|
it('пустой массив без filter_ в query', () => {
|
|
9
9
|
const filters = ref([]);
|
|
10
10
|
const route = { query: {} };
|
|
11
|
-
const { chipsItems } = useFilterChipsItems(filters, route, t);
|
|
11
|
+
const { chipsItems } = useFilterChipsItems(filters, route, t, ref('en'));
|
|
12
12
|
expect(chipsItems.value).toEqual([]);
|
|
13
13
|
});
|
|
14
14
|
|
|
15
15
|
it('игнорирует не-filter параметры', () => {
|
|
16
16
|
const filters = ref([]);
|
|
17
17
|
const route = { query: { page: '1', sort: 'popular' } };
|
|
18
|
-
const { chipsItems } = useFilterChipsItems(filters, route, t);
|
|
18
|
+
const { chipsItems } = useFilterChipsItems(filters, route, t, ref('en'));
|
|
19
19
|
expect(chipsItems.value).toEqual([]);
|
|
20
20
|
});
|
|
21
21
|
|
|
@@ -29,7 +29,7 @@ describe('useFilterChipsItems', () => {
|
|
|
29
29
|
],
|
|
30
30
|
}]);
|
|
31
31
|
const route = { query: { filter_hair_color: 'blonde' } };
|
|
32
|
-
const { chipsItems } = useFilterChipsItems(filters, route, t);
|
|
32
|
+
const { chipsItems } = useFilterChipsItems(filters, route, t, ref('en'));
|
|
33
33
|
expect(chipsItems.value).toHaveLength(1);
|
|
34
34
|
expect(chipsItems.value[0].title).toBe('Hair color: Blonde');
|
|
35
35
|
expect(chipsItems.value[0].value).toEqual(['filter_hair_color']);
|
|
@@ -45,7 +45,7 @@ describe('useFilterChipsItems', () => {
|
|
|
45
45
|
],
|
|
46
46
|
}]);
|
|
47
47
|
const route = { query: { filter_age_from: '18', filter_age_to: '30' } };
|
|
48
|
-
const { chipsItems } = useFilterChipsItems(filters, route, t);
|
|
48
|
+
const { chipsItems } = useFilterChipsItems(filters, route, t, ref('en'));
|
|
49
49
|
expect(chipsItems.value).toHaveLength(1);
|
|
50
50
|
expect(chipsItems.value[0].title).toBe('Age: 18 - 30');
|
|
51
51
|
expect(chipsItems.value[0].value).toEqual(['filter_age_from', 'filter_age_to']);
|
|
@@ -61,7 +61,21 @@ describe('useFilterChipsItems', () => {
|
|
|
61
61
|
],
|
|
62
62
|
}]);
|
|
63
63
|
const route = { query: { filter_weight: '65' } };
|
|
64
|
-
const { chipsItems } = useFilterChipsItems(filters, route, t);
|
|
64
|
+
const { chipsItems } = useFilterChipsItems(filters, route, t, ref('en'));
|
|
65
65
|
expect(chipsItems.value[0].title).toBe('Weight: 65');
|
|
66
66
|
});
|
|
67
|
+
|
|
68
|
+
it('месяц → локализованное название', () => {
|
|
69
|
+
const filters = ref([{
|
|
70
|
+
name: 'birth_month',
|
|
71
|
+
title: 'Birth month',
|
|
72
|
+
options: [
|
|
73
|
+
{ name: '1', title: '1' },
|
|
74
|
+
{ name: '12', title: '12' },
|
|
75
|
+
],
|
|
76
|
+
}]);
|
|
77
|
+
const route = { query: { filter_birth_month: '6' } };
|
|
78
|
+
const { chipsItems } = useFilterChipsItems(filters, route, t, ref('en'));
|
|
79
|
+
expect(chipsItems.value[0].title).toBe('Birth month: June');
|
|
80
|
+
});
|
|
67
81
|
});
|
|
@@ -1,26 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Форматирует Unix timestamp (в секундах) в относительную
|
|
3
|
-
*
|
|
2
|
+
* Форматирует Unix timestamp (в секундах) в относительную строку в локали пользователя
|
|
3
|
+
* («сегодня», «вчера», «5 дней назад») через Intl.RelativeTimeFormat.
|
|
4
4
|
* @param time - Unix timestamp в секундах
|
|
5
|
+
* @param locale - активная локаль (код или BCP-47)
|
|
5
6
|
*/
|
|
6
|
-
export const useFormatTimeAgo = (
|
|
7
|
-
const
|
|
8
|
-
const
|
|
7
|
+
export const useFormatTimeAgo = (time: number, locale: string): string => {
|
|
8
|
+
const diffInDays = Math.floor((Date.now() - time * 1000) / 86_400_000);
|
|
9
|
+
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if (diffInDays
|
|
14
|
-
return
|
|
15
|
-
} else if (diffInDays === 1) {
|
|
16
|
-
return t('date.day_ago');
|
|
17
|
-
} else if (diffInDays < 30) {
|
|
18
|
-
return `${diffInDays} ${t('date.days_ago')}`;
|
|
19
|
-
} else if (diffInDays < 365) {
|
|
20
|
-
const diffInMonths = Math.floor(diffInDays / 30);
|
|
21
|
-
return `${diffInMonths} ${t('date.month')}${diffInMonths > 1 ? 's' : ''}`;
|
|
22
|
-
} else {
|
|
23
|
-
const diffInYears = Math.floor(diffInDays / 365);
|
|
24
|
-
return `${diffInYears} ${t('date.year')}${diffInYears > 1 ? 's' : ''}`;
|
|
11
|
+
if (diffInDays < 30) {
|
|
12
|
+
return rtf.format(-diffInDays, 'day');
|
|
13
|
+
}
|
|
14
|
+
if (diffInDays < 365) {
|
|
15
|
+
return rtf.format(-Math.floor(diffInDays / 30), 'month');
|
|
25
16
|
}
|
|
26
|
-
|
|
17
|
+
return rtf.format(-Math.floor(diffInDays / 365), 'year');
|
|
18
|
+
};
|
|
@@ -6,7 +6,8 @@ import type { Ref } from 'vue';
|
|
|
6
6
|
export function useFilterChipsItems(
|
|
7
7
|
filters: Ref<IModelFilter[]>,
|
|
8
8
|
route: { query: LocationQuery },
|
|
9
|
-
t: (key: string) => string
|
|
9
|
+
t: (key: string) => string,
|
|
10
|
+
locale: Ref<string>
|
|
10
11
|
) {
|
|
11
12
|
const chipsItems = computed<IChipsItem[]>(() => {
|
|
12
13
|
const queryItems = Object.keys(route.query)
|
|
@@ -60,7 +61,7 @@ export function useFilterChipsItems(
|
|
|
60
61
|
|
|
61
62
|
if (item.includes('month')) {
|
|
62
63
|
return getMonth(
|
|
63
|
-
|
|
64
|
+
locale.value,
|
|
64
65
|
Number(value ?? defaultValue?.name) - 1
|
|
65
66
|
);
|
|
66
67
|
}
|
|
@@ -64,7 +64,7 @@ export const reportFormsScheme: Array<IReportScheme> = [
|
|
|
64
64
|
marginClass: '_mb-12'
|
|
65
65
|
},
|
|
66
66
|
{
|
|
67
|
-
type: '
|
|
67
|
+
type: 'email',
|
|
68
68
|
value: 'from',
|
|
69
69
|
label: 'email',
|
|
70
70
|
hideLabel: true,
|
|
@@ -113,7 +113,7 @@ export const reportFormsScheme: Array<IReportScheme> = [
|
|
|
113
113
|
marginClass: '_mb-12 _mt-4'
|
|
114
114
|
},
|
|
115
115
|
{
|
|
116
|
-
type: '
|
|
116
|
+
type: 'email',
|
|
117
117
|
value: 'from',
|
|
118
118
|
label: 'email',
|
|
119
119
|
hideLabel: true,
|
|
@@ -153,7 +153,7 @@ export const reportFormsScheme: Array<IReportScheme> = [
|
|
|
153
153
|
marginClass: '_mb-12 _mt-4'
|
|
154
154
|
},
|
|
155
155
|
{
|
|
156
|
-
type: '
|
|
156
|
+
type: 'email',
|
|
157
157
|
value: 'from',
|
|
158
158
|
label: 'email',
|
|
159
159
|
hideLabel: true,
|
|
@@ -185,7 +185,7 @@ export const reportFormsScheme: Array<IReportScheme> = [
|
|
|
185
185
|
marginClass: '_mb-12'
|
|
186
186
|
})),
|
|
187
187
|
{
|
|
188
|
-
type: '
|
|
188
|
+
type: 'email',
|
|
189
189
|
value: 'from',
|
|
190
190
|
label: 'email',
|
|
191
191
|
hideLabel: true,
|
|
@@ -247,7 +247,7 @@ export const reportFormsScheme: Array<IReportScheme> = [
|
|
|
247
247
|
marginClass: '_mt-4 _mb-12'
|
|
248
248
|
},
|
|
249
249
|
{
|
|
250
|
-
type: '
|
|
250
|
+
type: 'email',
|
|
251
251
|
value: 'from',
|
|
252
252
|
label: 'email',
|
|
253
253
|
hideLabel: true,
|
package/package.json
CHANGED
package/runtime/index.ts
CHANGED
|
@@ -8,7 +8,6 @@ export * from './constants/report-forms-subjects';
|
|
|
8
8
|
export * from './constants/playlist-step';
|
|
9
9
|
export * from './constants/thumb-size';
|
|
10
10
|
export * from './utils/format-date';
|
|
11
|
-
export * from './utils/format-number';
|
|
12
11
|
export * from './utils/format-time';
|
|
13
12
|
export * from './utils/get-multiple-query';
|
|
14
13
|
export * from './utils/get-duration';
|
|
@@ -35,7 +34,6 @@ export * from './utils/cleaners/clean-video-data';
|
|
|
35
34
|
export * from './utils/cleaners/clean-video-card';
|
|
36
35
|
export * from './utils/cleaners/clean-model-info';
|
|
37
36
|
export * from './utils/video-data-add-model-icon';
|
|
38
|
-
export * from './utils/format-time-ago';
|
|
39
37
|
export * from './utils/converters/convert-categories-to-footer';
|
|
40
38
|
export * from './utils/converters/convert-categories-to-chips';
|
|
41
39
|
export * from './utils/converters/convert-model-card-to-chips';
|
|
@@ -1,30 +1,32 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
2
|
import { getMonth } from '../get-month';
|
|
3
3
|
|
|
4
|
-
const t = (key: string) => key;
|
|
5
|
-
|
|
6
4
|
describe('getMonth', () => {
|
|
7
5
|
it('январь = 0', () => {
|
|
8
|
-
expect(getMonth(
|
|
6
|
+
expect(getMonth('en', 0)).toBe('January');
|
|
9
7
|
});
|
|
10
8
|
|
|
11
9
|
it('декабрь = 11', () => {
|
|
12
|
-
expect(getMonth(
|
|
10
|
+
expect(getMonth('en', 11)).toBe('December');
|
|
13
11
|
});
|
|
14
12
|
|
|
15
13
|
it('июнь = 5', () => {
|
|
16
|
-
expect(getMonth(
|
|
14
|
+
expect(getMonth('en', 5)).toBe('June');
|
|
17
15
|
});
|
|
18
16
|
|
|
19
17
|
it('undefined → декабрь (дефолт)', () => {
|
|
20
|
-
expect(getMonth(
|
|
18
|
+
expect(getMonth('en')).toBe('December');
|
|
21
19
|
});
|
|
22
20
|
|
|
23
21
|
it('отрицательное число → декабрь', () => {
|
|
24
|
-
expect(getMonth(
|
|
22
|
+
expect(getMonth('en', -1)).toBe('December');
|
|
25
23
|
});
|
|
26
24
|
|
|
27
25
|
it('число >= 12 → декабрь', () => {
|
|
28
|
-
expect(getMonth(
|
|
26
|
+
expect(getMonth('en', 12)).toBe('December');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('локализация (ru)', () => {
|
|
30
|
+
expect(getMonth('ru', 5)).toBe('июнь');
|
|
29
31
|
});
|
|
30
32
|
});
|
|
@@ -1,24 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Возвращает локализованное название месяца по его номеру (0-based).
|
|
3
|
-
* @param
|
|
4
|
-
* @param number - номер месяца от 0 (январь) до 11 (декабрь)
|
|
2
|
+
* Возвращает локализованное название месяца по его номеру (0-based) через Intl.DateTimeFormat.
|
|
3
|
+
* @param locale - локаль (код или BCP-47)
|
|
4
|
+
* @param number - номер месяца от 0 (январь) до 11 (декабрь); вне диапазона → декабрь
|
|
5
5
|
*/
|
|
6
|
-
export function getMonth(
|
|
7
|
-
const months = [
|
|
8
|
-
'january',
|
|
9
|
-
'february',
|
|
10
|
-
'march',
|
|
11
|
-
'april',
|
|
12
|
-
'may',
|
|
13
|
-
'june',
|
|
14
|
-
'july',
|
|
15
|
-
'august',
|
|
16
|
-
'september',
|
|
17
|
-
'october',
|
|
18
|
-
'november',
|
|
19
|
-
'december'
|
|
20
|
-
];
|
|
6
|
+
export function getMonth(locale: string, number?: number): string {
|
|
21
7
|
const index = typeof number === 'number' && number >= 0 && number < 12 ? number : 11;
|
|
22
|
-
|
|
23
|
-
return t(key) || key;
|
|
8
|
+
return new Intl.DateTimeFormat(locale, { month: 'long' }).format(new Date(2000, index, 1));
|
|
24
9
|
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { formatNumber } from '../format-number';
|
|
3
|
-
|
|
4
|
-
describe('formatNumber', () => {
|
|
5
|
-
it('тысячи', () => {
|
|
6
|
-
expect(formatNumber(10400)).toBe('10.4K');
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
it('миллионы', () => {
|
|
10
|
-
expect(formatNumber(1500000)).toBe('1.5M');
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it('маленькое число без сокращения', () => {
|
|
14
|
-
expect(formatNumber(999)).toBe('999');
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('ноль', () => {
|
|
18
|
-
expect(formatNumber(0)).toBe('0');
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('ровно тысяча', () => {
|
|
22
|
-
expect(formatNumber(1000)).toBe('1K');
|
|
23
|
-
});
|
|
24
|
-
});
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { formatTimeAgo } from '../format-time-ago';
|
|
3
|
-
|
|
4
|
-
const t = (key: string) => key;
|
|
5
|
-
|
|
6
|
-
describe('formatTimeAgo', () => {
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
vi.useFakeTimers();
|
|
9
|
-
vi.setSystemTime(new Date('2025-06-15T12:00:00Z'));
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
afterEach(() => {
|
|
13
|
-
vi.useRealTimers();
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
const now = () => Math.floor(Date.now() / 1000);
|
|
17
|
-
|
|
18
|
-
it('сегодня', () => {
|
|
19
|
-
expect(formatTimeAgo(now(), t)).toBe('today');
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('1 день назад', () => {
|
|
23
|
-
expect(formatTimeAgo(now() - 86400, t)).toBe('day_ago');
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('5 дней назад', () => {
|
|
27
|
-
expect(formatTimeAgo(now() - 5 * 86400, t)).toBe('5 days_ago');
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('29 дней назад', () => {
|
|
31
|
-
expect(formatTimeAgo(now() - 29 * 86400, t)).toBe('29 days_ago');
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('1 месяц', () => {
|
|
35
|
-
expect(formatTimeAgo(now() - 31 * 86400, t)).toBe('1 month ago');
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('3 месяца — plural', () => {
|
|
39
|
-
expect(formatTimeAgo(now() - 90 * 86400, t)).toBe('3 months ago');
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('1 год', () => {
|
|
43
|
-
expect(formatTimeAgo(now() - 366 * 86400, t)).toBe('1 year ago');
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('2 года — plural', () => {
|
|
47
|
-
expect(formatTimeAgo(now() - 730 * 86400, t)).toBe('2 years ago');
|
|
48
|
-
});
|
|
49
|
-
});
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Форматирует число в короткий формат: 10400 → 10.4K.
|
|
3
|
-
* @param number - исходное число
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export function formatNumber (number: number) {
|
|
7
|
-
return number.toLocaleString('en-US', {
|
|
8
|
-
maximumFractionDigits: 2,
|
|
9
|
-
notation: 'compact',
|
|
10
|
-
compactDisplay: 'short'
|
|
11
|
-
});
|
|
12
|
-
};
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Форматирует Unix timestamp (в секундах) в относительную строку: «сегодня», «вчера», «3 дня назад» и т.д.
|
|
3
|
-
* @param date - Unix timestamp в секундах
|
|
4
|
-
* @param t - функция перевода (i18n)
|
|
5
|
-
*/
|
|
6
|
-
export function formatTimeAgo(date: number, t) {
|
|
7
|
-
const pastDate = new Date(date * 1000);
|
|
8
|
-
const now = new Date();
|
|
9
|
-
|
|
10
|
-
const diffInMs = Number(now) - Number(pastDate);
|
|
11
|
-
const diffInDays = Math.floor(diffInMs / (1000 * 60 * 60 * 24));
|
|
12
|
-
|
|
13
|
-
if (diffInDays === 0) {
|
|
14
|
-
return t('today');
|
|
15
|
-
} else if (diffInDays === 1) {
|
|
16
|
-
return t('day_ago');
|
|
17
|
-
} else if (diffInDays < 30) {
|
|
18
|
-
return `${diffInDays} ${t('days_ago')}`;
|
|
19
|
-
} else if (diffInDays < 365) {
|
|
20
|
-
const diffInMonths = Math.floor(diffInDays / 30);
|
|
21
|
-
return `${diffInMonths} ${t('month')}${diffInMonths > 1 ? 's' : ''} ${t('ago')}`;
|
|
22
|
-
} else {
|
|
23
|
-
const diffInYears = Math.floor(diffInDays / 365);
|
|
24
|
-
return `${diffInYears} ${t('year')}${diffInYears > 1 ? 's' : ''} ${t('ago')}`;
|
|
25
|
-
}
|
|
26
|
-
}
|