Skip to content

Commit 15bf4f2

Browse files
authored
Merge pull request #1228 from Patternslib/fix-inject
fix inject with non-existing sources, add tests for title insertion
2 parents 7506139 + fcd87d4 commit 15bf4f2

File tree

2 files changed

+203
-19
lines changed

2 files changed

+203
-19
lines changed

src/pat/inject/inject.js

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ const inject = {
6464
// if the injection shall add a history entry and HTML5 pushState
6565
// is missing, then don't initialize the injection.
6666
log.warn("HTML5 pushState is missing, aborting");
67-
return;
67+
return $el;
6868
}
6969
$el.data("pat-inject", cfgs);
7070

@@ -350,6 +350,7 @@ const inject = {
350350
return false;
351351
}
352352
cfg.$target = this.createTarget(cfg.target);
353+
cfg.$created_target = cfg.$target;
353354
}
354355
return true;
355356
},
@@ -453,7 +454,7 @@ const inject = {
453454
return $target;
454455
},
455456

456-
_performInjection(target, $el, $source, cfg, trigger, title) {
457+
_performInjection(target, $el, $source, cfg, trigger, $title) {
457458
/* Called after the XHR has succeeded and we have a new $source
458459
* element to inject.
459460
*/
@@ -484,13 +485,13 @@ const inject = {
484485
// Now the injection actually happens.
485486
if (this._inject(trigger, source_nodes, target, cfg)) {
486487
// Update history
487-
this._update_history(cfg, trigger, title);
488+
this._update_history(cfg, trigger, $title);
488489
// Post-injection
489-
this._afterInjection($el, $(source_nodes), cfg);
490+
this._afterInjection($el, cfg.$created_target || $(source_nodes), cfg);
490491
}
491492
},
492493

493-
_update_history(cfg, trigger, title) {
494+
_update_history(cfg, trigger, $title) {
494495
// History support. if subform is submitted, append form params
495496
if (cfg.history !== "record" || !history?.pushState) {
496497
return;
@@ -502,10 +503,10 @@ const inject = {
502503
}
503504
history.pushState({ url: url }, "", url);
504505
// Also inject title element if we have one
505-
if (title) {
506+
if ($title.length) {
506507
const title_el = document.querySelector("title");
507508
if (title_el) {
508-
this._inject(trigger, title, title_el, {
509+
this._inject(trigger, $title, title_el, {
509510
action: "element",
510511
});
511512
}
@@ -584,14 +585,14 @@ const inject = {
584585
]);
585586
/* pick the title source for dedicated handling later
586587
Title - if present - is always appended at the end. */
587-
let title;
588+
let $title;
588589
if (
589590
sources$ &&
590591
sources$[sources$.length - 1] &&
591592
sources$[sources$.length - 1][0] &&
592593
sources$[sources$.length - 1][0].nodeName === "TITLE"
593594
) {
594-
title = sources$[sources$.length - 1];
595+
$title = sources$[sources$.length - 1];
595596
}
596597
cfgs.forEach((cfg, idx1) => {
597598
const perform_inject = () => {
@@ -603,7 +604,7 @@ const inject = {
603604
sources$[idx1],
604605
cfg,
605606
ev.target,
606-
title
607+
$title
607608
);
608609
}
609610
}
@@ -674,8 +675,8 @@ const inject = {
674675

675676
// clean up
676677
for (const cfg of cfgs) {
677-
if ("$injected" in cfg) {
678-
cfg.$injected.remove();
678+
if ("$created_target" in cfg) {
679+
cfg.$created_target.remove();
679680
}
680681
cfg.$target.removeClass(cfg.loadingClass);
681682
$el.removeClass(cfg.executingClass);
@@ -779,14 +780,14 @@ const inject = {
779780
}
780781
},
781782

782-
_inject(trigger, source, target, cfg) {
783+
_inject(trigger, source_nodes, target, cfg) {
783784
if (cfg.source === "none") {
784785
// Special case. Clear the target after ajax call.
785786
target.replaceWith("");
786787
return true;
787788
}
788-
if (source.length === 0) {
789-
log.warn("Aborting injection, source not found:", source);
789+
if (source_nodes.length === 0) {
790+
log.warn("Aborting injection, source not found:", source_nodes);
790791
$(trigger).trigger("pat-inject-missingSource", {
791792
url: cfg.url,
792793
selector: cfg.source,
@@ -816,7 +817,7 @@ const inject = {
816817
}[cfg.action];
817818

818819
// Inject the content HERE!
819-
target[method](...source);
820+
target[method](...source_nodes);
820821

821822
return true;
822823
},
@@ -966,8 +967,11 @@ const inject = {
966967
let clean_html = html
967968
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "")
968969
.replace(/<head\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/head>/gi, "")
970+
.replace(/<html([^>]*?)>/gi, "")
971+
.replace(/<\/html([^>]*?)>/gi, "")
969972
.replace(/<body([^>]*?)>/gi, '<div id="__original_body">')
970973
.replace(/<\/body([^>]*?)>/gi, "</div>");
974+
971975
if (title && title.length == 2) {
972976
clean_html = title[0] + clean_html;
973977
}
@@ -1108,7 +1112,8 @@ const inject = {
11081112
sources(cfgs, data) {
11091113
const sources = cfgs.map((cfg) => cfg.source);
11101114
sources.push("title");
1111-
return this._sourcesFromHtml(data, cfgs[0].url, sources);
1115+
const result = this._sourcesFromHtml(data, cfgs[0].url, sources);
1116+
return result;
11121117
},
11131118
},
11141119
},

src/pat/inject/inject.test.js

Lines changed: 181 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1557,12 +1557,191 @@ describe("pat-inject", function () {
15571557

15581558
expect(catched).toBe(true);
15591559
expect(pattern.execute).toHaveBeenCalled();
1560-
15611560
});
1562-
15631561
});
15641562
});
15651563
});
1564+
1565+
describe("9.3 - inject with not existing target element", function () {
1566+
let spy_ajax;
1567+
1568+
beforeEach(function () {
1569+
spy_ajax = jest.spyOn($, "ajax").mockImplementation(() => deferred);
1570+
});
1571+
1572+
afterEach(function () {
1573+
spy_ajax.mockRestore();
1574+
});
1575+
1576+
it("9.3.1 - Create non-existing target", async function () {
1577+
// Test a real-world scenario with modals where the target
1578+
// element does not exist and the source is implicitly defined
1579+
// via the href attribue.
1580+
// NOTE: pat-modal does the injecton part on it's own, so you
1581+
// normally use the `pat-modal` class and do not specify a
1582+
// target.
1583+
// For examples, see the pat-modal pattern.
1584+
1585+
document.body.innerHTML = `
1586+
<a class="pat-inject"
1587+
href="test.html#document-content"
1588+
data-pat-inject="target: #pat-modal">link</a>
1589+
`;
1590+
1591+
answer(`
1592+
<html>
1593+
<body>
1594+
<div id="document-content">
1595+
<dialog>
1596+
<h1>hello</h1>
1597+
</dialog>
1598+
</div>
1599+
</body>
1600+
</html>
1601+
`);
1602+
1603+
const inject = document.querySelector(".pat-inject");
1604+
1605+
pattern.init($(inject));
1606+
await utils.timeout(1); // wait a tick for async to settle.
1607+
1608+
inject.click();
1609+
1610+
await utils.timeout(1); // wait a tick for async to settle.
1611+
1612+
console.log(document.body.innerHTML);
1613+
const modal = document.querySelector("#pat-modal");
1614+
expect(modal).toBeTruthy();
1615+
expect(modal.innerHTML.replace(/\s/g, "")).toBe(
1616+
"<dialog><h1>hello</h1></dialog>"
1617+
);
1618+
});
1619+
1620+
it("9.3.2 - Does not create non-existing target without a target specifier", async function () {
1621+
document.body.innerHTML = `
1622+
<a class="pat-inject"
1623+
href="test.html#document-content">link</a>
1624+
`;
1625+
1626+
answer(`
1627+
<html>
1628+
<body>
1629+
<div id="document-content">
1630+
<dialog>
1631+
<h1>hello</h1>
1632+
</dialog>
1633+
</div>
1634+
</body>
1635+
</html>
1636+
`);
1637+
1638+
const inject = document.querySelector(".pat-inject");
1639+
1640+
pattern.init($(inject));
1641+
await utils.timeout(1); // wait a tick for async to settle.
1642+
1643+
inject.click();
1644+
1645+
await utils.timeout(1); // wait a tick for async to settle.
1646+
1647+
console.log(document.body.innerHTML);
1648+
const modal = document.querySelector("#pat-modal");
1649+
expect(modal).toBeFalsy();
1650+
});
1651+
});
1652+
1653+
describe("9.4 - injecton of the title element.", function () {
1654+
let spy_ajax;
1655+
1656+
beforeEach(function () {
1657+
spy_ajax = jest.spyOn($, "ajax").mockImplementation(() => deferred);
1658+
});
1659+
1660+
afterEach(function () {
1661+
spy_ajax.mockRestore();
1662+
});
1663+
1664+
it("9.4.1 - Injects a title element with history:record", async function () {
1665+
document.head.innerHTML = `
1666+
<title>test</title>
1667+
`;
1668+
document.body.innerHTML = `
1669+
<a class="pat-inject"
1670+
href="test.html"
1671+
data-pat-inject="
1672+
source: body;
1673+
target: body;
1674+
history: record;
1675+
">link</a>
1676+
`;
1677+
1678+
answer(`
1679+
<html>
1680+
<head>
1681+
<title>hello</title>
1682+
</head>
1683+
<body>
1684+
OK
1685+
</body>
1686+
</html>
1687+
`);
1688+
1689+
const inject = document.querySelector(".pat-inject");
1690+
1691+
pattern.init($(inject));
1692+
await utils.timeout(1); // wait a tick for async to settle.
1693+
1694+
inject.click();
1695+
1696+
await utils.timeout(1); // wait a tick for async to settle.
1697+
1698+
expect(document.body.textContent.trim()).toBe("OK");
1699+
1700+
const title = document.head.querySelector("title");
1701+
expect(title).toBeTruthy();
1702+
expect(title.textContent.trim()).toBe("hello");
1703+
});
1704+
1705+
it("9.4.2 - Does not inject a title element without history:record", async function () {
1706+
document.head.innerHTML = `
1707+
<title>test</title>
1708+
`;
1709+
document.body.innerHTML = `
1710+
<a class="pat-inject"
1711+
href="test.html"
1712+
data-pat-inject="
1713+
source: body;
1714+
target: body;
1715+
">link</a>
1716+
`;
1717+
1718+
answer(`
1719+
<html>
1720+
<head>
1721+
<title>hello</title>
1722+
</head>
1723+
<body>
1724+
OK
1725+
</body>
1726+
</html>
1727+
`);
1728+
1729+
const inject = document.querySelector(".pat-inject");
1730+
1731+
pattern.init($(inject));
1732+
await utils.timeout(1); // wait a tick for async to settle.
1733+
1734+
inject.click();
1735+
1736+
await utils.timeout(1); // wait a tick for async to settle.
1737+
1738+
expect(document.body.textContent.trim()).toBe("OK");
1739+
1740+
const title = document.head.querySelector("title");
1741+
expect(title).toBeTruthy();
1742+
expect(title.textContent.trim()).toBe("test"); // Old title
1743+
});
1744+
});
15661745
});
15671746

15681747
describe("10 - Error handling", () => {

0 commit comments

Comments
 (0)