Unverified Commit ee67f0eb authored by Robert Knight's avatar Robert Knight Committed by GitHub

Merge pull request #1848 from hypothesis/remove-unused-ng-directives

Remove unused Angular filters and directives
parents 5407f085 9d3aacb2
/** An attribute directive that focuses an <input> when it's linked by Angular.
*
* The HTML5 autofocus attribute automatically puts the keyboard focus in an
* <input> on page load. But this doesn't work for <input>s that are
* rendered by JavaScript/Angular after page load, for example an <input> that
* is shown/hidden by JavaScript when an ng-if condition becomes true.
*
* To automatically put the keyboard focus on such an input when it's linked by
* Angular, attach this directive to it as an attribute:
*
* <input ng-if="..." h-autofocus>
*
*/
export default function autofocusDirective() {
return {
restrict: 'A',
link: function($scope, $element) {
$element[0].focus();
},
};
}
/**
* Install an event handler on an element.
*
* The event handler follows the same behavior as the ng-<event name>
* directives that Angular includes. This means:
*
* - The handler function is passed an object with an $event property
* - The handler function is executed in the context of `$scope.$apply()`
*
* @param {Element} element
* @param {Array<string>} events
* @param {Function} handler
*/
function addEventHandler($scope, element, events, handler) {
const callback = function(event) {
$scope.$apply(function() {
handler($scope, { $event: event });
});
};
events.forEach(function(name) {
element.addEventListener(name, callback);
});
}
/**
* A directive which adds an event handler for mouse press or touch to
* a directive. This is similar to `ng-click` etc. but reacts either on
* mouse press OR touch.
*/
// @ngInject
export default function onTouchDirective($parse) {
return {
restrict: 'A',
link: function($scope, $element, $attrs) {
const fn = $parse($attrs.hOnTouch, null /* interceptor */);
addEventHandler(
$scope,
$element[0],
['click', 'mousedown', 'touchstart'],
fn
);
},
};
}
let theTooltip;
/**
* A custom tooltip similar to the one used in Google Docs which appears
* instantly when activated on a target element.
*
* The tooltip is displayed and hidden by setting its target element.
*
* var tooltip = new Tooltip(document.body);
* tooltip.setState({target: aWidget}); // Show tooltip
* tooltip.setState({target: null}); // Hide tooltip
*
* The tooltip's label is derived from the target element's 'aria-label'
* attribute.
*
* @param {Element} rootElement - The container for the tooltip.
*/
function Tooltip(rootElement) {
this.setState = function(state) {
this.state = Object.freeze(Object.assign({}, this.state, state));
this.render();
};
this.render = function() {
const TOOLTIP_ARROW_HEIGHT = 7;
if (!this.state.target) {
this._el.style.visibility = 'hidden';
return;
}
const target = this.state.target;
const label = target.getAttribute('aria-label');
this._labelEl.textContent = label;
const tooltipRect = this._el.getBoundingClientRect();
const targetRect = target.getBoundingClientRect();
let top;
if (this.state.direction === 'up') {
top = targetRect.bottom + TOOLTIP_ARROW_HEIGHT;
} else {
top = targetRect.top - tooltipRect.height - TOOLTIP_ARROW_HEIGHT;
}
const left = targetRect.right - tooltipRect.width;
this._el.classList.toggle('tooltip--up', this.state.direction === 'up');
this._el.classList.toggle('tooltip--down', this.state.direction === 'down');
Object.assign(this._el.style, {
visibility: '',
top: top + 'px',
left: left + 'px',
});
};
this._el = rootElement.ownerDocument.createElement('div');
this._el.innerHTML = '<span class="tooltip-label js-tooltip-label"></span>';
this._el.className = 'tooltip';
rootElement.appendChild(this._el);
this._labelEl = this._el.querySelector('.js-tooltip-label');
this.setState({
direction: 'down',
});
}
/**
* Attribute directive which displays a custom tooltip when hovering the
* associated element.
*
* The associated element should use the `aria-label` attribute to specify
* the tooltip instead of the `title` attribute, which would trigger the
* display of the browser's native tooltip.
*
* Example: '<button aria-label="Tooltip label" h-tooltip></button>'
*/
export default function tooltipDirective() {
if (!theTooltip) {
theTooltip = new Tooltip(document.body);
}
return {
restrict: 'A',
link: function($scope, $element) {
const el = $element[0];
el.addEventListener('mouseover', function() {
const direction = el.getAttribute('tooltip-direction') || 'down';
theTooltip.setState({
direction: direction,
target: el,
});
});
el.addEventListener('mouseout', function() {
theTooltip.setState({ target: null });
});
// Hide the tooltip if the element is removed whilst the tooltip is active
$scope.$on('$destroy', function() {
if (theTooltip.state.target === el) {
theTooltip.setState({ target: null });
}
});
},
};
}
import angular from 'angular';
import hOnTouch from '../h-on-touch';
import * as util from './util';
function testComponent() {
return {
bindToController: true,
controllerAs: 'vm',
controller: function() {
this.tapCount = 0;
this.tap = function() {
++this.tapCount;
};
},
restrict: 'E',
template: '<div h-on-touch="vm.tap()">Tap me</div>',
};
}
describe('hOnTouch', function() {
let testEl;
before(function() {
angular
.module('app', [])
.directive('hOnTouch', hOnTouch)
.directive('test', testComponent);
});
beforeEach(function() {
angular.mock.module('app');
testEl = util.createDirective(document, 'test', {});
});
[
{
event: 'touchstart',
},
{
event: 'mousedown',
},
{
event: 'click',
},
].forEach(testCase => {
it(`calls the handler when activated with a "${testCase.event}" event`, () => {
util.sendEvent(testEl[0].querySelector('div'), testCase.event);
assert.equal(testEl.ctrl.tapCount, 1);
});
});
});
import angular from 'angular';
import hTooltip from '../h-tooltip';
import * as util from './util';
function testComponent() {
return {
controller: function() {},
restrict: 'E',
template: '<div aria-label="Share" h-tooltip>Label</div>',
};
}
describe('h-tooltip', function() {
let targetEl;
let tooltipEl;
before(function() {
angular
.module('app', [])
.directive('hTooltip', hTooltip)
.directive('test', testComponent);
});
beforeEach(function() {
angular.mock.module('app');
const testEl = util.createDirective(document, 'test', {});
targetEl = testEl[0].querySelector('div');
tooltipEl = document.querySelector('.tooltip');
});
afterEach(function() {
const testEl = document.querySelector('test');
testEl.parentNode.removeChild(testEl);
});
it('appears when the target is hovered', function() {
util.sendEvent(targetEl, 'mouseover');
assert.equal(tooltipEl.style.visibility, '');
});
it('sets the label from the target\'s "aria-label" attribute', function() {
util.sendEvent(targetEl, 'mouseover');
assert.equal(tooltipEl.textContent, 'Share');
});
it('sets the direction from the target\'s "tooltip-direction" attribute', function() {
targetEl.setAttribute('tooltip-direction', 'up');
util.sendEvent(targetEl, 'mouseover');
assert.deepEqual(Array.from(tooltipEl.classList), [
'tooltip',
'tooltip--up',
]);
targetEl.setAttribute('tooltip-direction', 'down');
util.sendEvent(targetEl, 'mouseover');
assert.deepEqual(Array.from(tooltipEl.classList), [
'tooltip',
'tooltip--down',
]);
});
it('disappears when the target is unhovered', function() {
util.sendEvent(targetEl, 'mouseout');
assert.equal(tooltipEl.style.visibility, 'hidden');
});
it('disappears when the target is destroyed', function() {
util.sendEvent(targetEl, 'mouseover');
angular
.element(targetEl)
.scope()
.$broadcast('$destroy');
assert.equal(tooltipEl.style.visibility, 'hidden');
});
});
...@@ -10,30 +10,6 @@ function hyphenate(name) { ...@@ -10,30 +10,6 @@ function hyphenate(name) {
return name.replace(uppercasePattern, '-$1').toLowerCase(); return name.replace(uppercasePattern, '-$1').toLowerCase();
} }
/**
* Helper for retrieving an Angular module in a test.
*
* Given the 'inject' function from the 'angular-mocks' module,
* retrieves an instance of the specified Angular module.
*/
export function ngModule(inject, name) {
let module;
const helper = function(_module) {
module = _module;
};
// Tell Angular which module we want using $inject
// annotations. These take priority over function argument names.
helper.$inject = [name];
// inject() creates a new 'angular.$injector' service instance
// for the current test, if one has not already been created and then
// calls the passed function, injecting the modules it depends upon.
inject(helper);
return module;
}
/** /**
* A helper for instantiating an AngularJS directive in a unit test. * A helper for instantiating an AngularJS directive in a unit test.
* *
...@@ -176,28 +152,3 @@ export function sendEvent(element, eventType) { ...@@ -176,28 +152,3 @@ export function sendEvent(element, eventType) {
const event = new Event(eventType, { bubbles: true, cancelable: true }); const event = new Event(eventType, { bubbles: true, cancelable: true });
element.dispatchEvent(event); element.dispatchEvent(event);
} }
/**
* Return true if a given element is hidden on the page.
*
* There are many possible ways of hiding DOM elements on a page, this just
* looks for approaches that are common in our app.
*/
export function isHidden(element) {
const style = window.getComputedStyle(element);
if (style.display === 'none') {
return true;
}
// Test for element or ancestor being hidden with `ng-hide` directive
let el = element;
while (el) {
if (el.classList.contains('ng-hide')) {
return true;
}
el = el.parentElement;
}
return false;
}
import * as url from '../url';
describe('url.encode', function() {
it('urlencodes its input', function() {
const expect = 'http%3A%2F%2Ffoo.com%2Fhello%20there.pdf';
const result = url.encode('http://foo.com/hello there.pdf');
assert.equal(result, expect);
});
it('returns the empty string for null values', function() {
assert.equal(url.encode(null), '');
assert.equal(url.encode(undefined), '');
});
});
/**
* URL encode a string, dealing appropriately with null values.
*/
export function encode(str) {
if (str) {
return window.encodeURIComponent(str);
}
return '';
}
...@@ -162,10 +162,7 @@ import threadList from './components/thread-list'; ...@@ -162,10 +162,7 @@ import threadList from './components/thread-list';
// Angular directives. // Angular directives.
import hAutofocusDirective from './directive/h-autofocus';
import hBrandingDirective from './directive/h-branding'; import hBrandingDirective from './directive/h-branding';
import hOnTouchDirective from './directive/h-on-touch';
import hTooltipDirective from './directive/h-tooltip';
import windowScrollDirective from './directive/window-scroll'; import windowScrollDirective from './directive/window-scroll';
// Services. // Services.
...@@ -296,10 +293,7 @@ function startAngularApp(config) { ...@@ -296,10 +293,7 @@ function startAngularApp(config) {
.component('tagList', wrapComponent(TagList)) .component('tagList', wrapComponent(TagList))
.component('threadList', threadList) .component('threadList', threadList)
.component('topBar', wrapComponent(TopBar)) .component('topBar', wrapComponent(TopBar))
.directive('hAutofocus', hAutofocusDirective)
.directive('hBranding', hBrandingDirective) .directive('hBranding', hBrandingDirective)
.directive('hOnTouch', hOnTouchDirective)
.directive('hTooltip', hTooltipDirective)
.directive('windowScroll', windowScrollDirective) .directive('windowScroll', windowScrollDirective)
// Register services, the store and utilities with Angular, so that // Register services, the store and utilities with Angular, so that
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment