diff --git a/src/main/java/org/apache/commons/text/WordUtils.java b/src/main/java/org/apache/commons/text/WordUtils.java index accd436fb4..5044cc8259 100644 --- a/src/main/java/org/apache/commons/text/WordUtils.java +++ b/src/main/java/org/apache/commons/text/WordUtils.java @@ -561,13 +561,13 @@ public static String uncapitalize(final String str, final char... delimiters) { } /** - * Wraps a single line of text, identifying words by {@code ' '}. + * Wraps a single line of text, identifying word boundaries by {@code ' '}. * *

New lines will be separated by the system property line separator. * Very long words, such as URLs will not be wrapped.

* - *

Leading spaces on a new line are stripped. - * Trailing spaces are not stripped.

+ *

Leading spaces on a new line are trimmed. + * Trailing spaces on a line are not trimmed.

* * * @@ -614,10 +614,10 @@ public static String wrap(final String str, final int wrapLength) { } /** - * Wraps a single line of text, identifying words by {@code ' '}. + * Wraps a single line of text, identifying word boundaries by {@code ' '}. * - *

Leading spaces on a new line are stripped. - * Trailing spaces are not stripped.

+ *

Leading spaces on a new line are trimmed. + * Trailing spaces on a line are not trimmed.

* *
Examples
* @@ -696,10 +696,11 @@ public static String wrap(final String str, } /** - * Wraps a single line of text, identifying words by {@code wrapOn}. + * Wraps a single line of text, identifying words boundaries by {@code wrapOn}, + * parsed as a regular expression. * - *

Leading spaces on a new line are stripped. - * Trailing spaces are not stripped.

+ *

Leading matches of {@code wrapOn} on a new line are trimmed. + * Trailing matches of {@code wrapOn} on a line are not trimmed.

* *
Examples
* @@ -783,7 +784,7 @@ public static String wrap(final String str, * @param newLineStr the string to insert for a new line, * {@code null} uses the system property line separator * @param wrapLongWords true if long words (such as URLs) should be wrapped - * @param wrapOn regex expression to be used as a breakable characters, + * @param wrapOn regex expression to be used as word boundary, * if blank string is provided a space character will be used * @return a line with newlines inserted, {@code null} if null input */ @@ -804,85 +805,73 @@ public static String wrap(final String str, if (StringUtils.isBlank(wrapOn)) { wrapOn = " "; } - final Pattern patternToWrapOn = Pattern.compile(wrapOn); + final int inputLineLength = str.length(); - int offset = 0; + final StringBuilder wrappedLine = new StringBuilder(inputLineLength + 32); - int matcherSize = -1; - - while (offset < inputLineLength) { - int spaceToWrapAt = -1; - Matcher matcher = patternToWrapOn.matcher(str.substring(offset, - Math.min((int) Math.min(Integer.MAX_VALUE, offset + wrapLength + 1L), inputLineLength))); - if (matcher.find()) { - if (matcher.start() == 0) { - matcherSize = matcher.end(); - if (matcherSize != 0) { - offset += matcher.end(); - continue; - } - offset += 1; - } - spaceToWrapAt = matcher.start() + offset; - } - // only last line without leading spaces is left - if (inputLineLength - offset <= wrapLength) { - break; - } + final Pattern wrapOnPattern = Pattern.compile(wrapOn); + final Pattern newLineStrPattern = Pattern.compile(newLineStr); - while (matcher.find()) { - spaceToWrapAt = matcher.start() + offset; - } + int lineStart = 0; + int lineEnd = 0; + int nextLineStart = 0; - if (spaceToWrapAt >= offset) { - // normal case - wrappedLine.append(str, offset, spaceToWrapAt); - wrappedLine.append(newLineStr); - offset = spaceToWrapAt + 1; + Matcher newlineStrMatcher = newLineStrPattern.matcher(str); + Matcher wrapOnMatcher = wrapOnPattern.matcher(str); - } else // really long word or URL - if (wrapLongWords) { - if (matcherSize == 0) { - offset--; - } - // wrap really long word one line at a time - wrappedLine.append(str, offset, wrapLength + offset); + int nextForcedWrap = wrapLength; + int nextNewlineStr = newlineStrMatcher.find() ? newlineStrMatcher.start() : inputLineLength; + int nextWrapOn = wrapOnMatcher.find() ? wrapOnMatcher.start() : inputLineLength; + + boolean suppressNewline = true; + + while(lineStart < inputLineLength) { + // Here is always(!) the beginning of a line + + if(!suppressNewline) { wrappedLine.append(newLineStr); - offset += wrapLength; - matcherSize = -1; } else { - // do not wrap really long word, just extend beyond limit - matcher = patternToWrapOn.matcher(str.substring(offset + wrapLength)); - if (matcher.find()) { - matcherSize = matcher.end() - matcher.start(); - spaceToWrapAt = matcher.start() + offset + wrapLength; - } + suppressNewline = false; + } - if (spaceToWrapAt >= 0) { - if (matcherSize == 0 && offset != 0) { - offset--; - } - wrappedLine.append(str, offset, spaceToWrapAt); - wrappedLine.append(newLineStr); - offset = spaceToWrapAt + 1; - } else { - if (matcherSize == 0 && offset != 0) { - offset--; - } - wrappedLine.append(str, offset, str.length()); - offset = inputLineLength; - matcherSize = -1; - } + // Trim all leading instances of wrapOn + while(nextWrapOn == lineStart && lineStart < inputLineLength) { + lineStart = nextLineStart = !wrapOnMatcher.hitEnd() ? wrapOnMatcher.end() : inputLineLength; + nextForcedWrap = lineStart + wrapLength; + nextWrapOn = !wrapOnMatcher.hitEnd() && wrapOnMatcher.find() ? wrapOnMatcher.start() : inputLineLength; } - } - if (matcherSize == 0 && offset < inputLineLength) { - offset--; - } + if(wrapLongWords && nextForcedWrap < nextNewlineStr && nextForcedWrap < nextWrapOn) { + // We need to wrap the line due to a long word + lineEnd = nextLineStart = nextForcedWrap; + } else if(nextNewlineStr <= nextForcedWrap || (nextNewlineStr > nextForcedWrap && nextNewlineStr <= nextWrapOn)) { + // There is a newLineStr before the length limit of the line, + // or after the length limit and before the next wrapOn, + // so we wrap just after that newline string. + // This preserves trailing instances of wrapOn. + nextLineStart = !newlineStrMatcher.hitEnd() ? newlineStrMatcher.end() : inputLineLength; + lineEnd = nextLineStart; + nextNewlineStr = !newlineStrMatcher.hitEnd() && newlineStrMatcher.find() ? newlineStrMatcher.start() : inputLineLength; + suppressNewline = true; // This saves a call to append() + } else { + // Here we are not forced to wrap due to long word, nor due to + // the existence of a newline string. + // So we can just keep look for wrapOn, + // until we run out of room on the line + do { + lineEnd = nextWrapOn; + nextLineStart = !wrapOnMatcher.hitEnd() ? wrapOnMatcher.end() : inputLineLength; + nextWrapOn = !wrapOnMatcher.hitEnd() && wrapOnMatcher.find() ? wrapOnMatcher.start() : inputLineLength; + } while(nextWrapOn < inputLineLength && nextWrapOn <= nextForcedWrap); + } - // Whatever is left in line is short enough to just pass through - wrappedLine.append(str, offset, str.length()); + // We have found the end of this line, and can append it to the result + wrappedLine.append(str.substring(lineStart, lineEnd)); + + lineStart = nextLineStart; + nextForcedWrap = nextLineStart + wrapLength; + } return wrappedLine.toString(); } diff --git a/src/test/java/org/apache/commons/text/WordUtilsTest.java b/src/test/java/org/apache/commons/text/WordUtilsTest.java index 6c8d97bc99..9dd934ad83 100644 --- a/src/test/java/org/apache/commons/text/WordUtilsTest.java +++ b/src/test/java/org/apache/commons/text/WordUtilsTest.java @@ -21,16 +21,23 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Modifier; +import java.util.stream.Stream; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; /** * Tests {@link WordUtils} class. */ public class WordUtilsTest { + private final static String systemNewLine = System.lineSeparator(); + @Test public void testAbbreviateForLowerThanMinusOneValues() { assertThatIllegalArgumentException().isThrownBy(() -> assertThat(WordUtils.abbreviate("01 23 45 67 89", 9, -10, null)).isEqualTo("01 23 45 67")); @@ -388,163 +395,262 @@ public void testUncapitalizeWithDelimiters_String() { assertThat(WordUtils.uncapitalize("I AM.FINE", null)).isEqualTo("i aM.FINE"); } - @Test - public void testWrap_StringInt() { + @DisplayName("Wrap, null and empty strings") + @Test + public void testWrap_NullAndEmpty() { assertThat(WordUtils.wrap(null, 20)).isNull(); assertThat(WordUtils.wrap(null, -1)).isNull(); - assertThat(WordUtils.wrap("", 20)).isEqualTo(""); assertThat(WordUtils.wrap("", -1)).isEqualTo(""); - // normal - final String systemNewLine = System.lineSeparator(); - String input = "Here is one line of text that is going to be wrapped after 20 columns."; - String expected = "Here is one line of" + systemNewLine + "text that is going" + systemNewLine - + "to be wrapped after" + systemNewLine + "20 columns."; - assertThat(WordUtils.wrap(input, 20)).isEqualTo(expected); - - // long word at end - input = "Click here to jump to the commons website - https://commons.apache.org"; - expected = "Click here to jump" + systemNewLine + "to the commons" + systemNewLine + "website -" + systemNewLine - + "https://commons.apache.org"; - assertThat(WordUtils.wrap(input, 20)).isEqualTo(expected); - - // long word in middle - input = "Click here, https://commons.apache.org, to jump to the commons website"; - expected = "Click here," + systemNewLine + "https://commons.apache.org," + systemNewLine + "to jump to the" - + systemNewLine + "commons website"; - assertThat(WordUtils.wrap(input, 20)).isEqualTo(expected); - - // leading spaces on a new line are stripped - // trailing spaces are not stripped - input = "word1 word2 word3"; - expected = "word1 " + systemNewLine + "word2 " + systemNewLine + "word3"; - assertThat(WordUtils.wrap(input, 7)).isEqualTo(expected); - } - - @Test - public void testWrap_StringIntStringBoolean() { assertThat(WordUtils.wrap(null, 20, "\n", false)).isNull(); assertThat(WordUtils.wrap(null, 20, "\n", true)).isNull(); assertThat(WordUtils.wrap(null, 20, null, true)).isNull(); assertThat(WordUtils.wrap(null, 20, null, false)).isNull(); assertThat(WordUtils.wrap(null, -1, null, true)).isNull(); assertThat(WordUtils.wrap(null, -1, null, false)).isNull(); - + assertThat(WordUtils.wrap("", 20, "\n", false)).isEqualTo(""); assertThat(WordUtils.wrap("", 20, "\n", true)).isEqualTo(""); assertThat(WordUtils.wrap("", 20, null, false)).isEqualTo(""); assertThat(WordUtils.wrap("", 20, null, true)).isEqualTo(""); assertThat(WordUtils.wrap("", -1, null, false)).isEqualTo(""); assertThat(WordUtils.wrap("", -1, null, true)).isEqualTo(""); - - // normal - String input = "Here is one line of text that is going to be wrapped after 20 columns."; - String expected = "Here is one line of\ntext that is going\nto be wrapped after\n20 columns."; - assertThat(WordUtils.wrap(input, 20, "\n", false)).isEqualTo(expected); - assertThat(WordUtils.wrap(input, 20, "\n", true)).isEqualTo(expected); - - // unusual newline char - input = "Here is one line of text that is going to be wrapped after 20 columns."; - expected = "Here is one line of
text that is going
to be wrapped after
20 columns."; - assertThat(WordUtils.wrap(input, 20, "
", false)).isEqualTo(expected); - assertThat(WordUtils.wrap(input, 20, "
", true)).isEqualTo(expected); - - // short line length - input = "Here is one line"; - expected = "Here\nis one\nline"; - assertThat(WordUtils.wrap(input, 6, "\n", false)).isEqualTo(expected); - expected = "Here\nis\none\nline"; - assertThat(WordUtils.wrap(input, 2, "\n", false)).isEqualTo(expected); - assertThat(WordUtils.wrap(input, -1, "\n", false)).isEqualTo(expected); - - // system newline char - final String systemNewLine = System.lineSeparator(); - input = "Here is one line of text that is going to be wrapped after 20 columns."; - expected = "Here is one line of" + systemNewLine + "text that is going" + systemNewLine + "to be wrapped after" - + systemNewLine + "20 columns."; - assertThat(WordUtils.wrap(input, 20, null, false)).isEqualTo(expected); - assertThat(WordUtils.wrap(input, 20, null, true)).isEqualTo(expected); - - // with extra spaces - input = " Here: is one line of text that is going to be wrapped after 20 columns."; - expected = "Here: is one line\nof text that is \ngoing to be \nwrapped after 20 \ncolumns."; - assertThat(WordUtils.wrap(input, 20, "\n", false)).isEqualTo(expected); - assertThat(WordUtils.wrap(input, 20, "\n", true)).isEqualTo(expected); - - // with tab - input = "Here is\tone line of text that is going to be wrapped after 20 columns."; - expected = "Here is\tone line of\ntext that is going\nto be wrapped after\n20 columns."; - assertThat(WordUtils.wrap(input, 20, "\n", false)).isEqualTo(expected); - assertThat(WordUtils.wrap(input, 20, "\n", true)).isEqualTo(expected); - - // with tab at wrapColumn - input = "Here is one line of\ttext that is going to be wrapped after 20 columns."; - expected = "Here is one line\nof\ttext that is\ngoing to be wrapped\nafter 20 columns."; - assertThat(WordUtils.wrap(input, 20, "\n", false)).isEqualTo(expected); - assertThat(WordUtils.wrap(input, 20, "\n", true)).isEqualTo(expected); - - // difference because of long word - input = "Click here to jump to the commons website - https://commons.apache.org"; - expected = "Click here to jump\nto the commons\nwebsite -\nhttps://commons.apache.org"; - assertThat(WordUtils.wrap(input, 20, "\n", false)).isEqualTo(expected); - expected = "Click here to jump\nto the commons\nwebsite -\nhttps://commons.apac\nhe.org"; - assertThat(WordUtils.wrap(input, 20, "\n", true)).isEqualTo(expected); - - // difference because of long word in middle - input = "Click here, https://commons.apache.org, to jump to the commons website"; - expected = "Click here,\nhttps://commons.apache.org,\nto jump to the\ncommons website"; - assertThat(WordUtils.wrap(input, 20, "\n", false)).isEqualTo(expected); - expected = "Click here,\nhttps://commons.apac\nhe.org, to jump to\nthe commons website"; - assertThat(WordUtils.wrap(input, 20, "\n", true)).isEqualTo(expected); } - @Test - public void testWrap_StringIntStringBooleanString() { - - // no changes test - String input = "flammable/inflammable"; - String expected = "flammable/inflammable"; - assertThat(WordUtils.wrap(input, 30, "\n", false, "/")).isEqualTo(expected); - - // wrap on / and small width - expected = "flammable\ninflammable"; - assertThat(WordUtils.wrap(input, 2, "\n", false, "/")).isEqualTo(expected); - - // wrap long words on / 1 - expected = "flammable\ninflammab\nle"; - assertThat(WordUtils.wrap(input, 9, "\n", true, "/")).isEqualTo(expected); - - // wrap long words on / 2 - expected = "flammable\ninflammable"; - assertThat(WordUtils.wrap(input, 15, "\n", true, "/")).isEqualTo(expected); - - // wrap long words on / 3 - input = "flammableinflammable"; - expected = "flammableinflam\nmable"; - assertThat(WordUtils.wrap(input, 15, "\n", true, "/")).isEqualTo(expected); - } - - @Test - public void testWrapAtMiddleTwice() { - assertThat(WordUtils.wrap("abcdefggabcdef", 2, "\n", false, "(?=g)")).isEqualTo("abcdef\n\nabcdef"); - } - - - - @Test - public void testWrapAtStartAndEnd() { - assertThat(WordUtils.wrap("nabcdefabcdefn", 2, "\n", false, "(?=n)")).isEqualTo("\nabcdefabcdef\n"); - } - - @Test - public void testWrapWithMultipleRegexMatchOfLength0() { - assertThat(WordUtils.wrap("abcdefabcdef", 2, "\n", false, "(?=d)")).isEqualTo("abc\ndefabc\ndef"); - } - - @Test - public void testWrapWithRegexMatchOfLength0() { - assertThat(WordUtils.wrap("abcdef", 2, "\n", false, "(?=d)")).isEqualTo("abc\ndef"); - } + @DisplayName("Wrap, typical use cases, length argument") + @ParameterizedTest(name = "{0}") + @MethodSource("provideDataForTypicalUseCasesLengthArgument") + public void testWrap_TypicalUseCasesLengthArgument(String name, String input, int lineLength, String expected) { + String actual = WordUtils.wrap(input, lineLength); + assertThat(actual).isEqualTo(expected); + } + + private static Stream provideDataForTypicalUseCasesLengthArgument() { + return Stream.of( + Arguments.of( + "Normal", + "Here is one line of text that is going to be wrapped after 20 columns.", 20, + "Here is one line of" + systemNewLine + "text that is going" + systemNewLine + + "to be wrapped after" + systemNewLine + "20 columns."), + Arguments.of( + "Long word at end", + "Click here to jump to the commons website - https://commons.apache.org", 20, + "Click here to jump" + systemNewLine + "to the commons" + systemNewLine + "website -" + systemNewLine + + "https://commons.apache.org"), + Arguments.of( + "Long word in the middle", + "Click here, https://commons.apache.org, to jump to the commons website", 20, + "Click here," + systemNewLine + "https://commons.apache.org," + systemNewLine + "to jump to the" + + systemNewLine + "commons website"), + Arguments.of( + "Strip leading, keep trailing", + "word1 word2 word3", 7, + "word1 " + systemNewLine + "word2 " + systemNewLine + "word3"), + + Arguments.of( + "Long word, followed by wrap", + "LongForALine Line 2 and Line 3", 12, + "LongForALine" + systemNewLine + "Line 2 and" + systemNewLine + "Line 3"), + Arguments.of( + "Long word, followed by newline", + "LongForALine" + systemNewLine + "Line 2 and Line 3", 12, + "LongForALine" + systemNewLine + "Line 2 and" + systemNewLine + "Line 3"), + Arguments.of( + "Too long word, followed by wrap", + "TooLongForALine Line 2 and Line 3", 10, + "TooLongForALine" + systemNewLine + "Line 2 and" + systemNewLine + "Line 3"), + Arguments.of( + "Too long word, followed by newline", + "TooLongForALine" + systemNewLine + "Line 2 and Line 3", 10, + "TooLongForALine" + systemNewLine + "Line 2 and" + systemNewLine + "Line 3") + ); + } + + @DisplayName("Wrap, typical use cases, default word wrap") + @ParameterizedTest(name = "{0}") + @MethodSource("provideDataForTypicalUseCasesDefaultWordWrap") + public void testWrap_TypicalUseCasesDefaultWordWrap(String name, String input, int lineLength, String newLineString, boolean breakLongWords, String expected) { + String actual = WordUtils.wrap(input, lineLength, newLineString, breakLongWords); + assertThat(actual).isEqualTo(expected); + } + + private static Stream provideDataForTypicalUseCasesDefaultWordWrap() { + return Stream.of( + Arguments.of( + "Normal", + "Here is one line of text that is going to be wrapped after 20 columns.", + 20, null, false, + "Here is one line of" + systemNewLine + "text that is going" + systemNewLine + + "to be wrapped after" + systemNewLine + "20 columns."), + + Arguments.of("Custom newline string, preserve long words", + "Here is one line of text that is going to be wrapped after 20 columns.", + 20, "
", false, + "Here is one line of
text that is going
to be wrapped after
20 columns."), + + Arguments.of("Short line, length 6", "Here is one line", 6, "\n", false, "Here\nis one\nline"), + Arguments.of("Short line, length 2", "Here is one line", 2, "\n", false, "Here\nis\none\nline"), + Arguments.of("Short line, length -1", "Here is one line", -1, "\n", false, "Here\nis\none\nline"), + + Arguments.of("With extra spaces, preserve long words", + " Here: is one line of text that is going to be wrapped after 20 columns.", + 20, "\n", false, + "Here: is one line\nof text that is \ngoing to be \nwrapped after 20 \ncolumns."), + Arguments.of("With extra spaces, break long words", + " Here: is one line of text that is going to be wrapped after 20 columns.", + 20, "\n", true, + "Here: is one line\nof text that is \ngoing to be \nwrapped after 20 \ncolumns."), + + Arguments.of("With tabs, preserve long words", + "Here is\tone line of text that is going to be wrapped after 20 columns.", + 20, "\n", false, + "Here is\tone line of\ntext that is going\nto be wrapped after\n20 columns."), + Arguments.of("With tabs, break long words", + "Here is\tone line of text that is going to be wrapped after 20 columns.", + 20, "\n", true, + "Here is\tone line of\ntext that is going\nto be wrapped after\n20 columns."), + + Arguments.of("With tab at wrapColumn, preserve long words", + "Here is one line of\ttext that is going to be wrapped after 20 columns.", + 20, "\n", false, + "Here is one line\nof\ttext that is\ngoing to be wrapped\nafter 20 columns."), + Arguments.of("With tab at wrapColumn, break long words", + "Here is one line of\ttext that is going to be wrapped after 20 columns.", + 20, "\n", true, + "Here is one line\nof\ttext that is\ngoing to be wrapped\nafter 20 columns."), + + Arguments.of("Difference because of long word, preserve long words", + "Click here to jump to the commons website - https://commons.apache.org", + 20, "\n", false, + "Click here to jump\nto the commons\nwebsite -\nhttps://commons.apache.org"), + Arguments.of("Difference because of long word, break long words", + "Click here to jump to the commons website - https://commons.apache.org", + 20, "\n", true, + "Click here to jump\nto the commons\nwebsite -\nhttps://commons.apac\nhe.org"), + + Arguments.of("Difference because of long word in middle, preserve long words", + "Click here, https://commons.apache.org, to jump to the commons website", + 20, "\n", false, + "Click here,\nhttps://commons.apache.org,\nto jump to the\ncommons website"), + Arguments.of("Difference because of long word in middle, break long words", + "Click here, https://commons.apache.org, to jump to the commons website", + 20, "\n", true, + "Click here,\nhttps://commons.apac\nhe.org, to jump to\nthe commons website") + + ); + } + + @DisplayName("Wrap, typical use cases, custom word wrap") + @ParameterizedTest(name = "{0}") + @MethodSource("provideDataForTypicalUseCasesCustomWordWrap") + public void testWrap_TypicalUseCasesCustomWordWrap(String name, String input, int lineLength, String newLineString, boolean breakLongWords, String wordWrap, String expected) { + String actual = WordUtils.wrap(input, lineLength, newLineString, breakLongWords, wordWrap); + assertThat(actual).isEqualTo(expected); + } + + private static Stream provideDataForTypicalUseCasesCustomWordWrap() { + // The word wrap pattern is either a whitespace character that is stripped, or a hyphen that is kept + // Also note that \n is both the newline string — which must force a newline — and one of the acceptable wrap characters + return Stream.of( + Arguments.of("Difference because of long word in middle, preserve long words", + "This\nhere is a\ntest of word-wrap where hyphens and whitespace-characters act as potential line-breaks along\nthe new-line character", + 9, "\n", false, "(?<=-)|\\s", + "This\nhere is a\ntest of\nword-wrap\nwhere\nhyphens\nand\nwhitespace-\ncharacters\nact as\npotential\nline-\nbreaks\nalong\nthe new-\nline\ncharacter"), + Arguments.of("Difference because of long word in middle, break long words", + "This\nhere is a\ntest of word-wrap where hyphens and whitespace-characters act as potential line-breaks along\nthe new-line character", + 9, "\n", true, "(?<=-)|\\s", + "This\nhere is a\ntest of\nword-wrap\nwhere\nhyphens\nand\nwhitespac\ne-\ncharacter\ns act as\npotential\nline-\nbreaks\nalong\nthe new-\nline\ncharacter") + ); + } + + @DisplayName("Wrap, with break in the middle") + @ParameterizedTest(name = "{0}") + @MethodSource("provideDataForBreakInTheMiddle") + public void testWrap_BreakInTheMiddle(String name, String input, int lineLength, String newLineString, boolean breakLongWords, String wrapOn, String expected) { + String actual = WordUtils.wrap(input, lineLength, newLineString, breakLongWords, wrapOn); + assertThat(actual).isEqualTo(expected); + } + + private static Stream provideDataForBreakInTheMiddle() { + return Stream.of( + Arguments.of( + "No changes", "flammable/inflammable", 30, "\n", false, null, "flammable/inflammable"), + Arguments.of( + "Wrap on / and small width", "flammable/inflammable", 2, "\n", false, "/", "flammable\ninflammable"), + Arguments.of( + "Wrap long words on / 1", "flammable/inflammable", 9, "\n", true, "/", "flammable\ninflammab\nle"), + Arguments.of( + "Wrap long words on / 2", "flammable/inflammable", 15, "\n", true, "/", "flammable\ninflammable"), + Arguments.of( + "Wrap long words on / 3", "flammableinflammable", 15, "\n", true, "/", "flammableinflam\nmable") + ); + } + + @DisplayName("Wrap, with wrap at start, middle, end") + @ParameterizedTest(name = "{0}") + @MethodSource("provideDataForWrapAtStartMiddleEnd") + public void testWrap_WrapAtStartMiddleEnd(String name, String input, int lineLength, String newLineString, boolean breakLongWords, String wrapOn, String expected) { + String actual = WordUtils.wrap(input, lineLength, newLineString, breakLongWords, wrapOn); + assertThat(actual).isEqualTo(expected); + } + + private static Stream provideDataForWrapAtStartMiddleEnd() { + return Stream.of( + Arguments.of( + "Wrap at middle twice, zero length match, preserve long words", + "abcdef--abcdef", 3, "\n", false, "(?=-)", "abcdef\n-\n-abcdef" + ), + Arguments.of( + "Wrap at middle twice, zero length match, break long words", + "abcdef--abcdef", 3, "\n", true, "(?=-)", "abc\ndef\n-\n-ab\ncde\nf" + ), + Arguments.of( + "Wrap at middle twice, one length match, preserve long words", + "abcdef--abcdef", 3, "\n", false, "-", "abcdef\nabcdef" + ), + Arguments.of( + "Wrap at middle twice, one length match, break long words", + "abcdef--abcdef", 3, "\n", true, "-", "abc\ndef\nabc\ndef" + ), + Arguments.of( + "Wrap at start and end, zero length match, preserve long words", + "-abcdefabcdef-", 2, "\n", false, "(?=-)", "-abcdefabcdef\n-" + ), + Arguments.of( + "Wrap at start and end, zero length match, break long words", + "-abcdefabcdef-", 2, "\n", false, "(?<=-)", "-\nabcdefabcdef-" + ), + Arguments.of( + "Wrap at start and end, one length match, preserve long words", + "-abcdefabcdef-", 2, "\n", false, "-", "abcdefabcdef" + ), + Arguments.of( + "Wrap at start and end, one length match, break long words", + "-abcdefabcdef-", 2, "\n", true, "-", "ab\ncd\nef\nab\ncd\nef" + ) + ); + } + + @DisplayName("Wrap, zero length regular expression") + @ParameterizedTest(name = "{0}") + @MethodSource("provideDataForZeroLengthRegEx") + public void testWrap_ZeroLengthRegEx(String name, String input, int lineLength, String newLineString, boolean breakLongWords, String wrapOn, String expected) { + String actual = WordUtils.wrap(input, lineLength, newLineString, breakLongWords, wrapOn); + assertThat(actual).isEqualTo(expected); + } + + private static Stream provideDataForZeroLengthRegEx() { + return Stream.of( + Arguments.of("Single, 1", "abc-def", 2, "\n", false, "(?=-)", "abc\n-def"), + Arguments.of("Single, 2", "abc-def", 2, "\n", false, "(?<=-)", "abc-\ndef"), + Arguments.of("Single, 3", "abc-def", 2, "\n", true, "(?=-)", "ab\nc\n-d\nef"), + Arguments.of("Single, 4", "abc-def", 2, "\n", true, "(?<=-)", "ab\nc-\nde\nf"), + Arguments.of("Multiple, 1", "abc-deabc-de", 2, "\n", false, "(?=-)", "abc\n-deabc\n-de"), + Arguments.of("Multiple, 2", "abc-deabc-de", 2, "\n", false, "(?<=-)", "abc-\ndeabc-\nde"), + Arguments.of("Multiple, 3", "abc-deab-cde", 3, "\n", true , "(?=-)", "abc\n-de\nab\n-cd\ne"), + Arguments.of("Multiple, 4", "abc-deab-cde", 3, "\n", true , "(?<=-)", "abc\n-\ndea\nb-\ncde") + ); + } }
Examples