Fix module-level store init in files.js, apiError.js, cytoscapeMap.js and add files store tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,13 +1,10 @@
|
|||||||
import router from "@/router/index.ts";
|
import router from "@/router/index.ts";
|
||||||
import loadingStore from '@/stores/loading.js';
|
import loadingStore from '@/stores/loading.js';
|
||||||
import pinia from '@/stores/main.ts';
|
|
||||||
import {useToast} from 'vue-toast-notification';
|
import {useToast} from 'vue-toast-notification';
|
||||||
import 'vue-toast-notification/dist/theme-sugar.css';
|
import 'vue-toast-notification/dist/theme-sugar.css';
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { deleteCookie } from "@/utils/cookieUtil.js";
|
import { deleteCookie } from "@/utils/cookieUtil.js";
|
||||||
|
|
||||||
const loading = loadingStore(pinia);
|
|
||||||
const $toast = useToast();
|
|
||||||
// Delay loading and toast
|
// Delay loading and toast
|
||||||
const delay = (s = 0) => new Promise((resolve, reject) => setTimeout(resolve, s));
|
const delay = (s = 0) => new Promise((resolve, reject) => setTimeout(resolve, s));
|
||||||
|
|
||||||
@@ -23,6 +20,8 @@ export default async function apiError(error, toastMessage) {
|
|||||||
deleteCookie("luciaToken");
|
deleteCookie("luciaToken");
|
||||||
return router.push('/login');
|
return router.push('/login');
|
||||||
}
|
}
|
||||||
|
const loading = loadingStore();
|
||||||
|
const $toast = useToast();
|
||||||
await delay();
|
await delay();
|
||||||
loading.isLoading = true;
|
loading.isLoading = true;
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { getTimeLabel } from '@/module/timeLabel.js'; // 時間格式轉換器
|
|||||||
import CytoscapeStore from '@/stores/cytoscapeStore';
|
import CytoscapeStore from '@/stores/cytoscapeStore';
|
||||||
import { SAVE_KEY_NAME } from '@/constants/constants.js';
|
import { SAVE_KEY_NAME } from '@/constants/constants.js';
|
||||||
|
|
||||||
const mapPathStore = MapPathStore();
|
|
||||||
const composeFreqTypeText = (baseText, dataLayerOption, optionValue) => { //sonar-qube
|
const composeFreqTypeText = (baseText, dataLayerOption, optionValue) => { //sonar-qube
|
||||||
let text = baseText;
|
let text = baseText;
|
||||||
const textInt = dataLayerOption === 'rel_freq' ? baseText + optionValue * 100 + "%" : baseText + optionValue;
|
const textInt = dataLayerOption === 'rel_freq' ? baseText + optionValue * 100 + "%" : baseText + optionValue;
|
||||||
@@ -233,12 +232,12 @@ export default function cytoscapeMap(mapData, dataLayerType, dataLayerOption, cu
|
|||||||
|
|
||||||
// 按下節點光暈效果與鄰邊光暈效果
|
// 按下節點光暈效果與鄰邊光暈效果
|
||||||
cy.on('tap, mousedown', 'node', function (event) {
|
cy.on('tap, mousedown', 'node', function (event) {
|
||||||
mapPathStore.onNodeClickHighlightEdges(event.target);
|
MapPathStore().onNodeClickHighlightEdges(event.target);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 按下線段光暈效果與兩端點光暈效果
|
// 按下線段光暈效果與兩端點光暈效果
|
||||||
cy.on('tap, mousedown', 'edge', function (event) {
|
cy.on('tap, mousedown', 'edge', function (event) {
|
||||||
mapPathStore.onEdgeClickHighlightNodes(event.target);
|
MapPathStore().onEdgeClickHighlightNodes(event.target);
|
||||||
});
|
});
|
||||||
|
|
||||||
// creat tippy.js
|
// creat tippy.js
|
||||||
|
|||||||
@@ -4,11 +4,8 @@ import moment from 'moment';
|
|||||||
import apiError from '@/module/apiError.js';
|
import apiError from '@/module/apiError.js';
|
||||||
import Swal from 'sweetalert2';
|
import Swal from 'sweetalert2';
|
||||||
import { uploadFailedFirst, uploadFailedSecond, uploadloader, uploadSuccess, deleteSuccess } from '@/module/alertModal.js';
|
import { uploadFailedFirst, uploadFailedSecond, uploadloader, uploadSuccess, deleteSuccess } from '@/module/alertModal.js';
|
||||||
import pinia from '@/stores/main.ts';
|
|
||||||
import loadingStore from '@/stores/loading.js';
|
import loadingStore from '@/stores/loading.js';
|
||||||
|
|
||||||
const loading = loadingStore(pinia);
|
|
||||||
|
|
||||||
export default defineStore('filesStore', {
|
export default defineStore('filesStore', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
allEventFiles: [
|
allEventFiles: [
|
||||||
@@ -261,6 +258,7 @@ export default defineStore('filesStore', {
|
|||||||
if(id == null || isNaN(id)) {
|
if(id == null || isNaN(id)) {
|
||||||
return $toast.default('Delete File API Error.', {position: 'bottom'});
|
return $toast.default('Delete File API Error.', {position: 'bottom'});
|
||||||
};
|
};
|
||||||
|
const loading = loadingStore();
|
||||||
loading.isLoading = true;
|
loading.isLoading = true;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'log':
|
case 'log':
|
||||||
@@ -294,6 +292,7 @@ export default defineStore('filesStore', {
|
|||||||
async deletionRecord(id) {
|
async deletionRecord(id) {
|
||||||
let api = '';
|
let api = '';
|
||||||
|
|
||||||
|
const loading = loadingStore();
|
||||||
loading.isLoading = true;
|
loading.isLoading = true;
|
||||||
api = `/api/deletion/${id}`;
|
api = `/api/deletion/${id}`;
|
||||||
try {
|
try {
|
||||||
|
|||||||
201
tests/stores/files.test.js
Normal file
201
tests/stores/files.test.js
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||||
|
import { setActivePinia, createPinia } from 'pinia';
|
||||||
|
|
||||||
|
// Mock modules that have deep import chains (router, Swal, pinia, toast)
|
||||||
|
vi.mock('@/module/apiError.js', () => ({
|
||||||
|
default: vi.fn(),
|
||||||
|
}));
|
||||||
|
vi.mock('@/module/alertModal.js', () => ({
|
||||||
|
uploadFailedFirst: vi.fn(),
|
||||||
|
uploadFailedSecond: vi.fn(),
|
||||||
|
uploadloader: vi.fn(),
|
||||||
|
uploadSuccess: vi.fn(),
|
||||||
|
deleteSuccess: vi.fn(),
|
||||||
|
}));
|
||||||
|
vi.mock('sweetalert2', () => ({
|
||||||
|
default: { close: vi.fn(), fire: vi.fn() },
|
||||||
|
}));
|
||||||
|
// Prevent module-level store init in cytoscapeMap.js (loaded via router → Map.vue)
|
||||||
|
vi.mock('@/module/cytoscapeMap.js', () => ({}));
|
||||||
|
vi.mock('@/router/index.ts', () => ({
|
||||||
|
default: { push: vi.fn(), currentRoute: { value: { path: '/' } } },
|
||||||
|
}));
|
||||||
|
|
||||||
|
import axios from 'axios';
|
||||||
|
import useFilesStore from '@/stores/files.js';
|
||||||
|
|
||||||
|
vi.spyOn(axios, 'get').mockImplementation(vi.fn());
|
||||||
|
vi.spyOn(axios, 'post').mockImplementation(vi.fn());
|
||||||
|
vi.spyOn(axios, 'put').mockImplementation(vi.fn());
|
||||||
|
vi.spyOn(axios, 'delete').mockImplementation(vi.fn());
|
||||||
|
|
||||||
|
describe('filesStore', () => {
|
||||||
|
let store;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
setActivePinia(createPinia());
|
||||||
|
store = useFilesStore();
|
||||||
|
store.$router = { push: vi.fn() };
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has correct default state', () => {
|
||||||
|
expect(store.filesTag).toBe('ALL');
|
||||||
|
expect(store.httpStatus).toBe(200);
|
||||||
|
expect(store.uploadId).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('allFiles getter', () => {
|
||||||
|
it('filters files by current filesTag', () => {
|
||||||
|
store.allEventFiles = [
|
||||||
|
{ fileType: 'Log', name: 'a.xes' },
|
||||||
|
{ fileType: 'Filter', name: 'b' },
|
||||||
|
{ fileType: 'Design', name: 'c' },
|
||||||
|
];
|
||||||
|
|
||||||
|
store.filesTag = 'COMPARE';
|
||||||
|
expect(store.allFiles.map((f) => f.name)).toEqual(['a.xes', 'b']);
|
||||||
|
|
||||||
|
store.filesTag = 'ALL';
|
||||||
|
expect(store.allFiles).toHaveLength(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('fetchAllFiles', () => {
|
||||||
|
it('fetches and transforms file data', async () => {
|
||||||
|
axios.get.mockResolvedValue({
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
type: 'log',
|
||||||
|
name: 'test.xes',
|
||||||
|
owner: { name: 'Alice' },
|
||||||
|
updated_at: '2024-01-15T10:00:00Z',
|
||||||
|
accessed_at: '2024-01-15T11:00:00Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'filter',
|
||||||
|
name: 'filter1',
|
||||||
|
parent: { name: 'test.xes' },
|
||||||
|
owner: { name: 'Bob' },
|
||||||
|
updated_at: '2024-01-16T10:00:00Z',
|
||||||
|
accessed_at: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
await store.fetchAllFiles();
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith('/api/files');
|
||||||
|
expect(store.allEventFiles).toHaveLength(2);
|
||||||
|
expect(store.allEventFiles[0].fileType).toBe('Log');
|
||||||
|
expect(store.allEventFiles[0].icon).toBe('work_history');
|
||||||
|
expect(store.allEventFiles[0].ownerName).toBe('Alice');
|
||||||
|
expect(store.allEventFiles[1].fileType).toBe('Filter');
|
||||||
|
expect(store.allEventFiles[1].parentLog).toBe('test.xes');
|
||||||
|
expect(store.allEventFiles[1].accessed_at).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not throw on API failure', async () => {
|
||||||
|
axios.get.mockRejectedValue(new Error('Network error'));
|
||||||
|
await expect(store.fetchAllFiles()).resolves.toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('upload', () => {
|
||||||
|
it('uploads file and navigates to Upload page', async () => {
|
||||||
|
axios.post.mockResolvedValue({ data: { id: 42 } });
|
||||||
|
const formData = new FormData();
|
||||||
|
|
||||||
|
await store.upload(formData);
|
||||||
|
|
||||||
|
expect(axios.post).toHaveBeenCalledWith(
|
||||||
|
'/api/logs/csv-uploads',
|
||||||
|
formData,
|
||||||
|
expect.objectContaining({
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(store.uploadId).toBe(42);
|
||||||
|
expect(store.$router.push).toHaveBeenCalledWith({ name: 'Upload' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getUploadDetail', () => {
|
||||||
|
it('fetches upload preview', async () => {
|
||||||
|
store.uploadId = 10;
|
||||||
|
axios.get.mockResolvedValue({
|
||||||
|
data: { preview: { columns: ['a', 'b'] } },
|
||||||
|
});
|
||||||
|
|
||||||
|
await store.getUploadDetail();
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith('/api/logs/csv-uploads/10');
|
||||||
|
expect(store.allUploadDetail).toEqual({ columns: ['a', 'b'] });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('rename', () => {
|
||||||
|
it('renames a log file', async () => {
|
||||||
|
axios.put.mockResolvedValue({});
|
||||||
|
axios.get.mockResolvedValue({ data: [] });
|
||||||
|
|
||||||
|
await store.rename('log', 5, 'new-name');
|
||||||
|
|
||||||
|
expect(axios.put).toHaveBeenCalledWith('/api/logs/5/name', {
|
||||||
|
name: 'new-name',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getDependents', () => {
|
||||||
|
it('fetches dependents for a log', async () => {
|
||||||
|
axios.get.mockResolvedValue({ data: [{ id: 1 }, { id: 2 }] });
|
||||||
|
|
||||||
|
await store.getDependents('log', 7);
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith('/api/logs/7/dependents');
|
||||||
|
expect(store.allDependentsData).toEqual([{ id: 1 }, { id: 2 }]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('deleteFile', () => {
|
||||||
|
it('deletes a log file via axios.delete', async () => {
|
||||||
|
axios.get.mockResolvedValue({ data: [] });
|
||||||
|
axios.delete.mockResolvedValue({});
|
||||||
|
|
||||||
|
await store.deleteFile('log', 1);
|
||||||
|
|
||||||
|
expect(axios.delete).toHaveBeenCalledWith('/api/logs/1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('deletionRecord', () => {
|
||||||
|
it('deletes a deletion record', async () => {
|
||||||
|
axios.delete.mockResolvedValue({});
|
||||||
|
|
||||||
|
await store.deletionRecord(5);
|
||||||
|
|
||||||
|
expect(axios.delete).toHaveBeenCalledWith('/api/deletion/5');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('downloadFileCSV', () => {
|
||||||
|
it('downloads CSV for a log', async () => {
|
||||||
|
axios.get.mockResolvedValue({ data: 'col1,col2\na,b' });
|
||||||
|
|
||||||
|
window.URL.createObjectURL = vi.fn().mockReturnValue('blob:test');
|
||||||
|
window.URL.revokeObjectURL = vi.fn();
|
||||||
|
|
||||||
|
await store.downloadFileCSV('log', 3, 'my-file');
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith('/api/logs/3/csv');
|
||||||
|
expect(window.URL.revokeObjectURL).toHaveBeenCalledWith('blob:test');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns early for unsupported type', async () => {
|
||||||
|
await store.downloadFileCSV('log-check', 3, 'file');
|
||||||
|
|
||||||
|
expect(axios.get).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user