Add Portal Settings Web Part with styles, localization, and tests

- Implemented the PortalSettings Web Part with a new manifest and TypeScript file.
- Created SCSS styles for the Web Part, defining various UI elements and responsive design.
- Added localization support for English and German languages.
- Developed tests for Provider Registry and static assets validation.
- Included PowerShell script for project validation.
This commit is contained in:
Torsten Brendgen
2026-07-21 22:43:35 +02:00
parent 62fef65c12
commit 10f7463094
49 changed files with 2411 additions and 462 deletions

View File

@@ -0,0 +1,55 @@
import { cloneValue, getPathValue, mergePreservingUnknown, setPathValue } from '../src/core/ObjectPath';
import { ISettingsProvider } from '../src/core/ProviderContracts';
import { getSettingsProvider, getSettingsProviders } from '../src/providers/ProviderRegistry';
let passed: number = 0;
function assert(condition: boolean, message: string): void {
if (!condition) { throw new Error(message); }
passed++;
}
const providers: Array<ISettingsProvider<any>> = getSettingsProviders(); // tslint:disable-line:no-any
assert(providers.length === 3, 'Genau drei Provider werden erwartet.');
assert(!!getSettingsProvider('megamenu'), 'MegaMenu-Provider fehlt.');
assert(!!getSettingsProvider('custombranding'), 'CustomBranding-Provider fehlt.');
assert(!!getSettingsProvider('expiryindicator'), 'ExpiryIndicator-Provider fehlt.');
providers.forEach((provider: ISettingsProvider<any>): void => { // tslint:disable-line:no-any
assert(provider.sections.length > 0, provider.key + ' hat keine Sections.');
assert(provider.sections.every(section => section.elements.length > 0), provider.key + ' enthält eine leere Section.');
assert(provider.validate(provider.createDefault()) instanceof Array, provider.key + ' liefert keine Validierungsliste.');
});
const mega: ISettingsProvider<any> = getSettingsProvider('megamenu'); // tslint:disable-line:no-any
const normalizedMega: any = mega.normalize({ cacheMinutes: 5000, cacheVersion: '', unknownFuture: 'keep', menuMode: 'other' }); // tslint:disable-line:no-any
assert(normalizedMega.cacheMinutes === 1440, 'MegaMenu Cache-Maximum greift nicht.');
assert(normalizedMega.cacheVersion === '1', 'MegaMenu Cache-Fallback fehlt.');
assert(normalizedMega.menuMode === 'megaMenu', 'MegaMenu Modus-Fallback fehlt.');
assert(normalizedMega.unknownFuture === 'keep', 'Unbekannte MegaMenu-Properties gehen verloren.');
const branding: ISettingsProvider<any> = getSettingsProvider('custombranding'); // tslint:disable-line:no-any
const normalizedBranding: any = branding.normalize({ elements: [{ type: 'span' }], future: 7 }); // tslint:disable-line:no-any
assert(normalizedBranding.placeholdertop.elements.length === 1, 'Legacy-Branding-Elemente werden nicht übernommen.');
assert(normalizedBranding.future === 7, 'Unbekannte Branding-Properties gehen verloren.');
assert(normalizedBranding.elements === undefined, 'Legacy-Root-Elemente werden nicht bereinigt.');
const unsafeBranding: any = branding.createDefault(); // tslint:disable-line:no-any
unsafeBranding.placeholdertop.elements = [{ type: 'script', content: 'alert(1)' }, { type: 'a', attributes: { onclick: 'alert(1)', href: 'javascript:alert(1)' } }];
assert(branding.validate(unsafeBranding).length >= 3, 'Unsichere Branding-Elemente werden nicht erkannt.');
const expiry: ISettingsProvider<any> = getSettingsProvider('expiryindicator'); // tslint:disable-line:no-any
const normalizedExpiry: any = expiry.normalize({ baseField: 'Invalid field', future: true }); // tslint:disable-line:no-any
assert(normalizedExpiry.baseField === 'Created', 'Expiry-Feldname wird nicht abgesichert.');
assert(normalizedExpiry.future === true, 'Unbekannte Expiry-Properties gehen verloren.');
const invalidExpiry: any = expiry.createDefault(); // tslint:disable-line:no-any
invalidExpiry.rules = [{ condition: { operator: 'xor', conditions: [] }, lifeTime: { value: 0, unit: 'hours' }, columnRule: [{}] }];
assert(expiry.validate(invalidExpiry).length >= 3, 'Ungültige Expiry-Regeln werden nicht erkannt.');
const value: any = {}; // tslint:disable-line:no-any
setPathValue(value, 'default.lifeTime.value', 3);
assert(getPathValue(value, 'default.lifeTime.value') === 3, 'Objektpfade funktionieren nicht.');
const merged: any = mergePreservingUnknown({ known: 1, future: { keep: true } }, { known: 2 }); // tslint:disable-line:no-any
assert(merged.known === 2 && merged.future.keep === true, 'Schemaerhaltendes Mergen ist fehlerhaft.');
const cloned: any = cloneValue(merged); cloned.future.keep = false; // tslint:disable-line:no-any
assert(merged.future.keep === true, 'Konfiguration wird nicht tief kopiert.');
console.log('PortalSettings Provider tests passed: ' + passed);