/** * Copyright 2010 Google Inc. * * Licensed 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.client.wavepanel.view.dom.full; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.fail; import com.google.gwt.resources.client.CssResource; import org.apache.xerces.jaxp.DocumentBuilderFactoryImpl; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.w3c.dom.Document; import org.waveprotocol.wave.client.common.safehtml.SafeHtml; import org.waveprotocol.wave.client.common.safehtml.SafeHtmlBuilder; import org.waveprotocol.wave.client.uibuilder.BuilderHelper.Component; import org.waveprotocol.wave.client.uibuilder.HtmlClosure; import org.waveprotocol.wave.client.uibuilder.UiBuilder; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.xml.sax.helpers.DefaultHandler; import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; /** * Utility function for testing ui builder implementation. * */ public class UiBuilderTestHelper { // Re-use DocumentBuilderFactory across tests, because it's expensive? private static final DocumentBuilderFactory factory = new DocumentBuilderFactoryImpl(); /** Error handler that treats errors and fatals as failure. */ private static final class ErrorHandler extends DefaultHandler { private final List<SAXParseException> failures = new ArrayList<SAXParseException>(); @Override public void error(SAXParseException e) { failures.add(e); } @Override public void fatalError(SAXParseException e) { failures.add(e); } void throwIfErrors() throws SAXException { if (!failures.isEmpty()) { throw new SAXException(buildExceptionMessage()); } } private String buildExceptionMessage() { StringBuilder sb = new StringBuilder(); for (SAXParseException e : failures) { sb.append(e.toString()).append('\n'); } return sb.toString(); } } private UiBuilderTestHelper() { } /** * Returns a DOM object representing parsed xml. * * @param xml The xml to parse * @return Parsed XML document */ private static Document parse(String xml) throws IOException, ParserConfigurationException, SAXException { ErrorHandler errors = new ErrorHandler(); try { synchronized (factory) { DocumentBuilder builder = factory.newDocumentBuilder(); builder.setErrorHandler(errors); return builder.parse(new InputSource(new StringReader(xml))); } } catch (SAXException se) { // Prefer parse errors over general errors. errors.throwIfErrors(); throw se; } } /** * Renders an HTML closure to HTML. */ public static String render(HtmlClosure html) { SafeHtmlBuilder output = new SafeHtmlBuilder(); html.outputHtml(output); return output.toSafeHtml().asString(); } /** * Verifies that an HTML closure produces well formed HTML, and that it * includes all its components. * * @param builder builder to test * @param id DOM id for the builder * @param components components of the builder */ public static void verifyHtml(UiBuilder builder, String id, Component [] components) throws IOException, ParserConfigurationException, SAXException { String html = render(builder); // make sure the html is wellformed Document dom = parse("<xml>" + html + "</xml>"); assertFalse("It is not valid html to have '/>'", html.contains("/>")); String[] domIds = new String[components.length]; for (int i = 0; i < components.length; i++) { domIds[i] = components[i].getDomId(id); } // make sure all the expected elements are there exactly once assertDomContainsUniqueIds(html, dom, domIds); } /** * @param str * @param pattern * @return the */ public static int countOccurrences(String str, String pattern) { int num = 0; int index = -1; do { index = str.indexOf(pattern, index + 1); if (index >= 0) { num++; } } while (index >= 0); return num; } private static void assertDomContainsUniqueIds(String html, Document document, String[] ids) { Set<String> idsExamine = new HashSet<String>(); for (String id : ids) { if (idsExamine.contains(id)) { fail("Duplicated ids"); } idsExamine.add(id); // for each id, it must be unique in the string and has an element with // the given id. assertEquals( "Cannot find id " + id, 1, UiBuilderTestHelper.countOccurrences(html, "id='" + id + "'")); } } /** * Convert a string to safehtml string, only used for testing. * * @param str */ public static SafeHtml toSafeHtml(String str) { return new SafeHtmlBuilder().appendPlainText(str).toSafeHtml(); } /** @return a mock CSS resouces */ public static <T extends CssResource> T mockCss(Class<T> cssClass) { return Mockito.mock(cssClass, Mockito.withSettings().defaultAnswer( // \u2620 new Answer<String>() { int i; @Override public String answer(InvocationOnMock invocation) throws Throwable { return "asdf" + i++; } })); } }