2
0
mirror of https://github.com/tenrok/vue-select.git synced 2026-06-22 10:30:34 +03:00

switch to vue cli for build

This commit is contained in:
Jeff
2020-12-23 20:21:02 -08:00
parent 1484366039
commit f80559ab89
50 changed files with 1975 additions and 1586 deletions
+3
View File
@@ -0,0 +1,3 @@
> 1%
last 2 versions
not dead
+25
View File
@@ -0,0 +1,25 @@
module.exports = {
root: true,
env: {
node: true
},
extends: ["plugin:vue/vue3-essential", "eslint:recommended", "@vue/prettier"],
parserOptions: {
parser: "babel-eslint"
},
rules: {
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off"
},
overrides: [
{
files: [
"**/__tests__/*.{j,t}s?(x)",
"**/tests/unit/**/*.spec.{j,t}s?(x)"
],
env: {
jest: true
}
}
]
};
+7 -11
View File
@@ -1,8 +1,12 @@
.DS_Store
node_modules
/dist
/tests/e2e/videos/
/tests/e2e/screenshots/
# local env files
.env
.env.local
.env.*.local
@@ -10,6 +14,7 @@ node_modules
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
@@ -18,13 +23,4 @@ yarn-error.log*
*.ntvs*
*.njsproj
*.sln
*.sw*
# Project specific
coverage
dist
test/unit/coverage
package-lock.json
dev/dist
docs/.vuepress/dist
.netlify
*.sw?
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"]
};
+3
View File
@@ -0,0 +1,3 @@
{
"pluginsFile": "tests/e2e/plugins/index.js"
}
+1
View File
@@ -0,0 +1 @@
GITHUB_TOKEN=cc0a5db007fd27e736b9c2b23d2b626da44b4bbc
+56 -30
View File
@@ -1,39 +1,49 @@
{
"name": "vue-select",
"version": "3.11.2",
"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",
"directories": {
"doc": "docs",
"test": "tests"
},
"private": false,
"main": "dist/vue-select.js",
"license": "MIT",
"prepare": "npm run build",
"description": "Everything you wish the HTML <select> element could do, wrapped up into a lightweight, accessible and composable Vue component.",
"author": {
"name": "Jeff Sagal",
"email": "sagalbot@gmail.com"
},
"scripts": {
"serve": "webpack-dev-server --config build/webpack.dev.conf.js --hot --progress -d",
"serve:docs": "cd docs && yarn serve",
"build": "cross-env NODE_ENV=production webpack --config build/webpack.prod.conf.js --progress",
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"test:unit": "vue-cli-service test:unit",
"test:e2e": "vue-cli-service test:e2e",
"lint": "vue-cli-service lint",
"build:docs": "cd docs && yarn build",
"build:preview": "cd docs && yarn build",
"test": "jest",
"commit": "git-cz",
"semantic-release": "semantic-release",
"commit": "git-cz"
"serve:docs": "cd docs && yarn serve",
"test": "jest"
},
"repository": {
"type": "git",
"url": "https://github.com/sagalbot/vue-select.git"
},
"peerDependencies": {
"vue": "2.x"
},
"resolutions": {
"ajv": "6.8.1"
"main": "dist/vue-select.js",
"dependencies": {
"core-js": "^3.6.5",
"vue": "^3.0.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-e2e-cypress": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-unit-jest": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/compiler-sfc": "^3.0.0",
"@vue/eslint-config-prettier": "^6.0.0",
"@vue/test-utils": "^2.0.0-0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-prettier": "^3.1.3",
"eslint-plugin-vue": "^7.0.0-0",
"prettier": "^1.19.1",
"typescript": "~3.9.3",
"vue-jest": "^5.0.0-0"
},
"peerDependencies": {
"vue": "3.x"
},
"jest": {
"moduleFileExtensions": [
@@ -66,12 +76,11 @@
],
"coverageReporters": [
"text"
]
],
"preset": "@vue/cli-plugin-unit-jest"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
"bugs": {
"url": "https://github.com/sagalbot/vue-select/issues"
},
"bundlewatch": {
"files": [
@@ -86,5 +95,22 @@
"maxSize": "6 KB"
}
]
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
"directories": {
"doc": "docs",
"test": "tests"
},
"homepage": "https://vue-select.org",
"license": "MIT",
"prepare": "npm run build",
"readme": "ERROR: No README data found!",
"repository": {
"type": "git",
"url": "git+https://github.com/sagalbot/vue-select.git"
}
}
+5 -5
View File
@@ -1,8 +1,8 @@
module.exports = {
plugins: [
require('autoprefixer'),
require('cssnano')({
preset: 'default',
}),
],
require("autoprefixer"),
require("cssnano")({
preset: "default"
})
]
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

+17
View File
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
+26
View File
@@ -0,0 +1,26 @@
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<HelloWorld msg="Welcome to Your Vue.js App" />
</template>
<script>
import HelloWorld from "./components/HelloWorld.vue";
export default {
name: "App",
components: {
HelloWorld
}
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

+3 -1
View File
@@ -1,5 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10">
<path d="M6.895455 5l2.842897-2.842898c.348864-.348863.348864-.914488 0-1.263636L9.106534.261648c-.348864-.348864-.914489-.348864-1.263636 0L5 3.104545 2.157102.261648c-.348863-.348864-.914488-.348864-1.263636 0L.261648.893466c-.348864.348864-.348864.914489 0 1.263636L3.104545 5 .261648 7.842898c-.348864.348863-.348864.914488 0 1.263636l.631818.631818c.348864.348864.914773.348864 1.263636 0L5 6.895455l2.842898 2.842897c.348863.348864.914772.348864 1.263636 0l.631818-.631818c.348864-.348864.348864-.914489 0-1.263636L6.895455 5z"/>
<path
d="M6.895455 5l2.842897-2.842898c.348864-.348863.348864-.914488 0-1.263636L9.106534.261648c-.348864-.348864-.914489-.348864-1.263636 0L5 3.104545 2.157102.261648c-.348863-.348864-.914488-.348864-1.263636 0L.261648.893466c-.348864.348864-.348864.914489 0 1.263636L3.104545 5 .261648 7.842898c-.348864.348863-.348864.914488 0 1.263636l.631818.631818c.348864.348864.914773.348864 1.263636 0L5 6.895455l2.842898 2.842897c.348863.348864.914772.348864 1.263636 0l.631818-.631818c.348864-.348864.348864-.914489 0-1.263636L6.895455 5z"
/>
</svg>
</template>
+130
View File
@@ -0,0 +1,130 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br />
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener"
>vue-cli documentation</a
>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li>
<a
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel"
target="_blank"
rel="noopener"
>babel</a
>
</li>
<li>
<a
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint"
target="_blank"
rel="noopener"
>eslint</a
>
</li>
<li>
<a
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-unit-jest"
target="_blank"
rel="noopener"
>unit-jest</a
>
</li>
<li>
<a
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-e2e-cypress"
target="_blank"
rel="noopener"
>e2e-cypress</a
>
</li>
</ul>
<h3>Essential Links</h3>
<ul>
<li>
<a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a>
</li>
<li>
<a href="https://forum.vuejs.org" target="_blank" rel="noopener"
>Forum</a
>
</li>
<li>
<a href="https://chat.vuejs.org" target="_blank" rel="noopener"
>Community Chat</a
>
</li>
<li>
<a href="https://twitter.com/vuejs" target="_blank" rel="noopener"
>Twitter</a
>
</li>
<li>
<a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a>
</li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li>
<a href="https://router.vuejs.org" target="_blank" rel="noopener"
>vue-router</a
>
</li>
<li>
<a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a>
</li>
<li>
<a
href="https://github.com/vuejs/vue-devtools#vue-devtools"
target="_blank"
rel="noopener"
>vue-devtools</a
>
</li>
<li>
<a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener"
>vue-loader</a
>
</li>
<li>
<a
href="https://github.com/vuejs/awesome-vue"
target="_blank"
rel="noopener"
>awesome-vue</a
>
</li>
</ul>
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
msg: String
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
+3 -1
View File
@@ -1,5 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="10">
<path d="M9.211364 7.59931l4.48338-4.867229c.407008-.441854.407008-1.158247 0-1.60046l-.73712-.80023c-.407008-.441854-1.066904-.441854-1.474243 0L7 5.198617 2.51662.33139c-.407008-.441853-1.066904-.441853-1.474243 0l-.737121.80023c-.407008.441854-.407008 1.158248 0 1.600461l4.48338 4.867228L7 10l2.211364-2.40069z"/>
<path
d="M9.211364 7.59931l4.48338-4.867229c.407008-.441854.407008-1.158247 0-1.60046l-.73712-.80023c-.407008-.441854-1.066904-.441854-1.474243 0L7 5.198617 2.51662.33139c-.407008-.441853-1.066904-.441853-1.474243 0l-.737121.80023c-.407008.441854-.407008 1.158248 0 1.600461l4.48338 4.867228L7 10l2.211364-2.40069z"
/>
</svg>
</template>
+204 -246
View File
@@ -1,104 +1,18 @@
<style lang="scss">
@import '../scss/vue-select.scss';
</style>
<template>
<div :dir="dir" class="v-select" :class="stateClasses">
<slot name="header" v-bind="scope.header" />
<div :id="`vs${uid}__combobox`" ref="toggle" @mousedown="toggleDropdown($event)" class="vs__dropdown-toggle" role="combobox" :aria-expanded="dropdownOpen.toString()" :aria-owns="`vs${uid}__listbox`" aria-label="Search for option">
<div class="vs__selected-options" ref="selectedOptions">
<slot v-for="option in selectedValue"
name="selected-option-container"
:option="normalizeOptionForSlot(option)"
:deselect="deselect"
:multiple="multiple"
:disabled="disabled">
<span :key="getOptionKey(option)" class="vs__selected">
<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" :title="`Deselect ${getOptionLabel(option)}`" :aria-label="`Deselect ${getOptionLabel(option)}`" ref="deselectButtons">
<component :is="childComponents.Deselect" />
</button>
</span>
</slot>
<slot name="search" v-bind="scope.search">
<input class="vs__search" v-bind="scope.search.attributes" v-on="scope.search.events">
</slot>
</div>
<div class="vs__actions" ref="actions">
<button
v-show="showClearButton"
:disabled="disabled"
@click="clearSelection"
type="button"
class="vs__clear"
title="Clear Selected"
aria-label="Clear Selected"
ref="clearButton"
>
<component :is="childComponents.Deselect" />
</button>
<slot name="open-indicator" v-bind="scope.openIndicator">
<component :is="childComponents.OpenIndicator" v-if="!noDrop" v-bind="scope.openIndicator.attributes"/>
</slot>
<slot name="spinner" v-bind="scope.spinner">
<div class="vs__spinner" v-show="mutableLoading">Loading...</div>
</slot>
</div>
</div>
<transition :name="transition">
<ul ref="dropdownMenu" v-if="dropdownOpen" :id="`vs${uid}__listbox`" :key="`vs${uid}__listbox`" class="vs__dropdown-menu" role="listbox" @mousedown.prevent="onMousedown" @mouseup="onMouseUp" tabindex="-1" v-append-to-body>
<slot name="list-header" v-bind="scope.listHeader" />
<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">
<slot name="no-options" v-bind="scope.noOptions">Sorry, no matching options.</slot>
</li>
<slot name="list-footer" v-bind="scope.listFooter" />
</ul>
<ul v-else :id="`vs${uid}__listbox`" role="listbox" style="display: none; visibility: hidden;"></ul>
</transition>
<slot name="footer" v-bind="scope.footer" />
</div>
</template>
<script type="text/babel">
import pointerScroll from '../mixins/pointerScroll'
import typeAheadPointer from '../mixins/typeAheadPointer'
import ajax from '../mixins/ajax'
import childComponents from './childComponents';
import appendToBody from '../directives/appendToBody';
import sortAndStringify from '../utility/sortAndStringify'
import uniqueId from '../utility/uniqueId';
<script>
/**
/**
* @name VueSelect
*/
export default {
components: {...childComponents},
export default {
components: { ...childComponents },
mixins: [pointerScroll, typeAheadPointer, ajax],
directives: {appendToBody},
directives: { appendToBody },
props: {
/**
@@ -118,7 +32,7 @@
*/
components: {
type: Object,
default: () => ({}),
default: () => ({})
},
/**
@@ -131,8 +45,8 @@
options: {
type: Array,
default() {
return []
},
return [];
}
},
/**
@@ -177,7 +91,7 @@
*/
placeholder: {
type: String,
default: ''
default: ""
},
/**
@@ -186,7 +100,7 @@
*/
transition: {
type: String,
default: 'vs__fade'
default: "vs__fade"
},
/**
@@ -215,7 +129,7 @@
*/
label: {
type: String,
default: 'label'
default: "label"
},
/**
@@ -225,7 +139,7 @@
*/
autocomplete: {
type: String,
default: 'off'
default: "off"
},
/**
@@ -237,7 +151,7 @@
*/
reduce: {
type: Function,
default: option => option,
default: option => option
},
/**
@@ -251,7 +165,7 @@
*/
selectable: {
type: Function,
default: option => true,
default: option => true
},
/**
@@ -270,15 +184,15 @@
getOptionLabel: {
type: Function,
default(option) {
if (typeof option === 'object') {
if (typeof option === "object") {
if (!option.hasOwnProperty(this.label)) {
return console.warn(
`[vue-select warn]: Label key "option.${this.label}" does not` +
` exist in options object ${JSON.stringify(option)}.\n` +
'https://vue-select.org/api/props.html#getoptionlabel'
)
"https://vue-select.org/api/props.html#getoptionlabel"
);
}
return option[this.label]
return option[this.label];
}
return option;
}
@@ -302,21 +216,24 @@
*/
getOptionKey: {
type: Function,
default (option) {
if (typeof option !== 'object') {
default(option) {
if (typeof option !== "object") {
return option;
}
try {
return option.hasOwnProperty('id') ? option.id : sortAndStringify(option);
return option.hasOwnProperty("id")
? option.id
: sortAndStringify(option);
} catch (e) {
const warning = `[vue-select warn]: Could not stringify this option ` +
const warning =
`[vue-select warn]: Could not stringify this option ` +
`to generate unique key. Please provide'getOptionKey' prop ` +
`to return a unique key for each option.\n` +
'https://vue-select.org/api/props.html#getoptionkey';
"https://vue-select.org/api/props.html#getoptionkey";
return console.warn(warning, option, e);
}
},
}
},
/**
@@ -325,11 +242,11 @@
*/
onTab: {
type: Function,
default: function () {
default: function() {
if (this.selectOnTab && !this.isComposing) {
this.typeAheadSelect();
}
},
}
},
/**
@@ -384,7 +301,7 @@
filterBy: {
type: Function,
default(option, label, search) {
return (label || '').toLowerCase().indexOf(search.toLowerCase()) > -1
return (label || "").toLowerCase().indexOf(search.toLowerCase()) > -1;
}
},
@@ -402,12 +319,12 @@
filter: {
type: Function,
default(options, search) {
return options.filter((option) => {
let label = this.getOptionLabel(option)
if (typeof label === 'number') {
label = label.toString()
return options.filter(option => {
let label = this.getOptionLabel(option);
if (typeof label === "number") {
label = label.toString();
}
return this.filterBy(option, label, search)
return this.filterBy(option, label, search);
});
}
},
@@ -418,9 +335,11 @@
*/
createOption: {
type: Function,
default (option) {
return (typeof this.optionList[0] === 'object') ? {[this.label]: option} : option;
},
default(option) {
return typeof this.optionList[0] === "object"
? { [this.label]: option }
: option;
}
},
/**
@@ -437,7 +356,7 @@
*/
resetOnOptionsChange: {
default: false,
validator: (value) => ['function', 'boolean'].includes(typeof value)
validator: value => ["function", "boolean"].includes(typeof value)
},
/**
@@ -446,8 +365,8 @@
*/
clearSearchOnBlur: {
type: Function,
default: function ({ clearSearchOnSelect, multiple }) {
return clearSearchOnSelect && !multiple
default: function({ clearSearchOnSelect, multiple }) {
return clearSearchOnSelect && !multiple;
}
},
@@ -477,7 +396,7 @@
*/
dir: {
type: String,
default: 'auto'
default: "auto"
},
/**
@@ -496,7 +415,7 @@
*/
selectOnKeyCodes: {
type: Array,
default: () => [13],
default: () => [13]
},
/**
@@ -510,7 +429,7 @@
*/
searchInputQuerySelector: {
type: String,
default: '[type=search]'
default: "[type=search]"
},
/**
@@ -525,7 +444,7 @@
* @param vm {VueSelect}
* @return {Object}
*/
default: (map, vm) => map,
default: (map, vm) => map
},
/**
@@ -560,7 +479,7 @@
* @param left {string} absolute position left value in pixels relative to the document
* @return {function|void}
*/
default(dropdownList, component, {width, top, left}) {
default(dropdownList, component, { width, top, left }) {
dropdownList.style.top = top;
dropdownList.style.left = left;
dropdownList.style.width = width;
@@ -571,12 +490,12 @@
data() {
return {
uid: uniqueId(),
search: '',
search: "",
open: false,
isComposing: false,
pushedTags: [],
_value: [] // Internal value managed by Vue Select if no `value` prop is passed
}
};
},
watch: {
@@ -587,9 +506,14 @@
* is correct.
* @return {[type]} [description]
*/
options (newOptions, oldOptions) {
let shouldReset = () => typeof this.resetOnOptionsChange === 'function'
? this.resetOnOptionsChange(newOptions, oldOptions, this.selectedValue)
options(newOptions, oldOptions) {
let shouldReset = () =>
typeof this.resetOnOptionsChange === "function"
? this.resetOnOptionsChange(
newOptions,
oldOptions,
this.selectedValue
)
: this.resetOnOptionsChange;
if (!this.taggable && shouldReset()) {
@@ -607,7 +531,7 @@
*/
value(val) {
if (this.isTrackingValues) {
this.setInternalValueFromOptions(val)
this.setInternalValueFromOptions(val);
}
},
@@ -618,11 +542,11 @@
* @return {void}
*/
multiple() {
this.clearSelection()
this.clearSelection();
},
open(isOpen) {
this.$emit(isOpen ? 'open' : 'close');
this.$emit(isOpen ? "open" : "close");
}
},
@@ -630,10 +554,10 @@
this.mutableLoading = this.loading;
if (typeof this.value !== "undefined" && this.isTrackingValues) {
this.setInternalValueFromOptions(this.value)
this.setInternalValueFromOptions(this.value);
}
this.$on('option:created', this.pushTag)
this.$on("option:created", this.pushTag);
},
methods: {
@@ -645,7 +569,9 @@
*/
setInternalValueFromOptions(value) {
if (Array.isArray(value)) {
this.$data._value = value.map(val => this.findOptionFromReducedValue(val));
this.$data._value = value.map(val =>
this.findOptionFromReducedValue(val)
);
} else {
this.$data._value = this.findOptionFromReducedValue(value);
}
@@ -657,18 +583,18 @@
* @return {void}
*/
select(option) {
this.$emit('option:selecting', option);
this.$emit("option:selecting", option);
if (!this.isOptionSelected(option)) {
if (this.taggable && !this.optionExists(option)) {
this.$emit('option:created', option);
this.$emit("option:created", option);
}
if (this.multiple) {
option = this.selectedValue.concat(option)
option = this.selectedValue.concat(option);
}
this.updateValue(option);
this.$emit('option:selected', option);
this.$emit("option:selected", option);
}
this.onAfterSelect(option)
this.onAfterSelect(option);
},
/**
@@ -676,12 +602,14 @@
* @param {Object|String} option
* @return {void}
*/
deselect (option) {
this.$emit('option:deselecting', option);
this.updateValue(this.selectedValue.filter(val => {
deselect(option) {
this.$emit("option:deselecting", option);
this.updateValue(
this.selectedValue.filter(val => {
return !this.optionComparator(val, option);
}));
this.$emit('option:deselected', option);
})
);
this.$emit("option:deselected", option);
},
/**
@@ -689,7 +617,7 @@
* @return {void}
*/
clearSelection() {
this.updateValue(this.multiple ? [] : null)
this.updateValue(this.multiple ? [] : null);
},
/**
@@ -700,11 +628,11 @@
onAfterSelect(option) {
if (this.closeOnSelect) {
this.open = !this.open;
this.searchEl.blur()
this.searchEl.blur();
}
if (this.clearSearchOnSelect) {
this.search = ''
this.search = "";
}
},
@@ -716,8 +644,8 @@
* @emits input
* @param value
*/
updateValue (value) {
if (typeof this.value === 'undefined') {
updateValue(value) {
if (typeof this.value === "undefined") {
// Vue select has to manage value
this.$data._value = value;
}
@@ -730,7 +658,7 @@
}
}
this.$emit('input', value);
this.$emit("input", value);
},
/**
@@ -738,7 +666,7 @@
* @param {Event} event
* @return {void}
*/
toggleDropdown (event) {
toggleDropdown(event) {
const targetIsNotSearch = event.target !== this.searchEl;
if (targetIsNotSearch) {
event.preventDefault();
@@ -747,11 +675,16 @@
// don't react to click on deselect/clear buttons,
// they dropdown state will be set in their click handlers
const ignoredButtons = [
...(this.$refs['deselectButtons'] || []),
...([this.$refs['clearButton']] || []),
...(this.$refs["deselectButtons"] || []),
...([this.$refs["clearButton"]] || [])
];
if (this.searchEl === undefined || ignoredButtons.filter(Boolean).some(ref => ref.contains(event.target) || ref === event.target)) {
if (
this.searchEl === undefined ||
ignoredButtons
.filter(Boolean)
.some(ref => ref.contains(event.target) || ref === event.target)
) {
event.preventDefault();
return;
}
@@ -770,7 +703,9 @@
* @return {Boolean} True when selected | False otherwise
*/
isOptionSelected(option) {
return this.selectedValue.some(value => this.optionComparator(value, option))
return this.selectedValue.some(value =>
this.optionComparator(value, option)
);
},
/**
@@ -792,13 +727,11 @@
* @param value {Object}
* @returns {*}
*/
findOptionFromReducedValue (value) {
const predicate = option => JSON.stringify(this.reduce(option)) === JSON.stringify(value);
findOptionFromReducedValue(value) {
const predicate = option =>
JSON.stringify(this.reduce(option)) === JSON.stringify(value);
const matches = [
...this.options,
...this.pushedTags,
].filter(predicate);
const matches = [...this.options, ...this.pushedTags].filter(predicate);
if (matches.length === 1) {
return matches[0];
@@ -810,7 +743,11 @@
* unique reduced value.
* @see https://github.com/sagalbot/vue-select/issues/1089#issuecomment-597238735
*/
return matches.find(match => this.optionComparator(match, this.$data._value)) || value;
return (
matches.find(match =>
this.optionComparator(match, this.$data._value)
) || value
);
},
/**
@@ -818,9 +755,9 @@
* @emits {search:blur}
* @returns {void}
*/
closeSearchOptions(){
this.open = false
this.$emit('search:blur')
closeSearchOptions() {
this.open = false;
this.$emit("search:blur");
},
/**
@@ -829,12 +766,19 @@
* @return {this.value}
*/
maybeDeleteValue() {
if (!this.searchEl.value.length && this.selectedValue && this.selectedValue.length && this.clearable) {
if (
!this.searchEl.value.length &&
this.selectedValue &&
this.selectedValue.length &&
this.clearable
) {
let value = null;
if (this.multiple) {
value = [...this.selectedValue.slice(0, this.selectedValue.length - 1)]
value = [
...this.selectedValue.slice(0, this.selectedValue.length - 1)
];
}
this.updateValue(value)
this.updateValue(value);
}
},
@@ -846,7 +790,9 @@
* @return {boolean}
*/
optionExists(option) {
return this.optionList.some(_option => this.optionComparator(_option, option))
return this.optionList.some(_option =>
this.optionComparator(_option, option)
);
},
/**
@@ -855,8 +801,8 @@
* @param option
* @return {*}
*/
normalizeOptionForSlot (option) {
return (typeof option === 'object') ? option : {[this.label]: option};
normalizeOptionForSlot(option) {
return typeof option === "object" ? option : { [this.label]: option };
},
/**
@@ -866,7 +812,7 @@
* @param {Object || String} option
* @return {void}
*/
pushTag (option) {
pushTag(option) {
this.pushedTags.push(option);
},
@@ -877,9 +823,9 @@
*/
onEscape() {
if (!this.search.length) {
this.searchEl.blur()
this.searchEl.blur();
} else {
this.search = ''
this.search = "";
}
},
@@ -890,19 +836,19 @@
*/
onSearchBlur() {
if (this.mousedown && !this.searching) {
this.mousedown = false
this.mousedown = false;
} else {
const { clearSearchOnSelect, multiple } = this;
if (this.clearSearchOnBlur({ clearSearchOnSelect, multiple })) {
this.search = ''
this.search = "";
}
this.closeSearchOptions()
return
this.closeSearchOptions();
return;
}
// Fixed bug where no-options message could not be closed
if (this.search.length === 0 && this.options.length === 0){
this.closeSearchOptions()
return
if (this.search.length === 0 && this.options.length === 0) {
this.closeSearchOptions();
return;
}
},
@@ -912,8 +858,8 @@
* @return {void}
*/
onSearchFocus() {
this.open = true
this.$emit('search:focus')
this.open = true;
this.$emit("search:focus");
},
/**
@@ -925,7 +871,7 @@
* @return {void}
*/
onMousedown() {
this.mousedown = true
this.mousedown = true;
},
/**
@@ -934,7 +880,7 @@
* @return {void}
*/
onMouseUp() {
this.mousedown = false
this.mousedown = false;
},
/**
@@ -942,7 +888,7 @@
* @param e {KeyboardEvent}
* @return {Function}
*/
onSearchKeyDown (e) {
onSearchKeyDown(e) {
const preventAndSelect = e => {
e.preventDefault();
return !this.isComposing && this.typeAheadSelect();
@@ -964,14 +910,16 @@
40: e => {
e.preventDefault();
return this.typeAheadDown();
},
}
};
this.selectOnKeyCodes.forEach(keyCode => defaults[keyCode] = preventAndSelect);
this.selectOnKeyCodes.forEach(
keyCode => (defaults[keyCode] = preventAndSelect)
);
const handlers = this.mapKeydown(defaults, this);
if (typeof handlers[e.keyCode] === 'function') {
if (typeof handlers[e.keyCode] === "function") {
return handlers[e.keyCode](e);
}
}
@@ -983,15 +931,18 @@
* track the state of values internally.
* @return {boolean}
*/
isTrackingValues () {
return typeof this.value === 'undefined' || this.$options.propsData.hasOwnProperty('reduce');
isTrackingValues() {
return (
typeof this.value === "undefined" ||
this.$options.propsData.hasOwnProperty("reduce")
);
},
/**
* The options that are currently selected.
* @return {Array}
*/
selectedValue () {
selectedValue() {
let value = this.value;
if (this.isTrackingValues) {
// Vue select has to manage value internally
@@ -1012,7 +963,7 @@
*
* @return {Array}
*/
optionList () {
optionList() {
return this.options.concat(this.pushTags ? this.pushedTags : []);
},
@@ -1020,9 +971,11 @@
* Find the search input DOM element.
* @returns {HTMLInputElement}
*/
searchEl () {
return !!this.$scopedSlots['search']
? this.$refs.selectedOptions.querySelector(this.searchInputQuerySelector)
searchEl() {
return this.$slots["search"]
? this.$refs.selectedOptions.querySelector(
this.searchInputQuerySelector
)
: this.$refs.search;
},
@@ -1030,7 +983,7 @@
* The object to be bound to the $slots.search scoped slot.
* @returns {Object}
*/
scope () {
scope() {
const listSlot = {
search: this.search,
loading: this.loading,
@@ -1040,30 +993,32 @@
return {
search: {
attributes: {
'disabled': this.disabled,
'placeholder': this.searchPlaceholder,
'tabindex': this.tabindex,
'readonly': !this.searchable,
'id': this.inputId,
'aria-autocomplete': 'list',
'aria-labelledby': `vs${this.uid}__combobox`,
'aria-controls': `vs${this.uid}__listbox`,
'ref': 'search',
'type': 'search',
'autocomplete': this.autocomplete,
'value': this.search,
...(this.dropdownOpen && this.filteredOptions[this.typeAheadPointer] ? {
'aria-activedescendant': `vs${this.uid}__option-${this.typeAheadPointer}`
} : {}),
disabled: this.disabled,
placeholder: this.searchPlaceholder,
tabindex: this.tabindex,
readonly: !this.searchable,
id: this.inputId,
"aria-autocomplete": "list",
"aria-labelledby": `vs${this.uid}__combobox`,
"aria-controls": `vs${this.uid}__listbox`,
ref: "search",
type: "search",
autocomplete: this.autocomplete,
value: this.search,
...(this.dropdownOpen && this.filteredOptions[this.typeAheadPointer]
? {
"aria-activedescendant": `vs${this.uid}__option-${this.typeAheadPointer}`
}
: {})
},
events: {
'compositionstart': () => this.isComposing = true,
'compositionend': () => this.isComposing = false,
'keydown': this.onSearchKeyDown,
'blur': this.onSearchBlur,
'focus': this.onSearchFocus,
'input': (e) => this.search = e.target.value,
},
compositionstart: () => (this.isComposing = true),
compositionend: () => (this.isComposing = false),
keydown: this.onSearchKeyDown,
blur: this.onSearchBlur,
focus: this.onSearchFocus,
input: e => (this.search = e.target.value)
}
},
spinner: {
loading: this.mutableLoading
@@ -1071,14 +1026,14 @@
noOptions: {
search: this.search,
loading: this.loading,
searching: this.searching,
searching: this.searching
},
openIndicator: {
attributes: {
'ref': 'openIndicator',
'role': 'presentation',
'class': 'vs__open-indicator',
},
ref: "openIndicator",
role: "presentation",
class: "vs__open-indicator"
}
},
listHeader: listSlot,
listFooter: listSlot,
@@ -1094,7 +1049,7 @@
*
* @return {Object}
*/
childComponents () {
childComponents() {
return {
...childComponents,
...this.components
@@ -1107,14 +1062,14 @@
*/
stateClasses() {
return {
'vs--open': this.dropdownOpen,
'vs--single': !this.multiple,
'vs--searching': this.searching && !this.noDrop,
'vs--searchable': this.searchable && !this.noDrop,
'vs--unsearchable': !this.searchable,
'vs--loading': this.mutableLoading,
'vs--disabled': this.disabled
}
"vs--open": this.dropdownOpen,
"vs--single": !this.multiple,
"vs--searching": this.searching && !this.noDrop,
"vs--searchable": this.searchable && !this.noDrop,
"vs--unsearchable": !this.searchable,
"vs--loading": this.mutableLoading,
"vs--disabled": this.disabled
};
},
/**
@@ -1123,7 +1078,7 @@
* @return {Boolean} True if non empty value
*/
searching() {
return !! this.search
return !!this.search;
},
/**
@@ -1132,7 +1087,7 @@
* @return {Boolean} True if open
*/
dropdownOpen() {
return this.noDrop ? false : this.open && !this.mutableLoading
return this.noDrop ? false : this.open && !this.mutableLoading;
},
/**
@@ -1161,7 +1116,9 @@
return optionList;
}
let options = this.search.length ? this.filter(optionList, this.search, this) : optionList;
let options = this.search.length
? this.filter(optionList, this.search, this)
: optionList;
if (this.taggable && this.search.length) {
const createdOption = this.createOption(this.search);
if (!this.optionExists(createdOption)) {
@@ -1184,9 +1141,10 @@
* @return {Boolean}
*/
showClearButton() {
return !this.multiple && this.clearable && !this.open && !this.isValueEmpty
},
},
return (
!this.multiple && this.clearable && !this.open && !this.isValueEmpty
);
}
}
};
</script>
+3 -3
View File
@@ -1,7 +1,7 @@
import Deselect from './Deselect';
import OpenIndicator from './OpenIndicator';
import Deselect from "./Deselect";
import OpenIndicator from "./OpenIndicator";
export default {
Deselect,
OpenIndicator
}
};
+14 -9
View File
@@ -1,27 +1,32 @@
export default {
inserted (el, bindings, {context}) {
inserted(el, bindings, { context }) {
if (context.appendToBody) {
const {height, top, left, width} = context.$refs.toggle.getBoundingClientRect();
const {
height,
top,
left,
width
} = context.$refs.toggle.getBoundingClientRect();
let scrollX = window.scrollX || window.pageXOffset;
let scrollY = window.scrollY || window.pageYOffset;
el.unbindPosition = context.calculatePosition(el, context, {
width: width + 'px',
left: (scrollX + left) + 'px',
top: (scrollY + top + height) + 'px',
width: width + "px",
left: scrollX + left + "px",
top: scrollY + top + height + "px"
});
document.body.appendChild(el);
}
},
unbind (el, bindings, {context}) {
unbind(el, bindings, { context }) {
if (context.appendToBody) {
if (el.unbindPosition && typeof el.unbindPosition === 'function') {
if (el.unbindPosition && typeof el.unbindPosition === "function") {
el.unbindPosition();
}
if (el.parentNode) {
el.parentNode.removeChild(el);
}
}
},
}
}
};
+4 -4
View File
@@ -1,5 +1,5 @@
import VueSelect from './components/Select.vue'
import mixins from './mixins/index'
import VueSelect from "./components/Select.vue";
import mixins from "./mixins/index";
export default VueSelect
export { VueSelect, mixins }
export default VueSelect;
export { VueSelect, mixins };
+4
View File
@@ -0,0 +1,4 @@
import { createApp } from "vue";
import App from "./App.vue";
createApp(App).mount("#app");
+11 -11
View File
@@ -7,13 +7,13 @@ export default {
*/
loading: {
type: Boolean,
default: false,
},
default: false
}
},
data () {
data() {
return {
mutableLoading: false,
mutableLoading: false
};
},
@@ -27,8 +27,8 @@ export default {
*
* @emits search
*/
search () {
this.$emit('search', this.search, this.toggleLoading);
search() {
this.$emit("search", this.search, this.toggleLoading);
},
/**
@@ -36,9 +36,9 @@ export default {
* mutable loading value.
* @param val
*/
loading (val) {
loading(val) {
this.mutableLoading = val;
},
}
},
methods: {
@@ -49,11 +49,11 @@ export default {
* @param toggle Boolean
* @returns {*}
*/
toggleLoading (toggle = null) {
toggleLoading(toggle = null) {
if (toggle == null) {
return (this.mutableLoading = !this.mutableLoading);
}
return (this.mutableLoading = toggle);
},
},
}
}
};
+4 -4
View File
@@ -1,5 +1,5 @@
import ajax from './ajax'
import pointer from './typeAheadPointer'
import pointerScroll from './pointerScroll'
import ajax from "./ajax";
import pointer from "./typeAheadPointer";
import pointerScroll from "./pointerScroll";
export default { ajax, pointer, pointerScroll }
export default { ajax, pointer, pointerScroll };
+1 -1
View File
@@ -11,7 +11,7 @@ export default {
if (this.autoscroll) {
this.maybeAdjustScroll();
}
},
}
},
methods: {
+7 -3
View File
@@ -2,7 +2,7 @@ export default {
data() {
return {
typeAheadPointer: -1
}
};
},
watch: {
@@ -37,7 +37,11 @@ export default {
* @return {void}
*/
typeAheadDown() {
for (let i = this.typeAheadPointer + 1; i < this.filteredOptions.length; i++) {
for (
let i = this.typeAheadPointer + 1;
i < this.filteredOptions.length;
i++
) {
if (this.selectable(this.filteredOptions[i])) {
this.typeAheadPointer = i;
break;
@@ -58,4 +62,4 @@ export default {
}
}
}
}
};
+3 -1
View File
@@ -5,7 +5,9 @@
function sortAndStringify(sortable) {
const ordered = {};
Object.keys(sortable).sort().forEach(key => {
Object.keys(sortable)
.sort()
.forEach(key => {
ordered[key] = sortable[key];
});
+10
View File
@@ -0,0 +1,10 @@
module.exports = {
plugins: ["cypress"],
env: {
mocha: true,
"cypress/globals": true
},
rules: {
strict: "off"
}
};
+25
View File
@@ -0,0 +1,25 @@
/* eslint-disable arrow-body-style */
// https://docs.cypress.io/guides/guides/plugins-guide.html
// if you need a custom webpack configuration you can uncomment the following import
// and then use the `file:preprocessor` event
// as explained in the cypress docs
// https://docs.cypress.io/api/plugins/preprocessors-api.html#Examples
// /* eslint-disable import/no-extraneous-dependencies, global-require */
// const webpack = require('@cypress/webpack-preprocessor')
module.exports = (on, config) => {
// on('file:preprocessor', webpack({
// webpackOptions: require('@vue/cli-service/webpack.config'),
// watchOptions: {}
// }))
return Object.assign({}, config, {
fixturesFolder: "tests/e2e/fixtures",
integrationFolder: "tests/e2e/specs",
screenshotsFolder: "tests/e2e/screenshots",
videosFolder: "tests/e2e/videos",
supportFile: "tests/e2e/support/index.js"
});
};
+8
View File
@@ -0,0 +1,8 @@
// https://docs.cypress.io/api/introduction/api.html
describe("My First Test", () => {
it("Visits the app root url", () => {
cy.visit("/");
cy.contains("h1", "Welcome to Your Vue.js App");
});
});
+25
View File
@@ -0,0 +1,25 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This is will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
+20
View File
@@ -0,0 +1,20 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import "./commands";
// Alternatively you can use CommonJS syntax:
// require('./commands')
+10 -10
View File
@@ -1,6 +1,6 @@
import { shallowMount } from "@vue/test-utils";
import VueSelect from "../src/components/Select.vue";
import Vue from 'vue';
import Vue from "vue";
/**
* Trigger a submit event on the search
@@ -13,7 +13,7 @@ export const searchSubmit = (Wrapper, searchText = false) => {
if (searchText) {
Wrapper.vm.search = searchText;
}
Wrapper.find({ ref: "search" }).trigger("keydown.enter")
Wrapper.find({ ref: "search" }).trigger("keydown.enter");
};
/**
@@ -52,14 +52,13 @@ export const selectWithProps = (propsData = {}) => {
export const mountDefault = (props = {}, options = {}) => {
return shallowMount(VueSelect, {
propsData: {
options: ['one', 'two', 'three'],
...props,
options: ["one", "two", "three"],
...props
},
...options,
...options
});
};
/**
* Returns a v-select component directly.
* @param props
@@ -68,11 +67,12 @@ export const mountDefault = (props = {}, options = {}) => {
*/
export const mountWithoutTestUtils = (props = {}, options = {}) => {
return new Vue({
render: createEl => createEl('vue-select', {
ref: 'select',
props: {options: ['one', 'two', 'three'], ...props},
render: createEl =>
createEl("vue-select", {
ref: "select",
props: { options: ["one", "two", "three"], ...props },
...options
}),
components: {VueSelect},
components: { VueSelect }
}).$mount().$refs.select;
};
+2 -2
View File
@@ -3,6 +3,6 @@ module.exports = {
jest: true
},
rules: {
'import/no-extraneous-dependencies': 'off'
"import/no-extraneous-dependencies": "off"
}
}
};
+7 -7
View File
@@ -1,6 +1,6 @@
import { selectWithProps } from "../helpers";
import { shallowMount } from '@vue/test-utils';
import vSelect from '../../src/components/Select';
import { shallowMount } from "@vue/test-utils";
import vSelect from "../../src/components/Select";
describe("Asynchronous Loading", () => {
it("can toggle the loading class", () => {
@@ -43,19 +43,19 @@ describe("Asynchronous Loading", () => {
const Select = shallowMount(vSelect, {
listeners: {
search: (search, loading) => {
loading(false)
},
},
loading(false);
}
}
});
Select.vm.mutableLoading = true;
Select.vm.search = 'foo';
Select.vm.search = "foo";
await Select.vm.$nextTick();
expect(Select.vm.mutableLoading).toEqual(false);
});
it('will sync mutable loading with the loading prop', async () => {
it("will sync mutable loading with the loading prop", async () => {
const Select = selectWithProps({ loading: false });
Select.setProps({ loading: true });
await Select.vm.$nextTick();
+15 -17
View File
@@ -1,30 +1,28 @@
import Vue from 'vue';
import { selectWithProps } from '../helpers';
import Vue from "vue";
import { selectWithProps } from "../helpers";
describe('Components API', () => {
it('swap the Deselect component', () => {
const Deselect = Vue.component('Deselect', {
render (createElement) {
return createElement('button', 'remove');
},
describe("Components API", () => {
it("swap the Deselect component", () => {
const Deselect = Vue.component("Deselect", {
render(createElement) {
return createElement("button", "remove");
}
});
const Select = selectWithProps({components: {Deselect}});
const Select = selectWithProps({ components: { Deselect } });
expect(Select.contains(Deselect)).toBeTruthy();
});
it('swap the OpenIndicator component', () => {
const OpenIndicator = Vue.component('OpenIndicator', {
render (createElement) {
return createElement('i', '^');
},
it("swap the OpenIndicator component", () => {
const OpenIndicator = Vue.component("OpenIndicator", {
render(createElement) {
return createElement("i", "^");
}
});
const Select = selectWithProps({components: {OpenIndicator}});
const Select = selectWithProps({ components: { OpenIndicator } });
expect(Select.contains(OpenIndicator)).toBeTruthy();
});
});
+12 -12
View File
@@ -1,9 +1,9 @@
import { mountDefault, selectWithProps } from '../helpers';
import { mountDefault, selectWithProps } from "../helpers";
describe("Removing values", () => {
it("can remove the given tag when its close icon is clicked", async () => {
const Select = selectWithProps({ multiple: true });
Select.vm.$data._value = 'one';
Select.vm.$data._value = "one";
await Select.vm.$nextTick();
Select.find(".vs__deselect").trigger("click");
@@ -31,9 +31,9 @@ describe("Removing values", () => {
Select.vm.$data._value = ["one", "two"];
Select.find('.vs__search').trigger('keydown.backspace')
Select.find(".vs__search").trigger("keydown.backspace");
expect(Select.emitted().input).toEqual([[['one']]]);
expect(Select.emitted().input).toEqual([[["one"]]]);
expect(Select.vm.selectedValue).toEqual(["one"]);
});
@@ -42,20 +42,20 @@ describe("Removing values", () => {
options: ["one", "two", "three"]
});
Select.vm.$data._value = 'one';
Select.vm.$data._value = "one";
Select.vm.maybeDeleteValue();
expect(Select.vm.selectedValue).toEqual([]);
});
it('will not emit input event if value has not changed with backspace', () => {
it("will not emit input event if value has not changed with backspace", () => {
const Select = mountDefault();
Select.vm.$data._value = 'one';
Select.find({ ref: 'search' }).trigger('keydown.backspace');
Select.vm.$data._value = "one";
Select.find({ ref: "search" }).trigger("keydown.backspace");
expect(Select.emitted().input.length).toBe(1);
Select.find({ ref: 'search' }).trigger('keydown.backspace');
Select.find({ ref: 'search' }).trigger('keydown.backspace');
Select.find({ ref: "search" }).trigger("keydown.backspace");
Select.find({ ref: "search" }).trigger("keydown.backspace");
expect(Select.emitted().input.length).toBe(1);
});
@@ -81,9 +81,9 @@ describe("Removing values", () => {
it("should remove selected value when clicked", () => {
const Select = selectWithProps({
options: ["foo", "bar"],
options: ["foo", "bar"]
});
Select.vm.$data._value = 'foo';
Select.vm.$data._value = "foo";
expect(Select.vm.selectedValue).toEqual(["foo"]);
Select.find("button.vs__clear").trigger("click");
+18 -19
View File
@@ -1,10 +1,10 @@
import { selectWithProps } from "../helpers";
import OpenIndicator from "../../src/components/OpenIndicator";
const preventDefault = jest.fn()
const preventDefault = jest.fn();
function clickEvent (currentTarget) {
return { currentTarget, preventDefault }
function clickEvent(currentTarget) {
return { currentTarget, preventDefault };
}
describe("Toggling Dropdown", () => {
@@ -34,7 +34,7 @@ describe("Toggling Dropdown", () => {
Select.vm.toggleDropdown(clickEvent(Select.vm.$refs.search));
expect(Select.vm.open).toEqual(true);
Select.vm.toggleDropdown(clickEvent(Select.vm.$el));
expect(Select.vm.open).toEqual(false)
expect(Select.vm.open).toEqual(false);
});
it("should open the dropdown when the selected tag is clicked", () => {
@@ -135,22 +135,22 @@ describe("Toggling Dropdown", () => {
});
Select.vm.search = "foo";
Select.find('.vs__search').trigger('keydown.esc')
Select.find(".vs__search").trigger("keydown.esc");
expect(Select.vm.search).toEqual("");
});
it("should have an open class when dropdown is active", () => {
const Select = selectWithProps();
expect(Select.vm.stateClasses['vs--open']).toEqual(false);
expect(Select.vm.stateClasses["vs--open"]).toEqual(false);
Select.vm.open = true;
expect(Select.vm.stateClasses['vs--open']).toEqual(true);
expect(Select.vm.stateClasses["vs--open"]).toEqual(true);
});
it("should not display the dropdown if noDrop is true", async () => {
const Select = selectWithProps({
noDrop: true,
noDrop: true
});
Select.vm.toggleDropdown(clickEvent(Select.vm.$refs.search));
@@ -158,34 +158,33 @@ describe("Toggling Dropdown", () => {
expect(Select.vm.open).toEqual(true);
await Select.vm.$nextTick();
expect(Select.contains('.vs__dropdown-menu')).toBeFalsy();
expect(Select.contains('.vs__dropdown-option')).toBeFalsy();
expect(Select.contains('.vs__no-options')).toBeFalsy();
expect(Select.vm.stateClasses['vs--open']).toBeFalsy();
expect(Select.contains(".vs__dropdown-menu")).toBeFalsy();
expect(Select.contains(".vs__dropdown-option")).toBeFalsy();
expect(Select.contains(".vs__no-options")).toBeFalsy();
expect(Select.vm.stateClasses["vs--open"]).toBeFalsy();
});
it("should hide the open indicator if noDrop is true", () => {
const Select = selectWithProps({
noDrop: true,
noDrop: true
});
expect(Select.contains(OpenIndicator)).toBeFalsy();
});
it("should not add the searchable state class when noDrop is true", () => {
const Select = selectWithProps({
noDrop: true,
noDrop: true
});
expect(Select.classes('vs--searchable')).toBeFalsy();
expect(Select.classes("vs--searchable")).toBeFalsy();
});
it("should not add the searching state class when noDrop is true", () => {
const Select = selectWithProps({
noDrop: true,
noDrop: true
});
Select.vm.search = 'Canada';
Select.vm.search = "Canada";
expect(Select.classes('vs--searching')).toBeFalsy();
expect(Select.classes("vs--searching")).toBeFalsy();
});
});
+4 -4
View File
@@ -7,10 +7,10 @@ describe("Filtering Options", () => {
propsData: { options: ["foo", "bar", "baz"] }
});
const input = Select.find('.vs__search');
input.element.value = 'a'
input.trigger('input')
expect(Select.vm.search).toEqual('a');
const input = Select.find(".vs__search");
input.element.value = "a";
input.trigger("input");
expect(Select.vm.search).toEqual("a");
});
it("should filter an array of strings", () => {
+27 -31
View File
@@ -1,74 +1,70 @@
import { mountDefault } from '../helpers';
import { mountDefault } from "../helpers";
describe('Custom Keydown Handlers', () => {
it('can use the map-keydown prop to trigger custom behaviour', () => {
describe("Custom Keydown Handlers", () => {
it("can use the map-keydown prop to trigger custom behaviour", () => {
const onKeyDown = jest.fn();
const Select = mountDefault({
mapKeydown: (defaults, vm) => ({ ...defaults, 32: onKeyDown }),
mapKeydown: (defaults, vm) => ({ ...defaults, 32: onKeyDown })
});
Select.find({ ref: 'search' }).trigger('keydown.space');
Select.find({ ref: "search" }).trigger("keydown.space");
expect(onKeyDown.mock.calls.length).toBe(1);
});
it('selectOnKeyCodes should trigger a selection for custom keycodes', () => {
it("selectOnKeyCodes should trigger a selection for custom keycodes", () => {
const Select = mountDefault({
selectOnKeyCodes: [32],
selectOnKeyCodes: [32]
});
const spy = jest.spyOn(Select.vm, 'typeAheadSelect');
const spy = jest.spyOn(Select.vm, "typeAheadSelect");
Select.find({ ref: 'search' }).trigger('keydown.space');
Select.find({ ref: "search" }).trigger("keydown.space");
expect(spy).toHaveBeenCalledTimes(1);
});
it('even works when combining selectOnKeyCodes with map-keydown', () => {
it("even works when combining selectOnKeyCodes with map-keydown", () => {
const onKeyDown = jest.fn();
const Select = mountDefault({
mapKeydown: (defaults, vm) => ({ ...defaults, 32: onKeyDown }),
selectOnKeyCodes: [9],
selectOnKeyCodes: [9]
});
const spy = jest.spyOn(Select.vm, 'typeAheadSelect');
const spy = jest.spyOn(Select.vm, "typeAheadSelect");
Select.find({ ref: 'search' }).trigger('keydown.space');
Select.find({ ref: "search" }).trigger("keydown.space");
expect(onKeyDown.mock.calls.length).toBe(1);
Select.find({ ref: 'search' }).trigger('keydown.tab');
Select.find({ ref: "search" }).trigger("keydown.tab");
expect(spy).toHaveBeenCalledTimes(1);
});
describe('CompositionEvent support', () => {
it('will not select a value with enter if the user is composing', () => {
describe("CompositionEvent support", () => {
it("will not select a value with enter if the user is composing", () => {
const Select = mountDefault();
const spy = jest.spyOn(Select.vm, 'typeAheadSelect');
const spy = jest.spyOn(Select.vm, "typeAheadSelect");
Select.find({ ref: 'search' }).trigger('compositionstart');
Select.find({ ref: 'search' }).trigger('keydown.enter');
Select.find({ ref: "search" }).trigger("compositionstart");
Select.find({ ref: "search" }).trigger("keydown.enter");
expect(spy).toHaveBeenCalledTimes(0);
Select.find({ ref: 'search' }).trigger('compositionend');
Select.find({ ref: 'search' }).trigger('keydown.enter');
Select.find({ ref: "search" }).trigger("compositionend");
Select.find({ ref: "search" }).trigger("keydown.enter");
expect(spy).toHaveBeenCalledTimes(1);
});
it('will not select a value with tab if the user is composing', () => {
it("will not select a value with tab if the user is composing", () => {
const Select = mountDefault({ selectOnTab: true });
const spy = jest.spyOn(Select.vm, 'typeAheadSelect');
const spy = jest.spyOn(Select.vm, "typeAheadSelect");
Select.find({ ref: 'search' }).trigger('compositionstart');
Select.find({ ref: 'search' }).trigger('keydown.tab');
Select.find({ ref: "search" }).trigger("compositionstart");
Select.find({ ref: "search" }).trigger("keydown.tab");
expect(spy).toHaveBeenCalledTimes(0);
Select.find({ ref: 'search' }).trigger('compositionend');
Select.find({ ref: 'search' }).trigger('keydown.tab');
Select.find({ ref: "search" }).trigger("compositionend");
Select.find({ ref: "search" }).trigger("keydown.tab");
expect(spy).toHaveBeenCalledTimes(1);
});
});
});
+20 -16
View File
@@ -42,15 +42,19 @@ describe("Labels", () => {
expect(Select.vm.searchPlaceholder).not.toBeDefined();
});
describe('getOptionLabel', () => {
it('will return undefined if the option lacks the label key', () => {
const getOptionLabel = VueSelect.props.getOptionLabel.default.bind({ label: 'label' });
expect(getOptionLabel({name: 'vue'})).toEqual(undefined);
describe("getOptionLabel", () => {
it("will return undefined if the option lacks the label key", () => {
const getOptionLabel = VueSelect.props.getOptionLabel.default.bind({
label: "label"
});
expect(getOptionLabel({ name: "vue" })).toEqual(undefined);
});
it('will return a string value for a valid key', () => {
const getOptionLabel = VueSelect.props.getOptionLabel.default.bind({ label: 'label' });
expect(getOptionLabel({label: 'vue'})).toEqual('vue');
it("will return a string value for a valid key", () => {
const getOptionLabel = VueSelect.props.getOptionLabel.default.bind({
label: "label"
});
expect(getOptionLabel({ label: "vue" })).toEqual("vue");
});
/**
@@ -59,23 +63,23 @@ describe("Labels", () => {
* @see https://github.com/vuejs/vue/issues/10224
* @see https://github.com/vuejs/vue/pull/10229
*/
xit('will not call getOptionLabel if both scoped option slots are used and a filter is provided', () => {
const spy = spyOn(VueSelect.props.getOptionLabel, 'default');
xit("will not call getOptionLabel if both scoped option slots are used and a filter is provided", () => {
const spy = spyOn(VueSelect.props.getOptionLabel, "default");
const Select = shallowMount(VueSelect, {
propsData: {
options: [{name: 'one'}],
filter: () => {},
options: [{ name: "one" }],
filter: () => {}
},
scopedSlots: {
'option': '<span class="option">{{ props.name }}</span>',
'selected-option': '<span class="selected">{{ props.name }}</span>',
},
option: '<span class="option">{{ props.name }}</span>',
"selected-option": '<span class="selected">{{ props.name }}</span>'
}
});
Select.vm.select({name: 'one'});
Select.vm.select({ name: "one" });
expect(spy).toHaveBeenCalledTimes(0);
expect(Select.find('.selected').exists()).toBeTruthy();
expect(Select.find(".selected").exists()).toBeTruthy();
});
});
});
+17 -16
View File
@@ -1,31 +1,32 @@
import Select from '../../src/components/Select';
describe('Comparing Options', () => {
import Select from "../../src/components/Select";
describe("Comparing Options", () => {
const comparator = Select.methods.optionComparator.bind({
getOptionKey: Select.props.getOptionKey.default,
getOptionKey: Select.props.getOptionKey.default
});
it('can compare numbers', () => {
it("can compare numbers", () => {
expect(comparator(1, 2)).toBeFalsy();
expect(comparator(1, 1)).toBeTruthy();
});
it('can compare strings', () => {
expect(comparator('one', 'one')).toBeTruthy();
expect(comparator('one', 'two')).toBeFalsy();
it("can compare strings", () => {
expect(comparator("one", "one")).toBeTruthy();
expect(comparator("one", "two")).toBeFalsy();
});
it('can compare objects', () => {
it("can compare objects", () => {
// compare ID keys
expect(comparator({label: 'halo', id: 1}, {label: 'halo', id: 2}))
.toBeFalsy();
expect(
comparator({ label: "halo", id: 1 }, { label: "halo", id: 2 })
).toBeFalsy();
// compare objects
expect(comparator({label: 'halo', value: 1}, {label: 'halo', value: 1}))
.toBeTruthy();
expect(
comparator({ label: "halo", value: 1 }, { label: "halo", value: 1 })
).toBeTruthy();
// compare objects with different orders
expect(comparator({value: 1, label: 'halo'}, {label: 'halo', value: 1}))
.toBeTruthy();
expect(
comparator({ value: 1, label: "halo" }, { label: "halo", value: 1 })
).toBeTruthy();
});
});
+13 -13
View File
@@ -1,25 +1,25 @@
import Select from '../../src/components/Select.vue';
describe('Serializing Option Keys', () => {
import Select from "../../src/components/Select.vue";
describe("Serializing Option Keys", () => {
const getOptionKey = Select.props.getOptionKey.default;
it('can serialize strings to a key', () => {
expect(getOptionKey('vue')).toBe('vue');
it("can serialize strings to a key", () => {
expect(getOptionKey("vue")).toBe("vue");
});
it('can serialize integers to a key', () => {
it("can serialize integers to a key", () => {
expect(getOptionKey(1)).toBe(1);
});
it('can serialize objects to a key', () => {
expect(getOptionKey({label: 'vue'})).toBe('{"label":"vue"}');
it("can serialize objects to a key", () => {
expect(getOptionKey({ label: "vue" })).toBe('{"label":"vue"}');
});
it('will use an ID property if the object contains one', () => {
expect(getOptionKey({id: 1})).toBe(1);
expect(getOptionKey({id: 'one'})).toBe('one');
expect(getOptionKey({id: {im: 'a nested object'}}))
.toEqual({im: 'a nested object'});
it("will use an ID property if the object contains one", () => {
expect(getOptionKey({ id: 1 })).toBe(1);
expect(getOptionKey({ id: "one" })).toBe("one");
expect(getOptionKey({ id: { im: "a nested object" } })).toEqual({
im: "a nested object"
});
});
});
+82 -60
View File
@@ -1,6 +1,6 @@
import { mount, shallowMount } from '@vue/test-utils';
import { mount, shallowMount } from "@vue/test-utils";
import VueSelect from "../../src/components/Select";
import { mountDefault } from '../helpers';
import { mountDefault } from "../helpers";
describe("Reset on options change", () => {
it("should not reset the selected value by default when the options property changes", () => {
@@ -8,80 +8,94 @@ describe("Reset on options change", () => {
propsData: { options: ["one"] }
});
Select.vm.$data._value = 'one';
Select.vm.$data._value = "one";
Select.setProps({options: ["four", "five", "six"]});
Select.setProps({ options: ["four", "five", "six"] });
expect(Select.vm.selectedValue).toEqual(["one"]);
});
describe('resetOnOptionsChange as a function', () => {
it('will yell at you if resetOnOptionsChange is not a function or boolean', () => {
const spy = jest.spyOn(console, 'error').mockImplementation(() => {});
describe("resetOnOptionsChange as a function", () => {
it("will yell at you if resetOnOptionsChange is not a function or boolean", () => {
const spy = jest.spyOn(console, "error").mockImplementation(() => {});
mountDefault({resetOnOptionsChange: 1});
expect(spy.mock.calls[0][0]).toContain('Invalid prop: custom validator check failed for prop "resetOnOptionsChange"')
mountDefault({resetOnOptionsChange: 'one'});
expect(spy.mock.calls[1][0]).toContain('Invalid prop: custom validator check failed for prop "resetOnOptionsChange"')
mountDefault({resetOnOptionsChange: []});
expect(spy.mock.calls[2][0]).toContain('Invalid prop: custom validator check failed for prop "resetOnOptionsChange"')
mountDefault({resetOnOptionsChange: {}});
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', async () => {
let resetOnOptionsChange = jest.fn(option => option);
const Select = mountDefault(
{resetOnOptionsChange, options: ['bear'], value: 'selected'},
mountDefault({ resetOnOptionsChange: 1 });
expect(spy.mock.calls[0][0]).toContain(
'Invalid prop: custom validator check failed for prop "resetOnOptionsChange"'
);
Select.setProps({options: ['lake', 'kite']});
mountDefault({ resetOnOptionsChange: "one" });
expect(spy.mock.calls[1][0]).toContain(
'Invalid prop: custom validator check failed for prop "resetOnOptionsChange"'
);
mountDefault({ resetOnOptionsChange: [] });
expect(spy.mock.calls[2][0]).toContain(
'Invalid prop: custom validator check failed for prop "resetOnOptionsChange"'
);
mountDefault({ resetOnOptionsChange: {} });
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", 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']);
expect(resetOnOptionsChange).toHaveBeenCalledWith(
["lake", "kite"],
["bear"],
["selected"]
);
});
it('should allow resetOnOptionsChange to be a function that returns true', async () => {
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'},
propsData: { resetOnOptionsChange, options: ["one"], value: "one" }
});
const spy = jest.spyOn(Select.vm, 'clearSelection');
const spy = jest.spyOn(Select.vm, "clearSelection");
Select.setProps({options: ['one', 'two']});
Select.setProps({ options: ["one", "two"] });
await Select.vm.$nextTick();
expect(spy).toHaveBeenCalledTimes(1);
});
it('should allow resetOnOptionsChange to be a function that returns false', () => {
it("should allow resetOnOptionsChange to be a function that returns false", () => {
let resetOnOptionsChange = () => false;
const Select = shallowMount(VueSelect, {
propsData: {resetOnOptionsChange, options: ['one'], value: 'one'},
propsData: { resetOnOptionsChange, options: ["one"], value: "one" }
});
const spy = jest.spyOn(Select.vm, 'clearSelection');
const spy = jest.spyOn(Select.vm, "clearSelection");
Select.setProps({options: ['one', 'two']});
Select.setProps({ options: ["one", "two"] });
expect(spy).not.toHaveBeenCalled();
});
it('should reset the options if the selectedValue does not exist in the new options', async () => {
let resetOnOptionsChange = (options, old, val) => val.some(val => options.includes(val));
it("should reset the options if the selectedValue does not exist in the new options", async () => {
let resetOnOptionsChange = (options, old, val) =>
val.some(val => options.includes(val));
const Select = shallowMount(VueSelect, {
propsData: {resetOnOptionsChange, options: ['one'], value: 'one'},
propsData: { resetOnOptionsChange, options: ["one"], value: "one" }
});
const spy = jest.spyOn(Select.vm, 'clearSelection');
const spy = jest.spyOn(Select.vm, "clearSelection");
Select.setProps({options: ['one', 'two']});
Select.setProps({ options: ["one", "two"] });
await Select.vm.$nextTick();
expect(Select.vm.selectedValue).toEqual(['one']);
expect(Select.vm.selectedValue).toEqual(["one"]);
Select.setProps({options: ['two']});
Select.setProps({ options: ["two"] });
await Select.vm.$nextTick();
expect(spy).toHaveBeenCalledTimes(1);
@@ -93,9 +107,9 @@ describe("Reset on options change", () => {
propsData: { resetOnOptionsChange: true, options: ["one"] }
});
Select.vm.$data._value = 'one';
Select.vm.$data._value = "one";
Select.setProps({options: ["four", "five", "six"]});
Select.setProps({ options: ["four", "five", "six"] });
await Select.vm.$nextTick();
expect(Select.vm.selectedValue).toEqual([]);
@@ -103,39 +117,47 @@ describe("Reset on options change", () => {
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 } }
propsData: {
value: "one",
options: [],
reduce(option) {
return option.value;
}
}
});
Select.setProps({options: [{ label: "oneLabel", value: "one" }]});
Select.setProps({ options: [{ label: "oneLabel", value: "one" }] });
await Select.vm.$nextTick();
expect(Select.vm.selectedValue).toEqual([{ label: "oneLabel", value: "one" }]);
expect(Select.vm.selectedValue).toEqual([
{ label: "oneLabel", value: "one" }
]);
});
it('clearSearchOnBlur returns false when multiple is true', () => {
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');
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,
multiple: false
});
expect(Select.vm.search).toBe('');
expect(Select.vm.search).toBe("");
});
it('clearSearchOnBlur accepts a function', () => {
it("clearSearchOnBlur accepts a function", () => {
let clearSearchOnBlur = jest.fn(() => false);
const Select = mountDefault({clearSearchOnBlur});
const Select = mountDefault({ clearSearchOnBlur });
Select.find({ref: 'search'}).trigger('click');
Select.setData({search: 'one'});
Select.find({ref: 'search'}).trigger('blur');
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');
expect(Select.vm.search).toBe("one");
});
});
+49 -35
View File
@@ -10,7 +10,9 @@ describe("When reduce prop is defined", () => {
options: [{ label: "This is Foo", value: "foo" }]
}
});
expect(Select.vm.selectedValue).toEqual([{ label: "This is Foo", value: "foo" }]);
expect(Select.vm.selectedValue).toEqual([
{ label: "This is Foo", value: "foo" }
]);
});
it("can determine if an object is pre-selected", () => {
@@ -35,20 +37,22 @@ describe("When reduce prop is defined", () => {
).toEqual(true);
});
it('can determine if an object is selected after its been chosen', () => {
it("can determine if an object is selected after its been chosen", () => {
const Select = shallowMount(VueSelect, {
propsData: {
reduce: option => option.id,
options: [{id: 'foo', label: 'FooBar'}],
},
options: [{ id: "foo", label: "FooBar" }]
}
});
Select.vm.select({id: 'foo', label: 'FooBar'});
Select.vm.select({ id: "foo", label: "FooBar" });
expect(Select.vm.isOptionSelected({
id: 'foo',
label: 'This is FooBar',
})).toEqual(true);
expect(
Select.vm.isOptionSelected({
id: "foo",
label: "This is FooBar"
})
).toEqual(true);
});
it("can accept an array of objects and pre-selected values (multiple)", () => {
@@ -64,7 +68,9 @@ describe("When reduce prop is defined", () => {
}
});
expect(Select.vm.selectedValue).toEqual([{ label: "This is Foo", value: "foo" }]);
expect(Select.vm.selectedValue).toEqual([
{ label: "This is Foo", value: "foo" }
]);
});
it("can deselect a pre-selected object", () => {
@@ -79,7 +85,7 @@ describe("When reduce prop is defined", () => {
}
});
Select.vm.$data._value = ['foo', 'bar'];
Select.vm.$data._value = ["foo", "bar"];
Select.vm.deselect("foo");
expect(Select.vm.selectedValue).toEqual(["bar"]);
@@ -118,7 +124,7 @@ describe("When reduce prop is defined", () => {
return this.current;
},
set(value) {
if (value == 'baz') return;
if (value == "baz") return;
this.current = value;
}
}
@@ -134,18 +140,24 @@ describe("When reduce prop is defined", () => {
const Select = Parent.vm.$children[0];
expect(Select.value).toEqual("foo");
expect(Select.selectedValue).toEqual([{ label: "This is Foo", value: "foo" }]);
expect(Select.selectedValue).toEqual([
{ label: "This is Foo", value: "foo" }
]);
Select.select({ label: "This is Bar", value: "bar" });
await Select.$nextTick();
expect(Parent.vm.value).toEqual("bar");
expect(Select.selectedValue).toEqual([{ label: "This is Bar", value: "bar" }]);
expect(Select.selectedValue).toEqual([
{ label: "This is Bar", value: "bar" }
]);
// Parent denies to set baz
Select.select({ label: "This is Baz", value: "baz" });
await Select.$nextTick();
expect(Select.selectedValue).toEqual([{ label: "This is Bar", value: "bar" }]);
expect(Parent.vm.value).toEqual('bar');
expect(Select.selectedValue).toEqual([
{ label: "This is Bar", value: "bar" }
]);
expect(Parent.vm.value).toEqual("bar");
});
it("can generate labels using a custom label key", () => {
@@ -155,7 +167,10 @@ describe("When reduce prop is defined", () => {
reduce: option => option.value,
value: ["CA"],
label: "name",
options: [{ value: "CA", name: "Canada" }, { value: "US", name: "United States" }]
options: [
{ value: "CA", name: "Canada" },
{ value: "US", name: "United States" }
]
}
});
@@ -177,28 +192,28 @@ describe("When reduce prop is defined", () => {
);
});
it('can work with falsey values', () => {
const option = {value: 0, label: 'No'};
it("can work with falsey values", () => {
const option = { value: 0, label: "No" };
const Select = shallowMount(VueSelect, {
propsData: {
reduce: option => option.value,
options: [option, {value: 1, label: 'Yes'}],
value: 0,
},
options: [option, { value: 1, label: "Yes" }],
value: 0
}
});
expect(Select.vm.findOptionFromReducedValue(option)).toEqual(option);
expect(Select.vm.selectedValue).toEqual([option]);
});
it('works with null values', () => {
const option = {value: null, label: 'No'};
it("works with null values", () => {
const option = { value: null, label: "No" };
const Select = shallowMount(VueSelect, {
propsData: {
reduce: option => option.value,
options: [option, {value: 1, label: 'Yes'}],
value: null,
},
options: [option, { value: 1, label: "Yes" }],
value: null
}
});
expect(Select.vm.findOptionFromReducedValue(option)).toEqual(option);
@@ -233,7 +248,6 @@ describe("When reduce prop is defined", () => {
Select.vm.select(nestedOption);
expect(Select.vm.isOptionSelected(nestedOption)).toEqual(true);
});
});
it("reacts correctly when value property changes", async () => {
@@ -252,10 +266,10 @@ describe("When reduce prop is defined", () => {
expect(Select.vm.selectedValue).toEqual([optionToChangeTo]);
});
describe('Reducing Tags', () => {
it('tracks values that have been created by the user', async () => {
describe("Reducing Tags", () => {
it("tracks values that have been created by the user", async () => {
const Parent = mount({
data: () => ({selected: null, options: []}),
data: () => ({ selected: null, options: [] }),
template: `
<v-select
v-model="selected"
@@ -265,7 +279,7 @@ describe("When reduce prop is defined", () => {
:create-option="label => ({ label, value: -1 })"
/>
`,
components: {'v-select': VueSelect},
components: { "v-select": VueSelect }
});
const Select = Parent.vm.$children[0];
@@ -273,15 +287,15 @@ describe("When reduce prop is defined", () => {
Select.$refs.search.focus();
await Select.$nextTick();
Select.search = 'hello';
Select.search = "hello";
await Select.$nextTick();
Select.typeAheadSelect();
await Select.$nextTick();
// Then
expect(Select.selectedValue).toEqual([{label: 'hello', value: -1}]);
expect(Select.$refs.selectedOptions.textContent.trim()).toEqual('hello');
expect(Select.selectedValue).toEqual([{ label: "hello", value: -1 }]);
expect(Select.$refs.selectedOptions.textContent.trim()).toEqual("hello");
expect(Parent.vm.selected).toEqual(-1);
});
});
+8 -8
View File
@@ -4,7 +4,7 @@ describe("Selectable prop", () => {
it("should select selectable option if clicked", async () => {
const Select = selectWithProps({
options: ["one", "two", "three"],
selectable: (option) => option === "one"
selectable: option => option === "one"
});
Select.vm.$data.open = true;
@@ -14,12 +14,12 @@ describe("Selectable prop", () => {
await Select.vm.$nextTick();
expect(Select.vm.selectedValue).toEqual(["one"]);
})
});
it("should not select not selectable option if clicked", async () => {
const Select = selectWithProps({
options: ["one", "two", "three"],
selectable: (option) => option === "one"
selectable: option => option === "one"
});
Select.vm.$data.open = true;
@@ -34,7 +34,7 @@ describe("Selectable prop", () => {
it("should skip non-selectable option on down arrow keyDown", () => {
const Select = selectWithProps({
options: ["one", "two", "three"],
selectable: (option) => option !== "two"
selectable: option => option !== "two"
});
Select.vm.typeAheadPointer = 1;
@@ -42,12 +42,12 @@ describe("Selectable prop", () => {
Select.find({ ref: "search" }).trigger("keydown.down");
expect(Select.vm.typeAheadPointer).toEqual(2);
})
});
it("should skip non-selectable option on up arrow keyDown", () => {
const Select = selectWithProps({
options: ["one", "two", "three"],
selectable: (option) => option !== "two"
selectable: option => option !== "two"
});
Select.vm.typeAheadPointer = 2;
@@ -55,5 +55,5 @@ describe("Selectable prop", () => {
Select.find({ ref: "search" }).trigger("keydown.up");
expect(Select.vm.typeAheadPointer).toEqual(0);
})
})
});
});
+30 -11
View File
@@ -1,6 +1,6 @@
import { mount, shallowMount } from "@vue/test-utils";
import VueSelect from "../../src/components/Select.vue";
import { mountDefault } from '../helpers';
import { mountDefault } from "../helpers";
describe("VS - Selecting Values", () => {
let defaultProps;
@@ -81,7 +81,9 @@ describe("VS - Selecting Values", () => {
];
Select.vm.deselect({ label: "This is Foo", value: "foo" });
expect(Select.vm.selectedValue).toEqual([{ label: "This is Bar", value: "bar" }]);
expect(Select.vm.selectedValue).toEqual([
{ label: "This is Bar", value: "bar" }
]);
});
it("can deselect a pre-selected string", () => {
@@ -193,15 +195,20 @@ describe("VS - Selecting Values", () => {
value: [{ label: "foo", value: "bar" }]
}
});
expect(Select.vm.isOptionSelected({ label: "foo", value: "bar" })).toEqual(true);
expect(Select.vm.isOptionSelected({ label: "foo", value: "bar" })).toEqual(
true
);
});
it('can select two options with the same label', () => {
const options = [{label: 'one', id: 1}, {label: 'one', id: 2}];
const Select = mountDefault({options, multiple: true});
it("can select two options with the same label", () => {
const options = [
{ label: "one", id: 1 },
{ label: "one", id: 2 }
];
const Select = mountDefault({ options, multiple: true });
Select.vm.select({label: 'one', id: 1});
Select.vm.select({label: 'one', id: 2});
Select.vm.select({ label: "one", id: 1 });
Select.vm.select({ label: "one", id: 2 });
expect(Select.vm.selectedValue).toEqual(options);
});
@@ -223,7 +230,11 @@ describe("VS - Selecting Values", () => {
it("will not trigger the input event when multiple is true and selection is repeated", () => {
const Select = shallowMount(VueSelect, {
propsData: { multiple: true, value: ["foo ", "bar"], options: ["foo", "bar", "baz"] }
propsData: {
multiple: true,
value: ["foo ", "bar"],
options: ["foo", "bar", "baz"]
}
});
Select.vm.select("bar");
@@ -257,7 +268,11 @@ describe("VS - Selecting Values", () => {
it("will trigger the option:selecting event regardless of current value when multiple is true", () => {
const Select = shallowMount(VueSelect, {
propsData: { multiple: true, value: ["foo", "bar"], options: ["foo", "bar"] }
propsData: {
multiple: true,
value: ["foo", "bar"],
options: ["foo", "bar"]
}
});
Select.vm.select("bar");
Select.vm.select("bar");
@@ -293,7 +308,11 @@ describe("VS - Selecting Values", () => {
it("will trigger the option:selected event regardless of current value when multiple is true", () => {
const Select = shallowMount(VueSelect, {
propsData: { multiple: true, value: ["foo", "bar"], options: ["foo", "bar"] }
propsData: {
multiple: true,
value: ["foo", "bar"],
options: ["foo", "bar"]
}
});
Select.vm.deselect("bar");
Select.vm.deselect("bar");
+90 -59
View File
@@ -1,122 +1,153 @@
import { mountDefault } from '../helpers';
import { mountDefault } from "../helpers";
describe('Scoped Slots', () => {
it('receives an option object to the selected-option-container slot', () => {
describe("Scoped Slots", () => {
it("receives an option object to the selected-option-container slot", () => {
const Select = mountDefault(
{value: 'one'},
{ value: "one" },
{
scopedSlots: {
'selected-option-container': `<span slot="selected-option-container" slot-scope="{option}">{{ option.label }}</span>`,
},
"selected-option-container": `<span slot="selected-option-container" slot-scope="{option}">{{ option.label }}</span>`
}
}
);
expect(Select.find({ ref: "selectedOptions" }).text()).toEqual("one");
});
expect(Select.find({ref: 'selectedOptions'}).text()).toEqual('one');
});
describe('Slot: selected-option', () => {
it('receives an option object to the selected-option slot', () => {
describe("Slot: selected-option", () => {
it("receives an option object to the selected-option slot", () => {
const Select = mountDefault(
{value: 'one'},
{ value: "one" },
{
scopedSlots: {
'selected-option': `<span slot="selected-option" slot-scope="option">{{ option.label }}</span>`,
},
"selected-option": `<span slot="selected-option" slot-scope="option">{{ option.label }}</span>`
}
}
);
expect(Select.find(".vs__selected").text()).toEqual("one");
});
expect(Select.find('.vs__selected').text()).toEqual('one');
});
it('opens the dropdown when clicking an option in selected-option slot',
() => {
it("opens the dropdown when clicking an option in selected-option slot", () => {
const Select = mountDefault(
{value: 'one'},
{ value: "one" },
{
scopedSlots: {
'selected-option': `<span class="my-option" slot-scope="option">{{ option.label }}</span>`,
},
});
"selected-option": `<span class="my-option" slot-scope="option">{{ option.label }}</span>`
}
}
);
Select.find('.my-option').trigger('mousedown');
Select.find(".my-option").trigger("mousedown");
expect(Select.vm.open).toEqual(true);
});
});
it('receives an option object to the option slot in the dropdown menu',
async () => {
it("receives an option object to the option slot in the dropdown menu", async () => {
const Select = mountDefault(
{value: 'one'},
{ value: "one" },
{
scopedSlots: {
'option': `<span slot="option" slot-scope="option">{{ option.label }}</span>`,
},
});
option: `<span slot="option" slot-scope="option">{{ option.label }}</span>`
}
}
);
Select.vm.open = true;
await Select.vm.$nextTick();
expect(Select.find({ref: 'dropdownMenu'}).text()).toEqual('onetwothree');
expect(Select.find({ ref: "dropdownMenu" }).text()).toEqual("onetwothree");
});
it('noOptions slot receives the current search text', async () => {
it("noOptions slot receives the current search text", async () => {
const noOptions = jest.fn();
const Select = mountDefault({}, {
scopedSlots: {'no-options': noOptions},
});
const Select = mountDefault(
{},
{
scopedSlots: { "no-options": noOptions }
}
);
Select.vm.search = 'something not there';
Select.vm.search = "something not there";
Select.vm.open = true;
await Select.vm.$nextTick();
expect(noOptions).toHaveBeenCalledWith({
loading: false,
search: 'something not there',
searching: true,
})
search: "something not there",
searching: true
});
});
test('header slot props', async () => {
test("header slot props", async () => {
const header = jest.fn();
const Select = mountDefault({}, {
scopedSlots: {header: header},
});
const Select = mountDefault(
{},
{
scopedSlots: { header: header }
}
);
await Select.vm.$nextTick();
expect(Object.keys(header.mock.calls[0][0])).toEqual([
'search', 'loading', 'searching', 'filteredOptions', 'deselect',
"search",
"loading",
"searching",
"filteredOptions",
"deselect"
]);
});
test('footer slot props', async () => {
test("footer slot props", async () => {
const footer = jest.fn();
const Select = mountDefault({}, {
scopedSlots: {footer: footer},
});
const Select = mountDefault(
{},
{
scopedSlots: { footer: footer }
}
);
await Select.vm.$nextTick();
expect(Object.keys(footer.mock.calls[0][0])).toEqual([
'search', 'loading', 'searching', 'filteredOptions', 'deselect',
"search",
"loading",
"searching",
"filteredOptions",
"deselect"
]);
});
test('list-header slot props', async () => {
test("list-header slot props", async () => {
const header = jest.fn();
const Select = mountDefault({}, {
scopedSlots: {'list-header': header},
});
const Select = mountDefault(
{},
{
scopedSlots: { "list-header": header }
}
);
Select.vm.open = true;
await Select.vm.$nextTick();
expect(Object.keys(header.mock.calls[0][0])).toEqual([
'search', 'loading', 'searching', 'filteredOptions',
"search",
"loading",
"searching",
"filteredOptions"
]);
});
test('list-footer slot props', async () => {
test("list-footer slot props", async () => {
const footer = jest.fn();
const Select = mountDefault({}, {
scopedSlots: {'list-footer': footer},
});
const Select = mountDefault(
{},
{
scopedSlots: { "list-footer": footer }
}
);
Select.vm.open = true;
await Select.vm.$nextTick();
expect(Object.keys(footer.mock.calls[0][0])).toEqual([
'search', 'loading', 'searching', 'filteredOptions',
"search",
"loading",
"searching",
"filteredOptions"
]);
});
});
+11 -14
View File
@@ -2,12 +2,11 @@ import {
mountDefault,
searchSubmit,
selectTag,
selectWithProps,
} from '../helpers';
import Select from '../../src/components/Select';
selectWithProps
} from "../helpers";
import Select from "../../src/components/Select";
describe("When Tagging Is Enabled", () => {
it("can determine if a given option string already exists", () => {
const Select = selectWithProps({ taggable: true, options: ["one", "two"] });
expect(Select.vm.optionExists("one")).toEqual(true);
@@ -20,8 +19,8 @@ describe("When Tagging Is Enabled", () => {
options: [{ label: "one" }, { label: "two" }]
});
expect(Select.vm.optionExists({label: "one"})).toEqual(true);
expect(Select.vm.optionExists({label: "three"})).toEqual(false);
expect(Select.vm.optionExists({ label: "one" })).toEqual(true);
expect(Select.vm.optionExists({ label: "three" })).toEqual(false);
});
it("can determine if a given option object already exists when using custom labels", () => {
@@ -31,7 +30,7 @@ describe("When Tagging Is Enabled", () => {
label: "foo"
});
const createOption = (text) => Select.vm.createOption(text);
const createOption = text => Select.vm.createOption(text);
expect(Select.vm.optionExists(createOption("one"))).toEqual(true);
expect(Select.vm.optionExists(createOption("three"))).toEqual(false);
@@ -71,9 +70,7 @@ describe("When Tagging Is Enabled", () => {
await selectTag(Select, "two");
expect(Select.vm.selectedValue).toEqual([
{ label: "two" }
]);
expect(Select.vm.selectedValue).toEqual([{ label: "two" }]);
});
it("should add a freshly created option/tag to the options list when pushTags is true", async () => {
@@ -236,11 +233,11 @@ describe("When Tagging Is Enabled", () => {
multiple: true,
options: [{ label: "two" }]
});
const spy = jest.spyOn(Select.vm, 'select');
const spy = jest.spyOn(Select.vm, "select");
await selectTag(Select, "one");
expect(Select.vm.selectedValue).toEqual([{ label: "one" }]);
expect(spy).lastCalledWith({label: 'one'});
expect(spy).lastCalledWith({ label: "one" });
expect(Select.vm.search).toEqual("");
await selectTag(Select, "one");
@@ -258,6 +255,6 @@ describe("When Tagging Is Enabled", () => {
Select.find({ ref: "search" }).trigger("keydown.tab");
await Select.vm.$nextTick();
expect(Select.vm.selectedValue).toEqual(['one']);
})
expect(Select.vm.selectedValue).toEqual(["one"]);
});
});
+12
View File
@@ -0,0 +1,12 @@
import { shallowMount } from "@vue/test-utils";
import HelloWorld from "@/components/HelloWorld.vue";
describe("HelloWorld.vue", () => {
it("renders props.msg when passed", () => {
const msg = "new message";
const wrapper = shallowMount(HelloWorld, {
props: { msg }
});
expect(wrapper.text()).toMatch(msg);
});
});
+9 -8
View File
@@ -1,14 +1,15 @@
import sortAndStringify from '../../../src/utility/sortAndStringify';
import sortAndStringify from "../../../src/utility/sortAndStringify";
test('it will stringify an object', () => {
expect(sortAndStringify({hello: 'world'})).toEqual('{"hello":"world"}');
test("it will stringify an object", () => {
expect(sortAndStringify({ hello: "world" })).toEqual('{"hello":"world"}');
});
test('it will sort attributes alphabetically', () => {
expect(sortAndStringify({b: 'b', a: 'a'})).toEqual('{"a":"a","b":"b"}');
test("it will sort attributes alphabetically", () => {
expect(sortAndStringify({ b: "b", a: "a" })).toEqual('{"a":"a","b":"b"}');
});
test('comparing two objects with unsorted keys', () => {
expect(sortAndStringify({b: 'b', a: 'a'}))
.toEqual(sortAndStringify({a: 'a', b: 'b'}))
test("comparing two objects with unsorted keys", () => {
expect(sortAndStringify({ b: "b", a: "a" })).toEqual(
sortAndStringify({ a: "a", b: "b" })
);
});
+2 -2
View File
@@ -1,5 +1,5 @@
import uniqueId from '../../../src/utility/uniqueId';
import uniqueId from "../../../src/utility/uniqueId";
test('it generates a unique number', () => {
test("it generates a unique number", () => {
expect(uniqueId()).not.toEqual(uniqueId());
});