@wordpress/compose 8.7.2-next.v.202609031004.0 → 8.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/CHANGELOG.md +5 -1
  2. package/build/hooks/use-resize-observer/use-resize-observer.cjs +9 -0
  3. package/build/hooks/use-resize-observer/use-resize-observer.cjs.map +2 -2
  4. package/build-module/hooks/use-resize-observer/use-resize-observer.mjs +10 -1
  5. package/build-module/hooks/use-resize-observer/use-resize-observer.mjs.map +2 -2
  6. package/build-types/hooks/use-resize-observer/use-resize-observer.d.ts.map +1 -1
  7. package/package.json +12 -12
  8. package/src/higher-order/pure/test/index.jsdom.test.jsx +1 -0
  9. package/src/higher-order/with-global-events/test/index.jsdom.test.jsx +18 -15
  10. package/src/higher-order/with-global-events/test/listener.jsdom.test.js +13 -4
  11. package/src/higher-order/with-instance-id/test/index.jsdom.test.jsx +1 -0
  12. package/src/higher-order/with-state/test/index.jsdom.test.jsx +1 -0
  13. package/src/hooks/use-copy-on-click/test/index.jsdom.test.tsx +18 -17
  14. package/src/hooks/use-copy-to-clipboard/test/index.jsdom.test.tsx +12 -11
  15. package/src/hooks/use-dialog/test/index.jsdom.test.tsx +14 -13
  16. package/src/hooks/use-disabled/test/index.jsdom.test.jsx +1 -0
  17. package/src/hooks/use-drop-zone/test/index.jsdom.test.jsx +1 -0
  18. package/src/hooks/use-focus-outside/test/index.jsdom.test.jsx +7 -8
  19. package/src/hooks/use-instance-id/test/index.jsdom.test.jsx +1 -0
  20. package/src/hooks/use-merge-refs/test/index.jsdom.test.jsx +1 -0
  21. package/src/hooks/use-observable-value/test/index.jsdom.test.jsx +2 -1
  22. package/src/hooks/use-resize-observer/test/index.jsdom.test.ts +37 -0
  23. package/src/hooks/use-resize-observer/use-resize-observer.ts +13 -1
  24. package/src/hooks/use-state-with-history/test/index.jsdom.test.jsx +1 -0
  25. package/src/hooks/use-viewport-match/test/index.jsdom.test.jsx +2 -3
  26. package/src/utils/subscribe-delegated-listener/test/index.jsdom.test.js +17 -16
package/CHANGELOG.md CHANGED
@@ -2,7 +2,11 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
- ## 8.7.1-next.0 (2026-09-03)
5
+ ## 8.8.0 (2026-09-10)
6
+
7
+ ### Bug Fixes
8
+
9
+ - `useResizeObserver`: Disconnect the observer when the hook unmounts so queued callbacks cannot run after teardown ([#82687](https://github.com/WordPress/gutenberg/pull/82687)).
6
10
 
7
11
  ### Internal
8
12
 
@@ -39,6 +39,15 @@ function useResizeObserver(callback, resizeObserverOptions = {}) {
39
39
  const callbackEvent = (0, import_use_event.default)(callback);
40
40
  const observedElementRef = (0, import_element.useRef)(null);
41
41
  const resizeObserverRef = (0, import_element.useRef)(void 0);
42
+ (0, import_element.useEffect)(() => {
43
+ if (observedElementRef.current) {
44
+ resizeObserverRef.current?.observe(
45
+ observedElementRef.current,
46
+ resizeObserverOptions
47
+ );
48
+ }
49
+ return () => resizeObserverRef.current?.disconnect();
50
+ }, []);
42
51
  return (0, import_use_event.default)((element) => {
43
52
  if (element === observedElementRef.current) {
44
53
  return;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/hooks/use-resize-observer/use-resize-observer.ts"],
4
- "sourcesContent": ["import { useRef } from '@wordpress/element';\nimport useEvent from '../use-event';\n\n// This is the current implementation of `useResizeObserver`.\n//\n// The legacy implementation is still supported for backwards compatibility.\n// This is achieved by overloading the exported function with both signatures,\n// and detecting which API is being used at runtime.\nexport function useResizeObserver< T extends HTMLElement >(\n\tcallback: ResizeObserverCallback,\n\tresizeObserverOptions: ResizeObserverOptions = {}\n): ( element?: T | null ) => void {\n\tconst callbackEvent = useEvent( callback );\n\n\tconst observedElementRef = useRef< T | null >( null );\n\tconst resizeObserverRef = useRef< ResizeObserver >( undefined );\n\treturn useEvent( ( element?: T | null ) => {\n\t\tif ( element === observedElementRef.current ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Set up `ResizeObserver`.\n\t\tresizeObserverRef.current ??= new ResizeObserver( callbackEvent );\n\t\tconst { current: resizeObserver } = resizeObserverRef;\n\n\t\t// Unobserve previous element.\n\t\tif ( observedElementRef.current ) {\n\t\t\tresizeObserver.unobserve( observedElementRef.current );\n\t\t}\n\n\t\t// Observe new element.\n\t\tobservedElementRef.current = element ?? null;\n\t\tif ( element ) {\n\t\t\tresizeObserver.observe( element, resizeObserverOptions );\n\t\t}\n\t} );\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAAuB;AACvB,uBAAqB;AAOd,SAAS,kBACf,UACA,wBAA+C,CAAC,GACf;AACjC,QAAM,oBAAgB,iBAAAA,SAAU,QAAS;AAEzC,QAAM,yBAAqB,uBAAoB,IAAK;AACpD,QAAM,wBAAoB,uBAA0B,MAAU;AAC9D,aAAO,iBAAAA,SAAU,CAAE,YAAwB;AAC1C,QAAK,YAAY,mBAAmB,SAAU;AAC7C;AAAA,IACD;AAGA,sBAAkB,YAAY,IAAI,eAAgB,aAAc;AAChE,UAAM,EAAE,SAAS,eAAe,IAAI;AAGpC,QAAK,mBAAmB,SAAU;AACjC,qBAAe,UAAW,mBAAmB,OAAQ;AAAA,IACtD;AAGA,uBAAmB,UAAU,WAAW;AACxC,QAAK,SAAU;AACd,qBAAe,QAAS,SAAS,qBAAsB;AAAA,IACxD;AAAA,EACD,CAAE;AACH;",
4
+ "sourcesContent": ["import { useEffect, useRef } from '@wordpress/element';\nimport useEvent from '../use-event';\n\n// This is the current implementation of `useResizeObserver`.\n//\n// The legacy implementation is still supported for backwards compatibility.\n// This is achieved by overloading the exported function with both signatures,\n// and detecting which API is being used at runtime.\nexport function useResizeObserver< T extends HTMLElement >(\n\tcallback: ResizeObserverCallback,\n\tresizeObserverOptions: ResizeObserverOptions = {}\n): ( element?: T | null ) => void {\n\tconst callbackEvent = useEvent( callback );\n\n\tconst observedElementRef = useRef< T | null >( null );\n\tconst resizeObserverRef = useRef< ResizeObserver >( undefined );\n\tuseEffect( () => {\n\t\tif ( observedElementRef.current ) {\n\t\t\tresizeObserverRef.current?.observe(\n\t\t\t\tobservedElementRef.current,\n\t\t\t\tresizeObserverOptions\n\t\t\t);\n\t\t}\n\t\treturn () => resizeObserverRef.current?.disconnect();\n\t\t// This effect owns the observer lifecycle. The returned ref handles\n\t\t// element and option changes without recreating the observer.\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, [] );\n\treturn useEvent( ( element?: T | null ) => {\n\t\tif ( element === observedElementRef.current ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Set up `ResizeObserver`.\n\t\tresizeObserverRef.current ??= new ResizeObserver( callbackEvent );\n\t\tconst { current: resizeObserver } = resizeObserverRef;\n\n\t\t// Unobserve previous element.\n\t\tif ( observedElementRef.current ) {\n\t\t\tresizeObserver.unobserve( observedElementRef.current );\n\t\t}\n\n\t\t// Observe new element.\n\t\tobservedElementRef.current = element ?? null;\n\t\tif ( element ) {\n\t\t\tresizeObserver.observe( element, resizeObserverOptions );\n\t\t}\n\t} );\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAAkC;AAClC,uBAAqB;AAOd,SAAS,kBACf,UACA,wBAA+C,CAAC,GACf;AACjC,QAAM,oBAAgB,iBAAAA,SAAU,QAAS;AAEzC,QAAM,yBAAqB,uBAAoB,IAAK;AACpD,QAAM,wBAAoB,uBAA0B,MAAU;AAC9D,gCAAW,MAAM;AAChB,QAAK,mBAAmB,SAAU;AACjC,wBAAkB,SAAS;AAAA,QAC1B,mBAAmB;AAAA,QACnB;AAAA,MACD;AAAA,IACD;AACA,WAAO,MAAM,kBAAkB,SAAS,WAAW;AAAA,EAIpD,GAAG,CAAC,CAAE;AACN,aAAO,iBAAAA,SAAU,CAAE,YAAwB;AAC1C,QAAK,YAAY,mBAAmB,SAAU;AAC7C;AAAA,IACD;AAGA,sBAAkB,YAAY,IAAI,eAAgB,aAAc;AAChE,UAAM,EAAE,SAAS,eAAe,IAAI;AAGpC,QAAK,mBAAmB,SAAU;AACjC,qBAAe,UAAW,mBAAmB,OAAQ;AAAA,IACtD;AAGA,uBAAmB,UAAU,WAAW;AACxC,QAAK,SAAU;AACd,qBAAe,QAAS,SAAS,qBAAsB;AAAA,IACxD;AAAA,EACD,CAAE;AACH;",
6
6
  "names": ["useEvent"]
7
7
  }
@@ -1,10 +1,19 @@
1
1
  // packages/compose/src/hooks/use-resize-observer/use-resize-observer.ts
2
- import { useRef } from "@wordpress/element";
2
+ import { useEffect, useRef } from "@wordpress/element";
3
3
  import useEvent from "../use-event/index.mjs";
4
4
  function useResizeObserver(callback, resizeObserverOptions = {}) {
5
5
  const callbackEvent = useEvent(callback);
6
6
  const observedElementRef = useRef(null);
7
7
  const resizeObserverRef = useRef(void 0);
8
+ useEffect(() => {
9
+ if (observedElementRef.current) {
10
+ resizeObserverRef.current?.observe(
11
+ observedElementRef.current,
12
+ resizeObserverOptions
13
+ );
14
+ }
15
+ return () => resizeObserverRef.current?.disconnect();
16
+ }, []);
8
17
  return useEvent((element) => {
9
18
  if (element === observedElementRef.current) {
10
19
  return;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/hooks/use-resize-observer/use-resize-observer.ts"],
4
- "sourcesContent": ["import { useRef } from '@wordpress/element';\nimport useEvent from '../use-event';\n\n// This is the current implementation of `useResizeObserver`.\n//\n// The legacy implementation is still supported for backwards compatibility.\n// This is achieved by overloading the exported function with both signatures,\n// and detecting which API is being used at runtime.\nexport function useResizeObserver< T extends HTMLElement >(\n\tcallback: ResizeObserverCallback,\n\tresizeObserverOptions: ResizeObserverOptions = {}\n): ( element?: T | null ) => void {\n\tconst callbackEvent = useEvent( callback );\n\n\tconst observedElementRef = useRef< T | null >( null );\n\tconst resizeObserverRef = useRef< ResizeObserver >( undefined );\n\treturn useEvent( ( element?: T | null ) => {\n\t\tif ( element === observedElementRef.current ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Set up `ResizeObserver`.\n\t\tresizeObserverRef.current ??= new ResizeObserver( callbackEvent );\n\t\tconst { current: resizeObserver } = resizeObserverRef;\n\n\t\t// Unobserve previous element.\n\t\tif ( observedElementRef.current ) {\n\t\t\tresizeObserver.unobserve( observedElementRef.current );\n\t\t}\n\n\t\t// Observe new element.\n\t\tobservedElementRef.current = element ?? null;\n\t\tif ( element ) {\n\t\t\tresizeObserver.observe( element, resizeObserverOptions );\n\t\t}\n\t} );\n}\n"],
5
- "mappings": ";AAAA,SAAS,cAAc;AACvB,OAAO,cAAc;AAOd,SAAS,kBACf,UACA,wBAA+C,CAAC,GACf;AACjC,QAAM,gBAAgB,SAAU,QAAS;AAEzC,QAAM,qBAAqB,OAAoB,IAAK;AACpD,QAAM,oBAAoB,OAA0B,MAAU;AAC9D,SAAO,SAAU,CAAE,YAAwB;AAC1C,QAAK,YAAY,mBAAmB,SAAU;AAC7C;AAAA,IACD;AAGA,sBAAkB,YAAY,IAAI,eAAgB,aAAc;AAChE,UAAM,EAAE,SAAS,eAAe,IAAI;AAGpC,QAAK,mBAAmB,SAAU;AACjC,qBAAe,UAAW,mBAAmB,OAAQ;AAAA,IACtD;AAGA,uBAAmB,UAAU,WAAW;AACxC,QAAK,SAAU;AACd,qBAAe,QAAS,SAAS,qBAAsB;AAAA,IACxD;AAAA,EACD,CAAE;AACH;",
4
+ "sourcesContent": ["import { useEffect, useRef } from '@wordpress/element';\nimport useEvent from '../use-event';\n\n// This is the current implementation of `useResizeObserver`.\n//\n// The legacy implementation is still supported for backwards compatibility.\n// This is achieved by overloading the exported function with both signatures,\n// and detecting which API is being used at runtime.\nexport function useResizeObserver< T extends HTMLElement >(\n\tcallback: ResizeObserverCallback,\n\tresizeObserverOptions: ResizeObserverOptions = {}\n): ( element?: T | null ) => void {\n\tconst callbackEvent = useEvent( callback );\n\n\tconst observedElementRef = useRef< T | null >( null );\n\tconst resizeObserverRef = useRef< ResizeObserver >( undefined );\n\tuseEffect( () => {\n\t\tif ( observedElementRef.current ) {\n\t\t\tresizeObserverRef.current?.observe(\n\t\t\t\tobservedElementRef.current,\n\t\t\t\tresizeObserverOptions\n\t\t\t);\n\t\t}\n\t\treturn () => resizeObserverRef.current?.disconnect();\n\t\t// This effect owns the observer lifecycle. The returned ref handles\n\t\t// element and option changes without recreating the observer.\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, [] );\n\treturn useEvent( ( element?: T | null ) => {\n\t\tif ( element === observedElementRef.current ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Set up `ResizeObserver`.\n\t\tresizeObserverRef.current ??= new ResizeObserver( callbackEvent );\n\t\tconst { current: resizeObserver } = resizeObserverRef;\n\n\t\t// Unobserve previous element.\n\t\tif ( observedElementRef.current ) {\n\t\t\tresizeObserver.unobserve( observedElementRef.current );\n\t\t}\n\n\t\t// Observe new element.\n\t\tobservedElementRef.current = element ?? null;\n\t\tif ( element ) {\n\t\t\tresizeObserver.observe( element, resizeObserverOptions );\n\t\t}\n\t} );\n}\n"],
5
+ "mappings": ";AAAA,SAAS,WAAW,cAAc;AAClC,OAAO,cAAc;AAOd,SAAS,kBACf,UACA,wBAA+C,CAAC,GACf;AACjC,QAAM,gBAAgB,SAAU,QAAS;AAEzC,QAAM,qBAAqB,OAAoB,IAAK;AACpD,QAAM,oBAAoB,OAA0B,MAAU;AAC9D,YAAW,MAAM;AAChB,QAAK,mBAAmB,SAAU;AACjC,wBAAkB,SAAS;AAAA,QAC1B,mBAAmB;AAAA,QACnB;AAAA,MACD;AAAA,IACD;AACA,WAAO,MAAM,kBAAkB,SAAS,WAAW;AAAA,EAIpD,GAAG,CAAC,CAAE;AACN,SAAO,SAAU,CAAE,YAAwB;AAC1C,QAAK,YAAY,mBAAmB,SAAU;AAC7C;AAAA,IACD;AAGA,sBAAkB,YAAY,IAAI,eAAgB,aAAc;AAChE,UAAM,EAAE,SAAS,eAAe,IAAI;AAGpC,QAAK,mBAAmB,SAAU;AACjC,qBAAe,UAAW,mBAAmB,OAAQ;AAAA,IACtD;AAGA,uBAAmB,UAAU,WAAW;AACxC,QAAK,SAAU;AACd,qBAAe,QAAS,SAAS,qBAAsB;AAAA,IACxD;AAAA,EACD,CAAE;AACH;",
6
6
  "names": []
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"use-resize-observer.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-resize-observer/use-resize-observer.ts"],"names":[],"mappings":"AAQA,wBAAgB,iBAAiB,CAAE,CAAC,SAAS,WAAW,EACvD,QAAQ,EAAE,sBAAsB,EAChC,qBAAqB,GAAE,qBAA0B,GAC/C,CAAE,OAAO,CAAC,EAAE,CAAC,GAAG,IAAI,KAAM,IAAI,CAyBhC"}
1
+ {"version":3,"file":"use-resize-observer.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-resize-observer/use-resize-observer.ts"],"names":[],"mappings":"AAQA,wBAAgB,iBAAiB,CAAE,CAAC,SAAS,WAAW,EACvD,QAAQ,EAAE,sBAAsB,EAChC,qBAAqB,GAAE,qBAA0B,GAC/C,CAAE,OAAO,CAAC,EAAE,CAAC,GAAG,IAAI,KAAM,IAAI,CAqChC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress/compose",
3
- "version": "8.7.2-next.v.202609031004.0",
3
+ "version": "8.8.0",
4
4
  "description": "WordPress higher-order components (HOCs).",
5
5
  "author": "The WordPress Contributors",
6
6
  "license": "GPL-2.0-or-later",
@@ -46,26 +46,26 @@
46
46
  "sideEffects": false,
47
47
  "dependencies": {
48
48
  "@types/mousetrap": "^1.6.8",
49
- "@wordpress/deprecated": "^4.54.1-next.v.202609031004.0",
50
- "@wordpress/dom": "^4.54.1-next.v.202609031004.0",
51
- "@wordpress/element": "^8.6.2-next.v.202609031004.0",
52
- "@wordpress/is-shallow-equal": "^5.54.1-next.v.202609031004.0",
53
- "@wordpress/keycodes": "^4.54.1-next.v.202609031004.0",
54
- "@wordpress/priority-queue": "^3.54.1-next.v.202609031004.0",
55
- "@wordpress/private-apis": "^1.54.2-next.v.202609031004.0",
56
- "@wordpress/undo-manager": "^1.54.1-next.v.202609031004.0",
49
+ "@wordpress/deprecated": "^4.55.0",
50
+ "@wordpress/dom": "^4.55.0",
51
+ "@wordpress/element": "^8.7.0",
52
+ "@wordpress/is-shallow-equal": "^5.55.0",
53
+ "@wordpress/keycodes": "^4.55.0",
54
+ "@wordpress/priority-queue": "^3.55.0",
55
+ "@wordpress/private-apis": "^1.55.0",
56
+ "@wordpress/undo-manager": "^1.55.0",
57
57
  "change-case": "^4.1.2",
58
58
  "mousetrap": "^1.6.5",
59
59
  "use-memo-one": "^1.1.1"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@testing-library/dom": "^10.4.1",
63
- "@testing-library/react": "^16.3.2",
63
+ "@testing-library/react": "^16.3.3",
64
64
  "@testing-library/user-event": "^14.6.1",
65
65
  "@types/jest": "^30.0.0",
66
66
  "mock-match-media": "^0.4.2",
67
67
  "react-dom": "^18.3.1",
68
- "vitest": "^4.1.10"
68
+ "vitest": "^5.0.0"
69
69
  },
70
70
  "peerDependencies": {
71
71
  "@types/react": "^18 || ^19",
@@ -79,5 +79,5 @@
79
79
  "publishConfig": {
80
80
  "access": "public"
81
81
  },
82
- "gitHead": "7275d6c6d93b542fca3cd98cdb9c7344ddd11e19"
82
+ "gitHead": "485f42ae8a1c58ceea18371a507fd4acfa86fbd8"
83
83
  }
@@ -1,3 +1,4 @@
1
+ import { afterEach, describe, expect, it } from 'vitest';
1
2
  import { render, screen } from '@testing-library/react';
2
3
  import { logged } from '@wordpress/deprecated';
3
4
  import pure from '../';
@@ -1,20 +1,23 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
1
2
  import { render, screen } from '@testing-library/react';
2
3
  import { Component } from '@wordpress/element';
3
4
  import withGlobalEvents from '../';
4
5
  import Listener from '../listener';
5
6
 
6
- jest.mock( '../listener', () => {
7
- const ActualListener = jest.requireActual( '../listener' ).default;
7
+ vi.mock( import( '../listener' ), async ( importOriginal ) => {
8
+ const { default: ActualListener } = await importOriginal();
8
9
 
9
- return class extends ActualListener {
10
- constructor() {
11
- super( ...arguments );
10
+ return {
11
+ default: class extends ActualListener {
12
+ constructor() {
13
+ super( ...arguments );
12
14
 
13
- this.constructor._instance = this;
15
+ this.constructor._instance = this;
14
16
 
15
- jest.spyOn( this, 'add' );
16
- jest.spyOn( this, 'remove' );
17
- }
17
+ vi.spyOn( this, 'add' );
18
+ vi.spyOn( this, 'remove' );
19
+ }
20
+ },
18
21
  };
19
22
  } );
20
23
 
@@ -30,12 +33,12 @@ describe( 'withGlobalEvents', () => {
30
33
  }
31
34
  }
32
35
 
33
- beforeAll( () => {
34
- jest.spyOn( OriginalComponent.prototype, 'handleResize' );
35
- } );
36
-
37
36
  beforeEach( () => {
38
- jest.clearAllMocks();
37
+ vi.spyOn( OriginalComponent.prototype, 'handleResize' );
38
+ if ( Listener._instance ) {
39
+ vi.spyOn( Listener._instance, 'add' );
40
+ vi.spyOn( Listener._instance, 'remove' );
41
+ }
39
42
  } );
40
43
 
41
44
  it( 'renders with original component', () => {
@@ -67,7 +70,7 @@ describe( 'withGlobalEvents', () => {
67
70
  const EnhancedComponent = withGlobalEvents( {
68
71
  resize: 'handleResize',
69
72
  } )( OriginalComponent );
70
- const onResize = jest.fn();
73
+ const onResize = vi.fn();
71
74
 
72
75
  render(
73
76
  <EnhancedComponent ref={ () => {} } onResize={ onResize }>
@@ -1,19 +1,28 @@
1
+ import {
2
+ afterAll,
3
+ beforeAll,
4
+ beforeEach,
5
+ describe,
6
+ expect,
7
+ it,
8
+ vi,
9
+ } from 'vitest';
1
10
  import Listener from '../listener';
2
11
 
3
12
  describe( 'Listener', () => {
4
- const createHandler = () => ( { handleEvent: jest.fn() } );
13
+ const createHandler = () => ( { handleEvent: vi.fn() } );
5
14
 
6
15
  let listener, _addEventListener, _removeEventListener;
7
16
  beforeAll( () => {
8
17
  _addEventListener = global.window.addEventListener;
9
18
  _removeEventListener = global.window.removeEventListener;
10
- global.window.addEventListener = jest.fn();
11
- global.window.removeEventListener = jest.fn();
19
+ global.window.addEventListener = vi.fn();
20
+ global.window.removeEventListener = vi.fn();
12
21
  } );
13
22
 
14
23
  beforeEach( () => {
15
24
  listener = new Listener();
16
- jest.clearAllMocks();
25
+ vi.clearAllMocks();
17
26
  } );
18
27
 
19
28
  afterAll( () => {
@@ -1,3 +1,4 @@
1
+ import { describe, expect, it } from 'vitest';
1
2
  import { render, screen } from '@testing-library/react';
2
3
  import withInstanceId from '../';
3
4
 
@@ -1,3 +1,4 @@
1
+ import { describe, expect, it } from 'vitest';
1
2
  import { render, screen } from '@testing-library/react';
2
3
  import userEvent from '@testing-library/user-event';
3
4
  import withState from '../';
@@ -1,10 +1,11 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
1
2
  import { render, screen, act } from '@testing-library/react';
2
3
  import userEvent from '@testing-library/user-event';
3
4
  import deprecated from '@wordpress/deprecated';
4
5
  import { useRef } from '@wordpress/element';
5
6
  import useCopyOnClick from '../';
6
7
 
7
- jest.mock( '@wordpress/deprecated' );
8
+ vi.mock( import( '@wordpress/deprecated' ) );
8
9
 
9
10
  interface TestComponentProps {
10
11
  text: string | ( () => string );
@@ -12,6 +13,10 @@ interface TestComponentProps {
12
13
  }
13
14
 
14
15
  describe( 'useCopyOnClick', () => {
16
+ afterEach( () => {
17
+ vi.useRealTimers();
18
+ } );
19
+
15
20
  const TestComponent = ( { text, timeout = 4000 }: TestComponentProps ) => {
16
21
  const ref = useRef< HTMLButtonElement >( null );
17
22
  const hasCopied = useCopyOnClick( ref, text, timeout );
@@ -23,7 +28,7 @@ describe( 'useCopyOnClick', () => {
23
28
  };
24
29
 
25
30
  it( 'should call deprecated when the hook is used', () => {
26
- jest.mocked( deprecated ).mockClear();
31
+ vi.mocked( deprecated ).mockClear();
27
32
  render( <TestComponent text="test text" /> );
28
33
 
29
34
  expect( deprecated ).toHaveBeenCalledWith(
@@ -39,7 +44,7 @@ describe( 'useCopyOnClick', () => {
39
44
  const user = userEvent.setup();
40
45
  render( <TestComponent text="test text" /> );
41
46
 
42
- const writeTextMock = jest
47
+ const writeTextMock = vi
43
48
  .spyOn( navigator.clipboard, 'writeText' )
44
49
  .mockResolvedValue();
45
50
 
@@ -53,7 +58,7 @@ describe( 'useCopyOnClick', () => {
53
58
  const user = userEvent.setup();
54
59
  render( <TestComponent text="test text" /> );
55
60
 
56
- jest.spyOn( navigator.clipboard, 'writeText' ).mockResolvedValue();
61
+ vi.spyOn( navigator.clipboard, 'writeText' ).mockResolvedValue();
57
62
 
58
63
  expect( screen.getByRole( 'button' ) ).toHaveTextContent( 'Copy' );
59
64
 
@@ -67,36 +72,32 @@ describe( 'useCopyOnClick', () => {
67
72
  } );
68
73
 
69
74
  it( 'should reset hasCopied after timeout', async () => {
70
- jest.useFakeTimers();
75
+ vi.useFakeTimers( { shouldAdvanceTime: true } );
71
76
  const user = userEvent.setup( {
72
- advanceTimers: jest.advanceTimersByTime,
77
+ advanceTimers: ( delay ) => vi.advanceTimersByTime( delay ),
73
78
  } );
74
79
  render( <TestComponent text="test text" timeout={ 1000 } /> );
75
80
 
76
- jest.spyOn( navigator.clipboard, 'writeText' ).mockResolvedValue();
81
+ vi.spyOn( navigator.clipboard, 'writeText' ).mockResolvedValue();
77
82
 
78
83
  await user.click( screen.getByRole( 'button' ) );
79
84
 
80
85
  await act( async () => {
81
- const p = new Promise( ( resolve ) => setTimeout( resolve, 0 ) );
82
- jest.advanceTimersByTime( 0 );
83
- await p;
86
+ await vi.advanceTimersByTimeAsync( 0 );
84
87
  } );
85
88
  expect( screen.getByRole( 'button' ) ).toHaveTextContent( 'Copied!' );
86
89
 
87
- act( () => {
88
- jest.advanceTimersByTime( 1000 );
90
+ await act( async () => {
91
+ await vi.advanceTimersByTimeAsync( 1000 );
89
92
  } );
90
93
  expect( screen.getByRole( 'button' ) ).toHaveTextContent( 'Copy' );
91
-
92
- jest.useRealTimers();
93
94
  } );
94
95
 
95
96
  it( 'should not set hasCopied when copy fails', async () => {
96
97
  const user = userEvent.setup();
97
98
  render( <TestComponent text="test text" /> );
98
99
 
99
- jest.spyOn( navigator.clipboard, 'writeText' ).mockRejectedValue(
100
+ vi.spyOn( navigator.clipboard, 'writeText' ).mockRejectedValue(
100
101
  new Error()
101
102
  );
102
103
 
@@ -110,7 +111,7 @@ describe( 'useCopyOnClick', () => {
110
111
  } );
111
112
 
112
113
  it( 'should not update hasCopied after unmount', async () => {
113
- const renderSpy = jest.fn();
114
+ const renderSpy = vi.fn();
114
115
 
115
116
  const SpyComponent = ( { text }: { text: string } ) => {
116
117
  const ref = useRef< HTMLButtonElement >( null );
@@ -127,7 +128,7 @@ describe( 'useCopyOnClick', () => {
127
128
  const delayedPromise = new Promise< void >( ( resolve ) => {
128
129
  resolvePromise = resolve;
129
130
  } );
130
- jest.spyOn( navigator.clipboard, 'writeText' ).mockReturnValue(
131
+ vi.spyOn( navigator.clipboard, 'writeText' ).mockReturnValue(
131
132
  delayedPromise as Promise< void >
132
133
  );
133
134
 
@@ -1,3 +1,4 @@
1
+ import { describe, expect, it, vi } from 'vitest';
1
2
  import { render, screen, act } from '@testing-library/react';
2
3
  import userEvent from '@testing-library/user-event';
3
4
  import useCopyToClipboard, { copyToClipboard, restoreFocus } from '../';
@@ -17,7 +18,7 @@ describe( 'useCopyToClipboard', () => {
17
18
  const user = userEvent.setup();
18
19
  render( <TestComponent text="test text" /> );
19
20
 
20
- const writeTextMock = jest
21
+ const writeTextMock = vi
21
22
  .spyOn( navigator.clipboard, 'writeText' )
22
23
  .mockResolvedValue();
23
24
 
@@ -29,7 +30,7 @@ describe( 'useCopyToClipboard', () => {
29
30
 
30
31
  it( 'should call onSuccess when copy succeeds', async () => {
31
32
  const user = userEvent.setup();
32
- const onSuccess = jest.fn();
33
+ const onSuccess = vi.fn();
33
34
  render( <TestComponent text="test text" onSuccess={ onSuccess } /> );
34
35
 
35
36
  await user.click( screen.getByRole( 'button' ) );
@@ -39,7 +40,7 @@ describe( 'useCopyToClipboard', () => {
39
40
 
40
41
  it( 'should call onSuccess when copy empty text', async () => {
41
42
  const user = userEvent.setup();
42
- const onSuccess = jest.fn();
43
+ const onSuccess = vi.fn();
43
44
  render( <TestComponent text="" onSuccess={ onSuccess } /> );
44
45
 
45
46
  await user.click( screen.getByRole( 'button' ) );
@@ -49,10 +50,10 @@ describe( 'useCopyToClipboard', () => {
49
50
 
50
51
  it( 'should not call onSuccess when copy fails', async () => {
51
52
  const user = userEvent.setup();
52
- const onSuccess = jest.fn();
53
+ const onSuccess = vi.fn();
53
54
  render( <TestComponent text="test text" onSuccess={ onSuccess } /> );
54
55
 
55
- jest.spyOn( navigator.clipboard, 'writeText' ).mockRejectedValue(
56
+ vi.spyOn( navigator.clipboard, 'writeText' ).mockRejectedValue(
56
57
  new Error()
57
58
  );
58
59
 
@@ -66,12 +67,12 @@ describe( 'useCopyToClipboard', () => {
66
67
  const delayedPromise = new Promise< void >( ( resolve ) => {
67
68
  resolvePromise = resolve;
68
69
  } );
69
- jest.spyOn( navigator.clipboard, 'writeText' ).mockReturnValue(
70
+ vi.spyOn( navigator.clipboard, 'writeText' ).mockReturnValue(
70
71
  delayedPromise
71
72
  );
72
73
 
73
74
  const user = userEvent.setup();
74
- const onSuccess = jest.fn();
75
+ const onSuccess = vi.fn();
75
76
  const { unmount } = render(
76
77
  <TestComponent text="test" onSuccess={ onSuccess } />
77
78
  );
@@ -92,7 +93,7 @@ describe( 'useCopyToClipboard', () => {
92
93
  const delayedPromise = new Promise< void >( ( resolve ) => {
93
94
  resolvePromise = resolve;
94
95
  } );
95
- jest.spyOn( navigator.clipboard, 'writeText' ).mockReturnValue(
96
+ vi.spyOn( navigator.clipboard, 'writeText' ).mockReturnValue(
96
97
  delayedPromise
97
98
  );
98
99
 
@@ -100,7 +101,7 @@ describe( 'useCopyToClipboard', () => {
100
101
  const { unmount } = render( <TestComponent text="test" /> );
101
102
 
102
103
  const button = screen.getByRole( 'button' );
103
- const focusSpy = jest.spyOn( button, 'focus' );
104
+ const focusSpy = vi.spyOn( button, 'focus' );
104
105
 
105
106
  await user.click( button );
106
107
  unmount();
@@ -129,7 +130,7 @@ describe( 'copyToClipboard', () => {
129
130
 
130
131
  // JSDOM does not implement execCommand; add a mock for the fallback path.
131
132
  // See: https://github.com/jsdom/jsdom/issues/1742
132
- const execCommandMock = jest.fn().mockReturnValue( true );
133
+ const execCommandMock = vi.fn().mockReturnValue( true );
133
134
  Object.defineProperty( document, 'execCommand', {
134
135
  value: execCommandMock,
135
136
  configurable: true,
@@ -154,7 +155,7 @@ describe( 'restoreFocus', () => {
154
155
  it( 'should focus the trigger element', () => {
155
156
  const trigger = document.createElement( 'button' );
156
157
  document.body.appendChild( trigger );
157
- const focusMock = jest.spyOn( trigger, 'focus' );
158
+ const focusMock = vi.spyOn( trigger, 'focus' );
158
159
 
159
160
  restoreFocus( trigger );
160
161
 
@@ -1,3 +1,4 @@
1
+ import { describe, expect, it, vi } from 'vitest';
1
2
  import { render, screen } from '@testing-library/react';
2
3
  import userEvent from '@testing-library/user-event';
3
4
  import { createPortal } from 'react-dom';
@@ -20,7 +21,7 @@ function Dialog( { onClose }: { onClose?: () => void } ) {
20
21
  describe( 'useDialog', () => {
21
22
  it( 'should call onClose when Escape is pressed', async () => {
22
23
  const user = userEvent.setup();
23
- const onClose = jest.fn();
24
+ const onClose = vi.fn();
24
25
 
25
26
  render( <Dialog onClose={ onClose } /> );
26
27
 
@@ -32,7 +33,7 @@ describe( 'useDialog', () => {
32
33
 
33
34
  it( 'should not call onClose when Escape is pressed if defaultPrevented', async () => {
34
35
  const user = userEvent.setup();
35
- const onClose = jest.fn();
36
+ const onClose = vi.fn();
36
37
 
37
38
  function DialogWithChild() {
38
39
  const [ ref, props ] = useDialog( {
@@ -69,8 +70,8 @@ describe( 'useDialog', () => {
69
70
 
70
71
  it( 'should stop Escape event from propagating to parent elements', async () => {
71
72
  const user = userEvent.setup();
72
- const onClose = jest.fn();
73
- const parentKeyDownHandler = jest.fn();
73
+ const onClose = vi.fn();
74
+ const parentKeyDownHandler = vi.fn();
74
75
 
75
76
  render(
76
77
  // eslint-disable-next-line jsx-a11y/no-static-element-interactions
@@ -88,7 +89,7 @@ describe( 'useDialog', () => {
88
89
 
89
90
  it( 'should let Escape propagate when there is no onClose handler', async () => {
90
91
  const user = userEvent.setup();
91
- const parentKeyDownHandler = jest.fn();
92
+ const parentKeyDownHandler = vi.fn();
92
93
 
93
94
  render(
94
95
  // eslint-disable-next-line jsx-a11y/no-static-element-interactions
@@ -105,8 +106,8 @@ describe( 'useDialog', () => {
105
106
 
106
107
  it( 'should not close when a portaled descendant handles Escape and stops propagation', async () => {
107
108
  const user = userEvent.setup();
108
- const onClose = jest.fn();
109
- const descendantHandled = jest.fn();
109
+ const onClose = vi.fn();
110
+ const descendantHandled = vi.fn();
110
111
 
111
112
  const portalTarget = document.createElement( 'div' );
112
113
  document.body.appendChild( portalTarget );
@@ -153,8 +154,8 @@ describe( 'useDialog', () => {
153
154
 
154
155
  it( 'should close only the innermost dialog when nested', async () => {
155
156
  const user = userEvent.setup();
156
- const outerOnClose = jest.fn();
157
- const innerOnClose = jest.fn();
157
+ const outerOnClose = vi.fn();
158
+ const innerOnClose = vi.fn();
158
159
 
159
160
  function NestedDialogs() {
160
161
  const [ outerRef, outerProps ] = useDialog( {
@@ -196,8 +197,8 @@ describe( 'useDialog', () => {
196
197
 
197
198
  it( 'should call the consumer-provided onKeyDown alongside close-on-Escape', async () => {
198
199
  const user = userEvent.setup();
199
- const onClose = jest.fn();
200
- const consumerOnKeyDown = jest.fn();
200
+ const onClose = vi.fn();
201
+ const consumerOnKeyDown = vi.fn();
201
202
 
202
203
  function DialogWithConsumerOnKeyDown() {
203
204
  const [ ref, props ] = useDialog( {
@@ -227,7 +228,7 @@ describe( 'useDialog', () => {
227
228
 
228
229
  it( 'should let the consumer-provided onKeyDown opt out of close-on-Escape via preventDefault', async () => {
229
230
  const user = userEvent.setup();
230
- const onClose = jest.fn();
231
+ const onClose = vi.fn();
231
232
 
232
233
  function DialogOptingOut() {
233
234
  const [ ref, props ] = useDialog( {
@@ -255,7 +256,7 @@ describe( 'useDialog', () => {
255
256
 
256
257
  it( 'should not call onClose for non-Escape keys', async () => {
257
258
  const user = userEvent.setup();
258
- const onClose = jest.fn();
259
+ const onClose = vi.fn();
259
260
 
260
261
  render( <Dialog onClose={ onClose } /> );
261
262
 
@@ -1,3 +1,4 @@
1
+ import { describe, expect, it } from 'vitest';
1
2
  import { render, screen, waitFor } from '@testing-library/react';
2
3
  import { forwardRef } from '@wordpress/element';
3
4
  import useDisabled from '../';
@@ -1,3 +1,4 @@
1
+ import { describe, expect, it } from 'vitest';
1
2
  import { render, screen } from '@testing-library/react';
2
3
  import { useState } from '@wordpress/element';
3
4
  import useDropZone from '../';
@@ -1,3 +1,4 @@
1
+ import { describe, expect, it, vi } from 'vitest';
1
2
  import { render, screen } from '@testing-library/react';
2
3
  import userEvent from '@testing-library/user-event';
3
4
  import useFocusOutside from '../';
@@ -19,7 +20,7 @@ const FocusOutsideComponent = ( { onFocusOutside: callback } ) => (
19
20
 
20
21
  describe( 'useFocusOutside', () => {
21
22
  it( 'should not call handler if focus shifts to element within component', async () => {
22
- const mockOnFocusOutside = jest.fn();
23
+ const mockOnFocusOutside = vi.fn();
23
24
  const user = userEvent.setup();
24
25
 
25
26
  render(
@@ -40,7 +41,7 @@ describe( 'useFocusOutside', () => {
40
41
  } );
41
42
 
42
43
  it( 'should not call handler if focus transitions via click to button', async () => {
43
- const mockOnFocusOutside = jest.fn();
44
+ const mockOnFocusOutside = vi.fn();
44
45
  const user = userEvent.setup();
45
46
 
46
47
  render(
@@ -57,7 +58,7 @@ describe( 'useFocusOutside', () => {
57
58
  } );
58
59
 
59
60
  it( 'should call handler if focus shifts to element outside component', async () => {
60
- const mockOnFocusOutside = jest.fn();
61
+ const mockOnFocusOutside = vi.fn();
61
62
  const user = userEvent.setup();
62
63
 
63
64
  render(
@@ -82,10 +83,10 @@ describe( 'useFocusOutside', () => {
82
83
  it( 'should not call handler if focus shifts outside the component when the document does not have focus', async () => {
83
84
  // Force document.hasFocus() to return false to simulate the window/document losing focus
84
85
  // See https://developer.mozilla.org/en-US/docs/Web/API/Document/hasFocus.
85
- const mockedDocumentHasFocus = jest
86
+ const mockedDocumentHasFocus = vi
86
87
  .spyOn( document, 'hasFocus' )
87
88
  .mockImplementation( () => false );
88
- const mockOnFocusOutside = jest.fn();
89
+ const mockOnFocusOutside = vi.fn();
89
90
  const user = userEvent.setup();
90
91
 
91
92
  render(
@@ -114,9 +115,7 @@ describe( 'useFocusOutside', () => {
114
115
  const promise = new Promise( ( resolve ) => {
115
116
  resolvePromise = resolve;
116
117
  } );
117
- const mockOnFocusOutside = jest
118
- .fn()
119
- .mockImplementation( resolvePromise );
118
+ const mockOnFocusOutside = vi.fn().mockImplementation( resolvePromise );
120
119
  const user = userEvent.setup();
121
120
 
122
121
  const { unmount } = render(
@@ -1,3 +1,4 @@
1
+ import { describe, expect, it } from 'vitest';
1
2
  import { render } from '@testing-library/react';
2
3
  import useInstanceId from '../';
3
4
 
@@ -1,3 +1,4 @@
1
+ import { afterEach, describe, expect, it } from 'vitest';
1
2
  import { render, screen, fireEvent } from '@testing-library/react';
2
3
  import { useCallback, useState } from '@wordpress/element';
3
4
  import useMergeRefs from '../';
@@ -1,3 +1,4 @@
1
+ import { describe, expect, test, vi } from 'vitest';
1
2
  import { render, screen, act } from '@testing-library/react';
2
3
  import { observableMap } from '../../../utils/observable-map';
3
4
  import useObservableValue from '..';
@@ -7,7 +8,7 @@ describe( 'useObservableValue', () => {
7
8
  const map = observableMap();
8
9
  map.set( 'a', 1 );
9
10
 
10
- const MapUI = jest.fn( () => {
11
+ const MapUI = vi.fn( () => {
11
12
  const value = useObservableValue( map, 'a' );
12
13
  return <div>value is { value }</div>;
13
14
  } );
@@ -0,0 +1,37 @@
1
+ import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
2
+ import { act, renderHook } from '@testing-library/react';
3
+ import useResizeObserver from '..';
4
+
5
+ const disconnect = vi.fn();
6
+
7
+ class MockResizeObserver {
8
+ observe() {}
9
+ unobserve() {}
10
+ disconnect = disconnect;
11
+ }
12
+
13
+ const originalResizeObserver = globalThis.ResizeObserver;
14
+
15
+ beforeAll( () => {
16
+ globalThis.ResizeObserver =
17
+ MockResizeObserver as unknown as typeof ResizeObserver;
18
+ } );
19
+
20
+ afterAll( () => {
21
+ globalThis.ResizeObserver = originalResizeObserver;
22
+ } );
23
+
24
+ describe( 'useResizeObserver', () => {
25
+ it( 'disconnects the observer on unmount', () => {
26
+ const { result, unmount } = renderHook( () =>
27
+ useResizeObserver( vi.fn() )
28
+ );
29
+
30
+ act( () => result.current( document.createElement( 'div' ) ) );
31
+ expect( disconnect ).not.toHaveBeenCalled();
32
+
33
+ unmount();
34
+
35
+ expect( disconnect ).toHaveBeenCalledOnce();
36
+ } );
37
+ } );
@@ -1,4 +1,4 @@
1
- import { useRef } from '@wordpress/element';
1
+ import { useEffect, useRef } from '@wordpress/element';
2
2
  import useEvent from '../use-event';
3
3
 
4
4
  // This is the current implementation of `useResizeObserver`.
@@ -14,6 +14,18 @@ export function useResizeObserver< T extends HTMLElement >(
14
14
 
15
15
  const observedElementRef = useRef< T | null >( null );
16
16
  const resizeObserverRef = useRef< ResizeObserver >( undefined );
17
+ useEffect( () => {
18
+ if ( observedElementRef.current ) {
19
+ resizeObserverRef.current?.observe(
20
+ observedElementRef.current,
21
+ resizeObserverOptions
22
+ );
23
+ }
24
+ return () => resizeObserverRef.current?.disconnect();
25
+ // This effect owns the observer lifecycle. The returned ref handles
26
+ // element and option changes without recreating the observer.
27
+ // eslint-disable-next-line react-hooks/exhaustive-deps
28
+ }, [] );
17
29
  return useEvent( ( element?: T | null ) => {
18
30
  if ( element === observedElementRef.current ) {
19
31
  return;
@@ -1,3 +1,4 @@
1
+ import { describe, expect, it } from 'vitest';
1
2
  import { screen, render, fireEvent } from '@testing-library/react';
2
3
  import useStateWithHistory from '../';
3
4
 
@@ -1,9 +1,8 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
1
2
  import { render } from '@testing-library/react';
2
3
  import useViewportMatch from '../';
3
- jest.mock( '../../use-media-query', () => {
4
- return jest.fn();
5
- } );
6
4
  import useMediaQueryMock from '../../use-media-query';
5
+ vi.mock( import( '../../use-media-query' ), () => ( { default: vi.fn() } ) );
7
6
 
8
7
  describe( 'useViewportMatch', () => {
9
8
  afterEach( () => {
@@ -1,3 +1,4 @@
1
+ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
1
2
  import subscribeDelegatedListener from '..';
2
3
 
3
4
  describe( 'subscribeDelegatedListener', () => {
@@ -27,14 +28,14 @@ describe( 'subscribeDelegatedListener', () => {
27
28
  }
28
29
 
29
30
  test( 'invokes element subscriber when event fires on that element', () => {
30
- const cb = jest.fn();
31
+ const cb = vi.fn();
31
32
  subscribeDelegatedListener( target, 'click', cb );
32
33
  fire( target );
33
34
  expect( cb ).toHaveBeenCalledTimes( 1 );
34
35
  } );
35
36
 
36
37
  test( 'invokes element subscriber when event fires on a descendant', () => {
37
- const cb = jest.fn();
38
+ const cb = vi.fn();
38
39
  const child = document.createElement( 'b' );
39
40
  target.appendChild( child );
40
41
  subscribeDelegatedListener( target, 'click', cb );
@@ -43,7 +44,7 @@ describe( 'subscribeDelegatedListener', () => {
43
44
  } );
44
45
 
45
46
  test( 'does not invoke element subscriber when event fires outside its subtree', () => {
46
- const cb = jest.fn();
47
+ const cb = vi.fn();
47
48
  const sibling = document.createElement( 'div' );
48
49
  document.body.appendChild( sibling );
49
50
  subscribeDelegatedListener( target, 'click', cb );
@@ -83,8 +84,8 @@ describe( 'subscribeDelegatedListener', () => {
83
84
  } );
84
85
 
85
86
  test( 'capture and bubble registries are independent', () => {
86
- const captureCb = jest.fn();
87
- const bubbleCb = jest.fn();
87
+ const captureCb = vi.fn();
88
+ const bubbleCb = vi.fn();
88
89
  subscribeDelegatedListener( target, 'click', captureCb, true );
89
90
  subscribeDelegatedListener( target, 'click', bubbleCb, false );
90
91
  fire( target );
@@ -93,15 +94,15 @@ describe( 'subscribeDelegatedListener', () => {
93
94
  } );
94
95
 
95
96
  test( 'document subscriber always fires for events in the document', () => {
96
- const cb = jest.fn();
97
+ const cb = vi.fn();
97
98
  subscribeDelegatedListener( document, 'click', cb );
98
99
  fire( target );
99
100
  expect( cb ).toHaveBeenCalledTimes( 1 );
100
101
  } );
101
102
 
102
103
  test( 'window subscriber fans out independently of element subscribers', () => {
103
- const winCb = jest.fn();
104
- const elCb = jest.fn();
104
+ const winCb = vi.fn();
105
+ const elCb = vi.fn();
105
106
  subscribeDelegatedListener( window, 'resize', winCb );
106
107
  subscribeDelegatedListener( target, 'resize', elCb );
107
108
  window.dispatchEvent( new Event( 'resize' ) );
@@ -111,8 +112,8 @@ describe( 'subscribeDelegatedListener', () => {
111
112
  } );
112
113
 
113
114
  test( 'multiple callbacks for the same element all fire', () => {
114
- const a = jest.fn();
115
- const b = jest.fn();
115
+ const a = vi.fn();
116
+ const b = vi.fn();
116
117
  subscribeDelegatedListener( target, 'click', a );
117
118
  subscribeDelegatedListener( target, 'click', b );
118
119
  fire( target );
@@ -121,7 +122,7 @@ describe( 'subscribeDelegatedListener', () => {
121
122
  } );
122
123
 
123
124
  test( 'unsubscribe stops further dispatch', () => {
124
- const cb = jest.fn();
125
+ const cb = vi.fn();
125
126
  const unsub = subscribeDelegatedListener( target, 'click', cb );
126
127
  fire( target );
127
128
  expect( cb ).toHaveBeenCalledTimes( 1 );
@@ -131,12 +132,12 @@ describe( 'subscribeDelegatedListener', () => {
131
132
  } );
132
133
 
133
134
  test( 'attaches one native listener per (root, eventType, phase) regardless of subscriber count', () => {
134
- const spy = jest.spyOn( document, 'addEventListener' );
135
+ const spy = vi.spyOn( document, 'addEventListener' );
135
136
  // First subscribe attaches the native listener; subsequent ones
136
137
  // for the same key share it.
137
- subscribeDelegatedListener( target, 'mousemove', jest.fn() );
138
- subscribeDelegatedListener( root, 'mousemove', jest.fn() );
139
- subscribeDelegatedListener( document, 'mousemove', jest.fn() );
138
+ subscribeDelegatedListener( target, 'mousemove', vi.fn() );
139
+ subscribeDelegatedListener( root, 'mousemove', vi.fn() );
140
+ subscribeDelegatedListener( document, 'mousemove', vi.fn() );
140
141
  const nativeMousemoves = spy.mock.calls.filter(
141
142
  ( [ type, , opts ] ) => type === 'mousemove' && ! opts // bubble-phase only
142
143
  ).length;
@@ -154,7 +155,7 @@ describe( 'subscribeDelegatedListener', () => {
154
155
  const iframeTarget = iframeDoc.createElement( 'span' );
155
156
  iframeDoc.body.appendChild( iframeTarget );
156
157
 
157
- const cb = jest.fn();
158
+ const cb = vi.fn();
158
159
  expect( () =>
159
160
  subscribeDelegatedListener( iframeTarget, 'click', cb )
160
161
  ).not.toThrow();