import { buildMenuHierarchy, buildTermCacheKey, compareTermsByConfiguredOrder, getLocalizedTermLabel, getTermProperty, isExternalNavigationUrl, isNavigationUrlActive, isTermHidden, MegaMenuTermProperties, parseBooleanProperty, readCachedValue, resolveNavigationUrl } from '../src/services/MegaMenuCore'; import { ITerm } from '../src/services/ISPTermStorePickerService'; let passed: number = 0; function assert(name: string, condition: boolean): void { if (!condition) { throw new Error('FAILED: ' + name); } passed++; console.log('OK:', name); } function term(values: any): ITerm { // tslint:disable-line:no-any return values as ITerm; } assert('boolean property accepts German yes', parseBooleanProperty('ja')); assert('boolean property rejects false', !parseBooleanProperty('false')); assert('hidden local term property', isTermHidden(term({ LocalCustomProperties: { 'MegaMenu.Hidden': 'true' } }))); assert('description local term property', getTermProperty(term({ LocalCustomProperties: { 'MegaMenu.Description': 'Forms' } }), MegaMenuTermProperties.description) === 'Forms'); assert('open-in-new-window local term property', parseBooleanProperty(getTermProperty(term({ LocalCustomProperties: { 'MegaMenu.OpenInNewWindow': '1' } }), MegaMenuTermProperties.openInNewWindow))); assert('site collection token is resolved', resolveNavigationUrl('~sitecollection/SitePages/Home.aspx', 'https://portal/sites/hr') === 'https://portal/sites/hr/SitePages/Home.aspx'); assert('relative URL is accepted', resolveNavigationUrl('/sites/hr', 'https://portal') === '/sites/hr'); assert('javascript URL is blocked', resolveNavigationUrl('javascript:alert(1)', 'https://portal') === undefined); assert('data URL is blocked', resolveNavigationUrl('data:text/html,test', 'https://portal') === undefined); assert('control characters in URL are blocked', resolveNavigationUrl('java\nscript:alert(1)', 'https://portal') === undefined); assert('mailto URL is accepted', resolveNavigationUrl('mailto:test@example.com', 'https://portal') === 'mailto:test@example.com'); assert('external host is detected', isExternalNavigationUrl('https://example.com/a', 'https://portal')); assert('protocol-relative external host is detected', isExternalNavigationUrl('//example.com/a', 'https://portal')); assert('same origin is not external', !isExternalNavigationUrl('https://portal/a', 'https://portal')); assert('current child page activates navigation parent', isNavigationUrlActive('https://portal/sites/hr/docs/view.aspx?x=1', '/sites/hr/docs', 'https://portal')); assert('similar path does not create false active state', !isNavigationUrlActive('https://portal/sites/hr/documents', '/sites/hr/docs', 'https://portal')); const keyA: string = buildTermCacheKey('https://portal/sites/a', 'ABC', 1031, '1'); const keyB: string = buildTermCacheKey('https://portal/sites/a', 'ABC', 1033, '1'); const keyC: string = buildTermCacheKey('https://portal/sites/a', 'ABC', 1031, '2'); assert('cache key includes language', keyA !== keyB); assert('cache key includes configuration version', keyA !== keyC); assert('cache key is deterministic', keyA === buildTermCacheKey('https://portal/sites/a', 'abc', 1031, '1')); assert('valid cache value is returned', readCachedValue(JSON.stringify({ expiresAt: 200, value: 'menu' }), 100, false) === 'menu'); assert('expired cache is rejected normally', readCachedValue(JSON.stringify({ expiresAt: 50, value: 'menu' }), 100, false) === undefined); assert('expired cache is available as fallback', readCachedValue(JSON.stringify({ expiresAt: 50, value: 'menu' }), 100, true) === 'menu'); assert('malformed cache is rejected', readCachedValue('{invalid', 100, true) === undefined); const localized = term({ Name: 'Default', Labels: { _Child_Items_: [ { Language: 1033, Value: 'Company', IsDefaultForLanguage: true }, { Language: 1031, Value: 'Unternehmen' } ] } }); assert('German term label is selected', getLocalizedTermLabel(localized, 1031) === 'Unternehmen'); assert('default label is fallback', getLocalizedTermLabel(localized, 1040) === 'Company'); interface ITestItem { name: string; items?: ITestItem[]; } const child: ITestItem = { name: 'child' }; const root: ITestItem = { name: 'root' }; const hierarchy: ITestItem[] = buildMenuHierarchy([ { id: 'child', parentId: 'root', depth: 2, item: child }, { id: 'root', depth: 1, item: root } ]); assert('hierarchy works when child precedes parent', hierarchy.length === 1 && hierarchy[0].items[0] === child); const sorted: ITerm[] = [ term({ PathOfTerm: 'B', CustomSortOrderIndex: 1 }), term({ PathOfTerm: 'A', CustomSortOrderIndex: 0 }) ].sort(compareTermsByConfiguredOrder); assert('custom term order wins', sorted[0].PathOfTerm === 'A'); console.log('MegaMenu core tests passed:', passed);