@@ -1052,23 +1052,244 @@ describe('#components/Builder', () => {
10521052 />
10531053 ) ;
10541054
1055- const previousTextValue = (
1056- getByDataTest ( container , 'CustomTextModeEditorInput' ) as HTMLTextAreaElement
1057- ) . value ;
1058-
10591055 fireEvent . change ( getByDataTest ( container , 'CustomTextModeEditorInput' ) , {
10601056 target : { value : '(MOCK_NUMBER != 8)' } ,
10611057 } ) ;
10621058
10631059 expect ( onChange ) . not . toHaveBeenCalled ( ) ;
10641060 expect ( getByDataTest ( container , 'CustomTextModeEditorInput' ) ) . toHaveValue (
1065- previousTextValue
1061+ '(MOCK_NUMBER != 8)'
10661062 ) ;
10671063 expect ( getByDataTest ( container , 'CustomTextModeEditorError' ) ) . toHaveTextContent (
10681064 'One or more read-only clauses cannot be changed or removed in text mode.'
10691065 ) ;
10701066 } ) ;
10711067
1068+ it ( 'allows adding another rule with the same locked field and operator in text mode' , async ( ) => {
1069+ const onChange = jest . fn ( ) ;
1070+ const { container } = render (
1071+ < Builder
1072+ fields = { fields }
1073+ data = { [
1074+ {
1075+ type : 'GROUP' ,
1076+ value : 'AND' ,
1077+ isNegated : false ,
1078+ children : [
1079+ {
1080+ field : 'MOCK_FIELD' ,
1081+ value : 'alpha' ,
1082+ operator : 'EQUAL' ,
1083+ readOnly : {
1084+ enabled : true ,
1085+ targets : [ 'field' , 'operator' ] ,
1086+ } ,
1087+ } ,
1088+ {
1089+ field : 'MOCK_NUMBER' ,
1090+ value : 5 ,
1091+ operator : 'NOT_EQUAL' ,
1092+ } ,
1093+ ] ,
1094+ } ,
1095+ ] }
1096+ textMode
1097+ defaultMode = "text"
1098+ components = { {
1099+ ...defaultComponents ,
1100+ TextModeEditor : CustomTextModeEditor ,
1101+ } }
1102+ onChange = { onChange }
1103+ />
1104+ ) ;
1105+
1106+ fireEvent . change ( getByDataTest ( container , 'CustomTextModeEditorInput' ) , {
1107+ target : {
1108+ value :
1109+ "(MOCK_FIELD = 'alpha' AND MOCK_FIELD = 'beta' AND MOCK_NUMBER != 5)" ,
1110+ } ,
1111+ } ) ;
1112+
1113+ await waitFor ( ( ) =>
1114+ expect ( onChange ) . toHaveBeenLastCalledWith ( [
1115+ {
1116+ type : 'GROUP' ,
1117+ value : 'AND' ,
1118+ isNegated : false ,
1119+ children : [
1120+ {
1121+ field : 'MOCK_FIELD' ,
1122+ value : 'alpha' ,
1123+ operator : 'EQUAL' ,
1124+ readOnly : {
1125+ enabled : true ,
1126+ targets : [ 'field' , 'operator' ] ,
1127+ } ,
1128+ } ,
1129+ {
1130+ field : 'MOCK_FIELD' ,
1131+ value : 'beta' ,
1132+ operator : 'EQUAL' ,
1133+ } ,
1134+ {
1135+ field : 'MOCK_NUMBER' ,
1136+ value : 5 ,
1137+ operator : 'NOT_EQUAL' ,
1138+ } ,
1139+ ] ,
1140+ } ,
1141+ ] )
1142+ ) ;
1143+
1144+ expect ( queryByDataTest ( container , 'CustomTextModeEditorError' ) ) . toBeNull ( ) ;
1145+ } ) ;
1146+
1147+ it ( 'allows adding another rule next to a targeted lock when another identical read-only rule exists elsewhere' , async ( ) => {
1148+ const onChange = jest . fn ( ) ;
1149+ const { container } = render (
1150+ < Builder
1151+ fields = { fields }
1152+ data = { [
1153+ {
1154+ type : 'GROUP' ,
1155+ value : 'AND' ,
1156+ isNegated : false ,
1157+ children : [
1158+ {
1159+ field : 'MOCK_FIELD' ,
1160+ value : 'alpha' ,
1161+ operator : 'EQUAL' ,
1162+ readOnly : {
1163+ enabled : true ,
1164+ targets : [ 'field' , 'operator' ] ,
1165+ } ,
1166+ } ,
1167+ {
1168+ type : 'GROUP' ,
1169+ value : 'OR' ,
1170+ isNegated : false ,
1171+ children : [
1172+ {
1173+ field : 'MOCK_FIELD' ,
1174+ value : 'gamma' ,
1175+ operator : 'EQUAL' ,
1176+ } ,
1177+ {
1178+ field : 'MOCK_NUMBER' ,
1179+ value : 5 ,
1180+ operator : 'NOT_EQUAL' ,
1181+ } ,
1182+ ] ,
1183+ } ,
1184+ {
1185+ type : 'GROUP' ,
1186+ value : 'AND' ,
1187+ isNegated : false ,
1188+ children : [
1189+ {
1190+ field : 'MOCK_FIELD' ,
1191+ value : 'gamma' ,
1192+ operator : 'EQUAL' ,
1193+ readOnly : true ,
1194+ } ,
1195+ {
1196+ field : 'MOCK_NUMBER' ,
1197+ value : 8 ,
1198+ operator : 'NOT_EQUAL' ,
1199+ } ,
1200+ ] ,
1201+ } ,
1202+ ] ,
1203+ } ,
1204+ ] }
1205+ textMode
1206+ defaultMode = "text"
1207+ components = { {
1208+ ...defaultComponents ,
1209+ TextModeEditor : CustomTextModeEditor ,
1210+ } }
1211+ onChange = { onChange }
1212+ />
1213+ ) ;
1214+
1215+ fireEvent . change ( getByDataTest ( container , 'CustomTextModeEditorInput' ) , {
1216+ target : {
1217+ value :
1218+ "((MOCK_FIELD = 'alpha' AND MOCK_FIELD = 'beta') AND (MOCK_FIELD = 'gamma' OR MOCK_NUMBER != 5) AND (MOCK_FIELD = 'gamma' AND MOCK_NUMBER != 8))" ,
1219+ } ,
1220+ } ) ;
1221+
1222+ await waitFor ( ( ) =>
1223+ expect ( onChange ) . toHaveBeenLastCalledWith ( [
1224+ {
1225+ type : 'GROUP' ,
1226+ value : 'AND' ,
1227+ isNegated : false ,
1228+ children : [
1229+ {
1230+ type : 'GROUP' ,
1231+ value : 'AND' ,
1232+ isNegated : false ,
1233+ children : [
1234+ {
1235+ field : 'MOCK_FIELD' ,
1236+ value : 'alpha' ,
1237+ operator : 'EQUAL' ,
1238+ readOnly : {
1239+ enabled : true ,
1240+ targets : [ 'field' , 'operator' ] ,
1241+ } ,
1242+ } ,
1243+ {
1244+ field : 'MOCK_FIELD' ,
1245+ value : 'beta' ,
1246+ operator : 'EQUAL' ,
1247+ } ,
1248+ ] ,
1249+ } ,
1250+ {
1251+ type : 'GROUP' ,
1252+ value : 'OR' ,
1253+ isNegated : false ,
1254+ children : [
1255+ {
1256+ field : 'MOCK_FIELD' ,
1257+ value : 'gamma' ,
1258+ operator : 'EQUAL' ,
1259+ } ,
1260+ {
1261+ field : 'MOCK_NUMBER' ,
1262+ value : 5 ,
1263+ operator : 'NOT_EQUAL' ,
1264+ } ,
1265+ ] ,
1266+ } ,
1267+ {
1268+ type : 'GROUP' ,
1269+ value : 'AND' ,
1270+ isNegated : false ,
1271+ children : [
1272+ {
1273+ field : 'MOCK_FIELD' ,
1274+ value : 'gamma' ,
1275+ operator : 'EQUAL' ,
1276+ readOnly : true ,
1277+ } ,
1278+ {
1279+ field : 'MOCK_NUMBER' ,
1280+ value : 8 ,
1281+ operator : 'NOT_EQUAL' ,
1282+ } ,
1283+ ] ,
1284+ } ,
1285+ ] ,
1286+ } ,
1287+ ] )
1288+ ) ;
1289+
1290+ expect ( queryByDataTest ( container , 'CustomTextModeEditorError' ) ) . toBeNull ( ) ;
1291+ } ) ;
1292+
10721293 it ( 'allows deleting a group with a read-only descendant in text mode when readOnlyProtectsDelete is false' , async ( ) => {
10731294 const onChange = jest . fn ( ) ;
10741295 const { container } = render (
@@ -1755,6 +1976,55 @@ describe('#components/Builder', () => {
17551976 } ) ;
17561977 } ) ;
17571978
1979+ it ( 'Keeps exact local spacing for valid text-mode edits after a combinator' , async ( ) => {
1980+ const onChange = jest . fn ( ) ;
1981+ const { container } = render (
1982+ < Builder
1983+ fields = { fields }
1984+ data = { [
1985+ {
1986+ type : 'GROUP' ,
1987+ value : 'AND' ,
1988+ isNegated : false ,
1989+ children : [
1990+ { field : 'MOCK_FIELD' , value : 'alpha' , operator : 'EQUAL' } ,
1991+ { field : 'MOCK_NUMBER' , value : 2 , operator : 'EQUAL' } ,
1992+ ] ,
1993+ } ,
1994+ ] }
1995+ textMode
1996+ defaultMode = "text"
1997+ components = { {
1998+ ...defaultComponents ,
1999+ TextModeEditor : CustomTextModeEditor ,
2000+ } }
2001+ onChange = { onChange }
2002+ />
2003+ ) ;
2004+
2005+ fireEvent . change ( getByDataTest ( container , 'CustomTextModeEditorInput' ) , {
2006+ target : { value : "(MOCK_FIELD = 'alpha' AND MOCK_NUMBER = 3)" } ,
2007+ } ) ;
2008+
2009+ await waitFor ( ( ) =>
2010+ expect ( onChange ) . toHaveBeenLastCalledWith ( [
2011+ {
2012+ type : 'GROUP' ,
2013+ value : 'AND' ,
2014+ isNegated : false ,
2015+ children : [
2016+ { field : 'MOCK_FIELD' , value : 'alpha' , operator : 'EQUAL' } ,
2017+ { field : 'MOCK_NUMBER' , value : 3 , operator : 'EQUAL' } ,
2018+ ] ,
2019+ } ,
2020+ ] )
2021+ ) ;
2022+
2023+ expect ( getByDataTest ( container , 'CustomTextModeEditorInput' ) ) . toHaveValue (
2024+ "(MOCK_FIELD = 'alpha' AND MOCK_NUMBER = 3)"
2025+ ) ;
2026+ } ) ;
2027+
17582028 it ( 'Clones a rule directly below the original rule' , ( ) => {
17592029 const onChange = jest . fn ( ) ;
17602030 const { container } = render (
0 commit comments