mirror of
https://github.com/radzenhq/radzen-blazor.git
synced 2026-02-04 05:35:44 +00:00
Compare commits
2615 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3bfc16b6a8 | ||
|
|
1e54619e81 | ||
|
|
fc4ac98246 | ||
|
|
534e3ee98f | ||
|
|
9b8b8f020d | ||
|
|
aea7fb8069 | ||
|
|
b933633cc0 | ||
|
|
a822eb521d | ||
|
|
b6bb19538f | ||
|
|
c0a1f86801 | ||
|
|
439d79c677 | ||
|
|
b6ee0f118e | ||
|
|
ed037ca6d7 | ||
|
|
58f6239cb2 | ||
|
|
5e7bfcb591 | ||
|
|
03cd99c43c | ||
|
|
dc1742aca6 | ||
|
|
a23142cc3f | ||
|
|
de47319b00 | ||
|
|
c9a2549018 | ||
|
|
966253f7b6 | ||
|
|
b0ce7fb3cf | ||
|
|
f1f85a6563 | ||
|
|
c10ee5e0a8 | ||
|
|
7e86d3128c | ||
|
|
4f3da0ea2e | ||
|
|
406c7d8c6b | ||
|
|
809f1192f4 | ||
|
|
ff280d27f1 | ||
|
|
c566eb95a2 | ||
|
|
393d80600c | ||
|
|
1e44165d18 | ||
|
|
03f4774810 | ||
|
|
b64e0446a3 | ||
|
|
e51cab2418 | ||
|
|
f533d106c1 | ||
|
|
2557691053 | ||
|
|
5f53f95b5d | ||
|
|
2c71574072 | ||
|
|
45d38494f2 | ||
|
|
5c03d0d14c | ||
|
|
8404294f56 | ||
|
|
7f519bb71f | ||
|
|
7a5995002d | ||
|
|
ba8fe8e830 | ||
|
|
949a5b19cc | ||
|
|
5a68a00f38 | ||
|
|
d52d91852a | ||
|
|
b8b39e1ebd | ||
|
|
e795e63d02 | ||
|
|
27dc58a90f | ||
|
|
d4679353af | ||
|
|
73a53c8829 | ||
|
|
eb51f78f8c | ||
|
|
8667443e64 | ||
|
|
0650472551 | ||
|
|
7653ea683a | ||
|
|
d633d2f424 | ||
|
|
98ec370545 | ||
|
|
51b9579d6e | ||
|
|
af42791fa2 | ||
|
|
f7e9a88575 | ||
|
|
331ac4787e | ||
|
|
4ac2e0aa49 | ||
|
|
0cb10c206a | ||
|
|
5fd031f518 | ||
|
|
48c204a7b0 | ||
|
|
e61fc1c84e | ||
|
|
fb64381af9 | ||
|
|
84954f1919 | ||
|
|
cdabe516eb | ||
|
|
e5ddb2cef9 | ||
|
|
963185b927 | ||
|
|
f7ef6a207e | ||
|
|
8088d14340 | ||
|
|
f710b782a3 | ||
|
|
b13921f9aa | ||
|
|
522191052a | ||
|
|
e9e8844342 | ||
|
|
ccbf7b24cf | ||
|
|
57f1417d4c | ||
|
|
d5318e6e88 | ||
|
|
978e3fc610 | ||
|
|
dcb5eef814 | ||
|
|
518ad838e7 | ||
|
|
ba6ced577e | ||
|
|
138253e728 | ||
|
|
ffd299962b | ||
|
|
354a147155 | ||
|
|
a623017325 | ||
|
|
654bd80abd | ||
|
|
3249591351 | ||
|
|
40f934d7a6 | ||
|
|
53717dcc46 | ||
|
|
f16a00fe2e | ||
|
|
ab1af0ce28 | ||
|
|
be444bbbc4 | ||
|
|
e161872604 | ||
|
|
e21fa015bf | ||
|
|
fb3275d89f | ||
|
|
f42e4ffc71 | ||
|
|
7b15c190d3 | ||
|
|
73c2d27620 | ||
|
|
288db125ce | ||
|
|
0effaae435 | ||
|
|
a71f1dd3c6 | ||
|
|
c08843d587 | ||
|
|
44094cd4f4 | ||
|
|
a4bd9d518c | ||
|
|
a739a6e24a | ||
|
|
955b52d54b | ||
|
|
004293008c | ||
|
|
9f26a0b512 | ||
|
|
b16f95a4ba | ||
|
|
c079531d2e | ||
|
|
773f2dc074 | ||
|
|
64d783179b | ||
|
|
3dafd6c277 | ||
|
|
fb2fdb10b2 | ||
|
|
50dedf84d5 | ||
|
|
d7b96a7dca | ||
|
|
1d1acdf7c8 | ||
|
|
440bc76f08 | ||
|
|
d2a598dbf5 | ||
|
|
931f9dab6f | ||
|
|
10ec2af398 | ||
|
|
46382ef89f | ||
|
|
c4116feda4 | ||
|
|
4c18342cf4 | ||
|
|
92f103c1dd | ||
|
|
8c9374e00a | ||
|
|
870f3d2ef9 | ||
|
|
d75b3596aa | ||
|
|
c388d84f57 | ||
|
|
782f6c9655 | ||
|
|
078d12f2a1 | ||
|
|
271e1aa26a | ||
|
|
c9c11c7ff7 | ||
|
|
75da4667b7 | ||
|
|
a2591f7f77 | ||
|
|
72b4e8bd39 | ||
|
|
740b0bc3b1 | ||
|
|
3e48b15db0 | ||
|
|
f1b8a22cc8 | ||
|
|
02b75fcb68 | ||
|
|
9b2541975f | ||
|
|
d3ff9a5b7c | ||
|
|
75acf7d132 | ||
|
|
d6a9430c20 | ||
|
|
5a89720f59 | ||
|
|
bee7133e81 | ||
|
|
8522f88d66 | ||
|
|
10ecc5c75d | ||
|
|
cdd722c975 | ||
|
|
7fd9b258aa | ||
|
|
5368d398fe | ||
|
|
30cc8c8711 | ||
|
|
f7d1fea2af | ||
|
|
ee5674bb6d | ||
|
|
5e1fa61c55 | ||
|
|
92f968f933 | ||
|
|
9eabd75864 | ||
|
|
df121e68b4 | ||
|
|
be0721fa5d | ||
|
|
d670872d73 | ||
|
|
68eb9162a9 | ||
|
|
a65ce23a10 | ||
|
|
bea9bca891 | ||
|
|
9f2c6fe8cc | ||
|
|
07e4276190 | ||
|
|
3db15c168d | ||
|
|
7e9f66ec61 | ||
|
|
c7ce4ec4e3 | ||
|
|
ad32aed204 | ||
|
|
2df9235516 | ||
|
|
a0efb0b9a4 | ||
|
|
a244741640 | ||
|
|
e3f5360a7f | ||
|
|
c280bae14f | ||
|
|
e99671ba81 | ||
|
|
73338462e5 | ||
|
|
1912e69375 | ||
|
|
60a3789e9c | ||
|
|
a89bc5a5ce | ||
|
|
08f3126f46 | ||
|
|
53e746146b | ||
|
|
1e7cc0d655 | ||
|
|
b37a81b297 | ||
|
|
6aec8118f2 | ||
|
|
756732803f | ||
|
|
a6b8abb9ce | ||
|
|
38feb54237 | ||
|
|
ee828de086 | ||
|
|
bc82638fea | ||
|
|
edb6f58f66 | ||
|
|
23b9a6e7bf | ||
|
|
0b3c6058e2 | ||
|
|
cb6572980e | ||
|
|
78e5bbb9dc | ||
|
|
12b52b3e79 | ||
|
|
e226749388 | ||
|
|
001cd0cdcd | ||
|
|
c3806348a0 | ||
|
|
c6a8d80d45 | ||
|
|
2babacd15a | ||
|
|
456ab29a34 | ||
|
|
1de201841a | ||
|
|
a5586fd4ea | ||
|
|
a14ac78957 | ||
|
|
1193ede1ca | ||
|
|
8ad75a1773 | ||
|
|
9ea1b44e64 | ||
|
|
b92ef7922d | ||
|
|
3d6eff639f | ||
|
|
011e761d7f | ||
|
|
a2fdb687a1 | ||
|
|
ff846e0cc1 | ||
|
|
df0ad0d9f1 | ||
|
|
716d1a6cba | ||
|
|
3e7468177d | ||
|
|
d4113e6715 | ||
|
|
7f7ce06d0e | ||
|
|
7cfea596fd | ||
|
|
7eae5d0f6e | ||
|
|
a390658ad9 | ||
|
|
ecc403cbbc | ||
|
|
e870ca856c | ||
|
|
e7b8fec063 | ||
|
|
7767878c4c | ||
|
|
63de20fe36 | ||
|
|
6114a9a8ed | ||
|
|
d5514ca0cd | ||
|
|
8cf0565211 | ||
|
|
32246b1d69 | ||
|
|
418dbf09ec | ||
|
|
a198d09b71 | ||
|
|
f3bb0e7b90 | ||
|
|
b3917e19a2 | ||
|
|
e7d8b7454f | ||
|
|
4d4660eed9 | ||
|
|
b80e47672d | ||
|
|
3b370841cc | ||
|
|
7ec2a9a0a5 | ||
|
|
e340a376ef | ||
|
|
a2709afc5c | ||
|
|
9c7ed95f23 | ||
|
|
524f1f99cb | ||
|
|
2ac6d7f2bb | ||
|
|
6b3fd9efae | ||
|
|
d99daa6538 | ||
|
|
13e0bba379 | ||
|
|
ed7c5bc537 | ||
|
|
03b7968b99 | ||
|
|
274d64246b | ||
|
|
4450053fd3 | ||
|
|
8c321f18e1 | ||
|
|
e015807edc | ||
|
|
4ebcd2f214 | ||
|
|
67cd8b7a61 | ||
|
|
0a802ca73b | ||
|
|
097f37bfbc | ||
|
|
524e42980a | ||
|
|
34eebc9406 | ||
|
|
65f20c232c | ||
|
|
d742fdae86 | ||
|
|
397e2baf6c | ||
|
|
4b1d951083 | ||
|
|
ab65a5a975 | ||
|
|
9947cbf47b | ||
|
|
f781ba0c6a | ||
|
|
e3c605a7a9 | ||
|
|
849e6761b8 | ||
|
|
407ef36b70 | ||
|
|
fef5ceb36d | ||
|
|
2e22f556d8 | ||
|
|
95b833402f | ||
|
|
c0e7418e7c | ||
|
|
4d72ef1efe | ||
|
|
a00bce399f | ||
|
|
2b8658b233 | ||
|
|
fc2784450b | ||
|
|
896d9bd3ae | ||
|
|
9199096f69 | ||
|
|
bdb2694734 | ||
|
|
98ab3cd3d0 | ||
|
|
22f7d3f9f4 | ||
|
|
7f27dafe32 | ||
|
|
37a1e6d4b5 | ||
|
|
91d5473bf2 | ||
|
|
b883cccb30 | ||
|
|
2ca7b3ba5f | ||
|
|
faada7ae7b | ||
|
|
3910fb778f | ||
|
|
4577d063cc | ||
|
|
1f5c70166e | ||
|
|
f920f9f08d | ||
|
|
e6272e88e6 | ||
|
|
4be24abf7f | ||
|
|
85cc05a144 | ||
|
|
7f726c4e2a | ||
|
|
5c86ffd2ac | ||
|
|
257444b640 | ||
|
|
c61d453de4 | ||
|
|
4b88b18e78 | ||
|
|
1125894e7a | ||
|
|
e8894360fa | ||
|
|
168c071ac3 | ||
|
|
d6dd67951e | ||
|
|
c7e4470a60 | ||
|
|
cb6789504a | ||
|
|
314664c4e2 | ||
|
|
58794f806c | ||
|
|
6a470a6448 | ||
|
|
27bf1713a0 | ||
|
|
c1e428dd3e | ||
|
|
661e5b50f8 | ||
|
|
749c2cf600 | ||
|
|
153e9e01bc | ||
|
|
c1797cc215 | ||
|
|
00eb31cd88 | ||
|
|
bd4b1e485b | ||
|
|
387eacf5ff | ||
|
|
5d00e79e0b | ||
|
|
3aac6a785d | ||
|
|
3506256dff | ||
|
|
5c49ab32e5 | ||
|
|
f128209c3a | ||
|
|
4a4254847a | ||
|
|
d21697367c | ||
|
|
1ceaab2788 | ||
|
|
0c56f20f55 | ||
|
|
261339c7e9 | ||
|
|
1d1335fed5 | ||
|
|
c9b5a53be3 | ||
|
|
11a037aeec | ||
|
|
0743bb5f54 | ||
|
|
9fe5163d86 | ||
|
|
acd107b1a2 | ||
|
|
1ff96df224 | ||
|
|
fb537c46d4 | ||
|
|
a588b454ae | ||
|
|
0f145800fa | ||
|
|
44dfa72f16 | ||
|
|
19152f498c | ||
|
|
056a61c9fe | ||
|
|
a0cfc5d267 | ||
|
|
eb6dbf0c67 | ||
|
|
af2083120e | ||
|
|
95d4c0e992 | ||
|
|
b14f67685a | ||
|
|
b82fa04aec | ||
|
|
93d1e8604a | ||
|
|
53d32dbcc2 | ||
|
|
da43d91b5a | ||
|
|
0694ab0777 | ||
|
|
207940426d | ||
|
|
58a204ed56 | ||
|
|
f68a2719f4 | ||
|
|
4311b019f4 | ||
|
|
c1b7ce0bcc | ||
|
|
fe0ca5403c | ||
|
|
c5c33ebfeb | ||
|
|
2c24843e4d | ||
|
|
b0e17e572c | ||
|
|
7cd5727ccc | ||
|
|
d02bafc7a6 | ||
|
|
ca4bdad32a | ||
|
|
4fab7f9180 | ||
|
|
224f54c676 | ||
|
|
fbc1dbb443 | ||
|
|
4da4e532c5 | ||
|
|
a16447faa2 | ||
|
|
a883b26dda | ||
|
|
b9937ee6ca | ||
|
|
40ed1d5841 | ||
|
|
0d3b86d06b | ||
|
|
f8464d6f23 | ||
|
|
d2995e3b6c | ||
|
|
eda37027d5 | ||
|
|
11f9e5cfa4 | ||
|
|
a59a02a6a7 | ||
|
|
26db867bd5 | ||
|
|
18a92cbaca | ||
|
|
62ac151847 | ||
|
|
0b3628e085 | ||
|
|
ffb23e7f34 | ||
|
|
678d861e22 | ||
|
|
652e08ebbe | ||
|
|
95672569c5 | ||
|
|
c9bedc9315 | ||
|
|
dbaeb39564 | ||
|
|
1e9801b0d4 | ||
|
|
eed82f2b6e | ||
|
|
a0f1545421 | ||
|
|
a69909ff23 | ||
|
|
65c99d3fbd | ||
|
|
687094fec0 | ||
|
|
757debaecc | ||
|
|
6d5dda80ee | ||
|
|
b9d9e965d5 | ||
|
|
e845df2a10 | ||
|
|
93cfc0e15d | ||
|
|
7a35bc2340 | ||
|
|
dc7eebb8ae | ||
|
|
1dfc967900 | ||
|
|
bb5217948d | ||
|
|
c2269711c5 | ||
|
|
e707b99860 | ||
|
|
ec43241ccc | ||
|
|
c45436dcf4 | ||
|
|
d41c3ee72b | ||
|
|
0058d0aa56 | ||
|
|
868638f031 | ||
|
|
a8250f17c3 | ||
|
|
c3bbc7e31c | ||
|
|
5d5565b832 | ||
|
|
5da0ff61b2 | ||
|
|
79efa87d39 | ||
|
|
a56981bc70 | ||
|
|
24f30bf18f | ||
|
|
0fa843fd78 | ||
|
|
aecee331a4 | ||
|
|
c1b7cc6d65 | ||
|
|
b8ddfa6537 | ||
|
|
fd4bd631e5 | ||
|
|
96b8901cde | ||
|
|
124033d122 | ||
|
|
cbb292b828 | ||
|
|
22b5fc2452 | ||
|
|
d549a314bc | ||
|
|
f32fe71dbc | ||
|
|
169cf14fa6 | ||
|
|
3befe007fd | ||
|
|
6d84bafa2e | ||
|
|
f3badc0ad4 | ||
|
|
df9887b629 | ||
|
|
cbecf5b954 | ||
|
|
040cccebe2 | ||
|
|
efffdf54c3 | ||
|
|
c3cb0c8968 | ||
|
|
d676c67b50 | ||
|
|
9316170c89 | ||
|
|
9101614d35 | ||
|
|
7312b2859d | ||
|
|
bf7d99ccba | ||
|
|
fb0d588cbf | ||
|
|
8e7bd6323a | ||
|
|
8bebd1933d | ||
|
|
2429b2c027 | ||
|
|
769820792f | ||
|
|
36f39b5023 | ||
|
|
bb675ee040 | ||
|
|
9b4aff7a3f | ||
|
|
e312d71286 | ||
|
|
a397c686b8 | ||
|
|
9b92005810 | ||
|
|
d4e0c8c3bb | ||
|
|
ae1ec9aaa2 | ||
|
|
e811bb9213 | ||
|
|
7de5d90c82 | ||
|
|
bf5265c15b | ||
|
|
49b111a0e9 | ||
|
|
85964d9f17 | ||
|
|
4f9075fa77 | ||
|
|
0856c9005c | ||
|
|
31d8bccfcf | ||
|
|
9213e3d3ef | ||
|
|
3665079cc9 | ||
|
|
44881e521c | ||
|
|
c136d8050b | ||
|
|
a5cc04b46f | ||
|
|
a9a8b6314b | ||
|
|
5c7be3bcfc | ||
|
|
e3893198af | ||
|
|
6a4e6dc5a1 | ||
|
|
643916f733 | ||
|
|
e964851d53 | ||
|
|
79e495dc14 | ||
|
|
13326879f0 | ||
|
|
293a871db4 | ||
|
|
4c9dc6350d | ||
|
|
4c54afdc55 | ||
|
|
d60841b040 | ||
|
|
7e640f47a5 | ||
|
|
f3d1b273f1 | ||
|
|
e80f5b85e9 | ||
|
|
1b5725caee | ||
|
|
820e8f8323 | ||
|
|
30672e52bb | ||
|
|
ac9796f50d | ||
|
|
66b7b4cad0 | ||
|
|
f417b9cc73 | ||
|
|
a304d4643c | ||
|
|
8386e5a2b5 | ||
|
|
27f57b96c0 | ||
|
|
d9a049d523 | ||
|
|
f53f724b04 | ||
|
|
a41105656a | ||
|
|
54269abfd1 | ||
|
|
e812ff1337 | ||
|
|
ae28f8f239 | ||
|
|
470f261dc0 | ||
|
|
80cfda23b6 | ||
|
|
5321c4d52b | ||
|
|
0466912f3e | ||
|
|
3d3932e657 | ||
|
|
d63117a17a | ||
|
|
ba06a72326 | ||
|
|
2671f6ca61 | ||
|
|
8dddfcbc92 | ||
|
|
ebc5793f36 | ||
|
|
4946221651 | ||
|
|
de9817a97f | ||
|
|
1218c4f385 | ||
|
|
e9cee62d03 | ||
|
|
fa7cf37eab | ||
|
|
b9ecb7556f | ||
|
|
661b38e1e6 | ||
|
|
769f4ba589 | ||
|
|
6118253ebf | ||
|
|
0f7f717330 | ||
|
|
0b16f1eb97 | ||
|
|
e733894c02 | ||
|
|
c47dcfbe27 | ||
|
|
8ae008ec9d | ||
|
|
45ef0c78a3 | ||
|
|
3ac46f239e | ||
|
|
1e106f7ada | ||
|
|
09738b2dd7 | ||
|
|
dc67a0b0fc | ||
|
|
1258bbc770 | ||
|
|
5960318a60 | ||
|
|
d4faf758f4 | ||
|
|
f88216b9a0 | ||
|
|
e21d648588 | ||
|
|
dbcbdc9bbc | ||
|
|
c5d16e8381 | ||
|
|
ac1aa6f3b1 | ||
|
|
866c222c74 | ||
|
|
06843477cb | ||
|
|
9a7a6fc6ab | ||
|
|
4986da415f | ||
|
|
cdbd20b794 | ||
|
|
49567ade65 | ||
|
|
4d857449d2 | ||
|
|
dc03621a14 | ||
|
|
b83cca17e2 | ||
|
|
343afffd40 | ||
|
|
163eac9511 | ||
|
|
167bfcb0b3 | ||
|
|
f0f94eb84c | ||
|
|
7123c9ca12 | ||
|
|
41e12d8e54 | ||
|
|
473ab14ae6 | ||
|
|
127ffd451c | ||
|
|
32bb43b66f | ||
|
|
765f5acbcc | ||
|
|
8ac137f22d | ||
|
|
10bbf6b157 | ||
|
|
390dc0605b | ||
|
|
6fda9f0691 | ||
|
|
0a1c5830d2 | ||
|
|
33efa2316d | ||
|
|
b3de9374a7 | ||
|
|
1d869e8d2b | ||
|
|
56156dd5ff | ||
|
|
35c9f55c41 | ||
|
|
e70e3559eb | ||
|
|
5957439475 | ||
|
|
322ed552ed | ||
|
|
bf848d5ef8 | ||
|
|
285057a9df | ||
|
|
4de62ffa55 | ||
|
|
f97fde1387 | ||
|
|
8a5bba625c | ||
|
|
108a209ac8 | ||
|
|
8390a4aba9 | ||
|
|
9740416e56 | ||
|
|
8cc82642f9 | ||
|
|
5f4804f4cf | ||
|
|
24dbee5d3d | ||
|
|
298f84d25e | ||
|
|
182c66f0a9 | ||
|
|
fd11c34646 | ||
|
|
6bcc7388bb | ||
|
|
60da0c0082 | ||
|
|
2b2b48bec4 | ||
|
|
d579f79399 | ||
|
|
bf85df68b3 | ||
|
|
e6e7ec49df | ||
|
|
e7801e1222 | ||
|
|
3327254022 | ||
|
|
d53d09e358 | ||
|
|
34c02ac391 | ||
|
|
cf4eb0c9cd | ||
|
|
ee6220b1fe | ||
|
|
a264638726 | ||
|
|
3e6a10a57d | ||
|
|
5258816770 | ||
|
|
fb4457f9a1 | ||
|
|
627a74038c | ||
|
|
54c297e898 | ||
|
|
a4d7201f7d | ||
|
|
b04cf69b90 | ||
|
|
ea654f0b8d | ||
|
|
9b16aaf9e4 | ||
|
|
87672de761 | ||
|
|
171a8def6d | ||
|
|
f09179f4cd | ||
|
|
03f62068b3 | ||
|
|
828b4230e3 | ||
|
|
2b23e5d472 | ||
|
|
17dbe70460 | ||
|
|
587c3a0647 | ||
|
|
83f0879b11 | ||
|
|
212b741828 | ||
|
|
ee81b608f9 | ||
|
|
ad17080ee8 | ||
|
|
6a8611c348 | ||
|
|
3ba3610b21 | ||
|
|
8deed23e01 | ||
|
|
356106263b | ||
|
|
0566244b61 | ||
|
|
17d76b4e6f | ||
|
|
9c3490f275 | ||
|
|
b1b6499d7c | ||
|
|
a36a74091f | ||
|
|
4dfbd676af | ||
|
|
f72f8b3e39 | ||
|
|
45cc6880f9 | ||
|
|
17281cd3b1 | ||
|
|
332b452b56 | ||
|
|
c647515186 | ||
|
|
6faf0ed9b9 | ||
|
|
fad024d9bb | ||
|
|
67718df278 | ||
|
|
273c3283c2 | ||
|
|
aa7306ebd1 | ||
|
|
56cb173ad5 | ||
|
|
174f0a9393 | ||
|
|
a1dbc1a6fb | ||
|
|
d9de2ee0bc | ||
|
|
cf1e0eda27 | ||
|
|
ccc82e5a61 | ||
|
|
0859f939dd | ||
|
|
519c133c0c | ||
|
|
9f62860821 | ||
|
|
238ed1ac2c | ||
|
|
cd5903b358 | ||
|
|
f78125c4fb | ||
|
|
6394241b9f | ||
|
|
d4dfc9bbab | ||
|
|
cf668be964 | ||
|
|
6ef443d724 | ||
|
|
75af217864 | ||
|
|
7a7343c3c8 | ||
|
|
18bfa8f562 | ||
|
|
a76a0815a0 | ||
|
|
332f384a65 | ||
|
|
3c5e1216c8 | ||
|
|
c7c11a1c1e | ||
|
|
0dd993c1d0 | ||
|
|
c977e9b59c | ||
|
|
81055b5500 | ||
|
|
c8df446846 | ||
|
|
97ac4621b9 | ||
|
|
9346c474cf | ||
|
|
d9a55965d3 | ||
|
|
50306343a8 | ||
|
|
08d7457620 | ||
|
|
b80a382d2f | ||
|
|
ab066bb441 | ||
|
|
4a64260d6e | ||
|
|
9390077007 | ||
|
|
baf5386e34 | ||
|
|
86e72b2f7f | ||
|
|
70bc02b12e | ||
|
|
6bce8b0195 | ||
|
|
dd4afb1d0a | ||
|
|
95493fe157 | ||
|
|
42b9ef3956 | ||
|
|
97ee7b7a7a | ||
|
|
230860f773 | ||
|
|
5f7f6d975e | ||
|
|
e7c95fdcc1 | ||
|
|
933367a685 | ||
|
|
4857307533 | ||
|
|
1ee50e42ca | ||
|
|
5bbc108331 | ||
|
|
11d62516d9 | ||
|
|
ef714a8bfc | ||
|
|
c6b8441a79 | ||
|
|
200ad86575 | ||
|
|
54fe2f260f | ||
|
|
e056cf743d | ||
|
|
8dbc747928 | ||
|
|
969b37e4e9 | ||
|
|
79a111668e | ||
|
|
9c90e3110b | ||
|
|
59237fb9c6 | ||
|
|
fc3705f019 | ||
|
|
15adbc0823 | ||
|
|
ca6472f5f2 | ||
|
|
de38322f34 | ||
|
|
ed45d30639 | ||
|
|
f99fc6f15f | ||
|
|
56aaa082da | ||
|
|
0aaa2f20bd | ||
|
|
bac589e3ec | ||
|
|
1dc7b02a3c | ||
|
|
83965afe68 | ||
|
|
0e9b110023 | ||
|
|
44f01a77fd | ||
|
|
58552b7a50 | ||
|
|
ddf3feac51 | ||
|
|
c2d59fcee1 | ||
|
|
ee3b2592aa | ||
|
|
ed79dfb220 | ||
|
|
6fbb942ad9 | ||
|
|
94a9340ee3 | ||
|
|
ef6d2b7d2b | ||
|
|
7f0b603f60 | ||
|
|
04ec31ea5c | ||
|
|
d1ff36c44a | ||
|
|
d703443469 | ||
|
|
5455a02c4d | ||
|
|
4bbb186f63 | ||
|
|
b62e8bf976 | ||
|
|
7ccc4fe33a | ||
|
|
b52d1cf1e2 | ||
|
|
6ee9db0eb8 | ||
|
|
36ac2a5bfb | ||
|
|
e1841c652a | ||
|
|
6fc2c88482 | ||
|
|
b8a13a5ba4 | ||
|
|
d36623c1fb | ||
|
|
06ab88556c | ||
|
|
b552790035 | ||
|
|
6610dd22df | ||
|
|
04dddf92d8 | ||
|
|
bedd9c97c2 | ||
|
|
81c859ba31 | ||
|
|
041e662e07 | ||
|
|
65c7c66b50 | ||
|
|
287f2762f9 | ||
|
|
3750e94ff5 | ||
|
|
e127d60345 | ||
|
|
5189298884 | ||
|
|
5a9c370b29 | ||
|
|
ec4f370f29 | ||
|
|
da194dd88d | ||
|
|
91e6b86eb3 | ||
|
|
550e2d59b4 | ||
|
|
80b7ef08c8 | ||
|
|
7c9ddf0c3f | ||
|
|
9abdb1d47b | ||
|
|
a5171dd9b4 | ||
|
|
4e7c04efbe | ||
|
|
d3c66dc750 | ||
|
|
7e14357902 | ||
|
|
92f6c2448f | ||
|
|
b05ebe8bdf | ||
|
|
0b62390a54 | ||
|
|
8c9b5f2a18 | ||
|
|
960894c329 | ||
|
|
a5786ecccf | ||
|
|
33a83928e7 | ||
|
|
149c6271b3 | ||
|
|
031d23df0c | ||
|
|
30134bf3b5 | ||
|
|
7907150f17 | ||
|
|
e5f9639d1e | ||
|
|
47bad16b54 | ||
|
|
efa944ef8c | ||
|
|
68d4cfff16 | ||
|
|
c9c6c4c5ea | ||
|
|
792b3a1267 | ||
|
|
5b64cf6925 | ||
|
|
097878b4b2 | ||
|
|
32eca9aabf | ||
|
|
7a27432885 | ||
|
|
92e4249eec | ||
|
|
6fac0aa0f5 | ||
|
|
cd2ede5ce8 | ||
|
|
ac407375ab | ||
|
|
1730effc55 | ||
|
|
21c063ac53 | ||
|
|
2ba2ef9617 | ||
|
|
cfe2424cff | ||
|
|
397fe2a0c8 | ||
|
|
e846cc532c | ||
|
|
d273f99940 | ||
|
|
f0763c2b67 | ||
|
|
48d6293ab6 | ||
|
|
0e270595df | ||
|
|
db6770159e | ||
|
|
e6bf1ed6f7 | ||
|
|
996dd11e24 | ||
|
|
4ea1f20022 | ||
|
|
a56cfc5688 | ||
|
|
d71d05374f | ||
|
|
99b82b24d0 | ||
|
|
5cfe624e25 | ||
|
|
62066e04e3 | ||
|
|
5478c014f1 | ||
|
|
aea3147454 | ||
|
|
e466de4d0e | ||
|
|
0de3cf72b8 | ||
|
|
41fa97994e | ||
|
|
d03a563e0d | ||
|
|
0f89c0b112 | ||
|
|
07ae096af2 | ||
|
|
11e4d482f4 | ||
|
|
f959e3b869 | ||
|
|
ec19bfb556 | ||
|
|
fa4edf9845 | ||
|
|
ab52430846 | ||
|
|
6045869260 | ||
|
|
c08a598658 | ||
|
|
340483bf7f | ||
|
|
87395e0ec4 | ||
|
|
694194dd01 | ||
|
|
aa6935820d | ||
|
|
57c038dca6 | ||
|
|
fe69b1419f | ||
|
|
0cf7365755 | ||
|
|
7e96f776ff | ||
|
|
e5ce3d46de | ||
|
|
f097cb3c3a | ||
|
|
23aea7b794 | ||
|
|
c2839fdc29 | ||
|
|
3fbd64338c | ||
|
|
36a2917b17 | ||
|
|
9f3fc43791 | ||
|
|
4774d2d1fd | ||
|
|
91de8a5a8f | ||
|
|
652005264b | ||
|
|
98967410cb | ||
|
|
55b2e3f1ff | ||
|
|
cf8ddc98af | ||
|
|
f259fa85a5 | ||
|
|
5ea6c7739f | ||
|
|
eac071af9d | ||
|
|
2b0f424af6 | ||
|
|
fcbfd28025 | ||
|
|
37982fe416 | ||
|
|
6824c85820 | ||
|
|
bd9f76222a | ||
|
|
349e771f74 | ||
|
|
83c2993cef | ||
|
|
dbb91fa140 | ||
|
|
849ca6803d | ||
|
|
f41288f441 | ||
|
|
9e974d5c30 | ||
|
|
62d25ee917 | ||
|
|
652a5dd27d | ||
|
|
1051bfa661 | ||
|
|
3a3c481e3b | ||
|
|
9642c58d94 | ||
|
|
72d37ab218 | ||
|
|
a2047f9498 | ||
|
|
22a94130f2 | ||
|
|
6a907ff0c2 | ||
|
|
11e0ca29d4 | ||
|
|
47cd4ef790 | ||
|
|
25572a9d57 | ||
|
|
02b94e5672 | ||
|
|
23790fefd6 | ||
|
|
3c8e4a24c6 | ||
|
|
0f754055ab | ||
|
|
ac7c2612b2 | ||
|
|
7b54ff2046 | ||
|
|
c7743ffdbe | ||
|
|
7a604d39bd | ||
|
|
7c67f8bba1 | ||
|
|
6f2ca41eb6 | ||
|
|
b26ac16201 | ||
|
|
71b7ba70d1 | ||
|
|
96bd62e0cc | ||
|
|
c2ce598d0f | ||
|
|
cf56069804 | ||
|
|
7834b535d6 | ||
|
|
4b440199a2 | ||
|
|
4facfa5c7a | ||
|
|
99ab247c06 | ||
|
|
5cf12fced5 | ||
|
|
5abc92308a | ||
|
|
ed50ca5b53 | ||
|
|
3e8eb1a6eb | ||
|
|
d360c584c1 | ||
|
|
55482bf28a | ||
|
|
bcf5b4c1b2 | ||
|
|
7098e28532 | ||
|
|
ab8aa2e1ae | ||
|
|
97fc9a8b37 | ||
|
|
5f91a4561b | ||
|
|
b5dd56f187 | ||
|
|
819f0b7d7e | ||
|
|
16399d4512 | ||
|
|
86df184463 | ||
|
|
a4a2d66b85 | ||
|
|
1fb972cf2a | ||
|
|
f1ab0e3449 | ||
|
|
5032646090 | ||
|
|
2facce9633 | ||
|
|
bce50b991e | ||
|
|
bd1a5ef046 | ||
|
|
47e9b4b587 | ||
|
|
676e6127cd | ||
|
|
7b192a4e14 | ||
|
|
e3239ed0b3 | ||
|
|
1423454191 | ||
|
|
285b6367ed | ||
|
|
2907403f82 | ||
|
|
9a735639d4 | ||
|
|
375e3c4636 | ||
|
|
ab3d7078bf | ||
|
|
3b191b917b | ||
|
|
4be129917a | ||
|
|
507f508497 | ||
|
|
4b063b3493 | ||
|
|
8e457a20bc | ||
|
|
b9716cf2a3 | ||
|
|
b05a4ab83a | ||
|
|
ca75eb651d | ||
|
|
a6329fd3c0 | ||
|
|
89e33e984b | ||
|
|
40c0b85a10 | ||
|
|
7cdb199855 | ||
|
|
8c165dc17f | ||
|
|
43fe21a87d | ||
|
|
4b7559f2ec | ||
|
|
9e026034d7 | ||
|
|
9466ccb12a | ||
|
|
f53f314f0b | ||
|
|
9b53a3a052 | ||
|
|
7f8a95646a | ||
|
|
88d452a9fa | ||
|
|
ee2e1412d6 | ||
|
|
f2ea6af1c3 | ||
|
|
76bd5d7518 | ||
|
|
70626ccb79 | ||
|
|
124fca6d1f | ||
|
|
37154cc1ef | ||
|
|
9f42ad5746 | ||
|
|
94c19c8724 | ||
|
|
c3dd9a5f14 | ||
|
|
8f2a6acb7b | ||
|
|
821817fc89 | ||
|
|
0f3097590f | ||
|
|
20826fff54 | ||
|
|
9067e1d8d1 | ||
|
|
dbf983e287 | ||
|
|
f81043133a | ||
|
|
cab9b0bd1e | ||
|
|
d9b42016dc | ||
|
|
5ee5a23b29 | ||
|
|
a1f33d93c1 | ||
|
|
1f9a19c37a | ||
|
|
9e9d371a3f | ||
|
|
b97136a606 | ||
|
|
f1df463a07 | ||
|
|
e59c335cac | ||
|
|
a9280edf97 | ||
|
|
68eca70a31 | ||
|
|
dd68c35295 | ||
|
|
57530af792 | ||
|
|
4947fa91ef | ||
|
|
c605cc77d5 | ||
|
|
6fe6b681ac | ||
|
|
08884af624 | ||
|
|
2adfbb5169 | ||
|
|
831c3c4fa4 | ||
|
|
df87b51e63 | ||
|
|
a17fceac39 | ||
|
|
f6e05a27d8 | ||
|
|
19cb4ee7a4 | ||
|
|
8d4f853432 | ||
|
|
76eaac5d31 | ||
|
|
5c02943ab3 | ||
|
|
09a38e2dcd | ||
|
|
4a772953dd | ||
|
|
be544e09cf | ||
|
|
8a817980db | ||
|
|
1dbfbe5d5f | ||
|
|
53ed0dc12d | ||
|
|
1bbff65f5d | ||
|
|
8e5e9ab517 | ||
|
|
c2d37932e1 | ||
|
|
7e7bbd4591 | ||
|
|
6d2d3c4c3b | ||
|
|
a1ba7f9e5f | ||
|
|
b89949e361 | ||
|
|
72ad111198 | ||
|
|
00b4e9a9b4 | ||
|
|
a460fe12a1 | ||
|
|
7e4fdcd9a3 | ||
|
|
0f4d06b4d4 | ||
|
|
bfbf6edc87 | ||
|
|
5027e2f03a | ||
|
|
70ea05c0ca | ||
|
|
5e9d9abe10 | ||
|
|
128495dd91 | ||
|
|
314a8c0741 | ||
|
|
8c6ca6f8f7 | ||
|
|
421bb3b701 | ||
|
|
a8e9ef49f4 | ||
|
|
2474ad8d1d | ||
|
|
dd58ac01ed | ||
|
|
fd4fc377a1 | ||
|
|
3b31cec3e7 | ||
|
|
28fc95dd1f | ||
|
|
d7dc51980a | ||
|
|
1fd1f08f33 | ||
|
|
f43ec50369 | ||
|
|
d4b6b5744d | ||
|
|
3ae9ddf0f9 | ||
|
|
4a61e5cbdb | ||
|
|
44b031af15 | ||
|
|
ad197f85de | ||
|
|
e7c2990b79 | ||
|
|
21aa33c862 | ||
|
|
3f8cd143b6 | ||
|
|
5d25fec242 | ||
|
|
8aec9f3f25 | ||
|
|
81d14cbecc | ||
|
|
6f1f4cc576 | ||
|
|
f81be0a075 | ||
|
|
2aa57404f2 | ||
|
|
d43cb4b097 | ||
|
|
7d69100d2d | ||
|
|
c04a975dba | ||
|
|
59f920445c | ||
|
|
72425b243b | ||
|
|
8af777fac4 | ||
|
|
510d94b21c | ||
|
|
476cfec461 | ||
|
|
642869ffed | ||
|
|
4e6a552689 | ||
|
|
707581f103 | ||
|
|
ef68d43acb | ||
|
|
50f15f7539 | ||
|
|
9f2ae92236 | ||
|
|
854f14a014 | ||
|
|
cdeef6aae0 | ||
|
|
5000a56620 | ||
|
|
e5438a7c37 | ||
|
|
ed984a0642 | ||
|
|
077c88b778 | ||
|
|
2077b0a35f | ||
|
|
8cbffd7c11 | ||
|
|
3bc3038c2f | ||
|
|
ee3c54ecd0 | ||
|
|
89b10a8ccf | ||
|
|
31d702c550 | ||
|
|
a5f98cb46b | ||
|
|
b88156a672 | ||
|
|
311a53e4ce | ||
|
|
bd55bb88b9 | ||
|
|
7461c5ee1f | ||
|
|
6daf5b6afa | ||
|
|
32c9678513 | ||
|
|
2f74296de6 | ||
|
|
2d32d53075 | ||
|
|
9fa389bb99 | ||
|
|
c9151306da | ||
|
|
5d11777dd8 | ||
|
|
eab9ced72f | ||
|
|
929a72be79 | ||
|
|
c62c8d338e | ||
|
|
524af22cb0 | ||
|
|
b7f891ce39 | ||
|
|
178a20c6d4 | ||
|
|
83e13ae782 | ||
|
|
dc654b89fb | ||
|
|
bee2e084ba | ||
|
|
9ea3f9fd80 | ||
|
|
5d78740f6f | ||
|
|
9cda942dea | ||
|
|
2b728114a2 | ||
|
|
6f1ab140a6 | ||
|
|
8655349f00 | ||
|
|
d75edae498 | ||
|
|
6d5970adcb | ||
|
|
40f925e660 | ||
|
|
d9aab7db19 | ||
|
|
6f7a12c8be | ||
|
|
48d7d5a0b9 | ||
|
|
d459270d7d | ||
|
|
9e7a7ad920 | ||
|
|
ffcd3aa3c2 | ||
|
|
ecd933f94a | ||
|
|
9db8365930 | ||
|
|
64187169a0 | ||
|
|
f016ba700c | ||
|
|
02076b680e | ||
|
|
a6e16ad99f | ||
|
|
3f2de363c9 | ||
|
|
511d8d0cb4 | ||
|
|
4798d6b7e5 | ||
|
|
47775d2758 | ||
|
|
789e22ea08 | ||
|
|
fb8badbfd9 | ||
|
|
1c36f33a69 | ||
|
|
58dd31611d | ||
|
|
7d9033a212 | ||
|
|
564255978d | ||
|
|
cc87b603fb | ||
|
|
118a0b4214 | ||
|
|
b56580e3d1 | ||
|
|
2a1b77ff6b | ||
|
|
d94a54e392 | ||
|
|
1ab5db3183 | ||
|
|
560348fcfe | ||
|
|
e9f74bfb4d | ||
|
|
13eec1c31a | ||
|
|
aa64ca58f4 | ||
|
|
018c3a1cc6 | ||
|
|
e0bf4394b7 | ||
|
|
d684a3ede2 | ||
|
|
0bb1ab04d3 | ||
|
|
f35f5ccfe4 | ||
|
|
dc58737310 | ||
|
|
f246415f27 | ||
|
|
e7badbf124 | ||
|
|
caddb16b53 | ||
|
|
0d18206ae6 | ||
|
|
71c82c0f0d | ||
|
|
cc52fdcf5a | ||
|
|
6d31991fda | ||
|
|
7988f8cc1b | ||
|
|
59366d6f33 | ||
|
|
4f7970cb0e | ||
|
|
5794b2e977 | ||
|
|
291feff903 | ||
|
|
19ce81e995 | ||
|
|
4b20d5b954 | ||
|
|
90221a0e16 | ||
|
|
085036bc93 | ||
|
|
21186d0858 | ||
|
|
22e8d15f60 | ||
|
|
f258e6d17f | ||
|
|
a6a4185585 | ||
|
|
9924a574a8 | ||
|
|
7282d02c22 | ||
|
|
1ea62c3c60 | ||
|
|
5929a37d73 | ||
|
|
f4a0ec30ec | ||
|
|
c5c1e3c1ca | ||
|
|
449c74eb66 | ||
|
|
016717c8ef | ||
|
|
75bb80d343 | ||
|
|
c04c32e876 | ||
|
|
09025e0a8f | ||
|
|
13e845a5da | ||
|
|
2f4284f806 | ||
|
|
d4af5926cb | ||
|
|
0ad2f463ab | ||
|
|
3b49f9947f | ||
|
|
514b536f57 | ||
|
|
040143f557 | ||
|
|
c75d0cc989 | ||
|
|
701ac3f85d | ||
|
|
f283cff6bc | ||
|
|
d8e62cc541 | ||
|
|
8853c8a124 | ||
|
|
027fa4119b | ||
|
|
5424375e0d | ||
|
|
6007658c72 | ||
|
|
e0916858a8 | ||
|
|
1f397978d6 | ||
|
|
518f58885e | ||
|
|
ece24fa24b | ||
|
|
6524eb2b55 | ||
|
|
b16a6b1ab1 | ||
|
|
cda2ca43dd | ||
|
|
3375d30fad | ||
|
|
6b61c462a0 | ||
|
|
226fab5904 | ||
|
|
be2791462e | ||
|
|
9b38e6cb8f | ||
|
|
bce7830b86 | ||
|
|
4e6a3311a4 | ||
|
|
f8c1c87b01 | ||
|
|
94c3ae6533 | ||
|
|
8ca1ed17d4 | ||
|
|
a645363091 | ||
|
|
7db3c29e14 | ||
|
|
4c28475c0d | ||
|
|
c509f17789 | ||
|
|
6690ea7c0e | ||
|
|
347942668d | ||
|
|
1a3643d6f8 | ||
|
|
0af64210c5 | ||
|
|
7989b8eb0e | ||
|
|
4d902a03ca | ||
|
|
8af5b826b2 | ||
|
|
953b8e4fb2 | ||
|
|
95c4af6f84 | ||
|
|
f2be35ee94 | ||
|
|
056bc1af22 | ||
|
|
46501093ce | ||
|
|
0344e3f633 | ||
|
|
7882607c6e | ||
|
|
684c02b1c5 | ||
|
|
7d8889d045 | ||
|
|
a05b293582 | ||
|
|
c37085a1c7 | ||
|
|
bb8d7e2339 | ||
|
|
84c87d2dee | ||
|
|
710566e817 | ||
|
|
a1ca96017c | ||
|
|
686868519c | ||
|
|
f16f0e7e08 | ||
|
|
f96bf404e6 | ||
|
|
6d6a80dd4d | ||
|
|
94532e8598 | ||
|
|
254449ec1a | ||
|
|
3c69021c6c | ||
|
|
68955d67bb | ||
|
|
bc1eadad26 | ||
|
|
ebe6245243 | ||
|
|
d12c624b23 | ||
|
|
11c1668d65 | ||
|
|
158c27e52b | ||
|
|
36b777da8f | ||
|
|
012010957d | ||
|
|
b0f041328d | ||
|
|
94a12d9767 | ||
|
|
06cbb3e7f8 | ||
|
|
47de40856b | ||
|
|
c044fa2932 | ||
|
|
10f2145c15 | ||
|
|
de89355b05 | ||
|
|
54f9eed786 | ||
|
|
d8e73e9cb4 | ||
|
|
ba7f07e1c8 | ||
|
|
9353015ddc | ||
|
|
780e300815 | ||
|
|
4ed6464bbe | ||
|
|
8a9ed91051 | ||
|
|
784fabc288 | ||
|
|
1a25d1b866 | ||
|
|
b4bd2c3785 | ||
|
|
c72a18c7e4 | ||
|
|
51095f4a9f | ||
|
|
f2af835a02 | ||
|
|
fa8965f7bd | ||
|
|
e4da8a26b0 | ||
|
|
45351a504f | ||
|
|
48ce3fd4d0 | ||
|
|
98825a7d5f | ||
|
|
7011688812 | ||
|
|
ea8a3049cd | ||
|
|
d2c1e7aca5 | ||
|
|
2d80e831be | ||
|
|
c83e7b2b14 | ||
|
|
c7c71bc785 | ||
|
|
8c0e27b83b | ||
|
|
d275837da5 | ||
|
|
c1ecb703bf | ||
|
|
f804b56f92 | ||
|
|
d7a04c86f3 | ||
|
|
652b76ba50 | ||
|
|
359de1a1e5 | ||
|
|
eecec3106b | ||
|
|
f233b23569 | ||
|
|
0862a81322 | ||
|
|
6421fc9519 | ||
|
|
b1ef59bff9 | ||
|
|
cd7670ad11 | ||
|
|
79fa482f70 | ||
|
|
11705f7ccd | ||
|
|
11da4794b5 | ||
|
|
5cc6ae012a | ||
|
|
938c321a54 | ||
|
|
867422a2d9 | ||
|
|
8a547877b5 | ||
|
|
3e301af80c | ||
|
|
41ad6dc339 | ||
|
|
2f17f0d4ae | ||
|
|
f1f99a1d92 | ||
|
|
ad6fbe2bf3 | ||
|
|
e0bfad4807 | ||
|
|
3572ab9452 | ||
|
|
30ed285dcf | ||
|
|
2881491cc2 | ||
|
|
96f4d3e136 | ||
|
|
9e9ab6ae13 | ||
|
|
029cad9224 | ||
|
|
0029315c2e | ||
|
|
247c3b58ec | ||
|
|
7a165adfb8 | ||
|
|
5777319bfa | ||
|
|
9219c5a1ad | ||
|
|
096295d258 | ||
|
|
aa26e0c44d | ||
|
|
8fcd0c5e4c | ||
|
|
1a26487df3 | ||
|
|
7afedf59bf | ||
|
|
e81267995d | ||
|
|
9c206629b1 | ||
|
|
1e8dcdf4ea | ||
|
|
027e120c87 | ||
|
|
09de9e5fda | ||
|
|
b1c64bbd98 | ||
|
|
7ec6f6caad | ||
|
|
85babbeb01 | ||
|
|
0006223494 | ||
|
|
5f1bf408f7 | ||
|
|
8e8fd019c3 | ||
|
|
c7b98005bb | ||
|
|
508e95b9d8 | ||
|
|
5245df3ce3 | ||
|
|
ec66dd5649 | ||
|
|
d28c3f9ba3 | ||
|
|
64705b393a | ||
|
|
42b4557de7 | ||
|
|
d089a06566 | ||
|
|
4bbfdf8e85 | ||
|
|
1e6db0f0a5 | ||
|
|
0778c150b6 | ||
|
|
30a7170f50 | ||
|
|
b9ca828522 | ||
|
|
5f73d1e57a | ||
|
|
b0f4c4b8e0 | ||
|
|
10e343b8bb | ||
|
|
0ab5630ebf | ||
|
|
86a47ad533 | ||
|
|
fecf36a7ff | ||
|
|
16b392d360 | ||
|
|
dd6709c14a | ||
|
|
69622ad25a | ||
|
|
7e6a5642c1 | ||
|
|
ed9923dbf9 | ||
|
|
bb13d0b6f9 | ||
|
|
423e00eccd | ||
|
|
90ffa50718 | ||
|
|
4f0ed25e8e | ||
|
|
3f99cc7a6d | ||
|
|
a7e9d43411 | ||
|
|
c79bc6e32f | ||
|
|
a2b18aaec4 | ||
|
|
886e96f5d6 | ||
|
|
88388fc5b1 | ||
|
|
7cd4dec4df | ||
|
|
567c43f2f0 | ||
|
|
566a2509c9 | ||
|
|
dbaad2e169 | ||
|
|
2fdf9f9a8c | ||
|
|
ebfcef12a7 | ||
|
|
a0fc26afd7 | ||
|
|
5c8c8b6502 | ||
|
|
7e38749e40 | ||
|
|
e538336c5e | ||
|
|
f14b519756 | ||
|
|
034a1b3991 | ||
|
|
a2144d21c2 | ||
|
|
4a50a3161d | ||
|
|
d983b1f329 | ||
|
|
c1d5429245 | ||
|
|
c094016c33 | ||
|
|
5caf503784 | ||
|
|
93cd9f6880 | ||
|
|
7c7f5220da | ||
|
|
09e916fe18 | ||
|
|
471d9eed35 | ||
|
|
4457385281 | ||
|
|
c37d999ff3 | ||
|
|
6813b01c20 | ||
|
|
2e6032f81b | ||
|
|
001e9989b8 | ||
|
|
45dc0a2a72 | ||
|
|
dbb511b2f9 | ||
|
|
cbd13b49ef | ||
|
|
5902a7f113 | ||
|
|
70e71fc1da | ||
|
|
91d941b663 | ||
|
|
75ca1ff498 | ||
|
|
b0e5ad3ef5 | ||
|
|
0c12ff7662 | ||
|
|
d44c6c593d | ||
|
|
cdce35ec7a | ||
|
|
20210d7d54 | ||
|
|
8ade843108 | ||
|
|
faab0e841e | ||
|
|
efae6afd5a | ||
|
|
bb8f13d57b | ||
|
|
ff1903b1cc | ||
|
|
db193bd94a | ||
|
|
0ad1d57785 | ||
|
|
0c340286ef | ||
|
|
e60d681992 | ||
|
|
fff48260be | ||
|
|
2e7752559d | ||
|
|
aa000e46ed | ||
|
|
8ed441d968 | ||
|
|
c711a0c32c | ||
|
|
2bdfe2e12a | ||
|
|
1023fd90f1 | ||
|
|
059a59c746 | ||
|
|
2363a1fbb6 | ||
|
|
26b2cc816e | ||
|
|
93fc4950df | ||
|
|
f042076cab | ||
|
|
9a6fd69885 | ||
|
|
ffbf4b07f1 | ||
|
|
88ac570031 | ||
|
|
70c5d4eb43 | ||
|
|
1899f97b1e | ||
|
|
56cbb44f94 | ||
|
|
42c6b70f2b | ||
|
|
f673a36ccd | ||
|
|
80f319311e | ||
|
|
d256d5f7da | ||
|
|
0b7737a054 | ||
|
|
89f0c01acc | ||
|
|
d66cde252d | ||
|
|
c9103bd521 | ||
|
|
d2e87b0e13 | ||
|
|
a7eb042276 | ||
|
|
114796006f | ||
|
|
38932f14cc | ||
|
|
3ee1286cf4 | ||
|
|
ffef5923c6 | ||
|
|
e670c679df | ||
|
|
06ba235947 | ||
|
|
db72bb33f2 | ||
|
|
0c700cd1f8 | ||
|
|
721b63b197 | ||
|
|
41ca477b8a | ||
|
|
9fd5220e8c | ||
|
|
65be8cd2ce | ||
|
|
4b24ed4a21 | ||
|
|
93bdb4236e | ||
|
|
db82a14837 | ||
|
|
b6cb460085 | ||
|
|
358bb18f8f | ||
|
|
bd2cc60aae | ||
|
|
f00bd31eaf | ||
|
|
191ca88daa | ||
|
|
611571d566 | ||
|
|
11550edcd0 | ||
|
|
3c51f44190 | ||
|
|
ee3fbcae78 | ||
|
|
63ad119e63 | ||
|
|
bfcae3fc70 | ||
|
|
21b261b2d3 | ||
|
|
9ac94167d2 | ||
|
|
170c782ba2 | ||
|
|
7465259340 | ||
|
|
c871e8b64e | ||
|
|
1dfaa93568 | ||
|
|
866c480634 | ||
|
|
ae6fbc1acb | ||
|
|
1cbf9c5eb8 | ||
|
|
210da6c94b | ||
|
|
5be889d169 | ||
|
|
03f992bfc5 | ||
|
|
db6afdbfbb | ||
|
|
624c562c1f | ||
|
|
4809eccb9e | ||
|
|
1f9f46175c | ||
|
|
71b16e1824 | ||
|
|
3975b2c6e3 | ||
|
|
48bcb02f82 | ||
|
|
9fd83e56e2 | ||
|
|
a40742a45c | ||
|
|
4d10e7e290 | ||
|
|
83f1a1ef8e | ||
|
|
47581eb453 | ||
|
|
045ac03837 | ||
|
|
6439845d32 | ||
|
|
0b0721b9d3 | ||
|
|
f1ba65affa | ||
|
|
e60bdbf80c | ||
|
|
2269b500f3 | ||
|
|
089f2e964c | ||
|
|
1e477f71ef | ||
|
|
430e8efd42 | ||
|
|
bddd7e5417 | ||
|
|
10deb4c51a | ||
|
|
530c3a5576 | ||
|
|
4e5cb34051 | ||
|
|
e7c5eb6bee | ||
|
|
f2cb6ed1a6 | ||
|
|
ce745204f7 | ||
|
|
8b3c9f2f72 | ||
|
|
2aa1c85972 | ||
|
|
766e5db363 | ||
|
|
c1504e104d | ||
|
|
3705b740c9 | ||
|
|
85889ee338 | ||
|
|
32254fbed8 | ||
|
|
62fbac2d19 | ||
|
|
6b02bb973f | ||
|
|
1ad15035de | ||
|
|
dd7a21010a | ||
|
|
f69ab641f7 | ||
|
|
27308b59ea | ||
|
|
f1aa9fe906 | ||
|
|
112b28342a | ||
|
|
2f9fe92b6c | ||
|
|
4a45c78512 | ||
|
|
0c78f66c21 | ||
|
|
be6f934e7b | ||
|
|
79fa11f2c9 | ||
|
|
349966a7c3 | ||
|
|
4867825367 | ||
|
|
36686afe04 | ||
|
|
9547896074 | ||
|
|
4ecb737ea7 | ||
|
|
0147f398ec | ||
|
|
3fd0420b52 | ||
|
|
decba39373 | ||
|
|
15972a9203 | ||
|
|
94c0a52824 | ||
|
|
d583f52d7e | ||
|
|
9b243f90ee | ||
|
|
18168e4577 | ||
|
|
a18e5454ce | ||
|
|
7bf16f2891 | ||
|
|
8491387a15 | ||
|
|
123ae18a64 | ||
|
|
f3a19597fc | ||
|
|
6c6ccbf07c | ||
|
|
09af9a2576 | ||
|
|
cf813d7eb4 | ||
|
|
f0ca523dfb | ||
|
|
6a08443853 | ||
|
|
030b9b0c82 | ||
|
|
8a0b511649 | ||
|
|
7853b830ae | ||
|
|
77eb7f7821 | ||
|
|
65e71ac56f | ||
|
|
a2a1bcb77b | ||
|
|
e8fadc638c | ||
|
|
507a8330b7 | ||
|
|
1dc1c99c33 | ||
|
|
0cd89585e2 | ||
|
|
c54148c291 | ||
|
|
f7d7fa5031 | ||
|
|
d959bd6c34 | ||
|
|
92240d3e8b | ||
|
|
0c634a4fa6 | ||
|
|
c8e2414a70 | ||
|
|
a6956b34db | ||
|
|
055c2ec202 | ||
|
|
1c0e03eadc | ||
|
|
f8c44cf2d3 | ||
|
|
d99eeb1e1e | ||
|
|
c42a4d9d1c | ||
|
|
2b8eb11bc3 | ||
|
|
f4f62c6edf | ||
|
|
142f75e662 | ||
|
|
1bc19921ff | ||
|
|
d901b795f7 | ||
|
|
de19e3abec | ||
|
|
ee1ab72d0a | ||
|
|
c699dd71af | ||
|
|
3452bafd4b | ||
|
|
0aabac338d | ||
|
|
a9a25a9197 | ||
|
|
aa295e9688 | ||
|
|
9cf21b1192 | ||
|
|
7efb6fc125 | ||
|
|
a050af7985 | ||
|
|
25e3fd90c5 | ||
|
|
e79384a3c4 | ||
|
|
c784af67da | ||
|
|
74d4681f33 | ||
|
|
eaa939eda3 | ||
|
|
a3b21debb0 | ||
|
|
db9168e179 | ||
|
|
d68a5106a5 | ||
|
|
5244d7af1e | ||
|
|
0ee095617e | ||
|
|
8d7d022576 | ||
|
|
098cf78753 | ||
|
|
4131811e32 | ||
|
|
260cd62329 | ||
|
|
b6b8ce55c6 | ||
|
|
1d3e5a60e2 | ||
|
|
b2397cfd54 | ||
|
|
15ef3bdadb | ||
|
|
c353e713cc | ||
|
|
1eaaad320f | ||
|
|
e0c0071d8b | ||
|
|
0a7c274f33 | ||
|
|
d4621f8b8a | ||
|
|
5b0c1fd16e | ||
|
|
6b5847f149 | ||
|
|
b227d89a01 | ||
|
|
a4191556ea | ||
|
|
a7d90c0b39 | ||
|
|
6edef4ec97 | ||
|
|
17f7ae7daa | ||
|
|
03d33f2074 | ||
|
|
8787d8ad43 | ||
|
|
69f98aa756 | ||
|
|
9620faafe1 | ||
|
|
2ba0b164fc | ||
|
|
77b9b2e8ce | ||
|
|
58be436aaa | ||
|
|
5995eb8b8e | ||
|
|
0e4f942b15 | ||
|
|
c14f773ed4 | ||
|
|
3ef08a53d1 | ||
|
|
67d32c5a96 | ||
|
|
e351793043 | ||
|
|
f0a987e7a2 | ||
|
|
c0354961f7 | ||
|
|
4c8c8dcdab | ||
|
|
cae80096e1 | ||
|
|
45d4fcc0bf | ||
|
|
da5380a472 | ||
|
|
b27544663e | ||
|
|
75c35aa9e5 | ||
|
|
ad0fd8d5c8 | ||
|
|
c60cd1fabb | ||
|
|
7cfb6258d5 | ||
|
|
ff1bc2577f | ||
|
|
07ab3621b3 | ||
|
|
0af3f3fcce | ||
|
|
cb8a70744d | ||
|
|
c6a8d3e251 | ||
|
|
d871292d39 | ||
|
|
255febcd70 | ||
|
|
c7d8f77fb4 | ||
|
|
248de4ef0f | ||
|
|
56ae5d2c33 | ||
|
|
2cc575d74b | ||
|
|
b0b68d16a4 | ||
|
|
45fd510e15 | ||
|
|
0376a58374 | ||
|
|
b1d1d2c7f8 | ||
|
|
6de14c91e4 | ||
|
|
a04cbeba78 | ||
|
|
82610e281d | ||
|
|
e9b3213d8b | ||
|
|
313ee50bf2 | ||
|
|
fca72e3fad | ||
|
|
97657da95d | ||
|
|
f32240ba65 | ||
|
|
1d8d20a7d7 | ||
|
|
5fba2685b1 | ||
|
|
2c39551560 | ||
|
|
265ca8b433 | ||
|
|
8c4b33ec17 | ||
|
|
937bf6f57a | ||
|
|
e2f142ed28 | ||
|
|
9a835690aa | ||
|
|
5f4ec1c446 | ||
|
|
7b11516520 | ||
|
|
4a6543b0c6 | ||
|
|
fa197c1b7f | ||
|
|
ab4ab7e63b | ||
|
|
b88a8e605a | ||
|
|
519d8fc64a | ||
|
|
8032fde262 | ||
|
|
02b4b0b9b6 | ||
|
|
933c820f20 | ||
|
|
8476fdb77f | ||
|
|
ae1f280062 | ||
|
|
d58dd28051 | ||
|
|
a212382da6 | ||
|
|
b1819ff755 | ||
|
|
2317305f0e | ||
|
|
da5f7e27c4 | ||
|
|
4c4dd71195 | ||
|
|
61c3d3c34a | ||
|
|
59d4e30469 | ||
|
|
e0dd34df2b | ||
|
|
41366d9376 | ||
|
|
1779b8d67e | ||
|
|
b673cbe411 | ||
|
|
1a595c5043 | ||
|
|
bce5ae88ee | ||
|
|
b81d15cdef | ||
|
|
ec6747ac09 | ||
|
|
5cd35bbfb5 | ||
|
|
1b07509e18 | ||
|
|
35211b94db | ||
|
|
0e9338a0e4 | ||
|
|
95b099d62c | ||
|
|
b295963d8d | ||
|
|
884ca797d8 | ||
|
|
3664b4acec | ||
|
|
95f33310bf | ||
|
|
a1500051b9 | ||
|
|
be1a6cc985 | ||
|
|
32525a0204 | ||
|
|
8b64c8afd3 | ||
|
|
2795413fc1 | ||
|
|
8db9fc3f17 | ||
|
|
d7c2fb8a02 | ||
|
|
6c9d09f6c3 | ||
|
|
1482fe3e1b | ||
|
|
c2f0422a68 | ||
|
|
4b3a7af308 | ||
|
|
7b2cd6e4b0 | ||
|
|
60dbe28dca | ||
|
|
3dde8c5905 | ||
|
|
9207ef357f | ||
|
|
ce7ea27255 | ||
|
|
c3eb701c56 | ||
|
|
180c1fe185 | ||
|
|
9b3138102d | ||
|
|
e43412c02f | ||
|
|
a223fad28c | ||
|
|
9ade31a813 | ||
|
|
fd91024b8d | ||
|
|
0d9526ed72 | ||
|
|
d4e51a1b51 | ||
|
|
ded26df5d0 | ||
|
|
a3ba44771f | ||
|
|
83f4ac980e | ||
|
|
5da102c058 | ||
|
|
27608327af | ||
|
|
fcbbd53dd8 | ||
|
|
76aaab319c | ||
|
|
6ea6a46c15 | ||
|
|
c0f3e62a10 | ||
|
|
5f71660c7a | ||
|
|
e6b15de5a4 | ||
|
|
bf74569906 | ||
|
|
b70f1f84be | ||
|
|
a2209e73fc | ||
|
|
f77ebe3af7 | ||
|
|
8c4370780d | ||
|
|
e4b352f1ce | ||
|
|
a679e33d54 | ||
|
|
e477ce3d9a | ||
|
|
f15d7ac445 | ||
|
|
d36af364bf | ||
|
|
b0ebcf86e6 | ||
|
|
247ec75c9e | ||
|
|
3b43bf3579 | ||
|
|
2be6d5cdef | ||
|
|
fef7526af4 | ||
|
|
88ee26ecf8 | ||
|
|
947c602cb0 | ||
|
|
fd0cf4e4e3 | ||
|
|
0d11bffa82 | ||
|
|
c9a9c48de3 | ||
|
|
fbbcb58e3f | ||
|
|
ed9dbea34a | ||
|
|
9ccca42011 | ||
|
|
d7bc92e465 | ||
|
|
5d29ebfde2 | ||
|
|
efa14f2dcd | ||
|
|
2e07802a05 | ||
|
|
34a83a46d9 | ||
|
|
9db5c68a64 | ||
|
|
5dac84c511 | ||
|
|
ce834c9b18 | ||
|
|
0af5f626bc | ||
|
|
0d68186dfe | ||
|
|
65e226d02d | ||
|
|
15d0654cd7 | ||
|
|
f7c36efab8 | ||
|
|
09e372f37b | ||
|
|
a020e06c3d | ||
|
|
650f2caf42 | ||
|
|
bfaa5a9878 | ||
|
|
7d22fc727b | ||
|
|
e78db0d549 | ||
|
|
4a610d9868 | ||
|
|
2c08ab1e52 | ||
|
|
70c26493c4 | ||
|
|
e5f45550d4 | ||
|
|
80c196d863 | ||
|
|
516ee4b762 | ||
|
|
7371e15ebb | ||
|
|
b6f6f79c00 | ||
|
|
d8dd38dcdd | ||
|
|
1384c2b477 | ||
|
|
2006a44fdd | ||
|
|
f7c04c9f7e | ||
|
|
1dbb36bd77 | ||
|
|
54cabebbfa | ||
|
|
f31c2296dd | ||
|
|
6f66fd9597 | ||
|
|
8c33bd828a | ||
|
|
068352de91 | ||
|
|
f5c166906b | ||
|
|
514772f5c2 | ||
|
|
2e0c2aa8bc | ||
|
|
bfd641f1b1 | ||
|
|
1c45af855d | ||
|
|
5d7cf75bed | ||
|
|
04ee23b7b5 | ||
|
|
99c3c71be8 | ||
|
|
0c2fb54d14 | ||
|
|
eaf875dfd5 | ||
|
|
29e61087ac | ||
|
|
ef989e7909 | ||
|
|
ab1fd464cf | ||
|
|
4a042b73b8 | ||
|
|
b416a7de09 | ||
|
|
ebec631cde | ||
|
|
cd6a751a68 | ||
|
|
f90692b526 | ||
|
|
b4ff72d424 | ||
|
|
61de002e0e | ||
|
|
6c063c856c | ||
|
|
a075a0b29b | ||
|
|
0e85e70470 | ||
|
|
0e84d1dad1 | ||
|
|
092b3bf9c2 | ||
|
|
87cf688e27 | ||
|
|
892cab3adf | ||
|
|
c547f50dc8 | ||
|
|
952bcecb12 | ||
|
|
1da97c3cd6 | ||
|
|
b9abc13364 | ||
|
|
fcd2ffa630 | ||
|
|
1f769bc7b5 | ||
|
|
e84a5ad62a | ||
|
|
5a481aeb5e | ||
|
|
24cea1d8d2 | ||
|
|
134f368953 | ||
|
|
8a6749e38b | ||
|
|
0722639001 | ||
|
|
e372dd8ddf | ||
|
|
c66022eaa8 | ||
|
|
3b873fc06c | ||
|
|
f2ac7cca28 | ||
|
|
d21d909de6 | ||
|
|
7c476ec104 | ||
|
|
3716772a06 | ||
|
|
c1100a5c62 | ||
|
|
0ae1332480 | ||
|
|
222debc335 | ||
|
|
7702ac5475 | ||
|
|
b4f4b10332 | ||
|
|
fb795a22c8 | ||
|
|
e5a4af88cc | ||
|
|
f68508a9ef | ||
|
|
1e4adf115c | ||
|
|
dbc8a2b7b2 | ||
|
|
f339bfe45d | ||
|
|
a78b88c2a4 | ||
|
|
4a338ad94e | ||
|
|
03d0e38f15 | ||
|
|
7da16b469c | ||
|
|
c80fcc5494 | ||
|
|
0286396175 | ||
|
|
edf381c914 | ||
|
|
58d85518f0 | ||
|
|
32e8ebf4f8 | ||
|
|
c8cba6ba91 | ||
|
|
fba5487231 | ||
|
|
9d81fbcbcb | ||
|
|
40c55b3515 | ||
|
|
e5aaaff32e | ||
|
|
6071b138f0 | ||
|
|
71174f5d40 | ||
|
|
bb4ca4ff9e | ||
|
|
93f42e9a67 | ||
|
|
2b6c1ec771 | ||
|
|
958546fd0c | ||
|
|
d263494229 | ||
|
|
12d51318a8 | ||
|
|
aa8861643f | ||
|
|
0f0e4e35ce | ||
|
|
79f63a794d | ||
|
|
47e01dadb7 | ||
|
|
3bfa2bd9e4 | ||
|
|
b1aac49824 | ||
|
|
16fdfa3cb1 | ||
|
|
9c43badbfc | ||
|
|
96689b8e67 | ||
|
|
95b2a56f80 | ||
|
|
6c33f6ff08 | ||
|
|
ab515920de | ||
|
|
46fd65f98a | ||
|
|
e9986c9667 | ||
|
|
fec40dd91c | ||
|
|
e8bffd8dc6 | ||
|
|
59e6271373 | ||
|
|
0dbb0b6e12 | ||
|
|
5d1c166606 | ||
|
|
366463ac4e | ||
|
|
ed5d4f5fcd | ||
|
|
787bc2243f | ||
|
|
8b5fccdd0d | ||
|
|
fa79467c19 | ||
|
|
5a4577b7ec | ||
|
|
0c82c60c03 | ||
|
|
84e33ec18a | ||
|
|
779be53910 | ||
|
|
cda6e3c204 | ||
|
|
c73aee1633 | ||
|
|
d486b8b4cf | ||
|
|
31909ba17b | ||
|
|
27dc30ca57 | ||
|
|
f5b7ddcf0e | ||
|
|
f1aac3e9e2 | ||
|
|
0f626dada2 | ||
|
|
74f4c74f47 | ||
|
|
3e93ab0fac | ||
|
|
4ac4ec58a8 | ||
|
|
af552414ac | ||
|
|
728d7c5d24 | ||
|
|
ee17b54872 | ||
|
|
373a557ad2 | ||
|
|
062eeb8ac0 | ||
|
|
473a7fc647 | ||
|
|
4ddd9dd874 | ||
|
|
05dd048410 | ||
|
|
c2c4afa094 | ||
|
|
be2567f478 | ||
|
|
e9e9c9f702 | ||
|
|
1e3d653d1c | ||
|
|
63f3f3899e | ||
|
|
08362d1bb6 | ||
|
|
f1d3d78f89 | ||
|
|
5f084b9d7c | ||
|
|
224d694ffa | ||
|
|
1be4b06d33 | ||
|
|
155c3a4b50 | ||
|
|
371f912c1d | ||
|
|
41c47fbc58 | ||
|
|
167030c15c | ||
|
|
7403a07614 | ||
|
|
fd0bad9a91 | ||
|
|
c2381ac3f5 | ||
|
|
ee18fbac19 | ||
|
|
b83ec99e4f | ||
|
|
72bd03d368 | ||
|
|
a6ee68efe8 | ||
|
|
795ca64ac0 | ||
|
|
ee26df3b68 | ||
|
|
bc0cf84297 | ||
|
|
049f846980 | ||
|
|
67308b8e3a | ||
|
|
8c9cdb28d0 | ||
|
|
dd30b30b34 | ||
|
|
6446097840 | ||
|
|
589cf2afd8 | ||
|
|
fb1928d5ec | ||
|
|
552b41f855 | ||
|
|
e2a036192a | ||
|
|
b6c5f670b3 | ||
|
|
ecac1c5b78 | ||
|
|
1d3f124845 | ||
|
|
d0b39df3e2 | ||
|
|
beb52670fa | ||
|
|
c924407569 | ||
|
|
bb72002452 | ||
|
|
78f045ddf2 | ||
|
|
ba49220e43 | ||
|
|
857bceba76 | ||
|
|
859280c2d7 | ||
|
|
e8ac5dbe8d | ||
|
|
86ec75b05e | ||
|
|
6b00fdb19e | ||
|
|
b9707ea486 | ||
|
|
f9dd71c05a | ||
|
|
0720a9b8f0 | ||
|
|
0289733f7f | ||
|
|
50ed8ad1e5 | ||
|
|
a10fabd927 | ||
|
|
0dd9fdf7e6 | ||
|
|
ade5f386b6 | ||
|
|
315b135a3b | ||
|
|
bc3d1daf11 | ||
|
|
50d738c7b6 | ||
|
|
0a23ac1401 | ||
|
|
9cbfb4ccbe | ||
|
|
7c1a76d38e | ||
|
|
b501b86656 | ||
|
|
f118cff5c0 | ||
|
|
66d7a9e520 | ||
|
|
a3424ce020 | ||
|
|
7cb72b25be | ||
|
|
f3584b02ac | ||
|
|
f2aaf9cc26 | ||
|
|
ea0bab5bd6 | ||
|
|
78be6f9839 | ||
|
|
a7df5c7fac | ||
|
|
516a6be98e | ||
|
|
7077e30d94 | ||
|
|
21b4053b8b | ||
|
|
85c634480d | ||
|
|
54eefe2295 | ||
|
|
b32dbc3ac5 | ||
|
|
4a5d089e5d | ||
|
|
22cad5a2ab | ||
|
|
acbebd7102 | ||
|
|
301ae12169 | ||
|
|
f04bdcee69 | ||
|
|
d4b78fbf87 | ||
|
|
500fbc9190 | ||
|
|
420f7e9b4e | ||
|
|
b273c6083f | ||
|
|
8877c705c5 | ||
|
|
1e30f794fe | ||
|
|
29ad82db7c | ||
|
|
a9046be00c | ||
|
|
a70efc95c6 | ||
|
|
b4b8774110 | ||
|
|
d87e03b0cf | ||
|
|
af07ccb87e | ||
|
|
dc43918a77 | ||
|
|
a04b131394 | ||
|
|
874f18fa6a | ||
|
|
fdc93e5130 | ||
|
|
6109e18fe6 | ||
|
|
27aca28523 | ||
|
|
2ac4aacee5 | ||
|
|
824a741634 | ||
|
|
819722e7da | ||
|
|
959d442690 | ||
|
|
b20868328f | ||
|
|
679c729985 | ||
|
|
bc2ca273a5 | ||
|
|
8a2612878d | ||
|
|
dd76d149ee | ||
|
|
2770d0fbec | ||
|
|
55b2c1e186 | ||
|
|
c4c291056b | ||
|
|
b80b81ada6 | ||
|
|
a0f3d44832 | ||
|
|
d9e4c00ccf | ||
|
|
c34a186eaf | ||
|
|
be27c930d3 | ||
|
|
66818872c2 | ||
|
|
cc1ac368b3 | ||
|
|
03e22098ca | ||
|
|
724b5eb1e0 | ||
|
|
45a1ce4b55 | ||
|
|
3e5cb2e30b | ||
|
|
d0a4c6aaf1 | ||
|
|
be4bcb7390 | ||
|
|
a63c3aa4c2 | ||
|
|
e8e43d97f2 | ||
|
|
9939ff4452 | ||
|
|
b69bbc899e | ||
|
|
f0cba24cc7 | ||
|
|
fd5fa0e798 | ||
|
|
1ba1d53884 | ||
|
|
00a118b901 | ||
|
|
ba2ea55884 | ||
|
|
70b4c3dd0f | ||
|
|
412e19252c | ||
|
|
73e1974583 | ||
|
|
25c57d645b | ||
|
|
a6c6021923 | ||
|
|
2886ac9d7c | ||
|
|
c121414767 | ||
|
|
35f02ef7a9 | ||
|
|
ffeb972a82 | ||
|
|
f20a74674f | ||
|
|
03396fbbe2 | ||
|
|
54a5c0805e | ||
|
|
486cb66b6c | ||
|
|
e5d7a344c7 | ||
|
|
60fe0ed25f | ||
|
|
04741c702a | ||
|
|
6e190fe903 | ||
|
|
5bb3e65eb9 | ||
|
|
c3b29ffcd1 | ||
|
|
a889c3e97b | ||
|
|
bd578ca7cd | ||
|
|
2a9a115c31 | ||
|
|
4d1848a5e1 | ||
|
|
4bd8f777e0 | ||
|
|
4654a9011b | ||
|
|
22a5dc148a | ||
|
|
88dc4c0607 | ||
|
|
0a7c47fa70 | ||
|
|
e6f311ebba | ||
|
|
88d09f0fef | ||
|
|
7467d87e36 | ||
|
|
b50e793a9c | ||
|
|
1147390576 | ||
|
|
539785f079 | ||
|
|
b99b516346 | ||
|
|
c0dc4f4236 | ||
|
|
5b8f664530 | ||
|
|
cc490692ad | ||
|
|
5ef415c87d | ||
|
|
ff3c67409d | ||
|
|
582a9bae31 | ||
|
|
95f51ed935 | ||
|
|
0dca22c219 | ||
|
|
eaf67e197b | ||
|
|
b8db918348 | ||
|
|
c865073605 | ||
|
|
192ccbec25 | ||
|
|
b8b3b29d01 | ||
|
|
00ea9a4393 | ||
|
|
267392d0b7 | ||
|
|
e5f041208f | ||
|
|
82010353c6 | ||
|
|
7daf4d1f58 | ||
|
|
ebb940ee0e | ||
|
|
1ce8c98837 | ||
|
|
257948cbe1 | ||
|
|
8971e0aa1a | ||
|
|
2209b050b0 | ||
|
|
b5a876a93e | ||
|
|
4c64ee24d5 | ||
|
|
1a3376374f | ||
|
|
ee88560090 | ||
|
|
aae7eba068 | ||
|
|
6715bc34d8 | ||
|
|
55d0ea5e22 | ||
|
|
11c692b146 | ||
|
|
ab47bd2e7b | ||
|
|
6dd9933d55 | ||
|
|
d08d398b47 | ||
|
|
aa3903d8f0 | ||
|
|
26f9030cef | ||
|
|
0500a10671 | ||
|
|
c3ae2d3eda | ||
|
|
e502c51886 | ||
|
|
063c1aa6c0 | ||
|
|
5e5db9193e | ||
|
|
26a38fc56c | ||
|
|
aad0558a9c | ||
|
|
a911f975da | ||
|
|
a99c8fbc88 | ||
|
|
79525227a9 | ||
|
|
66b8eb72bf | ||
|
|
1eff269b80 | ||
|
|
2dc01b528c | ||
|
|
30e200896d | ||
|
|
7d2e827e6f | ||
|
|
94e6b3086b | ||
|
|
0849e90574 | ||
|
|
d3eef33371 | ||
|
|
1cee0d10e5 | ||
|
|
3f044b407a | ||
|
|
b25ce3760b | ||
|
|
6e810761f8 | ||
|
|
6dea80e13e | ||
|
|
a715616a6a | ||
|
|
e87db432d5 | ||
|
|
0b4b89dcbd | ||
|
|
0e980051e3 | ||
|
|
4bd489a530 | ||
|
|
ed02c0dbc1 | ||
|
|
81e2432426 | ||
|
|
3a14099831 | ||
|
|
84712967b0 | ||
|
|
1fc70b7591 | ||
|
|
b9f8345964 | ||
|
|
fcd9e674d5 | ||
|
|
2cf2dfaea5 | ||
|
|
797be4e57c | ||
|
|
2478f1478f | ||
|
|
58cd508be4 | ||
|
|
9ca094f4f8 | ||
|
|
9ed9878193 | ||
|
|
c878a47f92 | ||
|
|
42d7a8860a | ||
|
|
718824f359 | ||
|
|
3b5b649530 | ||
|
|
8702af0c42 | ||
|
|
b038157f2f | ||
|
|
0033bdd570 | ||
|
|
99b40cac43 | ||
|
|
7fa91f98c6 | ||
|
|
a6e3f48044 | ||
|
|
d25921f6c8 | ||
|
|
e1986dc8eb | ||
|
|
ebba5b860f | ||
|
|
4beff8932e | ||
|
|
e26bd9adfb | ||
|
|
2eadc7b5ea | ||
|
|
a15aeade6a | ||
|
|
1990b7fad5 | ||
|
|
d81d5a37f8 | ||
|
|
1c1da0b080 | ||
|
|
4063bce178 | ||
|
|
b4d33c6472 | ||
|
|
44005c607d | ||
|
|
c17852a583 | ||
|
|
f94b22666b | ||
|
|
3c8c690a79 | ||
|
|
6f416ffe79 | ||
|
|
e4f8fe9ea2 | ||
|
|
003738149e | ||
|
|
6f39223787 | ||
|
|
06649c2207 | ||
|
|
18f51becd8 | ||
|
|
916792abea | ||
|
|
c8f8e019f6 | ||
|
|
7b6c6bb930 | ||
|
|
b826993a0d | ||
|
|
00385c8e15 | ||
|
|
5b982766fb | ||
|
|
be27dfd918 | ||
|
|
a82089aff3 | ||
|
|
215ba8bfbf | ||
|
|
7b1e9185f7 | ||
|
|
8c756e99c1 | ||
|
|
d2384e74cd | ||
|
|
b2ff2852bd | ||
|
|
3b4505af27 | ||
|
|
87eee126e5 | ||
|
|
6234f04457 | ||
|
|
c8e808082f | ||
|
|
b2f2642c82 | ||
|
|
0f77730011 | ||
|
|
b062d56928 | ||
|
|
a3f8552b80 | ||
|
|
8faf5a81ca | ||
|
|
7d7c0ae320 | ||
|
|
2bd8b71927 | ||
|
|
19a323ec77 | ||
|
|
29ee95165e | ||
|
|
c618a31e44 | ||
|
|
f972de507f | ||
|
|
51a0be31d5 | ||
|
|
d986c12564 | ||
|
|
1ae17b121e | ||
|
|
afb294f170 | ||
|
|
2b63fa343a | ||
|
|
35121f1141 | ||
|
|
9d0f26fede | ||
|
|
f0289b73b8 | ||
|
|
1009619ecd | ||
|
|
f30b1b1e08 | ||
|
|
d151f39a22 | ||
|
|
be6d4accff | ||
|
|
69e71202ee | ||
|
|
fece284aee | ||
|
|
4bcb44bcb2 | ||
|
|
0891dbf5a8 | ||
|
|
b5b5a86025 | ||
|
|
3fcbe831d7 | ||
|
|
d253151296 | ||
|
|
4e9beb86b0 | ||
|
|
7f85b49f3f | ||
|
|
1ea8c56631 | ||
|
|
5d76cbee26 | ||
|
|
682e4af619 | ||
|
|
c446476fbc | ||
|
|
e18a05001e | ||
|
|
0685f8b9c0 | ||
|
|
bdd1d4e8c1 | ||
|
|
bf6923f909 | ||
|
|
faa37c3322 | ||
|
|
1fe5b07f86 | ||
|
|
7a9ad3933b | ||
|
|
df2e447362 | ||
|
|
b2033f3dcd | ||
|
|
186bce852d | ||
|
|
e1494441b0 | ||
|
|
1b67c130c7 | ||
|
|
628b980655 | ||
|
|
0e04a17380 | ||
|
|
cee1a14ebd | ||
|
|
3862205053 | ||
|
|
9696f044a0 | ||
|
|
b61a0f2c94 | ||
|
|
afb2e3a8a2 | ||
|
|
65cc0a0ed5 | ||
|
|
dba6f2ec6a | ||
|
|
b39ea03ba6 | ||
|
|
528483b905 | ||
|
|
fb7434f1b4 | ||
|
|
a239e6405b | ||
|
|
d5c00b81d7 | ||
|
|
590f6c8c8b | ||
|
|
edf2495a9f | ||
|
|
ed0a034414 | ||
|
|
3e33e0d0e1 | ||
|
|
c9e1c2711e | ||
|
|
1f0420dc1f | ||
|
|
f0320c6ff9 | ||
|
|
904eb1364c | ||
|
|
e5ef184515 | ||
|
|
2a4111643c | ||
|
|
53ffe057b2 | ||
|
|
b2632e1727 | ||
|
|
84554d6dea | ||
|
|
6fd8f8380b | ||
|
|
5330929efd | ||
|
|
c67bc9938c | ||
|
|
f41f0e94be | ||
|
|
74b1c6f4ed | ||
|
|
54d7e20baa | ||
|
|
221d88c52c | ||
|
|
eb3c8050fe | ||
|
|
fdab854799 | ||
|
|
ba32761a0c | ||
|
|
6d002838a5 | ||
|
|
d8f53db387 | ||
|
|
f15db1637e | ||
|
|
5f98e2fdc5 | ||
|
|
603dc9f9c6 | ||
|
|
0692079384 | ||
|
|
e046cdce45 | ||
|
|
595419df77 | ||
|
|
1a641afaa8 | ||
|
|
29c73ae90f | ||
|
|
e055f99f63 | ||
|
|
888d4797d0 | ||
|
|
b95d63e974 | ||
|
|
195abb59ff | ||
|
|
306ceba474 | ||
|
|
2be1476130 | ||
|
|
ca80356c27 | ||
|
|
eed34d9b7d | ||
|
|
1144ab32b7 | ||
|
|
0061e609d4 | ||
|
|
680b64c98f | ||
|
|
325048efcb | ||
|
|
aedb293d50 | ||
|
|
6cdc1943fb | ||
|
|
9ff40875c2 | ||
|
|
3a851d59fe | ||
|
|
64de7abada | ||
|
|
6d9feebedd | ||
|
|
441658eb7a | ||
|
|
01a441a99c | ||
|
|
1d10141c1d | ||
|
|
952313acee | ||
|
|
d537b742dc | ||
|
|
f3b706bcc2 | ||
|
|
2a57af944f | ||
|
|
3f42b93300 | ||
|
|
59a58eb886 | ||
|
|
d91d5bcd9b | ||
|
|
c6a08c79e1 | ||
|
|
d550b65bc7 | ||
|
|
42418ef393 | ||
|
|
12f4156ae0 | ||
|
|
ff58873fcb | ||
|
|
925c72f6aa | ||
|
|
345d853e2d | ||
|
|
10b064dc40 | ||
|
|
75a7187a87 | ||
|
|
6182e00c31 | ||
|
|
e3ba93a6b8 | ||
|
|
109d8cacbc | ||
|
|
3bcc18f035 | ||
|
|
92baa1affb | ||
|
|
32a9457de3 | ||
|
|
32c147fa16 | ||
|
|
d61d75f8bb | ||
|
|
eae6cd5396 | ||
|
|
6f16f230d1 | ||
|
|
f458a057e8 | ||
|
|
d013300fa3 | ||
|
|
d6b8394044 | ||
|
|
c1fa630602 | ||
|
|
28a03dadaf | ||
|
|
29cfc2ea6b | ||
|
|
f4c776f10e | ||
|
|
04be6bc38b | ||
|
|
4f0bbeeab0 | ||
|
|
5a356a8d75 | ||
|
|
7f35f46eaa | ||
|
|
ea7be67d83 | ||
|
|
8f0d65766d | ||
|
|
a140f318a2 | ||
|
|
d0a2d1644f | ||
|
|
e69b153e1c | ||
|
|
d19d383738 | ||
|
|
253f288323 | ||
|
|
d6ce0536ca | ||
|
|
0168c01915 | ||
|
|
7329fec67a | ||
|
|
5fea22294e | ||
|
|
669124a6b3 | ||
|
|
c44d141c07 | ||
|
|
0b30e00b8c | ||
|
|
1e7bd1bf48 | ||
|
|
c2c4d6aa02 | ||
|
|
7e0c64191c | ||
|
|
323e4e971a | ||
|
|
99b4f8f8fc | ||
|
|
7c1cf76c5c | ||
|
|
305e1b7af6 | ||
|
|
6880a70227 | ||
|
|
0482969755 | ||
|
|
4c9b429dae | ||
|
|
60b8400e29 | ||
|
|
7260be98d6 | ||
|
|
be94094de7 | ||
|
|
645077f39b | ||
|
|
b42940441d | ||
|
|
fc7071c04e | ||
|
|
de887a4e3f | ||
|
|
7f629309c7 | ||
|
|
fa45e209d5 | ||
|
|
40aec6cd0d | ||
|
|
969ae7aeca | ||
|
|
a64bbd34f3 | ||
|
|
79acf83d6f | ||
|
|
786e8d0be1 | ||
|
|
2757850f28 | ||
|
|
7c6f39f3b5 | ||
|
|
70723d0437 | ||
|
|
bd9b0f798b | ||
|
|
0c406b2ad8 | ||
|
|
822891541c | ||
|
|
bf064fd4e3 | ||
|
|
6c9e055d42 | ||
|
|
7455d1bfb2 | ||
|
|
7b923b6625 | ||
|
|
bcd18e9395 | ||
|
|
082d577834 | ||
|
|
68bf4f9df3 | ||
|
|
9348698aac | ||
|
|
979025f7d9 | ||
|
|
9aa09050da | ||
|
|
f331de4185 | ||
|
|
1ce067c854 | ||
|
|
e6d537f0f8 | ||
|
|
6a69a8d21a | ||
|
|
415cc09a06 | ||
|
|
fb482e133e | ||
|
|
c8134ce2ec | ||
|
|
a59c062dda | ||
|
|
fe604d3439 | ||
|
|
53ad8466ad | ||
|
|
9e3c844231 | ||
|
|
85a10e7c72 | ||
|
|
902dc5bdd1 | ||
|
|
666cbc9e47 | ||
|
|
745fae3f76 | ||
|
|
bb2c4d4c3f | ||
|
|
f8c8d6725c | ||
|
|
54747dc800 | ||
|
|
63b6c0cab4 | ||
|
|
92c85a0791 | ||
|
|
83f47bfe6e | ||
|
|
2f034e98c0 | ||
|
|
cb416c3583 | ||
|
|
584353a240 | ||
|
|
729456c2a0 | ||
|
|
a02a2e5332 | ||
|
|
78f0204d86 | ||
|
|
b76ef5ca80 | ||
|
|
788fc01cfb | ||
|
|
4dc9360b34 | ||
|
|
6c0e1b7f01 | ||
|
|
27b91642f2 | ||
|
|
0c7be4b2c7 | ||
|
|
e41ba71828 | ||
|
|
2b2b6b98f1 | ||
|
|
8f6b20abd1 | ||
|
|
21a69c4a61 | ||
|
|
e7b671283a | ||
|
|
d46a7a50b5 | ||
|
|
dfae4e0848 | ||
|
|
a0f7e924e5 | ||
|
|
fd2f3c7dbe | ||
|
|
da442dc02b | ||
|
|
eaea900100 | ||
|
|
449662f858 | ||
|
|
def2219f41 | ||
|
|
4670fc477d | ||
|
|
cea4572843 | ||
|
|
b769d4644a | ||
|
|
3d9b117a74 | ||
|
|
3b1a5f227e | ||
|
|
a66a2d936e | ||
|
|
8b5c334d47 | ||
|
|
5597cf6753 | ||
|
|
9d2d83d8d1 | ||
|
|
bc695479fa | ||
|
|
c0f7f2accd | ||
|
|
08309d6162 | ||
|
|
b217036ad0 | ||
|
|
a276652cbc | ||
|
|
39f643330a | ||
|
|
2967cc917c | ||
|
|
78d22b3165 | ||
|
|
989036bdf2 | ||
|
|
926e9ff92c | ||
|
|
4696567514 | ||
|
|
e153a30186 | ||
|
|
e8bbd3dace | ||
|
|
d2d1344858 | ||
|
|
6be828079d | ||
|
|
7a07a5c646 | ||
|
|
ee649ebeb9 | ||
|
|
67fe2a5a67 | ||
|
|
6ac193c139 | ||
|
|
b7492a6dfa | ||
|
|
224e86f673 | ||
|
|
f014e155d6 | ||
|
|
7aa37cd6cb | ||
|
|
b1cc09fcb6 | ||
|
|
4717293666 | ||
|
|
a80cd0720f | ||
|
|
e238676efc | ||
|
|
e1df231137 | ||
|
|
f40d10b35c | ||
|
|
ea20e5445e | ||
|
|
4a6f2dbdc3 | ||
|
|
11ff01bc61 | ||
|
|
2a292e6a82 | ||
|
|
dc11242b77 | ||
|
|
4ec8f5fc28 | ||
|
|
690f3ed87c | ||
|
|
04e8f6f2e3 | ||
|
|
e96ab86198 | ||
|
|
eae010a23b | ||
|
|
d9d829e37d | ||
|
|
53680fc774 | ||
|
|
46b8378297 | ||
|
|
a26b4f779e | ||
|
|
c2e8c87f3b | ||
|
|
c99e82bd8e | ||
|
|
1398e54896 | ||
|
|
2d93e5bb1c | ||
|
|
6b901d42f7 | ||
|
|
0cc8a968ed | ||
|
|
3f2914c6fa | ||
|
|
9e01786278 | ||
|
|
78ec625cd5 | ||
|
|
02855e3ac4 | ||
|
|
2fc65f135d | ||
|
|
88085ca5a6 | ||
|
|
fe26a82d66 | ||
|
|
e98678b1f7 | ||
|
|
ef2b4d785a | ||
|
|
b721f3e3ce | ||
|
|
13cc0fc08c | ||
|
|
65fe95d464 | ||
|
|
80a887b0d0 | ||
|
|
bed1915da4 | ||
|
|
65cde0af99 | ||
|
|
a0a2b8b265 | ||
|
|
ed01254c84 | ||
|
|
e03864ac03 | ||
|
|
83796c5aa4 | ||
|
|
0e329def1a | ||
|
|
27859cf183 | ||
|
|
5560e7ee3d | ||
|
|
72bab915b1 | ||
|
|
f5fefcdd55 | ||
|
|
160634ea38 | ||
|
|
50fb5a96d8 | ||
|
|
79c7ea16d8 | ||
|
|
a166db0ea1 | ||
|
|
2b905615d1 | ||
|
|
a18c1b5e0c | ||
|
|
c066043ceb | ||
|
|
41d64f1b7c | ||
|
|
36023fdc35 | ||
|
|
8437cf279d | ||
|
|
d2e19cf3c7 | ||
|
|
9a90be6e79 | ||
|
|
903a4c78e2 | ||
|
|
19049bfea8 | ||
|
|
0c4e92ebec | ||
|
|
aefe937adc | ||
|
|
63eda04815 | ||
|
|
18c7783942 | ||
|
|
6c17e1e9c5 | ||
|
|
955355460e | ||
|
|
51a110b9a3 | ||
|
|
43da6c3656 | ||
|
|
411c916a26 | ||
|
|
c0cc14ec99 | ||
|
|
4f8dd8844c | ||
|
|
36f8f43aad | ||
|
|
534d7f570e | ||
|
|
599e514afc | ||
|
|
42054827a2 | ||
|
|
bfe517ef75 | ||
|
|
c585f3455d | ||
|
|
fe1d3dd82b | ||
|
|
92d503335b | ||
|
|
782f93169d | ||
|
|
0c12177091 | ||
|
|
cc90ff97e3 | ||
|
|
525230cf2d | ||
|
|
2df9ef3fca | ||
|
|
9de1b6d9d2 | ||
|
|
94b096259a | ||
|
|
4e7011f12b | ||
|
|
ff123ddcb4 | ||
|
|
9d98fefdef | ||
|
|
573bddb1ae | ||
|
|
8386848e0e | ||
|
|
5ba0220a74 | ||
|
|
d24cadcf00 | ||
|
|
fbe53df424 | ||
|
|
eb0347073e | ||
|
|
45907bdcca | ||
|
|
b5b8aaf731 | ||
|
|
d4eb749729 | ||
|
|
3e09882b0c | ||
|
|
a443b57ead | ||
|
|
3a430b3586 | ||
|
|
f09f8ba4eb | ||
|
|
c6a1560a0f | ||
|
|
805f596f1f | ||
|
|
bcee601d4c | ||
|
|
ea009e802b | ||
|
|
e421eaaf1a | ||
|
|
997cd88db8 | ||
|
|
ccc8fbde05 | ||
|
|
0fdf7721a7 | ||
|
|
96f1863711 | ||
|
|
ea9c7543d9 | ||
|
|
b69a02c878 | ||
|
|
4d9c792a25 | ||
|
|
7a6992845a | ||
|
|
f2deb8f402 | ||
|
|
c067b162b9 | ||
|
|
f1415d7583 | ||
|
|
14f23e79f1 | ||
|
|
a5a55e207c |
244
.editorconfig
Normal file
244
.editorconfig
Normal file
@@ -0,0 +1,244 @@
|
||||
# Remove the line below if you want to inherit .editorconfig settings from higher directories
|
||||
root = true
|
||||
|
||||
# C# files
|
||||
[*.cs]
|
||||
|
||||
#### Core EditorConfig Options ####
|
||||
|
||||
# Indentation and spacing
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
tab_width = 4
|
||||
|
||||
# New line preferences
|
||||
end_of_line = crlf
|
||||
insert_final_newline = false
|
||||
|
||||
#### .NET Code Actions ####
|
||||
|
||||
# Type members
|
||||
dotnet_hide_advanced_members = false
|
||||
dotnet_member_insertion_location = with_other_members_of_the_same_kind
|
||||
dotnet_property_generation_behavior = prefer_throwing_properties
|
||||
|
||||
# Symbol search
|
||||
dotnet_search_reference_assemblies = true
|
||||
|
||||
#### .NET Coding Conventions ####
|
||||
|
||||
# Organize usings
|
||||
dotnet_separate_import_directive_groups = false
|
||||
dotnet_sort_system_directives_first = false
|
||||
file_header_template = unset
|
||||
|
||||
# this. and Me. preferences
|
||||
dotnet_style_qualification_for_event = false
|
||||
dotnet_style_qualification_for_field = false
|
||||
dotnet_style_qualification_for_method = false
|
||||
dotnet_style_qualification_for_property = false
|
||||
|
||||
# Language keywords vs BCL types preferences
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true
|
||||
dotnet_style_predefined_type_for_member_access = true
|
||||
|
||||
# Parentheses preferences
|
||||
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
|
||||
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
|
||||
dotnet_style_parentheses_in_other_operators = never_if_unnecessary
|
||||
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
|
||||
|
||||
# Modifier preferences
|
||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members
|
||||
|
||||
# Expression-level preferences
|
||||
dotnet_prefer_system_hash_code = true
|
||||
dotnet_style_coalesce_expression = true
|
||||
dotnet_style_collection_initializer = true
|
||||
dotnet_style_explicit_tuple_names = true
|
||||
dotnet_style_namespace_match_folder = true
|
||||
dotnet_style_null_propagation = true
|
||||
dotnet_style_object_initializer = true
|
||||
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
||||
dotnet_style_prefer_auto_properties = true
|
||||
dotnet_style_prefer_collection_expression = when_types_loosely_match
|
||||
dotnet_style_prefer_compound_assignment = true
|
||||
dotnet_style_prefer_conditional_expression_over_assignment = true
|
||||
dotnet_style_prefer_conditional_expression_over_return = true
|
||||
dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed
|
||||
dotnet_style_prefer_inferred_anonymous_type_member_names = true
|
||||
dotnet_style_prefer_inferred_tuple_names = true
|
||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true
|
||||
dotnet_style_prefer_simplified_boolean_expressions = true
|
||||
dotnet_style_prefer_simplified_interpolation = true
|
||||
|
||||
# Field preferences
|
||||
dotnet_style_readonly_field = true
|
||||
|
||||
# Parameter preferences
|
||||
dotnet_code_quality_unused_parameters = all
|
||||
|
||||
# Suppression preferences
|
||||
dotnet_remove_unnecessary_suppression_exclusions = none
|
||||
|
||||
# New line preferences
|
||||
dotnet_style_allow_multiple_blank_lines_experimental = true
|
||||
dotnet_style_allow_statement_immediately_after_block_experimental = true
|
||||
|
||||
#### C# Coding Conventions ####
|
||||
|
||||
# var preferences
|
||||
csharp_style_var_elsewhere = false
|
||||
csharp_style_var_for_built_in_types = false
|
||||
csharp_style_var_when_type_is_apparent = false
|
||||
|
||||
# Expression-bodied members
|
||||
csharp_style_expression_bodied_accessors = true
|
||||
csharp_style_expression_bodied_constructors = false
|
||||
csharp_style_expression_bodied_indexers = true
|
||||
csharp_style_expression_bodied_lambdas = true
|
||||
csharp_style_expression_bodied_local_functions = false
|
||||
csharp_style_expression_bodied_methods = false
|
||||
csharp_style_expression_bodied_operators = false
|
||||
csharp_style_expression_bodied_properties = true
|
||||
|
||||
# Pattern matching preferences
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true
|
||||
csharp_style_pattern_matching_over_is_with_cast_check = true
|
||||
csharp_style_prefer_extended_property_pattern = true
|
||||
csharp_style_prefer_not_pattern = true
|
||||
csharp_style_prefer_pattern_matching = true
|
||||
csharp_style_prefer_switch_expression = true
|
||||
|
||||
# Null-checking preferences
|
||||
csharp_style_conditional_delegate_call = true
|
||||
|
||||
# Modifier preferences
|
||||
csharp_prefer_static_anonymous_function = true
|
||||
csharp_prefer_static_local_function = true
|
||||
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async
|
||||
csharp_style_prefer_readonly_struct = true
|
||||
csharp_style_prefer_readonly_struct_member = true
|
||||
|
||||
# Code-block preferences
|
||||
csharp_prefer_braces = true
|
||||
csharp_prefer_simple_using_statement = true
|
||||
csharp_prefer_system_threading_lock = true
|
||||
csharp_style_namespace_declarations = block_scoped
|
||||
csharp_style_prefer_method_group_conversion = true
|
||||
csharp_style_prefer_primary_constructors = true
|
||||
csharp_style_prefer_top_level_statements = true
|
||||
|
||||
# Expression-level preferences
|
||||
csharp_prefer_simple_default_expression = true
|
||||
csharp_style_deconstructed_variable_declaration = true
|
||||
csharp_style_implicit_object_creation_when_type_is_apparent = true
|
||||
csharp_style_inlined_variable_declaration = true
|
||||
csharp_style_prefer_index_operator = true
|
||||
csharp_style_prefer_local_over_anonymous_function = true
|
||||
csharp_style_prefer_null_check_over_type_check = true
|
||||
csharp_style_prefer_range_operator = true
|
||||
csharp_style_prefer_tuple_swap = true
|
||||
csharp_style_prefer_utf8_string_literals = true
|
||||
csharp_style_throw_expression = true
|
||||
csharp_style_unused_value_assignment_preference = discard_variable
|
||||
csharp_style_unused_value_expression_statement_preference = discard_variable
|
||||
|
||||
# 'using' directive preferences
|
||||
csharp_using_directive_placement = outside_namespace
|
||||
|
||||
# New line preferences
|
||||
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true
|
||||
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true
|
||||
csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true
|
||||
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true
|
||||
csharp_style_allow_embedded_statements_on_same_line_experimental = true
|
||||
|
||||
#### C# Formatting Rules ####
|
||||
|
||||
# New line preferences
|
||||
csharp_new_line_before_catch = true
|
||||
csharp_new_line_before_else = true
|
||||
csharp_new_line_before_finally = true
|
||||
csharp_new_line_before_members_in_anonymous_types = true
|
||||
csharp_new_line_before_members_in_object_initializers = true
|
||||
csharp_new_line_before_open_brace = all
|
||||
csharp_new_line_between_query_expression_clauses = true
|
||||
|
||||
# Indentation preferences
|
||||
csharp_indent_block_contents = true
|
||||
csharp_indent_braces = false
|
||||
csharp_indent_case_contents = true
|
||||
csharp_indent_case_contents_when_block = true
|
||||
csharp_indent_labels = no_change
|
||||
csharp_indent_switch_labels = true
|
||||
|
||||
# Space preferences
|
||||
csharp_space_after_cast = false
|
||||
csharp_space_after_colon_in_inheritance_clause = true
|
||||
csharp_space_after_comma = true
|
||||
csharp_space_after_dot = false
|
||||
csharp_space_after_keywords_in_control_flow_statements = true
|
||||
csharp_space_after_semicolon_in_for_statement = true
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
csharp_space_around_declaration_statements = false
|
||||
csharp_space_before_colon_in_inheritance_clause = true
|
||||
csharp_space_before_comma = false
|
||||
csharp_space_before_dot = false
|
||||
csharp_space_before_open_square_brackets = false
|
||||
csharp_space_before_semicolon_in_for_statement = false
|
||||
csharp_space_between_empty_square_brackets = false
|
||||
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||
csharp_space_between_method_call_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_name_and_open_parenthesis = false
|
||||
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||
csharp_space_between_parentheses = false
|
||||
csharp_space_between_square_brackets = false
|
||||
|
||||
# Wrapping preferences
|
||||
csharp_preserve_single_line_blocks = true
|
||||
csharp_preserve_single_line_statements = true
|
||||
|
||||
#### Naming styles ####
|
||||
|
||||
# Naming rules
|
||||
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
|
||||
|
||||
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
|
||||
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
|
||||
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
|
||||
|
||||
# Symbol specifications
|
||||
|
||||
dotnet_naming_symbols.interface.applicable_kinds = interface
|
||||
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.interface.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
|
||||
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.types.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
|
||||
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.non_field_members.required_modifiers =
|
||||
|
||||
# Naming styles
|
||||
|
||||
dotnet_naming_style.pascal_case.required_prefix =
|
||||
dotnet_naming_style.pascal_case.required_suffix =
|
||||
dotnet_naming_style.pascal_case.word_separator =
|
||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.begins_with_i.required_prefix = I
|
||||
dotnet_naming_style.begins_with_i.required_suffix =
|
||||
dotnet_naming_style.begins_with_i.word_separator =
|
||||
dotnet_naming_style.begins_with_i.capitalization = pascal_case
|
||||
28
.github/workflows/ci.yml
vendored
Normal file
28
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
# This workflow will build a .NET project
|
||||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
|
||||
|
||||
name: .NET
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
pull_request:
|
||||
branches: [ "master" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
- name: Build
|
||||
run: dotnet build Radzen.Blazor/Radzen.Blazor.csproj
|
||||
- name: Test
|
||||
run: dotnet test Radzen.Blazor.Tests/Radzen.Blazor.Tests.csproj
|
||||
@@ -19,7 +19,7 @@ You can ask your question here. Please use the [Radzen.Blazor Components](https:
|
||||
|
||||
### Dedicated technical support
|
||||
|
||||
Radzen staff provides technical support with guaranteed response time to Radzen Professional and Enterprise subscribers. The pricing options are available [here](https://www.radzen.com/pricing/).
|
||||
Radzen staff provides technical support with guaranteed response time to Radzen Professional and Enterprise subscribers. The pricing options are available [here](https://www.radzen.com/blazor-studio/pricing/).
|
||||
|
||||
## How Can I Contribute?
|
||||
|
||||
|
||||
15
Dockerfile
15
Dockerfile
@@ -13,18 +13,19 @@ RUN apt-get update && apt-get install unzip wget git -y && wget -q -P /tmp https
|
||||
COPY Radzen.Blazor /app/Radzen.Blazor
|
||||
COPY Radzen.DocFX /app/DocFX
|
||||
COPY RadzenBlazorDemos /app/RadzenBlazorDemos
|
||||
COPY RadzenBlazorDemos.Host /app/RadzenBlazorDemos.Host
|
||||
WORKDIR /app
|
||||
RUN docfx DocFX/docfx.json
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:7.0
|
||||
FROM mcr.microsoft.com/dotnet/sdk:9.0
|
||||
|
||||
COPY --from=0 /app/RadzenBlazorDemos /app
|
||||
WORKDIR /app
|
||||
COPY --from=0 /app/RadzenBlazorDemos.Host /app/RadzenBlazorDemos.Host
|
||||
COPY --from=0 /app/RadzenBlazorDemos /app/RadzenBlazorDemos
|
||||
|
||||
WORKDIR /app/RadzenBlazorDemos.Host
|
||||
RUN dotnet publish -c Release -o out
|
||||
COPY RadzenBlazorDemos/northwind.db /app/out
|
||||
COPY RadzenBlazorDemos/northwind.sql /app/out
|
||||
|
||||
ENV ASPNETCORE_URLS http://*:5000
|
||||
WORKDIR /app/out
|
||||
WORKDIR /app/RadzenBlazorDemos.Host/out
|
||||
|
||||
ENTRYPOINT ["dotnet", "RadzenBlazorDemos.dll"]
|
||||
ENTRYPOINT ["dotnet", "RadzenBlazorDemos.Host.dll"]
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2022 Radzen Ltd
|
||||
Copyright (c) 2018-2025 Radzen Ltd
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
110
README.md
110
README.md
@@ -1,47 +1,21 @@
|
||||

|
||||

|
||||
|
||||
<h1 align="center">
|
||||
Radzen Blazor Components
|
||||
</h1>
|
||||
Radzen Blazor Components
|
||||
========================
|
||||
|
||||
<p align="center">
|
||||
A set of <strong>70+ free and open source</strong> native Blazor UI controls.
|
||||
</p>
|
||||
A set of **90+ free and open source** native Blazor UI controls.
|
||||
|
||||
<div align="center">
|
||||
See Online Demos or Read the Docs
|
||||
|
||||
[See Online Demos](https://blazor.radzen.com) or [Read the Docs](https://blazor.radzen.com/docs/)
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/radzenhq/radzen-blazor/blob/master/LICENSE">
|
||||
<img alt="License - MIT" src="https://img.shields.io/github/license/radzenhq/radzen-blazor?logo=github&style=for-the-badge" />
|
||||
</a>
|
||||
<a href="https://www.nuget.org/packages/Radzen.Blazor">
|
||||
<img alt="Nuget Downloads" src="https://img.shields.io/nuget/dt/Radzen.Blazor?color=%232694F9&label=nuget%20downloads&logo=nuget&style=for-the-badge" />
|
||||
</a>
|
||||
<img alt="Last Commit" src="https://img.shields.io/github/last-commit/radzenhq/radzen-blazor?logo=github&style=for-the-badge" />
|
||||
<a href="https://github.com/radzenhq/radzen-blazor/graphs/contributors">
|
||||
<img alt="Github Contributors" src="https://img.shields.io/github/contributors/radzenhq/radzen-blazor?logo=github&style=for-the-badge" />
|
||||
</a>
|
||||
<a href="https://blazor.radzen.com">
|
||||
<img alt="Radzen Blazor Components - Online Demos" src="https://img.shields.io/badge/demos-online-brightgreen?color=%232694F9&logo=blazor&style=for-the-badge" />
|
||||
</a>
|
||||
<a href="https://blazor.radzen.com/docs">
|
||||
<img alt="Radzen Blazor Components - Documentation" src="https://img.shields.io/badge/docs-online-brightgreen?color=%232694F9&logo=blazor&style=for-the-badge" />
|
||||
</a>
|
||||
</p>
|
||||
[](https://github.com/radzenhq/radzen-blazor/blob/master/LICENSE)[ ](https://www.nuget.org/packages/Radzen.Blazor) [ ](https://github.com/radzenhq/radzen-blazor/graphs/contributors)[ ](https://blazor.radzen.com)[](https://blazor.radzen.com/docs)
|
||||
|
||||
## Why choose Radzen Blazor Components?
|
||||
|
||||
### :sparkles: Free
|
||||
|
||||
Radzen Blazor Components are open source and free for commercial use. You can install them from [nuget](https://www.nuget.org/packages/Radzen.Blazor) or build your own copy from source.
|
||||
Radzen Blazor Components are open source and free for commercial use. You can install them from [NuGet](https://www.nuget.org/packages/Radzen.Blazor) or build your own copy from source.
|
||||
|
||||
Paid support is available as part of the [Radzen Professional subscription](https://www.radzen.com/pricing/).
|
||||
Paid support is available as part of the [Radzen Professional subscription](https://www.radzen.com/blazor-studio/pricing/).
|
||||
|
||||
### :computer: Native
|
||||
|
||||
@@ -64,10 +38,10 @@ Everybody is welcome to visit the [Radzen Community forum](https://forum.radzen.
|
||||
|
||||
The Radzen team monitors the forum threads, but does not guarantee a response to every question. For guaranteed responses you may consider the dedicated support option.
|
||||
|
||||
Dedicated support for the Radzen Blazor Components is available as part of the [Radzen Professional subscription](https://www.radzen.com/pricing/).
|
||||
Dedicated support for the Radzen Blazor Components is available as part of the [Radzen Professional subscription](https://www.radzen.com/blazor-studio/pricing/).
|
||||
|
||||
Our flagship product [Radzen Studio](https://www.radzen.com/features/) provides tons of productivity features for Blazor developers:
|
||||
- The first in the industry WYSIWYG Blazor design time canvas
|
||||
Our flagship product [Radzen Blazor Studio](https://www.radzen.com/blazor-studio/) provides tons of productivity features for Blazor developers:
|
||||
- An industry-leading WYSIWYG Blazor design time canvas
|
||||
- Scaffolding a complete CRUD applications from a database
|
||||
- Built-in security - authentication and authorization
|
||||
- Visual Studio Code and Professional support
|
||||
@@ -76,64 +50,8 @@ Our flagship product [Radzen Studio](https://www.radzen.com/features/) provides
|
||||
|
||||
## Get started with Radzen Blazor Components
|
||||
|
||||
### 1. Install
|
||||
Check the [getting started](https://blazor.radzen.com/getting-started) instructions to start making awesome Blazor applications.
|
||||
|
||||
Radzen Blazor Components are distributed as a [Radzen.Blazor nuget package](https://www.nuget.org/packages/Radzen.Blazor). You can add them to your project in one of the following ways
|
||||
- Install the package from command line by running `dotnet add package Radzen.Blazor`
|
||||
- Add the project from the Visual Nuget Package Manager
|
||||
- Manually edit the .csproj file and add a project reference
|
||||
## Run demos locally
|
||||
|
||||
### 2. Import the namespace
|
||||
|
||||
Open the `_Imports.razor` file of your Blazor application and add this line `@using Radzen.Blazor`.
|
||||
|
||||
### 3. Include a theme
|
||||
|
||||
Radzen Blazor components come with five free themes: Material, Standard, Default, Dark, Software and Humanistic.
|
||||
|
||||
To use a theme
|
||||
1. Pick a theme. The [online demos](https://blazor.radzen.com/colors) allow you to preview the available options via the theme dropdown located in the header. The Material theme is currently selected by default.
|
||||
1. Include the theme CSS file in your Blazor application. Open `Pages\_Layout.cshtml` (Blazor Server .NET 6+), `Pages\_Host.cshtml` (Blazor Server before .NET 6) or `wwwroot/index.html` (Blazor WebAssembly) and include the CSS file of a theme CSS file by adding this snippet
|
||||
```html
|
||||
<link rel="stylesheet" href="_content/Radzen.Blazor/css/material-base.css">
|
||||
```
|
||||
|
||||
To include a different theme (i.e. Standard) just change the name of the CSS file:
|
||||
```
|
||||
<link rel="stylesheet" href="_content/Radzen.Blazor/css/standard-base.css">
|
||||
```
|
||||
|
||||
### 4. Include Radzen.Blazor.js
|
||||
|
||||
Open `Pages\_Layout.cshtml` (Blazor Server .NET 6+), `Pages\_Host.cshtml` (Blazor Server before .NET 6) or `wwwroot/index.html` (Blazor WebAssembly) and include this snippet:
|
||||
|
||||
```html
|
||||
<script src="_content/Radzen.Blazor/Radzen.Blazor.js"></script>
|
||||
```
|
||||
|
||||
### 5. Use a component
|
||||
Use any Radzen Blazor component by typing its tag name in a Blazor page e.g.
|
||||
```html
|
||||
<RadzenButton Text="Hi"></RadzenButton>
|
||||
```
|
||||
|
||||
#### Data-binding a property
|
||||
```razor
|
||||
<RadzenButton Text=@text />
|
||||
<RadzenTextBox @bind-Value=@text />
|
||||
@code {
|
||||
string text = "Hi";
|
||||
}
|
||||
```
|
||||
|
||||
#### Handing events
|
||||
|
||||
```razor
|
||||
<RadzenButton Click="@ButtonClicked" Text="Hi"></RadzenButton>
|
||||
@code {
|
||||
void ButtonClicked()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
Use Radzen.Server.sln to open and run demos as Blazor server application or Radzen.WebAssembly.sln to open and run demos as Blazor WebAssembly application. Radzen.sln has reference to all projects including tests.
|
||||
|
||||
74
Radzen.Blazor.Tests/AutoCompleteTests.cs
Normal file
74
Radzen.Blazor.Tests/AutoCompleteTests.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Tests
|
||||
{
|
||||
public class AutoCompleteTests
|
||||
{
|
||||
[Fact]
|
||||
public void AutoComplete_Enum_Converts_To_Attr_Value()
|
||||
{
|
||||
// Options
|
||||
Assert.Equal("off", AutoCompleteType.Off.GetAutoCompleteValue());
|
||||
Assert.Equal("on", AutoCompleteType.On.GetAutoCompleteValue());
|
||||
Assert.Equal("name", AutoCompleteType.Name.GetAutoCompleteValue());
|
||||
Assert.Equal("honorific-prefix", AutoCompleteType.HonorificPrefix.GetAutoCompleteValue());
|
||||
Assert.Equal("given-name", AutoCompleteType.GivenName.GetAutoCompleteValue());
|
||||
Assert.Equal("additional-name", AutoCompleteType.AdditionalName.GetAutoCompleteValue());
|
||||
Assert.Equal("family-name", AutoCompleteType.FamilyName.GetAutoCompleteValue());
|
||||
Assert.Equal("honorific-suffix", AutoCompleteType.HonorificSuffix.GetAutoCompleteValue());
|
||||
Assert.Equal("nickname", AutoCompleteType.Nickname.GetAutoCompleteValue());
|
||||
Assert.Equal("email", AutoCompleteType.Email.GetAutoCompleteValue());
|
||||
Assert.Equal("username", AutoCompleteType.Username.GetAutoCompleteValue());
|
||||
Assert.Equal("new-password", AutoCompleteType.NewPassword.GetAutoCompleteValue());
|
||||
Assert.Equal("current-password", AutoCompleteType.CurrentPassword.GetAutoCompleteValue());
|
||||
Assert.Equal("one-time-code", AutoCompleteType.OneTimeCode.GetAutoCompleteValue());
|
||||
Assert.Equal("organization-title", AutoCompleteType.OrganizationTitle.GetAutoCompleteValue());
|
||||
Assert.Equal("organization", AutoCompleteType.Organization.GetAutoCompleteValue());
|
||||
Assert.Equal("street-address", AutoCompleteType.StreetAddress.GetAutoCompleteValue());
|
||||
Assert.Equal("address-line1", AutoCompleteType.AddressLine1.GetAutoCompleteValue());
|
||||
Assert.Equal("address-line2", AutoCompleteType.AddressLine2.GetAutoCompleteValue());
|
||||
Assert.Equal("address-line3", AutoCompleteType.AddressLine3.GetAutoCompleteValue());
|
||||
Assert.Equal("address-level1", AutoCompleteType.AddressLevel1.GetAutoCompleteValue());
|
||||
Assert.Equal("address-level2", AutoCompleteType.AddressLevel2.GetAutoCompleteValue());
|
||||
Assert.Equal("address-level3", AutoCompleteType.AddressLevel3.GetAutoCompleteValue());
|
||||
Assert.Equal("address-level4", AutoCompleteType.AddressLevel4.GetAutoCompleteValue());
|
||||
Assert.Equal("country", AutoCompleteType.Country.GetAutoCompleteValue());
|
||||
Assert.Equal("country-name", AutoCompleteType.CountryName.GetAutoCompleteValue());
|
||||
Assert.Equal("postal-code", AutoCompleteType.PostalCode.GetAutoCompleteValue());
|
||||
Assert.Equal("cc-name", AutoCompleteType.CcName.GetAutoCompleteValue());
|
||||
Assert.Equal("cc-given-name", AutoCompleteType.CcGivenName.GetAutoCompleteValue());
|
||||
Assert.Equal("cc-additional-name", AutoCompleteType.CcAdditionalName.GetAutoCompleteValue());
|
||||
Assert.Equal("cc-family-name", AutoCompleteType.CcFamilyName.GetAutoCompleteValue());
|
||||
Assert.Equal("cc-number", AutoCompleteType.CcNumber.GetAutoCompleteValue());
|
||||
Assert.Equal("cc-exp", AutoCompleteType.CcExp.GetAutoCompleteValue());
|
||||
Assert.Equal("cc-exp-month", AutoCompleteType.CcExpMonth.GetAutoCompleteValue());
|
||||
Assert.Equal("cc-exp-year", AutoCompleteType.CcExpYear.GetAutoCompleteValue());
|
||||
Assert.Equal("cc-csc", AutoCompleteType.CcCsc.GetAutoCompleteValue());
|
||||
Assert.Equal("cc-type", AutoCompleteType.CcType.GetAutoCompleteValue());
|
||||
Assert.Equal("transaction-currency", AutoCompleteType.TransactionCurrency.GetAutoCompleteValue());
|
||||
Assert.Equal("transaction-amount", AutoCompleteType.TransactionAmount.GetAutoCompleteValue());
|
||||
Assert.Equal("language", AutoCompleteType.Language.GetAutoCompleteValue());
|
||||
Assert.Equal("bday", AutoCompleteType.Bday.GetAutoCompleteValue());
|
||||
Assert.Equal("bday-day", AutoCompleteType.BdayDay.GetAutoCompleteValue());
|
||||
Assert.Equal("bday-month", AutoCompleteType.BdayMonth.GetAutoCompleteValue());
|
||||
Assert.Equal("bday-year", AutoCompleteType.BdayYear.GetAutoCompleteValue());
|
||||
Assert.Equal("sex", AutoCompleteType.Sex.GetAutoCompleteValue());
|
||||
Assert.Equal("tel", AutoCompleteType.Tel.GetAutoCompleteValue());
|
||||
Assert.Equal("tel-country-code", AutoCompleteType.TelCountryCode.GetAutoCompleteValue());
|
||||
Assert.Equal("tel-national", AutoCompleteType.TelNational.GetAutoCompleteValue());
|
||||
Assert.Equal("tel-area-code", AutoCompleteType.TelAreaCode.GetAutoCompleteValue());
|
||||
Assert.Equal("tel-local", AutoCompleteType.TelLocal.GetAutoCompleteValue());
|
||||
Assert.Equal("tel-extension", AutoCompleteType.TelExtension.GetAutoCompleteValue());
|
||||
Assert.Equal("impp", AutoCompleteType.Impp.GetAutoCompleteValue());
|
||||
Assert.Equal("url", AutoCompleteType.Url.GetAutoCompleteValue());
|
||||
Assert.Equal("photo", AutoCompleteType.Photo.GetAutoCompleteValue());
|
||||
// Synonyms
|
||||
Assert.Equal("address-level1", AutoCompleteType.State.GetAutoCompleteValue());
|
||||
Assert.Equal("address-level1", AutoCompleteType.Province.GetAutoCompleteValue());
|
||||
Assert.Equal("postal-code", AutoCompleteType.ZipCode.GetAutoCompleteValue());
|
||||
Assert.Equal("given-name", AutoCompleteType.FirstName.GetAutoCompleteValue());
|
||||
Assert.Equal("additional-name", AutoCompleteType.MiddleName.GetAutoCompleteValue());
|
||||
Assert.Equal("family-name", AutoCompleteType.LastName.GetAutoCompleteValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add(p => p.Icon, icon));
|
||||
|
||||
Assert.Contains(@$"<i class=""rz-button-icon-left rzi"">{icon}</i>", component.Markup);
|
||||
Assert.Contains(@$"<i class=""notranslate rz-button-icon-left rzi"">{icon}</i>", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -48,7 +48,7 @@ namespace Radzen.Blazor.Tests
|
||||
);
|
||||
|
||||
// does not render the actual icon when busy
|
||||
Assert.DoesNotContain(@$"<i class=""rz-button-icon-left rzi"">{icon}</i>", component.Markup);
|
||||
Assert.DoesNotContain(@$"<i class=""notranslate rz-button-icon-left rzi"">{icon}</i>", component.Markup);
|
||||
|
||||
// renders the icon with busy spin animation
|
||||
Assert.Contains(@"<i style=""animation: rotation", component.Markup);
|
||||
@@ -71,7 +71,7 @@ namespace Radzen.Blazor.Tests
|
||||
parameters.Add(p => p.Icon, icon);
|
||||
});
|
||||
|
||||
Assert.Contains(@$"<i class=""rz-button-icon-left rzi"">{icon}</i>", component.Markup);
|
||||
Assert.Contains(@$"<i class=""notranslate rz-button-icon-left rzi"">{icon}</i>", component.Markup);
|
||||
Assert.Contains(@$"<span class=""rz-button-text"">{text}</span>", component.Markup);
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add(p => p.Image, image));
|
||||
|
||||
Assert.Contains(@$"<img class=""rz-button-icon-left rzi"" src=""{image}"" />", component.Markup);
|
||||
Assert.Contains(@$"<img class=""notranslate rz-button-icon-left rzi"" src=""{image}"" alt=""button"" />", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -103,9 +103,10 @@ namespace Radzen.Blazor.Tests
|
||||
{
|
||||
parameters.Add(p => p.Text, text);
|
||||
parameters.Add(p => p.Image, image);
|
||||
parameters.Add(p => p.ImageAlternateText, text);
|
||||
});
|
||||
|
||||
Assert.Contains(@$"<img class=""rz-button-icon-left rzi"" src=""{image}"" />", component.Markup);
|
||||
Assert.Contains(@$"<img class=""notranslate rz-button-icon-left rzi"" src=""{image}"" alt=""{text}"" />", component.Markup);
|
||||
Assert.Contains(@$"<span class=""rz-button-text"">{text}</span>", component.Markup);
|
||||
}
|
||||
|
||||
|
||||
58
Radzen.Blazor.Tests/ChartTests.cs
Normal file
58
Radzen.Blazor.Tests/ChartTests.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Bunit;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Radzen.Blazor.Rendering;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Radzen.Blazor.Tests;
|
||||
|
||||
public class ChartTests
|
||||
{
|
||||
private readonly ITestOutputHelper output;
|
||||
public ChartTests(ITestOutputHelper output)
|
||||
{
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
[Fact(Timeout = 30000)]
|
||||
public async Task Chart_Tooltip_Performance()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.Setup<Rect>("Radzen.createChart", _ => true).SetResult(new Rect {Left = 0, Top = 0, Width = 200, Height = 200});
|
||||
ctx.Services.AddScoped<TooltipService>();
|
||||
ctx.JSInterop.SetupVoid("Radzen.openChartTooltip", _ => true);
|
||||
ctx.RenderComponent<RadzenChartTooltip>();
|
||||
|
||||
var seriesData = Enumerable.Range(0, 5000).Select(i => new Point { X = i, Y = i });
|
||||
var chart = ctx.RenderComponent<RadzenChart>(chartParameters =>
|
||||
chartParameters
|
||||
.AddChildContent<RadzenLineSeries<Point>>(seriesParameters =>
|
||||
seriesParameters
|
||||
.Add(p => p.CategoryProperty, nameof(Point.X))
|
||||
.Add(p => p.ValueProperty, nameof(Point.Y))
|
||||
.Add(p => p.Data, seriesData))
|
||||
.AddChildContent<RadzenCategoryAxis>(axisParameters =>
|
||||
axisParameters
|
||||
.Add(p => p.Step, 100)
|
||||
.Add(p => p.Formatter, x =>
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
return $"{x}";
|
||||
})));
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
foreach (var invocation in Enumerable.Range(0, 10))
|
||||
{
|
||||
await chart.InvokeAsync(() => chart.Instance.MouseMove(100, 80));
|
||||
Assert.Equal((invocation + 1) * 2, ctx.JSInterop.Invocations.Count(x => x.Identifier == "Radzen.openChartTooltip"));
|
||||
await chart.InvokeAsync(() => chart.Instance.MouseMove(0, 0));
|
||||
Assert.Equal(invocation + 1, ctx.JSInterop.Invocations.Count(x => x.Identifier == "Radzen.closeTooltip"));
|
||||
}
|
||||
output.WriteLine($"Time took: {stopwatch.Elapsed}");
|
||||
}
|
||||
}
|
||||
@@ -160,5 +160,83 @@ namespace Radzen.Blazor.Tests
|
||||
Assert.Contains(@$"rz-state-active", component.Markup);
|
||||
Assert.Contains(@$"rzi-times", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckBox_Renders_ReadonlyParameter()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
var component = ctx.RenderComponent<RadzenCheckBox<bool>>();
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add<bool>(p => p.ReadOnly, true));
|
||||
|
||||
Assert.Contains(@$"readonly", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckBox_DoesNotRaise_ChangedEvent_ReadonlyParameter()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
var component = ctx.RenderComponent<RadzenCheckBox<bool>>();
|
||||
|
||||
var raised = false;
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters
|
||||
.Add<bool>(p => p.ReadOnly, true)
|
||||
.Add(p => p.Change, args => { raised = true; })
|
||||
);
|
||||
|
||||
component.Find("div.rz-chkbox-box").Click();
|
||||
|
||||
Assert.False(raised);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckBox_DoesNotRaise_ValueChangedEvent_ReadonlyParameter()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
var component = ctx.RenderComponent<RadzenCheckBox<bool>>();
|
||||
|
||||
var raised = false;
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters
|
||||
.Add<bool>(p => p.ReadOnly, true)
|
||||
.Add(p => p.ValueChanged, args => { raised = true; })
|
||||
);
|
||||
|
||||
component.Find("div.rz-chkbox-box").Click();
|
||||
|
||||
Assert.False(raised);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckBox_ValueNotChanged_ReadonlyParameter()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
var component = ctx.RenderComponent<RadzenCheckBox<bool>>();
|
||||
|
||||
var value = true;
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters
|
||||
.Add<bool>(p => p.ReadOnly, true)
|
||||
.Add<bool>(p => p.Value, value)
|
||||
);
|
||||
|
||||
component.Find("div.rz-chkbox-box").Click();
|
||||
|
||||
Assert.Contains(@$"rz-state-active", component.Markup);
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters
|
||||
.Add<bool>(p => p.ReadOnly, !true)
|
||||
.Add<bool>(p => p.Value, value)
|
||||
);
|
||||
|
||||
component.Find("div.rz-chkbox-box").Click();
|
||||
|
||||
Assert.DoesNotContain(@$"rz-state-active", component.Markup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
16
Radzen.Blazor.Tests/ColorPickerTests.cs
Normal file
16
Radzen.Blazor.Tests/ColorPickerTests.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Bunit;
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Tests
|
||||
{
|
||||
public class ColorPickerTests
|
||||
{
|
||||
[Fact]
|
||||
public void ColorPicker_ShouldAcceptInvalidValues()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
var component = ctx.RenderComponent<RadzenColorPicker>(ComponentParameter.CreateParameter("Value", "invalid"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,39 +20,29 @@ namespace Radzen.Blazor.Tests
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenGrid<dynamic>>(parameterBuilder =>
|
||||
var component = ctx.RenderComponent<RadzenDataGrid<dynamic>>(parameterBuilder =>
|
||||
{
|
||||
parameterBuilder.Add<IEnumerable<dynamic>>(p => p.Data, new[] { new { Id = 1 }, new { Id = 2 }, new { Id = 3 } });
|
||||
parameterBuilder.Add<RenderFragment>(p => p.Columns, builder =>
|
||||
{
|
||||
builder.OpenComponent(0, typeof(RadzenGridColumn<dynamic>));
|
||||
builder.OpenComponent(0, typeof(RadzenDataGridColumn<dynamic>));
|
||||
builder.AddAttribute(1, "Property", "Id");
|
||||
builder.CloseComponent();
|
||||
});
|
||||
});
|
||||
|
||||
// Main
|
||||
Assert.Contains(@$"rz-datatable-scrollable-wrapper", component.Markup);
|
||||
Assert.Contains(@$"rz-datatable-scrollable-view", component.Markup);
|
||||
Assert.Contains(@$"rz-data-grid", component.Markup);
|
||||
Assert.Contains(@$"rz-datatable", component.Markup);
|
||||
Assert.Contains(@$"rz-datatable-scrollable", component.Markup);
|
||||
|
||||
// Header
|
||||
Assert.Contains(@$"rz-datatable-scrollable-header", component.Markup);
|
||||
Assert.Contains(@$"rz-datatable-scrollable-header-box", component.Markup);
|
||||
Assert.Contains(@$"rz-datatable-thead", component.Markup);
|
||||
Assert.Contains(@$"rz-datatable-scrollable-colgroup", component.Markup);
|
||||
// Data
|
||||
Assert.Contains(@$"rz-data-grid-data", component.Markup);
|
||||
|
||||
//Body
|
||||
Assert.Contains(@$"rz-datatable-scrollable-body", component.Markup);
|
||||
Assert.Contains(@$"rz-datatable-scrollable-table-wrapper", component.Markup);
|
||||
Assert.Contains(@$"rz-datatable-data", component.Markup);
|
||||
Assert.Contains(@$"rz-datatable-hoverable-rows", component.Markup);
|
||||
|
||||
// Footer
|
||||
Assert.DoesNotContain(@$"rz-datatable-scrollable-footer", component.Markup);
|
||||
Assert.DoesNotContain(@$"rz-datatable-scrollable-footer-box", component.Markup);
|
||||
|
||||
//Columns
|
||||
Assert.DoesNotContain(@$"rz-sortable-column", component.Markup);
|
||||
// Table
|
||||
Assert.Contains(@$"rz-grid-table", component.Markup);
|
||||
Assert.Contains(@$"rz-grid-table-fixed", component.Markup);
|
||||
Assert.Contains(@$"rz-grid-table-striped", component.Markup);
|
||||
}
|
||||
|
||||
// Columns tests
|
||||
@@ -63,22 +53,22 @@ namespace Radzen.Blazor.Tests
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenGrid<dynamic>>(parameterBuilder =>
|
||||
var component = ctx.RenderComponent<RadzenDataGrid<dynamic>>(parameterBuilder =>
|
||||
{
|
||||
parameterBuilder.Add<IEnumerable<dynamic>>(p => p.Data, new[] { new { Id = 1 }, new { Id = 2 }, new { Id = 3 } });
|
||||
parameterBuilder.Add<RenderFragment>(p => p.Columns, builder =>
|
||||
{
|
||||
builder.OpenComponent(0, typeof(RadzenGridColumn<dynamic>));
|
||||
builder.OpenComponent(0, typeof(RadzenDataGridColumn<dynamic>));
|
||||
builder.AddAttribute(1, "Property", "Id");
|
||||
builder.CloseComponent();
|
||||
});
|
||||
});
|
||||
|
||||
var markup = new Regex(@"\s\s+").Replace(component.Markup, "").Trim();
|
||||
var data = component.FindAll(".rz-cell-data");
|
||||
|
||||
Assert.Contains(@$"<span class=""rz-cell-data"">1</span>", markup);
|
||||
Assert.Contains(@$"<span class=""rz-cell-data"">2</span>", markup);
|
||||
Assert.Contains(@$"<span class=""rz-cell-data"">3</span>", markup);
|
||||
Assert.Equal("1", data[0].TextContent.Trim());
|
||||
Assert.Equal("2", data[1].TextContent.Trim());
|
||||
Assert.Equal("3", data[2].TextContent.Trim());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -88,20 +78,67 @@ namespace Radzen.Blazor.Tests
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenGrid<dynamic>>(parameterBuilder =>
|
||||
var component = ctx.RenderComponent<RadzenDataGrid<dynamic>>(parameterBuilder =>
|
||||
{
|
||||
parameterBuilder.Add<IEnumerable<dynamic>>(p => p.Data, new[] { new { Id = 1 }, new { Id = 2 }, new { Id = 3 } });
|
||||
parameterBuilder.Add<RenderFragment>(p => p.Columns, builder =>
|
||||
{
|
||||
builder.OpenComponent(0, typeof(RadzenGridColumn<dynamic>));
|
||||
builder.OpenComponent(0, typeof(RadzenDataGridColumn<dynamic>));
|
||||
builder.AddAttribute(1, "Title", "MyId");
|
||||
builder.CloseComponent();
|
||||
});
|
||||
});
|
||||
|
||||
var markup = new Regex(@"\s\s+").Replace(component.Markup, "").Trim();
|
||||
var title = component.Find(".rz-column-title");
|
||||
Assert.Equal("MyId", title.TextContent.Trim());
|
||||
}
|
||||
|
||||
Assert.Contains(@$"<span class=""rz-column-title"">MyId</span>", markup);
|
||||
[Fact]
|
||||
public void DataGrid_Renders_TitleAttribute()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenDataGrid<dynamic>>(parameterBuilder =>
|
||||
{
|
||||
parameterBuilder.Add(p => p.ShowColumnTitleAsTooltip, true);
|
||||
parameterBuilder.Add<IEnumerable<dynamic>>(p => p.Data, new[] { new { Id = 1 }, new { Id = 2 }, new { Id = 3 } });
|
||||
parameterBuilder.Add<RenderFragment>(p => p.Columns, builder =>
|
||||
{
|
||||
builder.OpenComponent(0, typeof(RadzenDataGridColumn<dynamic>));
|
||||
builder.AddAttribute(1, "Title", "MyId");
|
||||
builder.CloseComponent();
|
||||
});
|
||||
});
|
||||
|
||||
var title = component.Find(".rz-column-title");
|
||||
Assert.Equal("MyId", title.TextContent.Trim());
|
||||
Assert.Equal("MyId", title.GetAttribute("title"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DataGrid_DoesNotRender_TitleAttribute()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenDataGrid<dynamic>>(parameterBuilder =>
|
||||
{
|
||||
parameterBuilder.Add(p => p.ShowColumnTitleAsTooltip, false);
|
||||
parameterBuilder.Add<IEnumerable<dynamic>>(p => p.Data, new[] { new { Id = 1 }, new { Id = 2 }, new { Id = 3 } });
|
||||
parameterBuilder.Add<RenderFragment>(p => p.Columns, builder =>
|
||||
{
|
||||
builder.OpenComponent(0, typeof(RadzenDataGridColumn<dynamic>));
|
||||
builder.AddAttribute(1, "Title", "MyId");
|
||||
builder.CloseComponent();
|
||||
});
|
||||
});
|
||||
|
||||
var title = component.Find(".rz-column-title");
|
||||
Assert.Equal("MyId", title.TextContent.Trim());
|
||||
Assert.Null(title.GetAttribute("title"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -111,12 +148,12 @@ namespace Radzen.Blazor.Tests
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenGrid<dynamic>>(parameterBuilder =>
|
||||
var component = ctx.RenderComponent<RadzenDataGrid<dynamic>>(parameterBuilder =>
|
||||
{
|
||||
parameterBuilder.Add<IEnumerable<dynamic>>(p => p.Data, new[] { new { Id = 1 }, new { Id = 2 }, new { Id = 3 } });
|
||||
parameterBuilder.Add<RenderFragment>(p => p.Columns, builder =>
|
||||
{
|
||||
builder.OpenComponent(0, typeof(RadzenGridColumn<dynamic>));
|
||||
builder.OpenComponent(0, typeof(RadzenDataGridColumn<dynamic>));
|
||||
builder.AddAttribute(1, "Property", "Id");
|
||||
builder.AddAttribute(2, "Title", "Id");
|
||||
builder.CloseComponent();
|
||||
@@ -141,12 +178,12 @@ namespace Radzen.Blazor.Tests
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenGrid<dynamic>>(parameterBuilder =>
|
||||
var component = ctx.RenderComponent<RadzenDataGrid<dynamic>>(parameterBuilder =>
|
||||
{
|
||||
parameterBuilder.Add<IEnumerable<dynamic>>(p => p.Data, new[] { new { Id = 1 }, new { Id = 2 }, new { Id = 3 } });
|
||||
parameterBuilder.Add<RenderFragment>(p => p.Columns, builder =>
|
||||
{
|
||||
builder.OpenComponent(0, typeof(RadzenGridColumn<dynamic>));
|
||||
builder.OpenComponent(0, typeof(RadzenDataGridColumn<dynamic>));
|
||||
builder.AddAttribute(1, "Property", "Id");
|
||||
builder.AddAttribute(2, "Title", "Id");
|
||||
builder.AddAttribute(3, "Sortable", false);
|
||||
@@ -165,12 +202,12 @@ namespace Radzen.Blazor.Tests
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenGrid<dynamic>>(parameterBuilder =>
|
||||
var component = ctx.RenderComponent<RadzenDataGrid<dynamic>>(parameterBuilder =>
|
||||
{
|
||||
parameterBuilder.Add<IEnumerable<dynamic>>(p => p.Data, new[] { new { Id = 1 }, new { Id = 2 }, new { Id = 3 } });
|
||||
parameterBuilder.Add<RenderFragment>(p => p.Columns, builder =>
|
||||
{
|
||||
builder.OpenComponent(0, typeof(RadzenGridColumn<dynamic>));
|
||||
builder.OpenComponent(0, typeof(RadzenDataGridColumn<dynamic>));
|
||||
builder.AddAttribute(1, "Property", "Id");
|
||||
builder.AddAttribute(2, "Title", "Id");
|
||||
builder.CloseComponent();
|
||||
@@ -178,14 +215,14 @@ namespace Radzen.Blazor.Tests
|
||||
parameterBuilder.Add<bool>(p => p.AllowFiltering, true);
|
||||
});
|
||||
|
||||
Assert.Contains(@$"rz-cell-filter", component.Markup);
|
||||
Assert.Contains(@$"rz-grid-filter-icon", component.Markup);
|
||||
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add<bool>(p => p.AllowFiltering, false);
|
||||
});
|
||||
|
||||
Assert.DoesNotContain(@$"rz-cell-filter", component.Markup);
|
||||
Assert.DoesNotContain(@$"rz-grid-filter-icon", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -195,12 +232,12 @@ namespace Radzen.Blazor.Tests
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenGrid<dynamic>>(parameterBuilder =>
|
||||
var component = ctx.RenderComponent<RadzenDataGrid<dynamic>>(parameterBuilder =>
|
||||
{
|
||||
parameterBuilder.Add<IEnumerable<dynamic>>(p => p.Data, new[] { new { Id = 1 }, new { Id = 2 }, new { Id = 3 } });
|
||||
parameterBuilder.Add<RenderFragment>(p => p.Columns, builder =>
|
||||
{
|
||||
builder.OpenComponent(0, typeof(RadzenGridColumn<dynamic>));
|
||||
builder.OpenComponent(0, typeof(RadzenDataGridColumn<dynamic>));
|
||||
builder.AddAttribute(1, "Property", "Id");
|
||||
builder.AddAttribute(2, "Title", "Id");
|
||||
builder.AddAttribute(3, "Filterable", false);
|
||||
@@ -219,12 +256,12 @@ namespace Radzen.Blazor.Tests
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenGrid<dynamic>>(parameterBuilder =>
|
||||
var component = ctx.RenderComponent<RadzenDataGrid<dynamic>>(parameterBuilder =>
|
||||
{
|
||||
parameterBuilder.Add<IEnumerable<dynamic>>(p => p.Data, new[] { new { Id = 1 }, new { Id = 2 }, new { Id = 3 } });
|
||||
parameterBuilder.Add<RenderFragment>(p => p.Columns, builder =>
|
||||
{
|
||||
builder.OpenComponent(0, typeof(RadzenGridColumn<dynamic>));
|
||||
builder.OpenComponent(0, typeof(RadzenDataGridColumn<dynamic>));
|
||||
builder.AddAttribute(1, "Property", "Id");
|
||||
builder.AddAttribute(2, "Title", "Id");
|
||||
builder.CloseComponent();
|
||||
@@ -250,12 +287,12 @@ namespace Radzen.Blazor.Tests
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenGrid<int>>(parameterBuilder =>
|
||||
var component = ctx.RenderComponent<RadzenDataGrid<int>>(parameterBuilder =>
|
||||
{
|
||||
parameterBuilder.Add<IEnumerable<int>>(p => p.Data, new[] { 1, 2, 3 });
|
||||
parameterBuilder.Add<RenderFragment>(p => p.Columns, builder =>
|
||||
{
|
||||
builder.OpenComponent(0, typeof(RadzenGridColumn<int>));
|
||||
builder.OpenComponent(0, typeof(RadzenDataGridColumn<int>));
|
||||
|
||||
builder.AddAttribute(1, "HeaderTemplate", (RenderFragment)delegate (RenderTreeBuilder b)
|
||||
{
|
||||
@@ -276,12 +313,12 @@ namespace Radzen.Blazor.Tests
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenGrid<int>>(parameterBuilder =>
|
||||
var component = ctx.RenderComponent<RadzenDataGrid<int>>(parameterBuilder =>
|
||||
{
|
||||
parameterBuilder.Add<IEnumerable<int>>(p => p.Data, new[] { 1, 2, 3 });
|
||||
parameterBuilder.Add<RenderFragment>(p => p.Columns, builder =>
|
||||
{
|
||||
builder.OpenComponent(0, typeof(RadzenGridColumn<int>));
|
||||
builder.OpenComponent(0, typeof(RadzenDataGridColumn<int>));
|
||||
|
||||
builder.AddAttribute(1, "FooterTemplate", (RenderFragment)delegate (RenderTreeBuilder b)
|
||||
{
|
||||
@@ -292,8 +329,8 @@ namespace Radzen.Blazor.Tests
|
||||
});
|
||||
});
|
||||
|
||||
Assert.Contains(@$"rz-datatable-scrollable-footer", component.Markup);
|
||||
Assert.Contains(@$"rz-datatable-scrollable-footer-box", component.Markup);
|
||||
Assert.Contains(@$"rz-datatable-tfoot", component.Markup);
|
||||
Assert.Contains(@$"rz-column-footer", component.Markup);
|
||||
Assert.Contains(@$"Footer", component.Markup);
|
||||
}
|
||||
|
||||
@@ -305,12 +342,12 @@ namespace Radzen.Blazor.Tests
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenGrid<dynamic>>(parameterBuilder =>
|
||||
var component = ctx.RenderComponent<RadzenDataGrid<dynamic>>(parameterBuilder =>
|
||||
{
|
||||
parameterBuilder.Add<IEnumerable<dynamic>>(p => p.Data, Enumerable.Range(0, 100).Select(i => new { Id = i }));
|
||||
parameterBuilder.Add<RenderFragment>(p => p.Columns, builder =>
|
||||
{
|
||||
builder.OpenComponent(0, typeof(RadzenGridColumn<dynamic>));
|
||||
builder.OpenComponent(0, typeof(RadzenDataGridColumn<dynamic>));
|
||||
builder.AddAttribute(1, "Property", "Id");
|
||||
builder.AddAttribute(2, "Title", "Id");
|
||||
builder.CloseComponent();
|
||||
@@ -335,11 +372,20 @@ namespace Radzen.Blazor.Tests
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenGrid<int>>(parameterBuilder => parameterBuilder.Add<IEnumerable<int>>(p => p.Data, Enumerable.Range(0, 100)));
|
||||
var component = ctx.RenderComponent<RadzenDataGrid<dynamic>>(parameterBuilder =>
|
||||
{
|
||||
parameterBuilder.Add<IEnumerable<dynamic>>(p => p.Data, Enumerable.Range(0, 100).Select(i => new { Id = i }));
|
||||
parameterBuilder.Add<RenderFragment>(p => p.Columns, builder =>
|
||||
{
|
||||
builder.OpenComponent(0, typeof(RadzenDataGridColumn<dynamic>));
|
||||
builder.AddAttribute(1, "Property", "Id");
|
||||
builder.AddAttribute(2, "Title", "Id");
|
||||
builder.CloseComponent();
|
||||
});
|
||||
parameterBuilder.Add<bool>(p => p.AllowPaging, true);
|
||||
});
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add<bool>(p => p.AllowPaging, true));
|
||||
|
||||
Assert.Contains(@$"rz-paginator-bottom", component.Markup);
|
||||
Assert.Contains(@$"rz-pager", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -349,16 +395,21 @@ namespace Radzen.Blazor.Tests
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenGrid<int>>(parameterBuilder => parameterBuilder.Add<IEnumerable<int>>(p => p.Data, Enumerable.Range(0, 100)));
|
||||
|
||||
component.SetParametersAndRender(parameters =>
|
||||
var component = ctx.RenderComponent<RadzenDataGrid<dynamic>>(parameterBuilder =>
|
||||
{
|
||||
parameters.Add<bool>(p => p.AllowPaging, true);
|
||||
parameters.Add<PagerPosition>(p => p.PagerPosition, PagerPosition.Top);
|
||||
parameterBuilder.Add<IEnumerable<dynamic>>(p => p.Data, Enumerable.Range(0, 100).Select(i => new { Id = i }));
|
||||
parameterBuilder.Add<RenderFragment>(p => p.Columns, builder =>
|
||||
{
|
||||
builder.OpenComponent(0, typeof(RadzenDataGridColumn<dynamic>));
|
||||
builder.AddAttribute(1, "Property", "Id");
|
||||
builder.AddAttribute(2, "Title", "Id");
|
||||
builder.CloseComponent();
|
||||
});
|
||||
parameterBuilder.Add<bool>(p => p.AllowPaging, true);
|
||||
parameterBuilder.Add<PagerPosition>(p => p.PagerPosition, PagerPosition.Top);
|
||||
});
|
||||
|
||||
Assert.Contains(@$"rz-paginator", component.Markup);
|
||||
Assert.DoesNotContain(@$"rz-paginator-bottom", component.Markup);
|
||||
Assert.Contains(@$"rz-pager", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -368,16 +419,59 @@ namespace Radzen.Blazor.Tests
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenGrid<int>>(parameterBuilder => parameterBuilder.Add<IEnumerable<int>>(p => p.Data, Enumerable.Range(0, 100)));
|
||||
var component = ctx.RenderComponent<RadzenDataGrid<dynamic>>(parameterBuilder =>
|
||||
{
|
||||
parameterBuilder.Add<IEnumerable<dynamic>>(p => p.Data, Enumerable.Range(0, 100).Select(i => new { Id = i }));
|
||||
parameterBuilder.Add<RenderFragment>(p => p.Columns, builder =>
|
||||
{
|
||||
builder.OpenComponent(0, typeof(RadzenDataGridColumn<dynamic>));
|
||||
builder.AddAttribute(1, "Property", "Id");
|
||||
builder.AddAttribute(2, "Title", "Id");
|
||||
builder.CloseComponent();
|
||||
});
|
||||
parameterBuilder.Add<bool>(p => p.AllowPaging, true);
|
||||
parameterBuilder.Add<PagerPosition>(p => p.PagerPosition, PagerPosition.TopAndBottom);
|
||||
});
|
||||
|
||||
Assert.Contains(@$"rz-pager", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DataGrid_Renders_PagerDensityDefault()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenDataGrid<int>>(parameterBuilder => parameterBuilder.Add<IEnumerable<int>>(p => p.Data, Enumerable.Range(0, 100)));
|
||||
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add<bool>(p => p.AllowPaging, true);
|
||||
parameters.Add<PagerPosition>(p => p.PagerPosition, PagerPosition.TopAndBottom);
|
||||
parameters.Add<PagerPosition>(p => p.PagerPosition, PagerPosition.Top);
|
||||
parameters.Add<Density>(p => p.Density, Density.Default);
|
||||
});
|
||||
|
||||
Assert.Contains(@$"rz-paginator", component.Markup);
|
||||
Assert.Contains(@$"rz-paginator-bottom", component.Markup);
|
||||
Assert.DoesNotContain(@$"rz-density-compact", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DataGrid_Renders_PagerDensityCompact()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenDataGrid<int>>(parameterBuilder => parameterBuilder.Add<IEnumerable<int>>(p => p.Data, Enumerable.Range(0, 100)));
|
||||
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add<bool>(p => p.AllowPaging, true);
|
||||
parameters.Add<PagerPosition>(p => p.PagerPosition, PagerPosition.Top);
|
||||
parameters.Add<Density>(p => p.Density, Density.Compact);
|
||||
});
|
||||
|
||||
Assert.Contains(@$"rz-density-compact", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -387,7 +481,7 @@ namespace Radzen.Blazor.Tests
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenGrid<int>>(parameterBuilder => parameterBuilder.Add<IEnumerable<int>>(p => p.Data, Array.Empty<int>()));
|
||||
var component = ctx.RenderComponent<RadzenDataGrid<int>>(parameterBuilder => parameterBuilder.Add<IEnumerable<int>>(p => p.Data, Array.Empty<int>()));
|
||||
component.Render();
|
||||
|
||||
Assert.Contains("No records to display.", component.Markup);
|
||||
@@ -401,7 +495,7 @@ namespace Radzen.Blazor.Tests
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenGrid<int>>(parameterBuilder => parameterBuilder.Add<IEnumerable<int>>(p => p.Data, Array.Empty<int>()));
|
||||
var component = ctx.RenderComponent<RadzenDataGrid<int>>(parameterBuilder => parameterBuilder.Add<IEnumerable<int>>(p => p.Data, Array.Empty<int>()));
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add(p => p.EmptyText, emptyText);
|
||||
@@ -417,7 +511,7 @@ namespace Radzen.Blazor.Tests
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenGrid<int>>(parameterBuilder => parameterBuilder.Add<IEnumerable<int>>(p => p.Data, Array.Empty<int>()));
|
||||
var component = ctx.RenderComponent<RadzenDataGrid<int>>(parameterBuilder => parameterBuilder.Add<IEnumerable<int>>(p => p.Data, Array.Empty<int>()));
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add<RenderFragment>(p => p.EmptyTemplate, builder =>
|
||||
@@ -438,18 +532,28 @@ namespace Radzen.Blazor.Tests
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenGrid<int>>(parameterBuilder => parameterBuilder.Add<IEnumerable<int>>(p => p.Data, Enumerable.Range(0, 100)));
|
||||
var component = ctx.RenderComponent<RadzenDataGrid<dynamic>>(parameterBuilder =>
|
||||
{
|
||||
parameterBuilder.Add<IEnumerable<dynamic>>(p => p.Data, Enumerable.Range(0, 100).Select(i => new { Id = i }));
|
||||
parameterBuilder.Add<RenderFragment>(p => p.Columns, builder =>
|
||||
{
|
||||
builder.OpenComponent(0, typeof(RadzenDataGridColumn<dynamic>));
|
||||
builder.AddAttribute(1, "Property", "Id");
|
||||
builder.AddAttribute(2, "Title", "Id");
|
||||
builder.CloseComponent();
|
||||
});
|
||||
parameterBuilder.Add<bool>(p => p.AllowPaging, true);
|
||||
});
|
||||
|
||||
var raised = false;
|
||||
LoadDataArgs newArgs = null;
|
||||
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add<bool>(p => p.AllowPaging, true);
|
||||
parameters.Add<LoadDataArgs>(p => p.LoadData, args => { raised = true; newArgs = args; });
|
||||
});
|
||||
|
||||
component.Find(".rz-paginator-next").Click();
|
||||
component.Find(".rz-pager-next").Click();
|
||||
|
||||
Assert.True(raised);
|
||||
Assert.True(newArgs.Skip == 10);
|
||||
@@ -463,18 +567,28 @@ namespace Radzen.Blazor.Tests
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenGrid<int>>(parameterBuilder => parameterBuilder.Add<IEnumerable<int>>(p => p.Data, Enumerable.Range(0, 100)));
|
||||
var component = ctx.RenderComponent<RadzenDataGrid<dynamic>>(parameterBuilder =>
|
||||
{
|
||||
parameterBuilder.Add<IEnumerable<dynamic>>(p => p.Data, Enumerable.Range(0, 100).Select(i => new { Id = i }));
|
||||
parameterBuilder.Add<RenderFragment>(p => p.Columns, builder =>
|
||||
{
|
||||
builder.OpenComponent(0, typeof(RadzenDataGridColumn<dynamic>));
|
||||
builder.AddAttribute(1, "Property", "Id");
|
||||
builder.AddAttribute(2, "Title", "Id");
|
||||
builder.CloseComponent();
|
||||
});
|
||||
parameterBuilder.Add<bool>(p => p.AllowPaging, true);
|
||||
});
|
||||
|
||||
var raised = false;
|
||||
LoadDataArgs newArgs = null;
|
||||
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add<bool>(p => p.AllowPaging, true);
|
||||
parameters.Add<LoadDataArgs>(p => p.LoadData, args => { raised = true; newArgs = args; });
|
||||
});
|
||||
|
||||
component.Find(".rz-paginator-last").Click();
|
||||
component.Find(".rz-pager-last").Click();
|
||||
|
||||
Assert.True(raised);
|
||||
Assert.True(newArgs.Skip == 90);
|
||||
@@ -488,19 +602,29 @@ namespace Radzen.Blazor.Tests
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenGrid<int>>(parameterBuilder => parameterBuilder.Add<IEnumerable<int>>(p => p.Data, Enumerable.Range(0, 100)));
|
||||
var component = ctx.RenderComponent<RadzenDataGrid<dynamic>>(parameterBuilder =>
|
||||
{
|
||||
parameterBuilder.Add<IEnumerable<dynamic>>(p => p.Data, Enumerable.Range(0, 100).Select(i => new { Id = i }));
|
||||
parameterBuilder.Add<RenderFragment>(p => p.Columns, builder =>
|
||||
{
|
||||
builder.OpenComponent(0, typeof(RadzenDataGridColumn<dynamic>));
|
||||
builder.AddAttribute(1, "Property", "Id");
|
||||
builder.AddAttribute(2, "Title", "Id");
|
||||
builder.CloseComponent();
|
||||
});
|
||||
parameterBuilder.Add<bool>(p => p.AllowPaging, true);
|
||||
});
|
||||
|
||||
var raised = false;
|
||||
LoadDataArgs newArgs = null;
|
||||
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add<bool>(p => p.AllowPaging, true);
|
||||
parameters.Add<LoadDataArgs>(p => p.LoadData, args => { raised = true; newArgs = args; });
|
||||
});
|
||||
|
||||
component.Find(".rz-paginator-next").Click();
|
||||
component.Find(".rz-paginator-prev").Click();
|
||||
component.Find(".rz-pager-next").Click();
|
||||
component.Find(".rz-pager-prev").Click();
|
||||
|
||||
Assert.True(raised);
|
||||
Assert.True(newArgs.Skip == 0);
|
||||
@@ -514,19 +638,29 @@ namespace Radzen.Blazor.Tests
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenGrid<int>>(parameterBuilder => parameterBuilder.Add<IEnumerable<int>>(p => p.Data, Enumerable.Range(0, 100)));
|
||||
var component = ctx.RenderComponent<RadzenDataGrid<dynamic>>(parameterBuilder =>
|
||||
{
|
||||
parameterBuilder.Add<IEnumerable<dynamic>>(p => p.Data, Enumerable.Range(0, 100).Select(i => new { Id = i }));
|
||||
parameterBuilder.Add<RenderFragment>(p => p.Columns, builder =>
|
||||
{
|
||||
builder.OpenComponent(0, typeof(RadzenDataGridColumn<dynamic>));
|
||||
builder.AddAttribute(1, "Property", "Id");
|
||||
builder.AddAttribute(2, "Title", "Id");
|
||||
builder.CloseComponent();
|
||||
});
|
||||
parameterBuilder.Add<bool>(p => p.AllowPaging, true);
|
||||
});
|
||||
|
||||
var raised = false;
|
||||
LoadDataArgs newArgs = null;
|
||||
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add<bool>(p => p.AllowPaging, true);
|
||||
parameters.Add<LoadDataArgs>(p => p.LoadData, args => { raised = true; newArgs = args; });
|
||||
});
|
||||
|
||||
component.Find(".rz-paginator-next").Click();
|
||||
component.Find(".rz-paginator-first").Click();
|
||||
component.Find(".rz-pager-next").Click();
|
||||
component.Find(".rz-pager-first").Click();
|
||||
|
||||
Assert.True(raised);
|
||||
Assert.True(newArgs.Skip == 0);
|
||||
@@ -540,17 +674,27 @@ namespace Radzen.Blazor.Tests
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenGrid<int>>(parameterBuilder => parameterBuilder.Add<IEnumerable<int>>(p => p.Data, Enumerable.Range(0, 100)));
|
||||
var component = ctx.RenderComponent<RadzenDataGrid<dynamic>>(parameterBuilder =>
|
||||
{
|
||||
parameterBuilder.Add<IEnumerable<dynamic>>(p => p.Data, Enumerable.Range(0, 100).Select(i => new { Id = i }));
|
||||
parameterBuilder.Add<RenderFragment>(p => p.Columns, builder =>
|
||||
{
|
||||
builder.OpenComponent(0, typeof(RadzenDataGridColumn<dynamic>));
|
||||
builder.AddAttribute(1, "Property", "Id");
|
||||
builder.AddAttribute(2, "Title", "Id");
|
||||
builder.CloseComponent();
|
||||
});
|
||||
parameterBuilder.Add<bool>(p => p.AllowPaging, true);
|
||||
});
|
||||
|
||||
var raised = false;
|
||||
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add<bool>(p => p.AllowPaging, true);
|
||||
parameters.Add<LoadDataArgs>(p => p.LoadData, args => { raised = true; });
|
||||
});
|
||||
|
||||
component.Find(".rz-paginator-first").Click();
|
||||
component.Find(".rz-pager-first").Click();
|
||||
|
||||
Assert.False(raised);
|
||||
}
|
||||
@@ -562,17 +706,27 @@ namespace Radzen.Blazor.Tests
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenGrid<int>>(parameterBuilder => parameterBuilder.Add<IEnumerable<int>>(p => p.Data, Enumerable.Range(0, 100)));
|
||||
var component = ctx.RenderComponent<RadzenDataGrid<dynamic>>(parameterBuilder =>
|
||||
{
|
||||
parameterBuilder.Add<IEnumerable<dynamic>>(p => p.Data, Enumerable.Range(0, 100).Select(i => new { Id = i }));
|
||||
parameterBuilder.Add<RenderFragment>(p => p.Columns, builder =>
|
||||
{
|
||||
builder.OpenComponent(0, typeof(RadzenDataGridColumn<dynamic>));
|
||||
builder.AddAttribute(1, "Property", "Id");
|
||||
builder.AddAttribute(2, "Title", "Id");
|
||||
builder.CloseComponent();
|
||||
});
|
||||
parameterBuilder.Add<bool>(p => p.AllowPaging, true);
|
||||
});
|
||||
|
||||
var raised = false;
|
||||
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add<bool>(p => p.AllowPaging, true);
|
||||
parameters.Add<LoadDataArgs>(p => p.LoadData, args => { raised = true; });
|
||||
});
|
||||
|
||||
component.Find(".rz-paginator-prev").Click();
|
||||
component.Find(".rz-pager-prev").Click();
|
||||
|
||||
Assert.False(raised);
|
||||
}
|
||||
@@ -584,23 +738,29 @@ namespace Radzen.Blazor.Tests
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenGrid<int>>(parameterBuilder => parameterBuilder.Add<IEnumerable<int>>(p => p.Data, Enumerable.Range(0, 100)));
|
||||
var component = ctx.RenderComponent<RadzenDataGrid<dynamic>>(parameterBuilder =>
|
||||
{
|
||||
parameterBuilder.Add<IEnumerable<dynamic>>(p => p.Data, Enumerable.Range(0, 100).Select(i => new { Id = i }));
|
||||
parameterBuilder.Add<RenderFragment>(p => p.Columns, builder =>
|
||||
{
|
||||
builder.OpenComponent(0, typeof(RadzenDataGridColumn<dynamic>));
|
||||
builder.AddAttribute(1, "Property", "Id");
|
||||
builder.AddAttribute(2, "Title", "Id");
|
||||
builder.CloseComponent();
|
||||
});
|
||||
parameterBuilder.Add<bool>(p => p.AllowPaging, true);
|
||||
});
|
||||
|
||||
var raised = false;
|
||||
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add<bool>(p => p.AllowPaging, true);
|
||||
});
|
||||
|
||||
component.Find(".rz-paginator-last").Click();
|
||||
component.Find(".rz-pager-last").Click();
|
||||
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add<LoadDataArgs>(p => p.LoadData, args => { raised = true; });
|
||||
});
|
||||
|
||||
component.Find(".rz-paginator-last").Click();
|
||||
component.Find(".rz-pager-last").Click();
|
||||
|
||||
Assert.False(raised);
|
||||
}
|
||||
@@ -612,23 +772,29 @@ namespace Radzen.Blazor.Tests
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenGrid<int>>(parameterBuilder => parameterBuilder.Add<IEnumerable<int>>(p => p.Data, Enumerable.Range(0, 100)));
|
||||
var component = ctx.RenderComponent<RadzenDataGrid<dynamic>>(parameterBuilder =>
|
||||
{
|
||||
parameterBuilder.Add<IEnumerable<dynamic>>(p => p.Data, Enumerable.Range(0, 100).Select(i => new { Id = i }));
|
||||
parameterBuilder.Add<RenderFragment>(p => p.Columns, builder =>
|
||||
{
|
||||
builder.OpenComponent(0, typeof(RadzenDataGridColumn<dynamic>));
|
||||
builder.AddAttribute(1, "Property", "Id");
|
||||
builder.AddAttribute(2, "Title", "Id");
|
||||
builder.CloseComponent();
|
||||
});
|
||||
parameterBuilder.Add<bool>(p => p.AllowPaging, true);
|
||||
});
|
||||
|
||||
var raised = false;
|
||||
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add<bool>(p => p.AllowPaging, true);
|
||||
});
|
||||
|
||||
component.Find(".rz-paginator-last").Click();
|
||||
component.Find(".rz-pager-last").Click();
|
||||
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add<LoadDataArgs>(p => p.LoadData, args => { raised = true; });
|
||||
});
|
||||
|
||||
component.Find(".rz-paginator-next").Click();
|
||||
component.Find(".rz-pager-next").Click();
|
||||
|
||||
Assert.False(raised);
|
||||
}
|
||||
@@ -640,19 +806,33 @@ namespace Radzen.Blazor.Tests
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenGrid<int>>(parameterBuilder => parameterBuilder.Add<IEnumerable<int>>(p => p.Data, Enumerable.Range(0, 100)));
|
||||
var component = ctx.RenderComponent<RadzenDataGrid<dynamic>>(parameterBuilder =>
|
||||
{
|
||||
parameterBuilder.Add<IEnumerable<dynamic>>(p => p.Data, Enumerable.Range(0, 100).Select(i => new { Id = i }));
|
||||
parameterBuilder.Add<RenderFragment>(p => p.Columns, builder =>
|
||||
{
|
||||
builder.OpenComponent(0, typeof(RadzenDataGridColumn<dynamic>));
|
||||
builder.AddAttribute(1, "Property", "Id");
|
||||
builder.AddAttribute(2, "Title", "Id");
|
||||
builder.CloseComponent();
|
||||
});
|
||||
parameterBuilder.Add<bool>(p => p.AllowPaging, true);
|
||||
});
|
||||
|
||||
var raised = false;
|
||||
LoadDataArgs newArgs = null;
|
||||
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add<bool>(p => p.AllowPaging, true);
|
||||
parameters.Add<int>(p => p.PageSize, 20);
|
||||
parameters.Add<LoadDataArgs>(p => p.LoadData, args => { raised = true; newArgs = args; });
|
||||
});
|
||||
|
||||
component.Find(".rz-paginator-next").Click();
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add<int>(p => p.PageSize, 20);
|
||||
});
|
||||
|
||||
component.Find(".rz-pager-next").Click();
|
||||
|
||||
Assert.True(raised);
|
||||
Assert.True(newArgs.Skip == 20);
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add<bool>(p => p.AllowPaging, true));
|
||||
|
||||
Assert.Contains(@$"rz-paginator-bottom", component.Markup);
|
||||
Assert.Contains(@$"rz-pager-bottom", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -44,8 +44,8 @@ namespace Radzen.Blazor.Tests
|
||||
parameters.Add<PagerPosition>(p => p.PagerPosition, PagerPosition.Top);
|
||||
});
|
||||
|
||||
Assert.Contains(@$"rz-paginator", component.Markup);
|
||||
Assert.DoesNotContain(@$"rz-paginator-bottom", component.Markup);
|
||||
Assert.Contains(@$"rz-pager", component.Markup);
|
||||
Assert.DoesNotContain(@$"rz-pager-bottom", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -60,8 +60,42 @@ namespace Radzen.Blazor.Tests
|
||||
parameters.Add<PagerPosition>(p => p.PagerPosition, PagerPosition.TopAndBottom);
|
||||
});
|
||||
|
||||
Assert.Contains(@$"rz-paginator", component.Markup);
|
||||
Assert.Contains(@$"rz-paginator-bottom", component.Markup);
|
||||
Assert.Contains(@$"rz-pager", component.Markup);
|
||||
Assert.Contains(@$"rz-pager-bottom", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DataList_Renders_PagerDensityDefault()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
var component = ctx.RenderComponent<RadzenDataList<int>>(parameterBuilder => parameterBuilder.Add<IEnumerable<int>>(p => p.Data, Enumerable.Range(0, 100)));
|
||||
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add<bool>(p => p.AllowPaging, true);
|
||||
parameters.Add<PagerPosition>(p => p.PagerPosition, PagerPosition.Top);
|
||||
parameters.Add<Density>(p => p.Density, Density.Default);
|
||||
});
|
||||
|
||||
Assert.DoesNotContain(@$"rz-density-compact", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DataList_Renders_PagerDensityCompact()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
var component = ctx.RenderComponent<RadzenDataList<int>>(parameterBuilder => parameterBuilder.Add<IEnumerable<int>>(p => p.Data, Enumerable.Range(0, 100)));
|
||||
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add<bool>(p => p.AllowPaging, true);
|
||||
parameters.Add<PagerPosition>(p => p.PagerPosition, PagerPosition.Top);
|
||||
parameters.Add<Density>(p => p.Density, Density.Compact);
|
||||
});
|
||||
|
||||
Assert.Contains(@$"rz-density-compact", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -93,7 +127,7 @@ namespace Radzen.Blazor.Tests
|
||||
parameters.Add<LoadDataArgs>(p => p.LoadData, args => { raised = true; newArgs = args; });
|
||||
});
|
||||
|
||||
component.Find(".rz-paginator-next").Click();
|
||||
component.Find(".rz-pager-next").Click();
|
||||
|
||||
Assert.True(raised);
|
||||
Assert.True(newArgs.Skip == 10);
|
||||
@@ -115,7 +149,7 @@ namespace Radzen.Blazor.Tests
|
||||
parameters.Add<LoadDataArgs>(p => p.LoadData, args => { raised = true; newArgs = args; });
|
||||
});
|
||||
|
||||
component.Find(".rz-paginator-last").Click();
|
||||
component.Find(".rz-pager-last").Click();
|
||||
|
||||
Assert.True(raised);
|
||||
Assert.True(newArgs.Skip == 90);
|
||||
@@ -137,8 +171,8 @@ namespace Radzen.Blazor.Tests
|
||||
parameters.Add<LoadDataArgs>(p => p.LoadData, args => { raised = true; newArgs = args; });
|
||||
});
|
||||
|
||||
component.Find(".rz-paginator-next").Click();
|
||||
component.Find(".rz-paginator-prev").Click();
|
||||
component.Find(".rz-pager-next").Click();
|
||||
component.Find(".rz-pager-prev").Click();
|
||||
|
||||
Assert.True(raised);
|
||||
Assert.True(newArgs.Skip == 0);
|
||||
@@ -160,8 +194,8 @@ namespace Radzen.Blazor.Tests
|
||||
parameters.Add<LoadDataArgs>(p => p.LoadData, args => { raised = true; newArgs = args; });
|
||||
});
|
||||
|
||||
component.Find(".rz-paginator-next").Click();
|
||||
component.Find(".rz-paginator-first").Click();
|
||||
component.Find(".rz-pager-next").Click();
|
||||
component.Find(".rz-pager-first").Click();
|
||||
|
||||
Assert.True(raised);
|
||||
Assert.True(newArgs.Skip == 0);
|
||||
@@ -182,7 +216,7 @@ namespace Radzen.Blazor.Tests
|
||||
parameters.Add<LoadDataArgs>(p => p.LoadData, args => { raised = true; });
|
||||
});
|
||||
|
||||
component.Find(".rz-paginator-first").Click();
|
||||
component.Find(".rz-pager-first").Click();
|
||||
|
||||
Assert.False(raised);
|
||||
}
|
||||
@@ -201,7 +235,7 @@ namespace Radzen.Blazor.Tests
|
||||
parameters.Add<LoadDataArgs>(p => p.LoadData, args => { raised = true; });
|
||||
});
|
||||
|
||||
component.Find(".rz-paginator-prev").Click();
|
||||
component.Find(".rz-pager-prev").Click();
|
||||
|
||||
Assert.False(raised);
|
||||
}
|
||||
@@ -219,13 +253,13 @@ namespace Radzen.Blazor.Tests
|
||||
parameters.Add<bool>(p => p.AllowPaging, true);
|
||||
});
|
||||
|
||||
component.Find(".rz-paginator-last").Click();
|
||||
component.Find(".rz-pager-last").Click();
|
||||
|
||||
component.SetParametersAndRender(parameters => {
|
||||
parameters.Add<LoadDataArgs>(p => p.LoadData, args => { raised = true; });
|
||||
});
|
||||
|
||||
component.Find(".rz-paginator-last").Click();
|
||||
component.Find(".rz-pager-last").Click();
|
||||
|
||||
Assert.False(raised);
|
||||
}
|
||||
@@ -243,13 +277,13 @@ namespace Radzen.Blazor.Tests
|
||||
parameters.Add<bool>(p => p.AllowPaging, true);
|
||||
});
|
||||
|
||||
component.Find(".rz-paginator-last").Click();
|
||||
component.Find(".rz-pager-last").Click();
|
||||
|
||||
component.SetParametersAndRender(parameters => {
|
||||
parameters.Add<LoadDataArgs>(p => p.LoadData, args => { raised = true; });
|
||||
});
|
||||
|
||||
component.Find(".rz-paginator-next").Click();
|
||||
component.Find(".rz-pager-next").Click();
|
||||
|
||||
Assert.False(raised);
|
||||
}
|
||||
@@ -270,7 +304,7 @@ namespace Radzen.Blazor.Tests
|
||||
parameters.Add<LoadDataArgs>(p => p.LoadData, args => { raised = true; newArgs = args; });
|
||||
});
|
||||
|
||||
component.Find(".rz-paginator-next").Click();
|
||||
component.Find(".rz-pager-next").Click();
|
||||
|
||||
Assert.True(raised);
|
||||
Assert.True(newArgs.Skip == 20);
|
||||
|
||||
@@ -18,10 +18,10 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>();
|
||||
|
||||
Assert.Contains(@$"rz-datepicker", component.Markup);
|
||||
Assert.Contains(@$"rz-calendar", component.Markup);
|
||||
Assert.Contains(@$"rz-datepicker-group", component.Markup);
|
||||
Assert.Contains(@$"rz-datepicker-header", component.Markup);
|
||||
Assert.Contains(@$"rz-datepicker-calendar", component.Markup);
|
||||
Assert.Contains(@$"rz-calendar-header", component.Markup);
|
||||
Assert.Contains(@$"rz-calendar-view", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -49,7 +49,8 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>();
|
||||
|
||||
component.SetParametersAndRender(parameters => {
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add<bool>(p => p.ShowTime, true);
|
||||
parameters.Add<bool>(p => p.ShowSeconds, true);
|
||||
});
|
||||
@@ -69,7 +70,8 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>();
|
||||
|
||||
component.SetParametersAndRender(parameters => {
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add<bool>(p => p.ShowTime, true);
|
||||
parameters.Add<bool>(p => p.ShowTimeOkButton, true);
|
||||
});
|
||||
@@ -91,9 +93,10 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
var format = "d";
|
||||
|
||||
component.SetParametersAndRender(parameters => {
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add(p => p.DateFormat, format);
|
||||
parameters.Add<object>(p => p.Value, DateTime.Now);
|
||||
parameters.Add<object>(p => p.Value, DateTime.Now);
|
||||
});
|
||||
|
||||
Assert.Contains(@$"value=""{string.Format("{0:" + format + "}", DateTime.Now)}""", component.Markup);
|
||||
@@ -108,7 +111,8 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>();
|
||||
|
||||
component.SetParametersAndRender(parameters => {
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add<bool>(p => p.ShowTime, true);
|
||||
parameters.Add(p => p.HourFormat, "12");
|
||||
});
|
||||
@@ -127,12 +131,13 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>();
|
||||
|
||||
component.SetParametersAndRender(parameters => {
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add<bool>(p => p.ShowTime, true);
|
||||
parameters.Add<bool>(p => p.TimeOnly, true);
|
||||
});
|
||||
|
||||
Assert.DoesNotContain(@$"rz-datepicker-header", component.Markup);
|
||||
Assert.DoesNotContain(@$"rz-calendar-header", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -144,12 +149,13 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>();
|
||||
|
||||
component.SetParametersAndRender(parameters => {
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add<object>(p => p.Value, DateTime.Now);
|
||||
parameters.Add<bool>(p => p.AllowClear, true);
|
||||
parameters.Add<bool>(p => p.AllowClear, true);
|
||||
});
|
||||
|
||||
Assert.Contains(@$"<i class=""rz-dropdown-clear-icon rzi rzi-times""", component.Markup);
|
||||
Assert.Contains(@$"<i class=""notranslate rz-dropdown-clear-icon rzi rzi-times""", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -168,6 +174,34 @@ namespace Radzen.Blazor.Tests
|
||||
Assert.Contains(@$"tabindex=""{value}""", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DatePicker_Renders_EmptyCssClass_WhenValueIsEmpty()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>();
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add(p => p.Value, null));
|
||||
|
||||
Assert.Contains(@$"rz-state-empty", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DatePicker_DoesNotRender_EmptyCssClass_WhenValueIsNotEmpty()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>();
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add(p => p.Value, DateTime.Now));
|
||||
|
||||
Assert.DoesNotContain(@$"rz-state-empty", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DatePicker_Renders_DisabledParameter()
|
||||
{
|
||||
@@ -212,7 +246,7 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add(p => p.Style, value));
|
||||
|
||||
Assert.Contains(@$"style=""display: inline-block;{value}""", component.Markup);
|
||||
Assert.Contains(@$"style=""{value}""", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -241,11 +275,12 @@ namespace Radzen.Blazor.Tests
|
||||
var raised = false;
|
||||
object newValue = null;
|
||||
|
||||
component.SetParametersAndRender(parameters => {
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add(p => p.Change, args => { raised = true; newValue = args; });
|
||||
});
|
||||
|
||||
component.Find(".rz-datepicker-next-icon").Click();
|
||||
component.Find(".rz-calendar-next-icon").Click();
|
||||
|
||||
Assert.False(raised);
|
||||
}
|
||||
@@ -262,11 +297,12 @@ namespace Radzen.Blazor.Tests
|
||||
var raised = false;
|
||||
object newValue = null;
|
||||
|
||||
component.SetParametersAndRender(parameters => {
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add(p => p.ValueChanged, args => { raised = true; newValue = args; });
|
||||
});
|
||||
|
||||
component.Find(".rz-datepicker-next-icon").Click();
|
||||
component.Find(".rz-calendar-next-icon").Click();
|
||||
|
||||
Assert.False(raised);
|
||||
}
|
||||
@@ -283,11 +319,12 @@ namespace Radzen.Blazor.Tests
|
||||
var raised = false;
|
||||
object newValue = null;
|
||||
|
||||
component.SetParametersAndRender(parameters => {
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add(p => p.Change, args => { raised = true; newValue = args; });
|
||||
});
|
||||
|
||||
component.Find(".rz-datepicker-prev-icon").Click();
|
||||
component.Find(".rz-calendar-prev-icon").Click();
|
||||
|
||||
Assert.False(raised);
|
||||
}
|
||||
@@ -304,11 +341,12 @@ namespace Radzen.Blazor.Tests
|
||||
var raised = false;
|
||||
object newValue = null;
|
||||
|
||||
component.SetParametersAndRender(parameters => {
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add(p => p.ValueChanged, args => { raised = true; newValue = args; });
|
||||
});
|
||||
|
||||
component.Find(".rz-datepicker-prev-icon").Click();
|
||||
component.Find(".rz-calendar-prev-icon").Click();
|
||||
|
||||
Assert.False(raised);
|
||||
}
|
||||
@@ -320,14 +358,15 @@ namespace Radzen.Blazor.Tests
|
||||
DateTime previousDay = DateTime.Today.AddDays(-1);
|
||||
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
|
||||
var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>();
|
||||
|
||||
var raised = false;
|
||||
object newValue = null;
|
||||
|
||||
component.SetParametersAndRender(parameters => {
|
||||
var raised = false;
|
||||
object newValue = null;
|
||||
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add(p => p.ValueChanged, args => { raised = true; newValue = args; })
|
||||
.Add(p => p.DateRender, args => { args.Disabled = dates.Contains(args.Date); });
|
||||
});
|
||||
@@ -360,7 +399,8 @@ namespace Radzen.Blazor.Tests
|
||||
var raised = false;
|
||||
object newValue = null;
|
||||
|
||||
component.SetParametersAndRender(parameters => {
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add(p => p.ValueChanged, args => { raised = true; newValue = args; })
|
||||
.Add(p => p.DateRender, args => { args.Disabled = dates.Contains(args.Date); });
|
||||
});
|
||||
@@ -378,14 +418,80 @@ namespace Radzen.Blazor.Tests
|
||||
Assert.True(raised);
|
||||
Assert.Null(newValue);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void DatePicker_Parses_Input_Using_DateFormat()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
|
||||
var component = ctx.RenderComponent<RadzenDatePicker<DateTime?>>();
|
||||
|
||||
var raised = false;
|
||||
object newValue = null;
|
||||
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add(p => p.DateFormat, "ddMM");
|
||||
parameters.Add(p => p.ValueChanged, args => { raised = true; newValue = args; });
|
||||
});
|
||||
|
||||
var inputElement = component.Find(".rz-inputtext");
|
||||
|
||||
string input = "3012";
|
||||
ctx.JSInterop.Setup<string>("Radzen.getInputValue", invocation => true).SetResult(input);
|
||||
inputElement.Change(input);
|
||||
|
||||
Assert.True(raised);
|
||||
Assert.Equal(new DateTime(DateTime.Now.Year, 12, 30), newValue);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void DatePicker_Parses_Input_Using_ParseInput()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
|
||||
var component = ctx.RenderComponent<RadzenDatePicker<DateTime?>>();
|
||||
|
||||
Func<string, DateTime?> customParseInput = (input) =>
|
||||
{
|
||||
if (DateTime.TryParseExact(input, "ddMM", null, DateTimeStyles.None, out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
var raised = false;
|
||||
object newValue = null;
|
||||
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add(p => p.ParseInput, customParseInput);
|
||||
parameters.Add(p => p.ValueChanged, args => { raised = true; newValue = args; });
|
||||
});
|
||||
|
||||
var inputElement = component.Find(".rz-inputtext");
|
||||
|
||||
string input = "3012";
|
||||
ctx.JSInterop.Setup<string>("Radzen.getInputValue", invocation => true).SetResult(input);
|
||||
inputElement.Change(input);
|
||||
|
||||
Assert.True(raised);
|
||||
Assert.Equal(new DateTime(DateTime.Now.Year, 12, 30), newValue);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void DatePicker_Respects_DateTimeMaxValue()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
|
||||
var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
|
||||
{
|
||||
parameters.Add(p => p.Value, DateTime.MaxValue);
|
||||
@@ -393,26 +499,7 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
Assert.Contains(DateTime.MaxValue.ToString(component.Instance.DateFormat), component.Markup);
|
||||
|
||||
var exception = Record.Exception(() => component.Find(".rz-datepicker-next-icon")
|
||||
.Click());
|
||||
Assert.Null(exception);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DatePicker_Respects_DateTimeMinValue()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
|
||||
{
|
||||
parameters.Add(p => p.Value, DateTime.MinValue);
|
||||
});
|
||||
|
||||
Assert.Contains(DateTime.MinValue.ToString(component.Instance.DateFormat), component.Markup);
|
||||
|
||||
var exception = Record.Exception(() => component.Find(".rz-datepicker-prev-icon")
|
||||
var exception = Record.Exception(() => component.Find(".rz-calendar-next-icon")
|
||||
.Click());
|
||||
Assert.Null(exception);
|
||||
}
|
||||
@@ -441,7 +528,7 @@ namespace Radzen.Blazor.Tests
|
||||
parameters.Add(p => p.Change, args => { raised = true; newValue = args; });
|
||||
});
|
||||
|
||||
component.Find(".rz-datepicker-next-icon").Click();
|
||||
component.Find(".rz-calendar-next-icon").Click();
|
||||
component.FindAll(".rz-button-text").First(x => x.TextContent == "Ok").Click();
|
||||
|
||||
Assert.True(raised);
|
||||
@@ -505,5 +592,145 @@ namespace Radzen.Blazor.Tests
|
||||
Assert.Equal(kind, (component.Instance.Value as DateTime?)?.Kind);
|
||||
Assert.Equal(valueUtc.UtcDateTime.ToString(CultureInfo.InvariantCulture), (component.Instance.Value as DateTime?)?.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DatePicker_Displays_Calender_Icon()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>();
|
||||
|
||||
Assert.Contains(@$"rzi-calendar", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DatePicker_Displays_Schedule_Icon()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameters =>
|
||||
{
|
||||
parameters.Add(p => p.TimeOnly, true);
|
||||
});
|
||||
|
||||
Assert.Contains(@$"rzi-time", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DatePicker_Supports_DateOnly()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
DateOnly? dateOnly = new DateOnly(2024, 1, 31);
|
||||
DateOnly? valueChangedValue = null!;
|
||||
var component = ctx.RenderComponent<RadzenDatePicker<DateOnly?>>(parameters =>
|
||||
{
|
||||
parameters.Add(p => p.Value, dateOnly);
|
||||
parameters.Add(p => p.ValueChanged, args => { valueChangedValue = args; });
|
||||
});
|
||||
|
||||
Assert.False(component.Instance.ShowTime);
|
||||
var input = component.Find("input");
|
||||
input.GetAttribute("value").MarkupMatches(dateOnly.ToString());
|
||||
|
||||
// update to new value
|
||||
var inputElement = component.Find(".rz-inputtext");
|
||||
DateOnly? enteredValue = new DateOnly(2024, 2, 28);
|
||||
ctx.JSInterop.Setup<string>("Radzen.getInputValue", invocation => true).SetResult(enteredValue.Value.ToShortDateString());
|
||||
inputElement.Change(enteredValue);
|
||||
|
||||
input.GetAttribute("value").MarkupMatches(enteredValue.ToString());
|
||||
Assert.Equal(enteredValue, component.Instance.Value);
|
||||
Assert.Equal(enteredValue, valueChangedValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DatePicker_Supports_TimeOnly()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
TimeOnly? timeOnly = new TimeOnly(23, 59, 59);
|
||||
TimeOnly? valueChangedValue = null!;
|
||||
var component = ctx.RenderComponent<RadzenDatePicker<TimeOnly>>(parameters =>
|
||||
{
|
||||
parameters.Add(p => p.Value, timeOnly);
|
||||
parameters.Add(p => p.ValueChanged, args => { valueChangedValue = args; });
|
||||
});
|
||||
|
||||
Assert.True(component.Instance.TimeOnly);
|
||||
Assert.True(component.Instance.ShowTime);
|
||||
var input = component.Find("input");
|
||||
input.GetAttribute("value").MarkupMatches(timeOnly.ToString());
|
||||
|
||||
// update to new value
|
||||
var inputElement = component.Find(".rz-inputtext");
|
||||
TimeOnly? enteredValue = new TimeOnly(1, 4, 5);
|
||||
ctx.JSInterop.Setup<string>("Radzen.getInputValue", invocation => true).SetResult(enteredValue.Value.ToLongTimeString());
|
||||
inputElement.Change(enteredValue);
|
||||
|
||||
input.GetAttribute("value").MarkupMatches(enteredValue.ToString());
|
||||
Assert.Equal(enteredValue, component.Instance.Value);
|
||||
Assert.Equal(enteredValue, valueChangedValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DatePicker_ShowCalendarWeek_WeekNumberAddedInAdditionalColumn()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameter =>
|
||||
{
|
||||
parameter.Add(p => p.ShowCalendarWeek, true);
|
||||
});
|
||||
|
||||
Assert.Contains(@$"rz-calendar-week-number", component.Markup);
|
||||
Assert.Equal(8, component.FindAll(".rz-calendar-view th").Count());
|
||||
// check header and week number column
|
||||
Assert.Single(component.FindAll("th.rz-datepicker-week-number"));
|
||||
Assert.Equal(6, component.FindAll("td.rz-calendar-week-number").Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DatePicker_ShowCalendarWeekFalse_NoAdditionalColumn()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameter =>
|
||||
{
|
||||
parameter.Add(p => p.ShowCalendarWeek, false);
|
||||
});
|
||||
|
||||
Assert.DoesNotContain(@$"rz-calendar-week-number", component.Markup);
|
||||
Assert.Equal(7, component.FindAll(".rz-calendar-view th").Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DatePicker_ShowCalendarWeekWithCustomTitle_TitleCorrectlyRendered()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenDatePicker<DateTime>>(parameter =>
|
||||
{
|
||||
parameter.Add(p => p.ShowCalendarWeek, true);
|
||||
parameter.Add(p => p.CalendarWeekTitle, "Wk");
|
||||
});
|
||||
|
||||
var weekNumberHeader = component.Find(".rz-calendar-view th.rz-datepicker-week-number");
|
||||
Assert.Contains("Wk", weekNumberHeader.InnerHtml);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
58
Radzen.Blazor.Tests/Dollars.cs
Normal file
58
Radzen.Blazor.Tests/Dollars.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Radzen.Blazor.Tests;
|
||||
|
||||
[TypeConverter(typeof(DollarsTypeConverter))]
|
||||
public readonly record struct Dollars(decimal Amount) : IComparable<decimal>
|
||||
{
|
||||
public int CompareTo(decimal other)
|
||||
{
|
||||
return Amount.CompareTo(other);
|
||||
}
|
||||
|
||||
public string ToString(string format, CultureInfo culture = null) => Amount.ToString(format, culture ?? CultureInfo.CreateSpecificCulture("en-US"));
|
||||
public override string ToString() => Amount.ToString("F2", CultureInfo.CreateSpecificCulture("en-US"));
|
||||
}
|
||||
|
||||
public class DollarsTypeConverter : TypeConverter
|
||||
{
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
|
||||
{
|
||||
if (sourceType == typeof(decimal) ||
|
||||
sourceType == typeof(string))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(decimal))
|
||||
return true;
|
||||
|
||||
return base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||
{
|
||||
if (value is decimal d)
|
||||
return new Dollars(d);
|
||||
|
||||
if (value is string s)
|
||||
return decimal.TryParse(s, culture, out var val) ? new Dollars(val) : null;
|
||||
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
}
|
||||
|
||||
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(decimal) && value is Dollars d)
|
||||
return d.Amount;
|
||||
|
||||
return base.ConvertTo(context, culture, value, destinationType);
|
||||
}
|
||||
}
|
||||
357
Radzen.Blazor.Tests/DropDownTests.cs
Normal file
357
Radzen.Blazor.Tests/DropDownTests.cs
Normal file
@@ -0,0 +1,357 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using AngleSharp.Dom;
|
||||
using Bunit;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Tests
|
||||
{
|
||||
public class DropDownTests
|
||||
{
|
||||
class DataItem
|
||||
{
|
||||
public string Text { get; set; }
|
||||
public int Id { get; set; }
|
||||
public bool Disabled { get; set; } = false;
|
||||
}
|
||||
|
||||
private static IRenderedComponent<RadzenDropDown<T>> DropDown<T>(TestContext ctx, Action<ComponentParameterCollectionBuilder<RadzenDropDown<T>>> configure = null)
|
||||
{
|
||||
var data = new[] {
|
||||
new DataItem { Text = "Item 1", Id = 1 },
|
||||
new DataItem { Text = "Item 2", Id = 2 },
|
||||
};
|
||||
|
||||
var component = ctx.RenderComponent<RadzenDropDown<T>>();
|
||||
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add(p => p.Data, data);
|
||||
parameters.Add(p => p.TextProperty, nameof(DataItem.Text));
|
||||
|
||||
if (configure != null)
|
||||
{
|
||||
configure.Invoke(parameters);
|
||||
}
|
||||
else
|
||||
{
|
||||
parameters.Add(p => p.ValueProperty, nameof(DataItem.Id));
|
||||
}
|
||||
});
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Dropdown_SelectItem_Method_Should_Not_Throw()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
|
||||
var component = DropDown<int>(ctx);
|
||||
|
||||
var items = component.FindAll(".rz-dropdown-item");
|
||||
|
||||
Assert.Equal(2, items.Count);
|
||||
|
||||
//this throws
|
||||
await component.InvokeAsync(async () => await component.Instance.SelectItem(1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DropDown_RendersItems()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
|
||||
var component = DropDown<int>(ctx);
|
||||
|
||||
var items = component.FindAll(".rz-dropdown-item");
|
||||
|
||||
Assert.Equal(2, items.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DropDown_AppliesSelectionStyleForIntValue()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
|
||||
var component = DropDown<int>(ctx);
|
||||
|
||||
var items = component.FindAll(".rz-dropdown-item");
|
||||
|
||||
items[0].Click();
|
||||
|
||||
component.Render();
|
||||
|
||||
items = component.FindAll(".rz-dropdown-item");
|
||||
|
||||
Assert.Contains("rz-state-highlight", items[0].ClassList);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DropDown_AppliesSelectionStyleForStringValue()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
|
||||
var component = DropDown<string>(ctx, parameters =>
|
||||
{
|
||||
parameters.Add(p => p.ValueProperty, nameof(DataItem.Text));
|
||||
});
|
||||
|
||||
var items = component.FindAll(".rz-dropdown-item");
|
||||
|
||||
items[0].Click();
|
||||
|
||||
component.Render();
|
||||
|
||||
items = component.FindAll(".rz-dropdown-item");
|
||||
|
||||
Assert.Contains("rz-state-highlight", items[0].ClassList);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DropDown_Respects_ItemEqualityComparer()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
|
||||
List<DataItem> boundCollection = [new() { Text = "Item 2" }];
|
||||
|
||||
var component = DropDown<string>(ctx, parameters =>
|
||||
{
|
||||
parameters.Add(p => p.ItemComparer, new DataItemComparer());
|
||||
parameters.Add(p => p.Multiple, true);
|
||||
parameters.Add(p => p.Value, boundCollection);
|
||||
});
|
||||
|
||||
var selectedItems = component.FindAll(".rz-state-highlight");
|
||||
Assert.Equal(1, selectedItems.Count);
|
||||
Assert.Equal("Item 2", selectedItems[0].TextContent.Trim());
|
||||
|
||||
// select Item 1 in list
|
||||
var items = component.FindAll(".rz-multiselect-item");
|
||||
items[0].Click();
|
||||
component.Render();
|
||||
|
||||
selectedItems = component.FindAll(".rz-state-highlight");
|
||||
Assert.Equal(2, selectedItems.Count);
|
||||
Assert.Equal("Item 1", selectedItems[0].TextContent.Trim());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DropDown_AppliesSelectionStyleWhenMultipleSelectionIsEnabled()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
|
||||
var component = DropDown<string>(ctx, parameters =>
|
||||
{
|
||||
parameters.Add(p => p.ValueProperty, nameof(DataItem.Text));
|
||||
parameters.Add(p => p.Multiple, true);
|
||||
});
|
||||
|
||||
var items = component.FindAll(".rz-multiselect-item");
|
||||
|
||||
items[0].Click();
|
||||
|
||||
component.Render();
|
||||
|
||||
items = component.FindAll(".rz-multiselect-item");
|
||||
|
||||
items[1].Click();
|
||||
|
||||
component.Render();
|
||||
|
||||
var selectedItems = component.FindAll(".rz-state-highlight");
|
||||
|
||||
Assert.Equal(2, selectedItems.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DropDown_AppliesValueTemplateOnMultipleSelection()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
|
||||
var valueTemplateFragment = (RenderFragment<dynamic>)(_context =>
|
||||
builder =>
|
||||
{
|
||||
builder.AddContent(0, $"value: {_context.Text}");
|
||||
});
|
||||
|
||||
var component = DropDown<string>(ctx, parameters =>
|
||||
{
|
||||
parameters.Add(p => p.Multiple, true)
|
||||
.Add(p => p.ValueTemplate, valueTemplateFragment);
|
||||
});
|
||||
|
||||
var items = component.FindAll(".rz-multiselect-item");
|
||||
|
||||
items[0].Click();
|
||||
items[1].Click();
|
||||
|
||||
component.Render();
|
||||
|
||||
var selectedItems = component.Find(".rz-inputtext");
|
||||
|
||||
Assert.Contains("value: Item 1,value: Item 2", selectedItems.Text());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DropDown_AppliesValueTemplateWhenTepmlateDefined()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
|
||||
var valueTemplateFragment = (RenderFragment<dynamic>)(_context =>
|
||||
builder =>
|
||||
{
|
||||
builder.AddContent(0, $"value: {_context.Text}");
|
||||
});
|
||||
|
||||
var templateFragment = (RenderFragment<dynamic>)(_context =>
|
||||
builder =>
|
||||
{
|
||||
builder.AddContent(0, $"template: {_context.Text}");
|
||||
});
|
||||
|
||||
var component = DropDown<string>(ctx, parameters =>
|
||||
{
|
||||
parameters.Add(p => p.Multiple, true)
|
||||
.Add(p => p.ValueTemplate, valueTemplateFragment)
|
||||
.Add(p => p.Template, templateFragment);
|
||||
});
|
||||
|
||||
var items = component.FindAll(".rz-multiselect-item");
|
||||
|
||||
items[0].Click();
|
||||
items[1].Click();
|
||||
|
||||
component.Render();
|
||||
|
||||
var selectedItems = component.Find(".rz-inputtext");
|
||||
var itemsText = component.FindAll(".rz-multiselect-item-content");
|
||||
|
||||
Assert.Collection(itemsText, item => Assert.Contains("template: Item 1", item.Text()), item => Assert.Contains("template: Item 2", item.Text()));
|
||||
Assert.Contains("value: Item 1,value: Item 2", selectedItems.Text());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DropDown_AppliesValueTemplateOnMultipleSelectionChips()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
|
||||
var valueTemplateFragment = (RenderFragment<dynamic>)(_context =>
|
||||
builder =>
|
||||
{
|
||||
builder.AddContent(0, $"value: {_context.Text}");
|
||||
});
|
||||
|
||||
var component = DropDown<string>(ctx, parameters =>
|
||||
{
|
||||
parameters.Add(p => p.Multiple, true)
|
||||
.Add(p => p.ValueTemplate, valueTemplateFragment)
|
||||
.Add(p => p.Chips, true);
|
||||
});
|
||||
|
||||
var items = component.FindAll(".rz-multiselect-item");
|
||||
|
||||
items[0].Click();
|
||||
items[1].Click();
|
||||
|
||||
component.Render();
|
||||
|
||||
var selectedItems = component.FindAll(".rz-chip-text");
|
||||
|
||||
Assert.Collection(selectedItems, item => Assert.Contains("value: Item 1", item.Text()), item => Assert.Contains("value: Item 2", item.Text()));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false, true, false, true, "false")]
|
||||
[InlineData(true, false, true, false, "true")]
|
||||
[InlineData(true, false, false, false, "false")]
|
||||
[InlineData(true, false, false, true, "true")]
|
||||
[InlineData(false, false, false, true, "false")]
|
||||
public void DropDown_AllSelectedFalseIfListIsAllDisabled(bool item1Selected, bool item1Disabled, bool item2Selected, bool item2Disabled, string expectedAriaCheckedValue)
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
|
||||
var data = new[] {
|
||||
new DataItem { Text = "Item 1", Id = 1, Disabled = item1Disabled },
|
||||
new DataItem { Text = "Item 2", Id = 2, Disabled = item2Disabled },
|
||||
};
|
||||
|
||||
List<int> selectedValues = [];
|
||||
if (item1Selected)
|
||||
{
|
||||
selectedValues.Add(data[0].Id);
|
||||
}
|
||||
if (item2Selected)
|
||||
{
|
||||
selectedValues.Add(data[1].Id);
|
||||
}
|
||||
|
||||
var component = ctx.RenderComponent<RadzenDropDown<DataItem>>(parameters => parameters
|
||||
.Add(p => p.Data, data)
|
||||
.Add(p => p.Value, selectedValues)
|
||||
.Add(p => p.Multiple, true)
|
||||
.Add(p => p.AllowSelectAll, true)
|
||||
.Add(p => p.TextProperty, nameof(DataItem.Text))
|
||||
.Add(p => p.DisabledProperty, nameof(DataItem.Disabled))
|
||||
.Add(p => p.ValueProperty, nameof(DataItem.Id)));
|
||||
|
||||
Assert.NotNull(component);
|
||||
var highlightedItems = component.FindAll(".rz-state-highlight");
|
||||
Assert.Equal(selectedValues.Count, highlightedItems.Count);
|
||||
|
||||
|
||||
var selectAllCheckBox = component.Find(".rz-multiselect-header input[type='checkbox']");
|
||||
|
||||
Assert.Equal(expectedAriaCheckedValue, selectAllCheckBox.GetAttribute("aria-checked"));
|
||||
}
|
||||
|
||||
class DataItemComparer : IEqualityComparer<DataItem>, IEqualityComparer<object>
|
||||
{
|
||||
public bool Equals(DataItem x, DataItem y)
|
||||
{
|
||||
if (ReferenceEquals(x, y)) return true;
|
||||
if (x is null) return false;
|
||||
if (y is null) return false;
|
||||
if (x.GetType() != y.GetType()) return false;
|
||||
return x.Text == y.Text;
|
||||
}
|
||||
|
||||
public int GetHashCode(DataItem obj)
|
||||
{
|
||||
return obj.Text.GetHashCode();
|
||||
}
|
||||
|
||||
public new bool Equals(object x, object y)
|
||||
{
|
||||
return Equals((DataItem)x, (DataItem)y);
|
||||
}
|
||||
|
||||
public int GetHashCode(object obj)
|
||||
{
|
||||
return GetHashCode((DataItem)obj);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
423
Radzen.Blazor.Tests/ExpressionParserTests.cs
Normal file
423
Radzen.Blazor.Tests/ExpressionParserTests.cs
Normal file
@@ -0,0 +1,423 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Dynamic.Core;
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Tests
|
||||
{
|
||||
public enum CarType
|
||||
{
|
||||
Sedan,
|
||||
Coupe
|
||||
}
|
||||
|
||||
public class ExpressionParserTests
|
||||
{
|
||||
class Person
|
||||
{
|
||||
public short? Age { get; set; }
|
||||
public string Name { get; set; }
|
||||
public bool? Famous { get; set; }
|
||||
public DateTime BirthDate { get; set; }
|
||||
}
|
||||
|
||||
public class Car
|
||||
{
|
||||
public CarType Type { get; set; }
|
||||
}
|
||||
|
||||
public enum Status
|
||||
{
|
||||
Office,
|
||||
Remote,
|
||||
}
|
||||
class OrderDetail
|
||||
{
|
||||
public Product Product { get; set; }
|
||||
public int Quantity { get; set; }
|
||||
public Order Order { get; set; }
|
||||
|
||||
public List<WorkStatus> WorkStatuses { get; set; }
|
||||
public List<Status> Statuses { get; set; }
|
||||
}
|
||||
|
||||
class Category
|
||||
{
|
||||
public string CategoryName { get; set; }
|
||||
|
||||
}
|
||||
|
||||
class Order
|
||||
{
|
||||
public DateTime OrderDate { get; set; }
|
||||
}
|
||||
|
||||
class Product
|
||||
{
|
||||
public string ProductName { get; set; }
|
||||
|
||||
public Category Category { get; set; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_ParseBindaryExpression()
|
||||
{
|
||||
var expression = ExpressionParser.ParsePredicate<Person>("p => p.Name == \"foo\"");
|
||||
|
||||
var func = expression.Compile();
|
||||
|
||||
Assert.True(func(new Person() { Name = "foo" }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_ParseConditionalExpression()
|
||||
{
|
||||
var expression = ExpressionParser.ParsePredicate<OrderDetail>("it => (it.Product.ProductName == null ? \"\" : it.Product.ProductName).Contains(\"Queso\") && it.Quantity == 50");
|
||||
|
||||
var func = expression.Compile();
|
||||
|
||||
Assert.True(func(new OrderDetail() { Product = new Product { ProductName = "Queso" }, Quantity = 50 }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_ParseNestedLogicalOperations()
|
||||
{
|
||||
var expression = ExpressionParser.ParsePredicate<OrderDetail>("it => (it.Product.ProductName == null ? \"\" : it.Product.ProductName).Contains(\"Queso\") && (it.Quantity == 50 || it.Quantity == 12)");
|
||||
|
||||
var func = expression.Compile();
|
||||
|
||||
Assert.True(func(new OrderDetail() { Product = new Product { ProductName = "Queso" }, Quantity = 12 }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_SupportDateTimeParsing()
|
||||
{
|
||||
var expression = ExpressionParser.ParsePredicate<OrderDetail>("it => it.Order.OrderDate >= DateTime.Parse(\"2025-02-11\")");
|
||||
|
||||
var func = expression.Compile();
|
||||
Assert.True(func(new OrderDetail { Order = new Order { OrderDate = new DateTime(2025, 2, 11) } }));
|
||||
}
|
||||
|
||||
class ItemWithGenericProperty<T>
|
||||
{
|
||||
public T Value { get; set; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_SupportDateOnlyParsing()
|
||||
{
|
||||
var expression = ExpressionParser.ParsePredicate<ItemWithGenericProperty<DateOnly>>("it => it.Value >= DateOnly.Parse(\"2025-02-11\")");
|
||||
|
||||
var func = expression.Compile();
|
||||
|
||||
Assert.True(func(new ItemWithGenericProperty<DateOnly> { Value = new DateOnly(2025, 2, 11) }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_SupportTimeOnlyParsing()
|
||||
{
|
||||
var expression = ExpressionParser.ParsePredicate<ItemWithGenericProperty<TimeOnly>>("it => it.Value >= TimeOnly.Parse(\"12:00:00\")");
|
||||
var func = expression.Compile();
|
||||
Assert.True(func(new ItemWithGenericProperty<TimeOnly> { Value = new TimeOnly(12, 0, 0) }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_SupportGuidParsing()
|
||||
{
|
||||
var expression = ExpressionParser.ParsePredicate<ItemWithGenericProperty<Guid>>("it => it.Value == Guid.Parse(\"f0e7e7d8-4f4d-4b5f-8b3e-3f1d1b4f5f5f\")");
|
||||
var func = expression.Compile();
|
||||
Assert.True(func(new ItemWithGenericProperty<Guid> { Value = Guid.Parse("f0e7e7d8-4f4d-4b5f-8b3e-3f1d1b4f5f5f") }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_SupportDateTimeOffsetParsing()
|
||||
{
|
||||
var expression = ExpressionParser.ParsePredicate<ItemWithGenericProperty<DateTimeOffset>>("it => it.Value == DateTimeOffset.Parse(\"2025-02-11\")");
|
||||
var func = expression.Compile();
|
||||
Assert.True(func(new ItemWithGenericProperty<DateTimeOffset> { Value = DateTimeOffset.Parse("2025-02-11") }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_SupportEnumWithCasts()
|
||||
{
|
||||
var typeLocator = (string type) => AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()).FirstOrDefault(t => t.FullName == type);
|
||||
|
||||
var expression = ExpressionParser.ParsePredicate<ItemWithGenericProperty<CarType[]>>("it => it.Value.Any(i => (new []{0}).Contains(i))", typeLocator);
|
||||
|
||||
var func = expression.Compile();
|
||||
|
||||
Assert.True(func(new ItemWithGenericProperty<CarType[]> { Value = [CarType.Sedan] }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_SupportNullableCollection()
|
||||
{
|
||||
var expression = ExpressionParser.ParsePredicate<Person>("it => new bool?[]{ false }.Contains(it.Famous)");
|
||||
var func = expression.Compile();
|
||||
|
||||
Assert.True(func(new Person { Famous = false }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_SupportEnumCollections()
|
||||
{
|
||||
var typeLocator = (string type) => AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()).FirstOrDefault(t => t.FullName == type);
|
||||
|
||||
var expression = ExpressionParser.ParsePredicate<Car>($"it => it.Type == (Radzen.Blazor.Tests.CarType)1", typeLocator);
|
||||
|
||||
var func = expression.Compile();
|
||||
|
||||
Assert.True(func(new Car { Type = CarType.Coupe }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_SupportCollectionLiterals()
|
||||
{
|
||||
var expression = ExpressionParser.ParsePredicate<OrderDetail>("it => (new []{\"Tofu\"}).Contains(it.Product.ProductName)");
|
||||
var func = expression.Compile();
|
||||
|
||||
Assert.True(func(new OrderDetail { Product = new Product { ProductName = "Tofu" } }));
|
||||
}
|
||||
|
||||
class WorkStatus
|
||||
{
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void Should_SupportNestedLambdas()
|
||||
{
|
||||
var expression = ExpressionParser.ParsePredicate<OrderDetail>("it => it.WorkStatuses.Any(i => (new []{\"Office\"}).Contains(i.Name))");
|
||||
var func = expression.Compile();
|
||||
|
||||
Assert.True(func(new OrderDetail { WorkStatuses = [new() { Name = "Office" }] }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_SupportNestedLambdasWithNot()
|
||||
{
|
||||
var expression = ExpressionParser.ParsePredicate<OrderDetail>("it => it.WorkStatuses.Any(i => !(new []{\"Office\"}).Contains(i.Name))");
|
||||
var func = expression.Compile();
|
||||
|
||||
Assert.False(func(new OrderDetail { WorkStatuses = [new() { Name = "Office" }] }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_SupportNestedLambdasWithEnums()
|
||||
{
|
||||
var expression = ExpressionParser.ParsePredicate<OrderDetail>("it => it.Statuses.Any(i => (new []{1}).Contains(i))");
|
||||
var func = expression.Compile();
|
||||
|
||||
Assert.True(func(new OrderDetail { Statuses = new List<Status>() { (Status)1 } }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_SupportNestedLambdasWithComplexMethod()
|
||||
{
|
||||
var expression = ExpressionParser.ParsePredicate<OrderDetail>("it => new [] { (Status)1 }.Intersect(it.Statuses).Any()", type => typeof(Status));
|
||||
var func = expression.Compile();
|
||||
|
||||
Assert.True(func(new OrderDetail { Statuses = new List<Status>() { (Status)1 } }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_SupportToLower()
|
||||
{
|
||||
var expression = ExpressionParser.ParsePredicate<Person>("it => (it.Name == null ? \"\" : it.Name).ToLower().Contains(\"na\".ToLower())");
|
||||
|
||||
var func = expression.Compile();
|
||||
|
||||
Assert.True(func(new Person { Name = "Nana" }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_SupportNullableProperties()
|
||||
{
|
||||
var expression = ExpressionParser.ParsePredicate<Person>("it => it.Age == 50");
|
||||
|
||||
var func = expression.Compile();
|
||||
|
||||
Assert.True(func(new Person { Age = 50 }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_SupportNullablePropertiesWithArray()
|
||||
{
|
||||
var expression = ExpressionParser.ParsePredicate<Person>("it => (new []{}).Contains(it.Famous)");
|
||||
|
||||
var func = expression.Compile();
|
||||
|
||||
Assert.False(func(new Person { Famous = null }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_SupportDateTimeWithArray()
|
||||
{
|
||||
var expression = ExpressionParser.ParsePredicate<Person>("it => (new []{DateTime.Parse(\"5/5/2000 12:00:00 AM\")}).Contains(it.BirthDate)");
|
||||
|
||||
var func = expression.Compile();
|
||||
|
||||
Assert.True(func(new Person { BirthDate = DateTime.Parse("5/5/2000 12:00:00 AM") }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_SupportNumericConversion()
|
||||
{
|
||||
var expression = ExpressionParser.ParsePredicate<ItemWithGenericProperty<double>>("it => it.Value == 50");
|
||||
|
||||
var func = expression.Compile();
|
||||
|
||||
Assert.True(func(new ItemWithGenericProperty<double> { Value = 50.0 }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_SupportNullCoalescence()
|
||||
{
|
||||
var expression = ExpressionParser.ParsePredicate<ItemWithGenericProperty<double?>>("it => (it.Value ?? 0) == 0");
|
||||
|
||||
var func = expression.Compile();
|
||||
|
||||
Assert.True(func(new ItemWithGenericProperty<double?> { Value = null }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_SupportNullConditionAndCoalescence()
|
||||
{
|
||||
var expression = ExpressionParser.ParsePredicate<Person>("it => (((it == null) ? null : it.Name) ?? \"\").Contains(\"Da\")");
|
||||
|
||||
var func = expression.Compile();
|
||||
|
||||
Assert.True(func(new Person { Name = "Dali" }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_CreateProjection()
|
||||
{
|
||||
var expression = ExpressionParser.ParseLambda<OrderDetail>("it => new { ProductName = it.Product.ProductName}");
|
||||
|
||||
var func = expression.Compile();
|
||||
|
||||
var result = func.DynamicInvoke(new OrderDetail { Product = new Product { ProductName = "Queso" } });
|
||||
|
||||
var property = result.GetType().GetProperty("ProductName");
|
||||
|
||||
Assert.Equal(typeof(string), property.PropertyType);
|
||||
|
||||
Assert.Equal("Queso", property.GetValue(result));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_CreateProjectionFromNestedAccess()
|
||||
{
|
||||
var expression = ExpressionParser.ParseLambda<OrderDetail>("it => new { it.Product.Category.CategoryName }");
|
||||
|
||||
var func = expression.Compile();
|
||||
|
||||
var orderDetail = new OrderDetail { Product = new Product { Category = new Category { CategoryName = "Beverages" } } };
|
||||
|
||||
var x = new { orderDetail?.Product?.Category?.CategoryName };
|
||||
|
||||
var result = func.DynamicInvoke(orderDetail);
|
||||
|
||||
var property = result.GetType().GetProperty("CategoryName");
|
||||
|
||||
Assert.Equal(typeof(string), property.PropertyType);
|
||||
|
||||
Assert.Equal("Beverages", property.GetValue(result));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_CreateProjectionFromNestedConditionalAccess()
|
||||
{
|
||||
var expression = ExpressionParser.ParseLambda<OrderDetail>("it => new { it.Product?.Category?.CategoryName }");
|
||||
|
||||
var func = expression.Compile();
|
||||
|
||||
var orderDetail = new OrderDetail { Product = new Product { Category = new Category { CategoryName = "Beverages" } } };
|
||||
|
||||
var x = new { orderDetail?.Product?.Category?.CategoryName };
|
||||
|
||||
var result = func.DynamicInvoke(orderDetail);
|
||||
|
||||
var property = result.GetType().GetProperty("CategoryName");
|
||||
|
||||
Assert.Equal(typeof(string), property.PropertyType);
|
||||
|
||||
Assert.Equal("Beverages", property.GetValue(result));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_CreateProjectionFromConditionalAccess()
|
||||
{
|
||||
var expression = ExpressionParser.ParseLambda<OrderDetail>("it => new { ProductName = it.Product?.ProductName}");
|
||||
var func = expression.Compile();
|
||||
var result = func.DynamicInvoke(new OrderDetail { Product = null });
|
||||
|
||||
var property = result.GetType().GetProperty("ProductName");
|
||||
|
||||
Assert.Equal(typeof(string), property.PropertyType);
|
||||
Assert.Null(property.GetValue(result));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_CreateProjectionFromNestedConditionalAccessAndAssignment()
|
||||
{
|
||||
var expression = ExpressionParser.ParseLambda<OrderDetail>("it => new { CategoryName = it.Product?.Category?.CategoryName}");
|
||||
var func = expression.Compile();
|
||||
var result = func.DynamicInvoke(new OrderDetail { Product = null });
|
||||
|
||||
var property = result.GetType().GetProperty("CategoryName");
|
||||
|
||||
Assert.Equal(typeof(string), property.PropertyType);
|
||||
Assert.Null(property.GetValue(result));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_SelectByString()
|
||||
{
|
||||
var list = new List<OrderDetail> { new OrderDetail { Product = new Product { ProductName = "Chai" } } }.AsQueryable();
|
||||
|
||||
var result = DynamicExtensions.Select(list, "Product.ProductName as ProductName");
|
||||
|
||||
Assert.Equal("Chai", result.ElementType.GetProperty("ProductName").GetValue(result.FirstOrDefault()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_SelectByWithUntypedIQueryableString()
|
||||
{
|
||||
IQueryable list = new List<OrderDetail> { new OrderDetail { Product = new Product { ProductName = "Chai" } } }.AsQueryable();
|
||||
|
||||
var result = DynamicExtensions.Select(list, "Product.ProductName as ProductName");
|
||||
|
||||
Assert.Equal("Chai", result.ElementType.GetProperty("ProductName").GetValue(result.FirstOrDefault()));
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void Should_SupportDictionaryIndexAccess()
|
||||
{
|
||||
var expression = ExpressionParser.ParsePredicate<Dictionary<string, object>>("it => (int)it[\"foo\"] == 1");
|
||||
var func = expression.Compile();
|
||||
Assert.True(func(new Dictionary<string, object> { ["foo"] = 1 }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_SupportDictionaryIndexAccessWithNullableCast()
|
||||
{
|
||||
var expression = ExpressionParser.ParsePredicate<Dictionary<string, object>>("it => (Int32?)it[\"foo\"] == null");
|
||||
var func = expression.Compile();
|
||||
Assert.True(func(new Dictionary<string, object> { ["foo"] = null }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_SupportArrayIndexAccess()
|
||||
{
|
||||
var expression = ExpressionParser.ParsePredicate<ItemWithGenericProperty<int[]>>("it => it.Value[0] == 1");
|
||||
var func = expression.Compile();
|
||||
|
||||
Assert.True(func(new ItemWithGenericProperty<int[]> { Value = [1] }));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add(p => p.Icon, value));
|
||||
|
||||
Assert.Contains(@$"<i class=""rzi"">{value}</i>", component.Markup);
|
||||
Assert.Contains(@$"<i class=""notranslate rzi"">{value}</i>", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -104,11 +104,11 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add<bool>(p => p.AllowCollapse, true));
|
||||
|
||||
Assert.Contains(@"<span class=""rz-fieldset-toggler rzi rzi-w rzi-minus""></span>", component.Markup);
|
||||
Assert.Contains(@"<span class=""notranslate rz-fieldset-toggler rzi rzi-w rzi-minus""></span>", component.Markup);
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add<bool>(p => p.Collapsed, true));
|
||||
|
||||
Assert.Contains(@"<span class=""rz-fieldset-toggler rzi rzi-w rzi-plus""></span>", component.Markup);
|
||||
Assert.Contains(@"<span class=""notranslate rz-fieldset-toggler rzi rzi-w rzi-plus""></span>", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Radzen.Blazor.Tests
|
||||
component.SetParametersAndRender(parameters => parameters.Add(p => p.Icon, icon));
|
||||
|
||||
Assert.Contains(@$">{icon}</i>", component.Markup);
|
||||
Assert.Contains(@$"<i class=""rzi""", component.Markup);
|
||||
Assert.Contains(@$"class=""notranslate rzi""", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -58,6 +58,18 @@ namespace Radzen.Blazor.Tests
|
||||
Assert.Contains(@$"rzi-primary", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Icon_Renders_IconColor()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
var component = ctx.RenderComponent<RadzenIcon>();
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add(icon => icon.IconColor, Colors.Primary));
|
||||
|
||||
Assert.Contains(@$"color:", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Icon_NotRenders_IconStyleClass_WhenNull()
|
||||
{
|
||||
@@ -69,5 +81,17 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
Assert.DoesNotContain(@$"rzi-primary", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Icon_NotRenders_IconColor_WhenNull()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
var component = ctx.RenderComponent<RadzenIcon>();
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add(icon => icon.IconColor, null));
|
||||
|
||||
Assert.DoesNotContain(@$"color:", component.Markup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,10 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add(p => p.Text, text));
|
||||
|
||||
Assert.Contains(@$">{text}</span>", component.Markup);
|
||||
Assert.Contains(@$"class=""rz-link-text""", component.Markup);
|
||||
var textElement = component.Find(".rz-link-text");
|
||||
|
||||
Assert.NotNull(textElement);
|
||||
Assert.Equal(text, textElement.TextContent.Trim());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -45,7 +47,7 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add(p => p.Icon, icon));
|
||||
|
||||
Assert.Contains(@$"<i class=""rzi"">{icon}</i>", component.Markup);
|
||||
Assert.Contains(@$"<i class=""notranslate rzi"">{icon}</i>", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -76,6 +78,20 @@ namespace Radzen.Blazor.Tests
|
||||
Assert.Contains(@$"target=""{target}""", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Link_Renders_DisabledParameter()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
var component = ctx.RenderComponent<RadzenLink>();
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add(p => p.Disabled, true));
|
||||
|
||||
Assert.Contains("class=\"rz-link rz-link-disabled active\"", component.Markup);
|
||||
|
||||
Assert.DoesNotContain("href=", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Icon_Renders_UnmatchedParameter()
|
||||
{
|
||||
|
||||
@@ -26,7 +26,12 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
var component = ctx.RenderComponent<RadzenLogin>();
|
||||
|
||||
Assert.Contains(@$"<label class=""rz-label"" for=""username"">Username</label>", component.Markup);
|
||||
component.SetParametersAndRender(p => {
|
||||
p.AddUnmatched("id", "login");
|
||||
});
|
||||
|
||||
var label = component.Find($@"label[for=""login-username""]");
|
||||
Assert.NotNull(label);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -63,7 +68,7 @@ namespace Radzen.Blazor.Tests
|
||||
component.SetParametersAndRender(parameters => {
|
||||
parameters.Add(p => p.Username, "user");
|
||||
parameters.Add(p => p.Password, "pwd");
|
||||
parameters.Add(p => p.Login, args => { clicked = true; });
|
||||
parameters.Add(p => p.Login, args => { clicked = true; });
|
||||
});
|
||||
|
||||
component.Find("button").Click();
|
||||
|
||||
314
Radzen.Blazor.Tests/Markdown/BlockQuoteTests.cs
Normal file
314
Radzen.Blazor.Tests/Markdown/BlockQuoteTests.cs
Normal file
@@ -0,0 +1,314 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Markdown.Tests;
|
||||
|
||||
public class BlockQuoteTests
|
||||
{
|
||||
private static string ToXml(string markdown)
|
||||
{
|
||||
var document = MarkdownParser.Parse(markdown);
|
||||
|
||||
return XmlVisitor.ToXml(document);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_BasicBlockQuote()
|
||||
{
|
||||
|
||||
Assert.Equal(@"<document>
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
</document>", ToXml(@"> foo"));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"> # Foo
|
||||
> bar
|
||||
> baz", @"<document>
|
||||
<block_quote>
|
||||
<heading level=""1"">
|
||||
<text>Foo</text>
|
||||
</heading>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
<softbreak />
|
||||
<text>baz</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
</document>")]
|
||||
[InlineData(@"># Foo
|
||||
>bar
|
||||
> baz", @"<document>
|
||||
<block_quote>
|
||||
<heading level=""1"">
|
||||
<text>Foo</text>
|
||||
</heading>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
<softbreak />
|
||||
<text>baz</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
</document>")]
|
||||
[InlineData(@" > # Foo
|
||||
> bar
|
||||
> baz", @"<document>
|
||||
<block_quote>
|
||||
<heading level=""1"">
|
||||
<text>Foo</text>
|
||||
</heading>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
<softbreak />
|
||||
<text>baz</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
</document>")]
|
||||
[InlineData(@" > # Foo
|
||||
> bar
|
||||
> baz", @"<document>
|
||||
<code_block>> # Foo
|
||||
> bar
|
||||
> baz
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@"> # Foo
|
||||
> bar
|
||||
baz", @"<document>
|
||||
<block_quote>
|
||||
<heading level=""1"">
|
||||
<text>Foo</text>
|
||||
</heading>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
<softbreak />
|
||||
<text>baz</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
</document>")]
|
||||
[InlineData(@"> bar
|
||||
baz
|
||||
> foo", @"<document>
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
<softbreak />
|
||||
<text>baz</text>
|
||||
<softbreak />
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
</document>")]
|
||||
[InlineData(@"> foo
|
||||
---", @"<document>
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
<thematic_break />
|
||||
</document>")]
|
||||
[InlineData(@"> - foo
|
||||
- bar", @"<document>
|
||||
<block_quote>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</block_quote>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"> foo
|
||||
bar", @"<document>
|
||||
<block_quote>
|
||||
<code_block>foo
|
||||
</code_block>
|
||||
</block_quote>
|
||||
<code_block>bar
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@"> ```
|
||||
foo
|
||||
```", @"<document>
|
||||
<block_quote>
|
||||
<code_block></code_block>
|
||||
</block_quote>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
<code_block></code_block>
|
||||
</document>")]
|
||||
[InlineData(@"> foo
|
||||
- bar", @"<document>
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
<softbreak />
|
||||
<text>- bar</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
</document>")]
|
||||
[InlineData(@">", @"<document>
|
||||
<block_quote />
|
||||
</document>")]
|
||||
[InlineData(@">
|
||||
>
|
||||
> ", @"<document>
|
||||
<block_quote />
|
||||
</document>")]
|
||||
[InlineData(@">
|
||||
> foo
|
||||
> ", @"<document>
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
</document>")]
|
||||
[InlineData(@"> foo
|
||||
|
||||
> bar", @"<document>
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
</document>")]
|
||||
[InlineData(@"> foo
|
||||
>
|
||||
> bar", @"<document>
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
</document>")]
|
||||
[InlineData(@"foo
|
||||
> bar", @"<document>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
</document>")]
|
||||
[InlineData(@"> aaa
|
||||
***
|
||||
> bbb", @"<document>
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
<text>aaa</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
<thematic_break />
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
<text>bbb</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
</document>")]
|
||||
[InlineData(@"> bar
|
||||
baz", @"<document>
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
<softbreak />
|
||||
<text>baz</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
</document>")]
|
||||
[InlineData(@"> bar
|
||||
|
||||
baz", @"<document>
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
<paragraph>
|
||||
<text>baz</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"> bar
|
||||
>
|
||||
baz", @"<document>
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
<paragraph>
|
||||
<text>baz</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"> > > foo
|
||||
bar", @"<document>
|
||||
<block_quote>
|
||||
<block_quote>
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
<softbreak />
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
</block_quote>
|
||||
</block_quote>
|
||||
</document>")]
|
||||
[InlineData(@">>> foo
|
||||
> bar
|
||||
>>baz", @"<document>
|
||||
<block_quote>
|
||||
<block_quote>
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
<softbreak />
|
||||
<text>bar</text>
|
||||
<softbreak />
|
||||
<text>baz</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
</block_quote>
|
||||
</block_quote>
|
||||
</document>")]
|
||||
[InlineData(@"> code
|
||||
|
||||
> not code", @"<document>
|
||||
<block_quote>
|
||||
<code_block>code
|
||||
</code_block>
|
||||
</block_quote>
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
<text>not code</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
</document>")]
|
||||
public void Parse_BlockQuote(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
}
|
||||
175
Radzen.Blazor.Tests/Markdown/CodeTests.cs
Normal file
175
Radzen.Blazor.Tests/Markdown/CodeTests.cs
Normal file
@@ -0,0 +1,175 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Markdown.Tests;
|
||||
|
||||
public class CodeTests
|
||||
{
|
||||
private static string ToXml(string markdown)
|
||||
{
|
||||
var document = MarkdownParser.Parse(markdown);
|
||||
|
||||
return XmlVisitor.ToXml(document);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("`foo`",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<code>foo</code>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("`` foo ` bar ``",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<code>foo ` bar</code>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("` `` `",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<code>``</code>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
public void Parse_BasicCode_ReturnsCodeNode(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("` `` `",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<code> `` </code>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("` a`",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<code> a</code>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"` `
|
||||
` `",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<code> </code>
|
||||
<softbreak />
|
||||
<code> </code>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
public void Parse_CodeWithSpaces_PreservesSpaces(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"``
|
||||
foo
|
||||
bar
|
||||
baz
|
||||
``",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<code>foo bar baz</code>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"``
|
||||
foo
|
||||
``",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<code>foo </code>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"`foo bar
|
||||
baz`",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<code>foo bar baz</code>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
|
||||
public void Parse_CodeWithLineBreaks_ConvertsToSpace(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("`foo\\`bar`", @"<document>
|
||||
<paragraph>
|
||||
<code>foo\</code>
|
||||
<text>bar</text>
|
||||
<text>`</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("``foo`bar``", @"<document>
|
||||
<paragraph>
|
||||
<code>foo`bar</code>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("` foo `` bar `", @"<document>
|
||||
<paragraph>
|
||||
<code>foo `` bar</code>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
public void Parse_CodeWithBacktics(string mardown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(mardown));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("*foo`*`", @"<document>
|
||||
<paragraph>
|
||||
<text>*</text>
|
||||
<text>foo</text>
|
||||
<code>*</code>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("[not a `link](/foo`)",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<text>[</text>
|
||||
<text>not a </text>
|
||||
<code>link](/foo</code>
|
||||
<text>)</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("`<https://foo.bar.`baz>`",@"<document>
|
||||
<paragraph>
|
||||
<code><https://foo.bar.</code>
|
||||
<text>baz></text>
|
||||
<text>`</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
public void Parse_CodePrecedence(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("```foo``", @"<document>
|
||||
<paragraph>
|
||||
<text>```</text>
|
||||
<text>foo</text>
|
||||
<text>``</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("`foo", @"<document>
|
||||
<paragraph>
|
||||
<text>`</text>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("`foo``bar``", @"<document>
|
||||
<paragraph>
|
||||
<text>`</text>
|
||||
<text>foo</text>
|
||||
<code>bar</code>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
public void Parse_UnmatchingBacktics(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
}
|
||||
261
Radzen.Blazor.Tests/Markdown/EmphasisTests.cs
Normal file
261
Radzen.Blazor.Tests/Markdown/EmphasisTests.cs
Normal file
@@ -0,0 +1,261 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Markdown.Tests;
|
||||
|
||||
public class EmphasisTests
|
||||
{
|
||||
private static string ToXml(string markdown)
|
||||
{
|
||||
var document = MarkdownParser.Parse(markdown);
|
||||
return XmlVisitor.ToXml(document);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("*foo bar*",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<emph>
|
||||
<text>foo bar</text>
|
||||
</emph>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("a * foo bar*",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<text>a </text>
|
||||
<text>*</text>
|
||||
<text> foo bar</text>
|
||||
<text>*</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("a*\"foo\"*",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<text>a</text>
|
||||
<text>*</text>
|
||||
<text>""foo""</text>
|
||||
<text>*</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("* a *",
|
||||
@"<document>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>a </text>
|
||||
<text>*</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData("foo*bar*",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
<emph>
|
||||
<text>bar</text>
|
||||
</emph>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("5*6*78",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<text>5</text>
|
||||
<emph>
|
||||
<text>6</text>
|
||||
</emph>
|
||||
<text>78</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("_foo bar_",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<emph>
|
||||
<text>foo bar</text>
|
||||
</emph>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("_ foo bar_",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<text>_</text>
|
||||
<text> foo bar</text>
|
||||
<text>_</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("a_\"foo\"_",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<text>a</text>
|
||||
<text>_</text>
|
||||
<text>""foo""</text>
|
||||
<text>_</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("foo_bar_",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
<text>_</text>
|
||||
<text>bar</text>
|
||||
<text>_</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("5_6_78",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<text>5</text>
|
||||
<text>_</text>
|
||||
<text>6</text>
|
||||
<text>_</text>
|
||||
<text>78</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("пристаням_стремятся_",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<text>пристаням</text>
|
||||
<text>_</text>
|
||||
<text>стремятся</text>
|
||||
<text>_</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("aa_\"bb\"_cc",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<text>aa</text>
|
||||
<text>_</text>
|
||||
<text>""bb""</text>
|
||||
<text>_</text>
|
||||
<text>cc</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("foo-_(bar)_",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<text>foo-</text>
|
||||
<emph>
|
||||
<text>(bar)</text>
|
||||
</emph>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("_foo*",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<text>_</text>
|
||||
<text>foo</text>
|
||||
<text>*</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("*foo bar *",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<text>*</text>
|
||||
<text>foo bar </text>
|
||||
<text>*</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("*foo bar\nbaz*",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<emph>
|
||||
<text>foo bar</text>
|
||||
<softbreak />
|
||||
<text>baz</text>
|
||||
</emph>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("*(*foo)",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<text>*</text>
|
||||
<text>(</text>
|
||||
<text>*</text>
|
||||
<text>foo)</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("*(*foo*)*",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<emph>
|
||||
<text>(</text>
|
||||
<emph>
|
||||
<text>foo</text>
|
||||
</emph>
|
||||
<text>)</text>
|
||||
</emph>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("*foo*bar",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<emph>
|
||||
<text>foo</text>
|
||||
</emph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("_foo bar _",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<text>_</text>
|
||||
<text>foo bar </text>
|
||||
<text>_</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("_(_foo_)_",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<emph>
|
||||
<text>(</text>
|
||||
<emph>
|
||||
<text>foo</text>
|
||||
</emph>
|
||||
<text>)</text>
|
||||
</emph>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("_foo_bar",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<text>_</text>
|
||||
<text>foo</text>
|
||||
<text>_</text>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("_пристаням_стремятся",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<text>_</text>
|
||||
<text>пристаням</text>
|
||||
<text>_</text>
|
||||
<text>стремятся</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("_foo_bar_baz_",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<emph>
|
||||
<text>foo</text>
|
||||
<text>_</text>
|
||||
<text>bar</text>
|
||||
<text>_</text>
|
||||
<text>baz</text>
|
||||
</emph>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("_(bar)_.",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<emph>
|
||||
<text>(bar)</text>
|
||||
</emph>
|
||||
<text>.</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
public void Parse_EmphasisRules_AdheresToCommonMarkSpec(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
}
|
||||
264
Radzen.Blazor.Tests/Markdown/FencedCodeBlockTests.cs
Normal file
264
Radzen.Blazor.Tests/Markdown/FencedCodeBlockTests.cs
Normal file
@@ -0,0 +1,264 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Markdown.Tests;
|
||||
|
||||
public class FencedCodeBlockTests
|
||||
{
|
||||
private static string ToXml(string markdown)
|
||||
{
|
||||
var document = MarkdownParser.Parse(markdown);
|
||||
|
||||
return XmlVisitor.ToXml(document);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_BasicFencedCodeBlock()
|
||||
{
|
||||
Assert.Equal(@"<document>
|
||||
<code_block>foo
|
||||
</code_block>
|
||||
</document>", ToXml(@"```
|
||||
foo
|
||||
```"));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"```
|
||||
<
|
||||
>
|
||||
```", @"<document>
|
||||
<code_block><
|
||||
>
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@"~~~
|
||||
<
|
||||
>
|
||||
~~~", @"<document>
|
||||
<code_block><
|
||||
>
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@"``
|
||||
foo
|
||||
``", @"<document>
|
||||
<paragraph>
|
||||
<code>foo</code>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"```
|
||||
aaa
|
||||
~~~
|
||||
```", @"<document>
|
||||
<code_block>aaa
|
||||
~~~
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@"~~~
|
||||
aaa
|
||||
```
|
||||
~~~", @"<document>
|
||||
<code_block>aaa
|
||||
```
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@"````
|
||||
aaa
|
||||
```
|
||||
``````", @"<document>
|
||||
<code_block>aaa
|
||||
```
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@"~~~~
|
||||
aaa
|
||||
~~~
|
||||
~~~~", @"<document>
|
||||
<code_block>aaa
|
||||
~~~
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@"```", @"<document>
|
||||
<code_block></code_block>
|
||||
</document>")]
|
||||
[InlineData(@"`````
|
||||
|
||||
```
|
||||
aaa", @"<document>
|
||||
<code_block>
|
||||
```
|
||||
aaa
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@"> ```
|
||||
> aaa
|
||||
|
||||
bbb", @"<document>
|
||||
<block_quote>
|
||||
<code_block>aaa
|
||||
</code_block>
|
||||
</block_quote>
|
||||
<paragraph>
|
||||
<text>bbb</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"```
|
||||
|
||||
|
||||
```", @"<document>
|
||||
<code_block>
|
||||
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@"```
|
||||
```", @"<document>
|
||||
<code_block></code_block>
|
||||
</document>")]
|
||||
[InlineData(@" ```
|
||||
aaa
|
||||
aaa
|
||||
```", @"<document>
|
||||
<code_block>aaa
|
||||
aaa
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@" ```
|
||||
aaa
|
||||
aaa
|
||||
aaa
|
||||
```", @"<document>
|
||||
<code_block>aaa
|
||||
aaa
|
||||
aaa
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@" ```
|
||||
aaa
|
||||
aaa
|
||||
aaa
|
||||
```", @"<document>
|
||||
<code_block>aaa
|
||||
aaa
|
||||
aaa
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@" ```
|
||||
aaa
|
||||
```", @"<document>
|
||||
<code_block>```
|
||||
aaa
|
||||
```
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@"```
|
||||
aaa
|
||||
```", @"<document>
|
||||
<code_block>aaa
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@" ```
|
||||
aaa
|
||||
````", @"<document>
|
||||
<code_block>aaa
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@"```
|
||||
aaa
|
||||
```", @"<document>
|
||||
<code_block>aaa
|
||||
```
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@"``` ```
|
||||
aaa", @"<document>
|
||||
<paragraph>
|
||||
<code> </code>
|
||||
<softbreak />
|
||||
<text>aaa</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"~~~~~~
|
||||
aaa
|
||||
~~~ ~~", @"<document>
|
||||
<code_block>aaa
|
||||
~~~ ~~
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@"foo
|
||||
```
|
||||
bar
|
||||
```
|
||||
baz", @"<document>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
<code_block>bar
|
||||
</code_block>
|
||||
<paragraph>
|
||||
<text>baz</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"foo
|
||||
---
|
||||
~~~
|
||||
bar
|
||||
~~~
|
||||
# baz", @"<document>
|
||||
<heading level=""2"">
|
||||
<text>foo</text>
|
||||
</heading>
|
||||
<code_block>bar
|
||||
</code_block>
|
||||
<heading level=""1"">
|
||||
<text>baz</text>
|
||||
</heading>
|
||||
</document>")]
|
||||
[InlineData(@"```ruby
|
||||
def foo(x)
|
||||
return 3
|
||||
end
|
||||
```", @"<document>
|
||||
<code_block info=""ruby"">def foo(x)
|
||||
return 3
|
||||
end
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@"~~~~ ruby startline=3 $%@#$
|
||||
def foo(x)
|
||||
return 3
|
||||
end
|
||||
~~~~~~~", @"<document>
|
||||
<code_block info=""ruby startline=3 $%@#$"">def foo(x)
|
||||
return 3
|
||||
end
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@"````;
|
||||
````", @"<document>
|
||||
<code_block info="";""></code_block>
|
||||
</document>")]
|
||||
[InlineData(@"``` aa ```
|
||||
foo", @"<document>
|
||||
<paragraph>
|
||||
<code>aa</code>
|
||||
<softbreak />
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"~~~ aa ``` ~~~
|
||||
foo
|
||||
~~~", @"<document>
|
||||
<code_block info=""aa ``` ~~~"">foo
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@"```
|
||||
``` aaa
|
||||
```", @"<document>
|
||||
<code_block>``` aaa
|
||||
</code_block>
|
||||
</document>")]
|
||||
public void Parse_FencedCodeBlock(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
}
|
||||
132
Radzen.Blazor.Tests/Markdown/HardLineBreakTests.cs
Normal file
132
Radzen.Blazor.Tests/Markdown/HardLineBreakTests.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Markdown.Tests;
|
||||
|
||||
public class HardLineBreakTests
|
||||
{
|
||||
private static string ToXml(string markdown)
|
||||
{
|
||||
var document = MarkdownParser.Parse(markdown);
|
||||
|
||||
return XmlVisitor.ToXml(document);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"foo
|
||||
baz", @"<document>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
<linebreak />
|
||||
<text>baz</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("foo \r\nbaz", @"<document>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
<linebreak />
|
||||
<text>baz</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"foo\
|
||||
baz", @"<document>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
<linebreak />
|
||||
<text>baz</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"foo
|
||||
baz", @"<document>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
<linebreak />
|
||||
<text>baz</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"foo
|
||||
bar", @"<document>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
<linebreak />
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"foo\
|
||||
bar", @"<document>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
<linebreak />
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"*foo
|
||||
bar*", @"<document>
|
||||
<paragraph>
|
||||
<emph>
|
||||
<text>foo</text>
|
||||
<linebreak />
|
||||
<text>bar</text>
|
||||
</emph>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"*foo\
|
||||
bar*", @"<document>
|
||||
<paragraph>
|
||||
<emph>
|
||||
<text>foo</text>
|
||||
<linebreak />
|
||||
<text>bar</text>
|
||||
</emph>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"`code\
|
||||
span`", @"<document>
|
||||
<paragraph>
|
||||
<code>code\ span</code>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"`code
|
||||
span`", @"<document>
|
||||
<paragraph>
|
||||
<code>code span</code>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"<a href=""foo
|
||||
bar"">", @"<document>
|
||||
<paragraph>
|
||||
<html_inline><a href=""foo
|
||||
bar""></html_inline>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"<a href=""foo\
|
||||
bar"">", @"<document>
|
||||
<paragraph>
|
||||
<html_inline><a href=""foo\
|
||||
bar""></html_inline>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"foo\", @"<document>
|
||||
<paragraph>
|
||||
<text>foo\</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"foo ", @"<document>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"### foo\", @"<document>
|
||||
<heading level=""3"">
|
||||
<text>foo\</text>
|
||||
</heading>
|
||||
</document>")]
|
||||
[InlineData(@"### foo ", @"<document>
|
||||
<heading level=""3"">
|
||||
<text>foo</text>
|
||||
</heading>
|
||||
</document>")]
|
||||
public void Parse_HardLineBreak(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
}
|
||||
511
Radzen.Blazor.Tests/Markdown/HeadingTests.cs
Normal file
511
Radzen.Blazor.Tests/Markdown/HeadingTests.cs
Normal file
@@ -0,0 +1,511 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Markdown.Tests;
|
||||
|
||||
public class HeadingTests
|
||||
{
|
||||
private static string ToXml(string markdown)
|
||||
{
|
||||
var document = MarkdownParser.Parse(markdown);
|
||||
|
||||
return XmlVisitor.ToXml(document);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_BasicAtxHeading()
|
||||
{
|
||||
Assert.Equal(@"<document>
|
||||
<heading level=""1"">
|
||||
<text>foo</text>
|
||||
</heading>
|
||||
</document>", ToXml("# foo"));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"# foo
|
||||
## foo
|
||||
### foo
|
||||
#### foo
|
||||
##### foo
|
||||
###### foo", @"<document>
|
||||
<heading level=""1"">
|
||||
<text>foo</text>
|
||||
</heading>
|
||||
<heading level=""2"">
|
||||
<text>foo</text>
|
||||
</heading>
|
||||
<heading level=""3"">
|
||||
<text>foo</text>
|
||||
</heading>
|
||||
<heading level=""4"">
|
||||
<text>foo</text>
|
||||
</heading>
|
||||
<heading level=""5"">
|
||||
<text>foo</text>
|
||||
</heading>
|
||||
<heading level=""6"">
|
||||
<text>foo</text>
|
||||
</heading>
|
||||
</document>")]
|
||||
[InlineData(@"####### foo", @"<document>
|
||||
<paragraph>
|
||||
<text>####### foo</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"#5 bolt
|
||||
|
||||
#hashtag", @"<document>
|
||||
<paragraph>
|
||||
<text>#5 bolt</text>
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
<text>#hashtag</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"\## foo", @"<document>
|
||||
<paragraph>
|
||||
<text>#</text>
|
||||
<text># foo</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"# foo *bar* \*baz\*", @"<document>
|
||||
<heading level=""1"">
|
||||
<text>foo </text>
|
||||
<emph>
|
||||
<text>bar</text>
|
||||
</emph>
|
||||
<text> </text>
|
||||
<text>*</text>
|
||||
<text>baz</text>
|
||||
<text>*</text>
|
||||
</heading>
|
||||
</document>")]
|
||||
[InlineData(@"# foo ", @"<document>
|
||||
<heading level=""1"">
|
||||
<text>foo</text>
|
||||
</heading>
|
||||
</document>")]
|
||||
[InlineData(@" ### foo
|
||||
## foo
|
||||
# foo", @"<document>
|
||||
<heading level=""3"">
|
||||
<text>foo</text>
|
||||
</heading>
|
||||
<heading level=""2"">
|
||||
<text>foo</text>
|
||||
</heading>
|
||||
<heading level=""1"">
|
||||
<text>foo</text>
|
||||
</heading>
|
||||
</document>")]
|
||||
[InlineData(@" # foo", @"<document>
|
||||
<code_block># foo
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@"foo
|
||||
# bar", @"<document>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
<softbreak />
|
||||
<text># bar</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"## foo ##
|
||||
### bar ###", @"<document>
|
||||
<heading level=""2"">
|
||||
<text>foo</text>
|
||||
</heading>
|
||||
<heading level=""3"">
|
||||
<text>bar</text>
|
||||
</heading>
|
||||
</document>")]
|
||||
[InlineData(@"# foo ##################################
|
||||
##### foo ##", @"<document>
|
||||
<heading level=""1"">
|
||||
<text>foo</text>
|
||||
</heading>
|
||||
<heading level=""5"">
|
||||
<text>foo</text>
|
||||
</heading>
|
||||
</document>")]
|
||||
[InlineData(@"### foo ### ", @"<document>
|
||||
<heading level=""3"">
|
||||
<text>foo</text>
|
||||
</heading>
|
||||
</document>")]
|
||||
[InlineData(@"### foo ### b", @"<document>
|
||||
<heading level=""3"">
|
||||
<text>foo ### b</text>
|
||||
</heading>
|
||||
</document>")]
|
||||
[InlineData(@"# foo#", @"<document>
|
||||
<heading level=""1"">
|
||||
<text>foo#</text>
|
||||
</heading>
|
||||
</document>")]
|
||||
[InlineData(@"### foo \###
|
||||
## foo #\##
|
||||
# foo \#", @"<document>
|
||||
<heading level=""3"">
|
||||
<text>foo </text>
|
||||
<text>#</text>
|
||||
<text>##</text>
|
||||
</heading>
|
||||
<heading level=""2"">
|
||||
<text>foo #</text>
|
||||
<text>#</text>
|
||||
<text>#</text>
|
||||
</heading>
|
||||
<heading level=""1"">
|
||||
<text>foo </text>
|
||||
<text>#</text>
|
||||
</heading>
|
||||
</document>")]
|
||||
[InlineData(@"****
|
||||
## foo
|
||||
****", @"<document>
|
||||
<thematic_break />
|
||||
<heading level=""2"">
|
||||
<text>foo</text>
|
||||
</heading>
|
||||
<thematic_break />
|
||||
</document>")]
|
||||
[InlineData(@"Foo bar
|
||||
# baz
|
||||
Bar foo", @"<document>
|
||||
<paragraph>
|
||||
<text>Foo bar</text>
|
||||
</paragraph>
|
||||
<heading level=""1"">
|
||||
<text>baz</text>
|
||||
</heading>
|
||||
<paragraph>
|
||||
<text>Bar foo</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"##
|
||||
#
|
||||
### ###", @"<document>
|
||||
<heading level=""2"" />
|
||||
<heading level=""1"" />
|
||||
<heading level=""3"" />
|
||||
</document>")]
|
||||
public void Parse_AtxHeading(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"Foo *bar*
|
||||
=========
|
||||
|
||||
Foo *baz*
|
||||
---------", @"<document>
|
||||
<heading level=""1"">
|
||||
<text>Foo </text>
|
||||
<emph>
|
||||
<text>bar</text>
|
||||
</emph>
|
||||
</heading>
|
||||
<heading level=""2"">
|
||||
<text>Foo </text>
|
||||
<emph>
|
||||
<text>baz</text>
|
||||
</emph>
|
||||
</heading>
|
||||
</document>")]
|
||||
[InlineData(@"Foo *bar
|
||||
baz*
|
||||
====", @"<document>
|
||||
<heading level=""1"">
|
||||
<text>Foo </text>
|
||||
<emph>
|
||||
<text>bar</text>
|
||||
<softbreak />
|
||||
<text>baz</text>
|
||||
</emph>
|
||||
</heading>
|
||||
</document>")]
|
||||
[InlineData(@" Foo *bar
|
||||
baz*
|
||||
====", @"<document>
|
||||
<heading level=""1"">
|
||||
<text>Foo </text>
|
||||
<emph>
|
||||
<text>bar</text>
|
||||
<softbreak />
|
||||
<text>baz</text>
|
||||
</emph>
|
||||
</heading>
|
||||
</document>")]
|
||||
[InlineData(@"Foo
|
||||
-------------------------
|
||||
|
||||
Foo
|
||||
=", @"<document>
|
||||
<heading level=""2"">
|
||||
<text>Foo</text>
|
||||
</heading>
|
||||
<heading level=""1"">
|
||||
<text>Foo</text>
|
||||
</heading>
|
||||
</document>")]
|
||||
[InlineData(@" Foo
|
||||
---
|
||||
|
||||
Foo
|
||||
-----
|
||||
|
||||
Foo
|
||||
===", @"<document>
|
||||
<heading level=""2"">
|
||||
<text>Foo</text>
|
||||
</heading>
|
||||
<heading level=""2"">
|
||||
<text>Foo</text>
|
||||
</heading>
|
||||
<heading level=""1"">
|
||||
<text>Foo</text>
|
||||
</heading>
|
||||
</document>")]
|
||||
[InlineData(@" Foo
|
||||
---
|
||||
|
||||
Foo
|
||||
---", @"<document>
|
||||
<code_block>Foo
|
||||
---
|
||||
|
||||
Foo
|
||||
</code_block>
|
||||
<thematic_break />
|
||||
</document>")]
|
||||
[InlineData(@"Foo
|
||||
---- ", @"<document>
|
||||
<heading level=""2"">
|
||||
<text>Foo</text>
|
||||
</heading>
|
||||
</document>")]
|
||||
[InlineData(@"Foo
|
||||
---", @"<document>
|
||||
<paragraph>
|
||||
<text>Foo</text>
|
||||
<softbreak />
|
||||
<text>---</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"Foo
|
||||
= =
|
||||
|
||||
Foo
|
||||
--- -", @"<document>
|
||||
<paragraph>
|
||||
<text>Foo</text>
|
||||
<softbreak />
|
||||
<text>= =</text>
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
<text>Foo</text>
|
||||
</paragraph>
|
||||
<thematic_break />
|
||||
</document>")]
|
||||
[InlineData(@"Foo
|
||||
-----", @"<document>
|
||||
<heading level=""2"">
|
||||
<text>Foo</text>
|
||||
</heading>
|
||||
</document>")]
|
||||
[InlineData(@"Foo\
|
||||
----", @"<document>
|
||||
<heading level=""2"">
|
||||
<text>Foo\</text>
|
||||
</heading>
|
||||
</document>")]
|
||||
[InlineData(@"`Foo
|
||||
----
|
||||
`
|
||||
|
||||
<a title=""a lot
|
||||
---
|
||||
of dashes""/>", @"<document>
|
||||
<heading level=""2"">
|
||||
<text>`</text>
|
||||
<text>Foo</text>
|
||||
</heading>
|
||||
<paragraph>
|
||||
<text>`</text>
|
||||
</paragraph>
|
||||
<heading level=""2"">
|
||||
<text><a title=""a lot</text>
|
||||
</heading>
|
||||
<paragraph>
|
||||
<text>of dashes""/></text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"> Foo
|
||||
---", @"<document>
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
<text>Foo</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
<thematic_break />
|
||||
</document>")]
|
||||
[InlineData(@"> foo
|
||||
bar
|
||||
===", @"<document>
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
<softbreak />
|
||||
<text>bar</text>
|
||||
<softbreak />
|
||||
<text>===</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
</document>")]
|
||||
[InlineData(@"- Foo
|
||||
---", @"<document>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>Foo</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
<thematic_break />
|
||||
</document>")]
|
||||
[InlineData(@"Foo
|
||||
Bar
|
||||
---", @"<document>
|
||||
<heading level=""2"">
|
||||
<text>Foo</text>
|
||||
<softbreak />
|
||||
<text>Bar</text>
|
||||
</heading>
|
||||
</document>")]
|
||||
[InlineData(@"---
|
||||
Foo
|
||||
---
|
||||
Bar
|
||||
---
|
||||
Baz", @"<document>
|
||||
<thematic_break />
|
||||
<heading level=""2"">
|
||||
<text>Foo</text>
|
||||
</heading>
|
||||
<heading level=""2"">
|
||||
<text>Bar</text>
|
||||
</heading>
|
||||
<paragraph>
|
||||
<text>Baz</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"
|
||||
====", @"<document>
|
||||
<paragraph>
|
||||
<text>====</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"---
|
||||
---", @"<document>
|
||||
<thematic_break />
|
||||
<thematic_break />
|
||||
</document>")]
|
||||
[InlineData(@"- foo
|
||||
-----", @"<document>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
<thematic_break />
|
||||
</document>")]
|
||||
[InlineData(@" foo
|
||||
---", @"<document>
|
||||
<code_block>foo
|
||||
</code_block>
|
||||
<thematic_break />
|
||||
</document>")]
|
||||
[InlineData(@"> foo
|
||||
-----", @"<document>
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
<thematic_break />
|
||||
</document>")]
|
||||
[InlineData(@"\> foo
|
||||
------", @"<document>
|
||||
<heading level=""2"">
|
||||
<text>> foo</text>
|
||||
</heading>
|
||||
</document>")]
|
||||
[InlineData(@"Foo
|
||||
|
||||
bar
|
||||
---
|
||||
baz", @"<document>
|
||||
<paragraph>
|
||||
<text>Foo</text>
|
||||
</paragraph>
|
||||
<heading level=""2"">
|
||||
<text>bar</text>
|
||||
</heading>
|
||||
<paragraph>
|
||||
<text>baz</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"Foo
|
||||
bar
|
||||
|
||||
---
|
||||
|
||||
baz", @"<document>
|
||||
<paragraph>
|
||||
<text>Foo</text>
|
||||
<softbreak />
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
<thematic_break />
|
||||
<paragraph>
|
||||
<text>baz</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"Foo
|
||||
bar
|
||||
* * *
|
||||
baz", @"<document>
|
||||
<paragraph>
|
||||
<text>Foo</text>
|
||||
<softbreak />
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
<thematic_break />
|
||||
<paragraph>
|
||||
<text>baz</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"Foo
|
||||
bar
|
||||
\---
|
||||
baz", @"<document>
|
||||
<paragraph>
|
||||
<text>Foo</text>
|
||||
<softbreak />
|
||||
<text>bar</text>
|
||||
<softbreak />
|
||||
<text>-</text>
|
||||
<text>--</text>
|
||||
<softbreak />
|
||||
<text>baz</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
public void Parse_SetExtHeading(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
}
|
||||
487
Radzen.Blazor.Tests/Markdown/HtmlBlockTests.cs
Normal file
487
Radzen.Blazor.Tests/Markdown/HtmlBlockTests.cs
Normal file
@@ -0,0 +1,487 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Markdown.Tests;
|
||||
|
||||
public class HtmlBlockTests
|
||||
{
|
||||
private static string ToXml(string markdown)
|
||||
{
|
||||
var document = MarkdownParser.Parse(markdown);
|
||||
|
||||
return XmlVisitor.ToXml(document);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"<table><tr><td>
|
||||
<pre>
|
||||
**Hello**,
|
||||
|
||||
_world_.
|
||||
</pre>
|
||||
</td></tr></table>", @"<document>
|
||||
<html_block><table><tr><td>
|
||||
<pre>
|
||||
**Hello**,</html_block>
|
||||
<paragraph>
|
||||
<emph>
|
||||
<text>world</text>
|
||||
</emph>
|
||||
<text>.</text>
|
||||
<softbreak />
|
||||
<html_inline></pre></html_inline>
|
||||
</paragraph>
|
||||
<html_block></td></tr></table></html_block>
|
||||
</document>")]
|
||||
[InlineData(@"<table>
|
||||
<tr>
|
||||
<td>
|
||||
hi
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
okay.", @"<document>
|
||||
<html_block><table>
|
||||
<tr>
|
||||
<td>
|
||||
hi
|
||||
</td>
|
||||
</tr>
|
||||
</table></html_block>
|
||||
<paragraph>
|
||||
<text>okay.</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@" <div>
|
||||
*hello*
|
||||
<foo><a>", @"<document>
|
||||
<html_block> <div>
|
||||
*hello*
|
||||
<foo><a></html_block>
|
||||
</document>")]
|
||||
[InlineData(@"</div>
|
||||
*foo*", @"<document>
|
||||
<html_block></div>
|
||||
*foo*</html_block>
|
||||
</document>")]
|
||||
[InlineData(@"<DIV CLASS=""foo"">
|
||||
|
||||
*Markdown*
|
||||
|
||||
</DIV>", @"<document>
|
||||
<html_block><DIV CLASS=""foo""></html_block>
|
||||
<paragraph>
|
||||
<emph>
|
||||
<text>Markdown</text>
|
||||
</emph>
|
||||
</paragraph>
|
||||
<html_block></DIV></html_block>
|
||||
</document>")]
|
||||
[InlineData(@"<div id=""foo""
|
||||
class=""bar"">
|
||||
</div>", @"<document>
|
||||
<html_block><div id=""foo""
|
||||
class=""bar"">
|
||||
</div></html_block>
|
||||
</document>")]
|
||||
[InlineData(@"<div id=""foo"" class=""bar
|
||||
baz"">
|
||||
</div>", @"<document>
|
||||
<html_block><div id=""foo"" class=""bar
|
||||
baz"">
|
||||
</div></html_block>
|
||||
</document>")]
|
||||
[InlineData(@"<div>
|
||||
*foo*
|
||||
|
||||
*bar*", @"<document>
|
||||
<html_block><div>
|
||||
*foo*</html_block>
|
||||
<paragraph>
|
||||
<emph>
|
||||
<text>bar</text>
|
||||
</emph>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"<div id=""foo""
|
||||
*hi*", @"<document>
|
||||
<html_block><div id=""foo""
|
||||
*hi*</html_block>
|
||||
</document>")]
|
||||
[InlineData(@"<div class
|
||||
foo", @"<document>
|
||||
<html_block><div class
|
||||
foo</html_block>
|
||||
</document>")]
|
||||
[InlineData(@"<div *???-&&&-<---
|
||||
*foo*", @"<document>
|
||||
<html_block><div *???-&&&-<---
|
||||
*foo*</html_block>
|
||||
</document>")]
|
||||
[InlineData(@"<div><a href=""bar"">*foo*</a></div>", @"<document>
|
||||
<html_block><div><a href=""bar"">*foo*</a></div></html_block>
|
||||
</document>")]
|
||||
[InlineData(@"<table><tr><td>
|
||||
foo
|
||||
</td></tr></table>", @"<document>
|
||||
<html_block><table><tr><td>
|
||||
foo
|
||||
</td></tr></table></html_block>
|
||||
</document>")]
|
||||
[InlineData(@"<div></div>
|
||||
``` c
|
||||
int x = 33;
|
||||
```", @"<document>
|
||||
<html_block><div></div>
|
||||
``` c
|
||||
int x = 33;
|
||||
```</html_block>
|
||||
</document>")]
|
||||
[InlineData(@"<a href=""foo"">
|
||||
*bar*
|
||||
</a>", @"<document>
|
||||
<html_block><a href=""foo"">
|
||||
*bar*
|
||||
</a></html_block>
|
||||
</document>")]
|
||||
[InlineData(@"<Warning>
|
||||
*bar*
|
||||
</Warning>", @"<document>
|
||||
<html_block><Warning>
|
||||
*bar*
|
||||
</Warning></html_block>
|
||||
</document>")]
|
||||
[InlineData(@"<i class=""foo"">
|
||||
*bar*
|
||||
</i>", @"<document>
|
||||
<html_block><i class=""foo"">
|
||||
*bar*
|
||||
</i></html_block>
|
||||
</document>")]
|
||||
[InlineData(@"</ins>
|
||||
*bar*", @"<document>
|
||||
<html_block></ins>
|
||||
*bar*</html_block>
|
||||
</document>")]
|
||||
[InlineData(@"<del>
|
||||
*foo*
|
||||
</del>", @"<document>
|
||||
<html_block><del>
|
||||
*foo*
|
||||
</del></html_block>
|
||||
</document>")]
|
||||
[InlineData(@"<del>
|
||||
|
||||
*foo*
|
||||
|
||||
</del>", @"<document>
|
||||
<html_block><del></html_block>
|
||||
<paragraph>
|
||||
<emph>
|
||||
<text>foo</text>
|
||||
</emph>
|
||||
</paragraph>
|
||||
<html_block></del></html_block>
|
||||
</document>")]
|
||||
[InlineData(@"<del>*foo*</del>", @"<document>
|
||||
<paragraph>
|
||||
<html_inline><del></html_inline>
|
||||
<emph>
|
||||
<text>foo</text>
|
||||
</emph>
|
||||
<html_inline></del></html_inline>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"<pre language=""haskell""><code>
|
||||
import Text.HTML.TagSoup
|
||||
|
||||
main :: IO ()
|
||||
main = print $ parseTags tags
|
||||
</code></pre>
|
||||
okay", @"<document>
|
||||
<html_block><pre language=""haskell""><code>
|
||||
import Text.HTML.TagSoup
|
||||
|
||||
main :: IO ()
|
||||
main = print $ parseTags tags
|
||||
</code></pre></html_block>
|
||||
<paragraph>
|
||||
<text>okay</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"<script type=""text/javascript"">
|
||||
// JavaScript example
|
||||
|
||||
document.getElementById(""demo"").innerHTML = ""Hello JavaScript!"";
|
||||
</script>
|
||||
okay", @"<document>
|
||||
<html_block><script type=""text/javascript"">
|
||||
// JavaScript example
|
||||
|
||||
document.getElementById(""demo"").innerHTML = ""Hello JavaScript!"";
|
||||
</script></html_block>
|
||||
<paragraph>
|
||||
<text>okay</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"<textarea>
|
||||
|
||||
*foo*
|
||||
|
||||
_bar_
|
||||
|
||||
</textarea>", @"<document>
|
||||
<html_block><textarea>
|
||||
|
||||
*foo*
|
||||
|
||||
_bar_
|
||||
|
||||
</textarea></html_block>
|
||||
</document>")]
|
||||
[InlineData(@"<style
|
||||
type=""text/css"">
|
||||
h1 {color:red;}
|
||||
|
||||
p {color:blue;}
|
||||
</style>
|
||||
okay",@"<document>
|
||||
<html_block><style
|
||||
type=""text/css"">
|
||||
h1 {color:red;}
|
||||
|
||||
p {color:blue;}
|
||||
</style></html_block>
|
||||
<paragraph>
|
||||
<text>okay</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"<style
|
||||
type=""text/css"">
|
||||
|
||||
foo", @"<document>
|
||||
<html_block><style
|
||||
type=""text/css"">
|
||||
|
||||
foo</html_block>
|
||||
</document>")]
|
||||
[InlineData(@"> <div>
|
||||
> foo
|
||||
|
||||
bar", @"<document>
|
||||
<block_quote>
|
||||
<html_block><div>
|
||||
foo</html_block>
|
||||
</block_quote>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"- <div>
|
||||
- foo", @"<document>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<html_block><div></html_block>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"<style>p{color:red;}</style>
|
||||
*foo*", @"<document>
|
||||
<html_block><style>p{color:red;}</style></html_block>
|
||||
<paragraph>
|
||||
<emph>
|
||||
<text>foo</text>
|
||||
</emph>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"<!-- foo -->*bar*
|
||||
*baz*", @"<document>
|
||||
<html_block><!-- foo -->*bar*</html_block>
|
||||
<paragraph>
|
||||
<emph>
|
||||
<text>baz</text>
|
||||
</emph>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"<script>
|
||||
foo
|
||||
</script>1. *bar*", @"<document>
|
||||
<html_block><script>
|
||||
foo
|
||||
</script>1. *bar*</html_block>
|
||||
</document>")]
|
||||
[InlineData(@"<!-- Foo
|
||||
|
||||
bar
|
||||
baz -->
|
||||
okay", @"<document>
|
||||
<html_block><!-- Foo
|
||||
|
||||
bar
|
||||
baz --></html_block>
|
||||
<paragraph>
|
||||
<text>okay</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"<?php
|
||||
|
||||
echo '>';
|
||||
|
||||
?>
|
||||
okay", @"<document>
|
||||
<html_block><?php
|
||||
|
||||
echo '>';
|
||||
|
||||
?></html_block>
|
||||
<paragraph>
|
||||
<text>okay</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"<!DOCTYPE html>", @"<document>
|
||||
<html_block><!DOCTYPE html></html_block>
|
||||
</document>")]
|
||||
[InlineData(@"<![CDATA[
|
||||
function matchwo(a,b)
|
||||
{
|
||||
if (a < b && a < 0) then {
|
||||
return 1;
|
||||
|
||||
} else {
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
]]>
|
||||
okay", @"<document>
|
||||
<html_block><![CDATA[
|
||||
function matchwo(a,b)
|
||||
{
|
||||
if (a < b && a < 0) then {
|
||||
return 1;
|
||||
|
||||
} else {
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
]]></html_block>
|
||||
<paragraph>
|
||||
<text>okay</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@" <!-- foo -->
|
||||
|
||||
<!-- foo -->", @"<document>
|
||||
<html_block> <!-- foo --></html_block>
|
||||
<code_block><!-- foo -->
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@" <div>
|
||||
|
||||
<div>", @"<document>
|
||||
<html_block> <div></html_block>
|
||||
<code_block><div>
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@"Foo
|
||||
<div>
|
||||
bar
|
||||
</div>", @"<document>
|
||||
<paragraph>
|
||||
<text>Foo</text>
|
||||
</paragraph>
|
||||
<html_block><div>
|
||||
bar
|
||||
</div></html_block>
|
||||
</document>")]
|
||||
[InlineData(@"<div>
|
||||
bar
|
||||
</div>
|
||||
*foo*", @"<document>
|
||||
<html_block><div>
|
||||
bar
|
||||
</div>
|
||||
*foo*</html_block>
|
||||
</document>")]
|
||||
[InlineData(@"Foo
|
||||
<a href=""bar"">
|
||||
baz", @"<document>
|
||||
<paragraph>
|
||||
<text>Foo</text>
|
||||
<softbreak />
|
||||
<html_inline><a href=""bar""></html_inline>
|
||||
<softbreak />
|
||||
<text>baz</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"<div>
|
||||
|
||||
*Emphasized* text.
|
||||
|
||||
</div>", @"<document>
|
||||
<html_block><div></html_block>
|
||||
<paragraph>
|
||||
<emph>
|
||||
<text>Emphasized</text>
|
||||
</emph>
|
||||
<text> text.</text>
|
||||
</paragraph>
|
||||
<html_block></div></html_block>
|
||||
</document>")]
|
||||
[InlineData(@"<div>
|
||||
*Emphasized* text.
|
||||
</div>", @"<document>
|
||||
<html_block><div>
|
||||
*Emphasized* text.
|
||||
</div></html_block>
|
||||
</document>")]
|
||||
[InlineData(@"<table>
|
||||
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
Hi
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</table>", @"<document>
|
||||
<html_block><table></html_block>
|
||||
<html_block><tr></html_block>
|
||||
<html_block><td>
|
||||
Hi
|
||||
</td></html_block>
|
||||
<html_block></tr></html_block>
|
||||
<html_block></table></html_block>
|
||||
</document>")]
|
||||
[InlineData(@"<table>
|
||||
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
Hi
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</table>", @"<document>
|
||||
<html_block><table></html_block>
|
||||
<html_block> <tr></html_block>
|
||||
<code_block><td>
|
||||
Hi
|
||||
</td>
|
||||
</code_block>
|
||||
<html_block> </tr></html_block>
|
||||
<html_block></table></html_block>
|
||||
</document>")]
|
||||
public void Parse_HtmlBlock(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
}
|
||||
144
Radzen.Blazor.Tests/Markdown/HtmlInlineTests.cs
Normal file
144
Radzen.Blazor.Tests/Markdown/HtmlInlineTests.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Markdown.Tests;
|
||||
|
||||
public class HtmlInlineTests
|
||||
{
|
||||
private static string ToXml(string markdown)
|
||||
{
|
||||
var document = MarkdownParser.Parse(markdown);
|
||||
|
||||
return XmlVisitor.ToXml(document);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"<a><bab><c2c>", @"<document>
|
||||
<paragraph>
|
||||
<html_inline><a></html_inline>
|
||||
<html_inline><bab></html_inline>
|
||||
<html_inline><c2c></html_inline>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"<a/><b2/>", @"<document>
|
||||
<paragraph>
|
||||
<html_inline><a/></html_inline>
|
||||
<html_inline><b2/></html_inline>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"<a /><b2
|
||||
data=""foo"" >", @"<document>
|
||||
<paragraph>
|
||||
<html_inline><a /></html_inline>
|
||||
<html_inline><b2
|
||||
data=""foo"" ></html_inline>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"<a foo=""bar"" bam = 'baz <em>""</em>'
|
||||
_boolean zoop:33=zoop:33 />", @"<document>
|
||||
<paragraph>
|
||||
<html_inline><a foo=""bar"" bam = 'baz <em>""</em>'
|
||||
_boolean zoop:33=zoop:33 /></html_inline>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"Foo <responsive-image src=""foo.jpg"" />", @"<document>
|
||||
<paragraph>
|
||||
<text>Foo </text>
|
||||
<html_inline><responsive-image src=""foo.jpg"" /></html_inline>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("<33> <__>", @"<document>
|
||||
<paragraph>
|
||||
<text><33> <</text>
|
||||
<text>__</text>
|
||||
<text>></text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"<a h*#ref=""hi"">", @"<document>
|
||||
<paragraph>
|
||||
<text><a h</text>
|
||||
<text>*</text>
|
||||
<text>#ref=""hi""></text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"<a href='bar'title=title>", @"<document>
|
||||
<paragraph>
|
||||
<text><a href='bar'title=title></text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"</a></foo >", @"<document>
|
||||
<paragraph>
|
||||
<html_inline></a></html_inline>
|
||||
<html_inline></foo ></html_inline>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"</a href=""foo"">", @"<document>
|
||||
<paragraph>
|
||||
<text></a href=""foo""></text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"foo <!-- this is a --
|
||||
comment - with hyphens -->", @"<document>
|
||||
<paragraph>
|
||||
<text>foo </text>
|
||||
<html_inline><!-- this is a --
|
||||
comment - with hyphens --></html_inline>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"foo <!--> foo -->
|
||||
|
||||
foo <!---> foo -->
|
||||
", @"<document>
|
||||
<paragraph>
|
||||
<text>foo </text>
|
||||
<html_inline><!--></html_inline>
|
||||
<text> foo --></text>
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
<text>foo </text>
|
||||
<html_inline><!---></html_inline>
|
||||
<text> foo --></text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"foo <?php echo $a; ?>", @"<document>
|
||||
<paragraph>
|
||||
<text>foo </text>
|
||||
<html_inline><?php echo $a; ?></html_inline>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"foo <!ELEMENT br EMPTY>", @"<document>
|
||||
<paragraph>
|
||||
<text>foo </text>
|
||||
<html_inline><!ELEMENT br EMPTY></html_inline>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"foo <![CDATA[>&<]]>", @"<document>
|
||||
<paragraph>
|
||||
<text>foo </text>
|
||||
<html_inline><![CDATA[>&<]]></html_inline>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"foo <a href=""ö"">", @"<document>
|
||||
<paragraph>
|
||||
<text>foo </text>
|
||||
<html_inline><a href=""&ouml;""></html_inline>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"foo <a href=""\*"">", @"<document>
|
||||
<paragraph>
|
||||
<text>foo </text>
|
||||
<html_inline><a href=""\*""></html_inline>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"foo <a href=""\"""">", @"<document>
|
||||
<paragraph>
|
||||
<text>foo <a href=""</text>
|
||||
<text>""</text>
|
||||
<text>""></text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
public void Parse_Html(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
}
|
||||
74
Radzen.Blazor.Tests/Markdown/ImageTests.cs
Normal file
74
Radzen.Blazor.Tests/Markdown/ImageTests.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Markdown.Tests;
|
||||
|
||||
public class ImageTests
|
||||
{
|
||||
|
||||
private static string ToXml(string markdown)
|
||||
{
|
||||
var document = MarkdownParser.Parse(markdown);
|
||||
|
||||
return XmlVisitor.ToXml(document);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"", @"<document>
|
||||
<paragraph>
|
||||
<image destination=""/url"" title=""title"">
|
||||
<text>foo</text>
|
||||
</image>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"](/url2)", @"<document>
|
||||
<paragraph>
|
||||
<image destination=""/url2"" title="""">
|
||||
<text>foo </text>
|
||||
<image destination=""/url"" title="""">
|
||||
<text>bar</text>
|
||||
</image>
|
||||
</image>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"](/url2)", @"<document>
|
||||
<paragraph>
|
||||
<image destination=""/url2"" title="""">
|
||||
<text>foo </text>
|
||||
<link destination=""/url"" title="""">
|
||||
<text>bar</text>
|
||||
</link>
|
||||
</image>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"", @"<document>
|
||||
<paragraph>
|
||||
<image destination=""train.jpg"" title="""">
|
||||
<text>foo</text>
|
||||
</image>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"My ", @"<document>
|
||||
<paragraph>
|
||||
<text>My </text>
|
||||
<image destination=""/path/to/train.jpg"" title=""title"">
|
||||
<text>foo bar</text>
|
||||
</image>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"", @"<document>
|
||||
<paragraph>
|
||||
<image destination=""url"" title="""">
|
||||
<text>foo</text>
|
||||
</image>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"", @"<document>
|
||||
<paragraph>
|
||||
<image destination=""/url"" title="""" />
|
||||
</paragraph>
|
||||
</document>")]
|
||||
public void Parse_BasicImages_ReturnsImageElement(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
}
|
||||
145
Radzen.Blazor.Tests/Markdown/IndentedCodeBlockTests.cs
Normal file
145
Radzen.Blazor.Tests/Markdown/IndentedCodeBlockTests.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Markdown.Tests;
|
||||
|
||||
public class IndentedCodeBlockTests
|
||||
{
|
||||
private static string ToXml(string markdown)
|
||||
{
|
||||
var document = MarkdownParser.Parse(markdown);
|
||||
|
||||
return XmlVisitor.ToXml(document);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@" a simple
|
||||
indented code block
|
||||
", @"<document>
|
||||
<code_block>a simple
|
||||
indented code block
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@" - foo
|
||||
|
||||
bar", @"<document>
|
||||
<list type=""bullet"" tight=""false"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(
|
||||
@"
|
||||
1. foo
|
||||
|
||||
- bar", @"<document>
|
||||
<list type=""ordered"" start=""1"" tight=""false"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@" <a/>
|
||||
*hi*
|
||||
|
||||
- one", @"<document>
|
||||
<code_block><a/>
|
||||
*hi*
|
||||
|
||||
- one
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@" chunk1
|
||||
|
||||
chunk2
|
||||
|
||||
|
||||
|
||||
chunk3", @"<document>
|
||||
<code_block>chunk1
|
||||
|
||||
chunk2
|
||||
|
||||
|
||||
|
||||
chunk3
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@" chunk1
|
||||
|
||||
chunk2", @"<document>
|
||||
<code_block>chunk1
|
||||
|
||||
chunk2
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@"Foo
|
||||
bar", @"<document>
|
||||
<paragraph>
|
||||
<text>Foo</text>
|
||||
<softbreak />
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@" foo
|
||||
bar", @"<document>
|
||||
<code_block>foo
|
||||
</code_block>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"# Heading
|
||||
foo
|
||||
Heading
|
||||
------
|
||||
foo
|
||||
----", @"<document>
|
||||
<heading level=""1"">
|
||||
<text>Heading</text>
|
||||
</heading>
|
||||
<code_block>foo
|
||||
</code_block>
|
||||
<heading level=""2"">
|
||||
<text>Heading</text>
|
||||
</heading>
|
||||
<code_block>foo
|
||||
</code_block>
|
||||
<thematic_break />
|
||||
</document>")]
|
||||
[InlineData(@" foo
|
||||
bar", @"<document>
|
||||
<code_block> foo
|
||||
bar
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@"
|
||||
|
||||
foo
|
||||
", @"<document>
|
||||
<code_block>foo
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@" foo ", @"<document>
|
||||
<code_block>foo
|
||||
</code_block>
|
||||
</document>")]
|
||||
public void Parse_IndentedCodeBlock(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
}
|
||||
843
Radzen.Blazor.Tests/Markdown/LinkTests.cs
Normal file
843
Radzen.Blazor.Tests/Markdown/LinkTests.cs
Normal file
@@ -0,0 +1,843 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Markdown.Tests;
|
||||
|
||||
public class LinkTests
|
||||
{
|
||||
private static string ToXml(string markdown)
|
||||
{
|
||||
var document = MarkdownParser.Parse(markdown);
|
||||
|
||||
return XmlVisitor.ToXml(document);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("[link](/uri \"title\")",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<link destination=""/uri"" title=""title"">
|
||||
<text>link</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("[link](/uri)", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""/uri"" title="""">
|
||||
<text>link</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("[](./target.md)", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""./target.md"" title="""" />
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("[link]()", @"<document>
|
||||
<paragraph>
|
||||
<link destination="""" title="""">
|
||||
<text>link</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("[link](<>)", @"<document>
|
||||
<paragraph>
|
||||
<link destination="""" title="""">
|
||||
<text>link</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("[]()", @"<document>
|
||||
<paragraph>
|
||||
<link destination="""" title="""" />
|
||||
</paragraph>
|
||||
</document>")]
|
||||
public void Parse_BasicLinks_ReturnsLinkNode(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("[link](/my uri)", @"<document>
|
||||
<paragraph>
|
||||
<text>[</text>
|
||||
<text>link</text>
|
||||
<text>](/my uri)</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("[link](</my uri>)", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""/my uri"" title="""">
|
||||
<text>link</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
public void Parse_LinkDestinationWithSpaces(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"[link](foo
|
||||
bar)", @"<document>
|
||||
<paragraph>
|
||||
<text>[</text>
|
||||
<text>link</text>
|
||||
<text>](foo</text>
|
||||
<softbreak />
|
||||
<text>bar)</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
|
||||
public void Parse_LinkDestinationWithNewLines(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"[a](<b)c>)", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""b)c"" title="""">
|
||||
<text>a</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
public void Parse_LinkDestinationWithCloseParenthesis(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"[link](<foo\>)", @"<document>
|
||||
<paragraph>
|
||||
<text>[</text>
|
||||
<text>link</text>
|
||||
<text>](<foo>)</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"[a](<b)c
|
||||
[a](<b)c>
|
||||
[a](<b>c)", @"<document>
|
||||
<paragraph>
|
||||
<text>[</text>
|
||||
<text>a</text>
|
||||
<text>](<b)c</text>
|
||||
<softbreak />
|
||||
<text>[</text>
|
||||
<text>a</text>
|
||||
<text>](<b)c></text>
|
||||
<softbreak />
|
||||
<text>[</text>
|
||||
<text>a</text>
|
||||
<text>](</text>
|
||||
<html_inline><b></html_inline>
|
||||
<text>c)</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
public void Parse_LinkDestinationUnclosedPointyBracket(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"[link](\(foo\))", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""(foo)"" title="""">
|
||||
<text>link</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"[link](foo\)\:)", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""foo):"" title="""">
|
||||
<text>link</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"[link](foo\bar)", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""foo\bar"" title="""">
|
||||
<text>link</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
public void Parse_Escapes(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"[link](foo(and(bar)))", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""foo(and(bar))"" title="""">
|
||||
<text>link</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"[link](foo(and(bar))", @"<document>
|
||||
<paragraph>
|
||||
<text>[</text>
|
||||
<text>link</text>
|
||||
<text>](foo(and(bar))</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"[link](foo\(and\(bar\))", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""foo(and(bar)"" title="""">
|
||||
<text>link</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"[link](<foo(and(bar)>)", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""foo(and(bar)"" title="""">
|
||||
<text>link</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
public void Parse_BallancedParenthesisInLinkDestination(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"[link](#fragment)
|
||||
[link](https://example.com#fragment)
|
||||
[link](https://example.com?foo=3#frag)", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""#fragment"" title="""">
|
||||
<text>link</text>
|
||||
</link>
|
||||
<softbreak />
|
||||
<link destination=""https://example.com#fragment"" title="""">
|
||||
<text>link</text>
|
||||
</link>
|
||||
<softbreak />
|
||||
<link destination=""https://example.com?foo=3#frag"" title="""">
|
||||
<text>link</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
public void Parse_FragmentAndQueryStringInLinkDestination(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"[link](""title"")", @"<document>
|
||||
<paragraph>
|
||||
<link destination="""title""" title="""">
|
||||
<text>link</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
public void Parse_QuotesInDestination(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"[link](/url ""title"")
|
||||
[link](/url 'title')
|
||||
[link](/url (title))", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""/url"" title=""title"">
|
||||
<text>link</text>
|
||||
</link>
|
||||
<softbreak />
|
||||
<link destination=""/url"" title=""title"">
|
||||
<text>link</text>
|
||||
</link>
|
||||
<softbreak />
|
||||
<link destination=""/url"" title=""title"">
|
||||
<text>link</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"[link](/url ""title ""and"" title"")", @"<document>
|
||||
<paragraph>
|
||||
<text>[</text>
|
||||
<text>link</text>
|
||||
<text>](/url ""title ""and"" title"")</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"[link](/url 'title ""and"" title')", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""/url"" title=""title "and" title"">
|
||||
<text>link</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
public void Parse_Title(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"[link]( /url
|
||||
""title"" )", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""/url"" title=""title"">
|
||||
<text>link</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"[link] (/uri)", @"<document>
|
||||
<paragraph>
|
||||
<text>[</text>
|
||||
<text>link</text>
|
||||
<text>] (/uri)</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
public void Parse_SpacesInLink(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"[link [foo [bar]]](/uri)", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""/uri"" title="""">
|
||||
<text>link </text>
|
||||
<text>[</text>
|
||||
<text>foo </text>
|
||||
<text>[</text>
|
||||
<text>bar</text>
|
||||
<text>]</text>
|
||||
<text>]</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"[link] bar](/uri)", @"<document>
|
||||
<paragraph>
|
||||
<text>[</text>
|
||||
<text>link</text>
|
||||
<text>] bar](/uri)</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"[link [bar](/uri)", @"<document>
|
||||
<paragraph>
|
||||
<text>[</text>
|
||||
<text>link </text>
|
||||
<link destination=""/uri"" title="""">
|
||||
<text>bar</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"[link \[bar](/uri)", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""/uri"" title="""">
|
||||
<text>link </text>
|
||||
<text>[</text>
|
||||
<text>bar</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
public void Parse_BracketsInText(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"[link *foo **bar** `#`*](/uri)", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""/uri"" title="""">
|
||||
<text>link </text>
|
||||
<emph>
|
||||
<text>foo </text>
|
||||
<strong>
|
||||
<text>bar</text>
|
||||
</strong>
|
||||
<text> </text>
|
||||
<code>#</code>
|
||||
</emph>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
|
||||
public void Parse_LinkTextIsInlineContent(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"[foo [bar](/uri)](/uri)", @"<document>
|
||||
<paragraph>
|
||||
<text>[</text>
|
||||
<text>foo </text>
|
||||
<link destination=""/uri"" title="""">
|
||||
<text>bar</text>
|
||||
</link>
|
||||
<text>](/uri)</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"[foo *[bar [baz](/uri)](/uri)*](/uri)", @"<document>
|
||||
<paragraph>
|
||||
<text>[</text>
|
||||
<text>foo </text>
|
||||
<emph>
|
||||
<text>[</text>
|
||||
<text>bar </text>
|
||||
<link destination=""/uri"" title="""">
|
||||
<text>baz</text>
|
||||
</link>
|
||||
<text>](/uri)</text>
|
||||
</emph>
|
||||
<text>](/uri)</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
public void Parse_NestedLinks(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"*[foo*](/uri)", @"<document>
|
||||
<paragraph>
|
||||
<text>*</text>
|
||||
<link destination=""/uri"" title="""">
|
||||
<text>foo</text>
|
||||
<text>*</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"[foo *bar](baz*)", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""baz*"" title="""">
|
||||
<text>foo </text>
|
||||
<text>*</text>
|
||||
<text>bar</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"*foo [bar* baz]", @"<document>
|
||||
<paragraph>
|
||||
<emph>
|
||||
<text>foo </text>
|
||||
<text>[</text>
|
||||
<text>bar</text>
|
||||
</emph>
|
||||
<text> baz</text>
|
||||
<text>]</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"[foo`](/uri)`", @"<document>
|
||||
<paragraph>
|
||||
<text>[</text>
|
||||
<text>foo</text>
|
||||
<code>](/uri)</code>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
public void Parse_Precedence(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"<http://foo.bar.baz>", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""http://foo.bar.baz"" title="""">
|
||||
<text>http://foo.bar.baz</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"<https://foo.bar.baz/test?q=hello&id=22&boolean>", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""https://foo.bar.baz/test?q=hello&id=22&boolean"" title="""">
|
||||
<text>https://foo.bar.baz/test?q=hello&id=22&boolean</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"<irc://foo.bar:2233/baz>", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""irc://foo.bar:2233/baz"" title="""">
|
||||
<text>irc://foo.bar:2233/baz</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"<MAILTO:FOO@BAR.BAZ>", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""MAILTO:FOO@BAR.BAZ"" title="""">
|
||||
<text>MAILTO:FOO@BAR.BAZ</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"<https://foo.bar/baz bim>", @"<document>
|
||||
<paragraph>
|
||||
<text><https://foo.bar/baz bim></text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"<https://example.com/\[\>", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""https://example.com/\[\"" title="""">
|
||||
<text>https://example.com/\[\</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"<foo@bar.example.com>", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""mailto:foo@bar.example.com"" title="""">
|
||||
<text>foo@bar.example.com</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"<foo+special@Bar.baz-bar0.com>", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""mailto:foo+special@Bar.baz-bar0.com"" title="""">
|
||||
<text>foo+special@Bar.baz-bar0.com</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"<foo\+@bar.example.com>", @"<document>
|
||||
<paragraph>
|
||||
<text><foo+@bar.example.com></text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"<>", @"<document>
|
||||
<paragraph>
|
||||
<text><></text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"< https://foo.bar >", @"<document>
|
||||
<paragraph>
|
||||
<text>< https://foo.bar ></text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"<m:abc>", @"<document>
|
||||
<paragraph>
|
||||
<text><m:abc></text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"<foo.bar.baz>", @"<document>
|
||||
<paragraph>
|
||||
<text><foo.bar.baz></text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"https://example.com", @"<document>
|
||||
<paragraph>
|
||||
<text>https://example.com</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"foo@bar.example.com", @"<document>
|
||||
<paragraph>
|
||||
<text>foo@bar.example.com</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
public void Parse_AutoLink(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"[foo]: /url ""title""
|
||||
|
||||
[foo]", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""/url"" title=""title"">
|
||||
<text>foo</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@" [foo]:
|
||||
/url
|
||||
'the title'
|
||||
|
||||
[foo]", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""/url"" title=""the title"">
|
||||
<text>foo</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"[Foo*bar\]]:my_(url) 'title (with parens)'
|
||||
|
||||
[Foo*bar\]]", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""my_(url)"" title=""title (with parens)"">
|
||||
<text>Foo</text>
|
||||
<text>*</text>
|
||||
<text>bar</text>
|
||||
<text>]</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"[Foo bar]:
|
||||
<my url>
|
||||
'title'
|
||||
|
||||
[Foo bar]", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""my url"" title=""title"">
|
||||
<text>Foo bar</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"[foo]: /url '
|
||||
title
|
||||
line1
|
||||
line2
|
||||
'
|
||||
|
||||
[foo]", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""/url"" title=""
title
line1
line2
"">
|
||||
<text>foo</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"[foo]: /url 'title
|
||||
|
||||
with blank line'
|
||||
|
||||
[foo]", @"<document>
|
||||
<paragraph>
|
||||
<text>[</text>
|
||||
<text>foo</text>
|
||||
<text>]: /url 'title</text>
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
<text>with blank line'</text>
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
<text>[</text>
|
||||
<text>foo</text>
|
||||
<text>]</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"[foo]:
|
||||
/url
|
||||
|
||||
[foo]", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""/url"" title="""">
|
||||
<text>foo</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"[foo]:
|
||||
|
||||
[foo]", @"<document>
|
||||
<paragraph>
|
||||
<text>[</text>
|
||||
<text>foo</text>
|
||||
<text>]:</text>
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
<text>[</text>
|
||||
<text>foo</text>
|
||||
<text>]</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"[foo]: <>
|
||||
|
||||
[foo]", @"<document>
|
||||
<paragraph>
|
||||
<link destination="""" title="""">
|
||||
<text>foo</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"[foo]: <bar>(baz)
|
||||
|
||||
[foo]", @"<document>
|
||||
<paragraph>
|
||||
<text>[</text>
|
||||
<text>foo</text>
|
||||
<text>]: </text>
|
||||
<html_inline><bar></html_inline>
|
||||
<text>(baz)</text>
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
<text>[</text>
|
||||
<text>foo</text>
|
||||
<text>]</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"[foo]: /url\bar\*baz ""foo\""bar\baz""
|
||||
|
||||
[foo]
|
||||
", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""/url\bar*baz"" title=""foo\"bar\baz"">
|
||||
<text>foo</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"[foo]
|
||||
|
||||
[foo]: url", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""url"" title="""">
|
||||
<text>foo</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"[foo]
|
||||
|
||||
[foo]: first
|
||||
[foo]: second", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""first"" title="""">
|
||||
<text>foo</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"[FOO]: /url
|
||||
|
||||
[Foo]", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""/url"" title="""">
|
||||
<text>Foo</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"[ΑΓΩ]: /φου
|
||||
|
||||
[αγω]", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""/φου"" title="""">
|
||||
<text>αγω</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"[foo]: /url", @"<document />")]
|
||||
[InlineData(@"[
|
||||
foo
|
||||
]: /url
|
||||
bar", @"<document>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"[foo]: /url ""title"" ok", @"<document>
|
||||
<paragraph>
|
||||
<text>[</text>
|
||||
<text>foo</text>
|
||||
<text>]: /url ""title"" ok</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"[foo]: /url
|
||||
""title"" ok", @"<document>
|
||||
<paragraph>
|
||||
<text>""title"" ok</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@" [foo]: /url ""title""
|
||||
|
||||
[foo]", @"<document>
|
||||
<code_block>[foo]: /url ""title""
|
||||
</code_block>
|
||||
<paragraph>
|
||||
<text>[</text>
|
||||
<text>foo</text>
|
||||
<text>]</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"```
|
||||
[foo]: /url
|
||||
```
|
||||
|
||||
[foo]", @"<document>
|
||||
<code_block>[foo]: /url
|
||||
</code_block>
|
||||
<paragraph>
|
||||
<text>[</text>
|
||||
<text>foo</text>
|
||||
<text>]</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"Foo
|
||||
[bar]: /baz
|
||||
|
||||
[bar]", @"<document>
|
||||
<paragraph>
|
||||
<text>Foo</text>
|
||||
<softbreak />
|
||||
<text>[</text>
|
||||
<text>bar</text>
|
||||
<text>]: /baz</text>
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
<text>[</text>
|
||||
<text>bar</text>
|
||||
<text>]</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"# [Foo]
|
||||
[foo]: /url
|
||||
> bar", @"<document>
|
||||
<heading level=""1"">
|
||||
<link destination=""/url"" title="""">
|
||||
<text>Foo</text>
|
||||
</link>
|
||||
</heading>
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
</document>")]
|
||||
[InlineData(@"[foo]: /url
|
||||
bar
|
||||
===
|
||||
[foo]", @"<document>
|
||||
<heading level=""1"">
|
||||
<text>bar</text>
|
||||
</heading>
|
||||
<paragraph>
|
||||
<link destination=""/url"" title="""">
|
||||
<text>foo</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"[foo]: /url
|
||||
===
|
||||
[foo]", @"<document>
|
||||
<paragraph>
|
||||
<text>===</text>
|
||||
<softbreak />
|
||||
<link destination=""/url"" title="""">
|
||||
<text>foo</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"[foo]: /foo-url ""foo""
|
||||
[bar]: /bar-url
|
||||
""bar""
|
||||
[baz]: /baz-url
|
||||
|
||||
[foo],
|
||||
[bar],
|
||||
[baz]", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""/foo-url"" title=""foo"">
|
||||
<text>foo</text>
|
||||
</link>
|
||||
<text>,</text>
|
||||
<softbreak />
|
||||
<link destination=""/bar-url"" title=""bar"">
|
||||
<text>bar</text>
|
||||
</link>
|
||||
<text>,</text>
|
||||
<softbreak />
|
||||
<link destination=""/baz-url"" title="""">
|
||||
<text>baz</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"[foo]
|
||||
|
||||
> [foo]: /url
|
||||
", @"<document>
|
||||
<paragraph>
|
||||
<link destination=""/url"" title="""">
|
||||
<text>foo</text>
|
||||
</link>
|
||||
</paragraph>
|
||||
<block_quote />
|
||||
</document>")]
|
||||
public void Parse_LinkReference(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
}
|
||||
880
Radzen.Blazor.Tests/Markdown/ListItemTests.cs
Normal file
880
Radzen.Blazor.Tests/Markdown/ListItemTests.cs
Normal file
@@ -0,0 +1,880 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Markdown.Tests;
|
||||
|
||||
public class ListItemTests
|
||||
{
|
||||
private static string ToXml(string markdown)
|
||||
{
|
||||
var document = MarkdownParser.Parse(markdown);
|
||||
|
||||
return XmlVisitor.ToXml(document);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"1. A paragraph
|
||||
with two lines.
|
||||
|
||||
indented code
|
||||
|
||||
> A block quote.", @"<document>
|
||||
<list type=""ordered"" start=""1"" tight=""false"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>A paragraph</text>
|
||||
<softbreak />
|
||||
<text>with two lines.</text>
|
||||
</paragraph>
|
||||
<code_block>indented code
|
||||
</code_block>
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
<text>A block quote.</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"- one
|
||||
|
||||
two", @"<document>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>one</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
<paragraph>
|
||||
<text>two</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"- one
|
||||
|
||||
two", @"<document>
|
||||
<list type=""bullet"" tight=""false"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>one</text>
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
<text>two</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@" - one
|
||||
|
||||
two", @"<document>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>one</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
<code_block> two
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@" - one
|
||||
|
||||
two", @"<document>
|
||||
<list type=""bullet"" tight=""false"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>one</text>
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
<text>two</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@" > > 1. one
|
||||
>>
|
||||
>> two
|
||||
", @"<document>
|
||||
<block_quote>
|
||||
<block_quote>
|
||||
<list type=""ordered"" start=""1"" tight=""false"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>one</text>
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
<text>two</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</block_quote>
|
||||
</block_quote>
|
||||
</document>")]
|
||||
[InlineData(@">>- one
|
||||
>>
|
||||
> > two
|
||||
", @"<document>
|
||||
<block_quote>
|
||||
<block_quote>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>one</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
<paragraph>
|
||||
<text>two</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
</block_quote>
|
||||
</document>")]
|
||||
[InlineData(@"-one
|
||||
|
||||
2.two
|
||||
", @"<document>
|
||||
<paragraph>
|
||||
<text>-one</text>
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
<text>2.two</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"- foo
|
||||
|
||||
|
||||
bar", @"<document>
|
||||
<list type=""bullet"" tight=""false"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"1. foo
|
||||
|
||||
```
|
||||
bar
|
||||
```
|
||||
|
||||
baz
|
||||
|
||||
> bam", @"<document>
|
||||
<list type=""ordered"" start=""1"" tight=""false"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
<code_block>bar
|
||||
</code_block>
|
||||
<paragraph>
|
||||
<text>baz</text>
|
||||
</paragraph>
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
<text>bam</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"- Foo
|
||||
|
||||
bar
|
||||
|
||||
|
||||
baz", @"<document>
|
||||
<list type=""bullet"" tight=""false"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>Foo</text>
|
||||
</paragraph>
|
||||
<code_block>bar
|
||||
|
||||
|
||||
baz
|
||||
</code_block>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"123456789. ok", @"<document>
|
||||
<list type=""ordered"" start=""123456789"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>ok</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"1234567890. not ok", @"<document>
|
||||
<paragraph>
|
||||
<text>1234567890. not ok</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"0. ok", @"<document>
|
||||
<list type=""ordered"" start=""0"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>ok</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"003. ok", @"<document>
|
||||
<list type=""ordered"" start=""3"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>ok</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"-1. not ok", @"<document>
|
||||
<paragraph>
|
||||
<text>-1. not ok</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"- foo
|
||||
|
||||
bar", @"<document>
|
||||
<list type=""bullet"" tight=""false"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
<code_block>bar
|
||||
</code_block>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@" 10. foo
|
||||
|
||||
bar", @"<document>
|
||||
<list type=""ordered"" start=""10"" tight=""false"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
<code_block>bar
|
||||
</code_block>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"1. indented code
|
||||
|
||||
paragraph
|
||||
|
||||
more code", @"<document>
|
||||
<list type=""ordered"" start=""1"" tight=""false"">
|
||||
<item>
|
||||
<code_block>indented code
|
||||
</code_block>
|
||||
<paragraph>
|
||||
<text>paragraph</text>
|
||||
</paragraph>
|
||||
<code_block>more code
|
||||
</code_block>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"- indented code
|
||||
|
||||
paragraph
|
||||
|
||||
more code", @"<document>
|
||||
<list type=""bullet"" tight=""false"">
|
||||
<item>
|
||||
<code_block>indented code
|
||||
</code_block>
|
||||
<paragraph>
|
||||
<text>paragraph</text>
|
||||
</paragraph>
|
||||
<code_block>more code
|
||||
</code_block>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"1. indented code
|
||||
|
||||
paragraph
|
||||
|
||||
more code", @"<document>
|
||||
<list type=""ordered"" start=""1"" tight=""false"">
|
||||
<item>
|
||||
<code_block> indented code
|
||||
</code_block>
|
||||
<paragraph>
|
||||
<text>paragraph</text>
|
||||
</paragraph>
|
||||
<code_block>more code
|
||||
</code_block>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"- foo
|
||||
|
||||
bar", @"<document>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"- foo
|
||||
|
||||
bar", @"<document>
|
||||
<list type=""bullet"" tight=""false"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"-
|
||||
foo
|
||||
-
|
||||
```
|
||||
bar
|
||||
```
|
||||
-
|
||||
baz", @"<document>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<code_block>bar
|
||||
</code_block>
|
||||
</item>
|
||||
<item>
|
||||
<code_block>baz
|
||||
</code_block>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"1.
|
||||
foo", @"<document>
|
||||
<list type=""ordered"" start=""1"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"-
|
||||
foo", @"<document>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"-
|
||||
|
||||
foo", @"<document>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item />
|
||||
</list>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"- foo
|
||||
-
|
||||
- bar", @"<document>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item />
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"- foo
|
||||
-
|
||||
- bar", @"<document>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item />
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"1. foo
|
||||
2.
|
||||
3. bar", @"<document>
|
||||
<list type=""ordered"" start=""1"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item />
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"*", @"<document>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item />
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"foo
|
||||
*
|
||||
|
||||
foo
|
||||
1.", @"<document>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
<softbreak />
|
||||
<text>*</text>
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
<softbreak />
|
||||
<text>1.</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@" 1. A paragraph
|
||||
with two lines.
|
||||
|
||||
indented code
|
||||
|
||||
> A block quote.", @"<document>
|
||||
<list type=""ordered"" start=""1"" tight=""false"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>A paragraph</text>
|
||||
<softbreak />
|
||||
<text>with two lines.</text>
|
||||
</paragraph>
|
||||
<code_block>indented code
|
||||
</code_block>
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
<text>A block quote.</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@" 1. A paragraph
|
||||
with two lines.
|
||||
|
||||
indented code
|
||||
|
||||
> A block quote.", @"<document>
|
||||
<list type=""ordered"" start=""1"" tight=""false"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>A paragraph</text>
|
||||
<softbreak />
|
||||
<text>with two lines.</text>
|
||||
</paragraph>
|
||||
<code_block>indented code
|
||||
</code_block>
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
<text>A block quote.</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@" 1. A paragraph
|
||||
with two lines.
|
||||
|
||||
indented code
|
||||
|
||||
> A block quote.", @"<document>
|
||||
<list type=""ordered"" start=""1"" tight=""false"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>A paragraph</text>
|
||||
<softbreak />
|
||||
<text>with two lines.</text>
|
||||
</paragraph>
|
||||
<code_block>indented code
|
||||
</code_block>
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
<text>A block quote.</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@" 1. A paragraph
|
||||
with two lines.
|
||||
|
||||
indented code
|
||||
|
||||
> A block quote.", @"<document>
|
||||
<code_block>1. A paragraph
|
||||
with two lines.
|
||||
|
||||
indented code
|
||||
|
||||
> A block quote.
|
||||
</code_block>
|
||||
</document>")]
|
||||
public void Parse_ListItem(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@" 1. A paragraph
|
||||
with two lines.
|
||||
|
||||
indented code
|
||||
|
||||
> A block quote.", @"<document>
|
||||
<list type=""ordered"" start=""1"" tight=""false"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>A paragraph</text>
|
||||
<softbreak />
|
||||
<text>with two lines.</text>
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
<text>indented code</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
<code_block> > A block quote.
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@" 1. A paragraph
|
||||
with two lines.", @"<document>
|
||||
<list type=""ordered"" start=""1"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>A paragraph</text>
|
||||
<softbreak />
|
||||
<text>with two lines.</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"> 1. > Blockquote
|
||||
continued here.", @"<document>
|
||||
<block_quote>
|
||||
<list type=""ordered"" start=""1"" tight=""true"">
|
||||
<item>
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
<text>Blockquote</text>
|
||||
<softbreak />
|
||||
<text>continued here.</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
</item>
|
||||
</list>
|
||||
</block_quote>
|
||||
</document>")]
|
||||
[InlineData(@"> 1. > Blockquote
|
||||
> continued here.", @"<document>
|
||||
<block_quote>
|
||||
<list type=""ordered"" start=""1"" tight=""true"">
|
||||
<item>
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
<text>Blockquote</text>
|
||||
<softbreak />
|
||||
<text>continued here.</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
</item>
|
||||
</list>
|
||||
</block_quote>
|
||||
</document>")]
|
||||
public void Parse_ListItem_WithLazyContinuation(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"- foo
|
||||
- bar
|
||||
- baz
|
||||
- boo", @"<document>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>baz</text>
|
||||
</paragraph>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>boo</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</item>
|
||||
</list>
|
||||
</item>
|
||||
</list>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"- foo
|
||||
- bar
|
||||
- baz
|
||||
- boo", @"<document>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>baz</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>boo</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"1. foo
|
||||
1. bar
|
||||
1. baz
|
||||
1. boo", @"<document>
|
||||
<list type=""ordered"" start=""1"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>baz</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>boo</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"
|
||||
1. foo
|
||||
1. bar
|
||||
1. baz
|
||||
1. boo", @"<document>
|
||||
<list type=""ordered"" start=""1"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
<list type=""ordered"" start=""1"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
<list type=""ordered"" start=""1"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>baz</text>
|
||||
</paragraph>
|
||||
<list type=""ordered"" start=""1"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>boo</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</item>
|
||||
</list>
|
||||
</item>
|
||||
</list>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"10) foo
|
||||
- bar", @"<document>
|
||||
<list type=""ordered"" start=""10"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"
|
||||
10) foo
|
||||
- bar", @"<document>
|
||||
<list type=""ordered"" start=""10"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"- - foo", @"<document>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"1. - foo", @"<document>
|
||||
<list type=""ordered"" start=""1"" tight=""true"">
|
||||
<item>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"1. 1. foo", @"<document>
|
||||
<list type=""ordered"" start=""1"" tight=""true"">
|
||||
<item>
|
||||
<list type=""ordered"" start=""1"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"- 1. foo", @"<document>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<list type=""ordered"" start=""1"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"1. - 2. foo", @"<document>
|
||||
<list type=""ordered"" start=""1"" tight=""true"">
|
||||
<item>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<list type=""ordered"" start=""2"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</item>
|
||||
</list>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"- # Foo
|
||||
- Bar
|
||||
---
|
||||
baz", @"<document>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<heading level=""1"">
|
||||
<text>Foo</text>
|
||||
</heading>
|
||||
</item>
|
||||
<item>
|
||||
<heading level=""2"">
|
||||
<text>Bar</text>
|
||||
</heading>
|
||||
<paragraph>
|
||||
<text>baz</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
public void Parse_ListItem_WithNesting(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
}
|
||||
693
Radzen.Blazor.Tests/Markdown/ListTests.cs
Normal file
693
Radzen.Blazor.Tests/Markdown/ListTests.cs
Normal file
@@ -0,0 +1,693 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Markdown.Tests;
|
||||
|
||||
public class ListTests
|
||||
{
|
||||
private static string ToXml(string markdown)
|
||||
{
|
||||
var document = MarkdownParser.Parse(markdown);
|
||||
|
||||
return XmlVisitor.ToXml(document);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"- foo
|
||||
- bar
|
||||
+ baz", @"<document>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>baz</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"1. foo
|
||||
2. bar
|
||||
3) baz", @"<document>
|
||||
<list type=""ordered"" start=""1"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
<list type=""ordered"" start=""3"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>baz</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"Foo
|
||||
- bar
|
||||
- baz", @"<document>
|
||||
<paragraph>
|
||||
<text>Foo</text>
|
||||
</paragraph>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>baz</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
public void Parse_List(string markdown, string expected)
|
||||
{
|
||||
var actual = ToXml(markdown);
|
||||
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"The number of windows in my house is
|
||||
14. The number of doors is 6.", @"<document>
|
||||
<paragraph>
|
||||
<text>The number of windows in my house is</text>
|
||||
<softbreak />
|
||||
<text>14. The number of doors is 6.</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"The number of windows in my house is
|
||||
1. The number of doors is 6.", @"<document>
|
||||
<paragraph>
|
||||
<text>The number of windows in my house is</text>
|
||||
</paragraph>
|
||||
<list type=""ordered"" start=""1"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>The number of doors is 6.</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
public void Parse_OnlyNumberedListsThatStartWithOneCanInterruptParagraphs(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"- foo
|
||||
|
||||
- bar
|
||||
|
||||
|
||||
- baz", @"<document>
|
||||
<list type=""bullet"" tight=""false"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>baz</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"- foo
|
||||
- bar
|
||||
- baz
|
||||
|
||||
|
||||
bim
|
||||
", @"<document>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
<list type=""bullet"" tight=""false"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>baz</text>
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
<text>bim</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</item>
|
||||
</list>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"- a
|
||||
- b
|
||||
|
||||
- c", @"<document>
|
||||
<list type=""bullet"" tight=""false"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>a</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>b</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>c</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"* a
|
||||
*
|
||||
|
||||
* c", @"<document>
|
||||
<list type=""bullet"" tight=""false"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>a</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item />
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>c</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"- a
|
||||
- b
|
||||
|
||||
c
|
||||
- d", @"<document>
|
||||
<list type=""bullet"" tight=""false"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>a</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>b</text>
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
<text>c</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>d</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"- a
|
||||
- b
|
||||
|
||||
[ref]: /url
|
||||
- d", @"<document>
|
||||
<list type=""bullet"" tight=""false"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>a</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>b</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>d</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"- a
|
||||
- ```
|
||||
b
|
||||
|
||||
|
||||
```
|
||||
- c", @"<document>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>a</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<code_block>b
|
||||
|
||||
|
||||
</code_block>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>c</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"- a
|
||||
- b
|
||||
|
||||
c
|
||||
- d", @"<document>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>a</text>
|
||||
</paragraph>
|
||||
<list type=""bullet"" tight=""false"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>b</text>
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
<text>c</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>d</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"* a
|
||||
> b
|
||||
>
|
||||
* c", @"<document>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>a</text>
|
||||
</paragraph>
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
<text>b</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>c</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"- a
|
||||
> b
|
||||
```
|
||||
c
|
||||
```
|
||||
- d
|
||||
", @"<document>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>a</text>
|
||||
</paragraph>
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
<text>b</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
<code_block>c
|
||||
</code_block>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>d</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"- a", @"<document>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>a</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"- a
|
||||
- b", @"<document>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>a</text>
|
||||
</paragraph>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>b</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"1. ```
|
||||
foo
|
||||
```
|
||||
|
||||
bar", @"<document>
|
||||
<list type=""ordered"" start=""1"" tight=""false"">
|
||||
<item>
|
||||
<code_block>foo
|
||||
</code_block>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"* foo
|
||||
* bar
|
||||
|
||||
baz", @"<document>
|
||||
<list type=""bullet"" tight=""false"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
<paragraph>
|
||||
<text>baz</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"- a
|
||||
- b
|
||||
- c
|
||||
|
||||
- d
|
||||
- e
|
||||
- f", @"<document>
|
||||
<list type=""bullet"" tight=""false"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>a</text>
|
||||
</paragraph>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>b</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>c</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>d</text>
|
||||
</paragraph>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>e</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>f</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
public void Parse_TightAndLooseLists(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"- foo
|
||||
- bar
|
||||
|
||||
<!-- -->
|
||||
|
||||
- baz
|
||||
- bim", @"<document>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
<html_block><!-- --></html_block>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>baz</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>bim</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"- foo
|
||||
|
||||
notcode
|
||||
|
||||
- foo
|
||||
|
||||
<!-- -->
|
||||
|
||||
code", @"<document>
|
||||
<list type=""bullet"" tight=""false"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
<text>notcode</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
<html_block><!-- --></html_block>
|
||||
<code_block>code
|
||||
</code_block>
|
||||
</document>")]
|
||||
public void Parse_List_Separators(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"- a
|
||||
- b
|
||||
- c
|
||||
- d
|
||||
- e
|
||||
- f
|
||||
- g", @"<document>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>a</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>b</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>c</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>d</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>e</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>f</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>g</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"1. a
|
||||
|
||||
2. b
|
||||
|
||||
3. c
|
||||
", @"<document>
|
||||
<list type=""ordered"" start=""1"" tight=""false"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>a</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>b</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>c</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"- a
|
||||
- b
|
||||
- c
|
||||
- d
|
||||
- e", @"<document>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>a</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>b</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>c</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>d</text>
|
||||
<softbreak />
|
||||
<text>- e</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"1. a
|
||||
|
||||
2. b
|
||||
|
||||
3. c", @"<document>
|
||||
<list type=""ordered"" start=""1"" tight=""false"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>a</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>b</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
<code_block>3. c
|
||||
</code_block>
|
||||
</document>")]
|
||||
public void Parse_List_Identation(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_BasicNestedLists()
|
||||
{
|
||||
Assert.Equal(@"<document>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>a</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>b</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>c</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>", ToXml(@"
|
||||
- a
|
||||
- b
|
||||
- c"));
|
||||
}
|
||||
}
|
||||
109
Radzen.Blazor.Tests/Markdown/ParagraphTests.cs
Normal file
109
Radzen.Blazor.Tests/Markdown/ParagraphTests.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Markdown.Tests;
|
||||
|
||||
public class ParagraphTests
|
||||
{
|
||||
private static string ToXml(string markdown)
|
||||
{
|
||||
var document = MarkdownParser.Parse(markdown);
|
||||
|
||||
return XmlVisitor.ToXml(document);
|
||||
}
|
||||
[Fact]
|
||||
public void Parse_BasicParagraph()
|
||||
{
|
||||
Assert.Equal(@"<document>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</document>", ToXml(@"foo"));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"aaa
|
||||
|
||||
bbb", @"<document>
|
||||
<paragraph>
|
||||
<text>aaa</text>
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
<text>bbb</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"aaa
|
||||
bbb
|
||||
|
||||
ccc
|
||||
ddd", @"<document>
|
||||
<paragraph>
|
||||
<text>aaa</text>
|
||||
<softbreak />
|
||||
<text>bbb</text>
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
<text>ccc</text>
|
||||
<softbreak />
|
||||
<text>ddd</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"aaa
|
||||
|
||||
|
||||
bbb
|
||||
", @"<document>
|
||||
<paragraph>
|
||||
<text>aaa</text>
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
<text>bbb</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@" aaa
|
||||
bbb", @"<document>
|
||||
<paragraph>
|
||||
<text>aaa</text>
|
||||
<softbreak />
|
||||
<text>bbb</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"aaa
|
||||
bbb
|
||||
ccc", @"<document>
|
||||
<paragraph>
|
||||
<text>aaa</text>
|
||||
<softbreak />
|
||||
<text>bbb</text>
|
||||
<softbreak />
|
||||
<text>ccc</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@" aaa
|
||||
bbb", @"<document>
|
||||
<paragraph>
|
||||
<text>aaa</text>
|
||||
<softbreak />
|
||||
<text>bbb</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@" aaa
|
||||
bbb", @"<document>
|
||||
<code_block>aaa
|
||||
</code_block>
|
||||
<paragraph>
|
||||
<text>bbb</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"aaa
|
||||
bbb ", @"<document>
|
||||
<paragraph>
|
||||
<text>aaa</text>
|
||||
<linebreak />
|
||||
<text>bbb</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
public void Parse_Paragraph(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
}
|
||||
41
Radzen.Blazor.Tests/Markdown/SoftLineBreakTests.cs
Normal file
41
Radzen.Blazor.Tests/Markdown/SoftLineBreakTests.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Markdown.Tests;
|
||||
|
||||
public class SoftLineBreakTests
|
||||
{
|
||||
private static string ToXml(string markdown)
|
||||
{
|
||||
var document = MarkdownParser.Parse(markdown);
|
||||
|
||||
return XmlVisitor.ToXml(document);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"foo
|
||||
baz", @"<document>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
<softbreak />
|
||||
<text>baz</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
public void Parse_SoftLineBreak(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"foo
|
||||
baz", @"<document>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
<softbreak />
|
||||
<text>baz</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
public void Parse_SoftLine_RemovesSpacesAtEndAndStartOfLine(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
}
|
||||
135
Radzen.Blazor.Tests/Markdown/StrongTests.cs
Normal file
135
Radzen.Blazor.Tests/Markdown/StrongTests.cs
Normal file
@@ -0,0 +1,135 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Markdown.Tests;
|
||||
|
||||
public class StrongTests
|
||||
{
|
||||
private static string ToXml(string markdown)
|
||||
{
|
||||
var document = MarkdownParser.Parse(markdown);
|
||||
return XmlVisitor.ToXml(document);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("**foo bar**",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<strong>
|
||||
<text>foo bar</text>
|
||||
</strong>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("** foo bar**",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<text>**</text>
|
||||
<text> foo bar</text>
|
||||
<text>**</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("a**\"foo\"**",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<text>a</text>
|
||||
<text>**</text>
|
||||
<text>""foo""</text>
|
||||
<text>**</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("foo**bar**",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
<strong>
|
||||
<text>bar</text>
|
||||
</strong>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("__foo bar__",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<strong>
|
||||
<text>foo bar</text>
|
||||
</strong>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("__ foo bar__",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<text>__</text>
|
||||
<text> foo bar</text>
|
||||
<text>__</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("__\nfoo bar__",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<text>__</text>
|
||||
<softbreak />
|
||||
<text>foo bar</text>
|
||||
<text>__</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("a__\"foo\"__",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<text>a</text>
|
||||
<text>__</text>
|
||||
<text>""foo""</text>
|
||||
<text>__</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("foo__bar__",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
<text>__</text>
|
||||
<text>bar</text>
|
||||
<text>__</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("5__6__78",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<text>5</text>
|
||||
<text>__</text>
|
||||
<text>6</text>
|
||||
<text>__</text>
|
||||
<text>78</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("пристаням__стремятся__",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<text>пристаням</text>
|
||||
<text>__</text>
|
||||
<text>стремятся</text>
|
||||
<text>__</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("__foo, __bar__, baz__",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<strong>
|
||||
<text>foo, </text>
|
||||
<strong>
|
||||
<text>bar</text>
|
||||
</strong>
|
||||
<text>, baz</text>
|
||||
</strong>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData("foo-__(bar)__",
|
||||
@"<document>
|
||||
<paragraph>
|
||||
<text>foo-</text>
|
||||
<strong>
|
||||
<text>(bar)</text>
|
||||
</strong>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
public void Parse_StrongEmphasisRules_AdheresToCommonMarkSpec(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
}
|
||||
392
Radzen.Blazor.Tests/Markdown/TableTests.cs
Normal file
392
Radzen.Blazor.Tests/Markdown/TableTests.cs
Normal file
@@ -0,0 +1,392 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Markdown.Tests;
|
||||
|
||||
public class TableTests
|
||||
{
|
||||
private static string ToXml(string markdown)
|
||||
{
|
||||
var document = MarkdownParser.Parse(markdown);
|
||||
|
||||
return XmlVisitor.ToXml(document);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_BasicTable()
|
||||
{
|
||||
Assert.Equal(
|
||||
@"<document>
|
||||
<table>
|
||||
<header>
|
||||
<cell>
|
||||
<text>foo</text>
|
||||
</cell>
|
||||
<cell>
|
||||
<text>bar</text>
|
||||
</cell>
|
||||
</header>
|
||||
<row>
|
||||
<cell>
|
||||
<text>baz</text>
|
||||
</cell>
|
||||
<cell>
|
||||
<text>bim</text>
|
||||
</cell>
|
||||
</row>
|
||||
</table>
|
||||
</document>",
|
||||
ToXml(@"
|
||||
foo|bar
|
||||
--|--
|
||||
baz|bim"));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"| foo | bar |
|
||||
| --- | --- |
|
||||
| baz | bim |", @"<document>
|
||||
<table>
|
||||
<header>
|
||||
<cell>
|
||||
<text>foo</text>
|
||||
</cell>
|
||||
<cell>
|
||||
<text>bar</text>
|
||||
</cell>
|
||||
</header>
|
||||
<row>
|
||||
<cell>
|
||||
<text>baz</text>
|
||||
</cell>
|
||||
<cell>
|
||||
<text>bim</text>
|
||||
</cell>
|
||||
</row>
|
||||
</table>
|
||||
</document>")]
|
||||
[InlineData(@"| f\|oo |
|
||||
| ------ |
|
||||
| b `\|` az |
|
||||
| b **\|** im |", @"<document>
|
||||
<table>
|
||||
<header>
|
||||
<cell>
|
||||
<text>f|oo</text>
|
||||
</cell>
|
||||
</header>
|
||||
<row>
|
||||
<cell>
|
||||
<text>b </text>
|
||||
<code>|</code>
|
||||
<text> az</text>
|
||||
</cell>
|
||||
</row>
|
||||
<row>
|
||||
<cell>
|
||||
<text>b </text>
|
||||
<strong>
|
||||
<text>|</text>
|
||||
</strong>
|
||||
<text> im</text>
|
||||
</cell>
|
||||
</row>
|
||||
</table>
|
||||
</document>")]
|
||||
[InlineData(@"| abc | defghi |
|
||||
:-: | -----------:
|
||||
bar | baz", @"<document>
|
||||
<table>
|
||||
<header>
|
||||
<cell align=""center"">
|
||||
<text>abc</text>
|
||||
</cell>
|
||||
<cell align=""right"">
|
||||
<text>defghi</text>
|
||||
</cell>
|
||||
</header>
|
||||
<row>
|
||||
<cell align=""center"">
|
||||
<text>bar</text>
|
||||
</cell>
|
||||
<cell align=""right"">
|
||||
<text>baz</text>
|
||||
</cell>
|
||||
</row>
|
||||
</table>
|
||||
</document>")]
|
||||
|
||||
|
||||
[InlineData(@"| abc | def |
|
||||
| --- | --- |
|
||||
bar", @"<document>
|
||||
<table>
|
||||
<header>
|
||||
<cell>
|
||||
<text>abc</text>
|
||||
</cell>
|
||||
<cell>
|
||||
<text>def</text>
|
||||
</cell>
|
||||
</header>
|
||||
<row>
|
||||
<cell>
|
||||
<text>bar</text>
|
||||
</cell>
|
||||
<cell />
|
||||
</row>
|
||||
</table>
|
||||
</document>")]
|
||||
public void Parse_Table(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"| abc | def |
|
||||
| --- | --- |
|
||||
| bar | baz |
|
||||
|
||||
boo", @"<document>
|
||||
<table>
|
||||
<header>
|
||||
<cell>
|
||||
<text>abc</text>
|
||||
</cell>
|
||||
<cell>
|
||||
<text>def</text>
|
||||
</cell>
|
||||
</header>
|
||||
<row>
|
||||
<cell>
|
||||
<text>bar</text>
|
||||
</cell>
|
||||
<cell>
|
||||
<text>baz</text>
|
||||
</cell>
|
||||
</row>
|
||||
</table>
|
||||
<paragraph>
|
||||
<text>boo</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"| foo |
|
||||
| --- |
|
||||
# bar", @"<document>
|
||||
<table>
|
||||
<header>
|
||||
<cell>
|
||||
<text>foo</text>
|
||||
</cell>
|
||||
</header>
|
||||
</table>
|
||||
<heading level=""1"">
|
||||
<text>bar</text>
|
||||
</heading>
|
||||
</document>")]
|
||||
[InlineData(@"| foo |
|
||||
| --- |
|
||||
- bar", @"<document>
|
||||
<table>
|
||||
<header>
|
||||
<cell>
|
||||
<text>foo</text>
|
||||
</cell>
|
||||
</header>
|
||||
</table>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"| foo |
|
||||
| --- |
|
||||
1. bar", @"<document>
|
||||
<table>
|
||||
<header>
|
||||
<cell>
|
||||
<text>foo</text>
|
||||
</cell>
|
||||
</header>
|
||||
</table>
|
||||
<list type=""ordered"" start=""1"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"| abc | def |
|
||||
| --- | --- |
|
||||
| bar | baz |
|
||||
> bar", @"<document>
|
||||
<table>
|
||||
<header>
|
||||
<cell>
|
||||
<text>abc</text>
|
||||
</cell>
|
||||
<cell>
|
||||
<text>def</text>
|
||||
</cell>
|
||||
</header>
|
||||
<row>
|
||||
<cell>
|
||||
<text>bar</text>
|
||||
</cell>
|
||||
<cell>
|
||||
<text>baz</text>
|
||||
</cell>
|
||||
</row>
|
||||
</table>
|
||||
<block_quote>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</block_quote>
|
||||
</document>")]
|
||||
[InlineData(@"| abc | def |
|
||||
| --- | --- |
|
||||
| bar | baz |
|
||||
bar", @"<document>
|
||||
<table>
|
||||
<header>
|
||||
<cell>
|
||||
<text>abc</text>
|
||||
</cell>
|
||||
<cell>
|
||||
<text>def</text>
|
||||
</cell>
|
||||
</header>
|
||||
<row>
|
||||
<cell>
|
||||
<text>bar</text>
|
||||
</cell>
|
||||
<cell>
|
||||
<text>baz</text>
|
||||
</cell>
|
||||
</row>
|
||||
</table>
|
||||
<code_block>bar
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@"| abc | def |
|
||||
| --- | --- |
|
||||
| bar | baz |
|
||||
```
|
||||
bar
|
||||
```", @"<document>
|
||||
<table>
|
||||
<header>
|
||||
<cell>
|
||||
<text>abc</text>
|
||||
</cell>
|
||||
<cell>
|
||||
<text>def</text>
|
||||
</cell>
|
||||
</header>
|
||||
<row>
|
||||
<cell>
|
||||
<text>bar</text>
|
||||
</cell>
|
||||
<cell>
|
||||
<text>baz</text>
|
||||
</cell>
|
||||
</row>
|
||||
</table>
|
||||
<code_block>bar
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@"| abc | def |
|
||||
| --- | --- |
|
||||
| bar | baz |
|
||||
<div>
|
||||
</div>", @"<document>
|
||||
<table>
|
||||
<header>
|
||||
<cell>
|
||||
<text>abc</text>
|
||||
</cell>
|
||||
<cell>
|
||||
<text>def</text>
|
||||
</cell>
|
||||
</header>
|
||||
<row>
|
||||
<cell>
|
||||
<text>bar</text>
|
||||
</cell>
|
||||
<cell>
|
||||
<text>baz</text>
|
||||
</cell>
|
||||
</row>
|
||||
</table>
|
||||
<html_block><div>
|
||||
</div></html_block>
|
||||
</document>")]
|
||||
[InlineData(@"| abc | def |
|
||||
| --- | --- |
|
||||
| bar | baz |
|
||||
---", @"<document>
|
||||
<table>
|
||||
<header>
|
||||
<cell>
|
||||
<text>abc</text>
|
||||
</cell>
|
||||
<cell>
|
||||
<text>def</text>
|
||||
</cell>
|
||||
</header>
|
||||
<row>
|
||||
<cell>
|
||||
<text>bar</text>
|
||||
</cell>
|
||||
<cell>
|
||||
<text>baz</text>
|
||||
</cell>
|
||||
</row>
|
||||
</table>
|
||||
<thematic_break />
|
||||
</document>")]
|
||||
public void Parse_Table_AnyBlockOrEmptyLineBreaksTable(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"| abc | def |
|
||||
| --- |", @"<document>
|
||||
<paragraph>
|
||||
<text>| abc | def |</text>
|
||||
<softbreak />
|
||||
<text>| --- |</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
public void Parse_Table_ChecksHeaderAndDelimiter(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"| abc |
|
||||
| --- |
|
||||
| bar | baz |", @"<document>
|
||||
<table>
|
||||
<header>
|
||||
<cell>
|
||||
<text>abc</text>
|
||||
</cell>
|
||||
</header>
|
||||
<row>
|
||||
<cell>
|
||||
<text>bar</text>
|
||||
</cell>
|
||||
</row>
|
||||
</table>
|
||||
</document>")]
|
||||
public void Parse_Table_IgnoresExtraCells(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
}
|
||||
162
Radzen.Blazor.Tests/Markdown/ThematicBreakTests.cs
Normal file
162
Radzen.Blazor.Tests/Markdown/ThematicBreakTests.cs
Normal file
@@ -0,0 +1,162 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Markdown.Tests;
|
||||
|
||||
public class ThematicBreakTests
|
||||
{
|
||||
private static string ToXml(string markdown)
|
||||
{
|
||||
var document = MarkdownParser.Parse(markdown);
|
||||
|
||||
return XmlVisitor.ToXml(document);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"***
|
||||
---
|
||||
___", @"<document>
|
||||
<thematic_break />
|
||||
<thematic_break />
|
||||
<thematic_break />
|
||||
</document>")]
|
||||
[InlineData(@"--
|
||||
**
|
||||
__", @"<document>
|
||||
<paragraph>
|
||||
<text>--</text>
|
||||
<softbreak />
|
||||
<text>**</text>
|
||||
<softbreak />
|
||||
<text>__</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@" ***
|
||||
***
|
||||
***", @"<document>
|
||||
<thematic_break />
|
||||
<thematic_break />
|
||||
<thematic_break />
|
||||
</document>")]
|
||||
[InlineData(@" ***", @"<document>
|
||||
<code_block>***
|
||||
</code_block>
|
||||
</document>")]
|
||||
[InlineData(@"Foo
|
||||
***", @"<document>
|
||||
<paragraph>
|
||||
<text>Foo</text>
|
||||
<softbreak />
|
||||
<text>***</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"_____________________________________", @"<document>
|
||||
<thematic_break />
|
||||
</document>")]
|
||||
[InlineData(@"- - -", @"<document>
|
||||
<thematic_break />
|
||||
</document>")]
|
||||
[InlineData(@" ** * ** * ** * **", @"<document>
|
||||
<thematic_break />
|
||||
</document>")]
|
||||
[InlineData(@"- - - -", @"<document>
|
||||
<thematic_break />
|
||||
</document>")]
|
||||
[InlineData(@"- - - - ", @"<document>
|
||||
<thematic_break />
|
||||
</document>")]
|
||||
[InlineData(@"_ _ _ _ a
|
||||
|
||||
a------
|
||||
|
||||
---a---", @"<document>
|
||||
<paragraph>
|
||||
<text>_</text>
|
||||
<text> </text>
|
||||
<text>_</text>
|
||||
<text> </text>
|
||||
<text>_</text>
|
||||
<text> </text>
|
||||
<text>_</text>
|
||||
<text> a</text>
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
<text>a------</text>
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
<text>---a---</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@" *-*", @"<document>
|
||||
<paragraph>
|
||||
<emph>
|
||||
<text>-</text>
|
||||
</emph>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"- foo
|
||||
***
|
||||
- bar", @"<document>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>foo</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
<thematic_break />
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"Foo
|
||||
***
|
||||
bar", @"<document>
|
||||
<paragraph>
|
||||
<text>Foo</text>
|
||||
</paragraph>
|
||||
<thematic_break />
|
||||
<paragraph>
|
||||
<text>bar</text>
|
||||
</paragraph>
|
||||
</document>")]
|
||||
[InlineData(@"* Foo
|
||||
* * *
|
||||
* Bar", @"<document>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>Foo</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
<thematic_break />
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>Bar</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
[InlineData(@"- Foo
|
||||
- * * *", @"<document>
|
||||
<list type=""bullet"" tight=""true"">
|
||||
<item>
|
||||
<paragraph>
|
||||
<text>Foo</text>
|
||||
</paragraph>
|
||||
</item>
|
||||
<item>
|
||||
<thematic_break />
|
||||
</item>
|
||||
</list>
|
||||
</document>")]
|
||||
public void Parse_ThematicBreak(string markdown, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ToXml(markdown));
|
||||
}
|
||||
}
|
||||
210
Radzen.Blazor.Tests/Markdown/XmlVisitor.cs
Normal file
210
Radzen.Blazor.Tests/Markdown/XmlVisitor.cs
Normal file
@@ -0,0 +1,210 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
|
||||
namespace Radzen.Blazor.Markdown.Tests;
|
||||
|
||||
public class XmlVisitor : NodeVisitorBase, IDisposable
|
||||
{
|
||||
private readonly XmlWriter writer;
|
||||
|
||||
private XmlVisitor(StringBuilder xml)
|
||||
{
|
||||
writer = XmlWriter.Create(xml, new XmlWriterSettings { OmitXmlDeclaration = true, Indent = true, IndentChars = " ", });
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
writer.Dispose();
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
writer.Close();
|
||||
}
|
||||
|
||||
public static string ToXml(Document document)
|
||||
{
|
||||
var xml = new StringBuilder();
|
||||
|
||||
using var visitor = new XmlVisitor(xml);
|
||||
|
||||
document.Accept(visitor);
|
||||
|
||||
visitor.Close();
|
||||
|
||||
return xml.ToString()!;
|
||||
}
|
||||
|
||||
public override void VisitBlockQuote(BlockQuote blockQuote)
|
||||
{
|
||||
writer.WriteStartElement("block_quote");
|
||||
base.VisitBlockQuote(blockQuote);
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
|
||||
public override void VisitDocument(Document document)
|
||||
{
|
||||
writer.WriteStartDocument();
|
||||
writer.WriteStartElement("document");
|
||||
base.VisitDocument(document);
|
||||
writer.WriteEndElement();
|
||||
writer.WriteEndDocument();
|
||||
}
|
||||
|
||||
public override void VisitHeading(Heading heading)
|
||||
{
|
||||
writer.WriteStartElement($"heading");
|
||||
writer.WriteAttributeString("level", heading.Level.ToString());
|
||||
base.VisitHeading(heading);
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
|
||||
public override void VisitListItem(ListItem listItem)
|
||||
{
|
||||
writer.WriteStartElement("item");
|
||||
base.VisitListItem(listItem);
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
|
||||
public override void VisitParagraph(Paragraph paragraph)
|
||||
{
|
||||
writer.WriteStartElement("paragraph");
|
||||
base.VisitParagraph(paragraph);
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
|
||||
public override void VisitUnorderedList(UnorderedList unorderedList)
|
||||
{
|
||||
writer.WriteStartElement("list");
|
||||
writer.WriteAttributeString("type", "bullet");
|
||||
writer.WriteAttributeString("tight", unorderedList.Tight.ToString().ToLowerInvariant());
|
||||
base.VisitUnorderedList(unorderedList);
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
|
||||
public override void VisitText(Text text)
|
||||
{
|
||||
writer.WriteElementString("text", text.Value);
|
||||
}
|
||||
|
||||
public override void VisitOrderedList(OrderedList orderedList)
|
||||
{
|
||||
writer.WriteStartElement("list");
|
||||
writer.WriteAttributeString("type", "ordered");
|
||||
writer.WriteAttributeString("start", orderedList.Start.ToString());
|
||||
writer.WriteAttributeString("tight", orderedList.Tight.ToString().ToLowerInvariant());
|
||||
base.VisitOrderedList(orderedList);
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
|
||||
public override void VisitLink(Link link)
|
||||
{
|
||||
writer.WriteStartElement("link");
|
||||
writer.WriteAttributeString("destination", link.Destination);
|
||||
writer.WriteAttributeString("title", link.Title);
|
||||
base.VisitLink(link);
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
|
||||
public override void VisitImage(Image image)
|
||||
{
|
||||
writer.WriteStartElement("image");
|
||||
writer.WriteAttributeString("destination", image.Destination);
|
||||
writer.WriteAttributeString("title", image.Title);
|
||||
base.VisitImage(image);
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
|
||||
public override void VisitEmphasis(Emphasis emphasis)
|
||||
{
|
||||
writer.WriteStartElement("emph");
|
||||
base.VisitEmphasis(emphasis);
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
|
||||
public override void VisitStrong(Strong strong)
|
||||
{
|
||||
writer.WriteStartElement("strong");
|
||||
base.VisitStrong(strong);
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
|
||||
public override void VisitCode(Code code)
|
||||
{
|
||||
writer.WriteElementString("code", code.Value);
|
||||
}
|
||||
|
||||
public override void VisitHtmlInline(HtmlInline html)
|
||||
{
|
||||
writer.WriteElementString("html_inline", html.Value);
|
||||
}
|
||||
|
||||
public override void VisitLineBreak(LineBreak lineBreak)
|
||||
{
|
||||
writer.WriteElementString("linebreak", string.Empty);
|
||||
}
|
||||
|
||||
public override void VisitSoftLineBreak(SoftLineBreak softLineBreak)
|
||||
{
|
||||
writer.WriteElementString("softbreak", string.Empty);
|
||||
}
|
||||
|
||||
public override void VisitThematicBreak(ThematicBreak thematicBreak)
|
||||
{
|
||||
writer.WriteElementString("thematic_break", string.Empty);
|
||||
}
|
||||
|
||||
public override void VisitIndentedCodeBlock(IndentedCodeBlock codeBlock)
|
||||
{
|
||||
writer.WriteElementString("code_block", codeBlock.Value);
|
||||
}
|
||||
|
||||
public override void VisitFencedCodeBlock(FencedCodeBlock fencedCodeBlock)
|
||||
{
|
||||
writer.WriteStartElement("code_block");
|
||||
if (!string.IsNullOrEmpty(fencedCodeBlock.Info))
|
||||
{
|
||||
writer.WriteAttributeString("info", fencedCodeBlock.Info);
|
||||
}
|
||||
writer.WriteString(fencedCodeBlock.Value);
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
|
||||
public override void VisitHtmlBlock(HtmlBlock htmlBlock)
|
||||
{
|
||||
writer.WriteElementString("html_block", htmlBlock.Value);
|
||||
}
|
||||
|
||||
public override void VisitTable(Table table)
|
||||
{
|
||||
writer.WriteStartElement("table");
|
||||
base.VisitTable(table);
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
|
||||
public override void VisitTableHeaderRow(TableHeaderRow header)
|
||||
{
|
||||
writer.WriteStartElement("header");
|
||||
base.VisitTableHeaderRow(header);
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
|
||||
public override void VisitTableRow(TableRow row)
|
||||
{
|
||||
writer.WriteStartElement("row");
|
||||
base.VisitTableRow(row);
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
|
||||
public override void VisitTableCell(TableCell cell)
|
||||
{
|
||||
writer.WriteStartElement("cell");
|
||||
if (cell.Alignment != TableCellAlignment.None)
|
||||
{
|
||||
writer.WriteAttributeString("align", cell.Alignment.ToString().ToLowerInvariant());
|
||||
}
|
||||
base.VisitTableCell(cell);
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
}
|
||||
@@ -119,13 +119,54 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
var component = ctx.RenderComponent<RadzenMask>();
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add<bool>(p => p.AutoComplete, false));
|
||||
component.SetParametersAndRender(parameters => parameters.AddUnmatched("AutoComplete", false));
|
||||
|
||||
Assert.Contains(@$"autocomplete=""off""", component.Markup);
|
||||
Assert.Contains(@$"aria-autocomplete=""none""", component.Markup);
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.AddUnmatched("AutoComplete", true));
|
||||
|
||||
Assert.Contains(@$"autocomplete=""on""", component.Markup);
|
||||
Assert.DoesNotContain(@$"aria-autocomplete", component.Markup);
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.AddUnmatched("autocomplete", "custom"));
|
||||
|
||||
Assert.Contains(@$"autocomplete=""custom""", component.Markup);
|
||||
Assert.DoesNotContain(@$"aria-autocomplete", component.Markup);
|
||||
|
||||
component.Instance.DefaultAutoCompleteAttribute = "autocomplete-custom";
|
||||
component.SetParametersAndRender(parameters => parameters.AddUnmatched("AutoComplete", false));
|
||||
|
||||
Assert.Contains(@$"autocomplete=""autocomplete-custom""", component.Markup);
|
||||
Assert.Contains(@$"aria-autocomplete=""none""", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Mask_Renders_TypedAutoCompleteParameter()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
var component = ctx.RenderComponent<RadzenMask>();
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.AddUnmatched("AutoComplete", false));
|
||||
component.SetParametersAndRender(parameters => parameters.Add<AutoCompleteType>(p => p.AutoCompleteType, AutoCompleteType.On));
|
||||
|
||||
Assert.Contains(@$"autocomplete=""off""", component.Markup);
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add<bool>(p => p.AutoComplete, true));
|
||||
component.SetParametersAndRender(parameters => parameters.AddUnmatched("AutoComplete", true));
|
||||
component.SetParametersAndRender(parameters => parameters.Add<AutoCompleteType>(p => p.AutoCompleteType, AutoCompleteType.Off));
|
||||
|
||||
Assert.Contains(@$"autocomplete=""on""", component.Markup);
|
||||
Assert.Contains(@$"autocomplete=""off""", component.Markup);
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.AddUnmatched("AutoComplete", true));
|
||||
component.SetParametersAndRender(parameters => parameters.Add<AutoCompleteType>(p => p.AutoCompleteType, AutoCompleteType.AdditionalName));
|
||||
|
||||
Assert.Contains(@$"autocomplete=""{AutoCompleteType.AdditionalName.GetAutoCompleteValue()}""", component.Markup);
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.AddUnmatched("AutoComplete", true));
|
||||
component.SetParametersAndRender(parameters => parameters.Add<AutoCompleteType>(p => p.AutoCompleteType, AutoCompleteType.Email));
|
||||
|
||||
Assert.Contains(@$"autocomplete=""{AutoCompleteType.Email.GetAutoCompleteValue()}""", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
214
Radzen.Blazor.Tests/NotificationServiceTests.cs
Normal file
214
Radzen.Blazor.Tests/NotificationServiceTests.cs
Normal file
@@ -0,0 +1,214 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Tests
|
||||
{
|
||||
public class NotificationServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public void NotificationService_IsMessageIsNull_ExceptionExpected()
|
||||
{
|
||||
NotificationService notificationService = new NotificationService();
|
||||
NotificationMessage notificationMessage = null;
|
||||
|
||||
var exception = Record.Exception(() => notificationService.Notify(notificationMessage));
|
||||
|
||||
Assert.IsType<ArgumentNullException>(exception);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NotificationService_CheckAreTwoMessages_Equals()
|
||||
{
|
||||
var messageOne = new NotificationMessage()
|
||||
{
|
||||
Severity = NotificationSeverity.Info,
|
||||
Summary = "Info Summary",
|
||||
Detail = "Info Detail",
|
||||
Duration = 4000
|
||||
};
|
||||
|
||||
var messageTwo = new NotificationMessage()
|
||||
{
|
||||
Severity = NotificationSeverity.Info,
|
||||
Summary = "Info Summary",
|
||||
Detail = "Info Detail",
|
||||
Duration = 4000
|
||||
};
|
||||
|
||||
Assert.True(messageOne.Equals(messageTwo));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NotificationService_CheckAreTwoMessages_NotEquals()
|
||||
{
|
||||
var messageOne = new NotificationMessage()
|
||||
{
|
||||
Severity = NotificationSeverity.Info,
|
||||
Summary = "Info Summary",
|
||||
Detail = "Info Detail",
|
||||
Duration = 4000
|
||||
};
|
||||
|
||||
var messageTwo = new NotificationMessage()
|
||||
{
|
||||
Severity = NotificationSeverity.Info,
|
||||
Summary = "Info Summary Two",
|
||||
Detail = "Info Detail Two",
|
||||
Duration = 6000
|
||||
};
|
||||
|
||||
Assert.False(messageOne.Equals(messageTwo));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NotificationService_CheckAreTwoMessages_EqualsByReference()
|
||||
{
|
||||
var messageOne = new NotificationMessage()
|
||||
{
|
||||
Severity = NotificationSeverity.Info,
|
||||
Summary = "Info Summary",
|
||||
Detail = "Info Detail",
|
||||
Duration = 4000
|
||||
};
|
||||
|
||||
var messageTwo = messageOne;
|
||||
|
||||
Assert.True(messageOne.Equals(messageTwo));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NotificationService_CheckAreTwoMessages_EqualsByOperator()
|
||||
{
|
||||
var messageOne = new NotificationMessage()
|
||||
{
|
||||
Severity = NotificationSeverity.Info,
|
||||
Summary = "Info Summary",
|
||||
Detail = "Info Detail",
|
||||
Duration = 4000
|
||||
};
|
||||
|
||||
var messageTwo = new NotificationMessage()
|
||||
{
|
||||
Severity = NotificationSeverity.Info,
|
||||
Summary = "Info Summary",
|
||||
Detail = "Info Detail",
|
||||
Duration = 4000
|
||||
};
|
||||
|
||||
Assert.True(messageOne == messageTwo);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NotificationService_CheckAreTwoMessages_NotEqualsByOperator()
|
||||
{
|
||||
var messageOne = new NotificationMessage()
|
||||
{
|
||||
Severity = NotificationSeverity.Info,
|
||||
Summary = "Info Summary",
|
||||
Detail = "Info Detail",
|
||||
Duration = 4000
|
||||
};
|
||||
|
||||
var messageTwo = new NotificationMessage()
|
||||
{
|
||||
Severity = NotificationSeverity.Info,
|
||||
Summary = "Info Summary Two",
|
||||
Detail = "Info Detail Two",
|
||||
Duration = 6000
|
||||
};
|
||||
|
||||
Assert.True(messageOne != messageTwo);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NotificationService_CheckAreTwoMessages_EqualsByHashCode()
|
||||
{
|
||||
var messageOne = new NotificationMessage()
|
||||
{
|
||||
Severity = NotificationSeverity.Info,
|
||||
Summary = "Info Summary",
|
||||
Detail = "Info Detail",
|
||||
Duration = 4000
|
||||
};
|
||||
|
||||
var messageTwo = new NotificationMessage()
|
||||
{
|
||||
Severity = NotificationSeverity.Info,
|
||||
Summary = "Info Summary",
|
||||
Detail = "Info Detail",
|
||||
Duration = 4000
|
||||
};
|
||||
|
||||
var messageOneHashCode = messageOne.GetHashCode();
|
||||
var messageTwoHashCode = messageTwo.GetHashCode();
|
||||
|
||||
Assert.Equal(messageOneHashCode, messageTwoHashCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NotificationService_CheckAreTwoMessages_NotEqualsByHashCode()
|
||||
{
|
||||
var messageOne = new NotificationMessage()
|
||||
{
|
||||
Severity = NotificationSeverity.Info,
|
||||
Summary = "Info Summary",
|
||||
Detail = "Info Detail",
|
||||
Duration = 4000
|
||||
};
|
||||
|
||||
var messageTwo = new NotificationMessage()
|
||||
{
|
||||
Severity = NotificationSeverity.Info,
|
||||
Summary = "Info Summary Two",
|
||||
Detail = "Info Detail Tow",
|
||||
Duration = 5000
|
||||
};
|
||||
|
||||
var messageOneHashCode = messageOne.GetHashCode();
|
||||
var messageTwoHashCode = messageTwo.GetHashCode();
|
||||
|
||||
Assert.NotEqual(messageOneHashCode, messageTwoHashCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NotificationService_MessagesCount_AfterAddingMessages()
|
||||
{
|
||||
NotificationService notificationService = new NotificationService();
|
||||
|
||||
//Messages are the same so only one should be added
|
||||
notificationService.Notify(NotificationSeverity.Info, "Info Summary", "Info Detail", 4000);
|
||||
notificationService.Notify(new NotificationMessage()
|
||||
{
|
||||
Severity = NotificationSeverity.Info,
|
||||
Summary = "Info Summary",
|
||||
Detail = "Info Detail",
|
||||
Duration = 4000
|
||||
});
|
||||
|
||||
int expectedMessagesNumber = 1;
|
||||
|
||||
Assert.Equal(expectedMessagesNumber, notificationService.Messages.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NotificationService_MessagesCount_AfterAddingTwoDifferentMessages()
|
||||
{
|
||||
NotificationService notificationService = new NotificationService();
|
||||
|
||||
//Messages are the same so only one should be added
|
||||
notificationService.Notify(NotificationSeverity.Info, "Info Summary 2", "Info Detail 2", 6000);
|
||||
notificationService.Notify(new NotificationMessage()
|
||||
{
|
||||
Severity = NotificationSeverity.Info,
|
||||
Summary = "Info Summary",
|
||||
Detail = "Info Detail",
|
||||
Duration = 4000
|
||||
});
|
||||
|
||||
int expectedMessagesNumber = 2;
|
||||
|
||||
Assert.Equal(expectedMessagesNumber, notificationService.Messages.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
176
Radzen.Blazor.Tests/NumericRangeValidatorTests.cs
Normal file
176
Radzen.Blazor.Tests/NumericRangeValidatorTests.cs
Normal file
@@ -0,0 +1,176 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bunit;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Tests
|
||||
{
|
||||
public class NumericRangeValidatorTests
|
||||
{
|
||||
class FormComponentTestDouble : IRadzenFormComponent
|
||||
{
|
||||
public bool IsBound => false;
|
||||
|
||||
public bool HasValue => true;
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public FieldIdentifier FieldIdentifier => throw new System.NotImplementedException();
|
||||
|
||||
public object GetValue()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
|
||||
public ValueTask FocusAsync()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool Disabled { get; set; }
|
||||
public bool Visible { get; set; }
|
||||
public IFormFieldContext FormFieldContext => null;
|
||||
|
||||
public object Value { get; set; }
|
||||
}
|
||||
|
||||
class RadzenNumericRangeValidatorTestDouble : RadzenNumericRangeValidator
|
||||
{
|
||||
public bool Validate(object value)
|
||||
{
|
||||
return base.Validate(new FormComponentTestDouble { Value = value });
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Throws_Exception_If_Min_And_Max_Are_Null()
|
||||
{
|
||||
var validator = new RadzenNumericRangeValidatorTestDouble();
|
||||
|
||||
Assert.Throws<System.ArgumentException>(() => validator.Validate(1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Returns_False_If_Value_Is_Null()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
var component = ctx.RenderComponent<RadzenNumericRangeValidatorTestDouble>();
|
||||
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
component.SetParametersAndRender(parameters => parameters.Add(p => p.Min, 0).Add(p => p.Max, 10));
|
||||
});
|
||||
|
||||
Assert.False(component.Instance.Validate(null));
|
||||
}
|
||||
[Fact]
|
||||
public void Returns_True_If_Value_Is_Null_And_AllowNull_Is_True()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
var component = ctx.RenderComponent<RadzenNumericRangeValidatorTestDouble>();
|
||||
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
component.SetParametersAndRender(parameters => parameters.Add(p => p.Min, 0).Add(p => p.Max, 10).Add(p => p.AllowNull, true));
|
||||
});
|
||||
|
||||
Assert.True(component.Instance.Validate(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Returns_False_If_Value_Overflows()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
var component = ctx.RenderComponent<RadzenNumericRangeValidatorTestDouble>();
|
||||
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
component.SetParametersAndRender(parameters => parameters.Add(p => p.Min, 0).Add(p => p.Max, 10));
|
||||
});
|
||||
|
||||
Assert.False(component.Instance.Validate(long.MaxValue));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Returns_True_If_Value_Is_Greater_Than_Min()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
var component = ctx.RenderComponent<RadzenNumericRangeValidatorTestDouble>();
|
||||
component.SetParametersAndRender(parameters => parameters.Add(p => p.Min, 0));
|
||||
|
||||
Assert.True(component.Instance.Validate(1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Returns_True_If_Value_Is_Equal_To_Min()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
var component = ctx.RenderComponent<RadzenNumericRangeValidatorTestDouble>();
|
||||
component.SetParametersAndRender(parameters => parameters.Add(p => p.Min, 0));
|
||||
|
||||
Assert.True(component.Instance.Validate(0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Returns_True_If_Value_Is_Less_Than_Max()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
var component = ctx.RenderComponent<RadzenNumericRangeValidatorTestDouble>();
|
||||
component.SetParametersAndRender(parameters => parameters.Add(p => p.Max, 10));
|
||||
|
||||
Assert.True(component.Instance.Validate(9));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Returns_True_If_Value_Is_Equal_To_Max()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
var component = ctx.RenderComponent<RadzenNumericRangeValidatorTestDouble>();
|
||||
component.SetParametersAndRender(parameters => parameters.Add(p => p.Max, 10));
|
||||
|
||||
Assert.True(component.Instance.Validate(10));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Returns_True_If_Value_Is_Between_Min_And_Max()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
var component = ctx.RenderComponent<RadzenNumericRangeValidatorTestDouble>();
|
||||
component.SetParametersAndRender(parameters => parameters.Add(p => p.Min, 0).Add(p => p.Max, 10));
|
||||
|
||||
Assert.True(component.Instance.Validate(5));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Returns_True_If_Value_Is_Between_Min_And_Max_And_They_Are_Nullable()
|
||||
{
|
||||
int? min = 0;
|
||||
int? max = 10;
|
||||
using var ctx = new TestContext();
|
||||
var component = ctx.RenderComponent<RadzenNumericRangeValidatorTestDouble>();
|
||||
component.SetParametersAndRender(parameters => parameters.Add(p => p.Min, min).Add(p => p.Max, max));
|
||||
Assert.True(component.Instance.Validate(5));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Returns_True_When_Value_Is_Of_DifferentType()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
var component = ctx.RenderComponent<RadzenNumericRangeValidatorTestDouble>();
|
||||
component.SetParametersAndRender(parameters => parameters.Add(p => p.Min, 0m).Add(p => p.Max, 10m));
|
||||
|
||||
Assert.True(component.Instance.Validate(5));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Returns_False_If_Cannot_Conert_Value()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
var component = ctx.RenderComponent<RadzenNumericRangeValidatorTestDouble>();
|
||||
component.SetParametersAndRender(parameters => parameters.Add(p => p.Min, 0m).Add(p => p.Max, 10m));
|
||||
|
||||
Assert.False(component.Instance.Validate(DateTime.Now));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using Bunit;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Tests
|
||||
@@ -14,9 +16,9 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
component.Render();
|
||||
|
||||
Assert.Contains(@$"rz-spinner", component.Markup);
|
||||
Assert.Contains(@$"rz-spinner-up", component.Markup);
|
||||
Assert.Contains(@$"rz-spinner-down", component.Markup);
|
||||
Assert.Contains(@$"rz-numeric", component.Markup);
|
||||
Assert.Contains(@$"rz-numeric-up", component.Markup);
|
||||
Assert.Contains(@$"rz-numeric-down", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -52,11 +54,41 @@ namespace Radzen.Blazor.Tests
|
||||
parameters.Add<decimal?>(p => p.Min, minValue);
|
||||
});
|
||||
|
||||
component.Find(".rz-spinner-down").Click();
|
||||
component.Find(".rz-numeric-down").Click();
|
||||
|
||||
Assert.False(raised, $"Numeric value should Change event if value is less than min value.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Numeric_Respect_Nullable_With_MinParameter()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenNumeric<double?>>();
|
||||
|
||||
var raised = false;
|
||||
var value = 3.5;
|
||||
object newValue = null;
|
||||
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add(p => p.Change, args => { raised = true; newValue = args; });
|
||||
parameters.Add<decimal?>(p => p.Min, 1);
|
||||
});
|
||||
|
||||
component.Find("input").Change(value);
|
||||
|
||||
Assert.True(raised);
|
||||
Assert.True(object.Equals(value, newValue));
|
||||
|
||||
component.Find("input").Change("");
|
||||
|
||||
Assert.True(raised);
|
||||
Assert.True(object.Equals(null, newValue));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Numeric_Respect_MaxParameter()
|
||||
{
|
||||
@@ -76,7 +108,7 @@ namespace Radzen.Blazor.Tests
|
||||
parameters.Add<decimal?>(p => p.Max, maxValue);
|
||||
});
|
||||
|
||||
component.Find(".rz-spinner-up").Click();
|
||||
component.Find(".rz-numeric-up").Click();
|
||||
|
||||
Assert.False(raised, $"Numeric value should Change event if value is less than min value.");
|
||||
}
|
||||
@@ -184,13 +216,54 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
var component = ctx.RenderComponent<RadzenNumeric<double>>();
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add<bool>(p => p.AutoComplete, false));
|
||||
component.SetParametersAndRender(parameters => parameters.AddUnmatched("AutoComplete", false));
|
||||
|
||||
Assert.Contains(@$"autocomplete=""off""", component.Markup);
|
||||
Assert.Contains(@$"aria-autocomplete=""none""", component.Markup);
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.AddUnmatched("AutoComplete", true));
|
||||
|
||||
Assert.Contains(@$"autocomplete=""on""", component.Markup);
|
||||
Assert.DoesNotContain(@$"aria-autocomplete", component.Markup);
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.AddUnmatched("autocomplete", "custom"));
|
||||
|
||||
Assert.Contains(@$"autocomplete=""custom""", component.Markup);
|
||||
Assert.DoesNotContain(@$"aria-autocomplete", component.Markup);
|
||||
|
||||
component.Instance.DefaultAutoCompleteAttribute = "autocomplete-custom";
|
||||
component.SetParametersAndRender(parameters => parameters.AddUnmatched("AutoComplete", false));
|
||||
|
||||
Assert.Contains(@$"autocomplete=""autocomplete-custom""", component.Markup);
|
||||
Assert.Contains(@$"aria-autocomplete=""none""", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Numeric_Renders_TypedAutoCompleteParameter()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
var component = ctx.RenderComponent<RadzenNumeric<double>>();
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.AddUnmatched("AutoComplete", false));
|
||||
component.SetParametersAndRender(parameters => parameters.Add<AutoCompleteType>(p => p.AutoCompleteType, AutoCompleteType.On));
|
||||
|
||||
Assert.Contains(@$"autocomplete=""off""", component.Markup);
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add<bool>(p => p.AutoComplete, true));
|
||||
component.SetParametersAndRender(parameters => parameters.AddUnmatched("AutoComplete", true));
|
||||
component.SetParametersAndRender(parameters => parameters.Add<AutoCompleteType>(p => p.AutoCompleteType, AutoCompleteType.Off));
|
||||
|
||||
Assert.Contains(@$"autocomplete=""on""", component.Markup);
|
||||
Assert.Contains(@$"autocomplete=""off""", component.Markup);
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.AddUnmatched("AutoComplete", true));
|
||||
component.SetParametersAndRender(parameters => parameters.Add<AutoCompleteType>(p => p.AutoCompleteType, AutoCompleteType.BdayMonth));
|
||||
|
||||
Assert.Contains(@$"autocomplete=""{AutoCompleteType.BdayMonth.GetAutoCompleteValue()}""", component.Markup);
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.AddUnmatched("AutoComplete", true));
|
||||
component.SetParametersAndRender(parameters => parameters.Add<AutoCompleteType>(p => p.AutoCompleteType, AutoCompleteType.BdayYear));
|
||||
|
||||
Assert.Contains(@$"autocomplete=""{AutoCompleteType.BdayYear.GetAutoCompleteValue()}""", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -256,7 +329,7 @@ namespace Radzen.Blazor.Tests
|
||||
parameters.Add(p => p.Change, args => { raised = true; newValue = args; });
|
||||
});
|
||||
|
||||
component.Find(".rz-spinner-up").Click();
|
||||
component.Find(".rz-numeric-up").Click();
|
||||
|
||||
Assert.True(raised, "Numeric Change should be raised on step up");
|
||||
Assert.True(object.Equals(expectedValue, newValue), $"Numeric value should be incremented on step up. Expected value: {expectedValue}, value: {newValue}");
|
||||
@@ -265,7 +338,7 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add(p => p.ValueChanged, args => { raised = true; }));
|
||||
|
||||
component.Find(".rz-spinner-up").Click();
|
||||
component.Find(".rz-numeric-up").Click();
|
||||
|
||||
Assert.True(raised, "Numeric ValueChanged should be raised on step up");
|
||||
}
|
||||
@@ -291,7 +364,7 @@ namespace Radzen.Blazor.Tests
|
||||
parameters.Add(p => p.Change, args => { raised = true; newValue = args; });
|
||||
});
|
||||
|
||||
component.Find(".rz-spinner-down").Click();
|
||||
component.Find(".rz-numeric-down").Click();
|
||||
|
||||
Assert.True(raised, "Numeric Change should be raised on step up");
|
||||
Assert.True(object.Equals(expectedValue, newValue), $"Numeric value should be incremented on step up. Expected value: {expectedValue}, value: {newValue}");
|
||||
@@ -300,7 +373,7 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add(p => p.ValueChanged, args => { raised = true; }));
|
||||
|
||||
component.Find(".rz-spinner-down").Click();
|
||||
component.Find(".rz-numeric-down").Click();
|
||||
|
||||
Assert.True(raised, "Numeric ValueChanged should be raised on step up");
|
||||
}
|
||||
@@ -314,9 +387,9 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
component.Render();
|
||||
|
||||
Assert.Contains(@$"rz-spinner-button-icon", component.Markup);
|
||||
Assert.Contains(@$"rz-spinner-up", component.Markup);
|
||||
Assert.Contains(@$"rz-spinner-down", component.Markup);
|
||||
Assert.Contains(@$"rz-numeric-button-icon", component.Markup);
|
||||
Assert.Contains(@$"rz-numeric-up", component.Markup);
|
||||
Assert.Contains(@$"rz-numeric-down", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -328,9 +401,9 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
component.Render();
|
||||
|
||||
Assert.DoesNotContain(@$"rz-spinner-button-icon", component.Markup);
|
||||
Assert.DoesNotContain(@$"rz-spinner-up", component.Markup);
|
||||
Assert.DoesNotContain(@$"rz-spinner-down", component.Markup);
|
||||
Assert.DoesNotContain(@$"rz-numeric-button-icon", component.Markup);
|
||||
Assert.DoesNotContain(@$"rz-numeric-up", component.Markup);
|
||||
Assert.DoesNotContain(@$"rz-numeric-down", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -350,5 +423,219 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
Assert.Contains($" value=\"{valueToTest.ToString(format)}\"", component.Markup);
|
||||
}
|
||||
|
||||
public static TheoryData<decimal, decimal> NumericFormatterPreservesLeadingZerosData =>
|
||||
new()
|
||||
{
|
||||
{ 10.000m, 100.000m },
|
||||
{ 100.000m, 10.000m }
|
||||
};
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(NumericFormatterPreservesLeadingZerosData))]
|
||||
public void Numeric_Formatter_PreservesLeadingZeros(decimal oldValue, decimal newValue)
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
string format = "0.000";
|
||||
|
||||
var component = ctx.RenderComponent<RadzenNumeric<decimal>>(
|
||||
ComponentParameter.CreateParameter(nameof(RadzenNumeric<decimal>.Format), format),
|
||||
ComponentParameter.CreateParameter(nameof(RadzenNumeric<decimal>.Value), oldValue)
|
||||
);
|
||||
|
||||
component.Render();
|
||||
|
||||
Assert.Contains($" value=\"{oldValue.ToString(format)}\"", component.Markup);
|
||||
|
||||
component.Find("input").Change(newValue);
|
||||
|
||||
Assert.Contains($" value=\"{newValue.ToString(format)}\"", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Numeric_Uses_ConvertValue()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var value = new Dollars(11m);
|
||||
Dollars? ConvertFunc(string s) => decimal.TryParse(s, System.Globalization.CultureInfo.InvariantCulture, out var val) ? new Dollars(val) : null;
|
||||
var component = ctx.RenderComponent<RadzenNumeric<Dollars?>>(
|
||||
ComponentParameter.CreateParameter(nameof(RadzenNumeric<Dollars?>.ConvertValue), (Func<string, Dollars?>)ConvertFunc),
|
||||
ComponentParameter.CreateParameter(nameof(RadzenNumeric<Dollars?>.Value), value),
|
||||
ComponentParameter.CreateParameter(nameof(RadzenNumeric<Dollars>.Culture), System.Globalization.CultureInfo.InvariantCulture)
|
||||
);
|
||||
|
||||
component.Render();
|
||||
|
||||
Assert.Contains($" value=\"{value.ToString()}\"", component.Markup);
|
||||
|
||||
var newValue = new Dollars(13.53m);
|
||||
component.Find("input").Change("13.53");
|
||||
|
||||
Assert.Contains($" value=\"{newValue.ToString()}\"", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Numeric_Supports_TypeConverter()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
var valueToTest = new Dollars(100.234m);
|
||||
string format = "0.00";
|
||||
|
||||
var component = ctx.RenderComponent<RadzenNumeric<Dollars>>(
|
||||
ComponentParameter.CreateParameter(nameof(RadzenNumeric<Dollars>.Format), format),
|
||||
ComponentParameter.CreateParameter(nameof(RadzenNumeric<Dollars>.Value), valueToTest)
|
||||
);
|
||||
|
||||
component.Render();
|
||||
|
||||
Assert.Contains($" value=\"{valueToTest.ToString(format, System.Globalization.CultureInfo.CurrentCulture)}\"", component.Markup);
|
||||
}
|
||||
[Fact]
|
||||
public void Numeric_Supports_TypeConverterWithCulture()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
var valueToTest = new Dollars(100.234m);
|
||||
string format = "0.00";
|
||||
|
||||
var component = ctx.RenderComponent<RadzenNumeric<Dollars>>(
|
||||
ComponentParameter.CreateParameter(nameof(RadzenNumeric<Dollars>.Format), format),
|
||||
ComponentParameter.CreateParameter(nameof(RadzenNumeric<Dollars>.Value), valueToTest),
|
||||
ComponentParameter.CreateParameter(nameof(RadzenNumeric<Dollars>.Culture), System.Globalization.CultureInfo.InvariantCulture)
|
||||
);
|
||||
|
||||
component.Render();
|
||||
|
||||
Assert.Contains($" value=\"{valueToTest.ToString(format, System.Globalization.CultureInfo.InvariantCulture)}\"", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Numeric_Supports_EmptyString()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
var valueToTest = "";
|
||||
string format = "0.00";
|
||||
|
||||
var component = ctx.RenderComponent<RadzenNumeric<string>>(
|
||||
ComponentParameter.CreateParameter(nameof(RadzenNumeric<string>.Format), format),
|
||||
ComponentParameter.CreateParameter(nameof(RadzenNumeric<string>.Value), valueToTest),
|
||||
ComponentParameter.CreateParameter(nameof(RadzenNumeric<Dollars>.Culture), System.Globalization.CultureInfo.InvariantCulture)
|
||||
);
|
||||
|
||||
component.Render();
|
||||
|
||||
Assert.Contains($" value=\"0.00\"", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Numeric_Supports_ValueString()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
var valueToTest = "12.50";
|
||||
string format = "0.00";
|
||||
|
||||
var component = ctx.RenderComponent<RadzenNumeric<string>>(
|
||||
ComponentParameter.CreateParameter(nameof(RadzenNumeric<string>.Format), format),
|
||||
ComponentParameter.CreateParameter(nameof(RadzenNumeric<string>.Value), valueToTest),
|
||||
ComponentParameter.CreateParameter(nameof(RadzenNumeric<Dollars>.Culture), System.Globalization.CultureInfo.InvariantCulture)
|
||||
);
|
||||
|
||||
component.Render();
|
||||
|
||||
Assert.Contains($" value=\"{valueToTest}\"", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Numeric_Supports_ValueStringEsCLCulture()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
var valueToTest = "12,50";
|
||||
string format = "0.00";
|
||||
|
||||
var component = ctx.RenderComponent<RadzenNumeric<string>>(
|
||||
ComponentParameter.CreateParameter(nameof(RadzenNumeric<string>.Format), format),
|
||||
ComponentParameter.CreateParameter(nameof(RadzenNumeric<string>.Value), valueToTest),
|
||||
ComponentParameter.CreateParameter(nameof(RadzenNumeric<Dollars>.Culture), System.Globalization.CultureInfo.GetCultureInfo("es-CL"))
|
||||
);
|
||||
|
||||
component.Render();
|
||||
|
||||
Assert.Contains($" value=\"{valueToTest}\"", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Numeric_Supports_ValueStringEnUSCulture()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
var valueToTest = "12.50";
|
||||
string format = "0.00";
|
||||
|
||||
var component = ctx.RenderComponent<RadzenNumeric<string>>(
|
||||
ComponentParameter.CreateParameter(nameof(RadzenNumeric<string>.Format), format),
|
||||
ComponentParameter.CreateParameter(nameof(RadzenNumeric<string>.Value), valueToTest),
|
||||
ComponentParameter.CreateParameter(nameof(RadzenNumeric<Dollars>.Culture), System.Globalization.CultureInfo.GetCultureInfo("en-US"))
|
||||
);
|
||||
|
||||
component.Render();
|
||||
|
||||
Assert.Contains($" value=\"{valueToTest}\"", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Numeric_Supports_IComparable()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenNumeric<Dollars>>();
|
||||
|
||||
var maxValue = 2;
|
||||
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add(p => p.Value, new Dollars(1m));
|
||||
parameters.Add(p => p.Max, maxValue);
|
||||
});
|
||||
});
|
||||
|
||||
component.Find("input").Change(13.53);
|
||||
|
||||
var maxDollars = new Dollars(maxValue);
|
||||
Assert.Contains($" value=\"{maxDollars}\"", component.Markup);
|
||||
Assert.Equal(component.Instance.Value, maxDollars);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Numeric_Supports_IFormattable()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
var valueToTest = new Temperature(60.23m);
|
||||
const string format = "F";
|
||||
|
||||
var component = ctx.RenderComponent<RadzenNumeric<Temperature>>(
|
||||
ComponentParameter.CreateParameter(nameof(RadzenNumeric<Temperature>.Format), format),
|
||||
ComponentParameter.CreateParameter(nameof(RadzenNumeric<Temperature>.Value), valueToTest)
|
||||
);
|
||||
|
||||
component.Render();
|
||||
|
||||
var input = component.Find("input").GetAttribute("value");
|
||||
input.MarkupMatches(valueToTest.ToString(format));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,14 +23,14 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
component.Render();
|
||||
|
||||
Assert.Contains(@$"rz-paginator", component.Markup);
|
||||
Assert.Contains(@$"rz-pager", component.Markup);
|
||||
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add<int>(p => p.PageSize, 101);
|
||||
parameters.Add<int>(p => p.Count, 100);
|
||||
});
|
||||
Assert.DoesNotContain(@$"rz-paginator", component.Markup);
|
||||
Assert.DoesNotContain(@$"rz-pager", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -49,7 +49,7 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
component.Render();
|
||||
|
||||
Assert.Contains(@$"rz-paginator", component.Markup);
|
||||
Assert.Contains(@$"rz-pager", component.Markup);
|
||||
Assert.Contains(@$"rz-dropdown-trigger", component.Markup);
|
||||
}
|
||||
|
||||
@@ -67,14 +67,93 @@ namespace Radzen.Blazor.Tests
|
||||
await component.Instance.GoToPage(2);
|
||||
component.Render();
|
||||
|
||||
Assert.Contains(@$"rz-paginator-summary", component.Markup);
|
||||
Assert.Contains(@$"rz-pager-summary", component.Markup);
|
||||
Assert.Contains(@$"Page 3 of 10 (100 items)", component.Markup);
|
||||
|
||||
component.SetParametersAndRender(parameters => {
|
||||
parameters.Add<bool>(p => p.ShowPagingSummary, false);
|
||||
});
|
||||
Assert.DoesNotContain(@$"rz-paginator-summary", component.Markup);
|
||||
Assert.DoesNotContain(@$"rz-pager-summary", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RadzenPager_Renders_PagerDensityDefault()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenPager>(parameters =>
|
||||
{
|
||||
parameters.Add<int>(p => p.PageSize, 20);
|
||||
parameters.Add<int>(p => p.Count, 100);
|
||||
parameters.Add<Density>(p => p.Density, Density.Default);
|
||||
});
|
||||
|
||||
Assert.DoesNotContain(@$"rz-density-compact", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RadzenPager_Renders_PagerDensityCompact()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenPager>(parameters =>
|
||||
{
|
||||
parameters.Add<int>(p => p.PageSize, 20);
|
||||
parameters.Add<int>(p => p.Count, 100);
|
||||
parameters.Add<Density>(p => p.Density, Density.Compact);
|
||||
});
|
||||
|
||||
Assert.Contains(@$"rz-density-compact", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void RadzenPager_First_And_Prev_Buttons_Are_Disabled_When_On_The_First_Page()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenPager>(parameters => {
|
||||
parameters.Add<int>(p => p.PageSize, 10);
|
||||
parameters.Add<int>(p => p.Count, 100);
|
||||
parameters.Add<bool>(p => p.ShowPagingSummary, true);
|
||||
});
|
||||
|
||||
await component.Instance.GoToPage(0);
|
||||
component.Render();
|
||||
|
||||
var firstPageButton = component.Find("a.rz-pager-first");
|
||||
Assert.True(firstPageButton.HasAttribute("disabled"));
|
||||
|
||||
var prevPageButton = component.Find("a.rz-pager-prev");
|
||||
Assert.True(prevPageButton.HasAttribute("disabled"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void RadzenPager_Last_And_Next_Buttons_Are_Disabled_When_On_The_Last_Page()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
|
||||
var component = ctx.RenderComponent<RadzenPager>(parameters => {
|
||||
parameters.Add<int>(p => p.PageSize, 10);
|
||||
parameters.Add<int>(p => p.Count, 100);
|
||||
parameters.Add<bool>(p => p.ShowPagingSummary, true);
|
||||
});
|
||||
|
||||
await component.Instance.GoToPage(9);
|
||||
component.Render();
|
||||
|
||||
var lastPageButton = component.Find("a.rz-pager-last");
|
||||
Assert.True(lastPageButton.HasAttribute("disabled"));
|
||||
|
||||
var nextPageButton = component.Find("a.rz-pager-next");
|
||||
Assert.True(nextPageButton.HasAttribute("disabled"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add(p => p.Icon, value));
|
||||
|
||||
Assert.Contains(@$"<i class=""rzi"">{value}</i>", component.Markup);
|
||||
Assert.Contains(@$"<i class=""notranslate rzi"">{value}</i>", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -119,11 +119,11 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add<bool>(p => p.AllowCollapse, true));
|
||||
|
||||
Assert.Contains(@"<span class=""rzi rzi-minus""></span>", component.Markup);
|
||||
Assert.Contains(@"<span class=""notranslate rzi rzi-minus""></span>", component.Markup);
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add<bool>(p => p.Collapsed, true));
|
||||
|
||||
Assert.Contains(@"<span class=""rzi rzi-plus""></span>", component.Markup);
|
||||
Assert.Contains(@"<span class=""notranslate rzi rzi-plus""></span>", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -119,13 +119,45 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
var component = ctx.RenderComponent<RadzenPassword>();
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add<bool>(p => p.AutoComplete, false));
|
||||
component.SetParametersAndRender(parameters => parameters.AddUnmatched("AutoComplete", false));
|
||||
|
||||
Assert.Contains(@$"autocomplete=""new-password""", component.Markup);
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add<bool>(p => p.AutoComplete, true));
|
||||
component.SetParametersAndRender(parameters => parameters.AddUnmatched("AutoComplete", true));
|
||||
|
||||
Assert.Contains(@$"autocomplete=""on""", component.Markup);
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.AddUnmatched("autocomplete", "custom"));
|
||||
|
||||
Assert.Contains(@$"autocomplete=""custom""", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Password_Renders_TypedAutoCompleteParameter()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
var component = ctx.RenderComponent<RadzenPassword>();
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.AddUnmatched("AutoComplete", false));
|
||||
component.SetParametersAndRender(parameters => parameters.Add<AutoCompleteType>(p => p.AutoCompleteType, AutoCompleteType.On));
|
||||
|
||||
Assert.Contains(@$"autocomplete=""new-password""", component.Markup);
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.AddUnmatched("AutoComplete", true));
|
||||
component.SetParametersAndRender(parameters => parameters.Add<AutoCompleteType>(p => p.AutoCompleteType, AutoCompleteType.Off));
|
||||
|
||||
Assert.Contains(@$"autocomplete=""off""", component.Markup);
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.AddUnmatched("AutoComplete", true));
|
||||
component.SetParametersAndRender(parameters => parameters.Add<AutoCompleteType>(p => p.AutoCompleteType, AutoCompleteType.CurrentPassword));
|
||||
|
||||
Assert.Contains(@$"autocomplete=""{AutoCompleteType.CurrentPassword.GetAutoCompleteValue()}""", component.Markup);
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.AddUnmatched("AutoComplete", true));
|
||||
component.SetParametersAndRender(parameters => parameters.Add<AutoCompleteType>(p => p.AutoCompleteType, AutoCompleteType.NewPassword));
|
||||
|
||||
Assert.Contains(@$"autocomplete=""{AutoCompleteType.NewPassword.GetAutoCompleteValue()}""", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add(p => p.Style, value));
|
||||
|
||||
Assert.Contains(@$"style=""{value}""", component.Markup);
|
||||
Assert.Contains(@$"style=""--rz-progressbar-value: 0%;{value}""", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -118,7 +118,7 @@ namespace Radzen.Blazor.Tests
|
||||
parameters.Add<double>(p => p.Max, max);
|
||||
});
|
||||
|
||||
Assert.Contains(@$"style=""width: {Math.Min(value / max * 100, 100).ToInvariantString()}%;""", component.Markup);
|
||||
Assert.Contains(@$"style=""--rz-progressbar-value: {Math.Min(value / max * 100, 100).ToInvariantString()}%;""", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -129,18 +129,18 @@ namespace Radzen.Blazor.Tests
|
||||
var component = ctx.RenderComponent<RadzenProgressBar>();
|
||||
|
||||
component.SetParametersAndRender(parameters=>parameters.Add(p=>p.ProgressBarStyle, ProgressBarStyle.Success));
|
||||
Assert.Contains(@$"rz-progressbar-determinate-success", component.Markup);
|
||||
Assert.Contains(@$"rz-progressbar-success", component.Markup);
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add(p => p.ProgressBarStyle, ProgressBarStyle.Info));
|
||||
Assert.Contains(@$"rz-progressbar-determinate-info", component.Markup);
|
||||
Assert.Contains(@$"rz-progressbar-info", component.Markup);
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add(p => p.ProgressBarStyle, ProgressBarStyle.Success));
|
||||
component.SetParametersAndRender(parameters => parameters.Add(p => p.Mode, ProgressBarMode.Indeterminate));
|
||||
Assert.Contains(@$"rz-progressbar-indeterminate-success", component.Markup);
|
||||
Assert.Contains(@$"rz-progressbar-success", component.Markup);
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add(p => p.ProgressBarStyle, ProgressBarStyle.Info));
|
||||
component.SetParametersAndRender(parameters => parameters.Add(p => p.Mode, ProgressBarMode.Indeterminate));
|
||||
Assert.Contains(@$"rz-progressbar-indeterminate-info", component.Markup);
|
||||
Assert.Contains(@$"rz-progressbar-info", component.Markup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
117
Radzen.Blazor.Tests/PropertyAccessTests.cs
Normal file
117
Radzen.Blazor.Tests/PropertyAccessTests.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Tests
|
||||
{
|
||||
public class PropertyAccessTests
|
||||
{
|
||||
public partial class TestData
|
||||
{
|
||||
public string PROPERTY { get; set; }
|
||||
public string Property { get; set; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Getter_With_DifferentTargetType()
|
||||
{
|
||||
var o = new TestData { Property = "test" };
|
||||
var getter = PropertyAccess.Getter<object, object>(nameof(TestData.Property), typeof(TestData));
|
||||
var value = getter(o);
|
||||
Assert.Equal(o.Property, value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Getter_With_Members_That_Differ_Only_In_Casing()
|
||||
{
|
||||
var o = new TestData { PROPERTY = nameof(TestData.PROPERTY), Property = nameof(TestData.Property) };
|
||||
var getter = PropertyAccess.Getter<TestData, string>(nameof(TestData.PROPERTY));
|
||||
var value = getter(o);
|
||||
Assert.Equal(nameof(TestData.PROPERTY), value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Getter_Resolves_Property_On_Simple_Object()
|
||||
{
|
||||
var o = new SimpleObject() { Prop1 = "TestString" };
|
||||
var getter = PropertyAccess.Getter<SimpleObject, string>("Prop1");
|
||||
var value = getter(o);
|
||||
Assert.Equal("TestString", value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Getter_Resolves_Property_On_Simple_Object_QueryableType()
|
||||
{
|
||||
var _data = new List<SimpleObject>()
|
||||
{
|
||||
new SimpleObject() { Prop1 = "TestString" },
|
||||
};
|
||||
|
||||
Func<object, object> getter = PropertyAccess.Getter<object, object>("Prop1", typeof(SimpleObject));
|
||||
|
||||
var value = getter(_data[0]);
|
||||
Assert.Equal("TestString", value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Getter_Resolves_Property_On_Nested_Object()
|
||||
{
|
||||
var o = new NestedObject() { Obj = new SimpleObject { Prop1 = "TestString" } };
|
||||
var getter = PropertyAccess.Getter<NestedObject, string>("Obj.Prop1");
|
||||
var value = getter(o);
|
||||
Assert.Equal("TestString", value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Getter_Resolves_Property_From_Array()
|
||||
{
|
||||
var o = new ArrayObject() { Values = new string[] { "1", "2", "3" } };
|
||||
var getter = PropertyAccess.Getter<ArrayObject, string>("Values[1]");
|
||||
var value = getter(o);
|
||||
Assert.Equal("2", value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Getter_Resolves_Property_From_Nested_Array()
|
||||
{
|
||||
var o = new NestedArrayObject() { Obj = new ArrayObject() { Values = new string[] { "1", "2", "3" } } };
|
||||
var getter = PropertyAccess.Getter<NestedArrayObject, string>("Obj.Values[2]");
|
||||
var value = getter(o);
|
||||
Assert.Equal("3", value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Getter_Resolves_Property_From_List()
|
||||
{
|
||||
var o = new ListObject() { Values = new List<string>() { "1", "2", "3" } };
|
||||
var getter = PropertyAccess.Getter<ListObject, string>("Values[1]");
|
||||
var value = getter(o);
|
||||
Assert.Equal("2", value);
|
||||
}
|
||||
|
||||
public class SimpleObject
|
||||
{
|
||||
public string Prop1 { get; set; }
|
||||
}
|
||||
|
||||
public class NestedObject
|
||||
{
|
||||
public SimpleObject Obj { get; set; }
|
||||
}
|
||||
|
||||
public class ArrayObject
|
||||
{
|
||||
public string[] Values { get; set; }
|
||||
}
|
||||
|
||||
public class NestedArrayObject
|
||||
{
|
||||
public ArrayObject Obj { get; set; }
|
||||
}
|
||||
|
||||
public class ListObject
|
||||
{
|
||||
public List<string> Values { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7</TargetFramework>
|
||||
<TargetFramework>net9</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="bunit.web" Version="1.2.49" />
|
||||
<PackageReference Include="bunit.web" Version="1.36.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
25
Radzen.Blazor.Tests/Rendering/StepGeneratorTests.cs
Normal file
25
Radzen.Blazor.Tests/Rendering/StepGeneratorTests.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System.Collections.Generic;
|
||||
using Radzen.Blazor.Rendering;
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Tests.Rendering;
|
||||
|
||||
public class StepGeneratorTests
|
||||
{
|
||||
[Fact]
|
||||
public void Renders_Path_Correctly()
|
||||
{
|
||||
var data = new List<Point>
|
||||
{
|
||||
new() { X = 10, Y = 10 },
|
||||
new() { X = 20, Y = 15 },
|
||||
new() { X = 30, Y = 20 },
|
||||
new() { X = 40, Y = 25 },
|
||||
new() { X = 50, Y = 50 }
|
||||
};
|
||||
|
||||
var path = new StepGenerator().Path(data);
|
||||
|
||||
Assert.Equal("10 10 H 20 V 15 H 30 V 20 H 40 V 25 H 50 V 50", path);
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ namespace Radzen.Blazor.Tests
|
||||
component.SetParametersAndRender(parameters => parameters.Add<int>(p => p.Value, value));
|
||||
|
||||
Assert.Contains(@$"style=""width: {Math.Round((value / max * 100)).ToInvariantString()}%;""", component.Markup);
|
||||
Assert.Contains(@$"style=""left: {Math.Round((value / max * 100)).ToInvariantString()}%;""", component.Markup);
|
||||
Assert.Contains(@$"style=""inset-inline-start: {Math.Round((value / max * 100)).ToInvariantString()}%;""", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -55,9 +55,9 @@ namespace Radzen.Blazor.Tests
|
||||
parameters.Add<IEnumerable<int>>(p => p.Value, new int[] { 4, 30 });
|
||||
});
|
||||
|
||||
Assert.Contains(@$"left: 4%", component.Markup);
|
||||
Assert.Contains(@$"left: 30%", component.Markup);
|
||||
Assert.Contains(@$"left: 4%; width: 26%;", component.Markup);
|
||||
Assert.Contains(@$"inset-inline-start: 4%", component.Markup);
|
||||
Assert.Contains(@$"inset-inline-start: 30%", component.Markup);
|
||||
Assert.Contains(@$"inset-inline-start: 4%; width: 26%;", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
197
Radzen.Blazor.Tests/SpeechToTextButtonTests.cs
Normal file
197
Radzen.Blazor.Tests/SpeechToTextButtonTests.cs
Normal file
@@ -0,0 +1,197 @@
|
||||
using Bunit;
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Tests
|
||||
{
|
||||
public class SpeechToTextButtonTests
|
||||
{
|
||||
[Fact]
|
||||
public void SpeechToTextButton_Renders_Record_Button_When_Visible()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
var component = ctx.RenderComponent<RadzenSpeechToTextButton>();
|
||||
|
||||
component.Render();
|
||||
|
||||
var recordButton = component.Find("button.rz-button-icon-only.rz-speech-to-text-button");
|
||||
|
||||
Assert.NotNull(recordButton);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpeechToTextButton_Does_Not_Renders_Record_Button_When_Visible_False()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
var component = ctx.RenderComponent<RadzenSpeechToTextButton>();
|
||||
|
||||
component.SetParametersAndRender(parameters =>
|
||||
{
|
||||
parameters.Add(p => p.Visible, false);
|
||||
});
|
||||
|
||||
Assert.Throws<ElementNotFoundException>(() => component.Find("button.rz-button-icon-only.rz-speech-to-text-button"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpeechToTextButton_Renders_Additional_Css()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
var component =
|
||||
ctx.RenderComponent<RadzenSpeechToTextButton>(ComponentParameter.CreateParameter("class", "another-class"));
|
||||
|
||||
var recordButton = component.Find("button.rz-button-icon-only.rz-speech-to-text-button.another-class");
|
||||
|
||||
Assert.NotNull(recordButton);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpeechToTextButton_Can_Override_Default_Title_And_Aria_Label()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
var component =
|
||||
ctx.RenderComponent<RadzenSpeechToTextButton>(
|
||||
ComponentParameter.CreateParameter("title", "title override"),
|
||||
ComponentParameter.CreateParameter("aria-label", "aria-label override"));
|
||||
|
||||
var recordButton = component.Find("button.rz-button-icon-only.rz-speech-to-text-button");
|
||||
|
||||
Assert.NotNull(recordButton);
|
||||
Assert.Equal("title override", recordButton.GetAttribute("title"));
|
||||
Assert.Equal("aria-label override", recordButton.GetAttribute("aria-label"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpeechToTextButton_Sets_Record_Button_Css_When_Record_Button_Clicked()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
var component = ctx.RenderComponent<RadzenSpeechToTextButton>();
|
||||
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
|
||||
component.Render();
|
||||
|
||||
var recordButton = component.Find("button.rz-button-icon-only.rz-speech-to-text-button");
|
||||
|
||||
Assert.NotNull(recordButton);
|
||||
|
||||
recordButton.Click();
|
||||
|
||||
component.Render();
|
||||
|
||||
var blinkingRecordButton = component.Find("button.rz-button-icon-only.rz-speech-to-text-button-recording");
|
||||
|
||||
Assert.NotNull(blinkingRecordButton);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpeechToTextButton_Sets_StopTitle_When_Record_Button_Clicked()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
var component = ctx.RenderComponent<RadzenSpeechToTextButton>();
|
||||
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
|
||||
component.Render();
|
||||
|
||||
var recordButton = component.Find("button.rz-button-icon-only.rz-speech-to-text-button");
|
||||
|
||||
Assert.NotNull(recordButton);
|
||||
|
||||
recordButton.Click();
|
||||
|
||||
component.Render();
|
||||
|
||||
var blinkingRecordButton = component.Find("button.rz-button-icon-only.rz-speech-to-text-button-recording");
|
||||
|
||||
Assert.Equal(component.Instance.StopTitle, blinkingRecordButton.GetAttribute("title"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpeechToTextButton_ChangesIconWhen_When_Record_Button_Clicked()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
var component = ctx.RenderComponent<RadzenSpeechToTextButton>();
|
||||
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
|
||||
component.Render();
|
||||
|
||||
var recordButton = component.Find("button.rz-button-icon-only.rz-speech-to-text-button");
|
||||
|
||||
Assert.NotNull(recordButton);
|
||||
|
||||
recordButton.Click();
|
||||
|
||||
component.Render();
|
||||
|
||||
var blinkingRecordButton = component.Find("button span");
|
||||
|
||||
Assert.Contains("stop", blinkingRecordButton.TextContent);
|
||||
Assert.DoesNotContain("mic", blinkingRecordButton.TextContent);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpeechToTextButton_UnSets_Record_Button_Css_When_Record_Button_Clicked_Twice()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
var component = ctx.RenderComponent<RadzenSpeechToTextButton>();
|
||||
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
|
||||
component.Render();
|
||||
|
||||
var recordButton = component.Find("button.rz-button-icon-only.rz-speech-to-text-button");
|
||||
|
||||
Assert.NotNull(recordButton);
|
||||
|
||||
recordButton.Click();
|
||||
|
||||
component.Render();
|
||||
|
||||
const string blinkingRecordButtonSelector = "button.rz-button-icon-only.rz-speech-to-text-button-recording";
|
||||
var blinkingRecordButton = component.Find(blinkingRecordButtonSelector);
|
||||
|
||||
Assert.NotNull(blinkingRecordButton);
|
||||
|
||||
blinkingRecordButton.Click();
|
||||
|
||||
component.Render();
|
||||
|
||||
Assert.Throws<ElementNotFoundException>(() => component.Find(blinkingRecordButtonSelector));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpeechToTextButton_Invokes_OnResult_FromJs()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
|
||||
var component = ctx.RenderComponent<RadzenSpeechToTextButton>();
|
||||
|
||||
string resultsFromJs = null;
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add(p => p.Change, r => resultsFromJs = r));
|
||||
|
||||
var recordButton = component.Find("button.rz-button-icon-only.rz-speech-to-text-button");
|
||||
|
||||
Assert.NotNull(recordButton);
|
||||
|
||||
recordButton.Click();
|
||||
|
||||
const string speechResults = "results from js";
|
||||
|
||||
component.InvokeAsync(() => component.Instance.OnResult(speechResults));
|
||||
|
||||
Assert.Equal(speechResults, resultsFromJs);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Bunit;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Tests
|
||||
@@ -44,7 +45,7 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add(p => p.Icon, icon));
|
||||
|
||||
Assert.Contains(@$"<i class=""rz-button-icon-left rzi"">{icon}</i>", component.Markup);
|
||||
Assert.Contains(@$"<i class=""notranslate rz-button-icon-left rzi"">{icon}</i>", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -59,10 +60,10 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
component.SetParametersAndRender(parameters => {
|
||||
parameters.Add(p => p.Text, text);
|
||||
parameters.Add(p => p.Icon, icon);
|
||||
parameters.Add(p => p.Icon, icon);
|
||||
});
|
||||
|
||||
Assert.Contains(@$"<i class=""rz-button-icon-left rzi"">{icon}</i>", component.Markup);
|
||||
Assert.Contains(@$"<i class=""notranslate rz-button-icon-left rzi"">{icon}</i>", component.Markup);
|
||||
Assert.Contains(@$"<span class=""rz-button-text"">{text}</span>", component.Markup);
|
||||
}
|
||||
|
||||
@@ -77,7 +78,7 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add(p => p.Image, image));
|
||||
|
||||
Assert.Contains(@$"<img class=""rz-button-icon-left rzi"" src=""{image}"" />", component.Markup);
|
||||
Assert.Contains(@$"<img class=""notranslate rz-button-icon-left rzi"" src=""{image}"" alt=""image"" />", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -93,12 +94,29 @@ namespace Radzen.Blazor.Tests
|
||||
component.SetParametersAndRender(parameters => {
|
||||
parameters.Add(p => p.Text, text);
|
||||
parameters.Add(p => p.Image, image);
|
||||
parameters.Add(p => p.ImageAlternateText, text);
|
||||
});
|
||||
|
||||
Assert.Contains(@$"<img class=""rz-button-icon-left rzi"" src=""{image}"" />", component.Markup);
|
||||
Assert.Contains(@$"<img class=""notranslate rz-button-icon-left rzi"" src=""{image}"" alt=""{text}"" />", component.Markup);
|
||||
Assert.Contains(@$"<span class=""rz-button-text"">{text}</span>", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SplitButton_Renders_ButtonContent()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
RenderFragment buttonContent = (builder) => builder.AddMarkupContent(0, "<strong>Custom button content</strong>");
|
||||
|
||||
var text = "Test";
|
||||
var component = ctx.RenderComponent<RadzenSplitButton>(parameters => parameters
|
||||
.Add(p => p.ButtonContent, buttonContent)
|
||||
.Add(p => p.Text, text));
|
||||
|
||||
Assert.Contains(@$"<strong>Custom button content</strong>", component.Markup);
|
||||
Assert.DoesNotContain(@$"<span class=""rz-button-text"">{text}</span>", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SplitButton_Renders_DisabledParameter()
|
||||
{
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add(p => p.Style, value));
|
||||
|
||||
Assert.Contains(@$"style=""outline: 0 none;{value}""", component.Markup);
|
||||
Assert.Contains(@$"style=""{value}""", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -134,5 +134,18 @@ namespace Radzen.Blazor.Tests
|
||||
Assert.True(raised);
|
||||
Assert.True(object.Equals(value, !(bool)newValue));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Switch_Renders_ReadOnlyParameter()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
var component = ctx.RenderComponent<RadzenSwitch>();
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add<bool>(p => p.ReadOnly, true));
|
||||
|
||||
Assert.Contains(@$"rz-readonly", component.Markup);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
35
Radzen.Blazor.Tests/Temperature.cs
Normal file
35
Radzen.Blazor.Tests/Temperature.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Radzen.Blazor.Tests;
|
||||
|
||||
public record struct Temperature(decimal DegreesCelsius)
|
||||
: IFormattable
|
||||
{
|
||||
public decimal Celsius => DegreesCelsius;
|
||||
public decimal Fahrenheit => DegreesCelsius * 9 / 5 + 32;
|
||||
public decimal Kelvin => DegreesCelsius + 273.15m;
|
||||
|
||||
public override string ToString() => ToString("G");
|
||||
public string ToString(string format) => ToString(format, CultureInfo.CurrentCulture);
|
||||
|
||||
public string ToString(string format, IFormatProvider provider)
|
||||
{
|
||||
provider ??= CultureInfo.CurrentCulture;
|
||||
if (string.IsNullOrEmpty(format))
|
||||
format = "G";
|
||||
|
||||
switch (format.ToUpperInvariant())
|
||||
{
|
||||
case "G":
|
||||
case "C":
|
||||
return $"{Celsius.ToString("F2", provider)} °C";
|
||||
case "F":
|
||||
return $"{Fahrenheit.ToString("F2", provider)} °F";
|
||||
case "K":
|
||||
return $"{Kelvin.ToString("F2", provider)} K";
|
||||
default:
|
||||
throw new FormatException($"The {format} format string is not supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,13 +119,54 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
var component = ctx.RenderComponent<RadzenTextBox>();
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add<bool>(p => p.AutoComplete, false));
|
||||
component.SetParametersAndRender(parameters => parameters.AddUnmatched("AutoComplete", false));
|
||||
|
||||
Assert.Contains(@$"autocomplete=""off""", component.Markup);
|
||||
Assert.Contains(@$"aria-autocomplete=""none""", component.Markup);
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.AddUnmatched("AutoComplete", true));
|
||||
|
||||
Assert.Contains(@$"autocomplete=""on""", component.Markup);
|
||||
Assert.DoesNotContain(@$"aria-autocomplete", component.Markup);
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.AddUnmatched("autocomplete", "custom"));
|
||||
|
||||
Assert.Contains(@$"autocomplete=""custom""", component.Markup);
|
||||
Assert.DoesNotContain(@$"aria-autocomplete", component.Markup);
|
||||
|
||||
component.Instance.DefaultAutoCompleteAttribute = "autocomplete-custom";
|
||||
component.SetParametersAndRender(parameters => parameters.AddUnmatched("AutoComplete", false));
|
||||
|
||||
Assert.Contains(@$"autocomplete=""autocomplete-custom""", component.Markup);
|
||||
Assert.Contains(@$"aria-autocomplete=""none""", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TextBox_Renders_TypedAutoCompleteParameter()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
|
||||
var component = ctx.RenderComponent<RadzenTextBox>();
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.AddUnmatched("AutoComplete", false));
|
||||
component.SetParametersAndRender(parameters => parameters.Add<AutoCompleteType>(p => p.AutoCompleteType, AutoCompleteType.On));
|
||||
|
||||
Assert.Contains(@$"autocomplete=""off""", component.Markup);
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.Add<bool>(p => p.AutoComplete, true));
|
||||
component.SetParametersAndRender(parameters => parameters.AddUnmatched("AutoComplete", true));
|
||||
component.SetParametersAndRender(parameters => parameters.Add<AutoCompleteType>(p => p.AutoCompleteType, AutoCompleteType.Off));
|
||||
|
||||
Assert.Contains(@$"autocomplete=""on""", component.Markup);
|
||||
Assert.Contains(@$"autocomplete=""off""", component.Markup);
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.AddUnmatched("AutoComplete", true));
|
||||
component.SetParametersAndRender(parameters => parameters.Add<AutoCompleteType>(p => p.AutoCompleteType, AutoCompleteType.AdditionalName));
|
||||
|
||||
Assert.Contains(@$"autocomplete=""{AutoCompleteType.AdditionalName.GetAutoCompleteValue()}""", component.Markup);
|
||||
|
||||
component.SetParametersAndRender(parameters => parameters.AddUnmatched("AutoComplete", true));
|
||||
component.SetParametersAndRender(parameters => parameters.Add<AutoCompleteType>(p => p.AutoCompleteType, AutoCompleteType.FamilyName));
|
||||
|
||||
Assert.Contains(@$"autocomplete=""{AutoCompleteType.FamilyName.GetAutoCompleteValue()}""", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
138
Radzen.Blazor/AutoCompleteType.cs
Normal file
138
Radzen.Blazor/AutoCompleteType.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
namespace Radzen.Blazor
|
||||
{
|
||||
/// <summary>
|
||||
/// The <c>AutomCompleteType</c> is a string-associated enum of
|
||||
/// browser-supported autocomplete attribute values.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class lists the autocomplete attirbute options allowing
|
||||
/// developers to provide the browser with guidance on how to pre-populate
|
||||
/// the form fields. It is a class rather than a simpler enum to associate
|
||||
/// each option with the string the browser expects. For more information
|
||||
/// please review the list of options (https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete)
|
||||
/// and the spec (https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofill).
|
||||
/// </remarks>
|
||||
public enum AutoCompleteType
|
||||
{
|
||||
/// <summary>Autocomplete is disabled. </summary>
|
||||
Off,
|
||||
/// <summary>Autocomplete is enabled. The browser chooses what values to suggest. </summary>
|
||||
On,
|
||||
/// <summary>The field expects the value to be a person's full name.</summary>
|
||||
Name,
|
||||
/// <summary>The prefix or title, such as "Mrs.", "Mr.", "Miss", "Ms.", "Dr." etc.</summary>
|
||||
HonorificPrefix,
|
||||
/// <summary>The given (or "first") name.</summary>
|
||||
GivenName,
|
||||
/// <summary>The middle name.</summary>
|
||||
AdditionalName,
|
||||
/// <summary>The family (or "last") name.</summary>
|
||||
FamilyName,
|
||||
/// <summary>The suffix, such as "Jr.", "B.Sc.", "PhD.", "MBASW", etc.</summary>
|
||||
HonorificSuffix,
|
||||
/// <summary>The nickname or handle.</summary>
|
||||
Nickname,
|
||||
/// <summary>The email address.</summary>
|
||||
Email,
|
||||
/// <summary>The username or account name.</summary>
|
||||
Username,
|
||||
/// <summary>A new password. When creating a new account or changing passwords.</summary>
|
||||
NewPassword,
|
||||
/// <summary>A current password. When filling in an existing password.</summary>
|
||||
CurrentPassword,
|
||||
/// <summary>A one-time code used for verifying user identity.</summary>
|
||||
OneTimeCode,
|
||||
/// <summary>A job title, or the title a person has within an organization, such as "Senior Technical Writer", "President", or "Assistant Troop Leader".</summary>
|
||||
OrganizationTitle,
|
||||
/// <summary>A company, business, or organization name.</summary>
|
||||
Organization,
|
||||
/// <summary>A street address. Use multiple address lines when more space is needed.</summary>
|
||||
StreetAddress,
|
||||
/// <summary>The line 1 of a street address. For example, "1234 Main Street".</summary>
|
||||
AddressLine1,
|
||||
/// <summary>The line 2 of a street address. For example, "Apartment 123".</summary>
|
||||
AddressLine2,
|
||||
/// <summary>The line 3 of a street address. For example, "c/o Jane Doe".</summary>
|
||||
AddressLine3,
|
||||
/// <summary>The city or locality.</summary>
|
||||
AddressLevel1,
|
||||
/// <summary>The state, province, prefecture, or region.</summary>
|
||||
AddressLevel2,
|
||||
/// <summary>The zip code or postal code.</summary>
|
||||
AddressLevel3,
|
||||
/// <summary>The country name.</summary>
|
||||
AddressLevel4,
|
||||
/// <summary>The country code.</summary>
|
||||
Country,
|
||||
/// <summary>The country name.</summary>
|
||||
CountryName,
|
||||
/// <summary>The postal code.</summary>
|
||||
PostalCode,
|
||||
/// <summary>The full name as printed on or associated with a payment instrument such as a credit card.</summary>
|
||||
CcName,
|
||||
/// <summary>The given (or "first") name as printed on or associated with a payment instrument such as a credit card.</summary>
|
||||
CcGivenName,
|
||||
/// <summary>The middle name as printed on or associated with a payment instrument such as a credit card.</summary>
|
||||
CcAdditionalName,
|
||||
/// <summary>The family (or "last") name as printed on or associated with a payment instrument such as a credit card.</summary>
|
||||
CcFamilyName,
|
||||
/// <summary>A credit card number or other number identifying a payment method, such as an account number.</summary>
|
||||
CcNumber,
|
||||
/// <summary>A payment method expiration date, typically in the form "MM/YY" or "MM/YYYY".</summary>
|
||||
CcExp,
|
||||
/// <summary>A payment method expiration month, typically in numeric form (MM).</summary>
|
||||
CcExpMonth,
|
||||
/// <summary>A payment method expiration year, typically in numeric form (YYYY).</summary>
|
||||
CcExpYear,
|
||||
/// <summary>The security code for your payment method, such as the CVV code.</summary>
|
||||
CcCsc,
|
||||
/// <summary>The type of payment instrument, such as "Visa", "Master Card", "Checking", or "Savings".</summary>
|
||||
CcType,
|
||||
/// <summary>The currency in which the transaction was completed. Use the ISO 4217 currency codes, such as "USD" for the US dollar.</summary>
|
||||
TransactionCurrency,
|
||||
/// <summary>The amount, in the currency specified by the transaction currency attribute, of the transaction completed.</summary>
|
||||
TransactionAmount,
|
||||
/// <summary>The language in which the transaction was completed. Use the relevant BCP 47 language tag.</summary>
|
||||
Language,
|
||||
/// <summary>A birth date, as a full date.</summary>
|
||||
Bday,
|
||||
/// <summary>The day of the month of a birth date.</summary>
|
||||
BdayDay,
|
||||
/// <summary>The month of the year of a birth date.</summary>
|
||||
BdayMonth,
|
||||
/// <summary>The year of a birth date.</summary>
|
||||
BdayYear,
|
||||
/// <summary>A gender identity (such as "Female", "Fa'afafine", "Hijra", "Male", "Nonbinary"), as freeform text without newlines.</summary>
|
||||
Sex,
|
||||
/// <summary>A full telephone number, including the country code. </summary>
|
||||
Tel,
|
||||
/// <summary>A country code, such as "1" for the United States, Canada, and other areas in North America and parts of the Caribbean.</summary>
|
||||
TelCountryCode,
|
||||
/// <summary>The entire phone number without the country code component, including a country-internal prefix.</summary>
|
||||
TelNational,
|
||||
/// <summary>The area code, with any country-internal prefix applied if appropriate.</summary>
|
||||
TelAreaCode,
|
||||
/// <summary>The phone number without the country or area code.</summary>
|
||||
TelLocal,
|
||||
/// <summary>The extension number, if applicable.</summary>
|
||||
TelExtension,
|
||||
/// <summary>A URL for an instant messaging protocol endpoint, such as "xmpp:username@example.net".</summary>
|
||||
Impp,
|
||||
/// <summary>A URL, such as a home page or company website address as appropriate given the context of the other fields in the form.</summary>
|
||||
Url,
|
||||
/// <summary>The URL of an image representing the person, company, or contact information given in the other fields in the form.</summary>
|
||||
Photo,
|
||||
/// <summary>State.</summary>
|
||||
State,
|
||||
/// <summary>Province.</summary>
|
||||
Province,
|
||||
/// <summary>Zip code.</summary>
|
||||
ZipCode,
|
||||
/// <summary>Firs name.</summary>
|
||||
FirstName,
|
||||
/// <summary>Middle name.</summary>
|
||||
MiddleName,
|
||||
/// <summary>Last name.</summary>
|
||||
LastName,
|
||||
}
|
||||
}
|
||||
@@ -101,6 +101,17 @@ namespace Radzen.Blazor
|
||||
/// <value><c>true</c> if visible; otherwise, <c>false</c>.</value>
|
||||
[Parameter]
|
||||
public bool Visible { get; set; } = true;
|
||||
/// <summary>
|
||||
/// Specifies the label rotation angle in degrees. Set to <c>null</c> by default which means no rotation is applied. Has higher precedence than <see cref="LabelAutoRotation"/>.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public double? LabelRotation { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the automatic label rotation angle in degrees. If set RadzenChart will automatically rotate the labels to fit the available space by the specified value. Has lower precedence than <see cref="LabelRotation"/>.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public double? LabelAutoRotation { get; set; } = null;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool ShouldRefreshChart(ParameterView parameters)
|
||||
@@ -108,6 +119,8 @@ namespace Radzen.Blazor
|
||||
return DidParameterChange(parameters, nameof(Min), Min) ||
|
||||
DidParameterChange(parameters, nameof(Max), Max) ||
|
||||
DidParameterChange(parameters, nameof(Visible), Visible) ||
|
||||
DidParameterChange(parameters, nameof(LabelRotation), LabelRotation) ||
|
||||
DidParameterChange(parameters, nameof(LabelAutoRotation), LabelAutoRotation) ||
|
||||
DidParameterChange(parameters, nameof(Step), Step);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,9 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using System.Linq;
|
||||
using System.Linq.Dynamic.Core;
|
||||
using Radzen.Blazor.Rendering;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections;
|
||||
using System.Net.Mime;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
|
||||
namespace Radzen.Blazor
|
||||
@@ -16,22 +15,34 @@ namespace Radzen.Blazor
|
||||
/// <typeparam name="TItem">The type of the series data.</typeparam>
|
||||
public abstract class CartesianSeries<TItem> : RadzenChartComponentBase, IChartSeries, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Cache for the value returned by <see cref="Category"/> when that value is only dependent on
|
||||
/// <see cref="CategoryProperty"/>.
|
||||
/// </summary>
|
||||
Func<TItem, double> categoryPropertyCache;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a getter function that returns a value from the specified category scale for the specified data item.
|
||||
/// </summary>
|
||||
/// <param name="scale">The scale.</param>
|
||||
internal Func<TItem, double> Category(ScaleBase scale)
|
||||
{
|
||||
if (categoryPropertyCache != null)
|
||||
{
|
||||
return categoryPropertyCache;
|
||||
}
|
||||
|
||||
if (IsNumeric(CategoryProperty))
|
||||
{
|
||||
return PropertyAccess.Getter<TItem, double>(CategoryProperty);
|
||||
categoryPropertyCache = PropertyAccess.Getter<TItem, double>(CategoryProperty);
|
||||
return categoryPropertyCache;
|
||||
}
|
||||
|
||||
if (IsDate(CategoryProperty))
|
||||
{
|
||||
var category = PropertyAccess.Getter<TItem, DateTime>(CategoryProperty);
|
||||
|
||||
return (item) => category(item).Ticks;
|
||||
categoryPropertyCache = (item) => category(item).Ticks;
|
||||
return categoryPropertyCache;
|
||||
}
|
||||
|
||||
if (scale is OrdinalScale ordinal)
|
||||
@@ -80,6 +91,12 @@ namespace Radzen.Blazor
|
||||
throw new ArgumentException($"Property {propertyName} does not exist");
|
||||
}
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
if(PropertyAccess.IsDateOnly(property))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return PropertyAccess.IsDate(property);
|
||||
}
|
||||
|
||||
@@ -351,6 +368,7 @@ namespace Radzen.Blazor
|
||||
var shouldRefresh = parameters.DidParameterChange(nameof(Data), Data);
|
||||
var visibleChanged = parameters.DidParameterChange(nameof(Visible), Visible);
|
||||
var hiddenChanged = parameters.DidParameterChange(nameof(Hidden), Hidden);
|
||||
var categoryChanged = parameters.DidParameterChange(nameof(CategoryProperty), CategoryProperty);
|
||||
|
||||
await base.SetParametersAsync(parameters);
|
||||
|
||||
@@ -366,6 +384,11 @@ namespace Radzen.Blazor
|
||||
shouldRefresh = true;
|
||||
}
|
||||
|
||||
if (categoryChanged || shouldRefresh)
|
||||
{
|
||||
categoryPropertyCache = null;
|
||||
}
|
||||
|
||||
if (Data != null && Data.Count() != Items.Count)
|
||||
{
|
||||
shouldRefresh = true;
|
||||
@@ -454,28 +477,74 @@ namespace Radzen.Blazor
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual RenderFragment RenderTooltip(object data, double marginLeft, double marginTop)
|
||||
public virtual RenderFragment RenderTooltip(object data)
|
||||
{
|
||||
var item = (TItem)data;
|
||||
|
||||
return builder =>
|
||||
{
|
||||
if (Chart.Tooltip.Shared)
|
||||
{
|
||||
var category = PropertyAccess.GetValue(item, CategoryProperty);
|
||||
builder.OpenComponent<ChartSharedTooltip>(0);
|
||||
builder.AddAttribute(1, nameof(ChartSharedTooltip.Class), TooltipClass(item));
|
||||
builder.AddAttribute(2, nameof(ChartSharedTooltip.Title), TooltipTitle(item));
|
||||
builder.AddAttribute(3, nameof(ChartSharedTooltip.ChildContent), RenderSharedTooltipItems(category));
|
||||
builder.CloseComponent();
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.OpenComponent<ChartTooltip>(0);
|
||||
builder.AddAttribute(1, nameof(ChartTooltip.ChildContent), TooltipTemplate?.Invoke(item));
|
||||
builder.AddAttribute(2, nameof(ChartTooltip.Title), TooltipTitle(item));
|
||||
builder.AddAttribute(3, nameof(ChartTooltip.Label), TooltipLabel(item));
|
||||
builder.AddAttribute(4, nameof(ChartTooltip.Value), TooltipValue(item));
|
||||
builder.AddAttribute(5, nameof(ChartTooltip.Class), TooltipClass(item));
|
||||
builder.AddAttribute(6, nameof(ChartTooltip.Style), TooltipStyle(item));
|
||||
builder.CloseComponent();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private RenderFragment RenderSharedTooltipItems(object category)
|
||||
{
|
||||
return builder =>
|
||||
{
|
||||
var visibleSeries = Chart.Series.Where(s => s.Visible).ToList();
|
||||
|
||||
foreach (var series in visibleSeries)
|
||||
{
|
||||
builder.AddContent(1, series.RenderSharedTooltipItem(category));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual RenderFragment RenderSharedTooltipItem(object category)
|
||||
{
|
||||
return builder =>
|
||||
{
|
||||
var item = Items.FirstOrDefault(i => object.Equals(PropertyAccess.GetValue(i, CategoryProperty), category));
|
||||
|
||||
if (item != null)
|
||||
{
|
||||
builder.OpenComponent<ChartSharedTooltipItem>(0);
|
||||
builder.AddAttribute(1, nameof(ChartSharedTooltipItem.Value), TooltipValue(item));
|
||||
builder.AddAttribute(2, nameof(ChartSharedTooltipItem.ChildContent), TooltipTemplate?.Invoke(item));
|
||||
builder.AddAttribute(3, nameof(ChartSharedTooltipItem.LegendItem), RenderLegendItem(false));
|
||||
builder.CloseComponent();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Point GetTooltipPosition(object data)
|
||||
{
|
||||
var item = (TItem)data;
|
||||
var x = TooltipX(item);
|
||||
var y = TooltipY(item);
|
||||
|
||||
return builder =>
|
||||
{
|
||||
builder.OpenComponent<ChartTooltip>(0);
|
||||
builder.AddAttribute(1, nameof(ChartTooltip.X), x + marginLeft);
|
||||
builder.AddAttribute(2, nameof(ChartTooltip.Y), y + marginTop);
|
||||
|
||||
builder.AddAttribute(3, nameof(ChartTooltip.ChildContent), TooltipTemplate == null ? null : TooltipTemplate(item));
|
||||
|
||||
builder.AddAttribute(4, nameof(ChartTooltip.Title), TooltipTitle(item));
|
||||
builder.AddAttribute(5, nameof(ChartTooltip.Label), TooltipLabel(item));
|
||||
builder.AddAttribute(6, nameof(ChartTooltip.Value), TooltipValue(item));
|
||||
builder.AddAttribute(7, nameof(ChartTooltip.Class), TooltipClass(item));
|
||||
builder.AddAttribute(8, nameof(ChartTooltip.Style), TooltipStyle(item));
|
||||
builder.CloseComponent();
|
||||
};
|
||||
return new Point { X = x, Y = y };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -498,6 +567,14 @@ namespace Radzen.Blazor
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual RenderFragment RenderLegendItem()
|
||||
{
|
||||
return RenderLegendItem(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders the legend item for this series.
|
||||
/// </summary>
|
||||
protected virtual RenderFragment RenderLegendItem(bool clickable)
|
||||
{
|
||||
var style = new List<string>();
|
||||
|
||||
@@ -516,6 +593,7 @@ namespace Radzen.Blazor
|
||||
builder.AddAttribute(5, nameof(LegendItem.MarkerSize), MarkerSize);
|
||||
builder.AddAttribute(6, nameof(LegendItem.Text), GetTitle());
|
||||
builder.AddAttribute(7, nameof(LegendItem.Click), EventCallback.Factory.Create(this, OnLegendItemClick));
|
||||
builder.AddAttribute(8, nameof(LegendItem.Clickable), clickable);
|
||||
builder.CloseComponent();
|
||||
};
|
||||
}
|
||||
@@ -545,13 +623,13 @@ namespace Radzen.Blazor
|
||||
/// <inheritdoc />
|
||||
public double GetMean()
|
||||
{
|
||||
return Data.Select(e => Value(e)).Average();
|
||||
return Data.Select(e => Value(e)).DefaultIfEmpty(double.NaN).Average();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public double GetMode()
|
||||
{
|
||||
return Data.GroupBy(e => Value(e)).Select(g => new { Value = g.Key, Count = g.Count() }).OrderByDescending(e => e.Count).FirstOrDefault().Value;
|
||||
return Data.Any() ? Data.GroupBy(e => Value(e)).Select(g => new { Value = g.Key, Count = g.Count() }).OrderByDescending(e => e.Count).FirstOrDefault().Value : double.NaN;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -559,35 +637,38 @@ namespace Radzen.Blazor
|
||||
/// </summary>
|
||||
public (double a, double b) GetTrend()
|
||||
{
|
||||
double a, b;
|
||||
double a = double.NaN, b = double.NaN;
|
||||
|
||||
Func<TItem, double> X;
|
||||
Func<TItem, double> Y;
|
||||
if (Chart.ShouldInvertAxes())
|
||||
if (Data.Any())
|
||||
{
|
||||
X = e => Chart.CategoryScale.Scale(Value(e));
|
||||
Y = e => Chart.ValueScale.Scale(Category(Chart.ValueScale)(e));
|
||||
}
|
||||
else
|
||||
{
|
||||
X = e => Chart.CategoryScale.Scale(Category(Chart.CategoryScale)(e));
|
||||
Y = e => Chart.ValueScale.Scale(Value(e));
|
||||
}
|
||||
Func<TItem, double> X;
|
||||
Func<TItem, double> Y;
|
||||
if (Chart.ShouldInvertAxes())
|
||||
{
|
||||
X = e => Chart.CategoryScale.Scale(Value(e));
|
||||
Y = e => Chart.ValueScale.Scale(Category(Chart.ValueScale)(e));
|
||||
}
|
||||
else
|
||||
{
|
||||
X = e => Chart.CategoryScale.Scale(Category(Chart.CategoryScale)(e));
|
||||
Y = e => Chart.ValueScale.Scale(Value(e));
|
||||
}
|
||||
|
||||
var avgX = Data.Select(e => X(e)).Average();
|
||||
var avgY = Data.Select(e => Y(e)).Average();
|
||||
var sumXY = Data.Sum(e => (X(e) - avgX) * (Y(e) - avgY));
|
||||
if (Chart.ShouldInvertAxes())
|
||||
{
|
||||
var sumYSq = Data.Sum(e => (Y(e) - avgY) * (Y(e) - avgY));
|
||||
b = sumXY / sumYSq;
|
||||
a = avgX - b * avgY;
|
||||
}
|
||||
else
|
||||
{
|
||||
var sumXSq = Data.Sum(e => (X(e) - avgX) * (X(e) - avgX));
|
||||
b = sumXY / sumXSq;
|
||||
a = avgY - b * avgX;
|
||||
var avgX = Data.Select(e => X(e)).Average();
|
||||
var avgY = Data.Select(e => Y(e)).Average();
|
||||
var sumXY = Data.Sum(e => (X(e) - avgX) * (Y(e) - avgY));
|
||||
if (Chart.ShouldInvertAxes())
|
||||
{
|
||||
var sumYSq = Data.Sum(e => (Y(e) - avgY) * (Y(e) - avgY));
|
||||
b = sumXY / sumYSq;
|
||||
a = avgX - b * avgY;
|
||||
}
|
||||
else
|
||||
{
|
||||
var sumXSq = Data.Sum(e => (X(e) - avgX) * (X(e) - avgX));
|
||||
b = sumXY / sumXSq;
|
||||
a = avgY - b * avgX;
|
||||
}
|
||||
}
|
||||
|
||||
return (a, b);
|
||||
@@ -669,29 +750,32 @@ namespace Radzen.Blazor
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual object DataAt(double x, double y)
|
||||
public virtual (object, Point) DataAt(double x, double y)
|
||||
{
|
||||
if (Items.Any())
|
||||
{
|
||||
return Items.Select(item =>
|
||||
var retObject = Items.Select(item =>
|
||||
{
|
||||
var distance = Math.Abs(TooltipX(item) - x);
|
||||
return new { Item = item, Distance = distance };
|
||||
}).Aggregate((a, b) => a.Distance < b.Distance ? a : b).Item;
|
||||
|
||||
return (retObject,
|
||||
new Point() { X = TooltipX(retObject), Y = TooltipY(retObject)});
|
||||
}
|
||||
|
||||
return null;
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEnumerable<ChartDataLabel> GetDataLabels(double offsetX, double offsetY)
|
||||
{
|
||||
var list = new List<ChartDataLabel>();
|
||||
|
||||
|
||||
foreach (var d in Data)
|
||||
{
|
||||
list.Add(new ChartDataLabel
|
||||
{
|
||||
list.Add(new ChartDataLabel
|
||||
{
|
||||
Position = new Point { X = TooltipX(d) + offsetX, Y = TooltipY(d) + offsetY },
|
||||
TextAnchor = "middle",
|
||||
Text = Chart.ValueAxis.Format(Chart.ValueScale, Value(d))
|
||||
@@ -707,16 +791,25 @@ namespace Radzen.Blazor
|
||||
/// <param name="index">The index.</param>
|
||||
/// <param name="colors">The colors.</param>
|
||||
/// <param name="defaultValue">The default value.</param>
|
||||
protected string PickColor(int index, IEnumerable<string> colors, string defaultValue = null)
|
||||
/// <param name="colorRange">The color range value.</param>
|
||||
/// <param name="value">The value of the item.</param>
|
||||
protected string PickColor(int index, IEnumerable<string> colors, string defaultValue = null, IList<SeriesColorRange> colorRange = null, double value = 0.0)
|
||||
{
|
||||
if (colors == null || !colors.Any())
|
||||
if (colorRange != null)
|
||||
{
|
||||
return defaultValue;
|
||||
var result = colorRange.Where(r => r.Min <= value && r.Max >= value).FirstOrDefault<SeriesColorRange>();
|
||||
return result != null ? result.Color : defaultValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (colors == null || !colors.Any())
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return colors.ElementAt(index % colors.Count());
|
||||
return colors.ElementAt(index % colors.Count());
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -65,11 +65,8 @@ namespace Radzen
|
||||
/// <param name="sender">The source of the event.</param>
|
||||
/// <param name="e">The <see cref="Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs"/> instance containing the event data.</param>
|
||||
private void UriHelper_OnLocationChanged(object sender, Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs e)
|
||||
{
|
||||
if (this.OnNavigate != null)
|
||||
{
|
||||
this.OnNavigate();
|
||||
}
|
||||
{
|
||||
this.OnNavigate?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -199,5 +196,30 @@ namespace Radzen
|
||||
/// </summary>
|
||||
/// <value>The value.</value>
|
||||
public object Value { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the icon.
|
||||
/// </summary>
|
||||
/// <value>The icon.</value>
|
||||
public string Icon { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the icon color.
|
||||
/// </summary>
|
||||
/// <value>The icon color.</value>
|
||||
public string IconColor { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the image.
|
||||
/// </summary>
|
||||
/// <value>The image.</value>
|
||||
public string Image { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the image style.
|
||||
/// </summary>
|
||||
/// <value>The image style.</value>
|
||||
public string ImageStyle { get; set; }
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is disabled.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is disabled; otherwise, <c>false</c>.</value>
|
||||
public bool Disabled { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
150
Radzen.Blazor/CookieThemeService.cs
Normal file
150
Radzen.Blazor/CookieThemeService.cs
Normal file
@@ -0,0 +1,150 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace Radzen
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the SameSite attribute for the cookie.
|
||||
/// </summary>
|
||||
public enum CookieSameSiteMode
|
||||
{
|
||||
/// <summary>
|
||||
/// No SameSite attribute.
|
||||
/// </summary>
|
||||
None,
|
||||
/// <summary>
|
||||
/// Lax SameSite attribute.
|
||||
/// </summary>
|
||||
Lax,
|
||||
/// <summary>
|
||||
/// Strict SameSite attribute.
|
||||
/// </summary>
|
||||
Strict
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Options for the <see cref="CookieThemeService" />.
|
||||
/// </summary>
|
||||
public class CookieThemeServiceOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the cookie name.
|
||||
/// </summary>
|
||||
public string Name { get; set; } = "Theme";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the cookie duration.
|
||||
/// </summary>
|
||||
public TimeSpan Duration { get; set; } = TimeSpan.FromDays(365);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to use secure cookies.
|
||||
/// </summary>
|
||||
public bool IsSecure { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the SameSite attribute for the cookie.
|
||||
/// </summary>
|
||||
public CookieSameSiteMode? SameSite { get; set; } = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Persist the current theme in a cookie. Requires <see cref="ThemeService" /> to be registered in the DI container.
|
||||
/// </summary>
|
||||
public class CookieThemeService
|
||||
{
|
||||
private readonly CookieThemeServiceOptions options;
|
||||
private readonly IJSRuntime jsRuntime;
|
||||
private readonly ThemeService themeService;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CookieThemeService" /> class.
|
||||
/// </summary>
|
||||
public CookieThemeService(IJSRuntime jsRuntime, ThemeService themeService, IOptions<CookieThemeServiceOptions> options)
|
||||
{
|
||||
this.jsRuntime = jsRuntime;
|
||||
this.themeService = themeService;
|
||||
this.options = options.Value;
|
||||
|
||||
themeService.ThemeChanged += OnThemeChanged;
|
||||
|
||||
_ = InitializeAsync();
|
||||
}
|
||||
|
||||
private async Task InitializeAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var cookies = await jsRuntime.InvokeAsync<string>("eval", "document.cookie");
|
||||
|
||||
var themeCookie = cookies?.Split("; ").Select(x =>
|
||||
{
|
||||
var parts = x.Split("=");
|
||||
|
||||
return (Key: parts[0], Value: parts[1]);
|
||||
})
|
||||
.FirstOrDefault(x => x.Key == options.Name);
|
||||
|
||||
var theme = themeCookie?.Value;
|
||||
|
||||
if (!string.IsNullOrEmpty(theme) && themeService.Theme != theme)
|
||||
{
|
||||
themeService.SetTheme(theme);
|
||||
}
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void OnThemeChanged()
|
||||
{
|
||||
var expiration = DateTime.Now.Add(options.Duration);
|
||||
var cookie = $"{options.Name}={themeService.Theme}; expires={expiration:R}; path=/";
|
||||
|
||||
if (options.SameSite.HasValue)
|
||||
{
|
||||
cookie += $"; SameSite={options.SameSite}";
|
||||
}
|
||||
|
||||
if (options.IsSecure)
|
||||
{
|
||||
cookie += "; Secure";
|
||||
}
|
||||
|
||||
_ = jsRuntime.InvokeVoidAsync("eval", $"document.cookie = \"{cookie}\"");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods to register the <see cref="CookieThemeService" />.
|
||||
/// </summary>
|
||||
public static class CookieThemeServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds the <see cref="CookieThemeService" /> to the service collection.
|
||||
/// </summary>
|
||||
public static IServiceCollection AddRadzenCookieThemeService(this IServiceCollection services)
|
||||
{
|
||||
services.AddOptions<CookieThemeServiceOptions>();
|
||||
services.AddScoped<CookieThemeService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the <see cref="CookieThemeService" /> to the service collection with the specified configuration.
|
||||
/// </summary>
|
||||
public static IServiceCollection AddRadzenCookieThemeService(this IServiceCollection services, Action<CookieThemeServiceOptions> configure)
|
||||
{
|
||||
services.Configure(configure);
|
||||
services.AddScoped<CookieThemeService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Dynamic.Core;
|
||||
using System.Linq.Expressions;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Radzen.Blazor;
|
||||
using Radzen.Blazor.Rendering;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Radzen
|
||||
{
|
||||
@@ -213,6 +212,32 @@ namespace Radzen
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the search text
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string SearchText
|
||||
{
|
||||
get
|
||||
{
|
||||
return searchText;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (searchText != value)
|
||||
{
|
||||
searchText = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the search text changed.
|
||||
/// </summary>
|
||||
/// <value>The search text changed.</value>
|
||||
[Parameter]
|
||||
public EventCallback<string> SearchTextChanged { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The search text
|
||||
/// </summary>
|
||||
@@ -234,23 +259,7 @@ namespace Radzen
|
||||
{
|
||||
if (!string.IsNullOrEmpty(searchText))
|
||||
{
|
||||
var ignoreCase = FilterCaseSensitivity == FilterCaseSensitivity.CaseInsensitive;
|
||||
|
||||
var query = new List<string>();
|
||||
|
||||
if (!string.IsNullOrEmpty(TextProperty))
|
||||
{
|
||||
query.Add(TextProperty);
|
||||
}
|
||||
|
||||
if (ignoreCase)
|
||||
{
|
||||
query.Add("ToLower()");
|
||||
}
|
||||
|
||||
query.Add($"{Enum.GetName(typeof(StringFilterOperator), FilterOperator)}(@0)");
|
||||
|
||||
_view = Query.Where(String.Join(".", query), ignoreCase ? searchText.ToLower() : searchText);
|
||||
_view = Query.Where(TextProperty, searchText, FilterOperator, FilterCaseSensitivity);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -288,20 +297,35 @@ namespace Radzen
|
||||
/// <returns>A Task representing the asynchronous operation.</returns>
|
||||
public override async Task SetParametersAsync(ParameterView parameters)
|
||||
{
|
||||
var searchTextChanged = parameters.DidParameterChange(nameof(SearchText), SearchText);
|
||||
if (searchTextChanged)
|
||||
{
|
||||
searchText = parameters.GetValueOrDefault<string>(SearchText);
|
||||
}
|
||||
|
||||
var dataChanged = parameters.DidParameterChange(nameof(Data), Data);
|
||||
|
||||
if (dataChanged)
|
||||
{
|
||||
await OnDataChanged();
|
||||
}
|
||||
|
||||
var disabledChanged = parameters.DidParameterChange(nameof(Disabled), Disabled);
|
||||
|
||||
var result = base.SetParametersAsync(parameters);
|
||||
|
||||
if (EditContext != null && ValueExpression != null && FieldIdentifier.Model != EditContext.Model)
|
||||
{
|
||||
FieldIdentifier = FieldIdentifier.Create(ValueExpression);
|
||||
EditContext.OnValidationStateChanged -= ValidationStateChanged;
|
||||
EditContext.OnValidationStateChanged += ValidationStateChanged;
|
||||
}
|
||||
|
||||
if (disabledChanged)
|
||||
{
|
||||
FormFieldContext?.DisabledChanged(Disabled);
|
||||
}
|
||||
|
||||
await result;
|
||||
}
|
||||
|
||||
@@ -334,7 +358,7 @@ namespace Radzen
|
||||
/// Gets the value.
|
||||
/// </summary>
|
||||
/// <returns>System.Object.</returns>
|
||||
public object GetValue()
|
||||
public virtual object GetValue()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
@@ -347,6 +371,35 @@ namespace Radzen
|
||||
/// <returns>ClassList.</returns>
|
||||
protected ClassList GetClassList(string className) => ClassList.Create(className)
|
||||
.AddDisabled(Disabled)
|
||||
.Add(FieldIdentifier, EditContext);
|
||||
.Add(FieldIdentifier, EditContext)
|
||||
.Add("rz-state-empty", !HasValue);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask FocusAsync()
|
||||
{
|
||||
await Element.FocusAsync();
|
||||
}
|
||||
|
||||
/// <summary> Provides support for RadzenFormField integration. </summary>
|
||||
[CascadingParameter]
|
||||
public IFormFieldContext FormFieldContext { get; set; }
|
||||
|
||||
/// <summary> Gets the current placeholder. Returns empty string if this component is inside a RadzenFormField.</summary>
|
||||
protected string CurrentPlaceholder => FormFieldContext?.AllowFloatingLabel == true ? " " : Placeholder;
|
||||
|
||||
/// <summary>
|
||||
/// Handles the <see cref="E:ContextMenu" /> event.
|
||||
/// </summary>
|
||||
/// <param name="args">The <see cref="MouseEventArgs"/> instance containing the event data.</param>
|
||||
/// <returns>Task.</returns>
|
||||
public override Task OnContextMenu(MouseEventArgs args)
|
||||
{
|
||||
if (!Disabled)
|
||||
{
|
||||
return base.OnContextMenu(args);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
using Microsoft.JSInterop;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Radzen
|
||||
@@ -24,7 +26,7 @@ namespace Radzen
|
||||
/// <div class="row">
|
||||
/// <div class="col-md-12">
|
||||
/// <RadzenButton Text="Ok" Click="() => ds.Close(true)" Style="margin-bottom: 10px; width: 150px" />
|
||||
/// <RadzenButton Text="Cancel" Click="() => ds.Close(false)" ButtonStyle="ButtonStyle.Secondary" Style="margin-bottom: 10px; width: 150px"/>
|
||||
/// <RadzenButton Text="Cancel" Click="() => ds.Close(false)" ButtonStyle="ButtonStyle.Base" Style="margin-bottom: 10px; width: 150px"/>
|
||||
/// <RadzenButton Text="Refresh" Click="(() => { orderID = 10249; ds.Refresh(); })" ButtonStyle="ButtonStyle.Info" Style="margin-bottom: 10px; width: 150px"/>
|
||||
/// Order ID: @orderID
|
||||
/// </div>
|
||||
@@ -76,10 +78,15 @@ namespace Radzen
|
||||
|
||||
private void UriHelper_OnLocationChanged(object sender, Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs e)
|
||||
{
|
||||
if (dialogs.Count > 0)
|
||||
while (dialogs.Any())
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
if (_sideDialogTask?.Task.IsCompleted == false)
|
||||
{
|
||||
CloseSide();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -97,18 +104,47 @@ namespace Radzen
|
||||
/// </summary>
|
||||
public event Action<string, Type, Dictionary<string, object>, DialogOptions> OnOpen;
|
||||
|
||||
/// <summary>
|
||||
/// Raises the Close event for the side dialog
|
||||
/// </summary>
|
||||
public event Action<dynamic> OnSideClose;
|
||||
|
||||
/// <summary>
|
||||
/// Raises the Open event for the side dialog
|
||||
/// </summary>
|
||||
public event Action<Type, Dictionary<string, object>, SideDialogOptions> OnSideOpen;
|
||||
|
||||
/// <summary>
|
||||
/// Opens a dialog with the specified arguments.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the Blazor component which will be displayed in a dialog.</typeparam>
|
||||
/// <param name="title">The text displayed in the title bar of the dialog.</param>
|
||||
/// <param name="parameters">The dialog parameters. Passed as property values of <typeparamref name="T" />.</param>
|
||||
/// <param name="parameters">The dialog parameters.</param>
|
||||
/// <param name="options">The dialog options.</param>
|
||||
public void Open<T>(string title, Dictionary<string, object> parameters = null, DialogOptions options = null) where T : ComponentBase
|
||||
public virtual void Open<T>(string title, Dictionary<string, object> parameters = null, DialogOptions options = null) where T : ComponentBase
|
||||
{
|
||||
OpenDialog<T>(title, parameters, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a dialog with the specified arguments.
|
||||
/// </summary>
|
||||
/// <param name="title">The text displayed in the title bar of the dialog.</param>
|
||||
/// <param name="componentType">The type of the component to be displayed in the dialog. Must inherit from <see cref="ComponentBase"/>.</param>
|
||||
/// <param name="parameters">The dialog parameters.</param>
|
||||
/// <param name="options">The dialog options.</param>
|
||||
public virtual void Open(string title, Type componentType, Dictionary<string, object> parameters = null, DialogOptions options = null)
|
||||
{
|
||||
if (!typeof(ComponentBase).IsAssignableFrom(componentType))
|
||||
{
|
||||
throw new ArgumentException("The component type must be a subclass of ComponentBase.", nameof(componentType));
|
||||
}
|
||||
|
||||
var method = GetType().GetMethod(nameof(OpenDialog), BindingFlags.Instance | BindingFlags.Public);
|
||||
|
||||
method.MakeGenericMethod(componentType).Invoke(this, new object[] { title, parameters, options });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes <see cref="OnRefresh" />.
|
||||
/// </summary>
|
||||
@@ -121,6 +157,7 @@ namespace Radzen
|
||||
/// The tasks
|
||||
/// </summary>
|
||||
protected List<TaskCompletionSource<dynamic>> tasks = new List<TaskCompletionSource<dynamic>>();
|
||||
private TaskCompletionSource<dynamic> _sideDialogTask;
|
||||
|
||||
/// <summary>
|
||||
/// Opens a dialog with the specified arguments.
|
||||
@@ -130,7 +167,7 @@ namespace Radzen
|
||||
/// <param name="parameters">The dialog parameters. Passed as property values of <typeparamref name="T" />.</param>
|
||||
/// <param name="options">The dialog options.</param>
|
||||
/// <returns>The value passed as argument to <see cref="Close" />.</returns>
|
||||
public Task<dynamic> OpenAsync<T>(string title, Dictionary<string, object> parameters = null, DialogOptions options = null) where T : ComponentBase
|
||||
public virtual Task<dynamic> OpenAsync<T>(string title, Dictionary<string, object> parameters = null, DialogOptions options = null) where T : ComponentBase
|
||||
{
|
||||
var task = new TaskCompletionSource<dynamic>();
|
||||
tasks.Add(task);
|
||||
@@ -140,6 +177,149 @@ namespace Radzen
|
||||
return task.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a dialog with the specified arguments dynamically.
|
||||
/// </summary>
|
||||
/// <param name="title">The text displayed in the title bar of the dialog.</param>
|
||||
/// <param name="componentType">The type of the Blazor component to be displayed in a dialog. Must inherit from <see cref="ComponentBase"/>.</param>
|
||||
/// <param name="parameters">The dialog parameters, passed as property values of the specified component.</param>
|
||||
/// <param name="options">The dialog options.</param>
|
||||
/// <returns>A task that represents the result passed as an argument to <see cref="Close"/>.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="componentType"/> does not inherit from <see cref="ComponentBase"/>.</exception>
|
||||
public virtual Task<dynamic> OpenAsync(string title, Type componentType, Dictionary<string, object> parameters = null, DialogOptions options = null)
|
||||
{
|
||||
if (!typeof(ComponentBase).IsAssignableFrom(componentType))
|
||||
{
|
||||
throw new ArgumentException("The component type must be a subclass of ComponentBase.", nameof(componentType));
|
||||
}
|
||||
|
||||
var task = new TaskCompletionSource<dynamic>();
|
||||
tasks.Add(task);
|
||||
|
||||
var method = GetType().GetMethod(nameof(OpenDialog), BindingFlags.Instance | BindingFlags.Public);
|
||||
|
||||
method.MakeGenericMethod(componentType).Invoke(this, new object[] { title, parameters, options });
|
||||
|
||||
return task.Task;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Opens a side dialog with the specified arguments
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of Blazor component which will be displayed in the side dialog.</typeparam>
|
||||
/// <param name="title">The text displayed in the title bar of the side dialog.</param>
|
||||
/// <param name="parameters">The dialog parameters. Passed as property values of <typeparamref name="T"/></param>
|
||||
/// <param name="options">The side dialog options.</param>
|
||||
/// <returns>A task that completes when the dialog is closed or a new one opened</returns>
|
||||
public Task<dynamic> OpenSideAsync<T>(string title, Dictionary<string, object> parameters = null, SideDialogOptions options = null)
|
||||
where T : ComponentBase
|
||||
{
|
||||
CloseSide();
|
||||
_sideDialogTask = new TaskCompletionSource<dynamic>();
|
||||
if (options == null)
|
||||
{
|
||||
options = new SideDialogOptions();
|
||||
}
|
||||
|
||||
options.Title = title;
|
||||
OnSideOpen?.Invoke(typeof(T), parameters ?? new Dictionary<string, object>(), options);
|
||||
return _sideDialogTask.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a side dialog with the specified arguments dynamically.
|
||||
/// </summary>
|
||||
/// <param name="title">The text displayed in the title bar of the side dialog.</param>
|
||||
/// <param name="componentType">The type of the Blazor component to be displayed in the side dialog. Must inherit from <see cref="ComponentBase"/>.</param>
|
||||
/// <param name="parameters">The dialog parameters, passed as property values of the specified component.</param>
|
||||
/// <param name="options">The side dialog options.</param>
|
||||
/// <returns>A task that represents the result passed as an argument to <see cref="CloseSide"/>.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="componentType"/> does not inherit from <see cref="ComponentBase"/>.</exception>
|
||||
public Task<dynamic> OpenSideAsync(string title, Type componentType, Dictionary<string, object> parameters = null, SideDialogOptions options = null)
|
||||
{
|
||||
if (!typeof(ComponentBase).IsAssignableFrom(componentType))
|
||||
{
|
||||
throw new ArgumentException("The component type must be a subclass of ComponentBase.", nameof(componentType));
|
||||
}
|
||||
|
||||
CloseSide();
|
||||
_sideDialogTask = new TaskCompletionSource<dynamic>();
|
||||
|
||||
if (options == null)
|
||||
{
|
||||
options = new SideDialogOptions();
|
||||
}
|
||||
|
||||
options.Title = title;
|
||||
OnSideOpen?.Invoke(componentType, parameters ?? new Dictionary<string, object>(), options);
|
||||
|
||||
return _sideDialogTask.Task;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Opens a side dialog with the specified arguments
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of Blazor component which will be displayed in the side dialog.</typeparam>
|
||||
/// <param name="title">The text displayed in the title bar of the side dialog.</param>
|
||||
/// <param name="parameters">The dialog parameters. Passed as property values of <typeparamref name="T"/></param>
|
||||
/// <param name="options">The side dialog options.</param>
|
||||
public void OpenSide<T>(string title, Dictionary<string, object> parameters = null, SideDialogOptions options = null)
|
||||
where T : ComponentBase
|
||||
{
|
||||
CloseSide();
|
||||
|
||||
if (options == null)
|
||||
{
|
||||
options = new SideDialogOptions();
|
||||
}
|
||||
|
||||
options.Title = title;
|
||||
OnSideOpen?.Invoke(typeof(T), parameters ?? new Dictionary<string, object>(), options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a side dialog with the specified arguments dynamically.
|
||||
/// </summary>
|
||||
/// <param name="title">The text displayed in the title bar of the side dialog.</param>
|
||||
/// <param name="componentType">The type of the Blazor component to be displayed in the side dialog. Must inherit from <see cref="ComponentBase"/>.</param>
|
||||
/// <param name="parameters">The dialog parameters, passed as property values of the specified component.</param>
|
||||
/// <param name="options">The side dialog options.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="componentType"/> does not inherit from <see cref="ComponentBase"/>.</exception>
|
||||
public void OpenSide(string title, Type componentType, Dictionary<string, object> parameters = null, SideDialogOptions options = null)
|
||||
{
|
||||
if (!typeof(ComponentBase).IsAssignableFrom(componentType))
|
||||
{
|
||||
throw new ArgumentException("The component type must be a subclass of ComponentBase.", nameof(componentType));
|
||||
}
|
||||
|
||||
CloseSide();
|
||||
|
||||
if (options == null)
|
||||
{
|
||||
options = new SideDialogOptions();
|
||||
}
|
||||
|
||||
options.Title = title;
|
||||
OnSideOpen?.Invoke(componentType, parameters ?? new Dictionary<string, object>(), options);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Closes the side dialog
|
||||
/// </summary>
|
||||
/// <param name="result">The result of the Dialog</param>
|
||||
public void CloseSide(dynamic result = null)
|
||||
{
|
||||
if (_sideDialogTask?.Task.IsCompleted == false)
|
||||
{
|
||||
_sideDialogTask.TrySetResult(result);
|
||||
}
|
||||
|
||||
OnSideClose?.Invoke(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a dialog with the specified content.
|
||||
/// </summary>
|
||||
@@ -147,7 +327,7 @@ namespace Radzen
|
||||
/// <param name="childContent">The content displayed in the dialog.</param>
|
||||
/// <param name="options">The dialog options.</param>
|
||||
/// <returns>The value passed as argument to <see cref="Close" />.</returns>
|
||||
public Task<dynamic> OpenAsync(string title, RenderFragment<DialogService> childContent, DialogOptions options = null)
|
||||
public virtual Task<dynamic> OpenAsync(string title, RenderFragment<DialogService> childContent, DialogOptions options = null)
|
||||
{
|
||||
var task = new TaskCompletionSource<dynamic>();
|
||||
tasks.Add(task);
|
||||
@@ -161,13 +341,36 @@ namespace Radzen
|
||||
return task.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a dialog with the specified content.
|
||||
/// </summary>
|
||||
/// <param name="titleContent">The content displayed in the title bar of the dialog.</param>
|
||||
/// <param name="childContent">The content displayed in the dialog.</param>
|
||||
/// <param name="options">The dialog options.</param>
|
||||
/// <returns>The value passed as argument to <see cref="Close" />.</returns>
|
||||
public virtual Task<dynamic> OpenAsync(RenderFragment<DialogService> titleContent, RenderFragment<DialogService> childContent, DialogOptions options = null)
|
||||
{
|
||||
var task = new TaskCompletionSource<dynamic>();
|
||||
tasks.Add(task);
|
||||
|
||||
options = options ?? new DialogOptions();
|
||||
|
||||
options.ChildContent = childContent;
|
||||
|
||||
options.TitleContent = titleContent;
|
||||
|
||||
OpenDialog<object>(null, null, options);
|
||||
|
||||
return task.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a dialog with the specified content.
|
||||
/// </summary>
|
||||
/// <param name="title">The text displayed in the title bar of the dialog.</param>
|
||||
/// <param name="childContent">The content displayed in the dialog.</param>
|
||||
/// <param name="options">The dialog options.</param>
|
||||
public void Open(string title, RenderFragment<DialogService> childContent, DialogOptions options = null)
|
||||
public virtual void Open(string title, RenderFragment<DialogService> childContent, DialogOptions options = null)
|
||||
{
|
||||
options = options ?? new DialogOptions();
|
||||
|
||||
@@ -196,11 +399,17 @@ namespace Radzen
|
||||
Resizable = options != null ? options.Resizable : false,
|
||||
Draggable = options != null ? options.Draggable : false,
|
||||
ChildContent = options?.ChildContent,
|
||||
TitleContent = options?.TitleContent,
|
||||
Style = options != null ? options.Style : "",
|
||||
AutoFocusFirstElement = options != null ? options.AutoFocusFirstElement : true,
|
||||
CloseDialogOnOverlayClick = options != null ? options.CloseDialogOnOverlayClick : false,
|
||||
CloseDialogOnEsc = options != null ? options.CloseDialogOnEsc : true,
|
||||
CssClass = options != null ? options.CssClass : "",
|
||||
WrapperCssClass = options != null ? options.WrapperCssClass : "",
|
||||
CloseTabIndex = options != null ? options.CloseTabIndex : 0,
|
||||
ContentCssClass = options != null ? options.ContentCssClass : "",
|
||||
Resize = options?.Resize,
|
||||
Drag = options?.Drag
|
||||
});
|
||||
}
|
||||
|
||||
@@ -243,7 +452,7 @@ namespace Radzen
|
||||
/// <param name="title">The text displayed in the title bar of the dialog.</param>
|
||||
/// <param name="options">The options.</param>
|
||||
/// <returns><c>true</c> if the user clicked the OK button, <c>false</c> otherwise.</returns>
|
||||
public async Task<bool?> Confirm(string message = "Confirm?", string title = "Confirm", ConfirmOptions options = null)
|
||||
public virtual async Task<bool?> Confirm(string message = "Confirm?", string title = "Confirm", ConfirmOptions options = null)
|
||||
{
|
||||
var dialogOptions = new DialogOptions()
|
||||
{
|
||||
@@ -262,10 +471,10 @@ namespace Radzen
|
||||
CloseDialogOnOverlayClick = options != null ? options.CloseDialogOnOverlayClick : false,
|
||||
CloseDialogOnEsc = options != null ? options.CloseDialogOnEsc : true,
|
||||
CssClass = options != null ? $"rz-dialog-confirm {options.CssClass}" : "rz-dialog-confirm",
|
||||
WrapperCssClass = options != null ? $"rz-dialog-wrapper {options.WrapperCssClass}" : "rz-dialog-wrapper",
|
||||
CloseTabIndex = options != null ? options.CloseTabIndex : 0,
|
||||
};
|
||||
|
||||
await JSRuntime.InvokeAsync<string>("Radzen.openDialog", dialogOptions, Reference);
|
||||
|
||||
return await OpenAsync(title, ds =>
|
||||
{
|
||||
RenderFragment content = b =>
|
||||
@@ -273,7 +482,7 @@ namespace Radzen
|
||||
var i = 0;
|
||||
b.OpenElement(i++, "p");
|
||||
b.AddAttribute(i++, "class", "rz-dialog-confirm-message");
|
||||
b.AddContent(i++, message);
|
||||
b.AddContent(i++, (MarkupString)message);
|
||||
b.CloseElement();
|
||||
|
||||
b.OpenElement(i++, "div");
|
||||
@@ -286,7 +495,7 @@ namespace Radzen
|
||||
|
||||
b.OpenComponent<Blazor.RadzenButton>(i++);
|
||||
b.AddAttribute(i++, "Text", options != null ? options.CancelButtonText : "Cancel");
|
||||
b.AddAttribute(i++, "ButtonStyle", ButtonStyle.Secondary);
|
||||
b.AddAttribute(i++, "ButtonStyle", ButtonStyle.Base);
|
||||
b.AddAttribute(i++, "Click", EventCallback.Factory.Create<Microsoft.AspNetCore.Components.Web.MouseEventArgs>(this, () => ds.Close(false)));
|
||||
b.CloseComponent();
|
||||
|
||||
@@ -303,7 +512,7 @@ namespace Radzen
|
||||
/// <param name="title">The text displayed in the title bar of the dialog.</param>
|
||||
/// <param name="options">The options.</param>
|
||||
/// <returns><c>true</c> if the user clicked the OK button, <c>false</c> otherwise.</returns>
|
||||
public async Task<bool?> Alert(string message = "", string title = "Message", AlertOptions options = null)
|
||||
public virtual async Task<bool?> Alert(string message = "", string title = "Message", AlertOptions options = null)
|
||||
{
|
||||
var dialogOptions = new DialogOptions()
|
||||
{
|
||||
@@ -322,10 +531,11 @@ namespace Radzen
|
||||
CloseDialogOnOverlayClick = options != null ? options.CloseDialogOnOverlayClick : false,
|
||||
CloseDialogOnEsc = options != null ? options.CloseDialogOnEsc : true,
|
||||
CssClass = options != null ? $"rz-dialog-alert {options.CssClass}" : "rz-dialog-alert",
|
||||
WrapperCssClass = options != null ? $"rz-dialog-wrapper {options.WrapperCssClass}" : "rz-dialog-wrapper",
|
||||
ContentCssClass = options != null ? $"rz-dialog-content {options.ContentCssClass}" : "rz-dialog-content",
|
||||
CloseTabIndex = options != null ? options.CloseTabIndex : 0,
|
||||
};
|
||||
|
||||
await JSRuntime.InvokeAsync<string>("Radzen.openDialog", dialogOptions, Reference);
|
||||
|
||||
return await OpenAsync(title, ds =>
|
||||
{
|
||||
RenderFragment content = b =>
|
||||
@@ -333,7 +543,7 @@ namespace Radzen
|
||||
var i = 0;
|
||||
b.OpenElement(i++, "p");
|
||||
b.AddAttribute(i++, "class", "rz-dialog-alert-message");
|
||||
b.AddContent(i++, message);
|
||||
b.AddContent(i++, (MarkupString)message);
|
||||
b.CloseElement();
|
||||
|
||||
b.OpenElement(i++, "div");
|
||||
@@ -352,9 +562,9 @@ namespace Radzen
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class DialogOptions.
|
||||
/// Base Class for dialog options
|
||||
/// </summary>
|
||||
public class DialogOptions
|
||||
public abstract class DialogOptionsBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to show the title bar. Set to <c>true</c> by default.
|
||||
@@ -367,17 +577,127 @@ namespace Radzen
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if the close button is shown; otherwise, <c>false</c>.</value>
|
||||
public bool ShowClose { get; set; } = true;
|
||||
/// <summary>
|
||||
/// Gets or sets the width of the dialog.
|
||||
/// </summary>
|
||||
/// <value>The width.</value>
|
||||
public string Width { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the height of the dialog.
|
||||
/// </summary>
|
||||
/// <value>The height.</value>
|
||||
public string Height { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the CSS style of the dialog
|
||||
/// </summary>
|
||||
/// <value>The style.</value>
|
||||
public string Style { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the dialog should be closed by clicking the overlay.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if closeable; otherwise, <c>false</c>.</value>
|
||||
public bool CloseDialogOnOverlayClick { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets dialog box custom class
|
||||
/// </summary>
|
||||
public string CssClass { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the CSS classes added to the dialog's wrapper element.
|
||||
/// </summary>
|
||||
public string WrapperCssClass { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the CSS classes added to the dialog's content element.
|
||||
/// </summary>
|
||||
public string ContentCssClass { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value the dialog escape tabindex. Set to <c>0</c> by default.
|
||||
/// </summary>
|
||||
public int CloseTabIndex { get; set; } = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class SideDialogOptions
|
||||
/// </summary>
|
||||
public class SideDialogOptions : DialogOptionsBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The title displayed on the dialog.
|
||||
/// </summary>
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Position on which the dialog will be positioned
|
||||
/// </summary>
|
||||
public DialogPosition Position { get; set; } = DialogPosition.Right;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to show a mask on the background or not
|
||||
/// </summary>
|
||||
public bool ShowMask { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to focus the first focusable HTML element. Set to <c>true</c> by default.
|
||||
/// </summary>
|
||||
public bool AutoFocusFirstElement { get; set; } = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DialogPosition enum
|
||||
/// </summary>
|
||||
public enum DialogPosition
|
||||
{
|
||||
/// <summary>
|
||||
/// Dialog will be positioned on the right side
|
||||
/// </summary>
|
||||
Right,
|
||||
/// <summary>
|
||||
/// Dialog will be positioned on the left side
|
||||
/// </summary>
|
||||
Left,
|
||||
/// <summary>
|
||||
/// Dialog will be positioned on the top of the page
|
||||
/// </summary>
|
||||
Top,
|
||||
/// <summary>
|
||||
/// Dialog will be positioned at the bottom of the page
|
||||
/// </summary>
|
||||
Bottom
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class DialogOptions.
|
||||
/// </summary>
|
||||
public class DialogOptions : DialogOptionsBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the dialog is resizable. Set to <c>false</c> by default.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if resizable; otherwise, <c>false</c>.</value>
|
||||
public bool Resizable { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the change.
|
||||
/// </summary>
|
||||
/// <value>The change.</value>
|
||||
public Action<Size> Resize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the dialog is draggable. Set to <c>false</c> by default.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if draggable; otherwise, <c>false</c>.</value>
|
||||
public bool Draggable { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the change.
|
||||
/// </summary>
|
||||
/// <value>The change.</value>
|
||||
public Action<Point> Drag { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the X coordinate of the dialog. Maps to the <c>left</c> CSS attribute.
|
||||
/// </summary>
|
||||
@@ -394,46 +714,24 @@ namespace Radzen
|
||||
/// <value>The bottom.</value>
|
||||
public string Bottom { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the width of the dialog.
|
||||
/// </summary>
|
||||
/// <value>The width.</value>
|
||||
public string Width { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the height of the dialog.
|
||||
/// </summary>
|
||||
/// <value>The height.</value>
|
||||
public string Height { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the CSS style of the dialog
|
||||
/// </summary>
|
||||
/// <value>The style.</value>
|
||||
public string Style { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the child content.
|
||||
/// </summary>
|
||||
/// <value>The child content.</value>
|
||||
public RenderFragment<DialogService> ChildContent { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the title content.
|
||||
/// </summary>
|
||||
/// <value>The title content.</value>
|
||||
public RenderFragment<DialogService> TitleContent { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to focus the first focusable HTML element. Set to <c>true</c> by default.
|
||||
/// </summary>
|
||||
public bool AutoFocusFirstElement { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the dialog should be closed by clicking the overlay.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if closeable; otherwise, <c>false</c>.</value>
|
||||
public bool CloseDialogOnOverlayClick { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the dialog should be closed on ESC key press.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if closeable; otherwise, <c>false</c>.</value>
|
||||
public bool CloseDialogOnEsc { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets dialog box custom class
|
||||
/// </summary>
|
||||
public string CssClass { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
using Microsoft.JSInterop;
|
||||
using Radzen.Blazor;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Dynamic.Core;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace Radzen
|
||||
{
|
||||
@@ -16,7 +16,12 @@ namespace Radzen
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class DropDownBase<T> : DataBoundFormComponent<T>
|
||||
{
|
||||
#if NET5_0_OR_GREATER
|
||||
/// <summary>
|
||||
/// Gets or sets a value that determines how many additional items will be rendered before and after the visible region. This help to reduce the frequency of rendering during scrolling. However, higher values mean that more elements will be present in the page.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public int VirtualizationOverscanCount { get; set; }
|
||||
|
||||
internal Microsoft.AspNetCore.Components.Web.Virtualization.Virtualize<object> virtualize;
|
||||
|
||||
/// <summary>
|
||||
@@ -39,14 +44,14 @@ namespace Radzen
|
||||
var totalItemsCount = LoadData.HasDelegate ? Count : view.Count();
|
||||
var top = request.Count;
|
||||
|
||||
if(top <= 0)
|
||||
if (top <= 0)
|
||||
{
|
||||
top = PageSize;
|
||||
}
|
||||
|
||||
if (LoadData.HasDelegate)
|
||||
{
|
||||
await LoadData.InvokeAsync(new Radzen.LoadDataArgs() { Skip = request.StartIndex, Top = request.Count, Filter = await JSRuntime.InvokeAsync<string>("Radzen.getInputValue", search) });
|
||||
await LoadData.InvokeAsync(new Radzen.LoadDataArgs() { Skip = request.StartIndex, Top = top, Filter = searchText });
|
||||
}
|
||||
|
||||
virtualItems = (LoadData.HasDelegate ? Data : view.Skip(request.StartIndex).Take(top)).Cast<object>().ToList();
|
||||
@@ -71,18 +76,19 @@ namespace Radzen
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public int PageSize { get; set; } = 5;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether virtualization is allowed.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if virtualization is allowed; otherwise, <c>false</c>.</returns>
|
||||
internal bool IsVirtualizationAllowed()
|
||||
{
|
||||
#if NET5_0_OR_GREATER
|
||||
return AllowVirtualization;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
internal int GetVirtualizationOverscanCount()
|
||||
{
|
||||
return VirtualizationOverscanCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -93,7 +99,6 @@ namespace Radzen
|
||||
{
|
||||
return new RenderFragment(builder =>
|
||||
{
|
||||
#if NET5_0_OR_GREATER
|
||||
if (AllowVirtualization)
|
||||
{
|
||||
builder.OpenComponent(0, typeof(Microsoft.AspNetCore.Components.Web.Virtualization.Virtualize<object>));
|
||||
@@ -106,6 +111,11 @@ namespace Radzen
|
||||
});
|
||||
}));
|
||||
|
||||
if (VirtualizationOverscanCount != default(int))
|
||||
{
|
||||
builder.AddAttribute(3, "OverscanCount", VirtualizationOverscanCount);
|
||||
}
|
||||
|
||||
builder.AddComponentReferenceCapture(7, c => { virtualize = (Microsoft.AspNetCore.Components.Web.Virtualization.Virtualize<object>)c; });
|
||||
|
||||
builder.CloseComponent();
|
||||
@@ -117,12 +127,6 @@ namespace Radzen
|
||||
RenderItem(builder, item);
|
||||
}
|
||||
}
|
||||
#else
|
||||
foreach (var item in LoadData.HasDelegate ? Data : View)
|
||||
{
|
||||
RenderItem(builder, item);
|
||||
}
|
||||
#endif
|
||||
});
|
||||
}
|
||||
|
||||
@@ -173,6 +177,13 @@ namespace Radzen
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the header template.
|
||||
/// </summary>
|
||||
/// <value>The header template.</value>
|
||||
[Parameter]
|
||||
public RenderFragment HeaderTemplate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether filtering is allowed. Set to <c>false</c> by default.
|
||||
/// </summary>
|
||||
@@ -180,6 +191,13 @@ namespace Radzen
|
||||
[Parameter]
|
||||
public virtual bool AllowFiltering { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether filtering is allowed as you type. Set to <c>true</c> by default.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if filtering is allowed; otherwise, <c>false</c>.</value>
|
||||
[Parameter]
|
||||
public virtual bool FilterAsYouType { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the user can clear the value. Set to <c>false</c> by default.
|
||||
/// </summary>
|
||||
@@ -222,17 +240,38 @@ namespace Radzen
|
||||
[Parameter]
|
||||
public string DisabledProperty { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the remove chip button title.
|
||||
/// </summary>
|
||||
/// <value>The remove chip button title.</value>
|
||||
[Parameter]
|
||||
public string RemoveChipTitle { get; set; } = "Remove";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the search aria label text.
|
||||
/// </summary>
|
||||
/// <value>The search aria label text.</value>
|
||||
[Parameter]
|
||||
public string SearchAriaLabel { get; set; } = "Search";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the empty value aria label text.
|
||||
/// </summary>
|
||||
/// <value>The empty value aria label text.</value>
|
||||
[Parameter]
|
||||
public string EmptyAriaLabel { get; set; } = "Empty";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the selected item changed.
|
||||
/// </summary>
|
||||
/// <value>The selected item changed.</value>
|
||||
[Parameter]
|
||||
public Action<object> SelectedItemChanged { get; set; }
|
||||
public EventCallback<object> SelectedItemChanged { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The selected items
|
||||
/// </summary>
|
||||
protected IList<object> selectedItems = new List<object>();
|
||||
protected ISet<object> selectedItems = new HashSet<object>();
|
||||
/// <summary>
|
||||
/// The selected item
|
||||
/// </summary>
|
||||
@@ -241,17 +280,17 @@ namespace Radzen
|
||||
/// <summary>
|
||||
/// Selects all.
|
||||
/// </summary>
|
||||
protected async System.Threading.Tasks.Task SelectAll()
|
||||
protected virtual async System.Threading.Tasks.Task SelectAll()
|
||||
{
|
||||
if (Disabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedItems.Count != View.Cast<object>().Count())
|
||||
if (selectedItems.Count != View.Cast<object>().ToList().Where(i => disabledPropertyGetter == null || disabledPropertyGetter(i) as bool? != true).Count())
|
||||
{
|
||||
selectedItems.Clear();
|
||||
selectedItems = View.Cast<object>().ToList();
|
||||
selectedItems = View.Cast<object>().ToList().Where(i => disabledPropertyGetter == null || disabledPropertyGetter(i) as bool? != true).ToHashSet(ItemComparer);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -278,6 +317,15 @@ namespace Radzen
|
||||
}
|
||||
await ValueChanged.InvokeAsync((T)(object)list);
|
||||
}
|
||||
else if (typeof(T).IsGenericType && typeof(ICollection<>).MakeGenericType(typeof(T).GetGenericArguments()[0]).IsAssignableFrom(typeof(T)))
|
||||
{
|
||||
var list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(typeof(T).GetGenericArguments()[0]));
|
||||
foreach (var i in (IEnumerable)internalValue)
|
||||
{
|
||||
list.Add(i);
|
||||
}
|
||||
await ValueChanged.InvokeAsync((T)(object)list);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ValueChanged.InvokeAsync((T)internalValue);
|
||||
@@ -286,16 +334,23 @@ namespace Radzen
|
||||
await Change.InvokeAsync(internalValue);
|
||||
|
||||
StateHasChanged();
|
||||
|
||||
await JSRuntime.InvokeVoidAsync("Radzen.focusElement", GetId());
|
||||
}
|
||||
|
||||
internal bool IsAllSelected()
|
||||
{
|
||||
List<object> notDisabledItemsInList = View.Cast<object>().ToList()
|
||||
.Where(i => disabledPropertyGetter == null || disabledPropertyGetter(i) as bool? != true)
|
||||
.ToList();
|
||||
|
||||
if (LoadData.HasDelegate && !string.IsNullOrEmpty(ValueProperty))
|
||||
{
|
||||
return View != null && View.Cast<object>().All(i => IsItemSelectedByValue(GetItemOrValueFromProperty(i, ValueProperty)));
|
||||
return View != null && notDisabledItemsInList.Count > 0 && notDisabledItemsInList
|
||||
.All(i => IsItemSelectedByValue(GetItemOrValueFromProperty(i, ValueProperty)));
|
||||
}
|
||||
|
||||
return View != null && selectedItems.Count == View.Cast<object>().Count();
|
||||
return View != null && notDisabledItemsInList.Count > 0 && selectedItems.Count == notDisabledItemsInList.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -316,6 +371,7 @@ namespace Radzen
|
||||
return;
|
||||
|
||||
searchText = null;
|
||||
await SearchTextChanged.InvokeAsync(searchText);
|
||||
await JSRuntime.InvokeAsync<string>("Radzen.setInputValue", search, "");
|
||||
|
||||
internalValue = default(T);
|
||||
@@ -362,7 +418,7 @@ namespace Radzen
|
||||
selectedItem = null;
|
||||
selectedItems.Clear();
|
||||
}
|
||||
|
||||
|
||||
InvokeAsync(OnDataChanged);
|
||||
}
|
||||
}
|
||||
@@ -379,28 +435,50 @@ namespace Radzen
|
||||
|
||||
var type = query.ElementType;
|
||||
|
||||
if (type == typeof(object) && typeof(EnumerableQuery).IsAssignableFrom(query.GetType()) && query.Any())
|
||||
{
|
||||
type = query.FirstOrDefault().GetType();
|
||||
if (type == typeof(object) && typeof(EnumerableQuery).IsAssignableFrom(query.GetType()) && query.Cast<object>().Any())
|
||||
{
|
||||
var firstElement = query.Cast<object>().FirstOrDefault(i => i != null);
|
||||
if (firstElement != null)
|
||||
{
|
||||
type = firstElement.GetType();
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(ValueProperty))
|
||||
{
|
||||
valuePropertyGetter = PropertyAccess.Getter<object, object>(ValueProperty, type);
|
||||
valuePropertyGetter = GetGetter(ValueProperty, type);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(TextProperty))
|
||||
{
|
||||
textPropertyGetter = PropertyAccess.Getter<object, object>(TextProperty, type);
|
||||
textPropertyGetter = GetGetter(TextProperty, type);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(DisabledProperty))
|
||||
{
|
||||
disabledPropertyGetter = PropertyAccess.Getter<object, object>(DisabledProperty, type);
|
||||
disabledPropertyGetter = GetGetter(DisabledProperty, type);
|
||||
}
|
||||
|
||||
if (selectedItems.Count == 0)
|
||||
{
|
||||
selectedItems = new HashSet<object>(ItemComparer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Func<object, object> GetGetter(string propertyName, Type type)
|
||||
{
|
||||
if (propertyName?.Contains("[") == true)
|
||||
{
|
||||
var getter = typeof(PropertyAccess).GetMethod("Getter", [typeof(string), typeof(Type)]);
|
||||
var getterMethod = getter.MakeGenericMethod([type, typeof(object)]);
|
||||
|
||||
return (i) => getterMethod.Invoke(i, [propertyName, type]);
|
||||
}
|
||||
|
||||
return PropertyAccess.Getter<object, object>(propertyName, type);
|
||||
}
|
||||
|
||||
internal Func<object, object> valuePropertyGetter;
|
||||
internal Func<object, object> textPropertyGetter;
|
||||
internal Func<object, object> disabledPropertyGetter;
|
||||
@@ -413,23 +491,31 @@ namespace Radzen
|
||||
/// <returns>System.Object.</returns>
|
||||
public object GetItemOrValueFromProperty(object item, string property)
|
||||
{
|
||||
if (property == TextProperty && textPropertyGetter != null)
|
||||
if (item != null)
|
||||
{
|
||||
return textPropertyGetter(item);
|
||||
}
|
||||
else if (property == ValueProperty && valuePropertyGetter != null)
|
||||
{
|
||||
return valuePropertyGetter(item);
|
||||
}
|
||||
else if (property == DisabledProperty && disabledPropertyGetter != null)
|
||||
{
|
||||
return disabledPropertyGetter(item);
|
||||
var enumValue = item as Enum;
|
||||
if (enumValue != null)
|
||||
{
|
||||
return Radzen.Blazor.EnumExtensions.GetDisplayDescription(enumValue);
|
||||
}
|
||||
|
||||
if (property == TextProperty)
|
||||
{
|
||||
return textPropertyGetter != null ? textPropertyGetter(item) : PropertyAccess.GetItemOrValueFromProperty(item, property);
|
||||
}
|
||||
else if (property == ValueProperty)
|
||||
{
|
||||
return valuePropertyGetter != null ? valuePropertyGetter(item) : PropertyAccess.GetItemOrValueFromProperty(item, property);
|
||||
}
|
||||
else if (property == DisabledProperty)
|
||||
{
|
||||
return disabledPropertyGetter != null ? disabledPropertyGetter(item) : PropertyAccess.GetItemOrValueFromProperty(item, property);
|
||||
}
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnDataChanged()
|
||||
{
|
||||
@@ -440,7 +526,6 @@ namespace Radzen
|
||||
await InvokeAsync(Virtualize.RefreshDataAsync);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Gets the popup identifier.
|
||||
@@ -458,7 +543,7 @@ namespace Radzen
|
||||
/// Gets the search identifier.
|
||||
/// </summary>
|
||||
/// <value>The search identifier.</value>
|
||||
protected string SearchID
|
||||
public string SearchID
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -534,12 +619,15 @@ namespace Radzen
|
||||
}
|
||||
}
|
||||
|
||||
internal bool preventKeydown = false;
|
||||
|
||||
/// <summary>
|
||||
/// Handles the key press.
|
||||
/// </summary>
|
||||
/// <param name="args">The <see cref="Microsoft.AspNetCore.Components.Web.KeyboardEventArgs"/> instance containing the event data.</param>
|
||||
/// <param name="isFilter">if set to <c>true</c> [is filter].</param>
|
||||
private async System.Threading.Tasks.Task HandleKeyPress(Microsoft.AspNetCore.Components.Web.KeyboardEventArgs args, bool isFilter = false)
|
||||
/// <param name="shouldSelectOnChange">Should select item on item change with keyboard.</param>
|
||||
protected virtual async System.Threading.Tasks.Task HandleKeyPress(Microsoft.AspNetCore.Components.Web.KeyboardEventArgs args, bool isFilter = false, bool? shouldSelectOnChange = null)
|
||||
{
|
||||
if (Disabled)
|
||||
return;
|
||||
@@ -557,9 +645,7 @@ namespace Radzen
|
||||
{
|
||||
if (IsVirtualizationAllowed())
|
||||
{
|
||||
#if NET5_0_OR_GREATER
|
||||
items = virtualItems;
|
||||
#endif
|
||||
items = virtualItems ?? Enumerable.Empty<object>().ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -571,48 +657,72 @@ namespace Radzen
|
||||
|
||||
if (!args.AltKey && (key == "ArrowDown" || key == "ArrowLeft" || key == "ArrowUp" || key == "ArrowRight"))
|
||||
{
|
||||
preventKeydown = true;
|
||||
|
||||
try
|
||||
{
|
||||
var currentViewIndex = Multiple ? selectedIndex : items.IndexOf(selectedItem);
|
||||
selectedIndex = await JSRuntime.InvokeAsync<int>("Radzen.focusListItem", search, list, key == "ArrowDown" || key == "ArrowRight", selectedIndex);
|
||||
|
||||
var newSelectedIndex = await JSRuntime.InvokeAsync<int>("Radzen.focusListItem", search, list, key == "ArrowDown" || key == "ArrowRight", currentViewIndex);
|
||||
var popupOpened = await JSRuntime.InvokeAsync<bool>("Radzen.popupOpened", PopupID);
|
||||
|
||||
if (!Multiple)
|
||||
if (!Multiple && !popupOpened && shouldSelectOnChange != false)
|
||||
{
|
||||
if (newSelectedIndex != currentViewIndex && newSelectedIndex >= 0 && newSelectedIndex <= items.Count() - 1)
|
||||
var itemToSelect = items.ElementAtOrDefault(selectedIndex);
|
||||
if (itemToSelect != null)
|
||||
{
|
||||
selectedIndex = newSelectedIndex;
|
||||
await OnSelectItem(items.ElementAt(selectedIndex), true);
|
||||
await OnSelectItem(itemToSelect, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedIndex = await JSRuntime.InvokeAsync<int>("Radzen.focusListItem", search, list, key == "ArrowDown", currentViewIndex);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
else if (Multiple && key == "Space")
|
||||
else if (key == "Enter" || key == "NumpadEnter")
|
||||
{
|
||||
preventKeydown = true;
|
||||
|
||||
if (selectedIndex >= 0 && selectedIndex <= items.Count() - 1)
|
||||
{
|
||||
var itemToSelect = items.ElementAtOrDefault(selectedIndex);
|
||||
|
||||
await JSRuntime.InvokeAsync<string>("Radzen.setInputValue", search, $"{searchText}".Trim());
|
||||
await OnSelectItem(items.ElementAt(selectedIndex), true);
|
||||
|
||||
if (itemToSelect != null)
|
||||
{
|
||||
await OnSelectItem(itemToSelect, true);
|
||||
}
|
||||
}
|
||||
|
||||
var popupOpened = await JSRuntime.InvokeAsync<bool>("Radzen.popupOpened", PopupID);
|
||||
|
||||
if (!popupOpened)
|
||||
{
|
||||
await OpenPopup(key, isFilter);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!Multiple)
|
||||
{
|
||||
await ClosePopup(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (key == "Enter" || (args.AltKey && key == "ArrowDown"))
|
||||
else if (args.AltKey && key == "ArrowDown")
|
||||
{
|
||||
preventKeydown = true;
|
||||
|
||||
await OpenPopup(key, isFilter);
|
||||
}
|
||||
else if (key == "Escape" || key == "Tab")
|
||||
{
|
||||
await JSRuntime.InvokeVoidAsync("Radzen.closePopup", PopupID);
|
||||
await ClosePopup(key);
|
||||
}
|
||||
else if (key == "Delete" && AllowClear)
|
||||
{
|
||||
preventKeydown = true;
|
||||
|
||||
if (!Multiple && selectedItem != null)
|
||||
{
|
||||
selectedIndex = -1;
|
||||
@@ -624,12 +734,59 @@ namespace Radzen
|
||||
Debounce(DebounceFilter, FilterDelay);
|
||||
}
|
||||
}
|
||||
else if (AllowFiltering && isFilter)
|
||||
else if (AllowFiltering && isFilter && FilterAsYouType)
|
||||
{
|
||||
preventKeydown = true;
|
||||
|
||||
Debounce(DebounceFilter, FilterDelay);
|
||||
}
|
||||
else
|
||||
{
|
||||
var filteredItems = (!string.IsNullOrEmpty(TextProperty) ?
|
||||
Query.Where(TextProperty, args.Key, StringFilterOperator.StartsWith, FilterCaseSensitivity.CaseInsensitive) :
|
||||
Query)
|
||||
.Cast(Query.ElementType).Cast<dynamic>().ToList();
|
||||
|
||||
|
||||
if (previousKey != args.Key)
|
||||
{
|
||||
previousKey = args.Key;
|
||||
itemIndex = -1;
|
||||
}
|
||||
|
||||
itemIndex = itemIndex + 1 >= filteredItems.Count() ? 0 : itemIndex + 1;
|
||||
var itemToSelect = filteredItems.ElementAtOrDefault(itemIndex);
|
||||
|
||||
if (itemToSelect is not null)
|
||||
{
|
||||
if (!Multiple)
|
||||
{
|
||||
await SelectItem(itemToSelect);
|
||||
}
|
||||
|
||||
var result = items.Select((x, i) => new { Item = x, Index = i }).FirstOrDefault(itemWithIndex => object.Equals(itemWithIndex.Item, itemToSelect));
|
||||
if (result != null)
|
||||
{
|
||||
if (!Multiple)
|
||||
{
|
||||
selectedIndex = result.Index;
|
||||
}
|
||||
await JSRuntime.InvokeVoidAsync("Radzen.selectListItem", list, list, result.Index);
|
||||
}
|
||||
}
|
||||
|
||||
preventKeydown = false;
|
||||
}
|
||||
}
|
||||
|
||||
internal virtual async Task ClosePopup(string key)
|
||||
{
|
||||
await JSRuntime.InvokeVoidAsync("Radzen.closePopup", PopupID);
|
||||
}
|
||||
|
||||
int itemIndex;
|
||||
string previousKey;
|
||||
|
||||
/// <summary>
|
||||
/// Handles the <see cref="E:FilterKeyPress" /> event.
|
||||
/// </summary>
|
||||
@@ -646,17 +803,14 @@ namespace Radzen
|
||||
{
|
||||
if (!LoadData.HasDelegate)
|
||||
{
|
||||
searchText = await JSRuntime.InvokeAsync<string>("Radzen.getInputValue", search);
|
||||
_view = null;
|
||||
if (IsVirtualizationAllowed())
|
||||
{
|
||||
#if NET5_0_OR_GREATER
|
||||
if (virtualize != null)
|
||||
{
|
||||
await virtualize.RefreshDataAsync();
|
||||
}
|
||||
await InvokeAsync(() => { StateHasChanged(); });
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -667,13 +821,15 @@ namespace Radzen
|
||||
{
|
||||
if (IsVirtualizationAllowed())
|
||||
{
|
||||
#if NET5_0_OR_GREATER
|
||||
if (virtualize != null)
|
||||
{
|
||||
await InvokeAsync(virtualize.RefreshDataAsync);
|
||||
}
|
||||
else
|
||||
{
|
||||
await LoadData.InvokeAsync(await GetLoadDataArgs());
|
||||
}
|
||||
await InvokeAsync(() => { StateHasChanged(); });
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -685,15 +841,17 @@ namespace Radzen
|
||||
selectedIndex = -1;
|
||||
|
||||
await JSRuntime.InvokeAsync<string>("Radzen.repositionPopup", Element, PopupID);
|
||||
await SearchTextChanged.InvokeAsync(SearchText);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the <see cref="E:KeyPress" /> event.
|
||||
/// </summary>
|
||||
/// <param name="args">The <see cref="Microsoft.AspNetCore.Components.Web.KeyboardEventArgs"/> instance containing the event data.</param>
|
||||
protected async System.Threading.Tasks.Task OnKeyPress(Microsoft.AspNetCore.Components.Web.KeyboardEventArgs args)
|
||||
/// <param name="shouldSelectOnChange">Should select item on item change with keyboard.</param>
|
||||
protected virtual async System.Threading.Tasks.Task OnKeyPress(Microsoft.AspNetCore.Components.Web.KeyboardEventArgs args, bool? shouldSelectOnChange = null)
|
||||
{
|
||||
await HandleKeyPress(args);
|
||||
await HandleKeyPress(args, false, shouldSelectOnChange);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -721,18 +879,14 @@ namespace Radzen
|
||||
/// <returns>LoadDataArgs.</returns>
|
||||
internal virtual async System.Threading.Tasks.Task<LoadDataArgs> GetLoadDataArgs()
|
||||
{
|
||||
#if NET5_0_OR_GREATER
|
||||
if (AllowVirtualization)
|
||||
{
|
||||
return new Radzen.LoadDataArgs() { Skip = 0, Top = PageSize, Filter = await JSRuntime.InvokeAsync<string>("Radzen.getInputValue", search) };
|
||||
return await Task.FromResult(new Radzen.LoadDataArgs() { Skip = 0, Top = PageSize, Filter = searchText });
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Radzen.LoadDataArgs() { Filter = await JSRuntime.InvokeAsync<string>("Radzen.getInputValue", search) };
|
||||
return await Task.FromResult(new Radzen.LoadDataArgs() { Filter = searchText });
|
||||
}
|
||||
#else
|
||||
return new Radzen.LoadDataArgs() { Filter = await JSRuntime.InvokeAsync<string>("Radzen.getInputValue", search) };
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -767,13 +921,12 @@ namespace Radzen
|
||||
/// <returns>A Task representing the asynchronous operation.</returns>
|
||||
public override async Task SetParametersAsync(ParameterView parameters)
|
||||
{
|
||||
#if NET5_0_OR_GREATER
|
||||
var pageSize = parameters.GetValueOrDefault<int>(nameof(PageSize));
|
||||
if(pageSize != default(int))
|
||||
if (pageSize != default(int))
|
||||
{
|
||||
PageSize = pageSize;
|
||||
}
|
||||
#endif
|
||||
|
||||
var selectedItemChanged = parameters.DidParameterChange(nameof(SelectedItem), SelectedItem);
|
||||
if (selectedItemChanged)
|
||||
{
|
||||
@@ -811,7 +964,7 @@ namespace Radzen
|
||||
|
||||
if (valueAsEnumerable != null)
|
||||
{
|
||||
if (valueAsEnumerable.OfType<object>().Count() != selectedItems.Count)
|
||||
if (!valueAsEnumerable.Cast<object>().SequenceEqual(selectedItems.Select(i => string.IsNullOrEmpty(ValueProperty) ? i : GetItemOrValueFromProperty(i, ValueProperty))))
|
||||
{
|
||||
selectedItems.Clear();
|
||||
}
|
||||
@@ -836,7 +989,7 @@ namespace Radzen
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <returns><c>true</c> if the specified item is selected; otherwise, <c>false</c>.</returns>
|
||||
internal bool isSelected(object item)
|
||||
internal bool IsSelected(object item)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(ValueProperty))
|
||||
{
|
||||
@@ -846,11 +999,11 @@ namespace Radzen
|
||||
{
|
||||
if (Multiple)
|
||||
{
|
||||
return selectedItems.IndexOf(item) != -1;
|
||||
return selectedItems.Contains(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
return object.Equals(item,selectedItem);
|
||||
return object.Equals(item, selectedItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -904,42 +1057,7 @@ namespace Radzen
|
||||
{
|
||||
if (_view == null && Query != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(searchText))
|
||||
{
|
||||
var ignoreCase = FilterCaseSensitivity == FilterCaseSensitivity.CaseInsensitive;
|
||||
|
||||
var query = new List<string>();
|
||||
|
||||
if (!string.IsNullOrEmpty(TextProperty))
|
||||
{
|
||||
query.Add(TextProperty);
|
||||
}
|
||||
|
||||
if (typeof(EnumerableQuery).IsAssignableFrom(Query.GetType()))
|
||||
{
|
||||
query.Add("ToString()");
|
||||
}
|
||||
|
||||
if (ignoreCase)
|
||||
{
|
||||
query.Add("ToLower()");
|
||||
}
|
||||
|
||||
query.Add($"{Enum.GetName(typeof(StringFilterOperator), FilterOperator)}(@0)");
|
||||
|
||||
_view = Query.Where(String.Join(".", query), ignoreCase ? searchText.ToLower() : searchText);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IsVirtualizationAllowed())
|
||||
{
|
||||
_view = Query;
|
||||
}
|
||||
else
|
||||
{
|
||||
_view = (typeof(IQueryable).IsAssignableFrom(Data.GetType())) ? (Query as IEnumerable).Cast<object>().ToList().AsQueryable() : Query;
|
||||
}
|
||||
}
|
||||
_view = Query.Where(TextProperty, searchText, FilterOperator, FilterCaseSensitivity);
|
||||
}
|
||||
|
||||
return _view;
|
||||
@@ -987,6 +1105,11 @@ namespace Radzen
|
||||
/// <param name="raiseChange">if set to <c>true</c> [raise change].</param>
|
||||
public async System.Threading.Tasks.Task SelectItem(object item, bool raiseChange = true)
|
||||
{
|
||||
if (disabledPropertyGetter != null && item != null && disabledPropertyGetter(item) as bool? == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Multiple)
|
||||
{
|
||||
if (object.Equals(item, selectedItem))
|
||||
@@ -995,7 +1118,7 @@ namespace Radzen
|
||||
selectedItem = item;
|
||||
if (!string.IsNullOrEmpty(ValueProperty))
|
||||
{
|
||||
internalValue = GetItemOrValueFromProperty(item, ValueProperty);
|
||||
internalValue = PropertyAccess.GetItemOrValueFromProperty(item, ValueProperty);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1004,7 +1127,7 @@ namespace Radzen
|
||||
|
||||
SetSelectedIndexFromSelectedItem();
|
||||
|
||||
SelectedItemChanged?.Invoke(selectedItem);
|
||||
await SelectedItemChanged.InvokeAsync(selectedItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1020,9 +1143,13 @@ namespace Radzen
|
||||
var query = Data.AsQueryable();
|
||||
var elementType = query.ElementType;
|
||||
|
||||
if (elementType == typeof(object) && typeof(EnumerableQuery).IsAssignableFrom(query.GetType()) && query.Any())
|
||||
if (elementType == typeof(object) && typeof(EnumerableQuery).IsAssignableFrom(query.GetType()) && query.Cast<object>().Any())
|
||||
{
|
||||
elementType = query.FirstOrDefault().GetType();
|
||||
var firstElement = query.Cast<object>().FirstOrDefault(i => i != null);
|
||||
if (firstElement != null)
|
||||
{
|
||||
elementType = firstElement.GetType();
|
||||
}
|
||||
}
|
||||
|
||||
if (elementType != null)
|
||||
@@ -1055,6 +1182,22 @@ namespace Radzen
|
||||
await ValueChanged.InvokeAsync((T)(object)list);
|
||||
}
|
||||
}
|
||||
else if (typeof(T).IsGenericType && typeof(ICollection<>).MakeGenericType(typeof(T).GetGenericArguments()[0]).IsAssignableFrom(typeof(T)))
|
||||
{
|
||||
if (object.Equals(internalValue, null))
|
||||
{
|
||||
await ValueChanged.InvokeAsync(default(T));
|
||||
}
|
||||
else
|
||||
{
|
||||
var list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(typeof(T).GetGenericArguments()[0]));
|
||||
foreach (var i in (IEnumerable)internalValue)
|
||||
{
|
||||
list.Add(i);
|
||||
}
|
||||
await ValueChanged.InvokeAsync((T)(object)list);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await ValueChanged.InvokeAsync(object.Equals(internalValue, null) ? default(T) : (T)internalValue);
|
||||
@@ -1068,6 +1211,12 @@ namespace Radzen
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object GetValue()
|
||||
{
|
||||
return internalValue;
|
||||
}
|
||||
|
||||
internal void UpdateSelectedItems(object item)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(ValueProperty))
|
||||
@@ -1080,18 +1229,14 @@ namespace Radzen
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedItems = selectedItems.AsQueryable().Where($@"!object.Equals({ValueProperty},@0)", value).ToList();
|
||||
selectedItems = selectedItems.AsQueryable().Where(i => !object.Equals(GetItemOrValueFromProperty(i, ValueProperty), value)).ToHashSet(ItemComparer);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!selectedItems.Any(i => object.Equals(i, item)))
|
||||
if (!selectedItems.Add(item))
|
||||
{
|
||||
selectedItems.Add(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedItems = selectedItems.Where(i => !object.Equals(i, item)).ToList();
|
||||
selectedItems.Remove(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1102,19 +1247,29 @@ namespace Radzen
|
||||
/// <param name="value">The value.</param>
|
||||
protected virtual void SelectItemFromValue(object value)
|
||||
{
|
||||
if (value != null && View != null)
|
||||
var view = LoadData.HasDelegate ? Data : View;
|
||||
if (value != null && view != null)
|
||||
{
|
||||
if (!Multiple)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(ValueProperty))
|
||||
{
|
||||
if (typeof(EnumerableQuery).IsAssignableFrom(View.GetType()))
|
||||
if (typeof(EnumerableQuery).IsAssignableFrom(view.GetType()))
|
||||
{
|
||||
SelectedItem = View.OfType<object>().Where(i => object.Equals(GetItemOrValueFromProperty(i, ValueProperty), value)).FirstOrDefault();
|
||||
SelectedItem = view.OfType<object>().Where(i => object.Equals(GetItemOrValueFromProperty(i, ValueProperty), value)).FirstOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedItem = View.AsQueryable().Where($@"{ValueProperty} == @0", value).FirstOrDefault();
|
||||
SelectedItem = view.AsQueryable().Where(new FilterDescriptor[]
|
||||
{
|
||||
new FilterDescriptor()
|
||||
{
|
||||
Property = ValueProperty,
|
||||
FilterValue = value
|
||||
}
|
||||
},
|
||||
LogicalFilterOperator.And,
|
||||
FilterCaseSensitivity.Default).FirstOrDefault();
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -1123,8 +1278,6 @@ namespace Radzen
|
||||
}
|
||||
|
||||
SetSelectedIndexFromSelectedItem();
|
||||
|
||||
SelectedItemChanged?.Invoke(selectedItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1133,20 +1286,29 @@ namespace Radzen
|
||||
{
|
||||
if (!string.IsNullOrEmpty(ValueProperty))
|
||||
{
|
||||
foreach (object v in values.ToDynamicList())
|
||||
foreach (object v in values.Cast<dynamic>().ToList())
|
||||
{
|
||||
dynamic item;
|
||||
|
||||
if (typeof(EnumerableQuery).IsAssignableFrom(View.GetType()))
|
||||
if (typeof(EnumerableQuery).IsAssignableFrom(view.GetType()))
|
||||
{
|
||||
item = View.OfType<object>().Where(i => object.Equals(GetItemOrValueFromProperty(i, ValueProperty), v)).FirstOrDefault();
|
||||
item = view.OfType<object>().Where(i => object.Equals(GetItemOrValueFromProperty(i, ValueProperty), v)).FirstOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
item = View.AsQueryable().Where($@"{ValueProperty} == @0", v).FirstOrDefault();
|
||||
item = view.AsQueryable().Where(new FilterDescriptor[]
|
||||
{
|
||||
new FilterDescriptor()
|
||||
{
|
||||
Property = ValueProperty,
|
||||
FilterValue = v
|
||||
}
|
||||
},
|
||||
LogicalFilterOperator.And,
|
||||
FilterCaseSensitivity.Default).FirstOrDefault();
|
||||
}
|
||||
|
||||
if (!object.Equals(item, null) && !selectedItems.AsQueryable().Where($@"object.Equals({ValueProperty},@0)", v).Any())
|
||||
if (!object.Equals(item, null) && !selectedItems.AsQueryable().Where(i => object.Equals(GetItemOrValueFromProperty(i, ValueProperty), v)).Any())
|
||||
{
|
||||
selectedItems.Add(item);
|
||||
}
|
||||
@@ -1154,7 +1316,7 @@ namespace Radzen
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedItems = ((IEnumerable)values).Cast<object>().ToList();
|
||||
selectedItems = values.Cast<object>().ToHashSet(ItemComparer);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1166,22 +1328,24 @@ namespace Radzen
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For lists of objects, an IEqualityComparer to control how selected items are determined
|
||||
/// </summary>
|
||||
[Parameter] public IEqualityComparer<object> ItemComparer { get; set; }
|
||||
|
||||
internal bool IsItemSelectedByValue(object v)
|
||||
{
|
||||
if (internalValue != null)
|
||||
switch (internalValue)
|
||||
{
|
||||
var values = internalValue as IEnumerable;
|
||||
if (values != null)
|
||||
{
|
||||
return values.Cast<object>().Contains(v);
|
||||
}
|
||||
else
|
||||
{
|
||||
case string s:
|
||||
return object.Equals(s, v);
|
||||
case IEnumerable enumerable:
|
||||
return enumerable.Cast<object>().Contains(v);
|
||||
case null:
|
||||
return false;
|
||||
default:
|
||||
return object.Equals(internalValue, v);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
144
Radzen.Blazor/DynamicExtensions.cs
Normal file
144
Radzen.Blazor/DynamicExtensions.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
using Radzen;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace System.Linq.Dynamic.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Class DynamicExtensions used to replace System.Linq.Dynamic.Core library.
|
||||
/// </summary>
|
||||
public static class DynamicExtensions
|
||||
{
|
||||
static readonly Func<string, Type> typeLocator = type => AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(a => a.GetTypes()).FirstOrDefault(t => t.FullName.Replace("+", ".") == type);
|
||||
|
||||
/// <summary>
|
||||
/// Filters using the specified filter descriptors.
|
||||
/// </summary>
|
||||
public static IQueryable<T> Where<T>(
|
||||
this IQueryable<T> source,
|
||||
string predicate,
|
||||
object[] parameters = null, object[] otherParameters = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (parameters != null && !string.IsNullOrEmpty(predicate))
|
||||
{
|
||||
for (var i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
object param = parameters[i];
|
||||
string value = param switch
|
||||
{
|
||||
string s when s == string.Empty => @"""""",
|
||||
null => "null",
|
||||
string s => @$"""{s.Replace("\"", "\\\"")}""",
|
||||
bool b => b.ToString().ToLower(),
|
||||
Guid g => $"Guid.Parse(\"{g}\")",
|
||||
DateTime dt => $"DateTime.Parse(\"{dt:yyyy-MM-ddTHH:mm:ss.fffZ}\")",
|
||||
DateTimeOffset dto => $"DateTime.Parse(\"{dto.UtcDateTime:yyyy-MM-ddTHH:mm:ss.fffZ}\")",
|
||||
DateOnly d => $"DateOnly.Parse(\"{d:yyy-MM-dd}\")",
|
||||
TimeOnly t => $"TimeOnly.Parse(\"{t:HH:mm:ss}\")",
|
||||
_ => param.ToString()
|
||||
};
|
||||
|
||||
predicate = predicate.Replace($"@{i}", $"{value}");
|
||||
}
|
||||
}
|
||||
|
||||
predicate = (predicate == "true" ? "" : predicate)
|
||||
.Replace("DateTime(", "DateTime.Parse(")
|
||||
.Replace("DateTimeOffset(", "DateTimeOffset.Parse(")
|
||||
.Replace("DateOnly(", "DateOnly.Parse(")
|
||||
.Replace("Guid(", "Guid.Parse(")
|
||||
.Replace(" = ", " == ");
|
||||
|
||||
return !string.IsNullOrEmpty(predicate) ?
|
||||
source.Where(ExpressionParser.ParsePredicate<T>(predicate, typeLocator)) : source;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException($"Invalid predicate: {predicate}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts the elements of a sequence in ascending or descending order according to a key.
|
||||
/// </summary>
|
||||
public static IOrderedQueryable<T> OrderBy<T>(
|
||||
this IQueryable<T> source,
|
||||
string selector,
|
||||
object[] parameters = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
return QueryableExtension.OrderBy(source, selector);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException($"Invalid selector: {selector}.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Projects each element of a sequence into a collection of property values.
|
||||
/// </summary>
|
||||
public static IQueryable Select<T>(this IQueryable<T> source, string selector, object[] parameters = null)
|
||||
{
|
||||
if (source.ElementType == typeof(object))
|
||||
{
|
||||
var elementType = source.ElementType;
|
||||
|
||||
if (source.Expression is MethodCallExpression methodCall && methodCall.Method.Name == "Cast")
|
||||
{
|
||||
elementType = methodCall.Arguments[0].Type.GetGenericArguments().FirstOrDefault() ?? typeof(object);
|
||||
}
|
||||
else if (typeof(EnumerableQuery).IsAssignableFrom(source.GetType()))
|
||||
{
|
||||
elementType = source.FirstOrDefault()?.GetType() ?? typeof(object);
|
||||
}
|
||||
|
||||
return source.Cast(elementType).Select(selector, expression => ExpressionParser.ParseLambda(expression, elementType));
|
||||
}
|
||||
|
||||
return source.Select(selector, expression => ExpressionParser.ParseLambda<T>(expression));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Projects each element of a sequence into a collection of property values.
|
||||
/// </summary>
|
||||
public static IQueryable Select(this IQueryable source, string selector, object[] parameters = null)
|
||||
{
|
||||
return source.Select(selector, expression => ExpressionParser.ParseLambda(expression, source.ElementType));
|
||||
}
|
||||
|
||||
private static IQueryable Select(this IQueryable source, string selector, Func<string, LambdaExpression> lambdaCreator)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(selector))
|
||||
{
|
||||
return source;
|
||||
}
|
||||
|
||||
if (!selector.Contains("=>"))
|
||||
{
|
||||
var properties = selector
|
||||
.Replace("new (", "").Replace(")", "").Replace("new {", "").Replace("}", "").Trim()
|
||||
.Split(",", StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
selector = string.Join(", ", properties
|
||||
.Select(s => (s.Contains(" as ") ? s.Split(" as ").LastOrDefault().Trim().Replace(".", "_") : s.Trim().Replace(".", "_")) +
|
||||
" = " + $"it.{s.Split(" as ").FirstOrDefault().Replace(".", "?.").Trim()}"));
|
||||
}
|
||||
|
||||
var lambda = lambdaCreator(selector.Contains("=>") ? selector : $"it => new {{ {selector} }}");
|
||||
|
||||
return source.Provider.CreateQuery(Expression.Call(typeof(Queryable), nameof(Queryable.Select),
|
||||
[source.ElementType, lambda.Body.Type], source.Expression, Expression.Quote(lambda)));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException($"Invalid selector: {selector}.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
548
Radzen.Blazor/ExpressionParser.cs
Normal file
548
Radzen.Blazor/ExpressionParser.cs
Normal file
@@ -0,0 +1,548 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
|
||||
namespace Radzen;
|
||||
|
||||
static class DynamicTypeFactory
|
||||
{
|
||||
public static Type CreateType(string typeName, string[] propertyNames, Type[] propertyTypes)
|
||||
{
|
||||
if (propertyNames.Length != propertyTypes.Length)
|
||||
{
|
||||
throw new ArgumentException("Property names and types count mismatch.");
|
||||
}
|
||||
|
||||
var assemblyName = new AssemblyName("DynamicTypesAssembly");
|
||||
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
|
||||
var moduleBuilder = assemblyBuilder.DefineDynamicModule("DynamicTypesModule");
|
||||
|
||||
var typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.Sealed);
|
||||
|
||||
for (int i = 0; i < propertyNames.Length; i++)
|
||||
{
|
||||
var fieldBuilder = typeBuilder.DefineField("_" + propertyNames[i], propertyTypes[i], FieldAttributes.Private);
|
||||
var propertyBuilder = typeBuilder.DefineProperty(propertyNames[i], PropertyAttributes.None, propertyTypes[i], null);
|
||||
|
||||
var getterMethod = typeBuilder.DefineMethod(
|
||||
"get_" + propertyNames[i],
|
||||
MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig,
|
||||
propertyTypes[i],
|
||||
Type.EmptyTypes);
|
||||
|
||||
var getterIl = getterMethod.GetILGenerator();
|
||||
getterIl.Emit(OpCodes.Ldarg_0);
|
||||
getterIl.Emit(OpCodes.Ldfld, fieldBuilder);
|
||||
getterIl.Emit(OpCodes.Ret);
|
||||
|
||||
propertyBuilder.SetGetMethod(getterMethod);
|
||||
|
||||
var setterMethod = typeBuilder.DefineMethod(
|
||||
"set_" + propertyNames[i],
|
||||
MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig,
|
||||
null,
|
||||
[propertyTypes[i]]);
|
||||
|
||||
var setterIl = setterMethod.GetILGenerator();
|
||||
setterIl.Emit(OpCodes.Ldarg_0);
|
||||
setterIl.Emit(OpCodes.Ldarg_1);
|
||||
setterIl.Emit(OpCodes.Stfld, fieldBuilder);
|
||||
setterIl.Emit(OpCodes.Ret);
|
||||
|
||||
propertyBuilder.SetSetMethod(setterMethod);
|
||||
}
|
||||
|
||||
var dynamicType = typeBuilder.CreateType();
|
||||
return dynamicType;
|
||||
}
|
||||
}
|
||||
|
||||
class ExpressionSyntaxVisitor : CSharpSyntaxVisitor<Expression>
|
||||
{
|
||||
private readonly ParameterExpression parameter;
|
||||
private readonly Func<string, Type> typeLocator;
|
||||
|
||||
public ExpressionSyntaxVisitor(ParameterExpression parameter, Func<string, Type> typeLocator)
|
||||
{
|
||||
this.parameter = parameter;
|
||||
this.typeLocator = typeLocator;
|
||||
}
|
||||
|
||||
public override Expression VisitBinaryExpression(BinaryExpressionSyntax node)
|
||||
{
|
||||
var left = Visit(node.Left);
|
||||
|
||||
var right = ConvertIfNeeded(Visit(node.Right), left.Type);
|
||||
|
||||
return Expression.MakeBinary(ParseBinaryOperator(node.OperatorToken), left, right);
|
||||
}
|
||||
|
||||
|
||||
public override Expression VisitMemberAccessExpression(MemberAccessExpressionSyntax node)
|
||||
{
|
||||
var expression = Visit(node.Expression) ?? parameter;
|
||||
return Expression.PropertyOrField(expression, node.Name.Identifier.Text);
|
||||
}
|
||||
|
||||
public override Expression VisitLiteralExpression(LiteralExpressionSyntax node)
|
||||
{
|
||||
return Expression.Constant(ParseLiteral(node));
|
||||
}
|
||||
|
||||
public override Expression VisitIdentifierName(IdentifierNameSyntax node)
|
||||
{
|
||||
if (node.Identifier.Text == parameter.Name)
|
||||
{
|
||||
return parameter;
|
||||
}
|
||||
|
||||
var type = GetType(node.Identifier.Text);
|
||||
|
||||
if (type != null)
|
||||
{
|
||||
return Expression.Constant(type);
|
||||
}
|
||||
|
||||
throw new NotSupportedException("Unsupported identifier: " + node.Identifier.Text);
|
||||
}
|
||||
|
||||
public override Expression VisitConditionalExpression(ConditionalExpressionSyntax node)
|
||||
{
|
||||
var condition = Visit(node.Condition);
|
||||
|
||||
var whenTrue = Visit(node.WhenTrue);
|
||||
|
||||
var whenFalse = Visit(node.WhenFalse);
|
||||
|
||||
if (whenTrue.Type != whenFalse.Type)
|
||||
{
|
||||
if (whenTrue.Type == typeof(object))
|
||||
{
|
||||
whenTrue = Expression.Convert(whenTrue, whenFalse.Type);
|
||||
}
|
||||
else if (whenFalse.Type == typeof(object))
|
||||
{
|
||||
whenFalse = Expression.Convert(whenFalse, whenTrue.Type);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException("Conditional expression types mismatch: " + whenTrue.Type + " and " + whenFalse.Type);
|
||||
}
|
||||
}
|
||||
|
||||
return Expression.Condition(condition, whenTrue, whenFalse);
|
||||
}
|
||||
|
||||
public override Expression VisitParenthesizedExpression(ParenthesizedExpressionSyntax node)
|
||||
{
|
||||
return Visit(node.Expression);
|
||||
}
|
||||
|
||||
private Type GetType(string typeName)
|
||||
{
|
||||
var nullable = typeName.EndsWith('?');
|
||||
|
||||
if (nullable)
|
||||
{
|
||||
typeName = typeName[..^1];
|
||||
}
|
||||
|
||||
var type = typeName switch
|
||||
{
|
||||
nameof(Int32) => typeof(int),
|
||||
nameof(Int64) => typeof(long),
|
||||
nameof(Double) => typeof(double),
|
||||
nameof(Single) => typeof(float),
|
||||
nameof(Decimal) => typeof(decimal),
|
||||
nameof(String) => typeof(string),
|
||||
nameof(Boolean) => typeof(bool),
|
||||
nameof(DateTime) => typeof(DateTime),
|
||||
nameof(DateOnly) => typeof(DateOnly),
|
||||
nameof(DateTimeOffset) => typeof(DateTimeOffset),
|
||||
nameof(TimeOnly) => typeof(TimeOnly),
|
||||
nameof(Guid) => typeof(Guid),
|
||||
nameof(Char) => typeof(char),
|
||||
"int" => typeof(int),
|
||||
"long" => typeof(long),
|
||||
"double" => typeof(double),
|
||||
"float" => typeof(float),
|
||||
"decimal" => typeof(decimal),
|
||||
"string" => typeof(string),
|
||||
"char" => typeof(char),
|
||||
"bool" => typeof(bool),
|
||||
_ => typeLocator?.Invoke(typeName)
|
||||
};
|
||||
|
||||
if (nullable && type != null)
|
||||
{
|
||||
type = typeof(Nullable<>).MakeGenericType(type);
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
public override Expression VisitCastExpression(CastExpressionSyntax node)
|
||||
{
|
||||
var typeName = node.Type.ToString();
|
||||
|
||||
var targetType = GetType(typeName);
|
||||
|
||||
if (targetType == null)
|
||||
{
|
||||
throw new NotSupportedException("Unsupported cast type: " + node.Type);
|
||||
}
|
||||
|
||||
var operand = Visit(node.Expression);
|
||||
|
||||
return Expression.Convert(operand, targetType);
|
||||
}
|
||||
|
||||
public override Expression VisitImplicitArrayCreationExpression(ImplicitArrayCreationExpressionSyntax node)
|
||||
{
|
||||
var expressions = node.Initializer.Expressions.Select(Visit).ToArray();
|
||||
var elementType = expressions.Length > 0 ? expressions[0].Type : typeof(object);
|
||||
return Expression.NewArrayInit(elementType, expressions);
|
||||
}
|
||||
|
||||
public override Expression VisitArrayCreationExpression(ArrayCreationExpressionSyntax node)
|
||||
{
|
||||
var elementType = GetType(node.Type.ElementType.ToString());
|
||||
|
||||
if (elementType == null)
|
||||
{
|
||||
throw new NotSupportedException("Unsupported array element type: " + node.Type.ElementType);
|
||||
}
|
||||
|
||||
var expressions = node.Initializer.Expressions.Select(e => ConvertIfNeeded(Visit(e), elementType));
|
||||
|
||||
return Expression.NewArrayInit(elementType, expressions);
|
||||
}
|
||||
|
||||
private static MethodCallExpression CallStaticMethod(Type type, string methodName, Expression[] arguments, Type[] argumentTypes)
|
||||
{
|
||||
var methodInfo = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static, argumentTypes);
|
||||
|
||||
if (methodInfo != null)
|
||||
{
|
||||
return Expression.Call(methodInfo, arguments);
|
||||
}
|
||||
|
||||
throw new NotSupportedException("Method not found: " + methodName);
|
||||
}
|
||||
|
||||
public override Expression DefaultVisit(SyntaxNode node)
|
||||
{
|
||||
throw new NotSupportedException("Unsupported syntax: " + node.GetType().Name);
|
||||
}
|
||||
|
||||
public override Expression VisitSimpleLambdaExpression(SimpleLambdaExpressionSyntax node)
|
||||
{
|
||||
var body = Visit(node.Body);
|
||||
|
||||
return Expression.Lambda(body, parameter);
|
||||
}
|
||||
|
||||
private Expression VisitArgument(Expression instance, ArgumentSyntax argument)
|
||||
{
|
||||
if (argument.Expression is SimpleLambdaExpressionSyntax lambda)
|
||||
{
|
||||
var itemType = GetItemType(instance.Type);
|
||||
|
||||
var visitor = new ExpressionSyntaxVisitor(Expression.Parameter(itemType, lambda.Parameter.Identifier.Text), typeLocator);
|
||||
|
||||
return visitor.Visit(lambda);
|
||||
}
|
||||
|
||||
return Visit(argument.Expression);
|
||||
}
|
||||
|
||||
private static Expression ConvertIfNeeded(Expression expression, Type targetType)
|
||||
{
|
||||
if (expression is not LambdaExpression)
|
||||
{
|
||||
return expression.Type == targetType ? expression : Expression.Convert(expression, targetType);
|
||||
}
|
||||
|
||||
return expression;
|
||||
}
|
||||
|
||||
public override Expression VisitInvocationExpression(InvocationExpressionSyntax node)
|
||||
{
|
||||
if (node.Expression is MemberAccessExpressionSyntax methodCall)
|
||||
{
|
||||
var instance = Visit(methodCall.Expression);
|
||||
var arguments = node.ArgumentList.Arguments.Select(a => VisitArgument(instance, a)).ToArray();
|
||||
var argumentTypes = arguments.Select(a => a.Type).ToArray();
|
||||
|
||||
if (instance is ConstantExpression constant && constant.Value is Type type)
|
||||
{
|
||||
return CallStaticMethod(type, methodCall.Name.Identifier.Text, arguments, argumentTypes);
|
||||
}
|
||||
|
||||
var instanceType = instance.Type;
|
||||
var methodInfo = instanceType.GetMethod(methodCall.Name.Identifier.Text, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static, argumentTypes);
|
||||
|
||||
if (methodInfo == null)
|
||||
{
|
||||
methodInfo = typeof(Enumerable)
|
||||
.GetMethods(BindingFlags.Public | BindingFlags.Static)
|
||||
.FirstOrDefault(m => m.Name == methodCall.Name.Identifier.Text && m.GetParameters().Length == arguments.Length + 1);
|
||||
|
||||
if (methodInfo != null)
|
||||
{
|
||||
var argumentType = GetItemType(instanceType);
|
||||
var genericMethod = methodInfo.MakeGenericMethod(argumentType);
|
||||
var parameters = genericMethod.GetParameters();
|
||||
var argumentsWithInstance = new[] { instance }.Concat(arguments).ToArray();
|
||||
|
||||
if (parameters.Length != argumentsWithInstance.Length)
|
||||
{
|
||||
throw new NotSupportedException("Unsupported method call: " + methodCall.Name.Identifier.Text);
|
||||
}
|
||||
|
||||
return Expression.Call(genericMethod, argumentsWithInstance.Select((a, index) => ConvertIfNeeded(a, parameters[index].ParameterType)));
|
||||
}
|
||||
}
|
||||
|
||||
if (methodInfo == null)
|
||||
{
|
||||
throw new NotSupportedException("Unsupported method call: " + methodCall.Name.Identifier.Text);
|
||||
}
|
||||
|
||||
return Expression.Call(instance, methodInfo, arguments);
|
||||
}
|
||||
|
||||
throw new NotSupportedException("Unsupported invocation expression: " + node.ToString());
|
||||
}
|
||||
|
||||
private static Type GetItemType(Type enumerableOrArray)
|
||||
{
|
||||
return enumerableOrArray.IsArray ? enumerableOrArray.GetElementType() : enumerableOrArray.GetGenericArguments()[0];
|
||||
}
|
||||
|
||||
|
||||
private static object ParseLiteral(LiteralExpressionSyntax literal)
|
||||
{
|
||||
return literal.Kind() switch
|
||||
{
|
||||
SyntaxKind.StringLiteralExpression => literal.Token.ValueText,
|
||||
SyntaxKind.NumericLiteralExpression => literal.Token.Value,
|
||||
SyntaxKind.TrueLiteralExpression => true,
|
||||
SyntaxKind.FalseLiteralExpression => false,
|
||||
SyntaxKind.NullLiteralExpression => null,
|
||||
_ => throw new NotSupportedException("Unsupported literal: " + literal),
|
||||
};
|
||||
}
|
||||
|
||||
private static ExpressionType ParseBinaryOperator(SyntaxToken token)
|
||||
{
|
||||
return token.Kind() switch
|
||||
{
|
||||
SyntaxKind.EqualsEqualsToken => ExpressionType.Equal,
|
||||
SyntaxKind.LessThanToken => ExpressionType.LessThan,
|
||||
SyntaxKind.GreaterThanToken => ExpressionType.GreaterThan,
|
||||
SyntaxKind.LessThanEqualsToken => ExpressionType.LessThanOrEqual,
|
||||
SyntaxKind.GreaterThanEqualsToken => ExpressionType.GreaterThanOrEqual,
|
||||
SyntaxKind.ExclamationEqualsToken => ExpressionType.NotEqual,
|
||||
SyntaxKind.AmpersandAmpersandToken => ExpressionType.AndAlso,
|
||||
SyntaxKind.BarBarToken => ExpressionType.OrElse,
|
||||
SyntaxKind.QuestionQuestionToken => ExpressionType.Coalesce,
|
||||
_ => throw new NotSupportedException("Unsupported operator: " + token.Text),
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetPropertyNameFromInitializer(AnonymousObjectMemberDeclaratorSyntax initializer)
|
||||
{
|
||||
if (initializer.NameEquals != null)
|
||||
{
|
||||
return initializer.NameEquals.Name.Identifier.Text;
|
||||
}
|
||||
|
||||
var expression = initializer.Expression;
|
||||
|
||||
if (expression is MemberAccessExpressionSyntax memberAccess)
|
||||
{
|
||||
expression = memberAccess.Name;
|
||||
}
|
||||
|
||||
while (expression is ConditionalAccessExpressionSyntax conditionalAccess)
|
||||
{
|
||||
expression = conditionalAccess.WhenNotNull;
|
||||
}
|
||||
|
||||
if (expression is MemberBindingExpressionSyntax memberBinding)
|
||||
{
|
||||
expression = memberBinding.Name;
|
||||
}
|
||||
|
||||
if (expression is IdentifierNameSyntax identifier)
|
||||
{
|
||||
return identifier.Identifier.Text;
|
||||
}
|
||||
|
||||
throw new NotSupportedException("Unsupported initializer: " + initializer.ToString());
|
||||
}
|
||||
|
||||
public override Expression VisitAnonymousObjectCreationExpression(AnonymousObjectCreationExpressionSyntax node)
|
||||
{
|
||||
var properties = node.Initializers.Select(init =>
|
||||
{
|
||||
var name = GetPropertyNameFromInitializer(init);
|
||||
var value = Visit(init.Expression);
|
||||
return new { Name = name, Value = value };
|
||||
}).ToList();
|
||||
|
||||
var propertyNames = properties.Select(p => p.Name).ToArray();
|
||||
var propertyTypes = properties.Select(p => p.Value.Type).ToArray();
|
||||
var dynamicType = DynamicTypeFactory.CreateType(parameter.Type.Name, propertyNames, propertyTypes);
|
||||
|
||||
var bindings = properties.Select(p => Expression.Bind(dynamicType.GetProperty(p.Name), p.Value));
|
||||
return Expression.MemberInit(Expression.New(dynamicType), bindings);
|
||||
}
|
||||
|
||||
private Expression instance;
|
||||
|
||||
public override Expression VisitConditionalAccessExpression(ConditionalAccessExpressionSyntax node)
|
||||
{
|
||||
var expression = Visit(node.Expression);
|
||||
|
||||
instance = expression;
|
||||
|
||||
var whenNotNull = Visit(node.WhenNotNull);
|
||||
|
||||
instance = null;
|
||||
|
||||
if (expression.Type.IsValueType && Nullable.GetUnderlyingType(expression.Type) == null)
|
||||
{
|
||||
throw new NotSupportedException("Conditional access is not supported on non-nullable value types: " + expression.Type);
|
||||
}
|
||||
|
||||
if (!expression.Type.IsValueType || Nullable.GetUnderlyingType(expression.Type) != null)
|
||||
{
|
||||
return Expression.Condition(Expression.NotEqual(expression, Expression.Constant(null, expression.Type)),
|
||||
whenNotNull, Expression.Default(whenNotNull.Type)
|
||||
);
|
||||
}
|
||||
|
||||
return whenNotNull;
|
||||
}
|
||||
|
||||
public override Expression VisitMemberBindingExpression(MemberBindingExpressionSyntax node)
|
||||
{
|
||||
if (instance is Expression expression)
|
||||
{
|
||||
return Expression.PropertyOrField(expression, node.Name.Identifier.Text);
|
||||
}
|
||||
|
||||
throw new NotSupportedException("Unsupported member binding: " + node.ToString());
|
||||
}
|
||||
|
||||
public override Expression VisitElementAccessExpression(ElementAccessExpressionSyntax node)
|
||||
{
|
||||
var expression = Visit(node.Expression);
|
||||
var arguments = node.ArgumentList.Arguments.Select(arg => Visit(arg.Expression)).ToArray();
|
||||
|
||||
if (expression.Type.IsArray)
|
||||
{
|
||||
return Expression.ArrayIndex(expression, arguments);
|
||||
}
|
||||
|
||||
var indexer = expression.Type.GetProperties()
|
||||
.FirstOrDefault(p => p.GetIndexParameters().Length == arguments.Length);
|
||||
|
||||
if (indexer != null)
|
||||
{
|
||||
return Expression.MakeIndex(expression, indexer, arguments);
|
||||
}
|
||||
|
||||
throw new NotSupportedException("Unsupported element access: " + node.ToString());
|
||||
}
|
||||
|
||||
public override Expression VisitPrefixUnaryExpression(PrefixUnaryExpressionSyntax node)
|
||||
{
|
||||
var operand = Visit(node.Operand);
|
||||
|
||||
return node.OperatorToken.Kind() switch
|
||||
{
|
||||
SyntaxKind.MinusToken => Expression.Negate(operand),
|
||||
SyntaxKind.ExclamationToken => Expression.Not(operand),
|
||||
_ => throw new NotSupportedException("Unsupported unary operator: " + node.OperatorToken.Text),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse lambda expressions from strings.
|
||||
/// </summary>
|
||||
public static class ExpressionParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses a lambda expression that returns a boolean value.
|
||||
/// </summary>
|
||||
public static Expression<Func<T, bool>> ParsePredicate<T>(string expression, Func<string, Type> typeLocator = null)
|
||||
{
|
||||
return ParseLambda<T, bool>(expression, typeLocator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a lambda expression that returns a typed result.
|
||||
/// </summary>
|
||||
public static Expression<Func<T, TResult>> ParseLambda<T, TResult>(string expression, Func<string, Type> typeLocator = null)
|
||||
{
|
||||
var (parameter, body) = Parse<T>(expression, typeLocator);
|
||||
|
||||
return Expression.Lambda<Func<T, TResult>>(body, parameter);
|
||||
}
|
||||
|
||||
|
||||
private static (ParameterExpression, Expression) Parse<T>(string expression, Func<string, Type> typeLocator)
|
||||
{
|
||||
var syntaxTree = CSharpSyntaxTree.ParseText(expression);
|
||||
var root = syntaxTree.GetRoot();
|
||||
var lambdaExpression = root.DescendantNodes().OfType<SimpleLambdaExpressionSyntax>().FirstOrDefault();
|
||||
if (lambdaExpression == null)
|
||||
{
|
||||
throw new ArgumentException("Invalid lambda expression.");
|
||||
}
|
||||
var parameter = Expression.Parameter(typeof(T), lambdaExpression.Parameter.Identifier.Text);
|
||||
var visitor = new ExpressionSyntaxVisitor(parameter, typeLocator);
|
||||
var body = visitor.Visit(lambdaExpression.Body);
|
||||
return (parameter, body);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a lambda expression that returns untyped result.
|
||||
/// </summary>
|
||||
public static LambdaExpression ParseLambda<T>(string expression, Func<string, Type> typeLocator = null)
|
||||
{
|
||||
var (parameter, body) = Parse<T>(expression, typeLocator);
|
||||
|
||||
return Expression.Lambda(body, parameter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a lambda expression that returns untyped result.
|
||||
/// </summary>
|
||||
public static LambdaExpression ParseLambda(string expression, Type type, Func<string, Type> typeLocator = null)
|
||||
{
|
||||
var syntaxTree = CSharpSyntaxTree.ParseText(expression);
|
||||
var root = syntaxTree.GetRoot();
|
||||
var lambdaExpression = root.DescendantNodes().OfType<SimpleLambdaExpressionSyntax>().FirstOrDefault();
|
||||
|
||||
if (lambdaExpression == null)
|
||||
{
|
||||
throw new ArgumentException("Invalid lambda expression.");
|
||||
}
|
||||
|
||||
var parameter = Expression.Parameter(type, lambdaExpression.Parameter.Identifier.Text);
|
||||
var visitor = new ExpressionSyntaxVisitor(parameter, typeLocator);
|
||||
var body = visitor.Visit(lambdaExpression.Body);
|
||||
|
||||
return Expression.Lambda(body, parameter);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Radzen.Blazor
|
||||
{
|
||||
@@ -14,22 +18,57 @@ namespace Radzen.Blazor
|
||||
/// <summary>
|
||||
/// Gets enum description.
|
||||
/// </summary>
|
||||
public static string GetDisplayDescription(this Enum enumValue)
|
||||
public static string GetDisplayDescription(this Enum enumValue, Func<string, string> translationFunction = null)
|
||||
{
|
||||
var enumValueAsString = enumValue.ToString();
|
||||
var val = enumValue.GetType().GetMember(enumValueAsString).FirstOrDefault();
|
||||
var enumVal = val?.GetCustomAttribute<DisplayAttribute>()?.GetDescription() ?? enumValueAsString;
|
||||
|
||||
return val?.GetCustomAttribute<DisplayAttribute>()?.GetDescription() ?? enumValueAsString;
|
||||
if (translationFunction != null)
|
||||
return translationFunction(enumVal);
|
||||
|
||||
return enumVal;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts Enum to IEnumerable of Value/Text.
|
||||
/// </summary>
|
||||
public static IEnumerable<object> EnumAsKeyValuePair(Type enumType)
|
||||
public static IEnumerable<object> EnumAsKeyValuePair(Type enumType, Func<string, string> translationFunction = null)
|
||||
{
|
||||
return Enum.GetValues(enumType).Cast<Enum>().Distinct().Select(val => new { Value = Convert.ToInt32(val), Text = val.GetDisplayDescription() });
|
||||
Type underlyingType = Enum.GetUnderlyingType(enumType);
|
||||
return Enum.GetValues(enumType).Cast<Enum>().Distinct().Select(val => new { Value = Convert.ChangeType(val, underlyingType), Text = val.GetDisplayDescription(translationFunction) });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the autocomplete type enum value to the expected
|
||||
/// autocomplete attribute value.
|
||||
/// </summary>
|
||||
/// <returns>The autocomplete attribute string value.</returns>
|
||||
public static string GetAutoCompleteValue(this AutoCompleteType typeValue)
|
||||
{
|
||||
// Handle synonyms.
|
||||
switch (typeValue)
|
||||
{
|
||||
case AutoCompleteType.FirstName:
|
||||
return "given-name";
|
||||
case AutoCompleteType.LastName:
|
||||
return "family-name";
|
||||
case AutoCompleteType.MiddleName:
|
||||
return "additional-name";
|
||||
case AutoCompleteType.ZipCode:
|
||||
return "postal-code";
|
||||
case AutoCompleteType.Province:
|
||||
return "address-level1";
|
||||
case AutoCompleteType.State:
|
||||
return "address-level1";
|
||||
}
|
||||
|
||||
// Handle standard values.
|
||||
var value = typeValue.ToString();
|
||||
value = Regex.Replace(value, "([^A-Z])([A-Z])", "$1-$2");
|
||||
return Regex.Replace(value, "([A-Z]+)([A-Z][^A-Z$])", "$1-$2")
|
||||
.Trim().ToLower();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,80 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Radzen.Blazor;
|
||||
using Radzen.Blazor.Rendering;
|
||||
|
||||
namespace Radzen
|
||||
{
|
||||
/// <summary>
|
||||
/// Class FormComponentWithAutoComplete.
|
||||
/// </summary>
|
||||
public class FormComponentWithAutoComplete<T> : FormComponent<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating the type of built-in autocomplete
|
||||
/// the browser should use.
|
||||
/// <see cref="Blazor.AutoCompleteType" />
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The type of built-in autocomplete.
|
||||
/// </value>
|
||||
[Parameter]
|
||||
public virtual AutoCompleteType AutoCompleteType { get; set; } = AutoCompleteType.On;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the autocomplete attribute's string value.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>off</c> if the AutoComplete parameter is false or the
|
||||
/// AutoCompleteType parameter is "off". When the AutoComplete
|
||||
/// parameter is true, the value is <c>on</c> or, if set, the value of
|
||||
/// AutoCompleteType.</value>
|
||||
public virtual string AutoCompleteAttribute
|
||||
{
|
||||
get => Attributes != null && Attributes.ContainsKey("AutoComplete") && $"{Attributes["AutoComplete"]}".ToLower() == "false" ? DefaultAutoCompleteAttribute :
|
||||
Attributes != null && Attributes.ContainsKey("AutoComplete") ? Attributes["AutoComplete"] as string ?? AutoCompleteType.GetAutoCompleteValue() : AutoCompleteType.GetAutoCompleteValue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default autocomplete attribute's string value.
|
||||
/// </summary>
|
||||
public virtual string DefaultAutoCompleteAttribute { get; set; } = "off";
|
||||
|
||||
object ariaAutoComplete;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task SetParametersAsync(ParameterView parameters)
|
||||
{
|
||||
parameters = parameters.TryGetValue("aria-autocomplete", out ariaAutoComplete) ?
|
||||
ParameterView.FromDictionary(parameters
|
||||
.ToDictionary().Where(i => i.Key != "aria-autocomplete").ToDictionary(i => i.Key, i => i.Value)
|
||||
.ToDictionary(i => i.Key, i => i.Value))
|
||||
: parameters;
|
||||
|
||||
await base.SetParametersAsync(parameters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default aria-autocomplete attribute's string value.
|
||||
/// </summary>
|
||||
public virtual string DefaultAriaAutoCompleteAttribute { get; set; } = "none";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the aria-autocomplete attribute's string value.
|
||||
/// </summary>
|
||||
public virtual string AriaAutoCompleteAttribute
|
||||
{
|
||||
get => AutoCompleteAttribute == DefaultAutoCompleteAttribute ? DefaultAriaAutoCompleteAttribute : ariaAutoComplete as string;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class FormComponent.
|
||||
/// Implements the <see cref="Radzen.RadzenComponent" />
|
||||
@@ -162,6 +228,8 @@ namespace Radzen
|
||||
/// <returns>Task.</returns>
|
||||
public override Task SetParametersAsync(ParameterView parameters)
|
||||
{
|
||||
var disabledChanged = parameters.DidParameterChange(nameof(Disabled), Disabled);
|
||||
|
||||
var result = base.SetParametersAsync(parameters);
|
||||
|
||||
if (EditContext != null && ValueExpression != null && FieldIdentifier.Model != EditContext.Model)
|
||||
@@ -171,6 +239,11 @@ namespace Radzen
|
||||
EditContext.OnValidationStateChanged += ValidationStateChanged;
|
||||
}
|
||||
|
||||
if (disabledChanged)
|
||||
{
|
||||
FormFieldContext?.DisabledChanged(Disabled);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -230,6 +303,20 @@ namespace Radzen
|
||||
/// <returns>ClassList.</returns>
|
||||
protected ClassList GetClassList(string className) => ClassList.Create(className)
|
||||
.AddDisabled(Disabled)
|
||||
.Add(FieldIdentifier, EditContext);
|
||||
.Add(FieldIdentifier, EditContext)
|
||||
.Add("rz-state-empty", !HasValue);
|
||||
|
||||
/// <summary> Provides support for RadzenFormField integration. </summary>
|
||||
[CascadingParameter]
|
||||
public IFormFieldContext FormFieldContext { get; set; }
|
||||
|
||||
/// <summary> Gets the current placeholder. Returns empty string if this component is inside a RadzenFormField.</summary>
|
||||
protected string CurrentPlaceholder => FormFieldContext?.AllowFloatingLabel == true ? " " : Placeholder;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask FocusAsync()
|
||||
{
|
||||
await Element.FocusAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,14 +50,23 @@ namespace Radzen.Blazor
|
||||
/// <param name="valueScale">The value scale.</param>
|
||||
/// <returns>RenderFragment.</returns>
|
||||
RenderFragment RenderOverlays(ScaleBase categoryScale, ScaleBase valueScale);
|
||||
|
||||
/// <summary>
|
||||
/// Renders the series tooltip.
|
||||
/// </summary>
|
||||
/// <param name="data">The data.</param>
|
||||
/// <param name="marginLeft">The left margin.</param>
|
||||
/// <param name="marginTop">The right margin.</param>
|
||||
/// <returns>RenderFragment.</returns>
|
||||
RenderFragment RenderTooltip(object data, double marginLeft, double marginTop);
|
||||
RenderFragment RenderTooltip(object data);
|
||||
/// <summary>
|
||||
/// Renders a tooltip item with the specified data to be displayed in a shared tooltip
|
||||
/// </summary>
|
||||
RenderFragment RenderSharedTooltipItem(object category);
|
||||
/// <summary>
|
||||
/// Get position of the series tooltip.
|
||||
/// </summary>
|
||||
/// <param name="data">The data.</param>
|
||||
/// <returns>Position.</returns>
|
||||
Point GetTooltipPosition(object data);
|
||||
/// <summary>
|
||||
/// Renders the legend item.
|
||||
/// </summary>
|
||||
@@ -96,7 +105,7 @@ namespace Radzen.Blazor
|
||||
/// </summary>
|
||||
/// <param name="x">The x.</param>
|
||||
/// <param name="y">The y.</param>
|
||||
object DataAt(double x, double y);
|
||||
(object, Point) DataAt(double x, double y);
|
||||
/// <summary>
|
||||
/// Returns data chart position
|
||||
/// </summary>
|
||||
|
||||
@@ -28,6 +28,12 @@ namespace Radzen.Blazor
|
||||
/// <summary>
|
||||
/// Renders tooltip
|
||||
/// </summary>
|
||||
RenderFragment RenderTooltip(double mouseX, double mouseY, double marginLeft, double marginTop);
|
||||
RenderFragment RenderTooltip(double mouseX, double mouseY);
|
||||
|
||||
/// <summary>
|
||||
/// Get position of the overlay tooltip.
|
||||
/// </summary>
|
||||
/// <returns>Position.</returns>
|
||||
Point GetTooltipPosition(double mouseX, double mouseY);
|
||||
}
|
||||
}
|
||||
|
||||
26
Radzen.Blazor/IChartStackedAreaSeries.cs
Normal file
26
Radzen.Blazor/IChartStackedAreaSeries.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Radzen.Blazor
|
||||
{
|
||||
/// <summary>
|
||||
/// Marker interface for <see cref="RadzenStackedAreaSeries{TItem}" />.
|
||||
/// </summary>
|
||||
public interface IChartStackedAreaSeries
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the count.
|
||||
/// </summary>
|
||||
/// <value>The count.</value>
|
||||
int Count { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the values for category.
|
||||
/// </summary>
|
||||
IEnumerable<double> ValuesForCategory(double category);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value at the specified index.
|
||||
/// </summary>
|
||||
double ValueAt(int index);
|
||||
}
|
||||
}
|
||||
26
Radzen.Blazor/IChartStackedBarSeries.cs
Normal file
26
Radzen.Blazor/IChartStackedBarSeries.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Radzen.Blazor
|
||||
{
|
||||
/// <summary>
|
||||
/// Marker interface for <see cref="RadzenStackedBarSeries{TItem}" />.
|
||||
/// </summary>
|
||||
public interface IChartStackedBarSeries : IChartBarSeries
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the value at the specified index.
|
||||
/// </summary>
|
||||
double ValueAt(int index);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the values for category.
|
||||
/// </summary>
|
||||
IEnumerable<double> ValuesForCategory(double category);
|
||||
/// <summary>
|
||||
/// Gets the items for category.
|
||||
/// </summary>
|
||||
/// <param name="category"></param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<object> ItemsForCategory(double category);
|
||||
}
|
||||
}
|
||||
33
Radzen.Blazor/IChartStackedColumnSeries.cs
Normal file
33
Radzen.Blazor/IChartStackedColumnSeries.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Radzen.Blazor
|
||||
{
|
||||
/// <summary>
|
||||
/// Marker interface for <see cref="RadzenStackedColumnSeries{TItem}" />.
|
||||
/// </summary>
|
||||
public interface IChartStackedColumnSeries
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the count.
|
||||
/// </summary>
|
||||
/// <value>The count.</value>
|
||||
int Count { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the values for category.
|
||||
/// </summary>
|
||||
IEnumerable<double> ValuesForCategory(double category);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the items for category.
|
||||
/// </summary>
|
||||
/// <param name="category"></param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<object> ItemsForCategory(double category);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value at the specified index.
|
||||
/// </summary>
|
||||
double ValueAt(int index);
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,11 @@ namespace Radzen.Blazor
|
||||
/// </summary>
|
||||
public interface IScheduler
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the appointment move event callback.
|
||||
/// </summary>
|
||||
/// <value>The appointment move event callback.</value>
|
||||
EventCallback<SchedulerAppointmentMoveEventArgs> AppointmentMove { get; set; }
|
||||
/// <summary>
|
||||
/// Gets the appointments in the specified range.
|
||||
/// </summary>
|
||||
@@ -59,6 +64,32 @@ namespace Radzen.Blazor
|
||||
/// <param name="end">The end.</param>
|
||||
Task SelectSlot(DateTime start, DateTime end);
|
||||
/// <summary>
|
||||
/// Selects the specified slot.
|
||||
/// </summary>
|
||||
/// <param name="start">The start.</param>
|
||||
/// <param name="end">The end.</param>
|
||||
/// <param name="appointments">The appointments for this range.</param>
|
||||
Task<bool> SelectSlot(DateTime start, DateTime end, IEnumerable<AppointmentData> appointments);
|
||||
/// <summary>
|
||||
/// Selects the specified month.
|
||||
/// </summary>
|
||||
/// <param name="monthStart">The start of the month.</param>
|
||||
/// <param name="appointments">The appointments for this range.</param>
|
||||
Task SelectMonth(DateTime monthStart, IEnumerable<AppointmentData> appointments);
|
||||
/// <summary>
|
||||
/// Selects the specified day.
|
||||
/// </summary>
|
||||
/// <param name="day">The selected day.</param>
|
||||
/// <param name="appointments">The appointments for this range.</param>
|
||||
Task SelectDay(DateTime day, IEnumerable<AppointmentData> appointments);
|
||||
/// <summary>
|
||||
/// Selects the specified more link.
|
||||
/// </summary>
|
||||
/// <param name="start">The start.</param>
|
||||
/// <param name="end">The end.</param>
|
||||
/// <param name="appointments">The appointments for this range.</param>
|
||||
Task<bool> SelectMore(DateTime start, DateTime end, IEnumerable<AppointmentData> appointments);
|
||||
/// <summary>
|
||||
/// Gets the appointment HTML attributes.
|
||||
/// </summary>
|
||||
/// <param name="item">The appointment.</param>
|
||||
@@ -77,6 +108,31 @@ namespace Radzen.Blazor
|
||||
/// <param name="item">The item.</param>
|
||||
/// <returns>RenderFragment.</returns>
|
||||
RenderFragment RenderAppointment(AppointmentData item);
|
||||
|
||||
/// <summary>
|
||||
/// Notifies the scheduler that the user has moved the mouse over the specified appointment.
|
||||
/// </summary>
|
||||
/// <param name="reference"></param>
|
||||
/// <param name="data"></param>
|
||||
Task MouseEnterAppointment(ElementReference reference, AppointmentData data);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the scheduler has a mouse enter appointment listener.
|
||||
/// </summary>
|
||||
bool HasMouseEnterAppointmentDelegate();
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the scheduler has an AppointmentMove listener.
|
||||
/// </summary>
|
||||
bool HasAppointmentMoveDelegate();
|
||||
|
||||
/// <summary>
|
||||
/// Notifies the scheduler that the user has moved the mouse out of the specified appointment.
|
||||
/// </summary>
|
||||
/// <param name="reference"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
Task MouseLeaveAppointment(ElementReference reference, AppointmentData data);
|
||||
/// <summary>
|
||||
/// Reloads this instance.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Radzen.Blazor
|
||||
@@ -49,5 +50,11 @@ namespace Radzen.Blazor
|
||||
/// Gets the end date.
|
||||
/// </summary>
|
||||
DateTime EndDate { get; }
|
||||
/// <summary>
|
||||
/// Handles appointent move event.
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
Task OnAppointmentMove(SchedulerAppointmentMoveEventArgs data);
|
||||
}
|
||||
}
|
||||
22
Radzen.Blazor/Interpolation.cs
Normal file
22
Radzen.Blazor/Interpolation.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
namespace Radzen.Blazor
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the interpolation mode of lines between data points. Used by <see cref="RadzenAreaSeries{TItem}"/> and <see cref="RadzenLineSeries{TItem}"/>.
|
||||
/// </summary>
|
||||
public enum Interpolation
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Points are connected by a straight line.
|
||||
/// </summary>
|
||||
Line,
|
||||
/// <summary>
|
||||
/// Points are connected by a smooth curve.
|
||||
/// </summary>
|
||||
Spline,
|
||||
/// <summary>
|
||||
/// Points are connected by horizontal and vertical lines only.
|
||||
/// </summary>
|
||||
Step
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2022 Radzen Ltd
|
||||
Copyright (c) 2018-2025 Radzen Ltd
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Radzen.Blazor
|
||||
|
||||
protected virtual double CalculateTickCount(int distance)
|
||||
{
|
||||
return Math.Ceiling(Math.Abs(Output.End - Output.Start) / distance);
|
||||
return Math.Max(1, Math.Ceiling(Math.Abs(Output.End - Output.Start) / distance));
|
||||
}
|
||||
|
||||
public override (double Start, double End, double Step) Ticks(int distance)
|
||||
@@ -88,11 +88,6 @@ namespace Radzen.Blazor
|
||||
if (Step is IConvertible)
|
||||
{
|
||||
step = Convert.ToDouble(Step);
|
||||
|
||||
if (step <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("Step must be greater than zero");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +97,7 @@ namespace Radzen.Blazor
|
||||
end = Math.Ceiling(end / step) * step;
|
||||
}
|
||||
|
||||
if (!Double.IsFinite(Input.Start) && !Double.IsFinite(Input.End))
|
||||
if (!double.IsFinite(Input.Start) || !double.IsFinite(Input.End))
|
||||
{
|
||||
Input.Start = start = 0;
|
||||
Input.End = end = 2;
|
||||
@@ -110,7 +105,7 @@ namespace Radzen.Blazor
|
||||
Round = false;
|
||||
}
|
||||
|
||||
if (!Double.IsFinite(start) && !Double.IsFinite(end))
|
||||
if (!double.IsFinite(start) || !double.IsFinite(end))
|
||||
{
|
||||
Input.Start = start = 0;
|
||||
Input.End = end = 2;
|
||||
@@ -118,7 +113,12 @@ namespace Radzen.Blazor
|
||||
Round = false;
|
||||
}
|
||||
|
||||
return (start, end, step);
|
||||
if (step == 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("Step must be non-zero");
|
||||
}
|
||||
|
||||
return (start, end, Math.Abs(step));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,5 +3,4 @@
|
||||
<assembly fullname="System.Core">
|
||||
<type fullname="System.Linq.Queryable" preserve="all" />
|
||||
</assembly>
|
||||
<assembly fullname="System.Linq.Dynamic.Core" />
|
||||
</linker>
|
||||
47
Radzen.Blazor/Markdown/AtxHeading.cs
Normal file
47
Radzen.Blazor/Markdown/AtxHeading.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Radzen.Blazor.Markdown;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a markdown ATX heading: <c># Heading</c>.
|
||||
/// </summary>
|
||||
public class AtxHeading : Heading
|
||||
{
|
||||
private static readonly Regex MarkerRegex = new(@"^#{1,6}(?:[ \t]+|$)");
|
||||
|
||||
private static readonly Regex StartRegex = new(@"^[ \t]*#+[ \t]*$");
|
||||
|
||||
private static readonly Regex EndRegex = new(@"[ \t]+#+[ \t]*$");
|
||||
|
||||
internal static BlockStart Start(BlockParser parser, Block block)
|
||||
{
|
||||
if (parser.Indented)
|
||||
{
|
||||
return BlockStart.Skip;
|
||||
}
|
||||
|
||||
var line = parser.CurrentLine[parser.NextNonSpace..];
|
||||
|
||||
var match = MarkerRegex.Match(line);
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
parser.AdvanceNextNonSpace();
|
||||
parser.AdvanceOffset(match.Length, false);
|
||||
parser.CloseUnmatchedBlocks();
|
||||
var container = parser.AddChild<AtxHeading>(parser.NextNonSpace);
|
||||
container.Level = match.Value.Trim().Length;
|
||||
|
||||
// remove trailing ###s:
|
||||
line = parser.CurrentLine[parser.Offset..];
|
||||
|
||||
container.Value = EndRegex.Replace(StartRegex.Replace(line, ""), "");
|
||||
|
||||
parser.AdvanceOffset(parser.CurrentLine.Length - parser.Offset, false);
|
||||
|
||||
return BlockStart.Leaf;
|
||||
}
|
||||
|
||||
return BlockStart.Skip;
|
||||
}
|
||||
}
|
||||
256
Radzen.Blazor/Markdown/BlazorMarkdownRenderer.cs
Normal file
256
Radzen.Blazor/Markdown/BlazorMarkdownRenderer.cs
Normal file
@@ -0,0 +1,256 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
|
||||
namespace Radzen.Blazor.Markdown;
|
||||
|
||||
class BlazorMarkdownRenderer(RenderTreeBuilder builder, Action<RenderTreeBuilder, int> outlet) : NodeVisitorBase
|
||||
{
|
||||
public const string Outlet = "<!--rz-outlet-{0}-->";
|
||||
|
||||
public override void VisitHeading(Heading heading)
|
||||
{
|
||||
builder.OpenComponent<RadzenText>(0);
|
||||
builder.AddAttribute(1, nameof(RadzenText.ChildContent), RenderChildren(heading.Children));
|
||||
switch (heading.Level)
|
||||
{
|
||||
case 1:
|
||||
builder.AddAttribute(2, nameof(RadzenText.TextStyle), TextStyle.H1);
|
||||
break;
|
||||
case 2:
|
||||
builder.AddAttribute(2, nameof(RadzenText.TextStyle), TextStyle.H2);
|
||||
break;
|
||||
case 3:
|
||||
builder.AddAttribute(2, nameof(RadzenText.TextStyle), TextStyle.H3);
|
||||
break;
|
||||
case 4:
|
||||
builder.AddAttribute(2, nameof(RadzenText.TextStyle), TextStyle.H4);
|
||||
break;
|
||||
case 5:
|
||||
builder.AddAttribute(2, nameof(RadzenText.TextStyle), TextStyle.H5);
|
||||
break;
|
||||
case 6:
|
||||
builder.AddAttribute(2, nameof(RadzenText.TextStyle), TextStyle.H6);
|
||||
break;
|
||||
}
|
||||
builder.CloseComponent();
|
||||
}
|
||||
|
||||
public override void VisitTable(Table table)
|
||||
{
|
||||
builder.OpenComponent<RadzenTable>(0);
|
||||
builder.AddAttribute(1, nameof(RadzenTable.ChildContent), RenderChildren(table.Rows));
|
||||
builder.CloseComponent();
|
||||
}
|
||||
|
||||
public override void VisitTableRow(TableRow row)
|
||||
{
|
||||
builder.OpenComponent<RadzenTableRow>(0);
|
||||
builder.AddAttribute(1, nameof(RadzenTableRow.ChildContent), RenderChildren(row.Cells));
|
||||
builder.CloseComponent();
|
||||
}
|
||||
public override void VisitTableCell(TableCell cell)
|
||||
{
|
||||
builder.OpenComponent<RadzenTableCell>(0);
|
||||
builder.AddAttribute(1, nameof(RadzenTableCell.ChildContent), RenderChildren(cell.Children));
|
||||
RenderCellAlignment(builder, cell.Alignment);
|
||||
builder.CloseComponent();
|
||||
}
|
||||
|
||||
private static void RenderCellAlignment(RenderTreeBuilder builder, TableCellAlignment alignment)
|
||||
{
|
||||
switch (alignment)
|
||||
{
|
||||
case TableCellAlignment.Center:
|
||||
builder.AddAttribute(2, nameof(RadzenTableCell.Style), "text-align: center");
|
||||
break;
|
||||
case TableCellAlignment.Right:
|
||||
builder.AddAttribute(2, nameof(RadzenTableCell.Style), "text-align: right");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void VisitTableHeaderRow(TableHeaderRow header)
|
||||
{
|
||||
builder.OpenComponent<RadzenTableHeader>(0);
|
||||
builder.AddAttribute(1, nameof(RadzenTableHeader.ChildContent), new RenderFragment(headerBuilder =>
|
||||
{
|
||||
headerBuilder.OpenComponent<RadzenTableHeaderRow>(0);
|
||||
headerBuilder.AddAttribute(1, nameof(RadzenTableHeaderRow.ChildContent), new RenderFragment(headerRowBuilder =>
|
||||
{
|
||||
foreach (var cell in header.Cells)
|
||||
{
|
||||
headerRowBuilder.OpenComponent<RadzenTableHeaderCell>(0);
|
||||
headerRowBuilder.AddAttribute(1, nameof(RadzenTableHeaderCell.ChildContent), RenderChildren(cell.Children));
|
||||
RenderCellAlignment(headerRowBuilder, cell.Alignment);
|
||||
headerRowBuilder.CloseComponent();
|
||||
}
|
||||
}));
|
||||
headerBuilder.CloseComponent();
|
||||
}));
|
||||
builder.CloseComponent();
|
||||
}
|
||||
|
||||
public override void VisitIndentedCodeBlock(IndentedCodeBlock code)
|
||||
{
|
||||
builder.OpenElement(0, "pre");
|
||||
builder.OpenElement(1, "code");
|
||||
builder.AddContent(2, code.Value);
|
||||
builder.CloseElement();
|
||||
builder.CloseElement();
|
||||
}
|
||||
|
||||
public override void VisitParagraph(Paragraph paragraph)
|
||||
{
|
||||
if (paragraph.Parent is ListItem item && item.Parent is List list && list.Tight)
|
||||
{
|
||||
VisitChildren(paragraph.Children);
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.OpenComponent<RadzenText>(0);
|
||||
builder.AddAttribute(1, nameof(RadzenText.ChildContent), RenderChildren(paragraph.Children));
|
||||
builder.CloseComponent();
|
||||
}
|
||||
}
|
||||
|
||||
private RenderFragment RenderChildren(IEnumerable<INode> children)
|
||||
{
|
||||
return innerBuilder =>
|
||||
{
|
||||
var inner = new BlazorMarkdownRenderer(innerBuilder, outlet);
|
||||
inner.VisitChildren(children);
|
||||
};
|
||||
}
|
||||
|
||||
public override void VisitBlockQuote(BlockQuote blockQuote)
|
||||
{
|
||||
builder.OpenElement(0, "blockquote");
|
||||
VisitChildren(blockQuote.Children);
|
||||
builder.CloseElement();
|
||||
}
|
||||
|
||||
public override void VisitCode(Code code)
|
||||
{
|
||||
builder.OpenElement(0, "code");
|
||||
builder.AddContent(1, code.Value);
|
||||
builder.CloseElement();
|
||||
}
|
||||
|
||||
public override void VisitStrong(Strong strong)
|
||||
{
|
||||
builder.OpenElement(0, "strong");
|
||||
VisitChildren(strong.Children);
|
||||
builder.CloseElement();
|
||||
}
|
||||
|
||||
public override void VisitEmphasis(Emphasis emphasis)
|
||||
{
|
||||
builder.OpenElement(0, "em");
|
||||
VisitChildren(emphasis.Children);
|
||||
builder.CloseElement();
|
||||
}
|
||||
|
||||
public override void VisitLink(Link link)
|
||||
{
|
||||
builder.OpenComponent<RadzenLink>(0);
|
||||
builder.AddAttribute(1, nameof(RadzenLink.Path), link.Destination);
|
||||
builder.AddAttribute(2, nameof(RadzenLink.ChildContent), RenderChildren(link.Children));
|
||||
|
||||
if (!string.IsNullOrEmpty(link.Title))
|
||||
{
|
||||
builder.AddAttribute(3, "title", link.Title);
|
||||
}
|
||||
|
||||
builder.CloseComponent();
|
||||
}
|
||||
|
||||
public override void VisitImage(Image image)
|
||||
{
|
||||
builder.OpenComponent<RadzenImage>(0);
|
||||
builder.AddAttribute(1, nameof(RadzenImage.Path), image.Destination);
|
||||
|
||||
if (!string.IsNullOrEmpty(image.Title))
|
||||
{
|
||||
builder.AddAttribute(2, nameof(RadzenImage.AlternateText), image.Title);
|
||||
}
|
||||
|
||||
builder.CloseElement();
|
||||
}
|
||||
|
||||
public override void VisitOrderedList(OrderedList orderedList)
|
||||
{
|
||||
builder.OpenElement(0, "ol");
|
||||
VisitChildren(orderedList.Children);
|
||||
builder.CloseElement();
|
||||
}
|
||||
|
||||
public override void VisitUnorderedList(UnorderedList unorderedList)
|
||||
{
|
||||
builder.OpenElement(0, "ul");
|
||||
VisitChildren(unorderedList.Children);
|
||||
builder.CloseElement();
|
||||
}
|
||||
|
||||
public override void VisitListItem(ListItem listItem)
|
||||
{
|
||||
builder.OpenElement(0, "li");
|
||||
VisitChildren(listItem.Children);
|
||||
builder.CloseElement();
|
||||
}
|
||||
|
||||
public override void VisitFencedCodeBlock(FencedCodeBlock fencedCodeBlock)
|
||||
{
|
||||
builder.OpenElement(0, "pre");
|
||||
builder.OpenElement(1, "code");
|
||||
builder.AddContent(2, fencedCodeBlock.Value);
|
||||
builder.CloseElement();
|
||||
builder.CloseElement();
|
||||
}
|
||||
|
||||
public override void VisitThematicBreak(ThematicBreak thematicBreak)
|
||||
{
|
||||
builder.OpenElement(0, "hr");
|
||||
builder.CloseElement();
|
||||
}
|
||||
|
||||
public override void VisitHtmlBlock(HtmlBlock htmlBlock)
|
||||
{
|
||||
VisitHtml(htmlBlock.Value);
|
||||
}
|
||||
|
||||
public override void VisitLineBreak(LineBreak lineBreak)
|
||||
{
|
||||
builder.OpenElement(0, "br");
|
||||
builder.CloseElement();
|
||||
}
|
||||
|
||||
public override void VisitText(Text text)
|
||||
{
|
||||
builder.AddContent(0, text.Value);
|
||||
}
|
||||
|
||||
private static readonly Regex OutletRegex = new (@"<!--rz-outlet-(\d+)-->");
|
||||
|
||||
private void VisitHtml(string html)
|
||||
{
|
||||
var match = OutletRegex.Match(html);
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
var markerId = Convert.ToInt32(match.Groups[1].Value);
|
||||
|
||||
outlet(builder, markerId);
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AddMarkupContent(0, html);
|
||||
}
|
||||
}
|
||||
public override void VisitHtmlInline(HtmlInline html)
|
||||
{
|
||||
VisitHtml(html.Value);
|
||||
}
|
||||
}
|
||||
73
Radzen.Blazor/Markdown/Block.cs
Normal file
73
Radzen.Blazor/Markdown/Block.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
|
||||
namespace Radzen.Blazor.Markdown;
|
||||
|
||||
#nullable enable
|
||||
/// <summary>
|
||||
/// Base class for a markdown block nodes.
|
||||
/// </summary>
|
||||
public abstract class Block : INode
|
||||
{
|
||||
/// <summary>
|
||||
/// Accepts a visitor.
|
||||
/// </summary>
|
||||
/// <param name="visitor"></param>
|
||||
public abstract void Accept(INodeVisitor visitor);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the last child of the block.
|
||||
/// </summary>
|
||||
public virtual Block? LastChild => null;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the first child of the block.
|
||||
/// </summary>
|
||||
public virtual Block? FirstChild => null;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the next sibling of the block.
|
||||
/// </summary>
|
||||
public virtual Block? Next => Parent.NextSibling(this);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the parent node of the block.
|
||||
/// </summary>
|
||||
public BlockContainer Parent { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Removes the block from its parent.
|
||||
/// </summary>
|
||||
public void Remove()
|
||||
{
|
||||
Parent.Remove(this);
|
||||
}
|
||||
|
||||
internal virtual BlockMatch Matches(BlockParser parser) => 0;
|
||||
|
||||
internal bool Open { get; set; } = true;
|
||||
|
||||
internal Range Range;
|
||||
|
||||
internal virtual void Close(BlockParser parser)
|
||||
{
|
||||
Open = false;
|
||||
}
|
||||
}
|
||||
|
||||
enum BlockMatch
|
||||
{
|
||||
Match,
|
||||
Skip,
|
||||
Break
|
||||
}
|
||||
|
||||
struct Position
|
||||
{
|
||||
public int Line { get; set; }
|
||||
public int Column { get; set; }
|
||||
}
|
||||
|
||||
struct Range
|
||||
{
|
||||
public Position Start;
|
||||
public Position End;
|
||||
}
|
||||
87
Radzen.Blazor/Markdown/BlockContainer.cs
Normal file
87
Radzen.Blazor/Markdown/BlockContainer.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Radzen.Blazor.Markdown;
|
||||
|
||||
#nullable enable
|
||||
/// <summary>
|
||||
/// Base class for markdown block nodes that can contain other blocks.
|
||||
/// </summary>
|
||||
public abstract class BlockContainer : Block
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the children of the block.
|
||||
/// </summary>
|
||||
public IReadOnlyList<Block> Children => children;
|
||||
|
||||
private readonly List<Block> children = [];
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the block can contain the specified node.
|
||||
/// </summary>
|
||||
public virtual bool CanContain(Block node) => false;
|
||||
|
||||
/// <summary>
|
||||
/// Appends a block to the children of the block.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the block.</typeparam>
|
||||
/// <param name="block">The block to add.</param>
|
||||
/// <returns>The added block.</returns>
|
||||
public virtual T Add<T>(T block) where T : Block
|
||||
{
|
||||
children.Add(block);
|
||||
|
||||
block.Parent = this;
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces a block with another block.
|
||||
/// </summary>
|
||||
/// <param name="source">The block to replace.</param>
|
||||
/// <param name="target">The block to replace with.</param>
|
||||
public void Replace(Block source, Block target)
|
||||
{
|
||||
var index = children.IndexOf(source);
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
children[index] = target;
|
||||
target.Parent = this;
|
||||
target.Range = source.Range;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a block from the children of the block.
|
||||
/// </summary>
|
||||
/// <param name="block">The block to remove.</param>
|
||||
public void Remove(Block block)
|
||||
{
|
||||
children.Remove(block);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the next sibling of the block.
|
||||
/// </summary>
|
||||
/// <param name="block">The block to get the next sibling of.</param>
|
||||
/// <returns>The next sibling of the block.</returns>
|
||||
public Block? NextSibling(Block block)
|
||||
{
|
||||
var index = children.IndexOf(block);
|
||||
|
||||
if (index >= 0 && index < children.Count - 1)
|
||||
{
|
||||
return children[index + 1];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Block? LastChild => children.Count > 0 ? children[^1] : null;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Block? FirstChild => children.Count > 0 ? children[0] : null;
|
||||
}
|
||||
491
Radzen.Blazor/Markdown/BlockParser.cs
Normal file
491
Radzen.Blazor/Markdown/BlockParser.cs
Normal file
@@ -0,0 +1,491 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Radzen.Blazor.Markdown;
|
||||
|
||||
#nullable enable
|
||||
|
||||
class BlockParser
|
||||
{
|
||||
private static readonly string tagName = @"[A-Za-z][A-Za-z0-9-]*";
|
||||
private static readonly string attributeName = @"[a-zA-Z_:][a-zA-Z0-9:._-]*";
|
||||
private static readonly string unquotedValue = @"[^""'=<>`\x00-\x20]+";
|
||||
private static readonly string singleQuotedValue = @"'[^']*'";
|
||||
private static readonly string doubleQuotedValue = @"""[^""]*""";
|
||||
private static readonly string attributeValue = @$"(?:{unquotedValue}|{singleQuotedValue}|{doubleQuotedValue})";
|
||||
private static readonly string attributeValueSpec = @$"(?:\s*=\s*{attributeValue})";
|
||||
private static readonly string attribute = @$"(?:\s+{attributeName}{attributeValueSpec}?)";
|
||||
|
||||
private static readonly string OpenTag = @$"<{tagName}{attribute}*\s*/?>";
|
||||
private static readonly string CloseTag = @$"</{tagName}\s*[>]";
|
||||
private static readonly string htmlComment = @"<!-->|<!--->|<!--[\s\S]*?-->";
|
||||
private static readonly string processingInstruction = @"<\?[ \s\S]*?\?>";
|
||||
private static readonly string declaration = @$"<![A-Za-z]+[^>]*>";
|
||||
private static readonly string cdata = @"<!\[CDATA\[[\s\S]*?\]\]>";
|
||||
|
||||
public static readonly Regex HtmlRegex = new(@$"^(?:{OpenTag}|{CloseTag}|{htmlComment}|{processingInstruction}|{declaration}|{cdata})");
|
||||
|
||||
private BlockParser()
|
||||
{
|
||||
Tip = document;
|
||||
|
||||
OldTip = document;
|
||||
|
||||
lastMatchedContainer = document;
|
||||
}
|
||||
|
||||
public static Document Parse(string markdown)
|
||||
{
|
||||
var parser = new BlockParser();
|
||||
|
||||
var document = parser.ParseBlocks(markdown);
|
||||
|
||||
parser.ParseInlines(document);
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
public static readonly Regex NewLineRegex = new(@"\r\n|\r|\n");
|
||||
|
||||
private readonly Document document = new();
|
||||
|
||||
private void ParseInlines(Document document)
|
||||
{
|
||||
var visitor = new InlineVisitor(linkReferences);
|
||||
document.Accept(visitor);
|
||||
}
|
||||
|
||||
public char Peek()
|
||||
{
|
||||
return CurrentLine.Peek(Offset);
|
||||
}
|
||||
|
||||
public char PeekNonSpace(int offset = 0)
|
||||
{
|
||||
return CurrentLine.Peek(NextNonSpace + offset);
|
||||
}
|
||||
|
||||
public void AdvanceOffset(int count, bool columns)
|
||||
{
|
||||
var currentLine = CurrentLine;
|
||||
char c;
|
||||
|
||||
while (count > 0 && Offset < currentLine.Length != default)
|
||||
{
|
||||
c = currentLine[Offset];
|
||||
|
||||
if (c == '\t')
|
||||
{
|
||||
var charsToTab = 4 - (Column % 4);
|
||||
|
||||
if (columns)
|
||||
{
|
||||
PartiallyConsumedTab = charsToTab > count;
|
||||
var charsToAdvance = charsToTab > count ? count : charsToTab;
|
||||
Column += charsToAdvance;
|
||||
Offset += PartiallyConsumedTab ? 0 : 1;
|
||||
count -= charsToAdvance;
|
||||
}
|
||||
else
|
||||
{
|
||||
PartiallyConsumedTab = false;
|
||||
Column += charsToTab;
|
||||
Offset += 1;
|
||||
count -= 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PartiallyConsumedTab = false;
|
||||
Offset += 1;
|
||||
Column += 1;
|
||||
count -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Document ParseBlocks(string markdown)
|
||||
{
|
||||
LineNumber = 0;
|
||||
|
||||
lastMatchedContainer = document;
|
||||
|
||||
var lines = NewLineRegex.Split(markdown);
|
||||
|
||||
var length = lines.Length;
|
||||
|
||||
if (markdown.EndsWith(InlineParser.LineFeed))
|
||||
{
|
||||
length--;
|
||||
}
|
||||
|
||||
for (var index = 0; index < length; index++)
|
||||
{
|
||||
IncorporateLine(lines[index]);
|
||||
}
|
||||
|
||||
while (Tip != null)
|
||||
{
|
||||
Close(Tip, length);
|
||||
}
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
private void IncorporateLine(string line)
|
||||
{
|
||||
Offset = 0;
|
||||
Column = 0;
|
||||
Blank = false;
|
||||
PartiallyConsumedTab = false;
|
||||
LineNumber++;
|
||||
|
||||
Block container = document;
|
||||
OldTip = Tip;
|
||||
Block? tail;
|
||||
var allMatched = true;
|
||||
CurrentLine = line;
|
||||
|
||||
while ((tail = container.LastChild) != null && tail.Open)
|
||||
{
|
||||
container = tail;
|
||||
|
||||
FindNextNonSpace();
|
||||
|
||||
switch (container.Matches(this))
|
||||
{
|
||||
case BlockMatch.Match: // we've matched, keep going
|
||||
break;
|
||||
case BlockMatch.Skip: // we've failed to match a block
|
||||
allMatched = false;
|
||||
break;
|
||||
case BlockMatch.Break: // we've hit end of line for fenced code close and can return
|
||||
return;
|
||||
default:
|
||||
throw new InvalidOperationException("Invalid continue result");
|
||||
}
|
||||
|
||||
if (!allMatched)
|
||||
{
|
||||
container = container.Parent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
AllClosed = container == OldTip;
|
||||
lastMatchedContainer = container;
|
||||
|
||||
var matchedLeaf = container is not (Paragraph or Table) && container is Leaf;
|
||||
|
||||
while (!matchedLeaf)
|
||||
{
|
||||
FindNextNonSpace();
|
||||
|
||||
int blockIndex;
|
||||
|
||||
for (blockIndex = 0; blockIndex < blockStarts.Length; blockIndex++)
|
||||
{
|
||||
var blockStart = blockStarts[blockIndex];
|
||||
|
||||
var result = blockStart(this, container);
|
||||
|
||||
if (result == BlockStart.Container)
|
||||
{
|
||||
container = Tip;
|
||||
break;
|
||||
}
|
||||
else if (result == BlockStart.Leaf)
|
||||
{
|
||||
container = Tip;
|
||||
matchedLeaf = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (blockIndex == blockStarts.Length)
|
||||
{
|
||||
AdvanceNextNonSpace();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// What remains at the offset is a text line. Add the text to the
|
||||
// appropriate container.
|
||||
|
||||
if (!AllClosed && !Blank && this.Tip is Paragraph or Table)
|
||||
{
|
||||
// lazy paragraph continuation
|
||||
if (Tip is Paragraph paragraph)
|
||||
{
|
||||
paragraph.AddLine(this);
|
||||
}
|
||||
else if (Tip is Table table)
|
||||
{
|
||||
table.AddLine(this);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// not a lazy continuation
|
||||
|
||||
// finalize any blocks not matched
|
||||
CloseUnmatchedBlocks();
|
||||
|
||||
if (container is Leaf leaf)
|
||||
{
|
||||
leaf.AddLine(this);
|
||||
|
||||
if (container is HtmlBlock block && block.Type >= 1 && block.Type <= 5 && HtmlBlockCloseRegex[block.Type].IsMatch(line[Offset..]))
|
||||
{
|
||||
LastLineLength = line.Length;
|
||||
Close(container, LineNumber);
|
||||
}
|
||||
}
|
||||
else if (Offset < line.Length && !Blank)
|
||||
{
|
||||
var paragraph = AddChild<Paragraph>(Offset);
|
||||
AdvanceNextNonSpace();
|
||||
paragraph.AddLine(this);
|
||||
}
|
||||
}
|
||||
LastLineLength = line.Length;
|
||||
}
|
||||
|
||||
public int LastLineLength { get; set; }
|
||||
|
||||
public void Close(Block block, int lineNumber)
|
||||
{
|
||||
var above = block.Parent;
|
||||
block.Range.End.Line = lineNumber;
|
||||
block.Range.End.Column = LastLineLength;
|
||||
block.Close(this);
|
||||
Tip = above;
|
||||
}
|
||||
|
||||
public T AddChild<T>(int offset) where T : Block, new()
|
||||
{
|
||||
var node = new T();
|
||||
|
||||
AddChild(node, offset);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
public void AddChild(Block node, int offset)
|
||||
{
|
||||
while (Tip is not BlockContainer container || !container.CanContain(node))
|
||||
{
|
||||
Close(Tip, LineNumber - 1);
|
||||
}
|
||||
|
||||
if (Tip is BlockContainer parent)
|
||||
{
|
||||
parent.Add(node);
|
||||
}
|
||||
|
||||
var columnNumber = offset + 1; // offset 0 = column 1
|
||||
|
||||
node.Range.Start.Line = LineNumber;
|
||||
node.Range.Start.Column = columnNumber;
|
||||
|
||||
Tip = node;
|
||||
}
|
||||
|
||||
public void CloseUnmatchedBlocks()
|
||||
{
|
||||
if (!AllClosed)
|
||||
{
|
||||
while (OldTip != lastMatchedContainer)
|
||||
{
|
||||
var parent = OldTip.Parent;
|
||||
Close(OldTip, LineNumber - 1);
|
||||
OldTip = parent;
|
||||
}
|
||||
|
||||
AllClosed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void AdvanceNextNonSpace()
|
||||
{
|
||||
Offset = NextNonSpace;
|
||||
Column = NextNonSpaceColumn;
|
||||
PartiallyConsumedTab = false;
|
||||
}
|
||||
|
||||
private static readonly Func<BlockParser, Block, BlockStart>[] blockStarts =
|
||||
[
|
||||
BlockQuote.Start,
|
||||
AtxHeading.Start,
|
||||
FencedCodeBlock.Start,
|
||||
HtmlBlock.Start,
|
||||
SetExtHeading.Start,
|
||||
ThematicBreak.Start,
|
||||
ListItem.Start,
|
||||
IndentedCodeBlock.Start,
|
||||
Table.Start,
|
||||
];
|
||||
|
||||
public bool AllClosed { get; private set; }
|
||||
|
||||
private Block lastMatchedContainer;
|
||||
|
||||
public void FindNextNonSpace()
|
||||
{
|
||||
var currentLine = CurrentLine;
|
||||
var i = Offset;
|
||||
var cols = Column;
|
||||
char c = default;
|
||||
|
||||
while (i < currentLine.Length)
|
||||
{
|
||||
c = currentLine[i];
|
||||
|
||||
if (c == ' ')
|
||||
{
|
||||
i++;
|
||||
cols++;
|
||||
}
|
||||
else if (c == '\t')
|
||||
{
|
||||
i++;
|
||||
cols += 4 - (cols % 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Blank = c == '\n' || c == '\r' || i == currentLine.Length;
|
||||
NextNonSpace = i;
|
||||
NextNonSpaceColumn = cols;
|
||||
Indent = NextNonSpaceColumn - Column;
|
||||
Indented = Indent >= CodeIndent;
|
||||
}
|
||||
|
||||
public const int CodeIndent = 4;
|
||||
|
||||
public int Indent { get; private set; }
|
||||
public bool Indented { get; private set; }
|
||||
public int NextNonSpaceColumn { get; private set; }
|
||||
|
||||
public int NextNonSpace { get; private set; }
|
||||
|
||||
public bool Blank { get; private set; }
|
||||
public bool PartiallyConsumedTab { get; private set; }
|
||||
public Block Tip { get; set; }
|
||||
public Block OldTip { get; private set; }
|
||||
public string CurrentLine { get; private set; } = string.Empty;
|
||||
public int Offset { get; set; }
|
||||
public int Column { get; set; }
|
||||
public int LineNumber { get; private set; }
|
||||
|
||||
private static readonly Regex LinkReferenceRegex = new(@"^[ \t]{0,3}\[");
|
||||
|
||||
private readonly Dictionary<string, LinkReference> linkReferences = [];
|
||||
|
||||
// https://spec.commonmark.org/0.31.2/#html-blocks
|
||||
internal static readonly Regex[] HtmlBlockOpenRegex = [
|
||||
new (@"."), // dummy for 1 based indexing
|
||||
new (@"^<(?:script|pre|textarea|style)(?:\s|>|$)", RegexOptions.IgnoreCase),
|
||||
new (@"^<!--"),
|
||||
new (@"^<[?]"),
|
||||
new (@"^<![A-Za-z]"),
|
||||
new (@"^<!\[CDATA\["),
|
||||
new (@"^<[/]?(?:address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[123456]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|nav|noframes|ol|optgroup|option|p|param|section|search|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul)(?:\s|[/]?[>]|$)", RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
new (@$"^(?:{OpenTag}|{CloseTag})\s*$", RegexOptions.IgnoreCase)
|
||||
];
|
||||
|
||||
private static readonly Regex[] HtmlBlockCloseRegex = [
|
||||
new (@"."), // dummy for 1 based indexing
|
||||
new (@"</(?:script|pre|textarea|style)>", RegexOptions.IgnoreCase),
|
||||
new (@"-->"),
|
||||
new (@"\?>"),
|
||||
new (@">"),
|
||||
new (@"\]\]>")
|
||||
];
|
||||
|
||||
|
||||
public bool TryParseLinkReference(string markdown, out int newIndex)
|
||||
{
|
||||
newIndex = 0;
|
||||
|
||||
if (!LinkReferenceRegex.IsMatch(markdown))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var position = 0;
|
||||
|
||||
while (position < markdown.Length - 1 && (markdown[position] is not InlineParser.CloseBracket || (position > 0 && markdown[position - 1] is InlineParser.Backslash)))
|
||||
{
|
||||
position++;
|
||||
}
|
||||
|
||||
if (position >= markdown.Length - 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
position++;
|
||||
|
||||
if (position >= markdown.Length || markdown[position] is not InlineParser.Colon)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var colonIndex = position;
|
||||
var closeIndex = colonIndex - 1;
|
||||
var openIndex = 0;
|
||||
|
||||
while (openIndex < closeIndex && markdown[openIndex] is not InlineParser.OpenBracket)
|
||||
{
|
||||
openIndex++;
|
||||
}
|
||||
|
||||
if (openIndex == closeIndex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var id = new StringBuilder();
|
||||
|
||||
for (var index = openIndex + 1; index < closeIndex; index++)
|
||||
{
|
||||
var next = index < closeIndex - 1 ? markdown[index + 1] : default;
|
||||
|
||||
if (markdown[index] is not InlineParser.Backslash || !next.IsPunctuation())
|
||||
{
|
||||
id.Append(markdown[index]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!InlineParser.TryParseDestinationAndTitle(markdown, colonIndex + 1, out var destination, out var title, out position))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var link = new LinkReference { Destination = destination, Title = title };
|
||||
|
||||
var key = id.ToString().ToLowerInvariant();
|
||||
|
||||
if (!linkReferences.ContainsKey(key))
|
||||
{
|
||||
linkReferences[key] = link;
|
||||
}
|
||||
|
||||
newIndex = position;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
enum BlockStart
|
||||
{
|
||||
Skip,
|
||||
Container,
|
||||
Leaf
|
||||
}
|
||||
64
Radzen.Blazor/Markdown/BlockQuote.cs
Normal file
64
Radzen.Blazor/Markdown/BlockQuote.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
namespace Radzen.Blazor.Markdown;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Represents a markdown block quote: <c>> Quote</c>.
|
||||
/// </summary>
|
||||
public class BlockQuote : BlockContainer
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Accept(INodeVisitor visitor)
|
||||
{
|
||||
visitor.VisitBlockQuote(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanContain(Block node)
|
||||
{
|
||||
return node is not ListItem;
|
||||
}
|
||||
|
||||
internal override BlockMatch Matches(BlockParser parser)
|
||||
{
|
||||
if (!parser.Indented && parser.PeekNonSpace() == '>')
|
||||
{
|
||||
parser.AdvanceNextNonSpace();
|
||||
parser.AdvanceOffset(1, false);
|
||||
// optional following space
|
||||
|
||||
if (parser.Peek().IsSpaceOrTab())
|
||||
{
|
||||
parser.AdvanceOffset(1, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return BlockMatch.Skip;
|
||||
}
|
||||
|
||||
return BlockMatch.Match;
|
||||
}
|
||||
|
||||
internal static BlockStart Start(BlockParser parser, Block block)
|
||||
{
|
||||
if (!parser.Indented && parser.PeekNonSpace() == '>')
|
||||
{
|
||||
parser.AdvanceNextNonSpace();
|
||||
parser.AdvanceOffset(1, false);
|
||||
// optional following space
|
||||
|
||||
if (parser.Peek().IsSpaceOrTab())
|
||||
{
|
||||
parser.AdvanceOffset(1, true);
|
||||
}
|
||||
|
||||
parser.CloseUnmatchedBlocks();
|
||||
|
||||
parser.AddChild<BlockQuote>(parser.NextNonSpace);
|
||||
|
||||
return BlockStart.Container;
|
||||
}
|
||||
|
||||
return BlockStart.Skip;
|
||||
}
|
||||
}
|
||||
10
Radzen.Blazor/Markdown/CharExtensions.cs
Normal file
10
Radzen.Blazor/Markdown/CharExtensions.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Radzen.Blazor.Markdown;
|
||||
|
||||
static class CharExtensions
|
||||
{
|
||||
public static bool IsNullOrWhiteSpace(this char ch) => ch == '\0' || char.IsWhiteSpace(ch);
|
||||
|
||||
public static bool IsPunctuation(this char ch) => char.IsPunctuation(ch);
|
||||
|
||||
public static bool IsSpaceOrTab(this char ch) => ch == ' ' || ch == '\t';
|
||||
}
|
||||
19
Radzen.Blazor/Markdown/Code.cs
Normal file
19
Radzen.Blazor/Markdown/Code.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace Radzen.Blazor.Markdown;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a markdown inline code block: <c>`code`</c>.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
public class Code(string value) : Inline
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the code value.
|
||||
/// </summary>
|
||||
public string Value { get; set; } = value;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Accept(INodeVisitor visitor)
|
||||
{
|
||||
visitor.VisitCode(this);
|
||||
}
|
||||
}
|
||||
35
Radzen.Blazor/Markdown/Document.cs
Normal file
35
Radzen.Blazor/Markdown/Document.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
namespace Radzen.Blazor.Markdown;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a markdown document.
|
||||
/// </summary>
|
||||
public class Document : BlockContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Document"/> class.
|
||||
/// </summary>
|
||||
public Document()
|
||||
{
|
||||
Range.Start.Line = 1;
|
||||
Range.Start.Column = 1;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Accept(INodeVisitor visitor)
|
||||
{
|
||||
visitor.VisitDocument(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanContain(Block node)
|
||||
{
|
||||
return node is not ListItem;
|
||||
}
|
||||
|
||||
internal override void Close(BlockParser parser)
|
||||
{
|
||||
base.Close(parser);
|
||||
|
||||
LinkReferenceParser.Parse(parser, this);
|
||||
}
|
||||
}
|
||||
13
Radzen.Blazor/Markdown/Emphasis.cs
Normal file
13
Radzen.Blazor/Markdown/Emphasis.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Radzen.Blazor.Markdown;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an emphasis element in a markdown document: <c>_emphasis_</c> or <c>*emphasis*</c>.
|
||||
/// </summary>
|
||||
public class Emphasis : InlineContainer
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Accept(INodeVisitor visitor)
|
||||
{
|
||||
visitor.VisitEmphasis(this);
|
||||
}
|
||||
}
|
||||
98
Radzen.Blazor/Markdown/FencedCodeBlock.cs
Normal file
98
Radzen.Blazor/Markdown/FencedCodeBlock.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Radzen.Blazor.Markdown;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a fenced code block in a markdown document: <c>```</c> or <c>~~~</c>.
|
||||
/// </summary>
|
||||
public class FencedCodeBlock : Leaf
|
||||
{
|
||||
/// <summary>
|
||||
/// The delimiter used to start and end the code block.
|
||||
/// </summary>
|
||||
public string Delimiter { get; private set; }
|
||||
internal int Indent { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The info string of the code block. This is the first line of the code block and is used to specify the language of the code block.
|
||||
/// </summary>
|
||||
public string Info { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Accept(INodeVisitor visitor)
|
||||
{
|
||||
visitor.VisitFencedCodeBlock(this);
|
||||
}
|
||||
|
||||
internal override void Close(BlockParser parser)
|
||||
{
|
||||
base.Close(parser);
|
||||
|
||||
// first line becomes info string
|
||||
var newlinePos = Value.IndexOf('\n');
|
||||
var firstLine = Value[..newlinePos];
|
||||
Info = firstLine.Trim();
|
||||
Value = Value[(newlinePos + 1)..];
|
||||
}
|
||||
|
||||
internal override BlockMatch Matches(BlockParser parser)
|
||||
{
|
||||
var line = parser.CurrentLine[parser.NextNonSpace..];
|
||||
|
||||
var indent = parser.Indent;
|
||||
|
||||
var match = ClosingFenceRegex.Match(line);
|
||||
|
||||
if (indent <= 3 && parser.PeekNonSpace() == Delimiter[0] && match.Success && match.Length >= Delimiter.Length)
|
||||
{
|
||||
// closing fence - we're at end of line, so we can return
|
||||
parser.LastLineLength = parser.Offset + indent + match.Length;
|
||||
parser.Close(this, parser.LineNumber);
|
||||
return BlockMatch.Break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// skip optional spaces of fence offset
|
||||
var i = Indent;
|
||||
|
||||
while (i > 0 && parser.Peek().IsSpaceOrTab())
|
||||
{
|
||||
parser.AdvanceOffset(1, true);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
return BlockMatch.Match;
|
||||
}
|
||||
|
||||
|
||||
private static readonly Regex ClosingFenceRegex = new(@"^(?:`{3,}|~{3,})(?=[ \t]*$)");
|
||||
|
||||
private static readonly Regex OpeningFenceRegex = new(@"^`{3,}(?!.*`)|^~{3,}");
|
||||
|
||||
internal static BlockStart Start(BlockParser parser, Block node)
|
||||
{
|
||||
if (parser.Indented)
|
||||
{
|
||||
return BlockStart.Skip;
|
||||
}
|
||||
|
||||
var line = parser.CurrentLine[parser.NextNonSpace..];
|
||||
|
||||
var match = OpeningFenceRegex.Match(line);
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
parser.CloseUnmatchedBlocks();
|
||||
|
||||
var container = parser.AddChild<FencedCodeBlock>(parser.NextNonSpace);
|
||||
container.Delimiter = match.Value;
|
||||
container.Indent = parser.Indent;
|
||||
parser.AdvanceNextNonSpace();
|
||||
parser.AdvanceOffset(match.Value.Length, false);
|
||||
return BlockStart.Leaf;
|
||||
}
|
||||
|
||||
return BlockStart.Skip;
|
||||
}
|
||||
}
|
||||
25
Radzen.Blazor/Markdown/Heading.cs
Normal file
25
Radzen.Blazor/Markdown/Heading.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
namespace Radzen.Blazor.Markdown;
|
||||
|
||||
/// <summary>
|
||||
/// A base class for all heading elements.
|
||||
/// </summary>
|
||||
public abstract class Heading : Leaf
|
||||
{
|
||||
/// <summary>
|
||||
/// The level of the heading. The value is between 1 and 6.
|
||||
/// </summary>
|
||||
public int Level { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Accept(INodeVisitor visitor)
|
||||
{
|
||||
visitor.VisitHeading(this);
|
||||
}
|
||||
|
||||
internal override BlockMatch Matches(BlockParser parser)
|
||||
{
|
||||
// a heading can never container another line
|
||||
return BlockMatch.Skip;
|
||||
}
|
||||
}
|
||||
56
Radzen.Blazor/Markdown/HtmlBlock.cs
Normal file
56
Radzen.Blazor/Markdown/HtmlBlock.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Radzen.Blazor.Markdown;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an HTML block.
|
||||
/// </summary>
|
||||
public class HtmlBlock : Leaf
|
||||
{
|
||||
internal int Type { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Accept(INodeVisitor visitor)
|
||||
{
|
||||
visitor.VisitHtmlBlock(this);
|
||||
}
|
||||
|
||||
internal override BlockMatch Matches(BlockParser parser)
|
||||
{
|
||||
return parser.Blank && (Type == 6 || Type == 7) ? BlockMatch.Skip : BlockMatch.Match;
|
||||
}
|
||||
|
||||
private static readonly Regex TrailinNewLineRegex = new(@"\n$");
|
||||
|
||||
internal override void Close(BlockParser parser)
|
||||
{
|
||||
base.Close(parser);
|
||||
|
||||
Value = TrailinNewLineRegex.Replace(Value, "");
|
||||
}
|
||||
|
||||
internal static BlockStart Start(BlockParser parser, Block node)
|
||||
{
|
||||
if (!parser.Indented && parser.PeekNonSpace() == '<')
|
||||
{
|
||||
var line = parser.CurrentLine[parser.NextNonSpace..];
|
||||
|
||||
for (var blockType = 1; blockType <= 7; blockType++) {
|
||||
|
||||
if (BlockParser.HtmlBlockOpenRegex[blockType].IsMatch(line) &&
|
||||
(blockType < 7 || (node is not Paragraph &&
|
||||
!(!parser.AllClosed && !parser.Blank && parser.Tip is Paragraph) // maybe lazy
|
||||
))) {
|
||||
parser.CloseUnmatchedBlocks();
|
||||
// We don't adjust parser.offset;
|
||||
// spaces are part of the HTML block:
|
||||
var block = parser.AddChild<HtmlBlock>(parser.Offset);
|
||||
block.Type = blockType;
|
||||
return BlockStart.Leaf;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return BlockStart.Skip;
|
||||
}
|
||||
}
|
||||
19
Radzen.Blazor/Markdown/HtmlInline.cs
Normal file
19
Radzen.Blazor/Markdown/HtmlInline.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace Radzen.Blazor.Markdown;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an inline HTML element.
|
||||
/// </summary>
|
||||
public class HtmlInline : Inline
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the HTML element value.
|
||||
/// </summary>
|
||||
public string Value { get; set; }
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Accept(INodeVisitor visitor)
|
||||
{
|
||||
visitor.VisitHtmlInline(this);
|
||||
}
|
||||
}
|
||||
25
Radzen.Blazor/Markdown/IBlockInlineContainer.cs
Normal file
25
Radzen.Blazor/Markdown/IBlockInlineContainer.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Radzen.Blazor.Markdown;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a block node that has inline children.
|
||||
/// </summary>
|
||||
public interface IBlockInlineContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the inline children of the block.
|
||||
/// </summary>
|
||||
IReadOnlyList<Inline> Children { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Adds an inline child to the block.
|
||||
/// </summary>
|
||||
/// <param name="child"></param>
|
||||
void Add(Inline child);
|
||||
|
||||
/// <summary>
|
||||
/// Gets string value of the block.
|
||||
/// </summary>
|
||||
string Value { get; }
|
||||
}
|
||||
15
Radzen.Blazor/Markdown/INode.cs
Normal file
15
Radzen.Blazor/Markdown/INode.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Radzen.Blazor.Markdown;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a markdown node that can be visited by a <see cref="INodeVisitor"/>.
|
||||
/// </summary>
|
||||
public interface INode
|
||||
{
|
||||
/// <summary>
|
||||
/// Accepts a <see cref="INodeVisitor"/>.
|
||||
/// </summary>
|
||||
/// <param name="visitor"></param>
|
||||
public void Accept(INodeVisitor visitor);
|
||||
}
|
||||
128
Radzen.Blazor/Markdown/INodeVisitor.cs
Normal file
128
Radzen.Blazor/Markdown/INodeVisitor.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
namespace Radzen.Blazor.Markdown;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Represents a visitor for Markdown AST nodes.
|
||||
/// </summary>
|
||||
public interface INodeVisitor
|
||||
{
|
||||
/// <summary>
|
||||
/// Visits a heading node.
|
||||
/// </summary>
|
||||
void VisitHeading(Heading heading);
|
||||
|
||||
/// <summary>
|
||||
/// Visits a paragraph node.
|
||||
/// </summary>
|
||||
void VisitParagraph(Paragraph paragraph);
|
||||
|
||||
/// <summary>
|
||||
/// Visits a block quote node.
|
||||
/// </summary>
|
||||
void VisitBlockQuote(BlockQuote blockQuote);
|
||||
|
||||
/// <summary>
|
||||
/// Visits a document node.
|
||||
/// </summary>
|
||||
void VisitDocument(Document document);
|
||||
|
||||
/// <summary>
|
||||
/// Visits an unordered list node.
|
||||
/// </summary>
|
||||
void VisitUnorderedList(UnorderedList unorderedList);
|
||||
|
||||
/// <summary>
|
||||
/// Visits a list item node.
|
||||
/// </summary>
|
||||
void VisitListItem(ListItem listItem);
|
||||
|
||||
/// <summary>
|
||||
/// Visits a text node.
|
||||
/// </summary>
|
||||
void VisitText(Text text);
|
||||
|
||||
/// <summary>
|
||||
/// Visits an ordered list node.
|
||||
/// </summary>
|
||||
void VisitOrderedList(OrderedList orderedList);
|
||||
|
||||
/// <summary>
|
||||
/// Visits an emphasis node.
|
||||
/// </summary>
|
||||
void VisitEmphasis(Emphasis emphasis);
|
||||
|
||||
/// <summary>
|
||||
/// Visits a strong node.
|
||||
/// </summary>
|
||||
void VisitStrong(Strong strong);
|
||||
|
||||
/// <summary>
|
||||
/// Visits a code node.
|
||||
/// </summary>
|
||||
void VisitCode(Code code);
|
||||
|
||||
/// <summary>
|
||||
/// Visits a link node.
|
||||
/// </summary>
|
||||
void VisitLink(Link link);
|
||||
|
||||
/// <summary>
|
||||
/// Visits an image node.
|
||||
/// </summary>
|
||||
void VisitImage(Image image);
|
||||
|
||||
/// <summary>
|
||||
/// Visits an HTML inline node.
|
||||
/// </summary>
|
||||
void VisitHtmlInline(HtmlInline html);
|
||||
|
||||
/// <summary>
|
||||
/// Visits a line break node.
|
||||
/// </summary>
|
||||
void VisitLineBreak(LineBreak lineBreak);
|
||||
|
||||
/// <summary>
|
||||
/// Visits a soft line break node.
|
||||
/// </summary>
|
||||
void VisitSoftLineBreak(SoftLineBreak softLineBreak);
|
||||
|
||||
/// <summary>
|
||||
/// Visits a thematic break node.
|
||||
/// </summary>
|
||||
void VisitThematicBreak(ThematicBreak thematicBreak);
|
||||
|
||||
/// <summary>
|
||||
/// Visits an indented code block node.
|
||||
/// </summary>
|
||||
void VisitIndentedCodeBlock(IndentedCodeBlock codeBlock);
|
||||
|
||||
/// <summary>
|
||||
/// Visits a fenced code block node.
|
||||
/// </summary>
|
||||
void VisitFencedCodeBlock(FencedCodeBlock fencedCodeBlock);
|
||||
|
||||
/// <summary>
|
||||
/// Visits an HTML block node.
|
||||
/// </summary>
|
||||
void VisitHtmlBlock(HtmlBlock htmlBlock);
|
||||
|
||||
/// <summary>
|
||||
/// Visits a table node.
|
||||
/// </summary>
|
||||
void VisitTable(Table table);
|
||||
|
||||
/// <summary>
|
||||
/// Visits a table header row node.
|
||||
/// </summary>
|
||||
void VisitTableHeaderRow(TableHeaderRow header);
|
||||
|
||||
/// <summary>
|
||||
/// Visits a table row node.
|
||||
/// </summary>
|
||||
void VisitTableRow(TableRow row);
|
||||
|
||||
/// <summary>
|
||||
/// Visits a table cell node.
|
||||
/// </summary>
|
||||
void VisitTableCell(TableCell cell);
|
||||
}
|
||||
23
Radzen.Blazor/Markdown/Image.cs
Normal file
23
Radzen.Blazor/Markdown/Image.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace Radzen.Blazor.Markdown;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an inline image element: <c></c>
|
||||
/// </summary>
|
||||
public class Image : InlineContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the destination (URL) of the image.
|
||||
/// </summary>
|
||||
public string Destination { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the alternative text of the image.
|
||||
/// </summary>
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Accept(INodeVisitor visitor)
|
||||
{
|
||||
visitor.VisitImage(this);
|
||||
}
|
||||
}
|
||||
69
Radzen.Blazor/Markdown/IndentedCodeBlock.cs
Normal file
69
Radzen.Blazor/Markdown/IndentedCodeBlock.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Radzen.Blazor.Markdown;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a markdown indented code block.
|
||||
/// </summary>
|
||||
public class IndentedCodeBlock : Leaf
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Accept(INodeVisitor visitor)
|
||||
{
|
||||
visitor.VisitIndentedCodeBlock(this);
|
||||
}
|
||||
|
||||
internal override BlockMatch Matches(BlockParser parser)
|
||||
{
|
||||
if (parser.Indent >= BlockParser.CodeIndent)
|
||||
{
|
||||
parser.AdvanceOffset(BlockParser.CodeIndent, true);
|
||||
}
|
||||
else if (parser.Blank)
|
||||
{
|
||||
parser.AdvanceNextNonSpace();
|
||||
}
|
||||
else
|
||||
{
|
||||
return BlockMatch.Skip;
|
||||
}
|
||||
|
||||
return BlockMatch.Match;
|
||||
}
|
||||
|
||||
private static readonly Regex TrailingWhiteSpaceRegex = new(@"^[ \t]*$");
|
||||
|
||||
internal override void Close(BlockParser parser)
|
||||
{
|
||||
base.Close(parser);
|
||||
|
||||
var lines = Value.Split('\n').ToList();;
|
||||
// Note that indented code block cannot be empty, so
|
||||
// lines.length cannot be zero.
|
||||
|
||||
while (TrailingWhiteSpaceRegex.IsMatch(lines[^1]))
|
||||
{
|
||||
lines.RemoveAt(lines.Count - 1);
|
||||
}
|
||||
|
||||
Value = string.Join('\n', lines) + '\n';
|
||||
|
||||
Range.End.Line = Range.Start.Line + lines.Count - 1;
|
||||
Range.End.Column = Range.Start.Column + lines[^1].Length - 1;
|
||||
}
|
||||
|
||||
internal static BlockStart Start(BlockParser parser, Block container)
|
||||
{
|
||||
if (parser.Indented && parser.Tip is not Paragraph && !parser.Blank)
|
||||
{
|
||||
// indented code
|
||||
parser.AdvanceOffset(BlockParser.CodeIndent, true);
|
||||
parser.CloseUnmatchedBlocks();
|
||||
parser.AddChild<IndentedCodeBlock>(parser.Offset);
|
||||
return BlockStart.Leaf;
|
||||
}
|
||||
|
||||
return BlockStart.Skip;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user