Group panel with alphabetized group and method names.
diff --git a/src/main/java/org/testng/collections/Maps.java b/src/main/java/org/testng/collections/Maps.java
index fa5da96..a5ec025 100755
--- a/src/main/java/org/testng/collections/Maps.java
+++ b/src/main/java/org/testng/collections/Maps.java
@@ -18,4 +18,8 @@
public static <K, V> ListMultiMap<K, V> newListMultiMap() {
return new ListMultiMap<K, V>();
}
+
+ public static <K, V> SetMultiMap<K, V> newSetMultiMap() {
+ return new SetMultiMap<K, V>();
+ }
}
diff --git a/src/main/java/org/testng/collections/SetMultiMap.java b/src/main/java/org/testng/collections/SetMultiMap.java
new file mode 100644
index 0000000..ade4759
--- /dev/null
+++ b/src/main/java/org/testng/collections/SetMultiMap.java
@@ -0,0 +1,81 @@
+package org.testng.collections;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * A container to hold sets indexed by a key.
+ */
+public class SetMultiMap<K, V> {
+ private Map<K, Set<V>> m_objects = Maps.newHashMap();
+
+ public void put(K key, V method) {
+ Set<V> l = m_objects.get(key);
+ if (l == null) {
+ l = Sets.newHashSet();
+ m_objects.put(key, l);
+ }
+ l.add(method);
+ }
+
+ public Set<V> get(K key) {
+ return m_objects.get(key);
+ }
+
+ public Set<K> getKeys() {
+ return new HashSet(m_objects.keySet());
+// Set<K> result = new ArraySet<K>();
+// for (K k : m_objects.keySet()) {
+// result.add(k);
+// }
+// Collections.sort(result);
+// return result;
+ }
+
+ public boolean containsKey(K k) {
+ return m_objects.containsKey(k);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder();
+ Set<K> indices = getKeys();
+// Collections.sort(indices);
+ for (K i : indices) {
+ result.append("\n ").append(i).append(" <-- ");
+ for (Object o : m_objects.get(i)) {
+ result.append(o).append(" ");
+ }
+ }
+ return result.toString();
+ }
+
+ public boolean isEmpty() {
+ return m_objects.size() == 0;
+ }
+
+ public int getSize() {
+ return m_objects.size();
+ }
+
+ public Set<V> remove(K key) {
+ return m_objects.remove(key);
+ }
+
+ public Set<Entry<K, Set<V>>> getEntrySet() {
+ return m_objects.entrySet();
+ }
+
+ public Collection<Set<V>> getValues() {
+ return m_objects.values();
+ }
+
+ public void putAll(K k, Collection<V> values) {
+ for (V v : values) {
+ put(k, v);
+ }
+ }
+}
diff --git a/src/main/java/org/testng/reporters/jq/GroupPanel.java b/src/main/java/org/testng/reporters/jq/GroupPanel.java
new file mode 100644
index 0000000..ac70c87
--- /dev/null
+++ b/src/main/java/org/testng/reporters/jq/GroupPanel.java
@@ -0,0 +1,48 @@
+package org.testng.reporters.jq;
+
+import org.testng.ISuite;
+import org.testng.reporters.XMLStringBuffer;
+
+import java.util.Collections;
+import java.util.List;
+
+public class GroupPanel extends BaseMultiSuitePanel {
+ public GroupPanel(Model model) {
+ super(model);
+ }
+
+ private static String getTag(ISuite suite) {
+ return "group-" + suiteToTag(suite);
+ }
+
+ @Override
+ public String getHeader(ISuite suite) {
+ return "Groups for " + suite.getName();
+ }
+
+ @Override
+ public String getPanelName(ISuite suite) {
+ return getTag(suite);
+ }
+
+ @Override
+ public String getContent(ISuite suite, XMLStringBuffer main) {
+ XMLStringBuffer xsb = new XMLStringBuffer(main.getCurrentIndent());
+ List<String> sortedGroups = getModel().getGroups(suite.getName());
+ Collections.sort(sortedGroups);
+ for (String group : sortedGroups) {
+ xsb.push(D, C, "test-group");
+ xsb.addRequired(S, group, C, "test-group-name");
+ xsb.addEmptyElement("br");
+ List<String> sortedMethods = getModel().getMethodsInGroup(group);
+ for (String method : sortedMethods) {
+ xsb.push(D, C, "method-in-group");
+ xsb.addRequired(S, method, C, "method-in-group-name");
+ xsb.addEmptyElement("br");
+ xsb.pop(D);
+ }
+ xsb.pop(D);
+ }
+ return xsb.toXML();
+ }
+}
diff --git a/src/main/java/org/testng/reporters/jq/Main.java b/src/main/java/org/testng/reporters/jq/Main.java
index ffa9a87..2c815fc 100644
--- a/src/main/java/org/testng/reporters/jq/Main.java
+++ b/src/main/java/org/testng/reporters/jq/Main.java
@@ -38,7 +38,8 @@
// Navigator on the left hand side
TestNgXmlPanel testNgPanel = new TestNgXmlPanel(m_model);
TestPanel testPanel = new TestPanel(m_model);
- new NavigatorPanel(m_model, testNgPanel, testPanel).generate(xsb);
+ GroupPanel groupPanel = new GroupPanel(m_model);
+ new NavigatorPanel(m_model, testNgPanel, testPanel, groupPanel).generate(xsb);
xsb.push(D, C, "wrapper");
xsb.push(D, "class", "main-panel-root");
@@ -49,6 +50,10 @@
new SuitePanel(m_model).generate(xsb);
//
+ // Group panel
+ groupPanel.generate(xsb);
+
+ //
// Panel that displays the list of test names
//
testPanel.generate(xsb);
diff --git a/src/main/java/org/testng/reporters/jq/Model.java b/src/main/java/org/testng/reporters/jq/Model.java
index 796e471..1e66ad1 100644
--- a/src/main/java/org/testng/reporters/jq/Model.java
+++ b/src/main/java/org/testng/reporters/jq/Model.java
@@ -8,9 +8,13 @@
import org.testng.collections.ListMultiMap;
import org.testng.collections.Lists;
import org.testng.collections.Maps;
+import org.testng.collections.SetMultiMap;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Set;
public class Model {
private ListMultiMap<ISuite, ITestResult> m_model = Maps.newListMultiMap();
@@ -24,6 +28,8 @@
private List<ITestResult> m_allFailedResults = Lists.newArrayList();
// Each suite is mapped to failed.png, skipped.png or nothing (which means passed.png)
private Map<String, String> m_imageBySuiteName = Maps.newHashMap();
+ private SetMultiMap<String, String> m_groupsBySuiteName = Maps.newSetMultiMap();
+ private SetMultiMap<String, String> m_methodsByGroup = Maps.newSetMultiMap();
public Model(List<ISuite> suites) {
m_suites = suites;
@@ -68,6 +74,7 @@
ResultsByClass rbc = new ResultsByClass();
for (ITestResult tr : passed) {
rbc.addResult(tr.getTestClass().getRealClass(), tr);
+ updateGroups(suite, tr);
}
m_passedResultsByClass.put(suite, rbc);
}
@@ -78,6 +85,7 @@
for (ITestResult tr : skipped) {
m_imageBySuiteName.put(suite.getName(), getImage("skipped"));
rbc.addResult(tr.getTestClass().getRealClass(), tr);
+ updateGroups(suite, tr);
}
m_skippedResultsByClass.put(suite, rbc);
}
@@ -89,6 +97,7 @@
m_imageBySuiteName.put(suite.getName(), getImage("failed"));
rbc.addResult(tr.getTestClass().getRealClass(), tr);
m_allFailedResults.add(tr);
+ updateGroups(suite, tr);
}
m_failedResultsByClass.put(suite, rbc);
}
@@ -99,6 +108,15 @@
}
}
+ private void updateGroups(ISuite suite, ITestResult tr) {
+ String[] groups = tr.getMethod().getGroups();
+ m_groupsBySuiteName.putAll(suite.getName(),
+ Arrays.asList(groups));
+ for (String group : groups) {
+ m_methodsByGroup.put(group, tr.getMethod().getMethodName());
+ }
+ }
+
public ResultsByClass getFailedResultsByClass(ISuite suite) {
return m_failedResultsByClass.get(suite);
}
@@ -152,4 +170,24 @@
}
return result;
}
+
+ public <T> Set<T> nonnullSet(Set<T> l) {
+ return l != null ? l : Collections.<T>emptySet();
+ }
+
+ public <T> List<T> nonnullList(List<T> l) {
+ return l != null ? l : Collections.<T>emptyList();
+ }
+
+ public List<String> getGroups(String name) {
+ List<String> result = Lists.newArrayList(nonnullSet(m_groupsBySuiteName.get(name)));
+ Collections.sort(result);
+ return result;
+ }
+
+ public List<String> getMethodsInGroup(String groupName) {
+ List<String> result = Lists.newArrayList(nonnullSet(m_methodsByGroup.get(groupName)));
+ Collections.sort(result);
+ return result;
+ }
}
diff --git a/src/main/java/org/testng/reporters/jq/NavigatorPanel.java b/src/main/java/org/testng/reporters/jq/NavigatorPanel.java
index 161bf89..ddcfb0e 100644
--- a/src/main/java/org/testng/reporters/jq/NavigatorPanel.java
+++ b/src/main/java/org/testng/reporters/jq/NavigatorPanel.java
@@ -14,11 +14,14 @@
private TestNgXmlPanel m_testNgPanel;
private TestPanel m_testPanel;
+ private GroupPanel m_groupPanel;
- public NavigatorPanel(Model model, TestNgXmlPanel testNgPanel, TestPanel testPanel) {
+ public NavigatorPanel(Model model, TestNgXmlPanel testNgPanel, TestPanel testPanel,
+ GroupPanel groupPanel) {
super(model);
m_testNgPanel = testNgPanel;
m_testPanel = testPanel;
+ m_groupPanel = groupPanel;
}
@Override
@@ -69,7 +72,9 @@
header.addRequired(S, "Info");
header.pop(D);
- // Info content
+ //
+ // Info
+ //
header.push(D, C, "suite-section-content");
int total = failed + skipped + passed;
String stats = String.format("%s, %s %s %s",
@@ -80,7 +85,7 @@
header.push("ul");
- // Tests
+ // "59 Tests"
header.push("li");
header.push("a", "href", "#",
"panel-name", m_testPanel.getPanelName(suite),
@@ -90,7 +95,17 @@
header.pop("a");
header.pop("li");
- // testng.xml
+ // "12 groups"
+ header.push("li");
+ header.push("a", "href", "#",
+ "panel-name", m_groupPanel.getPanelName(suite),
+ C, "navigator-link ");
+ header.addOptional(S,
+ String.format("%s ", pluralize(getModel().getGroups(suite.getName()).size(), "group")));
+ header.pop("a");
+ header.pop("li");
+
+ // "testng.xml"
header.push("li");
header.push("a", "href", "#",
"panel-name", m_testNgPanel.getPanelName(suite),
@@ -106,7 +121,7 @@
header.pop(D); // suite-section-content
//
- // Methods
+ // Results
//
header.push(D, C, "result-section");
diff --git a/src/main/resources/testng-reports.css b/src/main/resources/testng-reports.css
index 9165dee..1bc7698 100644
--- a/src/main/resources/testng-reports.css
+++ b/src/main/resources/testng-reports.css
@@ -150,3 +150,20 @@
float: right;
height: 20;
}
+
+.test-group {
+ font: 20px 'Lucida Grande';
+ margin: 5px 5px 10px 5px;
+ border-width: 0px 0px 1px 0px;
+ border-style: solid;
+ padding: 5px;
+}
+
+.test-group-name {
+ font-weight: bold;
+}
+
+.method-in-group {
+ font-size: 16px;
+ margin-left: 80px;
+}