From ecce3943156f57944f69dee8e36152f2bc842d34 Mon Sep 17 00:00:00 2001 From: Matt Isenhower Date: Sun, 15 Feb 2026 15:41:00 -0800 Subject: [PATCH] Add unit tests for Pinia stores Co-Authored-By: Claude Opus 4.6 --- src/stores/gear.test.mjs | 81 ++++++++++++++ src/stores/schedules.test.mjs | 199 +++++++++++++++++++++++++++++++++ src/stores/splatfests.test.mjs | 151 +++++++++++++++++++++++++ src/stores/time.test.mjs | 88 +++++++++++++++ 4 files changed, 519 insertions(+) create mode 100644 src/stores/gear.test.mjs create mode 100644 src/stores/schedules.test.mjs create mode 100644 src/stores/splatfests.test.mjs create mode 100644 src/stores/time.test.mjs diff --git a/src/stores/gear.test.mjs b/src/stores/gear.test.mjs new file mode 100644 index 0000000..e47847f --- /dev/null +++ b/src/stores/gear.test.mjs @@ -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'); + }); + }); +}); diff --git a/src/stores/schedules.test.mjs b/src/stores/schedules.test.mjs new file mode 100644 index 0000000..8c996ef --- /dev/null +++ b/src/stores/schedules.test.mjs @@ -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'); + }); + }); +}); diff --git a/src/stores/splatfests.test.mjs b/src/stores/splatfests.test.mjs new file mode 100644 index 0000000..4d21180 --- /dev/null +++ b/src/stores/splatfests.test.mjs @@ -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'); + }); + }); +}); diff --git a/src/stores/time.test.mjs b/src/stores/time.test.mjs new file mode 100644 index 0000000..95eee8d --- /dev/null +++ b/src/stores/time.test.mjs @@ -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); + }); + }); +});