package org.fxmisc.richtext.api; import com.nitorcreations.junit.runners.NestedRunner; import javafx.geometry.Bounds; import javafx.geometry.Insets; import javafx.geometry.Point2D; import javafx.geometry.Pos; import javafx.stage.Stage; import org.fxmisc.richtext.InlineCssTextAreaAppTest; import org.fxmisc.richtext.NavigationActions; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.util.concurrent.ExecutionException; import java.util.function.Consumer; import static javafx.scene.input.MouseButton.PRIMARY; import static org.junit.Assert.assertEquals; import static org.testfx.util.WaitForAsyncUtils.asyncFx; @RunWith(NestedRunner.class) public class HitTests extends InlineCssTextAreaAppTest { private static final String ALPHABET = "abcdefghijklmnopqrstuvwxyz"; private static final String FIFTY_PARS; private static final double PADDING_AMOUNT = 20; static { int totalPars = 50; int indexLimit = totalPars - 1; StringBuilder sb = new StringBuilder(); Consumer<Integer> appendParagraph = i -> sb.append("Par #").append(i).append(" ").append(ALPHABET); for (int i = 0; i < indexLimit; i++) { appendParagraph.accept(i); sb.append("\n"); } appendParagraph.accept(indexLimit); FIFTY_PARS = sb.toString(); } @Override public void start(Stage stage) throws Exception { super.start(stage); // insure stage width doesn't change irregardless of changes in superclass' start method stage.setWidth(400); stage.setHeight(400); } private void moveCaretToAreaEnd() { area.moveTo(area.getLength()); } public class When_Area_Is_Padded { public class And_Hits_Occur_Outside_Area { String text = "text"; String fullText = text + "\n" + text; @Before public void setup() { interact(() -> area.replaceText(fullText)); } @Test public void clicking_in_top_padding_moves_caret_to_top_line() { interact(() -> { area.setPadding(new Insets(PADDING_AMOUNT, 0, 0, 0)); moveCaretToAreaEnd(); }); moveTo(position(Pos.TOP_LEFT, 1, 2)).clickOn(PRIMARY); assertEquals(0, area.getCurrentParagraph()); interact(() -> moveCaretToAreaEnd()); moveTo(position(Pos.TOP_CENTER, 0, 0)).clickOn(PRIMARY); assertEquals(0, area.getCurrentParagraph()); } @Test public void clicking_in_left_padding_moves_caret_to_beginning_of_line_on_single_line_paragraph() { interact(() -> area.setPadding(new Insets(0, 0, 0, PADDING_AMOUNT))); moveCaretToAreaEnd(); moveTo(position(Pos.TOP_LEFT, 1, 1)).clickOn(PRIMARY); assertEquals(0, area.getCaretColumn()); } @Test public void clicking_in_right_padding_moves_caret_to_end_of_line_on_single_line_paragraph() { interact(() -> { area.setPadding(new Insets(0, PADDING_AMOUNT, 0, 0)); area.moveTo(0); // insure we're scrolled all the way to the right area.scrollBy(new Point2D(100, 0)); }); moveTo(position(Pos.TOP_RIGHT, -1, 1)).clickOn(PRIMARY); assertEquals(area.getParagraphLength(0), area.getCaretColumn()); } @Test public void clicking_in_bottom_padding_moves_caret_to_bottom_line() { interact(() -> { area.setPadding(new Insets(0, 0, PADDING_AMOUNT, 0)); area.moveTo(0); // insure we're scrolled all the way to the bottom area.scrollBy(new Point2D(0, 100)); }); moveTo(position(Pos.BOTTOM_CENTER, 0, -2)).clickOn(PRIMARY); assertEquals(1, area.getCurrentParagraph()); } } public class And_Hits_Occur_Inside_Area { @Before public void setup() { interact(() -> { area.replaceText(FIFTY_PARS); area.setPadding(new Insets(PADDING_AMOUNT)); area.setStyle("-fx-font-family: monospace; -fx-font-size: 12pt;"); }); } @Test public void clicking_character_should_move_caret_to_that_position() throws InterruptedException, ExecutionException { int start = area.getAbsolutePosition(3, 8); Bounds b = asyncFx( () -> area.getCharacterBoundsOnScreen(start, start + 1).get()) .get(); moveTo(b).clickOn(PRIMARY); assertEquals(start, area.getCaretPosition()); } @Test public void prev_page_leaves_caret_at_bottom_of_page() { area.showParagraphAtBottom(area.getParagraphs().size() - 1); // move to last line, column 0 area.moveTo(area.getParagraphs().size() - 1, 0); interact(() -> { // hit is called here area.prevPage(NavigationActions.SelectionPolicy.CLEAR); }); assertEquals(0, area.getCaretColumn()); assertEquals(area.lastVisibleParToAllParIndex(), area.getCurrentParagraph() + (IS_LINUX?0:1)); } @Test public void next_page_leaves_caret_at_top_of_page() { area.showParagraphAtTop(0); interact(() -> { area.moveTo(0); // hit is called here area.nextPage(NavigationActions.SelectionPolicy.CLEAR); }); assertEquals(0, area.getCaretColumn()); assertEquals(area.firstVisibleParToAllParIndex(), area.getCurrentParagraph() - (IS_LINUX?0:1)); } } } public class When_ParagraphBox_Is_Padded { @Before public void setup() { interact(() -> { area.replaceText(FIFTY_PARS); area.setStyle("-fx-font-family: monospace; -fx-font-size: 12pt;"); scene.getStylesheets().add(HitTests.class.getResource("padded-paragraph-box.css").toExternalForm()); }); } private void runTest() throws InterruptedException, ExecutionException { int start = area.getAbsolutePosition(3, 8); Bounds b = asyncFx( () -> area.getCharacterBoundsOnScreen(start, start + 1).get()) .get(); moveTo(b).clickOn(PRIMARY); assertEquals(start, area.getCaretPosition()); } public class And_Area_Is_Padded { @Test public void clicking_character_should_move_caret_to_that_position() throws InterruptedException, ExecutionException { interact(() -> area.setPadding(new Insets(PADDING_AMOUNT))); runTest(); } } public class And_Area_Is_Not_Padded { @Test public void clicking_character_should_move_caret_to_that_position() throws InterruptedException, ExecutionException { runTest(); } } } }