| /* ==================================================================== |
| 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.jupiter.api.Assertions.assertEquals; |
| import static org.junit.jupiter.api.Assertions.assertNotNull; |
| import static org.junit.jupiter.api.Assertions.assertThrows; |
| import static org.junit.jupiter.api.Assertions.assertTrue; |
| |
| 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.jupiter.api.Test; |
| |
| /** |
| * Tests for {@link FormulaShifter}. |
| */ |
| 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 |
| void testShiftAreasSourceRows() { |
| |
| // all these operations are on an area ref spanning rows 10 to 20 |
| AreaPtg aptg = createAreaPtgRow(10, 20); |
| |
| confirmAreaRowShift(aptg, 9, 21, 20, 30, 40); |
| confirmAreaRowShift(aptg, 10, 21, 20, 30, 40); |
| confirmAreaRowShift(aptg, 9, 20, 20, 30, 40); |
| |
| confirmAreaRowShift(aptg, 8, 11, -3, 7, 20); // simple expansion of top |
| // rows containing area top being shifted down: |
| confirmAreaRowShift(aptg, 8, 11, 3, 13, 20); |
| confirmAreaRowShift(aptg, 8, 11, 7, 17, 20); |
| confirmAreaRowShift(aptg, 8, 11, 8, 18, 20); |
| confirmAreaRowShift(aptg, 8, 11, 9, 12, 20); // note behaviour changes here |
| confirmAreaRowShift(aptg, 8, 11, 10, 12, 21); |
| confirmAreaRowShift(aptg, 8, 11, 12, 12, 23); |
| confirmAreaRowShift(aptg, 8, 11, 13, 10, 20); // ignored |
| |
| // rows from within being moved: |
| confirmAreaRowShift(aptg, 12, 16, 3, 10, 20); // stay within - no change |
| confirmAreaRowShift(aptg, 11, 19, 20, 10, 20); // move completely out - no change |
| confirmAreaRowShift(aptg, 16, 17, -6, 10, 20); // moved exactly to top - no change |
| confirmAreaRowShift(aptg, 16, 17, -7, 11, 20); // truncation at top |
| confirmAreaRowShift(aptg, 12, 16, 4, 10, 20); // moved exactly to bottom - no change |
| confirmAreaRowShift(aptg, 12, 16, 6, 10, 17); // truncation at bottom |
| |
| // rows containing area bottom being shifted up: |
| confirmAreaRowShift(aptg, 18, 22, -1, 10, 19); // simple contraction at bottom |
| confirmAreaRowShift(aptg, 18, 22, -7, 10, 13); // simple contraction at bottom |
| confirmAreaRowShift(aptg, 18, 22, -8, 10, 17); // top calculated differently here |
| confirmAreaRowShift(aptg, 18, 22, -9, 9, 17); |
| confirmAreaRowShift(aptg, 18, 22,-15, 10, 20); // no change because range would be turned inside out |
| confirmAreaRowShift(aptg, 15, 19, -7, 13, 20); // dest truncates top (even though src is from inside range) |
| confirmAreaRowShift(aptg, 19, 23,-12, 7, 18); // complex: src encloses bottom, dest encloses top |
| |
| confirmAreaRowShift(aptg, 18, 22, 5, 10, 25); // simple expansion at bottom |
| } |
| |
| @Test |
| void testShiftAreasSourceColumns() { |
| |
| // all these operations are on an area ref spanning columns 10 to 20 |
| AreaPtg aptg = createAreaPtgColumn(10, 20); |
| |
| confirmAreaColumnShift(aptg, 9, 21, 20, 30, 40); |
| confirmAreaColumnShift(aptg, 10, 21, 20, 30, 40); |
| confirmAreaColumnShift(aptg, 9, 20, 20, 30, 40); |
| |
| confirmAreaColumnShift(aptg, 8, 11, -3, 7, 20); // simple expansion of top |
| // columns containing area top being shifted down: |
| confirmAreaColumnShift(aptg, 8, 11, 3, 13, 20); |
| confirmAreaColumnShift(aptg, 8, 11, 7, 17, 20); |
| confirmAreaColumnShift(aptg, 8, 11, 8, 18, 20); |
| confirmAreaColumnShift(aptg, 8, 11, 9, 12, 20); // note behaviour changes here |
| confirmAreaColumnShift(aptg, 8, 11, 10, 12, 21); |
| confirmAreaColumnShift(aptg, 8, 11, 12, 12, 23); |
| confirmAreaColumnShift(aptg, 8, 11, 13, 10, 20); // ignored |
| |
| // columns from within being moved: |
| confirmAreaColumnShift(aptg, 12, 16, 3, 10, 20); // stay within - no change |
| confirmAreaColumnShift(aptg, 11, 19, 20, 10, 20); // move completely out - no change |
| confirmAreaColumnShift(aptg, 16, 17, -6, 10, 20); // moved exactly to top - no change |
| confirmAreaColumnShift(aptg, 16, 17, -7, 11, 20); // truncation at top |
| confirmAreaColumnShift(aptg, 12, 16, 4, 10, 20); // moved exactly to bottom - no change |
| confirmAreaColumnShift(aptg, 12, 16, 6, 10, 17); // truncation at bottom |
| |
| // columns containing area bottom being shifted up: |
| confirmAreaColumnShift(aptg, 18, 22, -1, 10, 19); // simple contraction at bottom |
| confirmAreaColumnShift(aptg, 18, 22, -7, 10, 13); // simple contraction at bottom |
| confirmAreaColumnShift(aptg, 18, 22, -8, 10, 17); // top calculated differently here |
| confirmAreaColumnShift(aptg, 18, 22, -9, 9, 17); |
| confirmAreaColumnShift(aptg, 18, 22,-15, 10, 20); // no change because range would be turned inside out |
| confirmAreaColumnShift(aptg, 15, 19, -7, 13, 20); // dest truncates top (even though src is from inside range) |
| confirmAreaColumnShift(aptg, 19, 23,-12, 7, 18); // complex: src encloses bottom, dest encloses top |
| |
| confirmAreaColumnShift(aptg, 18, 22, 5, 10, 25); // simple expansion at bottom |
| } |
| |
| @Test |
| void testCopyAreasSourceRowsRelRel() { |
| |
| // all these operations are on an area ref spanning rows 10 to 20 |
| final AreaPtg aptg = createAreaPtgRow(10, 20, true, true); |
| |
| confirmAreaRowCopy(aptg, 0, 30, 20, 30, 40, true); |
| confirmAreaRowCopy(aptg, 15, 25, -15, -1, -1, true); //DeletedRef |
| } |
| |
| @Test |
| void testCopyAreasSourceRowsRelAbs() { |
| |
| // all these operations are on an area ref spanning rows 10 to 20 |
| final AreaPtg aptg = createAreaPtgRow(10, 20, true, false); |
| |
| // Only first row should move |
| confirmAreaRowCopy(aptg, 0, 30, 20, 20, 30, true); |
| confirmAreaRowCopy(aptg, 15, 25, -15, -1, -1, true); //DeletedRef |
| } |
| |
| @Test |
| 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 = createAreaPtgRow(10, 20, false, true); |
| |
| // Only last row should move |
| confirmAreaRowCopy(aptg, 0, 30, 20, 10, 40, true); |
| confirmAreaRowCopy(aptg, 15, 25, -15, 5, 10, true); //sortTopLeftToBottomRight swapped firstRow and lastRow because firstRow is absolute |
| } |
| |
| @Test |
| 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 = createAreaPtgRow(10, 20, false, false); |
| |
| //AbsFirstRow AbsLastRow references should't change when copied to a different row |
| confirmAreaRowCopy(aptg, 0, 30, 20, 10, 20, false); |
| confirmAreaRowCopy(aptg, 15, 25, -15, 10, 20, false); |
| } |
| |
| @Test |
| void testCopyAreasSourceColumnsRelRel() { |
| |
| // all these operations are on an area ref spanning columns 10 to 20 |
| final AreaPtg aptg = createAreaPtgColumn(10, 20, true, true); |
| |
| confirmAreaColumnCopy(aptg, 0, 30, 20, 30, 40, true); |
| confirmAreaColumnCopy(aptg, 15, 25, -15, -1, -1, true); //DeletedRef |
| } |
| |
| @Test |
| void testCopyAreasSourceColumnsRelAbs() { |
| |
| // all these operations are on an area ref spanning columns 10 to 20 |
| final AreaPtg aptg = createAreaPtgColumn(10, 20, true, false); |
| |
| // Only first column should move |
| confirmAreaColumnCopy(aptg, 0, 30, 20, 20, 30, true); |
| confirmAreaColumnCopy(aptg, 15, 25, -15, -1, -1, true); //DeletedRef |
| } |
| |
| @Test |
| void testCopyAreasSourceColumnsAbsRel() { |
| // aptg is part of a formula in a cell that was just copied to another column |
| // aptg column references should be updated by the difference in columns that the cell was copied |
| // No other references besides the cells that were involved in the copy need to be updated |
| // this makes the column copy significantly different from the column shift, where all references |
| // in the workbook need to track the column shift |
| |
| // all these operations are on an area ref spanning columns 10 to 20 |
| final AreaPtg aptg = createAreaPtgColumn(10, 20, false, true); |
| |
| // Only last column should move |
| confirmAreaColumnCopy(aptg, 0, 30, 20, 10, 40, true); |
| confirmAreaColumnCopy(aptg, 15, 25, -15, 5, 10, true); //sortTopLeftToBottomRight swapped firstColumn and lastColumn because firstColumn is absolute |
| } |
| |
| @Test |
| void testCopyAreasSourceColumnsAbsAbs() { |
| // aptg is part of a formula in a cell that was just copied to another column |
| // aptg column references should be updated by the difference in columns that the cell was copied |
| // No other references besides the cells that were involved in the copy need to be updated |
| // this makes the column copy significantly different from the column shift, where all references |
| // in the workbook need to track the column shift |
| |
| // all these operations are on an area ref spanning columns 10 to 20 |
| final AreaPtg aptg = createAreaPtgColumn(10, 20, false, false); |
| |
| //AbsFirstColumn AbsLastColumn references should't change when copied to a different column |
| confirmAreaColumnCopy(aptg, 0, 30, 20, 10, 20, false); |
| confirmAreaColumnCopy(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 |
| void testShiftAreasDestRows() { |
| // all these operations are on an area ref spanning rows 20 to 25 |
| AreaPtg aptg = createAreaPtgRow(20, 25); |
| |
| // no change because no overlap: |
| confirmAreaRowShift(aptg, 5, 10, 9, 20, 25); |
| confirmAreaRowShift(aptg, 5, 10, 21, 20, 25); |
| |
| confirmAreaRowShift(aptg, 11, 14, 10, 20, 25); |
| |
| confirmAreaRowShift(aptg, 7, 17, 10, -1, -1); // converted to DeletedAreaRef |
| confirmAreaRowShift(aptg, 5, 15, 7, 23, 25); // truncation at top |
| confirmAreaRowShift(aptg, 13, 16, 10, 20, 22); // truncation at bottom |
| } |
| |
| /** |
| * Tests what happens to an area ref when some outside columns are moved to overlap |
| * that area ref |
| */ |
| @Test |
| void testShiftAreasDestColumns() { |
| // all these operations are on an area ref spanning columns 20 to 25 |
| AreaPtg aptg = createAreaPtgColumn(20, 25); |
| |
| // no change because no overlap: |
| confirmAreaColumnShift(aptg, 5, 10, 9, 20, 25); |
| confirmAreaColumnShift(aptg, 5, 10, 21, 20, 25); |
| |
| confirmAreaColumnShift(aptg, 11, 14, 10, 20, 25); |
| |
| confirmAreaColumnShift(aptg, 7, 17, 10, -1, -1); // converted to DeletedAreaRef |
| confirmAreaColumnShift(aptg, 5, 15, 7, 23, 25); // truncation at top |
| confirmAreaColumnShift(aptg, 13, 16, 10, 20, 22); // truncation at bottom |
| } |
| |
| private static void confirmAreaRowShift( |
| 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; |
| |
| // clone so we can re-use aptg in calling method |
| AreaPtg copyPtg = aptg.copy(); |
| Ptg[] ptgs = { copyPtg, }; |
| boolean actualChanged = fs.adjustFormula(ptgs, 0); |
| if (expectedAreaFirstRow < 0) { |
| assertEquals(AreaErrPtg.class, ptgs[0].getClass()); |
| return; |
| } |
| assertEquals(expectedChanged, actualChanged); |
| // expected to change in place (although this is not a strict requirement) |
| assertEquals(copyPtg, ptgs[0]); |
| assertEquals(expectedAreaFirstRow, copyPtg.getFirstRow()); |
| assertEquals(expectedAreaLastRow, copyPtg.getLastRow()); |
| |
| } |
| |
| private static void confirmAreaColumnShift( |
| AreaPtg aptg, |
| int firstColumnMoved, int lastColumnMoved, int numberColumnsMoved, |
| int expectedAreaFirstColumn, int expectedAreaLastColumn) { |
| |
| FormulaShifter fs = FormulaShifter.createForColumnShift(0, "", firstColumnMoved, lastColumnMoved, numberColumnsMoved, SpreadsheetVersion.EXCEL2007); |
| boolean expectedChanged = aptg.getFirstColumn() != expectedAreaFirstColumn || aptg.getLastColumn() != expectedAreaLastColumn; |
| |
| // clone so we can re-use aptg in calling method |
| AreaPtg copyPtg = aptg.copy(); |
| Ptg[] ptgs = { copyPtg, }; |
| boolean actualChanged = fs.adjustFormula(ptgs, 0); |
| if (expectedAreaFirstColumn < 0) { |
| assertEquals(AreaErrPtg.class, ptgs[0].getClass()); |
| return; |
| } |
| assertEquals(expectedChanged, actualChanged); |
| // expected to change in place (although this is not a strict requirement) |
| assertEquals(copyPtg, ptgs[0]); |
| assertEquals(expectedAreaFirstColumn, copyPtg.getFirstColumn()); |
| assertEquals(expectedAreaLastColumn, copyPtg.getLastColumn()); |
| |
| } |
| |
| |
| private static void confirmAreaRowCopy(AreaPtg aptg, |
| int firstRowCopied, int lastRowCopied, int rowOffset, |
| int expectedFirstRow, int expectedLastRow, boolean expectedChanged) { |
| // clone so we can re-use aptg in calling method |
| final AreaPtg copyPtg = aptg.copy(); |
| 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(AreaErrPtg.class, ptgs[0].getClass(), "Reference should have shifted off worksheet, producing #REF! error: " + ptgs[0]); |
| return; |
| } |
| |
| assertEquals(expectedChanged, actualChanged, "Should this AreaPtg change due to row copy?"); |
| // expected to change in place (although this is not a strict requirement) |
| assertEquals(copyPtg, ptgs[0], "AreaPtgs should be modified in-place when a row containing the AreaPtg is copied"); |
| assertEquals(expectedFirstRow, copyPtg.getFirstRow(), "AreaPtg first row"); |
| assertEquals(expectedLastRow, copyPtg.getLastRow(), "AreaPtg last row"); |
| |
| } |
| |
| private static void confirmAreaColumnCopy(AreaPtg aptg, |
| int firstColumnCopied, int lastColumnCopied, int columnOffset, |
| int expectedFirstColumn, int expectedLastColumn, boolean expectedChanged) { |
| // clone so we can re-use aptg in calling method |
| final AreaPtg copyPtg = aptg.copy(); |
| final Ptg[] ptgs = { copyPtg, }; |
| final FormulaShifter fs = FormulaShifter.createForColumnCopy(0, null, firstColumnCopied, lastColumnCopied, columnOffset, SpreadsheetVersion.EXCEL2007); |
| final boolean actualChanged = fs.adjustFormula(ptgs, 0); |
| |
| // DeletedAreaRef |
| if (expectedFirstColumn < 0 || expectedLastColumn < 0) { |
| assertEquals(AreaErrPtg.class, ptgs[0].getClass(), |
| "Reference should have shifted off worksheet, producing #REF! error: " + ptgs[0]); |
| return; |
| } |
| |
| assertEquals(expectedChanged, actualChanged, "Should this AreaPtg change due to column copy?"); |
| // expected to change in place (although this is not a strict requirement) |
| assertEquals(copyPtg, ptgs[0], "AreaPtgs should be modified in-place when a column containing the AreaPtg is copied"); |
| assertEquals(expectedFirstColumn, copyPtg.getFirstColumn(), "AreaPtg first column"); |
| assertEquals(expectedLastColumn, copyPtg.getLastColumn(), "AreaPtg last column"); |
| |
| } |
| |
| private static AreaPtg createAreaPtgRow(int initialAreaFirstRow, int initialAreaLastRow) { |
| return createAreaPtgRow(initialAreaFirstRow, initialAreaLastRow, false, false); |
| } |
| |
| private static AreaPtg createAreaPtgColumn(int initialAreaFirstColumn, int initialAreaLastColumn) { |
| return createAreaPtgColumn(initialAreaFirstColumn, initialAreaLastColumn, false, false); |
| } |
| |
| private static AreaPtg createAreaPtgRow(int initialAreaFirstRow, int initialAreaLastRow, boolean firstRowRelative, boolean lastRowRelative) { |
| return new AreaPtg(initialAreaFirstRow, initialAreaLastRow, 2, 5, firstRowRelative, lastRowRelative, false, false); |
| } |
| |
| private static AreaPtg createAreaPtgColumn(int initialAreaFirstColumn, int initialAreaLastColumn, boolean firstColumnRelative, boolean lastColumnRelative) { |
| return new AreaPtg(2, 5, initialAreaFirstColumn, initialAreaLastColumn, false, false, firstColumnRelative, lastColumnRelative); |
| } |
| |
| @Test |
| 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(1, ((Ref3DPtg)ptgs[0]).getExternSheetIndex(), |
| "formula previously pointing to sheet 0 should now point to sheet 1"); |
| assertEquals(2, ((Ref3DPtg)ptgs[1]).getExternSheetIndex(), |
| "formula previously pointing to sheet 1 should now point to sheet 2"); |
| assertEquals(0, ((Ref3DPtg)ptgs[2]).getExternSheetIndex(), |
| "formula previously pointing to sheet 2 should now point to sheet 0"); |
| assertEquals(3, ((Ref3DPtg)ptgs[3]).getExternSheetIndex(), |
| "formula previously pointing to sheet 3 should be unchanged"); |
| } |
| |
| @Test |
| 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(0, ((Ref3DPtg)ptgs[0]).getExternSheetIndex(), |
| "formula previously pointing to sheet 0 should be unchanged"); |
| assertEquals(2, ((Ref3DPtg)ptgs[1]).getExternSheetIndex(), |
| "formula previously pointing to sheet 1 should now point to sheet 2"); |
| assertEquals(1, ((Ref3DPtg)ptgs[2]).getExternSheetIndex(), |
| "formula previously pointing to sheet 2 should now point to sheet 1"); |
| assertEquals(3, ((Ref3DPtg)ptgs[3]).getExternSheetIndex(), |
| "formula previously pointing to sheet 3 should be unchanged"); |
| } |
| |
| @Test |
| void testInvalidArgument() { |
| assertThrows(IllegalArgumentException.class, () -> |
| FormulaShifter.createForRowShift(1, "name", 1, 2, 0, SpreadsheetVersion.EXCEL97)); |
| assertThrows(IllegalArgumentException.class, () -> |
| FormulaShifter.createForRowShift(1, "name", 2, 1, 2, SpreadsheetVersion.EXCEL97)); |
| } |
| |
| @Test |
| void testConstructor() { |
| assertNotNull(FormulaShifter.createForRowShift(1, "name", 1, 2, 2, SpreadsheetVersion.EXCEL97)); |
| } |
| |
| @Test |
| 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")); |
| } |
| } |