Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/core/plugins/deep-linking/helpers.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
export const setHash = (value) => {
if(value) {
return history.pushState(null, null, `#${value}`)
// Include the current pathname and search explicitly so that a
// document-level `<base href>` (set by frameworks like Angular) does not
// cause `pushState` to resolve `#value` against the base URL and replace
// the current route. See https://github.com/swagger-api/swagger-ui/issues/10591
const { pathname, search } = window.location
return history.pushState(null, null, `${pathname}${search}#${value}`)
} else {
return window.location.hash = ""
}
Expand Down
33 changes: 33 additions & 0 deletions test/e2e-cypress/e2e/bugs/10591.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
describe("#10591: deep-linking with document <base href> should not replace current route", () => {
const baseUrl = "/?deepLinking=true&url=/documents/features/deep-linking.openapi.yaml"

it("should preserve the page pathname and search when an operation is expanded", () => {
cy.intercept(
{
method: "GET",
pathname: "/",
},
(req) => {
req.continue((res) => {
if (typeof res.body === "string" && res.body.includes("</head>")) {
res.body = res.body.replace("</head>", "<base href=\"/\">\n</head>")
}
})
}
).as("indexHtml")

cy.visit(baseUrl)
cy.wait("@indexHtml")
cy.get(".opblock-get").first().click()

cy.location().should((loc) => {
// The URL hash must change to reflect the deep link...
expect(loc.hash).to.not.equal("")
// ...but the pathname (and search) must NOT have been replaced by the
// browser resolving "#..." against the document <base href="/">.
expect(loc.pathname).to.equal("/")
expect(loc.search).to.contain("deepLinking=true")
expect(loc.search).to.contain("url=/documents/features/deep-linking.openapi.yaml")
})
})
})
63 changes: 63 additions & 0 deletions test/unit/core/plugins/deep-linking/helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* @prettier
*/
import { setHash } from "core/plugins/deep-linking/helpers"

describe("deep-linking plugin - helpers", () => {
describe("setHash", () => {
let originalLocation
let pushStateSpy

beforeEach(() => {
originalLocation = window.location
pushStateSpy = jest
.spyOn(window.history, "pushState")
.mockImplementation(() => {})
})

afterEach(() => {
pushStateSpy.mockRestore()
Object.defineProperty(window, "location", {
configurable: true,
value: originalLocation,
})
})

const stubLocation = (pathname, search) => {
Object.defineProperty(window, "location", {
configurable: true,
value: { ...originalLocation, pathname, search, hash: "" },
})
}

it("should preserve the current pathname and search when pushing a hash", () => {
stubLocation("/app/api-docs", "?env=staging")

setHash("/myTag/myOperation")

expect(pushStateSpy).toHaveBeenCalledWith(
null,
null,
"/app/api-docs?env=staging#/myTag/myOperation"
)
})

it("should still push a hash when the current URL has no search string", () => {
stubLocation("/swagger", "")

setHash("/foo")

expect(pushStateSpy).toHaveBeenCalledWith(null, null, "/swagger#/foo")
})

it("should clear window.location.hash when called with an empty value", () => {
stubLocation("/swagger", "")
window.location.hash = "#/foo"

setHash("")

expect(window.location.hash).toBe("")
expect(pushStateSpy).not.toHaveBeenCalled()
})
})
})