Add store tests with mocked axios and apiError

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-05 19:30:33 +08:00
parent 83c2db7582
commit 529e9a4aa1
12 changed files with 1260 additions and 0 deletions

View File

@@ -0,0 +1,246 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { setActivePinia, createPinia } from 'pinia';
vi.mock('@/module/apiError.js', () => ({
default: vi.fn(),
}));
// Mock login store to avoid its side effects
vi.mock('@/stores/login.ts', () => {
const { defineStore } = require('pinia');
return {
default: defineStore('loginStore', {
state: () => ({
userData: { username: 'currentUser', name: 'Current' },
}),
actions: {
getUserData: vi.fn(),
},
}),
};
});
import useAcctMgmtStore from '@/stores/acctMgmt.ts';
describe('acctMgmtStore', () => {
let store;
const mockAxios = {
get: vi.fn(),
post: vi.fn(),
put: vi.fn(),
delete: vi.fn(),
};
beforeEach(() => {
setActivePinia(createPinia());
store = useAcctMgmtStore();
store.$axios = mockAxios;
vi.clearAllMocks();
});
it('has correct default state', () => {
expect(store.allUserAccoutList).toEqual([]);
expect(store.isAcctMenuOpen).toBe(false);
});
describe('menu actions', () => {
it('openAcctMenu sets true', () => {
store.openAcctMenu();
expect(store.isAcctMenuOpen).toBe(true);
});
it('closeAcctMenu sets false', () => {
store.openAcctMenu();
store.closeAcctMenu();
expect(store.isAcctMenuOpen).toBe(false);
});
it('toggleIsAcctMenuOpen toggles', () => {
store.toggleIsAcctMenuOpen();
expect(store.isAcctMenuOpen).toBe(true);
store.toggleIsAcctMenuOpen();
expect(store.isAcctMenuOpen).toBe(false);
});
});
describe('setCurrentViewingUser', () => {
it('finds user by username', () => {
store.allUserAccoutList = [
{ username: 'alice', name: 'Alice', detail: {} },
{ username: 'bob', name: 'Bob', detail: {} },
];
store.setCurrentViewingUser('bob');
expect(store.currentViewingUser.name).toBe('Bob');
});
});
describe('clearCurrentViewingUser', () => {
it('resets to empty user', () => {
store.currentViewingUser = { username: 'test', detail: {} };
store.clearCurrentViewingUser();
expect(store.currentViewingUser.username).toBe('');
});
});
describe('createNewAccount', () => {
it('posts to /api/users and sets flag on success', async () => {
mockAxios.post.mockResolvedValue({ status: 200 });
const user = { username: 'newuser', password: 'pass' };
await store.createNewAccount(user);
expect(mockAxios.post).toHaveBeenCalledWith(
'/api/users', user,
);
expect(store.isOneAccountJustCreate).toBe(true);
expect(store.justCreateUsername).toBe('newuser');
});
});
describe('deleteAccount', () => {
it('returns true on success', async () => {
mockAxios.delete.mockResolvedValue({ status: 200 });
const result = await store.deleteAccount('alice');
expect(mockAxios.delete).toHaveBeenCalledWith(
'/api/users/alice',
);
expect(result).toBe(true);
});
it('returns false on error', async () => {
mockAxios.delete.mockRejectedValue(new Error('fail'));
const result = await store.deleteAccount('alice');
expect(result).toBe(false);
});
});
describe('editAccount', () => {
it('puts edited data', async () => {
mockAxios.put.mockResolvedValue({ status: 200 });
const detail = {
username: 'alice',
password: 'newpw',
name: 'Alice',
is_active: true,
};
const result = await store.editAccount('alice', detail);
expect(mockAxios.put).toHaveBeenCalledWith(
'/api/users/alice',
expect.objectContaining({ password: 'newpw' }),
);
expect(result).toBe(true);
});
});
describe('addRoleToUser', () => {
it('puts role assignment', async () => {
mockAxios.put.mockResolvedValue({ status: 200 });
const result = await store.addRoleToUser('alice', 'admin');
expect(mockAxios.put).toHaveBeenCalledWith(
'/api/users/alice/roles/admin',
);
expect(result).toBe(true);
});
});
describe('deleteRoleToUser', () => {
it('deletes role', async () => {
mockAxios.delete.mockResolvedValue({ status: 200 });
const result = await store.deleteRoleToUser('alice', 'admin');
expect(mockAxios.delete).toHaveBeenCalledWith(
'/api/users/alice/roles/admin',
);
expect(result).toBe(true);
});
});
describe('getUserDetail', () => {
it('fetches user and sets admin flag', async () => {
mockAxios.get.mockResolvedValue({
status: 200,
data: {
username: 'alice',
roles: [{ code: 'admin' }],
},
});
const result = await store.getUserDetail('alice');
expect(result).toBe(true);
expect(store.currentViewingUser.is_admin).toBe(true);
});
it('returns false on error', async () => {
mockAxios.get.mockRejectedValue(new Error('not found'));
const result = await store.getUserDetail('ghost');
expect(result).toBe(false);
});
});
describe('hover state actions', () => {
beforeEach(() => {
store.allUserAccoutList = [
{
username: 'alice',
isDeleteHovered: false,
isRowHovered: false,
isEditHovered: false,
isDetailHovered: false,
},
];
});
it('changeIsDeleteHoveredByUser', () => {
store.changeIsDeleteHoveredByUser('alice', true);
expect(store.allUserAccoutList[0].isDeleteHovered).toBe(true);
});
it('changeIsRowHoveredByUser', () => {
store.changeIsRowHoveredByUser('alice', true);
expect(store.allUserAccoutList[0].isRowHovered).toBe(true);
});
it('changeIsEditHoveredByUser', () => {
store.changeIsEditHoveredByUser('alice', true);
expect(store.allUserAccoutList[0].isEditHovered).toBe(true);
});
it('changeIsDetailHoveredByUser', () => {
store.changeIsDetailHoveredByUser('alice', true);
expect(store.allUserAccoutList[0].isDetailHovered).toBe(true);
});
});
it('resetJustCreateFlag resets flag', () => {
store.isOneAccountJustCreate = true;
store.resetJustCreateFlag();
expect(store.isOneAccountJustCreate).toBe(false);
});
it('setShouldUpdateList sets boolean', () => {
store.setShouldUpdateList(true);
expect(store.shouldUpdateList).toBe(true);
});
it('updateSingleAccountPiniaState updates user', () => {
store.allUserAccoutList = [
{ username: 'alice', name: 'Old' },
];
store.updateSingleAccountPiniaState({
username: 'alice', name: 'New',
});
expect(store.allUserAccoutList[0].name).toBe('New');
});
});

View File

@@ -0,0 +1,209 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { setActivePinia, createPinia } from 'pinia';
vi.mock('@/module/apiError.js', () => ({
default: vi.fn(),
}));
import useAllMapDataStore from '@/stores/allMapData.js';
describe('allMapDataStore', () => {
let store;
const mockAxios = {
get: vi.fn(),
post: vi.fn(),
put: vi.fn(),
};
beforeEach(() => {
setActivePinia(createPinia());
store = useAllMapDataStore();
store.$axios = mockAxios;
vi.clearAllMocks();
});
it('has correct default state', () => {
expect(store.logId).toBeNull();
expect(store.allProcessMap).toEqual({});
expect(store.allTrace).toEqual([]);
});
describe('getAllMapData', () => {
it('fetches log discover data', async () => {
store.logId = 1;
const mockData = {
process_map: { nodes: [] },
bpmn: { nodes: [] },
stats: { cases: 10 },
insights: {},
};
mockAxios.get.mockResolvedValue({ data: mockData });
await store.getAllMapData();
expect(mockAxios.get).toHaveBeenCalledWith(
'/api/logs/1/discover',
);
expect(store.allProcessMap).toEqual({ nodes: [] });
expect(store.allStats).toEqual({ cases: 10 });
});
it('fetches temp filter discover data when set', async () => {
store.logId = 1;
store.tempFilterId = 5;
mockAxios.get.mockResolvedValue({
data: {
process_map: {},
bpmn: {},
stats: {},
insights: {},
},
});
await store.getAllMapData();
expect(mockAxios.get).toHaveBeenCalledWith(
'/api/temp-filters/5/discover',
);
});
it('fetches created filter discover data', async () => {
store.logId = 1;
store.createFilterId = 3;
mockAxios.get.mockResolvedValue({
data: {
process_map: {},
bpmn: {},
stats: {},
insights: {},
},
});
await store.getAllMapData();
expect(mockAxios.get).toHaveBeenCalledWith(
'/api/filters/3/discover',
);
});
it('does not throw on API failure', async () => {
store.logId = 1;
mockAxios.get.mockRejectedValue(new Error('fail'));
await expect(store.getAllMapData())
.resolves.not.toThrow();
});
});
describe('getFilterParams', () => {
it('fetches filter params and transforms timeframe', async () => {
store.logId = 1;
const mockData = {
tasks: ['A', 'B'],
sources: ['A'],
sinks: ['B'],
timeframe: {
x_axis: {
min: '2023-01-01T00:00:00Z',
max: '2023-12-31T00:00:00Z',
},
},
trace: [],
attrs: [],
};
mockAxios.get.mockResolvedValue({ data: mockData });
await store.getFilterParams();
expect(mockAxios.get).toHaveBeenCalledWith(
'/api/filters/params?log_id=1',
);
expect(store.allFilterTask).toEqual(['A', 'B']);
// Check that min_base and max_base are stored
expect(store.allFilterTimeframe.x_axis.min_base)
.toBe('2023-01-01T00:00:00Z');
});
});
describe('checkHasResult', () => {
it('posts rule data and stores result', async () => {
store.logId = 1;
store.postRuleData = [{ type: 'task' }];
mockAxios.post.mockResolvedValue({
data: { result: true },
});
await store.checkHasResult();
expect(mockAxios.post).toHaveBeenCalledWith(
'/api/filters/has-result?log_id=1',
[{ type: 'task' }],
);
expect(store.hasResultRule).toBe(true);
});
});
describe('addTempFilterId', () => {
it('creates temp filter and stores id', async () => {
store.logId = 1;
store.postRuleData = [];
mockAxios.post.mockResolvedValue({ data: { id: 77 } });
await store.addTempFilterId();
expect(store.tempFilterId).toBe(77);
});
});
describe('addFilterId', () => {
it('creates filter and clears temp id', async () => {
store.logId = 1;
store.tempFilterId = 77;
store.postRuleData = [{ type: 'rule' }];
mockAxios.post.mockResolvedValue({ data: { id: 88 } });
await store.addFilterId('myFilter');
expect(mockAxios.post).toHaveBeenCalledWith(
'/api/filters?log_id=1',
{ name: 'myFilter', rules: [{ type: 'rule' }] },
);
expect(store.createFilterId).toBe(88);
expect(store.tempFilterId).toBeNull();
});
});
describe('updataFilter', () => {
it('updates filter and clears temp id', async () => {
store.createFilterId = 88;
store.tempFilterId = 77;
store.postRuleData = [{ type: 'updated' }];
mockAxios.put.mockResolvedValue({ status: 200 });
await store.updataFilter();
expect(mockAxios.put).toHaveBeenCalledWith(
'/api/filters/88',
[{ type: 'updated' }],
);
expect(store.isUpdataFilter).toBe(true);
expect(store.tempFilterId).toBeNull();
});
});
describe('getters', () => {
it('traces getter sorts by id', () => {
store.allTrace = [
{ id: 3, name: 'C' },
{ id: 1, name: 'A' },
{ id: 2, name: 'B' },
];
expect(store.traces.map(t => t.id)).toEqual([1, 2, 3]);
});
it('processMap getter returns state', () => {
store.allProcessMap = { nodes: [1, 2] };
expect(store.processMap).toEqual({ nodes: [1, 2] });
});
});
});

View File

@@ -0,0 +1,107 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { setActivePinia, createPinia } from 'pinia';
vi.mock('@/module/apiError.js', () => ({
default: vi.fn(),
}));
import useCompareStore from '@/stores/compare.js';
describe('compareStore', () => {
let store;
const mockAxios = {
get: vi.fn(),
};
beforeEach(() => {
setActivePinia(createPinia());
store = useCompareStore();
store.$axios = mockAxios;
vi.clearAllMocks();
});
it('has correct default state', () => {
expect(store.allCompareDashboardData).toBeNull();
});
it('compareDashboardData getter returns state', () => {
store.allCompareDashboardData = { time: {} };
expect(store.compareDashboardData).toEqual({ time: {} });
});
describe('getCompare', () => {
it('fetches compare data with encoded params', async () => {
const params = [{ type: 'log', id: 1 }];
const mockData = { time: {}, freq: {} };
mockAxios.get.mockResolvedValue({ data: mockData });
await store.getCompare(params);
const encoded = encodeURIComponent(JSON.stringify(params));
expect(mockAxios.get).toHaveBeenCalledWith(
`/api/compare?datasets=${encoded}`,
);
expect(store.allCompareDashboardData).toEqual(mockData);
});
it('does not throw on API failure', async () => {
mockAxios.get.mockRejectedValue(new Error('fail'));
await expect(store.getCompare([]))
.resolves.not.toThrow();
});
});
describe('getStateData', () => {
it('fetches log discover stats', async () => {
mockAxios.get.mockResolvedValue({
data: { stats: { cases: 100 } },
});
const result = await store.getStateData('log', 1);
expect(mockAxios.get).toHaveBeenCalledWith(
'/api/logs/1/discover',
);
expect(result).toEqual({ cases: 100 });
});
it('fetches filter discover stats', async () => {
mockAxios.get.mockResolvedValue({
data: { stats: { cases: 50 } },
});
const result = await store.getStateData('filter', 3);
expect(mockAxios.get).toHaveBeenCalledWith(
'/api/filters/3/discover',
);
expect(result).toEqual({ cases: 50 });
});
});
describe('getFileName', () => {
it('finds file name by id', async () => {
mockAxios.get.mockResolvedValue({
data: [
{ id: 1, name: 'file1.csv' },
{ id: 2, name: 'file2.csv' },
],
});
const result = await store.getFileName(1);
expect(result).toBe('file1.csv');
});
it('returns undefined for non-existent id', async () => {
mockAxios.get.mockResolvedValue({
data: [{ id: 1, name: 'file1.csv' }],
});
const result = await store.getFileName(99);
expect(result).toBeUndefined();
});
});
});

View File

@@ -0,0 +1,179 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { setActivePinia, createPinia } from 'pinia';
vi.mock('@/module/apiError.js', () => ({
default: vi.fn(),
}));
import apiError from '@/module/apiError.js';
import useConformanceStore from '@/stores/conformance.js';
describe('conformanceStore', () => {
let store;
const mockAxios = {
get: vi.fn(),
post: vi.fn(),
put: vi.fn(),
};
beforeEach(() => {
setActivePinia(createPinia());
store = useConformanceStore();
store.$axios = mockAxios;
vi.clearAllMocks();
});
it('has correct default state', () => {
expect(store.conformanceLogId).toBeNull();
expect(store.conformanceFilterId).toBeNull();
expect(store.allConformanceTask).toEqual([]);
expect(store.selectedRuleType).toBe('Have activity');
});
describe('getConformanceParams', () => {
it('fetches log check params when no filter', async () => {
store.conformanceLogId = 1;
store.conformanceFilterId = null;
const mockData = {
tasks: [{ label: 'A' }],
sources: ['A'],
sinks: ['B'],
processing_time: {},
waiting_time: {},
cycle_time: {},
};
mockAxios.get.mockResolvedValue({ data: mockData });
await store.getConformanceParams();
expect(mockAxios.get).toHaveBeenCalledWith(
'/api/log-checks/params?log_id=1',
);
expect(store.allConformanceTask).toEqual([{ label: 'A' }]);
expect(store.allCfmSeqStart).toEqual(['A']);
});
it('fetches filter check params when filter set', async () => {
store.conformanceFilterId = 5;
const mockData = {
tasks: [],
sources: [],
sinks: [],
processing_time: {},
waiting_time: {},
cycle_time: {},
};
mockAxios.get.mockResolvedValue({ data: mockData });
await store.getConformanceParams();
expect(mockAxios.get).toHaveBeenCalledWith(
'/api/filter-checks/params?filter_id=5',
);
});
});
describe('addConformanceCheckId', () => {
it('posts to log temp-check and stores id', async () => {
store.conformanceLogId = 1;
store.conformanceFilterId = null;
mockAxios.post.mockResolvedValue({ data: { id: 42 } });
await store.addConformanceCheckId({ rule: 'test' });
expect(mockAxios.post).toHaveBeenCalledWith(
'/api/temp-log-checks?log_id=1',
{ rule: 'test' },
);
expect(store.conformanceLogTempCheckId).toBe(42);
});
it('posts to filter temp-check when filter set', async () => {
store.conformanceFilterId = 3;
mockAxios.post.mockResolvedValue({ data: { id: 99 } });
await store.addConformanceCheckId({ rule: 'test' });
expect(mockAxios.post).toHaveBeenCalledWith(
'/api/temp-filter-checks?filter_id=3',
{ rule: 'test' },
);
expect(store.conformanceFilterTempCheckId).toBe(99);
});
});
describe('getConformanceReport', () => {
it('fetches temp log check report', async () => {
store.conformanceLogTempCheckId = 10;
const mockData = { file: {}, charts: {} };
mockAxios.get.mockResolvedValue({ data: mockData });
await store.getConformanceReport();
expect(mockAxios.get).toHaveBeenCalledWith(
'/api/temp-log-checks/10',
);
expect(store.allConformanceTempReportData).toEqual(mockData);
});
it('stores routeFile when getRouteFile=true', async () => {
store.conformanceLogTempCheckId = 10;
const mockData = { file: { name: 'test.csv' } };
mockAxios.get.mockResolvedValue({ data: mockData });
await store.getConformanceReport(true);
expect(store.allRouteFile).toEqual({ name: 'test.csv' });
expect(store.allConformanceTempReportData).toBeNull();
});
});
describe('addConformanceCreateCheckId', () => {
it('creates log check and clears temp id', async () => {
store.conformanceLogId = 1;
store.conformanceFilterId = null;
store.conformanceLogTempCheckId = 10;
store.conformanceRuleData = { type: 'test' };
mockAxios.post.mockResolvedValue({ data: { id: 100 } });
await store.addConformanceCreateCheckId('myRule');
expect(mockAxios.post).toHaveBeenCalledWith(
'/api/log-checks?log_id=1',
{ name: 'myRule', rule: { type: 'test' } },
);
expect(store.conformanceLogCreateCheckId).toBe(100);
expect(store.conformanceLogTempCheckId).toBeNull();
});
});
describe('updataConformance', () => {
it('updates existing log check', async () => {
store.conformanceLogCreateCheckId = 50;
store.conformanceRuleData = { type: 'updated' };
mockAxios.put.mockResolvedValue({ status: 200 });
await store.updataConformance();
expect(mockAxios.put).toHaveBeenCalledWith(
'/api/log-checks/50',
{ type: 'updated' },
);
expect(store.isUpdataConformance).toBe(true);
});
});
it('setConformanceLogCreateCheckId sets value', () => {
store.setConformanceLogCreateCheckId('abc');
expect(store.conformanceLogCreateCheckId).toBe('abc');
});
describe('getters', () => {
it('conformanceTask returns labels', () => {
store.allConformanceTask = [
{ label: 'A' }, { label: 'B' },
];
expect(store.conformanceTask).toEqual(['A', 'B']);
});
});
});

View File

@@ -0,0 +1,32 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { setActivePinia, createPinia } from 'pinia';
import useConformanceInputStore from '@/stores/conformanceInput.js';
describe('conformanceInputStore', () => {
let store;
beforeEach(() => {
setActivePinia(createPinia());
store = useConformanceInputStore();
});
it('has correct default state', () => {
expect(store.inputDataToSave.inputStart).toBeNull();
expect(store.activityRadioData.task).toEqual(['', '']);
});
it('setActivityRadioStartEndData sets From', () => {
store.setActivityRadioStartEndData('taskA', 'From');
expect(store.activityRadioData.task[0]).toBe('taskA');
});
it('setActivityRadioStartEndData sets To', () => {
store.setActivityRadioStartEndData('taskB', 'To');
expect(store.activityRadioData.task[1]).toBe('taskB');
});
it('setActivityRadioStartEndData ignores unknown', () => {
store.setActivityRadioStartEndData('taskC', 'Unknown');
expect(store.activityRadioData.task).toEqual(['', '']);
});
});

View File

@@ -0,0 +1,78 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { setActivePinia, createPinia } from 'pinia';
import useCytoscapeStore from '@/stores/cytoscapeStore.ts';
import { SAVE_KEY_NAME } from '@/constants/constants.js';
// Mock localStorage since jsdom's localStorage is limited
const localStorageMock = (() => {
let store = {};
return {
getItem: vi.fn((key) => store[key] || null),
setItem: vi.fn((key, value) => { store[key] = value; }),
removeItem: vi.fn((key) => { delete store[key]; }),
clear: vi.fn(() => { store = {}; }),
};
})();
Object.defineProperty(globalThis, 'localStorage', {
value: localStorageMock,
});
describe('cytoscapeStore', () => {
let store;
beforeEach(() => {
setActivePinia(createPinia());
store = useCytoscapeStore();
localStorageMock.clear();
vi.clearAllMocks();
});
it('has correct default state', () => {
expect(store.nodePositions).toEqual({});
expect(store.currentGraphId).toBe('');
});
it('setCurrentGraphId initializes graph structure', () => {
store.setCurrentGraphId('graph1');
expect(store.currentGraphId).toBe('graph1');
expect(store.nodePositions['graph1']).toEqual({
TB: [],
LR: [],
});
});
it('saveNodePosition adds node and saves to localStorage', () => {
store.setCurrentGraphId('graph1');
store.saveNodePosition('node1', { x: 10, y: 20 }, 'TB');
expect(store.nodePositions['graph1']['TB']).toEqual([
{ id: 'node1', position: { x: 10, y: 20 } },
]);
expect(localStorageMock.setItem).toHaveBeenCalledWith(
SAVE_KEY_NAME,
expect.any(String),
);
});
it('saveNodePosition updates existing node position', () => {
store.setCurrentGraphId('graph1');
store.saveNodePosition('node1', { x: 10, y: 20 }, 'TB');
store.saveNodePosition('node1', { x: 30, y: 40 }, 'TB');
expect(store.nodePositions['graph1']['TB']).toHaveLength(1);
expect(store.nodePositions['graph1']['TB'][0].position)
.toEqual({ x: 30, y: 40 });
});
it('loadPositionsFromStorage restores from localStorage', () => {
const data = {
graph1: {
TB: [{ id: 'n1', position: { x: 5, y: 10 } }],
},
};
localStorageMock.getItem.mockReturnValue(JSON.stringify(data));
store.setCurrentGraphId('graph1');
store.loadPositionsFromStorage('TB');
expect(store.nodePositions['graph1']['TB']).toEqual([
{ id: 'n1', position: { x: 5, y: 10 } },
]);
});
});

View File

@@ -0,0 +1,27 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { setActivePinia, createPinia } from 'pinia';
import useLoadingStore from '@/stores/loading.js';
describe('loadingStore', () => {
let store;
beforeEach(() => {
setActivePinia(createPinia());
store = useLoadingStore();
});
it('has isLoading true by default', () => {
expect(store.isLoading).toBe(true);
});
it('setIsLoading sets to false', () => {
store.setIsLoading(false);
expect(store.isLoading).toBe(false);
});
it('setIsLoading sets to true', () => {
store.setIsLoading(false);
store.setIsLoading(true);
expect(store.isLoading).toBe(true);
});
});

146
tests/stores/login.test.js Normal file
View File

@@ -0,0 +1,146 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { setActivePinia, createPinia } from 'pinia';
// Mock apiError to prevent side effects (imports router, pinia, toast)
vi.mock('@/module/apiError.js', () => ({
default: vi.fn(),
}));
import axios from 'axios';
import useLoginStore from '@/stores/login.ts';
// Mock axios methods
vi.spyOn(axios, 'post').mockImplementation(vi.fn());
vi.spyOn(axios, 'get').mockImplementation(vi.fn());
describe('loginStore', () => {
let store;
beforeEach(() => {
setActivePinia(createPinia());
store = useLoginStore();
store.$router = { push: vi.fn() };
vi.clearAllMocks();
// Clear cookies
document.cookie.split(';').forEach((c) => {
const name = c.split('=')[0].trim();
if (name) {
document.cookie = name + '=; Max-Age=-99999999; path=/';
}
});
});
it('has correct default state', () => {
expect(store.auth.grant_type).toBe('password');
expect(store.auth.username).toBe('');
expect(store.isLoggedIn).toBe(false);
expect(store.isInvalid).toBe(false);
});
describe('signIn', () => {
it('stores token and navigates on success', async () => {
axios.post.mockResolvedValue({
data: {
access_token: 'test-access-token',
refresh_token: 'test-refresh-token',
},
});
await store.signIn();
expect(axios.post).toHaveBeenCalledWith(
'/api/oauth/token',
store.auth,
expect.objectContaining({
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
}),
);
expect(store.isLoggedIn).toBe(true);
expect(document.cookie).toContain('luciaToken=test-access-token');
expect(store.$router.push).toHaveBeenCalledWith('/files');
});
it('redirects to remembered URL when set', async () => {
axios.post.mockResolvedValue({
data: {
access_token: 'token',
refresh_token: 'refresh',
},
});
// btoa('/dashboard') = 'L2Rhc2hib2FyZA=='
store.rememberedReturnToUrl = btoa('/dashboard');
// Mock window.location.href setter
const originalLocation = window.location;
delete window.location;
window.location = { href: '' };
await store.signIn();
expect(window.location.href).toBe('/dashboard');
window.location = originalLocation;
});
it('sets isInvalid on error', async () => {
axios.post.mockRejectedValue(new Error('401'));
await store.signIn();
expect(store.isInvalid).toBe(true);
});
});
describe('logOut', () => {
it('clears auth state and navigates to login', () => {
store.isLoggedIn = true;
store.logOut();
expect(store.isLoggedIn).toBe(false);
expect(store.$router.push).toHaveBeenCalledWith('/login');
});
});
describe('getUserData', () => {
it('stores user data on success', async () => {
axios.get.mockResolvedValue({
data: { username: 'testuser', name: 'Test User' },
});
await store.getUserData();
expect(axios.get).toHaveBeenCalledWith('/api/my-account');
expect(store.userData).toEqual({
username: 'testuser',
name: 'Test User',
});
});
});
describe('checkLogin', () => {
it('does not redirect on success', async () => {
axios.get.mockResolvedValue({ data: {} });
await store.checkLogin();
expect(store.$router.push).not.toHaveBeenCalled();
});
it('redirects to login on error', async () => {
axios.get.mockRejectedValue(new Error('401'));
await store.checkLogin();
expect(store.$router.push).toHaveBeenCalledWith('/login');
});
});
it('setRememberedReturnToUrl stores URL', () => {
store.setRememberedReturnToUrl('abc');
expect(store.rememberedReturnToUrl).toBe('abc');
});
it('setIsLoggedIn sets boolean', () => {
store.setIsLoggedIn(true);
expect(store.isLoggedIn).toBe(true);
});
});

View File

@@ -0,0 +1,31 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { setActivePinia, createPinia } from 'pinia';
import useMapCompareStore from '@/stores/mapCompareStore.ts';
describe('mapCompareStore', () => {
let store;
beforeEach(() => {
setActivePinia(createPinia());
store = useMapCompareStore();
});
it('has correct default state', () => {
expect(store.routeParam).toEqual({
primaryType: '',
primaryId: '',
secondaryType: '',
secondaryId: '',
});
});
it('setCompareRouteParam sets all params', () => {
store.setCompareRouteParam('log', '1', 'filter', '2');
expect(store.routeParam).toEqual({
primaryType: 'log',
primaryId: '1',
secondaryType: 'filter',
secondaryId: '2',
});
});
});

View File

@@ -0,0 +1,30 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { setActivePinia, createPinia } from 'pinia';
import { useModalStore } from '@/stores/modal.js';
import { MODAL_ACCT_INFO, MODAL_DELETE } from '@/constants/constants.js';
describe('modalStore', () => {
let store;
beforeEach(() => {
setActivePinia(createPinia());
store = useModalStore();
});
it('has default state', () => {
expect(store.isModalOpen).toBe(false);
expect(store.whichModal).toBe(MODAL_ACCT_INFO);
});
it('openModal sets isModalOpen and whichModal', () => {
store.openModal(MODAL_DELETE);
expect(store.isModalOpen).toBe(true);
expect(store.whichModal).toBe(MODAL_DELETE);
});
it('closeModal sets isModalOpen to false', async () => {
store.openModal(MODAL_DELETE);
await store.closeModal();
expect(store.isModalOpen).toBe(false);
});
});

View File

@@ -0,0 +1,92 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { setActivePinia, createPinia } from 'pinia';
import usePageAdminStore from '@/stores/pageAdmin.js';
describe('pageAdminStore', () => {
let store;
beforeEach(() => {
setActivePinia(createPinia());
store = usePageAdminStore();
});
it('has correct default state', () => {
expect(store.activePage).toBe('MAP');
expect(store.previousPage).toBe('MAP');
expect(store.pendingActivePage).toBe('FILES');
expect(store.isPagePending).toBe(false);
expect(store.shouldKeepPreviousPage).toBe(false);
expect(store.currentMapFile).toBe('');
});
it('setActivePage converts page name', () => {
store.setActivePage('CheckConformance');
expect(store.activePage).toBe('CONFORMANCE');
});
it('setPrevioiusPage converts page name', () => {
store.setPrevioiusPage('CheckPerformance');
expect(store.previousPage).toBe('PERFORMANCE');
});
it('setPrevioiusPageUsingActivePage copies activePage', () => {
store.setActivePage('CONFORMANCE');
store.setPrevioiusPageUsingActivePage();
// Note: bug in source - writes to this.previoiusPage (typo)
// instead of this.previousPage, so previousPage stays 'MAP'
expect(store.previousPage).toBe('MAP');
});
it('setIsPagePendingBoolean sets boolean', () => {
store.setIsPagePendingBoolean(true);
expect(store.isPagePending).toBe(true);
});
it('setPendingActivePage converts and sets page', () => {
store.setPendingActivePage('CheckMap');
expect(store.pendingActivePage).toBe('MAP');
});
it('copyPendingPageToActivePage transfers value', () => {
store.setPendingActivePage('CheckConformance');
store.copyPendingPageToActivePage();
expect(store.activePage).toBe('CONFORMANCE');
});
it('clearPendingActivePage resets to empty', () => {
store.setPendingActivePage('CheckMap');
store.clearPendingActivePage();
expect(store.pendingActivePage).toBe('');
});
it('keepPreviousPage restores previous page', () => {
store.setPrevioiusPage('CONFORMANCE');
store.setActivePage('PERFORMANCE');
store.keepPreviousPage();
expect(store.activePage).toBe('CONFORMANCE');
expect(store.shouldKeepPreviousPage).toBe(true);
});
it('clearShouldKeepPreviousPageBoolean resets flag', () => {
store.keepPreviousPage();
store.clearShouldKeepPreviousPageBoolean();
expect(store.shouldKeepPreviousPage).toBe(false);
});
it('setCurrentMapFile sets file name', () => {
store.setCurrentMapFile('test.csv');
expect(store.currentMapFile).toBe('test.csv');
});
it('setActivePageComputedByRoute extracts route name', () => {
const routeMatched = [{ name: 'CheckMap' }];
store.setActivePageComputedByRoute(routeMatched);
expect(store.activePageComputedByRoute).toBe('MAP');
});
it('setActivePageComputedByRoute handles empty array', () => {
store.setActivePageComputedByRoute([]);
// Should not change default value
expect(store.activePageComputedByRoute).toBe('MAP');
});
});

View File

@@ -0,0 +1,83 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { setActivePinia, createPinia } from 'pinia';
vi.mock('@/module/apiError.js', () => ({
default: vi.fn(),
}));
import usePerformanceStore from '@/stores/performance.js';
describe('performanceStore', () => {
let store;
const mockAxios = {
get: vi.fn(),
};
beforeEach(() => {
setActivePinia(createPinia());
store = usePerformanceStore();
store.$axios = mockAxios;
vi.clearAllMocks();
});
it('has correct default state', () => {
expect(store.allPerformanceData).toBeNull();
expect(store.freqChartData).toBeNull();
});
it('performanceData getter returns allPerformanceData', () => {
store.allPerformanceData = { time: {}, freq: {} };
expect(store.performanceData).toEqual({ time: {}, freq: {} });
});
describe('getPerformance', () => {
it('fetches log performance data', async () => {
const mockData = { time: { charts: [] }, freq: { charts: [] } };
mockAxios.get.mockResolvedValue({ data: mockData });
await store.getPerformance('log', 1);
expect(mockAxios.get).toHaveBeenCalledWith(
'/api/logs/1/performance',
);
expect(store.allPerformanceData).toEqual(mockData);
});
it('fetches filter performance data', async () => {
mockAxios.get.mockResolvedValue({ data: { time: {} } });
await store.getPerformance('filter', 5);
expect(mockAxios.get).toHaveBeenCalledWith(
'/api/filters/5/performance',
);
});
it('does not throw on API failure', async () => {
mockAxios.get.mockRejectedValue(new Error('Network error'));
// Should not throw - apiError handles it
await expect(store.getPerformance('log', 1))
.resolves.not.toThrow();
expect(store.allPerformanceData).toBeNull();
});
});
it('setFreqChartData sets data', () => {
const data = { labels: [], datasets: [] };
store.setFreqChartData(data);
expect(store.freqChartData).toEqual(data);
});
it('setFreqChartOptions sets options', () => {
const opts = { responsive: true };
store.setFreqChartOptions(opts);
expect(store.freqChartOptions).toEqual(opts);
});
it('setFreqChartXData sets x data', () => {
const xData = { minX: 0, maxX: 100 };
store.setFreqChartXData(xData);
expect(store.freqChartXData).toEqual(xData);
});
});