summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMika Murtojarvi <mika@vaadin.com>2014-12-03 17:53:02 +0200
committerArtur Signell <artur@vaadin.com>2014-12-04 14:47:21 +0000
commitbe35a9b3f1250d9139baf5eeb3f48cb4a20c2822 (patch)
treed42906cf8bfc825fd060a4fc55ef2e61b5dfffc2
parentccbed3fcfa5870c376014036bde8455aaa952384 (diff)
downloadvaadin-framework-be35a9b3f1250d9139baf5eeb3f48cb4a20c2822.tar.gz
vaadin-framework-be35a9b3f1250d9139baf5eeb3f48cb4a20c2822.zip
Fixes for the public API of DesignContext.
Also adds tests for parsing and for finding components by id. Change-Id: I3202a19f1699ee906f97cc57b08a9b2fd540f51b
-rw-r--r--server/src/com/vaadin/ui/AbstractOrderedLayout.java1
-rw-r--r--server/src/com/vaadin/ui/declarative/ComponentInstantiationException.java18
-rw-r--r--server/src/com/vaadin/ui/declarative/DesignContext.java143
-rw-r--r--server/src/com/vaadin/ui/declarative/DesignException.java18
-rw-r--r--server/src/com/vaadin/ui/declarative/LayoutHandler.java78
-rw-r--r--server/src/com/vaadin/ui/declarative/LayoutInflaterException.java18
-rw-r--r--server/tests/src/com/vaadin/tests/layoutparser/ParseLayoutTest.java176
-rw-r--r--server/tests/src/com/vaadin/tests/layoutparser/testFile.html19
8 files changed, 323 insertions, 148 deletions
diff --git a/server/src/com/vaadin/ui/AbstractOrderedLayout.java b/server/src/com/vaadin/ui/AbstractOrderedLayout.java
index 139a4eb545..87b2ff6f48 100644
--- a/server/src/com/vaadin/ui/AbstractOrderedLayout.java
+++ b/server/src/com/vaadin/ui/AbstractOrderedLayout.java
@@ -541,7 +541,6 @@ public abstract class AbstractOrderedLayout extends AbstractLayout implements
DesignSynchronizable childComponent = (DesignSynchronizable) child;
Element childNode = designContext.createNode(childComponent);
designElement.appendChild(childNode);
- childComponent.synchronizeToDesign(childNode, designContext);
// handle alignment
Alignment alignment = getComponentAlignment(child);
if (alignment.isMiddle()) {
diff --git a/server/src/com/vaadin/ui/declarative/ComponentInstantiationException.java b/server/src/com/vaadin/ui/declarative/ComponentInstantiationException.java
deleted file mode 100644
index 5a8ec05d87..0000000000
--- a/server/src/com/vaadin/ui/declarative/ComponentInstantiationException.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.vaadin.ui.declarative;
-
-@SuppressWarnings("serial")
-public class ComponentInstantiationException extends RuntimeException {
-
- public ComponentInstantiationException() {
- super();
- }
-
- public ComponentInstantiationException(String message) {
- super(message);
- }
-
- public ComponentInstantiationException(String message, Throwable e) {
- super(message, e);
- }
-
-}
diff --git a/server/src/com/vaadin/ui/declarative/DesignContext.java b/server/src/com/vaadin/ui/declarative/DesignContext.java
index 961affd5b0..a846edff2c 100644
--- a/server/src/com/vaadin/ui/declarative/DesignContext.java
+++ b/server/src/com/vaadin/ui/declarative/DesignContext.java
@@ -31,7 +31,9 @@ import com.vaadin.ui.DesignSynchronizable;
/**
* This class contains contextual information that is collected when a component
- * tree is constructed based on HTML design template.
+ * tree is constructed based on HTML design template. This information includes
+ * mappings from local ids, global ids and captions to components , as well as a
+ * mapping between prefixes and package names (such as "v" -> "com.vaadin.ui").
*
* @since 7.4
* @author Vaadin Ltd
@@ -48,12 +50,12 @@ public class DesignContext {
// id
public static final String ID_ATTRIBUTE = "id";
public static final String CAPTION_ATTRIBUTE = "caption";
- public static final String LOCAL_ID_PREFIX = "_";
- // Mappings from IDs to components. Modified when synchronizing from design.
- private Map<String, DesignSynchronizable> globalIdToComponent = new HashMap<String, DesignSynchronizable>();
+ public static final String LOCAL_ID_ATTRIBUTE = "_id";
+ // Mappings from ids to components. Modified when synchronizing from design.
+ private Map<String, DesignSynchronizable> idToComponent = new HashMap<String, DesignSynchronizable>();
private Map<String, DesignSynchronizable> localIdToComponent = new HashMap<String, DesignSynchronizable>();
private Map<String, DesignSynchronizable> captionToComponent = new HashMap<String, DesignSynchronizable>();
- // Mapping from components to local IDs. Accessed when synchronizing to
+ // Mapping from components to local ids. Accessed when synchronizing to
// design. Modified when synchronizing from design.
private Map<DesignSynchronizable, String> componentToLocalId = new HashMap<DesignSynchronizable, String>();
private Document doc; // required for calling createElement(String)
@@ -80,36 +82,73 @@ public class DesignContext {
}
/**
+ * Returns a component having the specified local id. If no component is
+ * found, returns null.
+ *
+ * @param localId
+ * The local id of the component
+ * @return a component whose local id equals localId
+ */
+ public DesignSynchronizable getComponentByLocalId(String localId) {
+ return localIdToComponent.get(localId);
+ }
+
+ /**
+ * Returns a component having the specified global id. If no component is
+ * found, returns null.
+ *
+ * @param globalId
+ * The global id of the component
+ * @return a component whose global id equals globalId
+ */
+ public DesignSynchronizable getComponentById(String globalId) {
+ return idToComponent.get(globalId);
+ }
+
+ /**
+ * Returns a component having the specified caption. If no component is
+ * found, returns null.
+ *
+ * @param caption
+ * The caption of the component
+ * @return a component whose caption equals the caption given as a parameter
+ */
+ public DesignSynchronizable getComponentByCaption(String caption) {
+ return captionToComponent.get(caption);
+ }
+
+ /**
* Creates a mapping between the given global id and the component. Returns
- * true if globalId was already mapped to some component or if component was
- * mapped to some string. Otherwise returns false. Also sets the id of the
- * component to globalId.
+ * true if globalId was already mapped to some component. Otherwise returns
+ * false. Also sets the id of the component to globalId.
+ *
+ * If there is a mapping from the component to a global id (gid) different
+ * from globalId, the mapping from gid to component is removed.
*
* If the string was mapped to a component c different from the given
* component, the mapping from c to the string is removed. Similarly, if
* component was mapped to some string s different from globalId, the
* mapping from s to component is removed.
*
- * @since
* @param globalId
* The new global id of the component.
* @param component
* The component whose global id is to be set.
* @return true, if there already was a global id mapping from the string to
- * some component or from the component to some string. Otherwise
- * returns false.
+ * some component.
*/
- public boolean mapGlobalId(String globalId, DesignSynchronizable component) {
- DesignSynchronizable oldComponent = globalIdToComponent.get(globalId);
+ private boolean mapId(String globalId, DesignSynchronizable component) {
+ DesignSynchronizable oldComponent = idToComponent.get(globalId);
if (oldComponent != null && !oldComponent.equals(component)) {
oldComponent.setId(null);
}
String oldGID = component.getId();
if (oldGID != null && !oldGID.equals(globalId)) {
- globalIdToComponent.remove(oldGID);
+ idToComponent.remove(oldGID);
}
component.setId(globalId);
- return oldComponent != null || oldGID != null;
+ idToComponent.put(globalId, component);
+ return oldComponent != null && !oldComponent.equals(component);
}
/**
@@ -122,8 +161,7 @@ public class DesignContext {
* component was mapped to some string s different from localId, the mapping
* from s to component is removed.
*
- * @since
- * @param globalId
+ * @param localId
* The new local id of the component.
* @param component
* The component whose local id is to be set.
@@ -131,7 +169,7 @@ public class DesignContext {
* some component or from the component to some string. Otherwise
* returns false.
*/
- public boolean mapLocalId(String localId, DesignSynchronizable component) {
+ private boolean mapLocalId(String localId, DesignSynchronizable component) {
return twoWayMap(localId, component, localIdToComponent,
componentToLocalId);
}
@@ -146,7 +184,6 @@ public class DesignContext {
* a given caption can be found using the map captionToComponent. Hence, any
* captions that are used to identify an object should be unique.
*
- * @since
* @param caption
* The new caption of the component.
* @param component
@@ -154,7 +191,7 @@ public class DesignContext {
* @return true, if there already was a caption mapping from the string to
* some component.
*/
- public boolean mapCaption(String caption, DesignSynchronizable component) {
+ private boolean mapCaption(String caption, DesignSynchronizable component) {
return captionToComponent.put(caption, component) != null;
}
@@ -168,7 +205,6 @@ public class DesignContext {
* Returns true if there already was a mapping from key to some value v or
* if there was a mapping from value to some key k. Otherwise returns false.
*
- * @since
* @param key
* The new key in keyToValue.
* @param value
@@ -198,7 +234,6 @@ public class DesignContext {
* true if prefix was already mapped to some package name or packageName to
* some prefix.
*
- * @since
* @param prefix
* the prefix name without an ending dash (for instance, "v" is
* always used for "com.vaadin.ui")
@@ -207,7 +242,7 @@ public class DesignContext {
* @return whether there was a mapping from prefix to some package name or
* from packageName to some prefix.
*/
- public boolean mapPrefixToPackage(String prefix, String packageName) {
+ private boolean mapPrefixToPackage(String prefix, String packageName) {
return twoWayMap(prefix, packageName, prefixToPackage, packageToPrefix);
}
@@ -240,7 +275,6 @@ public class DesignContext {
* Get and store the mappings from prefixes to package names from meta tags
* located under <head> in the html document.
*
- * @since
*/
public void getPrefixes(Document doc) {
Element head = doc.head();
@@ -258,7 +292,7 @@ public class DesignContext {
String contentString = attributes.get("content");
String[] parts = contentString.split(":");
if (parts.length != 2) {
- throw new LayoutInflaterException("The meta tag '"
+ throw new DesignException("The meta tag '"
+ child.toString() + "' cannot be parsed.");
}
String prefixName = parts[0];
@@ -297,7 +331,6 @@ public class DesignContext {
* the synchronizeToDesign() call, this method creates the entire subtree
* rooted at the returned Node.
*
- * @since
* @param childComponent
* A component implementing the DesignSynchronizable interface.
* @return An html tree node corresponding to the given component. The tag
@@ -321,8 +354,7 @@ public class DesignContext {
// care of by synchronizeToDesign.
String localId = componentToLocalId.get(childComponent);
if (localId != null) {
- localId = LOCAL_ID_PREFIX + localId;
- newElement.attr(localId, "");
+ newElement.attr(LOCAL_ID_ATTRIBUTE, localId);
}
return newElement;
}
@@ -333,7 +365,6 @@ public class DesignContext {
* inserting a dash before the letter. No dash is inserted before the first
* letter of the class name.
*
- * @since
* @param className
* the name of the class without a package name
* @return the html tag name corresponding to className
@@ -359,7 +390,6 @@ public class DesignContext {
* node. Also calls synchronizeFromDesign() for the created node, in effect
* creating the entire component hierarchy rooted at the returned component.
*
- * @since
* @param componentDesign
* The html tree node containing the description of the component
* to be created.
@@ -370,16 +400,16 @@ public class DesignContext {
// Create the component.
DesignSynchronizable component = instantiateComponent(componentDesign);
component.synchronizeFromDesign(componentDesign, this);
- // Get the IDs and the caption of the component and store them in the
+ // Get the ids and the caption of the component and store them in the
// maps of this design context.
org.jsoup.nodes.Attributes attributes = componentDesign.attributes();
// global id: only update the mapping, the id has already been set for
// the component
- String id = component.getCaption();
+ String id = component.getId();
if (id != null && id.length() > 0) {
- boolean mappingExists = mapGlobalId(id, component);
+ boolean mappingExists = mapId(id, component);
if (mappingExists) {
- throw new LayoutInflaterException(
+ throw new DesignException(
"The following global id is not unique: " + id);
}
}
@@ -387,17 +417,12 @@ public class DesignContext {
// from the attributes of componentDesign
String localId = null;
for (Attribute attribute : attributes.asList()) {
- if (attribute.getKey().startsWith(LOCAL_ID_PREFIX)) {
+ if (attribute.getKey().equals(LOCAL_ID_ATTRIBUTE)) {
if (localId != null) {
- throw new LayoutInflaterException(
- "Duplicate local ids specified: "
- + localId
- + " and "
- + attribute.getKey().substring(
- LOCAL_ID_PREFIX.length()));
+ throw new DesignException("Duplicate local ids specified: "
+ + localId + ".");
}
- localId = attribute.getKey()
- .substring(LOCAL_ID_PREFIX.length());
+ localId = attribute.getValue();
mapLocalId(localId, component); // two-way map
}
}
@@ -413,7 +438,6 @@ public class DesignContext {
* Creates a DesignSynchronizable component corresponding to the given node.
* Does not set the attributes for the created object.
*
- * @since
* @param node
* a node of an html tree
* @return a DesignSynchronizable object corresponding to node, with no
@@ -427,16 +451,15 @@ public class DesignContext {
DesignSynchronizable newComponent = componentClass.newInstance();
return newComponent;
} catch (Exception e) {
- throw createException(e, qualifiedClassName);
+ throw new DesignException("No component class could be found for "
+ + node.nodeName() + ".", e);
}
}
/**
* Returns the qualified class name corresponding to the given html tree
- * node. If the node is not a span or a div, the class name is extracted
- * from the tag name of node.
+ * node. The class name is extracted from the tag name of node.
*
- * @since
* @param node
* an html tree node
* @return The qualified class name corresponding to the given node.
@@ -445,22 +468,19 @@ public class DesignContext {
String tagName = node.nodeName();
if (tagName.equals("v-addon")) {
return node.attr("class");
- } else if (tagName.toLowerCase(Locale.ENGLISH).equals("span")
- || tagName.toLowerCase(Locale.ENGLISH).equals("div")) {
- return "com.vaadin.ui.Label";
}
// Otherwise, get the full class name using the prefix to package
// mapping. Example: "v-vertical-layout" ->
// "com.vaadin.ui.VerticalLayout"
String[] parts = tagName.split("-");
if (parts.length < 2) {
- throw new LayoutInflaterException("The tagname '" + tagName
+ throw new DesignException("The tagname '" + tagName
+ "' is invalid: missing prefix.");
}
String prefixName = parts[0];
String packageName = prefixToPackage.get(prefixName);
if (packageName == null) {
- throw new LayoutInflaterException("Unknown tag: " + tagName);
+ throw new DesignException("Unknown tag: " + tagName);
}
int firstCharacterIndex = prefixName.length() + 1; // +1 is for '-'
tagName = tagName.substring(firstCharacterIndex,
@@ -498,20 +518,6 @@ public class DesignContext {
}
}
- /*
- * Create a new ComponentInstantiationException.
- */
- private ComponentInstantiationException createException(Exception e,
- String qualifiedClassName) {
- String message = String.format(
- "Couldn't instantiate a component for %s.", qualifiedClassName);
- if (e != null) {
- return new ComponentInstantiationException(message, e);
- } else {
- return new ComponentInstantiationException(message);
- }
- }
-
/**
* Returns {@code true} if the given {@link Class} implements the
* {@link Component} interface of Vaadin Framework otherwise {@code false}.
@@ -532,7 +538,6 @@ public class DesignContext {
/**
* Returns the root component of a created component hierarchy.
*
- * @since
* @return
*/
public DesignSynchronizable getComponentRoot() {
@@ -545,4 +550,4 @@ public class DesignContext {
public void setComponentRoot(DesignSynchronizable componentRoot) {
this.componentRoot = componentRoot;
}
-} \ No newline at end of file
+}
diff --git a/server/src/com/vaadin/ui/declarative/DesignException.java b/server/src/com/vaadin/ui/declarative/DesignException.java
new file mode 100644
index 0000000000..74bcd4cdc6
--- /dev/null
+++ b/server/src/com/vaadin/ui/declarative/DesignException.java
@@ -0,0 +1,18 @@
+package com.vaadin.ui.declarative;
+
+@SuppressWarnings("serial")
+public class DesignException extends RuntimeException {
+
+ public DesignException() {
+ super();
+ }
+
+ public DesignException(String message) {
+ super(message);
+ }
+
+ public DesignException(String message, Throwable e) {
+ super(message, e);
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/declarative/LayoutHandler.java b/server/src/com/vaadin/ui/declarative/LayoutHandler.java
index 045fb43b2c..84d454bb01 100644
--- a/server/src/com/vaadin/ui/declarative/LayoutHandler.java
+++ b/server/src/com/vaadin/ui/declarative/LayoutHandler.java
@@ -16,11 +16,8 @@
package com.vaadin.ui.declarative;
import java.io.BufferedWriter;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.OutputStreamWriter;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
@@ -38,18 +35,17 @@ import com.vaadin.ui.DesignSynchronizable;
* component hierarchy must contain a single root.
*
*
- * @since
+ * @since 7.4
* @author Vaadin Ltd
*/
public class LayoutHandler {
/**
* Constructs a component hierarchy from the design specified as an html
- * document. The hierarchy must contain exactly one top-level Component. The
- * component should be located under <body>, but also invalid html
- * containing the hierarchy without <html>, <head> and <body> tags is
+ * document. The component hierarchy must contain exactly one top-level
+ * Component. The component should be located under <body>, but also invalid
+ * html containing the hierarchy without <html>, <head> and <body> tags is
* accepted.
*
- * @since
* @param html
* the html document describing the component design
* @return the DesignContext created while traversing the tree. The
@@ -63,9 +59,37 @@ public class LayoutHandler {
try {
doc = Jsoup.parse(html, "UTF-8", "", Parser.htmlParser());
} catch (IOException e) {
- throw new LayoutInflaterException(
- "The html document cannot be parsed.");
+ throw new DesignException("The html document cannot be parsed.");
}
+ return parse(doc);
+ }
+
+ /**
+ * Constructs a component hierarchy from the design specified as an html
+ * document given as a string. The component hierarchy must contain exactly
+ * one top-level Component. The component should be located under <body>,
+ * but also invalid html containing the hierarchy without <html>, <head> and
+ * <body> tags is accepted.
+ *
+ * @param html
+ * the html document describing the component design
+ * @return the DesignContext created while traversing the tree. The
+ * top-level component of the created component hierarchy can be
+ * accessed using result.getRootComponent(), where result is the
+ * object returned by this method.
+ * @throws IOException
+ */
+ public static DesignContext parse(String html) {
+ Document doc = Jsoup.parse(html);
+ return parse(doc);
+ }
+
+ /**
+ * Constructs a component hierarchy from the design specified as an html
+ * tree.
+ *
+ */
+ private static DesignContext parse(Document doc) {
DesignContext designContext = new DesignContext(doc);
designContext.getPrefixes(doc);
// No special handling for a document without a body element - should be
@@ -75,7 +99,7 @@ public class LayoutHandler {
for (Node element : root.childNodes()) {
if (element instanceof Element) {
if (componentRoot != null) {
- throw new LayoutInflaterException(
+ throw new DesignException(
"The first level of a component hierarchy should contain a single root component, but found "
+ "two: "
+ componentRoot
@@ -96,7 +120,6 @@ public class LayoutHandler {
* the tree. The generated tree corresponds to a valid html document.
*
*
- * @since
* @param root
* the root of the component hierarchy
* @return an html tree representation of the component hierarchy
@@ -126,7 +149,6 @@ public class LayoutHandler {
* Generates an html file corresponding to the component hierarchy with the
* given root.
*
- * @since
* @param writer
* @param root
* @throws IOException
@@ -136,32 +158,4 @@ public class LayoutHandler {
String docAsString = createHtml(ctx).toString();
writer.write(docAsString);
}
-
- /**
- * Used for testing only.
- *
- * This method reads and constructs a component hierarchy from a file whose
- * name is given as the first parameter. The constructed hierarchy is then
- * transformed back to html form, the resulting html being written to a file
- * whose name is given as the second parameter.
- *
- * This is useful for checking that: 1) a design can be successfully parsed
- * 2) a component hierarchy can be transformed to html form and 3) No
- * relevant information is lost in these conversions.
- *
- */
- public static void main(String[] args) throws IOException {
- String inputFileName = args[0];
- String outputFileName = args[1];
- // Read and parse the contents of the output file.
- FileInputStream fis = new FileInputStream(inputFileName);
- DesignContext ctx = parse(fis);
- DesignSynchronizable root = ctx.getComponentRoot();
- // Write
- BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
- new FileOutputStream(outputFileName)));
- createHtml(writer, ctx);
- fis.close();
- writer.close();
- }
-} \ No newline at end of file
+}
diff --git a/server/src/com/vaadin/ui/declarative/LayoutInflaterException.java b/server/src/com/vaadin/ui/declarative/LayoutInflaterException.java
deleted file mode 100644
index 3482856645..0000000000
--- a/server/src/com/vaadin/ui/declarative/LayoutInflaterException.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.vaadin.ui.declarative;
-
-@SuppressWarnings("serial")
-public class LayoutInflaterException extends RuntimeException {
-
- public LayoutInflaterException(String message) {
- super(message);
- }
-
- public LayoutInflaterException(String message, Throwable e) {
- super(message, e);
- }
-
- public LayoutInflaterException(Throwable e) {
- super(e);
- }
-
-}
diff --git a/server/tests/src/com/vaadin/tests/layoutparser/ParseLayoutTest.java b/server/tests/src/com/vaadin/tests/layoutparser/ParseLayoutTest.java
new file mode 100644
index 0000000000..adba5e57b9
--- /dev/null
+++ b/server/tests/src/com/vaadin/tests/layoutparser/ParseLayoutTest.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * 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 com.vaadin.tests.layoutparser;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import junit.framework.TestCase;
+
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.nodes.Node;
+import org.junit.Test;
+
+import com.vaadin.ui.Button;
+import com.vaadin.ui.DesignSynchronizable;
+import com.vaadin.ui.HorizontalLayout;
+import com.vaadin.ui.Label;
+import com.vaadin.ui.NativeButton;
+import com.vaadin.ui.TextArea;
+import com.vaadin.ui.TextField;
+import com.vaadin.ui.VerticalLayout;
+import com.vaadin.ui.declarative.DesignContext;
+import com.vaadin.ui.declarative.LayoutHandler;
+
+/**
+ * A test for checking that parsing a layout preserves the IDs and the mapping
+ * from prefixes to package names (for example <meta name=”package-mapping”
+ * content=”my:com.addon.mypackage” />)
+ *
+ * @since
+ * @author Vaadin Ltd
+ */
+public class ParseLayoutTest extends TestCase {
+ // The context is used for accessing the created component hierarchy.
+ private DesignContext ctx;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ ctx = LayoutHandler
+ .parse(new FileInputStream(
+ "server/tests/src/com/vaadin/tests/layoutparser/testFile.html"));
+ }
+
+ /*
+ * Checks the component hierarchy created by parsing a design. Also checks
+ * that components can be found by id and caption.
+ */
+ @Test
+ public void testGettingByIDAndCaption() throws FileNotFoundException {
+ findElements(ctx);
+ checkHierarchy(ctx);
+ }
+
+ /*
+ * Check that captions, ids and package mappings are preserved when an html
+ * tree is generated from a DesignContext containing the component root of
+ * the component hierarchy. Done by writing the design to a string and then
+ * reading it back, not using the original context information after reading
+ * the written design. The mapping from prefixes to package names is checked
+ * directly from the html tree.
+ */
+ @Test
+ public void testThatSerializationPreservesProperties() throws IOException {
+ Document doc = LayoutHandler.createHtml(ctx);
+ DesignContext newContext = LayoutHandler.parse(doc.toString());
+ // Check that the elements can still be found by id and caption
+ findElements(newContext);
+ checkHierarchy(newContext);
+ // Check the mapping from prefixes to package names using the html tree
+ String[] expectedPrefixes = { "my" };
+ String[] expectedPackageNames = { "com.addon.mypackage" };
+ int index = 0;
+ Element head = doc.head();
+ for (Node child : head.childNodes()) {
+ if ("meta".equals(child.nodeName())) {
+ String name = child.attributes().get("name");
+ if ("package-mapping".equals(name)) {
+ String content = child.attributes().get("content");
+ String[] parts = content.split(":");
+ assertEquals("Unexpected prefix.", expectedPrefixes[index],
+ parts[0]);
+ assertEquals("Unexpected package name.",
+ expectedPackageNames[index], parts[1]);
+ index++;
+ }
+ }
+ }
+ assertEquals("Unexpected number of prefix - package name pairs.", 1,
+ index);
+ }
+
+ /*
+ * Checks that the correct components occur in the correct order in the
+ * component hierarchy rooted at context.getComponentRoot().
+ */
+ private void checkHierarchy(DesignContext context) {
+ DesignSynchronizable root = context.getComponentRoot();
+ VerticalLayout vlayout = (VerticalLayout) root;
+ int numComponents = vlayout.getComponentCount();
+ assertEquals("Wrong number of child components", 3, numComponents);
+
+ // Check the contents of the horizontal layout
+ HorizontalLayout hlayout = (HorizontalLayout) vlayout.getComponent(0);
+ int numHLComponents = hlayout.getComponentCount();
+ assertEquals(5, numHLComponents);
+ Label label = (Label) hlayout.getComponent(0);
+ assertEquals("Wrong caption.", "FooBar", label.getCaption());
+ NativeButton nb = (NativeButton) hlayout.getComponent(1);
+ assertEquals("Wrong caption.", "Native click me", nb.getCaption());
+ nb = (NativeButton) hlayout.getComponent(2);
+ assertEquals("Wrong caption.", "Another button", nb.getCaption());
+ nb = (NativeButton) hlayout.getComponent(3);
+ assertEquals("Wrong caption.", "Yet another button", nb.getCaption());
+ Button b = (Button) hlayout.getComponent(4);
+ assertEquals("Wrong caption.", "Click me", b.getCaption());
+ assertEquals("Wrong width.", 150f, b.getWidth());
+
+ // Check the remaining two components of the vertical layout
+ TextField tf = (TextField) vlayout.getComponent(1);
+ assertEquals("Wrong caption.", "Text input", tf.getCaption());
+ TextArea ta = (TextArea) vlayout.getComponent(2);
+ assertEquals("Wrong caption.", "Text area", ta.getCaption());
+ assertEquals("Wrong width.", 300f, ta.getWidth());
+ assertEquals("Wrong height.", 200f, ta.getHeight());
+ }
+
+ /*
+ * Checks that the correct elements are found using a local id, a global id
+ * or a caption.
+ */
+ private void findElements(DesignContext designContext) {
+ NativeButton firstButton = (NativeButton) designContext
+ .getComponentByLocalId("firstButton");
+ NativeButton firstButton_2 = (NativeButton) designContext
+ .getComponentByCaption("Native click me");
+ NativeButton secondButton = (NativeButton) designContext
+ .getComponentById("secondButton");
+ NativeButton secondButton_2 = (NativeButton) designContext
+ .getComponentByLocalId("localID");
+ NativeButton secondButton_3 = (NativeButton) designContext
+ .getComponentByCaption("Another button");
+ NativeButton thirdButton = (NativeButton) designContext
+ .getComponentByCaption("Yet another button");
+ // Check that the first button was found using both identifiers.
+ assertEquals("The found buttons should be identical but they are not.",
+ firstButton, firstButton_2);
+ assertTrue("The found button element is incorrect.", firstButton
+ .getCaption().equals("Native click me"));
+ // Check that the second button was found using all three identifiers.
+ assertEquals("The found buttons should be identical but they are not.",
+ secondButton, secondButton_2);
+ assertEquals("The found buttons should be identical but they are not.",
+ secondButton_2, secondButton_3);
+ assertTrue("The found button is incorrect.", secondButton.getCaption()
+ .equals("Another button"));
+ // Check that the third button was found by caption.
+ assertTrue("The found button is incorrect.", thirdButton.getCaption()
+ .equals("Yet another button"));
+ }
+}
diff --git a/server/tests/src/com/vaadin/tests/layoutparser/testFile.html b/server/tests/src/com/vaadin/tests/layoutparser/testFile.html
new file mode 100644
index 0000000000..3e4fdc4cd3
--- /dev/null
+++ b/server/tests/src/com/vaadin/tests/layoutparser/testFile.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta name="package-mapping" content="my:com.addon.mypackage"/>
+ </head>
+ <body>
+ <v-vertical-layout width="500px">
+ <v-horizontal-layout>
+ <v-label plain-text caption="FooBar"></v-label>
+ <v-native-button caption="Native click me" _id=firstButton></v-native-button>
+ <v-native-button caption="Another button" id = secondButton _id="localID"></v-native-button>
+ <v-native-button caption="Yet another button"></v-native-button>
+ <v-button plain-text caption="Click me" width = "150px"></v-button>
+ </v-horizontal-layout>
+ <v-text-field caption = "Text input"/>
+ <v-text-area caption = "Text area" height="200px" width="300px"/>
+ </v-vertical-layout>
+ </body>
+</html> \ No newline at end of file