@@ -1953,4 +1953,272 @@ describe("findTeamMembersMatchingAttributeLogic", () => {
19531953 } ) ;
19541954 } ) ;
19551955 } ) ;
1956+
1957+ describe ( "negation operators with TEXT and NUMBER attribute types" , ( ) => {
1958+ describe ( "TEXT not_equal" , ( ) => {
1959+ it ( "should match users without the attribute (undefined != 'sales manager' is true)" , async ( ) => {
1960+ const JobTitleAttribute = {
1961+ id : "job-title-attr" ,
1962+ name : "Job Title" ,
1963+ type : AttributeType . TEXT ,
1964+ slug : "job-title" ,
1965+ options : [
1966+ { id : "job-sales-mgr" , value : "Sales Manager" , slug : "sales-manager" } ,
1967+ { id : "job-engineer" , value : "Engineer" , slug : "engineer" } ,
1968+ ] ,
1969+ } ;
1970+
1971+ const { createdUsers } = await createAttributesScenario ( {
1972+ attributes : [ JobTitleAttribute ] ,
1973+ teamMembersWithAttributeOptionValuePerAttribute : [
1974+ { attributes : { [ JobTitleAttribute . id ] : "Sales Manager" } } ,
1975+ { attributes : { [ JobTitleAttribute . id ] : "Engineer" } } ,
1976+ { attributes : { } } ,
1977+ ] ,
1978+ } ) ;
1979+
1980+ const attributesQueryValue = buildQueryValue ( {
1981+ rules : [
1982+ {
1983+ raqbFieldId : JobTitleAttribute . id ,
1984+ value : [ "sales manager" ] ,
1985+ operator : "not_equal" ,
1986+ valueSrc : [ "value" ] ,
1987+ valueType : [ "text" ] ,
1988+ } ,
1989+ ] ,
1990+ } ) as AttributesQueryValue ;
1991+
1992+ const { teamMembersMatchingAttributeLogic : result } = await findTeamMembersMatchingAttributeLogic ( {
1993+ dynamicFieldValueOperands : { fields : [ ] , response : { } } ,
1994+ attributesQueryValue,
1995+ teamId : testFixtures . team . id ,
1996+ orgId : testFixtures . org . id ,
1997+ } ) ;
1998+
1999+ expect ( result ) . toEqual (
2000+ expect . arrayContaining ( [
2001+ { userId : createdUsers [ 1 ] . userId , result : RaqbLogicResult . MATCH } ,
2002+ { userId : createdUsers [ 2 ] . userId , result : RaqbLogicResult . MATCH } ,
2003+ ] )
2004+ ) ;
2005+ expect ( result ) . not . toContainEqual ( { userId : createdUsers [ 0 ] . userId , result : RaqbLogicResult . MATCH } ) ;
2006+ } ) ;
2007+ } ) ;
2008+
2009+ describe ( "TEXT not_like" , ( ) => {
2010+ it ( "should match users without the attribute (undefined not contains 'engineer' is true)" , async ( ) => {
2011+ const JobTitleAttribute = {
2012+ id : "job-title-attr-2" ,
2013+ name : "Job Title" ,
2014+ type : AttributeType . TEXT ,
2015+ slug : "job-title-2" ,
2016+ options : [
2017+ { id : "job-sr-eng" , value : "Senior Engineer" , slug : "senior-engineer" } ,
2018+ { id : "job-designer" , value : "Designer" , slug : "designer" } ,
2019+ ] ,
2020+ } ;
2021+
2022+ const { createdUsers } = await createAttributesScenario ( {
2023+ attributes : [ JobTitleAttribute ] ,
2024+ teamMembersWithAttributeOptionValuePerAttribute : [
2025+ { attributes : { [ JobTitleAttribute . id ] : "Senior Engineer" } } ,
2026+ { attributes : { [ JobTitleAttribute . id ] : "Designer" } } ,
2027+ { attributes : { } } ,
2028+ ] ,
2029+ } ) ;
2030+
2031+ const attributesQueryValue = buildQueryValue ( {
2032+ rules : [
2033+ {
2034+ raqbFieldId : JobTitleAttribute . id ,
2035+ value : [ "engineer" ] ,
2036+ operator : "not_like" ,
2037+ valueSrc : [ "value" ] ,
2038+ valueType : [ "text" ] ,
2039+ } ,
2040+ ] ,
2041+ } ) as AttributesQueryValue ;
2042+
2043+ const { teamMembersMatchingAttributeLogic : result } = await findTeamMembersMatchingAttributeLogic ( {
2044+ dynamicFieldValueOperands : { fields : [ ] , response : { } } ,
2045+ attributesQueryValue,
2046+ teamId : testFixtures . team . id ,
2047+ orgId : testFixtures . org . id ,
2048+ } ) ;
2049+
2050+ expect ( result ) . toEqual (
2051+ expect . arrayContaining ( [
2052+ { userId : createdUsers [ 1 ] . userId , result : RaqbLogicResult . MATCH } ,
2053+ { userId : createdUsers [ 2 ] . userId , result : RaqbLogicResult . MATCH } ,
2054+ ] )
2055+ ) ;
2056+ expect ( result ) . not . toContainEqual ( { userId : createdUsers [ 0 ] . userId , result : RaqbLogicResult . MATCH } ) ;
2057+ } ) ;
2058+ } ) ;
2059+
2060+ describe ( "NUMBER not_equal" , ( ) => {
2061+ it ( "should match users without the attribute (undefined != '5' is true)" , async ( ) => {
2062+ const ExperienceAttribute = {
2063+ id : "exp-attr" ,
2064+ name : "Experience Years" ,
2065+ type : AttributeType . NUMBER ,
2066+ slug : "experience-years" ,
2067+ options : [
2068+ { id : "exp-5" , value : "5" , slug : "5" } ,
2069+ { id : "exp-10" , value : "10" , slug : "10" } ,
2070+ ] ,
2071+ } ;
2072+
2073+ const { createdUsers } = await createAttributesScenario ( {
2074+ attributes : [ ExperienceAttribute ] ,
2075+ teamMembersWithAttributeOptionValuePerAttribute : [
2076+ { attributes : { [ ExperienceAttribute . id ] : "5" } } ,
2077+ { attributes : { [ ExperienceAttribute . id ] : "10" } } ,
2078+ { attributes : { } } ,
2079+ ] ,
2080+ } ) ;
2081+
2082+ const attributesQueryValue = buildQueryValue ( {
2083+ rules : [
2084+ {
2085+ raqbFieldId : ExperienceAttribute . id ,
2086+ value : [ "5" ] ,
2087+ operator : "not_equal" ,
2088+ valueSrc : [ "value" ] ,
2089+ valueType : [ "number" ] ,
2090+ } ,
2091+ ] ,
2092+ } ) as AttributesQueryValue ;
2093+
2094+ const { teamMembersMatchingAttributeLogic : result } = await findTeamMembersMatchingAttributeLogic ( {
2095+ dynamicFieldValueOperands : { fields : [ ] , response : { } } ,
2096+ attributesQueryValue,
2097+ teamId : testFixtures . team . id ,
2098+ orgId : testFixtures . org . id ,
2099+ } ) ;
2100+
2101+ expect ( result ) . toEqual (
2102+ expect . arrayContaining ( [
2103+ { userId : createdUsers [ 1 ] . userId , result : RaqbLogicResult . MATCH } ,
2104+ { userId : createdUsers [ 2 ] . userId , result : RaqbLogicResult . MATCH } ,
2105+ ] )
2106+ ) ;
2107+ expect ( result ) . not . toContainEqual ( { userId : createdUsers [ 0 ] . userId , result : RaqbLogicResult . MATCH } ) ;
2108+ } ) ;
2109+ } ) ;
2110+ } ) ;
2111+
2112+ describe ( "compound rules with users missing some attributes" , ( ) => {
2113+ const DepartmentAttribute = {
2114+ id : "dept-attr-2" ,
2115+ name : "Department" ,
2116+ type : AttributeType . SINGLE_SELECT ,
2117+ slug : "department-2" ,
2118+ options : [
2119+ { id : "dept-sales-2" , value : "Sales" , slug : "sales" } ,
2120+ { id : "dept-eng-2" , value : "Engineering" , slug : "engineering" } ,
2121+ ] ,
2122+ } ;
2123+
2124+ const LocationAttribute = {
2125+ id : "loc-attr-2" ,
2126+ name : "Location" ,
2127+ type : AttributeType . SINGLE_SELECT ,
2128+ slug : "location-2" ,
2129+ options : [
2130+ { id : "loc-nyc-2" , value : "NYC" , slug : "nyc" } ,
2131+ { id : "loc-la-2" , value : "LA" , slug : "la" } ,
2132+ ] ,
2133+ } ;
2134+
2135+ it ( "positive AND negation: should match user with one attribute but missing the negated one" , async ( ) => {
2136+ const { createdUsers } = await createAttributesScenario ( {
2137+ attributes : [ DepartmentAttribute , LocationAttribute ] ,
2138+ teamMembersWithAttributeOptionValuePerAttribute : [
2139+ {
2140+ attributes : { [ DepartmentAttribute . id ] : "Sales" , [ LocationAttribute . id ] : "NYC" } ,
2141+ } ,
2142+ { attributes : { [ DepartmentAttribute . id ] : "Sales" } } ,
2143+ { attributes : { } } ,
2144+ ] ,
2145+ } ) ;
2146+
2147+ const attributesQueryValue = buildSelectTypeFieldQueryValue ( {
2148+ rules : [
2149+ {
2150+ raqbFieldId : DepartmentAttribute . id ,
2151+ value : [ "dept-sales-2" ] ,
2152+ operator : "select_equals" ,
2153+ } ,
2154+ {
2155+ raqbFieldId : LocationAttribute . id ,
2156+ value : [ "loc-nyc-2" ] ,
2157+ operator : "select_not_equals" ,
2158+ } ,
2159+ ] ,
2160+ } ) as AttributesQueryValue ;
2161+
2162+ const { teamMembersMatchingAttributeLogic : result } = await findTeamMembersMatchingAttributeLogic ( {
2163+ dynamicFieldValueOperands : { fields : [ ] , response : { } } ,
2164+ attributesQueryValue,
2165+ teamId : testFixtures . team . id ,
2166+ orgId : testFixtures . org . id ,
2167+ } ) ;
2168+
2169+ // User 0: Dept=Sales (== matches) AND Location=NYC (!= fails) -> NO MATCH
2170+ // User 1: Dept=Sales (== matches) AND Location=undefined (!= matches) -> MATCH
2171+ // User 2: no Dept (== fails) -> NO MATCH (AND short-circuits)
2172+ expect ( result ) . toEqual ( [ { userId : createdUsers [ 1 ] . userId , result : RaqbLogicResult . MATCH } ] ) ;
2173+ } ) ;
2174+
2175+ it ( "multiple negation rules: should match users missing different attributes" , async ( ) => {
2176+ const { createdUsers } = await createAttributesScenario ( {
2177+ attributes : [ DepartmentAttribute , LocationAttribute ] ,
2178+ teamMembersWithAttributeOptionValuePerAttribute : [
2179+ {
2180+ attributes : { [ DepartmentAttribute . id ] : "Sales" , [ LocationAttribute . id ] : "NYC" } ,
2181+ } ,
2182+ { attributes : { [ DepartmentAttribute . id ] : "Engineering" } } ,
2183+ { attributes : { [ LocationAttribute . id ] : "LA" } } ,
2184+ { attributes : { } } ,
2185+ ] ,
2186+ } ) ;
2187+
2188+ const attributesQueryValue = buildSelectTypeFieldQueryValue ( {
2189+ rules : [
2190+ {
2191+ raqbFieldId : DepartmentAttribute . id ,
2192+ value : [ "dept-sales-2" ] ,
2193+ operator : "select_not_equals" ,
2194+ } ,
2195+ {
2196+ raqbFieldId : LocationAttribute . id ,
2197+ value : [ "loc-nyc-2" ] ,
2198+ operator : "select_not_equals" ,
2199+ } ,
2200+ ] ,
2201+ } ) as AttributesQueryValue ;
2202+
2203+ const { teamMembersMatchingAttributeLogic : result } = await findTeamMembersMatchingAttributeLogic ( {
2204+ dynamicFieldValueOperands : { fields : [ ] , response : { } } ,
2205+ attributesQueryValue,
2206+ teamId : testFixtures . team . id ,
2207+ orgId : testFixtures . org . id ,
2208+ } ) ;
2209+
2210+ // User 0: Dept=Sales (!= fails) -> NO MATCH
2211+ // User 1: Dept=Engineering (!= matches) AND Location=undefined (!= matches) -> MATCH
2212+ // User 2: Dept=undefined (!= matches) AND Location=LA (!= matches) -> MATCH
2213+ // User 3: both undefined (both != match) -> MATCH
2214+ expect ( result ) . toEqual (
2215+ expect . arrayContaining ( [
2216+ { userId : createdUsers [ 1 ] . userId , result : RaqbLogicResult . MATCH } ,
2217+ { userId : createdUsers [ 2 ] . userId , result : RaqbLogicResult . MATCH } ,
2218+ { userId : createdUsers [ 3 ] . userId , result : RaqbLogicResult . MATCH } ,
2219+ ] )
2220+ ) ;
2221+ expect ( result ) . not . toContainEqual ( { userId : createdUsers [ 0 ] . userId , result : RaqbLogicResult . MATCH } ) ;
2222+ } ) ;
2223+ } ) ;
19562224} ) ;
0 commit comments