Package org.waveprotocol.wave.model.richtext

Source Code of org.waveprotocol.wave.model.richtext.RichTextMutationBuilderTest

/**
* 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.waveprotocol.wave.model.richtext;


import junit.framework.TestCase;

import org.waveprotocol.wave.model.document.ReadableWDocument;
import org.waveprotocol.wave.model.document.indexed.IndexedDocument;
import org.waveprotocol.wave.model.document.operation.Nindo;
import org.waveprotocol.wave.model.document.raw.RawDocumentProviderImpl;
import org.waveprotocol.wave.model.document.raw.impl.Element;
import org.waveprotocol.wave.model.document.raw.impl.Node;
import org.waveprotocol.wave.model.document.raw.impl.RawDocumentImpl;
import org.waveprotocol.wave.model.document.raw.impl.Text;
import org.waveprotocol.wave.model.document.util.DocCompare;
import org.waveprotocol.wave.model.document.util.DocProviders;
import org.waveprotocol.wave.model.document.util.ElementStyleView;
import org.waveprotocol.wave.model.document.util.LineContainers;
import org.waveprotocol.wave.model.document.util.Point;
import org.waveprotocol.wave.model.document.util.RawElementStyleView;
import org.waveprotocol.wave.model.operation.OperationException;
import org.waveprotocol.wave.model.richtext.RichTextTokenizerImpl.Token;
import org.waveprotocol.wave.model.util.Pair;

/**
* Tests logic of the rich text mutation builder.
*
*/

public class RichTextMutationBuilderTest extends TestCase {

  @Override
  protected void setUp() throws Exception {
    LineContainers.setTopLevelContainerTagname("body");
  }

  public void testInline() {
    verifyMutations("{++\"foo\"; }", buildTokens(
        token(RichTextTokenizer.Type.TEXT, "foo")));

    verifyMutations("{++\"foo\"; ++\"bar\"; }", buildTokens(
        token(RichTextTokenizer.Type.TEXT, "foo"),
        token(RichTextTokenizer.Type.TEXT, "bar")));
  }

  public void testSingleLine() {
    // This tests a special case in pasting single paragraphs.
    // Because a cursor is always in a <p> tag within the editor,
    // we don't want to close it and start a new one when pasting
    // <p>foo</p>. So we ignore the first and last newline, otherwise
    // pasting naively would result in something like:
    // <p></p>
    // <p>foo</p>
    // <p>|</p>
    verifyMutationsLC("{++\"foo\"; }",
        buildTokens(token(RichTextTokenizer.Type.NEW_LINE),
            token(RichTextTokenizer.Type.TEXT, "foo"),
            token(RichTextTokenizer.Type.NEW_LINE)));
  }

  public void testMultipleLines() {
    verifyMutationsLC("{++\"foo\"; << line {}; >>; ++\"bar\"; }",
        buildTokens(token(RichTextTokenizer.Type.NEW_LINE),
            token(RichTextTokenizer.Type.TEXT, "foo"),
            token(RichTextTokenizer.Type.NEW_LINE),
            token(RichTextTokenizer.Type.TEXT, "bar"),
            token(RichTextTokenizer.Type.NEW_LINE)));

    verifyMutationsLC("{++\"one\"; << line {}; >>; ++\"two\"; << line {}; >>; ++\"three\"; }",
        buildTokens(token(RichTextTokenizer.Type.TEXT, "one"),
            token(RichTextTokenizer.Type.NEW_LINE),
            token(RichTextTokenizer.Type.TEXT, "two"),
            token(RichTextTokenizer.Type.NEW_LINE),
            token(RichTextTokenizer.Type.TEXT, "three")));
  }

  public void testExtraSpaces() {
    verifyMutationsLC("{++\"foo\"; << line {}; >>; ++\"bar\"; }",
        buildTokens(token(RichTextTokenizer.Type.NEW_LINE),
            token(RichTextTokenizer.Type.TEXT, "foo"),
            token(RichTextTokenizer.Type.NEW_LINE),
            token(RichTextTokenizer.Type.TEXT, "bar"),
            token(RichTextTokenizer.Type.NEW_LINE)));
  }

  public void testSplits() {
    verifyMutationsLC("{++\"foo\"; << line {}; >>; }",
        buildTokens(token(RichTextTokenizer.Type.NEW_LINE),
            token(RichTextTokenizer.Type.TEXT, "foo"),
            token(RichTextTokenizer.Type.NEW_LINE),
            token(RichTextTokenizer.Type.NEW_LINE)));

    verifyMutationsLC("{++\"foo\"; << line {}; >>; ++\"bar\"; }",
        buildTokens(token(RichTextTokenizer.Type.TEXT, "foo"),
            token(RichTextTokenizer.Type.NEW_LINE),
            token(RichTextTokenizer.Type.TEXT, "bar"),
            token(RichTextTokenizer.Type.NEW_LINE)));

    verifyMutationsLC("{++\"foo\"; << line {}; >>; ++\"bar\"; << line {}; >>; ++\"baz\"; }",
        buildTokens(token(RichTextTokenizer.Type.TEXT, "foo"),
            token(RichTextTokenizer.Type.NEW_LINE),
            token(RichTextTokenizer.Type.TEXT, "bar"),
            token(RichTextTokenizer.Type.NEW_LINE),
            token(RichTextTokenizer.Type.TEXT, "baz")));
  }

  public void testHeading() {
    verifyMutationsLC("{++\"foo\"; }",
        buildTokens(token(RichTextTokenizer.Type.NEW_LINE, "h1"),
            token(RichTextTokenizer.Type.TEXT, "foo"),
            token(RichTextTokenizer.Type.NEW_LINE)));
  }

  public void testLinks() {
    verifyMutations("{(( link/manual=\"http://www.google.com/\"; ++\"Goog\"; )) link/manual; }",
        buildTokens(token(RichTextTokenizer.Type.LINK_START, "http://www.google.com/"),
            token(RichTextTokenizer.Type.TEXT, "Goog"),
            token(RichTextTokenizer.Type.LINK_END)));

  }

  public void testStyles() {
    verifyStyle(RichTextTokenizer.Type.STYLE_FONT_WEIGHT_START,
        RichTextTokenizer.Type.STYLE_FONT_WEIGHT_END, "style/fontWeight", "bold");
    verifyStyle(RichTextTokenizer.Type.STYLE_FONT_STYLE_START,
        RichTextTokenizer.Type.STYLE_FONT_STYLE_END, "style/fontStyle", "italic");
    verifyStyle(RichTextTokenizer.Type.STYLE_FONT_FAMILY_START,
        RichTextTokenizer.Type.STYLE_FONT_FAMILY_END,
        "style/fontFamily", "Times New Roman");
    verifyStyle(RichTextTokenizer.Type.STYLE_COLOR_START,
        RichTextTokenizer.Type.STYLE_COLOR_END,
        "style/color", "#FF00FF");
  }

  public void testInvalidStyles() {
    verifyMutations("{(( style/fontWeight=\"bold\"; ++\"foo\"; )) style/fontWeight; }",
        buildTokens(token(RichTextTokenizer.Type.STYLE_FONT_WEIGHT_START, "bold"),
            token(RichTextTokenizer.Type.TEXT, "foo")));

    verifyMutations("{(( style/fontWeight=\"bold\"; ++\"foo\"; )) style/fontWeight; }",
        buildTokens(token(RichTextTokenizer.Type.STYLE_FONT_WEIGHT_START, "bold"),
            token(RichTextTokenizer.Type.STYLE_FONT_WEIGHT_START, "bold"),
            token(RichTextTokenizer.Type.TEXT, "foo")));

    verifyMutations("{(( style/fontWeight=\"bold\"; )) style/fontWeight; }",
        buildTokens(token(RichTextTokenizer.Type.STYLE_FONT_WEIGHT_START, "bold"),
            token(RichTextTokenizer.Type.STYLE_FONT_WEIGHT_END)));
  }

  public void testList() {
//    verifyMutationsLC("{<< line {t=li}; >>; ++\"foo\"; }",
//        buildTokens(token(RichTextTokenizer.Type.LIST_ITEM),
//        token(RichTextTokenizer.Type.TEXT, "foo"),
//        token(RichTextTokenizer.Type.NEW_LINE)));

    verifyMutationsLC("{<< line {t=li}; >>; ++\"foo\"; << line {i=1}; >>; ++\"nested\"; "
        + "<< line {t=li}; >>; ++\"bar\"; }",
        buildTokens(
        token(RichTextTokenizer.Type.UNORDERED_LIST_START),
        token(RichTextTokenizer.Type.LIST_ITEM),
        token(RichTextTokenizer.Type.TEXT, "foo"),
        token(RichTextTokenizer.Type.NEW_LINE),
        token(RichTextTokenizer.Type.TEXT, "nested"),
        token(RichTextTokenizer.Type.LIST_ITEM),
        token(RichTextTokenizer.Type.TEXT, "bar"),
        token(RichTextTokenizer.Type.UNORDERED_LIST_END)));

    verifyMutationsLC("{<< line {t=li}; >>; ++\"foo\"; << line {i=1}; >>; ++\"nested\"; "
        + "<< line {t=li}; >>; ++\"bar\"; << line {}; >>; ++\"after\"; }",
        buildTokens(
        token(RichTextTokenizer.Type.UNORDERED_LIST_START),
        token(RichTextTokenizer.Type.LIST_ITEM),
        token(RichTextTokenizer.Type.TEXT, "foo"),
        token(RichTextTokenizer.Type.NEW_LINE),
        token(RichTextTokenizer.Type.TEXT, "nested"),
        token(RichTextTokenizer.Type.LIST_ITEM),
        token(RichTextTokenizer.Type.TEXT, "bar"),
        token(RichTextTokenizer.Type.UNORDERED_LIST_END),
        token(RichTextTokenizer.Type.NEW_LINE),
        token(RichTextTokenizer.Type.TEXT, "after")));
  }

  /**
   * Tests that html strings with styling is converted into correct annotations.
   * NOTE(user): This exercises code in document and tokenizer as well.
   */
  public void testAnnotations() {
    annotationTest("<?a 'style/fontWeight'='bold'?>abc<?a 'style/fontWeight'?>", "<b>abc</b>");
    annotationTest("<?a 'style/fontWeight'='bold'?>abcdef<?a 'style/fontWeight'?>",
        ("<b>ab<b>c</b>def</b>"));

    // Test that default styles are ignored.
    annotationTest("abcdef",
        "abc<span style='textDecoration: none;'>def</span>");
    annotationTest("abcd<?a 'style/textDecoration'='underline'?>e<?a 'style/textDecoration'?>f",
        "abc<span style='textDecoration: none;'>d<u>e</u>f</span>");
    annotationTest(
        "n" +
        "<?a 'style/textDecoration'='underline'?>u" +
        "<?a 'style/textDecoration'='overline'?>o" +
        "<?a 'style/textDecoration'='none'?>n" +
        "<?a 'style/textDecoration'='overline'?>o" +
        "<?a 'style/textDecoration'='underline'?>u" +
        "<?a 'style/textDecoration'?>n",

        "n" +
        "<u>u" +
        "<span style='textDecoration: overline;'>o" +
        "<span style='textDecoration: none;'>n" +
        "</span>o" +
        "</span>u" +
        "</u>n");
  }

  private ElementStyleView<Node, Element, Text> parseDocumentContents(String xmlString) {
    return new RawElementStyleView(
        RawDocumentProviderImpl.create(RawDocumentImpl.BUILDER).parse(
            "<div>" + xmlString + "</div>"));
  }

  private Pair<Nindo, IndexedDocument<Node, Element, Text>> applyTokensToEmptyDoc(
      RichTextTokenizer tokens) {
    IndexedDocument<Node, Element, Text> doc = DocProviders.POJO.parse("<body><line/></body>");
    Point<Node> insertAt = doc.locate(3);

    Nindo.Builder builder = new Nindo.Builder();
    builder.skip(3);
    new RichTextMutationBuilder().applyMutations(tokens, builder, doc, insertAt.getContainer());

    Nindo nindo = builder.build();
    try {
      doc.consumeAndReturnInvertible(nindo);
    } catch (OperationException e) {
      fail("Operation Exception " + e);
    }

    return new Pair<Nindo, IndexedDocument<Node,Element,Text>>(nindo, doc);
  }

  /**
   * Asserts that the expectedContent is produced from srcHtml.
   *
   * Single quotes are used as they do not require escaping and makes the
   * strings more readable.
   *
   * @param expectedContent expected content with annotation key/value in single
   *        quotes.
   * @param srcHtml source html with attributes in single quotes.
   */
  private void annotationTest(String expectedContent, String srcHtml) {
    // The parser implementation does not accept single quotes, so we must
    // convert.
    RichTextTokenizerImpl<Node, Element, Text> tokenizer =
        new RichTextTokenizerImpl<Node, Element, Text>(parseDocumentContents(srcHtml));

    Pair<Nindo, IndexedDocument<Node, Element, Text>> result = applyTokensToEmptyDoc(tokenizer);
    ReadableWDocument<Node, Element, Text> doc = result.second;

    DocCompare.equivalent(DocCompare.ALL, expectedContent, result.second);
  }

  private void verifyMutations(String opString, RichTextTokenizer tokens) {
    verifyMutationsLC(opString, tokens);
  }

  private void verifyStyle(RichTextTokenizer.Type start, RichTextTokenizer.Type end,
      String key, String value) {
    verifyMutations("{(( " + key + "=\"" + value + "\"; ++\"foo\"; )) " + key + "; }",
        buildTokens(token(start, value),
            token(RichTextTokenizer.Type.TEXT, "foo"),
            token(end)));
  }

  private void verifyMutationsLC(String opString, RichTextTokenizer tokens) {
    Pair<Nindo, IndexedDocument<Node, Element, Text>> result = applyTokensToEmptyDoc(tokens);
    Nindo nindo = Nindo.shift(-3, result.first);
    if (!opString.equals(nindo.toString())) {
      System.out.println("ACTUAL: " + nindo);
      System.out.println("EXPECT: " + opString);
      throw new AssertionError(opString + ", " + nindo);
    }
    assertEquals(opString, nindo.toString());
  }

  private RichTextTokenizer buildTokens(Token ... tokens) {
    MockRichTextTokenizer mockTokenizer = new MockRichTextTokenizer();
    for (Token token : tokens) {
      mockTokenizer.expectToken(token);
    }
    return mockTokenizer;
  }

  private static Token token(RichTextTokenizer.Type type, String data) {
    return new Token(type, data);
  }

  private static Token token(RichTextTokenizer.Type type) {
    return new Token(type, null);
  }
}
TOP

Related Classes of org.waveprotocol.wave.model.richtext.RichTextMutationBuilderTest

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.