From 0a1d273697305a12b8ab6b3602c1fdb0820431de Mon Sep 17 00:00:00 2001
From: Boris Vasilenko <bvasilenko@ixsystems.com>
Date: Wed, 11 Dec 2024 17:16:36 +0300
Subject: [PATCH] NAS-132636: Fix unit tests (Part 2)

---
 .../ix-dynamic-form-item.component.spec.ts    | 69 +++++++------------
 .../global-search-trigger.component.spec.ts   |  3 +-
 .../global-search/global-search.component.ts  | 19 +++--
 .../scheduler-preview-column.component.ts     | 18 +++--
 .../custom-app-button.component.spec.ts       |  7 +-
 src/app/pages/audit/audit.component.spec.ts   |  2 +-
 .../widget-group-slot-form.component.ts       | 15 ++--
 .../network-chart.component.spec.ts           | 26 +++++--
 .../widget-interface.component.spec.ts        | 12 ++--
 .../manual-selection-disks.component.spec.ts  |  1 +
 .../bootenv-status.component.spec.ts          |  1 +
 .../qr-viewer/qr-viewer.component.spec.ts     |  9 +--
 .../two-factor.component.spec.ts              |  5 +-
 13 files changed, 95 insertions(+), 92 deletions(-)

diff --git a/src/app/modules/forms/ix-dynamic-form/components/ix-dynamic-form/ix-dynamic-form-item/ix-dynamic-form-item.component.spec.ts b/src/app/modules/forms/ix-dynamic-form/components/ix-dynamic-form/ix-dynamic-form-item/ix-dynamic-form-item.component.spec.ts
index b5ab839591a..707406f0c78 100644
--- a/src/app/modules/forms/ix-dynamic-form/components/ix-dynamic-form/ix-dynamic-form-item/ix-dynamic-form-item.component.spec.ts
+++ b/src/app/modules/forms/ix-dynamic-form/components/ix-dynamic-form/ix-dynamic-form-item/ix-dynamic-form-item.component.spec.ts
@@ -2,7 +2,6 @@ import {
   FormArray, FormControl, FormGroup, ReactiveFormsModule,
 } from '@angular/forms';
 import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
-import { MockComponent } from 'ng-mocks';
 import { BehaviorSubject, of } from 'rxjs';
 import { CodeEditorLanguage } from 'app/enums/code-editor-language.enum';
 import {
@@ -20,15 +19,12 @@ import { CustomUntypedFormField } from 'app/modules/forms/ix-dynamic-form/compon
 import { IxDynamicFormItemComponent } from 'app/modules/forms/ix-dynamic-form/components/ix-dynamic-form/ix-dynamic-form-item/ix-dynamic-form-item.component';
 import { IxCheckboxComponent } from 'app/modules/forms/ix-forms/components/ix-checkbox/ix-checkbox.component';
 import { IxCodeEditorComponent } from 'app/modules/forms/ix-forms/components/ix-code-editor/ix-code-editor.component';
-import { IxErrorsComponent } from 'app/modules/forms/ix-forms/components/ix-errors/ix-errors.component';
 import { IxExplorerComponent } from 'app/modules/forms/ix-forms/components/ix-explorer/ix-explorer.component';
 import { IxInputComponent } from 'app/modules/forms/ix-forms/components/ix-input/ix-input.component';
 import { IxIpInputWithNetmaskComponent } from 'app/modules/forms/ix-forms/components/ix-ip-input-with-netmask/ix-ip-input-with-netmask.component';
-import { IxLabelComponent } from 'app/modules/forms/ix-forms/components/ix-label/ix-label.component';
 import { IxListItemComponent } from 'app/modules/forms/ix-forms/components/ix-list/ix-list-item/ix-list-item.component';
 import { IxListComponent } from 'app/modules/forms/ix-forms/components/ix-list/ix-list.component';
 import { IxSelectComponent } from 'app/modules/forms/ix-forms/components/ix-select/ix-select.component';
-import { CastPipe } from 'app/modules/pipes/cast/cast.pipe';
 
 const dynamicForm = new FormGroup({
   dict: new FormGroup({
@@ -138,19 +134,6 @@ describe('IxDynamicFormItemComponent', () => {
     imports: [
       ReactiveFormsModule,
     ],
-    declarations: [
-      MockComponent(IxErrorsComponent),
-      MockComponent(IxLabelComponent),
-      MockComponent(IxInputComponent),
-      MockComponent(IxListComponent),
-      MockComponent(IxCodeEditorComponent),
-      MockComponent(IxListItemComponent),
-      MockComponent(IxSelectComponent),
-      MockComponent(IxCheckboxComponent),
-      MockComponent(IxIpInputWithNetmaskComponent),
-      MockComponent(IxExplorerComponent),
-      CastPipe,
-    ],
   });
 
   beforeEach(() => {
@@ -166,12 +149,12 @@ describe('IxDynamicFormItemComponent', () => {
         },
       });
       expect(spectator.query('ix-input')).toBeVisible();
-      expect(spectator.query(IxInputComponent).required).toBe(inputSchema.required);
-      expect(spectator.query(IxInputComponent).type).toBe(inputSchema.inputType);
-      expect(spectator.query(IxInputComponent).tooltip).toBe(inputSchema.tooltip);
+      expect(spectator.query(IxInputComponent).required()).toBe(inputSchema.required);
+      expect(spectator.query(IxInputComponent).type()).toBe(inputSchema.inputType);
+      expect(spectator.query(IxInputComponent).tooltip()).toBe(inputSchema.tooltip);
 
       expect(spectator.query('ix-input')).not.toBeHidden();
-      const field = spectator.component.dynamicForm.controls.input as CustomUntypedFormField;
+      const field = spectator.component.dynamicForm().controls.input as CustomUntypedFormField;
       if (!field.hidden$) {
         field.hidden$ = new BehaviorSubject<boolean>(false);
       }
@@ -188,10 +171,10 @@ describe('IxDynamicFormItemComponent', () => {
         },
       });
       expect(spectator.query('ix-code-editor')).toBeVisible();
-      expect(spectator.query(IxCodeEditorComponent).required).toBe(textSchema.required);
-      expect(spectator.query(IxCodeEditorComponent).tooltip).toBe(textSchema.tooltip);
+      expect(spectator.query(IxCodeEditorComponent).required()).toBe(textSchema.required);
+      expect(spectator.query(IxCodeEditorComponent).tooltip()).toBe(textSchema.tooltip);
       expect(spectator.query('ix-code-editor')).not.toBeHidden();
-      const field = spectator.component.dynamicForm.controls.text as CustomUntypedFormField;
+      const field = spectator.component.dynamicForm().controls.text as CustomUntypedFormField;
       if (!field.hidden$) {
         field.hidden$ = new BehaviorSubject<boolean>(false);
       }
@@ -208,12 +191,12 @@ describe('IxDynamicFormItemComponent', () => {
         },
       });
       expect(spectator.query('ix-select')).toBeVisible();
-      expect(spectator.query(IxSelectComponent).required).toBe(selectSchema.required);
-      expect(spectator.query(IxSelectComponent).hideEmpty).toBe(selectSchema.hideEmpty);
-      expect(spectator.query(IxSelectComponent).tooltip).toBe(selectSchema.tooltip);
+      expect(spectator.query(IxSelectComponent).required()).toBe(selectSchema.required);
+      expect(spectator.query(IxSelectComponent).hideEmpty()).toBe(selectSchema.hideEmpty);
+      expect(spectator.query(IxSelectComponent).tooltip()).toBe(selectSchema.tooltip);
 
       expect(spectator.query('ix-select')).not.toBeHidden();
-      const field = spectator.component.dynamicForm.controls.select as CustomUntypedFormField;
+      const field = spectator.component.dynamicForm().controls.select as CustomUntypedFormField;
       if (!field.hidden$) {
         field.hidden$ = new BehaviorSubject<boolean>(false);
       }
@@ -230,11 +213,11 @@ describe('IxDynamicFormItemComponent', () => {
         },
       });
       expect(spectator.query('ix-checkbox')).toBeVisible();
-      expect(spectator.query(IxCheckboxComponent).required).toBe(checkboxSchema.required);
-      expect(spectator.query(IxCheckboxComponent).tooltip).toBe(checkboxSchema.tooltip);
+      expect(spectator.query(IxCheckboxComponent).required()).toBe(checkboxSchema.required);
+      expect(spectator.query(IxCheckboxComponent).tooltip()).toBe(checkboxSchema.tooltip);
 
       expect(spectator.query('ix-checkbox')).not.toBeHidden();
-      const field = spectator.component.dynamicForm.controls.checkbox as CustomUntypedFormField;
+      const field = spectator.component.dynamicForm().controls.checkbox as CustomUntypedFormField;
       if (!field.hidden$) {
         field.hidden$ = new BehaviorSubject<boolean>(false);
       }
@@ -251,11 +234,11 @@ describe('IxDynamicFormItemComponent', () => {
         },
       });
       expect(spectator.query('ix-ip-input-with-netmask')).toBeVisible();
-      expect(spectator.query(IxIpInputWithNetmaskComponent).required).toBe(ipaddrSchema.required);
-      expect(spectator.query(IxIpInputWithNetmaskComponent).tooltip).toBe(ipaddrSchema.tooltip);
+      expect(spectator.query(IxIpInputWithNetmaskComponent).required()).toBe(ipaddrSchema.required);
+      expect(spectator.query(IxIpInputWithNetmaskComponent).tooltip()).toBe(ipaddrSchema.tooltip);
 
       expect(spectator.query('ix-ip-input-with-netmask')).not.toBeHidden();
-      const field = spectator.component.dynamicForm.controls.ipaddr as CustomUntypedFormField;
+      const field = spectator.component.dynamicForm().controls.ipaddr as CustomUntypedFormField;
       if (!field.hidden$) {
         field.hidden$ = new BehaviorSubject<boolean>(false);
       }
@@ -272,11 +255,11 @@ describe('IxDynamicFormItemComponent', () => {
         },
       });
       expect(spectator.query('ix-explorer')).toBeVisible();
-      expect(spectator.query(IxExplorerComponent).required).toBe(explorerSchema.required);
-      expect(spectator.query(IxExplorerComponent).tooltip).toBe(explorerSchema.tooltip);
+      expect(spectator.query(IxExplorerComponent).required()).toBe(explorerSchema.required);
+      expect(spectator.query(IxExplorerComponent).tooltip()).toBe(explorerSchema.tooltip);
 
       expect(spectator.query('ix-explorer')).not.toBeHidden();
-      const field = spectator.component.dynamicForm.controls.explorer as CustomUntypedFormField;
+      const field = spectator.component.dynamicForm().controls.explorer as CustomUntypedFormField;
       if (!field.hidden$) {
         field.hidden$ = new BehaviorSubject<boolean>(false);
       }
@@ -295,11 +278,11 @@ describe('IxDynamicFormItemComponent', () => {
       expect(spectator.query('ix-list')).toBeVisible();
       expect(spectator.queryAll('ix-list-item')).toHaveLength(1);
       expect(spectator.queryAll('ix-dynamic-form-item')).toHaveLength(listSchema.items.length);
-      expect(spectator.query(IxListComponent).empty).toBe(false);
-      expect(spectator.query(IxListComponent).label).toBe(listSchema.title);
+      expect(spectator.query(IxListComponent).empty()).toBe(false);
+      expect(spectator.query(IxListComponent).label()).toBe(listSchema.title);
 
       expect(spectator.query('ix-list')).not.toBeHidden();
-      const field = spectator.component.dynamicForm.controls.list as CustomUntypedFormField;
+      const field = spectator.component.dynamicForm().controls.list as CustomUntypedFormField;
       if (!field.hidden$) {
         field.hidden$ = new BehaviorSubject<boolean>(false);
       }
@@ -322,7 +305,7 @@ describe('IxDynamicFormItemComponent', () => {
         expect(item).not.toBeHidden();
       });
 
-      const field = spectator.component.dynamicForm.controls.dict as CustomUntypedFormField;
+      const field = spectator.component.dynamicForm().controls.dict as CustomUntypedFormField;
       if (!field.hidden$) {
         field.hidden$ = new BehaviorSubject<boolean>(false);
       }
@@ -344,7 +327,7 @@ describe('IxDynamicFormItemComponent', () => {
         },
       });
 
-      const field = spectator.component.dynamicForm.controls.list as CustomUntypedFormField;
+      const field = spectator.component.dynamicForm().controls.list as CustomUntypedFormField;
       if (!field.hidden$) {
         field.hidden$ = new BehaviorSubject<boolean>(false);
       }
@@ -368,7 +351,7 @@ describe('IxDynamicFormItemComponent', () => {
         },
       });
 
-      const field = spectator.component.dynamicForm.controls.list as CustomUntypedFormField;
+      const field = spectator.component.dynamicForm().controls.list as CustomUntypedFormField;
       if (!field.hidden$) {
         field.hidden$ = new BehaviorSubject<boolean>(false);
       }
diff --git a/src/app/modules/global-search/components/global-search-trigger/global-search-trigger.component.spec.ts b/src/app/modules/global-search/components/global-search-trigger/global-search-trigger.component.spec.ts
index 9ed18f19cc9..a9a479659be 100644
--- a/src/app/modules/global-search/components/global-search-trigger/global-search-trigger.component.spec.ts
+++ b/src/app/modules/global-search/components/global-search-trigger/global-search-trigger.component.spec.ts
@@ -21,7 +21,6 @@ describe('GlobalSearchTriggerComponent', () => {
     imports: [
       MockComponent(KeyboardShortcutComponent),
       MockComponent(GlobalSearchComponent),
-      MatInput,
     ],
     providers: [
       mockProvider(UiSearchProvider, {
@@ -43,7 +42,7 @@ describe('GlobalSearchTriggerComponent', () => {
   });
 
   it('renders and input prompting for search', () => {
-    const input = spectator.query('input');
+    const input = spectator.query(MatInput);
     expect(input).toExist();
     expect(input).toHaveAttribute('placeholder', 'Search UI');
   });
diff --git a/src/app/modules/global-search/components/global-search/global-search.component.ts b/src/app/modules/global-search/components/global-search/global-search.component.ts
index 1e1d37f3a15..f827e373bd3 100644
--- a/src/app/modules/global-search/components/global-search/global-search.component.ts
+++ b/src/app/modules/global-search/components/global-search/global-search.component.ts
@@ -1,11 +1,10 @@
 import { CdkTrapFocus } from '@angular/cdk/a11y';
 import { DOCUMENT } from '@angular/common';
 import {
-  Component, ChangeDetectionStrategy, OnInit, ElementRef, ChangeDetectorRef,
+  Component, ChangeDetectionStrategy, OnInit, ViewChild, ElementRef, ChangeDetectorRef,
   Inject,
   AfterViewInit,
   OnDestroy,
-  viewChild,
 } from '@angular/core';
 import { FormControl, ReactiveFormsModule } from '@angular/forms';
 import { MatInput } from '@angular/material/input';
@@ -54,8 +53,8 @@ import { waitForSystemInfo } from 'app/store/system-info/system-info.selectors';
   ],
 })
 export class GlobalSearchComponent implements OnInit, AfterViewInit, OnDestroy {
-  readonly searchInput = viewChild<ElementRef<HTMLInputElement>>('searchInput');
-  readonly searchBoxWrapper = viewChild<ElementRef<HTMLElement>>('searchBoxWrapper');
+  @ViewChild('searchInput') searchInput: ElementRef<HTMLInputElement>;
+  @ViewChild('searchBoxWrapper') searchBoxWrapper: ElementRef<HTMLElement>;
 
   searchControl = new FormControl<string>('');
   searchResults: UiSearchableElement[];
@@ -64,7 +63,7 @@ export class GlobalSearchComponent implements OnInit, AfterViewInit, OnDestroy {
   detachOverlay: () => void; // passed from global-search-trigger
 
   get isSearchInputFocused(): boolean {
-    return document.activeElement === this.searchInput()?.nativeElement;
+    return document.activeElement === this.searchInput?.nativeElement;
   }
 
   constructor(
@@ -89,11 +88,11 @@ export class GlobalSearchComponent implements OnInit, AfterViewInit, OnDestroy {
   }
 
   ngAfterViewInit(): void {
-    this.searchBoxWrapper().nativeElement.addEventListener('focusout', this.handleFocusOut.bind(this));
+    this.searchBoxWrapper.nativeElement.addEventListener('focusout', this.handleFocusOut.bind(this));
   }
 
   ngOnDestroy(): void {
-    this.searchBoxWrapper().nativeElement.removeEventListener('focusout', this.handleFocusOut.bind(this));
+    this.searchBoxWrapper.nativeElement.removeEventListener('focusout', this.handleFocusOut.bind(this));
   }
 
   handleKeyDown(event: KeyboardEvent): void {
@@ -178,7 +177,7 @@ export class GlobalSearchComponent implements OnInit, AfterViewInit, OnDestroy {
   }
 
   private focusInputElement(): void {
-    this.searchInput().nativeElement?.focus();
+    this.searchInput.nativeElement?.focus();
   }
 
   private getSystemVersion(): void {
@@ -211,7 +210,7 @@ export class GlobalSearchComponent implements OnInit, AfterViewInit, OnDestroy {
   }
 
   private handleTabOutFromGlobalSearch(event: KeyboardEvent): void {
-    const focusableElements = this.focusService.getFocusableElements(this.searchBoxWrapper().nativeElement);
+    const focusableElements = this.focusService.getFocusableElements(this.searchBoxWrapper.nativeElement);
     const firstElement = focusableElements[0];
     const lastElement = focusableElements[focusableElements.length - 1];
 
@@ -230,7 +229,7 @@ export class GlobalSearchComponent implements OnInit, AfterViewInit, OnDestroy {
 
   private handleFocusOut(event: FocusEvent): void {
     const relatedTarget = event.relatedTarget as HTMLElement;
-    if (relatedTarget && !this.searchBoxWrapper().nativeElement.contains(relatedTarget)) {
+    if (relatedTarget && !this.searchBoxWrapper.nativeElement.contains(relatedTarget)) {
       this.detachOverlay();
     }
   }
diff --git a/src/app/modules/scheduler/components/scheduler-modal/scheduler-preview-column/scheduler-preview-column.component.ts b/src/app/modules/scheduler/components/scheduler-modal/scheduler-preview-column/scheduler-preview-column.component.ts
index 80688895f6d..32b9ff5026a 100644
--- a/src/app/modules/scheduler/components/scheduler-modal/scheduler-preview-column/scheduler-preview-column.component.ts
+++ b/src/app/modules/scheduler/components/scheduler-modal/scheduler-preview-column/scheduler-preview-column.component.ts
@@ -4,7 +4,7 @@ import {
   input,
   OnChanges,
   OnInit,
-  viewChild,
+  ViewChild,
 } from '@angular/core';
 import { MatIconButton } from '@angular/material/button';
 import { MatCalendar, MatCalendarCellClassFunction } from '@angular/material/datepicker';
@@ -54,19 +54,18 @@ export class SchedulerPreviewColumnComponent implements OnChanges, OnInit {
 
   cronPreview: CronSchedulePreview;
 
-  readonly calendar = viewChild<MatCalendar<Date>>('calendar');
+  @ViewChild('calendar', { static: true }) calendar: MatCalendar<Date>;
 
   get startDate(): Date {
-    const calendar = this.calendar();
-    if (!calendar.activeDate || differenceInCalendarMonths(calendar.activeDate, new Date()) < 1) {
+    if (!this.calendar.activeDate || differenceInCalendarMonths(this.calendar.activeDate, new Date()) < 1) {
       return utcToZonedTime(new Date(), this.timezone());
     }
 
-    return startOfMonth(calendar.activeDate);
+    return startOfMonth(this.calendar.activeDate);
   }
 
   get isPastMonth(): boolean {
-    return isBefore(this.calendar().activeDate, startOfMonth(new Date()));
+    return isBefore(this.calendar.activeDate, startOfMonth(new Date()));
   }
 
   ngOnChanges(): void {
@@ -75,7 +74,7 @@ export class SchedulerPreviewColumnComponent implements OnChanges, OnInit {
   }
 
   ngOnInit(): void {
-    this.calendar().stateChanges
+    this.calendar.stateChanges
       .pipe(untilDestroyed(this))
       .subscribe(() => this.onCalendarUpdated());
   }
@@ -114,11 +113,10 @@ export class SchedulerPreviewColumnComponent implements OnChanges, OnInit {
   }
 
   private refreshCalendar(): void {
-    const calendar = this.calendar();
-    if (!calendar.monthView) {
+    if (!this.calendar.monthView) {
       return;
     }
 
-    calendar.updateTodaysDate();
+    this.calendar.updateTodaysDate();
   }
 }
diff --git a/src/app/pages/apps/components/available-apps/custom-app-button/custom-app-button.component.spec.ts b/src/app/pages/apps/components/available-apps/custom-app-button/custom-app-button.component.spec.ts
index 86b80ffcad6..8420ad34dae 100644
--- a/src/app/pages/apps/components/available-apps/custom-app-button/custom-app-button.component.spec.ts
+++ b/src/app/pages/apps/components/available-apps/custom-app-button/custom-app-button.component.spec.ts
@@ -5,6 +5,7 @@ import { MatMenuHarness } from '@angular/material/menu/testing';
 import { Router } from '@angular/router';
 import { SpectatorRouting } from '@ngneat/spectator';
 import { createRoutingFactory, mockProvider } from '@ngneat/spectator/jest';
+import { LazyLoadImageDirective } from 'ng-lazyload-image';
 import { MockComponent } from 'ng-mocks';
 import { of, Subject } from 'rxjs';
 import { customAppTrain, customApp } from 'app/constants/catalog.constants';
@@ -22,8 +23,10 @@ describe('CustomAppButtonComponent', () => {
 
   const createComponent = createRoutingFactory({
     component: CustomAppButtonComponent,
-    imports: [],
-    declarations: [MockComponent(AppCardComponent)],
+    imports: [
+      LazyLoadImageDirective,
+      MockComponent(AppCardComponent),
+    ],
     providers: [
       mockAuth(),
       mockProvider(DockerStore, {
diff --git a/src/app/pages/audit/audit.component.spec.ts b/src/app/pages/audit/audit.component.spec.ts
index e5091574eb7..5b9c5672f2e 100644
--- a/src/app/pages/audit/audit.component.spec.ts
+++ b/src/app/pages/audit/audit.component.spec.ts
@@ -120,7 +120,7 @@ describe('AuditComponent', () => {
   it('loads and shows a table with audit entries', async () => {
     expect(api.call).toHaveBeenCalledWith(
       'audit.query',
-      [{ 'query-filters': [], 'query-options': { limit: 50, offset: 0, order_by: ['-message_timestamp'] } }],
+      [{ 'query-filters': [], 'query-options': { limit: 50, offset: 0, order_by: ['-message_timestamp'] }, remote_controller: false }],
     );
 
     await spectator.fixture.whenStable();
diff --git a/src/app/pages/dashboard/components/widget-group-form/widget-group-slot-form/widget-group-slot-form.component.ts b/src/app/pages/dashboard/components/widget-group-form/widget-group-slot-form/widget-group-slot-form.component.ts
index 00572362b83..3ec3040ca7c 100644
--- a/src/app/pages/dashboard/components/widget-group-form/widget-group-slot-form/widget-group-slot-form.component.ts
+++ b/src/app/pages/dashboard/components/widget-group-form/widget-group-slot-form/widget-group-slot-form.component.ts
@@ -9,6 +9,7 @@ import {
   OnInit,
   Signal,
   Type,
+  ViewChild,
   ViewContainerRef,
   WritableSignal,
   computed,
@@ -17,7 +18,6 @@ import {
   output,
   runInInjectionContext,
   signal,
-  viewChild,
 } from '@angular/core';
 import { toSignal } from '@angular/core/rxjs-interop';
 import {
@@ -69,7 +69,7 @@ export class WidgetGroupSlotFormComponent implements OnInit, AfterViewInit, OnCh
       : false;
   }
 
-  readonly settingsContainer = viewChild('settingsContainer', { read: ViewContainerRef });
+  @ViewChild('settingsContainer', { static: true, read: ViewContainerRef }) settingsContainer: ViewContainerRef;
   widgetCategoriesOptions = computed<Observable<Option[]>>(() => {
     const layoutSupportedWidgets = this.getLayoutSupportedWidgets();
     const uniqCategories = new Set(layoutSupportedWidgets.map((widget) => widget.category));
@@ -222,7 +222,7 @@ export class WidgetGroupSlotFormComponent implements OnInit, AfterViewInit, OnCh
   clearUpdates(): void {
     this.categorySubscription?.unsubscribe();
     this.typeSubscription?.unsubscribe();
-    this.settingsContainer()?.clear();
+    this.settingsContainer?.clear();
   }
 
   private setLayoutSupportedWidgets(): void {
@@ -257,12 +257,11 @@ export class WidgetGroupSlotFormComponent implements OnInit, AfterViewInit, OnCh
   }
 
   private refreshSettingsContainer(): void {
-    const settingsContainer = this.settingsContainer();
-    if (!settingsContainer) {
+    if (!this.settingsContainer) {
       return;
     }
-    settingsContainer.remove();
-    settingsContainer.clear();
+    this.settingsContainer.remove();
+    this.settingsContainer.clear();
     const slotConfig = this.slot();
     if (slotConfig) {
       this.validityChange.emit([slotConfig.slotPosition, {} as ValidationErrors]);
@@ -275,7 +274,7 @@ export class WidgetGroupSlotFormComponent implements OnInit, AfterViewInit, OnCh
       return;
     }
 
-    settingsContainer.createComponent(settingsComponent, { injector: this.getInjector() });
+    this.settingsContainer.createComponent(settingsComponent, { injector: this.getInjector() });
   }
 
   getInjector(): Injector {
diff --git a/src/app/pages/dashboard/widgets/network/common/network-chart/network-chart.component.spec.ts b/src/app/pages/dashboard/widgets/network/common/network-chart/network-chart.component.spec.ts
index a9c810a71ae..d00e15adec1 100644
--- a/src/app/pages/dashboard/widgets/network/common/network-chart/network-chart.component.spec.ts
+++ b/src/app/pages/dashboard/widgets/network/common/network-chart/network-chart.component.spec.ts
@@ -1,6 +1,6 @@
+import { Component, ChangeDetectionStrategy, input } from '@angular/core';
 import { Spectator } from '@ngneat/spectator';
 import { createComponentFactory, mockProvider } from '@ngneat/spectator/jest';
-import { MockComponent } from 'ng-mocks';
 import { ViewChartAreaComponent } from 'app/modules/charts/view-chart-area/view-chart-area.component';
 import { NetworkChartComponent } from 'app/pages/dashboard/widgets/network/common/network-chart/network-chart.component';
 import { LocaleService } from 'app/services/locale.service';
@@ -8,12 +8,28 @@ import { LocaleService } from 'app/services/locale.service';
 // TODO: Update when fix is ready
 // See https://github.com/help-me-mom/ng-mocks/issues/8634
 
+@Component({
+  selector: 'ix-view-chart-area-mock',
+  changeDetection: ChangeDetectionStrategy.OnPush,
+  standalone: true,
+})
+class ViewChartAreaMockComponent {
+  data = input();
+  options = input();
+}
+
 describe('NetworkChartComponent', () => {
   let spectator: Spectator<NetworkChartComponent>;
   const createComponent = createComponentFactory({
     component: NetworkChartComponent,
-    declarations: [
-      MockComponent(ViewChartAreaComponent),
+    overrideComponents: [
+      [NetworkChartComponent, {
+        add: {
+          imports: [ViewChartAreaMockComponent],
+          template: '<ix-view-chart-area-mock [data]="data()" [options]="options()"></ix-view-chart-area-mock>',
+        },
+        remove: { imports: [ViewChartAreaComponent] },
+      }],
     ],
     providers: [
       mockProvider(LocaleService, {
@@ -32,10 +48,10 @@ describe('NetworkChartComponent', () => {
     spectator.setInput('data', { datasets: [], labels: [] });
     spectator.detectChanges();
 
-    const chart = spectator.query(ViewChartAreaComponent);
+    const chart = spectator.query(ViewChartAreaMockComponent);
     expect(chart).toBeTruthy();
 
-    const data = chart.data;
+    const data = chart.data();
     expect(data).toMatchObject({
       datasets: [],
       labels: [],
diff --git a/src/app/pages/dashboard/widgets/network/widget-interface/widget-interface.component.spec.ts b/src/app/pages/dashboard/widgets/network/widget-interface/widget-interface.component.spec.ts
index 78a0d668310..b147673c496 100644
--- a/src/app/pages/dashboard/widgets/network/widget-interface/widget-interface.component.spec.ts
+++ b/src/app/pages/dashboard/widgets/network/widget-interface/widget-interface.component.spec.ts
@@ -91,6 +91,7 @@ describe('WidgetInterfaceComponent', () => {
   });
 
   describe('Full Size', () => {
+    global.Date.now = jest.fn(() => (new Date('2024-07-23')).getTime()); // 1721689140000
     beforeEach(() => {
       spectator = createComponent({
         props: {
@@ -123,8 +124,7 @@ describe('WidgetInterfaceComponent', () => {
       expect(spectator.query('.info-list-item.out')).toHaveText('Out:32.77 kb/s');
     }));
 
-    it('shows a chart with network traffic', fakeAsync(() => {
-      spectator.tick(1);
+    it('shows a chart with network traffic', () => {
       startDate = Date.now() - oneHourMillis - oneMinuteMillis;
       const chart = spectator.query(NetworkChartComponent);
       expect(chart).not.toBeNull();
@@ -162,7 +162,7 @@ describe('WidgetInterfaceComponent', () => {
           },
         ],
       });
-    }));
+    });
 
     it('checks first entry selection when settings are null', () => {
       spectator.setInput('settings', null);
@@ -177,6 +177,7 @@ describe('WidgetInterfaceComponent', () => {
 
   describe('Half Size', () => {
     beforeEach(() => {
+      global.Date.now = jest.fn(() => (new Date('2024-07-23')).getTime()); // 1721689140000
       spectator = createComponent({
         props: {
           size: SlotSize.Half,
@@ -207,8 +208,7 @@ describe('WidgetInterfaceComponent', () => {
       expect(spectator.query('.info-list-item.out')).toHaveText('Out:32.77 kb/s');
     }));
 
-    it('shows a chart with network traffic', fakeAsync(() => {
-      spectator.tick(1);
+    it('shows a chart with network traffic', () => {
       startDate = Date.now() - oneHourMillis - oneMinuteMillis;
       const chart = spectator.query(NetworkChartComponent);
       expect(chart).not.toBeNull();
@@ -246,7 +246,7 @@ describe('WidgetInterfaceComponent', () => {
           },
         ],
       });
-    }));
+    });
   });
 
   describe('Quarter Size', () => {
diff --git a/src/app/pages/storage/modules/pool-manager/components/manual-disk-selection/components/manual-selection-disks/manual-selection-disks.component.spec.ts b/src/app/pages/storage/modules/pool-manager/components/manual-disk-selection/components/manual-selection-disks/manual-selection-disks.component.spec.ts
index f8c33cc9221..cc3d6191846 100644
--- a/src/app/pages/storage/modules/pool-manager/components/manual-disk-selection/components/manual-selection-disks/manual-selection-disks.component.spec.ts
+++ b/src/app/pages/storage/modules/pool-manager/components/manual-disk-selection/components/manual-selection-disks/manual-selection-disks.component.spec.ts
@@ -67,6 +67,7 @@ describe('ManualSelectionDisksComponent', () => {
   });
 
   beforeEach(() => {
+    jest.spyOn(console, 'warn').mockImplementation();
     spectator = createComponent({
       props: {
         enclosures: [
diff --git a/src/app/pages/system/bootenv/bootenv-status/bootenv-status.component.spec.ts b/src/app/pages/system/bootenv/bootenv-status/bootenv-status.component.spec.ts
index 5ffcff705b1..fb3e69b3cdd 100644
--- a/src/app/pages/system/bootenv/bootenv-status/bootenv-status.component.spec.ts
+++ b/src/app/pages/system/bootenv/bootenv-status/bootenv-status.component.spec.ts
@@ -83,6 +83,7 @@ describe('BootStatusListComponent', () => {
   });
 
   beforeEach(() => {
+    jest.spyOn(console, 'warn').mockImplementation();
     spectator = createComponent();
     loader = TestbedHarnessEnvironment.loader(spectator.fixture);
     api = spectator.inject(MockApiService);
diff --git a/src/app/pages/two-factor-auth/qr-viewer/qr-viewer.component.spec.ts b/src/app/pages/two-factor-auth/qr-viewer/qr-viewer.component.spec.ts
index d82d3ea0885..428c3cf4f87 100644
--- a/src/app/pages/two-factor-auth/qr-viewer/qr-viewer.component.spec.ts
+++ b/src/app/pages/two-factor-auth/qr-viewer/qr-viewer.component.spec.ts
@@ -1,6 +1,7 @@
+import 'jest-canvas-mock';
 import { Spectator, createComponentFactory } from '@ngneat/spectator/jest';
 import { MockComponent, MockModule } from 'ng-mocks';
-import { QrCodeComponent, QrCodeModule } from 'ng-qrcode';
+import { QrCodeComponent, QrCodeDirective, QrCodeModule } from 'ng-qrcode';
 import { helptext2fa } from 'app/helptext/system/2fa';
 import { WarningComponent } from 'app/modules/forms/ix-forms/components/warning/warning.component';
 import { QrViewerComponent } from 'app/pages/two-factor-auth/qr-viewer/qr-viewer.component';
@@ -10,10 +11,10 @@ describe('QrViewerComponent', () => {
 
   const createComponent = createComponentFactory({
     component: QrViewerComponent,
-    declarations: [
-      MockComponent(WarningComponent),
-    ],
     imports: [
+      MockComponent(WarningComponent),
+      QrCodeComponent,
+      QrCodeDirective,
       MockModule(QrCodeModule),
     ],
   });
diff --git a/src/app/pages/two-factor-auth/two-factor.component.spec.ts b/src/app/pages/two-factor-auth/two-factor.component.spec.ts
index 281e4e42353..89b29a921be 100644
--- a/src/app/pages/two-factor-auth/two-factor.component.spec.ts
+++ b/src/app/pages/two-factor-auth/two-factor.component.spec.ts
@@ -3,6 +3,7 @@ import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
 import { MatButtonHarness } from '@angular/material/button/testing';
 import { Spectator, createComponentFactory, mockProvider } from '@ngneat/spectator/jest';
 import { MockComponent } from 'ng-mocks';
+import { QrCodeComponent, QrCodeDirective } from 'ng-qrcode';
 import { of } from 'rxjs';
 import { mockCall, mockApi } from 'app/core/testing/utils/mock-api.utils';
 import { helptext2fa } from 'app/helptext/system/2fa';
@@ -23,7 +24,9 @@ describe('TwoFactorComponent', () => {
 
   const createComponent = createComponentFactory({
     component: TwoFactorComponent,
-    declarations: [
+    imports: [
+      QrCodeComponent,
+      QrCodeDirective,
       MockComponent(WarningComponent),
       MockComponent(QrViewerComponent),
       MockComponent(CopyButtonComponent),