Skip to content

Commit

Permalink
UIHandler: fix support for empty selector (promiscuous binding)
Browse files Browse the repository at this point in the history
  • Loading branch information
jiripudil committed Jul 7, 2024
1 parent ef8f0e7 commit 94e9916
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 1 deletion.
2 changes: 1 addition & 1 deletion src/core/UIHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export class UIHandler extends EventTarget {
}

const {submitter} = event;
if (element.matches(this.selector) || submitter?.matches(this.selector)) {
if (this.selector === '' || element.matches(this.selector) || submitter?.matches(this.selector)) {
this.submitForm(submitter ?? element, options, event).catch(ignoreErrors);
}
}
Expand Down
86 changes: 86 additions & 0 deletions tests/Naja.UIHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,42 @@ describe('UIHandler', function () {
document.body.removeChild(customSelectorLink);
});

it('binds to all elements when selector is empty', function () {
const emptySelectorLink = document.createElement('a');
emptySelectorLink.href = '/UIHandler/emptySelector';
document.body.appendChild(emptySelectorLink);

const emptySelectorForm = document.createElement('form');
emptySelectorForm.action = '/UIHandler/emptySelector';
document.body.appendChild(emptySelectorForm);

const emptySelectorFormWithSubmitter = document.createElement('form');
emptySelectorFormWithSubmitter.action = '/UIHandler/emptySelector';
document.body.appendChild(emptySelectorFormWithSubmitter);

const emptySelectorSubmitter = document.createElement('input');
emptySelectorSubmitter.type = 'submit';
emptySelectorFormWithSubmitter.appendChild(emptySelectorSubmitter);

const naja = mockNaja({
snippetHandler: SnippetHandler,
uiHandler: UIHandler,
});
naja.uiHandler.selector = '';
naja.uiHandler.handler = sinon.spy((evt) => evt.preventDefault());
naja.initialize();

emptySelectorLink.dispatchEvent(new MouseEvent('click', {cancelable: true}));
emptySelectorForm.dispatchEvent(new SubmitEvent('submit', {cancelable: true}));
emptySelectorFormWithSubmitter.dispatchEvent(new SubmitEvent('submit', {submitter: emptySelectorSubmitter, cancelable: true}));

assert.equal(naja.uiHandler.handler.callCount, 3);

document.body.removeChild(emptySelectorLink);
document.body.removeChild(emptySelectorForm);
document.body.removeChild(emptySelectorFormWithSubmitter);
});

it('binds after snippet update', async function () {
const snippetDiv = document.createElement('div');
snippetDiv.id = 'snippet-uiHandler-snippet-bind';
Expand Down Expand Up @@ -415,11 +451,21 @@ describe('UIHandler', function () {
.withExactArgs('POST', '/UIHandler/form', sinon.match.instanceOf(FormData), {fetch: {}})
.once();

const listener = sinon.spy();
const handler = new UIHandler(naja);
handler.addEventListener('interaction', listener);

const event = new SubmitEvent('submit', {cancelable: true});
simulateInteraction(handler, this.form, event);

assert.isTrue(listener.calledWith(
sinon.match((event) => event.constructor.name === 'CustomEvent')
.and(sinon.match.has('detail', sinon.match.object
.and(sinon.match.has('element', this.form))
.and(sinon.match.has('originalEvent', event))
))
));

assert.isTrue(event.defaultPrevented);
mock.verify();
});
Expand All @@ -433,11 +479,21 @@ describe('UIHandler', function () {
.withExactArgs('GET', '/UIHandler/submit', sinon.match.instanceOf(FormData).and(containsSubmit), {fetch: {}})
.once();

const listener = sinon.spy();
const handler = new UIHandler(naja);
handler.addEventListener('interaction', listener);

const event = new SubmitEvent('submit', {cancelable: true, submitter: this.input});
simulateInteraction(handler, this.form2, event);

assert.isTrue(listener.calledWith(
sinon.match((event) => event.constructor.name === 'CustomEvent')
.and(sinon.match.has('detail', sinon.match.object
.and(sinon.match.has('element', this.input))
.and(sinon.match.has('originalEvent', event))
))
));

assert.isTrue(event.defaultPrevented);
mock.verify();
});
Expand All @@ -451,11 +507,21 @@ describe('UIHandler', function () {
.withExactArgs('GET', '/UIHandler/image', sinon.match.instanceOf(FormData).and(containsImage), {fetch: {}})
.once();

const listener = sinon.spy();
const handler = new UIHandler(naja);
handler.addEventListener('interaction', listener);

const event = new SubmitEvent('submit', {cancelable: true, submitter: this.image});
simulateInteraction(handler, this.form3, event);

assert.isTrue(listener.calledWith(
sinon.match((event) => event.constructor.name === 'CustomEvent')
.and(sinon.match.has('detail', sinon.match.object
.and(sinon.match.has('element', this.image))
.and(sinon.match.has('originalEvent', event))
))
));

assert.isTrue(event.defaultPrevented);
mock.verify();
});
Expand All @@ -469,11 +535,21 @@ describe('UIHandler', function () {
.withExactArgs('GET', '/UIHandler/defaultSubmit', sinon.match.instanceOf(FormData).and(containsSubmit), {fetch: {}})
.once();

const listener = sinon.spy();
const handler = new UIHandler(naja);
handler.addEventListener('interaction', listener);

const event = new SubmitEvent('submit', {cancelable: true, submitter: this.submitButton});
simulateInteraction(handler, this.form4, event);

assert.isTrue(listener.calledWith(
sinon.match((event) => event.constructor.name === 'CustomEvent')
.and(sinon.match.has('detail', sinon.match.object
.and(sinon.match.has('element', this.submitButton))
.and(sinon.match.has('originalEvent', event))
))
));

assert.isTrue(event.defaultPrevented);
mock.verify();
});
Expand All @@ -487,11 +563,21 @@ describe('UIHandler', function () {
.withExactArgs('POST', '/UIHandler/externalSubmitOverride', sinon.match.instanceOf(FormData).and(containsSubmit), {fetch: {}})
.once();

const listener = sinon.spy();
const handler = new UIHandler(naja);
handler.addEventListener('interaction', listener);

const event = new SubmitEvent('submit', {cancelable: true, submitter: this.externalButton});
simulateInteraction(handler, this.form5, event);

assert.isTrue(listener.calledWith(
sinon.match((event) => event.constructor.name === 'CustomEvent')
.and(sinon.match.has('detail', sinon.match.object
.and(sinon.match.has('element', this.externalButton))
.and(sinon.match.has('originalEvent', event))
))
));

assert.isTrue(event.defaultPrevented);
mock.verify();
});
Expand Down

0 comments on commit 94e9916

Please sign in to comment.