Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/browser/context.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export interface UserEvent {
* @see {@link https://vitest.dev/guide/browser/interactivity-api.html#userevent-setup}
*/
setup: () => UserEvent
cleanup: () => Promise<void>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need documentation for every public API - in jsdoc and vitest.dev

/**
* Click on an element. Uses provider's API under the hood and supports all its options.
* @see {@link https://playwright.dev/docs/api/class-locator#locator-click} Playwright API
Expand Down
7 changes: 7 additions & 0 deletions packages/browser/src/client/tester/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ export function createUserEvent(__tl_user_event__?: TestingLibraryUserEvent): Us
setup(options?: any) {
return createUserEvent(__tl_user_event__?.setup(options))
},
async cleanup() {
if (typeof __tl_user_event__ !== 'undefined') {
return
}
await triggerCommand('__vitest_cleanup', keyboard)
keyboard.unreleased = []
},
click(element: Element | Locator, options: UserEventClickOptions = {}) {
return convertToLocator(element).click(processClickOptions(options))
},
Expand Down
4 changes: 4 additions & 0 deletions packages/browser/src/client/tester/tester.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { VitestBrowserClientMocker } from './mocker'
import { setupExpectDom } from './expect-element'

const cleanupSymbol = Symbol.for('vitest:component-cleanup')
const userEventCleanupSymbol = Symbol.for('vitest:user-event-cleanup')

const url = new URL(location.href)
const reloadStart = url.searchParams.get('__reloadStart')
Expand Down Expand Up @@ -168,6 +169,9 @@ async function executeTests(method: 'run' | 'collect', files: string[]) {
if (cleanupSymbol in page) {
(page[cleanupSymbol] as any)()
}
if (userEventCleanupSymbol in page) {
await (page[userEventCleanupSymbol] as any)()
}
}
catch (error: any) {
await client.rpc.onUnhandledError({
Expand Down
3 changes: 2 additions & 1 deletion packages/browser/src/node/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { clear } from './clear'
import { fill } from './fill'
import { selectOptions } from './select'
import { tab } from './tab'
import { keyboard } from './keyboard'
import { keyboard, keyboardCleanup } from './keyboard'
import { dragAndDrop } from './dragAndDrop'
import { hover } from './hover'
import { upload } from './upload'
Expand Down Expand Up @@ -34,4 +34,5 @@ export default {
__vitest_selectOptions: selectOptions,
__vitest_dragAndDrop: dragAndDrop,
__vitest_hover: hover,
__vitest_cleanup: keyboardCleanup,
}
13 changes: 13 additions & 0 deletions packages/browser/src/node/commands/keyboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,19 @@ export const keyboard: UserEventCommand<(text: string, state: KeyboardState) =>
}
}

export const keyboardCleanup: UserEventCommand<(state: KeyboardState) => Promise<void>> = async (
context,
state,
) => {
const { provider, contextId } = context
if (provider instanceof PlaywrightBrowserProvider) {
const page = provider.getPage(contextId)
for (const key of state.unreleased) {
await page.keyboard.up(key)
}
}
}

export async function keyboardImplementation(
pressed: Set<string>,
provider: BrowserProvider,
Expand Down
1 change: 1 addition & 0 deletions packages/browser/src/node/plugins/pluginContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export const server = {
export const commands = server.commands
export const userEvent = createUserEvent(_userEventSetup)
export { page, cdp }
page[Symbol.for('vitest:user-event-cleanup')] = () => userEvent.cleanup()
`
}

Expand Down
30 changes: 30 additions & 0 deletions test/browser/fixtures/user-event/cleanup1.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { expect, onTestFinished, test } from 'vitest'
import { userEvent } from '@vitest/browser/context'

test('cleanup1', async () => {
let logs: any[] = [];
function handler(e: KeyboardEvent) {
logs.push([e.key, e.altKey]);
};
document.addEventListener('keydown', handler)
onTestFinished(() => {
document.removeEventListener('keydown', handler);
})

await userEvent.keyboard('{Tab}')
// keep alt being pressed, which should be reset
// before running cleanup2.test.ts
await userEvent.keyboard("{Alt>}")
expect(logs).toMatchInlineSnapshot(`
[
[
"Tab",
false,
],
[
"Alt",
true,
],
]
`)
})
30 changes: 30 additions & 0 deletions test/browser/fixtures/user-event/cleanup2.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { expect, onTestFinished, test } from 'vitest'
import { userEvent } from '@vitest/browser/context'

// exact same test as cleanup1.test.ts

test('cleanup2', async () => {
let logs: any[] = [];
function handler(e: KeyboardEvent) {
logs.push([e.key, e.altKey]);
};
document.addEventListener('keydown', handler)
onTestFinished(() => {
document.removeEventListener('keydown', handler);
})

await userEvent.keyboard('{Tab}')
await userEvent.keyboard("{Alt>}")
expect(logs).toMatchInlineSnapshot(`
[
[
"Tab",
false,
],
[
"Alt",
true,
],
]
`)
})
17 changes: 17 additions & 0 deletions test/browser/fixtures/user-event/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { fileURLToPath } from 'node:url'
import { defineConfig } from 'vitest/config'

const provider = process.env.PROVIDER || 'playwright'
const name =
process.env.BROWSER || (provider === 'playwright' ? 'chromium' : 'chrome')

export default defineConfig({
cacheDir: fileURLToPath(new URL("./node_modules/.vite", import.meta.url)),
test: {
browser: {
enabled: true,
provider,
name,
},
},
})
12 changes: 12 additions & 0 deletions test/browser/specs/runner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,15 @@ error with a stack
expect(stderr).toContain('Access denied to "/inaccesible/path".')
})
})

test('user-event', async () => {
const { ctx } = await runBrowserTests({
root: './fixtures/user-event',
})
expect(Object.fromEntries(ctx.state.getFiles().map(f => [f.name, f.result.state]))).toMatchInlineSnapshot(`
{
"cleanup1.test.ts": "pass",
"cleanup2.test.ts": "pass",
}
`)
})
Loading