diff --git a/create-quasar/package.json b/create-quasar/package.json
index fb969105170..c22a8d7704d 100644
--- a/create-quasar/package.json
+++ b/create-quasar/package.json
@@ -1,6 +1,6 @@
{
"name": "create-quasar",
- "version": "2.0.0",
+ "version": "2.0.1",
"description": "Scaffolds Quasar Apps, AppExtensions or UI kits",
"type": "module",
"author": {
diff --git a/create-quasar/templates/app/quasar-v2/ts-vite-2/BASE/_package.json b/create-quasar/templates/app/quasar-v2/ts-vite-2/BASE/_package.json
index 1a72db6f6e2..50910f108a2 100644
--- a/create-quasar/templates/app/quasar-v2/ts-vite-2/BASE/_package.json
+++ b/create-quasar/templates/app/quasar-v2/ts-vite-2/BASE/_package.json
@@ -30,7 +30,7 @@
"eslint-plugin-vue": "^9.30.0",
"globals": "^15.12.0",
"vue-tsc": "^2.0.29",
- "@vue/eslint-config-typescript": "^14.1.3",
+ "@vue/eslint-config-typescript": "^14.4.0",
"vite-plugin-checker": "^0.8.0",
<% } %>
<% if (prettier) { %>
diff --git a/create-quasar/templates/app/quasar-v2/ts-vite-2/eslint/_eslint.config.js b/create-quasar/templates/app/quasar-v2/ts-vite-2/eslint/_eslint.config.js
index 24320bf8e41..6ef1fd3ca18 100644
--- a/create-quasar/templates/app/quasar-v2/ts-vite-2/eslint/_eslint.config.js
+++ b/create-quasar/templates/app/quasar-v2/ts-vite-2/eslint/_eslint.config.js
@@ -2,10 +2,10 @@ import js from '@eslint/js'
import globals from 'globals'
import pluginVue from 'eslint-plugin-vue'
import pluginQuasar from '@quasar/app-vite/eslint'
-import vueTsEslintConfig from '@vue/eslint-config-typescript'<% if (prettier) { %>
+import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'<% if (prettier) { %>
import prettierSkipFormatting from '@vue/eslint-config-prettier/skip-formatting'<% } %>
-export default [
+export default defineConfigWithVueTs(
{
/**
* Ignore the following files.
@@ -18,7 +18,7 @@ export default [
// ignores: []
},
- ...pluginQuasar.configs.recommended(),
+ pluginQuasar.configs.recommended(),
js.configs.recommended,
/**
@@ -33,7 +33,7 @@ export default [
* pluginVue.configs["flat/recommended"]
* -> Above, plus rules to enforce subjective community defaults to ensure consistency.
*/
- ...pluginVue.configs[ 'flat/essential' ],
+ pluginVue.configs[ 'flat/essential' ],
{
files: ['**/*.ts', '**/*.vue'],
@@ -45,20 +45,7 @@ export default [
}
},
// https://github.com/vuejs/eslint-config-typescript
- ...vueTsEslintConfig({
- // Optional: extend additional configurations from typescript-eslint'.
- // Supports all the configurations in
- // https://typescript-eslint.io/users/configs#recommended-configurations
- extends: [
- // By default, only the 'recommendedTypeChecked' rules are enabled.
- 'recommendedTypeChecked'
- // You can also manually enable the stylistic rules.
- // "stylistic",
-
- // Other utility configurations, such as 'eslintRecommended', (note that it's in camelCase)
- // are also extendable here. But we don't recommend using them directly.
- ]
- }),
+ vueTsConfigs.recommendedTypeChecked,
{
languageOptions: {
@@ -96,4 +83,4 @@ export default [
}<% if (prettier) { %>,
prettierSkipFormatting<% } %>
-]
+)
diff --git a/create-quasar/templates/app/quasar-v2/ts-webpack-4/BASE/_package.json b/create-quasar/templates/app/quasar-v2/ts-webpack-4/BASE/_package.json
index b8a8e904285..b5055ef6404 100644
--- a/create-quasar/templates/app/quasar-v2/ts-webpack-4/BASE/_package.json
+++ b/create-quasar/templates/app/quasar-v2/ts-webpack-4/BASE/_package.json
@@ -30,7 +30,7 @@
"eslint": "^9.14.0",
"eslint-plugin-vue": "^9.30.0",
"eslint-webpack-plugin": "^4.2.0",
- "@vue/eslint-config-typescript": "^14.1.3",
+ "@vue/eslint-config-typescript": "^14.4.0",
"globals": "^15.12.0",
<% } %>
<% if (prettier) { %>
diff --git a/create-quasar/templates/app/quasar-v2/ts-webpack-4/eslint/_eslint.config.js b/create-quasar/templates/app/quasar-v2/ts-webpack-4/eslint/_eslint.config.js
index 1da668fd247..30bcc8e06c3 100644
--- a/create-quasar/templates/app/quasar-v2/ts-webpack-4/eslint/_eslint.config.js
+++ b/create-quasar/templates/app/quasar-v2/ts-webpack-4/eslint/_eslint.config.js
@@ -2,10 +2,10 @@ import js from '@eslint/js'
import globals from 'globals'
import pluginVue from 'eslint-plugin-vue'
import pluginQuasar from '@quasar/app-webpack/eslint'
-import vueTsEslintConfig from '@vue/eslint-config-typescript'<% if (prettier) { %>
+import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'<% if (prettier) { %>
import prettierSkipFormatting from '@vue/eslint-config-prettier/skip-formatting'<% } %>
-export default [
+export default defineConfigWithVueTs(
{
/**
* Ignore the following files.
@@ -18,7 +18,7 @@ export default [
// ignores: []
},
- ...pluginQuasar.configs.recommended(),
+ pluginQuasar.configs.recommended(),
js.configs.recommended,
/**
@@ -33,7 +33,7 @@ export default [
* pluginVue.configs["flat/recommended"]
* -> Above, plus rules to enforce subjective community defaults to ensure consistency.
*/
- ...pluginVue.configs[ 'flat/essential' ],
+ pluginVue.configs[ 'flat/essential' ],
// this rule needs to be above the vueTsEslintConfig to avoid error: 'You have used a rule which requires type information, but don't have parserOptions set to generate type information for this file.'
{
@@ -46,20 +46,7 @@ export default [
}
},
// https://github.com/vuejs/eslint-config-typescript
- ...vueTsEslintConfig({
- // Optional: extend additional configurations from typescript-eslint'.
- // Supports all the configurations in
- // https://typescript-eslint.io/users/configs#recommended-configurations
- extends: [
- // By default, only the 'recommendedTypeChecked' rules are enabled.
- 'recommendedTypeChecked'
- // You can also manually enable the stylistic rules.
- // "stylistic",
-
- // Other utility configurations, such as 'eslintRecommended', (note that it's in camelCase)
- // are also extendable here. But we don't recommend using them directly.
- ]
- }),
+ vueTsConfigs.recommendedTypeChecked,
{
languageOptions: {
@@ -97,4 +84,4 @@ export default [
}<% if (prettier) { %>,
prettierSkipFormatting<% } %>
-]
+)
diff --git a/docs/src/assets/docs-homepage.js b/docs/src/assets/docs-homepage.js
index a77aff158f8..45142aff97a 100644
--- a/docs/src/assets/docs-homepage.js
+++ b/docs/src/assets/docs-homepage.js
@@ -8,7 +8,7 @@ import {
mdiImageSizeSelectSmall,
mdiTable,
mdiHumanMaleBoard
-} from '@quasar/extras/mdi-v6'
+} from '@quasar/extras/mdi-v7'
export const mostUsedPages = [
{
diff --git a/docs/src/assets/links.header.js b/docs/src/assets/links.header.js
index b68fa218d17..52a6eaa71a4 100644
--- a/docs/src/assets/links.header.js
+++ b/docs/src/assets/links.header.js
@@ -5,7 +5,7 @@ import { fasCubes, fasBolt } from '@quasar/extras/fontawesome-v5'
import {
mdiBug, mdiClipboardText, mdiCodepen, mdiFlare, mdiFlask, mdiGithub, mdiJsfiddle, mdiPaletteSwatch, mdiPuzzle, mdiShoppingMusic,
mdiStarCircle, mdiThemeLightDark, mdiViewDashboard, mdiSecurity, mdiMathIntegralBox
-} from '@quasar/extras/mdi-v6'
+} from '@quasar/extras/mdi-v7'
import { socialLinks } from './links.social.js'
diff --git a/docs/src/assets/links.integrations.js b/docs/src/assets/links.integrations.js
index 46765f85573..4fd39054bb1 100644
--- a/docs/src/assets/links.integrations.js
+++ b/docs/src/assets/links.integrations.js
@@ -10,7 +10,7 @@ import {
mdiServer, mdiStarCircle, mdiSvg,
mdiTelevisionPlay,
mdiTestTube
-} from '@quasar/extras/mdi-v6'
+} from '@quasar/extras/mdi-v7'
export const platformIcons = [
'img:https://cdn.quasar.dev/img/custom-svg-icons/chrome.svg',
diff --git a/docs/src/assets/links.social.js b/docs/src/assets/links.social.js
index 5d7d46a47bc..73a49f5358d 100644
--- a/docs/src/assets/links.social.js
+++ b/docs/src/assets/links.social.js
@@ -1,12 +1,12 @@
-import { fabFacebook, fabGithub, fabXTwitter } from '@quasar/extras/fontawesome-v6'
-import { mdiDiscord, mdiForum } from '@quasar/extras/mdi-v6'
+import { fabFacebook, fabGithub, fabXTwitter, fabDiscord } from '@quasar/extras/fontawesome-v6'
+import { mdiForum } from '@quasar/extras/mdi-v7'
export const socialLinks = {
name: 'Social',
mq: 1310,
children: [
{ name: 'Github', icon: fabGithub, path: 'https://github.quasar.dev', external: true },
- { name: 'Discord', icon: mdiDiscord, path: 'https://chat.quasar.dev', external: true },
+ { name: 'Discord', icon: fabDiscord, path: 'https://chat.quasar.dev', external: true },
{ name: 'Forum', icon: mdiForum, path: 'https://forum.quasar.dev', external: true },
{ name: 'X (Twitter)', icon: fabXTwitter, path: 'https://twitter.quasar.dev', external: true },
{ name: 'Facebook', icon: fabFacebook, path: 'https://facebook.quasar.dev', external: true }
diff --git a/docs/src/components/DocApi.vue b/docs/src/components/DocApi.vue
index 6a82c3bf48f..93b31776358 100644
--- a/docs/src/components/DocApi.vue
+++ b/docs/src/components/DocApi.vue
@@ -81,7 +81,7 @@
diff --git a/docs/src/examples/QTable/CustomColor.vue b/docs/src/examples/QTable/CustomColor.vue
index fb510de1465..f47499c46d6 100644
--- a/docs/src/examples/QTable/CustomColor.vue
+++ b/docs/src/examples/QTable/CustomColor.vue
@@ -2,9 +2,11 @@
diff --git a/docs/src/layouts/doc-layout/search/ResultError.vue b/docs/src/layouts/doc-layout/search/ResultError.vue
index f9c10dbdb08..71b2d129430 100644
--- a/docs/src/layouts/doc-layout/search/ResultError.vue
+++ b/docs/src/layouts/doc-layout/search/ResultError.vue
@@ -7,5 +7,5 @@
diff --git a/docs/src/layouts/doc-layout/search/SearchResults.vue b/docs/src/layouts/doc-layout/search/SearchResults.vue
index fdfcc47ca0e..784e8437a6b 100644
--- a/docs/src/layouts/doc-layout/search/SearchResults.vue
+++ b/docs/src/layouts/doc-layout/search/SearchResults.vue
@@ -24,7 +24,7 @@
diff --git a/docs/src/pages/layout/grid/flex-playground/FlexChild.vue b/docs/src/pages/layout/grid/flex-playground/FlexChild.vue
index b2754a1cb22..882b9bfd9b4 100644
--- a/docs/src/pages/layout/grid/flex-playground/FlexChild.vue
+++ b/docs/src/pages/layout/grid/flex-playground/FlexChild.vue
@@ -28,7 +28,7 @@
```
@@ -154,7 +154,7 @@ import { fasFont } from '@quasar/extras/fontawesome-v5'
```html
diff --git a/ui/src/components/menu/__tests__/WrapperTwo.vue b/ui/src/components/menu/__tests__/WrapperTwo.vue
deleted file mode 100644
index 599c7b9655f..00000000000
--- a/ui/src/components/menu/__tests__/WrapperTwo.vue
+++ /dev/null
@@ -1,38 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/ui/src/components/pagination/QPagination.js b/ui/src/components/pagination/QPagination.js
index 2fd8fc299cb..bacf3fb0cfd 100644
--- a/ui/src/components/pagination/QPagination.js
+++ b/ui/src/components/pagination/QPagination.js
@@ -317,7 +317,8 @@ export default createComponent({
getBtn({
key: 'bls',
disable: props.disable || props.modelValue <= minProp.value,
- icon: icons.value[ 0 ]
+ icon: icons.value[ 0 ],
+ 'aria-label': $q.lang.pagination.first
}, minProp.value)
)
@@ -325,7 +326,8 @@ export default createComponent({
getBtn({
key: 'ble',
disable: props.disable || props.modelValue >= maxProp.value,
- icon: icons.value[ 3 ]
+ icon: icons.value[ 3 ],
+ 'aria-label': $q.lang.pagination.last
}, maxProp.value)
)
}
@@ -335,7 +337,8 @@ export default createComponent({
getBtn({
key: 'bdp',
disable: props.disable || props.modelValue <= minProp.value,
- icon: icons.value[ 1 ]
+ icon: icons.value[ 1 ],
+ 'aria-label': $q.lang.pagination.prev
}, props.modelValue - 1)
)
@@ -343,7 +346,8 @@ export default createComponent({
getBtn({
key: 'bdn',
disable: props.disable || props.modelValue >= maxProp.value,
- icon: icons.value[ 2 ]
+ icon: icons.value[ 2 ],
+ 'aria-label': $q.lang.pagination.next
}, props.modelValue + 1)
)
}
diff --git a/ui/src/components/parallax/QParallax.js b/ui/src/components/parallax/QParallax.js
index c9ec464cea7..48a58df42b2 100644
--- a/ui/src/components/parallax/QParallax.js
+++ b/ui/src/components/parallax/QParallax.js
@@ -134,7 +134,7 @@ export default createComponent({
onBeforeUnmount(() => {
stop()
- observer !== void 0 && observer.disconnect()
+ observer?.disconnect()
mediaEl.onload = mediaEl.onloadstart = mediaEl.loadedmetadata = null
})
diff --git a/ui/src/components/popup-edit/QPopupEdit.js b/ui/src/components/popup-edit/QPopupEdit.js
index c6a8f9a854b..9efc783ab3b 100644
--- a/ui/src/components/popup-edit/QPopupEdit.js
+++ b/ui/src/components/popup-edit/QPopupEdit.js
@@ -163,8 +163,8 @@ export default createComponent({
Object.assign(proxy, {
set,
cancel,
- show (e) { menuRef.value !== null && menuRef.value.show(e) },
- hide (e) { menuRef.value !== null && menuRef.value.hide(e) },
+ show (e) { menuRef.value?.show(e) },
+ hide (e) { menuRef.value?.hide(e) },
updatePosition
})
diff --git a/ui/src/components/pull-to-refresh/QPullToRefresh.js b/ui/src/components/pull-to-refresh/QPullToRefresh.js
index 0bef55d5ca9..2ac570043f1 100644
--- a/ui/src/components/pull-to-refresh/QPullToRefresh.js
+++ b/ui/src/components/pull-to-refresh/QPullToRefresh.js
@@ -149,7 +149,7 @@ export default createComponent({
timer = setTimeout(() => {
timer = null
animating.value = false
- done && done()
+ done?.()
}, 300)
}
diff --git a/ui/src/components/rating/QRating.sass b/ui/src/components/rating/QRating.sass
index eb5878bd6b8..f96ecb3ce58 100644
--- a/ui/src/components/rating/QRating.sass
+++ b/ui/src/components/rating/QRating.sass
@@ -14,7 +14,7 @@
text-shadow: $rating-shadow
position: relative
opacity: .4
- transition: transform .2s ease-in, opacity .2s ease-in
+ transition: transform .2s ease-in, opacity .2s ease-in, color .2s ease-in
&--hovered
transform: scale(1.3)
diff --git a/ui/src/components/resize-observer/QResizeObserver.js b/ui/src/components/resize-observer/QResizeObserver.js
index c889c61820b..8b97cbe4cac 100644
--- a/ui/src/components/resize-observer/QResizeObserver.js
+++ b/ui/src/components/resize-observer/QResizeObserver.js
@@ -117,7 +117,7 @@ export default createComponent({
function onObjLoad () {
cleanup()
- if (targetEl && targetEl.contentDocument) {
+ if (targetEl?.contentDocument) {
curDocView = targetEl.contentDocument.defaultView
curDocView.addEventListener('resize', trigger, listenOpts.passive)
emitEvent()
diff --git a/ui/src/components/scroll-observer/QScrollObserver.js b/ui/src/components/scroll-observer/QScrollObserver.js
index 5d49315a3e9..a011386f411 100644
--- a/ui/src/components/scroll-observer/QScrollObserver.js
+++ b/ui/src/components/scroll-observer/QScrollObserver.js
@@ -53,7 +53,7 @@ export default createComponent({
})
function emitEvent () {
- clearTimer !== null && clearTimer()
+ clearTimer?.()
const top = Math.max(0, getVerticalScrollPosition(localScrollTarget))
const left = getHorizontalScrollPosition(localScrollTarget)
@@ -123,7 +123,7 @@ export default createComponent({
})
onBeforeUnmount(() => {
- clearTimer !== null && clearTimer()
+ clearTimer?.()
unconfigureScrollTarget()
})
diff --git a/ui/src/components/select/QSelect.js b/ui/src/components/select/QSelect.js
index 42e4446d797..2f06dcc4724 100644
--- a/ui/src/components/select/QSelect.js
+++ b/ui/src/components/select/QSelect.js
@@ -273,7 +273,7 @@ export default createComponent({
const needsHtmlFn = computed(() => (
props.optionsHtml === true
? () => true
- : opt => opt !== void 0 && opt !== null && opt.html === true
+ : opt => opt?.html === true
))
const valueAsHtml = computed(() => (
@@ -533,7 +533,7 @@ export default createComponent({
hidePopup()
}
- targetRef.value !== null && targetRef.value.focus()
+ targetRef.value?.focus()
if (
innerValue.value.length === 0
@@ -545,7 +545,9 @@ export default createComponent({
return
}
- (hasDialog !== true || dialogFieldFocused.value === true) && state.focus()
+ if (hasDialog !== true || dialogFieldFocused.value === true) {
+ state.focus()
+ }
selectInputText()
@@ -894,7 +896,7 @@ export default createComponent({
fn(val, mode === 'add-unique')
if (props.multiple !== true) {
- targetRef.value !== null && targetRef.value.focus()
+ targetRef.value?.focus()
hidePopup()
}
}
@@ -1214,7 +1216,7 @@ export default createComponent({
function onDialogFieldFocus (e) {
stop(e)
- targetRef.value !== null && targetRef.value.focus()
+ targetRef.value?.focus()
dialogFieldFocused.value = true
window.scrollTo(window.pageXOffset || window.scrollX || document.body.scrollLeft || 0, 0)
}
@@ -1511,7 +1513,7 @@ export default createComponent({
if (hasDialog !== true && menu.value === true) {
closeMenu()
- targetRef.value !== null && targetRef.value.focus()
+ targetRef.value?.focus()
return
}
diff --git a/ui/src/components/select/QSelect.json b/ui/src/components/select/QSelect.json
index 65c39d49c19..4e282fff5f1 100644
--- a/ui/src/components/select/QSelect.json
+++ b/ui/src/components/select/QSelect.json
@@ -260,7 +260,7 @@
"fill-input": {
"type": "Boolean",
- "desc": "Fills the input with current value; Useful along with 'hide-selected'; Does NOT works along with 'multiple' selection",
+ "desc": "Fills the input with current value; Useful along with 'hide-selected'; Does NOT work along with 'multiple' selection",
"category": "behavior"
},
diff --git a/ui/src/components/select/__tests__/QSelect.cy.js b/ui/src/components/select/__tests__/QSelect.cy.js
deleted file mode 100644
index eb40d27cffc..00000000000
--- a/ui/src/components/select/__tests__/QSelect.cy.js
+++ /dev/null
@@ -1,2018 +0,0 @@
-/* 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 = '') {
- // A majority of tests click on the select, so we ensure the select is visible before the click happens.
- // See https://github.com/cypress-io/cypress/discussions/14889
- // See https://www.cypress.io/blog/2020/08/17/when-can-the-test-navigate#visible-elements
- return extendedSelector ? cy.get(`.q-select ${ extendedSelector }`) : cy.get('.q-select').should('be.visible')
-}
-
-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')
- .should('be.visible')
- .click()
- cy.get('.q-menu')
- .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')
- .should('be.visible')
- .click()
- cy.get('.q-menu')
- .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')
- .should('be.visible')
- .click()
- cy.contains('Option 1')
- .then(() => {
- expect(model.value).to.equal(options[ 0 ])
- })
- })
-
- getHostElement().click()
- cy.withinSelectMenu(() => {
- cy.contains('Option 2')
- .should('be.visible')
- .click()
- cy.contains('Option 2')
- .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')
- .should('be.visible')
- .click()
- cy.contains('Option 1')
- .then(() => {
- expect(model.value).to.eql([ options[ 0 ] ])
- })
-
- cy.contains('Option 2')
- .should('be.visible')
- .click()
- cy.contains('Option 2')
- .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)
- .should('be.visible')
- .click()
- cy.get('.q-menu')
- .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)
- .should('be.visible')
- .click()
- cy.get('.q-menu')
- .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)
- .should('be.visible')
- .click()
- cy.get('.q-menu')
- .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 = [ 'Option 1', '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 = [ 'Option 1', '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()
- cy.get('.q-menu').should('be.visible')
- getHostElement()
- .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 = [ 'Option 1', '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 = 'Option 1'
- 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 = 'Option 1'
- 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"]')
- .should('be.visible')
- .as('clicked')
- .click({ multiple: true })
- cy.get('@clicked')
- .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()
- .should('be.visible')
- .as('clicked')
- .click()
- cy.get('@clicked')
- .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,
- inputDebounce: 0
- }
- })
-
- 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()
- .should('be.visible')
- .as('clicked')
- .click()
- cy.get('@clicked')
- .then(() => {
- expect(fn).not.to.be.called
- })
- cy.get('.q-menu')
- .get('[role="option"]')
- .first()
- .should('be.visible')
- .as('clicked')
- .click()
- cy.get('@clicked')
- .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()
- .should('be.visible')
- .as('clicked')
- .click()
- cy.get('@clicked')
- .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 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' ]
- }
- })
-
- getHostElement()
- 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(() => {
- Cypress.vueWrapper.vm.moveOptionSelection(3)
- })
- .get('[role="option"]')
- .last()
- .should('have.class', 'q-manual-focusable--focused')
- })
- })
-
- describe('(method): filter', () => {
- it('should filter the options list', () => {
- const options = [ '1', '2', '3', '4' ]
- const fn = cy.stub()
- const text = 'test'
- mountQSelect({
- props: {
- options,
- useInput: true,
- onFilter: fn
- }
- })
- getHostElement()
- .click()
- .then(() => {
- expect(fn).not.to.be.calledWith(text)
- Cypress.vueWrapper.vm.filter(text)
- expect(fn).to.be.calledWith(text)
- })
- })
- })
-
- describe('(method): updateMenuPosition', () => {
- it.skip(' ', () => {
- // Not sure in what scenario this is needed, there is also some auto repositioning going on
- })
- })
-
- describe('(method): updateInputValue', () => {
- it('should update the input value', () => {
- const options = [ '1', '2', '3', '4' ]
- const fn = cy.stub()
- const text = 'test'
- mountQSelect({
- props: {
- options,
- useInput: true,
- onFilter: fn
- }
- })
- getHostElement()
- .click()
- .then(() => {
- expect(fn).not.to.be.calledWith(text)
- Cypress.vueWrapper.vm.updateInputValue(text)
- expect(fn).to.be.calledWith(text)
- })
- .get('input')
- .should('have.value', text)
- })
-
- it('should not trigger the filter when specified', () => {
- const options = [ '1', '2', '3', '4' ]
- const fn = cy.stub()
- const text = 'test'
- mountQSelect({
- props: {
- options,
- useInput: true,
- onFilter: fn
- }
- })
- getHostElement()
- .click()
- .then(() => {
- expect(fn).not.to.be.calledWith(text)
- Cypress.vueWrapper.vm.updateInputValue(text, true)
- expect(fn).not.to.be.calledWith(text)
- })
- .get('input')
- .should('have.value', text)
- })
- })
-
- describe('(method): isOptionSelected', () => {
- it('should tell when an option is selected', () => {
- const options = [ '1', '2', '3', '4' ]
- const modelValue = [ '1', '2', '4' ]
- mountQSelect({
- props: {
- modelValue,
- multiple: true,
- options
- }
- })
- .then(() => {
- expect(Cypress.vueWrapper.vm.isOptionSelected(options[ 0 ])).to.be.true
- expect(Cypress.vueWrapper.vm.isOptionSelected(options[ 2 ])).to.be.false
- })
- })
- })
-
- describe('(method): getEmittingOptionValue', () => {
- it('should return the emit value with plain options', () => {
- const options = [ '1', '2', '3', '4' ]
- const modelValue = '1'
- mountQSelect({
- props: {
- modelValue,
- options
- }
- })
- .then(() => {
- expect(Cypress.vueWrapper.vm.getEmittingOptionValue(options[ 2 ])).to.equal(options[ 2 ])
- })
- })
-
- it('should return the emit value with object options', () => {
- const options = [ { label: '1', value: 1 }, { label: '2', value: 2 }, { label: '3', value: 3 } ]
- const modelValue = options[ 0 ]
- mountQSelect({
- props: {
- modelValue,
- options
- }
- })
- .then(() => {
- expect(Cypress.vueWrapper.vm.getEmittingOptionValue(options[ 2 ])).to.equal(options[ 2 ])
- })
- })
-
- it('should respect emit-value when using options', () => {
- const options = [ { label: '1', value: 1 }, { label: '2', value: 2 }, { label: '3', value: 3 } ]
- const modelValue = options[ 0 ]
- mountQSelect({
- props: {
- modelValue,
- options,
- emitValue: true
- }
- })
- .then(() => {
- expect(Cypress.vueWrapper.vm.getEmittingOptionValue(options[ 2 ])).to.equal(options[ 2 ].value)
- })
- })
- })
-
- describe('(method): getOptionValue', () => {
- it('should return the option value with plain options', () => {
- const options = [ '1', '2', '3', '4' ]
- const modelValue = '1'
- mountQSelect({
- props: {
- modelValue,
- options
- }
- })
- .then(() => {
- expect(Cypress.vueWrapper.vm.getOptionValue(options[ 2 ])).to.equal(options[ 2 ])
- })
- })
-
- it('should return the option value with object options (value by default)', () => {
- const options = [ { label: '1', value: 1 }, { label: '2', value: 2 }, { label: '3', value: 3 } ]
- const modelValue = options[ 0 ]
- mountQSelect({
- props: {
- modelValue,
- options
- }
- })
- .then(() => {
- expect(Cypress.vueWrapper.vm.getOptionValue(options[ 2 ])).to.equal(options[ 2 ].value)
- })
- })
-
- it('should respect the option-value option', () => {
- const options = [ { label: '1', test: 1 }, { label: '2', test: 2 }, { label: '3', test: 3 } ]
- const modelValue = options[ 0 ]
- mountQSelect({
- props: {
- modelValue,
- options,
- optionValue: 'test'
- }
- })
- .then(() => {
- expect(Cypress.vueWrapper.vm.getOptionValue(options[ 2 ])).to.equal(options[ 2 ].test)
- })
- })
- })
-
- describe('(method): getOptionLabel', () => {
- it('should return the option label with plain options', () => {
- const options = [ '1', '2', '3', '4' ]
- const modelValue = '1'
- mountQSelect({
- props: {
- modelValue,
- options
- }
- })
- .then(() => {
- expect(Cypress.vueWrapper.vm.getOptionLabel(options[ 2 ])).to.equal(options[ 2 ])
- })
- })
-
- it('should return the option label with object options (label by default)', () => {
- const options = [ { label: '1', value: 1 }, { label: '2', value: 2 }, { label: '3', value: 3 } ]
- const modelValue = options[ 0 ]
- mountQSelect({
- props: {
- modelValue,
- options
- }
- })
- .then(() => {
- expect(Cypress.vueWrapper.vm.getOptionLabel(options[ 2 ])).to.equal(options[ 2 ].label)
- })
- })
-
- it('should respect the option-value option', () => {
- const options = [ { test: '1', value: 1 }, { test: '2', value: 2 }, { test: '3', value: 3 } ]
- const modelValue = options[ 0 ]
- mountQSelect({
- props: {
- modelValue,
- options,
- optionLabel: 'test'
- }
- })
- .then(() => {
- expect(Cypress.vueWrapper.vm.getOptionLabel(options[ 2 ])).to.equal(options[ 2 ].test)
- })
- })
- })
-
- describe('(method): isOptionDisabled', () => {
- it('should return if an option is disabled correctly', () => {
- const options = [ { label: '1', value: 1, disable: true }, { label: '2', value: 2 }, { label: '3', value: 3 } ]
- const modelValue = options[ 0 ]
- mountQSelect({
- props: {
- modelValue,
- options
- }
- })
- .then(() => {
- expect(Cypress.vueWrapper.vm.isOptionDisabled(options[ 0 ])).to.be.true
- // This currently fails: https://github.com/quasarframework/quasar/issues/12046
- // expect(Cypress.vueWrapper.vm.isOptionDisabled(options[ 1 ])).to.be.false
- // expect(Cypress.vueWrapper.vm.isOptionDisabled(options[ 2 ])).to.be.false
- })
- })
- })
- })
-})
diff --git a/ui/src/components/slide-transition/QSlideTransition.js b/ui/src/components/slide-transition/QSlideTransition.js
index 34b22a011ce..cf2af63ab73 100644
--- a/ui/src/components/slide-transition/QSlideTransition.js
+++ b/ui/src/components/slide-transition/QSlideTransition.js
@@ -20,7 +20,7 @@ export default createComponent({
let timer = null, timerFallback = null, animListener, lastEvent
function cleanup () {
- doneFn && doneFn()
+ doneFn?.()
doneFn = null
animating = false
@@ -34,7 +34,7 @@ export default createComponent({
timerFallback = null
}
- element !== void 0 && element.removeEventListener('transitionend', animListener)
+ element?.removeEventListener('transitionend', animListener)
animListener = null
}
diff --git a/ui/src/components/stepper/StepHeader.js b/ui/src/components/stepper/StepHeader.js
index f0e2958a977..b7f69a2ba10 100644
--- a/ui/src/components/stepper/StepHeader.js
+++ b/ui/src/components/stepper/StepHeader.js
@@ -119,7 +119,7 @@ export default createComponent({
))
function onActivate () {
- blurRef.value !== null && blurRef.value.focus()
+ blurRef.value?.focus()
isActive.value === false && props.goToPanel(props.step.name)
}
diff --git a/ui/src/components/table/QTable.js b/ui/src/components/table/QTable.js
index 31525c50b6f..2af079dfe82 100644
--- a/ui/src/components/table/QTable.js
+++ b/ui/src/components/table/QTable.js
@@ -91,10 +91,14 @@ export default createComponent({
tableClass: [ String, Array, Object ],
tableHeaderStyle: [ String, Array, Object ],
tableHeaderClass: [ String, Array, Object ],
+ tableRowStyleFn: Function,
+ tableRowClassFn: Function,
cardContainerClass: [ String, Array, Object ],
cardContainerStyle: [ String, Array, Object ],
cardStyle: [ String, Array, Object ],
cardClass: [ String, Array, Object ],
+ cardStyleFn: Function,
+ cardClassFn: Function,
hideBottom: Boolean,
hideSelectedBanner: Boolean,
@@ -148,7 +152,7 @@ export default createComponent({
+ (props.bordered === true ? ' q-table--bordered' : '')
)
- const __containerClass = computed(() =>
+ const containerClass = computed(() =>
`q-table__container q-table--${ props.separator }-separator column no-wrap`
+ (props.grid === true ? ' q-table--grid' : cardDefaultClass.value)
+ (isDark.value === true ? ' q-table--dark' : '')
@@ -157,13 +161,13 @@ export default createComponent({
+ (inFullscreen.value === true ? ' fullscreen scroll' : '')
)
- const containerClass = computed(() =>
- __containerClass.value + (props.loading === true ? ' q-table--loading' : '')
+ const rootContainerClass = computed(() =>
+ containerClass.value + (props.loading === true ? ' q-table--loading' : '')
)
watch(
- () => props.tableStyle + props.tableClass + props.tableHeaderStyle + props.tableHeaderClass + __containerClass.value,
- () => { hasVirtScroll.value === true && virtScrollRef.value !== null && virtScrollRef.value.reset() }
+ () => props.tableStyle + props.tableClass + props.tableHeaderStyle + props.tableHeaderClass + containerClass.value,
+ () => { hasVirtScroll.value === true && virtScrollRef.value?.reset() }
)
const {
@@ -382,13 +386,26 @@ export default createComponent({
selected = isRowSelected(key)
if (bodySlot !== void 0) {
+ const cfg = {
+ key,
+ row,
+ pageIndex,
+ __trClass: selected ? 'selected' : ''
+ }
+
+ if (props.tableRowStyleFn !== void 0) {
+ cfg.__trStyle = props.tableRowStyleFn(row)
+ }
+
+ if (props.tableRowClassFn !== void 0) {
+ const cls = props.tableRowClassFn(row)
+ if (cls) {
+ cfg.__trClass = `${ cls } ${ cfg.__trClass }`
+ }
+ }
+
return bodySlot(
- getBodyScope({
- key,
- row,
- pageIndex,
- __trClass: selected ? 'selected' : ''
- })
+ getBodyScope(cfg)
)
}
@@ -451,6 +468,17 @@ export default createComponent({
}
}
+ if (props.tableRowStyleFn !== void 0) {
+ data.style = props.tableRowStyleFn(row)
+ }
+
+ if (props.tableRowClassFn !== void 0) {
+ const cls = props.tableRowClassFn(row)
+ if (cls) {
+ data.class[ cls ] = true
+ }
+ }
+
return h('tr', data, child)
}
@@ -802,30 +830,28 @@ export default createComponent({
h('div', { class: 'q-table__separator col' })
)
- if (hasOpts === true) {
- child.push(
- h('div', { class: 'q-table__control' }, [
- h('span', { class: 'q-table__bottom-item' }, [
- props.rowsPerPageLabel || $q.lang.table.recordsPerPage
- ]),
- h(QSelect, {
- class: 'q-table__select inline q-table__bottom-item',
- color: props.color,
- modelValue: rowsPerPage,
- options: computedRowsPerPageOptions.value,
- displayValue: rowsPerPage === 0
- ? $q.lang.table.allRows
- : rowsPerPage,
- dark: isDark.value,
- borderless: true,
- dense: true,
- optionsDense: true,
- optionsCover: true,
- 'onUpdate:modelValue': onPagSelection
- })
- ])
- )
- }
+ hasOpts === true && child.push(
+ h('div', { class: 'q-table__control' }, [
+ h('span', { class: 'q-table__bottom-item' }, [
+ props.rowsPerPageLabel || $q.lang.table.recordsPerPage
+ ]),
+ h(QSelect, {
+ class: 'q-table__select inline q-table__bottom-item',
+ color: props.color,
+ modelValue: rowsPerPage,
+ options: computedRowsPerPageOptions.value,
+ displayValue: rowsPerPage === 0
+ ? $q.lang.table.allRows
+ : rowsPerPage,
+ dark: isDark.value,
+ borderless: true,
+ dense: true,
+ optionsDense: true,
+ optionsCover: true,
+ 'onUpdate:modelValue': onPagSelection
+ })
+ ])
+ )
if (paginationSlot !== void 0) {
control = paginationSlot(marginalsScope.value)
@@ -857,6 +883,7 @@ export default createComponent({
...btnProps,
icon: navIcon.value[ 0 ],
disable: isFirstPage.value,
+ ariaLabel: $q.lang.pagination.first,
onClick: firstPage
})
)
@@ -867,6 +894,7 @@ export default createComponent({
...btnProps,
icon: navIcon.value[ 1 ],
disable: isFirstPage.value,
+ ariaLabel: $q.lang.pagination.prev,
onClick: prevPage
}),
@@ -875,6 +903,7 @@ export default createComponent({
...btnProps,
icon: navIcon.value[ 2 ],
disable: isLastPage.value,
+ ariaLabel: $q.lang.pagination.next,
onClick: nextPage
})
)
@@ -885,6 +914,7 @@ export default createComponent({
...btnProps,
icon: navIcon.value[ 3 ],
disable: isLastPage.value,
+ ariaLabel: $q.lang.pagination.last,
onClick: lastPage
})
)
@@ -955,6 +985,17 @@ export default createComponent({
style: props.cardStyle
}
+ if (props.cardStyleFn !== void 0) {
+ data.style = [ data.style, props.cardStyleFn(scope.row) ]
+ }
+
+ if (props.cardClassFn !== void 0) {
+ const cls = props.cardClassFn(scope.row)
+ if (cls) {
+ data.class[ 0 ] += ` ${ cls }`
+ }
+ }
+
if (
props.onRowClick !== void 0
|| props.onRowDblclick !== void 0
@@ -1030,7 +1071,7 @@ export default createComponent({
return () => {
const child = [ getTopDiv() ]
- const data = { ref: rootRef, class: containerClass.value }
+ const data = { ref: rootRef, class: rootContainerClass.value }
if (props.grid === true) {
child.push(getGridHeader())
diff --git a/ui/src/components/table/QTable.json b/ui/src/components/table/QTable.json
index 0ee312501c7..43c9c9e4d80 100644
--- a/ui/src/components/table/QTable.json
+++ b/ui/src/components/table/QTable.json
@@ -563,6 +563,50 @@
"category": "style"
},
+ "table-row-style-fn": {
+ "type": "Function",
+ "desc": "CSS style to apply to the table rows (which are TR elements); For best performance, reference it from your scope and do not define it inline",
+ "params": {
+ "row": {
+ "type": "Object",
+ "desc": "The current row being processed",
+ "examples": [ "{ name: 'Frozen Yogurt', calories: 159 }" ]
+ }
+ },
+ "returns": {
+ "type": "String",
+ "desc": "CSS style to apply to the row",
+ "examples": [
+ "'color: blue'",
+ "'background-color: #ff0000; color: green'"
+ ]
+ },
+ "category": "style",
+ "addedIn": "v2.18"
+ },
+
+ "table-row-class-fn": {
+ "type": "Function",
+ "desc": "CSS class(es) to apply the table rows (which are TR elements); For best performance, reference it from your scope and do not define it inline",
+ "params": {
+ "row": {
+ "type": "Object",
+ "desc": "The current row being processed",
+ "examples": [ "{ name: 'Frozen Yogurt', calories: 159 }" ]
+ }
+ },
+ "returns": {
+ "type": "String",
+ "desc": "CSS class(es) to apply to the row, space separated",
+ "examples": [
+ "'my-special-class'",
+ "'my-class my-second-class'"
+ ]
+ },
+ "category": "style",
+ "addedIn": "v2.18"
+ },
+
"card-container-style": {
"type": [ "String", "Array", "Object" ],
"tsType": "VueStyleProp",
@@ -608,6 +652,50 @@
"category": "style"
},
+ "card-style-fn": {
+ "type": "Function",
+ "desc": "(Grid mode only) CSS style to apply to the row/record card; Has no effect when the 'item' slot is used; For best performance, reference it from your scope and do not define it inline",
+ "params": {
+ "row": {
+ "type": "Object",
+ "desc": "The current row/record being processed",
+ "examples": [ "{ name: 'Frozen Yogurt', calories: 159 }" ]
+ }
+ },
+ "returns": {
+ "type": "String",
+ "desc": "CSS style to apply to the row/record",
+ "examples": [
+ "'color: blue'",
+ "'background-color: #ff0000; color: green'"
+ ]
+ },
+ "category": "style",
+ "addedIn": "v2.18"
+ },
+
+ "card-class-fn": {
+ "type": "Function",
+ "desc": "(Grid mode only) CSS class(es) to apply the row/record card; Has no effect when the 'item' slot is used; For best performance, reference it from your scope and do not define it inline",
+ "params": {
+ "row": {
+ "type": "Object",
+ "desc": "The current row/record being processed",
+ "examples": [ "{ name: 'Frozen Yogurt', calories: 159 }" ]
+ }
+ },
+ "returns": {
+ "type": "String",
+ "desc": "CSS class(es) to apply to the row, space separated",
+ "examples": [
+ "'my-special-class'",
+ "'my-class my-second-class'"
+ ]
+ },
+ "category": "style",
+ "addedIn": "v2.18"
+ },
+
"title-class": {
"type": [ "String", "Array", "Object" ],
"tsType": "VueClassProp",
@@ -896,6 +984,12 @@
"__trClass": {
"type": "String",
"desc": "Internal prop passed down to QTr (if used)"
+ },
+ "__trStyle": {
+ "type": "String",
+ "required": false,
+ "desc": "Internal prop passed down to QTr (if used)",
+ "addedIn": "v2.18"
}
}
},
@@ -1848,6 +1942,10 @@
"type": "String",
"desc": "The suggested icon name (following Quasar convention)",
"examples": [ "'warning'" ]
+ },
+ "filter": {
+ "type": [ "String", "Object" ],
+ "desc": "String/Object to filter table with (the 'filter' prop)"
}
}
}
diff --git a/ui/src/components/table/QTr.js b/ui/src/components/table/QTr.js
index 6aa5bbf376c..8f58b8f0dc0 100644
--- a/ui/src/components/table/QTr.js
+++ b/ui/src/components/table/QTr.js
@@ -18,6 +18,9 @@ export default createComponent({
+ (props.noHover === true ? ' q-tr--no-hover' : '')
)
- return () => h('tr', { class: classes.value }, hSlot(slots.default))
+ return () => h('tr', {
+ style: props.props?.__trStyle,
+ class: classes.value
+ }, hSlot(slots.default))
}
})
diff --git a/ui/src/components/table/__tests__/QTable.cy.js b/ui/src/components/table/__tests__/QTable.cy.js
deleted file mode 100644
index 0d228702aa0..00000000000
--- a/ui/src/components/table/__tests__/QTable.cy.js
+++ /dev/null
@@ -1,635 +0,0 @@
-describe('Table API', () => {
- describe('Props', () => {
- describe('Category: behavior', () => {
- describe('(prop): grid', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-
- describe('Category: behavior|content', () => {
- describe('(prop): grid-header', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): loading', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-
- describe('Category: column', () => {
- describe('(prop): columns', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): visible-columns', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-
- describe('Category: content', () => {
- describe('(prop): icon-first-page', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): icon-prev-page', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): icon-next-page', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): icon-last-page', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): title', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): hide-header', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): hide-bottom', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): hide-selected-banner', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): hide-no-data', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): hide-pagination', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): separator', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): wrap-cells', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): no-data-label', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): no-results-label', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): loading-label', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-
- describe('Category: expansion', () => {
- describe('(prop): expanded', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-
- describe('Category: filter', () => {
- describe('(prop): filter', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): filter-method', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-
- describe('Category: general', () => {
- describe('(prop): rows', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): row-key', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-
- describe('Category: pagination', () => {
- describe('(prop): rows-per-page-label', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): pagination-label', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): pagination', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): rows-per-page-options', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-
- describe('Category: selection', () => {
- describe('(prop): selected-rows-label', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): selection', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): selected', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-
- describe('Category: sorting', () => {
- describe('(prop): binary-state-sort', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): column-sort-order', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): sort-method', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-
- describe('Category: style', () => {
- describe('(prop): color', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): dense', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): dark', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): flat', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): bordered', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): square', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): table-style', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): table-class', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): table-header-style', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): table-header-class', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): card-container-style', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): card-container-class', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): card-style', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): card-class', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): title-class', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-
- describe('Category: virtual-scroll', () => {
- describe('(prop): virtual-scroll', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): virtual-scroll-slice-size', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): virtual-scroll-slice-ratio-before', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): virtual-scroll-slice-ratio-after', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): virtual-scroll-item-size', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): virtual-scroll-sticky-size-start', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): virtual-scroll-sticky-size-end', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-
- describe('Category: virtual-scroll|content', () => {
- describe('(prop): table-colspan', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
- })
-
- describe('Slots', () => {
- describe('(slot): loading', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(slot): item', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(slot): body', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(slot): body-cell', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(slot): body-cell-[name]', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(slot): header', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(slot): header-cell', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(slot): header-cell-[name]', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(slot): body-selection', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(slot): header-selection', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(slot): top-row', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(slot): bottom-row', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(slot): top', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(slot): bottom', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(slot): pagination', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(slot): top-left', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(slot): top-right', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(slot): top-selection', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(slot): no-data', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-
- describe('Events', () => {
- describe('(event): row-click', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(event): row-dblclick', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(event): row-contextmenu', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(event): request', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(event): selection', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(event): update:pagination', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(event): update:selected', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(event): update:expanded', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(event): virtual-scroll', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-
- describe('Methods', () => {
- describe('(method): requestServerInteraction', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(method): setPagination', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(method): firstPage', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(method): prevPage', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(method): nextPage', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(method): lastPage', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(method): isRowSelected', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(method): clearSelection', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(method): isRowExpanded', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(method): setExpanded', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(method): sort', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(method): toggleFullscreen', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(method): resetVirtualScroll', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(method): scrollTo', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-})
diff --git a/ui/src/components/table/__tests__/QTd.cy.js b/ui/src/components/table/__tests__/QTd.cy.js
deleted file mode 100644
index 6b5f3a6558f..00000000000
--- a/ui/src/components/table/__tests__/QTd.cy.js
+++ /dev/null
@@ -1,35 +0,0 @@
-describe('Td API', () => {
- describe('Props', () => {
- describe('Category: content', () => {
- describe('(prop): auto-width', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-
- describe('Category: general', () => {
- describe('(prop): props', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-
- describe('Category: style', () => {
- describe('(prop): no-hover', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
- })
-
- describe('Slots', () => {
- describe('(slot): default', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-})
diff --git a/ui/src/components/table/__tests__/QTh.cy.js b/ui/src/components/table/__tests__/QTh.cy.js
deleted file mode 100644
index 59ebaf586a6..00000000000
--- a/ui/src/components/table/__tests__/QTh.cy.js
+++ /dev/null
@@ -1,27 +0,0 @@
-describe('Th API', () => {
- describe('Props', () => {
- describe('Category: content', () => {
- describe('(prop): auto-width', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-
- describe('Category: general', () => {
- describe('(prop): props', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
- })
-
- describe('Slots', () => {
- describe('(slot): default', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-})
diff --git a/ui/src/components/table/__tests__/QTr.cy.js b/ui/src/components/table/__tests__/QTr.cy.js
deleted file mode 100644
index 04c6c8db2e2..00000000000
--- a/ui/src/components/table/__tests__/QTr.cy.js
+++ /dev/null
@@ -1,27 +0,0 @@
-describe('Tr API', () => {
- describe('Props', () => {
- describe('Category: general', () => {
- describe('(prop): props', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-
- describe('Category: style', () => {
- describe('(prop): no-hover', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
- })
-
- describe('Slots', () => {
- describe('(slot): default', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-})
diff --git a/ui/src/components/table/table-sort.js b/ui/src/components/table/table-sort.js
index ccd3ce2100d..c1df1b0d412 100644
--- a/ui/src/components/table/table-sort.js
+++ b/ui/src/components/table/table-sort.js
@@ -87,7 +87,7 @@ export function useTableSort (props, computedPagination, colList, setPagination)
}
else {
const def = colList.value.find(def => def.name === col)
- if (def !== void 0 && def.sortOrder) {
+ if (def?.sortOrder) {
sortOrder = def.sortOrder
}
}
diff --git a/ui/src/components/tabs/QTabs.js b/ui/src/components/tabs/QTabs.js
index 1b619fec210..e9c51f786ea 100644
--- a/ui/src/components/tabs/QTabs.js
+++ b/ui/src/components/tabs/QTabs.js
@@ -424,7 +424,7 @@ export default createComponent({
function updateActiveRoute () {
let name = null, bestScore = { matchedLen: 0, queryDiff: 9999, hrefLen: 0 }
- const list = tabDataList.filter(tab => tab.routeData !== void 0 && tab.routeData.hasRouterLink.value === true)
+ const list = tabDataList.filter(tab => tab.routeData?.hasRouterLink.value === true)
const { hash: currentHash, query: currentQuery } = proxy.$route
const currentQueryLen = Object.keys(currentQuery).length
@@ -632,7 +632,7 @@ export default createComponent({
function cleanup () {
animateTimer !== null && clearTimeout(animateTimer)
stopAnimScroll()
- unwatchRoute !== void 0 && unwatchRoute()
+ unwatchRoute?.()
}
let hadRouteWatcher, hadActivated
diff --git a/ui/src/components/tabs/__tests__/QTab.cy.js b/ui/src/components/tabs/__tests__/QTab.cy.js
deleted file mode 100644
index aec94afe889..00000000000
--- a/ui/src/components/tabs/__tests__/QTab.cy.js
+++ /dev/null
@@ -1,79 +0,0 @@
-describe('Tab API', () => {
- describe('Props', () => {
- describe('Category: content', () => {
- describe('(prop): icon', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): label', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): alert', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): alert-icon', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): no-caps', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-
- describe('Category: general', () => {
- describe('(prop): name', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): tabindex', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-
- describe('Category: state', () => {
- describe('(prop): disable', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-
- describe('Category: style', () => {
- describe('(prop): content-class', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): ripple', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
- })
-
- describe('Slots', () => {
- describe('(slot): default', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-})
diff --git a/ui/src/components/tabs/__tests__/QTabs.cy.js b/ui/src/components/tabs/__tests__/QTabs.cy.js
deleted file mode 100644
index f55a3e8d1b8..00000000000
--- a/ui/src/components/tabs/__tests__/QTabs.cy.js
+++ /dev/null
@@ -1,147 +0,0 @@
-describe('Tabs API', () => {
- describe('Props', () => {
- describe('Category: content', () => {
- describe('(prop): vertical', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): outside-arrows', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): mobile-arrows', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): align', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): left-icon', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): right-icon', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): stretch', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): shrink', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): switch-indicator', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): narrow-indicator', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): inline-label', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): no-caps', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-
- describe('Category: content|behavior', () => {
- describe('(prop): breakpoint', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-
- describe('Category: model', () => {
- describe('(prop): model-value', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-
- describe('Category: style', () => {
- describe('(prop): active-color', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): active-bg-color', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): indicator-color', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): content-class', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): active-class', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): dense', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
- })
-
- describe('Slots', () => {
- describe('(slot): default', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-
- describe('Events', () => {
- describe('(event): update:model-value', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-})
diff --git a/ui/src/components/tabs/use-tab.js b/ui/src/components/tabs/use-tab.js
index 0e3d9782b05..cbd8e4a4fa5 100644
--- a/ui/src/components/tabs/use-tab.js
+++ b/ui/src/components/tabs/use-tab.js
@@ -99,13 +99,13 @@ export default function (props, slots, emit, routeData) {
))
function onClick (e, keyboard) {
- if (keyboard !== true && blurTargetRef.value !== null) {
- blurTargetRef.value.focus()
+ if (keyboard !== true && e?.qAvoidFocus !== true) {
+ blurTargetRef.value?.focus()
}
if (props.disable === true) {
// we should hinder native navigation though
- if (routeData !== void 0 && routeData.hasRouterLink.value === true) {
+ if (routeData?.hasRouterLink.value === true) {
stopAndPrevent(e)
}
return
@@ -140,7 +140,7 @@ export default function (props, slots, emit, routeData) {
if (
hardError === void 0 && (
softError === void 0
- || (softError.message !== void 0 && softError.message.startsWith('Avoided redundant navigation') === true)
+ || (softError.message?.startsWith('Avoided redundant navigation') === true)
)
) {
$tabs.updateModel({ name: props.name })
diff --git a/ui/src/components/time/QTime.js b/ui/src/components/time/QTime.js
index aade80786ec..37fb083342e 100644
--- a/ui/src/components/time/QTime.js
+++ b/ui/src/components/time/QTime.js
@@ -264,7 +264,7 @@ export default createComponent({
for (let val = start, index = start; val <= end; val += step, index++) {
const
actualVal = val + offset,
- disable = values !== void 0 && values.includes(actualVal) === false,
+ disable = values?.includes(actualVal) === false,
label = view.value === 'hour' && val === 0
? (computedFormat24h.value === true ? '00' : '12')
: val
diff --git a/ui/src/components/tree/QTree.js b/ui/src/components/tree/QTree.js
index 0d8b23cbe6f..a9266198957 100644
--- a/ui/src/components/tree/QTree.js
+++ b/ui/src/components/tree/QTree.js
@@ -334,7 +334,7 @@ export default createComponent({
node[ props.childrenKey ] = Array.isArray(children) === true ? children : []
nextTick(() => {
const localMeta = meta.value[ key ]
- if (localMeta && localMeta.isParent === true) {
+ if (localMeta?.isParent === true) {
localSetExpanded(key, true)
}
})
@@ -627,8 +627,7 @@ export default createComponent({
}
function blur (key) {
- const blurTarget = blurTargets[ key ]
- blurTarget && blurTarget.focus()
+ blurTargets[ key ]?.focus()
}
function onClick (node, meta, e, keyboard) {
diff --git a/ui/src/components/uploader/__tests__/QUploader.cy.js b/ui/src/components/uploader/__tests__/QUploader.cy.js
deleted file mode 100644
index 2e3ceb7dc20..00000000000
--- a/ui/src/components/uploader/__tests__/QUploader.cy.js
+++ /dev/null
@@ -1,191 +0,0 @@
-import QUploader from '../QUploader'
-
-describe('Uploader API', () => {
- describe('Props', () => {
- describe('Category: behavior', () => {
- describe('(prop): auto-upload', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): hide-upload-btn', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-
- describe('Category: content', () => {
- describe('(prop): label', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): no-thumbnails', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-
- describe('Category: state', () => {
- describe('(prop): disable', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): readonly', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-
- describe('Category: style', () => {
- describe('(prop): color', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): text-color', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): dark', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): square', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): flat', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): bordered', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
- })
-
- describe('Slots', () => {
- describe('(slot): header', () => {
- it('renders correctly using the scope', () => {
- // Taken from ui/dev/src/pages/form/uploader.vue
- const headerSlot = `
-
-
-
-
-
-
-
- Upload your files
-
-
- {{ scope.uploadSizeLabel }} / {{ scope.uploadProgressLabel }}
-
-
-
-
-
-
-
-
-
-`
-
- cy.mount(QUploader, {
- slots: {
- header: headerSlot
- }
- })
- })
- })
-
- describe('(slot): list', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-
- describe('Events', () => {
- describe('(event): added', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(event): removed', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(event): start', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(event): finish', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-
- describe('Methods', () => {
- describe('(method): upload', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(method): abort', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(method): reset', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(method): removeUploadedFiles', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(method): removeQueuedFiles', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(method): removeFile', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-})
diff --git a/ui/src/components/virtual-scroll/use-virtual-scroll.js b/ui/src/components/virtual-scroll/use-virtual-scroll.js
index 87376495d4c..673545ab589 100644
--- a/ui/src/components/virtual-scroll/use-virtual-scroll.js
+++ b/ui/src/components/virtual-scroll/use-virtual-scroll.js
@@ -40,7 +40,7 @@ const setOverflowAnchor = __QUASAR_SSR__ || window.getComputedStyle(document.bod
const el = children[ index ]
- if (el && el.dataset) {
+ if (el?.dataset) {
el.dataset.qVsAnchor = ''
}
})
@@ -422,7 +422,7 @@ export function useVirtualScroll ({
contentEl.addEventListener('focusout', onBlurRefocusFn)
setTimeout(() => {
- contentEl !== null && contentEl.removeEventListener('focusout', onBlurRefocusFn)
+ contentEl?.removeEventListener('focusout', onBlurRefocusFn)
})
}
@@ -533,7 +533,7 @@ export function useVirtualScroll ({
}
function onBlurRefocusFn () {
- contentRef.value !== null && contentRef.value !== void 0 && contentRef.value.focus()
+ contentRef.value?.focus()
}
function localResetVirtualScroll (toIndex, fullReset) {
diff --git a/ui/src/composables/__tests__/FieldWrapper.vue b/ui/src/composables/__tests__/FieldWrapper.vue
deleted file mode 100644
index bf7fb667a9b..00000000000
--- a/ui/src/composables/__tests__/FieldWrapper.vue
+++ /dev/null
@@ -1,54 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/ui/src/composables/__tests__/use-anchor.cy.js b/ui/src/composables/__tests__/use-anchor.cy.js
deleted file mode 100644
index 807114957ad..00000000000
--- a/ui/src/composables/__tests__/use-anchor.cy.js
+++ /dev/null
@@ -1,98 +0,0 @@
-import WrapperOne from '../../../components/menu/__tests__/WrapperOne.vue'
-
-describe('use-anchor API', () => {
- describe('Props', () => {
- describe('Category: behavior', () => {
- describe('(prop): target', () => {
- it('should use another target using a CSS selector', () => {
- cy.mount(WrapperOne, {
- props: {
- target: '.other-target'
- }
- })
-
- // Click on parent element should not show menu
- cy.dataCy('wrapper')
- .click()
- cy.dataCy('menu')
- .should('not.exist')
-
- // Click on other target should show menu
- cy.dataCy('other-target')
- .click()
- cy.dataCy('menu')
- .should('exist')
- })
-
- it('should not show when target is false', () => {
- cy.mount(WrapperOne, {
- props: {
- target: false
- }
- })
-
- cy.dataCy('wrapper')
- .click()
- cy.dataCy('menu')
- .should('not.exist')
- })
- })
-
- describe('(prop): no-parent-event', () => {
- it('should not show when clicking parent with no-parent-event true', () => {
- cy.mount(WrapperOne, {
- props: {
- 'no-parent-event': true
- }
- })
-
- cy.dataCy('wrapper')
- .click()
- cy.dataCy('menu')
- .should('not.exist')
- })
-
- it('should show when clicking parent with no-parent-event false', () => {
- cy.mount(WrapperOne, {
- props: {
- 'no-parent-event': false
- }
- })
-
- cy.dataCy('wrapper')
- .click()
- cy.dataCy('menu')
- .should('exist')
- })
- })
-
- describe('(prop): context-menu', () => {
- it('should not show when left clicking parent', () => {
- cy.mount(WrapperOne, {
- props: {
- 'context-menu': true
- }
- })
-
- cy.dataCy('wrapper')
- .click()
- cy.dataCy('menu')
- .should('not.exist')
- })
-
- it('should show when right clicking parent', () => {
- cy.mount(WrapperOne, {
- props: {
- 'context-menu': true
- }
- })
-
- cy.dataCy('wrapper')
- .rightclick()
- cy.dataCy('menu')
- .should('exist')
- })
- })
- })
- })
-})
diff --git a/ui/src/composables/__tests__/use-field.cy.js b/ui/src/composables/__tests__/use-field.cy.js
deleted file mode 100644
index cd391296b17..00000000000
--- a/ui/src/composables/__tests__/use-field.cy.js
+++ /dev/null
@@ -1,547 +0,0 @@
-import FieldWrapper from './FieldWrapper.vue'
-import { ref } from 'vue'
-import { vModelAdapter } from '@quasar/quasar-app-extension-testing-e2e-cypress'
-import Icons from '../../../../icon-set/material-icons.mjs'
-
-function mountQFieldWrapper (options) {
- return cy.mount(FieldWrapper, options)
-}
-
-function getHostElement () {
- return cy.get('.select-root')
-}
-
-const selectOptions = [ 'Option 1', 'Option 2' ]
-
-describe('use-field API', () => {
- describe('Props', () => {
- describe('Category: behavior', () => {
- describe('(prop): autofocus', () => {
- it('should autofocus on component', () => {
- mountQFieldWrapper({
- props: {
- autofocus: true
- }
- })
-
- getHostElement()
- .get('.q-field--focused')
- .should('exist')
- .get('input')
- .should('have.focus')
- })
- })
-
- describe('(prop): for', () => {
- it('should set the for attribute for the value and the id of the control', () => {
- const forProp = 'hello-there'
-
- mountQFieldWrapper({
- props: {
- for: forProp
- }
- })
-
- getHostElement().should('have.attr', 'for', forProp)
- getHostElement().get('input').should('have.id', forProp)
- })
- })
-
- describe('(prop): name', () => {
- it('should use the value of the for prop as control name if name is not set', () => {
- const forProp = 'hello-there'
- const model = ref(selectOptions[ 0 ])
-
- mountQFieldWrapper({
- props: {
- ...vModelAdapter(model),
- options: selectOptions,
- for: forProp
- }
- })
-
- getHostElement().get('select').should('have.attr', 'name', forProp)
- })
-
- it('should set the name of the control', () => {
- const name = 'hello-there'
- const model = ref(selectOptions[ 0 ])
-
- mountQFieldWrapper({
- props: {
- ...vModelAdapter(model),
- options: selectOptions,
- name
- }
- })
-
- getHostElement().get('select').should('have.attr', 'name', name)
- })
- })
- })
-
- describe('Category: behavior|content', () => {
- describe('(prop): loading', () => {
- it('should set the component into a loading state', () => {
- mountQFieldWrapper({
- props: {
- loading: true
- }
- })
-
- getHostElement().get('.q-spinner').should('exist')
- })
- })
-
- describe('(prop): clearable', () => {
- it('should append a clear icon', () => {
- const model = ref(selectOptions[ 0 ])
-
- mountQFieldWrapper({
- props: {
- ...vModelAdapter(model),
- clearable: true,
- options: selectOptions
- }
- })
-
- getHostElement().find('input').should('exist').should('have.value', model.value)
- getHostElement().get('button[type="button"]').contains(Icons.field.clear).click()
- getHostElement().get('input').should('have.value', '')
- })
- })
- })
-
- describe('Category: content', () => {
- describe('(prop): label', () => {
- it('should show the label when supplied', () => {
- const label = 'Select something'
- cy.mount(FieldWrapper, {
- props: {
- label
- }
- })
- cy.get('.select-root')
- .should('contain.text', label)
- })
-
- it('should show the label centered when not focused', () => {
- const label = 'Select something'
- cy.mount(FieldWrapper, {
- props: {
- label
- }
- })
- cy.get('.select-root')
- .get(`div:contains(${ label })`)
- .should('exist')
- .should('not.have.class', 'q-field--float')
- })
-
- it('should show the label stacked when focused', () => {
- const label = 'Select something'
- cy.mount(FieldWrapper, {
- props: {
- label
- }
- })
- cy.get('.select-root [tabindex="0"]')
- .focus()
- cy.get(`.select-root:contains(${ label })`)
- .should('exist')
- .should('have.class', 'q-field--float')
- })
- })
-
- describe('(prop): stack-label', () => {
- it('should show the label stacked', () => {
- const label = 'Select something'
- cy.mount(FieldWrapper, {
- props: {
- label,
- stackLabel: true
- }
- })
- cy.get(`.select-root:contains(${ label })`)
- .should('exist')
- .should('have.class', 'q-field--float')
- })
- })
-
- describe('(prop): hint', () => {
- it('should show a hint text', () => {
- const hint = 'Select something'
- cy.mount(FieldWrapper, {
- props: {
- hint
- }
- })
- cy.get(`.select-root:contains(${ hint })`)
- .should('exist')
- })
- })
-
- describe('(prop): hide-hint', () => {
- it('should not show a hint text when not focused', () => {
- const hint = 'Select something'
- cy.mount(FieldWrapper, {
- props: {
- hint,
- hideHint: true
- }
- })
- cy.get(`.select-root:contains(${ hint })`)
- .should('not.exist')
- })
- it('should show a hint text when focused', () => {
- const hint = 'Select something'
- cy.mount(FieldWrapper, {
- props: {
- hint,
- hideHint: true
- }
- })
- cy.get('.select-root [tabindex="0"]')
- .focus()
- cy.get(`.select-root:contains(${ hint })`)
- .should('exist')
- })
- })
-
- describe('(prop): prefix', () => {
- it('should display a prefix', () => {
- const prefix = 'Hello there!'
- mountQFieldWrapper({
- props: {
- prefix
- }
- })
-
- getHostElement().get('.q-field__prefix').should('exist').should('contain', prefix)
- })
- })
-
- describe('(prop): suffix', () => {
- it('should display a suffix', () => {
- const suffix = 'Hello there!'
- mountQFieldWrapper({
- props: {
- suffix
- }
- })
-
- getHostElement().get('.q-field__suffix').should('exist').should('contain', suffix)
- })
- })
-
- describe('(prop): clear-icon', () => {
- it('should display custom clear-icon when one is set', () => {
- const model = ref(selectOptions[ 0 ])
- const clearIcon = 'custom-clear-icon'
- mountQFieldWrapper({
- props: {
- ...vModelAdapter(model),
- options: selectOptions,
- clearable: true,
- clearIcon
- }
- })
-
- getHostElement().get('.q-field__append').get('button').should('contain', clearIcon)
- })
- })
-
- describe('(prop): label-slot', () => {
- it('should force the use of the label slot even when a label prop is set', () => {
- const model = ref(selectOptions[ 0 ])
- const labelSlot = 'Hello world'
-
- mountQFieldWrapper({
- props: {
- ...vModelAdapter(model),
- options: selectOptions,
- labelSlot: true,
- label: 'Hello there'
- },
- slots: {
- label: () => labelSlot
- }
- })
-
- getHostElement().find('.q-field__label').should('contain.text', labelSlot)
- })
- })
-
- describe('(prop): bottom-slots', () => {
- it('should use a bottom error slot', () => {
- const model = ref(selectOptions[ 0 ])
- const bottomSlot = 'Hello there'
-
- mountQFieldWrapper({
- props: {
- ...vModelAdapter(model),
- options: selectOptions,
- bottomSlotSlots: true,
- error: true
- },
- slots: {
- error: () => bottomSlot
- }
- })
-
- getHostElement().find('.q-field__bottom')
- .should('contain.text', bottomSlot)
- })
-
- it('should use a bottom hint slot', () => {
- const model = ref(selectOptions[ 0 ])
- const bottomSlot = 'Hello there'
-
- mountQFieldWrapper({
- props: {
- ...vModelAdapter(model),
- options: selectOptions,
- bottomSlots: true
- },
- slots: {
- hint: () => bottomSlot
- }
- })
-
- getHostElement().find('.q-field__bottom').should('contain.text', bottomSlot)
- })
-
- it('should use a bottom counter slot', () => {
- const model = ref(selectOptions[ 0 ])
-
- mountQFieldWrapper({
- props: {
- ...vModelAdapter(model),
- options: selectOptions,
- counter: true
- }
- })
-
- getHostElement().find('.q-field__bottom').should('contain.text', model.value.length)
- })
- })
-
- describe('(prop): counter', () => {
- it('should show an automatic counter on bottom right', () => {
- const model = ref(selectOptions[ 0 ])
- mountQFieldWrapper({
- props: {
- ...vModelAdapter(model),
- options: selectOptions,
- counter: true
- }
- })
-
- getHostElement().get('.q-field__counter').should('contain', model.value.length)
- })
- })
- })
-
- describe('Category: state', () => {
- describe('(prop): disable', () => {
- it('should put the component on disable state', () => {
- mountQFieldWrapper({
- props: {
- disable: true
- }
- })
-
- getHostElement().should('have.class', 'q-field--disabled')
- })
- })
-
- describe('(prop): readonly', () => {
- it('should put the component on readonly state', () => {
- mountQFieldWrapper({
- props: {
- readonly: true
- }
- })
-
- getHostElement().should('have.class', 'q-field--readonly')
- })
- })
- })
-
- describe('Category: style', () => {
- describe('(prop): label-color', () => {
- it('should display a label color', () => {
- const label = 'Hello there!'
- mountQFieldWrapper({
- props: {
- label,
- labelColor: 'red'
- }
- })
-
- getHostElement().get('.q-field__label.text-red').should('contain', label)
- })
- })
-
- describe('(prop): color', () => {
- it('should set a color on the component', () => {
- mountQFieldWrapper()
- getHostElement().get('.q-field__control.text-red').should('not.exist')
-
- getHostElement().get('input').then(() => {
- Cypress.vueWrapper.setProps({ color: 'red' })
-
- getHostElement().get('.q-field__control.text-red').should('exist')
- })
- })
- })
-
- describe('(prop): bg-color', () => {
- it('should display a background color', () => {
- mountQFieldWrapper({
- props: {
- bgColor: 'red'
- }
- })
-
- getHostElement().get('.q-field__control.bg-red').should('exist')
- })
- })
-
- describe('(prop): hide-bottom-space', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- const fieldLooks = [ 'item-aligned', 'dark', 'filled', 'outlined', 'borderless', 'standout', 'rounded', 'square', 'dense' ]
- for (const style of fieldLooks) {
- describe(`(prop): ${ style }`, () => {
- it(`should apply ${ style } design style`, () => {
- mountQFieldWrapper({
- props: {
- [ style ]: true
- }
- })
-
- getHostElement().get(`.q-field--${ style }`).should('exist')
- })
- })
- }
- })
- })
-
- describe('Slots', () => {
- const slots = [ 'prepend', 'append', 'before', 'after', 'label' ]
-
- describe('(slot): default', () => {
- it('should use the default slot', () => {
- const model = ref(selectOptions[ 0 ])
- const labelSlot = 'Hello world'
-
- mountQFieldWrapper({
- props: {
- ...vModelAdapter(model),
- options: selectOptions,
- labelSlot: true
- },
- slots: {
- default: () => labelSlot
- }
- })
-
- getHostElement().should('contain.text', labelSlot)
- })
- })
-
- for (const slot of slots) {
- describe(`(slot): ${ slot }`, () => {
- it(`should append a '${ slot }' slot`, () => {
- const model = ref(selectOptions[ 0 ])
- const labelSlot = 'Hello world'
-
- mountQFieldWrapper({
- props: {
- ...vModelAdapter(model),
- options: selectOptions,
- labelSlot: true
- },
- slots: {
- [ slot ]: () => labelSlot
- }
- })
-
- getHostElement().get(`.q-field__${ slot }`).should('contain.text', labelSlot)
- })
- })
- }
- })
-
- describe('Events', () => {
- describe('(event): clear', () => {
- it('should emit the clear event when the clear button is clicked', () => {
- const model = ref(selectOptions[ 0 ])
- const fn = cy.stub()
-
- mountQFieldWrapper({
- props: {
- ...vModelAdapter(model),
- clearable: true,
- options: selectOptions,
- onClear: fn
- }
- })
-
- getHostElement().get('button[type="button"]')
- .contains(Icons.field.clear).click()
- .then(() => {
- expect(fn).to.be.calledWith()
- })
- })
- })
- })
-
- describe('Methods', () => {
- describe('(method): focus', () => {
- it('should focus the component', () => {
- mountQFieldWrapper()
-
- getHostElement()
- .get('input')
- .should('not.have.focus')
- getHostElement()
- .then(() => {
- Cypress.vueWrapper.vm.focusMethod()
- })
- getHostElement()
- .get('input')
- .should('have.focus')
- })
- })
-
- describe('(method): blur', () => {
- it('should blur the component', () => {
- mountQFieldWrapper()
-
- getHostElement()
- .get('input').focus()
- getHostElement()
- .get('.q-field--focused')
- .as('focused-element')
- .should('exist')
-
- cy.get('@focused-element')
- .get('input')
- .should('have.focus')
-
- getHostElement()
- .then(() => {
- Cypress.vueWrapper.vm.blur()
- })
-
- cy.get('@focused-element').should('not.exist')
- getHostElement().get('input').should('not.have.focus')
- })
- })
- })
-})
diff --git a/ui/src/composables/__tests__/use-file.cy.js b/ui/src/composables/__tests__/use-file.cy.js
deleted file mode 100644
index 6e1a691598c..00000000000
--- a/ui/src/composables/__tests__/use-file.cy.js
+++ /dev/null
@@ -1,69 +0,0 @@
-describe('use-file API', () => {
- describe('Props', () => {
- describe('Category: behavior', () => {
- describe('(prop): multiple', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): accept', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): capture', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): max-file-size', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): max-total-size', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): max-files', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): filter', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
- })
-
- describe('Events', () => {
- describe('(event): rejected', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-
- describe('Methods', () => {
- describe('(method): pickFiles', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(method): addFiles', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-})
diff --git a/ui/src/composables/__tests__/use-fullscreen.cy.js b/ui/src/composables/__tests__/use-fullscreen.cy.js
deleted file mode 100644
index f397b839eef..00000000000
--- a/ui/src/composables/__tests__/use-fullscreen.cy.js
+++ /dev/null
@@ -1,37 +0,0 @@
-describe('use-fullscreen API', () => {
- describe('Props', () => {
- describe('Category: behavior', () => {
- describe('(prop): fullscreen', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): no-route-fullscreen-exit', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
- })
-
- describe('Methods', () => {
- describe('(method): toggleFullscreen', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(method): setFullscreen', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(method): exitFullscreen', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-})
diff --git a/ui/src/composables/__tests__/use-model-toggle.cy.js b/ui/src/composables/__tests__/use-model-toggle.cy.js
deleted file mode 100644
index 7dc701c39b5..00000000000
--- a/ui/src/composables/__tests__/use-model-toggle.cy.js
+++ /dev/null
@@ -1,350 +0,0 @@
-/* eslint-disable no-unused-expressions */
-import { vModelAdapter } from '@quasar/quasar-app-extension-testing-e2e-cypress'
-import { ref } from 'vue'
-import WrapperOne from '../../../components/menu/__tests__/WrapperOne.vue'
-import WrapperTwo from '../../../components/menu/__tests__/WrapperTwo.vue'
-
-describe('use-model-toggle API', () => {
- describe('Props', () => {
- describe('Category: model', () => {
- describe('(prop): model-value', () => {
- it('should open the dialog when modifying the model-value', () => {
- const model = ref(false)
- cy.mount(WrapperOne, {
- props: {
- ...vModelAdapter(model)
- }
- })
- cy.dataCy('wrapper')
- cy.dataCy('menu')
- .should('not.exist')
- .then(() => {
- model.value = true
- cy.dataCy('menu')
- .should('exist')
- })
- })
-
- it('should close the dialog when modifying the model-value', () => {
- const model = ref(true)
- cy.mount(WrapperOne, {
- props: {
- ...vModelAdapter(model)
- }
- })
- cy.dataCy('wrapper')
- cy.dataCy('menu')
- .should('exist')
- .then(() => {
- model.value = false
- cy.dataCy('menu')
- .should('not.exist')
- })
- })
- })
- })
- })
-
- describe('Events', () => {
- describe('(event): update:model-value', () => {
- it('should emit @update:model-value event when state changes', () => {
- const fn = cy.stub()
- cy.mount(WrapperOne, {
- props: {
- 'onUpdate:modelValue': fn
- }
- })
-
- expect(fn).not.to.be.called
- cy.dataCy('wrapper')
- .click()
- cy.dataCy('menu')
- .should('exist')
- .then(() => {
- expect(fn).to.be.called
- })
- })
- })
-
- describe('(event): show', () => {
- it('should emit @show event when menu is triggered by parent', () => {
- const fn = cy.stub()
- cy.mount(WrapperOne, {
- props: {
- onShow: fn
- }
- })
-
- expect(fn).not.to.be.called
- cy.dataCy('wrapper')
- .click()
- // eslint-disable-next-line cypress/no-unnecessary-waiting
- cy.dataCy('menu')
- .should('exist')
- .wait(300) // Await menu animation
- .then(() => {
- expect(fn).to.be.called
- })
- })
-
- it('should emit @show event when component is triggered with the show() method', () => {
- const fn = cy.stub()
- cy.mount(WrapperOne, {
- props: {
- onShow: fn
- }
- })
-
- expect(fn).not.to.be.called
- cy.dataCy('wrapper')
- cy.dataCy('method-show')
- .click({ force: true }) // Element is hidden to prevent clogging the window
- // eslint-disable-next-line cypress/no-unnecessary-waiting
- cy.dataCy('menu')
- .should('exist')
- .wait(300) // Await menu animation
- .then(() => {
- expect(fn).to.be.called
- })
- })
- })
-
- describe('(event): before-show', () => {
- it('should emit @before-show event when menu is triggered by parent', () => {
- const fn = cy.stub()
- cy.mount(WrapperOne, {
- props: {
- onBeforeShow: fn
- }
- })
-
- expect(fn).not.to.be.called
- cy.dataCy('wrapper')
- .click()
- cy.dataCy('menu')
- .should('exist')
- .then(() => {
- expect(fn).to.be.called
- })
- })
-
- it('should emit @before-show event when component is triggered with the show() method', () => {
- const fn = cy.stub()
- cy.mount(WrapperOne, {
- props: {
- onBeforeShow: fn
- }
- })
-
- expect(fn).not.to.be.called
- cy.dataCy('wrapper')
- cy.dataCy('method-show')
- .click({ force: true }) // Element is hidden to prevent clogging the window
- cy.dataCy('menu')
- .should('exist')
- .then(() => {
- expect(fn).to.be.called
- })
- })
- })
-
- describe('(event): hide', () => {
- it('should emit @hide event when menu is triggered by parent', () => {
- const fn = cy.stub()
- cy.mount(WrapperOne, {
- props: {
- onHide: fn,
- // After the dialog has been closed, the hide event is fired only after transitionDuration.
- // The default value for transitionDuration is 300ms. Hence, we cannot depend on the dialog closing
- // to conclude that the event has been fired. So let's set it to 0 so that it fires immediately.
- transitionDuration: 0
- }
- })
-
- expect(fn).not.to.be.called
- cy.dataCy('wrapper')
- .click()
- cy.dataCy('menu')
- .should('exist')
- .then(() => {
- expect(fn).not.to.be.called
- })
-
- // We are clicking x distance from the left to ensure it always clicks on the body
- // For some reason it some times does not work without it
- cy.get('body')
- .click(499, 0)
- cy.dataCy('menu')
- .should('not.exist')
- .then(() => {
- expect(fn).to.be.called
- })
- })
-
- it('should emit @hide event when component is triggered with the hide() method', () => {
- const fn = cy.stub()
- cy.mount(WrapperOne, {
- props: {
- onHide: fn,
- // After the dialog has been closed, the hide event is fired only after transitionDuration.
- // The default value for transitionDuration is 300ms. Hence, we cannot depend on the dialog closing
- // to conclude that the event has been fired. So let's set it to 0 so that it fires immediately.
- transitionDuration: 0
- }
- })
-
- expect(fn).not.to.be.called
- cy.dataCy('wrapper')
- cy.dataCy('method-show')
- .click({ force: true }) // Element is hidden to prevent clogging the window
- cy.dataCy('menu')
- .should('exist')
- .then(() => {
- expect(fn).not.to.be.called
- })
- cy.dataCy('method-hide')
- .click({ force: true })
- cy.dataCy('menu')
- .should('not.exist') // Element is hidden to prevent clogging the window
- .then(() => {
- expect(fn).to.be.called
- })
- })
-
- it('should emit @hide event after transition duration', () => {
- const fn = cy.stub()
- cy.mount(WrapperOne, {
- props: {
- onHide: fn,
- transitionDuration: 500
- }
- })
-
- expect(fn).not.to.be.called
- cy.dataCy('method-show')
- .click({ force: true })
-
- cy.dataCy('wrapper')
- cy.dataCy('method-hide')
- .click({ force: true })
-
- // eslint-disable-next-line cypress/no-unnecessary-waiting
- cy.dataCy('menu')
- .wait(300)
- .then(() => {
- expect(fn).not.to.be.called
- })
- .wait(500)
- .then(() => {
- expect(fn).to.be.called
- })
- })
- })
-
- describe('(event): before-hide', () => {
- it('should emit @before-hide event when menu is triggered by parent', () => {
- const fn = cy.stub()
- cy.mount(WrapperOne, {
- props: {
- onBeforeHide: fn
- }
- })
-
- expect(fn).not.to.be.called
- cy.dataCy('wrapper')
- .click()
- cy.dataCy('menu')
- .should('exist')
- .then(() => {
- expect(fn).not.to.be.called
- })
- cy.get('body')
- .click(499, 0)
- cy.get('body')
- .then(() => {
- expect(fn).to.be.called
- })
- cy.dataCy('menu')
- .should('not.exist')
- })
-
- it('should emit @before-hide event when component is triggered with the show() method', () => {
- const fn = cy.stub()
- cy.mount(WrapperOne, {
- props: {
- onBeforeHide: fn
- }
- })
-
- expect(fn).not.to.be.called
- cy.dataCy('wrapper')
- cy.dataCy('method-show')
- .click({ force: true }) // Element is hidden to prevent clogging the window
- cy.dataCy('menu')
- .should('exist')
- .then(() => {
- expect(fn).not.to.be.called
- })
- cy.dataCy('method-hide')
- .click({ force: true })
- cy.dataCy('method-hide')
- .then(() => {
- expect(fn).to.be.called
- })
- cy.dataCy('menu')
- .should('not.exist')
- })
- })
- })
-
- describe('Methods', () => {
- describe('(method): show', () => {
- it('should trigger the menu to show', () => {
- cy.mount(WrapperOne)
-
- cy.dataCy('wrapper')
- cy.dataCy('method-show')
- .click({ force: true })
- cy.dataCy('menu')
- .should('exist')
- })
- })
-
- describe('(method): hide', () => {
- it('should trigger the menu to hide', () => {
- cy.mount(WrapperOne)
-
- cy.dataCy('wrapper')
- .click()
- cy.dataCy('menu')
- .should('exist')
- cy.dataCy('method-hide')
- .click({ force: true })
- cy.dataCy('menu')
- .should('not.exist')
- })
- })
-
- describe('(method): toggle', () => {
- it('should toggle the menu', () => {
- cy.mount(WrapperTwo)
-
- cy.dataCy('wrapper')
- .then(() => {
- // Need to call method from wrapper
- // Click a button closes the menu
- Cypress.vueWrapper.vm.toggle()
- })
- cy.dataCy('menu')
- .should('exist')
- .then(() => {
- // Need to call method from wrapper
- // Click a button closes the menu
- Cypress.vueWrapper.vm.toggle()
- })
- cy.dataCy('menu')
- .should('not.exist')
- })
- })
- })
-})
diff --git a/ui/src/composables/__tests__/use-portal.cy.js b/ui/src/composables/__tests__/use-portal.cy.js
deleted file mode 100644
index f5160569561..00000000000
--- a/ui/src/composables/__tests__/use-portal.cy.js
+++ /dev/null
@@ -1,4 +0,0 @@
-describe('use-portal API', () => {
- describe('Props', () => {
- })
-})
diff --git a/ui/src/composables/__tests__/use-router-link.cy.js b/ui/src/composables/__tests__/use-router-link.cy.js
deleted file mode 100644
index 12a052fae5a..00000000000
--- a/ui/src/composables/__tests__/use-router-link.cy.js
+++ /dev/null
@@ -1,55 +0,0 @@
-describe('use-router-link API', () => {
- describe('Props', () => {
- describe('Category: navigation', () => {
- describe('(prop): to', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): exact', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): replace', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): active-class', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): exact-active-class', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): href', () => {
- it.skip(' ', () => {
- //
- })
- })
-
- describe('(prop): target', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
-
- describe('Category: state', () => {
- describe('(prop): disable', () => {
- it.skip(' ', () => {
- //
- })
- })
- })
- })
-})
diff --git a/ui/src/composables/__tests__/use-validate.cy.js b/ui/src/composables/__tests__/use-validate.cy.js
deleted file mode 100644
index 8c4bbef979e..00000000000
--- a/ui/src/composables/__tests__/use-validate.cy.js
+++ /dev/null
@@ -1,257 +0,0 @@
-import FieldWrapper from './FieldWrapper.vue'
-import Icons from '../../../../icon-set/material-icons.mjs'
-import { ref } from 'vue'
-import { vModelAdapter } from '@quasar/quasar-app-extension-testing-e2e-cypress'
-
-function mountQFieldWrapper (options) {
- return cy.mount(FieldWrapper, options)
-}
-
-function getHostElement () {
- return cy.get('.select-root')
-}
-
-describe('use-validate API', () => {
- describe('Props', () => {
- describe('Category: behavior', () => {
- describe('(prop): error', () => {
- it('should mark the field as having an error', () => {
- mountQFieldWrapper()
- getHostElement().should('not.have.class', 'q-field--error')
-
- getHostElement().then(() => {
- Cypress.vueWrapper.setProps({ error: true })
- })
-
- getHostElement().should('have.class', 'q-field--error')
- })
- })
-
- describe('(prop): rules', () => {
- it('should validate value using custom validation logic', () => {
- const errorMessage = 'Selected value should be greater than 10 characters'
- const model = ref('Option 1')
- const options = [ 'Option 1', 'Option 2' ]
-
- mountQFieldWrapper({
- props: {
- ...vModelAdapter(model),
- options,
- rules: [ val => val.length > 10 || errorMessage ]
- }
- })
-
- getHostElement().then(() => {
- model.value = 'Option 2'
- })
- getHostElement().get('.q-field__messages').should('contain.text', errorMessage)
- })
-
- it('should validate email using inbuilt validation logic', () => {
- const errorMessage = 'Enter a valid email address'
- const model = ref('Option 1')
- const options = [ 'Option 1', 'Option 2' ]
- mountQFieldWrapper({
- props: {
- ...vModelAdapter(model),
- options,
- rules: [ (val, rules) => rules.email(val) || errorMessage ]
- }
- })
- getHostElement().then(() => {
- model.value = 'Option 2'
- })
- getHostElement().get('.q-field__messages').should('contain.text', errorMessage)
- })
- })
-
- describe('(prop): reactive-rules', () => {
- it('should trigger validation when there\'s a change of rules', () => {
- const errorMessage = 'Error message'
- const model = ref('Option 1')
- const options = [ 'Option 1', 'Option 2' ]
- mountQFieldWrapper({
- props: {
- ...vModelAdapter(model),
- options
- }
- })
-
- getHostElement().then(() => {
- Cypress.vueWrapper.setProps({ rules: [ (value) => value.length < 3 || errorMessage ] })
-
- Cypress.vueWrapper.vm.focusMethod()
- Cypress.vueWrapper.vm.blur()
- })
- getHostElement().get('.q-field__messages').should('not.exist')
-
- getHostElement().then(() => {
- Cypress.vueWrapper.setProps({ reactiveRules: true, rules: [ (value, rules) => rules.email(value) || errorMessage ] })
- })
- getHostElement().get('.q-field__messages').should('contain.text', errorMessage)
- })
- })
-
- describe('(prop): lazy-rules', () => {
- it('should validate the input only when component loses focus', () => {
- const errorMessage = 'Use a max 3 of characters'
- const model = ref('Option 1')
- const options = [ 'Option 1', 'Option 2' ]
- mountQFieldWrapper({
- props: {
- ...vModelAdapter(model),
- options,
- lazyRules: true,
- rules: [ () => errorMessage ]
- }
- })
-
- getHostElement().select('Option 2')
- getHostElement().get('.q-field__messages').should('not.contain', errorMessage)
-
- getHostElement().then(() => {
- Cypress.vueWrapper.vm.blur()
- getHostElement().get('.q-field__messages').should('contain.text', errorMessage)
- })
- })
- })
- })
-
- describe('Category: content', () => {
- describe('(prop): error-message', () => {
- it('should show an error-message when error is true', () => {
- const message = 'Please select something'
- cy.mount(FieldWrapper, {
- props: {
- error: true,
- errorMessage: message
- }
- })
- cy.get('.select-root')
- .should('include.text', message)
- })
-
- it('should not show an error-message when error is false', () => {
- const message = 'Please select something'
- cy.mount(FieldWrapper, {
- props: {
- error: false,
- errorMessage: message
- }
- })
- cy.get('.select-root')
- .should('not.include.text', message)
- })
- })
-
- describe('(prop): no-error-icon', () => {
- it('should not show an error icon when error is true', () => {
- cy.mount(FieldWrapper, {
- props: {
- error: true,
- noErrorIcon: true
- }
- })
- cy.get('.select-root')
- .get(`i:contains(${ Icons.field.error })`)
- .should('not.exist')
- })
-
- it('should show an error icon when error is true an no-error-icon is false', () => {
- cy.mount(FieldWrapper, {
- props: {
- error: true,
- noErrorIcon: false
- }
- })
- cy.get('.select-root')
- .get(`i:contains(${ Icons.field.error })`)
- .should('exist')
- })
- })
- })
-
- describe('Category: model', () => {
- describe('(prop): model-value', () => {
- it('should correctly update the model value', () => {
- const model = ref()
- const options = [ 'Option 1', 'Option 2' ]
- mountQFieldWrapper({
- props: {
- ...vModelAdapter(model),
- options
- }
- })
- getHostElement().get('input').should('not.have.value', options[ 0 ])
-
- getHostElement().then(() => {
- model.value = options[ 0 ]
- })
-
- getHostElement().get('input').should('have.value', options[ 0 ])
- })
- })
- })
- })
-
- describe('Methods', () => {
- describe('(method): resetValidation', () => {
- it('should reset validation', () => {
- const errorMessage = 'Use a max 3 of characters'
- const model = ref('Option 1')
- const options = [ 'Option 1', 'Option 2' ]
- mountQFieldWrapper({
- props: {
- ...vModelAdapter(model),
- options,
- rules: [ val => val.length <= 3 || errorMessage ],
- lazyRules: 'ondemand'
- }
- })
-
- getHostElement().then(() => {
- model.value = 'Option 2'
- })
- getHostElement().get('.q-field__messages').should('not.contain', errorMessage)
-
- getHostElement().then(() => {
- Cypress.vueWrapper.vm.validate()
- getHostElement().get('.q-field__messages').should('contain', errorMessage)
- })
-
- getHostElement().then(() => {
- Cypress.vueWrapper.vm.resetValidation()
- getHostElement().get('.q-field__messages').should('not.contain', errorMessage)
- })
- })
- })
-
- describe('(method): validate', () => {
- it('should validate the input only when component\'s validate() method is called', () => {
- const errorMessage = 'Use a max 3 of characters'
- const model = ref('Option 1')
- const options = [ 'Option 1', 'Option 2' ]
- mountQFieldWrapper({
- props: {
- ...vModelAdapter(model),
- options,
- rules: [ val => val.length <= 3 || errorMessage ],
- lazyRules: 'ondemand'
- }
- })
-
- getHostElement().then(() => {
- model.value = 'Option 2'
- })
- getHostElement().get('.q-field__messages').should('not.contain', errorMessage)
-
- getHostElement().get('input').then(() => {
- Cypress.vueWrapper.vm.blur()
- getHostElement().get('.q-field__messages').should('not.contain', errorMessage)
- Cypress.vueWrapper.vm.validate()
- getHostElement().get('.q-field__messages').should('contain', errorMessage)
- })
- })
- })
- })
-})
diff --git a/ui/src/composables/private.use-field/use-field.js b/ui/src/composables/private.use-field/use-field.js
index 04e0e98a3c5..a2e015b4a64 100644
--- a/ui/src/composables/private.use-field/use-field.js
+++ b/ui/src/composables/private.use-field/use-field.js
@@ -265,12 +265,12 @@ export default function (state) {
function focusHandler () {
const el = document.activeElement
- let target = state.targetRef !== void 0 && state.targetRef.value
+ let target = state.targetRef?.value
if (target && (el === null || el.id !== state.targetUid.value)) {
target.hasAttribute('tabindex') === true || (target = target.querySelector('[tabindex]'))
- if (target && target !== el) {
- target.focus({ preventScroll: true })
+ if (target !== el) {
+ target?.focus({ preventScroll: true })
}
}
}
@@ -318,7 +318,7 @@ export default function (state) {
emit('blur', e)
}
- then !== void 0 && then()
+ then?.()
})
}
@@ -327,7 +327,7 @@ export default function (state) {
stopAndPrevent(e)
if ($q.platform.is.mobile !== true) {
- const el = (state.targetRef !== void 0 && state.targetRef.value) || state.rootRef.value
+ const el = state.targetRef?.value || state.rootRef.value
el.focus()
}
else if (state.rootRef.value.contains(document.activeElement) === true) {
diff --git a/ui/src/composables/private.use-file/use-file.js b/ui/src/composables/private.use-file/use-file.js
index 8cbe4d1c123..4926f356752 100644
--- a/ui/src/composables/private.use-file/use-file.js
+++ b/ui/src/composables/private.use-file/use-file.js
@@ -19,7 +19,10 @@ function filterFiles (files, rejectedFiles, failedPropValidation, filterFn) {
}
function stopAndPreventDrag (e) {
- e && e.dataTransfer && (e.dataTransfer.dropEffect = 'copy')
+ if (e?.dataTransfer) {
+ e.dataTransfer.dropEffect = 'copy'
+ }
+
stopAndPrevent(e)
}
@@ -69,13 +72,13 @@ export default function ({
e = { target: null }
}
- if (e.target !== null && e.target.matches('input[type="file"]') === true) {
+ if (e.target?.matches('input[type="file"]') === true) {
// stop propagation if it's not a real pointer event
e.clientX === 0 && e.clientY === 0 && stop(e)
}
else {
const input = getFileInput()
- input && input !== e.target && input.click(e)
+ if (input !== e.target) input?.click(e)
}
}
}
diff --git a/ui/src/composables/private.use-model-toggle/use-model-toggle.js b/ui/src/composables/private.use-model-toggle/use-model-toggle.js
index c2f2c7e868b..672ff183796 100644
--- a/ui/src/composables/private.use-model-toggle/use-model-toggle.js
+++ b/ui/src/composables/private.use-model-toggle/use-model-toggle.js
@@ -41,8 +41,8 @@ export default function ({
function show (evt) {
if (
- props.disable === true
- || (evt !== void 0 && evt.qAnchorHandled === true)
+ (props.disable === true)
+ || (evt?.qAnchorHandled === true)
|| (canShow !== void 0 && canShow(evt) !== true)
) return
diff --git a/ui/src/composables/private.use-refocus-target/use-refocus-target.js b/ui/src/composables/private.use-refocus-target/use-refocus-target.js
index cbfd52f7ba6..07f3a14ca6a 100644
--- a/ui/src/composables/private.use-refocus-target/use-refocus-target.js
+++ b/ui/src/composables/private.use-refocus-target/use-refocus-target.js
@@ -18,18 +18,19 @@ export default function (props, rootRef) {
function refocusTarget (e) {
const root = rootRef.value
- if (e !== void 0 && e.type.indexOf('key') === 0) {
+ if (e?.qAvoidFocus === true) return
+
+ if (e?.type.indexOf('key') === 0) {
if (
- root !== null
- && document.activeElement !== root
- && root.contains(document.activeElement) === true
+ document.activeElement !== root
+ && root?.contains(document.activeElement) === true
) {
root.focus()
}
}
else if (
refocusRef.value !== null
- && (e === void 0 || (root !== null && root.contains(e.target) === true))
+ && ((e === void 0) || (root?.contains(e.target) === true))
) {
refocusRef.value.focus()
}
diff --git a/ui/src/composables/private.use-validate/use-validate.js b/ui/src/composables/private.use-validate/use-validate.js
index 6d52d7703e7..00a23c8079f 100644
--- a/ui/src/composables/private.use-validate/use-validate.js
+++ b/ui/src/composables/private.use-validate/use-validate.js
@@ -201,7 +201,7 @@ export default function (focused, innerLoading) {
const debouncedValidate = debounce(validate, 0)
onBeforeUnmount(() => {
- unwatchRules !== void 0 && unwatchRules()
+ unwatchRules?.()
debouncedValidate.cancel()
})
diff --git a/ui/src/directives/intersection/Intersection.js b/ui/src/directives/intersection/Intersection.js
index 56e358fe394..c7997685f84 100644
--- a/ui/src/directives/intersection/Intersection.js
+++ b/ui/src/directives/intersection/Intersection.js
@@ -28,7 +28,7 @@ function update (el, ctx, value) {
if (changed === true) {
ctx.cfg = cfg
- ctx.observer !== void 0 && ctx.observer.unobserve(el)
+ ctx.observer?.unobserve(el)
ctx.observer = new IntersectionObserver(([ entry ]) => {
if (typeof ctx.handler === 'function') {
@@ -62,7 +62,7 @@ function destroy (el) {
const ctx = el.__qvisible
if (ctx !== void 0) {
- ctx.observer !== void 0 && ctx.observer.unobserve(el)
+ ctx.observer?.unobserve(el)
delete el.__qvisible
}
}
diff --git a/ui/src/directives/morph/Morph.js b/ui/src/directives/morph/Morph.js
index 51a2b65abac..5d0fc939c91 100644
--- a/ui/src/directives/morph/Morph.js
+++ b/ui/src/directives/morph/Morph.js
@@ -44,7 +44,7 @@ function trigger (group) {
},
...to.opts,
onEnd (dir, aborted) {
- to.opts.onEnd !== void 0 && to.opts.onEnd(dir, aborted)
+ to.opts.onEnd?.(dir, aborted)
if (aborted === true) return
@@ -206,7 +206,7 @@ export default createDirective(__QUASAR_SSR_SERVER__
group.queue = group.queue.filter(item => item !== ctx)
if (group.queue.length === 0) {
- group.cancel !== void 0 && group.cancel()
+ group.cancel?.()
delete morphGroups[ ctx.group ]
}
}
diff --git a/ui/src/directives/mutation/Mutation.js b/ui/src/directives/mutation/Mutation.js
index c0d767faa28..82f5e7d80f0 100644
--- a/ui/src/directives/mutation/Mutation.js
+++ b/ui/src/directives/mutation/Mutation.js
@@ -12,7 +12,7 @@ const defaultCfg = {
function update (el, ctx, value) {
ctx.handler = value
- ctx.observer !== void 0 && ctx.observer.disconnect()
+ ctx.observer?.disconnect()
ctx.observer = new MutationObserver(list => {
if (typeof ctx.handler === 'function') {
@@ -30,7 +30,7 @@ function destroy (el) {
const ctx = el.__qmutation
if (ctx !== void 0) {
- ctx.observer !== void 0 && ctx.observer.disconnect()
+ ctx.observer?.disconnect()
delete el.__qmutation
}
}
diff --git a/ui/src/directives/touch-hold/TouchHold.js b/ui/src/directives/touch-hold/TouchHold.js
index 60e834c84b6..07ddec134b9 100644
--- a/ui/src/directives/touch-hold/TouchHold.js
+++ b/ui/src/directives/touch-hold/TouchHold.js
@@ -106,7 +106,7 @@ export default createDirective(__QUASAR_SSR_SERVER__
cleanEvt(ctx, 'temp')
// delay needed otherwise selection still occurs
- ctx.styleCleanup !== void 0 && ctx.styleCleanup(ctx.triggered)
+ ctx.styleCleanup?.(ctx.triggered)
if (ctx.triggered === true) {
evt !== void 0 && stopAndPrevent(evt)
@@ -166,7 +166,7 @@ export default createDirective(__QUASAR_SSR_SERVER__
cleanEvt(ctx, 'temp')
ctx.timer !== void 0 && clearTimeout(ctx.timer)
- ctx.styleCleanup !== void 0 && ctx.styleCleanup()
+ ctx.styleCleanup?.()
delete el.__qtouchhold
}
diff --git a/ui/src/directives/touch-pan/TouchPan.js b/ui/src/directives/touch-pan/TouchPan.js
index e47a5be9333..f13989ad4ae 100644
--- a/ui/src/directives/touch-pan/TouchPan.js
+++ b/ui/src/directives/touch-pan/TouchPan.js
@@ -355,7 +355,7 @@ export default createDirective(__QUASAR_SSR_SERVER__
client.is.firefox === true && preventDraggable(el, false)
if (abort === true) {
- ctx.styleCleanup !== void 0 && ctx.styleCleanup()
+ ctx.styleCleanup?.()
if (ctx.event.detected !== true && ctx.initialEvent !== void 0) {
ctx.initialEvent.target.dispatchEvent(ctx.initialEvent.event)
@@ -426,7 +426,7 @@ export default createDirective(__QUASAR_SSR_SERVER__
cleanEvt(ctx, 'temp')
client.is.firefox === true && preventDraggable(el, false)
- ctx.styleCleanup !== void 0 && ctx.styleCleanup()
+ ctx.styleCleanup?.()
delete el.__qtouchpan
}
diff --git a/ui/src/directives/touch-repeat/TouchRepeat.js b/ui/src/directives/touch-repeat/TouchRepeat.js
index 0c86adc9e55..4357d91b829 100644
--- a/ui/src/directives/touch-repeat/TouchRepeat.js
+++ b/ui/src/directives/touch-repeat/TouchRepeat.js
@@ -186,8 +186,8 @@ export default createDirective(__QUASAR_SSR_SERVER__
end (evt) {
if (ctx.event === void 0) return
- ctx.styleCleanup !== void 0 && ctx.styleCleanup(true)
- evt !== void 0 && ctx.event.repeatCount > 0 && stopAndPrevent(evt)
+ ctx.styleCleanup?.(true)
+ if ((evt !== void 0) && (ctx.event.repeatCount > 0)) stopAndPrevent(evt)
cleanEvt(ctx, 'temp')
@@ -248,7 +248,7 @@ export default createDirective(__QUASAR_SSR_SERVER__
cleanEvt(ctx, 'main')
cleanEvt(ctx, 'temp')
- ctx.styleCleanup !== void 0 && ctx.styleCleanup()
+ ctx.styleCleanup?.()
delete el.__qtouchrepeat
}
diff --git a/ui/src/directives/touch-swipe/TouchSwipe.js b/ui/src/directives/touch-swipe/TouchSwipe.js
index e012b670a0f..1183b7a86c5 100644
--- a/ui/src/directives/touch-swipe/TouchSwipe.js
+++ b/ui/src/directives/touch-swipe/TouchSwipe.js
@@ -221,8 +221,8 @@ export default createDirective(__QUASAR_SSR_SERVER__
cleanEvt(ctx, 'temp')
client.is.firefox === true && preventDraggable(el, false)
- ctx.styleCleanup !== void 0 && ctx.styleCleanup(true)
- evt !== void 0 && ctx.event.dir !== false && stopAndPrevent(evt)
+ ctx.styleCleanup?.(true)
+ if ((evt !== void 0) && (ctx.event.dir !== false)) stopAndPrevent(evt)
ctx.event = void 0
}
@@ -268,7 +268,7 @@ export default createDirective(__QUASAR_SSR_SERVER__
cleanEvt(ctx, 'temp')
client.is.firefox === true && preventDraggable(el, false)
- ctx.styleCleanup !== void 0 && ctx.styleCleanup()
+ ctx.styleCleanup?.()
delete el.__qtouchswipe
}
diff --git a/ui/src/plugins/lang/Lang.test.js b/ui/src/plugins/lang/Lang.test.js
index e7a41f0308e..f2467117015 100644
--- a/ui/src/plugins/lang/Lang.test.js
+++ b/ui/src/plugins/lang/Lang.test.js
@@ -61,6 +61,12 @@ describe('[Lang API]', () => {
pagination: expect.any(Function),
columns: expect.any(String)
},
+ pagination: {
+ first: expect.any(String),
+ last: expect.any(String),
+ next: expect.any(String),
+ prev: expect.any(String)
+ },
editor: {
url: expect.any(String),
bold: expect.any(String),
@@ -173,6 +179,12 @@ describe('[Lang API]', () => {
pagination: (start, end, total) => start + '-' + end + ' of ' + total,
columns: 'Columns'
},
+ pagination: {
+ first: expect.any(String),
+ last: expect.any(String),
+ next: expect.any(String),
+ prev: expect.any(String)
+ },
editor: {
url: 'URL',
bold: 'Bold',
diff --git a/ui/src/plugins/loading/Loading.js b/ui/src/plugins/loading/Loading.js
index bd5940f225f..d8f3431135a 100644
--- a/ui/src/plugins/loading/Loading.js
+++ b/ui/src/plugins/loading/Loading.js
@@ -34,7 +34,7 @@ const originalDefaults = {
const defaults = { ...originalDefaults }
function registerProps (opts) {
- if (opts && opts.group !== void 0 && activeGroups[ opts.group ] !== void 0) {
+ if (opts?.group !== void 0 && activeGroups[ opts.group ] !== void 0) {
return Object.assign(activeGroups[ opts.group ], opts)
}
diff --git a/ui/src/plugins/notify/Notify.js b/ui/src/plugins/notify/Notify.js
index 5396d7e2ed4..541861a6b84 100644
--- a/ui/src/plugins/notify/Notify.js
+++ b/ui/src/plugins/notify/Notify.js
@@ -149,7 +149,7 @@ function addNotification (config, $q, originalApi) {
? defaults.actions
: []
).concat(
- notifTypes[ config.type ] !== void 0 && Array.isArray(notifTypes[ config.type ].actions) === true
+ Array.isArray(notifTypes[ config.type ]?.actions) === true
? notifTypes[ config.type ].actions
: []
)
diff --git a/ui/src/plugins/private.body/Body.js b/ui/src/plugins/private.body/Body.js
index fc9e9c90a49..b8cceb32641 100644
--- a/ui/src/plugins/private.body/Body.js
+++ b/ui/src/plugins/private.body/Body.js
@@ -100,7 +100,7 @@ export default {
const { $q, ssrContext } = opts
const cls = getBodyClasses($q.platform, $q.config)
- if ($q.config.screen !== void 0 && $q.config.screen.bodyClass === true) {
+ if ($q.config.screen?.bodyClass === true) {
cls.push('screen--xs')
}
diff --git a/ui/src/plugins/private.history/History.js b/ui/src/plugins/private.history/History.js
index 100264b0a80..58f7cec3578 100644
--- a/ui/src/plugins/private.history/History.js
+++ b/ui/src/plugins/private.history/History.js
@@ -51,7 +51,7 @@ export default {
const qConf = $q.config[ cordova === true ? 'cordova' : 'capacitor' ]
- if (qConf !== void 0 && qConf.backButton === false) return
+ if (qConf?.backButton === false) return
// if the '@capacitor/app' plugin is not installed
// then we got nothing to do
diff --git a/ui/src/plugins/screen/Screen.js b/ui/src/plugins/screen/Screen.js
index ccf6262eea8..84e60a4a707 100644
--- a/ui/src/plugins/screen/Screen.js
+++ b/ui/src/plugins/screen/Screen.js
@@ -71,7 +71,7 @@ export default createReactivePlugin({
visualViewport.height * visualViewport.scale + window.innerHeight - scrollingElement.clientHeight
]
- const classes = $q.config.screen !== void 0 && $q.config.screen.bodyClasses === true
+ const classes = $q.config.screen?.bodyClasses === true
this.__update = force => {
const [ w, h ] = getSize()
diff --git a/ui/src/utils/morph/morph.js b/ui/src/utils/morph/morph.js
index 68bd7b11881..9e3111c5360 100644
--- a/ui/src/utils/morph/morph.js
+++ b/ui/src/utils/morph/morph.js
@@ -256,7 +256,7 @@ export default function morph (_options) {
// we clean the clone of the initial element
elFromClone.remove()
- elFromTween !== void 0 && elFromTween.remove()
+ elFromTween?.remove()
options.hideFromClone === true && elFromClone.classList.remove('q-morph--internal')
@@ -301,7 +301,7 @@ export default function morph (_options) {
// we clean the clone of the initial element
elFromClone.remove()
- elFromTween !== void 0 && elFromTween.remove()
+ elFromTween?.remove()
options.hideFromClone === true && elFromClone.classList.remove('q-morph--internal')
@@ -516,12 +516,15 @@ export default function morph (_options) {
elTo.style.cssText = elToStyleSaved
elTo.className = elToClassSaved
}
- elToClone.parentNode === elToParent && elToParent.insertBefore(elTo, elToClone)
+
+ if (elToClone.parentNode === elToParent) {
+ elToParent.insertBefore(elTo, elToClone)
+ }
// we clean the spacers
elFromClone.remove()
elToClone.remove()
- elFromTween !== void 0 && elFromTween.remove()
+ elFromTween?.remove()
// cancel will be no longer available
cancel = () => false
@@ -677,9 +680,9 @@ export default function morph (_options) {
})
const cleanup = abort => {
- animationFromClone !== void 0 && animationFromClone.cancel()
- animationFromTween !== void 0 && animationFromTween.cancel()
- animationToClone !== void 0 && animationToClone.cancel()
+ animationFromClone?.cancel()
+ animationFromTween?.cancel()
+ animationToClone?.cancel()
animationTo.cancel()
animationTo.removeEventListener('finish', cleanup)
@@ -721,9 +724,9 @@ export default function morph (_options) {
endElementTo = endElementTo !== true
- animationFromClone !== void 0 && animationFromClone.reverse()
- animationFromTween !== void 0 && animationFromTween.reverse()
- animationToClone !== void 0 && animationToClone.reverse()
+ animationFromClone?.reverse()
+ animationFromTween?.reverse()
+ animationToClone?.reverse()
animationTo.reverse()
return true
diff --git a/ui/src/utils/open-url/open-url.js b/ui/src/utils/open-url/open-url.js
index 4a1541fe4e2..52832c6b220 100644
--- a/ui/src/utils/open-url/open-url.js
+++ b/ui/src/utils/open-url/open-url.js
@@ -22,10 +22,10 @@ function openWindow (url, reject, windowFeatures) {
let open = window.open
if (Platform.is.cordova === true) {
- if (cordova !== void 0 && cordova.InAppBrowser !== void 0 && cordova.InAppBrowser.open !== void 0) {
+ if (cordova?.InAppBrowser?.open !== void 0) {
open = cordova.InAppBrowser.open
}
- else if (navigator !== void 0 && navigator.app !== void 0) {
+ else if (navigator?.app !== void 0) {
return navigator.app.loadUrl(url, {
openExternal: true
})
@@ -39,7 +39,7 @@ function openWindow (url, reject, windowFeatures) {
return win
}
else {
- reject && reject()
+ reject?.()
}
}
diff --git a/ui/src/utils/private.dialog/create-dialog.js b/ui/src/utils/private.dialog/create-dialog.js
index ff6ef516f10..8a0f4b33799 100644
--- a/ui/src/utils/private.dialog/create-dialog.js
+++ b/ui/src/utils/private.dialog/create-dialog.js
@@ -57,14 +57,14 @@ export default function (DefaultComponent, supportsCustomComponent, parentApp) {
const el = createGlobalNode(false, 'dialog')
const applyState = cmd => {
- if (dialogRef.value !== null && dialogRef.value[ cmd ] !== void 0) {
+ if (dialogRef.value?.[ cmd ] !== void 0) {
dialogRef.value[ cmd ]()
return
}
const target = vm.$.subTree
- if (target && target.component) {
+ if (target?.component) {
// account for "script setup" way of declaring component
if (target.component.proxy && target.component.proxy[ cmd ]) {
target.component.proxy[ cmd ]()
diff --git a/ui/src/utils/private.portal/portal.js b/ui/src/utils/private.portal/portal.js
index 4023a60c584..6e8801cc49a 100644
--- a/ui/src/utils/private.portal/portal.js
+++ b/ui/src/utils/private.portal/portal.js
@@ -25,7 +25,7 @@ export function closePortalMenus (proxy, evt) {
// and hide it too
const parent = getParentProxy(proxy)
- if (parent !== void 0 && parent.$options.name === 'QPopupProxy') {
+ if (parent?.$options.name === 'QPopupProxy') {
proxy.hide(evt)
return parent
}