diff --git a/code/lib/manager-api/src/modules/stories.ts b/code/lib/manager-api/src/modules/stories.ts index c69e5e23cdb5..aff85aabcea3 100644 --- a/code/lib/manager-api/src/modules/stories.ts +++ b/code/lib/manager-api/src/modules/stories.ts @@ -263,7 +263,10 @@ export interface SubAPI { * @param {StatusUpdate} update - An object containing the updated status information. * @returns {Promise} A promise that resolves when the status has been updated. */ - experimental_updateStatus: (addonId: string, update: API_StatusUpdate) => Promise; + experimental_updateStatus: ( + addonId: string, + update: API_StatusUpdate | ((state: API_StatusState) => API_StatusUpdate) + ) => Promise; /** * Updates the filtering of the index. * @@ -581,13 +584,27 @@ export const init: ModuleFn = ({ }, /* EXPERIMENTAL APIs */ - experimental_updateStatus: async (id, update) => { + experimental_updateStatus: async (id, input) => { const { status, internal_index: index } = store.getState(); const newStatus = { ...status }; + const update = typeof input === 'function' ? input(status) : input; + + if (Object.keys(update).length === 0) { + return; + } + Object.entries(update).forEach(([storyId, value]) => { newStatus[storyId] = { ...(newStatus[storyId] || {}) }; - newStatus[storyId][id] = value; + if (value === null) { + delete newStatus[storyId][id]; + } else { + newStatus[storyId][id] = value; + } + + if (Object.keys(newStatus[storyId]).length === 0) { + delete newStatus[storyId]; + } }); await store.setState({ status: newStatus }, { persistence: 'session' }); diff --git a/code/lib/manager-api/src/tests/stories.test.ts b/code/lib/manager-api/src/tests/stories.test.ts index 0ee0c9be4ebb..a93cd1df9a99 100644 --- a/code/lib/manager-api/src/tests/stories.test.ts +++ b/code/lib/manager-api/src/tests/stories.test.ts @@ -1273,6 +1273,90 @@ describe('stories API', () => { } `); }); + it('delete when value is null', async () => { + const moduleArgs = createMockModuleArgs({}); + const { api } = initStories(moduleArgs as unknown as ModuleArgs); + const { store } = moduleArgs; + + await api.setIndex({ v: 4, entries: mockEntries }); + + await expect( + api.experimental_updateStatus('a-addon-id', { + 'a-story-id': { + status: 'pending', + title: 'an addon title', + description: 'an addon description', + }, + 'another-story-id': { status: 'success', title: 'a addon title', description: '' }, + }) + ).resolves.not.toThrow(); + + // do a second update, this time with null + await expect( + api.experimental_updateStatus('a-addon-id', { + 'a-story-id': null, + 'another-story-id': { status: 'success', title: 'a addon title', description: '' }, + }) + ).resolves.not.toThrow(); + + expect(store.getState().status).toMatchInlineSnapshot(` + Object { + "another-story-id": Object { + "a-addon-id": Object { + "description": "", + "status": "success", + "title": "a addon title", + }, + }, + } + `); + }); + it('updates with a function', async () => { + const moduleArgs = createMockModuleArgs({}); + const { api } = initStories(moduleArgs as unknown as ModuleArgs); + const { store } = moduleArgs; + + await api.setIndex({ v: 4, entries: mockEntries }); + + // setup initial state + await expect( + api.experimental_updateStatus('a-addon-id', () => ({ + 'a-story-id': { + status: 'pending', + title: 'an addon title', + description: 'an addon description', + }, + 'another-story-id': { status: 'success', title: 'a addon title', description: '' }, + })) + ).resolves.not.toThrow(); + + // use existing state in function + await expect( + api.experimental_updateStatus('a-addon-id', (current) => { + return Object.fromEntries( + Object.entries(current).map(([k, v]) => [k, { ...v['a-addon-id'], status: 'success' }]) + ); + }) + ).resolves.not.toThrow(); + expect(store.getState().status).toMatchInlineSnapshot(` + Object { + "a-story-id": Object { + "a-addon-id": Object { + "description": "an addon description", + "status": "success", + "title": "an addon title", + }, + }, + "another-story-id": Object { + "a-addon-id": Object { + "description": "", + "status": "success", + "title": "a addon title", + }, + }, + } + `); + }); }); describe('experimental_setFilter', () => { it('is included in the initial state', async () => { diff --git a/code/lib/types/src/modules/api-stories.ts b/code/lib/types/src/modules/api-stories.ts index 3df42dd812ec..45acc19e35c7 100644 --- a/code/lib/types/src/modules/api-stories.ts +++ b/code/lib/types/src/modules/api-stories.ts @@ -186,5 +186,5 @@ export type API_StatusState = Record>; export type API_StatusUpdate = Record; export type API_FilterFunction = ( - item: API_PreparedIndexEntry & { status: Record } + item: API_PreparedIndexEntry & { status: Record } ) => boolean;