chore: import upstream snapshot with attribution
This commit is contained in:
Binary file not shown.
@@ -0,0 +1,22 @@
|
||||
/* ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
* ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
* ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
* ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
* ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
* ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
* ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
* ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
* ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
* ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
* ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
*/
|
||||
|
||||
perspective-viewer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
font-family: "Roboto Mono" !important;
|
||||
--preload-fonts: "Roboto Mono:400";
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
Row ID,Order ID,Order Date,Ship Date,Ship Mode,Customer ID,Segment,Country,City,State,Postal Code,Region,Product ID,Category,Sub-Category,Sales,Quantity,Discount,Profit
|
||||
1,CA-2013-152156,11/9/2013,11/12/2013,Second Class,CG-12520,Consumer,United States,Henderson,Kentucky,42420,South,FUR-BO-10001798,Furniture,Bookcases,261.96,2,0.0,41.9136
|
||||
2,CA-2013-152156,11/9/2013,11/12/2013,Second Class,CG-12520,Consumer,United States,Henderson,Kentucky,42420,South,FUR-CH-10000454,Furniture,Chairs,731.94,3,0.0,219.582
|
||||
3,CA-2013-138688,6/13/2013,6/17/2013,Second Class,DV-13045,Corporate,United States,Los Angeles,California,90036,West,OFF-LA-10000240,Office Supplies,Labels,14.62,2,0.0,6.8714
|
||||
4,US-2012-108966,10/11/2012,10/18/2012,Standard Class,SO-20335,Consumer,United States,Fort Lauderdale,Florida,33311,South,FUR-TA-10000577,Furniture,Tables,957.5775,5,0.45,-383.031
|
||||
5,US-2012-108966,10/11/2012,10/18/2012,Standard Class,SO-20335,Consumer,United States,Fort Lauderdale,Florida,33311,South,OFF-ST-10000760,Office Supplies,Storage,22.368,2,0.2,2.5164
|
||||
6,CA-2011-115812,6/9/2011,6/14/2011,Standard Class,BH-11710,Consumer,United States,Los Angeles,California,90032,West,FUR-FU-10001487,Furniture,Furnishings,48.86,7,0.0,14.1694
|
||||
7,CA-2011-115812,6/9/2011,6/14/2011,Standard Class,BH-11710,Consumer,United States,Los Angeles,California,90032,West,OFF-AR-10002833,Office Supplies,Art,7.28,4,0.0,1.9656
|
||||
8,CA-2011-115812,6/9/2011,6/14/2011,Standard Class,BH-11710,Consumer,United States,Los Angeles,California,90032,West,TEC-PH-10002275,Technology,Phones,907.152,6,0.2,90.7152
|
||||
9,CA-2011-115812,6/9/2011,6/14/2011,Standard Class,BH-11710,Consumer,United States,Los Angeles,California,90032,West,OFF-BI-10003910,Office Supplies,Binders,18.504,3,0.2,5.7825
|
||||
10,CA-2011-115812,6/9/2011,6/14/2011,Standard Class,BH-11710,Consumer,United States,Los Angeles,California,90032,West,OFF-AP-10002892,Office Supplies,Appliances,114.9,5,0.0,34.47
|
||||
11,CA-2011-115812,6/9/2011,6/14/2011,Standard Class,BH-11710,Consumer,United States,Los Angeles,California,90032,West,FUR-TA-10001539,Furniture,Tables,1706.184,9,0.2,85.3092
|
||||
12,CA-2011-115812,6/9/2011,6/14/2011,Standard Class,BH-11710,Consumer,United States,Los Angeles,California,90032,West,TEC-PH-10002033,Technology,Phones,911.424,4,0.2,68.3568
|
||||
13,CA-2014-114412,4/16/2014,4/21/2014,Standard Class,AA-10480,Consumer,United States,Concord,North Carolina,28027,South,OFF-PA-10002365,Office Supplies,Paper,15.552,3,0.2,5.4432
|
||||
14,CA-2013-161389,12/6/2013,12/11/2013,Standard Class,IM-15070,Consumer,United States,Seattle,Washington,98103,West,OFF-BI-10003656,Office Supplies,Binders,407.976,3,0.2,132.5922
|
||||
15,US-2012-118983,11/22/2012,11/26/2012,Standard Class,HP-14815,Home Office,United States,Fort Worth,Texas,76106,Central,OFF-AP-10002311,Office Supplies,Appliances,68.81,5,0.8,-123.858
|
||||
16,US-2012-118983,11/22/2012,11/26/2012,Standard Class,HP-14815,Home Office,United States,Fort Worth,Texas,76106,Central,OFF-BI-10000756,Office Supplies,Binders,2.544,3,0.8,-3.816
|
||||
17,CA-2011-105893,11/11/2011,11/18/2011,Standard Class,PK-19075,Consumer,United States,Madison,Wisconsin,53711,Central,OFF-ST-10004186,Office Supplies,Storage,665.88,6,0.0,13.3176
|
||||
18,CA-2011-167164,5/13/2011,5/15/2011,Second Class,AG-10270,Consumer,United States,West Jordan,Utah,84084,West,OFF-ST-10000107,Office Supplies,Storage,55.5,2,0.0,9.99
|
||||
19,CA-2011-143336,8/27/2011,9/1/2011,Second Class,ZD-21925,Consumer,United States,San Francisco,California,94109,West,OFF-AR-10003056,Office Supplies,Art,8.56,2,0.0,2.4824
|
||||
20,CA-2011-143336,8/27/2011,9/1/2011,Second Class,ZD-21925,Consumer,United States,San Francisco,California,94109,West,TEC-PH-10001949,Technology,Phones,213.48,3,0.2,16.011
|
||||
21,CA-2011-143336,8/27/2011,9/1/2011,Second Class,ZD-21925,Consumer,United States,San Francisco,California,94109,West,OFF-BI-10002215,Office Supplies,Binders,22.72,4,0.2,7.384
|
||||
22,CA-2013-137330,12/10/2013,12/14/2013,Standard Class,KB-16585,Corporate,United States,Fremont,Nebraska,68025,Central,OFF-AR-10000246,Office Supplies,Art,19.46,7,0.0,5.0596
|
||||
23,CA-2013-137330,12/10/2013,12/14/2013,Standard Class,KB-16585,Corporate,United States,Fremont,Nebraska,68025,Central,OFF-AP-10001492,Office Supplies,Appliances,60.34,7,0.0,15.6884
|
||||
24,US-2014-156909,7/17/2014,7/19/2014,Second Class,SF-20065,Consumer,United States,Philadelphia,Pennsylvania,19140,East,FUR-CH-10002774,Furniture,Chairs,71.372,2,0.3,-1.0196
|
||||
25,CA-2012-106320,9/25/2012,9/30/2012,Standard Class,EB-13870,Consumer,United States,Orem,Utah,84057,West,FUR-TA-10000577,Furniture,Tables,1044.63,3,0.0,240.2649
|
||||
26,CA-2013-121755,1/16/2013,1/20/2013,Second Class,EH-13945,Consumer,United States,Los Angeles,California,90049,West,OFF-BI-10001634,Office Supplies,Binders,11.648,2,0.2,4.2224
|
||||
27,CA-2013-121755,1/16/2013,1/20/2013,Second Class,EH-13945,Consumer,United States,Los Angeles,California,90049,West,TEC-AC-10003027,Technology,Accessories,90.57,3,0.0,11.7741
|
||||
28,US-2012-150630,9/17/2012,9/21/2012,Standard Class,TB-21520,Consumer,United States,Philadelphia,Pennsylvania,19140,East,FUR-BO-10004834,Furniture,Bookcases,3083.43,7,0.5,-1665.0522
|
||||
29,US-2012-150630,9/17/2012,9/21/2012,Standard Class,TB-21520,Consumer,United States,Philadelphia,Pennsylvania,19140,East,OFF-BI-10000474,Office Supplies,Binders,9.618,2,0.7,-7.0532
|
||||
30,US-2012-150630,9/17/2012,9/21/2012,Standard Class,TB-21520,Consumer,United States,Philadelphia,Pennsylvania,19140,East,FUR-FU-10004848,Furniture,Furnishings,124.2,3,0.2,15.525
|
||||
31,US-2012-150630,9/17/2012,9/21/2012,Standard Class,TB-21520,Consumer,United States,Philadelphia,Pennsylvania,19140,East,OFF-EN-10001509,Office Supplies,Envelopes,3.264,2,0.2,1.1016
|
||||
32,US-2012-150630,9/17/2012,9/21/2012,Standard Class,TB-21520,Consumer,United States,Philadelphia,Pennsylvania,19140,East,OFF-AR-10004042,Office Supplies,Art,86.304,6,0.2,9.7092
|
||||
33,US-2012-150630,9/17/2012,9/21/2012,Standard Class,TB-21520,Consumer,United States,Philadelphia,Pennsylvania,19140,East,OFF-BI-10001525,Office Supplies,Binders,6.858,6,0.7,-5.715
|
||||
34,US-2012-150630,9/17/2012,9/21/2012,Standard Class,TB-21520,Consumer,United States,Philadelphia,Pennsylvania,19140,East,OFF-AR-10001683,Office Supplies,Art,15.76,2,0.2,3.546
|
||||
35,CA-2014-107727,10/20/2014,10/24/2014,Second Class,MA-17560,Home Office,United States,Houston,Texas,77095,Central,OFF-PA-10000249,Office Supplies,Paper,29.472,3,0.2,9.9468
|
||||
36,CA-2013-117590,12/9/2013,12/11/2013,First Class,GH-14485,Corporate,United States,Richardson,Texas,75080,Central,TEC-PH-10004977,Technology,Phones,1097.544,7,0.2,123.4737
|
||||
37,CA-2013-117590,12/9/2013,12/11/2013,First Class,GH-14485,Corporate,United States,Richardson,Texas,75080,Central,FUR-FU-10003664,Furniture,Furnishings,190.92,5,0.6,-147.963
|
||||
38,CA-2012-117415,12/27/2012,12/31/2012,Standard Class,SN-20710,Home Office,United States,Houston,Texas,77041,Central,OFF-EN-10002986,Office Supplies,Envelopes,113.328,9,0.2,35.415
|
||||
39,CA-2012-117415,12/27/2012,12/31/2012,Standard Class,SN-20710,Home Office,United States,Houston,Texas,77041,Central,FUR-BO-10002545,Furniture,Bookcases,532.3992,3,0.32,-46.9764
|
||||
40,CA-2012-117415,12/27/2012,12/31/2012,Standard Class,SN-20710,Home Office,United States,Houston,Texas,77041,Central,FUR-CH-10004218,Furniture,Chairs,212.058,3,0.3,-15.147
|
||||
41,CA-2012-117415,12/27/2012,12/31/2012,Standard Class,SN-20710,Home Office,United States,Houston,Texas,77041,Central,TEC-PH-10000486,Technology,Phones,371.168,4,0.2,41.7564
|
||||
42,CA-2014-120999,9/11/2014,9/16/2014,Standard Class,LC-16930,Corporate,United States,Naperville,Illinois,60540,Central,TEC-PH-10004093,Technology,Phones,147.168,4,0.2,16.5564
|
||||
43,CA-2013-101343,7/18/2013,7/23/2013,Standard Class,RA-19885,Corporate,United States,Los Angeles,California,90049,West,OFF-ST-10003479,Office Supplies,Storage,77.88,2,0.0,3.894
|
||||
44,CA-2014-139619,9/20/2014,9/24/2014,Standard Class,ES-14080,Corporate,United States,Melbourne,Florida,32935,South,OFF-ST-10003282,Office Supplies,Storage,95.616,2,0.2,9.5616
|
||||
45,CA-2013-118255,3/12/2013,3/14/2013,First Class,ON-18715,Corporate,United States,Eagan,Minnesota,55122,Central,TEC-AC-10000171,Technology,Accessories,45.98,2,0.0,19.7714
|
||||
46,CA-2013-118255,3/12/2013,3/14/2013,First Class,ON-18715,Corporate,United States,Eagan,Minnesota,55122,Central,OFF-BI-10003291,Office Supplies,Binders,17.46,2,0.0,8.2062
|
||||
47,CA-2011-146703,10/20/2011,10/25/2011,Second Class,PO-18865,Consumer,United States,Westland,Michigan,48185,Central,OFF-ST-10001713,Office Supplies,Storage,211.96,4,0.0,8.4784
|
||||
48,CA-2013-169194,6/21/2013,6/26/2013,Standard Class,LH-16900,Consumer,United States,Dover,Delaware,19901,East,TEC-AC-10002167,Technology,Accessories,45.0,3,0.0,4.95
|
||||
49,CA-2013-169194,6/21/2013,6/26/2013,Standard Class,LH-16900,Consumer,United States,Dover,Delaware,19901,East,TEC-PH-10003988,Technology,Phones,21.8,2,0.0,6.104
|
||||
50,CA-2012-115742,4/18/2012,4/22/2012,Standard Class,DP-13000,Consumer,United States,New Albany,Indiana,47150,Central,OFF-BI-10004410,Office Supplies,Binders,38.22,6,0.0,17.9634
|
||||
51,CA-2012-115742,4/18/2012,4/22/2012,Standard Class,DP-13000,Consumer,United States,New Albany,Indiana,47150,Central,OFF-LA-10002762,Office Supplies,Labels,75.18,6,0.0,35.3346
|
||||
52,CA-2012-115742,4/18/2012,4/22/2012,Standard Class,DP-13000,Consumer,United States,New Albany,Indiana,47150,Central,FUR-FU-10001706,Furniture,Furnishings,6.16,2,0.0,2.9568
|
||||
53,CA-2012-115742,4/18/2012,4/22/2012,Standard Class,DP-13000,Consumer,United States,New Albany,Indiana,47150,Central,FUR-CH-10003061,Furniture,Chairs,89.99,1,0.0,17.0981
|
||||
54,CA-2013-105816,12/12/2013,12/18/2013,Standard Class,JM-15265,Corporate,United States,New York City,New York,10024,East,OFF-FA-10000304,Office Supplies,Fasteners,15.26,7,0.0,6.2566
|
||||
55,CA-2013-105816,12/12/2013,12/18/2013,Standard Class,JM-15265,Corporate,United States,New York City,New York,10024,East,TEC-PH-10002447,Technology,Phones,1029.95,5,0.0,298.6855
|
||||
56,CA-2013-111682,6/18/2013,6/19/2013,First Class,TB-21055,Consumer,United States,Troy,New York,12180,East,OFF-ST-10000604,Office Supplies,Storage,208.56,6,0.0,52.14
|
||||
57,CA-2013-111682,6/18/2013,6/19/2013,First Class,TB-21055,Consumer,United States,Troy,New York,12180,East,OFF-PA-10001569,Office Supplies,Paper,32.4,5,0.0,15.552
|
||||
58,CA-2013-111682,6/18/2013,6/19/2013,First Class,TB-21055,Consumer,United States,Troy,New York,12180,East,FUR-CH-10003968,Furniture,Chairs,319.41,5,0.1,7.098
|
||||
59,CA-2013-111682,6/18/2013,6/19/2013,First Class,TB-21055,Consumer,United States,Troy,New York,12180,East,OFF-PA-10000587,Office Supplies,Paper,14.56,2,0.0,6.9888
|
||||
60,CA-2013-111682,6/18/2013,6/19/2013,First Class,TB-21055,Consumer,United States,Troy,New York,12180,East,TEC-AC-10002167,Technology,Accessories,30.0,2,0.0,3.3
|
||||
61,CA-2013-111682,6/18/2013,6/19/2013,First Class,TB-21055,Consumer,United States,Troy,New York,12180,East,OFF-BI-10001460,Office Supplies,Binders,48.48,4,0.2,16.362
|
||||
62,CA-2013-111682,6/18/2013,6/19/2013,First Class,TB-21055,Consumer,United States,Troy,New York,12180,East,OFF-AR-10001868,Office Supplies,Art,1.68,1,0.0,0.84
|
||||
63,CA-2012-135545,11/24/2012,11/30/2012,Standard Class,KM-16720,Consumer,United States,Los Angeles,California,90004,West,TEC-AC-10004633,Technology,Accessories,13.98,2,0.0,6.1512
|
||||
64,CA-2012-135545,11/24/2012,11/30/2012,Standard Class,KM-16720,Consumer,United States,Los Angeles,California,90004,West,OFF-BI-10001078,Office Supplies,Binders,25.824,6,0.2,9.3612
|
||||
65,CA-2012-135545,11/24/2012,11/30/2012,Standard Class,KM-16720,Consumer,United States,Los Angeles,California,90004,West,OFF-PA-10003892,Office Supplies,Paper,146.73,3,0.0,68.9631
|
||||
66,CA-2012-135545,11/24/2012,11/30/2012,Standard Class,KM-16720,Consumer,United States,Los Angeles,California,90004,West,FUR-FU-10000397,Furniture,Furnishings,79.76,4,0.0,22.3328
|
||||
67,US-2012-164175,4/30/2012,5/5/2012,Standard Class,PS-18970,Home Office,United States,Chicago,Illinois,60610,Central,FUR-CH-10001146,Furniture,Chairs,213.115,5,0.3,-15.2225
|
||||
68,CA-2011-106376,12/5/2011,12/10/2011,Standard Class,BS-11590,Corporate,United States,Gilbert,Arizona,85234,West,OFF-AR-10002671,Office Supplies,Art,1113.024,8,0.2,111.3024
|
||||
69,CA-2011-106376,12/5/2011,12/10/2011,Standard Class,BS-11590,Corporate,United States,Gilbert,Arizona,85234,West,TEC-PH-10002726,Technology,Phones,167.968,4,0.2,62.988
|
||||
70,CA-2013-119823,6/5/2013,6/7/2013,First Class,KD-16270,Consumer,United States,Springfield,Virginia,22153,South,OFF-PA-10000482,Office Supplies,Paper,75.88,2,0.0,35.6636
|
||||
71,CA-2013-106075,9/19/2013,9/24/2013,Standard Class,HM-14980,Consumer,United States,New York City,New York,10009,East,OFF-BI-10004654,Office Supplies,Binders,4.616,1,0.2,1.731
|
||||
72,CA-2014-114440,9/15/2014,9/18/2014,Second Class,TB-21520,Consumer,United States,Jackson,Michigan,49201,Central,OFF-PA-10004675,Office Supplies,Paper,19.05,3,0.0,8.763
|
||||
73,US-2012-134026,4/26/2012,5/2/2012,Standard Class,JE-15745,Consumer,United States,Memphis,Tennessee,38109,South,FUR-CH-10000513,Furniture,Chairs,831.936,8,0.2,-114.3912
|
||||
74,US-2012-134026,4/26/2012,5/2/2012,Standard Class,JE-15745,Consumer,United States,Memphis,Tennessee,38109,South,FUR-FU-10003708,Furniture,Furnishings,97.04,2,0.2,1.213
|
||||
75,US-2012-134026,4/26/2012,5/2/2012,Standard Class,JE-15745,Consumer,United States,Memphis,Tennessee,38109,South,OFF-ST-10004123,Office Supplies,Storage,72.784,1,0.2,-18.196
|
||||
76,US-2014-118038,12/10/2014,12/12/2014,First Class,KB-16600,Corporate,United States,Houston,Texas,77041,Central,OFF-BI-10004182,Office Supplies,Binders,1.248,3,0.8,-1.9344
|
||||
77,US-2014-118038,12/10/2014,12/12/2014,First Class,KB-16600,Corporate,United States,Houston,Texas,77041,Central,FUR-FU-10000260,Furniture,Furnishings,9.708,3,0.6,-5.8248
|
||||
78,US-2014-118038,12/10/2014,12/12/2014,First Class,KB-16600,Corporate,United States,Houston,Texas,77041,Central,OFF-ST-10000615,Office Supplies,Storage,27.24,3,0.2,2.724
|
||||
79,US-2011-147606,11/26/2011,12/1/2011,Second Class,JE-15745,Consumer,United States,Houston,Texas,77070,Central,FUR-FU-10003194,Furniture,Furnishings,19.3,5,0.6,-14.475
|
||||
80,CA-2013-127208,6/13/2013,6/16/2013,First Class,SC-20770,Corporate,United States,Decatur,Alabama,35601,South,OFF-AP-10002118,Office Supplies,Appliances,208.16,1,0.0,56.2032
|
||||
81,CA-2013-127208,6/13/2013,6/16/2013,First Class,SC-20770,Corporate,United States,Decatur,Alabama,35601,South,OFF-BI-10002309,Office Supplies,Binders,16.74,3,0.0,8.0352
|
||||
82,CA-2011-139451,10/12/2011,10/16/2011,Standard Class,DN-13690,Consumer,United States,San Francisco,California,94122,West,OFF-AR-10002053,Office Supplies,Art,14.9,5,0.0,4.172
|
||||
83,CA-2011-139451,10/12/2011,10/16/2011,Standard Class,DN-13690,Consumer,United States,San Francisco,California,94122,West,OFF-ST-10002370,Office Supplies,Storage,21.39,1,0.0,6.2031
|
||||
84,CA-2012-149734,9/3/2012,9/8/2012,Standard Class,JC-16105,Corporate,United States,Durham,North Carolina,27707,South,OFF-EN-10000927,Office Supplies,Envelopes,200.984,7,0.2,62.8075
|
||||
85,US-2014-119662,11/14/2014,11/17/2014,First Class,CS-12400,Home Office,United States,Chicago,Illinois,60623,Central,OFF-ST-10003656,Office Supplies,Storage,230.376,3,0.2,-48.9549
|
||||
86,CA-2014-140088,5/29/2014,5/31/2014,Second Class,PO-18865,Consumer,United States,Columbia,South Carolina,29203,South,FUR-CH-10000863,Furniture,Chairs,301.96,2,0.0,33.2156
|
||||
87,CA-2014-155558,10/27/2014,11/3/2014,Standard Class,PG-18895,Consumer,United States,Rochester,Minnesota,55901,Central,TEC-AC-10001998,Technology,Accessories,19.99,1,0.0,6.7966
|
||||
88,CA-2014-155558,10/27/2014,11/3/2014,Standard Class,PG-18895,Consumer,United States,Rochester,Minnesota,55901,Central,OFF-LA-10000134,Office Supplies,Labels,6.16,2,0.0,2.9568
|
||||
89,CA-2013-159695,4/6/2013,4/11/2013,Second Class,GM-14455,Home Office,United States,Houston,Texas,77095,Central,OFF-ST-10003442,Office Supplies,Storage,158.368,7,0.2,13.8572
|
||||
90,CA-2013-109806,9/18/2013,9/23/2013,Standard Class,JS-15685,Corporate,United States,Los Angeles,California,90036,West,OFF-AR-10004930,Office Supplies,Art,20.1,3,0.0,6.633
|
||||
91,CA-2013-109806,9/18/2013,9/23/2013,Standard Class,JS-15685,Corporate,United States,Los Angeles,California,90036,West,TEC-PH-10004093,Technology,Phones,73.584,2,0.2,8.2782
|
||||
92,CA-2013-109806,9/18/2013,9/23/2013,Standard Class,JS-15685,Corporate,United States,Los Angeles,California,90036,West,OFF-PA-10000304,Office Supplies,Paper,6.48,1,0.0,3.1104
|
||||
93,CA-2012-149587,1/31/2012,2/5/2012,Second Class,KB-16315,Consumer,United States,Minneapolis,Minnesota,55407,Central,OFF-PA-10003177,Office Supplies,Paper,12.96,2,0.0,6.2208
|
||||
94,CA-2012-149587,1/31/2012,2/5/2012,Second Class,KB-16315,Consumer,United States,Minneapolis,Minnesota,55407,Central,FUR-FU-10003799,Furniture,Furnishings,53.34,3,0.0,16.5354
|
||||
95,CA-2012-149587,1/31/2012,2/5/2012,Second Class,KB-16315,Consumer,United States,Minneapolis,Minnesota,55407,Central,OFF-BI-10002852,Office Supplies,Binders,32.96,2,0.0,16.1504
|
||||
96,US-2014-109484,11/7/2014,11/13/2014,Standard Class,RB-19705,Home Office,United States,Portland,Oregon,97206,West,OFF-BI-10004738,Office Supplies,Binders,5.682,1,0.7,-3.788
|
||||
97,CA-2014-161018,11/10/2014,11/12/2014,Second Class,PN-18775,Home Office,United States,New York City,New York,10009,East,FUR-FU-10000629,Furniture,Furnishings,96.53,7,0.0,40.5426
|
||||
98,CA-2014-157833,6/18/2014,6/21/2014,First Class,KD-16345,Consumer,United States,San Francisco,California,94122,West,OFF-BI-10001721,Office Supplies,Binders,51.312,3,0.2,17.9592
|
||||
99,CA-2013-149223,9/7/2013,9/12/2013,Standard Class,ER-13855,Corporate,United States,Saint Paul,Minnesota,55106,Central,OFF-AP-10000358,Office Supplies,Appliances,77.88,6,0.0,22.5852
|
||||
|
@@ -0,0 +1,16 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<script type="module">
|
||||
import "/node_modules/@perspective-dev/viewer/dist/cdn/perspective-viewer.js";
|
||||
import perspective from "/node_modules/@perspective-dev/client/dist/cdn/perspective.js";
|
||||
window.WORKER = perspective.worker();
|
||||
</script>
|
||||
<link rel="stylesheet" href="../css/demo.css" />
|
||||
<link rel="stylesheet" href="/node_modules/@perspective-dev/viewer/dist/css/pro.css" />
|
||||
<link rel="stylesheet" href="/node_modules/@fontsource/roboto-mono/400.css" />
|
||||
</head>
|
||||
<body>
|
||||
<perspective-viewer></perspective-viewer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,44 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../css/demo.css" />
|
||||
<link rel="stylesheet" href="/node_modules/@perspective-dev/viewer/dist/css/pro.css" />
|
||||
<link rel="stylesheet" href="/node_modules/@fontsource/roboto-mono/400.css" />
|
||||
</head>
|
||||
<body>
|
||||
<script type="module">
|
||||
import perspective from "/node_modules/@perspective-dev/client/dist/cdn/perspective.js";
|
||||
import "/node_modules/@perspective-dev/viewer/dist/cdn/perspective-viewer.js";
|
||||
|
||||
class TestChartPlugin extends customElements.get("perspective-viewer-plugin") {
|
||||
get_static_config() {
|
||||
return {
|
||||
name: "test chart",
|
||||
select_mode: "toggle",
|
||||
config_column_names: ["X Axis", "Y Axis", "Color", "Size", "Tooltip"],
|
||||
min_config_columns: 2,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("test-chart-plugin", TestChartPlugin);
|
||||
const Viewer = customElements.get("perspective-viewer");
|
||||
Viewer.registerPlugin("test-chart-plugin");
|
||||
|
||||
const viewer = document.createElement("perspective-viewer");
|
||||
document.body.appendChild(viewer);
|
||||
let resp = await fetch("/node_modules/@perspective-dev/test/assets/superstore.csv");
|
||||
|
||||
let csv = await resp.text();
|
||||
const worker = await perspective.worker();
|
||||
const table = worker.table(csv, {
|
||||
index: "Row ID",
|
||||
name: "load-viewer-csv",
|
||||
});
|
||||
|
||||
await viewer.load(table);
|
||||
window.__TEST_WORKER__ = worker;
|
||||
window.__TEST_PERSPECTIVE_READY__ = true;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,48 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../css/demo.css" />
|
||||
<link rel="stylesheet" href="/node_modules/@perspective-dev/viewer/dist/css/pro.css" />
|
||||
<link rel="stylesheet" href="/node_modules/@fontsource/roboto-mono/400.css" />
|
||||
</head>
|
||||
<body>
|
||||
<script type="module">
|
||||
import perspective from "/node_modules/@perspective-dev/client/dist/cdn/perspective.js";
|
||||
import "/node_modules/@perspective-dev/viewer/dist/cdn/perspective-viewer.js";
|
||||
|
||||
class TestColumnSettingsPlugin extends customElements.get("perspective-viewer-plugin") {
|
||||
get_static_config() {
|
||||
return {
|
||||
name: "test column settings",
|
||||
select_mode: "select",
|
||||
config_column_names: [],
|
||||
can_render_column_styles: true,
|
||||
};
|
||||
}
|
||||
|
||||
column_style_controls(type, group) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("test-column-settings-plugin", TestColumnSettingsPlugin);
|
||||
const Viewer = customElements.get("perspective-viewer");
|
||||
Viewer.registerPlugin("test-column-settings-plugin");
|
||||
|
||||
const viewer = document.createElement("perspective-viewer");
|
||||
document.body.appendChild(viewer);
|
||||
let resp = await fetch("/node_modules/@perspective-dev/test/assets/superstore.csv");
|
||||
|
||||
let csv = await resp.text();
|
||||
const worker = await perspective.worker();
|
||||
const table = worker.table(csv, {
|
||||
index: "Row ID",
|
||||
name: "load-viewer-csv",
|
||||
});
|
||||
|
||||
await viewer.load(table);
|
||||
window.__TEST_WORKER__ = worker;
|
||||
window.__TEST_PERSPECTIVE_READY__ = true;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,141 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import "/node_modules/@perspective-dev/viewer/dist/cdn/perspective-viewer.js";
|
||||
import perspective from "/node_modules/@perspective-dev/client/dist/cdn/perspective.js";
|
||||
|
||||
const BasePlugin = customElements.get("perspective-viewer-plugin");
|
||||
|
||||
class DebugStyledPlugin extends BasePlugin {
|
||||
get_static_config() {
|
||||
return {
|
||||
name: "Debug Styled",
|
||||
select_mode: "toggle",
|
||||
config_column_names: ["Columns"],
|
||||
priority: 1,
|
||||
can_render_column_styles: true,
|
||||
};
|
||||
}
|
||||
|
||||
plugin_config_schema() {
|
||||
return {
|
||||
fields: [
|
||||
{
|
||||
kind: "Enum",
|
||||
key: "edit_mode",
|
||||
default: "READ_ONLY",
|
||||
variants: [
|
||||
{ value: "EDIT", label: "Edit" },
|
||||
{ value: "READ_ONLY", label: "Read-only" },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
column_config_schema(type) {
|
||||
const fields = [];
|
||||
if (type === "integer" || type === "float") {
|
||||
fields.push({
|
||||
kind: "ColorRange",
|
||||
key_pos: "pos_fg_color",
|
||||
key_neg: "neg_fg_color",
|
||||
default_pos: "#2771a8",
|
||||
default_neg: "#ff471e",
|
||||
is_gradient: false,
|
||||
});
|
||||
fields.push({ kind: "NumberFormat" });
|
||||
} else if (type === "date") {
|
||||
fields.push({ kind: "DatetimeFormat" });
|
||||
} else if (type === "datetime") {
|
||||
fields.push({ kind: "DatetimeFormat" });
|
||||
fields.push({
|
||||
kind: "Enum",
|
||||
key: "datetime_color_mode",
|
||||
default: "none",
|
||||
variants: [
|
||||
{ value: "none", label: "None" },
|
||||
{ value: "foreground", label: "Foreground" },
|
||||
],
|
||||
});
|
||||
} else if (type === "string") {
|
||||
fields.push({ kind: "StringFormat" });
|
||||
}
|
||||
|
||||
return { fields };
|
||||
}
|
||||
|
||||
restore(token, columns_config) {
|
||||
this._restored_plugin_config = token;
|
||||
this._restored_columns_config = columns_config;
|
||||
this._restore_count = (this._restore_count ?? 0) + 1;
|
||||
}
|
||||
|
||||
async update(view) {
|
||||
return this.draw(view);
|
||||
}
|
||||
|
||||
async draw(view) {
|
||||
this._draw_count = (this._draw_count ?? 0) + 1;
|
||||
const num_rows = await view.num_rows();
|
||||
this.textContent = `${this.get_static_config().name}: ${num_rows} rows`;
|
||||
}
|
||||
}
|
||||
|
||||
class DebugAltPlugin extends DebugStyledPlugin {
|
||||
get_static_config() {
|
||||
return {
|
||||
name: "Debug Alt",
|
||||
select_mode: "select",
|
||||
priority: 0,
|
||||
can_render_column_styles: false,
|
||||
};
|
||||
}
|
||||
|
||||
plugin_config_schema() {
|
||||
return { fields: [] };
|
||||
}
|
||||
|
||||
column_config_schema() {
|
||||
return { fields: [] };
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("perspective-viewer-debug-styled", DebugStyledPlugin);
|
||||
customElements.define("perspective-viewer-debug-alt", DebugAltPlugin);
|
||||
|
||||
const Viewer = customElements.get("perspective-viewer");
|
||||
Viewer.registerPlugin("perspective-viewer-debug-styled");
|
||||
Viewer.registerPlugin("perspective-viewer-debug-alt");
|
||||
|
||||
async function load() {
|
||||
const resp = await fetch(
|
||||
"/node_modules/@perspective-dev/test/assets/superstore.csv",
|
||||
);
|
||||
|
||||
const csv = await resp.text();
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
const worker = await perspective.worker();
|
||||
const table = worker.table(csv, {
|
||||
index: "Row ID",
|
||||
name: "load-viewer-csv",
|
||||
});
|
||||
|
||||
if (viewer) {
|
||||
await viewer.load(table);
|
||||
}
|
||||
|
||||
window.__TEST_WORKER__ = worker;
|
||||
}
|
||||
|
||||
await load();
|
||||
window.__TEST_PERSPECTIVE_READY__ = true;
|
||||
@@ -0,0 +1,48 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<script type="module" src="/node_modules/@perspective-dev/viewer/dist/cdn/perspective-viewer.js"></script>
|
||||
<link rel="stylesheet" href="../css/demo.css" />
|
||||
<link rel="stylesheet" href="/node_modules/@perspective-dev/viewer/dist/css/pro.css" />
|
||||
<link rel="stylesheet" href="/node_modules/@fontsource/roboto-mono/400.css" />
|
||||
</head>
|
||||
<body>
|
||||
<perspective-viewer> </perspective-viewer>
|
||||
<script type="module">
|
||||
import perspective from "/node_modules/@perspective-dev/client/dist/cdn/perspective.js";
|
||||
window.WORKER = perspective.worker();
|
||||
const datetime_range_data = [
|
||||
{
|
||||
v: new Date(2020, 2, 1),
|
||||
w: new Date(2020, 2, 1, 12, 30, 55),
|
||||
x: 1,
|
||||
y: "a",
|
||||
z: true,
|
||||
}, // 2020/03/01 12:30:55 GMT-0400
|
||||
{
|
||||
v: new Date(2020, 9, 1),
|
||||
w: new Date(2020, 9, 1, 15, 30, 55),
|
||||
x: 2,
|
||||
y: "b",
|
||||
z: false,
|
||||
}, // 2020/10/01 15:30:55 GMT-0400
|
||||
{
|
||||
v: new Date(2020, 10, 1),
|
||||
w: new Date(2020, 10, 1, 19, 30, 55),
|
||||
x: 3,
|
||||
y: "c",
|
||||
z: true,
|
||||
}, // 2020/11/01 19:30:55 GMT-0500
|
||||
{
|
||||
v: new Date(2020, 11, 1),
|
||||
w: new Date(2020, 11, 1, 23, 30, 55),
|
||||
x: 4,
|
||||
y: "d",
|
||||
z: false,
|
||||
}, // 2020/12/01 23:30:55 GMT-0500
|
||||
];
|
||||
const table = window.WORKER.table(datetime_range_data);
|
||||
document.querySelector("perspective-viewer").load(table);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,43 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<script type="module" src="/node_modules/@perspective-dev/viewer/dist/cdn/perspective-viewer.js"></script>
|
||||
<script type="module">
|
||||
await customElements.whenDefined("perspective-viewer-plugin");
|
||||
const BASE = customElements.get("perspective-viewer-plugin");
|
||||
class TestChartPluginHighPriority extends BASE {
|
||||
get_static_config() {
|
||||
return {
|
||||
name: "HighPriority",
|
||||
config_column_names: [],
|
||||
priority: 10,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class TestChartPluginLowPriority extends BASE {
|
||||
get_static_config() {
|
||||
return {
|
||||
name: "LowPriority",
|
||||
config_column_names: [],
|
||||
priority: -10,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("low-priority", TestChartPluginLowPriority);
|
||||
customElements.define("high-priority", TestChartPluginHighPriority);
|
||||
await customElements.whenDefined("perspective-viewer").then((viewer) => {
|
||||
viewer.registerPlugin("low-priority");
|
||||
viewer.registerPlugin("high-priority");
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="module" src="/node_modules/@perspective-dev/test/load-viewer-csv.js"></script>
|
||||
<link rel="stylesheet" href="../css/demo.css" />
|
||||
<link rel="stylesheet" href="/node_modules/@perspective-dev/viewer/dist/css/pro.css" />
|
||||
</head>
|
||||
<body>
|
||||
<perspective-viewer></perspective-viewer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,49 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<script type="module" src="/node_modules/@perspective-dev/viewer/dist/cdn/perspective-viewer.js"></script>
|
||||
<script type="module">
|
||||
await customElements.whenDefined("perspective-viewer-plugin");
|
||||
const BASE = customElements.get("perspective-viewer-plugin");
|
||||
|
||||
class TestPluginImplementsResize extends BASE {
|
||||
get_static_config() {
|
||||
return {
|
||||
name: "Resizing Plugin",
|
||||
select_mode: "select",
|
||||
config_column_names: [],
|
||||
};
|
||||
}
|
||||
|
||||
async draw(view) {
|
||||
this._view = view;
|
||||
console.log(await this._view.num_rows());
|
||||
return await super.draw(view);
|
||||
}
|
||||
|
||||
async update(view) {
|
||||
this._view = view;
|
||||
console.log(await this._view.num_rows());
|
||||
return await super.update(view);
|
||||
}
|
||||
|
||||
async resize(view) {
|
||||
console.log(await this._view.num_rows());
|
||||
await this._view.to_csv();
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("resize-plugin", TestPluginImplementsResize);
|
||||
await customElements.whenDefined("perspective-viewer").then((viewer) => {
|
||||
viewer.registerPlugin("resize-plugin");
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="module" src="/node_modules/@perspective-dev/test/load-viewer-csv.js"></script>
|
||||
<link rel="stylesheet" href="../css/demo.css" />
|
||||
<link rel="stylesheet" href="/node_modules/@perspective-dev/viewer/dist/css/pro.css" />
|
||||
</head>
|
||||
<body>
|
||||
<perspective-viewer></perspective-viewer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,16 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<script type="module" src="/node_modules/@perspective-dev/viewer/dist/cdn/perspective-viewer.js"></script>
|
||||
<script type="module" src="/node_modules/@perspective-dev/viewer-charts/dist/cdn/perspective-viewer-charts.js"></script>
|
||||
<script type="module" src="/node_modules/@perspective-dev/viewer-datagrid/dist/cdn/perspective-viewer-datagrid.js"></script>
|
||||
<script type="module" src="/node_modules/@perspective-dev/test/load-viewer-csv.js"></script>
|
||||
<link rel="stylesheet" href="../css/demo.css" />
|
||||
<link rel="stylesheet" href="/node_modules/@perspective-dev/viewer/dist/css/pro.css" />
|
||||
<link rel="stylesheet" href="/@fontsource/roboto-mono/400.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<perspective-viewer> </perspective-viewer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<script type="module" src="./debug_plugins.js"></script>
|
||||
<link rel="stylesheet" href="../css/demo.css" />
|
||||
<link rel="stylesheet" href="/node_modules/@perspective-dev/viewer/dist/css/pro.css" />
|
||||
<link rel="stylesheet" href="/node_modules/@fontsource/roboto-mono/400.css" />
|
||||
</head>
|
||||
<body>
|
||||
<perspective-viewer></perspective-viewer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,28 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<script type="module">
|
||||
import "/node_modules/@perspective-dev/viewer/dist/esm/perspective-viewer.inline.js";
|
||||
import perspective from "/node_modules/@perspective-dev/client/dist/cdn/perspective.js";
|
||||
|
||||
async function load() {
|
||||
let resp = await fetch("/node_modules/@perspective-dev/test/assets/superstore.csv");
|
||||
let csv = await resp.text();
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
const worker = await perspective.worker();
|
||||
const table = worker.table(csv, { index: "Row ID" });
|
||||
await viewer.load(table);
|
||||
window.__TEST_WORKER__ = worker;
|
||||
}
|
||||
|
||||
await load();
|
||||
window.__TEST_PERSPECTIVE_READY__ = true;
|
||||
</script>
|
||||
<link rel="stylesheet" href="../css/demo.css" />
|
||||
<link rel="stylesheet" href="/node_modules/@perspective-dev/viewer/dist/css/pro.css" />
|
||||
<link rel="stylesheet" href="/node_modules/@fontsource/roboto-mono/400.css" />
|
||||
</head>
|
||||
<body>
|
||||
<perspective-viewer></perspective-viewer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,21 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<script type="module" src="http://localhost:6598/node_modules/@perspective-dev/viewer/dist/cdn/perspective-viewer.js"></script>
|
||||
<script type="module" src="http://localhost:6598/node_modules/@perspective-dev/test/load-viewer-csv-single-threaded.js"></script>
|
||||
<link rel="stylesheet" href="http://localhost:6598/node_modules/@perspective-dev/viewer/dist/css/pro.css" />
|
||||
<link rel="stylesheet" href="http://localhost:6598/node_modules/@fontsource/roboto-mono/400.css" />
|
||||
<style>
|
||||
perspective-viewer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<perspective-viewer></perspective-viewer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<script type="module" src="/node_modules/@perspective-dev/viewer/dist/cdn/perspective-viewer.js"></script>
|
||||
<script type="module" src="/node_modules/@perspective-dev/test/load-viewer-csv.js"></script>
|
||||
<link rel="stylesheet" href="../css/demo.css" />
|
||||
<link rel="stylesheet" href="/node_modules/@perspective-dev/viewer/dist/css/pro.css" />
|
||||
<link rel="stylesheet" href="/node_modules/@fontsource/roboto-mono/400.css" />
|
||||
</head>
|
||||
<body>
|
||||
<perspective-viewer></perspective-viewer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,11 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<script type="module" src="/node_modules/@perspective-dev/viewer/dist/cdn/perspective-viewer.js"></script>
|
||||
<script type="module" src="/node_modules/@perspective-dev/test/load-viewer.js"></script>
|
||||
<link rel="stylesheet" href="../css/demo.css" />
|
||||
<link rel="stylesheet" href="/node_modules/@perspective-dev/viewer/dist/css/pro.css" />
|
||||
<link rel="stylesheet" href="/node_modules/@fontsource/roboto-mono/400.css" />
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
@@ -0,0 +1,19 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<script type="module" src="/node_modules/@perspective-dev/viewer/dist/cdn/perspective-viewer.js"></script>
|
||||
<script type="module" src="/node_modules/@perspective-dev/test/load-viewer-csv.js"></script>
|
||||
<link rel="stylesheet" href="../css/demo.css" />
|
||||
<link rel="stylesheet" href="/node_modules/@perspective-dev/viewer/dist/css/pro.css" />
|
||||
<link rel="stylesheet" href="/node_modules/@fontsource/roboto-mono/400.css" />
|
||||
<style>
|
||||
perspective-viewer {
|
||||
top: 50px !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<input />
|
||||
<perspective-viewer></perspective-viewer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,209 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { test, expect, PageView } from "../helpers.ts";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("/rust/perspective-viewer/test/html/superstore-debug.html");
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Attributes Tab", () => {
|
||||
test("Expression column name becomes editable when attributes tab is open", async ({
|
||||
page,
|
||||
}) => {
|
||||
let view = new PageView(page);
|
||||
await view.restore({
|
||||
settings: true,
|
||||
expressions: { expr: "12345" },
|
||||
columns: ["expr"],
|
||||
});
|
||||
|
||||
let expr =
|
||||
await view.settingsPanel.activeColumns.getColumnByName("expr");
|
||||
|
||||
await expr.editBtn.click();
|
||||
let input = view.columnSettingsSidebar.nameInput;
|
||||
await view.columnSettingsSidebar.openTab("Style");
|
||||
await expect(input).toBeDisabled();
|
||||
await view.columnSettingsSidebar.openTab("Attributes");
|
||||
await expect(input).toBeEnabled();
|
||||
});
|
||||
|
||||
test("Empty expression names", async ({ page }) => {
|
||||
// Setup
|
||||
let expr_value = "12345";
|
||||
let view = new PageView(page);
|
||||
await view.openSettingsPanel();
|
||||
await view.settingsPanel.createNewExpression("", expr_value);
|
||||
// Empty text matches expression
|
||||
let input = view.columnSettingsSidebar.nameInput;
|
||||
expect(await input.evaluate((input) => input!.value)).toBe("");
|
||||
expect(await input.evaluate((input) => input!.placeholder)).toBe(
|
||||
expr_value,
|
||||
);
|
||||
// Reopening the column shows an empty header with placeholder text that matches
|
||||
await view.columnSettingsSidebar.closeBtn.click();
|
||||
let expr =
|
||||
await view.settingsPanel.inactiveColumns.getColumnByName(
|
||||
expr_value,
|
||||
);
|
||||
await expr.editBtn.click();
|
||||
expect(await input.evaluate((input) => input!.value)).toBe("");
|
||||
expect(await input.evaluate((input) => input!.placeholder)).toBe(
|
||||
expr_value,
|
||||
);
|
||||
// Expression alias is the expression on serialization
|
||||
let config = await view.save();
|
||||
expect(config.expressions?.[expr_value]).toBe(expr_value);
|
||||
});
|
||||
|
||||
test("Expression names do not overlap existing columns", async ({
|
||||
page,
|
||||
}) => {
|
||||
// Expressions should have an error state and be unsavable when the name matches an existing column
|
||||
let view = new PageView(page);
|
||||
await view.restore({
|
||||
expressions: { expr: "12345" },
|
||||
columns: ["expr", "Row ID"],
|
||||
settings: true,
|
||||
});
|
||||
let expr =
|
||||
await view.settingsPanel.activeColumns.getColumnByName("expr");
|
||||
await expr.editBtn.click();
|
||||
await view.columnSettingsSidebar.openTab("Attributes");
|
||||
let input = view.columnSettingsSidebar.nameInput;
|
||||
await input.clear();
|
||||
await input.type("Row ID", { delay: 100 });
|
||||
await expect(view.columnSettingsSidebar.nameInputWrapper).toHaveClass(
|
||||
/invalid/,
|
||||
);
|
||||
await expect(
|
||||
view.columnSettingsSidebar.attributesTab.saveBtn,
|
||||
).toBeDisabled();
|
||||
});
|
||||
|
||||
test("Tab Button Click enters 4 spaces.", async ({ page }) => {
|
||||
let view = new PageView(page);
|
||||
await view.restore({
|
||||
expressions: { expr: "12345" },
|
||||
columns: ["expr", "Row ID"],
|
||||
settings: true,
|
||||
});
|
||||
let expr =
|
||||
await view.settingsPanel.activeColumns.getColumnByName("expr");
|
||||
await expr.editBtn.click();
|
||||
await view.columnSettingsSidebar.openTab("Attributes");
|
||||
|
||||
let sidebar = view.columnSettingsSidebar;
|
||||
let attributesTab = sidebar.attributesTab;
|
||||
|
||||
let textarea = attributesTab.expressionEditor.textarea;
|
||||
|
||||
await textarea.type("foo", { delay: 100 });
|
||||
expect(await textarea.evaluate((input) => input!.value)).toStrictEqual(
|
||||
"foo12345",
|
||||
);
|
||||
|
||||
await page.keyboard.press("Tab");
|
||||
const expected = await textarea.evaluate((input) => input!.value);
|
||||
|
||||
expect(expected).toContain("\t");
|
||||
|
||||
const caretPosition = await textarea.evaluate(
|
||||
(input) => input!.selectionStart,
|
||||
);
|
||||
expect(caretPosition).toBe(4); // length of foo + length of '\t' = 4;
|
||||
});
|
||||
|
||||
test("Reset button", async ({ page }) => {
|
||||
let view = new PageView(page);
|
||||
await view.restore({
|
||||
expressions: { expr: "12345" },
|
||||
columns: ["expr", "Row ID"],
|
||||
settings: true,
|
||||
});
|
||||
let expr =
|
||||
await view.settingsPanel.activeColumns.getColumnByName("expr");
|
||||
await expr.editBtn.click();
|
||||
await view.columnSettingsSidebar.openTab("Attributes");
|
||||
|
||||
let sidebar = view.columnSettingsSidebar;
|
||||
let attributesTab = sidebar.attributesTab;
|
||||
|
||||
await expect(attributesTab.resetBtn).toBeDisabled();
|
||||
|
||||
await sidebar.nameInput.type("foo", { delay: 100 });
|
||||
await expect(attributesTab.resetBtn).toBeEnabled();
|
||||
await attributesTab.resetBtn.click();
|
||||
expect(
|
||||
await sidebar.nameInput.evaluate((input) => input!.value),
|
||||
).toStrictEqual("expr");
|
||||
|
||||
let textarea = attributesTab.expressionEditor.textarea;
|
||||
await textarea.type("foo", {
|
||||
delay: 100,
|
||||
});
|
||||
await expect(attributesTab.resetBtn).toBeEnabled();
|
||||
await attributesTab.resetBtn.click();
|
||||
expect(await textarea.evaluate((input) => input!.value)).toStrictEqual(
|
||||
"12345",
|
||||
);
|
||||
});
|
||||
|
||||
test("Delete button", async ({ page }) => {
|
||||
let view = new PageView(page);
|
||||
await view.restore({
|
||||
expressions: { expr: "12345" },
|
||||
columns: ["expr", "Row ID"],
|
||||
settings: true,
|
||||
});
|
||||
let expr =
|
||||
await view.settingsPanel.activeColumns.getColumnByName("expr");
|
||||
await expr.editBtn.click();
|
||||
await view.columnSettingsSidebar.openTab("Attributes");
|
||||
let attributesTab = view.columnSettingsSidebar.attributesTab;
|
||||
|
||||
await expect(attributesTab.deleteBtn).toBeDisabled();
|
||||
await expr.activeBtn.click();
|
||||
|
||||
expr = await view.settingsPanel.inactiveColumns.getColumnByName("expr");
|
||||
await expect(attributesTab.deleteBtn).toBeEnabled();
|
||||
await attributesTab.deleteBtn.click();
|
||||
await expect(attributesTab.container).toBeHidden();
|
||||
|
||||
let config = await view.save();
|
||||
expect(config?.expressions).toStrictEqual({});
|
||||
});
|
||||
|
||||
test("Rename empty header as expression value", async ({ page }) => {
|
||||
let view = new PageView(page);
|
||||
let settingsPanel = await view.openSettingsPanel();
|
||||
await settingsPanel.createNewExpression("", "1234");
|
||||
await view.columnSettingsSidebar.nameInput.type("1234");
|
||||
await expect(
|
||||
view.columnSettingsSidebar.nameInputWrapper,
|
||||
).not.toHaveClass("invalid");
|
||||
// NOTE: Currently when you rename a column as the contents of its placeholder,
|
||||
// it gets serialized with the expression name. This confuses the components and it deserializes
|
||||
// as if it had an empty header.
|
||||
// Changing this behavior may require changing the way we serialize expressions.
|
||||
// For this to work, an expression should have a unique ID. API might look like this:
|
||||
// `const settings = {expressions: {"SOME_ID": {name: "foobar", expr: "123"}, "ANOTHER_ID": {expr: "'i have no name'"}}};`
|
||||
// We could then change the ColumnLocator struct to look like this:
|
||||
// `enum ColumnLocator {TableColumn(String), ExprColumn(Option<String>), NewExpr()}`
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,647 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { PageView, ColumnSelector, test, expect } from "../helpers.ts";
|
||||
|
||||
test.describe.configure({ mode: "parallel" });
|
||||
|
||||
type SidebarState =
|
||||
| {
|
||||
open: true;
|
||||
tabs: string[];
|
||||
selectedTab: string;
|
||||
type: string | "expression";
|
||||
snapshot: string;
|
||||
}
|
||||
| { open: false };
|
||||
|
||||
type Action =
|
||||
| "open"
|
||||
| "activate"
|
||||
| "deactivate"
|
||||
| "reorder"
|
||||
| "aggregate"
|
||||
| "deaggregate"
|
||||
| "groupby"
|
||||
| "splitby"
|
||||
| "orderby"
|
||||
| "where";
|
||||
|
||||
// Given an initial state and a list of possible actions, expect the given outputs.
|
||||
// Each action will be tested against the three methods - button click,
|
||||
// drag'n'drop, and text input - according to what is possible.
|
||||
// For instance, it's impossible to button click a column into a query position,
|
||||
// so this will be skipped.
|
||||
type TestSpec = {
|
||||
initial_state: { open: boolean; active: boolean; columns?: string[] };
|
||||
actions: Action[];
|
||||
outputs: {
|
||||
table_col?: ExpectedState;
|
||||
expr_col?: ExpectedState;
|
||||
};
|
||||
};
|
||||
type ExpectedState =
|
||||
| { closed: true }
|
||||
| { unchanged: true }
|
||||
| ({ tabs: string[]; selectedTab: string } & { type?: string });
|
||||
|
||||
// Note: impossible states are not encoded here.
|
||||
const TEST_SPEC: Record<string, TestSpec> = {
|
||||
"Open - Active": {
|
||||
initial_state: {
|
||||
open: false,
|
||||
active: true,
|
||||
},
|
||||
actions: ["open"],
|
||||
outputs: {
|
||||
table_col: {
|
||||
tabs: ["Style"],
|
||||
selectedTab: "Style",
|
||||
},
|
||||
expr_col: {
|
||||
tabs: ["Style", "Attributes"],
|
||||
selectedTab: "Style",
|
||||
},
|
||||
},
|
||||
},
|
||||
"Open - Inactive": {
|
||||
initial_state: {
|
||||
open: false,
|
||||
active: false,
|
||||
},
|
||||
actions: ["open"],
|
||||
outputs: {
|
||||
// The Style tab requires the column to be in
|
||||
// `view_config.columns` (see `column_settings_sidebar.rs`
|
||||
// `show_styles`); inactive expression columns only see
|
||||
// Attributes.
|
||||
expr_col: {
|
||||
tabs: ["Attributes"],
|
||||
selectedTab: "Attributes",
|
||||
},
|
||||
},
|
||||
},
|
||||
Deactivate: {
|
||||
initial_state: {
|
||||
open: true,
|
||||
active: true,
|
||||
},
|
||||
actions: ["deactivate"],
|
||||
outputs: {
|
||||
table_col: {
|
||||
closed: true,
|
||||
},
|
||||
// Deactivating drops the column from `view_config.columns`,
|
||||
// which removes the Style tab; the sidebar stays open on
|
||||
// the expression with only Attributes left.
|
||||
expr_col: {
|
||||
tabs: ["Attributes"],
|
||||
selectedTab: "Attributes",
|
||||
},
|
||||
},
|
||||
},
|
||||
Activate: {
|
||||
initial_state: {
|
||||
open: true,
|
||||
active: false,
|
||||
},
|
||||
actions: ["activate"],
|
||||
outputs: {
|
||||
expr_col: {
|
||||
tabs: ["Style", "Attributes"],
|
||||
selectedTab: "Attributes",
|
||||
},
|
||||
},
|
||||
},
|
||||
"Reorder - Active": {
|
||||
initial_state: {
|
||||
open: true,
|
||||
active: true,
|
||||
},
|
||||
actions: ["reorder"],
|
||||
outputs: {
|
||||
table_col: {
|
||||
unchanged: true,
|
||||
},
|
||||
expr_col: {
|
||||
unchanged: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Aggregate: {
|
||||
initial_state: {
|
||||
open: true,
|
||||
active: true,
|
||||
},
|
||||
actions: ["aggregate"],
|
||||
outputs: {
|
||||
table_col: {
|
||||
unchanged: true,
|
||||
type: "integer",
|
||||
},
|
||||
expr_col: {
|
||||
unchanged: true,
|
||||
type: "integer",
|
||||
},
|
||||
},
|
||||
},
|
||||
Deaggregate: {
|
||||
initial_state: {
|
||||
open: true,
|
||||
active: true,
|
||||
},
|
||||
actions: ["deaggregate"],
|
||||
outputs: {
|
||||
table_col: {
|
||||
unchanged: true,
|
||||
type: "string",
|
||||
},
|
||||
expr_col: {
|
||||
unchanged: true,
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"Groupby - Active": {
|
||||
initial_state: {
|
||||
open: true,
|
||||
active: true,
|
||||
},
|
||||
actions: ["groupby"],
|
||||
outputs: {
|
||||
expr_col: {
|
||||
unchanged: true,
|
||||
type: "integer",
|
||||
},
|
||||
table_col: {
|
||||
unchanged: true,
|
||||
type: "integer",
|
||||
},
|
||||
},
|
||||
},
|
||||
"Splitby - Active": {
|
||||
initial_state: {
|
||||
open: true,
|
||||
active: true,
|
||||
},
|
||||
actions: ["splitby"],
|
||||
outputs: {
|
||||
expr_col: {
|
||||
unchanged: true,
|
||||
},
|
||||
table_col: {
|
||||
unchanged: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
"Filter - Active": {
|
||||
initial_state: { open: true, active: true },
|
||||
actions: ["orderby", "where"],
|
||||
outputs: {
|
||||
expr_col: { unchanged: true },
|
||||
table_col: { unchanged: true },
|
||||
},
|
||||
},
|
||||
"Query - Inactive": {
|
||||
initial_state: {
|
||||
open: true,
|
||||
active: false,
|
||||
},
|
||||
actions: ["groupby", "splitby", "orderby", "where"],
|
||||
outputs: {
|
||||
expr_col: {
|
||||
unchanged: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** Restores the viewer to a simple state and returns the columnSettingsSidebar's innerHTML */
|
||||
async function open(
|
||||
view: PageView,
|
||||
initial_state: TestSpec["initial_state"],
|
||||
expr: boolean,
|
||||
): Promise<[ColumnSelector, SidebarState]> {
|
||||
const { active, open, columns } = initial_state;
|
||||
|
||||
// expr_value is a string to keep things consistent between aggregate values
|
||||
const expr_name = "expr";
|
||||
const expr_value = "'hello'";
|
||||
|
||||
let view_columns = columns ? [...columns] : ["Category", "City"];
|
||||
if (active && expr) {
|
||||
view_columns.push(expr_name);
|
||||
}
|
||||
|
||||
const viewer_settings = {
|
||||
settings: true,
|
||||
columns: view_columns,
|
||||
expressions: { [expr_name]: expr_value },
|
||||
};
|
||||
|
||||
await view.restore(viewer_settings);
|
||||
|
||||
await view.page.evaluate((viewer_settings) => {
|
||||
console.log(viewer_settings);
|
||||
}, viewer_settings);
|
||||
|
||||
let selector = active
|
||||
? await view.settingsPanel.activeColumns.getColumnByName(
|
||||
expr ? expr_name : view_columns[0],
|
||||
)
|
||||
: expr
|
||||
? await view.settingsPanel.inactiveColumns.getColumnByName(expr_name)
|
||||
: view.settingsPanel.inactiveColumns.getColumnByType("string");
|
||||
|
||||
let state: SidebarState;
|
||||
if (open) {
|
||||
await selector.editBtn.click();
|
||||
state = {
|
||||
open: true,
|
||||
tabs: await view.columnSettingsSidebar.getTabs(),
|
||||
selectedTab: await view.columnSettingsSidebar.getSelectedTab(),
|
||||
type: await view.columnSettingsSidebar.getType(),
|
||||
snapshot: await view.columnSettingsSidebar.container.innerHTML(),
|
||||
};
|
||||
} else {
|
||||
state = { open: false };
|
||||
}
|
||||
return [selector, state];
|
||||
}
|
||||
|
||||
async function checkOutput(
|
||||
view: PageView,
|
||||
expected: ExpectedState,
|
||||
initial_state: SidebarState,
|
||||
) {
|
||||
const POSSIBLE_TABS = ["Style", "Attributes"];
|
||||
|
||||
const check_tabs = async (
|
||||
selectedTab: string,
|
||||
tabs: string[],
|
||||
unexpected_tabs: string[],
|
||||
) => {
|
||||
if (selectedTab === "") {
|
||||
await view.columnSettingsSidebar.container
|
||||
.locator(".settings_tab.selected_tab .tab-title")
|
||||
.first()
|
||||
.waitFor();
|
||||
} else {
|
||||
await view.columnSettingsSidebar.container
|
||||
.locator(`.settings_tab.selected_tab #${selectedTab}`)
|
||||
.waitFor();
|
||||
}
|
||||
|
||||
for (let tab of tabs) {
|
||||
if (tab === "") {
|
||||
await view.columnSettingsSidebar.container
|
||||
.locator(`.tab-title`)
|
||||
.first()
|
||||
.waitFor();
|
||||
} else {
|
||||
await view.columnSettingsSidebar.container
|
||||
.locator(`#${tab}`)
|
||||
.waitFor();
|
||||
}
|
||||
}
|
||||
for (let tab of unexpected_tabs) {
|
||||
await view.columnSettingsSidebar.tabTitle
|
||||
.locator(`#${tab}`)
|
||||
.waitFor({ state: "hidden" });
|
||||
}
|
||||
};
|
||||
|
||||
if ("type" in expected) {
|
||||
await view.columnSettingsSidebar.container
|
||||
.locator(`.type-icon.${expected.type}`)
|
||||
.waitFor();
|
||||
}
|
||||
if ("unchanged" in expected) {
|
||||
if (!initial_state.open) {
|
||||
await expect(view.columnSettingsSidebar.container).toBeHidden({
|
||||
timeout: 1000,
|
||||
});
|
||||
} else {
|
||||
const unexpected_tabs = POSSIBLE_TABS.filter(
|
||||
(t) => !initial_state.tabs.includes(t),
|
||||
);
|
||||
await check_tabs(
|
||||
initial_state.selectedTab,
|
||||
initial_state.tabs,
|
||||
unexpected_tabs,
|
||||
);
|
||||
}
|
||||
} else if ("closed" in expected) {
|
||||
await expect(view.columnSettingsSidebar.container).toBeHidden({
|
||||
timeout: 1000,
|
||||
});
|
||||
} else {
|
||||
const unexpected_tabs = POSSIBLE_TABS.filter(
|
||||
(t) => !expected.tabs.includes(t),
|
||||
);
|
||||
await check_tabs(expected.selectedTab, expected.tabs, unexpected_tabs);
|
||||
}
|
||||
}
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("/rust/perspective-viewer/test/html/superstore-debug.html");
|
||||
await page.evaluate(async () => {
|
||||
while (!(window as any)["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/** These tests perform interactions on the viewer column selector and check for the intended behavior on the column settings sidebar.
|
||||
* They all start with the assumption that the sidebar is open.
|
||||
* NOTE: if we ever allow styles from an inactive tab, then these tests will need to accomodate for it.
|
||||
*/
|
||||
test.describe("Column Settings State on Interaction", () => {
|
||||
const excluded_actions: Record<string, Action[]> = {
|
||||
button: [
|
||||
"reorder",
|
||||
"aggregate",
|
||||
"groupby",
|
||||
"splitby",
|
||||
"orderby",
|
||||
"where",
|
||||
],
|
||||
text: ["reorder", "open", "deactivate", "deaggregate"],
|
||||
dnd: ["open", "deactivate", "groupby", "splitby"],
|
||||
};
|
||||
|
||||
for (const [name, spec] of Object.entries(TEST_SPEC)) {
|
||||
for (const [col_type, expected_output] of Object.entries(
|
||||
spec.outputs,
|
||||
)) {
|
||||
const expr = col_type === "expr_col";
|
||||
for (const action of spec.actions) {
|
||||
const name_prefix = `${name} - ${col_type} - ${action}`;
|
||||
|
||||
if (!excluded_actions["button"].includes(action)) {
|
||||
test(`${name_prefix} - Button`, async ({ page }) => {
|
||||
let pageView = new PageView(page);
|
||||
const [selector, snapshot_state] = await open(
|
||||
pageView,
|
||||
spec.initial_state,
|
||||
expr,
|
||||
);
|
||||
switch (action) {
|
||||
case "open": {
|
||||
await selector.editBtn.click();
|
||||
break;
|
||||
}
|
||||
case "activate": {
|
||||
await selector.activeBtn.click();
|
||||
break;
|
||||
}
|
||||
case "deactivate": {
|
||||
await selector.activeBtn.click();
|
||||
break;
|
||||
}
|
||||
case "deaggregate": {
|
||||
await pageView.settingsPanel.groupby("Row ID");
|
||||
await pageView.settingsPanel.removeViewParameter(
|
||||
"groupby",
|
||||
"Row ID",
|
||||
);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
test.fail(true, "Unreachable");
|
||||
}
|
||||
}
|
||||
await checkOutput(
|
||||
pageView,
|
||||
expected_output,
|
||||
snapshot_state,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (!excluded_actions["text"].includes(action)) {
|
||||
test(`${name_prefix} - Text`, async ({ page }) => {
|
||||
let pageView = new PageView(page);
|
||||
const [selector, snapshot_state] = await open(
|
||||
pageView,
|
||||
spec.initial_state,
|
||||
expr,
|
||||
);
|
||||
let selector_name = await selector.name.innerText();
|
||||
switch (action) {
|
||||
case "activate": {
|
||||
await pageView.settingsPanel.activeColumns.activateColumn(
|
||||
selector_name,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "groupby": {
|
||||
await pageView.settingsPanel.groupby(
|
||||
selector_name,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "splitby": {
|
||||
await pageView.settingsPanel.splitby(
|
||||
selector_name,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "orderby": {
|
||||
await pageView.settingsPanel.orderby(
|
||||
selector_name,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "where": {
|
||||
await pageView.settingsPanel.where(
|
||||
selector_name,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "aggregate": {
|
||||
await pageView.settingsPanel.groupby("Row ID");
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
test.fail(true, "Unreachable");
|
||||
}
|
||||
}
|
||||
await checkOutput(
|
||||
pageView,
|
||||
expected_output,
|
||||
snapshot_state,
|
||||
);
|
||||
});
|
||||
}
|
||||
if (!excluded_actions["dnd"].includes(action)) {
|
||||
test(`${name_prefix} - Drag'n'Drop`, async ({ page }) => {
|
||||
let pageView = new PageView(page);
|
||||
const [selector, snapshot_state] = await open(
|
||||
pageView,
|
||||
spec.initial_state,
|
||||
expr,
|
||||
);
|
||||
switch (action) {
|
||||
case "activate": {
|
||||
await selector.container.dragTo(
|
||||
pageView.settingsPanel.activeColumns.columnSelector.last(),
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "reorder": {
|
||||
await selector.container.dragTo(
|
||||
pageView.settingsPanel.activeColumns.columnSelector.nth(
|
||||
1,
|
||||
),
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "groupby": {
|
||||
await selector.container.dragTo(
|
||||
pageView.settingsPanel.groupbyInput,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "splitby": {
|
||||
await selector.container.dragTo(
|
||||
pageView.settingsPanel.splitbyInput,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "orderby": {
|
||||
await selector.container.dragTo(
|
||||
pageView.settingsPanel.orderbyInput,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "where": {
|
||||
await selector.container.dragTo(
|
||||
pageView.settingsPanel.whereInput,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "aggregate": {
|
||||
const aggregator =
|
||||
pageView.settingsPanel.inactiveColumns.columnSelector.first();
|
||||
await aggregator.dragTo(
|
||||
pageView.settingsPanel.groupbyInput,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "deaggregate": {
|
||||
let aggregator =
|
||||
pageView.settingsPanel.inactiveColumns.columnSelector.first();
|
||||
await aggregator.dragTo(
|
||||
pageView.settingsPanel.groupbyInput,
|
||||
);
|
||||
aggregator = page
|
||||
.locator("#group_by .pivot-column")
|
||||
.first();
|
||||
await aggregator.dragTo(
|
||||
pageView.settingsPanel.activeColumns.container
|
||||
.locator(".column-selector-column")
|
||||
.first(),
|
||||
);
|
||||
}
|
||||
}
|
||||
await checkOutput(
|
||||
pageView,
|
||||
expected_output,
|
||||
snapshot_state,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test.describe("Unique Behaviors", () => {
|
||||
for (const destination of ["groupby", "splitby"]) {
|
||||
test(`${destination} - Single Active Column - table_col`, async ({
|
||||
page,
|
||||
}) => {
|
||||
const view = new PageView(page);
|
||||
const [selector, state_snapshot] = await open(
|
||||
view,
|
||||
{ open: true, active: true, columns: ["City"] },
|
||||
false,
|
||||
);
|
||||
|
||||
await selector.container.dragTo(
|
||||
destination === "groupby"
|
||||
? view.settingsPanel.groupbyInput
|
||||
: view.settingsPanel.splitbyInput,
|
||||
);
|
||||
await checkOutput(
|
||||
view,
|
||||
destination == "groupby"
|
||||
? {
|
||||
unchanged: true,
|
||||
type: "integer",
|
||||
}
|
||||
: { unchanged: true },
|
||||
state_snapshot,
|
||||
);
|
||||
});
|
||||
test(`${destination} - Multiple Active Columns - table_col`, async ({
|
||||
page,
|
||||
}) => {
|
||||
const view = new PageView(page);
|
||||
const [selector, state_snapshot] = await open(
|
||||
view,
|
||||
{ open: true, active: true, columns: ["City", "State"] },
|
||||
false,
|
||||
);
|
||||
|
||||
await selector.container.dragTo(
|
||||
destination === "groupby"
|
||||
? view.settingsPanel.groupbyInput
|
||||
: view.settingsPanel.splitbyInput,
|
||||
);
|
||||
// Dragging a table column onto group_by/split_by removes
|
||||
// it from `view_config.columns` (so `show_styles` is false
|
||||
// → no Style tab); a non-expression column has no
|
||||
// Attributes tab either, so the sidebar closes.
|
||||
await checkOutput(view, { closed: true }, state_snapshot);
|
||||
});
|
||||
}
|
||||
|
||||
test("switching between date and datetime should rerender", async ({
|
||||
page,
|
||||
}) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({
|
||||
settings: true,
|
||||
plugin: "Debug Styled",
|
||||
columns: ["date", "datetime"],
|
||||
expressions: { date: "date(0,0,0)", datetime: "now()" },
|
||||
});
|
||||
const date = await view.settingsPanel.activeColumns.getColumnByName(
|
||||
"date",
|
||||
true,
|
||||
);
|
||||
await date.editBtn.click();
|
||||
await view.columnSettingsSidebar.openTab("Style");
|
||||
const dateSnapshot =
|
||||
await view.columnSettingsSidebar.styleTab.container.innerHTML();
|
||||
const datetime = await view.settingsPanel.activeColumns.getColumnByName(
|
||||
"datetime",
|
||||
true,
|
||||
);
|
||||
await datetime.editBtn.click();
|
||||
const datetimeSnapshot =
|
||||
await view.columnSettingsSidebar.styleTab.container.innerHTML();
|
||||
expect(datetimeSnapshot).not.toEqual(dateSnapshot);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,307 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { test, expect, PageView, ColumnSettingsSidebar } from "../helpers.ts";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("/rust/perspective-viewer/test/html/superstore-debug.html");
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
export async function checkTab(
|
||||
columnSettingsSidebar: ColumnSettingsSidebar,
|
||||
active: boolean,
|
||||
expression: boolean,
|
||||
hasStyles: boolean = true,
|
||||
) {
|
||||
await columnSettingsSidebar.container.waitFor({
|
||||
state: "visible",
|
||||
});
|
||||
let titles = await columnSettingsSidebar.tabTitle.all();
|
||||
if (active) {
|
||||
if (expression) {
|
||||
if (hasStyles) {
|
||||
expect(titles.length).toBe(2);
|
||||
expect(await titles[0].getAttribute("id")).toBe("Style");
|
||||
expect(await titles[1].getAttribute("id")).toBe("Attributes");
|
||||
} else {
|
||||
expect(titles.length).toBe(1);
|
||||
expect(await titles[0].getAttribute("id")).toBe("Attributes");
|
||||
}
|
||||
} else {
|
||||
expect(titles.length).toBe(1);
|
||||
expect(await titles[0].getAttribute("id")).toBe("Style");
|
||||
}
|
||||
} else {
|
||||
if (expression) {
|
||||
expect(titles.length).toBe(1);
|
||||
expect(await titles[0].getAttribute("id")).toBe("Attributes");
|
||||
} else {
|
||||
test.fail(
|
||||
true,
|
||||
"No settings exist for non-expression, inactive columns!",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test.describe("Column Settings Sidebar", () => {
|
||||
test("edit button > opens sidebar for active table and expression columns", async ({
|
||||
page,
|
||||
}) => {
|
||||
let view = new PageView(page);
|
||||
let settingsPanel = await view.openSettingsPanel();
|
||||
let inactiveColumns = settingsPanel.inactiveColumns;
|
||||
let activeColumns = settingsPanel.activeColumns;
|
||||
|
||||
await settingsPanel.createNewExpression("expr", "true");
|
||||
await inactiveColumns.container.waitFor({
|
||||
state: "visible",
|
||||
});
|
||||
let exprCol = await activeColumns.activateColumn("expr");
|
||||
let firstCol = await activeColumns.getFirstVisibleColumn();
|
||||
|
||||
firstCol.editBtn.waitFor();
|
||||
await firstCol.editBtn.click();
|
||||
await checkTab(view.columnSettingsSidebar, true, false);
|
||||
|
||||
await activeColumns.scrollToBottom();
|
||||
exprCol.editBtn.waitFor();
|
||||
await exprCol.editBtn.click();
|
||||
await checkTab(view.columnSettingsSidebar, true, true);
|
||||
});
|
||||
test("edit button > opens sidebar for inactive expression columns", async ({
|
||||
page,
|
||||
}) => {
|
||||
let view = new PageView(page);
|
||||
let settingsPanel = await view.openSettingsPanel();
|
||||
let inactiveColumns = settingsPanel.inactiveColumns;
|
||||
let activeColumns = settingsPanel.activeColumns;
|
||||
|
||||
await settingsPanel.createNewExpression("expr", "true");
|
||||
let exprCol = await inactiveColumns.getColumnByName("expr");
|
||||
await activeColumns.toggleColumn("Row ID");
|
||||
let rowId = await inactiveColumns.getColumnByName("Row ID");
|
||||
expect(exprCol).toBeDefined();
|
||||
expect(rowId).toBeDefined();
|
||||
|
||||
await exprCol.editBtn.waitFor();
|
||||
await rowId.editBtn.waitFor({ state: "detached", timeout: 1000 });
|
||||
|
||||
await view.assureColumnSettingsClosed();
|
||||
await exprCol.editBtn.click();
|
||||
await checkTab(view.columnSettingsSidebar, false, true);
|
||||
});
|
||||
test("tabs > switches between style and attributes tabs", async ({
|
||||
page,
|
||||
}) => {
|
||||
let view = new PageView(page);
|
||||
let settingsPanel = await view.openSettingsPanel();
|
||||
let sidebar = view.columnSettingsSidebar;
|
||||
let activeColumns = settingsPanel.activeColumns;
|
||||
let inactiveColumns = settingsPanel.inactiveColumns;
|
||||
|
||||
await settingsPanel.createNewExpression("expr", "'string'");
|
||||
await activeColumns.activateColumn("expr");
|
||||
let col = await activeColumns.getColumnByName("expr");
|
||||
await inactiveColumns.container.waitFor({ state: "hidden" });
|
||||
await activeColumns.scrollToBottom();
|
||||
await view.assureColumnSettingsOpen(col);
|
||||
await checkTab(sidebar, true, true, true);
|
||||
let tabs = await sidebar.tabTitle.all();
|
||||
await tabs[1].click();
|
||||
await sidebar.attributesTab.container.waitFor();
|
||||
await tabs[0].click();
|
||||
await sidebar.styleTab.container.waitFor();
|
||||
});
|
||||
|
||||
test("state > persists sidebar when table data updates", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.evaluate(async () => {
|
||||
// @ts-ignore
|
||||
let table = await window.__TEST_WORKER__.table({ x: [0] });
|
||||
// @ts-ignore
|
||||
window.__TEST_TABLE__ = table;
|
||||
let viewer = document.querySelector("perspective-viewer");
|
||||
await viewer?.load(table);
|
||||
});
|
||||
|
||||
let view = new PageView(page);
|
||||
let settingsPanel = await view.openSettingsPanel();
|
||||
let col = settingsPanel.activeColumns.getFirstVisibleColumn();
|
||||
await col.editBtn.click();
|
||||
await view.columnSettingsSidebar.container.waitFor();
|
||||
await page.evaluate(() => {
|
||||
// @ts-ignore
|
||||
window.__TEST_TABLE__.update({ x: [1] });
|
||||
});
|
||||
|
||||
await page.waitForFunction(() => {
|
||||
// The Debug Styled test plugin renders its row count as text into
|
||||
// the viewer's light DOM; wait for it to reflect the update.
|
||||
const plugin = document
|
||||
.querySelector("perspective-viewer")
|
||||
?.querySelector("perspective-viewer-debug-styled");
|
||||
return (plugin?.textContent ?? "").includes("rows");
|
||||
});
|
||||
|
||||
await expect(view.columnSettingsSidebar.container).toBeVisible();
|
||||
});
|
||||
|
||||
test("layout > constrains width with long expression text", async ({
|
||||
page,
|
||||
}) => {
|
||||
let view = new PageView(page);
|
||||
|
||||
const MAX_WIDTH = 350;
|
||||
let checkWidth = async () => {
|
||||
let width = await view.columnSettingsSidebar.container.evaluate(
|
||||
(sidebar) => sidebar.getBoundingClientRect().width,
|
||||
);
|
||||
expect(width).toEqual(MAX_WIDTH);
|
||||
};
|
||||
|
||||
let settings = await view.openSettingsPanel();
|
||||
await settings.activeColumns.scrollToBottom();
|
||||
await settings.addExpressionButton.click();
|
||||
let editor = view.columnSettingsSidebar.attributesTab.expressionEditor;
|
||||
await editor.textarea.focus();
|
||||
await editor.textarea.clear();
|
||||
|
||||
await editor.textarea.type(
|
||||
"'0000000000000000000000000000000000000000000000000000000000",
|
||||
);
|
||||
await checkWidth();
|
||||
await editor.textarea.clear();
|
||||
await checkWidth();
|
||||
});
|
||||
|
||||
test("tabs > preserves selected tab when toggling and saving expression", async ({
|
||||
page,
|
||||
}) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({
|
||||
expressions: {
|
||||
expr: "1234",
|
||||
},
|
||||
columns: ["Row ID"],
|
||||
settings: true,
|
||||
});
|
||||
|
||||
const col =
|
||||
await view.settingsPanel.inactiveColumns.getColumnByName("expr");
|
||||
await col.editBtn.click();
|
||||
await view.columnSettingsSidebar.openTab("Attributes");
|
||||
await checkTab(view.columnSettingsSidebar, false, true);
|
||||
const selectedTab = async () => {
|
||||
return await view.columnSettingsSidebar.selectedTab
|
||||
.locator(".tab-title")
|
||||
.getAttribute("id");
|
||||
};
|
||||
|
||||
expect(await selectedTab()).toBe("Attributes");
|
||||
await col.activeBtn.click();
|
||||
await view.columnSettingsSidebar.container
|
||||
.locator(".tab-title#Style")
|
||||
.waitFor({ state: "visible" });
|
||||
|
||||
await checkTab(view.columnSettingsSidebar, true, true, true);
|
||||
expect(await selectedTab()).toBe("Attributes");
|
||||
await view.columnSettingsSidebar.attributesTab.expressionEditor.textarea.clear();
|
||||
await view.columnSettingsSidebar.attributesTab.expressionEditor.textarea.type(
|
||||
"'new expr value'",
|
||||
);
|
||||
|
||||
await view.columnSettingsSidebar.attributesTab.saveBtn.click();
|
||||
expect(await selectedTab()).toBe("Attributes");
|
||||
});
|
||||
|
||||
test("color range > resets to default when switching to a different number column", async ({
|
||||
page,
|
||||
}) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({
|
||||
plugin: "Debug Styled",
|
||||
columns: ["Row ID", "Postal Code"],
|
||||
settings: true,
|
||||
});
|
||||
|
||||
const defaultNegColor = "#ff471e";
|
||||
|
||||
let settingsPanel = await view.openSettingsPanel();
|
||||
|
||||
let activeColumns = settingsPanel.activeColumns;
|
||||
let firstCol = await activeColumns.getColumnByName("Row ID");
|
||||
let secondCol = await activeColumns.getColumnByName("Postal Code");
|
||||
|
||||
// edit first number column
|
||||
await firstCol.editBtn.waitFor();
|
||||
await firstCol.editBtn.click();
|
||||
await checkTab(view.columnSettingsSidebar, true, false, false);
|
||||
|
||||
// expect style tab is selected
|
||||
const selectedTab = async () => {
|
||||
return await view.columnSettingsSidebar.selectedTab
|
||||
.locator(".tab-title")
|
||||
.getAttribute("id");
|
||||
};
|
||||
expect(await selectedTab()).toBe("Style");
|
||||
const getFgColorNeg = async () => {
|
||||
return view.columnSettingsSidebar.styleTab.container.locator(
|
||||
"input.neg_fg_color",
|
||||
);
|
||||
};
|
||||
|
||||
let fgColorNeg = await getFgColorNeg();
|
||||
expect(fgColorNeg).toBeTruthy();
|
||||
expect(fgColorNeg).toBeVisible();
|
||||
expect(fgColorNeg).toBeEditable();
|
||||
expect(fgColorNeg).toHaveValue(defaultNegColor);
|
||||
|
||||
// change -ve color from default
|
||||
await fgColorNeg.fill("#ffff00");
|
||||
|
||||
let negColorValue = await fgColorNeg.inputValue();
|
||||
expect(negColorValue).toBe("#ffff00");
|
||||
|
||||
// edit second number column
|
||||
await secondCol.editBtn.waitFor();
|
||||
await secondCol.editBtn.click();
|
||||
|
||||
// expect -ve color input on color range is present
|
||||
fgColorNeg = await getFgColorNeg();
|
||||
|
||||
expect(fgColorNeg).toBeVisible();
|
||||
|
||||
// expect -ve color input updates and switches back to default
|
||||
negColorValue = await fgColorNeg.inputValue();
|
||||
expect(negColorValue).toBe(defaultNegColor);
|
||||
|
||||
// edit first number column
|
||||
await firstCol.editBtn.waitFor();
|
||||
await firstCol.editBtn.click();
|
||||
|
||||
// expect -ve color input on color range is present
|
||||
fgColorNeg = await getFgColorNeg();
|
||||
|
||||
expect(fgColorNeg).toBeVisible();
|
||||
|
||||
// expect -ve color input updates and switches back to the changed value
|
||||
negColorValue = await fgColorNeg.inputValue();
|
||||
expect(negColorValue).toBe("#ffff00");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,200 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { test, expect } from "@perspective-dev/test";
|
||||
import { compareContentsToSnapshot } from "@perspective-dev/test";
|
||||
import { PageView } from "@perspective-dev/test";
|
||||
import {
|
||||
ACTIVE_DRAG,
|
||||
getSettingsPanelContents,
|
||||
INACTIVE_DRAG,
|
||||
shadowDragCancel,
|
||||
} from "./dragdrop_test_utils";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(
|
||||
"/rust/perspective-viewer/test/html/column-settings-enabled.html",
|
||||
);
|
||||
await page.evaluate(async () => {
|
||||
while (!(window as any)["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Drag and Drop", () => {
|
||||
// Cancel — drag starts but no drop occurs.
|
||||
// These verify the panel returns to its original state with no side-effects.
|
||||
test.describe("cancel", () => {
|
||||
test("drag inactive column and cancel (dragend with no drop)", async ({
|
||||
page,
|
||||
}) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({ settings: true, columns: ["Sales"] });
|
||||
await shadowDragCancel(
|
||||
page,
|
||||
view.container.locator(INACTIVE_DRAG).first(),
|
||||
);
|
||||
|
||||
const config = await view.save();
|
||||
expect(config.columns).toEqual(["Sales"]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("drag active column and cancel (dragend with no drop)", async ({
|
||||
page,
|
||||
}) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({
|
||||
settings: true,
|
||||
columns: ["Sales", "Profit"],
|
||||
});
|
||||
|
||||
await shadowDragCancel(
|
||||
page,
|
||||
view.container.locator(ACTIVE_DRAG).first(),
|
||||
);
|
||||
|
||||
const config = await view.save();
|
||||
expect(config.columns).toEqual(["Sales", "Profit"]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("drag inactive column over wrong target (status bar) then cancel", async ({
|
||||
page,
|
||||
}) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({ settings: true, columns: ["Sales"] });
|
||||
|
||||
// Hover over the status bar which is not a valid drop target.
|
||||
await shadowDragCancel(
|
||||
page,
|
||||
view.container.locator(INACTIVE_DRAG).first(),
|
||||
view.container.locator("#status_bar"),
|
||||
);
|
||||
|
||||
const config = await view.save();
|
||||
expect(config.columns).toEqual(["Sales"]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("drag inactive column over Group By then cancel before drop", async ({
|
||||
page,
|
||||
}) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({ settings: true, columns: ["Sales"] });
|
||||
await shadowDragCancel(
|
||||
page,
|
||||
view.container.locator(INACTIVE_DRAG).first(),
|
||||
view.container.locator("#group_by"),
|
||||
);
|
||||
|
||||
const config = await view.save();
|
||||
expect(config.group_by).toEqual([]);
|
||||
expect(config.columns).toEqual(["Sales"]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("drag inactive column over Split By then cancel before drop", async ({
|
||||
page,
|
||||
}) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({ settings: true, columns: ["Sales"] });
|
||||
await shadowDragCancel(
|
||||
page,
|
||||
view.container.locator(INACTIVE_DRAG).first(),
|
||||
view.container.locator("#split_by"),
|
||||
);
|
||||
const config = await view.save();
|
||||
expect(config.split_by).toEqual([]);
|
||||
expect(config.columns).toEqual(["Sales"]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("drag inactive column over Sort then cancel before drop", async ({
|
||||
page,
|
||||
}) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({ settings: true, columns: ["Sales"] });
|
||||
await shadowDragCancel(
|
||||
page,
|
||||
view.container.locator(INACTIVE_DRAG).first(),
|
||||
view.container.locator("#sort"),
|
||||
);
|
||||
const config = await view.save();
|
||||
expect(config.sort).toEqual([]);
|
||||
expect(config.columns).toEqual(["Sales"]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("drag inactive column over Filter then cancel before drop", async ({
|
||||
page,
|
||||
}) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({ settings: true, columns: ["Sales"] });
|
||||
await shadowDragCancel(
|
||||
page,
|
||||
view.container.locator(INACTIVE_DRAG).first(),
|
||||
view.container.locator("#filter"),
|
||||
);
|
||||
const config = await view.save();
|
||||
expect(config.filter).toEqual([]);
|
||||
expect(config.columns).toEqual(["Sales"]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("drag inactive column outside component (simulate drop outside browser)", async ({
|
||||
page,
|
||||
}) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({ settings: true, columns: ["Sales"] });
|
||||
|
||||
// Start a drag then immediately cancel — no hover over any target.
|
||||
await shadowDragCancel(
|
||||
page,
|
||||
view.container.locator(INACTIVE_DRAG).first(),
|
||||
);
|
||||
|
||||
const config = await view.save();
|
||||
expect(config.columns).toEqual(["Sales"]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("drag active column outside component (simulate drop outside browser)", async ({
|
||||
page,
|
||||
}) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({
|
||||
settings: true,
|
||||
columns: ["Sales", "Profit"],
|
||||
});
|
||||
|
||||
await shadowDragCancel(
|
||||
page,
|
||||
view.container.locator(ACTIVE_DRAG).first(),
|
||||
);
|
||||
|
||||
const config = await view.save();
|
||||
expect(config.columns).toEqual(["Sales", "Profit"]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,103 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { Locator, Page } from "@playwright/test";
|
||||
|
||||
/** The draggable handle of the first visible inactive column. */
|
||||
export const INACTIVE_DRAG =
|
||||
"#sub-columns .column-selector-column:not(.column-selector-column-hidden) .column-selector-draggable";
|
||||
|
||||
/** The draggable handle of the first active column. */
|
||||
export const ACTIVE_DRAG =
|
||||
"#active-columns .column-selector-column .column-selector-draggable";
|
||||
|
||||
/** Returns the `#settings_panel` innerHTML from the viewer's shadow root. */
|
||||
export async function getSettingsPanelContents(page: Page) {
|
||||
return page.evaluate(() => {
|
||||
const viewer = document.querySelector("perspective-viewer")!;
|
||||
return viewer.shadowRoot!.querySelector("#settings_panel")!.innerHTML;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates a real browser drag from `src` to `tgt` using mouse events, without
|
||||
* completing the drop. The drag is left in-flight so the caller can snapshot the
|
||||
* intermediate dragover UI state.
|
||||
*/
|
||||
export async function shadowDragOver(page: Page, src: Locator, tgt: Locator) {
|
||||
const srcBox = (await src.boundingBox())!;
|
||||
const tgtBox = (await tgt.boundingBox())!;
|
||||
const srcX = srcBox.x + srcBox.width / 2;
|
||||
const srcY = srcBox.y + srcBox.height / 2;
|
||||
const tgtX = tgtBox.x + tgtBox.width / 2;
|
||||
const tgtY = tgtBox.y + tgtBox.height / 2;
|
||||
|
||||
await page.mouse.move(srcX, srcY);
|
||||
await page.mouse.down();
|
||||
|
||||
// Small initial move to trigger the browser's drag-start threshold.
|
||||
await page.mouse.move(srcX + 5, srcY, { steps: 2 });
|
||||
|
||||
// Move to the target center, generating dragenter + dragover events.
|
||||
await page.mouse.move(tgtX, tgtY, { steps: 10 });
|
||||
|
||||
// Allow Yew to process events and re-render.
|
||||
await page.waitForTimeout(100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates a real browser drag from `src`, optionally hovers over
|
||||
* `wrongTgt`, then presses Escape to cancel — firing `dragend` without a
|
||||
* `drop`.
|
||||
*/
|
||||
export async function shadowDragCancel(
|
||||
page: Page,
|
||||
src: Locator,
|
||||
wrongTgt: Locator | null = null,
|
||||
) {
|
||||
const srcBox = (await src.boundingBox())!;
|
||||
const srcX = srcBox.x + srcBox.width / 2;
|
||||
const srcY = srcBox.y + srcBox.height / 2;
|
||||
|
||||
await page.evaluate(() => {
|
||||
window["dragend_resolvers"] = Promise.withResolvers();
|
||||
document.body.addEventListener("dragend", () => {
|
||||
window["dragend_resolvers"].resolve();
|
||||
});
|
||||
});
|
||||
|
||||
await page.mouse.move(srcX, srcY);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(srcX + 5, srcY, { steps: 2 });
|
||||
|
||||
if (wrongTgt) {
|
||||
const wrongBox = (await wrongTgt.boundingBox())!;
|
||||
await page.mouse.move(
|
||||
wrongBox.x + wrongBox.width / 2,
|
||||
wrongBox.y + wrongBox.height / 2,
|
||||
{ steps: 10 },
|
||||
);
|
||||
}
|
||||
|
||||
// Drag is a cruel mistress.
|
||||
await page.waitForTimeout(100);
|
||||
|
||||
// Cancel the drag (fires dragend with no preceding drop).
|
||||
await page.keyboard.press("Escape");
|
||||
await page.evaluate(async () => {
|
||||
await window["dragend_resolvers"].promise;
|
||||
});
|
||||
}
|
||||
|
||||
export async function localDrag(page: any, source: any, target: any) {
|
||||
await source.dragTo(target, { steps: 100 });
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { test, expect } from "@perspective-dev/test";
|
||||
import { compareContentsToSnapshot } from "../helpers.ts";
|
||||
import { PageView } from "@perspective-dev/test";
|
||||
import {
|
||||
ACTIVE_DRAG,
|
||||
getSettingsPanelContents,
|
||||
INACTIVE_DRAG,
|
||||
localDrag,
|
||||
shadowDragOver,
|
||||
} from "./dragdrop_test_utils";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(
|
||||
"/rust/perspective-viewer/test/html/column-settings-enabled.html",
|
||||
);
|
||||
await page.evaluate(async () => {
|
||||
while (!(window as any)["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Drag and Drop", () => {
|
||||
// Drag over a target without releasing (no drop)
|
||||
// Tests the intermediate UI state — dragover highlights, etc.
|
||||
test.describe("dragover (without drop)", () => {
|
||||
test("inactive column dragged over Active Columns", async ({
|
||||
page,
|
||||
}) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({ settings: true, columns: ["Sales"] });
|
||||
await shadowDragOver(
|
||||
page,
|
||||
view.container.locator(INACTIVE_DRAG).first(),
|
||||
view.container
|
||||
.locator("#active-columns .column-selector-column")
|
||||
.nth(0),
|
||||
);
|
||||
|
||||
const config = await view.save();
|
||||
expect(config.columns).toEqual(["Sales"]);
|
||||
expect(config.columns).toEqual(["Sales"]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await page.pause();
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("inactive column dragged over Group By", async ({ page }) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({ settings: true, columns: ["Sales"] });
|
||||
await shadowDragOver(
|
||||
page,
|
||||
view.container.locator(INACTIVE_DRAG).first(),
|
||||
view.container.locator("#group_by"),
|
||||
);
|
||||
const config = await view.save();
|
||||
expect(config.group_by).toEqual([]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("inactive column dragged over Split By", async ({ page }) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({ settings: true, columns: ["Sales"] });
|
||||
await shadowDragOver(
|
||||
page,
|
||||
view.container.locator(INACTIVE_DRAG).first(),
|
||||
view.container.locator("#split_by"),
|
||||
);
|
||||
const config = await view.save();
|
||||
expect(config.split_by).toEqual([]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("inactive column dragged over Sort", async ({ page }) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({ settings: true, columns: ["Sales"] });
|
||||
await shadowDragOver(
|
||||
page,
|
||||
view.container.locator(INACTIVE_DRAG).first(),
|
||||
view.container.locator("#sort"),
|
||||
);
|
||||
const config = await view.save();
|
||||
expect(config.sort).toEqual([]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("inactive column dragged over Filter", async ({ page }) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({ settings: true, columns: ["Sales"] });
|
||||
await shadowDragOver(
|
||||
page,
|
||||
view.container.locator(INACTIVE_DRAG).first(),
|
||||
view.container.locator("#filter"),
|
||||
);
|
||||
const config = await view.save();
|
||||
expect(config.filter).toEqual([]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("active column dragged over Group By", async ({ page }) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({
|
||||
settings: true,
|
||||
columns: ["Sales", "Profit"],
|
||||
});
|
||||
|
||||
await shadowDragOver(
|
||||
page,
|
||||
view.container.locator(ACTIVE_DRAG).first(),
|
||||
view.container.locator("#group_by"),
|
||||
);
|
||||
const config = await view.save();
|
||||
expect(config.group_by).toEqual([]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("active column dragged over Split By", async ({ page }) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({
|
||||
settings: true,
|
||||
columns: ["Sales", "Profit"],
|
||||
});
|
||||
|
||||
await shadowDragOver(
|
||||
page,
|
||||
view.container.locator(ACTIVE_DRAG).first(),
|
||||
view.container.locator("#split_by"),
|
||||
);
|
||||
const config = await view.save();
|
||||
expect(config.split_by).toEqual([]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("active column dragged over Sort", async ({ page }) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({
|
||||
settings: true,
|
||||
columns: ["Sales", "Profit"],
|
||||
});
|
||||
|
||||
await shadowDragOver(
|
||||
page,
|
||||
view.container.locator(ACTIVE_DRAG).first(),
|
||||
view.container.locator("#sort"),
|
||||
);
|
||||
const config = await view.save();
|
||||
expect(config.sort).toEqual([]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("active column dragged over Filter", async ({ page }) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({
|
||||
settings: true,
|
||||
columns: ["Sales", "Profit"],
|
||||
});
|
||||
|
||||
await shadowDragOver(
|
||||
page,
|
||||
view.container.locator(ACTIVE_DRAG).first(),
|
||||
view.container.locator("#filter"),
|
||||
);
|
||||
|
||||
const config = await view.save();
|
||||
expect(config.filter).toEqual([]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,182 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { test, expect } from "@perspective-dev/test";
|
||||
import { compareContentsToSnapshot } from "../helpers.ts";
|
||||
import { PageView } from "@perspective-dev/test";
|
||||
import {
|
||||
ACTIVE_DRAG,
|
||||
getSettingsPanelContents,
|
||||
INACTIVE_DRAG,
|
||||
localDrag,
|
||||
} from "./dragdrop_test_utils";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(
|
||||
"/rust/perspective-viewer/test/html/column-settings-enabled.html",
|
||||
);
|
||||
await page.evaluate(async () => {
|
||||
while (!(window as any)["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Drag and Drop", () => {
|
||||
test.describe("drop", () => {
|
||||
test("inactive to first active columns", async ({ page }) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({
|
||||
settings: true,
|
||||
columns: ["Sales", "Profit"],
|
||||
});
|
||||
|
||||
const configUpdated = await view.getEventListener(
|
||||
"perspective-config-update",
|
||||
);
|
||||
|
||||
const source = view.container.locator(INACTIVE_DRAG).first();
|
||||
const target = view.container
|
||||
.locator("#active-columns .column-selector-column")
|
||||
.nth(0);
|
||||
|
||||
await localDrag(page, source, target);
|
||||
await configUpdated();
|
||||
const config = await view.save();
|
||||
expect(config.columns).toEqual(["Category", "Sales", "Profit"]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("inactive to second active columns", async ({ page }) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({
|
||||
settings: true,
|
||||
columns: ["Sales", "Profit"],
|
||||
});
|
||||
|
||||
const configUpdated = await view.getEventListener(
|
||||
"perspective-config-update",
|
||||
);
|
||||
|
||||
const source = view.container.locator(INACTIVE_DRAG).first();
|
||||
const target = view.container
|
||||
.locator("#active-columns .column-selector-column")
|
||||
.nth(1);
|
||||
|
||||
await localDrag(page, source, target);
|
||||
await configUpdated();
|
||||
const config = await view.save();
|
||||
expect(config.columns).toEqual(["Sales", "Category", "Profit"]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("active to active (reorder)", async ({ page }) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({
|
||||
settings: true,
|
||||
columns: ["Sales", "Profit", "Quantity"],
|
||||
});
|
||||
|
||||
const configUpdated = await view.getEventListener(
|
||||
"perspective-config-update",
|
||||
);
|
||||
|
||||
const source = view.container.locator(ACTIVE_DRAG).first();
|
||||
|
||||
// Drop the first active column onto the third position.
|
||||
const target = view.container
|
||||
.locator("#active-columns .column-selector-column")
|
||||
.nth(2);
|
||||
|
||||
await localDrag(page, source, target);
|
||||
await configUpdated();
|
||||
const config = await view.save();
|
||||
expect(config.columns).toEqual(["Profit", "Quantity", "Sales"]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("inactive to Group By", async ({ page }) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({ settings: true, columns: ["Sales"] });
|
||||
const configUpdated = await view.getEventListener(
|
||||
"perspective-config-update",
|
||||
);
|
||||
|
||||
const source = view.container.locator(INACTIVE_DRAG).first();
|
||||
const target = view.container.locator("#group_by");
|
||||
await localDrag(page, source, target);
|
||||
await configUpdated();
|
||||
const config = await view.save();
|
||||
expect(config.group_by).toEqual(["Category"]);
|
||||
expect(config.columns).toEqual(["Sales"]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("inactive to Split By", async ({ page }) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({ settings: true, columns: ["Sales"] });
|
||||
const configUpdated = await view.getEventListener(
|
||||
"perspective-config-update",
|
||||
);
|
||||
|
||||
const source = view.container.locator(INACTIVE_DRAG).first();
|
||||
const target = view.container.locator("#split_by");
|
||||
await localDrag(page, source, target);
|
||||
await configUpdated();
|
||||
const config = await view.save();
|
||||
expect(config.split_by).toEqual(["Category"]);
|
||||
expect(config.columns).toEqual(["Sales"]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("inactive to Sort", async ({ page }) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({ settings: true, columns: ["Sales"] });
|
||||
const configUpdated = await view.getEventListener(
|
||||
"perspective-config-update",
|
||||
);
|
||||
|
||||
const source = view.container.locator(INACTIVE_DRAG).first();
|
||||
const target = view.container.locator("#sort");
|
||||
await localDrag(page, source, target);
|
||||
await configUpdated();
|
||||
const config = await view.save();
|
||||
expect(config.columns).toEqual(["Sales"]);
|
||||
expect(config.sort).toEqual([["Category", "asc"]]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("inactive to Filter", async ({ page }) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({ settings: true, columns: ["Sales"] });
|
||||
const configUpdated = await view.getEventListener(
|
||||
"perspective-config-update",
|
||||
);
|
||||
|
||||
const source = view.container.locator(INACTIVE_DRAG).first();
|
||||
const target = view.container.locator("#filter");
|
||||
await localDrag(page, source, target);
|
||||
await configUpdated();
|
||||
const config = await view.save();
|
||||
expect(config.filter).toEqual([["Category", "==", null]]);
|
||||
expect(config.columns).toEqual(["Sales"]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,209 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { test, expect } from "@perspective-dev/test";
|
||||
import { compareContentsToSnapshot } from "@perspective-dev/test";
|
||||
import { PageView } from "@perspective-dev/test";
|
||||
import {
|
||||
ACTIVE_DRAG,
|
||||
getSettingsPanelContents,
|
||||
INACTIVE_DRAG,
|
||||
localDrag,
|
||||
} from "./dragdrop_test_utils";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(
|
||||
"/rust/perspective-viewer/test/html/column-settings-enabled.html",
|
||||
);
|
||||
await page.evaluate(async () => {
|
||||
while (!(window as any)["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Drag and Drop", () => {
|
||||
test.describe("drop with Column Settings Sidebar open", () => {
|
||||
test("inactive to active while sidebar is open on the active column", async ({
|
||||
page,
|
||||
}) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({ settings: true, columns: ["Sales"] });
|
||||
|
||||
// Open the sidebar on the current active column.
|
||||
const activeCol =
|
||||
view.settingsPanel.activeColumns.getFirstVisibleColumn();
|
||||
|
||||
await activeCol.editBtn.click();
|
||||
await view.columnSettingsSidebar.container.waitFor({
|
||||
state: "visible",
|
||||
});
|
||||
|
||||
const configUpdated = await view.getEventListener(
|
||||
"perspective-config-update",
|
||||
);
|
||||
|
||||
const source = view.container.locator(INACTIVE_DRAG).first();
|
||||
const target = activeCol.container;
|
||||
await localDrag(page, source, target);
|
||||
await configUpdated();
|
||||
const config = await view.save();
|
||||
expect(config.columns).toEqual(["Category", "Sales"]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("active to Group By while sidebar is open on the dragged column", async ({
|
||||
page,
|
||||
}) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({
|
||||
settings: true,
|
||||
columns: ["Category", "Profit"],
|
||||
});
|
||||
|
||||
// Open the sidebar on the first active column (Sales).
|
||||
const activeCol =
|
||||
view.settingsPanel.activeColumns.getFirstVisibleColumn();
|
||||
await activeCol.editBtn.click();
|
||||
await view.columnSettingsSidebar.container.waitFor({
|
||||
state: "visible",
|
||||
});
|
||||
|
||||
const configUpdated = await view.getEventListener(
|
||||
"perspective-config-update",
|
||||
);
|
||||
|
||||
const source = view.container.locator(ACTIVE_DRAG).first();
|
||||
const target = view.container.locator("#group_by");
|
||||
await localDrag(page, source, target);
|
||||
await configUpdated();
|
||||
const config = await view.save();
|
||||
expect(config.group_by).toEqual(["Category"]);
|
||||
expect(config.columns).toEqual(["Profit"]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
// TODO
|
||||
|
||||
test("inactive to Group By while sidebar is open on a different column", async ({
|
||||
page,
|
||||
}) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({
|
||||
settings: true,
|
||||
columns: ["Sales", "Profit"],
|
||||
});
|
||||
|
||||
// Open sidebar on first active column.
|
||||
const activeCol =
|
||||
view.settingsPanel.activeColumns.getFirstVisibleColumn();
|
||||
await activeCol.editBtn.click();
|
||||
await view.columnSettingsSidebar.container.waitFor({
|
||||
state: "visible",
|
||||
});
|
||||
|
||||
const configUpdated = await view.getEventListener(
|
||||
"perspective-config-update",
|
||||
);
|
||||
|
||||
const source = view.container.locator(INACTIVE_DRAG).first();
|
||||
const target = view.container.locator("#group_by");
|
||||
await localDrag(page, source, target);
|
||||
await configUpdated();
|
||||
const config = await view.save();
|
||||
expect(config.group_by).toEqual(["Category"]);
|
||||
expect(config.columns).toEqual(["Sales", "Profit"]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("inactive to Split By while sidebar is open", async ({ page }) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({ settings: true, columns: ["Sales"] });
|
||||
|
||||
const activeCol =
|
||||
view.settingsPanel.activeColumns.getFirstVisibleColumn();
|
||||
await activeCol.editBtn.click();
|
||||
await view.columnSettingsSidebar.container.waitFor({
|
||||
state: "visible",
|
||||
});
|
||||
|
||||
const configUpdated = await view.getEventListener(
|
||||
"perspective-config-update",
|
||||
);
|
||||
|
||||
const source = view.container.locator(INACTIVE_DRAG).first();
|
||||
const target = view.container.locator("#split_by");
|
||||
await localDrag(page, source, target);
|
||||
await configUpdated();
|
||||
const config = await view.save();
|
||||
expect(config.split_by).toEqual(["Category"]);
|
||||
expect(config.columns).toEqual(["Sales"]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("inactive to Filter while sidebar is open", async ({ page }) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({ settings: true, columns: ["Sales"] });
|
||||
|
||||
const activeCol =
|
||||
view.settingsPanel.activeColumns.getFirstVisibleColumn();
|
||||
await activeCol.editBtn.click();
|
||||
await view.columnSettingsSidebar.container.waitFor({
|
||||
state: "visible",
|
||||
});
|
||||
|
||||
const configUpdated = await view.getEventListener(
|
||||
"perspective-config-update",
|
||||
);
|
||||
|
||||
const source = view.container.locator(INACTIVE_DRAG).first();
|
||||
const target = view.container.locator("#filter");
|
||||
await localDrag(page, source, target);
|
||||
await configUpdated();
|
||||
const config = await view.save();
|
||||
expect(config.filter).toEqual([["Category", "==", null]]);
|
||||
expect(config.columns).toEqual(["Sales"]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("inactive to Sort while sidebar is open", async ({ page }) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({ settings: true, columns: ["Sales"] });
|
||||
|
||||
const activeCol =
|
||||
view.settingsPanel.activeColumns.getFirstVisibleColumn();
|
||||
await activeCol.editBtn.click();
|
||||
await view.columnSettingsSidebar.container.waitFor({
|
||||
state: "visible",
|
||||
});
|
||||
|
||||
const configUpdated = await view.getEventListener(
|
||||
"perspective-config-update",
|
||||
);
|
||||
|
||||
const source = view.container.locator(INACTIVE_DRAG).first();
|
||||
const target = view.container.locator("#sort");
|
||||
await localDrag(page, source, target);
|
||||
await configUpdated();
|
||||
const config = await view.save();
|
||||
expect(config.sort).toEqual([["Category", "asc"]]);
|
||||
expect(config.columns).toEqual(["Sales"]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,229 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { test, expect } from "@perspective-dev/test";
|
||||
import { compareContentsToSnapshot } from "../helpers.ts";
|
||||
import { PageView } from "@perspective-dev/test";
|
||||
import {
|
||||
ACTIVE_DRAG,
|
||||
getSettingsPanelContents,
|
||||
INACTIVE_DRAG,
|
||||
localDrag,
|
||||
} from "./dragdrop_test_utils";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(
|
||||
"/rust/perspective-viewer/test/html/column-selector-modes.html",
|
||||
);
|
||||
await page.evaluate(async () => {
|
||||
while (!(window as any)["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Drag and Drop", () => {
|
||||
test.describe("drop", () => {
|
||||
test("inactive to first active columns", async ({ page }) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({
|
||||
settings: true,
|
||||
columns: ["Sales", "Profit"],
|
||||
});
|
||||
|
||||
const configUpdated = await view.getEventListener(
|
||||
"perspective-config-update",
|
||||
);
|
||||
|
||||
const source = view.container.locator(INACTIVE_DRAG).first();
|
||||
const target = view.container
|
||||
.locator("#active-columns .column-selector-column")
|
||||
.nth(0);
|
||||
|
||||
await localDrag(page, source, target);
|
||||
await configUpdated();
|
||||
const config = await view.save();
|
||||
expect(config.columns).toEqual([
|
||||
"Category",
|
||||
"Profit",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("inactive to second active columns", async ({ page }) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({
|
||||
settings: true,
|
||||
columns: ["Sales", "Profit"],
|
||||
});
|
||||
|
||||
const configUpdated = await view.getEventListener(
|
||||
"perspective-config-update",
|
||||
);
|
||||
|
||||
const source = view.container.locator(INACTIVE_DRAG).first();
|
||||
const target = view.container
|
||||
.locator("#active-columns .column-selector-column")
|
||||
.nth(1);
|
||||
|
||||
await localDrag(page, source, target);
|
||||
await configUpdated();
|
||||
const config = await view.save();
|
||||
expect(config.columns).toEqual([
|
||||
"Sales",
|
||||
"Category",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("inactive to third active columns", async ({ page }) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({
|
||||
settings: true,
|
||||
columns: ["Sales", "Profit"],
|
||||
});
|
||||
|
||||
const configUpdated = await view.getEventListener(
|
||||
"perspective-config-update",
|
||||
);
|
||||
|
||||
const source = view.container.locator(INACTIVE_DRAG).first();
|
||||
const target = view.container
|
||||
.locator("#active-columns .column-selector-column")
|
||||
.nth(2);
|
||||
|
||||
await localDrag(page, source, target);
|
||||
await configUpdated();
|
||||
const config = await view.save();
|
||||
expect(config.columns).toEqual([
|
||||
"Sales",
|
||||
"Profit",
|
||||
"Category",
|
||||
null,
|
||||
null,
|
||||
]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("active to active (reorder)", async ({ page }) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({
|
||||
settings: true,
|
||||
columns: ["Sales", "Profit", "Quantity"],
|
||||
});
|
||||
|
||||
const configUpdated = await view.getEventListener(
|
||||
"perspective-config-update",
|
||||
);
|
||||
|
||||
const source = view.container.locator(ACTIVE_DRAG).first();
|
||||
const target = view.container
|
||||
.locator("#active-columns .column-selector-column")
|
||||
.nth(2);
|
||||
|
||||
await localDrag(page, source, target);
|
||||
await configUpdated();
|
||||
const config = await view.save();
|
||||
expect(config.columns).toEqual([
|
||||
"Quantity",
|
||||
"Profit",
|
||||
"Sales",
|
||||
null,
|
||||
null,
|
||||
]);
|
||||
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("inactive to Group By", async ({ page }) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({ settings: true, columns: ["Sales"] });
|
||||
const configUpdated = await view.getEventListener(
|
||||
"perspective-config-update",
|
||||
);
|
||||
|
||||
const source = view.container.locator(INACTIVE_DRAG).first();
|
||||
const target = view.container.locator("#group_by");
|
||||
await localDrag(page, source, target);
|
||||
await configUpdated();
|
||||
const config = await view.save();
|
||||
expect(config.group_by).toEqual(["Category"]);
|
||||
expect(config.columns).toEqual(["Sales"]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("inactive to Split By", async ({ page }) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({ settings: true, columns: ["Sales"] });
|
||||
const configUpdated = await view.getEventListener(
|
||||
"perspective-config-update",
|
||||
);
|
||||
|
||||
const source = view.container.locator(INACTIVE_DRAG).first();
|
||||
const target = view.container.locator("#split_by");
|
||||
await localDrag(page, source, target);
|
||||
await configUpdated();
|
||||
const config = await view.save();
|
||||
expect(config.split_by).toEqual(["Category"]);
|
||||
expect(config.columns).toEqual(["Sales"]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("inactive to Sort", async ({ page }) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({ settings: true, columns: ["Sales"] });
|
||||
const configUpdated = await view.getEventListener(
|
||||
"perspective-config-update",
|
||||
);
|
||||
|
||||
const source = view.container.locator(INACTIVE_DRAG).first();
|
||||
const target = view.container.locator("#sort");
|
||||
await localDrag(page, source, target);
|
||||
await configUpdated();
|
||||
const config = await view.save();
|
||||
expect(config.columns).toEqual(["Sales"]);
|
||||
expect(config.sort).toEqual([["Category", "asc"]]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("inactive to Filter", async ({ page }) => {
|
||||
const view = new PageView(page);
|
||||
await view.restore({ settings: true, columns: ["Sales"] });
|
||||
const configUpdated = await view.getEventListener(
|
||||
"perspective-config-update",
|
||||
);
|
||||
|
||||
const source = view.container.locator(INACTIVE_DRAG).first();
|
||||
const target = view.container.locator("#filter");
|
||||
await localDrag(page, source, target);
|
||||
await configUpdated();
|
||||
const config = await view.save();
|
||||
expect(config.filter).toEqual([["Category", "==", null]]);
|
||||
expect(config.columns).toEqual(["Sales"]);
|
||||
const contents = await getSettingsPanelContents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,129 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import * as url from "node:url";
|
||||
import type { ViewerConfigUpdate } from "@perspective-dev/viewer";
|
||||
import type { Locator, Page } from "@playwright/test";
|
||||
import * as prettier from "prettier";
|
||||
|
||||
// Re-export test framework (preserves consoleLogs auto-fixture)
|
||||
export { test, expect } from "@perspective-dev/test";
|
||||
|
||||
// Re-export page object models
|
||||
export { PageView } from "@perspective-dev/test";
|
||||
export { ColumnSettingsSidebar } from "@perspective-dev/test/src/js/models/column_settings.ts";
|
||||
export { ColumnSelector } from "@perspective-dev/test/src/js/models/settings_panel.ts";
|
||||
|
||||
const __filename = url.fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
export const API_VERSION: string = JSON.parse(
|
||||
fs
|
||||
.readFileSync(
|
||||
path.resolve(__dirname, "../../../../tools/test/package.json"),
|
||||
)
|
||||
.toString(),
|
||||
)["version"];
|
||||
|
||||
export const DEFAULT_CONFIG: ViewerConfigUpdate = {
|
||||
aggregates: {},
|
||||
columns_config: {},
|
||||
columns: [],
|
||||
expressions: {},
|
||||
filter: [],
|
||||
group_by: [],
|
||||
group_rollup_mode: "rollup",
|
||||
plugin: "",
|
||||
plugin_config: {},
|
||||
settings: false,
|
||||
sort: [],
|
||||
split_by: [],
|
||||
version: API_VERSION,
|
||||
table: "load-viewer-csv",
|
||||
title: null,
|
||||
theme: "Pro Light",
|
||||
};
|
||||
|
||||
export async function compareContentsToSnapshot(
|
||||
contents: string,
|
||||
extraSnapshotPath?: string[],
|
||||
): Promise<void> {
|
||||
const { expect, test } = await import("@playwright/test");
|
||||
let titlePath = test.info().titlePath;
|
||||
if (extraSnapshotPath) {
|
||||
titlePath = titlePath.concat(extraSnapshotPath);
|
||||
}
|
||||
|
||||
const snapshotPath = [
|
||||
titlePath
|
||||
.slice(1)
|
||||
.map((s) =>
|
||||
s
|
||||
.trim()
|
||||
.replace(/[^a-z0-9]+/gi, "-")
|
||||
.toLowerCase(),
|
||||
)
|
||||
.join("-") + ".txt",
|
||||
];
|
||||
|
||||
const pathArray = Array.isArray(snapshotPath)
|
||||
? snapshotPath
|
||||
: [snapshotPath];
|
||||
|
||||
const cleanedContents = contents
|
||||
.replace(/style=""/g, "")
|
||||
.replace(/(min-|max-)?(width|height): *\d+\.*\d+(px)?;? */g, "");
|
||||
|
||||
const formatted = await prettier.format(cleanedContents, {
|
||||
parser: "html",
|
||||
});
|
||||
|
||||
await expect(formatted).toMatchSnapshot(pathArray);
|
||||
}
|
||||
|
||||
export async function compareNodes(
|
||||
left: Locator,
|
||||
right: Locator,
|
||||
page: Page,
|
||||
): Promise<boolean> {
|
||||
const leftEl = await left.elementHandle();
|
||||
const rightEl = await right.elementHandle();
|
||||
return await page.evaluate(
|
||||
async (compare) => {
|
||||
return compare.leftEl?.isEqualNode(compare.rightEl) || false;
|
||||
},
|
||||
{ leftEl, rightEl },
|
||||
);
|
||||
}
|
||||
|
||||
export async function getShadowContents(page: Page): Promise<string> {
|
||||
const raw = await page.evaluate(async () => {
|
||||
const viewer =
|
||||
document.querySelector("perspective-viewer")!.shadowRoot!;
|
||||
return viewer.innerHTML;
|
||||
});
|
||||
|
||||
return await prettier.format(raw, {
|
||||
parser: "html",
|
||||
});
|
||||
}
|
||||
|
||||
export async function getLightContents(page: Page): Promise<string> {
|
||||
return await page.evaluate(async () => {
|
||||
const viewer = document.querySelector(
|
||||
"perspective-viewer perspective-viewer-plugin",
|
||||
)!;
|
||||
return viewer.innerHTML;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import type { ViewerConfigUpdate } from "@perspective-dev/viewer";
|
||||
import { test, compareContentsToSnapshot } from "../helpers.ts";
|
||||
|
||||
export type ContentExtractor = (page: any) => Promise<string>;
|
||||
|
||||
async function restoreViewer(page: any, viewerConfig: ViewerConfigUpdate) {
|
||||
return await page.evaluate(async (viewerConfig: ViewerConfigUpdate) => {
|
||||
const viewer = document.querySelector("perspective-viewer")!;
|
||||
await viewer.restore(viewerConfig);
|
||||
}, viewerConfig);
|
||||
}
|
||||
|
||||
function runSimpleCompareTest(
|
||||
viewerConfig: ViewerConfigUpdate,
|
||||
extractContent: ContentExtractor,
|
||||
snapshotPath: string[],
|
||||
) {
|
||||
return async ({ page }: { page: any }) => {
|
||||
await restoreViewer(page, viewerConfig);
|
||||
const content = await extractContent(page);
|
||||
await compareContentsToSnapshot(content);
|
||||
};
|
||||
}
|
||||
|
||||
export function run_standard_tests(
|
||||
context: string,
|
||||
extractContent: ContentExtractor,
|
||||
) {
|
||||
test("grid > renders without settings panel", async ({ page }) => {
|
||||
await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer")!;
|
||||
await viewer.getTable();
|
||||
await viewer.restore({ settings: true });
|
||||
});
|
||||
|
||||
const content = await extractContent(page);
|
||||
await compareContentsToSnapshot(content);
|
||||
});
|
||||
|
||||
test("columns > displays only visible columns", async ({ page }) => {
|
||||
await restoreViewer(page, {
|
||||
columns: ["Discount", "Profit", "Sales", "Quantity"],
|
||||
});
|
||||
|
||||
const visibleColumnContent = await extractContent(page);
|
||||
await compareContentsToSnapshot(visibleColumnContent);
|
||||
});
|
||||
|
||||
test.describe("Pivot tests", () => {
|
||||
test(
|
||||
"group_by > pivots by a single row",
|
||||
runSimpleCompareTest(
|
||||
{ group_by: ["State"], settings: true },
|
||||
extractContent,
|
||||
[context, `pivot-by-row.txt`],
|
||||
),
|
||||
);
|
||||
|
||||
test(
|
||||
"group_by > pivots by two rows",
|
||||
runSimpleCompareTest(
|
||||
{
|
||||
group_by: ["Category", "Sub-Category"],
|
||||
settings: true,
|
||||
},
|
||||
extractContent,
|
||||
[context, `pivot-by-two-rows.txt`],
|
||||
),
|
||||
);
|
||||
|
||||
test(
|
||||
"split_by > pivots by a single column",
|
||||
runSimpleCompareTest(
|
||||
{ split_by: ["Category"], settings: true },
|
||||
extractContent,
|
||||
[context, `pivot-by-column.txt`],
|
||||
),
|
||||
);
|
||||
|
||||
test(
|
||||
"pivot > pivots by a row and a column",
|
||||
runSimpleCompareTest(
|
||||
{
|
||||
group_by: ["State"],
|
||||
split_by: ["Category"],
|
||||
settings: true,
|
||||
},
|
||||
extractContent,
|
||||
[context, `pivot-by-row-and-column.txt`],
|
||||
),
|
||||
);
|
||||
|
||||
test(
|
||||
"pivot > pivots by two rows and two columns",
|
||||
runSimpleCompareTest(
|
||||
{
|
||||
group_by: ["Region", "State"],
|
||||
split_by: ["Category", "Sub-Category"],
|
||||
settings: true,
|
||||
},
|
||||
extractContent,
|
||||
[context, `pivot-by-two-rows-and-two-columns.txt`],
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test.describe("Sort tests", () => {
|
||||
test(
|
||||
"sort > sorts by a hidden column",
|
||||
runSimpleCompareTest(
|
||||
{
|
||||
columns: ["Row ID", "Quantity"],
|
||||
sort: [["Sales", "asc"]],
|
||||
settings: true,
|
||||
},
|
||||
extractContent,
|
||||
[context, `sort-by-hidden-column.txt`],
|
||||
),
|
||||
);
|
||||
|
||||
test(
|
||||
"sort > sorts by a numeric column",
|
||||
runSimpleCompareTest(
|
||||
{
|
||||
columns: ["Row ID", "Sales"],
|
||||
sort: [["Quantity", "asc"]],
|
||||
settings: true,
|
||||
},
|
||||
extractContent,
|
||||
[context, `sort-by-numeric-column.txt`],
|
||||
),
|
||||
);
|
||||
|
||||
test(
|
||||
"sort > sorts by an alpha column",
|
||||
runSimpleCompareTest(
|
||||
{
|
||||
columns: ["Row ID", "State", "Sales"],
|
||||
sort: [["State", "asc"]],
|
||||
settings: true,
|
||||
},
|
||||
extractContent,
|
||||
[context, `sort-by-alpha-column.txt`],
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test.describe("Filter tests", () => {
|
||||
test(
|
||||
"filter > filters by a numeric column",
|
||||
runSimpleCompareTest(
|
||||
{
|
||||
columns: ["Row ID", "State", "Sales"],
|
||||
filter: [["Sales", ">", 500]],
|
||||
settings: true,
|
||||
},
|
||||
extractContent,
|
||||
[context, `filter-by-numeric-column.txt`],
|
||||
),
|
||||
);
|
||||
|
||||
test(
|
||||
"filter > filters by an alpha column",
|
||||
runSimpleCompareTest(
|
||||
{
|
||||
columns: ["Row ID", "State", "Sales"],
|
||||
filter: [["State", "==", "Texas"]],
|
||||
settings: true,
|
||||
},
|
||||
extractContent,
|
||||
[context, `filter-by-alpha-column.txt`],
|
||||
),
|
||||
);
|
||||
|
||||
test(
|
||||
"filter > filters with 'in' comparator",
|
||||
runSimpleCompareTest(
|
||||
{
|
||||
columns: ["Row ID", "State", "Sales"],
|
||||
filter: [["State", "in", ["Texas", "California"]]],
|
||||
settings: true,
|
||||
},
|
||||
extractContent,
|
||||
[context, `filter-with-in-comparator.txt`],
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { test, expect, DEFAULT_CONFIG, API_VERSION } from "../helpers.ts";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(
|
||||
"/node_modules/@perspective-dev/viewer/test/html/plugin-priority-order.html",
|
||||
);
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Close button", () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("/rust/perspective-viewer/test/html/superstore.html");
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
|
||||
await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.restore({ plugin: "Debug" });
|
||||
await viewer.getTable();
|
||||
await viewer.restore({ settings: true });
|
||||
});
|
||||
});
|
||||
|
||||
test("close button hides the settings panel", async ({ page }) => {
|
||||
const shadowRoot = page.locator("perspective-viewer");
|
||||
await shadowRoot.locator("#settings_close_button").first().click();
|
||||
|
||||
const saved = await page.evaluate(async () =>
|
||||
document.querySelector("perspective-viewer").save(),
|
||||
);
|
||||
|
||||
expect(saved.settings).toBe(false);
|
||||
});
|
||||
|
||||
test("settings panel round-trips open/close via save()", async ({
|
||||
page,
|
||||
}) => {
|
||||
// Open → close via API → open again. save() must reflect each state.
|
||||
const saved1 = await page.evaluate(async () =>
|
||||
document.querySelector("perspective-viewer").save(),
|
||||
);
|
||||
expect(saved1.settings).toBe(true);
|
||||
|
||||
await page.evaluate(async () =>
|
||||
document.querySelector("perspective-viewer").restore({
|
||||
settings: false,
|
||||
}),
|
||||
);
|
||||
|
||||
const saved2 = await page.evaluate(async () =>
|
||||
document.querySelector("perspective-viewer").save(),
|
||||
);
|
||||
|
||||
expect(saved2.settings).toBe(false);
|
||||
|
||||
await page.evaluate(async () =>
|
||||
document.querySelector("perspective-viewer").restore({
|
||||
settings: true,
|
||||
}),
|
||||
);
|
||||
|
||||
const saved3 = await page.evaluate(async () =>
|
||||
document.querySelector("perspective-viewer").save(),
|
||||
);
|
||||
|
||||
expect(saved3.settings).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { test, expect, DEFAULT_CONFIG, API_VERSION } from "../helpers.ts";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(
|
||||
"/node_modules/@perspective-dev/viewer/test/html/plugin-priority-order.html",
|
||||
);
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Plugin Registration", () => {
|
||||
test("priority > loads highest priority plugin by default", async ({
|
||||
page,
|
||||
}) => {
|
||||
let saved = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
window.__TABLE__ = await viewer.getTable();
|
||||
await viewer.reset();
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
const expected = {
|
||||
...DEFAULT_CONFIG,
|
||||
version: API_VERSION,
|
||||
columns: [
|
||||
"Row ID",
|
||||
"Order ID",
|
||||
"Order Date",
|
||||
"Ship Date",
|
||||
"Ship Mode",
|
||||
"Customer ID",
|
||||
"Segment",
|
||||
"Country",
|
||||
"City",
|
||||
"State",
|
||||
"Postal Code",
|
||||
"Region",
|
||||
"Product ID",
|
||||
"Category",
|
||||
"Sub-Category",
|
||||
"Sales",
|
||||
"Quantity",
|
||||
"Discount",
|
||||
"Profit",
|
||||
],
|
||||
plugin: "HighPriority",
|
||||
};
|
||||
|
||||
expect(saved).toEqual(expected);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import {
|
||||
test,
|
||||
expect,
|
||||
compareContentsToSnapshot,
|
||||
getShadowContents,
|
||||
} from "../helpers.ts";
|
||||
|
||||
const get_contents = getShadowContents;
|
||||
|
||||
test.describe("Settings Panel", () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("/rust/perspective-viewer/test/html/superstore.html");
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
|
||||
await page.evaluate(async () => {
|
||||
await document.querySelector("perspective-viewer").restore({
|
||||
plugin: "Debug",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("toggle > opens when settings is true", async ({ page }) => {
|
||||
await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.getTable();
|
||||
await viewer.restore({ settings: true });
|
||||
});
|
||||
|
||||
const contents = await get_contents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("toggle > stays closed when settings is false", async ({ page }) => {
|
||||
await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.getTable();
|
||||
await viewer.restore({ settings: false });
|
||||
});
|
||||
|
||||
const contents = await get_contents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { test, expect } from "../helpers.ts";
|
||||
import * as path from "node:path";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
const p = path.resolve(
|
||||
"rust/perspective-viewer/test/html/superstore-single-threaded.html",
|
||||
);
|
||||
|
||||
await page.goto(`file://${p}`);
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
|
||||
await page.evaluate(async () => {
|
||||
await document.querySelector("perspective-viewer")!.restore({
|
||||
plugin: "Debug",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Single-Threaded Engine", () => {
|
||||
test("render > produces correct output in single-threaded mode", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer")!;
|
||||
await viewer.restore({
|
||||
group_by: ["State"],
|
||||
columns: ["Sales", "Profit"],
|
||||
settings: true,
|
||||
});
|
||||
});
|
||||
|
||||
const value = await page.evaluate(async () => {
|
||||
return document.querySelector("perspective-viewer")!.innerText
|
||||
.length;
|
||||
});
|
||||
|
||||
// Superstore as a CSV
|
||||
expect(value).toEqual(836);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,163 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { test, expect, compareContentsToSnapshot } from "../helpers.ts";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("/rust/perspective-viewer/test/html/superstore.html");
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
await page.evaluate(async () => {
|
||||
await document.querySelector("perspective-viewer").restore({
|
||||
plugin: "Debug",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Memory Leaks", () => {
|
||||
// This originally has a timeout of 120000
|
||||
test("elements > no leak on repeated element recreation", async ({
|
||||
page,
|
||||
}) => {
|
||||
let viewer = await page.$("perspective-viewer");
|
||||
await page.evaluate(async (viewer) => {
|
||||
window.__TABLE__ = await viewer.getTable();
|
||||
await viewer.reset();
|
||||
}, viewer);
|
||||
|
||||
// From a helpful blog
|
||||
// https://media-codings.com/articles/automatically-detect-memory-leaks-with-puppeteer
|
||||
await page.evaluate(() => window.gc());
|
||||
const heap1 = await page.evaluate(
|
||||
() => performance.memory.usedJSHeapSize,
|
||||
);
|
||||
|
||||
for (var i = 0; i < 500; i++) {
|
||||
await page.evaluate(async () => {
|
||||
const element = document.querySelector("perspective-viewer");
|
||||
await element.delete();
|
||||
document.body.innerHTML =
|
||||
"<perspective-viewer></perspective-viewer>";
|
||||
|
||||
const new_element =
|
||||
document.querySelector("perspective-viewer");
|
||||
|
||||
await new_element.load(Promise.resolve(window.__TABLE__));
|
||||
});
|
||||
}
|
||||
|
||||
// TODO this is very generous memory allowance suggests we
|
||||
// leak ~0.1% per instance.
|
||||
// TODO: Not yet sure how to access window.gc() in Playwright
|
||||
await page.evaluate(() => window.gc());
|
||||
const heap2 = await page.evaluate(
|
||||
() => performance.memory.usedJSHeapSize,
|
||||
);
|
||||
|
||||
expect((heap2 - heap1) / heap1).toBeLessThan(1);
|
||||
|
||||
const contents = await page.evaluate(async () => {
|
||||
const element = document.querySelector("perspective-viewer");
|
||||
await element.toggleConfig();
|
||||
return element.innerHTML;
|
||||
});
|
||||
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("views > no leak on repeated group_by changes", async ({ page }) => {
|
||||
let viewer = await page.$("perspective-viewer");
|
||||
await page.evaluate(async (viewer) => {
|
||||
window.__TABLE__ = await viewer.getTable();
|
||||
await viewer.reset();
|
||||
}, viewer);
|
||||
|
||||
await page.evaluate(() => window.gc());
|
||||
const heap1 = await page.evaluate(
|
||||
() => performance.memory.usedJSHeapSize,
|
||||
);
|
||||
|
||||
for (var i = 0; i < 500; i++) {
|
||||
await page.evaluate(async (element) => {
|
||||
await element.reset();
|
||||
let pivots = [
|
||||
"State",
|
||||
"City",
|
||||
"Segment",
|
||||
"Ship Mode",
|
||||
"Region",
|
||||
"Category",
|
||||
];
|
||||
let start = Math.floor(Math.random() * pivots.length);
|
||||
let length = Math.ceil(Math.random() * (pivots.length - start));
|
||||
await element.restore({
|
||||
group_by: pivots.slice(start, length),
|
||||
});
|
||||
}, viewer);
|
||||
}
|
||||
|
||||
await page.evaluate(() => window.gc());
|
||||
const heap2 = await page.evaluate(
|
||||
() => performance.memory.usedJSHeapSize,
|
||||
);
|
||||
expect((heap2 - heap1) / heap1).toBeLessThan(0.5);
|
||||
|
||||
const contents = await page.evaluate(async (viewer) => {
|
||||
await viewer.restore({ group_by: ["State"] });
|
||||
await viewer.toggleConfig();
|
||||
return viewer.innerHTML;
|
||||
}, viewer);
|
||||
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("views > no leak on repeated filter changes", async ({ page }) => {
|
||||
let viewer = await page.$("perspective-viewer");
|
||||
await page.evaluate(async (viewer) => {
|
||||
window.__TABLE__ = await viewer.getTable();
|
||||
await viewer.reset();
|
||||
}, viewer);
|
||||
|
||||
await page.evaluate(() => window.gc());
|
||||
const heap1 = await page.evaluate(
|
||||
() => performance.memory.usedJSHeapSize,
|
||||
);
|
||||
|
||||
for (var i = 0; i < 500; i++) {
|
||||
await page.evaluate(async (element) => {
|
||||
await element.reset();
|
||||
await element.restore({
|
||||
filter: [["Sales", ">", Math.random() * 100 + 100]],
|
||||
});
|
||||
}, viewer);
|
||||
}
|
||||
|
||||
await page.evaluate(() => window.gc());
|
||||
const heap2 = await page.evaluate(
|
||||
() => performance.memory.usedJSHeapSize,
|
||||
);
|
||||
expect((heap2 - heap1) / heap1).toBeLessThan(0.5);
|
||||
|
||||
const contents = await page.evaluate(async (viewer) => {
|
||||
await viewer.restore({
|
||||
filter: [["Sales", "<", 10]],
|
||||
});
|
||||
await viewer.toggleConfig();
|
||||
return viewer.innerHTML;
|
||||
}, viewer);
|
||||
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { test } from "../helpers.ts";
|
||||
import { run_standard_tests } from "../helpers/standard_tests.ts";
|
||||
|
||||
async function get_contents(page) {
|
||||
return await page.evaluate(async () => {
|
||||
const viewer = document.querySelector(
|
||||
"perspective-viewer perspective-viewer-plugin",
|
||||
);
|
||||
|
||||
// Don't format - light DOM is CSV in a <pre> tag.
|
||||
return viewer.innerHTML;
|
||||
});
|
||||
}
|
||||
|
||||
test.describe("Superstore Inline", () => {
|
||||
test.beforeEach(async function init({ page }) {
|
||||
await page.goto(
|
||||
"/node_modules/@perspective-dev/viewer/test/html/superstore-inline.html",
|
||||
);
|
||||
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
|
||||
await page.evaluate(async () => {
|
||||
await document.querySelector("perspective-viewer").restore({
|
||||
plugin: "Debug",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
run_standard_tests("superstore inline", get_contents);
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { test } from "../helpers.ts";
|
||||
import { run_standard_tests } from "../helpers/standard_tests.ts";
|
||||
|
||||
async function get_contents(page) {
|
||||
return await page.evaluate(async () => {
|
||||
const viewer = document.querySelector(
|
||||
"perspective-viewer perspective-viewer-plugin",
|
||||
);
|
||||
|
||||
// Don't format - light DOM is CSV in a <pre> tag.
|
||||
return viewer.innerHTML;
|
||||
});
|
||||
}
|
||||
|
||||
test.describe("Superstore", () => {
|
||||
test.beforeEach(async function init({ page }) {
|
||||
await page.goto(
|
||||
"/node_modules/@perspective-dev/viewer/test/html/superstore.html",
|
||||
);
|
||||
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
|
||||
await page.evaluate(async () => {
|
||||
await document.querySelector("perspective-viewer").restore({
|
||||
plugin: "Debug",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
run_standard_tests("superstore", get_contents);
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"rootDir": ".",
|
||||
"allowImportingTsExtensions": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["./**/*.ts"]
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { test } from "../helpers.ts";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("/rust/perspective-viewer/test/html/superstore.html");
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
|
||||
await page.evaluate(async () => {
|
||||
await document.querySelector("perspective-viewer").restore({
|
||||
plugin: "Debug",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Delete", async () => {
|
||||
test("delete > blocks until the underlying view is removed", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.restore({
|
||||
settings: true,
|
||||
group_by: ["State"],
|
||||
columns: ["Profit", "Sales"],
|
||||
});
|
||||
|
||||
const table = await viewer.getTable();
|
||||
await viewer.delete();
|
||||
await table.delete();
|
||||
});
|
||||
});
|
||||
|
||||
// `delete()` is destructive now
|
||||
test.skip("delete > flush after delete waits for view removal", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.restore({
|
||||
settings: true,
|
||||
group_by: ["State"],
|
||||
columns: ["Profit", "Sales"],
|
||||
});
|
||||
|
||||
const table = await viewer.getTable();
|
||||
viewer.delete();
|
||||
await viewer.flush();
|
||||
await table.delete();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,337 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { test, expect, compareContentsToSnapshot } from "../helpers.ts";
|
||||
|
||||
const RESULT = {
|
||||
aggregates: {},
|
||||
columns: [
|
||||
"Row ID",
|
||||
"Order ID",
|
||||
"Order Date",
|
||||
"Ship Date",
|
||||
"Ship Mode",
|
||||
"Customer ID",
|
||||
"Segment",
|
||||
"Country",
|
||||
"City",
|
||||
"State",
|
||||
"Postal Code",
|
||||
"Region",
|
||||
"Product ID",
|
||||
"Category",
|
||||
"Sub-Category",
|
||||
"Sales",
|
||||
"Quantity",
|
||||
"Discount",
|
||||
"Profit",
|
||||
],
|
||||
columns_config: {},
|
||||
expressions: {},
|
||||
filter: [],
|
||||
group_by: [],
|
||||
plugin: "Debug",
|
||||
plugin_config: {},
|
||||
settings: false,
|
||||
sort: [],
|
||||
split_by: [],
|
||||
table: "load-viewer-csv",
|
||||
theme: "Pro Light",
|
||||
title: null,
|
||||
group_rollup_mode: "rollup",
|
||||
};
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(
|
||||
"/node_modules/@perspective-dev/viewer/test/html/superstore.html",
|
||||
);
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
await page.evaluate(async () => {
|
||||
await document.querySelector("perspective-viewer")!.restore({
|
||||
plugin: "Debug",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("DOM API", () => {
|
||||
test.describe("restore with table name > sets default plugin and columns", () => {
|
||||
test("await order > resolves correctly when fully awaited", async ({
|
||||
page,
|
||||
}) => {
|
||||
const x = await page.evaluate(async () => {
|
||||
const old = document.querySelector("perspective-viewer")!;
|
||||
old.parentElement!.removeChild(old);
|
||||
const viewer = document.createElement("perspective-viewer");
|
||||
await viewer.load((window as any).__TEST_WORKER__);
|
||||
await viewer.restore({ table: "load-viewer-csv" });
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
delete x.version;
|
||||
expect(x).toEqual(RESULT);
|
||||
});
|
||||
|
||||
test("await order > resolves correctly when load is not awaited", async ({
|
||||
page,
|
||||
}) => {
|
||||
const x = await page.evaluate(async () => {
|
||||
const old = document.querySelector("perspective-viewer")!;
|
||||
old.parentElement!.removeChild(old);
|
||||
const viewer = document.createElement("perspective-viewer");
|
||||
viewer.load((window as any).__TEST_WORKER__);
|
||||
await viewer.restore({ table: "load-viewer-csv" });
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
delete x.version;
|
||||
expect(x).toEqual(RESULT);
|
||||
});
|
||||
|
||||
test("await order > resolves correctly when restore is not awaited", async ({
|
||||
page,
|
||||
}) => {
|
||||
const x = await page.evaluate(async () => {
|
||||
const old = document.querySelector("perspective-viewer")!;
|
||||
old.parentElement!.removeChild(old);
|
||||
const viewer = document.createElement("perspective-viewer");
|
||||
await viewer.load((window as any).__TEST_WORKER__);
|
||||
viewer.restore({ table: "load-viewer-csv" });
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
delete x.version;
|
||||
expect(x).toEqual(RESULT);
|
||||
});
|
||||
|
||||
test("await order > resolves correctly when neither load nor restore is awaited", async ({
|
||||
page,
|
||||
}) => {
|
||||
const x = await page.evaluate(async () => {
|
||||
const old = document.querySelector("perspective-viewer")!;
|
||||
old.parentElement!.removeChild(old);
|
||||
const viewer = document.createElement("perspective-viewer");
|
||||
viewer.load((window as any).__TEST_WORKER__);
|
||||
viewer.restore({ table: "load-viewer-csv" });
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
delete x.version;
|
||||
expect(x).toEqual(RESULT);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("load and restore before DOM append", () => {
|
||||
test("append > renders correctly when fully awaited", async ({
|
||||
page,
|
||||
}) => {
|
||||
const contents = await page.evaluate(async () => {
|
||||
const old = document.querySelector("perspective-viewer")!;
|
||||
old.parentElement!.removeChild(old);
|
||||
const viewer = document.createElement("perspective-viewer");
|
||||
await viewer.load((window as any).__TEST_WORKER__);
|
||||
await viewer.restore({ table: "load-viewer-csv" });
|
||||
document.body.appendChild(viewer);
|
||||
await viewer.flush();
|
||||
return document.body.innerHTML;
|
||||
});
|
||||
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("append > renders correctly when restore is not awaited", async ({
|
||||
page,
|
||||
}) => {
|
||||
const contents = await page.evaluate(async () => {
|
||||
const old = document.querySelector("perspective-viewer")!;
|
||||
old.parentElement!.removeChild(old);
|
||||
const viewer = document.createElement("perspective-viewer");
|
||||
await viewer.load((window as any).__TEST_WORKER__);
|
||||
viewer.restore({ table: "load-viewer-csv" });
|
||||
document.body.appendChild(viewer);
|
||||
await viewer.flush();
|
||||
return document.body.innerHTML;
|
||||
});
|
||||
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Regression: `<perspective-workspace>.addViewer({ columns_config })` was
|
||||
// losing the `columns_config` because the workspace awaits `viewer.restore`
|
||||
// while the viewer is still detached from the DOM (the Lumino widget hasn't
|
||||
// attached yet, so `onAfterAttach` hasn't fired). On that code path:
|
||||
// (a) `create_view`'s `reset_clean` early-return could leave `view_schema`
|
||||
// unpopulated in some interleavings, and
|
||||
// (b) `query_column_config_schema` then returned an EMPTY schema, which the
|
||||
// strip-on-write in `update_columns_configs` treats as "no allowed
|
||||
// keys" — dropping every entry in the incoming `columns_config`.
|
||||
// These tests reproduce the workspace's call sequence (createElement →
|
||||
// load(client) → restore(config) → appendChild) with non-trivial
|
||||
// `columns_config` / `plugin_config` payloads. They use the test-only
|
||||
// "Debug Styled" plugin (declared in the viewer test module), which — like a
|
||||
// real plugin — declares a non-empty `column_config_schema` /
|
||||
// `plugin_config_schema`. An empty-schema plugin would make the regression
|
||||
// untestable: the strip would drop the keys whether or not the bug is present.
|
||||
test.describe("restore with config payload before DOM append", () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(
|
||||
"/rust/perspective-viewer/test/html/superstore-debug.html",
|
||||
);
|
||||
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test("save > columns_config round-trips when fully awaited", async ({
|
||||
page,
|
||||
}) => {
|
||||
const config = await page.evaluate(async () => {
|
||||
const old = document.querySelector("perspective-viewer")!;
|
||||
old.parentElement!.removeChild(old);
|
||||
const viewer = document.createElement("perspective-viewer");
|
||||
await viewer.load((window as any).__TEST_WORKER__);
|
||||
await viewer.restore({
|
||||
table: "load-viewer-csv",
|
||||
plugin: "Debug Styled",
|
||||
columns: ["Profit"],
|
||||
columns_config: {
|
||||
Profit: {
|
||||
number_format: {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
document.body.appendChild(viewer);
|
||||
await viewer.flush();
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
expect(config.columns_config).toMatchObject({
|
||||
Profit: {
|
||||
number_format: {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("save > columns_config round-trips when restore is not awaited", async ({
|
||||
page,
|
||||
}) => {
|
||||
const config = await page.evaluate(async () => {
|
||||
const old = document.querySelector("perspective-viewer")!;
|
||||
old.parentElement!.removeChild(old);
|
||||
const viewer = document.createElement("perspective-viewer");
|
||||
await viewer.load((window as any).__TEST_WORKER__);
|
||||
viewer.restore({
|
||||
table: "load-viewer-csv",
|
||||
plugin: "Debug Styled",
|
||||
columns: ["Profit"],
|
||||
columns_config: {
|
||||
Profit: {
|
||||
number_format: {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
document.body.appendChild(viewer);
|
||||
await viewer.flush();
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
expect(config.columns_config).toMatchObject({
|
||||
Profit: {
|
||||
number_format: {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("save > plugin_config round-trips when fully awaited", async ({
|
||||
page,
|
||||
}) => {
|
||||
const config = await page.evaluate(async () => {
|
||||
const old = document.querySelector("perspective-viewer")!;
|
||||
old.parentElement!.removeChild(old);
|
||||
const viewer = document.createElement("perspective-viewer");
|
||||
await viewer.load((window as any).__TEST_WORKER__);
|
||||
await viewer.restore({
|
||||
table: "load-viewer-csv",
|
||||
plugin: "Debug Styled",
|
||||
columns: ["Profit"],
|
||||
plugin_config: { edit_mode: "EDIT" },
|
||||
});
|
||||
document.body.appendChild(viewer);
|
||||
await viewer.flush();
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
expect(config.plugin_config).toMatchObject({ edit_mode: "EDIT" });
|
||||
});
|
||||
|
||||
test("columns_config is applied to the plugin on first render", async ({
|
||||
page,
|
||||
}) => {
|
||||
const applied = await page.evaluate(async () => {
|
||||
const old = document.querySelector("perspective-viewer")!;
|
||||
old.parentElement!.removeChild(old);
|
||||
const viewer = document.createElement("perspective-viewer");
|
||||
await viewer.load((window as any).__TEST_WORKER__);
|
||||
await viewer.restore({
|
||||
table: "load-viewer-csv",
|
||||
plugin: "Debug Styled",
|
||||
columns: ["Profit"],
|
||||
columns_config: {
|
||||
Profit: {
|
||||
number_format: {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
document.body.appendChild(viewer);
|
||||
await viewer.flush();
|
||||
const plugin = viewer.querySelector(
|
||||
"perspective-viewer-debug-styled",
|
||||
) as any;
|
||||
return plugin?._restored_columns_config;
|
||||
});
|
||||
|
||||
expect(applied).toMatchObject({
|
||||
Profit: {
|
||||
number_format: {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,119 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import {
|
||||
test,
|
||||
expect,
|
||||
compareContentsToSnapshot,
|
||||
API_VERSION,
|
||||
getShadowContents,
|
||||
} from "../helpers.ts";
|
||||
|
||||
const get_contents = getShadowContents;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("/rust/perspective-viewer/test/html/superstore.html");
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
|
||||
await page.evaluate(async () => {
|
||||
await document.querySelector("perspective-viewer")!.restore({
|
||||
plugin: "Debug",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Events", () => {
|
||||
test("config-update event > fires on restore", async ({ page }) => {
|
||||
const config = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
|
||||
if (!viewer) {
|
||||
throw new Error("Viewer not found");
|
||||
}
|
||||
// @ts-ignore
|
||||
await viewer.getTable();
|
||||
let config;
|
||||
viewer.addEventListener("perspective-config-update", (event) => {
|
||||
// @ts-ignore
|
||||
config = event.detail;
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
await viewer.restore({
|
||||
settings: true,
|
||||
group_by: ["State"],
|
||||
columns: ["Profit", "Sales"],
|
||||
});
|
||||
|
||||
return config;
|
||||
});
|
||||
|
||||
expect(config).toEqual({
|
||||
version: API_VERSION,
|
||||
aggregates: {},
|
||||
split_by: [],
|
||||
columns: ["Profit", "Sales"],
|
||||
columns_config: {},
|
||||
expressions: {},
|
||||
filter: [],
|
||||
plugin: "Debug",
|
||||
plugin_config: {},
|
||||
group_by: ["State"],
|
||||
group_rollup_mode: "rollup",
|
||||
settings: true,
|
||||
sort: [],
|
||||
table: "load-viewer-csv",
|
||||
theme: "Pro Light",
|
||||
title: null,
|
||||
});
|
||||
|
||||
const contents = await get_contents(page);
|
||||
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("config-update event > fires on title edit", async ({ page }) => {
|
||||
await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
window["acc"] = [];
|
||||
await viewer!.restore({
|
||||
settings: true,
|
||||
});
|
||||
|
||||
viewer!.addEventListener("perspective-config-update", (event) => {
|
||||
window["acc"].push(event.detail);
|
||||
});
|
||||
});
|
||||
|
||||
const titleInput = page.locator("perspective-viewer #status_bar input");
|
||||
await titleInput.focus();
|
||||
await titleInput.pressSequentially("New Title");
|
||||
await titleInput.blur();
|
||||
|
||||
const result = await page.evaluate(async () => {
|
||||
return window["acc"];
|
||||
});
|
||||
|
||||
expect(result.map((x) => x.title)).toEqual(["New Title"]);
|
||||
|
||||
const config = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
return await viewer?.save();
|
||||
});
|
||||
|
||||
expect(config.title).toEqual("New Title");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,123 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { test, expect } from "../helpers.ts";
|
||||
|
||||
async function load(page) {
|
||||
await page.goto("/rust/perspective-viewer/test/html/blank.html");
|
||||
await page.waitForFunction(() => "WORKER" in window);
|
||||
const viewer = page.locator("perspective-viewer");
|
||||
await viewer.evaluate(async (viewer) => {
|
||||
const table = (await window.WORKER).table("a,b,c\n1,2,3\n4,5,6");
|
||||
await viewer.load(table);
|
||||
await viewer.flush();
|
||||
});
|
||||
return viewer;
|
||||
}
|
||||
|
||||
test.describe("Viewer Export", () => {
|
||||
test("export > defaults to CSV", async ({ page }) => {
|
||||
const viewer = await load(page);
|
||||
const result = await viewer.evaluate((viewer) => viewer.export());
|
||||
expect(typeof result).toBe("string");
|
||||
expect(result).toBe('"a","b","c"\n1,2,3\n4,5,6\n');
|
||||
});
|
||||
|
||||
test("export > csv method", async ({ page }) => {
|
||||
const viewer = await load(page);
|
||||
const result = await viewer.evaluate((viewer) => viewer.export("csv"));
|
||||
expect(result).toBe('"a","b","c"\n1,2,3\n4,5,6\n');
|
||||
});
|
||||
|
||||
test("export > json method", async ({ page }) => {
|
||||
const viewer = await load(page);
|
||||
const result = await viewer.evaluate(async (viewer) =>
|
||||
JSON.stringify(await viewer.export("json")),
|
||||
);
|
||||
|
||||
const parsed = JSON.parse(result as string);
|
||||
expect(parsed).toEqual({ a: [1, 4], b: [2, 5], c: [3, 6] });
|
||||
});
|
||||
|
||||
test("export > ndjson method", async ({ page }) => {
|
||||
const viewer = await load(page);
|
||||
const result = await viewer.evaluate((viewer) =>
|
||||
viewer.export("ndjson"),
|
||||
);
|
||||
|
||||
const lines = (result as string)
|
||||
.trim()
|
||||
.split("\n")
|
||||
.map((l) => JSON.parse(l));
|
||||
|
||||
expect(lines).toEqual([
|
||||
{ a: 1, b: 2, c: 3 },
|
||||
{ a: 4, b: 5, c: 6 },
|
||||
]);
|
||||
});
|
||||
|
||||
test("export > arrow method returns ArrayBuffer", async ({ page }) => {
|
||||
const viewer = await load(page);
|
||||
const byteLength = await viewer.evaluate(async (viewer) => {
|
||||
const result = await viewer.export("arrow");
|
||||
return (result as ArrayBuffer).byteLength;
|
||||
});
|
||||
|
||||
expect(byteLength).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Viewer Export UTF8", () => {
|
||||
test("export > json to csv round-trip", async ({ page }) => {
|
||||
await page.goto("/rust/perspective-viewer/test/html/blank.html");
|
||||
await page.waitForFunction(() => "WORKER" in window);
|
||||
const viewer = page.locator("perspective-viewer");
|
||||
await viewer.evaluate(async (viewer) => {
|
||||
const table = (await window.WORKER).table([
|
||||
{
|
||||
年月: "2023/01/01",
|
||||
轄區分局: "第四分局",
|
||||
路口名稱: "南屯區",
|
||||
路口名稱split: "五權西路與環中路口",
|
||||
A1: 0.0,
|
||||
A2: 3.0,
|
||||
A3: 130.0,
|
||||
總件數: 18.0,
|
||||
死亡人數: 0.0,
|
||||
受傷人數: null,
|
||||
主要肇因: "未注意車前狀態",
|
||||
},
|
||||
{
|
||||
年月: "2023/01/01",
|
||||
轄區分局: "第六分局",
|
||||
路口名稱: "西屯區",
|
||||
路口名稱split: "中清聯絡道與環中路口",
|
||||
A1: 0.0,
|
||||
A2: 2.0,
|
||||
A3: 100.0,
|
||||
總件數: 15.0,
|
||||
死亡人數: 0.0,
|
||||
受傷人數: null,
|
||||
主要肇因: "未注意車前狀態",
|
||||
},
|
||||
]);
|
||||
|
||||
await viewer.load(table);
|
||||
await viewer.flush();
|
||||
});
|
||||
|
||||
const result = await viewer.evaluate((viewer) => viewer.export("csv"));
|
||||
expect(result).toBe(
|
||||
`"年月","轄區分局","路口名稱","路口名稱split","A1","A2","A3","總件數","死亡人數","主要肇因","受傷人數"\n2023-01-01,"第四分局","南屯區","五權西路與環中路口",0,3,130,18,0,"未注意車前狀態",\n2023-01-01,"第六分局","西屯區","中清聯絡道與環中路口",0,2,100,15,0,"未注意車前狀態",\n`,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,137 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { test, expect } from "../helpers.ts";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("/rust/perspective-viewer/test/html/superstore.html");
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
|
||||
await page.evaluate(async () => {
|
||||
await document.querySelector("perspective-viewer").restore({
|
||||
plugin: "Debug",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Flush method", async () => {
|
||||
test("flush > awaits settings field update", async ({ page }) => {
|
||||
const old_config = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
expect(old_config.settings).toBeFalsy();
|
||||
const config = await page.evaluate(async (columns) => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.getTable();
|
||||
viewer.restore({
|
||||
settings: true,
|
||||
});
|
||||
|
||||
await viewer.flush();
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
expect(config).toEqual({
|
||||
...old_config,
|
||||
settings: true,
|
||||
});
|
||||
});
|
||||
|
||||
test("flush > awaits view query field updates", async ({ page }) => {
|
||||
const old_config = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
expect(old_config.settings).toBeFalsy();
|
||||
const config = await page.evaluate(async (columns) => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.getTable();
|
||||
viewer.restore({
|
||||
settings: true,
|
||||
group_by: ["State", "City"],
|
||||
split_by: ["Category"],
|
||||
});
|
||||
|
||||
await viewer.flush();
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
expect(config).toEqual({
|
||||
...old_config,
|
||||
group_by: ["State", "City"],
|
||||
split_by: ["Category"],
|
||||
settings: true,
|
||||
});
|
||||
});
|
||||
|
||||
test("flush > awaits config-update event triggered by restore", async ({
|
||||
page,
|
||||
}) => {
|
||||
const result = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.getTable();
|
||||
let count = 0;
|
||||
viewer.addEventListener("perspective-config-update", () => {
|
||||
count++;
|
||||
});
|
||||
|
||||
viewer.restore({
|
||||
settings: true,
|
||||
group_by: ["State", "City"],
|
||||
split_by: ["Category"],
|
||||
});
|
||||
|
||||
const first = count;
|
||||
await viewer.flush();
|
||||
const second = count;
|
||||
return [first, second];
|
||||
});
|
||||
|
||||
expect(result).toEqual([0, 1]);
|
||||
});
|
||||
|
||||
test("flush > awaits config-update event on load and does not repeat on DOM connect", async ({
|
||||
page,
|
||||
}) => {
|
||||
const result = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
const table = await viewer.getTable();
|
||||
await viewer.delete();
|
||||
viewer.parentElement.removeChild(viewer);
|
||||
|
||||
const new_viewer = document.createElement("perspective-viewer");
|
||||
let count = 0;
|
||||
|
||||
new_viewer.addEventListener("perspective-config-update", () => {
|
||||
count++;
|
||||
});
|
||||
|
||||
new_viewer.load(table);
|
||||
const first = count;
|
||||
await new_viewer.flush();
|
||||
const second = count;
|
||||
document.body.appendChild(new_viewer);
|
||||
await new_viewer.flush();
|
||||
const third = count;
|
||||
return [first, second, third];
|
||||
});
|
||||
|
||||
expect(result).toEqual([0, 1, 1]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { test, expect } from "../helpers.ts";
|
||||
|
||||
test.describe("Focus", async () => {
|
||||
test.beforeEach(async function init({ page }) {
|
||||
await page.goto(
|
||||
"/node_modules/@perspective-dev/viewer/test/html/superstore_with_input.html",
|
||||
);
|
||||
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
|
||||
await page.evaluate(async () => {
|
||||
await document.querySelector("perspective-viewer").restore({
|
||||
plugin: "Debug",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("focus > preserves external widget focus during restore", async ({
|
||||
page,
|
||||
}) => {
|
||||
const viewer = page.locator("perspective-viewer");
|
||||
const tagName = await viewer.evaluate(async (viewer) => {
|
||||
const input = document.querySelector("input");
|
||||
input.focus();
|
||||
await viewer.restore({ group_by: ["State"] });
|
||||
return document.activeElement.tagName;
|
||||
});
|
||||
|
||||
expect(tagName).toEqual("INPUT");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,142 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import {
|
||||
test,
|
||||
expect,
|
||||
compareContentsToSnapshot,
|
||||
getShadowContents,
|
||||
} from "../helpers.ts";
|
||||
|
||||
const get_contents = getShadowContents;
|
||||
|
||||
test.describe("Viewer Load", () => {
|
||||
test("load > resolves with valid Table promise", async ({ page }) => {
|
||||
await page.goto("/rust/perspective-viewer/test/html/blank.html");
|
||||
await page.waitForFunction(() => "WORKER" in window);
|
||||
|
||||
const viewer = page.locator("perspective-viewer");
|
||||
await viewer.evaluate(async (viewer) => {
|
||||
const goodTable = (await window.WORKER).table("a,b,c\n1,2,3");
|
||||
return viewer.load(goodTable);
|
||||
});
|
||||
await expect(viewer).toHaveText(/"a","b","c"/); // column titles
|
||||
});
|
||||
|
||||
test("load > rejects with failed Table promise", async ({ page }) => {
|
||||
await page.goto("/rust/perspective-viewer/test/html/blank.html");
|
||||
await page.waitForFunction(() => "WORKER" in window);
|
||||
|
||||
const viewer = page.locator("perspective-viewer");
|
||||
await expect(
|
||||
viewer.evaluate((viewer) => {
|
||||
const errorTable = Promise.reject(new Error("blimpy"));
|
||||
return viewer.load(errorTable);
|
||||
}),
|
||||
).rejects.toThrow("blimpy");
|
||||
});
|
||||
|
||||
test("load > recovers after a rejected Table promise", async ({ page }) => {
|
||||
await page.goto("/rust/perspective-viewer/test/html/blank.html");
|
||||
await page.waitForFunction(() => "WORKER" in window);
|
||||
|
||||
const viewer = page.locator("perspective-viewer");
|
||||
const didError = await viewer.evaluate(async (viewer) => {
|
||||
const errorTable = Promise.reject(new Error("blimpy"));
|
||||
const worker = await window.WORKER;
|
||||
let didError = false;
|
||||
try {
|
||||
await viewer.load(errorTable);
|
||||
} catch (e) {
|
||||
if (e.message.includes("blimpy")) {
|
||||
didError = true;
|
||||
}
|
||||
}
|
||||
|
||||
const goodTable = worker.table("a,b,c\n1,2,3");
|
||||
await viewer.load(goodTable);
|
||||
return didError;
|
||||
});
|
||||
expect(didError).toBe(true);
|
||||
});
|
||||
|
||||
test("load > is inert when called twice with the same Table", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/rust/perspective-viewer/test/html/superstore.html");
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
|
||||
await page.evaluate(async () => {
|
||||
await document.querySelector("perspective-viewer").restore({
|
||||
plugin: "Debug",
|
||||
});
|
||||
});
|
||||
|
||||
const contents = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.restore({ settings: true });
|
||||
const table = await viewer.getTable();
|
||||
await viewer.load(table);
|
||||
await viewer.flush();
|
||||
return viewer.shadowRoot.innerHTML;
|
||||
});
|
||||
|
||||
await compareContentsToSnapshot(
|
||||
contents,
|
||||
"load-called-twice-with-the-same-table.txt",
|
||||
);
|
||||
});
|
||||
|
||||
test("load > does not throw when restore is called during a failed load", async ({
|
||||
page,
|
||||
consoleLogs,
|
||||
}) => {
|
||||
const errors = [];
|
||||
page.on("pageerror", async (msg) => {
|
||||
errors.push(`${msg.name}::${msg.message}`);
|
||||
});
|
||||
|
||||
await page.goto("/rust/perspective-viewer/test/html/blank.html", {
|
||||
waitUntil: "networkidle",
|
||||
});
|
||||
|
||||
await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
viewer.load(
|
||||
new Promise((_, reject) => reject("Intentional Load Error")),
|
||||
);
|
||||
try {
|
||||
await viewer.restore({
|
||||
settings: true,
|
||||
plugin: "Debug",
|
||||
});
|
||||
} catch (e) {
|
||||
// We need to catch this error else the `evaluate()` fails.
|
||||
// We need to await the call because we want it to fail
|
||||
// before continuing the test.
|
||||
console.error("Caught error:", e);
|
||||
}
|
||||
|
||||
await new Promise((x) => setTimeout(x, 1000));
|
||||
});
|
||||
|
||||
const contents = await get_contents(page);
|
||||
expect(errors).toEqual([
|
||||
'Error::Failed to construct table from JsValue("Intentional Load Error")',
|
||||
]);
|
||||
consoleLogs.expectedLogs.push("error", /Intentional Load Error/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,89 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { test, expect } from "../helpers.ts";
|
||||
|
||||
// Uses test-only plugins ("Debug Styled" / "Debug Alt") declared in the
|
||||
// viewer test module so this spec does not depend on the descendant
|
||||
// `@perspective-dev/viewer-datagrid` / `-charts` packages.
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("/rust/perspective-viewer/test/html/superstore-debug.html");
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Reset", () => {
|
||||
test("soft reset restores columns_config after plugin swap", async ({
|
||||
page,
|
||||
}) => {
|
||||
const result = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer")!;
|
||||
await viewer.getTable();
|
||||
await viewer.restore({
|
||||
plugin: "Debug Styled",
|
||||
columns: ["Profit"],
|
||||
columns_config: {
|
||||
Profit: {
|
||||
number_format: {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await viewer.restore({ plugin: "Debug Alt" });
|
||||
await viewer.reset();
|
||||
await viewer.flush();
|
||||
|
||||
// The active plugin is slotted into the viewer's light DOM, not
|
||||
// its shadow root.
|
||||
const plugin = viewer.querySelector(
|
||||
"perspective-viewer-debug-styled",
|
||||
) as any;
|
||||
|
||||
const config = await viewer.save();
|
||||
return {
|
||||
active_plugin: config.plugin,
|
||||
saved_columns_config: config.columns_config,
|
||||
applied_columns_config: plugin?._restored_columns_config,
|
||||
};
|
||||
});
|
||||
|
||||
// Soft reset reverts to the default plugin ...
|
||||
expect(result.active_plugin).toBe("Debug Styled");
|
||||
|
||||
// ... the preserved `columns_config` is re-applied to that plugin
|
||||
// (the plugin's own `restore()` saw it) ...
|
||||
expect(result.applied_columns_config).toMatchObject({
|
||||
Profit: {
|
||||
number_format: {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// ... and it survives the round-trip back out through `save()`.
|
||||
expect(result.saved_columns_config).toMatchObject({
|
||||
Profit: {
|
||||
number_format: {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,198 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import {
|
||||
test,
|
||||
expect,
|
||||
DEFAULT_CONFIG,
|
||||
compareContentsToSnapshot,
|
||||
API_VERSION,
|
||||
getShadowContents,
|
||||
} from "../helpers.ts";
|
||||
|
||||
const get_contents = getShadowContents;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("/rust/perspective-viewer/test/html/superstore.html");
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
|
||||
await page.evaluate(async () => {
|
||||
await document.querySelector("perspective-viewer").restore({
|
||||
plugin: "Debug",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Save/Restore", async () => {
|
||||
test("save > returns the current viewer config", async ({ page }) => {
|
||||
const config = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.getTable();
|
||||
await viewer.restore({
|
||||
settings: true,
|
||||
group_by: ["State"],
|
||||
columns: ["Profit", "Sales"],
|
||||
});
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
expect(config).toEqual({
|
||||
...DEFAULT_CONFIG,
|
||||
columns: ["Profit", "Sales"],
|
||||
plugin: "Debug",
|
||||
group_by: ["State"],
|
||||
settings: true,
|
||||
});
|
||||
|
||||
const contents = await get_contents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("restore > roundtrips a saved config", async ({ page }) => {
|
||||
const config = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.getTable();
|
||||
await viewer.restore({
|
||||
settings: true,
|
||||
group_by: ["State"],
|
||||
columns: ["Profit", "Sales"],
|
||||
});
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
expect(config).toEqual({
|
||||
...DEFAULT_CONFIG,
|
||||
columns: ["Profit", "Sales"],
|
||||
plugin: "Debug",
|
||||
group_by: ["State"],
|
||||
settings: true,
|
||||
});
|
||||
|
||||
const config2 = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.reset();
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
expect(config2).toEqual({
|
||||
...DEFAULT_CONFIG,
|
||||
columns: [
|
||||
"Row ID",
|
||||
"Order ID",
|
||||
"Order Date",
|
||||
"Ship Date",
|
||||
"Ship Mode",
|
||||
"Customer ID",
|
||||
"Segment",
|
||||
"Country",
|
||||
"City",
|
||||
"State",
|
||||
"Postal Code",
|
||||
"Region",
|
||||
"Product ID",
|
||||
"Category",
|
||||
"Sub-Category",
|
||||
"Sales",
|
||||
"Quantity",
|
||||
"Discount",
|
||||
"Profit",
|
||||
],
|
||||
plugin: "Debug",
|
||||
settings: true,
|
||||
theme: "Pro Light",
|
||||
});
|
||||
|
||||
const config3 = await page.evaluate(async (config) => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.restore(config);
|
||||
return await viewer.save();
|
||||
}, config);
|
||||
|
||||
expect(config3).toEqual({
|
||||
...DEFAULT_CONFIG,
|
||||
columns: ["Profit", "Sales"],
|
||||
plugin: "Debug",
|
||||
group_by: ["State"],
|
||||
settings: true,
|
||||
theme: "Pro Light",
|
||||
});
|
||||
|
||||
const contents = await get_contents(page);
|
||||
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("save > roundtrips in string format", async ({ page }) => {
|
||||
const config = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.getTable();
|
||||
await viewer.restore({
|
||||
settings: true,
|
||||
group_by: ["State"],
|
||||
columns: ["Profit", "Sales"],
|
||||
});
|
||||
return await viewer.save("string");
|
||||
});
|
||||
|
||||
const config3 = await page.evaluate(async (config) => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.reset();
|
||||
await viewer.restore(config);
|
||||
return await viewer.save();
|
||||
}, config);
|
||||
|
||||
expect(config3).toEqual({
|
||||
...DEFAULT_CONFIG,
|
||||
columns: ["Profit", "Sales"],
|
||||
plugin: "Debug",
|
||||
group_by: ["State"],
|
||||
settings: true,
|
||||
theme: "Pro Light",
|
||||
});
|
||||
|
||||
const contents = await get_contents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("save > roundtrips in arraybuffer format", async ({ page }) => {
|
||||
const config3 = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.getTable();
|
||||
await viewer.restore({
|
||||
settings: true,
|
||||
group_by: ["State"],
|
||||
columns: ["Profit", "Sales"],
|
||||
});
|
||||
|
||||
const config = await viewer.save("arraybuffer");
|
||||
await viewer.reset();
|
||||
await viewer.restore(config);
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
expect(config3).toEqual({
|
||||
...DEFAULT_CONFIG,
|
||||
columns: ["Profit", "Sales"],
|
||||
plugin: "Debug",
|
||||
group_by: ["State"],
|
||||
settings: true,
|
||||
theme: "Pro Light",
|
||||
});
|
||||
|
||||
const contents = await get_contents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { test, expect } from "@perspective-dev/test";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("/rust/perspective-viewer/test/html/superstore.html");
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
|
||||
await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer")!;
|
||||
await viewer.restore({ plugin: "Debug" });
|
||||
// Explicitly register both themes so the memoized theme cache contains
|
||||
// "Pro Dark" even though superstore.html only loads pro.css.
|
||||
await viewer.resetThemes(["Pro Light", "Pro Dark"]);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Theme", () => {
|
||||
test("restore sets the theme attribute on the host element", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer")!;
|
||||
await viewer.getTable();
|
||||
await viewer.restore({ theme: "Pro Dark" });
|
||||
});
|
||||
|
||||
const themeAttr = await page.evaluate(() =>
|
||||
document.querySelector("perspective-viewer")!.getAttribute("theme"),
|
||||
);
|
||||
|
||||
expect(themeAttr).toBe("Pro Dark");
|
||||
});
|
||||
|
||||
test("save returns the correct theme after restore", async ({ page }) => {
|
||||
const saved = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer")!;
|
||||
await viewer.getTable();
|
||||
await viewer.restore({ theme: "Pro Dark" });
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
expect(saved.theme).toBe("Pro Dark");
|
||||
});
|
||||
|
||||
test("theme is preserved across settings panel open/close toggle", async ({
|
||||
page,
|
||||
}) => {
|
||||
// Set theme, then toggle settings open and closed.
|
||||
// This exercises the UpdateSettingsOpen path which must NOT wipe
|
||||
// available_themes from the PresentationProps snapshot.
|
||||
const saved = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer")!;
|
||||
await viewer.getTable();
|
||||
await viewer.restore({ theme: "Pro Dark" });
|
||||
await viewer.restore({ settings: true });
|
||||
await viewer.restore({ settings: false });
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
expect(saved.theme).toBe("Pro Dark");
|
||||
expect(saved.settings).toBe(false);
|
||||
});
|
||||
|
||||
test("switching theme updates the theme attribute", async ({ page }) => {
|
||||
const themeAttr = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer")!;
|
||||
await viewer.getTable();
|
||||
await viewer.restore({ theme: "Pro Dark" });
|
||||
await viewer.restore({ theme: "Pro Light" });
|
||||
return viewer.getAttribute("theme");
|
||||
});
|
||||
|
||||
expect(themeAttr).toBe("Pro Light");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,95 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { test, expect } from "../helpers.ts";
|
||||
|
||||
test.describe("View Lifecycle", () => {
|
||||
test("conflation > silences view-not-found during rapid restore", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto(
|
||||
"/rust/perspective-viewer/test/html/superstore_lazy_viewer.html",
|
||||
);
|
||||
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
|
||||
let vnf = false;
|
||||
page.on("console", (msg) => {
|
||||
if (msg.type() === "error") {
|
||||
if (msg.text().includes("View not found")) {
|
||||
vnf = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await page.evaluate(async () => {
|
||||
const worker = window.__TEST_WORKER__;
|
||||
let resolve;
|
||||
let is_paused = false;
|
||||
const BasePlugin = customElements.get("perspective-viewer-plugin");
|
||||
class PausePlugin extends BasePlugin {
|
||||
get_static_config() {
|
||||
return {
|
||||
name: "pause-plugin",
|
||||
select_mode: "select",
|
||||
config_column_names: [],
|
||||
};
|
||||
}
|
||||
|
||||
async draw(view) {
|
||||
if (is_paused) {
|
||||
await new Promise((x) => {
|
||||
resolve = x;
|
||||
});
|
||||
}
|
||||
|
||||
const size = await view.num_rows();
|
||||
this.textContent = `Rows: ${size}`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("pause-plugin", PausePlugin);
|
||||
const Viewer = customElements.get("perspective-viewer");
|
||||
Viewer.registerPlugin("pause-plugin");
|
||||
|
||||
// use a new viewer because only new viewers get loaded with the registered plugin
|
||||
const viewer = document.createElement("perspective-viewer");
|
||||
document.body.append(viewer);
|
||||
worker.table("a,b,c\n1,2,3", { name: "A" });
|
||||
|
||||
await viewer.load(worker);
|
||||
await viewer.restore({ table: "A", plugin: "pause-plugin" });
|
||||
is_paused = true;
|
||||
|
||||
// Change in 4.1.0 - empty restore now does not render
|
||||
const restore_task = viewer.restore({
|
||||
plugin: "pause-plugin",
|
||||
});
|
||||
|
||||
while (!resolve) {
|
||||
await new Promise((x) => setTimeout(x, 0));
|
||||
}
|
||||
|
||||
await new Promise((x) => setTimeout(x, 0));
|
||||
resolve();
|
||||
resolve = undefined;
|
||||
is_paused = false;
|
||||
await restore_task;
|
||||
});
|
||||
|
||||
expect(vnf).toBeFalsy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import {
|
||||
test,
|
||||
compareContentsToSnapshot,
|
||||
getShadowContents,
|
||||
} from "../helpers.ts";
|
||||
|
||||
const get_contents = getShadowContents;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("/rust/perspective-viewer/test/html/plugin-resize.html");
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Cancellable Views", () => {
|
||||
test("resize > does not throw after view deletion", async ({ page }) => {
|
||||
await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.restore({
|
||||
group_by: ["State"],
|
||||
columns: ["Sales"],
|
||||
settings: true,
|
||||
filter: [
|
||||
["State", "not in", ["California", "Texas", "New York"]],
|
||||
],
|
||||
});
|
||||
|
||||
const view = await viewer.getView();
|
||||
await view.delete();
|
||||
await viewer.resize({ force: true });
|
||||
});
|
||||
|
||||
const contents = await get_contents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { test, expect } from "../helpers.ts";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("/rust/perspective-viewer/test/html/superstore.html");
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
|
||||
await page.evaluate(async () => {
|
||||
await document.querySelector("perspective-viewer").restore({
|
||||
plugin: "Debug",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Custom Elements", () => {
|
||||
test("registration > registers copy and export menu elements", async ({
|
||||
page,
|
||||
}) => {
|
||||
const export_exists = await page.evaluate(async () => {
|
||||
return !!window.customElements.get("perspective-export-menu");
|
||||
});
|
||||
|
||||
const copy_exists = await page.evaluate(async () => {
|
||||
return !!window.customElements.get("perspective-copy-menu");
|
||||
});
|
||||
|
||||
expect(export_exists).toBeTruthy();
|
||||
expect(copy_exists).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,417 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import {
|
||||
test,
|
||||
expect,
|
||||
PageView,
|
||||
compareContentsToSnapshot,
|
||||
} from "../helpers.ts";
|
||||
|
||||
async function openSidebarAndScrollToBottom() {
|
||||
const elem = document.querySelector("perspective-viewer");
|
||||
await elem.getTable();
|
||||
await elem.toggleConfig(true);
|
||||
elem.shadowRoot.querySelector("#active-columns").scrollTop = 500;
|
||||
}
|
||||
|
||||
async function checkSaveDisabled(page, expr) {
|
||||
let view = new PageView(page);
|
||||
let settingsPanel = await view.openSettingsPanel();
|
||||
await settingsPanel.createNewExpression("", expr, false);
|
||||
}
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(
|
||||
"/node_modules/@perspective-dev/viewer/test/html/superstore.html",
|
||||
);
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
|
||||
await page.evaluate(async () => {
|
||||
await document.querySelector("perspective-viewer").restore({
|
||||
plugin: "Debug",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Expressions", () => {
|
||||
test("editor > opens on add-column button click", async ({ page }) => {
|
||||
await page.evaluate(openSidebarAndScrollToBottom);
|
||||
|
||||
await page.waitForFunction(
|
||||
() =>
|
||||
!!document
|
||||
.querySelector("perspective-viewer")
|
||||
.shadowRoot.querySelector("#add-expression"),
|
||||
);
|
||||
|
||||
await page.locator("perspective-viewer #add-expression").click();
|
||||
|
||||
await page.waitForFunction(() => {
|
||||
const root = document
|
||||
.querySelector("perspective-viewer")
|
||||
.shadowRoot.querySelector("#editor-container");
|
||||
|
||||
return !!root;
|
||||
});
|
||||
|
||||
const editor = await page.waitForFunction(async () => {
|
||||
const root = document
|
||||
.querySelector("perspective-viewer")
|
||||
.shadowRoot.querySelector("#editor-container");
|
||||
return root.querySelector("#content");
|
||||
});
|
||||
|
||||
const contents = await editor.evaluate((x) => x.outerHTML);
|
||||
|
||||
await page.evaluate(() => document.activeElement.blur());
|
||||
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("editor > closes on close button click", async ({ page }) => {
|
||||
await page.evaluate(openSidebarAndScrollToBottom);
|
||||
await page.waitForFunction(
|
||||
() =>
|
||||
!!document
|
||||
.querySelector("perspective-viewer")
|
||||
.shadowRoot.querySelector("#add-expression"),
|
||||
);
|
||||
|
||||
await page.locator("perspective-viewer #add-expression").click();
|
||||
await page.waitForSelector("#editor-container");
|
||||
await page.evaluate(async () => {
|
||||
let root = document.querySelector("perspective-viewer").shadowRoot;
|
||||
await root.querySelector("#column_settings_close_button").click();
|
||||
});
|
||||
|
||||
await page.waitForSelector("#editor-container", {
|
||||
state: "hidden",
|
||||
});
|
||||
|
||||
const contents = await page.evaluate(async () => {
|
||||
let root = document.querySelector("perspective-viewer").shadowRoot;
|
||||
return (
|
||||
root.querySelector("#editor-container")?.innerHTML || "MISSING"
|
||||
);
|
||||
});
|
||||
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("validation > disables save for unknown symbols", async ({ page }) => {
|
||||
await checkSaveDisabled(page, "abc");
|
||||
});
|
||||
|
||||
test("validation > disables save for type-invalid expression", async ({
|
||||
page,
|
||||
}) => {
|
||||
await checkSaveDisabled(page, '"Sales" + "Category";');
|
||||
});
|
||||
|
||||
test("validation > disables save for invalid column references", async ({
|
||||
page,
|
||||
}) => {
|
||||
await checkSaveDisabled(page, '"aaaa" + "Sales";');
|
||||
});
|
||||
|
||||
test("columns > shows both aliased and non-aliased expressions", async ({
|
||||
page,
|
||||
}) => {
|
||||
const contents = await page.evaluate(async () => {
|
||||
document.activeElement.blur();
|
||||
const elem = document.querySelector("perspective-viewer");
|
||||
await elem.toggleConfig(true);
|
||||
await elem.restore({
|
||||
expressions: { "1 + 2": "1 + 2", abc: "3 + 4" },
|
||||
});
|
||||
return elem.shadowRoot.querySelector("#sub-columns").innerHTML;
|
||||
});
|
||||
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
// No longer relevant as we cannot save a duplicate identifier
|
||||
test.skip("columns > overwrites a duplicate expression alias", async ({
|
||||
page,
|
||||
}) => {
|
||||
let view = new PageView(page);
|
||||
view.restore({ expressions: { "4 + 5": "3 + 4" } });
|
||||
|
||||
await view.settingsPanel.createNewExpression("", "4 + 5", true);
|
||||
|
||||
const contents = await page.evaluate(async () => {
|
||||
const elem = document.querySelector("perspective-viewer");
|
||||
return elem.shadowRoot.querySelector("#sub-columns").innerHTML;
|
||||
});
|
||||
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
// No longer relevant as we cannot save a duplicate identifier
|
||||
test.skip("columns > overwrites a duplicate expression", async ({
|
||||
page,
|
||||
}) => {
|
||||
let view = new PageView(page);
|
||||
view.restore({ expressions: { "3 + 4": "3 + 4" } });
|
||||
await view.settingsPanel.createNewExpression("", "3 + 4", true);
|
||||
|
||||
const contents = await page.evaluate(async () => {
|
||||
const elem = document.querySelector("perspective-viewer");
|
||||
return elem.shadowRoot.querySelector("#sub-columns").innerHTML;
|
||||
});
|
||||
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("reset > deletes all expressions on full reset", async ({ page }) => {
|
||||
await page.evaluate(async () => {
|
||||
const elem = document.querySelector("perspective-viewer");
|
||||
await elem.toggleConfig(true);
|
||||
await elem.restore({
|
||||
expressions: { "3 + 4": "3 + 4", "1 + 2": "1 + 2" },
|
||||
});
|
||||
await elem.reset(true);
|
||||
});
|
||||
|
||||
const contents = await page.evaluate(async () => {
|
||||
const elem = document.querySelector("perspective-viewer");
|
||||
return (
|
||||
elem.shadowRoot.querySelector("#sub-columns")?.innerHTML ||
|
||||
"MISSING"
|
||||
);
|
||||
});
|
||||
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("reset > preserves expressions on partial reset", async ({ page }) => {
|
||||
await page.evaluate(async () => {
|
||||
const elem = document.querySelector("perspective-viewer");
|
||||
await elem.toggleConfig(true);
|
||||
await elem.restore({
|
||||
expressions: { "3 + 4": "3 + 4", "1 + 2": "1 + 2" },
|
||||
});
|
||||
await elem.reset(false);
|
||||
});
|
||||
|
||||
const content = await page.evaluate(async () => {
|
||||
const elem = document.querySelector("perspective-viewer");
|
||||
return elem.shadowRoot.querySelector("#sub-columns").innerHTML;
|
||||
});
|
||||
|
||||
await compareContentsToSnapshot(content);
|
||||
});
|
||||
|
||||
test("reset > deletes expressions used in columns on full reset", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.evaluate(async () => {
|
||||
const elem = document.querySelector("perspective-viewer");
|
||||
await elem.toggleConfig(true);
|
||||
await elem.restore({
|
||||
columns: ["1 + 2"],
|
||||
expressions: { "3 + 4": "3 + 4", "1 + 2": "1 + 2" },
|
||||
});
|
||||
await elem.reset(true);
|
||||
});
|
||||
|
||||
const contents = await page.evaluate(async () => {
|
||||
const elem = document.querySelector("perspective-viewer");
|
||||
return (
|
||||
elem.shadowRoot.querySelector("#sub-columns")?.innerHTML ||
|
||||
"MISSING"
|
||||
);
|
||||
});
|
||||
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("reset > preserves expressions used in columns on partial reset", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.evaluate(async () => {
|
||||
const elem = document.querySelector("perspective-viewer");
|
||||
await elem.toggleConfig(true);
|
||||
await elem.restore({
|
||||
columns: ["1 + 2"],
|
||||
expressions: { "3 + 4": "3 + 4", "1 + 2": "1 + 2" },
|
||||
});
|
||||
await elem.reset(false);
|
||||
});
|
||||
|
||||
const contents = await page.evaluate(async () => {
|
||||
const elem = document.querySelector("perspective-viewer");
|
||||
return elem.shadowRoot.querySelector("#sub-columns").innerHTML;
|
||||
});
|
||||
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("reset > deletes expressions used in group_by/sort/filter on full reset", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.evaluate(async () => {
|
||||
const elem = document.querySelector("perspective-viewer");
|
||||
await elem.toggleConfig(true);
|
||||
await elem.restore({
|
||||
columns: ["1 + 2"],
|
||||
group_by: ["3 + 4"],
|
||||
sort: [["1 + 2", "asc"]],
|
||||
filter: [["1 + 2", "==", 3]],
|
||||
expressions: { "3 + 4": "3 + 4", "1 + 2": "1 + 2" },
|
||||
});
|
||||
await elem.reset(true);
|
||||
});
|
||||
|
||||
const contents = await page.evaluate(async () => {
|
||||
const elem = document.querySelector("perspective-viewer");
|
||||
return (
|
||||
elem.shadowRoot.querySelector("#sub-columns")?.innerHTML ||
|
||||
"MISSING"
|
||||
);
|
||||
});
|
||||
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("persistence > survives views that don't reference them", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.evaluate(async () => {
|
||||
const elem = document.querySelector("perspective-viewer");
|
||||
await elem.toggleConfig(true);
|
||||
await elem.restore({
|
||||
expressions: { "3 + 4": "3 + 4", "1 + 2": "1 + 2" },
|
||||
});
|
||||
await elem.restore({
|
||||
columns: ["State"],
|
||||
});
|
||||
});
|
||||
|
||||
const contents = await page.evaluate(async () => {
|
||||
const elem = document.querySelector("perspective-viewer");
|
||||
return elem.shadowRoot.querySelector("#sub-columns").innerHTML;
|
||||
});
|
||||
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("add button > marks on hover", async ({ page }) => {
|
||||
await page.evaluate(openSidebarAndScrollToBottom);
|
||||
let addExprButton = await page.waitForSelector("#add-expression");
|
||||
let notHovered = await addExprButton.getAttribute("class");
|
||||
expect(notHovered).toBeNull();
|
||||
|
||||
await addExprButton.hover();
|
||||
let hovered = await addExprButton.getAttribute("class");
|
||||
expect(hovered).toBe("dragdrop-hover");
|
||||
});
|
||||
|
||||
// Currently does not work in Firefox!
|
||||
test("add button > marks on click", async ({ page }) => {
|
||||
await page.evaluate(openSidebarAndScrollToBottom);
|
||||
let addExprButton = await page.waitForSelector("#add-expression");
|
||||
let unclicked = await addExprButton.getAttribute("class");
|
||||
expect(unclicked).toBeNull();
|
||||
await addExprButton.click();
|
||||
await page.evaluate(openSidebarAndScrollToBottom);
|
||||
let clicked = await addExprButton.getAttribute("class");
|
||||
expect(clicked).toBe("dragdrop-hover");
|
||||
});
|
||||
|
||||
test("persistence > survives views that reference them", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.evaluate(async () => {
|
||||
const elem = document.querySelector("perspective-viewer");
|
||||
await elem.toggleConfig(true);
|
||||
await elem.restore({
|
||||
expressions: { "3 + 4": "3 + 4", "1 + 2": "1 + 2" },
|
||||
});
|
||||
await elem.restore({
|
||||
columns: ["3 + 4"],
|
||||
});
|
||||
});
|
||||
|
||||
const contents = await page.evaluate(async () => {
|
||||
const elem = document.querySelector("perspective-viewer");
|
||||
return elem.shadowRoot.querySelector("#sub-columns").innerHTML;
|
||||
});
|
||||
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("aggregates > applies aggregate to expression column", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.evaluate(async () => {
|
||||
const elem = document.querySelector("perspective-viewer");
|
||||
await elem.toggleConfig(true);
|
||||
await elem.restore({
|
||||
expressions: { '"Sales" + 100': '"Sales" + 100' },
|
||||
aggregates: { '"Sales" + 100': "avg" },
|
||||
group_by: ["State"],
|
||||
columns: ['"Sales" + 100'],
|
||||
});
|
||||
});
|
||||
|
||||
const contents = await page.evaluate(async () => {
|
||||
const elem = document.querySelector("perspective-viewer");
|
||||
return elem.shadowRoot.querySelector("#sub-columns").innerHTML;
|
||||
});
|
||||
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("sort > sorts by a hidden expression column", async ({ page }) => {
|
||||
await page.evaluate(async () => {
|
||||
const elem = document.querySelector("perspective-viewer");
|
||||
await elem.toggleConfig(true);
|
||||
await elem.restore({
|
||||
expressions: { '"Sales" + 100': '"Sales" + 100' },
|
||||
sort: [['"Sales" + 100', "asc"]],
|
||||
columns: ["Row ID"],
|
||||
});
|
||||
});
|
||||
|
||||
const contents = await page.evaluate(async () => {
|
||||
const elem = document.querySelector("perspective-viewer");
|
||||
return elem.shadowRoot.querySelector("#sub-columns").innerHTML;
|
||||
});
|
||||
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("filter > filters by an expression column", async ({ page }) => {
|
||||
await page.evaluate(async () => {
|
||||
const elem = document.querySelector("perspective-viewer");
|
||||
await elem.toggleConfig(true);
|
||||
await elem.restore({
|
||||
expressions: { '"Sales" + 100': '"Sales" + 100' },
|
||||
filter: [['"Sales" + 100', ">", 150]],
|
||||
columns: ["Row ID", '"Sales" + 100'],
|
||||
});
|
||||
});
|
||||
|
||||
const contents = await page.evaluate(async () => {
|
||||
const elem = document.querySelector("perspective-viewer");
|
||||
return elem.shadowRoot.querySelector("#sub-columns").innerHTML;
|
||||
});
|
||||
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,134 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import {
|
||||
test,
|
||||
expect,
|
||||
DEFAULT_CONFIG,
|
||||
compareContentsToSnapshot,
|
||||
getShadowContents,
|
||||
} from "../helpers.ts";
|
||||
|
||||
const get_contents = getShadowContents;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("/rust/perspective-viewer/test/html/superstore.html");
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
|
||||
await page.evaluate(async () => {
|
||||
await document.querySelector("perspective-viewer").restore({
|
||||
plugin: "Debug",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Filter Config", () => {
|
||||
test("not_in > renders filtered results correctly", async ({ page }) => {
|
||||
await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.restore({
|
||||
group_by: ["State"],
|
||||
columns: ["Sales"],
|
||||
settings: true,
|
||||
filter: [
|
||||
["State", "not in", ["California", "Texas", "New York"]],
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
const contents = await get_contents(page);
|
||||
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("in > generates correct config from dropdown selection", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.restore({
|
||||
group_by: ["State"],
|
||||
columns: ["Sales"],
|
||||
settings: true,
|
||||
filter: [["State", "in", []]],
|
||||
});
|
||||
|
||||
const filter = viewer.shadowRoot.querySelector(
|
||||
".pivot-column input[type=search]",
|
||||
);
|
||||
filter.value = "C";
|
||||
const event = new Event("input", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
});
|
||||
|
||||
filter.dispatchEvent(event);
|
||||
});
|
||||
|
||||
const elem = await page.waitForSelector("perspective-dropdown");
|
||||
await page.evaluate((elem) => {
|
||||
let node = elem.shadowRoot.querySelector("span:first-of-type");
|
||||
var clickEvent = document.createEvent("MouseEvents");
|
||||
clickEvent.initEvent("mousedown", true, true);
|
||||
node.dispatchEvent(clickEvent);
|
||||
}, elem);
|
||||
|
||||
const config = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.flush();
|
||||
return await viewer.save();
|
||||
});
|
||||
|
||||
expect(config).toEqual({
|
||||
...DEFAULT_CONFIG,
|
||||
columns: ["Sales"],
|
||||
filter: [["State", "in", ["California"]]],
|
||||
group_by: ["State"],
|
||||
plugin: "Debug",
|
||||
settings: true,
|
||||
});
|
||||
|
||||
const contents = await get_contents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
|
||||
test("numeric input > appends digits without re-rendering on trailing zeroes", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.restore({
|
||||
filter: [["Sales", ">", 1.1]],
|
||||
settings: true,
|
||||
});
|
||||
});
|
||||
|
||||
const numFilter = page.locator("perspective-viewer input.num-filter");
|
||||
await numFilter.focus();
|
||||
await numFilter.pressSequentially("0001");
|
||||
await numFilter.blur();
|
||||
|
||||
const value = await page.evaluate(async () => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
await viewer.flush();
|
||||
return viewer.shadowRoot.querySelector("input.num-filter").value;
|
||||
});
|
||||
|
||||
expect(value).toEqual("1.10001");
|
||||
const contents = await get_contents(page);
|
||||
await compareContentsToSnapshot(contents);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { PageView as PspViewer, expect, test } from "../helpers.ts";
|
||||
import fs from "node:fs";
|
||||
import { fileURLToPath } from "url";
|
||||
import path from "path";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
test.describe("Localization", function () {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("/tools/test/src/html/basic-test.html");
|
||||
await page.evaluate(async () => {
|
||||
while (!window["__TEST_PERSPECTIVE_READY__"]) {
|
||||
await new Promise((x) => setTimeout(x, 10));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test("labels > renders no visible label text", async function ({ page }) {
|
||||
const view = new PspViewer(page);
|
||||
await view.openSettingsPanel();
|
||||
const editBtn = view.dataGrid.regularTable.editBtnRow
|
||||
.locator("th.psp-menu-enabled span")
|
||||
.first();
|
||||
|
||||
await editBtn.click();
|
||||
await view.columnSettingsSidebar.container.waitFor();
|
||||
const contents = await page.evaluate(() => {
|
||||
const viewer = document.querySelector("perspective-viewer");
|
||||
return Array.from(viewer.shadowRoot.querySelectorAll("label"))
|
||||
.map((x) => x.textContent)
|
||||
.filter((x) => x != "");
|
||||
});
|
||||
|
||||
expect(contents).toEqual(["+", "-"]);
|
||||
});
|
||||
|
||||
const intl = fs
|
||||
.readFileSync(`${__dirname}/../../../src/themes/intl.css`)
|
||||
.toString();
|
||||
|
||||
const keys = Array.from(intl.matchAll(/--[a-zA-Z0-9\-]+/g)).flat();
|
||||
const langfiles = fs.readdirSync(`${__dirname}/../../../src/themes/intl`);
|
||||
for (const file of langfiles) {
|
||||
test(`${file} > contains all required intl keys`, async function ({
|
||||
page,
|
||||
}) {
|
||||
const langfile = fs
|
||||
.readFileSync(`${__dirname}/../../../src/themes/intl/${file}`)
|
||||
.toString();
|
||||
for (const key of keys) {
|
||||
const re = new RegExp(key, "g");
|
||||
const x = langfile.match(re);
|
||||
expect(x).toEqual([key]);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user