From 7b20fd142c3a5c2445a76049b476987057eb57de Mon Sep 17 00:00:00 2001 From: Jordan Sanz Date: Fri, 8 Nov 2024 09:05:35 -0500 Subject: [PATCH 01/10] fix(): update iscomboboxitemselected function to pay attention to selection mode --- .../src/utils/isComboboxItemSelected.ts | 24 ++++--- .../test/utils/isComboboxItemSelected.test.ts | 72 ++++++++++++++----- 2 files changed, 69 insertions(+), 27 deletions(-) diff --git a/packages/common/src/utils/isComboboxItemSelected.ts b/packages/common/src/utils/isComboboxItemSelected.ts index 1e52dbdc9c1..61f384b4260 100644 --- a/packages/common/src/utils/isComboboxItemSelected.ts +++ b/packages/common/src/utils/isComboboxItemSelected.ts @@ -4,17 +4,25 @@ import { IUiSchemaComboboxItem } from "../core/schemas/types"; * To be used with combobox items; determines if a given combobox item is selected * @param node the combo box item (to possible check) * @param values the selected values in the greater combobox options + * @param selectionMode the selection mode of the combobox (this determines if children's selection status determines the parent's selection status) * @returns whether or not the combo box item is selected */ export function isComboboxItemSelected( node: IUiSchemaComboboxItem, - values: string[] + values: string[], + selectionMode: string ): boolean { - return ( - values.includes(node.value) || // if this node is selected - (!!node.children?.length && - node.children.some((child: IUiSchemaComboboxItem) => - isComboboxItemSelected(child, values) - )) - ); // or any of its children are selected + let isSelected = values.includes(node.value); + + // we only check if children are selected if the selectionMode is "ancestors" + if (selectionMode === "ancestors") { + isSelected = + isSelected || // if this node is selected + (!!node.children?.length && + node.children.some((child: IUiSchemaComboboxItem) => + isComboboxItemSelected(child, values, selectionMode) + )); + } + + return isSelected; } diff --git a/packages/common/test/utils/isComboboxItemSelected.test.ts b/packages/common/test/utils/isComboboxItemSelected.test.ts index 7be7f95ebf1..920fda5b7db 100644 --- a/packages/common/test/utils/isComboboxItemSelected.test.ts +++ b/packages/common/test/utils/isComboboxItemSelected.test.ts @@ -35,31 +35,65 @@ const nodes = [ describe("isComboboxItemSelected:", () => { it("will select top level node", async () => { const selected = ["/Categories/Thing A"]; - expect(isComboboxItemSelected(nodes[0], selected)).toBe(true); - expect(isComboboxItemSelected(nodes[1], selected)).toBe(false); - expect(isComboboxItemSelected(nodes[1].children![0], selected)).toBe(false); - expect(isComboboxItemSelected(nodes[2], selected)).toBe(false); - expect(isComboboxItemSelected(nodes[2].children![0], selected)).toBe(false); - expect(isComboboxItemSelected(nodes[2].children![1], selected)).toBe(false); + expect(isComboboxItemSelected(nodes[0], selected, "single")).toBe(true); + expect(isComboboxItemSelected(nodes[1], selected, "single")).toBe(false); + expect( + isComboboxItemSelected(nodes[1].children![0], selected, "single") + ).toBe(false); + expect(isComboboxItemSelected(nodes[2], selected, "single")).toBe(false); + expect( + isComboboxItemSelected(nodes[2].children![0], selected, "single") + ).toBe(false); + expect( + isComboboxItemSelected(nodes[2].children![1], selected, "single") + ).toBe(false); }); - it("will select child node and subsequently its parent node", async () => { + it("will select child node and subsequently its parent node with ancestors selection mode", async () => { const selected = ["/Categories/Thing B/Child of B"]; - expect(isComboboxItemSelected(nodes[0], selected)).toBe(false); - expect(isComboboxItemSelected(nodes[1], selected)).toBe(true); - expect(isComboboxItemSelected(nodes[1].children![0], selected)).toBe(true); - expect(isComboboxItemSelected(nodes[2], selected)).toBe(false); - expect(isComboboxItemSelected(nodes[2].children![0], selected)).toBe(false); - expect(isComboboxItemSelected(nodes[2].children![1], selected)).toBe(false); + expect(isComboboxItemSelected(nodes[0], selected, "ancestors")).toBe(false); + expect(isComboboxItemSelected(nodes[1], selected, "ancestors")).toBe(true); + expect( + isComboboxItemSelected(nodes[1].children![0], selected, "ancestors") + ).toBe(true); + expect(isComboboxItemSelected(nodes[2], selected, "ancestors")).toBe(false); + expect( + isComboboxItemSelected(nodes[2].children![0], selected, "ancestors") + ).toBe(false); + expect( + isComboboxItemSelected(nodes[2].children![1], selected, "ancestors") + ).toBe(false); }); it("will check correct nodes even if there are two nodes with equal labels (but differing values)", async () => { const selected = ["/Categories/Thing C/Thing A"]; - expect(isComboboxItemSelected(nodes[0], selected)).toBe(false); - expect(isComboboxItemSelected(nodes[1], selected)).toBe(false); - expect(isComboboxItemSelected(nodes[1].children![0], selected)).toBe(false); - expect(isComboboxItemSelected(nodes[2], selected)).toBe(true); - expect(isComboboxItemSelected(nodes[2].children![0], selected)).toBe(false); - expect(isComboboxItemSelected(nodes[2].children![1], selected)).toBe(true); + expect(isComboboxItemSelected(nodes[0], selected, "ancestors")).toBe(false); + expect(isComboboxItemSelected(nodes[1], selected, "ancestors")).toBe(false); + expect( + isComboboxItemSelected(nodes[1].children![0], selected, "ancestors") + ).toBe(false); + expect(isComboboxItemSelected(nodes[2], selected, "ancestors")).toBe(true); + expect( + isComboboxItemSelected(nodes[2].children![0], selected, "ancestors") + ).toBe(false); + expect( + isComboboxItemSelected(nodes[2].children![1], selected, "ancestors") + ).toBe(true); + }); + + it("will select child node but will not select parent node with multiple selection mode", async () => { + const selected = ["/Categories/Thing B/Child of B"]; + expect(isComboboxItemSelected(nodes[0], selected, "multiple")).toBe(false); + expect(isComboboxItemSelected(nodes[1], selected, "multiple")).toBe(false); + expect( + isComboboxItemSelected(nodes[1].children![0], selected, "multiple") + ).toBe(true); + expect(isComboboxItemSelected(nodes[2], selected, "multiple")).toBe(false); + expect( + isComboboxItemSelected(nodes[2].children![0], selected, "multiple") + ).toBe(false); + expect( + isComboboxItemSelected(nodes[2].children![1], selected, "multiple") + ).toBe(false); }); }); From 99aa2bd758527510fbce95767e0e71794bd02626 Mon Sep 17 00:00:00 2001 From: Jordan Sanz Date: Thu, 21 Nov 2024 12:18:42 -0500 Subject: [PATCH 02/10] style(): add comment --- packages/common/src/utils/isComboboxItemSelected.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/common/src/utils/isComboboxItemSelected.ts b/packages/common/src/utils/isComboboxItemSelected.ts index 61f384b4260..fa7627e0db4 100644 --- a/packages/common/src/utils/isComboboxItemSelected.ts +++ b/packages/common/src/utils/isComboboxItemSelected.ts @@ -19,6 +19,7 @@ export function isComboboxItemSelected( isSelected = isSelected || // if this node is selected (!!node.children?.length && + // or any of its children are selected node.children.some((child: IUiSchemaComboboxItem) => isComboboxItemSelected(child, values, selectionMode) )); From 56fa3ea47b34f686e4f39b3cc32ead7f6935d5e5 Mon Sep 17 00:00:00 2001 From: Jordan Sanz Date: Tue, 26 Nov 2024 08:28:26 -0500 Subject: [PATCH 03/10] feat(): include expansions for match objects by using families --- .../src/search/_internal/portalSearchItems.ts | 66 ++++++++++++++++++- packages/common/src/types.ts | 43 +++++++----- .../_internal/portalSearchItems.test.ts | 52 +++++++++++++++ 3 files changed, 143 insertions(+), 18 deletions(-) diff --git a/packages/common/src/search/_internal/portalSearchItems.ts b/packages/common/src/search/_internal/portalSearchItems.ts index d9b90b35360..fbc641cd20a 100644 --- a/packages/common/src/search/_internal/portalSearchItems.ts +++ b/packages/common/src/search/_internal/portalSearchItems.ts @@ -7,7 +7,7 @@ import { enrichProjectSearchResult } from "../../projects"; import { enrichSiteSearchResult } from "../../sites"; import { enrichInitiativeSearchResult } from "../../initiatives/HubInitiatives"; import { enrichTemplateSearchResult } from "../../templates/fetch"; -import { IHubRequestOptions } from "../../types"; +import { HubFamilies, HubFamily, IHubRequestOptions } from "../../types"; import { IFilter, @@ -26,6 +26,7 @@ import { enrichContentSearchResult } from "../../content/search"; import { cloneObject } from "../../util"; import { getWellknownCollection } from "../wellKnownCatalog"; import { getProp } from "../../objects"; +import { getFamilyTypes } from "../../content/get-family"; /** * @internal @@ -475,6 +476,48 @@ export function applyWellKnownItemPredicates(query: IQuery): IQuery { ); acc = [...acc, ...replacements]; replacedPredicates = true; + } else if ( + /** + * NOTE: as of Nov. 26 2024, we have elected to start using the family types + * for a type replacement rather than the entire replacement itself. This updates + * a well-known predicate to only have type values, rather than types, typekeywords, etc etc. + * We also use the family types to replace the type values. Almost all of our current type + * replacements include typekeywords only to also retrieve old items -- i.e. having -- we need to be aware + * that by using family types, we are not including these old items in results in these cases. + * + * This clause is primarily used by custom-build catalogs using the new catalog editor. + * + * We specifically do not say that we have replaced filters here either as we want to leave the + * operator as is. + */ + predicate.type && + typeof predicate.type !== "string" && + typeof predicate.type === "object" + ) { + // we have an IMatchOptions object, so we have to iterate over the all/any/not + Object.keys(predicate.type)?.forEach((key) => { + const types = predicate.type[key]; + + // for each type, try to replace it with the family types if it is an expansion + predicate.type[key] = types.reduce( + (typesAcc: string[], type: string) => { + if (isFamilyExpansionType(type)) { + // we need the type keyword without the dollar sign + const family = type.slice(1); + // get the family types from the given expansion + const familyTypes = getFamilyTypes(family as HubFamily); + typesAcc = [...typesAcc, ...familyTypes]; + } else { + typesAcc.push(type); + } + return typesAcc; + }, + [] + ); + }); + + // keep the updated predicate + acc.push(predicate); } else { // this predicate does not have a well-known type // so we just keep it @@ -515,6 +558,27 @@ export function isWellKnownTypeFilter( return result; } +/** + * Checks to see if our type is a family expansion, + * i.e. our type is a key in HubFamilies and it begins with a dollar sign + * + * $content, $site, etc. + * @param key + * @returns + */ +export function isFamilyExpansionType(key: string): boolean { + let result = false; + // if we have a key, the first character of the key is a $, and the key without the $ is in Hub Families + if ( + key && + key.charAt(0) === "$" && + HubFamilies.includes(key.slice(1) as HubFamily) + ) { + result = true; + } + return result; +} + /** * Return the predicates for a well-known type * @param key diff --git a/packages/common/src/types.ts b/packages/common/src/types.ts index 8c944836382..b80b844ed1c 100644 --- a/packages/common/src/types.ts +++ b/packages/common/src/types.ts @@ -177,23 +177,32 @@ export type IRevertableTaskResult = | IRevertableTaskSuccess | IRevertableTaskFailed; -export type HubFamily = - | "app" - | "content" - | "dataset" - | "document" - | "event" - | "feedback" - | "initiative" - | "map" - | "people" - | "site" - | "team" - | "template" - | "project" - | "channel" - | "discussion" - | "eventAttendee"; +/** + * All Hub families + */ +export const HubFamilies = [ + "app", + "content", + "dataset", + "document", + "event", + "feedback", + "initiative", + "map", + "people", + "site", + "team", + "template", + "project", + "channel", + "discussion", + "eventAttendee", +] as const; + +/** + * All Hub families + */ +export type HubFamily = (typeof HubFamilies)[number]; /** * Visibility levels of a Hub resource diff --git a/packages/common/test/search/_internal/portalSearchItems.test.ts b/packages/common/test/search/_internal/portalSearchItems.test.ts index aa24d696583..23de7c81a83 100644 --- a/packages/common/test/search/_internal/portalSearchItems.test.ts +++ b/packages/common/test/search/_internal/portalSearchItems.test.ts @@ -6,6 +6,7 @@ import { IQuery, WellKnownCollection, } from "../../../src"; +import { getFamilyTypes } from "../../../src/content/get-family"; import * as SimpleResponse from "../../mocks/portal-search/simple-response.json"; import * as AllTypesResponse from "../../mocks/portal-search/response-with-key-types.json"; @@ -13,6 +14,7 @@ import { MOCK_AUTH } from "../../mocks/mock-auth"; import { applyWellKnownCollectionFilters, applyWellKnownItemPredicates, + isFamilyExpansionType, portalSearchItems, portalSearchItemsAsItems, WellKnownItemPredicates, @@ -484,6 +486,38 @@ describe("portalSearchItems Module:", () => { expect(chk.filters[0].predicates[0].owner).not.toBeDefined(); expect(chk.filters[0].predicates[1].group).toEqual("00c"); }); + + it("handles a match options object with expansions", () => { + const qry: IQuery = { + targetEntity: "item", + filters: [ + { + predicates: [ + { + type: { + any: ["$content", "$app"], + not: ["$site", "Hub Initiative"], + }, + }, + ], + operation: "AND", + }, + ], + }; + + const chk = applyWellKnownItemPredicates(qry); + expect(chk.filters.length).toBe(1); + expect(chk.filters[0].operation).toBe("AND"); + expect(chk.filters[0].predicates.length).toBe(1); + expect(chk.filters[0].predicates[0].type.any).toEqual([ + ...getFamilyTypes("content"), + ...getFamilyTypes("app"), + ]); + expect(chk.filters[0].predicates[0].type.not).toEqual([ + ...getFamilyTypes("site"), + "Hub Initiative", + ]); + }); }); describe("applyWellKnownCollectionFilters", () => { const baseQuery: IQuery = { @@ -525,6 +559,24 @@ describe("portalSearchItems Module:", () => { expect(result).toEqual(expected); }); + + describe("isFamilyExpansionType", () => { + it("returns true for a family type", () => { + expect(isFamilyExpansionType("$content")).toBe(true); + }); + + it("returns false for a non-family type", () => { + expect(isFamilyExpansionType("$webmap")).toBe(false); + }); + + it("returns false for a family type that does not have a dollar sign in front", () => { + expect(isFamilyExpansionType("content")).toBe(false); + }); + + it("returns false for an empty key", () => { + expect(isFamilyExpansionType("")).toBe(false); + }); + }); }); }); From 05eca9fccf7aeaa640e786e72fd33b7634b60f7d Mon Sep 17 00:00:00 2001 From: Jordan Sanz Date: Tue, 26 Nov 2024 08:33:42 -0500 Subject: [PATCH 04/10] test(): update test coverage --- .../common/test/search/_internal/portalSearchItems.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/common/test/search/_internal/portalSearchItems.test.ts b/packages/common/test/search/_internal/portalSearchItems.test.ts index 23de7c81a83..e0bc5b5cece 100644 --- a/packages/common/test/search/_internal/portalSearchItems.test.ts +++ b/packages/common/test/search/_internal/portalSearchItems.test.ts @@ -569,6 +569,10 @@ describe("portalSearchItems Module:", () => { expect(isFamilyExpansionType("$webmap")).toBe(false); }); + it("returns false for a dollar sign type that is not a family", () => { + expect(isFamilyExpansionType("$storymap")).toBe(false); + }); + it("returns false for a family type that does not have a dollar sign in front", () => { expect(isFamilyExpansionType("content")).toBe(false); }); From 9a72b5abcb729ad8df89240300db848829c2fc6c Mon Sep 17 00:00:00 2001 From: Jordan Sanz Date: Tue, 26 Nov 2024 09:00:34 -0500 Subject: [PATCH 05/10] fix(): include only objects in the else if --- .../src/search/_internal/portalSearchItems.ts | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/common/src/search/_internal/portalSearchItems.ts b/packages/common/src/search/_internal/portalSearchItems.ts index fbc641cd20a..dd5346f659e 100644 --- a/packages/common/src/search/_internal/portalSearchItems.ts +++ b/packages/common/src/search/_internal/portalSearchItems.ts @@ -477,22 +477,22 @@ export function applyWellKnownItemPredicates(query: IQuery): IQuery { acc = [...acc, ...replacements]; replacedPredicates = true; } else if ( - /** - * NOTE: as of Nov. 26 2024, we have elected to start using the family types - * for a type replacement rather than the entire replacement itself. This updates - * a well-known predicate to only have type values, rather than types, typekeywords, etc etc. - * We also use the family types to replace the type values. Almost all of our current type - * replacements include typekeywords only to also retrieve old items -- i.e. having -- we need to be aware - * that by using family types, we are not including these old items in results in these cases. - * - * This clause is primarily used by custom-build catalogs using the new catalog editor. - * - * We specifically do not say that we have replaced filters here either as we want to leave the - * operator as is. - */ + /** + * NOTE: as of Nov. 26 2024, we have elected to start using the family types + * for a type replacement rather than the entire replacement itself. This updates + * a well-known predicate to only have type values, rather than types, typekeywords, etc etc. + * We also use the family types to replace the type values. Almost all of our current type + * replacements include typekeywords only to also retrieve old items -- i.e. having -- we need to be aware + * that by using family types, we are not including these old items in results in these cases. + * + * This clause is primarily used by custom-build catalogs using the new catalog editor. + * + * We specifically do not say that we have replaced filters here either as we want to leave the + * operator as is. + */ predicate.type && typeof predicate.type !== "string" && - typeof predicate.type === "object" + !Array.isArray(predicate.type) ) { // we have an IMatchOptions object, so we have to iterate over the all/any/not Object.keys(predicate.type)?.forEach((key) => { From 26eb339fbe3ccf95c61b5d615a2071da075b54a2 Mon Sep 17 00:00:00 2001 From: Jordan Sanz Date: Tue, 26 Nov 2024 09:02:31 -0500 Subject: [PATCH 06/10] test(): cover branch --- .../_internal/portalSearchItems.test.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/common/test/search/_internal/portalSearchItems.test.ts b/packages/common/test/search/_internal/portalSearchItems.test.ts index e0bc5b5cece..0f300b5f510 100644 --- a/packages/common/test/search/_internal/portalSearchItems.test.ts +++ b/packages/common/test/search/_internal/portalSearchItems.test.ts @@ -487,6 +487,27 @@ describe("portalSearchItems Module:", () => { expect(chk.filters[0].predicates[1].group).toEqual("00c"); }); + it("handles when type is an array", () => { + const qry: IQuery = { + targetEntity: "item", + filters: [ + { + predicates: [ + { + type: ["Hub Initiative"], + }, + ], + }, + ], + }; + + const chk = applyWellKnownItemPredicates(qry); + expect(chk.filters.length).toBe(1); + expect(chk.filters[0].operation).toBe("AND"); + expect(chk.filters[0].predicates.length).toBe(1); + expect(chk.filters[0].predicates[0].type).toEqual(["Hub Initiative"]); + }); + it("handles a match options object with expansions", () => { const qry: IQuery = { targetEntity: "item", From 95fdc70afafa25ce247a49d47861968dabee79f3 Mon Sep 17 00:00:00 2001 From: Jordan Sanz Date: Tue, 26 Nov 2024 09:39:51 -0500 Subject: [PATCH 07/10] fix(): coverage --- packages/common/src/search/_internal/portalSearchItems.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/common/src/search/_internal/portalSearchItems.ts b/packages/common/src/search/_internal/portalSearchItems.ts index dd5346f659e..39230ce08c4 100644 --- a/packages/common/src/search/_internal/portalSearchItems.ts +++ b/packages/common/src/search/_internal/portalSearchItems.ts @@ -495,7 +495,7 @@ export function applyWellKnownItemPredicates(query: IQuery): IQuery { !Array.isArray(predicate.type) ) { // we have an IMatchOptions object, so we have to iterate over the all/any/not - Object.keys(predicate.type)?.forEach((key) => { + Object.keys(predicate.type).forEach((key) => { const types = predicate.type[key]; // for each type, try to replace it with the family types if it is an expansion From dc155d405d75ce9b0ff1a3b1e2040ee39b226284 Mon Sep 17 00:00:00 2001 From: Jordan Sanz Date: Tue, 26 Nov 2024 09:45:28 -0500 Subject: [PATCH 08/10] test(): fix test --- packages/common/test/search/_internal/portalSearchItems.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/common/test/search/_internal/portalSearchItems.test.ts b/packages/common/test/search/_internal/portalSearchItems.test.ts index 0f300b5f510..143c3af0ce6 100644 --- a/packages/common/test/search/_internal/portalSearchItems.test.ts +++ b/packages/common/test/search/_internal/portalSearchItems.test.ts @@ -497,6 +497,7 @@ describe("portalSearchItems Module:", () => { type: ["Hub Initiative"], }, ], + operation: "AND", }, ], }; From 37817357202f749f4b23d21ec73abdcfb0aaef2b Mon Sep 17 00:00:00 2001 From: Jordan Sanz Date: Mon, 2 Dec 2024 08:55:28 -0500 Subject: [PATCH 09/10] fix(): update check for array --- .../src/search/_internal/portalSearchItems.ts | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/packages/common/src/search/_internal/portalSearchItems.ts b/packages/common/src/search/_internal/portalSearchItems.ts index 39230ce08c4..f1b81105f98 100644 --- a/packages/common/src/search/_internal/portalSearchItems.ts +++ b/packages/common/src/search/_internal/portalSearchItems.ts @@ -498,22 +498,25 @@ export function applyWellKnownItemPredicates(query: IQuery): IQuery { Object.keys(predicate.type).forEach((key) => { const types = predicate.type[key]; - // for each type, try to replace it with the family types if it is an expansion - predicate.type[key] = types.reduce( - (typesAcc: string[], type: string) => { - if (isFamilyExpansionType(type)) { - // we need the type keyword without the dollar sign - const family = type.slice(1); - // get the family types from the given expansion - const familyTypes = getFamilyTypes(family as HubFamily); - typesAcc = [...typesAcc, ...familyTypes]; - } else { - typesAcc.push(type); - } - return typesAcc; - }, - [] - ); + // try to reduce the array if it is an array + if (Array.isArray(types)) { + // for each type, try to replace it with the family types if it is an expansion + predicate.type[key] = types.reduce( + (typesAcc: string[], type: string) => { + if (isFamilyExpansionType(type)) { + // we need the type keyword without the dollar sign + const family = type.slice(1); + // get the family types from the given expansion + const familyTypes = getFamilyTypes(family as HubFamily); + typesAcc = [...typesAcc, ...familyTypes]; + } else { + typesAcc.push(type); + } + return typesAcc; + }, + [] + ); + } }); // keep the updated predicate From 1f785da04521cc982b66e96e5c1572ff2f86b717 Mon Sep 17 00:00:00 2001 From: Jordan Sanz Date: Mon, 2 Dec 2024 09:02:22 -0500 Subject: [PATCH 10/10] test(): update test --- .../_internal/portalSearchItems.test.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/packages/common/test/search/_internal/portalSearchItems.test.ts b/packages/common/test/search/_internal/portalSearchItems.test.ts index 143c3af0ce6..f57415f1d34 100644 --- a/packages/common/test/search/_internal/portalSearchItems.test.ts +++ b/packages/common/test/search/_internal/portalSearchItems.test.ts @@ -509,6 +509,32 @@ describe("portalSearchItems Module:", () => { expect(chk.filters[0].predicates[0].type).toEqual(["Hub Initiative"]); }); + it("handles a match options object", () => { + const qry: IQuery = { + targetEntity: "item", + filters: [ + { + predicates: [ + { + type: { + not: "Code attachment", + }, + }, + ], + operation: "AND", + }, + ], + }; + + const chk = applyWellKnownItemPredicates(qry); + expect(chk.filters.length).toBe(1); + expect(chk.filters[0].operation).toBe("AND"); + expect(chk.filters[0].predicates.length).toBe(1); + expect(chk.filters[0].predicates[0].type.not).toEqual( + "Code attachment" + ); + }); + it("handles a match options object with expansions", () => { const qry: IQuery = { targetEntity: "item",