AI Implementation feature(880): Authenticated Shell, Quick Tabs and Change Password 1.04 (#19)

This commit was merged in pull request #19.
This commit is contained in:
2026-07-22 09:37:10 +00:00
parent d134a56abe
commit 90e570c43c
12 changed files with 248 additions and 242 deletions
+55 -35
View File
@@ -1,54 +1,74 @@
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { EventState } from '../../frontend/src/app/core/services/event-status.store';
import {
EventState,
LED_COLOR_BY_STATE,
ledBackgroundColor,
} from '../../frontend/src/app/core/services/event-status.pure';
describe('ShellHeaderComponent LED styling (component-scoped)', () => {
describe('LED state -> background-color mapping', () => {
it.each([
['running', 'var(--color-success)'],
['countdown', 'var(--color-warning)'],
['stopped', 'var(--color-danger)'],
['unconfigured', 'var(--color-secondary)'],
] as const)('maps %s to %s', (state, expected) => {
expect(ledBackgroundColor(state)).toBe(expected);
expect(LED_COLOR_BY_STATE[state]).toBe(expected);
});
it('covers every EventState variant with a non-transparent token', () => {
for (const state of Object.keys(LED_COLOR_BY_STATE) as EventState[]) {
const color = ledBackgroundColor(state);
expect(color).toMatch(/^var\(--color-/);
expect(color).not.toBe('transparent');
expect(color).not.toBe('');
}
});
});
describe('ShellHeaderComponent LED wiring', () => {
const sourcePath = resolve(
__dirname,
'../../frontend/src/app/features/shell/header/shell-header.component.ts',
);
const source = readFileSync(sourcePath, 'utf8');
it('declares component-scoped styles with the LED base shape', () => {
expect(source).toMatch(/styles:\s*\[/);
expect(source).toMatch(/display:\s*inline-block/);
expect(source).toMatch(/width:\s*10px/);
expect(source).toMatch(/height:\s*10px/);
expect(source).toMatch(/border-radius:\s*50%/);
it('does not declare a transparent background on the LED base', () => {
const baseBlock = source.match(/\.led\s*\{[^}]*\}/);
expect(baseBlock).not.toBeNull();
expect(baseBlock?.[0]).not.toMatch(/background-color\s*:\s*transparent/);
});
it.each([
['.led-running', '--color-success'],
['.led-countdown', '--color-warning'],
['.led-stopped', '--color-danger'],
['.led-unconfigured', '--color-secondary'],
] as const)(
'scopes %s to the existing %s token',
(selector, token) => {
const escaped = selector.replace('.', '\\.');
const re = new RegExp(`${escaped}\\s*\\{[^}]*var\\(${token}\\)`);
expect(source).toMatch(re);
},
);
it('keeps the component shape (size, radius, no border, inline-block, vertical-align)', () => {
const baseBlock = source.match(/\.led\s*\{[^}]*\}/);
expect(baseBlock?.[0]).toMatch(/display:\s*inline-block/);
expect(baseBlock?.[0]).toMatch(/width:\s*10px/);
expect(baseBlock?.[0]).toMatch(/height:\s*10px/);
expect(baseBlock?.[0]).toMatch(/border-radius:\s*50%/);
expect(baseBlock?.[0]).toMatch(/border:\s*0/);
expect(baseBlock?.[0]).toMatch(/vertical-align:\s*middle/);
});
it('keeps the existing state class bindings and aria-label in the template', () => {
expect(source).toContain('class="led"');
it('binds the LED background color directly from the state signal', () => {
expect(source).toMatch(/\[style\.background-color\]="ledColor\(\)"/);
expect(source).toMatch(/ledColor\s*=\s*computed\(/);
expect(source).toMatch(/ledBackgroundColor\(this\.eventState\(\)\)/);
});
it('keeps the four state class bindings, testid, and accessible state label', () => {
expect(source).toContain('[class.led-running]="eventState() === \'running\'"');
expect(source).toContain('[class.led-countdown]="eventState() === \'countdown\'"');
expect(source).toContain('[class.led-stopped]="eventState() === \'stopped\'"');
expect(source).toContain('[class.led-unconfigured]="eventState() === \'unconfigured\'"');
expect(source).toContain("data-testid=\"shell-led\"");
expect(source).toContain('data-testid="shell-led"');
expect(source).toContain("[attr.aria-label]=\"'Event status: ' + eventState()\"");
});
});
describe('EventState union covers all four LED classes', () => {
it.each(['running', 'countdown', 'stopped', 'unconfigured'] as EventState[])(
'recognises %s',
(state) => {
const expected = `led led-${state}`;
expect(expected.startsWith('led ')).toBe(true);
expect(expected.endsWith(state)).toBe(true);
},
);
});
it('does not redeclare LED state colors in the component styles', () => {
expect(source).not.toMatch(/\.led-running\s*\{[^}]*background-color/);
expect(source).not.toMatch(/\.led-countdown\s*\{[^}]*background-color/);
expect(source).not.toMatch(/\.led-stopped\s*\{[^}]*background-color/);
expect(source).not.toMatch(/\.led-unconfigured\s*\{[^}]*background-color/);
});
});