Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ jobs:
run: pnpm install --frozen-lockfile --ignore-scripts
working-directory: insights-webapp/src/main/frontend

- name: Typecheck Frontend
run: pnpm typecheck
working-directory: insights-webapp/src/main/frontend

- name: Lint Frontend
run: pnpm lint
working-directory: insights-webapp/src/main/frontend
Expand Down
4 changes: 2 additions & 2 deletions insights-webapp/src/main/frontend/angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
},
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": ["zone.js"],
"polyfills": [],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
Expand Down Expand Up @@ -78,7 +78,7 @@
"test": {
"builder": "@angular/build:karma",
"options": {
"polyfills": ["zone.js", "zone.js/testing"],
"polyfills": [],
"tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss",
"assets": ["src/assets"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ describe('Graph Rendering and Interaction', () => {
cy.get('@graphSvg').then(($svg) => {
const nightlyNodes = $svg.find('a[data-cy*="-nightly"]');
if (nightlyNodes.length > 0) {
nightlyNodes.each((idx, node) => {
nightlyNodes.each((idx: number, node: HTMLElement) => {
const transform = Cypress.$(node).attr('transform');
const yPos = transform?.match(/translate\([^,]+,([^)]+)\)/)?.[1];
const isOnMasterBranch = yPos === '0';
Expand Down
8 changes: 4 additions & 4 deletions insights-webapp/src/main/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"test": "ng test",
"lint": "ng lint",
"lint:fix": "ng lint --fix",
"typecheck": "ngc -p tsconfig.app.json --noEmit && ngc -p tsconfig.spec.json --noEmit && tsc -p cypress/tsconfig.json --noEmit",
"e2e": "ng e2e",
"cypress:open": "cypress open --project cypress",
"cypress:run": "cypress run --project cypress"
Expand All @@ -30,8 +31,7 @@
"ng2-charts": "^10.0.0",
"ngx-markdown": "^22.0.0",
"rxjs": "~7.8.2",
"tslib": "^2.8.1",
"zone.js": "~0.16.2"
"tslib": "^2.8.1"
},
"devDependencies": {
"@angular-eslint/builder": "^22.1.0",
Expand All @@ -57,12 +57,12 @@
"eslint-plugin-sonarjs": "^4.2.0",
"eslint-plugin-unicorn": "^64.0.0",
"istanbul-lib-instrument": "^6.0.3",
"jasmine-core": "~6.3.0",
"jasmine-core": "~7.0.1",
"karma": "~6.4.4",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.1",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.2.0",
"karma-jasmine-html-reporter": "~2.3.0",
"prettier": "^3.9.6",
"typescript": "~6.0.3",
"typescript-eslint": "^8.66.0"
Expand Down
4 changes: 2 additions & 2 deletions insights-webapp/src/main/frontend/src/app/app.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { ApplicationConfig, provideZonelessChangeDetection } from '@angular/core';
import { provideRouter, UrlSerializer, withComponentInputBinding } from '@angular/router';
import { routes } from './app.routes';
import { ReadableUrlSerializer } from './services/readable-url.serializer';
Expand All @@ -13,7 +13,7 @@ import { HttpInterceptorService } from './services/http-interceptor.service';

export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideZonelessChangeDetection(),
provideRouter(routes, withComponentInputBinding()),
{ provide: UrlSerializer, useClass: ReadableUrlSerializer },
provideHttpClient(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { WritableSignal, signal } from '@angular/core';
import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';

import { TooltipComponent } from './tooltip.component';
Expand Down Expand Up @@ -83,7 +83,7 @@ describe('TooltipComponent', () => {
expect(tooltipElement).toBeNull();
});

it('should display the tooltip with correct data when service emits state', fakeAsync(() => {
it('should display the tooltip with correct data when service emits state', () => {
const tooltipData: TooltipData = {
title: 'Tooltip Test Issue',
details: [
Expand All @@ -95,7 +95,6 @@ describe('TooltipComponent', () => {
};
tooltipState.set(tooltipData);
fixture.detectChanges();
tick();

const tooltipElement = fixture.debugElement.query(By.css('.tooltip'));

Expand All @@ -108,9 +107,9 @@ describe('TooltipComponent', () => {
expect(detailsElement.length).toBe(2);
expect(detailsElement[0].nativeElement.textContent).toContain('Priority: High');
expect(detailsElement[1].nativeElement.textContent).toContain('Points: 8');
}));
});

it('should render a detail without a label as plain text', fakeAsync(() => {
it('should render a detail without a label as plain text', () => {
const tooltipData: TooltipData = {
title: 'Attack Vector',
details: [{ value: 'How the vulnerability is exploited' }],
Expand All @@ -119,29 +118,26 @@ describe('TooltipComponent', () => {
};
tooltipState.set(tooltipData);
fixture.detectChanges();
tick();

const detailElement = fixture.debugElement.query(By.css('.tooltip-detail'));

expect(detailElement.nativeElement.textContent.trim()).toBe('How the vulnerability is exploited');
}));
});

it('should hide the tooltip when service emits null after showing', fakeAsync(() => {
it('should hide the tooltip when service emits null after showing', () => {
const tooltipData: TooltipData = { title: 'Tooltip Test Issue', details: [], top: '100px', left: '200px' };
tooltipState.set(tooltipData);
fixture.detectChanges();
tick();

let tooltipElement = fixture.debugElement.query(By.css('.tooltip'));

expect(tooltipElement).not.toBeNull();

tooltipState.set(null);
fixture.detectChanges();
tick();

tooltipElement = fixture.debugElement.query(By.css('.tooltip'));

expect(tooltipElement).toBeNull();
}));
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideHttpClient, withXhr } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { provideRouter } from '@angular/router';
Expand Down Expand Up @@ -552,26 +552,38 @@ describe('CveOverviewComponent', () => {
});

describe('onSearch()', () => {
it('calls the search subject (debounced)', fakeAsync(() => {
const DEBOUNCE_TICK_MS = 400;

beforeEach(() => {
TestBed.tick();
jasmine.clock().install();
jasmine.clock().mockDate();
});

afterEach(() => {
jasmine.clock().uninstall();
});

it('calls the search subject (debounced)', () => {
component.searchQuery.set('CVE-2024');
fixture.detectChanges();
tick(400);
TestBed.tick();
jasmine.clock().tick(DEBOUNCE_TICK_MS);

expect(vulnerabilityService.getAllVulnerabilitiesPaginated).toHaveBeenCalledWith(0, 20, 'CVE-2024');
}));
});

it('deduplicated rapid searches only trigger once', fakeAsync(() => {
it('deduplicated rapid searches only trigger once', () => {
component.searchQuery.set('CVE');
fixture.detectChanges();
TestBed.tick();
component.searchQuery.set('CVE');
fixture.detectChanges();
tick(400);
TestBed.tick();
jasmine.clock().tick(DEBOUNCE_TICK_MS);
const uniqueCveSearches = vulnerabilityService.getAllVulnerabilitiesPaginated.calls
.all()
.filter((c) => c.args[2] === 'CVE');

expect(uniqueCveSearches.length).toBe(1);
}));
});
});

describe('LTS branch detection', () => {
Expand Down
Loading
Loading