From eb2f56b3c99a0f2cd7be2e755f998efe6de36377 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Wed, 25 Jun 2025 11:17:42 +0100 Subject: [PATCH] test: migrate more browser tests to use vitest spies Migrates a few easy ones. --- test/_util/helpers.js | 14 +++--- test/browser/customBuiltInElements.test.js | 5 +- test/browser/hydrate.test.js | 55 ++++++++++------------ test/browser/keys.test.js | 5 +- test/browser/placeholders.test.js | 9 ++-- test/browser/render.test.js | 13 +++-- test/browser/spec.test.js | 31 ++++++------ test/browser/style.test.js | 13 +++-- test/browser/svg.test.js | 20 ++++---- 9 files changed, 85 insertions(+), 80 deletions(-) diff --git a/test/_util/helpers.js b/test/_util/helpers.js index 738e8860e1..6ad9e58891 100644 --- a/test/_util/helpers.js +++ b/test/_util/helpers.js @@ -1,6 +1,7 @@ import { createElement, options } from 'preact'; import { clearLog, getLog } from './logCall'; import { teardown as testUtilTeardown } from 'preact/test-utils'; +import { vi } from 'vitest'; /** @jsx createElement */ @@ -263,10 +264,11 @@ let attributesSpy, originalAttributesPropDescriptor; export function spyOnElementAttributes() { const test = Object.getOwnPropertyDescriptor(Element.prototype, 'attributes'); + const getter = test?.get; // IE11 doesn't correctly restore the prototype methods so we have to check - // whether this prototype method is already a sinon spy. - if (!attributesSpy && !(test && test.get && test.get.isSinonProxy)) { + // whether this prototype method is already a spy. + if (!attributesSpy && !(getter && vi.isMockFunction(getter))) { if (!originalAttributesPropDescriptor) { originalAttributesPropDescriptor = Object.getOwnPropertyDescriptor( Element.prototype, @@ -274,13 +276,13 @@ export function spyOnElementAttributes() { ); } - attributesSpy = sinon.spy(Element.prototype, 'attributes', ['get']); - } else if (test && test.get && test.get.isSinonProxy) { + attributesSpy = vi.spyOn(Element.prototype, 'attributes', 'get'); + } else if (getter && vi.isMockFunction(getter)) { // Due to IE11 not resetting we will do this manually when it is a proxy. - test.get.resetHistory(); + getter.mockClear(); } - return attributesSpy || test; + return attributesSpy || getter; } function restoreElementAttributes() { diff --git a/test/browser/customBuiltInElements.test.js b/test/browser/customBuiltInElements.test.js index eb8ce1708f..d5a9f32bfc 100644 --- a/test/browser/customBuiltInElements.test.js +++ b/test/browser/customBuiltInElements.test.js @@ -1,5 +1,6 @@ import { createElement, render, Component } from 'preact'; import { setupScratch, teardown } from '../_util/helpers'; +import { vi } from 'vitest'; /** @jsx createElement */ @@ -23,7 +24,7 @@ runSuite('customised built-in elements', () => { } } - const spy = sinon.spy(); + const spy = vi.fn(); class BuiltIn extends HTMLDivElement { connectedCallback() { @@ -35,6 +36,6 @@ runSuite('customised built-in elements', () => { render(, scratch); - expect(spy).to.have.been.calledOnce; + expect(spy).toHaveBeenCalledTimes(1); }); }); diff --git a/test/browser/hydrate.test.js b/test/browser/hydrate.test.js index f25d6be7e3..896ee6f455 100644 --- a/test/browser/hydrate.test.js +++ b/test/browser/hydrate.test.js @@ -10,6 +10,7 @@ import { } from '../_util/helpers'; import { ul, li, div } from '../_util/dom'; import { logCall, clearLog, getLog } from '../_util/logCall'; +import { vi } from 'vitest'; /** @jsx createElement */ @@ -77,7 +78,7 @@ describe('hydrate()', () => { }); it('should reuse existing DOM', () => { - const onClickSpy = sinon.spy(); + const onClickSpy = vi.fn(); const html = ul([li('1'), li('2'), li('3')]); scratch.innerHTML = html; @@ -94,11 +95,11 @@ describe('hydrate()', () => { expect(scratch.innerHTML).to.equal(html); expect(getLog()).to.deep.equal([]); - expect(onClickSpy).not.to.have.been.called; + expect(onClickSpy).not.toHaveBeenCalled(); scratch.querySelector('li:last-child').dispatchEvent(createEvent('click')); - expect(onClickSpy).to.have.been.called.calledOnce; + expect(onClickSpy).toHaveBeenCalledOnce(); }); it('should skip comment nodes between dom nodes', () => { @@ -115,7 +116,7 @@ describe('hydrate()', () => { }); it('should reuse existing DOM when given components', () => { - const onClickSpy = sinon.spy(); + const onClickSpy = vi.fn(); const html = ul([li('1'), li('2'), li('3')]); scratch.innerHTML = html; @@ -132,18 +133,18 @@ describe('hydrate()', () => { expect(scratch.innerHTML).to.equal(html); expect(getLog()).to.deep.equal([]); - expect(onClickSpy).not.to.have.been.called; + expect(onClickSpy).not.toHaveBeenCalled(); scratch.querySelector('li:last-child').dispatchEvent(createEvent('click')); - expect(onClickSpy).to.have.been.called.calledOnce; + expect(onClickSpy).toHaveBeenCalledOnce(); }); it('should properly set event handlers to existing DOM when given components', () => { const proto = Element.prototype; - sinon.spy(proto, 'addEventListener'); + vi.spyOn(proto, 'addEventListener'); - const clickHandlers = [sinon.spy(), sinon.spy(), sinon.spy()]; + const clickHandlers = [vi.fn(), vi.fn(), vi.fn()]; const html = ul([li('1'), li('2'), li('3')]); @@ -161,11 +162,11 @@ describe('hydrate()', () => { expect(scratch.innerHTML).to.equal(html); expect(getLog()).to.deep.equal([]); - expect(proto.addEventListener).to.have.been.calledThrice; - expect(clickHandlers[2]).not.to.have.been.called; + expect(proto.addEventListener).toHaveBeenCalledTimes(3); + expect(clickHandlers[2]).not.toHaveBeenCalled(); scratch.querySelector('li:last-child').dispatchEvent(createEvent('click')); - expect(clickHandlers[2]).to.have.been.calledOnce; + expect(clickHandlers[2]).toHaveBeenCalledTimes(1); }); it('should add missing nodes to existing DOM when hydrating', () => { @@ -225,7 +226,7 @@ describe('hydrate()', () => { clearLog(); hydrate(vnode, scratch); - expect(attributesSpy.get).to.not.have.been.called; + expect(attributesSpy).not.toHaveBeenCalled(); expect(serializeHtml(scratch)).to.equal( sortAttributes( @@ -247,7 +248,7 @@ describe('hydrate()', () => { scratch.innerHTML = html; clearLog(); - const clickHandlers = [sinon.spy(), sinon.spy(), sinon.spy(), sinon.spy()]; + const clickHandlers = [vi.fn(), vi.fn(), vi.fn(), vi.fn()]; hydrate( @@ -263,13 +264,13 @@ describe('hydrate()', () => { expect(scratch.innerHTML).to.equal(html); expect(getLog()).to.deep.equal([]); - expect(clickHandlers[2]).not.to.have.been.called; + expect(clickHandlers[2]).not.toHaveBeenCalled(); scratch .querySelector('li:nth-child(3)') .dispatchEvent(createEvent('click')); - expect(clickHandlers[2]).to.have.been.called.calledOnce; + expect(clickHandlers[2]).toHaveBeenCalledOnce(); }); it('should correctly hydrate root Fragments', () => { @@ -281,13 +282,7 @@ describe('hydrate()', () => { scratch.innerHTML = html; clearLog(); - const clickHandlers = [ - sinon.spy(), - sinon.spy(), - sinon.spy(), - sinon.spy(), - sinon.spy() - ]; + const clickHandlers = [vi.fn(), vi.fn(), vi.fn(), vi.fn(), vi.fn()]; hydrate( @@ -306,19 +301,19 @@ describe('hydrate()', () => { expect(scratch.innerHTML).to.equal(html); expect(getLog()).to.deep.equal([]); - expect(clickHandlers[2]).not.to.have.been.called; + expect(clickHandlers[2]).not.toHaveBeenCalled(); scratch .querySelector('li:nth-child(3)') .dispatchEvent(createEvent('click')); - expect(clickHandlers[2]).to.have.been.calledOnce; - expect(clickHandlers[4]).not.to.have.been.called; + expect(clickHandlers[2]).toHaveBeenCalledTimes(1); + expect(clickHandlers[4]).not.toHaveBeenCalled(); scratch.querySelector('div').dispatchEvent(createEvent('click')); - expect(clickHandlers[2]).to.have.been.calledOnce; - expect(clickHandlers[4]).to.have.been.calledOnce; + expect(clickHandlers[2]).toHaveBeenCalledTimes(1); + expect(clickHandlers[4]).toHaveBeenCalledTimes(1); }); it('should override incorrect pre-existing DOM with VNodes passed into render', () => { @@ -374,7 +369,7 @@ describe('hydrate()', () => { ); hydrate(preactElement, scratch); - expect(attributesSpy.get).to.not.have.been.called; + expect(attributesSpy).not.toHaveBeenCalled(); expect(scratch).to.have.property( 'innerHTML', '
' @@ -382,14 +377,14 @@ describe('hydrate()', () => { }); it('should attach event handlers', () => { - let spy = sinon.spy(); + let spy = vi.fn(); scratch.innerHTML = 'Test'; let vnode = Test; hydrate(vnode, scratch); scratch.firstChild.click(); - expect(spy).to.be.calledOnce; + expect(spy).toHaveBeenCalledOnce(); }); // #2237 diff --git a/test/browser/keys.test.js b/test/browser/keys.test.js index 283aad4c3a..349ab2de65 100644 --- a/test/browser/keys.test.js +++ b/test/browser/keys.test.js @@ -3,6 +3,7 @@ import { setupRerender } from 'preact/test-utils'; import { setupScratch, teardown } from '../_util/helpers'; import { logCall, clearLog, getLog } from '../_util/logCall'; import { div } from '../_util/dom'; +import { vi } from 'vitest'; /** @jsx createElement */ @@ -101,11 +102,11 @@ describe('keys', () => { // https://fb.me/react-special-props it('should not pass key in props', () => { - const Foo = sinon.spy(function Foo() { + const Foo = vi.fn(function Foo() { return null; }); render(, scratch); - expect(Foo.args[0][0]).to.deep.equal({}); + expect(Foo.mock.calls[0][0]).to.deep.equal({}); }); it('should update in-place keyed DOM nodes', () => { diff --git a/test/browser/placeholders.test.js b/test/browser/placeholders.test.js index bb9367becc..9571288fe4 100644 --- a/test/browser/placeholders.test.js +++ b/test/browser/placeholders.test.js @@ -3,6 +3,7 @@ import { setupRerender } from 'preact/test-utils'; import { setupScratch, teardown } from '../_util/helpers'; import { logCall, clearLog, getLog } from '../_util/logCall'; import { div } from '../_util/dom'; +import { vi } from 'vitest'; /** @jsx createElement */ @@ -436,7 +437,7 @@ describe('null placeholders', () => { const Test2 = () =>
Test2
; - const ref = sinon.spy(); + const ref = vi.fn(); class App extends Component { constructor(props) { @@ -466,9 +467,9 @@ describe('null placeholders', () => { expect(scratch.innerHTML).to.equal( '
Test2
Test3
Iframe
' ); - expect(ref).to.have.been.calledOnce; + expect(ref).toHaveBeenCalledTimes(1); - ref.resetHistory(); + ref.mockClear(); clearLog(); setState({ value: false }); rerender(); @@ -477,6 +478,6 @@ describe('null placeholders', () => { '
Test2
Iframe
' ); expect(getLog()).to.deep.equal(['
Test3.remove()']); - expect(ref).to.have.been.calledOnce; + expect(ref).toHaveBeenCalledTimes(1); }); }); diff --git a/test/browser/render.test.js b/test/browser/render.test.js index 95444aa8ba..67d0c9bdd2 100644 --- a/test/browser/render.test.js +++ b/test/browser/render.test.js @@ -13,6 +13,7 @@ import { } from '../_util/helpers'; import { clearLog, getLog, logCall } from '../_util/logCall'; import { useState } from 'preact/hooks'; +import { vi } from 'vitest'; /** @jsx createElement */ @@ -1236,19 +1237,17 @@ describe('render()', () => { render(, scratch); expect(scratch.innerHTML).to.equal('
0
'); - const sandbox = sinon.createSandbox(); + const debounceSpy = vi.spyOn(options, 'debounceRendering'); try { - sandbox.spy(options, 'debounceRendering'); - comp.setState({ updates: 1 }, () => { comp.setState({ updates: 2 }); }); rerender(); expect(scratch.innerHTML).to.equal('
2
'); - expect(options.debounceRendering).to.have.been.calledOnce; + expect(debounceSpy).toHaveBeenCalledTimes(1); } finally { - sandbox.restore(); + debounceSpy.mockRestore(); } }); @@ -1300,7 +1299,7 @@ describe('render()', () => { '
Page 1
' ); - expect(attributesSpy.get).to.not.have.been.called; + expect(attributesSpy).not.toHaveBeenCalled(); render(
@@ -1312,7 +1311,7 @@ describe('render()', () => { '
Page 2
' ); - expect(attributesSpy.get).to.not.have.been.called; + expect(attributesSpy).not.toHaveBeenCalled(); }); // #2926 diff --git a/test/browser/spec.test.js b/test/browser/spec.test.js index 2511f803d5..34c2a2ffff 100644 --- a/test/browser/spec.test.js +++ b/test/browser/spec.test.js @@ -1,6 +1,7 @@ import { setupRerender } from 'preact/test-utils'; import { createElement, render, Component } from 'preact'; import { setupScratch, teardown } from '../_util/helpers'; +import { vi } from 'vitest'; /** @jsx createElement */ @@ -29,24 +30,26 @@ describe('Component spec', () => { return
; } } - sinon.spy(ForceUpdateComponent.prototype, 'componentWillUpdate'); - sinon.spy(ForceUpdateComponent.prototype, 'forceUpdate'); + vi.spyOn(ForceUpdateComponent.prototype, 'componentWillUpdate'); + vi.spyOn(ForceUpdateComponent.prototype, 'forceUpdate'); render(, scratch); - expect(ForceUpdateComponent.prototype.componentWillUpdate).not.to.have - .been.called; + expect( + ForceUpdateComponent.prototype.componentWillUpdate + ).not.toHaveBeenCalled(); forceUpdate(); rerender(); - expect(ForceUpdateComponent.prototype.componentWillUpdate).to.have.been - .called; - expect(ForceUpdateComponent.prototype.forceUpdate).to.have.been.called; + expect( + ForceUpdateComponent.prototype.componentWillUpdate + ).toHaveBeenCalled(); + expect(ForceUpdateComponent.prototype.forceUpdate).toHaveBeenCalled(); }); it('should add callback to renderCallbacks', () => { /** @type {() => void} */ let forceUpdate; - let callback = sinon.spy(); + let callback = vi.fn(); class ForceUpdateComponent extends Component { componentDidMount() { forceUpdate = () => this.forceUpdate(callback); @@ -55,17 +58,17 @@ describe('Component spec', () => { return
; } } - sinon.spy(ForceUpdateComponent.prototype, 'forceUpdate'); + vi.spyOn(ForceUpdateComponent.prototype, 'forceUpdate'); render(, scratch); forceUpdate(); rerender(); - expect(ForceUpdateComponent.prototype.forceUpdate).to.have.been.called; - expect( - ForceUpdateComponent.prototype.forceUpdate - ).to.have.been.calledWith(callback); - expect(callback).to.have.been.called; + expect(ForceUpdateComponent.prototype.forceUpdate).toHaveBeenCalled(); + expect(ForceUpdateComponent.prototype.forceUpdate).toHaveBeenCalledWith( + callback + ); + expect(callback).toHaveBeenCalled(); }); }); }); diff --git a/test/browser/style.test.js b/test/browser/style.test.js index e4baf0f99c..6192482a6a 100644 --- a/test/browser/style.test.js +++ b/test/browser/style.test.js @@ -1,5 +1,6 @@ import { createElement, render } from 'preact'; import { setupScratch, teardown, sortCss } from '../_util/helpers'; +import { vi } from 'vitest'; /** @jsx createElement */ @@ -24,9 +25,11 @@ describe('style attribute', () => { it('should not call CSSStyleDeclaration.setProperty for style strings', () => { render(
, scratch); - sinon.stub(scratch.firstChild.style, 'setProperty'); + vi.spyOn(scratch.firstChild.style, 'setProperty').mockImplementation( + () => {} + ); render(
, scratch); - expect(scratch.firstChild.style.setProperty).to.not.be.called; + expect(scratch.firstChild.style.setProperty).not.toHaveBeenCalled(); }); it('should properly switch from string styles to object styles and back', () => { @@ -194,12 +197,14 @@ describe('style attribute', () => { it('should call CSSStyleDeclaration.setProperty for css vars', () => { render(
, scratch); - sinon.stub(scratch.firstChild.style, 'setProperty'); + vi.spyOn(scratch.firstChild.style, 'setProperty').mockImplementation( + () => {} + ); render(
, scratch ); - expect(scratch.firstChild.style.setProperty).to.be.calledWith( + expect(scratch.firstChild.style.setProperty).toHaveBeenCalledWith( '--foo', '10px' ); diff --git a/test/browser/svg.test.js b/test/browser/svg.test.js index 1b19b50854..21c6bfc277 100644 --- a/test/browser/svg.test.js +++ b/test/browser/svg.test.js @@ -1,6 +1,7 @@ import { createElement, Component, render } from 'preact'; import { setupRerender } from 'preact/test-utils'; import { setupScratch, teardown, sortAttributes } from '../_util/helpers'; +import { vi } from 'vitest'; /** @jsx createElement */ @@ -195,25 +196,22 @@ describe('svg', () => { ); render(, scratch); let root = scratch.firstChild; - sinon.spy(root, 'removeAttribute'); + vi.spyOn(root, 'removeAttribute'); render(, scratch); - expect(root.removeAttribute).to.have.been.calledOnce.and.calledWith( - 'class' - ); + expect(root.removeAttribute).toHaveBeenCalledTimes(1); + expect(root.removeAttribute).toHaveBeenCalledWith('class'); - root.removeAttribute.restore(); + root.removeAttribute.mockRestore(); render(
, scratch); render(, scratch); root = scratch.firstChild; - sinon.spy(root, 'setAttribute'); + vi.spyOn(root, 'setAttribute'); render(, scratch); - expect(root.setAttribute).to.have.been.calledOnce.and.calledWith( - 'class', - 'foo_2' - ); + expect(root.setAttribute).toHaveBeenCalledTimes(1); + expect(root.setAttribute).toHaveBeenCalledWith('class', 'foo_2'); - root.setAttribute.restore(); + root.setAttribute.mockRestore(); }); it('should still support class attribute', () => {