diff --git a/frontend/cypress/e2e/check-in.cy.ts b/frontend/cypress/e2e/check-in.cy.ts index d7cdd63fef..ef3c48c1a2 100644 --- a/frontend/cypress/e2e/check-in.cy.ts +++ b/frontend/cypress/e2e/check-in.cy.ts @@ -17,7 +17,7 @@ describe('okr check-in', () => { cy.loginAsUser(users.gl); }); - it(`should create check-in metric`, () => { + it('should create check-in metric', () => { overviewPage .addKeyResult() .fillKeyResultTitle('Very important keyresult') @@ -39,7 +39,7 @@ describe('okr check-in', () => { cy.contains('We bought a new house'); }); - it(`should create check-in metric with confidence 0`, () => { + it('should create check-in metric with confidence 0', () => { overviewPage .addKeyResult() .fillKeyResultTitle('Very important keyresult') @@ -61,7 +61,7 @@ describe('okr check-in', () => { cy.contains('We bought a new house'); }); - it(`should create check-in metric with value below baseline`, () => { + it('should create check-in metric with value below baseline', () => { overviewPage .addKeyResult() .fillKeyResultTitle('This will not be good') @@ -249,7 +249,7 @@ describe('okr check-in', () => { cy.contains('STRETCH'); }); - it(`should display confirm dialog when creating check-in on draft objective`, () => { + it('should display confirm dialog when creating check-in on draft objective', () => { overviewPage.addObjective() .fillObjectiveTitle('draft objective title') .selectQuarter('3') @@ -270,7 +270,7 @@ describe('okr check-in', () => { .checkDescription('Dein Objective befindet sich noch im DRAFT Status. Möchtest du das Check-in trotzdem erfassen?'); }); - it(`should only display last value div if last check-in is present`, () => { + it('should only display last value div if last check-in is present', () => { const objectiveName = uniqueSuffix('new objective'); overviewPage.addObjective() @@ -310,8 +310,12 @@ function getCurrentDate() { let dd_str = '' + dd; let mm_str = '' + mm; - if (dd < 10) dd_str = '0' + dd_str; - if (mm < 10) mm_str = '0' + mm_str; + if (dd < 10) { + dd_str = '0' + dd_str; + } + if (mm < 10) { + mm_str = '0' + mm_str; + } return dd_str + '.' + mm_str + '.' + yyyy; } diff --git a/frontend/cypress/e2e/objective-backlog.cy.ts b/frontend/cypress/e2e/objective-backlog.cy.ts index d3df96c62e..13870e8967 100644 --- a/frontend/cypress/e2e/objective-backlog.cy.ts +++ b/frontend/cypress/e2e/objective-backlog.cy.ts @@ -10,7 +10,7 @@ describe('okr objective backlog', () => { cy.loginAsUser(users.gl); }); - it(`should not have save button when creating objective in backlog quarter`, () => { + it('should not have save button when creating objective in backlog quarter', () => { overviewPage .addObjective() .fillObjectiveTitle('Objective in quarter backlog') @@ -27,7 +27,7 @@ describe('okr objective backlog', () => { cy.contains('Objective in quarter backlog'); }); - it(`should edit objective and move it to backlog`, () => { + it('should edit objective and move it to backlog', () => { overviewPage.addObjective() .fillObjectiveTitle('Move to another quarter on edit') .submitDraftObjective(); @@ -50,7 +50,7 @@ describe('okr objective backlog', () => { cy.contains('This goes now to backlog'); }); - it(`should not be able to select backlog quarter when editing ongoing objective`, () => { + it('should not be able to select backlog quarter when editing ongoing objective', () => { overviewPage.addObjective() .fillObjectiveTitle('We can not move this to backlog') .submit(); @@ -67,7 +67,7 @@ describe('okr objective backlog', () => { .should('not.contain', 'Backlog'); }); - it(`should release objective to another quarter from backlog`, () => { + it('should release objective to another quarter from backlog', () => { overviewPage.visitBacklogQuarter(); overviewPage.addObjective() .fillObjectiveTitle('We can not release this') @@ -105,7 +105,7 @@ describe('okr objective backlog', () => { cy.contains('This is our first released objective'); }); - it(`should edit objective title in backlog`, () => { + it('should edit objective title in backlog', () => { overviewPage.visitBacklogQuarter(); overviewPage.addObjective() .fillObjectiveTitle('This is possible for edit') @@ -124,7 +124,7 @@ describe('okr objective backlog', () => { overviewPage.getObjectiveByNameAndState('My new title', 'draft'); }); - it(`should edit objective in backlog and change quarter`, () => { + it('should edit objective in backlog and change quarter', () => { overviewPage.visitBacklogQuarter(); overviewPage.addObjective() .fillObjectiveTitle('This goes to other quarter later') @@ -144,7 +144,7 @@ describe('okr objective backlog', () => { overviewPage.getObjectiveByNameAndState('This goes to other quarter later', 'draft'); }); - it(`should duplicate objective in backlog`, () => { + it('should duplicate objective in backlog', () => { overviewPage.visitBacklogQuarter(); overviewPage.addObjective() .fillObjectiveTitle('Ready for duplicate in backlog') @@ -189,7 +189,7 @@ describe('okr objective backlog', () => { .should('exist'); }); - it(`should duplicate ongoing objective to backlog`, () => { + it('should duplicate ongoing objective to backlog', () => { overviewPage.addObjective() .fillObjectiveTitle('Possible to duplicate into backlog') .submit(); diff --git a/frontend/cypress/e2e/objective-crud.cy.ts b/frontend/cypress/e2e/objective-crud.cy.ts index 22bf882b66..a7d52c3db0 100644 --- a/frontend/cypress/e2e/objective-crud.cy.ts +++ b/frontend/cypress/e2e/objective-crud.cy.ts @@ -18,7 +18,7 @@ describe('crud operations', () => { 'draft-icon.svg']].forEach(([objectiveTitle, buttonTestId, icon]) => { - it(`should create objective without key-results`, () => { + it('should create objective without key-results', () => { overviewPage.addObjective() .fillObjectiveTitle(objectiveTitle) .selectQuarter('3'); @@ -32,7 +32,7 @@ describe('crud operations', () => { }); }); - it(`should display error message when title not set`, () => { + it('should display error message when title not set', () => { overviewPage.addObjective(); cy.getByTestId('title') .first() @@ -47,7 +47,7 @@ describe('crud operations', () => { .should('not.be.disabled'); }); - it(`should cancel creating an objective`, () => { + it('should cancel creating an objective', () => { const objectiveTitle = 'this is a canceled objective'; overviewPage.addObjective() .selectQuarter('3') @@ -57,7 +57,7 @@ describe('crud operations', () => { .should('not.exist'); }); - it(`should delete existing objective`, () => { + it('should delete existing objective', () => { overviewPage.getFirstObjective() .findByTestId('three-dot-menu') .click(); @@ -69,7 +69,7 @@ describe('crud operations', () => { .submit(); }); - it(`should open objective detail view via click`, () => { + it('should open objective detail view via click', () => { overviewPage.getFirstObjective() .find('.title') .click(); @@ -77,7 +77,7 @@ describe('crud operations', () => { .should('include', 'objective'); }); - it(`should edit objective`, () => { + it('should edit objective', () => { const updatedTitle = 'This is an updated title'; overviewPage.getFirstObjective() .findByTestId('three-dot-menu') @@ -90,7 +90,7 @@ describe('crud operations', () => { .should('exist'); }); - it(`should duplicate objective`, () => { + it('should duplicate objective', () => { const duplicatedTitle = 'This is a duplicated objective'; overviewPage.getFirstObjective() .findByTestId('three-dot-menu') diff --git a/frontend/cypress/e2e/objective.cy.ts b/frontend/cypress/e2e/objective.cy.ts index f437e050d7..43ee55abaf 100644 --- a/frontend/cypress/e2e/objective.cy.ts +++ b/frontend/cypress/e2e/objective.cy.ts @@ -11,7 +11,7 @@ describe('okr objective', () => { }); describe('tests via click', () => { - it(`should release objective from draft to ongoing`, () => { + it('should release objective from draft to ongoing', () => { overviewPage.addObjective() .fillObjectiveTitle('A objective in state draft') .submitDraftObjective(); @@ -31,7 +31,7 @@ describe('okr objective', () => { .should('exist'); }); - it(`should complete objective with successful`, () => { + it('should complete objective with successful', () => { overviewPage.addObjective() .fillObjectiveTitle('We want to complete this successful') .submit(); @@ -58,7 +58,7 @@ describe('okr objective', () => { overviewPage.getObjectiveByNameAndState('We want to complete this successful', 'successful'); }); - it(`should complete objective with not-successful`, () => { + it('should complete objective with not-successful', () => { overviewPage.addObjective() .fillObjectiveTitle('A not successful objective') .submit(); @@ -84,7 +84,7 @@ describe('okr objective', () => { overviewPage.getObjectiveByNameAndState('A not successful objective', 'not-successful'); }); - it(`should reopen successful objective`, () => { + it('should reopen successful objective', () => { overviewPage.addObjective() .fillObjectiveTitle('This objective will be reopened after') .submit(); @@ -119,7 +119,7 @@ describe('okr objective', () => { .should('exist'); }); - it(`should cancel reopening successful objective`, () => { + it('should cancel reopening successful objective', () => { overviewPage.addObjective() .fillObjectiveTitle('The reopening of this objective will be canceled') .submit(); @@ -198,7 +198,7 @@ describe('okr objective', () => { .should('exist'); }); - it(`should search and find objectives`, () => { + it('should search and find objectives', () => { overviewPage.addObjective() .fillObjectiveTitle('Search after this objective') .submit(); @@ -258,7 +258,7 @@ describe('okr objective', () => { .should('not.exist'); }); - it(`should create objective in other quarter`, () => { + it('should create objective in other quarter', () => { overviewPage.addObjective() .fillObjectiveTitle('Objective in quarter 3') .selectQuarter('3') @@ -272,7 +272,7 @@ describe('okr objective', () => { cy.contains('Objective in quarter 3'); }); - it(`should edit objective and move it to another quarter`, () => { + it('should edit objective and move it to another quarter', () => { overviewPage.addObjective() .fillObjectiveTitle('Move to another quarter on edit') .submit(); @@ -296,7 +296,7 @@ describe('okr objective', () => { }); describe('tests via keyboard', () => { - it(`should open objective aside via enter`, () => { + it('should open objective aside via enter', () => { cy.getByTestId('objective') .first() .find('[tabindex]') diff --git a/frontend/cypress/e2e/team-management.cy.ts b/frontend/cypress/e2e/team-management.cy.ts index f41ac166ec..a20c961b85 100644 --- a/frontend/cypress/e2e/team-management.cy.ts +++ b/frontend/cypress/e2e/team-management.cy.ts @@ -461,7 +461,7 @@ describe('okr team-management', () => { .trim() !== 'Findus Peterson') { return; } - $row.find(`[data-testId='edit-role']`) + $row.find('[data-testId=\'edit-role\']') .click(); cy.wait(500); // wait for dialog to open }) @@ -495,7 +495,7 @@ describe('okr team-management', () => { ConfirmDialog.do() .checkTitle('Mitglied entfernen') - .checkDescription(`Möchtest du Findus Peterson wirklich aus dem Team '/BBT' entfernen?`) + .checkDescription('Möchtest du Findus Peterson wirklich aus dem Team \'/BBT\' entfernen?') .submit(); cy.get('app-member-detail') diff --git a/frontend/cypress/support/commands.ts b/frontend/cypress/support/commands.ts index e8a1ce80f8..e4c4efdc80 100644 --- a/frontend/cypress/support/commands.ts +++ b/frontend/cypress/support/commands.ts @@ -19,19 +19,17 @@ Cypress.Commands.add('getByTestId', (testId: string, text?: string): Chainable = } }); -Cypress.Commands.add('findByTestId', - { prevSubject: true }, - (subject: JQuery, testId: string, text?: string): Chainable => { - const selector = `[data-testId=${testId}]`; - if (text) { - return cy.wrap(subject) - .find(selector) - .contains(text); - } else { - return cy.wrap(subject) - .find(selector); - } - }); +Cypress.Commands.add('findByTestId', { prevSubject: true }, (subject: JQuery, testId: string, text?: string): Chainable => { + const selector = `[data-testId=${testId}]`; + if (text) { + return cy.wrap(subject) + .find(selector) + .contains(text); + } else { + return cy.wrap(subject) + .find(selector); + } +}); Cypress.Commands.add('pressUntilContains', (text: string, key: keyof typeof keyCodeDefinitions) => { pressUntilContains(text, key); diff --git a/frontend/cypress/support/component.ts b/frontend/cypress/support/component.ts index 2733f4b08b..3d45dba0b0 100644 --- a/frontend/cypress/support/component.ts +++ b/frontend/cypress/support/component.ts @@ -2,7 +2,7 @@ import './commands'; import { keyCodeDefinitions } from 'cypress-real-events/keyCodeDefinitions'; declare global { - namespace Cypress { + export namespace Cypress { interface Chainable { loginAsUser(user: any): Chainable; getByTestId(testsId: string, text?: string): Chainable; diff --git a/frontend/cypress/support/helper/dom-helper/dialogs/objectiveDialog.ts b/frontend/cypress/support/helper/dom-helper/dialogs/objectiveDialog.ts index 4957e72663..6252040b00 100644 --- a/frontend/cypress/support/helper/dom-helper/dialogs/objectiveDialog.ts +++ b/frontend/cypress/support/helper/dom-helper/dialogs/objectiveDialog.ts @@ -21,7 +21,7 @@ export default class ObjectiveDialog extends Dialog { toggleCreateKeyResults() { cy.getByTestId('keyResult-checkbox') - .find("[type='checkbox']") + .find('[type=\'checkbox\']') .check(); return this; } diff --git a/frontend/cypress/support/helper/dom-helper/filterHelper.ts b/frontend/cypress/support/helper/dom-helper/filterHelper.ts index aab16bf130..0b377d8ea4 100644 --- a/frontend/cypress/support/helper/dom-helper/filterHelper.ts +++ b/frontend/cypress/support/helper/dom-helper/filterHelper.ts @@ -1,7 +1,9 @@ import { PageObjectMapperBase } from './pageObjectMapperBase'; export default class FilterHelper extends PageObjectMapperBase { - validatePage(): void {} + validatePage(): void { + // Does not need to be implemented this comment is for making linter happy + } optionShouldBeSelected(text: string, isOnOverview = true): this { if (isOnOverview) { diff --git a/frontend/cypress/support/helper/dom-helper/pages/overviewPage.ts b/frontend/cypress/support/helper/dom-helper/pages/overviewPage.ts index 87b20f8ccc..68a806b995 100644 --- a/frontend/cypress/support/helper/dom-helper/pages/overviewPage.ts +++ b/frontend/cypress/support/helper/dom-helper/pages/overviewPage.ts @@ -196,7 +196,9 @@ export default class CyOverviewPage extends Page { return ''; } - validatePage(): void {} + validatePage(): void { + // Does not need to be implemented this comment is for making linter happy + } protected doVisit(): void { this.elements.logo() diff --git a/frontend/cypress/support/helper/scoringSupport.ts b/frontend/cypress/support/helper/scoringSupport.ts index ccb55625de..cfa2c75aca 100644 --- a/frontend/cypress/support/helper/scoringSupport.ts +++ b/frontend/cypress/support/helper/scoringSupport.ts @@ -20,7 +20,9 @@ export function validateScoring(isOverview: boolean, percentage: number) { validateScoringWidth('commit', scoringValue.commitPercent, isOverview); validateScoringWidth('target', scoringValue.targetPercent, isOverview); - if (percentage == 0) return; + if (percentage == 0) { + return; + } validateScoringColor('fail', rgbCode, isOverview); validateScoringColor('commit', rgbCode, isOverview); validateScoringColor('target', rgbCode, isOverview); @@ -30,15 +32,22 @@ export function getPercentageMetric(baseline: number, stretchGoal: number, value if (isLastCheckInNegative(baseline, stretchGoal, value)) { return -1; } - return (Math.abs(value - baseline) / Math.abs(stretchGoal - baseline)) * 100; + return Math.abs(value - baseline) / Math.abs(stretchGoal - baseline) * 100; } export function getPercentageOrdinal(zone: string) { - if (zone == 'stretch') return 101; - if (zone == 'target') return 99.99; - if (zone == 'commit') return 70; - if (zone == 'fail') return 30; - return 0; + switch (zone) { + case 'fail': + return 30; + case 'commit': + return 70; + case 'target': + return 99.99; + case 'stretch': + return 101; + default: + return 0; + } } function validateScoringWidth(zone: string, percent: number, isOverview: boolean) { @@ -46,7 +55,7 @@ function validateScoringWidth(zone: string, percent: number, isOverview: boolean .parent() .invoke('width') .then((parentWidth) => { - expect(parentWidth).not.to.be.undefined; + expect(parentWidth).not.to.equal(undefined); cy.getZone(zone, isOverview) .invoke('width') .should('be.within', parentWidth! * (percent / 100) - 3, parentWidth! * (percent / 100) + 3); @@ -77,30 +86,43 @@ function checkVisibilityOfScoringComponent(isOverview: boolean, displayProperty: } function colorFromPercentage(percentage: number) { - if (percentage >= 100) return 'rgba(0, 0, 0, 0)'; - if (percentage > 70) return 'rgb(30, 138, 41)'; - if (percentage > 30) return 'rgb(255, 214, 0)'; - return 'rgb(186, 56, 56)'; + switch (true) { + case percentage >= 100: + return 'rgba(0, 0, 0, 0)'; + case percentage > 70: + return 'rgb(30, 138, 41)'; + case percentage > 30: + return 'rgb(255, 214, 0)'; + default: + return 'rgb(186, 56, 56)'; + } } function scoringValueFromPercentage(percentage: number): ScoringValue { - if (percentage >= 100) { - return { failPercent: 0, - commitPercent: 0, - targetPercent: 0 }; - } else if (percentage > 70) { - const targetPercent = (percentage - 70) * (100 / 30); - return { failPercent: 100, - commitPercent: 100, - targetPercent: targetPercent }; - } else if (percentage > 30) { - const commitPercent = (percentage - 30) * (100 / 40); - return { failPercent: 100, - commitPercent: commitPercent, - targetPercent: -1 }; + switch (true) { + case percentage >= 100: + return { + failPercent: 0, + commitPercent: 0, + targetPercent: 0 + }; + case percentage > 70: + return { + failPercent: 100, + commitPercent: 100, + targetPercent: (percentage - 70) * (100 / 30) + }; + case percentage > 30: + return { + failPercent: 100, + commitPercent: (percentage - 30) * (100 / 40), + targetPercent: -1 + }; + default: + return { + failPercent: percentage * (100 / 30), + commitPercent: -1, + targetPercent: -1 + }; } - const failPercent = percentage * (100 / 30); - return { failPercent: failPercent, - commitPercent: -1, - targetPercent: -1 }; } diff --git a/frontend/cypress/support/helper/utils.ts b/frontend/cypress/support/helper/utils.ts index 454ba2433c..3600ff1b54 100644 --- a/frontend/cypress/support/helper/utils.ts +++ b/frontend/cypress/support/helper/utils.ts @@ -30,7 +30,9 @@ function doUntil( limit = 100, count = 0 ) { - if (count >= limit) return; + if (count >= limit) { + return; + } cy.focused() .then((element) => { diff --git a/frontend/eslint.config.mjs b/frontend/eslint.config.mjs index 3b2afd4e25..bc3c1546ad 100644 --- a/frontend/eslint.config.mjs +++ b/frontend/eslint.config.mjs @@ -7,6 +7,9 @@ import angular from 'angular-eslint' import htmlParser from '@html-eslint/parser' export default tsEslint.config( + { + ignores: ['cypress/downloads/**/*'], + }, { files: ['**/*.ts'], extends: [ @@ -16,35 +19,83 @@ export default tsEslint.config( ...angular.configs.tsRecommended, ], processor: angular.processInlineTemplates, + languageOptions: { + globals: { + //Cypress things not recognized by eslint + cy: 'readonly', + Cypress: 'readonly', + it: 'readonly', + describe: 'readonly', + expect: 'readonly', + beforeEach: 'readonly', + before: 'readonly', + //Dom things not recognized by eslint + localStorage: 'readonly', + console: 'readonly', + window: 'readonly', + document: 'readonly', + //Event not recognized by eslint + MouseEvent: 'readonly', + KeyboardEvent: 'readonly', + Event: 'readonly', + //HTML Elements not recognized by eslint + HTMLDivElement: 'readonly', + HTMLInputElement: 'readonly', + HTMLSpanElement: 'readonly', + HTMLElement: 'readonly', + HTMLTitleElement: 'readonly', + HTMLHtmlElement: 'readonly', + //Others not recognized by eslint + ResizeObserver: 'readonly', + ResizeObserverEntry: 'readonly', + setTimeout: 'readonly', + JQuery: 'readonly', + Document: 'readonly', + URL: 'readonly', + }, + }, rules: { ...stylistic.configs['all-flat'].rules, + //eslint rules 'unused-imports/no-unused-imports': 'error', + 'no-undef': 'error', + curly: 'error', + 'prefer-rest-params': 'error', + 'space-before-function-paren': ['error', 'never'], - // ToDo: Disable rules so eslint passes, fix in followup ticket - '@typescript-eslint/no-unused-vars': 'off', - '@typescript-eslint/no-unused-expressions': 'off', - '@typescript-eslint/ban-ts-comment': 'off', - 'no-undef': 'off', + //Typescript eslint rules + '@typescript-eslint/ban-ts-comment': 'error', + '@typescript-eslint/no-unused-expressions': [ + 'error', + { + allowTernary: true, + }, + ], + + '@typescript-eslint/no-unused-vars': [ + 'error', + { + args: 'none', + }, + ], '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-namespace': 'off', - 'prefer-rest-params': 'off', - '@typescript-eslint/no-empty-function': ['off'], - '@stylistic/lines-around-comment': ['off'], - '@angular-eslint/no-empty-lifecycle-method': 'off', - '@angular-eslint/component-class-suffix': 'off', - '@angular-eslint/template/eqeqeq': 'off', - '@angular-eslint/template/interactive-supports-focus': 'off', - '@typescript-eslint/no-non-null-asserted-optional-chain': 'off', + '@typescript-eslint/no-namespace': [ + 'error', + { + allowDeclarations: true, + }, + ], + '@typescript-eslint/no-empty-function': ['error', { allow: ['arrowFunctions', 'constructors'] }], + //Turned off to allow ! in the code '@typescript-eslint/no-non-null-assertion': 'off', - '@stylistic/no-extra-parens': 'off', - '@typescript-eslint/no-confusing-non-null-assertion': 'off', - //Delete these rules after fixing all the issues and enabling the actual rules - '@stylistic/quotes': 'off', - '@stylistic/function-call-argument-newline': 'off', + '@typescript-eslint/no-non-null-asserted-optional-chain': 'off', - //Actual formatting rules - // '@stylistic/function-call-argument-newline': ['error', 'never'], - // '@stylistic/quotes': ['error', 'double'], + '@typescript-eslint/no-confusing-non-null-assertion': 'error', + + //Stylistic eslint rules + '@stylistic/no-extra-parens': 'error', + '@stylistic/function-call-argument-newline': ['error', 'never'], + '@stylistic/quotes': ['error', 'single'], '@stylistic/padded-blocks': ['error', 'never'], '@stylistic/dot-location': ['error', 'property'], '@stylistic/newline-per-chained-call': ['error', { ignoreChainWithDepth: 1 }], @@ -55,10 +106,13 @@ export default tsEslint.config( '@stylistic/object-curly-spacing': ['error', 'always'], '@stylistic/array-bracket-newline': ['error', { minItems: 4 }], '@stylistic/semi-style': ['error'], - 'space-before-function-paren': ['error', 'never'], '@stylistic/function-paren-newline': ['error', { minItems: 4 }], '@stylistic/space-before-function-paren': ['error', 'never'], - + // Disabled because it's an unnecessary rule in our case + '@stylistic/lines-around-comment': 'off', + //Angular eslint rules + '@angular-eslint/no-empty-lifecycle-method': 'error', + '@angular-eslint/component-class-suffix': 'error', '@angular-eslint/directive-selector': [ 'error', { @@ -80,10 +134,14 @@ export default tsEslint.config( }, { files: ['**/*.spec.ts'], + extends: [...tsEslint.configs.recommended], rules: { + //Rules removed for Test files because they are unnecessary for tests '@typescript-eslint/no-explicit-any': 'off', 'prefer-rest-params': 'off', '@typescript-eslint/no-empty-function': 'off', + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', }, }, @@ -96,11 +154,13 @@ export default tsEslint.config( }, rules: { ...html.configs['flat/recommended'].rules, + //Html eslint rules // Must be defined. If not, all recommended rules will be lost - '@html-eslint/indent': ['error', 2], '@html-eslint/require-img-alt': 'off', - '@html-eslint/element-newline': 'off', + '@html-eslint/indent': ['error', 2], '@html-eslint/require-closing-tags': ['error', { selfClosing: 'always' }], + //Doesn't work with Angular 17+ + '@html-eslint/element-newline': 'off', }, }, { diff --git a/frontend/package.json b/frontend/package.json index 3e183315ea..fb2e149a98 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -53,6 +53,8 @@ "@angular/compiler-cli": "^18.2.8", "@cypress/schematic": "^2.5.2", "@cypress/skip-test": "^2.6.1", + "eslint-plugin-i18n-json": "^4.0.0", + "@angular-eslint/eslint-plugin-template": "^19.0.2", "@stylistic/eslint-plugin": "^2.11.0", "@typescript-eslint/eslint-plugin": "^8.15.0", "@eslint/js": "^9.15.0", diff --git a/frontend/src/app/app.component.spec.ts b/frontend/src/app/app.component.spec.ts index da41e72992..59730f25ce 100644 --- a/frontend/src/app/app.component.spec.ts +++ b/frontend/src/app/app.component.spec.ts @@ -3,51 +3,32 @@ import { RouterTestingModule } from '@angular/router/testing'; import { AppComponent } from './app.component'; import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { TranslateTestingModule } from 'ngx-translate-testing'; -import { AuthConfig, OAuthModule, OAuthService } from 'angular-oauth2-oidc'; +import { OAuthModule, OAuthService } from 'angular-oauth2-oidc'; import { HttpClientTestingModule } from '@angular/common/http/testing'; // @ts-ignore import * as de from '../assets/i18n/de.json'; -import { HarnessLoader } from '@angular/cdk/testing'; -import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; import { MatSidenavModule } from '@angular/material/sidenav'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { NavigationEnd, Routes } from '@angular/router'; -import { of } from 'rxjs'; +import { Routes } from '@angular/router'; import { OverviewComponent } from './components/overview/overview.component'; import { ObjectiveDetailComponent } from './components/objective-detail/objective-detail.component'; import { CommonModule } from '@angular/common'; -const oAuthServiceMock = { - configure(environment: AuthConfig): void {}, - initCodeFlow(): void {}, - setupAutomaticSilentRefresh(): void {}, - hasValidAccessToken(): boolean { - return true; - }, - loadDiscoveryDocumentAndTryLogin(): Promise { - this.initCodeFlow(); - return Promise.resolve(); - } -}; - -const routerMock = { - root: jest.fn(), - // Router - events: of(new NavigationEnd(0, 'http://localhost:4200/objective/2', 'http://localhost:4200/objective/2')) -}; +const oAuthServiceMock = {}; const routes: Routes = [{ path: '', component: OverviewComponent, - children: [{ path: 'objective/:id', + children: [{ + path: 'objective/:id', component: ObjectiveDetailComponent, - pathMatch: 'full' }] + pathMatch: 'full' + }] }]; describe('AppComponent', () => { let component: AppComponent; let fixture: ComponentFixture; - let loader: HarnessLoader; beforeEach(async() => { await TestBed.configureTestingModule({ @@ -62,8 +43,10 @@ describe('AppComponent', () => { NoopAnimationsModule, CommonModule ], - providers: [{ provide: OAuthService, - useValue: oAuthServiceMock }], + providers: [{ + provide: OAuthService, + useValue: oAuthServiceMock + }], declarations: [AppComponent, OverviewComponent], schemas: [CUSTOM_ELEMENTS_SCHEMA] @@ -74,7 +57,6 @@ describe('AppComponent', () => { component = fixture.componentInstance; fixture.detectChanges(); - loader = TestbedHarnessEnvironment.loader(fixture); fixture.detectChanges(); }); }); diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts index 8b2861a876..33a11643d9 100644 --- a/frontend/src/app/app.component.ts +++ b/frontend/src/app/app.component.ts @@ -13,9 +13,7 @@ export class AppComponent { constructor(private matIconRegistry: MatIconRegistry, private domSanitizer: DomSanitizer) { - this.matIconRegistry.addSvgIcon('pz-search', - this.domSanitizer.bypassSecurityTrustResourceUrl(this.PATH_PREFIX + 'search-icon.svg')); - this.matIconRegistry.addSvgIcon('pz-menu-icon', - this.domSanitizer.bypassSecurityTrustResourceUrl(this.PATH_PREFIX + 'three-dot-menu-icon.svg')); + this.matIconRegistry.addSvgIcon('pz-search', this.domSanitizer.bypassSecurityTrustResourceUrl(this.PATH_PREFIX + 'search-icon.svg')); + this.matIconRegistry.addSvgIcon('pz-menu-icon', this.domSanitizer.bypassSecurityTrustResourceUrl(this.PATH_PREFIX + 'three-dot-menu-icon.svg')); } } diff --git a/frontend/src/app/components/action-plan/action-plan.component.spec.ts b/frontend/src/app/components/action-plan/action-plan.component.spec.ts index 943111a5fb..a898fe8db3 100644 --- a/frontend/src/app/components/action-plan/action-plan.component.spec.ts +++ b/frontend/src/app/components/action-plan/action-plan.component.spec.ts @@ -116,7 +116,7 @@ describe('ActionPlanComponent', () => { action3]); component.handleKeyDown(keyEvent, 2); - expect((component.activeItem = 1)); + expect(component.activeItem = 1); expect(component.control.getValue()!.toString()) .toBe([action1, action3, @@ -136,7 +136,7 @@ describe('ActionPlanComponent', () => { ]); component.handleKeyDown(keyEvent, 2); - expect((component.activeItem = 3)); + expect(component.activeItem = 3); expect(component.control.getValue()!.toString()) .toBe([ action1, diff --git a/frontend/src/app/components/action-plan/action-plan.component.ts b/frontend/src/app/components/action-plan/action-plan.component.ts index 623055e1d8..674ddcf58d 100644 --- a/frontend/src/app/components/action-plan/action-plan.component.ts +++ b/frontend/src/app/components/action-plan/action-plan.component.ts @@ -22,7 +22,8 @@ export class ActionPlanComponent { listItems!: QueryList; constructor(private actionService: ActionService, - public dialogService: DialogService) {} + public dialogService: DialogService) { + } handleKeyDown(event: Event, currentIndex: number) { let newIndex = currentIndex; @@ -44,18 +45,18 @@ export class ActionPlanComponent { const currentActionPlan: Action[] = this.control.getValue()!; this.updateActionTexts(currentActionPlan); moveItemInArray(currentActionPlan, currentIndex, newIndex); - currentActionPlan.forEach((action: Action, index: number) => (action.priority = index)); + currentActionPlan.forEach((action: Action, index: number) => action.priority = index); this.control.next(currentActionPlan); } updateActionTexts(currentActionPlan: Action[]) { const texts = Array.from(this.listItems) .map((input: any) => input.nativeElement.value); - currentActionPlan.forEach((action: Action, index: number) => (action.action = texts[index])); + currentActionPlan.forEach((action: Action, index: number) => action.action = texts[index]); } increaseActiveItemWithTab() { - if (this.activeItem <= this.control.value!.length - 2) { + if (this.activeItem <= this.control.getValue()!.length - 2) { this.activeItem++; } } @@ -70,8 +71,10 @@ export class ActionPlanComponent { const value: string = (event.container.element.nativeElement.children[event.previousIndex].children[1] as HTMLInputElement).value; const actions: Action[] = this.control.getValue()!; if (actions[event.previousIndex].action == '' && value != '') { - actions[event.previousIndex] = { ...actions[event.previousIndex], - action: value }; + actions[event.previousIndex] = { + ...actions[event.previousIndex], + action: value + }; this.control.next(actions); } if (event.previousContainer === event.container) { @@ -122,9 +125,11 @@ export class ActionPlanComponent { addNewAction() { const actions: Action[] = this.control.getValue()!; - actions.push({ action: '', + actions.push({ + action: '', priority: actions.length, - keyResultId: this.keyResultId } as Action); + keyResultId: this.keyResultId + } as Action); this.control.next(actions); this.activeItem = actions.length - 1; } diff --git a/frontend/src/app/components/checkin/check-in-form-metric/metric-check-in-directive.spec.ts b/frontend/src/app/components/checkin/check-in-form-metric/metric-check-in-directive.spec.ts index 4ef1d3d017..c4a93de78e 100644 --- a/frontend/src/app/components/checkin/check-in-form-metric/metric-check-in-directive.spec.ts +++ b/frontend/src/app/components/checkin/check-in-form-metric/metric-check-in-directive.spec.ts @@ -12,7 +12,7 @@ describe('MetricCheckInDirective', () => { 200], ['200HelloWorld', 200], - ["200'000", + ['200\'000', 200000], ['1050&%ç*', 1050], diff --git a/frontend/src/app/components/checkin/check-in-form-metric/metric-check-in-directive.ts b/frontend/src/app/components/checkin/check-in-form-metric/metric-check-in-directive.ts index 98f4638545..55375f93c5 100644 --- a/frontend/src/app/components/checkin/check-in-form-metric/metric-check-in-directive.ts +++ b/frontend/src/app/components/checkin/check-in-form-metric/metric-check-in-directive.ts @@ -1,4 +1,4 @@ -import { Directive, HostListener, forwardRef } from '@angular/core'; +import { Directive, forwardRef, HostListener } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; @Directive({ @@ -10,20 +10,21 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; }] }) export class MetricCheckInDirective implements ControlValueAccessor { - private onChange: (value: number | null) => void = () => {}; + private onChange: (value: number | null) => void = () => { + }; protected readonly CHAR_REGEX = /[^0-9.]/g; - writeValue(value: any): void { - // does not need to be implemented because the display value does not need to be modified + writeValue(): void { + // does not need to be implemented because the display value does not need to be modified comment is here to make linter happy } registerOnChange(fn: (value: number | null) => void): void { this.onChange = fn; } - registerOnTouched(fn: () => void): void { - // does not need to be implemented + registerOnTouched(): void { + // does not need to be implemented comment is here to make linter happy } @HostListener('input', ['$event.target.value']) diff --git a/frontend/src/app/components/checkin/check-in-form-ordinal/check-in-form-ordinal.component.html b/frontend/src/app/components/checkin/check-in-form-ordinal/check-in-form-ordinal.component.html index a15793ca9b..99eec7cbc4 100644 --- a/frontend/src/app/components/checkin/check-in-form-ordinal/check-in-form-ordinal.component.html +++ b/frontend/src/app/components/checkin/check-in-form-ordinal/check-in-form-ordinal.component.html @@ -12,7 +12,8 @@ class="ordinal-zone" >
- Fail:  Commit / Target / Stretch noch nicht erreicht + Fail:  + Commit / Target / Stretch noch nicht erreicht
@@ -22,7 +23,10 @@ class="ordinal-zone" >
- Commit: {{ keyResult.commitZone }} + + Commit:  + {{ keyResult.commitZone }} +
@@ -32,7 +36,10 @@ class="ordinal-zone" >
- Target: {{ keyResult.targetZone }} + + Target:  + {{ keyResult.targetZone }} +
@@ -42,7 +49,10 @@ class="ordinal-zone" >
- Stretch: {{ keyResult.stretchZone }} + + Stretch:  + {{ keyResult.stretchZone }} +
diff --git a/frontend/src/app/components/checkin/check-in-form/check-in-form.component.ts b/frontend/src/app/components/checkin/check-in-form/check-in-form.component.ts index 3298e393e1..6711b0ba98 100644 --- a/frontend/src/app/components/checkin/check-in-form/check-in-form.component.ts +++ b/frontend/src/app/components/checkin/check-in-form/check-in-form.component.ts @@ -72,6 +72,7 @@ export class CheckInFormComponent implements OnInit { this.dialogForm.controls.initiatives.setValue(this.checkIn.initiatives); return; } + /* If KeyResult has lastCheckIn set checkIn to this value */ if ((this.keyResult as KeyResultMetric | KeyResultOrdinal).lastCheckIn != null) { this.checkIn = { @@ -81,6 +82,7 @@ export class CheckInFormComponent implements OnInit { this.dialogForm.controls.confidence.setValue(this.checkIn.confidence); return; } + /* If Check-in is null set as object with confidence 5 default value */ this.checkIn = { confidence: 5 } as CheckInMin; } @@ -136,8 +138,10 @@ export class CheckInFormComponent implements OnInit { changeIsChecked(event: any, index: number) { const actions = this.dialogForm.value.actionList!; - actions[index] = { ...actions[index], - isChecked: event.checked }; + actions[index] = { + ...actions[index], + isChecked: event.checked + }; this.dialogForm.patchValue({ actionList: actions }); } diff --git a/frontend/src/app/components/confidence/confidence.component.spec.ts b/frontend/src/app/components/confidence/confidence.component.spec.ts index 6c6e4b623f..583ebd8b0f 100644 --- a/frontend/src/app/components/confidence/confidence.component.spec.ts +++ b/frontend/src/app/components/confidence/confidence.component.spec.ts @@ -45,7 +45,7 @@ describe('ConfidenceComponent', () => { }); fixture.detectChanges(); await fixture.whenStable(); - const textField = fixture.debugElement.query(By.css("[data-testId='confidence']")); + const textField = fixture.debugElement.query(By.css('[data-testId=\'confidence\']')); const expectedLabel = expected + '/' + component.max; const sliderInputField = fixture.debugElement.query(By.css('mat-slider > input ')); diff --git a/frontend/src/app/components/key-result-detail/key-result-detail.component.html b/frontend/src/app/components/key-result-detail/key-result-detail.component.html index 6be9fbb3ef..fee2d36990 100644 --- a/frontend/src/app/components/key-result-detail/key-result-detail.component.html +++ b/frontend/src/app/components/key-result-detail/key-result-detail.component.html @@ -52,7 +52,7 @@

Confidence

- +

Baseline: {{ keyResultMetric.baseline | unitTransformation: keyResultMetric.unit }} @@ -76,7 +76,7 @@

Confidence

- +

Commit

@@ -106,7 +106,7 @@

Beschrieb

{{ keyResult.description || "-" }}

-
+

Action Plan

@@ -120,7 +120,10 @@

Action Plan

-
- {{ action.action }}
+
+ - + {{ action.action }} +
diff --git a/frontend/src/app/components/key-result-form/key-result-form.component.ts b/frontend/src/app/components/key-result-form/key-result-form.component.ts index 7fd70a3585..40c87ce96d 100644 --- a/frontend/src/app/components/key-result-form/key-result-form.component.ts +++ b/frontend/src/app/components/key-result-form/key-result-form.component.ts @@ -40,9 +40,7 @@ export class KeyResultFormComponent implements OnInit, OnDestroy { ngOnInit(): void { this.users$ = this.userService.getUsers(); - this.filteredUsers$ = this.keyResultForm.get('owner')?.valueChanges.pipe(startWith(''), - filter((value) => typeof value === 'string'), - switchMap((value) => this.filter(value as string))); + this.filteredUsers$ = this.keyResultForm.get('owner')?.valueChanges.pipe(startWith(''), filter((value) => typeof value === 'string'), switchMap((value) => this.filter(value as string))); if (this.keyResult) { this.keyResultForm.patchValue({ actionList: this.keyResult.actionList }); this.keyResultForm.controls['title'].setValue(this.keyResult.title); diff --git a/frontend/src/app/components/key-result-type/key-result-type.component.ts b/frontend/src/app/components/key-result-type/key-result-type.component.ts index 3c143d8a71..6015b9b135 100644 --- a/frontend/src/app/components/key-result-type/key-result-type.component.ts +++ b/frontend/src/app/components/key-result-type/key-result-type.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; import { KeyResult } from '../../shared/types/model/KeyResult'; import { FormGroup, Validators } from '@angular/forms'; import { KeyResultMetric } from '../../shared/types/model/KeyResultMetric'; @@ -17,7 +17,6 @@ export class KeyResultTypeComponent implements OnInit { @Input() keyResult!: KeyResult | null; - @Output() formValidityEmitter = new EventEmitter(); isMetric = true; @@ -29,7 +28,8 @@ export class KeyResultTypeComponent implements OnInit { protected readonly hasFormFieldErrors = hasFormFieldErrors; - constructor(private translate: TranslateService) {} + constructor(private translate: TranslateService) { + } ngOnInit(): void { if (this.keyResult) { @@ -65,7 +65,6 @@ export class KeyResultTypeComponent implements OnInit { async updateFormValidity() { await new Promise((r) => setTimeout(r, 100)); - this.formValidityEmitter.emit(this.keyResultForm.invalid); } setValidatorsMetric() { @@ -98,7 +97,7 @@ export class KeyResultTypeComponent implements OnInit { } switchKeyResultType(type: string) { - if (((type == 'metric' && !this.isMetric) || (type == 'ordinal' && this.isMetric)) && this.typeChangeAllowed) { + if ((type == 'metric' && !this.isMetric || type == 'ordinal' && this.isMetric) && this.typeChangeAllowed) { this.isMetric = !this.isMetric; const keyResultType = this.isMetric ? 'metric' : 'ordinal'; this.keyResultForm.controls['keyResultType'].setValue(keyResultType); diff --git a/frontend/src/app/components/objective-detail/objective-detail.component.html b/frontend/src/app/components/objective-detail/objective-detail.component.html index 9e9b5094c8..55ac578e19 100644 --- a/frontend/src/app/components/objective-detail/objective-detail.component.html +++ b/frontend/src/app/components/objective-detail/objective-detail.component.html @@ -19,7 +19,9 @@

{{ objective.title }}

Beschrieb

-

-

+
+

-

+
Beschrieb

{{ objective.description }}

-
+
+ >Abbrechen +
-
+