UNPKG

quasar

Version:

Build high-performance VueJS user interfaces (SPA, PWA, SSR, Mobile and Desktop) in record time

1,724 lines (1,607 loc) 57.9 kB
/* eslint-disable no-unused-expressions */ import { vModelAdapter } from '@quasar/quasar-app-extension-testing-e2e-cypress' import { h, ref } from 'vue' import QSelect from '../QSelect.js' function getHostElement (extendedSelector = '') { return cy.get(`.q-select ${ extendedSelector }`) } function mountQSelect (options = {}) { if (!options.props?.modelValue) { options.props = { modelValue: null, ...options.props ?? {} } } return cy.mount(QSelect, options) } // QSelect does not set the `data-cy` attribute on the root element, but on the `.q-field__native` element // This means we cannot use data-cy everywhere, but instead use a custom class `select-root` for this purpose describe('QSelect API', () => { describe('Props', () => { describe('Category: behavior', () => { describe('(prop): fill-input', () => { it.skip(' ', () => { // }) }) describe('(prop): new-value-mode', () => { it.skip(' ', () => { // }) }) describe('(prop): autocomplete', () => { it.skip(' ', () => { // }) }) describe('(prop): transition-show', () => { it.skip(' ', () => { // }) }) describe('(prop): transition-hide', () => { it.skip(' ', () => { // }) }) describe('(prop): transition-duration', () => { it.skip(' ', () => { // }) }) describe('(prop): behavior', () => { it.skip(' ', () => { // }) }) }) describe('Category: content', () => { describe('(prop): dropdown-icon', () => { it('should use the dropdown-icon supplied', () => { const icon = 'map' mountQSelect({ props: { dropdownIcon: icon } }) getHostElement() .get(`div:contains(${ icon })`) .should('exist') }) }) describe('(prop): use-input', () => { it('should render an input inside the select', () => { mountQSelect({ props: { useInput: true } }) getHostElement() .get('input') .should('exist') }) it('should render an input, but it shouldn\'t be visible', () => { mountQSelect() getHostElement() .get('input') .should('not.be.visible') }) it.skip('should not render an input by default', () => { // Native input is now always rendered, due to having a target for autocomplete // Refer to commit: https://github.com/quasarframework/quasar/commit/21a3af0dfe01bac0da617737562b599edee397a2 }) }) describe('(prop): input-debounce', () => { it('should use an input-debounce of 500ms by default', () => { const fn = cy.stub() const text = 'Hello there' mountQSelect({ props: { useInput: true, onFilter: fn } }) getHostElement() .get('input') .type(text) .then(() => { expect(fn).not.to.be.calledWith(text) }) .wait(500) .then(() => { expect(fn).to.be.calledWith(text) }) }) it('should use a custom input-debounce', () => { const fn = cy.stub() const text = 'Hello there' mountQSelect({ props: { useInput: true, onFilter: fn, inputDebounce: 800 } }) getHostElement() .get('input') .type(text) .wait(500) .then(() => { expect(fn).not.to.be.calledWith(text) }) .wait(300) .then(() => { expect(fn).to.be.calledWith(text) }) }) }) }) describe('Category: content|behavior', () => { describe('(prop): hide-dropdown-icon', () => { it('should show the dropdown-icon when this property is not supplied', () => { mountQSelect() getHostElement() .get('.q-icon') .should('exist') }) it('should hide the dropdown-icon when this property is true', () => { mountQSelect({ props: { hideDropdownIcon: true } }) getHostElement() .get('.q-icon') .should('not.exist') }) }) }) describe('Category: general', () => { describe('(prop): tabindex', () => { it('should have a default tabindex of 0', () => { mountQSelect() getHostElement('[tabindex="0"]') .should('exist') }) it('should set the tabindex to the supplied value', () => { const tabindex = 2 mountQSelect({ props: { tabindex } }) getHostElement(`[tabindex="${ tabindex }"]`) .should('exist') getHostElement('[tabindex="0"]') .should('not.exist') }) }) }) describe('Category: model', () => { describe('(prop): model-value', () => { it('should have the option selected passed in the model-value', () => { const modelValue = 'Option 1' mountQSelect({ props: { modelValue, options: [ 'Option 1', 'Option 2', 'Option 3' ] } }) getHostElement() .should('include.text', modelValue) }) }) describe('(prop): emit-value', () => { it('should emit the value under the value key, if options are objects', () => { const fn = cy.stub() mountQSelect({ props: { emitValue: true, 'onUpdate:modelValue': fn, options: [ { label: 'Option 1', value: 1 }, { label: 'Option 2', value: 2 } ] } }) getHostElement() .click() cy.get('.q-menu') .contains('Option 1') .click() .then(() => { expect(fn).to.have.been.calledWith(1) }) }) it('should emit the whole object by default if options are objects', () => { const fn = cy.stub() const options = [ { label: 'Option 1', value: 1 }, { label: 'Option 2', value: 2 } ] mountQSelect({ props: { 'onUpdate:modelValue': fn, options } }) getHostElement() .click() cy.get('.q-menu') .contains('Option 1') .click() .then(() => { expect(fn).to.have.been.calledWith(options[ 0 ]) }) }) }) }) describe('Category: model|selection', () => { describe('(prop): multiple', () => { it('should select only one option by default', () => { const options = [ 'Option 1', 'Option 2' ] const model = ref(null) mountQSelect({ props: { ...vModelAdapter(model), options } }) getHostElement().click() cy.withinSelectMenu(() => { cy.contains('Option 1') .click() .then(() => { expect(model.value).to.equal(options[ 0 ]) }) }) getHostElement().click() cy.withinSelectMenu(() => { cy.contains('Option 2') .click() .then(() => { expect(model.value).to.equal(options[ 1 ]) }) }) }) it('should select multiple options if multiple is true', () => { const options = [ 'Option 1', 'Option 2' ] const model = ref([]) mountQSelect({ props: { ...vModelAdapter(model), multiple: true, options } }) getHostElement().click() cy.withinSelectMenu({ persistent: true, fn: () => { cy.contains('Option 1') .click() .then(() => { expect(model.value).to.eql([ options[ 0 ] ]) }) cy.contains('Option 2') .click() .then(() => { expect(model.value).to.eql(options) }) } }) }) }) }) describe('Category: options', () => { describe('(prop): options', () => { it('should show each option when opening the dropdown', () => { const options = [ 'Option 1', 'Option 2', 'Option 3', 'Option 4' ] mountQSelect({ props: { options } }) getHostElement() .click() cy.get('.q-menu') .children() .should('contain', options[ 0 ]) .and('contain', options[ 1 ]) .and('contain', options[ 2 ]) .and('contain', options[ 3 ]) }) }) describe('(prop): option-value', () => { it('should use the value key as option-value by default', () => { const options = [ { label: 'Option one', value: 1 }, { label: 'Option two', value: 2 } ] const model = ref(null) mountQSelect({ props: { ...vModelAdapter(model), options, emitValue: true } }) getHostElement() .click() cy.get('.q-menu') .contains(options[ 0 ].label) .click() .then(() => { expect(model.value).to.equal(options[ 0 ].value) }) }) it('should use a custom key supplied by option-value', () => { const options = [ { label: 'Option one', test: 1 }, { label: 'Option two', test: 2 } ] const model = ref(null) mountQSelect({ props: { ...vModelAdapter(model), options, emitValue: true, optionValue: 'test' } }) getHostElement() .click() cy.get('.q-menu') .contains(options[ 0 ].label) .click() .then(() => { expect(model.value).to.equal(options[ 0 ].test) }) }) it('should accept a function as option-value', () => { const options = [ { label: 'Option one', test: 1 }, { label: 'Option two', test: 2 } ] const model = ref(null) mountQSelect({ props: { ...vModelAdapter(model), options, emitValue: true, optionValue: (val) => val.test } }) getHostElement() .click() cy.get('.q-menu') .contains(options[ 0 ].label) .click() .then(() => { expect(model.value).to.equal(options[ 0 ].test) }) }) }) describe('(prop): option-label', () => { it('should use the "label" key by default as option-label', () => { const options = [ { label: 'Option one', value: 1 }, { label: 'Option two', value: 2 } ] mountQSelect({ props: { options } }) getHostElement() .click() cy.get('.q-menu') .children() .should('contain', options[ 0 ].label) .and('contain', options[ 1 ].label) }) it('should use the key supplied by option-label', () => { const options = [ { test: 'Option one', value: 1 }, { test: 'Option two', value: 2 } ] mountQSelect({ props: { options, optionLabel: 'test' } }) getHostElement() .click() cy.get('.q-menu') .children() .should('contain', options[ 0 ].test) .and('contain', options[ 1 ].test) }) it('should accept a function as option-label', () => { const options = [ { test: 'Option one', value: 1 }, { test: 'Option two', value: 2 } ] mountQSelect({ props: { options, optionLabel: (item) => (item === null ? 'Null' : item.test) } }) getHostElement() .click() cy.get('.q-menu') .children() .should('contain', options[ 0 ].test) .and('contain', options[ 1 ].test) }) }) describe('(prop): option-disable', () => { it('should use the "disable" key by default as option-disable', () => { const options = [ { label: 'Option one', value: 1, disable: true }, { label: 'Option two', value: 2, disable: true } ] mountQSelect({ props: { options } }) getHostElement() .click() cy.get('.q-menu') .get('[role="option"][aria-disabled="true"]') .should('have.length', 2) }) it('should use the key supplied by option-disable', () => { const options = [ { label: 'Option one', value: 1, test: true }, { label: 'Option two', value: 2, disable: true } ] mountQSelect({ props: { options, optionDisable: 'test' } }) getHostElement() .click() cy.get('.q-menu') .get('[role="option"][aria-disabled="true"]') .should('have.length', 1) .should('have.text', options[ 0 ].label) }) it('should accept a function as option-disable', () => { const options = [ { label: 'Option one', value: 1, test: true }, { label: 'Option two', value: 2, disable: true } ] mountQSelect({ props: { options, optionDisable: (item) => (item === null ? true : item.test) } }) getHostElement() .click() cy.get('.q-menu') .get('[role="option"][aria-disabled="true"]') .should('have.length', 1) .should('have.text', options[ 0 ].label) }) }) describe('(prop): options-dense', () => { it('should show options list dense', () => { const options = [ 'Option 1', 'Option 2', 'Option 3', 'Option 4' ] mountQSelect({ props: { options, optionsDense: true } }) getHostElement() .click() cy.get('.q-menu') .get('.q-item') .should('have.class', 'q-item--dense') }) }) describe('(prop): options-dark', () => { it('should show options list in dark mode', () => { const options = [ 'Option 1', 'Option 2', 'Option 3', 'Option 4' ] mountQSelect({ props: { options, optionsDark: true } }) getHostElement() .click() cy.get('.q-menu') .get('.q-item') .should('have.class', 'q-item--dark') }) }) describe('(prop): options-selected-class', () => { it('should have text-{color} applied as selected by default', () => { const options = [ 'Option 1', 'Option 2', 'Option 3', 'Option 4' ] mountQSelect({ props: { options, modelValue: 'Option 1', color: 'orange' } }) getHostElement() .click() cy.get('.q-menu') .contains('[role="option"]', options[ 0 ]) .should('have.class', 'text-orange') }) it('should not have default active class when passed option is empty', () => { const options = [ 'Option 1', 'Option 2', 'Option 3', 'Option 4' ] mountQSelect({ props: { options, modelValue: 'Option 1', optionsSelectedClass: '', color: 'orange' } }) getHostElement() .click() cy.get('.q-menu') .contains('[role="option"]', options[ 0 ]) .should('not.have.class', 'text-orange') }) it('should have class name supplied by options-selected-class on active item', () => { const options = [ 'Option 1', 'Option 2', 'Option 3', 'Option 4' ] mountQSelect({ props: { options, modelValue: 'Option 1', optionsSelectedClass: 'test-class', color: 'orange' } }) getHostElement() .click() cy.get('.q-menu') .contains('[role="option"]', options[ 0 ]) .should('not.have.class', 'text-orange') .should('have.class', 'test-class') cy.get('.q-menu') .contains('[role="option"]', options[ 1 ]) .should('not.have.class', 'text-orange') .should('not.have.class', 'test-class') }) }) describe('(prop): options-html', () => { it('should not render options with html by default', () => { const options = [ '<b style="color: red">Option 1</b>', 'Option 2' ] mountQSelect({ props: { options } }) getHostElement() .click() cy.get('.q-menu') .contains('Option 1') .should('have.color', 'black') .should('not.have.css', 'font-weight', '700') }) it('should render options with html when options-html is true', () => { const options = [ '<b style="color: red">Option 1</b>', 'Option 2' ] mountQSelect({ props: { options, optionsHtml: true } }) getHostElement() .click() cy.get('.q-menu') .contains('Option 1') .should('have.color', 'red') .should('have.css', 'font-weight', '700') }) }) describe('(prop): options-cover', () => { it('should make the popup menu cover the select', (done) => { const options = [ 'Option 1', 'Option 2', 'Option 3', 'Option 4' ] mountQSelect({ props: { options, optionsCover: true } }) getHostElement() .click() .isNotActionable(done) }) it('should not make the popup menu cover the select when use-input is used', () => { const options = [ 'Option 1', 'Option 2', 'Option 3', 'Option 4' ] mountQSelect({ props: { options, optionsCover: true, useInput: true } }) getHostElement() .click() .click({ timeout: 100 }) }) }) describe('(prop): menu-shrink', () => { it('should shrink the menu', () => { const options = [ '1', '2', '3', '4' ] mountQSelect({ props: { options, menuShrink: true } }) let selectWidth = 0 getHostElement() .then(($el) => { selectWidth = $el[ 0 ].clientWidth }) .click() cy.get('.q-menu') .then(($el) => { expect($el[ 0 ].clientWidth).to.below(selectWidth) }) }) }) describe('(prop): map-options', () => { it('should display the label of the selected value instead of the value itself', () => { const options = [ { label: 'Option one', value: 1 }, { label: 'Option two', value: 2 } ] mountQSelect({ props: { options, modelValue: 1, mapOptions: true } }) getHostElement() .contains(options[ 0 ].label) }) it('should display the selected value as string by default', () => { const options = [ { label: 'Option one', value: 1 }, { label: 'Option two', value: 2 } ] mountQSelect({ props: { options, modelValue: 1 } }) getHostElement() .contains(options[ 0 ].value) }) }) }) describe('Category: position', () => { describe.skip('(prop): menu-anchor', () => { // This is a menu property which is tested in QMenu.spec.js }) describe.skip('(prop): menu-self', () => { // This is a menu property which is tested in QMenu.spec.js }) describe.skip('(prop): menu-offset', () => { // This is a menu property which is tested in QMenu.spec.js }) }) describe('Category: selection', () => { describe('(prop): display-value', () => { it('should override the default selection string', () => { const options = [ { label: 'Option one', value: 1 }, { label: 'Option two', value: 2 } ] mountQSelect({ props: { options, modelValue: 1, displayValue: 'Test' } }) getHostElement() .should('not.contain', options[ 0 ].value) .should('contain', 'Test') }) it('should not override the default selection string when using `use-chips`', () => { const options = [ { label: 'Option one', value: 1 }, { label: 'Option two', value: 2 } ] mountQSelect({ props: { options, modelValue: 1, displayValue: 'Test', useChips: true } }) getHostElement() .should('contain', options[ 0 ].value) .should('not.contain', 'Test') }) it('should not override the default selection string when using `selected` slot', () => { const options = [ { label: 'Option one', value: 1 }, { label: 'Option two', value: 2 } ] mountQSelect({ props: { options, modelValue: 1, displayValue: 'Test' }, slots: { selected: () => 'Hello there' } }) getHostElement() .should('not.contain', options[ 0 ].value) .should('not.contain', 'Test') .should('contain', 'Hello there') }) }) describe('(prop): display-value-html', () => { it('should render the selected option as html', () => { const options = [ '<b style="color: red">Option 1</b>', 'Option 2' ] mountQSelect({ props: { options, modelValue: options[ 0 ], displayValueHtml: true } }) getHostElement() .contains('Option 1') .should('have.color', 'red') .should('have.css', 'font-weight', '700') }) it('should not render the selected option as html when using `selected` slot', () => { const html = '<b style="color: red">Option 1</b>' const options = [ 'Option 1', 'Option 2' ] mountQSelect({ props: { options, modelValue: options[ 0 ], displayValueHtml: true }, slots: { selected: () => html } }) getHostElement() .contains(html) }) it('should not render the selected option as html when using `selected-item` slot', () => { const html = '<b style="color: red">Option 1</b>' const options = [ 'Option 1', 'Option 2' ] mountQSelect({ props: { options, modelValue: options[ 0 ], displayValueHtml: true }, slots: { 'selected-item': () => html } }) getHostElement() .contains(html) }) }) describe('(prop): hide-selected', () => { it('should not show the selected value', () => { const options = [ 'Option 1', 'Option 2' ] mountQSelect({ props: { options, modelValue: options[ 0 ], hideSelected: true } }) getHostElement() .should('not.contain', options[ 0 ]) }) it('should set the value on the underlying input when using hide-selected', () => { // Todo: it its not really clear from the docs that you need to use `useInput` and `fillInput` together with this prop to achieve this const options = [ 'Option 1', 'Option 2' ] mountQSelect({ props: { options, modelValue: options[ 0 ], hideSelected: true, fillInput: true, useInput: true } }) getHostElement() .get('input') .should('have.value', options[ 0 ]) }) }) describe('(prop): max-values', () => { it('should allow a maximum number of selections', () => { const max = 3 const options = [ '1', '2', '3', '4', '5' ] const model = ref([]) mountQSelect({ props: { ...vModelAdapter(model), options, maxValues: max, multiple: true } }) getHostElement() .click() cy.get('.q-menu') .get('[role="option"]') .click({ multiple: true }) .then(() => { expect(model.value.length).to.equal(max) }) }) }) describe('(prop): use-chips', () => { it('should use QChips to show the selected value', () => { const options = [ 'Option 1', 'Option 2' ] mountQSelect({ props: { options, modelValue: options[ 0 ], useChips: true } }) getHostElement() .get('.q-chip') .should('contain', options[ 0 ]) }) }) }) describe('Category: style', () => { describe('(prop): popup-content-class', () => { it('should apply the class to the popup element', () => { const className = 'test-class' mountQSelect({ props: { options: [ '1', '2 ' ], popupContentClass: className } }) getHostElement() .click() cy.get('.q-menu') .should('have.class', className) }) }) describe('(prop): popup-content-style', () => { it('should apply the style definitions to the popup element', () => { const style = 'background: red;' mountQSelect({ props: { options: [ '1', '2 ' ], popupContentStyle: style } }) getHostElement() .click() cy.get('.q-menu') .should('have.backgroundColor', 'red') }) }) describe('(prop): input-class', () => { it('should apply a class to the input element when using `useInput`', () => { const className = 'test-class' mountQSelect({ props: { useInput: true, inputClass: className } }) getHostElement() .get('input') .should('have.class', className) }) }) describe('(prop): input-style', () => { it('should apply a style to the input element when using `useInput`', () => { const style = 'font-size: 30px' mountQSelect({ props: { useInput: true, inputStyle: style } }) getHostElement() .get('input') .should('have.css', 'font-size', '30px') }) }) }) }) describe('Slots', () => { describe('(slot): selected', () => { it('should display when something is selected', () => { const selectedString = 'Test slot selected' const options = [ 'Option 1', 'Option 2' ] mountQSelect({ props: { options, modelValue: options[ 0 ] }, slots: { selected: () => selectedString } }) getHostElement() .should('contain', selectedString) }) }) describe('(slot): loading', () => { it('should display when element is loading', () => { const loadingString = 'Test slot loading' mountQSelect({ props: { loading: true }, slots: { loading: () => loadingString } }) getHostElement() .should('contain', loadingString) }) it('should not display when element is loading', () => { const loadingString = 'Test slot loading' mountQSelect({ props: { loading: false }, slots: { loading: () => loadingString } }) getHostElement() .should('not.contain', loadingString) }) }) describe('(slot): before-options', () => { it('should display the slot content before the options', () => { mountQSelect({ props: { options: [ '1', '2', '3' ] }, slots: { 'before-options': () => h('div', { class: 'dummyClass' }, 'Hello') } }) getHostElement() .click() cy.get('.q-menu') .children().first() .should('have.class', 'dummyClass') }) }) describe('(slot): after-options', () => { it('should display the slot content after the options', () => { mountQSelect({ props: { options: [ '1', '2', '3' ] }, slots: { 'after-options': () => h('div', { class: 'dummyClass' }, 'Hello') } }) getHostElement() .click() cy.get('.q-menu') .children().last() .should('have.class', 'dummyClass') }) }) describe('(slot): no-option', () => { it('should display the slot content when there are no options', () => { const compareString = 'No options :(' mountQSelect({ props: { options: [ ] }, slots: { 'no-option': () => compareString } }) getHostElement() .click() cy.get('.q-menu') .should('contain', compareString) }) it('should pass the inputValue to the slot scope', () => { const compareString = 'No options :(' mountQSelect({ props: { options: [ ], useInput: true }, slots: { 'no-option': (scope) => compareString + scope.inputValue } }) getHostElement() .click() .type('Hello') cy.get('.q-menu') .should('contain', compareString + 'Hello') }) it('should not display the slot content when there are options', () => { const compareString = 'No options :(' mountQSelect({ props: { options: [ '1', '2', '3' ] }, slots: { 'no-option': () => compareString } }) getHostElement() .click() cy.get('.q-menu') .should('not.contain', compareString) }) }) describe('(slot): selected-item', () => { it('should override the default selection slot', () => { const options = [ { label: 'Option one', value: 1 }, { label: 'Option two', value: 2 } ] mountQSelect({ props: { options, modelValue: 1 }, slots: { 'selected-item': () => 'Test' } }) getHostElement() .should('not.contain', options[ 0 ].value) .should('contain', 'Test') }) it('should pass the selected option index to the slot scope', () => { const options = [ { label: 'Option one', value: 1 }, { label: 'Option two', value: 2 } ] mountQSelect({ props: { options, modelValue: 1 }, slots: { 'selected-item': (scope) => 'Test' + scope.index } }) getHostElement() .should('contain', 'Test0') }) it('should pass the selected option value to the slot scope', () => { const options = [ { label: 'Option one', value: 1 }, { label: 'Option two', value: 2 } ] mountQSelect({ props: { options, modelValue: 1 }, slots: { 'selected-item': (scope) => 'Test' + scope.opt } }) getHostElement() .should('contain', 'Test1') }) it('should pass a removeAtIndex function to the slot scope', () => { const options = [ { label: 'Option one', value: 1 }, { label: 'Option two', value: 2 } ] const model = ref(1) mountQSelect({ props: { ...vModelAdapter(model), options }, slots: { 'selected-item': (scope) => h('button', { class: 'remove', onClick: () => scope.removeAtIndex(scope.index) }, 'Remove') } }) getHostElement() .get('button.remove') .click() getHostElement() .get('button.remove') .should('not.exist') }) it('should pass a toggleOption function to the slot scope', () => { const options = [ { label: 'Option one', value: 1 }, { label: 'Option two', value: 2 } ] const model = ref(1) mountQSelect({ props: { ...vModelAdapter(model), options }, slots: { 'selected-item': (scope) => h('button', { class: 'toggle', onClick: () => scope.toggleOption(2) }, 'Toggle' + scope.opt) } }) getHostElement() .get('button.toggle') .should('contain', 'Toggle1') .click() getHostElement() .get('button.toggle') .should('contain', 'Toggle2') }) }) describe('(slot): option', () => { it('should render a list of the provided slot as options', () => { const options = [ '1', '2', '3' ] mountQSelect({ props: { options }, slots: { option: (scope) => h('div', { class: 'custom-option' }, scope.opt) } }) getHostElement() .click() cy.get('.q-menu') .get('.custom-option') .should('have.length', options.length) }) it('should have a selected property in the scope', () => { const options = [ '1', '2', '3' ] mountQSelect({ props: { modelValue: '1', options }, slots: { option: (scope) => h('div', { class: `custom-option-${ scope.selected }` }, scope.opt + scope.selected) } }) getHostElement() .click() cy.get('.q-menu') .get('.custom-option-true') .should('have.length', 1) .should('contain', options[ 0 ]) }) }) }) describe('Events', () => { describe('(event): update:model-value', () => { it('should emit event when model value changes', () => { const fn = cy.stub() mountQSelect({ props: { options: [ '1', '2', '3' ], modelValue: null, 'onUpdate:modelValue': fn } }) expect(fn).not.to.be.called getHostElement() .click() cy.get('.q-menu') .get('[role="option"]') .first() .click() .then(() => { expect(fn).to.be.calledWith('1') }) }) }) describe('(event): input-value', () => { it('should emit event when text input changes', () => { const fn = cy.stub() mountQSelect({ props: { modelValue: null, onInputValue: fn, useInput: true } }) expect(fn).not.to.be.called getHostElement() .get('input') .type('h') .then(() => { expect(fn).to.be.calledWith('h') }) }) }) describe('(event): remove', () => { it('should emit event when a selected item is removed from selection', () => { const fn = cy.stub() const model = ref([ '2', '3' ]) mountQSelect({ props: { ...vModelAdapter(model), onRemove: fn, multiple: true, options: [ '1', '2', '3' ] } }) expect(fn).not.to.be.called getHostElement() .click() cy.get('.q-menu') .get('[role="option"]') .first() .click() .then(() => { expect(fn).not.to.be.called }) cy.get('.q-menu') .get('[role="option"]') .first() .click() .then(() => { // Item is added in the previous step at the end of the array, so at index 2 expect(fn).to.be.calledWith({ index: 2, value: '1' }) }) }) }) describe('(event): add', () => { it('should emit event when an option is added to the selection', () => { const fn = cy.stub() const model = ref([ '2' ]) mountQSelect({ props: { ...vModelAdapter(model), onAdd: fn, multiple: true, options: [ '1', '2', '3' ] } }) expect(fn).not.to.be.called getHostElement() .click() cy.get('.q-menu') .get('[role="option"]') .first() .click() .then(() => { // Item is added in the previous step at the end of the array, so at index 2 expect(fn).to.be.calledWith({ index: 1, value: '1' }) }) }) }) describe('(event): new-value', () => { it('should emit event when something is typed into the input field and enter is pressed', () => { const fn = cy.stub() const model = ref([ '2' ]) mountQSelect({ props: { ...vModelAdapter(model), onNewValue: fn, multiple: true, useInput: true, hideDropdownIcon: true } }) expect(fn).not.to.be.called getHostElement() .get('input') .type('100') .then(() => { expect(fn).not.to.be.called }) .type('{enter}') .then(() => { expect(fn).to.be.calledWith('100') }) }) it('should add the value to the model when the doneFn is called', () => { const model = ref([ '2' ]) mountQSelect({ props: { ...vModelAdapter(model), onNewValue: (val, doneFn) => { doneFn(val) }, multiple: true, useInput: true, hideDropdownIcon: true } }) getHostElement() .get('input') .type('100') .type('{enter}') .then(() => { expect(model.value).includes('100') }) }) }) describe('(event): filter', () => { it('should emit event when something is typed into the input field', () => { const fn = cy.stub() mountQSelect({ props: { onFilter: fn, useInput: true, inputDebounce: 0 } }) expect(fn).not.to.be.called getHostElement() .get('input') .type('h') .then(() => { expect(fn).to.be.calledWith('h') }) }) }) describe('(event): filter-abort', () => { it('should emit event when the the filterFn has not called the doneFn yet and a new filter is requested', () => { const fn = cy.stub() const filterFn = cy.stub() mountQSelect({ props: { onFilter: filterFn, onFilterAbort: fn, useInput: true, inputDebounce: 0 } }) expect(fn).not.to.be.called getHostElement() .get('input') .click() .then(() => { expect(filterFn).to.be.calledOnce expect(fn).not.to.be.called }) .type('h') .then(() => { expect(fn).to.be.calledOnce }) }) it('should not emit event when the filter has called its doneFn', () => { const fn = cy.stub() mountQSelect({ props: { onFilter: (val, doneFn) => { doneFn() }, onFilterAbort: fn, useInput: true, inputDebounce: 0 } }) expect(fn).not.to.be.called getHostElement() .get('input') .click() .then(() => { expect(fn).not.to.be.called }) .type('h') .then(() => { expect(fn).not.to.be.called }) }) }) describe('(event): popup-show', () => { it('should emit event when the options are shown', () => { const fn = cy.stub() mountQSelect({ props: { onPopupShow: fn, options: [ '1', '2', '3' ] } }) expect(fn).not.to.be.called getHostElement() .click() .then(() => { expect(fn).to.be.called }) }) }) describe('(event): popup-hide', () => { it('should emit event when the options are hidden', () => { const fn = cy.stub() mountQSelect({ props: { onPopupHide: fn, options: [ '1', '2', '3' ] } }) expect(fn).not.to.be.called getHostElement() .click() .then(() => { expect(fn).not.to.be.called }) .type('{esc}') .then(() => { expect(fn).to.be.called }) }) }) describe('(event): virtual-scroll', () => { it.skip('', () => { // The virtual scroll code is tested as a composable // The property is included in the QSelect.json to add typings // for the QSelect ref passed in this event. // I think testing the component ref that is passed is of type QSelect is out of scope for unit tests. }) }) }) describe('Methods', () => { describe('(method): focus', () => { it('should focus the component', () => { mountQSelect() getHostElement() .get('[tabindex="0"]') .should('not.have.focus') getHostElement() .then(() => { Cypress.vueWrapper.vm.focus() }) getHostElement() .get('[tabindex="0"]') .should('have.focus') }) }) describe('(method): showPopup', () => { it('should open the popup and focus the component', () => { mountQSelect({ props: { options: [ '1', '2' ] } }) cy.get('.q-menu') .should('not.exist') .then(() => { Cypress.vueWrapper.vm.showPopup() }) cy.get('.q-menu') .should('be.visible') getHostElement() .get('[tabindex="0"]') .should('have.focus') }) }) describe('(method): hidePopup', () => { it('should hide the popup', () => { mountQSelect({ props: { options: [ '1', '2' ] } }) getHostElement() .click() cy.get('.q-menu') .should('be.visible') .then(() => { Cypress.vueWrapper.vm.hidePopup() }) cy.get('.q-menu') .should('not.exist') }) }) describe('(method): removeAtIndex', () => { it('should remove a selected option at the correct index', () => { const options = [ '1', '2', '3', '4' ] const model = ref([ '1', '2', '4' ]) mountQSelect({ props: { ...vModelAdapter(model), multiple: true, options } }) .then(() => { expect(model.value.includes('4')).to.be.true Cypress.vueWrapper.vm.removeAtIndex(2) expect(model.value.includes('4')).to.be.false }) }) }) describe('(method): add', () => { it('should add a selected option', () => { const model = ref([ '1', '2' ]) mountQSelect({ props: { ...vModelAdapter(model), multiple: true } }) .then(() => { expect(model.value.includes('100')).to.be.false Cypress.vueWrapper.vm.add('100') expect(model.value.includes('100')).to.be.true }) }) it('should not add a duplicate option when unique is true', () => { const model = ref([ '1', '2' ]) mountQSelect({ props: { ...vModelAdapter(model), multiple: true } }) .then(() => { expect(model.value.length).to.be.equal(2) Cypress.vueWrapper.vm.add('2', true) expect(model.value.length).to.be.equal(2) Cypress.vueWrapper.vm.add('2') expect(model.value.length).to.be.equal(3) }) }) }) describe('(method): toggleOption', () => { it('should toggle an option', () => { const model = ref([ '1', '2' ]) mountQSelect({ props: { ...vModelAdapter(model), multiple: true } }) .then(() => { expect(model.value.length).to.be.equal(2) Cypress.vueWrapper.vm.toggleOption('2') expect(model.value.length).to.be.equal(1) }) // When not using this wait this test will succeed on `open-ct` but fail on `run-ct` .wait(50) .then(() => { Cypress.vueWrapper.vm.toggleOption('2') expect(model.value.length).to.be.equal(2) }) }) // Todo: toggleOption argument keepOpen only does something when using single select. This is not clear from the docs. // should this be consistent? E.g. use `true` as argument when multiple is true by default but make sure it can be overridden. it('should close the menu and clear the filter', () => { const model = ref('1') mountQSelect({ props: { ...vModelAdapter(model), options: [ '1', '2' ], useInput: true } }) getHostElement() .click() .get('input') .type('h') cy.get('.q-menu') .should('be.visible') .then(() => { Cypress.vueWrapper.vm.toggleOption('2') }) cy.get('.q-menu') .should('not.exist') cy.get('input') .should('have.value', '') }) it('should not close the menu and clear the filter when keepOpen is true', () => { const model = ref('1') mountQSelect({ props: { ...vModelAdapter(model), options: [ '1', '2' ], useInput: true } }) getHostElement() .click() .get('input') .type('h') cy.get('.q-menu') .should('be.visible') .then(() => { Cypress.vueWrapper.vm.toggleOption('2', true) }) cy.get('.q-menu') .should('be.visible') cy.get('input') .should('have.value', 'h') }) }) describe('(method): setOptionIndex', () => { it('should set an option from the menu dropdown as focused', () => { const options = [ '1', '2', '3', '4' ] mountQSelect({ props: { options } }) getHostElement() .click() .then(() => { Cypress.vueWrapper.vm.setOptionIndex(0) }) .get('[role="option"]') .first() .should('have.class', 'q-manual-focusable--focused') }) }) describe('(method): moveOptionSelection', () => { it('should move the optionSelection by some index offset', () => { const options = [ '1', '2', '3', '4' ] mountQSelect({ props: { options } }) getHostElement() .click() .then(() => { Cypress.vueWrapper.vm.setOptionIndex(0) }) .get('[role="option"]') .first() .should('have.class', 'q-manual-focusable--focused') .then(() => {