diff --git a/babel.config.json b/babel.config.json index f0e1a5822..dae68b118 100644 --- a/babel.config.json +++ b/babel.config.json @@ -1,5 +1,5 @@ { - "presets": ["@babel/preset-react", "@babel/env"], + "presets": ["@babel/preset-react", "@babel/env", "@babel/preset-typescript"], "plugins": [ [ "transform-inline-environment-variables", diff --git a/eslint.config.mjs b/eslint.config.mjs index 1db758d98..3a90cca9e 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -26,6 +26,8 @@ export default [ }, ...compat.extends('eslint:recommended', 'plugin:react/recommended', 'prettier'), { + // Apply only to js/jsx files while migrating + files: ['**/*.{js,jsx}'], plugins: { react, prettier, @@ -129,6 +131,9 @@ export default [ plugins: { playwright: playwright, '@typescript-eslint': typescriptEslint, + prettier, + 'unused-imports': unusedImports, + import: importPlugin, }, languageOptions: { globals: { @@ -139,7 +144,15 @@ export default [ ecmaVersion: 12, sourceType: 'module', }, + settings: { + react: { + version: 'detect', + }, + }, rules: { + 'unused-imports/no-unused-imports': 'error', + 'unused-imports/no-unused-vars': ['warn', { varsIgnorePattern: '^_', argsIgnorePattern: '^_' }], + 'no-unused-vars': 'off', ...playwright.configs.recommended.rules, 'playwright/no-conditional-in-test': 'off', 'playwright/no-conditional-expect': 'off', @@ -152,4 +165,43 @@ export default [ ], }, }, + { + // Override for files migrated to typescript + files: ['src/**/*.{ts,tsx}'], + plugins: { + '@typescript-eslint': typescriptEslint, + react, + prettier, + 'unused-imports': unusedImports, + import: importPlugin, + }, + languageOptions: { + globals: { + ...globals.node, + ...globals.jest, + ...globals.browser, + }, + parser: tsParser, + ecmaVersion: 12, + sourceType: 'module', + }, + settings: { + react: { + version: 'detect', + }, + }, + rules: { + ...typescriptEslint.configs.recommended.rules, + 'arrow-body-style': ['error', 'as-needed'], + 'react/react-in-jsx-scope': 'off', + camelcase: 'off', + 'spaced-comment': 'error', + 'prettier/prettier': ['warn', { singleQuote: true }], + 'no-duplicate-imports': 'error', + 'unused-imports/no-unused-imports': 'error', + 'unused-imports/no-unused-vars': ['warn'], + 'no-empty-pattern': ['error', { allowObjectPatternsAsParameters: true }], + '@typescript-eslint/no-explicit-any': 'error', + }, + }, ]; diff --git a/jest.config.js b/jest.config.js index 854eaa737..c817b8ed2 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,11 +1,12 @@ module.exports = { coverageDirectory: "coverage-jest", testEnvironment: "jsdom", + preset: "ts-jest", transform: { - "^.+\\.(js|jsx)$": "babel-jest", + "^.+\\.(js|jsx|ts|tsx)$": "ts-jest", }, collectCoverage: true, - collectCoverageFrom: ["src/**/*.js", "!src/**/stories/*"], + collectCoverageFrom: ["src/**/*.{js,ts,tsx}", "!src/**/stories/*"], setupFiles: ["/config/setupTests.js"], roots: ["/src/"], moduleNameMapper: { diff --git a/package-lock.json b/package-lock.json index aa50b8735..5fc685b85 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,6 +44,7 @@ "@babel/plugin-transform-runtime": "^7.29.0", "@babel/preset-env": "^7.29.0", "@babel/preset-react": "^7.28.5", + "@babel/preset-typescript": "^7.28.5", "@commitlint/cli": "^19.8.0", "@commitlint/config-conventional": "^19.8.0", "@currents/playwright": "^1.21.1", @@ -90,6 +91,7 @@ "stylelint-config-prettier-scss": "^1.0.0", "stylelint-config-recommended-scss": "^17.0.0", "stylelint-scss": "^7.0.0", + "ts-jest": "^29.4.4", "ts-patch": "^3.1.2", "typescript": "^5.4.3" } @@ -834,13 +836,13 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1745,6 +1747,26 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", + "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-unicode-escapes": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", @@ -1947,6 +1969,26 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/preset-typescript": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", + "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/runtime": { "version": "7.28.3", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz", @@ -9401,6 +9443,19 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", @@ -13455,6 +13510,38 @@ "dev": true, "license": "MIT" }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/handlebars/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/harmony-reflect": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz", @@ -17903,6 +17990,13 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -18124,6 +18218,13 @@ "node": ">=10" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -23164,6 +23265,72 @@ "typescript": ">=4.8.4" } }, + "node_modules/ts-jest": { + "version": "29.4.6", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz", + "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.8", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.3", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/ts-loader": { "version": "9.5.4", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.4.tgz", @@ -23490,6 +23657,19 @@ "node": ">=4" } }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -23596,6 +23776,20 @@ "node": ">=14.17" } }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -24518,6 +24712,13 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", diff --git a/package.json b/package.json index bebfc52e9..c8d5ffa41 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "@babel/plugin-transform-runtime": "^7.29.0", "@babel/preset-env": "^7.29.0", "@babel/preset-react": "^7.28.5", + "@babel/preset-typescript": "^7.28.5", "@commitlint/cli": "^19.8.0", "@commitlint/config-conventional": "^19.8.0", "@currents/playwright": "^1.21.1", @@ -86,7 +87,8 @@ "stylelint-config-recommended-scss": "^17.0.0", "stylelint-scss": "^7.0.0", "ts-patch": "^3.1.2", - "typescript": "^5.4.3" + "typescript": "^5.4.3", + "ts-jest": "^29.4.4" }, "scripts": { "commit": "./node_modules/cz-customizable/standalone.js", diff --git a/src/Messages.js b/src/Messages.ts similarity index 100% rename from src/Messages.js rename to src/Messages.ts diff --git a/src/PresentationalComponents/Filters/OsVersionFilter.js b/src/PresentationalComponents/Filters/OsVersionFilter.js index c92d1d26e..5ec7963ef 100644 --- a/src/PresentationalComponents/Filters/OsVersionFilter.js +++ b/src/PresentationalComponents/Filters/OsVersionFilter.js @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; import { useLoadModule } from '@scalprum/react-core'; -import { getOperatingSystems } from '../../Utilities/api'; +import { getOperatingSystems } from '../../Utilities/api/api'; const useOsVersionFilter = (currentFilter = '', apply) => { const versions = useSelector(({ entities }) => entities?.operatingSystems); diff --git a/src/PresentationalComponents/StatusReports/AdvisoriesStatusReport.js b/src/PresentationalComponents/StatusReports/AdvisoriesStatusReport.js index 9f2f3aa46..fd0a7d787 100644 --- a/src/PresentationalComponents/StatusReports/AdvisoriesStatusReport.js +++ b/src/PresentationalComponents/StatusReports/AdvisoriesStatusReport.js @@ -2,7 +2,7 @@ import React from 'react'; import propTypes from 'prop-types'; import { PowerOffIcon, SecurityIcon } from '@patternfly/react-icons'; import { intl } from '../../Utilities/IntlProvider'; -import { fetchApplicableAdvisoriesApi } from '../../Utilities/api'; +import { fetchApplicableAdvisoriesApi } from '../../Utilities/api/api'; import messages from '../../Messages'; import { CardTitle, diff --git a/src/PresentationalComponents/StatusReports/SystemsStatusReport.js b/src/PresentationalComponents/StatusReports/SystemsStatusReport.js index 50f0afe34..2d146c19b 100644 --- a/src/PresentationalComponents/StatusReports/SystemsStatusReport.js +++ b/src/PresentationalComponents/StatusReports/SystemsStatusReport.js @@ -17,7 +17,7 @@ import { Icon as PfIcon, } from '@patternfly/react-core'; import { Main } from '@redhat-cloud-services/frontend-components/Main'; -import { fetchSystems } from '../../Utilities/api'; +import { fetchSystems } from '../../Utilities/api/api'; const StatusCard = ({ title, color, Icon, value, filter, apply }) => ( diff --git a/src/SmartComponents/Advisories/Advisories.js b/src/SmartComponents/Advisories/Advisories.js index b48e85489..eb7e4c1df 100644 --- a/src/SmartComponents/Advisories/Advisories.js +++ b/src/SmartComponents/Advisories/Advisories.js @@ -15,7 +15,7 @@ import { fetchApplicableAdvisories, selectAdvisoryRow, } from '../../store/Actions/Actions'; -import { exportAdvisoriesCSV, exportAdvisoriesJSON } from '../../Utilities/api'; +import { exportAdvisoriesCSV, exportAdvisoriesJSON } from '../../Utilities/api/api'; import { createAdvisoriesRows } from '../../Utilities/DataMappers'; import { createSortBy, diff --git a/src/SmartComponents/Advisories/Advisories.test.js b/src/SmartComponents/Advisories/Advisories.test.js index 692c86712..1dfc6b2de 100644 --- a/src/SmartComponents/Advisories/Advisories.test.js +++ b/src/SmartComponents/Advisories/Advisories.test.js @@ -3,7 +3,7 @@ import { advisoryRows } from '../../Utilities/RawDataForTesting'; import configureStore from 'redux-mock-store'; import { initMocks } from '../../Utilities/unitTestingUtilities.js'; import { storeListDefaults } from '../../Utilities/constants'; -import { exportAdvisoriesCSV, exportAdvisoriesJSON, fetchIDs } from '../../Utilities/api'; +import { exportAdvisoriesCSV, exportAdvisoriesJSON, fetchIDs } from '../../Utilities/api/api'; import AsyncRemediationButton from '../Remediation/AsyncRemediationButton'; import { ComponentWithContext, @@ -20,8 +20,8 @@ jest.mock('@redhat-cloud-services/frontend-components-utilities/helpers', () => downloadFile: jest.fn(), })); -jest.mock('../../Utilities/api', () => ({ - ...jest.requireActual('../../Utilities/api'), +jest.mock('../../Utilities/api/api', () => ({ + ...jest.requireActual('../../Utilities/api/api'), exportAdvisoriesJSON: jest.fn(() => Promise.resolve({ success: true }).catch((err) => console.log(err)), ), diff --git a/src/SmartComponents/AdvisoryDetail/CveModal.test.js b/src/SmartComponents/AdvisoryDetail/CveModal.test.js deleted file mode 100644 index 41a0e266f..000000000 --- a/src/SmartComponents/AdvisoryDetail/CveModal.test.js +++ /dev/null @@ -1,103 +0,0 @@ -import configureStore from 'redux-mock-store'; -import { storeListDefaults } from '../../Utilities/constants'; -import { cveRows, readyCveRows } from '../../Utilities/RawDataForTesting'; -import { initMocks } from '../../Utilities/unitTestingUtilities.js'; -import CvesModal from './CvesModal'; -import { createCvesRows } from '../../Utilities/DataMappers'; -import { render, screen, waitFor } from '@testing-library/react'; -import { ComponentWithContext } from '../../Utilities/TestingUtilities.js'; - -initMocks(); - -jest.mock('../../Utilities/DataMappers', () => ({ - ...jest.requireActual('../../Utilities/DataMappers'), - createCvesRows: jest.fn(), -})); - -jest.mock('../../Utilities/api', () => ({ - ...jest.requireActual('../../Utilities/api'), - fetchCvesInfo: jest.fn(() => Promise.resolve('success').catch((err) => console.log(err))), -})); - -const mockState = { ...storeListDefaults, rows: cveRows }; - -const initStore = (state) => { - const mockStore = configureStore([]); - return mockStore({ CvesListStore: state }); -}; - -let store = initStore(mockState); - -beforeEach(() => { - createCvesRows.mockImplementation(() => readyCveRows); - - render( - - - , - ); -}); - -// TODO: convert disabled tests to RTL after react&paterrnfly migration -describe('CveModal.js', () => { - it('should render the CVEs modal', async () => { - await waitFor(() => screen.getByText('CVEs')); - }); - // it('should set rows to undefined to close the modal', () => { - // screen.debug(undefined, 30000); - // // const handleClose = wrapper.find('Modal').props().onClose; - // // handleClose(); - // // wrapper.update(); - // // expect(wrapper.find('TableView').exists()).toBeFalsy(); - // }); - - // it('should handle page change', () => { - // let tempWrapper; - // tempWrapper = mount( - // - // - // - // ); - - // const handlePageChange = tempWrapper.find('TableView').props().onSetPage; - // act(() => handlePageChange('', 2)); - // act(() => tempWrapper.update()); - // expect(tempWrapper.find('TableView').props().store.rows).toEqual( - // [{ cells: [{ title: '[Object] ' }, { title: '[Object]', value: 'Moderate' }, - // { title: '7.3' }], id: 'CVE-2021-29931', key: 'CVE-2021-29931' }, - // { cells: [{ title: '[Object] ' }, { title: '[Object]', value: 'Moderate' }, - // { title: '7.3' }], id: 'CVE-2021-29932', key: 'CVE-2021-29932' }] - // ); - // }); - - // it('should handle perPage change', () => { - // let tempWrapper; - // tempWrapper = mount( - // - // - // - // ); - - // const handlePerPageChange = tempWrapper.find('TableView').props().onPerPageSelect; - // act(() => handlePerPageChange('', 20)); - // act(() => tempWrapper.update()); - // expect(tempWrapper.find('TableView').props().store.rows).toEqual(readyCveRows); - // }); - - // it('should handle sorting', () => { - // let tempWrapper; - // tempWrapper = mount( - // - // - // ); - - // const handleSort = tempWrapper.find('TableView').props().onSort; - // act(() => handleSort('', 0, 'desc')); - // act(() => tempWrapper.update()); - - // expect(tempWrapper.find('TableView').props().store.rows). - // toEqual(readyCveRows.slice(0, 10).sort(({ id: aId }, { id: bId }) => { - // return !aId.localeCompare(bId); - // })); - // }); -}); diff --git a/src/SmartComponents/AdvisoryDetail/CvesModal.test.tsx b/src/SmartComponents/AdvisoryDetail/CvesModal.test.tsx new file mode 100644 index 000000000..44da60c45 --- /dev/null +++ b/src/SmartComponents/AdvisoryDetail/CvesModal.test.tsx @@ -0,0 +1,89 @@ +import React from 'react'; +import configureStore from 'redux-mock-store'; +import { storeListDefaults } from '../../Utilities/constants'; +import { cveRows } from '../../Utilities/RawDataForTesting'; +import { initMocks } from '../../Utilities/unitTestingUtilities.js'; +import CvesModal from './CvesModal'; +import { createCvesRows } from '../../Utilities/DataMappers'; +import { render, screen, waitFor } from '@testing-library/react'; +import { ComponentWithContext } from '../../Utilities/TestingUtilities.js'; +import userEvent from '@testing-library/user-event'; + +initMocks(); + +jest.mock('../../Utilities/DataMappers', () => ({ + ...jest.requireActual('../../Utilities/DataMappers'), + createCvesRows: jest.fn(), +})); + +jest.mock('../../Utilities/api/vulnerabilityApi', () => ({ + ...jest.requireActual('../../Utilities/api/vulnerabilityApi'), + fetchCvesInfo: jest.fn(() => Promise.resolve('success').catch((err) => console.log(err))), +})); + +const actualDataMappers = jest.requireActual('../../Utilities/DataMappers'); + +const mockState = { ...storeListDefaults, rows: cveRows, status: 'fulfilled' }; + +const initStore = (state) => { + const mockStore = configureStore([]); + return mockStore({ CvesListStore: state }); +}; + +let store = initStore(mockState); + +let cveIds: Array = []; +cveRows.forEach((cve) => { + cveIds.push(cve.id); +}); + +const renderComponent = async () => { + render( + + + , + ); +}; + +beforeEach(() => { + (createCvesRows as jest.Mock).mockImplementation(actualDataMappers.createCvesRows); + renderComponent(); +}); + +describe('CvesModal', () => { + it('should render the CVEs modal', async () => { + await waitFor(() => expect(screen.getByText('CVEs'))); + await waitFor(() => expect(screen.findByPlaceholderText('Filter by CVE ID'))); + await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument()); + }); + + it('should close the modal when user clicks close', async () => { + await userEvent.click(screen.getByRole('button', { name: 'Close' })); + await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument()); + }); + + it('should handle page change', async () => { + const nextPageButtons = await screen.findAllByRole('button', { name: 'Go to next page' }); + await userEvent.click(nextPageButtons[0]); + expect(screen.getByText(cveRows[cveRows.length - 1].id)).toBeInTheDocument(); + expect(screen.getByText(cveRows[cveRows.length - 2].id)).toBeInTheDocument(); + expect(screen.queryByText(cveRows[cveRows.length - 3].id)).not.toBeInTheDocument(); + }); + + it('should handle per page change', async () => { + let rows = document.querySelectorAll('tbody tr'); + expect(rows).toHaveLength(10); + await userEvent.click(document.querySelector('#options-menu-top-toggle') as HTMLElement); + await userEvent.click(screen.getByRole('menuitem', { name: '20 per page' })); + rows = document.querySelectorAll('tbody tr'); + expect(rows).toHaveLength(cveRows.length); + }); + + it('should handle sorting', async () => { + let rows = document.querySelectorAll('tbody tr'); + expect(rows[0]).toHaveTextContent(cveRows[0].id); + await userEvent.click(screen.getByRole('button', { name: 'CVE ID' })); + rows = document.querySelectorAll('tbody tr'); + expect(rows[0]).toHaveTextContent(cveRows[cveRows.length - 1].id); + }); +}); diff --git a/src/SmartComponents/AdvisoryDetail/CvesModal.js b/src/SmartComponents/AdvisoryDetail/CvesModal.tsx similarity index 70% rename from src/SmartComponents/AdvisoryDetail/CvesModal.js rename to src/SmartComponents/AdvisoryDetail/CvesModal.tsx index 5113de20e..85580339d 100644 --- a/src/SmartComponents/AdvisoryDetail/CvesModal.js +++ b/src/SmartComponents/AdvisoryDetail/CvesModal.tsx @@ -1,38 +1,43 @@ -import React, { useState, useMemo } from 'react'; +import React, { useState, useMemo, useEffect } from 'react'; import { Modal } from '@patternfly/react-core/deprecated'; import messages from '../../Messages'; import { intl } from '../../Utilities/IntlProvider'; import TableView from '../../PresentationalComponents/TableView/TableView'; import searchFilter from '../../PresentationalComponents/Filters/SearchFilter'; import { cvesTableColumns } from '../../PresentationalComponents/TableView/TableViewAssets'; -import { useDispatch, useSelector } from 'react-redux'; -import { fetchCves } from '../../store/Actions/Actions'; -import propTypes from 'prop-types'; +import { fetchCves } from '../../store/Actions/VulnerabilityActions'; import { createCvesRows } from '../../Utilities/DataMappers'; -import { sortCves } from '..//../Utilities/Helpers'; +import { sortCves } from '../../Utilities/Helpers'; import { SortByDirection } from '@patternfly/react-table'; +import { CveItem } from '../../Utilities/api/vulnerabilityApi'; +import { useAppDispatch, useAppSelector } from '../../store/hooks'; -const CvesModal = ({ cveIds }) => { - const dispatch = useDispatch(); - const [cves, setCves] = useState([]); - const [rows, setRows] = useState([]); +interface CvesModalProps { + cveIds: Array; +} + +const CvesModal = ({ cveIds }: CvesModalProps) => { + const [cves, setCves] = useState>([]); + const [rows, setRows] = useState>([]); const [page, setPage] = useState(1); const [perPage, setPerPage] = useState(10); - const [search, setSearch] = useState(undefined); + const [search, setSearch] = useState(''); const [sortBy, setSortBy] = useState({ direction: SortByDirection.asc, index: 0, }); - const data = useSelector(({ CvesListStore }) => CvesListStore.rows); + const data = useAppSelector(({ CvesListStore }) => CvesListStore.rows); + + const status = useAppSelector(({ CvesListStore }) => CvesListStore.status); - const status = useSelector(({ CvesListStore }) => CvesListStore.status); + const dispatch = useAppDispatch(); - React.useEffect(() => { + useEffect(() => { dispatch(fetchCves({ cveIds })); }, []); - React.useMemo(() => { + useMemo(() => { setRows(cves.slice((page - 1) * perPage, page * perPage)); }, [cves, page, perPage, sortBy]); @@ -40,7 +45,7 @@ const CvesModal = ({ cveIds }) => { const sortedCves = (search !== undefined && search !== '' && - data.filter((cve) => { + data.filter((cve: CveItem) => { const { attributes: { synopsis }, } = cve; @@ -52,24 +57,24 @@ const CvesModal = ({ cveIds }) => { }, [search, data]); const handleClose = () => { - setRows(undefined); + setRows([]); }; - const handleFilter = ({ search }) => { + const handleFilter = ({ search }: { search: string }) => { setPage(page); setSearch(search); }; - const handlePageChange = (_, page) => { + const handlePageChange = (_, page: number) => { setPage(page); }; - const handlePerPageChange = (_, perPage) => { + const handlePerPageChange = (_, perPage: number) => { setPage(1); setPerPage(perPage); }; - const handleSort = (_, index, direction) => { + const handleSort = (_, index: number, direction: SortByDirection) => { const { sortBy, sortedCves } = sortCves(cves, index, direction); setSortBy(sortBy); @@ -81,7 +86,7 @@ const CvesModal = ({ cveIds }) => { 0} onClose={handleClose} > { ); }; -CvesModal.propTypes = { - cveIds: propTypes.array, -}; - export default CvesModal; diff --git a/src/SmartComponents/AdvisorySystems/AdvisorySystems.test.js b/src/SmartComponents/AdvisorySystems/AdvisorySystems.test.js index de1c12e31..c0da34b3e 100644 --- a/src/SmartComponents/AdvisorySystems/AdvisorySystems.test.js +++ b/src/SmartComponents/AdvisorySystems/AdvisorySystems.test.js @@ -29,7 +29,7 @@ jest.mock('../Remediation/RemediationWizard', () => ({ )), })); -jest.mock('../../Utilities/api', () => ({ +jest.mock('../../Utilities/api/api', () => ({ fetchSystems: jest.fn(() => Promise.resolve({ meta: { diff --git a/src/SmartComponents/AdvisorySystems/AdvisorySystemsTable.js b/src/SmartComponents/AdvisorySystems/AdvisorySystemsTable.js index 11bc54776..d4dd37778 100644 --- a/src/SmartComponents/AdvisorySystems/AdvisorySystemsTable.js +++ b/src/SmartComponents/AdvisorySystems/AdvisorySystemsTable.js @@ -16,7 +16,7 @@ import { exportAdvisorySystemsCSV, exportAdvisorySystemsJSON, fetchAdvisorySystems, -} from '../../Utilities/api'; +} from '../../Utilities/api/api'; import { remediationIdentifiers } from '../../Utilities/constants'; import { arrayFromObj, diff --git a/src/SmartComponents/PackageSystems/PackageSystems.js b/src/SmartComponents/PackageSystems/PackageSystems.js index 733efc46f..5df63a000 100644 --- a/src/SmartComponents/PackageSystems/PackageSystems.js +++ b/src/SmartComponents/PackageSystems/PackageSystems.js @@ -25,7 +25,7 @@ import { exportPackageSystemsJSON, fetchPackageSystems, fetchPackageVersions, -} from '../../Utilities/api'; +} from '../../Utilities/api/api'; import { remediationIdentifiers } from '../../Utilities/constants'; import { arrayFromObj, diff --git a/src/SmartComponents/PackageSystems/PackageSystems.test.js b/src/SmartComponents/PackageSystems/PackageSystems.test.js index 6bc989f02..ead3c75ab 100644 --- a/src/SmartComponents/PackageSystems/PackageSystems.test.js +++ b/src/SmartComponents/PackageSystems/PackageSystems.test.js @@ -14,8 +14,8 @@ jest.mock('@redhat-cloud-services/frontend-components-utilities/helpers', () => jest.mock('../../PresentationalComponents/Filters/OsVersionFilter'); -jest.mock('../../Utilities/api', () => ({ - ...jest.requireActual('../../Utilities/api'), +jest.mock('../../Utilities/api/api', () => ({ + ...jest.requireActual('../../Utilities/api/api'), exportPackageSystemsCSV: jest.fn(() => Promise.resolve({ success: true }).catch((err) => console.log(err)), ), diff --git a/src/SmartComponents/Packages/Packages.js b/src/SmartComponents/Packages/Packages.js index 78dbf6e63..c194f24f2 100644 --- a/src/SmartComponents/Packages/Packages.js +++ b/src/SmartComponents/Packages/Packages.js @@ -8,7 +8,7 @@ import Header from '../../PresentationalComponents/Header/Header'; import TableView from '../../PresentationalComponents/TableView/TableView'; import { packagesColumns } from '../../PresentationalComponents/TableView/TableViewAssets'; import { changePackagesListParams, fetchPackagesAction } from '../../store/Actions/Actions'; -import { exportPackagesCSV, exportPackagesJSON } from '../../Utilities/api'; +import { exportPackagesCSV, exportPackagesJSON } from '../../Utilities/api/api'; import { packagesListDefaultFilters } from '../../Utilities/constants'; import { createPackagesRows } from '../../Utilities/DataMappers'; import { createSortBy, decodeQueryparams, encodeURLParams } from '../../Utilities/Helpers'; diff --git a/src/SmartComponents/Packages/Packages.test.js b/src/SmartComponents/Packages/Packages.test.js index b589b2316..4f151fecc 100644 --- a/src/SmartComponents/Packages/Packages.test.js +++ b/src/SmartComponents/Packages/Packages.test.js @@ -6,15 +6,15 @@ import { storeListDefaults } from '../../Utilities/constants'; import { BrowserRouter as Router } from 'react-router-dom'; import { render } from '@testing-library/react'; import { systemPackages } from '../../Utilities/RawDataForTesting'; -import { exportPackagesJSON, exportPackagesCSV } from '../../Utilities/api'; +import { exportPackagesJSON, exportPackagesCSV } from '../../Utilities/api/api'; import { queryByText, queryAllByText } from '@testing-library/dom'; import { ComponentWithContext, testExport } from '../../Utilities/TestingUtilities'; import '@testing-library/jest-dom'; initMocks(); -jest.mock('../../Utilities/api', () => ({ - ...jest.requireActual('../../Utilities/api'), +jest.mock('../../Utilities/api/api', () => ({ + ...jest.requireActual('../../Utilities/api/api'), exportPackagesCSV: jest.fn(() => Promise.resolve({ success: true }).catch((err) => console.log(err)), ), diff --git a/src/SmartComponents/SystemAdvisories/SystemAdvisories.js b/src/SmartComponents/SystemAdvisories/SystemAdvisories.js index 34ebc89a2..416238d5d 100644 --- a/src/SmartComponents/SystemAdvisories/SystemAdvisories.js +++ b/src/SmartComponents/SystemAdvisories/SystemAdvisories.js @@ -17,7 +17,7 @@ import { fetchApplicableSystemAdvisories, selectSystemAdvisoryRow, } from '../../store/Actions/Actions'; -import { exportSystemAdvisoriesCSV, exportSystemAdvisoriesJSON } from '../../Utilities/api'; +import { exportSystemAdvisoriesCSV, exportSystemAdvisoriesJSON } from '../../Utilities/api/api'; import { remediationIdentifiers } from '../../Utilities/constants'; import { createSystemAdvisoriesRows } from '../../Utilities/DataMappers'; import { diff --git a/src/SmartComponents/SystemAdvisories/SystemAdvisories.test.js b/src/SmartComponents/SystemAdvisories/SystemAdvisories.test.js index 694983a6e..11e153418 100644 --- a/src/SmartComponents/SystemAdvisories/SystemAdvisories.test.js +++ b/src/SmartComponents/SystemAdvisories/SystemAdvisories.test.js @@ -2,7 +2,7 @@ import SystemAdvisories from './SystemAdvisories'; import { systemAdvisoryRows } from '../../Utilities/RawDataForTesting'; import configureStore from 'redux-mock-store'; import { initMocks } from '../../Utilities/unitTestingUtilities.js'; -import { fetchIDs } from '../../Utilities/api'; +import { fetchIDs } from '../../Utilities/api/api'; import { ComponentWithContext, testBulkSelection } from '../../Utilities/TestingUtilities.js'; import { render, waitFor, screen } from '@testing-library/react'; @@ -12,8 +12,8 @@ jest.mock('../../Utilities/Helpers', () => ({ ...jest.requireActual('../../Utilities/Helpers'), remediationProvider: jest.fn(), })); -jest.mock('../../Utilities/api', () => ({ - ...jest.requireActual('../../Utilities/api'), +jest.mock('../../Utilities/api/api', () => ({ + ...jest.requireActual('../../Utilities/api/api'), fetchIDs: jest.fn(() => Promise.resolve({ ids: [] }).catch((err) => console.log(err))), })); jest.mock('../Remediation/AsyncRemediationButton', () => ({ diff --git a/src/SmartComponents/SystemPackages/SystemPackages.js b/src/SmartComponents/SystemPackages/SystemPackages.js index 46dce062e..c7de221ad 100644 --- a/src/SmartComponents/SystemPackages/SystemPackages.js +++ b/src/SmartComponents/SystemPackages/SystemPackages.js @@ -14,7 +14,7 @@ import { fetchApplicableSystemPackages, selectSystemPackagesRow, } from '../../store/Actions/Actions'; -import { exportSystemPackagesCSV, exportSystemPackagesJSON } from '../../Utilities/api'; +import { exportSystemPackagesCSV, exportSystemPackagesJSON } from '../../Utilities/api/api'; import { remediationIdentifiers, systemPackagesDefaultFilters } from '../../Utilities/constants'; import { createSystemPackagesRows } from '../../Utilities/DataMappers'; import { arrayFromObj, createSortBy, remediationProvider } from '../../Utilities/Helpers'; diff --git a/src/SmartComponents/SystemPackages/SystemPackages.test.js b/src/SmartComponents/SystemPackages/SystemPackages.test.js index 6d872d5a1..fcf7f8b4d 100644 --- a/src/SmartComponents/SystemPackages/SystemPackages.test.js +++ b/src/SmartComponents/SystemPackages/SystemPackages.test.js @@ -3,7 +3,7 @@ import { systemPackages } from '../../Utilities/RawDataForTesting'; import configureStore from 'redux-mock-store'; import { initMocks } from '../../Utilities/unitTestingUtilities.js'; import { storeListDefaults, remediationIdentifiers } from '../../Utilities/constants'; -import { fetchIDs } from '../../Utilities/api'; +import { fetchIDs } from '../../Utilities/api/api'; import { remediationProvider } from '../../Utilities/Helpers'; import { render, waitFor, screen } from '@testing-library/react'; import { ComponentWithContext, testBulkSelection } from '../../Utilities/TestingUtilities.js'; @@ -12,8 +12,8 @@ import userEvent from '@testing-library/user-event'; initMocks(); -jest.mock('../../Utilities/api', () => ({ - ...jest.requireActual('../../Utilities/api'), +jest.mock('../../Utilities/api/api', () => ({ + ...jest.requireActual('../../Utilities/api/api'), fetchIDs: jest.fn(() => Promise.resolve({ data: [ diff --git a/src/SmartComponents/Systems/SystemsListAssets.js b/src/SmartComponents/Systems/SystemsListAssets.js index 3460f2b4d..b2e898d40 100644 --- a/src/SmartComponents/Systems/SystemsListAssets.js +++ b/src/SmartComponents/Systems/SystemsListAssets.js @@ -1,5 +1,5 @@ import React, { useCallback } from 'react'; -import { fetchApplicableSystemAdvisoriesApi } from '../../Utilities/api'; +import { fetchApplicableSystemAdvisoriesApi } from '../../Utilities/api/api'; import { remediationIdentifiers } from '../../Utilities/constants'; import { createAdvisoriesIcons, diff --git a/src/SmartComponents/Systems/SystemListAssets.test.js b/src/SmartComponents/Systems/SystemsListAssets.test.js similarity index 96% rename from src/SmartComponents/Systems/SystemListAssets.test.js rename to src/SmartComponents/Systems/SystemsListAssets.test.js index efbc860d2..0b8a4018a 100644 --- a/src/SmartComponents/Systems/SystemListAssets.test.js +++ b/src/SmartComponents/Systems/SystemsListAssets.test.js @@ -9,7 +9,7 @@ import { createUpgradableColumn, remediationProvider, } from '../../Utilities/Helpers'; -import { fetchApplicableSystemAdvisoriesApi } from '../../Utilities/api'; +import { fetchApplicableSystemAdvisoriesApi } from '../../Utilities/api/api'; import { remediationIdentifiers } from '../../Utilities/constants'; import { renderHook, waitFor } from '@testing-library/react'; @@ -19,8 +19,8 @@ jest.mock('../../Utilities/Helpers', () => ({ createUpgradableColumn: jest.fn(), remediationProvider: jest.fn(), })); -jest.mock('../../Utilities/api', () => ({ - ...jest.requireActual('../../Utilities/api'), +jest.mock('../../Utilities/api/api', () => ({ + ...jest.requireActual('../../Utilities/api/api'), fetchApplicableSystemAdvisoriesApi: jest.fn(), })); diff --git a/src/SmartComponents/Systems/SystemsMainContent.test.js b/src/SmartComponents/Systems/SystemsMainContent.test.js index e05643fb4..222be9821 100644 --- a/src/SmartComponents/Systems/SystemsMainContent.test.js +++ b/src/SmartComponents/Systems/SystemsMainContent.test.js @@ -29,7 +29,7 @@ jest.mock('../Remediation/RemediationWizard', () => ({ )), })); -jest.mock('../../Utilities/api', () => ({ +jest.mock('../../Utilities/api/api', () => ({ fetchSystems: jest.fn(() => Promise.resolve({ meta: { diff --git a/src/SmartComponents/Systems/SystemsTable.js b/src/SmartComponents/Systems/SystemsTable.js index 6869d5efc..76f3e82d0 100644 --- a/src/SmartComponents/Systems/SystemsTable.js +++ b/src/SmartComponents/Systems/SystemsTable.js @@ -8,7 +8,7 @@ import { inventoryEntitiesReducer, modifyInventory, } from '../../store/Reducers/InventoryEntitiesReducer'; -import { exportSystemsCSV, exportSystemsJSON, fetchSystems } from '../../Utilities/api'; +import { exportSystemsCSV, exportSystemsJSON, fetchSystems } from '../../Utilities/api/api'; import { systemsListDefaultFilters, NO_ADVISORIES_TEXT } from '../../Utilities/constants'; import { arrayFromObj, persistantParams } from '../../Utilities/Helpers'; import { diff --git a/src/Utilities/IntlProvider.js b/src/Utilities/IntlProvider.ts similarity index 100% rename from src/Utilities/IntlProvider.js rename to src/Utilities/IntlProvider.ts diff --git a/src/Utilities/api.js b/src/Utilities/api/api.js similarity index 90% rename from src/Utilities/api.js rename to src/Utilities/api/api.js index fecbf3cdd..aa074597b 100644 --- a/src/Utilities/api.js +++ b/src/Utilities/api/api.js @@ -1,7 +1,7 @@ import { APIFactory } from '@redhat-cloud-services/javascript-clients-shared/utils'; import apiSystemProfileGetOperatingSystem from '@redhat-cloud-services/host-inventory-client/ApiSystemProfileGetOperatingSystem'; -import axiosInstance from './axiosInterceptors'; -import { encodeApiParams, prepareEntitiesParams } from './Helpers'; +import axiosInstance from '../axiosInterceptors'; +import { encodeApiParams, prepareEntitiesParams } from '../Helpers'; const INVENTORY_API_BASE = '/api/inventory/v1'; @@ -96,26 +96,6 @@ export const fetchPackagesList = (params) => { return createApiCall('/packages', 'v3', 'get', params); }; -export const fetchCvesInfo = async ({ cveIds }) => { - const result = await fetch( - `/api/vulnerability/v1/vulnerabilities/cves?limit=${cveIds && cveIds.length}`, - { - method: 'POST', - credentials: 'include', - headers: { - Accept: 'application/vnd.api+json', - 'Content-Type': 'application/vnd.api+json', - }, - body: JSON.stringify({ cve_list: cveIds }), - }, - ) - .then((res) => res.json()) - .then((data) => data) - .catch((err) => err); - - return result; -}; - const fetchFile = (params, endpoint, type) => { endpoint = endpoint.concat(encodeApiParams(params)); return fetch('/api/patch/v3' + endpoint, { diff --git a/src/Utilities/api.test.js b/src/Utilities/api/api.test.js similarity index 98% rename from src/Utilities/api.test.js rename to src/Utilities/api/api.test.js index 586ce6cf9..81c3ee163 100644 --- a/src/Utilities/api.test.js +++ b/src/Utilities/api/api.test.js @@ -5,7 +5,7 @@ import { exportSystemsCSV, exportSystemsJSON, } from './api'; -import { initMocks } from '../Utilities/unitTestingUtilities'; +import { initMocks } from '../unitTestingUtilities'; initMocks(); diff --git a/src/Utilities/api/vulnerabilityApi.ts b/src/Utilities/api/vulnerabilityApi.ts new file mode 100644 index 000000000..687ed628f --- /dev/null +++ b/src/Utilities/api/vulnerabilityApi.ts @@ -0,0 +1,56 @@ +export interface FetchCvesInfoRequest { + cveIds: Array; +} + +export interface CveItem { + attributes: { + cvss_score: string; + impact: string; + synopsis: string; + }; + id: string; + type: string; +} + +export interface Links { + first: string; + last: string; + next?: string; + previous?: string; +} + +export interface VulnerabilityMeta { + data_format: string; + limit: number; + offset: number; + page: number; + page_size: number; + pages: number; + total_items: number; +} + +export interface FetchCvesInfoResponse { + data: Array; + links: Links; + meta: VulnerabilityMeta; +} + +export const fetchCvesInfo = async ({ cveIds }: FetchCvesInfoRequest) => { + const result: FetchCvesInfoResponse = await fetch( + `/api/vulnerability/v1/vulnerabilities/cves?limit=${cveIds && cveIds.length}`, + { + method: 'POST', + credentials: 'include', + headers: { + Accept: 'application/vnd.api+json', + 'Content-Type': 'application/vnd.api+json', + }, + body: JSON.stringify({ cve_list: cveIds }), + }, + ) + .then((res) => res.json()) + .then((data) => data) + .catch((err) => err); + + return result; +}; diff --git a/src/Utilities/hooks/useOnSelect.js b/src/Utilities/hooks/useOnSelect.js index d0a6be02b..c419f6a12 100644 --- a/src/Utilities/hooks/useOnSelect.js +++ b/src/Utilities/hooks/useOnSelect.js @@ -1,6 +1,6 @@ import { useCallback } from 'react'; import { useDispatch } from 'react-redux'; -import { fetchIDs } from '../api'; +import { fetchIDs } from '../api/api'; import { toggleAllSelectedAction } from '../../store/Actions/Actions'; import { isObject } from '../Helpers'; import { useFetchBatched } from './useFetchBatched'; diff --git a/src/Utilities/hooks/useOnSelect.test.js b/src/Utilities/hooks/useOnSelect.test.js index 0465e0738..456830ed7 100644 --- a/src/Utilities/hooks/useOnSelect.test.js +++ b/src/Utilities/hooks/useOnSelect.test.js @@ -1,13 +1,13 @@ import { act, renderHook, waitFor } from '@testing-library/react'; -import { fetchIDs } from '../api'; +import { fetchIDs } from '../api/api'; import { useOnSelect } from './useOnSelect'; jest.mock('react-redux', () => ({ ...jest.requireActual('react-redux'), useDispatch: jest.fn(() => () => {}), })); -jest.mock('../api', () => ({ - ...jest.requireActual('../api'), +jest.mock('../api/api', () => ({ + ...jest.requireActual('../api/api'), fetchIDs: jest.fn(() => Promise.resolve({ data: [{ id: 'db-item' }], diff --git a/src/store/Actions/Actions.js b/src/store/Actions/Actions.js index da8d0cb07..9821b75c9 100644 --- a/src/store/Actions/Actions.js +++ b/src/store/Actions/Actions.js @@ -6,9 +6,8 @@ import { fetchPackageDetailsApi, fetchPackagesList, fetchPackageSystems, - fetchCvesInfo, fetchSystemDetails, -} from '../../Utilities/api'; +} from '../../Utilities/api/api'; import * as ActionTypes from '../ActionTypes'; export const fetchApplicableAdvisories = (params) => ({ @@ -156,18 +155,6 @@ export const changePackageSystemsParams = (params) => ({ payload: params, }); -export const fetchCves = (params) => ({ - type: ActionTypes.FETCH_CVES_INFO, - payload: new Promise((resolve) => { - resolve(fetchCvesInfo(params)); - }).then((result) => result), -}); - -export const changeCvesListParams = (params) => ({ - type: ActionTypes.CHANGE_CVES_STORE_PARAMS, - payload: params, -}); - export const fetchSystemDetailsAction = (params) => ({ type: ActionTypes.FETCH_SYSTEM_DETAIL, payload: new Promise((resolve) => { diff --git a/src/store/Actions/VulnerabilityActions.ts b/src/store/Actions/VulnerabilityActions.ts new file mode 100644 index 000000000..884be1186 --- /dev/null +++ b/src/store/Actions/VulnerabilityActions.ts @@ -0,0 +1,18 @@ +import { + fetchCvesInfo, + FetchCvesInfoRequest, + FetchCvesInfoResponse, +} from '../../Utilities/api/vulnerabilityApi'; +import * as ActionTypes from '../ActionTypes'; + +export const fetchCves = (params: FetchCvesInfoRequest) => ({ + type: ActionTypes.FETCH_CVES_INFO, + payload: new Promise((resolve) => { + resolve(fetchCvesInfo(params)); + }).then((result) => result as FetchCvesInfoResponse), +}); + +export const changeCvesListParams = (params: { search: string }) => ({ + type: ActionTypes.CHANGE_CVES_STORE_PARAMS, + payload: params, +}); diff --git a/src/store/Reducers/CvesListStore.test.js b/src/store/Reducers/CvesListStore.test.ts similarity index 93% rename from src/store/Reducers/CvesListStore.test.js rename to src/store/Reducers/CvesListStore.test.ts index 417991d7c..f926a744c 100644 --- a/src/store/Reducers/CvesListStore.test.js +++ b/src/store/Reducers/CvesListStore.test.ts @@ -1,6 +1,8 @@ import { changeFilters, fetchFulfilled, fetchPending, fetchRejected } from './HelperReducers'; import { CvesListStore } from './CvesListStore'; import { FETCH_CVES_INFO } from '../ActionTypes'; +import { storeListDefaults } from '../../Utilities/constants'; + jest.mock('./HelperReducers', () => ({ ...jest.requireActual('./HelperReducers'), changeFilters: jest.fn(), @@ -9,7 +11,7 @@ jest.mock('./HelperReducers', () => ({ fetchRejected: jest.fn(), })); -const state = { testObj: 'testVal' }; +const state = { ...storeListDefaults }; const actionFulfilled = FETCH_CVES_INFO + '_FULFILLED'; const actionRejected = FETCH_CVES_INFO + '_REJECTED'; const actionPending = FETCH_CVES_INFO + '_PENDING'; @@ -22,10 +24,12 @@ describe('PackageListStore', () => { payload: { search: 'testSearch' }, }); }); + it('should fetch package list', () => { CvesListStore(state, { type: actionPending, payload: { search: 'testSearch' } }); expect(fetchPending).toHaveBeenCalledWith(state); }); + it('should handle rejected call', () => { CvesListStore(state, { type: actionRejected, payload: { search: 'testSearch' } }); expect(fetchRejected).toHaveBeenCalledWith(state, { diff --git a/src/store/Reducers/CvesListStore.js b/src/store/Reducers/CvesListStore.ts similarity index 100% rename from src/store/Reducers/CvesListStore.js rename to src/store/Reducers/CvesListStore.ts diff --git a/src/store/hooks.ts b/src/store/hooks.ts new file mode 100644 index 000000000..4f9e33ea4 --- /dev/null +++ b/src/store/hooks.ts @@ -0,0 +1,9 @@ +import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; +import { store } from './index'; + +export type AppStore = typeof store; +export type RootState = ReturnType; +export type AppDispatch = AppStore['dispatch']; + +export const useAppSelector: TypedUseSelectorHook = useSelector; +export const useAppDispatch: () => AppDispatch = useDispatch; diff --git a/tsconfig.json b/tsconfig.json index 90332dce9..34e2e343f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "outDir": "./dist/", "sourceMap": true, - "noImplicitAny": true, + "noImplicitAny": false, "module": "esnext", "target": "esnext", "jsx": "react",