@eclipse-scout/core
Version:
Eclipse Scout runtime
244 lines (217 loc) • 8.61 kB
text/typescript
/*
* Copyright (c) 2010, 2025 BSI Business Systems Integration AG
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
import {
AbstractConstructor, arrays, AutoLeafPageWithNodes, BaseDoEntity, Constructor, dataObjects, DoEntity, HybridActionEvent, HybridManager, ObjectFactory, Page, PageWithNodes, PageWithTable, scout, Session, strings, TypeDescriptor, Widget
} from '../index';
import $ from 'jquery';
import 'jasmine-ajax';
let _jsonResourceCache = {};
/**
* Utility functions for jasmine tests.
*/
export const JasmineScoutUtil = {
/**
* @returns the loaded JSON data structure
*/
loadJsonResource(jsonResourceUrl: string, options: { useCache?: boolean } = {}): JQuery.Promise<any> {
scout.assertParameter('jsonResourceUrl', jsonResourceUrl);
if (scout.nvl(options.useCache, true)) {
let json = _jsonResourceCache[jsonResourceUrl];
if (json) {
return $.resolvedPromise(json);
}
}
return $.ajax({
async: false,
method: 'GET',
dataType: 'json',
contentType: 'application/json; charset=UTF-8',
cache: false,
url: jsonResourceUrl
})
.done(json => {
if (scout.nvl(options.useCache, true)) {
_jsonResourceCache[jsonResourceUrl] = json;
}
return $.resolvedPromise(json);
})
.fail((jqXHR, textStatus, errorThrown) => {
throw new Error('Could not load resource from url: ' + jsonResourceUrl);
});
},
loadJsonResourceAndMockRestCall(resourceUrlToMock: string, jsonResourceUrl: string, options: {
useCache?: boolean;
restriction?: any;
method?: string;
} = {}) {
scout.assertParameter('resourceUrlToMock', resourceUrlToMock);
JasmineScoutUtil.loadJsonResource(jsonResourceUrl, options)
.then(json => JasmineScoutUtil.mockRestCall(resourceUrlToMock, json, options));
},
mockRestLookupCall(resourceUrlToMock: string, lookupRows: any[], parentRestriction?: any) {
scout.assertParameter('resourceUrlToMock', resourceUrlToMock);
// Normalize lookup rows
lookupRows = arrays.ensure(lookupRows).map(lookupRow => $.extend({
active: true,
enabled: true,
parentId: null
}, lookupRow));
// getAll()
JasmineScoutUtil.mockRestCall(resourceUrlToMock, {
rows: lookupRows
}, {
restriction: parentRestriction
});
// getKey()
lookupRows.forEach(lookupRow => {
JasmineScoutUtil.mockRestCall(resourceUrlToMock, {
rows: [lookupRow]
}, {
restriction: lookupRow.id
});
});
},
mockRestCall(resourceUrlToMock: string, responseData: any, options: {
restriction?: any;
method?: string;
/**
* Used to serialize the responseData. Default is {@link JSON.stringify}.
*/
stringify?: (any) => string;
} = {}) {
let url = new RegExp('.*' + strings.quote(resourceUrlToMock) + '.*');
let data = options.restriction ? new RegExp('.*' + strings.quote(options.restriction) + '.*') : undefined;
const stringify = options.stringify || JSON.stringify;
const responseText = stringify(responseData);
jasmine.Ajax.stubRequest(url, data, options.method).andReturn({
status: 200,
responseText
});
},
mockDataObjectRestCall(resourceUrlToMock: string, responseData: BaseDoEntity | void, options: {
restriction?: any;
method?: string;
/**
* Used to serialize the responseData. Default is {@link dataObjects.stringify}.
*/
stringify?: (any) => string;
} = {}) {
options.stringify = options.stringify || dataObjects.stringify;
this.mockRestCall(resourceUrlToMock, responseData, options);
},
/**
* If an ajax call is not mocked, this fallback will be triggered to show information about which url is not mocked.
*/
captureNotMockedCalls() {
jasmine.Ajax.stubRequest(/.*/).andCallFunction((request: JasmineAjaxRequest) => {
fail('Ajax call not mocked for url: ' + request.url + ', method: ' + request.method);
});
},
/**
* Calls the given mock as soon as a hybrid action with the given actionType is called.
* The mock is called asynchronously using setTimeout to let the runtime code add any required event listeners first.
*
* The mock may return an object with [id, widget] if the action is supposed to create widgets.
* The format of the id depends on the method used to add widgets:
* - `AbstractHybridAction.addWidget(IWidget)` (e.g. `AbstractFormHybridAction`): `${widgetId}`
* - `AbstractHybridAction.addWidgets(Map<String, IWidget>)`: `${actionId}${widgetId}`
*/
mockHybridAction<TData extends DoEntity>(session: Session, actionType: string, mock: (event: HybridActionEvent<TData>) => Record<string, Widget>) {
let hm = HybridManager.get(session);
hm.on('hybridAction', (event: HybridActionEvent<TData>) => {
if (event.data.actionType === actionType) {
setTimeout(() => {
let widgets = mock(event);
if (widgets) {
hm.setProperty('widgets', widgets);
}
});
}
});
},
/**
* Asserts that every page has an uuid and a specific {@link PageParamDo}, if required.
*/
assertPageCompleteness(options?: PageCompletenessOptions) {
options = options || {};
let pagesNotRequiringUuid: Set<Constructor<Page> | AbstractConstructor<Page>> = new Set([PageWithNodes, PageWithTable, AutoLeafPageWithNodes, ...options.pagesNotRequiringUuid || []]);
let pagesNotRequiringPageParam: Set<Constructor<Page> | AbstractConstructor<Page>> = new Set([PageWithNodes, PageWithTable, AutoLeafPageWithNodes, ...options.pagesNotRequiringPageParam || []]);
let missingUuids: Set<string> = new Set();
let missingPageParams = new Set();
let completePages = new Set();
for (const PageConstructor of ObjectFactory.get().getSubClassesOf(Page)) {
let pageType = ObjectFactory.get().getObjectType(PageConstructor);
if (options.namespace && !pageType.startsWith(options.namespace)) {
continue;
}
let page = new PageConstructor();
page.minimalInit();
// Assert uuid
if (scout.nvl(options.assertUuid, true) && !page.uuid && !pagesNotRequiringUuid.has(PageConstructor)) {
missingUuids.add(pageType);
}
// Assert pageParam
if (scout.nvl(options.assertPageParam, true)) {
let pageParamType = `${pageType}ParamDo`;
let PageParam = TypeDescriptor.resolveType(pageParamType);
if (!PageParam && !pagesNotRequiringPageParam.has(PageConstructor)) {
missingPageParams.add(pageType);
}
}
if (!missingUuids.has(pageType) && !missingPageParams.has(pageType)) {
completePages.add(pageType);
}
}
if (missingUuids.size > 0) {
fail([
`Found ${missingUuids.size} pages without a uuid. Please ensure every page has a uuid.`,
...missingUuids
].join('\n'));
}
if (missingPageParams.size > 0) {
fail([
`Found ${missingPageParams.size} pages without a pageParam.`,
'If a pageParam is required, create one and add `declare pageParam: NewPageParam` to your page.',
'Otherwise, add the page to the ignore list (`options.pagesNotRequiringPageParam`).',
...missingPageParams
].join('\n'));
}
if (completePages.size > 0) {
console.log(`PageCompleteness: the following pages are complete: ${Array.from(completePages).join(', ')}`);
}
if (completePages.size === 0 && missingUuids.size === 0 && missingPageParams.size === 0) {
console.log('PageCompleteness: no pages found in this module.');
}
// A test without an expectation logs a warning
expect(true).toBe(true);
}
};
export type PageCompletenessOptions = {
/**
* Specifies whether the pages need an uuid. Default is true.
*/
assertUuid?: boolean;
/**
* Specifies whether the pages need a pageParam. Default is true.
*/
assertPageParam?: boolean;
/**
* Contains the pages that do not require an uuid.
*/
pagesNotRequiringUuid?: (Constructor<Page> | AbstractConstructor<Page>)[];
/**
* Contains the pages that do not require a pageParam, e.g. because the page does not have any parameters.
*/
pagesNotRequiringPageParam?: (Constructor<Page> | AbstractConstructor<Page>)[];
/**
* If specified, only the pages in this namespace are considered.
*/
namespace?: string;
};