diff --git a/.stylelintrc b/.stylelintrc index 88baf6a4240c..e1b27f90f245 100644 --- a/.stylelintrc +++ b/.stylelintrc @@ -30,5 +30,8 @@ { "ignoreKeywords": ["dummyValue"], "camelCaseSvgKeywords": true } ] }, - "ignoreFiles": ["packages/decap-cms-lib-auth/index.d.ts"] + "ignoreFiles": [ + "packages/decap-cms-lib-auth/index.d.ts", + "packages/decap-cms-core/src/backend.ts" + ] } diff --git a/packages/decap-cms-core/src/__tests__/backend.spec.js b/packages/decap-cms-core/src/__tests__/backend.spec.js index c673c19a01c4..7a1a1f0768a5 100644 --- a/packages/decap-cms-core/src/__tests__/backend.spec.js +++ b/packages/decap-cms-core/src/__tests__/backend.spec.js @@ -95,6 +95,33 @@ describe('Backend', () => { expect(result.length).toBe(1); }); + it('filters multiple values', () => { + const result = backend.filterEntries( + { + entries: [ + { + data: { + testField: 'one', + }, + }, + { + data: { + testField: 'two', + }, + }, + { + data: { + testField: 'three', + }, + }, + ], + }, + Map({ field: 'testField', value: ['one', 'two'] }), + ); + + expect(result.length).toBe(2); + }); + it('filters list values', () => { const result = backend.filterEntries( { @@ -116,6 +143,23 @@ describe('Backend', () => { expect(result.length).toBe(1); }); + + it('behaves when field values are absent', () => { + const result = backend.filterEntries( + { + entries: [ + { + data: { + otherField: 'testValue', + }, + }, + ], + }, + Map({ field: 'testField', value: 'testValue' }), + ); + + expect(result.length).toBe(0); + }); }); describe('getLocalDraftBackup', () => { diff --git a/packages/decap-cms-core/src/backend.ts b/packages/decap-cms-core/src/backend.ts index c998d645041d..2a1528eeedb7 100644 --- a/packages/decap-cms-core/src/backend.ts +++ b/packages/decap-cms-core/src/backend.ts @@ -57,6 +57,7 @@ import { I18N_STRUCTURE, } from './lib/i18n'; +import type { StaticallyTypedRecord } from './types/immutable'; import type { I18nInfo } from './lib/i18n'; import type AssetProxy from './valueObjects/AssetProxy'; import type { @@ -1359,14 +1360,46 @@ export class Backend { } filterEntries(collection: { entries: EntryValue[] }, filterRule: FilterRule) { + const filterValues = this.valuesAsArray(filterRule.get('value')); + + const fieldName = filterRule.get('field'); + return collection.entries.filter(entry => { - const fieldValue = entry.data[filterRule.get('field')]; - if (Array.isArray(fieldValue)) { - return fieldValue.includes(filterRule.get('value')); - } - return fieldValue === filterRule.get('value'); + const fieldValues = this.valuesAsArray(entry.data[fieldName]); + + return filterValues.some(filterValue => fieldValues.includes(filterValue)); }); } + + // Values can be a string, a list of strings, or statically-typed-record lists of strings + // depending on context (unit test vs. actual config, single vs. multiple values, etc.) + // + // We normalize to a simple list for easier processing above. + // + // If the value is null/undefined, return an empty array. + // + valuesAsArray(rawValue: string | List | StaticallyTypedRecord>): string[] { + let values: string[] = []; + + if (rawValue === null || rawValue === undefined) { + values = []; + } else if ((>>rawValue).toJS) { + const valueList = (>>rawValue).toJS(); + + if (valueList.toArray) { + values = valueList.toArray(); + } + else { + values = valueList; + } + } else if (Array.isArray(rawValue)) { + values = rawValue; + } else { + values = [rawValue]; + } + + return values; + } } export function resolveBackend(config: CmsConfig) { diff --git a/packages/decap-cms-core/src/types/redux.ts b/packages/decap-cms-core/src/types/redux.ts index a691e1c7e642..9067595ab7fe 100644 --- a/packages/decap-cms-core/src/types/redux.ts +++ b/packages/decap-cms-core/src/types/redux.ts @@ -575,7 +575,7 @@ export type EntryField = StaticallyTypedRecord<{ export type EntryFields = List; export type FilterRule = StaticallyTypedRecord<{ - value: string; + value: string | List | StaticallyTypedRecord>; field: string; }>;