diff --git a/package.json b/package.json index 4a3b34cd20..dedec73edb 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "@matrix-org/react-sdk-module-api": "^0.0.3", "@sentry/browser": "^6.11.0", "@sentry/tracing": "^6.11.0", + "@testing-library/react-hooks": "^8.0.1", "@types/geojson": "^7946.0.8", "@types/ua-parser-js": "^0.7.36", "await-lock": "^2.1.0", diff --git a/test/hooks/useDebouncedCallback-test.tsx b/test/hooks/useDebouncedCallback-test.tsx index a9b8e04e95..d0428358f0 100644 --- a/test/hooks/useDebouncedCallback-test.tsx +++ b/test/hooks/useDebouncedCallback-test.tsx @@ -14,70 +14,85 @@ See the License for the specific language governing permissions and limitations under the License. */ -// eslint-disable-next-line deprecate/import -import { mount } from "enzyme"; -import { sleep } from "matrix-js-sdk/src/utils"; -import React from "react"; -import { act } from "react-dom/test-utils"; +import { renderHook } from "@testing-library/react-hooks"; import { useDebouncedCallback } from "../../src/hooks/spotlight/useDebouncedCallback"; -function DebouncedCallbackComponent({ enabled, params, callback }) { - useDebouncedCallback(enabled, callback, params); - return
- { JSON.stringify(params) } -
; -} - describe("useDebouncedCallback", () => { + beforeAll(() => jest.useFakeTimers()); + afterAll(() => jest.useRealTimers()); + + function render(enabled: boolean, callback: (...params: any) => void, params: any) { + return renderHook( + ({ enabled, callback, params }) => useDebouncedCallback(enabled, callback, params), + { initialProps: { + enabled, + callback, + params, + } }); + } + it("should be able to handle empty parameters", async () => { + // When const params = []; const callback = jest.fn(); + render(true, callback, params); + jest.advanceTimersByTime(1); - const wrapper = mount(); - await act(async () => { - await sleep(1); - wrapper.setProps({ enabled: true, params, callback }); - return act(() => sleep(500)); - }); + // Then + expect(callback).toHaveBeenCalledTimes(0); - expect(wrapper.text()).toContain(JSON.stringify(params)); + // When + jest.advanceTimersByTime(500); + + // Then expect(callback).toHaveBeenCalledTimes(1); }); it("should call the callback with the parameters", async () => { + // When const params = ["USER NAME"]; const callback = jest.fn(); + render(true, callback, params); + jest.advanceTimersByTime(500); - const wrapper = mount(); - await act(async () => { - await sleep(1); - wrapper.setProps({ enabled: true, params, callback }); - return act(() => sleep(500)); - }); + // Then + expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenCalledWith(...params); + }); - expect(wrapper.text()).toContain(JSON.stringify(params)); + it("should call the callback with the parameters when parameters change during the timeout", async () => { + // When + const params = ["USER NAME"]; + const callback = jest.fn(); + const { rerender } = render(true, callback, []); + + jest.advanceTimersByTime(1); + rerender({ enabled: true, callback, params }); + jest.advanceTimersByTime(500); + + // Then expect(callback).toHaveBeenCalledTimes(1); expect(callback).toHaveBeenCalledWith(...params); }); it("should handle multiple parameters", async () => { + // When const params = [4, 8, 15, 16, 23, 42]; const callback = jest.fn(); + const { rerender } = render(true, callback, []); - const wrapper = mount(); - await act(async () => { - await sleep(1); - wrapper.setProps({ enabled: true, params, callback }); - return act(() => sleep(500)); - }); + jest.advanceTimersByTime(1); + rerender({ enabled: true, callback, params }); + jest.advanceTimersByTime(500); - expect(wrapper.text()).toContain(JSON.stringify(params)); + // Then expect(callback).toHaveBeenCalledTimes(1); expect(callback).toHaveBeenCalledWith(...params); }); it("should debounce quick changes", async () => { + // When const queries = [ "U", "US", @@ -95,23 +110,24 @@ describe("useDebouncedCallback", () => { ]; const callback = jest.fn(); - const wrapper = mount(); - await act(async () => { - await sleep(1); - for (const query of queries) { - wrapper.setProps({ enabled: true, params: [query], callback }); - await sleep(50); - } - return act(() => sleep(500)); - }); + const { rerender } = render(true, callback, []); + jest.advanceTimersByTime(1); + for (const query of queries) { + rerender({ enabled: true, callback, params: [query] }); + jest.advanceTimersByTime(50); + } + + jest.advanceTimersByTime(500); + + // Then const query = queries[queries.length - 1]; - expect(wrapper.text()).toContain(JSON.stringify(query)); expect(callback).toHaveBeenCalledTimes(1); expect(callback).toHaveBeenCalledWith(query); }); it("should not debounce slow changes", async () => { + // When const queries = [ "U", "US", @@ -129,23 +145,23 @@ describe("useDebouncedCallback", () => { ]; const callback = jest.fn(); - const wrapper = mount(); - await act(async () => { - await sleep(1); - for (const query of queries) { - wrapper.setProps({ enabled: true, params: [query], callback }); - await sleep(200); - } - return act(() => sleep(500)); - }); + const { rerender } = render(true, callback, []); + jest.advanceTimersByTime(1); + for (const query of queries) { + rerender({ enabled: true, callback, params: [query] }); + jest.advanceTimersByTime(200); + } + jest.advanceTimersByTime(500); + + // Then const query = queries[queries.length - 1]; - expect(wrapper.text()).toContain(JSON.stringify(query)); expect(callback).toHaveBeenCalledTimes(queries.length); expect(callback).toHaveBeenCalledWith(query); }); it("should not call the callback if it’s disabled", async () => { + // When const queries = [ "U", "US", @@ -163,18 +179,16 @@ describe("useDebouncedCallback", () => { ]; const callback = jest.fn(); - const wrapper = mount(); - await act(async () => { - await sleep(1); - for (const query of queries) { - wrapper.setProps({ enabled: false, params: [query], callback }); - await sleep(200); - } - return act(() => sleep(500)); - }); + const { rerender } = render(false, callback, []); + jest.advanceTimersByTime(1); + for (const query of queries) { + rerender({ enabled: false, callback, params: [query] }); + jest.advanceTimersByTime(200); + } - const query = queries[queries.length - 1]; - expect(wrapper.text()).toContain(JSON.stringify(query)); + jest.advanceTimersByTime(500); + + // Then expect(callback).toHaveBeenCalledTimes(0); }); }); diff --git a/yarn.lock b/yarn.lock index 647b29a0b6..f538e16acd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2205,6 +2205,14 @@ lodash "^4.17.15" redent "^3.0.0" +"@testing-library/react-hooks@^8.0.1": + version "8.0.1" + resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-8.0.1.tgz#0924bbd5b55e0c0c0502d1754657ada66947ca12" + integrity sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g== + dependencies: + "@babel/runtime" "^7.12.5" + react-error-boundary "^3.1.0" + "@testing-library/react@^12.1.5": version "12.1.5" resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-12.1.5.tgz#bb248f72f02a5ac9d949dea07279095fa577963b" @@ -8185,6 +8193,13 @@ react-dom@17.0.2: object-assign "^4.1.1" scheduler "^0.20.2" +react-error-boundary@^3.1.0: + version "3.1.4" + resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.4.tgz#255db92b23197108757a888b01e5b729919abde0" + integrity sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA== + dependencies: + "@babel/runtime" "^7.12.5" + react-focus-lock@^2.5.1: version "2.9.1" resolved "https://registry.yarnpkg.com/react-focus-lock/-/react-focus-lock-2.9.1.tgz#094cfc19b4f334122c73bb0bff65d77a0c92dd16"