Skip to content

Commit

Permalink
Merge pull request #13279 from asakusuma/action-instrumentation
Browse files Browse the repository at this point in the history
Add action and dom event instrumentation
  • Loading branch information
krisselden committed Apr 11, 2016
2 parents 03a4919 + ee67815 commit 46649f2
Show file tree
Hide file tree
Showing 8 changed files with 239 additions and 16 deletions.
7 changes: 7 additions & 0 deletions FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,10 @@ for a detailed explanation.
],
dedupedComments: Ember.computed.uniqBy('comments', 'id')
```

* `ember-improved-instrumentation`

Adds additional instrumentation to Ember:

- `interaction.<event-name>` for events handled by a component.
- `interaction.ember-action` for closure actions.
3 changes: 2 additions & 1 deletion features.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"ember-htmlbars-local-lookup": true,
"ember-application-engines": null,
"ember-glimmer": null,
"ember-runtime-computed-uniq-by": null
"ember-runtime-computed-uniq-by": null,
"ember-improved-instrumentation": null
}
}
23 changes: 19 additions & 4 deletions packages/ember-metal/lib/instrumentation.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Ember from 'ember-metal/core';
import isEnabled from 'ember-metal/features';

/**
The purpose of the Ember Instrumentation module is
Expand Down Expand Up @@ -105,14 +106,26 @@ export function instrument(name, _payload, callback, binding) {
}
}

var flaggedInstrument;
if (isEnabled('ember-improved-instrumentation')) {
flaggedInstrument = instrument;
} else {
flaggedInstrument = function(name, payload, callback) {
return callback();
};
}
export { flaggedInstrument };

function withFinalizer(callback, finalizer, payload, binding) {
let result;
try {
return callback.call(binding);
result = callback.call(binding);
} catch(e) {
payload.exception = e;
return payload;
result = payload;
} finally {
return finalizer();
finalizer();
return result;
}
}

Expand Down Expand Up @@ -151,7 +164,9 @@ export function _instrumentStart(name, _payload) {
var timestamp = time();
for (i = 0, l = listeners.length; i < l; i++) {
listener = listeners[i];
listener.after(name, timestamp, payload, beforeValues[i]);
if (typeof listener.after === 'function') {
listener.after(name, timestamp, payload, beforeValues[i]);
}
}

if (STRUCTURED_PROFILE) {
Expand Down
18 changes: 13 additions & 5 deletions packages/ember-routing-htmlbars/lib/keywords/closure-action.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { Stream } from 'ember-metal/streams/stream';
import {
read,
readArray
readArray,
labelFor
} from 'ember-metal/streams/utils';
import symbol from 'ember-metal/symbol';
import { get } from 'ember-metal/property_get';
import { labelForSubexpr } from 'ember-htmlbars/hooks/subexpr';
import EmberError from 'ember-metal/error';
import run from 'ember-metal/run_loop';
import { flaggedInstrument } from 'ember-metal/instrumentation';

export const INVOKE = symbol('INVOKE');
export const ACTION = symbol('ACTION');
Expand Down Expand Up @@ -59,7 +61,7 @@ export default function closureAction(morph, env, scope, params, hash, template,
valuePath = read(hash.value);
}

return createClosureAction(target, action, valuePath, actionArguments);
return createClosureAction(this, target, action, valuePath, actionArguments);
}, function() {
return labelForSubexpr(params, hash, 'action');
});
Expand All @@ -70,7 +72,7 @@ export default function closureAction(morph, env, scope, params, hash, template,
return s;
}

function createClosureAction(target, action, valuePath, actionArguments) {
function createClosureAction(stream, target, action, valuePath, actionArguments) {
var closureAction;

if (actionArguments.length > 0) {
Expand All @@ -83,15 +85,21 @@ function createClosureAction(target, action, valuePath, actionArguments) {
args[0] = get(args[0], valuePath);
}

return run.join(target, action, ...args);
let payload = { target, args, label: labelFor(stream) };
return flaggedInstrument('interaction.ember-action', payload, () => {
return run.join(target, action, ...args);
});
};
} else {
closureAction = function(...args) {
if (valuePath && args.length > 0) {
args[0] = get(args[0], valuePath);
}

return run.join(target, action, ...args);
let payload = { target, args, label: labelFor(stream) };
return flaggedInstrument('interaction.ember-action', payload, () => {
return run.join(target, action, ...args);
});
};
}

Expand Down
134 changes: 132 additions & 2 deletions packages/ember-routing-htmlbars/tests/helpers/closure_action_test.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import run from 'ember-metal/run_loop';
import compile from 'ember-template-compiler/system/compile';
import EmberComponent from 'ember-views/components/component';
import EmberView from 'ember-views/views/view';
import { computed } from 'ember-metal/computed';
import { INVOKE } from 'ember-routing-htmlbars/keywords/closure-action';
import { subscribe, unsubscribe } from 'ember-metal/instrumentation';
import buildOwner from 'container/tests/test-helpers/build-owner';
import { OWNER } from 'container/owner';
import ComponentLookup from 'ember-views/component_lookup';
import EventDispatcher from 'ember-views/system/event_dispatcher';

import {
runAppend,
Expand All @@ -11,25 +17,149 @@ import {

import { registerKeyword, resetKeyword } from 'ember-htmlbars/tests/utils';
import viewKeyword from 'ember-htmlbars/keywords/view';
import isEnabled from 'ember-metal/features';

var innerComponent, outerComponent, originalViewKeyword;
var innerComponent, outerComponent, originalViewKeyword, owner, view, dispatcher;

function buildResolver() {
let resolver = {
resolve() { },
expandLocalLookup(fullName, sourceFullName) {
let [sourceType, sourceName ] = sourceFullName.split(':');
let [type, name ] = fullName.split(':');

if (type !== 'template' && sourceType === 'template' && sourceName.slice(0, 11) === 'components/') {
sourceName = sourceName.slice(11);
}

if (type === 'template' && sourceType === 'template' && name.slice(0, 11) === 'components/') {
name = name.slice(11);
}


let result = `${type}:${sourceName}/${name}`;

return result;
}
};

return resolver;
}

function registerTemplate(moduleName, snippet) {
owner.register(`template:${moduleName}`, compile(snippet, { moduleName }));
}

function registerComponent(name, factory) {
owner.register(`component:${name}`, factory);
}

function appendViewFor(template, moduleName='', hash={}) {
let view = EmberView.extend({
template: compile(template, { moduleName }),
[OWNER]: owner
}).create(hash);

runAppend(view);

return view;
}

import isEnabled from 'ember-metal/features';
if (!isEnabled('ember-glimmer')) {
// jscs:disable

let subscriber;
QUnit.module('ember-routing-htmlbars: action helper', {
setup() {
originalViewKeyword = registerKeyword('view', viewKeyword);
owner = buildOwner({
_registryOptions: {
resolver: buildResolver()
}
});
owner.registerOptionsForType('component', { singleton: false });
owner.registerOptionsForType('view', { singleton: false });
owner.registerOptionsForType('template', { instantiate: false });
owner.register('component-lookup:main', ComponentLookup);
dispatcher = EventDispatcher.create();
dispatcher.setup();
},

teardown() {
runDestroy(innerComponent);
runDestroy(outerComponent);
runDestroy(view);
runDestroy(owner);
resetKeyword('view', originalViewKeyword);
if (subscriber) {
unsubscribe(subscriber);
}
owner = view = null;
runDestroy(dispatcher);
}
});

if (isEnabled('ember-improved-instrumentation')) {
QUnit.test('action should fire interaction event', function(assert) {
assert.expect(2);

subscriber = subscribe('interaction.ember-action', {
before() {
assert.ok(true, 'instrumentation subscriber was called');
}
});

registerTemplate('components/inner-component', '<button id="instrument-button" {{action "fireAction"}}>What it do</button>');
registerComponent('inner-component', EmberComponent.extend({
actions: {
fireAction() {
this.attrs.submit();
}
}
}));

registerTemplate('components/outer-component', '{{inner-component submit=(action outerSubmit)}}');
registerComponent('outer-component', EmberComponent.extend({
innerComponent,
outerSubmit() {
assert.ok(true, 'action is called');
}
}));

view = appendViewFor(`{{outer-component}}`);

view.$('#instrument-button').trigger('click');
});

QUnit.test('instrumented action should return value', function(assert) {
assert.expect(1);

var returnedValue = 'Chris P is so krispy';

registerTemplate('components/inner-component', '<button id="instrument-button" {{action "fireAction"}}>What it do</button>');
registerComponent('inner-component', EmberComponent.extend({
actions: {
fireAction() {
var actualReturnedValue = this.attrs.submit();
assert.equal(actualReturnedValue, returnedValue, 'action can return to caller');
}
}
}));

registerTemplate('components/outer-component', '{{inner-component submit=(action outerSubmit)}}');
registerComponent('outer-component', EmberComponent.extend({
innerComponent,
outerSubmit() {
return returnedValue;
}
}));

view = appendViewFor(`{{outer-component}}`);

view.$('#instrument-button').trigger('click');
});
}

QUnit.test('action should be called', function(assert) {
assert.expect(1);

Expand Down
7 changes: 5 additions & 2 deletions packages/ember-views/lib/views/states/has_element.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import _default from 'ember-views/views/states/default';
import assign from 'ember-metal/assign';
import jQuery from 'ember-views/system/jquery';
import run from 'ember-metal/run_loop';
import { flaggedInstrument } from 'ember-metal/instrumentation';

/**
@module ember
Expand Down Expand Up @@ -46,11 +47,13 @@ assign(hasElement, {
},

// Handle events from `Ember.EventDispatcher`
handleEvent(view, eventName, evt) {
handleEvent(view, eventName, event) {
if (view.has(eventName)) {
// Handler should be able to re-dispatch events, so we don't
// preventDefault or stopPropagation.
return run.join(view, view.trigger, eventName, evt);
return flaggedInstrument(`interaction.${eventName}`, { event, view }, () => {
return run.join(view, view.trigger, eventName, event);
});
} else {
return true; // continue event propagation
}
Expand Down
56 changes: 56 additions & 0 deletions packages/ember-views/tests/system/event_dispatcher_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@ import { runAppend, runDestroy } from 'ember-runtime/tests/utils';
import { registerKeyword, resetKeyword } from 'ember-htmlbars/tests/utils';
import viewKeyword from 'ember-htmlbars/keywords/view';

import { subscribe, unsubscribe } from 'ember-metal/instrumentation';

var owner, view, originalViewKeyword;
var dispatcher;

import isEnabled from 'ember-metal/features';

if (!isEnabled('ember-glimmer')) {
// jscs:disable

Expand Down Expand Up @@ -47,6 +50,59 @@ QUnit.module('EventDispatcher', {
}
});

if (isEnabled('ember-improved-instrumentation')) {
QUnit.test('should instrument triggered events', function() {
let clicked = 0;

run(function () {
view = View.create({
click(evt) {
clicked++;
},

template: compile('<p>hello</p>')
}).appendTo(dispatcher.get('rootElement'));
});

view.$().trigger('click');

equal(clicked, 1, 'precond - The click handler was invoked');

let clickInstrumented = 0;
let clickSubscriber = subscribe('interaction.click', {
before() {
clickInstrumented++;
equal(clicked, 1, 'invoked before event is handled');
},
after() {
clickInstrumented++;
equal(clicked, 2, 'invoked after event is handled');
}
});

let keypressInstrumented = 0;
let keypressSubscriber = subscribe('interaction.keypress', {
before() {
keypressInstrumented++;
},
after() {
keypressInstrumented++;
}
});

try {
view.$().trigger('click');
view.$().trigger('change');
equal(clicked, 2, 'precond - The click handler was invoked');
equal(clickInstrumented, 2, 'The click was instrumented');
strictEqual(keypressInstrumented, 0, 'The keypress was not instrumented');
} finally {
unsubscribe(clickSubscriber);
unsubscribe(keypressSubscriber);
}
});
}

QUnit.test('should dispatch events to views', function() {
var receivedEvent;
var parentMouseDownCalled = 0;
Expand Down
Loading

0 comments on commit 46649f2

Please sign in to comment.