diff --git a/litmus/bugs/link-target.js b/litmus/bugs/link-target.js new file mode 100644 index 000000000..00c1b233b --- /dev/null +++ b/litmus/bugs/link-target.js @@ -0,0 +1,21 @@ +// Link and LinkButton with a local href and a target other than _self. The browser should follow +// them: _blank opens a new tab, a named target opens (and then reuses) a tab with that name, and +// the url shown below stays the same. A link without a target navigates inside the app. +// +// Run: import Demo from "./bugs/link-target"; in litmus/index.js +import { Link, LinkButton } from "cx/widgets"; + +export default ( + +
+
+ Current url: +
+ + + + + +
+
+); diff --git a/litmus/index.js b/litmus/index.js index eadadfe6e..c7a41ad9d 100644 --- a/litmus/index.js +++ b/litmus/index.js @@ -141,7 +141,8 @@ import "./index.scss"; // import Demo from "./features/charts/line-graph/LineGraph"; // import Demo from "./features/charts/line-graph/SmoothingOvershoot"; //import Demo from "./features/window/resize-cursor"; -import Demo from "./features/dropdown/lookup-inside-iframe-portal"; +// import Demo from "./features/dropdown/lookup-inside-iframe-portal"; +import Demo from "./bugs/link-target"; // import Demo from "./features/dropdown/lookup-inside-real-iframe"; // import Demo from "./features/dropdown/context-menu-iframe-focus"; // import Demo from "./bugs/GridDefaultSortFieldClearableSortIssue"; diff --git a/packages/cx/src/widgets/nav/Link.spec.tsx b/packages/cx/src/widgets/nav/Link.spec.tsx index c013643a3..59e4badd1 100644 --- a/packages/cx/src/widgets/nav/Link.spec.tsx +++ b/packages/cx/src/widgets/nav/Link.spec.tsx @@ -1,5 +1,6 @@ import { Link } from "./Link"; import { Store } from "../../data/Store"; +import { History } from "../../ui/app/History"; import { createModel } from "../../data/createAccessorModelProxy"; import { createTestRenderer } from "../../util/test/createTestRenderer"; import assert from "assert"; @@ -110,6 +111,46 @@ describe("Link", () => { }); }); + describe("click on a local href", () => { + let pushState = History.pushState; + let pushed: string[]; + + beforeEach(() => { + pushed = []; + History.pushState = (state, title, url) => { + pushed.push(url); + return true; + }; + }); + + afterEach(() => { + History.pushState = pushState; + }); + + async function click(target?: string) { + let store = new Store(); + let component = await createTestRenderer( + store, + + + , + ); + let prevented = false; + findAnchor(component.toJSON()).props.onClick({ preventDefault: () => (prevented = true) }); + return prevented; + } + + it("navigates in the app without a target", async () => { + assert.equal(await click(), true); + assert.deepEqual(pushed, ["/reports"]); + }); + + it("leaves target=_blank to the browser", async () => { + assert.equal(await click("_blank"), false); + assert.deepEqual(pushed, []); + }); + }); + describe("active override", () => { it("true wins over a non-matching url", async () => { assert.equal(await isActive("~/other", { href: "~/items", active: true }), true); diff --git a/packages/cx/src/widgets/nav/LinkButton.ts b/packages/cx/src/widgets/nav/LinkButton.ts index 695511a77..d29573a2b 100644 --- a/packages/cx/src/widgets/nav/LinkButton.ts +++ b/packages/cx/src/widgets/nav/LinkButton.ts @@ -168,7 +168,14 @@ export class LinkButton extends Button { if (this.onClick && instance.invoke("onClick", e, instance) === false) return; - if (data.href && Url.isLocal(data.href) && !e.ctrlKey && !e.shiftKey && !e.metaKey) { + if ( + data.href && + Url.isLocal(data.href) && + (!data.target || data.target === "_self") && + !e.ctrlKey && + !e.shiftKey && + !e.metaKey + ) { e.preventDefault(); History.pushState({}, null, data.href); }