Skip to content

Commit 9776889

Browse files
fix failing tests
1 parent b499989 commit 9776889

File tree

3 files changed

+505
-32
lines changed

3 files changed

+505
-32
lines changed

packages/ui/cypress/component/SPopover.spec.cy.ts

Lines changed: 267 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { SPopover, SPopoverWrappedTransition } from '@/components/Popover'
22
import { usePopoverApi } from '@/components/Popover/api'
33

4-
import type { Instance } from '@popperjs/core'
4+
import type { Instance, Options } from '@popperjs/core'
55
import { VueTestUtils } from 'cypress/vue'
66

77
before(() => {
@@ -338,12 +338,273 @@ describe('SPopoverWrappedTransition', () => {
338338
})
339339
})
340340

341-
it('Passing props & events to transition component itself')
342-
it('Binding class, style & attrs to the wrapper element')
343-
it('Binding class, style & attrs to the content element')
341+
it('Passing props & events to transition component itself', () => {
342+
cy.mount({
343+
setup() {
344+
const afterEnterCount = ref(0)
345+
346+
return {
347+
afterEnterCount,
348+
onAfterEnter: () => {
349+
afterEnterCount.value += 1
350+
},
351+
}
352+
},
353+
template: `
354+
<div style="padding: 150px">
355+
<SPopover trigger="click">
356+
<template #trigger>
357+
<button data-cy="trigger">trigger</button>
358+
</template>
359+
360+
<template #popper>
361+
<SPopoverWrappedTransition
362+
name="wrapped-popover-transition"
363+
@after-enter="onAfterEnter"
364+
>
365+
<span data-cy="content">content</span>
366+
</SPopoverWrappedTransition>
367+
</template>
368+
</SPopover>
369+
370+
<output data-cy="after-enter-count">{{ afterEnterCount }}</output>
371+
</div>
372+
`,
373+
})
374+
375+
cy.get('[data-cy=trigger]').click()
376+
377+
cy.get('[data-cy=content]').should('have.class', 'wrapped-popover-transition-enter-active')
378+
cy.get('[data-cy=after-enter-count]').should('have.text', '1')
379+
})
380+
381+
it('Binding class, style & attrs to the wrapper element', () => {
382+
cy.mount({
383+
template: `
384+
<div style="padding: 150px">
385+
<SPopover trigger="click">
386+
<template #trigger>
387+
<button data-cy="trigger">trigger</button>
388+
</template>
389+
390+
<template #popper>
391+
<SPopoverWrappedTransition
392+
:wrapper-attrs="{
393+
'data-cy': 'wrapper',
394+
class: 'popover-wrapper-extra',
395+
style: { padding: '12px' },
396+
'data-extra': 'value',
397+
}"
398+
>
399+
<span>content</span>
400+
</SPopoverWrappedTransition>
401+
</template>
402+
</SPopover>
403+
</div>
404+
`,
405+
})
406+
407+
cy.get('[data-cy=trigger]').click()
408+
409+
cy.get('[data-cy=wrapper]')
410+
.should('have.class', 'popover-wrapper-extra')
411+
.and('have.attr', 'data-extra', 'value')
412+
.and('have.css', 'padding-top', '12px')
413+
})
414+
415+
it('Binding class, style & attrs to the content element', () => {
416+
cy.mount({
417+
template: `
418+
<div style="padding: 150px">
419+
<SPopover trigger="click">
420+
<template #trigger>
421+
<button data-cy="trigger">trigger</button>
422+
</template>
423+
424+
<template #popper>
425+
<SPopoverWrappedTransition
426+
:inner-wrapper-attrs="{
427+
'data-cy': 'content-wrapper',
428+
class: 'popover-content-extra',
429+
style: { color: 'rgb(255, 0, 0)' },
430+
'data-extra': 'inner',
431+
}"
432+
>
433+
<span>content</span>
434+
</SPopoverWrappedTransition>
435+
</template>
436+
</SPopover>
437+
</div>
438+
`,
439+
})
440+
441+
cy.get('[data-cy=trigger]').click()
442+
443+
cy.get('[data-cy=content-wrapper]')
444+
.should('have.class', 'popover-content-extra')
445+
.and('have.attr', 'data-extra', 'inner')
446+
.and('have.css', 'color', 'rgb(255, 0, 0)')
447+
})
344448
})
345449

346450
describe('Popper options reactivity', () => {
347-
it('snap: placement change')
348-
it('snap: distance & skidding changes')
451+
it('snap: placement change', () => {
452+
cy.mount({
453+
setup() {
454+
const placement = ref<'top' | 'bottom'>('top')
455+
456+
return {
457+
placement,
458+
setBottom: () => {
459+
placement.value = 'bottom'
460+
},
461+
}
462+
},
463+
template: `
464+
<div style="padding: 200px">
465+
<button data-cy="set-bottom" @click="setBottom">set bottom</button>
466+
467+
<SPopover
468+
trigger="click"
469+
:placement="placement"
470+
>
471+
<template #trigger>
472+
<button data-cy="trigger">trigger</button>
473+
</template>
474+
475+
<template #popper="{ popper }">
476+
<SPopoverWrappedTransition eager>
477+
<div data-cy="popper">
478+
<div data-cy="placement">{{ popper?.state.placement }}</div>
479+
</div>
480+
</SPopoverWrappedTransition>
481+
</template>
482+
</SPopover>
483+
</div>
484+
`,
485+
})
486+
487+
cy.get('[data-cy=trigger]').click()
488+
489+
cy.get('[data-cy=placement]').should('contain', 'top')
490+
491+
cy.get('[data-cy=set-bottom]').click()
492+
493+
cy.get('[data-cy=placement]').should('contain', 'bottom')
494+
})
495+
496+
it('snap: distance & skidding changes', () => {
497+
cy.mount({
498+
components: {
499+
OffsetWatcher: {
500+
name: 'OffsetWatcher',
501+
setup() {
502+
const api = usePopoverApi()
503+
const offsetSnapshot = ref('')
504+
505+
const extractOffset = (options: Partial<Options> | undefined) => {
506+
const modifiers = options?.modifiers ?? []
507+
const offsetModifier = modifiers.find((modifier: any) => modifier?.name === 'offset')
508+
if (!offsetModifier) return null
509+
510+
let rawOffset = offsetModifier.options?.offset
511+
if (!rawOffset) return null
512+
513+
if (Array.isArray(rawOffset)) return rawOffset
514+
if (typeof rawOffset === 'object' && 'value' in rawOffset) return rawOffset.value
515+
516+
return null
517+
}
518+
519+
const updateFromInstance = (instance: Instance) => {
520+
const current = instance.state.modifiersData.offset?.[instance.state.placement]
521+
if (current) {
522+
offsetSnapshot.value = current.map((part) => Math.round(part)).join(',')
523+
return
524+
}
525+
526+
const fallback = extractOffset(instance.state.options)
527+
if (fallback) {
528+
offsetSnapshot.value = fallback.map((part) => Number(part)).join(',')
529+
}
530+
}
531+
532+
watch(
533+
() => api.popper as Instance | null,
534+
(instance) => {
535+
if (!instance) return
536+
537+
updateFromInstance(instance)
538+
539+
if (!(instance as any).__offsetWatcherPatched) {
540+
const original = instance.setOptions.bind(instance)
541+
542+
instance.setOptions = (options) => {
543+
const extracted = extractOffset(options)
544+
if (extracted) {
545+
offsetSnapshot.value = extracted.map((part) => Number(part)).join(',')
546+
}
547+
548+
return original(options)
549+
}
550+
;(instance as any).__offsetWatcherPatched = true
551+
}
552+
},
553+
{ immediate: true },
554+
)
555+
556+
return { offsetSnapshot }
557+
},
558+
template: `<div data-cy="offset">{{ offsetSnapshot }}</div>`,
559+
},
560+
},
561+
setup() {
562+
const skidding = ref(0)
563+
const distance = ref(0)
564+
565+
return {
566+
skidding,
567+
distance,
568+
setOffset: () => {
569+
skidding.value = 10
570+
distance.value = 20
571+
},
572+
}
573+
},
574+
template: `
575+
<div style="padding: 200px">
576+
<button data-cy="set-offset" @click="setOffset">set offset</button>
577+
578+
<SPopover
579+
trigger="click"
580+
placement="bottom"
581+
:skidding="skidding"
582+
:distance="distance"
583+
>
584+
<template #trigger>
585+
<button data-cy="trigger">trigger</button>
586+
</template>
587+
588+
<template #popper>
589+
<SPopoverWrappedTransition eager>
590+
<div data-cy="popper">
591+
<OffsetWatcher />
592+
</div>
593+
</SPopoverWrappedTransition>
594+
</template>
595+
</SPopover>
596+
</div>
597+
`,
598+
})
599+
600+
cy.get('[data-cy=trigger]').click()
601+
602+
cy.get('[data-cy=offset]')
603+
.invoke('text')
604+
.should('match', /0[, ]0/)
605+
606+
cy.get('[data-cy=set-offset]').click()
607+
608+
cy.get('[data-cy=offset]').should('contain', '10,20')
609+
})
349610
})

packages/ui/src/components/Popover/SPopover.ts

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -157,32 +157,55 @@ export default /* @__PURE__ */ defineComponent({
157157
})
158158
const popperRef = eagerComputed(() => somePopperRefOverride.value || popperNativeRef.value)
159159

160+
const optionsState = reactive({
161+
placement: computed(() => props.placement),
162+
modifiers: [
163+
{
164+
name: 'offset',
165+
options: { offset: computed(() => [skidding.value, distance.value]) },
166+
},
167+
shallowReactive({
168+
name: 'sameWidth',
169+
enabled: props.sameWidth,
170+
phase: 'beforeWrite' as const,
171+
requires: ['computeStyles'],
172+
fn: ({ state }: { state: State }) => {
173+
state.styles.popper.width = `${state.rects.reference.width}px`
174+
},
175+
effect: ({ state }: { state: State }) => {
176+
if (state.elements.reference instanceof HTMLElement) {
177+
state.elements.popper.style.width = `${state.elements.reference.offsetWidth}px`
178+
}
179+
},
180+
}),
181+
],
182+
})
183+
160184
const { instance } = usePopper({
161185
referenceElem: triggerRef,
162186
popperElem: popperRef,
163-
options: reactive({
164-
placement: computed(() => props.placement),
165-
modifiers: [
166-
{
167-
name: 'offset',
168-
options: { offset: computed(() => [skidding.value, distance.value]) },
169-
},
170-
shallowReactive({
171-
name: 'sameWidth',
172-
enabled: props.sameWidth,
173-
phase: 'beforeWrite' as const,
174-
requires: ['computeStyles'],
175-
fn: ({ state }: { state: State }) => {
176-
state.styles.popper.width = `${state.rects.reference.width}px`
177-
},
178-
effect: ({ state }: { state: State }) => {
179-
if (state.elements.reference instanceof HTMLElement) {
180-
state.elements.popper.style.width = `${state.elements.reference.offsetWidth}px`
181-
}
182-
},
183-
}),
184-
],
185-
}),
187+
options: optionsState,
188+
})
189+
190+
watch([skidding, distance], () => {
191+
const inst = instance.value
192+
if (!inst) return
193+
194+
inst.setOptions({
195+
...inst.state.options,
196+
modifiers: (inst.state.options.modifiers || []).map((modifier: any) => {
197+
if (modifier?.name === 'offset') {
198+
return {
199+
...modifier,
200+
options: {
201+
...modifier.options,
202+
offset: [skidding.value, distance.value],
203+
},
204+
}
205+
}
206+
return modifier
207+
}),
208+
})
186209
})
187210

188211
const show = useVModel(props, 'show', emit, { passive: true })

0 commit comments

Comments
 (0)