@wordpress/compose 8.7.2-next.v.202608281122.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 (41) hide show
  1. package/CHANGELOG.md +5 -1
  2. package/build/higher-order/pipe.cjs.map +1 -1
  3. package/build/hooks/use-resize-observer/use-resize-observer.cjs +9 -0
  4. package/build/hooks/use-resize-observer/use-resize-observer.cjs.map +2 -2
  5. package/build-module/higher-order/pipe.mjs.map +1 -1
  6. package/build-module/hooks/use-resize-observer/use-resize-observer.mjs +10 -1
  7. package/build-module/hooks/use-resize-observer/use-resize-observer.mjs.map +2 -2
  8. package/build-types/higher-order/compose.d.ts +1 -1
  9. package/build-types/higher-order/compose.d.ts.map +1 -1
  10. package/build-types/higher-order/pipe.d.ts +2 -2
  11. package/build-types/higher-order/pipe.d.ts.map +1 -1
  12. package/build-types/hooks/use-resize-observer/use-resize-observer.d.ts.map +1 -1
  13. package/package.json +13 -12
  14. package/src/higher-order/pipe.ts +1 -1
  15. package/src/higher-order/pure/test/{index.jsx → index.jsdom.test.jsx} +1 -0
  16. package/src/higher-order/test/compose.ts +12 -11
  17. package/src/higher-order/test/pipe.ts +12 -11
  18. package/src/higher-order/with-global-events/test/{index.jsx → index.jsdom.test.jsx} +18 -15
  19. package/src/higher-order/with-global-events/test/{listener.js → listener.jsdom.test.js} +13 -4
  20. package/src/higher-order/with-instance-id/test/{index.jsx → index.jsdom.test.jsx} +1 -0
  21. package/src/higher-order/with-state/test/{index.jsx → index.jsdom.test.jsx} +1 -0
  22. package/src/hooks/use-copy-on-click/test/{index.tsx → index.jsdom.test.tsx} +18 -17
  23. package/src/hooks/use-copy-to-clipboard/test/{index.tsx → index.jsdom.test.tsx} +12 -11
  24. package/src/hooks/use-dialog/test/{index.tsx → index.jsdom.test.tsx} +14 -13
  25. package/src/hooks/use-disabled/test/{index.jsx → index.jsdom.test.jsx} +1 -0
  26. package/src/hooks/use-drop-zone/test/{index.jsx → index.jsdom.test.jsx} +1 -0
  27. package/src/hooks/use-focus-outside/test/{index.jsx → index.jsdom.test.jsx} +7 -8
  28. package/src/hooks/use-instance-id/test/{index.jsx → index.jsdom.test.jsx} +1 -0
  29. package/src/hooks/use-media-query/test/ssr.jsx +1 -3
  30. package/src/hooks/use-merge-refs/test/{index.jsx → index.jsdom.test.jsx} +1 -0
  31. package/src/hooks/use-observable-value/test/{index.jsx → index.jsdom.test.jsx} +2 -1
  32. package/src/hooks/use-resize-observer/test/index.jsdom.test.ts +37 -0
  33. package/src/hooks/use-resize-observer/use-resize-observer.ts +13 -1
  34. package/src/hooks/use-state-with-history/test/{index.jsx → index.jsdom.test.jsx} +1 -0
  35. package/src/hooks/use-viewport-match/test/{index.jsx → index.jsdom.test.jsx} +2 -3
  36. package/src/utils/create-higher-order-component/test/index.jsx +1 -0
  37. package/src/utils/debounce/test/index.ts +4 -3
  38. package/src/utils/observable-map/test/index.js +3 -2
  39. package/src/utils/subscribe-delegated-listener/test/{index.js → index.jsdom.test.js} +17 -16
  40. package/src/utils/throttle/test/index.ts +18 -17
  41. /package/src/hooks/use-media-query/test/{index.jsx → index.jsdom.test.jsx} +0 -0
package/CHANGELOG.md CHANGED
@@ -2,7 +2,11 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
- ## 8.7.1-next.0 (2026-08-28)
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
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/higher-order/pipe.ts"],
4
- "sourcesContent": ["/**\n * Parts of this source were derived and modified from lodash,\n * released under the MIT license.\n *\n * https://github.com/lodash/lodash\n *\n * Copyright JS Foundation and other contributors <https://js.foundation/>\n *\n * Based on Underscore.js, copyright Jeremy Ashkenas,\n * DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>\n *\n * This software consists of voluntary contributions made by many\n * individuals. For exact contribution history, see the revision history\n * available at https://github.com/lodash/lodash\n *\n * The following license applies to all parts of this software except as\n * documented below:\n *\n * ====\n *\n * Permission is hereby granted, free of charge, to any person obtaining\n * a copy of this software and associated documentation files (the\n * \"Software\"), to deal in the Software without restriction, including\n * without limitation the rights to use, copy, modify, merge, publish,\n * distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to\n * the following conditions:\n *\n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n/**\n * Creates a pipe function.\n *\n * Allows to choose whether to perform left-to-right or right-to-left composition.\n *\n * @see https://lodash.com/docs/4#flow\n *\n * @param {boolean} reverse True if right-to-left, false for left-to-right composition.\n */\nconst basePipe =\n\t( reverse: boolean = false ) =>\n\t( ...funcs: Function[] ) =>\n\t( ...args: unknown[] ) => {\n\t\tconst functions = funcs.flat();\n\t\tif ( reverse ) {\n\t\t\tfunctions.reverse();\n\t\t}\n\t\treturn functions.reduce(\n\t\t\t( prev, func ) => [ func( ...prev ) ],\n\t\t\targs\n\t\t)[ 0 ];\n\t};\n\n/**\n * Composes multiple higher-order components into a single higher-order component. Performs left-to-right function\n * composition, where each successive invocation is supplied the return value of the previous.\n *\n * This is inspired by `lodash`'s `flow` function.\n *\n * @see https://lodash.com/docs/4#flow\n */\nconst pipe = basePipe();\n\nexport { basePipe };\n\nexport default pipe;\n"],
4
+ "sourcesContent": ["/**\n * Parts of this source were derived and modified from lodash,\n * released under the MIT license.\n *\n * https://github.com/lodash/lodash\n *\n * Copyright JS Foundation and other contributors <https://js.foundation/>\n *\n * Based on Underscore.js, copyright Jeremy Ashkenas,\n * DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>\n *\n * This software consists of voluntary contributions made by many\n * individuals. For exact contribution history, see the revision history\n * available at https://github.com/lodash/lodash\n *\n * The following license applies to all parts of this software except as\n * documented below:\n *\n * ====\n *\n * Permission is hereby granted, free of charge, to any person obtaining\n * a copy of this software and associated documentation files (the\n * \"Software\"), to deal in the Software without restriction, including\n * without limitation the rights to use, copy, modify, merge, publish,\n * distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to\n * the following conditions:\n *\n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n/**\n * Creates a pipe function.\n *\n * Allows to choose whether to perform left-to-right or right-to-left composition.\n *\n * @see https://lodash.com/docs/4#flow\n *\n * @param {boolean} reverse True if right-to-left, false for left-to-right composition.\n */\nconst basePipe =\n\t( reverse: boolean = false ) =>\n\t( ...funcs: ( Function | Function[] )[] ) =>\n\t( ...args: unknown[] ) => {\n\t\tconst functions = funcs.flat();\n\t\tif ( reverse ) {\n\t\t\tfunctions.reverse();\n\t\t}\n\t\treturn functions.reduce(\n\t\t\t( prev, func ) => [ func( ...prev ) ],\n\t\t\targs\n\t\t)[ 0 ];\n\t};\n\n/**\n * Composes multiple higher-order components into a single higher-order component. Performs left-to-right function\n * composition, where each successive invocation is supplied the return value of the previous.\n *\n * This is inspired by `lodash`'s `flow` function.\n *\n * @see https://lodash.com/docs/4#flow\n */\nconst pipe = basePipe();\n\nexport { basePipe };\n\nexport default pipe;\n"],
5
5
  "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiDA,IAAM,WACL,CAAE,UAAmB,UACrB,IAAK,UACL,IAAK,SAAqB;AACzB,QAAM,YAAY,MAAM,KAAK;AAC7B,MAAK,SAAU;AACd,cAAU,QAAQ;AAAA,EACnB;AACA,SAAO,UAAU;AAAA,IAChB,CAAE,MAAM,SAAU,CAAE,KAAM,GAAG,IAAK,CAAE;AAAA,IACpC;AAAA,EACD,EAAG,CAAE;AACN;AAUD,IAAM,OAAO,SAAS;AAItB,IAAO,eAAQ;",
6
6
  "names": []
7
7
  }
@@ -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,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/higher-order/pipe.ts"],
4
- "sourcesContent": ["/**\n * Parts of this source were derived and modified from lodash,\n * released under the MIT license.\n *\n * https://github.com/lodash/lodash\n *\n * Copyright JS Foundation and other contributors <https://js.foundation/>\n *\n * Based on Underscore.js, copyright Jeremy Ashkenas,\n * DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>\n *\n * This software consists of voluntary contributions made by many\n * individuals. For exact contribution history, see the revision history\n * available at https://github.com/lodash/lodash\n *\n * The following license applies to all parts of this software except as\n * documented below:\n *\n * ====\n *\n * Permission is hereby granted, free of charge, to any person obtaining\n * a copy of this software and associated documentation files (the\n * \"Software\"), to deal in the Software without restriction, including\n * without limitation the rights to use, copy, modify, merge, publish,\n * distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to\n * the following conditions:\n *\n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n/**\n * Creates a pipe function.\n *\n * Allows to choose whether to perform left-to-right or right-to-left composition.\n *\n * @see https://lodash.com/docs/4#flow\n *\n * @param {boolean} reverse True if right-to-left, false for left-to-right composition.\n */\nconst basePipe =\n\t( reverse: boolean = false ) =>\n\t( ...funcs: Function[] ) =>\n\t( ...args: unknown[] ) => {\n\t\tconst functions = funcs.flat();\n\t\tif ( reverse ) {\n\t\t\tfunctions.reverse();\n\t\t}\n\t\treturn functions.reduce(\n\t\t\t( prev, func ) => [ func( ...prev ) ],\n\t\t\targs\n\t\t)[ 0 ];\n\t};\n\n/**\n * Composes multiple higher-order components into a single higher-order component. Performs left-to-right function\n * composition, where each successive invocation is supplied the return value of the previous.\n *\n * This is inspired by `lodash`'s `flow` function.\n *\n * @see https://lodash.com/docs/4#flow\n */\nconst pipe = basePipe();\n\nexport { basePipe };\n\nexport default pipe;\n"],
4
+ "sourcesContent": ["/**\n * Parts of this source were derived and modified from lodash,\n * released under the MIT license.\n *\n * https://github.com/lodash/lodash\n *\n * Copyright JS Foundation and other contributors <https://js.foundation/>\n *\n * Based on Underscore.js, copyright Jeremy Ashkenas,\n * DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>\n *\n * This software consists of voluntary contributions made by many\n * individuals. For exact contribution history, see the revision history\n * available at https://github.com/lodash/lodash\n *\n * The following license applies to all parts of this software except as\n * documented below:\n *\n * ====\n *\n * Permission is hereby granted, free of charge, to any person obtaining\n * a copy of this software and associated documentation files (the\n * \"Software\"), to deal in the Software without restriction, including\n * without limitation the rights to use, copy, modify, merge, publish,\n * distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to\n * the following conditions:\n *\n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n/**\n * Creates a pipe function.\n *\n * Allows to choose whether to perform left-to-right or right-to-left composition.\n *\n * @see https://lodash.com/docs/4#flow\n *\n * @param {boolean} reverse True if right-to-left, false for left-to-right composition.\n */\nconst basePipe =\n\t( reverse: boolean = false ) =>\n\t( ...funcs: ( Function | Function[] )[] ) =>\n\t( ...args: unknown[] ) => {\n\t\tconst functions = funcs.flat();\n\t\tif ( reverse ) {\n\t\t\tfunctions.reverse();\n\t\t}\n\t\treturn functions.reduce(\n\t\t\t( prev, func ) => [ func( ...prev ) ],\n\t\t\targs\n\t\t)[ 0 ];\n\t};\n\n/**\n * Composes multiple higher-order components into a single higher-order component. Performs left-to-right function\n * composition, where each successive invocation is supplied the return value of the previous.\n *\n * This is inspired by `lodash`'s `flow` function.\n *\n * @see https://lodash.com/docs/4#flow\n */\nconst pipe = basePipe();\n\nexport { basePipe };\n\nexport default pipe;\n"],
5
5
  "mappings": ";AAiDA,IAAM,WACL,CAAE,UAAmB,UACrB,IAAK,UACL,IAAK,SAAqB;AACzB,QAAM,YAAY,MAAM,KAAK;AAC7B,MAAK,SAAU;AACd,cAAU,QAAQ;AAAA,EACnB;AACA,SAAO,UAAU;AAAA,IAChB,CAAE,MAAM,SAAU,CAAE,KAAM,GAAG,IAAK,CAAE;AAAA,IACpC;AAAA,EACD,EAAG,CAAE;AACN;AAUD,IAAM,OAAO,SAAS;AAItB,IAAO,eAAQ;",
6
6
  "names": []
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
  }
@@ -6,6 +6,6 @@
6
6
  *
7
7
  * @see https://lodash.com/docs/4#flow-right
8
8
  */
9
- declare const compose: (...funcs: Function[]) => (...args: unknown[]) => unknown;
9
+ declare const compose: (...funcs: (Function | Function[])[]) => (...args: unknown[]) => unknown;
10
10
  export default compose;
11
11
  //# sourceMappingURL=compose.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../../src/higher-order/compose.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,QAAA,MAAM,OAAO,2DAAmB,CAAC;eAElB,OAAO"}
1
+ {"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../../src/higher-order/compose.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,QAAA,MAAM,OAAO,0EAAmB,CAAC;eAElB,OAAO"}
@@ -46,7 +46,7 @@
46
46
  *
47
47
  * @param {boolean} reverse True if right-to-left, false for left-to-right composition.
48
48
  */
49
- declare const basePipe: (reverse?: boolean) => (...funcs: Function[]) => (...args: unknown[]) => unknown;
49
+ declare const basePipe: (reverse?: boolean) => (...funcs: (Function | Function[])[]) => (...args: unknown[]) => unknown;
50
50
  /**
51
51
  * Composes multiple higher-order components into a single higher-order component. Performs left-to-right function
52
52
  * composition, where each successive invocation is supplied the return value of the previous.
@@ -55,7 +55,7 @@ declare const basePipe: (reverse?: boolean) => (...funcs: Function[]) => (...arg
55
55
  *
56
56
  * @see https://lodash.com/docs/4#flow
57
57
  */
58
- declare const pipe: (...funcs: Function[]) => (...args: unknown[]) => unknown;
58
+ declare const pipe: (...funcs: (Function | Function[])[]) => (...args: unknown[]) => unknown;
59
59
  export { basePipe };
60
60
  export default pipe;
61
61
  //# sourceMappingURL=pipe.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pipe.d.ts","sourceRoot":"","sources":["../../src/higher-order/pipe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH;;;;;;;;GAQG;AACH,QAAA,MAAM,QAAQ,aACF,OAAO,gBACN,QAAQ,EAAE,eACX,OAAO,EAAE,YASnB,CAAC;AAEH;;;;;;;GAOG;AACH,QAAA,MAAM,IAAI,aApBG,QAAQ,EAAE,eACX,OAAO,EAAE,YAmBE,CAAC;AAExB,OAAO,EAAE,QAAQ,EAAE,CAAC;eAEL,IAAI"}
1
+ {"version":3,"file":"pipe.d.ts","sourceRoot":"","sources":["../../src/higher-order/pipe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH;;;;;;;;GAQG;AACH,QAAA,MAAM,QAAQ,aACF,OAAO,gBACN,CAAE,QAAQ,GAAG,QAAQ,EAAE,CAAE,EAAE,eAC5B,OAAO,EAAE,YASnB,CAAC;AAEH;;;;;;;GAOG;AACH,QAAA,MAAM,IAAI,aApBG,CAAE,QAAQ,GAAG,QAAQ,EAAE,CAAE,EAAE,eAC5B,OAAO,EAAE,YAmBE,CAAC;AAExB,OAAO,EAAE,QAAQ,EAAE,CAAC;eAEL,IAAI"}
@@ -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.202608281122.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,25 +46,26 @@
46
46
  "sideEffects": false,
47
47
  "dependencies": {
48
48
  "@types/mousetrap": "^1.6.8",
49
- "@wordpress/deprecated": "^4.54.1-next.v.202608281122.0",
50
- "@wordpress/dom": "^4.54.1-next.v.202608281122.0",
51
- "@wordpress/element": "^8.6.2-next.v.202608281122.0",
52
- "@wordpress/is-shallow-equal": "^5.54.1-next.v.202608281122.0",
53
- "@wordpress/keycodes": "^4.54.1-next.v.202608281122.0",
54
- "@wordpress/priority-queue": "^3.54.1-next.v.202608281122.0",
55
- "@wordpress/private-apis": "^1.54.1-next.v.202608281122.0",
56
- "@wordpress/undo-manager": "^1.54.1-next.v.202608281122.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
- "react-dom": "^18.3.1"
67
+ "react-dom": "^18.3.1",
68
+ "vitest": "^5.0.0"
68
69
  },
69
70
  "peerDependencies": {
70
71
  "@types/react": "^18 || ^19",
@@ -78,5 +79,5 @@
78
79
  "publishConfig": {
79
80
  "access": "public"
80
81
  },
81
- "gitHead": "de479d415171765f7b573c81aeec5334ff506a12"
82
+ "gitHead": "485f42ae8a1c58ceea18371a507fd4acfa86fbd8"
82
83
  }
@@ -49,7 +49,7 @@
49
49
  */
50
50
  const basePipe =
51
51
  ( reverse: boolean = false ) =>
52
- ( ...funcs: Function[] ) =>
52
+ ( ...funcs: ( Function | Function[] )[] ) =>
53
53
  ( ...args: unknown[] ) => {
54
54
  const functions = funcs.flat();
55
55
  if ( reverse ) {
@@ -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,3 +1,4 @@
1
+ import { describe, expect, it } from 'vitest';
1
2
  import compose from '../compose';
2
3
 
3
4
  describe( 'compose', () => {
@@ -6,27 +7,27 @@ describe( 'compose', () => {
6
7
  } );
7
8
 
8
9
  it( 'executes functions right-to-left when passed as separate arguments', () => {
9
- const a = ( value ) => ( value += 'a' );
10
- const b = ( value ) => ( value += 'b' );
11
- const c = ( value ) => ( value += 'c' );
10
+ const a = ( value: string ) => ( value += 'a' );
11
+ const b = ( value: string ) => ( value += 'b' );
12
+ const c = ( value: string ) => ( value += 'c' );
12
13
 
13
14
  expect( compose( a, b, c )( 'test' ) ).toBe( 'testcba' );
14
15
  } );
15
16
 
16
17
  it( 'executes functions right-to-left when passed as a single array', () => {
17
- const a = ( value ) => ( value += 'a' );
18
- const b = ( value ) => ( value += 'b' );
19
- const c = ( value ) => ( value += 'c' );
18
+ const a = ( value: string ) => ( value += 'a' );
19
+ const b = ( value: string ) => ( value += 'b' );
20
+ const c = ( value: string ) => ( value += 'c' );
20
21
 
21
22
  expect( compose( [ a, b, c ] )( 'test' ) ).toBe( 'testcba' );
22
23
  } );
23
24
 
24
25
  it( 'executes functions right-to-left when passed as a mix of separate arguments and arrays', () => {
25
- const a = ( value ) => ( value += 'a' );
26
- const b = ( value ) => ( value += 'b' );
27
- const c = ( value ) => ( value += 'c' );
28
- const d = ( value ) => ( value += 'd' );
29
- const e = ( value ) => ( value += 'e' );
26
+ const a = ( value: string ) => ( value += 'a' );
27
+ const b = ( value: string ) => ( value += 'b' );
28
+ const c = ( value: string ) => ( value += 'c' );
29
+ const d = ( value: string ) => ( value += 'd' );
30
+ const e = ( value: string ) => ( value += 'e' );
30
31
 
31
32
  expect( compose( [ a, b ], c, [ d ], e )( 'test' ) ).toBe(
32
33
  'testedcba'
@@ -1,3 +1,4 @@
1
+ import { describe, expect, it } from 'vitest';
1
2
  import pipe from '../pipe';
2
3
 
3
4
  describe( 'pipe', () => {
@@ -6,27 +7,27 @@ describe( 'pipe', () => {
6
7
  } );
7
8
 
8
9
  it( 'executes functions left-to-right when passed as separate arguments', () => {
9
- const a = ( value ) => ( value += 'a' );
10
- const b = ( value ) => ( value += 'b' );
11
- const c = ( value ) => ( value += 'c' );
10
+ const a = ( value: string ) => ( value += 'a' );
11
+ const b = ( value: string ) => ( value += 'b' );
12
+ const c = ( value: string ) => ( value += 'c' );
12
13
 
13
14
  expect( pipe( a, b, c )( 'test' ) ).toBe( 'testabc' );
14
15
  } );
15
16
 
16
17
  it( 'executes functions left-to-right when passed as a single array', () => {
17
- const a = ( value ) => ( value += 'a' );
18
- const b = ( value ) => ( value += 'b' );
19
- const c = ( value ) => ( value += 'c' );
18
+ const a = ( value: string ) => ( value += 'a' );
19
+ const b = ( value: string ) => ( value += 'b' );
20
+ const c = ( value: string ) => ( value += 'c' );
20
21
 
21
22
  expect( pipe( [ a, b, c ] )( 'test' ) ).toBe( 'testabc' );
22
23
  } );
23
24
 
24
25
  it( 'executes functions left-to-right when passed as a mix of separate arguments and arrays', () => {
25
- const a = ( value ) => ( value += 'a' );
26
- const b = ( value ) => ( value += 'b' );
27
- const c = ( value ) => ( value += 'c' );
28
- const d = ( value ) => ( value += 'd' );
29
- const e = ( value ) => ( value += 'e' );
26
+ const a = ( value: string ) => ( value += 'a' );
27
+ const b = ( value: string ) => ( value += 'b' );
28
+ const c = ( value: string ) => ( value += 'c' );
29
+ const d = ( value: string ) => ( value += 'd' );
30
+ const e = ( value: string ) => ( value += 'e' );
30
31
 
31
32
  expect( pipe( [ a, b ], c, [ d ], e )( 'test' ) ).toBe( 'testabcde' );
32
33
  } );
@@ -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,6 +1,4 @@
1
- /**
2
- * @jest-environment node
3
- */
1
+ import { describe, expect, it } from 'vitest';
4
2
  import { renderToString } from 'react-dom/server';
5
3
  import useMediaQuery from '../';
6
4
  import useViewportMatch from '../../use-viewport-match';
@@ -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 { describe, expect, it } from 'vitest';
1
2
  import { Component } from '@wordpress/element';
2
3
  import { createHigherOrderComponent } from '../';
3
4
 
@@ -1,6 +1,7 @@
1
+ import { describe, expect, it } from 'vitest';
1
2
  import { debounce } from '../index';
2
3
 
3
- const identity = ( value ) => value;
4
+ const identity = < T >( value: T ) => value;
4
5
 
5
6
  describe( 'debounce', () => {
6
7
  it( 'should debounce a function', () => {
@@ -326,12 +327,12 @@ describe( 'debounce', () => {
326
327
 
327
328
  it( 'should invoke the trailing call with the correct arguments and `this` binding', () => {
328
329
  return new Promise( ( done ) => {
329
- let actual;
330
+ let actual: unknown[] | undefined;
330
331
  let callCount = 0;
331
332
  const object = {};
332
333
 
333
334
  const debounced = debounce(
334
- function ( ...args ) {
335
+ function ( this: object, ...args: unknown[] ) {
335
336
  actual = [ this ];
336
337
  Array.prototype.push.apply( actual, args );
337
338
  return ++callCount !== 2;
@@ -1,11 +1,12 @@
1
+ import { describe, expect, test, vi } from 'vitest';
1
2
  import { observableMap } from '..';
2
3
 
3
4
  describe( 'ObservableMap', () => {
4
5
  test( 'should observe individual values', () => {
5
6
  const map = observableMap();
6
7
 
7
- const listenerA = jest.fn();
8
- const listenerB = jest.fn();
8
+ const listenerA = vi.fn();
9
+ const listenerB = vi.fn();
9
10
 
10
11
  const unsubA = map.subscribe( 'a', listenerA );
11
12
  const unsubB = map.subscribe( 'b', listenerB );
@@ -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();
@@ -1,14 +1,15 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
1
2
  import { throttle } from '../index';
2
3
 
3
- const identity = ( value ) => value;
4
+ const identity = < T >( value: T ) => value;
4
5
 
5
6
  describe( 'throttle', () => {
6
7
  beforeEach( () => {
7
- jest.useFakeTimers();
8
+ vi.useFakeTimers();
8
9
  } );
9
10
 
10
11
  afterEach( () => {
11
- jest.useRealTimers();
12
+ vi.useRealTimers();
12
13
  } );
13
14
 
14
15
  it( 'should throttle a function', () => {
@@ -24,7 +25,7 @@ describe( 'throttle', () => {
24
25
  const lastCount = callCount;
25
26
  expect( callCount ).toBeGreaterThan( 0 );
26
27
 
27
- jest.advanceTimersByTime( 64 );
28
+ vi.advanceTimersByTime( 64 );
28
29
 
29
30
  expect( callCount ).toBeGreaterThan( lastCount );
30
31
  } );
@@ -35,7 +36,7 @@ describe( 'throttle', () => {
35
36
 
36
37
  expect( results ).toStrictEqual( [ 'a', 'a' ] );
37
38
 
38
- jest.advanceTimersByTime( 64 );
39
+ vi.advanceTimersByTime( 64 );
39
40
 
40
41
  results = [ throttled( 'c' ), throttled( 'd' ) ];
41
42
  expect( results[ 0 ] ).not.toBe( 'a' );
@@ -61,7 +62,7 @@ describe( 'throttle', () => {
61
62
  throttled();
62
63
  throttled();
63
64
 
64
- jest.advanceTimersByTime( 64 );
65
+ vi.advanceTimersByTime( 64 );
65
66
 
66
67
  expect( callCount ).toBe( 2 );
67
68
  global.Date.now = globalDateNow;
@@ -76,7 +77,7 @@ describe( 'throttle', () => {
76
77
  throttled();
77
78
  expect( callCount ).toBe( 1 );
78
79
 
79
- jest.advanceTimersByTime( 64 );
80
+ vi.advanceTimersByTime( 64 );
80
81
 
81
82
  expect( callCount ).toBe( 1 );
82
83
  } );
@@ -100,11 +101,11 @@ describe( 'throttle', () => {
100
101
  const start = Date.now();
101
102
  while ( Date.now() - start < limit ) {
102
103
  throttled();
103
- jest.advanceTimersByTime( 1 );
104
+ vi.advanceTimersByTime( 1 );
104
105
  }
105
106
  const actual = callCount;
106
107
 
107
- jest.advanceTimersByTime( 1 );
108
+ vi.advanceTimersByTime( 1 );
108
109
 
109
110
  expect( actual ).toBeGreaterThan( 1 );
110
111
  }
@@ -124,16 +125,16 @@ describe( 'throttle', () => {
124
125
 
125
126
  throttled();
126
127
 
127
- jest.advanceTimersByTime( 192 );
128
+ vi.advanceTimersByTime( 192 );
128
129
 
129
130
  expect( callCount ).toBe( 1 );
130
131
  throttled();
131
132
 
132
- jest.advanceTimersByTime( 64 );
133
+ vi.advanceTimersByTime( 64 );
133
134
 
134
135
  expect( callCount ).toBe( 1 );
135
136
 
136
- jest.advanceTimersByTime( 130 );
137
+ vi.advanceTimersByTime( 130 );
137
138
 
138
139
  expect( callCount ).toBe( 2 );
139
140
  } );
@@ -152,7 +153,7 @@ describe( 'throttle', () => {
152
153
  throttled();
153
154
  expect( callCount ).toBe( 1 );
154
155
 
155
- jest.advanceTimersByTime( 128 );
156
+ vi.advanceTimersByTime( 128 );
156
157
 
157
158
  expect( callCount ).toBe( 2 );
158
159
  } );
@@ -193,7 +194,7 @@ describe( 'throttle', () => {
193
194
  expect( withoutTrailing( 'a' ) ).toBe( 'a' );
194
195
  expect( withoutTrailing( 'b' ) ).toBe( 'a' );
195
196
 
196
- jest.advanceTimersByTime( 256 );
197
+ vi.advanceTimersByTime( 256 );
197
198
 
198
199
  expect( withCount ).toBe( 2 );
199
200
  expect( withoutCount ).toBe( 1 );
@@ -213,12 +214,12 @@ describe( 'throttle', () => {
213
214
  throttled();
214
215
  throttled();
215
216
 
216
- jest.advanceTimersByTime( 96 );
217
+ vi.advanceTimersByTime( 96 );
217
218
 
218
219
  throttled();
219
220
  throttled();
220
221
 
221
- jest.advanceTimersByTime( 96 );
222
+ vi.advanceTimersByTime( 96 );
222
223
 
223
224
  expect( callCount ).toBeGreaterThan( 1 );
224
225
  } );
@@ -245,7 +246,7 @@ describe( 'throttle', () => {
245
246
  expect( results ).toStrictEqual( [ 'a', 'a', 'a' ] );
246
247
  expect( callCount ).toBe( 1 );
247
248
 
248
- jest.advanceTimersByTime( 64 );
249
+ vi.advanceTimersByTime( 64 );
249
250
 
250
251
  expect( callCount ).toBe( 2 );
251
252
  global.Date.now = globalDateNow;