Compare commits

...

596 Commits

Author SHA1 Message Date
Vladimir Enchev
273ba0381f Version updated 2026-01-06 10:45:51 +02:00
Vladimir Enchev
63a05d86e8 Nested DataGrid ContextMenu not working properly 2026-01-06 10:41:52 +02:00
Vladimir Enchev
283e115d0a Upload file cannot be reselected after removed 2026-01-06 10:08:40 +02:00
Atanas Korchev
b513ebba8e Allow HEAD requests to routable URLs. 2026-01-05 11:41:22 +02:00
Vladimir Enchev
e324cea7b9 DropDown will select item on ENTER if there is only one item 2025-12-27 16:47:01 +02:00
Vladimir Enchev
c3cda91a2d DataGrid columns visibility not saved in settings when changing PageSize 2025-12-23 10:35:49 +02:00
Vladimir Enchev
bb9f611629 ClearSearchAfterSelection does not reset filtering after reopening DropDown
Fix #2405
2025-12-23 09:57:24 +02:00
Atanas Korchev
6cc8e62b15 Update the bug report issue template to include playground snippet instructions. 2025-12-22 14:46:27 +02:00
Atanas Korchev
5a44b76adb Use unique image name to produce a new deploy every time. 2025-12-22 12:45:07 +02:00
Atanas Korchev
109d6bc617 Update deploy.yml 2025-12-22 12:22:06 +02:00
Vladimir Enchev
7249ff6107 Version updated 2025-12-22 11:38:43 +02:00
Vladimir Enchev
406830fa13 Numeric fails to accept pasted input when Min is specified
Fix #2401
2025-12-22 10:25:17 +02:00
Vladimir Enchev
a6a3278443 Single selection filtered dropdown popup does not close when selecting an item with enter key
Fix #2398
2025-12-22 09:43:17 +02:00
Atanas Korchev
9fd8531529 Using ___ or *** in markdown content causes exception in RadzenMarkdown. Fixes #2402. 2025-12-22 08:29:07 +02:00
Atanas Korchev
c687976796 Update deploy.yml 2025-12-20 06:49:59 +02:00
Atanas Korchev
ae511929f7 Keep only one container version (the latest) 2025-12-20 06:30:41 +02:00
Atanas Korchev
e78e756206 Workaround for https://github.com/dotnet/aspnetcore/issues/64693. 2025-12-19 20:46:55 +02:00
Atanas Korchev
2e9e0dac1a Update deploy.yml 2025-12-19 19:44:36 +02:00
Atanas Korchev
bff97712e2 Update deploy.yml 2025-12-19 19:29:28 +02:00
Atanas Korchev
78bc25cce0 Update deploy.yml 2025-12-19 19:17:34 +02:00
Atanas Korchev
a2e829417d Update deploy.yml 2025-12-19 18:55:44 +02:00
Atanas Korchev
9902413daf Logging. 2025-12-19 18:54:05 +02:00
Atanas Korchev
06a35f4a73 Create deploy.yml 2025-12-19 18:26:49 +02:00
Atanas Korchev
e2a46157b9 Playground (#2400)
* Playground page.

* Persist the changes.

* Add copy button.

* Style Playground

* Update Playground button in CodeViewer

* Add AntiForgery support.

* Update Save alert

* Extract common code.

* Loading tweaks.

---------

Co-authored-by: yordanov <vasil@yordanov.info>
2025-12-19 17:36:32 +02:00
Atanas Korchev
c4913a94c4 Remove duplicate anchors from the chart series demo. 2025-12-19 11:29:47 +02:00
Vladimir Enchev
d6e04c3ae8 DropDownDataGrid ContextMenuDataGrid added 2025-12-18 16:04:02 +02:00
Vladimir Enchev
5b17ef5217 Version updated 2025-12-17 16:19:05 +02:00
Anspitzen
8edeedc32c Rework paste handler and add same function to new drop handler (#2394)
* Rework paste handler and add same function to new drop handler

* Remove eventlistener on destroy

* Try restore formating of Radzen.Balzor.js

* Fix missing delegate flag on create editor from restoring formating

* Handle drop and paste with same c# EventCallback OnPaste/Paste
2025-12-17 11:34:04 +02:00
Atanas Korchev
543dbc9e50 Serve uploaded images as static assets. 2025-12-17 11:18:30 +02:00
tharreck
3710f4297b Add ExpandAll/CollapseAll button in hierarchy datagrid (#2391)
* Add ExpandAll/CollapseAll button in hierarchy datagrid

* update icons of ExpandAll/CollapseAll button

* Update naming and use default values for ExpandAllTitle and CollapseAllTitle

---------

Co-authored-by: Steven Torrelle <steven.torrelle@uzgent.be>
2025-12-17 08:44:30 +02:00
Paul Ruston
91d6ba4da8 Add another condition for overlapping appointments (#2392) 2025-12-16 13:56:18 +02:00
Atanas Korchev
fbfa3c1abe Update README.md 2025-12-16 13:50:54 +02:00
Atanas Korchev
d13d7bef0c RadzenMarkdown allows unsafe protocols in attributes - javascript: etc. 2025-12-16 13:45:54 +02:00
joriverm
510f5d7190 RadzenDropdown: correctly close popup on item selection, ignoring OpenOnFocus (#2389)
Co-authored-by: AI\jvermeyl <joris.vermeylen@uzgent.be>
2025-12-15 16:08:29 +02:00
Vladimir Enchev
595b98d4b7 DropDownDataGrid row not focused on select 2025-12-15 11:01:29 +02:00
Atanas Korchev
c696107aeb Build does not finish. 2025-12-11 18:18:16 +02:00
Atanas Korchev
632722cd7b Llms.txt fixes. 2025-12-11 17:54:58 +02:00
Atanas Korchev
60c264ea49 Remove UseStaticAssets to restore compression. 2025-12-11 17:10:19 +02:00
Vladimir Enchev
10ce92264d Version updated 2025-12-11 10:35:03 +02:00
Vladimir Enchev
2e5c7aa6bc Mask and Numeric Immediate property added
Fix #2384, #2386
2025-12-11 10:15:24 +02:00
yordanov
1e7e2c5b51 Add video to RadzenIcon demos 2025-12-11 10:08:29 +02:00
Atanas Korchev
46790ce3fe Html editor dropdown aria attributes. Closes #2385. (#2387)
* Add ARIA attributes to HtmlEditorDropdown.

* Fix spacing and font size of rz-html-editor-dropdown

---------

Co-authored-by: yordanov <vasil@yordanov.info>
2025-12-10 11:31:33 +02:00
yordanov
7bd891f8f0 Add video to RadzenText demos 2025-12-10 09:50:27 +02:00
Vladimir Enchev
1ae16acbac Fixed possible DropDown NullReferenceException
Fix #2380
2025-12-10 08:20:33 +02:00
Vladimir Enchev
2281fb7f61 DataGrid expand/collapse of items using right/left keys added 2025-12-10 08:12:10 +02:00
Vladimir Enchev
89e8db6c6e Version updated 2025-12-09 10:24:30 +02:00
Vladimir Enchev
11300692e5 DropDownBase search by key enabled when bound without TextProperty 2025-12-09 10:21:38 +02:00
Barry
a31161c6d8 Fix: TreeItem Template click does not toggle Checkbox (#2374)
* add click handler for treeviewitem when Template used with checkboxes

* change approach to expose a toggle option

* remove unused method and add select to toggle checkbox selection

* rename to toggle checked

---------

Co-authored-by: Barry Wright <ace90210ace@msn.com>
2025-12-09 09:02:38 +02:00
xsainteer
edd85f8ec0 added UseDisplayName parameter to DataGridColumn component (#2378) 2025-12-08 08:48:18 +02:00
Vladimir Enchev
0d420604dd code fixed 2025-12-05 12:03:43 +02:00
Vladimir Enchev
777f5666e2 Docker improved to avoid running demos browse problems 2025-12-05 11:59:42 +02:00
Vladimir Enchev
e7bded641f Version updated 2025-12-05 11:16:11 +02:00
Vladimir Enchev
6f0dfbe038 Fixed cannot use ?? unparenthesized within || and && expressions 2025-12-05 11:15:57 +02:00
Vladimir Enchev
0e92eeb50e UseStaticFiles() added to enable llms.txt visibility 2025-12-05 11:06:56 +02:00
Vladimir Enchev
50b6cb879a Version updated 2025-12-05 10:14:58 +02:00
Vladimir Enchev
739ebde6a8 Llmtxt generation added (#2379)
* RadzenBlazorDemos.Tools added

* GenerateLlmsTxt Condition update to Release

* GenerateLlmsTxt updated again

* code fixed

* content generation cleaned

* razor/html tags removed from example descriptions and the code is now inside codeblock

* more fixes
2025-12-05 10:14:15 +02:00
Vladimir Enchev
24ef74f16d Upload remove file throws cannot read property 'find' exception 2025-12-05 10:10:13 +02:00
Vladimir Enchev
9e0a57ca5b RadzenDropDownDataGrid ColumnResized, ColumnReordering and ColumnReordered added 2025-12-04 11:00:07 +02:00
Vladimir Enchev
feaebb6f0f Version updated 2025-12-04 10:52:15 +02:00
yordanov
9a45759414 Add rz-default-scrollbars CSS class to base themes 2025-12-04 10:49:10 +02:00
yordanov
14c5a0447b Remove Black Friday promo banner 2025-12-04 10:37:47 +02:00
yordanov
7793e03d82 Update premium themes 2025-12-03 10:27:13 +02:00
Martin Hans
f422573fcc Add resizable option to side dialog. (#2331)
* Add resizable option to side dialog.

* Fix resize bar CSS class typo and make sideDialogResizeHandleJsModule nullable

* Add tests for resizable side dialog.

* Rename element references.

* Enable nullable refernce types for RadzenDialg.razor.cs

* Resolve requested change

Move code back to RadzenDialog.razor.

* Use sideDialog.classList to gather position rather than data-dir attribute.

* Rework initSideDialogResize function.

- Take position from options.
- Take width and height from sideDialogs clientWidth and clientHeight.
- Take minWidth and minHeight from options.
- Remove superflous pointer capturing.
- Remove superflous 'dragging' class.

* Replace title and aria label string constants by properties.

* Treat resizableMinWidth and resizableMinHeight as present.

* Reformat

* Use options.resizableMinWidth/Height only

* No need to set min-width neither min-height on side dialog.

* Rename ResizableMinWidth => MinWidth, ResizableMinHeight => MinHeight.

* Rename initSideDialogResize => createSideDialogResizer.

* Add dialog resize bar css variables

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: yordanov <vasil@yordanov.info>
2025-12-03 10:22:33 +02:00
tharreck
516109ecd3 Correctly select RadzenTocItem based on scroll direction in RadzenToc (#2370)
* Select toc item based on scroll direction in TOC

* Select correct RadzenTocItem when reloading a page with anchor
cleanup code

* add debounce back into scrollhandler

---------

Co-authored-by: Torrelle Steven <steven.torrelle@uzgent.be>
2025-12-02 15:45:10 +02:00
Mason Voxland
31d32b16a5 Clicking SelectAllText in CheckBoxList will do change (#2377) 2025-12-02 11:13:16 +02:00
yordanov
435c1bd6e2 Update Black Friday promo 2025-12-01 10:56:10 +02:00
Vladimir Enchev
6d5ebf3f58 Version updated 2025-12-01 10:55:13 +02:00
Vladimir Enchev
b97036b447 Support large strings added to QRCode
Fix #2373
2025-12-01 10:51:55 +02:00
Vladimir Enchev
d7ef3cb896 Upload file not removed properly when on file delete from UI 2025-11-27 14:10:49 +02:00
Robyn Choi
2218eefa67 RadzenPivotDataGrid AddPivotAggregate exposed (#2371) 2025-11-27 08:14:10 +02:00
Vladimir Enchev
666d33d767 Version updated 2025-11-26 14:00:45 +02:00
Vladimir Enchev
3f4f83d354 PivotDataGrid filter icon popup exception after adding more fields 2025-11-26 08:35:43 +02:00
Atanas Korchev
3dea547559 Enable CSS minification for the embedded themes. 2025-11-25 18:33:46 +02:00
rklfss
0e9c6acb84 Allow Specifying the interactive SortOrder Sequence on Data Grids (#2366) 2025-11-25 15:46:23 +02:00
Vladimir Enchev
1b6881673e AI assistant messages are sent as ‘user’ instead of ‘assistant’ role. ChatMessage Role property added
Fix #2365
2025-11-25 10:17:15 +02:00
yordanov
f069b33b60 Add videos to UI fundamentals demos 2025-11-25 10:03:54 +02:00
Vladimir Enchev
c0a86e31da Version updated 2025-11-24 10:05:17 +02:00
Vladimir Enchev
7b95778efe PivotDataGrid ColumnsCollection, RowsCollection and AggregatesCollection added 2025-11-24 09:45:02 +02:00
Vladimir Enchev
ef8a102d0a DataGrid OData column Type Guid exception on set filter
Fix #2363
2025-11-24 09:18:55 +02:00
Vladimir Enchev
95448de3ed DataGrid column CloseFilter() does not work when FilterProperty has a value 2025-11-21 10:14:08 +02:00
Vladimir Enchev
0a574762c7 RadzenTocItem inherits RadzenComponentWithChildren 2025-11-18 14:10:07 +02:00
joriverm
59b1440990 RadzenTocItem: take in attributes to pass on to the list item element (#2360)
Co-authored-by: AI\jvermeyl <joris.vermeylen@uzgent.be>
2025-11-18 13:56:36 +02:00
Vladimir Enchev
98d6729d4b Version updated 2025-11-18 09:06:25 +02:00
Vladimir Enchev
135a0bbe5c RadzenPivotDataGrid AddPivotField, AddPivotColumn and AddPivotRow exposed 2025-11-18 09:05:05 +02:00
Vladimir Enchev
c3f579931d RadzenDropDownDataGrid LoadChildData event added 2025-11-18 08:57:30 +02:00
Vladimir Enchev
21c51e81d2 PagedDataBoundComponent current page not synced for top and bottom pagers
Fix #2357
2025-11-18 08:48:03 +02:00
Ali Yousefi
e6538c95ad feat: keyboard key that triggers opening the popup (#2356) 2025-11-17 15:50:59 +02:00
Vladimir Enchev
0e63e87f9b DatePicker will not populate iniitially hour and minutes when bound to TimeOnly 2025-11-14 09:49:22 +02:00
Vladimir Enchev
1e900ec775 Added XML comments to Popup public API 2025-11-14 09:30:44 +02:00
edgett
5ff30874dd Add CloseOnClickOutside parameter to Popup (#2355)
* Add CloseOnClickOutside parameter to Popup

Add a parameter called CloseOnClickOutside Popup, default is true. This maintains the current behavior. If set false, the Popup will remain open with the user click outside of it.

* Fix js function usage

Updated the parameters for the Radzen.openPopup JS function to include closeOnDocumentClick.
2025-11-14 09:27:36 +02:00
Vladimir Enchev
94550006c4 RadzenPivotDataGrid code improved 2025-11-13 15:54:11 +02:00
yordanov
202636ce72 Add BlackFriday offer 2025-11-13 13:12:12 +02:00
Vladimir Enchev
cae8c6f622 version updated 2025-11-13 10:37:42 +02:00
Vladimir Enchev
0e03c4377f PivotDataGrid set filter value should invalidate filter 2025-11-13 10:32:16 +02:00
Vladimir Enchev
7497ea1262 PivotDataGrid dynamic data support added 2025-11-13 10:19:37 +02:00
Atanas Korchev
d68bb34f6f Update getting started to include .NET 10. 2025-11-12 12:38:43 +02:00
Vladimir Enchev
732a6f4942 DataGrid sort ambiguous match found 2025-11-12 09:40:55 +02:00
Atanas Korchev
70fb896ae1 Update CI workflow to use .NET 10 2025-11-12 08:04:17 +02:00
Vladimir Enchev
034eae6722 Version updated 2025-11-11 17:50:22 +02:00
Vladimir Enchev
3472949bf0 sdk updated 2025-11-11 17:46:25 +02:00
Vladimir Enchev
c333b8ca30 Net10 support added (#2353)
* NET10 support added

* DataGrid Paging Disappears on Click in .NET Core 10.0.0 RC

Fix #2286

* various fixes

* KnownIPNetworks used instead KnownNetworks

* build fixed
2025-11-11 17:43:29 +02:00
Vladimir Enchev
17e3fbdabf version updated 2025-11-10 10:27:36 +02:00
Vladimir Enchev
30c5c9dfaf More strict GetDeclaredMethods() for FirstOrDefault(), LastOrDefault(), Cast() and Distinct()
Fix #2343
2025-11-10 09:41:06 +02:00
Vladimir Enchev
585d1ee38a DropDown ReadOnly XML description fixed 2025-11-10 09:37:02 +02:00
Vladimir Enchev
3255afb487 DialogService method CloseSideAsync made virtual
Fix #2349
2025-11-10 09:28:07 +02:00
Vladimir Enchev
9f75648b50 DropDownDataGrid exception with sorting when no columns are defined 2025-11-10 09:18:21 +02:00
yordanov
50406f8984 Update Material Icons font. Resolves #2342 2025-11-07 15:05:05 +02:00
Atanas Korchev
f24c7ebc5f Add demo showing how to use Google Fonts. 2025-11-07 13:34:18 +02:00
Vladimir Enchev
75dcecfab0 Version updated 2025-11-07 08:18:47 +02:00
Atanas Korchev
34c603ce53 Revert "Cleanup code a little (#2336)"
This reverts commit d816d841a8.
2025-11-07 07:01:16 +02:00
Vladimir Enchev
7785a73876 Version updated 2025-11-06 16:26:34 +02:00
Vladimir Enchev
cfa8f731f2 DropDown FooterTemplate added
Close #2344
2025-11-06 16:25:59 +02:00
Ondrej Bach
5ecd05c7b3 Additional parameters passed from RadzenDropDownDataGrid to RadzenDataGrid (#2347)
* Inject ServiceProvider to RadzenDataAnnotationValidator to use it within ValidationContext

* Fix ... add inject attribute

* Add pass-through parameters AllowColumnPicking, AllowColumnReorder and PageSizeOptions to DropDownDataGrid

---------

Co-authored-by: Ondrej Bach <ondrej.bach@external.drivalia.com>
2025-11-06 16:17:08 +02:00
Vladimir Enchev
4ec95c2f1c Various types extracted in separate files 2025-11-05 14:31:34 +02:00
Ondrej Bach
9e4413b02e Inject ServiceProvider to RadzenDataAnnotationValidator to use it wit… (#2339)
* Inject ServiceProvider to RadzenDataAnnotationValidator to use it within ValidationContext

* Fix ... add inject attribute

---------

Co-authored-by: Ondrej Bach <ondrej.bach@external.drivalia.com>
2025-11-04 18:43:12 +02:00
Vladimir Enchev
38d0d689b3 DataGrid CollectionFilterMode improved and added example 2025-11-04 11:38:06 +02:00
Vladimir Enchev
799c5e9e4e DataGrid column CollectionFilterMode property added
Close #2313
2025-11-04 10:27:11 +02:00
joriverm
b98dffda8f Add Immediate to Radzen Password (#2337)
Co-authored-by: AI\jvermeyl <joris.vermeylen@uzgent.be>
2025-11-03 19:08:12 +02:00
joriverm
d816d841a8 Cleanup code a little (#2336)
* TextArea/Box change tests extended

fixup copy paste fail

* Cleanup warnings about FieldIdentifier.FieldName not able to be null

its defined as a non-nullable string, and an empty string is considerd valid

---------

Co-authored-by: AI\jvermeyl <joris.vermeylen@uzgent.be>
2025-11-03 19:06:46 +02:00
Vladimir Enchev
1730e02dc3 Version updated 2025-11-02 10:19:59 +02:00
Vladimir Enchev
53737d2f3c TextArea/TextBox adding custom oninput attribute fixed 2025-11-02 10:14:55 +02:00
MVSAlex
932dc3f9f6 Fix for Dialog autofocus for HTMLEditors wrapped in RadzenFormField with Tabindex correct (#2334)
* Fix for Dialog Autofocus for HTMLEditors inside RadzenFormField

If a Dialog had an HTMLEditor which was wrapped inside of a RadzenFormField it would always get focused no matter where it is breaking the Autofocus of the first Element

* Fix for unintentionally ignored TabIndex

Made a mistake that would completely ignore the TabIndex being set to -1 and therefore still include those elements
2025-10-30 15:54:27 +02:00
Vladimir Enchev
567fdb6a36 ListBoxTests extended 2025-10-30 14:33:24 +02:00
Vladimir Enchev
3e323b929d DropDownDataGridTests extended 2025-10-30 14:26:46 +02:00
Vladimir Enchev
a53fb126ac Version updated 2025-10-30 14:00:46 +02:00
MVSAlex
ec2c17bc21 Fix for Dialog Autofocus for HTMLEditors inside RadzenFormField (#2332)
If a Dialog had an HTMLEditor which was wrapped inside of a RadzenFormField it would always get focused no matter where it is breaking the Autofocus of the first Element
2025-10-30 13:43:29 +02:00
Vladimir Enchev
3894f31c2c RadzenPivotDataGrid tests added 2025-10-30 10:04:48 +02:00
Vladimir Enchev
342b96801a RadzenTextArea/RadzenTestBox Immediate code improved 2025-10-29 16:20:04 +02:00
Vladimir Enchev
36f1dfe2e0 more tests added 2025-10-29 16:07:47 +02:00
Vladimir Enchev
f90951add1 various tests added 2025-10-29 15:24:49 +02:00
joriverm
7d65c45833 Add Support for Immediate changes in TextArea (#2329)
Co-authored-by: AI\jvermeyl <joris.vermeylen@uzgent.be>
2025-10-29 15:12:11 +02:00
joriverm
3629235a00 Cleanup RadzenStack's style compiling to not generate an extra semicolon (#2330)
Co-authored-by: AI\jvermeyl <joris.vermeylen@uzgent.be>
2025-10-29 15:10:40 +02:00
Atanas Korchev
09ff7744dc Load compilation dependencies on demand. 2025-10-28 23:30:57 +02:00
Atanas Korchev
1fa9d59634 Use specific static asset provider for the source code. 2025-10-28 21:53:07 +02:00
Atanas Korchev
ccec9dbc70 Remove UseStaticAssets as it seems to disable compression. 2025-10-28 20:51:59 +02:00
Vladimir Enchev
a49dbad913 Version updated 2025-10-28 16:17:54 +02:00
Vladimir Enchev
4f0497158b Upload cannot drag & drop files after file remove
Fix #2160
2025-10-28 14:19:10 +02:00
Vladimir Enchev
4757d158a8 Label moved to Forms 2025-10-28 10:25:46 +02:00
Vladimir Enchev
4bbd371a42 Label demos added 2025-10-27 15:31:02 +02:00
Vladimir Enchev
10b4b877de RadzenMediaQuery examples added
Fix #1755
2025-10-27 14:56:27 +02:00
Vladimir Enchev
3bd030c397 Splitter double click will toggle expand/collapse
Fix #385
2025-10-27 14:47:07 +02:00
Vladimir Enchev
7c408808c9 more descriptions added 2025-10-27 13:53:33 +02:00
Vladimir Enchev
39dcaa8fc4 demos descriptions improved 2025-10-27 12:27:25 +02:00
Vladimir Enchev
04cdd584a2 demo improved 2025-10-27 11:27:41 +02:00
Atanas Korchev
2693487f81 Move AddRadzenComponents in theme registration as RadzenTheme needs it. 2025-10-27 11:15:15 +02:00
Vladimir Enchev
d73dce8804 Components API remarks moved to summary 2025-10-27 09:22:20 +02:00
Vladimir Enchev
cf333e8c15 Version updated 2025-10-24 17:46:02 +03:00
Vladimir Enchev
5aea28611b DataGrid LoadDataArgs.Filter and LoadDataArgs.Filters represent different filters after remove filtered column by column picker
Fix #1286
2025-10-24 17:44:16 +03:00
Vladimir Enchev
c58ccf0a66 SecurityCode Doesn't work well with Android
Fix #2323
2025-10-24 17:14:51 +03:00
Vladimir Enchev
d5c870eb9d XML API improved 2025-10-24 16:28:11 +03:00
Vladimir Enchev
a41b00e0be various demos descriptions added 2025-10-24 12:32:11 +03:00
Vladimir Enchev
383ee7e577 Cascading DropDowns demo extended with additional DataGrid 2025-10-24 09:50:15 +03:00
Atanas Korchev
b3b1f887ef Enable color lists in the color picker tools of RadzenHtmlEditor. 2025-10-23 21:28:56 +03:00
yordanov
b6089f5d28 Update banners 2025-10-23 18:37:18 +03:00
yordanov
2d7d5cad39 Update subscription info 2025-10-23 15:55:52 +03:00
Vladimir Enchev
cb06833882 ContextMenu on Datagrid does not respect specificity
Fix #2325
2025-10-23 14:38:26 +03:00
Vladimir Enchev
cf3ab86cec Version updated 2025-10-23 14:22:29 +03:00
Vladimir Enchev
efd3d18484 RadzenProfileMenuItem Template property added
Fix #1754
2025-10-23 12:45:27 +03:00
Atanas Korchev
e6baaa6184 (internal) Flatten elements within <span class="rbs-text"> for improved RBS support. 2025-10-23 12:32:57 +03:00
Vladimir Enchev
81c943ecda "Update dialog properties" does not work for side dialogs.
Fix #2091
2025-10-23 12:10:38 +03:00
Vladimir Enchev
587a4f479c Screen reader is not announcing the name for the 'progress bar' control in Windows.
Fix #2184
2025-10-23 11:51:21 +03:00
Vladimir Enchev
5638152121 Wrong Pager position/displayed data on PagedDataBoundComponent after triggering Visible Parameter
Fix #2225
2025-10-23 11:39:57 +03:00
Vladimir Enchev
81fc744270 Focus not set to the RadzenDropDown with options 'AllowFiltering=true' and 'Multiple=true', when pressing Esc or TAB to close dropdown
Fix #2308
2025-10-23 11:32:51 +03:00
Vladimir Enchev
2437b9e028 more descriptions added 2025-10-23 11:18:57 +03:00
Vladimir Enchev
d148cac10e more descriptions added 2025-10-23 11:10:26 +03:00
Vladimir Enchev
af8c9e3cc3 Fixed DatePicker keyboard navigation with multiple selection 2025-10-23 10:50:29 +03:00
Vladimir Enchev
b64c71c307 various descriptions added 2025-10-23 10:39:49 +03:00
Vladimir Enchev
2d60d8fcc1 Version updated 2025-10-22 16:30:35 +03:00
Vladimir Enchev
814221997e DatePicker invalid cast error with Multiple 2025-10-22 16:30:20 +03:00
Vladimir Enchev
5e112b6f59 await removed 2025-10-22 16:03:44 +03:00
Vladimir Enchev
6b253c30b2 Version updated 2025-10-22 15:56:02 +03:00
Vladimir Enchev
b5b0c1efa7 DatePicker async void removed 2025-10-22 15:55:33 +03:00
Vladimir Enchev
5dce6f81af Version updated 2025-10-22 15:26:07 +03:00
yordanov
6ff186d7de Update premium themes 2025-10-22 15:19:55 +03:00
yordanov
801a1cab1a Update links 2025-10-22 12:54:05 +03:00
Vladimir Enchev
737539e3a8 DataGrid demo improved
Fix #2311
2025-10-22 09:17:57 +03:00
Petar Slavov
2992f868ef Added ShowFooter property to GroupDescriptor. Added ability to show group footer rows on every group lavel. Added ability to control group footer visibility from the ShowFooter property of the GroupDescriptor. (#2321) 2025-10-22 09:08:36 +03:00
yordanov
e718ef80dc Update responsive styles 2025-10-21 18:31:07 +03:00
yordanov
220a441422 Move QRCode to Data Visualization category 2025-10-21 17:21:38 +03:00
yordanov
8440eb513a Update font-sizes 2025-10-21 17:20:53 +03:00
yordanov
09004fd7db Update demos homepage and footer 2025-10-21 16:41:35 +03:00
Vladimir Enchev
e9fb2bf677 various descriptions improved 2025-10-21 14:31:42 +03:00
Vladimir Enchev
81261e2413 DatePicker Multiple property added 2025-10-21 11:44:15 +03:00
Vladimir Enchev
2c7b51dc00 DatePicker ShowHour and ShowMinutes properties added 2025-10-20 15:05:37 +03:00
Atanas Korchev
47447badc1 Remove first line from Dockerfile. 2025-10-20 11:47:05 +03:00
Vladimir Enchev
1233a2347d Version updated 2025-10-20 11:18:21 +03:00
Vladimir Enchev
b0cec6f668 RadzenQRCode component added (#2319)
* RadzenQRCode added

* RadzenQRCode render improved

* QRCode improved

* Update qrcode demos

* Reorder color pickers in qrcode demo

---------

Co-authored-by: yordanov <vasil@yordanov.info>
2025-10-20 11:09:25 +03:00
Atanas Korchev
05e25d51ec Remove trailing / from sitemap. 2025-10-19 12:14:21 +03:00
Atanas Korchev
81c27836c2 Proxy the protocol. 2025-10-19 00:06:44 +03:00
Atanas Korchev
24aa1ec1b3 Add sitemap.xml and robots.txt 2025-10-18 23:33:39 +03:00
Atanas Korchev
6c7bc84ff4 Add canonical links. 2025-10-18 21:17:20 +03:00
Bilal Korkmaz
bc1d139cca set filter datetimes according to specified DateTimeKind (#2317)
* set filter datetimes according to specified DateTimeKind

* Updated DateTimeKind implementation to avoid new parameter as per code review

---------

Co-authored-by: Bilal Korkmaz <b58korkmaz@hotmail.com>
2025-10-17 14:53:34 +03:00
Vladimir Enchev
c1afef18f3 ChatUser color not applied 2025-10-17 12:34:44 +03:00
Vladimir Enchev
f46ff6237b demos fixed 2025-10-15 14:38:34 +03:00
Atanas Korchev
fd94e8037d Add NotFound page. 2025-10-15 09:21:36 +03:00
Vladimir Enchev
b95bf75dce Filtering of collection by item property tests added 2025-10-14 14:24:27 +03:00
Vladimir Enchev
b08b3aa3b1 more filtering tests added 2025-10-14 13:26:59 +03:00
Vladimir Enchev
5ed931243e QueryableExtensionTests added 2025-10-14 11:19:32 +03:00
Vladimir Enchev
f5ab48c8af More DataGrid filtering tests added 2025-10-14 11:12:02 +03:00
Vladimir Enchev
75202f4ff8 DataGrid filtering tests added 2025-10-14 10:59:21 +03:00
Vladimir Enchev
3c679674d5 Version updated 2025-10-08 14:59:23 +03:00
yordanov
6f7921691c The total number of UI components is now over 100. 2025-10-08 10:49:35 +03:00
Vladimir Enchev
13ba136c66 DataGrid IsValid should call all EditContexts Validate() 2025-10-08 09:35:21 +03:00
Vladimir Enchev
abd80af816 DatePicker InitialViewDate change should be respected 2025-10-08 09:20:24 +03:00
Vladimir Enchev
aac927e6ea AIChatServiceOptions exposed as individual properties of RadzenAIChat 2025-10-07 10:06:21 +03:00
Atanas Korchev
f7e0d7d698 Theme changing does not work in RenderMode.InteractiveAuto. 2025-10-07 08:14:30 +03:00
Vladimir Enchev
eb633bae3a Version updated 2025-10-06 17:37:39 +03:00
Vladimir Enchev
76a0b5e3e2 PivotDataGrid expression for non string values improved 2025-10-06 17:36:39 +03:00
Vladimir Enchev
0679525905 Version updated 2025-10-06 16:17:44 +03:00
Vladimir Enchev
3396c7bca4 PivotDataGrid aggregates for nested properties fixed 2025-10-06 15:00:33 +03:00
Vladimir Enchev
5031ce3936 PivotDataGrid invalid expression with non string row values 2025-10-06 14:06:11 +03:00
Vladimir Enchev
8b774b47e2 New examples updated 2025-10-06 13:06:29 +03:00
Vladimir Enchev
f0e7a398fa version updated 2025-10-06 12:02:32 +03:00
Vladimir Enchev
72c3f23c67 Fields picking properties moved to RadzenPivotDataGrid 2025-10-06 12:02:08 +03:00
yordanov
33c01b611e Update demos 2025-10-06 10:58:35 +03:00
yordanov
7cc2e6953b Update changelog 2025-10-06 10:33:17 +03:00
yordanov
1284d4ab80 Add PivotDataGrid to demos home and update component icon 2025-10-06 10:13:28 +03:00
Vladimir Enchev
cd83f01132 Version updated 2025-10-06 10:06:18 +03:00
Vasil Yordanov
3b4480fa8b Sidebar Position and FullHeight options added (#2310)
* Add SidebarPosition and FullHeight options to RadzenSidebar and adjust layout styles accordingly

* Update RadzenLayout demos
2025-10-06 10:02:00 +03:00
Vladimir Enchev
a9f8fbbff5 code fixed 2025-10-06 09:10:18 +03:00
Vladimir Enchev
e37b055e24 DatePicker updates year/months before setting YearRange which can cause a crash
Fix #2250
2025-10-06 09:08:12 +03:00
Vladimir Enchev
8eeda0e647 RadzenAIChat MessageTemplate and EmptyTemplate added 2025-10-03 14:14:46 +03:00
Vladimir Enchev
c28b47e231 RadzenPivotDataGrid added (#2307) 2025-10-03 11:07:39 +03:00
Atanas Korchev
ab989107a5 Serve demo source from wwwroot instead of github. 2025-10-02 15:50:21 +03:00
Vladimir Enchev
268fe93dd9 code fixed 2025-10-02 15:34:57 +03:00
Vladimir Enchev
e154fb05ea RadzenChat component added (#2306)
* RadzenChat component added

* Update chat message layout

* Update participant avatars in chat header

---------

Co-authored-by: yordanov <vasil@yordanov.info>
2025-10-02 14:45:45 +03:00
Vasil Yordanov
fb14fe587b Add new RadzenFab and RadzenFabMenu components (#2305)
* Add Fab and FabMenu components

---------

Co-authored-by: Atanas Korchev <akorchev@gmail.com>
2025-10-01 15:23:52 +03:00
Vladimir Enchev
e72a17ce32 Update DropDownBase.cs
Closes #2302, Closes #2298, Closes #2304
2025-10-01 11:46:30 +03:00
Vladimir Enchev
6c11409e7e Version updated 2025-09-29 15:14:16 +03:00
Vladimir Enchev
f43db71b01 SecurityCode invalid index error on paste
Fix #2300
2025-09-29 14:40:47 +03:00
Atanas Korchev
dc3611474f Markdown anchors that contain only fragments (e.g. [link](#fragment)) navigate to the root. 2025-09-28 13:50:35 +03:00
Atanas Korchev
5c5a306f7f Optimize the drag-and-drop performance of Scheduler appointments. 2025-09-27 13:42:38 +03:00
Vladimir Enchev
024d9fb5ac Version updated 2025-09-26 07:36:01 +03:00
Vladimir Enchev
6fb480042f Revert "RadzenDataGrid throws an exception when a filter value is loaded for a sub property (#2290)"
This reverts commit 224032d0c2.
2025-09-26 07:35:34 +03:00
Vladimir Enchev
acc1f8624e Version updated 2025-09-24 16:49:33 +03:00
Atanas Korchev
0b97853a57 Enabling WCAG colors via ThemeService no longer works. Fixes #2296. 2025-09-24 16:47:14 +03:00
Vladimir Enchev
8408b55e08 Version updated 2025-09-24 16:08:17 +03:00
Atanas Korchev
e49d9f7eab Prevent FOUC in RadzenTheme (#2294)
* ThemeService.SetTheme uses JavaScript to change the theme CSS files to prevent FOUC during Blazor initial hydration.

* Update the getting started instructions.

* Initialize the theme in ThemeService from RadzenTheme.
2025-09-23 16:47:24 +03:00
yordanov
6202bd78f3 Update banners 2025-09-23 15:21:27 +03:00
Vladimir Enchev
a2a5916e09 Version updated 2025-09-23 10:05:52 +03:00
Atanas Korchev
3f52e42661 Add Reload method to RadzenSankeyDiagram. 2025-09-22 11:37:34 +03:00
IncaTechnologies
224032d0c2 RadzenDataGrid throws an exception when a filter value is loaded for a sub property (#2290)
* Add tests

* Fix load settings from json

---------

Co-authored-by: noname <noname@nomail>
2025-09-22 08:37:52 +03:00
joriverm
4dece2e58a DataBoundFormComponent: Generate FieldIdentifier if ValueChanged is given (#2291)
Co-authored-by: AI\jvermeyl <joris.vermeylen@uzgent.be>
2025-09-22 08:37:40 +03:00
Atanas Korchev
7eb85d4454 Scheduler does not use the Culture when determining the start of the week. Fixes #2293. 2025-09-20 14:19:18 +03:00
Atanas Korchev
bd750e6f97 RadzenScheduler no longer uses a private Rect class. 2025-09-20 13:31:58 +03:00
Vladimir Enchev
ee0d4d29e1 Source links fixed
Fix #2289
2025-09-16 14:09:14 +03:00
Vladimir Enchev
10d2ea7c0a Version updated 2025-09-16 13:29:53 +03:00
Vladimir Enchev
0ab76d53ec DataGrid GroupFooterTemplate wrong rendering when ShowGroupExpandColumn=false 2025-09-15 15:31:07 +03:00
Vladimir Enchev
3dea0a5f67 Chat model updated 2025-09-15 11:28:13 +03:00
Vladimir Enchev
41c2e04cfa Chat messages cleared in some cases 2025-09-15 10:29:10 +03:00
DinuRoth
d8183feb07 only use the designated fragment when pasting html into a RadzenHtmlEditor (#2285)
* only use the designated fragment when pasting html into a RadzenHtmlEditor

* fragment-extraction inlined

---------

Co-authored-by: Martin Roth <martin.roth@swisscom.com>
2025-09-15 10:10:45 +03:00
Vladimir Enchev
e49fe2bca8 Version updated 2025-09-11 10:34:12 +03:00
Vladimir Enchev
67664a2816 DataGrid CheckBoxList with OData demo updated
Fix https://github.com/radzenhq/radzen-blazor/issues/2252
2025-09-09 09:37:16 +03:00
Gincsai Gábor
eefbe00aec Fix grid settings handling for sub properties column (#2274)
* Fix loading settings when multiple colum has same Property (e.g using sub-property filtering)
Fix loaded settings type handling to handle IEnumerable column with non ValueKind.Array.
Added default value for Unique ID is not set.

Add example.

* Set shouldUpdateState too.

* Make QueryableExtension.Select public

* Change filter mode back to original on demo page.
2025-09-09 09:15:34 +03:00
Atanas Korchev
be67d5f120 JavaScript error is thrown when the user selects an image in RadzenHtmlEditor and presses the insert link tool. 2025-09-04 13:57:50 +03:00
Vladimir Enchev
fa1fe694cf version updated 2025-09-02 13:53:40 +03:00
yordanov
9f681c0c09 Add CSS variables for DataGrid alternating row backgrounds in frozen columns 2025-09-02 12:43:31 +03:00
Vladimir Enchev
18dde2849e DataGrid will look first for column UniqueID before Property when loading settings 2025-08-29 18:25:20 +03:00
Vladimir Enchev
5678774de0 PickList MoveFilteredItemsOnlyOnMoveAll added 2025-08-29 18:19:51 +03:00
Vladimir Enchev
f5370e1384 RadzenColorPicker SelectedColorChanged null exception fixed 2025-08-29 08:46:45 +03:00
Vladimir Enchev
934d7cb104 Version updated 2025-08-28 09:58:56 +03:00
Vladimir Enchev
33b201147d DynamicExtensions Where() should use ExpressionSerializer Value for Format for FilterParameters 2025-08-28 09:58:27 +03:00
yordanov
abe2006e15 RadzenDialog source link should point to DialogService as well. Resolves #2270 2025-08-27 19:25:00 +03:00
Vladimir Enchev
81b5cb386d DataGridLoadColumnFilterDataEventArgs Property setter made public 2025-08-27 08:53:44 +03:00
Vladimir Enchev
c713dd6c87 Version updated 2025-08-26 09:18:01 +03:00
Vladimir Enchev
eda5c490b7 DropDownDataGrid AllowFilteringByAllStringColumns will raise exception with IDictionary binding 2025-08-26 09:17:22 +03:00
yordanov
b6bc9e1257 Fix link to Skeleton animations example source 2025-08-25 17:50:31 +03:00
yordanov
123ffd8886 Update premium themes 2025-08-25 12:51:51 +03:00
Vladimir Enchev
9a2c09c6e8 version updated 2025-08-25 11:38:24 +03:00
Vladimir Enchev
5b5fdde9a2 Skeleton component added (#2268)
* RadzenSkeleton component added

* Update Skeleton component properties, themes, and demos

* Skeleton tests fixed

---------

Co-authored-by: yordanov <vasil@yordanov.info>
2025-08-25 11:31:34 +03:00
Vladimir Enchev
0cc50d70da Fixed RadzenPanelMenuItem will not show tooltip 2025-08-25 11:17:13 +03:00
mtboeke
c79ecddee4 Expose Responsive MaxWidth in RadzenSidebar (#2260)
* Expose ResponsiveMaxWidth

* Change property to a string to be more versatile

---------

Co-authored-by: Matt Boeke <gangolfus@hotmail.com>
2025-08-25 11:07:11 +03:00
Nopke
e929edbd53 ColorPicker: highlight selected color item (#2267) 2025-08-25 11:00:39 +03:00
Atanas Korchev
8de7a1ec70 Wrong value axis display when all values in a series are the same and negative. 2025-08-22 19:17:20 +03:00
Vladimir Enchev
0b6ae4d225 Version updated 2025-08-21 07:48:09 +03:00
yordanov
c69bb8bb2b Add MCP logo 2025-08-20 22:26:08 +03:00
yordanov
e8dda6f946 Update list of components 2025-08-20 18:21:40 +03:00
yordanov
3daebfc0c1 Update AI page layout 2025-08-20 18:09:00 +03:00
Atanas Korchev
d5fcbb97c3 Add AI page. 2025-08-20 16:47:11 +03:00
yordanov
97555e7849 FrozenColumn should have alternating rows. Resolves #2265 2025-08-20 13:43:14 +03:00
Vladimir Enchev
7eb799d2a6 DataGridLoadColumnFilterDataEventArgs Property added 2025-08-20 11:17:08 +03:00
Vladimir Enchev
d06a85a3b6 Fixed DataGrid CheckBoxFilter form id when both Property and FilterProperty are defined 2025-08-20 11:05:32 +03:00
Vladimir Enchev
f616eadccf DataGrid column AlwaysShowAllCheckBoxListData property added 2025-08-20 10:57:57 +03:00
Vladimir Enchev
b69d6c193b Fixed DataGrid endless loop in load settings with columns width 2025-08-19 16:02:57 +03:00
Josh
d12908778e RadzenDropDown/Grid: Modify bound collection in-place (#2261)
* Refactor out code that assigns and notifies for Multiple when items are Selected

* Add alternative handling to support keeping the bound collection instance unchanged. This also enables support for custom collections without default ctors, and HashSets

* Ensure ClearAll maintains collection instance, instead of setting to null

* Ensure SelectdAll uses the same new behavior

* resolve the actual type of the bound collection, in determining if to apply the ReferenceGenericCollectionAssignment

# Conflicts:
#	Radzen.Blazor/DropDownBase.cs

* fixup: Ensure non-multiple/non-collection sources are accomodated

# Conflicts:
#	Radzen.Blazor/DropDownBase.cs

* use the more tactical approach for altering the collection to match selectedItems.  This preserves ordering and will raise expected INotifyCollectionChanged events if implemented

* cleanup: naming convention
2025-08-19 07:44:31 +03:00
Vladimir Enchev
95c42f3266 Version updated 2025-08-18 10:58:09 +03:00
Vladimir Enchev
597e6de682 AIChatService HttpClient resolved runtime 2025-08-18 10:38:57 +03:00
Vladimir Enchev
406141a4f1 Injected HttpClient removed from RadzenAIChat since it's not needed 2025-08-18 10:32:28 +03:00
Ali Yousefi
0db7fe63b0 feat: make ClearFilters virtual (#2263) 2025-08-18 10:09:03 +03:00
Vladimir Enchev
e5a43ef5b6 DatePicker will not display value initially when set to midnight 2025-08-15 10:03:00 +03:00
Petar Slavov
4d9a9c9ac2 Add GetProperty that supports retrieval of interface properties. (#2255)
* Introduces a new static method `GetProperty` in the `Radzen.PropertyAccess` class to retrieve properties by name, including support for interfaces. Updates `DropDownBase.cs` to utilize this new method, enabling property retrieval for collections of interfaces in multiselect DropDowns.

* Add unit tests for property resolution in interfaces

Implemented unit tests in `PropertyAccessTests.cs` to verify
the resolution of `Description`, `Name`, and `Id` properties
from the interfaces `ISimpleInterface`, `ISimpleNestedInterface`,
and `ISimpleBaseInterface`. Defined the interfaces to support
the tests for the `GetProperty` method functionality.
2025-08-15 09:28:52 +03:00
Vladimir Enchev
3cc1f6b994 Version updated 2025-08-14 08:57:50 +03:00
Vladimir Enchev
4322361269 AIChatService added to AddRadzenComponents 2025-08-14 08:57:12 +03:00
Vladimir Enchev
44d3651aa8 chat tests fixed 2025-08-14 07:55:09 +03:00
Atanas Korchev
03423f32c8 Remove the version. 2025-08-13 20:38:23 +03:00
Atanas Korchev
bf8c950a8a Build the API reference without mono. 2025-08-13 19:12:24 +03:00
Vladimir Enchev
f16ae734cc More DocFX removed 2025-08-13 18:18:01 +03:00
Vladimir Enchev
91196dca39 DOCFX removed 2025-08-13 18:17:17 +03:00
Vladimir Enchev
ea36ff5011 update 2025-08-13 18:13:50 +03:00
Vladimir Enchev
dad418140b mono:latest updated 2025-08-13 18:11:43 +03:00
Vladimir Enchev
d5f8877768 docker updated 2025-08-13 17:54:50 +03:00
Vladimir Enchev
95f2dfa8ac docker updated 2025-08-13 17:53:10 +03:00
Vladimir Enchev
d60b14c686 various warnings fixed 2025-08-13 16:23:26 +03:00
Vladimir Enchev
fabcd7b8f4 Version updated 2025-08-13 16:16:36 +03:00
yordanov
045880c234 Update premium themes 2025-08-13 16:09:38 +03:00
Vladimir Enchev
d2071102c5 RadzenChat component added (#2254) 2025-08-13 15:37:01 +03:00
Ehab Hussein
864bc8c7f9 Add Sankey Diagram Component to Radzen Blazor (#2220)
* Add SankeyDiagram Component

---------

Co-authored-by: Atanas Korchev <akorchev@gmail.com>
Co-authored-by: yordanov <vasil@yordanov.info>
2025-08-13 15:20:06 +03:00
Nenad Kovačević
b63b861b8a Fixed a typo in IconPage.razor (#2253) 2025-08-13 13:35:58 +03:00
Vladimir Enchev
23effbb3ec Upload MaxFileCount error handling fixed 2025-08-13 09:55:42 +03:00
Vladimir Enchev
c0a87c834c all tests fixed 2025-08-13 09:29:41 +03:00
Vladimir Enchev
4d02213260 test fixed 2025-08-13 09:19:41 +03:00
Vladimir Enchev
46118964ea Added DataTable support for DataGrid 2025-08-13 09:14:59 +03:00
Vladimir Enchev
ca52c588fd Revert "feature: DataGrid Columns allow Sort and Filter when bound to Array<o… (#2233)"
This reverts commit 4503361877.
2025-08-13 08:52:41 +03:00
Vladimir Enchev
b9905e10de Revert "XML comment fixed"
This reverts commit 62cf565a09.
2025-08-13 08:52:30 +03:00
Vladimir Enchev
7ea08af177 Revert "DataGrid DataTable improved"
This reverts commit c90e3bedb1.
2025-08-13 08:52:25 +03:00
Vladimir Enchev
fa59aff6d5 RadzenDropDown ReadOnly can be bypassed with keyboard select as you type 2025-08-13 07:59:49 +03:00
Vladimir Enchev
c90e3bedb1 DataGrid DataTable improved 2025-08-12 10:39:37 +03:00
Vladimir Enchev
62cf565a09 XML comment fixed 2025-08-12 10:26:49 +03:00
EdJohnstonG9
4503361877 feature: DataGrid Columns allow Sort and Filter when bound to Array<o… (#2233)
* feature: DataGrid Columns allow Sort and Filter when bound to Array<object>[] i.e. DataRow.ItemArray[n]

* Expose Test page and fix AmbiguousMatch in test (ItemArray not Item)

* Working filters before test

* In Sorting Tests

* Filter by ChecBoxList to be done

* Better solution to filter types

* Solution for Enum in DataTable

* Tidy up and fix of Enum Null
Working Enums

* Re-enabled all data sources in tests

* Prevent display of simple filter input when fitler expects enumerable
Show correct demo code, only when merged with Master

* Fix to handle Filter lists with nullable enums
2025-08-12 10:24:53 +03:00
Vladimir Enchev
f2bc7ef3ed Version updated 2025-08-11 09:16:59 +03:00
Vladimir Enchev
f9505e42c4 Fixed DataGrid CheckBoxList Filter error when filtering ICollection property 2025-08-10 09:04:28 +03:00
GAUSS-LVS Dev
d340df758e Fixed timing problem in RadzenTreeItem OnInitializedAsync (#2246)
Fixed an issue that the order of CurrentItems could be wrong when items are expanded simultaneously.

Co-authored-by: mde <m.deppe@gauss-lvs.de>
2025-08-09 10:30:53 +03:00
Vladimir Enchev
741ecf0955 Fixed DataGrid DoesNotContains filter throws No coercion operator is defined between types ‘System.String’ and ‘System.Boolean’ 2025-08-09 10:28:33 +03:00
yordanov
90ab2e468e Preloader should have properly set opacity when the grid is loading. Resolves #2205 2025-08-08 15:01:01 +03:00
yordanov
360b9d7730 Wrong link in image URL demo. Resolves #2245 2025-08-07 18:26:12 +03:00
Vladimir Enchev
bb698dbe9d version updated 2025-08-04 08:31:12 +03:00
Vladimir Enchev
5e120a7328 DataGrid filter clear not visible, for simple FilterMode, when FilterProperty is different from Property
Fix #2237
2025-08-04 08:23:39 +03:00
Vladimir Enchev
206daba741 DataGrid simple filter should be rendered only for relevant columns 2025-08-04 08:08:33 +03:00
Mohammad Mohammadi
79e86e8b76 Improve numeric input handling for all locales (including Persian and Arabic) (#2240)
* Improve numeric input support in JS: accept all Unicode digits using `\p{Nd}`

Replaced hardcoded digit ranges with Unicode-based digit detection using `\p{Nd}‍` to ensure support for Persian, Arabic, and other locale-specific numerals.

* fix: support all Unicode digit inputs in RadzenNumeric

Replaced digit handling logic with Unicode-aware checks using `char.IsDigit` to support numeric input from any language, including Persian and Arabic.

Previously, non-ASCII digits were blocked, which caused issues for users entering Persian or Arabic numerals. This fix makes the component input-agnostic to digit origin.
2025-08-01 08:08:51 +03:00
Atanas Korchev
398c17c75a Add support for immediate changes to RadzenTextBox (enabled via Immediate=true). Closes #909. 2025-07-25 10:03:22 +03:00
Vladimir Enchev
49adc95170 Version updated 2025-07-24 10:10:15 +03:00
Dmitry Solovev
2b71bd1178 Fix IconColor in ProfileMenuItem (#2229)
Co-authored-by: Дмитрий Соловьев <dsolo@codestetic.com>
2025-07-23 10:16:49 +02:00
Frank Folsche
c53938bc0a #1761 offset when filtermenu row is active (#2223) 2025-07-16 15:26:44 +03:00
joriverm
718827d757 feature: implement disabled on panel menu items (#2222)
Co-authored-by: AI\jvermeyl <joris.vermeylen@uzgent.be>
2025-07-16 15:22:20 +03:00
Vladimir Enchev
b4c87e7e10 Role "tab" removed from a element in RadzenTabsItem 2025-07-16 11:54:52 +03:00
Vladimir Enchev
20dd0e9b2d TabsItem role set to "tab" 2025-07-16 11:53:21 +03:00
Vladimir Enchev
397e2d207e Version updated 2025-07-10 15:36:45 +03:00
Vladimir Enchev
29f7b6cb74 DropDownBase should allow filter on SPACE while still be able to select/deselect items 2025-07-10 15:33:33 +03:00
joriverm
a004b55e8c feature: make fieldidentifier settable as parameter (#2216)
Co-authored-by: AI\jvermeyl <joris.vermeylen@uzgent.be>
2025-07-10 14:48:29 +03:00
Vladimir Enchev
221d8f0697 DataGrid should set column CustomFilterExpression to null on Reset() 2025-07-10 14:36:03 +03:00
Vladimir Enchev
e7a243ab7f DataGrid LoadDataArgs.Filter should use it for instance name in case of column CustomFilterExpression 2025-07-10 14:27:45 +03:00
Vladimir Enchev
ce1ffe2070 DataGrid FilterMode.CheckBoxList crashes when selecting all items in a non string column
Fix #2213
2025-07-10 12:05:22 +03:00
Gincsai Gábor
32e2419b67 Suppott filtering List<string> like sub-property. (#2210)
* Suppott filtering List<string> like sub-property. In this case no nested property so use the collectionItemTypeParameter.

* Extend the FilterValueDemo with Awards that uses List<string> property with multiple filter.
The demo works with in memory data, but not with on backend side with Oracle database.
2025-07-09 17:59:15 +03:00
Piotr Mazur
5f7079e526 Fix type assignment in RadzenDataGrid to get the correct type (#2209)
* Add missing Type field when creating FilterDescriptors in RadzenDataGrid InvokeLoadData method

* Fix type assignment in RadzenDataGrid to get the correct type

---------

Co-authored-by: Piotr Mazur <piotr.mazur@lstsoft.com.pl>
2025-07-08 13:34:39 +03:00
Vladimir Enchev
4ffeae11e2 code fixed 2025-07-08 13:32:39 +03:00
Vladimir Enchev
7621e31d38 RadzenAutoComplete will select value on NumpadEnter 2025-07-08 11:32:29 +03:00
Gincsai Gábor
c00c751127 System.Type cannot be serialized out of the box in C# (possible security issue) (#2208)
With ignoring the Type property FilterDescriptor can be used for custom server side filtering.
2025-07-08 08:11:23 +03:00
Vladimir Enchev
a09f550b52 DataGrid double IsEmpty/IsNotEmpty filters fixed 2025-07-07 16:33:52 +03:00
Vladimir Enchev
0aeb1cfa97 Version updated 2025-07-07 14:55:49 +03:00
Piotr Mazur
46da7e45b9 Add missing Type field when creating FilterDescriptors in RadzenDataGrid InvokeLoadData method (#2207)
Co-authored-by: Piotr Mazur <piotr.mazur@lstsoft.com.pl>
2025-07-07 14:54:59 +03:00
Vladimir Enchev
83bf448bdd Add in RadzenTree with async Expand calls error 2025-07-07 10:30:14 +03:00
Atanas Korchev
5ab15fb812 Add crossorigin attribute when preloading the icon font. 2025-07-03 15:32:38 +03:00
mvoxland
0dc70f3e42 DropDownDataGrid null check to prevent error that occurs when using AllowFilteringByAllStringColumns="true" (#2201)
To replicate this bug: just add AllowFilteringByAllStringColumns="true" to the Multiple Selection example for DropDownDataGrid
2025-07-01 13:47:14 +03:00
Vladimir Enchev
5bc86a75bc DataGrid column ShowCellDataAsTooltip property added 2025-07-01 13:46:26 +03:00
Vladimir Enchev
51d3df9282 Version updated 2025-06-30 11:08:57 +03:00
Vladimir Enchev
31925ea099 DropDownBase select item as you type fixed 2025-06-28 09:22:30 +03:00
yordanov
a56e6540e8 Update premium themes 2025-06-27 11:32:34 +03:00
Cosmatevs
03bccb4cb4 DropDown, DropDownDataGrid: fix font family in filter inputs 2025-06-27 11:30:38 +03:00
yordanov
bba9b7b6ec Update Material Icons font. Resolves #2187 2025-06-26 11:47:37 +03:00
yordanov
5b8ea6e4b0 Add font-display rules 2025-06-26 11:14:41 +03:00
yordanov
91676d802c Improve ripple styles for rz-state-disabled 2025-06-25 18:54:33 +03:00
yordanov
395d343f69 Grid cell focus state should be visually distinct from selected state in Standard and Material3 themes 2025-06-25 16:36:38 +03:00
Atanas Korchev
201993ff08 RadzenTheme preloads the icon font. 2025-06-24 13:43:30 +03:00
Vladimir Enchev
f9499904f3 DataGrid simple filter menu will not show string filter operators for string columns 2025-06-24 13:26:07 +03:00
Vladimir Enchev
0a152474a2 Version updated 2025-06-23 14:39:59 +03:00
Lukas Grützmacher
8dc522e054 Function key should not trigger DropDown Change event (#2195) 2025-06-23 07:40:29 +03:00
Vladimir Enchev
eb8209d575 Missing XML comments added 2025-06-20 11:05:29 +03:00
Simon Lissack
96b3ac7435 Support deserializing unsigned integers when loading filter column values (#2190) 2025-06-20 11:00:25 +03:00
Vladimir Enchev
42d7113896 DropDownDataGrid GridLines property added 2025-06-20 07:45:17 +03:00
Vladimir Enchev
7f19786053 ContextMenu should prevent the browser context menu 2025-06-20 07:37:31 +03:00
Lee
52b121a9f4 Updating HtmlEditor methods and properties to public (#2189)
* Updating methods and properties to public, to make it easier to customise and create custom HtmlEditor buttons in your own projects.

* Putting SourceChanged back to private
2025-06-19 16:37:11 +03:00
Atanas Korchev
d191a8223e Events with zero duration are sometimes invisible in month view. 2025-06-19 15:09:52 +03:00
Vincent Schmandt
b371b43a86 fix: avoid accessing parameter view in SetParametersAsync after an async call to prevent a race condition (#2186)
* fix: avoid accessing parameter view in SetParametersAsync after an async call to prevent a race condition

Fixes: https://forum.radzen.com/t/the-parameterview-instance-can-no-longer-be-read-because-it-has-expired/18912/7
See also: https://stackoverflow.com/questions/66717054/the-parameterview-instance-can-no-longer-be-read-because-it-has-expired-paramet

* fix SetParametersAsync reordering
2025-06-18 14:35:59 +03:00
Vladimir Enchev
c243f384a5 DataGrid column filter menu not unique in case of columns with same property names 2025-06-18 14:06:16 +03:00
Vladimir Enchev
e10f8d759c Version updated 2025-06-18 11:15:21 +03:00
Vladimir Enchev
977e5b8cb6 DataGrid LoadDataArgs Filters should respect the column FilterProperty 2025-06-17 14:59:57 +03:00
yordanov
b68f95a7e7 Update main navigation 2025-06-17 10:08:37 +03:00
yordanov
e30b01de23 Update premium themes 2025-06-17 09:39:27 +03:00
Vladimir Enchev
68f87db33e DataGrid CheckBoxList filter on wrong column in some cases 2025-06-16 14:26:21 +03:00
Romaniv Volodymyr
f2bfa462a0 Fix #2167 (#2185)
Set cursor to initial for disabled RadioButtonList elements
2025-06-16 10:07:11 +03:00
yordanov
adffcf5229 RadzenLayout components should not have 100vw on mobile screens. Resolves #1956 2025-06-13 10:43:40 +03:00
yordanov
a4f37cbcfa Fix active state styles in demos navigation menu 2025-06-12 17:21:44 +03:00
Vladimir Enchev
18d8328a9c Version updated 2025-06-12 11:04:17 +03:00
Vladimir Enchev
a466578ebf ExpressionSerializer should respect the DateTime.Kind
Fix #2183
2025-06-12 11:03:51 +03:00
yordanov
c92f8833e2 Fix toc confing in home layout 2025-06-12 10:44:17 +03:00
Vladimir Enchev
2f46c58028 Enabled search on non string properties for DataGrid CheckBoxList filter
Fix #2182
2025-06-12 10:35:47 +03:00
joriverm
a1c24e346c fixes: Let all DataBoundFormComponent's expose value as the actual type that it is (#2179)
Co-authored-by: AI\jvermeyl <joris.vermeylen@uzgent.be>
2025-06-11 15:39:37 +03:00
yordanov
fb7fb62a0e Update links in main nav 2025-06-11 15:28:39 +03:00
Vladimir Enchev
7378ee1a32 Login ShowLoginButton not respected in some cases 2025-06-11 15:25:15 +03:00
yordanov
0bee5b4365 Update demos main navigation and overview layout 2025-06-11 15:23:20 +03:00
yordanov
47bfd6f539 RadzenMenu active state should be visually distinguishable 2025-06-11 15:23:20 +03:00
Erik van der Boom
1b92cb7f86 Reset select on filter (#2180)
* Add reset index on filter option

* remove unwanted spaces

* correct order
2025-06-11 09:34:31 +03:00
Vladimir Enchev
2931d47d05 AllowFilteringByWordCount default value set to 10 2025-06-09 11:12:43 +03:00
Vladimir Enchev
7668a54648 DropDownDataGrid AllowFilteringByWordCount property added 2025-06-06 10:08:03 +03:00
Vladimir Enchev
aa0aaf7c10 Version updated 2025-06-05 10:04:29 +03:00
Vladimir Enchev
4b80da96ab Shift key should not trigger DropDown Change event
Fix #2165
2025-06-05 10:03:20 +03:00
Vladimir Enchev
396fa19d7b ListBox registers two keys on focus
Fix #2172
2025-06-04 07:57:02 +03:00
Vladimir Enchev
4cd235a08e DataGrid filter menu IsEmpty/IsNotEmpty options added for string columns 2025-06-04 07:46:53 +03:00
Vladimir Enchev
edd21956c5 DateTimeWithRoundtripKind test added 2025-05-30 09:58:59 +03:00
mvoxland-MoorheadSchools
f842156253 Datetime parsing fix, stop converting to local Kind (#2171) 2025-05-30 09:44:56 +03:00
Paul Ruston
ce0ce0f229 Supply Appointment data to SchedulerSlotRenderEventArgs - #2050 (#2154)
* Supply Appointment data to SchedulerSlotRenderEventArgs

* Get appointments on demand

* Revert YearView SlotRender code to remove breaking change. Update demo
2025-05-27 10:24:37 +03:00
Vladimir Enchev
ae2e9219c3 Version updated 2025-05-27 09:14:27 +03:00
Vladimir Enchev
146b374ca8 DataGrid CheckBoxList filter not populated properly in case of self-referencing hierarchy 2025-05-27 07:54:53 +03:00
Vladimir Enchev
9264676407 Non generic IDictionary support added for expressions
Close #2169
2025-05-27 07:17:05 +03:00
Victor Ureta
eb74460fcf add icon in Title Dialog (#2170)
* add icon in Title Dialog

* fix IconColor in DialogService.cs

* fix variables
2025-05-26 18:48:47 +03:00
Vladimir Enchev
16ed582052 DataGrid column with CustomFilterExpression will not filter innitially 2025-05-23 09:26:15 +03:00
Vladimir Enchev
a7ddb81ad1 ToFilterString() should serialize only date of DateTime with zero time 2025-05-23 09:02:38 +03:00
Atanas Korchev
d09f3870b1 RadzenCompareValidator validates when the Visible property is set to true. Closes #2148. 2025-05-22 11:58:20 +03:00
Atanas Korchev
9a8e249964 RadzenScheduler throws error on window resize when placed in a hidden element. 2025-05-22 11:47:56 +03:00
Vladimir Enchev
c63d9cf0b9 DataGrid CheckBoxList filter search will work properly when binding to dynamic data 2025-05-22 09:46:06 +03:00
Vladimir Enchev
f6648f80ec Numeric null. reference exception fixed 2025-05-22 09:23:19 +03:00
Vladimir Enchev
250bff7ef3 ToCSV() export method will handle comma 2025-05-22 09:23:18 +03:00
Nopke
fa0b925aa9 TimeSpanPicker: fix missing panel fields if only Min or only Max is set (#2166) 2025-05-21 08:39:43 +03:00
Atanas Korchev
28fe2949f2 Add CloseSideAsync method to DialogService which waits for the close animation to complete. Closes #2161. 2025-05-20 11:49:50 +03:00
Vladimir Enchev
d7f46216e9 ToFilterString() and ToODataFilterString() extension methods added to IEnumerable<CompositeFilterDescriptor> 2025-05-20 11:46:24 +03:00
Vladimir Enchev
3f0d82a427 Version updated 2025-05-20 08:56:30 +03:00
yordanov
d2811c9fba Fix typo. 2025-05-16 22:20:20 +03:00
yordanov
028e26ef59 Add Accessibility Conformance Report 2025-05-16 10:19:42 +03:00
yordanov
4086332857 RadzenAccordion items should be displayed vertically in RadzenStep 2025-05-15 15:32:43 +03:00
yordanov
1eebfccf14 Remove Dialog's scale animation if it has RadzenChart inside 2025-05-15 15:03:32 +03:00
Vladimir Enchev
f1127f917d DataGrid CheckBoxList filter list not populated in some cases 2025-05-15 13:59:47 +03:00
yordanov
6cc8700bb9 Update UI blocks description 2025-05-15 10:44:16 +03:00
yordanov
3cabcd5cf9 Update info on how to use UI Blocks 2025-05-15 10:25:56 +03:00
yordanov
c5aaeb67b5 Update badges on homepage 2025-05-15 10:25:56 +03:00
Vladimir Enchev
47fcd54d1c version updated 2025-05-15 07:28:28 +03:00
Vladimir Enchev
5dd5cd8ae1 DataGrid grouping exception with wrong type discovery
Fix #2153
2025-05-15 07:28:10 +03:00
Vladimir Enchev
8ab18160a5 Demo updated to handle query that cannot be translated to SQL 2025-05-14 10:52:26 +03:00
Vladimir Enchev
b70b697f3f DataGrid column FilterProperty should be respected if specified 2025-05-14 09:18:30 +03:00
Vladimir Enchev
48eb35e20d Version updated 2025-05-13 20:57:58 +03:00
Atanas Korchev
4fd899133f Cannot parse certain expressions that include string and number comparisons. 2025-05-13 20:52:44 +03:00
Vladimir Enchev
3b134cf8b5 code fixed 2025-05-13 09:28:55 +03:00
Atanas Korchev
86bc538601 Some zero-length events appear on top of each other in month view. 2025-05-13 08:42:11 +03:00
Atanas Korchev
fb00fbc850 Add anchor for the day, week and month view demo. 2025-05-13 08:42:11 +03:00
Vladimir Enchev
309f9fcfa1 DataGrid will populate Query.Filter and LoadDataArgs.Filter only if accessed 2025-05-12 13:46:29 +03:00
Vladimir Enchev
253aa63980 Version updated 2025-05-12 13:03:28 +03:00
Vladimir Enchev
93dea13f20 Added support for indexer expressions with sub properties 2025-05-12 12:36:02 +03:00
Atanas Korchev
3d3d1718b7 Add editable source code to dashboard demo. 2025-05-12 11:00:25 +03:00
Cosmatevs
90c2a5d51f fix recognizing standard dark theme as dark by components 2025-05-12 10:34:44 +03:00
Vladimir Enchev
8846a229d8 Added support for Dictionary<string, string> for expressions 2025-05-12 09:31:12 +03:00
Pedro Constantino
acf0591adc [FEAT] - Adding the possibility to return the source in the upload event args when managing file uploads via RadzenUpload. (#2151) 2025-05-12 07:31:42 +03:00
yordanov
95bee47826 PanelMenu item expand arrow should not be displayed when child items are absent. Resolves #2147 2025-05-09 19:44:18 +03:00
Atanas Korchev
b701976356 Expanded panel menu items do not always collapse when Multiple is set to false. 2025-05-09 14:53:54 +03:00
Vladimir Enchev
2b61941e34 Version updated 2025-05-08 17:24:08 +03:00
yordanov
1526ffa3af Update premium themes 2025-05-08 17:19:02 +03:00
Cosmatevs
bb4227e537 fix the bottom border color of a hovered/focused input field in Standard themes 2025-05-08 17:08:01 +03:00
Vladimir Enchev
778691405d version updated 2025-05-08 15:04:17 +03:00
Atanas Korchev
cb7d3b91c6 Add Template property to RadzenTocItem. 2025-05-08 15:01:53 +03:00
Atanas Korchev
fd8cf16336 NullReferenceException thrown by RadzenLayout when AddRadzenComponents is not used. 2025-05-08 14:54:33 +03:00
yordanov
2403cf222f Add TOC to demos homepage 2025-05-08 14:39:31 +03:00
yordanov
20152dd8a6 Fix duration progress position in Notification. Resolves #2104 2025-05-08 11:47:42 +03:00
Vladimir Enchev
b459207b7a Links inside popup should close the popup only on left mouse button click 2025-05-08 07:48:44 +03:00
Atanas Korchev
8b009509c8 Add accordion breaking change to changelog. 2025-05-07 20:13:29 +03:00
Atanas Korchev
c456891a11 Convert to the left operand type if the right type is object during null coalescing. 2025-05-07 15:25:34 +03:00
yordanov
6b044d8086 Update demos' badges 2025-05-07 14:53:07 +03:00
Atanas Korchev
3162de84ed Update the busy dialog demo. 2025-05-07 14:32:20 +03:00
Atanas Korchev
641768240a Update the changelog. 2025-05-07 14:07:30 +03:00
Vladimir Enchev
8b7ade4b9c Version updated 2025-05-07 13:14:37 +03:00
Vladimir Enchev
c7db9394c8 V7 (#2144)
* Remove Microsoft.CodeAnalysis. Add own C# expression parser.

* Popup dialog animations (#2118)

* Experiment with popup and dialog animations.

* Move animations to a separate _animations.scss

* Remove dialog closing animation.

* Support side dialog positions. Prefix keyframes.

* Use --rz-transition for animation function and duration

* Reset site.css.

* PanelMenu animations.

* Display none is toggled a bit late.

* RadzenPanel animations.

* More animations.

* Use transitions for panel menu.

* Remove old code.

* Accordion uses transitions.

* Panel uses transitions.

* Extract expand and collapse implementation in a separate component.

* Set initial expand state earlier to prevent two renders.

* Add open animation to notifications.

* Handle onanimationend before toggling the animation classes.

* Add menu animation.

* Experiment with tree animation.

* Add animations to fieldset.

---------

Co-authored-by: yordanov <vasil@yordanov.info>

* ExpressionSerializer and tests added (#2119)

* Fix failing tests.

* various components RequiresUnreferencedCode attribute added (#2120)

* RequiresUnreferencedCode added to ExpressionSerializer

* Update premium themes

* RequiresUnreferencedCode added to ExpressionParser

* FormComponentWithAutoComplete RequiresUnreferencedCode removed

* Revert "FormComponentWithAutoComplete RequiresUnreferencedCode removed"

This reverts commit ec900a4df8.

* Revert "RequiresUnreferencedCode added to ExpressionParser"

This reverts commit f93b3b159b.

* Revert "RequiresUnreferencedCode added to ExpressionSerializer"

This reverts commit 06fecec9a6.

* Revert "various components RequiresUnreferencedCode attribute added (#2120)"

This reverts commit 2ed1a6cac1.

* Remove RadzenHtml.

* ExpressionSerializer FormatValue updated to use InvariantCulture

* Catch potential JS interop exceptions that could occur during disposing.

* Revert "Remove RadzenHtml."

This reverts commit 319085bf49.

* SelectBar made single tab stop

* RadioButtonList and CheckBoxList made single tab stop

* SelectBar accessibility improved

* RadioButtonList accessibility improved

* CheckBoxList accessibility improved

* Update radio button focus styles

* Update checkbox list focus styles

* Update Checkbox Radio and SelectBar focus styles

* SelectBar, CheckBoxList and RadioButtonList focus state improved

* Check for Multiple added

* Use non-rendering event handlers for transitionend.

* Rename css class rz-selectbutton to rz-selectbar and improve focus states

* Fix selectbar focus outline offset

* Update premium themes

* Selectbar item focus styles should not be visible if the item is disabled.

* CheckBoxList and RadioButtonList item focus should be visible only on keyboard input

* SelectBar, CheckBoxList and RadioButtonList focus logic improved

* Update animations

* RadzenText margin-block should be 0 if it is in RadzenStack. Resolves #2134

* RadzenText margin-block should be 0 if it is first level child in RadzenStack

* CheckBoxList focused fixed

* Add toggle state classes to panel menu icon.

* Update accordion styles to reflect expander changes

* Add animation styles to expand arrow in Menu and ProfileMenu

* Use a instead of NavLink as it seems to cause performance issues.

* Set @bind-Expanded.

* Revert "Set @bind-Expanded."

This reverts commit 994107367bdf09043950f8bbe701eb9edefec676.

* Revert "Use a instead of NavLink as it seems to cause performance issues."

This reverts commit 05d5bef8f421bbeb5828ba1e9c5af6793ea3d32a.

* Reduce rendering of panel menu items.

* Add panel menu component.

* Use ChildContent to render the toggle icon of the panel menu item.

* Sync panel menu item selection in the item itself.

* Rename ExpandedInternal to expanded.

* Move filtering to the panel menu component.

* Remove the transitionend handler to avoid a second rendering pass.

* Build the assets for the net9.0 framework.

* Do not trigger render when Click is used.

* Panel menu keyboard navigation renders only when needed.

* Focus reworked to use AsNonRenderingEventHandler

* Focus the first item.

* Update Panel demo

* Use a more robust algorithm for month view event rendering that handles overlapping of events across a week.

* Use RadzenStack in RadioButtonList

* Add parsing support for `&` and `|`.

* Add parsing support for `^`, `>>` and `<<`.

* Simplify expression parsing tests.

* Use RadzenStack in RadioButtonList and CheckBoxList

* Change defaults for AlignItems and JustifyContent in RadioButtonList and CheckBoxList

* Update RadioButtonList and CheckBoxList demos

* Add --rz-input-border-block-end css variables to improve Fluent theme styles

* Removed AsNonRenderingEventHandler from RadioButtonList and HtmlEditor focus and blur

* Removed AsNonRenderingEventHandler from CheckBoxList

* Simplify RadzenTable rendering.

* Optimize memory usage of the ClassList utility.

* Refactor RadzenButton to use ClassList.

* RadzenSelectBar and RadzenSplitButton use ClassList.

* Refactor RadzenBadge and RadzenAlert to use ClassList.

* Refactor RadzenCard and RadzenFormField to use ClassList.

* Refactor RadzenCardGroup and progress components to use ClassList.

* Refactor RadzenMenu to use ClassList.

* Use ClassList in RadzenBody, RadzenLayout and editor rendering components.

* RadzenDialog uses ClassList.

* RadzenDataGrid uses ClassList.

* RadzenPager uses ClassList.

* RadzenColumn uses ClassList.

* Fix RadzenSplitButtonItem focused state.

---------

Co-authored-by: Atanas Korchev <akorchev@gmail.com>
Co-authored-by: Atanas Korchev <454726+akorchev@users.noreply.github.com>
Co-authored-by: yordanov <vasil@yordanov.info>
Co-authored-by: Quentin H <67709967+quintushr@users.noreply.github.com>
2025-05-07 13:11:30 +03:00
yordanov
513e63329b Fix RadzenSplitButtonItem focused state. Resolves #2137 2025-05-05 16:36:54 +03:00
amir7800h
cfd104385d Update RadzenDatePicker.razor.cs (#2140)
When setting the project culture (e.g., to fa-IR), the year was still being displayed using the Gregorian calendar, which caused incorrect date representation in the UI.
This fix ensures that the DateTimeFormat.Calendar is properly respected, and now the year is displayed correctly based on the assigned calendar (e.g., PersianCalendar).

Before
Culture set to fa-IR

Year displayed as Gregorian (e.g., 2025 instead of 1404)

After
Year display matches the Calendar set in DateTimeFormat

Fully localized and culture-respecting date output
2025-05-05 08:09:59 +03:00
Atanas Korchev
2d9641eecf Toc preserves query string parameters. 2025-05-03 15:18:09 +03:00
Vladimir Enchev
65a78125b2 DropDownBase select item as you type fixed 2025-04-30 11:45:09 +03:00
Vladimir Enchev
28572ba4d3 version updated 2025-04-29 18:06:24 +03:00
Vladimir Enchev
28a603ca1e Dynamic Where() with parameters fixed 2025-04-29 18:06:11 +03:00
Frank
b9fa303f7f NotifyValidationStateChanged() / StateHasChanged() is only be executed if the IsValid field has actually changed. (#2127) 2025-04-28 11:14:11 +03:00
Vladimir Enchev
756dde90ab DataGrid will allow combining advanced and simple filtering for different columns
Close #2128
2025-04-25 11:40:59 +03:00
Vladimir Enchev
c2a396167e DataGrid advanced filter clear fixed 2025-04-24 16:34:23 +03:00
Vladimir Enchev
c1fd207723 Version updated 2025-04-24 13:05:03 +03:00
Vladimir Enchev
8f1fc0a164 Revert "QueryableExtension OrderBy() should not use ExpressionParser"
This reverts commit a389dc702b.
2025-04-24 13:04:31 +03:00
Evan Dixon
538ec3c744 Make OnChipRemove overridable (#2122)
Co-authored-by: Dixon, Evan <edixon@mimeo.com>
2025-04-24 08:43:47 +03:00
Victor Ureta
6fed13bf12 Add TextTemplate parameter to RadzenFormField Blazor component (#2123)
* Update RadzenFormField.razor

* Update RadzenFormField.razor.cs
2025-04-24 08:25:02 +03:00
Vladimir Enchev
104cc7c900 Version updated 2025-04-23 10:00:49 +03:00
Reinhard
8fa699a92e Fixing 1st column with sub-row expander visible not taking column.IsInEditMode callback into account for rendering edit template. (#2097) 2025-04-23 09:06:24 +03:00
Quasmo
1ab7059830 Added DisplayToolTipFor Method to RadzenChart.razor.cs. (#2115)
* Added DisplayToolTipFor Method to RadzenChart.razor.cs. Made Series property public.

* Chnaged Series public accessor back to internal. Added GetSeries Method that returns an IReadOnlyList of the Series. Added XML documentation. Made changes to DisplayTooltipFor Method. Removed null checks and try/catch block.

* removed unnessecary usings.

---------

Co-authored-by: John Tolliver <jtolliver@wbrsllc.com>
2025-04-23 07:44:50 +03:00
Greg Horvath
377b2613db Fixes bug #2113. (#2114) 2025-04-23 07:18:00 +03:00
Atanas Korchev
bc7b0a9bdb Take axis label rotation into account when measuring the category axis size. 2025-04-22 10:36:09 +03:00
Krystian Szatan
8665a351db Update RadzenSplitButton (#2103)
Added ButtonType parameter to allow EditForm submit
2025-04-18 11:29:44 +03:00
Vladimir Enchev
dd02bf8b8d Notification demo reworked with toc 2025-04-16 11:31:03 +03:00
Vladimir Enchev
487c423eef Version updated 2025-04-15 18:20:24 +03:00
Atanas Korchev
87bcfa729c RadzenMarkdown renders HTML attributes incorrectly. 2025-04-15 18:18:50 +03:00
yordanov
0332e8c671 Fix anchors on Accordion demo page 2025-04-15 15:56:37 +03:00
Atanas Korchev
7264354ce6 Soft line breaks are not rendered and strip whitespace. Fixes #2095. 2025-04-15 13:14:05 +03:00
Vladimir Enchev
bde3315994 Version updated again 2025-04-15 12:59:24 +03:00
Vladimir Enchev
7ecd08b0d8 Version raised 2025-04-15 12:58:01 +03:00
Vladimir Enchev
93feb382ca Version updated 2025-04-15 12:56:35 +03:00
yordanov
186d9b3798 Update toc demos 2025-04-15 12:27:23 +03:00
yordanov
1f3e44819d Remove selected rz-toc-item font-weight and fix width of rz-toc-link 2025-04-15 12:06:33 +03:00
Atanas Korchev
4b942f2f45 Add toc to the upload demo. 2025-04-15 11:46:29 +03:00
Vasil Yordanov
7214cd7179 Add RadzenToc component (#2094)
RadzenToc component is a table of contents based on the titles in a page allowing users to quickly navigate the page.

---------

Co-authored-by: Atanas Korchev <akorchev@gmail.com>
2025-04-15 11:14:38 +03:00
Vladimir Enchev
90c11b5c04 Version updated 2025-04-14 11:39:59 +03:00
Atanas Korchev
4007339d26 Sanitize link and image destination. 2025-04-14 11:37:18 +03:00
yordanov
8bc43443a7 Fix contrast with new styles for active filter in DataGrid's simple filter modes. Resolves #2090 2025-04-14 10:26:52 +03:00
Vladimir Enchev
a389dc702b QueryableExtension OrderBy() should not use ExpressionParser 2025-04-14 09:33:02 +03:00
Nopke
1b39fa37e0 TimeSpanPicker: fix not using last changed input value, disable autocomplete (#2089)
* TimeSpanPicker:
* fix occasionally not using last changed input value after clicking the confirmation button,
* disable autocomplete in unit fields

* TimeSpanPicker:
* store last field input and try to parse it only if needed
2025-04-14 09:03:41 +03:00
Atanas Korchev
bfa18f72fa Sanitize HTML content in RadzenMarkdown. Expose a property to disable html content (AllowHtml) and also provide means to provide a custom list of allowed HTML tags and attributes. 2025-04-13 15:58:20 +03:00
Vladimir Enchev
82c2ec0c43 Version updated 2025-04-11 10:00:14 +03:00
Pedro Constantino
3810d088b5 [FEAT] - Add Fill property to RadzenSeriesDataLabels for customizing text color, default behavior is preserved when Fill is not specified. (#2086)
Co-authored-by: Pedro Constantino <pedro.constantino@enerwatt.com.br>
2025-04-11 09:38:43 +03:00
yordanov
7b34b096fe Update premium themes 2025-04-11 08:47:20 +03:00
leon42
831b0bd2d1 Added a ShowInput property to RadzenDatePicker to allow only the button to be shown. See the demo for an example. (#2085)
Added demos for ShowInput and ShowDateBox options.

Co-authored-by: Noel <noel.s@ivoterguide.com>
2025-04-11 08:23:30 +03:00
Vladimir Enchev
5875057282 Fixed Tabs component re-rendered on every focus
Close #2087
2025-04-11 07:56:27 +03:00
Vladimir Enchev
b78df8df2a Fix Ctrl / Alt navigate in DropDownList
Fix #2084
2025-04-10 14:21:47 +03:00
Vladimir Enchev
7fa3d08e61 DataGrid expand/collapse column headers accessibility error fixed 2025-04-10 11:00:00 +03:00
Vladimir Enchev
549303a34c code improved 2025-04-10 09:50:19 +03:00
Vladimir Enchev
9d2cbae115 DropDownBase should not try to handle keypress when not bound 2025-04-09 18:57:55 +03:00
Vladimir Enchev
d1a76922c5 Version updated 2025-04-09 12:23:00 +03:00
yordanov
648889d2d2 Accordion header icon styles should not propagate accross other icons. Resolves #2081 2025-04-09 10:26:51 +03:00
Vladimir Enchev
984e566fe2 RadzenLink cannot be clicked in Popup
Fix #2083
2025-04-09 10:12:30 +03:00
Atanas Korchev
145296ee10 RadzenTheme no longer requires PersistentComponentState but uses it if available. 2025-04-09 10:03:21 +03:00
yordanov
91b91ca96f Add RealEstate app template 2025-04-09 09:46:11 +03:00
Atanas Korchev
5ea1e9d6d5 PagingSummaryFormat does not apply to the bottom pager in RadzenDataGrid. 2025-04-09 09:23:41 +03:00
Vladimir Enchev
78f83fe103 Version updated 2025-04-08 17:32:55 +03:00
Atanas Korchev
1b0ee6a757 Add specific upload examples. 2025-04-08 13:36:52 +03:00
Atanas Korchev
c185853405 Update the changelog to include 6.x changes. 2025-04-08 10:05:24 +03:00
Atanas Korchev
f50b8bceb6 Js minify (#2080)
* Minify the JavaScript.

* Update the terser package.
2025-04-08 09:32:06 +03:00
Atanas Korchev
559a10603a Update the innerHTML of the editor when source changes. 2025-04-08 07:58:48 +03:00
Atanas Korchev
defe38daaa Update the HTML content of RadzenHtmlEditor after running ValueChanged. 2025-04-07 16:54:46 +03:00
Vladimir Enchev
6acdaf0603 Version updated 2025-04-07 16:06:51 +03:00
Vladimir Enchev
e9991fc995 DialogService Alert() and Confirm() overloads with RenderFragment added 2025-04-07 14:55:22 +03:00
Vladimir Enchev
7ad14174d7 code fixed 2025-04-07 10:54:12 +03:00
Vladimir Enchev
a638387dd4 DataGrid FilterOperator changing to Contains on clear even when restricted
Fix #2074
2025-04-07 10:28:59 +03:00
Jeffrey van der Stad
3818d0e607 PR: Add PagingSummaryTemplate to RadzenDataGrid (Fixes [#2077](https://github.com/radzenhq/radzen-blazor/issues/2077)) (#2079)
* fix: added PagingSummaryTemplate as replacement for PagingSummaryFormat

* In `DataGridPagerApi.razor`, added a `PagingSummaryTemplate` within the `RadzenDataGrid` component to provide a visual summary of the current page and total records displayed.
2025-04-05 15:51:16 +03:00
Jeffrey van der Stad
3ce4f8da3a Updated unit tests in DialogServiceTests.cs to assert properties OkButtonText and CancelButtonText in ConfirmOptions and AlertOptions, with default values set to "Ok" and "Cancel". Modified DialogService.cs to initialize these properties if not specified. (#2063) 2025-03-30 10:48:45 +03:00
Vladimir Enchev
9d272a1b19 version updated 2025-03-29 16:48:58 +02:00
Atanas Korchev
3b9224b4da Parse images inside markdown links. Closes #2062. 2025-03-29 16:15:02 +02:00
Atanas Korchev
b254152746 Remove markup string (#2066)
* Remove MarkupString usage from the demos.

* Remove MarkupString from RadzenPanelMenu.

* Remove MarkupString from RadzenAccordion.

* Remove MarkupString from RadzenIcon

* Remove MarkupString from RadzenSplitButton

* Remove MarkupString from RadzenButton

* Add spaces in the keyboard navigation grid.

* Remove MarkupString from RadzenDialog

* Use literal strings for the icons in the Index page.

* Remove MarkupString from RadzenSelectbar

* Remove MarkupString from RadzenFieldset

* Remove MarkupString from everything else.
2025-03-29 12:30:41 +02:00
yordanov
a2d796476e Update premium themes 2025-03-28 15:25:46 +02:00
nielsNocore
898e744767 Fix small dot in rz-ripple on state :active (#2061) 2025-03-28 15:22:25 +02:00
Vladimir Enchev
9ef9c5b3de Numeric MaxLength property added 2025-03-28 14:25:11 +02:00
Vladimir Enchev
b734eeb252 Version updated 2025-03-28 07:39:29 +02:00
Vladimir Enchev
92d69d9053 DialogService Alert and Confirm buttons text missing when no options are specified
Fix #2060
2025-03-28 07:39:12 +02:00
Vladimir Enchev
4879c49476 Version updated 2025-03-26 17:10:50 +02:00
Vladimir Enchev
e81ea15bb0 DropDown select item with SPACE added
Closes #2011, #2054
2025-03-26 09:18:55 +02:00
Vladimir Enchev
647f174b53 Menu should be closed on location changed not toggled 2025-03-25 13:14:39 +02:00
Atanas Korchev
d79e6a7606 Escaped characters in Markdown tables display as a number. Fixes #2053. 2025-03-24 11:39:32 +02:00
Jeffrey van der Stad
3fd3916ef9 Enhance dialog options with property change notifications to support automatic UI updates (#2051)
* Enhance dialog options with property change notifications

* Update dialog properties and functionality

- Changed dialog text in `DialogPage.razor` to reflect new functionality.
- Updated button text in `DialogWithCascadingValueCaller.razor` and modified dialog title.
- Enhanced `DialogWithCascadingValueImplementation.razor` with new buttons for setting titles and toggling close options, and added methods for updating the dialog title and counter.

* Add unit tests and improve DialogService options handling

* Allow internal methods visibility for unit tests

Added an `ItemGroup` in the project file to include an
`InternalsVisibleTo` attribute for the `Radzen.Blazor.Tests`
assembly, enabling unit tests to access internal methods.
2025-03-24 08:10:57 +02:00
yordanov
b9b73d44c2 Fix titles and descriptions of UI Blocks demo pages 2025-03-21 15:01:28 +02:00
Vladimir Enchev
afa7c2030c demos update 2025-03-21 08:02:17 +02:00
Vladimir Enchev
f668a7a629 Version updated 2025-03-21 07:04:37 +02:00
Vladimir Enchev
958fb43ac2 Should not use Coalesce in OrderBy() 2025-03-21 07:04:17 +02:00
Jeffrey van der Stad
9ef8f592ba Add cascading value support to dialog components (#2046)
- Updated `DialogContainer.razor` to wrap the `Dialog` in a `CascadingValue`, enabling access to cascading parameters.
- Enhanced `DialogPage.razor` with explanatory text and a link to documentation regarding cascading values.
- Introduced `DialogWithCascadingValueCaller.razor` to provide a button for opening a dialog that uses cascading values.
- Created `DialogWithCascadingValueImplementation.razor` to implement dynamic dialog functionality with a counter that updates the title.
2025-03-21 07:01:14 +02:00
yordanov
e6feae6c68 Update premium themes 2025-03-20 17:00:43 +02:00
yordanov
9dc5810eaa Update TimeSpanPicker styles 2025-03-20 17:00:43 +02:00
Vladimir Enchev
c960d6d96a RadzenText demo description fixed 2025-03-20 14:42:44 +02:00
Nopke
45d95d7b45 New component: TimeSpanPicker (#2037)
* TimeSpanPicker: initial

* TimeSpanPicker: adjust material themes, handle long labels

* TimeSpanPicker: fix value set, move invoking Change events for consistency with other components, update demos

* TimeSpanPicker: fix preventKeyPress, adjust styles

* TimeSpanPicker: adjust demos and add new

* PopupOrInline: handle Inline parameter change

* TimeSpanPicker: change icon and improve handling Disabled/ReadMe parameters in panel

* TimeSpanPicker: update demos

* TimeSpanPicker: add to the example panel

* DatePicker: add a tag and info about time picker

* TimeSpanPicker: minor code cleanup, adjust styles, open popup on click if input not allowed

* * TimeSpanPicker: minor style and code adjustments
* DatePicker: add readonly class only if not disabled

* TimeSpanPicker: add to the index page

* TimeSpanPicker: adjust style code, use proper minus sign

* TimeSpanPicker:
* read input value without using JS
* fix Value and ConfirmedValue inconsistency

* TimeSpanPicker: always refresh the displayed value on input change

* TimeSpanPicker: add some tests (not finished yet)

* Numeric: fix culture-dependent test fail

* TimeSpanPicker: reorganize parameters

* TimeSpanPicker tests: cover input parameters, add regions

* TimeSpanPicker: tweak regions of input and panel parameters

* TimeSpanPicker tests: reorganize regions, use more specific selectors

* TimeSpanPicker: improve value change and popup toggle prevention

* TimeSpanPicker:
* use culture while parsing
* use consistent culture in tests

* FormField: add TimeSpanPicker in the demo

* TimeSpanPicker: more tests

* revert unrelated and unintended changed

* TimeSpanPicker test: move theory data to properties, add step tests

* TimeSpanPicker test: minor code cleanup, add remaining label tests

* TimeSpanPicker test: use dictionary of unit element selectors

* TimeSpanPicker test: create separate methods instead passing field expressions as theory, add more regions

* TimeSpanPicker: rename properties to match the convention and update their summaries

* TimeSpanPicker: remove UnconfirmedValueChanged param
TimeSpanPicker test: add event-related tests for the panel, simplify some tests related to timespan units

* TimeSpanPicker: pass culture to numeric fields, add panel field rendering tests

* TimeSpanPicker: unify unit fields under one RenderFragment function

* TimeSpanPicker: fix missing microseconds picker, fix classes of input fields

* TimeSpanPicker:
* prevent from changing UnconfirmedValue externally by changing Value,
* prevent from raising ValueChanged and Change events if the value haven't actually change

* revert unintended changes

* TimeSpanPicker, DatePicker: fix icon positions in FormFields having filled and flat variants

* TimeSpanPicker: make AllowClear false by default, like in DatePicker

* revert unintended change in demos project

* * remove PopupOrInline component
* revert changes in Popup component
* TimeSpanPicker: reimplement inline/popup handling
2025-03-20 10:37:43 +02:00
Vladimir Enchev
3d1c32a5b4 Version updated 2025-03-20 08:57:23 +02:00
Atanas Korchev
6a67580c33 Replace AutoLinkHeadings with AutoLinkHeadingDepth. 2025-03-20 08:49:36 +02:00
Vladimir Enchev
dbafa0a473 Should not use Coalesce in OrderBy() for non sub properties 2025-03-20 07:43:12 +02:00
Atanas Korchev
d348e73b55 Append html block elements as content during markdown rendering. 2025-03-19 19:06:43 +02:00
Atanas Korchev
3f3c1bd6e3 Add support for self-closed and void HTML elements in RadzenMarkdown. 2025-03-19 18:12:40 +02:00
Vladimir Enchev
1a3f907c62 DataGrid filtering by DateOnly and TimeOnly settings save/load fixed 2025-03-19 16:37:48 +02:00
Atanas Korchev
4f8027c6f7 Support URL fragments when matching the current URL. 2025-03-19 14:59:28 +02:00
Atanas Korchev
86cd2a976b Improve support for html tags inside RadzenMarkdown. 2025-03-19 11:51:41 +02:00
Vladimir Enchev
52a935bf09 RadzenSplitterPane class attribute not rendered 2025-03-19 11:01:58 +02:00
Atanas Korchev
ea7c3c4a5b RadzenMarkdown fails to parse image after strong node. 2025-03-19 09:15:12 +02:00
Atanas Korchev
9639d98dc3 Allow auto linking of markdown headers. 2025-03-19 08:09:04 +02:00
Vladimir Enchev
3bfc16b6a8 Version updated 2025-03-18 17:54:25 +02:00
Atanas Korchev
1e54619e81 Markdown table alignment support. 2025-03-18 17:46:31 +02:00
yordanov
fc4ac98246 Update demo pages 2025-03-18 17:40:36 +02:00
yordanov
534e3ee98f Card text styles should only affect text components that are placed directly inside RadzenCard 2025-03-18 17:40:10 +02:00
yordanov
9b8b8f020d Add App Templates 2025-03-18 16:49:27 +02:00
Atanas Korchev
aea7fb8069 Markdown (#2039)
* Add markdown parser and tests.

* Add demo.

* Add to home page.

* Add demo for nested blazor components.

* Use Radzen components for markdown rendering.
2025-03-18 16:29:32 +02:00
Vladimir Enchev
b933633cc0 code fixed 2025-03-18 15:15:38 +02:00
Vladimir Enchev
a822eb521d Version updated 2025-03-18 15:15:24 +02:00
Vladimir Enchev
b6bb19538f Fixed Popup IAsyncDisposable implementation caused errors 2025-03-18 15:14:59 +02:00
Vladimir Enchev
c0a1f86801 Version updated 2025-03-17 17:12:16 +02:00
yordanov
439d79c677 Fix Carousel scroll on touch devices 2025-03-17 16:51:38 +02:00
Vladimir Enchev
b6ee0f118e Menu should be closed on location change 2025-03-17 15:11:16 +02:00
Vladimir Enchev
ed037ca6d7 Popup IAsyncDisposable implemented 2025-03-17 13:40:35 +02:00
Vladimir Enchev
58f6239cb2 Version updated 2025-03-17 09:03:43 +02:00
Vladimir Enchev
5e7bfcb591 Accordion item Visible will not show the item if hidden 2025-03-17 07:47:09 +02:00
protoface
03cd99c43c Use RadzenDataGrid.EnumFilterTranslationFunc where applicable (#2036) 2025-03-17 07:35:48 +02:00
Vladimir Enchev
dc1742aca6 SideDialog ContentCssClass not applied 2025-03-17 07:33:53 +02:00
Atanas Korchev
a23142cc3f Scheduler navigation buttons submit containing form. Fixes #2035. 2025-03-15 08:37:27 +02:00
Vladimir Enchev
de47319b00 Version updated 2025-03-14 15:22:40 +02:00
Vladimir Enchev
c9a2549018 DatePicker ReadOnly should not allow selecting a day with keyboard
Fix #2028
2025-03-13 14:54:53 +02:00
Maks
966253f7b6 Enhanced ReloadSettings and LoadSettingsInternal methods with additional parameters and improved documentation (#2027)
- Added `forceReload` parameter to `ReloadSettings` method to allow forced reloading of settings.
- Added `forceUpdate` parameter to `LoadSettingsInternal` method to allow forced updates of settings.
- Updated XML comments for both methods to clarify their purpose, parameters, and behavior.

Co-authored-by: Sacred <Sacred>
2025-03-12 13:28:09 +02:00
Vladimir Enchev
b0ce7fb3cf DropDownDataGrid PopupStyle added 2025-03-11 15:59:03 +02:00
Vladimir Enchev
f1f85a6563 version updated 2025-03-11 13:59:56 +02:00
Vladimir Enchev
c10ee5e0a8 Avoid executing possible IQueryable code on DataGrid RowSelect 2025-03-11 13:59:39 +02:00
Joel Mandell
7e86d3128c (docs): Clarify that ShowHeader needs to be set to true if using NavigationTemplate. (#2026) 2025-03-11 13:29:30 +02:00
Frank
4f3da0ea2e Added SameSite and Secure options for theme cookie. (#2025)
* Added SameSite and Secure options for theme cookie.

* CookieThemeServiceOptionsSameSiteMode renamed to CookieSameSiteMode
2025-03-10 17:35:51 +02:00
Vladimir Enchev
406c7d8c6b Version updated 2025-03-10 10:36:01 +02:00
yordanov
809f1192f4 Update premium themes 2025-03-10 10:06:12 +02:00
Nopke
ff280d27f1 AutoComplete, Numeric: fix font family in inputs (#2022) 2025-03-10 09:37:25 +02:00
Ben Croughs
c566eb95a2 != null causing issues on KeyValuePart<string,int?> (#2021)
Co-authored-by: Ben Croughs <ben.croughs@telenet.be>
2025-03-10 07:49:19 +02:00
Vladimir Enchev
393d80600c OrderBy() by string selector improved to handle better sort strings discovery 2025-03-07 11:14:15 +02:00
Vladimir Enchev
1e44165d18 demo updated 2025-03-07 06:46:58 +02:00
Vladimir Enchev
03f4774810 version updated 2025-03-07 06:24:08 +02:00
Vladimir Enchev
b64e0446a3 DataGrid sorting of non dynamic data fixed 2025-03-07 06:23:48 +02:00
Vladimir Enchev
e51cab2418 version updated 2025-03-06 21:09:47 +02:00
Vladimir Enchev
f533d106c1 DataGrid dynamic data sort by property with space in the name fixed 2025-03-06 21:09:26 +02:00
Vladimir Enchev
2557691053 DataGrid dynamic data source fixed 2025-03-06 21:03:35 +02:00
Vladimir Enchev
5f53f95b5d Version updated 2025-03-06 15:47:04 +02:00
Vladimir Enchev
2c71574072 Revert "Fixed datagrid sort issues due to duplicate sortDescriptor lists. (#1996)"
This reverts commit 57f1417d4c.
2025-03-06 15:46:03 +02:00
Vladimir Enchev
45d38494f2 version updated 2025-03-06 10:32:31 +02:00
Vladimir Enchev
5c03d0d14c QueryableExtension.Select made internal 2025-03-06 10:30:58 +02:00
1017 changed files with 66220 additions and 11254 deletions

View File

@@ -11,10 +11,10 @@ assignees: ''
IMPORTANT: Read this first!!!
1. If you own a Radzen Professional or Еnterprise subscription you can report your issue or ask us a question via email at info@radzen.com. Radzen staff will reply within 24 hours (Professional) or 16 hours (Enterprise)
1. If you own a Radzen Blazor Pro or Team subscription you can also report your issue or ask us a question via email at info@radzen.com. Radzen staff will reply within 24 hours (Pro) or 16 hours (Team)
2. The Radzen staff guarantees a response to issues in this repo only to paid subscribers.
3. If you have a HOW TO question start a new forum thread in the Radzen Community forum: https://forum.radzen.com. Radzen staff will close issues that are HOWTO questions.
4. Please adhere to the issue template. Specify all the steps required to reproduce the issue or link a project which reproduces it easily (without requiring extra steps such as restoring a database).
4. Please adhere to the issue template. Specify all the steps required to reproduce the issue.
-->
**Describe the bug**
@@ -27,7 +27,12 @@ Steps to reproduce the behavior:
3. Scroll down to '....'
4. See error
Alternatively link your repo with a sample project that can be run.
Alternatively make a new [playground](https://blazor.radzen.com/playground) snippet and paste its URL.
1. Go to any live demo at https://blazor.radzen.com
2. Click the **Edit Source** tab.
3. Then click **Open in Playground**.
4. Reproduce the problem and save the snippet.
5. Copy the snippet URL and provide it in the issue description.
**Expected behavior**
A clear and concise description of what you expected to happen.

View File

@@ -5,4 +5,4 @@ contact_links:
about: Please ask and answer questions here.
- name: Radzen Commercial Support
url: info@radzen.com
about: Radzen Professional or Enterprise subscribers can get dedicated support over email.
about: Radzen Blazor subscribers can get dedicated support over email.

View File

@@ -11,7 +11,7 @@ assignees: ''
IMPORTANT: Read this first!!!
1. If you own a Radzen Professional or Еnterprise subscription you can request your feature via email at info@radzen.com. Radzen staff will reply within 24 hours (Professional) or 16 hours (Enterprise)
1. If you own a Radzen Blazor subscription you can request your feature via email at info@radzen.com. Radzen staff will reply within 24 hours (Pro) or 16 hours (Team)
2. The Radzen staff guarantees a response to issues in this repo only to paid subscribers.
3. If you have a HOW TO question start a new forum thread in the Radzen Community forum: https://forum.radzen.com. Radzen staff will close issues that are HOWTO questions.
4. Please adhere to the issue template.

View File

@@ -20,7 +20,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 9.0.x
dotnet-version: 10.0.x
- name: Build
run: dotnet build Radzen.Blazor/Radzen.Blazor.csproj

66
.github/workflows/deploy.yml vendored Normal file
View File

@@ -0,0 +1,66 @@
name: Deploy to blazor.radzen.com
on:
workflow_dispatch:
concurrency:
group: blazor-radzen-prod
cancel-in-progress: true
jobs:
build-and-deploy:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Compute image tag (timestamp)
id: meta
run: |
TAG=$(date -u +"%Y%m%d%H%M%S")
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${TAG}"
echo "image=$IMAGE" >> "$GITHUB_OUTPUT"
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ steps.meta.outputs.image }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Push to dokku
uses: dokku/github-action@master
with:
git_remote_url: ${{ secrets.DOKKU_REPO }}
ssh_private_key: ${{ secrets.DOKKU_SSH_PRIVATE_KEY }}
deploy_docker_image: ${{ steps.meta.outputs.image }}
- name: Prune GHCR versions (keep 1)
uses: actions/delete-package-versions@v5
with:
package-name: radzen-blazor
package-type: container
min-versions-to-keep: 1
delete-only-pre-release-versions: false

7
.gitignore vendored
View File

@@ -341,3 +341,10 @@ Radzen.DocFX/_exported_templates
Radzen.DocFX/api/*.yml
!Radzen.DocFX/api/index.md
Radzen.DocFX/api/.manifest
Radzen.Blazor.min.js
/.claude
/SANKEY_PATTERN_COMPARISON.md
*.md
/.gitignore
/.gitignore
RadzenBlazorDemos/wwwroot/demos

View File

@@ -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/blazor-studio/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/pricing).
## How Can I Contribute?

View File

@@ -1,31 +1,48 @@
# syntax=docker/dockerfile:1
FROM mono:latest
# =============================
# BUILD STAGE
# =============================
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
ENV DOCFX_VER 2.58.4
# Copy project files first for better caching
COPY Radzen.Blazor/*.csproj Radzen.Blazor/
COPY RadzenBlazorDemos/*.csproj RadzenBlazorDemos/
COPY RadzenBlazorDemos.Host/*.csproj RadzenBlazorDemos.Host/
RUN apt-get update && apt-get install unzip wget git -y && wget -q -P /tmp https://github.com/dotnet/docfx/releases/download/v${DOCFX_VER}/docfx.zip && \
mkdir -p /opt/docfx && \
unzip /tmp/docfx.zip -d /opt/docfx && \
echo '#!/bin/bash\nmono /opt/docfx/docfx.exe $@' > /usr/bin/docfx && \
chmod +x /usr/bin/docfx && \
rm -rf /tmp/*
# Radzen.DocFX usually has no csproj → copy full folder
COPY Radzen.DocFX/ Radzen.DocFX/
COPY Radzen.Blazor /app/Radzen.Blazor
COPY Radzen.DocFX /app/DocFX
COPY RadzenBlazorDemos /app/RadzenBlazorDemos
COPY RadzenBlazorDemos.Host /app/RadzenBlazorDemos.Host
# Restore dependencies
RUN dotnet restore RadzenBlazorDemos.Host/RadzenBlazorDemos.Host.csproj
# Copy full source after restore layer
COPY . .
# Install docfx (build stage only)
RUN dotnet tool install -g docfx
ENV PATH="$PATH:/root/.dotnet/tools"
# Build shared project (keep net8.0 if required)
RUN dotnet build -c Release Radzen.Blazor/Radzen.Blazor.csproj -f net8.0
# Generate documentation
RUN docfx Radzen.DocFX/docfx.json
# Publish the Blazor host app
WORKDIR /src/RadzenBlazorDemos.Host
RUN dotnet publish -c Release -o /app/out
# =============================
# RUNTIME STAGE
# =============================
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
WORKDIR /app
RUN docfx DocFX/docfx.json
FROM mcr.microsoft.com/dotnet/sdk:9.0
# Copy only published output
COPY --from=build /app/out ./
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
ENV ASPNETCORE_URLS http://*:5000
WORKDIR /app/RadzenBlazorDemos.Host/out
# Set runtime URL
ENV ASPNETCORE_URLS=http://+:5000
ENTRYPOINT ["dotnet", "RadzenBlazorDemos.Host.dll"]

View File

@@ -3,7 +3,7 @@
Radzen Blazor Components
========================
A set of **90+ free and open source** native Blazor UI controls.
The most sophisticated free UI component library for Blazor, featuring **100+ native components**.
See Online Demos or Read the Docs
@@ -15,7 +15,7 @@ See Online Demos or Read the Docs
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/blazor-studio/pricing/).
Paid support is available as part of the [Radzen Blazor subscription](https://www.radzen.com/pricing).
### :computer: Native
@@ -38,9 +38,9 @@ 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/blazor-studio/pricing/).
Dedicated support for the Radzen Blazor Components is available as part of the [Radzen Blazor subscription](https://www.radzen.com/pricing).
Our flagship product [Radzen Blazor Studio](https://www.radzen.com/blazor-studio/) provides tons of productivity features for Blazor developers:
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
@@ -50,8 +50,8 @@ Our flagship product [Radzen Blazor Studio](https://www.radzen.com/blazor-studio
## Get started with Radzen Blazor Components
Check the [getting started](https://blazor.radzen.com/getting-started) instructions to start making awesome Blazor applications.
Check the [getting started](https://blazor.radzen.com/get-started) instructions to start making awesome Blazor applications.
## Run demos locally
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.
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. The demos require the .NET 10 SDK and should preferably be opened in VS2026.

View File

@@ -0,0 +1,171 @@
using Bunit;
using Microsoft.Extensions.DependencyInjection;
using Radzen;
using System;
using System.Net.Http;
using Microsoft.Extensions.Options;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class AIChatTests
{
private void RegisterChatService(TestContext ctx)
{
// Register a dummy HttpClient and default options for AIChatService
ctx.Services.AddSingleton(new HttpClient());
ctx.Services.AddScoped<IAIChatService, AIChatService>();
}
[Fact]
public void RadzenAIChat_ShouldRenderWithDefaultProperties()
{
using var ctx = new TestContext();
RegisterChatService(ctx);
var component = ctx.RenderComponent<RadzenAIChat>();
Assert.Contains("Type your message...", component.Markup);
}
[Fact]
public void RadzenAIChat_ShouldRenderWithCustomTitle()
{
using var ctx = new TestContext();
RegisterChatService(ctx);
var component = ctx.RenderComponent<RadzenAIChat>(parameters => parameters
.Add(p => p.Title, "Custom Chat"));
Assert.Contains("Custom Chat", component.Markup);
}
[Fact]
public void RadzenAIChat_ShouldRenderWithCustomPlaceholder()
{
using var ctx = new TestContext();
RegisterChatService(ctx);
var component = ctx.RenderComponent<RadzenAIChat>(parameters => parameters
.Add(p => p.Placeholder, "Enter your message here..."));
Assert.Contains("Enter your message here...", component.Markup);
}
[Fact]
public void RadzenAIChat_ShouldRenderWithCustomEmptyMessage()
{
using var ctx = new TestContext();
RegisterChatService(ctx);
var component = ctx.RenderComponent<RadzenAIChat>(parameters => parameters
.Add(p => p.EmptyMessage, "No messages yet"));
Assert.Contains("No messages yet", component.Markup);
}
[Fact]
public void RadzenAIChat_ShouldShowClearButtonByDefault()
{
using var ctx = new TestContext();
RegisterChatService(ctx);
var component = ctx.RenderComponent<RadzenAIChat>();
Assert.Contains("rz-chat-header-clear", component.Markup);
}
[Fact]
public void RadzenAIChat_ShouldHideClearButtonWhenShowClearButtonIsFalse()
{
using var ctx = new TestContext();
RegisterChatService(ctx);
var component = ctx.RenderComponent<RadzenAIChat>(parameters => parameters
.Add(p => p.ShowClearButton, false));
Assert.DoesNotContain("clear_all", component.Markup);
}
[Fact]
public void RadzenAIChat_ShouldBeDisabledWhenDisabledIsTrue()
{
using var ctx = new TestContext();
RegisterChatService(ctx);
var component = ctx.RenderComponent<RadzenAIChat>(parameters => parameters
.Add(p => p.Disabled, true));
Assert.Contains("disabled", component.Markup);
}
[Fact]
public void RadzenAIChat_ShouldBeReadOnlyWhenReadOnlyIsTrue()
{
using var ctx = new TestContext();
RegisterChatService(ctx);
var component = ctx.RenderComponent<RadzenAIChat>(parameters => parameters
.Add(p => p.ReadOnly, true));
Assert.Contains("readonly", component.Markup);
}
[Fact]
public void RadzenAIChat_ShouldHaveCorrectCssClass()
{
using var ctx = new TestContext();
RegisterChatService(ctx);
var component = ctx.RenderComponent<RadzenAIChat>();
Assert.Contains("rz-chat", component.Markup);
}
[Fact]
public void ChatMessage_ShouldHaveCorrectProperties()
{
// Arrange
var message = new ChatMessage
{
Content = "Test message",
IsUser = true,
Timestamp = DateTime.Now
};
// Assert
Assert.NotEmpty(message.Id);
Assert.Equal("Test message", message.Content);
Assert.True(message.IsUser);
Assert.False(message.IsStreaming);
}
[Fact]
public void RadzenAIChat_AddMessage_ShouldAddMessageToList()
{
using var ctx = new TestContext();
RegisterChatService(ctx);
var component = ctx.RenderComponent<RadzenAIChat>();
// Act
component.Instance.AddMessage("Test message", true);
// Assert
var messages = component.Instance.GetMessages();
Assert.Single(messages);
Assert.Equal("Test message", messages[0].Content);
Assert.True(messages[0].IsUser);
}
[Fact]
public void RadzenAIChat_ClearChat_ShouldRemoveAllMessages()
{
using var ctx = new TestContext();
RegisterChatService(ctx);
var component = ctx.RenderComponent<RadzenAIChat>();
component.Instance.AddMessage("Test message 1", true);
component.Instance.AddMessage("Test message 2", false);
// Act
component.InvokeAsync(async () => await component.Instance.ClearChat()).Wait();
// Assert
Assert.Empty(component.Instance.GetMessages());
}
[Fact]
public void RadzenAIChat_ShouldLimitMessagesToMaxMessages()
{
using var ctx = new TestContext();
RegisterChatService(ctx);
var component = ctx.RenderComponent<RadzenAIChat>(parameters => parameters.Add(p => p.MaxMessages, 3));
component.Instance.AddMessage("Message 1", true);
component.Instance.AddMessage("Message 2", false);
component.Instance.AddMessage("Message 3", true);
component.Instance.AddMessage("Message 4", false);
// Assert
var messages = component.Instance.GetMessages();
Assert.Equal(3, messages.Count);
Assert.Equal("Message 2", messages[0].Content);
Assert.Equal("Message 3", messages[1].Content);
Assert.Equal("Message 4", messages[2].Content);
}
}
}

View File

@@ -0,0 +1,195 @@
using Bunit;
using Microsoft.AspNetCore.Components;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class AccordionTests
{
[Fact]
public void Accordion_Renders_CssClasses()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenAccordion>();
Assert.Contains(@"rz-accordion", component.Markup);
}
[Fact]
public void Accordion_Renders_AccordionItems()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenAccordion>(parameters =>
{
parameters.Add(p => p.Items, builder =>
{
builder.OpenComponent<RadzenAccordionItem>(0);
builder.AddAttribute(1, "Text", "Test Item");
builder.AddAttribute(2, "ChildContent", (RenderFragment)(contentBuilder =>
{
contentBuilder.AddContent(0, "Item Content");
}));
builder.CloseComponent();
});
});
Assert.Contains("Test Item", component.Markup);
Assert.Contains("Item Content", component.Markup);
}
[Fact]
public void Accordion_Renders_ItemWithIcon()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenAccordion>(parameters =>
{
parameters.Add(p => p.Items, builder =>
{
builder.OpenComponent<RadzenAccordionItem>(0);
builder.AddAttribute(1, "Text", "Orders");
builder.AddAttribute(2, "Icon", "account_balance_wallet");
builder.AddAttribute(3, "ChildContent", (RenderFragment)(contentBuilder =>
{
contentBuilder.AddContent(0, "Order Details");
}));
builder.CloseComponent();
});
});
Assert.Contains("account_balance_wallet", component.Markup);
Assert.Contains("Orders", component.Markup);
}
[Fact]
public void Accordion_SingleExpand_OnlyOneItemExpanded()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenAccordion>(parameters =>
{
parameters.Add(p => p.Multiple, false); // Single expand mode
parameters.Add(p => p.Items, builder =>
{
// Add first item
builder.OpenComponent<RadzenAccordionItem>(0);
builder.AddAttribute(1, "Text", "Item 1");
builder.AddAttribute(2, "ChildContent", (RenderFragment)(b => b.AddContent(0, "Content 1")));
builder.CloseComponent();
// Add second item
builder.OpenComponent<RadzenAccordionItem>(1);
builder.AddAttribute(1, "Text", "Item 2");
builder.AddAttribute(2, "ChildContent", (RenderFragment)(b => b.AddContent(0, "Content 2")));
builder.CloseComponent();
});
});
Assert.False(component.Instance.Multiple);
}
[Fact]
public void Accordion_MultipleExpand_AllowsMultipleItemsExpanded()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenAccordion>(parameters =>
{
parameters.Add(p => p.Multiple, true);
});
Assert.True(component.Instance.Multiple);
}
[Fact]
public void Accordion_Raises_ExpandEvent()
{
using var ctx = new TestContext();
var expandRaised = false;
int expandedIndex = -1;
var component = ctx.RenderComponent<RadzenAccordion>(parameters =>
{
parameters.Add(p => p.Expand, EventCallback.Factory.Create<int>(this, (index) =>
{
expandRaised = true;
expandedIndex = index;
}));
parameters.Add(p => p.Items, builder =>
{
builder.OpenComponent<RadzenAccordionItem>(0);
builder.AddAttribute(1, "Text", "Test Item");
builder.AddAttribute(2, "ChildContent", (RenderFragment)(b => b.AddContent(0, "Content")));
builder.CloseComponent();
});
});
// Find and click the accordion header link to expand
var header = component.Find(".rz-accordion-header a");
header.Click();
Assert.True(expandRaised);
Assert.Equal(0, expandedIndex);
}
[Fact]
public void Accordion_Raises_CollapseEvent()
{
using var ctx = new TestContext();
var collapseRaised = false;
int collapsedIndex = -1;
var component = ctx.RenderComponent<RadzenAccordion>(parameters =>
{
parameters.Add(p => p.Collapse, EventCallback.Factory.Create<int>(this, (index) =>
{
collapseRaised = true;
collapsedIndex = index;
}));
parameters.Add(p => p.Items, builder =>
{
builder.OpenComponent<RadzenAccordionItem>(0);
builder.AddAttribute(1, "Text", "Test Item");
builder.AddAttribute(2, "Selected", true); // Start expanded
builder.AddAttribute(3, "ChildContent", (RenderFragment)(b => b.AddContent(0, "Content")));
builder.CloseComponent();
});
});
// Find and click the accordion header link to collapse
var header = component.Find(".rz-accordion-header a");
header.Click();
Assert.True(collapseRaised);
Assert.Equal(0, collapsedIndex);
}
[Fact]
public void Accordion_DisabledItem_CannotExpand()
{
using var ctx = new TestContext();
var expandRaised = false;
var component = ctx.RenderComponent<RadzenAccordion>(parameters =>
{
parameters.Add(p => p.Expand, EventCallback.Factory.Create<int>(this, (_) => expandRaised = true));
parameters.Add(p => p.Items, builder =>
{
builder.OpenComponent<RadzenAccordionItem>(0);
builder.AddAttribute(1, "Text", "Disabled Item");
builder.AddAttribute(2, "Disabled", true);
builder.AddAttribute(3, "ChildContent", (RenderFragment)(b => b.AddContent(0, "Content")));
builder.CloseComponent();
});
});
// Try to click the disabled item
var header = component.Find(".rz-accordion-header a");
header.Click();
// Event should not be raised for disabled item
Assert.False(expandRaised);
}
}
}

View File

@@ -0,0 +1,195 @@
using Bunit;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class AlertTests
{
[Fact]
public void Alert_Renders_CssClasses()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenAlert>();
Assert.Contains(@"rz-alert", component.Markup);
}
[Fact]
public void Alert_Renders_AlertStyle()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenAlert>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.AlertStyle, AlertStyle.Danger));
Assert.Contains("rz-danger", component.Markup);
component.SetParametersAndRender(parameters => parameters.Add(p => p.AlertStyle, AlertStyle.Success));
Assert.Contains("rz-success", component.Markup);
component.SetParametersAndRender(parameters => parameters.Add(p => p.AlertStyle, AlertStyle.Warning));
Assert.Contains("rz-warning", component.Markup);
component.SetParametersAndRender(parameters => parameters.Add(p => p.AlertStyle, AlertStyle.Info));
Assert.Contains("rz-info", component.Markup);
}
[Fact]
public void Alert_Renders_Shade()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenAlert>();
component.SetParametersAndRender(parameters => parameters
.Add(p => p.AlertStyle, AlertStyle.Primary)
.Add(p => p.Shade, Shade.Lighter));
Assert.Contains("rz-shade-lighter", component.Markup);
component.SetParametersAndRender(parameters => parameters
.Add(p => p.AlertStyle, AlertStyle.Primary)
.Add(p => p.Shade, Shade.Darker));
Assert.Contains("rz-shade-darker", component.Markup);
}
[Fact]
public void Alert_Renders_Variant()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenAlert>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.Variant, Variant.Outlined));
Assert.Contains("rz-variant-outlined", component.Markup);
component.SetParametersAndRender(parameters => parameters.Add(p => p.Variant, Variant.Flat));
Assert.Contains("rz-variant-flat", component.Markup);
}
[Fact]
public void Alert_Renders_Title()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenAlert>();
var title = "Alert Title";
component.SetParametersAndRender(parameters => parameters.Add(p => p.Title, title));
Assert.Contains(title, component.Markup);
Assert.Contains("rz-alert-title", component.Markup);
}
[Fact]
public void Alert_Renders_Text()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenAlert>();
var text = "This is an alert message";
component.SetParametersAndRender(parameters => parameters.Add(p => p.Text, text));
Assert.Contains(text, component.Markup);
}
[Fact]
public void Alert_Renders_ChildContent()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenAlert>(parameters =>
{
parameters.AddChildContent("Custom alert content");
});
Assert.Contains("Custom alert content", component.Markup);
}
[Fact]
public void Alert_ShowIcon_DisplaysIcon()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenAlert>();
// Default should show icon
Assert.Contains("rz-alert-icon", component.Markup);
component.SetParametersAndRender(parameters => parameters.Add(p => p.ShowIcon, false));
Assert.DoesNotContain("rz-alert-icon", component.Markup);
}
[Fact]
public void Alert_AllowClose_DisplaysCloseButton()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenAlert>();
// Default AllowClose is true - should contain a button with close icon
component.SetParametersAndRender(parameters => parameters.Add(p => p.AllowClose, true));
Assert.Contains("close", component.Markup);
Assert.Contains("rz-button", component.Markup);
component.SetParametersAndRender(parameters => parameters.Add(p => p.AllowClose, false));
// When AllowClose is false, should not have close button
var buttonCount = System.Text.RegularExpressions.Regex.Matches(component.Markup, "rz-button").Count;
Assert.Equal(0, buttonCount);
}
[Fact]
public void Alert_CloseButton_RaisesCloseEvent()
{
using var ctx = new TestContext();
var closeRaised = false;
var component = ctx.RenderComponent<RadzenAlert>(parameters =>
{
parameters.Add(p => p.AllowClose, true);
parameters.Add(p => p.Close, () => closeRaised = true);
});
var closeButton = component.Find("button.rz-button");
closeButton.Click();
Assert.True(closeRaised);
}
[Fact]
public void Alert_Visible_ControlsDisplay()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenAlert>(parameters =>
{
parameters.Add(p => p.Visible, true);
parameters.Add(p => p.Text, "Visible Alert");
});
Assert.Contains("Visible Alert", component.Markup);
component.SetParametersAndRender(parameters => parameters.Add(p => p.Visible, false));
// When not visible, component should not render
Assert.DoesNotContain("Visible Alert", component.Markup);
}
[Fact]
public void Alert_CloseButton_SetsVisibleToFalse()
{
using var ctx = new TestContext();
var visibleValue = true;
var component = ctx.RenderComponent<RadzenAlert>(parameters =>
{
parameters.Add(p => p.Visible, visibleValue);
parameters.Add(p => p.AllowClose, true);
parameters.Add(p => p.VisibleChanged, (bool value) => visibleValue = value);
});
var closeButton = component.Find("button.rz-button");
closeButton.Click();
Assert.False(visibleValue);
}
}
}

View File

@@ -1,9 +1,74 @@
using Xunit;
using Bunit;
using Xunit;
using System.Collections.Generic;
namespace Radzen.Blazor.Tests
{
public class AutoCompleteTests
{
[Fact]
public void AutoComplete_Renders_WithClassName()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenAutoComplete>();
Assert.Contains(@"rz-autocomplete", component.Markup);
}
[Fact]
public void AutoComplete_Renders_InputElement()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenAutoComplete>();
Assert.Contains("type=\"text\"", component.Markup);
Assert.Contains("rz-inputtext", component.Markup);
}
[Fact]
public void AutoComplete_Renders_Disabled()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenAutoComplete>(parameters =>
{
parameters.Add(p => p.Disabled, true);
});
Assert.Contains("disabled", component.Markup);
Assert.Contains("rz-state-disabled", component.Markup);
}
[Fact]
public void AutoComplete_Renders_Placeholder()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenAutoComplete>(parameters =>
{
parameters.Add(p => p.Placeholder, "Type to search...");
});
Assert.Contains("placeholder=\"Type to search...\"", component.Markup);
}
[Fact]
public void AutoComplete_Renders_WithData()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var data = new List<string> { "Apple", "Banana", "Cherry" };
var component = ctx.RenderComponent<RadzenAutoComplete>(parameters =>
{
parameters.Add(p => p.Data, data);
});
Assert.Contains("rz-autocomplete-panel", component.Markup);
}
[Fact]
public void AutoComplete_Enum_Converts_To_Attr_Value()
{

View File

@@ -0,0 +1,46 @@
using Bunit;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class CardTests
{
[Fact]
public void Card_Renders_WithClassName()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenCard>();
Assert.Contains(@"rz-card", component.Markup);
}
[Fact]
public void Card_Renders_ChildContent()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenCard>(parameters =>
{
parameters.AddChildContent("<div>Card Content</div>");
});
Assert.Contains("Card Content", component.Markup);
}
[Fact]
public void Card_Renders_Variant()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenCard>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.Variant, Variant.Outlined));
Assert.Contains("rz-variant-outlined", component.Markup);
component.SetParametersAndRender(parameters => parameters.Add(p => p.Variant, Variant.Filled));
Assert.Contains("rz-variant-filled", component.Markup);
component.SetParametersAndRender(parameters => parameters.Add(p => p.Variant, Variant.Flat));
Assert.Contains("rz-variant-flat", component.Markup);
}
}
}

View File

@@ -0,0 +1,173 @@
using Bunit;
using Microsoft.AspNetCore.Components;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class CarouselTests
{
[Fact]
public void Carousel_Renders_WithClassName()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenCarousel>();
Assert.Contains(@"rz-carousel", component.Markup);
}
[Fact]
public void Carousel_Renders_AllowPaging_True()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenCarousel>(parameters =>
{
parameters.Add(p => p.AllowPaging, true);
parameters.Add(p => p.Items, builder =>
{
builder.OpenComponent<RadzenCarouselItem>(0);
builder.AddAttribute(1, "ChildContent", (RenderFragment)(b => b.AddContent(0, "Slide 1")));
builder.CloseComponent();
builder.OpenComponent<RadzenCarouselItem>(2);
builder.AddAttribute(3, "ChildContent", (RenderFragment)(b => b.AddContent(0, "Slide 2")));
builder.CloseComponent();
});
});
Assert.Contains("rz-carousel-pager-button", component.Markup);
}
[Fact]
public void Carousel_Renders_AllowPaging_False()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenCarousel>(parameters =>
{
parameters.Add(p => p.AllowPaging, false);
parameters.Add(p => p.Items, builder =>
{
builder.OpenComponent<RadzenCarouselItem>(0);
builder.AddAttribute(1, "ChildContent", (RenderFragment)(b => b.AddContent(0, "Slide 1")));
builder.CloseComponent();
});
});
Assert.DoesNotContain("rz-carousel-pager-button", component.Markup);
}
[Fact]
public void Carousel_Renders_AllowNavigation_True()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenCarousel>(parameters =>
{
parameters.Add(p => p.AllowNavigation, true);
});
Assert.Contains("rz-carousel-prev", component.Markup);
Assert.Contains("rz-carousel-next", component.Markup);
}
[Fact]
public void Carousel_Renders_AllowNavigation_False()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenCarousel>(parameters =>
{
parameters.Add(p => p.AllowNavigation, false);
});
Assert.DoesNotContain("rz-carousel-prev", component.Markup);
Assert.DoesNotContain("rz-carousel-next", component.Markup);
Assert.Contains("rz-carousel-no-navigation", component.Markup);
}
[Fact]
public void Carousel_Renders_PagerPosition_Top()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenCarousel>(parameters =>
{
parameters.Add(p => p.PagerPosition, PagerPosition.Top);
parameters.Add(p => p.AllowPaging, true);
parameters.Add(p => p.Items, builder =>
{
builder.OpenComponent<RadzenCarouselItem>(0);
builder.AddAttribute(1, "ChildContent", (RenderFragment)(b => b.AddContent(0, "Slide")));
builder.CloseComponent();
});
});
Assert.Contains("rz-carousel-pager-top", component.Markup);
}
[Fact]
public void Carousel_Renders_PagerPosition_Bottom()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenCarousel>(parameters =>
{
parameters.Add(p => p.PagerPosition, PagerPosition.Bottom);
parameters.Add(p => p.AllowPaging, true);
parameters.Add(p => p.Items, builder =>
{
builder.OpenComponent<RadzenCarouselItem>(0);
builder.AddAttribute(1, "ChildContent", (RenderFragment)(b => b.AddContent(0, "Slide")));
builder.CloseComponent();
});
});
Assert.Contains("rz-carousel-pager-bottom", component.Markup);
}
[Fact]
public void Carousel_Renders_PagerPosition_TopAndBottom()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenCarousel>(parameters =>
{
parameters.Add(p => p.PagerPosition, PagerPosition.TopAndBottom);
parameters.Add(p => p.AllowPaging, true);
parameters.Add(p => p.Items, builder =>
{
builder.OpenComponent<RadzenCarouselItem>(0);
builder.AddAttribute(1, "ChildContent", (RenderFragment)(b => b.AddContent(0, "Slide")));
builder.CloseComponent();
});
});
Assert.Contains("rz-carousel-pager-top", component.Markup);
Assert.Contains("rz-carousel-pager-bottom", component.Markup);
}
[Fact]
public void Carousel_Renders_PagerOverlay_True()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenCarousel>(parameters =>
{
parameters.Add(p => p.PagerOverlay, true);
});
Assert.Contains("rz-carousel-pager-overlay", component.Markup);
}
[Fact]
public void Carousel_Renders_PagerOverlay_False()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenCarousel>(parameters =>
{
parameters.Add(p => p.PagerOverlay, false);
});
Assert.DoesNotContain("rz-carousel-pager-overlay", component.Markup);
}
}
}

View File

@@ -0,0 +1,369 @@
using Bunit;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.DependencyInjection;
using Radzen.Blazor;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class ChatTests
{
[Fact]
public void RadzenChat_ShouldRenderWithTitle()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenChat>(parameters => parameters
.Add(p => p.Title, "Test Chat")
.Add(p => p.CurrentUserId, "user1")
.Add(p => p.Users, new List<ChatUser>())
.Add(p => p.Messages, new List<ChatMessage>())
);
Assert.Contains("Test Chat", component.Markup);
}
[Fact]
public void RadzenChat_ShouldShowEmptyMessageWhenNoMessages()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenChat>(parameters => parameters
.Add(p => p.Title, "Test Chat")
.Add(p => p.CurrentUserId, "user1")
.Add(p => p.Users, new List<ChatUser>())
.Add(p => p.Messages, new List<ChatMessage>())
.Add(p => p.EmptyMessage, "No messages yet!")
);
Assert.Contains("No messages yet!", component.Markup);
}
[Fact]
public void RadzenChat_ShouldDisplayMessages()
{
var messages = new List<ChatMessage>
{
new ChatMessage { Content = "Hello", UserId = "user1", Timestamp = DateTime.Now },
new ChatMessage { Content = "Hi there!", UserId = "user2", Timestamp = DateTime.Now }
};
var users = new List<ChatUser>
{
new ChatUser { Id = "user1", Name = "John" },
new ChatUser { Id = "user2", Name = "Jane" }
};
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenChat>(parameters => parameters
.Add(p => p.Title, "Test Chat")
.Add(p => p.CurrentUserId, "user1")
.Add(p => p.Users, users)
.Add(p => p.Messages, messages)
);
Assert.Contains("Hello", component.Markup);
Assert.Contains("Hi there!", component.Markup);
}
[Fact]
public void RadzenChat_ShouldShowUsersInHeader()
{
var users = new List<ChatUser>
{
new ChatUser { Id = "user1", Name = "John" },
new ChatUser { Id = "user2", Name = "Jane" }
};
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenChat>(parameters => parameters
.Add(p => p.Title, "Test Chat")
.Add(p => p.CurrentUserId, "user1")
.Add(p => p.Users, users)
.Add(p => p.Messages, new List<ChatMessage>())
.Add(p => p.ShowUsers, true)
);
Assert.Contains("John", component.Markup);
Assert.Contains("Jane", component.Markup);
}
[Fact]
public void RadzenChat_ShouldShowUserNamesAboveMessages()
{
var messages = new List<ChatMessage>
{
new ChatMessage { Content = "Hello", UserId = "user2", Timestamp = DateTime.Now }
};
var users = new List<ChatUser>
{
new ChatUser { Id = "user1", Name = "John" },
new ChatUser { Id = "user2", Name = "Jane" }
};
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenChat>(parameters => parameters
.Add(p => p.Title, "Test Chat")
.Add(p => p.CurrentUserId, "user1")
.Add(p => p.Users, users)
.Add(p => p.Messages, messages)
.Add(p => p.ShowUserNames, true)
);
Assert.Contains("Jane", component.Markup);
}
[Fact]
public void RadzenChat_ShouldShowClearButtonWhenEnabled()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenChat>(parameters => parameters
.Add(p => p.Title, "Test Chat")
.Add(p => p.CurrentUserId, "user1")
.Add(p => p.Users, new List<ChatUser>())
.Add(p => p.Messages, new List<ChatMessage>())
.Add(p => p.ShowClearButton, true)
);
var clearButton = component.Find(".rz-chat-header-clear");
Assert.NotNull(clearButton);
}
[Fact]
public void RadzenChat_ShouldNotShowClearButtonWhenDisabled()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenChat>(parameters => parameters
.Add(p => p.Title, "Test Chat")
.Add(p => p.CurrentUserId, "user1")
.Add(p => p.Users, new List<ChatUser>())
.Add(p => p.Messages, new List<ChatMessage>())
.Add(p => p.ShowClearButton, false)
);
var clearButton = component.FindAll(".rz-chat-header-clear");
Assert.Empty(clearButton);
}
[Fact]
public void RadzenChat_ShouldLimitVisibleUsers()
{
var users = new List<ChatUser>
{
new ChatUser { Id = "user1", Name = "John" },
new ChatUser { Id = "user2", Name = "Jane" },
new ChatUser { Id = "user3", Name = "Bob" },
new ChatUser { Id = "user4", Name = "Alice" },
new ChatUser { Id = "user5", Name = "Charlie" },
new ChatUser { Id = "user6", Name = "David" }
};
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenChat>(parameters => parameters
.Add(p => p.Title, "Test Chat")
.Add(p => p.CurrentUserId, "user1")
.Add(p => p.Users, users)
.Add(p => p.Messages, new List<ChatMessage>())
.Add(p => p.ShowUsers, true)
.Add(p => p.MaxVisibleUsers, 3)
);
// Should show "+3" for the remaining users
Assert.Contains("+3", component.Markup);
}
[Fact]
public void RadzenChat_ShouldShowUserMessagesOnRight()
{
var messages = new List<ChatMessage>
{
new ChatMessage { Content = "My message", UserId = "user1", Timestamp = DateTime.Now }
};
var users = new List<ChatUser>
{
new ChatUser { Id = "user1", Name = "John" }
};
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenChat>(parameters => parameters
.Add(p => p.Title, "Test Chat")
.Add(p => p.CurrentUserId, "user1")
.Add(p => p.Users, users)
.Add(p => p.Messages, messages)
);
var userMessage = component.Find(".rz-chat-message-user");
Assert.NotNull(userMessage);
}
[Fact]
public void RadzenChat_ShouldShowUserMessagesOnLeft()
{
var messages = new List<ChatMessage>
{
new ChatMessage { Content = "Other message", UserId = "user2", Timestamp = DateTime.Now }
};
var users = new List<ChatUser>
{
new ChatUser { Id = "user1", Name = "John" },
new ChatUser { Id = "user2", Name = "Jane" }
};
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenChat>(parameters => parameters
.Add(p => p.Title, "Test Chat")
.Add(p => p.CurrentUserId, "user1")
.Add(p => p.Users, users)
.Add(p => p.Messages, messages)
);
var participantMessage = component.Find(".rz-chat-message-participant");
Assert.NotNull(participantMessage);
}
[Fact]
public void RadzenChat_ShouldShowAvatarInitials()
{
var users = new List<ChatUser>
{
new ChatUser { Id = "user1", Name = "John Doe" }
};
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenChat>(parameters => parameters
.Add(p => p.Title, "Test Chat")
.Add(p => p.CurrentUserId, "user1")
.Add(p => p.Users, users)
.Add(p => p.Messages, new List<ChatMessage>())
.Add(p => p.ShowUsers, true)
);
Assert.Contains("JD", component.Markup);
}
[Fact]
public void RadzenChat_ShouldShowAvatarImageWhenProvided()
{
var users = new List<ChatUser>
{
new ChatUser { Id = "user1", Name = "John", AvatarUrl = "https://example.com/avatar.jpg" }
};
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenChat>(parameters => parameters
.Add(p => p.Title, "Test Chat")
.Add(p => p.CurrentUserId, "user1")
.Add(p => p.Users, users)
.Add(p => p.Messages, new List<ChatMessage>())
.Add(p => p.ShowUsers, true)
);
var avatarImage = component.Find(".rz-chat-participant-image");
Assert.NotNull(avatarImage);
Assert.Equal("https://example.com/avatar.jpg", avatarImage.GetAttribute("src"));
}
[Fact]
public void RadzenChat_ShouldBeDisabledWhenDisabled()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenChat>(parameters => parameters
.Add(p => p.Title, "Test Chat")
.Add(p => p.CurrentUserId, "user1")
.Add(p => p.Users, new List<ChatUser>())
.Add(p => p.Messages, new List<ChatMessage>())
.Add(p => p.Disabled, true)
);
var textarea = component.Find(".rz-chat-textarea");
Assert.True(textarea.HasAttribute("disabled"));
}
[Fact]
public void RadzenChat_ShouldBeReadOnlyWhenReadOnly()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenChat>(parameters => parameters
.Add(p => p.Title, "Test Chat")
.Add(p => p.CurrentUserId, "user1")
.Add(p => p.Users, new List<ChatUser>())
.Add(p => p.Messages, new List<ChatMessage>())
.Add(p => p.ReadOnly, true)
);
var textarea = component.Find(".rz-chat-textarea");
Assert.True(textarea.HasAttribute("readonly"));
}
[Fact]
public void RadzenChat_ShouldShowCustomPlaceholder()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenChat>(parameters => parameters
.Add(p => p.Title, "Test Chat")
.Add(p => p.CurrentUserId, "user1")
.Add(p => p.Users, new List<ChatUser>())
.Add(p => p.Messages, new List<ChatMessage>())
.Add(p => p.Placeholder, "Custom placeholder")
);
var textarea = component.Find(".rz-chat-textarea");
Assert.Equal("Custom placeholder", textarea.GetAttribute("placeholder"));
}
[Fact]
public void RadzenChat_ShouldShowStreamingIndicator()
{
var messages = new List<ChatMessage>
{
new ChatMessage { Content = "Streaming message", UserId = "user1", IsStreaming = true, Timestamp = DateTime.Now }
};
var users = new List<ChatUser>
{
new ChatUser { Id = "user1", Name = "John" }
};
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenChat>(parameters => parameters
.Add(p => p.Title, "Test Chat")
.Add(p => p.CurrentUserId, "user1")
.Add(p => p.Users, users)
.Add(p => p.Messages, messages)
);
var streamingIcon = component.Find(".rz-chat-message-streaming-icon");
Assert.NotNull(streamingIcon);
}
[Fact]
public void RadzenChat_ShouldShowMessageTimestamps()
{
var timestamp = DateTime.Now.AddHours(-1);
var messages = new List<ChatMessage>
{
new ChatMessage { Content = "Test message", UserId = "user1", Timestamp = timestamp }
};
var users = new List<ChatUser>
{
new ChatUser { Id = "user1", Name = "John" }
};
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenChat>(parameters => parameters
.Add(p => p.Title, "Test Chat")
.Add(p => p.CurrentUserId, "user1")
.Add(p => p.Users, users)
.Add(p => p.Messages, messages)
);
Assert.Contains(timestamp.ToString("HH:mm"), component.Markup);
}
}
}

View File

@@ -0,0 +1,168 @@
using Bunit;
using Xunit;
using System.Collections.Generic;
namespace Radzen.Blazor.Tests
{
public class CheckBoxListTests
{
class Item
{
public int Id { get; set; }
public string Name { get; set; }
public bool Disabled { get; set; }
}
[Fact]
public void CheckBoxList_Renders_WithClassName()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenCheckBoxList<int>>();
Assert.Contains(@"rz-checkbox-list", component.Markup);
}
[Fact]
public void CheckBoxList_Renders_WithData()
{
using var ctx = new TestContext();
var data = new List<string> { "Option 1", "Option 2", "Option 3" };
var component = ctx.RenderComponent<RadzenCheckBoxList<string>>(parameters =>
{
parameters.Add(p => p.Data, data);
});
Assert.Contains("Option 1", component.Markup);
Assert.Contains("Option 2", component.Markup);
Assert.Contains("Option 3", component.Markup);
}
[Fact]
public void CheckBoxList_Renders_WithCustomTextValueProperties()
{
using var ctx = new TestContext();
var data = new List<Item>
{
new Item { Id = 1, Name = "First" },
new Item { Id = 2, Name = "Second" }
};
var component = ctx.RenderComponent<RadzenCheckBoxList<int>>(parameters =>
{
parameters.Add(p => p.Data, data);
parameters.Add(p => p.TextProperty, "Name");
parameters.Add(p => p.ValueProperty, "Id");
});
Assert.Contains("First", component.Markup);
Assert.Contains("Second", component.Markup);
}
[Fact]
public void CheckBoxList_Renders_Orientation_Horizontal()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenCheckBoxList<int>>(parameters =>
{
parameters.Add(p => p.Orientation, Orientation.Horizontal);
});
Assert.Contains("rz-flex-row", component.Markup);
}
[Fact]
public void CheckBoxList_Renders_Orientation_Vertical()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenCheckBoxList<int>>(parameters =>
{
parameters.Add(p => p.Orientation, Orientation.Vertical);
});
Assert.Contains("rz-flex-column", component.Markup);
}
[Fact]
public void CheckBoxList_Renders_Disabled()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenCheckBoxList<int>>(parameters =>
{
parameters.Add(p => p.Disabled, true);
});
Assert.Contains("disabled", component.Markup);
Assert.Contains("rz-state-disabled", component.Markup);
}
[Fact]
public void CheckBoxList_Renders_AllowSelectAll()
{
using var ctx = new TestContext();
var data = new List<string> { "Option 1", "Option 2" };
var component = ctx.RenderComponent<RadzenCheckBoxList<string>>(parameters =>
{
parameters.Add(p => p.AllowSelectAll, true);
parameters.Add(p => p.Data, data);
});
Assert.Contains("rz-multiselect-header", component.Markup);
}
[Fact]
public void CheckBoxList_Renders_SelectAllText()
{
using var ctx = new TestContext();
var data = new List<string> { "Option 1", "Option 2" };
var component = ctx.RenderComponent<RadzenCheckBoxList<string>>(parameters =>
{
parameters.Add(p => p.AllowSelectAll, true);
parameters.Add(p => p.SelectAllText, "Select All Options");
parameters.Add(p => p.Data, data);
});
Assert.Contains("Select All Options", component.Markup);
}
[Fact]
public void CheckBoxList_Renders_CheckboxInputs()
{
using var ctx = new TestContext();
var data = new List<string> { "Option 1", "Option 2" };
var component = ctx.RenderComponent<RadzenCheckBoxList<string>>(parameters =>
{
parameters.Add(p => p.Data, data);
});
Assert.Contains("type=\"checkbox\"", component.Markup);
Assert.Contains("rz-chkbox", component.Markup);
}
[Fact]
public void CheckBoxList_Renders_DisabledItems()
{
using var ctx = new TestContext();
var data = new List<Item>
{
new Item { Id = 1, Name = "Enabled", Disabled = false },
new Item { Id = 2, Name = "Disabled", Disabled = true }
};
var component = ctx.RenderComponent<RadzenCheckBoxList<int>>(parameters =>
{
parameters.Add(p => p.Data, data);
parameters.Add(p => p.TextProperty, "Name");
parameters.Add(p => p.ValueProperty, "Id");
parameters.Add(p => p.DisabledProperty, "Disabled");
});
Assert.Contains("Enabled", component.Markup);
Assert.Contains("Disabled", component.Markup);
}
}
}

View File

@@ -0,0 +1,74 @@
using Bunit;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class ColumnTests
{
[Fact]
public void Column_Renders_WithClassName()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenColumn>();
Assert.Contains(@"rz-col", component.Markup);
}
[Fact]
public void Column_Renders_ChildContent()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenColumn>(parameters =>
{
parameters.AddChildContent("<div>Column Content</div>");
});
Assert.Contains("Column Content", component.Markup);
}
[Fact]
public void Column_Renders_SizeParameter()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenColumn>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.Size, 6));
Assert.Contains("rz-col-6", component.Markup);
}
[Fact]
public void Column_Renders_SizeMD()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenColumn>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.SizeMD, 4));
Assert.Contains("rz-col-md-4", component.Markup);
}
[Fact]
public void Column_Renders_SizeSM()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenColumn>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.SizeSM, 12));
Assert.Contains("rz-col-sm-12", component.Markup);
}
[Fact]
public void Column_Renders_Offset()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenColumn>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.Offset, 2));
Assert.Contains("rz-offset-2", component.Markup);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -732,5 +732,130 @@ namespace Radzen.Blazor.Tests
var weekNumberHeader = component.Find(".rz-calendar-view th.rz-datepicker-week-number");
Assert.Contains("Wk", weekNumberHeader.InnerHtml);
}
[Fact]
public void DatePicker_Multiple_Selects_IEnumerableDateTime()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
IEnumerable<DateTime> emitted = null;
var initial = new DateTime(2024, 1, 1);
var component = ctx.RenderComponent<RadzenDatePicker<IEnumerable<DateTime>>>(parameters =>
{
parameters.Add(p => p.Multiple, true);
parameters.Add(p => p.InitialViewDate, initial);
parameters.Add(p => p.ValueChanged, args => { emitted = args; });
});
component.InvokeAsync(() => component.FindAll("td:not(.rz-calendar-other-month) span").First(e => e.TextContent == "10").ParentElement.Click());
component.InvokeAsync(() => component.FindAll("td:not(.rz-calendar-other-month) span").First(e => e.TextContent == "12").ParentElement.Click());
Assert.NotNull(emitted);
var list = emitted.ToList();
Assert.Equal(2, list.Count);
Assert.Contains(new DateTime(2024, 1, 10), list.Select(d => d.Date));
Assert.Contains(new DateTime(2024, 1, 12), list.Select(d => d.Date));
}
[Fact]
public void DatePicker_Multiple_Selects_IEnumerableNullableDateTime()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
IEnumerable<DateTime?> emitted = null;
var initial = new DateTime(2024, 2, 1);
var component = ctx.RenderComponent<RadzenDatePicker<IEnumerable<DateTime?>>>(parameters =>
{
parameters.Add(p => p.Multiple, true);
parameters.Add(p => p.InitialViewDate, initial);
parameters.Add(p => p.ValueChanged, args => { emitted = args; });
});
component.InvokeAsync(() => component.FindAll("td:not(.rz-calendar-other-month) span").First(e => e.TextContent == "3").ParentElement.Click());
component.InvokeAsync(() => component.FindAll("td:not(.rz-calendar-other-month) span").First(e => e.TextContent == "14").ParentElement.Click());
Assert.NotNull(emitted);
var list = emitted.ToList();
Assert.Equal(2, list.Count);
Assert.Contains(new DateTime(2024, 2, 3), list.Select(d => d.Value.Date));
Assert.Contains(new DateTime(2024, 2, 14), list.Select(d => d.Value.Date));
}
[Fact]
public void DatePicker_Multiple_Emits_IEnumerableDateTimeOffsetNullable_WithUtcKind()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
IEnumerable<DateTimeOffset?> emitted = null;
var initial = new DateTime(2024, 3, 1);
var component = ctx.RenderComponent<RadzenDatePicker<IEnumerable<DateTimeOffset?>>>(parameters =>
{
parameters.Add(p => p.Multiple, true);
parameters.Add(p => p.InitialViewDate, initial);
parameters.Add(p => p.Kind, DateTimeKind.Utc);
parameters.Add(p => p.ValueChanged, args => { emitted = args; });
});
component.InvokeAsync(() => component.FindAll("td:not(.rz-calendar-other-month) span").First(e => e.TextContent == "5").ParentElement.Click());
component.InvokeAsync(() => component.FindAll("td:not(.rz-calendar-other-month) span").First(e => e.TextContent == "20").ParentElement.Click());
Assert.NotNull(emitted);
var list = emitted.ToList();
Assert.Equal(2, list.Count);
Assert.All(list, dto => Assert.Equal(TimeSpan.Zero, dto.Value.Offset));
}
[Fact]
public void DatePicker_Multiple_Emits_IEnumerableDateOnlyAndTimeOnlyNullable()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
IEnumerable<DateOnly?> emittedDates = null;
IEnumerable<TimeOnly?> emittedTimes = null;
var initial = new DateTime(2024, 4, 1);
var compDates = ctx.RenderComponent<RadzenDatePicker<IEnumerable<DateOnly?>>>(parameters =>
{
parameters.Add(p => p.Multiple, true);
parameters.Add(p => p.InitialViewDate, initial);
parameters.Add(p => p.ValueChanged, args => { emittedDates = args; });
});
compDates.InvokeAsync(() => compDates.FindAll("td:not(.rz-calendar-other-month) span").First(e => e.TextContent == "7").ParentElement.Click());
compDates.InvokeAsync(() => compDates.FindAll("td:not(.rz-calendar-other-month) span").First(e => e.TextContent == "9").ParentElement.Click());
Assert.NotNull(emittedDates);
var dateList = emittedDates.ToList();
Assert.Equal(2, dateList.Count);
Assert.Contains(new DateOnly(2024, 4, 7), dateList.Select(d => d.Value));
Assert.Contains(new DateOnly(2024, 4, 9), dateList.Select(d => d.Value));
// TimeOnly? emission should produce midnight times for selected dates
var compTimes = ctx.RenderComponent<RadzenDatePicker<IEnumerable<TimeOnly?>>>(parameters =>
{
parameters.Add(p => p.Multiple, true);
parameters.Add(p => p.InitialViewDate, initial);
parameters.Add(p => p.ValueChanged, args => { emittedTimes = args; });
});
compTimes.InvokeAsync(() => compTimes.FindAll("td:not(.rz-calendar-other-month) span").First(e => e.TextContent == "1").ParentElement.Click());
compTimes.InvokeAsync(() => compTimes.FindAll("td:not(.rz-calendar-other-month) span").First(e => e.TextContent == "2").ParentElement.Click());
Assert.NotNull(emittedTimes);
var timeList = emittedTimes.ToList();
Assert.Equal(2, timeList.Count);
Assert.All(timeList, t => Assert.Equal(new TimeOnly(0, 0, 0), t.Value));
}
}
}

View File

@@ -0,0 +1,413 @@
using Bunit;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class DialogServiceTests : ComponentBase
{
public class OpenDialogTests
{
[Fact(DisplayName = "DialogOptions default values are set correctly")]
public void DialogOptions_DefaultValues_AreSetCorrectly()
{
// Arrange
var options = new DialogOptions();
var dialogService = new DialogService(null, null);
// Act
dialogService.OpenDialog<DialogServiceTests>("Test", [], options);
// Assert
Assert.Equal("600px", options.Width);
Assert.Equal("", options.Left);
Assert.Equal("", options.Top);
Assert.Equal("", options.Bottom);
Assert.Equal("", options.Height);
Assert.Equal("", options.Style);
Assert.Equal("", options.CssClass);
Assert.Equal("", options.WrapperCssClass);
Assert.Equal("", options.ContentCssClass);
}
[Fact(DisplayName = "DialogOptions values are retained after OpenDialog call")]
public void DialogOptions_Values_AreRetained_AfterOpenDialogCall()
{
// Arrange
var options = new DialogOptions
{
Width = "800px",
Left = "10px",
Top = "20px",
Bottom = "30px",
Height = "400px",
Style = "background-color: red;",
CssClass = "custom-class",
WrapperCssClass = "wrapper-class",
ContentCssClass = "content-class"
};
var dialogService = new DialogService(null, null);
// Act
dialogService.OpenDialog<DialogServiceTests>("Test", [], options);
// Assert
Assert.Equal("800px", options.Width);
Assert.Equal("10px", options.Left);
Assert.Equal("20px", options.Top);
Assert.Equal("30px", options.Bottom);
Assert.Equal("400px", options.Height);
Assert.Equal("background-color: red;", options.Style);
Assert.Equal("custom-class", options.CssClass);
Assert.Equal("wrapper-class", options.WrapperCssClass);
Assert.Equal("content-class", options.ContentCssClass);
}
[Fact(DisplayName = "DialogOptions is null and default values are set correctly")]
public void DialogOptions_IsNull_DefaultValues_AreSetCorrectly()
{
// Arrange
DialogOptions resultingOptions = null;
var dialogService = new DialogService(null, null);
dialogService.OnOpen += (title, type, parameters, options) => resultingOptions = options;
// Act
dialogService.OpenDialog<DialogServiceTests>("Test", [], null);
// Assert
Assert.NotNull(resultingOptions);
Assert.Equal("600px", resultingOptions.Width);
Assert.Equal("", resultingOptions.Left);
Assert.Equal("", resultingOptions.Top);
Assert.Equal("", resultingOptions.Bottom);
Assert.Equal("", resultingOptions.Height);
Assert.Equal("", resultingOptions.Style);
Assert.Equal("", resultingOptions.CssClass);
Assert.Equal("", resultingOptions.WrapperCssClass);
Assert.Equal("", resultingOptions.ContentCssClass);
}
[Fact(DisplayName = "Open with dynamic component type reflective calls are resolved without exception")]
public void Open_DynamicComponentType_Reflective_Calls_Resolve()
{
// Arrange
string resultingTitle = null;
Type resultingType = null;
var dialogService = new DialogService(null, null);
dialogService.OnOpen += (title, type, _, _) =>
{
resultingTitle = title;
resultingType = type;
};
dialogService.Open("Dynamic Open", typeof(RadzenButton), []);
// Assert
Assert.Equal("Dynamic Open", resultingTitle);
Assert.Equal(typeof(RadzenButton), resultingType);
}
[Fact(DisplayName = "OpenAsync with dynamic component type reflective calls are resolved without exception")]
public async Task OpenAsync_DynamicComponentType_Reflective_Calls_Resolve()
{
// Arrange
string resultingTitle = null;
Type resultingType = null;
var dialogService = new DialogService(null, null);
dialogService.OnOpen += (title, type, _, _) =>
{
resultingTitle = title;
resultingType = type;
};
var openTask = dialogService.OpenAsync("Dynamic Open", typeof(RadzenButton), []);
dialogService.Close();
await openTask;
// Assert
Assert.Equal("Dynamic Open", resultingTitle);
Assert.Equal(typeof(RadzenButton), resultingType);
}
}
public class OpenSideDialogTests
{
[Fact(DisplayName = "SideDialogOptions resizable option is retained after OpenSideDialog call")]
public void SideDialogOptions_Resizable_AreRetained_AfterOpenSideDialogCall()
{
// Arrange
var options = new SideDialogOptions { Resizable = true };
SideDialogOptions resultingOptions = null;
var dialogService = new DialogService(null, null);
dialogService.OnSideOpen += (_, _, sideOptions) => resultingOptions = sideOptions;
// Act
dialogService.OpenSide<DialogServiceTests>("Test", [], options);
// Assert
Assert.NotNull(resultingOptions);
Assert.Same(options, resultingOptions);
Assert.True(resultingOptions.Resizable);
}
[Fact(DisplayName = "Side dialog shows resize bar when Resizable is true")]
public void SideDialog_Resizable_ShowsResizeBar()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.Services.AddScoped<DialogService>();
// Render the dialog host
var cut = ctx.RenderComponent<RadzenDialog>();
// Open a side dialog with Resizable=true
var dialogService = ctx.Services.GetRequiredService<DialogService>();
cut.InvokeAsync(() => dialogService.OpenSide("Test", typeof(RadzenButton),
new Dictionary<string, object>(), new SideDialogOptions { Resizable = true }));
// Assert: the resize bar element is present
cut.WaitForAssertion(() =>
{
var markup = cut.Markup;
Assert.Contains("rz-dialog-resize-bar", markup);
// Optionally ensure the inner handle exists too
Assert.Contains("rz-resize", markup);
});
}
[Fact(DisplayName = "Side dialog hides resize bar when Resizable is false")]
public void SideDialog_NonResizable_HidesResizeBar()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.Services.AddScoped<DialogService>();
// Render the dialog host
var cut = ctx.RenderComponent<RadzenDialog>();
// Open a side dialog with Resizable=false
var dialogService = ctx.Services.GetRequiredService<DialogService>();
cut.InvokeAsync(() => dialogService.OpenSide("Test", typeof(RadzenButton),
new Dictionary<string, object>(), new SideDialogOptions()));
// Assert: the resize bar element is not present
cut.WaitForAssertion(() =>
{
var markup = cut.Markup;
Assert.DoesNotContain("rz-dialog-resize-bar", markup);
});
}
}
public class ConfirmTests
{
[Fact(DisplayName = "ConfirmOptions is null and default values are set correctly")]
public async Task ConfirmOptions_IsNull_AreSetCorrectly()
{
// Arrange
var dialogService = new DialogService(null, null);
ConfirmOptions resultingOptions = null;
dialogService.OnOpen += (title, type, parameters, options) => resultingOptions = options as ConfirmOptions;
using var cancellationTokenSource = new CancellationTokenSource();
cancellationTokenSource.Cancel();
// Act
try
{
await dialogService.Confirm(cancellationToken: cancellationTokenSource.Token);
}
catch (TaskCanceledException)
{
// this is expected
}
// Assert
Assert.NotNull(resultingOptions);
Assert.Equal("Ok", resultingOptions.OkButtonText);
Assert.Equal("Cancel", resultingOptions.CancelButtonText);
Assert.Equal("600px", resultingOptions.Width);
Assert.Equal("", resultingOptions.Style);
Assert.Equal("rz-dialog-confirm", resultingOptions.CssClass);
Assert.Equal("rz-dialog-wrapper", resultingOptions.WrapperCssClass);
}
[Fact(DisplayName = "ConfirmOptions default values are set correctly")]
public async Task ConfirmOptions_DefaultValues_AreSetCorrectly()
{
// Arrange
var dialogService = new DialogService(null, null);
ConfirmOptions resultingOptions = null;
dialogService.OnOpen += (title, type, parameters, options) => resultingOptions = options as ConfirmOptions;
using var cancellationTokenSource = new CancellationTokenSource();
cancellationTokenSource.Cancel();
// Act
try
{
await dialogService.Confirm(options: new(), cancellationToken: cancellationTokenSource.Token);
}
catch (TaskCanceledException)
{
// this is expected
}
// Assert
Assert.NotNull(resultingOptions);
Assert.Equal("Ok", resultingOptions.OkButtonText);
Assert.Equal("Cancel", resultingOptions.CancelButtonText);
Assert.Equal("600px", resultingOptions.Width);
Assert.Equal("", resultingOptions.Style);
Assert.Equal("rz-dialog-confirm", resultingOptions.CssClass);
Assert.Equal("rz-dialog-wrapper", resultingOptions.WrapperCssClass);
}
[Fact(DisplayName = "ConfirmOptions values are retained after Confirm call")]
public async Task Confirm_ProvidedValues_AreRetained()
{
// Arrange
var dialogService = new DialogService(null, null);
var options = new ConfirmOptions
{
OkButtonText = "XXX",
CancelButtonText = "YYY",
Width = "800px",
Style = "background-color: red;",
CssClass = "custom-class",
WrapperCssClass = "wrapper-class"
};
ConfirmOptions resultingOptions = null;
dialogService.OnOpen += (title, type, parameters, options) => resultingOptions = options as ConfirmOptions;
// We break out of the dialog immediately, but the options should still be set
using var cancellationTokenSource = new CancellationTokenSource();
cancellationTokenSource.Cancel();
// Act
try
{
await dialogService.Confirm("Confirm?", "Confirm", options, cancellationToken: cancellationTokenSource.Token);
}
catch (TaskCanceledException)
{
// this is expected
}
// Assert
Assert.NotNull(resultingOptions);
Assert.Equal("XXX", resultingOptions.OkButtonText);
Assert.Equal("YYY", resultingOptions.CancelButtonText);
Assert.Equal("800px", resultingOptions.Width);
Assert.Equal("background-color: red;", resultingOptions.Style);
Assert.Equal("rz-dialog-confirm custom-class", resultingOptions.CssClass);
Assert.Equal("rz-dialog-wrapper wrapper-class", resultingOptions.WrapperCssClass);
}
}
public class AlertTests
{
[Fact(DisplayName = "AlertOptions is null and default values are set correctly")]
public async Task AlertOptions_IsNull_AreSetCorrectly()
{
// Arrange
var dialogService = new DialogService(null, null);
AlertOptions resultingOptions = null;
dialogService.OnOpen += (title, type, parameters, options) => resultingOptions = options as AlertOptions;
using var cancellationTokenSource = new CancellationTokenSource();
cancellationTokenSource.Cancel();
// Act
try
{
await dialogService.Alert(cancellationToken: cancellationTokenSource.Token);
}
catch (TaskCanceledException)
{
// this is expected
}
// Assert
Assert.NotNull(resultingOptions);
Assert.Equal("Ok", resultingOptions.OkButtonText);
Assert.Equal("600px", resultingOptions.Width);
Assert.Equal("", resultingOptions.Style);
Assert.Equal("rz-dialog-alert", resultingOptions.CssClass);
Assert.Equal("rz-dialog-wrapper", resultingOptions.WrapperCssClass);
}
[Fact(DisplayName = "AlertOptions default values are set correctly")]
public async Task AlertOptions_DefaultValues_AreSetCorrectly()
{
// Arrange
var dialogService = new DialogService(null, null);
AlertOptions resultingOptions = null;
dialogService.OnOpen += (title, type, parameters, options) => resultingOptions = options as AlertOptions;
using var cancellationTokenSource = new CancellationTokenSource();
cancellationTokenSource.Cancel();
// Act
try
{
await dialogService.Alert(options: new(), cancellationToken: cancellationTokenSource.Token);
}
catch (TaskCanceledException)
{
// this is expected
}
// Assert
Assert.NotNull(resultingOptions);
Assert.Equal("Ok", resultingOptions.OkButtonText);
Assert.Equal("600px", resultingOptions.Width);
Assert.Equal("", resultingOptions.Style);
Assert.Equal("rz-dialog-alert", resultingOptions.CssClass);
Assert.Equal("rz-dialog-wrapper", resultingOptions.WrapperCssClass);
}
[Fact(DisplayName = "AlertOptions values are retained after Alert call")]
public async Task Alert_ProvidedValues_AreRetained()
{
// Arrange
var dialogService = new DialogService(null, null);
var options = new AlertOptions
{
OkButtonText = "XXX",
Width = "800px",
Style = "background-color: red;",
CssClass = "custom-class",
WrapperCssClass = "wrapper-class"
};
AlertOptions resultingOptions = null;
dialogService.OnOpen += (title, type, parameters, options) => resultingOptions = options as AlertOptions;
// We break out of the dialog immediately, but the options should still be set
using var cancellationTokenSource = new CancellationTokenSource();
cancellationTokenSource.Cancel();
// Act
try
{
await dialogService.Alert("Alert?", "Alert", options, cancellationToken: cancellationTokenSource.Token);
}
catch (TaskCanceledException)
{
// this is expected
}
// Assert
Assert.NotNull(resultingOptions);
Assert.Equal("XXX", resultingOptions.OkButtonText);
Assert.Equal("800px", resultingOptions.Width);
Assert.Equal("background-color: red;", resultingOptions.Style);
Assert.Equal("rz-dialog-alert custom-class", resultingOptions.CssClass);
Assert.Equal("rz-dialog-wrapper wrapper-class", resultingOptions.WrapperCssClass);
}
}
}
}

View File

@@ -0,0 +1,289 @@
using Bunit;
using Xunit;
using System.Collections.Generic;
using System.Linq;
namespace Radzen.Blazor.Tests
{
public class DropDownDataGridTests
{
class Customer
{
public int Id { get; set; }
public string CompanyName { get; set; }
public string ContactName { get; set; }
}
[Fact]
public void DropDownDataGrid_Renders_WithClassName()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenDropDownDataGrid<int>>();
Assert.Contains(@"rz-dropdown", component.Markup);
}
[Fact]
public void DropDownDataGrid_Renders_DropdownTrigger()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenDropDownDataGrid<int>>();
Assert.Contains("rz-dropdown-trigger", component.Markup);
Assert.Contains("rzi-chevron-down", component.Markup);
}
[Fact]
public void DropDownDataGrid_Renders_WithData()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var data = new List<string> { "Item1", "Item2", "Item3" };
var component = ctx.RenderComponent<RadzenDropDownDataGrid<string>>(parameters =>
{
parameters.Add(p => p.Data, data);
});
Assert.Contains("rz-lookup-panel", component.Markup);
}
[Fact]
public void DropDownDataGrid_Renders_WithCustomTextValueProperties()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var data = new List<Customer>
{
new Customer { Id = 1, CompanyName = "Acme Corp", ContactName = "John Doe" },
new Customer { Id = 2, CompanyName = "Tech Inc", ContactName = "Jane Smith" }
};
var component = ctx.RenderComponent<RadzenDropDownDataGrid<int>>(parameters =>
{
parameters.Add(p => p.Data, data);
parameters.Add(p => p.TextProperty, "CompanyName");
parameters.Add(p => p.ValueProperty, "Id");
});
Assert.Contains("rz-lookup-panel", component.Markup);
Assert.Contains("rz-data-grid", component.Markup);
}
[Fact]
public void DropDownDataGrid_Renders_DataGrid()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var data = new List<string> { "Item1" };
var component = ctx.RenderComponent<RadzenDropDownDataGrid<string>>(parameters =>
{
parameters.Add(p => p.Data, data);
});
// DropDownDataGrid embeds a DataGrid
Assert.Contains("rz-data-grid", component.Markup);
}
[Fact]
public void DropDownDataGrid_Renders_AllowFiltering()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var data = new List<string> { "Item1", "Item2" };
var component = ctx.RenderComponent<RadzenDropDownDataGrid<string>>(parameters =>
{
parameters.Add(p => p.AllowFiltering, true);
parameters.Add(p => p.Data, data);
});
Assert.Contains("rz-lookup-search", component.Markup);
Assert.Contains("rz-lookup-search-input", component.Markup);
}
[Fact]
public void DropDownDataGrid_Renders_Placeholder()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenDropDownDataGrid<int>>(parameters =>
{
parameters.Add(p => p.Placeholder, "Select an item");
});
Assert.Contains("Select an item", component.Markup);
Assert.Contains("rz-placeholder", component.Markup);
}
[Fact]
public void DropDownDataGrid_Renders_AllowClear_WithValue()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var data = new List<string> { "Item1" };
var component = ctx.RenderComponent<RadzenDropDownDataGrid<string>>(parameters =>
{
parameters.Add(p => p.AllowClear, true);
parameters.Add(p => p.Data, data);
parameters.Add(p => p.Value, "Item1");
});
Assert.Contains("rz-dropdown-clear-icon", component.Markup);
}
[Fact]
public void DropDownDataGrid_DoesNotRender_AllowClear_WhenNotAllowed()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var data = new List<string> { "Item1" };
var component = ctx.RenderComponent<RadzenDropDownDataGrid<string>>(parameters =>
{
parameters.Add(p => p.AllowClear, false);
parameters.Add(p => p.Data, data);
parameters.Add(p => p.Value, "Item1");
});
Assert.DoesNotContain("rz-dropdown-clear-icon", component.Markup);
}
[Fact]
public void DropDownDataGrid_Renders_Disabled()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenDropDownDataGrid<int>>(parameters =>
{
parameters.Add(p => p.Disabled, true);
});
Assert.Contains("disabled", component.Markup);
Assert.Contains("rz-state-disabled", component.Markup);
}
[Fact]
public void DropDownDataGrid_Renders_Multiple_Panel()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenDropDownDataGrid<IEnumerable<int>>>(parameters =>
{
parameters.Add(p => p.Multiple, true);
});
Assert.Contains("rz-multiselect-panel", component.Markup);
}
[Fact]
public void DropDownDataGrid_Renders_Multiple_WithChips()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var data = new List<string> { "Item1", "Item2" };
var selectedItems = new List<string> { "Item1" };
var component = ctx.RenderComponent<RadzenDropDownDataGrid<IEnumerable<string>>>(parameters =>
{
parameters.Add(p => p.Multiple, true);
parameters.Add(p => p.Chips, true);
parameters.Add(p => p.Data, data);
parameters.Add(p => p.Value, selectedItems);
});
Assert.Contains("rz-dropdown-chips-wrapper", component.Markup);
Assert.Contains("rz-chip", component.Markup);
}
[Fact]
public void DropDownDataGrid_Renders_AllowSorting()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var data = new List<Customer>
{
new Customer { Id = 1, CompanyName = "Acme" }
};
var component = ctx.RenderComponent<RadzenDropDownDataGrid<int>>(parameters =>
{
parameters.Add(p => p.AllowSorting, true);
parameters.Add(p => p.Data, data);
parameters.Add(p => p.TextProperty, "CompanyName");
});
Assert.Contains("rz-data-grid", component.Markup);
}
[Fact]
public void DropDownDataGrid_Renders_SearchTextPlaceholder()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var data = new List<string> { "Item1" };
var component = ctx.RenderComponent<RadzenDropDownDataGrid<string>>(parameters =>
{
parameters.Add(p => p.AllowFiltering, true);
parameters.Add(p => p.SearchTextPlaceholder, "Type to filter...");
parameters.Add(p => p.Data, data);
});
Assert.Contains("placeholder=\"Type to filter...\"", component.Markup);
}
[Fact]
public void DropDownDataGrid_Renders_EmptyText()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenDropDownDataGrid<string>>(parameters =>
{
parameters.Add(p => p.Data, new List<string>());
parameters.Add(p => p.EmptyText, "No items found");
});
Assert.Contains("No items found", component.Markup);
}
[Fact]
public void DropDownDataGrid_Renders_PageSize()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var data = Enumerable.Range(1, 20).Select(i => $"Item {i}").ToList();
var component = ctx.RenderComponent<RadzenDropDownDataGrid<string>>(parameters =>
{
parameters.Add(p => p.Data, data);
parameters.Add(p => p.PageSize, 10);
});
// DataGrid with paging should be present
Assert.Contains("rz-data-grid", component.Markup);
}
[Fact]
public void DropDownDataGrid_Renders_AllowRowSelectOnRowClick()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var data = new List<string> { "Item1", "Item2" };
var component = ctx.RenderComponent<RadzenDropDownDataGrid<string>>(parameters =>
{
parameters.Add(p => p.AllowRowSelectOnRowClick, true);
parameters.Add(p => p.Data, data);
});
Assert.Contains("rz-data-grid", component.Markup);
}
}
}

View File

@@ -1,10 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AngleSharp.Dom;
using Bunit;
using Microsoft.AspNetCore.Components;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
namespace Radzen.Blazor.Tests
@@ -15,7 +16,7 @@ namespace Radzen.Blazor.Tests
{
public string Text { get; set; }
public int Id { get; set; }
public bool Disabled { get; set; } = false;
public bool Disabled { get; set; }
}
private static IRenderedComponent<RadzenDropDown<T>> DropDown<T>(TestContext ctx, Action<ComponentParameterCollectionBuilder<RadzenDropDown<T>>> configure = null)
@@ -45,6 +46,7 @@ namespace Radzen.Blazor.Tests
return component;
}
[Fact]
public async Task Dropdown_SelectItem_Method_Should_Not_Throw()
{
@@ -127,7 +129,7 @@ namespace Radzen.Blazor.Tests
List<DataItem> boundCollection = [new() { Text = "Item 2" }];
var component = DropDown<string>(ctx, parameters =>
var component = DropDown<List<DataItem>>(ctx, parameters =>
{
parameters.Add(p => p.ItemComparer, new DataItemComparer());
parameters.Add(p => p.Multiple, true);
@@ -307,7 +309,7 @@ namespace Radzen.Blazor.Tests
selectedValues.Add(data[1].Id);
}
var component = ctx.RenderComponent<RadzenDropDown<DataItem>>(parameters => parameters
var component = ctx.RenderComponent<RadzenDropDown<List<int>>>(parameters => parameters
.Add(p => p.Data, data)
.Add(p => p.Value, selectedValues)
.Add(p => p.Multiple, true)
@@ -326,6 +328,321 @@ namespace Radzen.Blazor.Tests
Assert.Equal(expectedAriaCheckedValue, selectAllCheckBox.GetAttribute("aria-checked"));
}
[Fact]
public void DropDown_ReferenceGenericCollectionAssignment_HashSet_ReferencesInstance()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var originalHashSet = new HashSet<int>();
var capturedValue = (HashSet<int>)null;
var component = DropDownWithReferenceCollection<HashSet<int>>(ctx, parameters =>
{
parameters.Add(p => p.Multiple, true);
parameters.Add(p => p.Value, originalHashSet);
parameters.Add(p => p.ValueChanged, EventCallback.Factory.Create<HashSet<int>>(this, value => capturedValue = value));
parameters.Add(p => p.ValueProperty, nameof(DataItem.Id));
});
var items = component.FindAll(".rz-multiselect-item");
// Select first item
items[0].Click();
component.Render();
// Verify the same HashSet instance is Referenced
Assert.Same(originalHashSet, capturedValue);
// Verify the item was added correctly
Assert.Single(originalHashSet);
Assert.Contains(1, originalHashSet);
}
[Fact]
public void DropDown_ReferenceGenericCollectionAssignment_HashSet_MultipleSelections()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var originalHashSet = new HashSet<int> { 2 }; // Pre-populate with Item 2
var capturedValues = new List<HashSet<int>>();
var component = DropDownWithReferenceCollection<HashSet<int>>(ctx, parameters =>
{
parameters.Add(p => p.Multiple, true);
parameters.Add(p => p.Value, originalHashSet);
parameters.Add(p => p.ValueChanged, EventCallback.Factory.Create<HashSet<int>>(this, value => capturedValues.Add(value)));
parameters.Add(p => p.ValueProperty, nameof(DataItem.Id));
});
var items = component.FindAll(".rz-multiselect-item");
// Select first item (should add to existing collection)
items[0].Click();
component.Render();
// Verify the same HashSet instance is Referenced
Assert.Single(capturedValues);
Assert.Same(originalHashSet, capturedValues[0]);
// Verify both items are now in the collection
Assert.Equal(2, originalHashSet.Count);
Assert.Contains(1, originalHashSet);
Assert.Contains(2, originalHashSet);
// Deselect second item (should remove from collection)
items = component.FindAll(".rz-multiselect-item"); // Re-find items after render
items[1].Click();
component.Render();
// Verify the same HashSet instance is still Referenced
Assert.Equal(2, capturedValues.Count);
Assert.Same(originalHashSet, capturedValues[1]);
// Verify only first item remains
Assert.Single(originalHashSet);
Assert.Contains(1, originalHashSet);
Assert.DoesNotContain(2, originalHashSet);
}
[Fact]
public void DropDown_ReferenceGenericCollectionAssignment_SortedSet_ReferencesInstance()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var originalSortedSet = new SortedSet<int>();
var capturedValue = (SortedSet<int>)null;
var component = DropDownWithReferenceCollection<SortedSet<int>>(ctx, parameters =>
{
parameters.Add(p => p.Multiple, true);
parameters.Add(p => p.Value, originalSortedSet);
parameters.Add(p => p.ValueChanged, EventCallback.Factory.Create<SortedSet<int>>(this, value => capturedValue = value));
parameters.Add(p => p.ValueProperty, nameof(DataItem.Id));
});
var items = component.FindAll(".rz-multiselect-item");
// Select both items
items[0].Click();
component.Render();
items = component.FindAll(".rz-multiselect-item"); // Re-find items after first click
items[1].Click();
component.Render();
// Verify the same SortedSet instance is Referenced
Assert.Same(originalSortedSet, capturedValue);
// Verify items are sorted correctly
Assert.Equal(2, originalSortedSet.Count);
var sortedItems = originalSortedSet.ToList();
Assert.Equal(1, sortedItems[0]);
Assert.Equal(2, sortedItems[1]);
}
[Fact]
public void DropDown_ReferenceGenericCollectionAssignment_CustomCollection_ReferencesInstance()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var originalCollection = new CustomCollection<int>();
var capturedValue = (CustomCollection<int>)null;
var component = DropDownWithReferenceCollection<CustomCollection<int>>(ctx, parameters =>
{
parameters.Add(p => p.Multiple, true);
parameters.Add(p => p.Value, originalCollection);
parameters.Add(p => p.ValueChanged, EventCallback.Factory.Create<CustomCollection<int>>(this, value => capturedValue = value));
parameters.Add(p => p.ValueProperty, nameof(DataItem.Id));
});
var items = component.FindAll(".rz-multiselect-item");
// Select first item
items[0].Click();
component.Render();
// Verify the same custom collection instance is Referenced
Assert.Same(originalCollection, capturedValue);
// Verify the item was added correctly
Assert.Single(originalCollection);
Assert.Contains(1, originalCollection);
}
[Fact]
public void DropDown_ReferenceGenericCollectionAssignment_List_ReferencesInstance()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var originalList = new List<int>();
var capturedValue = (List<int>)null;
var component = DropDownWithReferenceCollection<List<int>>(ctx, parameters =>
{
parameters.Add(p => p.Multiple, true);
parameters.Add(p => p.Value, originalList);
parameters.Add(p => p.ValueChanged, EventCallback.Factory.Create<List<int>>(this, value => capturedValue = value));
parameters.Add(p => p.ValueProperty, nameof(DataItem.Id));
});
var items = component.FindAll(".rz-multiselect-item");
// Select first item
items[0].Click();
component.Render();
// For List<T>, it should now Reference the instance since we removed the IList exclusion
// Arrays are now excluded instead
Assert.Same(originalList, capturedValue);
// And the content should be correct
Assert.Single(capturedValue);
Assert.Contains(1, capturedValue);
}
[Fact]
public void DropDown_ReferenceGenericCollectionAssignment_DisabledByDefault()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var originalList = new List<int>();
var capturedValue = (List<int>)null;
var component = DropDown<List<int>>(ctx, parameters =>
{
parameters.Add(p => p.Multiple, true);
parameters.Add(p => p.Value, originalList);
parameters.Add(p => p.ValueChanged, EventCallback.Factory.Create<List<int>>(this, value => capturedValue = value));
parameters.Add(p => p.ValueProperty, nameof(DataItem.Id));
});
var items = component.FindAll(".rz-multiselect-item");
// Select first item
items[0].Click();
component.Render();
// When ReferenceCollectionOnSelection is false (default), a new instance should be created
Assert.NotSame(originalList, capturedValue);
// But the content should still be correct
Assert.Single(capturedValue);
Assert.Contains(1, capturedValue);
}
[Fact]
public void DropDown_Reset_PreservesCollectionInstanceButClears()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var originalHashSet = new HashSet<int> { 1, 2 }; // Pre-populate
var capturedValues = new List<HashSet<int>>();
var component = DropDownWithReferenceCollection<HashSet<int>>(ctx, parameters =>
{
parameters.Add(p => p.Multiple, true);
parameters.Add(p => p.Value, originalHashSet);
parameters.Add(p => p.ValueChanged, EventCallback.Factory.Create<HashSet<int>>(this, value => capturedValues.Add(value)));
parameters.Add(p => p.ValueProperty, nameof(DataItem.Id));
});
// Verify initial state - collection should have 2 items
Assert.Equal(2, originalHashSet.Count);
Assert.Contains(1, originalHashSet);
Assert.Contains(2, originalHashSet);
// Call Reset (public method that calls ClearAll internally)
component.InvokeAsync(() => component.Instance.Reset());
component.Render();
// Verify the same HashSet instance is preserved
Assert.Single(capturedValues);
Assert.Same(originalHashSet, capturedValues[0]);
// Verify the collection is now cleared
Assert.Empty(originalHashSet);
}
[Fact]
public void DropDown_SelectAll_PreservesCollectionInstanceAndPopulates()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var originalHashSet = new HashSet<int>(); // Start empty
var capturedValues = new List<HashSet<int>>();
var component = DropDownWithReferenceCollection<HashSet<int>>(ctx, parameters =>
{
parameters.Add(p => p.Multiple, true);
parameters.Add(p => p.AllowSelectAll, true);
parameters.Add(p => p.Value, originalHashSet);
parameters.Add(p => p.ValueChanged, EventCallback.Factory.Create<HashSet<int>>(this, value => capturedValues.Add(value)));
parameters.Add(p => p.ValueProperty, nameof(DataItem.Id));
});
// Verify initial state - collection should be empty
Assert.Empty(originalHashSet);
// Find and click the "Select All" checkbox
var selectAllCheckBox = component.Find(".rz-multiselect-header input[type='checkbox']");
selectAllCheckBox.Click();
component.Render();
// Verify the same HashSet instance is preserved
Assert.Single(capturedValues);
Assert.Same(originalHashSet, capturedValues[0]);
// Verify the collection now contains both items
Assert.Equal(2, originalHashSet.Count);
Assert.Contains(1, originalHashSet);
Assert.Contains(2, originalHashSet);
}
class ReferenceCollectionDropDown<T> : Radzen.Blazor.RadzenDropDown<T>
{
protected override void OnInitialized()
{
PreserveCollectionOnSelection = true;
base.OnInitialized();
}
}
private static IRenderedComponent<ReferenceCollectionDropDown<T>> DropDownWithReferenceCollection<T>(TestContext ctx, Action<ComponentParameterCollectionBuilder<ReferenceCollectionDropDown<T>>> configure = null)
{
var data = new[] {
new DataItem { Text = "Item 1", Id = 1 },
new DataItem { Text = "Item 2", Id = 2 },
};
var component = ctx.RenderComponent<ReferenceCollectionDropDown<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;
}
class DataItemComparer : IEqualityComparer<DataItem>, IEqualityComparer<object>
{
public bool Equals(DataItem x, DataItem y)
@@ -353,5 +670,74 @@ namespace Radzen.Blazor.Tests
}
}
class CustomCollection<T> : ICollection<T>
{
private readonly List<T> _items = new();
public int Count => _items.Count;
public bool IsReadOnly => false;
public void Add(T item) => _items.Add(item);
public void Clear() => _items.Clear();
public bool Contains(T item) => _items.Contains(item);
public void CopyTo(T[] array, int arrayIndex) => _items.CopyTo(array, arrayIndex);
public bool Remove(T item) => _items.Remove(item);
public IEnumerator<T> GetEnumerator() => _items.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
[Fact]
public void DropDown_Renders_Placeholder()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenDropDown<int>>(parameters =>
{
parameters.Add(p => p.Placeholder, "Select an option");
});
Assert.Contains("Select an option", component.Markup);
Assert.Contains("rz-placeholder", component.Markup);
}
[Fact]
public void DropDown_Renders_AllowClear_WithValue()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var data = new[] { new DataItem { Text = "Item 1", Id = 1 } };
var component = ctx.RenderComponent<RadzenDropDown<int>>(parameters =>
{
parameters.Add(p => p.AllowClear, true);
parameters.Add(p => p.Data, data);
parameters.Add(p => p.TextProperty, nameof(DataItem.Text));
parameters.Add(p => p.ValueProperty, nameof(DataItem.Id));
parameters.Add(p => p.Value, 1);
});
Assert.Contains("rz-dropdown-clear-icon", component.Markup);
}
[Fact]
public void DropDown_DoesNotRender_AllowClear_WhenNotAllowed()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var data = new[] { new DataItem { Text = "Item 1", Id = 1 } };
var component = ctx.RenderComponent<RadzenDropDown<int>>(parameters =>
{
parameters.Add(p => p.AllowClear, false);
parameters.Add(p => p.Data, data);
parameters.Add(p => p.TextProperty, nameof(DataItem.Text));
parameters.Add(p => p.ValueProperty, nameof(DataItem.Id));
parameters.Add(p => p.Value, 1);
});
Assert.DoesNotContain("rz-dropdown-clear-icon", component.Markup);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,323 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using Xunit;
namespace Radzen.Blazor.Tests
{
class TestEntity
{
public string Name { get; set; }
public int Age { get; set; }
public double Salary { get; set; }
public float Score { get; set; }
public decimal Balance { get; set; }
public short Level { get; set; }
public long Population { get; set; }
public Status AccountStatus { get; set; }
public DateTime CreatedAt { get; set; }
public DateTimeOffset LastUpdated { get; set; }
public Guid Id { get; set; }
public TimeOnly StartTime { get; set; }
public DateOnly BirthDate { get; set; }
public List<int> Scores { get; set; }
public List<string> Tags { get; set; }
public List<TestEntity> Children { get; set; }
public Address Address { get; set; }
public double[] Salaries { get; set; }
public float[] Heights { get; set; }
public decimal[] Balances { get; set; }
public short[] Levels { get; set; }
public long[] Populations { get; set; }
public string[] Names { get; set; }
public Guid[] Ids { get; set; }
public DateTime[] CreatedDates { get; set; }
public DateTimeOffset[] UpdatedDates { get; set; }
public TimeOnly[] StartTimes { get; set; }
public DateOnly[] BirthDates { get; set; }
public Status[] Statuses { get; set; }
}
enum Status
{
Active,
Inactive,
Suspended
}
class Address
{
public string City { get; set; }
public string Country { get; set; }
}
public class ExpressionSerializerTests
{
private readonly ExpressionSerializer _serializer = new ExpressionSerializer();
[Fact]
public void Serializes_SimpleBinaryExpression()
{
Expression<Func<int, bool>> expr = e => e > 10;
Assert.Equal("e => (e > 10)", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_StringEquality()
{
Expression<Func<TestEntity, bool>> expr = e => e.Name == "John";
Assert.Equal("e => (e.Name == \"John\")", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_IntComparison()
{
Expression<Func<TestEntity, bool>> expr = e => e.Age > 18;
Assert.Equal("e => (e.Age > 18)", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_DoubleComparison()
{
Expression<Func<TestEntity, bool>> expr = e => e.Salary < 50000.50;
Assert.Equal("e => (e.Salary < 50000.5)", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_FloatComparison()
{
Expression<Func<TestEntity, bool>> expr = e => e.Score >= 85.3f;
Assert.Equal("e => (e.Score >= 85.3)", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_DecimalComparison()
{
Expression<Func<TestEntity, bool>> expr = e => e.Balance <= 1000.75m;
Assert.Equal("e => (e.Balance <= 1000.75)", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_ShortComparison()
{
Expression<Func<TestEntity, bool>> expr = e => e.Level == 3;
Assert.Equal("e => (e.Level == 3)", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_LongComparison()
{
Expression<Func<TestEntity, bool>> expr = e => e.Population > 1000000L;
Assert.Equal("e => (e.Population > 1000000)", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_EnumComparison()
{
Expression<Func<TestEntity, bool>> expr = e => e.AccountStatus == Status.Inactive;
Assert.Equal("e => (e.AccountStatus == 1)", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_ArrayContainsValue()
{
Expression<Func<TestEntity, bool>> expr = e => e.Scores.Contains(100);
Assert.Equal("e => e.Scores.Contains(100)", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_ArrayNotContainsValue()
{
Expression<Func<TestEntity, bool>> expr = e => !e.Scores.Contains(100);
Assert.Equal("e => (!(e.Scores.Contains(100)))", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_ArrayInValue()
{
Expression<Func<TestEntity, bool>> expr = e => e.Scores.Intersect(new [] { 100 }).Any();
Assert.Equal("e => e.Scores.Intersect(new [] { 100 }).Any()", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_ArrayNotInValue()
{
Expression<Func<TestEntity, bool>> expr = e => e.Scores.Except(new[] { 100 }).Any();
Assert.Equal("e => e.Scores.Except(new [] { 100 }).Any()", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_ArrayInValueOposite()
{
Expression<Func<TestEntity, bool>> expr = e => new[] { 100 }.Intersect(e.Scores).Any();
Assert.Equal("e => new [] { 100 }.Intersect(e.Scores).Any()", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_ArrayNotInValueOposite()
{
Expression<Func<TestEntity, bool>> expr = e => new[] { 100 }.Except(e.Scores).Any();
Assert.Equal("e => new [] { 100 }.Except(e.Scores).Any()", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_IntArrayInValueOposite()
{
Expression<Func<TestEntity, bool>> expr = e => new[] { 100 }.Intersect(e.Scores).Any();
Assert.Equal("e => new [] { 100 }.Intersect(e.Scores).Any()", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_IntArrayNotInValueOposite()
{
Expression<Func<TestEntity, bool>> expr = e => !new[] { 100 }.Intersect(e.Scores).Any();
Assert.Equal("e => (!(new [] { 100 }.Intersect(e.Scores).Any()))", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_DoubleArrayInValueOposite()
{
Expression<Func<TestEntity, bool>> expr = e => new[] { 99.99 }.Intersect(e.Salaries).Any();
Assert.Equal("e => new [] { 99.99 }.Intersect(e.Salaries).Any()", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_FloatArrayInValueOposite()
{
Expression<Func<TestEntity, bool>> expr = e => new[] { 5.5f }.Intersect(e.Heights).Any();
Assert.Equal("e => new [] { 5.5 }.Intersect(e.Heights).Any()", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_DecimalArrayInValueOposite()
{
Expression<Func<TestEntity, bool>> expr = e => new[] { 1000.75m }.Intersect(e.Balances).Any();
Assert.Equal("e => new [] { 1000.75 }.Intersect(e.Balances).Any()", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_ShortArrayInValueOposite()
{
Expression<Func<TestEntity, bool>> expr = e => new [] { (short)3 }.Intersect(e.Levels).Any();
Assert.Equal("e => new [] { 3 }.Intersect(e.Levels).Any()", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_LongArrayInValueOposite()
{
Expression<Func<TestEntity, bool>> expr = e => new [] { 1000000L }.Intersect(e.Populations).Any();
Assert.Equal("e => new [] { 1000000 }.Intersect(e.Populations).Any()", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_StringArrayInValueOposite()
{
Expression<Func<TestEntity, bool>> expr = e => new[] { "Alice", "Bob" }.Intersect(e.Names).Any();
Assert.Equal("e => (new [] { \"Alice\", \"Bob\" }).Intersect(e.Names).Any()", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_GuidArrayInValueOposite()
{
Expression<Func<TestEntity, bool>> expr = e => new[] { Guid.Parse("12345678-1234-1234-1234-123456789abc") }.Intersect(e.Ids).Any();
Assert.Equal("e => (new [] { Guid.Parse(\"12345678-1234-1234-1234-123456789abc\") }).Intersect(e.Ids).Any()", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_DateTimeArrayInValueOposite()
{
Expression<Func<TestEntity, bool>> expr = e => new[] { DateTime.Parse("2023-01-01T00:00:00.000Z") }.Intersect(e.CreatedDates).Any();
Assert.Equal("e => (new [] { DateTime.Parse(\"2023-01-01T00:00:00.000Z\") }).Intersect(e.CreatedDates).Any()", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_DateTimeOffsetArrayInValueOposite()
{
Expression<Func<TestEntity, bool>> expr = e => new[] { DateTimeOffset.Parse("2023-01-01T10:30:00.000+00:00") }.Intersect(e.UpdatedDates).Any();
Assert.Equal("e => (new [] { DateTimeOffset.Parse(\"2023-01-01T10:30:00.000+00:00\") }).Intersect(e.UpdatedDates).Any()", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_DateTimeWithRoundtripKind()
{
Expression<Func<TestEntity, bool>> expr = e =>
DateTime.Parse("2023-01-01T00:00:00.000Z", null, DateTimeStyles.RoundtripKind) > e.CreatedAt;
Assert.Equal(
"e => (DateTime.Parse(\"2023-01-01T00:00:00.000Z\", null, (System.Globalization.DateTimeStyles)128) > e.CreatedAt)",
_serializer.Serialize(expr));
}
[Fact]
public void Serializes_TimeOnlyArrayInValueOposite()
{
Expression<Func<TestEntity, bool>> expr = e => new[] { TimeOnly.Parse("12:00:00") }.Intersect(e.StartTimes).Any();
Assert.Equal("e => (new [] { TimeOnly.Parse(\"12:00:00\") }).Intersect(e.StartTimes).Any()", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_DateOnlyArrayInValueOposite()
{
Expression<Func<TestEntity, bool>> expr = e => new[] { DateOnly.Parse("2000-01-01") }.Intersect(e.BirthDates).Any();
Assert.Equal("e => (new [] { DateOnly.Parse(\"2000-01-01\") }).Intersect(e.BirthDates).Any()", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_EnumArrayInValueOposite()
{
Expression<Func<TestEntity, bool>> expr = e => new[] { Status.Active, Status.Inactive }.Intersect(e.Statuses).Any();
Assert.Equal("e => (new [] { (Radzen.Blazor.Tests.Status)0, (Radzen.Blazor.Tests.Status)1 }).Intersect(e.Statuses).Any()", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_ListContainsValue()
{
Expression<Func<TestEntity, bool>> expr = e => e.Tags.Contains("VIP");
Assert.Equal("e => e.Tags.Contains(\"VIP\")", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_ListNotContainsValue()
{
Expression<Func<TestEntity, bool>> expr = e => !e.Tags.Contains("VIP");
Assert.Equal("e => (!(e.Tags.Contains(\"VIP\")))", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_ListAnyCheck()
{
Expression<Func<TestEntity, bool>> expr = e => e.Children.Any(c => c.Age > 18);
Assert.Equal("e => e.Children.Any(c => (c.Age > 18))", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_ListNotAnyCheck()
{
Expression<Func<TestEntity, bool>> expr = e => !e.Children.Any(c => c.Age > 18);
Assert.Equal("e => (!(e.Children.Any(c => (c.Age > 18))))", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_EntitySubPropertyCheck()
{
Expression<Func<TestEntity, bool>> expr = e => e.Address.City == "New York";
Assert.Equal("e => (e.Address.City == \"New York\")", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_ComplexExpressionWithProperties()
{
Expression<Func<TestEntity, bool>> expr = e => e.Age > 18 && e.Tags.Contains("Member") || e.Address.City == "London";
Assert.Equal("e => (((e.Age > 18) && e.Tags.Contains(\"Member\")) || (e.Address.City == \"London\"))", _serializer.Serialize(expr));
}
[Fact]
public void Serializes_NotContains()
{
Expression<Func<TestEntity, bool>> expr = e => !e.Tags.Contains("Member");
Assert.Equal("e => (!(e.Tags.Contains(\"Member\")))", _serializer.Serialize(expr));
}
}
}

View File

@@ -184,13 +184,13 @@ namespace Radzen.Blazor.Tests
Assert.Contains("SummaryContent", component.Markup);
Assert.Equal(
"",
component.Find(".rz-fieldset-content-summary").ParentElement.Attributes.First(attr => attr.Name == "style").Value
"false",
component.Find(".rz-fieldset-content-summary").ParentElement.ParentElement.Attributes.First(attr => attr.Name == "aria-hidden").Value
);
}
[Fact]
public void Fieldset_DontRenders_SummaryWhenOpen()
public void Fieldset_DoesNotRender_SummaryWhenOpen()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenFieldset>();
@@ -210,8 +210,8 @@ namespace Radzen.Blazor.Tests
Assert.Contains("SummaryContent", component.Markup);
Assert.Equal(
"display: none",
component.Find(".rz-fieldset-content-summary").ParentElement.Attributes.First(attr => attr.Name == "style").Value
"true",
component.Find(".rz-fieldset-content-summary").ParentElement.ParentElement.Attributes.First(attr => attr.Name == "aria-hidden").Value
);
}
}

View File

@@ -0,0 +1,182 @@
using Bunit;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class FileInputTests
{
[Fact]
public void FileInput_Renders_WithClassName()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenFileInput<string>>();
Assert.Contains(@"rz-fileupload", component.Markup);
}
[Fact]
public void FileInput_Renders_ChooseButton()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenFileInput<string>>();
Assert.Contains("rz-fileupload-choose", component.Markup);
Assert.Contains("rz-button", component.Markup);
}
[Fact]
public void FileInput_Renders_ChooseText()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenFileInput<string>>(parameters =>
{
parameters.Add(p => p.ChooseText, "Select File");
});
Assert.Contains("Select File", component.Markup);
}
[Fact]
public void FileInput_Renders_DefaultChooseText()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenFileInput<string>>();
Assert.Contains("Choose", component.Markup);
}
[Fact]
public void FileInput_Renders_Disabled()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenFileInput<string>>(parameters =>
{
parameters.Add(p => p.Disabled, true);
});
Assert.Contains("rz-state-disabled", component.Markup);
Assert.Contains("disabled", component.Markup);
}
[Fact]
public void FileInput_Renders_Accept()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenFileInput<string>>(parameters =>
{
parameters.Add(p => p.Accept, "application/pdf");
});
Assert.Contains("accept=\"application/pdf\"", component.Markup);
}
[Fact]
public void FileInput_Renders_DefaultAccept()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenFileInput<string>>();
Assert.Contains("accept=\"image/*\"", component.Markup);
}
[Fact]
public void FileInput_Renders_FileInputElement()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenFileInput<string>>();
Assert.Contains("type=\"file\"", component.Markup);
}
[Fact]
public void FileInput_Renders_Title_WhenSet()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenFileInput<string>>(parameters =>
{
parameters.Add(p => p.Title, "MyDocument.pdf");
parameters.Add(p => p.Value, "data:application/pdf;base64,test");
});
Assert.Contains("MyDocument.pdf", component.Markup);
}
[Fact]
public void FileInput_Renders_FileName_WhenTitleNotSet()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenFileInput<string>>(parameters =>
{
parameters.Add(p => p.FileName, "document.pdf");
parameters.Add(p => p.Value, "data:application/pdf;base64,test");
});
Assert.Contains("document.pdf", component.Markup);
}
[Fact]
public void FileInput_Renders_DeleteButton_WhenValueSet()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenFileInput<string>>(parameters =>
{
parameters.Add(p => p.Value, "data:text/plain;base64,test");
});
Assert.Contains("rz-icon-trash", component.Markup);
}
[Fact]
public void FileInput_Renders_CustomDeleteText()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenFileInput<string>>(parameters =>
{
parameters.Add(p => p.DeleteText, "Remove File");
parameters.Add(p => p.Value, "data:text/plain;base64,test");
});
Assert.Contains("title=\"Remove File\"", component.Markup);
}
[Fact]
public void FileInput_Renders_ImagePreview_ForImageFile()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenFileInput<string>>(parameters =>
{
parameters.Add(p => p.Value, "data:image/png;base64,test");
});
Assert.Contains("<img", component.Markup);
}
[Fact]
public void FileInput_Renders_ImageAlternateText()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenFileInput<string>>(parameters =>
{
parameters.Add(p => p.ImageAlternateText, "User Photo");
parameters.Add(p => p.Value, "data:image/png;base64,test");
});
Assert.Contains("alt=\"User Photo\"", component.Markup);
}
}
}

View File

@@ -0,0 +1,163 @@
using Bunit;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class FormFieldTests
{
[Fact]
public void FormField_Renders_WithClassName()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenFormField>();
Assert.Contains(@"rz-form-field", component.Markup);
}
[Fact]
public void FormField_Renders_Text()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenFormField>(parameters =>
{
parameters.Add(p => p.Text, "Email Address");
});
Assert.Contains("Email Address", component.Markup);
Assert.Contains("rz-form-field-label", component.Markup);
}
[Fact]
public void FormField_Renders_Variant_Outlined()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenFormField>(parameters =>
{
parameters.Add(p => p.Variant, Variant.Outlined);
});
Assert.Contains("rz-variant-outlined", component.Markup);
}
[Fact]
public void FormField_Renders_Variant_Filled()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenFormField>(parameters =>
{
parameters.Add(p => p.Variant, Variant.Filled);
});
Assert.Contains("rz-variant-filled", component.Markup);
}
[Fact]
public void FormField_Renders_Variant_Flat()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenFormField>(parameters =>
{
parameters.Add(p => p.Variant, Variant.Flat);
});
Assert.Contains("rz-variant-flat", component.Markup);
}
[Fact]
public void FormField_Renders_Variant_Text()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenFormField>(parameters =>
{
parameters.Add(p => p.Variant, Variant.Text);
});
Assert.Contains("rz-variant-text", component.Markup);
}
[Fact]
public void FormField_Renders_AllowFloatingLabel_True()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenFormField>(parameters =>
{
parameters.Add(p => p.AllowFloatingLabel, true);
});
Assert.Contains("rz-floating-label", component.Markup);
}
[Fact]
public void FormField_Renders_AllowFloatingLabel_False()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenFormField>(parameters =>
{
parameters.Add(p => p.AllowFloatingLabel, false);
});
Assert.DoesNotContain("rz-floating-label", component.Markup);
}
[Fact]
public void FormField_Renders_Component_Attribute()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenFormField>(parameters =>
{
parameters.Add(p => p.Component, "email-input");
});
Assert.Contains("for=\"email-input\"", component.Markup);
}
[Fact]
public void FormField_Renders_Helper()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenFormField>(parameters =>
{
parameters.Add(p => p.Helper, builder => builder.AddContent(0, "Enter your email address"));
});
Assert.Contains("rz-form-field-helper", component.Markup);
Assert.Contains("Enter your email address", component.Markup);
}
[Fact]
public void FormField_Renders_Start()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenFormField>(parameters =>
{
parameters.Add(p => p.Start, builder => builder.AddMarkupContent(0, "<span>Start</span>"));
});
Assert.Contains("rz-form-field-start", component.Markup);
Assert.Contains("Start", component.Markup);
}
[Fact]
public void FormField_Renders_End()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenFormField>(parameters =>
{
parameters.Add(p => p.End, builder => builder.AddMarkupContent(0, "<span>End</span>"));
});
Assert.Contains("rz-form-field-end", component.Markup);
Assert.Contains("End", component.Markup);
}
[Fact]
public void FormField_Renders_FormFieldContent()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenFormField>();
Assert.Contains("rz-form-field-content", component.Markup);
}
}
}

View File

@@ -0,0 +1,103 @@
using Bunit;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class HeadingTests
{
[Fact]
public void Heading_Renders_WithClassName()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenHeading>();
Assert.Contains(@"rz-heading", component.Markup);
}
[Fact]
public void Heading_Renders_TextParameter()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenHeading>();
var text = "Test Heading";
component.SetParametersAndRender(parameters => parameters.Add(p => p.Text, text));
Assert.Contains(text, component.Markup);
}
[Fact]
public void Heading_Renders_H1_ByDefault()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenHeading>();
var text = "Heading Text";
component.SetParametersAndRender(parameters => parameters.Add(p => p.Text, text));
Assert.Contains("<h1", component.Markup);
}
[Fact]
public void Heading_Renders_H2_Size()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenHeading>();
var text = "Heading 2";
component.SetParametersAndRender(parameters =>
{
parameters.Add(p => p.Text, text);
parameters.Add(p => p.Size, "H2");
});
Assert.Contains("<h2", component.Markup);
Assert.Contains(text, component.Markup);
}
[Fact]
public void Heading_Renders_H3_Size()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenHeading>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.Size, "H3"));
Assert.Contains("<h3", component.Markup);
}
[Fact]
public void Heading_Renders_H4_Size()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenHeading>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.Size, "H4"));
Assert.Contains("<h4", component.Markup);
}
[Fact]
public void Heading_Renders_H5_Size()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenHeading>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.Size, "H5"));
Assert.Contains("<h5", component.Markup);
}
[Fact]
public void Heading_Renders_H6_Size()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenHeading>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.Size, "H6"));
Assert.Contains("<h6", component.Markup);
}
}
}

View File

@@ -0,0 +1,104 @@
using Bunit;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class HtmlEditorTests
{
[Fact]
public void HtmlEditor_Renders_WithClassName()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.Services.AddScoped<DialogService>();
var component = ctx.RenderComponent<RadzenHtmlEditor>();
Assert.Contains(@"rz-html-editor", component.Markup);
}
[Fact]
public void HtmlEditor_Renders_ShowToolbar_True()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.Services.AddScoped<DialogService>();
var component = ctx.RenderComponent<RadzenHtmlEditor>(parameters =>
{
parameters.Add(p => p.ShowToolbar, true);
});
Assert.Contains("rz-html-editor-toolbar", component.Markup);
}
[Fact]
public void HtmlEditor_Renders_ShowToolbar_False()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.Services.AddScoped<DialogService>();
var component = ctx.RenderComponent<RadzenHtmlEditor>(parameters =>
{
parameters.Add(p => p.ShowToolbar, false);
});
Assert.DoesNotContain("rz-html-editor-toolbar", component.Markup);
}
[Fact]
public void HtmlEditor_Renders_Mode_Design()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.Services.AddScoped<DialogService>();
var component = ctx.RenderComponent<RadzenHtmlEditor>(parameters =>
{
parameters.Add(p => p.Mode, HtmlEditorMode.Design);
});
// Design mode shows the content editable div
Assert.Contains("contenteditable", component.Markup);
}
[Fact]
public void HtmlEditor_Renders_Mode_Source()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.Services.AddScoped<DialogService>();
var component = ctx.RenderComponent<RadzenHtmlEditor>(parameters =>
{
parameters.Add(p => p.Mode, HtmlEditorMode.Source);
});
// Source mode shows the textarea for HTML editing
Assert.Contains("rz-html-editor-source", component.Markup);
}
[Fact]
public void HtmlEditor_Renders_Disabled_Attribute()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.Services.AddScoped<DialogService>();
var component = ctx.RenderComponent<RadzenHtmlEditor>(parameters =>
{
parameters.Add(p => p.Disabled, true);
});
Assert.Contains("disabled", component.Markup);
}
[Fact]
public void HtmlEditor_Renders_ContentArea()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.Services.AddScoped<DialogService>();
var component = ctx.RenderComponent<RadzenHtmlEditor>();
Assert.Contains("rz-html-editor-content", component.Markup);
}
}
}

View File

@@ -0,0 +1,197 @@
using Bunit;
using Xunit;
using System.Collections.Generic;
namespace Radzen.Blazor.Tests
{
public class ListBoxTests
{
class Item
{
public int Id { get; set; }
public string Name { get; set; }
}
[Fact]
public void ListBox_Renders_WithClassName()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenListBox<int>>();
Assert.Contains(@"rz-listbox", component.Markup);
}
[Fact]
public void ListBox_Renders_WithData_SimpleList()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var data = new List<string> { "Apple", "Banana", "Cherry" };
var component = ctx.RenderComponent<RadzenListBox<string>>(parameters =>
{
parameters.Add(p => p.Data, data);
});
Assert.Contains("Apple", component.Markup);
Assert.Contains("Banana", component.Markup);
Assert.Contains("Cherry", component.Markup);
}
[Fact]
public void ListBox_Renders_WithData_CustomTextValueProperties()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var data = new List<Item>
{
new Item { Id = 1, Name = "First Item" },
new Item { Id = 2, Name = "Second Item" }
};
var component = ctx.RenderComponent<RadzenListBox<int>>(parameters =>
{
parameters.Add(p => p.Data, data);
parameters.Add(p => p.TextProperty, "Name");
parameters.Add(p => p.ValueProperty, "Id");
});
Assert.Contains("First Item", component.Markup);
Assert.Contains("Second Item", component.Markup);
}
[Fact]
public void ListBox_Renders_AllowFiltering()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenListBox<int>>(parameters =>
{
parameters.Add(p => p.AllowFiltering, true);
parameters.Add(p => p.Data, new List<string> { "Item1", "Item2" });
});
Assert.Contains("rz-listbox-filter", component.Markup);
Assert.Contains("rz-listbox-header", component.Markup);
}
[Fact]
public void ListBox_Renders_Disabled_Attribute()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenListBox<int>>(parameters =>
{
parameters.Add(p => p.Disabled, true);
});
Assert.Contains("disabled", component.Markup);
}
[Fact]
public void ListBox_Renders_Multiple_WithSelectAll()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenListBox<IEnumerable<int>>>(parameters =>
{
parameters.Add(p => p.Multiple, true);
parameters.Add(p => p.SelectAllText, "Select All Items");
parameters.Add(p => p.Data, new List<string> { "Item1", "Item2" });
});
Assert.Contains("Select All Items", component.Markup);
Assert.Contains("rz-chkbox", component.Markup);
}
[Fact]
public void ListBox_Renders_FilterPlaceholder()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenListBox<int>>(parameters =>
{
parameters.Add(p => p.Placeholder, "Select an item");
parameters.Add(p => p.AllowFiltering, true);
parameters.Add(p => p.Data, new List<string> { "Item1", "Item2" });
});
Assert.Contains("Select an item", component.Markup);
}
[Fact]
public void ListBox_Renders_Multiple_WithCheckboxes()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var data = new List<string> { "Item1", "Item2" };
var component = ctx.RenderComponent<RadzenListBox<IEnumerable<string>>>(parameters =>
{
parameters.Add(p => p.Multiple, true);
parameters.Add(p => p.Data, data);
});
// Multiple selection shows checkboxes in header
Assert.Contains("rz-listbox-header-w-checkbox", component.Markup);
}
[Fact]
public void ListBox_Renders_ReadOnly()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenListBox<int>>(parameters =>
{
parameters.Add(p => p.ReadOnly, true);
});
Assert.Contains("readonly", component.Markup);
}
[Fact]
public void ListBox_Renders_TabIndex()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenListBox<int>>();
Assert.Contains("tabindex=", component.Markup);
}
[Fact]
public void ListBox_Renders_ListWrapper()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var data = new List<string> { "Item1" };
var component = ctx.RenderComponent<RadzenListBox<string>>(parameters =>
{
parameters.Add(p => p.Data, data);
});
Assert.Contains("rz-listbox-list-wrapper", component.Markup);
Assert.Contains("rz-listbox-list", component.Markup);
}
[Fact]
public void ListBox_Renders_SearchAriaLabel()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenListBox<int>>(parameters =>
{
parameters.Add(p => p.AllowFiltering, true);
parameters.Add(p => p.SearchAriaLabel, "Search items");
parameters.Add(p => p.Data, new List<string> { "Item1" });
});
Assert.Contains("aria-label=\"Search items\"", component.Markup);
}
}
}

View 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>&gt; # Foo
&gt; bar
&gt; 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));
}
}

View 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>&lt;https://foo.bar.</code>
<text>baz&gt;</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));
}
}

View File

@@ -0,0 +1,272 @@
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
baz",@"<document>
<paragraph>
<strong>
<text>foo</text>
</strong>
<text> bar</text>
<softbreak />
<text>baz</text>
</paragraph>
</document>")]
[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));
}
}

View 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>&lt;
&gt;
</code_block>
</document>")]
[InlineData(@"~~~
<
>
~~~", @"<document>
<code_block>&lt;
&gt;
</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));
}
}

View 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>&lt;a href=""foo
bar""&gt;</html_inline>
</paragraph>
</document>")]
[InlineData(@"<a href=""foo\
bar"">", @"<document>
<paragraph>
<html_inline>&lt;a href=""foo\
bar""&gt;</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));
}
}

View 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>&lt;a title=""a lot</text>
</heading>
<paragraph>
<text>of dashes""/&gt;</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>&gt; 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));
}
}

View 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>&lt;table&gt;&lt;tr&gt;&lt;td&gt;
&lt;pre&gt;
**Hello**,</html_block>
<paragraph>
<emph>
<text>world</text>
</emph>
<text>.</text>
<softbreak />
<html_inline>&lt;/pre&gt;</html_inline>
</paragraph>
<html_block>&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;</html_block>
</document>")]
[InlineData(@"<table>
<tr>
<td>
hi
</td>
</tr>
</table>
okay.", @"<document>
<html_block>&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;
hi
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;</html_block>
<paragraph>
<text>okay.</text>
</paragraph>
</document>")]
[InlineData(@" <div>
*hello*
<foo><a>", @"<document>
<html_block> &lt;div&gt;
*hello*
&lt;foo&gt;&lt;a&gt;</html_block>
</document>")]
[InlineData(@"</div>
*foo*", @"<document>
<html_block>&lt;/div&gt;
*foo*</html_block>
</document>")]
[InlineData(@"<DIV CLASS=""foo"">
*Markdown*
</DIV>", @"<document>
<html_block>&lt;DIV CLASS=""foo""&gt;</html_block>
<paragraph>
<emph>
<text>Markdown</text>
</emph>
</paragraph>
<html_block>&lt;/DIV&gt;</html_block>
</document>")]
[InlineData(@"<div id=""foo""
class=""bar"">
</div>", @"<document>
<html_block>&lt;div id=""foo""
class=""bar""&gt;
&lt;/div&gt;</html_block>
</document>")]
[InlineData(@"<div id=""foo"" class=""bar
baz"">
</div>", @"<document>
<html_block>&lt;div id=""foo"" class=""bar
baz""&gt;
&lt;/div&gt;</html_block>
</document>")]
[InlineData(@"<div>
*foo*
*bar*", @"<document>
<html_block>&lt;div&gt;
*foo*</html_block>
<paragraph>
<emph>
<text>bar</text>
</emph>
</paragraph>
</document>")]
[InlineData(@"<div id=""foo""
*hi*", @"<document>
<html_block>&lt;div id=""foo""
*hi*</html_block>
</document>")]
[InlineData(@"<div class
foo", @"<document>
<html_block>&lt;div class
foo</html_block>
</document>")]
[InlineData(@"<div *???-&&&-<---
*foo*", @"<document>
<html_block>&lt;div *???-&amp;&amp;&amp;-&lt;---
*foo*</html_block>
</document>")]
[InlineData(@"<div><a href=""bar"">*foo*</a></div>", @"<document>
<html_block>&lt;div&gt;&lt;a href=""bar""&gt;*foo*&lt;/a&gt;&lt;/div&gt;</html_block>
</document>")]
[InlineData(@"<table><tr><td>
foo
</td></tr></table>", @"<document>
<html_block>&lt;table&gt;&lt;tr&gt;&lt;td&gt;
foo
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;</html_block>
</document>")]
[InlineData(@"<div></div>
``` c
int x = 33;
```", @"<document>
<html_block>&lt;div&gt;&lt;/div&gt;
``` c
int x = 33;
```</html_block>
</document>")]
[InlineData(@"<a href=""foo"">
*bar*
</a>", @"<document>
<html_block>&lt;a href=""foo""&gt;
*bar*
&lt;/a&gt;</html_block>
</document>")]
[InlineData(@"<Warning>
*bar*
</Warning>", @"<document>
<html_block>&lt;Warning&gt;
*bar*
&lt;/Warning&gt;</html_block>
</document>")]
[InlineData(@"<i class=""foo"">
*bar*
</i>", @"<document>
<html_block>&lt;i class=""foo""&gt;
*bar*
&lt;/i&gt;</html_block>
</document>")]
[InlineData(@"</ins>
*bar*", @"<document>
<html_block>&lt;/ins&gt;
*bar*</html_block>
</document>")]
[InlineData(@"<del>
*foo*
</del>", @"<document>
<html_block>&lt;del&gt;
*foo*
&lt;/del&gt;</html_block>
</document>")]
[InlineData(@"<del>
*foo*
</del>", @"<document>
<html_block>&lt;del&gt;</html_block>
<paragraph>
<emph>
<text>foo</text>
</emph>
</paragraph>
<html_block>&lt;/del&gt;</html_block>
</document>")]
[InlineData(@"<del>*foo*</del>", @"<document>
<paragraph>
<html_inline>&lt;del&gt;</html_inline>
<emph>
<text>foo</text>
</emph>
<html_inline>&lt;/del&gt;</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>&lt;pre language=""haskell""&gt;&lt;code&gt;
import Text.HTML.TagSoup
main :: IO ()
main = print $ parseTags tags
&lt;/code&gt;&lt;/pre&gt;</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>&lt;script type=""text/javascript""&gt;
// JavaScript example
document.getElementById(""demo"").innerHTML = ""Hello JavaScript!"";
&lt;/script&gt;</html_block>
<paragraph>
<text>okay</text>
</paragraph>
</document>")]
[InlineData(@"<textarea>
*foo*
_bar_
</textarea>", @"<document>
<html_block>&lt;textarea&gt;
*foo*
_bar_
&lt;/textarea&gt;</html_block>
</document>")]
[InlineData(@"<style
type=""text/css"">
h1 {color:red;}
p {color:blue;}
</style>
okay",@"<document>
<html_block>&lt;style
type=""text/css""&gt;
h1 {color:red;}
p {color:blue;}
&lt;/style&gt;</html_block>
<paragraph>
<text>okay</text>
</paragraph>
</document>")]
[InlineData(@"<style
type=""text/css"">
foo", @"<document>
<html_block>&lt;style
type=""text/css""&gt;
foo</html_block>
</document>")]
[InlineData(@"> <div>
> foo
bar", @"<document>
<block_quote>
<html_block>&lt;div&gt;
foo</html_block>
</block_quote>
<paragraph>
<text>bar</text>
</paragraph>
</document>")]
[InlineData(@"- <div>
- foo", @"<document>
<list type=""bullet"" tight=""true"">
<item>
<html_block>&lt;div&gt;</html_block>
</item>
<item>
<paragraph>
<text>foo</text>
</paragraph>
</item>
</list>
</document>")]
[InlineData(@"<style>p{color:red;}</style>
*foo*", @"<document>
<html_block>&lt;style&gt;p{color:red;}&lt;/style&gt;</html_block>
<paragraph>
<emph>
<text>foo</text>
</emph>
</paragraph>
</document>")]
[InlineData(@"<!-- foo -->*bar*
*baz*", @"<document>
<html_block>&lt;!-- foo --&gt;*bar*</html_block>
<paragraph>
<emph>
<text>baz</text>
</emph>
</paragraph>
</document>")]
[InlineData(@"<script>
foo
</script>1. *bar*", @"<document>
<html_block>&lt;script&gt;
foo
&lt;/script&gt;1. *bar*</html_block>
</document>")]
[InlineData(@"<!-- Foo
bar
baz -->
okay", @"<document>
<html_block>&lt;!-- Foo
bar
baz --&gt;</html_block>
<paragraph>
<text>okay</text>
</paragraph>
</document>")]
[InlineData(@"<?php
echo '>';
?>
okay", @"<document>
<html_block>&lt;?php
echo '&gt;';
?&gt;</html_block>
<paragraph>
<text>okay</text>
</paragraph>
</document>")]
[InlineData(@"<!DOCTYPE html>", @"<document>
<html_block>&lt;!DOCTYPE html&gt;</html_block>
</document>")]
[InlineData(@"<![CDATA[
function matchwo(a,b)
{
if (a < b && a < 0) then {
return 1;
} else {
return 0;
}
}
]]>
okay", @"<document>
<html_block>&lt;![CDATA[
function matchwo(a,b)
{
if (a &lt; b &amp;&amp; a &lt; 0) then {
return 1;
} else {
return 0;
}
}
]]&gt;</html_block>
<paragraph>
<text>okay</text>
</paragraph>
</document>")]
[InlineData(@" <!-- foo -->
<!-- foo -->", @"<document>
<html_block> &lt;!-- foo --&gt;</html_block>
<code_block>&lt;!-- foo --&gt;
</code_block>
</document>")]
[InlineData(@" <div>
<div>", @"<document>
<html_block> &lt;div&gt;</html_block>
<code_block>&lt;div&gt;
</code_block>
</document>")]
[InlineData(@"Foo
<div>
bar
</div>", @"<document>
<paragraph>
<text>Foo</text>
</paragraph>
<html_block>&lt;div&gt;
bar
&lt;/div&gt;</html_block>
</document>")]
[InlineData(@"<div>
bar
</div>
*foo*", @"<document>
<html_block>&lt;div&gt;
bar
&lt;/div&gt;
*foo*</html_block>
</document>")]
[InlineData(@"Foo
<a href=""bar"">
baz", @"<document>
<paragraph>
<text>Foo</text>
<softbreak />
<html_inline>&lt;a href=""bar""&gt;</html_inline>
<softbreak />
<text>baz</text>
</paragraph>
</document>")]
[InlineData(@"<div>
*Emphasized* text.
</div>", @"<document>
<html_block>&lt;div&gt;</html_block>
<paragraph>
<emph>
<text>Emphasized</text>
</emph>
<text> text.</text>
</paragraph>
<html_block>&lt;/div&gt;</html_block>
</document>")]
[InlineData(@"<div>
*Emphasized* text.
</div>", @"<document>
<html_block>&lt;div&gt;
*Emphasized* text.
&lt;/div&gt;</html_block>
</document>")]
[InlineData(@"<table>
<tr>
<td>
Hi
</td>
</tr>
</table>", @"<document>
<html_block>&lt;table&gt;</html_block>
<html_block>&lt;tr&gt;</html_block>
<html_block>&lt;td&gt;
Hi
&lt;/td&gt;</html_block>
<html_block>&lt;/tr&gt;</html_block>
<html_block>&lt;/table&gt;</html_block>
</document>")]
[InlineData(@"<table>
<tr>
<td>
Hi
</td>
</tr>
</table>", @"<document>
<html_block>&lt;table&gt;</html_block>
<html_block> &lt;tr&gt;</html_block>
<code_block>&lt;td&gt;
Hi
&lt;/td&gt;
</code_block>
<html_block> &lt;/tr&gt;</html_block>
<html_block>&lt;/table&gt;</html_block>
</document>")]
public void Parse_HtmlBlock(string markdown, string expected)
{
Assert.Equal(expected, ToXml(markdown));
}
}

View File

@@ -0,0 +1,168 @@
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>&lt;a&gt;</html_inline>
<html_inline>&lt;bab&gt;</html_inline>
<html_inline>&lt;c2c&gt;</html_inline>
</paragraph>
</document>")]
[InlineData(@"<a/><b2/>", @"<document>
<paragraph>
<html_inline>&lt;a/&gt;</html_inline>
<html_inline>&lt;b2/&gt;</html_inline>
</paragraph>
</document>")]
[InlineData(@"<a /><b2
data=""foo"" >", @"<document>
<paragraph>
<html_inline>&lt;a /&gt;</html_inline>
<html_inline>&lt;b2
data=""foo"" &gt;</html_inline>
</paragraph>
</document>")]
[InlineData(@"<a foo=""bar"" bam = 'baz <em>""</em>'
_boolean zoop:33=zoop:33 />", @"<document>
<paragraph>
<html_inline>&lt;a foo=""bar"" bam = 'baz &lt;em&gt;""&lt;/em&gt;'
_boolean zoop:33=zoop:33 /&gt;</html_inline>
</paragraph>
</document>")]
[InlineData(@"Foo <responsive-image src=""foo.jpg"" />", @"<document>
<paragraph>
<text>Foo </text>
<html_inline>&lt;responsive-image src=""foo.jpg"" /&gt;</html_inline>
</paragraph>
</document>")]
[InlineData("<33> <__>", @"<document>
<paragraph>
<text>&lt;33&gt; &lt;</text>
<text>__</text>
<text>&gt;</text>
</paragraph>
</document>")]
[InlineData(@"<a h*#ref=""hi"">", @"<document>
<paragraph>
<text>&lt;a h</text>
<text>*</text>
<text>#ref=""hi""&gt;</text>
</paragraph>
</document>")]
[InlineData(@"<a href='bar'title=title>", @"<document>
<paragraph>
<text>&lt;a href='bar'title=title&gt;</text>
</paragraph>
</document>")]
[InlineData(@"</a></foo >", @"<document>
<paragraph>
<html_inline>&lt;/a&gt;</html_inline>
<html_inline>&lt;/foo &gt;</html_inline>
</paragraph>
</document>")]
[InlineData(@"</a href=""foo"">", @"<document>
<paragraph>
<text>&lt;/a href=""foo""&gt;</text>
</paragraph>
</document>")]
[InlineData(@"foo <!-- this is a --
comment - with hyphens -->", @"<document>
<paragraph>
<text>foo </text>
<html_inline>&lt;!-- this is a --
comment - with hyphens --&gt;</html_inline>
</paragraph>
</document>")]
[InlineData(@"foo <!--> foo -->
foo <!---> foo -->
", @"<document>
<paragraph>
<text>foo </text>
<html_inline>&lt;!--&gt;</html_inline>
<text> foo --&gt;</text>
</paragraph>
<paragraph>
<text>foo </text>
<html_inline>&lt;!---&gt;</html_inline>
<text> foo --&gt;</text>
</paragraph>
</document>")]
[InlineData(@"foo <?php echo $a; ?>", @"<document>
<paragraph>
<text>foo </text>
<html_inline>&lt;?php echo $a; ?&gt;</html_inline>
</paragraph>
</document>")]
[InlineData(@"foo <!ELEMENT br EMPTY>", @"<document>
<paragraph>
<text>foo </text>
<html_inline>&lt;!ELEMENT br EMPTY&gt;</html_inline>
</paragraph>
</document>")]
[InlineData(@"foo <![CDATA[>&<]]>", @"<document>
<paragraph>
<text>foo </text>
<html_inline>&lt;![CDATA[&gt;&amp;&lt;]]&gt;</html_inline>
</paragraph>
</document>")]
[InlineData(@"foo <a href=""&ouml;"">", @"<document>
<paragraph>
<text>foo </text>
<html_inline>&lt;a href=""&amp;ouml;""&gt;</html_inline>
</paragraph>
</document>")]
[InlineData(@"foo <a href=""\*"">", @"<document>
<paragraph>
<text>foo </text>
<html_inline>&lt;a href=""\*""&gt;</html_inline>
</paragraph>
</document>")]
[InlineData(@"foo <a href=""\"""">", @"<document>
<paragraph>
<text>foo &lt;a href=""</text>
<text>""</text>
<text>""&gt;</text>
</paragraph>
</document>")]
public void Parse_Html(string markdown, string expected)
{
Assert.Equal(expected, ToXml(markdown));
}
[Theory]
[InlineData("***foo bar***", @"<document>
<paragraph>
<emph>
<strong>
<text>foo bar</text>
</strong>
</emph>
</paragraph>
</document>")]
[InlineData("___foo bar___", @"<document>
<paragraph>
<emph>
<strong>
<text>foo bar</text>
</strong>
</emph>
</paragraph>
</document>")]
public void Parse_BoldAndItalic_ShouldNotThrowException(string markdown, string expected)
{
Assert.Equal(expected, ToXml(markdown));
}
}

View File

@@ -0,0 +1,85 @@
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(@"![foo](/url ""title"")", @"<document>
<paragraph>
<image destination=""/url"" title=""title"">
<text>foo</text>
</image>
</paragraph>
</document>")]
[InlineData(@"![foo ![bar](/url)](/url2)", @"<document>
<paragraph>
<image destination=""/url2"" title="""">
<text>foo </text>
<image destination=""/url"" title="""">
<text>bar</text>
</image>
</image>
</paragraph>
</document>")]
[InlineData(@"![foo [bar](/url)](/url2)", @"<document>
<paragraph>
<image destination=""/url2"" title="""">
<text>foo </text>
<link destination=""/url"" title="""">
<text>bar</text>
</link>
</image>
</paragraph>
</document>")]
[InlineData(@"![foo](train.jpg)", @"<document>
<paragraph>
<image destination=""train.jpg"" title="""">
<text>foo</text>
</image>
</paragraph>
</document>")]
[InlineData(@"My ![foo bar](/path/to/train.jpg ""title"" )", @"<document>
<paragraph>
<text>My </text>
<image destination=""/path/to/train.jpg"" title=""title"">
<text>foo bar</text>
</image>
</paragraph>
</document>")]
[InlineData(@"![foo](<url>)", @"<document>
<paragraph>
<image destination=""url"" title="""">
<text>foo</text>
</image>
</paragraph>
</document>")]
[InlineData(@"![](/url)", @"<document>
<paragraph>
<image destination=""/url"" title="""" />
</paragraph>
</document>")]
[InlineData(@"__Applications__ ![macOS DMG](/assets/img/macOS-drag-and-drop.png)", @"<document>
<paragraph>
<strong>
<text>Applications</text>
</strong>
<text> </text>
<image destination=""/assets/img/macOS-drag-and-drop.png"" title="""">
<text>macOS DMG</text>
</image>
</paragraph>
</document>")]
public void Parse_BasicImages_ReturnsImageElement(string markdown, string expected)
{
Assert.Equal(expected, ToXml(markdown));
}
}

View 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>&lt;a/&gt;
*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));
}
}

View File

@@ -0,0 +1,852 @@
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>](&lt;foo&gt;)</text>
</paragraph>
</document>")]
[InlineData(@"[a](<b)c
[a](<b)c>
[a](<b>c)", @"<document>
<paragraph>
<text>[</text>
<text>a</text>
<text>](&lt;b)c</text>
<softbreak />
<text>[</text>
<text>a</text>
<text>](&lt;b)c&gt;</text>
<softbreak />
<text>[</text>
<text>a</text>
<text>](</text>
<html_inline>&lt;b&gt;</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=""&quot;title&quot;"" 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 &quot;and&quot; 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>")]
[InlineData(@"[![alt](img)](url)", @"<document>
<paragraph>
<link destination=""url"" title="""">
<image destination=""img"" title="""">
<text>alt</text>
</image>
</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&amp;id=22&amp;boolean"" title="""">
<text>https://foo.bar.baz/test?q=hello&amp;id=22&amp;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>&lt;https://foo.bar/baz bim&gt;</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>&lt;foo+@bar.example.com&gt;</text>
</paragraph>
</document>")]
[InlineData(@"<>", @"<document>
<paragraph>
<text>&lt;&gt;</text>
</paragraph>
</document>")]
[InlineData(@"< https://foo.bar >", @"<document>
<paragraph>
<text>&lt; https://foo.bar &gt;</text>
</paragraph>
</document>")]
[InlineData(@"<m:abc>", @"<document>
<paragraph>
<text>&lt;m:abc&gt;</text>
</paragraph>
</document>")]
[InlineData(@"<foo.bar.baz>", @"<document>
<paragraph>
<text>&lt;foo.bar.baz&gt;</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=""&#xA;title&#xA;line1&#xA;line2&#xA;"">
<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>&lt;bar&gt;</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\&quot;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));
}
}

View 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
&gt; 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> &gt; 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));
}
}

View 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>&lt;!-- --&gt;</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>&lt;!-- --&gt;</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"));
}
}

View 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));
}
}

View 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));
}
}

View 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));
}
}

View File

@@ -0,0 +1,414 @@
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>")]
[InlineData(@"| abc | def |
| --- | --- |
c:\\foo", @"<document>
<table>
<header>
<cell>
<text>abc</text>
</cell>
<cell>
<text>def</text>
</cell>
</header>
<row>
<cell>
<text>c:</text>
<text>\</text>
<text>foo</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>&lt;div&gt;
&lt;/div&gt;</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));
}
}

View 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));
}
}

View 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();
}
}

View File

@@ -0,0 +1,65 @@
using Bunit;
using Microsoft.AspNetCore.Components;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class MediaQueryTests
{
[Fact]
public void MediaQuery_Renders()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenMediaQuery>(parameters =>
{
parameters.Add(p => p.Query, "(max-width: 768px)");
});
Assert.NotNull(component.Instance);
}
[Fact]
public void MediaQuery_HasQueryParameter()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var query = "(max-width: 1024px)";
var component = ctx.RenderComponent<RadzenMediaQuery>(parameters =>
{
parameters.Add(p => p.Query, query);
});
Assert.Equal(query, component.Instance.Query);
}
[Fact]
public void MediaQuery_InvokesChangeCallback()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
bool changeInvoked = false;
bool matchResult = false;
var component = ctx.RenderComponent<RadzenMediaQuery>(parameters =>
{
parameters.Add(p => p.Query, "(max-width: 768px)");
parameters.Add(p => p.Change, EventCallback.Factory.Create<bool>(this, (matches) =>
{
changeInvoked = true;
matchResult = matches;
}));
});
// Invoke the JSInvokable method directly
component.Instance.OnChange(true);
Assert.True(changeInvoked);
Assert.True(matchResult);
}
}
}

View File

@@ -0,0 +1,58 @@
using Bunit;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class MenuTests
{
[Fact]
public void Menu_Renders_WithClassName()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenMenu>();
Assert.Contains(@"rz-menu", component.Markup);
}
[Fact]
public void Menu_Renders_Responsive_True()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenMenu>(parameters =>
{
parameters.Add(p => p.Responsive, true);
});
Assert.Contains("rz-menu-closed", component.Markup);
Assert.Contains("rz-menu-toggle", component.Markup);
}
[Fact]
public void Menu_Renders_Responsive_False()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenMenu>(parameters =>
{
parameters.Add(p => p.Responsive, false);
});
Assert.DoesNotContain("rz-menu-toggle", component.Markup);
Assert.DoesNotContain("rz-menu-closed", component.Markup);
}
[Fact]
public void Menu_Renders_CustomToggleAriaLabel()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenMenu>(parameters =>
{
parameters.Add(p => p.Responsive, true);
parameters.Add(p => p.ToggleAriaLabel, "Open navigation");
});
Assert.Contains("Open navigation", component.Markup);
}
}
}

View File

@@ -16,7 +16,7 @@ namespace Radzen.Blazor.Tests
public string Name { get; set; }
public FieldIdentifier FieldIdentifier => throw new System.NotImplementedException();
public FieldIdentifier FieldIdentifier { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public object GetValue()
{

View File

@@ -1,8 +1,9 @@
using Bunit;
using Bunit.TestDoubles;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.Extensions.DependencyInjection.Extensions;
using System.Collections.Generic;
using System.Linq;
using Xunit;
namespace Radzen.Blazor.Tests
@@ -22,6 +23,9 @@ namespace Radzen.Blazor.Tests
}
private static string CreatePanelMenu(string currentAbsoluteUrl, NavLinkMatch match, params string[] urls)
=> CreatePanelMenu(currentAbsoluteUrl, match, new Dictionary<string, bool>(urls.Select(url => new KeyValuePair<string, bool>(url, false))));
private static string CreatePanelMenu(string currentAbsoluteUrl, NavLinkMatch match, Dictionary<string, bool> urls)
{
using var ctx = new TestContext();
@@ -30,12 +34,13 @@ namespace Radzen.Blazor.Tests
var component = ctx.RenderComponent<RadzenPanelMenu>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.Match, match).AddChildContent(builder =>
component.SetParametersAndRender(parameters => parameters.Add(p => p.Match, match).AddChildContent(builder =>
{
foreach (var url in urls)
{
builder.OpenComponent<RadzenPanelMenuItem>(0);
builder.AddAttribute(1, nameof(RadzenPanelMenuItem.Path), url);
builder.AddAttribute(1, nameof(RadzenPanelMenuItem.Path), url.Key);
builder.AddAttribute(2, nameof(RadzenPanelMenuItem.Disabled), url.Value);
builder.CloseComponent();
}
}));
@@ -55,6 +60,19 @@ namespace Radzen.Blazor.Tests
Assert.Equal(firstIndex, lastIndex);
}
[Fact]
public void RadzenPanelMenu_CanDisableMenuItem()
{
var urls = new Dictionary<string, bool>
{
{"/datagrid", false},
{"/disabled-url", true}
};
var component = CreatePanelMenu("http://www.example.com/", NavLinkMatch.All, urls);
Assert.Contains("rz-state-disabled", component);
}
[Fact]
public void RadzenPanelMenu_MatchesQueryStringParameters()
{

View File

@@ -198,14 +198,10 @@ namespace Radzen.Blazor.Tests
});
Assert.Contains("SummaryContent", component.Markup);
Assert.Equal(
"display: block",
component.Find(".rz-panel-content-summary").ParentElement.Attributes.First(attr => attr.Name == "style").Value
);
}
[Fact]
public void Panel_DontRenders_SummaryWhenOpen()
public void Panel_DoesNotRender_SummaryWhenOpen()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenPanel>();
@@ -225,8 +221,8 @@ namespace Radzen.Blazor.Tests
Assert.Contains("SummaryContent", component.Markup);
Assert.Equal(
"display: none",
component.Find(".rz-panel-content-summary").ParentElement.Attributes.First(attr => attr.Name == "style").Value
"true",
component.Find(".rz-panel-content-summary").ParentElement.ParentElement.Attributes.First(attr => attr.Name == "aria-hidden").Value
);
}
}

View File

@@ -171,24 +171,49 @@ namespace Radzen.Blazor.Tests
Assert.Contains(@$"autofocus", component.Markup);
}
[Fact]
public void Password_Raises_ChangedEvent()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenPassword>();
var raised = false;
var hasRaised = false;
var value = "Test";
object newValue = null;
component.SetParametersAndRender(parameters => parameters.Add(p => p.Change, args => { raised = true; newValue = args; }));
var component = ctx.RenderComponent<RadzenPassword>(parameters =>
{
parameters.Add(p => p.Change, args => { hasRaised = true; newValue = args; });
parameters.Add(p => p.Immediate, false);
});
component.Find("input").Change(value);
var inputElement = component.Find("input");
inputElement.Change(value);
Assert.True(raised);
Assert.True(object.Equals(value, newValue));
Assert.DoesNotContain("oninput", inputElement.ToMarkup());
Assert.True(hasRaised);
Assert.Equal(value, newValue);
}
[Fact]
public void Password_Raises_InputEvent()
{
using var ctx = new TestContext();
var hasRaised = false;
var value = "Test";
object newValue = null;
var component = ctx.RenderComponent<RadzenPassword>(parameters =>
{
parameters.Add(p => p.Change, args => { hasRaised = true; newValue = args; });
parameters.Add(p => p.Immediate, true);
});
var inputElement = component.Find("input");
inputElement.Input(value);
Assert.DoesNotContain("onchange", inputElement.ToMarkup());
Assert.True(hasRaised);
Assert.Equal(value, newValue);
}
[Fact]

View File

@@ -0,0 +1,161 @@
using Bunit;
using Xunit;
using System.Collections.Generic;
namespace Radzen.Blazor.Tests
{
public class PickListTests
{
class Item
{
public int Id { get; set; }
public string Name { get; set; }
}
[Fact]
public void PickList_Renders_WithClassName()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenPickList<Item>>();
Assert.Contains(@"rz-picklist", component.Markup);
}
[Fact]
public void PickList_Renders_SourceAndTargetLists()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenPickList<Item>>();
Assert.Contains("rz-picklist-source-wrapper", component.Markup);
Assert.Contains("rz-picklist-target-wrapper", component.Markup);
}
[Fact]
public void PickList_Renders_TransferButtons()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenPickList<Item>>();
Assert.Contains("rz-picklist-buttons", component.Markup);
}
[Fact]
public void PickList_Renders_Orientation_Horizontal()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenPickList<Item>>(parameters =>
{
parameters.Add(p => p.Orientation, Orientation.Horizontal);
});
Assert.Contains("rz-flex-row", component.Markup);
}
[Fact]
public void PickList_Renders_Orientation_Vertical()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenPickList<Item>>(parameters =>
{
parameters.Add(p => p.Orientation, Orientation.Vertical);
});
Assert.Contains("rz-flex-column", component.Markup);
}
[Fact]
public void PickList_Renders_AllowFiltering()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var data = new List<Item>
{
new Item { Id = 1, Name = "Item 1" }
};
var component = ctx.RenderComponent<RadzenPickList<Item>>(parameters =>
{
parameters.Add(p => p.AllowFiltering, true);
parameters.Add(p => p.Source, data);
parameters.Add(p => p.TextProperty, "Name");
});
Assert.Contains("rz-listbox-filter", component.Markup);
}
[Fact]
public void PickList_Renders_SourceData()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var data = new List<Item>
{
new Item { Id = 1, Name = "Source Item 1" },
new Item { Id = 2, Name = "Source Item 2" }
};
var component = ctx.RenderComponent<RadzenPickList<Item>>(parameters =>
{
parameters.Add(p => p.Source, data);
parameters.Add(p => p.TextProperty, "Name");
});
Assert.Contains("Source Item 1", component.Markup);
Assert.Contains("Source Item 2", component.Markup);
}
[Fact]
public void PickList_Renders_ShowHeader()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenPickList<Item>>(parameters =>
{
parameters.Add(p => p.ShowHeader, true);
parameters.Add(p => p.SourceHeader, builder => builder.AddContent(0, "Available Items"));
parameters.Add(p => p.TargetHeader, builder => builder.AddContent(0, "Selected Items"));
});
Assert.Contains("Available Items", component.Markup);
Assert.Contains("Selected Items", component.Markup);
}
[Fact]
public void PickList_Renders_AllowMoveAll_Buttons()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var data = new List<Item> { new Item { Id = 1, Name = "Item" } };
var component = ctx.RenderComponent<RadzenPickList<Item>>(parameters =>
{
parameters.Add(p => p.AllowMoveAll, true);
parameters.Add(p => p.Source, data);
});
// Should have 4 buttons when AllowMoveAll is true
var buttonCount = System.Text.RegularExpressions.Regex.Matches(component.Markup, "rz-button").Count;
Assert.True(buttonCount >= 4);
}
[Fact]
public void PickList_Renders_Disabled()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenPickList<Item>>(parameters =>
{
parameters.Add(p => p.Disabled, true);
});
Assert.Contains("disabled", component.Markup);
}
}
}

View File

@@ -0,0 +1,644 @@
using Bunit;
using Microsoft.AspNetCore.Components;
using Radzen;
using Radzen.Blazor;
using System;
using System.Collections.Generic;
using System.Linq;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class PivotDataGridTests
{
private static readonly List<SalesData> SampleData = new()
{
new SalesData { Region = "North", Category = "Electronics", Product = "Laptop", Amount = 1000, Year = 2023 },
new SalesData { Region = "North", Category = "Electronics", Product = "Laptop", Amount = 1500, Year = 2024 },
new SalesData { Region = "South", Category = "Home", Product = "Vacuum", Amount = 500, Year = 2023 }
};
public class SalesData
{
public string Region { get; set; }
public string Category { get; set; }
public string Product { get; set; }
public double Amount { get; set; }
public int Year { get; set; }
}
[Fact]
public void PivotDataGrid_Renders_CssClasses()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = RenderPivotDataGrid(ctx);
component.WaitForAssertion(() =>
{
Assert.Contains("rz-pivot-data-grid", component.Markup);
Assert.Contains("rz-pivot-table", component.Markup);
});
}
[Fact]
public void PivotDataGrid_Renders_RowAndColumnHeaders()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = RenderPivotDataGrid(ctx);
component.WaitForAssertion(() =>
{
var table = component.Find(".rz-pivot-content .rz-pivot-table");
var headers = table.GetElementsByClassName("rz-pivot-header-text").Select(h => h.TextContent.Trim()).ToList();
var aggregateHeaders = table.GetElementsByClassName("rz-pivot-aggregate-header").Select(h => h.TextContent.Trim()).ToList();
Assert.Contains("Region", headers);
Assert.Contains("2023", headers);
Assert.Contains("Sales", aggregateHeaders);
});
}
[Fact]
public void PivotDataGrid_AllowSorting_RendersSortableClass()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = RenderPivotDataGrid(ctx, parameters =>
{
parameters.Add(p => p.AllowSorting, true);
});
component.WaitForAssertion(() =>
{
var sortableHeaders = component.FindAll(".rz-pivot-header-content.rz-sortable");
Assert.NotEmpty(sortableHeaders);
});
}
[Fact]
public void PivotDataGrid_AllowFiltering_RendersFilterIcon()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = RenderPivotDataGrid(ctx, parameters =>
{
parameters.Add(p => p.AllowFiltering, true);
});
component.WaitForAssertion(() =>
{
var filterIcons = component.FindAll(".rz-grid-filter-icon");
Assert.NotEmpty(filterIcons);
});
}
[Fact]
public void PivotDataGrid_Renders_AggregateValues()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = RenderPivotDataGrid(ctx);
component.WaitForAssertion(() =>
{
Assert.Contains("1000", component.Markup);
Assert.Contains("1500", component.Markup);
Assert.Contains("500", component.Markup);
});
}
[Fact]
public void PivotDataGrid_DisallowFiltering_HidesFilterIcon()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = RenderPivotDataGrid(ctx, parameters =>
{
parameters.Add(p => p.AllowFiltering, false);
});
component.WaitForAssertion(() =>
{
var filterIcons = component.FindAll(".rz-grid-filter-icon");
Assert.Empty(filterIcons);
});
}
[Fact]
public void PivotDataGrid_DisallowSorting_HidesSortableClass()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = RenderPivotDataGrid(ctx, parameters =>
{
parameters.Add(p => p.AllowSorting, false);
});
component.WaitForAssertion(() =>
{
var sortableHeaders = component.FindAll(".rz-pivot-header-content.rz-sortable");
Assert.Empty(sortableHeaders);
});
}
[Fact]
public void PivotDataGrid_ShowColumnsTotals_RendersFooter()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = RenderPivotDataGrid(ctx, parameters =>
{
parameters.Add(p => p.ShowColumnsTotals, true);
});
component.WaitForAssertion(() =>
{
var footer = component.FindAll(".rz-pivot-footer");
Assert.NotEmpty(footer);
});
}
[Fact]
public void PivotDataGrid_HideColumnsTotals_NoFooter()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = RenderPivotDataGrid(ctx, parameters =>
{
parameters.Add(p => p.ShowColumnsTotals, false);
});
component.WaitForAssertion(() =>
{
var footer = component.FindAll(".rz-pivot-footer");
Assert.Empty(footer);
});
}
[Fact]
public void PivotDataGrid_Renders_DefaultEmptyText()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = ctx.RenderComponent<RadzenPivotDataGrid<SalesData>>(parameters =>
{
parameters.Add(p => p.Data, new List<SalesData>());
parameters.Add(p => p.AllowFieldsPicking, false);
});
component.WaitForAssertion(() =>
{
Assert.Contains("No records to display.", component.Markup);
});
}
[Fact]
public void PivotDataGrid_Renders_CustomEmptyText()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = ctx.RenderComponent<RadzenPivotDataGrid<SalesData>>(parameters =>
{
parameters.Add(p => p.Data, new List<SalesData>());
parameters.Add(p => p.EmptyText, "No data available");
parameters.Add(p => p.AllowFieldsPicking, false);
});
component.WaitForAssertion(() =>
{
Assert.Contains("No data available", component.Markup);
});
}
[Fact]
public void PivotDataGrid_AllowPaging_RendersPager()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = RenderPivotDataGrid(ctx, parameters =>
{
parameters.Add(p => p.AllowPaging, true);
parameters.Add(p => p.PageSize, 2);
});
component.WaitForAssertion(() =>
{
Assert.Contains("rz-pager", component.Markup);
});
}
[Fact]
public void PivotDataGrid_DisallowPaging_HidesPager()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = RenderPivotDataGrid(ctx, parameters =>
{
parameters.Add(p => p.AllowPaging, false);
});
component.WaitForAssertion(() =>
{
Assert.DoesNotContain("rz-pager", component.Markup);
});
}
[Fact]
public void PivotDataGrid_PagerPosition_Top()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = RenderPivotDataGrid(ctx, parameters =>
{
parameters.Add(p => p.AllowPaging, true);
parameters.Add(p => p.PagerPosition, PagerPosition.Top);
parameters.Add(p => p.PageSize, 2);
});
component.WaitForAssertion(() =>
{
Assert.Contains("rz-pager", component.Markup);
});
}
[Fact]
public void PivotDataGrid_PagerPosition_TopAndBottom()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = RenderPivotDataGrid(ctx, parameters =>
{
parameters.Add(p => p.AllowPaging, true);
parameters.Add(p => p.PagerPosition, PagerPosition.TopAndBottom);
parameters.Add(p => p.PageSize, 2);
});
component.WaitForAssertion(() =>
{
var pagers = component.FindAll(".rz-pager");
Assert.True(pagers.Count >= 1); // Should have at least one pager
});
}
[Fact]
public void PivotDataGrid_Density_Compact()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = RenderPivotDataGrid(ctx, parameters =>
{
parameters.Add(p => p.AllowPaging, true);
parameters.Add(p => p.PageSize, 1); // Force pager to show with small page size
parameters.Add(p => p.AllowFieldsPicking, false);
parameters.Add(p => p.Density, Density.Compact);
});
component.WaitForAssertion(() =>
{
Assert.Contains("rz-density-compact", component.Markup);
});
}
[Fact]
public void PivotDataGrid_AllowAlternatingRows_True()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = RenderPivotDataGrid(ctx, parameters =>
{
parameters.Add(p => p.AllowAlternatingRows, true);
});
component.WaitForAssertion(() =>
{
Assert.Contains("rz-grid-table-striped", component.Markup);
});
}
[Fact]
public void PivotDataGrid_AllowAlternatingRows_False()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = RenderPivotDataGrid(ctx, parameters =>
{
parameters.Add(p => p.AllowAlternatingRows, false);
});
component.WaitForAssertion(() =>
{
Assert.DoesNotContain("rz-grid-table-striped", component.Markup);
});
}
[Fact]
public void PivotDataGrid_AllowFieldsPicking_ShowsPanel()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = RenderPivotDataGrid(ctx, parameters =>
{
parameters.Add(p => p.AllowFieldsPicking, true);
});
component.WaitForAssertion(() =>
{
Assert.Contains("rz-panel", component.Markup);
});
}
[Fact]
public void PivotDataGrid_AllowFieldsPicking_False_HidesPanel()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = RenderPivotDataGrid(ctx, parameters =>
{
parameters.Add(p => p.AllowFieldsPicking, false);
});
component.WaitForAssertion(() =>
{
Assert.DoesNotContain("rz-panel", component.Markup);
});
}
[Fact]
public void PivotDataGrid_Renders_AllowDrillDown()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = RenderPivotDataGrid(ctx, parameters =>
{
parameters.Add(p => p.AllowDrillDown, true);
});
component.WaitForAssertion(() =>
{
// Should render pivot content
Assert.Contains("rz-pivot-content", component.Markup);
});
}
[Fact]
public void PivotDataGrid_Renders_RowValues()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = RenderPivotDataGrid(ctx);
component.WaitForAssertion(() =>
{
var cells = component.FindAll(".rz-pivot-row-header");
var cellTexts = cells.Select(c => c.TextContent.Trim()).ToList();
Assert.Contains("North", cellTexts);
Assert.Contains("South", cellTexts);
});
}
[Fact]
public void PivotDataGrid_Renders_MultipleRows()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = ctx.RenderComponent<RadzenPivotDataGrid<SalesData>>(parameters =>
{
parameters.Add(p => p.Data, SampleData);
parameters.Add<RenderFragment>(p => p.Rows, builder =>
{
builder.OpenComponent<RadzenPivotRow<SalesData>>(0);
builder.AddAttribute(1, nameof(RadzenPivotRow<SalesData>.Property), nameof(SalesData.Region));
builder.AddAttribute(2, nameof(RadzenPivotRow<SalesData>.Title), "Region");
builder.CloseComponent();
builder.OpenComponent<RadzenPivotRow<SalesData>>(2);
builder.AddAttribute(3, nameof(RadzenPivotRow<SalesData>.Property), nameof(SalesData.Category));
builder.AddAttribute(4, nameof(RadzenPivotRow<SalesData>.Title), "Category");
builder.CloseComponent();
});
parameters.Add<RenderFragment>(p => p.Columns, builder =>
{
builder.OpenComponent<RadzenPivotColumn<SalesData>>(0);
builder.AddAttribute(1, nameof(RadzenPivotColumn<SalesData>.Property), nameof(SalesData.Year));
builder.AddAttribute(2, nameof(RadzenPivotColumn<SalesData>.Title), "Year");
builder.CloseComponent();
});
parameters.Add<RenderFragment>(p => p.Aggregates, builder =>
{
builder.OpenComponent<RadzenPivotAggregate<SalesData>>(0);
builder.AddAttribute(1, nameof(RadzenPivotAggregate<SalesData>.Property), nameof(SalesData.Amount));
builder.AddAttribute(2, nameof(RadzenPivotAggregate<SalesData>.Title), "Sales");
builder.AddAttribute(3, nameof(RadzenPivotAggregate<SalesData>.Aggregate), AggregateFunction.Sum);
builder.CloseComponent();
});
});
component.WaitForAssertion(() =>
{
var headers = component.FindAll(".rz-pivot-header-text").Select(h => h.TextContent.Trim()).ToList();
Assert.Contains("Region", headers);
Assert.Contains("Category", headers);
});
}
[Fact]
public void PivotDataGrid_Renders_MultipleAggregates()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = ctx.RenderComponent<RadzenPivotDataGrid<SalesData>>(parameters =>
{
parameters.Add(p => p.Data, SampleData);
parameters.Add<RenderFragment>(p => p.Rows, builder =>
{
builder.OpenComponent<RadzenPivotRow<SalesData>>(0);
builder.AddAttribute(1, nameof(RadzenPivotRow<SalesData>.Property), nameof(SalesData.Region));
builder.AddAttribute(2, nameof(RadzenPivotRow<SalesData>.Title), "Region");
builder.CloseComponent();
});
parameters.Add<RenderFragment>(p => p.Columns, builder =>
{
builder.OpenComponent<RadzenPivotColumn<SalesData>>(0);
builder.AddAttribute(1, nameof(RadzenPivotColumn<SalesData>.Property), nameof(SalesData.Year));
builder.AddAttribute(2, nameof(RadzenPivotColumn<SalesData>.Title), "Year");
builder.CloseComponent();
});
parameters.Add<RenderFragment>(p => p.Aggregates, builder =>
{
builder.OpenComponent<RadzenPivotAggregate<SalesData>>(0);
builder.AddAttribute(1, nameof(RadzenPivotAggregate<SalesData>.Property), nameof(SalesData.Amount));
builder.AddAttribute(2, nameof(RadzenPivotAggregate<SalesData>.Title), "Total Sales");
builder.AddAttribute(3, nameof(RadzenPivotAggregate<SalesData>.Aggregate), AggregateFunction.Sum);
builder.CloseComponent();
builder.OpenComponent<RadzenPivotAggregate<SalesData>>(4);
builder.AddAttribute(5, nameof(RadzenPivotAggregate<SalesData>.Property), nameof(SalesData.Amount));
builder.AddAttribute(6, nameof(RadzenPivotAggregate<SalesData>.Title), "Count Sales");
builder.AddAttribute(7, nameof(RadzenPivotAggregate<SalesData>.Aggregate), AggregateFunction.Count);
builder.CloseComponent();
});
});
component.WaitForAssertion(() =>
{
var aggregateHeaders = component.FindAll(".rz-pivot-aggregate-header").Select(h => h.TextContent.Trim()).ToList();
Assert.Contains("Total Sales", aggregateHeaders);
Assert.Contains("Count Sales", aggregateHeaders);
});
}
[Fact]
public void PivotDataGrid_Renders_AlternatingRowClasses()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = RenderPivotDataGrid(ctx, parameters =>
{
parameters.Add(p => p.AllowAlternatingRows, true);
});
component.WaitForAssertion(() =>
{
Assert.Contains("rz-pivot-row-even", component.Markup);
Assert.Contains("rz-pivot-row-odd", component.Markup);
});
}
[Fact]
public void PivotDataGrid_PageSize_DefaultsTo10()
{
var grid = new RadzenPivotDataGrid<SalesData>();
Assert.Equal(10, grid.PageSize);
}
[Fact]
public void PivotDataGrid_AllowSorting_DefaultsToTrue()
{
var grid = new RadzenPivotDataGrid<SalesData>();
Assert.True(grid.AllowSorting);
}
[Fact]
public void PivotDataGrid_AllowFiltering_DefaultsToTrue()
{
var grid = new RadzenPivotDataGrid<SalesData>();
Assert.True(grid.AllowFiltering);
}
[Fact]
public void PivotDataGrid_AllowAlternatingRows_DefaultsToTrue()
{
var grid = new RadzenPivotDataGrid<SalesData>();
Assert.True(grid.AllowAlternatingRows);
}
[Fact]
public void PivotDataGrid_AllowDrillDown_DefaultsToTrue()
{
var grid = new RadzenPivotDataGrid<SalesData>();
Assert.True(grid.AllowDrillDown);
}
[Fact]
public void PivotDataGrid_AllowFieldsPicking_DefaultsToTrue()
{
var grid = new RadzenPivotDataGrid<SalesData>();
Assert.True(grid.AllowFieldsPicking);
}
private static IRenderedComponent<RadzenPivotDataGrid<SalesData>> RenderPivotDataGrid(TestContext ctx, Action<ComponentParameterCollectionBuilder<RadzenPivotDataGrid<SalesData>>>? configure = null)
{
return ctx.RenderComponent<RadzenPivotDataGrid<SalesData>>(parameters =>
{
parameters.Add(p => p.Data, SampleData);
parameters.Add<RenderFragment>(p => p.Rows, builder =>
{
builder.OpenComponent<RadzenPivotRow<SalesData>>(0);
builder.AddAttribute(1, nameof(RadzenPivotRow<SalesData>.Property), nameof(SalesData.Region));
builder.AddAttribute(2, nameof(RadzenPivotRow<SalesData>.Title), "Region");
builder.CloseComponent();
});
parameters.Add<RenderFragment>(p => p.Columns, builder =>
{
builder.OpenComponent<RadzenPivotColumn<SalesData>>(0);
builder.AddAttribute(1, nameof(RadzenPivotColumn<SalesData>.Property), nameof(SalesData.Year));
builder.AddAttribute(2, nameof(RadzenPivotColumn<SalesData>.Title), "Year");
builder.CloseComponent();
});
parameters.Add<RenderFragment>(p => p.Aggregates, builder =>
{
builder.OpenComponent<RadzenPivotAggregate<SalesData>>(0);
builder.AddAttribute(1, nameof(RadzenPivotAggregate<SalesData>.Property), nameof(SalesData.Amount));
builder.AddAttribute(2, nameof(RadzenPivotAggregate<SalesData>.Title), "Sales");
builder.AddAttribute(3, nameof(RadzenPivotAggregate<SalesData>.Aggregate), AggregateFunction.Sum);
builder.CloseComponent();
});
configure?.Invoke(parameters);
});
}
}
}

View File

@@ -0,0 +1,120 @@
using Bunit;
using Microsoft.AspNetCore.Components;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class ProfileMenuItemTests
{
[Fact]
public void ProfileMenuItem_Renders_TextParameter()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenProfileMenu>(parameters =>
{
parameters.Add(p => p.ChildContent, builder =>
{
builder.OpenComponent<RadzenProfileMenuItem>(0);
builder.AddAttribute(1, "Text", "Profile");
builder.CloseComponent();
});
});
Assert.Contains("Profile", component.Markup);
}
[Fact]
public void ProfileMenuItem_Renders_IconParameter()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenProfileMenu>(parameters =>
{
parameters.Add(p => p.ChildContent, builder =>
{
builder.OpenComponent<RadzenProfileMenuItem>(0);
builder.AddAttribute(1, "Icon", "account_circle");
builder.AddAttribute(2, "Text", "Profile");
builder.CloseComponent();
});
});
Assert.Contains("account_circle", component.Markup);
}
[Fact]
public void ProfileMenuItem_Template_OverridesText()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenProfileMenu>(parameters =>
{
parameters.Add(p => p.ChildContent, builder =>
{
builder.OpenComponent<RadzenProfileMenuItem>(0);
builder.AddAttribute(1, "Text", "This should not appear");
builder.AddAttribute(2, "Template", (RenderFragment)((templateBuilder) =>
{
templateBuilder.OpenElement(0, "span");
templateBuilder.AddAttribute(1, "class", "template-content");
templateBuilder.AddContent(2, "Template Content");
templateBuilder.CloseElement();
}));
builder.CloseComponent();
});
});
// Template should be rendered
Assert.Contains("template-content", component.Markup);
// Text should not be rendered in navigation-item-text span when Template is present
Assert.DoesNotContain("<span class=\"rz-navigation-item-text\">This should not appear</span>", component.Markup);
}
[Fact]
public void ProfileMenuItem_Renders_TemplateWithSwitch()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenProfileMenu>(parameters =>
{
parameters.Add(p => p.ChildContent, builder =>
{
builder.OpenComponent<RadzenProfileMenuItem>(0);
builder.AddAttribute(1, "Icon", "settings");
builder.AddAttribute(2, "Template", (RenderFragment)((templateBuilder) =>
{
templateBuilder.OpenComponent<RadzenSwitch>(0);
templateBuilder.CloseComponent();
}));
builder.CloseComponent();
});
});
// Icon should still be rendered
Assert.Contains("settings", component.Markup);
// Switch should be rendered from template
Assert.Contains("rz-switch", component.Markup);
}
[Fact]
public void ProfileMenuItem_Renders_PathParameter()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenProfileMenu>(parameters =>
{
parameters.Add(p => p.ChildContent, builder =>
{
builder.OpenComponent<RadzenProfileMenuItem>(0);
builder.AddAttribute(1, "Text", "Settings");
builder.AddAttribute(2, "Path", "/settings");
builder.CloseComponent();
});
});
Assert.Contains("href=\"/settings\"", component.Markup);
}
}
}

View File

@@ -142,5 +142,45 @@ namespace Radzen.Blazor.Tests
component.SetParametersAndRender(parameters => parameters.Add(p => p.Mode, ProgressBarMode.Indeterminate));
Assert.Contains(@$"rz-progressbar-info", component.Markup);
}
[Fact]
public void ProgressBar_Renders_ShowValue_True()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenProgressBar>(parameters =>
{
parameters.Add(p => p.ShowValue, true);
parameters.Add(p => p.Value, 50);
});
Assert.Contains("rz-progressbar-label", component.Markup);
Assert.Contains("50%", component.Markup);
}
[Fact]
public void ProgressBar_Renders_ShowValue_False()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenProgressBar>(parameters =>
{
parameters.Add(p => p.ShowValue, false);
parameters.Add(p => p.Value, 50);
});
Assert.DoesNotContain("rz-progressbar-label", component.Markup);
}
[Fact]
public void ProgressBar_Renders_Template()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenProgressBar>(parameters =>
{
parameters.Add(p => p.Value, 75);
parameters.Add(p => p.Template, builder => builder.AddContent(0, "Custom: 75%"));
});
Assert.Contains("Custom: 75%", component.Markup);
}
}
}

View File

@@ -113,5 +113,43 @@ namespace Radzen.Blazor.Tests
{
public List<string> Values { get; set; }
}
[Fact]
public void GetProperty_Should_Resolve_DescriptionProperty()
{
var descriptionProperty = PropertyAccess.GetProperty(typeof(ISimpleInterface), nameof(ISimpleInterface.Description));
Assert.NotNull(descriptionProperty);
}
[Fact]
public void GetProperty_Should_Resolve_NameProperty()
{
var nameProperty = PropertyAccess.GetProperty(typeof(ISimpleInterface), nameof(ISimpleInterface.Name));
Assert.NotNull(nameProperty);
}
[Fact]
public void GetProperty_Should_Resolve_IdProperty()
{
var idProperty = PropertyAccess.GetProperty(typeof(ISimpleInterface), nameof(ISimpleBaseInterface.Id));
Assert.NotNull(idProperty);
}
interface ISimpleInterface : ISimpleNestedInterface
{
string Description { get; set; }
}
interface ISimpleNestedInterface : ISimpleBaseInterface
{
string Name { get; set; }
}
interface ISimpleBaseInterface
{
int Id { get; set; }
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,62 @@
using Bunit;
using System.Collections.Generic;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class RadioButtonListTests
{
[Fact]
public void RadioButtonList_Renders_WithClassName()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenRadioButtonList<int>>();
Assert.Contains(@"rz-radio-button-list", component.Markup);
}
[Fact]
public void RadioButtonList_Renders_Orientation()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenRadioButtonList<int>>(parameters =>
{
parameters.Add(p => p.Items, builder =>
{
builder.OpenComponent<RadzenRadioButtonListItem<int>>(0);
builder.AddAttribute(1, "Text", "Option 1");
builder.AddAttribute(2, "Value", 1);
builder.CloseComponent();
});
});
component.SetParametersAndRender(parameters => parameters.Add(p => p.Orientation, Orientation.Horizontal));
// Orientation is applied via RadzenStack which uses flex-direction
Assert.Contains("rz-flex-row", component.Markup);
component.SetParametersAndRender(parameters => parameters.Add(p => p.Orientation, Orientation.Vertical));
Assert.Contains("rz-flex-column", component.Markup);
}
[Fact]
public void RadioButtonList_Renders_Disabled()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenRadioButtonList<int>>(parameters =>
{
parameters.Add(p => p.Disabled, true);
parameters.Add(p => p.Items, builder =>
{
builder.OpenComponent<RadzenRadioButtonListItem<int>>(0);
builder.AddAttribute(1, "Text", "Option 1");
builder.AddAttribute(2, "Value", 1);
builder.CloseComponent();
});
});
// Disabled class is on the radio button items
Assert.Contains("rz-state-disabled", component.Markup);
}
}
}

View File

@@ -1,13 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
<PackageReference Include="bunit.web" Version="1.36.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">

View File

@@ -0,0 +1,82 @@
using Bunit;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class RatingTests
{
[Fact]
public void Rating_Renders_WithClassName()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenRating>();
Assert.Contains(@"rz-rating", component.Markup);
}
[Fact]
public void Rating_Renders_Stars()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenRating>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.Stars, 5));
// Should render 5 star icons (rzi-star or rzi-star-o) + 1 clear button icon = 6 total
var starCount = System.Text.RegularExpressions.Regex.Matches(component.Markup, "rz-rating-icon").Count;
Assert.Equal(6, starCount); // 5 stars + 1 clear button
}
[Fact]
public void Rating_Renders_CustomStarCount()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenRating>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.Stars, 10));
var starCount = System.Text.RegularExpressions.Regex.Matches(component.Markup, "rz-rating-icon").Count;
Assert.Equal(11, starCount); // 10 stars + 1 clear button
}
[Fact]
public void Rating_Renders_Value()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenRating>();
component.SetParametersAndRender(parameters =>
{
parameters.Add(p => p.Value, 3);
parameters.Add(p => p.Stars, 5);
});
// Should have 3 filled stars (rzi-star) and 2 outline stars (rzi-star-o)
var filledStars = System.Text.RegularExpressions.Regex.Matches(component.Markup, "rzi-star\"").Count;
Assert.Equal(3, filledStars);
}
[Fact]
public void Rating_Renders_ReadOnly()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenRating>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.ReadOnly, true));
Assert.Contains("rz-state-readonly", component.Markup);
}
[Fact]
public void Rating_Renders_Disabled()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenRating>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.Disabled, true));
Assert.Contains("rz-state-disabled", component.Markup);
}
}
}

View File

@@ -0,0 +1,63 @@
using Bunit;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class RowTests
{
[Fact]
public void Row_Renders_WithClassName()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenRow>();
Assert.Contains(@"rz-row", component.Markup);
}
[Fact]
public void Row_Renders_ChildContent()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenRow>(parameters =>
{
parameters.AddChildContent("<div>Row Content</div>");
});
Assert.Contains("Row Content", component.Markup);
}
[Fact]
public void Row_Renders_Gap()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenRow>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.Gap, "2rem"));
Assert.Contains("--rz-gap:2rem", component.Markup);
}
[Fact]
public void Row_Renders_RowGap()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenRow>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.RowGap, "1.5rem"));
Assert.Contains("--rz-row-gap:1.5rem", component.Markup);
}
[Fact]
public void Row_Renders_ColumnGap()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenRow>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.Gap, "1rem"));
Assert.Contains("--rz-gap:1rem", component.Markup);
}
}
}

View File

@@ -0,0 +1,45 @@
using Bunit;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class SecurityCodeTests
{
[Fact]
public void SecurityCode_Renders_WithClassName()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenSecurityCode>();
Assert.Contains(@"rz-security-code", component.Markup);
}
[Fact]
public void SecurityCode_Renders_Count()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenSecurityCode>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.Count, 6));
// Should render 6 input boxes + 1 hidden input for form submission = 7 total
var inputCount = System.Text.RegularExpressions.Regex.Matches(component.Markup, "<input").Count;
Assert.Equal(7, inputCount);
}
[Fact]
public void SecurityCode_Renders_Disabled()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenSecurityCode>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.Disabled, true));
Assert.Contains("disabled", component.Markup);
}
}
}

View File

@@ -0,0 +1,83 @@
using Bunit;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class SelectBarTests
{
[Fact]
public void SelectBar_Renders_WithClassName()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenSelectBar<int>>();
Assert.Contains(@"rz-selectbar", component.Markup);
}
[Fact]
public void SelectBar_Renders_Orientation()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenSelectBar<int>>(parameters =>
{
parameters.Add(p => p.Items, builder =>
{
builder.OpenComponent<RadzenSelectBarItem>(0);
builder.AddAttribute(1, "Text", "Option 1");
builder.AddAttribute(2, "Value", 1);
builder.CloseComponent();
});
});
component.SetParametersAndRender(parameters => parameters.Add(p => p.Orientation, Orientation.Horizontal));
Assert.Contains("rz-selectbar-horizontal", component.Markup);
component.SetParametersAndRender(parameters => parameters.Add(p => p.Orientation, Orientation.Vertical));
Assert.Contains("rz-selectbar-vertical", component.Markup);
}
[Fact]
public void SelectBar_Renders_Multiple()
{
using var ctx = new TestContext();
// When Multiple is true, TValue should be IEnumerable<T>
var component = ctx.RenderComponent<RadzenSelectBar<System.Collections.Generic.IEnumerable<int>>>(parameters =>
{
parameters.Add(p => p.Multiple, true);
parameters.Add(p => p.Items, builder =>
{
builder.OpenComponent<RadzenSelectBarItem>(0);
builder.AddAttribute(1, "Text", "Option 1");
builder.AddAttribute(2, "Value", 1);
builder.CloseComponent();
});
});
Assert.NotNull(component.Instance);
Assert.True(component.Instance.Multiple);
}
[Fact]
public void SelectBar_Renders_Size()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenSelectBar<int>>(parameters =>
{
parameters.Add(p => p.Items, builder =>
{
builder.OpenComponent<RadzenSelectBarItem>(0);
builder.AddAttribute(1, "Text", "Option 1");
builder.AddAttribute(2, "Value", 1);
builder.CloseComponent();
});
});
component.SetParametersAndRender(parameters => parameters.Add(p => p.Size, ButtonSize.Small));
Assert.Contains("rz-button-sm", component.Markup);
component.SetParametersAndRender(parameters => parameters.Add(p => p.Size, ButtonSize.Large));
Assert.Contains("rz-button-lg", component.Markup);
}
}
}

View File

@@ -0,0 +1,65 @@
using Bunit;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class SidebarToggleTests
{
[Fact]
public void SidebarToggle_Renders_WithClassName()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenSidebarToggle>();
Assert.Contains(@"rz-sidebar-toggle", component.Markup);
}
[Fact]
public void SidebarToggle_Renders_DefaultIcon()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenSidebarToggle>();
Assert.Contains("menu", component.Markup);
}
[Fact]
public void SidebarToggle_Renders_CustomIcon()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenSidebarToggle>();
var icon = "close";
component.SetParametersAndRender(parameters => parameters.Add(p => p.Icon, icon));
Assert.Contains(icon, component.Markup);
}
[Fact]
public void SidebarToggle_Renders_AriaLabel()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenSidebarToggle>();
var ariaLabel = "Toggle Navigation";
component.SetParametersAndRender(parameters => parameters.Add(p => p.ToggleAriaLabel, ariaLabel));
Assert.Contains($"aria-label=\"{ariaLabel}\"", component.Markup);
}
[Fact]
public void SidebarToggle_Raises_ClickEvent()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenSidebarToggle>();
var clicked = false;
component.SetParametersAndRender(parameters => parameters.Add(p => p.Click, args => { clicked = true; }));
component.Find("button").Click();
Assert.True(clicked);
}
}
}

View File

@@ -0,0 +1,158 @@
using Bunit;
using Radzen.Blazor.Rendering;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class SkeletonTests
{
[Fact]
public void Skeleton_Renders_CssClass()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = ctx.RenderComponent<RadzenSkeleton>();
Assert.Contains("rz-skeleton", component.Markup);
Assert.Contains("rz-skeleton-text", component.Markup);
}
[Fact]
public void Skeleton_Renders_TypeParameter()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = ctx.RenderComponent<RadzenSkeleton>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.Variant, SkeletonVariant.Circular));
Assert.Contains("rz-skeleton", component.Markup);
Assert.Contains("rz-skeleton-circular", component.Markup);
}
[Theory]
[InlineData(SkeletonVariant.Text, "rz-skeleton-text")]
[InlineData(SkeletonVariant.Circular, "rz-skeleton-circular")]
[InlineData(SkeletonVariant.Rectangular, "rz-skeleton-rectangular")]
public void Skeleton_Renders_AllTypes(SkeletonVariant type, string expectedClass)
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = ctx.RenderComponent<RadzenSkeleton>(parameters => parameters.Add(p => p.Variant, type));
Assert.Contains("rz-skeleton", component.Markup);
Assert.Contains(expectedClass, component.Markup);
}
[Fact]
public void Skeleton_Renders_AnimationParameter()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = ctx.RenderComponent<RadzenSkeleton>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.Animation, SkeletonAnimation.Wave));
Assert.Contains("rz-skeleton", component.Markup);
Assert.Contains("rz-skeleton-wave", component.Markup);
}
[Theory]
[InlineData(SkeletonAnimation.Wave, "rz-skeleton-wave")]
[InlineData(SkeletonAnimation.Pulse, "rz-skeleton-pulse")]
public void Skeleton_Renders_AllAnimations(SkeletonAnimation animation, string expectedClass)
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = ctx.RenderComponent<RadzenSkeleton>(parameters => parameters.Add(p => p.Animation, animation));
Assert.Contains("rz-skeleton", component.Markup);
Assert.Contains(expectedClass, component.Markup);
}
[Fact]
public void Skeleton_Renders_StyleParameter()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = ctx.RenderComponent<RadzenSkeleton>();
var style = "width: 200px; height: 20px;";
component.SetParametersAndRender(parameters => parameters.Add(p => p.Style, style));
Assert.Contains($"style=\"{style}\"", component.Markup);
}
[Fact]
public void Skeleton_Renders_VisibleParameter()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = ctx.RenderComponent<RadzenSkeleton>();
// Should be visible by default
Assert.Contains("rz-skeleton", component.Markup);
component.SetParametersAndRender(parameters => parameters.Add(p => p.Visible, false));
// Should not render when not visible
Assert.DoesNotContain("rz-skeleton", component.Markup);
}
[Fact]
public void Skeleton_Renders_UnmatchedParameter()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = ctx.RenderComponent<RadzenSkeleton>();
component.SetParametersAndRender(parameters => parameters.AddUnmatched("data-testid", "skeleton-test"));
Assert.Contains("data-testid=\"skeleton-test\"", component.Markup);
}
[Fact]
public void Skeleton_DefaultType_IsText()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = ctx.RenderComponent<RadzenSkeleton>();
// Should render with text type by default
Assert.Contains("rz-skeleton-text", component.Markup);
}
[Fact]
public void Skeleton_DefaultAnimation_IsNone()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = ctx.RenderComponent<RadzenSkeleton>();
// Should not render animation classes by default
Assert.DoesNotContain("rz-skeleton-wave", component.Markup);
Assert.DoesNotContain("rz-skeleton-pulse", component.Markup);
}
}
}

View File

@@ -89,5 +89,71 @@ namespace Radzen.Blazor.Tests
Assert.Contains(@$"autofocus", component.Markup);
}
[Fact]
public void Slider_Renders_Orientation_Vertical()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = ctx.RenderComponent<RadzenSlider<int>>(parameters =>
{
parameters.Add(p => p.Orientation, Orientation.Vertical);
});
Assert.Contains("rz-slider-vertical", component.Markup);
}
[Fact]
public void Slider_Renders_Disabled()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = ctx.RenderComponent<RadzenSlider<int>>(parameters =>
{
parameters.Add(p => p.Disabled, true);
});
Assert.Contains("rz-state-disabled", component.Markup);
}
[Fact]
public void Slider_Renders_SliderHandle()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = ctx.RenderComponent<RadzenSlider<int>>();
Assert.Contains("rz-slider-handle", component.Markup);
}
[Fact]
public void Slider_Renders_SliderRange()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = ctx.RenderComponent<RadzenSlider<int>>();
Assert.Contains("rz-slider-range", component.Markup);
}
[Fact]
public void Slider_Renders_TabIndex()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
var component = ctx.RenderComponent<RadzenSlider<int>>();
Assert.Contains("tabindex=\"0\"", component.Markup);
}
}
}

View File

@@ -0,0 +1,42 @@
using Bunit;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class SplitterTests
{
[Fact]
public void Splitter_Renders_WithClassName()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenSplitter>();
Assert.Contains(@"rz-splitter", component.Markup);
}
[Fact]
public void Splitter_Renders_Orientation_Horizontal()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenSplitter>(parameters =>
{
parameters.Add(p => p.Orientation, Orientation.Horizontal);
});
Assert.Contains("rz-splitter-horizontal", component.Markup);
}
[Fact]
public void Splitter_Renders_Orientation_Vertical()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenSplitter>(parameters =>
{
parameters.Add(p => p.Orientation, Orientation.Vertical);
});
Assert.Contains("rz-splitter-vertical", component.Markup);
}
}
}

View File

@@ -0,0 +1,98 @@
using Bunit;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class StackTests
{
[Fact]
public void Stack_Renders_WithClassName()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenStack>();
Assert.Contains(@"rz-stack", component.Markup);
}
[Fact]
public void Stack_Renders_ChildContent()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenStack>(parameters =>
{
parameters.AddChildContent("<div>Stack Content</div>");
});
Assert.Contains("Stack Content", component.Markup);
}
[Fact]
public void Stack_Renders_Orientation()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenStack>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.Orientation, Orientation.Horizontal));
Assert.Contains("rz-flex-row", component.Markup);
component.SetParametersAndRender(parameters => parameters.Add(p => p.Orientation, Orientation.Vertical));
Assert.Contains("rz-flex-column", component.Markup);
}
[Fact]
public void Stack_Renders_Gap()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenStack>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.Gap, "1.5rem"));
Assert.Contains("--rz-gap:1.5rem", component.Markup);
}
[Fact]
public void Stack_Renders_AlignItems()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenStack>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.AlignItems, AlignItems.Center));
Assert.Contains("rz-align-items-center", component.Markup);
}
[Fact]
public void Stack_Renders_JustifyContent()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenStack>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.JustifyContent, JustifyContent.SpaceBetween));
Assert.Contains("rz-justify-content-space-between", component.Markup);
}
[Fact]
public void Stack_Renders_Wrap()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenStack>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.Wrap, FlexWrap.Wrap));
Assert.Contains("flex-wrap:wrap", component.Markup);
}
[Fact]
public void Stack_Renders_Reverse()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenStack>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.Reverse, true));
Assert.Contains("rz-flex-column-reverse", component.Markup);
}
}
}

View File

@@ -0,0 +1,70 @@
using Bunit;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class StepsTests
{
[Fact]
public void Steps_Renders_WithClassName()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenSteps>();
Assert.Contains(@"rz-steps", component.Markup);
}
[Fact]
public void Steps_Renders_ShowStepsButtons_True()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenSteps>(parameters =>
{
parameters.Add(p => p.ShowStepsButtons, true);
});
Assert.Contains("rz-steps-buttons", component.Markup);
}
[Fact]
public void Steps_Renders_ShowStepsButtons_False()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenSteps>(parameters =>
{
parameters.Add(p => p.ShowStepsButtons, false);
});
Assert.DoesNotContain("rz-steps-buttons", component.Markup);
}
[Fact]
public void Steps_Renders_StepsButtons()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenSteps>(parameters =>
{
parameters.Add(p => p.ShowStepsButtons, true);
});
Assert.Contains("rz-steps-prev", component.Markup);
Assert.Contains("rz-steps-next", component.Markup);
}
[Fact]
public void Steps_Renders_CustomButtonText()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenSteps>(parameters =>
{
parameters.Add(p => p.ShowStepsButtons, true);
parameters.Add(p => p.NextText, "Continue");
parameters.Add(p => p.PreviousText, "Back");
});
Assert.Contains("Continue", component.Markup);
Assert.Contains("Back", component.Markup);
}
}
}

View File

@@ -0,0 +1,100 @@
using Bunit;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class TableTests
{
[Fact]
public void Table_Renders_WithClassName()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenTable>();
Assert.Contains(@"rz-datatable", component.Markup);
}
[Fact]
public void Table_Renders_TableElement()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenTable>();
Assert.Contains("rz-grid-table", component.Markup);
Assert.Contains("<table", component.Markup);
}
[Fact]
public void Table_Renders_GridLines_None()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenTable>(parameters =>
{
parameters.Add(p => p.GridLines, DataGridGridLines.None);
});
Assert.Contains("rz-grid-gridlines-none", component.Markup);
}
[Fact]
public void Table_Renders_GridLines_Horizontal()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenTable>(parameters =>
{
parameters.Add(p => p.GridLines, DataGridGridLines.Horizontal);
});
Assert.Contains("rz-grid-gridlines-horizontal", component.Markup);
}
[Fact]
public void Table_Renders_GridLines_Vertical()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenTable>(parameters =>
{
parameters.Add(p => p.GridLines, DataGridGridLines.Vertical);
});
Assert.Contains("rz-grid-gridlines-vertical", component.Markup);
}
[Fact]
public void Table_Renders_GridLines_Both()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenTable>(parameters =>
{
parameters.Add(p => p.GridLines, DataGridGridLines.Both);
});
Assert.Contains("rz-grid-gridlines-both", component.Markup);
}
[Fact]
public void Table_Renders_AllowAlternatingRows_True()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenTable>(parameters =>
{
parameters.Add(p => p.AllowAlternatingRows, true);
});
Assert.Contains("rz-grid-table-striped", component.Markup);
}
[Fact]
public void Table_Renders_AllowAlternatingRows_False()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenTable>(parameters =>
{
parameters.Add(p => p.AllowAlternatingRows, false);
});
Assert.DoesNotContain("rz-grid-table-striped", component.Markup);
}
}
}

View File

@@ -0,0 +1,108 @@
using Bunit;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class TabsTests
{
[Fact]
public void Tabs_Renders_WithClassName()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenTabs>();
Assert.Contains(@"rz-tabview", component.Markup);
}
[Fact]
public void Tabs_Renders_TabPosition_Top()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenTabs>(parameters =>
{
parameters.Add(p => p.TabPosition, TabPosition.Top);
});
Assert.Contains("rz-tabview-top", component.Markup);
}
[Fact]
public void Tabs_Renders_TabPosition_Bottom()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenTabs>(parameters =>
{
parameters.Add(p => p.TabPosition, TabPosition.Bottom);
});
Assert.Contains("rz-tabview-bottom", component.Markup);
}
[Fact]
public void Tabs_Renders_TabPosition_Left()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenTabs>(parameters =>
{
parameters.Add(p => p.TabPosition, TabPosition.Left);
});
Assert.Contains("rz-tabview-left", component.Markup);
}
[Fact]
public void Tabs_Renders_TabPosition_Right()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenTabs>(parameters =>
{
parameters.Add(p => p.TabPosition, TabPosition.Right);
});
Assert.Contains("rz-tabview-right", component.Markup);
}
[Fact]
public void Tabs_Renders_TabPosition_TopRight()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenTabs>(parameters =>
{
parameters.Add(p => p.TabPosition, TabPosition.TopRight);
});
Assert.Contains("rz-tabview-top-right", component.Markup);
}
[Fact]
public void Tabs_Renders_TabPosition_BottomRight()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenTabs>(parameters =>
{
parameters.Add(p => p.TabPosition, TabPosition.BottomRight);
});
Assert.Contains("rz-tabview-bottom-right", component.Markup);
}
[Fact]
public void Tabs_Renders_TabNav()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenTabs>();
Assert.Contains("rz-tabview-nav", component.Markup);
}
[Fact]
public void Tabs_Renders_TabPanels()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenTabs>();
Assert.Contains("rz-tabview-panels", component.Markup);
}
}
}

View File

@@ -1,4 +1,6 @@
using Bunit;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Xunit;
namespace Radzen.Blazor.Tests
@@ -29,6 +31,26 @@ namespace Radzen.Blazor.Tests
Assert.Contains(@$"value=""{value}""", component.Markup);
}
[Fact]
public void TextboxCanSetFieldIdentifier()
{
using var ctx = new TestContext();
var editContext = new EditContext(ctx);
var fieldIdentifier = new FieldIdentifier(ctx, nameof(RadzenTextBox.Value));
ctx.RenderTree.TryAdd<CascadingValue<EditContext>>(parameters =>
{
parameters.Add(e => e.Value, editContext);
});
var component = ctx.RenderComponent<RadzenTextBox>(parameters =>
{
parameters.Add(p => p.FieldIdentifier, fieldIdentifier);
});
Assert.Equal(component.Instance.FieldIdentifier, fieldIdentifier);
}
[Fact]
public void TextBox_Renders_StyleParameter()
{

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,71 @@
using Bunit;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class TimelineTests
{
[Fact]
public void Timeline_Renders_WithClassName()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenTimeline>();
Assert.Contains(@"rz-timeline", component.Markup);
}
[Fact]
public void Timeline_Renders_Orientation()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenTimeline>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.Orientation, Orientation.Horizontal));
Assert.Contains("rz-timeline-row", component.Markup);
component.SetParametersAndRender(parameters => parameters.Add(p => p.Orientation, Orientation.Vertical));
Assert.Contains("rz-timeline-column", component.Markup);
}
[Fact]
public void Timeline_Renders_LinePosition()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenTimeline>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.LinePosition, LinePosition.Start));
Assert.Contains("rz-timeline-start", component.Markup);
component.SetParametersAndRender(parameters => parameters.Add(p => p.LinePosition, LinePosition.End));
Assert.Contains("rz-timeline-end", component.Markup);
component.SetParametersAndRender(parameters => parameters.Add(p => p.LinePosition, LinePosition.Center));
Assert.Contains("rz-timeline-center", component.Markup);
}
[Fact]
public void Timeline_Renders_AlignItems()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenTimeline>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.AlignItems, AlignItems.Start));
Assert.Contains("rz-timeline-align-items-start", component.Markup);
component.SetParametersAndRender(parameters => parameters.Add(p => p.AlignItems, AlignItems.Center));
Assert.Contains("rz-timeline-align-items-center", component.Markup);
}
[Fact]
public void Timeline_Renders_Reverse()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenTimeline>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.Reverse, true));
Assert.Contains("rz-timeline-reverse", component.Markup);
}
}
}

View File

@@ -0,0 +1,27 @@
using Bunit;
using System.Collections.Generic;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class TocTests
{
[Fact]
public void TocItem_Renders_With_Attributes()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenTocItem>(parameters =>
{
parameters.Add(p => p.Attributes, new Dictionary<string, object>
{
{ "data-enhance-nav", "false" },
{ "aria-label", "Table of Contents Item" }
});
});
Assert.Contains("data-enhance-nav=\"false\"", component.Markup);
Assert.Contains("aria-label=\"Table of Contents Item\"", component.Markup);
}
}
}

View File

@@ -0,0 +1,98 @@
using Bunit;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class ToggleButtonTests
{
[Fact]
public void ToggleButton_Renders_WithClassName()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenToggleButton>();
Assert.Contains(@"rz-button", component.Markup);
}
[Fact]
public void ToggleButton_Renders_TextParameter()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenToggleButton>();
var text = "Toggle Me";
component.SetParametersAndRender(parameters => parameters.Add(p => p.Text, text));
Assert.Contains(text, component.Markup);
}
[Fact]
public void ToggleButton_Renders_IconParameter()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenToggleButton>();
var icon = "toggle_on";
component.SetParametersAndRender(parameters => parameters.Add(p => p.Icon, icon));
Assert.Contains(icon, component.Markup);
}
[Fact]
public void ToggleButton_Renders_Value()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenToggleButton>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.Value, true));
Assert.Contains("rz-state-active", component.Markup);
}
[Fact]
public void ToggleButton_Renders_Disabled()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenToggleButton>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.Disabled, true));
Assert.Contains("rz-state-disabled", component.Markup);
}
[Fact]
public void ToggleButton_Renders_ButtonStyle()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenToggleButton>();
component.SetParametersAndRender(parameters => parameters.Add(p => p.ButtonStyle, ButtonStyle.Primary));
Assert.Contains("rz-primary", component.Markup);
component.SetParametersAndRender(parameters => parameters.Add(p => p.ButtonStyle, ButtonStyle.Success));
Assert.Contains("rz-success", component.Markup);
}
[Fact]
public void ToggleButton_Raises_ChangeEvent()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenToggleButton>();
var changed = false;
bool newValue = false;
component.SetParametersAndRender(parameters => parameters.Add(p => p.Change, args =>
{
changed = true;
newValue = args;
}));
component.Find("button").Click();
Assert.True(changed);
Assert.True(newValue);
}
}
}

View File

@@ -0,0 +1,211 @@
using Bunit;
using Xunit;
using System.Collections.Generic;
using System.Linq;
namespace Radzen.Blazor.Tests
{
public class TreeTests
{
class Category
{
public string Name { get; set; }
public List<Product> Products { get; set; } = new List<Product>();
}
class Product
{
public string Name { get; set; }
}
class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public List<Employee> Employees { get; set; } = new List<Employee>();
}
[Fact]
public void Tree_Renders_WithClassName()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenTree>();
Assert.Contains(@"rz-tree", component.Markup);
}
[Fact]
public void Tree_Renders_TreeContainer()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenTree>();
Assert.Contains("rz-tree-container", component.Markup);
}
[Fact]
public void Tree_Renders_TabIndex()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenTree>();
Assert.Contains("tabindex=\"0\"", component.Markup);
}
[Fact]
public void Tree_Renders_WithData_SingleLevel()
{
using var ctx = new TestContext();
var data = new List<Category>
{
new Category { Name = "Electronics" },
new Category { Name = "Clothing" }
};
var component = ctx.RenderComponent<RadzenTree>(parameters =>
{
parameters.Add(p => p.Data, data);
parameters.Add(p => p.ChildContent, builder =>
{
builder.OpenComponent<RadzenTreeLevel>(0);
builder.AddAttribute(1, "TextProperty", "Name");
builder.CloseComponent();
});
});
Assert.Contains("Electronics", component.Markup);
Assert.Contains("Clothing", component.Markup);
}
[Fact]
public void Tree_Renders_WithData_HierarchicalData()
{
using var ctx = new TestContext();
var data = new List<Category>
{
new Category
{
Name = "Electronics",
Products = new List<Product>
{
new Product { Name = "Laptop" },
new Product { Name = "Phone" }
}
}
};
var component = ctx.RenderComponent<RadzenTree>(parameters =>
{
parameters.Add(p => p.Data, data);
parameters.Add(p => p.ChildContent, builder =>
{
builder.OpenComponent<RadzenTreeLevel>(0);
builder.AddAttribute(1, "TextProperty", "Name");
builder.AddAttribute(2, "ChildrenProperty", "Products");
builder.CloseComponent();
builder.OpenComponent<RadzenTreeLevel>(3);
builder.AddAttribute(4, "TextProperty", "Name");
builder.AddAttribute(5, "HasChildren", (object product) => false);
builder.CloseComponent();
});
});
Assert.Contains("Electronics", component.Markup);
}
[Fact]
public void Tree_Renders_WithData_SelfReferencing()
{
using var ctx = new TestContext();
var data = new List<Employee>
{
new Employee
{
FirstName = "Nancy",
LastName = "Davolio",
Employees = new List<Employee>
{
new Employee { FirstName = "Andrew", LastName = "Fuller" }
}
}
};
var component = ctx.RenderComponent<RadzenTree>(parameters =>
{
parameters.Add(p => p.Data, data);
parameters.Add(p => p.ChildContent, builder =>
{
builder.OpenComponent<RadzenTreeLevel>(0);
builder.AddAttribute(1, "TextProperty", "LastName");
builder.AddAttribute(2, "ChildrenProperty", "Employees");
builder.AddAttribute(3, "HasChildren", (object e) => (e as Employee).Employees.Any());
builder.CloseComponent();
});
});
Assert.Contains("Davolio", component.Markup);
}
[Fact]
public void Tree_Renders_WithCheckBoxes()
{
using var ctx = new TestContext();
var data = new List<Category>
{
new Category { Name = "Electronics" }
};
var component = ctx.RenderComponent<RadzenTree>(parameters =>
{
parameters.Add(p => p.AllowCheckBoxes, true);
parameters.Add(p => p.Data, data);
parameters.Add(p => p.ChildContent, builder =>
{
builder.OpenComponent<RadzenTreeLevel>(0);
builder.AddAttribute(1, "TextProperty", "Name");
builder.CloseComponent();
});
});
Assert.Contains("rz-chkbox", component.Markup);
}
[Fact]
public void Tree_Renders_WithExpandableItems()
{
using var ctx = new TestContext();
var data = new List<Category>
{
new Category
{
Name = "Electronics",
Products = new List<Product>
{
new Product { Name = "Laptop" }
}
}
};
var component = ctx.RenderComponent<RadzenTree>(parameters =>
{
parameters.Add(p => p.Data, data);
parameters.Add(p => p.ChildContent, builder =>
{
builder.OpenComponent<RadzenTreeLevel>(0);
builder.AddAttribute(1, "TextProperty", "Name");
builder.AddAttribute(2, "ChildrenProperty", "Products");
builder.CloseComponent();
builder.OpenComponent<RadzenTreeLevel>(3);
builder.AddAttribute(4, "TextProperty", "Name");
builder.CloseComponent();
});
});
// Expandable items should have a toggle icon
Assert.Contains("rz-tree-toggler", component.Markup);
}
}
}

View File

@@ -0,0 +1,95 @@
using Bunit;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class UploadTests
{
[Fact]
public void Upload_Renders_WithClassName()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenUpload>();
Assert.Contains(@"rz-fileupload", component.Markup);
}
[Fact]
public void Upload_Renders_Disabled()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenUpload>(parameters =>
{
parameters.Add(p => p.Disabled, true);
});
Assert.Contains("rz-state-disabled", component.Markup);
}
[Fact]
public void Upload_Renders_ChooseText()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenUpload>(parameters =>
{
parameters.Add(p => p.ChooseText, "Select Files");
});
Assert.Contains("Select Files", component.Markup);
}
[Fact]
public void Upload_Renders_DefaultChooseText()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenUpload>();
Assert.Contains("Choose", component.Markup);
}
[Fact]
public void Upload_Renders_Icon()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenUpload>(parameters =>
{
parameters.Add(p => p.Icon, "upload");
});
Assert.Contains("upload", component.Markup);
Assert.Contains("rzi", component.Markup);
}
[Fact]
public void Upload_Renders_Multiple_Attribute()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenUpload>(parameters =>
{
parameters.Add(p => p.Multiple, true);
});
Assert.Contains("multiple", component.Markup);
}
[Fact]
public void Upload_Renders_Accept_Attribute()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var component = ctx.RenderComponent<RadzenUpload>(parameters =>
{
parameters.Add(p => p.Accept, "image/*");
});
Assert.Contains("accept=\"image/*\"", component.Markup);
}
}
}

View File

@@ -0,0 +1,214 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Net.Http;
using System.Net.Http.Headers;
using System.IO;
using System.Text.Json.Serialization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System.Linq;
namespace Radzen;
/// <summary>
/// Service for interacting with AI chat models to get completions with conversation memory.
/// </summary>
public class AIChatService(IServiceProvider serviceProvider, IOptions<AIChatServiceOptions> options) : IAIChatService
{
private readonly Dictionary<string, ConversationSession> sessions = new();
private readonly object sessionsLock = new();
/// <summary>
/// Gets the configuration options for the chat streaming service.
/// </summary>
public AIChatServiceOptions Options => options.Value;
/// <inheritdoc />
public async IAsyncEnumerable<string> GetCompletionsAsync(string userInput, string sessionId = null, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default, string model = null, string systemPrompt = null, double? temperature = null, int? maxTokens = null, string endpoint = null, string proxy = null, string apiKey = null, string apiKeyHeader = null)
{
if (string.IsNullOrWhiteSpace(userInput))
{
throw new ArgumentException("User input cannot be null or empty.", nameof(userInput));
}
// Get or create session
var session = GetOrCreateSession(sessionId);
// Add user message to conversation history
session.AddMessage("user", userInput);
// Use runtime parameters or fall back to configured options
var url = proxy ?? Options.Proxy ?? endpoint ?? Options.Endpoint;
var effectiveApiKey = apiKey ?? Options.ApiKey;
var effectiveApiKeyHeader = apiKeyHeader ?? Options.ApiKeyHeader;
// Get formatted messages including conversation history
var messages = session.GetFormattedMessages(systemPrompt ?? Options.SystemPrompt);
var payload = new
{
model = model ?? Options.Model,
messages = messages,
temperature = temperature ?? Options.Temperature,
max_tokens = maxTokens ?? Options.MaxTokens,
stream = true
};
var request = new HttpRequestMessage(HttpMethod.Post, url)
{
Content = new StringContent(JsonSerializer.Serialize(payload, new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }), Encoding.UTF8, "application/json")
};
if (!string.IsNullOrEmpty(effectiveApiKey))
{
if (string.Equals(effectiveApiKeyHeader, "Authorization", StringComparison.OrdinalIgnoreCase))
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", effectiveApiKey);
}
else
{
request.Headers.Add(effectiveApiKeyHeader, effectiveApiKey);
}
}
var httpClient = serviceProvider.GetRequiredService<HttpClient>();
var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
if (!response.IsSuccessStatusCode)
{
throw new Exception($"Chat stream failed: {await response.Content.ReadAsStringAsync(cancellationToken)}");
}
using var stream = await response.Content.ReadAsStreamAsync(cancellationToken);
using var reader = new StreamReader(stream);
var assistantResponse = new StringBuilder();
string line;
while ((line = await reader.ReadLineAsync()) is not null && !cancellationToken.IsCancellationRequested)
{
if (string.IsNullOrWhiteSpace(line) || !line.StartsWith("data:"))
{
continue;
}
var json = line["data:".Length..].Trim();
if (json == "[DONE]")
{
break;
}
var content = ParseStreamingResponse(json);
if (!string.IsNullOrEmpty(content))
{
assistantResponse.Append(content);
yield return content;
}
}
// Add assistant response to conversation history
if (assistantResponse.Length > 0)
{
session.AddMessage("assistant", assistantResponse.ToString());
}
}
/// <inheritdoc />
public ConversationSession GetOrCreateSession(string sessionId = null)
{
lock (sessionsLock)
{
if (string.IsNullOrEmpty(sessionId))
{
sessionId = Guid.NewGuid().ToString();
}
if (!sessions.TryGetValue(sessionId, out var session))
{
session = new ConversationSession
{
Id = sessionId,
MaxMessages = Options.MaxMessages
};
sessions[sessionId] = session;
}
return session;
}
}
/// <inheritdoc />
public void ClearSession(string sessionId)
{
lock (sessionsLock)
{
if (sessions.TryGetValue(sessionId, out var session))
{
session.Clear();
}
}
}
/// <inheritdoc />
public IEnumerable<ConversationSession> GetActiveSessions()
{
lock (sessionsLock)
{
return sessions.Values.ToList();
}
}
/// <inheritdoc />
public void CleanupOldSessions(int maxAgeHours = 24)
{
lock (sessionsLock)
{
var cutoffTime = DateTime.Now.AddHours(-maxAgeHours);
var sessionsToRemove = sessions.Values
.Where(s => s.LastUpdated < cutoffTime)
.Select(s => s.Id)
.ToList();
foreach (var sessionId in sessionsToRemove)
{
sessions.Remove(sessionId);
}
}
}
private static string ParseStreamingResponse(string json)
{
try
{
var doc = JsonDocument.Parse(json);
var root = doc.RootElement;
if (!root.TryGetProperty("choices", out var choices) || choices.GetArrayLength() == 0)
{
return string.Empty;
}
var firstChoice = choices[0];
if (!firstChoice.TryGetProperty("delta", out var delta))
{
return string.Empty;
}
if (delta.TryGetProperty("content", out var contentElement))
{
return contentElement.GetString() ?? string.Empty;
}
return string.Empty;
}
catch
{
return string.Empty;
}
}
}

View File

@@ -0,0 +1,48 @@
using System;
using Microsoft.Extensions.DependencyInjection;
namespace Radzen;
/// <summary>
/// Extension methods for configuring AIChatService in the dependency injection container.
/// </summary>
public static class AIChatServiceExtensions
{
/// <summary>
/// Adds the AIChatService to the service collection with the specified configuration.
/// </summary>
/// <param name="services">The service collection.</param>
/// <param name="configureOptions">The action to configure the AIChatService options.</param>
/// <returns>The updated service collection.</returns>
public static IServiceCollection AddAIChatService(this IServiceCollection services, Action<AIChatServiceOptions> configureOptions)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
if (configureOptions == null)
{
throw new ArgumentNullException(nameof(configureOptions));
}
services.Configure(configureOptions);
services.AddScoped<IAIChatService, AIChatService>();
return services;
}
/// <summary>
/// Adds the AIChatService to the service collection with default options.
/// </summary>
/// <param name="services">The service collection.</param>
/// <returns>The updated service collection.</returns>
public static IServiceCollection AddAIChatService(this IServiceCollection services)
{
services.AddOptions<AIChatServiceOptions>();
services.AddScoped<IAIChatService, AIChatService>();
return services;
}
}

View File

@@ -0,0 +1,58 @@
namespace Radzen;
/// <summary>
/// Configuration options for the <see cref="AIChatService"/>.
/// </summary>
public class AIChatServiceOptions
{
/// <summary>
/// Gets or sets the endpoint URL for the AI service.
/// </summary>
public string Endpoint { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the proxy URL for the AI service, if any. If set, this will override the Endpoint.
/// </summary>
public string Proxy { get; set; } = null;
/// <summary>
/// Gets or sets the API key for authentication with the AI service.
/// </summary>
public string ApiKey { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the header name for the API key (e.g., 'Authorization' or 'api-key').
/// </summary>
public string ApiKeyHeader { get; set; } = "Authorization";
/// <summary>
/// Gets or sets the model name to use for executing chat completions (e.g., 'gpt-3.5-turbo').
/// </summary>
public string Model { get; set; }
/// <summary>
/// Gets or sets the system prompt for the AI assistant.
/// </summary>
public string SystemPrompt { get; set; } = "You are a helpful AI code assistant.";
/// <summary>
/// Gets or sets the temperature for the AI model (0.0 to 2.0). Set to 0.0 for deterministic responses, higher values for more creative outputs.
/// </summary>
public double Temperature { get; set; } = 0.7;
/// <summary>
/// Gets or sets the maximum number of tokens to generate in the response.
/// </summary>
public int? MaxTokens { get; set; }
/// <summary>
/// Gets or sets the maximum number of messages to keep in conversation memory.
/// </summary>
public int MaxMessages { get; set; } = 50;
/// <summary>
/// Gets or sets the maximum age in hours for conversation sessions before cleanup.
/// </summary>
public int SessionMaxAgeHours { get; set; } = 24;
}

View File

@@ -0,0 +1,43 @@
namespace Radzen;
/// <summary>
/// Specifies the aggregate function for pivot values.
/// </summary>
public enum AggregateFunction
{
/// <summary>
/// Sum of values.
/// </summary>
Sum,
/// <summary>
/// Count of items.
/// </summary>
Count,
/// <summary>
/// Average of values.
/// </summary>
Average,
/// <summary>
/// Minimum value.
/// </summary>
Min,
/// <summary>
/// Maximum value.
/// </summary>
Max,
/// <summary>
/// First value.
/// </summary>
First,
/// <summary>
/// Last value.
/// </summary>
Last
}

View File

@@ -0,0 +1,28 @@
namespace Radzen;
/// <summary>
/// Specifies the size of a <see cref="Radzen.Blazor.RadzenAlert" />.
/// </summary>
public enum AlertSize
{
/// <summary>
/// The smallest alert.
/// </summary>
ExtraSmall,
/// <summary>
/// A alert smaller than the default.
/// </summary>
Small,
/// <summary>
/// The default size of an alert.
/// </summary>
Medium,
/// <summary>
/// An alert larger than the default.
/// </summary>
Large
}

View File

@@ -0,0 +1,53 @@
namespace Radzen;
/// <summary>
/// Specifies the display style or severity of a <see cref="Radzen.Blazor.RadzenAlert" />. Affects the visual styling of RadzenAlert (background and text color).
/// </summary>
public enum AlertStyle
{
/// <summary>
/// Primary styling. Similar to primary buttons.
/// </summary>
Primary,
/// <summary>
/// Secondary styling. Similar to secondary buttons.
/// </summary>
Secondary,
/// <summary>
/// Light styling. Similar to light buttons.
/// </summary>
Light,
/// <summary>
/// Base styling. Similar to base buttons.
/// </summary>
Base,
/// <summary>
/// Dark styling. Similar to dark buttons.
/// </summary>
Dark,
/// <summary>
/// Success styling.
/// </summary>
Success,
/// <summary>
/// Danger styling.
/// </summary>
Danger,
/// <summary>
/// Warning styling.
/// </summary>
Warning,
/// <summary>
/// Informative styling.
/// </summary>
Info
}

View File

@@ -0,0 +1,33 @@
namespace Radzen;
/// <summary>
/// Represents the alignment of Stack items.
/// </summary>
public enum AlignItems
{
/// <summary>
/// Normal items alignment.
/// </summary>
Normal,
/// <summary>
/// Center items alignment.
/// </summary>
Center,
/// <summary>
/// Start items alignment.
/// </summary>
Start,
/// <summary>
/// End items alignment.
/// </summary>
End,
/// <summary>
/// Stretch items alignment.
/// </summary>
Stretch
}

View File

@@ -1,17 +1,12 @@
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)
/// The <c>AutomCompleteType</c> is a string-associated enum of browser-supported autocomplete attribute values.
/// Lists the autocomplete attribute 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>
/// </summary>
public enum AutoCompleteType
{
/// <summary>Autocomplete is disabled. </summary>

View File

@@ -0,0 +1,53 @@
namespace Radzen;
/// <summary>
/// Specifies the display style of a <see cref="Radzen.Blazor.RadzenBadge" />. Affects the visual styling of RadzenBadge (background and text color).
/// </summary>
public enum BadgeStyle
{
/// <summary>
/// Primary styling. Similar to primary buttons.
/// </summary>
Primary,
/// <summary>
/// Secondary styling. Similar to secondary buttons.
/// </summary>
Secondary,
/// <summary>
/// Light styling. Similar to light buttons.
/// </summary>
Light,
/// <summary>
/// Base styling. Similar to base buttons.
/// </summary>
Base,
/// <summary>
/// Dark styling. Similar to dark buttons.
/// </summary>
Dark,
/// <summary>
/// Success styling.
/// </summary>
Success,
/// <summary>
/// Danger styling.
/// </summary>
Danger,
/// <summary>
/// Warning styling.
/// </summary>
Warning,
/// <summary>
/// Informative styling.
/// </summary>
Info
}

View File

@@ -0,0 +1,28 @@
namespace Radzen;
/// <summary>
/// Specifies the size of a <see cref="Radzen.Blazor.RadzenButton" />.
/// </summary>
public enum ButtonSize
{
/// <summary>
/// The default size of a button.
/// </summary>
Medium,
/// <summary>
/// A button larger than the default.
/// </summary>
Large,
/// <summary>
/// A button smaller than the default.
/// </summary>
Small,
/// <summary>
/// The smallest button.
/// </summary>
ExtraSmall
}

View File

@@ -0,0 +1,53 @@
namespace Radzen;
/// <summary>
/// Specifies the display style of a <see cref="Radzen.Blazor.RadzenButton" />. Affects the visual styling of RadzenButton (background and text color).
/// </summary>
public enum ButtonStyle
{
/// <summary>
/// A primary button. Clicking it performs the primary action in a form or dialog (e.g. "save").
/// </summary>
Primary,
/// <summary>
/// A secondary button. Clicking it performs a secondary action in a form or dialog (e.g. close a dialog or cancel a form).
/// </summary>
Secondary,
/// <summary>
/// A button with lighter styling.
/// </summary>
Light,
/// <summary>
/// The base UI styling.
/// </summary>
Base,
/// <summary>
/// A button with dark styling.
/// </summary>
Dark,
/// <summary>
/// A button with success styling.
/// </summary>
Success,
/// <summary>
/// A button which represents a dangerous action e.g. "delete".
/// </summary>
Danger,
/// <summary>
/// A button with warning styling.
/// </summary>
Warning,
/// <summary>
/// A button with informative styling.
/// </summary>
Info
}

View File

@@ -0,0 +1,23 @@
namespace Radzen;
/// <summary>
/// Specifies the type of a <see cref="Radzen.Blazor.RadzenButton" />. Renders as the <c>type</c> HTML attribute.
/// </summary>
public enum ButtonType
{
/// <summary>
/// Generic button which does not submit its parent form.
/// </summary>
Button,
/// <summary>
/// Clicking a submit button submits its parent form.
/// </summary>
Submit,
/// <summary>
/// Clicking a reset button clears the value of all inputs in its parent form.
/// </summary>
Reset
}

View File

@@ -0,0 +1,43 @@
using System;
namespace Radzen.Blazor;
/// <summary>
/// Represents a chat message in the RadzenAIChat component.
/// </summary>
public class ChatMessage
{
/// <summary>
/// Gets or sets the unique identifier for the message.
/// </summary>
public string Id { get; set; } = Guid.NewGuid().ToString();
/// <summary>
/// Gets or sets the content of the message.
/// </summary>
public string Content { get; set; } = string.Empty;
/// <summary>
/// Gets or sets whether this message is from the user.
/// </summary>
public bool IsUser { get; set; }
/// <summary>
/// Gets or sets the ID of the user who sent the message.
/// </summary>
public string UserId { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the timestamp when the message was created.
/// </summary>
public DateTime Timestamp { get; set; } = DateTime.Now;
/// <summary>
/// Gets or sets whether this message is currently streaming.
/// </summary>
public bool IsStreaming { get; set; }
/// <summary>
/// Gets or sets the role associated with the message (e.g., "user", "assistant").
/// </summary>
public string Role { get; set; }
}

View File

@@ -0,0 +1,18 @@
namespace Radzen;
/// <summary>
/// Specifies how the filter should be applied to a collection of items.
/// </summary>
public enum CollectionFilterMode
{
/// <summary>
/// The filter condition is satisfied if at least one item in the collection matches.
/// </summary>
Any,
/// <summary>
/// The filter condition is satisfied only if all items in the collection match.
/// </summary>
All
}

258
Radzen.Blazor/Colors.cs Normal file
View File

@@ -0,0 +1,258 @@
namespace Radzen;
/// <summary>
/// Colors.
/// </summary>
public static class Colors
{
/// <summary>
/// Primary.
/// </summary>
public const string Primary = "var(--rz-primary)";
/// <summary>
/// Primary lighter.
/// </summary>
public const string PrimaryLighter = "var(--rz-primary-lighter)";
/// <summary>
/// Primary light.
/// </summary>
public const string PrimaryLight = "var(--rz-primary-light)";
/// <summary>
/// Primary dark.
/// </summary>
public const string PrimaryDark = "var(--rz-primary-dark)";
/// <summary>
/// Primary darker.
/// </summary>
public const string PrimaryDarker = "var(--rz-primary-darker)";
/// <summary>
/// Secondary.
/// </summary>
public const string Secondary = "var(--rz-secondary)";
/// <summary>
/// Secondary lighter.
/// </summary>
public const string SecondaryLighter = "var(--rz-secondary-lighter)";
/// <summary>
/// Secondary light.
/// </summary>
public const string SecondaryLight = "var(--rz-secondary-light)";
/// <summary>
/// Secondary dark.
/// </summary>
public const string SecondaryDark = "var(--rz-secondary-dark)";
/// <summary>
/// Secondary darker.
/// </summary>
public const string SecondaryDarker = "var(--rz-secondary-darker)";
/// <summary>
/// Info.
/// </summary>
public const string Info = "var(--rz-info)";
/// <summary>
/// Info lighter.
/// </summary>
public const string InfoLighter = "var(--rz-info-lighter)";
/// <summary>
/// Info light.
/// </summary>
public const string InfoLight = "var(--rz-info-light)";
/// <summary>
/// Info dark.
/// </summary>
public const string InfoDark = "var(--rz-info-dark)";
/// <summary>
/// Info darker.
/// </summary>
public const string InfoDarker = "var(--rz-info-darker)";
/// <summary>
/// Success.
/// </summary>
public const string Success = "var(--rz-success)";
/// <summary>
/// Success lighter.
/// </summary>
public const string SuccessLighter = "var(--rz-success-lighter)";
/// <summary>
/// Success light.
/// </summary>
public const string SuccessLight = "var(--rz-success-light)";
/// <summary>
/// Success dark.
/// </summary>
public const string SuccessDark = "var(--rz-success-dark)";
/// <summary>
/// Success darker.
/// </summary>
public const string SuccessDarker = "var(--rz-success-darker)";
/// <summary>
/// Warning.
/// </summary>
public const string Warning = "var(--rz-warning)";
/// <summary>
/// Warning lighter.
/// </summary>
public const string WarningLighter = "var(--rz-warning-lighter)";
/// <summary>
/// Warning light.
/// </summary>
public const string WarningLight = "var(--rz-warning-light)";
/// <summary>
/// Warning dark.
/// </summary>
public const string WarningDark = "var(--rz-warning-dark)";
/// <summary>
/// Warning darker.
/// </summary>
public const string WarningDarker = "var(--rz-warning-darker)";
/// <summary>
/// Danger.
/// </summary>
public const string Danger = "var(--rz-danger)";
/// <summary>
/// Danger lighter.
/// </summary>
public const string DangerLighter = "var(--rz-danger-lighter)";
/// <summary>
/// Danger light.
/// </summary>
public const string DangerLight = "var(--rz-danger-light)";
/// <summary>
/// Danger dark.
/// </summary>
public const string DangerDark = "var(--rz-danger-dark)";
/// <summary>
/// Danger darker.
/// </summary>
public const string DangerDarker = "var(--rz-danger-darker)";
/// <summary>
/// White.
/// </summary>
public const string White = "var(--rz-white)";
/// <summary>
/// Black.
/// </summary>
public const string Black = "var(--rz-black)";
/// <summary>
/// Base 50.
/// </summary>
public const string Base50 = "var(--rz-base-50)";
/// <summary>
/// Base 100.
/// </summary>
public const string Base100 = "var(--rz-base-100)";
/// <summary>
/// Base 200.
/// </summary>
public const string Base200 = "var(--rz-base-200)";
/// <summary>
/// Base 300.
/// </summary>
public const string Base300 = "var(--rz-base-300)";
/// <summary>
/// Base 400.
/// </summary>
public const string Base400 = "var(--rz-base-400)";
/// <summary>
/// Base 500.
/// </summary>
public const string Base500 = "var(--rz-base-500)";
/// <summary>
/// Base 600.
/// </summary>
public const string Base600 = "var(--rz-base-600)";
/// <summary>
/// Base 700.
/// </summary>
public const string Base700 = "var(--rz-base-700)";
/// <summary>
/// Base 800.
/// </summary>
public const string Base800 = "var(--rz-base-800)";
/// <summary>
/// Base 900.
/// </summary>
public const string Base900 = "var(--rz-base-900)";
/// <summary>
/// Series1.
/// </summary>
public const string Series1 = "var(--rz-series-1)";
/// <summary>
/// Series2.
/// </summary>
public const string Series2 = "var(--rz-series-2)";
/// <summary>
/// Series3.
/// </summary>
public const string Series3 = "var(--rz-series-3)";
/// <summary>
/// Series4.
/// </summary>
public const string Series4 = "var(--rz-series-4)";
/// <summary>
/// Series5.
/// </summary>
public const string Series5 = "var(--rz-series-5)";
/// <summary>
/// Series6.
/// </summary>
public const string Series6 = "var(--rz-series-6)";
/// <summary>
/// Series7.
/// </summary>
public const string Series7 = "var(--rz-series-7)";
/// <summary>
/// Series8.
/// </summary>
public const string Series8 = "var(--rz-series-8)";
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
namespace Radzen;
/// <summary>
/// Represents a filter in a component that supports filtering.
/// </summary>
public class CompositeFilterDescriptor
{
/// <summary>
/// Gets or sets the name of the filtered property.
/// </summary>
/// <value>The property.</value>
public string Property { get; set; }
/// <summary>
/// Gets or sets the property type.
/// </summary>
/// <value>The property type.</value>
public Type Type { get; set; }
/// <summary>
/// Gets or sets the name of the filtered property.
/// </summary>
/// <value>The property.</value>
public string FilterProperty { get; set; }
/// <summary>
/// Gets or sets the value to filter by.
/// </summary>
/// <value>The filter value.</value>
public object FilterValue { get; set; }
/// <summary>
/// Gets or sets the operator which will compare the property value with <see cref="FilterValue" />.
/// </summary>
/// <value>The filter operator.</value>
public FilterOperator? FilterOperator { get; set; }
/// <summary>
/// Gets or sets the logic used to combine the outcome of filtering by <see cref="FilterValue" />.
/// </summary>
/// <value>The logical filter operator.</value>
public LogicalFilterOperator LogicalFilterOperator { get; set; }
/// <summary>
/// Gets or sets the filters.
/// </summary>
/// <value>The filters.</value>
public IEnumerable<CompositeFilterDescriptor> Filters { get; set; }
}

Some files were not shown because too many files have changed in this diff Show More