diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index b1b87779f..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,18 +0,0 @@ -module.exports = { - root: true, - env: { - node: true, - }, - parserOptions: { - parser: '@babel/eslint-parser', - }, - extends: [ - 'eslint:recommended', - 'plugin:vue/essential', - 'plugin:prettier/recommended', - ], - rules: { - 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', - 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', - }, -}; diff --git a/.github/workflows/_meta-build.yaml b/.github/workflows/_meta-build.yaml index bb9a7b844..d088adc90 100644 --- a/.github/workflows/_meta-build.yaml +++ b/.github/workflows/_meta-build.yaml @@ -35,13 +35,27 @@ jobs: node-version: '20' cache: 'npm' - - name: Run Npm Build + - name: Install Dependencies env: CI: true run: |- npm ci + + - name: Run Npm Build + env: + CI: true + run: |- npm run build --if-present + - name: Run Cypress Tests + uses: cypress-io/github-action@v6 + env: + CI: true + timeout-minutes: 15 + with: + component: true + command: npm run cy:run:coverage --if-present + - name: Upload Artifacts uses: actions/upload-artifact@v4.6.2 with: diff --git a/.gitignore b/.gitignore index 83dde679d..9e81ac204 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ .DS_Store node_modules /dist -/coverage /tests/e2e/reports/ selenium-debug.log @@ -29,3 +28,11 @@ src/version.json # IntelliJ .idea/* !.idea/icon.svg + +# Test files +/coverage +/cypress/screenshots +/cypress/videos +/cypress/downloads +/.nyc_output +.output.txt diff --git a/.nycrc b/.nycrc new file mode 100644 index 000000000..09133f5c7 --- /dev/null +++ b/.nycrc @@ -0,0 +1,24 @@ +{ + "all": true, + "include": [ + "src/**/*.js", + "src/**/*.vue" + ], + "exclude": [ + "**/*.spec.js", + "**/*.cy.js", + "cypress/**/*.*", + "node_modules/**/*.*" + ], + "reporter": [ + "lcov", + "text", + "html" + ], + "report-dir": "coverage", + "extension": [ + ".js", + ".vue" + ], + "check-coverage": false +} diff --git a/.postcssrc.js b/.postcssrc.js deleted file mode 100644 index a47ef4f95..000000000 --- a/.postcssrc.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - plugins: { - autoprefixer: {}, - }, -}; diff --git a/babel.config.js b/babel.config.js index bcdde9806..d08fdd72b 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,12 +1,8 @@ module.exports = { - presets: [ - ['@vue/babel-preset-jsx'], - [ - '@babel/preset-env', - { - useBuiltIns: 'entry', - corejs: '3.33', - }, - ], - ], + presets: [['@vue/babel-preset-jsx']], + env: { + test: { + plugins: process.env.VITE_COVERAGE === 'true' ? ['istanbul'] : [], + }, + }, }; diff --git a/cypress.config.js b/cypress.config.js new file mode 100644 index 000000000..102af2d3f --- /dev/null +++ b/cypress.config.js @@ -0,0 +1,34 @@ +const { defineConfig } = require('cypress'); +const path = require('path'); + +module.exports = defineConfig({ + video: true, + videoCompression: 32, + trashAssetsBeforeRuns: false, + component: { + devServer: { + framework: 'vue2', + bundler: 'vite', + // TODO this is just a workaround, making the tests _more_ deterministic, but they are still non-deterministic + warmup: { + clientFiles: ['cypress/support/component.js', 'src/**/*.cy.js'], + }, + viteConfig: (viteConfig) => { + let configFile; + if (process.env.VITE_COVERAGE === 'true') { + configFile = path.resolve(__dirname, 'vite.coverage.config.js'); + } else { + configFile = path.resolve(__dirname, 'vite.config.js'); + } + + return { configFile }; + }, + }, + setupNodeEvents(on, config) { + if (process.env.VITE_COVERAGE === 'true') { + require('@cypress/code-coverage/task')(on, config); + } + return config; + }, + }, +}); diff --git a/cypress/support/component-index.html b/cypress/support/component-index.html new file mode 100644 index 000000000..5c221daf2 --- /dev/null +++ b/cypress/support/component-index.html @@ -0,0 +1,10 @@ + + + + + Cypress Component Test + + +
+ + diff --git a/cypress/support/component.js b/cypress/support/component.js new file mode 100644 index 000000000..5ed2374f4 --- /dev/null +++ b/cypress/support/component.js @@ -0,0 +1,99 @@ +import '@cypress/code-coverage/support'; +import { mount } from '@cypress/vue2'; +import Vue from 'vue'; + +import api from '@/shared/api.json'; +import oidc from '@/shared/oidc.json'; +import version from '@/version'; +import '@/validation'; +import { installBootstrap } from '@/plugins/bootstrap'; +import installPermissionDirective from '@/directives/VuePermission'; +import 'bootstrap/dist/css/bootstrap.min.css'; +import '@/assets/scss/style.scss'; + +Vue.prototype.$api = api; +Vue.prototype.$oidc = oidc; +Vue.prototype.$version = version; + +Vue.prototype.$dtrack = Vue.prototype.dtrack = { + application: 'Dependency-Track', + version: '4.8.0', + uuid: '12345-67890-abcdef', + timestamp: Date.now(), + framework: { + name: 'Alpine', + version: '3.16', + }, + database: { + productName: 'PostgreSQL', + productVersion: '14.5', + }, + systemUuid: '98765-43210-fedcba', + systemInitialDate: new Date().toISOString(), +}; + +installBootstrap(Vue); + +/** + * Enhanced mount command with common mocks + * + * @param {Object} component - The Vue component to mount + * @param {Object} options - Mount options + * @param {Object} options.mocks - Custom mocks to override defaults + */ +Cypress.Commands.add('mount', (component, options = {}) => { + const defaultPrototypeMocks = { + $i18n: Vue.observable({ + locale: 'en', + availableLocales: ['en', 'es', 'fr', 'de', 'zh', 'ja', 'hi'], + t: (key) => key, + }), + $t: (key) => key, + $router: { + afterEach: cy.stub(), + }, + }; + + const prototypeMocks = { + ...defaultPrototypeMocks, + ...(options.prototypeMocks || {}), + }; + + const localVue = Vue.extend(); + // TODO installBootstrapPageTitlePlugin(localVue, prototypeMocks.$router); + installPermissionDirective(localVue); + Object.keys(prototypeMocks).forEach((key) => { + Object.defineProperty(localVue.prototype, key, { + value: prototypeMocks[key], + writable: true, + configurable: true, + enumerable: true, + }); + }); + + return mount(component, { + ...options, + localVue, + }).then(({ wrapper }) => { + return cy.wrap(wrapper).as('vue'); + }); +}); + +Cypress.Commands.add('setToken', (permissions = []) => { + const tokenPayload = { + permissions: permissions.join(','), + }; + + // Encode the payload to Base64URL + const base64Url = Cypress.Buffer.from(JSON.stringify(tokenPayload)) + .toString('base64') + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=+$/, ''); + + const fakeToken = `fake-header.${base64Url}.fake-signature`; + + cy.window().then((win) => { + win.sessionStorage.setItem('token', fakeToken); + }); +}); diff --git a/cypress/support/utils.js b/cypress/support/utils.js new file mode 100644 index 000000000..52e176019 --- /dev/null +++ b/cypress/support/utils.js @@ -0,0 +1,31 @@ +export function genAxiosResponse(responses) { + return (url) => { + if (responses[url] === undefined) { + console.warn('axios stub for URL not available: ' + url); + return Promise.resolve({ + data: null, + statusCode: 500, + }); + } + + console.log('axios stub for URL: ' + url); + return Promise.resolve({ + data: responses[url], + }); + }; +} + +export function shouldShowModal(id) { + cy.get('@vue').then((wrapper) => { + const $root = wrapper.vm.$root; + const bvModal = $root.$bvModal; + if (bvModal) { + bvModal.show(id); + } else { + $root.$emit('bv::show::modal', id); + } + }); + + cy.get(`#${id}`).should('exist'); + cy.get(`#${id}`).should('be.visible'); +} diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 000000000..d08f49caa --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,42 @@ +import pluginVue from 'eslint-plugin-vue'; +import recommendedConfig from 'eslint-plugin-prettier/recommended'; + +export default [ + { + name: 'app/files-to-lint', + files: ['src/**/*.{js,jsx,vue,md,yaml}'], + }, + + { + languageOptions: { + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + ecmaFeatures: { + jsx: true, + }, + }, + }, + }, + + recommendedConfig, + ...pluginVue.configs['flat/vue2-essential'], + { + rules: { + 'no-var': 'error', + 'prefer-const': 'warn', + 'vue/component-tags-order': 'warn', + 'vue/order-in-components': 'warn', + 'vue/attribute-hyphenation': 'warn', + 'vue/one-component-per-file': 'warn', + 'vue/v-bind-style': 'warn', + 'vue/v-on-style': 'warn', + 'vue/v-slot-style': 'warn', + 'vue/no-unused-components': 'warn', + 'vue/no-side-effects-in-computed-properties': 'off', // FIXME enable this later (P1) + 'vue/no-mutating-props': 'off', // FIXME enable this later (P1) + 'vue/no-undef-components': 'warn', + 'vue/multi-word-component-names': 'off', // TODO enable this later (P3) + }, + }, +]; diff --git a/public/index.html b/index.html similarity index 96% rename from public/index.html rename to index.html index 6a576e19c..ab14067cd 100644 --- a/public/index.html +++ b/index.html @@ -15,6 +15,7 @@ >
+ diff --git a/src/assets/scss/style.scss b/src/assets/scss/style.scss index f1c68e7eb..7949b4512 100644 --- a/src/assets/scss/style.scss +++ b/src/assets/scss/style.scss @@ -2,9 +2,9 @@ @import "variables"; // Import styles -@import "~@coreui/coreui/scss/coreui"; +@import "@coreui/coreui/scss/coreui"; -@import '~vue-easy-pie-chart/dist/vue-easy-pie-chart.css'; +@import 'vue-easy-pie-chart/dist/vue-easy-pie-chart.css'; @import "code"; @@ -14,4 +14,4 @@ // ie fixes @import "ie-fix"; -@import "~vue-multiselect/dist/vue-multiselect.min.css"; +@import "vue-multiselect/dist/vue-multiselect.min.css"; diff --git a/src/containers/DefaultContainer.cy.js b/src/containers/DefaultContainer.cy.js new file mode 100644 index 000000000..62b5dd5b1 --- /dev/null +++ b/src/containers/DefaultContainer.cy.js @@ -0,0 +1,25 @@ +import DefaultContainer from './DefaultContainer.vue'; + +describe('DefaultContainer', () => { + it('mounts successfully', () => { + cy.setToken(); + + cy.mount(DefaultContainer, { + stubs: { + RouterView: true, + }, + prototypeMocks: { + $route: { + name: 'Home', + meta: { + sectionName: 'Test Section', + i18n: 'message.test_section', + sectionPath: '/test-section', + }, + }, + }, + }); + + cy.get('.app').should('exist'); + }); +}); diff --git a/src/containers/DefaultContainer.vue b/src/containers/DefaultContainer.vue index 98b9b369b..44a85975c 100644 --- a/src/containers/DefaultContainer.vue +++ b/src/containers/DefaultContainer.vue @@ -1,13 +1,13 @@ diff --git a/src/views/administration/configuration/Email.cy.js b/src/views/administration/configuration/Email.cy.js new file mode 100644 index 000000000..16b95cb87 --- /dev/null +++ b/src/views/administration/configuration/Email.cy.js @@ -0,0 +1,34 @@ +import Email from './Email.vue'; +import { genAxiosResponse } from '../../../../cypress/support/utils'; + +describe('Email', () => { + it('mounts successfully', () => { + cy.mount(Email, { + prototypeMocks: { + axios: { + get: genAxiosResponse({ + '/api/v1/configProperty/': [ + { + groupName: 'artifact', + propertyName: 'cyclonedx.enabled', + propertyValue: 'false', + }, + { + groupName: 'artifact', + propertyName: 'bom.validation.mode', + propertyValue: 'ENABLED', + }, + { + groupName: 'artifact', + propertyName: 'bom.validation.tags.inclusive', + propertyValue: '[]', + }, + ], + }), + }, + }, + }); + + cy.get('div.card').should('be.visible'); + }); +}); diff --git a/src/views/administration/configuration/Email.vue b/src/views/administration/configuration/Email.vue index 58d06fd1a..87870295d 100644 --- a/src/views/administration/configuration/Email.vue +++ b/src/views/administration/configuration/Email.vue @@ -98,20 +98,25 @@ diff --git a/src/views/administration/configuration/EmailTestConfigurationModal.cy.js b/src/views/administration/configuration/EmailTestConfigurationModal.cy.js new file mode 100644 index 000000000..36cc6e0bf --- /dev/null +++ b/src/views/administration/configuration/EmailTestConfigurationModal.cy.js @@ -0,0 +1,16 @@ +import { shouldShowModal } from '../../../../cypress/support/utils'; + +import EmailTestConfigurationModal from './EmailTestConfigurationModal.vue'; + +describe('EmailTestConfigurationModal', () => { + it('mounts successfully', () => { + cy.mount(EmailTestConfigurationModal, { + attachTo: document.body, + stubs: { + transition: false, + }, + }); + + shouldShowModal('emailTestConfigurationModal'); + }); +}); diff --git a/src/views/administration/configuration/EmailTestConfigurationModal.vue b/src/views/administration/configuration/EmailTestConfigurationModal.vue index 605e5df3f..986699856 100644 --- a/src/views/administration/configuration/EmailTestConfigurationModal.vue +++ b/src/views/administration/configuration/EmailTestConfigurationModal.vue @@ -17,7 +17,7 @@ tooltip="Enter an email address you control to test your send configuration." lazy="true" /> - diff --git a/src/views/administration/configuration/InternalComponents.cy.js b/src/views/administration/configuration/InternalComponents.cy.js new file mode 100644 index 000000000..98a37218b --- /dev/null +++ b/src/views/administration/configuration/InternalComponents.cy.js @@ -0,0 +1,18 @@ +import InternalComponents from './InternalComponents.vue'; +import { genAxiosResponse } from '../../../../cypress/support/utils'; + +describe('InternalComponents', () => { + it('mounts successfully', () => { + cy.mount(InternalComponents, { + prototypeMocks: { + axios: { + get: genAxiosResponse({ + '/api/v1/configProperty/': [], + }), + }, + }, + }); + + cy.get('div.card').should('be.visible'); + }); +}); diff --git a/src/views/administration/configuration/InternalComponents.vue b/src/views/administration/configuration/InternalComponents.vue index 74832bc0b..34381ca80 100644 --- a/src/views/administration/configuration/InternalComponents.vue +++ b/src/views/administration/configuration/InternalComponents.vue @@ -36,25 +36,46 @@ diff --git a/src/views/administration/configuration/JiraConfig.cy.js b/src/views/administration/configuration/JiraConfig.cy.js new file mode 100644 index 000000000..72a30bb56 --- /dev/null +++ b/src/views/administration/configuration/JiraConfig.cy.js @@ -0,0 +1,18 @@ +import JiraConfig from './JiraConfig.vue'; +import { genAxiosResponse } from '../../../../cypress/support/utils'; + +describe('JiraConfig', () => { + it('mounts successfully', () => { + cy.mount(JiraConfig, { + prototypeMocks: { + axios: { + get: genAxiosResponse({ + '/api/v1/configProperty/': [], + }), + }, + }, + }); + + cy.get('div.card').should('be.visible'); + }); +}); diff --git a/src/views/administration/configuration/JiraConfig.vue b/src/views/administration/configuration/JiraConfig.vue index 95cbaecfb..84ff47775 100644 --- a/src/views/administration/configuration/JiraConfig.vue +++ b/src/views/administration/configuration/JiraConfig.vue @@ -59,17 +59,23 @@ diff --git a/src/views/administration/configuration/Search.cy.js b/src/views/administration/configuration/Search.cy.js new file mode 100644 index 000000000..a1304329a --- /dev/null +++ b/src/views/administration/configuration/Search.cy.js @@ -0,0 +1,18 @@ +import Search from './Search.vue'; +import { genAxiosResponse } from '../../../../cypress/support/utils'; + +describe('Search', () => { + it('mounts successfully', () => { + cy.mount(Search, { + prototypeMocks: { + axios: { + get: genAxiosResponse({ + '/api/v1/configProperty/': [], + }), + }, + }, + }); + + cy.get('div').should('be.visible'); + }); +}); diff --git a/src/views/administration/configuration/Search.vue b/src/views/administration/configuration/Search.vue index 810b7a4d9..3ee23dd08 100644 --- a/src/views/administration/configuration/Search.vue +++ b/src/views/administration/configuration/Search.vue @@ -118,18 +118,23 @@ diff --git a/src/views/administration/configuration/Telemetry.cy.js b/src/views/administration/configuration/Telemetry.cy.js new file mode 100644 index 000000000..af27944f5 --- /dev/null +++ b/src/views/administration/configuration/Telemetry.cy.js @@ -0,0 +1,18 @@ +import Telemetry from './Telemetry.vue'; +import { genAxiosResponse } from '../../../../cypress/support/utils'; + +describe('Telemetry', () => { + it('mounts successfully', () => { + cy.mount(Telemetry, { + prototypeMocks: { + axios: { + get: genAxiosResponse({ + '/api/v1/configProperty/': [], + }), + }, + }, + }); + + cy.get('div.card').should('be.visible'); + }); +}); diff --git a/src/views/administration/configuration/Telemetry.vue b/src/views/administration/configuration/Telemetry.vue index b1e09703f..ccce006cf 100644 --- a/src/views/administration/configuration/Telemetry.vue +++ b/src/views/administration/configuration/Telemetry.vue @@ -44,11 +44,38 @@ diff --git a/src/views/administration/configuration/WelcomeMessage.cy.js b/src/views/administration/configuration/WelcomeMessage.cy.js new file mode 100644 index 000000000..a54fe0193 --- /dev/null +++ b/src/views/administration/configuration/WelcomeMessage.cy.js @@ -0,0 +1,18 @@ +import WelcomeMessage from './WelcomeMessage.vue'; +import { genAxiosResponse } from '../../../../cypress/support/utils'; + +describe('WelcomeMessage', () => { + it('mounts successfully', () => { + cy.mount(WelcomeMessage, { + prototypeMocks: { + axios: { + get: genAxiosResponse({ + '/api/v1/configProperty/': [], + }), + }, + }, + }); + + cy.get('div.card').should('be.visible'); + }); +}); diff --git a/src/views/administration/configuration/WelcomeMessage.vue b/src/views/administration/configuration/WelcomeMessage.vue index ba732c2c7..4ed17ad32 100644 --- a/src/views/administration/configuration/WelcomeMessage.vue +++ b/src/views/administration/configuration/WelcomeMessage.vue @@ -54,36 +54,53 @@ diff --git a/src/views/administration/integrations/FortifySsc.cy.js b/src/views/administration/integrations/FortifySsc.cy.js new file mode 100644 index 000000000..8c0e7c74e --- /dev/null +++ b/src/views/administration/integrations/FortifySsc.cy.js @@ -0,0 +1,18 @@ +import FortifySsc from './FortifySsc.vue'; +import { genAxiosResponse } from '../../../../cypress/support/utils'; + +describe('FortifySsc', () => { + it('mounts successfully', () => { + cy.mount(FortifySsc, { + prototypeMocks: { + axios: { + get: genAxiosResponse({ + '/api/v1/configProperty/': [], + }), + }, + }, + }); + + cy.get('div.card').should('be.visible'); + }); +}); diff --git a/src/views/administration/integrations/FortifySsc.vue b/src/views/administration/integrations/FortifySsc.vue index 9ef3c058b..c3901bf50 100644 --- a/src/views/administration/integrations/FortifySsc.vue +++ b/src/views/administration/integrations/FortifySsc.vue @@ -49,18 +49,23 @@ diff --git a/src/views/administration/integrations/KennaSecurity.cy.js b/src/views/administration/integrations/KennaSecurity.cy.js new file mode 100644 index 000000000..2bfc8b62c --- /dev/null +++ b/src/views/administration/integrations/KennaSecurity.cy.js @@ -0,0 +1,18 @@ +import KennaSecurity from './KennaSecurity.vue'; +import { genAxiosResponse } from '../../../../cypress/support/utils'; + +describe('KennaSecurity', () => { + it('mounts successfully', () => { + cy.mount(KennaSecurity, { + prototypeMocks: { + axios: { + get: genAxiosResponse({ + '/api/v1/configProperty/': [], + }), + }, + }, + }); + + cy.get('div.card').should('be.visible'); + }); +}); diff --git a/src/views/administration/integrations/KennaSecurity.vue b/src/views/administration/integrations/KennaSecurity.vue index 4c1618a62..e8b858ced 100644 --- a/src/views/administration/integrations/KennaSecurity.vue +++ b/src/views/administration/integrations/KennaSecurity.vue @@ -49,18 +49,23 @@ diff --git a/src/views/administration/mixins/configPropertyMixin.js b/src/views/administration/mixins/configPropertyMixin.js index 350cbb162..44a9b5175 100644 --- a/src/views/administration/mixins/configPropertyMixin.js +++ b/src/views/administration/mixins/configPropertyMixin.js @@ -1,6 +1,4 @@ -import Vue from 'vue'; -import axios from 'axios'; -import common from '../../../shared/common'; +import common from '@/shared/common'; export default { data() { @@ -14,13 +12,13 @@ export default { }, methods: { updateConfigProperties: function (configProperties) { - let props = []; + const props = []; for (let i = 0; i < configProperties.length; i++) { - let prop = configProperties[i]; + const prop = configProperties[i]; prop.propertyValue = common.trimToNull(prop.propertyValue); props.push(prop); } - let url = `${this.$api.BASE_URL}/${this.$api.URL_CONFIG_PROPERTY}/aggregate`; + const url = `${this.$api.BASE_URL}/${this.$api.URL_CONFIG_PROPERTY}/aggregate`; this.axios .post(url, props) .then((response) => { @@ -54,7 +52,7 @@ export default { */ updateConfigProperty: function (groupName, propertyName, propertyValue) { propertyValue = common.trimToNull(propertyValue); - let url = `${this.$api.BASE_URL}/${this.$api.URL_CONFIG_PROPERTY}/`; + const url = `${this.$api.BASE_URL}/${this.$api.URL_CONFIG_PROPERTY}/`; this.axios .post(url, { groupName: groupName, diff --git a/src/views/administration/notifications/Alerts.cy.js b/src/views/administration/notifications/Alerts.cy.js new file mode 100644 index 000000000..407949571 --- /dev/null +++ b/src/views/administration/notifications/Alerts.cy.js @@ -0,0 +1,13 @@ +import Alerts from './Alerts.vue'; + +describe('Alerts', () => { + it('mounts successfully', () => { + cy.mount(Alerts, { + stubs: { + 'create-alert-modal': true, + }, + }); + + cy.get('div.card').should('be.visible'); + }); +}); diff --git a/src/views/administration/notifications/Alerts.vue b/src/views/administration/notifications/Alerts.vue index cdbcbf762..a63fd44a3 100644 --- a/src/views/administration/notifications/Alerts.vue +++ b/src/views/administration/notifications/Alerts.vue @@ -18,46 +18,49 @@ > - + diff --git a/src/views/administration/notifications/CreateAlertModal.cy.js b/src/views/administration/notifications/CreateAlertModal.cy.js new file mode 100644 index 000000000..0252976fe --- /dev/null +++ b/src/views/administration/notifications/CreateAlertModal.cy.js @@ -0,0 +1,26 @@ +import { + genAxiosResponse, + shouldShowModal, +} from '../../../../cypress/support/utils'; + +import CreateAlertModal from './CreateAlertModal.vue'; + +describe('CreateAlertModal', () => { + it('mounts successfully', () => { + cy.mount(CreateAlertModal, { + attachTo: document.body, + stubs: { + transition: false, + }, + prototypeMocks: { + axios: { + get: genAxiosResponse({ + '/api/v1/notification/publisher': [], + }), + }, + }, + }); + + shouldShowModal('createAlertModal'); + }); +}); diff --git a/src/views/administration/notifications/CreateAlertModal.vue b/src/views/administration/notifications/CreateAlertModal.vue index cddfac151..10627c441 100644 --- a/src/views/administration/notifications/CreateAlertModal.vue +++ b/src/views/administration/notifications/CreateAlertModal.vue @@ -69,7 +69,7 @@ buttons /> - @@ -28,46 +28,27 @@ diff --git a/src/views/administration/repositories/RepositoryCreateRepositoryModal.cy.js b/src/views/administration/repositories/RepositoryCreateRepositoryModal.cy.js new file mode 100644 index 000000000..d73b5cc38 --- /dev/null +++ b/src/views/administration/repositories/RepositoryCreateRepositoryModal.cy.js @@ -0,0 +1,16 @@ +import { shouldShowModal } from '../../../../cypress/support/utils'; + +import RepositoryCreateRepositoryModal from './RepositoryCreateRepositoryModal.vue'; + +describe('RepositoryCreateRepositoryModal', () => { + it('mounts successfully', () => { + cy.mount(RepositoryCreateRepositoryModal, { + attachTo: document.body, + stubs: { + transition: false, + }, + }); + + shouldShowModal('repositoryCreateRepositoryModal'); + }); +}); diff --git a/src/views/administration/repositories/RepositoryCreateRepositoryModal.vue b/src/views/administration/repositories/RepositoryCreateRepositoryModal.vue index 23a759e5d..31742f2d6 100644 --- a/src/views/administration/repositories/RepositoryCreateRepositoryModal.vue +++ b/src/views/administration/repositories/RepositoryCreateRepositoryModal.vue @@ -67,7 +67,7 @@ $t('admin.enabled') }} - diff --git a/src/views/dashboard/ChartAuditingFindingsProgress.cy.js b/src/views/dashboard/ChartAuditingFindingsProgress.cy.js new file mode 100644 index 000000000..fe9471ff6 --- /dev/null +++ b/src/views/dashboard/ChartAuditingFindingsProgress.cy.js @@ -0,0 +1,11 @@ +import ChartAuditingFindingsProgress from '@/views/dashboard/ChartAuditingFindingsProgress.vue'; + +describe('ChartAuditingFindingsProgress', () => { + it('mounts successfully', () => { + cy.mount(ChartAuditingFindingsProgress, { + propsData: { + height: 300, + }, + }); + }); +}); diff --git a/src/views/dashboard/ChartAuditingFindingsProgress.vue b/src/views/dashboard/ChartAuditingFindingsProgress.vue index 971aa6a03..ab137e6ad 100644 --- a/src/views/dashboard/ChartAuditingFindingsProgress.vue +++ b/src/views/dashboard/ChartAuditingFindingsProgress.vue @@ -1,5 +1,5 @@ diff --git a/src/views/globalAudit/VulnerabilityAuditByOccurrence.cy.js b/src/views/globalAudit/VulnerabilityAuditByOccurrence.cy.js new file mode 100644 index 000000000..84d2d9235 --- /dev/null +++ b/src/views/globalAudit/VulnerabilityAuditByOccurrence.cy.js @@ -0,0 +1,20 @@ +import VulnerabilityAuditByOccurrence from './VulnerabilityAuditByOccurrence.vue'; + +describe('VulnerabilityAuditByOccurrence', () => { + it('mounts successfully', () => { + const mockCommon = { + formatSeverityLabel: cy.stub().returns(''), + formatAnalyzerLabel: cy.stub().returns(''), + formatTimestamp: cy.stub().returns(''), + formatSourceLabel: cy.stub().returns(''), + concatenateComponentName: cy.stub().returns(''), + valueWithDefault: cy.stub().returns(''), + capitalize: cy.stub().returns(''), + makeAnalysisStateLabelFormatter: cy.stub().returns(() => ''), + }; + + cy.mount(VulnerabilityAuditByOccurrence); + + cy.get('div').should('exist'); + }); +}); diff --git a/src/views/globalAudit/VulnerabilityAuditByOccurrence.vue b/src/views/globalAudit/VulnerabilityAuditByOccurrence.vue index 8942f65b9..dc22da314 100644 --- a/src/views/globalAudit/VulnerabilityAuditByOccurrence.vue +++ b/src/views/globalAudit/VulnerabilityAuditByOccurrence.vue @@ -79,35 +79,35 @@ id="publish-date-form-group" :label="this.$t('message.published')" > - - + + > - - + + > option.value, - ); - }, - methods: { - initializeWatchers: function () { - this.simpleWatcher = this.$watch('watchers', () => this.refreshTable()); - this.textSearchSelectedWatcher = this.$watch('textSearchSelected', () => { - if (this.textSearchInput.trim().length !== 0) { - this.refreshTable(); - } - }); - this.textSearchInputWatcher = this.$watch( - 'textSearchInput', - (newInput, oldInput) => { - if (!(newInput.trim().length === 0 && oldInput.trim().length === 0)) { - this.refreshTable(); - } - }, - ); - this.cvssv2FromWatcher = this.$watch('cvssv2From', () => { - if (this.cvssv2FromState() !== false) { - this.refreshTable(); - } - }); - this.cvssv2ToWatcher = this.$watch('cvssv2To', () => { - if (this.cvssv2ToState() !== false) { - this.refreshTable(); - } - }); - this.cvssv3FromWatcher = this.$watch('cvssv3From', () => { - if (this.cvssv3FromState() !== false) { - this.refreshTable(); - } - }); - this.cvssv3ToWatcher = this.$watch('cvssv3To', () => { - if (this.cvssv3ToState() !== false) { - this.refreshTable(); - } - }); - }, - apiUrl: function () { - let url = `${this.$api.BASE_URL}/${this.$api.URL_FINDING}`; - url += - '?showInactive=' + - (this.showInactive === 'true') + - '&showSuppressed=' + - (this.showSuppressed === 'true'); - if (this.severitySelected && this.severitySelected.length > 0) { - url += '&severity=' + this.severitySelected; - } - if ( - this.analysisStatusSelected && - this.analysisStatusSelected.length > 0 - ) { - url += '&analysisStatus=' + this.analysisStatusSelected; - } - if ( - this.vendorResponseSelected && - this.vendorResponseSelected.length > 0 - ) { - url += '&vendorResponse=' + this.vendorResponseSelected; - } - if (this.publishDateFrom && this.publishDateFrom.length > 0) { - url += '&publishDateFrom=' + this.publishDateFrom; - } - if (this.publishDateTo && this.publishDateTo.length > 0) { - url += '&publishDateTo=' + this.publishDateTo; - } - if (this.attributedOnDateFrom && this.attributedOnDateFrom.length > 0) { - url += '&attributedOnDateFrom=' + this.attributedOnDateFrom; - } - if (this.attributedOnDateTo && this.attributedOnDateTo.length > 0) { - url += '&attributedOnDateTo=' + this.attributedOnDateTo; - } - if (this.textSearchInput && this.textSearchInput.trim().length > 0) { - url += - '&textSearchField=' + - this.textSearchSelected + - '&textSearchInput=' + - this.textSearchInput.trim(); - } - if (this.cvssv2From && this.cvssv2From.trim().length > 0) { - url += '&cvssv2From=' + this.cvssv2From; - } - if (this.cvssv2To && this.cvssv2To.trim().length > 0) { - url += '&cvssv2To=' + this.cvssv2To; - } - if (this.cvssv3From && this.cvssv3From.trim().length > 0) { - url += '&cvssv3From=' + this.cvssv3From; - } - if (this.cvssv3To && this.cvssv3To.trim().length > 0) { - url += '&cvssv3To=' + this.cvssv3To; - } - return url; - }, - clearAllFilters: function () { - this.simpleWatcher(); - this.textSearchSelectedWatcher(); - this.textSearchInputWatcher(); - this.cvssv2FromWatcher(); - this.cvssv2ToWatcher(); - this.cvssv3FromWatcher(); - this.cvssv3ToWatcher(); - this.showInactive = false; - this.showSuppressed = false; - this.severitySelected = []; - this.analysisStatusSelected = []; - this.vendorResponseSelected = []; - this.publishDateFrom = ''; - this.publishDateTo = ''; - this.attributedOnDateFrom = ''; - this.attributedOnDateTo = ''; - this.textSearchInput = ''; - this.textSearchSelected = this.textSearchOptions.map( - (option) => option.value, - ); - this.cvssv2From = ''; - this.cvssv2To = ''; - this.cvssv3From = ''; - this.cvssv3To = ''; - this.refreshTable(); - this.initializeWatchers(); - }, - refreshTable: function () { - this.$refs.table.refresh({ - url: this.apiUrl(), - silent: true, - pageNumber: 1, - }); - }, - initializeTooltips: function () { - $('[data-toggle="tooltip"]').tooltip({ - trigger: 'hover', - }); - }, - cvssv2UpperLimit: function () { - return this.cvssv2To ? this.cvssv2To : 10; - }, - cvssv2LowerLimit: function () { - return this.cvssv2From ? this.cvssv2From : 0; - }, - cvssv2FromState() { - return this.cvssv2From - ? !isNaN(this.cvssv2From) && - (!this.cvssv2To || - !isNaN(this.cvssv2To) || - this.cvssv2From <= this.cvssv2To) && - this.cvssv2From <= 10 && - this.cvssv2From >= 0 - : null; - }, - cvssv2ToState() { - return this.cvssv2To - ? !isNaN(this.cvssv2To) && - (!this.cvssv2From || - !isNaN(this.cvssv2From) || - this.cvssv2To >= this.cvssv2From) && - this.cvssv2To <= 10 && - this.cvssv2To >= 0 - : null; - }, - cvssv3UpperLimit: function () { - return this.cvssv3To ? this.cvssv3To : 10; - }, - cvssv3LowerLimit: function () { - return this.cvssv3From ? this.cvssv3From : 0; - }, - cvssv3FromState() { - return this.cvssv3From - ? !isNaN(this.cvssv3From) && - (!this.cvssv3To || - !isNaN(this.cvssv3To) || - this.cvssv3From <= this.cvssv3To) && - this.cvssv3From <= 10 && - this.cvssv3From >= 0 - : null; - }, - cvssv3ToState() { - return this.cvssv3To - ? !isNaN(this.cvssv3To) && - (!this.cvssv3From || - !isNaN(this.cvssv3From) || - this.cvssv3To >= this.cvssv3From) && - this.cvssv3To <= 10 && - this.cvssv3To >= 0 - : null; - }, - onLoadSuccess: function () { - loadUserPreferencesForBootstrapTable( - this, - 'VulnerabilityAuditByOccurrence', - this.$refs.table.columns, - ); - }, + components: { + BRow, + BCol, + BButton, + BFormGroup, + BFormCheckbox, + BFormCheckboxGroup, + BFormInput, + BFormRow, + BFormDatepicker, + BootstrapTable, }, data() { return { @@ -490,7 +301,7 @@ export default { field: 'vulnerability.vulnId', sortable: true, formatter(value, row, index) { - let url = xssFilters.uriInUnQuotedAttr( + const url = xssFilters.uriInUnQuotedAttr( '../vulnerabilities/' + row.vulnerability.source + '/' + @@ -549,7 +360,7 @@ export default { if (typeof value !== 'undefined') { let s = ''; for (let i = 0; i < value.length; i++) { - let cwe = value[i]; + const cwe = value[i]; if (i > 0) { s += ',   '; } @@ -588,10 +399,10 @@ export default { field: 'component.projectName', sortable: true, formatter(value, row, index) { - let url = xssFilters.uriInUnQuotedAttr( + const url = xssFilters.uriInUnQuotedAttr( '../projects/' + row.component.project, ); - let name = common.concatenateComponentName( + const name = common.concatenateComponentName( null, row.component.projectName, row.component.projectVersion, @@ -604,10 +415,10 @@ export default { field: 'component.name', sortable: true, formatter: (value, row, index) => { - let url = xssFilters.uriInUnQuotedAttr( + const url = xssFilters.uriInUnQuotedAttr( '../../../components/' + row.component.uuid, ); - let dependencyGraphUrl = xssFilters.uriInUnQuotedAttr( + const dependencyGraphUrl = xssFilters.uriInUnQuotedAttr( '../../../projects/' + row.component.project + '/dependencyGraph/' + @@ -743,5 +554,218 @@ export default { }, }; }, + computed: { + watchers() { + return [ + this.showInactive, + this.showSuppressed, + this.severitySelected, + this.analysisStatusSelected, + this.vendorResponseSelected, + this.publishDateFrom, + this.publishDateTo, + this.attributedOnDateFrom, + this.attributedOnDateTo, + ]; + }, + }, + beforeMount() { + this.initializeWatchers(); + this.textSearchSelected = this.textSearchOptions.map( + (option) => option.value, + ); + }, + methods: { + initializeWatchers: function () { + this.simpleWatcher = this.$watch('watchers', () => this.refreshTable()); + this.textSearchSelectedWatcher = this.$watch('textSearchSelected', () => { + if (this.textSearchInput.trim().length !== 0) { + this.refreshTable(); + } + }); + this.textSearchInputWatcher = this.$watch( + 'textSearchInput', + (newInput, oldInput) => { + if (!(newInput.trim().length === 0 && oldInput.trim().length === 0)) { + this.refreshTable(); + } + }, + ); + this.cvssv2FromWatcher = this.$watch('cvssv2From', () => { + if (this.cvssv2FromState() !== false) { + this.refreshTable(); + } + }); + this.cvssv2ToWatcher = this.$watch('cvssv2To', () => { + if (this.cvssv2ToState() !== false) { + this.refreshTable(); + } + }); + this.cvssv3FromWatcher = this.$watch('cvssv3From', () => { + if (this.cvssv3FromState() !== false) { + this.refreshTable(); + } + }); + this.cvssv3ToWatcher = this.$watch('cvssv3To', () => { + if (this.cvssv3ToState() !== false) { + this.refreshTable(); + } + }); + }, + apiUrl: function () { + let url = `${this.$api.BASE_URL}/${this.$api.URL_FINDING}`; + url += + '?showInactive=' + + (this.showInactive === 'true') + + '&showSuppressed=' + + (this.showSuppressed === 'true'); + if (this.severitySelected && this.severitySelected.length > 0) { + url += '&severity=' + this.severitySelected; + } + if ( + this.analysisStatusSelected && + this.analysisStatusSelected.length > 0 + ) { + url += '&analysisStatus=' + this.analysisStatusSelected; + } + if ( + this.vendorResponseSelected && + this.vendorResponseSelected.length > 0 + ) { + url += '&vendorResponse=' + this.vendorResponseSelected; + } + if (this.publishDateFrom && this.publishDateFrom.length > 0) { + url += '&publishDateFrom=' + this.publishDateFrom; + } + if (this.publishDateTo && this.publishDateTo.length > 0) { + url += '&publishDateTo=' + this.publishDateTo; + } + if (this.attributedOnDateFrom && this.attributedOnDateFrom.length > 0) { + url += '&attributedOnDateFrom=' + this.attributedOnDateFrom; + } + if (this.attributedOnDateTo && this.attributedOnDateTo.length > 0) { + url += '&attributedOnDateTo=' + this.attributedOnDateTo; + } + if (this.textSearchInput && this.textSearchInput.trim().length > 0) { + url += + '&textSearchField=' + + this.textSearchSelected + + '&textSearchInput=' + + this.textSearchInput.trim(); + } + if (this.cvssv2From && this.cvssv2From.trim().length > 0) { + url += '&cvssv2From=' + this.cvssv2From; + } + if (this.cvssv2To && this.cvssv2To.trim().length > 0) { + url += '&cvssv2To=' + this.cvssv2To; + } + if (this.cvssv3From && this.cvssv3From.trim().length > 0) { + url += '&cvssv3From=' + this.cvssv3From; + } + if (this.cvssv3To && this.cvssv3To.trim().length > 0) { + url += '&cvssv3To=' + this.cvssv3To; + } + return url; + }, + clearAllFilters: function () { + this.simpleWatcher(); + this.textSearchSelectedWatcher(); + this.textSearchInputWatcher(); + this.cvssv2FromWatcher(); + this.cvssv2ToWatcher(); + this.cvssv3FromWatcher(); + this.cvssv3ToWatcher(); + this.showInactive = false; + this.showSuppressed = false; + this.severitySelected = []; + this.analysisStatusSelected = []; + this.vendorResponseSelected = []; + this.publishDateFrom = ''; + this.publishDateTo = ''; + this.attributedOnDateFrom = ''; + this.attributedOnDateTo = ''; + this.textSearchInput = ''; + this.textSearchSelected = this.textSearchOptions.map( + (option) => option.value, + ); + this.cvssv2From = ''; + this.cvssv2To = ''; + this.cvssv3From = ''; + this.cvssv3To = ''; + this.refreshTable(); + this.initializeWatchers(); + }, + refreshTable: function () { + this.$refs.table.refresh({ + url: this.apiUrl(), + silent: true, + pageNumber: 1, + }); + }, + initializeTooltips: function () { + $('[data-toggle="tooltip"]').tooltip({ + trigger: 'hover', + }); + }, + cvssv2UpperLimit: function () { + return this.cvssv2To ? this.cvssv2To : 10; + }, + cvssv2LowerLimit: function () { + return this.cvssv2From ? this.cvssv2From : 0; + }, + cvssv2FromState() { + return this.cvssv2From + ? !isNaN(this.cvssv2From) && + (!this.cvssv2To || + !isNaN(this.cvssv2To) || + this.cvssv2From <= this.cvssv2To) && + this.cvssv2From <= 10 && + this.cvssv2From >= 0 + : null; + }, + cvssv2ToState() { + return this.cvssv2To + ? !isNaN(this.cvssv2To) && + (!this.cvssv2From || + !isNaN(this.cvssv2From) || + this.cvssv2To >= this.cvssv2From) && + this.cvssv2To <= 10 && + this.cvssv2To >= 0 + : null; + }, + cvssv3UpperLimit: function () { + return this.cvssv3To ? this.cvssv3To : 10; + }, + cvssv3LowerLimit: function () { + return this.cvssv3From ? this.cvssv3From : 0; + }, + cvssv3FromState() { + return this.cvssv3From + ? !isNaN(this.cvssv3From) && + (!this.cvssv3To || + !isNaN(this.cvssv3To) || + this.cvssv3From <= this.cvssv3To) && + this.cvssv3From <= 10 && + this.cvssv3From >= 0 + : null; + }, + cvssv3ToState() { + return this.cvssv3To + ? !isNaN(this.cvssv3To) && + (!this.cvssv3From || + !isNaN(this.cvssv3From) || + this.cvssv3To >= this.cvssv3From) && + this.cvssv3To <= 10 && + this.cvssv3To >= 0 + : null; + }, + onLoadSuccess: function () { + loadUserPreferencesForBootstrapTable( + this, + 'VulnerabilityAuditByOccurrence', + this.$refs.table.columns, + ); + }, + }, }; diff --git a/src/views/globalAudit/VulnerabilityAuditGroupedByVulnerability.cy.js b/src/views/globalAudit/VulnerabilityAuditGroupedByVulnerability.cy.js new file mode 100644 index 000000000..c1d8e55f1 --- /dev/null +++ b/src/views/globalAudit/VulnerabilityAuditGroupedByVulnerability.cy.js @@ -0,0 +1,17 @@ +import VulnerabilityAuditGroupedByVulnerability from './VulnerabilityAuditGroupedByVulnerability.vue'; + +describe('VulnerabilityAuditGroupedByVulnerability', () => { + it('mounts successfully', () => { + const mockCommon = { + formatSeverityLabel: cy.stub().returns(''), + formatAnalyzerLabel: cy.stub().returns(''), + formatTimestamp: cy.stub().returns(''), + formatSourceLabel: cy.stub().returns(''), + capitalize: cy.stub().returns(''), + }; + + cy.mount(VulnerabilityAuditGroupedByVulnerability); + + cy.get('div').should('exist'); + }); +}); diff --git a/src/views/globalAudit/VulnerabilityAuditGroupedByVulnerability.vue b/src/views/globalAudit/VulnerabilityAuditGroupedByVulnerability.vue index 5deb43191..d42562fc5 100644 --- a/src/views/globalAudit/VulnerabilityAuditGroupedByVulnerability.vue +++ b/src/views/globalAudit/VulnerabilityAuditGroupedByVulnerability.vue @@ -41,18 +41,18 @@ id="grouped-publish-date-form-group" :label="this.$t('message.published')" > - - + + > option.value, - ); - }, - methods: { - initializeWatchers: function () { - this.simpleWatcher = this.$watch('watchers', () => this.refreshTable()); - this.textSearchSelectedWatcher = this.$watch('textSearchSelected', () => { - if (this.textSearchInput.trim().length !== 0) { - this.refreshTable(); - } - }); - this.textSearchInputWatcher = this.$watch( - 'textSearchInput', - (newInput, oldInput) => { - if (!(newInput.trim().length === 0 && oldInput.trim().length === 0)) { - this.refreshTable(); - } - }, - ); - this.cvssv2FromWatcher = this.$watch('cvssv2From', () => { - if (this.cvssv2FromState() !== false) { - this.refreshTable(); - } - }); - this.cvssv2ToWatcher = this.$watch('cvssv2To', () => { - if (this.cvssv2ToState() !== false) { - this.refreshTable(); - } - }); - this.cvssv3FromWatcher = this.$watch('cvssv3From', () => { - if (this.cvssv3FromState() !== false) { - this.refreshTable(); - } - }); - this.cvssv3ToWatcher = this.$watch('cvssv3To', () => { - if (this.cvssv3ToState() !== false) { - this.refreshTable(); - } - }); - this.occurrencesFromWatcher = this.$watch('occurrencesFrom', () => { - if (this.occurrencesFromState() !== false) { - this.refreshTable(); - } - }); - this.occurrencesToWatcher = this.$watch('occurrencesTo', () => { - if (this.occurrencesToState() !== false) { - this.refreshTable(); - } - }); - }, - apiUrl: function () { - let url = `${this.$api.BASE_URL}/${this.$api.URL_FINDING}/grouped`; - url += - '?showInactive=' + - (this.showInactive === 'true') + - '&showSuppressed=' + - (this.showSuppressed === 'true'); - if (this.severitySelected && this.severitySelected.length > 0) { - url += '&severity=' + this.severitySelected; - } - if (this.publishDateFrom && this.publishDateFrom.length > 0) { - url += '&publishDateFrom=' + this.publishDateFrom; - } - if (this.publishDateTo && this.publishDateTo.length > 0) { - url += '&publishDateTo=' + this.publishDateTo; - } - if (this.textSearchInput && this.textSearchInput.trim().length > 0) { - url += - '&textSearchField=' + - this.textSearchSelected + - '&textSearchInput=' + - this.textSearchInput.trim(); - } - if (this.cvssv2From && this.cvssv2From.trim().length > 0) { - url += '&cvssv2From=' + this.cvssv2From; - } - if (this.cvssv2To && this.cvssv2To.trim().length > 0) { - url += '&cvssv2To=' + this.cvssv2To; - } - if (this.cvssv3From && this.cvssv3From.trim().length > 0) { - url += '&cvssv3From=' + this.cvssv3From; - } - if (this.cvssv3To && this.cvssv3To.trim().length > 0) { - url += '&cvssv3To=' + this.cvssv3To; - } - if (this.occurrencesFrom && this.occurrencesFrom.trim().length > 0) { - url += '&occurrencesFrom=' + this.occurrencesFrom; - } - if (this.occurrencesTo && this.occurrencesTo.trim().length > 0) { - url += '&occurrencesTo=' + this.occurrencesTo; - } - return url; - }, - clearAllFilters: function () { - this.simpleWatcher(); - this.textSearchSelectedWatcher(); - this.textSearchInputWatcher(); - this.cvssv2FromWatcher(); - this.cvssv2ToWatcher(); - this.cvssv3FromWatcher(); - this.cvssv3ToWatcher(); - this.occurrencesFromWatcher(); - this.occurrencesToWatcher(); - this.showInactive = false; - this.severitySelected = []; - this.publishDateFrom = ''; - this.publishDateTo = ''; - this.textSearchInput = ''; - this.textSearchSelected = this.textSearchOptions.map( - (option) => option.value, - ); - this.cvssv2From = ''; - this.cvssv2To = ''; - this.cvssv3From = ''; - this.cvssv3To = ''; - this.occurrencesFrom = ''; - this.occurrencesTo = ''; - this.refreshTable(); - this.initializeWatchers(); - }, - refreshTable: function () { - this.$refs.table.refresh({ - url: this.apiUrl(), - silent: true, - pageNumber: 1, - }); - }, - cvssv2UpperLimit: function () { - return this.cvssv2To ? this.cvssv2To : 10; - }, - cvssv2LowerLimit: function () { - return this.cvssv2From ? this.cvssv2From : 0; - }, - cvssv2FromState() { - return this.cvssv2From - ? !isNaN(this.cvssv2From) && - (!this.cvssv2To || - !isNaN(this.cvssv2To) || - this.cvssv2From <= this.cvssv2To) && - this.cvssv2From <= 10 - : null; - }, - cvssv2ToState() { - return this.cvssv2To - ? !isNaN(this.cvssv2To) && - (!this.cvssv2From || - !isNaN(this.cvssv2From) || - this.cvssv2To >= this.cvssv2From) && - this.cvssv2To >= 0 - : null; - }, - cvssv3UpperLimit: function () { - return this.cvssv3To ? this.cvssv3To : 10; - }, - cvssv3LowerLimit: function () { - return this.cvssv3From ? this.cvssv3From : 0; - }, - cvssv3FromState() { - return this.cvssv3From - ? !isNaN(this.cvssv3From) && - (!this.cvssv3To || - !isNaN(this.cvssv3To) || - this.cvssv3From <= this.cvssv3To) && - this.cvssv3From <= 10 - : null; - }, - cvssv3ToState() { - return this.cvssv3To - ? !isNaN(this.cvssv3To) && - (!this.cvssv3From || - !isNaN(this.cvssv3From) || - this.cvssv3To >= this.cvssv3From) && - this.cvssv3To >= 0 - : null; - }, - occurrencesUpperLimit: function () { - return this.occurrencesTo ? this.occurrencesTo : null; - }, - occurrencesLowerLimit: function () { - return this.occurrencesFrom ? this.occurrencesFrom : 0; - }, - occurrencesFromState: function () { - return this.occurrencesFrom - ? !isNaN(this.occurrencesFrom) && - (!this.occurrencesTo || - !isNaN(this.occurrencesTo) || - this.occurrencesFrom <= this.occurrencesTo) && - this.cvssv3From >= 0 - : null; - }, - occurrencesToState: function () { - return this.occurrencesTo - ? !isNaN(this.occurrencesTo) && - (!this.occurrencesFrom || - !isNaN(this.occurrencesFrom) || - this.occurrencesTo >= this.occurrencesFrom) && - this.cvssv3To >= 0 - : null; - }, - onLoadSuccess: function () { - loadUserPreferencesForBootstrapTable( - this, - 'VulnerabilityAuditGroupedByVulnerability', - this.$refs.table.columns, - ); - }, + components: { + BRow, + BCol, + BFormCheckbox, + BButton, + BFormInput, + BFormGroup, + BFormCheckboxGroup, + BFormDatepicker, + BFormRow, + BootstrapTable, }, data() { return { @@ -455,7 +259,7 @@ export default { field: 'vulnerability.vulnId', sortable: true, formatter(value, row, index) { - let url = xssFilters.uriInUnQuotedAttr( + const url = xssFilters.uriInUnQuotedAttr( '../vulnerabilities/' + row.vulnerability.source + '/' + @@ -514,7 +318,7 @@ export default { if (typeof value !== 'undefined') { let s = ''; for (let i = 0; i < value.length; i++) { - let cwe = value[i]; + const cwe = value[i]; if (i > 0) { s += ',   '; } @@ -634,5 +438,225 @@ export default { }, }; }, + computed: { + watchers() { + return [ + this.showInactive, + this.severitySelected, + this.publishDateFrom, + this.publishDateTo, + ]; + }, + }, + beforeMount() { + this.initializeWatchers(); + this.textSearchSelected = this.textSearchOptions.map( + (option) => option.value, + ); + }, + methods: { + initializeWatchers: function () { + this.simpleWatcher = this.$watch('watchers', () => this.refreshTable()); + this.textSearchSelectedWatcher = this.$watch('textSearchSelected', () => { + if (this.textSearchInput.trim().length !== 0) { + this.refreshTable(); + } + }); + this.textSearchInputWatcher = this.$watch( + 'textSearchInput', + (newInput, oldInput) => { + if (!(newInput.trim().length === 0 && oldInput.trim().length === 0)) { + this.refreshTable(); + } + }, + ); + this.cvssv2FromWatcher = this.$watch('cvssv2From', () => { + if (this.cvssv2FromState() !== false) { + this.refreshTable(); + } + }); + this.cvssv2ToWatcher = this.$watch('cvssv2To', () => { + if (this.cvssv2ToState() !== false) { + this.refreshTable(); + } + }); + this.cvssv3FromWatcher = this.$watch('cvssv3From', () => { + if (this.cvssv3FromState() !== false) { + this.refreshTable(); + } + }); + this.cvssv3ToWatcher = this.$watch('cvssv3To', () => { + if (this.cvssv3ToState() !== false) { + this.refreshTable(); + } + }); + this.occurrencesFromWatcher = this.$watch('occurrencesFrom', () => { + if (this.occurrencesFromState() !== false) { + this.refreshTable(); + } + }); + this.occurrencesToWatcher = this.$watch('occurrencesTo', () => { + if (this.occurrencesToState() !== false) { + this.refreshTable(); + } + }); + }, + apiUrl: function () { + let url = `${this.$api.BASE_URL}/${this.$api.URL_FINDING}/grouped`; + url += + '?showInactive=' + + (this.showInactive === 'true') + + '&showSuppressed=' + + (this.showSuppressed === 'true'); + if (this.severitySelected && this.severitySelected.length > 0) { + url += '&severity=' + this.severitySelected; + } + if (this.publishDateFrom && this.publishDateFrom.length > 0) { + url += '&publishDateFrom=' + this.publishDateFrom; + } + if (this.publishDateTo && this.publishDateTo.length > 0) { + url += '&publishDateTo=' + this.publishDateTo; + } + if (this.textSearchInput && this.textSearchInput.trim().length > 0) { + url += + '&textSearchField=' + + this.textSearchSelected + + '&textSearchInput=' + + this.textSearchInput.trim(); + } + if (this.cvssv2From && this.cvssv2From.trim().length > 0) { + url += '&cvssv2From=' + this.cvssv2From; + } + if (this.cvssv2To && this.cvssv2To.trim().length > 0) { + url += '&cvssv2To=' + this.cvssv2To; + } + if (this.cvssv3From && this.cvssv3From.trim().length > 0) { + url += '&cvssv3From=' + this.cvssv3From; + } + if (this.cvssv3To && this.cvssv3To.trim().length > 0) { + url += '&cvssv3To=' + this.cvssv3To; + } + if (this.occurrencesFrom && this.occurrencesFrom.trim().length > 0) { + url += '&occurrencesFrom=' + this.occurrencesFrom; + } + if (this.occurrencesTo && this.occurrencesTo.trim().length > 0) { + url += '&occurrencesTo=' + this.occurrencesTo; + } + return url; + }, + clearAllFilters: function () { + this.simpleWatcher(); + this.textSearchSelectedWatcher(); + this.textSearchInputWatcher(); + this.cvssv2FromWatcher(); + this.cvssv2ToWatcher(); + this.cvssv3FromWatcher(); + this.cvssv3ToWatcher(); + this.occurrencesFromWatcher(); + this.occurrencesToWatcher(); + this.showInactive = false; + this.severitySelected = []; + this.publishDateFrom = ''; + this.publishDateTo = ''; + this.textSearchInput = ''; + this.textSearchSelected = this.textSearchOptions.map( + (option) => option.value, + ); + this.cvssv2From = ''; + this.cvssv2To = ''; + this.cvssv3From = ''; + this.cvssv3To = ''; + this.occurrencesFrom = ''; + this.occurrencesTo = ''; + this.refreshTable(); + this.initializeWatchers(); + }, + refreshTable: function () { + this.$refs.table.refresh({ + url: this.apiUrl(), + silent: true, + pageNumber: 1, + }); + }, + cvssv2UpperLimit: function () { + return this.cvssv2To ? this.cvssv2To : 10; + }, + cvssv2LowerLimit: function () { + return this.cvssv2From ? this.cvssv2From : 0; + }, + cvssv2FromState() { + return this.cvssv2From + ? !isNaN(this.cvssv2From) && + (!this.cvssv2To || + !isNaN(this.cvssv2To) || + this.cvssv2From <= this.cvssv2To) && + this.cvssv2From <= 10 + : null; + }, + cvssv2ToState() { + return this.cvssv2To + ? !isNaN(this.cvssv2To) && + (!this.cvssv2From || + !isNaN(this.cvssv2From) || + this.cvssv2To >= this.cvssv2From) && + this.cvssv2To >= 0 + : null; + }, + cvssv3UpperLimit: function () { + return this.cvssv3To ? this.cvssv3To : 10; + }, + cvssv3LowerLimit: function () { + return this.cvssv3From ? this.cvssv3From : 0; + }, + cvssv3FromState() { + return this.cvssv3From + ? !isNaN(this.cvssv3From) && + (!this.cvssv3To || + !isNaN(this.cvssv3To) || + this.cvssv3From <= this.cvssv3To) && + this.cvssv3From <= 10 + : null; + }, + cvssv3ToState() { + return this.cvssv3To + ? !isNaN(this.cvssv3To) && + (!this.cvssv3From || + !isNaN(this.cvssv3From) || + this.cvssv3To >= this.cvssv3From) && + this.cvssv3To >= 0 + : null; + }, + occurrencesUpperLimit: function () { + return this.occurrencesTo ? this.occurrencesTo : null; + }, + occurrencesLowerLimit: function () { + return this.occurrencesFrom ? this.occurrencesFrom : 0; + }, + occurrencesFromState: function () { + return this.occurrencesFrom + ? !isNaN(this.occurrencesFrom) && + (!this.occurrencesTo || + !isNaN(this.occurrencesTo) || + this.occurrencesFrom <= this.occurrencesTo) && + this.cvssv3From >= 0 + : null; + }, + occurrencesToState: function () { + return this.occurrencesTo + ? !isNaN(this.occurrencesTo) && + (!this.occurrencesFrom || + !isNaN(this.occurrencesFrom) || + this.occurrencesTo >= this.occurrencesFrom) && + this.cvssv3To >= 0 + : null; + }, + onLoadSuccess: function () { + loadUserPreferencesForBootstrapTable( + this, + 'VulnerabilityAuditGroupedByVulnerability', + this.$refs.table.columns, + ); + }, + }, }; diff --git a/src/views/modals/InformationalModal.cy.js b/src/views/modals/InformationalModal.cy.js new file mode 100644 index 000000000..9a6bbe4d9 --- /dev/null +++ b/src/views/modals/InformationalModal.cy.js @@ -0,0 +1,31 @@ +import InformationalModal from '@/views/modals/InformationalModal.vue'; +import { shouldShowModal } from '../../../cypress/support/utils'; + +describe('InformationalModal', () => { + it('contains the message', () => { + cy.setToken(); + + cy.mount(InformationalModal, { + attachTo: document.body, + stubs: { + transition: false, + }, + propsData: { + message: 'Foo', + }, + }); + + shouldShowModal('modal-informational'); + + cy.get('#modal-informational').invoke('css', 'display', 'block'); + cy.get('#modal-informational').invoke('css', 'opacity', '1'); + cy.get('#modal-informational').invoke('addClass', 'show'); + + cy.get('#modal-informational').should('be.visible'); + + cy.get('#modal-informational .modal-content').should('be.visible'); + cy.get('#modal-informational .modal-content p') + .should('be.visible') + .should('contain.text', 'Foo'); + }); +}); diff --git a/src/views/modals/InformationalModal.vue b/src/views/modals/InformationalModal.vue index a91936428..424e7280b 100644 --- a/src/views/modals/InformationalModal.vue +++ b/src/views/modals/InformationalModal.vue @@ -1,6 +1,6 @@ diff --git a/src/views/pages/PasswordForceChange.cy.js b/src/views/pages/PasswordForceChange.cy.js new file mode 100644 index 000000000..39a95be0b --- /dev/null +++ b/src/views/pages/PasswordForceChange.cy.js @@ -0,0 +1,13 @@ +import PasswordForceChange from './PasswordForceChange.vue'; + +describe('PasswordForceChange', () => { + it('mounts successfully', () => { + cy.mount(PasswordForceChange, { + mocks: { + $toastr: { + s: cy.stub(), + }, + }, + }); + }); +}); diff --git a/src/views/pages/PasswordForceChange.vue b/src/views/pages/PasswordForceChange.vue index f4d987cb5..893fa3990 100644 --- a/src/views/pages/PasswordForceChange.vue +++ b/src/views/pages/PasswordForceChange.vue @@ -59,8 +59,8 @@ variant="primary" class="px-4" type="submit" - >{{ $t('message.password_change') }} + >{{ $t('message.password_change') }} + @@ -77,6 +77,7 @@ DependencyTrack Logo @@ -85,24 +86,38 @@ - + diff --git a/src/views/policy/PolicyCondition.cy.js b/src/views/policy/PolicyCondition.cy.js new file mode 100644 index 000000000..3d0bd879f --- /dev/null +++ b/src/views/policy/PolicyCondition.cy.js @@ -0,0 +1,9 @@ +import PolicyCondition from './PolicyCondition.vue'; + +describe('PolicyCondition', () => { + it('mounts successfully', () => { + cy.mount(PolicyCondition); + + cy.get('li.list-group-item.align-middle').should('be.visible'); + }); +}); diff --git a/src/views/policy/PolicyCondition.vue b/src/views/policy/PolicyCondition.vue index 559c5e046..183158449 100644 --- a/src/views/policy/PolicyCondition.vue +++ b/src/views/policy/PolicyCondition.vue @@ -1,14 +1,14 @@ diff --git a/src/views/policy/PolicyManagement.cy.js b/src/views/policy/PolicyManagement.cy.js new file mode 100644 index 000000000..6c5fcc178 --- /dev/null +++ b/src/views/policy/PolicyManagement.cy.js @@ -0,0 +1,23 @@ +import PolicyManagement from './PolicyManagement.vue'; + +describe('PolicyManagement', () => { + it('mounts successfully', () => { + cy.setToken(['POLICY_MANAGEMENT']); + + cy.mount(PolicyManagement, { + prototypeMocks: { + $route: { + fullPath: '/policy', + query: { + searchText: '', + }, + }, + $router: { + push: cy.stub(), + }, + }, + }); + + cy.get('div.animated.fadeIn').should('be.visible'); + }); +}); diff --git a/src/views/policy/PolicyManagement.vue b/src/views/policy/PolicyManagement.vue index 3cc1cd92b..bdaf73e0c 100644 --- a/src/views/policy/PolicyManagement.vue +++ b/src/views/policy/PolicyManagement.vue @@ -11,43 +11,55 @@ active @click="routeTo()" > - - + - - + diff --git a/src/views/policy/SelectLicenseModal.cy.js b/src/views/policy/SelectLicenseModal.cy.js new file mode 100644 index 000000000..02d14671b --- /dev/null +++ b/src/views/policy/SelectLicenseModal.cy.js @@ -0,0 +1,16 @@ +import { shouldShowModal } from '../../../cypress/support/utils'; + +import SelectLicenseModal from './SelectLicenseModal.vue'; + +describe('SelectLicenseModal', () => { + it('mounts successfully', () => { + cy.mount(SelectLicenseModal, { + attachTo: document.body, + stubs: { + transition: false, + }, + }); + + shouldShowModal('selectLicenseModal'); + }); +}); diff --git a/src/views/policy/SelectLicenseModal.vue b/src/views/policy/SelectLicenseModal.vue index a61d9fcf7..94e1c3adb 100644 --- a/src/views/policy/SelectLicenseModal.vue +++ b/src/views/policy/SelectLicenseModal.vue @@ -13,7 +13,7 @@ :options="options" > -