From 17008a694d3bc6c28cf9d03c9d18410818266c5b Mon Sep 17 00:00:00 2001 From: Alex K Date: Mon, 6 Feb 2023 12:42:18 +0100 Subject: [PATCH 1/4] change(tracker): unit tests for tracker * chore(tracker):exclude test files from compilation * feat(tracker): webworker unit tests * fix(tracker): no empty tests * change(tracker): add jest test to workflow * change(tracker): fix jest run --------- Co-authored-by: nick-delirium --- .github/workflows/ui-tests.js.yml | 6 +- .gitignore | 1 + tracker/tracker/.eslintignore | 3 + tracker/tracker/.eslintrc.cjs | 2 +- .../webworker/PrimitiveEncoder.unit.test.ts | 63 ++++++++++++ .../src/webworker/QueueSender.unit.test.ts | 95 +++++++++++++++++++ tracker/tracker/tsconfig-base.json | 3 +- 7 files changed, 169 insertions(+), 4 deletions(-) create mode 100644 tracker/tracker/src/webworker/PrimitiveEncoder.unit.test.ts create mode 100644 tracker/tracker/src/webworker/QueueSender.unit.test.ts diff --git a/.github/workflows/ui-tests.js.yml b/.github/workflows/ui-tests.js.yml index 94d5a05d5..dbc6d377e 100644 --- a/.github/workflows/ui-tests.js.yml +++ b/.github/workflows/ui-tests.js.yml @@ -47,8 +47,10 @@ jobs: cd tracker/tracker npm i -g yarn yarn -# - name: Jest tests -# run: yarn test + - name: Jest tests + run: | + cd tracker/tracker + yarn test - name: Build tracker inst run: | cd tracker/tracker diff --git a/.gitignore b/.gitignore index d509c8e8c..5eeb1c83b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,6 @@ public node_modules *DS_Store *.env +*.log **/*.envrc .idea diff --git a/tracker/tracker/.eslintignore b/tracker/tracker/.eslintignore index 688a8d852..228c6174c 100644 --- a/tracker/tracker/.eslintignore +++ b/tracker/tracker/.eslintignore @@ -6,3 +6,6 @@ build .cache .eslintrc.cjs *.gen.ts + +**/*.test.ts +src/main/plugin.ts \ No newline at end of file diff --git a/tracker/tracker/.eslintrc.cjs b/tracker/tracker/.eslintrc.cjs index 72c31e9a4..3b3ebc4fc 100644 --- a/tracker/tracker/.eslintrc.cjs +++ b/tracker/tracker/.eslintrc.cjs @@ -3,7 +3,7 @@ module.exports = { root: true, parser: '@typescript-eslint/parser', parserOptions: { - project: ['./tsconfig-base.json', './src/main/tsconfig-cjs.json'], + project: ['./tsconfig-base.json', './src/main/tsconfig-cjs.json'], // ??TODO: use correct project tsconfigRootDir: __dirname, }, plugins: ['prettier', '@typescript-eslint'], diff --git a/tracker/tracker/src/webworker/PrimitiveEncoder.unit.test.ts b/tracker/tracker/src/webworker/PrimitiveEncoder.unit.test.ts new file mode 100644 index 000000000..2d4be80b4 --- /dev/null +++ b/tracker/tracker/src/webworker/PrimitiveEncoder.unit.test.ts @@ -0,0 +1,63 @@ +import { describe, expect, test, jest, beforeEach, afterEach } from '@jest/globals' +import PrimitiveEncoder from './PrimitiveEncoder.js' + +describe('PrimitiveEncoder', () => { + test('initial state', () => { + const enc = new PrimitiveEncoder(10) + + expect(enc.getCurrentOffset()).toBe(0) + expect(enc.isEmpty).toBe(true) + expect(enc.flush().length).toBe(0) + }) + + test('skip()', () => { + const enc = new PrimitiveEncoder(10) + enc.skip(5) + expect(enc.getCurrentOffset()).toBe(5) + expect(enc.isEmpty).toBe(false) + }) + + test('checkpoint()', () => { + const enc = new PrimitiveEncoder(10) + enc.skip(5) + enc.checkpoint() + expect(enc.flush().length).toBe(5) + }) + + test('boolean(true)', () => { + const enc = new PrimitiveEncoder(10) + enc.boolean(true) + enc.checkpoint() + const bytes = enc.flush() + expect(bytes.length).toBe(1) + expect(bytes[0]).toBe(1) + }) + test('boolean(false)', () => { + const enc = new PrimitiveEncoder(10) + enc.boolean(false) + enc.checkpoint() + const bytes = enc.flush() + expect(bytes.length).toBe(1) + expect(bytes[0]).toBe(0) + }) + // TODO: test correct enc/dec on a top level(?) with player(PrimitiveReader.ts)/tracker(PrimitiveEncoder.ts) + + test('buffer oveflow with string()', () => { + const N = 10 + const enc = new PrimitiveEncoder(N) + const wasWritten = enc.string('long string'.repeat(N)) + expect(wasWritten).toBe(false) + }) + test('buffer oveflow with uint()', () => { + const enc = new PrimitiveEncoder(1) + const wasWritten = enc.uint(Number.MAX_SAFE_INTEGER) + expect(wasWritten).toBe(false) + }) + test('buffer oveflow with boolean()', () => { + const enc = new PrimitiveEncoder(1) + let wasWritten = enc.boolean(true) + expect(wasWritten).toBe(true) + wasWritten = enc.boolean(true) + expect(wasWritten).toBe(false) + }) +}) diff --git a/tracker/tracker/src/webworker/QueueSender.unit.test.ts b/tracker/tracker/src/webworker/QueueSender.unit.test.ts new file mode 100644 index 000000000..fc89a60a4 --- /dev/null +++ b/tracker/tracker/src/webworker/QueueSender.unit.test.ts @@ -0,0 +1,95 @@ +import { describe, expect, test, jest, beforeEach, afterEach } from '@jest/globals' +import QueueSender from './QueueSender.js' + +global.fetch = () => Promise.resolve(new Response()) // jsdom does not have it + +function mockFetch(status: number) { + return jest + .spyOn(global, 'fetch') + .mockImplementation(() => Promise.resolve({ status } as Response)) +} +const baseURL = 'MYBASEURL' +const sampleArray = new Uint8Array(1) +const randomToken = 'abc' +function defaultQueueSender({ + url = baseURL, + onUnauthorised = () => {}, + onFailed = () => {}, +} = {}) { + return new QueueSender(baseURL, onUnauthorised, onFailed) +} + +describe('QueueSender', () => { + afterEach(() => { + jest.restoreAllMocks() + }) + + // Test fetch first parameter + authorization header to be present + + // authorise() / push() + test('Does not call fetch if not authorised', () => { + const queueSender = defaultQueueSender() + const fetchMock = mockFetch(200) + + queueSender.push(sampleArray) + expect(fetchMock).not.toBeCalled() + }) + test('Calls fetch on push() if authorised', () => { + const queueSender = defaultQueueSender() + const fetchMock = mockFetch(200) + + queueSender.authorise(randomToken) + expect(fetchMock).toBeCalledTimes(0) + queueSender.push(sampleArray) + expect(fetchMock).toBeCalledTimes(1) + }) + test('Calls fetch on authorisation if there was a push() call before', () => { + const queueSender = defaultQueueSender() + const fetchMock = mockFetch(200) + + queueSender.push(sampleArray) + queueSender.authorise(randomToken) + expect(fetchMock).toBeCalledTimes(1) + }) + + // .clean() + test("Doesn't call fetch on push() after clean()", () => { + const queueSender = defaultQueueSender() + const fetchMock = mockFetch(200) + + queueSender.authorise(randomToken) + queueSender.clean() + queueSender.push(sampleArray) + expect(fetchMock).not.toBeCalled() + }) + test("Doesn't call fetch on authorisation if there was push() & clean() calls before", () => { + const queueSender = defaultQueueSender() + const fetchMock = mockFetch(200) + + queueSender.push(sampleArray) + queueSender.clean() + queueSender.authorise(randomToken) + expect(fetchMock).not.toBeCalled() + }) + + //Test N sequential ToBeCalledTimes(N) + //Test N sequential pushes with different timeouts to be sequential + + // onUnauthorised + test('Calls onUnauthorized callback on 401', (done) => { + const onUnauthorised = jest.fn() + const queueSender = defaultQueueSender({ + onUnauthorised, + }) + const fetchMock = mockFetch(401) + queueSender.authorise(randomToken) + queueSender.push(sampleArray) + setTimeout(() => { + // how to make test simpler and more explicit? + expect(onUnauthorised).toBeCalled() + done() + }, 100) + }) + //Test onFailure + //Test attempts timeout/ attempts count (toBeCalledTimes on one batch) +}) diff --git a/tracker/tracker/tsconfig-base.json b/tracker/tracker/tsconfig-base.json index 9af9edb73..fa70026d2 100644 --- a/tracker/tracker/tsconfig-base.json +++ b/tracker/tracker/tsconfig-base.json @@ -10,5 +10,6 @@ "target": "es6", "module": "es6", "moduleResolution": "nodenext" - } + }, + "exclude": ["**/*.test.ts"] } From aee0778f628930b8bb5aa3f39ba2e393598a891b Mon Sep 17 00:00:00 2001 From: nick-delirium Date: Mon, 6 Feb 2023 12:54:33 +0100 Subject: [PATCH 2/4] fix(ui): fix sharing loaders, fix skip intervals --- .../Session_/Player/Controls/Controls.tsx | 2 ++ .../components/shared/SharePopup/SharePopup.js | 16 +++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/frontend/app/components/Session_/Player/Controls/Controls.tsx b/frontend/app/components/Session_/Player/Controls/Controls.tsx index 221cc064a..37d8bdbef 100644 --- a/frontend/app/components/Session_/Player/Controls/Controls.tsx +++ b/frontend/app/components/Session_/Player/Controls/Controls.tsx @@ -18,6 +18,7 @@ import { STACKEVENTS, STORAGE, toggleBottomBlock, + changeSkipInterval, } from 'Duck/components/player'; import { PlayerContext } from 'App/components/Session/playerContext'; import { observer } from 'mobx-react-lite'; @@ -290,6 +291,7 @@ export default connect( fullscreenOff, toggleBottomBlock, fetchSessions, + changeSkipInterval, } )(ControlPlayer); diff --git a/frontend/app/components/shared/SharePopup/SharePopup.js b/frontend/app/components/shared/SharePopup/SharePopup.js index 8e09f819c..5e01dde3d 100644 --- a/frontend/app/components/shared/SharePopup/SharePopup.js +++ b/frontend/app/components/shared/SharePopup/SharePopup.js @@ -25,7 +25,8 @@ export default class SharePopup extends React.PureComponent { isOpen: false, channelId: this.props.channels.getIn([0, 'webhookId']), teamsChannel: this.props.msTeamsChannels.getIn([0, 'webhookId']), - loading: false, + loadingSlack: false, + loadingTeams: false, }; componentDidMount() { @@ -39,7 +40,7 @@ export default class SharePopup extends React.PureComponent { editMessage = (e) => this.setState({ comment: e.target.value }); shareToSlack = () => { - this.setState({ loading: true }, () => { + this.setState({ loadingSlack: true }, () => { this.props .sendSlackMsg({ integrationId: this.state.channelId, @@ -52,7 +53,7 @@ export default class SharePopup extends React.PureComponent { }; shareToMSTeams = () => { - this.setState({ loading: true }, () => { + this.setState({ loadingTeams: true }, () => { this.props .sendMsTeamsMsg({ integrationId: this.state.teamsChannel, @@ -75,7 +76,8 @@ export default class SharePopup extends React.PureComponent { }; handleSuccess = (endpoint) => { - this.setState({ isOpen: false, comment: '', loading: false }); + const obj = endpoint === 'Slack' ? { isOpen: false, comment: '', loadingSlack: false } : { isOpen: false, comment: '', loadingTeams: false } + this.setState(obj); toast.success(`Sent to ${endpoint}.`); }; @@ -89,7 +91,7 @@ export default class SharePopup extends React.PureComponent { render() { const { trigger, channels, msTeamsChannels, showCopyLink = false } = this.props; - const { comment, channelId, teamsChannel, loading } = this.state; + const { comment, channelId, teamsChannel, loadingSlack, loadingTeams } = this.state; const slackOptions = channels .map(({ webhookId, name }) => ({ value: webhookId, label: name })) @@ -137,7 +139,7 @@ export default class SharePopup extends React.PureComponent { )} @@ -163,7 +165,7 @@ export default class SharePopup extends React.PureComponent { size="18" marginRight="10" /> - {loading ? 'Sending...' : 'Send'} + {loadingTeams ? 'Sending...' : 'Send'} )} From 2bd39b0d5167959f24f75ce4a5478d8c9bc770b4 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 3 Feb 2023 17:00:58 +0100 Subject: [PATCH 3/4] change(ui) - card request clear unused key --- .../Dashboard/components/WidgetSessions/WidgetSessions.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx b/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx index 3c7f6aacb..4052e7a7e 100644 --- a/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx +++ b/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx @@ -42,6 +42,7 @@ function WidgetSessions(props: Props) { const fetchSessions = (metricId: any, filter: any) => { if (!isMounted()) return; setLoading(true); + delete filter.eventsOrderSupport; widget .fetchSessions(metricId, filter) .then((res: any) => { From 42378defdcbd5a76e4edce32dc1df01f8725823d Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Mon, 6 Feb 2023 15:25:28 +0100 Subject: [PATCH 4/4] fix(ui) - auto play safari issue --- .../Session_/QueueControls/QueueControls.tsx | 82 ++++++++++--------- 1 file changed, 44 insertions(+), 38 deletions(-) diff --git a/frontend/app/components/Session_/QueueControls/QueueControls.tsx b/frontend/app/components/Session_/QueueControls/QueueControls.tsx index cbb49d02d..cfb2f7a0c 100644 --- a/frontend/app/components/Session_/QueueControls/QueueControls.tsx +++ b/frontend/app/components/Session_/QueueControls/QueueControls.tsx @@ -1,8 +1,8 @@ import React, { useEffect } from 'react'; import { connect } from 'react-redux'; import { setAutoplayValues } from 'Duck/sessions'; -import { session as sessionRoute } from 'App/routes'; -import { Link, Icon, Tooltip } from 'UI';; +import { withSiteId, session as sessionRoute } from 'App/routes'; +import { Link, Icon, Tooltip } from 'UI'; import { withRouter, RouteComponentProps } from 'react-router-dom'; import cn from 'classnames'; import { fetchAutoplaySessions } from 'Duck/search'; @@ -10,18 +10,20 @@ import { fetchAutoplaySessions } from 'Duck/search'; const PER_PAGE = 10; interface Props extends RouteComponentProps { + siteId: string; previousId: string; nextId: string; defaultList: any; currentPage: number; total: number; - setAutoplayValues?: () => void; + setAutoplayValues: () => void; latestRequestTime: any; sessionIds: any; - fetchAutoplaySessions?: (page: number) => Promise; + fetchAutoplaySessions: (page: number) => Promise; } function QueueControls(props: Props) { const { + siteId, previousId, nextId, currentPage, @@ -49,43 +51,46 @@ function QueueControls(props: Props) { } }, []); + const nextHandler = () => { + props.history.push(withSiteId(sessionRoute(nextId), siteId)); + }; + + const prevHandler = () => { + props.history.push(withSiteId(sessionRoute(previousId), siteId)); + }; + return (
- Play Previous Session
} - disabled={!previousId} +
- -
- -
- - - - Play Next Session
} - disabled={!nextId} + Play Previous Session} + disabled={!previousId} + > + + + +
- -
- -
- - + Play Next Session
} + disabled={!nextId} + > + + + ); } @@ -98,6 +103,7 @@ export default connect( total: state.getIn(['sessions', 'total']) || 0, sessionIds: state.getIn(['sessions', 'sessionIds']) || [], latestRequestTime: state.getIn(['search', 'latestRequestTime']), + siteId: state.getIn(['site', 'siteId']), }), { setAutoplayValues, fetchAutoplaySessions } -)(withRouter(QueueControls)) +)(withRouter(QueueControls));