Skip to content

Commit

Permalink
fix(browser): fix user event state on preview provider (#7041)
Browse files Browse the repository at this point in the history
  • Loading branch information
hi-ogawa authored Dec 9, 2024
1 parent 2b5c520 commit 8e94427
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 66 deletions.
122 changes: 101 additions & 21 deletions packages/browser/src/client/tester/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,20 @@ function triggerCommand<T>(command: string, ...args: any[]) {
}

export function createUserEvent(__tl_user_event_base__?: TestingLibraryUserEvent, options?: TestingLibraryOptions): UserEvent {
let __tl_user_event__ = __tl_user_event_base__?.setup(options ?? {})
if (__tl_user_event_base__) {
return createPreviewUserEvent(__tl_user_event_base__, options ?? {})
}

const keyboard = {
unreleased: [] as string[],
}

return {
setup(options?: any) {
return createUserEvent(__tl_user_event_base__, options)
setup() {
return createUserEvent()
},
async cleanup() {
return ensureAwaited(async () => {
if (typeof __tl_user_event_base__ !== 'undefined') {
__tl_user_event__ = __tl_user_event_base__?.setup(options ?? {})
return
}
await triggerCommand('__vitest_cleanup', keyboard)
keyboard.unreleased = []
})
Expand Down Expand Up @@ -87,14 +86,6 @@ export function createUserEvent(__tl_user_event_base__?: TestingLibraryUserEvent
// testing-library user-event
async type(element: Element | Locator, text: string, options: UserEventTypeOptions = {}) {
return ensureAwaited(async () => {
if (typeof __tl_user_event__ !== 'undefined') {
return __tl_user_event__.type(
element instanceof Element ? element : element.element(),
text,
options,
)
}

const selector = convertToSelector(element)
const { unreleased } = await triggerCommand<{ unreleased: string[] }>(
'__vitest_type',
Expand All @@ -107,17 +98,11 @@ export function createUserEvent(__tl_user_event_base__?: TestingLibraryUserEvent
},
tab(options: UserEventTabOptions = {}) {
return ensureAwaited(() => {
if (typeof __tl_user_event__ !== 'undefined') {
return __tl_user_event__.tab(options)
}
return triggerCommand('__vitest_tab', options)
})
},
async keyboard(text: string) {
return ensureAwaited(async () => {
if (typeof __tl_user_event__ !== 'undefined') {
return __tl_user_event__.keyboard(text)
}
const { unreleased } = await triggerCommand<{ unreleased: string[] }>(
'__vitest_keyboard',
text,
Expand All @@ -129,6 +114,101 @@ export function createUserEvent(__tl_user_event_base__?: TestingLibraryUserEvent
}
}

function createPreviewUserEvent(userEventBase: TestingLibraryUserEvent, options: TestingLibraryOptions): UserEvent {
let userEvent = userEventBase.setup(options)

function toElement(element: Element | Locator) {
return element instanceof Element ? element : element.element()
}

const vitestUserEvent: UserEvent = {
setup(options?: any) {
return createPreviewUserEvent(userEventBase, options)
},
async cleanup() {
userEvent = userEventBase.setup(options ?? {})
},
async click(element) {
await userEvent.click(toElement(element))
},
async dblClick(element) {
await userEvent.dblClick(toElement(element))
},
async tripleClick(element) {
await userEvent.tripleClick(toElement(element))
},
async selectOptions(element, value) {
const options = (Array.isArray(value) ? value : [value]).map((option) => {
if (typeof option !== 'string') {
return toElement(option)
}
return option
})
await userEvent.selectOptions(
element,
options as string[] | HTMLElement[],
)
},
async clear(element) {
await userEvent.clear(toElement(element))
},
async hover(element: Element | Locator) {
await userEvent.hover(toElement(element))
},
async unhover(element: Element | Locator) {
await userEvent.unhover(toElement(element))
},
async upload(element: Element | Locator, files: string | string[] | File | File[]) {
const uploadPromise = (Array.isArray(files) ? files : [files]).map(async (file) => {
if (typeof file !== 'string') {
return file
}

const { content: base64, basename, mime } = await triggerCommand<{
content: string
basename: string
mime: string
}>('__vitest_fileInfo', file, 'base64')

const fileInstance = fetch(`data:${mime};base64,${base64}`)
.then(r => r.blob())
.then(blob => new File([blob], basename, { type: mime }))
return fileInstance
})
const uploadFiles = await Promise.all(uploadPromise)
return userEvent.upload(toElement(element) as HTMLElement, uploadFiles)
},

async fill(element: Element | Locator, text: string) {
await userEvent.clear(toElement(element))
return userEvent.type(toElement(element), text)
},
async dragAndDrop() {
throw new Error(`The "preview" provider doesn't support 'userEvent.dragAndDrop'`)
},

async type(element: Element | Locator, text: string, options: UserEventTypeOptions = {}) {
await userEvent.type(toElement(element), text, options)
},
async tab(options: UserEventTabOptions = {}) {
await userEvent.tab(options)
},
async keyboard(text: string) {
await userEvent.keyboard(text)
},
}

for (const [name, fn] of Object.entries(vitestUserEvent)) {
if (name !== 'setup') {
(vitestUserEvent as any)[name] = function (this: any, ...args: any[]) {
return ensureAwaited(() => fn.apply(this, args))
}
}
}

return vitestUserEvent
}

export function cdp() {
return getBrowserState().cdp!
}
Expand Down
55 changes: 11 additions & 44 deletions packages/browser/src/client/tester/locators/preview.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { userEvent } from '@testing-library/user-event'
import { page, server } from '@vitest/browser/context'
import { page, server, userEvent } from '@vitest/browser/context'
import {
getByAltTextSelector,
getByLabelSelector,
Expand All @@ -9,7 +8,7 @@ import {
getByTextSelector,
getByTitleSelector,
} from 'ivya'
import { convertElementToCssSelector, ensureAwaited } from '../../utils'
import { convertElementToCssSelector } from '../../utils'
import { getElementError } from '../public-utils'
import { Locator, selectorEngine } from './index'

Expand Down Expand Up @@ -58,71 +57,39 @@ class PreviewLocator extends Locator {
}

click(): Promise<void> {
return ensureAwaited(() => userEvent.click(this.element()))
return userEvent.click(this.element())
}

dblClick(): Promise<void> {
return ensureAwaited(() => userEvent.dblClick(this.element()))
return userEvent.dblClick(this.element())
}

tripleClick(): Promise<void> {
return ensureAwaited(() => userEvent.tripleClick(this.element()))
return userEvent.tripleClick(this.element())
}

hover(): Promise<void> {
return ensureAwaited(() => userEvent.hover(this.element()))
return userEvent.hover(this.element())
}

unhover(): Promise<void> {
return ensureAwaited(() => userEvent.unhover(this.element()))
return userEvent.unhover(this.element())
}

async fill(text: string): Promise<void> {
await this.clear()
return ensureAwaited(() => userEvent.type(this.element(), text))
return userEvent.fill(this.element(), text)
}

async upload(file: string | string[] | File | File[]): Promise<void> {
const uploadPromise = (Array.isArray(file) ? file : [file]).map(async (file) => {
if (typeof file !== 'string') {
return file
}

const { content: base64, basename, mime } = await this.triggerCommand<{
content: string
basename: string
mime: string
}>('__vitest_fileInfo', file, 'base64')

const fileInstance = fetch(`data:${mime};base64,${base64}`)
.then(r => r.blob())
.then(blob => new File([blob], basename, { type: mime }))
return fileInstance
})
const uploadFiles = await Promise.all(uploadPromise)
return ensureAwaited(() => userEvent.upload(this.element() as HTMLElement, uploadFiles))
return userEvent.upload(this.element(), file)
}

selectOptions(options_: string | string[] | HTMLElement | HTMLElement[] | Locator | Locator[]): Promise<void> {
const options = (Array.isArray(options_) ? options_ : [options_]).map((option) => {
if (typeof option !== 'string' && 'element' in option) {
return option.element() as HTMLElement
}
return option
})
return ensureAwaited(() => userEvent.selectOptions(this.element(), options as string[] | HTMLElement[]))
}

async dropTo(): Promise<void> {
throw new Error('The "preview" provider doesn\'t support `dropTo` method.')
return userEvent.selectOptions(this.element(), options_)
}

clear(): Promise<void> {
return ensureAwaited(() => userEvent.clear(this.element()))
}

async screenshot(): Promise<never> {
throw new Error('The "preview" provider doesn\'t support `screenshot` method.')
return userEvent.clear(this.element())
}

protected locator(selector: string) {
Expand Down
19 changes: 18 additions & 1 deletion test/browser/fixtures/user-event/keyboard.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect, test } from 'vitest'
import { userEvent, page, server } from '@vitest/browser/context'
import { userEvent, page } from '@vitest/browser/context'

test('non US keys', async () => {
document.body.innerHTML = `
Expand Down Expand Up @@ -34,3 +34,20 @@ test('non US keys', async () => {
console.error(e)
}
})

test('click with modifier', async () => {
document.body.innerHTML = `
<div id="test">test shift and click</div>
`
const el = document.getElementById("test")
el.addEventListener("pointerup", (e) => {
if (e.shiftKey && e.type === 'pointerup') {
el.textContent += " [ok]"
}
});

await userEvent.keyboard('{Shift>}')
await userEvent.click(el)
await userEvent.keyboard('{/Shift}')
await expect.poll(() => el.textContent).toContain("[ok]")
})

0 comments on commit 8e94427

Please sign in to comment.