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..8f2333955a 100644 --- a/frontend/cypress/e2e/team-management.cy.ts +++ b/frontend/cypress/e2e/team-management.cy.ts @@ -304,7 +304,7 @@ describe('okr team-management', () => { return true; }) .then(() => { - expect(foundEsha).to.be.true; + expect(foundEsha).to.equal(true); }); }); @@ -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-lock.json b/frontend/package-lock.json index 3b7dd96e24..050060cc9a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -30,6 +30,7 @@ }, "devDependencies": { "@angular-devkit/build-angular": "^18.2.9", + "@angular-eslint/eslint-plugin-template": "^19.0.2", "@angular/cli": "~18.2.9", "@angular/compiler-cli": "^18.2.8", "@cypress/schematic": "^2.5.2", @@ -47,6 +48,7 @@ "cypress": "^13.15.0", "cypress-real-events": "^1.13.0", "eslint": "^9.16.0", + "eslint-plugin-i18n-json": "^4.0.0", "eslint-plugin-unused-imports": "^4.1.4", "jest": "^29.7.0", "jest-preset-angular": "^14.2.4", @@ -3620,6 +3622,62 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@formatjs/ecma402-abstract": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.1.tgz", + "integrity": "sha512-Ip9uV+/MpLXWRk03U/GzeJMuPeOXpJBSB5V1tjA6kJhvqssye5J5LoYLc7Z5IAHb7nR62sRoguzrFiVCP/hnzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@formatjs/fast-memoize": "2.2.5", + "@formatjs/intl-localematcher": "0.5.9", + "decimal.js": "10", + "tslib": "2" + } + }, + "node_modules/@formatjs/fast-memoize": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.5.tgz", + "integrity": "sha512-6PoewUMrrcqxSoBXAOJDiW1m+AmkrAj0RiXnOMD59GRaswjXhm3MDhgepXPBgonc09oSirAJTsAggzAGQf6A6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "2" + } + }, + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "2.9.7", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.9.7.tgz", + "integrity": "sha512-cuEHyRM5VqLQobANOjtjlgU7+qmk9Q3fDQuBiRRJ3+Wp3ZoZhpUPtUfuimZXsir6SaI2TaAJ+SLo9vLnV5QcbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@formatjs/ecma402-abstract": "2.3.1", + "@formatjs/icu-skeleton-parser": "1.8.11", + "tslib": "2" + } + }, + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "1.8.11", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.11.tgz", + "integrity": "sha512-8LlHHE/yL/zVJZHAX3pbKaCjZKmBIO6aJY1mkVh4RMSEu/2WRZ4Ysvv3kKXJ9M8RJLBHdnk1/dUQFdod1Dt7Dw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@formatjs/ecma402-abstract": "2.3.1", + "tslib": "2" + } + }, + "node_modules/@formatjs/intl-localematcher": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.9.tgz", + "integrity": "sha512-8zkGu/sv5euxbjfZ/xmklqLyDGQSxsLqg8XOq88JW3cmJtzhCP8EtSJXlaKZnVO4beEaoiT9wj4eIoCQ9smwxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "2" + } + }, "node_modules/@html-eslint/eslint-plugin": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/@html-eslint/eslint-plugin/-/eslint-plugin-0.27.0.tgz", @@ -6577,6 +6635,16 @@ "node": ">=8" } }, + "node_modules/aggregate-error/node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/ajv": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", @@ -8947,6 +9015,16 @@ "dev": true, "license": "MIT" }, + "node_modules/diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -9029,9 +9107,9 @@ } }, "node_modules/domutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", - "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.0.tgz", + "integrity": "sha512-pmjBRsZD5Fz3XR8NlM1zTBDlHjB5OvLjMkGMGHrLabzoECJNPPOCiCNEgSSLQn2pYIOO4Ytfia7zIehRiit+sQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -9506,6 +9584,142 @@ } } }, + "node_modules/eslint-plugin-i18n-json": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-i18n-json/-/eslint-plugin-i18n-json-4.0.1.tgz", + "integrity": "sha512-LNQS2XeEy1fdCOn/n3Aeh7RWPVbwKL0tH4Q1c2Y/M1TN6Jo3uN6C3cTvtjzJEms7ul0rmCAPoGqM4IsVCOIxnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@formatjs/icu-messageformat-parser": "^2.0.18", + "chalk": "^2.3.2", + "indent-string": "^3.2.0", + "jest-diff": "^22.0.3", + "lodash": "^4.17.21", + "log-symbols": "^2.2.0", + "parse-json": "^5.2.0", + "plur": "^2.1.2", + "pretty-format": "^22.0.3" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "eslint": ">=4.0.0" + } + }, + "node_modules/eslint-plugin-i18n-json/node_modules/ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-i18n-json/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-i18n-json/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-i18n-json/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/eslint-plugin-i18n-json/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-i18n-json/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint-plugin-i18n-json/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-i18n-json/node_modules/log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-i18n-json/node_modules/pretty-format": { + "version": "22.4.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-22.4.3.tgz", + "integrity": "sha512-S4oT9/sT6MN7/3COoOy+ZJeA92VmOnveLHgrwBE3Z1W5N9S2A1QGNYiE1z75DAENbJrXXUb+OWXhpJcg05QKQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^3.0.0", + "ansi-styles": "^3.2.0" + } + }, + "node_modules/eslint-plugin-i18n-json/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/eslint-plugin-unused-imports": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.1.4.tgz", @@ -11146,13 +11360,13 @@ } }, "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha512-BYqTHXTGUIvg7t1r4sJNKcbDZkL92nkXA8YtRpbjFHRHGDL/NtUeiBJMeE60kIFN/Mg8ESaWQvftaYMGJzQZCQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=4" } }, "node_modules/inflight": { @@ -11208,6 +11422,16 @@ "node": ">= 10" } }, + "node_modules/irregular-plurals": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-1.4.0.tgz", + "integrity": "sha512-kniTIJmaZYiwa17eTtWIfm0K342seyugl6vuC8DiiyiRAJWAVlLkqGCI0Im0neo0TkXw+pRcKaBPRdcKHnQJ6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -11894,19 +12118,122 @@ } }, "node_modules/jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "version": "22.4.3", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-22.4.3.tgz", + "integrity": "sha512-/QqGvCDP5oZOF6PebDuLwrB2BMD8ffJv6TAGAdEVuDx1+uEgrHpSFrfrOiMRx2eJ1hgNjlQrOQEHetVwij90KA==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "chalk": "^2.0.1", + "diff": "^3.2.0", + "jest-get-type": "^22.4.3", + "pretty-format": "^22.4.3" + } + }, + "node_modules/jest-diff/node_modules/ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=4" + } + }, + "node_modules/jest-diff/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/jest-diff/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/jest-diff/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-diff/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/jest-diff/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/jest-diff/node_modules/jest-get-type": { + "version": "22.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", + "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-diff/node_modules/pretty-format": { + "version": "22.4.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-22.4.3.tgz", + "integrity": "sha512-S4oT9/sT6MN7/3COoOy+ZJeA92VmOnveLHgrwBE3Z1W5N9S2A1QGNYiE1z75DAENbJrXXUb+OWXhpJcg05QKQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^3.0.0", + "ansi-styles": "^3.2.0" + } + }, + "node_modules/jest-diff/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" } }, "node_modules/jest-docblock": { @@ -12051,6 +12378,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-matcher-utils/node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/jest-message-util": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", @@ -12328,6 +12671,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-snapshot/node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/jest-util": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", @@ -14987,6 +15346,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/plur": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/plur/-/plur-2.1.2.tgz", + "integrity": "sha512-WhcHk576xg9y/iv6RWOuroZgsqvCbJN+XGvAypCJwLAYs2iWDp5LUmvaCdV6JR2O0SMBf8l6p7A94AyLCFVMlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "irregular-plurals": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/postcss": { "version": "8.4.41", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", 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..a628972571 100644 --- a/frontend/src/app/components/confidence/confidence.component.spec.ts +++ b/frontend/src/app/components/confidence/confidence.component.spec.ts @@ -1,8 +1,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ConfidenceComponent } from './confidence.component'; -import { HarnessLoader } from '@angular/cdk/testing'; -import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; import { checkInMetric } from '../../shared/testData'; import { MatSliderModule } from '@angular/material/slider'; import { CheckInMin } from '../../shared/types/model/CheckInMin'; @@ -13,7 +11,6 @@ import { By } from '@angular/platform-browser'; describe('ConfidenceComponent', () => { let component: ConfidenceComponent; let fixture: ComponentFixture; - let loader: HarnessLoader; beforeEach(() => { TestBed.configureTestingModule({ @@ -25,7 +22,6 @@ describe('ConfidenceComponent', () => { fixture = TestBed.createComponent(ConfidenceComponent); component = fixture.componentInstance; - loader = TestbedHarnessEnvironment.loader(fixture); component.checkIn = checkInMetric; component.isEdit = true; }); @@ -45,7 +41,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..6f88e55fef 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); @@ -145,7 +143,9 @@ export class KeyResultFormComponent implements OnInit, OnDestroy { return this.keyResult ? this.keyResult.id : null; } - updateFormValidity() {} + updateFormValidity() { + // Implemented because of interface this comment is to satisfy the linter + } getFullNameOfLoggedInUser() { return this.getFullNameOfUser(this.userService.getCurrentUser()); 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 +
-
+