unit movetests;

{$mode objfpc}{$H+}

interface
{ Tests for copying cells
  NOTE: The code in these tests is very fragile because the test results are
  hard-coded. Any modification in "InitCopyData" must be carefully verified!
}

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;

type
  { TSpreadMoveTests }
  TSpreadMoveTests = class(TTestCase)
  private

  protected
    procedure Test_MoveCell(ATestKind: Integer);

  published
    procedure Test_MoveCell_Value;
    procedure Test_MoveCell_Format;
    procedure Test_MoveCell_Comment;
    procedure Test_MoveCell_Hyperlink;
    procedure Test_MoveCell_Formula_REL;
    procedure Test_MoveCell_Formula_ABS;
    procedure Test_MoveCell_FormulaRef_REL;
    procedure Test_MoveCell_FormulaRef_ABS;
    
    procedure Test_MoveCell_CircRef;
    procedure Test_MoveCell_OverwriteFormula;
    procedure Test_MoveCell_EmptyToValue;
    procedure Test_MoveCell_EmptyToFormula;
  end;

implementation

uses
  fpsutils;

const
  MoveTestSheet = 'Move';


{ TSpreadMoveTests }

{ In this test an occupied cell is moved to a different location. 
  ATestKind = 1: cell contains only a value
              2: cell contains also a format
              3: cell contains also a comment
              4: cell contains also a hyperlink
              5: cell contains a formula with relative reference to another cell
              6: like 5, but absolute reference.
              7: there is another cell with a formula pointing to the moved cell 
                 (relative reference). 
              8: like 7, but absolute reference. }
procedure TSpreadMoveTests.Test_MoveCell(ATestKind: Integer);
const
  SRC_ROW = 0;
  SRC_COL = 0;
  DEST_ROW = 11;
  DEST_COL = 6;
var
  worksheet: TsWorksheet;
  workbook: TsWorkbook;
  src_cell: PCell = nil;
  fmla_cell: PCell = nil;
  dest_cell: PCell = nil;
  nf: TsNumberFormat;
  nfs: String;
  hyperlink: PsHyperlink;
begin
  workbook := TsWorkbook.Create;
  try
    workbook.Options := workbook.Options + [boAutoCalc];

    worksheet := workBook.AddWorksheet(MoveTestSheet);
    
    // Prepare the worksheet in which a cell is moved:
    // The source cell is A1, and it is moved to G10
    src_cell := worksheet.WriteNumber(SRC_ROW, SRC_COL, 3.141592);      // A1
    case ATestKind of
      1: ;                                                  // just the value
      2: worksheet.WriteNumberFormat(src_cell, nfFixed, 2); // value + formatting
      3: worksheet.WriteComment(src_cell, 'test');          // value + comment
      4: worksheet.WriteHyperlink(src_cell, 'B2');          // value + hyperlink
      5: begin     // The test cell constains a formula pointing the B2
           worksheet.WriteNumber(1, 1, 3.141592);
           src_cell := worksheet.WriteFormula(SRC_ROW, SRC_COL, 'B2');
         end;  
      6: begin    // like 5, just absolute reference
           worksheet.WriteNumber(1, 1, 3.141592);
           src_cell := worksheet.WriteFormula(SRC_ROW, SRC_COL, '$B$2');
         end;  
      // In the two last tests, there is a formula pointing to the test cell.
      // It must be updated when the test cell is moved.
      7: fmla_cell := worksheet.WriteFormula(2, 2, 'A1');   // rel reference
      8: fmla_cell := worksheet.WriteFormula(2, 2, '$A$1'); // abs reference
    end;
    
    // Now move the cell
    worksheet.MoveCell(src_cell, DEST_ROW, DEST_COL);   // move cell (in A1) to G10
    
    // Check removal of source cell
    CheckEquals(true, worksheet.FindCell(SRC_ROW, SRC_COL) = nil, 'Source cell not removed');
    
    // Check existence of target cell
    dest_cell := worksheet.FindCell(DEST_ROW, DEST_COL);
    CheckEquals(true, dest_cell <> nil, 'Moved cell not found.');
    
    // Check value in target cell
    if not (ATestKind in [5, 6]) then
      CheckEquals(3.141592, worksheet.ReadAsNumber(dest_cell), 1E-9, 'Cell value mismatch');
    
    case ATestKind of
      1: ;
      2: begin
           worksheet.ReadNumFormat(dest_cell, nf, nfs);
           CheckEquals(Integer(nfFixed), Integer(nf), 'Number format mismatch');
           CheckEquals('0.00', nfs, 'Number format string mismatch');
         end;
      3: CheckEquals('test', worksheet.ReadComment(dest_cell), 'Comment mismatch');
      4: begin
           hyperlink := worksheet.FindHyperlink(dest_cell);
           CheckEquals(true, hyperlink <> nil, 'Hyperlink not found');
           CheckEquals('B2', hyperlink^.Target, 'hyperlink target mismatch');
         end;
      5: CheckEquals('B2', worksheet.ReadFormula(dest_cell), 'Moved formula mismatch');
      6: CheckEquals('$B$2', worksheet.ReadFormula(dest_cell), 'Moved formula mismatch');
      7: CheckEquals('G12', worksheet.ReadFormula(fmla_cell), 'Referencing formula mismatch');
      8: CheckEquals('$G$12', worksheet.ReadFormula(fmla_cell), 'Referencing formula mismatch');
    end;
    
  finally
    workbook.Free;
  end;
end;

{ Move cell with a number, no attached data. }
procedure TSpreadMoveTests.Test_MoveCell_Value;
begin
  Test_MoveCell(1);
end;

{ Move cell with a number, the cell contains a number format. }
procedure TSpreadMoveTests.Test_MoveCell_Format;
begin
  Test_MoveCell(2);
end;

{ Move cell with a number and comment. }
procedure TSpreadMoveTests.Test_MoveCell_Comment;
begin
  Test_MoveCell(3);
end;

{ Move cell with a number and hyperlink. }
procedure TSpreadMoveTests.Test_MoveCell_Hyperlink;
begin
  Test_MoveCell(4);
end;

{ Move cell with a formula (relative reference) }
procedure TSpreadMoveTests.Test_MoveCell_Formula_REL;
begin
  Test_MoveCell(5);
end;

{ Move cell with a formula (absolute reference). }
procedure TSpreadMoveTests.Test_MoveCell_Formula_ABS;
begin
  Test_MoveCell(6);
end;

{ Move cell with a number, a formula points to the cell with a relative reference. }
procedure TSpreadMoveTests.Test_MoveCell_FormulaRef_REL;
begin
  Test_MoveCell(7);
end;

{ Move cell with a number, a formula points to the cell with an absolute reference. }
procedure TSpreadMoveTests.Test_MoveCell_FormulaRef_ABS;
begin
  Test_MoveCell(8);
end;

{==============================================================================}

{ In the following test an occupied cell with a formula is moved to a location
  referenced by the formula. 
  This must result in a circular reference error. }
procedure TSpreadMoveTests.Test_MoveCell_CircRef;
const
  VALUE_CELL_ROW = 0;        // A1
  VALUE_CELL_COL = 0;
  FORMULA_CELL_ROW = 11;     // F10
  FORMULA_CELL_COL = 6;
var
  worksheet: TsWorksheet;
  workbook: TsWorkbook;
  formula_cell: PCell = nil;
  dest_cell: PCell = nil;
begin
  workbook := TsWorkbook.Create;
  try
    workbook.Options := workbook.Options + [boAutoCalc];

    worksheet := workBook.AddWorksheet(MoveTestSheet);
    
    // Prepare the worksheet in which a cell is moved.
    // The value cell is A1, the formula cell is F10 and it points to A1
    worksheet.WriteText(VALUE_CELL_ROW, VALUE_CELL_COL, 'abc');   // A1
    formula_cell := worksheet.WriteFormula(FORMULA_CELL_ROW, FORMULA_CELL_COL, 'A1');
    
    // Move the formula cell to overwrite the value cell
    try
      worksheet.MoveCell(formula_cell, VALUE_CELL_ROW, VALUE_CELL_COL);
      dest_cell := worksheet.FindCell(VALUE_CELL_ROW, VALUE_CELL_COL);
    except
    end;
    
    // The destination cell should contain a #REF! error
    CheckEquals(true, dest_cell^.ErrorValue = errIllegalRef, 'Circular reference not detected.');
    
  finally
    workbook.Free;
  end;
end;

{ In the following test an occupied cell with a value formula is moved to 
  a location with a formula cell pointing to the moved value cell.
  This operation must delete the formula after moving. }
procedure TSpreadMoveTests.Test_MoveCell_OverwriteFormula;
const
  VALUE_CELL_ROW = 0;        // A1
  VALUE_CELL_COL = 0;
  FORMULA_CELL_ROW = 11;     // F10
  FORMULA_CELL_COL = 6;
var
  worksheet: TsWorksheet;
  workbook: TsWorkbook;
  value_cell: PCell = nil;
  dest_cell: PCell = nil;
begin
  workbook := TsWorkbook.Create;
  try
    workbook.Options := workbook.Options + [boAutoCalc];

    worksheet := workBook.AddWorksheet(MoveTestSheet);
    
    // Prepare the worksheet in which a cell is moved.
    // The value cell is A1, the formula cell is F10 and it points to A1
    value_cell := worksheet.WriteText(VALUE_CELL_ROW, VALUE_CELL_COL, 'abc');   // A1
    worksheet.WriteFormula(FORMULA_CELL_ROW, FORMULA_CELL_COL, 'A1');
    
    // Move the value cell to overwrite the formula cell
    try
      worksheet.MoveCell(value_cell, FORMULA_CELL_ROW, FORMULA_CELL_COL);
      dest_cell := worksheet.FindCell(FORMULA_CELL_ROW, FORMULA_CELL_COL);
    except
    end;
    
    // The destination cell should not contain a formula any more.
    CheckEquals(false, HasFormula(dest_cell), 'Formula has not been removed.');
    // Check value at destination after moving
    CheckEquals('abc', worksheet.ReadAsText(dest_cell), 'Moved value mismatch.');
    // Check value at source after moving
    CheckEquals('', worksheet.ReadAsText(value_cell), 'Source value mismatch after moving.');
    
  finally
    workbook.Free;
  end;
end;
  
{ In the following test an empty cell is moved to a location with a value cell. 
  This operation must delete the value in the destination cell after moving. }
procedure TSpreadMoveTests.Test_MoveCell_EmptyToValue;
const
  SOURCE_CELL_ROW = 0;   // A1
  SOURCE_CELL_COL = 0;
  DEST_CELL_ROW = 2;     // C3
  DEST_CELL_COL = 2; 
var
  worksheet: TsWorksheet;
  workbook: TsWorkbook;
  src_cell: PCell = nil;
  dest_cell: PCell = nil;
begin
  workbook := TsWorkbook.Create;
  try
    workbook.Options := workbook.Options + [boAutoCalc];

    worksheet := workBook.AddWorksheet(MoveTestSheet);
    
    // Prepare the worksheet in which an empty cell is moved.
    src_cell := nil;     // A1
    dest_cell := worksheet.WriteText(DEST_CELL_ROW, DEST_CELL_COL, 'abc');   // C3
    
    // Move the source cell to overwrite the value cell
    try
      worksheet.MoveCell(src_cell, DEST_CELL_ROW, DEST_CELL_COL);
      dest_cell := worksheet.FindCell(DEST_CELL_ROW, DEST_CELL_COL);
    except
    end;
    
    // The destination cell should be empty.
    CheckEquals(true, dest_cell = nil, 'Destination cell nas not been deleted.');
    
  finally
    workbook.Free;
  end;
end;
  
{ In the following test an empty cell is moved to a location with a formula cell.
  This operation must delete the destination cell after moving. In particular, 
  there must not be a formula any more. }
procedure TSpreadMoveTests.Test_MoveCell_EmptyToFormula;
const
  SOURCE_CELL_ROW = 0;   // A1
  SOURCE_CELL_COL = 0;
  DEST_CELL_ROW = 2;     // C3
  DEST_CELL_COL = 2; 
var
  worksheet: TsWorksheet;
  workbook: TsWorkbook;
  src_cell: PCell = nil;
  dest_cell: PCell = nil;
begin
  workbook := TsWorkbook.Create;
  try
    workbook.Options := workbook.Options + [boAutoCalc];

    worksheet := workBook.AddWorksheet(MoveTestSheet);
    
    // Prepare the worksheet in which a cell is moved.
    // The value cell is A1, the formula cell is B2 and it points to A1
    src_cell := nil;     // A1
    dest_cell := worksheet.WriteFormula(DEST_CELL_ROW, DEST_CELL_COL, 'PI()');   // C3
    
    // Move the empty source cell to overwrite the formula cell
    try
      worksheet.MoveCell(src_cell, DEST_CELL_ROW, DEST_CELL_COL);
      dest_cell := worksheet.FindCell(DEST_CELL_ROW, DEST_CELL_COL);
    except
    end;
    
    // The destination cell should be empty.
    CheckEquals(false, HasFormula(dest_cell), 'Destination cell still contains a formula.');
    CheckEquals(true, dest_cell = nil, 'Destination cell has not been deleted.');
    
  finally
    workbook.Free;
  end;
end;
  
initialization
  RegisterTest(TSpreadMoveTests);

end.