Compare commits

..

40 Commits

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

* code improved

* EventConsole added

* Pastel is the default color scheme

* Fix color schemes in Spider chart

* Update SpiderChart styles

---------

Co-authored-by: Ehab Hussein <me@ehabhussein.com>
Co-authored-by: Vladimir Enchev <vladimir.enchev@gmail.com>
Co-authored-by: yordanov <vasil@yordanov.info>
2026-01-22 14:27:01 +02:00
Vladimir Enchev
a65c1a9482 version updated 2026-01-21 17:31:55 +02:00
Vladimir Enchev
4939e8498a OData filtering by no string columns fixed 2026-01-21 17:31:33 +02:00
Vladimir Enchev
e380467853 Version updated 2026-01-21 10:17:06 +02:00
Vladimir Enchev
d3adc9733b PivotDataGrid user interaction breaks multi-column/row sorting
Fix #2427
2026-01-20 13:37:13 +02:00
Vladimir Enchev
a03fc50ee8 DataFilter should set filter Type on property change 2026-01-20 09:31:07 +02:00
Mason Voxland
c9201bf947 Fix TextProperty documentation in RadzenScheduler (#2426)
Should be string, not DateTime
2026-01-20 09:17:23 +02:00
yordanov
8c8d288afd Fix #2188 rz-layout height should be 100dvh by default 2026-01-19 17:15:40 +02:00
Vladimir Enchev
1a679af008 Chart demo source code fixed 2026-01-19 16:00:26 +02:00
yordanov
bc1654a405 Fix #2218 - RadzenSidebar border should take into account the sidebar's position 2026-01-19 13:12:54 +02:00
Atanas Korchev
94ef62e00b Customize split button dropdown icon demo is showing wrong source code. 2026-01-19 11:33:24 +02:00
317 changed files with 3513 additions and 29922 deletions

View File

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

View File

@@ -328,7 +328,7 @@ namespace Radzen.Blazor.Tests
var result = data.Where(filters, LogicalFilterOperator.And, FilterCaseSensitivity.Default).ToList();
Assert.Single(result);
Assert.Equal(1, result.Count);
Assert.Equal("Eve", result[0].Name);
}

View File

@@ -1,49 +0,0 @@
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);
}
}

View File

@@ -1,160 +0,0 @@
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);
}
}

View File

@@ -1,105 +0,0 @@
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);
}
}

View File

@@ -1,114 +0,0 @@
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);
}
}

View File

@@ -1,261 +0,0 @@
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));
}

View File

@@ -1,69 +0,0 @@
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);
}
}

View File

@@ -1,34 +0,0 @@
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);
}
}

View File

@@ -1,40 +0,0 @@
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);
}
}

View File

@@ -1,30 +0,0 @@
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);
}
}

View File

@@ -1,44 +0,0 @@
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);
}
}

View File

@@ -1,150 +0,0 @@
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);
}
}

View File

@@ -1,139 +0,0 @@
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);
}
}

View File

@@ -1,32 +0,0 @@
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>());
}
}

View File

@@ -1,81 +0,0 @@
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);
}
}

View File

@@ -1,146 +0,0 @@
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);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,51 +0,0 @@
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>());
}
}

View File

@@ -1,298 +0,0 @@
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>());
}
}

View File

@@ -1,143 +0,0 @@
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);
}
}

View File

@@ -1,644 +0,0 @@
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);
}
}

View File

@@ -1,74 +0,0 @@
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);
}
}

View File

@@ -1,59 +0,0 @@
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);
}
}

View File

@@ -1,33 +0,0 @@
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>());
}
}

View File

@@ -1,138 +0,0 @@
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);
}
}

View File

@@ -1,178 +0,0 @@
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);
}
}

View File

@@ -1,100 +0,0 @@
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);
}
}

View File

@@ -1,64 +0,0 @@
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);
}
}

View File

@@ -1,30 +0,0 @@
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);
}
}

View File

@@ -1,74 +0,0 @@
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);
}
}

View File

@@ -1,42 +0,0 @@
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>());
}
}

View File

@@ -1,59 +0,0 @@
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);
}
}

View File

@@ -1,24 +0,0 @@
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);
}
}

View File

@@ -1,64 +0,0 @@
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);
}
}

View File

@@ -1,69 +0,0 @@
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);
}
}

View File

@@ -1,51 +0,0 @@
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>());
}
}

View File

@@ -1,64 +0,0 @@
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);
}
}

View File

@@ -1,69 +0,0 @@
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);
}
}

View File

@@ -1,34 +0,0 @@
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>());
}
}

View File

@@ -1,31 +0,0 @@
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>());
}
}

View File

@@ -1,200 +0,0 @@
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);
}
}

View File

@@ -1,33 +0,0 @@
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);
}
}

View File

@@ -1,216 +0,0 @@
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);
}
}

View File

@@ -1,33 +0,0 @@
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);
}
}

View File

@@ -1,31 +0,0 @@
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);
}
}

View File

@@ -1,36 +0,0 @@
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));
}
}

View File

@@ -1,132 +0,0 @@
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);
}
}

View File

@@ -1,51 +0,0 @@
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>());
}
}

View File

@@ -1,47 +0,0 @@
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>());
}
}

View File

@@ -1,42 +0,0 @@
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>());
}
}

View File

@@ -1,39 +0,0 @@
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);
}
}

View File

@@ -1,41 +0,0 @@
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);
}
}

View File

@@ -1,39 +0,0 @@
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);
}
}

View File

@@ -1,301 +0,0 @@
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);
}
}

View File

@@ -1,38 +0,0 @@
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);
}
}

View File

@@ -1,30 +0,0 @@
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);
}
}

View File

@@ -1,65 +0,0 @@
using Xunit;
namespace Radzen.Blazor.Spreadsheet.Tests;
public class SearchFunctionTests
{
[Fact]
public void Search_SimpleCharacter()
{
var sheet = new Sheet(10, 10);
sheet.Cells["A1"].Formula = "=SEARCH(\"n\",\"printer\")";
Assert.Equal(4d, sheet.Cells["A1"].Data.Value);
}
[Fact]
public void Search_Substring()
{
var sheet = new Sheet(10, 10);
sheet.Cells["A1"].Formula = "=SEARCH(\"base\",\"database\")";
Assert.Equal(5d, sheet.Cells["A1"].Data.Value);
}
[Fact]
public void Search_StartNum_SkipsPrefix()
{
var sheet = new Sheet(10, 30);
sheet.Cells["A1"].Value = "AYF0093.YoungMensApparel";
sheet.Cells["B1"].Formula = "=SEARCH(\"Y\",A1,8)";
Assert.Equal(9d, sheet.Cells["B1"].Data.Value);
}
[Fact]
public void Search_Wildcards_QuestionAndAsterisk()
{
var sheet = new Sheet(10, 10);
sheet.Cells["A1"].Value = "Profit Margin";
sheet.Cells["B1"].Formula = "=SEARCH(\"M*r?in\",A1)";
Assert.Equal(8d, sheet.Cells["B1"].Data.Value);
}
[Fact]
public void Search_TildeEscapesWildcards()
{
var sheet = new Sheet(10, 10);
sheet.Cells["A1"].Value = "a*?c";
sheet.Cells["B1"].Formula = "=SEARCH(\"~*~?\",A1)";
Assert.Equal(2d, sheet.Cells["B1"].Data.Value);
}
[Fact]
public void Search_StartNumInvalid_ReturnsValueError()
{
var sheet = new Sheet(10, 10);
sheet.Cells["A1"].Formula = "=SEARCH(\"e\",\"printer\",0)";
Assert.Equal(CellError.Value, sheet.Cells["A1"].Data.GetValueOrDefault<CellError>());
}
[Fact]
public void Search_NotFound_ReturnsValueError()
{
var sheet = new Sheet(10, 10);
sheet.Cells["A1"].Formula = "=SEARCH(\"zzz\",\"printer\")";
Assert.Equal(CellError.Value, sheet.Cells["A1"].Data.GetValueOrDefault<CellError>());
}
}

View File

@@ -1,33 +0,0 @@
using Xunit;
namespace Radzen.Blazor.Spreadsheet.Tests;
public class SecondFunctionTests
{
[Fact]
public void Second_FromDateTimeWithSeconds_ReturnsSeconds()
{
var sheet = new Sheet(10, 10);
sheet.Cells["A1"].Data = CellData.FromDate(new System.DateTime(2020, 1, 1, 16, 48, 18));
sheet.Cells["B1"].Formula = "=SECOND(A1)";
Assert.Equal(18, sheet.Cells["B1"].Data.GetValueOrDefault<double>());
}
[Fact]
public void Second_FromDateTimeWithoutSeconds_ReturnsZero()
{
var sheet = new Sheet(10, 10);
sheet.Cells["A2"].Data = CellData.FromDate(new System.DateTime(2020, 1, 1, 16, 48, 0));
sheet.Cells["B2"].Formula = "=SECOND(A2)";
Assert.Equal(0, sheet.Cells["B2"].Data.GetValueOrDefault<double>());
}
[Fact]
public void Second_FromDateOnly_ReturnsZero()
{
var sheet = new Sheet(10, 10);
sheet.Cells["A3"].Data = CellData.FromDate(new System.DateTime(2020, 1, 1));
sheet.Cells["B3"].Formula = "=SECOND(A3)";
Assert.Equal(0, sheet.Cells["B3"].Data.GetValueOrDefault<double>());
}
}

View File

@@ -1,287 +0,0 @@
using Xunit;
namespace Radzen.Blazor.Spreadsheet.Tests;
public class SelectionCycleTests
{
readonly Sheet sheet = new(4, 4);
[Fact]
public void Should_MoveToTheNextHorizontalCell()
{
sheet.Selection.Select(RangeRef.Parse("A1:C1"));
var cell = sheet.Selection.Cycle(0, 1);
Assert.Equal("B1", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
Assert.Equal(RangeRef.Parse("A1:C1"), sheet.Selection.Range);
}
[Fact]
public void Should_MoveToTheFirstCellInTheRangeWhenAtLastColumn()
{
sheet.Selection.Select(CellRef.Parse("C1"), RangeRef.Parse("A1:C1"));
var cell = sheet.Selection.Cycle(0, 1);
Assert.Equal("A1", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
Assert.Equal(RangeRef.Parse("A1:C1"), sheet.Selection.Range);
}
[Fact]
public void Should_MoveToTheNextRowInTheRangeWhenAtLastColumn()
{
sheet.Selection.Select(CellRef.Parse("C1"), RangeRef.Parse("A1:C2"));
var cell = sheet.Selection.Cycle(0, 1);
Assert.Equal("A2", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
Assert.Equal(RangeRef.Parse("A1:C2"), sheet.Selection.Range);
}
[Fact]
public void Should_MoveToTheNextVerticalCell()
{
sheet.Selection.Select(RangeRef.Parse("A1:A3"));
var cell = sheet.Selection.Cycle(1, 0);
Assert.Equal("A2", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
Assert.Equal(RangeRef.Parse("A1:A3"), sheet.Selection.Range);
}
[Fact]
public void Should_MoveToTheFirstCellInTheRangeWhenAtLastRow()
{
sheet.Selection.Select(CellRef.Parse("A3"), RangeRef.Parse("A1:A3"));
var cell = sheet.Selection.Cycle(1, 0);
Assert.Equal("A1", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
Assert.Equal(RangeRef.Parse("A1:A3"), sheet.Selection.Range);
}
[Fact]
public void Should_MoveToTheNextColumnInTheRangeWhenAtLastRow()
{
sheet.Selection.Select(CellRef.Parse("A3"), RangeRef.Parse("A1:B3"));
var cell = sheet.Selection.Cycle(1, 0);
Assert.Equal("B1", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
Assert.Equal(RangeRef.Parse("A1:B3"), sheet.Selection.Range);
}
[Fact]
public void Should_MoveToThePreviousHorizontalCell()
{
sheet.Selection.Select(RangeRef.Parse("B1:C1"));
var cell = sheet.Selection.Cycle(0, -1);
Assert.Equal("C1", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
Assert.Equal(RangeRef.Parse("B1:C1"), sheet.Selection.Range);
}
[Fact]
public void Should_MoveToTheLastCellInTheRangeWhenAtFirstColumn()
{
sheet.Selection.Select(CellRef.Parse("A1"), RangeRef.Parse("A1:C1"));
var cell = sheet.Selection.Cycle(0, -1);
Assert.Equal("C1", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
Assert.Equal(RangeRef.Parse("A1:C1"), sheet.Selection.Range);
}
[Fact]
public void Should_MoveToThePreviousRowInTheRangeWhenAtFirstColumn()
{
sheet.Selection.Select(CellRef.Parse("A1"), RangeRef.Parse("A1:C2"));
var cell = sheet.Selection.Cycle(0, -1);
Assert.Equal("C2", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
Assert.Equal(RangeRef.Parse("A1:C2"), sheet.Selection.Range);
}
[Fact]
public void Should_MoveToTheNextHorizontalCellIfOnlyOneCellIsSelected()
{
sheet.Selection.Select(CellRef.Parse("A1"));
var cell = sheet.Selection.Cycle(0, 1);
Assert.Equal("B1", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
Assert.Equal(RangeRef.Parse("B1:B1"), sheet.Selection.Range);
}
[Fact]
public void Should_MoveToTheNextVerticalCellIfOnlyOneCellIsSelected()
{
sheet.Selection.Select(CellRef.Parse("A1"));
var cell = sheet.Selection.Cycle(1, 0);
Assert.Equal("A2", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
Assert.Equal(RangeRef.Parse("A2:A2"), sheet.Selection.Range);
}
[Fact]
public void Should_MoveToThePreviousHorizontalCellIfOnlyOneCellIsSelected()
{
sheet.Selection.Select(CellRef.Parse("B1"));
var cell = sheet.Selection.Cycle(0, -1);
Assert.Equal("A1", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
Assert.Equal(RangeRef.Parse("A1:A1"), sheet.Selection.Range);
}
[Fact]
public void Should_MoveToThePreviousVerticalCellIfOnlyOneCellIsSelected()
{
sheet.Selection.Select(CellRef.Parse("A2"));
var cell = sheet.Selection.Cycle(-1, 0);
Assert.Equal("A1", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
Assert.Equal(RangeRef.Parse("A1:A1"), sheet.Selection.Range);
}
[Fact]
public void Should_StayInTheSameCellIfTheFirstCellIsSelectedAndMovingBackwards()
{
sheet.Selection.Select(CellRef.Parse("A1"));
var cell = sheet.Selection.Cycle(0, -1);
Assert.Equal("A1", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
Assert.Equal(RangeRef.Parse("A1:A1"), sheet.Selection.Range);
}
[Fact]
public void Should_StayInTheSameCellIfTheLastCellIsSelectedAndMovingForwards()
{
sheet.Selection.Select(CellRef.Parse("D4"));
var cell = sheet.Selection.Cycle(0, 1);
Assert.Equal("D4", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
Assert.Equal(RangeRef.Parse("D4:D4"), sheet.Selection.Range);
}
[Fact]
public void Should_StayInTheSameCellIfOnlyOneCellIsSelectedAndMovingUp()
{
sheet.Selection.Select(CellRef.Parse("A1"));
var cell = sheet.Selection.Cycle(-1, 0);
Assert.Equal("A1", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
Assert.Equal(RangeRef.Parse("A1:A1"), sheet.Selection.Range);
}
[Fact]
public void Should_StayInTheSameCellIfOnlyOneCellIsSelectedAndMovingDown()
{
sheet.Selection.Select(CellRef.Parse("D4"));
var cell = sheet.Selection.Cycle(1, 0);
Assert.Equal("D4", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
Assert.Equal(RangeRef.Parse("D4:D4"), sheet.Selection.Range);
}
[Fact]
public void Should_GoToTheFirstCellInTheSheetIfNoSelection()
{
var cell = sheet.Selection.Cycle(0, 1);
Assert.Equal("A1", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
Assert.Equal(RangeRef.Parse("A1:A1"), sheet.Selection.Range);
}
[Fact]
public void Should_GoToTheNextHorizontalMergedCellInTheSelectedRange()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:C1"));
sheet.Selection.Select(RangeRef.Parse("A1:D1"));
var cell = sheet.Selection.Cycle(0, 1);
Assert.Equal("B1", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
Assert.Equal(RangeRef.Parse("A1:D1"), sheet.Selection.Range);
}
[Fact]
public void Should_GoToThePreviousHorizontalMergedCellInTheSelectedRange()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:C1"));
sheet.Selection.Select(CellRef.Parse("D1"), RangeRef.Parse("A1:D1"));
var cell = sheet.Selection.Cycle(0, -1);
Assert.Equal("B1", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
Assert.Equal(RangeRef.Parse("A1:D1"), sheet.Selection.Range);
}
[Fact]
public void Should_GoToTheNextVerticalMergedCellInTheSelectedRange()
{
sheet.MergedCells.Add(RangeRef.Parse("A2:A3"));
sheet.Selection.Select(RangeRef.Parse("A1:A4"));
var cell = sheet.Selection.Cycle(1, 0);
Assert.Equal("A2", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
Assert.Equal(RangeRef.Parse("A1:A4"), sheet.Selection.Range);
}
[Fact]
public void Should_GoToThePreviousVerticalMergedCellInTheSelectedRange()
{
sheet.MergedCells.Add(RangeRef.Parse("A2:A3"));
sheet.Selection.Select(CellRef.Parse("A4"), RangeRef.Parse("A1:A4"));
var cell = sheet.Selection.Cycle(-1, 0);
Assert.Equal("A2", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
Assert.Equal(RangeRef.Parse("A1:A4"), sheet.Selection.Range);
}
[Fact]
public void Should_MoveFromMergedCellToNextCell()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:C1"));
sheet.Selection.Select(CellRef.Parse("B1"));
var cell = sheet.Selection.Cycle(0, 1);
Assert.Equal("D1", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
Assert.Equal(RangeRef.Parse("D1:D1"), sheet.Selection.Range);
}
}

View File

@@ -1,445 +0,0 @@
using Xunit;
namespace Radzen.Blazor.Spreadsheet.Tests;
public class SelectionExtensionTests
{
readonly Sheet sheet = new(10, 10);
[Fact]
public void Extend_ShouldAppendNextHorizontalCellToRange()
{
sheet.Selection.Select(CellRef.Parse("B1"));
sheet.Selection.Extend(0, 1);
Assert.Equal("B1:C1", sheet.Selection.Range.ToString());
Assert.Equal("B1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldAppendNextVerticalCellToRange()
{
sheet.Selection.Select(CellRef.Parse("B1"));
sheet.Selection.Extend(1, 0);
Assert.Equal("B1:B2", sheet.Selection.Range.ToString());
Assert.Equal("B1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldSubtractPreviousHorizontalCellFromRange()
{
sheet.Selection.Select(RangeRef.Parse("B1:C1"));
sheet.Selection.Extend(0, -1);
Assert.Equal("B1:B1", sheet.Selection.Range.ToString());
Assert.Equal("B1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldSubtractPreviousVerticalCellFromRange()
{
sheet.Selection.Select(RangeRef.Parse("B1:B2"));
sheet.Selection.Extend(-1, 0);
Assert.Equal("B1:B1", sheet.Selection.Range.ToString());
Assert.Equal("B1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldAppendPreviousHorizontalCellToRange()
{
sheet.Selection.Select(CellRef.Parse("B1"));
sheet.Selection.Extend(0, -1);
Assert.Equal("A1:B1", sheet.Selection.Range.ToString());
Assert.Equal("B1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldFirstAppendAndThenSubtractNextHorizontalCell()
{
sheet.Selection.Select(CellRef.Parse("B1"));
sheet.Selection.Extend(0, 1);
sheet.Selection.Extend(0, -1);
Assert.Equal("B1:B1", sheet.Selection.Range.ToString());
Assert.Equal("B1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldFirstAppendAndThenSubtractPreviousHorizontalCell()
{
sheet.Selection.Select(CellRef.Parse("B1"));
sheet.Selection.Extend(0, -1);
sheet.Selection.Extend(0, 1);
Assert.Equal("B1:B1", sheet.Selection.Range.ToString());
Assert.Equal("B1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldAppendPreviousVerticalCellToRange()
{
sheet.Selection.Select(CellRef.Parse("B2"));
sheet.Selection.Extend(-1, 0);
Assert.Equal("B1:B2", sheet.Selection.Range.ToString());
Assert.Equal("B2", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldFirstAppendAndThenSubtractVerticalCell()
{
sheet.Selection.Select(CellRef.Parse("B2"));
sheet.Selection.Extend(1, 0);
sheet.Selection.Extend(-1, 0);
Assert.Equal("B2:B2", sheet.Selection.Range.ToString());
Assert.Equal("B2", sheet.Selection.Cell.ToString());
}
[Fact]
public void Select_ShouldSelectTheWholeMergedCellRangeWhenTheStartIsUsed()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:C1"));
sheet.Selection.Select(CellRef.Parse("B1"));
Assert.Equal("B1:C1", sheet.Selection.Range.ToString());
Assert.Equal("B1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Select_ShouldSelectTheWholeMergedCellRangeWhenTheEndIsUsed()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:C1"));
sheet.Selection.Select(CellRef.Parse("C1"));
Assert.Equal("B1:C1", sheet.Selection.Range.ToString());
Assert.Equal("B1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldAddNextHorizontalCellAfterMergedCell()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:C1"));
sheet.Selection.Select(CellRef.Parse("B1"));
sheet.Selection.Extend(0, 1);
Assert.Equal("B1:D1", sheet.Selection.Range.ToString());
Assert.Equal("B1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldAddNextHorizontalCellAfterMergedCellAgain()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:C1"));
sheet.Selection.Select(CellRef.Parse("B1"));
sheet.Selection.Extend(0, 1);
sheet.Selection.Extend(0, 1);
Assert.Equal("B1:E1", sheet.Selection.Range.ToString());
Assert.Equal("B1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldAddNextMergedCellAfterMergedCell()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:C1"));
sheet.MergedCells.Add(RangeRef.Parse("D1:E1"));
sheet.Selection.Select(CellRef.Parse("B1"));
sheet.Selection.Extend(0, 1);
Assert.Equal("B1:E1", sheet.Selection.Range.ToString());
Assert.Equal("B1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldSubtractNextMergedCellAfterMergedCell()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:C1"));
sheet.MergedCells.Add(RangeRef.Parse("D1:E1"));
sheet.Selection.Select(CellRef.Parse("B1"));
sheet.Selection.Extend(0, 1);
sheet.Selection.Extend(0, -1);
Assert.Equal("B1:C1", sheet.Selection.Range.ToString());
Assert.Equal("B1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldAddPreviousMergedCellBeforeMergedCell()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:C1"));
sheet.MergedCells.Add(RangeRef.Parse("D1:E1"));
sheet.Selection.Select(CellRef.Parse("D1"));
sheet.Selection.Extend(0, -1);
Assert.Equal("B1:E1", sheet.Selection.Range.ToString());
Assert.Equal("D1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldSubtractPreviousMergedCellBeforeMergedCell()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:C1"));
sheet.MergedCells.Add(RangeRef.Parse("D1:E1"));
sheet.Selection.Select(CellRef.Parse("D1"));
sheet.Selection.Extend(0, -1);
sheet.Selection.Extend(0, 1);
Assert.Equal("D1:E1", sheet.Selection.Range.ToString());
Assert.Equal("D1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldAddPreviousHorizontalMergedCell()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:C1"));
sheet.Selection.Select(CellRef.Parse("D1"));
sheet.Selection.Extend(0, -1);
Assert.Equal("B1:D1", sheet.Selection.Range.ToString());
Assert.Equal("D1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldSubractPreviousMergedCellFromSelection()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:C1"));
sheet.Selection.Select(CellRef.Parse("D1"));
sheet.Selection.Extend(0, -1);
Assert.Equal("B1:D1", sheet.Selection.Range.ToString());
sheet.Selection.Extend(0, 1);
Assert.Equal("D1:D1", sheet.Selection.Range.ToString());
Assert.Equal("D1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldSubtractNextMergedCellFromSelection()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:C1"));
sheet.Selection.Select(RangeRef.Parse("A1:C1"));
sheet.Selection.Extend(0, -1);
Assert.Equal("A1:A1", sheet.Selection.Range.ToString());
Assert.Equal("A1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldSubtractPreviousHorizontalCellBeforeMergedCellFromSelection()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:C1"));
sheet.Selection.Select(RangeRef.Parse("B1:C1"));
sheet.Selection.Extend(0, -1);
Assert.Equal("A1:C1", sheet.Selection.Range.ToString());
sheet.Selection.Extend(0, 1);
Assert.Equal("B1:C1", sheet.Selection.Range.ToString());
Assert.Equal("B1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldAddPreviousHorizontalCellBeforeMergedCell()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:C1"));
sheet.Selection.Select(CellRef.Parse("B1"));
sheet.Selection.Extend(0, -1);
Assert.Equal("A1:C1", sheet.Selection.Range.ToString());
Assert.Equal("B1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldAddNextVerticalCellAfterMergedCell()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:B2"));
sheet.Selection.Select(CellRef.Parse("B1"));
sheet.Selection.Extend(1, 0);
Assert.Equal("B1:B3", sheet.Selection.Range.ToString());
Assert.Equal("B1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldAddNextVerticalCellAfterMergedCellAgain()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:B2"));
sheet.Selection.Select(CellRef.Parse("B1"));
sheet.Selection.Extend(1, 0);
sheet.Selection.Extend(1, 0);
Assert.Equal("B1:B4", sheet.Selection.Range.ToString());
Assert.Equal("B1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldHandleMergedCellsConsistentlyWhenMovingLeft()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:C1"));
sheet.Selection.Select(CellRef.Parse("D1"));
sheet.Selection.Extend(0, -1);
Assert.Equal("B1:D1", sheet.Selection.Range.ToString());
Assert.Equal("D1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldHandleMergedCellsConsistentlyWhenMovingLeftFromStart()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:C1"));
sheet.Selection.Select(CellRef.Parse("B1"));
sheet.Selection.Extend(0, -1);
Assert.Equal("A1:C1", sheet.Selection.Range.ToString());
Assert.Equal("B1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldHandleMergedCellsConsistentlyWhenMovingLeftFromEnd()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:C1"));
sheet.Selection.Select(CellRef.Parse("C1"));
sheet.Selection.Extend(0, -1);
Assert.Equal("A1:C1", sheet.Selection.Range.ToString());
Assert.Equal("B1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldHandleMergedCellsConsistentlyWhenMovingLeftFromEndAndThenRight()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:C1"));
sheet.Selection.Select(CellRef.Parse("C1"));
sheet.Selection.Extend(0, -1);
sheet.Selection.Extend(0, 1);
Assert.Equal("B1:C1", sheet.Selection.Range.ToString());
Assert.Equal("B1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldHandleMergedCellsConsistentlyWhenMovingLeftFromMiddle()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:D1"));
sheet.Selection.Select(CellRef.Parse("C1"));
sheet.Selection.Extend(0, -1);
Assert.Equal("A1:D1", sheet.Selection.Range.ToString());
Assert.Equal("B1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldAddNextMergedRowAfterMergedRow()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:B2"));
sheet.MergedCells.Add(RangeRef.Parse("B3:B4"));
sheet.Selection.Select(CellRef.Parse("B1"));
sheet.Selection.Extend(1, 0);
Assert.Equal("B1:B4", sheet.Selection.Range.ToString());
Assert.Equal("B1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldSubtractNextMergedRowAfterMergedRow()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:B2"));
sheet.MergedCells.Add(RangeRef.Parse("B3:B4"));
sheet.Selection.Select(CellRef.Parse("B1"));
sheet.Selection.Extend(1, 0);
sheet.Selection.Extend(-1, 0);
Assert.Equal("B1:B2", sheet.Selection.Range.ToString());
Assert.Equal("B1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldAddPreviousMergedRowBeforeMergedRow()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:B2"));
sheet.MergedCells.Add(RangeRef.Parse("B3:B4"));
sheet.Selection.Select(CellRef.Parse("B3"));
sheet.Selection.Extend(-1, 0);
Assert.Equal("B1:B4", sheet.Selection.Range.ToString());
Assert.Equal("B3", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldSubtractPreviousMergedRowBeforeMergedRow()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:B2"));
sheet.MergedCells.Add(RangeRef.Parse("B3:B4"));
sheet.Selection.Select(CellRef.Parse("B3"));
sheet.Selection.Extend(-1, 0);
sheet.Selection.Extend(1, 0);
Assert.Equal("B3:B4", sheet.Selection.Range.ToString());
Assert.Equal("B3", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldAddPreviousVerticalMergedRow()
{
sheet.MergedCells.Add(RangeRef.Parse("B2:B3"));
sheet.Selection.Select(CellRef.Parse("B4"));
sheet.Selection.Extend(-1, 0);
Assert.Equal("B2:B4", sheet.Selection.Range.ToString());
Assert.Equal("B4", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldSubtractPreviousMergedRowFromSelection()
{
sheet.MergedCells.Add(RangeRef.Parse("B2:B3"));
sheet.Selection.Select(CellRef.Parse("B4"));
sheet.Selection.Extend(-1, 0);
Assert.Equal("B2:B4", sheet.Selection.Range.ToString());
sheet.Selection.Extend(1, 0);
Assert.Equal("B4:B4", sheet.Selection.Range.ToString());
Assert.Equal("B4", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldSubtractNextMergedRowFromSelection()
{
sheet.MergedCells.Add(RangeRef.Parse("B2:B3"));
sheet.Selection.Select(RangeRef.Parse("B1:B3"));
sheet.Selection.Extend(-1, 0);
Assert.Equal("B1:B1", sheet.Selection.Range.ToString());
Assert.Equal("B1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldSubtractPreviousVerticalCellBeforeMergedRowFromSelection()
{
sheet.MergedCells.Add(RangeRef.Parse("B2:B3"));
sheet.Selection.Select(RangeRef.Parse("B2:B3"));
sheet.Selection.Extend(-1, 0);
Assert.Equal("B1:B3", sheet.Selection.Range.ToString());
sheet.Selection.Extend(1, 0);
Assert.Equal("B2:B3", sheet.Selection.Range.ToString());
Assert.Equal("B2", sheet.Selection.Cell.ToString());
}
[Fact]
public void Extend_ShouldAddPreviousVerticalCellBeforeMergedRow()
{
sheet.MergedCells.Add(RangeRef.Parse("B2:B3"));
sheet.Selection.Select(CellRef.Parse("B2"));
sheet.Selection.Extend(-1, 0);
Assert.Equal("B1:B3", sheet.Selection.Range.ToString());
Assert.Equal("B2", sheet.Selection.Cell.ToString());
}
}

View File

@@ -1,71 +0,0 @@
using Xunit;
namespace Radzen.Blazor.Spreadsheet.Tests;
public class SelectionMergingTests
{
readonly Sheet sheet = new(4, 4);
[Fact]
public void Should_SelectAllHorizontalCellsBetweenTheCurrentCellAndTheNewOne()
{
sheet.Selection.Select(CellRef.Parse("A1"));
sheet.Selection.Merge(CellRef.Parse("C1"));
Assert.Equal("A1:C1", sheet.Selection.Range.ToString());
Assert.Equal("A1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Should_SelectAllVerticalCellsBetweenTheCurrentCellAndTheNewOne()
{
sheet.Selection.Select(CellRef.Parse("A1"));
sheet.Selection.Merge(CellRef.Parse("A3"));
Assert.Equal("A1:A3", sheet.Selection.Range.ToString());
Assert.Equal("A1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Should_ExtendRangeWhenMerginANewCell()
{
sheet.Selection.Select(RangeRef.Parse("A1:C1"));
sheet.Selection.Merge(CellRef.Parse("D2"));
Assert.Equal("A1:D2", sheet.Selection.Range.ToString());
Assert.Equal("A1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Should_AddTheEntireMergedCell()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:C1"));
sheet.Selection.Select(CellRef.Parse("A1"));
sheet.Selection.Merge(CellRef.Parse("B1"));
Assert.Equal("A1:C1", sheet.Selection.Range.ToString());
Assert.Equal("A1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Should_AddTheEntireMergedCellWhenSelectingIt()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:C1"));
sheet.Selection.Select(CellRef.Parse("B1"));
sheet.Selection.Merge(CellRef.Parse("A1"));
Assert.Equal("A1:C1", sheet.Selection.Range.ToString());
Assert.Equal("B1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Should_ExpandSelectionToIncludeMergedCells()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:C1"));
sheet.Selection.Select(CellRef.Parse("A1"));
sheet.Selection.Merge(CellRef.Parse("B2"));
Assert.Equal("A1:C2", sheet.Selection.Range.ToString());
Assert.Equal("A1", sheet.Selection.Cell.ToString());
}
}

View File

@@ -1,155 +0,0 @@
using Xunit;
namespace Radzen.Blazor.Spreadsheet.Tests;
public class SelectionMovingTests
{
readonly Sheet sheet = new(4, 4);
[Fact]
public void Should_MoveToTheNextHorizontalCell()
{
sheet.Selection.Select(CellRef.Parse("A1"));
var cell = sheet.Selection.Move(0, 1);
Assert.Equal("B1", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
}
[Fact]
public void Should_NotMoveToNextHorizontalCellWhenAlreadyAtLastColumn()
{
sheet.Selection.Select(CellRef.Parse("D1"));
var cell = sheet.Selection.Move(0, 1);
Assert.Equal("D1", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
}
[Fact]
public void Should_MoveToTheNextVerticalCell()
{
sheet.Selection.Select(CellRef.Parse("A1"));
var cell = sheet.Selection.Move(1, 0);
Assert.Equal("A2", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
}
[Fact]
public void Should_NotMoveToNextVerticalCellWhenAlreadyAtLastRow()
{
sheet.Selection.Select(CellRef.Parse("A4"));
var cell = sheet.Selection.Move(1, 0);
Assert.Equal("A4", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
}
[Fact]
public void Should_MoveToThePreviousHorizontalCell()
{
sheet.Selection.Select(CellRef.Parse("B1"));
var cell = sheet.Selection.Move(0, -1);
Assert.Equal("A1", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
}
[Fact]
public void Should_NotMoveToPreviousHorizontalCellWhenAlreadyAtFirstColumn()
{
sheet.Selection.Select(CellRef.Parse("A1"));
var cell = sheet.Selection.Move(0, -1);
Assert.Equal("A1", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
}
[Fact]
public void Should_MoveToThePreviousVerticalCell()
{
sheet.Selection.Select(CellRef.Parse("A2"));
var cell = sheet.Selection.Move(-1, 0);
Assert.Equal("A1", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
}
[Fact]
public void Should_NotMoveToPreviousVerticalCellWhenAlreadyAtFirstRow()
{
sheet.Selection.Select(CellRef.Parse("A1"));
var cell = sheet.Selection.Move(-1, 0);
Assert.Equal("A1", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
}
[Fact]
public void Should_MoveToNextHorizontalCellAfterMergedCell()
{
sheet.MergedCells.Add(RangeRef.Parse("A1:B1"));
sheet.Selection.Select(CellRef.Parse("A1"));
var cell = sheet.Selection.Move(0, 1);
Assert.Equal("C1", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
}
[Fact]
public void Should_MoveToNextVerticalCellAfterMergedCell()
{
sheet.MergedCells.Add(RangeRef.Parse("A1:A2"));
sheet.Selection.Select(CellRef.Parse("A1"));
var cell = sheet.Selection.Move(1, 0);
Assert.Equal("A3", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
}
[Fact]
public void Should_MoveToPreviousHorizontalCellBeforeMergedCell()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:C1"));
sheet.Selection.Select(CellRef.Parse("B1"));
var cell = sheet.Selection.Move(0, -1);
Assert.Equal("A1", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
}
[Fact]
public void Should_MoveToPreviousVerticalCellBeforeMergedCell()
{
sheet.MergedCells.Add(RangeRef.Parse("A2:A3"));
sheet.Selection.Select(CellRef.Parse("A2"));
var cell = sheet.Selection.Move(-1, 0);
Assert.Equal("A1", cell.ToString());
Assert.Equal(sheet.Selection.Cell, cell);
}
[Fact]
public void Should_MoveTheSelectedCellOnly()
{
sheet.Selection.Select(RangeRef.Parse("A1:C1"));
var cell = sheet.Selection.Move(0, 1);
Assert.Equal("B1", cell.ToString());
Assert.Equal("B1:B1", sheet.Selection.Range.ToString());
}
}

View File

@@ -1,75 +0,0 @@
using Xunit;
namespace Radzen.Blazor.Spreadsheet.Tests;
public class SelectionTests
{
readonly Sheet sheet = new(4, 4);
[Fact]
public void Should_SelectSingleCell()
{
sheet.Selection.Select(CellRef.Parse("A1"));
Assert.Equal("A1", sheet.Selection.Cell.ToString());
Assert.Equal("A1:A1", sheet.Selection.Range.ToString());
}
[Fact]
public void Should_SelectRangeOfCells()
{
sheet.Selection.Select(RangeRef.Parse("A1:C3"));
Assert.Equal("A1", sheet.Selection.Cell.ToString());
Assert.Equal("A1", sheet.Selection.Range.Start.ToString());
Assert.Equal("C3", sheet.Selection.Range.End.ToString());
Assert.Equal("A1:C3", sheet.Selection.Range.ToString());
}
[Fact]
public void Should_SelectTheWholeMergedCellAsPartOfRange()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:C1"));
sheet.Selection.Select(RangeRef.Parse("A1:B1"));
Assert.Equal("A1:C1", sheet.Selection.Range.ToString());
}
[Fact]
public void Should_SelectTheWholeMergedCellByStart()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:C1"));
sheet.Selection.Select(CellRef.Parse("B1"));
Assert.Equal("B1:C1", sheet.Selection.Range.ToString());
}
[Fact]
public void Should_SelectTheWholeMergedCellByEnd()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:C1"));
sheet.Selection.Select(CellRef.Parse("C1"));
Assert.Equal("B1:C1", sheet.Selection.Range.ToString());
}
[Fact]
public void Should_SelectTheWholeMergedCellByEndAndRange()
{
sheet.MergedCells.Add(RangeRef.Parse("B1:C1"));
sheet.Selection.Select(CellRef.Parse("C1"), RangeRef.Parse("B1:C1"));
Assert.Equal("B1:C1", sheet.Selection.Range.ToString());
Assert.Equal("B1", sheet.Selection.Cell.ToString());
}
[Fact]
public void Should_SelectCompleteMergedCellRange()
{
sheet.MergedCells.Add(RangeRef.Parse("A1:D1"));
sheet.Selection.Select(RangeRef.Parse("A1:B2"));
Assert.Equal("A1:D2", sheet.Selection.Range.ToString());
Assert.Equal("A1", sheet.Selection.Cell.ToString());
}
}

View File

@@ -1,43 +0,0 @@
using System;
using System.Linq;
using Xunit;
namespace Radzen.Blazor.Spreadsheet.Tests;
public class SheetRangeTests
{
[Fact]
public void Range_Parse_ShouldParseValidRange()
{
var range = RangeRef.Parse("A1:B2");
Assert.Equal(new CellRef(0, 0), range.Start);
Assert.Equal(new CellRef(1, 1), range.End);
}
[Fact]
public void Range_Parse_ShouldThrowOnInvalidRange()
{
Assert.Throws<ArgumentException>(() => RangeRef.Parse("A1:B2:C3"));
}
[Fact]
public void Range_GetCells_ShouldReturnAllCellsInRange()
{
var range = RangeRef.Parse("A1:B2");
var cells = range.GetCells().ToList();
Assert.Equal(4, cells.Count);
Assert.Equal(new CellRef(0, 0), cells[0]); // A1
Assert.Equal(new CellRef(0, 1), cells[1]); // B1
Assert.Equal(new CellRef(1, 0), cells[2]); // A2
Assert.Equal(new CellRef(1, 1), cells[3]); // B2
}
[Fact]
public void Range_ToString_ShouldReturnA1Notation()
{
var range = new RangeRef(new CellRef(0, 0), new CellRef(1, 1));
Assert.Equal("A1:B2", range.ToString());
}
}

View File

@@ -1,58 +0,0 @@
using Xunit;
namespace Radzen.Blazor.Spreadsheet.Tests;
public class SmallFunctionTests
{
readonly Sheet sheet = new(12, 12);
[Fact]
public void ShouldReturn4thSmallestInFirstColumn()
{
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["A7"].Value = 4;
sheet.Cells["A8"].Value = 6;
sheet.Cells["A9"].Value = 4;
sheet.Cells["A10"].Value = 7;
sheet.Cells["B1"].Formula = "=SMALL(A2:A10,4)";
Assert.Equal(4d, sheet.Cells["B1"].Value);
}
[Fact]
public void ShouldReturn2ndSmallestInSecondColumn()
{
sheet.Cells["B2"].Value = 1;
sheet.Cells["B3"].Value = 4;
sheet.Cells["B4"].Value = 8;
sheet.Cells["B5"].Value = 3;
sheet.Cells["B6"].Value = 7;
sheet.Cells["B7"].Value = 12;
sheet.Cells["B8"].Value = 54;
sheet.Cells["B9"].Value = 8;
sheet.Cells["B10"].Value = 23;
sheet.Cells["C1"].Formula = "=SMALL(B2:B10,2)";
Assert.Equal(3d, sheet.Cells["C1"].Value);
}
[Fact]
public void ShouldErrorForEmptyArrayOrInvalidK()
{
sheet.Cells["A1"].Formula = "=SMALL(A2:A2,1)"; // empty range
Assert.Equal(CellError.Num, sheet.Cells["A1"].Value);
sheet.Cells["A2"].Value = 5;
sheet.Cells["A3"].Formula = "=SMALL(A2:A2,0)"; // k <= 0
Assert.Equal(CellError.Num, sheet.Cells["A3"].Value);
sheet.Cells["A4"].Formula = "=SMALL(A2:A2,2)"; // k > count
Assert.Equal(CellError.Num, sheet.Cells["A4"].Value);
}
}

View File

@@ -1,147 +0,0 @@
using System;
using Xunit;
namespace Radzen.Blazor.Spreadsheet.Tests;
public class SortCommandTests
{
[Fact]
public void SortCommand_ShouldSortDataInAscendingOrder()
{
// Arrange
var sheet = new Sheet(5, 5);
var range = RangeRef.Parse("A1:A3");
// Set up test data
sheet.Cells["A1"].Value = "Charlie";
sheet.Cells["A2"].Value = "Alice";
sheet.Cells["A3"].Value = "Bob";
var command = new SortCommand(sheet, range, SortOrder.Ascending, 0);
// Act
var result = command.Execute();
// Assert
Assert.True(result);
Assert.Equal("Alice", sheet.Cells["A1"].Value);
Assert.Equal("Bob", sheet.Cells["A2"].Value);
Assert.Equal("Charlie", sheet.Cells["A3"].Value);
}
[Fact]
public void SortCommand_ShouldSortDataInDescendingOrder()
{
// Arrange
var sheet = new Sheet(5, 5);
var range = RangeRef.Parse("A1:A3");
// Set up test data
sheet.Cells["A1"].Value = "Alice";
sheet.Cells["A2"].Value = "Bob";
sheet.Cells["A3"].Value = "Charlie";
var command = new SortCommand(sheet, range, SortOrder.Descending, 0);
// Act
var result = command.Execute();
// Assert
Assert.True(result);
Assert.Equal("Charlie", sheet.Cells["A1"].Value);
Assert.Equal("Bob", sheet.Cells["A2"].Value);
Assert.Equal("Alice", sheet.Cells["A3"].Value);
}
[Fact]
public void SortCommand_ShouldRestoreOriginalOrderWhenUndone()
{
// Arrange
var sheet = new Sheet(5, 5);
var range = RangeRef.Parse("A1:A3");
// Set up test data
sheet.Cells["A1"].Value = "Charlie";
sheet.Cells["A2"].Value = "Alice";
sheet.Cells["A3"].Value = "Bob";
var command = new SortCommand(sheet, range, SortOrder.Ascending, 0);
// Act
command.Execute();
command.Unexecute();
// Assert
Assert.Equal("Charlie", sheet.Cells["A1"].Value);
Assert.Equal("Alice", sheet.Cells["A2"].Value);
Assert.Equal("Bob", sheet.Cells["A3"].Value);
}
[Fact]
public void SortCommand_ShouldReturnFalseForInvalidRange()
{
// Arrange
var sheet = new Sheet(5, 5);
var command = new SortCommand(sheet, RangeRef.Invalid, SortOrder.Ascending, 0);
// Act
var result = command.Execute();
// Assert
Assert.False(result);
}
[Fact]
public void SortCommand_ShouldPreserveCellFormatting()
{
// Arrange
var sheet = new Sheet(5, 5);
var range = RangeRef.Parse("A1:A2");
// Set up test data with formatting
sheet.Cells["A1"].Value = "Charlie";
sheet.Cells["A1"].Format.Bold = true;
sheet.Cells["A2"].Value = "Alice";
sheet.Cells["A2"].Format.Italic = true;
var command = new SortCommand(sheet, range, SortOrder.Ascending, 0);
// Act
command.Execute();
command.Unexecute();
// Assert
Assert.Equal("Charlie", sheet.Cells["A1"].Value);
Assert.True(sheet.Cells["A1"].Format.Bold);
Assert.Equal("Alice", sheet.Cells["A2"].Value);
Assert.True(sheet.Cells["A2"].Format.Italic);
}
[Fact]
public void SortCommand_ShouldWorkWithAutoFilterRange()
{
// Arrange
var sheet = new Sheet(5, 5);
var range = RangeRef.Parse("A1:B3");
// Set up test data in a format similar to AutoFilter
sheet.Cells["A1"].Value = "Name";
sheet.Cells["B1"].Value = "Age";
sheet.Cells["A2"].Value = "Charlie";
sheet.Cells["B2"].Value = 30;
sheet.Cells["A3"].Value = "Alice";
sheet.Cells["B3"].Value = 25;
var command = new SortCommand(sheet, range, SortOrder.Ascending, 0, skipHeaderRow: true);
// Act
var result = command.Execute();
// Assert
Assert.True(result);
Assert.Equal("Alice", sheet.Cells["A2"].Value);
Assert.Equal(25.0, sheet.Cells["B2"].Value);
Assert.Equal("Charlie", sheet.Cells["A3"].Value);
Assert.Equal(30.0, sheet.Cells["B3"].Value);
}
}

View File

@@ -1,231 +0,0 @@
using Xunit;
namespace Radzen.Blazor.Spreadsheet.Tests;
public class SortingTests
{
private readonly Sheet sheet = new(5, 5);
[Fact]
public void Should_SortStringsInAscendingOrder()
{
sheet.Cells[0, 0].Value = "C";
sheet.Cells[1, 0].Value = "A";
sheet.Cells[2, 0].Value = "B";
sheet.Sort(RangeRef.Parse("A1:A3"), SortOrder.Ascending);
Assert.Equal("A", sheet.Cells[0, 0].Value);
Assert.Equal("B", sheet.Cells[1, 0].Value);
Assert.Equal("C", sheet.Cells[2, 0].Value);
}
[Fact]
public void Should_SortStringsInDescendingOrder()
{
sheet.Cells[0, 0].Value = "C";
sheet.Cells[1, 0].Value = "A";
sheet.Cells[2, 0].Value = "B";
sheet.Sort(RangeRef.Parse("A1:A3"), SortOrder.Descending);
Assert.Equal("C", sheet.Cells[0, 0].Value);
Assert.Equal("B", sheet.Cells[1, 0].Value);
Assert.Equal("A", sheet.Cells[2, 0].Value);
}
[Fact]
public void Should_SortNumbersInDescendingOrder()
{
sheet.Cells[0, 0].Value = 1;
sheet.Cells[1, 0].Value = 3;
sheet.Cells[2, 0].Value = 2;
sheet.Sort(RangeRef.Parse("A1:A3"), SortOrder.Descending);
Assert.Equal(3d, sheet.Cells[0, 0].Value);
Assert.Equal(2d, sheet.Cells[1, 0].Value);
Assert.Equal(1d, sheet.Cells[2, 0].Value);
}
[Fact]
public void Should_SortFullRowsByColumnA()
{
sheet.Cells[0, 0].Value = "B"; sheet.Cells[0, 1].Value = "B2";
sheet.Cells[1, 0].Value = "A"; sheet.Cells[1, 1].Value = "A2";
sheet.Cells[2, 0].Value = "C"; sheet.Cells[2, 1].Value = "C2";
sheet.Sort(RangeRef.Parse("A1:B3"), SortOrder.Ascending);
Assert.Equal("A", sheet.Cells[0, 0].Value);
Assert.Equal("A2", sheet.Cells[0, 1].Value);
Assert.Equal("B", sheet.Cells[1, 0].Value);
Assert.Equal("B2", sheet.Cells[1, 1].Value);
Assert.Equal("C", sheet.Cells[2, 0].Value);
Assert.Equal("C2", sheet.Cells[2, 1].Value);
}
[Fact]
public void Should_IgnoreHeaderRowInSort()
{
sheet.Cells[0, 0].Value = "Header";
sheet.Cells[1, 0].Value = "C";
sheet.Cells[2, 0].Value = "A";
sheet.Cells[3, 0].Value = "B";
sheet.Sort(RangeRef.Parse("A2:A4"), SortOrder.Ascending);
Assert.Equal("Header", sheet.Cells[0, 0].Value); // header unchanged
Assert.Equal("A", sheet.Cells[1, 0].Value);
Assert.Equal("B", sheet.Cells[2, 0].Value);
Assert.Equal("C", sheet.Cells[3, 0].Value);
}
[Fact]
public void Should_SortOnlySelectedRange_NotWholeSheet()
{
sheet.Cells[0, 0].Value = "Z";
sheet.Cells[1, 0].Value = "X";
sheet.Cells[2, 0].Value = "Y";
sheet.Cells[3, 0].Value = "A";
sheet.Sort(RangeRef.Parse("A2:A4"), SortOrder.Ascending);
Assert.Equal("Z", sheet.Cells[0, 0].Value); // unchanged
Assert.Equal("A", sheet.Cells[1, 0].Value);
Assert.Equal("X", sheet.Cells[2, 0].Value);
Assert.Equal("Y", sheet.Cells[3, 0].Value);
}
[Fact]
public void Should_SortBlanksLastInAscendingOrder()
{
sheet.Cells[0, 0].Value = "B";
sheet.Cells[1, 0].Value = null; // blank
sheet.Cells[2, 0].Value = "A";
sheet.Sort(RangeRef.Parse("A1:A3"), SortOrder.Ascending);
Assert.Equal("A", sheet.Cells[0, 0].Value);
Assert.Equal("B", sheet.Cells[1, 0].Value);
Assert.Null(sheet.Cells[2, 0].Value);
}
[Fact]
public void Should_NotBreakDataAlignmentAcrossColumns()
{
sheet.Cells[0, 0].Value = "Charlie"; sheet.Cells[0, 1].Value = "30";
sheet.Cells[1, 0].Value = "Alice"; sheet.Cells[1, 1].Value = "25";
sheet.Cells[2, 0].Value = "Bob"; sheet.Cells[2, 1].Value = "20";
sheet.Sort(RangeRef.Parse("A1:B3"), SortOrder.Ascending);
Assert.Equal("Alice", sheet.Cells[0, 0].Value);
Assert.Equal(25d, sheet.Cells[0, 1].Value);
Assert.Equal("Bob", sheet.Cells[1, 0].Value);
Assert.Equal(20d, sheet.Cells[1, 1].Value);
Assert.Equal("Charlie", sheet.Cells[2, 0].Value);
Assert.Equal(30d, sheet.Cells[2, 1].Value);
}
[Fact]
public void Should_SortByAbsoluteColumnIndex()
{
// Data layout now starts at column B (index 1):
// | B (Name) | C (Age) | D (Dept) |
// |----------|---------|----------|
// | Charlie | 42 | Dev |
// | Alice | 25 | HR |
// | Bob | 30 | Sales |
sheet.Cells[0, 1].Value = "Charlie"; // B1
sheet.Cells[0, 2].Value = 42; // C1
sheet.Cells[0, 3].Value = "Dev"; // D1
sheet.Cells[1, 1].Value = "Alice"; // B2
sheet.Cells[1, 2].Value = 25; // C2
sheet.Cells[1, 3].Value = "HR"; // D2
sheet.Cells[2, 1].Value = "Bob"; // B3
sheet.Cells[2, 2].Value = 30; // C3
sheet.Cells[2, 3].Value = "Sales"; // D3
// Sort range B1:D3 by **column C** (absolute index 2, "Age")
sheet.Sort(RangeRef.Parse("B1:D3"), SortOrder.Ascending, keyIndex: 2);
// Sorted order: Alice (25), Bob (30), Charlie (42)
Assert.Equal("Alice", sheet.Cells[0, 1].Value);
Assert.Equal(25d, sheet.Cells[0, 2].Value);
Assert.Equal("HR", sheet.Cells[0, 3].Value);
Assert.Equal("Bob", sheet.Cells[1, 1].Value);
Assert.Equal(30d, sheet.Cells[1, 2].Value);
Assert.Equal("Sales", sheet.Cells[1, 3].Value);
Assert.Equal("Charlie", sheet.Cells[2, 1].Value);
Assert.Equal(42d, sheet.Cells[2, 2].Value);
Assert.Equal("Dev", sheet.Cells[2, 3].Value);
}
[Fact]
public void Should_SortCellsWithRelativeFormulasCorrectly()
{
// Initial setup:
// A1: 2
// A2: =A1 + 1 (should be 3)
// A3: 1
sheet.Cells[0, 0].Value = 2; // A1
sheet.Cells[1, 0].Formula = "=A1 + 1"; // A2
sheet.Cells[2, 0].Value = 1; // A3
// Confirm initial formula result
Assert.Equal(3d, sheet.Cells[1, 0].Value);
// Now sort A1:A3 in ascending order
sheet.Sort(RangeRef.Parse("A1:A3"), SortOrder.Ascending);
// The sorted order should be:
// A1: 1 (was A3)
// A2: 2 (was A1)
// A3: =A1 + 1 (was A2)
Assert.Equal(1d, sheet.Cells[0, 0].Value); // A1
Assert.Equal(2d, sheet.Cells[1, 0].Value); // A2
Assert.Equal("=A1 + 1", sheet.Cells[2, 0].Formula); // A3
Assert.Equal(2d, sheet.Cells[2, 0].Value); // A3 evaluated: =1 + 1
}
[Fact]
public void Should_PlaceBlankValuesAtBottomInAscendingAndDescending()
{
sheet.Cells[0, 0].Value = 3;
sheet.Cells[1, 0].Value = null; // blank
sheet.Cells[2, 0].Value = 1;
sheet.Cells[3, 0].Value = 2;
// Sort ascending
sheet.Sort(RangeRef.Parse("A1:A4"), SortOrder.Ascending);
Assert.Equal(1d, sheet.Cells[0, 0].Value);
Assert.Equal(2d, sheet.Cells[1, 0].Value);
Assert.Equal(3d, sheet.Cells[2, 0].Value);
Assert.Null(sheet.Cells[3, 0].Value);
// Re-set original order
sheet.Cells[0, 0].Value = 3;
sheet.Cells[1, 0].Value = null;
sheet.Cells[2, 0].Value = 1;
sheet.Cells[3, 0].Value = 2;
// Sort descending
sheet.Sort(RangeRef.Parse("A1:A4"), SortOrder.Descending);
Assert.Equal(3d, sheet.Cells[0, 0].Value);
Assert.Equal(2d, sheet.Cells[1, 0].Value);
Assert.Equal(1d, sheet.Cells[2, 0].Value);
Assert.Null(sheet.Cells[3, 0].Value);
}
}

View File

@@ -1,60 +0,0 @@
using Xunit;
namespace Radzen.Blazor.Spreadsheet.Tests;
public class SpreadsheetClipboardTests
{
[Fact]
public void Copy_Down_AdjustsRelative_KeepsAbsolute()
{
var sheet = new Sheet(10, 10);
sheet.Cells[0, 0].Formula = "=A1"; // A1
sheet.PasteRange(sheet, RangeRef.Parse("A1"), CellRef.Parse("A2"), FormulaAdjustment.AdjustRelative);
Assert.Equal("=A2", sheet.Cells[1, 0].Formula);
sheet.Cells[0, 1].Formula = "=$A$1"; // B1
sheet.PasteRange(sheet, RangeRef.Parse("B1"), CellRef.Parse("B2"), FormulaAdjustment.AdjustRelative);
Assert.Equal("=$A$1", sheet.Cells[1, 1].Formula);
}
[Fact]
public void Copy_Right_AdjustsColumn_RetainsAbsoluteColumn()
{
var sheet = new Sheet(10, 10);
sheet.Cells[0, 0].Formula = "=A1"; // A1
sheet.PasteRange(sheet, RangeRef.Parse("A1"), CellRef.Parse("B1"), FormulaAdjustment.AdjustRelative);
Assert.Equal("=B1", sheet.Cells[0, 1].Formula);
sheet.Cells[0, 1].Formula = "=$A1"; // B1 absolute column
sheet.PasteRange(sheet, RangeRef.Parse("B1"), CellRef.Parse("C1"), FormulaAdjustment.AdjustRelative);
Assert.Equal("=$A1", sheet.Cells[0, 2].Formula);
}
[Fact]
public void Cut_DoesNotAdjustFormula()
{
var sheet = new Sheet(10, 10);
sheet.Cells[0, 0].Formula = "=A1"; // A1
sheet.Selection.Select(CellRef.Parse("A1"));
var clipboard = new SpreadsheetClipboard();
clipboard.Cut(sheet);
clipboard.Paste(sheet, CellRef.Parse("B2"));
Assert.Equal("=A1", sheet.Cells[1, 1].Formula); // not adjusted
Assert.Null(sheet.Cells[0, 0].Formula); // source cleared
Assert.Null(sheet.Cells[0, 0].Value);
}
[Fact]
public void Copy_AcrossSheets_AdjustsRelativeReferences()
{
var source = new Sheet(10, 10);
var target = new Sheet(10, 10);
source.Cells[0, 0].Formula = "=A1";
// Copy A1 from source to B2 in target
target.PasteRange(source, RangeRef.Parse("A1"), CellRef.Parse("B2"), FormulaAdjustment.AdjustRelative);
Assert.Equal("=B2", target.Cells[1, 1].Formula);
}
}

View File

@@ -1,33 +0,0 @@
using Xunit;
namespace Radzen.Blazor.Spreadsheet.Tests;
public class SubstituteFunctionTests
{
[Fact]
public void Substitute_AllOccurrences()
{
var sheet = new Sheet(10, 10);
sheet.Cells["A2"].Value = "Sales Data";
sheet.Cells["B1"].Formula = "=SUBSTITUTE(A2, \"Sales\", \"Cost\")";
Assert.Equal("Cost Data", sheet.Cells["B1"].Data.Value);
}
[Fact]
public void Substitute_FirstInstanceOnly()
{
var sheet = new Sheet(10, 10);
sheet.Cells["A3"].Value = "Quarter 1, 2008";
sheet.Cells["B1"].Formula = "=SUBSTITUTE(A3, \"1\", \"2\", 1)";
Assert.Equal("Quarter 2, 2008", sheet.Cells["B1"].Data.Value);
}
[Fact]
public void Substitute_ThirdInstanceOnly()
{
var sheet = new Sheet(10, 20);
sheet.Cells["A4"].Value = "Quarter 1, 2011";
sheet.Cells["B1"].Formula = "=SUBSTITUTE(A4, \"1\", \"2\", 3)";
Assert.Equal("Quarter 1, 2012", sheet.Cells["B1"].Data.Value);
}
}

View File

@@ -1,94 +0,0 @@
using Xunit;
namespace Radzen.Blazor.Spreadsheet.Tests;
public class SubtotalFunctionTests
{
readonly Sheet sheet = new(10, 10);
[Fact]
public void ShouldSumWithCode9()
{
sheet.Cells["A2"].Value = 120;
sheet.Cells["A3"].Value = 10;
sheet.Cells["A4"].Value = 150;
sheet.Cells["A5"].Value = 23;
sheet.Cells["B1"].Formula = "=SUBTOTAL(9,A2:A5)";
Assert.Equal(303d, sheet.Cells["B1"].Value);
}
[Fact]
public void ShouldAverageWithCode1()
{
sheet.Cells["A2"].Value = 120;
sheet.Cells["A3"].Value = 10;
sheet.Cells["A4"].Value = 150;
sheet.Cells["A5"].Value = 23;
sheet.Cells["B1"].Formula = "=SUBTOTAL(1,A2:A5)";
Assert.Equal(75.75, sheet.Cells["B1"].Value);
}
[Fact]
public void ShouldCountWithCode2()
{
sheet.Cells["A2"].Value = 120;
sheet.Cells["A3"].Value = "x"; // non-numeric, ignored by COUNT
sheet.Cells["A4"].Value = 150;
sheet.Cells["A5"].Value = null;
sheet.Cells["B1"].Formula = "=SUBTOTAL(2,A2:A5)";
Assert.Equal(2d, sheet.Cells["B1"].Value);
}
[Fact]
public void ShouldCountAWithCode3()
{
sheet.Cells["A2"].Value = 120;
sheet.Cells["A3"].Value = "x";
sheet.Cells["A4"].Value = null;
sheet.Cells["A5"].Value = 23;
sheet.Cells["B1"].Formula = "=SUBTOTAL(3,A2:A5)";
Assert.Equal(3d, sheet.Cells["B1"].Value);
}
[Fact]
public void ShouldMaxWithCode4()
{
sheet.Cells["A2"].Value = 10;
sheet.Cells["A3"].Value = 40;
sheet.Cells["A4"].Value = 30;
sheet.Cells["A5"].Value = 20;
sheet.Cells["B1"].Formula = "=SUBTOTAL(4,A2:A5)";
Assert.Equal(40d, sheet.Cells["B1"].Value);
}
[Fact]
public void ShouldMinWithCode5()
{
sheet.Cells["A2"].Value = 10;
sheet.Cells["A3"].Value = 40;
sheet.Cells["A4"].Value = 30;
sheet.Cells["A5"].Value = 20;
sheet.Cells["B1"].Formula = "=SUBTOTAL(5,A2:A5)";
Assert.Equal(10d, sheet.Cells["B1"].Value);
}
[Fact]
public void ShouldRespectHiddenRowsWith109()
{
sheet.Cells["A2"].Value = 120;
sheet.Cells["A3"].Value = 10;
sheet.Cells["A4"].Value = 150;
sheet.Cells["A5"].Value = 20;
sheet.Rows.Hide(2); // hide row 3 (A3)
sheet.Cells["B1"].Formula = "=SUBTOTAL(109,A2:A5)";
Assert.Equal(290d, sheet.Cells["B1"].Value); // 120 + 150 + 20 (excludes hidden 10)
}
}

View File

@@ -1,73 +0,0 @@
using Xunit;
namespace Radzen.Blazor.Spreadsheet.Tests;
public class SumFunctionTests
{
readonly Sheet sheet = new(5, 5);
[Fact]
public void ShouldEvaluateSumFunction()
{
sheet.Cells["A1"].Value = 1;
sheet.Cells["A2"].Value = 2;
sheet.Cells["A3"].Formula = "=SUM(A1,A2)";
Assert.Equal(3d, sheet.Cells["A3"].Value);
}
[Fact]
public void ShouldEvaluateSumFunctionWithEmptyCells()
{
sheet.Cells["A1"].Value = 1;
sheet.Cells["A3"].Formula = "=SUM(A1,A2)";
Assert.Equal(1d, sheet.Cells["A3"].Value);
}
[Fact]
public void ShouldEvaluateSumFunctionWithMultipleArguments()
{
sheet.Cells["A1"].Value = 1;
sheet.Cells["A2"].Value = 2;
sheet.Cells["A3"].Value = 3;
sheet.Cells["A4"].Formula = "=SUM(A1,A2,A3)";
Assert.Equal(6d, sheet.Cells["A4"].Value);
}
[Fact]
public void ShouldReturnValueErrorForEmptySumFunction()
{
sheet.Cells["A1"].Formula = "=SUM()";
Assert.Equal(CellError.Value, sheet.Cells["A1"].Value);
}
[Fact]
public void ShouldSumRangeOfCells()
{
sheet.Cells["A1"].Value = 1;
sheet.Cells["A2"].Value = 2;
sheet.Cells["A3"].Formula = "=SUM(A1:A2)";
Assert.Equal(3d, sheet.Cells["A3"].Value);
}
[Fact]
public void ShouldSumNumbersOfDifferentTypes()
{
sheet.Cells["A1"].Value = 1;
sheet.Cells["A2"].Value = 2.5;
sheet.Cells["A3"].Formula = "=SUM(A1,A2)";
Assert.Equal(3.5, sheet.Cells["A3"].Value);
sheet.Cells["A4"].Value = 2.5;
sheet.Cells["A5"].Formula = "=SUM(A4,A1)";
Assert.Equal(3.5, sheet.Cells["A5"].Value);
}
}

View File

@@ -1,278 +0,0 @@
using Xunit;
namespace Radzen.Blazor.Spreadsheet.Tests;
public class SumIfFunctionTests
{
readonly Sheet sheet = new(10, 10);
[Fact]
public void ShouldEvaluateSumIfFunctionWithNumericCriteria()
{
// Test data from Excel example
sheet.Cells["A1"].Value = 100000;
sheet.Cells["A2"].Value = 200000;
sheet.Cells["A3"].Value = 300000;
sheet.Cells["A4"].Value = 400000;
sheet.Cells["B1"].Value = 7000;
sheet.Cells["B2"].Value = 14000;
sheet.Cells["B3"].Value = 21000;
sheet.Cells["B4"].Value = 28000;
sheet.Cells["C1"].Formula = "=SUMIF(A1:A4,\">160000\",B1:B4)";
Assert.Equal(63000d, sheet.Cells["C1"].Value);
}
[Fact]
public void ShouldEvaluateSumIfFunctionWithoutSumRange()
{
// Test data from Excel example
sheet.Cells["A1"].Value = 100000;
sheet.Cells["A2"].Value = 200000;
sheet.Cells["A3"].Value = 300000;
sheet.Cells["A4"].Value = 400000;
sheet.Cells["C1"].Formula = "=SUMIF(A1:A4,\">160000\")";
Assert.Equal(900000d, sheet.Cells["C1"].Value);
}
[Fact]
public void ShouldEvaluateSumIfFunctionWithExactMatch()
{
// Test data from Excel example
sheet.Cells["A1"].Value = 100000;
sheet.Cells["A2"].Value = 200000;
sheet.Cells["A3"].Value = 300000;
sheet.Cells["A4"].Value = 400000;
sheet.Cells["B1"].Value = 7000;
sheet.Cells["B2"].Value = 14000;
sheet.Cells["B3"].Value = 21000;
sheet.Cells["B4"].Value = 28000;
sheet.Cells["C1"].Formula = "=SUMIF(A1:A4,300000,B1:B4)";
Assert.Equal(21000d, sheet.Cells["C1"].Value);
}
[Fact]
public void ShouldEvaluateSumIfFunctionWithStringCriteria()
{
// Test data from Excel example
sheet.Cells["A1"].Value = "Vegetables";
sheet.Cells["A2"].Value = "Vegetables";
sheet.Cells["A3"].Value = "Fruits";
sheet.Cells["A4"].Value = "";
sheet.Cells["A5"].Value = "Vegetables";
sheet.Cells["A6"].Value = "Fruits";
sheet.Cells["C1"].Value = 2300;
sheet.Cells["C2"].Value = 5500;
sheet.Cells["C3"].Value = 800;
sheet.Cells["C4"].Value = 400;
sheet.Cells["C5"].Value = 4200;
sheet.Cells["C6"].Value = 1200;
sheet.Cells["D1"].Formula = "=SUMIF(A1:A6,\"Fruits\",C1:C6)";
Assert.Equal(2000d, sheet.Cells["D1"].Value);
}
[Fact]
public void ShouldEvaluateSumIfFunctionWithWildcardPattern()
{
// Test data from Excel example
sheet.Cells["A1"].Value = "Vegetables";
sheet.Cells["A2"].Value = "Vegetables";
sheet.Cells["A3"].Value = "Fruits";
sheet.Cells["A4"].Value = "";
sheet.Cells["A5"].Value = "Vegetables";
sheet.Cells["A6"].Value = "Fruits";
sheet.Cells["B1"].Value = "Tomatoes";
sheet.Cells["B2"].Value = "Celery";
sheet.Cells["B3"].Value = "Oranges";
sheet.Cells["B4"].Value = "Butter";
sheet.Cells["B5"].Value = "Carrots";
sheet.Cells["B6"].Value = "Apples";
sheet.Cells["C1"].Value = 2300;
sheet.Cells["C2"].Value = 5500;
sheet.Cells["C3"].Value = 800;
sheet.Cells["C4"].Value = 400;
sheet.Cells["C5"].Value = 4200;
sheet.Cells["C6"].Value = 1200;
sheet.Cells["D1"].Formula = "=SUMIF(B1:B6,\"*es\",C1:C6)";
Assert.Equal(4300d, sheet.Cells["D1"].Value);
}
[Fact]
public void ShouldEvaluateSumIfFunctionWithEmptyCriteria()
{
// Test data from Excel example
sheet.Cells["A1"].Value = "Vegetables";
sheet.Cells["A2"].Value = "Vegetables";
sheet.Cells["A3"].Value = "Fruits";
sheet.Cells["A4"].Value = "";
sheet.Cells["A5"].Value = "Vegetables";
sheet.Cells["A6"].Value = "Fruits";
sheet.Cells["C1"].Value = 2300;
sheet.Cells["C2"].Value = 5500;
sheet.Cells["C3"].Value = 800;
sheet.Cells["C4"].Value = 400;
sheet.Cells["C5"].Value = 4200;
sheet.Cells["C6"].Value = 1200;
sheet.Cells["D1"].Formula = "=SUMIF(A1:A6,\"\",C1:C6)";
Assert.Equal(400d, sheet.Cells["D1"].Value);
}
[Fact]
public void ShouldEvaluateSumIfFunctionWithLessThanCriteria()
{
sheet.Cells["A1"].Value = 10;
sheet.Cells["A2"].Value = 20;
sheet.Cells["A3"].Value = 30;
sheet.Cells["A4"].Value = 40;
sheet.Cells["B1"].Value = 100;
sheet.Cells["B2"].Value = 200;
sheet.Cells["B3"].Value = 300;
sheet.Cells["B4"].Value = 400;
sheet.Cells["C1"].Formula = "=SUMIF(A1:A4,\"<25\",B1:B4)";
Assert.Equal(300d, sheet.Cells["C1"].Value);
}
[Fact]
public void ShouldEvaluateSumIfFunctionWithGreaterThanOrEqualCriteria()
{
sheet.Cells["A1"].Value = 10;
sheet.Cells["A2"].Value = 20;
sheet.Cells["A3"].Value = 30;
sheet.Cells["A4"].Value = 40;
sheet.Cells["B1"].Value = 100;
sheet.Cells["B2"].Value = 200;
sheet.Cells["B3"].Value = 300;
sheet.Cells["B4"].Value = 400;
sheet.Cells["C1"].Formula = "=SUMIF(A1:A4,\">=25\",B1:B4)";
Assert.Equal(700d, sheet.Cells["C1"].Value);
}
[Fact]
public void ShouldEvaluateSumIfFunctionWithNotEqualCriteria()
{
sheet.Cells["A1"].Value = 10;
sheet.Cells["A2"].Value = 20;
sheet.Cells["A3"].Value = 30;
sheet.Cells["A4"].Value = 40;
sheet.Cells["B1"].Value = 100;
sheet.Cells["B2"].Value = 200;
sheet.Cells["B3"].Value = 300;
sheet.Cells["B4"].Value = 400;
sheet.Cells["C1"].Formula = "=SUMIF(A1:A4,\"<>20\",B1:B4)";
Assert.Equal(800d, sheet.Cells["C1"].Value);
}
[Fact]
public void ShouldEvaluateSumIfFunctionWithQuestionMarkWildcard()
{
sheet.Cells["A1"].Value = "cat";
sheet.Cells["A2"].Value = "bat";
sheet.Cells["A3"].Value = "rat";
sheet.Cells["A4"].Value = "goat";
sheet.Cells["B1"].Value = 10;
sheet.Cells["B2"].Value = 20;
sheet.Cells["B3"].Value = 30;
sheet.Cells["B4"].Value = 40;
sheet.Cells["C1"].Formula = "=SUMIF(A1:A4,\"?at\",B1:B4)";
Assert.Equal(60d, sheet.Cells["C1"].Value);
}
[Fact]
public void ShouldEvaluateSumIfFunctionWithEscapedWildcards()
{
sheet.Cells["A1"].Value = "*";
sheet.Cells["A2"].Value = "?";
sheet.Cells["A3"].Value = "test";
sheet.Cells["B1"].Value = 10;
sheet.Cells["B2"].Value = 20;
sheet.Cells["B3"].Value = 30;
sheet.Cells["C1"].Formula = "=SUMIF(A1:A3,\"~*\",B1:B3)";
Assert.Equal(10d, sheet.Cells["C1"].Value);
}
[Fact]
public void ShouldReturnValueErrorForInvalidArgumentCount()
{
sheet.Cells["A1"].Formula = "=SUMIF(A2)";
Assert.Equal(CellError.Value, sheet.Cells["A1"].Value);
sheet.Cells["A2"].Formula = "=SUMIF(A3,A4,A5,A6)";
Assert.Equal(CellError.Value, sheet.Cells["A2"].Value);
}
[Fact]
public void ShouldReturnValueErrorForMismatchedRangeSizes()
{
sheet.Cells["A1"].Value = 1;
sheet.Cells["A2"].Value = 2;
sheet.Cells["B1"].Value = 10;
sheet.Cells["C1"].Formula = "=SUMIF(A1:A2,\">0\",B1)";
Assert.Equal(CellError.Value, sheet.Cells["C1"].Value);
}
[Fact]
public void ShouldHandleEmptyCellsInSumRange()
{
sheet.Cells["A1"].Value = 10;
sheet.Cells["A2"].Value = 20;
sheet.Cells["A3"].Value = 30;
// B2 is empty
sheet.Cells["B1"].Value = 100;
sheet.Cells["B3"].Value = 300;
sheet.Cells["C1"].Formula = "=SUMIF(A1:A3,\">15\",B1:B3)";
Assert.Equal(300d, sheet.Cells["C1"].Value);
}
[Fact]
public void ShouldHandleNonNumericValuesInSumRange()
{
sheet.Cells["A1"].Value = 10;
sheet.Cells["A2"].Value = 20;
sheet.Cells["A3"].Value = 30;
sheet.Cells["B1"].Value = 100;
sheet.Cells["B2"].Value = "text"; // Non-numeric
sheet.Cells["B3"].Value = 300;
sheet.Cells["C1"].Formula = "=SUMIF(A1:A3,\">15\",B1:B3)";
Assert.Equal(300d, sheet.Cells["C1"].Value);
}
}

View File

@@ -1,53 +0,0 @@
using Xunit;
namespace Radzen.Blazor.Spreadsheet.Tests;
public class TextFunctionTests
{
[Fact]
public void Text_Currency_Thousands_TwoDecimals()
{
var sheet = new Sheet(10, 10);
sheet.Cells["A1"].Value = 1234.567;
sheet.Cells["B1"].Formula = "=TEXT(A1,\"$#,##0.00\")";
Assert.Equal("$1,234.57", sheet.Cells["B1"].Data.Value);
}
[Fact]
public void Text_Date_MMDDYY()
{
var sheet = new Sheet(10, 10);
var dt = new System.DateTime(2012, 3, 14);
sheet.Cells["A1"].Value = dt;
sheet.Cells["B1"].Formula = "=TEXT(A1,\"MM/DD/YY\")";
Assert.Equal("03/14/12", sheet.Cells["B1"].Data.Value);
}
[Fact]
public void Text_Time_12h_AMPM()
{
var sheet = new Sheet(10, 10);
var dt = new System.DateTime(2020, 1, 1, 13, 29, 0);
sheet.Cells["A1"].Value = dt;
sheet.Cells["B1"].Formula = "=TEXT(A1,\"H:MM AM/PM\")";
Assert.Equal("1:29 PM", sheet.Cells["B1"].Data.Value);
}
[Fact]
public void Text_Percent_OneDecimal()
{
var sheet = new Sheet(10, 10);
sheet.Cells["A1"].Value = 0.285;
sheet.Cells["B1"].Formula = "=TEXT(A1,\"0.0%\")";
Assert.Equal("28.5%", sheet.Cells["B1"].Data.Value);
}
[Fact]
public void Text_LeadingZeros()
{
var sheet = new Sheet(10, 10);
sheet.Cells["A1"].Value = 1234;
sheet.Cells["B1"].Formula = "=TEXT(A1,\"0000000\")";
Assert.Equal("0001234", sheet.Cells["B1"].Data.Value);
}
}

View File

@@ -1,57 +0,0 @@
using Xunit;
namespace Radzen.Blazor.Spreadsheet.Tests;
public class TextJoinFunctionTests
{
[Fact]
public void TextJoin_Literals_IgnoreEmptyTrue()
{
var sheet = new Sheet(10, 10);
sheet.Cells["A1"].Formula = "=TEXTJOIN(\" \",TRUE,\"The\",\"sun\",\"will\",\"come\",\"up\",\"tomorrow.\")";
Assert.Equal("The sun will come up tomorrow.", sheet.Cells["A1"].Data.Value);
}
[Fact]
public void TextJoin_Range_CommaSpace_IgnoreEmptyTrue()
{
var sheet = new Sheet(20, 10);
// A2:A8 values
sheet.Cells["A2"].Value = "US Dollar";
sheet.Cells["A3"].Value = "Australian Dollar";
sheet.Cells["A4"].Value = "Chinese Yuan";
sheet.Cells["A5"].Value = "Hong Kong Dollar";
sheet.Cells["A6"].Value = "Israeli Shekel";
sheet.Cells["A7"].Value = "South Korean Won";
sheet.Cells["A8"].Value = "Russian Ruble";
sheet.Cells["B1"].Formula = "=TEXTJOIN(\", \", TRUE, A2:A8)";
var result = sheet.Cells["B1"].Data.GetValueOrDefault<string>();
Assert.Equal("US Dollar, Australian Dollar, Chinese Yuan, Hong Kong Dollar, Israeli Shekel, South Korean Won, Russian Ruble", result);
}
[Fact]
public void TextJoin_Range2D_CommaSpace_IgnoreEmptyVariants()
{
var sheet = new Sheet(20, 10);
// A2:B8 grid
sheet.Cells["A2"].Value = "a1";
sheet.Cells["B2"].Value = "b1";
sheet.Cells["A3"].Value = "a2";
sheet.Cells["B3"].Value = "b2";
sheet.Cells["A4"].Value = string.Empty; // empty cell value
sheet.Cells["B4"].Value = string.Empty;
sheet.Cells["A5"].Value = "a5";
sheet.Cells["B5"].Value = "b5";
sheet.Cells["A6"].Value = "a6";
sheet.Cells["B6"].Value = "b6";
sheet.Cells["A7"].Value = "a7";
sheet.Cells["B7"].Value = "b7";
sheet.Cells["B1"].Formula = "=TEXTJOIN(\", \", TRUE, A2:B7)";
Assert.Equal("a1, b1, a2, b2, a5, b5, a6, b6, a7, b7", sheet.Cells["B1"].Data.Value);
sheet.Cells["C1"].Formula = "=TEXTJOIN(\", \", FALSE, A2:B7)";
Assert.Equal("a1, b1, a2, b2, , , a5, b5, a6, b6, a7, b7", sheet.Cells["C1"].Data.Value);
}
}

View File

@@ -1,26 +0,0 @@
using Xunit;
using Radzen.Blazor.Spreadsheet;
namespace Radzen.Blazor.Spreadsheet.Tests;
public class TodayFunctionTests
{
[Fact]
public void Today_ReturnsCurrentDate()
{
var sheet = new Sheet(10, 10);
sheet.Cells["A1"].Formula = "=TODAY()";
var dt = sheet.Cells["A1"].Data.GetValueOrDefault<System.DateTime>();
Assert.Equal(System.DateTime.Today.Date, dt.Date);
}
[Fact]
public void Today_PlusFiveDays()
{
var sheet = new Sheet(10, 10);
sheet.Cells["A1"].Formula = "=TODAY()+5";
var serial = sheet.Cells["A1"].Data.GetValueOrDefault<double>();
var expected = System.DateTime.Today.AddDays(5).ToNumber();
Assert.Equal(expected, serial);
}
}

View File

@@ -1,31 +0,0 @@
using Xunit;
namespace Radzen.Blazor.Spreadsheet.Tests;
public class TrimFunctionTests
{
[Fact]
public void Trim_RemovesLeadingTrailingAndCollapsesInternalSpaces()
{
var sheet = new Sheet(10, 10);
sheet.Cells["A1"].Formula = "=TRIM(\" First Quarter Earnings \")";
Assert.Equal("First Quarter Earnings", sheet.Cells["A1"].Data.Value);
}
[Fact]
public void Trim_Empty_ReturnsEmpty()
{
var sheet = new Sheet(10, 10);
sheet.Cells["A1"].Formula = "=TRIM(\" \")";
Assert.Equal(string.Empty, sheet.Cells["A1"].Data.Value);
}
[Fact]
public void Trim_TextCell_Works()
{
var sheet = new Sheet(10, 10);
sheet.Cells["B1"].Value = " Hello world ";
sheet.Cells["A1"].Formula = "=TRIM(B1)";
Assert.Equal("Hello world", sheet.Cells["A1"].Data.Value);
}
}

View File

@@ -1,31 +0,0 @@
using Xunit;
namespace Radzen.Blazor.Spreadsheet.Tests;
public class TruncFunctionTests
{
readonly Sheet sheet = new(10, 10);
[Fact]
public void ShouldTruncatePositive()
{
sheet.Cells["A1"].Formula = "=TRUNC(8.9)";
Assert.Equal(8d, sheet.Cells["A1"].Value);
}
[Fact]
public void ShouldTruncateNegative()
{
sheet.Cells["A1"].Formula = "=TRUNC(0-8.9)";
Assert.Equal(-8d, sheet.Cells["A1"].Value);
}
[Fact]
public void ShouldTruncateLessThanOne()
{
sheet.Cells["A1"].Formula = "=TRUNC(0.45)";
Assert.Equal(0d, sheet.Cells["A1"].Value);
}
}

View File

@@ -1,24 +0,0 @@
using Xunit;
namespace Radzen.Blazor.Spreadsheet.Tests;
public class UpperFunctionTests
{
[Fact]
public void Upper_ConvertsToUppercase()
{
var sheet = new Sheet(10, 10);
sheet.Cells["A2"].Value = "total";
sheet.Cells["B1"].Formula = "=UPPER(A2)";
Assert.Equal("TOTAL", sheet.Cells["B1"].Data.Value);
}
[Fact]
public void Upper_AlreadyUppercase()
{
var sheet = new Sheet(10, 10);
sheet.Cells["A3"].Value = "Yield";
sheet.Cells["B1"].Formula = "=UPPER(A3)";
Assert.Equal("YIELD", sheet.Cells["B1"].Data.Value);
}
}

View File

@@ -1,22 +0,0 @@
using Xunit;
namespace Radzen.Blazor.Spreadsheet.Tests;
public class ValueFunctionTests
{
[Fact]
public void Value_ParsesCurrency()
{
var sheet = new Sheet(10, 10);
sheet.Cells["A1"].Formula = "=VALUE(\"$1,000\")";
Assert.Equal(1000d, sheet.Cells["A1"].Data.Value);
}
[Fact]
public void Value_TimeDifferenceFractionOfDay()
{
var sheet = new Sheet(10, 10);
sheet.Cells["A1"].Formula = "=VALUE(\"16:48:00\")-VALUE(\"12:00:00\")";
Assert.Equal(0.2d, sheet.Cells["A1"].Data.Value);
}
}

View File

@@ -1,61 +0,0 @@
using Xunit;
namespace Radzen.Blazor.Spreadsheet.Tests;
public class VerticalLookupFunctionTests
{
readonly Sheet sheet = new(10, 10);
[Fact]
public void ShouldFindExactMatchInTwoColumnRange()
{
sheet.Cells["A1"].Value = "T-Shirt";
sheet.Cells["A2"].Value = "Jeans";
sheet.Cells["B1"].Value = 19.99;
sheet.Cells["B2"].Value = 29.99;
sheet.Cells["C1"].Formula = "=VLOOKUP(\"T-Shirt\",A1:B2,2,0)";
Assert.Equal(19.99, sheet.Cells["C1"].Value);
}
[Fact]
public void ShouldReturnNAWhenNoExactMatch()
{
sheet.Cells["A1"].Value = "Hat";
sheet.Cells["B1"].Value = 9.99;
sheet.Cells["C1"].Formula = "=VLOOKUP(\"Gloves\",A1:B1,2,0)";
Assert.Equal(CellError.NA, sheet.Cells["C1"].Value);
}
[Fact]
public void ShouldFindApproximateMatchInSortedFirstColumn()
{
// First column sorted ascending
sheet.Cells["A1"].Value = 10;
sheet.Cells["A2"].Value = 20;
sheet.Cells["A3"].Value = 30;
sheet.Cells["B1"].Value = "Low";
sheet.Cells["B2"].Value = "Medium";
sheet.Cells["B3"].Value = "High";
// search_key 25 -> should pick row with 20
sheet.Cells["C1"].Formula = "=VLOOKUP(25,A1:B3,2,1)";
Assert.Equal("Medium", sheet.Cells["C1"].Value);
}
[Fact]
public void ShouldErrorWhenIndexOutOfRange()
{
sheet.Cells["A1"].Value = "X";
sheet.Cells["B1"].Value = 1;
sheet.Cells["C1"].Formula = "=VLOOKUP(\"X\",A1:B1,3,0)";
Assert.Equal(CellError.Ref, sheet.Cells["C1"].Value);
}
}

View File

@@ -1,42 +0,0 @@
using Xunit;
namespace Radzen.Blazor.Spreadsheet.Tests;
public class WeekdayFunctionTests
{
[Fact]
public void Weekday_Default_SundayToSaturday()
{
var sheet = new Sheet(10, 10);
sheet.Cells["A1"].Data = CellData.FromDate(new System.DateTime(2008, 2, 14)); // Thursday
sheet.Cells["B1"].Formula = "=WEEKDAY(A1)"; // default 1: Sun=1..Sat=7
Assert.Equal(5, sheet.Cells["B1"].Data.GetValueOrDefault<double>());
}
[Fact]
public void Weekday_Type2_MondayToSunday()
{
var sheet = new Sheet(10, 10);
sheet.Cells["A1"].Data = CellData.FromDate(new System.DateTime(2008, 2, 14)); // Thursday
sheet.Cells["B1"].Formula = "=WEEKDAY(A1, 2)"; // Mon=1..Sun=7
Assert.Equal(4, sheet.Cells["B1"].Data.GetValueOrDefault<double>());
}
[Fact]
public void Weekday_Type3_MondayZero_SundaySix()
{
var sheet = new Sheet(10, 10);
sheet.Cells["A1"].Data = CellData.FromDate(new System.DateTime(2008, 2, 14)); // Thursday
sheet.Cells["B1"].Formula = "=WEEKDAY(A1, 3)"; // Mon=0..Sun=6
Assert.Equal(3, sheet.Cells["B1"].Data.GetValueOrDefault<double>());
}
[Fact]
public void Weekday_InvalidReturnType_ReturnsNumError()
{
var sheet = new Sheet(10, 10);
sheet.Cells["A1"].Data = CellData.FromDate(new System.DateTime(2008, 2, 14));
sheet.Cells["B1"].Formula = "=WEEKDAY(A1, 10)"; // invalid
Assert.Equal(CellError.Num, sheet.Cells["B1"].Data.GetValueOrDefault<CellError>());
}
}

View File

@@ -1,42 +0,0 @@
using Xunit;
namespace Radzen.Blazor.Spreadsheet.Tests;
public class WeeknumFunctionTests
{
[Fact]
public void Weeknum_Default_SundayStart()
{
var sheet = new Sheet(10, 10);
sheet.Cells["A1"].Data = CellData.FromDate(new System.DateTime(2012, 3, 9)); // Excel example
sheet.Cells["B1"].Formula = "=WEEKNUM(A1)"; // default 1
Assert.Equal(10, sheet.Cells["B1"].Data.GetValueOrDefault<double>());
}
[Fact]
public void Weeknum_Type2_MondayStart()
{
var sheet = new Sheet(10, 10);
sheet.Cells["A1"].Data = CellData.FromDate(new System.DateTime(2012, 3, 9));
sheet.Cells["B1"].Formula = "=WEEKNUM(A1, 2)"; // Monday start, System 1
Assert.Equal(11, sheet.Cells["B1"].Data.GetValueOrDefault<double>());
}
[Fact]
public void Weeknum_Type21_ISO()
{
var sheet = new Sheet(10, 10);
sheet.Cells["A1"].Data = CellData.FromDate(new System.DateTime(2012, 3, 9));
sheet.Cells["B1"].Formula = "=WEEKNUM(A1, 21)"; // ISO
Assert.Equal(10, sheet.Cells["B1"].Data.GetValueOrDefault<double>());
}
[Fact]
public void Weeknum_InvalidReturnType_ReturnsNumError()
{
var sheet = new Sheet(10, 10);
sheet.Cells["A1"].Data = CellData.FromDate(new System.DateTime(2012, 3, 9));
sheet.Cells["B1"].Formula = "=WEEKNUM(A1, 4)"; // invalid code
Assert.Equal(CellError.Num, sheet.Cells["B1"].Data.GetValueOrDefault<CellError>());
}
}

View File

@@ -1,114 +0,0 @@
using Xunit;
namespace Radzen.Blazor.Spreadsheet.Tests;
public class XLookupFunctionTests
{
readonly Sheet sheet = new(20, 10);
[Fact]
public void ShouldFindExactMatchAndReturnFromAnotherColumn()
{
sheet.Cells["A1"].Value = "P1";
sheet.Cells["A2"].Value = "P2";
sheet.Cells["B1"].Value = 10;
sheet.Cells["B2"].Value = 20;
sheet.Cells["C1"].Formula = "=XLOOKUP(\"P2\",A1:A2,B1:B2)";
Assert.Equal(20d, sheet.Cells["C1"].Value);
}
[Fact]
public void ShouldReturnIfNotFoundValue()
{
sheet.Cells["A1"].Value = "P1";
sheet.Cells["B1"].Value = 10;
sheet.Cells["C1"].Formula = "=XLOOKUP(\"P2\",A1:A1,B1:B1,\"Missing\")";
Assert.Equal("Missing", sheet.Cells["C1"].Value);
}
[Fact]
public void ShouldSupportWildcardMatch()
{
sheet.Cells["A1"].Value = "Item-100";
sheet.Cells["A2"].Value = "Item-200";
sheet.Cells["B1"].Value = "A";
sheet.Cells["B2"].Value = "B";
sheet.Cells["C1"].Formula = "=XLOOKUP(\"Item-2*\",A1:A2,B1:B2,\"\",2)";
Assert.Equal("B", sheet.Cells["C1"].Value);
}
[Fact]
public void ShouldFindNextSmallerWhenNotFound()
{
sheet.Cells["A1"].Value = 10;
sheet.Cells["A2"].Value = 20;
sheet.Cells["A3"].Value = 30;
sheet.Cells["B1"].Value = "L";
sheet.Cells["B2"].Value = "M";
sheet.Cells["B3"].Value = "H";
sheet.Cells["C1"].Formula = "=XLOOKUP(25,A1:A3,B1:B3,\"\",0-1)";
Assert.Equal("M", sheet.Cells["C1"].Value);
}
[Fact]
public void ShouldFindNextLargerWhenNotFound()
{
sheet.Cells["A1"].Value = 10;
sheet.Cells["A2"].Value = 20;
sheet.Cells["A3"].Value = 30;
sheet.Cells["B1"].Value = "L";
sheet.Cells["B2"].Value = "M";
sheet.Cells["B3"].Value = "H";
sheet.Cells["C1"].Formula = "=XLOOKUP(25,A1:A3,B1:B3,\"\",1)";
Assert.Equal("H", sheet.Cells["C1"].Value);
}
[Fact]
public void ShouldSupportReverseSearch()
{
sheet.Cells["A1"].Value = "A";
sheet.Cells["A2"].Value = "A";
sheet.Cells["B1"].Value = 1;
sheet.Cells["B2"].Value = 2;
sheet.Cells["C1"].Formula = "=XLOOKUP(\"A\",A1:A2,B1:B2,\"\",0,0-1)";
Assert.Equal(2d, sheet.Cells["C1"].Value);
}
[Fact]
public void ShouldSupportBinarySearchAscending()
{
sheet.Cells["A1"].Value = 10;
sheet.Cells["A2"].Value = 20;
sheet.Cells["A3"].Value = 30;
sheet.Cells["B1"].Value = "L";
sheet.Cells["B2"].Value = "M";
sheet.Cells["B3"].Value = "H";
sheet.Cells["C1"].Formula = "=XLOOKUP(20,A1:A3,B1:B3,\"\",0,2)";
Assert.Equal("M", sheet.Cells["C1"].Value);
}
[Fact]
public void ShouldReturnNAWhenNotFoundAndNoIfNotFound()
{
sheet.Cells["A1"].Value = "A";
sheet.Cells["B1"].Value = 1;
sheet.Cells["C1"].Formula = "=XLOOKUP(\"B\",A1:A1,B1:B1)";
Assert.Equal(CellError.NA, sheet.Cells["C1"].Value);
}
}

View File

@@ -1,31 +0,0 @@
using Xunit;
namespace Radzen.Blazor.Spreadsheet.Tests;
public class YearFunctionTests
{
[Fact]
public void Year_FromDateSerial_ReturnsYear()
{
var sheet = new Sheet(10, 10);
sheet.Cells["A1"].Formula = "=YEAR(VALUE(\"2025-05-23\"))";
Assert.Equal(2025, sheet.Cells["A1"].Data.GetValueOrDefault<double>());
}
[Fact]
public void Year_FromDateValue_ReturnsYear()
{
var sheet = new Sheet(10, 10);
sheet.Cells["A1"].Data = CellData.FromDate(new System.DateTime(2023, 7, 5));
sheet.Cells["B1"].Formula = "=YEAR(A1)";
Assert.Equal(2023, sheet.Cells["B1"].Data.GetValueOrDefault<double>());
}
[Fact]
public void Year_InvalidText_ReturnsValueError()
{
var sheet = new Sheet(10, 10);
sheet.Cells["A1"].Formula = "=YEAR(\"abc\")";
Assert.Equal(CellError.Value, sheet.Cells["A1"].Data.GetValueOrDefault<CellError>());
}
}

View File

@@ -508,7 +508,7 @@ namespace Radzen
/// <param name="item">The item.</param>
/// <param name="property">The property.</param>
/// <returns>System.Object.</returns>
public object? GetItemOrValueFromProperty(object? item, string property)
public virtual object? GetItemOrValueFromProperty(object? item, string property)
{
if (item != null)
{
@@ -651,7 +651,11 @@ namespace Radzen
/// <param name="shouldSelectOnChange">Should select item on item change with keyboard.</param>
protected virtual async Task HandleKeyPress(Microsoft.AspNetCore.Components.Web.KeyboardEventArgs args, bool isFilter = false, bool? shouldSelectOnChange = null)
{
if (Disabled || Data == null || args == null)
ArgumentNullException.ThrowIfNull(args);
var key = args.Code != null ? args.Code : args.Key;
if (Disabled || Data == null || args == null || key == null)
return;
List<object> items = Enumerable.Empty<object>().ToList();
@@ -675,8 +679,6 @@ namespace Radzen
}
}
var key = args.Code != null ? args.Code : args.Key;
if (!args.AltKey && (key == "ArrowDown" || key == "ArrowLeft" || key == "ArrowUp" || key == "ArrowRight"))
{
preventKeydown = true;
@@ -936,6 +938,25 @@ namespace Radzen
}
}
/// <summary>
/// Handles filter input changes (e.g. paste).
/// </summary>
/// <param name="args">The <see cref="ChangeEventArgs"/> instance containing the event data.</param>
protected virtual async Task OnFilterInput(ChangeEventArgs args)
{
ArgumentNullException.ThrowIfNull(args);
searchText = $"{args.Value}";
await SearchTextChanged.InvokeAsync(searchText);
if (ResetSelectedIndexOnFilter)
{
selectedIndex = -1;
}
Debounce(DebounceFilter, FilterDelay);
}
/// <summary>
/// Gets the load data arguments.
/// </summary>

View File

@@ -17,6 +17,12 @@ public class GroupRowRenderEventArgs
/// </summary>
public Group? Group { get; internal set; }
/// <summary>
/// Gets or sets a value indicating whether this group row is expandable.
/// </summary>
/// <value><c>true</c> if expandable; otherwise, <c>false</c>.</value>
public bool Expandable { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this group row is expanded.
/// </summary>

View File

@@ -0,0 +1,22 @@
using System.Threading.Tasks;
namespace Radzen.Blazor
{
/// <summary>
/// Non-generic contract for <see cref="RadzenSpiderChart"/> used by configuration components
/// like <see cref="RadzenSpiderLegend"/> without relying on reflection (important for trimming/AOT).
/// </summary>
public interface IRadzenSpiderChart
{
/// <summary>
/// Gets or sets the legend configuration for the chart.
/// </summary>
RadzenSpiderLegend Legend { get; set; }
/// <summary>
/// Requests the chart to refresh its rendering.
/// </summary>
Task Refresh();
}
}

View File

@@ -0,0 +1,29 @@
using Microsoft.AspNetCore.Components;
using System.Collections.Generic;
namespace Radzen.Blazor
{
/// <summary>
/// Non-generic contract for spider series so <see cref="RadzenSpiderChart"/> can be non-generic.
/// </summary>
internal interface IRadzenSpiderSeries
{
int Index { get; set; }
string Title { get; }
bool IsVisible { get; set; }
bool MarkersVisible { get; }
double MarkerSize { get; }
double StrokeWidth { get; }
IEnumerable<string> GetCategories();
IEnumerable<double> GetValues();
double GetValue(string category);
object? GetData(string category);
string FormatValue(double value);
double MeasureLegend();
RenderFragment RenderLegendItem();
void ForceUpdate();
}
}

View File

@@ -41,6 +41,65 @@ namespace Radzen
return source.Provider.CreateQuery(selectExpression);
}
/// <summary>
/// Projects each element of a sequence into a collection of property values.
/// </summary>
public static IQueryable Select(this IQueryable source, IEnumerable<string> propertyNames)
{
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(propertyNames);
var parameter = Expression.Parameter(source.ElementType, "x");
var bindings = new List<MemberBinding>();
var allProperties = source.ElementType.GetProperties();
foreach (var property in allProperties.Where(p => propertyNames.Contains(p.Name)))
{
bindings.Add(Expression.Bind(property, Expression.Property(parameter, property)));
}
var body = Expression.MemberInit(Expression.New(source.ElementType), bindings);
var delegateType = typeof(Func<,>).MakeGenericType(source.ElementType, source.ElementType);
var lambda = Expression.Lambda(delegateType, body, parameter);
var selectExpression = Expression.Call(typeof(Queryable),
nameof(Queryable.Select), [source.ElementType, source.ElementType], source.Expression,
Expression.Quote(lambda));
return source.Provider.CreateQuery(selectExpression);
}
/// <summary>
/// Projects each element of a sequence into a collection of property values.
/// </summary>
public static IQueryable<T> Select<T>(this IQueryable<T> source, IEnumerable<string> propertyNames)
{
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(propertyNames);
var parameter = Expression.Parameter(typeof(T), "x");
var bindings = new List<MemberBinding>();
var allProperties = typeof(T).GetProperties();
foreach (var property in allProperties.Where(p => propertyNames.Contains(p.Name)))
{
bindings.Add(Expression.Bind(property, Expression.Property(parameter, property)));
}
var body = Expression.MemberInit(Expression.New(typeof(T)), bindings);
var lambda = Expression.Lambda<Func<T, T>>(body, parameter);
var selectExpression = Expression.Call(typeof(Queryable),
nameof(Queryable.Select), [typeof(T), typeof(T)], source.Expression,
Expression.Quote(lambda));
return source.Provider.CreateQuery<T>(selectExpression);
}
/// <summary>
/// Projects each element of a sequence to an IEnumerable and flattens the resulting sequences into one sequence.
@@ -1695,7 +1754,7 @@ namespace Radzen
return columns
.Where(c => c.Filterable
&& c.FilterPropertyType != null
&& (!string.IsNullOrEmpty(c.GetFilterValue() as string)
&& (!(c.GetFilterValue() == null || (c.GetFilterValue() as string)?.Length == 0)
|| !c.CanSetFilterValue()
|| c.HasCustomFilter())
&& c.GetFilterProperty() != null)

View File

@@ -11,7 +11,7 @@
<IsPackable>true</IsPackable>
<PackageId>Radzen.Blazor</PackageId>
<Product>Radzen.Blazor</Product>
<Version>8.6.3</Version>
<Version>8.7.5</Version>
<Copyright>Radzen Ltd.</Copyright>
<Authors>Radzen Ltd.</Authors>
<Description>Radzen Blazor is the most sophisticated free UI component library for Blazor, featuring 100+ native components including DataGrid, Scheduler, Charts, and advanced theming with full support for Material Design and Fluent UI.</Description>

View File

@@ -1,9 +1,11 @@
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Radzen.Blazor
{
@@ -93,22 +95,6 @@ namespace Radzen.Blazor
_ => false
};
internal readonly struct BarcodeRect
{
public readonly double X;
public readonly double Y;
public readonly double Width;
public readonly double Height;
public BarcodeRect(double x, double y, double width, double height)
{
X = x;
Y = y;
Width = width;
Height = height;
}
}
/// <summary>
/// Gets or sets the barcode value to encode.
/// </summary>
@@ -275,894 +261,20 @@ namespace Radzen.Blazor
if (vbWidth <= 0) vbWidth = 1;
return (geometry.bars, vbWidth, checksumText, null);
}
}
internal static class RadzenBarcodeEncoder
{
// Code 128 patterns (0..106). Each entry is 6 digits (bar/space/bar/space/bar/space) module widths.
// Stop code (106) is 7 digits in the spec (includes a final bar). We keep it as 7 digits and handle it.
static readonly string[] Code128Patterns = new[]
/// <summary>
/// Returns the SVG markup of the rendered QR code as a string.
/// </summary>
/// <returns>
/// A <see cref="Task{String}"/> representing the asynchronous operation. The task result contains the SVG markup of the QR code.
/// </returns>
public async Task<string> ToSvg()
{
"212222","222122","222221","121223","121322","131222","122213","122312","132212","221213",
"221312","231212","112232","122132","122231","113222","123122","123221","223211","221132",
"221231","213212","223112","312131","311222","321122","321221","312212","322112","322211",
"212123","212321","232121","111323","131123","131321","112313","132113","132311","211313",
"231113","231311","112133","112331","132131","113123","113321","133121","313121","211331",
"231131","213113","213311","213131","311123","311321","331121","312113","312311","332111",
"314111","221411","431111","111224","111422","121124","121421","141122","141221","112214",
"112412","122114","122411","142112","142211","241211","221114","413111","241112","134111",
"111242","121142","121241","114212","124112","124211","411212","421112","421211","212141",
"214121","412121","111143","111341","131141","114113","114311","411113","411311","113141",
"114131","311141","411131","211412","211214","211232","2331112"
};
public static IReadOnlyList<int> EncodeCode128B(string value) => EncodeCode128B(value, out _);
public static IReadOnlyList<int> EncodeCode128B(string value, out int checksum)
{
// Code 128 subset B supports ASCII 32..127 (inclusive). We treat 127 as DEL.
var codes = new List<int>(value.Length + 3);
const int startB = 104;
const int stop = 106;
codes.Add(startB);
for (int i = 0; i < value.Length; i++)
if (JSRuntime != null)
{
var ch = value[i];
int ascii = ch;
if (ascii < 32 || ascii > 127)
{
throw new ArgumentException($"Code128B supports ASCII 32..127. Invalid character: U+{ascii:X4}.");
}
// In Code128B, code value is ascii - 32
codes.Add(ascii - 32);
return await JSRuntime.InvokeAsync<string>("Radzen.outerHTML", Element);
}
int checksumValue = codes[0];
for (int i = 1; i < codes.Count; i++)
{
checksumValue += codes[i] * i;
}
checksumValue %= 103;
// expose checksum (0..102)
checksum = checksumValue;
codes.Add(checksumValue);
codes.Add(stop);
// Convert codes to module pattern widths, alternating bar/space.
// Most codes are 6 digits; stop is 7 digits.
var modules = new List<int>(codes.Count * 6);
foreach (var code in codes)
{
var p = Code128Patterns[code];
for (int i = 0; i < p.Length; i++)
{
modules.Add(p[i] - '0');
}
}
// Code 128 requires a 2-module termination bar after the stop pattern.
// Many pattern tables omit it because it can be represented by extending the
// final bar of the stop pattern by 2 modules.
if (modules.Count > 0)
{
modules[^1] += 2;
}
return modules;
}
static readonly Dictionary<char, string> Code39Map = new Dictionary<char, string>()
{
// Each pattern is 9 elements (bar/space alternating, starting with bar).
// 'n' = narrow (1), 'w' = wide (2). We expand to digits.
['0'] = "nnnwwnwnn",
['1'] = "wnnwnnnnw",
['2'] = "nnwwnnnnw",
['3'] = "wnwwnnnnn",
['4'] = "nnnwwnnnw",
['5'] = "wnnwwnnnn",
['6'] = "nnwwwnnnn",
['7'] = "nnnwnnwnw",
['8'] = "wnnwnnwnn",
['9'] = "nnwwnnwnn",
['A'] = "wnnnnwnnw",
['B'] = "nnwnnwnnw",
['C'] = "wnwnnwnnn",
['D'] = "nnnnwwnnw",
['E'] = "wnnnwwnnn",
['F'] = "nnwnwwnnn",
['G'] = "nnnnnwwnw",
['H'] = "wnnnnwwnn",
['I'] = "nnwnnwwnn",
['J'] = "nnnnwwwnn",
['K'] = "wnnnnnnww",
['L'] = "nnwnnnnww",
['M'] = "wnwnnnnwn",
['N'] = "nnnnwnnww",
['O'] = "wnnnwnnwn",
['P'] = "nnwnwnnwn",
['Q'] = "nnnnnnwww",
['R'] = "wnnnnnwwn",
['S'] = "nnwnnnwwn",
['T'] = "nnnnwnwwn",
['U'] = "wwnnnnnnw",
['V'] = "nwwnnnnnw",
['W'] = "wwwnnnnnn",
['X'] = "nwnnwnnnw",
['Y'] = "wwnnwnnnn",
['Z'] = "nwwnwnnnn",
['-'] = "nwnnnnwnw",
['.'] = "wwnnnnwnn",
[' '] = "nwwnnnwnn",
['$'] = "nwnwnwnnn",
['/'] = "nwnwnnnwn",
['+'] = "nwnnnwnwn",
['%'] = "nnnwnwnwn",
['*'] = "nwnnwnwnn", // start/stop
};
public static IReadOnlyList<int> EncodeCode39(string value)
{
// Code39 traditionally uses uppercase
var text = value.ToUpperInvariant();
foreach (var ch in text)
{
if (!Code39Map.ContainsKey(ch))
{
throw new ArgumentException($"Code39 does not support character '{ch}'.");
}
}
// Start + data + stop, inter-character gap (narrow space) between characters.
var full = "*" + text + "*";
var modules = new List<int>(full.Length * 10);
for (int idx = 0; idx < full.Length; idx++)
{
var pat = Code39Map[full[idx]];
for (int i = 0; i < pat.Length; i++)
{
modules.Add(pat[i] == 'w' ? 2 : 1);
}
// Inter-character gap (narrow space) except after last char.
if (idx != full.Length - 1)
{
modules.Add(1);
}
}
return modules;
}
public static IReadOnlyList<int> EncodeItf(string value)
{
var digits = new string(value.Where(char.IsDigit).ToArray());
if (digits.Length == 0)
{
throw new ArgumentException("ITF requires numeric input.");
}
if (digits.Length % 2 != 0)
{
// pad with leading zero (common behavior)
digits = "0" + digits;
}
const int narrow = 1;
const int wide = 3;
static string Pat(char d) => d switch
{
'0' => "nnwwn",
'1' => "wnnnw",
'2' => "nwnnw",
'3' => "wwnnn",
'4' => "nnwnw",
'5' => "wnwnn",
'6' => "nwwnn",
'7' => "nnnww",
'8' => "wnnwn",
'9' => "nwnwn",
_ => throw new ArgumentException("ITF requires numeric input.")
};
var widths = new List<int>(digits.Length * 10 + 16);
// Start: narrow bar, narrow space, narrow bar, narrow space (1010)
widths.Add(narrow);
widths.Add(narrow);
widths.Add(narrow);
widths.Add(narrow);
for (int i = 0; i < digits.Length; i += 2)
{
var a = Pat(digits[i]);
var b = Pat(digits[i + 1]);
for (int j = 0; j < 5; j++)
{
widths.Add(a[j] == 'w' ? wide : narrow); // bar
widths.Add(b[j] == 'w' ? wide : narrow); // space
}
}
// Stop: wide bar, narrow space, narrow bar (1101)
widths.Add(wide);
widths.Add(narrow);
widths.Add(narrow);
return widths;
}
public static IReadOnlyList<int> EncodeCodabar(string value)
{
// Wikipedia table mapping (bars: 1=wide, spaces: 0=wide) for the standard symbol set.
// Ensure start/stop are present; default to A ... B if missing.
var raw = (value ?? string.Empty).Trim().ToUpperInvariant();
if (raw.Length == 0)
{
throw new ArgumentException("Codabar requires a non-empty value.");
}
bool HasStartStop(string s)
{
if (s.Length < 2) return false;
bool isStart = s[0] == 'A' || s[0] == 'B' || s[0] == 'C' || s[0] == 'D';
bool isStop = s[s.Length - 1] == 'A' || s[s.Length - 1] == 'B' || s[s.Length - 1] == 'C' || s[s.Length - 1] == 'D';
return isStart && isStop;
}
var text = HasStartStop(raw) ? raw : $"A{raw}B";
static (string spaceBits, string barBits) Map(char ch) => ch switch
{
'0' => ("001", "0001"),
'1' => ("001", "0010"),
'2' => ("010", "0001"),
'3' => ("100", "1000"),
'4' => ("001", "0100"),
'5' => ("001", "1000"),
'6' => ("100", "0001"),
'7' => ("100", "0010"),
'8' => ("100", "0100"),
'9' => ("010", "1000"),
'-' => ("010", "0010"),
'$' => ("010", "0100"),
'.' => ("000", "0001"),
'/' => ("000", "0010"),
':' => ("000", "0100"),
'+' => ("000", "1000"),
'A' => ("011", "0100"),
'B' => ("110", "0001"),
'C' => ("011", "0001"),
'D' => ("011", "0010"),
_ => throw new ArgumentException($"Codabar does not support character '{ch}'.")
};
const int narrow = 1;
const int wide = 3;
var widths = new List<int>(text.Length * 8);
for (int idx = 0; idx < text.Length; idx++)
{
var ch = text[idx];
var (spaceBits, barBits) = Map(ch);
// Bars: 4 bits, 1=wide
int BarWidth(int pos) => barBits[pos] == '1' ? wide : narrow;
// Spaces: 3 bits, 0=wide (per wikipedia mapping table)
int SpaceWidth(int pos) => spaceBits[pos] == '0' ? wide : narrow;
widths.Add(BarWidth(0));
widths.Add(SpaceWidth(0));
widths.Add(BarWidth(1));
widths.Add(SpaceWidth(1));
widths.Add(BarWidth(2));
widths.Add(SpaceWidth(2));
widths.Add(BarWidth(3));
// Inter-character narrow space (except after last char).
if (idx != text.Length - 1)
{
widths.Add(narrow);
}
}
return widths;
}
static readonly string[] EanL = new[]
{
"0001101","0011001","0010011","0111101","0100011","0110001","0101111","0111011","0110111","0001011"
};
static readonly string[] EanG = new[]
{
"0100111","0110011","0011011","0100001","0011101","0111001","0000101","0010001","0001001","0010111"
};
static readonly string[] EanR = new[]
{
"1110010","1100110","1101100","1000010","1011100","1001110","1010000","1000100","1001000","1110100"
};
static readonly string[] Ean13Parity = new[]
{
"LLLLLL","LLGLGG","LLGGLG","LLGGGL","LGLLGG","LGGLLG","LGGGLL","LGLGLG","LGLGGL","LGGLGL"
};
static int ComputeEanCheckDigit(string digitsWithoutCheck)
{
// digitsWithoutCheck is 7/11/12 digits depending on symbology.
int sum = 0;
bool weight3 = true;
for (int i = digitsWithoutCheck.Length - 1; i >= 0; i--)
{
int d = digitsWithoutCheck[i] - '0';
sum += weight3 ? d * 3 : d;
weight3 = !weight3;
}
int mod = sum % 10;
return (10 - mod) % 10;
}
public static string EncodeEan13(string value, out string checksumText)
{
var digits = new string(value.Where(char.IsDigit).ToArray());
if (digits.Length != 12 && digits.Length != 13)
{
throw new ArgumentException("EAN-13 requires 12 or 13 digits.");
}
if (digits.Length == 12)
{
var check = ComputeEanCheckDigit(digits);
digits += check.ToString(CultureInfo.InvariantCulture);
}
else
{
var expected = ComputeEanCheckDigit(digits[..12]);
if (digits[12] - '0' != expected)
{
throw new ArgumentException("Invalid EAN-13 check digit.");
}
}
checksumText = digits[^1].ToString();
int first = digits[0] - '0';
var parity = Ean13Parity[first];
var sb = new StringBuilder(95);
sb.Append("101");
// digits 2..7 (index 1..6)
for (int i = 1; i <= 6; i++)
{
int d = digits[i] - '0';
sb.Append(parity[i - 1] == 'G' ? EanG[d] : EanL[d]);
}
sb.Append("01010");
for (int i = 7; i <= 12; i++)
{
int d = digits[i] - '0';
sb.Append(EanR[d]);
}
sb.Append("101");
return sb.ToString();
}
public static string EncodeUpcA(string value, out string checksumText)
{
var digits = new string(value.Where(char.IsDigit).ToArray());
if (digits.Length != 11 && digits.Length != 12)
{
throw new ArgumentException("UPC-A requires 11 or 12 digits.");
}
if (digits.Length == 11)
{
var check = ComputeEanCheckDigit(digits);
digits += check.ToString(CultureInfo.InvariantCulture);
}
else
{
var expected = ComputeEanCheckDigit(digits[..11]);
if (digits[11] - '0' != expected)
{
throw new ArgumentException("Invalid UPC-A check digit.");
}
}
checksumText = digits[^1].ToString();
var sb = new StringBuilder(95);
sb.Append("101");
for (int i = 0; i < 6; i++)
{
int d = digits[i] - '0';
sb.Append(EanL[d]);
}
sb.Append("01010");
for (int i = 6; i < 12; i++)
{
int d = digits[i] - '0';
sb.Append(EanR[d]);
}
sb.Append("101");
return sb.ToString();
}
public static string EncodeEan8(string value, out string checksumText)
{
var digits = new string(value.Where(char.IsDigit).ToArray());
if (digits.Length != 7 && digits.Length != 8)
{
throw new ArgumentException("EAN-8 requires 7 or 8 digits.");
}
if (digits.Length == 7)
{
var check = ComputeEanCheckDigit(digits);
digits += check.ToString(CultureInfo.InvariantCulture);
}
else
{
var expected = ComputeEanCheckDigit(digits[..7]);
if (digits[7] - '0' != expected)
{
throw new ArgumentException("Invalid EAN-8 check digit.");
}
}
checksumText = digits[^1].ToString();
var sb = new StringBuilder(67);
sb.Append("101");
for (int i = 0; i < 4; i++)
{
int d = digits[i] - '0';
sb.Append(EanL[d]);
}
sb.Append("01010");
for (int i = 4; i < 8; i++)
{
int d = digits[i] - '0';
sb.Append(EanR[d]);
}
sb.Append("101");
return sb.ToString();
}
public static string EncodeIsbnAsEan13(string value, out string checksumText)
{
var raw = new string(value.Where(char.IsLetterOrDigit).ToArray()).ToUpperInvariant();
if (raw.Length == 10)
{
// ISBN-10 -> EAN-13: 978 + first 9 digits + EAN check
var core = raw[..9];
if (!core.All(char.IsDigit)) throw new ArgumentException("Invalid ISBN-10.");
return EncodeEan13("978" + core, out checksumText);
}
if (raw.Length == 13)
{
if (!raw.All(char.IsDigit)) throw new ArgumentException("Invalid ISBN-13.");
return EncodeEan13(raw, out checksumText);
}
throw new ArgumentException("ISBN requires 10 or 13 characters.");
}
public static string EncodeIssnAsEan13(string value, out string checksumText)
{
// ISSN EAN-13: 977 + first 7 digits + 00 + EAN check
var raw = new string(value.Where(char.IsLetterOrDigit).ToArray()).ToUpperInvariant();
if (raw.Length != 8) throw new ArgumentException("ISSN requires 8 characters.");
var core = raw[..7];
if (!core.All(char.IsDigit)) throw new ArgumentException("Invalid ISSN.");
return EncodeEan13("977" + core + "00", out checksumText);
}
public static (IReadOnlyList<RadzenBarcode.BarcodeRect> bars, double vbWidth) EncodePharmacode(string value, double barHeight, int quietZone)
{
// Pharmacode one-track: numbers 3..131070
var digits = new string(value.Where(char.IsDigit).ToArray());
if (!int.TryParse(digits, NumberStyles.None, CultureInfo.InvariantCulture, out var n))
{
throw new ArgumentException("Pharmacode requires a numeric value.");
}
if (n < 3 || n > 131070)
{
throw new ArgumentException("Pharmacode value must be in range 3..131070.");
}
var bars = new List<(int width, bool isWide)>();
while (n > 0)
{
if (n % 2 == 0)
{
bars.Add((2, true));
n = (n - 2) / 2;
}
else
{
bars.Add((1, false));
n = (n - 1) / 2;
}
}
bars.Reverse();
double x = Math.Max(0, quietZone);
var rects = new List<RadzenBarcode.BarcodeRect>(bars.Count);
foreach (var b in bars)
{
rects.Add(new RadzenBarcode.BarcodeRect(x, 0, b.width, barHeight));
x += b.width + 1; // 1 module gap
}
var vbWidth = x + Math.Max(0, quietZone);
if (vbWidth <= 0) vbWidth = 1;
return (rects, vbWidth);
}
// POSTNET digit encoding from Wikipedia (weights 7,4,2,1,0). 1=full bar, 0=half bar.
static readonly Dictionary<char, string> PostnetDigitBits = new Dictionary<char, string>()
{
['0'] = "11000",
['1'] = "00011",
['2'] = "00101",
['3'] = "00110",
['4'] = "01001",
['5'] = "01010",
['6'] = "01100",
['7'] = "10001",
['8'] = "10010",
['9'] = "10100",
};
public static (IReadOnlyList<RadzenBarcode.BarcodeRect> bars, double vbWidth) EncodePostnet(string value, double barHeight, int quietZone, out string checksumText)
{
var digits = new string(value.Where(char.IsDigit).ToArray());
if ((digits.Length != 5 && digits.Length != 9 && digits.Length != 11))
{
throw new ArgumentException("POSTNET requires 5, 9, or 11 digits (ZIP / ZIP+4 / Delivery Point).");
}
int sum = digits.Sum(ch => ch - '0');
int check = (10 - (sum % 10)) % 10;
checksumText = check.ToString(CultureInfo.InvariantCulture);
var payload = digits + checksumText;
double fullH = barHeight;
double halfH = barHeight / 2.0;
double halfY = fullH - halfH;
double x = Math.Max(0, quietZone);
var rects = new List<RadzenBarcode.BarcodeRect>();
// Start frame bar (full)
rects.Add(new RadzenBarcode.BarcodeRect(x, 0, 1, fullH));
x += 2; // bar(1) + space(1)
foreach (var ch in payload)
{
var bits = PostnetDigitBits[ch];
for (int i = 0; i < 5; i++)
{
bool full = bits[i] == '1';
rects.Add(full
? new RadzenBarcode.BarcodeRect(x, 0, 1, fullH)
: new RadzenBarcode.BarcodeRect(x, halfY, 1, halfH));
x += 2;
}
}
// Stop frame bar (full)
rects.Add(new RadzenBarcode.BarcodeRect(x, 0, 1, fullH));
x += 1;
var vbWidth = x + Math.Max(0, quietZone);
if (vbWidth <= 0) vbWidth = 1;
return (rects, vbWidth);
}
// RM4SCC patterns and symbol matrix from Wikipedia:
// Top patterns (values 1..6) and Bottom patterns (values 1..6) are:
// 1=0011, 2=0101, 3=0110, 4=1001, 5=1010, 6=1100
static readonly string[] Rm4Patterns = new[] { "0011", "0101", "0110", "1001", "1010", "1100" };
// Matrix indexed by [topValue-1, bottomValue-1]
static readonly char[,] Rm4Matrix = new char[6, 6]
{
{ '0', '1', '2', '3', '4', '5' },
{ '6', '7', '8', '9', 'A', 'B' },
{ 'C', 'D', 'E', 'F', 'G', 'H' },
{ 'I', 'J', 'K', 'L', 'M', 'N' },
{ 'O', 'P', 'Q', 'R', 'S', 'T' },
{ 'U', 'V', 'W', 'X', 'Y', 'Z' }
};
static readonly Dictionary<char, (string top, string bottom)> Rm4CharToBits = BuildRm4CharToBits();
static Dictionary<char, (string top, string bottom)> BuildRm4CharToBits()
{
var dict = new Dictionary<char, (string top, string bottom)>();
for (int r = 0; r < 6; r++)
{
for (int c = 0; c < 6; c++)
{
dict[Rm4Matrix[r, c]] = (Rm4Patterns[r], Rm4Patterns[c]);
}
}
return dict;
}
public static (IReadOnlyList<RadzenBarcode.BarcodeRect> bars, double vbWidth) EncodeRm4scc(string value, double barHeight, int quietZone, out string checksumText)
{
var text = new string(value.Where(char.IsLetterOrDigit).ToArray()).ToUpperInvariant();
if (string.IsNullOrEmpty(text))
{
throw new ArgumentException("RM4SCC requires alphanumeric input.");
}
// Only symbols present in the Wikipedia table (0-9, A-Z).
foreach (var ch in text)
{
if (!(ch is >= '0' and <= '9') && !(ch is >= 'A' and <= 'Z'))
{
throw new ArgumentException($"RM4SCC does not support character '{ch}'.");
}
}
// Compute checksum per Wikipedia: sum top values and bottom values separately, mod 6, 0 => 6.
int sumTop = 0;
int sumBottom = 0;
foreach (var ch in text)
{
if (!Rm4CharToBits.TryGetValue(ch, out var bits))
{
throw new ArgumentException($"RM4SCC does not support character '{ch}'.");
}
int t = Array.IndexOf(Rm4Patterns, bits.top) + 1;
int b = Array.IndexOf(Rm4Patterns, bits.bottom) + 1;
sumTop += t;
sumBottom += b;
}
int topVal = sumTop % 6;
topVal = topVal == 0 ? 6 : topVal;
int bottomVal = sumBottom % 6;
bottomVal = bottomVal == 0 ? 6 : bottomVal;
var checkChar = Rm4Matrix[topVal - 1, bottomVal - 1];
checksumText = checkChar.ToString();
// Encode start + data + checksum + stop.
// Start/stop are single bars; we use ascender for start and descender for stop.
double h = barHeight;
double third = h / 3.0;
double trackerY = third;
double trackerH = third;
RadzenBarcode.BarcodeRect BarRect(double x, bool top, bool bottom)
{
return (top, bottom) switch
{
(false, false) => new RadzenBarcode.BarcodeRect(x, trackerY, 1, trackerH), // tracker
(true, false) => new RadzenBarcode.BarcodeRect(x, 0, 1, trackerY + trackerH), // ascender (top + tracker)
(false, true) => new RadzenBarcode.BarcodeRect(x, trackerY, 1, h - trackerY), // descender (tracker + bottom)
(true, true) => new RadzenBarcode.BarcodeRect(x, 0, 1, h), // full
};
}
double xPos = Math.Max(0, quietZone);
var rects = new List<RadzenBarcode.BarcodeRect>();
// Start bar (ascender)
rects.Add(BarRect(xPos, top: true, bottom: false));
xPos += 2;
void AddSymbol(char ch)
{
var (topBits, bottomBits) = Rm4CharToBits[ch];
for (int i = 0; i < 4; i++)
{
bool top = topBits[i] == '1';
bool bottom = bottomBits[i] == '1';
rects.Add(BarRect(xPos, top, bottom));
xPos += 2;
}
}
foreach (var ch in text) AddSymbol(ch);
AddSymbol(checkChar);
// Stop bar (descender)
rects.Add(BarRect(xPos, top: false, bottom: true));
xPos += 1;
var vbWidth = xPos + Math.Max(0, quietZone);
if (vbWidth <= 0) vbWidth = 1;
return (rects, vbWidth);
}
public static string EncodeMsiPlessey(string value, out string checksumText)
{
var digits = new string(value.Where(char.IsDigit).ToArray());
if (digits.Length == 0) throw new ArgumentException("Plessey (MSI) requires numeric input.");
// Mod 10 (Luhn) check digit (common)
int check = ComputeLuhnCheckDigit(digits);
checksumText = check.ToString(CultureInfo.InvariantCulture);
digits += checksumText;
// MSI map from Wikipedia.
static string DigitMap(char d) => d switch
{
'0' => "100100100100",
'1' => "100100100110",
'2' => "100100110100",
'3' => "100100110110",
'4' => "100110100100",
'5' => "100110100110",
'6' => "100110110100",
'7' => "100110110110",
'8' => "110100100100",
'9' => "110100100110",
_ => throw new ArgumentException("MSI requires numeric input.")
};
var sb = new StringBuilder();
sb.Append("110"); // start
foreach (var ch in digits) sb.Append(DigitMap(ch));
sb.Append("1001"); // stop
return sb.ToString();
}
static int ComputeLuhnCheckDigit(string digits)
{
int sum = 0;
bool dbl = true;
for (int i = digits.Length - 1; i >= 0; i--)
{
int d = digits[i] - '0';
if (dbl)
{
d *= 2;
if (d > 9) d -= 9;
}
sum += d;
dbl = !dbl;
}
return (10 - (sum % 10)) % 10;
}
public static IReadOnlyList<int> EncodeTelepen(string value, out string checksumText)
{
// Telepen algorithm per Wikipedia: even parity bytes, little-endian bit order, modulo-127 checksum.
var bytes = Encoding.ASCII.GetBytes(value ?? string.Empty);
int sum = 0;
for (int i = 0; i < bytes.Length; i++)
{
if (bytes[i] > 0x7F) throw new ArgumentException("Telepen supports ASCII only.");
sum = (sum + bytes[i]) % 127;
}
int check = (127 - sum) % 127;
checksumText = check.ToString(CultureInfo.InvariantCulture);
// Build payload: start '_' + data + checksum byte + stop 'z'
var payload = new List<byte>(bytes.Length + 3) { (byte)'_' };
payload.AddRange(bytes);
payload.Add((byte)check);
payload.Add((byte)'z');
// Build bit stream LSB-first with even parity bit as MSB.
var bitStream = new List<int>(payload.Count * 8);
foreach (var b0 in payload)
{
int b = b0 & 0x7F;
int ones = CountBits(b);
int parityBit = (ones % 2 == 0) ? 0 : 1; // make total even
int byteWithParity = b | (parityBit << 7);
for (int i = 0; i < 8; i++)
{
bitStream.Add((byteWithParity >> i) & 1);
}
}
// Encode bit stream into bar/space widths (narrow=1, wide=3).
// We produce alternating bar/space widths list, starting with bar.
const int narrow = 1;
const int wide = 3;
var widths = new List<int>();
int idx = 0;
while (idx < bitStream.Count)
{
if (bitStream[idx] == 1)
{
// "1" => narrow bar, narrow space
widths.Add(narrow);
widths.Add(narrow);
idx += 1;
continue;
}
// starts with 0
if (idx + 1 < bitStream.Count && bitStream[idx + 1] == 0)
{
// "00" => wide bar, narrow space
widths.Add(wide);
widths.Add(narrow);
idx += 2;
continue;
}
if (idx + 2 < bitStream.Count && bitStream[idx] == 0 && bitStream[idx + 1] == 1 && bitStream[idx + 2] == 0)
{
// "010" => wide bar, wide space
widths.Add(wide);
widths.Add(wide);
idx += 3;
continue;
}
// General block 0 1^k 0 with k>=2
if (idx + 3 >= bitStream.Count || bitStream[idx] != 0 || bitStream[idx + 1] != 1)
{
throw new ArgumentException("Invalid Telepen bit stream.");
}
int j = idx + 1;
while (j < bitStream.Count && bitStream[j] == 1) j++;
if (j >= bitStream.Count || bitStream[j] != 0)
{
throw new ArgumentException("Invalid Telepen bit stream.");
}
int k = j - (idx + 1); // number of 1s
if (k < 2) throw new ArgumentException("Invalid Telepen bit stream.");
// leading "01" => narrow bar, wide space
widths.Add(narrow);
widths.Add(wide);
// middle extra 1s (k-2) => narrow bar, narrow space
for (int m = 0; m < k - 2; m++)
{
widths.Add(narrow);
widths.Add(narrow);
}
// trailing "10" => narrow bar, wide space
widths.Add(narrow);
widths.Add(wide);
idx = j + 1;
}
return widths;
}
static int CountBits(int v)
{
int c = 0;
while (v != 0)
{
c += v & 1;
v >>= 1;
}
return c;
return string.Empty;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -55,9 +55,9 @@ namespace Radzen.Blazor
/// <summary>
/// Gets or sets the color scheme used to assign colors to chart series.
/// Determines the palette of colors applied sequentially to each series when series-specific colors are not set.
/// Available schemes include Pastel, Palette (default), Monochrome, and custom color schemes.
/// Available schemes include Pastel (default), Palette, Monochrome, and custom color schemes.
/// </summary>
/// <value>The color scheme. Default uses the Palette scheme.</value>
/// <value>The color scheme. Default uses the Pastel scheme.</value>
[Parameter]
public ColorScheme ColorScheme { get; set; }

View File

@@ -189,6 +189,8 @@ else if (Filter != null)
}
Filter.FilterValue = null;
Filter.Type = property.FilterPropertyType;
var defaultOperator = typeof(System.Collections.IEnumerable).IsAssignableFrom(property.FilterPropertyType) ? FilterOperator.Contains : default(FilterOperator);
if (property.GetFilterOperators().Any(o => o == property.FilterOperator))

View File

@@ -502,7 +502,7 @@
else
{
<input autocomplete="off" aria-label=@(column.Title + FilterValueAriaLabel + column.GetFilterValue()) disabled=@(!column.CanSetFilterValue()) id="@(getFilterInputId(column))" @onchange="@((args) => OnFilter(args, column))" @onkeydown="@((args) => OnFilterKeyPress(args, column))" value="@column.GetFilterValue()" type="text" placeholder="@column.GetFilterPlaceholder()" class="rz-textbox" style="width: 100%;" />
@if (column.GetFilterValue() != null && filters.Any(d => d.Property == column.Property && d.FilterProperty == column.FilterProperty))
@if (column.GetFilterValue() != null && filters.Any(d => d.Property == column.Property && $"{d.FilterProperty}" == $"{column.FilterProperty}"))
{
<i @onclick="@((args) => ClearFilter(column))" class="notranslate rzi rz-cell-filter-clear" style="position:absolute;inset-inline-end:10px;">close</i>
}

View File

@@ -10,6 +10,7 @@ using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Text.Json;
using System.Threading.Tasks;
@@ -892,7 +893,7 @@ namespace Radzen.Blazor
}
}
void ToggleColumns()
async Task ToggleColumns()
{
if (selectedColumns == null)
{
@@ -906,8 +907,13 @@ namespace Radzen.Blazor
c.SetVisible(selected.Contains(c));
}
PickedColumnsChanged.InvokeAsync(new DataGridPickedColumnsChangedEventArgs<TItem>() { Columns = selected });
await PickedColumnsChanged.InvokeAsync(new DataGridPickedColumnsChangedEventArgs<TItem>() { Columns = selected });
SaveSettings();
if (QueryOnlyVisibleColumns)
{
await Reload();
}
}
/// <summary>
@@ -1627,6 +1633,13 @@ namespace Radzen.Blazor
[Parameter]
public int ColumnsPickerMaxSelectedLabels { get; set; } = 2;
/// <summary>
/// Gets or sets a value indicating whether only visible columns are included in the query.
/// </summary>
/// <value><c>true</c> if only visible columns are included; otherwise, <c>false</c>.</value>
[Parameter]
public bool QueryOnlyVisibleColumns { get; set; }
/// <summary>
/// Gets or sets the column picker all columns text.
/// </summary>
@@ -2016,7 +2029,8 @@ namespace Radzen.Blazor
}
}
return view;
return QueryOnlyVisibleColumns ? view
.Select(allColumns.Where(c => c.GetVisible() && !string.IsNullOrEmpty(c.Property)).Select(c => c.Property)) : view;
}
}
@@ -2496,7 +2510,7 @@ namespace Radzen.Blazor
internal Tuple<GroupRowRenderEventArgs, IReadOnlyDictionary<string, object>> GroupRowAttributes(RadzenDataGridGroupRow<TItem> item)
{
var args = new Radzen.GroupRowRenderEventArgs() { Group = item.Group, FirstRender = firstRender };
var args = new Radzen.GroupRowRenderEventArgs() { Group = item.Group, FirstRender = firstRender, Expandable = item.GroupResult.Count > 0 };
if (GroupRowRender != null)
{
@@ -3576,6 +3590,59 @@ namespace Radzen.Blazor
}
}
if (expandedItems != null)
{
expandedItems.Clear();
}
if (editedItems != null)
{
editedItems.Clear();
}
if (editContexts != null)
{
editContexts.Clear();
}
if (childData != null)
{
childData.Clear();
}
if (selectedItems != null)
{
selectedItems.Clear();
}
if (rowSpans != null)
{
rowSpans.Clear();
}
if (columns != null)
{
columns.Clear();
}
if (allPickableColumns != null)
{
allPickableColumns.Clear();
}
if (allColumns != null)
{
allColumns.Clear();
}
if (childColumns != null)
{
childColumns.Clear();
}
_value = null;
Data = null;
GC.SuppressFinalize(this);
}

View File

@@ -1310,7 +1310,7 @@ namespace Radzen.Blazor
internal bool HasCustomFilter()
{
return GetFilterOperator() == FilterOperator.Custom && GetCustomFilterExpression() != null;
return GetFilterOperator() == FilterOperator.Custom && !string.IsNullOrEmpty(GetCustomFilterExpression());
}
internal bool HasActiveFilter()

View File

@@ -26,9 +26,12 @@
}
<td class="rz-col-icon">
<span class="rz-column-title"></span>
<a id="@(Grid.GridId() + Group.GetHashCode())" aria-label=@Grid.ExpandGroupAriaLabel @onclick:preventDefault="true" @onclick="@(_ => Grid.ExpandGroupItem(this, rowArgs?.Item1.Expanded ?? false))">
<span class="@(Grid.ExpandedGroupItemStyle(this, Grid.allGroupsExpanded != null ? Grid.allGroupsExpanded : rowArgs?.Item1.Expanded ?? false))"></span>
</a>
@if (rowArgs?.Item1.Expandable == true)
{
<a id="@(Grid.GridId() + Group.GetHashCode())" aria-label=@Grid.ExpandGroupAriaLabel @onclick:preventDefault="true" @onclick="@(_ => Grid.ExpandGroupItem(this, rowArgs?.Item1.Expanded))">
<span class="@(Grid.ExpandedGroupItemStyle(this, Grid.allGroupsExpanded != null ? Grid.allGroupsExpanded : rowArgs?.Item1.Expanded))"></span>
</a>
}
</td>
}
<td colspan="@(TotalColumnCount + (Grid?.Groups.Count ?? 0) - 1 - Group.Level + ((Grid?.Template != null && Grid?.ShowExpandColumn == true) ? 1 : 0))">

View File

@@ -151,8 +151,8 @@
<div class="rz-multiselect-filter-container">
<input id="@SearchID" tabindex="@(Disabled ? "-1" : $"{TabIndex}")" class="rz-inputtext" role="textbox" type="text"
onclick="Radzen.preventDefaultAndStopPropagation(event)" aria-label="@SearchAriaLabel"
@ref="@search" @oninput=@(args => { searchText = $"{args.Value}"; SearchTextChanged.InvokeAsync(searchText);})
@onchange="@((args) => OnFilter(args))" @onkeydown="@((args) => OnFilterKeyPress(args))" value="@searchText" autocomplete="@FilterAutoCompleteType" />
@ref="@search" @oninput="@OnFilterInput"
@onchange="@OnFilter" @onkeydown="@OnFilterKeyPress" value="@searchText" autocomplete="@FilterAutoCompleteType" />
<span class="notranslate rz-multiselect-filter-icon rzi rzi-search"></span>
</div>
}
@@ -197,7 +197,7 @@
<div class="rz-dropdown-filter-container">
<input aria-label="@SearchAriaLabel" id="@SearchID" @ref="@search" tabindex="@(Disabled ? "-1" : $"{TabIndex}")" placeholder="@FilterPlaceholder" class="rz-dropdown-filter rz-inputtext" autocomplete="@FilterAutoCompleteType" aria-autocomplete="none" type="text"
@onchange="@OnFilter" @onkeydown="@OnFilterKeyPress"
@bind:event="oninput" @bind:get="searchText" @bind:set="@(args => { searchText = $"{args}"; SearchTextChanged.InvokeAsync(searchText);})" />
@bind:event="oninput" @bind:get="searchText" @bind:set="@(args => OnFilterInput(new ChangeEventArgs(){ Value = args }))" />
<span class="notranslate rz-dropdown-filter-icon rzi rzi-search"></span>
</div>
</text>
@@ -208,7 +208,7 @@
<div class="rz-dropdown-filter-container">
<input aria-label="@SearchAriaLabel" id="@SearchID" @ref="@search" tabindex="@(Disabled ? "-1" : $"{TabIndex}")" placeholder="@FilterPlaceholder" class="rz-dropdown-filter rz-inputtext" autocomplete="@FilterAutoCompleteType" aria-autocomplete="none" type="text"
@onchange="@OnFilter" @onkeydown="@OnFilterKeyPress" value="@searchText"
@oninput="@((ChangeEventArgs args) => { searchText = $"{args.Value}"; SearchTextChanged.InvokeAsync(searchText);})" />
@oninput="@OnFilterInput" />
<span class="notranslate rz-dropdown-filter-icon rzi rzi-search"></span>
</div>
</text>

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