116 lines
3.0 KiB
TypeScript
116 lines
3.0 KiB
TypeScript
import { mount } from '@vue/test-utils'
|
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
import { nextTick } from 'vue'
|
|
|
|
import Select from '../Select.vue'
|
|
|
|
vi.mock('vue-i18n', async () => {
|
|
const actual = await vi.importActual<typeof import('vue-i18n')>('vue-i18n')
|
|
return {
|
|
...actual,
|
|
useI18n: () => ({ t: (key: string) => key }),
|
|
}
|
|
})
|
|
|
|
const originalInnerWidth = window.innerWidth
|
|
let unmountWrapper: (() => void) | undefined
|
|
|
|
const setViewportWidth = (width: number) => {
|
|
Object.defineProperty(window, 'innerWidth', {
|
|
configurable: true,
|
|
value: width,
|
|
})
|
|
}
|
|
|
|
const mockTriggerRect = (left: number, width: number) => {
|
|
vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue({
|
|
x: left,
|
|
y: 20,
|
|
top: 20,
|
|
right: left + width,
|
|
bottom: 60,
|
|
left,
|
|
width,
|
|
height: 40,
|
|
toJSON: () => ({}),
|
|
})
|
|
}
|
|
|
|
const openSelect = async () => {
|
|
const wrapper = mount(Select, {
|
|
props: {
|
|
modelValue: null,
|
|
options: [
|
|
{
|
|
value: 'example',
|
|
label: 'very-long-unbroken-option-value-that-must-not-overflow',
|
|
},
|
|
],
|
|
},
|
|
})
|
|
unmountWrapper = () => wrapper.unmount()
|
|
|
|
await wrapper.get('button').trigger('click')
|
|
await nextTick()
|
|
|
|
return document.body.querySelector<HTMLElement>('.select-dropdown-portal')
|
|
}
|
|
|
|
afterEach(() => {
|
|
unmountWrapper?.()
|
|
unmountWrapper = undefined
|
|
document.body.innerHTML = ''
|
|
setViewportWidth(originalInnerWidth)
|
|
vi.restoreAllMocks()
|
|
})
|
|
|
|
describe('Select dropdown viewport constraints', () => {
|
|
it('preserves the existing 200px minimum width when space is available', async () => {
|
|
setViewportWidth(1024)
|
|
mockTriggerRect(20, 80)
|
|
|
|
const dropdown = await openSelect()
|
|
|
|
expect(dropdown).not.toBeNull()
|
|
expect(dropdown?.style.left).toBe('20px')
|
|
expect(dropdown?.style.minWidth).toBe('200px')
|
|
expect(dropdown?.style.maxWidth).toBe('996px')
|
|
})
|
|
|
|
it('shrinks the minimum width to fit near the right viewport edge', async () => {
|
|
setViewportWidth(320)
|
|
mockTriggerRect(220, 80)
|
|
|
|
const dropdown = await openSelect()
|
|
|
|
expect(dropdown).not.toBeNull()
|
|
expect(dropdown?.style.left).toBe('220px')
|
|
expect(dropdown?.style.minWidth).toBe('92px')
|
|
expect(dropdown?.style.maxWidth).toBe('92px')
|
|
})
|
|
|
|
it('clamps a trigger left of the viewport to the safe padding', async () => {
|
|
setViewportWidth(320)
|
|
mockTriggerRect(-20, 80)
|
|
|
|
const dropdown = await openSelect()
|
|
|
|
expect(dropdown).not.toBeNull()
|
|
expect(dropdown?.style.left).toBe('8px')
|
|
expect(dropdown?.style.minWidth).toBe('200px')
|
|
expect(dropdown?.style.maxWidth).toBe('304px')
|
|
})
|
|
|
|
it('clamps an offscreen-right trigger position to the viewport boundary', async () => {
|
|
setViewportWidth(320)
|
|
mockTriggerRect(400, 80)
|
|
|
|
const dropdown = await openSelect()
|
|
|
|
expect(dropdown).not.toBeNull()
|
|
expect(dropdown?.style.left).toBe('312px')
|
|
expect(dropdown?.style.minWidth).toBe('0px')
|
|
expect(dropdown?.style.maxWidth).toBe('0px')
|
|
})
|
|
})
|