Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/frontend/src/api/hooks/useView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ export const useView: () => IView = () => {
sort_order: view.sort_order,
updated_at: view.updated_at,
};
dispatch(viewsStore.updateSubscribedViewsCache([viewPreview]));
dispatch(viewsStore.addOrUpdateSubscribedView(viewPreview));
dispatch(
viewsStore.setViewsCache({
...viewsCache,
Expand Down
125 changes: 123 additions & 2 deletions packages/frontend/src/api/store/slices/views.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import viewsReducer, {
selectedViewSelector,
getSelectedViewRefFromDraft,
updateSubscribedViewsCache,
addOrUpdateSubscribedView,
setSubscribedViewsCache,
} from "./views";

Expand Down Expand Up @@ -242,8 +243,9 @@ describe("updateSubscribedViewsCache", () => {
const viewIds = newSubscribedViews.map((view) => view.id);

// delay the last modified date by 1 second
// eslint-disable-next-line no-promise-executor-return
await new Promise((resolve) => setTimeout(resolve, 1000));
await new Promise((resolve) => {
setTimeout(resolve, 1000);
});

const newSubscribedViewsState = viewsReducer(
initialState,
Expand Down Expand Up @@ -308,6 +310,125 @@ describe("updateSubscribedViewsCache", () => {

expect(newSubscribedViews).toStrictEqual(updatedSubscribedViews);
});

it("should replace entire cache with payload (not merge)", () => {
// Start with cache containing views 1, 2, 3
const initialCache = {
1: fakeSubscribedViewsCacheMock(1),
2: fakeSubscribedViewsCacheMock(2),
3: fakeSubscribedViewsCacheMock(3),
};

initialState = {
...initialState,
subscribedViewsCache: initialCache,
};

// Update with only views 2 and 4
const newSubscribedViews = [
fakeSubscribedView(2),
fakeSubscribedView(4),
];

const newViewState = viewsReducer(
initialState,
updateSubscribedViewsCache(newSubscribedViews)
);

// Should have only views 2 and 4 (views 1 and 3 removed)
expect(Object.keys(newViewState.subscribedViewsCache || {})).toEqual([
"2",
"4",
]);
expect(newViewState.subscribedViewsCache?.[1]).toBeUndefined();
expect(newViewState.subscribedViewsCache?.[3]).toBeUndefined();
expect(newViewState.subscribedViewsCache?.[2]).toBeDefined();
expect(newViewState.subscribedViewsCache?.[4]).toBeDefined();
});
});

describe("addOrUpdateSubscribedView", () => {
it("should add a new subscribed view without affecting others", () => {
// Start with cache containing views 1, 2, 3
const initialCache = {
1: fakeSubscribedViewsCacheMock(1),
2: fakeSubscribedViewsCacheMock(2),
3: fakeSubscribedViewsCacheMock(3),
};

initialState = {
...initialState,
subscribedViewsCache: initialCache,
};

// Add view 4
const newView = fakeSubscribedView(4);

const newViewState = viewsReducer(
initialState,
addOrUpdateSubscribedView(newView)
);

// Should have all 4 views
expect(
Object.keys(newViewState.subscribedViewsCache || {})
).toHaveLength(4);
expect(newViewState.subscribedViewsCache?.[1]).toBeDefined();
expect(newViewState.subscribedViewsCache?.[2]).toBeDefined();
expect(newViewState.subscribedViewsCache?.[3]).toBeDefined();
expect(newViewState.subscribedViewsCache?.[4]).toBeDefined();
});

it("should update an existing subscribed view without affecting others", async () => {
// Start with cache containing views 1, 2, 3
const initialCache = {
1: fakeSubscribedViewsCacheMock(1),
2: fakeSubscribedViewsCacheMock(2),
3: fakeSubscribedViewsCacheMock(3),
};

initialState = {
...initialState,
subscribedViewsCache: initialCache,
};

// Wait 1 second to ensure lastSynced will be different
await new Promise((resolve) => {
setTimeout(resolve, 1000);
});

// Update view 2
const updatedView = {
...fakeSubscribedView(2),
name: "Updated View 2",
};

const newViewState = viewsReducer(
initialState,
addOrUpdateSubscribedView(updatedView)
);

// Should still have 3 views
expect(
Object.keys(newViewState.subscribedViewsCache || {})
).toHaveLength(3);

// View 2 should be updated
expect(newViewState.subscribedViewsCache?.[2]?.data.name).toBe(
"Updated View 2"
);

// lastSynced should be more recent than initial
expect(
moment(newViewState.subscribedViewsCache?.[2]?.lastSynced).isAfter(
initialCache[2].lastSynced
)
).toBe(true);

// Views 1 and 3 should be unchanged
expect(newViewState.subscribedViewsCache?.[1]).toEqual(initialCache[1]);
expect(newViewState.subscribedViewsCache?.[3]).toEqual(initialCache[3]);
});
});

describe("removeSharedViewFromCache", () => {
Expand Down
34 changes: 34 additions & 0 deletions packages/frontend/src/api/store/slices/views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,39 @@ const viewsSlice = createSlice({
}
draft.subscribedViewsCache = { ...draftViewsCache };
},
addOrUpdateSubscribedView(
draft,
action: PayloadAction<TUserViewPreview>
) {
const { payload: subscribedView } = action;
const { subscribedViewsCache } = draft;

const prevState = subscribedViewsCache
? subscribedViewsCache[subscribedView.id]
: undefined;

const updatedView: TSubscribedView =
prevState !== undefined
? {
...prevState,
lastSynced: new Date().toISOString(),
data: {
...subscribedView,
},
}
: {
lastModified: undefined,
lastSynced: new Date().toISOString(),
data: {
...subscribedView,
},
};

draft.subscribedViewsCache = {
...(subscribedViewsCache ?? {}),
[subscribedView.id]: updatedView,
};
},
syncView(draft, action: PayloadAction<TRemoteUserView>) {
const remoteView = mapRemoteView(action.payload);
Logger.debug(
Expand Down Expand Up @@ -761,6 +794,7 @@ export const {
addWidgetsToView,
removeWidgetFromView,
updateSubscribedViewsCache,
addOrUpdateSubscribedView,
setSubscribedViewsCache,
} = viewsSlice.actions;
export default viewsSlice.reducer;
Loading