diff --git a/.changeset/silly-doors-yell.md b/.changeset/silly-doors-yell.md new file mode 100644 index 0000000..6f98832 --- /dev/null +++ b/.changeset/silly-doors-yell.md @@ -0,0 +1,5 @@ +--- +'@commercetools/sync-actions': patch +--- + +Fix `types` sync actions to generate `removeEnumValues`/`removeLocalizedEnumValues` update actions when values are removed from an `Enum`/`LocalizedEnum` field definition. Previously, removed enum values were silently dropped and no update action was produced. diff --git a/packages/sync-actions/src/types/types-actions.ts b/packages/sync-actions/src/types/types-actions.ts index f26b592..63d0475 100644 --- a/packages/sync-actions/src/types/types-actions.ts +++ b/packages/sync-actions/src/types/types-actions.ts @@ -5,6 +5,7 @@ import isEqual from 'lodash.isequal'; import createBuildArrayActions, { ADD_ACTIONS, CHANGE_ACTIONS, + REMOVE_ACTIONS, } from '../utils/create-build-array-actions'; import { buildBaseAttributesActions } from '../utils/common-actions'; import * as diffPatcher from '../utils/diffpatcher'; @@ -68,12 +69,22 @@ function actionsMapEnums( ? 'changeEnumValueOrder' : 'changeLocalizedEnumValueOrder'; + const removeEnumActionName = + attributeType === 'Enum' + ? 'removeEnumValues' + : 'removeLocalizedEnumValues'; + const buildArrayActions = createBuildArrayActions('values', { [ADD_ACTIONS]: (newEnum: CustomFieldEnumType) => ({ fieldName, action: addEnumActionName, value: newEnum, }), + [REMOVE_ACTIONS]: (oldEnum: CustomFieldEnumValue) => ({ + fieldName, + action: removeEnumActionName, + key: oldEnum.key, + }), [CHANGE_ACTIONS]: ( oldEnum: CustomFieldEnumValue, newEnum: CustomFieldEnumValue @@ -126,18 +137,33 @@ function actionsMapEnums( const actions = []; // following lists are necessary to ensure that when we change the - // order of enumValues, we generate one updateAction instead of one at a time. + // order of enumValues, or remove enumValues, we generate one updateAction + // instead of one at a time. let newEnumValuesOrder = []; + const removedEnumValueKeys = []; buildArrayActions(attributeDiff, previous, next) .flat() .forEach((updateAction) => { if (updateAction.action === changeEnumOrderActionName) { newEnumValuesOrder = next.values.map((enumValue) => enumValue.key); + } else if (updateAction.action === removeEnumActionName) { + removedEnumValueKeys.push(updateAction.key); } else actions.push(updateAction); }); return [ + // Make sure to execute the removal of enum values before adding/changing + // any, in case a removed key is reused by a newly added value. + ...(removedEnumValueKeys.length > 0 + ? [ + { + fieldName, + action: removeEnumActionName, + keys: removedEnumValueKeys, + }, + ] + : []), ...actions, ...(newEnumValuesOrder.length > 0 ? [ diff --git a/packages/sync-actions/test/types-sync-enums.spec.ts b/packages/sync-actions/test/types-sync-enums.spec.ts index ceb0cd3..a1af209 100644 --- a/packages/sync-actions/test/types-sync-enums.spec.ts +++ b/packages/sync-actions/test/types-sync-enums.spec.ts @@ -122,6 +122,49 @@ describe('Actions', () => { }); }); + describe('with enum value removed', () => { + beforeEach(() => { + before = createTestType({ + fieldDefinitions: [ + { + name: 'enum', + type: { + name: 'Enum', + values: [ + { key: 'enum_1', label: 'enum-1' }, + { key: 'enum_2', label: 'enum-2' }, + { key: 'enum_3', label: 'enum-3' }, + ], + }, + }, + ], + }); + + now = createTestType({ + fieldDefinitions: [ + { + name: 'enum', + type: { + name: 'Enum', + values: [{ key: 'enum_1', label: 'enum-1' }], + }, + }, + ], + }); + updateActions = typesSync.buildActions(now, before); + }); + + test('should return `removeEnumValues` updateAction with all removed keys', () => { + expect(updateActions).toEqual([ + { + action: 'removeEnumValues', + fieldName: 'enum', + keys: ['enum_2', 'enum_3'], + }, + ]); + }); + }); + describe('with enum reordered', () => { beforeEach(() => { before = createTestType({ @@ -259,8 +302,13 @@ describe('Actions', () => { updateActions = typesSync.buildActions(now, before); }); - test('should return `addLocalizedEnumValue` updateAction', () => { + test('should return `removeLocalizedEnumValues` and `addLocalizedEnumValue` updateActions', () => { expect(updateActions).toEqual([ + { + fieldName: 'lenum', + action: 'removeLocalizedEnumValues', + keys: ['enum_4'], + }, { fieldName: 'lenum', action: 'addLocalizedEnumValue', @@ -273,6 +321,49 @@ describe('Actions', () => { }); }); + describe('with lenum value removed', () => { + beforeEach(() => { + before = createTestType({ + fieldDefinitions: [ + { + name: 'lenum', + type: { + name: 'lenum', + values: [ + { key: 'enum_1', label: { en: 'enum-1' } }, + { key: 'enum_2', label: { en: 'enum-2' } }, + { key: 'enum_3', label: { en: 'enum-3' } }, + ], + }, + }, + ], + }); + + now = createTestType({ + fieldDefinitions: [ + { + name: 'lenum', + type: { + name: 'lenum', + values: [{ key: 'enum_1', label: { en: 'enum-1' } }], + }, + }, + ], + }); + updateActions = typesSync.buildActions(now, before); + }); + + test('should return `removeLocalizedEnumValues` updateAction with all removed keys', () => { + expect(updateActions).toEqual([ + { + action: 'removeLocalizedEnumValues', + fieldName: 'lenum', + keys: ['enum_2', 'enum_3'], + }, + ]); + }); + }); + describe('with lenum reordered', () => { beforeEach(() => { before = createTestType({