-
- {{ props.item.message }}
+
+ {{ item.message }}
-
+
-
function pauseTouch(event: PointerEvent) {
if (!isMouse(event)) {
+ timeouts.clearDebounceTimeout()
timeouts.pause()
- clearTimeout(resumeTimeout)
- resumeTimeout = setTimeout(() => {
- timeouts.resume()
- }, 2000)
+ timeouts.resumeWithDebounce(2000)
}
}
onBeforeUnmount(() => {
- clearTimeout(resumeTimeout)
+ timeouts.clearDebounceTimeout()
})
return computed(() =>
diff --git a/packages/notivue/NotivueSwipe/NotivueSwipe.vue b/packages/notivue/NotivueSwipe/NotivueSwipe.vue
index 12f5a064..fd5f32e7 100644
--- a/packages/notivue/NotivueSwipe/NotivueSwipe.vue
+++ b/packages/notivue/NotivueSwipe/NotivueSwipe.vue
@@ -14,10 +14,23 @@ import {
import { useStore } from '@/core/useStore'
import { isMouse } from '@/core/utils'
import { NotificationTypeKeys as NType } from '@/core/constants'
-import { DEFAULT_PROPS } from './constants'
+import { DEFAULT_PROPS, DEBOUNCE, RETURN_DUR } from './constants'
import type { NotivueSwipeProps } from 'notivue'
+/**
+ * MOUSE - Notivue's mouse events (get from 'useMouseEvents') will still handle the pause/resume logic
+ * on hover. NotivueSwipe will only additionally pause timeouts while swiping and resume them
+ * when clearing.
+ *
+ * TOUCH / PEN - Notivue's touch events (get from 'useTouchEvents') execution is prevented when
+ * using NotivueSwipe. That's because a more granular timeout control is required due
+ * to all possible interactions hence the whole touch logic is handled here.
+ *
+ * When releasing, leaving or clearing a notification via Swipe a small debounce time is added to
+ * improve UX.
+ */
+
// Store
const { timeouts, elements, animations } = useStore()
@@ -40,12 +53,8 @@ const isEnabled = computed(
props.item.duration < Infinity
)
-const lastItemContainer = computed(() => elements.items.value[elements.items.value.length - 1])
-
// Internal
-const RETURN_DUR = 300
-
const itemRef = ref(null)
const state = shallowReactive({
@@ -114,11 +123,14 @@ function shouldSwipe(e: PointerEvent) {
function setTargetPosition() {
if (!itemRef.value) return
- const { offsetLeft, clientWidth } = itemRef.value
+ // Must use clientWidth so that scale transform is not taken into account
+ const { clientWidth } = itemRef.value
+
+ const { left } = itemRef.value.getBoundingClientRect()
setState({
- targetLeft: offsetLeft,
- targetRight: offsetLeft + clientWidth,
+ targetLeft: left,
+ targetRight: left + clientWidth,
targetWidth: clientWidth,
})
}
@@ -137,6 +149,20 @@ function isPointerInside(e: PointerEvent) {
return e.clientX > state.targetLeft && e.clientX < state.targetRight
}
+function getDebounceMs(e: PointerEvent) {
+ return isMouse(e) ? DEBOUNCE.Mouse : DEBOUNCE.Touch
+}
+
+function pauseTimeouts() {
+ timeouts.clearDebounceTimeout()
+ timeouts.pause()
+}
+
+function resumeTimeouts(ms: number) {
+ timeouts.clearDebounceTimeout()
+ timeouts.resumeWithDebounce(ms)
+}
+
/* ====================================================================================
* Event Handlers
* ==================================================================================== */
@@ -149,8 +175,8 @@ function onPointerDown(e: PointerEvent) {
if (!shouldSwipe(e)) return
/**
- * Prevents the `useTouchEvents` handler to fire, which is what
- * we're looking for so it doesn't interfere.
+ * Prevents `useTouchEvents` events to fire, which is what
+ * we're looking for so they doen't interfere with NotivueSwipe logic.
*/
e.stopPropagation()
@@ -160,10 +186,25 @@ function onPointerDown(e: PointerEvent) {
if (exclude.value) {
const excludedEls = Array.from(itemRef.value.querySelectorAll(exclude.value))
- if (excludedEls.includes(e.target as HTMLElement)) return
+
+ if (excludedEls.includes(e.target as HTMLElement)) {
+ /**
+ * When tapping an excluded element, we want to pause and resume timeouts
+ * after a bit, keeping the same behavior as Notivue touch events (useTouchEvents).
+ *
+ * This is not required when using the mouse as the pause/resume is already handled on
+ * hover by useMouseEvents.
+ */
+ if (!isMouse(e)) {
+ pauseTimeouts()
+ resumeTimeouts(DEBOUNCE.TouchExternal)
+ }
+
+ return
+ }
}
- if (!isMouse(e)) timeouts.pause()
+ if (!isMouse(e)) pauseTimeouts()
setState({
startX: e.clientX,
@@ -199,18 +240,23 @@ function onPointerMoveClear(e: PointerEvent) {
props.item.destroy()
if (isMouse(e) && isPointerInside(e)) {
- const isLastItem = lastItemContainer.value.contains(itemRef.value)
- if (!isLastItem) timeouts.pause()
+ const sortedContainers = elements.getSortedItems()
+ const isLastItem = sortedContainers[sortedContainers.length - 1].contains(itemRef.value)
+
+ if (!isLastItem) pauseTimeouts()
} else {
- timeouts.resume()
+ resumeTimeouts(getDebounceMs(e))
}
const animDuration = parseFloat(animations.getTransitionData()?.duration ?? 0) * 1000
- setTimeout(() => (state.isClearing = false), animDuration)
+ window.setTimeout(() => (state.isClearing = false), animDuration)
}
/**
- * Never triggered if the notification has been closed on pointer move.
+ * Triggered when the notification is swiped and then released but not enough
+ * to be cleared.
+ *
+ * Callback logic is not executed if the notification gets closed while swiping.
*/
function onPointerUp(e: PointerEvent) {
if (!shouldSwipe(e)) return
@@ -221,9 +267,9 @@ function onPointerUp(e: PointerEvent) {
if (state.isClearing || state.isLocked) return
if (isMouse(e) && isPointerInside(e)) {
- timeouts.pause()
+ pauseTimeouts()
} else {
- timeouts.resume()
+ resumeTimeouts(getDebounceMs(e))
}
setReturnStyles()
@@ -235,9 +281,12 @@ function onPointerUp(e: PointerEvent) {
isLocked: true,
})
- setTimeout(() => (state.isLocked = false), RETURN_DUR)
+ window.setTimeout(() => (state.isLocked = false), RETURN_DUR)
}
+/**
+ * Triggered when the pointer leaves the notification bounding box while swiping.
+ */
function onPointerLeave(e: PointerEvent) {
if (!shouldSwipe(e)) return
@@ -255,7 +304,7 @@ function onPointerLeave(e: PointerEvent) {
isLocked: false,
})
- timeouts.resume()
+ resumeTimeouts(getDebounceMs(e))
}
/* ====================================================================================
@@ -302,6 +351,7 @@ watch(
})
onCleanup(() => {
+ // timeouts.clearDebounceTimeout()
removeListeners()
resetDragStyles()
// No need to reset target position as they will be recomputed on next enabling
diff --git a/packages/notivue/NotivueSwipe/constants.ts b/packages/notivue/NotivueSwipe/constants.ts
index c39ff79b..0b6d62d0 100644
--- a/packages/notivue/NotivueSwipe/constants.ts
+++ b/packages/notivue/NotivueSwipe/constants.ts
@@ -4,3 +4,11 @@ export const DEFAULT_PROPS = {
disabled: false,
threshold: 0.5,
} as const
+
+export const RETURN_DUR = 300
+
+export const DEBOUNCE = {
+ Mouse: 200,
+ Touch: 1000,
+ TouchExternal: 1400,
+}
diff --git a/packages/notivue/core/animations.css b/packages/notivue/core/animations.css
index f3140460..697bfc28 100644
--- a/packages/notivue/core/animations.css
+++ b/packages/notivue/core/animations.css
@@ -13,7 +13,7 @@
}
.Notivue__enter {
- animation: Notivue__enter-kf 350ms cubic-bezier(0.5, 1, 0.25, 1) both;
+ animation: Notivue__enter-kf 350ms cubic-bezier(0.5, 1, 0.25, 1);
}
.Notivue__leave {
@@ -34,24 +34,24 @@
@keyframes Notivue__enter-kf {
0% {
- transform: translateY(var(--notivue-ty)) scale(0.25);
+ transform: translate3d(0, var(--notivue-ty), 0) scale(0.25);
opacity: 0;
}
100% {
- transform: translateY(0.1) scale(1);
+ transform: translate3d(0, 0, 0) scale(1);
opacity: 1;
}
}
@keyframes Notivue__leave-kf {
0% {
- transform: translateY(0.01) scale(1);
+ transform: translate3d(0, 0, 0) scale(1);
opacity: 0.7;
}
100% {
- transform: translateY(var(--notivue-ty)) scale(0.01);
+ transform: translate3d(0, var(--notivue-ty), 0) scale(0.01);
opacity: 0;
}
}
diff --git a/packages/notivue/core/createNotivue.ts b/packages/notivue/core/createNotivue.ts
index 8f90f3ca..4d10c7f9 100644
--- a/packages/notivue/core/createNotivue.ts
+++ b/packages/notivue/core/createNotivue.ts
@@ -31,7 +31,8 @@ export function createNotivue(app: App, userConfig: NotivueConfig = {}): Push {
watch(
() => items.getLength(),
- () => animations.updatePositions(TType.PUSH)
+ () => animations.updatePositions(TType.PUSH),
+ { flush: 'post' }
)
watch(
@@ -50,8 +51,9 @@ export function createNotivue(app: App, userConfig: NotivueConfig = {}): Push {
() => config.animations.value.enter,
(newEnter, prevEnter) => {
if (newEnter !== prevEnter) {
- console.log('Resetting transition data!')
animations.resetTransitionData()
+
+ console.log('Transition data reset!')
}
}
)
diff --git a/packages/notivue/core/createStore.ts b/packages/notivue/core/createStore.ts
index 06a91523..fb19933d 100644
--- a/packages/notivue/core/createStore.ts
+++ b/packages/notivue/core/createStore.ts
@@ -89,7 +89,7 @@ export function createItemsSlice(config: ConfigSlice, queue: QueueSlice) {
remove(id: string) {
this.entries.value = this.entries.value.filter(({ timeout, id: _id }) => {
if (id !== _id) return true
- return clearTimeout(timeout as number), false
+ return window.clearTimeout(timeout as number), false
})
const shouldDequeue = config.enqueue.value && queue.getLength() > 0
@@ -112,6 +112,10 @@ export function createElementsSlice() {
this.rootAttrs.value = newAttrs
},
items: ref([]),
+ getSortedItems() {
+ // This is a bit dirty, but it's better than cloning and reversing the array on every repositioning
+ return this.items.value.sort((a, b) => +b.dataset.notivueId! - +a.dataset.notivueId!)
+ },
containers: ref([]),
}
}
@@ -188,32 +192,31 @@ export function createAnimationsSlice(
},
updatePositions(type = TType.PUSH) {
const isReduced = this.isReducedMotion.value || type === TType.IMMEDIATE
+ const leaveClass = config.animations.value.leave
+
+ const { duration: transitionDuration, easing: transitionTimingFunction } =
+ this.getTransitionData()
let accPrevHeights = 0
- requestAnimationFrame(() => {
- for (const el of [...elements.items.value].reverse()) {
- const id = el.dataset.notivueId!
- const item = items.get(id)
- const leaveClass = config.animations.value.leave
-
- if (!el || !item || item.animationAttrs.class === leaveClass) continue
-
- items.update(id, {
- positionStyles: {
- transform: `translate3d(0, ${accPrevHeights}px, 0)`,
- ...(isReduced
- ? { transition: 'none' }
- : {
- transitionDuration: this.getTransitionData().duration,
- transitionTimingFunction: this.getTransitionData().easing,
- }),
- },
- })
-
- accPrevHeights += (config.isTopAlign.value ? 1 : -1) * el.clientHeight
- }
- })
+ for (const el of elements.getSortedItems()) {
+ const id = el.dataset.notivueId!
+ const item = items.get(id)
+
+ if (!el || !item || item.animationAttrs.class === leaveClass) continue
+
+ items.update(id, {
+ positionStyles: {
+ willChange: 'transform',
+ transform: `translate3d(0, ${accPrevHeights}px, 0)`,
+ ...(isReduced
+ ? { transition: 'none' }
+ : { transitionDuration, transitionTimingFunction }),
+ },
+ })
+
+ accPrevHeights += (config.isTopAlign.value ? 1 : -1) * el.clientHeight
+ }
},
}
}
@@ -222,13 +225,18 @@ export function createTimeoutsSlice(items: ItemsSlice, animations: AnimationsSli
return {
isStreamPaused: ref(false),
isStreamFocused: ref(false),
+ debounceTimeout: undefined as undefined | number,
setStreamPause(newVal = true) {
this.isStreamPaused.value = newVal
},
setStreamFocus(newVal = true) {
this.isStreamFocused.value = newVal
},
+ clearDebounceTimeout() {
+ window.clearTimeout(this.debounceTimeout)
+ },
reset() {
+ this.clearDebounceTimeout()
this.setStreamPause(false)
this.setStreamFocus(false)
},
@@ -251,7 +259,7 @@ export function createTimeoutsSlice(items: ItemsSlice, animations: AnimationsSli
console.log('Pausing timeouts')
items.updateAll((item) => {
- clearTimeout(item.timeout as number)
+ window.clearTimeout(item.timeout as number)
return {
...item,
@@ -266,7 +274,7 @@ export function createTimeoutsSlice(items: ItemsSlice, animations: AnimationsSli
console.log('Resuming timeouts')
items.updateAll((item) => {
- clearTimeout(item.timeout as number)
+ window.clearTimeout(item.timeout as number)
/**
* 'elapsed' may be equal to 'undefined' if a notification
* is pushed while the stream is paused as pause() won't be called.
@@ -298,6 +306,11 @@ export function createTimeoutsSlice(items: ItemsSlice, animations: AnimationsSli
this.setStreamPause(false)
},
+ resumeWithDebounce(ms: number) {
+ this.debounceTimeout = window.setTimeout(() => {
+ this.resume()
+ }, ms)
+ },
}
}
diff --git a/packages/notivue/nuxt/module.json b/packages/notivue/nuxt/module.json
index 55523402..773b8128 100644
--- a/packages/notivue/nuxt/module.json
+++ b/packages/notivue/nuxt/module.json
@@ -1,5 +1,5 @@
{
"name": "notivue/nuxt",
"configKey": "notivue",
- "version": "1.4.2"
+ "version": "1.4.3"
}
diff --git a/packages/notivue/package.json b/packages/notivue/package.json
index 661fec5e..344c2957 100644
--- a/packages/notivue/package.json
+++ b/packages/notivue/package.json
@@ -1,6 +1,6 @@
{
"name": "notivue",
- "version": "1.4.2",
+ "version": "1.4.3",
"private": false,
"description": "Fully-featured toast notification system for Vue and Nuxt",
"keywords": [
@@ -57,12 +57,12 @@
"watch": "rm -rf dist && concurrently \"vite build --watch\" \"pnpm build:css --watch\""
},
"devDependencies": {
- "@nuxt/kit": "^3.7.1",
- "@nuxt/schema": "^3.0.0",
- "@types/node": "^20.8.7",
+ "@nuxt/kit": "^3.8.0",
+ "@nuxt/schema": "^3.8.0",
+ "@types/node": "^20.8.10",
"@vitejs/plugin-vue": "4.4.0",
- "concurrently": "^8.2.1",
- "defu": "^6.1.2",
+ "concurrently": "^8.2.2",
+ "defu": "^6.1.3",
"esbuild": "^0.19.5",
"typescript": "5.2.2",
"vite": "4.4.11",
diff --git a/tests/Notivue/config-pause-on-hover.cy.ts b/tests/Notivue/config-pause-on-hover.cy.ts
index 71e0ca29..6363cc95 100644
--- a/tests/Notivue/config-pause-on-hover.cy.ts
+++ b/tests/Notivue/config-pause-on-hover.cy.ts
@@ -1,5 +1,3 @@
-import Notivue from './components/Notivue.vue'
-
import type { VueWrapper } from '@vue/test-utils'
describe('Pause on hover', () => {
diff --git a/tests/Notivue/config-pause-on-touch.cy.ts b/tests/Notivue/config-pause-on-touch.cy.ts
index 150f2d4a..f57c4de5 100644
--- a/tests/Notivue/config-pause-on-touch.cy.ts
+++ b/tests/Notivue/config-pause-on-touch.cy.ts
@@ -9,28 +9,25 @@ describe('Pause on touch', () => {
cy.mountNotivue()
.clickRandomStatic()
- .wait(4000) // Remaining: 2000ms
+ .wait(5000) // Remaining: 1000ms
.get('.Notification')
.trigger('pointerdown', { pointerType: 'touch' })
- .wait(4000) // Any value greater than the remaining time
+ .wait(2000) // More than the remaining time
.get('.Notification')
.should('exist')
-
- .get('.Notification')
- .should('not.exist')
})
it('Should not pause notifications if pauseOnTouch is false', () => {
cy.mountNotivue({ config: { pauseOnTouch: false } })
.clickRandomStatic()
- .wait(4000) // Remaining: 2000ms
+ .wait(5000) // Remaining: 1000ms
.get('.Notification')
.trigger('pointerdown', { pointerType: 'touch' })
- .wait(4000) // Any value greater than the remaining time
+ .wait(2000) // More than the remaining time
.get('.Notification')
.should('not.exist')
@@ -43,11 +40,11 @@ describe('Pause on touch', () => {
.then((wrapper) => wrapper.setProps({ pauseOnTouch: true }))
.clickRandomStatic()
- .wait(4000) // Remaining: 2000ms
+ .wait(5000) // Remaining: 1000ms
.get('.Notification')
.trigger('pointerdown', { pointerType: 'touch' })
- .wait(4000) // Any value greater than the remaining time
+ .wait(2000) // More than the remaining time
.get('.Notification')
.should('exist')
diff --git a/tests/NotivueSwipe/debounce.cy.ts b/tests/NotivueSwipe/debounce.cy.ts
new file mode 100644
index 00000000..ed5e9acf
--- /dev/null
+++ b/tests/NotivueSwipe/debounce.cy.ts
@@ -0,0 +1,102 @@
+import { DEFAULT_ANIM_DURATION as LEAVE_ANIM_DUR, getRandomInt } from '@/support/utils'
+
+import { DEBOUNCE } from '@/NotivueSwipe/constants'
+import { DEFAULT_DURATION } from '@/core/constants'
+import { SWIPE_NOTIFICATION_WIDTH as WIDTH } from '@/support/utils'
+
+const LENGTH = 5
+
+const pointerEventOptions = {
+ force: true,
+ eventConstructor: 'PointerEvent',
+ pointerType: 'touch',
+}
+
+describe('Debounce', () => {
+ beforeEach(() => {
+ cy.viewport('iphone-x')
+ })
+
+ /**
+ * In order for those tests to be accurate, we need a way to track how much time Cypress
+ * takes to swipe the element and to trigger pointer events.
+ *
+ * We can use Cypress retry-ability mixed with cy.then to track it.
+ * In the first test, this matches the moment the element is removed from the DOM.
+ *
+ * We also add the leave animation duration to the wait time, to be even more accurate.
+ */
+
+ it('Resume is delayed after clearing', () => {
+ cy.mountSwipe()
+
+ let elapsed = Date.now()
+
+ for (let i = 0; i < LENGTH; i++) {
+ cy.get('.Success').click()
+ }
+
+ cy.get('.SwipeNotification').eq(3).realSwipe('toLeft', { length: WIDTH })
+
+ cy.get('.SwipeNotification')
+ .should('have.length', LENGTH - 1)
+ .then(() => {
+ elapsed = Date.now() - elapsed
+ console.log('Elapsed: ', elapsed)
+
+ cy.wait(DEFAULT_DURATION - elapsed + DEBOUNCE.Touch + LEAVE_ANIM_DUR)
+ cy.get('.SwipeNotification').should('have.length', LENGTH - 1)
+ })
+ })
+
+ it('Resume is delayed after tapping an excluded element', () => {
+ const child = getRandomInt(0, LENGTH - 1)
+
+ cy.mountSwipe()
+
+ let elapsed = Date.now()
+
+ for (let i = 0; i < LENGTH; i++) {
+ cy.get('.Success').click()
+ }
+
+ cy.get('.CloseButton')
+ .eq(child)
+ .trigger('pointerdown', pointerEventOptions)
+ .then(() => {
+ elapsed = Date.now() - elapsed
+ console.log('Elapsed: ', elapsed)
+
+ cy.wait(DEFAULT_DURATION - elapsed + DEBOUNCE.TouchExternal + LEAVE_ANIM_DUR)
+ cy.get('.SwipeNotification').should('have.length', LENGTH - 1)
+ })
+ })
+
+ it('Resume is delayed after tappping a notification', () => {
+ const child = getRandomInt(0, LENGTH - 1)
+
+ cy.mountSwipe()
+
+ let elapsed = Date.now()
+
+ for (let i = 0; i < LENGTH; i++) {
+ cy.get('.Success').click()
+ }
+
+ cy.get('.SwipeNotification')
+ .eq(child)
+ .trigger('pointerdown', pointerEventOptions)
+ .then(() => {
+ cy.get('.SwipeNotification')
+ .eq(child)
+ .trigger('pointerup', pointerEventOptions)
+ .then(() => {
+ elapsed = Date.now() - elapsed
+ console.log('Elapsed: ', elapsed)
+
+ cy.wait(DEFAULT_DURATION - elapsed + DEBOUNCE.Touch + LEAVE_ANIM_DUR)
+ cy.get('.SwipeNotification').should('have.length', LENGTH)
+ })
+ })
+ })
+})
diff --git a/tests/NotivueSwipe/timeouts.cy.ts b/tests/NotivueSwipe/timeouts.cy.ts
index 6fd923bd..49c5c76e 100644
--- a/tests/NotivueSwipe/timeouts.cy.ts
+++ b/tests/NotivueSwipe/timeouts.cy.ts
@@ -27,7 +27,7 @@ describe('Leave timeouts', () => {
cy.get('.SwipeNotification').should('have.length', 0)
})
- it.only('Should not resume timeouts if hovering back on a notification after clearing', () => {
+ it('Should not resume timeouts if tapping back on a notification after clearing', () => {
const child = getRandomInt(0, REPEAT - 1)
cy.mountSwipe()
@@ -42,17 +42,14 @@ describe('Leave timeouts', () => {
.eq(child)
.realSwipe('toRight', { length: WIDTH / 2 })
- .get('.SwipeNotification')
- .its('length')
- .should('be.eq', REPEAT - 1)
-
- cy.get('.SwipeNotification').eq(0).trigger('pointerdown', {
- force: true,
- eventConstructor: 'PointerEvent',
- pointerType: 'touch',
- })
-
- cy.wait(DEFAULT_DURATION * 2) // Very long time
+ cy.get('.SwipeNotification')
+ .eq(0)
+ .trigger('pointerdown', {
+ force: true,
+ eventConstructor: 'PointerEvent',
+ pointerType: 'touch',
+ })
+ .wait(DEFAULT_DURATION * 2) // Hold for very long time
cy.get('.SwipeNotification').should('have.length', REPEAT - 1)
})
diff --git a/tests/cypress.config.ts b/tests/cypress.config.ts
index f75f5031..7e68b1ac 100644
--- a/tests/cypress.config.ts
+++ b/tests/cypress.config.ts
@@ -23,6 +23,7 @@ export default defineConfig({
'@/core': resolve(__dirname, '../packages/notivue/core'),
'@/Notivue': resolve(__dirname, '../packages/notivue/Notivue'),
+ '@/NotivueSwipe': resolve(__dirname, '../packages/notivue/NotivueSwipe'),
'@/Notifications': resolve(__dirname, '../packages/notivue/Notifications'),
},
},
diff --git a/tests/cypress/support/commands-notivue.ts b/tests/cypress/support/commands-notivue.ts
index 2d8d3ec2..13393d15 100644
--- a/tests/cypress/support/commands-notivue.ts
+++ b/tests/cypress/support/commands-notivue.ts
@@ -97,30 +97,19 @@ Cypress.Commands.add('checkSlotPropsAgainst', (obj: Record) =>
Cypress.Commands.add(
'checkAnimations',
- (enterClass: string, leaveClass: string, clearAllClass: string) =>
- cy
- .get('.Success')
- .click()
-
- .get(enterClass)
- .should('exist')
- .get(leaveClass)
- .should('not.exist')
-
- .wait(DEFAULT_DURATION)
-
- .get(leaveClass)
- .should('exist')
- .get(enterClass)
- .should('not.exist')
-
- .get('.Success')
- .click()
- .get('.ClearAll')
- .click()
-
- .get(clearAllClass)
- .should('exist')
+ (enterClass: string, leaveClass: string, clearAllClass: string) => {
+ cy.get('.Success').click()
+
+ cy.get(enterClass).should('exist').get(leaveClass).should('not.exist').wait(DEFAULT_DURATION)
+
+ cy.get(leaveClass).should('exist').get(enterClass).should('not.exist')
+
+ cy.get('.Success').click()
+
+ cy.get('.ClearAll').click()
+
+ cy.get(clearAllClass).should('exist')
+ }
)
Cypress.Commands.add('checkTransitions', (element: HTMLElement, height: number) =>
diff --git a/tests/package.json b/tests/package.json
index 58203bbc..74c9eed8 100644
--- a/tests/package.json
+++ b/tests/package.json
@@ -9,16 +9,16 @@
"notivue": "workspace:*"
},
"devDependencies": {
- "@types/node": "^20.8.7",
- "@vitejs/plugin-vue": "^4.3.4",
+ "@types/node": "^20.8.10",
+ "@vitejs/plugin-vue": "^4.4.0",
"@vue/test-utils": "^2.4.1",
- "axe-core": "^4.8.1",
- "cypress": "^13.3.0",
+ "axe-core": "^4.8.2",
+ "cypress": "^13.4.0",
"cypress-axe": "^1.5.0",
"cypress-real-events": "^1.10.3",
"typescript": "^5.2.2",
- "vite": "^4.4.9",
- "vue": "^3.3.4",
- "vue-tsc": "^1.8.10"
+ "vite": "^4.5.0",
+ "vue": "^3.3.7",
+ "vue-tsc": "^1.8.22"
}
}