feat: mount EnvBanner app-wide and expose --env-banner-height
Renders the banner once at the app root so every route picks it up, and exposes its height as a CSS custom property so fixed-viewport layouts (Shell, public pages) can compensate for it.
This commit is contained in:
@@ -1 +1,2 @@
|
|||||||
|
<app-env-banner />
|
||||||
<router-outlet />
|
<router-outlet />
|
||||||
|
|||||||
@@ -1,76 +1,44 @@
|
|||||||
import { signal } from '@angular/core';
|
|
||||||
import { TestBed } from '@angular/core/testing';
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
import { provideHttpClient } from '@angular/common/http';
|
||||||
|
import { provideHttpClientTesting } from '@angular/common/http/testing';
|
||||||
import { provideRouter } from '@angular/router';
|
import { provideRouter } from '@angular/router';
|
||||||
import { of } from 'rxjs';
|
|
||||||
import { App } from './app';
|
import { App } from './app';
|
||||||
import { AuthApi } from './core/auth/auth-api';
|
import { environment } from '../environments/environment';
|
||||||
import { AuthStore } from './core/auth/auth-store';
|
|
||||||
|
|
||||||
describe('App', () => {
|
describe('App', () => {
|
||||||
const token = signal<string | null>(null);
|
const originalProduction = environment.production;
|
||||||
const updateUser = vi.fn();
|
|
||||||
const meResponse = signal<Record<string, unknown>>({
|
|
||||||
id: 1,
|
|
||||||
email: 'a@b.de',
|
|
||||||
firstName: 'Alex',
|
|
||||||
lastName: 'Muster',
|
|
||||||
});
|
|
||||||
const me = vi.fn(() => of(meResponse()));
|
|
||||||
const setSession = vi.fn();
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(() => {
|
||||||
token.set(null);
|
localStorage.clear();
|
||||||
meResponse.set({ id: 1, email: 'a@b.de', firstName: 'Alex', lastName: 'Muster' });
|
});
|
||||||
updateUser.mockClear();
|
|
||||||
setSession.mockClear();
|
afterEach(() => {
|
||||||
me.mockClear();
|
environment.production = originalProduction;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets --env-banner-height to 0px and renders no banner in production', async () => {
|
||||||
|
environment.production = true;
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [App],
|
imports: [App],
|
||||||
providers: [
|
providers: [provideHttpClient(), provideHttpClientTesting(), provideRouter([])],
|
||||||
provideRouter([]),
|
|
||||||
{ provide: AuthStore, useValue: { token, updateUser, setSession } },
|
|
||||||
{ provide: AuthApi, useValue: { me } },
|
|
||||||
],
|
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
});
|
|
||||||
|
|
||||||
it('should create the app', () => {
|
|
||||||
const fixture = TestBed.createComponent(App);
|
const fixture = TestBed.createComponent(App);
|
||||||
expect(fixture.componentInstance).toBeTruthy();
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(fixture.nativeElement.style.getPropertyValue('--env-banner-height')).toBe('0px');
|
||||||
|
expect(fixture.nativeElement.querySelector('.env-banner')).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('validates and refreshes a restored session on startup', () => {
|
it('sets --env-banner-height to 28px and renders the banner outside production', async () => {
|
||||||
token.set('stored-token');
|
environment.production = false;
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [App],
|
||||||
|
providers: [provideHttpClient(), provideHttpClientTesting(), provideRouter([])],
|
||||||
|
}).compileComponents();
|
||||||
|
const fixture = TestBed.createComponent(App);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
TestBed.createComponent(App);
|
expect(fixture.nativeElement.style.getPropertyValue('--env-banner-height')).toBe('28px');
|
||||||
|
expect(fixture.nativeElement.querySelector('.env-banner')).not.toBeNull();
|
||||||
expect(me).toHaveBeenCalled();
|
|
||||||
expect(updateUser).toHaveBeenCalledWith({
|
|
||||||
id: 1,
|
|
||||||
email: 'a@b.de',
|
|
||||||
firstName: 'Alex',
|
|
||||||
lastName: 'Muster',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('replaces an expired stored token when me returns a refreshed token', () => {
|
|
||||||
token.set('expired-token');
|
|
||||||
meResponse.set({
|
|
||||||
id: 1,
|
|
||||||
email: 'a@b.de',
|
|
||||||
firstName: 'Alex',
|
|
||||||
lastName: 'Muster',
|
|
||||||
token: 'refreshed-token',
|
|
||||||
});
|
|
||||||
|
|
||||||
TestBed.createComponent(App);
|
|
||||||
|
|
||||||
expect(setSession).toHaveBeenCalledWith('refreshed-token', {
|
|
||||||
id: 1,
|
|
||||||
email: 'a@b.de',
|
|
||||||
firstName: 'Alex',
|
|
||||||
lastName: 'Muster',
|
|
||||||
});
|
|
||||||
expect(updateUser).not.toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,16 +2,22 @@ import { Component, inject } from '@angular/core';
|
|||||||
import { RouterOutlet } from '@angular/router';
|
import { RouterOutlet } from '@angular/router';
|
||||||
import { AuthApi } from './core/auth/auth-api';
|
import { AuthApi } from './core/auth/auth-api';
|
||||||
import { AuthStore } from './core/auth/auth-store';
|
import { AuthStore } from './core/auth/auth-store';
|
||||||
|
import { environment } from '../environments/environment';
|
||||||
|
import { ENV_BANNER_HEIGHT_PX, EnvBanner } from './shared/env-banner/env-banner';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
imports: [RouterOutlet],
|
imports: [RouterOutlet, EnvBanner],
|
||||||
templateUrl: './app.html',
|
templateUrl: './app.html',
|
||||||
styleUrl: './app.scss',
|
styleUrl: './app.scss',
|
||||||
|
host: {
|
||||||
|
'[style.--env-banner-height]': 'bannerHeight',
|
||||||
|
},
|
||||||
})
|
})
|
||||||
export class App {
|
export class App {
|
||||||
private readonly authApi = inject(AuthApi);
|
private readonly authApi = inject(AuthApi);
|
||||||
private readonly authStore = inject(AuthStore);
|
private readonly authStore = inject(AuthStore);
|
||||||
|
protected readonly bannerHeight = `${environment.production ? 0 : ENV_BANNER_HEIGHT_PX}px`;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
if (this.authStore.token()) {
|
if (this.authStore.token()) {
|
||||||
|
|||||||
Reference in New Issue
Block a user