mirror of
https://github.com/radzenhq/radzen-blazor.git
synced 2026-02-06 05:35:17 +00:00
Compare commits
356 Commits
8.1.2
...
spreadshee
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed0f8892a8 | ||
|
|
452e3f046c | ||
|
|
797fcefbc8 | ||
|
|
9677545c6c | ||
|
|
00f37c4562 | ||
|
|
69dfd2a672 | ||
|
|
8f35d495f2 | ||
|
|
e6e91e96d7 | ||
|
|
7b0e05069e | ||
|
|
425b36db7f | ||
|
|
7e28102477 | ||
|
|
722ae13c00 | ||
|
|
11863e2f91 | ||
|
|
0b5e629f78 | ||
|
|
1639325741 | ||
|
|
665372b397 | ||
|
|
0d218e174f | ||
|
|
c88a4c19fe | ||
|
|
bb1b810c72 | ||
|
|
d098a6c0f1 | ||
|
|
f0edbce9c6 | ||
|
|
112f7d1b70 | ||
|
|
cd6eba4882 | ||
|
|
b1aae0904c | ||
|
|
019977d7e7 | ||
|
|
c556e0de1a | ||
|
|
8fdf1ba312 | ||
|
|
7fe4f0f96d | ||
|
|
c70efee29a | ||
|
|
b1c1687c48 | ||
|
|
082894054b | ||
|
|
da19e7404b | ||
|
|
3cb7106c4f | ||
|
|
716a75872d | ||
|
|
ae1c0d8ee1 | ||
|
|
463db22a2e | ||
|
|
f104d939a2 | ||
|
|
9da74fb81d | ||
|
|
b694daa52e | ||
|
|
4fd3e3c910 | ||
|
|
4c619fa5c9 | ||
|
|
83bf1ae2cd | ||
|
|
f75ed5734c | ||
|
|
09f229602d | ||
|
|
c8225a71bb | ||
|
|
1e881ddd6e | ||
|
|
095c5d2059 | ||
|
|
019f367466 | ||
|
|
28918e792b | ||
|
|
57d832549d | ||
|
|
7c0768fc7a | ||
|
|
fd69705fd9 | ||
|
|
55b58a744c | ||
|
|
e264d654c6 | ||
|
|
d0f00bd7f6 | ||
|
|
1a62deaf35 | ||
|
|
7fcf4047a1 | ||
|
|
ac4fd8d5b3 | ||
|
|
48d1c87908 | ||
|
|
dd7a13f3cf | ||
|
|
db3c81388a | ||
|
|
1e6dd77759 | ||
|
|
8a81491c29 | ||
|
|
86245ae162 | ||
|
|
05b957a8d3 | ||
|
|
21828c6ae7 | ||
|
|
89e91e5c0e | ||
|
|
06c4d41273 | ||
|
|
60cb216fbd | ||
|
|
f4d916a0de | ||
|
|
eb54d808cf | ||
|
|
0565451a97 | ||
|
|
8174ac2725 | ||
|
|
493bd69a1e | ||
|
|
794d8255cf | ||
|
|
edadfe493b | ||
|
|
c7bda57838 | ||
|
|
947a23a4cb | ||
|
|
bf30dabe4d | ||
|
|
c5b6b8da95 | ||
|
|
e171252ecc | ||
|
|
7d090a4147 | ||
|
|
bbd2cd777b | ||
|
|
d7bc875d83 | ||
|
|
ae997416a2 | ||
|
|
97d94b2512 | ||
|
|
14ec0a8e4e | ||
|
|
985a802314 | ||
|
|
a3b4526c33 | ||
|
|
42e595ed85 | ||
|
|
cc62ceae3b | ||
|
|
1d0e748c91 | ||
|
|
b70a6cba99 | ||
|
|
cb469e97c5 | ||
|
|
0ebc789de0 | ||
|
|
299f565d66 | ||
|
|
8dd6aba59c | ||
|
|
ebf0e4fadf | ||
|
|
2e1aeffbf8 | ||
|
|
0a83fee55d | ||
|
|
5a89117c6f | ||
|
|
eb0e96ea42 | ||
|
|
a9f38fa9e9 | ||
|
|
b680633b5c | ||
|
|
ff5ce9298a | ||
|
|
2b4bf2aef4 | ||
|
|
fd59a1a1be | ||
|
|
3ff93dd0e6 | ||
|
|
b8edfad166 | ||
|
|
6aaf42d883 | ||
|
|
9306c6f6d7 | ||
|
|
1ddc854da9 | ||
|
|
ae5c48f5e5 | ||
|
|
e47848bf5a | ||
|
|
e09002fa43 | ||
|
|
eb817de8e4 | ||
|
|
b2bc210eca | ||
|
|
da230e0cbe | ||
|
|
ffae640c3f | ||
|
|
9f3e0d6098 | ||
|
|
4078927d58 | ||
|
|
c7994e0479 | ||
|
|
26c87de08c | ||
|
|
0b938dd51c | ||
|
|
8427d8688b | ||
|
|
9f198cbd23 | ||
|
|
a97e378800 | ||
|
|
790f13ef2b | ||
|
|
179d59c195 | ||
|
|
87bdc2f859 | ||
|
|
d75cf817c4 | ||
|
|
ca0806d5b1 | ||
|
|
3318386fe6 | ||
|
|
44a835cfe8 | ||
|
|
81b28b9602 | ||
|
|
dc8ea54c91 | ||
|
|
1d40582cbd | ||
|
|
e4cbcfdf74 | ||
|
|
7ab385e552 | ||
|
|
9b713a7f38 | ||
|
|
11400e3690 | ||
|
|
fc31bd6fc6 | ||
|
|
d1d9a254d7 | ||
|
|
d79f3a887d | ||
|
|
849ec9c91a | ||
|
|
13afd0fddb | ||
|
|
fa7120571d | ||
|
|
4d4e882dcc | ||
|
|
af8c7179d1 | ||
|
|
c743054184 | ||
|
|
935d850296 | ||
|
|
ad6b6886f1 | ||
|
|
d5d08c89de | ||
|
|
5a246f732e | ||
|
|
669ad421d9 | ||
|
|
98e8393d14 | ||
|
|
de8dfb7e93 | ||
|
|
61b32d6490 | ||
|
|
5057fb1cc8 | ||
|
|
ed49dfc2fa | ||
|
|
839018795c | ||
|
|
c541f9d3e5 | ||
|
|
5e5957e4c4 | ||
|
|
84a759267e | ||
|
|
eb1e6fab0d | ||
|
|
6094953009 | ||
|
|
b845c73ad6 | ||
|
|
2788606105 | ||
|
|
aebf267320 | ||
|
|
33896ea8db | ||
|
|
cb8699f315 | ||
|
|
2a43db6560 | ||
|
|
0ad1200870 | ||
|
|
2debdbfd38 | ||
|
|
07c96bd4bd | ||
|
|
704c6abe7c | ||
|
|
e0e0e608e2 | ||
|
|
40e15a0720 | ||
|
|
d6235fd147 | ||
|
|
c5a3a911e0 | ||
|
|
4e7a037bc5 | ||
|
|
f1c3f46ad7 | ||
|
|
6c0e39da20 | ||
|
|
2a9d638acf | ||
|
|
426e1ba8e8 | ||
|
|
1bb36ef207 | ||
|
|
67cde0fd59 | ||
|
|
0696cd20d5 | ||
|
|
34fce5188f | ||
|
|
273ba0381f | ||
|
|
63a05d86e8 | ||
|
|
283e115d0a | ||
|
|
b513ebba8e | ||
|
|
e324cea7b9 | ||
|
|
c3cda91a2d | ||
|
|
bb9f611629 | ||
|
|
6cc8e62b15 | ||
|
|
5a44b76adb | ||
|
|
109d6bc617 | ||
|
|
7249ff6107 | ||
|
|
406830fa13 | ||
|
|
a6a3278443 | ||
|
|
9fd8531529 | ||
|
|
c687976796 | ||
|
|
ae511929f7 | ||
|
|
e78e756206 | ||
|
|
2e9e0dac1a | ||
|
|
bff97712e2 | ||
|
|
78bc25cce0 | ||
|
|
a2e829417d | ||
|
|
9902413daf | ||
|
|
06a35f4a73 | ||
|
|
e2a46157b9 | ||
|
|
c4913a94c4 | ||
|
|
d6e04c3ae8 | ||
|
|
5b17ef5217 | ||
|
|
8edeedc32c | ||
|
|
543dbc9e50 | ||
|
|
3710f4297b | ||
|
|
91d6ba4da8 | ||
|
|
fbfa3c1abe | ||
|
|
d13d7bef0c | ||
|
|
510f5d7190 | ||
|
|
595b98d4b7 | ||
|
|
c696107aeb | ||
|
|
632722cd7b | ||
|
|
60c264ea49 | ||
|
|
10ce92264d | ||
|
|
2e5c7aa6bc | ||
|
|
1e7e2c5b51 | ||
|
|
46790ce3fe | ||
|
|
7bd891f8f0 | ||
|
|
1ae16acbac | ||
|
|
2281fb7f61 | ||
|
|
89e8db6c6e | ||
|
|
11300692e5 | ||
|
|
a31161c6d8 | ||
|
|
edd85f8ec0 | ||
|
|
0d420604dd | ||
|
|
777f5666e2 | ||
|
|
e7bded641f | ||
|
|
6f0dfbe038 | ||
|
|
0e92eeb50e | ||
|
|
50b6cb879a | ||
|
|
739ebde6a8 | ||
|
|
24ef74f16d | ||
|
|
9e0a57ca5b | ||
|
|
feaebb6f0f | ||
|
|
9a45759414 | ||
|
|
14c5a0447b | ||
|
|
7793e03d82 | ||
|
|
f422573fcc | ||
|
|
516109ecd3 | ||
|
|
31d32b16a5 | ||
|
|
435c1bd6e2 | ||
|
|
6d5ebf3f58 | ||
|
|
b97036b447 | ||
|
|
d7ef3cb896 | ||
|
|
2218eefa67 | ||
|
|
666d33d767 | ||
|
|
3f4f83d354 | ||
|
|
3dea547559 | ||
|
|
0e9c6acb84 | ||
|
|
1b6881673e | ||
|
|
f069b33b60 | ||
|
|
c0a86e31da | ||
|
|
7b95778efe | ||
|
|
ef8a102d0a | ||
|
|
95448de3ed | ||
|
|
0a574762c7 | ||
|
|
59b1440990 | ||
|
|
98d6729d4b | ||
|
|
135a0bbe5c | ||
|
|
c3f579931d | ||
|
|
21c51e81d2 | ||
|
|
e6538c95ad | ||
|
|
0e63e87f9b | ||
|
|
1e900ec775 | ||
|
|
5ff30874dd | ||
|
|
94550006c4 | ||
|
|
202636ce72 | ||
|
|
cae8c6f622 | ||
|
|
0e03c4377f | ||
|
|
7497ea1262 | ||
|
|
d68bb34f6f | ||
|
|
732a6f4942 | ||
|
|
70fb896ae1 | ||
|
|
034eae6722 | ||
|
|
3472949bf0 | ||
|
|
c333b8ca30 | ||
|
|
17e3fbdabf | ||
|
|
30c5c9dfaf | ||
|
|
585d1ee38a | ||
|
|
3255afb487 | ||
|
|
9f75648b50 | ||
|
|
50406f8984 | ||
|
|
f24c7ebc5f | ||
|
|
75dcecfab0 | ||
|
|
34c603ce53 | ||
|
|
7785a73876 | ||
|
|
cfa8f731f2 | ||
|
|
5ecd05c7b3 | ||
|
|
4ec95c2f1c | ||
|
|
9e4413b02e | ||
|
|
38d0d689b3 | ||
|
|
799c5e9e4e | ||
|
|
b98dffda8f | ||
|
|
d816d841a8 | ||
|
|
1730e02dc3 | ||
|
|
53737d2f3c | ||
|
|
932dc3f9f6 | ||
|
|
567fdb6a36 | ||
|
|
3e323b929d | ||
|
|
a53fb126ac | ||
|
|
ec2c17bc21 | ||
|
|
3894f31c2c | ||
|
|
342b96801a | ||
|
|
36f1dfe2e0 | ||
|
|
f90951add1 | ||
|
|
7d65c45833 | ||
|
|
3629235a00 | ||
|
|
09ff7744dc | ||
|
|
1fa9d59634 | ||
|
|
ccec9dbc70 | ||
|
|
a49dbad913 | ||
|
|
4f0497158b | ||
|
|
4757d158a8 | ||
|
|
4bbd371a42 | ||
|
|
10b4b877de | ||
|
|
3bd030c397 | ||
|
|
7c408808c9 | ||
|
|
39dcaa8fc4 | ||
|
|
04cdd584a2 | ||
|
|
2693487f81 | ||
|
|
d73dce8804 | ||
|
|
cf333e8c15 | ||
|
|
5aea28611b | ||
|
|
c58ccf0a66 | ||
|
|
d5c870eb9d | ||
|
|
a41b00e0be | ||
|
|
383ee7e577 | ||
|
|
b3b1f887ef | ||
|
|
b6089f5d28 | ||
|
|
2d7d5cad39 | ||
|
|
cb06833882 | ||
|
|
cf3ab86cec | ||
|
|
efd3d18484 | ||
|
|
e6baaa6184 | ||
|
|
81c943ecda | ||
|
|
587a4f479c | ||
|
|
5638152121 | ||
|
|
81fc744270 | ||
|
|
2437b9e028 | ||
|
|
d148cac10e | ||
|
|
af8c9e3cc3 | ||
|
|
b64c71c307 |
11
.github/ISSUE_TEMPLATE/bug_report.md
vendored
11
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -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.
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -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.
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -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.
|
||||
|
||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -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
66
.github/workflows/deploy.yml
vendored
Normal 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
|
||||
85
Directory.Build.props
Normal file
85
Directory.Build.props
Normal 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>
|
||||
|
||||
49
Dockerfile
49
Dockerfile
@@ -1,23 +1,48 @@
|
||||
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"]
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -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
|
||||
|
||||
@@ -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/pricing).
|
||||
Paid support is available as part of the [Radzen Blazor subscription](https://www.radzen.com/pricing).
|
||||
|
||||
### :computer: Native
|
||||
|
||||
@@ -38,7 +38,7 @@ Everybody is welcome to visit the [Radzen Community forum](https://forum.radzen.
|
||||
|
||||
The Radzen team monitors the forum threads, but does not guarantee a response to every question. For guaranteed responses you may consider the dedicated support option.
|
||||
|
||||
Dedicated support for the Radzen Blazor Components is available as part of the [Radzen Professional subscription](https://www.radzen.com/pricing).
|
||||
Dedicated support for the Radzen Blazor Components is available as part of the [Radzen 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:
|
||||
- An industry-leading WYSIWYG Blazor design time canvas
|
||||
@@ -54,4 +54,4 @@ Check the [getting started](https://blazor.radzen.com/get-started) instructions
|
||||
|
||||
## 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.
|
||||
|
||||
195
Radzen.Blazor.Tests/AccordionTests.cs
Normal file
195
Radzen.Blazor.Tests/AccordionTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
195
Radzen.Blazor.Tests/AlertTests.cs
Normal file
195
Radzen.Blazor.Tests/AlertTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
46
Radzen.Blazor.Tests/CardTests.cs
Normal file
46
Radzen.Blazor.Tests/CardTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
173
Radzen.Blazor.Tests/CarouselTests.cs
Normal file
173
Radzen.Blazor.Tests/CarouselTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
168
Radzen.Blazor.Tests/CheckBoxListTests.cs
Normal file
168
Radzen.Blazor.Tests/CheckBoxListTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
74
Radzen.Blazor.Tests/ColumnTests.cs
Normal file
74
Radzen.Blazor.Tests/ColumnTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1728,6 +1728,7 @@ namespace Radzen.Blazor.Tests
|
||||
{
|
||||
builder.OpenComponent(0, typeof(RadzenDataGridColumn<dynamic>));
|
||||
builder.AddAttribute(1, "Property", "Name");
|
||||
builder.AddAttribute(2, "Type", typeof(string));
|
||||
builder.CloseComponent();
|
||||
});
|
||||
parameterBuilder.Add<bool>(p => p.AllowFiltering, true);
|
||||
@@ -1751,6 +1752,7 @@ namespace Radzen.Blazor.Tests
|
||||
{
|
||||
builder.OpenComponent(0, typeof(RadzenDataGridColumn<dynamic>));
|
||||
builder.AddAttribute(1, "Property", "Name");
|
||||
builder.AddAttribute(1, "Type", typeof(string));
|
||||
builder.CloseComponent();
|
||||
});
|
||||
parameterBuilder.Add<bool>(p => p.AllowFiltering, true);
|
||||
@@ -2804,6 +2806,12 @@ namespace Radzen.Blazor.Tests
|
||||
builder.AddAttribute(1, "Property", "Id");
|
||||
builder.AddAttribute(2, "Title", "Id");
|
||||
builder.CloseComponent();
|
||||
|
||||
builder.OpenComponent(3, typeof(RadzenDataGridColumn<dynamic>));
|
||||
builder.AddAttribute(4, "Property", "Tags");
|
||||
builder.AddAttribute(5, "Title", "Tags");
|
||||
builder.AddAttribute(6, "Type", typeof(object[]));
|
||||
builder.CloseComponent();
|
||||
});
|
||||
parameterBuilder.Add<bool>(p => p.AllowFiltering, true);
|
||||
parameterBuilder.Add<FilterMode>(p => p.FilterMode, FilterMode.SimpleWithMenu);
|
||||
@@ -2834,6 +2842,12 @@ namespace Radzen.Blazor.Tests
|
||||
builder.AddAttribute(1, "Property", "Id");
|
||||
builder.AddAttribute(2, "Title", "Id");
|
||||
builder.CloseComponent();
|
||||
|
||||
builder.OpenComponent(3, typeof(RadzenDataGridColumn<dynamic>));
|
||||
builder.AddAttribute(4, "Property", "Tags");
|
||||
builder.AddAttribute(5, "Title", "Tags");
|
||||
builder.AddAttribute(6, "Type", typeof(object[]));
|
||||
builder.CloseComponent();
|
||||
});
|
||||
parameterBuilder.Add<bool>(p => p.AllowFiltering, true);
|
||||
parameterBuilder.Add<FilterMode>(p => p.FilterMode, FilterMode.SimpleWithMenu);
|
||||
|
||||
@@ -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")]
|
||||
|
||||
289
Radzen.Blazor.Tests/DropDownDataGridTests.cs
Normal file
289
Radzen.Blazor.Tests/DropDownDataGridTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
182
Radzen.Blazor.Tests/FileInputTests.cs
Normal file
182
Radzen.Blazor.Tests/FileInputTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
163
Radzen.Blazor.Tests/FormFieldTests.cs
Normal file
163
Radzen.Blazor.Tests/FormFieldTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
103
Radzen.Blazor.Tests/HeadingTests.cs
Normal file
103
Radzen.Blazor.Tests/HeadingTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
104
Radzen.Blazor.Tests/HtmlEditorTests.cs
Normal file
104
Radzen.Blazor.Tests/HtmlEditorTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
197
Radzen.Blazor.Tests/ListBoxTests.cs
Normal file
197
Radzen.Blazor.Tests/ListBoxTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
65
Radzen.Blazor.Tests/MediaQueryTests.cs
Normal file
65
Radzen.Blazor.Tests/MediaQueryTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
58
Radzen.Blazor.Tests/MenuTests.cs
Normal file
58
Radzen.Blazor.Tests/MenuTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
220
Radzen.Blazor.Tests/PickListTests.cs
Normal file
220
Radzen.Blazor.Tests/PickListTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
644
Radzen.Blazor.Tests/PivotDataGridTests.cs
Normal file
644
Radzen.Blazor.Tests/PivotDataGridTests.cs
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
120
Radzen.Blazor.Tests/ProfileMenuItemTests.cs
Normal file
120
Radzen.Blazor.Tests/ProfileMenuItemTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,7 +328,7 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
var result = data.Where(filters, LogicalFilterOperator.And, FilterCaseSensitivity.Default).ToList();
|
||||
|
||||
Assert.Equal(1, result.Count);
|
||||
Assert.Single(result);
|
||||
Assert.Equal("Eve", result[0].Name);
|
||||
}
|
||||
|
||||
@@ -1689,6 +1689,838 @@ namespace Radzen.Blazor.Tests
|
||||
Assert.Single(result);
|
||||
Assert.Equal(2, result[0].Id);
|
||||
}
|
||||
|
||||
// CollectionFilterMode tests
|
||||
[Fact]
|
||||
public void Where_FiltersCollectionItemProperty_WithCollectionFilterMode_Any_WithEquals()
|
||||
{
|
||||
var testData = new[]
|
||||
{
|
||||
new { Id = 1, Tags = new[] { new { Name = "tag1", Value = 10 }, new { Name = "tag2", Value = 20 } }.ToList() },
|
||||
new { Id = 2, Tags = new[] { new { Name = "tag3", Value = 30 }, new { Name = "tag4", Value = 40 } }.ToList() },
|
||||
new { Id = 3, Tags = new[] { new { Name = "tag1", Value = 50 }, new { Name = "tag5", Value = 60 } }.ToList() }
|
||||
}.AsQueryable();
|
||||
|
||||
var filters = new List<FilterDescriptor>
|
||||
{
|
||||
new FilterDescriptor
|
||||
{
|
||||
Property = "Tags",
|
||||
FilterProperty = "Name",
|
||||
FilterValue = "tag1",
|
||||
FilterOperator = FilterOperator.Equals,
|
||||
CollectionFilterMode = CollectionFilterMode.Any
|
||||
}
|
||||
};
|
||||
|
||||
var result = testData.Where(filters, LogicalFilterOperator.And, FilterCaseSensitivity.Default).ToList();
|
||||
|
||||
// Should return items where at least one tag has Name == "tag1"
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(result, r => r.Id == 1);
|
||||
Assert.Contains(result, r => r.Id == 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Where_FiltersCollectionItemProperty_WithCollectionFilterMode_All_WithEquals()
|
||||
{
|
||||
var testData = new[]
|
||||
{
|
||||
new { Id = 1, Tags = new[] { new { Name = "active", Value = 10 }, new { Name = "active", Value = 20 } }.ToList() },
|
||||
new { Id = 2, Tags = new[] { new { Name = "active", Value = 30 }, new { Name = "inactive", Value = 40 } }.ToList() },
|
||||
new { Id = 3, Tags = new[] { new { Name = "active", Value = 50 } }.ToList() }
|
||||
}.AsQueryable();
|
||||
|
||||
var filters = new List<FilterDescriptor>
|
||||
{
|
||||
new FilterDescriptor
|
||||
{
|
||||
Property = "Tags",
|
||||
FilterProperty = "Name",
|
||||
FilterValue = "active",
|
||||
FilterOperator = FilterOperator.Equals,
|
||||
CollectionFilterMode = CollectionFilterMode.All
|
||||
}
|
||||
};
|
||||
|
||||
var result = testData.Where(filters, LogicalFilterOperator.And, FilterCaseSensitivity.Default).ToList();
|
||||
|
||||
// Should return items where all tags have Name == "active"
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(result, r => r.Id == 1);
|
||||
Assert.Contains(result, r => r.Id == 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Where_FiltersCollectionItemProperty_WithCollectionFilterMode_Any_WithGreaterThan()
|
||||
{
|
||||
var testData = new[]
|
||||
{
|
||||
new { Id = 1, Tags = new[] { new { Name = "tag1", Value = 10 }, new { Name = "tag2", Value = 20 } }.ToList() },
|
||||
new { Id = 2, Tags = new[] { new { Name = "tag3", Value = 30 }, new { Name = "tag4", Value = 60 } }.ToList() },
|
||||
new { Id = 3, Tags = new[] { new { Name = "tag5", Value = 5 }, new { Name = "tag6", Value = 8 } }.ToList() }
|
||||
}.AsQueryable();
|
||||
|
||||
var filters = new List<FilterDescriptor>
|
||||
{
|
||||
new FilterDescriptor
|
||||
{
|
||||
Property = "Tags",
|
||||
FilterProperty = "Value",
|
||||
FilterValue = 50,
|
||||
FilterOperator = FilterOperator.GreaterThan,
|
||||
CollectionFilterMode = CollectionFilterMode.Any
|
||||
}
|
||||
};
|
||||
|
||||
var result = testData.Where(filters, LogicalFilterOperator.And, FilterCaseSensitivity.Default).ToList();
|
||||
|
||||
// Should return items where at least one tag has Value > 50
|
||||
Assert.Single(result);
|
||||
Assert.Equal(2, result[0].Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Where_FiltersCollectionItemProperty_WithCollectionFilterMode_All_WithGreaterThan()
|
||||
{
|
||||
var testData = new[]
|
||||
{
|
||||
new { Id = 1, Tags = new[] { new { Name = "tag1", Value = 60 }, new { Name = "tag2", Value = 70 } }.ToList() },
|
||||
new { Id = 2, Tags = new[] { new { Name = "tag3", Value = 30 }, new { Name = "tag4", Value = 60 } }.ToList() },
|
||||
new { Id = 3, Tags = new[] { new { Name = "tag5", Value = 55 }, new { Name = "tag6", Value = 80 } }.ToList() }
|
||||
}.AsQueryable();
|
||||
|
||||
var filters = new List<FilterDescriptor>
|
||||
{
|
||||
new FilterDescriptor
|
||||
{
|
||||
Property = "Tags",
|
||||
FilterProperty = "Value",
|
||||
FilterValue = 50,
|
||||
FilterOperator = FilterOperator.GreaterThan,
|
||||
CollectionFilterMode = CollectionFilterMode.All
|
||||
}
|
||||
};
|
||||
|
||||
var result = testData.Where(filters, LogicalFilterOperator.And, FilterCaseSensitivity.Default).ToList();
|
||||
|
||||
// Should return items where all tags have Value > 50
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(result, r => r.Id == 1);
|
||||
Assert.Contains(result, r => r.Id == 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Where_FiltersCollectionItemProperty_WithCollectionFilterMode_Any_WithContains()
|
||||
{
|
||||
var testData = new[]
|
||||
{
|
||||
new { Id = 1, Tags = new[] { new { Name = "important-task", Value = 10 }, new { Name = "review", Value = 20 } }.ToList() },
|
||||
new { Id = 2, Tags = new[] { new { Name = "normal", Value = 30 }, new { Name = "basic", Value = 40 } }.ToList() },
|
||||
new { Id = 3, Tags = new[] { new { Name = "important-meeting", Value = 50 }, new { Name = "urgent", Value = 60 } }.ToList() }
|
||||
}.AsQueryable();
|
||||
|
||||
var filters = new List<FilterDescriptor>
|
||||
{
|
||||
new FilterDescriptor
|
||||
{
|
||||
Property = "Tags",
|
||||
FilterProperty = "Name",
|
||||
FilterValue = "important",
|
||||
FilterOperator = FilterOperator.Contains,
|
||||
CollectionFilterMode = CollectionFilterMode.Any
|
||||
}
|
||||
};
|
||||
|
||||
var result = testData.Where(filters, LogicalFilterOperator.And, FilterCaseSensitivity.Default).ToList();
|
||||
|
||||
// Should return items where at least one tag contains "important"
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(result, r => r.Id == 1);
|
||||
Assert.Contains(result, r => r.Id == 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Where_FiltersCollectionItemProperty_WithCollectionFilterMode_All_WithContains()
|
||||
{
|
||||
var testData = new[]
|
||||
{
|
||||
new { Id = 1, Tags = new[] { new { Name = "important-task", Value = 10 }, new { Name = "important-note", Value = 20 } }.ToList() },
|
||||
new { Id = 2, Tags = new[] { new { Name = "important-meeting", Value = 30 }, new { Name = "basic", Value = 40 } }.ToList() },
|
||||
new { Id = 3, Tags = new[] { new { Name = "important-reminder", Value = 50 } }.ToList() }
|
||||
}.AsQueryable();
|
||||
|
||||
var filters = new List<FilterDescriptor>
|
||||
{
|
||||
new FilterDescriptor
|
||||
{
|
||||
Property = "Tags",
|
||||
FilterProperty = "Name",
|
||||
FilterValue = "important",
|
||||
FilterOperator = FilterOperator.Contains,
|
||||
CollectionFilterMode = CollectionFilterMode.All
|
||||
}
|
||||
};
|
||||
|
||||
var result = testData.Where(filters, LogicalFilterOperator.And, FilterCaseSensitivity.Default).ToList();
|
||||
|
||||
// Should return items where all tags contain "important"
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(result, r => r.Id == 1);
|
||||
Assert.Contains(result, r => r.Id == 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Where_FiltersCollectionItemProperty_WithCollectionFilterMode_Any_WithStartsWith()
|
||||
{
|
||||
var testData = new[]
|
||||
{
|
||||
new { Id = 1, Tags = new[] { new { Name = "prefix_alpha", Value = 10 }, new { Name = "other_beta", Value = 20 } }.ToList() },
|
||||
new { Id = 2, Tags = new[] { new { Name = "prefix_gamma", Value = 30 }, new { Name = "prefix_delta", Value = 40 } }.ToList() },
|
||||
new { Id = 3, Tags = new[] { new { Name = "other_epsilon", Value = 50 }, new { Name = "other_zeta", Value = 60 } }.ToList() }
|
||||
}.AsQueryable();
|
||||
|
||||
var filters = new List<FilterDescriptor>
|
||||
{
|
||||
new FilterDescriptor
|
||||
{
|
||||
Property = "Tags",
|
||||
FilterProperty = "Name",
|
||||
FilterValue = "prefix_",
|
||||
FilterOperator = FilterOperator.StartsWith,
|
||||
CollectionFilterMode = CollectionFilterMode.Any
|
||||
}
|
||||
};
|
||||
|
||||
var result = testData.Where(filters, LogicalFilterOperator.And, FilterCaseSensitivity.Default).ToList();
|
||||
|
||||
// Should return items where at least one tag starts with "prefix_"
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(result, r => r.Id == 1);
|
||||
Assert.Contains(result, r => r.Id == 2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Where_FiltersCollectionItemProperty_WithCollectionFilterMode_All_WithStartsWith()
|
||||
{
|
||||
var testData = new[]
|
||||
{
|
||||
new { Id = 1, Tags = new[] { new { Name = "prefix_alpha", Value = 10 }, new { Name = "prefix_beta", Value = 20 } }.ToList() },
|
||||
new { Id = 2, Tags = new[] { new { Name = "prefix_gamma", Value = 30 }, new { Name = "other_delta", Value = 40 } }.ToList() },
|
||||
new { Id = 3, Tags = new[] { new { Name = "prefix_epsilon", Value = 50 } }.ToList() }
|
||||
}.AsQueryable();
|
||||
|
||||
var filters = new List<FilterDescriptor>
|
||||
{
|
||||
new FilterDescriptor
|
||||
{
|
||||
Property = "Tags",
|
||||
FilterProperty = "Name",
|
||||
FilterValue = "prefix_",
|
||||
FilterOperator = FilterOperator.StartsWith,
|
||||
CollectionFilterMode = CollectionFilterMode.All
|
||||
}
|
||||
};
|
||||
|
||||
var result = testData.Where(filters, LogicalFilterOperator.And, FilterCaseSensitivity.Default).ToList();
|
||||
|
||||
// Should return items where all tags start with "prefix_"
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(result, r => r.Id == 1);
|
||||
Assert.Contains(result, r => r.Id == 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Where_FiltersCollectionItemProperty_WithCollectionFilterMode_Any_WithEndsWith()
|
||||
{
|
||||
var testData = new[]
|
||||
{
|
||||
new { Id = 1, Tags = new[] { new { Name = "file.txt", Value = 10 }, new { Name = "doc.pdf", Value = 20 } }.ToList() },
|
||||
new { Id = 2, Tags = new[] { new { Name = "image.png", Value = 30 }, new { Name = "photo.txt", Value = 40 } }.ToList() },
|
||||
new { Id = 3, Tags = new[] { new { Name = "data.csv", Value = 50 }, new { Name = "info.doc", Value = 60 } }.ToList() }
|
||||
}.AsQueryable();
|
||||
|
||||
var filters = new List<FilterDescriptor>
|
||||
{
|
||||
new FilterDescriptor
|
||||
{
|
||||
Property = "Tags",
|
||||
FilterProperty = "Name",
|
||||
FilterValue = ".txt",
|
||||
FilterOperator = FilterOperator.EndsWith,
|
||||
CollectionFilterMode = CollectionFilterMode.Any
|
||||
}
|
||||
};
|
||||
|
||||
var result = testData.Where(filters, LogicalFilterOperator.And, FilterCaseSensitivity.Default).ToList();
|
||||
|
||||
// Should return items where at least one tag ends with ".txt"
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(result, r => r.Id == 1);
|
||||
Assert.Contains(result, r => r.Id == 2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Where_FiltersCollectionItemProperty_WithCollectionFilterMode_All_WithEndsWith()
|
||||
{
|
||||
var testData = new[]
|
||||
{
|
||||
new { Id = 1, Tags = new[] { new { Name = "file.txt", Value = 10 }, new { Name = "doc.txt", Value = 20 } }.ToList() },
|
||||
new { Id = 2, Tags = new[] { new { Name = "image.txt", Value = 30 }, new { Name = "photo.png", Value = 40 } }.ToList() },
|
||||
new { Id = 3, Tags = new[] { new { Name = "data.txt", Value = 50 } }.ToList() }
|
||||
}.AsQueryable();
|
||||
|
||||
var filters = new List<FilterDescriptor>
|
||||
{
|
||||
new FilterDescriptor
|
||||
{
|
||||
Property = "Tags",
|
||||
FilterProperty = "Name",
|
||||
FilterValue = ".txt",
|
||||
FilterOperator = FilterOperator.EndsWith,
|
||||
CollectionFilterMode = CollectionFilterMode.All
|
||||
}
|
||||
};
|
||||
|
||||
var result = testData.Where(filters, LogicalFilterOperator.And, FilterCaseSensitivity.Default).ToList();
|
||||
|
||||
// Should return items where all tags end with ".txt"
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(result, r => r.Id == 1);
|
||||
Assert.Contains(result, r => r.Id == 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Where_FiltersCollectionItemProperty_WithCollectionFilterMode_Any_WithNotEquals()
|
||||
{
|
||||
var testData = new[]
|
||||
{
|
||||
new { Id = 1, Tags = new[] { new { Name = "active", Value = 10 }, new { Name = "inactive", Value = 20 } }.ToList() },
|
||||
new { Id = 2, Tags = new[] { new { Name = "inactive", Value = 30 }, new { Name = "inactive", Value = 40 } }.ToList() },
|
||||
new { Id = 3, Tags = new[] { new { Name = "pending", Value = 50 }, new { Name = "active", Value = 60 } }.ToList() }
|
||||
}.AsQueryable();
|
||||
|
||||
var filters = new List<FilterDescriptor>
|
||||
{
|
||||
new FilterDescriptor
|
||||
{
|
||||
Property = "Tags",
|
||||
FilterProperty = "Name",
|
||||
FilterValue = "inactive",
|
||||
FilterOperator = FilterOperator.NotEquals,
|
||||
CollectionFilterMode = CollectionFilterMode.Any
|
||||
}
|
||||
};
|
||||
|
||||
var result = testData.Where(filters, LogicalFilterOperator.And, FilterCaseSensitivity.Default).ToList();
|
||||
|
||||
// Should return items where at least one tag is not equal to "inactive"
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(result, r => r.Id == 1);
|
||||
Assert.Contains(result, r => r.Id == 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Where_FiltersCollectionItemProperty_WithCollectionFilterMode_All_WithNotEquals()
|
||||
{
|
||||
var testData = new[]
|
||||
{
|
||||
new { Id = 1, Tags = new[] { new { Name = "active", Value = 10 }, new { Name = "pending", Value = 20 } }.ToList() },
|
||||
new { Id = 2, Tags = new[] { new { Name = "active", Value = 30 }, new { Name = "inactive", Value = 40 } }.ToList() },
|
||||
new { Id = 3, Tags = new[] { new { Name = "pending", Value = 50 }, new { Name = "active", Value = 60 } }.ToList() }
|
||||
}.AsQueryable();
|
||||
|
||||
var filters = new List<FilterDescriptor>
|
||||
{
|
||||
new FilterDescriptor
|
||||
{
|
||||
Property = "Tags",
|
||||
FilterProperty = "Name",
|
||||
FilterValue = "inactive",
|
||||
FilterOperator = FilterOperator.NotEquals,
|
||||
CollectionFilterMode = CollectionFilterMode.All
|
||||
}
|
||||
};
|
||||
|
||||
var result = testData.Where(filters, LogicalFilterOperator.And, FilterCaseSensitivity.Default).ToList();
|
||||
|
||||
// Should return items where all tags are not equal to "inactive"
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(result, r => r.Id == 1);
|
||||
Assert.Contains(result, r => r.Id == 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Where_FiltersCollectionItemProperty_WithCollectionFilterMode_Any_WithDoesNotContain()
|
||||
{
|
||||
var testData = new[]
|
||||
{
|
||||
new { Id = 1, Tags = new[] { new { Name = "test-alpha", Value = 10 }, new { Name = "beta", Value = 20 } }.ToList() },
|
||||
new { Id = 2, Tags = new[] { new { Name = "test-gamma", Value = 30 }, new { Name = "test-delta", Value = 40 } }.ToList() },
|
||||
new { Id = 3, Tags = new[] { new { Name = "epsilon", Value = 50 }, new { Name = "zeta", Value = 60 } }.ToList() }
|
||||
}.AsQueryable();
|
||||
|
||||
var filters = new List<FilterDescriptor>
|
||||
{
|
||||
new FilterDescriptor
|
||||
{
|
||||
Property = "Tags",
|
||||
FilterProperty = "Name",
|
||||
FilterValue = "test",
|
||||
FilterOperator = FilterOperator.DoesNotContain,
|
||||
CollectionFilterMode = CollectionFilterMode.Any
|
||||
}
|
||||
};
|
||||
|
||||
var result = testData.Where(filters, LogicalFilterOperator.And, FilterCaseSensitivity.Default).ToList();
|
||||
|
||||
// Should return items where at least one tag does not contain "test"
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(result, r => r.Id == 1);
|
||||
Assert.Contains(result, r => r.Id == 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Where_FiltersCollectionItemProperty_WithCollectionFilterMode_All_WithDoesNotContain()
|
||||
{
|
||||
var testData = new[]
|
||||
{
|
||||
new { Id = 1, Tags = new[] { new { Name = "alpha", Value = 10 }, new { Name = "beta", Value = 20 } }.ToList() },
|
||||
new { Id = 2, Tags = new[] { new { Name = "test-gamma", Value = 30 }, new { Name = "delta", Value = 40 } }.ToList() },
|
||||
new { Id = 3, Tags = new[] { new { Name = "epsilon", Value = 50 }, new { Name = "zeta", Value = 60 } }.ToList() }
|
||||
}.AsQueryable();
|
||||
|
||||
var filters = new List<FilterDescriptor>
|
||||
{
|
||||
new FilterDescriptor
|
||||
{
|
||||
Property = "Tags",
|
||||
FilterProperty = "Name",
|
||||
FilterValue = "test",
|
||||
FilterOperator = FilterOperator.DoesNotContain,
|
||||
CollectionFilterMode = CollectionFilterMode.All
|
||||
}
|
||||
};
|
||||
|
||||
var result = testData.Where(filters, LogicalFilterOperator.And, FilterCaseSensitivity.Default).ToList();
|
||||
|
||||
// Should return items where all tags do not contain "test"
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(result, r => r.Id == 1);
|
||||
Assert.Contains(result, r => r.Id == 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Where_FiltersCollectionItemProperty_WithCollectionFilterMode_Any_WithLessThan()
|
||||
{
|
||||
var testData = new[]
|
||||
{
|
||||
new { Id = 1, Tags = new[] { new { Name = "tag1", Value = 10 }, new { Name = "tag2", Value = 50 } }.ToList() },
|
||||
new { Id = 2, Tags = new[] { new { Name = "tag3", Value = 60 }, new { Name = "tag4", Value = 70 } }.ToList() },
|
||||
new { Id = 3, Tags = new[] { new { Name = "tag5", Value = 15 }, new { Name = "tag6", Value = 80 } }.ToList() }
|
||||
}.AsQueryable();
|
||||
|
||||
var filters = new List<FilterDescriptor>
|
||||
{
|
||||
new FilterDescriptor
|
||||
{
|
||||
Property = "Tags",
|
||||
FilterProperty = "Value",
|
||||
FilterValue = 20,
|
||||
FilterOperator = FilterOperator.LessThan,
|
||||
CollectionFilterMode = CollectionFilterMode.Any
|
||||
}
|
||||
};
|
||||
|
||||
var result = testData.Where(filters, LogicalFilterOperator.And, FilterCaseSensitivity.Default).ToList();
|
||||
|
||||
// Should return items where at least one tag has Value < 20
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(result, r => r.Id == 1);
|
||||
Assert.Contains(result, r => r.Id == 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Where_FiltersCollectionItemProperty_WithCollectionFilterMode_All_WithLessThan()
|
||||
{
|
||||
var testData = new[]
|
||||
{
|
||||
new { Id = 1, Tags = new[] { new { Name = "tag1", Value = 10 }, new { Name = "tag2", Value = 15 } }.ToList() },
|
||||
new { Id = 2, Tags = new[] { new { Name = "tag3", Value = 18 }, new { Name = "tag4", Value = 70 } }.ToList() },
|
||||
new { Id = 3, Tags = new[] { new { Name = "tag5", Value = 5 }, new { Name = "tag6", Value = 12 } }.ToList() }
|
||||
}.AsQueryable();
|
||||
|
||||
var filters = new List<FilterDescriptor>
|
||||
{
|
||||
new FilterDescriptor
|
||||
{
|
||||
Property = "Tags",
|
||||
FilterProperty = "Value",
|
||||
FilterValue = 20,
|
||||
FilterOperator = FilterOperator.LessThan,
|
||||
CollectionFilterMode = CollectionFilterMode.All
|
||||
}
|
||||
};
|
||||
|
||||
var result = testData.Where(filters, LogicalFilterOperator.And, FilterCaseSensitivity.Default).ToList();
|
||||
|
||||
// Should return items where all tags have Value < 20
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(result, r => r.Id == 1);
|
||||
Assert.Contains(result, r => r.Id == 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Where_FiltersCollectionItemProperty_WithCollectionFilterMode_Any_WithLessThanOrEquals()
|
||||
{
|
||||
var testData = new[]
|
||||
{
|
||||
new { Id = 1, Tags = new[] { new { Name = "tag1", Value = 20 }, new { Name = "tag2", Value = 50 } }.ToList() },
|
||||
new { Id = 2, Tags = new[] { new { Name = "tag3", Value = 60 }, new { Name = "tag4", Value = 70 } }.ToList() },
|
||||
new { Id = 3, Tags = new[] { new { Name = "tag5", Value = 15 }, new { Name = "tag6", Value = 80 } }.ToList() }
|
||||
}.AsQueryable();
|
||||
|
||||
var filters = new List<FilterDescriptor>
|
||||
{
|
||||
new FilterDescriptor
|
||||
{
|
||||
Property = "Tags",
|
||||
FilterProperty = "Value",
|
||||
FilterValue = 20,
|
||||
FilterOperator = FilterOperator.LessThanOrEquals,
|
||||
CollectionFilterMode = CollectionFilterMode.Any
|
||||
}
|
||||
};
|
||||
|
||||
var result = testData.Where(filters, LogicalFilterOperator.And, FilterCaseSensitivity.Default).ToList();
|
||||
|
||||
// Should return items where at least one tag has Value <= 20
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(result, r => r.Id == 1);
|
||||
Assert.Contains(result, r => r.Id == 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Where_FiltersCollectionItemProperty_WithCollectionFilterMode_All_WithLessThanOrEquals()
|
||||
{
|
||||
var testData = new[]
|
||||
{
|
||||
new { Id = 1, Tags = new[] { new { Name = "tag1", Value = 10 }, new { Name = "tag2", Value = 20 } }.ToList() },
|
||||
new { Id = 2, Tags = new[] { new { Name = "tag3", Value = 18 }, new { Name = "tag4", Value = 70 } }.ToList() },
|
||||
new { Id = 3, Tags = new[] { new { Name = "tag5", Value = 5 }, new { Name = "tag6", Value = 12 } }.ToList() }
|
||||
}.AsQueryable();
|
||||
|
||||
var filters = new List<FilterDescriptor>
|
||||
{
|
||||
new FilterDescriptor
|
||||
{
|
||||
Property = "Tags",
|
||||
FilterProperty = "Value",
|
||||
FilterValue = 20,
|
||||
FilterOperator = FilterOperator.LessThanOrEquals,
|
||||
CollectionFilterMode = CollectionFilterMode.All
|
||||
}
|
||||
};
|
||||
|
||||
var result = testData.Where(filters, LogicalFilterOperator.And, FilterCaseSensitivity.Default).ToList();
|
||||
|
||||
// Should return items where all tags have Value <= 20
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(result, r => r.Id == 1);
|
||||
Assert.Contains(result, r => r.Id == 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Where_FiltersCollectionItemProperty_WithCollectionFilterMode_Any_WithGreaterThanOrEquals()
|
||||
{
|
||||
var testData = new[]
|
||||
{
|
||||
new { Id = 1, Tags = new[] { new { Name = "tag1", Value = 50 }, new { Name = "tag2", Value = 20 } }.ToList() },
|
||||
new { Id = 2, Tags = new[] { new { Name = "tag3", Value = 10 }, new { Name = "tag4", Value = 15 } }.ToList() },
|
||||
new { Id = 3, Tags = new[] { new { Name = "tag5", Value = 60 }, new { Name = "tag6", Value = 30 } }.ToList() }
|
||||
}.AsQueryable();
|
||||
|
||||
var filters = new List<FilterDescriptor>
|
||||
{
|
||||
new FilterDescriptor
|
||||
{
|
||||
Property = "Tags",
|
||||
FilterProperty = "Value",
|
||||
FilterValue = 50,
|
||||
FilterOperator = FilterOperator.GreaterThanOrEquals,
|
||||
CollectionFilterMode = CollectionFilterMode.Any
|
||||
}
|
||||
};
|
||||
|
||||
var result = testData.Where(filters, LogicalFilterOperator.And, FilterCaseSensitivity.Default).ToList();
|
||||
|
||||
// Should return items where at least one tag has Value >= 50
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(result, r => r.Id == 1);
|
||||
Assert.Contains(result, r => r.Id == 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Where_FiltersCollectionItemProperty_WithCollectionFilterMode_All_WithGreaterThanOrEquals()
|
||||
{
|
||||
var testData = new[]
|
||||
{
|
||||
new { Id = 1, Tags = new[] { new { Name = "tag1", Value = 50 }, new { Name = "tag2", Value = 60 } }.ToList() },
|
||||
new { Id = 2, Tags = new[] { new { Name = "tag3", Value = 55 }, new { Name = "tag4", Value = 40 } }.ToList() },
|
||||
new { Id = 3, Tags = new[] { new { Name = "tag5", Value = 70 }, new { Name = "tag6", Value = 80 } }.ToList() }
|
||||
}.AsQueryable();
|
||||
|
||||
var filters = new List<FilterDescriptor>
|
||||
{
|
||||
new FilterDescriptor
|
||||
{
|
||||
Property = "Tags",
|
||||
FilterProperty = "Value",
|
||||
FilterValue = 50,
|
||||
FilterOperator = FilterOperator.GreaterThanOrEquals,
|
||||
CollectionFilterMode = CollectionFilterMode.All
|
||||
}
|
||||
};
|
||||
|
||||
var result = testData.Where(filters, LogicalFilterOperator.And, FilterCaseSensitivity.Default).ToList();
|
||||
|
||||
// Should return items where all tags have Value >= 50
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(result, r => r.Id == 1);
|
||||
Assert.Contains(result, r => r.Id == 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Where_FiltersCollectionItemProperty_WithCollectionFilterMode_Any_WithIsNull()
|
||||
{
|
||||
var testData = new[]
|
||||
{
|
||||
new { Id = 1, Tags = new[] { new { Name = (string)null, Value = 10 }, new { Name = "tag2", Value = 20 } }.ToList() },
|
||||
new { Id = 2, Tags = new[] { new { Name = "tag3", Value = 30 }, new { Name = "tag4", Value = 40 } }.ToList() },
|
||||
new { Id = 3, Tags = new[] { new { Name = (string)null, Value = 50 }, new { Name = "tag6", Value = 60 } }.ToList() }
|
||||
}.AsQueryable();
|
||||
|
||||
var filters = new List<FilterDescriptor>
|
||||
{
|
||||
new FilterDescriptor
|
||||
{
|
||||
Property = "Tags",
|
||||
FilterProperty = "Name",
|
||||
FilterOperator = FilterOperator.IsNull,
|
||||
CollectionFilterMode = CollectionFilterMode.Any
|
||||
}
|
||||
};
|
||||
|
||||
var result = testData.Where(filters, LogicalFilterOperator.And, FilterCaseSensitivity.Default).ToList();
|
||||
|
||||
// Should return items where at least one tag has null Name
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(result, r => r.Id == 1);
|
||||
Assert.Contains(result, r => r.Id == 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Where_FiltersCollectionItemProperty_WithCollectionFilterMode_All_WithIsNull()
|
||||
{
|
||||
var testData = new[]
|
||||
{
|
||||
new { Id = 1, Tags = new[] { new { Name = (string)null, Value = 10 }, new { Name = (string)null, Value = 20 } }.ToList() },
|
||||
new { Id = 2, Tags = new[] { new { Name = (string)null, Value = 30 }, new { Name = "tag4", Value = 40 } }.ToList() },
|
||||
new { Id = 3, Tags = new[] { new { Name = (string)null, Value = 50 } }.ToList() }
|
||||
}.AsQueryable();
|
||||
|
||||
var filters = new List<FilterDescriptor>
|
||||
{
|
||||
new FilterDescriptor
|
||||
{
|
||||
Property = "Tags",
|
||||
FilterProperty = "Name",
|
||||
FilterOperator = FilterOperator.IsNull,
|
||||
CollectionFilterMode = CollectionFilterMode.All
|
||||
}
|
||||
};
|
||||
|
||||
var result = testData.Where(filters, LogicalFilterOperator.And, FilterCaseSensitivity.Default).ToList();
|
||||
|
||||
// Should return items where all tags have null Name
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(result, r => r.Id == 1);
|
||||
Assert.Contains(result, r => r.Id == 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Where_FiltersCollectionItemProperty_WithCollectionFilterMode_Any_WithIsNotNull()
|
||||
{
|
||||
var testData = new[]
|
||||
{
|
||||
new { Id = 1, Tags = new[] { new { Name = (string)null, Value = 10 }, new { Name = "tag2", Value = 20 } }.ToList() },
|
||||
new { Id = 2, Tags = new[] { new { Name = (string)null, Value = 30 }, new { Name = (string)null, Value = 40 } }.ToList() },
|
||||
new { Id = 3, Tags = new[] { new { Name = "tag5", Value = 50 }, new { Name = "tag6", Value = 60 } }.ToList() }
|
||||
}.AsQueryable();
|
||||
|
||||
var filters = new List<FilterDescriptor>
|
||||
{
|
||||
new FilterDescriptor
|
||||
{
|
||||
Property = "Tags",
|
||||
FilterProperty = "Name",
|
||||
FilterOperator = FilterOperator.IsNotNull,
|
||||
CollectionFilterMode = CollectionFilterMode.Any
|
||||
}
|
||||
};
|
||||
|
||||
var result = testData.Where(filters, LogicalFilterOperator.And, FilterCaseSensitivity.Default).ToList();
|
||||
|
||||
// Should return items where at least one tag has non-null Name
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(result, r => r.Id == 1);
|
||||
Assert.Contains(result, r => r.Id == 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Where_FiltersCollectionItemProperty_WithCollectionFilterMode_All_WithIsNotNull()
|
||||
{
|
||||
var testData = new[]
|
||||
{
|
||||
new { Id = 1, Tags = new[] { new { Name = "tag1", Value = 10 }, new { Name = "tag2", Value = 20 } }.ToList() },
|
||||
new { Id = 2, Tags = new[] { new { Name = "tag3", Value = 30 }, new { Name = (string)null, Value = 40 } }.ToList() },
|
||||
new { Id = 3, Tags = new[] { new { Name = "tag5", Value = 50 } }.ToList() }
|
||||
}.AsQueryable();
|
||||
|
||||
var filters = new List<FilterDescriptor>
|
||||
{
|
||||
new FilterDescriptor
|
||||
{
|
||||
Property = "Tags",
|
||||
FilterProperty = "Name",
|
||||
FilterOperator = FilterOperator.IsNotNull,
|
||||
CollectionFilterMode = CollectionFilterMode.All
|
||||
}
|
||||
};
|
||||
|
||||
var result = testData.Where(filters, LogicalFilterOperator.And, FilterCaseSensitivity.Default).ToList();
|
||||
|
||||
// Should return items where all tags have non-null Name
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(result, r => r.Id == 1);
|
||||
Assert.Contains(result, r => r.Id == 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Where_FiltersCollectionItemProperty_WithCollectionFilterMode_Any_WithIsEmpty()
|
||||
{
|
||||
var testData = new[]
|
||||
{
|
||||
new { Id = 1, Tags = new[] { new { Name = "", Value = 10 }, new { Name = "tag2", Value = 20 } }.ToList() },
|
||||
new { Id = 2, Tags = new[] { new { Name = "tag3", Value = 30 }, new { Name = "tag4", Value = 40 } }.ToList() },
|
||||
new { Id = 3, Tags = new[] { new { Name = "", Value = 50 }, new { Name = "tag6", Value = 60 } }.ToList() }
|
||||
}.AsQueryable();
|
||||
|
||||
var filters = new List<FilterDescriptor>
|
||||
{
|
||||
new FilterDescriptor
|
||||
{
|
||||
Property = "Tags",
|
||||
FilterProperty = "Name",
|
||||
FilterOperator = FilterOperator.IsEmpty,
|
||||
CollectionFilterMode = CollectionFilterMode.Any
|
||||
}
|
||||
};
|
||||
|
||||
var result = testData.Where(filters, LogicalFilterOperator.And, FilterCaseSensitivity.Default).ToList();
|
||||
|
||||
// Should return items where at least one tag has empty Name
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(result, r => r.Id == 1);
|
||||
Assert.Contains(result, r => r.Id == 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Where_FiltersCollectionItemProperty_WithCollectionFilterMode_All_WithIsEmpty()
|
||||
{
|
||||
var testData = new[]
|
||||
{
|
||||
new { Id = 1, Tags = new[] { new { Name = "", Value = 10 }, new { Name = "", Value = 20 } }.ToList() },
|
||||
new { Id = 2, Tags = new[] { new { Name = "", Value = 30 }, new { Name = "tag4", Value = 40 } }.ToList() },
|
||||
new { Id = 3, Tags = new[] { new { Name = "", Value = 50 } }.ToList() }
|
||||
}.AsQueryable();
|
||||
|
||||
var filters = new List<FilterDescriptor>
|
||||
{
|
||||
new FilterDescriptor
|
||||
{
|
||||
Property = "Tags",
|
||||
FilterProperty = "Name",
|
||||
FilterOperator = FilterOperator.IsEmpty,
|
||||
CollectionFilterMode = CollectionFilterMode.All
|
||||
}
|
||||
};
|
||||
|
||||
var result = testData.Where(filters, LogicalFilterOperator.And, FilterCaseSensitivity.Default).ToList();
|
||||
|
||||
// Should return items where all tags have empty Name
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(result, r => r.Id == 1);
|
||||
Assert.Contains(result, r => r.Id == 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Where_FiltersCollectionItemProperty_WithCollectionFilterMode_Any_WithIsNotEmpty()
|
||||
{
|
||||
var testData = new[]
|
||||
{
|
||||
new { Id = 1, Tags = new[] { new { Name = "", Value = 10 }, new { Name = "tag2", Value = 20 } }.ToList() },
|
||||
new { Id = 2, Tags = new[] { new { Name = "", Value = 30 }, new { Name = "", Value = 40 } }.ToList() },
|
||||
new { Id = 3, Tags = new[] { new { Name = "tag5", Value = 50 }, new { Name = "tag6", Value = 60 } }.ToList() }
|
||||
}.AsQueryable();
|
||||
|
||||
var filters = new List<FilterDescriptor>
|
||||
{
|
||||
new FilterDescriptor
|
||||
{
|
||||
Property = "Tags",
|
||||
FilterProperty = "Name",
|
||||
FilterOperator = FilterOperator.IsNotEmpty,
|
||||
CollectionFilterMode = CollectionFilterMode.Any
|
||||
}
|
||||
};
|
||||
|
||||
var result = testData.Where(filters, LogicalFilterOperator.And, FilterCaseSensitivity.Default).ToList();
|
||||
|
||||
// Should return items where at least one tag has non-empty Name
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(result, r => r.Id == 1);
|
||||
Assert.Contains(result, r => r.Id == 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Where_FiltersCollectionItemProperty_WithCollectionFilterMode_All_WithIsNotEmpty()
|
||||
{
|
||||
var testData = new[]
|
||||
{
|
||||
new { Id = 1, Tags = new[] { new { Name = "tag1", Value = 10 }, new { Name = "tag2", Value = 20 } }.ToList() },
|
||||
new { Id = 2, Tags = new[] { new { Name = "tag3", Value = 30 }, new { Name = "", Value = 40 } }.ToList() },
|
||||
new { Id = 3, Tags = new[] { new { Name = "tag5", Value = 50 } }.ToList() }
|
||||
}.AsQueryable();
|
||||
|
||||
var filters = new List<FilterDescriptor>
|
||||
{
|
||||
new FilterDescriptor
|
||||
{
|
||||
Property = "Tags",
|
||||
FilterProperty = "Name",
|
||||
FilterOperator = FilterOperator.IsNotEmpty,
|
||||
CollectionFilterMode = CollectionFilterMode.All
|
||||
}
|
||||
};
|
||||
|
||||
var result = testData.Where(filters, LogicalFilterOperator.And, FilterCaseSensitivity.Default).ToList();
|
||||
|
||||
// Should return items where all tags have non-empty Name
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(result, r => r.Id == 1);
|
||||
Assert.Contains(result, r => r.Id == 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
62
Radzen.Blazor.Tests/RadioButtonListTests.cs
Normal file
62
Radzen.Blazor.Tests/RadioButtonListTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
82
Radzen.Blazor.Tests/RatingTests.cs
Normal file
82
Radzen.Blazor.Tests/RatingTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
63
Radzen.Blazor.Tests/RowTests.cs
Normal file
63
Radzen.Blazor.Tests/RowTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
63
Radzen.Blazor.Tests/SchedulerYearRangeTests.cs
Normal file
63
Radzen.Blazor.Tests/SchedulerYearRangeTests.cs
Normal 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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
45
Radzen.Blazor.Tests/SecurityCodeTests.cs
Normal file
45
Radzen.Blazor.Tests/SecurityCodeTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
83
Radzen.Blazor.Tests/SelectBarTests.cs
Normal file
83
Radzen.Blazor.Tests/SelectBarTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
65
Radzen.Blazor.Tests/SidebarToggleTests.cs
Normal file
65
Radzen.Blazor.Tests/SidebarToggleTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
42
Radzen.Blazor.Tests/SplitterTests.cs
Normal file
42
Radzen.Blazor.Tests/SplitterTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
49
Radzen.Blazor.Tests/Spreadsheet/AggregateFunctionTests.cs
Normal file
49
Radzen.Blazor.Tests/Spreadsheet/AggregateFunctionTests.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class AggregateFunctionTests
|
||||
{
|
||||
readonly Sheet sheet = new(15, 5);
|
||||
|
||||
void SeedWithErrors()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=A2/0"; // #DIV/0!
|
||||
sheet.Cells["A2"].Value = 82;
|
||||
sheet.Cells["A3"].Value = 72;
|
||||
sheet.Cells["A4"].Value = 65;
|
||||
sheet.Cells["A5"].Value = 30;
|
||||
sheet.Cells["A6"].Value = 95;
|
||||
sheet.Cells["A7"].Formula = "=0/0"; // #DIV/0!
|
||||
sheet.Cells["A8"].Value = 63;
|
||||
sheet.Cells["A9"].Value = 31;
|
||||
sheet.Cells["A10"].Value = 53;
|
||||
sheet.Cells["A11"].Value = 96;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldComputeMaxIgnoringErrors()
|
||||
{
|
||||
SeedWithErrors();
|
||||
sheet.Cells["B1"].Formula = "=AGGREGATE(4,6,A1:A11)"; // MAX ignoring errors
|
||||
Assert.Equal(96d, sheet.Cells["B1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldComputeLargeIgnoringErrors()
|
||||
{
|
||||
SeedWithErrors();
|
||||
sheet.Cells["B1"].Formula = "=AGGREGATE(14,6,A1:A11,3)"; // LARGE k=3 ignoring errors
|
||||
Assert.Equal(82d, sheet.Cells["B1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnValueErrorWhenKMissingForSmall()
|
||||
{
|
||||
SeedWithErrors();
|
||||
sheet.Cells["B1"].Formula = "=AGGREGATE(15,6,A1:A11)"; // SMALL requires k
|
||||
Assert.Equal(CellError.Value, sheet.Cells["B1"].Value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
160
Radzen.Blazor.Tests/Spreadsheet/AndFunctionTests.cs
Normal file
160
Radzen.Blazor.Tests/Spreadsheet/AndFunctionTests.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class AndFunctionTests
|
||||
{
|
||||
readonly Sheet sheet = new(5, 5);
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateAndFunctionWithAllTrueValues()
|
||||
{
|
||||
sheet.Cells["A1"].Value = true;
|
||||
sheet.Cells["A2"].Value = true;
|
||||
sheet.Cells["A3"].Formula = "=AND(A1,A2)";
|
||||
|
||||
Assert.Equal(true, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateAndFunctionWithOneFalseValue()
|
||||
{
|
||||
sheet.Cells["A1"].Value = true;
|
||||
sheet.Cells["A2"].Value = false;
|
||||
sheet.Cells["A3"].Formula = "=AND(A1,A2)";
|
||||
|
||||
Assert.Equal(false, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateAndFunctionWithAllFalseValues()
|
||||
{
|
||||
sheet.Cells["A1"].Value = false;
|
||||
sheet.Cells["A2"].Value = false;
|
||||
sheet.Cells["A3"].Formula = "=AND(A1,A2)";
|
||||
|
||||
Assert.Equal(false, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateAndFunctionWithNumericValues()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 5;
|
||||
sheet.Cells["A2"].Value = 10;
|
||||
sheet.Cells["A3"].Formula = "=AND(A1>1,A2<100)";
|
||||
|
||||
Assert.Equal(true, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateAndFunctionWithZeroAsFalse()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 0;
|
||||
sheet.Cells["A2"].Value = 1;
|
||||
sheet.Cells["A3"].Formula = "=AND(A1,A2)";
|
||||
|
||||
Assert.Equal(false, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateAndFunctionWithNonZeroAsTrue()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 5;
|
||||
sheet.Cells["A2"].Value = 10;
|
||||
sheet.Cells["A3"].Formula = "=AND(A1,A2)";
|
||||
|
||||
Assert.Equal(true, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateAndFunctionWithStringValues()
|
||||
{
|
||||
sheet.Cells["A1"].Value = "test";
|
||||
sheet.Cells["A2"].Value = "hello";
|
||||
sheet.Cells["A3"].Formula = "=AND(A1,A2)";
|
||||
|
||||
Assert.Equal(CellError.Value, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateAndFunctionWithEmptyStringAsFalse()
|
||||
{
|
||||
sheet.Cells["A2"].Value = "hello";
|
||||
sheet.Cells["A3"].Formula = "=AND(A1,A2)";
|
||||
|
||||
Assert.Equal(CellError.Value, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateAndFunctionWithMultipleArguments()
|
||||
{
|
||||
sheet.Cells["A1"].Value = true;
|
||||
sheet.Cells["A2"].Value = true;
|
||||
sheet.Cells["A3"].Value = true;
|
||||
sheet.Cells["A4"].Formula = "=AND(A1,A2,A3)";
|
||||
|
||||
Assert.Equal(true, sheet.Cells["A4"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateAndFunctionWithOneFalseInMultipleArguments()
|
||||
{
|
||||
sheet.Cells["A1"].Value = true;
|
||||
sheet.Cells["A2"].Value = false;
|
||||
sheet.Cells["A3"].Value = true;
|
||||
sheet.Cells["A4"].Formula = "=AND(A1,A2,A3)";
|
||||
|
||||
Assert.Equal(false, sheet.Cells["A4"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnValueErrorForEmptyAndFunction()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=AND()";
|
||||
|
||||
Assert.Equal(CellError.Value, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateAndFunctionWithRangeExpression()
|
||||
{
|
||||
sheet.Cells["A1"].Value = true;
|
||||
sheet.Cells["A2"].Value = true;
|
||||
sheet.Cells["A3"].Formula = "=AND(A1:A2)";
|
||||
|
||||
Assert.Equal(true, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateAndFunctionWithMixedTypes()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 5;
|
||||
sheet.Cells["A2"].Value = "3";
|
||||
sheet.Cells["A3"].Value = true;
|
||||
sheet.Cells["A4"].Formula = "=AND(A1,A2,A3)";
|
||||
|
||||
Assert.Equal(true, sheet.Cells["A4"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateAndFunctionInIfStatement()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 5;
|
||||
sheet.Cells["A2"].Value = 10;
|
||||
sheet.Cells["A3"].Formula = "=IF(AND(A1>1,A2<100),A1,\"Out of range\")";
|
||||
|
||||
Assert.Equal(5d, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateAndFunctionInIfStatementWithFalseCondition()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 5;
|
||||
sheet.Cells["A2"].Value = 150;
|
||||
sheet.Cells["A3"].Formula = "=IF(AND(A1>1,A2<100),A1,\"Out of range\")";
|
||||
|
||||
Assert.Equal("Out of range", sheet.Cells["A3"].Value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
105
Radzen.Blazor.Tests/Spreadsheet/AutoFilterTests.cs
Normal file
105
Radzen.Blazor.Tests/Spreadsheet/AutoFilterTests.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class AutoFilterTests
|
||||
{
|
||||
private readonly Sheet sheet = new(10, 10);
|
||||
|
||||
[Fact]
|
||||
public void Should_ToggleSheetAutoFilter()
|
||||
{
|
||||
// Initially no auto filter
|
||||
Assert.Null(sheet.AutoFilter);
|
||||
|
||||
// Apply auto filter to range A1:C5
|
||||
var range = RangeRef.Parse("A1:C5");
|
||||
var command = new SheetAutoFilterCommand(sheet, range);
|
||||
command.Execute();
|
||||
|
||||
// Auto filter should be applied
|
||||
Assert.NotNull(sheet.AutoFilter);
|
||||
Assert.Equal(range, sheet.AutoFilter.Range);
|
||||
|
||||
// Undo the command
|
||||
command.Unexecute();
|
||||
|
||||
// Auto filter should be removed
|
||||
Assert.Null(sheet.AutoFilter);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_ToggleDataTableFilterButton()
|
||||
{
|
||||
// Add a data table
|
||||
var range = RangeRef.Parse("A1:C5");
|
||||
sheet.AddTable(range);
|
||||
|
||||
var table = sheet.Tables[0];
|
||||
|
||||
// Initially ShowFilterButton should be true
|
||||
Assert.True(table.ShowFilterButton);
|
||||
|
||||
// Toggle filter button off
|
||||
var command = new TableFilterCommand(sheet, 0);
|
||||
command.Execute();
|
||||
|
||||
// ShowFilterButton should be false
|
||||
Assert.False(table.ShowFilterButton);
|
||||
|
||||
// Undo the command
|
||||
command.Unexecute();
|
||||
|
||||
// ShowFilterButton should be true again
|
||||
Assert.True(table.ShowFilterButton);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_HandleMultipleDataTables()
|
||||
{
|
||||
// Add two data tables
|
||||
sheet.AddTable(RangeRef.Parse("A1:C5"));
|
||||
sheet.AddTable(RangeRef.Parse("E1:G5"));
|
||||
|
||||
var table1 = sheet.Tables[0];
|
||||
var table2 = sheet.Tables[1];
|
||||
|
||||
// Initially both should have ShowFilterButton = true
|
||||
Assert.True(table1.ShowFilterButton);
|
||||
Assert.True(table2.ShowFilterButton);
|
||||
|
||||
// Toggle filter button for first data table
|
||||
var command1 = new TableFilterCommand(sheet, 0);
|
||||
command1.Execute();
|
||||
|
||||
// Only first data table should be affected
|
||||
Assert.False(table1.ShowFilterButton);
|
||||
Assert.True(table2.ShowFilterButton);
|
||||
|
||||
// Toggle filter button for second data table
|
||||
var command2 = new TableFilterCommand(sheet, 1);
|
||||
command2.Execute();
|
||||
|
||||
// Both should be affected
|
||||
Assert.False(table1.ShowFilterButton);
|
||||
Assert.False(table2.ShowFilterButton);
|
||||
|
||||
// Undo second command
|
||||
command2.Unexecute();
|
||||
|
||||
// Only second data table should be restored
|
||||
Assert.False(table1.ShowFilterButton);
|
||||
Assert.True(table2.ShowFilterButton);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_HandleInvalidDataTableIndex()
|
||||
{
|
||||
// Try to toggle filter button for non-existent data table
|
||||
var command = new TableFilterCommand(sheet, 0);
|
||||
|
||||
// Should not throw exception
|
||||
var result = command.Execute();
|
||||
Assert.True(result);
|
||||
}
|
||||
}
|
||||
114
Radzen.Blazor.Tests/Spreadsheet/AverageFunctionTests.cs
Normal file
114
Radzen.Blazor.Tests/Spreadsheet/AverageFunctionTests.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class AverageFunctionTests
|
||||
{
|
||||
readonly Sheet sheet = new(5, 5);
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateAverageFunctionWithTwoArguments()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = 15;
|
||||
sheet.Cells["A3"].Formula = "=AVERAGE(A1,A2)";
|
||||
|
||||
Assert.Equal(12.5, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateAverageFunctionWithEmptyCells()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A3"].Formula = "=AVERAGE(A1,A2)";
|
||||
|
||||
Assert.Equal(10.0, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateAverageFunctionWithMultipleArguments()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = 15;
|
||||
sheet.Cells["A3"].Value = 20;
|
||||
sheet.Cells["A4"].Formula = "=AVERAGE(A1,A2,A3)";
|
||||
|
||||
Assert.Equal(15.0, sheet.Cells["A4"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnDiv0ErrorForEmptyAverageFunction()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=AVERAGE()";
|
||||
|
||||
Assert.Equal(CellError.Div0, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnDiv0ErrorForAverageFunctionWithNoNumericValues()
|
||||
{
|
||||
sheet.Cells["A1"].Value = "text";
|
||||
sheet.Cells["A2"].Value = "";
|
||||
sheet.Cells["A3"].Formula = "=AVERAGE(A1,A2)";
|
||||
|
||||
Assert.Equal(CellError.Div0, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateAverageFunctionWithRange()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = 15;
|
||||
sheet.Cells["A3"].Formula = "=AVERAGE(A1:A2)";
|
||||
|
||||
Assert.Equal(12.5, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateAverageFunctionWithMixedTypes()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = 15.5;
|
||||
sheet.Cells["A3"].Formula = "=AVERAGE(A1,A2)";
|
||||
|
||||
Assert.Equal(12.75, sheet.Cells["A3"].Value);
|
||||
|
||||
sheet.Cells["A4"].Value = 2.5;
|
||||
sheet.Cells["A5"].Formula = "=AVERAGE(A4,A1)";
|
||||
|
||||
Assert.Equal(6.25, sheet.Cells["A5"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateAverageFunctionIgnoringTextAndLogicalValues()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = "text";
|
||||
sheet.Cells["A3"].Value = true;
|
||||
sheet.Cells["A4"].Value = 20;
|
||||
sheet.Cells["A5"].Formula = "=AVERAGE(A1,A2,A3,A4)";
|
||||
|
||||
Assert.Equal(15.0, sheet.Cells["A5"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateAverageFunctionIncludingZeroValues()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = 0;
|
||||
sheet.Cells["A3"].Value = 20;
|
||||
sheet.Cells["A4"].Formula = "=AVERAGE(A1,A2,A3)";
|
||||
|
||||
Assert.Equal(10.0, sheet.Cells["A4"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldCreateRefErrorWhenAverageRangeOutOfBounds()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=AVERAGE(A2:A6)";
|
||||
|
||||
Assert.Equal(CellError.Ref, sheet.Cells["A1"].Value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
261
Radzen.Blazor.Tests/Spreadsheet/CellSelectionTests.cs
Normal file
261
Radzen.Blazor.Tests/Spreadsheet/CellSelectionTests.cs
Normal file
@@ -0,0 +1,261 @@
|
||||
using Bunit;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class CellSelectionTests : TestContext
|
||||
{
|
||||
private readonly Sheet sheet = new (4,4);
|
||||
|
||||
[Fact]
|
||||
public void CellSelection_RendersWithCorrectClasses()
|
||||
{
|
||||
// Arrange
|
||||
var cell = new CellRef(0, 0);
|
||||
sheet.Selection.Select(new RangeRef(cell, cell));
|
||||
var context = new MockVirtualGridContext();
|
||||
|
||||
// Act
|
||||
var cut = RenderComponent<CellSelection>(parameters => parameters
|
||||
.Add(p => p.Cell, cell)
|
||||
.Add(p => p.Sheet, sheet)
|
||||
.Add(p => p.Context, context));
|
||||
|
||||
// Assert
|
||||
var element = cut.Find(".rz-spreadsheet-selection-cell");
|
||||
Assert.NotNull(element);
|
||||
Assert.Contains("rz-spreadsheet-selection-cell", element.ClassName);
|
||||
Assert.Contains("rz-spreadsheet-selection-cell-top", element.ClassName);
|
||||
Assert.Contains("rz-spreadsheet-selection-cell-left", element.ClassName);
|
||||
Assert.Contains("rz-spreadsheet-selection-cell-bottom", element.ClassName);
|
||||
Assert.Contains("rz-spreadsheet-selection-cell-right", element.ClassName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CellSelection_AppliesFrozenColumnClass()
|
||||
{
|
||||
// Arrange
|
||||
var cell = new CellRef(0, 0);
|
||||
sheet.Columns.Frozen = 1;
|
||||
sheet.Selection.Select(new RangeRef(cell, cell));
|
||||
var context = new MockVirtualGridContext();
|
||||
|
||||
// Act
|
||||
var cut = RenderComponent<CellSelection>(parameters => parameters
|
||||
.Add(p => p.Cell, cell)
|
||||
.Add(p => p.Sheet, sheet)
|
||||
.Add(p => p.Context, context));
|
||||
|
||||
// Assert
|
||||
var element = cut.Find(".rz-spreadsheet-selection-cell");
|
||||
Assert.Contains("rz-spreadsheet-frozen-column", element.ClassName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CellSelection_AppliesFrozenRowClass()
|
||||
{
|
||||
// Arrange
|
||||
var cell = new CellRef(0, 0);
|
||||
var context = new MockVirtualGridContext();
|
||||
sheet.Rows.Frozen = 1;
|
||||
sheet.Selection.Select(new RangeRef(cell, cell));
|
||||
|
||||
// Act
|
||||
var cut = RenderComponent<CellSelection>(parameters => parameters
|
||||
.Add(p => p.Cell, cell)
|
||||
.Add(p => p.Sheet, sheet)
|
||||
.Add(p => p.Context, context));
|
||||
|
||||
// Assert
|
||||
var element = cut.Find(".rz-spreadsheet-selection-cell");
|
||||
Assert.Contains("rz-spreadsheet-frozen-row", element.ClassName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CellSelection_CalculatesStyle()
|
||||
{
|
||||
// Arrange
|
||||
var cell = new CellRef(0, 0);
|
||||
sheet.Selection.Select(new RangeRef(cell, cell));
|
||||
var context = new MockVirtualGridContext();
|
||||
|
||||
// Act
|
||||
var cut = RenderComponent<CellSelection>(parameters => parameters
|
||||
.Add(p => p.Cell, cell)
|
||||
.Add(p => p.Sheet, sheet)
|
||||
.Add(p => p.Context, context));
|
||||
|
||||
// Assert
|
||||
var element = cut.Find(".rz-spreadsheet-selection-cell");
|
||||
Assert.Equal("transform: translate(0px, 0px); width: 100px; height: 24px", element.GetAttribute("style"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CellSelection_SplitsMergedCell_WhenIntersectingFrozenRow()
|
||||
{
|
||||
// Arrange
|
||||
sheet.Rows.Frozen = 1;
|
||||
var range = new RangeRef(new CellRef(0, 0), new CellRef(2, 0));
|
||||
sheet.MergedCells.Add(range);
|
||||
sheet.Selection.Select(range);
|
||||
var context = new MockVirtualGridContext();
|
||||
|
||||
// Act
|
||||
var cut = RenderComponent<CellSelection>(parameters => parameters
|
||||
.Add(p => p.Cell, new CellRef(0, 0))
|
||||
.Add(p => p.Sheet, sheet)
|
||||
.Add(p => p.Context, context));
|
||||
|
||||
// Assert
|
||||
var elements = cut.FindAll(".rz-spreadsheet-selection-cell");
|
||||
Assert.Equal(2, elements.Count);
|
||||
|
||||
var frozen = cut.Find(".rz-spreadsheet-frozen-row");
|
||||
|
||||
Assert.NotNull(frozen);
|
||||
|
||||
// First element (frozen)
|
||||
Assert.Equal("transform: translate(0px, 0px); width: 100px; height: 24px", frozen.GetAttribute("style"));
|
||||
Assert.Contains("rz-spreadsheet-selection-cell-top", frozen.ClassName);
|
||||
Assert.Contains("rz-spreadsheet-selection-cell-left", frozen.ClassName);
|
||||
Assert.Contains("rz-spreadsheet-selection-cell-right", frozen.ClassName);
|
||||
Assert.DoesNotContain("rz-spreadsheet-selection-cell-bottom", frozen.ClassName);
|
||||
|
||||
var unfrozen = elements.Where(e => e != frozen).FirstOrDefault();
|
||||
|
||||
Assert.NotNull(unfrozen);
|
||||
|
||||
// Second element (non-frozen)
|
||||
Assert.Equal("transform: translate(0px, 24px); width: 100px; height: 48px", unfrozen.GetAttribute("style"));
|
||||
Assert.DoesNotContain("rz-spreadsheet-selection-cell-top", unfrozen.ClassName);
|
||||
Assert.Contains("rz-spreadsheet-selection-cell-left", unfrozen.ClassName);
|
||||
Assert.Contains("rz-spreadsheet-selection-cell-right", unfrozen.ClassName);
|
||||
Assert.Contains("rz-spreadsheet-selection-cell-bottom", unfrozen.ClassName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CellSelection_SplitsMergedCell_WhenIntersectingFrozenColumn()
|
||||
{
|
||||
// Arrange
|
||||
sheet.Columns.Frozen = 1;
|
||||
var range = new RangeRef(new CellRef(0, 0), new CellRef(0, 2));
|
||||
sheet.MergedCells.Add(range);
|
||||
sheet.Selection.Select(range);
|
||||
var context = new MockVirtualGridContext();
|
||||
|
||||
// Act
|
||||
var cut = RenderComponent<CellSelection>(parameters => parameters
|
||||
.Add(p => p.Cell, new CellRef(0, 0))
|
||||
.Add(p => p.Sheet, sheet)
|
||||
.Add(p => p.Context, context));
|
||||
|
||||
// Assert
|
||||
var elements = cut.FindAll(".rz-spreadsheet-selection-cell");
|
||||
Assert.Equal(2, elements.Count);
|
||||
|
||||
var frozen = cut.Find(".rz-spreadsheet-frozen-column");
|
||||
|
||||
Assert.NotNull(frozen);
|
||||
|
||||
// First element (frozen)
|
||||
Assert.Contains("rz-spreadsheet-frozen-column", frozen.ClassName);
|
||||
Assert.Equal("transform: translate(0px, 0px); width: 100px; height: 24px", frozen.GetAttribute("style"));
|
||||
Assert.Contains("rz-spreadsheet-selection-cell-top", frozen.ClassName);
|
||||
Assert.Contains("rz-spreadsheet-selection-cell-left", frozen.ClassName);
|
||||
Assert.Contains("rz-spreadsheet-selection-cell-bottom", frozen.ClassName);
|
||||
Assert.DoesNotContain("rz-spreadsheet-selection-cell-right", frozen.ClassName);
|
||||
|
||||
var unfrozen = elements.Where(e => e != frozen).FirstOrDefault();
|
||||
|
||||
Assert.NotNull(unfrozen);
|
||||
// Second element (non-frozen)
|
||||
Assert.Equal("transform: translate(100px, 0px); width: 200px; height: 24px", unfrozen.GetAttribute("style"));
|
||||
Assert.Contains("rz-spreadsheet-selection-cell-top", unfrozen.ClassName);
|
||||
Assert.DoesNotContain("rz-spreadsheet-selection-cell-left", unfrozen.ClassName);
|
||||
Assert.Contains("rz-spreadsheet-selection-cell-bottom", unfrozen.ClassName);
|
||||
Assert.Contains("rz-spreadsheet-selection-cell-right", unfrozen.ClassName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CellSelection_SplitsMergedCell_WhenIntersectingBothFrozen()
|
||||
{
|
||||
// Arrange
|
||||
sheet.Rows.Frozen = 1;
|
||||
sheet.Columns.Frozen = 1;
|
||||
var range = new RangeRef(new CellRef(0, 0), new CellRef(2, 2));
|
||||
sheet.MergedCells.Add(range);
|
||||
sheet.Selection.Select(range);
|
||||
var context = new MockVirtualGridContext();
|
||||
|
||||
// Act
|
||||
var cut = RenderComponent<CellSelection>(parameters => parameters
|
||||
.Add(p => p.Cell, new CellRef(0, 0))
|
||||
.Add(p => p.Sheet, sheet)
|
||||
.Add(p => p.Context, context));
|
||||
|
||||
// Assert
|
||||
var elements = cut.FindAll(".rz-spreadsheet-selection-cell");
|
||||
Assert.Equal(4, elements.Count);
|
||||
|
||||
// Top-left element (both frozen)
|
||||
var both = cut.Find(".rz-spreadsheet-frozen-row.rz-spreadsheet-frozen-column");
|
||||
Assert.NotNull(both);
|
||||
Assert.Contains("rz-spreadsheet-frozen-row", both.ClassName);
|
||||
Assert.Contains("rz-spreadsheet-frozen-column", both.ClassName);
|
||||
Assert.Equal("transform: translate(0px, 0px); width: 100px; height: 24px", both.GetAttribute("style"));
|
||||
Assert.Contains("rz-spreadsheet-selection-cell-top", both.ClassName);
|
||||
Assert.Contains("rz-spreadsheet-selection-cell-left", both.ClassName);
|
||||
Assert.DoesNotContain("rz-spreadsheet-selection-cell-bottom", both.ClassName);
|
||||
Assert.DoesNotContain("rz-spreadsheet-selection-cell-right", both.ClassName);
|
||||
|
||||
// Bottom-left element (column frozen)
|
||||
var frozenColumn = cut.Find(".rz-spreadsheet-frozen-column:not(.rz-spreadsheet-frozen-row)");
|
||||
Assert.NotNull(frozenColumn);
|
||||
Assert.DoesNotContain("rz-spreadsheet-frozen-row", frozenColumn.ClassName);
|
||||
Assert.Contains("rz-spreadsheet-frozen-column", frozenColumn.ClassName);
|
||||
Assert.Equal("transform: translate(0px, 24px); width: 100px; height: 48px", frozenColumn.GetAttribute("style"));
|
||||
Assert.DoesNotContain("rz-spreadsheet-selection-cell-top", frozenColumn.ClassName);
|
||||
Assert.Contains("rz-spreadsheet-selection-cell-left", frozenColumn.ClassName);
|
||||
Assert.Contains("rz-spreadsheet-selection-cell-bottom", frozenColumn.ClassName);
|
||||
Assert.DoesNotContain("rz-spreadsheet-selection-cell-right", frozenColumn.ClassName);
|
||||
|
||||
// Top-right element (row frozen)
|
||||
var frozenRow = cut.Find(".rz-spreadsheet-frozen-row:not(.rz-spreadsheet-frozen-column)");
|
||||
Assert.NotNull(frozenRow);
|
||||
Assert.Contains("rz-spreadsheet-frozen-row", frozenRow.ClassName);
|
||||
Assert.DoesNotContain("rz-spreadsheet-frozen-column", frozenRow.ClassName);
|
||||
Assert.Equal("transform: translate(100px, 0px); width: 200px; height: 24px", frozenRow.GetAttribute("style"));
|
||||
Assert.Contains("rz-spreadsheet-selection-cell-top", frozenRow.ClassName);
|
||||
Assert.DoesNotContain("rz-spreadsheet-selection-cell-left", frozenRow.ClassName);
|
||||
Assert.DoesNotContain("rz-spreadsheet-selection-cell-bottom", frozenRow.ClassName);
|
||||
Assert.Contains("rz-spreadsheet-selection-cell-right", frozenRow.ClassName);
|
||||
|
||||
// Bottom-right element (neither frozen)
|
||||
var neither = elements.FirstOrDefault(e => e != both && e != frozenColumn && e != frozenRow);
|
||||
Assert.NotNull(neither);
|
||||
Assert.DoesNotContain("rz-spreadsheet-frozen-row", neither.ClassName);
|
||||
Assert.DoesNotContain("rz-spreadsheet-frozen-column", neither.ClassName);
|
||||
Assert.Equal("transform: translate(100px, 24px); width: 200px; height: 48px", neither.GetAttribute("style"));
|
||||
Assert.DoesNotContain("rz-spreadsheet-selection-cell-top", neither.ClassName);
|
||||
Assert.DoesNotContain("rz-spreadsheet-selection-cell-left", neither.ClassName);
|
||||
Assert.Contains("rz-spreadsheet-selection-cell-bottom", neither.ClassName);
|
||||
Assert.Contains("rz-spreadsheet-selection-cell-right", neither.ClassName);
|
||||
}
|
||||
}
|
||||
|
||||
public class MockVirtualGridContext : IVirtualGridContext
|
||||
{
|
||||
private readonly Dictionary<(int Row, int Column), PixelRectangle> rectangle = [];
|
||||
|
||||
public void SetupRectangle(int row, int column, PixelRectangle rectangle)
|
||||
{
|
||||
this.rectangle[(row, column)] = rectangle;
|
||||
}
|
||||
|
||||
public PixelRectangle GetRectangle(int row, int column) => throw new NotImplementedException();
|
||||
|
||||
public PixelRectangle GetRectangle(int top, int left, int bottom, int right) => new(new (left * 100, (right + 1) * 100), new (top*24, (bottom + 1)*24));
|
||||
}
|
||||
69
Radzen.Blazor.Tests/Spreadsheet/CellStoreTests.cs
Normal file
69
Radzen.Blazor.Tests/Spreadsheet/CellStoreTests.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class CellStoreTests
|
||||
{
|
||||
readonly CellStore cellStore = new(new Sheet(5, 5));
|
||||
|
||||
[Fact]
|
||||
public void CellStore_ShouldReturnNewCell_WhenCellDoesNotExist()
|
||||
{
|
||||
var cell = cellStore[0, 0];
|
||||
|
||||
Assert.NotNull(cell);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CellStore_ShouldThrowArgumentOutOfRangeException_WhenRowExceedsMax()
|
||||
{
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => cellStore[5, 0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CellStore_ShouldThrowArgumentOutOfRangeException_WhenColumnExceedsMax()
|
||||
{
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => cellStore[0, 5]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CellStore_ShouldReturnExistingCell_WhenCellExists()
|
||||
{
|
||||
var expectedCell = new Cell(cellStore.Sheet, new CellRef(0, 0));
|
||||
cellStore[0, 0] = expectedCell;
|
||||
var cell = cellStore[0, 0];
|
||||
Assert.Same(expectedCell, cell);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CellStore_ShouldReturnExistingCell_ViaA1Notation()
|
||||
{
|
||||
var expectedCell = new Cell(cellStore.Sheet, new CellRef(0, 0));
|
||||
|
||||
cellStore[0, 0] = expectedCell;
|
||||
|
||||
var cell = cellStore["A1"];
|
||||
|
||||
Assert.Same(expectedCell, cell);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CellStore_ShouldThrowException_WhenInvalidA1Notation()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => cellStore["Invalid"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CellStore_ShouldSupport_MultipleLettersInA1Notation()
|
||||
{
|
||||
var cellStore = new CellStore(new Sheet(5, 30));
|
||||
var expectedCell = new Cell(cellStore.Sheet, new CellRef(0, 26));
|
||||
|
||||
cellStore[0, 26] = expectedCell;
|
||||
|
||||
var cell = cellStore["AA1"];
|
||||
|
||||
Assert.Same(expectedCell, cell);
|
||||
}
|
||||
}
|
||||
34
Radzen.Blazor.Tests/Spreadsheet/ChooseFunctionTests.cs
Normal file
34
Radzen.Blazor.Tests/Spreadsheet/ChooseFunctionTests.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class ChooseFunctionTests
|
||||
{
|
||||
readonly Sheet sheet = new(10, 10);
|
||||
|
||||
[Fact]
|
||||
public void ShouldPickScalarByIndex()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=CHOOSE(3,\"Wide\",115,\"world\",8)";
|
||||
Assert.Equal("world", sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldPickCellReferenceByIndex()
|
||||
{
|
||||
sheet.Cells["A2"].Value = "1st";
|
||||
sheet.Cells["A3"].Value = "2nd";
|
||||
sheet.Cells["A4"].Value = "3rd";
|
||||
sheet.Cells["A5"].Value = "Finished";
|
||||
|
||||
sheet.Cells["B1"].Formula = "=CHOOSE(2,A2,A3,A4,A5)";
|
||||
Assert.Equal("2nd", sheet.Cells["B1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnValueErrorWhenIndexOutOfRange()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=CHOOSE(5,1,2,3)";
|
||||
Assert.Equal(CellError.Value, sheet.Cells["A1"].Value);
|
||||
}
|
||||
}
|
||||
40
Radzen.Blazor.Tests/Spreadsheet/ColumnFunctionTests.cs
Normal file
40
Radzen.Blazor.Tests/Spreadsheet/ColumnFunctionTests.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class ColumnFunctionTests
|
||||
{
|
||||
[Fact]
|
||||
public void Column_OmittedReference_ReturnsCurrentColumn()
|
||||
{
|
||||
var sheet = new Sheet(20, 10);
|
||||
sheet.Cells["C10"].Formula = "=COLUMN()";
|
||||
Assert.Equal(3d, sheet.Cells["C10"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Column_SingleCellReference_ReturnsThatColumn()
|
||||
{
|
||||
var sheet = new Sheet(20, 10);
|
||||
sheet.Cells["A1"].Formula = "=COLUMN(C10)";
|
||||
Assert.Equal(3d, sheet.Cells["A1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Column_RangeReference_SingleRow_ReturnsLeftmostColumn()
|
||||
{
|
||||
var sheet = new Sheet(20, 10);
|
||||
sheet.Cells["B2"].Formula = "=COLUMN(C10:E10)";
|
||||
Assert.Equal(3d, sheet.Cells["B2"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Column_RangeReference_MultiRowAndColumn_IsError()
|
||||
{
|
||||
var sheet = new Sheet(20, 10);
|
||||
sheet.Cells["B2"].Formula = "=COLUMN(C10:D20)";
|
||||
Assert.Equal(CellError.Value, sheet.Cells["B2"].Data.Value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
30
Radzen.Blazor.Tests/Spreadsheet/ColumnsFunctionTests.cs
Normal file
30
Radzen.Blazor.Tests/Spreadsheet/ColumnsFunctionTests.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class ColumnsFunctionTests
|
||||
{
|
||||
[Fact]
|
||||
public void Columns_Range_ReturnsColumnCount()
|
||||
{
|
||||
var sheet = new Sheet(50, 20);
|
||||
sheet.Cells["A1"].Formula = "=COLUMNS(C1:E4)";
|
||||
Assert.Equal(3d, sheet.Cells["A1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Columns_SingleCell_ReturnsOne()
|
||||
{
|
||||
var sheet = new Sheet(50, 20);
|
||||
sheet.Cells["A1"].Formula = "=COLUMNS(C10)";
|
||||
Assert.Equal(1d, sheet.Cells["A1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Columns_SingleColumnRange_ReturnsOne()
|
||||
{
|
||||
var sheet = new Sheet(50, 20);
|
||||
sheet.Cells["A1"].Formula = "=COLUMNS(C10:C20)";
|
||||
Assert.Equal(1d, sheet.Cells["A1"].Data.Value);
|
||||
}
|
||||
}
|
||||
44
Radzen.Blazor.Tests/Spreadsheet/ConcatFunctionTests.cs
Normal file
44
Radzen.Blazor.Tests/Spreadsheet/ConcatFunctionTests.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class ConcatFunctionTests
|
||||
{
|
||||
[Fact]
|
||||
public void Concat_Literals_Works()
|
||||
{
|
||||
var sheet = new Sheet(20, 10);
|
||||
sheet.Cells["A1"].Formula = "=CONCAT(\"The\",\" \",\"sun\",\" \",\"will\",\" \",\"come\",\" \",\"up\",\" \",\"tomorrow.\")";
|
||||
Assert.Equal("The sun will come up tomorrow.", sheet.Cells["A1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Concat_SingleRange_LinearizesRowMajor()
|
||||
{
|
||||
var sheet = new Sheet(20, 10);
|
||||
sheet.Cells["B2"].Value = "a1";
|
||||
sheet.Cells["C2"].Value = "b1";
|
||||
sheet.Cells["B3"].Value = "a2";
|
||||
sheet.Cells["C3"].Value = "b2";
|
||||
sheet.Cells["B4"].Value = "a4";
|
||||
sheet.Cells["C4"].Value = "b4";
|
||||
sheet.Cells["B5"].Value = "a5";
|
||||
sheet.Cells["C5"].Value = "b5";
|
||||
sheet.Cells["B6"].Value = "a6";
|
||||
sheet.Cells["C6"].Value = "b6";
|
||||
sheet.Cells["B7"].Value = "a7";
|
||||
sheet.Cells["C7"].Value = "b7";
|
||||
sheet.Cells["A1"].Formula = "=CONCAT(B2:C7)";
|
||||
Assert.Equal("a1b1a2b2a4b4a5b5a6b6a7b7", sheet.Cells["A1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Concat_MixedArgs_RangeAndLiterals()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["B2"].Value = "Andreas";
|
||||
sheet.Cells["C2"].Value = "Hauser";
|
||||
sheet.Cells["A1"].Formula = "=CONCAT(B2,\" \",C2)";
|
||||
Assert.Equal("Andreas Hauser", sheet.Cells["A1"].Data.Value);
|
||||
}
|
||||
}
|
||||
150
Radzen.Blazor.Tests/Spreadsheet/CountAllFunctionTests.cs
Normal file
150
Radzen.Blazor.Tests/Spreadsheet/CountAllFunctionTests.cs
Normal file
@@ -0,0 +1,150 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class CountAllFunctionTests
|
||||
{
|
||||
readonly Sheet sheet = new(5, 5);
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateCountaFunctionWithTwoArguments()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = 15;
|
||||
sheet.Cells["A3"].Formula = "=COUNTA(A1,A2)";
|
||||
|
||||
Assert.Equal(2.0, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateCountaFunctionWithEmptyCells()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A3"].Formula = "=COUNTA(A1,A2)";
|
||||
|
||||
Assert.Equal(1.0, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateCountaFunctionWithMultipleArguments()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = 15;
|
||||
sheet.Cells["A3"].Value = 20;
|
||||
sheet.Cells["A4"].Formula = "=COUNTA(A1,A2,A3)";
|
||||
|
||||
Assert.Equal(3.0, sheet.Cells["A4"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnZeroForEmptyCountaFunction()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=COUNTA()";
|
||||
|
||||
Assert.Equal(0.0, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateCountaFunctionWithRange()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = 15;
|
||||
sheet.Cells["A3"].Formula = "=COUNTA(A1:A2)";
|
||||
|
||||
Assert.Equal(2.0, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateCountaFunctionIncludingTextValues()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = "text";
|
||||
sheet.Cells["A3"].Value = 20;
|
||||
sheet.Cells["A4"].Formula = "=COUNTA(A1,A2,A3)";
|
||||
|
||||
Assert.Equal(3.0, sheet.Cells["A4"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateCountaFunctionIncludingLogicalValues()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = true;
|
||||
sheet.Cells["A3"].Value = false;
|
||||
sheet.Cells["A4"].Value = 20;
|
||||
sheet.Cells["A5"].Formula = "=COUNTA(A1,A2,A3,A4)";
|
||||
|
||||
Assert.Equal(4.0, sheet.Cells["A5"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateCountaFunctionIncludingEmptyStrings()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = "";
|
||||
sheet.Cells["A3"].Value = 20;
|
||||
sheet.Cells["A4"].Formula = "=COUNTA(A1,A2,A3)";
|
||||
|
||||
Assert.Equal(3.0, sheet.Cells["A4"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateCountaFunctionIgnoringTrulyEmptyCells()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = "text";
|
||||
sheet.Cells["A4"].Value = 20;
|
||||
sheet.Cells["A5"].Formula = "=COUNTA(A1,A2,A3,A4)";
|
||||
|
||||
Assert.Equal(3.0, sheet.Cells["A5"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateCountaFunctionWithAllNumericValues()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = 20;
|
||||
sheet.Cells["A3"].Value = 30;
|
||||
sheet.Cells["A4"].Formula = "=COUNTA(A1,A2,A3)";
|
||||
|
||||
Assert.Equal(3.0, sheet.Cells["A4"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldShowDifferenceBetweenCountAndCounta()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = "text";
|
||||
sheet.Cells["A3"].Value = true;
|
||||
sheet.Cells["A4"].Value = "";
|
||||
sheet.Cells["A5"].Value = 20;
|
||||
sheet.Cells["B1"].Formula = "=COUNT(A1,A2,A3,A4,A5)";
|
||||
sheet.Cells["B2"].Formula = "=COUNTA(A1,A2,A3,A4,A5)";
|
||||
|
||||
Assert.Equal(3.0, sheet.Cells["B1"].Value);
|
||||
Assert.Equal(5.0, sheet.Cells["B2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateCountaFunctionWithMixedTypes()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = "text";
|
||||
sheet.Cells["A3"].Value = true;
|
||||
sheet.Cells["A4"].Value = "";
|
||||
sheet.Cells["A5"].Value = 3.14;
|
||||
sheet.Cells["B1"].Formula = "=COUNTA(A1,A2,A3,A4,A5)";
|
||||
|
||||
Assert.Equal(5.0, sheet.Cells["B1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldCreateRefErrorWhenCountaRangeOutOfBounds()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=COUNTA(A2:A6)";
|
||||
|
||||
Assert.Equal(CellError.Ref, sheet.Cells["A1"].Value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
139
Radzen.Blazor.Tests/Spreadsheet/CountFunctionTests.cs
Normal file
139
Radzen.Blazor.Tests/Spreadsheet/CountFunctionTests.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class CountFunctionTests
|
||||
{
|
||||
readonly Sheet sheet = new(5, 5);
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateCountFunctionWithTwoArguments()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = 15;
|
||||
sheet.Cells["A3"].Formula = "=COUNT(A1,A2)";
|
||||
|
||||
Assert.Equal(2.0, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateCountFunctionWithEmptyCells()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A3"].Formula = "=COUNT(A1,A2)";
|
||||
|
||||
Assert.Equal(1.0, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateCountFunctionWithMultipleArguments()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = 15;
|
||||
sheet.Cells["A3"].Value = 20;
|
||||
sheet.Cells["A4"].Formula = "=COUNT(A1,A2,A3)";
|
||||
|
||||
Assert.Equal(3.0, sheet.Cells["A4"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnZeroForEmptyCountFunction()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=COUNT()";
|
||||
|
||||
Assert.Equal(0.0, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateCountFunctionWithRange()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = 15;
|
||||
sheet.Cells["A3"].Formula = "=COUNT(A1:A2)";
|
||||
|
||||
Assert.Equal(2.0, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateCountFunctionWithMixedTypes()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = 15.5;
|
||||
sheet.Cells["A3"].Formula = "=COUNT(A1,A2)";
|
||||
|
||||
Assert.Equal(2.0, sheet.Cells["A3"].Value);
|
||||
|
||||
sheet.Cells["A4"].Value = 2.5;
|
||||
sheet.Cells["A5"].Formula = "=COUNT(A4,A1)";
|
||||
|
||||
Assert.Equal(2.0, sheet.Cells["A5"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateCountFunctionIncludingLogicalValues()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = true;
|
||||
sheet.Cells["A3"].Value = false;
|
||||
sheet.Cells["A4"].Value = 20;
|
||||
sheet.Cells["A5"].Formula = "=COUNT(A1,A2,A3,A4)";
|
||||
|
||||
Assert.Equal(4.0, sheet.Cells["A5"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateCountFunctionIncludingTextRepresentationsOfNumbers()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = "15";
|
||||
sheet.Cells["A3"].Value = "text";
|
||||
sheet.Cells["A4"].Value = "3.14";
|
||||
sheet.Cells["A5"].Formula = "=COUNT(A1,A2,A3,A4)";
|
||||
|
||||
Assert.Equal(3.0, sheet.Cells["A5"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateCountFunctionIgnoringTextAndEmptyCells()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = "text";
|
||||
sheet.Cells["A3"].Value = "";
|
||||
sheet.Cells["A4"].Value = 20;
|
||||
sheet.Cells["A5"].Formula = "=COUNT(A1,A2,A3,A4)";
|
||||
|
||||
Assert.Equal(2.0, sheet.Cells["A5"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateCountFunctionIncludingZeroValues()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = 0;
|
||||
sheet.Cells["A3"].Value = 20;
|
||||
sheet.Cells["A4"].Formula = "=COUNT(A1,A2,A3)";
|
||||
|
||||
Assert.Equal(3.0, sheet.Cells["A4"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateCountFunctionWithAllNumericValues()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = 20;
|
||||
sheet.Cells["A3"].Value = 30;
|
||||
sheet.Cells["A4"].Formula = "=COUNT(A1,A2,A3)";
|
||||
|
||||
Assert.Equal(3.0, sheet.Cells["A4"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldCreateRefErrorWhenCountRangeOutOfBounds()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=COUNT(A2:A6)";
|
||||
|
||||
Assert.Equal(CellError.Ref, sheet.Cells["A1"].Value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
32
Radzen.Blazor.Tests/Spreadsheet/DayFunctionTests.cs
Normal file
32
Radzen.Blazor.Tests/Spreadsheet/DayFunctionTests.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class DayFunctionTests
|
||||
{
|
||||
[Fact]
|
||||
public void Day_FromDateSerial_ReturnsDay()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
// Using DATEVALUE via VALUE on a date string to get a serial
|
||||
sheet.Cells["A1"].Formula = "=DAY(VALUE(\"2011-04-15\"))";
|
||||
Assert.Equal(15, sheet.Cells["A1"].Data.GetValueOrDefault<double>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Day_FromDateValue_ReturnsDay()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A1"].Data = CellData.FromDate(new System.DateTime(2011, 4, 15));
|
||||
sheet.Cells["B1"].Formula = "=DAY(A1)";
|
||||
Assert.Equal(15, sheet.Cells["B1"].Data.GetValueOrDefault<double>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Day_InvalidText_ReturnsValueError()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A1"].Formula = "=DAY(\"abc\")";
|
||||
Assert.Equal(CellError.Value, sheet.Cells["A1"].Data.GetValueOrDefault<CellError>());
|
||||
}
|
||||
}
|
||||
81
Radzen.Blazor.Tests/Spreadsheet/DeleteRowColumnTests.cs
Normal file
81
Radzen.Blazor.Tests/Spreadsheet/DeleteRowColumnTests.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class DeleteRowColumnTests
|
||||
{
|
||||
[Fact]
|
||||
public void DeleteColumn_ShiftsDataAndDecreasesColumnCount()
|
||||
{
|
||||
var sheet = new Sheet(3, 4);
|
||||
sheet.Cells[0, 0].Value = "A";
|
||||
sheet.Cells[0, 1].Value = "B";
|
||||
sheet.Cells[0, 2].Value = "C";
|
||||
sheet.Cells[0, 3].Value = "D";
|
||||
|
||||
sheet.DeleteColumn(1); // delete column B
|
||||
|
||||
Assert.Equal(3, sheet.ColumnCount);
|
||||
Assert.Equal("A", sheet.Cells[0, 0].Value);
|
||||
Assert.Equal("C", sheet.Cells[0, 1].Value);
|
||||
Assert.Equal("D", sheet.Cells[0, 2].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeleteRow_ShiftsDataAndDecreasesRowCount()
|
||||
{
|
||||
var sheet = new Sheet(4, 2);
|
||||
sheet.Cells[0, 0].Value = "R1";
|
||||
sheet.Cells[1, 0].Value = "R2";
|
||||
sheet.Cells[2, 0].Value = "R3";
|
||||
sheet.Cells[3, 0].Value = "R4";
|
||||
|
||||
sheet.DeleteRow(1); // delete row 2
|
||||
|
||||
Assert.Equal(3, sheet.RowCount);
|
||||
Assert.Equal("R1", sheet.Cells[0, 0].Value);
|
||||
Assert.Equal("R3", sheet.Cells[1, 0].Value);
|
||||
Assert.Equal("R4", sheet.Cells[2, 0].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeleteColumn_DoesNotAdjustFormulas_RefsBecomeError()
|
||||
{
|
||||
var sheet = new Sheet(5, 5);
|
||||
sheet.Cells[0, 0].Value = 1; // A1
|
||||
sheet.Cells[0, 1].Value = 2; // B1
|
||||
sheet.Cells[0, 2].Value = 3; // C1
|
||||
|
||||
// Formula in B2 references A1 and C1
|
||||
sheet.Cells[1, 1].Formula = "=A1+C1";
|
||||
Assert.Equal(4d, sheet.Cells[1, 1].Value);
|
||||
|
||||
// Delete referenced column A -> A1 becomes invalid => #REF!
|
||||
sheet.DeleteColumn(0);
|
||||
|
||||
Assert.Equal(CellError.Ref, sheet.Cells[1, 0].Value);
|
||||
Assert.Equal("=#REF!+C1", sheet.Cells[1, 0].Formula);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeleteRow_DoesNotAdjustFormulas_RefsBecomeError()
|
||||
{
|
||||
var sheet = new Sheet(5, 5);
|
||||
sheet.Cells[0, 0].Value = 1; // A1
|
||||
sheet.Cells[1, 0].Value = 2; // A2
|
||||
sheet.Cells[2, 0].Value = 3; // A3
|
||||
|
||||
// Formula in B2 references A1 and A3
|
||||
sheet.Cells[1, 1].Formula = "=A1+A3";
|
||||
Assert.Equal(4d, sheet.Cells[1, 1].Value);
|
||||
|
||||
// Delete referenced row 1 -> A1 becomes invalid => #REF!
|
||||
sheet.DeleteRow(0);
|
||||
|
||||
Assert.Equal(CellError.Ref, sheet.Cells[0, 1].Value);
|
||||
Assert.Equal("=#REF!+A3", sheet.Cells[0, 1].Formula);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
146
Radzen.Blazor.Tests/Spreadsheet/FilterCommandTests.cs
Normal file
146
Radzen.Blazor.Tests/Spreadsheet/FilterCommandTests.cs
Normal file
@@ -0,0 +1,146 @@
|
||||
using System;
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class FilterCommandTests
|
||||
{
|
||||
private readonly Sheet sheet = new(10, 10);
|
||||
|
||||
[Fact]
|
||||
public void Should_AddFilterWithCommand()
|
||||
{
|
||||
// Initially no filters
|
||||
Assert.Empty(sheet.Filters);
|
||||
|
||||
// Create a filter
|
||||
var filter = new SheetFilter(
|
||||
new EqualToCriterion { Column = 0, Value = "Test" },
|
||||
RangeRef.Parse("A1:A5")
|
||||
);
|
||||
|
||||
// Execute the command
|
||||
var command = new FilterCommand(sheet, filter);
|
||||
var result = command.Execute();
|
||||
|
||||
// Command should succeed
|
||||
Assert.True(result);
|
||||
|
||||
// Filter should be added
|
||||
Assert.Single(sheet.Filters);
|
||||
Assert.Contains(filter, sheet.Filters);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_UndoFilterCommand()
|
||||
{
|
||||
// Initially no filters
|
||||
Assert.Empty(sheet.Filters);
|
||||
|
||||
// Create a filter
|
||||
var filter = new SheetFilter(
|
||||
new EqualToCriterion { Column = 0, Value = "Test" },
|
||||
RangeRef.Parse("A1:A5")
|
||||
);
|
||||
|
||||
// Execute the command
|
||||
var command = new FilterCommand(sheet, filter);
|
||||
command.Execute();
|
||||
|
||||
// Filter should be added
|
||||
Assert.Single(sheet.Filters);
|
||||
|
||||
// Undo the command
|
||||
command.Unexecute();
|
||||
|
||||
// Filter should be removed
|
||||
Assert.Empty(sheet.Filters);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_WorkWithUndoRedoStack()
|
||||
{
|
||||
// Initially no filters
|
||||
Assert.Empty(sheet.Filters);
|
||||
|
||||
// Create a filter
|
||||
var filter = new SheetFilter(
|
||||
new EqualToCriterion { Column = 0, Value = "Test" },
|
||||
RangeRef.Parse("A1:A5")
|
||||
);
|
||||
|
||||
// Execute the command through the undo/redo stack
|
||||
var command = new FilterCommand(sheet, filter);
|
||||
var result = sheet.Commands.Execute(command);
|
||||
|
||||
// Command should succeed
|
||||
Assert.True(result);
|
||||
|
||||
// Filter should be added
|
||||
Assert.Single(sheet.Filters);
|
||||
|
||||
// Undo should be available
|
||||
Assert.True(sheet.Commands.CanUndo);
|
||||
|
||||
// Undo the command
|
||||
sheet.Commands.Undo();
|
||||
|
||||
// Filter should be removed
|
||||
Assert.Empty(sheet.Filters);
|
||||
|
||||
// Redo should be available
|
||||
Assert.True(sheet.Commands.CanRedo);
|
||||
|
||||
// Redo the command
|
||||
sheet.Commands.Redo();
|
||||
|
||||
// Filter should be added again
|
||||
Assert.Single(sheet.Filters);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_HandleMultipleFilters()
|
||||
{
|
||||
// Initially no filters
|
||||
Assert.Empty(sheet.Filters);
|
||||
|
||||
// Create multiple filters
|
||||
var filter1 = new SheetFilter(
|
||||
new EqualToCriterion { Column = 0, Value = "Test1" },
|
||||
RangeRef.Parse("A1:A5")
|
||||
);
|
||||
|
||||
var filter2 = new SheetFilter(
|
||||
new EqualToCriterion { Column = 1, Value = "Test2" },
|
||||
RangeRef.Parse("B1:B5")
|
||||
);
|
||||
|
||||
// Execute commands through the undo/redo stack
|
||||
var command1 = new FilterCommand(sheet, filter1);
|
||||
var command2 = new FilterCommand(sheet, filter2);
|
||||
|
||||
sheet.Commands.Execute(command1);
|
||||
sheet.Commands.Execute(command2);
|
||||
|
||||
// Both filters should be added
|
||||
Assert.Equal(2, sheet.Filters.Count);
|
||||
Assert.Contains(filter1, sheet.Filters);
|
||||
Assert.Contains(filter2, sheet.Filters);
|
||||
|
||||
// Undo both commands
|
||||
sheet.Commands.Undo(); // Undo filter2
|
||||
sheet.Commands.Undo(); // Undo filter1
|
||||
|
||||
// No filters should remain
|
||||
Assert.Empty(sheet.Filters);
|
||||
|
||||
// Redo both commands
|
||||
sheet.Commands.Redo(); // Redo filter1
|
||||
sheet.Commands.Redo(); // Redo filter2
|
||||
|
||||
// Both filters should be back
|
||||
Assert.Equal(2, sheet.Filters.Count);
|
||||
Assert.Contains(filter1, sheet.Filters);
|
||||
Assert.Contains(filter2, sheet.Filters);
|
||||
}
|
||||
}
|
||||
1105
Radzen.Blazor.Tests/Spreadsheet/FilteringTests.cs
Normal file
1105
Radzen.Blazor.Tests/Spreadsheet/FilteringTests.cs
Normal file
File diff suppressed because it is too large
Load Diff
51
Radzen.Blazor.Tests/Spreadsheet/FindFunctionTests.cs
Normal file
51
Radzen.Blazor.Tests/Spreadsheet/FindFunctionTests.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class FindFunctionTests
|
||||
{
|
||||
[Fact]
|
||||
public void Find_CaseSensitive_MatchesUppercase()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A2"].Value = "Miriam McGovern";
|
||||
sheet.Cells["B1"].Formula = "=FIND(\"M\",A2)";
|
||||
Assert.Equal(1d, sheet.Cells["B1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Find_CaseSensitive_MatchesLowercase()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A2"].Value = "Miriam McGovern";
|
||||
sheet.Cells["B1"].Formula = "=FIND(\"m\",A2)";
|
||||
Assert.Equal(6d, sheet.Cells["B1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Find_WithStartNum()
|
||||
{
|
||||
var sheet = new Sheet(10, 30);
|
||||
sheet.Cells["A1"].Value = "AYF0093.YoungMensApparel";
|
||||
sheet.Cells["B1"].Formula = "=FIND(\"Y\",A1,8)";
|
||||
Assert.Equal(9d, sheet.Cells["B1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Find_EmptyFindText_ReturnsStart()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A1"].Value = "abc";
|
||||
sheet.Cells["B1"].Formula = "=FIND(\"\",A1,2)";
|
||||
Assert.Equal(2d, sheet.Cells["B1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Find_NotFound_ReturnsValue()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A1"].Value = "abc";
|
||||
sheet.Cells["B1"].Formula = "=FIND(\"z\",A1)";
|
||||
Assert.Equal(CellError.Value, sheet.Cells["B1"].Data.GetValueOrDefault<CellError>());
|
||||
}
|
||||
}
|
||||
298
Radzen.Blazor.Tests/Spreadsheet/FormulaEvaluatorTests.cs
Normal file
298
Radzen.Blazor.Tests/Spreadsheet/FormulaEvaluatorTests.cs
Normal file
@@ -0,0 +1,298 @@
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class FormulaEvaluationTests
|
||||
{
|
||||
readonly Sheet sheet = new(5, 5);
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateFormulaAfterSettingIt()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 1;
|
||||
sheet.Cells["A2"].Formula = "=A1+1";
|
||||
|
||||
Assert.Equal(2d, sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateFormulaAfterSettingValue()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=A2+1";
|
||||
sheet.Cells["A2"].Value = 1;
|
||||
|
||||
Assert.Equal(2d, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldNotEvaluateFormulaIfEditing()
|
||||
{
|
||||
sheet.BeginUpdate();
|
||||
sheet.Cells["A1"].Formula = "=A2+1";
|
||||
sheet.Cells["A2"].Value = 1;
|
||||
|
||||
Assert.Null(sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateFormulaAfterEndingEdit()
|
||||
{
|
||||
sheet.BeginUpdate();
|
||||
sheet.Cells["A1"].Formula = "=A2+1";
|
||||
sheet.Cells["A2"].Value = 1;
|
||||
sheet.EndUpdate();
|
||||
|
||||
Assert.Equal(2d, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldSetCellValueToErrorValueIfStringIsUsedInBinaryOperation()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=A2+1";
|
||||
sheet.Cells["A2"].Value = "test";
|
||||
|
||||
Assert.Equal(CellError.Value, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldSetCellValueToErrorNameIfInvalidFunctionIsUsedInFormula()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=INVALID_FUNCTION()";
|
||||
sheet.Cells["A2"].Value = "test";
|
||||
|
||||
Assert.Equal(CellError.Name, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("=SUM(")]
|
||||
[InlineData("=SUM(A2,")]
|
||||
[InlineData("=SUM(A2:A2")]
|
||||
public void ShouldSetCellValueToErrorNameIfIncompleteFunctionIsUsedInFormula(string formula)
|
||||
{
|
||||
sheet.Cells["A1"].Formula = formula;
|
||||
sheet.Cells["A2"].Value = "test";
|
||||
|
||||
Assert.Equal(CellError.Name, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldSetCellValueToEqualsIfOnlyEqualsIsSetAsFormula()
|
||||
{
|
||||
sheet.Cells["A1"].SetValue("=");
|
||||
|
||||
Assert.Equal("=", sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateFormulaWhenDependencyIsChanged()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=A2+1";
|
||||
sheet.Cells["A2"].Formula = "=A3+1";
|
||||
sheet.Cells["A3"].Value = 1;
|
||||
|
||||
Assert.Equal(3d, sheet.Cells["A1"].Value);
|
||||
Assert.Equal(2d, sheet.Cells["A2"].Value);
|
||||
Assert.Equal(1d, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateFormulaWhenDependencyIsChangedAndEndEditIsCalled()
|
||||
{
|
||||
sheet.BeginUpdate();
|
||||
sheet.Cells["A1"].Formula = "=A2+1";
|
||||
sheet.Cells["A2"].Formula = "=A3+1";
|
||||
sheet.Cells["A3"].Value = 1;
|
||||
sheet.EndUpdate();
|
||||
|
||||
Assert.Equal(3d, sheet.Cells["A1"].Value);
|
||||
Assert.Equal(2d, sheet.Cells["A2"].Value);
|
||||
Assert.Equal(1d, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldTreatEmptyValueAsZeroInFormula()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=A2+1";
|
||||
|
||||
Assert.Equal(1d, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldHandleSelfReferencingFormulas()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=A1+1";
|
||||
|
||||
// Setting a value should not cause infinite recursion
|
||||
sheet.Cells["A1"].Value = 1;
|
||||
|
||||
// The value should be stable and not cause infinite recursion
|
||||
Assert.NotNull(sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldSetDiv0ErrorWhenDividingByZero()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=A2/A3";
|
||||
sheet.Cells["A2"].Value = 1;
|
||||
sheet.Cells["A3"].Value = 0;
|
||||
|
||||
Assert.Equal(CellError.Div0, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldSetErrorToCircularWhenCellFormulasReferenceEachOther()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=A2+1";
|
||||
sheet.Cells["A2"].Formula = "=A1+1";
|
||||
|
||||
// The value should be an error
|
||||
Assert.Equal(CellError.Circular, sheet.Cells["A1"].Value);
|
||||
Assert.Equal(CellError.Circular, sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnNameErrorForUnknownFunction()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=UNKNOWN()";
|
||||
|
||||
Assert.Equal(CellError.Name, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldCreateRefErrorWhenOutOfBounds()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=A6";
|
||||
|
||||
Assert.Equal(CellError.Ref, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldCreateRefErrorWhenRangeOutOfBounds()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=SUM(A2:A6)";
|
||||
|
||||
Assert.Equal(CellError.Ref, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldCreateRefErrorWhenCountRangeOutOfBounds()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=COUNT(A2:A6)";
|
||||
|
||||
Assert.Equal(CellError.Ref, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldCreateRefErrorWhenCountaRangeOutOfBounds()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=COUNTA(A2:A6)";
|
||||
|
||||
Assert.Equal(CellError.Ref, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateIfFunctionWithDecimalValues()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 0.5m;
|
||||
sheet.Cells["A2"].Formula = "=IF(A1,\"True\",\"False\")";
|
||||
|
||||
Assert.Equal("True", sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnNameErrorForUnknownFunctionUppercase()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=UNKNOWNFUNCTION(1,2,3)";
|
||||
|
||||
Assert.Equal(CellError.Name, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnNameErrorForUnknownFunctionWithMixedCase()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=UnknownFunction(1,2,3)";
|
||||
|
||||
Assert.Equal(CellError.Name, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnNameErrorForUnknownFunctionWithLowercase()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=unknownfunction(1,2,3)";
|
||||
|
||||
Assert.Equal(CellError.Name, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateAndFunctionInIfStatementWithFalseCondition()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 5;
|
||||
sheet.Cells["A2"].Value = 150;
|
||||
sheet.Cells["A3"].Formula = "=IF(AND(A1>1,A2<100),A1,\"Out of range\")";
|
||||
|
||||
Assert.Equal("Out of range", sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateOrFunctionWithProvidedExample3()
|
||||
{
|
||||
sheet.Cells["A2"].Value = 75;
|
||||
sheet.Cells["A3"].Formula = "=IF(OR(A2<0,A2>50),A2,\"The value is out of range\")";
|
||||
|
||||
Assert.Equal(75d, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateNotFunctionWithOrFunction()
|
||||
{
|
||||
sheet.Cells["A1"].Value = false;
|
||||
sheet.Cells["A2"].Value = false;
|
||||
sheet.Cells["A3"].Formula = "=NOT(OR(A1,A2))";
|
||||
|
||||
Assert.Equal(true, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
// IFERROR function tests are in IfErrorFunctionTests.cs
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateSimpleDivisionByZero()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Formula = "=A1/0";
|
||||
|
||||
Assert.Equal(CellError.Div0, sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Evaluator_ShouldResolveCrossSheetCellReference()
|
||||
{
|
||||
var wb = new Workbook();
|
||||
var s1 = wb.AddSheet("Sheet1", 5, 5);
|
||||
var s2 = wb.AddSheet("Sheet2", 5, 5);
|
||||
|
||||
s2.Cells[0, 2].Value = 42; // C1 on Sheet2
|
||||
|
||||
s1.Cells[0, 0].Formula = "=Sheet2!C1"; // A1 on Sheet1 refers to Sheet2!C1
|
||||
|
||||
Assert.Equal(42d, s1.Cells[0, 0].Data.GetValueOrDefault<double>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Evaluator_ShouldResolveCrossSheetRangeInFunction()
|
||||
{
|
||||
var wb = new Workbook();
|
||||
var s1 = wb.AddSheet("Sheet1", 5, 5);
|
||||
var s2 = wb.AddSheet("Sheet2", 5, 5);
|
||||
|
||||
s2.Cells[0, 0].Value = 1; // A1
|
||||
s2.Cells[0, 1].Value = 2; // B1
|
||||
s2.Cells[1, 0].Value = 3; // A2
|
||||
s2.Cells[1, 1].Value = 4; // B2
|
||||
|
||||
s1.Cells[0, 0].Formula = "=SUM(Sheet2!A1:Sheet2!B2)";
|
||||
|
||||
Assert.Equal(10d, s1.Cells[0, 0].Data.GetValueOrDefault<double>());
|
||||
}
|
||||
}
|
||||
143
Radzen.Blazor.Tests/Spreadsheet/FormulaLexerTests.cs
Normal file
143
Radzen.Blazor.Tests/Spreadsheet/FormulaLexerTests.cs
Normal file
@@ -0,0 +1,143 @@
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
using System;
|
||||
using Xunit;
|
||||
|
||||
public class FormulaLexerTests
|
||||
{
|
||||
[Fact]
|
||||
public void FormulaLexer_ShouldParseCellIdentifier()
|
||||
{
|
||||
var tokens = FormulaLexer.Scan("=A1");
|
||||
Assert.Equal(FormulaTokenType.Equals, tokens[0].Type);
|
||||
Assert.Equal(0, tokens[0].Start);
|
||||
Assert.Equal(1, tokens[0].End);
|
||||
Assert.Equal(FormulaTokenType.CellIdentifier, tokens[1].Type);
|
||||
Assert.Equal("A1", tokens[1].Address.ToString());
|
||||
Assert.Equal(1, tokens[1].Start);
|
||||
Assert.Equal(3, tokens[1].End);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaLexer_ShouldParseSimpleFormula()
|
||||
{
|
||||
var tokens = FormulaLexer.Scan("=A1+b2");
|
||||
Assert.Equal(FormulaTokenType.Equals, tokens[0].Type);
|
||||
Assert.Equal(0, tokens[0].Start);
|
||||
Assert.Equal(1, tokens[0].End);
|
||||
Assert.Equal(FormulaTokenType.CellIdentifier, tokens[1].Type);
|
||||
Assert.Equal("A1", tokens[1].Value);
|
||||
Assert.Equal(1, tokens[1].Start);
|
||||
Assert.Equal(3, tokens[1].End);
|
||||
Assert.Equal(FormulaTokenType.Plus, tokens[2].Type);
|
||||
Assert.Equal(3, tokens[2].Start);
|
||||
Assert.Equal(4, tokens[2].End);
|
||||
Assert.Equal(FormulaTokenType.CellIdentifier, tokens[3].Type);
|
||||
Assert.Equal("b2", tokens[3].Value);
|
||||
Assert.Equal(4, tokens[3].Start);
|
||||
Assert.Equal(6, tokens[3].End);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaLexer_ShouldPreserveWhitespaceAsTrivia()
|
||||
{
|
||||
var tokens = FormulaLexer.Scan("= A1 + b2 ");
|
||||
|
||||
// Check that whitespace is preserved as trivia
|
||||
Assert.Equal(FormulaTokenType.Equals, tokens[0].Type);
|
||||
Assert.Empty(tokens[0].LeadingTrivia);
|
||||
Assert.Single(tokens[0].TrailingTrivia);
|
||||
Assert.Equal(FormulaTokenTriviaKind.Whitespace, tokens[0].TrailingTrivia[0].Kind);
|
||||
Assert.Equal(" ", tokens[0].TrailingTrivia[0].Text);
|
||||
Assert.Equal(0, tokens[0].Start);
|
||||
Assert.Equal(2, tokens[0].End);
|
||||
|
||||
Assert.Equal(FormulaTokenType.CellIdentifier, tokens[1].Type);
|
||||
Assert.Empty(tokens[1].LeadingTrivia);
|
||||
Assert.Single(tokens[1].TrailingTrivia);
|
||||
Assert.Equal(FormulaTokenTriviaKind.Whitespace, tokens[1].TrailingTrivia[0].Kind);
|
||||
Assert.Equal(" ", tokens[1].TrailingTrivia[0].Text);
|
||||
Assert.Equal(2, tokens[1].Start);
|
||||
Assert.Equal(5, tokens[1].End);
|
||||
|
||||
Assert.Equal(FormulaTokenType.Plus, tokens[2].Type);
|
||||
Assert.Empty(tokens[2].LeadingTrivia);
|
||||
Assert.Single(tokens[2].TrailingTrivia);
|
||||
Assert.Equal(FormulaTokenTriviaKind.Whitespace, tokens[2].TrailingTrivia[0].Kind);
|
||||
Assert.Equal(" ", tokens[2].TrailingTrivia[0].Text);
|
||||
Assert.Equal(5, tokens[2].Start);
|
||||
Assert.Equal(7, tokens[2].End);
|
||||
|
||||
Assert.Equal(FormulaTokenType.CellIdentifier, tokens[3].Type);
|
||||
Assert.Empty(tokens[3].LeadingTrivia);
|
||||
Assert.Single(tokens[3].TrailingTrivia);
|
||||
Assert.Equal(FormulaTokenTriviaKind.Whitespace, tokens[3].TrailingTrivia[0].Kind);
|
||||
Assert.Equal(" ", tokens[3].TrailingTrivia[0].Text);
|
||||
Assert.Equal(7, tokens[3].Start);
|
||||
Assert.Equal(10, tokens[3].End);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaLexer_ShouldPreserveMultipleWhitespaceAsTrivia()
|
||||
{
|
||||
var tokens = FormulaLexer.Scan("= A1 + b2 ");
|
||||
|
||||
// Check that multiple whitespace characters are preserved
|
||||
Assert.Equal(FormulaTokenType.Equals, tokens[0].Type);
|
||||
Assert.Single(tokens[0].TrailingTrivia);
|
||||
Assert.Equal(" ", tokens[0].TrailingTrivia[0].Text);
|
||||
Assert.Equal(0, tokens[0].Start);
|
||||
Assert.Equal(3, tokens[0].End);
|
||||
|
||||
Assert.Equal(FormulaTokenType.CellIdentifier, tokens[1].Type);
|
||||
Assert.Single(tokens[1].TrailingTrivia);
|
||||
Assert.Equal(" ", tokens[1].TrailingTrivia[0].Text);
|
||||
Assert.Equal(3, tokens[1].Start);
|
||||
Assert.Equal(7, tokens[1].End);
|
||||
|
||||
Assert.Equal(FormulaTokenType.Plus, tokens[2].Type);
|
||||
Assert.Single(tokens[2].TrailingTrivia);
|
||||
Assert.Equal(" ", tokens[2].TrailingTrivia[0].Text);
|
||||
Assert.Equal(7, tokens[2].Start);
|
||||
Assert.Equal(10, tokens[2].End);
|
||||
|
||||
Assert.Equal(FormulaTokenType.CellIdentifier, tokens[3].Type);
|
||||
Assert.Single(tokens[3].TrailingTrivia);
|
||||
Assert.Equal(" ", tokens[3].TrailingTrivia[0].Text);
|
||||
Assert.Equal(10, tokens[3].Start);
|
||||
Assert.Equal(14, tokens[3].End);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaLexer_ShouldPreserveLineEndingsAsTrivia()
|
||||
{
|
||||
var tokens = FormulaLexer.Scan("=A1\n+b2");
|
||||
|
||||
// Check that line endings are preserved as trivia
|
||||
Assert.Equal(FormulaTokenType.Equals, tokens[0].Type);
|
||||
Assert.Empty(tokens[0].LeadingTrivia);
|
||||
Assert.Empty(tokens[0].TrailingTrivia);
|
||||
Assert.Equal(0, tokens[0].Start);
|
||||
Assert.Equal(1, tokens[0].End);
|
||||
|
||||
Assert.Equal(FormulaTokenType.CellIdentifier, tokens[1].Type);
|
||||
Assert.Empty(tokens[1].LeadingTrivia);
|
||||
Assert.Single(tokens[1].TrailingTrivia);
|
||||
Assert.Equal(FormulaTokenTriviaKind.EndOfLine, tokens[1].TrailingTrivia[0].Kind);
|
||||
Assert.Equal("\n", tokens[1].TrailingTrivia[0].Text);
|
||||
Assert.Equal(1, tokens[1].Start);
|
||||
Assert.Equal(4, tokens[1].End);
|
||||
|
||||
Assert.Equal(FormulaTokenType.Plus, tokens[2].Type);
|
||||
Assert.Empty(tokens[2].LeadingTrivia);
|
||||
Assert.Empty(tokens[2].TrailingTrivia);
|
||||
Assert.Equal(4, tokens[2].Start);
|
||||
Assert.Equal(5, tokens[2].End);
|
||||
|
||||
Assert.Equal(expected: FormulaTokenType.CellIdentifier, tokens[3].Type);
|
||||
Assert.Empty(tokens[3].LeadingTrivia);
|
||||
Assert.Empty(tokens[3].TrailingTrivia);
|
||||
Assert.Equal(5, tokens[3].Start);
|
||||
Assert.Equal(7, tokens[3].End);
|
||||
}
|
||||
}
|
||||
644
Radzen.Blazor.Tests/Spreadsheet/FormulaParserTests.cs
Normal file
644
Radzen.Blazor.Tests/Spreadsheet/FormulaParserTests.cs
Normal file
@@ -0,0 +1,644 @@
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
using System;
|
||||
using Xunit;
|
||||
|
||||
public class FormulaParserTests
|
||||
{
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldRequireEqualsAtStart()
|
||||
{
|
||||
var formula = "A1";
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.NotEmpty(syntaxTree.Errors);
|
||||
Assert.Contains("Unexpected token", syntaxTree.Errors[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldParseNumberLiteral()
|
||||
{
|
||||
var formula = "=123";
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.Empty(syntaxTree.Errors);
|
||||
Assert.IsType<NumberLiteralSyntaxNode>(syntaxTree.Root);
|
||||
var numberNode = (NumberLiteralSyntaxNode)syntaxTree.Root;
|
||||
Assert.Equal(123, numberNode.Token.IntValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldParseAdditionOfTwoNumberLiterals()
|
||||
{
|
||||
var formula = "=123+456";
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.Empty(syntaxTree.Errors);
|
||||
Assert.IsType<BinaryExpressionSyntaxNode>(syntaxTree.Root);
|
||||
var binaryNode = (BinaryExpressionSyntaxNode)syntaxTree.Root;
|
||||
Assert.Equal(BinaryOperator.Plus, binaryNode.Operator);
|
||||
Assert.IsType<NumberLiteralSyntaxNode>(binaryNode.Left);
|
||||
Assert.IsType<NumberLiteralSyntaxNode>(binaryNode.Right);
|
||||
Assert.Equal(123, ((NumberLiteralSyntaxNode)binaryNode.Left).Token.IntValue);
|
||||
Assert.Equal(456, ((NumberLiteralSyntaxNode)binaryNode.Right).Token.IntValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldParseAdditionOfMultipleNumberLiterals()
|
||||
{
|
||||
var formula = "=123+456+789";
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.Empty(syntaxTree.Errors);
|
||||
var node = syntaxTree.Root;
|
||||
Assert.IsType<BinaryExpressionSyntaxNode>(node);
|
||||
var binaryNode = (BinaryExpressionSyntaxNode)node;
|
||||
Assert.Equal(BinaryOperator.Plus, binaryNode.Operator);
|
||||
Assert.IsType<BinaryExpressionSyntaxNode>(binaryNode.Left);
|
||||
var leftBinaryNode = (BinaryExpressionSyntaxNode)binaryNode.Left;
|
||||
Assert.Equal(BinaryOperator.Plus, leftBinaryNode.Operator);
|
||||
Assert.Equal(123, ((NumberLiteralSyntaxNode)leftBinaryNode.Left).Token.IntValue);
|
||||
Assert.Equal(456, ((NumberLiteralSyntaxNode)leftBinaryNode.Right).Token.IntValue);
|
||||
Assert.Equal(789, ((NumberLiteralSyntaxNode)binaryNode.Right).Token.IntValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldParseSubtractionOfTwoNumberLiterals()
|
||||
{
|
||||
var formula = "=123-456";
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.Empty(syntaxTree.Errors);
|
||||
var node = syntaxTree.Root;
|
||||
Assert.IsType<BinaryExpressionSyntaxNode>(node);
|
||||
var binaryNode = (BinaryExpressionSyntaxNode)node;
|
||||
Assert.Equal(BinaryOperator.Minus, binaryNode.Operator);
|
||||
Assert.IsType<NumberLiteralSyntaxNode>(binaryNode.Left);
|
||||
Assert.IsType<NumberLiteralSyntaxNode>(binaryNode.Right);
|
||||
Assert.Equal(123, ((NumberLiteralSyntaxNode)binaryNode.Left).Token.IntValue);
|
||||
Assert.Equal(456, ((NumberLiteralSyntaxNode)binaryNode.Right).Token.IntValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldParseUnaryNegativeNumber()
|
||||
{
|
||||
var formula = "=-123";
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.Empty(syntaxTree.Errors);
|
||||
Assert.IsType<UnaryExpressionSyntaxNode>(syntaxTree.Root);
|
||||
var unary = (UnaryExpressionSyntaxNode)syntaxTree.Root;
|
||||
Assert.Equal(UnaryOperator.Negate, unary.Operator);
|
||||
Assert.IsType<NumberLiteralSyntaxNode>(unary.Operand);
|
||||
Assert.Equal(123, ((NumberLiteralSyntaxNode)unary.Operand).Token.IntValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldParseUnaryPlusNumber()
|
||||
{
|
||||
var formula = "=+123";
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.Empty(syntaxTree.Errors);
|
||||
Assert.IsType<UnaryExpressionSyntaxNode>(syntaxTree.Root);
|
||||
var unary = (UnaryExpressionSyntaxNode)syntaxTree.Root;
|
||||
Assert.Equal(UnaryOperator.Plus, unary.Operator);
|
||||
Assert.IsType<NumberLiteralSyntaxNode>(unary.Operand);
|
||||
Assert.Equal(123, ((NumberLiteralSyntaxNode)unary.Operand).Token.IntValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldParseUnaryPlusInFunctionArgument()
|
||||
{
|
||||
var formula = "=LEFT(A1,+1)";
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.Empty(syntaxTree.Errors);
|
||||
Assert.IsType<FunctionSyntaxNode>(syntaxTree.Root);
|
||||
var fn = (FunctionSyntaxNode)syntaxTree.Root;
|
||||
Assert.Equal("LEFT", fn.Name);
|
||||
Assert.IsType<UnaryExpressionSyntaxNode>(fn.Arguments[1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldParseMultipleUnaryOperators()
|
||||
{
|
||||
var formula = "=-+-+3";
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.Empty(syntaxTree.Errors);
|
||||
// Expect nested unary nodes: - ( + ( - ( + 3 ) ) )
|
||||
var node = syntaxTree.Root;
|
||||
Assert.IsType<UnaryExpressionSyntaxNode>(node);
|
||||
var u1 = (UnaryExpressionSyntaxNode)node; // '-'
|
||||
Assert.Equal(UnaryOperator.Negate, u1.Operator);
|
||||
Assert.IsType<UnaryExpressionSyntaxNode>(u1.Operand);
|
||||
var u2 = (UnaryExpressionSyntaxNode)u1.Operand; // '+'
|
||||
Assert.Equal(UnaryOperator.Plus, u2.Operator);
|
||||
Assert.IsType<UnaryExpressionSyntaxNode>(u2.Operand);
|
||||
var u3 = (UnaryExpressionSyntaxNode)u2.Operand; // '-'
|
||||
Assert.Equal(UnaryOperator.Negate, u3.Operator);
|
||||
Assert.IsType<UnaryExpressionSyntaxNode>(u3.Operand);
|
||||
var u4 = (UnaryExpressionSyntaxNode)u3.Operand; // '+'
|
||||
Assert.Equal(UnaryOperator.Plus, u4.Operator);
|
||||
Assert.IsType<NumberLiteralSyntaxNode>(u4.Operand);
|
||||
Assert.Equal(3, ((NumberLiteralSyntaxNode)u4.Operand).Token.IntValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldParseUnaryNegativeInFunctionArgument()
|
||||
{
|
||||
var formula = "=LEFT(A1,-1)";
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.Empty(syntaxTree.Errors);
|
||||
Assert.IsType<FunctionSyntaxNode>(syntaxTree.Root);
|
||||
var fn = (FunctionSyntaxNode)syntaxTree.Root;
|
||||
Assert.Equal("LEFT", fn.Name);
|
||||
Assert.Equal(2, fn.Arguments.Count);
|
||||
Assert.IsType<CellSyntaxNode>(fn.Arguments[0]);
|
||||
Assert.IsType<UnaryExpressionSyntaxNode>(fn.Arguments[1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldParseSubtractionOfMultipleNumberLiterals()
|
||||
{
|
||||
var formula = "=123-456-789";
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.Empty(syntaxTree.Errors);
|
||||
var node = syntaxTree.Root;
|
||||
Assert.IsType<BinaryExpressionSyntaxNode>(node);
|
||||
var binaryNode = (BinaryExpressionSyntaxNode)node;
|
||||
Assert.Equal(BinaryOperator.Minus, binaryNode.Operator);
|
||||
Assert.IsType<BinaryExpressionSyntaxNode>(binaryNode.Left);
|
||||
var leftBinaryNode = (BinaryExpressionSyntaxNode)binaryNode.Left;
|
||||
Assert.Equal(BinaryOperator.Minus, leftBinaryNode.Operator);
|
||||
Assert.Equal(123, ((NumberLiteralSyntaxNode)leftBinaryNode.Left).Token.IntValue);
|
||||
Assert.Equal(456, ((NumberLiteralSyntaxNode)leftBinaryNode.Right).Token.IntValue);
|
||||
Assert.Equal(789, ((NumberLiteralSyntaxNode)binaryNode.Right).Token.IntValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldParseMultiplicationOfTwoNumberLiterals()
|
||||
{
|
||||
var formula = "=123*456";
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.Empty(syntaxTree.Errors);
|
||||
var node = syntaxTree.Root;
|
||||
Assert.IsType<BinaryExpressionSyntaxNode>(node);
|
||||
var binaryNode = (BinaryExpressionSyntaxNode)node;
|
||||
Assert.Equal(BinaryOperator.Multiply, binaryNode.Operator);
|
||||
Assert.IsType<NumberLiteralSyntaxNode>(binaryNode.Left);
|
||||
Assert.IsType<NumberLiteralSyntaxNode>(binaryNode.Right);
|
||||
Assert.Equal(123, ((NumberLiteralSyntaxNode)binaryNode.Left).Token.IntValue);
|
||||
Assert.Equal(456, ((NumberLiteralSyntaxNode)binaryNode.Right).Token.IntValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParse_MultiplicationPrecedence()
|
||||
{
|
||||
var formula = "=123+456*789";
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.Empty(syntaxTree.Errors);
|
||||
var node = syntaxTree.Root;
|
||||
Assert.IsType<BinaryExpressionSyntaxNode>(node);
|
||||
var binaryNode = (BinaryExpressionSyntaxNode)node;
|
||||
Assert.Equal(BinaryOperator.Plus, binaryNode.Operator);
|
||||
Assert.IsType<NumberLiteralSyntaxNode>(binaryNode.Left);
|
||||
Assert.IsType<BinaryExpressionSyntaxNode>(binaryNode.Right);
|
||||
var rightBinaryNode = (BinaryExpressionSyntaxNode)binaryNode.Right;
|
||||
Assert.Equal(BinaryOperator.Multiply, rightBinaryNode.Operator);
|
||||
Assert.Equal(456, ((NumberLiteralSyntaxNode)rightBinaryNode.Left).Token.IntValue);
|
||||
Assert.Equal(789, ((NumberLiteralSyntaxNode)rightBinaryNode.Right).Token.IntValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldParseDivisionOfTwoNumberLiterals()
|
||||
{
|
||||
var formula = "=123/456";
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.Empty(syntaxTree.Errors);
|
||||
var node = syntaxTree.Root;
|
||||
Assert.IsType<BinaryExpressionSyntaxNode>(node);
|
||||
var binaryNode = (BinaryExpressionSyntaxNode)node;
|
||||
Assert.Equal(BinaryOperator.Divide, binaryNode.Operator);
|
||||
Assert.IsType<NumberLiteralSyntaxNode>(binaryNode.Left);
|
||||
Assert.IsType<NumberLiteralSyntaxNode>(binaryNode.Right);
|
||||
Assert.Equal(123, ((NumberLiteralSyntaxNode)binaryNode.Left).Token.IntValue);
|
||||
Assert.Equal(456, ((NumberLiteralSyntaxNode)binaryNode.Right).Token.IntValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldParseParentheses()
|
||||
{
|
||||
var formula = "=(123+456)*789";
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.Empty(syntaxTree.Errors);
|
||||
var node = syntaxTree.Root;
|
||||
Assert.IsType<BinaryExpressionSyntaxNode>(node);
|
||||
var binaryNode = (BinaryExpressionSyntaxNode)node;
|
||||
Assert.Equal(BinaryOperator.Multiply, binaryNode.Operator);
|
||||
Assert.IsType<BinaryExpressionSyntaxNode>(binaryNode.Left);
|
||||
Assert.IsType<NumberLiteralSyntaxNode>(binaryNode.Right);
|
||||
var leftBinaryNode = (BinaryExpressionSyntaxNode)binaryNode.Left;
|
||||
Assert.Equal(789, ((NumberLiteralSyntaxNode)binaryNode.Right).Token.IntValue);
|
||||
Assert.Equal(BinaryOperator.Plus, leftBinaryNode.Operator);
|
||||
Assert.Equal(123, ((NumberLiteralSyntaxNode)leftBinaryNode.Left).Token.IntValue);
|
||||
Assert.Equal(456, ((NumberLiteralSyntaxNode)leftBinaryNode.Right).Token.IntValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldParseNestedParentheses()
|
||||
{
|
||||
var formula = "=((123+456)*789)/101112";
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.Empty(syntaxTree.Errors);
|
||||
var node = syntaxTree.Root;
|
||||
Assert.IsType<BinaryExpressionSyntaxNode>(node);
|
||||
var binaryNode = (BinaryExpressionSyntaxNode)node;
|
||||
Assert.Equal(BinaryOperator.Divide, binaryNode.Operator);
|
||||
Assert.IsType<BinaryExpressionSyntaxNode>(binaryNode.Left);
|
||||
Assert.IsType<NumberLiteralSyntaxNode>(binaryNode.Right);
|
||||
var leftBinaryNode = (BinaryExpressionSyntaxNode)binaryNode.Left;
|
||||
Assert.Equal(101112, ((NumberLiteralSyntaxNode)binaryNode.Right).Token.IntValue);
|
||||
Assert.Equal(BinaryOperator.Multiply, leftBinaryNode.Operator);
|
||||
Assert.IsType<BinaryExpressionSyntaxNode>(leftBinaryNode.Left);
|
||||
var leftLeftBinaryNode = (BinaryExpressionSyntaxNode)leftBinaryNode.Left;
|
||||
Assert.Equal(123, ((NumberLiteralSyntaxNode)leftLeftBinaryNode.Left).Token.IntValue);
|
||||
Assert.Equal(456, ((NumberLiteralSyntaxNode)leftLeftBinaryNode.Right).Token.IntValue);
|
||||
Assert.Equal(789, ((NumberLiteralSyntaxNode)leftBinaryNode.Right).Token.IntValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldParseCellIndentifer()
|
||||
{
|
||||
var formula = "=A1";
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.Empty(syntaxTree.Errors);
|
||||
var node = syntaxTree.Root;
|
||||
Assert.IsType<CellSyntaxNode>(node);
|
||||
var cellIdentifierNode = (CellSyntaxNode)node;
|
||||
Assert.Equal("A1", cellIdentifierNode.Token.Address.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldParseSheetQualifiedCellIdentifier()
|
||||
{
|
||||
var formula = "=Sheet2!C1";
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.Empty(syntaxTree.Errors);
|
||||
var node = syntaxTree.Root;
|
||||
Assert.IsType<CellSyntaxNode>(node);
|
||||
var cellIdentifierNode = (CellSyntaxNode)node;
|
||||
Assert.Equal("C1", cellIdentifierNode.Token.Address.ToString());
|
||||
Assert.Equal("Sheet2", cellIdentifierNode.Token.Address.Sheet);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldParseFunction()
|
||||
{
|
||||
var formula = "=SUM(A1,1)";
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.Empty(syntaxTree.Errors);
|
||||
var node = syntaxTree.Root;
|
||||
Assert.IsType<FunctionSyntaxNode>(node);
|
||||
var functionNode = (FunctionSyntaxNode)node;
|
||||
Assert.Equal("SUM", functionNode.Name);
|
||||
Assert.Equal(2, functionNode.Arguments.Count);
|
||||
Assert.IsType<CellSyntaxNode>(functionNode.Arguments[0]);
|
||||
Assert.IsType<NumberLiteralSyntaxNode>(functionNode.Arguments[1]);
|
||||
|
||||
var cellIdentifierNode = (CellSyntaxNode)functionNode.Arguments[0];
|
||||
Assert.Equal("A1", cellIdentifierNode.Token.Address.ToString());
|
||||
var numberLiteralNode = (NumberLiteralSyntaxNode)functionNode.Arguments[1];
|
||||
Assert.Equal(1, numberLiteralNode.Token.IntValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldParseNestedFunctions()
|
||||
{
|
||||
var formula = "=SUM(A1,MAX(B1,C1))";
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.Empty(syntaxTree.Errors);
|
||||
var node = syntaxTree.Root;
|
||||
Assert.IsType<FunctionSyntaxNode>(node);
|
||||
var functionNode = (FunctionSyntaxNode)node;
|
||||
Assert.Equal("SUM", functionNode.Name);
|
||||
Assert.Equal(2, functionNode.Arguments.Count);
|
||||
Assert.IsType<CellSyntaxNode>(functionNode.Arguments[0]);
|
||||
Assert.IsType<FunctionSyntaxNode>(functionNode.Arguments[1]);
|
||||
|
||||
var cellIdentifierNode = (CellSyntaxNode)functionNode.Arguments[0];
|
||||
Assert.Equal("A1", cellIdentifierNode.Token.Address.ToString());
|
||||
|
||||
var nestedFunctionNode = (FunctionSyntaxNode)functionNode.Arguments[1];
|
||||
Assert.Equal("MAX", nestedFunctionNode.Name);
|
||||
Assert.Equal(2, nestedFunctionNode.Arguments.Count);
|
||||
Assert.IsType<CellSyntaxNode>(nestedFunctionNode.Arguments[0]);
|
||||
Assert.IsType<CellSyntaxNode>(nestedFunctionNode.Arguments[1]);
|
||||
|
||||
var firstCellIdentifierNode = (CellSyntaxNode)nestedFunctionNode.Arguments[0];
|
||||
var secondCellIdentifierNode = (CellSyntaxNode)nestedFunctionNode.Arguments[1];
|
||||
Assert.Equal("B1", firstCellIdentifierNode.Token.Address.ToString());
|
||||
Assert.Equal("C1", secondCellIdentifierNode.Token.Address.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldParseFunctionWithNoArguments()
|
||||
{
|
||||
var formula = "=SUM()";
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.Empty(syntaxTree.Errors);
|
||||
var node = syntaxTree.Root;
|
||||
Assert.IsType<FunctionSyntaxNode>(node);
|
||||
var functionNode = (FunctionSyntaxNode)node;
|
||||
Assert.Equal("SUM", functionNode.Name);
|
||||
Assert.Empty(functionNode.Arguments);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldParseCellRange()
|
||||
{
|
||||
var formula = "=A1:A2";
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.Empty(syntaxTree.Errors);
|
||||
var node = syntaxTree.Root;
|
||||
Assert.IsType<RangeSyntaxNode>(node);
|
||||
var rangeNode = (RangeSyntaxNode)node;
|
||||
Assert.Equal("A1", rangeNode.Start.Token.Address.ToString());
|
||||
Assert.Equal("A2", rangeNode.End.Token.Address.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldParseSheetQualifiedRange()
|
||||
{
|
||||
var formula = "=Sheet2!A1:Sheet2!B2";
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.Empty(syntaxTree.Errors);
|
||||
var node = syntaxTree.Root;
|
||||
Assert.IsType<RangeSyntaxNode>(node);
|
||||
var rangeNode = (RangeSyntaxNode)node;
|
||||
Assert.Equal("A1", rangeNode.Start.Token.Address.ToString());
|
||||
Assert.Equal("B2", rangeNode.End.Token.Address.ToString());
|
||||
Assert.Equal("Sheet2", rangeNode.Start.Token.Address.Sheet);
|
||||
Assert.Equal("Sheet2", rangeNode.End.Token.Address.Sheet);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldParseCellRangeInFunction()
|
||||
{
|
||||
var formula = "=SUM(A1:A2)";
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.Empty(syntaxTree.Errors);
|
||||
var node = syntaxTree.Root;
|
||||
Assert.IsType<FunctionSyntaxNode>(node);
|
||||
var functionNode = (FunctionSyntaxNode)node;
|
||||
Assert.Equal("SUM", functionNode.Name);
|
||||
Assert.Single(functionNode.Arguments);
|
||||
Assert.IsType<RangeSyntaxNode>(functionNode.Arguments[0]);
|
||||
var rangeNode = (RangeSyntaxNode)functionNode.Arguments[0];
|
||||
Assert.Equal("A1", rangeNode.Start.Token.Address.ToString());
|
||||
Assert.Equal("A2", rangeNode.End.Token.Address.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldHandleInvalidRange()
|
||||
{
|
||||
var formula = "=A2:A1";
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.Empty(syntaxTree.Errors);
|
||||
var node = syntaxTree.Root;
|
||||
Assert.IsType<RangeSyntaxNode>(node);
|
||||
var rangeNode = (RangeSyntaxNode)node;
|
||||
Assert.Equal("A2", rangeNode.Start.Token.Address.ToString());
|
||||
Assert.Equal("A1", rangeNode.End.Token.Address.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldHandleSingleCellRange()
|
||||
{
|
||||
var formula = "=A1:A1";
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.Empty(syntaxTree.Errors);
|
||||
var node = syntaxTree.Root;
|
||||
Assert.IsType<RangeSyntaxNode>(node);
|
||||
var rangeNode = (RangeSyntaxNode)node;
|
||||
Assert.Equal("A1", rangeNode.Start.Token.Address.ToString());
|
||||
Assert.Equal("A1", rangeNode.End.Token.Address.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldHandleMultiColumnRange()
|
||||
{
|
||||
var formula = "=A1:B1";
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.Empty(syntaxTree.Errors);
|
||||
var node = syntaxTree.Root;
|
||||
Assert.IsType<RangeSyntaxNode>(node);
|
||||
var rangeNode = (RangeSyntaxNode)node;
|
||||
Assert.Equal("A1", rangeNode.Start.Token.Address.ToString());
|
||||
Assert.Equal("B1", rangeNode.End.Token.Address.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldHandleMultiRowRange()
|
||||
{
|
||||
var formula = "=A1:A2";
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.Empty(syntaxTree.Errors);
|
||||
var node = syntaxTree.Root;
|
||||
Assert.IsType<RangeSyntaxNode>(node);
|
||||
var rangeNode = (RangeSyntaxNode)node;
|
||||
Assert.Equal("A1", rangeNode.Start.Token.Address.ToString());
|
||||
Assert.Equal("A2", rangeNode.End.Token.Address.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldHandleMultiCellRange()
|
||||
{
|
||||
var formula = "=A1:B2";
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.Empty(syntaxTree.Errors);
|
||||
var node = syntaxTree.Root;
|
||||
Assert.IsType<RangeSyntaxNode>(node);
|
||||
var rangeNode = (RangeSyntaxNode)node;
|
||||
Assert.Equal("A1", rangeNode.Start.Token.Address.ToString());
|
||||
Assert.Equal("B2", rangeNode.End.Token.Address.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldAddErrorOnInvalidFormula()
|
||||
{
|
||||
var formula = "A1"; // Missing equals sign
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.NotEmpty(syntaxTree.Errors);
|
||||
Assert.Contains("Unexpected token", syntaxTree.Errors[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldReturnPartialExpressionOnIncompleteExpression()
|
||||
{
|
||||
var formula = "=123+"; // Incomplete expression
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.NotEmpty(syntaxTree.Errors);
|
||||
Assert.IsType<BinaryExpressionSyntaxNode>(syntaxTree.Root);
|
||||
if (syntaxTree.Root is BinaryExpressionSyntaxNode binaryNode)
|
||||
{
|
||||
Assert.Equal(BinaryOperator.Plus, binaryNode.Operator);
|
||||
Assert.IsType<NumberLiteralSyntaxNode>(binaryNode.Left);
|
||||
Assert.Equal(123, ((NumberLiteralSyntaxNode)binaryNode.Left).Token.IntValue);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldAddErrorOnIncompleteExpression()
|
||||
{
|
||||
var formula = "=123+"; // Incomplete expression
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.NotEmpty(syntaxTree.Errors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldReturnPartialFunctionOnMissingCloseParen()
|
||||
{
|
||||
var formula = "=SUM(A1"; // Missing closing parenthesis
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.NotEmpty(syntaxTree.Errors);
|
||||
Assert.IsType<FunctionSyntaxNode>(syntaxTree.Root);
|
||||
var functionNode = (FunctionSyntaxNode)syntaxTree.Root;
|
||||
Assert.Equal("SUM", functionNode.Name);
|
||||
Assert.Single(functionNode.Arguments);
|
||||
Assert.IsType<CellSyntaxNode>(functionNode.Arguments[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldAddErrorOnInvalidFunctionSyntax()
|
||||
{
|
||||
var formula = "=SUM(A1"; // Missing closing parenthesis
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.NotEmpty(syntaxTree.Errors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldParseGroupedExpression()
|
||||
{
|
||||
var formula = "=(A1)"; // Parentheses without function name should parse as grouped expression
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.Empty(syntaxTree.Errors); // This should actually succeed as it's a valid grouped expression
|
||||
Assert.IsType<CellSyntaxNode>(syntaxTree.Root);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldReturnPartialRangeOnIncompleteRange()
|
||||
{
|
||||
var formula = "=A1:"; // Incomplete range
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.NotEmpty(syntaxTree.Errors);
|
||||
Assert.NotNull(syntaxTree.Root);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldAddErrorOnInvalidRange()
|
||||
{
|
||||
var formula = "=A1:"; // Incomplete range
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.NotEmpty(syntaxTree.Errors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldHandleUnterminatedString()
|
||||
{
|
||||
var formula = "=\"hello"; // Unterminated string literal
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.Empty(syntaxTree.Errors); // Should succeed as lexer handles unterminated strings
|
||||
Assert.IsType<StringLiteralSyntaxNode>(syntaxTree.Root);
|
||||
var stringNode = (StringLiteralSyntaxNode)syntaxTree.Root;
|
||||
Assert.Equal("hello", stringNode.Token.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldHandleMissingOperand()
|
||||
{
|
||||
var formula = "=*5"; // Missing left operand
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.NotEmpty(syntaxTree.Errors); // Should have errors
|
||||
Assert.NotNull(syntaxTree.Root);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldReturnPartialExpressionOnUnbalancedParentheses()
|
||||
{
|
||||
var formula = "=(A1+B1"; // Missing closing parenthesis
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.NotEmpty(syntaxTree.Errors);
|
||||
Assert.IsType<BinaryExpressionSyntaxNode>(syntaxTree.Root); // Should return the binary expression inside
|
||||
var binaryNode = (BinaryExpressionSyntaxNode)syntaxTree.Root;
|
||||
Assert.Equal(BinaryOperator.Plus, binaryNode.Operator);
|
||||
Assert.IsType<CellSyntaxNode>(binaryNode.Left);
|
||||
Assert.IsType<CellSyntaxNode>(binaryNode.Right);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldAddErrorOnUnbalancedParentheses()
|
||||
{
|
||||
var formula = "=(A1+B1"; // Missing closing parenthesis
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.NotEmpty(syntaxTree.Errors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldReturnPartialFunctionOnIncompleteArguments()
|
||||
{
|
||||
var formula = "=SUM(A1,"; // Incomplete function arguments
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.NotEmpty(syntaxTree.Errors);
|
||||
Assert.IsType<FunctionSyntaxNode>(syntaxTree.Root);
|
||||
var functionNode = (FunctionSyntaxNode)syntaxTree.Root;
|
||||
Assert.Equal("SUM", functionNode.Name);
|
||||
Assert.True(functionNode.Arguments.Count >= 1); // Should have at least the first argument
|
||||
Assert.IsType<CellSyntaxNode>(functionNode.Arguments[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_DefaultBehavior_ShouldStillWork()
|
||||
{
|
||||
// Test that default behavior still works as before
|
||||
var formula = "=123+456";
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.Empty(syntaxTree.Errors);
|
||||
var node = syntaxTree.Root;
|
||||
Assert.NotNull(node);
|
||||
Assert.IsType<BinaryExpressionSyntaxNode>(node);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldParseBooleanTrue()
|
||||
{
|
||||
var formula = "=TRUE";
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.Empty(syntaxTree.Errors);
|
||||
Assert.IsType<BooleanLiteralSyntaxNode>(syntaxTree.Root);
|
||||
var boolNode = (BooleanLiteralSyntaxNode)syntaxTree.Root;
|
||||
Assert.Equal("TRUE", boolNode.Token.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldParseBooleanFalse_Lowercase()
|
||||
{
|
||||
var formula = "=false";
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.Empty(syntaxTree.Errors);
|
||||
Assert.IsType<BooleanLiteralSyntaxNode>(syntaxTree.Root);
|
||||
var boolNode = (BooleanLiteralSyntaxNode)syntaxTree.Root;
|
||||
Assert.Equal("false", boolNode.Token.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldParseBooleanInFunction()
|
||||
{
|
||||
var formula = "=TEXTJOIN(\", \", TRUE, A1:A2)";
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.Empty(syntaxTree.Errors);
|
||||
var fn = Assert.IsType<FunctionSyntaxNode>(syntaxTree.Root);
|
||||
Assert.Equal("TEXTJOIN", fn.Name);
|
||||
Assert.IsType<BooleanLiteralSyntaxNode>(fn.Arguments[1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormulaParser_ShouldParse_Percent()
|
||||
{
|
||||
var formula = "=$%";
|
||||
var syntaxTree = FormulaParser.Parse(formula);
|
||||
Assert.NotEmpty(syntaxTree.Errors);
|
||||
}
|
||||
}
|
||||
74
Radzen.Blazor.Tests/Spreadsheet/FunctionRegistryTests.cs
Normal file
74
Radzen.Blazor.Tests/Spreadsheet/FunctionRegistryTests.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class FunctionRegistryTests
|
||||
{
|
||||
private readonly FunctionStore functionRegistry = new();
|
||||
|
||||
[Theory]
|
||||
[InlineData(2, -1)]
|
||||
[InlineData(4, -1)]
|
||||
[InlineData(5, 0)]
|
||||
[InlineData(6, 0)]
|
||||
[InlineData(7, 1)]
|
||||
[InlineData(8, 1)]
|
||||
[InlineData(9, 2)]
|
||||
[InlineData(10, 2)]
|
||||
public void Basic_Function_Provides_Correct_Arg_Index(int cursorPosition, int expectedArgIndex)
|
||||
{
|
||||
var func = "=SUM(1,2,3)";
|
||||
var result = functionRegistry.CreateFunctionHint(func, cursorPosition);
|
||||
|
||||
Assert.NotNull(result);
|
||||
|
||||
Assert.Equal(expectedArgIndex, result.ArgumentIndex);
|
||||
Assert.IsType<SumFunction>(result.Function);
|
||||
}
|
||||
[Theory]
|
||||
[InlineData(5, 0, "=SUM(")]
|
||||
[InlineData(4, -1, "=SUM(")]
|
||||
public void Basic_Function_Provides_Correct_Arg_Index_With_IncompleteFormula(int cursorPosition, int expectedArgIndex, string formula)
|
||||
{
|
||||
var result = functionRegistry.CreateFunctionHint(formula, cursorPosition);
|
||||
|
||||
Assert.NotNull(result);
|
||||
|
||||
Assert.Equal(expectedArgIndex, result.ArgumentIndex);
|
||||
Assert.IsType<SumFunction>(result.Function);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Position_Outside_Of_Formula_Returns_null()
|
||||
{
|
||||
var func = "=1 + SUM(1,2, 3) + 2";
|
||||
var result = functionRegistry.CreateFunctionHint(func, 0);
|
||||
|
||||
Assert.Null(result);
|
||||
|
||||
result = functionRegistry.CreateFunctionHint(func, 5);
|
||||
Assert.Null(result);
|
||||
|
||||
result = functionRegistry.CreateFunctionHint(func, 16);
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(5, 0, typeof(SumFunction))]
|
||||
[InlineData(7, 1, typeof(SumFunction))]
|
||||
[InlineData(8, -1, typeof(CountFunction))]
|
||||
[InlineData(13, 0, typeof(CountFunction))]
|
||||
[InlineData(15, 1, typeof(CountFunction))]
|
||||
[InlineData(17, 1, typeof(SumFunction))]
|
||||
public void Nested_Function_Produces_Correct_ArgIndex(int cursorPosition, int expectedArgIndex, Type expectedFunction)
|
||||
{
|
||||
var func = "=SUM(1,COUNT(1,2),3)";
|
||||
var result = functionRegistry.CreateFunctionHint(func, cursorPosition);
|
||||
|
||||
Assert.NotNull(result);
|
||||
|
||||
Assert.Equal(expectedArgIndex, result.ArgumentIndex);
|
||||
Assert.IsType(expectedFunction, result.Function);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class HorizontalLookupFunctionTests
|
||||
{
|
||||
readonly Sheet sheet = new(10, 10);
|
||||
|
||||
[Fact]
|
||||
public void ShouldFindExactMatchInTwoRowRange()
|
||||
{
|
||||
sheet.Cells["A1"].Value = "Size";
|
||||
sheet.Cells["B1"].Value = "Color";
|
||||
sheet.Cells["A2"].Value = "M";
|
||||
sheet.Cells["B2"].Value = "Blue";
|
||||
|
||||
sheet.Cells["C1"].Formula = "=HLOOKUP(\"Color\",A1:B2,2,0)";
|
||||
|
||||
Assert.Equal("Blue", sheet.Cells["C1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnNAWhenNoExactMatch()
|
||||
{
|
||||
sheet.Cells["A1"].Value = "Size";
|
||||
sheet.Cells["A2"].Value = "M";
|
||||
|
||||
sheet.Cells["B1"].Formula = "=HLOOKUP(\"Color\",A1:A2,2,0)";
|
||||
|
||||
Assert.Equal(CellError.NA, sheet.Cells["B1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldFindApproximateMatchInSortedTopRow()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["B1"].Value = 20;
|
||||
sheet.Cells["C1"].Value = 30;
|
||||
|
||||
sheet.Cells["A2"].Value = "Low";
|
||||
sheet.Cells["B2"].Value = "Medium";
|
||||
sheet.Cells["C2"].Value = "High";
|
||||
|
||||
sheet.Cells["D1"].Formula = "=HLOOKUP(25,A1:C2,2,1)";
|
||||
|
||||
Assert.Equal("Medium", sheet.Cells["D1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldErrorWhenIndexOutOfRange()
|
||||
{
|
||||
sheet.Cells["A1"].Value = "X";
|
||||
sheet.Cells["A2"].Value = 1;
|
||||
|
||||
sheet.Cells["B1"].Formula = "=HLOOKUP(\"X\",A1:A2,3,0)";
|
||||
|
||||
Assert.Equal(CellError.Ref, sheet.Cells["B1"].Value);
|
||||
}
|
||||
}
|
||||
33
Radzen.Blazor.Tests/Spreadsheet/HourFunctionTests.cs
Normal file
33
Radzen.Blazor.Tests/Spreadsheet/HourFunctionTests.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class HourFunctionTests
|
||||
{
|
||||
[Fact]
|
||||
public void Hour_FromFraction_Returns18()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A2"].Data = CellData.FromNumber(0.75); // 18:00
|
||||
sheet.Cells["B2"].Formula = "=HOUR(A2)";
|
||||
Assert.Equal(18, sheet.Cells["B2"].Data.GetValueOrDefault<double>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Hour_FromDateTimeValue_ReturnsHour()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A3"].Data = CellData.FromDate(new System.DateTime(2011, 7, 18, 7, 45, 0));
|
||||
sheet.Cells["B3"].Formula = "=HOUR(A3)";
|
||||
Assert.Equal(7, sheet.Cells["B3"].Data.GetValueOrDefault<double>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Hour_FromDateOnly_ReturnsZero()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A4"].Data = CellData.FromDate(new System.DateTime(2012, 4, 21));
|
||||
sheet.Cells["B4"].Formula = "=HOUR(A4)";
|
||||
Assert.Equal(0, sheet.Cells["B4"].Data.GetValueOrDefault<double>());
|
||||
}
|
||||
}
|
||||
138
Radzen.Blazor.Tests/Spreadsheet/IfErrorFunctionTests.cs
Normal file
138
Radzen.Blazor.Tests/Spreadsheet/IfErrorFunctionTests.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class IfErrorFunctionTests
|
||||
{
|
||||
readonly Sheet sheet = new(5, 5);
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateIfErrorFunctionWithNoError()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = 2;
|
||||
sheet.Cells["A3"].Formula = "=IFERROR(A1/A2, \"Error in calculation\")";
|
||||
|
||||
Assert.Equal(5d, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateIfErrorFunctionWithDivisionByZero()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = 0;
|
||||
sheet.Cells["A3"].Formula = "=IFERROR(A1/A2, \"Error in calculation\")";
|
||||
|
||||
Assert.Equal("Error in calculation", sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateIfErrorFunctionWithReferenceError()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=IFERROR(A6, \"Error in calculation\")";
|
||||
|
||||
Assert.Equal("Error in calculation", sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateIfErrorFunctionWithEmptyStringForError()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = 0;
|
||||
sheet.Cells["A3"].Formula = "=IFERROR(A1/A2, \"\")";
|
||||
|
||||
Assert.Equal("", sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateIfErrorFunctionWithNumericErrorValue()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = 0;
|
||||
sheet.Cells["A3"].Formula = "=IFERROR(A1/A2, 0)";
|
||||
|
||||
Assert.Equal(0d, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateIfErrorFunctionWithEmptyCellAsErrorValue()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = 0;
|
||||
sheet.Cells["A3"].Formula = "=IFERROR(A1/A2, A4)";
|
||||
|
||||
Assert.Equal("", sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateIfErrorFunctionWithEmptyCellAsValue()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=IFERROR(A2, \"Empty cell\")";
|
||||
|
||||
Assert.Equal("", sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateIfErrorFunctionWithStringValue()
|
||||
{
|
||||
sheet.Cells["A1"].Value = "Hello";
|
||||
sheet.Cells["A2"].Formula = "=IFERROR(A1, \"Error\")";
|
||||
|
||||
Assert.Equal("Hello", sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateIfErrorFunctionWithBooleanValue()
|
||||
{
|
||||
sheet.Cells["A1"].Value = true;
|
||||
sheet.Cells["A2"].Formula = "=IFERROR(A1, \"Error\")";
|
||||
|
||||
Assert.Equal(true, sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnValueErrorForIfErrorTooFewArguments()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=IFERROR(A2)";
|
||||
|
||||
Assert.Equal(CellError.Value, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnValueErrorForIfErrorTooManyArguments()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=IFERROR(A2, \"Error\", \"Extra\")";
|
||||
|
||||
Assert.Equal(CellError.Value, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateIfErrorFunctionWithNestedFormula()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = 0;
|
||||
sheet.Cells["A3"].Formula = "=IFERROR(A1/A2, IFERROR(A1/0, \"Nested Error\"))";
|
||||
|
||||
Assert.Equal("Nested Error", sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateIfErrorFunctionWithSumFunction()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = 20;
|
||||
sheet.Cells["A3"].Formula = "=IFERROR(SUM(A1:A2), \"Error\")";
|
||||
|
||||
Assert.Equal(30d, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateIfErrorFunctionWithSumFunctionError()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=IFERROR(SUM(A6:A8), \"Error\")";
|
||||
|
||||
Assert.Equal("Error", sheet.Cells["A1"].Value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
178
Radzen.Blazor.Tests/Spreadsheet/IfFunctionTests.cs
Normal file
178
Radzen.Blazor.Tests/Spreadsheet/IfFunctionTests.cs
Normal file
@@ -0,0 +1,178 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class IfFunctionTests
|
||||
{
|
||||
readonly Sheet sheet = new(5, 5);
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateIfFunctionWithTrueCondition()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 1;
|
||||
sheet.Cells["A2"].Formula = "=IF(A1=1,\"Yes\",\"No\")";
|
||||
|
||||
Assert.Equal("Yes", sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateIfFunctionWithFalseCondition()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 2;
|
||||
sheet.Cells["A2"].Formula = "=IF(A1=1,\"Yes\",\"No\")";
|
||||
|
||||
Assert.Equal("No", sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateIfFunctionWithNumericComparison()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = 5;
|
||||
sheet.Cells["A3"].Formula = "=IF(A1>A2,\"Over Budget\",\"Within Budget\")";
|
||||
|
||||
Assert.Equal("Over Budget", sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateIfFunctionWithNumericResult()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = 5;
|
||||
sheet.Cells["A3"].Formula = "=IF(A1>A2,A1-A2,0)";
|
||||
|
||||
Assert.Equal(5d, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateIfFunctionWithTwoArguments()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 1;
|
||||
sheet.Cells["A2"].Formula = "=IF(A1=1,\"True\")";
|
||||
|
||||
Assert.Equal("True", sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateIfFunctionWithTwoArgumentsFalseCondition()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 0;
|
||||
sheet.Cells["A2"].Formula = "=IF(A1=1,\"True\")";
|
||||
|
||||
Assert.Equal(false, sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateIfFunctionWithZeroAsFalse()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 0;
|
||||
sheet.Cells["A2"].Formula = "=IF(A1,\"True\",\"False\")";
|
||||
|
||||
Assert.Equal("False", sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateIfFunctionWithNonZeroAsTrue()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 5;
|
||||
sheet.Cells["A2"].Formula = "=IF(A1,\"True\",\"False\")";
|
||||
|
||||
Assert.Equal("True", sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateToErrorIfFunctionWithStringCondition()
|
||||
{
|
||||
sheet.Cells["A1"].Value = "test";
|
||||
sheet.Cells["A2"].Formula = "=IF(A1,\"Not Empty\",\"Empty\")";
|
||||
|
||||
Assert.Equal(CellError.Value, sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateIfFunctionWithEmptyStringCondition()
|
||||
{
|
||||
sheet.Cells["A2"].Formula = "=IF(A1,\"Not Empty\",\"Empty\")";
|
||||
|
||||
Assert.Equal("Empty", sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateIfFunctionWithNullCondition()
|
||||
{
|
||||
sheet.Cells["A2"].Formula = "=IF(A1,\"Not Empty\",\"Empty\")";
|
||||
|
||||
Assert.Equal("Empty", sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnValueErrorForTooFewArguments()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=IF(A2)";
|
||||
|
||||
Assert.Equal(CellError.Value, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnValueErrorForTooManyArguments()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=IF(A2,\"True\",\"False\",\"Extra\")";
|
||||
|
||||
Assert.Equal(CellError.Value, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldPropagateErrorFromCondition()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=IF(A6,\"True\",\"False\")";
|
||||
|
||||
Assert.Equal(CellError.Ref, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldPropagateErrorFromTrueValue()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 1;
|
||||
sheet.Cells["A2"].Formula = "=IF(A1=1,A6,\"False\")";
|
||||
|
||||
Assert.Equal(CellError.Ref, sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldPropagateErrorFromFalseValue()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 0;
|
||||
sheet.Cells["A2"].Formula = "=IF(A1=1,\"True\",A6)";
|
||||
|
||||
Assert.Equal(CellError.Ref, sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateNestedIfFunction()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 85;
|
||||
sheet.Cells["A2"].Formula = "=IF(A1>=90,\"A\",IF(A1>=80,\"B\",IF(A1>=70,\"C\",\"F\")))";
|
||||
|
||||
Assert.Equal("B", sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateIfFunctionWithBooleanValues()
|
||||
{
|
||||
sheet.Cells["A1"].Value = true;
|
||||
sheet.Cells["A2"].Formula = "=IF(A1,\"True\",\"False\")";
|
||||
|
||||
Assert.Equal("True", sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateIfFunctionWithDecimalValues()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 0.5m;
|
||||
sheet.Cells["A2"].Formula = "=IF(A1,\"True\",\"False\")";
|
||||
|
||||
Assert.Equal("True", sheet.Cells["A2"].Value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
100
Radzen.Blazor.Tests/Spreadsheet/IndexFunctionTests.cs
Normal file
100
Radzen.Blazor.Tests/Spreadsheet/IndexFunctionTests.cs
Normal file
@@ -0,0 +1,100 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class IndexFunctionTests
|
||||
{
|
||||
readonly Sheet sheet = new(10, 10);
|
||||
|
||||
void Seed()
|
||||
{
|
||||
sheet.Cells["A2"].Value = "Apples";
|
||||
sheet.Cells["B2"].Value = "Lemons";
|
||||
sheet.Cells["A3"].Value = "Bananas";
|
||||
sheet.Cells["B3"].Value = "Pears";
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnIntersectionValue()
|
||||
{
|
||||
Seed();
|
||||
|
||||
sheet.Cells["C1"].Formula = "=INDEX(A2:B3,2,2)";
|
||||
Assert.Equal("Pears", sheet.Cells["C1"].Value);
|
||||
|
||||
sheet.Cells["C2"].Formula = "=INDEX(A2:B3,2,1)";
|
||||
Assert.Equal("Bananas", sheet.Cells["C2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnRefErrorIfOutOfRange()
|
||||
{
|
||||
// numeric values just to ensure range exists
|
||||
sheet.Cells["A1"].Value = 1;
|
||||
sheet.Cells["B1"].Value = 2;
|
||||
sheet.Cells["A2"].Value = 3;
|
||||
sheet.Cells["B2"].Value = 4;
|
||||
|
||||
sheet.Cells["C1"].Formula = "=INDEX(A1:B2,3,1)"; // row 3 out of 2 rows
|
||||
Assert.Equal(CellError.Ref, sheet.Cells["C1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldDefaultColumnToFirstWhenOmitted()
|
||||
{
|
||||
Seed();
|
||||
sheet.Cells["C1"].Formula = "=INDEX(A2:B3,2)"; // column omitted -> first column
|
||||
Assert.Equal("Bananas", sheet.Cells["C1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldUseAreaOneWhenSpecified()
|
||||
{
|
||||
Seed();
|
||||
sheet.Cells["C1"].Formula = "=INDEX(A2:B3,2,2,1)";
|
||||
Assert.Equal("Pears", sheet.Cells["C1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnValueErrorWhenAreaGreaterThanOne()
|
||||
{
|
||||
Seed();
|
||||
sheet.Cells["C1"].Formula = "=INDEX(A2:B3,1,1,2)";
|
||||
Assert.Equal(CellError.Value, sheet.Cells["C1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnFirstOfEntireColumnWhenRowIsZero()
|
||||
{
|
||||
Seed();
|
||||
sheet.Cells["C1"].Formula = "=INDEX(A2:B3,0,2)";
|
||||
Assert.Equal("Lemons", sheet.Cells["C1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnFirstOfEntireRowWhenColumnIsZero()
|
||||
{
|
||||
Seed();
|
||||
sheet.Cells["C1"].Formula = "=INDEX(A2:B3,2,0)";
|
||||
Assert.Equal("Bananas", sheet.Cells["C1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnValueErrorWhenBothRowAndColumnOmitted()
|
||||
{
|
||||
Seed();
|
||||
sheet.Cells["C1"].Formula = "=INDEX(A2:B3)";
|
||||
Assert.Equal(CellError.Value, sheet.Cells["C1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnRefErrorOnNegativeIndices()
|
||||
{
|
||||
Seed();
|
||||
sheet.Cells["C1"].Formula = "=INDEX(A2:B3,0-1,1)"; // -1
|
||||
Assert.Equal(CellError.Ref, sheet.Cells["C1"].Value);
|
||||
|
||||
sheet.Cells["C2"].Formula = "=INDEX(A2:B3,1,0-1)"; // -1
|
||||
Assert.Equal(CellError.Ref, sheet.Cells["C2"].Value);
|
||||
}
|
||||
}
|
||||
64
Radzen.Blazor.Tests/Spreadsheet/InsertRowColumnTests.cs
Normal file
64
Radzen.Blazor.Tests/Spreadsheet/InsertRowColumnTests.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class InsertRowColumnTests
|
||||
{
|
||||
[Fact]
|
||||
public void InsertColumn_ShiftsReferencesAndValues()
|
||||
{
|
||||
var sheet = new Sheet(5, 5);
|
||||
|
||||
sheet.Cells[1, 0].Value = 1; // A2
|
||||
sheet.Cells[1, 1].Formula = "=A2+10"; // B2
|
||||
Assert.Equal(11d, sheet.Cells[1, 1].Value);
|
||||
|
||||
// Insert a column before A (index 0)
|
||||
sheet.InsertColumn(0, 1);
|
||||
|
||||
// Values shift right
|
||||
Assert.Equal(1d, sheet.Cells[1, 1].Value); // A2 moved to B2
|
||||
|
||||
// Formula shifts position and updates referenced address
|
||||
Assert.Equal("=B2+10", sheet.Cells[1, 2].Formula); // original B2 moved to C2
|
||||
Assert.Equal(11d, sheet.Cells[1, 2].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InsertRow_ShiftsReferencesAndValues()
|
||||
{
|
||||
var sheet = new Sheet(5, 5);
|
||||
|
||||
sheet.Cells[1, 0].Value = 1; // A2
|
||||
sheet.Cells[1, 1].Formula = "=A2+10"; // B2
|
||||
Assert.Equal(11d, sheet.Cells[1, 1].Value);
|
||||
|
||||
// Insert a row before row 2 (index 1)
|
||||
sheet.InsertRow(1, 1);
|
||||
|
||||
// Values shift down
|
||||
Assert.Equal(1d, sheet.Cells[2, 0].Value); // A2 moved to A3
|
||||
|
||||
// Formula shifts position and updates referenced address
|
||||
Assert.Equal("=A3+10", sheet.Cells[2, 1].Formula); // original B2 moved to B3
|
||||
Assert.Equal(11d, sheet.Cells[2, 1].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InsertRow_IncreasesRowCount()
|
||||
{
|
||||
var sheet = new Sheet(5, 5);
|
||||
sheet.InsertRow(2, 2);
|
||||
Assert.Equal(7, sheet.RowCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InsertColumn_IncreasesColumnCount()
|
||||
{
|
||||
var sheet = new Sheet(5, 5);
|
||||
sheet.InsertColumn(3, 3);
|
||||
Assert.Equal(8, sheet.ColumnCount);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
30
Radzen.Blazor.Tests/Spreadsheet/IntFunctionTests.cs
Normal file
30
Radzen.Blazor.Tests/Spreadsheet/IntFunctionTests.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class IntFunctionTests
|
||||
{
|
||||
readonly Sheet sheet = new(10, 10);
|
||||
|
||||
[Fact]
|
||||
public void ShouldRoundDownPositive()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=INT(8.9)";
|
||||
Assert.Equal(8d, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldRoundDownNegative()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=INT(0-8.9)";
|
||||
Assert.Equal(-9d, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnDecimalPart()
|
||||
{
|
||||
sheet.Cells["A2"].Value = 19.5;
|
||||
sheet.Cells["A1"].Formula = "=A2-INT(A2)";
|
||||
Assert.Equal(0.5, sheet.Cells["A1"].Value);
|
||||
}
|
||||
}
|
||||
74
Radzen.Blazor.Tests/Spreadsheet/LargeFunctionTests.cs
Normal file
74
Radzen.Blazor.Tests/Spreadsheet/LargeFunctionTests.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class LargeFunctionTests
|
||||
{
|
||||
readonly Sheet sheet = new(10, 10);
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnKthLargestAcrossRange()
|
||||
{
|
||||
// Populate A2:B6 as in example
|
||||
sheet.Cells["A2"].Value = 3;
|
||||
sheet.Cells["A3"].Value = 4;
|
||||
sheet.Cells["A4"].Value = 5;
|
||||
sheet.Cells["A5"].Value = 2;
|
||||
sheet.Cells["A6"].Value = 3;
|
||||
|
||||
sheet.Cells["B2"].Value = 4;
|
||||
sheet.Cells["B3"].Value = 5;
|
||||
sheet.Cells["B4"].Value = 6;
|
||||
sheet.Cells["B5"].Value = 4;
|
||||
sheet.Cells["B6"].Value = 7;
|
||||
|
||||
sheet.Cells["C1"].Formula = "=LARGE(A2:B6,3)";
|
||||
|
||||
Assert.Equal(5d, sheet.Cells["C1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturn7thLargestAsFour()
|
||||
{
|
||||
sheet.Cells["A2"].Value = 3;
|
||||
sheet.Cells["A3"].Value = 4;
|
||||
sheet.Cells["A4"].Value = 5;
|
||||
sheet.Cells["A5"].Value = 2;
|
||||
sheet.Cells["A6"].Value = 3;
|
||||
|
||||
sheet.Cells["B2"].Value = 4;
|
||||
sheet.Cells["B3"].Value = 5;
|
||||
sheet.Cells["B4"].Value = 6;
|
||||
sheet.Cells["B5"].Value = 4;
|
||||
sheet.Cells["B6"].Value = 7;
|
||||
|
||||
sheet.Cells["C1"].Formula = "=LARGE(A2:B6,7)";
|
||||
|
||||
Assert.Equal(4d, sheet.Cells["C1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnNumErrorForInvalidK()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 1;
|
||||
sheet.Cells["A2"].Value = 2;
|
||||
sheet.Cells["A3"].Value = 3;
|
||||
|
||||
sheet.Cells["B1"].Formula = "=LARGE(A1:A3,0)";
|
||||
Assert.Equal(CellError.Num, sheet.Cells["B1"].Value);
|
||||
|
||||
sheet.Cells["B2"].Formula = "=LARGE(A1:A3,5)";
|
||||
Assert.Equal(CellError.Num, sheet.Cells["B2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldIgnoreNonNumericCellsInArray()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = "x"; // ignored
|
||||
sheet.Cells["A3"].Value = 7;
|
||||
|
||||
sheet.Cells["B1"].Formula = "=LARGE(A1:A3,2)";
|
||||
Assert.Equal(7d, sheet.Cells["B1"].Value);
|
||||
}
|
||||
}
|
||||
42
Radzen.Blazor.Tests/Spreadsheet/LeftFunctionTests.cs
Normal file
42
Radzen.Blazor.Tests/Spreadsheet/LeftFunctionTests.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class LeftFunctionTests
|
||||
{
|
||||
[Fact]
|
||||
public void Left_WithCount_ReturnsPrefix()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A2"].Value = "Sale Price";
|
||||
sheet.Cells["B1"].Formula = "=LEFT(A2,4)";
|
||||
Assert.Equal("Sale", sheet.Cells["B1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Left_OmittedCount_DefaultsToOne()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A3"].Value = "Sweden";
|
||||
sheet.Cells["B1"].Formula = "=LEFT(A3)";
|
||||
Assert.Equal("S", sheet.Cells["B1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Left_CountExceedsLength_ReturnsWhole()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A1"].Value = "Hi";
|
||||
sheet.Cells["B1"].Formula = "=LEFT(A1,5)";
|
||||
Assert.Equal("Hi", sheet.Cells["B1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Left_NegativeCount_ReturnsValueError()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A1"].Value = "Test";
|
||||
sheet.Cells["B1"].Formula = "=LEFT(A1,-1)";
|
||||
Assert.Equal(CellError.Value, sheet.Cells["B1"].Data.GetValueOrDefault<CellError>());
|
||||
}
|
||||
}
|
||||
59
Radzen.Blazor.Tests/Spreadsheet/LenFunctionTests.cs
Normal file
59
Radzen.Blazor.Tests/Spreadsheet/LenFunctionTests.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class LenFunctionTests
|
||||
{
|
||||
[Fact]
|
||||
public void Len_String_ReturnsLength()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A1"].Value = "Phoenix, AZ"; // 11 characters
|
||||
sheet.Cells["B1"].Formula = "=LEN(A1)";
|
||||
Assert.Equal(11d, sheet.Cells["B1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Len_BooleanCellTrue_ReturnsFour()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A1"].Value = true;
|
||||
sheet.Cells["B1"].Formula = "=LEN(A1)";
|
||||
Assert.Equal(4d, sheet.Cells["B1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Len_BooleanCellFalse_ReturnsFive()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A1"].Value = false;
|
||||
sheet.Cells["B1"].Formula = "=LEN(A1)";
|
||||
Assert.Equal(5d, sheet.Cells["B1"].Data.Value);
|
||||
}
|
||||
[Fact]
|
||||
public void Len_Empty_ReturnsZero()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A1"].Value = null; // empty
|
||||
sheet.Cells["B1"].Formula = "=LEN(A1)";
|
||||
Assert.Equal(0d, sheet.Cells["B1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Len_String_WithSpaces_CountsSpaces()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A1"].Value = " One "; // 11 characters including spaces
|
||||
sheet.Cells["B1"].Formula = "=LEN(A1)";
|
||||
Assert.Equal(11d, sheet.Cells["B1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Len_Number_TreatsAsTextLength()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A1"].Value = 123.45; // "123.45" length 6
|
||||
sheet.Cells["B1"].Formula = "=LEN(A1)";
|
||||
Assert.Equal(6d, sheet.Cells["B1"].Data.Value);
|
||||
}
|
||||
}
|
||||
24
Radzen.Blazor.Tests/Spreadsheet/LowerFunctionTests.cs
Normal file
24
Radzen.Blazor.Tests/Spreadsheet/LowerFunctionTests.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class LowerFunctionTests
|
||||
{
|
||||
[Fact]
|
||||
public void Lower_ConvertsToLowercase()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A2"].Value = "E. E. Cummings";
|
||||
sheet.Cells["B1"].Formula = "=LOWER(A2)";
|
||||
Assert.Equal("e. e. cummings", sheet.Cells["B1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Lower_IgnoresNonLetters()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A3"].Value = "Apt. 2B";
|
||||
sheet.Cells["B1"].Formula = "=LOWER(A3)";
|
||||
Assert.Equal("apt. 2b", sheet.Cells["B1"].Data.Value);
|
||||
}
|
||||
}
|
||||
64
Radzen.Blazor.Tests/Spreadsheet/MaxAllFunctionTests.cs
Normal file
64
Radzen.Blazor.Tests/Spreadsheet/MaxAllFunctionTests.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class MaxAllFunctionTests
|
||||
{
|
||||
readonly Sheet sheet = new(10, 10);
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateLogicalValuesInRange()
|
||||
{
|
||||
sheet.Cells["A1"].Value = true; // 1
|
||||
sheet.Cells["A2"].Value = false; // 0
|
||||
sheet.Cells["A3"].Value = 5; // 5
|
||||
|
||||
sheet.Cells["B1"].Formula = "=MAXA(A1:A3)";
|
||||
|
||||
Assert.Equal(5d, sheet.Cells["B1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldTreatTextInRangeAsZeroAndNumericTextAsNumber()
|
||||
{
|
||||
sheet.Cells["A1"].Value = "abc"; // -> 0
|
||||
sheet.Cells["A2"].Value = "15"; // -> 15
|
||||
sheet.Cells["A3"].Value = 10;
|
||||
|
||||
sheet.Cells["B1"].Formula = "=MAXA(A1:A3)";
|
||||
|
||||
Assert.Equal(15d, sheet.Cells["B1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldCountDirectLogicalAndTextArguments()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=MAXA(1=1, \"7\", 1=2)"; // TRUE, "7", FALSE -> 7
|
||||
Assert.Equal(7d, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnZeroWhenNoValues()
|
||||
{
|
||||
sheet.Cells["A1"].Value = null; // empty
|
||||
sheet.Cells["A2"].Value = ""; // empty string -> Empty
|
||||
|
||||
sheet.Cells["B1"].Formula = "=MAXA(A1:A2)";
|
||||
|
||||
Assert.Equal(0d, sheet.Cells["B1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldPropagateErrors()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = 0;
|
||||
sheet.Cells["A3"].Formula = "=A1/A2"; // #DIV/0!
|
||||
|
||||
sheet.Cells["B1"].Formula = "=MAXA(A1:A3)";
|
||||
|
||||
Assert.Equal(CellError.Div0, sheet.Cells["B1"].Value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
69
Radzen.Blazor.Tests/Spreadsheet/MaxFunctionTests.cs
Normal file
69
Radzen.Blazor.Tests/Spreadsheet/MaxFunctionTests.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class MaxFunctionTests
|
||||
{
|
||||
readonly Sheet sheet = new(10, 10);
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnLargestValueFromNumbers()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = 7;
|
||||
sheet.Cells["A3"].Value = 9;
|
||||
sheet.Cells["A4"].Value = 27;
|
||||
sheet.Cells["A5"].Value = 2;
|
||||
|
||||
sheet.Cells["B1"].Formula = "=MAX(A1:A5)";
|
||||
|
||||
Assert.Equal(27d, sheet.Cells["B1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnLargestValueFromRangeAndLiteral()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = 7;
|
||||
sheet.Cells["A3"].Value = 9;
|
||||
sheet.Cells["A4"].Value = 27;
|
||||
sheet.Cells["A5"].Value = 2;
|
||||
|
||||
sheet.Cells["B1"].Formula = "=MAX(A1:A5,30)";
|
||||
|
||||
Assert.Equal(30d, sheet.Cells["B1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldIgnoreNonNumericInRange()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = "text";
|
||||
sheet.Cells["A3"].Value = true;
|
||||
sheet.Cells["A4"].Value = 27;
|
||||
sheet.Cells["A5"].Value = null;
|
||||
|
||||
sheet.Cells["B1"].Formula = "=MAX(A1:A5)";
|
||||
|
||||
Assert.Equal(27d, sheet.Cells["B1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldTreatDirectLogicalAndNumericStringsAsNumbers()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=MAX(\"15\", 5, 10)";
|
||||
Assert.Equal(15d, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnZeroWhenNoNumbers()
|
||||
{
|
||||
sheet.Cells["A1"].Value = "a";
|
||||
sheet.Cells["A2"].Value = false;
|
||||
sheet.Cells["A3"].Formula = "=MAX(A1:A2)";
|
||||
|
||||
Assert.Equal(0d, sheet.Cells["A3"].Value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
51
Radzen.Blazor.Tests/Spreadsheet/MidFunctionTests.cs
Normal file
51
Radzen.Blazor.Tests/Spreadsheet/MidFunctionTests.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class MidFunctionTests
|
||||
{
|
||||
[Fact]
|
||||
public void Mid_Start1_Take5()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A2"].Value = "Fluid Flow"; // length 10
|
||||
sheet.Cells["B1"].Formula = "=MID(A2,1,5)";
|
||||
Assert.Equal("Fluid", sheet.Cells["B1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Mid_Start7_Take20_Clamped()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A2"].Value = "Fluid Flow"; // length 10
|
||||
sheet.Cells["B1"].Formula = "=MID(A2,7,20)";
|
||||
Assert.Equal("Flow", sheet.Cells["B1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Mid_StartBeyondLength_ReturnsEmpty()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A2"].Value = "Fluid Flow"; // length 10
|
||||
sheet.Cells["B1"].Formula = "=MID(A2,20,5)";
|
||||
Assert.Equal(string.Empty, sheet.Cells["B1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Mid_StartLessThan1_ReturnsValueError()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A2"].Value = "Fluid Flow";
|
||||
sheet.Cells["B1"].Formula = "=MID(A2,0,5)";
|
||||
Assert.Equal(CellError.Value, sheet.Cells["B1"].Data.GetValueOrDefault<CellError>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Mid_NegativeNumChars_ReturnsValueError()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A2"].Value = "Fluid Flow";
|
||||
sheet.Cells["B1"].Formula = "=MID(A2,1,-1)";
|
||||
Assert.Equal(CellError.Value, sheet.Cells["B1"].Data.GetValueOrDefault<CellError>());
|
||||
}
|
||||
}
|
||||
64
Radzen.Blazor.Tests/Spreadsheet/MinAllFunctionTests.cs
Normal file
64
Radzen.Blazor.Tests/Spreadsheet/MinAllFunctionTests.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class MinAllFunctionTests
|
||||
{
|
||||
readonly Sheet sheet = new(10, 10);
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateLogicalValuesInRange()
|
||||
{
|
||||
sheet.Cells["A1"].Value = true; // 1
|
||||
sheet.Cells["A2"].Value = false; // 0
|
||||
sheet.Cells["A3"].Value = 5; // 5
|
||||
|
||||
sheet.Cells["B1"].Formula = "=MINA(A1:A3)";
|
||||
|
||||
Assert.Equal(0d, sheet.Cells["B1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldTreatTextInRangeAsZeroAndNumericTextAsNumber()
|
||||
{
|
||||
sheet.Cells["A1"].Value = "abc"; // -> 0
|
||||
sheet.Cells["A2"].Value = "15"; // -> 15
|
||||
sheet.Cells["A3"].Value = 10;
|
||||
|
||||
sheet.Cells["B1"].Formula = "=MINA(A1:A3)";
|
||||
|
||||
Assert.Equal(0d, sheet.Cells["B1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldCountDirectLogicalAndTextArguments()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=MINA(1=1, \"7\", 1=2)"; // TRUE, "7", FALSE -> min is 0
|
||||
Assert.Equal(0d, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnZeroWhenNoValues()
|
||||
{
|
||||
sheet.Cells["A1"].Value = null; // empty
|
||||
sheet.Cells["A2"].Value = ""; // empty string -> Empty
|
||||
|
||||
sheet.Cells["B1"].Formula = "=MINA(A1:A2)";
|
||||
|
||||
Assert.Equal(0d, sheet.Cells["B1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldPropagateErrors()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = 0;
|
||||
sheet.Cells["A3"].Formula = "=A1/A2"; // #DIV/0!
|
||||
|
||||
sheet.Cells["B1"].Formula = "=MINA(A1:A3)";
|
||||
|
||||
Assert.Equal(CellError.Div0, sheet.Cells["B1"].Value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
69
Radzen.Blazor.Tests/Spreadsheet/MinFunctionTests.cs
Normal file
69
Radzen.Blazor.Tests/Spreadsheet/MinFunctionTests.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class MinFunctionTests
|
||||
{
|
||||
readonly Sheet sheet = new(10, 10);
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnSmallestValueFromNumbers()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = 7;
|
||||
sheet.Cells["A3"].Value = 9;
|
||||
sheet.Cells["A4"].Value = 27;
|
||||
sheet.Cells["A5"].Value = 2;
|
||||
|
||||
sheet.Cells["B1"].Formula = "=MIN(A1:A5)";
|
||||
|
||||
Assert.Equal(2d, sheet.Cells["B1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnSmallestValueFromRangeAndLiteral()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = 7;
|
||||
sheet.Cells["A3"].Value = 9;
|
||||
sheet.Cells["A4"].Value = 27;
|
||||
sheet.Cells["A5"].Value = 2;
|
||||
|
||||
sheet.Cells["B1"].Formula = "=MIN(A1:A5,1)";
|
||||
|
||||
Assert.Equal(1d, sheet.Cells["B1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldIgnoreNonNumericInRange()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 10;
|
||||
sheet.Cells["A2"].Value = "text";
|
||||
sheet.Cells["A3"].Value = true;
|
||||
sheet.Cells["A4"].Value = 27;
|
||||
sheet.Cells["A5"].Value = null;
|
||||
|
||||
sheet.Cells["B1"].Formula = "=MIN(A1:A5)";
|
||||
|
||||
Assert.Equal(10d, sheet.Cells["B1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldTreatDirectNumericStringsAsNumbers()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=MIN(\"15\", 5, 10)";
|
||||
Assert.Equal(5d, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnZeroWhenNoNumbers()
|
||||
{
|
||||
sheet.Cells["A1"].Value = "a";
|
||||
sheet.Cells["A2"].Value = false;
|
||||
sheet.Cells["A3"].Formula = "=MIN(A1:A2)";
|
||||
|
||||
Assert.Equal(0d, sheet.Cells["A3"].Value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
34
Radzen.Blazor.Tests/Spreadsheet/MinuteFunctionTests.cs
Normal file
34
Radzen.Blazor.Tests/Spreadsheet/MinuteFunctionTests.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class MinuteFunctionTests
|
||||
{
|
||||
[Fact]
|
||||
public void Minute_FromFraction_ReturnsMinutes()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
// 0.78125 = 18:45 -> minutes 45
|
||||
sheet.Cells["A1"].Data = CellData.FromNumber(0.78125);
|
||||
sheet.Cells["B1"].Formula = "=MINUTE(A1)";
|
||||
Assert.Equal(45, sheet.Cells["B1"].Data.GetValueOrDefault<double>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Minute_FromDateTimeValue_ReturnsMinute()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A2"].Data = CellData.FromDate(new System.DateTime(2011, 7, 18, 7, 45, 0));
|
||||
sheet.Cells["B2"].Formula = "=MINUTE(A2)";
|
||||
Assert.Equal(45, sheet.Cells["B2"].Data.GetValueOrDefault<double>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Minute_FromDateOnly_ReturnsZero()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A3"].Data = CellData.FromDate(new System.DateTime(2012, 4, 21));
|
||||
sheet.Cells["B3"].Formula = "=MINUTE(A3)";
|
||||
Assert.Equal(0, sheet.Cells["B3"].Data.GetValueOrDefault<double>());
|
||||
}
|
||||
}
|
||||
31
Radzen.Blazor.Tests/Spreadsheet/MonthFunctionTests.cs
Normal file
31
Radzen.Blazor.Tests/Spreadsheet/MonthFunctionTests.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class MonthFunctionTests
|
||||
{
|
||||
[Fact]
|
||||
public void Month_FromDateSerial_ReturnsMonth()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A1"].Formula = "=MONTH(VALUE(\"2011-04-15\"))";
|
||||
Assert.Equal(4, sheet.Cells["A1"].Data.GetValueOrDefault<double>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Month_FromDateValue_ReturnsMonth()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A1"].Data = CellData.FromDate(new System.DateTime(2011, 4, 15));
|
||||
sheet.Cells["B1"].Formula = "=MONTH(A1)";
|
||||
Assert.Equal(4, sheet.Cells["B1"].Data.GetValueOrDefault<double>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Month_InvalidText_ReturnsValueError()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A1"].Formula = "=MONTH(\"abc\")";
|
||||
Assert.Equal(CellError.Value, sheet.Cells["A1"].Data.GetValueOrDefault<CellError>());
|
||||
}
|
||||
}
|
||||
200
Radzen.Blazor.Tests/Spreadsheet/NotFunctionTests.cs
Normal file
200
Radzen.Blazor.Tests/Spreadsheet/NotFunctionTests.cs
Normal file
@@ -0,0 +1,200 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class NotFunctionTests
|
||||
{
|
||||
readonly Sheet sheet = new(5, 5);
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateNotFunctionWithTrueValue()
|
||||
{
|
||||
sheet.Cells["A1"].Value = true;
|
||||
sheet.Cells["A2"].Formula = "=NOT(A1)";
|
||||
|
||||
Assert.Equal(false, sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateNotFunctionWithEmptyStringAsError()
|
||||
{
|
||||
sheet.Cells["A1"].Value = "";
|
||||
sheet.Cells["A2"].Formula = "=NOT(A1)";
|
||||
|
||||
Assert.Equal(CellError.Value, sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateNotFunctionWithFalseValue()
|
||||
{
|
||||
sheet.Cells["A1"].Value = false;
|
||||
sheet.Cells["A2"].Formula = "=NOT(A1)";
|
||||
|
||||
Assert.Equal(true, sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateNotFunctionWithNumericValue()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 5;
|
||||
sheet.Cells["A2"].Formula = "=NOT(A1)";
|
||||
|
||||
Assert.Equal(false, sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateNotFunctionWithZeroValue()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 0;
|
||||
sheet.Cells["A2"].Formula = "=NOT(A1)";
|
||||
|
||||
Assert.Equal(true, sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateNotFunctionWithStringValue()
|
||||
{
|
||||
sheet.Cells["A1"].Value = "test";
|
||||
sheet.Cells["A2"].Formula = "=NOT(A1)";
|
||||
|
||||
Assert.Equal(CellError.Value, sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateNotFunctionWithEmptyValue()
|
||||
{
|
||||
sheet.Cells["A2"].Formula = "=NOT(A1)";
|
||||
|
||||
Assert.Equal(true, sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateNotFunctionWithComparison()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 50;
|
||||
sheet.Cells["A2"].Formula = "=NOT(A1>100)";
|
||||
|
||||
Assert.Equal(true, sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateNotFunctionWithTrueComparison()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 150;
|
||||
sheet.Cells["A2"].Formula = "=NOT(A1>100)";
|
||||
|
||||
Assert.Equal(false, sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnValueErrorForNotFunctionWithNoArguments()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=NOT()";
|
||||
|
||||
Assert.Equal(CellError.Value, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnValueErrorForNotFunctionWithMultipleArguments()
|
||||
{
|
||||
sheet.Cells["A1"].Value = true;
|
||||
sheet.Cells["A2"].Value = false;
|
||||
sheet.Cells["A3"].Formula = "=NOT(A1,A2)";
|
||||
|
||||
Assert.Equal(CellError.Value, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateNotFunctionWithRangeExpression()
|
||||
{
|
||||
sheet.Cells["A1"].Value = true;
|
||||
sheet.Cells["A2"].Formula = "=NOT(A1:A1)";
|
||||
|
||||
Assert.Equal(false, sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateNotFunctionWithDecimalValue()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 0.5m;
|
||||
sheet.Cells["A2"].Formula = "=NOT(A1)";
|
||||
|
||||
Assert.Equal(false, sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateNotFunctionWithNegativeValue()
|
||||
{
|
||||
sheet.Cells["A1"].Value = -5;
|
||||
sheet.Cells["A2"].Formula = "=NOT(A1)";
|
||||
|
||||
Assert.Equal(false, sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateNotFunctionInIfStatement()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 50;
|
||||
sheet.Cells["A2"].Formula = "=IF(NOT(A1>100),\"Valid\",\"Invalid\")";
|
||||
|
||||
Assert.Equal("Valid", sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateNotFunctionInIfStatementWithFalseCondition()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 150;
|
||||
sheet.Cells["A2"].Formula = "=IF(NOT(A1>100),\"Valid\",\"Invalid\")";
|
||||
|
||||
Assert.Equal("Invalid", sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateNotFunctionWithProvidedExample1()
|
||||
{
|
||||
sheet.Cells["A2"].Value = 50;
|
||||
sheet.Cells["A3"].Formula = "=NOT(A2>100)";
|
||||
|
||||
Assert.Equal(true, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateNotFunctionWithProvidedExample2()
|
||||
{
|
||||
sheet.Cells["A2"].Value = 50;
|
||||
sheet.Cells["A3"].Formula = "=IF(AND(NOT(A2>1),NOT(A2<100)),A2,\"The value is out of range\")";
|
||||
|
||||
Assert.Equal("The value is out of range", sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateNotFunctionWithProvidedExample3()
|
||||
{
|
||||
sheet.Cells["A3"].Value = 100;
|
||||
sheet.Cells["A4"].Formula = "=IF(OR(NOT(A3<0),NOT(A3>50)),A3,\"The value is out of range\")";
|
||||
|
||||
Assert.Equal(100d, sheet.Cells["A4"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateNotFunctionWithNestedLogicalFunctions()
|
||||
{
|
||||
sheet.Cells["A1"].Value = true;
|
||||
sheet.Cells["A2"].Value = false;
|
||||
sheet.Cells["A3"].Formula = "=NOT(AND(A1,A2))";
|
||||
|
||||
Assert.Equal(true, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateNotFunctionWithOrFunction()
|
||||
{
|
||||
sheet.Cells["A1"].Value = false;
|
||||
sheet.Cells["A2"].Value = false;
|
||||
sheet.Cells["A3"].Formula = "=NOT(OR(A1,A2))";
|
||||
|
||||
Assert.Equal(true, sheet.Cells["A3"].Value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
33
Radzen.Blazor.Tests/Spreadsheet/NowFunctionTests.cs
Normal file
33
Radzen.Blazor.Tests/Spreadsheet/NowFunctionTests.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class NowFunctionTests
|
||||
{
|
||||
[Fact]
|
||||
public void Now_ReturnsCurrentDateTime()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A1"].Formula = "=NOW()";
|
||||
var dt = sheet.Cells["A1"].Data.GetValueOrDefault<System.DateTime>();
|
||||
Assert.Equal(System.DateTime.Today, dt.Date);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Now_MinusToday_IsFractionalDayBetween0And1()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A1"].Formula = "=NOW()-TODAY()";
|
||||
var serial = sheet.Cells["A1"].Data.GetValueOrDefault<double>();
|
||||
Assert.True(serial >= 0 && serial < 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Now_PlusSevenDays_MinusToday_IsBetweenSevenAndEight()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A1"].Formula = "=NOW()+7 - TODAY()";
|
||||
var serial = sheet.Cells["A1"].Data.GetValueOrDefault<double>();
|
||||
Assert.True(serial >= 7 && serial < 8);
|
||||
}
|
||||
}
|
||||
216
Radzen.Blazor.Tests/Spreadsheet/OrFunctionTests.cs
Normal file
216
Radzen.Blazor.Tests/Spreadsheet/OrFunctionTests.cs
Normal file
@@ -0,0 +1,216 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class OrFunctionTests
|
||||
{
|
||||
readonly Sheet sheet = new(5, 5);
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateOrFunctionWithAllTrueValues()
|
||||
{
|
||||
sheet.Cells["A1"].Value = true;
|
||||
sheet.Cells["A2"].Value = true;
|
||||
sheet.Cells["A3"].Formula = "=OR(A1,A2)";
|
||||
|
||||
Assert.Equal(true, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateOrFunctionWithOneTrueValue()
|
||||
{
|
||||
sheet.Cells["A1"].Value = true;
|
||||
sheet.Cells["A2"].Value = false;
|
||||
sheet.Cells["A3"].Formula = "=OR(A1,A2)";
|
||||
|
||||
Assert.Equal(true, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateOrFunctionWithAllFalseValues()
|
||||
{
|
||||
sheet.Cells["A1"].Value = false;
|
||||
sheet.Cells["A2"].Value = false;
|
||||
sheet.Cells["A3"].Formula = "=OR(A1,A2)";
|
||||
|
||||
Assert.Equal(false, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateOrFunctionWithNumericValues()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 5;
|
||||
sheet.Cells["A2"].Value = 10;
|
||||
sheet.Cells["A3"].Formula = "=OR(A1>1,A2<100)";
|
||||
|
||||
Assert.Equal(true, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateOrFunctionWithZeroAsFalse()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 0;
|
||||
sheet.Cells["A2"].Value = 1;
|
||||
sheet.Cells["A3"].Formula = "=OR(A1,A2)";
|
||||
|
||||
Assert.Equal(true, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateOrFunctionWithBothZeroAsFalse()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 0;
|
||||
sheet.Cells["A2"].Value = 0;
|
||||
sheet.Cells["A3"].Formula = "=OR(A1,A2)";
|
||||
|
||||
Assert.Equal(false, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateOrFunctionWithNonZeroAsTrue()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 5;
|
||||
sheet.Cells["A2"].Value = 10;
|
||||
sheet.Cells["A3"].Formula = "=OR(A1,A2)";
|
||||
|
||||
Assert.Equal(true, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateOrFunctionWithStringValues()
|
||||
{
|
||||
sheet.Cells["A1"].Value = "test";
|
||||
sheet.Cells["A2"].Value = "hello";
|
||||
sheet.Cells["A3"].Formula = "=OR(A1,A2)";
|
||||
|
||||
Assert.Equal(CellError.Value, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateOrFunctionWithStringValueAndNumber()
|
||||
{
|
||||
sheet.Cells["A1"].Value = "2";
|
||||
sheet.Cells["A2"].Value = "hello";
|
||||
sheet.Cells["A3"].Formula = "=OR(A1,A2)";
|
||||
|
||||
Assert.Equal(true, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateOrFunctionWithEmptyStringAsFalse()
|
||||
{
|
||||
sheet.Cells["A2"].Value = "2";
|
||||
sheet.Cells["A3"].Formula = "=OR(A1,A2)";
|
||||
|
||||
Assert.Equal(true, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateOrFunctionWithBothEmptyStringsAsFalse()
|
||||
{
|
||||
sheet.Cells["A3"].Formula = "=OR(A1,A2)";
|
||||
|
||||
Assert.Equal(CellError.Value, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateOrFunctionWithMultipleArguments()
|
||||
{
|
||||
sheet.Cells["A1"].Value = false;
|
||||
sheet.Cells["A2"].Value = false;
|
||||
sheet.Cells["A3"].Value = true;
|
||||
sheet.Cells["A4"].Formula = "=OR(A1,A2,A3)";
|
||||
|
||||
Assert.Equal(true, sheet.Cells["A4"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateOrFunctionWithAllFalseInMultipleArguments()
|
||||
{
|
||||
sheet.Cells["A1"].Value = false;
|
||||
sheet.Cells["A2"].Value = false;
|
||||
sheet.Cells["A3"].Value = false;
|
||||
sheet.Cells["A4"].Formula = "=OR(A1,A2,A3)";
|
||||
|
||||
Assert.Equal(false, sheet.Cells["A4"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldReturnValueErrorForEmptyOrFunction()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=OR()";
|
||||
|
||||
Assert.Equal(CellError.Value, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateOrFunctionWithRangeExpression()
|
||||
{
|
||||
sheet.Cells["A1"].Value = false;
|
||||
sheet.Cells["A2"].Value = true;
|
||||
sheet.Cells["A3"].Formula = "=OR(A1:A2)";
|
||||
|
||||
Assert.Equal(true, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateOrFunctionWithMixedTypes()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 0;
|
||||
sheet.Cells["A2"].Value = "";
|
||||
sheet.Cells["A3"].Value = true;
|
||||
sheet.Cells["A4"].Formula = "=OR(A1,A2,A3)";
|
||||
|
||||
Assert.Equal(true, sheet.Cells["A4"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateOrFunctionInIfStatement()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 5;
|
||||
sheet.Cells["A2"].Value = 10;
|
||||
sheet.Cells["A3"].Formula = "=IF(OR(A1>1,A2<100),A1,\"Out of range\")";
|
||||
|
||||
Assert.Equal(5d, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateOrFunctionInIfStatementWithFalseCondition()
|
||||
{
|
||||
sheet.Cells["A1"].Value = 0;
|
||||
sheet.Cells["A2"].Value = 150;
|
||||
sheet.Cells["A3"].Formula = "=IF(OR(A1>1,A2<100),A1,\"Out of range\")";
|
||||
|
||||
Assert.Equal("Out of range", sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateOrFunctionWithProvidedExample1()
|
||||
{
|
||||
sheet.Cells["A2"].Value = 50;
|
||||
sheet.Cells["A3"].Formula = "=OR(A2>1,A2<100)";
|
||||
|
||||
Assert.Equal(true, sheet.Cells["A3"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateOrFunctionWithProvidedExample2()
|
||||
{
|
||||
sheet.Cells["A2"].Value = 5;
|
||||
sheet.Cells["A3"].Value = 25;
|
||||
sheet.Cells["A4"].Formula = "=IF(OR(A2>1,A2<100),A3,\"The value is out of range\")";
|
||||
|
||||
Assert.Equal(25d, sheet.Cells["A4"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldEvaluateOrFunctionWithProvidedExample3()
|
||||
{
|
||||
sheet.Cells["A2"].Value = 75;
|
||||
sheet.Cells["A3"].Formula = "=IF(OR(A2<0,A2>50),A2,\"The value is out of range\")";
|
||||
|
||||
Assert.Equal(75d, sheet.Cells["A3"].Value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
33
Radzen.Blazor.Tests/Spreadsheet/ProperFunctionTests.cs
Normal file
33
Radzen.Blazor.Tests/Spreadsheet/ProperFunctionTests.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class ProperFunctionTests
|
||||
{
|
||||
[Fact]
|
||||
public void Proper_TitleCase_Simple()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A2"].Value = "this is a TITLE";
|
||||
sheet.Cells["B1"].Formula = "=PROPER(A2)";
|
||||
Assert.Equal("This Is A Title", sheet.Cells["B1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Proper_KeepsHyphenation()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A3"].Value = "2-way street";
|
||||
sheet.Cells["B1"].Formula = "=PROPER(A3)";
|
||||
Assert.Equal("2-Way Street", sheet.Cells["B1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Proper_AlnumBoundary()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A4"].Value = "76BudGet";
|
||||
sheet.Cells["B1"].Formula = "=PROPER(A4)";
|
||||
Assert.Equal("76Budget", sheet.Cells["B1"].Data.Value);
|
||||
}
|
||||
}
|
||||
31
Radzen.Blazor.Tests/Spreadsheet/RandBetweenFunctionTests.cs
Normal file
31
Radzen.Blazor.Tests/Spreadsheet/RandBetweenFunctionTests.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class RandBetweenFunctionTests
|
||||
{
|
||||
readonly Sheet sheet = new(5, 5);
|
||||
|
||||
[Theory]
|
||||
[InlineData(1, 100)]
|
||||
[InlineData(-1, 1)]
|
||||
[InlineData(0, 0)]
|
||||
public void RandBetween_ShouldReturnInclusiveRange(int bottom, int top)
|
||||
{
|
||||
static string Tok(int n) => n < 0 ? $"0{n}" : n.ToString();
|
||||
sheet.Cells["A1"].Formula = $"=RANDBETWEEN({Tok(bottom)},{Tok(top)})";
|
||||
var v = sheet.Cells["A1"].Value;
|
||||
Assert.IsType<double>(v); // numeric stored as double
|
||||
var d = (double)v;
|
||||
Assert.True(d >= bottom && d <= top);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RandBetween_ShouldReturnNumError_WhenBottomGreaterThanTop()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=RANDBETWEEN(5,1)";
|
||||
Assert.Equal(CellError.Num, sheet.Cells["A1"].Value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
36
Radzen.Blazor.Tests/Spreadsheet/RandFunctionTests.cs
Normal file
36
Radzen.Blazor.Tests/Spreadsheet/RandFunctionTests.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class RandFunctionTests
|
||||
{
|
||||
readonly Sheet sheet = new(5, 5);
|
||||
|
||||
[Fact]
|
||||
public void Rand_ShouldReturnInRangeZeroToOneExclusiveOfOne()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=RAND()";
|
||||
var v = sheet.Cells["A1"].Value;
|
||||
Assert.IsType<double>(v);
|
||||
var d = (double)v;
|
||||
Assert.True(d >= 0d && d < 1d);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Rand_RecalculatesOnFormulaReassignment()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=RAND()";
|
||||
var d1 = (double)sheet.Cells["A1"].Value;
|
||||
sheet.Cells["A1"].Formula = "=RAND()"; // force recalc
|
||||
var d2 = (double)sheet.Cells["A1"].Value;
|
||||
// It's possible (though unlikely) to be equal; allow a retry window
|
||||
if (d1 == d2)
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=RAND()";
|
||||
d2 = (double)sheet.Cells["A1"].Value;
|
||||
}
|
||||
Assert.True(d1 != d2 || (d1 >= 0d && d1 < 1d));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
132
Radzen.Blazor.Tests/Spreadsheet/RangeSelectionItemTests.cs
Normal file
132
Radzen.Blazor.Tests/Spreadsheet/RangeSelectionItemTests.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
using Bunit;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class RangeSelectionItemTests : TestContext
|
||||
{
|
||||
private readonly Sheet sheet = new(4, 4);
|
||||
|
||||
[Fact]
|
||||
public void RangeSelectionItem_ShouldCalculateCorrectMaskForMergedCells()
|
||||
{
|
||||
// Arrange
|
||||
var mergedRange = RangeRef.Parse("B1:C1");
|
||||
sheet.MergedCells.Add(mergedRange);
|
||||
|
||||
// Select a range that overlaps with the merged cell
|
||||
var selectionRange = RangeRef.Parse("A1:D1");
|
||||
sheet.Selection.Select(selectionRange);
|
||||
|
||||
var context = new MockVirtualGridContext();
|
||||
|
||||
// Act
|
||||
var cut = RenderComponent<RangeSelectionItem>(parameters => parameters
|
||||
.Add(p => p.Range, selectionRange)
|
||||
.Add(p => p.Sheet, sheet)
|
||||
.Add(p => p.Cell, sheet.Selection.Cell) // This should be A1 (the active cell)
|
||||
.Add(p => p.Context, context));
|
||||
|
||||
// Assert
|
||||
var element = cut.Find(".rz-spreadsheet-selection-range");
|
||||
Assert.NotNull(element);
|
||||
|
||||
// The style should include mask properties that account for the merged cell
|
||||
var style = element.GetAttribute("style");
|
||||
Assert.NotNull(style);
|
||||
Assert.Contains("mask-size", style);
|
||||
Assert.Contains("mask-position", style);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RangeSelectionItem_ShouldHandleNonMergedCellsCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var selectionRange = RangeRef.Parse("A1:B2");
|
||||
sheet.Selection.Select(selectionRange);
|
||||
|
||||
var context = new MockVirtualGridContext();
|
||||
|
||||
// Act
|
||||
var cut = RenderComponent<RangeSelectionItem>(parameters => parameters
|
||||
.Add(p => p.Range, selectionRange)
|
||||
.Add(p => p.Sheet, sheet)
|
||||
.Add(p => p.Cell, sheet.Selection.Cell)
|
||||
.Add(p => p.Context, context));
|
||||
|
||||
// Assert
|
||||
var element = cut.Find(".rz-spreadsheet-selection-range");
|
||||
Assert.NotNull(element);
|
||||
|
||||
var style = element.GetAttribute("style");
|
||||
Assert.NotNull(style);
|
||||
Assert.Contains("transform", style);
|
||||
Assert.Contains("width", style);
|
||||
Assert.Contains("height", style);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RangeSelectionItem_ShouldHandleMergedCellsAcrossFrozenColumnBoundary()
|
||||
{
|
||||
// Arrange
|
||||
sheet.Columns.Frozen = 1; // Freeze first column
|
||||
var mergedRange = RangeRef.Parse("A1:B1"); // Merged cell spans across frozen boundary
|
||||
sheet.MergedCells.Add(mergedRange);
|
||||
|
||||
// Select the merged cell
|
||||
sheet.Selection.Select(mergedRange);
|
||||
|
||||
var context = new MockVirtualGridContext();
|
||||
|
||||
// Get the split ranges (frozen and non-frozen parts)
|
||||
var ranges = sheet.GetRanges(mergedRange).ToList();
|
||||
Assert.Equal(2, ranges.Count); // Should be split into 2 parts
|
||||
|
||||
// Test the frozen part (A1:A1)
|
||||
var frozenRange = ranges.First(r => r.FrozenColumn);
|
||||
var frozenCut = RenderComponent<RangeSelectionItem>(parameters => parameters
|
||||
.Add(p => p.Range, frozenRange.Range)
|
||||
.Add(p => p.Sheet, sheet)
|
||||
.Add(p => p.Cell, sheet.Selection.Cell)
|
||||
.Add(p => p.Context, context)
|
||||
.Add(p => p.FrozenColumn, frozenRange.FrozenColumn)
|
||||
.Add(p => p.FrozenRow, frozenRange.FrozenRow)
|
||||
.Add(p => p.Top, frozenRange.Top)
|
||||
.Add(p => p.Left, frozenRange.Left)
|
||||
.Add(p => p.Bottom, frozenRange.Bottom)
|
||||
.Add(p => p.Right, frozenRange.Right));
|
||||
|
||||
var frozenElement = frozenCut.Find(".rz-spreadsheet-selection-range");
|
||||
Assert.NotNull(frozenElement);
|
||||
Assert.Contains("rz-spreadsheet-frozen-column", frozenElement.ClassName);
|
||||
|
||||
var frozenStyle = frozenElement.GetAttribute("style");
|
||||
Assert.NotNull(frozenStyle);
|
||||
Assert.Contains("mask-size", frozenStyle);
|
||||
Assert.Contains("mask-position", frozenStyle);
|
||||
|
||||
// Test the non-frozen part (B1:B1)
|
||||
var nonFrozenRange = ranges.First(r => !r.FrozenColumn);
|
||||
var nonFrozenCut = RenderComponent<RangeSelectionItem>(parameters => parameters
|
||||
.Add(p => p.Range, nonFrozenRange.Range)
|
||||
.Add(p => p.Sheet, sheet)
|
||||
.Add(p => p.Cell, sheet.Selection.Cell)
|
||||
.Add(p => p.Context, context)
|
||||
.Add(p => p.FrozenColumn, nonFrozenRange.FrozenColumn)
|
||||
.Add(p => p.FrozenRow, nonFrozenRange.FrozenRow)
|
||||
.Add(p => p.Top, nonFrozenRange.Top)
|
||||
.Add(p => p.Left, nonFrozenRange.Left)
|
||||
.Add(p => p.Bottom, nonFrozenRange.Bottom)
|
||||
.Add(p => p.Right, nonFrozenRange.Right));
|
||||
|
||||
var nonFrozenElement = nonFrozenCut.Find(".rz-spreadsheet-selection-range");
|
||||
Assert.NotNull(nonFrozenElement);
|
||||
Assert.DoesNotContain("rz-spreadsheet-frozen-column", nonFrozenElement.ClassName);
|
||||
|
||||
var nonFrozenStyle = nonFrozenElement.GetAttribute("style");
|
||||
Assert.NotNull(nonFrozenStyle);
|
||||
Assert.Contains("mask-size", nonFrozenStyle);
|
||||
Assert.Contains("mask-position", nonFrozenStyle);
|
||||
}
|
||||
}
|
||||
51
Radzen.Blazor.Tests/Spreadsheet/ReplaceFunctionTests.cs
Normal file
51
Radzen.Blazor.Tests/Spreadsheet/ReplaceFunctionTests.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class ReplaceFunctionTests
|
||||
{
|
||||
[Fact]
|
||||
public void Replace_Middle_WithAsterisk()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A2"].Value = "abcdefghijk";
|
||||
sheet.Cells["B1"].Formula = "=REPLACE(A2,6,5,\"*\")";
|
||||
Assert.Equal("abcde*k", sheet.Cells["B1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Replace_LastTwoDigits_With10()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A3"].Value = "2009";
|
||||
sheet.Cells["B1"].Formula = "=REPLACE(A3,3,2,\"10\")";
|
||||
Assert.Equal("2010", sheet.Cells["B1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Replace_FirstThree_WithAt()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A4"].Value = "123456";
|
||||
sheet.Cells["B1"].Formula = "=REPLACE(A4,1,3,\"@\")";
|
||||
Assert.Equal("@456", sheet.Cells["B1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Replace_StartBeyond_Appends()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A1"].Value = "abc";
|
||||
sheet.Cells["B1"].Formula = "=REPLACE(A1,10,2,\"XYZ\")";
|
||||
Assert.Equal("abcXYZ", sheet.Cells["B1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Replace_InvalidStartNum_ReturnsValueError()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A1"].Value = "abc";
|
||||
sheet.Cells["B1"].Formula = "=REPLACE(A1,0,2,\"X\")";
|
||||
Assert.Equal(CellError.Value, sheet.Cells["B1"].Data.GetValueOrDefault<CellError>());
|
||||
}
|
||||
}
|
||||
47
Radzen.Blazor.Tests/Spreadsheet/ReptFunctionTests.cs
Normal file
47
Radzen.Blazor.Tests/Spreadsheet/ReptFunctionTests.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class ReptFunctionTests
|
||||
{
|
||||
[Fact]
|
||||
public void Rept_Basic()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A1"].Formula = "=REPT(\"*-\",3)";
|
||||
Assert.Equal("*-*-*-", sheet.Cells["A1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Rept_DashesTen()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A1"].Formula = "=REPT(\"-\",10)";
|
||||
Assert.Equal("----------", sheet.Cells["A1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Rept_ZeroTimes_ReturnsEmpty()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A1"].Formula = "=REPT(\"x\",0)";
|
||||
Assert.Equal(string.Empty, sheet.Cells["A1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Rept_Negative_ReturnsValueError()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A1"].Formula = "=REPT(\"x\",-1)";
|
||||
Assert.Equal(CellError.Value, sheet.Cells["A1"].Data.GetValueOrDefault<CellError>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Rept_Overflow_ReturnsValueError()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
// text length 2 * 20000 = 40000 > 32767
|
||||
sheet.Cells["A1"].Formula = "=REPT(\"ab\",20000)";
|
||||
Assert.Equal(CellError.Value, sheet.Cells["A1"].Data.GetValueOrDefault<CellError>());
|
||||
}
|
||||
}
|
||||
42
Radzen.Blazor.Tests/Spreadsheet/RightFunctionTests.cs
Normal file
42
Radzen.Blazor.Tests/Spreadsheet/RightFunctionTests.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class RightFunctionTests
|
||||
{
|
||||
[Fact]
|
||||
public void Right_WithCount_ReturnsSuffix()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A2"].Value = "Sale Price";
|
||||
sheet.Cells["B1"].Formula = "=RIGHT(A2,5)";
|
||||
Assert.Equal("Price", sheet.Cells["B1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Right_OmittedCount_DefaultsToOne()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A3"].Value = "Stock Number";
|
||||
sheet.Cells["B1"].Formula = "=RIGHT(A3)";
|
||||
Assert.Equal("r", sheet.Cells["B1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Right_CountExceedsLength_ReturnsWhole()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A1"].Value = "Hi";
|
||||
sheet.Cells["B1"].Formula = "=RIGHT(A1,5)";
|
||||
Assert.Equal("Hi", sheet.Cells["B1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Right_NegativeCount_ReturnsValueError()
|
||||
{
|
||||
var sheet = new Sheet(10, 10);
|
||||
sheet.Cells["A1"].Value = "Test";
|
||||
sheet.Cells["B1"].Formula = "=RIGHT(A1,-1)";
|
||||
Assert.Equal(CellError.Value, sheet.Cells["B1"].Data.GetValueOrDefault<CellError>());
|
||||
}
|
||||
}
|
||||
39
Radzen.Blazor.Tests/Spreadsheet/RoundDownFunctionTests.cs
Normal file
39
Radzen.Blazor.Tests/Spreadsheet/RoundDownFunctionTests.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class RoundDownFunctionTests
|
||||
{
|
||||
readonly Sheet sheet = new(10, 10);
|
||||
|
||||
[Fact]
|
||||
public void ShouldRoundDownToZeroDecimalPlaces()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=ROUNDDOWN(3.2,0)";
|
||||
Assert.Equal(3d, sheet.Cells["A1"].Value);
|
||||
|
||||
sheet.Cells["A2"].Formula = "=ROUNDDOWN(76.9,0)";
|
||||
Assert.Equal(76d, sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldRoundDownToSpecifiedDecimalPlaces()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=ROUNDDOWN(3.14159,3)";
|
||||
Assert.Equal(3.141, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldRoundDownNegativeNumbersTowardZero()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=ROUNDDOWN(0-3.14159,1)";
|
||||
Assert.Equal(-3.1, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldRoundDownToLeftOfDecimalWhenNegativeDigits()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=ROUNDDOWN(31415.92654,0-2)";
|
||||
Assert.Equal(31400d, sheet.Cells["A1"].Value);
|
||||
}
|
||||
}
|
||||
41
Radzen.Blazor.Tests/Spreadsheet/RoundFunctionTests.cs
Normal file
41
Radzen.Blazor.Tests/Spreadsheet/RoundFunctionTests.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class RoundFunctionTests
|
||||
{
|
||||
readonly Sheet sheet = new(10, 10);
|
||||
|
||||
[Fact]
|
||||
public void ShouldRoundToOneDecimalPlace()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=ROUND(2.15,1)";
|
||||
Assert.Equal(2.2, sheet.Cells["A1"].Value);
|
||||
|
||||
sheet.Cells["A2"].Formula = "=ROUND(2.149,1)";
|
||||
Assert.Equal(2.1, sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldRoundNegativeWithAwayFromZeroMidpoint()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=ROUND(0-1.475,2)";
|
||||
Assert.Equal(-1.48, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldRoundWithNegativeDigits()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=ROUND(21.5,0-1)";
|
||||
Assert.Equal(20d, sheet.Cells["A1"].Value);
|
||||
|
||||
sheet.Cells["A2"].Formula = "=ROUND(626.3,0-3)";
|
||||
Assert.Equal(1000d, sheet.Cells["A2"].Value);
|
||||
|
||||
sheet.Cells["A3"].Formula = "=ROUND(1.98,0-1)";
|
||||
Assert.Equal(0d, sheet.Cells["A3"].Value);
|
||||
|
||||
sheet.Cells["A4"].Formula = "=ROUND(0-50.55,0-2)";
|
||||
Assert.Equal(-100d, sheet.Cells["A4"].Value);
|
||||
}
|
||||
}
|
||||
39
Radzen.Blazor.Tests/Spreadsheet/RoundUpFunctionTests.cs
Normal file
39
Radzen.Blazor.Tests/Spreadsheet/RoundUpFunctionTests.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class RoundUpFunctionTests
|
||||
{
|
||||
readonly Sheet sheet = new(10, 10);
|
||||
|
||||
[Fact]
|
||||
public void ShouldRoundUpToZeroDecimalPlaces()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=ROUNDUP(3.2,0)";
|
||||
Assert.Equal(4d, sheet.Cells["A1"].Value);
|
||||
|
||||
sheet.Cells["A2"].Formula = "=ROUNDUP(76.9,0)";
|
||||
Assert.Equal(77d, sheet.Cells["A2"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldRoundUpToSpecifiedDecimalPlaces()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=ROUNDUP(3.14159,3)";
|
||||
Assert.Equal(3.142, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldRoundUpNegativeNumbersAwayFromZero()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=ROUNDUP(0-3.14159,1)";
|
||||
Assert.Equal(-3.2, sheet.Cells["A1"].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldRoundUpToLeftOfDecimalWhenNegativeDigits()
|
||||
{
|
||||
sheet.Cells["A1"].Formula = "=ROUNDUP(31415.92654,0-2)";
|
||||
Assert.Equal(31500d, sheet.Cells["A1"].Value);
|
||||
}
|
||||
}
|
||||
301
Radzen.Blazor.Tests/Spreadsheet/RowColumnCommandTests.cs
Normal file
301
Radzen.Blazor.Tests/Spreadsheet/RowColumnCommandTests.cs
Normal file
@@ -0,0 +1,301 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class RowColumnCommandTests
|
||||
{
|
||||
[Fact]
|
||||
public void DeleteRowsCommand_SingleRow_ExecuteAndUndo_RestoresState()
|
||||
{
|
||||
var sheet = new Sheet(4, 3);
|
||||
sheet.Cells[0, 0].Value = "R1";
|
||||
sheet.Cells[1, 0].Value = "R2";
|
||||
sheet.Cells[2, 0].Value = "R3";
|
||||
sheet.Cells[3, 0].Value = "R4";
|
||||
|
||||
var cmd = new DeleteRowsCommand(sheet, 1, 1);
|
||||
Assert.True(sheet.Commands.Execute(cmd));
|
||||
|
||||
Assert.Equal(3, sheet.RowCount);
|
||||
Assert.Equal("R1", sheet.Cells[0, 0].Value);
|
||||
Assert.Equal("R3", sheet.Cells[1, 0].Value);
|
||||
Assert.Equal("R4", sheet.Cells[2, 0].Value);
|
||||
|
||||
sheet.Commands.Undo();
|
||||
|
||||
Assert.Equal(4, sheet.RowCount);
|
||||
Assert.Equal("R1", sheet.Cells[0, 0].Value);
|
||||
Assert.Equal("R2", sheet.Cells[1, 0].Value);
|
||||
Assert.Equal("R3", sheet.Cells[2, 0].Value);
|
||||
Assert.Equal("R4", sheet.Cells[3, 0].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeleteRowsCommand_ExecuteAndUndo_RestoresState()
|
||||
{
|
||||
var sheet = new Sheet(6, 3);
|
||||
sheet.Cells[0, 0].Value = "R1";
|
||||
sheet.Cells[1, 0].Value = "R2";
|
||||
sheet.Cells[2, 0].Value = "R3";
|
||||
sheet.Cells[3, 0].Value = "R4";
|
||||
sheet.Cells[4, 0].Value = "R5";
|
||||
sheet.Cells[5, 0].Value = "R6";
|
||||
|
||||
var cmd = new DeleteRowsCommand(sheet, 1, 3); // delete rows 2..4
|
||||
Assert.True(sheet.Commands.Execute(cmd));
|
||||
|
||||
Assert.Equal(3, sheet.RowCount);
|
||||
Assert.Equal("R1", sheet.Cells[0, 0].Value);
|
||||
Assert.Equal("R5", sheet.Cells[1, 0].Value);
|
||||
Assert.Equal("R6", sheet.Cells[2, 0].Value);
|
||||
|
||||
sheet.Commands.Undo();
|
||||
Assert.Equal(6, sheet.RowCount);
|
||||
Assert.Equal("R1", sheet.Cells[0, 0].Value);
|
||||
Assert.Equal("R2", sheet.Cells[1, 0].Value);
|
||||
Assert.Equal("R3", sheet.Cells[2, 0].Value);
|
||||
Assert.Equal("R4", sheet.Cells[3, 0].Value);
|
||||
Assert.Equal("R5", sheet.Cells[4, 0].Value);
|
||||
Assert.Equal("R6", sheet.Cells[5, 0].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InsertRowAfterCommand_ExecuteAndUndo_RestoresState()
|
||||
{
|
||||
var sheet = new Sheet(4, 3);
|
||||
sheet.Cells[1, 0].Value = 1d; // A2
|
||||
sheet.Cells[1, 1].Formula = "=A2+10"; // B2
|
||||
|
||||
var cmd = new InsertRowAfterCommand(sheet, 1); // after row 1 -> insert at index 2
|
||||
Assert.True(sheet.Commands.Execute(cmd));
|
||||
|
||||
Assert.Equal(5, sheet.RowCount);
|
||||
// Inserting after row 1 does not move row 1; A2 and B2 stay
|
||||
Assert.Equal(1d, sheet.Cells[1, 0].Value);
|
||||
Assert.Equal("=A2+10", sheet.Cells[1, 1].Formula);
|
||||
Assert.Equal(11d, sheet.Cells[1, 1].Value);
|
||||
|
||||
sheet.Commands.Undo();
|
||||
|
||||
Assert.Equal(4, sheet.RowCount);
|
||||
Assert.Equal(1d, sheet.Cells[1, 0].Value);
|
||||
Assert.Equal("=A2+10", sheet.Cells[1, 1].Formula);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InsertRowBeforeCommand_ExecuteAndUndo_RestoresState()
|
||||
{
|
||||
var sheet = new Sheet(4, 3);
|
||||
sheet.Cells[1, 0].Value = 1d; // A2
|
||||
sheet.Cells[1, 1].Formula = "=A2+10"; // B2
|
||||
|
||||
var cmd = new InsertRowBeforeCommand(sheet, 1); // before row 1 -> insert at index 1
|
||||
Assert.True(sheet.Commands.Execute(cmd));
|
||||
|
||||
Assert.Equal(5, sheet.RowCount);
|
||||
// value shifted down (A2 becomes A3)
|
||||
Assert.Equal(1d, sheet.Cells[2, 0].Value);
|
||||
// formula shifted down and reference updated A2->A3
|
||||
Assert.Equal("=A3+10", sheet.Cells[2, 1].Formula);
|
||||
Assert.Equal(11d, sheet.Cells[2, 1].Value);
|
||||
|
||||
sheet.Commands.Undo();
|
||||
|
||||
Assert.Equal(4, sheet.RowCount);
|
||||
Assert.Equal(1d, sheet.Cells[1, 0].Value);
|
||||
Assert.Equal("=A2+10", sheet.Cells[1, 1].Formula);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InsertRowAfterCommand_WithMultiSelection_InsertsAfterLastRow()
|
||||
{
|
||||
var sheet = new Sheet(6, 2);
|
||||
// Mark rows with their index in A
|
||||
for (int r = 0; r < 6; r++) sheet.Cells[r, 0].Value = r + 1;
|
||||
|
||||
// Simulate selection rows 1..3 (0-based), last is 3 -> insert after 3 at index 4
|
||||
var cmd = new InsertRowAfterCommand(sheet, 3);
|
||||
Assert.True(sheet.Commands.Execute(cmd));
|
||||
|
||||
Assert.Equal(7, sheet.RowCount);
|
||||
// Inserted row at index 4 is empty
|
||||
Assert.Null(sheet.Cells[4, 0].Value);
|
||||
// Row 5 takes previous row 4 value (5)
|
||||
Assert.Equal(5d, sheet.Cells[5, 0].Value);
|
||||
// Row 6 takes previous row 5 value (6)
|
||||
Assert.Equal(6d, sheet.Cells[6, 0].Value);
|
||||
|
||||
sheet.Commands.Undo();
|
||||
Assert.Equal(6, sheet.RowCount);
|
||||
Assert.Equal(5d, sheet.Cells[4, 0].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InsertRowBeforeCommand_WithMultiSelection_InsertsBeforeFirstRow()
|
||||
{
|
||||
var sheet = new Sheet(6, 2);
|
||||
for (int r = 0; r < 6; r++) sheet.Cells[r, 0].Value = r + 1;
|
||||
|
||||
// Simulate selection rows 2..4 (first is 2) -> insert at index 2
|
||||
var cmd = new InsertRowBeforeCommand(sheet, 2);
|
||||
Assert.True(sheet.Commands.Execute(cmd));
|
||||
|
||||
Assert.Equal(7, sheet.RowCount);
|
||||
// Inserted row at index 2 is empty
|
||||
Assert.Null(sheet.Cells[2, 0].Value);
|
||||
// Row 3 takes previous row 2 value (3)
|
||||
Assert.Equal(3d, sheet.Cells[3, 0].Value);
|
||||
|
||||
sheet.Commands.Undo();
|
||||
Assert.Equal(6, sheet.RowCount);
|
||||
Assert.Equal(3d, sheet.Cells[2, 0].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InsertColumnAfterCommand_WithMultiSelection_InsertsAfterLastColumn()
|
||||
{
|
||||
var sheet = new Sheet(2, 6);
|
||||
// Mark columns with their index in row 1
|
||||
for (int c = 0; c < 6; c++) sheet.Cells[0, c].Value = c + 1;
|
||||
|
||||
// Simulate selection columns 1..3 (last is 3) -> insert after col 3 at index 4
|
||||
var cmd = new InsertColumnAfterCommand(sheet, 3);
|
||||
Assert.True(sheet.Commands.Execute(cmd));
|
||||
|
||||
Assert.Equal(7, sheet.ColumnCount);
|
||||
// Inserted column at index 4 is empty
|
||||
Assert.Null(sheet.Cells[0, 4].Value);
|
||||
// Column 5 takes previous column 4 value (5)
|
||||
Assert.Equal(5d, sheet.Cells[0, 5].Value);
|
||||
|
||||
sheet.Commands.Undo();
|
||||
Assert.Equal(6, sheet.ColumnCount);
|
||||
Assert.Equal(5d, sheet.Cells[0, 4].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InsertColumnBeforeCommand_WithMultiSelection_InsertsBeforeFirstColumn()
|
||||
{
|
||||
var sheet = new Sheet(2, 6);
|
||||
for (int c = 0; c < 6; c++) sheet.Cells[0, c].Value = c + 1;
|
||||
|
||||
// Simulate selection columns 2..4 (first is 2) -> insert at index 2
|
||||
var cmd = new InsertColumnBeforeCommand(sheet, 2);
|
||||
Assert.True(sheet.Commands.Execute(cmd));
|
||||
|
||||
Assert.Equal(7, sheet.ColumnCount);
|
||||
// Inserted column at index 2 is empty
|
||||
Assert.Null(sheet.Cells[0, 2].Value);
|
||||
// Column 3 takes previous column 2 value (3)
|
||||
Assert.Equal(3d, sheet.Cells[0, 3].Value);
|
||||
|
||||
sheet.Commands.Undo();
|
||||
Assert.Equal(6, sheet.ColumnCount);
|
||||
Assert.Equal(3d, sheet.Cells[0, 2].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeleteColumnsCommand_SingleColumn_ExecuteAndUndo_RestoresState()
|
||||
{
|
||||
var sheet = new Sheet(3, 4);
|
||||
sheet.Cells[0, 0].Value = "A";
|
||||
sheet.Cells[0, 1].Value = "B";
|
||||
sheet.Cells[0, 2].Value = "C";
|
||||
sheet.Cells[0, 3].Value = "D";
|
||||
|
||||
var cmd = new DeleteColumnsCommand(sheet, 1, 1);
|
||||
Assert.True(sheet.Commands.Execute(cmd));
|
||||
|
||||
Assert.Equal(3, sheet.ColumnCount);
|
||||
Assert.Equal("A", sheet.Cells[0, 0].Value);
|
||||
Assert.Equal("C", sheet.Cells[0, 1].Value);
|
||||
Assert.Equal("D", sheet.Cells[0, 2].Value);
|
||||
|
||||
sheet.Commands.Undo();
|
||||
|
||||
Assert.Equal(4, sheet.ColumnCount);
|
||||
Assert.Equal("A", sheet.Cells[0, 0].Value);
|
||||
Assert.Equal("B", sheet.Cells[0, 1].Value);
|
||||
Assert.Equal("C", sheet.Cells[0, 2].Value);
|
||||
Assert.Equal("D", sheet.Cells[0, 3].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeleteColumnsCommand_ExecuteAndUndo_RestoresState()
|
||||
{
|
||||
var sheet = new Sheet(3, 6);
|
||||
sheet.Cells[0, 0].Value = "A";
|
||||
sheet.Cells[0, 1].Value = "B";
|
||||
sheet.Cells[0, 2].Value = "C";
|
||||
sheet.Cells[0, 3].Value = "D";
|
||||
sheet.Cells[0, 4].Value = "E";
|
||||
sheet.Cells[0, 5].Value = "F";
|
||||
|
||||
var cmd = new DeleteColumnsCommand(sheet, 1, 3); // delete B..D
|
||||
Assert.True(sheet.Commands.Execute(cmd));
|
||||
|
||||
Assert.Equal(3, sheet.ColumnCount);
|
||||
Assert.Equal("A", sheet.Cells[0, 0].Value);
|
||||
Assert.Equal("E", sheet.Cells[0, 1].Value);
|
||||
Assert.Equal("F", sheet.Cells[0, 2].Value);
|
||||
|
||||
sheet.Commands.Undo();
|
||||
|
||||
Assert.Equal(6, sheet.ColumnCount);
|
||||
Assert.Equal("A", sheet.Cells[0, 0].Value);
|
||||
Assert.Equal("B", sheet.Cells[0, 1].Value);
|
||||
Assert.Equal("C", sheet.Cells[0, 2].Value);
|
||||
Assert.Equal("D", sheet.Cells[0, 3].Value);
|
||||
Assert.Equal("E", sheet.Cells[0, 4].Value);
|
||||
Assert.Equal("F", sheet.Cells[0, 5].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InsertColumnBeforeCommand_ExecuteAndUndo_RestoresState()
|
||||
{
|
||||
var sheet = new Sheet(5, 5);
|
||||
sheet.Cells[1, 0].Value = 1d; // A2
|
||||
sheet.Cells[1, 1].Formula = "=A2+10"; // B2
|
||||
|
||||
var cmd = new InsertColumnBeforeCommand(sheet, 0); // before A
|
||||
Assert.True(sheet.Commands.Execute(cmd));
|
||||
|
||||
Assert.Equal(6, sheet.ColumnCount);
|
||||
// value shifted right (A2 becomes B2)
|
||||
Assert.Equal(1d, sheet.Cells[1, 1].Value);
|
||||
// formula shifted right and reference updated A2->B2
|
||||
Assert.Equal("=B2+10", sheet.Cells[1, 2].Formula); // original B2 moved to C2
|
||||
Assert.Equal(11d, sheet.Cells[1, 2].Value);
|
||||
|
||||
sheet.Commands.Undo();
|
||||
|
||||
Assert.Equal(5, sheet.ColumnCount);
|
||||
Assert.Equal(1d, sheet.Cells[1, 0].Value);
|
||||
Assert.Equal("=A2+10", sheet.Cells[1, 1].Formula);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InsertColumnAfterCommand_ExecuteAndUndo_RestoresState()
|
||||
{
|
||||
var sheet = new Sheet(5, 5);
|
||||
sheet.Cells[1, 0].Value = 1d; // A2
|
||||
sheet.Cells[1, 1].Formula = "=A2+10"; // B2
|
||||
|
||||
var cmd = new InsertColumnAfterCommand(sheet, 0); // after A
|
||||
Assert.True(sheet.Commands.Execute(cmd));
|
||||
|
||||
Assert.Equal(6, sheet.ColumnCount);
|
||||
// value at A2 stays, but B2 moves to C2; formula references should update if referencing >= inserted column
|
||||
Assert.Equal(1d, sheet.Cells[1, 0].Value);
|
||||
Assert.Equal("=A2+10", sheet.Cells[1, 2].Formula); // original B2 moved to C2
|
||||
Assert.Equal(11d, sheet.Cells[1, 2].Value);
|
||||
|
||||
sheet.Commands.Undo();
|
||||
|
||||
Assert.Equal(5, sheet.ColumnCount);
|
||||
Assert.Equal(1d, sheet.Cells[1, 0].Value);
|
||||
Assert.Equal("=A2+10", sheet.Cells[1, 1].Formula);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
38
Radzen.Blazor.Tests/Spreadsheet/RowFunctionTests.cs
Normal file
38
Radzen.Blazor.Tests/Spreadsheet/RowFunctionTests.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class RowFunctionTests
|
||||
{
|
||||
[Fact]
|
||||
public void Row_OmittedReference_ReturnsCurrentRow()
|
||||
{
|
||||
var sheet = new Sheet(20, 10);
|
||||
sheet.Cells["C10"].Formula = "=ROW()";
|
||||
Assert.Equal(10d, sheet.Cells["C10"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Row_SingleCellReference_ReturnsThatRow()
|
||||
{
|
||||
var sheet = new Sheet(20, 10);
|
||||
sheet.Cells["A1"].Formula = "=ROW(C10)";
|
||||
Assert.Equal(10d, sheet.Cells["A1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Row_RangeReference_ReturnsTopLeftRow()
|
||||
{
|
||||
var sheet = new Sheet(20, 10);
|
||||
sheet.Cells["B2"].Formula = "=ROW(C10:E10)";
|
||||
Assert.Equal(10d, sheet.Cells["B2"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Row_RangeReference_MultiRowAndColumn_IsError()
|
||||
{
|
||||
var sheet = new Sheet(20, 10);
|
||||
sheet.Cells["B2"].Formula = "=ROW(C10:D20)";
|
||||
Assert.Equal(CellError.Value, sheet.Cells["B2"].Data.Value);
|
||||
}
|
||||
}
|
||||
30
Radzen.Blazor.Tests/Spreadsheet/RowsFunctionTests.cs
Normal file
30
Radzen.Blazor.Tests/Spreadsheet/RowsFunctionTests.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Spreadsheet.Tests;
|
||||
|
||||
public class RowsFunctionTests
|
||||
{
|
||||
[Fact]
|
||||
public void Rows_Range_ReturnsRowCount()
|
||||
{
|
||||
var sheet = new Sheet(50, 20);
|
||||
sheet.Cells["A1"].Formula = "=ROWS(C1:E4)";
|
||||
Assert.Equal(4d, sheet.Cells["A1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Rows_SingleCell_ReturnsOne()
|
||||
{
|
||||
var sheet = new Sheet(50, 20);
|
||||
sheet.Cells["A1"].Formula = "=ROWS(C10)";
|
||||
Assert.Equal(1d, sheet.Cells["A1"].Data.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Rows_SingleRowRange_ReturnsOne()
|
||||
{
|
||||
var sheet = new Sheet(50, 20);
|
||||
sheet.Cells["A1"].Formula = "=ROWS(C10:E10)";
|
||||
Assert.Equal(1d, sheet.Cells["A1"].Data.Value);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user