refactor: replace teams merge mutation with full replace

This commit is contained in:
Shivam Mishra
2026-06-10 12:54:42 +05:30
parent 880c5e8bf4
commit 343b25842a
8 changed files with 18 additions and 39 deletions
@@ -6,9 +6,8 @@
//
// `dispatchPath` is the full Vuex dispatch path for the revalidate action.
// `setMutation` is the full commit path used by paintStoresFromCache to seed
// Vuex from IDB. `clearMutation` (optional) is committed BEFORE `setMutation`
// for modules whose SET_* mutation merges-by-id instead of replacing — without
// it, rows deleted server-side between sessions would survive as phantoms.
// Vuex from IDB. Every SET_* mutation must REPLACE its records (not merge) so
// rows deleted server-side never survive as phantoms.
export const cacheableModels = [
{
name: 'inbox',
@@ -24,7 +23,6 @@ export const cacheableModels = [
name: 'team',
dispatchPath: 'teams/revalidate',
setMutation: 'teams/SET_TEAMS',
clearMutation: 'teams/CLEAR_TEAMS',
},
{
name: 'canned_response',
@@ -25,7 +25,6 @@ export default async function paintStoresFromCache(store, accountId) {
cacheableModels.map(async model => {
const localData = await dm.get({ modelName: model.name });
if (localData.length === 0) return;
if (model.clearMutation) store.commit(model.clearMutation);
store.commit(model.setMutation, localData);
})
);
@@ -64,7 +64,7 @@ describe('paintStoresFromCache', () => {
expect(storeMock.dispatch).not.toHaveBeenCalled();
});
it('commits CLEAR_TEAMS before SET_TEAMS to drop phantom rows', async () => {
it('seeds teams via SET_TEAMS', async () => {
await dm.push({
modelName: 'team',
data: [{ id: 1, name: 'Sales' }],
@@ -72,10 +72,8 @@ describe('paintStoresFromCache', () => {
await paintStoresFromCache(storeMock, accountId);
const teamCommits = storeMock.commit.mock.calls.filter(call =>
call[0].startsWith('teams/')
);
expect(teamCommits[0][0]).toBe('teams/CLEAR_TEAMS');
expect(teamCommits[1][0]).toBe('teams/SET_TEAMS');
expect(storeMock.commit).toHaveBeenCalledWith('teams/SET_TEAMS', [
{ id: 1, name: 'Sales' },
]);
});
});
@@ -2,7 +2,6 @@ import axios from 'axios';
import { actions } from '../../teams/actions';
import {
SET_TEAM_UI_FLAG,
CLEAR_TEAMS,
SET_TEAMS,
SET_TEAM_ITEM,
EDIT_TEAM,
@@ -41,7 +40,6 @@ describe('#actions', () => {
await actions.get({ commit });
expect(commit.mock.calls).toEqual([
[SET_TEAM_UI_FLAG, { isFetching: true }],
[CLEAR_TEAMS],
[SET_TEAMS, teamsList[1]],
[SET_TEAM_UI_FLAG, { isFetching: false }],
]);
@@ -1,5 +1,4 @@
import {
CLEAR_TEAMS,
SET_TEAMS,
SET_TEAM_ITEM,
EDIT_TEAM,
@@ -11,10 +10,15 @@ describe('#mutations', () => {
describe('#SET_teams', () => {
it('set teams records', () => {
const state = { records: {} };
mutations[SET_TEAMS](state, [teams[1]]);
mutations[SET_TEAMS](state, [teams[2]]);
mutations[SET_TEAMS](state, [teams[1], teams[2]]);
expect(state.records).toEqual(teams);
});
it('drops records absent from the new list', () => {
const state = { records: { ...teams } };
mutations[SET_TEAMS](state, [teams[1]]);
expect(state.records).toEqual({ 1: teams[1] });
});
});
describe('#ADD_TEAM', () => {
@@ -43,12 +47,4 @@ describe('#mutations', () => {
expect(state.records).toEqual({});
});
});
describe('#CLEAR_TEAMS', () => {
it('delete teams record', () => {
const state = { records: { 1: teams[1] } };
mutations[CLEAR_TEAMS](state);
expect(state.records).toEqual({});
});
});
});
@@ -1,6 +1,5 @@
import {
SET_TEAM_UI_FLAG,
CLEAR_TEAMS,
SET_TEAMS,
SET_TEAM_ITEM,
EDIT_TEAM,
@@ -26,13 +25,11 @@ export const actions = {
revalidate: createCacheRevalidateAction({
api: TeamsAPI,
mutation: SET_TEAMS,
clearMutation: CLEAR_TEAMS,
}),
get: async ({ commit }) => {
commit(SET_TEAM_UI_FLAG, { isFetching: true });
try {
const { data } = await TeamsAPI.get(true);
commit(CLEAR_TEAMS);
commit(SET_TEAMS, data);
} catch (error) {
throw new Error(error);
@@ -1,6 +1,5 @@
import {
SET_TEAM_UI_FLAG,
CLEAR_TEAMS,
SET_TEAMS,
SET_TEAM_ITEM,
EDIT_TEAM,
@@ -15,19 +14,14 @@ export const mutations = {
};
},
[CLEAR_TEAMS]: $state => {
$state.records = {};
},
// Replaces (not merges) so rows deleted server-side never survive as
// phantoms — SET_TEAMS only ever receives the full list.
[SET_TEAMS]: ($state, data) => {
const updatedRecords = { ...$state.records };
const records = {};
data.forEach(team => {
updatedRecords[team.id] = {
...(updatedRecords[team.id] || {}),
...team,
};
records[team.id] = team;
});
$state.records = updatedRecords;
$state.records = records;
},
[SET_TEAM_ITEM]: ($state, data) => {
@@ -1,5 +1,4 @@
export const SET_TEAM_UI_FLAG = 'SET_TEAM_UI_FLAG';
export const CLEAR_TEAMS = 'CLEAR_TEAMS';
export const SET_TEAMS = 'SET_TEAMS';
export const SET_TEAM_ITEM = 'SET_TEAM_ITEM';
export const EDIT_TEAM = 'EDIT_TEAM';