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:
55
tests/ProviderRegistry.test.ts
Normal file
55
tests/ProviderRegistry.test.ts
Normal 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);
|
||||
20
tests/tsconfig.json
Normal file
20
tests/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "../temp/tests",
|
||||
"skipLibCheck": true,
|
||||
"types": [],
|
||||
"lib": ["es5", "dom", "es2015.collection"]
|
||||
},
|
||||
"files": [
|
||||
"../src/core/ProviderContracts.ts",
|
||||
"../src/core/ObjectPath.ts",
|
||||
"../src/providers/MegaMenuSettingsProvider.ts",
|
||||
"../src/providers/CustomBrandingSettingsProvider.ts",
|
||||
"../src/providers/ExpiryIndicatorSettingsProvider.ts",
|
||||
"../src/providers/ProviderRegistry.ts",
|
||||
"./ProviderRegistry.test.ts"
|
||||
]
|
||||
}
|
||||
26
tests/validate-project.ps1
Normal file
26
tests/validate-project.ps1
Normal file
@@ -0,0 +1,26 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
|
||||
$tokens = $null
|
||||
$errors = $null
|
||||
[void][System.Management.Automation.Language.Parser]::ParseFile(
|
||||
(Join-Path $root 'deployment\Add-PortalSettingsPage.ps1'),
|
||||
[ref]$tokens,
|
||||
[ref]$errors
|
||||
)
|
||||
if ($errors.Count -gt 0) {
|
||||
throw ('PowerShell-Syntaxfehler: ' + $errors[0].Message)
|
||||
}
|
||||
|
||||
$jsonFiles = @(
|
||||
'package.json',
|
||||
'config\config.json',
|
||||
'config\package-solution.json',
|
||||
'config\serve.json',
|
||||
'src\webparts\portalSettings\PortalSettingsWebPart.manifest.json'
|
||||
)
|
||||
foreach ($relativePath in $jsonFiles) {
|
||||
Get-Content -Raw -LiteralPath (Join-Path $root $relativePath) | ConvertFrom-Json | Out-Null
|
||||
}
|
||||
|
||||
Write-Host ("PortalSettings project validation passed: 1 PowerShell file and $($jsonFiles.Count) JSON files.")
|
||||
36
tests/validate-static-assets.js
Normal file
36
tests/validate-static-assets.js
Normal file
@@ -0,0 +1,36 @@
|
||||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var root = path.resolve(__dirname, '..');
|
||||
var passed = 0;
|
||||
|
||||
function read(file) { return fs.readFileSync(path.join(root, file), 'utf8'); }
|
||||
function json(file) { return JSON.parse(read(file)); }
|
||||
function assert(condition, message) { if (!condition) { throw new Error(message); } passed++; }
|
||||
|
||||
var packageJson = json('package.json');
|
||||
var solution = json('config/package-solution.json').solution;
|
||||
var config = json('config/config.json');
|
||||
var manifest = json('src/webparts/portalSettings/PortalSettingsWebPart.manifest.json');
|
||||
var styles = read('src/webparts/portalSettings/PortalSettings.scss');
|
||||
var sourceFiles = [
|
||||
'src/ui/Dom.ts',
|
||||
'src/ui/DynamicFormRenderer.ts',
|
||||
'src/ui/PortalSettingsApp.ts'
|
||||
].map(read).join('\n');
|
||||
|
||||
assert(packageJson.version === '3.0.0', 'Package-Version ist nicht 3.0.0.');
|
||||
assert(solution.version === '3.0.0.0', 'Solution-Version ist nicht 3.0.0.0.');
|
||||
assert(!solution.features, 'Alte ASPX-Feature-Assets sind noch registriert.');
|
||||
assert(!!config.bundles['portal-settings-web-part'], 'WebPart-Bundle fehlt.');
|
||||
assert(manifest.componentType === 'WebPart', 'Komponente ist kein WebPart.');
|
||||
assert(!fs.existsSync(path.join(root, 'src/assets/PortalSettings.aspx')), 'Alte ASPX-Seite ist noch vorhanden.');
|
||||
assert(!fs.existsSync(path.join(root, 'src/extensions/portalSettings/PortalSettingsApplicationCustomizer.ts')), 'Alter Application Customizer ist noch vorhanden.');
|
||||
assert(sourceFiles.indexOf('innerHTML') < 0, 'UI darf innerHTML nicht verwenden.');
|
||||
['themePrimary', 'neutralPrimary', 'neutralLight', 'white'].forEach(function (slot) {
|
||||
assert(styles.indexOf('[theme: ' + slot + ', default:') >= 0, 'Themeslot fehlt: ' + slot);
|
||||
});
|
||||
assert(packageJson.scripts.package.indexOf('npm test') === 0, 'Paketierung startet nicht mit Tests.');
|
||||
|
||||
console.log('PortalSettings static tests passed: ' + passed);
|
||||
Reference in New Issue
Block a user