From 0bedd6fbd89788f87414e7109ff3ba22ef6fe268 Mon Sep 17 00:00:00 2001 From: Keith Grant Date: Mon, 15 Jun 2020 14:24:44 -0700 Subject: [PATCH] mock websockets; test useWsJobs --- awx/ui_next/package-lock.json | 15 +++ awx/ui_next/package.json | 2 + .../src/components/JobList/useThrottle.js | 21 +++ .../components/JobList/useThrottle.test.jsx | 0 .../src/components/JobList/useWsJobs.js | 22 +-- .../src/components/JobList/useWsJobs.test.jsx | 126 ++++++++++++++++++ 6 files changed, 165 insertions(+), 21 deletions(-) create mode 100644 awx/ui_next/src/components/JobList/useThrottle.js create mode 100644 awx/ui_next/src/components/JobList/useThrottle.test.jsx create mode 100644 awx/ui_next/src/components/JobList/useWsJobs.test.jsx diff --git a/awx/ui_next/package-lock.json b/awx/ui_next/package-lock.json index 049b48ad8d..12df9b3259 100644 --- a/awx/ui_next/package-lock.json +++ b/awx/ui_next/package-lock.json @@ -8988,6 +8988,12 @@ } } }, + "jest-websocket-mock": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/jest-websocket-mock/-/jest-websocket-mock-2.0.2.tgz", + "integrity": "sha512-SFTUI8O/LDGqROOMnfAzbrrX5gQ8GDhRqkzVrt8Y67evnFKccRPFI3ymS05tKcMONvVfxumat4pX/LRjM/CjVg==", + "dev": true + }, "jest-worker": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", @@ -9893,6 +9899,15 @@ "minimist": "^1.2.5" } }, + "mock-socket": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.0.3.tgz", + "integrity": "sha512-SxIiD2yE/By79p3cNAAXyLQWTvEFNEzcAO7PH+DzRqKSFaplAPFjiQLmw8ofmpCsZf+Rhfn2/xCJagpdGmYdTw==", + "dev": true, + "requires": { + "url-parse": "^1.4.4" + } + }, "moo": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.1.tgz", diff --git a/awx/ui_next/package.json b/awx/ui_next/package.json index e5ca6eb7f2..74f857ce63 100644 --- a/awx/ui_next/package.json +++ b/awx/ui_next/package.json @@ -72,6 +72,8 @@ "eslint-plugin-react": "^7.11.1", "eslint-plugin-react-hooks": "^2.2.0", "http-proxy-middleware": "^1.0.3", + "jest-websocket-mock": "^2.0.2", + "mock-socket": "^9.0.3", "prettier": "^1.18.2" }, "jest": { diff --git a/awx/ui_next/src/components/JobList/useThrottle.js b/awx/ui_next/src/components/JobList/useThrottle.js new file mode 100644 index 0000000000..cfdedfecfc --- /dev/null +++ b/awx/ui_next/src/components/JobList/useThrottle.js @@ -0,0 +1,21 @@ +import { useState, useEffect, useRef } from 'react'; + +export default function useThrottle(value, limit) { + const [throttledValue, setThrottledValue] = useState(value); + const lastRan = useRef(Date.now()); + + useEffect(() => { + const handler = setTimeout(() => { + if (Date.now() - lastRan.current >= limit) { + setThrottledValue(value); + lastRan.current = Date.now(); + } + }, limit - (Date.now() - lastRan.current)); + + return () => { + clearTimeout(handler); + }; + }, [value, limit]); + + return throttledValue; +} diff --git a/awx/ui_next/src/components/JobList/useThrottle.test.jsx b/awx/ui_next/src/components/JobList/useThrottle.test.jsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/awx/ui_next/src/components/JobList/useWsJobs.js b/awx/ui_next/src/components/JobList/useWsJobs.js index 8602bc98c3..ab543d6d8e 100644 --- a/awx/ui_next/src/components/JobList/useWsJobs.js +++ b/awx/ui_next/src/components/JobList/useWsJobs.js @@ -1,10 +1,10 @@ import { useState, useEffect, useRef } from 'react'; +import useThrottle from './useThrottle'; export default function useWsJobs(initialJobs, fetchJobsById, filtersApplied) { const [jobs, setJobs] = useState(initialJobs); const [lastMessage, setLastMessage] = useState(null); const [jobsToFetch, setJobsToFetch] = useState([]); - // const debouncedJobsToFetch = useDebounce(jobsToFetch, 5000); const throttleJobsToFetch = useThrottle(jobsToFetch, 5000); useEffect(() => { @@ -101,23 +101,3 @@ function updateJob(jobs, index, message) { }; return [...jobs.slice(0, index), job, ...jobs.slice(index + 1)]; } - -function useThrottle(value, limit) { - const [throttledValue, setThrottledValue] = useState(value); - const lastRan = useRef(Date.now()); - - useEffect(() => { - const handler = setTimeout(() => { - if (Date.now() - lastRan.current >= limit) { - setThrottledValue(value); - lastRan.current = Date.now(); - } - }, limit - (Date.now() - lastRan.current)); - - return () => { - clearTimeout(handler); - }; - }, [value, limit]); - - return throttledValue; -} diff --git a/awx/ui_next/src/components/JobList/useWsJobs.test.jsx b/awx/ui_next/src/components/JobList/useWsJobs.test.jsx new file mode 100644 index 0000000000..1dead6d5da --- /dev/null +++ b/awx/ui_next/src/components/JobList/useWsJobs.test.jsx @@ -0,0 +1,126 @@ +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { mount } from 'enzyme'; +import WS from 'jest-websocket-mock'; +import useWsJobs from './useWsJobs'; + +/* + Jest mock timers don’t play well with jest-websocket-mock, + so we'll stub out throttling to resolve immediately +*/ +jest.mock('./useThrottle', () => ({ + __esModule: true, + default: jest.fn(val => val), +})); + +function TestInner() { + return
; +} +function Test({ jobs, fetch }) { + const syncedJobs = useWsJobs(jobs, fetch); + return ; +} + +describe('useWsJobs hook', () => { + let debug; + let wrapper; + beforeEach(() => { + debug = global.console.debug; // eslint-disable-line prefer-destructuring + global.console.debug = () => {}; + }); + + afterEach(() => { + global.console.debug = debug; + }); + + test('should return jobs list', () => { + const jobs = [{ id: 1 }]; + wrapper = mount(); + + expect(wrapper.find('TestInner').prop('jobs')).toEqual(jobs); + WS.clean(); + }); + + test('should establish websocket connection', async () => { + global.document.cookie = 'csrftoken=abc123'; + const mockServer = new WS('wss://localhost/websocket/'); + + const jobs = [{ id: 1 }]; + await act(async () => { + wrapper = await mount(); + }); + + await mockServer.connected; + await expect(mockServer).toReceiveMessage( + JSON.stringify({ + xrftoken: 'abc123', + groups: { + jobs: ['status_changed'], + schedules: ['changed'], + control: ['limit_reached_1'], + }, + }) + ); + WS.clean(); + }); + + test('should update job status', async () => { + global.document.cookie = 'csrftoken=abc123'; + const mockServer = new WS('wss://localhost/websocket/'); + + const jobs = [{ id: 1, status: 'running' }]; + await act(async () => { + wrapper = await mount(); + }); + + await mockServer.connected; + await expect(mockServer).toReceiveMessage( + JSON.stringify({ + xrftoken: 'abc123', + groups: { + jobs: ['status_changed'], + schedules: ['changed'], + control: ['limit_reached_1'], + }, + }) + ); + expect(wrapper.find('TestInner').prop('jobs')[0].status).toEqual('running'); + act(() => { + mockServer.send( + JSON.stringify({ + unified_job_id: 1, + status: 'successful', + }) + ); + }); + wrapper.update(); + + expect(wrapper.find('TestInner').prop('jobs')[0].status).toEqual( + 'successful' + ); + WS.clean(); + }); + + test('should fetch new job', async () => { + global.document.cookie = 'csrftoken=abc123'; + const mockServer = new WS('wss://localhost/websocket/'); + const jobs = [{ id: 1 }]; + const fetch = jest.fn(); + await act(async () => { + wrapper = await mount(); + }); + + await mockServer.connected; + act(() => { + mockServer.send( + JSON.stringify({ + unified_job_id: 2, + status: 'running', + }) + ); + }); + + expect(fetch).toHaveBeenCalledWith([2]); + WS.clean(); + }); +});