Skip to content

Migrate unit tests to Vitest#1332

Open
brentswisher wants to merge 9 commits into
developfrom
feature/migrate-test-to-vitest
Open

Migrate unit tests to Vitest#1332
brentswisher wants to merge 9 commits into
developfrom
feature/migrate-test-to-vitest

Conversation

@brentswisher

@brentswisher brentswisher commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

This change: (check at least one)

  • Adds a new feature
  • Fixes a bug
  • Improves maintainability
  • Improves documentation
  • Is a release activity

Is this a breaking change? (check one)

  • Yes
  • No

Is the: (complete all)

  • Title of this pull request clear, concise, and indicative of the issue number it addresses, if any?
  • Test suite(s) passing?
  • Code coverage maximal?
  • Changeset added?

What does this change address?
Closes #1009 and #580

How does this change work?
This updates all of the unit tests to use Vitest, by translating the assertions from open-wc/sinon to vitest.

Additional context

A few notes:

  • The test fixture no longer clears the DOM between tests. Vitest's browser mode handles isolation between test files, and the manual clearing caused issues with some tests which built additional dom structure insinde the test, but outside the fixture. For tests where the DOM does need to be cleared between tests, it is now done in a afterEach block.
  • There are a handful of places where errors are thrown in async functions because they use the updated lifecycle method. The errorFixture doesn't work for those case. I added one-off error listening/suppression in these few spots rather that update the fixture to handle them as doing so really complicated it.
  • One image card test is now skipped, it surfaces a pre-existing bug filed in No error is emitted when selected attribute set on a non-selectable variant of the image card #1331
  • Where tests previously relied on open-wc's elementUpdated/waitUntil for components that re-render asynchronously, those are now vi.waitFor(() => expect(...)) calls that wait on the settled state rather than guessing frame counts or timing.

@changeset-bot

changeset-bot Bot commented Jul 8, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 260a8b9

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size
packages/pharos/lib/index.js 1.05 MB (0%)

@brentswisher brentswisher force-pushed the feature/migrate-test-to-vitest branch from 48b1b7c to cc349dc Compare July 8, 2026 18:11
component = await fixture(html` <test-pharos-button>I am a button</test-pharos-button> `);
});

afterEach(() => {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Vitest clears the DOM between test file, but not tests. Tests which reference the document directly need to include cleanup like this to prevent cross-test interference

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

can you clarify what counts as 'referencing the document directly' here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, no problem, at least this is my understanding 😄

In some of the button tests like this one:

it('submits a form when type is set to "submit"', async () => {
const parentNode = document.createElement('form');
let formdata = new FormData();
parentNode.setAttribute('name', 'my-form');
const submitButton = document.createElement('test-pharos-button') as PharosButton;
submitButton.type = 'submit';
submitButton.appendChild(document.createTextNode('I am a button'));
parentNode.appendChild(submitButton);
component = await fixture(
html`
<test-pharos-text-input name="my-input" value="test">
<span slot="label">I am a label</span>
</test-pharos-text-input>
`,
{ parentNode }
);

We are creating a form element, and passing it in to the fixture function as the parent element.

The fixture then mounts the component in that element and appends it to the page DOM using document.body.appendChild:

const mount = (template: TemplateResult, parentNode?: HTMLElement): Element => {
const container = parentNode ?? document.createElement('div');
document.body.appendChild(container);
render(template, container);
return container.firstElementChild as Element;
};

The test then runs and asserts thing, but Vitest browser won't clean up that form element from the DOM. It only does that automatically in between different test files, not individual tests.

Then, in the next test we do the same thing: add a form element, mount, etc.

But when it gets to this line, the selector will find 2 form elements in the page DOM, one from the previous test and one from the current test, and fail.

const form = document.querySelector('form');

Adding the afterEach block will clear the DOM between tests to keep that from happening.

await expect(component).toBeAccessible();
});

it('throws an error for an invalid variant value', async () => {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is arguable a bug in the component we should fix, but for now just wanted to limit changes to the tests

trigger.setAttribute('data-sheet-id', 'focus-sheet');
document.body.appendChild(trigger);

const button = document.querySelector('#trigger') as HTMLButtonElement;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure why this was reselcting the trigger with a querySelector before?

@brentswisher brentswisher marked this pull request as ready for review July 8, 2026 18:49
@brentswisher brentswisher requested a review from a team as a code owner July 8, 2026 18:49
@brentswisher brentswisher requested review from jialin-he, lizlove and shoupeva-ithaka and removed request for a team July 8, 2026 18:49

@lizlove lizlove left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Congrats ! 🥳
Had a quick clarifying question but otherwise 👏

component = await fixture(html` <test-pharos-button>I am a button</test-pharos-button> `);
});

afterEach(() => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

can you clarify what counts as 'referencing the document directly' here?

@brentswisher brentswisher force-pushed the feature/migrate-test-to-vitest branch from e204eb3 to 260a8b9 Compare July 8, 2026 21:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate from @open-wc/testing to e.g. @testing-library

2 participants