From 315b9d796dd5f1e1095fd8ce2dd6b3a9866eff43 Mon Sep 17 00:00:00 2001
From: Joffrey JAFFEUX <j.jaffeux@gmail.com>
Date: Fri, 22 Dec 2017 13:08:12 +0100
Subject: [PATCH] select-kit refactoring

* better test helper
* more reliable tests
* more consistent use of data-value/data-name/title/aria-label everywhere: header and rows
---
 .eslintrc                                     |   5 +-
 .../discourse/controllers/composer.js.es6     |   4 +
 .../components/edit-category-security.hbs     |   2 +-
 .../templates/preferences/notifications.hbs   |   2 +-
 .../components/category-selector.js.es6       |   5 +
 .../dropdown-select-box-row.js.es6            |   1 -
 .../select-kit/components/multi-select.js.es6 |   1 +
 .../multi-select/multi-select-header.js.es6   |  22 +-
 .../multi-select/selected-category.js.es6     |   5 +-
 .../multi-select/selected-color.js.es6        |   2 +-
 .../multi-select/selected-name.js.es6         |  30 ++-
 .../components/pinned-options.js.es6          |   5 +-
 .../select-kit/components/select-kit.js.es6   |   4 +-
 .../select-kit/select-kit-header.js.es6       |  27 ++-
 .../select-kit/select-kit-row.js.es6          |  28 ++-
 .../components/single-select.js.es6           |   2 +
 .../templates/components/category-row.hbs     |   2 +-
 .../components/combo-box/combo-box-header.hbs |   4 +-
 .../dropdown-select-box-header.hbs            |   2 +-
 .../dropdown-select-box-row.hbs               |   2 +-
 .../future-date-input-selector-header.hbs     |   2 +-
 .../future-date-input-selector-row.hbs        |   2 +-
 .../multi-select/multi-select-header.hbs      |   2 +-
 .../multi-select/selected-category.hbs        |   2 +-
 .../multi-select/selected-color.hbs           |   4 +-
 .../components/multi-select/selected-name.hbs |   4 +-
 .../templates/components/select-kit.hbs       |   1 -
 .../select-kit/select-kit-collection.hbs      |   4 +-
 .../select-kit/select-kit-header.hbs          |   4 +-
 .../components/select-kit/select-kit-row.hbs  |   2 +-
 .../acceptance/details-button-test.js.es6     |  27 ++-
 .../poll-builder-disabled-test.js.es6         |   4 +
 .../poll-builder-enabled-test.js.es6          |   4 +
 .../javascripts/acceptance/polls-test.js.es6  |   6 +-
 .../display-poll-builder-button.js.es6        |   3 +-
 .../acceptance/admin-flags-test.js.es6        |  60 ++----
 .../acceptance/admin-suspend-user-test.js.es6 |   9 +-
 .../acceptance/category-chooser-test.js.es6   |  20 +-
 .../category-edit-security-test.js.es6        |  26 +--
 .../acceptance/category-edit-test.js.es6      |  10 +-
 .../acceptance/composer-test.js.es6           |   7 +-
 .../acceptance/preferences-test.js.es6        |   4 +-
 .../acceptance/search-full-test.js.es6        |  36 ++--
 .../javascripts/acceptance/search-test.js.es6 |  48 +++--
 .../acceptance/topic-edit-timer-test.js.es6   | 117 +++++++----
 .../topic-notifications-button-test.js.es6    |  20 +-
 test/javascripts/acceptance/topic-test.js.es6 |  11 +-
 .../categories-admin-dropdown-test.js.es6     |  10 +-
 .../components/category-chooser-test.js.es6   |  62 +++---
 .../components/category-selector-test.js.es6  |  87 ++++++++
 .../components/categpry-selector-test.js.es6  |  67 ------
 .../components/combo-box-test.js.es6          | 125 ++++-------
 .../components/list-setting-test.js.es6       |  35 ++--
 .../components/multi-select-test.js.es6       |  96 ++++++---
 .../components/pinned-options-test.js.es6     |  15 +-
 .../components/single-select-test.js.es6      | 197 +++++++++++-------
 .../topic-footer-mobile-dropdown-test.js.es6  |  27 ++-
 .../topic-notifications-button-test.js.es6    |   4 +-
 test/javascripts/helpers/assertions.js        |   6 -
 test/javascripts/helpers/select-kit-helper.js | 143 ++++++++-----
 60 files changed, 827 insertions(+), 641 deletions(-)
 create mode 100644 test/javascripts/components/category-selector-test.js.es6
 delete mode 100644 test/javascripts/components/categpry-selector-test.js.es6

diff --git a/.eslintrc b/.eslintrc
index bfbe34ea65e..218cb72b81e 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -41,11 +41,12 @@
     "visible":true,
     "invisible":true,
     "asyncRender":true,
-    "selectDropdown":true,
     "selectKit":true,
     "expandSelectKit":true,
     "collapseSelectKit":true,
-    "selectKitSelectRow":true,
+    "selectKitSelectRowByValue":true,
+    "selectKitSelectRowByName":true,
+    "selectKitSelectRowByIndex":true,
     "selectKitSelectNoneRow":true,
     "selectKitFillInFilter":true,
     "asyncTestDiscourse":true,
diff --git a/app/assets/javascripts/discourse/controllers/composer.js.es6 b/app/assets/javascripts/discourse/controllers/composer.js.es6
index 0e6f9c6193a..35f6b256e42 100644
--- a/app/assets/javascripts/discourse/controllers/composer.js.es6
+++ b/app/assets/javascripts/discourse/controllers/composer.js.es6
@@ -49,6 +49,10 @@ function loadDraft(store, opts) {
 
 const _popupMenuOptionsCallbacks = [];
 
+export function clearPopupMenuOptionsCallback() {
+  _popupMenuOptionsCallbacks.length = 0;
+}
+
 export function addPopupMenuOptionsCallback(callback) {
   _popupMenuOptionsCallbacks.push(callback);
 }
diff --git a/app/assets/javascripts/discourse/templates/components/edit-category-security.hbs b/app/assets/javascripts/discourse/templates/components/edit-category-security.hbs
index fb0a13c213d..ad078cda2f0 100644
--- a/app/assets/javascripts/discourse/templates/components/edit-category-security.hbs
+++ b/app/assets/javascripts/discourse/templates/components/edit-category-security.hbs
@@ -18,7 +18,7 @@
     {{#if category.availableGroups}}
       {{combo-box class="available-groups"
                   allowInitialValueMutation=true
-                  allowsContentReplacement=true
+                  allowContentReplacement=true
                   content=category.availableGroups
                   value=selectedGroup}}
       {{combo-box allowInitialValueMutation=true
diff --git a/app/assets/javascripts/discourse/templates/preferences/notifications.hbs b/app/assets/javascripts/discourse/templates/preferences/notifications.hbs
index 987a9c13863..c0441202b4a 100644
--- a/app/assets/javascripts/discourse/templates/preferences/notifications.hbs
+++ b/app/assets/javascripts/discourse/templates/preferences/notifications.hbs
@@ -1,7 +1,7 @@
 <div class="control-group notifications">
   <div class="controls controls-dropdown">
     <label>{{i18n 'user.new_topic_duration.label'}}</label>
-    {{combo-box valueAttribute="value" content=considerNewTopicOptions value=model.user_option.new_topic_duration_minutes}}
+    {{combo-box class="duration" valueAttribute="value" content=considerNewTopicOptions value=model.user_option.new_topic_duration_minutes}}
   </div>
 
   <div class="controls controls-dropdown">
diff --git a/app/assets/javascripts/select-kit/components/category-selector.js.es6 b/app/assets/javascripts/select-kit/components/category-selector.js.es6
index 0c31747cbcc..9484e13844e 100644
--- a/app/assets/javascripts/select-kit/components/category-selector.js.es6
+++ b/app/assets/javascripts/select-kit/components/category-selector.js.es6
@@ -7,10 +7,15 @@ export default MultiSelectComponent.extend({
   filterable: true,
   allowAny: false,
   rowComponent: "category-row",
+  categories: null,
+  blacklist: null,
 
   init() {
     this._super();
 
+    if (!this.get("categories")) this.set("categories", []);
+    if (!this.get("blacklist")) this.set("blacklist", []);
+
     this.get("headerComponentOptions").setProperties({
       selectedNameComponent: "multi-select/selected-category"
     });
diff --git a/app/assets/javascripts/select-kit/components/dropdown-select-box/dropdown-select-box-row.js.es6 b/app/assets/javascripts/select-kit/components/dropdown-select-box/dropdown-select-box-row.js.es6
index fd865fb6021..0d5989717c9 100644
--- a/app/assets/javascripts/select-kit/components/dropdown-select-box/dropdown-select-box-row.js.es6
+++ b/app/assets/javascripts/select-kit/components/dropdown-select-box/dropdown-select-box-row.js.es6
@@ -4,6 +4,5 @@ export default SelectKitRowComponent.extend({
   layoutName: "select-kit/templates/components/dropdown-select-box/dropdown-select-box-row",
   classNames: "dropdown-select-box-row",
 
-  name: Ember.computed.alias("computedContent.name"),
   description: Ember.computed.alias("computedContent.originalContent.description")
 });
diff --git a/app/assets/javascripts/select-kit/components/multi-select.js.es6 b/app/assets/javascripts/select-kit/components/multi-select.js.es6
index 9358d75e619..2929584f814 100644
--- a/app/assets/javascripts/select-kit/components/multi-select.js.es6
+++ b/app/assets/javascripts/select-kit/components/multi-select.js.es6
@@ -116,6 +116,7 @@ export default SelectKitComponent.extend({
 
   baseHeaderComputedContent() {
     return {
+      title: this.get("title"),
       selectedComputedContents: this.get("selectedComputedContents")
     };
   },
diff --git a/app/assets/javascripts/select-kit/components/multi-select/multi-select-header.js.es6 b/app/assets/javascripts/select-kit/components/multi-select/multi-select-header.js.es6
index d86af77ed8b..2db20196894 100644
--- a/app/assets/javascripts/select-kit/components/multi-select/multi-select-header.js.es6
+++ b/app/assets/javascripts/select-kit/components/multi-select/multi-select-header.js.es6
@@ -3,14 +3,23 @@ import computed from "ember-addons/ember-computed-decorators";
 import SelectKitHeaderComponent from "select-kit/components/select-kit/select-kit-header";
 
 export default SelectKitHeaderComponent.extend({
-  attributeBindings: ["names:data-name"],
+  attributeBindings: [
+    "label:title",
+    "label:aria-label",
+    "names:data-name",
+    "values:data-value"
+  ],
   classNames: "multi-select-header",
   layoutName: "select-kit/templates/components/multi-select/multi-select-header",
   selectedNameComponent: Ember.computed.alias("options.selectedNameComponent"),
 
+  ariaLabel: Ember.computed.or("computedContent.ariaLabel", "title", "names"),
+
+  title: Ember.computed.or("computedContent.title", "names"),
+
   @on("didRender")
   _positionFilter() {
-    if (this.get("shouldDisplayFilter") === false) { return; }
+    if (!this.get("shouldDisplayFilter")) return;
 
     const $filter = this.$(".filter");
     $filter.width(0);
@@ -26,7 +35,12 @@ export default SelectKitHeaderComponent.extend({
   },
 
   @computed("computedContent.selectedComputedContents.[]")
-  names(selectedComputedContents) {
-    return Ember.makeArray(selectedComputedContents).map(sc => sc.name).join(",");
+  names(selection) {
+    return Ember.makeArray(selection).map(s => s.name).join(",");
+  },
+
+  @computed("computedContent.selectedComputedContents.[]")
+  values(selection) {
+    return Ember.makeArray(selection).map(s => s.value).join(",");
   }
 });
diff --git a/app/assets/javascripts/select-kit/components/multi-select/selected-category.js.es6 b/app/assets/javascripts/select-kit/components/multi-select/selected-category.js.es6
index 0a09d69fabb..200f6a747a5 100644
--- a/app/assets/javascripts/select-kit/components/multi-select/selected-category.js.es6
+++ b/app/assets/javascripts/select-kit/components/multi-select/selected-category.js.es6
@@ -6,8 +6,9 @@ export default SelectedNameComponent.extend({
   classNames: "selected-category",
   layoutName: "select-kit/templates/components/multi-select/selected-category",
 
-  @computed("content.originalContent")
+  @computed("computedContent.originalContent")
   badge(category) {
-    return categoryBadgeHTML(category, {allowUncategorized: true, link: false}).htmlSafe();
+    return categoryBadgeHTML(category, { allowUncategorized: true, link: false })
+            .htmlSafe();
   }
 });
diff --git a/app/assets/javascripts/select-kit/components/multi-select/selected-color.js.es6 b/app/assets/javascripts/select-kit/components/multi-select/selected-color.js.es6
index 3349d95ff8a..7e835e84392 100644
--- a/app/assets/javascripts/select-kit/components/multi-select/selected-color.js.es6
+++ b/app/assets/javascripts/select-kit/components/multi-select/selected-color.js.es6
@@ -5,7 +5,7 @@ export default SelectedNameComponent.extend({
   layoutName: "select-kit/templates/components/multi-select/selected-color",
 
   didRender() {
-    const name = this.get("content.name");
+    const name = this.get("name");
     this.$(".color-preview").css("background", `#${name}`.htmlSafe());
   }
 });
diff --git a/app/assets/javascripts/select-kit/components/multi-select/selected-name.js.es6 b/app/assets/javascripts/select-kit/components/multi-select/selected-name.js.es6
index a201791c039..a6b672d50ed 100644
--- a/app/assets/javascripts/select-kit/components/multi-select/selected-name.js.es6
+++ b/app/assets/javascripts/select-kit/components/multi-select/selected-name.js.es6
@@ -3,8 +3,10 @@ import computed from "ember-addons/ember-computed-decorators";
 export default Ember.Component.extend({
   attributeBindings: [
     "tabindex",
-    "content.name:data-name",
-    "content.value:data-value",
+    "ariaLabel:aria-label",
+    "title",
+    "name:data-name",
+    "value:data-value",
     "guid:data-guid"
   ],
   classNames: ["selected-name", "choice"],
@@ -13,11 +15,27 @@ export default Ember.Component.extend({
   tagName: "span",
   tabindex: -1,
 
-  @computed("content")
-  guid(content) { return Ember.guidFor(content); },
+  @computed("computedContent")
+  guid(computedContent) { return Ember.guidFor(computedContent); },
 
-  isLocked: Ember.computed("content.locked", function() {
-    return this.getWithDefault("content.locked", false);
+  ariaLabel: Ember.computed.or("computedContent.ariaLabel", "title"),
+
+  @computed("computedContent.title", "name")
+  title(computedContentTitle, name) {
+    if (computedContentTitle) return computedContentTitle;
+    if (name) return name;
+
+    return null;
+  },
+
+  label: Ember.computed.or("computedContent.label", "title", "name"),
+
+  name: Ember.computed.alias("computedContent.name"),
+
+  value: Ember.computed.alias("computedContent.value"),
+
+  isLocked: Ember.computed("computedContent.locked", function() {
+    return this.getWithDefault("computedContent.locked", false);
   }),
 
   click() {
diff --git a/app/assets/javascripts/select-kit/components/pinned-options.js.es6 b/app/assets/javascripts/select-kit/components/pinned-options.js.es6
index 4d1aefb9d0c..ca4c65289c7 100644
--- a/app/assets/javascripts/select-kit/components/pinned-options.js.es6
+++ b/app/assets/javascripts/select-kit/components/pinned-options.js.es6
@@ -17,8 +17,9 @@ export default DropdownSelectBoxComponent.extend({
     const state = pinned ? `pinned${globally}` : "unpinned";
     const title = I18n.t(`topic_statuses.${state}.title`);
 
-    content.name = `${title}${iconHTML("caret-down")}`.htmlSafe();
-    content.dataName = title;
+    content.label = `${title}${iconHTML("caret-down")}`.htmlSafe();
+    content.title = title;
+    content.name = state;
     content.icon = `thumb-tack${state === "unpinned" ? " unpinned" : ''}`;
     return content;
   },
diff --git a/app/assets/javascripts/select-kit/components/select-kit.js.es6 b/app/assets/javascripts/select-kit/components/select-kit.js.es6
index c34c8b10f8a..90cb7c9d99c 100644
--- a/app/assets/javascripts/select-kit/components/select-kit.js.es6
+++ b/app/assets/javascripts/select-kit/components/select-kit.js.es6
@@ -61,7 +61,7 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi
   computedContent: null,
   limitMatches: 100,
   nameChanges: false,
-  allowsContentReplacement: false,
+  allowContentReplacement: false,
   collectionHeader: null,
 
   init() {
@@ -82,7 +82,7 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi
       this.addObserver(`content.@each.${this.get("nameProperty")}`, this, this._compute);
     }
 
-    if (this.get("allowsContentReplacement")) {
+    if (this.get("allowContentReplacement")) {
       this.addObserver(`content.[]`, this, this._compute);
     }
   },
diff --git a/app/assets/javascripts/select-kit/components/select-kit/select-kit-header.js.es6 b/app/assets/javascripts/select-kit/components/select-kit/select-kit-header.js.es6
index e8277095687..b7705fa9991 100644
--- a/app/assets/javascripts/select-kit/components/select-kit/select-kit-header.js.es6
+++ b/app/assets/javascripts/select-kit/components/select-kit/select-kit-header.js.es6
@@ -5,32 +5,37 @@ export default Ember.Component.extend({
   classNames: ["select-kit-header", "select-box-kit-header"],
   classNameBindings: ["isFocused"],
   attributeBindings: [
-    "dataName:data-name",
     "tabindex",
     "ariaLabel:aria-label",
     "ariaHasPopup:aria-haspopup",
-    "title"
+    "title",
+    "value:data-value",
+    "name:data-name",
   ],
 
   ariaHasPopup: true,
 
-  ariaLabel: Ember.computed.alias("title"),
+  ariaLabel: Ember.computed.or("computedContent.ariaLabel", "title"),
+
+  @computed("computedContent.title", "name")
+  title(computedContentTitle, name) {
+    if (computedContentTitle) return computedContentTitle;
+    if (name) return name;
+
+    return null;
+  },
+
+  label: Ember.computed.or("computedContent.label", "title", "name"),
 
   name: Ember.computed.alias("computedContent.name"),
 
+  value: Ember.computed.alias("computedContent.value"),
+
   @computed("computedContent.icon", "computedContent.icons")
   icons(icon, icons) {
     return Ember.makeArray(icon).concat(icons).filter(i => !Ember.isEmpty(i));
   },
 
-  @computed("computedContent.dataName", "name")
-  dataName(dataName, name) { return dataName || name; },
-
-  @computed("title", "computedContent.title", "name")
-  title(title, computedContentTitle, name) {
-    return title || computedContentTitle || name;
-  },
-
   click() {
     this.sendAction("onToggle");
   }
diff --git a/app/assets/javascripts/select-kit/components/select-kit/select-kit-row.js.es6 b/app/assets/javascripts/select-kit/components/select-kit/select-kit-row.js.es6
index f9c46b0a1e9..d4638a9386d 100644
--- a/app/assets/javascripts/select-kit/components/select-kit/select-kit-row.js.es6
+++ b/app/assets/javascripts/select-kit/components/select-kit/select-kit-row.js.es6
@@ -11,23 +11,35 @@ export default Ember.Component.extend(UtilsMixin, {
   attributeBindings: [
     "tabIndex",
     "title",
-    "computedContent.value:data-value",
-    "computedContent.name:data-name"
+    "value:data-value",
+    "name:data-name",
+    "ariaLabel:aria-label"
   ],
   classNameBindings: ["isHighlighted", "isSelected"],
 
-  @computed("computedContent.title", "computedContent.name")
-  title(title, name) { return title || name; },
+  ariaLabel: Ember.computed.or("computedContent.ariaLabel", "title"),
+
+  @computed("computedContent.title", "name")
+  title(computedContentTitle, name) {
+    if (computedContentTitle) return computedContentTitle;
+    if (name) return name;
+
+    return null;
+  },
+
+  label: Ember.computed.or("computedContent.label", "title", "name"),
+
+  name: Ember.computed.alias("computedContent.name"),
+
+  value: Ember.computed.alias("computedContent.value"),
 
   @computed("templateForRow")
   template(templateForRow) { return templateForRow(this); },
 
   @on("didReceiveAttrs")
   _setSelectionState() {
-    const contentValue = this.get("computedContent.value");
-
-    this.set("isSelected", this.get("computedValue") === contentValue);
-    this.set("isHighlighted", this.get("highlightedValue") === contentValue);
+    this.set("isSelected", this.get("computedValue") === this.get("value"));
+    this.set("isHighlighted", this.get("highlightedValue") === this.get("value"));
   },
 
   @on("willDestroyElement")
diff --git a/app/assets/javascripts/select-kit/components/single-select.js.es6 b/app/assets/javascripts/select-kit/components/single-select.js.es6
index c05c6b7db00..8e2a35a5ef4 100644
--- a/app/assets/javascripts/select-kit/components/single-select.js.es6
+++ b/app/assets/javascripts/select-kit/components/single-select.js.es6
@@ -78,7 +78,9 @@ export default SelectKitComponent.extend({
 
   baseHeaderComputedContent() {
     return {
+      title: this.get("title"),
       icons: Ember.makeArray(this.getWithDefault("headerIcon", [])),
+      value: this.get("selectedComputedContent.value"),
       name: this.get("selectedComputedContent.name") || this.get("noneRowComputedContent.name")
     };
   },
diff --git a/app/assets/javascripts/select-kit/templates/components/category-row.hbs b/app/assets/javascripts/select-kit/templates/components/category-row.hbs
index 0f3249ea46f..6ea0bf428cf 100644
--- a/app/assets/javascripts/select-kit/templates/components/category-row.hbs
+++ b/app/assets/javascripts/select-kit/templates/components/category-row.hbs
@@ -15,5 +15,5 @@
       <div class="category-desc">{{{description}}}</div>
     {{/if}}
 {{else}}
-  {{computedContent.name}}
+  {{{label}}}
 {{/if}}
diff --git a/app/assets/javascripts/select-kit/templates/components/combo-box/combo-box-header.hbs b/app/assets/javascripts/select-kit/templates/components/combo-box/combo-box-header.hbs
index adc9440ce54..97a66905a99 100644
--- a/app/assets/javascripts/select-kit/templates/components/combo-box/combo-box-header.hbs
+++ b/app/assets/javascripts/select-kit/templates/components/combo-box/combo-box-header.hbs
@@ -1,7 +1,7 @@
 {{#each icons as |icon|}} {{d-icon icon}} {{/each}}
 
-<span class="selected-name" title={{title}}>
-  {{{name}}}
+<span class="selected-name">
+  {{{label}}}
 </span>
 
 {{#if shouldDisplayClearableButton}}
diff --git a/app/assets/javascripts/select-kit/templates/components/dropdown-select-box/dropdown-select-box-header.hbs b/app/assets/javascripts/select-kit/templates/components/dropdown-select-box/dropdown-select-box-header.hbs
index 9cc8c31cf60..48af8fab951 100644
--- a/app/assets/javascripts/select-kit/templates/components/dropdown-select-box/dropdown-select-box-header.hbs
+++ b/app/assets/javascripts/select-kit/templates/components/dropdown-select-box/dropdown-select-box-header.hbs
@@ -2,6 +2,6 @@
 
 {{#if options.showFullTitle}}
   <span class="d-button-label selected-name">
-    {{name}}
+    {{label}}
   </span>
 {{/if}}
diff --git a/app/assets/javascripts/select-kit/templates/components/dropdown-select-box/dropdown-select-box-row.hbs b/app/assets/javascripts/select-kit/templates/components/dropdown-select-box/dropdown-select-box-row.hbs
index 556551a61f0..d6884bd75ad 100644
--- a/app/assets/javascripts/select-kit/templates/components/dropdown-select-box/dropdown-select-box-row.hbs
+++ b/app/assets/javascripts/select-kit/templates/components/dropdown-select-box/dropdown-select-box-row.hbs
@@ -9,7 +9,7 @@
   {{/if}}
 
   <div class="texts">
-    <span class="name">{{{name}}}</span>
+    <span class="name">{{{label}}}</span>
     <span class="desc">{{{description}}}</span>
   </div>
 {{/if}}
diff --git a/app/assets/javascripts/select-kit/templates/components/future-date-input-selector/future-date-input-selector-header.hbs b/app/assets/javascripts/select-kit/templates/components/future-date-input-selector/future-date-input-selector-header.hbs
index 4c8904e9a32..caa167199ec 100644
--- a/app/assets/javascripts/select-kit/templates/components/future-date-input-selector/future-date-input-selector-header.hbs
+++ b/app/assets/javascripts/select-kit/templates/components/future-date-input-selector/future-date-input-selector-header.hbs
@@ -5,7 +5,7 @@
 {{/if}}
 
 <span class="selected-name" title={{title}}>
-  {{{name}}}
+  {{{label}}}
 </span>
 
 {{#if computedContent.datetime}}
diff --git a/app/assets/javascripts/select-kit/templates/components/future-date-input-selector/future-date-input-selector-row.hbs b/app/assets/javascripts/select-kit/templates/components/future-date-input-selector/future-date-input-selector-row.hbs
index 8d62c2d1f94..a0217afca1f 100644
--- a/app/assets/javascripts/select-kit/templates/components/future-date-input-selector/future-date-input-selector-row.hbs
+++ b/app/assets/javascripts/select-kit/templates/components/future-date-input-selector/future-date-input-selector-row.hbs
@@ -4,7 +4,7 @@
   </div>
 {{/if}}
 
-<span class="name">{{computedContent.name}}</span>
+<span class="name">{{label}}</span>
 
 {{#if computedContent.datetime}}
   <span class="future-date-input-selector-datetime">
diff --git a/app/assets/javascripts/select-kit/templates/components/multi-select/multi-select-header.hbs b/app/assets/javascripts/select-kit/templates/components/multi-select/multi-select-header.hbs
index 466b5c6d251..5cfd580279b 100644
--- a/app/assets/javascripts/select-kit/templates/components/multi-select/multi-select-header.hbs
+++ b/app/assets/javascripts/select-kit/templates/components/multi-select/multi-select-header.hbs
@@ -1,6 +1,6 @@
 <div class="choices">
   {{#each computedContent.selectedComputedContents as |selectedComputedContent|}}
-    {{component selectedNameComponent onDeselect=onDeselect content=selectedComputedContent}}
+    {{component selectedNameComponent onDeselect=onDeselect computedContent=selectedComputedContent}}
   {{/each}}
   <span class="filter choice" tabindex="-1">
     {{component "select-kit/select-kit-filter"
diff --git a/app/assets/javascripts/select-kit/templates/components/multi-select/selected-category.hbs b/app/assets/javascripts/select-kit/templates/components/multi-select/selected-category.hbs
index c220d68cfc3..d96b31245b9 100644
--- a/app/assets/javascripts/select-kit/templates/components/multi-select/selected-category.hbs
+++ b/app/assets/javascripts/select-kit/templates/components/multi-select/selected-category.hbs
@@ -1,5 +1,5 @@
 <span class="name">
-  <span class="delete-icon" {{action onDeselect content bubbles=false}}>
+  <span class="delete-icon" {{action onDeselect computedContent bubbles=false}}>
     {{d-icon "times"}}
   </span>
 
diff --git a/app/assets/javascripts/select-kit/templates/components/multi-select/selected-color.hbs b/app/assets/javascripts/select-kit/templates/components/multi-select/selected-color.hbs
index c0d544ab9d5..2d70a3d7df3 100644
--- a/app/assets/javascripts/select-kit/templates/components/multi-select/selected-color.hbs
+++ b/app/assets/javascripts/select-kit/templates/components/multi-select/selected-color.hbs
@@ -1,12 +1,12 @@
 <div class="selected-color-wrapper">
   <span class="name">
     {{#unless isLocked}}
-      <span class="delete-icon" {{action onDeselect content bubbles=false}}>
+      <span class="delete-icon" {{action onDeselect computedContent bubbles=false}}>
         {{d-icon "times"}}
       </span>
     {{/unless}}
 
-    #{{content.name}}
+    #{{{label}}}
   </span>
 
   <span class="color-preview"></span>
diff --git a/app/assets/javascripts/select-kit/templates/components/multi-select/selected-name.hbs b/app/assets/javascripts/select-kit/templates/components/multi-select/selected-name.hbs
index 721a30ef218..a48a160ccc7 100644
--- a/app/assets/javascripts/select-kit/templates/components/multi-select/selected-name.hbs
+++ b/app/assets/javascripts/select-kit/templates/components/multi-select/selected-name.hbs
@@ -3,11 +3,11 @@
     {{d-icon "lock"}}
   </span>
 {{else}}
-  <span class="locked-icon" {{action onDeselect content bubbles=false}}>
+  <span class="locked-icon" {{action onDeselect computedContent bubbles=false}}>
     {{d-icon "times"}}
   </span>
 {{/if}}
 
 <span class="name">
-  {{content.name}}
+  {{{label}}}
 </span>
diff --git a/app/assets/javascripts/select-kit/templates/components/select-kit.hbs b/app/assets/javascripts/select-kit/templates/components/select-kit.hbs
index adc84ca1e7d..576cab22485 100644
--- a/app/assets/javascripts/select-kit/templates/components/select-kit.hbs
+++ b/app/assets/javascripts/select-kit/templates/components/select-kit.hbs
@@ -9,7 +9,6 @@
   onClear=(action "onClear")
   options=headerComponentOptions
   shouldDisplayFilter=shouldDisplayFilter
-  title=(i18n title)
 }}
 
 <div class="select-kit-body">
diff --git a/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-collection.hbs b/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-collection.hbs
index ca48220bb66..f6cd9cf1a60 100644
--- a/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-collection.hbs
+++ b/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-collection.hbs
@@ -12,7 +12,7 @@
       highlightedValue=highlightedValue
       onClear=onClear
       onHighlight=onHighlight
-      value=computedValue
+      computedValue=computedValue
       options=rowComponentOptions
     }}
   {{/if}}
@@ -25,7 +25,7 @@
     highlightedValue=highlightedValue
     onHighlight=onHighlight
     onCreate=onCreate
-    value=computedValue
+    computedValue=computedValue
     options=rowComponentOptions
   }}
 {{/if}}
diff --git a/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-header.hbs b/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-header.hbs
index abd119afda2..e65ce13500b 100644
--- a/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-header.hbs
+++ b/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-header.hbs
@@ -1,5 +1,5 @@
 {{#each icons as |icon|}} {{d-icon icon}} {{/each}}
 
-<span class="selected-name" title={{name}}>
-  {{{name}}}
+<span class="selected-name">
+  {{{label}}}
 </span>
diff --git a/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-row.hbs b/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-row.hbs
index 80444173f40..fc0df658357 100644
--- a/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-row.hbs
+++ b/app/assets/javascripts/select-kit/templates/components/select-kit/select-kit-row.hbs
@@ -2,5 +2,5 @@
   {{{template}}}
 {{else}}
   {{#each icons as |icon|}} {{d-icon icon}} {{/each}}
-  <span class="name">{{computedContent.name}}</span>
+  <span class="name">{{{label}}}</span>
 {{/if}}
diff --git a/plugins/discourse-details/test/javascripts/acceptance/details-button-test.js.es6 b/plugins/discourse-details/test/javascripts/acceptance/details-button-test.js.es6
index 79bbeddcdfe..a22705223e4 100644
--- a/plugins/discourse-details/test/javascripts/acceptance/details-button-test.js.es6
+++ b/plugins/discourse-details/test/javascripts/acceptance/details-button-test.js.es6
@@ -1,16 +1,24 @@
 import { acceptance } from "helpers/qunit-helpers";
+import { clearPopupMenuOptionsCallback } from "discourse/controllers/composer";
 
-acceptance('Details Button', { loggedIn: true });
+acceptance('Details Button', {
+  loggedIn: true,
+  beforeEach: function() {
+    clearPopupMenuOptionsCallback();
+  }
+});
 
 function findTextarea() {
   return find(".d-editor-input")[0];
 }
 
 test('details button', (assert) => {
+  const popupMenu = selectKit('.toolbar-popup-menu-options');
+
   visit("/");
   click('#create-topic');
-  expandSelectKit('.toolbar-popup-menu-options');
-  selectKitSelectRow('insertDetails', { selector: '.toolbar-popup-menu-options'});
+
+  popupMenu.expand().selectRowByValue('insertDetails');
 
   andThen(() => {
     assert.equal(
@@ -28,8 +36,7 @@ test('details button', (assert) => {
     textarea.selectionEnd = textarea.value.length;
   });
 
-  expandSelectKit('.toolbar-popup-menu-options');
-  selectKitSelectRow('insertDetails', { selector: '.toolbar-popup-menu-options'});
+  popupMenu.expand().selectRowByValue('insertDetails');
 
   andThen(() => {
     assert.equal(
@@ -51,8 +58,7 @@ test('details button', (assert) => {
     textarea.selectionEnd = 28;
   });
 
-  expandSelectKit('.toolbar-popup-menu-options');
-  selectKitSelectRow('insertDetails', { selector: '.toolbar-popup-menu-options'});
+  popupMenu.expand().selectRowByValue('insertDetails');
 
   andThen(() => {
     assert.equal(
@@ -74,8 +80,7 @@ test('details button', (assert) => {
     textarea.selectionEnd = 29;
   });
 
-  expandSelectKit('.toolbar-popup-menu-options');
-  selectKitSelectRow('insertDetails', { selector: '.toolbar-popup-menu-options'});
+  popupMenu.expand().selectRowByValue('insertDetails');
 
   andThen(() => {
     assert.equal(
@@ -92,6 +97,7 @@ test('details button', (assert) => {
 
 test('details button surrounds all selected text in a single details block', (assert) => {
   const multilineInput = 'first line\n\nsecond line\n\nthird line';
+  const popupMenu = selectKit('.toolbar-popup-menu-options');
 
   visit("/");
   click('#create-topic');
@@ -103,8 +109,7 @@ test('details button surrounds all selected text in a single details block', (as
     textarea.selectionEnd = textarea.value.length;
   });
 
-  expandSelectKit('.toolbar-popup-menu-options');
-  selectKitSelectRow('insertDetails', { selector: '.toolbar-popup-menu-options'});
+  popupMenu.expand().selectRowByValue('insertDetails');
 
   andThen(() => {
     assert.equal(
diff --git a/plugins/poll/test/javascripts/acceptance/poll-builder-disabled-test.js.es6 b/plugins/poll/test/javascripts/acceptance/poll-builder-disabled-test.js.es6
index 4fef0109ddd..69f9fb4365a 100644
--- a/plugins/poll/test/javascripts/acceptance/poll-builder-disabled-test.js.es6
+++ b/plugins/poll/test/javascripts/acceptance/poll-builder-disabled-test.js.es6
@@ -1,12 +1,16 @@
 import { acceptance } from "helpers/qunit-helpers";
 import { displayPollBuilderButton } from "discourse/plugins/poll/helpers/display-poll-builder-button";
 import { replaceCurrentUser } from "discourse/plugins/poll/helpers/replace-current-user";
+import { clearPopupMenuOptionsCallback } from "discourse/controllers/composer";
 
 acceptance("Poll Builder - polls are disabled", {
   loggedIn: true,
   settings: {
     poll_enabled: false,
     poll_minimum_trust_level_to_create: 2
+  },
+  beforeEach: function() {
+    clearPopupMenuOptionsCallback();
   }
 });
 
diff --git a/plugins/poll/test/javascripts/acceptance/poll-builder-enabled-test.js.es6 b/plugins/poll/test/javascripts/acceptance/poll-builder-enabled-test.js.es6
index 9d0eef23fad..4f210446f49 100644
--- a/plugins/poll/test/javascripts/acceptance/poll-builder-enabled-test.js.es6
+++ b/plugins/poll/test/javascripts/acceptance/poll-builder-enabled-test.js.es6
@@ -1,12 +1,16 @@
 import { acceptance } from "helpers/qunit-helpers";
 import { displayPollBuilderButton } from "discourse/plugins/poll/helpers/display-poll-builder-button";
 import { replaceCurrentUser } from "discourse/plugins/poll/helpers/replace-current-user";
+import { clearPopupMenuOptionsCallback } from "discourse/controllers/composer";
 
 acceptance("Poll Builder - polls are enabled", {
   loggedIn: true,
   settings: {
     poll_enabled: true,
     poll_minimum_trust_level_to_create: 1
+  },
+  beforeEach: function() {
+    clearPopupMenuOptionsCallback();
   }
 });
 
diff --git a/plugins/poll/test/javascripts/acceptance/polls-test.js.es6 b/plugins/poll/test/javascripts/acceptance/polls-test.js.es6
index c6cac9d19a5..b6f81eba610 100644
--- a/plugins/poll/test/javascripts/acceptance/polls-test.js.es6
+++ b/plugins/poll/test/javascripts/acceptance/polls-test.js.es6
@@ -1,8 +1,12 @@
 import { acceptance } from "helpers/qunit-helpers";
+import { clearPopupMenuOptionsCallback } from "discourse/controllers/composer";
 
 acceptance("Rendering polls", {
   loggedIn: true,
-  settings: { poll_enabled: true }
+  settings: { poll_enabled: true },
+  beforeEach: function() {
+    clearPopupMenuOptionsCallback();
+  }
 });
 
 test("Single Poll", (assert) => {
diff --git a/plugins/poll/test/javascripts/helpers/display-poll-builder-button.js.es6 b/plugins/poll/test/javascripts/helpers/display-poll-builder-button.js.es6
index 6ee6fa21cdc..ba6bdccdfb7 100644
--- a/plugins/poll/test/javascripts/helpers/display-poll-builder-button.js.es6
+++ b/plugins/poll/test/javascripts/helpers/display-poll-builder-button.js.es6
@@ -2,6 +2,5 @@ export function displayPollBuilderButton() {
   visit("/");
   click("#create-topic");
   click(".d-editor-button-bar .options");
-
-  expandSelectKit('.toolbar-popup-menu-options');
+  selectKit(".toolbar-popup-menu-options").expand();
 }
diff --git a/test/javascripts/acceptance/admin-flags-test.js.es6 b/test/javascripts/acceptance/admin-flags-test.js.es6
index 3b25ee2f087..e64e823039e 100644
--- a/test/javascripts/acceptance/admin-flags-test.js.es6
+++ b/test/javascripts/acceptance/admin-flags-test.js.es6
@@ -13,15 +13,11 @@ QUnit.test("flagged posts", assert => {
 });
 
 QUnit.test("flagged posts - agree", assert => {
+  const agreeFlag = selectKit('.agree-flag');
+
   visit("/admin/flags/active");
 
-  andThen(() => {
-    expandSelectKit('.agree-flag');
-  });
-
-  andThen(() => {
-    selectKitSelectRow('confirm-agree-keep', { selector: '.agree-flag'});
-  });
+  agreeFlag.expand().selectRowByValue('confirm-agree-keep');
 
   andThen(() => {
     assert.equal(find('.admin-flags .flagged-post').length, 0, 'post was removed');
@@ -29,15 +25,11 @@ QUnit.test("flagged posts - agree", assert => {
 });
 
 QUnit.test("flagged posts - agree + hide", assert => {
+  const agreeFlag = selectKit('.agree-flag');
+
   visit("/admin/flags/active");
 
-  andThen(() => {
-    expandSelectKit('.agree-flag');
-  });
-
-  andThen(() => {
-    selectKitSelectRow('confirm-agree-hide', { selector: '.agree-flag'});
-  });
+  agreeFlag.expand().selectRowByValue('confirm-agree-hide');
 
   andThen(() => {
     assert.equal(find('.admin-flags .flagged-post').length, 0, 'post was removed');
@@ -45,15 +37,11 @@ QUnit.test("flagged posts - agree + hide", assert => {
 });
 
 QUnit.test("flagged posts - agree + deleteSpammer", assert => {
+  const agreeFlag = selectKit('.agree-flag');
+
   visit("/admin/flags/active");
 
-  andThen(() => {
-    expandSelectKit('.agree-flag');
-  });
-
-  andThen(() => {
-    selectKitSelectRow('delete-spammer', { selector: '.agree-flag'});
-  });
+  agreeFlag.expand().selectRowByValue('delete-spammer');
 
   click('.confirm-delete');
 
@@ -79,15 +67,11 @@ QUnit.test("flagged posts - defer", assert => {
 });
 
 QUnit.test("flagged posts - delete + defer", assert => {
+  const deleteFlag = selectKit('.delete-flag');
+
   visit("/admin/flags/active");
 
-  andThen(() => {
-    expandSelectKit('.delete-flag');
-  });
-
-  andThen(() => {
-    selectKitSelectRow('delete-defer', { selector: '.delete-flag'});
-  });
+  deleteFlag.expand().selectRowByValue('delete-defer');
 
   andThen(() => {
     assert.equal(find('.admin-flags .flagged-post').length, 0);
@@ -95,15 +79,11 @@ QUnit.test("flagged posts - delete + defer", assert => {
 });
 
 QUnit.test("flagged posts - delete + agree", assert => {
+  const deleteFlag = selectKit('.delete-flag');
+
   visit("/admin/flags/active");
 
-  andThen(() => {
-    expandSelectKit('.delete-flag');
-  });
-
-  andThen(() => {
-    selectKitSelectRow('delete-agree', { selector: '.delete-flag'});
-  });
+  deleteFlag.expand().selectRowByValue('delete-agree');
 
   andThen(() => {
     assert.equal(find('.admin-flags .flagged-post').length, 0);
@@ -111,15 +91,11 @@ QUnit.test("flagged posts - delete + agree", assert => {
 });
 
 QUnit.test("flagged posts - delete + deleteSpammer", assert => {
+  const deleteFlag = selectKit('.delete-flag');
+
   visit("/admin/flags/active");
 
-  andThen(() => {
-    expandSelectKit('.delete-flag');
-  });
-
-  andThen(() => {
-    selectKitSelectRow('delete-spammer', { selector: '.delete-flag'});
-  });
+  deleteFlag.expand().selectRowByValue('delete-spammer');
 
   click('.confirm-delete');
 
diff --git a/test/javascripts/acceptance/admin-suspend-user-test.js.es6 b/test/javascripts/acceptance/admin-suspend-user-test.js.es6
index cfe73206013..cc677268867 100644
--- a/test/javascripts/acceptance/admin-suspend-user-test.js.es6
+++ b/test/javascripts/acceptance/admin-suspend-user-test.js.es6
@@ -33,6 +33,10 @@ QUnit.test("suspend a user - cancel", assert => {
 });
 
 QUnit.test("suspend, then unsuspend a user", assert => {
+  const suspendUntilCombobox = selectKit('.suspend-until .combobox');
+
+  visit("/admin/flags/active");
+
   visit("/admin/users/1234/regular");
 
   andThen(() => {
@@ -43,11 +47,10 @@ QUnit.test("suspend, then unsuspend a user", assert => {
 
   andThen(() => {
     assert.equal(find('.perform-suspend[disabled]').length, 1, 'disabled by default');
-
-    expandSelectKit('.suspend-until .combobox');
-    selectKitSelectRow('tomorrow', { selector: '.suspend-until .combobox'});
   });
 
+  suspendUntilCombobox.expand().selectRowByValue('tomorrow');
+
   fillIn('.suspend-reason', "for breaking the rules");
   fillIn('.suspend-message', "this is an email reason why");
   andThen(() => {
diff --git a/test/javascripts/acceptance/category-chooser-test.js.es6 b/test/javascripts/acceptance/category-chooser-test.js.es6
index 793674295f8..bd3979993f3 100644
--- a/test/javascripts/acceptance/category-chooser-test.js.es6
+++ b/test/javascripts/acceptance/category-chooser-test.js.es6
@@ -1,27 +1,29 @@
-import { acceptance } from "helpers/qunit-helpers";
+import { acceptance } from 'helpers/qunit-helpers';
 
-acceptance("CategoryChooser", {
+acceptance('CategoryChooser', {
   loggedIn: true,
   settings: {
     allow_uncategorized_topics: false
   }
 });
 
-QUnit.test("does not display uncategorized if not allowed", assert => {
-  visit("/");
+QUnit.test('does not display uncategorized if not allowed', assert => {
+  const categoryChooser = selectKit('.category-chooser');
+
+  visit('/');
   click('#create-topic');
 
-  expandSelectKit('.category-chooser');
+  categoryChooser.expand();
 
   andThen(() => {
-    assert.ok(selectKit('.category-chooser').rowByIndex(0).name() !== 'uncategorized');
+    assert.ok(categoryChooser.rowByIndex(0).name() !== 'uncategorized');
   });
 });
 
-QUnit.test("prefill category when category_id is set", assert => {
-  visit("/new-topic?category_id=1");
+QUnit.test('prefill category when category_id is set', assert => {
+  visit('/new-topic?category_id=1');
 
   andThen(() => {
-    assert.equal(selectKit('.category-chooser').header.name(), 'bug');
+    assert.equal(selectKit('.category-chooser').header().value(), 1);
   });
 });
diff --git a/test/javascripts/acceptance/category-edit-security-test.js.es6 b/test/javascripts/acceptance/category-edit-security-test.js.es6
index 9d0b6c71295..2af84a1898b 100644
--- a/test/javascripts/acceptance/category-edit-security-test.js.es6
+++ b/test/javascripts/acceptance/category-edit-security-test.js.es6
@@ -22,35 +22,38 @@ QUnit.test("default", assert => {
 });
 
 QUnit.test("removing a permission", assert => {
+  const availableGroups = selectKit(".available-groups");
+
   visit("/c/bug");
 
   click('.edit-category');
   click('li.edit-category-security a');
   click('.edit-category-tab-security .edit-permission');
-  expandSelectKit(".available-groups");
+  availableGroups.expand();
 
   andThen(() => {
-    assert.notOk(exists(".available-groups .select-kit-row[data-value='everyone']"), 'everyone is already used and is not in the available groups');
+    assert.notOk(availableGroups.rowByValue('everyone').exists(), 'everyone is already used and is not in the available groups');
   });
 
   click('.edit-category-tab-security .permission-list li:first-of-type .remove-permission');
-  expandSelectKit(".available-groups");
+  availableGroups.expand();
 
   andThen(() => {
-    assert.ok(exists(".available-groups .select-kit-row[data-value='everyone']"), 'everyone has been removed and appears in the available groups');
+    assert.ok(availableGroups.rowByValue('everyone').exists(), 'everyone has been removed and appears in the available groups');
   });
 });
 
 QUnit.test("adding a permission", assert => {
+  const availableGroups = selectKit(".available-groups");
+  const permissionSelector = selectKit(".permission-selector");
+
   visit("/c/bug");
 
   click('.edit-category');
   click('li.edit-category-security a');
   click('.edit-category-tab-security .edit-permission');
-  expandSelectKit('.available-groups');
-  selectKitSelectRow('staff', { selector: '.available-groups' });
-  expandSelectKit('.permission-selector');
-  selectKitSelectRow('2', { selector: '.permission-selector' });
+  availableGroups.expand().selectRowByValue('staff');
+  permissionSelector.expand().selectRowByValue('2');
   click('.edit-category-tab-security .add-permission');
 
   andThen(() => {
@@ -65,21 +68,20 @@ QUnit.test("adding a permission", assert => {
 });
 
 QUnit.test("adding a previously removed permission", assert => {
+  const availableGroups = selectKit(".available-groups");
+
   visit("/c/bug");
 
   click('.edit-category');
   click('li.edit-category-security a');
   click('.edit-category-tab-security .edit-permission');
-  expandSelectKit(".available-groups");
-
   click('.edit-category-tab-security .permission-list li:first-of-type .remove-permission');
 
   andThen(() => {
     assert.equal(find(".edit-category-tab-security .permission-list li").length, 0, 'it removes the permission from the list');
   });
 
-  expandSelectKit('.available-groups');
-  selectKitSelectRow('everyone', { selector: '.available-groups' });
+  availableGroups.expand().selectRowByValue('everyone');
   click('.edit-category-tab-security .add-permission');
 
   andThen(() => {
diff --git a/test/javascripts/acceptance/category-edit-test.js.es6 b/test/javascripts/acceptance/category-edit-test.js.es6
index 5f0415a43cb..4f9d2da99fb 100644
--- a/test/javascripts/acceptance/category-edit-test.js.es6
+++ b/test/javascripts/acceptance/category-edit-test.js.es6
@@ -59,6 +59,8 @@ QUnit.test("Error Saving", assert => {
 });
 
 QUnit.test("Subcategory list settings", assert => {
+  const categoryChooser = selectKit('.edit-category-tab-general .category-chooser');
+
   visit("/c/bug");
 
   click('.edit-category');
@@ -69,17 +71,15 @@ QUnit.test("Subcategory list settings", assert => {
   });
 
   click(".show-subcategory-list-field input[type=checkbox]");
+
   andThen(() => {
     assert.ok(visible(".subcategory-list-style-field"), "subcategory list style is shown if show subcategory list is checked");
   });
 
   click('.edit-category-general');
-
-  expandSelectKit('.edit-category-tab-general .category-chooser');
-
-  selectKitSelectRow(3, {selector: '.edit-category-tab-general .category-chooser'});
-
+  categoryChooser.expand().selectRowByValue(3);
   click('.edit-category-settings');
+
   andThen(() => {
     assert.ok(!visible(".show-subcategory-list-field"), "show subcategory list isn't visible for child categories");
     assert.ok(!visible(".subcategory-list-style-field"), "subcategory list style isn't visible for child categories");
diff --git a/test/javascripts/acceptance/composer-test.js.es6 b/test/javascripts/acceptance/composer-test.js.es6
index 58a95f1a87b..e35505eca67 100644
--- a/test/javascripts/acceptance/composer-test.js.es6
+++ b/test/javascripts/acceptance/composer-test.js.es6
@@ -262,8 +262,8 @@ QUnit.test("Composer can toggle between edit and reply", assert => {
 QUnit.test("Composer can toggle between reply and createTopic", assert => {
   visit("/t/this-is-a-test-topic/9");
   click('.topic-post:eq(0) button.reply');
-  expandSelectKit('.toolbar-popup-menu-options');
-  selectKitSelectRow('toggleWhisper', { selector: '.toolbar-popup-menu-options'});
+
+  selectKit('.toolbar-popup-menu-options').expand().selectRowByValue('toggleWhisper');
 
   andThen(() => {
     assert.ok(
@@ -285,8 +285,7 @@ QUnit.test("Composer can toggle between reply and createTopic", assert => {
     );
   });
 
-  expandSelectKit('.toolbar-popup-menu-options');
-  selectKitSelectRow('toggleInvisible', { selector: '.toolbar-popup-menu-options'});
+  selectKit('.toolbar-popup-menu-options').expand().selectRowByValue('toggleInvisible');
 
   andThen(() => {
     assert.ok(
diff --git a/test/javascripts/acceptance/preferences-test.js.es6 b/test/javascripts/acceptance/preferences-test.js.es6
index e4607f5bcf1..0014cefd652 100644
--- a/test/javascripts/acceptance/preferences-test.js.es6
+++ b/test/javascripts/acceptance/preferences-test.js.es6
@@ -30,7 +30,7 @@ QUnit.test("update some fields", assert => {
   savePreferences();
 
   click(".preferences-nav .nav-notifications a");
-  selectDropdown('.control-group.notifications select.combobox', 1440);
+  selectKit('.control-group.notifications .combo-box.duration').expand().selectRowByValue(1440);
   savePreferences();
 
   click(".preferences-nav .nav-categories a");
@@ -72,4 +72,4 @@ QUnit.test("email", assert => {
   andThen(() => {
     assert.equal(find('.tip.bad').text().trim(), I18n.t('user.email.invalid'), 'it should display invalid email tip');
   });
-});
\ No newline at end of file
+});
diff --git a/test/javascripts/acceptance/search-full-test.js.es6 b/test/javascripts/acceptance/search-full-test.js.es6
index ec4a486bba8..dba53a92525 100644
--- a/test/javascripts/acceptance/search-full-test.js.es6
+++ b/test/javascripts/acceptance/search-full-test.js.es6
@@ -133,13 +133,13 @@ QUnit.test("update username through advanced search ui", assert => {
 });
 
 QUnit.test("update category through advanced search ui", assert => {
+  const categoryChooser = selectKit('.search-advanced-options .category-chooser');
+
   visit("/search");
+
   fillIn('.search input.full-page-search', 'none');
   click('.search-advanced-btn');
-
-  expandSelectKit('.search-advanced-options .category-chooser');
-  selectKitFillInFilter('faq', { selector: '.search-advanced-options .category-chooser' });
-  selectKitSelectRow(4, { selector: '.search-advanced-options .category-chooser' });
+  categoryChooser.expand().fillInFilter('faq').selectRowByValue(4);
 
   andThen(() => {
     assert.ok(exists('.search-advanced-options .badge-category:contains("faq")'), 'has "faq" populated');
@@ -253,45 +253,47 @@ QUnit.test("update in:seen filter through advanced search ui", assert => {
 });
 
 QUnit.test("update in filter through advanced search ui", assert => {
+  const inSelector = selectKit('.search-advanced-options .select-kit#in');
+
   visit("/search");
+
   fillIn('.search input.full-page-search', 'none');
   click('.search-advanced-btn');
-
-  expandSelectKit('.search-advanced-options .select-kit#in');
-  selectKitSelectRow('bookmarks', { selector: '.search-advanced-options .select-kit#in' });
-  fillIn('.search-advanced-options .select-kit#in', 'bookmarks');
+  inSelector.expand().selectRowByValue('bookmarks');
 
   andThen(() => {
-    assert.ok(exists(selectKit('.search-advanced-options .select-kit#in').rowByName("I bookmarked").el), 'has "I bookmarked" populated');
+    assert.ok(inSelector.rowByName("I bookmarked").exists(), 'has "I bookmarked" populated');
     assert.equal(find('.search input.full-page-search').val(), "none in:bookmarks", 'has updated search term to "none in:bookmarks"');
   });
 });
 
 QUnit.test("update status through advanced search ui", assert => {
+  const statusSelector = selectKit('.search-advanced-options .select-kit#status');
+
   visit("/search");
+
   fillIn('.search input.full-page-search', 'none');
   click('.search-advanced-btn');
-  expandSelectKit('.search-advanced-options .select-kit#status');
-  selectKitSelectRow('closed', { selector: '.search-advanced-options .select-kit#status' });
-  fillIn('.search-advanced-options .select-kit#status', 'closed');
+  statusSelector.expand().selectRowByValue('closed');
 
   andThen(() => {
-    assert.ok(exists(selectKit('.search-advanced-options .select-kit#status').rowByName("are closed").el), 'has "are closed" populated');
+    assert.ok(statusSelector.rowByName("are closed").exists(), 'has "are closed" populated');
     assert.equal(find('.search input.full-page-search').val(), "none status:closed", 'has updated search term to "none status:closed"');
   });
 });
 
 QUnit.test("update post time through advanced search ui", assert => {
+  const postTimeSelector = selectKit('.search-advanced-options .select-kit#postTime');
+
   visit("/search");
+
   fillIn('.search input.full-page-search', 'none');
   click('.search-advanced-btn');
   fillIn('#search-post-date .date-picker', '2016-10-05');
-  expandSelectKit('.search-advanced-options .select-kit#postTime');
-  selectKitSelectRow('after', { selector: '.search-advanced-options .select-kit#postTime' });
-  fillIn('.search-advanced-options .select-kit#postTime', 'after');
+  postTimeSelector.expand().selectRowByValue('after');
 
   andThen(() => {
-    assert.ok(exists(selectKit('.search-advanced-options .select-kit#postTime').rowByName("after").el), 'has "after" populated');
+    assert.ok(postTimeSelector.rowByName("after").exists(), 'has "after" populated');
     assert.equal(find('.search input.full-page-search').val(), "none after:2016-10-05", 'has updated search term to "none after:2016-10-05"');
   });
 });
diff --git a/test/javascripts/acceptance/search-test.js.es6 b/test/javascripts/acceptance/search-test.js.es6
index d9fb690638e..5929078b285 100644
--- a/test/javascripts/acceptance/search-test.js.es6
+++ b/test/javascripts/acceptance/search-test.js.es6
@@ -87,22 +87,24 @@ QUnit.test("Search with context", assert => {
 });
 
 QUnit.test("Right filters are shown to anonymous users", assert => {
+  const inSelector = selectKit('.select-kit#in');
+
   visit("/search?expanded=true");
 
-  expandSelectKit(".select-kit#in");
+  inSelector.expand();
 
   andThen(() => {
-    assert.ok(exists('.select-kit#in .select-kit-row[data-value=first]'));
-    assert.ok(exists('.select-kit#in .select-kit-row[data-value=pinned]'));
-    assert.ok(exists('.select-kit#in .select-kit-row[data-value=unpinned]'));
-    assert.ok(exists('.select-kit#in .select-kit-row[data-value=wiki]'));
-    assert.ok(exists('.select-kit#in .select-kit-row[data-value=images]'));
+    assert.ok(inSelector.rowByValue('first').exists());
+    assert.ok(inSelector.rowByValue('pinned').exists());
+    assert.ok(inSelector.rowByValue('unpinned').exists());
+    assert.ok(inSelector.rowByValue('wiki').exists());
+    assert.ok(inSelector.rowByValue('images').exists());
 
-    assert.notOk(exists('.select-kit#in .select-kit-row[data-value=unseen]'));
-    assert.notOk(exists('.select-kit#in .select-kit-row[data-value=posted]'));
-    assert.notOk(exists('.select-kit#in .select-kit-row[data-value=watching]'));
-    assert.notOk(exists('.select-kit#in .select-kit-row[data-value=tracking]'));
-    assert.notOk(exists('.select-kit#in .select-kit-row[data-value=bookmarks]'));
+    assert.notOk(inSelector.rowByValue('unseen').exists());
+    assert.notOk(inSelector.rowByValue('posted').exists());
+    assert.notOk(inSelector.rowByValue('watching').exists());
+    assert.notOk(inSelector.rowByValue('tracking').exists());
+    assert.notOk(inSelector.rowByValue('bookmarks').exists());
 
     assert.notOk(exists('.search-advanced-options .in-likes'));
     assert.notOk(exists('.search-advanced-options .in-private'));
@@ -111,24 +113,26 @@ QUnit.test("Right filters are shown to anonymous users", assert => {
 });
 
 QUnit.test("Right filters are shown to logged-in users", assert => {
+  const inSelector = selectKit('.select-kit#in');
+
   logIn();
   Discourse.reset();
   visit("/search?expanded=true");
 
-  expandSelectKit(".select-kit#in");
+  inSelector.expand();
 
   andThen(() => {
-    assert.ok(exists('.select-kit#in .select-kit-row[data-value=first]'));
-    assert.ok(exists('.select-kit#in .select-kit-row[data-value=pinned]'));
-    assert.ok(exists('.select-kit#in .select-kit-row[data-value=unpinned]'));
-    assert.ok(exists('.select-kit#in .select-kit-row[data-value=wiki]'));
-    assert.ok(exists('.select-kit#in .select-kit-row[data-value=images]'));
+    assert.ok(inSelector.rowByValue('first').exists());
+    assert.ok(inSelector.rowByValue('pinned').exists());
+    assert.ok(inSelector.rowByValue('unpinned').exists());
+    assert.ok(inSelector.rowByValue('wiki').exists());
+    assert.ok(inSelector.rowByValue('images').exists());
 
-    assert.ok(exists('.select-kit#in .select-kit-row[data-value=unseen]'));
-    assert.ok(exists('.select-kit#in .select-kit-row[data-value=posted]'));
-    assert.ok(exists('.select-kit#in .select-kit-row[data-value=watching]'));
-    assert.ok(exists('.select-kit#in .select-kit-row[data-value=tracking]'));
-    assert.ok(exists('.select-kit#in .select-kit-row[data-value=bookmarks]'));
+    assert.ok(inSelector.rowByValue('unseen').exists());
+    assert.ok(inSelector.rowByValue('posted').exists());
+    assert.ok(inSelector.rowByValue('watching').exists());
+    assert.ok(inSelector.rowByValue('tracking').exists());
+    assert.ok(inSelector.rowByValue('bookmarks').exists());
 
     assert.ok(exists('.search-advanced-options .in-likes'));
     assert.ok(exists('.search-advanced-options .in-private'));
diff --git a/test/javascripts/acceptance/topic-edit-timer-test.js.es6 b/test/javascripts/acceptance/topic-edit-timer-test.js.es6
index 588908f6603..569639d810a 100644
--- a/test/javascripts/acceptance/topic-edit-timer-test.js.es6
+++ b/test/javascripts/acceptance/topic-edit-timer-test.js.es6
@@ -2,32 +2,42 @@ import { acceptance } from 'helpers/qunit-helpers';
 acceptance('Topic - Edit timer', { loggedIn: true });
 
 QUnit.test('default', assert => {
+  const timerType = selectKit('.select-kit.timer-type');
+  const futureDateInputSelector = selectKit('.future-date-input-selector');
+
   visit('/t/internationalization-localization');
   click('.toggle-admin-menu');
   click('.topic-admin-status-update button');
 
   andThen(() => {
-    assert.equal(selectKit('.select-kit.timer-type').header.name(), 'Auto-Close Topic');
-    assert.equal(selectKit('.future-date-input-selector').header.name(), 'Select a timeframe');
+    assert.equal(futureDateInputSelector.header().title(), 'Select a timeframe');
+    assert.equal(futureDateInputSelector.header().value(), null);
   });
 
   click('#private-topic-timer');
 
   andThen(() => {
-    assert.equal(selectKit('.select-kit.timer-type').header.name(), 'Remind Me');
-    assert.equal(selectKit('.future-date-input-selector').header.name(), 'Select a timeframe');
+    assert.equal(timerType.header().title(), 'Remind Me');
+    assert.equal(timerType.header().value(), 'reminder');
+
+    assert.equal(futureDateInputSelector.header().title(), 'Select a timeframe');
+    assert.equal(futureDateInputSelector.header().value(), null);
   });
 });
 
 QUnit.test('autoclose - specific time', assert => {
+  const futureDateInputSelector = selectKit('.future-date-input-selector');
+
   visit('/t/internationalization-localization');
   click('.toggle-admin-menu');
   click('.topic-admin-status-update button');
-  expandSelectKit('.future-date-input-selector');
-  selectKitSelectRow('next_week', { selector: '.future-date-input-selector' });
+
+  futureDateInputSelector.expand().selectRowByValue('next_week');
 
   andThen(() => {
-    assert.equal(selectKit('.future-date-input-selector').header.name(), 'Next week');
+    assert.equal(futureDateInputSelector.header().title(), 'Next week');
+    assert.equal(futureDateInputSelector.header().value(), 'next_week');
+
     const regex = /will automatically close in/g;
     const html = find('.future-date-input .topic-status-info').html().trim();
     assert.ok(regex.test(html));
@@ -35,35 +45,44 @@ QUnit.test('autoclose - specific time', assert => {
 });
 
 QUnit.test('autoclose', assert => {
+  const futureDateInputSelector = selectKit('.future-date-input-selector');
+
   visit('/t/internationalization-localization');
   click('.toggle-admin-menu');
   click('.topic-admin-status-update button');
-  expandSelectKit('.future-date-input-selector');
 
-  selectKitSelectRow('next_week', { selector: '.future-date-input-selector' });
+  futureDateInputSelector.expand().selectRowByValue('next_week');
+
   andThen(() => {
-    assert.equal(selectKit('.future-date-input-selector').header.name(), 'Next week');
+    assert.equal(futureDateInputSelector.header().title(), 'Next week');
+    assert.equal(futureDateInputSelector.header().value(), 'next_week');
+
     const regex = /will automatically close in/g;
     const html = find('.future-date-input .topic-status-info').html().trim();
     assert.ok(regex.test(html));
   });
 
-  expandSelectKit('.future-date-input-selector');
-  selectKitSelectRow('pick_date_and_time', { selector: '.future-date-input-selector' });
+  futureDateInputSelector.expand().selectRowByValue('pick_date_and_time');
+
   fillIn('.future-date-input .date-picker', '2099-11-24');
 
   andThen(() => {
-    assert.equal(selectKit('.future-date-input-selector').header.name(), 'Pick date and time');
+    assert.equal(futureDateInputSelector.header().title(), 'Pick date and time');
+    assert.equal(futureDateInputSelector.header().value(), 'pick_date_and_time');
+
     const regex = /will automatically close in/g;
     const html = find('.future-date-input .topic-status-info').html().trim();
     assert.ok(regex.test(html));
   });
 
-  expandSelectKit('.future-date-input-selector');
-  selectKitSelectRow('set_based_on_last_post', { selector: '.future-date-input-selector' });
+  futureDateInputSelector.expand().selectRowByValue('set_based_on_last_post');
+
   fillIn('.future-date-input input[type=number]', '2');
+
   andThen(() => {
-    assert.equal(selectKit('.future-date-input-selector').header.name(), 'Close based on last post');
+    assert.equal(futureDateInputSelector.header().title(), 'Close based on last post');
+    assert.equal(futureDateInputSelector.header().value(), 'set_based_on_last_post');
+
     const regex = /This topic will close.*after the last reply/g;
     const html = find('.future-date-input .topic-status-info').html().trim();
     assert.ok(regex.test(html));
@@ -71,32 +90,39 @@ QUnit.test('autoclose', assert => {
 });
 
 QUnit.test('close temporarily', assert => {
+  const timerType = selectKit('.select-kit.timer-type');
+  const futureDateInputSelector = selectKit('.future-date-input-selector');
+
   visit('/t/internationalization-localization');
   click('.toggle-admin-menu');
   click('.topic-admin-status-update button');
 
-  expandSelectKit('.select-kit.timer-type');
-  selectKitSelectRow('open', { selector: '.select-kit.timer-type' });
+  timerType.expand().selectRowByValue('open');
 
   andThen(() => {
-    assert.equal(selectKit('.future-date-input-selector').header.name(), 'Select a timeframe');
+    assert.equal(futureDateInputSelector.header().title(), 'Select a timeframe');
+    assert.equal(futureDateInputSelector.header().value(), null);
   });
 
-  expandSelectKit('.future-date-input-selector');
-  selectKitSelectRow('next_week', { selector: '.future-date-input-selector' });
+  futureDateInputSelector.expand().selectRowByValue('next_week');
+
   andThen(() => {
-    assert.equal(selectKit('.future-date-input-selector').header.name(), 'Next week');
+    assert.equal(futureDateInputSelector.header().title(), 'Next week');
+    assert.equal(futureDateInputSelector.header().value(), 'next_week');
+
     const regex = /will automatically open in/g;
     const html = find('.future-date-input .topic-status-info').html().trim();
     assert.ok(regex.test(html));
   });
 
-  expandSelectKit('.future-date-input-selector');
-  selectKitSelectRow('pick_date_and_time', { selector: '.future-date-input-selector' });
+  futureDateInputSelector.expand().selectRowByValue('pick_date_and_time');
+
   fillIn('.future-date-input .date-picker', '2099-11-24');
 
   andThen(() => {
-    assert.equal(selectKit('.future-date-input-selector').header.name(), 'Pick date and time');
+    assert.equal(futureDateInputSelector.header().title(), 'Pick date and time');
+    assert.equal(futureDateInputSelector.header().value(), 'pick_date_and_time');
+
     const regex = /will automatically open in/g;
     const html = find('.future-date-input .topic-status-info').html().trim();
     assert.ok(regex.test(html));
@@ -104,26 +130,32 @@ QUnit.test('close temporarily', assert => {
 });
 
 QUnit.test('schedule', assert => {
+  const timerType = selectKit('.select-kit.timer-type');
+  const categoryChooser = selectKit('.modal-body .category-chooser');
+  const futureDateInputSelector = selectKit('.future-date-input-selector');
+
   visit('/t/internationalization-localization');
   click('.toggle-admin-menu');
   click('.topic-admin-status-update button');
 
-  expandSelectKit('.select-kit.timer-type');
-  selectKitSelectRow('publish_to_category', { selector: '.select-kit.timer-type' });
+  timerType.expand().selectRowByValue('publish_to_category');
 
   andThen(() => {
-    assert.equal(selectKit('.modal-body .category-chooser').header.name(), 'uncategorized');
-    assert.equal(selectKit('.future-date-input-selector').header.name(), 'Select a timeframe');
+    assert.equal(categoryChooser.header().title(), 'uncategorized');
+    assert.equal(categoryChooser.header().value(), null);
+
+    assert.equal(futureDateInputSelector.header().title(), 'Select a timeframe');
+    assert.equal(futureDateInputSelector.header().value(), null);
   });
 
-  expandSelectKit('.modal-body .category-chooser');
-  selectKitSelectRow('7', { selector: '.modal-body .category-chooser' });
+  categoryChooser.expand().selectRowByValue('7');
 
-  expandSelectKit('.future-date-input-selector');
-  selectKitSelectRow('next_week', { selector: '.future-date-input-selector' });
+  futureDateInputSelector.expand().selectRowByValue('next_week');
 
   andThen(() => {
-    assert.equal(selectKit('.future-date-input-selector').header.name(), 'Next week');
+    assert.equal(futureDateInputSelector.header().title(), 'Next week');
+    assert.equal(futureDateInputSelector.header().value(), 'next_week');
+
     const regex = /will be published to #dev/g;
     const text = find('.future-date-input .topic-status-info').text().trim();
     assert.ok(regex.test(text));
@@ -131,21 +163,26 @@ QUnit.test('schedule', assert => {
 });
 
 QUnit.test('auto delete', assert => {
+  const timerType = selectKit('.select-kit.timer-type');
+  const futureDateInputSelector = selectKit('.future-date-input-selector');
+
   visit('/t/internationalization-localization');
   click('.toggle-admin-menu');
   click('.topic-admin-status-update button');
 
-  expandSelectKit('.select-kit.timer-type');
-  selectKitSelectRow('delete', { selector: '.select-kit.timer-type' });
+  timerType.expand().selectRowByValue('delete');
 
   andThen(() => {
-    assert.equal(selectKit('.future-date-input-selector').header.name(), 'Select a timeframe');
+    assert.equal(futureDateInputSelector.header().title(), 'Select a timeframe');
+    assert.equal(futureDateInputSelector.header().value(), null);
   });
 
-  expandSelectKit('.future-date-input-selector');
-  selectKitSelectRow('two_weeks', { selector: '.future-date-input-selector' });
+  futureDateInputSelector.expand().selectRowByValue('two_weeks');
+
   andThen(() => {
-    assert.equal(selectKit('.future-date-input-selector').header.name(), 'Two Weeks');
+    assert.equal(futureDateInputSelector.header().title(), 'Two Weeks');
+    assert.equal(futureDateInputSelector.header().value(), 'two_weeks');
+
     const regex = /will be automatically deleted/g;
     const html = find('.future-date-input .topic-status-info').html().trim();
     assert.ok(regex.test(html));
diff --git a/test/javascripts/acceptance/topic-notifications-button-test.js.es6 b/test/javascripts/acceptance/topic-notifications-button-test.js.es6
index 9d5935235a3..4fe88775954 100644
--- a/test/javascripts/acceptance/topic-notifications-button-test.js.es6
+++ b/test/javascripts/acceptance/topic-notifications-button-test.js.es6
@@ -17,32 +17,24 @@ acceptance("Topic Notifications button", {
 });
 
 QUnit.test("Updating topic notification level", assert => {
-  visit("/t/internationalization-localization/280");
+  const notificationOptions = selectKit("#topic-footer-buttons .topic-notifications-options");
 
-  const notificationOptions = "#topic-footer-buttons .topic-notifications-options";
+  visit("/t/internationalization-localization/280");
 
   andThen(() => {
     assert.ok(
-      exists(`${notificationOptions}`),
+      notificationOptions.exists(),
       "it should display the notification options button in the topic's footer"
     );
   });
 
-  expandSelectKit(notificationOptions);
-  selectKitSelectRow("3", { selector: notificationOptions});
+  notificationOptions.expand().selectRowByValue("3");
 
   andThen(() => {
     assert.equal(
-      selectKit(notificationOptions).selectedRow.name(),
-      "watching",
+      notificationOptions.selectedRow().name(),
+      "Watching",
       "it should display the right notification level"
     );
-
-    // This test is failing in headless mode
-    // assert.equal(
-    //   find(`.timeline-footer-controls .select-kit-header`).data().name,
-    //   'Watching',
-    //   'it should display the right notification level in topic timeline'
-    // );
   });
 });
diff --git a/test/javascripts/acceptance/topic-test.js.es6 b/test/javascripts/acceptance/topic-test.js.es6
index 499fe32022b..b84afe844f7 100644
--- a/test/javascripts/acceptance/topic-test.js.es6
+++ b/test/javascripts/acceptance/topic-test.js.es6
@@ -48,16 +48,13 @@ QUnit.test("Showing and hiding the edit controls", assert => {
 });
 
 QUnit.test("Updating the topic title and category", assert => {
+  const categoryChooser = selectKit('.title-wrapper .category-chooser');
+
   visit("/t/internationalization-localization/280");
 
   click('#topic-title .d-icon-pencil');
-
   fillIn('#edit-title', 'this is the new title');
-
-  expandSelectKit('.title-wrapper .category-chooser');
-
-  selectKitSelectRow(4, {selector: '.title-wrapper .category-chooser'});
-
+  categoryChooser.expand().selectRowByValue(4);
   click('#topic-title .submit-edit');
 
   andThen(() => {
@@ -104,7 +101,7 @@ QUnit.test("Reply as new topic", assert => {
       "it fills composer with the ring string"
     );
     assert.equal(
-      selectKit('.category-chooser').header.name(), "feature",
+      selectKit('.category-chooser').header().value(), "2",
       "it fills category selector with the right category"
     );
   });
diff --git a/test/javascripts/components/categories-admin-dropdown-test.js.es6 b/test/javascripts/components/categories-admin-dropdown-test.js.es6
index d6c63197b68..124a4fa8565 100644
--- a/test/javascripts/components/categories-admin-dropdown-test.js.es6
+++ b/test/javascripts/components/categories-admin-dropdown-test.js.es6
@@ -5,15 +5,15 @@ componentTest('default', {
   template: '{{categories-admin-dropdown}}',
 
   test(assert) {
-    const $selectKit = selectKit('.categories-admin-dropdown');
+    const subject = selectKit();
 
-    assert.equal($selectKit.el.find(".d-icon-bars").length, 1);
-    assert.equal($selectKit.el.find(".d-icon-caret-down").length, 1);
+    assert.equal(subject.el().find(".d-icon-bars").length, 1);
+    assert.equal(subject.el().find(".d-icon-caret-down").length, 1);
 
-    expandSelectKit();
+    subject.expand();
 
     andThen(() => {
-      assert.equal($selectKit.rowByValue("create").name(), "New Category");
+      assert.equal(subject.rowByValue("create").name(), "New Category");
     });
   }
 });
diff --git a/test/javascripts/components/category-chooser-test.js.es6 b/test/javascripts/components/category-chooser-test.js.es6
index ec29639148e..bb129c872bd 100644
--- a/test/javascripts/components/category-chooser-test.js.es6
+++ b/test/javascripts/components/category-chooser-test.js.es6
@@ -1,15 +1,19 @@
 import componentTest from 'helpers/component-test';
 
-moduleForComponent('category-chooser', {integration: true});
+moduleForComponent('category-chooser', {
+  integration: true,
+  beforeEach: function() {
+    this.set('subject', selectKit());
+  }
+});
 
 componentTest('with value', {
   template: '{{category-chooser value=2}}',
 
   test(assert) {
-    expandSelectKit();
-
     andThen(() => {
-      assert.equal(selectKit('.category-chooser').header.name(), "feature");
+      assert.equal(this.get('subject').header().value(), 2);
+      assert.equal(this.get('subject').header().title(), 'feature');
     });
   }
 });
@@ -18,11 +22,9 @@ componentTest('with excludeCategoryId', {
   template: '{{category-chooser excludeCategoryId=2}}',
 
   test(assert) {
-    expandSelectKit();
+    this.get('subject').expand();
 
-    andThen(() => {
-      assert.equal(selectKit('.category-chooser').rowByValue(2).el.length, 0);
-    });
+    andThen(() => assert.notOk(this.get('subject').rowByValue(2).exists()));
   }
 });
 
@@ -30,12 +32,14 @@ componentTest('with scopedCategoryId', {
   template: '{{category-chooser scopedCategoryId=2}}',
 
   test(assert) {
-    expandSelectKit();
+    this.get('subject').expand();
 
     andThen(() => {
-      assert.equal(selectKit('.category-chooser').rowByIndex(0).name(), "feature");
-      assert.equal(selectKit('.category-chooser').rowByIndex(1).name(), "spec");
-      assert.equal(selectKit('.category-chooser').el.find(".select-kit-row").length, 2);
+      assert.equal(this.get('subject').rowByIndex(0).title(), 'feature');
+      assert.equal(this.get('subject').rowByIndex(0).value(), 2);
+      assert.equal(this.get('subject').rowByIndex(1).title(), 'spec');
+      assert.equal(this.get('subject').rowByIndex(1).value(), 26);
+      assert.equal(this.get('subject').rows().length, 2);
     });
   }
 });
@@ -48,10 +52,9 @@ componentTest('with allowUncategorized=null', {
   },
 
   test(assert) {
-    expandSelectKit();
-
     andThen(() => {
-      assert.equal(selectKit('.category-chooser').header.name(), "Select a category&hellip;");
+      assert.equal(this.get('subject').header().value(), null);
+      assert.equal(this.get('subject').header().title(), "Select a category&hellip;");
     });
   }
 });
@@ -64,10 +67,9 @@ componentTest('with allowUncategorized=null rootNone=true', {
   },
 
   test(assert) {
-    expandSelectKit();
-
     andThen(() => {
-      assert.equal(selectKit('.category-chooser').header.name(), "Select a category&hellip;");
+      assert.equal(this.get('subject').header().value(), null);
+      assert.equal(this.get('subject').header().title(), 'Select a category&hellip;');
     });
   }
 });
@@ -76,15 +78,14 @@ componentTest('with disallowed uncategorized, rootNone and rootNoneLabel', {
   template: '{{category-chooser allowUncategorized=null rootNone=true rootNoneLabel="test.root"}}',
 
   beforeEach() {
-    I18n.translations[I18n.locale].js.test = {root: 'root none label'};
+    I18n.translations[I18n.locale].js.test = { root: 'root none label' };
     this.siteSettings.allow_uncategorized_topics = false;
   },
 
   test(assert) {
-    expandSelectKit();
-
     andThen(() => {
-      assert.equal(selectKit('.category-chooser').header.name(), "Select a category&hellip;");
+      assert.equal(this.get('subject').header().value(), null);
+      assert.equal(this.get('subject').header().title(), 'Select a category&hellip;');
     });
   }
 });
@@ -97,10 +98,9 @@ componentTest('with allowed uncategorized', {
   },
 
   test(assert) {
-    expandSelectKit();
-
     andThen(() => {
-      assert.equal(selectKit('.category-chooser').header.name(), "uncategorized");
+      assert.equal(this.get('subject').header().value(), null);
+      assert.equal(this.get('subject').header().title(), 'uncategorized');
     });
   }
 });
@@ -113,10 +113,9 @@ componentTest('with allowed uncategorized and rootNone', {
   },
 
   test(assert) {
-    expandSelectKit();
-
     andThen(() => {
-      assert.equal(selectKit('.category-chooser').header.name(), "(no category)");
+      assert.equal(this.get('subject').header().value(), null);
+      assert.equal(this.get('subject').header().title(), '(no category)');
     });
   }
 });
@@ -125,15 +124,14 @@ componentTest('with allowed uncategorized rootNone and rootNoneLabel', {
   template: '{{category-chooser allowUncategorized=true rootNone=true rootNoneLabel="test.root"}}',
 
   beforeEach() {
-    I18n.translations[I18n.locale].js.test = {root: 'root none label'};
+    I18n.translations[I18n.locale].js.test = { root: 'root none label' };
     this.siteSettings.allow_uncategorized_topics = true;
   },
 
   test(assert) {
-    expandSelectKit();
-
     andThen(() => {
-      assert.equal(selectKit('.category-chooser').header.name(), "root none label");
+      assert.equal(this.get('subject').header().value(), null);
+      assert.equal(this.get('subject').header().title(), 'root none label');
     });
   }
 });
diff --git a/test/javascripts/components/category-selector-test.js.es6 b/test/javascripts/components/category-selector-test.js.es6
new file mode 100644
index 00000000000..ff35b7817b4
--- /dev/null
+++ b/test/javascripts/components/category-selector-test.js.es6
@@ -0,0 +1,87 @@
+import componentTest from 'helpers/component-test';
+import Category from "discourse/models/category";
+
+moduleForComponent('category-selector', {
+  integration: true,
+  beforeEach: function() {
+    this.set('subject', selectKit());
+  }
+});
+
+componentTest('default', {
+  template: '{{category-selector categories=categories}}',
+
+  beforeEach() {
+    this.set('categories', [ Category.findById(2) ]);
+  },
+
+  test(assert) {
+    andThen(() => {
+      assert.equal(this.get('subject').header().value(), 2);
+      assert.notOk(
+        this.get('subject').rowByValue(2).exists(),
+        "selected categories are not in the list"
+      );
+    });
+  }
+});
+
+componentTest('with blacklist', {
+  template: '{{category-selector categories=categories blacklist=blacklist}}',
+
+  beforeEach() {
+    this.set('categories', [ Category.findById(2) ]);
+    this.set('blacklist', [ Category.findById(8) ]);
+  },
+
+  test(assert) {
+    this.get('subject').expand();
+
+    andThen(() => {
+      assert.ok(
+        this.get('subject').rowByValue(6).exists(),
+        "not blacklisted categories are in the list"
+      );
+      assert.notOk(
+        this.get('subject').rowByValue(8).exists(),
+        "blacklisted categories are not in the list"
+      );
+    });
+  }
+});
+
+componentTest('interactions', {
+  template: '{{category-selector categories=categories}}',
+
+  beforeEach() {
+    this.set('categories', [
+      Category.findById(2),
+      Category.findById(6)
+    ]);
+  },
+
+  test(assert) {
+    this.get('subject').expand().selectRowByValue(8);
+
+    andThen(() => {
+      assert.equal(
+        this.get('subject').header().value(),
+        '2,6,8',
+        'it adds the selected category'
+      );
+      assert.equal(this.get('categories').length, 3);
+    });
+
+    this.get('subject').keyboard().backspace();
+    this.get('subject').keyboard().backspace();
+
+    andThen(() => {
+      assert.equal(
+        this.get('subject').header().value(),
+        '2,6',
+        'it removes the last selected category'
+      );
+      assert.equal(this.get('categories').length, 2);
+    });
+  }
+});
diff --git a/test/javascripts/components/categpry-selector-test.js.es6 b/test/javascripts/components/categpry-selector-test.js.es6
deleted file mode 100644
index 8c0925587eb..00000000000
--- a/test/javascripts/components/categpry-selector-test.js.es6
+++ /dev/null
@@ -1,67 +0,0 @@
-import componentTest from 'helpers/component-test';
-import Category from "discourse/models/category";
-
-moduleForComponent('category-selector', {integration: true});
-
-componentTest('default', {
-  template: '{{category-selector categories=categories}}',
-
-  beforeEach() {
-    this.set('categories', [ Category.findById(2) ]);
-  },
-
-  test(assert) {
-    andThen(() => {
-      assert.propEqual(selectKit().header.name(), 'feature');
-      assert.ok(!exists(".select-kit .select-kit-row[data-value='2']"), "selected categories are not in the list");
-    });
-  }
-});
-
-componentTest('with blacklist', {
-  template: '{{category-selector categories=categories blacklist=blacklist}}',
-
-  beforeEach() {
-    this.set('categories', [ Category.findById(2) ]);
-    this.set('blacklist', [ Category.findById(8) ]);
-  },
-
-  test(assert) {
-    expandSelectKit();
-
-    andThen(() => {
-      assert.ok(exists(".select-kit .select-kit-row[data-value='6']"), "not blacklisted categories are in the list");
-      assert.ok(!exists(".select-kit .select-kit-row[data-value='8']"), "blacklisted categories are not in the list");
-    });
-  }
-});
-
-componentTest('interactions', {
-  template: '{{category-selector categories=categories}}',
-
-  beforeEach() {
-    this.set('categories', [
-      Category.findById(2),
-      Category.findById(6)
-    ]);
-  },
-
-  test(assert) {
-    expandSelectKit();
-
-    selectKitSelectRow(8);
-
-    andThen(() => {
-      assert.propEqual(selectKit().header.name(), 'feature,support,hosting', 'it adds the selected category');
-      assert.equal(this.get('categories').length, 3);
-    });
-
-    selectKit().keyboard.backspace();
-    selectKit().keyboard.backspace();
-
-    andThen(() => {
-      assert.propEqual(selectKit().header.name(), 'feature,support', 'it removes the last selected category');
-      assert.equal(this.get('categories').length, 2);
-    });
-  }
-});
diff --git a/test/javascripts/components/combo-box-test.js.es6 b/test/javascripts/components/combo-box-test.js.es6
index bf1534cf7ce..e1237ccc3f9 100644
--- a/test/javascripts/components/combo-box-test.js.es6
+++ b/test/javascripts/components/combo-box-test.js.es6
@@ -1,5 +1,10 @@
 import componentTest from 'helpers/component-test';
-moduleForComponent('combo-box', {integration: true});
+moduleForComponent('combo-box', {
+  integration: true,
+  beforeEach: function() {
+    this.set('subject', selectKit());
+  }
+});
 
 componentTest('default', {
   template: '{{combo-box content=items}}',
@@ -8,12 +13,12 @@ componentTest('default', {
   },
 
   test(assert) {
-    expandSelectKit();
+    this.get('subject').expand();
 
     andThen(() => {
-      assert.equal(selectKit('.combobox').header.name(), "hello");
-      assert.equal(selectKit('.combobox').rowByValue(1).name(), "hello");
-      assert.equal(selectKit('.combobox').rowByValue(2).name(), "world");
+      assert.equal(this.get('subject').header().name(), "hello");
+      assert.equal(this.get('subject').rowByValue(1).name(), "hello");
+      assert.equal(this.get('subject').rowByValue(2).name(), "world");
     });
   }
 });
@@ -25,11 +30,11 @@ componentTest('with valueAttribute', {
   },
 
   test(assert) {
-    expandSelectKit();
+    this.get('subject').expand();
 
     andThen(() => {
-      assert.equal(selectKit('.combobox').rowByValue(0).name(), "hello");
-      assert.equal(selectKit('.combobox').rowByValue(1).name(), "world");
+      assert.equal(this.get('subject').rowByValue(0).name(), "hello");
+      assert.equal(this.get('subject').rowByValue(1).name(), "world");
     });
   }
 });
@@ -41,11 +46,11 @@ componentTest('with nameProperty', {
   },
 
   test(assert) {
-    expandSelectKit();
+    this.get('subject').expand();
 
     andThen(() => {
-      assert.equal(selectKit('.combobox').rowByValue(0).name(), "hello");
-      assert.equal(selectKit('.combobox').rowByValue(1).name(), "world");
+      assert.equal(this.get('subject').rowByValue(0).name(), "hello");
+      assert.equal(this.get('subject').rowByValue(1).name(), "world");
     });
   }
 });
@@ -57,11 +62,11 @@ componentTest('with an array as content', {
   },
 
   test(assert) {
-    expandSelectKit();
+    this.get('subject').expand();
 
     andThen(() => {
-      assert.equal(selectKit('.combobox').rowByValue('evil').name(), "evil");
-      assert.equal(selectKit('.combobox').rowByValue('trout').name(), "trout");
+      assert.equal(this.get('subject').rowByValue('evil').name(), "evil");
+      assert.equal(this.get('subject').rowByValue('trout').name(), "trout");
     });
   }
 });
@@ -75,17 +80,17 @@ componentTest('with value and none as a string', {
   },
 
   test(assert) {
-    expandSelectKit();
+    this.get('subject').expand();
 
     andThen(() => {
-      assert.equal(selectKit('.combobox').noneRow.name(), 'none');
-      assert.equal(selectKit('.combobox').rowByValue("evil").name(), "evil");
-      assert.equal(selectKit('.combobox').rowByValue("trout").name(), "trout");
-      assert.equal(selectKit('.combobox').header.name(), 'trout');
+      assert.equal(this.get('subject').noneRow().name(), 'none');
+      assert.equal(this.get('subject').rowByValue("evil").name(), "evil");
+      assert.equal(this.get('subject').rowByValue("trout").name(), "trout");
+      assert.equal(this.get('subject').header().name(), 'trout');
       assert.equal(this.get('value'), 'trout');
     });
 
-    selectKitSelectRow('__none__', {selector: '.combobox' });
+    this.get('subject').selectNoneRow();
 
     andThen(() => {
       assert.equal(this.get('value'), null);
@@ -102,17 +107,17 @@ componentTest('with value and none as an object', {
   },
 
   test(assert) {
-    expandSelectKit();
+    this.get('subject').expand();
 
     andThen(() => {
-      assert.equal(selectKit('.combobox').noneRow.name(), 'none');
-      assert.equal(selectKit('.combobox').rowByValue("evil").name(), "evil");
-      assert.equal(selectKit('.combobox').rowByValue("trout").name(), "trout");
-      assert.equal(selectKit('.combobox').header.name(), 'evil');
+      assert.equal(this.get('subject').noneRow().name(), 'none');
+      assert.equal(this.get('subject').rowByValue("evil").name(), "evil");
+      assert.equal(this.get('subject').rowByValue("trout").name(), "trout");
+      assert.equal(this.get('subject').header().name(), 'evil');
       assert.equal(this.get('value'), 'evil');
     });
 
-    selectKitSelectNoneRow({ selector: '.combobox' });
+    this.get('subject').selectNoneRow();
 
     andThen(() => {
       assert.equal(this.get('value'), null);
@@ -130,10 +135,10 @@ componentTest('with no value and none as an object', {
   },
 
   test(assert) {
-    expandSelectKit();
+    this.get('subject').expand();
 
     andThen(() => {
-      assert.equal(selectKit('.combobox').header.name(), 'none');
+      assert.equal(this.get('subject').header().name(), 'none');
     });
   }
 });
@@ -148,10 +153,10 @@ componentTest('with no value and none string', {
   },
 
   test(assert) {
-    expandSelectKit();
+    this.get('subject').expand();
 
     andThen(() => {
-      assert.equal(selectKit('.combobox').header.name(), 'none');
+      assert.equal(this.get('subject').header().name(), 'none');
     });
   }
 });
@@ -164,66 +169,14 @@ componentTest('with no value and no none', {
   },
 
   test(assert) {
-    expandSelectKit();
+    this.get('subject').expand();
 
     andThen(() => {
-      assert.equal(selectKit('.combobox').header.name(), 'evil', 'it sets the first row as value');
+      assert.equal(this.get('subject').header().name(), 'evil', 'it sets the first row as value');
     });
   }
 });
 
-// componentTest('can be filtered', {
-//   template: '{{combo-box filterable=true value=1 content=content}}',
-//
-//   beforeEach() {
-//     this.set("content", [{ id: 1, name: "robin"}, { id: 2, name: "regis" }]);
-//   },
-//
-//   test(assert) {
-//     ();
-//
-//     andThen(() => assert.equal(find(".filter-input").length, 1, "it has a search input"));
-//
-//     selectKitFillInFilter("regis");
-//
-//     andThen(() => assert.equal(selectKit().rows.length, 1, "it filters results"));
-//
-//     selectKitFillInFilter("");
-//
-//     andThen(() => {
-//       assert.equal(
-//         selectKit().rows.length, 2,
-//         "it returns to original content when filter is empty"
-//       );
-//     });
-//   }
-// });
-
-// componentTest('persists filter state when expanding/collapsing', {
-//   template: '{{combo-box value=1 content=content filterable=true}}',
-//
-//   beforeEach() {
-//     this.set("content", [{ id: 1, name: "robin" }, { id: 2, name: "régis" }]);
-//   },
-//
-//   test(assert) {
-//     ();
-//
-//     selectKitFillInFilter("rob");
-//
-//     andThen(() => assert.equal(selectKit().rows.length, 1) );
-//
-//     collapseSelectKit();
-//
-//     andThen(() => assert.notOk(selectKit().isExpanded) );
-//
-//     ();
-//
-//     andThen(() => assert.equal(selectKit().rows.length, 1) );
-//   }
-// });
-
-
 componentTest('with empty string as value', {
   template: '{{combo-box content=items value=value}}',
   beforeEach() {
@@ -232,10 +185,10 @@ componentTest('with empty string as value', {
   },
 
   test(assert) {
-    expandSelectKit();
+    this.get('subject').expand();
 
     andThen(() => {
-      assert.equal(selectKit('.combobox').header.name(), 'evil', 'it sets the first row as value');
+      assert.equal(this.get('subject').header().name(), 'evil', 'it sets the first row as value');
     });
   }
 });
diff --git a/test/javascripts/components/list-setting-test.js.es6 b/test/javascripts/components/list-setting-test.js.es6
index 184d86a38d8..b5592974a07 100644
--- a/test/javascripts/components/list-setting-test.js.es6
+++ b/test/javascripts/components/list-setting-test.js.es6
@@ -11,15 +11,14 @@ componentTest('default', {
   },
 
   test(assert) {
-    expandSelectKit();
-
     andThen(() => {
-      assert.propEqual(selectKit().header.name(), 'bold,italic');
+      assert.equal(selectKit().header().title(), 'bold,italic');
+      assert.equal(selectKit().header().value(), 'bold,italic');
     });
   }
 });
 
-componentTest('with emptry string as value', {
+componentTest('with empty string as value', {
   template: '{{list-setting settingValue=settingValue}}',
 
   beforeEach() {
@@ -27,10 +26,8 @@ componentTest('with emptry string as value', {
   },
 
   test(assert) {
-    expandSelectKit();
-
     andThen(() => {
-      assert.equal(selectKit().header.el.find(".selected-name").length, 0);
+      assert.equal(selectKit().header().value(), "");
     });
   }
 });
@@ -43,10 +40,8 @@ componentTest('with only setting value', {
   },
 
   test(assert) {
-    expandSelectKit();
-
     andThen(() => {
-      assert.propEqual(selectKit().header.name(), 'bold,italic');
+      assert.equal(selectKit().header().value(), 'bold,italic');
     });
   }
 });
@@ -60,31 +55,31 @@ componentTest('interactions', {
   },
 
   test(assert) {
-    expandSelectKit();
+    const listSetting = selectKit();
 
-    selectKitSelectRow('underline');
+    listSetting.expand().selectRowByValue('underline');
 
     andThen(() => {
-      assert.propEqual(selectKit().header.name(), 'bold,italic,underline');
+      assert.equal(listSetting.header().value(), 'bold,italic,underline');
     });
 
-    selectKitFillInFilter('strike');
+    listSetting.fillInFilter('strike');
 
     andThen(() => {
-      assert.equal(selectKit().highlightedRow.name(), 'strike');
+      assert.equal(listSetting.highlightedRow().value(), 'strike');
     });
 
-    selectKit().keyboard.enter();
+    listSetting.keyboard().enter();
 
     andThen(() => {
-      assert.propEqual(selectKit().header.name(), 'bold,italic,underline,strike');
+      assert.equal(listSetting.header().value(), 'bold,italic,underline,strike');
     });
 
-    selectKit().keyboard.backspace();
-    selectKit().keyboard.backspace();
+    listSetting.keyboard().backspace();
+    listSetting.keyboard().backspace();
 
     andThen(() => {
-      assert.equal(this.get('choices').length, 3, 'it removes the created content from original list');
+      assert.equal(listSetting.header().value(), 'bold,italic,underline');
     });
   }
 });
diff --git a/test/javascripts/components/multi-select-test.js.es6 b/test/javascripts/components/multi-select-test.js.es6
index f1c95b17ec2..1bb5f940be8 100644
--- a/test/javascripts/components/multi-select-test.js.es6
+++ b/test/javascripts/components/multi-select-test.js.es6
@@ -1,5 +1,10 @@
 import componentTest from 'helpers/component-test';
-moduleForComponent('multi-select', {integration: true});
+moduleForComponent('multi-select', {
+  integration: true,
+  beforeEach: function() {
+    this.set('subject', selectKit());
+  }
+});
 
 componentTest('with objects and values', {
   template: '{{multi-select content=items values=values}}',
@@ -11,11 +16,24 @@ componentTest('with objects and values', {
 
   test(assert) {
     andThen(() => {
-      assert.propEqual(selectKit().header.name(), 'hello,world');
+      assert.equal(this.get('subject').header().value(), '1,2');
     });
   }
 });
 
+componentTest('with title', {
+  template: '{{multi-select title=(i18n "test.title")}}',
+
+  beforeEach() {
+    I18n.translations[I18n.locale].js.test = {title: 'My title'};
+  },
+
+  test(assert) {
+    andThen(() => assert.equal(selectKit().header().title(), 'My title') );
+  }
+});
+
+
 componentTest('interactions', {
   template: '{{multi-select none=none content=items values=values}}',
 
@@ -26,84 +44,104 @@ componentTest('interactions', {
   },
 
   test(assert) {
-    expandSelectKit();
+    this.get('subject').expand();
 
     andThen(() => {
-      assert.equal(selectKit().highlightedRow.name(), 'robin', 'it highlights the first content row');
+      assert.equal(
+        this.get('subject').highlightedRow().name(),
+        'robin',
+        'it highlights the first content row'
+      );
     });
 
     this.set('none', 'test.none');
 
     andThen(() => {
-      assert.equal(selectKit().noneRow.el.length, 1);
-      assert.equal(selectKit().highlightedRow.name(), 'robin', 'it highlights the first content row');
+      assert.ok(this.get('subject').noneRow().exists());
+      assert.equal(
+        this.get('subject').highlightedRow().name(),
+        'robin',
+        'it highlights the first content row'
+      );
     });
 
-    selectKitSelectRow(3);
+    this.get('subject').selectRowByValue(3);
 
     andThen(() => {
-      assert.equal(selectKit().highlightedRow.name(), 'none', 'it highlights none row if no content');
+      assert.equal(
+        this.get('subject').highlightedRow().name(),
+        'none',
+        'it highlights none row if no content'
+      );
     });
 
-    selectKitFillInFilter('joffrey');
+    this.get('subject').fillInFilter('joffrey');
 
     andThen(() => {
-      assert.equal(selectKit().highlightedRow.name(), 'joffrey', 'it highlights create row when filling filter');
+      assert.equal(
+        this.get('subject').highlightedRow().name(),
+        'joffrey',
+        'it highlights create row when filling filter'
+      );
     });
 
-    selectKit().keyboard.enter();
+    this.get('subject').keyboard().enter();
 
     andThen(() => {
-      assert.equal(selectKit().highlightedRow.name(), 'none', 'it highlights none row after creating content and no content left');
+      assert.equal(
+        this.get('subject').highlightedRow().name(),
+        'none',
+        'it highlights none row after creating content and no content left'
+      );
     });
 
-    selectKit().keyboard.backspace();
+    this.get('subject').keyboard().backspace();
 
     andThen(() => {
-      const $lastSelectedName = selectKit().header.el.find('.selected-name').last();
+      const $lastSelectedName = this.get('subject').header().el().find('.selected-name').last();
       assert.equal($lastSelectedName.attr('data-name'), 'joffrey');
       assert.ok($lastSelectedName.hasClass('is-highlighted'), 'it highlights the last selected name when using backspace');
     });
 
-    selectKit().keyboard.backspace();
+    this.get('subject').keyboard().backspace();
 
     andThen(() => {
-      const $lastSelectedName = selectKit().header.el.find('.selected-name').last();
+      const $lastSelectedName = this.get('subject').header().el().find('.selected-name').last();
       assert.equal($lastSelectedName.attr('data-name'), 'robin', 'it removes the previous highlighted selected content');
-      assert.notOk(exists(selectKit().rowByValue('joffrey').el), 'generated content shouldn’t appear in content when removed');
+      assert.notOk(this.get('subject').rowByValue('joffrey').exists(), 'generated content shouldn’t appear in content when removed');
     });
 
-    selectKit().keyboard.selectAll();
+    this.get('subject').keyboard().selectAll();
 
     andThen(() => {
-      const $highlightedSelectedNames = selectKit().header.el.find('.selected-name.is-highlighted');
+      const $highlightedSelectedNames = this.get('subject').header().el().find('.selected-name.is-highlighted');
       assert.equal($highlightedSelectedNames.length, 3, 'it highlights each selected name');
     });
 
-    selectKit().keyboard.backspace();
+    this.get('subject').keyboard().backspace();
 
     andThen(() => {
-      const $selectedNames = selectKit().header.el.find('.selected-name');
+      const $selectedNames = this.get('subject').header().el().find('.selected-name');
       assert.equal($selectedNames.length, 0, 'it removed all selected content');
     });
 
     andThen(() => {
-      assert.ok(this.$(".select-kit").hasClass("is-focused"));
-      assert.ok(this.$(".select-kit").hasClass("is-expanded"));
+      assert.ok(this.get('subject').isFocused());
+      assert.ok(this.get('subject').isExpanded());
     });
 
-    selectKit().keyboard.escape();
+    this.get('subject').keyboard().escape();
 
     andThen(() => {
-      assert.ok(this.$(".select-kit").hasClass("is-focused"));
-      assert.notOk(this.$(".select-kit").hasClass("is-expanded"));
+      assert.ok(this.get('subject').isFocused());
+      assert.notOk(this.get('subject').isExpanded());
     });
 
-    selectKit().keyboard.escape();
+    this.get('subject').keyboard().escape();
 
     andThen(() => {
-      assert.notOk(this.$(".select-kit").hasClass("is-focused"));
-      assert.notOk(this.$(".select-kit").hasClass("is-expanded"));
+      assert.notOk(this.get('subject').isFocused());
+      assert.notOk(this.get('subject').isExpanded());
     });
   }
 });
diff --git a/test/javascripts/components/pinned-options-test.js.es6 b/test/javascripts/components/pinned-options-test.js.es6
index 3808b63dca9..87aafc07d25 100644
--- a/test/javascripts/components/pinned-options-test.js.es6
+++ b/test/javascripts/components/pinned-options-test.js.es6
@@ -10,7 +10,12 @@ const buildTopic = function() {
   });
 };
 
-moduleForComponent('pinned-options', { integration: true });
+moduleForComponent('pinned-options', {
+  integration: true,
+  beforeEach: function() {
+    this.set('subject', selectKit());
+  }
+});
 
 componentTest('updating the content refreshes the list', {
   template: '{{pinned-options value=pinned topic=topic}}',
@@ -22,14 +27,14 @@ componentTest('updating the content refreshes the list', {
   },
 
   test(assert) {
-    expandSelectKit();
-
-    andThen(() => assert.equal(selectKit().header.name(), "Pinned") );
+    andThen(() => {
+      assert.equal(this.get('subject').header().name(), "pinned");
+    });
 
     andThen(() => this.set("pinned", false));
 
     andThen(() => {
-      assert.equal(selectKit().header.name(), "Unpinned");
+      assert.equal(this.get('subject').header().name(), "unpinned");
     });
   }
 });
diff --git a/test/javascripts/components/single-select-test.js.es6 b/test/javascripts/components/single-select-test.js.es6
index 2e9946f6727..f79b2b5182a 100644
--- a/test/javascripts/components/single-select-test.js.es6
+++ b/test/javascripts/components/single-select-test.js.es6
@@ -2,7 +2,12 @@ import componentTest from 'helpers/component-test';
 import { withPluginApi } from 'discourse/lib/plugin-api';
 import { clearCallbacks } from 'select-kit/mixins/plugin-api';
 
-moduleForComponent('single-select', { integration: true });
+moduleForComponent('single-select', {
+  integration: true,
+  beforeEach: function() {
+    this.set('subject', selectKit());
+  }
+});
 
 componentTest('updating the content refreshes the list', {
   template: '{{single-select value=1 content=content}}',
@@ -12,10 +17,10 @@ componentTest('updating the content refreshes the list', {
   },
 
   test(assert) {
-    expandSelectKit();
+    this.get('subject').expand();
 
     andThen(() => {
-      assert.equal(selectKit().rowByValue(1).name(), "BEFORE");
+      assert.equal(this.get('subject').rowByValue(1).name(), "BEFORE");
     });
 
     andThen(() => {
@@ -23,7 +28,7 @@ componentTest('updating the content refreshes the list', {
     });
 
     andThen(() => {
-      assert.equal(selectKit().rowByValue(1).name(), "AFTER");
+      assert.equal(this.get('subject').rowByValue(1).name(), "AFTER");
     });
   }
 });
@@ -37,16 +42,16 @@ componentTest('accepts a value by reference', {
   },
 
   test(assert) {
-    expandSelectKit();
+    this.get('subject').expand();
 
     andThen(() => {
       assert.equal(
-        selectKit().selectedRow.name(), "robin",
+        this.get('subject').selectedRow().name(), "robin",
         "it highlights the row corresponding to the value"
       );
     });
 
-    selectKitSelectRow(1);
+    this.get('subject').selectRowByValue(1);
 
     andThen(() => {
       assert.equal(this.get("value"), 1, "it mutates the value");
@@ -58,7 +63,11 @@ componentTest('no default icon', {
   template: '{{single-select}}',
 
   test(assert) {
-    assert.equal(selectKit().header.icon().length, 0, "it doesn’t have an icon if not specified");
+    assert.equal(
+      this.get('subject').header().icon().length,
+      0,
+      "it doesn’t have an icon if not specified"
+    );
   }
 });
 
@@ -66,10 +75,10 @@ componentTest('default search icon', {
   template: '{{single-select filterable=true}}',
 
   test(assert) {
-    expandSelectKit();
+    this.get('subject').expand();
 
     andThen(() => {
-      assert.ok(exists(selectKit().filter.icon), "it has a the correct icon");
+      assert.ok(exists(this.get('subject').filter().icon()), "it has an icon");
     });
   }
 });
@@ -78,10 +87,10 @@ componentTest('with no search icon', {
   template: '{{single-select filterable=true filterIcon=null}}',
 
   test(assert) {
-    expandSelectKit();
+    this.get('subject').expand();
 
     andThen(() => {
-      assert.equal(selectKit().filter.icon().length, 0, "it has no icon");
+      assert.notOk(exists(this.get('subject').filter().icon()), "it has no icon");
     });
   }
 });
@@ -90,10 +99,13 @@ componentTest('custom search icon', {
   template: '{{single-select filterable=true filterIcon="shower"}}',
 
   test(assert) {
-    expandSelectKit();
+    this.get('subject').expand();
 
     andThen(() => {
-      assert.ok(selectKit().filter.icon().hasClass("fa-shower"), "it has a the correct icon");
+      assert.ok(
+        this.get('subject').filter().icon().hasClass("fa-shower"),
+        "it has a the correct icon"
+      );
     });
   }
 });
@@ -101,13 +113,13 @@ componentTest('custom search icon', {
 componentTest('is expandable', {
   template: '{{single-select}}',
   test(assert) {
-    expandSelectKit();
+    this.get('subject').expand();
 
-    andThen(() => assert.ok(selectKit().isExpanded) );
+    andThen(() => assert.ok(this.get('subject').isExpanded()) );
 
-    collapseSelectKit();
+    this.get('subject').collapse();
 
-    andThen(() => assert.notOk(selectKit().isExpanded) );
+    andThen(() => assert.notOk(this.get('subject').isExpanded()) );
   }
 });
 
@@ -120,10 +132,10 @@ componentTest('accepts custom value/name keys', {
   },
 
   test(assert) {
-    expandSelectKit();
+    this.get('subject').expand();
 
     andThen(() => {
-      assert.equal(selectKit().selectedRow.name(), "robin");
+      assert.equal(this.get('subject').selectedRow().name(), "robin");
     });
   }
 });
@@ -138,7 +150,7 @@ componentTest('doesn’t render collection content before first expand', {
   test(assert) {
     assert.notOk(exists(find(".select-kit-collection")));
 
-    expandSelectKit();
+    this.get('subject').expand();
 
     andThen(() => {
       assert.ok(exists(find(".select-kit-collection")));
@@ -154,7 +166,7 @@ componentTest('supports options to limit size', {
   },
 
   test(assert) {
-    expandSelectKit();
+    this.get('subject').expand();
 
     andThen(() => {
       const height = find(".select-kit-collection").height();
@@ -171,16 +183,20 @@ componentTest('dynamic headerText', {
   },
 
   test(assert) {
-    expandSelectKit();
+    this.get('subject').expand();
 
     andThen(() => {
-      assert.equal(selectKit().header.name(), "robin");
+      assert.equal(this.get('subject').header().name(), 'robin');
     });
 
-    selectKitSelectRow(2);
+    this.get('subject').selectRowByValue(2);
 
     andThen(() => {
-      assert.equal(selectKit().header.name(), "regis", "it changes header text");
+      assert.equal(
+        this.get('subject').header().name(),
+        'regis',
+        'it changes header text'
+      );
     });
   }
 });
@@ -196,9 +212,13 @@ componentTest('supports custom row template', {
   },
 
   test(assert) {
-    expandSelectKit();
+    this.get('subject').expand();
 
-    andThen(() => assert.equal(selectKit().rowByValue(1).el.html().trim(), "<b>robin</b>") );
+    andThen(() => {
+      assert.equal(
+        this.get('subject').rowByValue(1).el().html().trim(), "<b>robin</b>"
+      );
+    });
   }
 });
 
@@ -206,22 +226,25 @@ componentTest('supports converting select value to integer', {
   template: '{{single-select value=value content=content castInteger=true}}',
 
   beforeEach() {
-    this.set("value", 2);
-    this.set("content", [{ id: "1", name: "robin"}, {id: "2", name: "régis" }]);
+    this.set('value', 2);
+    this.set('content', [{ id: '1', name: 'robin'}, {id: '2', name: 'régis' }]);
   },
 
   test(assert) {
-    expandSelectKit();
+    this.get('subject').expand();
 
-    andThen(() => assert.equal(selectKit().selectedRow.name(), "régis") );
+    andThen(() => assert.equal(this.get('subject').selectedRow().name(), 'régis') );
 
     andThen(() => {
-      this.set("value", 3);
-      this.set("content", [{ id: "3", name: "jeff" }]);
+      this.set('value', 1);
     });
 
     andThen(() => {
-      assert.equal(selectKit().selectedRow.name(), "jeff", "it works with dynamic content");
+      assert.equal(
+        this.get('subject').selectedRow().name(),
+        'robin',
+        'it works with dynamic content'
+      );
     });
   }
 });
@@ -234,49 +257,41 @@ componentTest('supports keyboard events', {
   },
 
   test(assert) {
-    expandSelectKit();
-
-    selectKit().keyboard.down();
+    this.get('subject').expand().keyboard().down();
 
     andThen(() => {
-      assert.equal(selectKit().highlightedRow.title(), "regis", "the next row is highlighted");
+      assert.equal(this.get('subject').highlightedRow().title(), "regis", "the next row is highlighted");
     });
 
-    selectKit().keyboard.down();
+    this.get('subject').keyboard().down();
 
     andThen(() => {
-      assert.equal(selectKit().highlightedRow.title(), "robin", "it returns to the first row");
+      assert.equal(this.get('subject').highlightedRow().title(), "robin", "it returns to the first row");
     });
 
-    selectKit().keyboard.up();
+    this.get('subject').keyboard().up();
 
     andThen(() => {
-      assert.equal(selectKit().highlightedRow.title(), "regis", "it highlights the last row");
+      assert.equal(this.get('subject').highlightedRow().title(), "regis", "it highlights the last row");
     });
 
-    selectKit().keyboard.enter();
+    this.get('subject').keyboard().enter();
 
     andThen(() => {
-      assert.equal(selectKit().selectedRow.title(), "regis", "it selects the row when pressing enter");
-      assert.notOk(selectKit().isExpanded, "it collapses the select box when selecting a row");
+      assert.equal(this.get('subject').selectedRow().title(), "regis", "it selects the row when pressing enter");
+      assert.notOk(this.get('subject').isExpanded(), "it collapses the select box when selecting a row");
     });
 
-    expandSelectKit();
-
-    selectKit().keyboard.escape();
+    this.get('subject').expand().keyboard().escape();
 
     andThen(() => {
-      assert.notOk(selectKit().isExpanded, "it collapses the select box");
+      assert.notOk(this.get('subject').isExpanded(), "it collapses the select box");
     });
 
-    expandSelectKit();
-
-    selectKitFillInFilter("regis");
-
-    selectKit().keyboard.tab();
+    this.get('subject').expand().fillInFilter('regis').keyboard().tab();
 
     andThen(() => {
-      assert.notOk(selectKit().isExpanded, "it collapses the select box when selecting a row");
+      assert.notOk(this.get('subject').isExpanded(), "it collapses the select box when selecting a row");
     });
   }
 });
@@ -302,18 +317,18 @@ componentTest('support appending content through plugin api', {
 
   beforeEach() {
     withPluginApi('0.8.13', api => {
-      api.modifySelectKit("select-kit")
-         .appendContent([{ id: "2", name: "regis"}]);
+      api.modifySelectKit('select-kit')
+         .appendContent([{ id: '2', name: 'regis'}]);
     });
 
-    this.set("content", [{ id: "1", name: "robin"}]);
+    this.set('content', [{ id: '1', name: 'robin'}]);
   },
   test(assert) {
-    expandSelectKit();
+    this.get('subject').expand();
 
     andThen(() => {
-      assert.equal(selectKit().rows.length, 2);
-      assert.equal(selectKit().rows.eq(1).data("name"), "regis");
+      assert.equal(this.get('subject').rows().length, 2);
+      assert.equal(this.get('subject').rowByIndex(1).name(), 'regis');
     });
 
     andThen(() => clearCallbacks());
@@ -336,11 +351,11 @@ componentTest('support modifying content through plugin api', {
   },
 
   test(assert) {
-    expandSelectKit();
+    this.get('subject').expand();
 
     andThen(() => {
-      assert.equal(selectKit().rows.length, 3);
-      assert.equal(selectKit().rows.eq(1).data("name"), "sam");
+      assert.equal(this.get('subject').rows().length, 3);
+      assert.equal(this.get('subject').rowByIndex(1).name(), "sam");
     });
 
     andThen(() => clearCallbacks());
@@ -360,11 +375,11 @@ componentTest('support prepending content through plugin api', {
   },
 
   test(assert) {
-    expandSelectKit();
+    this.get('subject').expand();
 
     andThen(() => {
-      assert.equal(selectKit().rows.length, 2);
-      assert.equal(selectKit().rows.eq(0).data("name"), "regis");
+      assert.equal(this.get('subject').rows().length, 2);
+      assert.equal(this.get('subject').rowByIndex(0).name(), "regis");
     });
 
     andThen(() => clearCallbacks());
@@ -387,9 +402,7 @@ componentTest('support modifying on select behavior through plugin api', {
   },
 
   test(assert) {
-    expandSelectKit();
-
-    selectKitSelectRow(1);
+    this.get('subject').expand().selectRowByValue(1);
 
     andThen(() => {
       assert.equal(find(".on-select-test").html(), "1");
@@ -408,10 +421,10 @@ componentTest('with nameChanges', {
   },
 
   test(assert) {
-    expandSelectKit();
+    this.get('subject').expand();
 
     andThen(() => {
-      assert.equal(selectKit().header.name(), "robin");
+      assert.equal(this.get('subject').header().name(), "robin");
     });
 
     andThen(() => {
@@ -419,7 +432,7 @@ componentTest('with nameChanges', {
     });
 
     andThen(() => {
-      assert.equal(selectKit().header.name(), "robin2");
+      assert.equal(this.get('subject').header().name(), "robin2");
     });
   }
 });
@@ -433,10 +446,11 @@ componentTest('with null value', {
   },
 
   test(assert) {
-    expandSelectKit();
+    this.get('subject').expand();
 
     andThen(() => {
-      assert.equal(selectKit().header.name(), "robin");
+      assert.equal(this.get('subject').header().name(), "robin");
+      assert.equal(this.get('subject').header().value(), undefined);
     });
   }
 });
@@ -449,7 +463,8 @@ componentTest('with collection header', {
   },
 
   test(assert) {
-    expandSelectKit();
+    this.get('subject').expand();
+
     andThen(() => assert.ok(exists(".collection-header h2")));
   }
 });
@@ -463,7 +478,9 @@ componentTest('with onToggle', {
 
   test(assert) {
     andThen(() => assert.notOk(exists(".onToggleTest")));
-    expandSelectKit();
+
+    this.get('subject').expand();
+
     andThen(() => assert.ok(exists(".onToggleTest")));
   }
 });
@@ -477,7 +494,9 @@ componentTest('with onExpand', {
 
   test(assert) {
     andThen(() => assert.notOk(exists(".onExpandTest")));
-    expandSelectKit();
+
+    this.get('subject').expand();
+
     andThen(() => assert.ok(exists(".onExpandTest")));
   }
 });
@@ -491,9 +510,25 @@ componentTest('with onCollapse', {
 
   test(assert) {
     andThen(() => assert.notOk(exists(".onCollapseTest")));
-    expandSelectKit();
+
+    this.get('subject').expand();
+
     andThen(() => assert.notOk(exists(".onCollapseTest")));
-    collapseSelectKit();
+
+    this.get('subject').collapse();
+
     andThen(() => assert.ok(exists(".onCollapseTest")));
   }
 });
+
+componentTest('with title', {
+  template: '{{single-select title=(i18n "test.title")}}',
+
+  beforeEach() {
+    I18n.translations[I18n.locale].js.test = {title: 'My title'};
+  },
+
+  test(assert) {
+    andThen(() => assert.equal(this.get('subject').header().title(), 'My title') );
+  }
+});
diff --git a/test/javascripts/components/topic-footer-mobile-dropdown-test.js.es6 b/test/javascripts/components/topic-footer-mobile-dropdown-test.js.es6
index 9a5534c3cb4..3bdd8ec4fc6 100644
--- a/test/javascripts/components/topic-footer-mobile-dropdown-test.js.es6
+++ b/test/javascripts/components/topic-footer-mobile-dropdown-test.js.es6
@@ -8,28 +8,37 @@ const buildTopic = function() {
   });
 };
 
-moduleForComponent('topic-footer-mobile-dropdown', {integration: true});
+moduleForComponent('topic-footer-mobile-dropdown', {
+  integration: true,
+  beforeEach: function() {
+    this.set('subject', selectKit());
+  }
+});
 
 componentTest('default', {
   template: '{{topic-footer-mobile-dropdown topic=topic}}',
   beforeEach() {
-    this.set("topic", buildTopic());
+    this.set('topic', buildTopic());
   },
 
   test(assert) {
-    expandSelectKit();
+    this.get('subject').expand();
 
     andThen(() => {
-      assert.equal(selectKit().header.name(), "Topic Controls");
-      assert.equal(selectKit().rowByIndex(0).name(), "Bookmark");
-      assert.equal(selectKit().rowByIndex(1).name(), "Share");
-      assert.equal(selectKit().selectedRow.el.length, 0, "it doesn’t preselect first row");
+      assert.equal(this.get('subject').header().title(), 'Topic Controls');
+      assert.equal(this.get('subject').header().value(), null);
+      assert.equal(this.get('subject').rowByIndex(0).name(), 'Bookmark');
+      assert.equal(this.get('subject').rowByIndex(1).name(), 'Share');
+      assert.notOk(
+        this.get('subject').selectedRow().exists(),
+        'it doesn’t preselect first row'
+      );
     });
 
-    selectKitSelectRow("share");
+    this.get('subject').selectRowByValue('share');
 
     andThen(() => {
-      assert.equal(this.get("value"), null, "it resets the value");
+      assert.equal(this.get('value'), null, 'it resets the value');
     });
   }
 });
diff --git a/test/javascripts/components/topic-notifications-button-test.js.es6 b/test/javascripts/components/topic-notifications-button-test.js.es6
index 0d41471e31a..8dac8739395 100644
--- a/test/javascripts/components/topic-notifications-button-test.js.es6
+++ b/test/javascripts/components/topic-notifications-button-test.js.es6
@@ -22,13 +22,13 @@ componentTest('the header has a localized title', {
 
   test(assert) {
     andThen(() => {
-      assert.equal(selectKit().header.name(), "Normal", "it has the correct title");
+      assert.equal(selectKit().header().name(), "Normal", "it has the correct title");
     });
 
     this.set("topic", buildTopic(2));
 
     andThen(() => {
-      assert.equal(selectKit().header.name(), "Tracking", "it correctly changes the title");
+      assert.equal(selectKit().header().name(), "Tracking", "it correctly changes the title");
     });
   }
 });
diff --git a/test/javascripts/helpers/assertions.js b/test/javascripts/helpers/assertions.js
index 4e6b53b5d16..95d7b2a7dd5 100644
--- a/test/javascripts/helpers/assertions.js
+++ b/test/javascripts/helpers/assertions.js
@@ -12,12 +12,6 @@ function visible(selector) {
   return find(selector + ":visible").length > 0;
 }
 
-Ember.Test.registerAsyncHelper('selectDropdown', function(app, selector, itemId) {
-  var $select2 = find(selector);
-  $select2.select2('val', itemId.toString());
-  $select2.trigger("change");
-});
-
 function invisible(selector) {
   var $items = find(selector + ":visible");
   return $items.length === 0 ||
diff --git a/test/javascripts/helpers/select-kit-helper.js b/test/javascripts/helpers/select-kit-helper.js
index 80c63736063..be2debd6a83 100644
--- a/test/javascripts/helpers/select-kit-helper.js
+++ b/test/javascripts/helpers/select-kit-helper.js
@@ -11,52 +11,42 @@ function checkSelectKitIsNotCollapsed(selector) {
 }
 
 Ember.Test.registerAsyncHelper('expandSelectKit', function(app, selector) {
-  selector = selector || '.select-kit';
-
   checkSelectKitIsNotExpanded(selector);
-
   click(selector + ' .select-kit-header');
 });
 
 Ember.Test.registerAsyncHelper('collapseSelectKit', function(app, selector) {
-  selector = selector || '.select-kit';
-
   checkSelectKitIsNotCollapsed(selector);
-
   click(selector + ' .select-kit-header');
 });
 
-Ember.Test.registerAsyncHelper('selectKitSelectRow', function(app, rowValue, options) {
-  options = options || {};
-  options.selector = options.selector || '.select-kit';
-
-  checkSelectKitIsNotCollapsed(options.selector);
-
-  click(options.selector + " .select-kit-row[data-value='" + rowValue + "']");
+Ember.Test.registerAsyncHelper('selectKitFillInFilter', function(app, filter, selector) {
+  checkSelectKitIsNotCollapsed(selector);
+  fillIn(selector + ' .filter-input', filter);
 });
 
-Ember.Test.registerAsyncHelper('selectKitSelectNoneRow', function(app, options) {
-  options = options || {};
-  options.selector = options.selector || '.select-kit';
-
-  checkSelectKitIsNotCollapsed(options.selector);
-
-  click(options.selector + " .select-kit-row.none");
+Ember.Test.registerAsyncHelper('selectKitSelectRowByValue', function(app, value, selector) {
+  checkSelectKitIsNotCollapsed(selector);
+  click(selector + " .select-kit-row[data-value='" + value + "']");
 });
 
-Ember.Test.registerAsyncHelper('selectKitFillInFilter', function(app, filter, options) {
-  options = options || {};
-  options.selector = options.selector || '.select-kit';
+Ember.Test.registerAsyncHelper('selectKitSelectRowByName', function(app, name, selector) {
+  checkSelectKitIsNotCollapsed(selector);
+  click(selector + " .select-kit-row[data-name='" + name + "']");
+});
 
-  checkSelectKitIsNotCollapsed(options.selector);
-
-  var filterQuerySelector = options.selector + ' .filter-input';
-  fillIn(filterQuerySelector, filter);
+Ember.Test.registerAsyncHelper('selectKitSelectNoneRow', function(app, selector) {
+  checkSelectKitIsNotCollapsed(selector);
+  click(selector + " .select-kit-row.none");
+});
 
+Ember.Test.registerAsyncHelper('selectKitSelectRowByIndex', function(app, index, selector) {
+  checkSelectKitIsNotCollapsed(selector);
+  click(find(selector + " .select-kit-row").eq(index));
 });
 
 function selectKit(selector) { // eslint-disable-line no-unused-vars
-  selector = selector || '.select-kit';
+  selector = selector || ".select-kit";
 
   function rowHelper(row) {
     return {
@@ -64,18 +54,18 @@ function selectKit(selector) { // eslint-disable-line no-unused-vars
       icon: function() { return row.find('.d-icon'); },
       title: function() { return row.attr('title'); },
       value: function() { return row.attr('data-value'); },
-      el: row
+      exists: function() { return exists(row); },
+      el: function() { return row; }
     };
   }
 
   function headerHelper(header) {
     return {
-      name: function() {
-        return header.attr('data-name');
-      },
+      value: function() { return header.attr('data-value'); },
+      name: function() { return header.attr('data-name'); },
       icon: function() { return header.find('.icon'); },
       title: function() { return header.attr('title'); },
-      el: header
+      el: function() { return header; }
     };
   }
 
@@ -83,14 +73,14 @@ function selectKit(selector) { // eslint-disable-line no-unused-vars
     return {
       icon: function() { return filter.find('.d-icon'); },
       exists: function() { return exists(filter); },
-      el: filter
+      el: function() { return filter; }
     };
   }
 
-  function keyboardHelper() {
+  function keyboardHelper(eventSelector) {
     function createEvent(target, keyCode, options) {
       target = target || ".filter-input";
-      selector = find(selector).find(target);
+      eventSelector = find(eventSelector).find(target);
       options = options || {};
 
       andThen(function() {
@@ -98,7 +88,7 @@ function selectKit(selector) { // eslint-disable-line no-unused-vars
         var event = jQuery.Event(type);
         event.keyCode = keyCode;
         if (options && options.metaKey === true) { event.metaKey = true; }
-        find(selector).trigger(event);
+        find(eventSelector).trigger(event);
       });
     }
 
@@ -111,20 +101,69 @@ function selectKit(selector) { // eslint-disable-line no-unused-vars
       backspace: function(target) { createEvent(target, 8); },
       selectAll: function(target) { createEvent(target, 65, {metaKey: true}); },
     };
-  }
+  };
 
   return {
-    keyboard: keyboardHelper(),
+    expand: function() {
+      expandSelectKit(selector);
+      return selectKit(selector);
+    },
 
-    isExpanded: find(selector).hasClass('is-expanded'),
+    collapse: function() {
+      collapseSelectKit(selector);
+      return selectKit(selector);
+    },
 
-    isHidden: find(selector).hasClass('is-hidden'),
+    selectRowByIndex: function(index) {
+      selectKitSelectRowByIndex(index, selector);
+      return selectKit(selector);
+    },
 
-    header: headerHelper(find(selector).find('.select-kit-header')),
+    selectRowByValue: function(value) {
+      selectKitSelectRowByValue(value, selector);
+      return selectKit(selector);
+    },
 
-    filter: filterHelper(find(selector).find('.select-kit-filter')),
+    selectRowByName: function(name) {
+      selectKitSelectRowByValue(name, selector);
+      return selectKit(selector);
+    },
 
-    rows: find(selector).find('.select-kit-row'),
+    selectNoneRow: function() {
+      selectKitSelectNoneRow(selector);
+      return selectKit(selector);
+    },
+
+    fillInFilter: function(filter) {
+      selectKitFillInFilter(filter, selector);
+      return selectKit(selector);
+    },
+
+    keyboard: function() { return keyboardHelper(selector); },
+
+    isExpanded: function() {
+      return find(selector).hasClass('is-expanded');
+    },
+
+    isFocused: function() {
+      return find(selector).hasClass('is-focused');
+    },
+
+    isHidden: function() {
+      return find(selector).hasClass('is-hidden');
+    },
+
+    header: function() {
+      return headerHelper(find(selector).find('.select-kit-header'));
+    },
+
+    filter: function() {
+      return filterHelper(find(selector).find('.select-kit-filter'));
+    },
+
+    rows: function() {
+      return find(selector).find('.select-kit-row');
+    },
 
     rowByValue: function(value) {
       return rowHelper(find(selector).find('.select-kit-row[data-value="' + value + '"]'));
@@ -138,12 +177,20 @@ function selectKit(selector) { // eslint-disable-line no-unused-vars
       return rowHelper(find(selector).find('.select-kit-row:eq(' + index + ')'));
     },
 
-    el: find(selector),
+    el: function() { return find(selector); },
 
-    noneRow: rowHelper(find(selector).find('.select-kit-row.none')),
+    noneRow: function() {
+      return rowHelper(find(selector).find('.select-kit-row.none'));
+    },
 
-    selectedRow: rowHelper(find(selector).find('.select-kit-row.is-selected')),
+    selectedRow: function() {
+      return rowHelper(find(selector).find('.select-kit-row.is-selected'));
+    },
 
-    highlightedRow: rowHelper(find(selector).find('.select-kit-row.is-highlighted'))
+    highlightedRow: function() {
+      return rowHelper(find(selector).find('.select-kit-row.is-highlighted'));
+    },
+
+    exists: function() { return exists(selector); }
   };
 }