This commit is contained in:
pa
2026-03-04 00:32:43 +09:00
parent 2946b58f47
commit dd0293d2a6
4 changed files with 453 additions and 0 deletions

View File

@@ -0,0 +1,93 @@
import { describe, expect, it } from 'vitest';
import { ref } from 'vue';
import { useActivityDataFilter } from '../useActivityDataFilter';
function setup({
detailData = [],
isDetailVisible = true,
isSoloInstanceVisible = true,
isNoFriendInstanceVisible = true
} = {}) {
return useActivityDataFilter(
ref(detailData),
ref(isDetailVisible),
ref(isSoloInstanceVisible),
ref(isNoFriendInstanceVisible)
);
}
describe('useActivityDataFilter', () => {
it('returns empty array when isDetailVisible is false', () => {
const { filteredActivityDetailData } = setup({
detailData: [[{ isFriend: true }]],
isDetailVisible: false
});
expect(filteredActivityDetailData.value).toEqual([]);
});
it('returns all data when all filters are enabled', () => {
const data = [
[{ isFriend: true }],
[{ isFriend: false }],
[{ isFriend: true }, { isFriend: false }]
];
const { filteredActivityDetailData } = setup({ detailData: data });
expect(filteredActivityDetailData.value).toHaveLength(3);
});
it('filters solo instances when isSoloInstanceVisible is false', () => {
const data = [
[{ isFriend: true }], // solo — filtered
[{ isFriend: true }, { isFriend: false }] // not solo — kept
];
const { filteredActivityDetailData } = setup({
detailData: data,
isSoloInstanceVisible: false
});
expect(filteredActivityDetailData.value).toHaveLength(1);
expect(filteredActivityDetailData.value[0]).toHaveLength(2);
});
it('filters no-friend instances when isNoFriendInstanceVisible is false', () => {
const data = [
[{ isFriend: false }, { isFriend: false }], // no friends — filtered
[{ isFriend: true }, { isFriend: false }] // has friend — kept
];
const { filteredActivityDetailData } = setup({
detailData: data,
isNoFriendInstanceVisible: false
});
expect(filteredActivityDetailData.value).toHaveLength(1);
});
it('keeps solo instances even when isNoFriendInstanceVisible is false', () => {
const data = [
[{ isFriend: false }] // solo — special case, kept
];
const { filteredActivityDetailData } = setup({
detailData: data,
isNoFriendInstanceVisible: false
});
expect(filteredActivityDetailData.value).toHaveLength(1);
});
it('combines solo and no-friend filters', () => {
const data = [
[{ isFriend: false }], // solo — filtered by solo filter
[{ isFriend: false }, { isFriend: false }], // no friends — filtered
[{ isFriend: true }, { isFriend: false }] // kept
];
const { filteredActivityDetailData } = setup({
detailData: data,
isSoloInstanceVisible: false,
isNoFriendInstanceVisible: false
});
expect(filteredActivityDetailData.value).toHaveLength(1);
});
it('returns empty array for empty input data', () => {
const { filteredActivityDetailData } = setup({ detailData: [] });
expect(filteredActivityDetailData.value).toEqual([]);
});
});

View File

@@ -0,0 +1,40 @@
import { describe, expect, it } from 'vitest';
import { ref } from 'vue';
import { useActivityStats } from '../useActivityStats';
describe('useActivityStats', () => {
it('sums all time values from activityData', () => {
const data = ref([{ time: 1000 }, { time: 2000 }, { time: 3000 }]);
const { totalOnlineTime } = useActivityStats(data);
expect(totalOnlineTime.value).toBe(6000);
});
it('returns 0 for empty array', () => {
const data = ref([]);
const { totalOnlineTime } = useActivityStats(data);
expect(totalOnlineTime.value).toBe(0);
});
it('returns undefined when activityData is null', () => {
const data = ref(null);
const { totalOnlineTime } = useActivityStats(data);
expect(totalOnlineTime.value).toBeUndefined();
});
it('handles single item', () => {
const data = ref([{ time: 42 }]);
const { totalOnlineTime } = useActivityStats(data);
expect(totalOnlineTime.value).toBe(42);
});
it('reacts to changes in activityData', () => {
const data = ref([{ time: 100 }]);
const { totalOnlineTime } = useActivityStats(data);
expect(totalOnlineTime.value).toBe(100);
data.value = [{ time: 200 }, { time: 300 }];
expect(totalOnlineTime.value).toBe(500);
});
});

View File

@@ -0,0 +1,148 @@
import { describe, expect, it } from 'vitest';
import {
findMatchingDetailData,
formatWorldName,
generateYAxisLabel,
isDetailDataFiltered
} from '../useChartHelpers';
describe('isDetailDataFiltered', () => {
it('returns false when both filters are enabled', () => {
const detailData = [{ isFriend: true }, { isFriend: false }];
expect(isDetailDataFiltered(detailData, true, true)).toBe(false);
});
it('returns false when detailData is null/undefined', () => {
expect(isDetailDataFiltered(null, false, false)).toBe(false);
expect(isDetailDataFiltered(undefined, true, false)).toBe(false);
});
it('filters solo instance when isSoloInstanceVisible is false and only 1 entry', () => {
const detailData = [{ isFriend: false }];
expect(isDetailDataFiltered(detailData, false, true)).toBe(true);
});
it('does not filter solo when isSoloInstanceVisible is true', () => {
const detailData = [{ isFriend: false }];
expect(isDetailDataFiltered(detailData, true, true)).toBe(false);
});
it('filters no-friend instance when isNoFriendInstanceVisible is false', () => {
const detailData = [{ isFriend: false }, { isFriend: false }];
expect(isDetailDataFiltered(detailData, true, false)).toBe(true);
});
it('does not filter when at least one friend exists', () => {
const detailData = [{ isFriend: true }, { isFriend: false }];
expect(isDetailDataFiltered(detailData, true, false)).toBe(false);
});
});
describe('findMatchingDetailData', () => {
const currentUser = { id: 'user1' };
it('returns null when activityItem is null', () => {
expect(findMatchingDetailData(null, [], currentUser)).toBeNull();
});
it('returns null when currentUser is null', () => {
expect(
findMatchingDetailData({ location: 'loc1' }, [], null)
).toBeNull();
});
it('finds matching detail data by location and joinTime', () => {
const joinTime = { isSame: (other) => other === 100 };
const activityItem = { location: 'wrld_abc', joinTime: 100 };
const detailData = [
[
{ location: 'wrld_abc', user_id: 'user1', joinTime },
{
location: 'wrld_abc',
user_id: 'user2',
joinTime: { isSame: () => false }
}
],
[
{
location: 'wrld_xyz',
user_id: 'user1',
joinTime: { isSame: () => false }
}
]
];
const result = findMatchingDetailData(
activityItem,
detailData,
currentUser
);
expect(result).toBe(detailData[0]);
});
it('returns undefined when no match is found', () => {
const activityItem = { location: 'wrld_abc', joinTime: 100 };
const detailData = [
[
{
location: 'wrld_xyz',
user_id: 'user1',
joinTime: { isSame: () => false }
}
]
];
const result = findMatchingDetailData(
activityItem,
detailData,
currentUser
);
expect(result).toBeUndefined();
});
});
describe('generateYAxisLabel', () => {
it('returns filtered label format for filtered data', () => {
expect(generateYAxisLabel('TestWorld', true)).toBe(
'{filtered|TestWorld}'
);
});
it('returns normal label format for non-filtered data', () => {
expect(generateYAxisLabel('TestWorld', false)).toBe(
'{normal|TestWorld}'
);
});
it('truncates long world names', () => {
const longName = 'A'.repeat(30);
const result = generateYAxisLabel(longName, false);
expect(result).toBe(`{normal|${'A'.repeat(20)}...}`);
});
it('respects custom maxLength', () => {
const result = generateYAxisLabel('Hello World!', false, 5);
expect(result).toBe('{normal|Hello...}');
});
});
describe('formatWorldName', () => {
it('returns name as-is when within maxLength', () => {
expect(formatWorldName('Short')).toBe('Short');
});
it('truncates and adds ellipsis when name exceeds maxLength', () => {
const longName = 'A'.repeat(25);
expect(formatWorldName(longName)).toBe(`${'A'.repeat(20)}...`);
});
it('respects custom maxLength', () => {
expect(formatWorldName('Hello World', 5)).toBe('Hello...');
});
it('does not truncate at exact maxLength boundary', () => {
const exactName = 'A'.repeat(20);
expect(formatWorldName(exactName)).toBe(exactName);
});
});

View File

@@ -0,0 +1,172 @@
import { beforeAll, describe, expect, it, vi } from 'vitest';
import { ref } from 'vue';
import dayjs from 'dayjs';
import isSameOrAfter from 'dayjs/plugin/isSameOrAfter';
import timezone from 'dayjs/plugin/timezone';
import utc from 'dayjs/plugin/utc';
import { useDateNavigation } from '../useDateNavigation';
beforeAll(() => {
dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.extend(isSameOrAfter);
dayjs.tz.setDefault('UTC');
});
function makeDates(...strings) {
return ref(new Set(strings));
}
function setup(dateStrings, reloadData = vi.fn()) {
const allDates = makeDates(...dateStrings);
const result = useDateNavigation(allDates, reloadData);
return { ...result, reloadData };
}
describe('useDateNavigation', () => {
describe('changeSelectedDateFromBtn', () => {
it('navigates to previous date', () => {
const dates = ['2025-01-03', '2025-01-02', '2025-01-01'];
const { selectedDate, changeSelectedDateFromBtn, reloadData } =
setup(dates);
// Start at the latest date
selectedDate.value = dayjs('2025-01-03').toDate();
changeSelectedDateFromBtn(false); // go prev
expect(dayjs(selectedDate.value).format('YYYY-MM-DD')).toBe(
'2025-01-02'
);
expect(reloadData).toHaveBeenCalled();
});
it('navigates to next date', () => {
const dates = ['2025-01-03', '2025-01-02', '2025-01-01'];
const { selectedDate, changeSelectedDateFromBtn, reloadData } =
setup(dates);
selectedDate.value = dayjs('2025-01-02').toDate();
changeSelectedDateFromBtn(true); // go next
expect(dayjs(selectedDate.value).format('YYYY-MM-DD')).toBe(
'2025-01-03'
);
expect(reloadData).toHaveBeenCalled();
});
it('does nothing when allDateOfActivity is empty', () => {
const { selectedDate, changeSelectedDateFromBtn, reloadData } =
setup([]);
const original = selectedDate.value;
changeSelectedDateFromBtn(false);
expect(selectedDate.value).toBe(original);
expect(reloadData).not.toHaveBeenCalled();
});
it('finds nearest previous date when current date is not in array', () => {
const dates = ['2025-01-05', '2025-01-03', '2025-01-01'];
const { selectedDate, changeSelectedDateFromBtn, reloadData } =
setup(dates);
// Set to a date not in the array
selectedDate.value = dayjs('2025-01-04').toDate();
changeSelectedDateFromBtn(false);
// Should find 2025-01-03 as the closest previous date
expect(dayjs(selectedDate.value).format('YYYY-MM-DD')).toBe(
'2025-01-03'
);
expect(reloadData).toHaveBeenCalled();
});
it('falls back to last date when going prev at boundary', () => {
const dates = ['2025-01-03', '2025-01-01'];
const { selectedDate, changeSelectedDateFromBtn } = setup(dates);
selectedDate.value = dayjs('2025-01-01').toDate();
changeSelectedDateFromBtn(false);
// Should stay at or fallback to the last date
expect(dayjs(selectedDate.value).format('YYYY-MM-DD')).toBe(
'2025-01-01'
);
});
it('falls back to first date when going next at boundary', () => {
const dates = ['2025-01-03', '2025-01-01'];
const { selectedDate, changeSelectedDateFromBtn } = setup(dates);
selectedDate.value = dayjs('2025-01-03').toDate();
changeSelectedDateFromBtn(true);
// Already at the latest, should fallback to first
expect(dayjs(selectedDate.value).format('YYYY-MM-DD')).toBe(
'2025-01-03'
);
});
});
describe('isNextDayBtnDisabled', () => {
it('is true when selected date is the latest', () => {
const dates = ['2025-01-03', '2025-01-02', '2025-01-01'];
const { selectedDate, isNextDayBtnDisabled } = setup(dates);
selectedDate.value = dayjs('2025-01-03').toDate();
expect(isNextDayBtnDisabled.value).toBe(true);
});
it('is false when selected date is not the latest', () => {
const dates = ['2025-01-03', '2025-01-02', '2025-01-01'];
const { selectedDate, isNextDayBtnDisabled } = setup(dates);
selectedDate.value = dayjs('2025-01-02').toDate();
expect(isNextDayBtnDisabled.value).toBe(false);
});
});
describe('isPrevDayBtnDisabled', () => {
it('is true when selected date is the earliest', () => {
const dates = ['2025-01-03', '2025-01-02', '2025-01-01'];
const { selectedDate, isPrevDayBtnDisabled } = setup(dates);
selectedDate.value = dayjs('2025-01-01').toDate();
expect(isPrevDayBtnDisabled.value).toBe(true);
});
it('is false when selected date is not the earliest', () => {
const dates = ['2025-01-03', '2025-01-02', '2025-01-01'];
const { selectedDate, isPrevDayBtnDisabled } = setup(dates);
selectedDate.value = dayjs('2025-01-02').toDate();
expect(isPrevDayBtnDisabled.value).toBe(false);
});
});
describe('getDatePickerDisabledDate', () => {
it('disables future dates', () => {
const dates = ['2025-01-03', '2025-01-02', '2025-01-01'];
const { getDatePickerDisabledDate } = setup(dates);
const futureDate = new Date(Date.now() + 86400000);
expect(getDatePickerDisabledDate(futureDate)).toBe(true);
});
it('disables dates not in the activity set', () => {
const dates = ['2025-01-03', '2025-01-01'];
const { getDatePickerDisabledDate } = setup(dates);
// 2025-01-02 is not in the set
const missingDate = dayjs('2025-01-02').toDate();
expect(getDatePickerDisabledDate(missingDate)).toBe(true);
});
it('enables dates that are in the activity set', () => {
const dates = ['2025-01-03', '2025-01-02', '2025-01-01'];
const { getDatePickerDisabledDate } = setup(dates);
const validDate = dayjs('2025-01-02').toDate();
expect(getDatePickerDisabledDate(validDate)).toBe(false);
});
});
});