Repository: vitest-dev/vitest
Pull request #7705: feat: Added `vitest-browser-lit` to `vitest init browser` and docs
Author: login: EskiMojo14
Labels: name: cr-tracked
Created: 2025-03-19T16:27:02Z | Merged: 2025-03-26T19:41:04Z

### Description

As discussed in #7696, I've made a package for rendering Lit components in Vitest Browser mode.

This PR updates the docs and `vitest init browser` to include this as an option.

<!-- You can also add additional context here -->

### Please don't delete this checklist! Before submitting the PR, please make sure you do the following:
- [x] It's really useful if your PR references an issue where it is discussed ahead of time. If the feature is substantial or introduces breaking changes without a discussion, PR might be closed.
- [x] Ideally, include a test that fails without this PR but passes with it.
  - _Doesn't seem to be tests for the creator command line - if I just missed it let me know_
- [x] Please, don't make changes to `pnpm-lock.yaml` unless you introduce a new test example.

### Tests
- [x] Run the tests with `pnpm test:ci`.

### Documentation
- [x] If you introduce new functionality, document it. You can run documentation with `pnpm run docs` command.

### Changesets
- [x] Changes in changelog are generated from PR name. Please, make sure that it explains your changes in an understandable manner. Please, prefix changeset messages with `feat:`, `fix:`, `perf:`, `docs:`, or `chore:`.



Diff:
diff --git a/docs/guide/browser/index.md b/docs/guide/browser/index.md
index a33310adee8a..d8c8499a539c 100644
--- a/docs/guide/browser/index.md
+++ b/docs/guide/browser/index.md
@@ -399,6 +399,10 @@ However, Vitest also provides packages to render components for several popular
 - [`vitest-browser-svelte`](https://github.com/vitest-dev/vitest-browser-svelte) to render [svelte](https://svelte.dev) components
 - [`vitest-browser-react`](https://github.com/vitest-dev/vitest-browser-react) to render [react](https://react.dev) components
 
+Community packages are available for other frameworks:
+
+- [`vitest-browser-lit`](https://github.com/EskiMojo14/vitest-browser-lit) to render [lit](https://lit.dev) components
+
 If your framework is not represented, feel free to create your own package - it is a simple wrapper around the framework renderer and `page.elementLocator` API. We will add a link to it on this page. Make sure it has a name starting with `vitest-browser-`.
 
 Besides rendering components and locating elements, you will also need to make assertions. Vitest bundles the [`@testing-library/jest-dom`](https://github.com/testing-library/jest-dom) library to provide a wide range of DOM assertions out of the box. Read more at the [Assertions API](/guide/browser/assertion-api).
@@ -473,6 +477,21 @@ test('loads and displays greeting', async () => {
   await expect.element(screen.getByRole('button')).toBeDisabled()
 })
 ```
+```ts [lit]
+import { render } from 'vitest-browser-lit'
+import { html } from 'lit'
+import './greeter-button'
+
+test('greeting appears on click', async () => {
+  const screen = render(html`<greeter-button name="World"></greeter-button>`)
+
+  const button = screen.getByRole('button')
+  await button.click()
+  const greeting = screen.getByText(/hello world/iu)
+
+  await expect.element(greeting).toBeInTheDocument()
+})
+```
 :::
 
 Vitest doesn't support all frameworks out of the box, but you can use external tools to run tests with these frameworks. We also encourage the community to create their own `vitest-browser` wrappers - if you have one, feel free to add it to the examples above.
diff --git a/packages/vitest/src/create/browser/creator.ts b/packages/vitest/src/create/browser/creator.ts
index f279de678c0e..21d84d629eac 100644
--- a/packages/vitest/src/create/browser/creator.ts
+++ b/packages/vitest/src/create/browser/creator.ts
@@ -84,6 +84,11 @@ function getFramework(): prompt.Choice[] {
       value: 'react',
       description: '"The library for web and native user interfaces"',
     },
+    {
+      title: 'lit',
+      value: 'lit',
+      description: '"A simple library for building fast, lightweight web components."',
+    },
     {
       title: 'preact',
       value: 'preact',
@@ -112,6 +117,8 @@ function getFrameworkTestPackage(framework: string) {
       return 'vitest-browser-svelte'
     case 'react':
       return 'vitest-browser-react'
+    case 'lit':
+      return 'vitest-browser-lit'
     case 'preact':
       return '@testing-library/preact'
     case 'solid':
@@ -205,6 +212,9 @@ function getPossibleFramework(dependencies: Record<string, string>) {
   if (dependencies.svelte || dependencies['@sveltejs/kit']) {
     return 'svelte'
   }
+  if (dependencies.lit || dependencies['lit-html']) {
+    return 'lit'
+  }
   if (dependencies.preact) {
     return 'preact'
   }
diff --git a/packages/vitest/src/create/browser/examples.ts b/packages/vitest/src/create/browser/examples.ts
index 7e30e29fced9..49ad549f48aa 100644
--- a/packages/vitest/src/create/browser/examples.ts
+++ b/packages/vitest/src/create/browser/examples.ts
@@ -135,6 +135,62 @@ test('renders name', async () => {
 `,
 }
 
+const litExample = {
+  name: 'HelloWorld.js',
+  js: `
+import { html, LitElement } from 'lit'
+
+export class HelloWorld extends LitElement {
+  static properties = {
+    name: { type: String },
+  }
+
+  constructor() {
+    super()
+    this.name = 'World'
+  }
+
+  render() {
+    return html\`<h1>Hello \${this.name}!</h1>\`
+  }
+}
+
+customElements.define('hello-world', HelloWorld)
+`,
+  ts: `
+import { html, LitElement } from 'lit'
+import { customElement, property } from 'lit/decorators.js'
+
+@customElement('hello-world')
+export class HelloWorld extends LitElement {
+  @property({ type: String })
+  name = 'World'
+
+  render() {
+    return html\`<h1>Hello \${this.name}!</h1>\`
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'hello-world': HelloWorld
+  }
+}
+`,
+  test: `
+import { expect, test } from 'vitest'
+import { render } from 'vitest-browser-lit'
+import { html } from 'lit'
+import './HelloWorld.js'
+
+test('renders name', async () => {
+  const screen = render(html\`<hello-world name="Vitest"></hello-world>\`)
+  const element = screen.getByText('Hello Vitest!')
+  await expect.element(element).toBeInTheDocument()
+})
+`,
+}
+
 const vanillaExample = {
   name: 'HelloWorld.js',
   js: `
@@ -191,6 +247,8 @@ function getExampleTest(framework: string) {
       return vueExample
     case 'svelte':
       return svelteExample
+    case 'lit':
+      return litExample
     case 'marko':
       return markoExample
     default:


Reviews:
user: login: sheremet-va
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-03-21T15:08:58Z
user: login: hi-ogawa
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-22T03:00:03Z
user: login: EskiMojo14
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-22T03:09:48Z
user: login: hi-ogawa
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-22T03:15:56Z

Review comments:
diff_hunk: @@ -135,6 +135,62 @@ test('renders name', async () => {
 `,
 }
 
+const litExample = {
+  name: 'HelloWorld.js',
+  js: `
+import { html, LitElement } from 'lit'
+
+export class HelloWorld extends LitElement {
+  static properties = {
+    name: { type: String },
+  }
+
+  constructor() {
+    super()
+    this.name = 'World'
+  }
+
+  render() {
+    return html\`<h1>Hello \${this.name}!</h1>\`
+  }
+}
+
+customElements.define('hello-world', HelloWorld)
+`,
+  ts: `
+import { html, LitElement } from 'lit'
+import { customElement, property } from 'lit/decorators.js'
+
+@customElement('hello-world')
+export class HelloWorld extends LitElement {
+  @property({ type: String })
+  name = 'World'
+
+  render() {
+    return html\`<h1>Hello \${this.name}!</h1>\`
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'hello-world': HelloWorld
+  }
+}
+`,
+  test: `
+import { expect, test } from 'vitest'
+import { render } from 'vitest-browser-lit'
+import { html } from 'lit'
+import './HelloWorld.js'
+
+test('renders name', async () => {
+  const screen = render(html\`<hello-world name="Vitest"></hello-world>\`)
+  const element = screen.getByText('Hello Vitest!')
+  expect(element).toBeInTheDocument()
path: packages/vitest/src/create/browser/examples.ts
user: login: hi-ogawa
body: Hey, thanks for creating `vitest-browser-lit`!

I was testing through `pnpm dlx https://pkg.pr.new/vitest@7705 init browser` and spotted this one is failing:

```
 FAIL   chromium  vitest-example/HelloWorld.test.ts > renders name
Error: expect(received).toBeInTheDocument()

received value must be an HTMLElement or an SVGElement.

 ❯ vitest-example/HelloWorld.test.ts:10:18
      8|   const element = screen.getByText('Hello Vitest!')
      9|   // await expect.element(element).toBeInTheDocument()
     10|   expect(element).toBeInTheDocument()
       |                  ^
     11| })
     12| 
```

```suggestion
  await expect.element(element).toBeInTheDocument()
```

I see marko and vanilla has `expect(element).toBeInTheDocument()`, but they might be different since they use testing-library? :thinking:
created_at: 2025-03-22T03:00:03Z
author_association: COLLABORATOR
diff_hunk: @@ -135,6 +135,62 @@ test('renders name', async () => {
 `,
 }
 
+const litExample = {
+  name: 'HelloWorld.js',
+  js: `
+import { html, LitElement } from 'lit'
+
+export class HelloWorld extends LitElement {
+  static properties = {
+    name: { type: String },
+  }
+
+  constructor() {
+    super()
+    this.name = 'World'
+  }
+
+  render() {
+    return html\`<h1>Hello \${this.name}!</h1>\`
+  }
+}
+
+customElements.define('hello-world', HelloWorld)
+`,
+  ts: `
+import { html, LitElement } from 'lit'
+import { customElement, property } from 'lit/decorators.js'
+
+@customElement('hello-world')
+export class HelloWorld extends LitElement {
+  @property({ type: String })
+  name = 'World'
+
+  render() {
+    return html\`<h1>Hello \${this.name}!</h1>\`
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'hello-world': HelloWorld
+  }
+}
+`,
+  test: `
+import { expect, test } from 'vitest'
+import { render } from 'vitest-browser-lit'
+import { html } from 'lit'
+import './HelloWorld.js'
+
+test('renders name', async () => {
+  const screen = render(html\`<hello-world name="Vitest"></hello-world>\`)
+  const element = screen.getByText('Hello Vitest!')
+  expect(element).toBeInTheDocument()
path: packages/vitest/src/create/browser/examples.ts
user: login: EskiMojo14
body: yep, that's a good spot - element here will be a locator, so we should use expect.element for retryability :)
created_at: 2025-03-22T03:09:48Z
author_association: CONTRIBUTOR

PR comments:
user: login: netlify[bot]
created_at: 2025-03-19T16:28:58Z
body: ### <span aria-hidden="true">✅</span> Deploy Preview for *vitest-dev* ready!
Built [without sensitive environment variables](https://docs.netlify.com/configure-builds/environment-variables/#sensitive-variable-policy)

|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 8951eccad476c538ee5dc0f2abbf012c5317a5d3 |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/vitest-dev/deploys/67daf13837e1bd0008340fb1 |
|<span aria-hidden="true">😎</span> Deploy Preview | [https://deploy-preview-7705--vitest-dev.netlify.app](https://deploy-preview-7705--vitest-dev.netlify.app) |
|<span aria-hidden="true">📱</span> Preview on mobile | <details><summary> Toggle QR Code... </summary><br /><br />![QR Code](https://app.netlify.com/qr-code/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL2RlcGxveS1wcmV2aWV3LTc3MDUtLXZpdGVzdC1kZXYubmV0bGlmeS5hcHAifQ.GMi6CPAfDP2n5QSbanL78_VjmK-JhJrMnO-5bdXMkjk)<br /><br />_Use your smartphone camera to open QR code link._</details> |
---
<!-- [vitest-dev Preview](https://deploy-preview-7705--vitest-dev.netlify.app) -->
_To edit notification comments on pull requests, go to your [Netlify site configuration](https://app.netlify.com/sites/vitest-dev/configuration/notifications#deploy-webhooks)._
author_association: NONE
user: login: pkg-pr-new[bot]
created_at: 2025-03-22T02:44:40Z
body: 



<details><summary><b>@vitest/browser</b></summary><p>

```
npm i https://pkg.pr.new/@vitest/browser@7705
```

</p></details>
      
    

<details><summary><b>@vitest/coverage-istanbul</b></summary><p>

```
npm i https://pkg.pr.new/@vitest/coverage-istanbul@7705
```

</p></details>
      
    

<details><summary><b>@vitest/coverage-v8</b></summary><p>

```
npm i https://pkg.pr.new/@vitest/coverage-v8@7705
```

</p></details>
      
    

<details><summary><b>@vitest/expect</b></summary><p>

```
npm i https://pkg.pr.new/@vitest/expect@7705
```

</p></details>
      
    

<details><summary><b>@vitest/mocker</b></summary><p>

```
npm i https://pkg.pr.new/@vitest/mocker@7705
```

</p></details>
      
    

<details><summary><b>@vitest/runner</b></summary><p>

```
npm i https://pkg.pr.new/@vitest/runner@7705
```

</p></details>
      
    

<details><summary><b>@vitest/pretty-format</b></summary><p>

```
npm i https://pkg.pr.new/@vitest/pretty-format@7705
```

</p></details>
      
    

<details><summary><b>@vitest/snapshot</b></summary><p>

```
npm i https://pkg.pr.new/@vitest/snapshot@7705
```

</p></details>
      
    

<details><summary><b>@vitest/spy</b></summary><p>

```
npm i https://pkg.pr.new/@vitest/spy@7705
```

</p></details>
      
    

<details><summary><b>@vitest/ui</b></summary><p>

```
npm i https://pkg.pr.new/@vitest/ui@7705
```

</p></details>
      
    

<details><summary><b>@vitest/utils</b></summary><p>

```
npm i https://pkg.pr.new/@vitest/utils@7705
```

</p></details>
      
    

<details><summary><b>vite-node</b></summary><p>

```
npm i https://pkg.pr.new/vite-node@7705
```

</p></details>
      
    

<details><summary><b>vitest</b></summary><p>

```
npm i https://pkg.pr.new/vitest@7705
```

</p></details>
      
    

<details><summary><b>@vitest/web-worker</b></summary><p>

```
npm i https://pkg.pr.new/@vitest/web-worker@7705
```

</p></details>
      
    

<details><summary><b>@vitest/ws-client</b></summary><p>

```
npm i https://pkg.pr.new/@vitest/ws-client@7705
```

</p></details>
      
    

_commit: <a href="https://github.com/vitest-dev/vitest/runs/39216767442"><code>b31b4da</code></a>_

author_association: NONE