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:
85
tests/BrandingConfig.test.js
Normal file
85
tests/BrandingConfig.test.js
Normal file
@@ -0,0 +1,85 @@
|
||||
'use strict';
|
||||
|
||||
var configModule = require('../temp/tests/BrandingConfig');
|
||||
var normalize = configModule.normalizeBrandingConfig;
|
||||
var siteUrl = 'http://sharepoint/sites/portal';
|
||||
var passed = 0;
|
||||
|
||||
function assert(condition, message) {
|
||||
if (!condition) {
|
||||
throw new Error(message);
|
||||
}
|
||||
passed++;
|
||||
}
|
||||
|
||||
function element(raw) {
|
||||
return normalize({ placeholdertop: { elements: [raw] } }, siteUrl).config.placeholdertop.elements[0];
|
||||
}
|
||||
|
||||
var empty = normalize({}, siteUrl);
|
||||
assert(empty.config.schemaVersion === 2, 'Das aktuelle Schema muss Version 2 verwenden.');
|
||||
assert(empty.config.enabled === true, 'Branding muss standardmaessig aktiviert sein.');
|
||||
|
||||
var legacy = normalize({ elements: [{ type: 'span', content: 'Alt' }] }, siteUrl);
|
||||
assert(legacy.config.placeholdertop.elements.length === 1, 'Das Legacy-Root-Array muss weiter funktionieren.');
|
||||
|
||||
['div', 'span', 'p', 'a', 'button', 'img', 'h1', 'h2', 'h3', 'strong', 'em', 'nav', 'section']
|
||||
.forEach(function (tag) {
|
||||
var attributes = tag === 'img' ? { alt: '' } : undefined;
|
||||
var content = tag === 'a' || tag === 'button' ? tag : undefined;
|
||||
assert(!!element({ type: tag, content: content, attributes: attributes }), 'Erlaubter Tag fehlt: ' + tag);
|
||||
});
|
||||
|
||||
assert(!element({ type: 'script', content: 'alert(1)' }), 'script darf nicht gerendert werden.');
|
||||
assert(!element({ type: 'iframe' }), 'iframe darf nicht gerendert werden.');
|
||||
|
||||
var safeLink = element({
|
||||
type: 'a',
|
||||
content: 'Portal',
|
||||
attributes: { href: '~sitecollection/SitePages/Home.aspx', target: '_blank', onclick: 'alert(1)' }
|
||||
});
|
||||
assert(safeLink.attributes.href === siteUrl + '/SitePages/Home.aspx', '~sitecollection wurde nicht aufgeloest.');
|
||||
assert(safeLink.attributes.rel === 'noopener noreferrer', 'Externe Fenster brauchen noopener/noreferrer.');
|
||||
assert(safeLink.attributes.onclick === undefined, 'Event-Attribute duerfen nicht uebernommen werden.');
|
||||
|
||||
var unsafeLink = element({ type: 'a', content: 'Unsicher', attributes: { href: 'javascript:alert(1)' } });
|
||||
assert(unsafeLink.attributes === undefined || unsafeLink.attributes.href === undefined, 'javascript: muss blockiert werden.');
|
||||
|
||||
var styled = element({
|
||||
type: 'div',
|
||||
styles: { color: '#fff', position: 'fixed', background: 'url(javascript:alert(1))' }
|
||||
});
|
||||
assert(styled.styles.color === '#fff', 'Erlaubte Styles muessen erhalten bleiben.');
|
||||
assert(styled.styles.position === undefined, 'Nicht freigegebene Styles muessen entfernt werden.');
|
||||
assert(styled.styles.background === undefined, 'CSS url() muss blockiert werden.');
|
||||
|
||||
assert(!element({ type: 'img', attributes: { src: '/logo.png' } }), 'Bilder ohne alt muessen verworfen werden.');
|
||||
assert(!!element({ type: 'img', attributes: { src: '/logo.png', alt: '' } }), 'Dekorative Bilder mit leerem alt sind erlaubt.');
|
||||
assert(!element({ type: 'button' }), 'Leere Buttons muessen verworfen werden.');
|
||||
|
||||
var css = normalize({
|
||||
allowedCssHosts: ['cdn.example.org'],
|
||||
cssfiles: [
|
||||
{ path: '/SiteAssets/site.css' },
|
||||
{ path: 'https://cdn.example.org/portal.css', media: 'screen' },
|
||||
{ path: 'https://evil.example.org/evil.css' },
|
||||
{ path: 'http://cdn.example.org/mixed.css' },
|
||||
{ path: '/SiteAssets/site.css' }
|
||||
]
|
||||
}, siteUrl).config.cssfiles;
|
||||
assert(css.length === 2, 'CSS-Allowlist oder Deduplizierung ist fehlerhaft.');
|
||||
assert(css[0].media === 'all' && css[1].media === 'screen', 'CSS-Media wurde nicht normalisiert.');
|
||||
|
||||
var deep = { type: 'div' };
|
||||
var cursor = deep;
|
||||
for (var depth = 0; depth < 12; depth++) {
|
||||
cursor.children = [{ type: 'div' }];
|
||||
cursor = cursor.children[0];
|
||||
}
|
||||
var deepResult = normalize({ placeholdertop: { elements: [deep] } }, siteUrl);
|
||||
assert(deepResult.warnings.some(function (warning) { return warning.indexOf('depth') >= 0; }), 'Das Tiefenlimit greift nicht.');
|
||||
|
||||
var huge = { placeholdertop: { elements: [] }, padding: new Array(100002).join('x') };
|
||||
assert(normalize(huge, siteUrl).config.placeholdertop.elements.length === 0, 'Zu grosse Konfiguration muss ignoriert werden.');
|
||||
|
||||
console.log('BrandingConfig: ' + passed + ' Pruefungen erfolgreich.');
|
||||
Reference in New Issue
Block a user