blob: 73536825217bc6a10f96bfb2309bccc416d82fe4 [file] [log] [blame]
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.ss.formula;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.formula.ptg.AreaErrPtg;
import org.apache.poi.ss.formula.ptg.AreaPtg;
import org.apache.poi.ss.formula.ptg.Ptg;
import org.apache.poi.ss.formula.ptg.Ref3DPtg;
import org.apache.poi.ss.util.CellReference;
import org.junit.Test;
/**
* Tests for {@link FormulaShifter}.
*
* @author Josh Micich
*/
public final class TestFormulaShifter {
// Note - the expected result row coordinates here were determined/verified
// in Excel 2007 by manually testing.
/**
* Tests what happens to area refs when a range of rows from inside, or overlapping are
* moved
*/
@Test
public void testShiftAreasSourceRows() {
// all these operations are on an area ref spanning rows 10 to 20
AreaPtg aptg = createAreaPtg(10, 20);
confirmAreaShift(aptg, 9, 21, 20, 30, 40);
confirmAreaShift(aptg, 10, 21, 20, 30, 40);
confirmAreaShift(aptg, 9, 20, 20, 30, 40);
confirmAreaShift(aptg, 8, 11, -3, 7, 20); // simple expansion of top
// rows containing area top being shifted down:
confirmAreaShift(aptg, 8, 11, 3, 13, 20);
confirmAreaShift(aptg, 8, 11, 7, 17, 20);
confirmAreaShift(aptg, 8, 11, 8, 18, 20);
confirmAreaShift(aptg, 8, 11, 9, 12, 20); // note behaviour changes here
confirmAreaShift(aptg, 8, 11, 10, 12, 21);
confirmAreaShift(aptg, 8, 11, 12, 12, 23);
confirmAreaShift(aptg, 8, 11, 13, 10, 20); // ignored
// rows from within being moved:
confirmAreaShift(aptg, 12, 16, 3, 10, 20); // stay within - no change
confirmAreaShift(aptg, 11, 19, 20, 10, 20); // move completely out - no change
confirmAreaShift(aptg, 16, 17, -6, 10, 20); // moved exactly to top - no change
confirmAreaShift(aptg, 16, 17, -7, 11, 20); // truncation at top
confirmAreaShift(aptg, 12, 16, 4, 10, 20); // moved exactly to bottom - no change
confirmAreaShift(aptg, 12, 16, 6, 10, 17); // truncation at bottom
// rows containing area bottom being shifted up:
confirmAreaShift(aptg, 18, 22, -1, 10, 19); // simple contraction at bottom
confirmAreaShift(aptg, 18, 22, -7, 10, 13); // simple contraction at bottom
confirmAreaShift(aptg, 18, 22, -8, 10, 17); // top calculated differently here
confirmAreaShift(aptg, 18, 22, -9, 9, 17);
confirmAreaShift(aptg, 18, 22,-15, 10, 20); // no change because range would be turned inside out
confirmAreaShift(aptg, 15, 19, -7, 13, 20); // dest truncates top (even though src is from inside range)
confirmAreaShift(aptg, 19, 23,-12, 7, 18); // complex: src encloses bottom, dest encloses top
confirmAreaShift(aptg, 18, 22, 5, 10, 25); // simple expansion at bottom
}
@Test
public void testCopyAreasSourceRowsRelRel() {
// all these operations are on an area ref spanning rows 10 to 20
final AreaPtg aptg = createAreaPtg(10, 20, true, true);
confirmAreaCopy(aptg, 0, 30, 20, 30, 40, true);
confirmAreaCopy(aptg, 15, 25, -15, -1, -1, true); //DeletedRef
}
@Test
public void testCopyAreasSourceRowsRelAbs() {
// all these operations are on an area ref spanning rows 10 to 20
final AreaPtg aptg = createAreaPtg(10, 20, true, false);
// Only first row should move
confirmAreaCopy(aptg, 0, 30, 20, 20, 30, true);
confirmAreaCopy(aptg, 15, 25, -15, -1, -1, true); //DeletedRef
}
@Test
public void testCopyAreasSourceRowsAbsRel() {
// aptg is part of a formula in a cell that was just copied to another row
// aptg row references should be updated by the difference in rows that the cell was copied
// No other references besides the cells that were involved in the copy need to be updated
// this makes the row copy significantly different from the row shift, where all references
// in the workbook need to track the row shift
// all these operations are on an area ref spanning rows 10 to 20
final AreaPtg aptg = createAreaPtg(10, 20, false, true);
// Only last row should move
confirmAreaCopy(aptg, 0, 30, 20, 10, 40, true);
confirmAreaCopy(aptg, 15, 25, -15, 5, 10, true); //sortTopLeftToBottomRight swapped firstRow and lastRow because firstRow is absolute
}
@Test
public void testCopyAreasSourceRowsAbsAbs() {
// aptg is part of a formula in a cell that was just copied to another row
// aptg row references should be updated by the difference in rows that the cell was copied
// No other references besides the cells that were involved in the copy need to be updated
// this makes the row copy significantly different from the row shift, where all references
// in the workbook need to track the row shift
// all these operations are on an area ref spanning rows 10 to 20
final AreaPtg aptg = createAreaPtg(10, 20, false, false);
//AbsFirstRow AbsLastRow references should't change when copied to a different row
confirmAreaCopy(aptg, 0, 30, 20, 10, 20, false);
confirmAreaCopy(aptg, 15, 25, -15, 10, 20, false);
}
/**
* Tests what happens to an area ref when some outside rows are moved to overlap
* that area ref
*/
@Test
public void testShiftAreasDestRows() {
// all these operations are on an area ref spanning rows 20 to 25
AreaPtg aptg = createAreaPtg(20, 25);
// no change because no overlap:
confirmAreaShift(aptg, 5, 10, 9, 20, 25);
confirmAreaShift(aptg, 5, 10, 21, 20, 25);
confirmAreaShift(aptg, 11, 14, 10, 20, 25);
confirmAreaShift(aptg, 7, 17, 10, -1, -1); // converted to DeletedAreaRef
confirmAreaShift(aptg, 5, 15, 7, 23, 25); // truncation at top
confirmAreaShift(aptg, 13, 16, 10, 20, 22); // truncation at bottom
}
private static void confirmAreaShift(AreaPtg aptg,
int firstRowMoved, int lastRowMoved, int numberRowsMoved,
int expectedAreaFirstRow, int expectedAreaLastRow) {
FormulaShifter fs = FormulaShifter.createForRowShift(0, "", firstRowMoved, lastRowMoved, numberRowsMoved, SpreadsheetVersion.EXCEL2007);
boolean expectedChanged = aptg.getFirstRow() != expectedAreaFirstRow || aptg.getLastRow() != expectedAreaLastRow;
AreaPtg copyPtg = (AreaPtg) aptg.copy(); // clone so we can re-use aptg in calling method
Ptg[] ptgs = { copyPtg, };
boolean actualChanged = fs.adjustFormula(ptgs, 0);
if (expectedAreaFirstRow < 0) {
assertEquals(AreaErrPtg.class, ptgs[0].getClass());
return;
}
assertEquals(expectedChanged, actualChanged);
assertEquals(copyPtg, ptgs[0]); // expected to change in place (although this is not a strict requirement)
assertEquals(expectedAreaFirstRow, copyPtg.getFirstRow());
assertEquals(expectedAreaLastRow, copyPtg.getLastRow());
}
private static void confirmAreaCopy(AreaPtg aptg,
int firstRowCopied, int lastRowCopied, int rowOffset,
int expectedFirstRow, int expectedLastRow, boolean expectedChanged) {
final AreaPtg copyPtg = (AreaPtg) aptg.copy(); // clone so we can re-use aptg in calling method
final Ptg[] ptgs = { copyPtg, };
final FormulaShifter fs = FormulaShifter.createForRowCopy(0, null, firstRowCopied, lastRowCopied, rowOffset, SpreadsheetVersion.EXCEL2007);
final boolean actualChanged = fs.adjustFormula(ptgs, 0);
// DeletedAreaRef
if (expectedFirstRow < 0 || expectedLastRow < 0) {
assertEquals("Reference should have shifted off worksheet, producing #REF! error: " + ptgs[0],
AreaErrPtg.class, ptgs[0].getClass());
return;
}
assertEquals("Should this AreaPtg change due to row copy?", expectedChanged, actualChanged);
assertEquals("AreaPtgs should be modified in-place when a row containing the AreaPtg is copied", copyPtg, ptgs[0]); // expected to change in place (although this is not a strict requirement)
assertEquals("AreaPtg first row", expectedFirstRow, copyPtg.getFirstRow());
assertEquals("AreaPtg last row", expectedLastRow, copyPtg.getLastRow());
}
private static AreaPtg createAreaPtg(int initialAreaFirstRow, int initialAreaLastRow) {
return createAreaPtg(initialAreaFirstRow, initialAreaLastRow, false, false);
}
private static AreaPtg createAreaPtg(int initialAreaFirstRow, int initialAreaLastRow, boolean firstRowRelative, boolean lastRowRelative) {
return new AreaPtg(initialAreaFirstRow, initialAreaLastRow, 2, 5, firstRowRelative, lastRowRelative, false, false);
}
@Test
public void testShiftSheet() {
// 4 sheets, move a sheet from pos 2 to pos 0, i.e. current 0 becomes 1, current 1 becomes pos 2
FormulaShifter shifter = FormulaShifter.createForSheetShift(2, 0);
Ptg[] ptgs = new Ptg[] {
new Ref3DPtg(new CellReference("first", 0, 0, true, true), 0),
new Ref3DPtg(new CellReference("second", 0, 0, true, true), 1),
new Ref3DPtg(new CellReference("third", 0, 0, true, true), 2),
new Ref3DPtg(new CellReference("fourth", 0, 0, true, true), 3),
};
shifter.adjustFormula(ptgs, -1);
assertEquals("formula previously pointing to sheet 0 should now point to sheet 1",
1, ((Ref3DPtg)ptgs[0]).getExternSheetIndex());
assertEquals("formula previously pointing to sheet 1 should now point to sheet 2",
2, ((Ref3DPtg)ptgs[1]).getExternSheetIndex());
assertEquals("formula previously pointing to sheet 2 should now point to sheet 0",
0, ((Ref3DPtg)ptgs[2]).getExternSheetIndex());
assertEquals("formula previously pointing to sheet 3 should be unchanged",
3, ((Ref3DPtg)ptgs[3]).getExternSheetIndex());
}
@Test
public void testShiftSheet2() {
// 4 sheets, move a sheet from pos 1 to pos 2, i.e. current 2 becomes 1, current 1 becomes pos 2
FormulaShifter shifter = FormulaShifter.createForSheetShift(1, 2);
Ptg[] ptgs = new Ptg[] {
new Ref3DPtg(new CellReference("first", 0, 0, true, true), 0),
new Ref3DPtg(new CellReference("second", 0, 0, true, true), 1),
new Ref3DPtg(new CellReference("third", 0, 0, true, true), 2),
new Ref3DPtg(new CellReference("fourth", 0, 0, true, true), 3),
};
shifter.adjustFormula(ptgs, -1);
assertEquals("formula previously pointing to sheet 0 should be unchanged",
0, ((Ref3DPtg)ptgs[0]).getExternSheetIndex());
assertEquals("formula previously pointing to sheet 1 should now point to sheet 2",
2, ((Ref3DPtg)ptgs[1]).getExternSheetIndex());
assertEquals("formula previously pointing to sheet 2 should now point to sheet 1",
1, ((Ref3DPtg)ptgs[2]).getExternSheetIndex());
assertEquals("formula previously pointing to sheet 3 should be unchanged",
3, ((Ref3DPtg)ptgs[3]).getExternSheetIndex());
}
@Test
public void testInvalidArgument() {
try {
FormulaShifter.createForRowShift(1, "name", 1, 2, 0, SpreadsheetVersion.EXCEL97);
fail("Should catch exception here");
} catch (IllegalArgumentException e) {
// expected here
}
try {
FormulaShifter.createForRowShift(1, "name", 2, 1, 2, SpreadsheetVersion.EXCEL97);
fail("Should catch exception here");
} catch (IllegalArgumentException e) {
// expected here
}
}
@Test
public void testConstructor() {
assertNotNull(FormulaShifter.createForRowShift(1, "name", 1, 2, 2, SpreadsheetVersion.EXCEL97));
}
@Test
public void testToString() {
FormulaShifter shifter = FormulaShifter.createForRowShift(0, "sheet", 123, 456, 789,
SpreadsheetVersion.EXCEL2007);
assertNotNull(shifter);
assertNotNull(shifter.toString());
assertTrue(shifter.toString().contains("123"));
assertTrue(shifter.toString().contains("456"));
assertTrue(shifter.toString().contains("789"));
}
}