Compare commits

...

347 Commits

Author SHA1 Message Date
Atanas Korchev
fc6fe54635 Fix 302 redirect during SSR that hurt SEO for clean URLs
During server-side prerendering the ThemeChanged handler would call
NavigateTo which resulted in a 302 redirect (e.g. /datagrid → /datagrid?theme=material3).
This conflicted with the canonical tag pointing back to the clean URL,
confusing Google and causing ranking drops. Unsubscribe from ThemeChanged
during SSR so the redirect no longer occurs. Interactive theme switching
is unaffected since WebAssembly uses a separate scoped instance.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-07 21:33:34 +02:00
Vladimir Enchev
505ffa7fe4 Splitter ChangeStateOnResize added 2026-02-06 12:36:29 +02:00
Vladimir Enchev
4f82ac9599 Accessing sub properties of nullable types improved 2026-02-06 10:36:53 +02:00
Vladimir Enchev
5bbf7c1fde build fixed 2026-02-04 14:46:25 +02:00
Vladimir Enchev
e416cede62 DropDownBase possible null reference exception with browser autofil 2026-02-04 12:08:41 +02:00
Vladimir Enchev
270a7e0f80 DropDown, ListBox and DropDownDatGrid paste using context menu into search box not filtering
Fix #2437
2026-02-04 11:28:12 +02:00
Vladimir Enchev
1a12a75bde Version updated 2026-02-03 09:56:04 +02:00
Vladimir Enchev
d1917eac0c Fixed QRCode eyes with transparent background 2026-02-03 08:44:24 +02:00
Vladimir Enchev
09830f0ea2 DataGrid possible memory leak fixed 2026-02-03 07:46:32 +02:00
Vladimir Enchev
d34e0684fb DataGrid GroupRowRenderEventArgs Expandable property added 2026-02-02 12:09:40 +02:00
Vladimir Enchev
482eca3278 tests fixed 2026-01-30 12:23:10 +02:00
Vladimir Enchev
5c8ac16c83 Version updated 2026-01-30 12:09:54 +02:00
Vladimir Enchev
29382cf0f4 DataGrid QueryOnlyVisibleColumns property added 2026-01-30 12:09:34 +02:00
Vladimir Enchev
53204cc8d6 RadzenPager GoToPage() will not update page index 2026-01-30 09:53:52 +02:00
Vladimir Enchev
6cf550c517 Version updated 2026-01-28 17:54:45 +02:00
vadimstrekha
7bf107af4c Fix sytax error in Radzen.Blazor.js (#2436) 2026-01-28 17:53:21 +02:00
Vladimir Enchev
adf2785a5a Version updated 2026-01-28 15:31:27 +02:00
Vladimir Enchev
cae44df00a Bar Charts have a zero for the min and max on the y-axis
Fix #2434
2026-01-28 15:31:05 +02:00
Vladimir Enchev
8ba1c69573 RadzenQRCode and RadzenBarcode ToSvg() methods added 2026-01-28 10:31:13 +02:00
Vladimir Enchev
56031c2fd4 QRCode and Barcode save to SVG examples added 2026-01-28 10:00:24 +02:00
Vladimir Enchev
ad44802d30 RadzenBarcodeEncoder and RadzenQREncoder made public
Fix #2433
2026-01-27 18:27:40 +02:00
Vladimir Enchev
64ca088e61 ListBox duplicates the first typed letter in WASM when inside Popup
Fix #2429
2026-01-27 14:08:46 +02:00
Theronguard
f4777565a2 Changed the method signature to virtual, to allow overrding Enum translation with component activators (#2432) 2026-01-27 13:14:28 +02:00
Vladimir Enchev
eb1423e757 Version update 2026-01-27 08:34:44 +02:00
Vladimir Enchev
596b251511 DataGrid column custom filter indicator is active even when no filter 2026-01-27 08:34:29 +02:00
Vladimir Enchev
e186315935 DataGrid grouping arrows do not show expanded state
Fix #2431
2026-01-27 08:27:26 +02:00
yordanov
cba9a5120d Reorder examples 2026-01-26 16:14:07 +02:00
Vladimir Enchev
ee62a21ab6 version updated 2026-01-26 14:36:48 +02:00
yordanov
0a5e318f80 Update premium themes 2026-01-26 14:34:01 +02:00
Vladimir Enchev
8dd7d7f521 DataGrid simple string filter clear button not shown 2026-01-26 10:34:05 +02:00
Vladimir Enchev
69573b2d7d demo updated 2026-01-26 10:06:23 +02:00
Vladimir Enchev
5b933c6643 DataGrid reorder column stick to mouse 2026-01-23 12:11:44 +02:00
Atanas Korchev
126b2d1efa RadzenSpiderChart (#2417)
* spider chart added

* code improved

* EventConsole added

* Pastel is the default color scheme

* Fix color schemes in Spider chart

* Update SpiderChart styles

---------

Co-authored-by: Ehab Hussein <me@ehabhussein.com>
Co-authored-by: Vladimir Enchev <vladimir.enchev@gmail.com>
Co-authored-by: yordanov <vasil@yordanov.info>
2026-01-22 14:27:01 +02:00
Vladimir Enchev
a65c1a9482 version updated 2026-01-21 17:31:55 +02:00
Vladimir Enchev
4939e8498a OData filtering by no string columns fixed 2026-01-21 17:31:33 +02:00
Vladimir Enchev
e380467853 Version updated 2026-01-21 10:17:06 +02:00
Vladimir Enchev
d3adc9733b PivotDataGrid user interaction breaks multi-column/row sorting
Fix #2427
2026-01-20 13:37:13 +02:00
Vladimir Enchev
a03fc50ee8 DataFilter should set filter Type on property change 2026-01-20 09:31:07 +02:00
Mason Voxland
c9201bf947 Fix TextProperty documentation in RadzenScheduler (#2426)
Should be string, not DateTime
2026-01-20 09:17:23 +02:00
yordanov
8c8d288afd Fix #2188 rz-layout height should be 100dvh by default 2026-01-19 17:15:40 +02:00
Vladimir Enchev
1a679af008 Chart demo source code fixed 2026-01-19 16:00:26 +02:00
yordanov
bc1654a405 Fix #2218 - RadzenSidebar border should take into account the sidebar's position 2026-01-19 13:12:54 +02:00
Atanas Korchev
94ef62e00b Customize split button dropdown icon demo is showing wrong source code. 2026-01-19 11:33:24 +02:00
Vladimir Enchev
13afd0fddb Version updated 2026-01-19 10:43:13 +02:00
Vladimir Enchev
fa7120571d DataGrid, DataFilter and PivotDataGrid numeric filter cannot be cleared 2026-01-19 10:42:35 +02:00
Vladimir Enchev
4d4e882dcc Upload API example fixed 2026-01-19 09:39:50 +02:00
Vladimir Enchev
af8c7179d1 Pivot Grid Sorting: SortOrder="SortOrder.Descending" can break sort controls
Fix #2425
2026-01-19 09:31:31 +02:00
Vladimir Enchev
c743054184 RadzenAIChat sometimes first character not detected in chat input field
Fix #2424
2026-01-19 09:17:31 +02:00
Vladimir Enchev
935d850296 Version updated 2026-01-16 11:46:46 +02:00
Vladimir Enchev
ad6b6886f1 ToFilterString() and ToODataString() fixed for non string values 2026-01-16 09:33:21 +02:00
Maxim Becker
d5d08c89de Fix filtering in RadzenAutoComplete if Data is simple list of strings (#2422) 2026-01-16 09:22:46 +02:00
Vladimir Enchev
5a246f732e Version updated 2026-01-15 18:28:58 +02:00
Vladimir Enchev
669ad421d9 Chart 0 at beginning and end of x-axis 2026-01-15 18:28:39 +02:00
Vladimir Enchev
98e8393d14 DataGrid column UniqueID initialization fixed 2026-01-15 18:08:38 +02:00
tharreck
de8dfb7e93 Fix issue where selectedItem is not updated when value is set to null in dropdown. (#2420)
Co-authored-by: Steven Torrelle <steven.torrelle@uzgent.be>
2026-01-15 17:14:16 +02:00
Vladimir Enchev
61b32d6490 RadzenDropDown with AllowFiltering and single selection should close on ENTER 2026-01-15 09:21:08 +02:00
Vladimir Enchev
5057fb1cc8 PivotDataGrid row/col groups should respect sorting
Fix #2413
2026-01-14 16:07:14 +02:00
Vladimir Enchev
ed49dfc2fa DatePicke invalid cast with nullable DateTimeOffset 2026-01-14 13:52:29 +02:00
Vladimir Enchev
839018795c version updated 2026-01-13 14:28:29 +02:00
yordanov
c541f9d3e5 Style borders demos 2026-01-13 14:11:02 +02:00
yordanov
5e5957e4c4 Add video to borders' demos 2026-01-13 14:05:46 +02:00
yordanov
84a759267e Style Barcode demo 2026-01-13 13:48:58 +02:00
Vladimir Enchev
eb1e6fab0d Barcode added (#2416)
demo improved

various fixes

more fixes
2026-01-13 10:44:09 +02:00
Vladimir Enchev
6094953009 DataGrid drag to group or reorder restricted over grid only 2026-01-13 10:41:01 +02:00
Vladimir Enchev
b845c73ad6 DataGrid cannot group by column in some cases 2026-01-13 10:00:58 +02:00
Vladimir Enchev
2788606105 Version updated 2026-01-12 17:09:07 +02:00
Vladimir Enchev
aebf267320 Scheduler Planner/Year view: appointments missing when start month is January
Fix #2415
2026-01-12 17:08:37 +02:00
Vladimir Enchev
33896ea8db RadzenChat user is typing option added
Fix #2376
2026-01-12 10:51:11 +02:00
Vladimir Enchev
cb8699f315 RadzenTabs aria-selected accessibility issue 2026-01-12 09:50:09 +02:00
Vladimir Enchev
2a43db6560 RadzenChat always eats first key press after sending first message 2026-01-12 09:37:35 +02:00
Pavlo Iatsiuk
0ad1200870 #2406 - Allow to define Http method for Upload component (#2407)
* #2406 - Allow to define Http method for Upload component

* #2406 - Allow to define Http method for Upload component and stream raw file data

* #2406 - Allow to define Http method for Upload component
Example how to stream file

---------

Co-authored-by: Pavlo Iatsiuk <pavlo.iatsiuk@nems.eco>
2026-01-12 09:22:15 +02:00
yordanov
2debdbfd38 Update copyright year 2026-01-11 13:20:15 +02:00
tharreck
07c96bd4bd fix strange oninput behavior on textbox and textarea (server rendering) (#2410)
* use bind:get and bind:set to fix  strange behavior on input while using server rendering

* only update the Immediate textarea and textbox

---------

Co-authored-by: Steven Torrelle <steven.torrelle@uzgent.be>
2026-01-09 14:29:03 +02:00
Vladimir Enchev
704c6abe7c Version updated 2026-01-09 08:09:02 +02:00
Vladimir Enchev
e0e0e608e2 DataGrid ToFilterString() fixed
Fix #2411
2026-01-09 08:07:17 +02:00
Vladimir Enchev
40e15a0720 Symbols package fixed 2026-01-08 10:48:34 +02:00
Vladimir Enchev
d6235fd147 Version updated 2026-01-08 10:24:26 +02:00
Vladimir Enchev
c5a3a911e0 DataGrid filtering by IsNull/IsNotNull fixed 2026-01-08 10:08:54 +02:00
Vladimir Enchev
4e7a037bc5 RadzenPickList selection by ValueProperty fixed 2026-01-08 10:08:29 +02:00
Vladimir Enchev
f1c3f46ad7 RadzenPickList ValueProperty added 2026-01-08 09:22:07 +02:00
Vladimir Enchev
6c0e39da20 DataGrid filter operators should check if the property is nullable 2026-01-07 13:48:59 +02:00
Vladimir Enchev
2a9d638acf null item selection restored 2026-01-07 13:23:04 +02:00
Vladimir Enchev
426e1ba8e8 DropDownBase/ListBox/DataGridHeaderCell selection of null item fixed 2026-01-07 13:03:07 +02:00
Vladimir Enchev
1bb36ef207 demo fixed 2026-01-07 10:06:45 +02:00
Vladimir Enchev
67cde0fd59 RadzenDropDownDataGrid paging fixed 2026-01-07 09:50:50 +02:00
Vladimir Enchev
0696cd20d5 Various warnings resolved and TreatWarningsAsErrors enabled (#2409) 2026-01-07 09:29:58 +02:00
Vladimir Enchev
34fce5188f DataGrid should not call Count() more than once 2026-01-07 09:11:33 +02:00
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
1026 changed files with 47483 additions and 11788 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

1
.gitignore vendored
View File

@@ -347,3 +347,4 @@ Radzen.Blazor.min.js
*.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?

85
Directory.Build.props Normal file
View File

@@ -0,0 +1,85 @@
<Project>
<!--
Common build properties for all projects in the Radzen.Blazor solution.
To use this file:
1. Rename to Directory.Build.props (remove .sample extension)
2. Adjust settings based on your needs
3. Review the analyzer settings in .editorconfig
This file will be automatically imported by all projects in subdirectories.
-->
<PropertyGroup Label="Language Configuration">
<!-- Use latest C# language features -->
<LangVersion>latest</LangVersion>
<!-- Do NOT enable implicit usings - explicit imports preferred for library code -->
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>CA2007</NoWarn>
</PropertyGroup>
<PropertyGroup Label="Code Analysis Configuration">
<!-- Enable .NET code analyzers -->
<AnalysisLevel>latest</AnalysisLevel>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<!-- Run analyzers during build and in IDE -->
<RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild>
<RunAnalyzersDuringLiveAnalysis>true</RunAnalyzersDuringLiveAnalysis>
<!-- Don't enforce code style in build (yet) - just show warnings -->
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<!-- Don't treat warnings as errors (yet) - too many to fix immediately -->
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<!-- Report all analyzer diagnostics -->
<AnalysisMode>All</AnalysisMode>
</PropertyGroup>
<PropertyGroup Label="Build Quality">
<!-- Enable deterministic builds for reproducibility -->
<Deterministic>true</Deterministic>
<!-- Enable deterministic builds in CI/CD -->
<ContinuousIntegrationBuild Condition="'$(CI)' == 'true'">true</ContinuousIntegrationBuild>
<!-- Embed source files for better debugging -->
<EmbedAllSources>true</EmbedAllSources>
<!--
IMPORTANT:
- NuGet symbol packages (.snupkg) require portable PDB files.
- If DebugType=embedded, there are no standalone PDBs, so the .snupkg ends up effectively empty.
Use portable PDBs when symbols are enabled; otherwise use embedded for local debugging convenience.
-->
<!--
NOTE: Directory.Build.props is imported before project files, so properties like IncludeSymbols
set in a .csproj may not be available yet for Conditions here.
IsPacking *is* set by `dotnet pack`, so use that to switch DebugType for symbol packages.
-->
<DebugType Condition="'$(IsPacking)' == 'true'">portable</DebugType>
<DebugType Condition="'$(IsPacking)' != 'true'">embedded</DebugType>
</PropertyGroup>
<PropertyGroup Label="Demos and Tests Project Configuration" Condition="$(MSBuildProjectName.Contains('Demos')) OR $(MSBuildProjectName.Contains('Tests'))">
<!-- Demo projects and Tests should not be packable -->
<IsPackable>false</IsPackable>
<!-- DISABLE ALL ANALYZERS FOR DEMO PROJECTS AND TESTS -->
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<RunAnalyzersDuringBuild>false</RunAnalyzersDuringBuild>
<RunAnalyzersDuringLiveAnalysis>false</RunAnalyzersDuringLiveAnalysis>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
</PropertyGroup>
<PropertyGroup Label="Performance">
<!-- Optimize startup time -->
<TieredCompilation>true</TieredCompilation>
<TieredCompilationQuickJit>true</TieredCompilationQuickJit>
</PropertyGroup>
</Project>

View File

@@ -1,24 +1,48 @@
# syntax=docker/dockerfile:1
FROM mcr.microsoft.com/dotnet/sdk:9.0
# =============================
# BUILD STAGE
# =============================
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY Radzen.Blazor /app/Radzen.Blazor
COPY Radzen.DocFX /app/Radzen.DocFX
COPY RadzenBlazorDemos /app/RadzenBlazorDemos
COPY RadzenBlazorDemos.Host /app/RadzenBlazorDemos.Host
# Copy project files first for better caching
COPY Radzen.Blazor/*.csproj Radzen.Blazor/
COPY RadzenBlazorDemos/*.csproj RadzenBlazorDemos/
COPY RadzenBlazorDemos.Host/*.csproj RadzenBlazorDemos.Host/
WORKDIR /app
# Radzen.DocFX usually has no csproj → copy full folder
COPY Radzen.DocFX/ Radzen.DocFX/
# 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"
RUN wget https://dot.net/v1/dotnet-install.sh \
&& bash dotnet-install.sh --channel 8.0 --runtime dotnet --install-dir /usr/share/dotnet
# 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
WORKDIR /app/RadzenBlazorDemos.Host
RUN dotnet publish -c Release -o out
# Publish the Blazor host app
WORKDIR /src/RadzenBlazorDemos.Host
RUN dotnet publish -c Release -o /app/out
ENV ASPNETCORE_URLS=http://*:5000
WORKDIR /app/RadzenBlazorDemos.Host/out
# =============================
# RUNTIME STAGE
# =============================
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
WORKDIR /app
# Copy only published output
COPY --from=build /app/out ./
# Set runtime URL
ENV ASPNETCORE_URLS=http://+:5000
ENTRYPOINT ["dotnet", "RadzenBlazorDemos.Host.dll"]

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2018-2025 Radzen Ltd
Copyright (c) 2018-2026 Radzen Ltd
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

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,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,77 @@
using Xunit;
using System.Collections;
using Bunit;
using Xunit;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
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()
{
@@ -70,5 +138,30 @@ namespace Radzen.Blazor.Tests
Assert.Equal("additional-name", AutoCompleteType.MiddleName.GetAutoCompleteValue());
Assert.Equal("family-name", AutoCompleteType.LastName.GetAutoCompleteValue());
}
[Fact]
public void AutoComplete_Filters_StringList()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var data = new List<string> { "Apple", "Banana", "Cherry" };
var component = ctx.RenderComponent<AutoCompleteWithAccessibleView>(parameters =>
{
parameters
.Add(p => p.Data, data)
.Add(p => p.SearchText, "Ban")
.Add(p => p.OpenOnFocus, true);
});
Assert.Contains("Banana", component.Instance.CurrentView.OfType<string>());
Assert.DoesNotContain("Apple", component.Instance.CurrentView.OfType<string>());
Assert.DoesNotContain("Cherry", component.Instance.CurrentView.OfType<string>());
}
private sealed class AutoCompleteWithAccessibleView : RadzenAutoComplete
{
public IEnumerable CurrentView => View;
}
}
}

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

@@ -1,11 +1,15 @@
using System;
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
public class DialogServiceTests : ComponentBase
{
public class OpenDialogTests
{
@@ -124,13 +128,81 @@ namespace Radzen.Blazor.Tests
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")]

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

@@ -686,5 +686,58 @@ namespace Radzen.Blazor.Tests
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);
}
}
}

View File

@@ -22,7 +22,7 @@ namespace Radzen.Blazor.Tests
public Guid Id { get; set; }
public TimeOnly StartTime { get; set; }
public DateOnly BirthDate { get; set; }
public int[] Scores { get; set; }
public IEnumerable<int> Scores { get; set; }
public List<string> Tags { get; set; }
public List<TestEntity> Children { get; set; }
public Address Address { get; set; }

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

@@ -141,4 +141,28 @@ foo <!---> foo -->
{
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,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

@@ -2,6 +2,7 @@ using Bunit;
using Bunit.JSInterop;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Xunit;
namespace Radzen.Blazor.Tests
@@ -54,7 +55,7 @@ namespace Radzen.Blazor.Tests
}
[Fact]
public async void RadzenPager_Renders_Summary() {
public async Task RadzenPager_Renders_Summary() {
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
@@ -64,7 +65,7 @@ namespace Radzen.Blazor.Tests
parameters.Add<int>(p => p.Count, 100);
parameters.Add<bool>(p => p.ShowPagingSummary, true);
});
await component.Instance.GoToPage(2);
await component.InvokeAsync(() => component.Instance.GoToPage(2));
component.Render();
Assert.Contains(@$"rz-pager-summary", component.Markup);
@@ -111,7 +112,7 @@ namespace Radzen.Blazor.Tests
}
[Fact]
public async void RadzenPager_First_And_Prev_Buttons_Are_Disabled_When_On_The_First_Page()
public async Task RadzenPager_First_And_Prev_Buttons_Are_Disabled_When_On_The_First_Page()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
@@ -123,7 +124,7 @@ namespace Radzen.Blazor.Tests
parameters.Add<bool>(p => p.ShowPagingSummary, true);
});
await component.Instance.GoToPage(0);
await component.InvokeAsync(() => component.Instance.GoToPage(0));
component.Render();
var firstPageButton = component.Find("a.rz-pager-first");
@@ -134,7 +135,7 @@ namespace Radzen.Blazor.Tests
}
[Fact]
public async void RadzenPager_Last_And_Next_Buttons_Are_Disabled_When_On_The_Last_Page()
public async Task RadzenPager_Last_And_Next_Buttons_Are_Disabled_When_On_The_Last_Page()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
@@ -146,7 +147,7 @@ namespace Radzen.Blazor.Tests
parameters.Add<bool>(p => p.ShowPagingSummary, true);
});
await component.Instance.GoToPage(9);
await component.InvokeAsync(() => component.Instance.GoToPage(9));
component.Render();
var lastPageButton = component.Find("a.rz-pager-last");

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,220 @@
using Bunit;
using Xunit;
using System.Collections.Generic;
using System.Linq;
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);
}
[Fact]
public void PickList_GetSelectedSources_Respects_ValueProperty_Single()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var data = new List<Item>
{
new Item { Id = 1, Name = "A" },
new Item { Id = 2, Name = "B" }
};
var component = ctx.RenderComponent<RadzenPickList<Item>>(parameters =>
{
parameters.Add(p => p.Source, data);
parameters.Add(p => p.TextProperty, "Name");
parameters.Add(p => p.ValueProperty, "Id");
parameters.Add(p => p.Multiple, false);
});
// Simulate ListBox selection when ValueProperty is set: selectedSourceItems becomes the value (Id)
var field = typeof(RadzenPickList<Item>).GetField("selectedSourceItems", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
field.SetValue(component.Instance, 2);
var selected = component.Instance.GetSelectedSources();
Assert.Single(selected);
Assert.Equal(2, selected.First().Id);
}
[Fact]
public void PickList_GetSelectedSources_Respects_ValueProperty_Multiple()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
var data = new List<Item>
{
new Item { Id = 1, Name = "A" },
new Item { Id = 2, Name = "B" },
new Item { Id = 3, Name = "C" }
};
var component = ctx.RenderComponent<RadzenPickList<Item>>(parameters =>
{
parameters.Add(p => p.Source, data);
parameters.Add(p => p.TextProperty, "Name");
parameters.Add(p => p.ValueProperty, "Id");
parameters.Add(p => p.Multiple, true);
});
// Simulate ListBox selection when ValueProperty is set: selectedSourceItems becomes IEnumerable of values (Ids)
var field = typeof(RadzenPickList<Item>).GetField("selectedSourceItems", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
field.SetValue(component.Instance, new[] { 1, 3 });
var selected = component.Instance.GetSelectedSources().Select(i => i.Id).OrderBy(i => i).ToArray();
Assert.Equal(new[] { 1, 3 }, selected);
}
}
}

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

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using Xunit;
@@ -114,6 +114,11 @@ namespace Radzen.Blazor.Tests
public List<string> Values { get; set; }
}
public class Order
{
public DateTime? OrderDate { get; set; }
}
[Fact]
public void GetProperty_Should_Resolve_DescriptionProperty()
{
@@ -137,6 +142,14 @@ namespace Radzen.Blazor.Tests
Assert.NotNull(idProperty);
}
[Fact]
public void GetPropertyType_Resolves_NullableDateTime_Date()
{
var propertyType = PropertyAccess.GetPropertyType(typeof(Order), "OrderDate.Date");
Assert.Equal(typeof(DateTime), propertyType);
}
interface ISimpleInterface : ISimpleNestedInterface
{
string Description { 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>
<Nullable>disable</Nullable>
<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,63 @@
using Bunit;
using Microsoft.JSInterop;
using Microsoft.Extensions.DependencyInjection;
using Radzen.Blazor;
using Radzen.Blazor.Rendering;
using Radzen;
using System;
using System.Collections.Generic;
using System.Globalization;
using Xunit;
namespace Radzen.Blazor.Tests
{
public class SchedulerYearRangeTests
{
class Appointment
{
public DateTime Start { get; set; }
public DateTime End { get; set; }
public string Text { get; set; } = "";
}
[Fact]
public void YearView_StartMonthJanuary_IncludesLastDaysOfYear_WhenYearStartsOnFirstDayOfWeek()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.Services.AddScoped<DialogService>();
ctx.JSInterop.Setup<Rect>("Radzen.createScheduler", _ => true)
.SetResult(new Rect { Left = 0, Top = 0, Width = 200, Height = 200 });
// Make the first day of week Monday and use a year where Jan 1 is Monday (2024-01-01).
var culture = (CultureInfo)CultureInfo.InvariantCulture.Clone();
culture.DateTimeFormat.FirstDayOfWeek = DayOfWeek.Monday;
var appointments = new List<Appointment>
{
new() { Start = new DateTime(2024, 12, 31), End = new DateTime(2025, 1, 1), Text = "Year end" }
};
var cut = ctx.RenderComponent<RadzenScheduler<Appointment>>(p =>
{
p.Add(x => x.Culture, culture);
p.Add(x => x.Date, new DateTime(2024, 6, 1));
p.Add(x => x.Data, appointments);
p.Add(x => x.StartProperty, nameof(Appointment.Start));
p.Add(x => x.EndProperty, nameof(Appointment.End));
p.Add(x => x.TextProperty, nameof(Appointment.Text));
p.AddChildContent<RadzenYearView>(v => v.Add(x => x.StartMonth, Radzen.Month.January));
});
var view = Assert.IsType<RadzenYearView>(cut.Instance.SelectedView);
// View should start on 2023-12-25 (one extra week above since 2024-01-01 is Monday).
Assert.Equal(new DateTime(2023, 12, 25), view.StartDate.Date);
// View end must include 2024-12-31 (it should extend to end-of-week containing the real year end).
Assert.True(view.EndDate.Date >= new DateTime(2024, 12, 31), $"EndDate was {view.EndDate:yyyy-MM-dd}");
}
}
}

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

@@ -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

@@ -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

@@ -6,6 +6,48 @@ root = true
#### Core EditorConfig Options ####
dotnet_diagnostic.CA1002.severity = none
dotnet_diagnostic.CA1003.severity = none
dotnet_diagnostic.CA1024.severity = none
dotnet_diagnostic.CA1030.severity = none
dotnet_diagnostic.CA1031.severity = none
dotnet_diagnostic.CA1033.severity = none
dotnet_diagnostic.CA1044.severity = none
dotnet_diagnostic.CA1050.severity = none
dotnet_diagnostic.CA1051.severity = none
dotnet_diagnostic.CA1052.severity = none
dotnet_diagnostic.CA1054.severity = none
dotnet_diagnostic.CA1055.severity = none
dotnet_diagnostic.CA1056.severity = none
dotnet_diagnostic.CA1063.severity = none
dotnet_diagnostic.CA1068.severity = none
dotnet_diagnostic.CA1308.severity = none
dotnet_diagnostic.CA1708.severity = none
dotnet_diagnostic.CA1711.severity = none
dotnet_diagnostic.CA1716.severity = none
dotnet_diagnostic.CA1720.severity = none
dotnet_diagnostic.CA1721.severity = none
dotnet_diagnostic.CA1724.severity = none
dotnet_diagnostic.CA1725.severity = none
dotnet_diagnostic.CA1802.severity = none
dotnet_diagnostic.CA1814.severity = none
dotnet_diagnostic.CA1815.severity = none
dotnet_diagnostic.CA1816.severity = none
dotnet_diagnostic.CA1819.severity = none
dotnet_diagnostic.CA1822.severity = none
dotnet_diagnostic.CA1827.severity = none
dotnet_diagnostic.CA1834.severity = none
dotnet_diagnostic.CA1845.severity = none
dotnet_diagnostic.CA1849.severity = none
dotnet_diagnostic.CA1851.severity = none
dotnet_diagnostic.CA1859.severity = none
dotnet_diagnostic.CA1863.severity = none
dotnet_diagnostic.CA1869.severity = none
dotnet_diagnostic.CA2007.severity = none
dotnet_diagnostic.CA2012.severity = none
dotnet_diagnostic.CA2211.severity = none
dotnet_diagnostic.CA2227.severity = none
# Indentation and spacing
indent_size = 4
indent_style = space

View File

@@ -13,218 +13,16 @@ using System.Linq;
namespace Radzen;
/// <summary>
/// Represents a chat message in the conversation history.
/// </summary>
public class ChatMessage
{
/// <summary>
/// Gets or sets the role of the message sender (system, user, or assistant).
/// </summary>
public string Role { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the content of the message.
/// </summary>
public string Content { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the timestamp when the message was created.
/// </summary>
public DateTime Timestamp { get; set; } = DateTime.Now;
}
/// <summary>
/// Represents a conversation session with memory.
/// </summary>
public class ConversationSession
{
/// <summary>
/// Gets or sets the unique identifier for the conversation session.
/// </summary>
public string Id { get; set; } = Guid.NewGuid().ToString();
/// <summary>
/// Gets or sets the list of messages in the conversation.
/// </summary>
public List<ChatMessage> Messages { get; set; } = new();
/// <summary>
/// Gets or sets the timestamp when the conversation was created.
/// </summary>
public DateTime CreatedAt { get; set; } = DateTime.Now;
/// <summary>
/// Gets or sets the timestamp when the conversation was last updated.
/// </summary>
public DateTime LastUpdated { get; set; } = DateTime.Now;
/// <summary>
/// Gets or sets the maximum number of messages to keep in memory.
/// </summary>
public int MaxMessages { get; set; } = 50;
/// <summary>
/// Adds a message to the conversation and manages memory limits.
/// </summary>
/// <param name="role">The role of the message sender.</param>
/// <param name="content">The message content.</param>
public void AddMessage(string role, string content)
{
Messages.Add(new ChatMessage
{
Role = role,
Content = content,
Timestamp = DateTime.Now
});
LastUpdated = DateTime.Now;
// Remove oldest messages if we exceed the limit
while (Messages.Count > MaxMessages)
{
Messages.RemoveAt(0);
}
}
/// <summary>
/// Clears all messages from the conversation.
/// </summary>
public void Clear()
{
Messages.Clear();
LastUpdated = DateTime.Now;
}
/// <summary>
/// Gets the conversation messages formatted for the AI API.
/// </summary>
/// <param name="systemPrompt">The system prompt to include.</param>
/// <returns>A list of message objects for the AI API.</returns>
public List<object> GetFormattedMessages(string systemPrompt)
{
var messages = new List<object>();
// Add system message
messages.Add(new { role = "system", content = systemPrompt });
// Add conversation messages
foreach (var message in Messages)
{
messages.Add(new { role = message.Role, content = message.Content });
}
return messages;
}
}
/// <summary>
/// Interface for getting chat completions from an AI model with conversation memory.
/// </summary>
public interface IAIChatService
{
/// <summary>
/// Streams chat completion responses from the AI model asynchronously with conversation memory.
/// </summary>
/// <param name="userInput">The user's input message to send to the AI model.</param>
/// <param name="sessionId">Optional session ID to maintain conversation context. If null, a new session will be created.</param>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
/// <param name="model">Optional model name to override the configured model.</param>
/// <param name="systemPrompt">Optional system prompt to override the configured system prompt.</param>
/// <param name="temperature">Optional temperature to override the configured temperature.</param>
/// <param name="maxTokens">Optional maximum tokens to override the configured max tokens.</param>
/// <returns>An async enumerable that yields streaming response chunks from the AI model.</returns>
IAsyncEnumerable<string> GetCompletionsAsync(string userInput, string sessionId = null, CancellationToken cancellationToken = default, string model = null, string systemPrompt = null, double? temperature = null, int? maxTokens = null);
/// <summary>
/// Gets or creates a conversation session.
/// </summary>
/// <param name="sessionId">The session ID. If null, a new session will be created.</param>
/// <returns>The conversation session.</returns>
ConversationSession GetOrCreateSession(string sessionId = null);
/// <summary>
/// Clears the conversation history for a specific session.
/// </summary>
/// <param name="sessionId">The session ID to clear.</param>
void ClearSession(string sessionId);
/// <summary>
/// Gets all active conversation sessions.
/// </summary>
/// <returns>A list of active conversation sessions.</returns>
IEnumerable<ConversationSession> GetActiveSessions();
/// <summary>
/// Removes old conversation sessions based on age.
/// </summary>
/// <param name="maxAgeHours">Maximum age in hours for sessions to keep.</param>
void CleanupOldSessions(int maxAgeHours = 24);
}
/// <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;
}
/// <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();
private readonly Dictionary<string, ConversationSession> sessions = new();
private readonly object sessionsLock = new();
// Add this static field to cache the JsonSerializerOptions instance
private static readonly JsonSerializerOptions CachedJsonSerializerOptions = new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull };
/// <summary>
/// Gets the configuration options for the chat streaming service.
@@ -232,7 +30,7 @@ public class AIChatService(IServiceProvider serviceProvider, IOptions<AIChatServ
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)
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))
{
@@ -241,11 +39,14 @@ public class AIChatService(IServiceProvider serviceProvider, IOptions<AIChatServ
// Get or create session
var session = GetOrCreateSession(sessionId);
// Add user message to conversation history
session.AddMessage("user", userInput);
var url = Options.Proxy ?? Options.Endpoint;
// 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);
@@ -259,41 +60,45 @@ public class AIChatService(IServiceProvider serviceProvider, IOptions<AIChatServ
stream = true
};
var request = new HttpRequestMessage(HttpMethod.Post, url)
using var request = new HttpRequestMessage(HttpMethod.Post, url)
{
Content = new StringContent(JsonSerializer.Serialize(payload, new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }), Encoding.UTF8, "application/json")
Content = new StringContent(JsonSerializer.Serialize(payload, CachedJsonSerializerOptions), Encoding.UTF8, "application/json")
};
if (!string.IsNullOrEmpty(Options.ApiKey))
if (!string.IsNullOrEmpty(effectiveApiKey))
{
if (string.Equals(Options.ApiKeyHeader, "Authorization", StringComparison.OrdinalIgnoreCase))
if (string.IsNullOrWhiteSpace(effectiveApiKeyHeader))
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Options.ApiKey);
throw new InvalidOperationException("API key header must be specified when an API key is provided.");
}
if (string.Equals(effectiveApiKeyHeader, "Authorization", StringComparison.OrdinalIgnoreCase))
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", effectiveApiKey);
}
else
{
request.Headers.Add(Options.ApiKeyHeader, Options.ApiKey);
request.Headers.Add(effectiveApiKeyHeader, effectiveApiKey);
}
}
var httpClient = serviceProvider.GetRequiredService<HttpClient>();
var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
if (!response.IsSuccessStatusCode)
{
throw new Exception($"Chat stream failed: {await response.Content.ReadAsStringAsync(cancellationToken)}");
throw new HttpRequestException($"Chat stream failed: {await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false)}");
}
using var stream = await response.Content.ReadAsStreamAsync(cancellationToken);
using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
using var reader = new StreamReader(stream);
var assistantResponse = new StringBuilder();
while (!reader.EndOfStream && !cancellationToken.IsCancellationRequested)
string? line;
while ((line = await reader.ReadLineAsync()) is not null && !cancellationToken.IsCancellationRequested)
{
var line = await reader.ReadLineAsync();
if (string.IsNullOrWhiteSpace(line) || !line.StartsWith("data:"))
if (string.IsNullOrWhiteSpace(line) || !line.StartsWith("data:", StringComparison.Ordinal))
{
continue;
}
@@ -321,23 +126,23 @@ public class AIChatService(IServiceProvider serviceProvider, IOptions<AIChatServ
}
/// <inheritdoc />
public ConversationSession GetOrCreateSession(string sessionId = null)
public ConversationSession GetOrCreateSession(string? sessionId = null)
{
lock (_sessionsLock)
lock (sessionsLock)
{
if (string.IsNullOrEmpty(sessionId))
{
sessionId = Guid.NewGuid().ToString();
}
if (!_sessions.TryGetValue(sessionId, out var session))
if (!sessions.TryGetValue(sessionId, out var session))
{
session = new ConversationSession
{
Id = sessionId,
MaxMessages = Options.MaxMessages
};
_sessions[sessionId] = session;
sessions[sessionId] = session;
}
return session;
@@ -347,9 +152,9 @@ public class AIChatService(IServiceProvider serviceProvider, IOptions<AIChatServ
/// <inheritdoc />
public void ClearSession(string sessionId)
{
lock (_sessionsLock)
lock (sessionsLock)
{
if (_sessions.TryGetValue(sessionId, out var session))
if (sessions.TryGetValue(sessionId, out var session))
{
session.Clear();
}
@@ -359,35 +164,40 @@ public class AIChatService(IServiceProvider serviceProvider, IOptions<AIChatServ
/// <inheritdoc />
public IEnumerable<ConversationSession> GetActiveSessions()
{
lock (_sessionsLock)
lock (sessionsLock)
{
return _sessions.Values.ToList();
return sessions.Values.ToList();
}
}
/// <inheritdoc />
public void CleanupOldSessions(int maxAgeHours = 24)
{
lock (_sessionsLock)
lock (sessionsLock)
{
var cutoffTime = DateTime.Now.AddHours(-maxAgeHours);
var sessionsToRemove = _sessions.Values
var sessionsToRemove = sessions.Values
.Where(s => s.LastUpdated < cutoffTime)
.Select(s => s.Id)
.ToList();
foreach (var sessionId in sessionsToRemove)
{
_sessions.Remove(sessionId);
sessions.Remove(sessionId);
}
}
}
private static string ParseStreamingResponse(string json)
{
if (string.IsNullOrWhiteSpace(json))
{
return string.Empty;
}
try
{
var doc = JsonDocument.Parse(json);
using var doc = JsonDocument.Parse(json);
var root = doc.RootElement;
if (!root.TryGetProperty("choices", out var choices) || choices.GetArrayLength() == 0)
@@ -409,52 +219,13 @@ public class AIChatService(IServiceProvider serviceProvider, IOptions<AIChatServ
return string.Empty;
}
catch
catch (JsonException)
{
return string.Empty;
}
catch (FormatException)
{
return string.Empty;
}
}
}
/// <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,41 @@
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)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(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; }
/// <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

@@ -22,19 +22,19 @@ namespace Radzen.Blazor
/// Gets or sets the text of the appointment.
/// </summary>
/// <value>The text.</value>
public string Text { get; set; }
public string? Text { get; set; }
/// <summary>
/// Gets or sets the data associated with the appointment
/// </summary>
/// <value>The data.</value>
public object Data { get; set; }
public object? Data { get; set; }
/// <summary>
/// Determines whether the specified object is equal to this instance. Used to check if two appointments are equal.
/// </summary>
/// <param name="obj">The object to compare with this instance.</param>
/// <returns><c>true</c> if the specified is equal to this instance; otherwise, <c>false</c>.</returns>
public override bool Equals(object obj)
public override bool Equals(object? obj)
{
return obj is AppointmentData data &&
Start == data.Start &&

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

@@ -13,7 +13,7 @@ namespace Radzen.Blazor
/// </summary>
/// <value>The stroke.</value>
[Parameter]
public string Stroke { get; set; }
public string? Stroke { get; set; }
/// <summary>
/// Gets or sets the pixel width of axis.
/// </summary>
@@ -26,21 +26,21 @@ namespace Radzen.Blazor
/// </summary>
/// <value>The child content.</value>
[Parameter]
public RenderFragment ChildContent { get; set; }
public RenderFragment? ChildContent { get; set; }
/// <summary>
/// Gets or sets the format string used to display the axis values.
/// </summary>
/// <value>The format string.</value>
[Parameter]
public string FormatString { get; set; }
public string? FormatString { get; set; }
/// <summary>
/// Gets or sets a formatter function that formats the axis values.
/// </summary>
/// <value>The formatter.</value>
[Parameter]
public Func<object, string> Formatter { get; set; }
public Func<object, string>? Formatter { get; set; }
/// <summary>
/// Gets or sets the type of the line used to display the axis.
@@ -80,20 +80,20 @@ namespace Radzen.Blazor
/// </summary>
/// <value>The minimum.</value>
[Parameter]
public object Min { get; set; }
public object? Min { get; set; }
/// <summary>
/// Specifies the maximum value of the axis.
/// </summary>
/// <value>The maximum.</value>
[Parameter]
public object Max { get; set; }
public object? Max { get; set; }
/// <summary>
/// Specifies the step of the axis.
/// </summary>
[Parameter]
public object Step { get; set; }
public object? Step { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this <see cref="AxisBase"/> is visible.
@@ -139,7 +139,7 @@ namespace Radzen.Blazor
}
else
{
return scale.FormatTick(FormatString, value);
return scale.FormatTick(FormatString ?? string.Empty, value);
}
}

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

@@ -19,7 +19,17 @@ namespace Radzen.Blazor
/// Cache for the value returned by <see cref="Category"/> when that value is only dependent on
/// <see cref="CategoryProperty"/>.
/// </summary>
Func<TItem, double> categoryPropertyCache;
Func<TItem, double>? categoryPropertyCache;
/// <summary>
/// Returns the parent <see cref="RadzenChart"/> instance or throws an <see cref="InvalidOperationException"/> if not present.
/// </summary>
/// <returns>The parent <see cref="RadzenChart"/>.</returns>
/// <exception cref="InvalidOperationException">Thrown when the parent chart is not set.</exception>
protected RadzenChart RequireChart()
{
return Chart ?? throw new InvalidOperationException($"{GetType().Name} requires a parent RadzenChart.");
}
/// <summary>
/// Creates a getter function that returns a value from the specified category scale for the specified data item.
@@ -34,13 +44,13 @@ namespace Radzen.Blazor
if (IsNumeric(CategoryProperty))
{
categoryPropertyCache = PropertyAccess.Getter<TItem, double>(CategoryProperty);
categoryPropertyCache = PropertyAccess.Getter<TItem, double>(CategoryProperty!);
return categoryPropertyCache;
}
if (IsDate(CategoryProperty))
{
var category = PropertyAccess.Getter<TItem, DateTime>(CategoryProperty);
var category = PropertyAccess.Getter<TItem, DateTime>(CategoryProperty!);
categoryPropertyCache = (item) => category(item).Ticks;
return categoryPropertyCache;
}
@@ -49,7 +59,7 @@ namespace Radzen.Blazor
{
Func<TItem, object> category = String.IsNullOrEmpty(CategoryProperty) ? (item) => string.Empty : PropertyAccess.Getter<TItem, object>(CategoryProperty);
return (item) => ordinal.Data.IndexOf(category(item));
return (item) => ordinal.Data?.IndexOf(category(item)) ?? -1;
}
return (item) => Items.IndexOf(item);
@@ -60,6 +70,8 @@ namespace Radzen.Blazor
/// </summary>
protected Func<TItem, double> ComposeCategory(ScaleBase scale)
{
ArgumentNullException.ThrowIfNull(scale);
return scale.Compose(Category(scale));
}
@@ -68,6 +80,8 @@ namespace Radzen.Blazor
/// </summary>
protected Func<TItem, double> ComposeValue(ScaleBase scale)
{
ArgumentNullException.ThrowIfNull(scale);
return scale.Compose(Value);
}
@@ -77,7 +91,7 @@ namespace Radzen.Blazor
/// <param name="propertyName">Name of the property.</param>
/// <returns><c>true</c> if the specified property name is date; otherwise, <c>false</c>.</returns>
/// <exception cref="ArgumentException">Property {propertyName} does not exist</exception>
protected bool IsDate(string propertyName)
protected bool IsDate(string? propertyName)
{
if (String.IsNullOrEmpty(propertyName))
{
@@ -106,7 +120,7 @@ namespace Radzen.Blazor
/// <param name="propertyName">Name of the property.</param>
/// <returns><c>true</c> if the specified property name is numeric; otherwise, <c>false</c>.</returns>
/// <exception cref="ArgumentException">Property {propertyName} does not exist</exception>
protected bool IsNumeric(string propertyName)
protected bool IsNumeric(string? propertyName)
{
if (String.IsNullOrEmpty(propertyName))
{
@@ -125,21 +139,21 @@ namespace Radzen.Blazor
/// <inheritdoc />
[Parameter]
public string Title { get; set; }
public string Title { get; set; } = null!;
/// <summary>
/// Gets or sets the child content.
/// </summary>
/// <value>The child content.</value>
[Parameter]
public RenderFragment ChildContent { get; set; }
public RenderFragment? ChildContent { get; set; }
/// <summary>
/// Gets or sets the tooltip template.
/// </summary>
/// <value>The tooltip template.</value>
[Parameter]
public RenderFragment<TItem> TooltipTemplate { get; set; }
public RenderFragment<TItem>? TooltipTemplate { get; set; }
/// <summary>
/// Gets the list of overlays.
@@ -157,7 +171,7 @@ namespace Radzen.Blazor
/// The name of the property of <typeparamref name="TItem" /> that provides the X axis (a.k.a. category axis) values.
/// </summary>
[Parameter]
public string CategoryProperty { get; set; }
public string? CategoryProperty { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this <see cref="CartesianSeries{TItem}"/> is visible.
@@ -194,7 +208,7 @@ namespace Radzen.Blazor
/// The name of the property of <typeparamref name="TItem" /> that provides the Y axis (a.k.a. value axis) values.
/// </summary>
[Parameter]
public string ValueProperty { get; set; }
public string? ValueProperty { get; set; }
/// <inheritdoc />
[Parameter]
@@ -223,7 +237,7 @@ namespace Radzen.Blazor
/// </summary>
/// <value>The data.</value>
[Parameter]
public IEnumerable<TItem> Data { get; set; }
public IEnumerable<TItem>? Data { get; set; }
/// <summary>
/// Stores <see cref="Data" /> as an IList of <typeparamref name="TItem"/>.
@@ -272,6 +286,8 @@ namespace Radzen.Blazor
/// <inheritdoc />
public virtual ScaleBase TransformCategoryScale(ScaleBase scale)
{
ArgumentNullException.ThrowIfNull(scale);
if (Items == null)
{
return scale;
@@ -298,7 +314,7 @@ namespace Radzen.Blazor
var data = GetCategories();
if (scale is OrdinalScale ordinal)
if (scale is OrdinalScale ordinal && ordinal.Data != null)
{
foreach (var item in ordinal.Data)
{
@@ -328,6 +344,8 @@ namespace Radzen.Blazor
/// <inheritdoc />
public virtual ScaleBase TransformValueScale(ScaleBase scale)
{
ArgumentNullException.ThrowIfNull(scale);
if (Items != null)
{
if (Items.Any())
@@ -398,29 +416,32 @@ namespace Radzen.Blazor
{
if (Data != null)
{
if (Data is IList<TItem>)
if (Data is IList<TItem> list)
{
Items = Data as IList<TItem>;
Items = list;
}
else
{
Items = Data.ToList();
}
if (IsDate(CategoryProperty) || IsNumeric(CategoryProperty))
if (!string.IsNullOrEmpty(CategoryProperty) && (IsDate(CategoryProperty) || IsNumeric(CategoryProperty)))
{
Items = Items.AsQueryable().OrderBy(CategoryProperty).ToList();
}
}
await Chart.Refresh(false);
if (Chart != null)
{
await Chart.Refresh(false);
}
}
}
/// <inheritdoc />
protected override void Initialize()
{
Chart.AddSeries(this);
Chart?.AddSeries(this);
}
/// <inheritdoc />
@@ -443,6 +464,9 @@ namespace Radzen.Blazor
/// <returns><c>true</c> if the polygon contains the point, <c>false</c> otherwise.</returns>
protected bool InsidePolygon(Point point, Point[] polygon)
{
ArgumentNullException.ThrowIfNull(point);
ArgumentNullException.ThrowIfNull(polygon);
var minX = polygon[0].X;
var maxX = polygon[0].X;
var minY = polygon[0].Y;
@@ -479,18 +503,22 @@ namespace Radzen.Blazor
/// <inheritdoc />
public virtual RenderFragment RenderTooltip(object data)
{
var chart = RequireChart();
var item = (TItem)data;
return builder =>
{
if (Chart.Tooltip.Shared)
if (chart.Tooltip.Shared)
{
var category = PropertyAccess.GetValue(item, CategoryProperty);
builder.OpenComponent<ChartSharedTooltip>(0);
builder.AddAttribute(1, nameof(ChartSharedTooltip.Class), TooltipClass(item));
builder.AddAttribute(2, nameof(ChartSharedTooltip.Title), TooltipTitle(item));
builder.AddAttribute(3, nameof(ChartSharedTooltip.ChildContent), RenderSharedTooltipItems(category));
builder.CloseComponent();
var category = !string.IsNullOrEmpty(CategoryProperty) ? PropertyAccess.GetValue(item, CategoryProperty) : null;
if (category != null)
{
builder.OpenComponent<ChartSharedTooltip>(0);
builder.AddAttribute(1, nameof(ChartSharedTooltip.Class), TooltipClass(item));
builder.AddAttribute(2, nameof(ChartSharedTooltip.Title), TooltipTitle(item));
builder.AddAttribute(3, nameof(ChartSharedTooltip.ChildContent), RenderSharedTooltipItems(category));
builder.CloseComponent();
}
}
else
{
@@ -508,9 +536,11 @@ namespace Radzen.Blazor
private RenderFragment RenderSharedTooltipItems(object category)
{
var chart = RequireChart();
return builder =>
{
var visibleSeries = Chart.Series.Where(s => s.Visible).ToList();
var visibleSeries = chart.Series.Where(s => s.Visible).ToList();
foreach (var series in visibleSeries)
{
@@ -524,7 +554,7 @@ namespace Radzen.Blazor
{
return builder =>
{
var item = Items.FirstOrDefault(i => object.Equals(PropertyAccess.GetValue(i, CategoryProperty), category));
var item = Items.FirstOrDefault(i => !string.IsNullOrEmpty(CategoryProperty) && object.Equals(PropertyAccess.GetValue(i, CategoryProperty), category));
if (item != null)
{
@@ -553,7 +583,7 @@ namespace Radzen.Blazor
/// <param name="item">The item.</param>
protected virtual string TooltipStyle(TItem item)
{
return Chart.Tooltip.Style;
return Chart?.Tooltip?.Style ?? string.Empty;
}
/// <summary>
@@ -562,7 +592,13 @@ namespace Radzen.Blazor
/// <param name="item">The item.</param>
protected virtual string TooltipClass(TItem item)
{
return $"rz-series-{Chart.Series.IndexOf(this)}-tooltip";
var chart = Chart;
if (chart == null)
{
return "rz-series-tooltip";
}
return $"rz-series-{chart.Series.IndexOf(this)}-tooltip";
}
/// <inheritdoc />
@@ -576,6 +612,8 @@ namespace Radzen.Blazor
/// </summary>
protected virtual RenderFragment RenderLegendItem(bool clickable)
{
var chart = RequireChart();
var index = chart.Series.IndexOf(this);
var style = new List<string>();
if (IsVisible == false)
@@ -586,7 +624,7 @@ namespace Radzen.Blazor
return builder =>
{
builder.OpenComponent<LegendItem>(0);
builder.AddAttribute(1, nameof(LegendItem.Index), Chart.Series.IndexOf(this));
builder.AddAttribute(1, nameof(LegendItem.Index), index);
builder.AddAttribute(2, nameof(LegendItem.Color), Color);
builder.AddAttribute(3, nameof(LegendItem.MarkerType), MarkerType);
builder.AddAttribute(4, nameof(LegendItem.Style), string.Join(";", style));
@@ -617,19 +655,35 @@ namespace Radzen.Blazor
/// <inheritdoc />
public double GetMedian()
{
return Data.Select(e => Value(e)).OrderBy(e => e).Skip(Data.Count() / 2).FirstOrDefault();
var values = Items.Select(Value).OrderBy(e => e).ToList();
if (values.Count == 0)
{
return 0;
}
return values[values.Count / 2];
}
/// <inheritdoc />
public double GetMean()
{
return Data.Select(e => Value(e)).DefaultIfEmpty(double.NaN).Average();
return Items.Any() ? Items.Select(Value).Average() : double.NaN;
}
/// <inheritdoc />
public double GetMode()
{
return Data.Any() ? Data.GroupBy(e => Value(e)).Select(g => new { Value = g.Key, Count = g.Count() }).OrderByDescending(e => e.Count).FirstOrDefault().Value : double.NaN;
if (!Items.Any())
{
return double.NaN;
}
return Items
.GroupBy(item => Value(item))
.Select(g => new { Value = g.Key, Count = g.Count() })
.OrderByDescending(e => e.Count)
.First()
.Value;
}
/// <summary>
@@ -639,33 +693,43 @@ namespace Radzen.Blazor
{
double a = double.NaN, b = double.NaN;
if (Data.Any())
var chart = Chart;
if (chart == null)
{
return (a, b);
}
if (Items.Any())
{
Func<TItem, double> X;
Func<TItem, double> Y;
if (Chart.ShouldInvertAxes())
if (chart.ShouldInvertAxes())
{
X = e => Chart.CategoryScale.Scale(Value(e));
Y = e => Chart.ValueScale.Scale(Category(Chart.ValueScale)(e));
var valueScale = chart.ValueScale;
var categoryAccessor = Category(chart.ValueScale);
X = e => chart.CategoryScale.Scale(Value(e));
Y = e => valueScale.Scale(categoryAccessor(e));
}
else
{
X = e => Chart.CategoryScale.Scale(Category(Chart.CategoryScale)(e));
Y = e => Chart.ValueScale.Scale(Value(e));
var categoryAccessor = Category(chart.CategoryScale);
X = e => chart.CategoryScale.Scale(categoryAccessor(e));
Y = e => chart.ValueScale.Scale(Value(e));
}
var avgX = Data.Select(e => X(e)).Average();
var avgY = Data.Select(e => Y(e)).Average();
var sumXY = Data.Sum(e => (X(e) - avgX) * (Y(e) - avgY));
if (Chart.ShouldInvertAxes())
var data = Items.ToList();
var avgX = data.Select(e => X(e)).Average();
var avgY = data.Select(e => Y(e)).Average();
var sumXY = data.Sum(e => (X(e) - avgX) * (Y(e) - avgY));
if (chart.ShouldInvertAxes())
{
var sumYSq = Data.Sum(e => (Y(e) - avgY) * (Y(e) - avgY));
var sumYSq = data.Sum(e => (Y(e) - avgY) * (Y(e) - avgY));
b = sumXY / sumYSq;
a = avgX - b * avgY;
}
else
{
var sumXSq = Data.Sum(e => (X(e) - avgX) * (X(e) - avgX));
var sumXSq = data.Sum(e => (X(e) - avgX) * (X(e) - avgX));
b = sumXY / sumXSq;
a = avgY - b * avgX;
}
@@ -678,7 +742,9 @@ namespace Radzen.Blazor
{
IsVisible = !IsVisible;
if (Chart.LegendClick.HasDelegate)
var chart = Chart;
if (chart?.LegendClick.HasDelegate == true)
{
var args = new LegendClickEventArgs
{
@@ -687,18 +753,28 @@ namespace Radzen.Blazor
IsVisible = IsVisible,
};
await Chart.LegendClick.InvokeAsync(args);
await chart.LegendClick.InvokeAsync(args);
IsVisible = args.IsVisible;
}
await Chart.Refresh();
if (chart != null)
{
await chart.Refresh();
}
}
/// <inheritdoc />
public string GetTitle()
{
return String.IsNullOrEmpty(Title) ? $"Series {Chart.Series.IndexOf(this) + 1}" : Title;
var chart = Chart;
if (string.IsNullOrEmpty(Title))
{
var index = chart?.Series.IndexOf(this) ?? 0;
return $"Series {index + 1}";
}
return Title;
}
/// <summary>
@@ -716,8 +792,9 @@ namespace Radzen.Blazor
/// <param name="item">The item.</param>
protected virtual string TooltipTitle(TItem item)
{
var category = Category(Chart.CategoryScale);
return Chart.CategoryAxis.Format(Chart.CategoryScale, Chart.CategoryScale.Value(category(item)));
var chart = RequireChart();
var category = Category(chart.CategoryScale);
return chart.CategoryAxis.Format(chart.CategoryScale, chart.CategoryScale.Value(category(item)));
}
/// <summary>
@@ -727,7 +804,8 @@ namespace Radzen.Blazor
/// <returns>System.String.</returns>
protected virtual string TooltipValue(TItem item)
{
return Chart.ValueAxis.Format(Chart.ValueScale, Chart.ValueScale.Value(Value(item)));
var chart = RequireChart();
return chart.ValueAxis.Format(chart.ValueScale, chart.ValueScale.Value(Value(item)));
}
/// <summary>
@@ -736,8 +814,9 @@ namespace Radzen.Blazor
/// <param name="item">The item.</param>
internal virtual double TooltipX(TItem item)
{
var category = Category(Chart.CategoryScale);
return Chart.CategoryScale.Scale(category(item), true);
var chart = RequireChart();
var category = Category(chart.CategoryScale);
return chart.CategoryScale.Scale(category(item), true);
}
/// <summary>
@@ -746,7 +825,8 @@ namespace Radzen.Blazor
/// <param name="item">The item.</param>
internal virtual double TooltipY(TItem item)
{
return Chart.ValueScale.Scale(Value(item), true);
var chart = RequireChart();
return chart.ValueScale.Scale(Value(item), true);
}
/// <inheritdoc />
@@ -760,25 +840,26 @@ namespace Radzen.Blazor
return new { Item = item, Distance = distance };
}).Aggregate((a, b) => a.Distance < b.Distance ? a : b).Item;
return (retObject,
return (retObject!,
new Point() { X = TooltipX(retObject), Y = TooltipY(retObject)});
}
return (null, null);
return (default!, new Point());
}
/// <inheritdoc />
public virtual IEnumerable<ChartDataLabel> GetDataLabels(double offsetX, double offsetY)
{
var chart = RequireChart();
var list = new List<ChartDataLabel>();
foreach (var d in Data)
foreach (var d in Items)
{
list.Add(new ChartDataLabel
{
Position = new Point { X = TooltipX(d) + offsetX, Y = TooltipY(d) + offsetY },
TextAnchor = "middle",
Text = Chart.ValueAxis.Format(Chart.ValueScale, Value(d))
Text = chart.ValueAxis.Format(chart.ValueScale, Value(d))
});
}
@@ -793,12 +874,12 @@ namespace Radzen.Blazor
/// <param name="defaultValue">The default value.</param>
/// <param name="colorRange">The color range value.</param>
/// <param name="value">The value of the item.</param>
protected string PickColor(int index, IEnumerable<string> colors, string defaultValue = null, IList<SeriesColorRange> colorRange = null, double value = 0.0)
protected string? PickColor(int index, IEnumerable<string>? colors, string? defaultValue = null, IList<SeriesColorRange>? colorRange = null, double value = 0.0)
{
if (colorRange != null)
{
var result = colorRange.Where(r => r.Min <= value && r.Max >= value).FirstOrDefault<SeriesColorRange>();
return result != null ? result.Color : defaultValue;
return result?.Color ?? defaultValue;
}
else
{
@@ -819,18 +900,20 @@ namespace Radzen.Blazor
/// <inheritdoc />
public async Task InvokeClick(EventCallback<SeriesClickEventArgs> handler, object data)
{
var category = Category(Chart.CategoryScale);
var chart = RequireChart();
var category = Category(chart.CategoryScale);
var dataItem = (TItem)data;
await handler.InvokeAsync(new SeriesClickEventArgs
{
Data = data,
Title = GetTitle(),
Category = PropertyAccess.GetValue(data, CategoryProperty),
Value = PropertyAccess.GetValue(data, ValueProperty),
Category = !string.IsNullOrEmpty(CategoryProperty) ? PropertyAccess.GetValue(data, CategoryProperty) : null,
Value = !string.IsNullOrEmpty(ValueProperty) ? PropertyAccess.GetValue(data, ValueProperty) : null,
Point = new SeriesPoint
{
Category = category((TItem)data),
Value = Value((TItem)data)
Category = category(dataItem),
Value = Value(dataItem)
}
});
}

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

View File

@@ -1,225 +1,227 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Radzen
{
/// <summary>
/// Class ContextMenuService. Contains various methods with options to open and close context menus.
/// Should be added as scoped service in the application services and RadzenContextMenu should be added in application main layout.
/// Implements the <see cref="IDisposable" />
/// </summary>
/// <seealso cref="IDisposable" />
/// <example>
/// <code>
/// @inject ContextMenuService ContextMenuService
/// &lt;RadzenButton Text="Show context menu" ContextMenu=@(args => ShowContextMenuWithContent(args)) /&gt;
/// @code {
/// void ShowContextMenuWithContent(MouseEventArgs args) =&gt; ContextMenuService.Open(args, ds =&gt;
/// @&lt;RadzenMenu Click="OnMenuItemClick"&gt;
/// &lt;RadzenMenuItem Text="Item1" Value="1"&gt;&lt;/RadzenMenuItem&gt;
/// &lt;RadzenMenuItem Text="Item2" Value="2"&gt;&lt;/RadzenMenuItem&gt;
/// &lt;RadzenMenuItem Text="More items" Value="3"&gt;
/// &lt;RadzenMenuItem Text="More sub items" Value="4"&gt;
/// &lt;RadzenMenuItem Text="Item1" Value="5"&gt;&lt;/RadzenMenuItem&gt;
/// &lt;RadzenMenuItem Text="Item2" Value="6"&gt;&lt;/RadzenMenuItem&gt;
/// &lt;/RadzenMenuItem&gt;
/// &lt;/RadzenMenuItem&gt;
/// &lt;/RadzenMenu&gt;);
///
/// void OnMenuItemClick(MenuItemEventArgs args)
/// {
/// Console.WriteLine($"Menu item with Value={args.Value} clicked");
/// }
/// }
/// </code>
/// </example>
public class ContextMenuService : IDisposable
{
/// <summary>
/// Gets or sets the navigation manager.
/// </summary>
/// <value>The navigation manager.</value>
NavigationManager navigationManager { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="ContextMenuService"/> class.
/// </summary>
/// <param name="uriHelper">The URI helper.</param>
public ContextMenuService(NavigationManager uriHelper)
{
navigationManager = uriHelper;
if (navigationManager != null)
{
navigationManager.LocationChanged += UriHelper_OnLocationChanged;
}
}
/// <summary>
/// Handles the OnLocationChanged event of the UriHelper control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs"/> instance containing the event data.</param>
private void UriHelper_OnLocationChanged(object sender, Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs e)
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using System;
using System.Collections.Generic;
namespace Radzen
{
/// <summary>
/// Class ContextMenuService. Contains various methods with options to open and close context menus.
/// Should be added as scoped service in the application services and RadzenContextMenu should be added in application main layout.
/// Implements the <see cref="IDisposable" />
/// </summary>
/// <seealso cref="IDisposable" />
/// <example>
/// <code>
/// @inject ContextMenuService ContextMenuService
/// &lt;RadzenButton Text="Show context menu" ContextMenu=@(args => ShowContextMenuWithContent(args)) /&gt;
/// @code {
/// void ShowContextMenuWithContent(MouseEventArgs args) =&gt; ContextMenuService.Open(args, ds =&gt;
/// @&lt;RadzenMenu Click="OnMenuItemClick"&gt;
/// &lt;RadzenMenuItem Text="Item1" Value="1"&gt;&lt;/RadzenMenuItem&gt;
/// &lt;RadzenMenuItem Text="Item2" Value="2"&gt;&lt;/RadzenMenuItem&gt;
/// &lt;RadzenMenuItem Text="More items" Value="3"&gt;
/// &lt;RadzenMenuItem Text="More sub items" Value="4"&gt;
/// &lt;RadzenMenuItem Text="Item1" Value="5"&gt;&lt;/RadzenMenuItem&gt;
/// &lt;RadzenMenuItem Text="Item2" Value="6"&gt;&lt;/RadzenMenuItem&gt;
/// &lt;/RadzenMenuItem&gt;
/// &lt;/RadzenMenuItem&gt;
/// &lt;/RadzenMenu&gt;);
///
/// void OnMenuItemClick(MenuItemEventArgs args)
/// {
/// Console.WriteLine($"Menu item with Value={args.Value} clicked");
/// }
/// }
/// </code>
/// </example>
public class ContextMenuService : IDisposable
{
/// <summary>
/// Gets or sets the navigation manager.
/// </summary>
/// <value>The navigation manager.</value>
NavigationManager? navigationManager { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="ContextMenuService"/> class.
/// </summary>
/// <param name="uriHelper">The URI helper.</param>
public ContextMenuService(NavigationManager? uriHelper)
{
this.OnNavigate?.Invoke();
}
/// <summary>
/// Occurs when [on navigate].
/// </summary>
public event Action OnNavigate;
/// <summary>
/// Raises the Close event.
/// </summary>
public event Action OnClose;
/// <summary>
/// Occurs when [on open].
/// </summary>
public event Action<MouseEventArgs, ContextMenuOptions> OnOpen;
/// <summary>
/// Opens the specified arguments.
/// </summary>
/// <param name="args">The <see cref="MouseEventArgs"/> instance containing the event data.</param>
/// <param name="items">The items.</param>
/// <param name="click">The click.</param>
public void Open(MouseEventArgs args, IEnumerable<ContextMenuItem> items, Action<MenuItemEventArgs> click = null)
{
var options = new ContextMenuOptions();
options.Items = items;
options.Click = click;
OpenTooltip(args, options);
}
/// <summary>
/// Opens the specified arguments.
/// </summary>
/// <param name="args">The <see cref="MouseEventArgs"/> instance containing the event data.</param>
/// <param name="childContent">Content of the child.</param>
public void Open(MouseEventArgs args, RenderFragment<ContextMenuService> childContent)
{
var options = new ContextMenuOptions();
options.ChildContent = childContent;
OpenTooltip(args, options);
}
/// <summary>
/// Opens the tooltip.
/// </summary>
/// <param name="args">The <see cref="MouseEventArgs"/> instance containing the event data.</param>
/// <param name="options">The options.</param>
private void OpenTooltip(MouseEventArgs args, ContextMenuOptions options)
{
OnOpen?.Invoke(args, options);
}
/// <summary>
/// Closes this instance.
/// </summary>
public void Close()
{
OnClose?.Invoke();
}
/// <summary>
/// Disposes this instance.
/// </summary>
public void Dispose()
{
navigationManager.LocationChanged -= UriHelper_OnLocationChanged;
}
}
/// <summary>
/// Class ContextMenuOptions.
/// </summary>
public class ContextMenuOptions
{
/// <summary>
/// Gets or sets the child content.
/// </summary>
/// <value>The child content.</value>
public RenderFragment<ContextMenuService> ChildContent { get; set; }
/// <summary>
/// Gets or sets the items.
/// </summary>
/// <value>The items.</value>
public IEnumerable<ContextMenuItem> Items { get; set; }
/// <summary>
/// Gets or sets the click.
/// </summary>
/// <value>The click.</value>
public Action<MenuItemEventArgs> Click { get; set; }
}
/// <summary>
/// Class ContextMenu.
/// </summary>
public class ContextMenu
{
/// <summary>
/// Gets or sets the options.
/// </summary>
/// <value>The options.</value>
public ContextMenuOptions Options { get; set; }
/// <summary>
/// Gets or sets the mouse event arguments.
/// </summary>
/// <value>The mouse event arguments.</value>
public MouseEventArgs MouseEventArgs { get; set; }
}
/// <summary>
/// Class ContextMenuItem.
/// </summary>
public class ContextMenuItem
{
/// <summary>
/// Gets or sets the text.
/// </summary>
/// <value>The text.</value>
public string Text { get; set; }
/// <summary>
/// Gets or sets the value.
/// </summary>
/// <value>The value.</value>
public object Value { get; set; }
/// <summary>
/// Gets or sets the icon.
/// </summary>
/// <value>The icon.</value>
public string Icon { get; set; }
/// <summary>
/// Gets or sets the icon color.
/// </summary>
/// <value>The icon color.</value>
public string IconColor { get; set; }
/// <summary>
/// Gets or sets the image.
/// </summary>
/// <value>The image.</value>
public string Image { get; set; }
/// <summary>
/// Gets or sets the image style.
/// </summary>
/// <value>The image style.</value>
public string ImageStyle { get; set; }
/// <summary>
/// Gets a value indicating whether this instance is disabled.
/// </summary>
/// <value><c>true</c> if this instance is disabled; otherwise, <c>false</c>.</value>
public bool Disabled { get; set; }
}
}
navigationManager = uriHelper;
if (navigationManager != null)
{
navigationManager.LocationChanged += UriHelper_OnLocationChanged;
}
}
/// <summary>
/// Handles the OnLocationChanged event of the UriHelper control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs"/> instance containing the event data.</param>
private void UriHelper_OnLocationChanged(object? sender, Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs e)
{
this.OnNavigate?.Invoke();
}
/// <summary>
/// Occurs when [on navigate].
/// </summary>
public event Action? OnNavigate;
/// <summary>
/// Raises the Close event.
/// </summary>
public event Action? OnClose;
/// <summary>
/// Occurs when [on open].
/// </summary>
public event Action<MouseEventArgs, ContextMenuOptions>? OnOpen;
/// <summary>
/// Opens the specified arguments.
/// </summary>
/// <param name="args">The <see cref="MouseEventArgs"/> instance containing the event data.</param>
/// <param name="items">The items.</param>
/// <param name="click">The click.</param>
public void Open(MouseEventArgs args, IEnumerable<ContextMenuItem> items, Action<MenuItemEventArgs>? click = null)
{
var options = new ContextMenuOptions();
options.Items = items;
options.Click = click;
OpenTooltip(args, options);
}
/// <summary>
/// Opens the specified arguments.
/// </summary>
/// <param name="args">The <see cref="MouseEventArgs"/> instance containing the event data.</param>
/// <param name="childContent">Content of the child.</param>
public void Open(MouseEventArgs args, RenderFragment<ContextMenuService> childContent)
{
var options = new ContextMenuOptions();
options.ChildContent = childContent;
OpenTooltip(args, options);
}
/// <summary>
/// Opens the tooltip.
/// </summary>
/// <param name="args">The <see cref="MouseEventArgs"/> instance containing the event data.</param>
/// <param name="options">The options.</param>
private void OpenTooltip(MouseEventArgs args, ContextMenuOptions options)
{
OnOpen?.Invoke(args, options);
}
/// <summary>
/// Closes this instance.
/// </summary>
public void Close()
{
OnClose?.Invoke();
}
/// <summary>
/// Disposes this instance.
/// </summary>
public void Dispose()
{
if (navigationManager != null)
{
navigationManager.LocationChanged -= UriHelper_OnLocationChanged;
}
GC.SuppressFinalize(this);
}
}
/// <summary>
/// Class ContextMenuOptions.
/// </summary>
public class ContextMenuOptions
{
/// <summary>
/// Gets or sets the child content.
/// </summary>
/// <value>The child content.</value>
public RenderFragment<ContextMenuService>? ChildContent { get; set; }
/// <summary>
/// Gets or sets the items.
/// </summary>
/// <value>The items.</value>
public IEnumerable<ContextMenuItem>? Items { get; set; }
/// <summary>
/// Gets or sets the click.
/// </summary>
/// <value>The click.</value>
public Action<MenuItemEventArgs>? Click { get; set; }
}
/// <summary>
/// Class ContextMenu.
/// </summary>
public class ContextMenu
{
/// <summary>
/// Gets or sets the options.
/// </summary>
/// <value>The options.</value>
public ContextMenuOptions? Options { get; set; }
/// <summary>
/// Gets or sets the mouse event arguments.
/// </summary>
/// <value>The mouse event arguments.</value>
public MouseEventArgs? MouseEventArgs { get; set; }
}
/// <summary>
/// Class ContextMenuItem.
/// </summary>
public class ContextMenuItem
{
/// <summary>
/// Gets or sets the text.
/// </summary>
/// <value>The text.</value>
public string? Text { get; set; }
/// <summary>
/// Gets or sets the value.
/// </summary>
/// <value>The value.</value>
public object? Value { get; set; }
/// <summary>
/// Gets or sets the icon.
/// </summary>
/// <value>The icon.</value>
public string? Icon { get; set; }
/// <summary>
/// Gets or sets the icon color.
/// </summary>
/// <value>The icon color.</value>
public string? IconColor { get; set; }
/// <summary>
/// Gets or sets the image.
/// </summary>
/// <value>The image.</value>
public string? Image { get; set; }
/// <summary>
/// Gets or sets the image style.
/// </summary>
/// <value>The image style.</value>
public string? ImageStyle { get; set; }
/// <summary>
/// Gets a value indicating whether this instance is disabled.
/// </summary>
/// <value><c>true</c> if this instance is disabled; otherwise, <c>false</c>.</value>
public bool Disabled { get; set; }
}
}

View File

@@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using Radzen.Blazor;
namespace Radzen;
/// <summary>
/// Represents a conversation session with memory.
/// </summary>
public class ConversationSession
{
/// <summary>
/// Gets or sets the unique identifier for the conversation session.
/// </summary>
public string Id { get; set; } = Guid.NewGuid().ToString();
/// <summary>
/// Gets or sets the list of messages in the conversation.
/// </summary>
public List<ChatMessage> Messages { get; set; } = new();
/// <summary>
/// Gets or sets the timestamp when the conversation was created.
/// </summary>
public DateTime CreatedAt { get; set; } = DateTime.Now;
/// <summary>
/// Gets or sets the timestamp when the conversation was last updated.
/// </summary>
public DateTime LastUpdated { get; set; } = DateTime.Now;
/// <summary>
/// Gets or sets the maximum number of messages to keep in memory.
/// </summary>
public int MaxMessages { get; set; } = 50;
/// <summary>
/// Adds a message to the conversation and manages memory limits.
/// </summary>
/// <param name="role">The role of the message sender.</param>
/// <param name="content">The message content.</param>
public void AddMessage(string role, string content)
{
Messages.Add(new ChatMessage
{
UserId = role,
Role = role,
IsUser = role == "user",
Content = content,
Timestamp = DateTime.Now
});
LastUpdated = DateTime.Now;
// Remove oldest messages if we exceed the limit
while (Messages.Count > MaxMessages)
{
Messages.RemoveAt(0);
}
}
/// <summary>
/// Clears all messages from the conversation.
/// </summary>
public void Clear()
{
Messages.Clear();
LastUpdated = DateTime.Now;
}
/// <summary>
/// Gets the conversation messages formatted for the AI API.
/// </summary>
/// <param name="systemPrompt">The system prompt to include.</param>
/// <returns>A list of message objects for the AI API.</returns>
public List<object> GetFormattedMessages(string systemPrompt)
{
var messages = new List<object>();
// Add system message
messages.Add(new { role = "system", content = systemPrompt });
// Add conversation messages
foreach (var message in Messages)
{
messages.Add(new { role = message.Role, content = message.Content });
}
return messages;
}
}

View File

@@ -0,0 +1,70 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace Radzen;
/// <summary>
/// Converts values to different types. Used internally.
/// </summary>
public static class ConvertType
{
/// <summary>
/// Changes the type of an object.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="type">The type.</param>
/// <param name="culture">The culture.</param>
/// <returns>System.Object</returns>
public static object? ChangeType(object value, Type type, CultureInfo? culture = null)
{
ArgumentNullException.ThrowIfNull(type);
// CA1062: Validate 'value' is non-null before using it
if (value == null)
{
if (Nullable.GetUnderlyingType(type) != null)
{
return null;
}
throw new ArgumentNullException(nameof(value));
}
if (culture == null)
{
culture = CultureInfo.CurrentCulture;
}
if ((Nullable.GetUnderlyingType(type) ?? type) == typeof(Guid) && value is string)
{
return Guid.Parse((string)value);
}
var underlyingEnumType = Nullable.GetUnderlyingType(type);
if (underlyingEnumType?.IsEnum == true)
{
var valueString = value.ToString();
if (valueString == null)
{
throw new ArgumentNullException(nameof(value), "Enum value cannot be null.");
}
return Enum.Parse(underlyingEnumType, valueString);
}
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
Type itemType = type.GetGenericArguments()[0];
var enumerable = value as IEnumerable<object>;
if (enumerable != null)
{
return enumerable.AsQueryable().Cast(itemType);
}
}
return value is IConvertible ? Convert.ChangeType(value, Nullable.GetUnderlyingType(type) ?? type, culture) : value;
}
}

View File

@@ -44,12 +44,12 @@ namespace Radzen
/// <summary>
/// Gets or sets a value indicating whether to use secure cookies.
/// </summary>
public bool IsSecure { get; set; } = false;
public bool IsSecure { get; set; }
/// <summary>
/// Gets or sets the SameSite attribute for the cookie.
/// </summary>
public CookieSameSiteMode? SameSite { get; set; } = null;
public CookieSameSiteMode? SameSite { get; set; }
}
/// <summary>
@@ -64,13 +64,16 @@ namespace Radzen
/// <summary>
/// Initializes a new instance of the <see cref="CookieThemeService" /> class.
/// </summary>
public CookieThemeService(IJSRuntime jsRuntime, ThemeService themeService, IOptions<CookieThemeServiceOptions> options)
public CookieThemeService(IJSRuntime jsRuntime, ThemeService themeService, IOptions<CookieThemeServiceOptions>? options)
{
this.jsRuntime = jsRuntime;
this.themeService = themeService;
this.options = options.Value;
this.options = options?.Value ?? new CookieThemeServiceOptions();
themeService.ThemeChanged += OnThemeChanged;
if (themeService != null)
{
themeService.ThemeChanged += OnThemeChanged;
}
_ = InitializeAsync();
}

View File

@@ -0,0 +1,18 @@
namespace Radzen;
/// <summary>
/// CoordinateSystem enum
/// </summary>
public enum CoordinateSystem
{
/// <summary>
/// Cartesian CoordinateSystem
/// </summary>
Cartesian,
/// <summary>
/// Polar CoordinateSystem
/// </summary>
Polar
}

View File

@@ -47,14 +47,14 @@ namespace Radzen
/// </summary>
/// <value>The name.</value>
[Parameter]
public string Name { get; set; }
public string? Name { get; set; }
/// <summary>
/// Gets or sets the placeholder.
/// </summary>
/// <value>The placeholder.</value>
[Parameter]
public string Placeholder { get; set; }
public string? Placeholder { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this <see cref="DataBoundFormComponent{T}"/> is disabled.
@@ -80,36 +80,36 @@ namespace Radzen
/// <summary>
/// The form
/// </summary>
IRadzenForm _form;
IRadzenForm? form;
/// <summary>
/// Gets or sets the form.
/// </summary>
/// <value>The form.</value>
[CascadingParameter]
public IRadzenForm Form
public IRadzenForm? Form
{
get
{
return _form;
return form;
}
set
{
_form = value;
_form?.AddComponent(this);
form = value;
form?.AddComponent(this);
}
}
/// <summary>
/// The value
/// </summary>
private T _value = default;
private T? _value;
/// <summary>
/// Gets or sets the value.
/// </summary>
/// <value>The value.</value>
[Parameter]
public T Value
public T? Value
{
get
{
@@ -167,18 +167,18 @@ namespace Radzen
/// </summary>
/// <value>The text property.</value>
[Parameter]
public string TextProperty { get; set; }
public string? TextProperty { get; set; }
/// <summary>
/// The data
/// </summary>
IEnumerable _data = null;
IEnumerable? _data;
/// <summary>
/// Gets or sets the data.
/// </summary>
/// <value>The data.</value>
[Parameter]
public virtual IEnumerable Data
public virtual IEnumerable? Data
{
get
{
@@ -208,7 +208,7 @@ namespace Radzen
/// Gets the query.
/// </summary>
/// <value>The query.</value>
protected virtual IQueryable Query
protected virtual IQueryable? Query
{
get
{
@@ -220,7 +220,7 @@ namespace Radzen
/// Gets or sets the search text
/// </summary>
[Parameter]
public string SearchText
public string? SearchText
{
get
{
@@ -245,29 +245,30 @@ namespace Radzen
/// <summary>
/// The search text
/// </summary>
internal string searchText;
internal string? searchText;
/// <summary>
/// The view
/// </summary>
protected IQueryable _view = null;
protected IQueryable? _view;
/// <summary>
/// Gets the view.
/// </summary>
/// <value>The view.</value>
protected virtual IEnumerable View
protected virtual IEnumerable? View
{
get
{
if (_view == null && Query != null)
var query = Query;
if (_view == null && query != null)
{
if (!string.IsNullOrEmpty(searchText))
if (!string.IsNullOrEmpty(searchText) && !string.IsNullOrEmpty(TextProperty))
{
_view = Query.Where(TextProperty, searchText, FilterOperator, FilterCaseSensitivity);
_view = query.Where(TextProperty, searchText, FilterOperator, FilterCaseSensitivity);
}
else
{
_view = (typeof(IQueryable).IsAssignableFrom(Data.GetType())) ? Query.Cast<object>().ToList().AsQueryable() : Query;
_view = Data is IQueryable ? query.Cast<object>().ToList().AsQueryable() : query;
}
}
@@ -275,12 +276,14 @@ namespace Radzen
}
}
internal IEnumerable? GetView() => View;
/// <summary>
/// Gets or sets the edit context.
/// </summary>
/// <value>The edit context.</value>
[CascadingParameter]
public EditContext EditContext { get; set; }
public EditContext? EditContext { get; set; }
/// <summary>
/// Gets the field identifier.
@@ -294,7 +297,7 @@ namespace Radzen
/// </summary>
/// <value>The value expression.</value>
[Parameter]
public Expression<Func<T>> ValueExpression { get; set; }
public Expression<Func<T>>? ValueExpression { get; set; }
/// <summary>
/// Set parameters as an asynchronous operation.
/// </summary>
@@ -305,7 +308,7 @@ namespace Radzen
// check for changes before setting the properties through the base call
var dataChanged = parameters.DidParameterChange(nameof(Data), Data);
var disabledChanged = parameters.DidParameterChange(nameof(Disabled), Disabled);
// allow the base class to process parameters and set the properties
// after this call the parameters object should be considered stale
await base.SetParametersAsync(parameters);
@@ -316,9 +319,11 @@ namespace Radzen
await OnDataChanged();
}
if (EditContext != null && ValueExpression != null && FieldIdentifier.Model != EditContext.Model)
if (EditContext != null && (ValueExpression != null || ValueChanged.HasDelegate) && FieldIdentifier.Model != EditContext.Model)
{
FieldIdentifier = FieldIdentifier.Create(ValueExpression);
FieldIdentifier = ValueExpression != null
? FieldIdentifier.Create(ValueExpression)
: FieldIdentifier.Create(() => Value);
EditContext.OnValidationStateChanged -= ValidationStateChanged;
EditContext.OnValidationStateChanged += ValidationStateChanged;
}
@@ -334,7 +339,7 @@ namespace Radzen
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="ValidationStateChangedEventArgs"/> instance containing the event data.</param>
private void ValidationStateChanged(object sender, ValidationStateChangedEventArgs e)
private void ValidationStateChanged(object? sender, ValidationStateChangedEventArgs e)
{
StateHasChanged();
}
@@ -352,13 +357,15 @@ namespace Radzen
}
Form?.RemoveComponent(this);
GC.SuppressFinalize(this);
}
/// <summary>
/// Gets the value.
/// </summary>
/// <returns>System.Object.</returns>
public virtual object GetValue()
public virtual object? GetValue()
{
return Value;
}
@@ -382,13 +389,13 @@ namespace Radzen
/// <summary> Provides support for RadzenFormField integration. </summary>
[CascadingParameter]
public IFormFieldContext FormFieldContext { get; set; }
public IFormFieldContext? FormFieldContext { get; set; }
/// <summary> Gets the current placeholder. Returns empty string if this component is inside a RadzenFormField.</summary>
protected string CurrentPlaceholder => FormFieldContext?.AllowFloatingLabel == true ? " " : Placeholder;
protected string? CurrentPlaceholder => FormFieldContext?.AllowFloatingLabel == true ? " " : Placeholder;
/// <summary>
/// Handles the <see cref="E:ContextMenu" /> event.
/// Handles the ContextMenu event.
/// </summary>
/// <param name="args">The <see cref="MouseEventArgs"/> instance containing the event data.</param>
/// <returns>Task.</returns>

View File

@@ -0,0 +1,21 @@
using Radzen.Blazor;
namespace Radzen;
/// <summary>
/// Supplies information about a <see cref="RadzenDataGrid{TItem}.CellContextMenu" /> event that is being raised.
/// </summary>
/// <typeparam name="T">The data item type.</typeparam>
public class DataGridCellMouseEventArgs<T> : Microsoft.AspNetCore.Components.Web.MouseEventArgs where T : notnull
{
/// <summary>
/// Gets the data item which the clicked DataGrid row represents.
/// </summary>
public T? Data { get; internal set; }
/// <summary>
/// Gets the RadzenDataGridColumn which this cells represents.
/// </summary>
public RadzenDataGridColumn<T>? Column { get; internal set; }
}

View File

@@ -0,0 +1,16 @@
using Radzen.Blazor;
namespace Radzen;
/// <summary>
/// Supplies information about a <see cref="RadzenDataGrid{TItem}.CellRender" /> event that is being raised.
/// </summary>
/// <typeparam name="T">The data item type.</typeparam>
public class DataGridCellRenderEventArgs<T> : RowRenderEventArgs<T> where T : notnull
{
/// <summary>
/// Gets the RadzenDataGridColumn which this cells represents.
/// </summary>
public RadzenDataGridColumn<T>? Column { get; internal set; }
}

View File

@@ -0,0 +1,26 @@
using System.Collections.Generic;
namespace Radzen;
/// <summary>
/// Internal class for managing hierarchical child data in DataGrid.
/// </summary>
/// <typeparam name="T">The data item type.</typeparam>
internal class DataGridChildData<T>
{
/// <summary>
/// Gets or sets the parent child data.
/// </summary>
internal DataGridChildData<T>? ParentChildData { get; set; }
/// <summary>
/// Gets or sets the level.
/// </summary>
internal int Level { get; set; }
/// <summary>
/// Gets or sets the data.
/// </summary>
internal IEnumerable<T>? Data { get; set; }
}

View File

@@ -0,0 +1,41 @@
using Radzen.Blazor;
namespace Radzen;
/// <summary>
/// Supplies information about a <see cref="RadzenDataGrid{TItem}.Filter" /> event that is being raised.
/// </summary>
/// <typeparam name="T">The data item type.</typeparam>
public class DataGridColumnFilterEventArgs<T> where T : notnull
{
/// <summary>
/// Gets the filtered RadzenDataGridColumn.
/// </summary>
public RadzenDataGridColumn<T>? Column { get; internal set; }
/// <summary>
/// Gets the new filter value of the filtered column.
/// </summary>
public object? FilterValue { get; internal set; }
/// <summary>
/// Gets the new second filter value of the filtered column.
/// </summary>
public object? SecondFilterValue { get; internal set; }
/// <summary>
/// Gets the new filter operator of the filtered column.
/// </summary>
public FilterOperator FilterOperator { get; internal set; }
/// <summary>
/// Gets the new second filter operator of the filtered column.
/// </summary>
public FilterOperator SecondFilterOperator { get; internal set; }
/// <summary>
/// Gets the new logical filter operator of the filtered column.
/// </summary>
public LogicalFilterOperator LogicalFilterOperator { get; internal set; }
}

View File

@@ -0,0 +1,21 @@
using Radzen.Blazor;
namespace Radzen;
/// <summary>
/// Supplies information about a <see cref="RadzenDataGrid{TItem}.Group" /> event that is being raised.
/// </summary>
/// <typeparam name="T">The data item type.</typeparam>
public class DataGridColumnGroupEventArgs<T> where T : notnull
{
/// <summary>
/// Gets the grouped RadzenDataGridColumn.
/// </summary>
public RadzenDataGridColumn<T>? Column { get; internal set; }
/// <summary>
/// Gets the group descriptor.
/// </summary>
public GroupDescriptor? GroupDescriptor { get; internal set; }
}

View File

@@ -0,0 +1,26 @@
using Radzen.Blazor;
namespace Radzen;
/// <summary>
/// Supplies information about a <see cref="RadzenDataGrid{TItem}.ColumnReordered" /> event that is being raised.
/// </summary>
/// <typeparam name="T">The data item type.</typeparam>
public class DataGridColumnReorderedEventArgs<T> where T : notnull
{
/// <summary>
/// Gets the reordered RadzenDataGridColumn.
/// </summary>
public RadzenDataGridColumn<T>? Column { get; internal set; }
/// <summary>
/// Gets the old index of the column.
/// </summary>
public int OldIndex { get; internal set; }
/// <summary>
/// Gets the new index of the column.
/// </summary>
public int NewIndex { get; internal set; }
}

View File

@@ -0,0 +1,27 @@
using Radzen.Blazor;
namespace Radzen;
/// <summary>
/// Supplies information about a <see cref="RadzenDataGrid{TItem}.ColumnReordering" /> event that is being raised.
/// </summary>
/// <typeparam name="T">The data item type.</typeparam>
public class DataGridColumnReorderingEventArgs<T> where T : notnull
{
/// <summary>
/// Gets the reordered RadzenDataGridColumn.
/// </summary>
public RadzenDataGridColumn<T>? Column { get; internal set; }
/// <summary>
/// Gets the reordered to RadzenDataGridColumn.
/// </summary>
public RadzenDataGridColumn<T>? ToColumn { get; internal set; }
/// <summary>
/// Gets or sets a value which will cancel the event.
/// </summary>
/// <value><c>true</c> to cancel the event; otherwise, <c>false</c>.</value>
public bool Cancel { get; set; }
}

View File

@@ -0,0 +1,21 @@
using Radzen.Blazor;
namespace Radzen;
/// <summary>
/// Supplies information about a <see cref="RadzenDataGrid{TItem}.ColumnResized" /> event that is being raised.
/// </summary>
/// <typeparam name="T">The data item type.</typeparam>
public class DataGridColumnResizedEventArgs<T> where T : notnull
{
/// <summary>
/// Gets the resized RadzenDataGridColumn.
/// </summary>
public RadzenDataGridColumn<T>? Column { get; internal set; }
/// <summary>
/// Gets the new width of the resized column.
/// </summary>
public double Width { get; internal set; }
}

View File

@@ -0,0 +1,81 @@
namespace Radzen;
/// <summary>
/// DataGrid column settings class used to Save/Load settings.
/// </summary>
public class DataGridColumnSettings
{
/// <summary>
/// Property.
/// </summary>
public string UniqueID { get; set; } = string.Empty;
/// <summary>
/// Property.
/// </summary>
public string Property { get; set; } = string.Empty;
/// <summary>
/// Visible.
/// </summary>
public bool Visible { get; set; }
/// <summary>
/// Width.
/// </summary>
public string Width { get; set; } = string.Empty;
/// <summary>
/// OrderIndex.
/// </summary>
public int? OrderIndex { get; set; }
/// <summary>
/// SortOrder.
/// </summary>
public SortOrder? SortOrder { get; set; }
/// <summary>
/// SortIndex.
/// </summary>
public int? SortIndex { get; set; }
/// <summary>
/// FilterValue.
/// </summary>
public object? FilterValue { get; set; }
/// <summary>
/// FilterOperator.
/// </summary>
public FilterOperator FilterOperator { get; set; }
/// <summary>
/// SecondFilterValue.
/// </summary>
public object? SecondFilterValue { get; set; }
/// <summary>
/// SecondFilterOperator.
/// </summary>
public FilterOperator SecondFilterOperator { get; set; }
/// <summary>
/// LogicalFilterOperator.
/// </summary>
public LogicalFilterOperator LogicalFilterOperator { get; set; }
/// <summary>
/// CustomFilterExpression.
/// </summary>
public string CustomFilterExpression { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the mode that determines whether the filter applies to any or all items in a collection.
/// </summary>
/// <value>
/// A <see cref="CollectionFilterMode"/> value indicating whether the filter is satisfied by any or all items.
/// </value>
public CollectionFilterMode CollectionFilterMode { get; set; }
}

View File

@@ -0,0 +1,21 @@
using Radzen.Blazor;
namespace Radzen;
/// <summary>
/// Supplies information about a <see cref="RadzenDataGrid{TItem}.Sort" /> event that is being raised.
/// </summary>
/// <typeparam name="T">The data item type.</typeparam>
public class DataGridColumnSortEventArgs<T> where T : notnull
{
/// <summary>
/// Gets the sorted RadzenDataGridColumn.
/// </summary>
public RadzenDataGridColumn<T>? Column { get; internal set; }
/// <summary>
/// Gets the new sort order of the sorted column.
/// </summary>
public SortOrder? SortOrder { get; internal set; }
}

View File

@@ -0,0 +1,18 @@
namespace Radzen;
/// <summary>
/// Specifies the inline edit mode behavior of <see cref="Radzen.Blazor.RadzenDataGrid{TItem}" />.
/// </summary>
public enum DataGridEditMode
{
/// <summary>
/// The user can edit only one row at a time. Editing a different row cancels edit mode for the last edited row.
/// </summary>
Single,
/// <summary>
/// The user can edit multiple rows.
/// </summary>
Multiple
}

View File

@@ -0,0 +1,18 @@
namespace Radzen;
/// <summary>
/// Specifies the expand behavior of <see cref="Radzen.Blazor.RadzenDataGrid{TItem}" />.
/// </summary>
public enum DataGridExpandMode
{
/// <summary>
/// The user can expand only one row at a time. Expanding a different row collapses the last expanded row.
/// </summary>
Single,
/// <summary>
/// The user can expand multiple rows.
/// </summary>
Multiple
}

View File

@@ -0,0 +1,33 @@
namespace Radzen;
/// <summary>
/// Specifies the grid lines of <see cref="Radzen.Blazor.RadzenDataGrid{TItem}" />.
/// </summary>
public enum DataGridGridLines
{
/// <summary>
/// Theme default.
/// </summary>
Default,
/// <summary>
/// Both horizontal and vertical grid lines.
/// </summary>
Both,
/// <summary>
/// No grid lines.
/// </summary>
None,
/// <summary>
/// Horizontal grid lines.
/// </summary>
Horizontal,
/// <summary>
/// Vertical grid lines.
/// </summary>
Vertical
}

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