diff --git a/CHANGES.md b/CHANGES.md index 516ea4540..0a5860873 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,6 +14,8 @@ - pat-slideshow-builder - simplePlaceholder from jquery-ext. - IE11 is not supported by default anymore. There is a ``polyfills`` bundle, which adds IE11 support for the time being. +- pat tooltip: Remove undocumented "souce: content-html" parameter. +- pat tooltip: Remove undocumented "souce: auto" parameter. This parameter should not be used as it is not explicit enough and would lead to unintuitive behavior. ### Features diff --git a/src/pat/tooltip/tooltip.js b/src/pat/tooltip/tooltip.js index 1fd19001a..471eb433d 100644 --- a/src/pat/tooltip/tooltip.js +++ b/src/pat/tooltip/tooltip.js @@ -30,13 +30,7 @@ const all_positions = [ parser.addArgument("position-list", [], all_positions, true); parser.addArgument("position-policy", "auto", ["auto", "force"]); parser.addArgument("trigger", "click", ["click", "hover"]); -parser.addArgument("source", "title", [ - "auto", - "ajax", - "content", - "content-html", - "title", -]); +parser.addArgument("source", "title", ["ajax", "content", "title"]); parser.addArgument("ajax-data-type", "html", ["html", "markdown"]); parser.addArgument("closing", "auto", ["auto", "sticky", "close-button"]); parser.addArgument("delay"); @@ -44,8 +38,6 @@ parser.addArgument("mark-inactive", true); parser.addArgument("class"); parser.addArgument("target", "body"); -// parser.addArgument("height", "auto", ["auto", "max"]); - export default Base.extend({ name: "tooltip", trigger: ".pat-tooltip, .pat-tooltip-ng", @@ -94,7 +86,10 @@ export default Base.extend({ el.classList.add("inactive"); } - if (this.options.trigger === "click") { + if ( + this.options.trigger === "click" && + this.options.source === "ajax" + ) { // prevent default action for "click" and "mouseenter click" el.addEventListener("click", (event) => { event.preventDefault(); @@ -173,26 +168,15 @@ export default Base.extend({ }, source: () => { - if (opts.source === "auto") { - const href = this.el.getAttribute("href"); - if (typeof href !== "string") { - log.error( - `href must be specified if 'source' is set to 'auto'` - ); - return; - } - if (href.indexOf("#") === 0) { - opts.source = "content"; - } else { - opts.source = "ajax"; - } - } let content; if (opts.source === "title") { // Tooltip content from title attribute content = this.el.getAttribute("title"); - } - if (["content", "ajax"].includes(opts.source)) { + } else if (opts.source === "content") { + // Tooltiop content from trigger child content. + content = this.el.innerHTML; + tippy_options.allowHTML = true; + } else if (opts.source === "ajax") { // Tooltiop content from AJAX request. content = document.createElement("progress"); tippy_options.allowHTML = true; @@ -276,7 +260,7 @@ export default Base.extend({ this.tippy.setProps({ trigger: "click" }); } - if (["content", "ajax"].includes(this.options.source)) { + if (this.options.source === "ajax") { await this._getContent(); } @@ -321,7 +305,7 @@ export default Base.extend({ this.tippy.setProps({ trigger: "mouseenter focus" }); } - if (["content", "ajax"].includes(this.options.source)) { + if (this.options.source === "ajax") { this.tippy.setContent(document.createElement("progress")); this.ajax_state.canFetch = true; } @@ -334,6 +318,7 @@ export default Base.extend({ const { url, selector, modifier } = this.get_url_parts( this.el.getAttribute("href") ); + let content; if (url) { // Tooltip from remote page. this.ajax_state = { @@ -347,21 +332,18 @@ export default Base.extend({ // TODO: use pat-inject, once it supports async const response = await fetch(url); const text = await response.text(); - const content = handler(text, url, selector, modifier); - this.tippy.setContent(content); + content = handler(text, url, selector, modifier); } catch (e) { log.error(`Error on ajax request ${e}`); } this.ajax_state.isFetching = false; } else if (selector) { // Tooltip content from current DOM tree. - const content = document.querySelector(selector); - if (!content) { - return; - } - this.tippy.setContent(content[modifier]); - } else { - this.tippy.setContent(this.el.innerHTML); + content = document.querySelector(selector); + content = content ? content[modifier] : undefined; + } + if (content) { + this.tippy.setContent(content); } }, diff --git a/src/pat/tooltip/tooltip.test.js b/src/pat/tooltip/tooltip.test.js index 7fbb142d6..3d5d901ea 100644 --- a/src/pat/tooltip/tooltip.test.js +++ b/src/pat/tooltip/tooltip.test.js @@ -834,11 +834,11 @@ describe("pat-tooltip", () => { }); }); describe(`if the 'source' parameter is 'content'`, () => { - it("and the href-hashtag reference cannot be found, it will show the content of the link", async (done) => { + it("it will show the content of the link", async (done) => { const content = "Local content"; const $el = testutils.createTooltip({ data: "source: content; trigger: hover", - href: "#", + href: "#lab", content: content, }); const instance = new pattern($el); @@ -855,29 +855,6 @@ describe("pat-tooltip", () => { document.querySelector(".tippy-box").textContent ).toBe(content); - done(); - }); - it("and the href-reference can be found, it will show that in the modal", async (done) => { - const content = "Local content"; - const $el = testutils.createTooltip({ - data: "source: content; trigger: hover", - href: "#tooltip-source", - }); - testutils.createTooltipSource(); - const instance = new pattern($el); - const spy_show = spyOn( - instance.tippy.props, - "onShow" - ).and.callThrough(); - - testutils.mouseenter($el); - await utils.timeout(1); - - expect(spy_show).toHaveBeenCalled(); - expect( - document.querySelector(".tippy-box strong").textContent - ).toBe(content); - done(); }); }); @@ -945,66 +922,76 @@ describe("pat-tooltip", () => { done(); }); }); - describe(`if the "source" parameter is "auto"`, () => { - describe(`if the "href" points to a document fragment`, () => { - it(`will revert to "content"`, (done) => { - const $el = testutils.createTooltip({ - data: "source: auto", - href: "#tooltip-source", - }); - testutils.createTooltipSource(); - const instance = new pattern($el); - - // options.source is changed to "content" - expect(instance.options.source).toEqual("content"); + }); - done(); - }); + describe("test the different `source` parameters", () => { + it("source: title will use the title attribute", async (done) => { + const $el = testutils.createTooltip({ + data: "source: title; trigger: click", }); - describe(`if the "href" points to an external URL`, () => { - it(`will revert to "ajax"`, (done) => { - const $el = testutils.createTooltip({ - data: "source: auto", - href: "/tests/content.html#content", - }); - const instance = new pattern($el); + const title = $el[0].title; + const instance = new pattern($el); - // options.source is changed to "ajax" - expect(instance.options.source).toEqual("ajax"); + testutils.click($el); + await utils.timeout(1); - done(); - }); + const expected = document.querySelector( + ".tooltip-container .tippy-content" + ).textContent; + expect(expected).toBe(title); + + done(); + }); + + it("source: content use the content of the link", async (done) => { + const content = "Local content"; + const $el = testutils.createTooltip({ + data: "source: content; trigger: click", + content: content, }); + const instance = new pattern($el); + + testutils.click($el); + await utils.timeout(1); + + expect(document.querySelector(".tippy-box").textContent).toBe( + content + ); + + done(); }); - }); - describe(`if the 'source' parameter is 'ajax'`, () => { - it("the default click action is prevented", (done) => { - global.fetch = jest.fn().mockImplementation(mockFetch()); + it("source: ajax and an external url will fetch its contents via ajax", async (done) => { + global.fetch = jest + .fn() + .mockImplementation( + mockFetch("External content fetched via an HTTP request.") + ); const $el = testutils.createTooltip({ data: "source: ajax", - href: "tests/content.html#content", + href: "http://test.com", }); const instance = new pattern($el); - const click = new Event("click"); - - const call_order = []; - spyOn(click, "preventDefault").and.callFake(() => - call_order.push("preventDefault") - ); - spyOn(instance, "_getContent").and.callFake(() => - call_order.push("_getContent") - ); + const spy_content = spyOn( + instance, + "_getContent" + ).and.callThrough(); + const spy_show = spyOn( + instance.tippy.props, + "onShow" + ).and.callThrough(); - $el[0].dispatchEvent(click); - $el[0].dispatchEvent(click); - $el[0].dispatchEvent(click); + testutils.click($el); + await utils.timeout(1); // wait a tick for async fetch - //expect(spy_ajax).toHaveBeenCalledBefore(spy_prevented); - expect(call_order.indexOf("_getContent")).toEqual(0); - expect(call_order.includes("preventDefault")).toBeTruthy(); + expect(global.fetch).toHaveBeenCalled(); + expect(spy_content).toHaveBeenCalled(); + expect(spy_show).toHaveBeenCalled(); + expect( + document.querySelector(".tippy-box .tippy-content").textContent + ).toBe("External content fetched via an HTTP request."); global.fetch.mockClear(); delete global.fetch; @@ -1012,7 +999,7 @@ describe("pat-tooltip", () => { done(); }); - it("will fetch its contents via ajax", async (done) => { + it("source: ajax with a local selector will not use ajax but get the contents from the current DOM", async (done) => { global.fetch = jest .fn() .mockImplementation( @@ -1021,11 +1008,14 @@ describe("pat-tooltip", () => { const $el = testutils.createTooltip({ data: "source: ajax", - href: "http://test.com", + href: "#lab", }); const instance = new pattern($el); - const spy_ajax = spyOn(instance, "_getContent").and.callThrough(); + const spy_content = spyOn( + instance, + "_getContent" + ).and.callThrough(); const spy_show = spyOn( instance.tippy.props, "onShow" @@ -1034,11 +1024,47 @@ describe("pat-tooltip", () => { testutils.click($el); await utils.timeout(1); // wait a tick for async fetch - expect(spy_ajax).toHaveBeenCalled(); + expect(global.fetch).not.toHaveBeenCalled(); + expect(spy_content).toHaveBeenCalled(); expect(spy_show).toHaveBeenCalled(); expect( - document.querySelector(".tippy-box .tippy-content").textContent - ).toBe("External content fetched via an HTTP request."); + document.querySelector(".tippy-box .tippy-content .pat-tooltip") + ).toBeTruthy(); + + global.fetch.mockClear(); + delete global.fetch; + + done(); + }); + }); + + describe(`if the 'source' parameter is 'ajax'`, () => { + it("the default click action is prevented", (done) => { + global.fetch = jest.fn().mockImplementation(mockFetch()); + + const $el = testutils.createTooltip({ + data: "source: ajax", + href: "tests/content.html#content", + }); + const instance = new pattern($el); + const click = new Event("click"); + + const call_order = []; + + spyOn(click, "preventDefault").and.callFake(() => + call_order.push("preventDefault") + ); + spyOn(instance, "_getContent").and.callFake(() => + call_order.push("_getContent") + ); + + $el[0].dispatchEvent(click); + $el[0].dispatchEvent(click); + $el[0].dispatchEvent(click); + + //expect(spy_ajax).toHaveBeenCalledBefore(spy_prevented); + expect(call_order.indexOf("_getContent")).toEqual(0); + expect(call_order.includes("preventDefault")).toBeTruthy(); global.fetch.mockClear(); delete global.fetch; @@ -1262,98 +1288,98 @@ this will be extracted. done(); }); }); + }); - describe("patterns-injected events", () => { - it("it throws the ``patterns-injected`` event", async (done) => { - global.fetch = jest - .fn() - .mockImplementation(mockFetch("External content")); + describe("patterns-injected events", () => { + it("it throws the ``patterns-injected`` event", async (done) => { + global.fetch = jest + .fn() + .mockImplementation(mockFetch("External content")); - let called = false; - $(document.body).on("patterns-injected", () => { - called = true; - }); + let called = false; + $(document.body).on("patterns-injected", () => { + called = true; + }); - const $el = testutils.createTooltip({ - data: "source: ajax; trigger: click", - href: "http://test.com", - }); - const instance = new pattern($el); + const $el = testutils.createTooltip({ + data: "source: ajax; trigger: click", + href: "http://test.com", + }); + const instance = new pattern($el); - testutils.click($el); - await utils.timeout(1); // wait a tick for async fetch + testutils.click($el); + await utils.timeout(1); // wait a tick for async fetch - expect(called).toBeTruthy(); + expect(called).toBeTruthy(); - global.fetch.mockClear(); - delete global.fetch; + global.fetch.mockClear(); + delete global.fetch; - done(); - }); + done(); + }); - it.skip("triggers event handlers in other patterns", async (done) => { - // TODO: fix tests - global.fetch = jest - .fn() - .mockImplementation( - mockFetch(``) - ); + it.skip("triggers event handlers in other patterns", async (done) => { + // TODO: fix tests + global.fetch = jest + .fn() + .mockImplementation( + mockFetch(``) + ); - const form = document.createElement("form"); - form.setAttribute("action", "test.html"); - form.setAttribute("class", "pat-autosubmit"); - document.body.appendChild(form); + const form = document.createElement("form"); + form.setAttribute("action", "test.html"); + form.setAttribute("class", "pat-autosubmit"); + document.body.appendChild(form); - const $el = testutils.createTooltip({ - data: "source: ajax; trigger: click; target: form", - href: "http://test.com", - }); - const instance = new pattern($el); + const $el = testutils.createTooltip({ + data: "source: ajax; trigger: click; target: form", + href: "http://test.com", + }); + const instance = new pattern($el); - const instance2 = new autosubmit($(form)); - const spy_handler1 = spyOn( - instance2, - "refreshListeners" - ).and.callThrough(); - const spy_handler2 = spyOn( - instance2, - "onInputChange" - ).and.callThrough(); + const instance2 = new autosubmit($(form)); + const spy_handler1 = spyOn( + instance2, + "refreshListeners" + ).and.callThrough(); + const spy_handler2 = spyOn( + instance2, + "onInputChange" + ).and.callThrough(); - testutils.click($el); - await utils.timeout(1); // wait a tick for async fetch + testutils.click($el); + await utils.timeout(1); // wait a tick for async fetch - document.body.querySelector("input[name=test]").click(); - await utils.timeout(1); // wait a tick for async fetch + document.body.querySelector("input[name=test]").click(); + await utils.timeout(1); // wait a tick for async fetch - // TODO: check why this isn't called - // manual tests show expected behavior. - expect(spy_handler1).toHaveBeenCalled(); - expect(spy_handler2).toHaveBeenCalled(); + // TODO: check why this isn't called + // manual tests show expected behavior. + expect(spy_handler1).toHaveBeenCalled(); + expect(spy_handler2).toHaveBeenCalled(); - global.fetch.mockClear(); - delete global.fetch; + global.fetch.mockClear(); + delete global.fetch; - done(); - }); + done(); + }); - it("only scans the tooltip content once", async (done) => { - const $el = testutils.createTooltip({ - data: "source: content; trigger: click", - }); - const instance = new pattern($el); + it("only scans the tooltip content once", async (done) => { + const $el = testutils.createTooltip({ + data: "source: content; trigger: click", + }); + const instance = new pattern($el); - const spy_scan = spyOn(registry, "scan"); + const spy_scan = spyOn(registry, "scan"); - testutils.click($el); - await utils.timeout(1); // wait a tick for async fetch + testutils.click($el); + await utils.timeout(1); // wait a tick for async fetch - // Test, if registry.scan isn't invoked twice - another time by - // pat-inject. - expect(spy_scan).toHaveBeenCalledTimes(1); + // Test, if registry.scan isn't invoked twice - another time by + // pat-inject. + expect(spy_scan).toHaveBeenCalledTimes(1); - done(); - }); + done(); }); }); @@ -1417,7 +1443,7 @@ this will be extracted. document.body.appendChild(content); const $el = testutils.createTooltip({ - data: "source: content; trigger: click", + data: "source: ajax; trigger: click", href: "#local-content::element", }); const instance = new pattern($el); @@ -1441,7 +1467,7 @@ this will be extracted. document.body.appendChild(content); const $el = testutils.createTooltip({ - data: "source: content; trigger: click", + data: "source: ajax; trigger: click", href: "#local-content", }); const instance = new pattern($el);