diff --git a/config/CSSStub.ts b/config/CSSStub.ts new file mode 100644 index 000000000..c8a9c8ebe --- /dev/null +++ b/config/CSSStub.ts @@ -0,0 +1 @@ +module.exports = null; \ No newline at end of file diff --git a/jest.config.js b/jest.config.js index 3bc5438dc..2786a91f4 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,9 +1,14 @@ +const esModules = ['@ms-ofb', 'ngx-bootstrap', 'lodash-es', '@fluentui'].join('|'); module.exports = { collectCoverageFrom: [ 'src/**/*.{js,jsx,ts,tsx}', '!**/node_modules/**', '!build/**', - '!src/**/*.d.ts' + '!src/**/*.d.ts', + '!src/index.tsx', + '!src/tests/accessibility/**', + '!src/app/middleware/telemetryMiddleware.ts', + '!src/telemetry/telemetry.ts' ], resolver: 'jest-pnp-resolver', setupFiles: ['react-app-polyfill/jsdom'], @@ -17,15 +22,17 @@ module.exports = { transform: { '^.+\\.(js|jsx|ts|tsx)$': 'ts-jest', '^.+\\.css$': '/config/jest/cssTransform.js', - '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)':'ts-jest' + '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)':'ts-jest', + [`(${esModules}).+\\.js$`]: 'ts-jest' }, transformIgnorePatterns: [ - '[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$', - '^.+\\.module\\.(css|sass|scss)$' + '^.+\\.module\\.(css|sass|scss)$', + '/node_modules/(?!@fluentui)' ], moduleNameMapper: { '^react-native$': 'react-native-web', - '^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy' + '^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy', + '^.+\\.(css|less|scss)$': '/config/CSSStub.ts' }, moduleFileExtensions: [ 'web.js', @@ -43,5 +50,6 @@ module.exports = { 'jest-watch-typeahead/filename', 'jest-watch-typeahead/testname' ], - testResultsProcessor: 'jest-sonar-reporter' + testResultsProcessor: 'jest-sonar-reporter', + testPathIgnorePatterns: ['/src/tests/accessibility/'] }; diff --git a/package-lock.json b/package-lock.json index 1091c34db..ff1370463 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2709,6 +2709,129 @@ "integrity": "sha512-1c4ZOETSRpI0iBfIFUqU4KqwBAB2lHUAlBjZz/YqOHqwM9dTTzjV6Km0ZkiEiSCx/tLr1BtESIKyWWMww+RUqw==", "dev": true }, + "@testing-library/dom": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.11.3.tgz", + "integrity": "sha512-9LId28I+lx70wUiZjLvi1DB/WT2zGOxUh46glrSNMaWVx849kKAluezVzZrXJfTKKoQTmEOutLes/bHg4Bj3aA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^4.2.0", + "aria-query": "^5.0.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.4.4", + "pretty-format": "^27.0.2" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz", + "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "aria-query": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.0.0.tgz", + "integrity": "sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@testing-library/react": { + "version": "12.1.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-12.1.2.tgz", + "integrity": "sha512-ihQiEOklNyHIpo2Y8FREkyD1QAea054U0MVbwH1m8N9TxeFz+KoJ9LkqoKqJlzx2JDm56DVwaJ1r36JYxZM05g==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^8.0.0" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz", + "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + } + } + } + }, + "@testing-library/user-event": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.5.0.tgz", + "integrity": "sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz", + "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + } + } + } + }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -2719,6 +2842,12 @@ "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==" }, + "@types/aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==", + "dev": true + }, "@types/atob-lite": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/atob-lite/-/atob-lite-2.0.0.tgz", @@ -3018,6 +3147,17 @@ "jest-diff": "*" } }, + "@types/jsdom": { + "version": "16.2.14", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-16.2.14.tgz", + "integrity": "sha512-6BAy1xXEmMuHeAJ4Fv4yXKwBDTGTOseExKE3OaHiNycdHdZw59KfYzrt0DkDluvwmik1HRt6QS7bImxUmpSy+w==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/parse5": "*", + "@types/tough-cookie": "*" + } + }, "@types/json-schema": { "version": "7.0.9", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", @@ -3080,6 +3220,12 @@ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, + "@types/parse5": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz", + "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==", + "dev": true + }, "@types/prettier": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.4.tgz", @@ -3264,6 +3410,12 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" }, + "@types/tough-cookie": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.1.tgz", + "integrity": "sha512-Y0K95ThC3esLEYD6ZuqNek29lNX2EM1qxV8y2FTLUB0ff5wWrk7az+mLrnNFUnaXcgKye22+sFBRXOgpPILZNg==", + "dev": true + }, "@types/trusted-types": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", @@ -12532,6 +12684,12 @@ "yallist": "^3.0.2" } }, + "lz-string": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", + "integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=", + "dev": true + }, "magic-string": { "version": "0.25.7", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", @@ -18741,4 +18899,4 @@ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index f991ae697..a7ea544a9 100644 --- a/package.json +++ b/package.json @@ -101,11 +101,13 @@ ], "devDependencies": { "@testing-library/react": "12.1.2", + "@testing-library/user-event": "13.5.0", "@types/chromedriver": "2.38.0", "@types/enzyme": "3.10.10", "@types/enzyme-adapter-react-16": "1.0.5", "@types/isomorphic-fetch": "0.0.35", "@types/jest": "24.0.6", + "@types/jsdom": "^16.2.14", "@types/jwt-decode": "2.2.1", "@types/node": "11.9.4", "@types/react": "17.0.35", diff --git a/src/app/services/actions/ocps-action-creators.ts b/src/app/services/actions/ocps-action-creators.ts index 704601ea0..d75a428e2 100644 --- a/src/app/services/actions/ocps-action-creators.ts +++ b/src/app/services/actions/ocps-action-creators.ts @@ -87,9 +87,9 @@ export function getPolicy(response: any): IPolicyValues { return values; } -function getPolicyUrl():string { +export function getPolicyUrl(): string { const { NODE_ENV } = process.env; - if(NODE_ENV === 'development'){ + if (NODE_ENV === 'development') { return 'https://sip.clients.config.office.net/user/v1.0/web/policies'; } else { return 'https://clients.config.office.net/user/v1.0/web/policies'; diff --git a/src/app/services/actions/permissions-action-creator.ts b/src/app/services/actions/permissions-action-creator.ts index a2fc09817..3a1676659 100644 --- a/src/app/services/actions/permissions-action-creator.ts +++ b/src/app/services/actions/permissions-action-creator.ts @@ -100,7 +100,7 @@ export function fetchScopes(): Function { }; } -function getPermissionsScopeType(profile: IUser | null | undefined) { +export function getPermissionsScopeType(profile: IUser | null | undefined) { if (profile?.profileType === ACCOUNT_TYPE.MSA) { return PERMS_SCOPE.PERSONAL; } diff --git a/src/app/services/actions/profile-action-creators.ts b/src/app/services/actions/profile-action-creators.ts index 574a9e358..921f7798c 100644 --- a/src/app/services/actions/profile-action-creators.ts +++ b/src/app/services/actions/profile-action-creators.ts @@ -68,7 +68,7 @@ export function getProfileInfo(): Function { }; } -async function getProfileInformation(): Promise { +export async function getProfileInformation(): Promise { const profile: IUser = { id: '', displayName: '', @@ -89,7 +89,7 @@ async function getProfileInformation(): Promise { } } -async function getBetaProfile(): Promise { +export async function getBetaProfile(): Promise { try { query.sampleUrl = BETA_USER_INFO_URL; const { userInfo } = await getProfileResponse(); @@ -101,7 +101,7 @@ async function getBetaProfile(): Promise { } } -function getAgeGroup(userInfo: any): AgeGroup { +export function getAgeGroup(userInfo: any): AgeGroup { const profileType = getProfileType(userInfo); if (profileType === ACCOUNT_TYPE.MSA) { const ageGroup = userInfo?.account?.[0]?.ageGroup; @@ -113,7 +113,7 @@ function getAgeGroup(userInfo: any): AgeGroup { return 0; } } -function getProfileType(userInfo: any): ACCOUNT_TYPE { +export function getProfileType(userInfo: any): ACCOUNT_TYPE { const profileType: ACCOUNT_TYPE = userInfo?.account?.[0]?.source?.type?.[0]; if (profileType === undefined) { return ACCOUNT_TYPE.UNDEFINED; @@ -121,7 +121,7 @@ function getProfileType(userInfo: any): ACCOUNT_TYPE { return profileType; } -async function getProfileImage(): Promise { +export async function getProfileImage(): Promise { let profileImageUrl = ''; try { query.sampleUrl = USER_PICTURE_URL; @@ -137,7 +137,7 @@ async function getProfileImage(): Promise { return profileImageUrl; } -async function getProfileResponse(): Promise { +export async function getProfileResponse(): Promise { const scopes = DEFAULT_USER_SCOPES.split(' '); const respHeaders: any = {}; diff --git a/src/app/services/actions/snippet-action-creator.ts b/src/app/services/actions/snippet-action-creator.ts index 6937a5f39..f8392cff4 100644 --- a/src/app/services/actions/snippet-action-creator.ts +++ b/src/app/services/actions/snippet-action-creator.ts @@ -56,13 +56,12 @@ export function getSnippet(language: string): Function { const requestBody = sampleQuery.sampleBody && - Object.keys(sampleQuery.sampleBody).length !== 0 && // check if empty object - sampleQuery.sampleBody.trim() !== '' + Object.keys(sampleQuery.sampleBody).length !== 0 && // check if empty object + sampleQuery.sampleBody.trim() !== '' ? JSON.stringify(sampleQuery.sampleBody) : ''; - const body = `${sampleQuery.selectedVerb} /${queryVersion}/${ - requestUrl + search + const body = `${sampleQuery.selectedVerb} /${queryVersion}/${requestUrl + search } HTTP/1.1\r\nHost: graph.microsoft.com\r\nContent-Type: application/json\r\n\r\n${requestBody}`; const options: IRequestOptions = { method, headers, body }; const obj: any = {}; diff --git a/src/app/services/reducers/query-runner-reducers.ts b/src/app/services/reducers/query-runner-reducers.ts index 7cd2ad1e2..a71e15446 100644 --- a/src/app/services/reducers/query-runner-reducers.ts +++ b/src/app/services/reducers/query-runner-reducers.ts @@ -20,6 +20,7 @@ export function graphResponse( case QUERY_GRAPH_SUCCESS: return action.response; case VIEW_HISTORY_ITEM_SUCCESS: + console.log('Here is the item', action.response); return action.response; case QUERY_GRAPH_RUNNING: return initialState; diff --git a/src/app/views/query-runner/query-input/QueryInput.tsx b/src/app/views/query-runner/query-input/QueryInput.tsx index cc53a4069..2fbd2967d 100644 --- a/src/app/views/query-runner/query-input/QueryInput.tsx +++ b/src/app/views/query-runner/query-input/QueryInput.tsx @@ -1,5 +1,4 @@ -import { IDropdownOption } from '@fluentui/react'; -import { Dropdown } from '@fluentui/react'; +import { IDropdownOption, Dropdown } from '@fluentui/react'; import React from 'react'; import { injectIntl } from 'react-intl'; import { useDispatch, useSelector } from 'react-redux'; diff --git a/src/app/views/query-runner/request/request.scss b/src/app/views/query-runner/request/request.scss index adb1ede63..5d18369c6 100644 --- a/src/app/views/query-runner/request/request.scss +++ b/src/app/views/query-runner/request/request.scss @@ -53,10 +53,4 @@ .query-request { overflow: hidden; - - @media screen and (min-width: 1320px){ - .pivot-request *[data-content='Got feedback? xx'] { - float: right !important; - } - } } \ No newline at end of file diff --git a/src/app/views/sidebar/Sidebar.tsx b/src/app/views/sidebar/Sidebar.tsx index 6b9827a21..a7497e7b8 100644 --- a/src/app/views/sidebar/Sidebar.tsx +++ b/src/app/views/sidebar/Sidebar.tsx @@ -5,7 +5,6 @@ import { telemetry } from '../../../telemetry'; import { translateMessage } from '../../utils/translate-messages'; import History from './history/History'; import SampleQueries from './sample-queries/SampleQueries'; - export const Sidebar = () => { return (
diff --git a/src/app/views/sidebar/resource-explorer/resources.styles.ts b/src/app/views/sidebar/resource-explorer/resources.styles.ts index b3c46388a..50fd22912 100644 --- a/src/app/views/sidebar/resource-explorer/resources.styles.ts +++ b/src/app/views/sidebar/resource-explorer/resources.styles.ts @@ -54,4 +54,4 @@ export const navStyles: any = (properties: any) => ({ } } ] -}); \ No newline at end of file +}); diff --git a/src/setupTests.ts b/src/setupTests.ts index 3a6f8bfa1..7e435a5ef 100644 --- a/src/setupTests.ts +++ b/src/setupTests.ts @@ -6,4 +6,4 @@ configure({ adapter: new Adapter() }); const customGlobal: GlobalWithFetchMock = global as GlobalWithFetchMock; customGlobal.fetch = require('jest-fetch-mock'); // tslint:disable-line -customGlobal.fetchMock = customGlobal.fetch; +customGlobal.fetchMock = customGlobal.fetch; \ No newline at end of file diff --git a/src/tests/authentication/AuthenticationWrapper.spec.ts b/src/tests/authentication/AuthenticationWrapper.spec.ts new file mode 100644 index 000000000..b59d62394 --- /dev/null +++ b/src/tests/authentication/AuthenticationWrapper.spec.ts @@ -0,0 +1,86 @@ +import { AuthenticationWrapper } from '../../../src/modules/authentication/AuthenticationWrapper'; +import { HOME_ACCOUNT_KEY } from '../../app/services/graph-constants'; + +window.open = jest.fn(); +jest.spyOn(window.sessionStorage.__proto__, 'clear'); + +jest.spyOn(window.localStorage.__proto__, 'setItem'); +jest.spyOn(window.localStorage.__proto__, 'getItem'); +jest.spyOn(window.localStorage.__proto__, 'removeItem'); + +jest.mock('../../modules/authentication/msal-app.ts', () => { + const msalApplication = { + account: null, + getAccount: jest.fn(), + logoutRedirect: jest.fn(), + logoutPopup: jest.fn(), + loginRedirect: jest.fn(), + loginPopup: jest.fn(), + acquireTokenSilent: jest.fn(), + acquireTokenPopup: jest.fn(), + getAllAccounts: jest.fn() + }; + + return { + msalApplication + }; +}) +describe('Tests authentication wrapper functions', () => { + it('Returns null when account data is null', () => { + const sessionId = new AuthenticationWrapper().getSessionId(); + expect(sessionId).toBeNull(); + }); + + it('Throws an error when logIn fails', () => { + const logIn = new AuthenticationWrapper().logIn(); + expect(logIn).rejects.toThrow(); + }); + + it('Throws an error when consenting to scopes fails', () => { + const consentToScopes = new AuthenticationWrapper().consentToScopes(); + expect(consentToScopes).rejects.toThrow(); + }) + + it('Returns undefined when getAccount is called and msalApplication is undefined', () => { + const account = new AuthenticationWrapper().getAccount(); + expect(account).toBeUndefined(); + }) + + it('Throws an error when getToken returns a rejected Promise', () => { + const getToken = new AuthenticationWrapper().getToken(); + expect(getToken).resolves.toBeUndefined(); + }); + + describe('Throws an error when getOcpsToken fails ', () => { + it('Throws an error when getOcpsToken fails', () => { + const getOcpsToken = new AuthenticationWrapper().getOcpsToken(); + expect(getOcpsToken).rejects.toThrow(); + }); + }) + + it('Logs out a user and calls removeItem with the home_account_key', () => { + new AuthenticationWrapper().logOut(); + expect(window.localStorage.removeItem).toHaveBeenCalledWith(HOME_ACCOUNT_KEY); + }) + + it('Logs out a user with logoutPopup and calls removeItem once with the home_account_key', () => { + new AuthenticationWrapper().logOutPopUp(); + expect(window.localStorage.removeItem).toHaveBeenCalledWith(HOME_ACCOUNT_KEY); + }) + + it('Calls removeItem from localStorage when deleting home account id', () => { + new AuthenticationWrapper().deleteHomeAccountId(); + expect(window.localStorage.removeItem).toHaveBeenCalledWith(HOME_ACCOUNT_KEY); + }); + + it('Clears the cache by calling removeItem with all available msal keys', () => { + new AuthenticationWrapper().clearCache(); + expect(window.localStorage.removeItem).toHaveBeenCalled(); + }); + + it('Clears user current session, calling removeItem from localStorage and window.sessionStorage.clear', () => { + new AuthenticationWrapper().clearSession(); + expect(window.localStorage.removeItem).toHaveBeenCalled(); + expect(window.sessionStorage.clear).toHaveBeenCalled(); + }) +}) \ No newline at end of file diff --git a/src/tests/authentication/authUtils.spec.ts b/src/tests/authentication/authUtils.spec.ts new file mode 100644 index 000000000..f3859e917 --- /dev/null +++ b/src/tests/authentication/authUtils.spec.ts @@ -0,0 +1,11 @@ +import { getLoginType, getCurrentUri } from '../../../src/modules/authentication/authUtils'; +import { LoginType } from '../../types/enums'; + +describe('Tests auth utils', () => { + it('Returns valid login type', () => { + expect(getLoginType()).toBe(LoginType.Popup); + }) + it('Returns valid uri for current app', () => { + expect(getCurrentUri()).toBeDefined(); + }) +}) \ No newline at end of file diff --git a/src/tests/common/copy.spec.ts b/src/tests/common/copy.spec.ts new file mode 100644 index 000000000..4daac2b6f --- /dev/null +++ b/src/tests/common/copy.spec.ts @@ -0,0 +1,12 @@ +import { genericCopy } from '../../app/views/common/copy'; + +describe('Tests copy.ts', () => { + it('Tests generic copy which resolves to an empty object ', () => { + document.execCommand = jest.fn(); + genericCopy('dummy text') + .then((response) => { + expect(response).toBe('copied'); + }) + .catch((e: Error) => { throw e }) + }); +}) diff --git a/src/tests/common/dimensions-adjustment/dimension-adjustment.spec.ts b/src/tests/common/dimensions-adjustment/dimension-adjustment.spec.ts new file mode 100644 index 000000000..4a825593a --- /dev/null +++ b/src/tests/common/dimensions-adjustment/dimension-adjustment.spec.ts @@ -0,0 +1,21 @@ +import { convertVhToPx, convertPxToVh, getResponseHeight } from '../../../app/views/common/dimensions-adjustment'; + +describe('Tests dimension adjustments', () => { + it('Converts vh to px', () => { + const height = '90vh'; + const adjustment = 10; + const newHeight = convertVhToPx(height, adjustment); + expect(newHeight).toBe('-10px'); + }); + it('Converts px to vh', () => { + const px = 890; + const newHeight = convertPxToVh(px); + expect(newHeight).toBe('115.88541666666667vh'); + }); + it('Gets response height', () => { + const height = '90vh'; + const responseAreaExpanded = true; + const newHeight = getResponseHeight(height, responseAreaExpanded); + expect(newHeight).toBe('90vh'); + }); +}) \ No newline at end of file diff --git a/src/tests/common/download.spec.ts b/src/tests/common/download.spec.ts new file mode 100644 index 000000000..c840910f7 --- /dev/null +++ b/src/tests/common/download.spec.ts @@ -0,0 +1,14 @@ +import { downloadToLocal } from '../../app/views/common/download'; + +window.URL.createObjectURL = jest.fn(); + +describe('Tests file downloads on resource collections', () => { + it('Downloads file to local', () => { + const content = { + app: '/me' + } + const fileName = 'TestFile'; + + downloadToLocal(content, fileName); + }) +}) \ No newline at end of file diff --git a/src/tests/common/monaco-tests/format-json.spec.ts b/src/tests/common/monaco-tests/format-json.spec.ts new file mode 100644 index 000000000..f0508b14b --- /dev/null +++ b/src/tests/common/monaco-tests/format-json.spec.ts @@ -0,0 +1,34 @@ +import { formatJsonStringForAllBrowsers } from '../../../app/views/common/monaco/util/format-json'; +import { formatXml } from '../../../app/views/common/monaco/util/format-xml'; + +describe('Tests json strings formatting in monaco editor ', () => { + it('Tests json strings formatting in monaco editor', () => { + const body = { + name: 'Megan', + surname: 'Bowen' + } + const formattedBody = formatJsonStringForAllBrowsers(body); + expect(isJSON(formattedBody)).toBe(true); + }) + + it('Tests the xml formatter function', () => { + // Arrange + const xml = 'Hello'; + // Act + const formattedXml = formatXml(xml); + // Assert + expect(formattedXml).toBeDefined(); + }) +}) + +const isJSON = (str: string) => { + try { + const json = JSON.parse(str); + if (Object.prototype.toString.call(json).slice(8, -1) !== 'Object') { + return false + } + } catch (e) { + return false + } + return true +} \ No newline at end of file diff --git a/src/tests/components/App.spec.tsx b/src/tests/components/App.spec.tsx new file mode 100644 index 000000000..99514e0fd --- /dev/null +++ b/src/tests/components/App.spec.tsx @@ -0,0 +1,114 @@ +import React from 'react'; +import { cleanup, render } from '@testing-library/react'; + +import App from '../../app/views/App'; +import { Mode } from '../../types/enums'; +import { IntlProvider } from 'react-intl'; +import { Provider } from 'react-redux'; +import { store } from '../../store'; +import { geLocale } from '../../appLocale'; +import messages from '../../messages'; + +afterEach(cleanup) +const renderApp = (args?: any) : any => { + const defaultProps = { + profile: null, + queryState: null, + termsOfUse: true, + graphExplorerMode: Mode.Complete, + sidebarProperties: {mobileScreen: args?.mobileScreen, showSidebar: true}, + sampleQuery: { + selectedVerb: 'GET', + selectedVersion: 'v1', + sampleUrl: 'https://graph.microsoft.com/v1.0/me', + sampleHeaders: [] + }, + authenticated: false, + actions: { + clearQueryStatus: jest.fn(), //Change the return value of mock to simulate actions + clearTermsOfUse: jest.fn(), + setSampleQuery: jest.fn(), + runQuery: jest.fn(), + toggleSidebar: jest.fn(), + signIn: jest.fn(), + storeScopes: jest.fn(), + changeTheme: jest.fn() + } + } + + Object.defineProperty(window, 'matchMedia', { + writable: true, + value: jest.fn().mockImplementation(query => ({ + matches: false, + media: query, + onchange: null, + addListener: jest.fn(), // Deprecated + removeListener: jest.fn(), // Deprecated + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn() + })) + }); + const props = {...args, ...defaultProps}; + const appStore: any = store; + + return render( + + + + + ); +} + +jest.mock('@microsoft/applicationinsights-react-js', () => ({ + // eslint-disable-next-line react/display-name + withAITracking: () => React.Component, + ReactPlugin: Object +})) + +jest.mock('@ms-ofb/officebrowserfeedbacknpm/scripts/app/Window/Window', () => ({ + OfficeBrowserFeedback: Object +})) + +jest.mock('@ms-ofb/officebrowserfeedbacknpm/Floodgate', () => ({ + makeFloodgate: Object +})) + +jest.mock('@ms-ofb/officebrowserfeedbacknpm/scripts/app/Configuration/IInitOptions', () => ({ + AuthenticationType: 0 +})) + +// eslint-disable-next-line no-console +console.warn = jest.fn() + +describe('It should render the main GE site', () => { + it('Should confirm that all the major sections are rendered', () => { + const { getByText } = renderApp({mobileScreen: false}); + getByText('Run query'); + getByText('Sign in to Graph Explorer'); + getByText('Request body'); + getByText('Request headers'); + getByText('Modify permissions (Preview)'); + getByText('Access token'); + getByText(/Help Improve/); + getByText('Response preview'); + getByText('Response headers'); + getByText('Code snippets'); + getByText('Toolkit component'); + getByText('Adaptive cards'); + getByText('Expand'); + getByText('Share'); + getByText('Authentication'); + getByText('Sample queries'); + getByText('History'); + }); + + it('Should render the main app with a mobile screen view', ()=> { + const { getByText } = renderApp({mobileScreen: true}); + getByText(/Run query/); + getByText(/Authentication/); + }); +}) \ No newline at end of file diff --git a/src/tests/components/AppTitle.spec.tsx b/src/tests/components/AppTitle.spec.tsx new file mode 100644 index 000000000..2ee48f27b --- /dev/null +++ b/src/tests/components/AppTitle.spec.tsx @@ -0,0 +1,83 @@ +import { appTitleDisplayOnMobileScreen, appTitleDisplayOnFullScreen } from '../../app/views/app-sections/AppTitle'; +import React from 'react'; +import { cleanup, render, screen } from '@testing-library/react'; + +afterEach(cleanup) +const renderTitle = () => { + const stackTokens = { + childrenGap: 10, + padding: 10 + } + const classes_ = jest.fn(); + return render( +
+ {appTitleDisplayOnMobileScreen(stackTokens, classes_, jest.fn())} +
+ ) +} + +const renderTitleOnFullScreen = (args?: any) => { + const classes_ = jest.fn(); + const mimimised = args?.minimised ? args?.minimised : false; + + return render( +
+ {appTitleDisplayOnFullScreen(classes_, mimimised, jest.fn())} +
+ ) +} + +jest.mock('@microsoft/applicationinsights-react-js', () => ({ + // eslint-disable-next-line react/display-name + withAITracking: () => React.Component, + ReactPlugin: Object +})) + +jest.mock('@ms-ofb/officebrowserfeedbacknpm/scripts/app/Window/Window', () => ({ + OfficeBrowserFeedback: Object +})) + +jest.mock('@ms-ofb/officebrowserfeedbacknpm/Floodgate', () => ({ + makeFloodgate: Object +})) + +jest.mock('@ms-ofb/officebrowserfeedbacknpm/scripts/app/Configuration/IInitOptions', () => ({ + AuthenticationType: 0 +})) + +// eslint-disable-next-line react/display-name +jest.mock('../../app/views/query-runner/request/feedback/FeedbackForm.tsx', () => () => { + return
Feedback
+}); + +jest.mock('react-redux', () => ({ + useSelector: jest.fn(() => { + return { + profile: { + profileType: 'MSA' + } + } + }), + useDispatch: jest.fn() +})); + +// eslint-disable-next-line no-console +console.warn = jest.fn() + +describe('It should render the app title section in mobile screen size', () => { + it('Renders app title section', () => { + const { getByText } = renderTitle(); + getByText('Graph Explorer'); + }); + + it('Renders app title section in full screen without crashing', () => { + const { getByText } = renderTitleOnFullScreen({minimised: false}); + getByText('Graph Explorer'); + }); + + it('Renders app title section in minimised mode without crashing', () => { + renderTitleOnFullScreen({minimised: true}); + expect(screen.getByRole('heading')).toBeDefined(); + }) +}) + diff --git a/src/tests/components/CopyButton.spec.tsx b/src/tests/components/CopyButton.spec.tsx new file mode 100644 index 000000000..c38c0de2e --- /dev/null +++ b/src/tests/components/CopyButton.spec.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { cleanup, render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event' +import { CopyButton } from '../../app/views/common/copy/CopyButton'; + +afterEach(cleanup); +const renderCopyButton = (args?: any): any => { + const copyProps = { + handleOnClick:jest.fn(), + isIconButton: true + } + + return render( + + ); +} + +// eslint-disable-next-line no-console +console.warn = jest.fn() + +describe('Tests copy button component', () => { + it('Renders copy button without crashing', () => { + renderCopyButton(); + userEvent.click(screen.getByRole('button')); + expect(screen.getByTitle('Copied')).toBeDefined(); + }) +}) \ No newline at end of file diff --git a/src/tests/components/FeedbackButton.spec.tsx b/src/tests/components/FeedbackButton.spec.tsx index 09559925d..6937ee138 100644 --- a/src/tests/components/FeedbackButton.spec.tsx +++ b/src/tests/components/FeedbackButton.spec.tsx @@ -31,7 +31,7 @@ jest.mock('../../app/views/query-runner/request/feedback/FeedbackForm.tsx', () = describe('Tests Feedback button', () => { it('Renders feedback button without crashing', () => { const { getByText } = renderFeedbackButton(); - expect(getByText(/Feedback/)).toBeDefined(); + expect(getByText(/Help Improve Graph Explorer/)).toBeDefined(); }); }) \ No newline at end of file diff --git a/src/tests/components/Image.spec.tsx b/src/tests/components/Image.spec.tsx new file mode 100644 index 000000000..3c8531e5f --- /dev/null +++ b/src/tests/components/Image.spec.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { cleanup, render } from '@testing-library/react'; +import { Image } from '../../app/views/common/image/Image'; + +afterEach(cleanup); +const renderImage = () => { + const body = new Blob([], { type: 'image/jpeg' }); + const alt = 'Sample Image'; + const styles = {}; + const props = { body, alt, styles }; + return render(); +} +// eslint-disable-next-line no-console +console.warn = jest.fn() + +describe('Tests image rendering', () => { + it('Renders image', () => { + const { getByAltText } = renderImage(); + getByAltText('Sample Image'); + }); +}) \ No newline at end of file diff --git a/src/tests/components/Profile.spec.tsx b/src/tests/components/Profile.spec.tsx new file mode 100644 index 000000000..603c6553c --- /dev/null +++ b/src/tests/components/Profile.spec.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { cleanup, render } from '@testing-library/react'; +import Profile from '../../app/views/authentication/profile/Profile'; +import { Mode } from '../../types/enums'; +afterEach(cleanup); +const renderProfile = () => { + return render(); +} + +jest.mock('react-redux', () => ({ + useDispatch: jest.fn(), + useSelector: jest.fn( () => { + return( + { + sidebarProperties: { + mobileScreen: false, + showSidebar: true + }, + profile: { + ageGroup: 0, + displayName: 'Megan Bowen', + emailAddress: 'megan. Bowen@microsoft.com', + profileImageUrl: 'https://www.microsoft.com', + profileType: 'AAD' + }, + authToken: { + token: '' + }, + graphExplorerMode: Mode.Complete + } + ) + }) +})) + +// eslint-disable-next-line no-console +console.warn = jest.fn() + +describe('Tests Profile.tsx', () => { + it('Renders Profile.tsx', () => { + const { getByText } = renderProfile(); + getByText('Megan Bowen'); + }); +}) \ No newline at end of file diff --git a/src/tests/components/ResponseDisplay.spec.tsx b/src/tests/components/ResponseDisplay.spec.tsx new file mode 100644 index 000000000..8427eb718 --- /dev/null +++ b/src/tests/components/ResponseDisplay.spec.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { cleanup, render } from '@testing-library/react'; +import ResponseDisplay from '../../app/views/query-response/response/ResponseDisplay'; + +afterEach(cleanup); +const renderResponseDisplay = (properties: any): any => { + const {contentType, body, height} = properties; + return render( + + ) +} + +// eslint-disable-next-line no-console +console.warn = jest.fn() + +describe('Tests Response display of different formats', () => { + it('Renders xml response type', ()=> { + // Arrange + const properties = { + contentType: ['application/xml'], + body: 'test', + height: 60 + } + + // Act + renderResponseDisplay(properties); + }); + + it('Renders html response type', () => { + // Arrange + const properties = { + contentType: ['text/html'], + body: 'test', + height: 60 + } + + // Act + renderResponseDisplay(properties); + }) +}) \ No newline at end of file diff --git a/src/tests/components/ResponseHeaders.spec.tsx b/src/tests/components/ResponseHeaders.spec.tsx new file mode 100644 index 000000000..8d006ca85 --- /dev/null +++ b/src/tests/components/ResponseHeaders.spec.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import { cleanup, render } from '@testing-library/react'; +import ResponseHeaders from '../../app/views/query-response/headers/ResponseHeaders'; + +afterEach(cleanup); +const renderResponseHeaders = (): any => { + return render( + + ) +} + +jest.mock('react-redux', () => ({ + useSelector: jest.fn( () => { + return({ + dimensions: { + request:{ + height: 60, + width: 60 + }, + response: { + height: 60, + width: 60 + } + }, + graphResponse: { + body: {}, + headers: { + 'Content-Type': 'application/json', + 'cache-control': 'public' + } + }, + responseAreaExpanded: false, + sampleQuery: { + selectedVerb: 'GET', + selectedVersion: 'v1', + sampleUrl: 'https://graph.microsoft.com/v1.0/me', + sampleHeaders: [] + } + }) + }) +})) + + +// eslint-disable-next-line no-console +console.warn = jest.fn() + +jest.mock('../../app/views/common/dimensions-adjustment.ts', () => { + return { + convertVhToPx: jest.fn(() => { + return 60 + }), + getResponseHeight: jest.fn(() => { + return 60 + }) + } +}) + +describe('Tests Response Headers', () => { + it('Renders response headers', () => { + renderResponseHeaders(); + }) +}) \ No newline at end of file diff --git a/src/tests/components/ResponseMessages.spec.tsx b/src/tests/components/ResponseMessages.spec.tsx new file mode 100644 index 000000000..71c17ecd1 --- /dev/null +++ b/src/tests/components/ResponseMessages.spec.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { cleanup, render} from '@testing-library/react'; +import { responseMessages } from '../../app/views/app-sections/ResponseMessages'; + +afterEach(cleanup); +const renderResponseMessages = (): any => { + const graphResponse = { + body: { + '@odata.nextLink': 'https://graph.microsoft.com/v1.0/me/messages?$skip=18', + contentDownloadUrl: 'https://www.microsoft.com', + isWorkaround: true + }, + headers: {} + }; + + const sampleQuery = { + selectedVerb: 'GET', + selectedVersion: 'v1', + sampleUrl: 'https://graph.microsoft.com/v1.0/me', + sampleHeaders: [] + }; + + const dispatch = jest.fn(); + + return render( +
+ {responseMessages(graphResponse, sampleQuery, dispatch)} +
+ ) +} + +// eslint-disable-next-line no-console +console.warn = jest.fn() + +describe('Renders response messages', () => { + it('Renders the response messages', () => { + renderResponseMessages(); + }) +}) \ No newline at end of file diff --git a/src/tests/components/Settings.spec.tsx b/src/tests/components/Settings.spec.tsx new file mode 100644 index 000000000..348582896 --- /dev/null +++ b/src/tests/components/Settings.spec.tsx @@ -0,0 +1,79 @@ +import React from 'react'; +import { cleanup, render, screen } from '@testing-library/react'; +import Settings from '../../app/views/settings/Settings'; +import { ISettingsProps } from '../../types/settings'; +import { messages_ } from '../utils/get-messages'; +import { IntlProvider } from 'react-intl'; +import { geLocale } from '../../appLocale'; +import userEvent from '@testing-library/user-event' + +afterEach(cleanup); +const renderSettings = (args? : any) => { + const messages = (messages_ as { [key: string]: object })['en-US']; + const settingsProps: ISettingsProps = { + actions: { + signOut: jest.fn(), + changeTheme: jest.fn(), + consentToScopes: jest.fn() + }, + intl: { + message: messages + } + } + + return render( + + + + ) +} + +jest.mock('react-redux', () => { + return{ + useDispatch: jest.fn(), + useSelector: jest.fn( () => { + return( + { + permissionsPanelOpen: false, + authToken: { + pending: false, + token: true + }, + theme: 'dark' + } + ) + }) + } +}) + +// eslint-disable-next-line no-console +console.warn = jest.fn() + +jest.mock('../../app/views/query-runner/request/permissions/Permission.tsx', () => { + return { + __esModule: true, + // eslint-disable-next-line react/display-name + default: () => { + return
Permissions
; + } + } +}) + +jest.mock('@microsoft/applicationinsights-react-js', () => ({ + // eslint-disable-next-line react/display-name + withAITracking: () => React.Component, + ReactPlugin: Object +})) + +describe('Tests Settings component', () => { + describe('Tests settings button', () => { + it('Renders Settings component without crashing', () => { + renderSettings(); + const settingsButton = screen.getByRole('button'); + userEvent.click(settingsButton); + }) + }); +}) \ No newline at end of file diff --git a/src/tests/components/StatusMessages.spec.tsx b/src/tests/components/StatusMessages.spec.tsx new file mode 100644 index 000000000..7cdf0c225 --- /dev/null +++ b/src/tests/components/StatusMessages.spec.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { cleanup, render, screen } from '@testing-library/react'; +import statusMessages from '../../app/views/app-sections/StatusMessages'; + +afterEach(cleanup) +const renderStatusMessage = () => { + return render( +
+ {statusMessages()} +
+ ) +} + +jest.mock('react-redux', () => { + return ({ + useDispatch: jest.fn(), + useSelector: jest.fn(() => { + return { + queryRunnerStatus: { + messageType: 1, + ok: true, + status: 200, + statusText: 'OK', + duration: 200 + }, + sampleQuery: { + selectedVerb: 'GET', + selectedVersion: 'v1.0', + sampleUrl: 'https://graph.microsoft.com/v1.0/me', + sampleHeaders: [] + } + } + }) + }) +}) + +// eslint-disable-next-line no-console +console.warn = jest.fn() + +describe('Renders the status bar', () =>{ + it('Renders the status bar', () => { + renderStatusMessage(); + expect(screen.getByRole('alert')).toBeDefined(); + expect(screen.getByTitle('Close')).toBeDefined(); + }) +}) \ No newline at end of file diff --git a/src/tests/components/auth/Auth.spec.tsx b/src/tests/components/auth/Auth.spec.tsx new file mode 100644 index 000000000..c73ec746e --- /dev/null +++ b/src/tests/components/auth/Auth.spec.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { act, cleanup, render, screen } from '@testing-library/react'; +import { Auth } from '../../../app/views/query-runner/request/auth/Auth'; + +afterEach(cleanup); +const renderAuthSection = () => { + return render( + + ) +} + +jest.mock('react-redux', () => { + return{ + useSelector: jest.fn(() => { + return { + authToken: 'JDJDJSKJDKS', + dimensions: { + request: { + height: 60, + width: 60 + }, + response: { + height: 60, + width: 60 + } + } + } + }) + } +}) + + +jest.mock('@microsoft/applicationinsights-react-js', () => ({ + // eslint-disable-next-line react/display-name + withAITracking: () => React.Component, + ReactPlugin: Object +})) + +// eslint-disable-next-line no-console +console.warn = jest.fn() + +// eslint-disable-next-line no-console +console.error = jest.fn(); + +jest.mock('../../../app/views/common/dimensions-adjustment.ts', () => { + return { + convertVhToPx: jest.fn(() => { + return 60 + }), + getResponseHeight: jest.fn(() => { + return 60 + }) + } +}) + +describe('Tests Auth component', () => { + it('Renders the Auth component without crashing', () => { + act(() => { + expect(renderAuthSection()).toBeDefined(); + expect(screen.getByRole('alert')).toBeDefined(); + }) + }) +}) \ No newline at end of file diff --git a/src/tests/components/history/History.spec.tsx b/src/tests/components/history/History.spec.tsx new file mode 100644 index 000000000..082546743 --- /dev/null +++ b/src/tests/components/history/History.spec.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { cleanup, render, screen } from '@testing-library/react'; +import {History} from '../../../app/views/sidebar/history/History'; +import { IHistoryProps } from '../../../types/history'; +import { geLocale } from '../../../appLocale'; +import {messages_} from '../../utils/get-messages'; +import { IntlProvider } from 'react-intl'; + +interface IExtendedHistory extends IHistoryProps { + intl: object; +} +afterEach(cleanup); +const renderHistoryTab = (args?: any) => { + const messages = messages_['en-US']; + const historyProps: IExtendedHistory = { + history: [ + { + url: 'https://graph.microsoft.com/v1.0/me', + method: 'GET', + headers: [], + body: '', + createdAt: '484848', + status: 200, + duration: 200 + } + ], + intl: { messages } + } + const _messages = (messages_ as { [key: string]: object })[geLocale]; + + const allProps = { ...args, ...historyProps}; + + return render( + + + + ) +} + +// eslint-disable-next-line no-console +console.warn = jest.fn(); +console.error = jest.fn(); + +jest.mock('@microsoft/applicationinsights-react-js', () => ({ + // eslint-disable-next-line react/display-name + withAITracking: () => React.Component, + ReactPlugin: Object +})) + +describe('Tests History Tab', () => { + it('Renders history tab without crashing', () => { + renderHistoryTab(); + expect(screen.getByRole('searchbox')).toBeDefined(); + screen.getByText(/Older/); + }) +}) \ No newline at end of file diff --git a/src/tests/components/history/har-utils.spec.ts b/src/tests/components/history/har-utils.spec.ts new file mode 100644 index 000000000..a466edf4b --- /dev/null +++ b/src/tests/components/history/har-utils.spec.ts @@ -0,0 +1,65 @@ +/* eslint-disable @typescript-eslint/indent */ +import { createHarPayload, generateHar } from '../../../app/views/sidebar/history/har-utils'; +import { IHarPayload } from '../../../types/har'; +import { IHistoryItem } from '../../../types/history'; + +describe('Tests history items util functions', () => { + it('creates har payload', () => { + const historyItem: IHistoryItem = { + index: 0, + statusText: 'OK', + responseHeaders: [], + result: {}, + duration: 0, + method: 'GET', + url: 'http://localhost:8080/', + status: 200, + body: '', + headers: [], + createdAt: '3232' + } + + // Act + const harPayload = createHarPayload(historyItem); + + // Assert + expect(harPayload.method).toBe('GET'); + + }) + + it('generates Har', () => { + const payloads: IHarPayload[] = [ + { + startedDateTime: '2020-04-01T00:00:00.000Z', + time: 0, + method: 'GET', + url: 'http://localhost:8080/', + cookies: [], + queryString: [{ name: '', value: '' }], + status: 200, + statusText: 'OK', + content: { + text: 'Some text', + size: 9, + mimeType: 'application/json' + }, + request: { + headers: [] + }, + response: { + headers: [] + }, + sendTime: 0, + waitTime: 0, + receiveTime: 0, + httpVersion: 'HTTP/1.1' + } + ] + + // Act + const har = generateHar(payloads); + + // Assert + expect(har.log.entries.length).toBe(1); + }) +}) \ No newline at end of file diff --git a/src/tests/components/history/history-utils.spec.ts b/src/tests/components/history/history-utils.spec.ts new file mode 100644 index 000000000..0c306640e --- /dev/null +++ b/src/tests/components/history/history-utils.spec.ts @@ -0,0 +1,75 @@ +import { + readHistoryData, removeHistoryData, + bulkRemoveHistoryData, writeHistoryData +} from '../../../app/views/sidebar/history/history-utils'; +import { IHistoryItem } from '../../../types/history'; + +describe('Tests history utils', () => { + it('Returns history data', async () => { + const historyItem: IHistoryItem = { + index: 0, + statusText: 'OK', + responseHeaders: [], + result: {}, + url: 'https://api.github.com/search/users?q=tom', + createdAt: '2020-04-01T00:00:00.000Z', + method: 'GET', + headers: [], + duration: 200, + status: 200 + } + await writeHistoryData(historyItem); + const historyData = await readHistoryData(); + expect(historyData).toBeDefined(); + }); + + it('Removes history data', async () => { + const historyItem: IHistoryItem = { + index: 0, + statusText: 'OK', + responseHeaders: [], + result: {}, + url: 'https://api.github.com/search/users?q=tom', + createdAt: '2020-04-01T00:00:00.000Z', + method: 'GET', + headers: [], + duration: 200, + status: 200 + } + await writeHistoryData(historyItem); + const result = await removeHistoryData(historyItem); + expect(result).toBeTruthy(); + }); + + it('Removes bulk of history data and returns undefined because history data is unavailable', async () => { + const historyData = [ + { + index: -1, + statusText: 'OK', + responseHeaders: [], + result: {}, + url: 'https://api.github.com/search/users?q=tom', + createdAt: '2020-04-01T00:00:00.000Z', + method: 'GET', + headers: [], + duration: 200, + status: 200 + }, + { + index: -1, + statusText: 'OK', + responseHeaders: [], + result: {}, + url: 'https://api.github.com/search/users?q=tom', + createdAt: '2020-04-01T00:00:00.000Z', + method: 'GET', + headers: [], + duration: 200, + status: 200 + } + ] + const listOfKeys = historyData.map(item => item.createdAt); + const result = await bulkRemoveHistoryData(listOfKeys); + expect(result).toBeUndefined(); + }); +}) \ No newline at end of file diff --git a/src/tests/components/permissions/Permission.spec.tsx b/src/tests/components/permissions/Permission.spec.tsx new file mode 100644 index 000000000..b5be07174 --- /dev/null +++ b/src/tests/components/permissions/Permission.spec.tsx @@ -0,0 +1,119 @@ +import React from 'react'; +import { cleanup, render , screen} from '@testing-library/react'; +import {Permission} from '../../../app/views/query-runner/request/permissions/Permission'; +import { IPermissionProps, IPermissionState } from '../../../types/permissions'; +import { Provider } from 'react-redux'; +import { IntlProvider } from 'react-intl'; +import { geLocale } from '../../../appLocale'; +import messages from '../../../messages'; +import { store } from '../../../../src/store'; + +afterEach(cleanup); + +interface IExtendedPermissions extends IPermissionProps{ + intl: object; +} +const renderPermission = (args?: any) => { + const permissionProps : IExtendedPermissions = { + dimensions: { + request:{ + width: '60', + height: '60' + }, + response:{ + width: '60', + height: '60' + } + }, + scopes: { + pending: false, + data: [ + { + value: 'profile.read', + isAdmin: false, + consentDescription: 'Read your profile', + consented: true + }, + { + value: 'profile.write', + isAdmin: false, + consentDescription: 'Write your profile', + consented: true + } + ], + hasUrl: true, + error: null + }, + panel: args?.panel || false, + sample: [ + { + selectedVerb: 'GET', + selectedVersion: 'v1.0', + sampleUrl: 'https://graph.microsoft.com/v1.0/me', + sampleHeaders: [] + } + ], + tokenPresent: true, + permissionsPanelOpen: args?.permissionsPanelOpen || false, + consentedScopes: ['profile.read', 'profile.write', 'mail.read', 'mail.write'], + setPermissions: jest.fn(), + ...args, + intl: { messages }, + actions: { + fetchScopes: jest.fn(), + consentToScopes: jest.fn() + } + } + + const permissionState : IPermissionState = { + permissions: [ + { + value: 'profile.read', + isAdmin: false, + consentDescription: 'Read your profile', + consented: true + }, + { + value: 'profile.write', + isAdmin: false, + consentDescription: 'Write your profile', + consented: true + } + ] + } + + const allProps = { ...permissionProps, ...permissionState }; + const appStore: any = store; + + return render( + + + + + + ) +} +// +jest.mock('@microsoft/applicationinsights-react-js', () => ({ + // eslint-disable-next-line react/display-name + withAITracking: () => React.Component, + ReactPlugin: Object +})) + +// eslint-disable-next-line no-console +console.warn = jest.fn() + +describe('Tests Permission', () => { + it('Renders permissions panel without crashing', () => { + renderPermission({panel: true, permissionsPanelOpen: true}); + screen.getByText(/To try out different Microsoft Graph API endpoints/) + }) + + it('Renders permissions tab without crashing', () => { + renderPermission(); + screen.getByText(/Permissions for the query are missing on this tab/) + }) +}) \ No newline at end of file diff --git a/src/tests/components/permissions/TabList.spec.tsx b/src/tests/components/permissions/TabList.spec.tsx new file mode 100644 index 000000000..ee5fbb694 --- /dev/null +++ b/src/tests/components/permissions/TabList.spec.tsx @@ -0,0 +1,88 @@ +import React from 'react'; +import { cleanup, render } from '@testing-library/react'; +import TabList from '../../../app/views/query-runner/request/permissions/TabList'; +import { IntlProvider } from 'react-intl'; +import { geLocale } from '../../../appLocale'; +import messages from '../../../messages'; + +afterEach(cleanup); +const renderTabList = () => { + const tabProps = { + columns: [ + { + key: 'value', + name: 'messages.Permission', + fieldName: 'value', + minWidth: 200, + maxWidth: 250, + isResizable: true, + columnActionsMode: 0 + }, + { + key: 'isAdmin', + isResizable: true, + name: 'Admin consent required', + fieldName: 'isAdmin', + minWidth: 200, + maxWidth: 300, + ariaLabel: 'Administrator permission', + columnActionsMode: 0 + } + ], + classes: '', + renderItemColumn: jest.fn(), + renderDetailsHeader: jest.fn(), + maxHeight: '100' + } + + return render( + + + + ) +} + +jest.mock('react-redux', () => { + return{ + useSelector: jest.fn(() => { + return({ + consentedScopes: ['profile.read', 'profile.write', 'mail.read', 'mail.write'], + scopes: { + pending: false, + data: [ + { + value: 'profile.read', + isAdmin: false, + consentDescription: 'Read your profile', + consented: true + }, + { + value: 'profile.write', + isAdmin: false, + consentDescription: 'Write your profile', + consented: true + } + ] + }, + authToken: { + pending: false, + token: true + } + }) + }), + useDispatch: jest.fn() + } +}) + +// eslint-disable-next-line no-console +console.warn = jest.fn() + +describe('Renders permissions tab', () => { + it('Renders Modify Permissions Tab without crasing', () => { + const { getByText } = renderTabList(); + getByText(/Permissions for the query are missing on this tab/) + }) +}) \ No newline at end of file diff --git a/src/tests/components/query-runner/Autocomplete.spec.tsx b/src/tests/components/query-runner/Autocomplete.spec.tsx new file mode 100644 index 000000000..6ee9005d9 --- /dev/null +++ b/src/tests/components/query-runner/Autocomplete.spec.tsx @@ -0,0 +1,107 @@ +import React from 'react'; +import { cleanup, render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event' +import AutoComplete from '../../../app/views/query-runner/query-input/auto-complete/AutoComplete'; +import { IAutoCompleteProps, IAutoCompleteState } from '../../../types/auto-complete'; +afterEach(cleanup); + +const renderAutoComplete = (args?: any): any => { + const autoCompleteProps: IAutoCompleteProps = { + suggestions: ['sugggestion 1', 'sugggestion 2', 'sugggestion 3'], + contentChanged: jest.fn(), + runQuery: jest.fn(), + sampleQuery: { + selectedVerb: 'GET', + selectedVersion: 'v1', + sampleUrl: 'https://graph.microsoft.com/v1.0/me', + sampleHeaders: [] + }, + fetchingSuggestions: false, + autoCompleteError: null, + autoCompleteOptions: { + url: 'https://graph.microsoft.com/v1.0/me', + parameters: [] + }, + actions: { + fetchAutoCompleteOptions: jest.fn() + } + } + + const autocompleteState : IAutoCompleteState = { + activeSuggestion: 0, + filteredSuggestions: [], + suggestions: ['sugggestion 1', 'sugggestion 2', 'sugggestion 3'], + showSuggestions: true, + userInput: '/', + compare: '', + queryUrl: 'https://graph.microsoft.com/v1.0/me', + multiline: false + } + + const allProps = {...autoCompleteProps, ...autocompleteState , ...args}; + return render( + + ); +} + +jest.mock('react-redux', () => { + return{ + useDispatch: jest.fn(), + connect: jest.fn( + // eslint-disable-next-line no-unused-vars +

(_props?: any) => (component: React.ComponentType

) => component + ), + useSelector: jest.fn(() => { + return({ + autoComplete: { + data: { + url: 'https://graph.microsoft.com/v1.0/me', + parameters: [ + { + verb: 'GET', + values: [ + { + name: '$select', + items: [] + } + ], + links: ['link1', 'link2'] + } + ], + createdAt: '364763737373' + } + }, + sampleQuery: { + selectedVerb: 'GET', + selectedVersion: 'v1', + sampleUrl: 'https://graph.microsoft.com/v1.0/me', + sampleHeaders: [] + }, + queryRunnerStatus: { + mwssageType: 0, + ok: true, + status: 200, + duration: 300 + }, + samples: { + queries: [], + pending: false, + error: null + } + }) + }) + } +}) + +// eslint-disable-next-line no-console +console.warn = jest.fn(); +// eslint-disable-next-line no-console +console.error = jest.fn(); + +describe('Tests AutoComplete render', () => { + it('Renders autocomplete suggestions without crashing', () => { + renderAutoComplete(); + screen.getByRole('textbox'); + userEvent.type(screen.getByRole('textbox'), 'sugggestion 1'); + }) +}) \ No newline at end of file diff --git a/src/tests/components/query-runner/HintList.spec.tsx b/src/tests/components/query-runner/HintList.spec.tsx new file mode 100644 index 000000000..6df6a4d15 --- /dev/null +++ b/src/tests/components/query-runner/HintList.spec.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { cleanup, render } from '@testing-library/react'; +import { HintList } from '../../../app/views/query-runner/query-input/auto-complete/suffix/HintList'; +import { IHint } from '../../../app/views/query-runner/query-input/auto-complete/suffix/suffix-util'; + +afterEach(cleanup); +const renderHintList = () : any => { + const hints: IHint[] = [ + { + link: { + url: 'https://www.microsoft.com/office', + name: 'MS' + }, + description: 'Sample hint suggestion' + } + ] + return render( + + ) +} + +// eslint-disable-next-line no-console +console.warn = jest.fn() +describe('Tests hint suggestions', () => { + it('Renders hint list without crashing', () => { + renderHintList(); + }) +}) \ No newline at end of file diff --git a/src/tests/components/query-runner/QueryInput.spec.tsx b/src/tests/components/query-runner/QueryInput.spec.tsx new file mode 100644 index 000000000..ec2785537 --- /dev/null +++ b/src/tests/components/query-runner/QueryInput.spec.tsx @@ -0,0 +1,81 @@ +import React from 'react'; +import { cleanup, render } from '@testing-library/react'; +import IntlQueryInput from '../../../app/views/query-runner/query-input/QueryInput'; +import { IQueryInputProps } from '../../../types/query-runner'; +import { Mode } from '../../../types/enums'; +import { messages_ } from '../../utils/get-messages'; +import { IntlProvider } from 'react-intl'; +import { geLocale } from '../../../appLocale'; +import messages from '../../../messages'; + +afterEach(cleanup); +const renderQueryInput = (args?: any): any => { + const queryInputProps: IQueryInputProps = { + handleOnRunQuery: jest.fn(), + handleOnMethodChange: jest.fn(), + handleOnUrlChange: jest.fn(), + handleOnVersionChange: jest.fn(), + handleOnBlur: jest.fn(), + sampleQuery: { + selectedVerb: 'GET', + selectedVersion: 'v1', + sampleUrl: 'https://graph.microsoft.com/v1.0/me', + sampleHeaders: [] + }, + submitting: false, + authenticated: true, + mode: Mode.Complete, + intl: { + message: messages_ + }, + actions: { + setSampleQuery: jest.fn() + } + } + + const allProps = {...queryInputProps, ...args}; + return render( + + + + ); +} + +jest.mock('../../../app/views/query-runner/query-input/auto-complete/AutoComplete.tsx') + +// eslint-disable-next-line no-console +console.warn = jest.fn(); + +jest.mock('react-redux', () => { + return{ + useDispatch: jest.fn(), + connect: jest.fn( + // eslint-disable-next-line no-unused-vars +

(_props?: any) => (component: React.ComponentType

) => component + ), + useSelector: jest.fn(() => { + return({ + sampleQuery: { + selectedVerb: 'GET', + selectedVersion: 'v1.0', + sampleUrl: 'https://graph.microsoft.com/v1.0/me', + sampleHeaders: [] + }, + authToken: { + pending: false, + token: true + }, + isLoadingData: false + }) + }) + } +}) + +describe('Renders QueryInput component without crashing', () => { + it('renders without crashing', () => { + renderQueryInput(); + }); +}) \ No newline at end of file diff --git a/src/tests/components/query-runner/QueryRunner.spec.tsx b/src/tests/components/query-runner/QueryRunner.spec.tsx new file mode 100644 index 000000000..b51cd09b7 --- /dev/null +++ b/src/tests/components/query-runner/QueryRunner.spec.tsx @@ -0,0 +1,114 @@ +import React from 'react'; +import { cleanup, render } from '@testing-library/react'; +import { QueryRunner } from '../../../app/views/query-runner/QueryRunner'; +import { IQueryRunnerProps, IQueryRunnerState } from '../../../types/query-runner'; +import { IntlProvider } from 'react-intl'; +import { geLocale } from '../../../appLocale'; +import messages from '../../../messages'; +import { Mode } from '../../../types/enums'; +afterEach(cleanup); +const renderQueryRunner = (args?: any): any => { + const queryRunnerProps: IQueryRunnerProps = { + headers: [], + onSelectVerb: jest.fn(), + sampleQuery: { + selectedVerb: 'GET', + selectedVersion: 'v1', + sampleUrl: 'https://graph.microsoft.com/v1.0/me', + sampleHeaders: [] + }, + actions: { + runQuery: jest.fn(), + addRequestHeader: jest.fn(), + setSampleQuery: jest.fn(), + setQueryResponseStatus: jest.fn() + } + } + + const queryRunnerState: IQueryRunnerState = { + url: 'https://graph.microsoft.com/v1.0/me' + } + + const allProps = {...queryRunnerState, ...queryRunnerProps, ...args}; + return render( + + + + ); +} + +jest.mock('react-redux', () => { + return{ + useDispatch: jest.fn(), + connect: jest.fn( + // eslint-disable-next-line no-unused-vars +

(_props?: any) => (component: React.ComponentType

) => component + ), + useSelector: jest.fn( () => { + return({ + sampleQuery: { + selectedVerb: 'GET', + selectedVersion: 'v1', + sampleUrl: 'https://graph.microsoft.com/v1.0/me', + sampleHeaders: [] + }, + authToken: { + pending: false, + token: true + }, + isLoadingData: false, + submitting: false, + authenticated: false, + mode: Mode.Complete, + intl: { + message: messages + }, + dimensions: { + request: { + width: 10, + height: 10 + }, + response: { + width: 10, + height: 10 + } + } + }) + }) + } +}) + +// eslint-disable-next-line react/display-name +jest.mock('../../../app/views/query-runner/query-input/QueryInput.tsx'); + +jest.mock('../../../app/views/query-runner/request/Request.tsx'); + +// eslint-disable-next-line no-console +console.warn = jest.fn() + +jest.mock('@microsoft/applicationinsights-react-js', () => ({ + // eslint-disable-next-line react/display-name + withAITracking: () => React.Component, + ReactPlugin: Object +})) + +jest.mock('@ms-ofb/officebrowserfeedbacknpm/scripts/app/Window/Window', () => ({ + OfficeBrowserFeedback: Object +})) + +jest.mock('@ms-ofb/officebrowserfeedbacknpm/Floodgate', () => ({ + makeFloodgate: Object +})) + +jest.mock('@ms-ofb/officebrowserfeedbacknpm/scripts/app/Configuration/IInitOptions', () => ({ + AuthenticationType: 0 +})) + +describe('Tests QueryRunner', () => { + it('Renders query runner without crashing', () => { + expect(renderQueryRunner()).toBeDefined(); + }) +}) \ No newline at end of file diff --git a/src/tests/components/query-runner/SuggestionsList.spec.tsx b/src/tests/components/query-runner/SuggestionsList.spec.tsx new file mode 100644 index 000000000..f79bd2374 --- /dev/null +++ b/src/tests/components/query-runner/SuggestionsList.spec.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { cleanup, render } from '@testing-library/react'; +import SuggestionsList from '../../../app/views/query-runner/query-input/auto-complete/SuggestionsList'; + +afterEach(cleanup); +const renderSuggestionsList = (args?: any): any => { + const defaultProps = { + filteredSuggestions: [], + activeSuggestion: 0, + onClick: jest.fn() + }; + const props = {...args, ...defaultProps}; + return render(); +} + +// eslint-disable-next-line no-console +console.warn = jest.fn() + +describe('Tests SuggestionsList component', () => { + it('Renders suggestionslist without crashing', () => { + renderSuggestionsList(); + }) +}) \ No newline at end of file diff --git a/src/tests/components/query-runner/cache-provider.spec.ts b/src/tests/components/query-runner/cache-provider.spec.ts new file mode 100644 index 000000000..98a0f2877 --- /dev/null +++ b/src/tests/components/query-runner/cache-provider.spec.ts @@ -0,0 +1,45 @@ +import { getSuggestionsFromCache, storeSuggestionsInCache } from '../../../modules/suggestions/cache-provider'; +import { IParsedOpenApiResponse } from '../../../types/open-api'; + +describe('Tests cache provider utils for suggestions', () => { + it('Returns options from local storage which is null because suggestions are expired', async () => { + const content_ = { + url: 'https://api.github.com/search/users?q=tom', + createdAt: '2020-04-01T00:00:00.000Z', + options: [ + { + name: 'Tom', + value: 'tom' + }, + { + name: 'Tommy', + value: 'tommy' + } + ] + }; + + const openApiContent: IParsedOpenApiResponse = { + url: 'https://api.github.com/search/users?q=tom', + parameters: [ + { + verb: 'GET', + values: [ + { + name: 'q', + items: ['tom', 'jerry'] + } + ], + links: [] + } + ], + createdAt: '2020-04-01T00:00:00.000Z' + } + + const version = 'v1'; + await storeSuggestionsInCache(openApiContent, version); + return getSuggestionsFromCache(content_.url) + .then((data) => { + expect(data).toBeNull(); + }) + }); +}) \ No newline at end of file diff --git a/src/tests/components/query-runner/suggestions.spec.ts b/src/tests/components/query-runner/suggestions.spec.ts new file mode 100644 index 000000000..b5e711dfc --- /dev/null +++ b/src/tests/components/query-runner/suggestions.spec.ts @@ -0,0 +1,37 @@ +import { suggestions } from '../../../modules/suggestions/suggestions'; + +describe('Tests suggestions fetching ', () => { + beforeEach(() => { + // eslint-disable-next-line no-undef + fetchMock.resetMocks(); + }); + it('Returns null when getSuggestions fails', () => { + const url = 'https://api.github.com/search/users?q=tom'; + const api = 'https://api.github.com'; + const version = 'v1'; + return suggestions.getSuggestions(url, api, version) + .then((data) => { + expect(data).toBeNull(); + }) + .catch((e: Error) => { throw e }); + }) + + it('Returns defined data when correct response is received', () => { + fetchMock.mockResponse(JSON.stringify({ + ok: true, + status: 200, + paths: { + 1: '/users', + requestUrl: '/users/$count' + } + })); + const url = 'https://test_url'; + const api = 'https://test_api'; + const version = 'v1'; + return suggestions.getSuggestions(url, api, version) + .then((data) => { + expect(data).toBeDefined(); + }) + .catch((e: Error) => { throw e }) + }) +}) \ No newline at end of file diff --git a/src/tests/components/request/headers/HeadersList.spec.tsx b/src/tests/components/request/headers/HeadersList.spec.tsx new file mode 100644 index 000000000..fd886171c --- /dev/null +++ b/src/tests/components/request/headers/HeadersList.spec.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { cleanup, render } from '@testing-library/react'; +import HeadersList from '../../../../app/views/query-runner/request/headers/HeadersList'; +import { Header } from '../../../../types/query-runner'; + +afterEach(cleanup); +const renderHeadersList = () => { + const headers : Header[] = [ + { + name: 'Content-Type', + value: 'application/json' + }, + { + name: 'consistencyLevel', + value: 'eventual' + } + ] + return render( + + ) +} + +// eslint-disable-next-line no-console +console.warn = jest.fn() + +describe('Tests HeadersList component', () => { + it('Renders HeadersList without crashing', () => { + const { getAllByText } = renderHeadersList(); + getAllByText(/Key/); + }) +}) \ No newline at end of file diff --git a/src/tests/components/request/headers/Request.spec.tsx b/src/tests/components/request/headers/Request.spec.tsx new file mode 100644 index 000000000..7851b73c3 --- /dev/null +++ b/src/tests/components/request/headers/Request.spec.tsx @@ -0,0 +1,120 @@ +import React from 'react'; +import { cleanup, render } from '@testing-library/react'; +import { Request } from '../../../../app/views/query-runner/request/Request'; +import { Mode } from '../../../../types/enums'; +import { messages_ } from '../../../utils/get-messages' +import { IRequestComponent } from '../../../../types/request'; +import { IntlProvider } from 'react-intl'; +import { geLocale } from '../../../../appLocale'; + +afterEach(cleanup); +const renderRequest = (): any => { + const messages = (messages_ as { [key: string]: object })[geLocale]; + + const requestProps : IRequestComponent = { + sampleQuery: { + selectedVerb: 'GET', + selectedVersion: 'v1', + sampleUrl: 'https://graph.microsoft.com/v1.0/me', + sampleHeaders: [] + }, + mode: Mode.Complete, + handleOnEditorChange: jest.fn(), + dimensions: { + request: { + width: '60px', + height: '60px' + }, + response: { + width: '60px', + height: '60px' + }, + sidebar: { + width: '60px', + height: '60px' + }, + content: { + width: '60px', + height: '60px' + } + }, + intl: { + messages + }, + actions: { + setDimensions: jest.fn() + }, + officeBrowserFeedback: {}, + enableShowSurvey: true + } + return render( + + + + + ) +} + +// eslint-disable-next-line no-console +console.warn = jest.fn() + +jest.mock('@microsoft/applicationinsights-react-js', () => ({ + // eslint-disable-next-line react/display-name + withAITracking: () => React.Component, + ReactPlugin: Object +})) + +jest.mock('@ms-ofb/officebrowserfeedbacknpm/scripts/app/Window/Window', () => ({ + OfficeBrowserFeedback: Object +})) + +jest.mock('@ms-ofb/officebrowserfeedbacknpm/Floodgate', () => ({ + makeFloodgate: Object +})) + +jest.mock('@ms-ofb/officebrowserfeedbacknpm/scripts/app/Configuration/IInitOptions', () => ({ + AuthenticationType: 0 +})) + +jest.mock('react-redux', () => { + return{ + useSelector: jest.fn(() => { + return({ + dimensions: { + request: { + width: '60', + height: '60' + }, + response: { + width: '60', + height: '60' + } + }, + sampleQuery: { + selectedVerb: 'GET', + selectedVersion: 'v1', + sampleUrl: 'https://graph.microsoft.com/v1.0/me', + sampleHeaders: [] + } + }) + }), + connect: jest.fn( + // eslint-disable-next-line no-unused-vars +

(_props?: any) => (component: React.ComponentType

) => component + ), + useDispatch: jest.fn() + } +}) + +describe('Tests Request component', () => { + it('Renders Request section without crashing', () => { + const { getByText } = renderRequest(); + getByText(/Request body/); + getByText(/Request headers/); + getByText(/Modify permissions/); + getByText(/Access token/); + }) +}) \ No newline at end of file diff --git a/src/tests/components/request/headers/RequestHeaders.spec.tsx b/src/tests/components/request/headers/RequestHeaders.spec.tsx new file mode 100644 index 000000000..cd245b1cd --- /dev/null +++ b/src/tests/components/request/headers/RequestHeaders.spec.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { cleanup, render } from '@testing-library/react'; +import RequestHeaders from '../../../../app/views/query-runner/request/headers/RequestHeaders'; +import { IntlProvider } from 'react-intl'; +import { geLocale } from '../../../../appLocale'; +import messages from '../../../../messages'; + +afterEach(cleanup); +const renderRequestHeaders = () => { + return render( + + + + ) +} + +jest.mock('react-redux', () => { + return{ + useSelector: jest.fn(() => { + return{ + sampleQuery: { + selectedVerb: 'GET', + selectedVersion: 'v1', + sampleUrl: 'https://graph.microsoft.com/v1.0/me', + sampleHeaders: [] + + }, + dimensions: { + request: { + height: 60, + width: 60 + }, + response: { + height: 60, + width: 60 + } + } + } + }), + useDispatch: jest.fn() + } +}) + +jest.mock('../../../../app/views/common/dimensions-adjustment.ts', () => { + return { + convertVhToPx: jest.fn(() => { + return 60 + }), + getResponseHeight: jest.fn(() => { + return 60 + }) + } +}) + +// eslint-disable-next-line no-console +console.warn = jest.fn() + +describe('Tests RequestHeaders component', () => { + it('Renders the RequestHeaders component without crashing', () => { + renderRequestHeaders(); + }) +}) \ No newline at end of file diff --git a/src/tests/components/resource-explorer/CommandOptions.spec.tsx b/src/tests/components/resource-explorer/CommandOptions.spec.tsx new file mode 100644 index 000000000..d2cffc208 --- /dev/null +++ b/src/tests/components/resource-explorer/CommandOptions.spec.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { cleanup, render } from '@testing-library/react'; +import CommandOptions from '../../../app/views/sidebar/resource-explorer/CommandOptions'; +afterEach(cleanup); +const renderCommandOptions = () => { + return render( + + ) +} +const paths = [ + { + key: '5-{serviceHealth-id}-issues', + url: '/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues', + name: 'issues (1)', + labels: [ + { name: 'v1.0', methods: ['Get', 'Post'] }, + { name: 'beta', methods: ['Get', 'Post'] } + ], + isExpanded: true, + parent: '{serviceHealth-id}', + level: 5, + paths: ['/', 'admin', 'serviceAnnouncement', 'healthOverviews', '{serviceHealth-id}'], + type: 'path', + links: [] + }, { + key: '6-issues-{serviceHealthIssue-id}', + url: '/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues/{serviceHealthIssue-id}', + name: '{serviceHealthIssue-id} (1)', + labels: [ + { name: 'v1.0', methods: ['Get', 'Patch', 'Delete'] }, + { name: 'beta', methods: ['Get', 'Patch', 'Delete'] } + ], + isExpanded: true, + parent: 'issues', + level: 6, + paths: ['/', 'admin', 'serviceAnnouncement', 'healthOverviews', '{serviceHealth-id}', 'issues'], + type: 'path', + links: [] + } +]; + +jest.mock('react-redux', () => { + return{ + useSelector: jest.fn(() =>{ + return({ + resources: paths + }) + }), + useDispatch: jest.fn() + } +}) + +// eslint-disable-next-line no-console +console.warn = jest.fn() + +describe('Tests CommandOptions', () => { + it('Renders command options', () => { + const { getByText } = renderCommandOptions(); + }) +}) \ No newline at end of file diff --git a/src/tests/components/resource-explorer/Paths.spec.tsx b/src/tests/components/resource-explorer/Paths.spec.tsx new file mode 100644 index 000000000..d03102154 --- /dev/null +++ b/src/tests/components/resource-explorer/Paths.spec.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { cleanup, render } from '@testing-library/react'; +import Paths from '../../../app/views/sidebar/resource-explorer/panels/Paths'; +import { IResourceLink, ResourceLinkType } from '../../../types/resources'; + +afterEach(cleanup); +const renderPaths = () => { + const columns = [ + { key: 'url', name: 'Url', fieldName: 'url', minWidth: 300, maxWidth: 350, isResizable: true } + ]; + + const paths : IResourceLink[] = [ + { + key: '5-{serviceHealth-id}-issues', + url: '/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues', + name: 'issues (1)', + labels: [ + { name: 'v1.0', methods: ['Get', 'Post'] }, + { name: 'beta', methods: ['Get', 'Post'] } + ], + isExpanded: true, + parent: '{serviceHealth-id}', + level: 5, + paths: ['/', 'admin', 'serviceAnnouncement', 'healthOverviews', '{serviceHealth-id}'], + type: ResourceLinkType.PATH, + links: [] + }, { + key: '6-issues-{serviceHealthIssue-id}', + url: '/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues/{serviceHealthIssue-id}', + name: '{serviceHealthIssue-id} (1)', + labels: [ + { name: 'v1.0', methods: ['Get', 'Patch', 'Delete'] }, + { name: 'beta', methods: ['Get', 'Patch', 'Delete'] } + ], + isExpanded: true, + parent: 'issues', + level: 6, + paths: ['/', 'admin', 'serviceAnnouncement', 'healthOverviews', '{serviceHealth-id}', 'issues'], + type: ResourceLinkType.PATH, + links: [] + } + ]; + return render( + + ) +} + +// eslint-disable-next-line no-console +console.warn = jest.fn() + +describe('Tests resource paths rendering', () => { + it('Renders resource paths without crashing', () => { + const { getByText } = renderPaths(); + getByText(/Toggle selection for all items/); + }) +}) \ No newline at end of file diff --git a/src/tests/components/resource-explorer/PathsReview.spec.tsx b/src/tests/components/resource-explorer/PathsReview.spec.tsx new file mode 100644 index 000000000..846eb7b8e --- /dev/null +++ b/src/tests/components/resource-explorer/PathsReview.spec.tsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { cleanup, render } from '@testing-library/react'; +import PathsReview from '../../../app/views/sidebar/resource-explorer/panels/PathsReview'; +import { IntlProvider } from 'react-intl'; +import { geLocale } from '../../../appLocale'; +import messages from '../../../messages'; + +afterEach(cleanup); +const renderPathsReview = () => { + return render( + + + + ) +} + +const paths = [ + { + key: '5-{serviceHealth-id}-issues', + url: '/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues', + name: 'issues (1)', + labels: [ + { name: 'v1.0', methods: ['Get', 'Post'] }, + { name: 'beta', methods: ['Get', 'Post'] } + ], + isExpanded: true, + parent: '{serviceHealth-id}', + level: 5, + paths: ['/', 'admin', 'serviceAnnouncement', 'healthOverviews', '{serviceHealth-id}'], + type: 'path', + links: [] + }, { + key: '6-issues-{serviceHealthIssue-id}', + url: '/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues/{serviceHealthIssue-id}', + name: '{serviceHealthIssue-id} (1)', + labels: [ + { name: 'v1.0', methods: ['Get', 'Patch', 'Delete'] }, + { name: 'beta', methods: ['Get', 'Patch', 'Delete'] } + ], + isExpanded: true, + parent: 'issues', + level: 6, + paths: ['/', 'admin', 'serviceAnnouncement', 'healthOverviews', '{serviceHealth-id}', 'issues'], + type: 'path', + links: [] + } +]; + +jest.mock('react-redux', () => { + return{ + useSelector: jest.fn(() =>{ + return({ + resources: paths + }) + }), + useDispatch: jest.fn() + } +}) + +// eslint-disable-next-line no-console +console.warn = jest.fn() + +describe('Tests postman collections panel', () => { + it('Renders the path review section of resource explorer', () => { + const { getByText } = renderPathsReview(); + getByText(/Download postman collection/); + } ) +}) \ No newline at end of file diff --git a/src/tests/components/resource-explorer/ResourceExplorer.spec.tsx b/src/tests/components/resource-explorer/ResourceExplorer.spec.tsx new file mode 100644 index 000000000..6830e71db --- /dev/null +++ b/src/tests/components/resource-explorer/ResourceExplorer.spec.tsx @@ -0,0 +1,89 @@ +import React from 'react'; +import { cleanup, render } from '@testing-library/react'; +import { ResourceExplorer } from '../../../app/views/sidebar/resource-explorer'; +import { IntlProvider } from 'react-intl'; +import { geLocale } from '../../../appLocale'; +import messages from '../../../messages'; + +afterEach(cleanup); +const renderResourceExplorer = () => { + return render( + + + + ) +} + +const paths = [ + { + key: '5-{serviceHealth-id}-issues', + url: '/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues', + name: 'issues (1)', + labels: [ + { name: 'v1.0', methods: ['Get', 'Post'] }, + { name: 'beta', methods: ['Get', 'Post'] } + ], + isExpanded: true, + parent: '{serviceHealth-id}', + level: 5, + paths: ['/', 'admin', 'serviceAnnouncement', 'healthOverviews', '{serviceHealth-id}'], + type: 'path', + links: [] + }, { + key: '6-issues-{serviceHealthIssue-id}', + url: '/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues/{serviceHealthIssue-id}', + name: '{serviceHealthIssue-id} (1)', + labels: [ + { name: 'v1.0', methods: ['Get', 'Patch', 'Delete'] }, + { name: 'beta', methods: ['Get', 'Patch', 'Delete'] } + ], + isExpanded: true, + parent: 'issues', + level: 6, + paths: ['/', 'admin', 'serviceAnnouncement', 'healthOverviews', '{serviceHealth-id}', 'issues'], + type: 'path', + links: [] + } +]; + +jest.mock('react-redux', () => { + return { + useSelector: jest.fn(() => { + return { + resources: { + pending: false, + error: null, + paths, + data: { + segment: '/', + labels: [ + { name: 'v1.0', methods: ['Get', 'Post'] }, + {name: 'beta', methods: ['Get', 'Post']} + ], + children: [ + { + segment: 'accessReviewDecisions', + labels: [ + { name: 'v1.0', methods: ['Get', 'Post'] } + ] + } + ] + } + } + } + }), + useDispatch: jest.fn() + } +}) + +// eslint-disable-next-line no-console +console.warn = jest.fn() + +describe('Tests Resource Explorer', () => { + it('Renders the resource explorer', () => { + const { getByTestId } = renderResourceExplorer(); + }) +}) \ No newline at end of file diff --git a/src/tests/components/response/QueryResponse.spec.tsx b/src/tests/components/response/QueryResponse.spec.tsx new file mode 100644 index 000000000..a34594a49 --- /dev/null +++ b/src/tests/components/response/QueryResponse.spec.tsx @@ -0,0 +1,95 @@ +import React from 'react'; +import { cleanup, render } from '@testing-library/react'; +import QueryResponse from '../../../app/views/query-response/QueryResponse'; +import { IQueryResponseProps } from '../../../types/query-response'; +import { Mode } from '../../../types/enums'; +import { IntlProvider } from 'react-intl'; +import { geLocale } from '../../../appLocale'; +import { messages_ } from '../../utils/get-messages'; +import messages from '../../../messages'; + +afterEach(cleanup); +const renderQueryResponse = () => { + const message = messages_['en-US'] as object; + const queryResponseProps : IQueryResponseProps = { + mode: Mode.Complete, + dispatch: jest.fn(), + graphResponse: { + body: {}, + headers: [] + }, + intl: { + message + }, + verb: 'GET', + theme: 'dark', + scopes: [], + sampleQuery: { + selectedVerb: 'GET', + selectedVersion: 'v1', + sampleUrl: 'https://graph.microsoft.com/v1.0/me', + sampleHeaders: [] + }, + actions: { + getConsent: jest.fn() + }, + mobileScreen: false + } + + return render( + + + ) +} + +jest.mock('@microsoft/applicationinsights-react-js', () => ({ + // eslint-disable-next-line react/display-name + withAITracking: () => React.Component, + ReactPlugin: Object +})) + +jest.mock('react-redux', () => { + return { + useDispatch: () => jest.fn(), + connect: jest.fn( + // eslint-disable-next-line no-unused-vars +

(_props?: any) => (component: React.ComponentType

) => component + ), + useSelector: jest.fn(() => { + return ({ + dimensions: { + request: { + height: '60', + width: '60' + }, + response: { + height: '60', + width: '60' + } + }, + sampleQuery: { + selectedVerb: 'GET', + selectedVersion: 'v1.0', + sampleUrl: 'https://graph.microsoft.com/v1.0/me', + sampleHeaders: [] + }, + graphExplorerMode: Mode.Complete, + graphResponse: { + body: {} + } + }) + }) + }; +}) + +// eslint-disable-next-line no-console +console.warn = jest.fn() + +describe('Renders QuerResponse', () => { + it('Renders QuerResponse without crashing', () => { + renderQueryResponse(); + }) +}) \ No newline at end of file diff --git a/src/tests/sample-queries/SampleQueries.spec.tsx b/src/tests/sample-queries/SampleQueries.spec.tsx new file mode 100644 index 000000000..efcf58193 --- /dev/null +++ b/src/tests/sample-queries/SampleQueries.spec.tsx @@ -0,0 +1,75 @@ +import React from 'react'; +import { cleanup, render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event' +import SampleQueries from '../../app/views/sidebar/sample-queries/SampleQueries'; +import { ISampleQueriesProps } from '../../types/query-runner'; +import { messages_ } from '../utils/get-messages'; +import { geLocale } from '../../appLocale'; +import { IntlProvider } from 'react-intl'; + +afterEach(cleanup); +const renderSampleQueries = () => { + const messages = (messages_ as { [key: string]: object })[geLocale]; + const sampleQueriesProps: ISampleQueriesProps = { + tokenPresent: true, + profile:{}, + samples: { + pending: false, + queries: [ + { + category: 'Getting Started', + method: 'GET', + requestUrl: '/v1.0/me/', + humanName: 'my profile', + docLink: 'https://graph.microsoft.com/v1.0/me', + skipTest: false + }, + { + category: 'Getting Started', + method: 'GET', + requestUrl: '/v1.0/me/photo', + humanName: 'my photo', + docLink: 'https://graph.microsoft.com/v1.0/me', + skipTest: false + } + ], + error: { + message: '' + } + }, + intl: { + message: messages + } + } + + jest.mock('../../app/views/query-runner/query-input/QueryInput.tsx') + + return render( + + + + ) +} +jest.mock('react-redux', () => { + return{ + connect: jest.fn( + // eslint-disable-next-line no-unused-vars +

(_props?: any) => (component: React.ComponentType

) => component + ) + } +}) + +// eslint-disable-next-line no-console +console.warn = jest.fn() + +describe('Tests SampleQueries', () => { + it('Renders SampleQueries without crashing', () => { + renderSampleQueries(); + expect(screen.getByRole('searchbox')).toBeDefined(); + userEvent.type(screen.getByRole('searchbox'), 'my profile'); + expect(screen.getByText(/my profile/)).toBeDefined(); + }) +}) diff --git a/src/tests/sample-queries/sample-query-utils.spec.ts b/src/tests/sample-queries/sample-query-utils.spec.ts new file mode 100644 index 000000000..ec63421c9 --- /dev/null +++ b/src/tests/sample-queries/sample-query-utils.spec.ts @@ -0,0 +1,11 @@ +import { isJsonString } from '../../app/views/sidebar/sample-queries/sample-query-utils'; + +describe('Tests isJsonString', () => { + it('Returns true for valid JSON strings', () => { + expect(isJsonString('{"foo": "bar"}')).toBe(true); + }); + + it('Returns false for invalid JSON strings', () => { + expect(isJsonString('{"foo": "bar"')).toBe(false); + }) +}) \ No newline at end of file diff --git a/src/tests/sample-queries/tokens.spec.ts b/src/tests/sample-queries/tokens.spec.ts new file mode 100644 index 000000000..dcee5f4fa --- /dev/null +++ b/src/tests/sample-queries/tokens.spec.ts @@ -0,0 +1,9 @@ +import { getTokens } from '../../app/views/sidebar/sample-queries/tokens'; + +describe('Tests getTokens function', () => { + it('should return an array of IToken objects', () => { + // Arrange, Act and Assert + const tokens = getTokens(); + expect(tokens.length).toBe(34); + }) +}) \ No newline at end of file diff --git a/src/tests/services/actions/adaptive-cards-action-creator.spec.ts b/src/tests/services/actions/adaptive-cards-action-creator.spec.ts index d8af32837..9bd15bf74 100644 --- a/src/tests/services/actions/adaptive-cards-action-creator.spec.ts +++ b/src/tests/services/actions/adaptive-cards-action-creator.spec.ts @@ -1,4 +1,5 @@ import { + getAdaptiveCard, getAdaptiveCardError, getAdaptiveCardPending, getAdaptiveCardSuccess @@ -8,7 +9,11 @@ import { FETCH_ADAPTIVE_CARD_PENDING, FETCH_ADAPTIVE_CARD_SUCCESS } from '../../../app/services/redux-constants'; - +import { IQuery } from '../../../types/query-runner'; +import configureMockStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; +const middleware = [thunk]; +const mockStore = configureMockStore(middleware); describe('Graph Explorer Adaptive Cards Action Creators\'', () => { beforeEach(() => { @@ -54,4 +59,157 @@ describe('Graph Explorer Adaptive Cards Action Creators\'', () => { }); + it('Dispatches FETCH_ADAPTIVE_CARD_SUCCESS with no payload', () => { + const result = { sample: 'response' }; + const expectedAction = { + type: FETCH_ADAPTIVE_CARD_SUCCESS, + response: {} + }; + + // eslint-disable-next-line no-undef + fetchMock.mockResponse(JSON.stringify(result)); + + const store = mockStore({}); + const sampleQuery: IQuery = { + selectedVerb: 'GET', + selectedVersion: 'v1', + sampleUrl: 'https://graph.microsoft.com/v1.0/me/events', + sampleBody: '', + sampleHeaders: [] + } + + // @ts-ignore + return store.dispatch(getAdaptiveCard('', sampleQuery)) + // @ts-ignore + .then(() => { + expect(store.getActions()).toEqual([expectedAction]); + }); + }); + + it('Dispatches FETCH_ADAPTIVE_CARD_SUCCESS with payload', () => { + const result = { sample: 'response' }; + const expectedAction = [ + { + type: FETCH_ADAPTIVE_CARD_SUCCESS, + response: { + 'Given name': 'Megan', + 'Surname': 'Bowen', + 'Job title': 'Auditor', + 'Office location': '12/1110', + 'Email': 'MeganBowen@M365x214355.onmicrosoft.com', + 'Business phones': '+1 412 555 0109' + } + }, + { + type: FETCH_ADAPTIVE_CARD_PENDING, + response: '' + } + ]; + + const payload = { + businessPhones: ['+1 412 555 0109'], + displayName: 'Megan Bowen', + givenName: 'Megan', + jobTitle: 'Auditor', + mail: 'MeganB@M365x214355.onmicrosoft.com', + mobilePhone: null, + officeLocation: '12/1110', + preferredLanguage: 'en-US', + surname: 'Bowen', + userPrincipalName: 'MeganB@M365x214355.onmicrosoft.com', + id: '48d31887-5fad-4d73-a9f5-3c356e68a038' + } + + // eslint-disable-next-line no-undef + fetchMock.mockResponse(JSON.stringify(result)); + + const store = mockStore({}); + const sampleQuery: IQuery = { + selectedVerb: 'GET', + selectedVersion: 'v1', + sampleUrl: 'https://graph.microsoft.com/v1.0/me/', + sampleBody: '', + sampleHeaders: [] + } + + // @ts-ignore + return store.dispatch(getAdaptiveCard(payload, sampleQuery)) + // @ts-ignore + .then(() => { + expect(store.getActions()[0].type).toEqual(expectedAction[1].type); + expect(store.getActions()[1].type).toEqual(expectedAction[0].type); + }); + }) + + it('Returns no template available if a sample query has no adaptive card', () => { + const result = { sample: 'response' }; + const expectedAction = { + type: FETCH_ADAPTIVE_CARD_ERROR, + response: 'No template available' + }; + + const payload = { + businessPhones: ['+1 412 555 0109'], + displayName: 'Megan Bowen', + givenName: 'Megan', + jobTitle: 'Auditor', + mail: 'MeganB@M365x214355.onmicrosoft.com', + mobilePhone: null, + officeLocation: '12/1110', + preferredLanguage: 'en-US', + surname: 'Bowen', + userPrincipalName: 'MeganB@M365x214355.onmicrosoft.com', + id: '48d31887-5fad-4d73-a9f5-3c356e68a038' + } + + // eslint-disable-next-line no-undef + fetchMock.mockResponse(JSON.stringify(result)); + + const store = mockStore({}); + const sampleQuery: IQuery = { + selectedVerb: 'GET', + selectedVersion: 'v1', + sampleUrl: 'https://graph.microsoft.com/v1.0/me/events', + sampleBody: '', + sampleHeaders: [] + } + + // @ts-ignore + return store.dispatch(getAdaptiveCard(payload, sampleQuery)) + // @ts-ignore + .then(() => { + expect(store.getActions()).toEqual([expectedAction]); + }); + }); + + it('Returns invalid payload for card if the payload received is an empty object', () => { + const result = { sample: 'response' }; + const expectedAction = { + type: FETCH_ADAPTIVE_CARD_ERROR, + response: 'Invalid payload for card' + }; + + const payload = {}; + + // eslint-disable-next-line no-undef + fetchMock.mockResponse(JSON.stringify(result)); + + const store = mockStore({}); + const sampleQuery: IQuery = { + selectedVerb: 'GET', + selectedVersion: 'v1', + sampleUrl: 'https://graph.microsoft.com/v1.0/me/events', + sampleBody: '', + sampleHeaders: [] + } + + // @ts-ignore + return store.dispatch(getAdaptiveCard(payload, sampleQuery)) + // @ts-ignore + .then(() => { + expect(store.getActions()).toEqual([expectedAction]); + }); + }); + + }); diff --git a/src/tests/services/actions/auth-action-creators.spec.ts b/src/tests/services/actions/auth-action-creators.spec.ts new file mode 100644 index 000000000..4047ebff6 --- /dev/null +++ b/src/tests/services/actions/auth-action-creators.spec.ts @@ -0,0 +1,153 @@ +import { + AUTHENTICATION_PENDING, GET_AUTH_TOKEN_SUCCESS, GET_CONSENTED_SCOPES_SUCCESS, + LOGOUT_SUCCESS +} from '../../../app/services/redux-constants'; + +import { + getAuthTokenSuccess, getConsentedScopesSuccess, signOutSuccess, + setAuthenticationPending, + storeScopes, signIn, signOut +} from '../../../app/services/actions/auth-action-creators'; + +import configureMockStore from 'redux-mock-store'; + +import thunk from 'redux-thunk'; +const middlewares = [thunk]; +const mockStore = configureMockStore(middlewares); +window.open = jest.fn(); +describe('Auth Action Creators test', () => { + it('tests the authentication pending action', () => { + // Arrange + const response: boolean = true; + const expectedAction = { + type: AUTHENTICATION_PENDING, + response + } + + // Act + const action = setAuthenticationPending(response); + + // Assert + expect(action).toEqual(expectedAction); + }) + + it('tests the auth token success function', () => { + // Arrange + const response: boolean = true; + const expectedAction = { + type: GET_AUTH_TOKEN_SUCCESS, + response + } + + // Act + const action = getAuthTokenSuccess(response); + + // Assert + expect(action).toEqual(expectedAction); + }) + + it('tests the scopes success action', () => { + // Arrange + const response: string[] = ['mail.read', 'profile.read']; + const expectedAction = { + type: GET_CONSENTED_SCOPES_SUCCESS, + response + } + + // Act + const action = getConsentedScopesSuccess(response); + + // Assert + expect(action).toEqual(expectedAction); + }) + + it('tests sign out success action', () => { + // Arrange + const response: boolean = true; + const expectedAction = { + type: LOGOUT_SUCCESS, + response + } + // Act + const action = signOutSuccess(response); + + // Assert + expect(action).toEqual(expectedAction); + }) + + it('Dispatches getConsentedScopesSuccess when storeScopes is called', () => { + // Arrange + const response: string[] = ['mail.read', 'profile.read']; + const expectedAction = { + type: GET_CONSENTED_SCOPES_SUCCESS, + response + } + + // Act + const store = mockStore({ consentedScopes: [] }); + + // @ts-ignore + store.dispatch(storeScopes(response)); + + // Assert + expect(store.getActions()).toEqual([expectedAction]); + + }) + + it('It dispatches the getAuthTokenSuccess action creater when signIn is called', () => { + // Arrange + const response: boolean = true; + const expectedAction = { + type: GET_AUTH_TOKEN_SUCCESS, + response + } + + // Act + const store = mockStore({ authToken: {} }); + + // @ts-ignore + store.dispatch(signIn(response)); + + // Assert + expect(store.getActions()).toEqual([expectedAction]); + }) + + it('Dispatches signout success when signOut is called', () => { + // Arrange + const response: boolean = false; + const expectedAction = { + type: LOGOUT_SUCCESS, + response + } + + // Act + const store = mockStore({ authToken: {} }); + + // @ts-ignore + store.dispatch(signOutSuccess(response)); + + // Assert + expect(store.getActions()).toEqual([expectedAction]); + + }) + + it('Tests signOut method', () => { + // Arrange + const store = mockStore({}); + const expectedActions = [ + { + type: AUTHENTICATION_PENDING, + response: true + } + ]; + + // Act and assert + //@ts-ignore + store.dispatch(signOut()) + + expect(store.getActions()[0].type).toEqual(expectedActions[0].type); + + }) + + +}) \ No newline at end of file diff --git a/src/tests/services/actions/autocomplete-action-creators.spec.ts b/src/tests/services/actions/autocomplete-action-creators.spec.ts new file mode 100644 index 000000000..fb2f7f1de --- /dev/null +++ b/src/tests/services/actions/autocomplete-action-creators.spec.ts @@ -0,0 +1,245 @@ +import { + AUTOCOMPLETE_FETCH_ERROR, + AUTOCOMPLETE_FETCH_PENDING, + AUTOCOMPLETE_FETCH_SUCCESS +} from '../../../app/services/redux-constants' +import thunk from 'redux-thunk'; +import configureMockStore from 'redux-mock-store'; +import fetch from 'jest-fetch-mock'; +import { store } from '../../../../src/store/index'; +import { IRootState } from '../../../types/root'; +import { Mode } from '../../../types/enums'; +import { + fetchAutocompleteSuccess, fetchAutocompleteError, + fetchAutocompletePending, fetchAutoCompleteOptions +} from '../../../app/services/actions/autocomplete-action-creators'; + +const middleware = [thunk]; +const mockStore = configureMockStore(middleware); + +jest.mock('../../../../src/store/index'); + +const mockState: IRootState = { + devxApi: { + baseUrl: 'https://graph.microsoft.com/v1.0/me', + parameters: '$count=true' + }, + permissionsPanelOpen: true, + profile: null, + sampleQuery: { + sampleUrl: 'http://localhost:8080/api/v1/samples/1', + selectedVerb: 'GET', + selectedVersion: 'v1', + sampleHeaders: [] + }, + authToken: { token: false, pending: false }, + consentedScopes: [], + isLoadingData: false, + queryRunnerStatus: null, + termsOfUse: true, + theme: 'dark', + adaptiveCard: { + pending: false, + data: { + template: 'Template' + } + }, + graphExplorerMode: Mode.Complete, + sidebarProperties: { + showSidebar: true, + mobileScreen: false + }, + samples: { + queries: [], + pending: false, + error: null + }, + scopes: { + pending: false, + data: [], + hasUrl: false, + error: null + }, + history: [], + graphResponse: { + body: undefined, + headers: undefined + }, + snippets: { + pending: false, + data: [], + error: null + }, + responseAreaExpanded: false, + dimensions: { + request: { + width: '100px', + height: '100px' + }, + response: { + width: '100px', + height: '100px' + }, + sidebar: { + width: '100px', + height: '100px' + }, + content: { + width: '100px', + height: '100px' + } + }, + autoComplete: { + data: null, + error: null, + pending: false + }, + resources: { + pending: false, + data: { + segment: '', + labels: [], + children: [] + }, + error: null, + paths: [] + }, + policies: { + pending: false, + data: {}, + error: null + } +} + +const currentState = store.getState(); +store.getState = () => { + return { + mockState, + ...currentState + } +} +describe('Test autocomplete action creators', () => { + beforeEach(() => { + // eslint-disable-next-line no-undef + fetchMock.resetMocks(); + }); + + it('Tests autocomplete error', () => { + // Arrange + const errorObject = {}; + const expectedAction = { + type: AUTOCOMPLETE_FETCH_ERROR, + response: errorObject + } + + // Act + const action = fetchAutocompleteError(errorObject); + + // Assert + expect(action).toEqual(expectedAction) + }) + + it('Tests autocomplete fetch success', () => { + // Arrange + const response = { + url: 'https://graph.microsoft.com/v1.0/', + parameters: { + verb: 'GET', + values: [], + links: [] + }, + createdAt: '' + } + const expectedAction = { + type: AUTOCOMPLETE_FETCH_SUCCESS, + response + } + + // Act + const action = fetchAutocompleteSuccess(response); + + // Assert + expect(action).toEqual(expectedAction); + }) + + it('Tests autocomplete fetch pending', () => { + // Arrange + const expectedAction = { + type: AUTOCOMPLETE_FETCH_PENDING + } + + // Act + const action = fetchAutocompletePending(); + + // Assert + expect(action).toEqual(expectedAction); + }) + + jest.mock('../../../../src/modules/suggestions/suggestions.ts', () => { + return { + getSuggestions: () => { + return Promise.resolve({ + url: 'https://graph.microsoft.com/v1.0/', + parameters: [ + { + name: 'verb', + values: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'] + } + ], + createdAt: '363647474' + }); + } + } + }) + + describe('Load autocomplete suggestions thunk', () => { + it('Should begin the api call and pull autocomplete suggestions, but fail and return an empty array ', () => { + // Arrange + const expectedResponse = { + url: 'https://graph.microsoft.com/v1.0/', + parameters: { + verb: 'GET', + values: [], + links: [] + }, + createdAt: '' + } + fetch.mockResponseOnce(JSON.stringify(expectedResponse)); + + const store_ = mockStore({ autocomplete: null }); + + // Act + // @ts-ignore + store_.dispatch(fetchAutoCompleteOptions('https://graph.microsoft.com/v1.0/', 'v1.0')); + + // Assert + expect(store_.getActions()).toEqual([]); + }); + + it('Dispatches autocomplete_fetch_error when fetch fails', () => { + // Arrange + const expectedAction = { + type: AUTOCOMPLETE_FETCH_ERROR, + response: {} + }; + const store_ = mockStore({}) + + // Act + // @ts-ignore + store_.dispatch(fetchAutoCompleteOptions('https://graph.microsoft.com/v1.0/', 'v1.0')) + .then(() => { + // Assert + expect(store_.getActions()).toEqual([expectedAction]); + }) + + }) + + it('Fetches autocomplete options', () => { + // @ts-ignore + const response = store.dispatch(fetchAutoCompleteOptions('https://graph.microsoft.com/v1.0/', 'v1.0')); + expect(response).toBe(undefined); + }) + }) + + +}) diff --git a/src/tests/services/actions/devxApi-action-creators.spec.ts b/src/tests/services/actions/devxApi-action-creators.spec.ts new file mode 100644 index 000000000..44c084c4c --- /dev/null +++ b/src/tests/services/actions/devxApi-action-creators.spec.ts @@ -0,0 +1,27 @@ +import { setDevxApiUrl } from '../../../app/services/actions/devxApi-action-creators'; +import { SET_DEVX_API_URL_SUCCESS } from '../../../app/services/redux-constants'; +import { IDevxAPI } from '../../../types/devx-api'; + +describe('Set devx api url', () => { + it('Changes theme to dark', () => { + + // Arrange + const devxApiUrl = new URLSearchParams(location.search).get('devx-api'); + const devxApi: IDevxAPI = { + baseUrl: devxApiUrl!, + parameters: '' + }; + + const expectedActions = + { + type: SET_DEVX_API_URL_SUCCESS, + response: devxApi + }; + + // Act + const action = setDevxApiUrl(devxApi); + + // Assert + expect(action).toEqual(expectedActions); + }) +}); \ No newline at end of file diff --git a/src/tests/services/actions/dimensions-action-creator.spec.ts b/src/tests/services/actions/dimensions-action-creator.spec.ts new file mode 100644 index 000000000..2837505ff --- /dev/null +++ b/src/tests/services/actions/dimensions-action-creator.spec.ts @@ -0,0 +1,38 @@ +import { setDimensions } from '../../../app/services/actions/dimensions-action-creator'; +import { RESIZE_SUCCESS } from '../../../app/services/redux-constants'; +import { IDimensions } from '../../../types/dimensions'; + +describe('Sets dimensions on GE', () => { + it('Sets dimensions', () => { + // Arrange + const dimensions: IDimensions = { + request: { + width: '100%', + height: '36vh' + }, + response: { + width: '100%', + height: '46vh' + }, + sidebar: { + width: '100%', + height: '46vh' + }, + content: { + width: '100%', + height: '46vh' + } + } + + const expectedActions = { + type: RESIZE_SUCCESS, + response: dimensions + } + + // Act + const action = setDimensions(dimensions); + + // Assert + expect(action).toEqual(expectedActions); + }) +}); \ No newline at end of file diff --git a/src/tests/services/actions/ocps-action-creators.spec.ts b/src/tests/services/actions/ocps-action-creators.spec.ts new file mode 100644 index 000000000..6ca5a44aa --- /dev/null +++ b/src/tests/services/actions/ocps-action-creators.spec.ts @@ -0,0 +1,131 @@ +import { GET_POLICY_ERROR, GET_POLICY_PENDING, GET_POLICY_SUCCESS } from '../../../app/services/redux-constants'; +import { + getPoliciesSuccess, getPoliciesError, getPoliciesPending, getPolicies, getPolicy, getPolicyUrl +} from '../../../app/services/actions/ocps-action-creators'; +import configureMockStore from 'redux-mock-store'; +import fetch from 'jest-fetch-mock'; +import thunk from 'redux-thunk'; +import { authenticationWrapper } from '../../../modules/authentication'; + +const middlewares = [thunk]; +const mockStore = configureMockStore(middlewares); + +authenticationWrapper.getOcpsToken = jest.fn(() => Promise.resolve('token')); + +describe('Tests OCPS action creators', () => { + beforeEach(() => { + // eslint-disable-next-line no-undef + fetchMock.resetMocks(); + }); + it('tests response with valid policies object ', () => { + // Arrange + const response = { + email: 1, + screenshot: 1, + feedback: 20 + } + const expectedAction = { + type: GET_POLICY_SUCCESS, + response + } + + // Act + const action = getPoliciesSuccess(response); + + // Assert + expect(action).toEqual(expectedAction); + }) + + it('tests the error object response when policies fetch fails', () => { + // Arrange + const error = {}; + const expectedAction = { + type: GET_POLICY_ERROR, + response: error + } + + // Act + const action = getPoliciesError(error); + + // Assert + expect(action).toEqual(expectedAction); + }) + + it('tests get policies pending', () => { + // Arrange + const expectedAction = { + type: GET_POLICY_PENDING + } + + // Act + const action = getPoliciesPending(); + + // Assert + expect(action).toEqual(expectedAction); + }) + + it('Tests getPolicies() and returns an empty array', () => { + // Arrange + const store = mockStore({}); + const expectedActions = [ + { + type: GET_POLICY_PENDING + }, + { + type: GET_POLICY_SUCCESS, + response: { + email: 0, screenshot: 0, feedback: 0 + } + } + ] + fetch.mockResponseOnce(JSON.stringify({ + ok: true, + email: 0, + screenshot: 0, + feedback: 0, + value: [ + { randomValue: 0 } + ] + })); + // @ts-ignore + store.dispatch(getPolicies()) + .then(() => { + expect(store.getActions()).toEqual(expectedActions); + }) + .catch((e: Error) => { throw e }); + }); + + it('Tests getPolicy which returns policy values', () => { + // Arrange + const response = { + email: 0, + screenshot: 0, + feedback: 0, + value: [ + { + policiesPayload: { + settingId: ['L_EmailCollection', 'L_Screenshot', 'L_SendFeedback'], + value: '20' + } + } + ] + } + + // Act + const policyValues = getPolicy(response) + + // Assert + expect(policyValues.email).toEqual(0); + expect(policyValues.screenshot).toEqual(0); + expect(policyValues.feedback).toEqual(0); + + }) + + it('Tests policy url generation', () => { + // Arrange + const url = getPolicyUrl(); + + // Assert + expect(url).toBeTruthy(); + }) +}) \ No newline at end of file diff --git a/src/tests/services/actions/permissions-action-creators.spec.ts b/src/tests/services/actions/permissions-action-creators.spec.ts new file mode 100644 index 000000000..90a4f7333 --- /dev/null +++ b/src/tests/services/actions/permissions-action-creators.spec.ts @@ -0,0 +1,239 @@ +import { + FETCH_SCOPES_ERROR, + FETCH_SCOPES_PENDING, + FETCH_SCOPES_SUCCESS, + QUERY_GRAPH_STATUS +} from '../../../app/services/redux-constants'; + +import { + fetchScopesSuccess, fetchScopesPending, fetchScopesError, getPermissionsScopeType, fetchScopes, + consentToScopes +} from + '../../../app/services/actions/permissions-action-creator'; +import { IPermissionsResponse } from '../../../types/permissions'; +import thunk from 'redux-thunk'; +import configureMockStore from 'redux-mock-store'; +import fetch from 'jest-fetch-mock'; +import { store } from '../../../../src/store/index'; +import { IRootState } from '../../../types/root'; +import { Mode } from '../../../types/enums'; +const middleware = [thunk]; +const mockStore = configureMockStore(middleware); + +window.open = jest.fn(); + +const mockState: IRootState = { + devxApi: { + baseUrl: 'https://graph.microsoft.com/v1.0/me', + parameters: '$count=true' + }, + permissionsPanelOpen: true, + profile: null, + sampleQuery: { + sampleUrl: 'http://localhost:8080/api/v1/samples/1', + selectedVerb: 'GET', + selectedVersion: 'v1', + sampleHeaders: [] + }, + authToken: { token: false, pending: false }, + consentedScopes: [], + isLoadingData: false, + queryRunnerStatus: null, + termsOfUse: true, + theme: 'dark', + adaptiveCard: { + pending: false, + data: { + template: 'Template' + } + }, + graphExplorerMode: Mode.Complete, + sidebarProperties: { + showSidebar: true, + mobileScreen: false + }, + samples: { + queries: [], + pending: false, + error: null + }, + scopes: { + pending: false, + data: [], + hasUrl: false, + error: null + }, + history: [], + graphResponse: { + body: undefined, + headers: undefined + }, + snippets: { + pending: false, + data: [], + error: null + }, + responseAreaExpanded: false, + dimensions: { + request: { + width: '100px', + height: '100px' + }, + response: { + width: '100px', + height: '100px' + }, + sidebar: { + width: '100px', + height: '100px' + }, + content: { + width: '100px', + height: '100px' + } + }, + autoComplete: { + data: null, + error: null, + pending: false + }, + resources: { + pending: false, + data: { + segment: '', + labels: [], + children: [] + }, + error: null, + paths: [] + }, + policies: { + pending: false, + data: {}, + error: null + } +} +const currentState = store.getState(); +store.getState = () => { + return { + mockState, + ...currentState + } +} + +jest.mock('../../../app/services/actions/autocomplete-action-creators.ts', () => { + const autocomplete_ = jest.requireActual('../../../app/services/actions/autocomplete-action-creators.ts'); + return { + ...autocomplete_, + getPermissionsScopeType: jest.fn(() => 'DelegatedWork') + } +}) + +describe('tests permissions action creators', () => { + it('Tests if FETCH_SCOPES_SUCCESS is dispatched when fetchScopesSuccess is called', () => { + // Arrange + const response: IPermissionsResponse = { + hasUrl: true, + scopes: [ + { + value: '', + consentDescription: '', + isAdmin: false, + consented: true + } + ] + } + + const expectedAction = { + type: FETCH_SCOPES_SUCCESS, + response + } + + // Act + const action = fetchScopesSuccess(response); + + // Assert + expect(action).toEqual(expectedAction); + }) + + it('Tests if FETCH_SCOPES_ERROR is dispatched when fetchScopesError is called', () => { + // Arrange + const response = { + hasUrl: true, + error: {} + } + + const expectedAction = { + type: FETCH_SCOPES_ERROR, + response + } + + // Act + const action = fetchScopesError(response); + + // Assert + expect(action).toEqual(expectedAction); + }) + + it('Tests if FETCH_SCOPES_PENDING is dispatched when fetchScopes pending is called', () => { + // Arrange + const expectedAction = { + type: FETCH_SCOPES_PENDING + } + + // Act + const action = fetchScopesPending(); + + // Assert + expect(action).toEqual(expectedAction); + }) + + it('returns valid scope type given a user profile or with null', () => { + // Arrange + const expectedResult = 'DelegatedWork'; + + // Act + const result = getPermissionsScopeType(null); + + // Assert + expect(result).toEqual(expectedResult); + + }); + + it('Tests the fetchScopes function', () => { + // Arrange + const api_response = { + scopes: [], + ok: true + } + fetch.mockResponseOnce(JSON.stringify(api_response)); + + try { + // Act + // @ts-ignore + store.dispatch(fetchScopes()) + .then((response_: any) => { + expect(response_.scopes).toBe(undefined); + }) + .catch((e: any) => { throw e }) + } + catch (e) { + // + } + }) + + // Revisit this test + it('Tests consenting to scopes function', () => { + // Arrange + const store_ = mockStore({}); + fetch.mockResponseOnce(JSON.stringify({})); + + // Act and Assert + // @ts-ignore + store_.dispatch(consentToScopes([])) + .then(() => { + expect(store_.getActions()[0].type).toEqual(QUERY_GRAPH_STATUS); + }) + .catch((e: any) => { throw e }) + }) +}) \ No newline at end of file diff --git a/src/tests/services/actions/permissions-panel-action-creators.spec.ts b/src/tests/services/actions/permissions-panel-action-creators.spec.ts new file mode 100644 index 000000000..8ee592026 --- /dev/null +++ b/src/tests/services/actions/permissions-panel-action-creators.spec.ts @@ -0,0 +1,19 @@ +import { PERMISSIONS_PANEL_OPEN } from '../../../app/services/redux-constants'; +import { togglePermissionsPanel } from '../../../app/services/actions/permissions-panel-action-creator'; + +describe('Tests permissions panel action creator', () => { + it('tests permissions panel action creator', () => { + // Arrange + const response: boolean = true; + const expectedAction = { + type: PERMISSIONS_PANEL_OPEN, + response + } + + // Act + const action = togglePermissionsPanel(response); + + // Assert + expect(action).toEqual(expectedAction); + }) +}) \ No newline at end of file diff --git a/src/tests/services/actions/profile-action-creators.spec.tsx b/src/tests/services/actions/profile-action-creators.spec.tsx index 3d2db81f0..2d0dce0f4 100644 --- a/src/tests/services/actions/profile-action-creators.spec.tsx +++ b/src/tests/services/actions/profile-action-creators.spec.tsx @@ -1,12 +1,11 @@ import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; -import { getProfileInfo, profileRequestSuccess } from '../../../app/services/actions/profile-action-creators'; +import { getProfileInfo, profileRequestError, getProfileInformation, + profileRequestSuccess, getBetaProfile, getProfileType, + getProfileImage, getProfileResponse } from '../../../app/services/actions/profile-action-creators'; import { - PROFILE_REQUEST_ERROR, PROFILE_REQUEST_SUCCESS, - QUERY_GRAPH_RUNNING -} from '../../../app/services/redux-constants'; - + PROFILE_REQUEST_ERROR, PROFILE_REQUEST_SUCCESS} from '../../../app/services/redux-constants'; const middlewares = [thunk]; const mockStore = configureMockStore(middlewares); @@ -35,6 +34,48 @@ describe('actions', () => { .then(() => { const includesError = store.getActions().filter(k => k.type === PROFILE_REQUEST_ERROR) expect(!!includesError).toEqual(true); - }); + }) + .catch( (e : Error) => { throw e}) + }); + + it('dispatches PROFILE_REQUEST_ERROR if profile request fails', () => { + // Arrange + const response = {}; + const expectedAction = { + type: PROFILE_REQUEST_ERROR, + response + } + + // Act + const action = profileRequestError(response); + + // Assert + expect(action).toEqual(expectedAction); + }); + + it('throws on getProfileInformation', () => { + fetchMock.mockResponseOnce(JSON.stringify({ ok: false })); + expect(getProfileInformation()).toBeDefined(); + }); + + it('throws on getProfileInformation', () => { + fetchMock.mockResponseOnce(JSON.stringify({ ok: false })); + expect(getBetaProfile()).toBeDefined(); + }); + + it('throws on getProfileInformation', () => { + fetchMock.mockResponseOnce(JSON.stringify({ ok: false })); + expect(getProfileImage()).toBeDefined(); + }); + + it('throws on getProfileInformation', () => { + fetchMock.mockResponseOnce(JSON.stringify({ ok: false })); + expect(getProfileResponse()).toBeDefined(); + }); + + it('throws on getProfileInformation', () => { + fetchMock.mockResponseOnce(JSON.stringify({ ok: false })); + expect(getProfileType({})).toBeDefined(); }); + }); diff --git a/src/tests/services/actions/proxy-action-creator.spec.ts b/src/tests/services/actions/proxy-action-creator.spec.ts new file mode 100644 index 000000000..b0e280a62 --- /dev/null +++ b/src/tests/services/actions/proxy-action-creator.spec.ts @@ -0,0 +1,47 @@ +import { SET_GRAPH_PROXY_URL } from '../../../app/services/redux-constants'; +import { setGraphProxyUrl, getGraphProxyUrl } from '../../../app/services/actions/proxy-action-creator'; +import configureMockStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; +const middleware = [thunk]; +const mockStore = configureMockStore(middleware); + +describe('Tests Proxy-Action-Creators', () => { + beforeEach(() => { + fetchMock.resetMocks(); + }); + it('dispatches SET_GRAPH_PROXY_URL when setGraphProxyUrl is called', () => { + // Arrange + const response: string = 'https://proxy.apisandbox.msdn.microsoft.com/svc'; + const expectedAction = { + type: SET_GRAPH_PROXY_URL, + response + } + + // Act + const action = setGraphProxyUrl(response); + + // Assert + expect(action).toEqual(expectedAction); + }) + + it('Throws an error and dispatches SET_GRAPH_PROXY_URL', () => { + // Arrange + fetchMock.mockResponseOnce(JSON.stringify({ ok: false })); + const expectedAction = { + type: SET_GRAPH_PROXY_URL, + response: { + ok: false + } + } + + const store = mockStore({}); + + // Act and Assert + // @ts-ignore + store.dispatch(getGraphProxyUrl()).then(() => { + expect(store.getActions()).toEqual([expectedAction]); + }) + .catch((e: Error) => { throw e }) + + }) +}) \ No newline at end of file diff --git a/src/tests/services/actions/query-action-creators.spec.tsx b/src/tests/services/actions/query-action-creators.spec.tsx index e3af79166..837a589d6 100644 --- a/src/tests/services/actions/query-action-creators.spec.tsx +++ b/src/tests/services/actions/query-action-creators.spec.tsx @@ -67,7 +67,8 @@ describe('query actions', () => { return store.dispatch(runQuery(query)) .then(() => { expect(store.getActions()[0]).toEqual(expectedActions[0]); - }); + }) + .catch((e : Error) => { throw e}); }); it('dispatches QUERY_GRAPH_STATUS for failed requests', () => { @@ -125,6 +126,7 @@ describe('query actions', () => { return store.dispatch(runQuery(query)) .then(() => { expect(store.getActions()[0]).toEqual(expectedActions[0]); - }); + }) + .catch((e : Error) => { throw e}); }); }); diff --git a/src/tests/services/actions/query-input-action-creators.spec.tsx b/src/tests/services/actions/query-input-action-creators.spec.tsx index 35e13f89f..0f20cb7b8 100644 --- a/src/tests/services/actions/query-input-action-creators.spec.tsx +++ b/src/tests/services/actions/query-input-action-creators.spec.tsx @@ -7,12 +7,12 @@ import { SET_SAMPLE_QUERY_SUCCESS } from '../../../app/services/redux-constants' const middlewares = [thunk]; const mockStore = configureMockStore(middlewares); -describe('actions', () => { +describe('Query input action creators should', () => { beforeEach(() => { fetchMock.resetMocks(); }); - it('creates SET_SAMPLE_QUERY_SUCCESS when setSampleQuery is called', () => { + it('dispatch SET_SAMPLE_QUERY_SUCCESS when setSampleQuery is called', () => { const expectedActions = [ { type: SET_SAMPLE_QUERY_SUCCESS, diff --git a/src/tests/services/actions/query-status-action-creator.spec.ts b/src/tests/services/actions/query-status-action-creator.spec.ts new file mode 100644 index 000000000..334ce082b --- /dev/null +++ b/src/tests/services/actions/query-status-action-creator.spec.ts @@ -0,0 +1,44 @@ +import { CLEAR_QUERY_STATUS, CLEAR_RESPONSE, QUERY_GRAPH_STATUS } from '../../../app/services/redux-constants'; +import { + clearQueryStatus, clearResponse, + setQueryResponseStatus +} from '../../../app/services/actions/query-status-action-creator'; + +describe('Query Action Creators', () => { + it('Tests the query response status action', () => { + // Arrange + const response = { + ok: false, + statusText: 'Something worked!', + status: 200, + messageType: 1, + hint: 'Something worked!' + } + + const expectedAction = { + type: QUERY_GRAPH_STATUS, + response + } + + // Act + const action = setQueryResponseStatus(response); + + // Assert + expect(action).toEqual(expectedAction); + }); + + it('Tests the clear response action', () => { + // Assert + const expectedAction = { + type: CLEAR_RESPONSE + } + + // Act + const action = clearResponse(); + + // Assert + expect(action).toEqual(expectedAction); + }); +}) + +//add test for clearQueryStatus \ No newline at end of file diff --git a/src/tests/services/actions/request-history-action-creators.spec.tsx b/src/tests/services/actions/request-history-action-creators.spec.tsx index dd91b4733..090e70805 100644 --- a/src/tests/services/actions/request-history-action-creators.spec.tsx +++ b/src/tests/services/actions/request-history-action-creators.spec.tsx @@ -1,8 +1,12 @@ import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; -import { addHistoryItem } from '../../../app/services/actions/request-history-action-creators'; -import { ADD_HISTORY_ITEM_SUCCESS } from '../../../app/services/redux-constants'; +import { addHistoryItem, viewHistoryItem, removeHistoryItem, + bulkRemoveHistoryItems } from '../../../app/services/actions/request-history-action-creators'; +import { ADD_HISTORY_ITEM_SUCCESS, VIEW_HISTORY_ITEM_SUCCESS, + REMOVE_HISTORY_ITEM_SUCCESS, + REMOVE_ALL_HISTORY_ITEMS_SUCCESS} from '../../../app/services/redux-constants'; +import { IHistoryItem } from '../../../types/history'; const middlewares = [thunk]; const mockStore = configureMockStore(middlewares); @@ -23,5 +27,111 @@ describe('Request History Action Creators', () => { store.dispatch(addHistoryItem(historyItem)); expect(store.getActions()).toEqual(expectedActions); }); + + it('dispatches VIEW_HISTORY_ITEM_SUCCESS when viewHistoryItem is called with a valid history item', () => { + // Assert + const response: IHistoryItem = { + index: 0, + statusText: 'Something worked!', + responseHeaders: [], + result: {}, + url: 'https://graph.microsoft.com/v1.0/me', + method: 'GET', + headers: [], + createdAt: Date.now().toString(), + status: 200, + duration: 200 + } + + const expectedAction = { + type: VIEW_HISTORY_ITEM_SUCCESS, + response + } + + // Act + const store = mockStore({ history: [] }); + + // Assert + // @ts-ignore + store.dispatch(viewHistoryItem(response)); + expect(store.getActions()).toEqual([expectedAction]); + }); + + it('dispatches REMOVE_HISTORY_ITEM_SUCCESS when a history item is removed ', () => { + // Arrange + const historyItem: IHistoryItem = { + index: 0, + statusText: 'Something worked!', + responseHeaders: [], + result: {}, + url: 'https://graph.microsoft.com/v1.0/me', + method: 'GET', + headers: [], + createdAt: Date.now().toString(), + status: 200, + duration: 200 + } + + const expectedAction = { + type: REMOVE_HISTORY_ITEM_SUCCESS, + response: historyItem + } + + const store = mockStore([historyItem]); + + // Act and Assert + // @ts-ignore + store.dispatch(removeHistoryItem(historyItem)) + .then(() => { + expect(store.getActions()).toEqual([expectedAction]); + }) + + }); + + it('dispatches REMOVE_ALL_HISTORY_ITEMS_SUCCESS when bulkRemoveHistoryItems is called', () => { + // Arrange + const historyItems = [ + { + index: 0, + statusText: 'Something worked!', + responseHeaders: [], + result: {}, + url: 'https://graph.microsoft.com/v1.0/me', + method: 'GET', + headers: [], + createdAt: '12345', + status: 200, + duration: 200 + }, + { + index: 1, + statusText: 'Another history item!', + responseHeaders: [], + result: {}, + url: 'https://graph.microsoft.com/v1.0/me/events', + method: 'GET', + headers: [], + createdAt: '12345', + status: 200, + duration: 200 + } + ] + + const expectedAction = { + type: REMOVE_ALL_HISTORY_ITEMS_SUCCESS, + response: ['12345', '12345'] + } + + const store = mockStore(historyItems); + + // Act and Assert + // @ts-ignore + store.dispatch(bulkRemoveHistoryItems(historyItems)) + .then(() => { + expect(store.getActions()).toEqual([expectedAction]); + }) + }) }); +//Add tests for the async functions + diff --git a/src/tests/services/actions/resource-explorer-action-creators.spec.ts b/src/tests/services/actions/resource-explorer-action-creators.spec.ts index 421170dbe..3e7f546a6 100644 --- a/src/tests/services/actions/resource-explorer-action-creators.spec.ts +++ b/src/tests/services/actions/resource-explorer-action-creators.spec.ts @@ -4,11 +4,107 @@ import thunk from 'redux-thunk'; const middlewares = [thunk]; const mockStore = configureMockStore(middlewares); import { - addResourcePaths, fetchResourcesSuccess + addResourcePaths, fetchResourcesSuccess, fetchResourcesError, + fetchResourcesPending, removeResourcePaths, fetchResources } from '../../../app/services/actions/resource-explorer-action-creators'; import { - FETCH_RESOURCES_SUCCESS, RESOURCEPATHS_ADD_SUCCESS + FETCH_RESOURCES_SUCCESS, RESOURCEPATHS_ADD_SUCCESS, RESOURCEPATHS_DELETE_SUCCESS, FETCH_RESOURCES_ERROR, + FETCH_RESOURCES_PENDING } from '../../../app/services/redux-constants'; +import { Mode } from '../../../types/enums'; +import { IRootState } from '../../../types/root'; + +const mockState: IRootState = { + devxApi: { + baseUrl: 'https://graph.microsoft.com/v1.0/me', + parameters: '$count=true' + }, + permissionsPanelOpen: true, + profile: null, + sampleQuery: { + sampleUrl: 'http://localhost:8080/api/v1/samples/1', + selectedVerb: 'GET', + selectedVersion: 'v1', + sampleHeaders: [] + }, + authToken: { token: false, pending: false }, + consentedScopes: [], + isLoadingData: false, + queryRunnerStatus: null, + termsOfUse: true, + theme: 'dark', + adaptiveCard: { + pending: false, + data: { + template: 'Template' + } + }, + graphExplorerMode: Mode.Complete, + sidebarProperties: { + showSidebar: true, + mobileScreen: false + }, + samples: { + queries: [], + pending: false, + error: null + }, + scopes: { + pending: false, + data: [], + hasUrl: false, + error: null + }, + history: [], + graphResponse: { + body: undefined, + headers: undefined + }, + snippets: { + pending: false, + data: [], + error: null + }, + responseAreaExpanded: false, + dimensions: { + request: { + width: '100px', + height: '100px' + }, + response: { + width: '100px', + height: '100px' + }, + sidebar: { + width: '100px', + height: '100px' + }, + content: { + width: '100px', + height: '100px' + } + }, + autoComplete: { + data: null, + error: null, + pending: false + }, + resources: { + pending: false, + data: { + segment: '', + labels: [], + children: [] + }, + error: null, + paths: [] + }, + policies: { + pending: false, + data: {}, + error: null + } +} const paths = [ { @@ -68,14 +164,83 @@ describe('Resource Explorer actions', () => { } ]; - const store = mockStore({ + const store_ = mockStore({ resources: { paths: [] } }); - store.dispatch(addResourcePaths(paths)); - expect(store.getActions()).toEqual(expectedActions); + store_.dispatch(addResourcePaths(paths)); + expect(store_.getActions()).toEqual(expectedActions); }); + it('dispatches RESOURCEPATHS_DELETE_SUCCESS when removeResourcePaths is dispatched', () => { + + const expectedActions = [ + { + type: RESOURCEPATHS_DELETE_SUCCESS, + response: paths + } + ]; + + const store_ = mockStore({ + resources: { + paths + } + }); + + store_.dispatch(removeResourcePaths(paths)); + expect(store_.getActions()).toEqual(expectedActions); + }) + + it('creates FETCH_RESOURCES_ERROR when fetchResourcesError is called', () => { + // Arrange + const response = {}; + const expectedAction = { + type: FETCH_RESOURCES_ERROR, + response + } + + // Act + const action = fetchResourcesError(response); + + // Assert + expect(action).toEqual(expectedAction); + }) + + it('crates FETCH_RESOURCES_PENDING when resources are being fetched', () => { + // Arrange + const expectedAction = { + type: FETCH_RESOURCES_PENDING + } + + // Act + const action = fetchResourcesPending(); + + // Assert + expect(action).toEqual(expectedAction); + }); + + it('fetches resource paths and dispatches FETCH_RESOURCES_SUCCESS', () => { + // Arrange + const expectedAction = [ + { type: FETCH_RESOURCES_PENDING }, + { + type: FETCH_RESOURCES_SUCCESS, + response: { paths, ok: true } + } + ] + + const store = mockStore(mockState); + fetchMock.mockResponseOnce(JSON.stringify({ paths, ok: true })); + + // Act and Assert + // @ts-ignore + store.dispatch(fetchResources()) + .then(() => { + expect(store.getActions()).toEqual(expectedAction); + }) + .catch((e: Error) => { throw e }) + }) + }); diff --git a/src/tests/services/actions/response-expanded-action-creator.spec.ts b/src/tests/services/actions/response-expanded-action-creator.spec.ts new file mode 100644 index 000000000..903937101 --- /dev/null +++ b/src/tests/services/actions/response-expanded-action-creator.spec.ts @@ -0,0 +1,20 @@ +import { RESPONSE_EXPANDED } from '../../../app/services/redux-constants'; +import { expandResponseArea } from '../../../app/services/actions/response-expanded-action-creator'; + +describe('Response Area Expansion', () => { + it('it creates RESPONSE_EXPANDED action when expandResponseArea is called', () => { + //Arrange + const response: boolean = true; + + const expectedAction = { + type: RESPONSE_EXPANDED, + response + } + + // Act + const action = expandResponseArea(response); + + // Assert + expect(action).toEqual(expectedAction); + }) +}) \ No newline at end of file diff --git a/src/tests/services/actions/samples-action-creators.spec.tsx b/src/tests/services/actions/samples-action-creators.spec.tsx index c516968d6..8625ee118 100644 --- a/src/tests/services/actions/samples-action-creators.spec.tsx +++ b/src/tests/services/actions/samples-action-creators.spec.tsx @@ -1,6 +1,7 @@ -import { fetchSamplesSuccess } from '../../../app/services/actions/samples-action-creators'; +import { fetchSamplesSuccess, fetchSamplesError, + fetchSamplesPending } from '../../../app/services/actions/samples-action-creators'; import { - SAMPLES_FETCH_SUCCESS + SAMPLES_FETCH_SUCCESS, SAMPLES_FETCH_PENDING, SAMPLES_FETCH_ERROR } from '../../../app/services/redux-constants'; @@ -21,4 +22,24 @@ describe('actions', () => { expect(action).toEqual(expectedAction); }); + it('creates SAMPLES_FETCH_PENDING when fetchSamplesPending is called', () => { + const expectedAction = { + type: SAMPLES_FETCH_PENDING + }; + + const action = fetchSamplesPending(); + expect(action).toEqual(expectedAction); + }) + + it('creates SAMPLES_FETCH_ERROR when fetchSamplesError is called', () => { + const response = new Error('error'); + const expectedAction = { + type: SAMPLES_FETCH_ERROR, + response + }; + + const action = fetchSamplesError(response); + expect(action).toEqual(expectedAction); + }) + }); diff --git a/src/tests/services/actions/snippets-action-creators.spec.ts b/src/tests/services/actions/snippets-action-creators.spec.ts index baaefb9a2..cea26b8e3 100644 --- a/src/tests/services/actions/snippets-action-creators.spec.ts +++ b/src/tests/services/actions/snippets-action-creators.spec.ts @@ -1,14 +1,18 @@ import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; -import { getSnippetSuccess } from '../../../app/services/actions/snippet-action-creator'; -import { GET_SNIPPET_SUCCESS } from '../../../app/services/redux-constants'; +import { + getSnippetSuccess, getSnippetError, + getSnippetPending, + getSnippet +} from '../../../app/services/actions/snippet-action-creator'; +import { GET_SNIPPET_SUCCESS, GET_SNIPPET_ERROR, GET_SNIPPET_PENDING } from '../../../app/services/redux-constants'; const middlewares = [thunk]; const mockStore = configureMockStore(middlewares); describe('snippet actions', () => { - it('dispatches GET_SNIPPET_SUCCESS', () => { + it('dispatches GET_SNIPPET_SUCCESS when getSnippetSuccess is called', () => { const snippet = 'GraphServiceClient graphClient = new GraphServiceClient( authProvider );'; const expectedAction = [{ @@ -27,4 +31,65 @@ describe('snippet actions', () => { expect(store.getActions()).toEqual(expectedAction); }); + + it('dispatches GET_SNIPPET_PENDING when getSnippetPending is called', () => { + const expectedAction = { + type: GET_SNIPPET_PENDING + }; + + const action = getSnippetPending(); + + expect(action).toEqual(expectedAction); + }) + + it('dispatches GET_SNIPPET_ERROR when getSnippetError is called', () => { + const response = {}; + const expectedAction = { + type: GET_SNIPPET_ERROR, + response + }; + + const action = getSnippetError(response); + + expect(action).toEqual(expectedAction); + }) + + it('dispatches GET_SNIPPET_ERROR when getSnippet function fails', () => { + // Arrange + const expectedActions = [ + { + type: 'GET_SNIPPET_PENDING' + }, + { + type: GET_SNIPPET_SUCCESS, + response: { + CSharp: '{"ok":true}' + } + } + ] + + const store = mockStore({ + devxApi: { + baseUrl: 'https://graphexplorerapi.azurewebsites.net', + parameters: '' + }, + sampleQuery: { + sampleUrl: 'https://graph.microsoft.com/v1.0/me/', + selectedVerb: 'GET', + sampleBody: {}, + sampleHeaders: [], + selectedVersion: 'v1.0' + } + }); + fetchMock.mockResponseOnce(JSON.stringify({ ok: true })); + + // Act and Assert + // @ts-ignore + store.dispatch(getSnippet('CSharp')) + .then(() => { + expect(store.getActions()).toEqual(expectedActions); + }) + .catch((e: Error) => { throw e }); + + }) }); diff --git a/src/tests/services/actions/theme-action-creator.spec.ts b/src/tests/services/actions/theme-action-creator.spec.ts index 307f36a56..b527ee078 100644 --- a/src/tests/services/actions/theme-action-creator.spec.ts +++ b/src/tests/services/actions/theme-action-creator.spec.ts @@ -1,4 +1,4 @@ -import { changeThemeSuccess } from '../../../app/services/actions/theme-action-creator'; +import { changeThemeSuccess, changeTheme } from '../../../app/services/actions/theme-action-creator'; import { CHANGE_THEME_SUCCESS } from '../../../app/services/redux-constants'; import configureMockStore from 'redux-mock-store'; @@ -22,4 +22,19 @@ describe('Change theme action creator', () => { store.dispatch(changeThemeSuccess('dark')); expect(store.getActions()).toEqual(expectedActions); }) + + it('dispatches an action that changes the theme ', () => { + const expectedActions = [ + { + type: CHANGE_THEME_SUCCESS, + response: 'dark' + } + ]; + + const store = mockStore({ theme: '' }); + + // @ts-ignore + store.dispatch(changeTheme('dark')); + expect(store.getActions()).toEqual(expectedActions); + }) }) \ No newline at end of file diff --git a/src/tests/services/actions/toggle-sidebar-action-creators.spec.ts b/src/tests/services/actions/toggle-sidebar-action-creators.spec.ts index eab6081a3..7f5c324ab 100644 --- a/src/tests/services/actions/toggle-sidebar-action-creators.spec.ts +++ b/src/tests/services/actions/toggle-sidebar-action-creators.spec.ts @@ -25,7 +25,7 @@ describe('Toggle Sidebar Action Creators', () => { store.dispatch(toggleSidebar({ mobileScreen: true, showSidebar: false - })); + })) expect(store.getActions()).toEqual(expectedActions); }); }); diff --git a/src/tests/services/reducers/auth-reducers.spec.tsx b/src/tests/services/reducers/auth-reducers.spec.tsx index 90cb96c5c..36a3652a7 100644 --- a/src/tests/services/reducers/auth-reducers.spec.tsx +++ b/src/tests/services/reducers/auth-reducers.spec.tsx @@ -1,5 +1,5 @@ -import { authToken } from '../../../app/services/reducers/auth-reducers'; -import { GET_AUTH_TOKEN_SUCCESS } from '../../../app/services/redux-constants'; +import { authToken, consentedScopes } from '../../../app/services/reducers/auth-reducers'; +import { GET_AUTH_TOKEN_SUCCESS, GET_CONSENTED_SCOPES_SUCCESS } from '../../../app/services/redux-constants'; describe('Auth Reducer', () => { it('should return initial state', () => { @@ -18,4 +18,32 @@ describe('Auth Reducer', () => { expect(newState).toEqual({ token: true, pending: false }); }); + + it('should handle LOGOUT_SUCCESS', () => { + const initialState = { token: true, pending: false }; + + const queryAction = { type: 'LOGOUT_SUCCESS', response: false }; + const newState = authToken(initialState, queryAction); + + expect(newState).toEqual({ token: false, pending: false }); + }); + + it('should handle AUTHENTICATION_PENDING', () => { + const initialState = { token: false, pending: false }; + + const queryAction = { type: 'AUTHENTICATION_PENDING', response: true }; + const newState = authToken(initialState, queryAction); + + expect(newState).toEqual({ token: true, pending: true }); + }); + + it('should handle GET_CONSENTED_SCOPES_SUCCESS', () => { + const initialState = ['profile.read', 'profile.write', 'email.read']; + const action_ = { + type: GET_CONSENTED_SCOPES_SUCCESS, + response: ['profile.read', 'profile.write', 'email.read', 'email.write'] + } + const newState = consentedScopes(initialState, action_); + expect(newState).toEqual(['profile.read', 'profile.write', 'email.read', 'email.write']); + }) }); diff --git a/src/tests/services/reducers/autocomplete-reducer.spec.ts b/src/tests/services/reducers/autocomplete-reducer.spec.ts new file mode 100644 index 000000000..aadf5b0ce --- /dev/null +++ b/src/tests/services/reducers/autocomplete-reducer.spec.ts @@ -0,0 +1,71 @@ +import { IAction } from '../../../types/action'; +import { autoComplete } from '../../../app/services/reducers/autocomplete-reducer'; +import { + AUTOCOMPLETE_FETCH_ERROR, AUTOCOMPLETE_FETCH_PENDING, + AUTOCOMPLETE_FETCH_SUCCESS +} from '../../../app/services/redux-constants'; + +const initialState = { + pending: false, + data: null, + error: null +} + +describe('Autocomplete reducer', () => { + it('should handle AUTOCOMPLETE_FETCH_PENDING', () => { + const action = { + type: AUTOCOMPLETE_FETCH_PENDING, + response: null + } + const expectedState = { + pending: true, + data: null, + error: null + } + expect(autoComplete(initialState, action)).toEqual(expectedState) + }); + + it('should handle AUTOCOMPLETE_FETCH_SUCCESS', () => { + const action = { + type: AUTOCOMPLETE_FETCH_SUCCESS, + response: { + data: 'test' + } + } + const expectedState = { + pending: false, + data: { + data: 'test' + }, + error: null + }; + expect(autoComplete(initialState, action)).toEqual(expectedState) + + }); + + it('should handle AUTOCOMPLETE_FETCH_ERROR', () => { + const action = { + type: AUTOCOMPLETE_FETCH_ERROR, + response: 'test' + } + const expectedState = { + pending: false, + data: null, + error: 'test' + } + expect(autoComplete(initialState, action)).toEqual(expectedState) + }); + + it('should return unaltered state', () => { + const action = { + type: '', + response: null + } + const expectedState = { + pending: false, + data: null, + error: null + } + expect(autoComplete(initialState, action)).toEqual(expectedState) + }) +}) \ No newline at end of file diff --git a/src/tests/services/reducers/ocps-reducer.spec.ts b/src/tests/services/reducers/ocps-reducer.spec.ts new file mode 100644 index 000000000..5acb8b87b --- /dev/null +++ b/src/tests/services/reducers/ocps-reducer.spec.ts @@ -0,0 +1,74 @@ +import { policies } from '../../../app/services/reducers/ocps-reducers'; +import { GET_POLICY_ERROR, GET_POLICY_PENDING, GET_POLICY_SUCCESS } from '../../../app/services/redux-constants'; + +const initialState = { + pending: false, + data: {}, + error: null +} + +describe('OCPS Reducer', () => { + it('should handle GET_POLICY_SUCCESS', () => { + const action = { + type: GET_POLICY_SUCCESS, + response: { + policies: ['policy1', 'policy2'] + } + } + const expectedState = { + pending: false, + data: { + policies: ['policy1', 'policy2'] + }, + error: null + } + + const newState = policies(initialState, action); + expect(newState).toEqual(expectedState); + }); + + it('should handle GET_POLICY_ERROR', () => { + const action = { + type: GET_POLICY_ERROR, + response: 'error' + } + const expectedState = { + pending: false, + data: null, + error: 'error' + } + + const newState = policies(initialState, action); + expect(newState).toEqual(expectedState); + }); + + it('should handle GET_POLICY_PENDING', () => { + const action = { + type: GET_POLICY_PENDING, + response: '' + } + const expectedState = { + pending: true, + data: null, + error: null + } + + const newState = policies(initialState, action); + expect(newState).toEqual(expectedState); + }); + + it('should return default state', () => { + const action = { + type: '', + response: '' + } + const expectedState = { + pending: false, + data: {}, + error: null + } + + const newState = policies(initialState, action); + expect(newState).toEqual(expectedState); + }) +}) \ No newline at end of file diff --git a/src/tests/services/reducers/permissions-panel-reducer.spec.ts b/src/tests/services/reducers/permissions-panel-reducer.spec.ts new file mode 100644 index 000000000..4a4e984d4 --- /dev/null +++ b/src/tests/services/reducers/permissions-panel-reducer.spec.ts @@ -0,0 +1,17 @@ +import { permissionsPanelOpen } from '../../../app/services/reducers/permissions-panel-reducer'; +import { PERMISSIONS_PANEL_OPEN } from '../../../app/services/redux-constants'; + +const initialState = true; + +describe('Permissions panel reducer', () => { + it('should handle PERMISSIONS_PANEL_OPEN', () => { + const action = { + type: PERMISSIONS_PANEL_OPEN, + response: false + } + const expectedState = false; + + const newState = permissionsPanelOpen(initialState, action); + expect(newState).toEqual(expectedState); + }); +}) \ No newline at end of file diff --git a/src/tests/services/reducers/permissions-reducer.spec.ts b/src/tests/services/reducers/permissions-reducer.spec.ts new file mode 100644 index 000000000..724ea65b5 --- /dev/null +++ b/src/tests/services/reducers/permissions-reducer.spec.ts @@ -0,0 +1,64 @@ +import { scopes } from '../../../app/services/reducers/permissions-reducer'; +import { FETCH_SCOPES_ERROR, FETCH_SCOPES_PENDING, FETCH_SCOPES_SUCCESS } from '../../../app/services/redux-constants'; + +const initialState = { + pending: false, + data: [], + hasUrl: false, + error: null +}; + +describe('Permissions reducer', () => { + it('should handle FETCH_SCOPES_SUCCESS', () => { + const action = { + type: FETCH_SCOPES_SUCCESS, + response: { + hasUrl: false, + scopes: ['profile.read', 'profile.write', 'email.read', 'email.write'] + } + } + + const expectedState = { + pending: false, + data: ['profile.read', 'profile.write', 'email.read', 'email.write'], + hasUrl: false, + error: null + } + + const newState = scopes(initialState, action); + expect(newState).toEqual(expectedState); + }); + + it('should handle FETCH_SCOPES_ERROR', () => { + const action = { + type: FETCH_SCOPES_ERROR, + response: 'error' + } + const expectedState = { + pending: false, + data: [], + hasUrl: false, + error: 'error' + } + + const newState = scopes(initialState, action); + expect(newState).toEqual(expectedState); + }); + + it('should handle FETCH_SCOPES_PENDING', () => { + const action = { + type: FETCH_SCOPES_PENDING, + response: '' + } + + const expectedState = { + pending: true, + data: [], + hasUrl: false, + error: null + } + + const newState = scopes(initialState, action); + expect(newState).toEqual(expectedState); + }) +}); \ No newline at end of file diff --git a/src/tests/services/reducers/profile-reducer.spec.ts b/src/tests/services/reducers/profile-reducer.spec.ts new file mode 100644 index 000000000..5ca2da69c --- /dev/null +++ b/src/tests/services/reducers/profile-reducer.spec.ts @@ -0,0 +1,42 @@ +import { profile } from '../../../app/services/reducers/profile-reducer'; +import { LOGOUT_SUCCESS, PROFILE_REQUEST_SUCCESS } from '../../../app/services/redux-constants'; + +const initialState = null; + +describe('Profile reducer', () => { + it('should handle LOGOUT_SUCCESS', () => { + const action = { + type: LOGOUT_SUCCESS, + response: null + } + + const expectedState = null; + + const newState = profile(initialState, action); + expect(newState).toEqual(expectedState); + }); + + it('should handle PROFILE_REQUEST_SUCCESS', () => { + const action = { + type: PROFILE_REQUEST_SUCCESS, + response: { + name: 'John Doe', + email: '', + phone: '', + address: '', + city: '' + } + } + + const expectedState = { + name: 'John Doe', + email: '', + phone: '', + address: '', + city: '' + }; + + const newState = profile(initialState, action); + expect(newState).toEqual(expectedState); + }) +}) \ No newline at end of file diff --git a/src/tests/services/reducers/query-loading-reducer.spec.ts b/src/tests/services/reducers/query-loading-reducer.spec.ts new file mode 100644 index 000000000..ac62da207 --- /dev/null +++ b/src/tests/services/reducers/query-loading-reducer.spec.ts @@ -0,0 +1,105 @@ +import { isLoadingData } from '../../../app/services/reducers/query-loading-reducers'; +import { + FETCH_SCOPES_ERROR, + GET_CONSENT_ERROR, + PROFILE_REQUEST_ERROR, + PROFILE_REQUEST_SUCCESS, + QUERY_GRAPH_RUNNING, + QUERY_GRAPH_STATUS, + QUERY_GRAPH_SUCCESS +} from '../../../app/services/redux-constants'; + +describe('Query loading reducer', () => { + it('should return false in case of get_consent error', () => { + const initialState = { + pending: false, + data: {}, + error: null + }; + const dummyAction = { + type: GET_CONSENT_ERROR, + response: null + }; + + const newState = isLoadingData(initialState, dummyAction); + expect(newState).toEqual(false); + }); + + it('should return false in case of QUERY_GRAPH_SUCCESS', () => { + const initialState = {}; + const dummyAction = { + type: QUERY_GRAPH_SUCCESS, + response: null + }; + + const newState = isLoadingData(initialState, dummyAction); + expect(newState).toEqual(false); + }); + + it('should return false in case of FETCH_SCOPES_ERROR', () => { + const initialState = {}; + const dummyAction = { + type: FETCH_SCOPES_ERROR, + response: null + } + + const newState = isLoadingData(initialState, dummyAction); + expect(newState).toEqual(false); + }); + + it('should retur false in case of PROFILE_REQUEST_SUCCESS', () => { + const initialState = {}; + const dummyAction = { + type: PROFILE_REQUEST_SUCCESS, + response: null + } + + const newState = isLoadingData(initialState, dummyAction); + expect(newState).toEqual(false); + }); + + it('should return false in case of PROFILE_REQUEST_SUCCESS', () => { + const initialState = {}; + const dummyAction = { + type: PROFILE_REQUEST_SUCCESS, + response: null + } + + const newState = isLoadingData(initialState, dummyAction); + expect(newState).toEqual(false); + }); + + it('should return unaltered state by default', () => { + const initialState = { + pending: false, + data: {}, + error: null + }; + const dummyAction = { + type: '', + response: null + } + + const newState = isLoadingData(initialState, dummyAction); + expect(newState).toEqual(initialState); + }); + + it('should return a response in case of QUERY_GRAPH_RUNNING', () => { + const initialState = { + pending: false, + data: {}, + error: null + }; + const dummyAction = { + type: QUERY_GRAPH_RUNNING, + response: { + pending: true, + data: {}, + error: null + } + } + + const newState = isLoadingData(initialState, dummyAction); + expect(newState).toEqual(dummyAction.response); + }); +}) \ No newline at end of file diff --git a/src/tests/services/reducers/query-runner-reducers.spec.tsx b/src/tests/services/reducers/query-runner-reducers.spec.tsx index 669ea158c..8fa3f8d18 100644 --- a/src/tests/services/reducers/query-runner-reducers.spec.tsx +++ b/src/tests/services/reducers/query-runner-reducers.spec.tsx @@ -1,6 +1,7 @@ import { graphResponse } from '../../../app/services/reducers/query-runner-reducers'; import { queryRunnerStatus } from '../../../app/services/reducers/query-runner-status-reducers'; -import { CLEAR_QUERY_STATUS, QUERY_GRAPH_STATUS, QUERY_GRAPH_SUCCESS } from '../../../app/services/redux-constants'; +import { CLEAR_QUERY_STATUS, QUERY_GRAPH_RUNNING, QUERY_GRAPH_STATUS, + QUERY_GRAPH_SUCCESS, VIEW_HISTORY_ITEM_SUCCESS } from '../../../app/services/redux-constants'; import { IGraphResponse } from '../../../types/query-response'; describe('Query Runner Reducer', () => { @@ -47,4 +48,30 @@ describe('Query Runner Reducer', () => { expect(newState).toEqual(null); }); + + it('should handle VIEW_HISTORY_ITEM_UCCESS', () => { + const initialState: IGraphResponse = { body: undefined, headers: undefined }; + const mockResponse = { + body: { + displayName: 'Megan Bowen' + }, + headers: { + 'content-type': 'application-json' + } + }; + + const action = { type: VIEW_HISTORY_ITEM_SUCCESS, response: mockResponse }; + + const newState = queryRunnerStatus(initialState, action); + expect(newState).toEqual(null); + }); + + it('should handle QUERY_GRAPH_RUNNING', () => { + const initialState: IGraphResponse = { body: undefined, headers: undefined }; + const expectedState = initialState; + + const action = { type: QUERY_GRAPH_RUNNING, response: '' }; + const newState = graphResponse(initialState, action); + expect(newState).toEqual(expectedState); + }) }); diff --git a/src/tests/services/reducers/request-history-reducers.spec.tsx b/src/tests/services/reducers/request-history-reducers.spec.tsx index 4633a6acf..6dc589bee 100644 --- a/src/tests/services/reducers/request-history-reducers.spec.tsx +++ b/src/tests/services/reducers/request-history-reducers.spec.tsx @@ -1,5 +1,6 @@ import { history } from '../../../app/services/reducers/request-history-reducers'; -import { ADD_HISTORY_ITEM_SUCCESS } from '../../../app/services/redux-constants'; +import { ADD_HISTORY_ITEM_SUCCESS, REMOVE_ALL_HISTORY_ITEMS_SUCCESS, + REMOVE_HISTORY_ITEM_SUCCESS } from '../../../app/services/redux-constants'; describe('Request History Reducer', () => { @@ -24,4 +25,44 @@ describe('Request History Reducer', () => { expect(newState).toEqual([dummy]); }); + it('should handle REMOVE_HISTORY_ITEM_SUCCESS', () => { + const initialState = [ + 1, 2 + ] + + const expectedState = [ + 1 + ] + + const action = { + type: REMOVE_HISTORY_ITEM_SUCCESS, + response: 2 + } + + const newState = history(initialState, action); + expect(newState).toEqual(expectedState); + }); + + it('should handle REMOVE_ALL_HISTORY_ITEMS_SUCCESS', () => { + const initialState = [ + { + index: 0 + } + ] + + const expectedState: any = [ + { + index: 0 + } + ] + + const action = { + type: REMOVE_ALL_HISTORY_ITEMS_SUCCESS, + response: initialState + } + + const newState = history(initialState, action); + expect(newState).toEqual(expectedState); + }) + }); diff --git a/src/tests/services/reducers/resources-reducer.spec.ts b/src/tests/services/reducers/resources-reducer.spec.ts index 7bc662a99..e840858c2 100644 --- a/src/tests/services/reducers/resources-reducer.spec.ts +++ b/src/tests/services/reducers/resources-reducer.spec.ts @@ -10,7 +10,7 @@ import { RESOURCEPATHS_DELETE_SUCCESS } from '../../../app/services/redux-constants'; import content from '../../../app/utils/resources/resources.json'; -import { IResource, IResources } from '../../../types/resources'; +import { IResource, IResourceLink, IResources, ResourceLinkType } from '../../../types/resources'; const res = JSON.parse(JSON.stringify(content)) as IResource; const middlewares = [thunk]; @@ -45,6 +45,24 @@ const paths = [{ links: [] }]; +const resourceLinks: IResourceLink[] = [ + { + labels: [ + { name: 'v1.0', methods: ['Get', 'Post'] } + ], + key: '5-issues', + url: '/issues', + name: 'issues (1)', + icon: 'LightningBolt', + isExpanded: true, + level: 7, + parent: '/', + paths: ['/'], + type: ResourceLinkType.PATH, + links: [] + } +]; + describe('Resources Reducer', () => { it('should return initial state', () => { const dummyAction = { type: 'Dummy', response: { dummy: 'Dummy' } }; @@ -103,4 +121,36 @@ describe('Resources Reducer', () => { expect(store.getActions()).toEqual(expectedActions); }); + it('should handle RESOURCEPATHS_ADD_SUCCESS and return new state with the paths', () => { + const newState = { ...initialState }; + newState.paths = resourceLinks; + const action_ = { + type: RESOURCEPATHS_ADD_SUCCESS, + response: paths + } + const state_ = resources(newState, action_); + expect(state_.paths).toEqual(resourceLinks); + }); + + it('should handle RESOURCEPATHS_DELETE_SUCCESS and return new state with no resource paths', () => { + const newState = { ...initialState }; + newState.paths = resourceLinks; + const action_ = { + type: RESOURCEPATHS_DELETE_SUCCESS, + response: paths + } + const state_ = resources(newState, action_); + expect(state_.paths).toEqual([]); + }); + + it('should return unchanged state if no relevant action is passed', () => { + const newState = { ...initialState }; + const action_ = { + type: 'Dummy', + response: { dummy: 'Dummy' } + } + const state_ = resources(newState, action_); + expect(state_).toEqual(newState); + }); + }); diff --git a/src/tests/services/reducers/snippets-reducer.spec.ts b/src/tests/services/reducers/snippets-reducer.spec.ts index 333fde8ce..5a0ce5241 100644 --- a/src/tests/services/reducers/snippets-reducer.spec.ts +++ b/src/tests/services/reducers/snippets-reducer.spec.ts @@ -1,5 +1,5 @@ import { snippets } from '../../../app/services/reducers/snippet-reducer'; -import { GET_SNIPPET_SUCCESS } from '../../../app/services/redux-constants'; +import { GET_SNIPPET_ERROR, GET_SNIPPET_SUCCESS } from '../../../app/services/redux-constants'; describe('Graph Explorer Snippet Reducer', () => { it('should set csharp code snippet', () => { @@ -23,4 +23,26 @@ describe('Graph Explorer Snippet Reducer', () => { const newState = snippets(initialState, dummyAction); expect(newState).toEqual(response); }); + + it('should handle GET_SNIPPET_ERROR', () => { + const initialState = { + pending: false, + data: {}, + error: null + } + + const action = { + type: GET_SNIPPET_ERROR, + response: 'error' + } + + const expectedAction = { + pending: false, + data: null, + error: 'error' + } + + const newState = snippets(initialState, action); + expect(newState).toEqual(expectedAction); + }) }); diff --git a/src/tests/services/reducers/togle-sidebar-reducer.spec.ts b/src/tests/services/reducers/togle-sidebar-reducer.spec.ts new file mode 100644 index 000000000..082269105 --- /dev/null +++ b/src/tests/services/reducers/togle-sidebar-reducer.spec.ts @@ -0,0 +1,109 @@ +import { sidebarProperties } from '../../../app/services/reducers/toggle-sidebar-reducer'; +import { + SET_SAMPLE_QUERY_SUCCESS, TOGGLE_SIDEBAR_SUCCESS, + VIEW_HISTORY_ITEM_SUCCESS +} from '../../../app/services/redux-constants'; + + +describe('Toggle sidebar', () => { + it('should handle TOGGLE_SIDEBAR_SUCCESS', () => { + const initialState = { + showSidebar: false, + mobileScreen: false + } + + const action = { + type: TOGGLE_SIDEBAR_SUCCESS, + response: { + showSidebar: true, + mobileScreen: false + } + } + + const expectedState = { + showSidebar: true, + mobileScreen: false + } + const newState = sidebarProperties(initialState, action); + expect(newState).toEqual(expectedState); + }); + + it('should handle SET_SAMPLE_QUERY_SUCCESS', () => { + const initialState = { + showSidebar: true, + mobileScreen: true + } + + const action = { + type: SET_SAMPLE_QUERY_SUCCESS, + response: {} + } + + const expectedState = { + showSidebar: false, + mobileScreen: true + } + const newState = sidebarProperties(initialState, action); + expect(newState.showSidebar).toEqual(expectedState.showSidebar); + }); + + it('should handle VIEW_HISTORY_ITEM_SUCCESS', () => { + const initialState = { + showSidebar: true, + mobileScreen: true + } + + const action = { + type: VIEW_HISTORY_ITEM_SUCCESS, + response: { + showSidebar: false, + mobileScreen: true + } + } + + const expectedState = { + showSidebar: false, + mobileScreen: true + } + const newState = sidebarProperties(initialState, action); + expect(newState.showSidebar).toEqual(expectedState.showSidebar); + }); + + it('should return default state', () => { + const initialState = { + showSidebar: false, + mobileScreen: false + } + + const action = { + type: 'NOT_A_VALID_ACTION', + response: '' + } + + const expectedState = { + showSidebar: false, + mobileScreen: false + } + const newState = sidebarProperties(initialState, action); + expect(newState).toEqual(expectedState); + }); + + it('should handle QUERY_GRAPH_RUNNING', () => { + const initialState = { + showSidebar: true, + mobileScreen: true + } + const action = { + type: 'QUERY_GRAPH_RUNNING', + response: '' + } + + const expectedState = { + showSidebar: false, + mobileScreen: true + } + const newState = sidebarProperties(initialState, action); + expect(newState).toEqual(expectedState); + }) + +}) \ No newline at end of file diff --git a/src/tests/telemetry/filters.spec.ts b/src/tests/telemetry/filters.spec.ts new file mode 100644 index 000000000..e1759c1e7 --- /dev/null +++ b/src/tests/telemetry/filters.spec.ts @@ -0,0 +1,111 @@ +import { ITelemetryItem } from '@microsoft/applicationinsights-web'; +import { + filterTelemetryTypes, filterRemoteDependencyData, addCommonTelemetryItemProperties, sanitizeTelemetryItemUriProperty, + sanitizeStackTrace +} from '../../../src/telemetry/filters'; + + +describe('Tests telemetry filters', () => { + it('Ensures telemetry types to include are correct', () => { + // Arrange + const envelope: ITelemetryItem = { + ver: '1.0', + name: 'test', + time: '', + iKey: '', + baseType: 'EventData' + } + + // Act + const result = filterTelemetryTypes(envelope); + + // Assert + expect(result).toBe(true); + + }); + + it('Returns true by default', () => { + // Arrange + const envelope: ITelemetryItem = { + ver: '1.0', + name: 'test', + time: '', + iKey: '', + baseType: 'EventData' + } + + // Act + const result = filterRemoteDependencyData(envelope); + + // Assert + expect(result).toBe(true); + }); + + it('Adds common telemetry item properties', () => { + // Arrange + const envelope: ITelemetryItem = { + ver: '1.0', + name: 'test', + time: '', + iKey: '', + baseType: 'EventData' + } + + // Act + const result = addCommonTelemetryItemProperties(envelope); + + // Assert + expect(result).toBe(true); + }); + + it('Sanitizes telemetry item uri property', () => { + // Arrange + const envelope: ITelemetryItem = { + ver: '1.0', + name: 'test', + time: '', + iKey: '', + baseType: 'EventData', + baseData: { + uri: 'https://test.com/test?#test=test' + } + } + + // Act + const result = sanitizeTelemetryItemUriProperty(envelope); + + // Assert + expect(result).toBe(true); + }) + + it('Sanitizes stack trace', () => { + // Arrange + const envelope: ITelemetryItem = { + ver: '1.0', + name: 'test', + time: '', + iKey: '', + baseType: 'ExceptionData', + baseData: { + exceptions: [ + { + parsedStack: [ + { + fileName: 'webpack-internal', + assembly: 'Assembly' + } + ], + stack: '\n First line of test, \n Second line of test' + } + ] + } + } + + // Act + const result = sanitizeStackTrace(envelope); + + // Assert + expect(result).toBe(true); + }) + +}) \ No newline at end of file diff --git a/src/tests/utils/auto-complete.util.spec.ts b/src/tests/utils/auto-complete.util.spec.ts new file mode 100644 index 000000000..9ffc9aeae --- /dev/null +++ b/src/tests/utils/auto-complete.util.spec.ts @@ -0,0 +1,80 @@ +import { + cleanUpSelectedSuggestion, getParametersWithVerb, getLastCharacterOf, getLastSymbolInUrl, + getFilteredSuggestions +} from '../../app/views/query-runner/query-input/auto-complete/auto-complete.util'; +import { IAutoCompleteProps } from '../../types/auto-complete'; + +describe('Tests autocomplete utils', () => { + it('Tests cleanUpSelectedSuggestion', () => { + const compare = 'test'; + const userInput = 'test'; + const selected = 'test'; + const result = cleanUpSelectedSuggestion(compare, userInput, selected); + expect(result).toEqual('test'); + }); + + it('Tests cleanUpSelectedSuggestion', () => { + const compare = ''; + const userInput = 'test'; + const selected = 'test'; + const result = cleanUpSelectedSuggestion(compare, userInput, selected); + expect(result).toEqual('testtest'); + }); + + it('Tests getParametersWithVerb', () => { + const properties: IAutoCompleteProps = { + autoCompleteOptions: { + parameters: [ + { + verb: 'get' + }, + { + verb: 'post' + }, + { + verb: 'put' + }, + { + verb: 'delete' + } + ], + url: 'https://graph.microsoft.com/' + }, + sampleQuery: { + selectedVerb: 'GET', + sampleUrl: 'https://graph.microsoft.com/v1.0/me', + selectedVersion: 'v1.0', + sampleHeaders: [] + }, + fetchingSuggestions: false, + autoCompleteError: false, + contentChanged: jest.fn(), + runQuery: jest.fn(), + suggestions: [] + }; + const result = getParametersWithVerb(properties); + expect(result).toEqual({ + verb: 'get' + }); + }); + + + it('Tests getLastCharacterOf', () => { + const url = 'https://graph.microsoft.com/v1.0/me'; + const result = getLastCharacterOf(url); + expect(result).toEqual('e'); + }); + + it('Tests getFilteredSuggestions', () => { + const suggestions = ['test', 'test2', 'test3']; + const userInput = 'test'; + const result = getFilteredSuggestions(userInput, suggestions); + expect(result).toEqual(['test', 'test2', 'test3']); + }) + + it('Tests getLastSymbolInUrl', () => { + const url = 'https://graph.microsoft.com/v1.0/me'; + const result = getLastSymbolInUrl(url); + expect(result).toEqual({ key: '/', value: 32 }); + }) +}) \ No newline at end of file diff --git a/src/tests/utils/external-link-validation.spec.ts b/src/tests/utils/external-link-validation.spec.ts index 421956974..65a1d39a6 100644 --- a/src/tests/utils/external-link-validation.spec.ts +++ b/src/tests/utils/external-link-validation.spec.ts @@ -1,6 +1,6 @@ -import { isValidHttpsUrl } from '../../app/utils/external-link-validation'; - +import { isValidHttpsUrl, validateExternalLink } from '../../app/utils/external-link-validation'; describe('External link', () => { + const links = [ { url: 'data:,%20{%22sampleQueries%22:[{%22id%22:%22%22,%22category%22:%22TEST%20%22,%22method%22:%22' + @@ -21,4 +21,18 @@ describe('External link', () => { expect(isValid).toEqual(link.result); }); }); + + it('Tests validateExternal link which throws an error for a failed fetch operation', () => { + const url = 'https://someurl'; + const componentName = 'TestComponent'; + const sampleId = '2345'; + const sampleQuery = { + selectedVerb: 'GET', + sampleUrl: '/v1.0/me', + selectedVersion: 'v1.0', + sampleBody: '', + sampleHeaders: [] + } + return expect(validateExternalLink(url, componentName, sampleId, sampleQuery)).resolves.toBe(undefined); + }) }); diff --git a/src/tests/utils/get-messages.ts b/src/tests/utils/get-messages.ts new file mode 100644 index 000000000..3f1d3df26 --- /dev/null +++ b/src/tests/utils/get-messages.ts @@ -0,0 +1,3 @@ +import messages from '../../../src/messages'; + +export const messages_ = messages; diff --git a/src/tests/utils/graph-toolkit-lookup.spec.ts b/src/tests/utils/graph-toolkit-lookup.spec.ts new file mode 100644 index 000000000..116ce2758 --- /dev/null +++ b/src/tests/utils/graph-toolkit-lookup.spec.ts @@ -0,0 +1,37 @@ +import { lookupToolkitUrl } from '../../app/utils/graph-toolkit-lookup'; + +describe('Tests lookToolkitUrl', () => { + it('Returns valid toolkit url depending on sampleQuery passed', () => { + // Arrange + const sampleQuery = { + selectedVerb: 'GET', + sampleUrl: 'https://graph.microsoft.com/v1.0/me/', + selectedVersion: 'v1.0', + sampleBody: '', + sampleHeaders: [] + } + // Act + const result = lookupToolkitUrl(sampleQuery); + + // Assert + expect(result.toolkitUrl). + toBe('https://mgt.dev/iframe.html?id=components-mgt-person-card--person-card&source=ge'); + }); + + it('Returns null in toolkiturl property when toolkit url is unavailable', () => { + // Arrange + const sampleQuery = { + selectedVerb: 'GET', + sampleUrl: 'https://graph.microsoft.com/v1.0', + selectedVersion: 'v1.0', + sampleBody: '', + sampleHeaders: [] + } + // Act + const result = lookupToolkitUrl(sampleQuery); + + // Assert + expect(result.toolkitUrl).toBe(null); + }) +} +) diff --git a/src/tests/utils/theme-utils.spec.ts b/src/tests/utils/theme-utils.spec.ts new file mode 100644 index 000000000..4fb1d6a6b --- /dev/null +++ b/src/tests/utils/theme-utils.spec.ts @@ -0,0 +1,8 @@ +import { saveTheme, readTheme } from '../../../src/themes/theme-utils'; +describe('Tests theme utils', () => { + it('Saves theme to local storage then retrieves the saved theme', () => { + const theme = 'dark'; + saveTheme(theme); + expect(readTheme()).toEqual(theme); + }) +}) \ No newline at end of file diff --git a/src/tests/utils/token-helpers.spec.ts b/src/tests/utils/token-helpers.spec.ts new file mode 100644 index 000000000..df8592854 --- /dev/null +++ b/src/tests/utils/token-helpers.spec.ts @@ -0,0 +1,36 @@ +import { getTokenSubstituteValue, substituteTokens } from '../../app/utils/token-helpers'; +import { IQuery } from '../../types/query-runner'; + +describe('Tests token helper utils', () => { + const token = { + placeholder: 'testHolder' + }; + const isAuthenticated = true; + const profile = { + tenant: { + id: 'tenantId', + name: 'tenantName' + }, + user: { + id: 'userId', + name: 'userName' + } + }; + it('Returns the value of the token', () => { + const value = getTokenSubstituteValue(token, isAuthenticated); + expect(value).toBeUndefined(); + + const unAuthenticatedValue = getTokenSubstituteValue(token, false); + expect(unAuthenticatedValue).toBeUndefined(); + }); + it('Substitutes the token with the correct value', () => { + const query: IQuery = { + selectedVerb: 'GET', + sampleUrl: '/v1.0/me', + selectedVersion: 'v1.0', + sampleBody: '', + sampleHeaders: [] + }; + substituteTokens(query, profile); + }); +}) \ No newline at end of file diff --git a/src/types/request.ts b/src/types/request.ts index 931d1c3b0..c9d1f5311 100644 --- a/src/types/request.ts +++ b/src/types/request.ts @@ -26,7 +26,7 @@ export interface IRequestComponent { dimensions: IDimensions; headers?: Header[]; intl: { - message: object; + messages: object; }; actions: { setDimensions: Function;