describe('hx-swap-oob attribute', function() { const savedConfig = htmx.config beforeEach(function() { this.server = makeServer() htmx.config = Object.assign({}, savedConfig) clearWorkArea() }) afterEach(function() { this.server.restore() htmx.config = savedConfig clearWorkArea() }) // Repeat the same test to make sure it works with different configurations for (const config of [{ allowNestedOobSwaps: true }, { allowNestedOobSwaps: false }]) { it('handles basic response properly with config ' + JSON.stringify(config), function() { Object.assign(htmx.config, config) this.server.respondWith('GET', '/test', "Clicked
Swapped0
") var div = make('
click me
') make('
') div.click() this.server.respond() div.innerHTML.should.equal('Clicked') byId('d1').innerHTML.should.equal('Swapped0') }) } for (const config of [{ allowNestedOobSwaps: true }, { allowNestedOobSwaps: false }]) { it('oob swap works when the response has a body tag with config ' + JSON.stringify(config), function() { Object.assign(htmx.config, config) this.server.respondWith('GET', '/test', "Clicked
Swapped0
") var div = make('
click me
') make('
') div.click() this.server.respond() div.innerHTML.should.equal('Clicked') byId('d2').innerHTML.should.equal('Swapped0') }) } for (const config of [{ allowNestedOobSwaps: true }, { allowNestedOobSwaps: false }]) { it('oob swap works when the response has html and body tags with config ' + JSON.stringify(config), function() { Object.assign(htmx.config, config) this.server.respondWith('GET', '/test', "Clicked
Swapped0
") var div = make('
click me
') make('
') div.click() this.server.respond() div.innerHTML.should.equal('Clicked') byId('d3').innerHTML.should.equal('Swapped0') }) } for (const config of [{ allowNestedOobSwaps: true }, { allowNestedOobSwaps: false }]) { it('handles more than one oob swap properly with config ' + JSON.stringify(config), function() { Object.assign(htmx.config, config) this.server.respondWith('GET', '/test', "Clicked
Swapped1
Swapped2
") var div = make('
click me
') make('
') make('
') div.click() this.server.respond() div.innerHTML.should.equal('Clicked') byId('d1').innerHTML.should.equal('Swapped1') byId('d2').innerHTML.should.equal('Swapped2') }) } it('handles remvoing hx-swap-oob tag', function() { this.server.respondWith('GET', '/test', "Clicked
Swapped3
") var div = make('
click me
') make('
') div.click() this.server.respond() div.innerHTML.should.equal('Clicked') byId('d1').innerHTML.should.equal('Swapped3') byId('d1').hasAttribute('hx-swap-oob').should.equal(false) }) it('handles remvoing data-hx-swap-oob tag', function() { this.server.respondWith('GET', '/test', "Clicked
Swapped3
") var div = make('
click me
') make('
') div.click() this.server.respond() div.innerHTML.should.equal('Clicked') byId('d1').innerHTML.should.equal('Swapped3') byId('d1').hasAttribute('data-hx-swap-oob').should.equal(false) }) it('handles no id match properly', function() { this.server.respondWith('GET', '/test', "Clicked
Swapped2
") var div = make('
click me
') div.click() this.server.respond() div.innerText.should.equal('Clicked') }) it('handles basic response properly w/ data-* prefix', function() { this.server.respondWith('GET', '/test', "Clicked
Swapped3
") var div = make('
click me
') make('
') div.click() this.server.respond() div.innerHTML.should.equal('Clicked') byId('d1').innerHTML.should.equal('Swapped3') }) it('handles outerHTML response properly', function() { this.server.respondWith('GET', '/test', "Clicked
Swapped4
") var div = make('
click me
') make('
') div.click() this.server.respond() byId('d1').getAttribute('foo').should.equal('bar') div.innerHTML.should.equal('Clicked') byId('d1').innerHTML.should.equal('Swapped4') }) it('handles innerHTML response properly', function() { this.server.respondWith('GET', '/test', "Clicked
Swapped5
") var div = make('
click me
') make('
') div.click() this.server.respond() should.equal(byId('d1').getAttribute('foo'), null) div.innerHTML.should.equal('Clicked') byId('d1').innerHTML.should.equal('Swapped5') }) it('oob swaps can be nested in content with config {"allowNestedOobSwaps": true}', function() { htmx.config.allowNestedOobSwaps = true this.server.respondWith('GET', '/test', "
Clicked
Swapped6
") var div = make('
click me
') make('
') div.click() this.server.respond() should.equal(byId('d1').getAttribute('foo'), null) div.innerHTML.should.equal('
Clicked
') byId('d1').innerHTML.should.equal('Swapped6') }) it('oob swaps in nested content are ignored and stripped with config {"allowNestedOobSwaps": false}', function() { htmx.config.allowNestedOobSwaps = false this.server.respondWith('GET', '/test', '
Clicked
Swapped6.1
') var div = make('
click me
') make('
') div.click() this.server.respond() byId('d1').innerHTML.should.equal('') div.innerHTML.should.equal('
Clicked
Swapped6.1
') }) it('oob swaps can use selectors to match up', function() { this.server.respondWith('GET', '/test', "
Clicked
Swapped7
") var div = make('
click me
') make('
') div.click() this.server.respond() should.equal(byId('d1').getAttribute('oob-foo'), 'bar') div.innerHTML.should.equal('
Clicked
') byId('d1').innerHTML.should.equal('Swapped7') }) it('swaps into all targets that match the selector (innerHTML)', function() { this.server.respondWith('GET', '/test', "
Clicked
Swapped8
") var div = make('
click me
') make('
No swap
') make('
Not swapped
') make('
Not swapped
') div.click() this.server.respond() byId('d1').innerHTML.should.equal('No swap') byId('d2').innerHTML.should.equal('Swapped8') byId('d3').innerHTML.should.equal('Swapped8') }) it('swaps into all targets that match the selector (outerHTML)', function() { var oobSwapContent = '
Swapped9
' var finalContent = '
Swapped9
' this.server.respondWith('GET', '/test', '
Clicked
' + oobSwapContent) var div = make('
click me
') make('
No swap
') make('
Not swapped
') make('
Not swapped
') div.click() this.server.respond() byId('d1').innerHTML.should.equal('
No swap
') byId('d2').innerHTML.should.equal(finalContent) byId('d3').innerHTML.should.equal(finalContent) }) it('oob swap delete works properly', function() { this.server.respondWith('GET', '/test', '
') var div = make('
Foo
') div.click() this.server.respond() should.equal(byId('d1'), null) }) it('oob swap removes templates used for oob encapsulation only properly', function() { this.server.respondWith('GET', '/test', '' + 'Clicked') var div = make('' + '
') var btn = byId('b1') btn.click() this.server.respond() should.equal(byId('b1').innerHTML, 'Clicked') should.equal(byId('d1').innerHTML, 'Foo') }) it('oob swap keeps templates not used for oob swap encapsulation', function() { this.server.respondWith('GET', '/test', '' + 'Clicked') var div = make('' + '
') var btn = byId('b1') btn.click() this.server.respond() should.equal(byId('b1').innerHTML, 'Clicked') should.equal(byId('d1').innerHTML, '') }) for (const config of [{ allowNestedOobSwaps: true }, { allowNestedOobSwaps: false }]) { it('oob swap supports table row in fragment along other oob swap elements with config ' + JSON.stringify(config), function() { Object.assign(htmx.config, config) this.server.respondWith('GET', '/test', `Clicked
Test
`) make(`
Bar
foo
Bar
`) var btn = make('') btn.click() this.server.respond() btn.innerText.should.equal('Clicked') byId('r1').innerHTML.should.equal('bar') byId('b2').innerHTML.should.equal('Another button') byId('d1').innerHTML.should.equal('Test') byId('td1').innerHTML.should.equal('hey') }) } for (const config of [{ allowNestedOobSwaps: true }, { allowNestedOobSwaps: false }]) { it('handles oob target in web components with both inside shadow root and config ' + JSON.stringify(config), function() { this.server.respondWith('GET', '/test', '
new contents
Clicked') class TestElement extends HTMLElement { connectedCallback() { const root = this.attachShadow({ mode: 'open' }) root.innerHTML = `
this should get swapped
` htmx.process(root) // Tell HTMX about this component's shadow DOM } } var elementName = 'test-oobswap-inside-' + config.allowNestedOobSwaps customElements.define(elementName, TestElement) var div = make(`
this should not get swapped
<${elementName}/>
`) var badTarget = div.querySelector('#oob-swap-target') var webComponent = div.querySelector(elementName) var btn = webComponent.shadowRoot.querySelector('button') var goodTarget = webComponent.shadowRoot.querySelector('#oob-swap-target') var mainTarget = webComponent.shadowRoot.querySelector('#main-target') btn.click() this.server.respond() should.equal(mainTarget.textContent, 'Clicked') should.equal(goodTarget.textContent, 'new contents') should.equal(badTarget.textContent, 'this should not get swapped') }) } for (const config of [{ allowNestedOobSwaps: true }, { allowNestedOobSwaps: false }]) { it('handles oob target in web components with main target outside web component config ' + JSON.stringify(config), function() { this.server.respondWith('GET', '/test', '
new contents
Clicked') class TestElement extends HTMLElement { connectedCallback() { const root = this.attachShadow({ mode: 'open' }) root.innerHTML = `
this should get swapped
` htmx.process(root) // Tell HTMX about this component's shadow DOM } } var elementName = 'test-oobswap-global-main-' + config.allowNestedOobSwaps customElements.define(elementName, TestElement) var div = make(`
this should not get swapped
<${elementName}/>
`) var badTarget = div.querySelector('#oob-swap-target') var webComponent = div.querySelector(elementName) var btn = webComponent.shadowRoot.querySelector('button') var goodTarget = webComponent.shadowRoot.querySelector('#oob-swap-target') var mainTarget = div.querySelector('#main-target') btn.click() this.server.respond() should.equal(mainTarget.textContent, 'Clicked') should.equal(goodTarget.textContent, 'new contents') should.equal(badTarget.textContent, 'this should not get swapped') }) } for (const config of [{ allowNestedOobSwaps: true }, { allowNestedOobSwaps: false }]) { it('handles global oob target in web components with main target inside web component config ' + JSON.stringify(config), function() { this.server.respondWith('GET', '/test', '
new contents
Clicked') class TestElement extends HTMLElement { connectedCallback() { const root = this.attachShadow({ mode: 'open' }) root.innerHTML = `
this should not get swapped
` htmx.process(root) // Tell HTMX about this component's shadow DOM } } var elementName = 'test-oobswap-global-oob-' + config.allowNestedOobSwaps customElements.define(elementName, TestElement) var div = make(`
this should get swapped
<${elementName}/>
`) var webComponent = div.querySelector(elementName) var badTarget = webComponent.shadowRoot.querySelector('#oob-swap-target') var btn = webComponent.shadowRoot.querySelector('button') var goodTarget = div.querySelector('#oob-swap-target') var mainTarget = webComponent.shadowRoot.querySelector('#main-target') btn.click() this.server.respond() should.equal(mainTarget.textContent, 'Clicked') should.equal(goodTarget.textContent, 'new contents') should.equal(badTarget.textContent, 'this should not get swapped') }) } it.skip('triggers htmx:oobErrorNoTarget when no targets found', function(done) { // this test fails right now because when targets not found it returns an empty array which makes it miss the event as it should be if (targets.lenght) this.server.respondWith('GET', '/test', "Clicked
Swapped
") var div = make('
click me
') // Define the event listener function so it can be removed later var eventListenerFunction = function(event) { event.detail.content.innerHTML.should.equal('Swapped') document.body.removeEventListener('htmx:oobErrorNoTarget', eventListenerFunction) done() } document.body.addEventListener('htmx:oobErrorNoTarget', eventListenerFunction) div.click() this.server.respond() }) })