fix: strip snapshot key for v4-migration tab (#8354)

This commit is contained in:
prateek-bruno
2026-06-30 19:04:58 +05:30
committed by GitHub
parent cfb0db3f90
commit bb21d4c1c9
2 changed files with 133 additions and 11 deletions

View File

@@ -26,6 +26,10 @@ const NON_REPLACEABLE_SINGLETON_TAB_TYPES = new Set([
'openapi-spec'
]);
const IGNORED_TAB_TYPES = new Set([
'v4-migration'
]);
export const SAVE_TRIGGERS = new Map([
['app/setSnapshotReady', null],
['tabs/addTab', null],
@@ -56,6 +60,24 @@ export const SAVE_TRIGGERS = new Map([
export const isRequestTab = (type) => REQUEST_TAB_TYPES.has(type);
const isIgnoredTab = (tab) => IGNORED_TAB_TYPES.has(tab?.type);
const isIgnoredActiveTab = (activeTab) => activeTab?.accessor === 'type' && IGNORED_TAB_TYPES.has(activeTab.value);
// Strip ignored tab types from a snapshot read on any path (lookups or ipc fallback),
// including an active tab that points at one.
const sanitizeSnapshotTabs = (tabsSnapshot) => {
if (!tabsSnapshot || !Array.isArray(tabsSnapshot.tabs)) {
return tabsSnapshot;
}
return {
...tabsSnapshot,
activeTab: isIgnoredActiveTab(tabsSnapshot.activeTab) ? null : tabsSnapshot.activeTab,
tabs: tabsSnapshot.tabs.filter((tab) => !isIgnoredTab(tab))
};
};
export const shouldExcludeTab = (tab, transientDirectory) => {
return transientDirectory && tab.pathname?.startsWith(transientDirectory);
};
@@ -107,8 +129,8 @@ const normalizeCollectionSnapshotEntry = (pathname, entry = {}, tabsEntry = {})
isMounted: typeof entry.isMounted === 'boolean' ? entry.isMounted : false,
activeTab: tabsEntry.activeTab ?? entry.activeTab ?? null,
tabs: Array.isArray(tabsEntry.tabs)
? tabsEntry.tabs.filter((tab) => isObject(tab))
: (Array.isArray(entry.tabs) ? entry.tabs.filter((tab) => isObject(tab)) : [])
? tabsEntry.tabs.filter((tab) => isObject(tab) && !isIgnoredTab(tab))
: (Array.isArray(entry.tabs) ? entry.tabs.filter((tab) => isObject(tab) && !isIgnoredTab(tab)) : [])
};
};
@@ -613,13 +635,15 @@ export const hydrateCollectionTabs = async (
) => {
const { ipcRenderer } = window;
const tabsSnapshot = getTabsSnapshotFromLookups(
collection.pathname,
snapshotLookups,
workspacePathname,
strictWorkspaceScope
)
|| await ipcRenderer.invoke('renderer:snapshot:get-tabs', collection.pathname, workspacePathname).catch(() => null);
const tabsSnapshot = sanitizeSnapshotTabs(
getTabsSnapshotFromLookups(
collection.pathname,
snapshotLookups,
workspacePathname,
strictWorkspaceScope
)
|| await ipcRenderer.invoke('renderer:snapshot:get-tabs', collection.pathname, workspacePathname).catch(() => null)
);
const hasPersistedTabs = Array.isArray(tabsSnapshot?.tabs) && tabsSnapshot.tabs.length > 0;
const hasPersistedActiveTab = Boolean(tabsSnapshot?.activeTab);
@@ -651,8 +675,10 @@ export const hydrateTabs = async (collections, dispatch, restoreTabs, snapshotLo
export const getActiveTabFromSnapshot = async (collectionPathname, collection, snapshotLookups = null, workspacePathname = null) => {
const { ipcRenderer } = window;
const tabsSnapshot = getTabsSnapshotFromLookups(collectionPathname, snapshotLookups, workspacePathname)
|| await ipcRenderer.invoke('renderer:snapshot:get-tabs', collectionPathname, workspacePathname).catch(() => null);
const tabsSnapshot = sanitizeSnapshotTabs(
getTabsSnapshotFromLookups(collectionPathname, snapshotLookups, workspacePathname)
|| await ipcRenderer.invoke('renderer:snapshot:get-tabs', collectionPathname, workspacePathname).catch(() => null)
);
if (!tabsSnapshot?.activeTab || !tabsSnapshot?.tabs?.length) return null;

View File

@@ -218,6 +218,27 @@ describe('hydrateSnapshotLookups', () => {
expect(lookups.hasWorkspaceScopedTabs).toBe(true);
});
it('drops legacy v4 migration tabs from snapshot lookups', () => {
const snapshot = {
collections: [
{
pathname: '/collections/legacy',
activeTab: { accessor: 'type', value: 'v4-migration' },
tabs: [
{ type: 'v4-migration', accessor: 'type', permanent: true },
{ type: 'variables', accessor: 'type', permanent: true }
]
}
]
};
const lookups = hydrateSnapshotLookups(snapshot);
expect(lookups.tabsByCollectionPath['/collections/legacy'].tabs).toEqual([
{ type: 'variables', accessor: 'type', permanent: true }
]);
});
});
describe('deserializeTab', () => {
@@ -766,4 +787,79 @@ describe('hydrateCollectionTabs', () => {
expect(dispatch).toHaveBeenCalledTimes(1);
expect(restoreTabs).toHaveBeenCalledTimes(1);
});
it('does not restore legacy v4 migration tabs from direct tab snapshots', async () => {
global.window.ipcRenderer.invoke.mockResolvedValue({
tabs: [
{ type: 'v4-migration', accessor: 'type', permanent: true },
{ type: 'variables', accessor: 'type', permanent: true }
],
activeTab: {
accessor: 'type',
value: 'v4-migration'
}
});
const dispatch = jest.fn();
const restoreTabs = jest.fn((payload) => ({
type: 'tabs/restoreTabs',
payload
}));
await hydrateCollectionTabs(
{ uid: 'collection-uid', pathname: '/collections/legacy' },
dispatch,
restoreTabs,
null,
null,
true
);
expect(restoreTabs).toHaveBeenCalledWith(
expect.objectContaining({
tabs: [{ type: 'variables', accessor: 'type', permanent: true }],
activeTab: null
})
);
});
});
describe('getActiveTabFromSnapshot', () => {
beforeEach(() => {
global.window = {
ipcRenderer: {
invoke: jest.fn().mockResolvedValue(null)
}
};
});
afterEach(() => {
delete global.window;
});
it('ignores a legacy v4 migration active tab snapshot', async () => {
const snapshot = {
collections: [
{
pathname: '/collections/legacy',
tabs: [
{ type: 'v4-migration', accessor: 'type', permanent: true }
],
activeTab: {
accessor: 'type',
value: 'v4-migration'
}
}
]
};
const lookups = hydrateSnapshotLookups(snapshot);
const activeTab = await getActiveTabFromSnapshot(
'/collections/legacy',
{ uid: 'collection-uid', pathname: '/collections/legacy' },
lookups
);
expect(activeTab).toBeNull();
});
});