unit sortingtests; {$mode objfpc}{$H+} interface { Tests for sorting cells } uses // Not using Lazarus package as the user may be working with multiple versions // Instead, add .. to unit search path Classes, SysUtils, fpcunit, testregistry, fpstypes, fpspreadsheet, xlsbiff2, xlsbiff5, xlsbiff8, fpsopendocument, {and a project requirement for lclbase for utf8 handling} testsutility; var // Norm to test against - list of numbers and strings that will be sorted SollSortNumbers: array[0..9] of Double; SollSortStrings: array[0..9] of String; CommentIsSortedToStringIndex: Integer; CommentIsSortedToNumberIndex: Integer; HyperlinkIsSortedToStringIndex: Integer; HyperlinkIsSortedToNumberIndex: Integer; procedure InitUnsortedData; type { TSpreadSortingTests } TSpreadSortingTests = class(TTestCase) private protected // Set up expected values: procedure SetUp; override; procedure TearDown; override; procedure Test_Sorting_1( // one column or row ASortByCols: Boolean; ADescending: Boolean; // true: desending order AWhat: Integer // What = 0: number, 1: strings, 2: mixed ); procedure Test_Sorting_2( // two columns/rows, primary keys equal ASortByCols: Boolean; ADescending: Boolean ); published procedure Test_SortingByCols1_Numbers_Asc; procedure Test_SortingByCols1_Numbers_Desc; procedure Test_SortingByCols1_Strings_Asc; procedure Test_SortingByCols1_Strings_Desc; procedure Test_SortingByCols1_NumbersStrings_Asc; procedure Test_SortingByCols1_NumbersStrings_Desc; procedure Test_SortingByRows1_Numbers_Asc; procedure Test_SortingByRows1_Numbers_Desc; procedure Test_SortingByRows1_Strings_Asc; procedure Test_SortingByRows1_Strings_Desc; procedure Test_SortingByRows1_NumbersStrings_Asc; procedure Test_SortingByRows1_NumbersStrings_Desc; procedure Test_SortingByCols2_Asc; procedure Test_SortingByCols2_Desc; procedure Test_SortingByRows2_Asc; procedure Test_SortingByRows2_Desc; end; implementation uses fpsutils; const SortingTestSheet = 'Sorting'; procedure InitUnsortedData; // The logics of the detection requires equal count of numbers and strings. begin // When sorted the value is equal to the index SollSortNumbers[0] := 9; // --> A1 --> will contain comment and hyperlink SollSortNumbers[1] := 8; SollSortNumbers[2] := 5; SollSortNumbers[3] := 2; SollSortNumbers[4] := 6; SollSortNumbers[5] := 7; SollSortNumbers[6] := 1; SollSortNumbers[7] := 3; SollSortNumbers[8] := 4; SollSortNumbers[9] := 0; CommentIsSortedToNumberIndex := 9; HyperlinkIsSortedToNumberIndex := 9; // When sorted the value is equal to 'A' + index SollSortStrings[0] := 'C'; // --> Ar --> will contain hyperlink and comment SollSortStrings[1] := 'G'; SollSortStrings[2] := 'F'; SollSortStrings[3] := 'I'; SollSortStrings[4] := 'B'; SollSortStrings[5] := 'D'; SollSortStrings[6] := 'J'; SollSortStrings[7] := 'H'; SollSortStrings[8] := 'E'; SollSortStrings[9] := 'A'; CommentIsSortedToStringIndex := 2; HyperlinkIsSortedToStringIndex := 2; end; { TSpreadSortingTests } procedure TSpreadSortingTests.SetUp; begin inherited SetUp; InitUnsortedData; end; procedure TSpreadSortingTests.TearDown; begin inherited TearDown; end; procedure TSpreadSortingTests.Test_Sorting_1(ASortByCols: Boolean; ADescending: Boolean; AWhat: Integer); const AFormat = sfExcel8; var MyWorksheet: TsWorksheet; MyWorkbook: TsWorkbook; i, ilast, row, col: Integer; TempFile: string; //write xls/xml to this file and read back from it sortParams: TsSortParams; actualNumber: Double; actualString: String; expectedNumber: Double; expectedString: String; cell: PCell; begin sortParams := InitSortParams(ASortByCols, 1); TempFile := GetTempFileName; MyWorkbook := TsWorkbook.Create; try MyWorkSheet:= MyWorkBook.AddWorksheet(SortingTestSheet); col := 0; row := 0; if ASortByCols then begin case AWhat of 0: for i :=0 to High(SollSortNumbers) do // Numbers only MyWorksheet.WriteNumber(i, col, SollSortNumbers[i]); 1: for i := 0 to High(SollSortStrings) do // Strings only Myworksheet.WriteText(i, col, SollSortStrings[i]); 2: begin // Numbers and strings for i := 0 to High(SollSortNumbers) do MyWorkSheet.WriteNumber(i*2, col, SollSortNumbers[i]); for i := 0 to High(SollSortStrings) do MyWorksheet.WriteText(i*2+1, col, SollSortStrings[i]); end; end end else begin case AWhat of 0: for i := 0 to High(SollSortNumbers) do MyWorksheet.WriteNumber(row, i, SollSortNumbers[i]); 1: for i := 0 to High(SollSortStrings) do MyWorksheet.WriteText(row, i, SollSortStrings[i]); 2: begin for i := 0 to High(SollSortNumbers) do myWorkSheet.WriteNumber(row, i*2, SollSortNumbers[i]); for i:=0 to High(SollSortStrings) do MyWorksheet.WriteText(row, i*2+1, SollSortStrings[i]); end; end; end; // Add comment and hyperlink to cell A1. After sorting it is expected // in cell defined by CommentIsSortedToXXXIndex (XXX = Number/String) if AFormat <> sfExcel8 then // Comments not implemented for writing Excel8 {%H-}MyWorksheet.WriteComment(0, 0, 'Test comment'); MyWorksheet.WriteHyperlink(0, 0, 'http://www.google.com'); MyWorkBook.WriteToFile(TempFile, AFormat, true); finally MyWorkbook.Free; end; MyWorkbook := TsWorkbook.Create; try // Read spreadsheet file... MyWorkbook.ReadFromFile(TempFile, AFormat); if AFormat = sfExcel2 then {%H-}MyWorksheet := MyWorkbook.GetFirstWorksheet else MyWorksheet := GetWorksheetByName(MyWorkBook, SortingTestSheet); if MyWorksheet = nil then fail('Error in test code. Failed to get named worksheet'); // ... set up sorting direction case ADescending of false: sortParams.Keys[0].Options := []; // Ascending sort true : sortParams.Keys[0].Options := [ssoDescending]; // Descending sort end; // ... and sort it. case AWhat of 0: iLast:= High(SollSortNumbers); 1: iLast := High(SollSortStrings); 2: iLast := Length(SollSortNumbers) + Length(SollSortStrings) - 1; end; if ASortByCols then MyWorksheet.Sort(sortParams, 0, 0, iLast, 0) else MyWorksheet.Sort(sortParams, 0, 0, 0, iLast); // for debugging, to see the sorted data //MyWorkbook.WriteToFile('sorted.xls', AFormat, true); row := 0; col := 0; for i:=0 to iLast do begin if ASortByCols then case ADescending of false: row := i; // ascending true : row := iLast - i; // descending end else case ADescending of false: col := i; // ascending true : col := iLast - i; // descending end; case AWhat of 0: begin cell := MyWorksheet.FindCell(row, col); actualNumber := MyWorksheet.ReadAsNumber(cell); expectedNumber := i; CheckEquals(expectednumber, actualnumber, 'Sorted cell number mismatch, cell '+CellNotation(MyWorksheet, row, col)); if AFormat <> sfExcel8 then // Comments are not written for sfExcel8 --> ignore {%H-}CheckEquals( i=CommentIsSortedToNumberIndex, MyWorksheet.HasComment(cell), 'Sorted comment position mismatch, cell '+CellNotation(MyWorksheet, row, col)); CheckEquals( i = HyperlinkisSortedToNumberIndex, MyWorksheet.HasHyperlink(cell), 'Sorted hyperlink position mismatch, cell '+CellNotation(MyWorksheet, row, col)); end; 1: begin cell := MyWorksheet.FindCell(row, col); actualString := MyWorksheet.ReadAsText(cell); expectedString := char(ord('A') + i); CheckEquals(expectedstring, actualstring, 'Sorted cell string mismatch, cell '+CellNotation(MyWorksheet, row, col)); if AFormat <> sfExcel8 then // Comments are not written for sfExcel8 --> ignore {%H-}CheckEquals( i=CommentIsSortedToStringIndex, MyWorksheet.HasComment(cell), 'Sorted comment position mismatch, cell '+CellNotation(MyWorksheet, row, col)); CheckEquals( i = HyperlinkisSortedToStringIndex, MyWorksheet.HasHyperlink(cell), 'Sorted hyperlink position mismatch, cell '+CellNotation(MyWorksheet, row, col)); end; 2: begin // with increasing i, we see first the numbers, then the strings if i <= High(SollSortNumbers) then begin cell := MyWorksheet.FindCell(row, col); actualnumber := MyWorksheet.ReadAsNumber(cell); expectedNumber := i; CheckEquals(expectednumber, actualnumber, 'Sorted cell number mismatch, cell '+CellNotation(MyWorksheet, row, col)); end else begin actualstring := MyWorksheet.ReadAsText(row, col); expectedstring := char(ord('A') + i - Length(SollSortNumbers)); CheckEquals(expectedstring, actualstring, 'Sorted cell string mismatch, cell '+CellNotation(MyWorksheet, row, col)); end; end; end; end; finally MyWorkbook.Free; end; DeleteFile(TempFile); end; procedure TSpreadSortingTests.Test_Sorting_2(ASortByCols: Boolean; ADescending: Boolean); const AFormat = sfExcel8; var MyWorksheet: TsWorksheet; MyWorkbook: TsWorkbook; i, ilast, row, col: Integer; TempFile: string; //write xls/xml to this file and read back from it sortParams: TsSortParams; actualNumber: Double; actualString: String; expectedNumber: Double; expectedString: String; begin sortParams := InitSortParams(ASortByCols, 2); sortParams.Keys[0].ColRowIndex := 0; // col/row 0 is primary key sortParams.Keys[1].ColRowIndex := 1; // col/row 1 is second key iLast := High(SollSortNumbers); TempFile := GetTempFileName; MyWorkbook := TsWorkbook.Create; try MyWorkSheet:= MyWorkBook.AddWorksheet(SortingTestSheet); col := 0; row := 0; if ASortByCols then begin // Write all randomized numbers to column B for i:=0 to iLast do MyWorksheet.WriteNumber(i, col+1, SollSortNumbers[i]); // divide each number by 2 and calculate the character assigned to it // and write it to column A // We will sort primarily according to column A, and seconarily according // to B. The construction allows us to determine if the sorting is correct. for i:=0 to iLast do MyWorksheet.WriteText(i, col, char(ord('A')+round(SollSortNumbers[i]) div 2)); end else begin // The same with the rows... for i:=0 to iLast do MyWorksheet.WriteNumber(row+1, i, SollSortNumbers[i]); for i:=0 to iLast do MyWorksheet.WriteText(row, i, char(ord('A')+round(SollSortNumbers[i]) div 2)); end; MyWorkBook.WriteToFile(TempFile, AFormat, true); finally MyWorkbook.Free; end; MyWorkbook := TsWorkbook.Create; try // Read spreadsheet file... MyWorkbook.ReadFromFile(TempFile, AFormat); if AFormat = sfExcel2 then {%H-}MyWorksheet := MyWorkbook.GetFirstWorksheet else MyWorksheet := GetWorksheetByName(MyWorkBook, SortingTestSheet); if MyWorksheet = nil then fail('Error in test code. Failed to get named worksheet'); // ... set up sort direction if ADescending then begin sortParams.Keys[0].Options := [ssoDescending]; sortParams.Keys[1].Options := [ssoDescending]; end else begin sortParams.Keys[0].Options := []; sortParams.Keys[1].Options := []; end; // ... and sort it. if ASortByCols then MyWorksheet.Sort(sortParams, 0, 0, iLast, 1) else MyWorksheet.Sort(sortParams, 0, 0, 1, iLast); // for debugging, to see the sorted data // MyWorkbook.WriteToFile('sorted.xls', AFormat, true); for i:=0 to iLast do begin if ASortByCols then begin // Read the number first, they must be in order 0...9 (if ascending). col := 1; case ADescending of false : row := i; true : row := iLast - i; end; actualNumber := MyWorksheet.ReadAsNumber(row, col); // col B is the number, must be 0...9 here expectedNumber := i; CheckEquals(expectednumber, actualnumber, 'Sorted cell number mismatch, cell '+CellNotation(MyWorksheet, row, col)); // Now read the string. It must be the character corresponding to the // half of the number col := 0; actualString := MyWorksheet.ReadAsText(row, col); expectedString := char(ord('A') + round(expectedNumber) div 2); CheckEquals(expectedstring, actualstring, 'Sorted cell string mismatch, cell '+CellNotation(MyWorksheet, row, col)); end else begin row := 1; case ADescending of false : col := i; true : col := iLast - i; end; actualNumber := MyWorksheet.ReadAsNumber(row, col); expectedNumber := i; CheckEquals(expectednumber, actualnumber, 'Sorted cell number mismatch, cell '+CellNotation(MyWorksheet, row, col)); row := 0; actualstring := MyWorksheet.ReadAsText(row, col); expectedString := char(ord('A') + round(expectedNumber) div 2); CheckEquals(expectedstring, actualstring, 'Sorted cell string mismatch, cell '+CellNotation(MyWorksheet, row, col)); end; end; finally MyWorkbook.Free; end; DeleteFile(TempFile); end; { Sort 1 column } procedure TSpreadSortingTests.Test_SortingByCols1_Numbers_ASC; begin Test_Sorting_1(true, false, 0); end; procedure TSpreadSortingTests.Test_SortingByCols1_Numbers_DESC; begin Test_Sorting_1(true, true, 0); end; procedure TSpreadSortingTests.Test_SortingByCols1_Strings_ASC; begin Test_Sorting_1(true, false, 1); end; procedure TSpreadSortingTests.Test_SortingByCols1_Strings_DESC; begin Test_Sorting_1(true, true, 1); end; procedure TSpreadSortingTests.Test_SortingByCols1_NumbersStrings_ASC; begin Test_Sorting_1(true, false, 2); end; procedure TSpreadSortingTests.Test_SortingByCols1_NumbersStrings_DESC; begin Test_Sorting_1(true, true, 2); end; { Sort 1 row } procedure TSpreadSortingTests.Test_SortingByRows1_Numbers_asc; begin Test_Sorting_1(false, false, 0); end; procedure TSpreadSortingTests.Test_SortingByRows1_Numbers_Desc; begin Test_Sorting_1(false, true, 0); end; procedure TSpreadSortingTests.Test_SortingByRows1_Strings_Asc; begin Test_Sorting_1(false, false, 1); end; procedure TSpreadSortingTests.Test_SortingByRows1_Strings_Desc; begin Test_Sorting_1(false, true, 1); end; procedure TSpreadSortingTests.Test_SortingByRows1_NumbersStrings_Asc; begin Test_Sorting_1(false, false, 2); end; procedure TSpreadSortingTests.Test_SortingByRows1_NumbersStrings_Desc; begin Test_Sorting_1(false, true, 2); end; { two columns } procedure TSpreadSortingTests.Test_SortingByCols2_Asc; begin Test_Sorting_2(true, false); end; procedure TSpreadSortingTests.Test_SortingByCols2_Desc; begin Test_Sorting_2(true, true); end; procedure TSpreadSortingTests.Test_SortingByRows2_Asc; begin Test_Sorting_2(false, false); end; procedure TSpreadSortingTests.Test_SortingByRows2_Desc; begin Test_Sorting_2(false, true); end; initialization RegisterTest(TSpreadSortingTests); InitUnsortedData; end.