Add unit tests for Pinia stores

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Matt Isenhower
2026-02-15 15:41:00 -08:00
parent 38caca3e48
commit ecce394315
4 changed files with 519 additions and 0 deletions

81
src/stores/gear.test.mjs Normal file
View File

@@ -0,0 +1,81 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { createPinia, setActivePinia } from 'pinia';
import { useTimeStore } from './time.mjs';
import { useGearDataStore } from './data.mjs';
import { useGearStore } from './gear.mjs';
function setGearData(store, gesotown) {
// The data store has a `d => d.data` transform, so wrap accordingly
store.setData({ data: { gesotown } });
}
describe('useGearStore', () => {
let time;
let gearData;
let gear;
beforeEach(() => {
setActivePinia(createPinia());
time = useTimeStore();
gearData = useGearDataStore();
});
describe('dailyDropBrand', () => {
it('returns brand when saleEndTime is in the future', () => {
time.setNow(Date.parse('2024-06-15T12:00:00Z'));
setGearData(gearData, {
pickupBrand: { saleEndTime: '2024-06-16T00:00:00Z', brandGears: [] },
limitedGears: [],
});
gear = useGearStore();
expect(gear.dailyDropBrand).toBeDefined();
expect(gear.dailyDropBrand.saleEndTime).toBe('2024-06-16T00:00:00Z');
});
it('returns null when saleEndTime is in the past', () => {
time.setNow(Date.parse('2024-06-17T00:00:00Z'));
setGearData(gearData, {
pickupBrand: { saleEndTime: '2024-06-16T00:00:00Z', brandGears: [] },
limitedGears: [],
});
gear = useGearStore();
expect(gear.dailyDropBrand).toBeNull();
});
});
describe('dailyDropGear', () => {
it('filters to only current gear items', () => {
time.setNow(Date.parse('2024-06-15T12:00:00Z'));
setGearData(gearData, {
pickupBrand: {
saleEndTime: '2024-06-16T00:00:00Z',
brandGears: [
{ id: 1, saleEndTime: '2024-06-16T00:00:00Z' },
{ id: 2, saleEndTime: '2024-06-14T00:00:00Z' }, // expired
{ id: 3, saleEndTime: '2024-06-16T00:00:00Z' },
],
},
limitedGears: [],
});
gear = useGearStore();
expect(gear.dailyDropGear).toHaveLength(2);
expect(gear.dailyDropGear.map(g => g.id)).toEqual([1, 3]);
});
});
describe('regularGear', () => {
it('filters limitedGears by current time', () => {
time.setNow(Date.parse('2024-06-15T12:00:00Z'));
setGearData(gearData, {
pickupBrand: { saleEndTime: '2024-06-16T00:00:00Z', brandGears: [] },
limitedGears: [
{ id: 'a', saleEndTime: '2024-06-16T00:00:00Z' },
{ id: 'b', saleEndTime: '2024-06-14T00:00:00Z' }, // expired
],
});
gear = useGearStore();
expect(gear.regularGear).toHaveLength(1);
expect(gear.regularGear[0].id).toBe('a');
});
});
});

View File

@@ -0,0 +1,199 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { createPinia, setActivePinia } from 'pinia';
import { useTimeStore } from './time.mjs';
import { useSchedulesDataStore } from './data.mjs';
import { useSalmonRunSchedulesStore } from './schedules.mjs';
function makeCoopNode({ startTime, endTime, isBigRun = false, weapons = [] }) {
return {
startTime,
endTime,
setting: { weapons, vsStages: [] },
...(isBigRun ? {} : {}),
};
}
function setSchedulesData(store, { regularNodes = [], bigRunNodes = [] } = {}) {
// The data store has a `d => d.data` transform, so wrap accordingly
store.setData({ data: {
regularSchedules: { nodes: [] },
bankaraSchedules: { nodes: [] },
xSchedules: { nodes: [] },
festSchedules: { nodes: [] },
eventSchedules: { nodes: [] },
coopGroupingSchedule: {
regularSchedules: { nodes: regularNodes },
bigRunSchedules: { nodes: bigRunNodes },
teamContestSchedules: { nodes: [] },
},
vsStages: { nodes: [] },
} });
}
describe('useSalmonRunSchedulesStore', () => {
let time;
let schedulesData;
let salmonRun;
beforeEach(() => {
setActivePinia(createPinia());
time = useTimeStore();
schedulesData = useSchedulesDataStore();
});
describe('merge', () => {
it('combines regular and bigRun schedules sorted by startTime', () => {
setSchedulesData(schedulesData, {
regularNodes: [
makeCoopNode({ startTime: '2024-06-15T10:00:00Z', endTime: '2024-06-15T20:00:00Z' }),
],
bigRunNodes: [
makeCoopNode({ startTime: '2024-06-15T08:00:00Z', endTime: '2024-06-15T18:00:00Z' }),
],
});
time.setNow(0);
salmonRun = useSalmonRunSchedulesStore();
expect(salmonRun.schedules).toHaveLength(2);
// Should be sorted: bigRun first (08:00), then regular (10:00)
expect(salmonRun.schedules[0].startTime).toBe('2024-06-15T08:00:00Z');
expect(salmonRun.schedules[1].startTime).toBe('2024-06-15T10:00:00Z');
});
});
describe('isBigRun', () => {
it('is true for bigRun nodes', () => {
setSchedulesData(schedulesData, {
bigRunNodes: [
makeCoopNode({ startTime: '2024-06-15T08:00:00Z', endTime: '2024-06-15T18:00:00Z' }),
],
});
time.setNow(0);
salmonRun = useSalmonRunSchedulesStore();
expect(salmonRun.schedules[0].isBigRun).toBe(true);
});
it('is false for regular nodes', () => {
setSchedulesData(schedulesData, {
regularNodes: [
makeCoopNode({ startTime: '2024-06-15T08:00:00Z', endTime: '2024-06-15T18:00:00Z' }),
],
});
time.setNow(0);
salmonRun = useSalmonRunSchedulesStore();
expect(salmonRun.schedules[0].isBigRun).toBe(false);
});
});
describe('isMystery', () => {
it('is true when any weapon has name Random', () => {
setSchedulesData(schedulesData, {
regularNodes: [
makeCoopNode({
startTime: '2024-06-15T08:00:00Z',
endTime: '2024-06-15T18:00:00Z',
weapons: [
{ name: 'Splattershot', __splatoon3ink_id: 'abc' },
{ name: 'Random', __splatoon3ink_id: 'def' },
],
}),
],
});
time.setNow(0);
salmonRun = useSalmonRunSchedulesStore();
expect(salmonRun.schedules[0].isMystery).toBe(true);
});
it('is false when no weapon has name Random', () => {
setSchedulesData(schedulesData, {
regularNodes: [
makeCoopNode({
startTime: '2024-06-15T08:00:00Z',
endTime: '2024-06-15T18:00:00Z',
weapons: [
{ name: 'Splattershot', __splatoon3ink_id: 'abc' },
],
}),
],
});
time.setNow(0);
salmonRun = useSalmonRunSchedulesStore();
expect(salmonRun.schedules[0].isMystery).toBe(false);
});
});
describe('isGrizzcoMystery', () => {
it('is true for specific __splatoon3ink_id values', () => {
setSchedulesData(schedulesData, {
regularNodes: [
makeCoopNode({
startTime: '2024-06-15T08:00:00Z',
endTime: '2024-06-15T18:00:00Z',
weapons: [
{ name: 'Random', __splatoon3ink_id: '6e17fbe20efecca9' },
],
}),
],
});
time.setNow(0);
salmonRun = useSalmonRunSchedulesStore();
expect(salmonRun.schedules[0].isGrizzcoMystery).toBe(true);
});
it('is false for other __splatoon3ink_id values', () => {
setSchedulesData(schedulesData, {
regularNodes: [
makeCoopNode({
startTime: '2024-06-15T08:00:00Z',
endTime: '2024-06-15T18:00:00Z',
weapons: [
{ name: 'Random', __splatoon3ink_id: 'other-id' },
],
}),
],
});
time.setNow(0);
salmonRun = useSalmonRunSchedulesStore();
expect(salmonRun.schedules[0].isGrizzcoMystery).toBe(false);
});
});
describe('time filtering', () => {
it('currentSchedules filters by endTime', () => {
time.setNow(Date.parse('2024-06-15T12:00:00Z'));
setSchedulesData(schedulesData, {
regularNodes: [
makeCoopNode({ startTime: '2024-06-15T08:00:00Z', endTime: '2024-06-15T10:00:00Z' }), // ended
makeCoopNode({ startTime: '2024-06-15T10:00:00Z', endTime: '2024-06-15T20:00:00Z' }), // current
],
});
salmonRun = useSalmonRunSchedulesStore();
expect(salmonRun.currentSchedules).toHaveLength(1);
expect(salmonRun.currentSchedules[0].startTime).toBe('2024-06-15T10:00:00Z');
});
it('activeSchedule returns schedule where now is between start and end', () => {
time.setNow(Date.parse('2024-06-15T12:00:00Z'));
setSchedulesData(schedulesData, {
regularNodes: [
makeCoopNode({ startTime: '2024-06-15T10:00:00Z', endTime: '2024-06-15T20:00:00Z' }),
],
});
salmonRun = useSalmonRunSchedulesStore();
expect(salmonRun.activeSchedule).toBeDefined();
expect(salmonRun.activeSchedule.startTime).toBe('2024-06-15T10:00:00Z');
});
it('upcomingSchedules filters to future startTime', () => {
time.setNow(Date.parse('2024-06-15T12:00:00Z'));
setSchedulesData(schedulesData, {
regularNodes: [
makeCoopNode({ startTime: '2024-06-15T10:00:00Z', endTime: '2024-06-15T20:00:00Z' }), // active, not upcoming
makeCoopNode({ startTime: '2024-06-16T10:00:00Z', endTime: '2024-06-16T20:00:00Z' }), // upcoming
],
});
salmonRun = useSalmonRunSchedulesStore();
expect(salmonRun.upcomingSchedules).toHaveLength(1);
expect(salmonRun.upcomingSchedules[0].startTime).toBe('2024-06-16T10:00:00Z');
});
});
});

View File

@@ -0,0 +1,151 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { createPinia, setActivePinia } from 'pinia';
import { useTimeStore } from './time.mjs';
import { useFestivalsDataStore } from './data.mjs';
import { useUSSplatfestsStore, STATUS_ACTIVE, STATUS_UPCOMING, STATUS_PAST } from './splatfests.mjs';
function makeFestNode({ id = 'UJ-1', startTime, endTime, teams = [], results = false }) {
return {
__splatoon3ink_id: id,
startTime,
endTime,
teams: teams.length ? teams : [
{ result: results ? {} : null },
{ result: results ? {} : null },
{ result: results ? {} : null },
],
};
}
function setFestivalsData(store, nodes) {
store.setData({
US: { data: { festRecords: { nodes } } },
});
}
describe('splatfests store', () => {
let time;
let festivalsData;
let splatfests;
beforeEach(() => {
setActivePinia(createPinia());
time = useTimeStore();
festivalsData = useFestivalsDataStore();
});
describe('status detection', () => {
it('marks festival as active when between start and end', () => {
time.setNow(Date.parse('2024-06-15T12:00:00Z'));
setFestivalsData(festivalsData, [
makeFestNode({ startTime: '2024-06-15T00:00:00Z', endTime: '2024-06-16T00:00:00Z' }),
]);
splatfests = useUSSplatfestsStore();
expect(splatfests.festivals[0].status).toBe(STATUS_ACTIVE);
});
it('marks festival as upcoming when before start', () => {
time.setNow(Date.parse('2024-06-14T00:00:00Z'));
setFestivalsData(festivalsData, [
makeFestNode({ startTime: '2024-06-15T00:00:00Z', endTime: '2024-06-16T00:00:00Z' }),
]);
splatfests = useUSSplatfestsStore();
expect(splatfests.festivals[0].status).toBe(STATUS_UPCOMING);
});
it('marks festival as past when after end', () => {
time.setNow(Date.parse('2024-06-17T00:00:00Z'));
setFestivalsData(festivalsData, [
makeFestNode({ startTime: '2024-06-15T00:00:00Z', endTime: '2024-06-16T00:00:00Z' }),
]);
splatfests = useUSSplatfestsStore();
expect(splatfests.festivals[0].status).toBe(STATUS_PAST);
});
});
describe('region parsing', () => {
it('parses U as NA', () => {
time.setNow(0);
setFestivalsData(festivalsData, [
makeFestNode({ id: 'U-1', startTime: '2020-01-01T00:00:00Z', endTime: '2020-01-02T00:00:00Z' }),
]);
splatfests = useUSSplatfestsStore();
expect(splatfests.festivals[0].regions).toEqual(['NA']);
});
it('parses multi-region UJEA correctly', () => {
time.setNow(0);
setFestivalsData(festivalsData, [
makeFestNode({ id: 'UJEA-1', startTime: '2020-01-01T00:00:00Z', endTime: '2020-01-02T00:00:00Z' }),
]);
splatfests = useUSSplatfestsStore();
expect(splatfests.festivals[0].regions).toEqual(['NA', 'JP', 'EU', 'AP']);
});
it('parses J as JP', () => {
time.setNow(0);
setFestivalsData(festivalsData, [
makeFestNode({ id: 'J-1', startTime: '2020-01-01T00:00:00Z', endTime: '2020-01-02T00:00:00Z' }),
]);
splatfests = useUSSplatfestsStore();
expect(splatfests.festivals[0].regions).toEqual(['JP']);
});
it('parses E as EU', () => {
time.setNow(0);
setFestivalsData(festivalsData, [
makeFestNode({ id: 'E-1', startTime: '2020-01-01T00:00:00Z', endTime: '2020-01-02T00:00:00Z' }),
]);
splatfests = useUSSplatfestsStore();
expect(splatfests.festivals[0].regions).toEqual(['EU']);
});
it('parses A as AP', () => {
time.setNow(0);
setFestivalsData(festivalsData, [
makeFestNode({ id: 'A-1', startTime: '2020-01-01T00:00:00Z', endTime: '2020-01-02T00:00:00Z' }),
]);
splatfests = useUSSplatfestsStore();
expect(splatfests.festivals[0].regions).toEqual(['AP']);
});
});
describe('recentFestival', () => {
it('returns past fest within 3 days', () => {
const endTime = new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(); // 1 day ago
const startTime = new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString();
time.setNow(Date.now());
setFestivalsData(festivalsData, [
makeFestNode({ startTime, endTime }),
]);
splatfests = useUSSplatfestsStore();
expect(splatfests.recentFestival).not.toBeNull();
});
it('returns null for fest ended more than 3 days ago', () => {
const endTime = new Date(Date.now() - 4 * 24 * 60 * 60 * 1000).toISOString();
const startTime = new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString();
time.setNow(Date.now());
setFestivalsData(festivalsData, [
makeFestNode({ startTime, endTime }),
]);
splatfests = useUSSplatfestsStore();
expect(splatfests.recentFestival).toBeUndefined();
});
});
describe('activeFestival / upcomingFestival', () => {
it('returns correct fest based on time', () => {
time.setNow(Date.parse('2024-06-15T12:00:00Z'));
setFestivalsData(festivalsData, [
makeFestNode({ id: 'UJ-1', startTime: '2024-06-15T00:00:00Z', endTime: '2024-06-16T00:00:00Z' }),
makeFestNode({ id: 'UJ-2', startTime: '2024-07-15T00:00:00Z', endTime: '2024-07-16T00:00:00Z' }),
]);
splatfests = useUSSplatfestsStore();
expect(splatfests.activeFestival).toBeDefined();
expect(splatfests.activeFestival.__splatoon3ink_id).toBe('UJ-1');
expect(splatfests.upcomingFestival).toBeDefined();
expect(splatfests.upcomingFestival.__splatoon3ink_id).toBe('UJ-2');
});
});
});

88
src/stores/time.test.mjs Normal file
View File

@@ -0,0 +1,88 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { createPinia, setActivePinia } from 'pinia';
import { useTimeStore } from './time.mjs';
describe('useTimeStore', () => {
let time;
beforeEach(() => {
setActivePinia(createPinia());
time = useTimeStore();
});
describe('isCurrent', () => {
it('returns true when endTime is in the future', () => {
time.setNow(1000);
expect(time.isCurrent('2099-01-01T00:00:00Z')).toBe(true);
});
it('returns false when endTime is in the past', () => {
time.setNow(Date.parse('2099-01-01T00:00:00Z') + 1);
expect(time.isCurrent('2099-01-01T00:00:00Z')).toBe(false);
});
it('returns false when endTime is null', () => {
expect(time.isCurrent(null)).toBe(false);
});
});
describe('isActive', () => {
it('returns true when now is between start and end', () => {
time.setNow(Date.parse('2024-06-15T12:00:00Z'));
expect(time.isActive('2024-06-15T00:00:00Z', '2024-06-16T00:00:00Z')).toBe(true);
});
it('returns false when now is before start', () => {
time.setNow(Date.parse('2024-06-14T00:00:00Z'));
expect(time.isActive('2024-06-15T00:00:00Z', '2024-06-16T00:00:00Z')).toBe(false);
});
it('returns false when now is after end', () => {
time.setNow(Date.parse('2024-06-17T00:00:00Z'));
expect(time.isActive('2024-06-15T00:00:00Z', '2024-06-16T00:00:00Z')).toBe(false);
});
it('returns false when startTime is null', () => {
time.setNow(1000);
expect(time.isActive(null, '2099-01-01T00:00:00Z')).toBe(false);
});
it('returns false when endTime is null', () => {
time.setNow(1000);
expect(time.isActive('2000-01-01T00:00:00Z', null)).toBe(false);
});
});
describe('isUpcoming', () => {
it('returns true when startTime is in the future', () => {
time.setNow(1000);
expect(time.isUpcoming('2099-01-01T00:00:00Z')).toBe(true);
});
it('returns false when startTime is in the past', () => {
time.setNow(Date.parse('2099-01-01T00:00:00Z') + 1);
expect(time.isUpcoming('2099-01-01T00:00:00Z')).toBe(false);
});
it('returns false when startTime is null', () => {
expect(time.isUpcoming(null)).toBe(false);
});
});
describe('setNow', () => {
it('sets the now value directly', () => {
time.setNow(42000);
expect(time.now).toBe(42000);
});
});
describe('setOffset', () => {
it('adjusts now by offset amount', () => {
const before = time.now;
time.setOffset(60000);
expect(time.offset).toBe(60000);
// now should be approximately before + 60000 (within a second tolerance)
expect(time.now).toBeGreaterThanOrEqual(before + 59000);
});
});
});