Skip to content
This repository has been archived by the owner on Aug 29, 2023. It is now read-only.

Commit

Permalink
fix(tooltip): always resolve expressions against the correct scope (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
jelbourn authored and mmalerba committed Jan 20, 2017
1 parent 5b799e2 commit 685b902
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 35 deletions.
2 changes: 1 addition & 1 deletion src/components/tooltip/tooltip.js
Expand Up @@ -106,7 +106,7 @@ function MdTooltipDirective($timeout, $window, $$rAF, $document, $interpolate,
if (override || !parent.attr('aria-label')) {
// Only interpolate the text from the HTML element because otherwise the custom text
// could be interpolated twice and cause XSS violations.
var interpolatedText = override || $interpolate(element.text().trim())(parent.scope);
var interpolatedText = override || $interpolate(element.text().trim())(scope.$parent);
parent.attr('aria-label', interpolatedText);
}
}
Expand Down
91 changes: 57 additions & 34 deletions src/components/tooltip/tooltip.spec.js
@@ -1,20 +1,29 @@
describe('MdTooltip Component', function() {
var $compile, $rootScope, $material, $timeout, $mdPanel, $$mdTooltipRegistry;
var $compile, $rootScope, parentScope, $material, $timeout, $mdPanel, $$mdTooltipRegistry;
var element;

var injectLocals = function($injector) {
$compile = $injector.get('$compile');
$rootScope = $injector.get('$rootScope');
parentScope = $rootScope.$new();
$material = $injector.get('$material');
$timeout = $injector.get('$timeout');
$mdPanel = $injector.get('$mdPanel');
$$mdTooltipRegistry = $injector.get('$$mdTooltipRegistry');
};

// Test filter for ensuring tooltip expressions are evaluated against the correct scope.
angular.module('fullNameFilter', []).filter('fullName', function() {
return function(user) {
return user.name.first + ' ' + user.name.last;
}
});

beforeEach(function() {
module(
'material.components.tooltip',
'material.components.button'
'material.components.button',
'fullNameFilter'
);

inject(injectLocals);
Expand Down Expand Up @@ -50,7 +59,7 @@ describe('MdTooltip Component', function() {
});

it('should not re-templatize tooltip content', function() {
$rootScope.name = '{{2 + 2}}';
parentScope.name = '{{2 + 2}}';

buildTooltip(
'<md-button>' +
Expand All @@ -61,6 +70,20 @@ describe('MdTooltip Component', function() {
expect(findTooltip().text()).toBe('{{2 + 2}}');
});

it('should evaluate expressions against the parent scope', function() {
parentScope.user = {name: {first: 'Neil', last: 'Diamond'}};

// Using a filter that dereferences the user object multiple times will cause an error to be
// thrown if user is not given correctly.
buildTooltip(
'<md-button>' +
'<md-tooltip md-visible="true">{{user | fullName}}</md-tooltip>' +
'</md-button>'
);

expect(findTooltip().text()).toBe('Neil Diamond');
});

it('should preserve parent text', function() {
buildTooltip(
'<md-button>' +
Expand Down Expand Up @@ -102,14 +125,14 @@ describe('MdTooltip Component', function() {
'</md-button>'
);

$rootScope.$apply(function() {
$rootScope.testModel.ariaText = 'test 1';
parentScope.$apply(function() {
parentScope.testModel.ariaText = 'test 1';
});

expect(element.attr('aria-label')).toBe('test 1');

$rootScope.$apply(function() {
$rootScope.testModel.ariaText = 'test 2';
parentScope.$apply(function() {
parentScope.testModel.ariaText = 'test 2';
});

expect(element.attr('aria-label')).toBe('test 2');
Expand All @@ -122,14 +145,14 @@ describe('MdTooltip Component', function() {
'</md-button>'
);

$rootScope.$apply(function() {
$rootScope.testModel.ariaTest = 'test {{1+1}}';
parentScope.$apply(function() {
parentScope.testModel.ariaTest = 'test {{1+1}}';
});

expect(element.attr('aria-label')).toBe('test {{1+1}}');

$rootScope.$apply(function() {
$rootScope.testModel.ariaTest = 'test {{1+1336}}';
parentScope.$apply(function() {
parentScope.testModel.ariaTest = 'test {{1+1336}}';
});

expect(element.attr('aria-label')).toBe('test {{1+1336}}');
Expand All @@ -152,7 +175,7 @@ describe('MdTooltip Component', function() {
);

triggerEvent('mouseenter', true);
expect($rootScope.testModel.isVisible).toBeUndefined();
expect(parentScope.testModel.isVisible).toBeUndefined();
}));

it('should show after tooltipDelay ms', function() {
Expand All @@ -166,15 +189,15 @@ describe('MdTooltip Component', function() {
);

triggerEvent('focus', true);
expect($rootScope.testModel.isVisible).toBeFalsy();
expect(parentScope.testModel.isVisible).toBeFalsy();

// Wait 1 below delay, nothing should happen
$timeout.flush(98);
expect($rootScope.testModel.isVisible).toBeFalsy();
expect(parentScope.testModel.isVisible).toBeFalsy();

// Total 300 == tooltipDelay
$timeout.flush(1);
expect($rootScope.testModel.isVisible).toBe(true);
expect(parentScope.testModel.isVisible).toBe(true);
});

it('should register itself with the $$mdTooltipRegistry', function() {
Expand Down Expand Up @@ -224,10 +247,10 @@ describe('MdTooltip Component', function() {
);

triggerEvent('mouseenter');
expect($rootScope.testModel.isVisible).toBe(true);
expect(parentScope.testModel.isVisible).toBe(true);

triggerEvent('mouseleave');
expect($rootScope.testModel.isVisible).toBe(false);
expect(parentScope.testModel.isVisible).toBe(false);
});

it('should toggle visibility on the next touch',
Expand All @@ -242,12 +265,12 @@ describe('MdTooltip Component', function() {
);

triggerEvent('touchstart');
expect($rootScope.testModel.isVisible).toBe(true);
expect(parentScope.testModel.isVisible).toBe(true);
triggerEvent('touchend');

$document.triggerHandler('touchend');
$timeout.flush();
expect($rootScope.testModel.isVisible).toBe(false);
expect(parentScope.testModel.isVisible).toBe(false);
}));

it('should cancel when mouseleave was before the delay', function() {
Expand All @@ -264,15 +287,15 @@ describe('MdTooltip Component', function() {
);

triggerEvent('mouseenter', true);
expect($rootScope.testModel.isVisible).toBeFalsy();
expect(parentScope.testModel.isVisible).toBeFalsy();

triggerEvent('mouseleave', true);
expect($rootScope.testModel.isVisible).toBeFalsy();
expect(parentScope.testModel.isVisible).toBeFalsy();

// Total 99 == tooltipDelay
$timeout.flush(99);

expect($rootScope.testModel.isVisible).toBe(false);
expect(parentScope.testModel.isVisible).toBe(false);
});

it('should throw when the tooltip text is empty', function() {
Expand Down Expand Up @@ -301,10 +324,10 @@ describe('MdTooltip Component', function() {
);

triggerEvent('focus');
expect($rootScope.testModel.isVisible).toBe(true);
expect(parentScope.testModel.isVisible).toBe(true);

triggerEvent('blur');
expect($rootScope.testModel.isVisible).toBe(false);
expect(parentScope.testModel.isVisible).toBe(false);
});

it('should not be visible on mousedown and then mouseleave',
Expand All @@ -324,10 +347,10 @@ describe('MdTooltip Component', function() {
triggerEvent('focus,mousedown');

expect($document[0].activeElement).toBe(element[0]);
expect($rootScope.testModel.isVisible).toBe(true);
expect(parentScope.testModel.isVisible).toBe(true);

triggerEvent('mouseleave');
expect($rootScope.testModel.isVisible).toBe(false);
expect(parentScope.testModel.isVisible).toBe(false);

// Clean up document.body.
// element.remove();
Expand Down Expand Up @@ -357,7 +380,7 @@ describe('MdTooltip Component', function() {

// Simulate focus event that occurs when tabbing back to the window.
triggerEvent('focus');
expect($rootScope.testModel.isVisible).toBe(false);
expect(parentScope.testModel.isVisible).toBe(false);

// Clean up document.body.
$document[0].body.removeChild(element[0]);
Expand Down Expand Up @@ -433,11 +456,11 @@ describe('MdTooltip Component', function() {
);

triggerEvent('focus');
expect($rootScope.testModel.isVisible).toBe(true);
expect(parentScope.testModel.isVisible).toBe(true);

element.remove();
triggerEvent('blur mouseleave touchend touchcancel');
expect($rootScope.testModel.isVisible).toBe(true);
expect(parentScope.testModel.isVisible).toBe(true);
});
});

Expand All @@ -446,10 +469,10 @@ describe('MdTooltip Component', function() {
// ******************************************************

function buildTooltip(markup) {
element = $compile(markup)($rootScope);
$rootScope.testModel = {};
element = $compile(markup)(parentScope);
parentScope.testModel = {};

$rootScope.$apply();
parentScope.$apply();
$material.flushOutstandingAnimations();

return element;
Expand All @@ -459,8 +482,8 @@ describe('MdTooltip Component', function() {
if (angular.isUndefined(isVisible)) {
isVisible = true;
}
$rootScope.testModel.isVisible = !!isVisible;
$rootScope.$apply();
parentScope.testModel.isVisible = !!isVisible;
parentScope.$apply();
$material.flushOutstandingAnimations();
}

Expand Down

0 comments on commit 685b902

Please sign in to comment.