feat: Add custom branding functionality with CSS and JSON configuration

- Introduced custom branding CSS styles in `custom-branding.css`.
- Created example JSON configuration for custom branding in `custom-branding.example.json`.
- Implemented branding configuration logic in `BrandingConfig.ts` to normalize and validate branding settings.
- Developed CSS loader to manage loading and unloading of custom stylesheets in `BrandingCssLoader.ts`.
- Added DOM rendering capabilities for branding elements in `BrandingDomRenderer.ts`.
- Defined types and interfaces for branding elements and configurations in `BrandingTypes.ts`.
- Included localization support for German in `de-de.js`.
- Added unit tests for branding configuration, CSS loader, and DOM renderer.
- Validated project structure and static assets with new validation scripts.
This commit is contained in:
Torsten Brendgen
2026-07-20 22:55:47 +02:00
parent b99ac31f4d
commit 490e9adbd8
29 changed files with 1809 additions and 588 deletions

View File

@@ -0,0 +1,56 @@
'use strict';
var jsdom = require('jsdom');
var document = jsdom.jsdom('<!doctype html><html><head></head><body></body></html>', {
url: 'http://sharepoint/sites/portal/SitePages/Home.aspx'
});
global.document = document;
global.window = document.defaultView;
var scheduled = [];
window.setTimeout = function (callback) { scheduled.push(callback); return scheduled.length; };
window.clearTimeout = function () {};
var BrandingCssLoader = require('../temp/tests/BrandingCssLoader').BrandingCssLoader;
var passed = 0;
function assert(condition, message) {
if (!condition) { throw new Error(message); }
passed++;
}
var first = new BrandingCssLoader('first', function () {});
var second = new BrandingCssLoader('second', function () {});
first.load([{ path: '/SiteAssets/a.css', media: 'screen' }, { path: '/SiteAssets/b.css', media: 'all' }]);
second.load([{ path: '/SiteAssets/a.css', media: 'screen' }]);
var links = document.querySelectorAll('link[rel="stylesheet"]');
assert(links.length === 2, 'Identische Stylesheets muessen instanzuebergreifend dedupliziert werden.');
assert(links[0].href.indexOf('/SiteAssets/a.css') >= 0 && links[1].href.indexOf('/SiteAssets/b.css') >= 0,
'Die konfigurierte CSS-Reihenfolge muss stabil bleiben.');
first.dispose();
assert(document.querySelectorAll('link[rel="stylesheet"]').length === 1,
'Eine noch referenzierte Datei darf beim ersten Dispose nicht entfernt werden.');
second.dispose();
assert(document.querySelectorAll('link[rel="stylesheet"]').length === 0,
'Eigene Stylesheets muessen nach der letzten Referenz entfernt werden.');
var existing = document.createElement('link');
existing.rel = 'stylesheet';
existing.href = '/SiteAssets/existing.css';
document.head.appendChild(existing);
var third = new BrandingCssLoader('third', function () {});
third.load([{ path: '/SiteAssets/existing.css', media: 'all' }]);
assert(document.querySelectorAll('link[rel="stylesheet"]').length === 1, 'Vorhandenes fremdes CSS darf nicht dupliziert werden.');
third.dispose();
assert(existing.parentNode === document.head, 'Fremdes CSS darf beim Dispose nicht entfernt werden.');
var messages = [];
var fourth = new BrandingCssLoader('fourth', function (message) { messages.push(message); });
fourth.load([{ path: '/SiteAssets/error.css', media: 'all' }]);
var errorLink = document.querySelector('link[href$="error.css"]');
errorLink.onerror();
assert(messages.indexOf('Stylesheet failed to load.') >= 0, 'CSS-Ladefehler muss protokolliert werden.');
scheduled[scheduled.length - 1]();
assert(messages.indexOf('Stylesheet load timed out.') >= 0, 'CSS-Timeout muss protokolliert werden.');
fourth.dispose();
console.log('BrandingCssLoader: ' + passed + ' Pruefungen erfolgreich.');