2
0
mirror of https://github.com/tenrok/vue-select.git synced 2026-05-17 02:29:37 +03:00

Merge branch 'master' into ft/el-as-a-button-better-focusing

This commit is contained in:
Jeff
2020-03-05 19:39:27 -08:00
27 changed files with 4758 additions and 1280 deletions
+12
View File
@@ -0,0 +1,12 @@
## Pull Requests
Looks like you want to help out on vue-select.. awesome! Here's a few things to keep in mind when contributing.
- Vue Select uses semantic release to automate the release process. This means that good commit
messages are critical. If you're unfamiliar with [conventional changelog](https://github.com/ajoslin/conventional-changelog) you can run `yarn commit` to generate a commit message.
- It's almost always better to ask before you jump into a PR if you want to add new functionality
. It's not fun for anyone when you spend time on something that doesn't end up in the component.
- If your PR fixes or references an open issue, be sure to reference it in your message.
- If you're adding new functionality, make sure your code has good test coverage.
:tada: Thanks for contributing, and an even bigger thanks for reading these guidelines!
+28
View File
@@ -0,0 +1,28 @@
name: Release
on:
push:
branches:
- master
jobs:
release:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v1
- uses: actions/setup-node@v1
with:
node-version: 12
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Test with Coverage
run: yarn test
- name: Build
run: yarn build
- name: Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npx semantic-release
+38
View File
@@ -0,0 +1,38 @@
name: Test & Build
on: [pull_request]
jobs:
test:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v1
- uses: actions/setup-node@v1
with:
node-version: 12
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Test with Coverage
run: yarn test --coverage --coverageReporters=lcov
- name: Report Coverage
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
build:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v1
- uses: actions/setup-node@v1
with:
node-version: 12
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Build
run: yarn build
- name: Bundlewatch
run: npx bundlewatch
-9
View File
@@ -1,9 +0,0 @@
language: node_js
cache: yarn
node_js:
- "8"
script:
- yarn test --coverage --coverageReporters=text-lcov | coveralls
- yarn build && bundlewatch --max-size 20kb ./dist/!(*.map)
-10
View File
@@ -1,10 +0,0 @@
## Pull Requests
Looks like you want to help out on vue-select.. awesome! Here's a few things to keep in mind when contributing.
1. If your PR contains multiple commits, try to keep those commits succinct, with descriptive messages. This makes it easier to understand your thought process.
2. **Don't run the build** before submitting. The build is only run and committed immediately before a new release.
3. If your PR fixes or references an open issue, be sure to reference it in your message.
4. If you're adding new functionality, make sure your code has good test coverage.
:tada: Thanks for contributing, and an even bigger thanks for reading these guidelines!
@@ -0,0 +1,16 @@
<template>
<v-select>
<template v-slot:no-options="{ search, searching }">
<template v-if="searching">
No results found for <em>{{ search }}</em>.
</template>
<em style="opacity: 0.5;" v-else>Start typing to search for a country.</em>
</template>
</v-select>
</template>
<script>
export default {
name: 'BetterNoOptions',
};
</script>
+13
View File
@@ -120,6 +120,19 @@ clearSearchOnSelect: {
},
```
## clearSearchOnBlur
Enables/disables clearing the search text when the search input is blurred.
```js
clearSearchOnBlur: {
type: Function,
default: function ({ clearSearchOnSelect, multiple }) {
return clearSearchOnSelect && !multiple
}
},
```
## closeOnSelect
Close a dropdown when an option is chosen. Set to false to keep the dropdown
+72 -19
View File
@@ -7,13 +7,16 @@ Slots can be used to change the look and feel of the UI, or to simply swap out t
### `selected-option`
#### Scope:
#### Scope:
- `option {Object}` - A selected option
```html
<slot name="selected-option" v-bind="(typeof option === 'object')?option:{[label]: option}">
{{ getOptionLabel(option) }}
<slot
name="selected-option"
v-bind="(typeof option === 'object')?option:{[label]: option}"
>
{{ getOptionLabel(option) }}
</slot>
```
@@ -27,16 +30,32 @@ Slots can be used to change the look and feel of the UI, or to simply swap out t
- `multiple {Boolean}` - If the component supports the selection of multiple values
```html
<slot v-for="option in valueAsArray" name="selected-option-container"
:option="(typeof option === 'object')?option:{[label]: option}" :deselect="deselect" :multiple="multiple" :disabled="disabled">
<span class="selected-tag" v-bind:key="option.index">
<slot name="selected-option" v-bind="(typeof option === 'object')?option:{[label]: option}">
{{ getOptionLabel(option) }}
</slot>
<button v-if="multiple" :disabled="disabled" @click="deselect(option)" type="button" class="close" aria-label="Remove option">
<span aria-hidden="true">&times;</span>
</button>
</span>
<slot
v-for="option in valueAsArray"
name="selected-option-container"
:option="(typeof option === 'object')?option:{[label]: option}"
:deselect="deselect"
:multiple="multiple"
:disabled="disabled"
>
<span class="selected-tag" v-bind:key="option.index">
<slot
name="selected-option"
v-bind="(typeof option === 'object')?option:{[label]: option}"
>
{{ getOptionLabel(option) }}
</slot>
<button
v-if="multiple"
:disabled="disabled"
@click="deselect(option)"
type="button"
class="close"
aria-label="Remove option"
>
<span aria-hidden="true">&times;</span>
</button>
</span>
</slot>
```
@@ -44,9 +63,29 @@ Slots can be used to change the look and feel of the UI, or to simply swap out t
### `spinner`
#### Scope:
- `loading {Boolean}` - if the component is in a loading state
```html
<slot name="spinner">
<div class="spinner" v-show="mutableLoading">Loading...</div>
<slot name="spinner" v-bind="scope.spinner">
<div class="vs__spinner" v-show="mutableLoading">Loading...</div>
</slot>
```
### `open-indicator`
```js
attributes : {
'ref': 'openIndicator',
'role': 'presentation',
'class': 'vs__open-indicator',
}
```
```vue
<slot name="open-indicator" v-bind="scope.openIndicator">
<component :is="childComponents.OpenIndicator" v-if="!noDrop" v-bind="scope.openIndicator.attributes"/>
</slot>
```
@@ -54,12 +93,26 @@ Slots can be used to change the look and feel of the UI, or to simply swap out t
### `option`
#### Scope:
- `option {Object}` - The currently iterated option from `filteredOptions`
```html
<slot name="option" v-bind="(typeof option === 'object')?option:{[label]: option}">
{{ getOptionLabel(option) }}
<slot
name="option"
v-bind="(typeof option === 'object')?option:{[label]: option}"
>
{{ getOptionLabel(option) }}
</slot>
```
### `no-options`
The no options slot is displayed in the dropdown when `filteredOptions.length === 0`.
- `search {String}` - the current search text
- `searching {Boolean}` - if the component has search text
```vue
<slot name="no-options" v-bind="scope.noOptions">
Sorry, no matching options.
</slot>
```
+18 -8
View File
@@ -1,22 +1,32 @@
::: tip 🚧
This section of the guide is a work in progress! Check back soon for an update.
Vue Select currently offers quite a few scoped slots, and you can check out the
Vue Select currently offers quite a few scoped slots, and you can check out the
[API Docs for Slots](../api/slots.md) in the meantime while a good guide is put together.
:::
#### Scoped Slot `option`
### Scoped Slot `option`
vue-select provides the scoped `option` slot in order to create custom dropdown templates.
```html
<v-select :options="options" label="title">
<template v-slot:option="option">
<span :class="option.icon"></span>
{{ option.title }}
</template>
</v-select>
```
<template v-slot:option="option">
<span :class="option.icon"></span>
{{ option.title }}
</template>
</v-select>
```
Using the `option` slot with props `"option"` provides the current option variable to the template.
<CodePen url="NXBwYG" height="500"/>
### Improving the default `no-options` text
The `no-options` slot is displayed in the dropdown when `filteredOptions === 0`. By default, it
displays _Sorry, no matching options_. You can add more contextual information by using the slot
in your own apps.
<BetterNoOptions />
<<< @/.vuepress/components/BetterNoOptions.vue
+30 -5
View File
@@ -1,6 +1,6 @@
{
"name": "vue-select",
"version": "3.4.0",
"version": "3.6.0",
"description": "Everything you wish the HTML <select> element could do, wrapped up into a lightweight, extensible Vue component.",
"author": "Jeff Sagal <sagalbot@gmail.com>",
"homepage": "https://vue-select.org",
@@ -18,7 +18,9 @@
"build": "cross-env NODE_ENV=production webpack --config build/webpack.prod.conf.js --progress",
"build:docs": "cd docs && yarn build",
"build:preview": "cd docs && yarn build",
"test": "jest"
"test": "jest",
"semantic-release": "semantic-release",
"commit": "git-cz"
},
"repository": {
"type": "git",
@@ -35,16 +37,20 @@
"@babel/plugin-transform-runtime": "^7.4.0",
"@babel/preset-env": "^7.4.2",
"@babel/runtime": "^7.4.2",
"@vue/test-utils": "^1.0.0-beta.29",
"@semantic-release/git": "^9.0.0",
"@semantic-release/github": "^7.0.4",
"@vue/test-utils": "^1.0.0-beta.31",
"autoprefixer": "^9.4.7",
"babel-core": "^7.0.0-bridge.0",
"babel-loader": "^8.0.5",
"bundlewatch": "^0.2.5",
"chokidar": "^2.1.5",
"commitizen": "^4.0.3",
"coveralls": "^3.0.2",
"cross-env": "^5.2.0",
"css-loader": "^2.1.0",
"cssnano": "^4.1.10",
"cz-conventional-changelog": "3.1.0",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0",
"jest": "^24.1.0",
@@ -55,6 +61,7 @@
"postcss-loader": "^3.0.0",
"postcss-scss": "^2.0.0",
"sass-loader": "^7.1.0",
"semantic-release": "^17.0.4",
"terser-webpack-plugin": "^1.2.3",
"url-loader": "^1.1.2",
"vue": "^2.6.10",
@@ -99,8 +106,26 @@
"!**/node_modules/**"
],
"coverageReporters": [
"html",
"text-summary"
"text"
]
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
"bundlewatch": {
"files": [
{
"path": "./dist/vue-select.js",
"compression": "none",
"maxSize": "21 KB"
},
{
"path": "./dist/vue-select.css",
"compression": "none",
"maxSize": "6 KB"
}
]
}
}
+20
View File
@@ -0,0 +1,20 @@
module.exports = {
release: {
branch: "master"
},
plugins: [
"@semantic-release/npm",
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
["@semantic-release/github", {
"assets": ["dist/**"]
}],
[
"@semantic-release/git",
{
assets: ["package.json"],
message: "chore(🚀): ${nextRelease.version}"
}
]
]
};
+62 -33
View File
@@ -3,8 +3,21 @@
</style>
<template>
<button :tabindex="tabindex" type="button" @keydown="keypressWhileToggleIsFocused" ref="toggle" @mousedown.prevent="maybeToggleDropdown" class="v-select vs__dropdown-toggle" :dir="dir" :class="stateClasses">
<button
type="button"
ref="toggle"
:dir="dir"
role="combobox"
:id="`vs${uid}__combobox`"
:tabindex="tabindex"
:aria-expanded="dropdownOpen.toString()"
:aria-owns="`vs${uid}__listbox`"
aria-label="Search for option"
class="v-select vs__dropdown-toggle"
:class="stateClasses"
@keydown="keypressWhileToggleIsFocused"
@mousedown.prevent="maybeToggleDropdown"
>
<div class="vs__selected-options" ref="selectedOptions">
<slot v-for="option in selectedValue"
name="selected-option-container"
@@ -16,7 +29,7 @@
<slot name="selected-option" v-bind="normalizeOptionForSlot(option)">
{{ getOptionLabel(option) }}
</slot>
<button v-if="multiple" :disabled="disabled" @click="deselect(option)" type="button" class="vs__deselect" aria-label="Deselect option" ref="deselectButtons">
<button v-if="multiple" :disabled="disabled" @click="deselect(option)" type="button" class="vs__deselect" :title="`Deselect ${getOptionLabel(option)}`" :aria-label="`Deselect ${getOptionLabel(option)}`" ref="deselectButtons">
<component :is="childComponents.Deselect" />
</button>
</span>
@@ -34,7 +47,8 @@
@click="clearSelection"
type="button"
class="vs__clear"
title="Clear selection"
title="Clear Selected"
aria-label="Clear Selected"
ref="clearButton"
>
<component :is="childComponents.Deselect" />
@@ -49,23 +63,27 @@
</slot>
</div>
<transition :name="transition">
<ul ref="dropdownMenu" v-if="dropdownOpen" class="vs__dropdown-menu" role="listbox" @mousedown.prevent="onMousedown" @mouseup="onMouseUp">
<li
role="option"
v-for="(option, index) in filteredOptions"
:key="getOptionKey(option)"
class="vs__dropdown-option"
:class="{ 'vs__dropdown-option--selected': isOptionSelected(option), 'vs__dropdown-option--highlight': index === typeAheadPointer, 'vs__dropdown-option--disabled': !selectable(option) }"
@mouseover="selectable(option) ? typeAheadPointer = index : null"
@mousedown.prevent.stop="selectable(option) ? select(option) : null"
>
<slot name="option" v-bind="normalizeOptionForSlot(option)">
{{ getOptionLabel(option) }}
</slot>
</li>
<li v-if="!filteredOptions.length" class="vs__no-options" @mousedown.stop="">
<slot name="no-options">Sorry, no matching options.</slot>
</li>
<ul ref="dropdownMenu" v-show="dropdownOpen" :id="`vs${uid}__listbox`" class="vs__dropdown-menu" role="listbox" @mousedown.prevent="onMousedown" @mouseup="onMouseUp">
<template v-if="dropdownOpen">
<li
role="option"
v-for="(option, index) in filteredOptions"
:key="getOptionKey(option)"
:id="`vs${uid}__option-${index}`"
class="vs__dropdown-option"
:class="{ 'vs__dropdown-option--selected': isOptionSelected(option), 'vs__dropdown-option--highlight': index === typeAheadPointer, 'vs__dropdown-option--disabled': !selectable(option) }"
:aria-selected="index === typeAheadPointer ? true : null"
@mouseover="selectable(option) ? typeAheadPointer = index : null"
@mousedown.prevent.stop="selectable(option) ? select(option) : null"
>
<slot name="option" v-bind="normalizeOptionForSlot(option)">
{{ getOptionLabel(option) }}
</slot>
</li>
<li v-if="filteredOptions.length === 0" class="vs__no-options" @mousedown.stop="">
<slot name="no-options" v-bind="scope.noOptions">Sorry, no matching options.</slot>
</li>
</template>
</ul>
</transition>
</button>
@@ -77,6 +95,7 @@
import typeAheadPointer from '../mixins/typeAheadPointer'
import ajax from '../mixins/ajax'
import childComponents from './childComponents';
import uniqueId from '../utility/uniqueId';
/**
* @name VueSelect
@@ -423,6 +442,17 @@
validator: (value) => ['function', 'boolean'].includes(typeof value)
},
/**
* If search text should clear on blur
* @return {Boolean} True when single and clearSearchOnSelect
*/
clearSearchOnBlur: {
type: Function,
default: function ({ clearSearchOnSelect, multiple }) {
return clearSearchOnSelect && !multiple
}
},
/**
* Disable the dropdown entirely.
* @type {Boolean}
@@ -503,6 +533,7 @@
data() {
return {
uid: uniqueId(),
search: '',
open: false,
isComposing: false,
@@ -829,7 +860,8 @@
if (this.mousedown && !this.searching) {
this.mousedown = false
} else {
if (this.clearSearchOnBlur) {
const { clearSearchOnSelect, multiple } = this;
if (this.clearSearchOnBlur({ clearSearchOnSelect, multiple })) {
this.search = ''
}
this.closeSearchOptions();
@@ -1002,10 +1034,11 @@
'tabindex': '-1',
'readonly': !this.searchable,
'id': this.inputId,
'aria-expanded': this.dropdownOpen,
'aria-label': 'Search for option',
'aria-autocomplete': 'list',
'aria-labelledby': `vs${this.uid}__combobox`,
'aria-controls': `vs${this.uid}__listbox`,
'aria-activedescendant': this.typeAheadPointer > -1 ? `vs${this.uid}__option-${this.typeAheadPointer}` : '',
'ref': 'search',
'role': 'combobox',
'type': 'search',
'autocomplete': this.autocomplete,
'value': this.search,
@@ -1022,6 +1055,10 @@
spinner: {
loading: this.mutableLoading
},
noOptions: {
search: this.search,
searching: this.searching,
},
openIndicator: {
attributes: {
'ref': 'openIndicator',
@@ -1062,14 +1099,6 @@
}
},
/**
* If search text should clear on blur
* @return {Boolean} True when single and clearSearchOnSelect
*/
clearSearchOnBlur() {
return this.clearSearchOnSelect && !this.multiple
},
/**
* Return the current state of the
* search input
+1 -1
View File
@@ -30,7 +30,7 @@ export default {
*/
pixelsToPointerTop() {
let pixelsToPointerTop = 0;
if (this.$refs.dropdownMenu) {
if (this.$refs.dropdownMenu && this.dropdownOpen) {
for (let i = 0; i < this.typeAheadPointer; i++) {
pixelsToPointerTop += this.$refs.dropdownMenu.children[i]
.offsetHeight;
+1
View File
@@ -23,6 +23,7 @@ $transition-duration: .15s;
/* Dropdown Default Transition */
.vs__fade-enter-active,
.vs__fade-leave-active {
pointer-events: none;
transition: opacity $transition-duration $transition-timing-function;
}
.vs__fade-enter,
+12
View File
@@ -0,0 +1,12 @@
let idCount = 0;
/**
* Dead simple unique ID implementation.
* Thanks lodash!
* @return {number}
*/
function uniqueId() {
return ++idCount;
}
export default uniqueId;
+10 -4
View File
@@ -13,10 +13,11 @@ describe("Asynchronous Loading", () => {
expect(Select.vm.mutableLoading).toEqual(true);
});
it("should trigger the search event when the search text changes", () => {
it("should trigger the search event when the search text changes", async () => {
const Select = selectWithProps();
Select.vm.search = "foo";
await Select.vm.$nextTick();
const events = Select.emitted("search");
@@ -24,11 +25,13 @@ describe("Asynchronous Loading", () => {
expect(events.length).toEqual(1);
});
it("should trigger the search event if the search text is empty", () => {
it("should trigger the search event if the search text is empty", async () => {
const Select = selectWithProps();
Select.vm.search = "foo";
await Select.vm.$nextTick();
Select.vm.search = "";
await Select.vm.$nextTick();
const events = Select.emitted("search");
@@ -36,7 +39,7 @@ describe("Asynchronous Loading", () => {
expect(events.length).toEqual(2);
});
it("can set loading to false from the @search event callback", () => {
it("can set loading to false from the @search event callback", async () => {
const Select = shallowMount(vSelect, {
listeners: {
search: (search, loading) => {
@@ -47,13 +50,16 @@ describe("Asynchronous Loading", () => {
Select.vm.mutableLoading = true;
Select.vm.search = 'foo';
await Select.vm.$nextTick();
expect(Select.vm.mutableLoading).toEqual(false);
});
it("will sync mutable loading with the loading prop", () => {
it('will sync mutable loading with the loading prop', async () => {
const Select = selectWithProps({ loading: false });
Select.setProps({ loading: true });
await Select.vm.$nextTick();
expect(Select.vm.mutableLoading).toEqual(true);
});
});
+2 -1
View File
@@ -1,9 +1,10 @@
import { selectWithProps } from "../helpers";
describe("Removing values", () => {
it("can remove the given tag when its close icon is clicked", () => {
it("can remove the given tag when its close icon is clicked", async () => {
const Select = selectWithProps({ multiple: true });
Select.vm.$data._value = 'one';
await Select.vm.$nextTick();
Select.find(".vs__deselect").trigger("click");
expect(Select.emitted().input).toEqual([[[]]]);
+7 -3
View File
@@ -2,7 +2,7 @@ import { mountDefault, selectWithProps } from '../helpers';
import OpenIndicator from "../../src/components/OpenIndicator";
describe("Toggling Dropdown", () => {
fdescribe('focusing on the button wrapper', () => {
describe('focusing on the button wrapper', () => {
test('when the search is focused buttonIsFocused is false', () => {
const Select = mountDefault();
Select.vm.$refs.search.focus();
@@ -144,14 +144,18 @@ describe("Toggling Dropdown", () => {
expect(Select.vm.stateClasses['vs--open']).toEqual(true);
});
it("should not display the dropdown if noDrop is true", () => {
it("should not display the dropdown if noDrop is true", async () => {
const Select = selectWithProps({
noDrop: true,
});
Select.vm.maybeToggleDropdown({ target: Select.vm.$refs.search });
expect(Select.vm.open).toEqual(true);
expect(Select.contains('.vs__dropdown-menu')).toBeFalsy();
await Select.vm.$nextTick();
expect(Select.find('.vs__dropdown-menu').element.style['display']).toEqual('none');
expect(Select.contains('.vs__dropdown-option')).toBeFalsy();
expect(Select.contains('.vs__no-options')).toBeFalsy();
expect(Select.vm.stateClasses['vs--open']).toBeFalsy();
});
+3 -1
View File
@@ -12,13 +12,15 @@ describe("Labels", () => {
expect(Select.find(".vs__selected").text()).toBe("Foo");
});
it("will console.warn when options contain objects without a valid label key", () => {
it("will console.warn when options contain objects without a valid label key", async () => {
const spy = jest.spyOn(console, "warn").mockImplementation(() => {});
const Select = selectWithProps({
options: [{}]
});
Select.vm.open = true;
await Select.vm.$nextTick();
expect(spy).toHaveBeenCalledWith(
'[vue-select warn]: Label key "option.label" does not exist in options object {}.' +
"\nhttps://vue-select.org/api/props.html#getoptionlabel"
+42 -4
View File
@@ -31,20 +31,21 @@ describe("Reset on options change", () => {
expect(spy.mock.calls[3][0]).toContain('Invalid prop: custom validator check failed for prop "resetOnOptionsChange"')
});
it('should receive the new options, old options, and current value', () => {
it('should receive the new options, old options, and current value', async () => {
let resetOnOptionsChange = jest.fn(option => option);
const Select = mountDefault(
{resetOnOptionsChange, options: ['bear'], value: 'selected'},
);
Select.setProps({options: ['lake', 'kite']});
await Select.vm.$nextTick();
expect(resetOnOptionsChange).toHaveBeenCalledTimes(1);
expect(resetOnOptionsChange)
.toHaveBeenCalledWith(['lake', 'kite'], ['bear'], ['selected']);
});
it('should allow resetOnOptionsChange to be a function that returns true', () => {
it('should allow resetOnOptionsChange to be a function that returns true', async () => {
let resetOnOptionsChange = () => true;
const Select = shallowMount(VueSelect, {
propsData: {resetOnOptionsChange, options: ['one'], value: 'one'},
@@ -52,6 +53,8 @@ describe("Reset on options change", () => {
const spy = jest.spyOn(Select.vm, 'clearSelection');
Select.setProps({options: ['one', 'two']});
await Select.vm.$nextTick();
expect(spy).toHaveBeenCalledTimes(1);
});
@@ -74,14 +77,18 @@ describe("Reset on options change", () => {
const spy = jest.spyOn(Select.vm, 'clearSelection');
Select.setProps({options: ['one', 'two']});
await Select.vm.$nextTick();
expect(Select.vm.selectedValue).toEqual(['one']);
Select.setProps({options: ['two']});
await Select.vm.$nextTick();
expect(spy).toHaveBeenCalledTimes(1);
});
});
it("should reset the selected value when the options property changes", () => {
it("should reset the selected value when the options property changes", async () => {
const Select = shallowMount(VueSelect, {
propsData: { resetOnOptionsChange: true, options: ["one"] }
});
@@ -89,15 +96,46 @@ describe("Reset on options change", () => {
Select.vm.$data._value = 'one';
Select.setProps({options: ["four", "five", "six"]});
await Select.vm.$nextTick();
expect(Select.vm.selectedValue).toEqual([]);
});
it("should return correct selected value when the options property changes and a new option matches", () => {
it("should return correct selected value when the options property changes and a new option matches", async () => {
const Select = shallowMount(VueSelect, {
propsData: { value: "one", options: [], reduce(option) { return option.value } }
});
Select.setProps({options: [{ label: "oneLabel", value: "one" }]});
await Select.vm.$nextTick();
expect(Select.vm.selectedValue).toEqual([{ label: "oneLabel", value: "one" }]);
});
it('clearSearchOnBlur returns false when multiple is true', () => {
const Select = mountDefault({});
let clearSearchOnBlur = jest.spyOn(Select.vm, 'clearSearchOnBlur');
Select.find({ref: 'search'}).trigger('click');
Select.setData({search: 'one'});
Select.find({ref: 'search'}).trigger('blur');
expect(clearSearchOnBlur).toHaveBeenCalledTimes(1);
expect(clearSearchOnBlur).toHaveBeenCalledWith({
clearSearchOnSelect: true,
multiple: false,
});
expect(Select.vm.search).toBe('');
});
it('clearSearchOnBlur accepts a function', () => {
let clearSearchOnBlur = jest.fn(() => false);
const Select = mountDefault({clearSearchOnBlur});
Select.find({ref: 'search'}).trigger('click');
Select.setData({search: 'one'});
Select.find({ref: 'search'}).trigger('blur');
expect(clearSearchOnBlur).toHaveBeenCalledTimes(1);
expect(Select.vm.search).toBe('one');
});
});
+2 -1
View File
@@ -211,7 +211,7 @@ describe("When reduce prop is defined", () => {
});
it("reacts correctly when value property changes", () => {
it("reacts correctly when value property changes", async () => {
const optionToChangeTo = { id: 1, label: "Foo" };
const Select = shallowMount(VueSelect, {
propsData: {
@@ -222,6 +222,7 @@ describe("When reduce prop is defined", () => {
});
Select.setProps({ value: optionToChangeTo.id });
await Select.vm.$nextTick();
expect(Select.vm.selectedValue).toEqual([optionToChangeTo]);
});
+8 -2
View File
@@ -1,27 +1,33 @@
import { selectWithProps } from "../helpers";
describe("Selectable prop", () => {
it("should select selectable option if clicked", () => {
it("should select selectable option if clicked", async () => {
const Select = selectWithProps({
options: ["one", "two", "three"],
selectable: (option) => option === "one"
});
Select.vm.$data.open = true;
await Select.vm.$nextTick();
Select.find(".vs__dropdown-menu li:first-child").trigger("mousedown");
await Select.vm.$nextTick();
expect(Select.vm.selectedValue).toEqual(["one"]);
})
it("should not select not selectable option if clicked", () => {
it("should not select not selectable option if clicked", async () => {
const Select = selectWithProps({
options: ["one", "two", "three"],
selectable: (option) => option === "one"
});
Select.vm.$data.open = true;
await Select.vm.$nextTick();
Select.find(".vs__dropdown-menu li:last-child").trigger("mousedown");
await Select.vm.$nextTick();
expect(Select.vm.selectedValue).toEqual([]);
});
+18 -1
View File
@@ -42,7 +42,7 @@ describe('Scoped Slots', () => {
});
it('receives an option object to the option slot in the dropdown menu',
() => {
async () => {
const Select = mountDefault(
{value: 'one'},
{
@@ -52,7 +52,24 @@ describe('Scoped Slots', () => {
});
Select.vm.open = true;
await Select.vm.$nextTick();
expect(Select.find({ref: 'dropdownMenu'}).text()).toEqual('onetwothree');
});
it('noOptions slot receives the current search text', async () => {
const noOptions = jest.fn();
const Select = mountDefault({}, {
scopedSlots: {'no-options': noOptions},
});
Select.vm.search = 'something not there';
Select.vm.open = true;
await Select.vm.$nextTick();
expect(noOptions).toHaveBeenCalledWith({
search: 'something not there',
searching: true,
})
});
});
+4 -2
View File
@@ -153,7 +153,7 @@ describe("When Tagging Is Enabled", () => {
expect(Select.vm.selectedValue).toEqual([two]);
});
it("should select an existing option if the search string matches an objects label from options", () => {
it("should select an existing option if the search string matches an objects label from options", async () => {
let two = { label: "two" };
const Select = selectWithProps({
taggable: true,
@@ -161,12 +161,13 @@ describe("When Tagging Is Enabled", () => {
});
Select.vm.search = "two";
await Select.vm.$nextTick();
searchSubmit(Select);
expect(Select.vm.selectedValue).toEqual([two]);
});
it("should select an existing option if the search string matches an objects label from options when filter-options is false", () => {
it("should select an existing option if the search string matches an objects label from options when filter-options is false", async () => {
let two = { label: "two" };
const Select = selectWithProps({
taggable: true,
@@ -175,6 +176,7 @@ describe("When Tagging Is Enabled", () => {
});
Select.vm.search = "two";
await Select.vm.$nextTick();
searchSubmit(Select);
expect(Select.vm.selectedValue).toEqual([two]);
+2 -1
View File
@@ -120,11 +120,12 @@ describe("Moving the Typeahead Pointer", () => {
});
describe("Measuring pixel distances", () => {
it("should calculate pointerHeight as the offsetHeight of the pointer element if it exists", () => {
it("should calculate pointerHeight as the offsetHeight of the pointer element if it exists", async () => {
const Select = mountDefault();
// Drop down must be open for $refs to exist
Select.vm.open = true;
await Select.vm.$nextTick();
/**
* Since JSDom doesn't render layouts, set the offsetHeight explicitly
+5
View File
@@ -0,0 +1,5 @@
import uniqueId from '../../../src/utility/uniqueId';
test('it generates a unique number', () => {
expect(uniqueId()).not.toEqual(uniqueId());
});
+4332 -1175
View File
File diff suppressed because it is too large Load Diff