diff --git a/.browserslistrc b/.browserslistrc new file mode 100644 index 0000000..214388f --- /dev/null +++ b/.browserslistrc @@ -0,0 +1,3 @@ +> 1% +last 2 versions +not dead diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..a713310 --- /dev/null +++ b/.eslintrc.js @@ -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 + } + } + ] +}; diff --git a/.gitignore b/.gitignore index 0c24238..4ec8281 100644 --- a/.gitignore +++ b/.gitignore @@ -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? diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000..397abca --- /dev/null +++ b/babel.config.js @@ -0,0 +1,3 @@ +module.exports = { + presets: ["@vue/cli-plugin-babel/preset"] +}; diff --git a/cypress.json b/cypress.json new file mode 100644 index 0000000..470c720 --- /dev/null +++ b/cypress.json @@ -0,0 +1,3 @@ +{ + "pluginsFile": "tests/e2e/plugins/index.js" +} diff --git a/docs/.env b/docs/.env new file mode 100644 index 0000000..eb0fb4c --- /dev/null +++ b/docs/.env @@ -0,0 +1 @@ +GITHUB_TOKEN=cc0a5db007fd27e736b9c2b23d2b626da44b4bbc diff --git a/package.json b/package.json index 2620c19..319677a 100644 --- a/package.json +++ b/package.json @@ -1,39 +1,49 @@ { "name": "vue-select", "version": "3.11.2", - "description": "Everything you wish the HTML 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" } } diff --git a/postcss.config.js b/postcss.config.js index 6bdfca6..df0a238 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -1,8 +1,8 @@ module.exports = { plugins: [ - require('autoprefixer'), - require('cssnano')({ - preset: 'default', - }), - ], + require("autoprefixer"), + require("cssnano")({ + preset: "default" + }) + ] }; diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..df36fcf Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..4123528 --- /dev/null +++ b/public/index.html @@ -0,0 +1,17 @@ + + + + + + + + <%= htmlWebpackPlugin.options.title %> + + + +
+ + + diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..4a4586e --- /dev/null +++ b/src/App.vue @@ -0,0 +1,26 @@ + + + + + diff --git a/src/assets/logo.png b/src/assets/logo.png new file mode 100644 index 0000000..f3d2503 Binary files /dev/null and b/src/assets/logo.png differ diff --git a/src/components/Deselect.vue b/src/components/Deselect.vue index ff84d84..1c4dacf 100644 --- a/src/components/Deselect.vue +++ b/src/components/Deselect.vue @@ -1,5 +1,7 @@ diff --git a/src/components/HelloWorld.vue b/src/components/HelloWorld.vue new file mode 100644 index 0000000..c2ccd40 --- /dev/null +++ b/src/components/HelloWorld.vue @@ -0,0 +1,130 @@ + + + + + + diff --git a/src/components/OpenIndicator.vue b/src/components/OpenIndicator.vue index 163f93e..ae1e7d6 100644 --- a/src/components/OpenIndicator.vue +++ b/src/components/OpenIndicator.vue @@ -1,5 +1,7 @@ diff --git a/src/components/Select.vue b/src/components/Select.vue index 0eede03..7a24694 100644 --- a/src/components/Select.vue +++ b/src/components/Select.vue @@ -1,1192 +1,1150 @@ - - - diff --git a/src/components/childComponents.js b/src/components/childComponents.js index ab01201..41ab96d 100644 --- a/src/components/childComponents.js +++ b/src/components/childComponents.js @@ -1,7 +1,7 @@ -import Deselect from './Deselect'; -import OpenIndicator from './OpenIndicator'; +import Deselect from "./Deselect"; +import OpenIndicator from "./OpenIndicator"; export default { Deselect, OpenIndicator -} +}; diff --git a/src/directives/appendToBody.js b/src/directives/appendToBody.js index 3c44567..43e177d 100644 --- a/src/directives/appendToBody.js +++ b/src/directives/appendToBody.js @@ -1,27 +1,32 @@ export default { - inserted (el, bindings, {context}) { - if (context.appendToBody) { - 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', - }); - - document.body.appendChild(el); - } - }, + inserted(el, bindings, { context }) { + if (context.appendToBody) { + 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" + }); - unbind (el, bindings, {context}) { - if (context.appendToBody) { - if (el.unbindPosition && typeof el.unbindPosition === 'function') { - el.unbindPosition(); - } - if (el.parentNode) { - el.parentNode.removeChild(el); - } - } - }, -} + document.body.appendChild(el); + } + }, + + unbind(el, bindings, { context }) { + if (context.appendToBody) { + if (el.unbindPosition && typeof el.unbindPosition === "function") { + el.unbindPosition(); + } + if (el.parentNode) { + el.parentNode.removeChild(el); + } + } + } +}; diff --git a/src/index.js b/src/index.js index c92e4d5..8a8e78f 100644 --- a/src/index.js +++ b/src/index.js @@ -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 }; diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..b670de8 --- /dev/null +++ b/src/main.js @@ -0,0 +1,4 @@ +import { createApp } from "vue"; +import App from "./App.vue"; + +createApp(App).mount("#app"); diff --git a/src/mixins/ajax.js b/src/mixins/ajax.js index 80d1950..de52a7d 100644 --- a/src/mixins/ajax.js +++ b/src/mixins/ajax.js @@ -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); - }, - }, + } + } }; diff --git a/src/mixins/index.js b/src/mixins/index.js index 991ad77..8d26a8e 100644 --- a/src/mixins/index.js +++ b/src/mixins/index.js @@ -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 }; diff --git a/src/mixins/pointerScroll.js b/src/mixins/pointerScroll.js index 23d80ee..c3512f7 100644 --- a/src/mixins/pointerScroll.js +++ b/src/mixins/pointerScroll.js @@ -11,7 +11,7 @@ export default { if (this.autoscroll) { this.maybeAdjustScroll(); } - }, + } }, methods: { diff --git a/src/mixins/typeAheadPointer.js b/src/mixins/typeAheadPointer.js index b878676..142481a 100644 --- a/src/mixins/typeAheadPointer.js +++ b/src/mixins/typeAheadPointer.js @@ -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 { } } } -} +}; diff --git a/src/utility/sortAndStringify.js b/src/utility/sortAndStringify.js index 14a619d..2b8b54e 100644 --- a/src/utility/sortAndStringify.js +++ b/src/utility/sortAndStringify.js @@ -5,9 +5,11 @@ function sortAndStringify(sortable) { const ordered = {}; - Object.keys(sortable).sort().forEach(key => { - ordered[key] = sortable[key]; - }); + Object.keys(sortable) + .sort() + .forEach(key => { + ordered[key] = sortable[key]; + }); return JSON.stringify(ordered); } diff --git a/tests/e2e/.eslintrc.js b/tests/e2e/.eslintrc.js new file mode 100644 index 0000000..053c39f --- /dev/null +++ b/tests/e2e/.eslintrc.js @@ -0,0 +1,10 @@ +module.exports = { + plugins: ["cypress"], + env: { + mocha: true, + "cypress/globals": true + }, + rules: { + strict: "off" + } +}; diff --git a/tests/e2e/plugins/index.js b/tests/e2e/plugins/index.js new file mode 100644 index 0000000..6bc6520 --- /dev/null +++ b/tests/e2e/plugins/index.js @@ -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" + }); +}; diff --git a/tests/e2e/specs/test.js b/tests/e2e/specs/test.js new file mode 100644 index 0000000..1d74984 --- /dev/null +++ b/tests/e2e/specs/test.js @@ -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"); + }); +}); diff --git a/tests/e2e/support/commands.js b/tests/e2e/support/commands.js new file mode 100644 index 0000000..c1f5a77 --- /dev/null +++ b/tests/e2e/support/commands.js @@ -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) => { ... }) diff --git a/tests/e2e/support/index.js b/tests/e2e/support/index.js new file mode 100644 index 0000000..d076cec --- /dev/null +++ b/tests/e2e/support/index.js @@ -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') diff --git a/tests/helpers.js b/tests/helpers.js index e055155..2946a2d 100755 --- a/tests/helpers.js +++ b/tests/helpers.js @@ -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}, - ...options - }), - components: {VueSelect}, + render: createEl => + createEl("vue-select", { + ref: "select", + props: { options: ["one", "two", "three"], ...props }, + ...options + }), + components: { VueSelect } }).$mount().$refs.select; }; diff --git a/tests/unit/.eslintrc.js b/tests/unit/.eslintrc.js index 4e51c63..fe40a2a 100755 --- a/tests/unit/.eslintrc.js +++ b/tests/unit/.eslintrc.js @@ -3,6 +3,6 @@ module.exports = { jest: true }, rules: { - 'import/no-extraneous-dependencies': 'off' + "import/no-extraneous-dependencies": "off" } -} \ No newline at end of file +}; diff --git a/tests/unit/Ajax.spec.js b/tests/unit/Ajax.spec.js index cfa9b79..4a36b5c 100755 --- a/tests/unit/Ajax.spec.js +++ b/tests/unit/Ajax.spec.js @@ -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(); diff --git a/tests/unit/Components.spec.js b/tests/unit/Components.spec.js index 3c43710..0cace70 100644 --- a/tests/unit/Components.spec.js +++ b/tests/unit/Components.spec.js @@ -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(); }); - }); diff --git a/tests/unit/Deselecting.spec.js b/tests/unit/Deselecting.spec.js index 011acee..c89ec2a 100755 --- a/tests/unit/Deselecting.spec.js +++ b/tests/unit/Deselecting.spec.js @@ -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"); diff --git a/tests/unit/Dropdown.spec.js b/tests/unit/Dropdown.spec.js index c73b2e1..537a297 100755 --- a/tests/unit/Dropdown.spec.js +++ b/tests/unit/Dropdown.spec.js @@ -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(); }); - }); diff --git a/tests/unit/Filtering.spec.js b/tests/unit/Filtering.spec.js index 05bff8b..462ad8f 100755 --- a/tests/unit/Filtering.spec.js +++ b/tests/unit/Filtering.spec.js @@ -6,11 +6,11 @@ describe("Filtering Options", () => { const Select = shallowMount(VueSelect, { 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", () => { diff --git a/tests/unit/Keydown.spec.js b/tests/unit/Keydown.spec.js index 3a4e865..3a11309 100644 --- a/tests/unit/Keydown.spec.js +++ b/tests/unit/Keydown.spec.js @@ -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); }); - }); - }); diff --git a/tests/unit/Labels.spec.js b/tests/unit/Labels.spec.js index 1cbe8ef..1edd062 100755 --- a/tests/unit/Labels.spec.js +++ b/tests/unit/Labels.spec.js @@ -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': '{{ props.name }}', - 'selected-option': '{{ props.name }}', - }, + option: '{{ props.name }}', + "selected-option": '{{ props.name }}' + } }); - 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(); }); }); }); diff --git a/tests/unit/OptionComparator.spec.js b/tests/unit/OptionComparator.spec.js index 0e4da48..aade813 100644 --- a/tests/unit/OptionComparator.spec.js +++ b/tests/unit/OptionComparator.spec.js @@ -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(); }); - }); diff --git a/tests/unit/OptionKey.spec.js b/tests/unit/OptionKey.spec.js index cbe40b2..17ec7b8 100644 --- a/tests/unit/OptionKey.spec.js +++ b/tests/unit/OptionKey.spec.js @@ -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" + }); }); }); diff --git a/tests/unit/ReactiveOptions.spec.js b/tests/unit/ReactiveOptions.spec.js index f28320f..6753554 100755 --- a/tests/unit/ReactiveOptions.spec.js +++ b/tests/unit/ReactiveOptions.spec.js @@ -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"); }); }); diff --git a/tests/unit/Reduce.spec.js b/tests/unit/Reduce.spec.js index de1a24d..bba97f7 100755 --- a/tests/unit/Reduce.spec.js +++ b/tests/unit/Reduce.spec.js @@ -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,22 +37,24 @@ describe("When reduce prop is defined", () => { ).toEqual(true); }); - 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'}], - }, - }); - - Select.vm.select({id: 'foo', label: 'FooBar'}); - - expect(Select.vm.isOptionSelected({ - id: 'foo', - label: 'This is FooBar', - })).toEqual(true); + 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" }] + } }); + Select.vm.select({ id: "foo", label: "FooBar" }); + + expect( + Select.vm.isOptionSelected({ + id: "foo", + label: "This is FooBar" + }) + ).toEqual(true); + }); + it("can accept an array of objects and pre-selected values (multiple)", () => { const Select = shallowMount(VueSelect, { propsData: { @@ -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: ` { :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); }); }); diff --git a/tests/unit/Selectable.spec.js b/tests/unit/Selectable.spec.js index c75c086..463b342 100644 --- a/tests/unit/Selectable.spec.js +++ b/tests/unit/Selectable.spec.js @@ -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); - }) -}) + }); +}); diff --git a/tests/unit/Selecting.spec.js b/tests/unit/Selecting.spec.js index a9eb33c..2f9327e 100755 --- a/tests/unit/Selecting.spec.js +++ b/tests/unit/Selecting.spec.js @@ -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"); diff --git a/tests/unit/Slots.spec.js b/tests/unit/Slots.spec.js index 955829b..09c8157 100644 --- a/tests/unit/Slots.spec.js +++ b/tests/unit/Slots.spec.js @@ -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': `{{ option.label }}`, - }, - }); + "selected-option-container": `{{ option.label }}` + } + } + ); - 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': `{{ option.label }}`, - }, - }); + "selected-option": `{{ option.label }}` + } + } + ); - 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', - () => { - const Select = mountDefault( - {value: 'one'}, - { - scopedSlots: { - 'selected-option': `{{ option.label }}`, - }, - }); + it("opens the dropdown when clicking an option in selected-option slot", () => { + const Select = mountDefault( + { value: "one" }, + { + scopedSlots: { + "selected-option": `{{ option.label }}` + } + } + ); - Select.find('.my-option').trigger('mousedown'); - expect(Select.vm.open).toEqual(true); - }); + 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 () => { - const Select = mountDefault( - {value: 'one'}, - { - scopedSlots: { - 'option': `{{ option.label }}`, - }, - }); + it("receives an option object to the option slot in the dropdown menu", async () => { + const Select = mountDefault( + { value: "one" }, + { + scopedSlots: { + option: `{{ option.label }}` + } + } + ); - Select.vm.open = true; - await Select.vm.$nextTick(); + 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" ]); }); }); diff --git a/tests/unit/Tagging.spec.js b/tests/unit/Tagging.spec.js index c5c11dc..b54503b 100755 --- a/tests/unit/Tagging.spec.js +++ b/tests/unit/Tagging.spec.js @@ -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"]); + }); }); diff --git a/tests/unit/example.spec.js b/tests/unit/example.spec.js new file mode 100644 index 0000000..8bab2e6 --- /dev/null +++ b/tests/unit/example.spec.js @@ -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); + }); +}); diff --git a/tests/unit/utility/sortAndStringify.spec.js b/tests/unit/utility/sortAndStringify.spec.js index 414e2a0..bfc948a 100644 --- a/tests/unit/utility/sortAndStringify.spec.js +++ b/tests/unit/utility/sortAndStringify.spec.js @@ -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" }) + ); }); diff --git a/tests/unit/utility/uniqueId.spec.js b/tests/unit/utility/uniqueId.spec.js index c425a7c..def756f 100644 --- a/tests/unit/utility/uniqueId.spec.js +++ b/tests/unit/utility/uniqueId.spec.js @@ -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()); });