Fix getters mutating state on repeated access

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-07 07:31:59 +08:00
parent ef8ce0d778
commit eeea16be38
4 changed files with 77 additions and 35 deletions

View File

@@ -124,23 +124,22 @@ export const useAllMapDataStore = defineStore('allMapDataStore', {
},
filterAttrs: state => {
if(state.allFilterAttrs !== null){
state.allFilterAttrs.forEach(att => {
switch (att.type) {
return state.allFilterAttrs.map(att => {
const copy = { ...att };
switch (copy.type) {
case 'date':
att.min = att.min !== null ? moment(att.min).format('YYYY/MM/DD HH:mm') : null;
att.max = att.max !== null ? moment(att.max).format('YYYY/MM/DD HH:mm') : null;
copy.min = copy.min !== null ? moment(copy.min).format('YYYY/MM/DD HH:mm') : null;
copy.max = copy.max !== null ? moment(copy.max).format('YYYY/MM/DD HH:mm') : null;
break;
case 'float':
// Decimal.ROUND_UP|0: 無條件進位; Decimal.ROUND_DOWN|1: 無條件捨去。
att.min = att.min !== null ? Number(new Decimal(att.min).toFixed(2, 1)) : null;
att.max = att.max !== null ? Number(new Decimal(att.max).toFixed(2, 0)) : null;
copy.min = copy.min !== null ? Number(new Decimal(copy.min).toFixed(2, 1)) : null;
copy.max = copy.max !== null ? Number(new Decimal(copy.max).toFixed(2, 0)) : null;
break
default:
break;
}
return att;
return copy;
});
return state.allFilterAttrs;
}
},
allFunnels: state => {

View File

@@ -163,38 +163,42 @@ export const useConformanceStore = defineStore('conformanceStore', {
},
cases: state => {
if(state.allCases !== null){
const newData = state.allCases.map(c => {
c.started_at = moment(c.started_at).format('YYYY/MM/DD HH:mm');
c.completed_at = moment(c.completed_at).format('YYYY/MM/DD HH:mm');
c.facets.forEach(fac => {
switch(fac.type) {
return state.allCases.map(c => {
const facets = c.facets.map(fac => {
const copy = { ...fac };
switch(copy.type) {
case 'dummy': //sonar-qube
case 'duration-list':
fac.value = fac.value.map(v => v !== null ? abbreviateNumber(new Decimal(v.toFixed(2))) : null);
fac.value = (fac.value).map(v => v.trim()).join(', ');
copy.value = copy.value.map(v => v !== null ? abbreviateNumber(new Decimal(v.toFixed(2))) : null);
copy.value = (copy.value).map(v => v.trim()).join(', ');
break;
default:
break;
};
return fac;
return copy;
});
c.attributes.forEach(att => {
switch (att.type) {
const attributes = c.attributes.map(att => {
const copy = { ...att };
switch (copy.type) {
case 'date':
att.value = att.value !== null ? moment(att.value).format('YYYY/MM/DD HH:mm:ss') : null;
copy.value = copy.value !== null ? moment(copy.value).format('YYYY/MM/DD HH:mm:ss') : null;
break;
case 'float':
att.value = att.value !== null ? new Decimal(att.value).toFixed(2) : null;
copy.value = copy.value !== null ? new Decimal(copy.value).toFixed(2) : null;
break
default:
break;
}
return att;
return copy;
});
const { facets, attributes, ...rest } = c;
return { ...rest, facets, attributes };
return {
...c,
started_at: moment(c.started_at).format('YYYY/MM/DD HH:mm'),
completed_at: moment(c.completed_at).format('YYYY/MM/DD HH:mm'),
facets,
attributes,
};
});
return newData
};
},
loopTraces: state => {
@@ -205,25 +209,28 @@ export const useConformanceStore = defineStore('conformanceStore', {
},
loopCases: state => {
if(state.allLoopCases !== null){
const newData = state.allLoopCases.map(c => {
c.started_at = moment(c.started_at).format('YYYY/MM/DD HH:mm');
c.completed_at = moment(c.completed_at).format('YYYY/MM/DD HH:mm');
c.attributes.forEach(att => {
switch (att.type) {
return state.allLoopCases.map(c => {
const attributes = c.attributes.map(att => {
const copy = { ...att };
switch (copy.type) {
case 'date':
att.value = att.value !== null ? moment(att.value).format('YYYY/MM/DD HH:mm:ss') : null;
copy.value = copy.value !== null ? moment(copy.value).format('YYYY/MM/DD HH:mm:ss') : null;
break;
case 'float':
att.value = att.value !== null ? new Decimal(att.value).toFixed(2) : null;
copy.value = copy.value !== null ? new Decimal(copy.value).toFixed(2) : null;
break
default:
break;
}
return att;
return copy;
});
return c;
return {
...c,
started_at: moment(c.started_at).format('YYYY/MM/DD HH:mm'),
completed_at: moment(c.completed_at).format('YYYY/MM/DD HH:mm'),
attributes,
};
});
return newData;
};
},
},

View File

@@ -237,5 +237,28 @@ describe('allMapDataStore', () => {
store.allBaseCase = [{ id: 1 }];
expect(store.BaseInfiniteFirstCases).toBeUndefined();
});
it('filterAttrs getter does not corrupt original state on repeated access', () => {
const originalDate = '2023-06-15T10:30:00Z';
store.allFilterAttrs = [
{ type: 'date', min: originalDate, max: '2023-12-31T23:59:00Z' },
];
// Access the getter once
store.filterAttrs;
// The original state should still contain the ISO date, not a formatted string
expect(store.allFilterAttrs[0].min).toBe(originalDate);
});
it('filterAttrs getter returns consistent float values on repeated access', () => {
store.allFilterAttrs = [
{ type: 'float', min: 1.239, max: 5.671 },
];
const first = store.filterAttrs;
const second = store.filterAttrs;
// min rounds down (ROUND_DOWN=1), max rounds up (ROUND_UP=0)
// Both accesses should produce the same result
expect(first[0].min).toBe(second[0].min);
expect(first[0].max).toBe(second[0].max);
});
});
});

View File

@@ -196,5 +196,18 @@ describe('conformanceStore', () => {
// The date attribute should show minutes (30), not month (06)
expect(result[0].attributes[0].value).toMatch(/:30:/);
});
it('cases getter does not corrupt original state on repeated access', () => {
const originalDate = '2023-06-15T10:30:00Z';
store.allCases = [{
started_at: originalDate,
completed_at: '2023-06-15T11:45:00Z',
facets: [],
attributes: [],
}];
store.cases;
// Original state should still contain the ISO date
expect(store.allCases[0].started_at).toBe(originalDate);
});
});
});