Normalize listeners at registration time; add tests for rich functions

This commit is contained in:
Will Binns-Smith
2017-08-21 16:22:24 -07:00
parent 3aa95d96d4
commit 9b995c68ec
2 changed files with 62 additions and 38 deletions

View File

@@ -273,7 +273,7 @@ describe("CommandRegistry", () => {
]);
});
it("returns commands with manual displayNames if set in the listener", () => {
it("returns commands with arbitrary metadata if set in a listener object", () => {
registry.add('.grandchild', 'namespace:command-1', () => {});
registry.add('.grandchild', 'namespace:command-2', {
displayName: 'Custom Command 2',
@@ -283,7 +283,15 @@ describe("CommandRegistry", () => {
},
handleEvent() {}
});
registry.add('.grandchild', 'namespace:command-3', () => {});
registry.add('.grandchild', 'namespace:command-3', {
name: 'some:other:incorrect:commandname',
displayName: 'Custom Command 3',
metadata: {
some: 'other',
object: 'data'
},
handleEvent() {}
});
const commands = registry.findCommands({target: grandchild});
expect(commands).toEqual([
@@ -300,30 +308,32 @@ describe("CommandRegistry", () => {
name: 'namespace:command-2'
},
{
displayName: 'Namespace: Command 3',
displayName: 'Custom Command 3',
metadata: {
some : 'other',
object : 'data'
},
name: 'namespace:command-3'
}
]);
});
it("ignores a `name` property if passed in registration object", () => {
registry.add('.grandchild', 'namespace:command-2', {
name: 'some:other:commandname',
displayName: 'Custom Command 2',
metadata: {
some: 'other',
object: 'data'
},
handleEvent() {}
});
it("returns commands with arbitrary metadata if set on a listener function", () => {
function listener () {}
listener.displayName = 'Custom Command 2'
listener.metadata = {
some: 'other',
object: 'data'
};
registry.add('.grandchild', 'namespace:command-2', listener);
const commands = registry.findCommands({target: grandchild});
expect(commands).toEqual([
{
displayName: 'Custom Command 2',
displayName : 'Custom Command 2',
metadata: {
some : 'other',
object : 'data'
some: 'other',
object: 'data'
},
name: 'namespace:command-2'
}

View File

@@ -144,7 +144,7 @@ module.exports = class CommandRegistry {
this.selectorBasedListenersByCommandName[commandName] = []
}
const listenersForCommand = this.selectorBasedListenersByCommandName[commandName]
const selectorListener = new SelectorBasedListener(selector, commandFromListener(listener))
const selectorListener = new SelectorBasedListener(selector, commandName, listener)
listenersForCommand.push(selectorListener)
this.commandRegistered(commandName)
@@ -158,17 +158,17 @@ module.exports = class CommandRegistry {
}
addInlineListener (element, commandName, listener) {
let listenersForElement
if (this.inlineListenersByCommandName[commandName] == null) {
this.inlineListenersByCommandName[commandName] = new WeakMap
}
const listenersForCommand = this.inlineListenersByCommandName[commandName]
if (!(listenersForElement = listenersForCommand.get(element))) {
let listenersForElement = listenersForCommand.get(element)
if (!listenersForElement) {
listenersForElement = []
listenersForCommand.set(element, listenersForElement)
}
const inlineListener = new InlineListener(commandFromListener(listener))
const inlineListener = new InlineListener(commandName, listener)
listenersForElement.push(inlineListener)
this.commandRegistered(commandName)
@@ -200,23 +200,20 @@ module.exports = class CommandRegistry {
listeners = this.inlineListenersByCommandName[name]
if (listeners.has(currentTarget) && !commandNames.has(name)) {
commandNames.add(name)
// don't allow those with a command derived from an object to invoke its
// handler directly. rather, they should call ::dispatch a CustomEvent with
// its `name` property
commands.push(_.omit(listeners.get(currentTarget), 'handleEvent'))
const targetListeners = listeners.get(currentTarget);
commands.push(
...targetListeners.map(listener => listener.getPublicCommand())
);
}
}
for (const commandName in this.selectorBasedListenersByCommandName) {
listeners = this.selectorBasedListenersByCommandName[commandName]
for (const listener of listeners) {
if (
currentTarget.webkitMatchesSelector &&
currentTarget.webkitMatchesSelector(listener.selector)
) {
if (listener.matchesTarget(currentTarget)) {
if (!commandNames.has(commandName)) {
commandNames.add(commandName)
commands.push(_.omit(listener, 'handleEvent'))
commands.push(listener.getPublicCommand())
}
}
}
@@ -341,9 +338,7 @@ module.exports = class CommandRegistry {
if (currentTarget.webkitMatchesSelector != null) {
const selectorBasedListeners =
(this.selectorBasedListenersByCommandName[event.type] || [])
.filter(listener =>
currentTarget.webkitMatchesSelector(listener.selector)
)
.filter(listener => listener.matchesTarget(currentTarget))
.sort((a, b) => a.compare(b))
listeners = selectorBasedListeners.concat(listeners)
}
@@ -360,7 +355,7 @@ module.exports = class CommandRegistry {
if (immediatePropagationStopped) {
break
}
listener.command.call(currentTarget, dispatchedEvent)
listener.command.handleEvent.call(currentTarget, dispatchedEvent)
}
if (currentTarget === window) {
@@ -386,9 +381,9 @@ module.exports = class CommandRegistry {
}
class SelectorBasedListener {
constructor (selector, command) {
constructor (selector, commandName, listener) {
this.selector = selector
this.command = command
this.command = commandFromListener(commandName, listener)
this.specificity = calculateSpecificity(this.selector)
this.sequenceNumber = SequenceCount++
}
@@ -399,11 +394,23 @@ class SelectorBasedListener {
this.sequenceNumber - other.sequenceNumber
)
}
getPublicCommand () {
return getPublicCommand.call(this)
}
matchesTarget (target) {
return target.webkitMatchesSelector && target.webkitMatchesSelector(this.selector)
}
}
class InlineListener {
constructor (command) {
this.command = command
constructor (commandName, listener) {
this.command = commandFromListener(commandName, listener)
}
getPublicCommand () {
return getPublicCommand.call(this)
}
}
@@ -414,7 +421,14 @@ function commandFromListener (name, listener) {
{
name,
displayName: listener.displayName ? listener.displayName : _.humanizeEventName(name),
handleEvent: typeof listener === 'function' ? listener : listener.handleEvent,
handleEvent: typeof listener === 'function' ? listener : listener.handleEvent
}
)
}
// don't allow those with a command derived from an object to invoke its
// handler directly. rather, they should call ::dispatch a CustomEvent with
// its `name` property
function getPublicCommand() {
return _.omit(this.command, 'handleEvent')
}