From ad509f3553a7e6328c501001f4d58773e02f68bc Mon Sep 17 00:00:00 2001 From: Adam Shire Date: Mon, 20 Jul 2026 14:15:28 -0400 Subject: [PATCH 1/2] Add mit-alert-banner component for NDE banner display Why these changes are being introduced: We wanted to add a customizeable alert banner to NDE. How this addresses that need: Created a new standalone Angular component (mit-alert-banner) that: * Fetches translated banner text from the 'mit.alertBanner' label via TranslateService * Displays HTML markup via [innerHTML] binding * Conditionally renders above the Header using @if, hiding when label empty string (by setting mit.alertBanner to NOT_DEFINED) * Reactively updates when language changes via onLangChange observable * Includes comprehensive unit tests with detailed comments for maintainability Side effects of this change: * Requires 'mit.alertBanner' label to be configured in Alma's Custom Defined Labels table * Component is hidden when label is set to NOT_DEFINED or returns empty string Relevant ticket(s): * https://mitlibraries.atlassian.net/browse/NDE-162 --- .../custom1-module/customComponentMappings.ts | 3 + .../mit-alert-banner.component.html | 3 + .../mit-alert-banner.component.scss | 0 .../mit-alert-banner.component.spec.ts | 90 +++++++++++++++++++ .../mit-alert-banner.component.ts | 27 ++++++ 5 files changed, 123 insertions(+) create mode 100644 src/app/mit-alert-banner/mit-alert-banner.component.html create mode 100644 src/app/mit-alert-banner/mit-alert-banner.component.scss create mode 100644 src/app/mit-alert-banner/mit-alert-banner.component.spec.ts create mode 100644 src/app/mit-alert-banner/mit-alert-banner.component.ts diff --git a/src/app/custom1-module/customComponentMappings.ts b/src/app/custom1-module/customComponentMappings.ts index e1aa0a69..c363378f 100644 --- a/src/app/custom1-module/customComponentMappings.ts +++ b/src/app/custom1-module/customComponentMappings.ts @@ -1,9 +1,12 @@ import { PrimoBrandedNameComponent } from '../primo-branded-name/primo-branded-name.component'; import { NoResultsComponent } from '../no-results/no-results.component'; import { MitRequestCardComponent } from '../mit-request-card/mit-request-card.component'; +import { MitAlertBannerComponent } from '../mit-alert-banner/mit-alert-banner.component'; + // Define the map export const selectorComponentMap = new Map([ ['nde-search-no-results', NoResultsComponent], ['nde-logo-after', PrimoBrandedNameComponent], ['nde-request-card-after', MitRequestCardComponent], + ['nde-header-before', MitAlertBannerComponent], ]); diff --git a/src/app/mit-alert-banner/mit-alert-banner.component.html b/src/app/mit-alert-banner/mit-alert-banner.component.html new file mode 100644 index 00000000..1b956d07 --- /dev/null +++ b/src/app/mit-alert-banner/mit-alert-banner.component.html @@ -0,0 +1,3 @@ +@if (bannerText()) { +
+} diff --git a/src/app/mit-alert-banner/mit-alert-banner.component.scss b/src/app/mit-alert-banner/mit-alert-banner.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/mit-alert-banner/mit-alert-banner.component.spec.ts b/src/app/mit-alert-banner/mit-alert-banner.component.spec.ts new file mode 100644 index 00000000..20465051 --- /dev/null +++ b/src/app/mit-alert-banner/mit-alert-banner.component.spec.ts @@ -0,0 +1,90 @@ +import { TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { LangChangeEvent, TranslateService } from '@ngx-translate/core'; +import { of, Subject } from 'rxjs'; +import { MitAlertBannerComponent } from './mit-alert-banner.component'; + +describe('MitAlertBannerComponent', () => { + let langChange: Subject; + let mockTranslate: TranslateService; + + beforeEach(async () => { + langChange = new Subject(); + + await TestBed.configureTestingModule({ + imports: [MitAlertBannerComponent], + providers: [ + { + provide: TranslateService, + useValue: { + instant: (key: string) => key, + currentLang: 'en', + defaultLang: 'en', + onLangChange: langChange, + onTranslationChange: of({}), + onDefaultLangChange: of({}), + }, + }, + ], + }).compileComponents(); + mockTranslate = TestBed.inject(TranslateService); + }); + + it('should create', () => { + const fixture = TestBed.createComponent(MitAlertBannerComponent); + const component = fixture.componentInstance; + expect(component).toBeTruthy(); + }); + + it('should display banner when translation has text', () => { + spyOn(mockTranslate, 'instant').and.returnValue( + '

Alert Banner Content

', + ); + const fixture = TestBed.createComponent(MitAlertBannerComponent); + fixture.detectChanges(); + const div = fixture.debugElement.query(By.css('div')); + expect(div).toBeTruthy(); + expect(div.nativeElement.innerHTML).toBe('

Alert Banner Content

'); + }); + + it('should hide banner when translation is empty', () => { + spyOn(mockTranslate, 'instant').and.returnValue(''); + const fixture = TestBed.createComponent(MitAlertBannerComponent); + fixture.detectChanges(); + const div = fixture.debugElement.query(By.css('div')); + expect(div).toBeFalsy(); + }); + + it('should update banner when language changes', () => { + spyOn(mockTranslate, 'instant').and.returnValue('Initial Banner'); + const fixture = TestBed.createComponent(MitAlertBannerComponent); + fixture.detectChanges(); + (mockTranslate.instant as jasmine.Spy).and.returnValue('Updated Banner'); + langChange.next({ lang: 'fr', translations: {} }); + fixture.detectChanges(); + const div = fixture.debugElement.query(By.css('div')); + expect(div.nativeElement.innerHTML).toBe('Updated Banner'); + }); + + it('should hide banner if new language has empty translation', () => { + spyOn(mockTranslate, 'instant').and.returnValue('Initial Banner'); + const fixture = TestBed.createComponent(MitAlertBannerComponent); + fixture.detectChanges(); + (mockTranslate.instant as jasmine.Spy).and.returnValue(''); + langChange.next({ lang: 'es', translations: {} }); + + fixture.detectChanges(); + const div = fixture.debugElement.query(By.css('div')); + expect(div).toBeFalsy(); + }); + + it('should use default label key', () => { + spyOn(mockTranslate, 'instant').and.returnValue('Default Banner'); + const fixture = TestBed.createComponent(MitAlertBannerComponent); + fixture.detectChanges(); + expect(mockTranslate.instant).toHaveBeenCalledWith('mit.alertBanner'); + const div = fixture.debugElement.query(By.css('div')); + expect(div).toBeTruthy(); + expect(div.nativeElement.innerHTML).toBe('Default Banner'); + }); +}); diff --git a/src/app/mit-alert-banner/mit-alert-banner.component.ts b/src/app/mit-alert-banner/mit-alert-banner.component.ts new file mode 100644 index 00000000..b247b1c5 --- /dev/null +++ b/src/app/mit-alert-banner/mit-alert-banner.component.ts @@ -0,0 +1,27 @@ +import { Component, inject, input } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { map, startWith } from 'rxjs'; + +@Component({ + selector: 'custom-mit-alert-banner', + standalone: true, + templateUrl: './mit-alert-banner.component.html', + styleUrl: './mit-alert-banner.component.scss', +}) +export class MitAlertBannerComponent { + private translate = inject(TranslateService); + + // Can be overridden by passing a different key via template binding + // Default: 'mit.alertBanner' - hide banner by setting to NOT_DEFINED in Alma + labelKey = input('mit.alertBanner'); + + // Compute banner text and only show if it has content + bannerText = toSignal( + this.translate.onLangChange.pipe( + startWith(null), + map(() => this.translate.instant(this.labelKey()).trim()), + ), + { initialValue: '' }, + ); +} From c614574a75a903b484a3c662d9f5ad3849ce6c0b Mon Sep 17 00:00:00 2001 From: djanelle-mit Date: Wed, 22 Jul 2026 12:26:42 -0400 Subject: [PATCH 2/2] Updated markup and styling for alert banner --- .../mit-alert-banner.component.html | 4 ++- .../mit-alert-banner.component.scss | 29 +++++++++++++++++++ src/app/styles/mit-theme.scss | 20 +++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/app/mit-alert-banner/mit-alert-banner.component.html b/src/app/mit-alert-banner/mit-alert-banner.component.html index 1b956d07..85ea9efe 100644 --- a/src/app/mit-alert-banner/mit-alert-banner.component.html +++ b/src/app/mit-alert-banner/mit-alert-banner.component.html @@ -1,3 +1,5 @@ @if (bannerText()) { -
+ } diff --git a/src/app/mit-alert-banner/mit-alert-banner.component.scss b/src/app/mit-alert-banner/mit-alert-banner.component.scss index e69de29b..a80fb6e4 100644 --- a/src/app/mit-alert-banner/mit-alert-banner.component.scss +++ b/src/app/mit-alert-banner/mit-alert-banner.component.scss @@ -0,0 +1,29 @@ +aside { + background-color: #007899; + padding: 20px 0; + + @media only screen and (max-width: 599.98px) { + padding: 20px 16px; + } + + .content-wrapper { + font-size: 18px; + font-weight: 500; + color: #e5f9ff; + margin: 0 auto; + + a:link, a:visited, a:active { + color: #fff !important; + text-decoration: underline !important; + text-decoration-color: #00c8ff !important; + + &:hover { + text-decoration-color: #fff !important; + } + } + + } +} + + + diff --git a/src/app/styles/mit-theme.scss b/src/app/styles/mit-theme.scss index f2956b1a..0d7db470 100644 --- a/src/app/styles/mit-theme.scss +++ b/src/app/styles/mit-theme.scss @@ -884,3 +884,23 @@ mat-accordion[displaymode="flat"] { mat-accordion[displaymode="flat"] > [id="nui.getit.service_viewit"] { order: -1; } + +// TOP ALERT BANNER STYLES +// Overriding styles that we can't get specificity to override in the component styles +#mitl-alert-banner { + .content-wrapper { + @include applyPrimoContainerWidths; + + a:link, a:visited, a:active { + color: #fff; + text-decoration: underline; + text-decoration-color: #00c8ff; + text-underline-offset: 5px; + transition: all .25s ease-in-out 0s; + + &:hover { + text-decoration-color: #fff; + } + } + } +} \ No newline at end of file