summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhil Hughes <me@iamphill.com>2018-10-22 08:31:24 +0000
committerPhil Hughes <me@iamphill.com>2018-10-22 08:31:24 +0000
commit5b6007f995b60b938c65efe9a18ed4f2c7dafa4d (patch)
treec1db004b8a9960da7a6bd2599e22f364a7ba966c
parent1438e322a5cb7f3207d64f35d873db8a160cde94 (diff)
parent25170fbe7b004e368d68c3b73be9232611bda48d (diff)
downloadgitlab-ce-5b6007f995b60b938c65efe9a18ed4f2c7dafa4d.tar.gz
Merge branch 'fe-ac-review-app-changes-33418' into 'master'
Frontend: Review app changes Closes #33418 See merge request gitlab-org/gitlab-ce!22363
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/deployment.vue75
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue7
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue143
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss15
-rw-r--r--app/controllers/projects/merge_requests_controller.rb3
-rw-r--r--changelogs/unreleased/fe-ac-review-app-changes-33418.yml5
-rw-r--r--locale/gitlab.pot3
-rw-r--r--spec/javascripts/vue_mr_widget/components/deployment_spec.js52
-rw-r--r--spec/javascripts/vue_mr_widget/mr_widget_options_spec.js94
-rw-r--r--spec/javascripts/vue_shared/components/filtered_search_dropdown_spec.js91
10 files changed, 427 insertions, 61 deletions
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
index 9161f703697..6c87287a4c4 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
@@ -1,6 +1,7 @@
<script>
import Icon from '~/vue_shared/components/icon.vue';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
+import FilteredSearchDropdown from '~/vue_shared/components/filtered_search_dropdown.vue';
import timeagoMixin from '../../vue_shared/mixins/timeago';
import tooltip from '../../vue_shared/directives/tooltip';
import LoadingButton from '../../vue_shared/components/loading_button.vue';
@@ -18,6 +19,7 @@ export default {
StatusIcon,
Icon,
TooltipOnTruncate,
+ FilteredSearchDropdown,
},
directives: {
tooltip,
@@ -30,8 +32,10 @@ export default {
},
},
data() {
+ const features = window.gon.features || {};
return {
isStopping: false,
+ enableCiEnvironmentsStatusChanges: features.ciEnvironmentsStatusChanges,
};
},
computed: {
@@ -118,18 +122,65 @@ export default {
/>
</div>
<div>
- <a
- v-if="hasExternalUrls"
- :href="deployment.external_url"
- target="_blank"
- rel="noopener noreferrer nofollow"
- class="deploy-link js-deploy-url btn btn-default btn-sm inline"
- >
- <span>
- View app
- <icon name="external-link" />
- </span>
- </a>
+ <template v-if="hasExternalUrls">
+ <filtered-search-dropdown
+ v-if="enableCiEnvironmentsStatusChanges"
+ class="js-mr-wigdet-deployment-dropdown inline"
+ :items="deployment.changes"
+ :main-action-link="deployment.external_url"
+ filter-key="path"
+ >
+ <template
+ slot="mainAction"
+ slot-scope="slotProps"
+ >
+ <a
+ :href="deployment.external_url"
+ target="_blank"
+ rel="noopener noreferrer nofollow"
+ class="deploy-link js-deploy-url inline"
+ :class="slotProps.className"
+ >
+ <span>
+ {{ __('View app') }}
+ <icon name="external-link" />
+ </span>
+ </a>
+ </template>
+
+ <template
+ slot="result"
+ slot-scope="slotProps"
+ >
+ <a
+ :href="slotProps.result.external_url"
+ target="_blank"
+ rel="noopener noreferrer nofollow"
+ class="menu-item"
+ >
+ <strong class="str-truncated-100 append-bottom-0 d-block">
+ {{ slotProps.result.path }}
+ </strong>
+
+ <p class="text-secondary str-truncated-100 append-bottom-0 d-block">
+ {{ slotProps.result.external_url }}
+ </p>
+ </a>
+ </template>
+ </filtered-search-dropdown>
+ <a
+ v-else
+ :href="deployment.external_url"
+ target="_blank"
+ rel="noopener noreferrer nofollow"
+ class="js-deploy-url js-deploy-url-feature-flag deploy-link btn btn-default btn-sm inline"
+ >
+ <span>
+ {{ __('View app') }}
+ <icon name="external-link" />
+ </span>
+ </a>
+ </template>
<loading-button
v-if="deployment.stop_url"
:loading="isStopping"
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index a599cc002d6..8180f13a7cb 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -112,7 +112,8 @@ export default {
eventHub.$on('mr.discussion.updated', this.checkStatus);
},
mounted() {
- this.handleMounted();
+ this.setFaviconHelper();
+ this.initDeploymentsPolling();
},
beforeDestroy() {
eventHub.$off('mr.discussion.updated', this.checkStatus);
@@ -250,10 +251,6 @@ export default {
this.stopPolling();
});
},
- handleMounted() {
- this.setFaviconHelper();
- this.initDeploymentsPolling();
- },
},
};
</script>
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue b/app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue
new file mode 100644
index 00000000000..460fa6ad72e
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue
@@ -0,0 +1,143 @@
+<script>
+import $ from 'jquery';
+import Icon from '~/vue_shared/components/icon.vue';
+/**
+ * Renders a split dropdown with
+ * an input that allows to search through the given
+ * array of options.
+ */
+export default {
+ name: 'FilteredSearchDropdown',
+ components: {
+ Icon,
+ },
+ props: {
+ title: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ buttonType: {
+ required: false,
+ validator: value =>
+ ['primary', 'default', 'secondary', 'success', 'info', 'warning', 'danger'].indexOf(
+ value,
+ ) !== -1,
+ default: 'default',
+ },
+ size: {
+ required: false,
+ type: String,
+ default: 'sm',
+ },
+ items: {
+ type: Array,
+ required: true,
+ },
+ visibleItems: {
+ type: Number,
+ required: false,
+ default: 5,
+ },
+ filterKey: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ filter: '',
+ };
+ },
+ computed: {
+ className() {
+ return `btn btn-${this.buttonType} btn-${this.size}`;
+ },
+ filteredResults() {
+ if (this.filter !== '') {
+ return this.items.filter(
+ item => item[this.filterKey] && item[this.filterKey].toLowerCase().includes(this.filter.toLowerCase()),
+ );
+ }
+
+ return this.items.slice(0, this.visibleItems);
+ }
+ },
+ mounted() {
+ /**
+ * Resets the filter every time the user closes the dropdown
+ */
+ $(this.$el)
+ .on('shown.bs.dropdown', () => {
+ this.$nextTick(() => this.$refs.searchInput.focus());
+ })
+ .on('hidden.bs.dropdown', () => {
+ this.filter = '';
+ });
+ },
+};
+</script>
+<template>
+ <div class="dropdown">
+ <div class="btn-group">
+ <slot
+ name="mainAction"
+ :class-name="className"
+ >
+ <button
+ type="button"
+ :class="className"
+ >
+ {{ title }}
+ </button>
+ </slot>
+
+ <button
+ type="button"
+ :class="className"
+ class="dropdown-toggle dropdown-toggle-split"
+ data-toggle="dropdown"
+ aria-haspopup="true"
+ aria-expanded="false"
+ aria-label="Expand dropdown"
+ >
+ <icon
+ name="angle-down"
+ :size="12"
+ />
+ </button>
+ <div class="dropdown-menu dropdown-menu-right">
+ <div class="dropdown-input">
+ <input
+ ref="searchInput"
+ v-model="filter"
+ type="search"
+ placeholder="Filter"
+ class="js-filtered-dropdown-input dropdown-input-field"
+ />
+ <icon
+ class="dropdown-input-search"
+ name="search"
+ />
+ </div>
+
+ <div class="dropdown-content">
+ <ul>
+ <li
+ v-for="(result, i) in filteredResults"
+ :key="i"
+ class="js-filtered-dropdown-result"
+ >
+ <slot
+ name="result"
+ :result="result"
+ >
+ {{ result[filterKey] }}
+ </slot>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 895db89f289..2feb7464ecb 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -47,7 +47,6 @@
}
}
-
.mr-widget-heading {
position: relative;
border: 1px solid $border-color;
@@ -454,7 +453,7 @@
.mr-list {
.merge-request {
- padding: 10px 0 10px 15px;
+ padding: 10px 0 10px 15px;
position: relative;
display: -webkit-flex;
display: flex;
@@ -468,7 +467,6 @@
margin-bottom: 2px;
.ci-status-link {
-
svg {
height: 16px;
width: 16px;
@@ -698,7 +696,6 @@
.table-holder {
.ci-table {
-
th {
background-color: $white-light;
color: $gl-text-color-secondary;
@@ -775,7 +772,7 @@
&.affix {
left: 0;
- transition: right .15s;
+ transition: right 0.15s;
@include media-breakpoint-down(xs) {
right: 0;
@@ -884,7 +881,7 @@
}
> *:not(:last-child) {
- margin-right: .3em;
+ margin-right: 0.3em;
}
svg {
@@ -907,6 +904,10 @@
.btn svg {
fill: $theme-gray-700;
}
+
+ .dropdown-menu {
+ width: 400px;
+ }
}
// Hack alert: we've rewritten `btn` class in a way that
@@ -917,7 +918,7 @@
&[disabled] {
cursor: not-allowed;
box-shadow: none;
- opacity: .65;
+ opacity: 0.65;
&:hover {
color: $gl-gray-500;
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index f87337b67aa..757b03d0b0e 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -14,6 +14,9 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
before_action :set_issuables_index, only: [:index]
before_action :authenticate_user!, only: [:assign_related_issues]
before_action :check_user_can_push_to_source_branch!, only: [:rebase]
+ before_action do
+ push_frontend_feature_flag(:ci_environments_status_changes)
+ end
def index
@merge_requests = @issuables
diff --git a/changelogs/unreleased/fe-ac-review-app-changes-33418.yml b/changelogs/unreleased/fe-ac-review-app-changes-33418.yml
new file mode 100644
index 00000000000..e4803683062
--- /dev/null
+++ b/changelogs/unreleased/fe-ac-review-app-changes-33418.yml
@@ -0,0 +1,5 @@
+---
+title: Adds filtered dropdown with changed files in review
+merge_request:
+author:
+type: changed
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index d73beaf93b6..bb18d4eccd8 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -6717,6 +6717,9 @@ msgstr ""
msgid "Version"
msgstr ""
+msgid "View app"
+msgstr ""
+
msgid "View file @ "
msgstr ""
diff --git a/spec/javascripts/vue_mr_widget/components/deployment_spec.js b/spec/javascripts/vue_mr_widget/components/deployment_spec.js
index 20b5532a837..ce850bc621e 100644
--- a/spec/javascripts/vue_mr_widget/components/deployment_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/deployment_spec.js
@@ -14,6 +14,20 @@ const deploymentMockData = {
external_url_formatted: 'diplo.',
deployed_at: '2017-03-22T22:44:42.258Z',
deployed_at_formatted: 'Mar 22, 2017 10:44pm',
+ changes: [
+ {
+ path: 'index.html',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html',
+ },
+ {
+ path: 'imgs/gallery.html',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html',
+ },
+ {
+ path: 'about/',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/',
+ },
+ ],
};
const createComponent = () => {
const Component = Vue.extend(deploymentComponent);
@@ -176,4 +190,42 @@ describe('Deployment component', () => {
expect(el.querySelector('.js-mr-memory-usage')).not.toBeNull();
});
});
+
+ describe('with `features.ciEnvironmentsStatusChanges` enabled', () => {
+ beforeEach(() => {
+ window.gon = window.gon || {};
+ window.gon.features = window.gon.features || {};
+ window.gon.features.ciEnvironmentsStatusChanges = true;
+
+ vm = createComponent(deploymentMockData);
+ });
+
+ afterEach(() => {
+ window.gon.features = {};
+ });
+
+ it('renders dropdown with changes', () => {
+ expect(vm.$el.querySelector('.js-mr-wigdet-deployment-dropdown')).not.toBeNull();
+ expect(vm.$el.querySelector('.js-deploy-url-feature-flag')).toBeNull();
+ });
+ });
+
+ describe('with `features.ciEnvironmentsStatusChanges` disabled', () => {
+ beforeEach(() => {
+ window.gon = window.gon || {};
+ window.gon.features = window.gon.features || {};
+ window.gon.features.ciEnvironmentsStatusChanges = false;
+
+ vm = createComponent(deploymentMockData);
+ });
+
+ afterEach(() => {
+ delete window.gon.features.ciEnvironmentsStatusChanges;
+ });
+
+ it('renders the old link to the review app', () => {
+ expect(vm.$el.querySelector('.js-mr-wigdet-deployment-dropdown')).toBeNull();
+ expect(vm.$el.querySelector('.js-deploy-url-feature-flag')).not.toBeNull();
+ });
+ });
});
diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
index 6b5e32fdfd5..d1a064b9f4d 100644
--- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
+++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
@@ -7,11 +7,12 @@ import mountComponent from 'spec/helpers/vue_mount_component_helper';
import mockData from './mock_data';
import { faviconDataUrl, overlayDataUrl, faviconWithOverlayDataUrl } from '../lib/utils/mock_data';
-const returnPromise = data => new Promise((resolve) => {
- resolve({
- data,
+const returnPromise = data =>
+ new Promise(resolve => {
+ resolve({
+ data,
+ });
});
-});
describe('mrWidgetOptions', () => {
let vm;
@@ -135,7 +136,7 @@ describe('mrWidgetOptions', () => {
describe('methods', () => {
describe('checkStatus', () => {
- it('should tell service to check status', (done) => {
+ it('should tell service to check status', done => {
spyOn(vm.service, 'checkStatus').and.returnValue(returnPromise(mockData));
spyOn(vm.mr, 'setData');
spyOn(vm, 'handleNotification');
@@ -185,7 +186,7 @@ describe('mrWidgetOptions', () => {
});
describe('fetchDeployments', () => {
- it('should fetch deployments', (done) => {
+ it('should fetch deployments', done => {
spyOn(vm.service, 'fetchDeployments').and.returnValue(returnPromise([{ id: 1 }]));
vm.fetchDeployments();
@@ -200,7 +201,7 @@ describe('mrWidgetOptions', () => {
});
describe('fetchActionsContent', () => {
- it('should fetch content of Cherry Pick and Revert modals', (done) => {
+ it('should fetch content of Cherry Pick and Revert modals', done => {
spyOn(vm.service, 'fetchMergeActionsContent').and.returnValue(returnPromise('hello world'));
vm.fetchActionsContent();
@@ -251,7 +252,7 @@ describe('mrWidgetOptions', () => {
};
const allArgs = eventHub.$on.calls.allArgs();
- allArgs.forEach((params) => {
+ allArgs.forEach(params => {
const eventName = params[0];
const callback = params[1];
@@ -270,18 +271,6 @@ describe('mrWidgetOptions', () => {
});
});
- describe('handleMounted', () => {
- it('should call required methods to do the initial kick-off', () => {
- spyOn(vm, 'initDeploymentsPolling');
- spyOn(vm, 'setFaviconHelper');
-
- vm.handleMounted();
-
- expect(vm.setFaviconHelper).toHaveBeenCalled();
- expect(vm.initDeploymentsPolling).toHaveBeenCalled();
- });
- });
-
describe('setFavicon', () => {
let faviconElement;
@@ -298,13 +287,14 @@ describe('mrWidgetOptions', () => {
document.body.removeChild(document.getElementById('favicon'));
});
- it('should call setFavicon method', (done) => {
+ it('should call setFavicon method', done => {
vm.mr.ciStatusFaviconPath = overlayDataUrl;
- vm.setFaviconHelper().then(() => {
- expect(faviconElement.getAttribute('href')).toEqual(faviconWithOverlayDataUrl);
- done();
- })
- .catch(done.fail);
+ vm.setFaviconHelper()
+ .then(() => {
+ expect(faviconElement.getAttribute('href')).toEqual(faviconWithOverlayDataUrl);
+ done();
+ })
+ .catch(done.fail);
});
it('should not call setFavicon when there is no ciStatusFaviconPath', () => {
@@ -379,7 +369,7 @@ describe('mrWidgetOptions', () => {
});
describe('rendering relatedLinks', () => {
- beforeEach((done) => {
+ beforeEach(done => {
vm.mr.relatedLinks = {
assignToMe: null,
closing: `
@@ -396,7 +386,7 @@ describe('mrWidgetOptions', () => {
expect(vm.$el.querySelector('.close-related-link')).toBeDefined();
});
- it('does not render if state is nothingToMerge', (done) => {
+ it('does not render if state is nothingToMerge', done => {
vm.mr.state = stateKey.nothingToMerge;
Vue.nextTick(() => {
expect(vm.$el.querySelector('.close-related-link')).toBeNull();
@@ -406,7 +396,7 @@ describe('mrWidgetOptions', () => {
});
describe('rendering source branch removal status', () => {
- it('renders when user cannot remove branch and branch should be removed', (done) => {
+ it('renders when user cannot remove branch and branch should be removed', done => {
vm.mr.canRemoveSourceBranch = false;
vm.mr.shouldRemoveSourceBranch = true;
vm.mr.state = 'readyToMerge';
@@ -423,7 +413,7 @@ describe('mrWidgetOptions', () => {
});
});
- it('does not render in merged state', (done) => {
+ it('does not render in merged state', done => {
vm.mr.canRemoveSourceBranch = false;
vm.mr.shouldRemoveSourceBranch = true;
vm.mr.state = 'merged';
@@ -438,6 +428,20 @@ describe('mrWidgetOptions', () => {
});
describe('rendering deployments', () => {
+ const changes = [
+ {
+ path: 'index.html',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html',
+ },
+ {
+ path: 'imgs/gallery.html',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html',
+ },
+ {
+ path: 'about/',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/',
+ },
+ ];
const deploymentMockData = {
id: 15,
name: 'review/diplo',
@@ -449,15 +453,23 @@ describe('mrWidgetOptions', () => {
external_url_formatted: 'diplo.',
deployed_at: '2017-03-22T22:44:42.258Z',
deployed_at_formatted: 'Mar 22, 2017 10:44pm',
+ changes,
};
- beforeEach((done) => {
- vm.mr.deployments.push({
- ...deploymentMockData,
- }, {
- ...deploymentMockData,
- id: deploymentMockData.id + 1,
- });
+ beforeEach(done => {
+ window.gon = window.gon || {};
+ window.gon.features = window.gon.features || {};
+ window.gon.features.ciEnvironmentsStatusChanges = true;
+
+ vm.mr.deployments.push(
+ {
+ ...deploymentMockData,
+ },
+ {
+ ...deploymentMockData,
+ id: deploymentMockData.id + 1,
+ },
+ );
vm.$nextTick(done);
});
@@ -465,5 +477,13 @@ describe('mrWidgetOptions', () => {
it('renders multiple deployments', () => {
expect(vm.$el.querySelectorAll('.deploy-heading').length).toBe(2);
});
+
+ it('renders dropdpown with multiple file changes', () => {
+ expect(
+ vm.$el
+ .querySelector('.js-mr-wigdet-deployment-dropdown')
+ .querySelectorAll('.js-filtered-dropdown-result').length,
+ ).toEqual(changes.length);
+ });
});
});
diff --git a/spec/javascripts/vue_shared/components/filtered_search_dropdown_spec.js b/spec/javascripts/vue_shared/components/filtered_search_dropdown_spec.js
new file mode 100644
index 00000000000..b71cb36ecf6
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/filtered_search_dropdown_spec.js
@@ -0,0 +1,91 @@
+import Vue from 'vue';
+import component from '~/vue_shared/components/filtered_search_dropdown.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+
+describe('Filtered search dropdown', () => {
+ const Component = Vue.extend(component);
+ let vm;
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('with an empty array of items', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ items: [],
+ filterKey: '',
+ });
+ });
+
+ it('renders empty list', () => {
+ expect(vm.$el.querySelectorAll('.js-filtered-dropdown-result').length).toEqual(0);
+ });
+
+ it('renders filter input', () => {
+ expect(vm.$el.querySelector('.js-filtered-dropdown-input')).not.toBeNull();
+ });
+ });
+
+ describe('when visible numbers is less than the items length', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ items: [{ title: 'One' }, { title: 'Two' }, { title: 'Three' }],
+ visibleItems: 2,
+ filterKey: 'title',
+ });
+ });
+
+ it('it renders only the maximum number provided', () => {
+ expect(vm.$el.querySelectorAll('.js-filtered-dropdown-result').length).toEqual(2);
+ });
+ });
+
+ describe('when visible number is bigger than the items lenght', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ items: [{ title: 'One' }, { title: 'Two' }, { title: 'Three' }],
+ filterKey: 'title',
+ });
+ });
+
+ it('it renders the full list of items the maximum number provided', () => {
+ expect(vm.$el.querySelectorAll('.js-filtered-dropdown-result').length).toEqual(3);
+ });
+ });
+
+ describe('while filtering', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ items: [
+ { title: 'One' },
+ { title: 'Two/three' },
+ { title: 'Three four' },
+ { title: 'Five' },
+ ],
+ filterKey: 'title',
+ });
+ });
+
+ it('updates the results to match the typed value', done => {
+ vm.$el.querySelector('.js-filtered-dropdown-input').value = 'three';
+ vm.$el.querySelector('.js-filtered-dropdown-input').dispatchEvent(new Event('input'));
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelectorAll('.js-filtered-dropdown-result').length).toEqual(2);
+ done();
+ });
+ });
+
+ describe('when no value matches the typed one', () => {
+ it('does not render any result', done => {
+ vm.$el.querySelector('.js-filtered-dropdown-input').value = 'six';
+ vm.$el.querySelector('.js-filtered-dropdown-input').dispatchEvent(new Event('input'));
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelectorAll('.js-filtered-dropdown-result').length).toEqual(0);
+ done();
+ });
+ });
+ });
+ });
+});