From: Patrik Lindström Date: Tue, 21 Jan 2014 22:25:33 +0000 (+0200) Subject: Implement TestBench4 features in debug window (#12694) X-Git-Tag: 7.2.0.beta1~169^2~3 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=58443b4a45e6fbdfed860959a5114f13ba7f4662;p=vaadin-framework.git Implement TestBench4 features in debug window (#12694) Done: - VaadinFinderLocatorStrategy returns optimal paths for any Widget selectable by it. - TestBenchSection of the Debug Window picks and clears as expected. - Debug Window no longer presents user with a widget hierarchy - Translation from VaadinFinderLocatorStrategy query strings to ElementQuery API calls (should be done in SelectorPath.java) - Make SelectorPaths change background color when hovered Change-Id: Ie122f962a319ddf560fa9ac4f6bc57f32a120f91 --- diff --git a/WebContent/VAADIN/themes/base/debug/debug.scss b/WebContent/VAADIN/themes/base/debug/debug.scss index 0992f19bb9..b50245a7be 100644 --- a/WebContent/VAADIN/themes/base/debug/debug.scss +++ b/WebContent/VAADIN/themes/base/debug/debug.scss @@ -251,6 +251,14 @@ width: 100%; } + .v-debugwindow-selector > span.value { + width: 100%; + } + + .v-debugwindow-selector :hover { + background: rgba(255,32,32,0.5); + } + /* LOG */ .v-debugwindow-log { font-family: monospace; diff --git a/client/src/com/vaadin/client/ConnectorMap.java b/client/src/com/vaadin/client/ConnectorMap.java index 810f12824a..c2f1eda21d 100644 --- a/client/src/com/vaadin/client/ConnectorMap.java +++ b/client/src/com/vaadin/client/ConnectorMap.java @@ -116,7 +116,7 @@ public class ConnectorMap { * no connector was found */ public ComponentConnector getConnector(Widget widget) { - return getConnector(widget.getElement()); + return widget == null ? null : getConnector(widget.getElement()); } public void registerConnector(String id, ServerConnector connector) { diff --git a/client/src/com/vaadin/client/componentlocator/ComponentLocator.java b/client/src/com/vaadin/client/componentlocator/ComponentLocator.java index aa841ce5b0..d2a89c00d5 100644 --- a/client/src/com/vaadin/client/componentlocator/ComponentLocator.java +++ b/client/src/com/vaadin/client/componentlocator/ComponentLocator.java @@ -201,4 +201,20 @@ public class ComponentLocator { return client; } + /** + * Check if a given selector is valid for LegacyLocatorStrategy. + * + * @param path + * Vaadin selector path + * @return true if passes path validation with LegacyLocatorStrategy + */ + public boolean isValidForLegacyLocator(String path) { + for (LocatorStrategy ls : locatorStrategies) { + if (ls instanceof LegacyLocatorStrategy) { + return ls.validatePath(path); + } + } + return false; + } + } diff --git a/client/src/com/vaadin/client/componentlocator/LocatorUtil.java b/client/src/com/vaadin/client/componentlocator/LocatorUtil.java new file mode 100644 index 0000000000..04624920a9 --- /dev/null +++ b/client/src/com/vaadin/client/componentlocator/LocatorUtil.java @@ -0,0 +1,76 @@ +/* + * Copyright 2000-2013 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.client.componentlocator; + +/** + * Common String manipulator utilities used in VaadinFinderLocatorStrategy and + * SelectorPredicates. + * + * @since 7.2 + * @author Vaadin Ltd + */ +public class LocatorUtil { + + /** + * Find first occurrence of character that's not inside quotes starting from + * specified index. + * + * @param str + * Full string for searching + * @param find + * Character we want to find + * @param startingAt + * Index where we start + * @return Index of character. -1 if character not found + */ + protected static int indexOfIgnoringQuoted(String str, char find, + int startingAt) { + boolean quote = false; + String quoteChars = "'\""; + char currentQuote = '"'; + for (int i = startingAt; i < str.length(); ++i) { + char cur = str.charAt(i); + if (quote) { + if (cur == currentQuote) { + quote = !quote; + } + continue; + } else if (cur == find) { + return i; + } else { + if (quoteChars.indexOf(cur) >= 0) { + currentQuote = cur; + quote = !quote; + } + } + } + return -1; + } + + /** + * Find first occurrence of character that's not inside quotes starting from + * the beginning of string. + * + * @param str + * Full string for searching + * @param find + * Character we want to find + * @return Index of character. -1 if character not found + */ + protected static int indexOfIgnoringQuoted(String str, char find) { + return indexOfIgnoringQuoted(str, find, 0); + } +} diff --git a/client/src/com/vaadin/client/componentlocator/SelectorPredicate.java b/client/src/com/vaadin/client/componentlocator/SelectorPredicate.java new file mode 100644 index 0000000000..32b33005ed --- /dev/null +++ b/client/src/com/vaadin/client/componentlocator/SelectorPredicate.java @@ -0,0 +1,228 @@ +/* + * Copyright 2000-2013 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.client.componentlocator; + +import java.util.ArrayList; +import java.util.List; + +/** + * SelectorPredicates are statements about the state of different components + * that VaadinFinderLocatorStrategy is finding. SelectorPredicates also provide + * useful information of said components to debug window by giving means to + * provide better variable naming. + * + * @since 7.2 + * @author Vaadin Ltd + */ +public class SelectorPredicate { + private String name = ""; + private String value = ""; + private boolean wildcard = false; + private int index = -1; + + public static List extractPostFilterPredicates( + String path) { + if (path.startsWith("(")) { + return extractPredicates(path.substring(path.lastIndexOf(')'))); + } + return new ArrayList(); + } + + /** + * Generate a list of predicates from a single predicate string + * + * @param str + * a comma separated string of predicates + * @return a List of Predicate objects + */ + public static List extractPredicates(String path) { + List predicates = new ArrayList(); + + String predicateStr = extractPredicateString(path); + if (null == predicateStr || predicateStr.length() == 0) { + return predicates; + } + + // Extract input strings + List input = readPredicatesFromString(predicateStr); + + // Process each predicate into proper predicate descriptor + for (String s : input) { + SelectorPredicate p = new SelectorPredicate(); + s = s.trim(); + + try { + // If we can parse out the predicate as a pure index argument, + // stop processing here. + p.index = Integer.parseInt(s); + predicates.add(p); + + continue; + } catch (Exception e) { + p.index = -1; + } + + int idx = LocatorUtil.indexOfIgnoringQuoted(s, '='); + if (idx < 0) { + continue; + } + p.name = s.substring(0, idx); + p.value = s.substring(idx + 1); + + if (p.value.equals("?")) { + p.wildcard = true; + p.value = null; + } else { + // Only unquote predicate value once we're sure it's a proper + // value... + + p.value = unquote(p.value); + } + + predicates.add(p); + } + // Move any (and all) index predicates to last place in the list. + for (int i = 0, l = predicates.size(); i < l - 1; ++i) { + if (predicates.get(i).index > -1) { + predicates.add(predicates.remove(i)); + --i; + --l; + } + } + + return predicates; + } + + /** + * Splits the predicate string to list of predicate strings. + * + * @param predicateStr + * Comma separated predicate strings + * @return List of predicate strings + */ + private static List readPredicatesFromString(String predicateStr) { + List predicates = new ArrayList(); + int prevIdx = 0; + int idx = LocatorUtil.indexOfIgnoringQuoted(predicateStr, ',', prevIdx); + + while (idx > -1) { + predicates.add(predicateStr.substring(prevIdx, idx)); + prevIdx = idx + 1; + idx = LocatorUtil.indexOfIgnoringQuoted(predicateStr, ',', prevIdx); + } + predicates.add(predicateStr.substring(prevIdx)); + + return predicates; + } + + /** + * Returns the predicate string, i.e. the string between the brackets in a + * path fragment. Examples: + * VTextField[0] => 0 + * VTextField[caption='foo'] => caption='foo' + * + * + * @param pathFragment + * The path fragment from which to extract the predicate string. + * @return The predicate string for the path fragment or empty string if not + * found. + */ + private static String extractPredicateString(String pathFragment) { + int ixOpenBracket = LocatorUtil + .indexOfIgnoringQuoted(pathFragment, '['); + if (ixOpenBracket >= 0) { + int ixCloseBracket = LocatorUtil.indexOfIgnoringQuoted( + pathFragment, ']', ixOpenBracket); + return pathFragment.substring(ixOpenBracket + 1, ixCloseBracket); + } + return ""; + } + + /** + * Removes the surrounding quotes from a string if it is quoted. + * + * @param str + * the possibly quoted string + * @return an unquoted version of str + */ + private static String unquote(String str) { + if ((str.startsWith("\"") && str.endsWith("\"")) + || (str.startsWith("'") && str.endsWith("'"))) { + return str.substring(1, str.length() - 1); + } + return str; + } + + /** + * @return the name + */ + public String getName() { + return name; + } + + /** + * @param name + * the name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return the value + */ + public String getValue() { + return value; + } + + /** + * @param value + * the value to set + */ + public void setValue(String value) { + this.value = value; + } + + /** + * @return the index + */ + public int getIndex() { + return index; + } + + /** + * @param index + * the index to set + */ + public void setIndex(int index) { + this.index = index; + } + + /** + * @return the wildcard + */ + public boolean isWildcard() { + return wildcard; + } + + /** + * @param wildcard + * the wildcard to set + */ + public void setWildcard(boolean wildcard) { + this.wildcard = wildcard; + } +} diff --git a/client/src/com/vaadin/client/componentlocator/VaadinFinderLocatorStrategy.java b/client/src/com/vaadin/client/componentlocator/VaadinFinderLocatorStrategy.java index 2bb08a52c9..49090b66db 100644 --- a/client/src/com/vaadin/client/componentlocator/VaadinFinderLocatorStrategy.java +++ b/client/src/com/vaadin/client/componentlocator/VaadinFinderLocatorStrategy.java @@ -19,22 +19,18 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import com.google.gwt.core.client.JsArrayString; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.ComponentConnector; -import com.vaadin.client.FastStringSet; import com.vaadin.client.HasComponentsConnector; import com.vaadin.client.Util; -import com.vaadin.client.metadata.NoDataException; import com.vaadin.client.metadata.Property; import com.vaadin.client.metadata.TypeDataStore; import com.vaadin.client.ui.AbstractConnector; import com.vaadin.client.ui.SubPartAware; import com.vaadin.client.ui.VNotification; -import com.vaadin.shared.AbstractComponentState; /** * The VaadinFinder locator strategy implements an XPath-like syntax for @@ -60,15 +56,11 @@ public class VaadinFinderLocatorStrategy implements LocatorStrategy { private final ApplicationConnection client; /** - * Internal container/descriptor for search predicates - * - * @author Vaadin Ltd + * Internal descriptor for connector/element/widget name combinations */ - private static final class Predicate { - private String name = ""; - private String value = ""; - private boolean wildcard = false; - private int index = -1; + private static final class ConnectorPath { + private String name; + private ComponentConnector connector; } public VaadinFinderLocatorStrategy(ApplicationConnection clientConnection) { @@ -80,11 +72,176 @@ public class VaadinFinderLocatorStrategy implements LocatorStrategy { */ @Override public String getPathForElement(Element targetElement) { - // Path generation functionality is not yet implemented as there is no - // current need for it. This might be implemented in the future if the - // need arises. Until then, all locator generation is handled by - // LegacyLocatorStrategy. - return null; + if (targetElement == null) { + return ""; + } + + List hierarchy = getConnectorHierarchyForElement(targetElement); + List path = new ArrayList(); + + // Assemble longname path components back-to-forth with useful + // predicates - first try ID, then caption. + for (int i = 0; i < hierarchy.size(); ++i) { + ConnectorPath cp = hierarchy.get(i); + String pathFragment = cp.name; + String identifier = getPropertyValue(cp.connector, "id"); + + if (identifier != null) { + pathFragment += "[id=\"" + identifier + "\"]"; + } else { + identifier = getPropertyValue(cp.connector, "caption"); + if (identifier != null) { + pathFragment += "[caption=\"" + identifier + "\"]"; + } + } + path.add(pathFragment); + } + + if (path.size() == 0) { + // If we didn't find a single element, return null.. + return null; + } + + return getBestSelector(generateQueries(path), targetElement); + } + + /** + * Search different queries for the best one. Use the fact that the lowest + * possible index is with the last selector. Last selector is the full + * search path containing the complete Component hierarchy. + * + * @param selectors + * List of selectors + * @param target + * Target element + * @return Best selector string formatted with a post filter + */ + private String getBestSelector(List selectors, Element target) { + // The last selector gives us smallest list index for target element. + String bestSelector = selectors.get(selectors.size() - 1); + int min = getElementsByPath(bestSelector).indexOf(target); + if (selectors.size() > 1 + && min == getElementsByPath(selectors.get(0)).indexOf(target)) { + // The first selector has same index as last. It's much shorter. + bestSelector = selectors.get(0); + } else if (selectors.size() > 2) { + // See if we get minimum from second last. If not then we already + // have the best one.. Second last one contains almost full + // component hierarchy. + if (getElementsByPath(selectors.get(selectors.size() - 2)).indexOf( + target) == min) { + for (int i = 1; i < selectors.size() - 2; ++i) { + // Loop through the remaining selectors and look for one + // with the same index + if (getElementsByPath(selectors.get(i)).indexOf(target) == min) { + bestSelector = selectors.get(i); + break; + } + } + + } + } + return "(" + bestSelector + ")[" + min + "]"; + + } + + /** + * Function to generate all possible search paths for given component list. + * Function strips out all the com.vaadin.ui. prefixes from elements as this + * functionality makes generating a query later on easier. + * + * @param components + * List of components + * @return List of Vaadin selectors + */ + private List generateQueries(List components) { + // Prepare to loop through all the elements. + List paths = new ArrayList(); + int compIdx = 0; + String basePath = components.get(compIdx).replace("com.vaadin.ui.", ""); + // Add a basic search for the first element (eg. //Button) + paths.add((components.size() == 1 ? "/" : "//") + basePath); + while (++compIdx < components.size()) { + // Loop through the remaining components + for (int i = components.size() - 1; i >= compIdx; --i) { + boolean recursive = false; + if (i > compIdx) { + recursive = true; + } + paths.add((i == components.size() - 1 ? "/" : "//") + + components.get(i).replace("com.vaadin.ui.", "") + + (recursive ? "//" : "/") + basePath); + } + // Add the element at index compIdx to the basePath so it is + // included in all the following searches. + basePath = components.get(compIdx).replace("com.vaadin.ui.", "") + + "/" + basePath; + } + + return paths; + } + + /** + * Helper method to get the string-form value of a named property of a + * component connector + * + * @since 7.2 + * @param c + * any ComponentConnector instance + * @param propertyName + * property name to test for + * @return a string, if the property is found, or null, if the property does + * not exist on the object (or some other error is encountered). + */ + private String getPropertyValue(ComponentConnector c, String propertyName) { + Property prop = AbstractConnector.getStateType(c).getProperty( + propertyName); + try { + return prop.getValue(c.getState()).toString(); + } catch (Exception e) { + return null; + } + } + + /** + * Generate a list representing the top-to-bottom connector hierarchy for + * any given element. ConnectorPath element provides long- and short names, + * as well as connector and widget root element references. + * + * @since 7.2 + * @param elem + * any Element that is part of a widget hierarchy + * @return a list of ConnectorPath objects, in descending order towards the + * common root container. + */ + private List getConnectorHierarchyForElement(Element elem) { + Element e = elem; + ComponentConnector c = Util.findPaintable(client, e); + List connectorHierarchy = new ArrayList(); + + while (c != null) { + + for (String id : getIDsForConnector(c)) { + ConnectorPath cp = new ConnectorPath(); + cp.name = getFullClassName(id); + cp.connector = c; + + // We want to make an exception for the UI object, since it's + // our default search context (and can't be found inside itself) + if (!cp.name.equals("com.vaadin.ui.UI")) { + connectorHierarchy.add(cp); + } + } + + e = (Element) e.getParentElement(); + if (e != null) { + c = Util.findPaintable(client, e); + e = c != null ? c.getWidget().getElement() : null; + } + + } + + return connectorHierarchy; } private boolean isNotificationExpression(String path) { @@ -118,21 +275,41 @@ public class VaadinFinderLocatorStrategy implements LocatorStrategy { */ @Override public List getElementsByPath(String path) { + List postFilters = SelectorPredicate + .extractPostFilterPredicates(path); + if (postFilters.size() > 0) { + path = path.substring(1, path.lastIndexOf(')')); + } + List elements = new ArrayList(); if (isNotificationExpression(path)) { - List elements = new ArrayList(); for (VNotification n : findNotificationsByPath(path)) { elements.add(n.getElement()); } - return elements; + } else { + + elements.addAll(eliminateDuplicates(getElementsByPathStartingAtConnector( + path, client.getUIConnector()))); } - List elems = eliminateDuplicates(getElementsByPathStartingAtConnector( - path, client.getUIConnector())); + for (SelectorPredicate p : postFilters) { + // Post filtering supports only indexes and follows instruction + // blindly. Index that is outside of our list results into an empty + // list and multiple indexes are likely to ruin a search completely + if (p.getIndex() >= 0) { + if (p.getIndex() >= elements.size()) { + elements.clear(); + } else { + Element e = elements.get(p.getIndex()); + elements.clear(); + elements.add(e); + } + } + } - return elems; + return elements; } /** @@ -140,90 +317,56 @@ public class VaadinFinderLocatorStrategy implements LocatorStrategy { */ @Override public Element getElementByPath(String path) { - if (isNotificationExpression(path)) { - return findNotificationsByPath(path).get(0).getElement(); + List elements = getElementsByPath(path); + if (elements.isEmpty()) { + return null; } - return getElementByPathStartingAtConnector(path, - client.getUIConnector()); + return elements.get(0); } /** - * Generate a list of predicates from a single predicate string - * - * @param str - * a comma separated string of predicates - * @return a List of Predicate objects + * {@inheritDoc} */ - private List extractPredicates(String path) { - List predicates = new ArrayList(); - - String str = extractPredicateString(path); - if (null == str || str.length() == 0) { - return predicates; - } - - // Extract input strings - List input = new ArrayList(); - { - int idx = indexOfIgnoringQuotes(str, ',', 0), p = 0; - if (idx == -1) { - input.add(str); - } else { - do { - input.add(str.substring(p, idx)); - p = idx + 1; - idx = indexOfIgnoringQuotes(str, ',', p); - } while (idx > -1); - input.add(str.substring(p)); - } + @Override + public Element getElementByPathStartingAt(String path, Element root) { + List elements = getElementsByPathStartingAt(path, root); + if (elements.isEmpty()) { + return null; } + return elements.get(0); - // Process each predicate into proper predicate descriptor - for (String s : input) { - Predicate p = new Predicate(); - s = s.trim(); - - try { - // If we can parse out the predicate as a pure index argument, - // stop processing here. - p.index = Integer.parseInt(s); - predicates.add(p); - - continue; - } catch (Exception e) { - p.index = -1; - } - - int idx = indexOfIgnoringQuotes(s, '='); - if (idx < 0) { - continue; - } - p.name = s.substring(0, idx); - p.value = s.substring(idx + 1); - - if (p.value.equals("?")) { - p.wildcard = true; - p.value = null; - } else { - // Only unquote predicate value once we're sure it's a proper - // value... - - p.value = unquote(p.value); - } + } - predicates.add(p); + /** + * {@inheritDoc} + */ + @Override + public List getElementsByPathStartingAt(String path, Element root) { + List postFilters = SelectorPredicate + .extractPostFilterPredicates(path); + if (postFilters.size() > 0) { + path = path.substring(1, path.lastIndexOf(')')); } - // Move any (and all) index predicates to last place in the list. - for (int i = 0, l = predicates.size(); i < l - 1; ++i) { - if (predicates.get(i).index > -1) { - predicates.add(predicates.remove(i)); - --i; - --l; + List elements = getElementsByPathStartingAtConnector(path, + Util.findPaintable(client, root)); + + for (SelectorPredicate p : postFilters) { + // Post filtering supports only indexes and follows instruction + // blindly. Index that is outside of our list results into an empty + // list and multiple indexes are likely to ruin a search completely + if (p.getIndex() >= 0) { + if (p.getIndex() >= elements.size()) { + elements.clear(); + } else { + Element e = elements.get(p.getIndex()); + elements.clear(); + elements.add(e); + } } } - return predicates; + return elements; } /** @@ -245,11 +388,12 @@ public class VaadinFinderLocatorStrategy implements LocatorStrategy { } } - List predicates = extractPredicates(path); - for (Predicate p : predicates) { + List predicates = SelectorPredicate + .extractPredicates(path); + for (SelectorPredicate p : predicates) { - if (p.index > -1) { - VNotification n = notifications.get(p.index); + if (p.getIndex() > -1) { + VNotification n = notifications.get(p.getIndex()); notifications.clear(); if (n != null) { notifications.add(n); @@ -261,59 +405,6 @@ public class VaadinFinderLocatorStrategy implements LocatorStrategy { return eliminateDuplicates(notifications); } - /** - * {@inheritDoc} - */ - @Override - public Element getElementByPathStartingAt(String path, Element root) { - return getElementByPathStartingAtConnector(path, - Util.findPaintable(client, root)); - } - - /** - * {@inheritDoc} - */ - @Override - public List getElementsByPathStartingAt(String path, Element root) { - List elements = getElementsByPathStartingAtConnector(path, - Util.findPaintable(client, root)); - return elements; - } - - /** - * Finds an element by the specified path, starting traversal of the - * connector hierarchy from the specified root. - * - * @param path - * the locator path - * @param root - * the root connector - * @return the element identified by path or null if not found. - */ - private Element getElementByPathStartingAtConnector(String path, - ComponentConnector root) { - String[] pathComponents = path.split(SUBPART_SEPARATOR); - ComponentConnector connector; - if (pathComponents[0].length() > 0) { - connector = findConnectorByPath(pathComponents[0], root); - } else { - connector = root; - } - if (connector != null) { - if (pathComponents.length > 1) { - // We have subparts - if (connector.getWidget() instanceof SubPartAware) { - return ((SubPartAware) connector.getWidget()) - .getSubPartElement(pathComponents[1]); - } else { - return null; - } - } - return connector.getWidget().getElement(); - } - return null; - } - /** * Finds a list of elements by the specified path, starting traversal of the * connector hierarchy from the specified root. @@ -355,41 +446,6 @@ public class VaadinFinderLocatorStrategy implements LocatorStrategy { return eliminateDuplicates(output); } - /** - * Recursively finds a connector for the element identified by the provided - * path by traversing the connector hierarchy starting from the - * {@code parent} connector. - * - * @param path - * The path identifying an element. - * @param parent - * The connector to start traversing from. - * @return The connector identified by {@code path} or null if no such - * connector could be found. - */ - private ComponentConnector findConnectorByPath(String path, - ComponentConnector parent) { - boolean findRecursively = path.startsWith("//"); - // Strip away the one or two slashes from the beginning of the path - path = path.substring(findRecursively ? 2 : 1); - - String[] fragments = splitFirstFragmentFromTheRest(path); - List potentialMatches = collectPotentialMatches( - parent, fragments[0], findRecursively); - - List connectors = filterMatches(potentialMatches, - extractPredicates(fragments[0])); - - if (!connectors.isEmpty()) { - if (fragments.length > 1) { - return findConnectorByPath(fragments[1], connectors.get(0)); - } else { - return connectors.get(0); - } - } - return null; - } - /** * Recursively finds connectors for the elements identified by the provided * path by traversing the connector hierarchy starting from {@code parents} @@ -414,7 +470,8 @@ public class VaadinFinderLocatorStrategy implements LocatorStrategy { for (ComponentConnector parent : parents) { connectors.addAll(filterMatches( collectPotentialMatches(parent, fragments[0], - findRecursively), extractPredicates(fragments[0]))); + findRecursively), SelectorPredicate + .extractPredicates(fragments[0]))); } if (!connectors.isEmpty() && fragments.length > 1) { @@ -423,28 +480,6 @@ public class VaadinFinderLocatorStrategy implements LocatorStrategy { return eliminateDuplicates(connectors); } - /** - * Returns the predicate string, i.e. the string between the brackets in a - * path fragment. Examples: - * VTextField[0] => 0 - * VTextField[caption='foo'] => caption='foo' - * - * - * @param pathFragment - * The path fragment from which to extract the predicate string. - * @return The predicate string for the path fragment or empty string if not - * found. - */ - private String extractPredicateString(String pathFragment) { - int ixOpenBracket = indexOfIgnoringQuotes(pathFragment, '['); - if (ixOpenBracket >= 0) { - int ixCloseBracket = indexOfIgnoringQuotes(pathFragment, ']', - ixOpenBracket); - return pathFragment.substring(ixOpenBracket + 1, ixCloseBracket); - } - return ""; - } - /** * Go through a list of potentially matching components, modifying that list * until all elements that remain in that list match the complete list of @@ -458,13 +493,13 @@ public class VaadinFinderLocatorStrategy implements LocatorStrategy { */ private List filterMatches( List potentialMatches, - List predicates) { + List predicates) { - for (Predicate p : predicates) { + for (SelectorPredicate p : predicates) { - if (p.index > -1) { + if (p.getIndex() > -1) { try { - ComponentConnector v = potentialMatches.get(p.index); + ComponentConnector v = potentialMatches.get(p.getIndex()); potentialMatches.clear(); potentialMatches.add(v); } catch (IndexOutOfBoundsException e) { @@ -476,20 +511,11 @@ public class VaadinFinderLocatorStrategy implements LocatorStrategy { for (int i = 0, l = potentialMatches.size(); i < l; ++i) { - ComponentConnector c = potentialMatches.get(i); - Property property = AbstractConnector.getStateType(c) - .getProperty(p.name); - - Object propData; - try { - propData = property.getValue(c.getState()); - } catch (NoDataException e) { - propData = null; - } + String propData = getPropertyValue(potentialMatches.get(i), + p.getName()); - if ((p.wildcard && propData == null) - || (!p.wildcard && !valueEqualsPropertyValue(p.value, - property, c.getState()))) { + if ((p.isWildcard() && propData == null) + || (!p.isWildcard() && !p.getValue().equals(propData))) { potentialMatches.remove(i); --l; --i; @@ -501,44 +527,6 @@ public class VaadinFinderLocatorStrategy implements LocatorStrategy { return eliminateDuplicates(potentialMatches); } - /** - * Returns true if the value matches the value of the property in the state - * object. - * - * @param value - * The value to compare against. - * @param property - * The property, whose value to check. - * @param state - * The connector, whose state object contains the property. - * @return true if the values match. - */ - private boolean valueEqualsPropertyValue(String value, Property property, - AbstractComponentState state) { - try { - return value.equals(property.getValue(state)); - } catch (Exception e) { - // The property doesn't exist in the state object, so they aren't - // equal. - return false; - } - } - - /** - * Removes the surrounding quotes from a string if it is quoted. - * - * @param str - * the possibly quoted string - * @return an unquoted version of str - */ - private String unquote(String str) { - if ((str.startsWith("\"") && str.endsWith("\"")) - || (str.startsWith("'") && str.endsWith("'"))) { - return str.substring(1, str.length() - 1); - } - return str; - } - /** * Collects all connectors that match the widget class name of the path * fragment. If the {@code collectRecursively} parameter is true, a @@ -579,6 +567,15 @@ public class VaadinFinderLocatorStrategy implements LocatorStrategy { return eliminateDuplicates(potentialMatches); } + private List getIDsForConnector(ComponentConnector connector) { + Class connectorClass = connector.getClass(); + List ids = new ArrayList(); + + TypeDataStore.get().findIdentifiersFor(connectorClass).addAllTo(ids); + + return ids; + } + /** * Determines whether a connector matches a path fragment. This is done by * comparing the path fragment to the name of the widget type of the @@ -593,16 +590,8 @@ public class VaadinFinderLocatorStrategy implements LocatorStrategy { */ private boolean connectorMatchesPathFragment(ComponentConnector connector, String widgetName) { - Class connectorClass = connector.getClass(); - List ids = new ArrayList(); - FastStringSet identifiers = TypeDataStore.get().findIdentifiersFor( - connectorClass); - JsArrayString str = identifiers.dump(); - - for (int j = 0; j < str.length(); ++j) { - ids.add(str.get(j)); - } + List ids = getIDsForConnector(connector); Integer[] widgetTags = client.getConfiguration() .getTagsForServerSideClassName(getFullClassName(widgetName)); @@ -677,7 +666,7 @@ public class VaadinFinderLocatorStrategy implements LocatorStrategy { * the path. */ private String[] splitFirstFragmentFromTheRest(String path) { - int ixOfSlash = indexOfIgnoringQuotes(path, '/'); + int ixOfSlash = LocatorUtil.indexOfIgnoringQuoted(path, '/'); if (ixOfSlash > 0) { return new String[] { path.substring(0, ixOfSlash), path.substring(ixOfSlash) }; @@ -685,33 +674,6 @@ public class VaadinFinderLocatorStrategy implements LocatorStrategy { return new String[] { path }; } - private int indexOfIgnoringQuotes(String str, char find) { - return indexOfIgnoringQuotes(str, find, 0); - } - - private int indexOfIgnoringQuotes(String str, char find, int startingAt) { - boolean quote = false; - String quoteChars = "'\""; - char currentQuote = '"'; - for (int i = startingAt; i < str.length(); ++i) { - char cur = str.charAt(i); - if (quote) { - if (cur == currentQuote) { - quote = !quote; - } - continue; - } else if (cur == find) { - return i; - } else { - if (quoteChars.indexOf(cur) >= 0) { - currentQuote = cur; - quote = !quote; - } - } - } - return -1; - } - private String getSimpleClassName(String s) { String[] parts = s.split("\\."); if (s.endsWith(".class")) { diff --git a/client/src/com/vaadin/client/debug/internal/SelectorPath.java b/client/src/com/vaadin/client/debug/internal/SelectorPath.java index 2ad77a246b..2f425ee1a7 100644 --- a/client/src/com/vaadin/client/debug/internal/SelectorPath.java +++ b/client/src/com/vaadin/client/debug/internal/SelectorPath.java @@ -16,851 +16,219 @@ package com.vaadin.client.debug.internal; -import com.google.gwt.core.client.JsArrayString; -import com.google.gwt.regexp.shared.MatchResult; -import com.google.gwt.regexp.shared.RegExp; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.ComponentConnector; -import com.vaadin.client.FastStringSet; import com.vaadin.client.ServerConnector; -import com.vaadin.client.Util; import com.vaadin.client.componentlocator.ComponentLocator; -import com.vaadin.client.componentlocator.VaadinFinderLocatorStrategy; -import com.vaadin.client.metadata.NoDataException; -import com.vaadin.client.metadata.Property; -import com.vaadin.client.metadata.TypeDataStore; -import com.vaadin.client.ui.AbstractConnector; -import com.vaadin.client.ui.SubPartAware; +import com.vaadin.client.componentlocator.SelectorPredicate; /** - * A single segment of a selector path with optional parent. - *

- * The static method {@link #findTestBenchSelector(ServerConnector, Element)} - * permits looking up a selector chain for an element (a selector and its - * parents, each selector relative to its parent). - *

- * The method {@link #findElement()} can be used to locate the element - * referenced by a {@link SelectorPath}. {@link #getJUnitSelector(String)} can - * be used to obtain the string to add to a JUnit test to refer to the element - * identified by the path. + * A single segment of a selector path pointing to an Element. *

* This class should be considered internal to the framework and may change at * any time. + *

* * @since 7.1.x */ -public abstract class SelectorPath { - private final SelectorPath parent; +public class SelectorPath { + private final String path; + private final Element element; private final ComponentLocator locator; + private static Map counter = new HashMap(); + private static Map legacyNames = new HashMap(); - private static final String SUBPART_SEPARATOR = VaadinFinderLocatorStrategy.SUBPART_SEPARATOR; - - /** - * Creates a {@link SelectorPath} from the root of the UI (without a parent) - * to identify an element. - *

- * The {@link ComponentLocator} is used to locate the corresponding - * {@link Element} in the context of a UI. If there are multiple UIs on a - * single page, the locator should correspond to the correct - * {@link ApplicationConnection}. - * - * @param locator - * {@link ComponentLocator} to use - */ - protected SelectorPath(ComponentLocator locator) { - this(null, locator); - } - - /** - * Creates a {@link SelectorPath} which is relative to another - * {@link SelectorPath}. to identify an element. - *

- * The {@link ComponentLocator} is used to locate the corresponding - * {@link Element} in the context of a UI. If there are multiple UIs on a - * single page, the locator should correspond to the correct - * {@link ApplicationConnection}. - * - * @param parent - * parent {@link SelectorPath} or null for root paths - * @param locator - * {@link ComponentLocator} to use - */ - protected SelectorPath(SelectorPath parent, ComponentLocator locator) { - this.parent = parent; - this.locator = locator; + static { + legacyNames.put("FilterSelect", "ComboBox"); + legacyNames.put("ScrollTable", "Table"); } - /** - * Returns the parent {@link SelectorPath} to which this path is relative. - * - * @return parent path - */ - public SelectorPath getParent() { - return parent; + protected SelectorPath(ServerConnector c, Element e) { + element = e; + locator = new ComponentLocator(c.getConnection()); + path = locator.getPathForElement(e); } - @Override - public String toString() { - return "SelectorPath: " + getJUnitSelector("..."); + public String getPath() { + return path; } - /** - * Returns the JUnit test fragment which can be used to refer to the element - * in a test. - * - * @param context - * the context to use (usually a variable name) or null for - * default - * @return string to add in a JUnit test - */ - public abstract String getJUnitSelector(String context); - - /** - * Returns the {@link Element} that this {@link SelectorPath} points to in - * the context of the {@link ComponentLocator} of the {@link SelectorPath}. - * - * @return Element identified by the path in the current UI - */ - public abstract Element findElement(); - - /** - * Returns the path to an element/connector, including separate intermediate - * paths and the final path segment. - * - * @param connector - * the connector to find - * @param element - * sub-element inside connector or null to use connector root - * element - * @return Vaadin locator path - */ - public static SelectorPath findTestBenchSelector(ServerConnector connector, - Element element) { - // TODO there should be a better way to locate and refer to captions - - // now using domChild in layout - SelectorPath selectorPath = null; - ApplicationConnection connection = connector.getConnection(); - if (connection != null) { - if (null == element) { - element = findConnectorRootElement(connector); - } - if (null != element) { - ComponentLocator locator = new ComponentLocator(connection); - String path = locator.getPathForElement(element); - SelectorPath parent = null; - - if (!path.isEmpty()) { - selectorPath = extractIdSelectorPath(path, locator); - if (null == selectorPath) { - // parent paths first if not rooted on an ID - if (connector.getParent() != null) { - parent = findTestBenchSelector( - connector.getParent(), null); - } - - if (parent != null) { - // update path to be relative to parent - Element parentElement = parent.findElement(); - if (null != parentElement) { - String parentPath = locator - .getPathForElement(parentElement); - if (path.startsWith(parentPath)) { - // remove path of parent to look for the - // children - path = path.substring(parentPath.length()); - } - } - } - - selectorPath = extractVaadinSelectorPath(path, parent, - locator); - } - if (null == selectorPath) { - if (path.startsWith("/V")) { - // fall-back: Vaadin - // this branch is needed for /VTabsheetPanel etc. - selectorPath = SelectorPath.vaadinPath(path, - parent, locator); - } else { - // fall-back: XPath - selectorPath = SelectorPath.xpath(path, parent, - locator); - } - } - } - } - } - return selectorPath; - } - - private static SelectorPath extractIdSelectorPath(String path, - ComponentLocator locator) { - SelectorPath selectorPath = null; - if (path.startsWith("PID_S")) { - // remove internal prefix - path = path.substring(5); - - // no parent for an ID selector - String pid = path; - String rest = null; - // split at first slash that is not in the subpart (if any) - int slashPos = path.indexOf("/"); - int subPartPos = path.indexOf(SUBPART_SEPARATOR); - if (subPartPos >= 0 && slashPos > subPartPos) { - // ignore slashes in subpart - slashPos = -1; - } else if (slashPos >= 0 && subPartPos > slashPos) { - // ignore subpart after slashes - handled as a part of rest - subPartPos = -1; - } - // split the ID part and any relative path after it - if (slashPos > 0) { - pid = path.substring(0, slashPos); - rest = path.substring(slashPos); - } - - // if there is a subpart directly after the id, need to use a Vaadin - // selector - SelectorPath pidSelector = null; - if (subPartPos > 0) { - String id = pid.substring(0, subPartPos); - // include the subpart separator - String subPart = pid.substring(subPartPos); - Element element = locator.getElementByPath("PID_S" + pid); - ComponentConnector connector = Util.findPaintable( - locator.getClient(), element); - if (null != connector && null != connector.getWidget()) { - String type = connector.getWidget().getClass() - .getSimpleName(); - pidSelector = SelectorPath.vaadinPath("//" + type - + "[id=\\\"" + id + "\\\"]" + subPart, null, - locator); - } else { - // no valid connector for the subpart - return null; - } - } else { - pidSelector = SelectorPath.id(pid, locator); - } - if (null != rest && !rest.isEmpty()) { - selectorPath = extractVaadinSelectorPath(path, pidSelector, - locator); - if (selectorPath == null) { - selectorPath = SelectorPath.xpath(rest, pidSelector, - locator); - } - } else { - selectorPath = pidSelector; - } - } - return selectorPath; + public Element getElement() { + return element; } - private static SelectorPath extractVaadinSelectorPath(String path, - SelectorPath parent, ComponentLocator locator) { - SelectorPath selectorPath = null; - - String xpathPart = null; - int xpathPos = Math.min(path.indexOf("/div"), path.indexOf("/span")); - if (xpathPos >= 0) { - xpathPart = path.substring(xpathPos); - path = path.substring(0, xpathPos); - } - - String subPartPart = null; - int subPartPos = path.indexOf("#"); - if (subPartPos >= 0) { - subPartPart = path.substring(subPartPos + 1); - path = path.substring(0, subPartPos); - } - - String domChildPart = null; - int domChildPos = path.indexOf("/domChild"); - if (domChildPos >= 0) { - // include the slash - domChildPart = path.substring(domChildPos); - path = path.substring(0, domChildPos); - } - - // is it something VaadinSelectorPath can handle? - String widgetClass = null; - // first cases in a layout slot - RegExp widgetInSlotMatcher = RegExp - .compile("^/(Slot\\[(\\d+)\\]/)([a-zA-Z]+)(\\[0\\])?$"); - MatchResult matchResult = widgetInSlotMatcher.exec(path); - if (null != matchResult) { - if (matchResult.getGroupCount() >= 3) { - widgetClass = matchResult.getGroup(3); - } - } - // handle cases without intervening slot - if (null == widgetClass) { - RegExp widgetDirectlyMatcher = RegExp - .compile("^//?([a-zA-Z]+)(\\[(\\d+)\\])?$"); - matchResult = widgetDirectlyMatcher.exec(path); - if (null != matchResult) { - if (matchResult.getGroupCount() >= 1) { - widgetClass = matchResult.getGroup(1); - } - } - } - if (null != widgetClass && !widgetClass.isEmpty()) { - selectorPath = findVaadinSelectorInParent(path, widgetClass, - parent, locator); - if (null != subPartPart - && selectorPath instanceof VaadinSelectorPath) { - ((VaadinSelectorPath) selectorPath).setSubPart(subPartPart); - } else if (null != xpathPart - && selectorPath instanceof VaadinSelectorPath) { - // try to find sub-part if supported - ComponentConnector connector = Util.findPaintable( - locator.getClient(), selectorPath.findElement()); - if (connector != null - && connector.getWidget() instanceof SubPartAware) { - // for SubPartAware, skip the XPath fall-back path - Element element = locator.getElementByPathStartingAt(path, - selectorPath.findElement()); - SubPartAware subPartAware = (SubPartAware) connector - .getWidget(); - String subPart = subPartAware.getSubPartName(element); - if (null != subPart) { - // type checked above - ((VaadinSelectorPath) selectorPath).setSubPart(subPart); - } - } else { - // fall-back to XPath for the last part of the path - selectorPath = SelectorPath.xpath(xpathPart, selectorPath, - locator); - } - } - - // the whole /domChild[i]/domChild[j]... part as a single selector - if (null != domChildPart - && selectorPath instanceof VaadinSelectorPath) { - selectorPath = SelectorPath.vaadinPath(domChildPart, - selectorPath, locator); - } - } else if (null != domChildPart) { - // cases with domChild path only (parent contains rest) - selectorPath = SelectorPath.vaadinPath(domChildPart, parent, - locator); - } - return selectorPath; + public ComponentLocator getLocator() { + return locator; } /** - * Find the zero-based index of the widget of type widgetClass identified by - * path within its parent and returns the corresponding Vaadin path (if - * any). For instance, the second button in a layout has index 1 regardless - * of non-button components in the parent. - *

- * The approach used internally is to try to find the caption of the element - * inside its parent and check whether it is sufficient to identify the - * element correctly. If not, possible indices are looped through to see if - * the component of the specified type within the specified parent - * identifies the correct element. This is inefficient but more reliable - * than some alternative approaches, and does not require special cases for - * various layouts etc. + * Generate ElementQuery code for Java. Fallback to By.vaadin(path) if + * dealing with LegacyLocator * - * @param path - * relative path for the widget of interest - * @param widgetClass - * type of the widget of interest - * @param parent - * parent component to which the path is relative - * @param locator - * ComponentLocator used to map paths to elements - * @return selector path for the element, null if none found + * @return String containing Java code for finding the element described by + * path */ - private static SelectorPath findVaadinSelectorInParent(String path, - String widgetClass, SelectorPath parent, ComponentLocator locator) { - if (null == parent) { - SelectorPath selectorPath = SelectorPath.vaadin(widgetClass, 0, - null, locator); - if (selectorPath.findElement() == locator.getElementByPath(path)) { - return selectorPath; + public String getElementQuery() { + if (locator.isValidForLegacyLocator(path)) { + return getLegacyLocatorQuery(); + } + + String[] fragments; + String tmpPath = path; + List postFilters = SelectorPredicate + .extractPostFilterPredicates(path); + if (postFilters.size() > 0) { + tmpPath = tmpPath.substring(1, tmpPath.lastIndexOf(')')); + } + + // Generate an ElementQuery + fragments = tmpPath.split("/"); + String elementQueryString; + int index = 0; + for (SelectorPredicate p : postFilters) { + if (p.getIndex() > 0) { + index = p.getIndex(); + } + } + if (index > 0) { + elementQueryString = ".get(" + index + ");"; + } else { + elementQueryString = ".fist();"; + } + for (int i = 1; i < fragments.length; ++i) { + if (fragments[i].isEmpty()) { + // Recursive search has occasional empty fragments + continue; + } + + // Get Element.class -name + String queryFragment = ""; + String elementClass = getComponentName(fragments[i]) + + "Element.class"; + for (SelectorPredicate p : SelectorPredicate + .extractPredicates(fragments[i])) { + // Add in predicates like .caption and .id + queryFragment += "." + p.getName() + "(\"" + p.getValue() + + "\")"; + } + if (i == fragments.length - 1) { + // Last element in path. + queryFragment = "$(" + elementClass + ")" + queryFragment; } else { - return null; - } - } - // This method uses an inefficient brute-force approach but its - // results should match what is used by the TestBench selectors. - Element parentElement = parent.findElement(); - String parentPathString = locator.getPathForElement(parentElement); - if (null == parentPathString) { - parentPathString = ""; - } - Element elementToFind = locator.getElementByPath(parentPathString - + path); - if (null == elementToFind) { - return null; - } - // if the connector has a caption, first try if the element can be - // located in parent with it; if that fails, use the index in parent - String caption = getCaptionForElement(elementToFind, locator); - if (null != caption) { - SelectorPath testPath = SelectorPath.vaadin(widgetClass, caption, - parent, locator); - Element testElement = testPath.findElement(); - // TODO in theory could also iterate upwards into parents, using - // "//" before the caption to find the shortest matching path that - // identifies the correct element - if (testElement == elementToFind) { - return testPath; - } - } - - // Assumes that the number of logical child elements is at most the - // number of direct children of the DOM element - e.g. layouts have a - // single component per slot. - for (int i = 0; i < parentElement.getChildCount(); ++i) { - SelectorPath testPath = SelectorPath.vaadin(widgetClass, i, parent, - locator); - Element testElement = testPath.findElement(); - if (testElement == elementToFind) { - return testPath; - } - } - return null; - } - - private static String getCaptionForElement(Element element, - ComponentLocator locator) { - String caption = null; - ComponentConnector connector = Util.findPaintable(locator.getClient(), - element); - if (null != connector) { - Property property = AbstractConnector.getStateType(connector) - .getProperty("caption"); - try { - Object value = property.getValue(connector.getState()); - if (null != value) { - caption = String.valueOf(value); + // If followed by an empty fragment search is recursive + boolean recursive = fragments[i + 1].isEmpty(); + if (recursive) { + queryFragment = ".in(" + elementClass + ")" + queryFragment; + } else { + queryFragment = ".childOf(" + elementClass + ")" + + queryFragment; } - } catch (NoDataException e) { - // skip the caption based selection and use index below } + elementQueryString = queryFragment + elementQueryString; } - return caption; - } - private static Element findConnectorRootElement(ServerConnector connector) { - Element element = null; - // try to find the root element of the connector - if (connector instanceof ComponentConnector) { - Widget widget = ((ComponentConnector) connector).getWidget(); - if (widget != null) { - element = widget.getElement(); - } - } - return element; - } - - public ComponentLocator getLocator() { - return locator; - } - - @Override - public int hashCode() { - return getJUnitSelector("context").hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - SelectorPath other = (SelectorPath) obj; - if (parent == null) { - if (other.parent != null) { - return false; - } - } else if (!parent.equals(other.parent)) { - return false; - } - if (!other.getJUnitSelector("context").equals( - getJUnitSelector("context"))) { - return false; - } - return true; - } - - protected static SelectorPath xpath(String path, SelectorPath parent, - ComponentLocator locator) { - return new XPathSelectorPath(path, parent, locator); - } - - protected static SelectorPath id(String id, ComponentLocator locator) { - return new IdSelectorPath(id, locator); - } - - protected static SelectorPath vaadin(String widgetClass, - String widgetCaption, SelectorPath parent, ComponentLocator locator) { - return new VaadinSelectorPath(widgetClass, widgetCaption, 0, parent, - locator); - } - - protected static SelectorPath vaadin(String widgetClass, int widgetIndex, - SelectorPath parent, ComponentLocator locator) { - return new VaadinSelectorPath(widgetClass, null, widgetIndex, parent, - locator); - } - - protected static SelectorPath vaadinPath(String vaadinPath, - SelectorPath parent, ComponentLocator locator) { - return new ByVaadinSelectorPath(vaadinPath, parent, locator); + // Return full Java variable assignment and eQuery + return generateJavaVariable(fragments[fragments.length - 1]) + + elementQueryString; } /** - * Selector path for finding an {@link Element} based on an XPath (relative - * to the parent {@link SelectorPath}). + * @since + * @param frags + * @param i + * @return */ - private static class XPathSelectorPath extends SelectorPath { - // path segment relative to parent - private final String path; - - /** - * Creates a relative XPath based component selector path. - * - * @param path - * XPath - * @param parent - * {@link SelectorPath} to which the XPath is relative, null - * if from the root - * @param locator - * ComponentLocator to use to find the element - */ - public XPathSelectorPath(String path, SelectorPath parent, - ComponentLocator locator) { - super(parent, locator); - this.path = path; - } - - /** - * Returns the XPath relative to the parent element. - * - * @return relative path string - */ - public String getPath() { - return path; - } - - @Override - public String getJUnitSelector(String context) { - // use driver by default - String contextString = null != context ? context : "getDriver()"; - return contextString + ".findElement(By.xpath(\"" + getPath() - + "\"))"; - } - - @Override - public Element findElement() { - if (null != getParent()) { - Element parentElement = getParent().findElement(); - if (null == parentElement) { - // broken path - possibly removed parent - return null; - } - Element element = getLocator().getElementByPathStartingAt( - getPath(), parentElement); - return element; - } else { - Element element = getLocator().getElementByPath(getPath()); - return element; - } - } + protected String getComponentName(String fragment) { + return fragment.split("\\[")[0]; } /** - * Element identifier based locator path. - *

- * Identifier paths never have a parent and the identifiers should be unique - * within the context of the {@link ComponentLocator}/page. + * Generates a legacy locator for SelectorPath. + * + * @return String containing Java code for element search and assignment */ - private static class IdSelectorPath extends SelectorPath { - private final String id; - - /** - * Creates an identifier based {@link SelectorPath}. The identifier - * should not contain the old "PID_S" prefix. - * - * @param id - * @param locator - */ - public IdSelectorPath(String id, ComponentLocator locator) { - super(locator); - this.id = id; - } + private String getLegacyLocatorQuery() { + String[] frags = path.split("/"); + String name = getComponentName(frags[frags.length - 1]).substring(1); - /** - * Returns the ID in the DOM used to identify the element. - * - * @return Vaadin debug ID or equivalent - */ - public String getId() { - return id; + if (legacyNames.containsKey(name)) { + name = legacyNames.get(name); } - @Override - public String getJUnitSelector(String context) { - String contextPart = null != context ? ", " + context : ""; - return "getElementById(\"" + getId() + "\"" + contextPart + ")"; - } + name = getNameWithCount(name); - @Override - public Element findElement() { - // this also works for IDs - return getLocator().getElementByPath("PID_S" + getId()); - } + // Use direct path and elementX naming style. + return "WebElement " + name.substring(0, 1).toLowerCase() + + name.substring(1) + " = getDriver().findElement(By.vaadin(\"" + + path + "\"));"; } /** - * Common base class for Vaadin selector paths (By.vaadin(...)). + * Get variable name with counter for given component name. + * + * @param name + * Component name + * @return name followed by count */ - private static abstract class AbstractVaadinSelectorPath extends - SelectorPath { - - protected AbstractVaadinSelectorPath(SelectorPath parent, - ComponentLocator locator) { - super(parent, locator); + protected String getNameWithCount(String name) { + if (!counter.containsKey(name)) { + counter.put(name, 0); } - - /** - * Returns the {@link ComponentLocator} path of the element relative to - * the parent path. - * - * @return path of the element for By.vaadin(...) - */ - protected abstract String getPath(); - - @Override - public Element findElement() { - if (null != getParent()) { - Element parentElement = getParent().findElement(); - Element element = getLocator().getElementByPathStartingAt( - getPath(), parentElement); - return element; - } else { - return getLocator().getElementByPath(getPath()); - } - } - + counter.put(name, counter.get(name) + 1); + name += counter.get(name); + return name; } /** - * TestBench selector path for Vaadin widgets. These selectors are based on - * the widget class and either the index among the widgets of that type in - * the parent or the widget caption. + * Generate Java variable assignment from given selector fragment + * + * @param pathFragment + * Selector fragment + * @return piece of java code */ - private static class VaadinSelectorPath extends AbstractVaadinSelectorPath { - private final String widgetClass; - private final String widgetCaption; - // negative for no index - private final int widgetIndex; - private String subPart; - - /** - * Creates a Vaadin {@link SelectorPath}. The path identifies an element - * of a given type under its parent based on either its caption or its - * index (if both are given, only the caption is used). See also - * {@link ComponentLocator} for more details. - * - * @param widgetClass - * client-side widget class - * @param widgetCaption - * caption of the widget - null to use the index instead - * @param widgetIndex - * index of the widget of the type within its parent, used - * only if the caption is not given - * @param parent - * parent {@link SelectorPath} or null - * @param locator - * component locator to use to find the corresponding - * {@link Element} - */ - public VaadinSelectorPath(String widgetClass, String widgetCaption, - int widgetIndex, SelectorPath parent, ComponentLocator locator) { - super(parent, locator); - this.widgetClass = widgetClass; - this.widgetCaption = widgetCaption; - this.widgetIndex = widgetIndex; - } - - /** - * Returns the widget type used to identify the element. - * - * @return Vaadin widget class - */ - public String getWidgetClass() { - return widgetClass; - } - - /** - * Returns the widget caption to look for or null if index is used - * instead. - * - * @return widget caption to match - */ - public String getWidgetCaption() { - return widgetCaption; - } - - /** - * Returns the index of the widget of that type within its parent - only - * used if caption is null. - * - * @return widget index - */ - public int getWidgetIndex() { - return widgetIndex; - } - - /** - * Returns the sub-part string (e.g. row and column identifiers within a - * table) used to identify a part of a component. See - * {@link ComponentLocator} and especially Vaadin selectors for more - * information. - * - * @return sub-part string or null if none - */ - public String getSubPart() { - return subPart; - } - - /** - * Sets the sub-part string (e.g. row and column identifiers within a - * table) used to identify a part of a component. See - * {@link ComponentLocator} and especially Vaadin selectors for more - * information. - * - * @param subPart - * sub-part string to use or null for none - */ - public void setSubPart(String subPart) { - this.subPart = subPart; - } - - @Override - public String getJUnitSelector(String context) { - String componentClass = getComponentClass(); - String contextPart = null != context ? ", " + context : ""; - // TODO update after subpart API finished - if (null != getSubPart() || null == componentClass) { - return "getElementByPath(\"" + getPath() + "\"" + contextPart - + ")"; - } else if (null != getWidgetCaption()) { - return "getElementByCaption(" + componentClass + ".class, \"" - + getWidgetCaption() + "\"" + contextPart + ")"; - } else if (getWidgetIndex() >= 0) { - return "getElementByIndex(" + componentClass + ".class, " - + getWidgetIndex() + contextPart + ")"; - } else { - return "getElement(" + componentClass + ".class" + contextPart - + ")"; - } - } + private String generateJavaVariable(String pathFragment) { + // Get element type and predicates from fragment + List predicates = SelectorPredicate + .extractPredicates(pathFragment); + String elementType = pathFragment.split("\\[")[0]; + String name = getNameFromPredicates(predicates, elementType); - /** - * Returns the Vaadin server side component class to use for a widget - * class. - * - * @return fully qualified server side class name, null if unable to - * determine it - */ - private String getComponentClass() { - ComponentConnector connector = Util.findPaintable(getLocator() - .getClient(), findElement()); - Class connectorClass = connector - .getClass(); - FastStringSet identifiers = TypeDataStore.get().findIdentifiersFor( - connectorClass); - JsArrayString ids = identifiers.dump(); - if (ids.length() == 1) { - return ids.get(0); - } else { - return null; - } + if (name.equals(elementType)) { + name = getNameWithCount(name); } - // these are used only to locate components on the client side by path - - @Override - protected String getPath() { - return "/" + getWidgetClass() + getIndexString(false) - + getSubPartPostfix(); - } + // Replace unusable characters + name = name.replaceAll("\\W", ""); - private String getIndexString(boolean escapeQuotes) { - if (null != getWidgetCaption()) { - if (escapeQuotes) { - return "[caption=\\\"" + widgetCaption + "\\\"]"; - } else { - return "[caption=\"" + widgetCaption + "\"]"; - } - } else if (widgetIndex >= 0) { - return "[" + getWidgetIndex() + "]"; - } else { - return ""; - } - } - - private String getSubPartPostfix() { - String subPartString = ""; - if (null != getSubPart()) { - subPartString = SUBPART_SEPARATOR + getSubPart(); - } - return subPartString; - } + // Lowercase the first character of name + return elementType + "Element " + name.substring(0, 1).toLowerCase() + + name.substring(1) + " = "; } /** - * TestBench selector path for Vaadin widgets, always using a - * By.vaadin(path) rather than other convenience methods. + * Get variable name based on predicates. Fallback to elementType + * + * @param predicates + * Predicates related to element + * @param elementType + * Element type + * @return name for Variable */ - private static class ByVaadinSelectorPath extends - AbstractVaadinSelectorPath { - private final String path; - - /** - * Vaadin selector path for an exact path (including any preceding - * slash). - * - * @param path - * path of the element (normally with a leading slash), not - * null - * @param parent - * parent selector path or null if none - * @param locator - * ComponentLocator to use to find the corresponding element - */ - public ByVaadinSelectorPath(String path, SelectorPath parent, - ComponentLocator locator) { - super(parent, locator); - this.path = path; - } - - @Override - public String getJUnitSelector(String context) { - String contextPart = null != context ? ", " + context : ""; - return "getElementByPath(\"" + getPath() + "\"" + contextPart + ")"; - } - - /** - * Returns the By.vaadin(...) path relative to the parent element. - * - * @return relative path string - */ - @Override - public String getPath() { - return path; - } + private String getNameFromPredicates(List predicates, + String elementType) { + String name = elementType; + for (SelectorPredicate p : predicates) { + if ("caption".equals(p.getName())) { + // Caption + elementType is a suitable name + name = p.getValue() + elementType; + } else if ("id".equals(p.getName())) { + // Just id. This is unique, use it. + return p.getValue(); + } + } + return name; } } \ No newline at end of file diff --git a/client/src/com/vaadin/client/debug/internal/TestBenchSection.java b/client/src/com/vaadin/client/debug/internal/TestBenchSection.java index 462309768f..35c0d7abe8 100644 --- a/client/src/com/vaadin/client/debug/internal/TestBenchSection.java +++ b/client/src/com/vaadin/client/debug/internal/TestBenchSection.java @@ -55,25 +55,14 @@ public class TestBenchSection implements Section { */ private static class SelectorWidget extends HTML implements MouseOverHandler, MouseOutHandler { - private static int selectorCounter = 1; + private final SelectorPath path; - final private SelectorPath path; - final private SelectorWidget parent; - final private int selectorIndex = selectorCounter++; - - public SelectorWidget(final SelectorPath path, - final SelectorWidget parent) { + public SelectorWidget(final SelectorPath path) { this.path = path; - this.parent = parent; - String parentString = (parent != null) ? ("element" + parent.selectorIndex) - : null; - String html = "

" - + Util.escapeHTML("WebElement element" + selectorIndex - + " = " + path.getJUnitSelector(parentString) + ";") - + "
"; + + Util.escapeHTML(path.getElementQuery()) + ""; setHTML(html); addMouseOverHandler(this); @@ -82,10 +71,10 @@ public class TestBenchSection implements Section { @Override public void onMouseOver(MouseOverEvent event) { - ApplicationConnection a = path.getLocator().getClient(); - Element element = path.findElement(); + Highlight.hideAll(); + + Element element = path.getElement(); if (null != element) { - Highlight.hideAll(); Highlight.show(element); } } @@ -96,13 +85,11 @@ public class TestBenchSection implements Section { } } - private final DebugButton tabButton = new DebugButton(Icon.SELECTOR, + private final DebugButton tabButton = new DebugButton(Icon.WARNING, "Pick Vaadin TestBench selectors"); private final FlowPanel content = new FlowPanel(); - private final HierarchyPanel hierarchyPanel = new HierarchyPanel(); - private final FlowPanel selectorPanel = new FlowPanel(); // map from full path to SelectorWidget to enable reuse of old selectors private Map selectorWidgets = new HashMap(); @@ -110,21 +97,14 @@ public class TestBenchSection implements Section { private final FlowPanel controls = new FlowPanel(); private final Button find = new DebugButton(Icon.HIGHLIGHT, - "Select a component on the page to inspect it"); - private final Button refreshHierarchy = new DebugButton(Icon.HIERARCHY, - "Refresh the connector hierarchy tree"); + "Pick an element and generate a query for it"); + + private final Button clear = new DebugButton(Icon.CLEAR, + "Clear current elements"); private HandlerRegistration highlightModeRegistration = null; public TestBenchSection() { - controls.add(refreshHierarchy); - refreshHierarchy.setStylePrimaryName(VDebugWindow.STYLENAME_BUTTON); - refreshHierarchy.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - hierarchyPanel.update(); - } - }); controls.add(find); find.setStylePrimaryName(VDebugWindow.STYLENAME_BUTTON); @@ -135,15 +115,16 @@ public class TestBenchSection implements Section { } }); - hierarchyPanel.addListener(new SelectConnectorListener() { + controls.add(clear); + clear.setStylePrimaryName(VDebugWindow.STYLENAME_BUTTON); + clear.addClickHandler(new ClickHandler() { @Override - public void select(ServerConnector connector, Element element) { - pickSelector(connector, element); + public void onClick(ClickEvent event) { + clearResults(); } }); content.setStylePrimaryName(VDebugWindow.STYLENAME + "-testbench"); - content.add(hierarchyPanel); content.add(selectorPanel); } @@ -209,33 +190,19 @@ public class TestBenchSection implements Section { highlightModeRegistration = null; find.removeStyleDependentName(VDebugWindow.STYLENAME_ACTIVE); } + Highlight.hideAll(); } private void pickSelector(ServerConnector connector, Element element) { - SelectorPath path = SelectorPath.findTestBenchSelector(connector, - element); - if (null != path) { - addSelectorWidgets(path); - } - } + SelectorPath p = new SelectorPath(connector, Util + .findPaintable(connector.getConnection(), element).getWidget() + .getElement()); + SelectorWidget w = new SelectorWidget(p); - private SelectorWidget addSelectorWidgets(SelectorPath path) { - // add selector widgets recursively from root towards children, reusing - // old ones - SelectorPath parent = path.getParent(); - SelectorWidget parentWidget = null; - if (null != parent) { - parentWidget = addSelectorWidgets(parent); - } - SelectorWidget widget = selectorWidgets.get(path); - if (null == widget) { - // the parent has already been added above - widget = new SelectorWidget(path, parentWidget); - selectorWidgets.put(path, widget); - selectorPanel.add(widget); - } - return widget; + content.add(w); + + stopFind(); } private final NativePreviewHandler highlightModeHandler = new NativePreviewHandler() { @@ -260,6 +227,7 @@ public class TestBenchSection implements Section { // make sure that not finding the highlight element only Highlight.hideAll(); + eventTarget = Util.getElementFromPoint(event.getNativeEvent() .getClientX(), event.getNativeEvent().getClientY()); ComponentConnector connector = findConnector(eventTarget); @@ -304,4 +272,8 @@ public class TestBenchSection implements Section { return null; } + private void clearResults() { + content.clear(); + } + }