htmx/test/tests/unit/swap.js

418 lines
19 KiB
JavaScript

describe('swap() unit tests', function() {
// TODO move to __parseSwapSpec unit test
it('prepend normalizes to afterbegin', function () {
assert.equal(htmx.__parseSwapSpec('prepend').style, 'afterbegin')
})
it('append normalizes to beforeend', function () {
assert.equal(htmx.__parseSwapSpec('append').style, 'beforeend')
})
it('before normalizes to beforebegin', function () {
assert.equal(htmx.__parseSwapSpec('before').style, 'beforebegin')
})
it('after normalizes to afterend', function () {
assert.equal(htmx.__parseSwapSpec('after').style, 'afterend')
})
it('new terminology works with modifiers', function () {
assert.equal(htmx.__parseSwapSpec('prepend swap:10').style, 'afterbegin')
assert.equal(htmx.__parseSwapSpec('prepend swap:10').swap, "10")
assert.equal(htmx.__parseSwapSpec('append swap:20').style, 'beforeend')
assert.equal(htmx.__parseSwapSpec('append swap:20').swap, "20")
})
// end TODO move to __parseSwapSpec unit test
it('swaps in plain content properly', async function () {
await htmx.swap({"target":"#test-playground", "text":"Hello Swap"})
playground().innerText.should.equal("Hello Swap")
})
it('swaps in html content properly', async function () {
await htmx.swap({"target":"#test-playground", "text":"<a>Hello Swap</a>"})
let child = playground().children[0];
child.tagName.should.equal("A");
child.innerText.should.equal("Hello Swap")
})
it('initializes htmx content properly', async function () {
await htmx.swap({"target":"#test-playground", "text":"<a hx-get='/foo'>Hello Swap</a>"})
let child = playground().children[0];
child.tagName.should.equal("A");
child.innerText.should.equal("Hello Swap")
assert.isNotNull(child._htmx);
})
it('swaps in plain content properly w/outerHTML', async function () {
createProcessedHTML("<div id='d1'></div>")
await htmx.swap({"target":"#d1", "text":"Hello Swap", "swap" : "outerHTML"})
playground().innerText.should.equal("Hello Swap")
})
it('swaps in html content properly w/outerHTML', async function () {
createProcessedHTML("<div id='d1'></div>")
await htmx.swap({"target":"#d1", "text":"<span>Hello Swap</span>", "swap" : "outerHTML"})
let child = playground().children[0];
child.tagName.should.equal("SPAN");
child.innerText.should.equal("Hello Swap")
})
it('replaces target element w/outerHTML', async function () {
createProcessedHTML("<div id='d1'></div>")
let original = find('#d1');
await htmx.swap({"target":"#d1", "text":"<span id='d1'>Replaced</span>", "swap" : "outerHTML"})
let replaced = find('#d1');
replaced.should.not.equal(original);
replaced.tagName.should.equal("SPAN");
})
it('inserts before target w/beforebegin', async function () {
createProcessedHTML("<div id='d1'>Target</div>")
await htmx.swap({"target":"#d1", "text":"<span>Before</span>", "swap" : "beforebegin"})
let children = playground().children;
children[0].tagName.should.equal("SPAN");
children[0].innerText.should.equal("Before");
children[1].innerText.should.equal("Target");
})
it('inserts plain text before target w/beforebegin', async function () {
createProcessedHTML("<div id='d1'>Target</div>")
await htmx.swap({"target":"#d1", "text":"Before", "swap" : "beforebegin"})
playground().childNodes[0].textContent.should.equal("Before");
find('#d1').innerText.should.equal("Target");
})
it('prepends content inside target w/afterbegin', async function () {
createProcessedHTML("<div id='d1'><span>Existing</span></div>")
await htmx.swap({"target":"#d1", "text":"<span>First</span>", "swap" : "afterbegin"})
let children = find('#d1').children;
children[0].innerText.should.equal("First");
children[1].innerText.should.equal("Existing");
})
it('prepends plain text inside target w/afterbegin', async function () {
createProcessedHTML("<div id='d1'>Existing</div>")
await htmx.swap({"target":"#d1", "text":"First", "swap" : "afterbegin"})
find('#d1').childNodes[0].textContent.should.equal("First");
})
it('appends content inside target w/beforeend', async function () {
createProcessedHTML("<div id='d1'><span>Existing</span></div>")
await htmx.swap({"target":"#d1", "text":"<span>Last</span>", "swap" : "beforeend"})
let children = find('#d1').children;
children[0].innerText.should.equal("Existing");
children[1].innerText.should.equal("Last");
})
it('appends plain text inside target w/beforeend', async function () {
createProcessedHTML("<div id='d1'>Existing</div>")
await htmx.swap({"target":"#d1", "text":"Last", "swap" : "beforeend"})
let target = find('#d1');
target.childNodes[target.childNodes.length - 1].textContent.should.equal("Last");
})
it('inserts after target w/afterend', async function () {
createProcessedHTML("<div id='d1'>Target</div>")
await htmx.swap({"target":"#d1", "text":"<span>After</span>", "swap" : "afterend"})
let children = playground().children;
children[0].innerText.should.equal("Target");
children[1].tagName.should.equal("SPAN");
children[1].innerText.should.equal("After");
})
it('inserts plain text after target w/afterend', async function () {
createProcessedHTML("<div id='d1'>Target</div>")
await htmx.swap({"target":"#d1", "text":"After", "swap" : "afterend"})
find('#d1').innerText.should.equal("Target");
playground().childNodes[1].textContent.should.equal("After");
})
it('executes script w/innerHTML', async function () {
window.testVar = 0;
await htmx.swap({"target":"#test-playground", "text":"<script>window.testVar = 1</script>"})
window.testVar.should.equal(1);
delete window.testVar;
})
it('executes script w/outerHTML', async function () {
window.testVar = 0;
createProcessedHTML("<div id='d1'></div>")
await htmx.swap({"target":"#d1", "text":"<div><script>window.testVar = 2</script></div>", "swap" : "outerHTML"})
window.testVar.should.equal(2);
delete window.testVar;
})
it('executes script w/beforebegin', async function () {
window.testVar = 0;
createProcessedHTML("<div id='d1'></div>")
await htmx.swap({"target":"#d1", "text":"<script>window.testVar = 3</script>", "swap" : "beforebegin"})
window.testVar.should.equal(3);
delete window.testVar;
})
it('executes script w/afterbegin', async function () {
window.testVar = 0;
createProcessedHTML("<div id='d1'></div>")
await htmx.swap({"target":"#d1", "text":"<script>window.testVar = 4</script>", "swap" : "afterbegin"})
window.testVar.should.equal(4);
delete window.testVar;
})
it('executes script w/beforeend', async function () {
window.testVar = 0;
createProcessedHTML("<div id='d1'></div>")
await htmx.swap({"target":"#d1", "text":"<script>window.testVar = 5</script>", "swap" : "beforeend"})
window.testVar.should.equal(5);
delete window.testVar;
})
it('executes script w/afterend', async function () {
window.testVar = 0;
createProcessedHTML("<div id='d1'></div>")
await htmx.swap({"target":"#d1", "text":"<script>window.testVar = 6</script>", "swap" : "afterend"})
window.testVar.should.equal(6);
delete window.testVar;
})
it('swaps oob content', async function () {
createProcessedHTML("<div id='d1'></div><div id='d2'></div>")
await htmx.swap({"target":"#d1", "text":"<div>Main</div><div id='d2' hx-swap-oob='true'>OOB</div>"})
find('#d1').innerText.trim().should.equal("Main");
find('#d2').innerText.should.equal("OOB");
})
it('swaps oob with outerHTML', async function () {
createProcessedHTML("<div id='d1'></div><div id='d2'></div>")
await htmx.swap({"target":"#d1", "text":"<div>Main</div><div id='d2' hx-swap-oob='outerHTML'>OOB</div>"})
find('#d2').innerText.should.equal("OOB");
})
it('swaps oob with innerHTML', async function () {
createProcessedHTML("<div id='d1'></div><div id='d2'><span>Old</span></div>")
await htmx.swap({"target":"#d1", "text":"<div>Main</div><div id='d2' hx-swap-oob='innerHTML'>OOB</div>"})
find('#d2').innerText.should.equal("OOB");
find('#d2').tagName.should.equal("DIV");
})
it('swaps partial with default target', async function () {
await htmx.swap({"target":"#test-playground", "text":"<hx-partial hx-target='#test-playground'>Partial</hx-partial>"})
playground().innerText.should.equal("Partial");
})
it('swaps partial with custom target', async function () {
createProcessedHTML("<div id='d1'></div><div id='d2'></div>")
await htmx.swap({"target":"#d1", "text":"<hx-partial hx-target='#d2'>Partial</hx-partial>"})
find('#d2').innerText.should.equal("Partial");
})
it('swaps partial with custom swap style', async function () {
createProcessedHTML("<div id='d1'>Existing</div>")
await htmx.swap({"target":"#test-playground", "text":"<hx-partial hx-target='#d1' hx-swap='beforeend'>Partial</hx-partial>"})
find('#d1').innerText.should.equal("ExistingPartial");
})
it('executes script in oob swap', async function () {
window.testVar = 0;
createProcessedHTML("<div id='d1'></div><div id='d2'></div>")
await htmx.swap({"target":"#d1", "text":"<div>Main</div><div id='d2' hx-swap-oob='true'><script>window.testVar = 7</script></div>"})
window.testVar.should.equal(7);
delete window.testVar;
})
it('executes script in partial', async function () {
window.testVar = 0;
await htmx.swap({"target":"#test-playground", "text":"<hx-partial hx-target='#test-playground'><script>window.testVar = 8</script></hx-partial>"})
window.testVar.should.equal(8);
delete window.testVar;
})
it('replaces attributes when swapping element with same id', async function () {
createProcessedHTML("<div id='d1' class='old' data-value='1'></div>")
await htmx.swap({"target":"#d1", "text":"<div id='d1' class='new' data-value='2'>Content</div>", "swap":"outerHTML"})
let replaced = find('#d1');
replaced.getAttribute('class').should.equal('new');
replaced.getAttribute('data-value').should.equal('2');
})
it('triggers CSS transitions during swap', async function () {
this.skip(); //fails on firefox for some reason
createProcessedHTML("<style>#d1 { transition: opacity 100ms; }</style><div id='d1' style='opacity: 1;'>Old</div>")
let transitioned = false;
htmx.on('transitionstart', () => {
transitioned = true;
});
await htmx.swap({"target":"#d1", "text":"<div id='d1' style='opacity: 0.5;'>New</div>", "swap":"outerHTML"})
await htmx.timeout(50);
transitioned.should.be.true;
})
it('triggers htmx:before:swap event', async function () {
let triggered = false;
htmx.on('htmx:before:swap', () => {
triggered = true;
});
await htmx.swap({"target":"#test-playground", "text":"<div>Content</div>"})
triggered.should.be.true;
})
it('triggers htmx:after:swap event', async function () {
let triggered = false;
htmx.on('htmx:after:swap', () => {
triggered = true;
});
await htmx.swap({"target":"#test-playground", "text":"<div>Content</div>"})
triggered.should.be.true;
})
it('triggers htmx:after:settle event', async function () {
let triggered = false;
htmx.on('htmx:after:settle', () => {
triggered = true;
});
await htmx.swap({"target":"#test-playground", "text":"<div>Content</div>"})
triggered.should.be.true;
})
it('triggers view transition events with transition:true', async function () {
htmx.config.logAll = true;
if (!document.startViewTransition) {
this.skip();
return;
}
let beforeTriggered = false;
let afterTriggered = false;
htmx.on('htmx:before:viewTransition', () => {
beforeTriggered = true;
});
htmx.on('htmx:after:viewTransition', () => {
afterTriggered = true;
});
await htmx.swap({"target":"#test-playground", "text":"<div id='d1'>Content</div>", "swap":"innerHTML transition:true"})
beforeTriggered.should.be.true;
afterTriggered.should.be.true;
})
it('sets document title from response', async function () {
let originalTitle = document.title;
await htmx.swap({"target":"#test-playground", "text":"<html><head><title>New Title</title></head><body><div>Content</div></body></html>"})
document.title.should.equal('New Title');
document.title = originalTitle;
})
it('ignores title when ignoreTitle:true modifier is set', async function () {
let originalTitle = document.title;
await htmx.swap({"target":"#test-playground", "text":"<html><head><title>Ignored Title</title></head><body><div>Content</div></body></html>", "swap":"innerHTML ignoreTitle:true"})
document.title.should.equal(originalTitle);
})
it('sets title from fragment without html/body tags', async function () {
let originalTitle = document.title;
await htmx.swap({"target":"#test-playground", "text":"<title>Fragment Title</title><div>Content</div>"})
document.title.should.equal('Fragment Title');
document.title = originalTitle;
})
it('does not set title when response has no title tag', async function () {
let originalTitle = document.title;
await htmx.swap({"target":"#test-playground", "text":"<div>Content without title</div>"})
document.title.should.equal(originalTitle);
})
it('sets title with oob swap', async function () {
let originalTitle = document.title;
createProcessedHTML("<div id='d1'></div><div id='d2'></div>")
await htmx.swap({"target":"#d1", "text":"<title>OOB Title</title><div>Main</div><div id='d2' hx-swap-oob='true'>OOB</div>"})
document.title.should.equal('OOB Title');
document.title = originalTitle;
})
it('sets title with partial swap', async function () {
let originalTitle = document.title;
createProcessedHTML("<div id='d1'></div>")
await htmx.swap({"target":"#test-playground", "text":"<title>Partial Title</title><hx-partial hx-target='#d1'>Partial Content</hx-partial>"})
document.title.should.equal('Partial Title');
document.title = originalTitle;
})
it('sets title from body tag response', async function () {
let originalTitle = document.title;
await htmx.swap({"target":"#test-playground", "text":"<body><title>Body Title</title><div>Content</div></body>"})
document.title.should.equal('Body Title');
document.title = originalTitle;
})
it('decodes HTML entities in title', async function () {
let originalTitle = document.title;
await htmx.swap({"target":"#test-playground", "text":"<title>&lt;/&gt; htmx &amp; friends</title><div>Content</div>"})
document.title.should.equal('</> htmx & friends');
document.title = originalTitle;
})
it('does not swap title tag into page content', async function () {
await htmx.swap({"target":"#test-playground", "text":"<title>Test Title</title><div id='content'>Main Content</div>"})
assert.isNull(playground().querySelector('title'));
find('#content').innerText.should.equal('Main Content');
})
it('supports autofocus', async function () {
let originalTitle = document.title;
await htmx.swap({"target":"#test-playground", "text":"<input id='i1' autofocus>"})
document.activeElement.id.should.equal("i1")
})
it('swaps both main target and partial target when both are present', async function () {
createProcessedHTML("<div id='target'>Hello</div><div id='target_oob'>OOB</div>")
await htmx.swap({
"target":"#target",
"text":"<div>Hello me!</div><hx-partial hx-target='#target_oob' hx-swap='innerHTML'><div>OOB swap!</div></hx-partial>"
})
find('#target').innerText.should.equal("Hello me!");
find('#target_oob').innerText.should.equal("OOB swap!");
})
it('swaps only partial target when response contains only partial', async function () {
createProcessedHTML("<div id='target'>Original</div><div id='target_oob'>OOB Original</div>")
await htmx.swap({
"target":"#target",
"text":"<hx-partial hx-target='#target_oob' hx-swap='innerHTML'><div>OOB Updated</div></hx-partial>"
})
find('#target').innerText.should.equal("Original");
find('#target_oob').innerText.should.equal("OOB Updated");
})
it('does not swap main target when only whitespace and partial present', async function () {
createProcessedHTML("<div id='target'>Original</div><div id='target_oob'>OOB</div>")
await htmx.swap({
"target":"#target",
"text":"\n <hx-partial hx-target='#target_oob' hx-swap='innerHTML'><div>OOB swap!</div></hx-partial> \n"
})
find('#target').innerText.should.equal("Original");
find('#target_oob').innerText.should.equal("OOB swap!");
})
it('swaps both targets when empty element and partial present', async function () {
createProcessedHTML("<div id='target'>Original</div><div id='target_oob'>OOB</div>")
await htmx.swap({
"target":"#target",
"text":"<p></p><hx-partial hx-target='#target_oob' hx-swap='innerHTML'><div>OOB swap!</div></hx-partial>"
})
find('#target').querySelector('p').should.not.be.null;
find('#target_oob').innerText.should.equal("OOB swap!");
})
it('swaps both targets when plain text and partial present', async function () {
createProcessedHTML("<div id='target'>Original</div><div id='target_oob'>OOB</div>")
await htmx.swap({
"target":"#target",
"text":"Hello<hx-partial hx-target='#target_oob' hx-swap='innerHTML'><div>OOB swap!</div></hx-partial>"
})
find('#target').textContent.should.equal("Hello");
find('#target_oob').innerText.should.equal("OOB swap!");
})
})