package pair.wordwrap.a20121210; import org.junit.Test; import org.mockito.InOrder; import java.util.regex.Matcher; import java.util.regex.Pattern; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; /** * Word Wrap Kata. * Copyright (c) 2011, Peter Kofler, licensed under BSD License. */ public class WrapperTest { interface Page { void renderLine(String lineOfProperLength); } interface LineAccumulator { void addAndHyphenateIfNeeded(String word); void addWithoutHyphenation(String part); void addCarriageReturn(); } interface ParagraphSplitter { void wrap(String paragraph); } interface HyphenationRule { void doHyphenate(String word, LineAccumulator lineAccumulator); } static class SeparateWordsOnBlanks implements ParagraphSplitter { private static final String BLANK = " "; private final LineAccumulator accumulator; public SeparateWordsOnBlanks(LineAccumulator accumulator) { this.accumulator = accumulator; } @Override public void wrap(String paragraph) { for (String word : paragraph.split(BLANK)) { accumulator.addAndHyphenateIfNeeded(word); } accumulator.addCarriageReturn(); } } @Test public void shouldWrapOnEachBlank() { LineAccumulator mockAccumulator = mock(LineAccumulator.class); ParagraphSplitter wrapper = new SeparateWordsOnBlanks(mockAccumulator); wrapper.wrap("This will"); InOrder inOrder = inOrder(mockAccumulator); inOrder.verify(mockAccumulator).addAndHyphenateIfNeeded("This"); inOrder.verify(mockAccumulator).addAndHyphenateIfNeeded("will"); verify(mockAccumulator, times(2)).addAndHyphenateIfNeeded(anyString()); inOrder.verify(mockAccumulator).addCarriageReturn(); } static class NoneHyphenationRule implements HyphenationRule { @Override public void doHyphenate(String word, LineAccumulator lineAccumulator) { lineAccumulator.addWithoutHyphenation(word); } } static class LineLengthAccumulator implements LineAccumulator { private static final String SPACE = " "; private static final String EMPTY = ""; private final Page page; private final HyphenationRule hyphenation; private final int maximumLineLength; private StringBuilder currentLine = new StringBuilder(); public LineLengthAccumulator(Page page, int maximumLineLength) { this(page, new NoneHyphenationRule(), maximumLineLength); } public LineLengthAccumulator(Page page, HyphenationRule hyphenation, int maximumLineLength) { this.page = page; this.hyphenation = hyphenation; this.maximumLineLength = maximumLineLength; } @Override public void addAndHyphenateIfNeeded(String word) { if (exceedingMaximumLineLength(SPACE, word)) { hyphenation.doHyphenate(word, this); return; } insertSpaceIfNeeded(); appendToCurrentLine(word); } @Override public void addWithoutHyphenation(String part) { if (exceedingMaximumLineLength(EMPTY, part)) { addCarriageReturn(); } appendToCurrentLine(part); } private boolean exceedingMaximumLineLength(String separator, String word) { return currentLine.length() + separator.length() + word.length() > maximumLineLength; } @Override public void addCarriageReturn() { if (currentLineHasWords()) { renderCurrentLine(); lineFeed(); } } private boolean currentLineHasWords() { return currentLine.length() > 0; } private void renderCurrentLine() { page.renderLine(currentLine.toString()); } private void lineFeed() { currentLine = new StringBuilder(); } private void insertSpaceIfNeeded() { if (currentLineHasWords()) { currentLine.append(SPACE); } } private void appendToCurrentLine(String word) { currentLine.append(word); } } @Test public void shouldNotWrapEmptyInput() { Page mockOutput = mock(Page.class); LineAccumulator wrapper = new LineLengthAccumulator(mockOutput, 78); wrapper.addAndHyphenateIfNeeded(""); wrapper.addCarriageReturn(); verify(mockOutput, times(0)).renderLine(anyString()); } @Test public void shouldWrapOnFirstBlank() { Page mockOutput = mock(Page.class); LineAccumulator wrapper = new LineLengthAccumulator(mockOutput, 4); wrapper.addAndHyphenateIfNeeded("This"); wrapper.addAndHyphenateIfNeeded("will"); wrapper.addCarriageReturn(); InOrder inOrder = inOrder(mockOutput); inOrder.verify(mockOutput).renderLine("This"); inOrder.verify(mockOutput).renderLine("will"); verify(mockOutput, times(2)).renderLine(anyString()); } @Test public void shouldNotWrap() { Page mockOutput = mock(Page.class); LineAccumulator accumulator = new LineLengthAccumulator(mockOutput, 78); accumulator.addAndHyphenateIfNeeded("This"); accumulator.addAndHyphenateIfNeeded("is"); accumulator.addCarriageReturn(); verify(mockOutput).renderLine("This is"); verify(mockOutput, times(1)).renderLine(anyString()); } @Test public void shouldWrapOnSecondBlank() { Page mockOutput = mock(Page.class); LineAccumulator accumulator = new LineLengthAccumulator(mockOutput, 9); accumulator.addAndHyphenateIfNeeded("This"); accumulator.addAndHyphenateIfNeeded("will"); accumulator.addAndHyphenateIfNeeded("work"); accumulator.addCarriageReturn(); InOrder inOrder = inOrder(mockOutput); inOrder.verify(mockOutput).renderLine("This will"); inOrder.verify(mockOutput).renderLine("work"); verify(mockOutput, times(2)).renderLine(anyString()); } @Test public void shouldWrapInTheMiddleOfLongWord() { Page mockOutput = mock(Page.class); HyphenationRule strategy = new HyphenationRule() { @Override public void doHyphenate(String word, LineAccumulator lineAccumulator) { lineAccumulator.addWithoutHyphenation("Line"); lineAccumulator.addWithoutHyphenation("Accumulator"); } }; LineAccumulator accumulator = new LineLengthAccumulator(mockOutput, strategy, 10); accumulator.addAndHyphenateIfNeeded("LineAccumulator"); accumulator.addCarriageReturn(); InOrder inOrder = inOrder(mockOutput); inOrder.verify(mockOutput).renderLine("Line"); inOrder.verify(mockOutput).renderLine("Accumulator"); verify(mockOutput, times(2)).renderLine(anyString()); } @Test public void shouldNotWrapExactLengthOfWord() { Page mockOutput = mock(Page.class); HyphenationRule strategy = new HyphenationRule() { @Override public void doHyphenate(String word, LineAccumulator lineAccumulator) { lineAccumulator.addWithoutHyphenation("T"); lineAccumulator.addWithoutHyphenation("his"); } }; LineAccumulator accumulator = new LineLengthAccumulator(mockOutput, strategy, 4); accumulator.addAndHyphenateIfNeeded("This"); accumulator.addCarriageReturn(); verify(mockOutput).renderLine("This"); verify(mockOutput, times(1)).renderLine(anyString()); } @Test public void shouldWrapHyphenatedSecondPartAtEndOfLine() { Page mockOutput = mock(Page.class); // use mock as we test this function in shouldHyphenateLongWordsTwice HyphenationRule strategy = new HyphenationRule() { @Override public void doHyphenate(String word, LineAccumulator lineAccumulator) { lineAccumulator.addWithoutHyphenation("it"); lineAccumulator.addWithoutHyphenation("Is"); lineAccumulator.addWithoutHyphenation("Blue"); lineAccumulator.addWithoutHyphenation("It"); } }; LineAccumulator accumulator = new LineLengthAccumulator(mockOutput, strategy, 7); accumulator.addAndHyphenateIfNeeded("itIsBlueIt"); accumulator.addCarriageReturn(); InOrder inOrder = inOrder(mockOutput); inOrder.verify(mockOutput).renderLine("itIs"); inOrder.verify(mockOutput).renderLine("BlueIt"); verify(mockOutput, times(2)).renderLine(anyString()); } static class SplitOnCamelCase implements HyphenationRule { // negative lookbehind private static final Pattern ANY_UPPER_CASE_LETTER = Pattern.compile("(?