diff --git a/src/ui/public/chrome/appSwitcher/__tests__/appSwitcher.js b/src/ui/public/chrome/appSwitcher/__tests__/appSwitcher.js
index 3135f6bcb519..35c33c7ef1f4 100644
--- a/src/ui/public/chrome/appSwitcher/__tests__/appSwitcher.js
+++ b/src/ui/public/chrome/appSwitcher/__tests__/appSwitcher.js
@@ -11,22 +11,6 @@ require('ui/chrome');
require('ui/chrome/appSwitcher');
var DomLocationProvider = require('ui/domLocation');
-function findTestSubject() {
- var subjectSelectors = [].slice.apply(arguments);
- var $context = subjectSelectors.shift();
- var $els = $();
-
- subjectSelectors.forEach(function (subjects) {
- var selector = subjects.split(/\s+/).map(function (subject) {
- return '[data-test-subj~="' + subject + '"]';
- }).join(' ');
-
- $els = $els.add($context.find(selector));
- });
-
- return $els;
-}
-
describe('appSwitcher directive', function () {
var env;
@@ -79,13 +63,13 @@ describe('appSwitcher directive', function () {
beforeEach(setup('http://localhost:5555/app/myApp/', [myLink, notMyLink]));
it('links to the inactive apps base url', function () {
- var $myLink = findTestSubject(env.$el, 'appLink').eq(0);
+ var $myLink = env.$el.findTestSubject('appLink').eq(0);
expect($myLink.prop('href')).to.be(myLink.url);
expect($myLink.prop('href')).to.not.be(myLink.lastSubUrl);
});
it('links to the inactive apps last sub url', function () {
- var $notMyLink = findTestSubject(env.$el, 'appLink').eq(1);
+ var $notMyLink = env.$el.findTestSubject('appLink').eq(1);
expect($notMyLink.prop('href')).to.be(notMyLink.lastSubUrl);
expect($notMyLink.prop('href')).to.not.be(notMyLink.url);
});
@@ -109,7 +93,7 @@ describe('appSwitcher directive', function () {
beforeEach(setup('http://localhost:5555/app/myApp/', [myLink, notMyLink]));
it('links to the lastSubUrl for each', function () {
- var $links = findTestSubject(env.$el, 'appLink');
+ var $links = env.$el.findTestSubject('appLink');
var $myLink = $links.eq(0);
var $notMyLink = $links.eq(1);
@@ -134,7 +118,7 @@ describe('appSwitcher directive', function () {
expect(event.isDefaultPrevented()).to.be(false);
expect(event.isPropagationStopped()).to.be(false);
- var $link = findTestSubject(env.$el, 'appLink');
+ var $link = env.$el.findTestSubject('appLink');
expect($link.prop('href')).to.be(url);
$link.trigger(event);
@@ -157,7 +141,7 @@ describe('appSwitcher directive', function () {
expect(event.isDefaultPrevented()).to.be(false);
expect(event.isPropagationStopped()).to.be(false);
- var $link = findTestSubject(env.$el, 'appLink');
+ var $link = env.$el.findTestSubject('appLink');
expect($link.prop('href')).to.be(env.currentHref);
$link.trigger(event);
@@ -182,7 +166,7 @@ describe('appSwitcher directive', function () {
expect(event.isDefaultPrevented()).to.be(false);
expect(event.isPropagationStopped()).to.be(false);
- var $link = findTestSubject(env.$el, 'appLink');
+ var $link = env.$el.findTestSubject('appLink');
expect($link.prop('href')).to.be(url);
$link.trigger(event);
@@ -206,7 +190,7 @@ describe('appSwitcher directive', function () {
expect(env.location.reload.callCount).to.be(0);
expect(event.isPropagationStopped()).to.be(false);
- findTestSubject(env.$el, 'appLink').trigger(event);
+ env.$el.findTestSubject('appLink').trigger(event);
expect(env.location.reload.callCount).to.be(0);
expect(event.isPropagationStopped()).to.be(false);
@@ -227,7 +211,7 @@ describe('appSwitcher directive', function () {
expect(env.location.reload.callCount).to.be(0);
expect(event.isPropagationStopped()).to.be(false);
- findTestSubject(env.$el, 'appLink').trigger(event);
+ env.$el.findTestSubject('appLink').trigger(event);
expect(env.location.reload.callCount).to.be(0);
expect(event.isPropagationStopped()).to.be(false);
diff --git a/src/ui/public/jquery/__tests__/findTestSubject.js b/src/ui/public/jquery/__tests__/findTestSubject.js
new file mode 100644
index 000000000000..891dbdd81a95
--- /dev/null
+++ b/src/ui/public/jquery/__tests__/findTestSubject.js
@@ -0,0 +1,69 @@
+var $ = require('jquery');
+var expect = require('expect.js');
+
+function $make(subject) {
+ return $('
').attr('data-test-subj', subject);
+}
+
+describe('jQuery.findTestSubject', function () {
+ it('finds all of the element with a subject', function () {
+ var $container = $('
');
+ var $match = $make('subject').appendTo($container);
+ var $noMatch = $make('notSubject').appendTo($container);
+
+ var $found = $container.findTestSubject('subject');
+ expect($found.is($match)).to.be(true);
+ expect($found.is($noMatch)).to.be(false);
+ });
+
+ it('finds multiple elements with a subject', function () {
+ var $container = $('
');
+ var $match = $make('subject').appendTo($container);
+ var $otherMatch = $make('subject').appendTo($container);
+
+ var $found = $container.findTestSubject('subject');
+ expect($found.filter($match).size()).to.be(1);
+ expect($found.filter($otherMatch).size()).to.be(1);
+ });
+
+ it('finds all of the elements with either subject', function () {
+ var $container = $('
');
+ var $match1 = $make('subject').appendTo($container);
+ var $match2 = $make('alsoSubject').appendTo($container);
+ var $noMatch = $make('notSubject').appendTo($container);
+
+ var $found = $container.findTestSubject('subject', 'alsoSubject');
+ expect($found.filter($match1).size()).to.be(1);
+ expect($found.filter($match2).size()).to.be(1);
+ expect($found.filter($noMatch).size()).to.be(0);
+ });
+
+ it('finds all of the elements with a decendant selector', function () {
+ var $container = $('
');
+ var $parent = $make('foo name').appendTo($container);
+ var $bar = $make('bar othername').appendTo($parent);
+ var $baz = $make('baz third name').appendTo($parent);
+
+ expect($container.findTestSubject('foo bar').is($bar)).to.be(true);
+ expect($container.findTestSubject('foo bar').is($baz)).to.be(false);
+
+ expect($container.findTestSubject('foo baz').is($bar)).to.be(false);
+ expect($container.findTestSubject('foo baz').is($baz)).to.be(true);
+ });
+
+ it('finds elements with compound subjects', function () {
+ var $container = $('
');
+ var $bar = $make('button bar').appendTo($container);
+ var $baz = $make('button baz').appendTo($container);
+
+ expect($container.findTestSubject('button&bar').is($bar)).to.be(true);
+ expect($container.findTestSubject('button& bar').is($bar)).to.be(true);
+ expect($container.findTestSubject('button & bar').is($bar)).to.be(true);
+ expect($container.findTestSubject('button &bar').is($bar)).to.be(true);
+
+ expect($container.findTestSubject('button&baz').is($baz)).to.be(true);
+ expect($container.findTestSubject('button& baz').is($baz)).to.be(true);
+ expect($container.findTestSubject('button & baz').is($baz)).to.be(true);
+ expect($container.findTestSubject('button &baz').is($baz)).to.be(true);
+ });
+});
diff --git a/src/ui/public/jquery/findTestSubject.js b/src/ui/public/jquery/findTestSubject.js
new file mode 100644
index 000000000000..6ffb01789a9a
--- /dev/null
+++ b/src/ui/public/jquery/findTestSubject.js
@@ -0,0 +1,71 @@
+module.exports = function bindToJquery($) {
+
+ /**
+ * Find elements with the `data-test-subj` attribute by the terms in that attribute.
+ *
+ * ```js
+ * // this
+ * var $button = $('[data-test-subj~="saveButton"]');
+ *
+ * // becomes this
+ * var $button = $.findTestSubject('saveButton');
+ * ```
+ *
+ * Supports multiple subjects
+ * ```js
+ * // find any saveButton or cancelButton
+ * var $buttons = $.findTestSubject('saveButton', 'cancelButton');
+ * ```
+ *
+ * Supports subject "selectors"
+ * ```js
+ * // find any saveButton inside a savedObjectForm
+ * var $button = $.findTestSubject('savedObjectForm saveButton');
+ * ```
+ *
+ * Supports selecting compound subjects
+ * ```js
+ * // find any smallButton that is also a saveButton inside a savedObjectForm
+ * var $input = $.findTestSubject('savedObjectForm smallButton&saveButton');
+ * ```
+ *
+ * @return {jQueryCollection}
+ */
+ $.findTestSubject = function () {
+ return findTestSubject.apply($(document.body), arguments);
+ };
+
+ /**
+ * Just like $.findTestSubject, except only finds elements within another element.
+ * @return {jQueryCollection}
+ */
+ $.fn.findTestSubject = findTestSubject;
+
+ function findTestSubject(/* ...subjectSelectors */) {
+ var subjectSelectors = [].slice.apply(arguments);
+ var $els = $();
+ var $context = this;
+
+ subjectSelectors.forEach(function (subjectSelector) {
+ var cssSelectors = [];
+ var terms = subjectSelector
+ .replace(/\s*&\s*/g, '&') // remove all whitespace around joins
+ .split(/\s+/);
+
+ function termToCssSelector(term) {
+ return term ? '[data-test-subj~="' + term + '"]' : '';
+ }
+
+ while (terms.length) {
+ var term = terms.shift();
+ // split each term by joins/& and map to css selectors
+ cssSelectors.push(term.split('&').map(termToCssSelector).join(''));
+ }
+
+ $els = $els.add($context.find(cssSelectors.join(' ')));
+ });
+
+ return $els;
+ };
+
+};
diff --git a/webpackShims/jquery.js b/webpackShims/jquery.js
index 4cef6e2479f3..e9ac7b7cbfda 100644
--- a/webpackShims/jquery.js
+++ b/webpackShims/jquery.js
@@ -1 +1,2 @@
-window.jQuery = window.$ = module.exports = require('node_modules/jquery/dist/jquery');
+var $ = window.jQuery = window.$ = module.exports = require('node_modules/jquery/dist/jquery');
+require('ui/jquery/findTestSubject')($);