diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java index 38ea40b8a63511a2b5b143b75b13d3ec0da151d6..faeb3f8842700d1849fe1b64d4bf66dc4a532086 100644 --- a/core/src/main/java/hudson/Functions.java +++ b/core/src/main/java/hudson/Functions.java @@ -919,42 +919,71 @@ public class Functions { } String path = ancestors.get(p); - if(path!=null) return path; + if(path!=null) { + return normalizeURI(path + '/'); + } Item i=p; String url = ""; + Collection viewItems; + if (view != null) { + viewItems = view.getItems(); + } else { + viewItems = Collections.emptyList(); + } while(true) { ItemGroup ig = i.getParent(); url = i.getShortUrl()+url; - if(ig== Jenkins.getInstance() || (view != null && ig == view.getOwner())) { + if(ig== Jenkins.getInstance() || (view != null && ig == view.getOwnerItemGroup())) { assert i instanceof TopLevelItem; - if(view!=null && view.contains((TopLevelItem)i)) { + if(viewItems.contains((TopLevelItem)i)) { // if p and the current page belongs to the same view, then return a relative path - return ancestors.get(view)+'/'+url; + return normalizeURI(ancestors.get(view)+'/'+url); } else { // otherwise return a path from the root Hudson - return request.getContextPath()+'/'+p.getUrl(); + return normalizeURI(request.getContextPath()+'/'+p.getUrl()); } } path = ancestors.get(ig); - if(path!=null) return path+'/'+url; + if(path!=null) { + return normalizeURI(path+'/'+url); + } assert ig instanceof Item; // if not, ig must have been the Hudson instance i = (Item) ig; } } + private static String normalizeURI(String uri) { + return URI.create(uri).normalize().toString(); + } + + /** + * Gets all the {@link TopLevelItem}s recursively in the {@link ItemGroup} tree. + * + * @since XXX + */ public static List getAllTopLevelItems(ItemGroup root) { return Items.getAllItems(root, TopLevelItem.class); } + + /** + * Gets the relative display name to the given item from the specified group. + * + * @since XXX + * @param p the Item we want the relative display name + * @param g the ItemGroup used as point of reference for the item + * @return + * String like "foo » bar" + */ public static String getRelativeDisplayNameFrom(Item p, ItemGroup g) { if (p == null) return null; String relativeName = p.getRelativeNameFrom(g); if (relativeName == null) return null; - return relativeName.replace("/", " \u00BB "); + return relativeName.replace("/", " » "); } public static Map dumpAllThreads() { diff --git a/core/src/test/java/hudson/FunctionsTest.java b/core/src/test/java/hudson/FunctionsTest.java index 614a23ad56cb66bcedbc0a58079c4806b6d6410c..03b6179e812b37520bcbd3a134e01450094ce126 100644 --- a/core/src/test/java/hudson/FunctionsTest.java +++ b/core/src/test/java/hudson/FunctionsTest.java @@ -23,18 +23,30 @@ */ package hudson; +import static org.junit.Assert.assertEquals; +import static org.powermock.api.mockito.PowerMockito.mock; +import static org.powermock.api.mockito.PowerMockito.mockStatic; +import static org.powermock.api.mockito.PowerMockito.when; +import hudson.model.Action; +import hudson.model.ItemGroup; +import hudson.model.TopLevelItem; +import hudson.model.View; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Locale; -import hudson.model.Action; -import static org.junit.Assert.*; +import jenkins.model.Jenkins; + import org.junit.Test; import org.junit.runner.RunWith; import org.jvnet.hudson.test.Bug; +import org.kohsuke.stapler.Ancestor; import org.kohsuke.stapler.Stapler; import org.kohsuke.stapler.StaplerRequest; -import org.powermock.modules.junit4.PowerMockRunner; import org.powermock.core.classloader.annotations.PrepareForTest; -import static org.powermock.api.mockito.PowerMockito.*; +import org.powermock.modules.junit4.PowerMockRunner; @RunWith(PowerMockRunner.class) public class FunctionsTest { @@ -101,6 +113,94 @@ public class FunctionsTest { assertEquals(contextPath + "/" + itUrl + path, result); } } + + @Test + @PrepareForTest({Stapler.class, Jenkins.class}) + public void testGetRelativeLinkTo_JobContainedInView() throws Exception{ + Jenkins j = createMockJenkins(); + ItemGroup parent = j; + String contextPath = "/jenkins"; + StaplerRequest req = createMockRequest(contextPath); + mockStatic(Stapler.class); + when(Stapler.getCurrentRequest()).thenReturn(req); + View view = mock(View.class); + when(view.getOwnerItemGroup()).thenReturn(parent); + createMockAncestors(req, createAncestor(view, "."), createAncestor(j, "../..")); + TopLevelItem i = createMockItem(parent, "job/i/"); + when(view.getItems()).thenReturn(Arrays.asList(i)); + String result = Functions.getRelativeLinkTo(i); + assertEquals("job/i/", result); + } + + @Test + @PrepareForTest({Stapler.class, Jenkins.class}) + public void testGetRelativeLinkTo_JobNotContainedInView() throws Exception{ + Jenkins j = createMockJenkins(); + ItemGroup parent = j; + String contextPath = "/jenkins"; + StaplerRequest req = createMockRequest(contextPath); + mockStatic(Stapler.class); + when(Stapler.getCurrentRequest()).thenReturn(req); + View view = mock(View.class); + when(view.getOwnerItemGroup()).thenReturn(parent); + createMockAncestors(req, createAncestor(j, "../.."), createAncestor(view, ".")); + TopLevelItem i = createMockItem(parent, "job/i/"); + when(view.getItems()).thenReturn(Collections.emptyList()); + String result = Functions.getRelativeLinkTo(i); + assertEquals("/jenkins/job/i/", result); + } + + private interface TopLevelItemAndItemGroup extends TopLevelItem, ItemGroup {} + + @Test + @PrepareForTest({Stapler.class,Jenkins.class}) + public void testGetRelativeLinkTo_JobContainedInViewWithinItemGroup() throws Exception{ + Jenkins j = createMockJenkins(); + TopLevelItemAndItemGroup parent = mock(TopLevelItemAndItemGroup.class); + when(parent.getShortUrl()).thenReturn("parent/"); + String contextPath = "/jenkins"; + StaplerRequest req = createMockRequest(contextPath); + mockStatic(Stapler.class); + when(Stapler.getCurrentRequest()).thenReturn(req); + View view = mock(View.class); + when(view.getOwnerItemGroup()).thenReturn(parent); + createMockAncestors(req, createAncestor(j, "../../.."), createAncestor(parent, "../.."), createAncestor(view, ".")); + TopLevelItem i = createMockItem(parent, "job/i/", "parent/job/i/"); + when(view.getItems()).thenReturn(Arrays.asList(i)); + String result = Functions.getRelativeLinkTo(i); + assertEquals("job/i/", result); + } + + private void createMockAncestors(StaplerRequest req, Ancestor... ancestors) { + List ancestorsList = Arrays.asList(ancestors); + when(req.getAncestors()).thenReturn(ancestorsList); + } + + private TopLevelItem createMockItem(ItemGroup p, String shortUrl) { + return createMockItem(p, shortUrl, shortUrl); + } + + private TopLevelItem createMockItem(ItemGroup p, String shortUrl, String url) { + TopLevelItem i = mock(TopLevelItem.class); + when(i.getShortUrl()).thenReturn(shortUrl); + when(i.getUrl()).thenReturn(url); + when(i.getParent()).thenReturn(p); + return i; + } + + private Jenkins createMockJenkins() { + mockStatic(Jenkins.class); + Jenkins j = mock(Jenkins.class); + when(Jenkins.getInstance()).thenReturn(j); + return j; + } + + private static Ancestor createAncestor(Object o, String relativePath) { + Ancestor a = mock(Ancestor.class); + when(a.getObject()).thenReturn(o); + when(a.getRelativePath()).thenReturn(relativePath); + return a; + } @Test @PrepareForTest(Stapler.class)