@@ -34,6 +34,7 @@ import { genCellKey } from './utils';
3434import sampleSetQueryInfoJSON from '../../../test/data/sampleSetAllFieldTypes-getQueryDetails.json' ;
3535import { MockEditableGridLoader } from './utils.test' ;
3636import { MULTI_CHOICE_TYPE } from '../domainproperties/PropDescType' ;
37+ import { joinMultiValueForExport } from '../../util/utils' ;
3738
3839describe ( 'column mutation actions' , ( ) => {
3940 const queryInfo = QueryInfo . fromJsonForTests ( sampleSet2QueryInfo ) ;
@@ -859,7 +860,7 @@ describe('insertPastedData', () => {
859860 fieldKey : mvtc ,
860861 jsonType : 'ARRAY' ,
861862 rangeURI : MULTI_CHOICE_TYPE . rangeURI ,
862- validValues : [ 'a' , 'ab' , 'cc' , 'cD' , 'A,B' , 'de' ] ,
863+ validValues : [ 'a' , 'ab' , 'cc' , 'cD' , 'A,B' , 'de' , 'A' , 'B' , 'C' , 'A,B,C' , '"A"' , '"A",B' , '"A,B,C"' ] ,
863864 } ) ,
864865 } ,
865866 } ) ;
@@ -1175,6 +1176,235 @@ describe('insertPastedData', () => {
11751176 expect ( cellMessages . get ( genCellKey ( mvtc , 1 ) ) ) . toBeUndefined ( ) ;
11761177 expect ( cellMessages . get ( genCellKey ( mvtc , 2 ) ) ) . toEqual ( { message : 'Could not find "bad"' } ) ;
11771178 } ) ;
1179+
1180+ test ( 'pasting string values with special characters, fromDragFill false' , async ( ) => {
1181+ const em = baseEditorModel . applyChanges ( {
1182+ selectionCells : [ genCellKey ( fkOne , 0 ) , genCellKey ( fkOne , 1 ) , genCellKey ( fkOne , 2 ) ] ,
1183+ selectedColIdx : 0 ,
1184+ selectedRowIdx : 2 ,
1185+ } ) ;
1186+ const changes = await validateAndInsertPastedData (
1187+ em ,
1188+ 'hello world\n"hello, world"\n"say ""hello"""' ,
1189+ undefined ,
1190+ true ,
1191+ true ,
1192+ undefined ,
1193+ true
1194+ ) ;
1195+ const cellValues = changes . cellValues ;
1196+ // Space is preserved as-is
1197+ expect ( cellValues . get ( genCellKey ( fkOne , 0 ) ) ) . toEqual ( List ( [ { display : 'hello world' , raw : 'hello world' } ] ) ) ;
1198+ // Quoted comma: without fromDragFill, CSV quoting is NOT stripped
1199+ expect ( cellValues . get ( genCellKey ( fkOne , 1 ) ) ) . toEqual (
1200+ List ( [ { display : '"hello, world"' , raw : '"hello, world"' } ] )
1201+ ) ;
1202+ // Escaped double quotes: without fromDragFill, CSV escaping is NOT processed
1203+ expect ( cellValues . get ( genCellKey ( fkOne , 2 ) ) ) . toEqual (
1204+ List ( [ { display : '"say ""hello"""' , raw : '"say ""hello"""' } ] )
1205+ ) ;
1206+ } ) ;
1207+
1208+ test ( 'drag fill string values with special characters, fromDragFill true' , async ( ) => {
1209+ const em = baseEditorModel . applyChanges ( {
1210+ selectionCells : [
1211+ genCellKey ( fkOne , 0 ) ,
1212+ genCellKey ( fkOne , 1 ) ,
1213+ genCellKey ( fkOne , 2 ) ,
1214+ genCellKey ( fkOne , 3 ) ,
1215+ genCellKey ( fkOne , 4 ) ,
1216+ genCellKey ( fkOne , 5 ) ,
1217+ ] ,
1218+ selectedColIdx : 0 ,
1219+ selectedRowIdx : 5 ,
1220+ } ) ;
1221+ const changes = await validateAndInsertPastedData (
1222+ em ,
1223+ 'hello world\n"hello, world"\n"say ""hello"""' ,
1224+ undefined ,
1225+ true ,
1226+ true ,
1227+ undefined ,
1228+ false ,
1229+ [ [ genCellKey ( fkOne , 3 ) , genCellKey ( fkOne , 4 ) , genCellKey ( fkOne , 5 ) ] ] ,
1230+ true
1231+ ) ;
1232+ const cellValues = changes . cellValues ;
1233+ // Original values unchanged
1234+ expect ( cellValues . get ( genCellKey ( fkOne , 0 ) ) ) . toEqual ( List ( [ { display : 'qwer' , raw : 'qwer' } ] ) ) ;
1235+ expect ( cellValues . get ( genCellKey ( fkOne , 1 ) ) ) . toEqual ( List ( [ { display : 'asdf' , raw : 'asdf' } ] ) ) ;
1236+ expect ( cellValues . get ( genCellKey ( fkOne , 2 ) ) ) . toEqual ( List ( [ { display : 'zxcv' , raw : 'zxcv' } ] ) ) ;
1237+ // Space: no CSV quoting to strip
1238+ expect ( cellValues . get ( genCellKey ( fkOne , 3 ) ) ) . toEqual ( List ( [ { display : 'hello world' , raw : 'hello world' } ] ) ) ;
1239+ // Quoted comma: fromDragFill strips CSV quoting, comma preserved in value
1240+ expect ( cellValues . get ( genCellKey ( fkOne , 4 ) ) ) . toEqual ( List ( [ { display : 'hello, world' , raw : 'hello, world' } ] ) ) ;
1241+ // Escaped double quotes: fromDragFill strips CSV quoting and unescapes ""
1242+ expect ( cellValues . get ( genCellKey ( fkOne , 5 ) ) ) . toEqual ( List ( [ { display : 'say "hello"' , raw : 'say "hello"' } ] ) ) ;
1243+ } ) ;
1244+
1245+ test ( 'pasting exactly A,B into mvtc matches single valid value' , async ( ) => {
1246+ const em = baseEditorModel . applyChanges ( {
1247+ selectionCells : [ genCellKey ( mvtc , 0 ) ] ,
1248+ selectedColIdx : 3 ,
1249+ selectedRowIdx : 0 ,
1250+ } ) ;
1251+ const changes = await validateAndInsertPastedData ( em , 'A,B' , undefined , true , true , undefined , true ) ;
1252+ // 'A,B' exactly matches a validValue, treated as a single value (not split on comma)
1253+ expect ( changes . cellValues . get ( genCellKey ( mvtc , 0 ) ) ) . toEqual ( List ( [ { display : 'A,B' , raw : 'A,B' } ] ) ) ;
1254+ expect ( changes . cellMessages . get ( genCellKey ( mvtc , 0 ) ) ) . toBeUndefined ( ) ;
1255+ } ) ;
1256+
1257+ test ( 'pasting exactly A,B,C without quote into mvtc matches single valid value' , async ( ) => {
1258+ const em = baseEditorModel . applyChanges ( {
1259+ selectionCells : [ genCellKey ( mvtc , 0 ) ] ,
1260+ selectedColIdx : 3 ,
1261+ selectedRowIdx : 0 ,
1262+ } ) ;
1263+ const changes = await validateAndInsertPastedData ( em , 'A,B,C' , undefined , true , true , undefined , true ) ;
1264+ // 'A,B' exactly matches a validValue, treated as a single value (not split on comma)
1265+ expect ( changes . cellValues . get ( genCellKey ( mvtc , 0 ) ) ) . toEqual ( List ( [ { display : 'A,B,C' , raw : 'A,B,C' } ] ) ) ;
1266+ expect ( changes . cellMessages . get ( genCellKey ( mvtc , 0 ) ) ) . toBeUndefined ( ) ;
1267+ } ) ;
1268+
1269+ test ( 'pasting A, B with space into mvtc parses as two values' , async ( ) => {
1270+ const em = baseEditorModel . applyChanges ( {
1271+ selectionCells : [ genCellKey ( mvtc , 0 ) ] ,
1272+ selectedColIdx : 3 ,
1273+ selectedRowIdx : 0 ,
1274+ } ) ;
1275+ const changes = await validateAndInsertPastedData ( em , 'A, B' , undefined , true , true , undefined , true ) ;
1276+ // 'A, B' does not match any validValue, so parsed as CSV → ' B' and 'A' (sorted with leading space)
1277+ expect ( changes . cellValues . get ( genCellKey ( mvtc , 0 ) ) ) . toEqual (
1278+ List ( [
1279+ { display : 'A' , raw : 'A' } ,
1280+ { display : 'B' , raw : 'B' }
1281+ ] )
1282+ ) ;
1283+ expect ( changes . cellMessages . get ( genCellKey ( mvtc , 0 ) ) ) . toBeUndefined ( ) ;
1284+ } ) ;
1285+
1286+ test ( 'pasting quoted "A,B" into mvtc treats as single value' , async ( ) => {
1287+ const em = baseEditorModel . applyChanges ( {
1288+ selectionCells : [ genCellKey ( mvtc , 0 ) ] ,
1289+ selectedColIdx : 3 ,
1290+ selectedRowIdx : 0 ,
1291+ } ) ;
1292+ const changes = await validateAndInsertPastedData ( em , '"A,B"' , undefined , true , true , undefined , true ) ;
1293+ // Quoted '"A,B"' is CSV-parsed to 'A,B' which is a valid value
1294+ expect ( changes . cellValues . get ( genCellKey ( mvtc , 0 ) ) ) . toEqual ( List ( [ { display : 'A,B' , raw : 'A,B' } ] ) ) ;
1295+ expect ( changes . cellMessages . get ( genCellKey ( mvtc , 0 ) ) ) . toBeUndefined ( ) ;
1296+ } ) ;
1297+
1298+ test ( 'pasting quoted "A, B" into mvtc, invalid' , async ( ) => {
1299+ const em = baseEditorModel . applyChanges ( {
1300+ selectionCells : [ genCellKey ( mvtc , 0 ) ] ,
1301+ selectedColIdx : 3 ,
1302+ selectedRowIdx : 0 ,
1303+ } ) ;
1304+ const changes = await validateAndInsertPastedData ( em , '"A, B"' , undefined , true , true , undefined , true ) ;
1305+ // Quoted '"A,B"' is CSV-parsed to 'A,B' which is a valid value
1306+ expect ( changes . cellValues . get ( genCellKey ( mvtc , 0 ) ) ) . toEqual ( List ( [ { display : 'A, B' , raw : 'A, B' } ] ) ) ;
1307+ expect ( changes . cellMessages . get ( genCellKey ( mvtc , 0 ) ) ) . toEqual ( {
1308+ message : 'Could not find "A, B". Please make sure values that contain commas are properly quoted.' ,
1309+ } ) ;
1310+ } ) ;
1311+
1312+ test ( 'pasting mvtc values combined with other valid values' , async ( ) => {
1313+ const em = baseEditorModel . applyChanges ( {
1314+ selectionCells : [ genCellKey ( mvtc , 0 ) , genCellKey ( mvtc , 1 ) , genCellKey ( mvtc , 2 ) ] ,
1315+ selectedColIdx : 3 ,
1316+ selectedRowIdx : 2 ,
1317+ } ) ;
1318+ const changes = await validateAndInsertPastedData (
1319+ em ,
1320+ 'A,B\n"A,B",cc\nA,B,cc' ,
1321+ undefined ,
1322+ true ,
1323+ true ,
1324+ undefined ,
1325+ true
1326+ ) ;
1327+ const cellValues = changes . cellValues ;
1328+ // Row 0: 'A,B' exactly matches single validValue
1329+ expect ( cellValues . get ( genCellKey ( mvtc , 0 ) ) ) . toEqual ( List ( [ { display : 'A,B' , raw : 'A,B' } ] ) ) ;
1330+ expect ( changes . cellMessages . get ( genCellKey ( mvtc , 0 ) ) ) . toBeUndefined ( ) ;
1331+ // Row 1: '"A,B",cc' → CSV parsed to ['A,B', 'cc'], both valid
1332+ expect ( cellValues . get ( genCellKey ( mvtc , 1 ) ) ) . toEqual (
1333+ List ( [
1334+ { display : 'A,B' , raw : 'A,B' } ,
1335+ { display : 'cc' , raw : 'cc' } ,
1336+ ] )
1337+ ) ;
1338+ expect ( changes . cellMessages . get ( genCellKey ( mvtc , 1 ) ) ) . toBeUndefined ( ) ;
1339+ // Row 2: 'A,B,cc' without quotes → CSV parsed to ['A', 'B', 'cc'], all valid
1340+ expect ( cellValues . get ( genCellKey ( mvtc , 2 ) ) ) . toEqual (
1341+ List ( [
1342+ { display : 'A' , raw : 'A' } ,
1343+ { display : 'B' , raw : 'B' } ,
1344+ { display : 'cc' , raw : 'cc' } ,
1345+ ] )
1346+ ) ;
1347+ expect ( changes . cellMessages . get ( genCellKey ( mvtc , 2 ) ) ) . toBeUndefined ( ) ;
1348+ } ) ;
1349+
1350+ test ( 'pasting mvtc values combined with invalid values' , async ( ) => {
1351+ const em = baseEditorModel . applyChanges ( {
1352+ selectionCells : [ genCellKey ( mvtc , 0 ) , genCellKey ( mvtc , 1 ) ] ,
1353+ selectedColIdx : 3 ,
1354+ selectedRowIdx : 1 ,
1355+ } ) ;
1356+ const changes = await validateAndInsertPastedData (
1357+ em ,
1358+ 'A, B, bad\n"A,B",bad' ,
1359+ undefined ,
1360+ true ,
1361+ true ,
1362+ undefined ,
1363+ true
1364+ ) ;
1365+ const cellValues = changes . cellValues ;
1366+ const cellMessages = changes . cellMessages ;
1367+ // Row 0: 'A,B,bad' → CSV parsed to ['A', 'B', 'bad'], 'bad' invalid
1368+ expect ( cellValues . get ( genCellKey ( mvtc , 0 ) ) ) . toEqual (
1369+ List ( [
1370+ { display : 'A' , raw : 'A' } ,
1371+ { display : 'B' , raw : 'B' } ,
1372+ { display : 'bad' , raw : 'bad' } ,
1373+ ] )
1374+ ) ;
1375+ expect ( cellMessages . get ( genCellKey ( mvtc , 0 ) ) ) . toEqual ( { message : 'Could not find "bad"' } ) ;
1376+ // Row 1: '"A,B",bad' → CSV parsed to ['A,B', 'bad'], 'bad' invalid
1377+ expect ( cellValues . get ( genCellKey ( mvtc , 1 ) ) ) . toEqual (
1378+ List ( [
1379+ { display : 'A,B' , raw : 'A,B' } ,
1380+ { display : 'bad' , raw : 'bad' } ,
1381+ ] )
1382+ ) ;
1383+ expect ( cellMessages . get ( genCellKey ( mvtc , 1 ) ) ) . toEqual ( { message : 'Could not find "bad"' } ) ;
1384+ } ) ;
1385+
1386+ test . each ( [
1387+ { values : [ 'A' , 'B' , 'C' ] , desc : 'simple values' } ,
1388+ { values : [ '"A",B' ] , desc : 'single value with quotes and comma' } ,
1389+ { values : [ '"A,B,C"' ] , desc : 'single value with quotes wrapping commas' } ,
1390+ { values : [ '"A",B' , 'B' ] , desc : 'tricky + simple' } ,
1391+ { values : [ 'A' , '"A"' , 'B' ] , desc : 'quotes among simple' } ,
1392+ { values : [ 'A' , 'A,B,C' , '"A,B,C"' ] , desc : 'plain + comma + quote-containing' } ,
1393+ { values : [ '"A"' , '"A",B' , '"A,B,C"' ] , desc : 'all contain quotes' } ,
1394+ ] ) ( 'pasting multi values round-trip: $desc' , async ( { values } ) => {
1395+ const exported = joinMultiValueForExport ( values ) ;
1396+ const em = baseEditorModel . applyChanges ( {
1397+ selectionCells : [ genCellKey ( mvtc , 0 ) ] ,
1398+ selectedColIdx : 3 ,
1399+ selectedRowIdx : 0 ,
1400+ } ) ;
1401+ const changes = await validateAndInsertPastedData ( em , exported , undefined , true , true , undefined , true ) ;
1402+ const cellValues = changes . cellValues . get ( genCellKey ( mvtc , 0 ) ) ;
1403+ const sortedExpected = [ ...values ] . sort ( ) ;
1404+ const actualValues = cellValues . toArray ( ) . map ( v => v . raw ) ;
1405+ expect ( actualValues ) . toStrictEqual ( sortedExpected ) ;
1406+ expect ( changes . cellMessages . get ( genCellKey ( mvtc , 0 ) ) ) . toBeUndefined ( ) ;
1407+ } ) ;
11781408} ) ;
11791409
11801410describe ( 'loadEditorModelData' , ( ) => {
0 commit comments