Bug 549110 - Add more tests for StyledText style handling

This adds some more tests using LineStyleListener and some additional
tests for styles applying metrics on tabs. (see bug 547532)

Change-Id: I404cabf1e5039e564e77183de816e703fb5c5e31
Signed-off-by: Paul Pazderski <paul-eclipse@ppazderski.de>
diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_StyledText.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_StyledText.java
index ea07d58..9196bd7 100644
--- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_StyledText.java
+++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_StyledText.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2018 IBM Corporation and others.
+ * Copyright (c) 2000, 2019 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -22,8 +22,10 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.function.BooleanSupplier;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 
@@ -32,6 +34,7 @@
 import org.eclipse.swt.custom.CaretListener;
 import org.eclipse.swt.custom.ExtendedModifyListener;
 import org.eclipse.swt.custom.LineBackgroundListener;
+import org.eclipse.swt.custom.LineStyleEvent;
 import org.eclipse.swt.custom.LineStyleListener;
 import org.eclipse.swt.custom.MovementEvent;
 import org.eclipse.swt.custom.MovementListener;
@@ -1951,6 +1954,21 @@
 
 }
 @Test
+public void test_getStyleRanges_Bug549110() {
+	text.setText("abc\tdef\n123\t");
+	StyleRange tabStyle = new StyleRange();
+	tabStyle.start = 3;
+	tabStyle.length = 1;
+	tabStyle.metrics = new GlyphMetrics(0, 0, 50);
+	text.setStyleRange(tabStyle);
+	tabStyle = new StyleRange();
+	tabStyle.start = 11;
+	tabStyle.length = 1;
+	tabStyle.metrics = new GlyphMetrics(0, 0, 100);
+	text.setStyleRange(tabStyle);
+	text.selectAll();
+}
+@Test
 public void test_getTabs() {
 	text.setTabs(1);
 	assertTrue(":a:", text.getTabs() == 1);
@@ -5245,6 +5263,102 @@
 	assertTrue(hasPixel(text, text.getDisplay().getSystemColor(SWT.COLOR_RED)));
 }
 
+/**
+ * Test LineStyleListener which provides styles but no ranges.
+ */
+@Test
+public void test_lineStyleListener_styles_render() throws InterruptedException {
+	final ArrayList<StyleRange> styles = new ArrayList<>();
+	styles.add(getStyle(0, 2, null, GREEN));
+	styles.add(getStyle(4, 1, null, GREEN));
+	styles.add(getStyle(7, 7, null, GREEN));
+	styles.add(getStyle(17, 13, null, GREEN));
+	final LineStyleListener listener = (LineStyleEvent event) -> {
+		event.styles = styles.toArray(new StyleRange[0]);
+	};
+	testLineStyleListener("0123456789\n123456789\n123456789", listener, () -> hasPixel(text, getColor(GREEN)));
+
+	// Bug 549110: test styling from LineStyleListener which include tab character with metrics
+	final StyleRange tabStyle = new StyleRange();
+	tabStyle.start = 15;
+	tabStyle.length = 1;
+	tabStyle.metrics = new GlyphMetrics(0, 0, 100);
+	styles.add(tabStyle);
+	testLineStyleListener("0123456789\n1234\t6789\n123456789", listener, () -> hasPixel(text, getColor(GREEN)));
+}
+
+/**
+ * Test LineStyleListener which provides styles and ranges.
+ */
+@Test
+public void test_lineStyleListener_stylesAndRanges_render() throws InterruptedException {
+	LineStyleListener listener = (LineStyleEvent event) -> {
+		final StyleRange style = getStyle(0, 0, null, GREEN);
+		event.styles =  new StyleRange[] { style, style, style, style };
+		event.ranges = new int[] {0,2, 4,1, 7,7, 17,13};
+	};
+	testLineStyleListener("0123456789\n123456789\n123456789", listener, () -> hasPixel(text, getColor(GREEN)));
+
+	// Bug 549110: test styling from LineStyleListener which include tab character with metrics
+	listener = (LineStyleEvent event) -> {
+		final StyleRange style = getStyle(0, 0, null, GREEN);
+		final StyleRange tabStyle = new StyleRange();
+		tabStyle.start = 15;
+		tabStyle.length = 1;
+		tabStyle.metrics = new GlyphMetrics(0, 0, 100);
+		event.styles =  new StyleRange[] { style, style, style, tabStyle, style };
+		event.ranges = new int[] {0,2, 4,1, 7,7, 15,1, 17,13};
+	};
+	testLineStyleListener("0123456789\n1234\t6789\n123456789", listener, () -> hasPixel(text, getColor(GREEN)));
+}
+
+/**
+ * Test LineStyleListener which provides invalid styles with invalid start or length.
+ */
+@Test
+public void test_lineStyleListener_invalidStyles_render() throws InterruptedException {
+	LineStyleListener listener = (LineStyleEvent event) -> {
+		event.styles = new StyleRange[] {
+			getStyle(-10, 4, null, GREEN),
+			getStyle(-5, 10, null, GREEN),
+			getStyle(12, 10, null, GREEN),
+			getStyle(40, 8, null, GREEN),
+		};
+	};
+	testLineStyleListener("0123456789\n123456789", listener, () -> hasPixel(text, getColor(GREEN)));
+
+	// Bug 549110: test styling from LineStyleListener which include tab character with metrics
+	final StyleRange tabStyle = new StyleRange();
+	listener = (LineStyleEvent event) -> {
+		event.styles = new StyleRange[] { tabStyle };
+	};
+	// The following tests do not need a condition to wait and test since they should trigger/test
+	// for exceptions and the getTextBounds fail due to bug 547532.
+	tabStyle.start = 0; tabStyle.length = 1; tabStyle.metrics = new GlyphMetrics(0, 0, 25);
+	testLineStyleListener("\t", listener, () -> true /*text.getTextBounds(0, 0).width == 25*/);
+	tabStyle.start = -2; tabStyle.length = 3; tabStyle.metrics = new GlyphMetrics(0, 0, 40);
+	testLineStyleListener("\t", listener, () -> true /*text.getTextBounds(0, 0).width == 40*/);
+	tabStyle.start = 0; tabStyle.length = 3; tabStyle.metrics = new GlyphMetrics(0, 0, 70);
+	testLineStyleListener("\t", listener, () -> true /*text.getTextBounds(0, 0).width == 70*/);
+	tabStyle.start = -2; tabStyle.length = 10; tabStyle.metrics = new GlyphMetrics(0, 0, 100);
+	testLineStyleListener("\t", listener, () -> true /*text.getTextBounds(0, 0).width == 100*/);
+}
+
+private void testLineStyleListener(String content, LineStyleListener listener, BooleanSupplier evaluation) throws InterruptedException {
+	try {
+		text.setText("");
+		text.addLineStyleListener(listener);
+		shell.setVisible(true);
+		text.setText(content);
+		text.setMargins(0, 0, 0, 0);
+		text.pack();
+		processEvents(1000, evaluation);
+		assertTrue("Text not styled correctly.", evaluation.getAsBoolean());
+	} finally {
+		text.removeLineStyleListener(listener);
+	}
+}
+
 private boolean hasPixel(StyledText text, Color expectedColor) {
 	GC gc = new GC(text);
 	final Image image = new Image(text.getDisplay(), text.getSize().x, text.getSize().y);
@@ -5360,5 +5474,37 @@
 	consistencyEvent(30, 10, 50, 0, ConsistencyUtility.MOUSE_DRAG);
 }
 
+/**
+ * This tests if a tab character styled with custom metrics affects other styled parts.
+ * <p>
+ * Such a problem was once caused with bug 547532 and discovered along bug 549110.
+ * </p>
+ */
+@Test
+public void test_GlyphMetricsOnTab_Bug549110() throws InterruptedException {
+	shell.setVisible(true);
+	text.setText("ab\tcde");
+	text.setMargins(0, 0, 0, 0);
+	text.pack();
+	processEvents(1000,
+			() -> hasPixel(text, text.getBackground())
+					&& !hasPixel(text, getColor(RED))
+					&& !hasPixel(text, getColor(BLUE)));
+	assertTrue(hasPixel(text, text.getBackground()));
+	assertFalse(hasPixel(text, getColor(RED)));
+	assertFalse(hasPixel(text, getColor(BLUE)));
+
+	final StyleRange styleR = getStyle(0, 1, null, RED);
+	final StyleRange styleB = getStyle(4, 1, null, BLUE);
+	final StyleRange tabStyle = new StyleRange();
+	tabStyle.start = 2;
+	tabStyle.length = 1;
+	tabStyle.metrics = new GlyphMetrics(0, 0, 100);
+	text.replaceStyleRanges(0, text.getText().length(), new StyleRange[] { styleR, tabStyle, styleB });
+	text.pack();
+	processEvents(1000, () -> hasPixel(text, getColor(RED)) && hasPixel(text, getColor(BLUE)));
+	assertTrue("Wrong style before tab", hasPixel(text, getColor(RED)));
+	assertTrue("Wrong style after tab", hasPixel(text, getColor(BLUE)));
 }
 
+}