mirror of
https://github.com/bigskysoftware/htmx.git
synced 2026-04-19 05:16:13 +00:00
unit tests for remaining public methods
This commit is contained in:
@@ -93,6 +93,7 @@
|
||||
<!-- Unit tests first -->
|
||||
<script src="tests/unit/__attributeValue.js"></script>
|
||||
<script src="tests/unit/__collectFormData.js"></script>
|
||||
<script src="tests/unit/__extractFilter.js"></script>
|
||||
<script src="tests/unit/__findAllExt.js"></script>
|
||||
<script src="tests/unit/__initializeTriggers.js"></script>
|
||||
<script src="tests/unit/__makeFragment.js"></script>
|
||||
@@ -105,10 +106,15 @@
|
||||
<script src="tests/unit/ajax.js"></script>
|
||||
<script src="tests/unit/core.js"></script>
|
||||
<script src="tests/unit/find.js"></script>
|
||||
<script src="tests/unit/forEvent.js"></script>
|
||||
<script src="tests/unit/htmx.config.prefix.js"></script>
|
||||
<script src="tests/unit/morph.js"></script>
|
||||
<script src="tests/unit/on.js"></script>
|
||||
<script src="tests/unit/parseInterval.js"></script>
|
||||
<script src="tests/unit/process.js"></script>
|
||||
<script src="tests/unit/swap.js"></script>
|
||||
<script src="tests/unit/timeout.js"></script>
|
||||
<script src="tests/unit/trigger.js"></script>
|
||||
|
||||
<!-- Fast attribute tests -->
|
||||
<script src="tests/direct/hx-get.js"></script>
|
||||
|
||||
63
test/tests/unit/__extractFilter.js
Normal file
63
test/tests/unit/__extractFilter.js
Normal file
@@ -0,0 +1,63 @@
|
||||
describe('__extractFilter unit tests', function() {
|
||||
|
||||
it('returns event name and null when no filter', function () {
|
||||
let [event, filter] = htmx.__extractFilter('click')
|
||||
assert.equal(event, 'click')
|
||||
assert.equal(filter, null)
|
||||
})
|
||||
|
||||
it('extracts filter from brackets', function () {
|
||||
let [event, filter] = htmx.__extractFilter('click[ctrlKey]')
|
||||
assert.equal(event, 'click')
|
||||
assert.equal(filter, 'ctrlKey')
|
||||
})
|
||||
|
||||
it('handles empty brackets', function () {
|
||||
let [event, filter] = htmx.__extractFilter('click[]')
|
||||
assert.equal(event, 'click')
|
||||
assert.equal(filter, '')
|
||||
})
|
||||
|
||||
it('handles complex filter expression', function () {
|
||||
let [event, filter] = htmx.__extractFilter('keydown[key==\'Enter\']')
|
||||
assert.equal(event, 'keydown')
|
||||
assert.equal(filter, 'key==\'Enter\'')
|
||||
})
|
||||
|
||||
it('returns only first bracket match', function () {
|
||||
let [event, filter] = htmx.__extractFilter('click[foo][bar]')
|
||||
assert.equal(event, 'click')
|
||||
assert.equal(filter, 'foo')
|
||||
})
|
||||
|
||||
it('handles event name with spaces before bracket', function () {
|
||||
let [event, filter] = htmx.__extractFilter('my event[filter]')
|
||||
assert.equal(event, 'my event')
|
||||
assert.equal(filter, 'filter')
|
||||
})
|
||||
|
||||
it('handles filter with spaces', function () {
|
||||
let [event, filter] = htmx.__extractFilter('click[a && b]')
|
||||
assert.equal(event, 'click')
|
||||
assert.equal(filter, 'a && b')
|
||||
})
|
||||
|
||||
it('returns original string when only opening bracket', function () {
|
||||
let [event, filter] = htmx.__extractFilter('click[')
|
||||
assert.equal(event, 'click[')
|
||||
assert.equal(filter, null)
|
||||
})
|
||||
|
||||
it('returns original string when only closing bracket', function () {
|
||||
let [event, filter] = htmx.__extractFilter('click]')
|
||||
assert.equal(event, 'click]')
|
||||
assert.equal(filter, null)
|
||||
})
|
||||
|
||||
it('handles empty string', function () {
|
||||
let [event, filter] = htmx.__extractFilter('')
|
||||
assert.equal(event, '')
|
||||
assert.equal(filter, null)
|
||||
})
|
||||
|
||||
});
|
||||
@@ -241,13 +241,13 @@ describe('Unit Tests', function() {
|
||||
'find',
|
||||
'findAll',
|
||||
'forEvent',
|
||||
'on',
|
||||
'parseInterval',
|
||||
'process',
|
||||
'swap',
|
||||
'timeout',
|
||||
'defineExtension',
|
||||
'trigger',
|
||||
'waitATick'
|
||||
].sort();
|
||||
|
||||
const expectedPublicProperties = [
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
describe('find functions', function() {
|
||||
|
||||
it('find() returns first matching element', function() {
|
||||
createProcessedHTML('<div class="foo"></div><div class="foo"></div>');
|
||||
const result = htmx.find('.foo');
|
||||
assert.equal(result.className, 'foo');
|
||||
});
|
||||
|
||||
it('findAll() returns all matching elements', function() {
|
||||
createProcessedHTML('<div class="foo"></div><div class="foo"></div>');
|
||||
const results = htmx.findAll('.foo');
|
||||
assert.equal(results.length, 2);
|
||||
});
|
||||
|
||||
it('_findExt() finds with closest selector', function() {
|
||||
const child = createProcessedHTML('<div class="parent"><span class="child"></span></div>').querySelector('.child');
|
||||
const result = htmx.__findExt(child, 'closest .parent');
|
||||
assert.equal(result.className, 'parent');
|
||||
});
|
||||
|
||||
it('_findExt() finds with next selector', function() {
|
||||
createProcessedHTML('<div id="first"></div><div id="second"></div>');
|
||||
const first = document.getElementById('first');
|
||||
const result = htmx.__findExt(first, 'next');
|
||||
assert.equal(result.id, 'second');
|
||||
});
|
||||
|
||||
it('_findExt() finds with previous selector', function() {
|
||||
createProcessedHTML('<div id="first"></div><div id="second"></div>');
|
||||
const second = document.getElementById('second');
|
||||
const result = htmx.__findExt(second, 'previous');
|
||||
assert.equal(result.id, 'first');
|
||||
});
|
||||
|
||||
it('_findExt() finds with hyperscript-style selector', function() {
|
||||
const div = createProcessedHTML('<div><span class="target"></span></div>');
|
||||
const result = htmx.__findExt(div, '<.target/>');
|
||||
assert.equal(result.className, 'target');
|
||||
});
|
||||
|
||||
it('__findAllExt() returns array with multiple selectors', function() {
|
||||
createProcessedHTML('<div class="a"></div><div class="b"></div>');
|
||||
const results = htmx.__findAllExt(document, '.a,.b');
|
||||
assert.equal(results.length, 2);
|
||||
});
|
||||
|
||||
it('__findAllExt() handles next with query', function() {
|
||||
createProcessedHTML('<div id="start"></div><span></span><div class="target"></div>');
|
||||
const start = document.getElementById('start');
|
||||
const results = htmx.__findAllExt(start, 'next .target');
|
||||
assert.equal(results[0].className, 'target');
|
||||
});
|
||||
|
||||
it('__findAllExt() handles previous with query', function() {
|
||||
createProcessedHTML('<div class="target"></div><span></span><div id="start"></div>');
|
||||
const start = document.getElementById('start');
|
||||
const results = htmx.__findAllExt(start, 'previous .target');
|
||||
assert.equal(results[0].className, 'target');
|
||||
});
|
||||
|
||||
it('__tokenizeExtendedSelector() splits by comma', function() {
|
||||
const parts = htmx.__tokenizeExtendedSelector('.foo,.bar');
|
||||
assert.equal(parts.length, 2);
|
||||
assert.equal(parts[0], '.foo');
|
||||
assert.equal(parts[1], '.bar');
|
||||
});
|
||||
|
||||
it('__tokenizeExtendedSelector() respects angle brackets', function() {
|
||||
const parts = htmx.__tokenizeExtendedSelector('<.foo,.bar/>,.baz');
|
||||
assert.equal(parts.length, 2);
|
||||
assert.equal(parts[0], '<.foo,.bar/>');
|
||||
assert.equal(parts[1], '.baz');
|
||||
});
|
||||
|
||||
it('__scanForwardQuery() finds next matching element', function() {
|
||||
createProcessedHTML('<div id="start"></div><span></span><div class="target"></div>');
|
||||
const start = document.getElementById('start');
|
||||
const result = htmx.__scanForwardQuery(start, '.target');
|
||||
assert.equal(result.className, 'target');
|
||||
});
|
||||
|
||||
it('__scanBackwardsQuery() finds previous matching element', function() {
|
||||
createProcessedHTML('<div class="target"></div><span></span><div id="start"></div>');
|
||||
const start = document.getElementById('start');
|
||||
const result = htmx.__scanBackwardsQuery(start, '.target');
|
||||
assert.equal(result.className, 'target');
|
||||
});
|
||||
|
||||
});
|
||||
45
test/tests/unit/forEvent.js
Normal file
45
test/tests/unit/forEvent.js
Normal file
@@ -0,0 +1,45 @@
|
||||
describe('forEvent() unit tests', function() {
|
||||
|
||||
it('resolves when event fires', async function () {
|
||||
let div = createProcessedHTML('<div></div>')
|
||||
let promise = htmx.forEvent('custom', null, div)
|
||||
setTimeout(() => div.dispatchEvent(new Event('custom')), 10)
|
||||
let evt = await promise
|
||||
assert.isNotNull(evt)
|
||||
assert.equal(evt.type, 'custom')
|
||||
})
|
||||
|
||||
it('resolves with null on timeout', async function () {
|
||||
let div = createProcessedHTML('<div></div>')
|
||||
let evt = await htmx.forEvent('custom', 50, div)
|
||||
assert.isNull(evt)
|
||||
})
|
||||
|
||||
it('defaults to document', async function () {
|
||||
let promise = htmx.forEvent('custom:test', null)
|
||||
setTimeout(() => document.dispatchEvent(new Event('custom:test')), 10)
|
||||
let evt = await promise
|
||||
assert.isNotNull(evt)
|
||||
})
|
||||
|
||||
it('resolves before timeout if event fires', async function () {
|
||||
let div = createProcessedHTML('<div></div>')
|
||||
let promise = htmx.forEvent('custom', 1000, div)
|
||||
setTimeout(() => div.dispatchEvent(new Event('custom')), 10)
|
||||
let start = Date.now()
|
||||
let evt = await promise
|
||||
let elapsed = Date.now() - start
|
||||
assert.isNotNull(evt)
|
||||
assert.isBelow(elapsed, 500)
|
||||
})
|
||||
|
||||
it('cleans up timeout when event fires', async function () {
|
||||
let div = createProcessedHTML('<div></div>')
|
||||
let promise = htmx.forEvent('custom', 1000, div)
|
||||
setTimeout(() => div.dispatchEvent(new Event('custom')), 10)
|
||||
await promise
|
||||
// If timeout wasn't cleared, this test would hang
|
||||
assert.isTrue(true)
|
||||
})
|
||||
|
||||
});
|
||||
40
test/tests/unit/on.js
Normal file
40
test/tests/unit/on.js
Normal file
@@ -0,0 +1,40 @@
|
||||
describe('on() unit tests', function() {
|
||||
|
||||
it('registers event listener on document by default', function () {
|
||||
let called = false
|
||||
htmx.on('custom:test', () => called = true)
|
||||
document.dispatchEvent(new Event('custom:test'))
|
||||
assert.isTrue(called)
|
||||
})
|
||||
|
||||
it('registers event listener on specific element', function () {
|
||||
let div = createProcessedHTML('<div></div>')
|
||||
let called = false
|
||||
htmx.on(div, 'custom', () => called = true)
|
||||
div.dispatchEvent(new Event('custom'))
|
||||
assert.isTrue(called)
|
||||
})
|
||||
|
||||
it('returns the callback', function () {
|
||||
let callback = () => {}
|
||||
let returned = htmx.on('custom', callback)
|
||||
assert.equal(returned, callback)
|
||||
})
|
||||
|
||||
it('receives event object', function () {
|
||||
let receivedEvent = null
|
||||
htmx.on('custom:test2', (evt) => receivedEvent = evt)
|
||||
document.dispatchEvent(new Event('custom:test2'))
|
||||
assert.isNotNull(receivedEvent)
|
||||
assert.equal(receivedEvent.type, 'custom:test2')
|
||||
})
|
||||
|
||||
it('works with selector string for element', function () {
|
||||
createProcessedHTML('<div id="target"></div>')
|
||||
let called = false
|
||||
htmx.on('#target', 'custom', () => called = true)
|
||||
document.getElementById('target').dispatchEvent(new Event('custom'))
|
||||
assert.isTrue(called)
|
||||
})
|
||||
|
||||
});
|
||||
93
test/tests/unit/process.js
Normal file
93
test/tests/unit/process.js
Normal file
@@ -0,0 +1,93 @@
|
||||
describe('process() unit tests', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
setupTest();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
cleanupTest();
|
||||
});
|
||||
|
||||
it('initializes element with hx-get', function () {
|
||||
let div = createHTML('<div hx-get="/test"></div>')
|
||||
htmx.process(div)
|
||||
assert.isTrue(div.hasAttribute('data-htmx-powered'))
|
||||
})
|
||||
|
||||
it('initializes descendant elements', function () {
|
||||
let container = createHTML('<div><button hx-get="/test"></button></div>')
|
||||
htmx.process(container)
|
||||
let button = container.querySelector('button')
|
||||
assert.isTrue(button.hasAttribute('data-htmx-powered'))
|
||||
})
|
||||
|
||||
it('initializes boosted elements', function () {
|
||||
let a = createHTML('<a href="/test" hx-boost="true">Link</a>')
|
||||
htmx.process(a)
|
||||
assert.isTrue(a.hasAttribute('data-htmx-powered'))
|
||||
})
|
||||
|
||||
it('processes scripts by default', function () {
|
||||
let container = createHTML('<div><script>window.testProcessed = true</script></div>')
|
||||
htmx.process(container)
|
||||
assert.isTrue(window.testProcessed)
|
||||
delete window.testProcessed
|
||||
})
|
||||
|
||||
it('skips scripts when processScripts is false', function () {
|
||||
let container = createHTML('<div><script>window.testSkipped = true</script></div>')
|
||||
htmx.process(container, false)
|
||||
assert.isUndefined(window.testSkipped)
|
||||
})
|
||||
|
||||
it('processes hx-on attributes', function () {
|
||||
let div = createHTML('<div hx-on:custom="this.setAttribute(\'fired\', \'true\')"></div>')
|
||||
htmx.process(div)
|
||||
div.dispatchEvent(new Event('custom'))
|
||||
assert.equal(div.getAttribute('fired'), 'true')
|
||||
})
|
||||
|
||||
it('ignores elements with hx-ignore', function () {
|
||||
let container = createHTML('<div hx-ignore><button hx-get="/test"></button></div>')
|
||||
htmx.process(container)
|
||||
let button = container.querySelector('button')
|
||||
assert.isFalse(button.hasAttribute('data-htmx-powered'))
|
||||
})
|
||||
|
||||
it('ignores descendants of hx-ignore', function () {
|
||||
let container = createHTML('<div><div hx-ignore><button hx-get="/test"></button></div></div>')
|
||||
htmx.process(container)
|
||||
let button = container.querySelector('button')
|
||||
assert.isFalse(button.hasAttribute('data-htmx-powered'))
|
||||
})
|
||||
|
||||
it('processes element itself if it matches', function () {
|
||||
let div = createHTML('<div hx-get="/test"></div>')
|
||||
htmx.process(div)
|
||||
assert.isTrue(div.hasAttribute('data-htmx-powered'))
|
||||
})
|
||||
|
||||
it('triggers htmx:before:process event', function () {
|
||||
let div = createHTML('<div hx-get="/test"></div>')
|
||||
let fired = false
|
||||
div.addEventListener('htmx:before:process', () => fired = true)
|
||||
htmx.process(div)
|
||||
assert.isTrue(fired)
|
||||
})
|
||||
|
||||
it('triggers htmx:after:process event', function () {
|
||||
let div = createHTML('<div hx-get="/test"></div>')
|
||||
let fired = false
|
||||
div.addEventListener('htmx:after:process', () => fired = true)
|
||||
htmx.process(div)
|
||||
assert.isTrue(fired)
|
||||
})
|
||||
|
||||
it('skips processing if htmx:before:process is cancelled', function () {
|
||||
let div = createHTML('<div hx-get="/test"></div>')
|
||||
div.addEventListener('htmx:before:process', (e) => e.preventDefault())
|
||||
htmx.process(div)
|
||||
assert.isFalse(div.hasAttribute('data-htmx-powered'))
|
||||
})
|
||||
|
||||
});
|
||||
34
test/tests/unit/timeout.js
Normal file
34
test/tests/unit/timeout.js
Normal file
@@ -0,0 +1,34 @@
|
||||
describe('timeout() unit tests', function() {
|
||||
|
||||
it('returns promise that resolves after milliseconds', async function () {
|
||||
let start = Date.now()
|
||||
await htmx.timeout(50)
|
||||
let elapsed = Date.now() - start
|
||||
assert.isAtLeast(elapsed, 45)
|
||||
})
|
||||
|
||||
it('accepts string time format', async function () {
|
||||
let start = Date.now()
|
||||
await htmx.timeout('50ms')
|
||||
let elapsed = Date.now() - start
|
||||
assert.isAtLeast(elapsed, 45)
|
||||
})
|
||||
|
||||
it('accepts seconds format', async function () {
|
||||
let start = Date.now()
|
||||
await htmx.timeout('0.05s')
|
||||
let elapsed = Date.now() - start
|
||||
assert.isAtLeast(elapsed, 45)
|
||||
})
|
||||
|
||||
it('returns undefined for zero time', function () {
|
||||
let result = htmx.timeout(0)
|
||||
assert.isUndefined(result)
|
||||
})
|
||||
|
||||
it('returns undefined for negative time', function () {
|
||||
let result = htmx.timeout(-1)
|
||||
assert.isUndefined(result)
|
||||
})
|
||||
|
||||
});
|
||||
66
test/tests/unit/trigger.js
Normal file
66
test/tests/unit/trigger.js
Normal file
@@ -0,0 +1,66 @@
|
||||
describe('trigger() unit tests', function() {
|
||||
|
||||
it('triggers event on element', function () {
|
||||
let div = createProcessedHTML('<div></div>')
|
||||
let called = false
|
||||
div.addEventListener('custom', () => called = true)
|
||||
htmx.trigger(div, 'custom')
|
||||
assert.isTrue(called)
|
||||
})
|
||||
|
||||
it('passes detail object', function () {
|
||||
let div = createProcessedHTML('<div></div>')
|
||||
let receivedDetail = null
|
||||
div.addEventListener('custom', (e) => receivedDetail = e.detail)
|
||||
htmx.trigger(div, 'custom', {foo: 'bar'})
|
||||
assert.deepEqual(receivedDetail, {foo: 'bar'})
|
||||
})
|
||||
|
||||
it('bubbles by default', function () {
|
||||
let parent = createProcessedHTML('<div><span id="child"></span></div>')
|
||||
let child = parent.querySelector('#child')
|
||||
let calledOnParent = false
|
||||
parent.addEventListener('custom', () => calledOnParent = true)
|
||||
htmx.trigger(child, 'custom')
|
||||
assert.isTrue(calledOnParent)
|
||||
})
|
||||
|
||||
it('can disable bubbling', function () {
|
||||
let parent = createProcessedHTML('<div><span id="child"></span></div>')
|
||||
let child = parent.querySelector('#child')
|
||||
let calledOnParent = false
|
||||
parent.addEventListener('custom', () => calledOnParent = true)
|
||||
htmx.trigger(child, 'custom', {}, false)
|
||||
assert.isFalse(calledOnParent)
|
||||
})
|
||||
|
||||
it('returns true when not cancelled', function () {
|
||||
let div = createProcessedHTML('<div></div>')
|
||||
let result = htmx.trigger(div, 'custom')
|
||||
assert.isTrue(result)
|
||||
})
|
||||
|
||||
it('returns false when event prevented', function () {
|
||||
let div = createProcessedHTML('<div></div>')
|
||||
div.addEventListener('custom', (e) => e.preventDefault())
|
||||
let result = htmx.trigger(div, 'custom')
|
||||
assert.isFalse(result)
|
||||
})
|
||||
|
||||
it('works with selector string', function () {
|
||||
createProcessedHTML('<div id="target"></div>')
|
||||
let called = false
|
||||
document.getElementById('target').addEventListener('custom', () => called = true)
|
||||
htmx.trigger('#target', 'custom')
|
||||
assert.isTrue(called)
|
||||
})
|
||||
|
||||
it('triggers on document when element not connected', function () {
|
||||
let div = document.createElement('div')
|
||||
let calledOnDocument = false
|
||||
document.addEventListener('custom:orphan', () => calledOnDocument = true)
|
||||
htmx.trigger(div, 'custom:orphan')
|
||||
assert.isTrue(calledOnDocument)
|
||||
})
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user