snapshot of https://github.com/espocrm/espocrm.git (master, 2026-09-18; blobs > 131072B omitted)
Some checks failed
Test / Test on PHP 8.3 (push) Failing after 5s
Test / Test on PHP 8.4 (push) Failing after 4s
Test / Test on PHP 8.5 (push) Failing after 3s
TypeScript static analysis / TypeScript check (push) Failing after 14s
Test Integration on MySQL / Test on PHP 8.3 (push) Failing after 1s
Test Integration on MySQL / Test on PHP 8.5 (push) Failing after 1s
Test Integration on PostgreSQL / Test on PHP 8.4 fix (push) Failing after 1s
Test Integration on PostgreSQL / Test on PHP 8.4 master (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-18 04:19:15 +05:00
commit 7cc7e6b4f3
10077 changed files with 900756 additions and 0 deletions

18
frontend/test/README.md Normal file
View File

@@ -0,0 +1,18 @@
Command to run:
```
npx jasmine-browser-runner runSpecs --config=frontend/test/jasmine-browser.json
```
Starting a server (`http://localhost:8888/`):
```
npx jasmine-browser-runner serve --config=frontend/test/jasmine-browser.json
```
Requires *Chromedriver*.
Run `grunt internal` before running tests to build a lib bundle. Then, run `grunt transpile` each time after changes in
source files, before running the tests.
To test a module that is not in the bundle, add it to the `srcFiles` parameter in the `jasmine-browser.json`.

88
frontend/test/init.js Normal file
View File

@@ -0,0 +1,88 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
const scriptEl = document.createElement('script');
scriptEl.setAttribute('type', 'application/json');
scriptEl.setAttribute('data-name', 'loader-params');
scriptEl.textContent = JSON.stringify({
basePath: '../../',
libsConfig: {
"jquery": {
"exposeAs": "$"
},
"backbone": {
"exportsTo": "window",
"exportsAs": "Backbone"
},
"bullbone": {
"exposeAs": "Bull"
},
"handlebars": {
"exposeAs": "Handlebars"
},
"underscore": {
"exposeAs": "_"
},
"marked": {},
"dompurify": {
"exposeAs": "DOMPurify"
},
"js-base64": {
"exportsTo": "window",
"exportsAs": "Base64"
},
"moment": {
"exportsTo": "window",
"exportsAs": "moment"
},
"jquery-ui": {
"exportsTo": "$",
"exportsAs": "ui"
},
"jquery-ui-touch-punch": {
"exportsTo": "$",
"exportsAs": "ui"
}
},
aliasMap: {
"jquery": "lib!jquery",
"jquery-ui": "lib!jquery-ui",
"jquery-ui-touch-punch": "lib!jquery-ui-touch-punch",
"underscore": "lib!underscore",
"cronstrue": "lib!cronstrue",
"exif-js": "lib!exif-js",
"moment": "lib!moment",
"js-base64": "lib!base64",
"handlebars": "lib!handlebars",
"bullbone": "lib!bullbone",
"backbone": "lib!backbone"
}
});
document.head.appendChild(scriptEl);

View File

@@ -0,0 +1,31 @@
{
"srcDir": ".",
"srcFiles": [
"frontend/test/init.js",
"client/lib/espo.js",
"client/lib/transpiled/src/exceptions.js",
"client/lib/transpiled/src/utils.js",
"client/lib/transpiled/src/ajax.js",
"client/lib/transpiled/src/model.js",
"client/lib/transpiled/src/router.js",
"client/lib/transpiled/src/collection.js",
"client/lib/transpiled/src/controller.js",
"client/lib/transpiled/src/cache.js",
"client/lib/transpiled/src/metadata.js",
"client/lib/transpiled/src/acl.js",
"client/lib/transpiled/src/acl-manager.js",
"client/lib/transpiled/src/layout-manager.js",
"client/lib/transpiled/src/date-time.js",
"client/lib/transpiled/src/field-manager.js",
"client/lib/transpiled/src/models/settings.js",
"client/lib/transpiled/src/models/preferences.js",
"client/lib/transpiled/src/metadata.js",
"client/lib/transpiled/src/theme-manager.js"
],
"specDir": "frontend/test/spec",
"specFiles": [
"**/test.*.js"
],
"helpers": [],
"browser": "chrome"
}

View File

@@ -0,0 +1,73 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
describe('acl-manager', () => {
let acl;
beforeEach(function (done) {
require('acl-manager', Acl => {
acl = new Acl();
acl.user = {
isAdmin: () => false
};
done();
});
});
it("should check an access properly", () => {
acl.set({
table: {
Lead: {
read: 'team',
edit: 'own',
delete: 'no',
},
Opportunity: false,
Meeting: true
}
});
expect(acl.check('Lead', 'read')).toBe(true);
expect(acl.check('Lead', 'read', false, false)).toBe(true);
expect(acl.check('Lead', 'read', false, true)).toBe(true);
expect(acl.check('Lead', 'read', true)).toBe(true);
expect(acl.check('Lead', 'edit')).toBe(true);
expect(acl.check('Lead', 'edit', false, true)).toBe(true);
expect(acl.check('Lead', 'edit', true, false)).toBe(true);
expect(acl.check('Lead', 'delete')).toBe(false);
expect(acl.check('Account', 'edit')).toBe(false);
expect(acl.check('Opportunity', 'edit')).toBe(false);
expect(acl.check('Meeting', 'edit')).toBe(true);
});
});

View File

@@ -0,0 +1,42 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
describe('cache', () => {
let cache;
beforeEach(function (done) {
require('cache', Cache => {
cache = new Cache();
done();
});
});
it('should have \'cache\' prefix', () => {
expect(cache.prefix).toBe('cache');
});
});

View File

@@ -0,0 +1,196 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
describe('collection', () => {
let collection;
/** @type {typeof import('collection').default} */
let CollectionClass;
/** @type {typeof import('model').default} */
let ModelClass;
beforeEach(done => {
Espo.loader.require(['collection', 'model'], (Collection, Model) => {
CollectionClass = Collection;
ModelClass = Model;
const m1 = new Model({id: '1'});
const m2 = new Model({id: '2'});
const m3 = new Model({id: '3'});
collection = new Collection([m1, m2, m3], {
entityType: 'Some',
});
collection.maxSize = 5;
spyOn(collection, 'fetch').and.returnValue(true);
done();
});
});
it('should have correct length', () => {
expect(collection.length).toBe(3);
const collection2 = new CollectionClass();
expect(collection2.length).toBe(0);
const collection3 = new CollectionClass([new ModelClass({id: '1'})]);
expect(collection3.length).toBe(1);
});
it('should add array', () => {
const model = new ModelClass({id: '4'});
collection.add([model]);
expect(collection.length).toBe(4);
expect(collection.at(3)).toBe(model);
expect(collection.get('4')).toBe(model);
expect(collection.has('4')).toBeTrue();
expect(collection.indexOf(model)).toBe(3);
});
it('should add one', () => {
const model = new ModelClass({id: '4'});
collection.add(model, {at: 1});
expect(collection.length).toBe(4);
expect(collection.at(1)).toBe(model);
});
it('should reset', () => {
collection.reset();
expect(collection.length).toBe(0);
expect(collection.at(3)).toBeUndefined();
expect(collection.get('3')).toBeUndefined();
});
it('should remove', () => {
collection.remove(collection.get('2'));
expect(collection.length).toBe(2);
expect(collection.get('2')).toBeUndefined();
collection.remove('1');
expect(collection.length).toBe(1);
expect(collection.get('1')).toBeUndefined();
});
it('should push', () => {
const model = new ModelClass({id: '4'});
collection.push(model);
expect(collection.length).toBe(4);
expect(collection.at(3).id).toBe('4');
});
it('should unshift', () => {
const model = new ModelClass({id: '4'});
collection.unshift(model);
expect(collection.length).toBe(4);
expect(collection.at(0).id).toBe('4');
});
it('should pop', () => {
const model = collection.pop();
expect(collection.length).toBe(2);
expect(model.id).toBe('3');
expect(collection.has('3')).toBeFalse();
});
it('should shift', () => {
const model = collection.shift();
expect(collection.length).toBe(2);
expect(model.id).toBe('1');
});
it('should for-each', () => {
const ids = [];
collection.forEach(m => ids.push(m.id));
expect(ids).toEqual(['1', '2', '3']);
});
it('should have entityType', () => {
expect(collection.entityType).toBe('Some');
expect(collection.name).toBe('Some');
});
it('should be created with models', () => {
expect(collection.length).toBe(3);
});
it('#sort should set order params', () => {
collection.sort('test', true);
expect(collection.orderBy).toBe('test');
expect(collection.order).toBe('desc');
});
it('#nextPage and #previousPage should change offset to the next and previous pages', () => {
collection.total = 16;
collection.add([
{id: '1'},
{id: '2'},
{id: '3'},
{id: '4'},
{id: '5'},
]);
collection.nextPage();
expect(collection.offset).toBe(5);
collection.nextPage();
collection.previousPage();
expect(collection.offset).toBe(5);
collection.previousPage();
expect(collection.offset).toBe(0);
});
it('#firstPage and #lastPage should change offset appropriate way', () => {
collection.total = 16;
collection.firstPage();
expect(collection.offset).toBe(0);
collection.lastPage();
expect(collection.offset).toBe(15);
});
});

View File

@@ -0,0 +1,74 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
describe('controller', () => {
let controller;
let viewFactory;
let view;
let ControllerClass;
beforeEach(done => {
Espo.loader.require('controller', Controller => {
ControllerClass = Controller;
viewFactory = {
create: {}
};
view = {
render: {},
setView: {}
};
controller = new Controller({}, {viewFactory: viewFactory});
spyOn(viewFactory, 'create').and.returnValue(view);
spyOn(view, 'render');
spyOn(view, 'setView');
done();
});
});
it ('#set should set param', () => {
controller.set('some', 'test');
expect(controller.params['some']).toBe('test');
});
it ('#get should get param', () => {
controller.set('some', 'test');
expect(controller.get('some')).toBe('test');
});
it ("different controllers should use same param set", () => {
const someController = new ControllerClass(controller.params, {viewFactory: viewFactory});
someController.set('some', 'test');
expect(controller.get('some')).toBe(someController.get('some'));
});
});

View File

@@ -0,0 +1,94 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
describe('date-time', () => {
let dateTime;
beforeEach(done => {
require('date-time', DateTime => {
dateTime = new DateTime();
done();
});
});
it("should convert date from display format", () => {
dateTime.dateFormat = 'MM/DD/YYYY';
expect(dateTime.fromDisplayDate('10/05/2013')).toBe('2013-10-05');
dateTime.dateFormat = 'DD.MM.YYYY';
expect(dateTime.fromDisplayDate('05.10.2013')).toBe('2013-10-05');
dateTime.dateFormat = 'YYYY-MM-DD';
expect(dateTime.fromDisplayDate('2013-10-05')).toBe('2013-10-05');
});
it("should convert date to display format", () => {
dateTime.dateFormat = 'MM/DD/YYYY';
expect(dateTime.toDisplayDate('2013-10-05')).toBe('10/05/2013');
dateTime.dateFormat = 'DD.MM.YYYY';
expect(dateTime.toDisplayDate('2013-10-05')).toBe('05.10.2013');
dateTime.dateFormat = 'YYYY-MM-DD';
expect(dateTime.toDisplayDate('2013-10-05')).toBe('2013-10-05');
});
it("should convert date/time from display format and consider timezone", () => {
dateTime.dateFormat = 'MM/DD/YYYY';
dateTime.timeFormat = 'hh:mm A';
expect(dateTime.fromDisplay('10/05/2013 02:45 PM')).toBe('2013-10-05 14:45:00');
dateTime.dateFormat = 'MM/DD/YYYY';
dateTime.timeFormat = 'hh:mm a';
expect(dateTime.fromDisplay('10/05/2013 02:45 pm')).toBe('2013-10-05 14:45:00');
dateTime.dateFormat = 'DD.MM.YYYY';
dateTime.timeFormat = 'HH:mm';
expect(dateTime.fromDisplay('05.10.2013 14:45')).toBe('2013-10-05 14:45:00');
dateTime.timeZone = 'Europe/Kiev';
expect(dateTime.fromDisplay('05.10.2013 17:45')).toBe('2013-10-05 14:45:00');
});
it("should convert date/time to display format and consider timezone", () => {
dateTime.dateFormat = 'MM/DD/YYYY';
dateTime.timeFormat = 'hh:mm A';
expect(dateTime.toDisplay('2013-10-05 14:45')).toBe('10/05/2013 02:45 PM');
dateTime.dateFormat = 'MM/DD/YYYY';
dateTime.timeFormat = 'hh:mm a';
expect(dateTime.toDisplay('2013-10-05 14:45')).toBe('10/05/2013 02:45 pm');
dateTime.dateFormat = 'DD.MM.YYYY';
dateTime.timeFormat = 'HH:mm';
expect(dateTime.toDisplay('2013-10-05 14:45')).toBe('05.10.2013 14:45');
dateTime.timeZone = 'Europe/Kiev';
expect(dateTime.toDisplay('2013-10-05 14:45')).toBe('05.10.2013 17:45');
});
});

View File

@@ -0,0 +1,209 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
describe('field-manager', () => {
let fieldManager;
const defs = {
'varchar': {
'params': [
{
'name': 'required',
'type': 'bool',
'default': false
},
{
'name': 'maxLength',
'type': 'int'
}
]
},
'int': {
'params': [
{
'name': 'required',
'type': 'bool',
'default': false
},
{
'name': 'min',
'type': 'int'
},
{
'name': 'max',
'type': 'int'
}
]
},
'float': {
'params': [
{
'name': 'required',
'type': 'bool',
'default': false
},
{
'name': 'min',
'type': 'float'
},
{
'name': 'max',
'type': 'float'
}
]
},
'enum': {
'params': [
{
'name': 'required',
'type': 'bool',
'default': false
},
{
'name': 'options',
'type': 'array'
}
]
},
'bool': {
'params': []
},
'date': {
'params': [
{
'name': 'required',
'type': 'bool',
'default': false
}
]
},
'datetime': {
'params': [
{
'name': 'required',
'type': 'bool',
'default': false
}
]
},
'link': {
'params': [
{
'name': 'required',
'type': 'bool',
'default': false
}
],
'actualFields': ['id']
},
'linkParent': {
'params': [
{
'name': 'required',
'type': 'bool',
'default': false
}
],
'actualFields': ['id', 'type']
},
'linkMultiple': {
'params': [
{
'name': 'required',
'type': 'bool',
'default': false
}
],
'actualFields': ['ids']
},
'personName': {
'actualFields': ['salutation', 'first', 'last'],
'fields': {
'salutation': {
'type': 'varchar'
},
'first': {
'type': 'enum'
},
'last': {
'type': 'varchar'
}
},
'naming': 'prefix',
'notMergeable': true,
},
'address': {
'actualFields': ['street', 'city', 'state', 'country', 'postalCode'],
'fields': {
'street': {
'type': 'varchar'
},
'city': {
'type': 'varchar'
},
'state': {
'type': 'varchar'
},
'country': {
'type': 'varchar'
},
'postalCode': {
'type': 'varchar'
},
},
'notMergeable': true
}
};
beforeEach(done => {
require(['field-manager', 'utils'], FieldManager => {
fieldManager = new FieldManager(defs);
done();
});
});
it ('#isMergable should work correctly', () => {
expect(fieldManager.isMergeable('address')).toBe(false);
expect(fieldManager.isMergeable('link')).toBe(true);
});
it ('#getActualAttributeList should work correctly', () => {
let fields = fieldManager.getActualAttributeList('address', 'billingAddress');
expect(fields[0]).toBe('billingAddressStreet');
fields = fieldManager.getActualAttributeList('personName', 'name');
expect(fields[2]).toBe('lastName');
fields = fieldManager.getActualAttributeList('link', 'account');
expect(fields[0]).toBe('accountId');
fields = fieldManager.getActualAttributeList('varchar', 'name');
expect(fields[0]).toBe('name');
});
});

View File

@@ -0,0 +1,48 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
describe('layout-manager', () => {
let layoutManager;
beforeEach((done) => {
require('layout-manager', (LayoutManager) => {
layoutManager = new LayoutManager();
spyOn(layoutManager.ajax, 'getRequest')
.and
.callFake((options) => Promise.resolve());
done();
});
});
it("should call ajax to fetch new layout", () => {
layoutManager.get('some', 'list');
expect(layoutManager.ajax.getRequest.calls.mostRecent().args[0]).toBe('some/layout/list');
});
});

View File

@@ -0,0 +1,50 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
describe('metadata', () => {
let metadata;
beforeEach(done => {
require('metadata', Metadata => {
metadata = new Metadata();
metadata.data = {
recordDefs: {
Lead: {
some: {type: 'varchar'},
}
}
};
done();
});
});
it('#get should work correctly', () => {
expect(metadata.get('recordDefs.Lead.some')).toBe(metadata.data.recordDefs.Lead.some);
expect(metadata.get('recordDefs.Contact')).toBe(null);
});
});

View File

@@ -0,0 +1,245 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
describe('model', () => {
let model;
beforeEach(done => {
Espo.loader.require(['model', 'utils'], ModelBase => {
const Model = class extends ModelBase {};
model = new Model({}, {
entityType: 'Some',
defs: {
fields: {
'id': {
type: 'id',
},
'name': {
maxLength: 150,
required: true,
},
'email': {
type: 'email',
},
'phone': {
type: 'phone',
maxLength: 50,
default: '007',
},
},
},
});
done();
});
});
it('should set urlRoot as name', () => {
expect(model.urlRoot).toBe('Some');
});
it('#getFieldType should return field type or null if undefined', () => {
expect(model.getFieldType('email')).toBe('email');
expect(model.getFieldType('name')).toBe(null);
});
it('#isRequired should return true if field is required and false if not', () => {
expect(model.isRequired('name')).toBe(true);
expect(model.isRequired('email')).toBe(false);
});
it('should set defaults correctly', () => {
model.populateDefaults();
expect(model.get('phone')).toBe('007');
});
it('should fire change event on set 1', () => {
return new Promise(resolve => {
model.once('change', () => {
expect(model.get('name')).toBe('test');
resolve();
});
model.set('name', 'test');
});
});
it('should fire change event on set 2', () => {
return new Promise(resolve => {
model.once('change:name', (m, v) => {
expect(model.get('name')).toBe('test');
expect(v).toBe('test');
resolve();
});
model.set({'name': 'test'});
});
});
it('should not fire change event on set with silent option', () => {
return new Promise((resolve, reject) => {
model.once('change:name', () => {
reject();
});
model.once('change:hello', (m, v, o) => {
expect(o.test).toBe(true);
resolve();
});
model.setMultiple({'name': 'test'}, {silent: true});
model.setMultiple({'hello': 'hello'}, {test: true});
});
});
it('should unset attribute', () => {
model.set('name', 'test');
expect(model.has('name')).toBeTrue();
model.unset('name');
expect(model.has('name')).toBeFalse();
});
it('should unset attribute and fire events', () => {
return new Promise((resolve, reject) => {
model.setMultiple({
'name': '1',
'hello': 1,
});
model.once('change:name', () => {
reject();
});
model.once('change:hello', (m, v, o) => {
expect(o.test).toBe(true);
resolve();
});
model.unset('name', {silent: true});
model.unset('hello', {test: true});
});
});
it('should clear attributes', () => {
model.set('name', 'test');
expect(model.has('name')).toBeTrue();
model.clear();
expect(model.has('name')).toBeFalse();
});
it('should update with PUT', () => {
model.id = '000';
model.set('test1', '1');
model.set('test2', '2');
return new Promise(resolve => {
model.on('request', (url, method, data) => {
expect(url).toBe('Some/000');
expect(method).toBe('PUT');
expect(data).toEqual({
test1: '1',
test2: '2',
test3: '3',
});
resolve();
});
model.save({test3: '3'}, {bypassRequest: true});
});
});
it('should patch with PUT', () => {
model.id = '000';
model.set('test1', '1');
model.set('test2', '2');
return new Promise(resolve => {
model.on('request', (url, method, data) => {
expect(url).toBe('Some/000');
expect(method).toBe('PUT');
expect(data).toEqual({
test3: '3',
});
resolve();
});
model.save({test3: '3'}, {bypassRequest: true, patch: true});
});
});
it('should create with POST', () => {
model.set('test1', '1');
model.set('test2', '2');
return new Promise(resolve => {
model.on('request', (url, method, data) => {
expect(url).toBe('Some');
expect(method).toBe('POST');
expect(data).toEqual({
test1: '1',
test2: '2',
test3: '3',
});
resolve();
});
model.save({test3: '3'}, {bypassRequest: true});
});
});
it('should destroy with DELETE', () => {
model.id = '000';
model.set('test1', '1');
model.set('test2', '2');
return new Promise(resolve => {
model.on('request', (url, method) => {
expect(url).toBe('Some/000');
expect(method).toBe('DELETE');
resolve();
});
model.destroy({bypassRequest: true});
});
});
});

View File

@@ -0,0 +1,57 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
describe('router', () => {
let router;
beforeEach(done => {
require('router', Router => {
router = new Router();
done();
});
});
it('should parse slashed options', () => {
let options = router._parseOptionsParams("p=1&t=2");
expect(options.p).toBe('1');
expect(options.t).toBe('2');
options = router._parseOptionsParams("p=1&");
expect(options.p).toBe('1');
options = router._parseOptionsParams("p");
expect(options).toBe("p");
options = router._parseOptionsParams("p=1&t");
expect(options.p).toBe('1');
expect(options.t).toBe(true);
options = router._parseOptionsParams("p=1&t=");
expect(options.t).toBe('');
});
});

View File

@@ -0,0 +1,233 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
describe('theme-manager', () => {
/** @type {module:models/settings} */
let config;
/** @type {module:models/preferences} */
let preferences;
/** @type {module:metadata} */
let metadata;
/** @type {module:theme-manager} */
let themeManager;
beforeEach(done => {
require(['models/settings', 'models/preferences', 'metadata', 'theme-manager'],
(Config, Preferences, Metadata, ThemeManager) => {
config = new Config();
preferences = new Preferences();
metadata = new Metadata();
themeManager = new ThemeManager(config, preferences, metadata)
metadata.data = {
themes: {
"Espo": {
"params": {
"navbar": {
"type": "enum",
"default": "side",
"options": [
"side",
"top"
]
},
"direction": {
"type": "enum",
"default": "ltr",
"options": [
"ltr",
"rtl"
]
}
},
"mappedParams": {
"navbarHeight": {
"param": "navbar",
"valueMap": {
"side": 30,
"top": 43
}
}
},
"someParam": "someValue"
}
}
};
done();
});
});
it('name', () => {
spyOn(preferences, 'get').and.callFake(name => {
if (name === 'theme') {
return 'Test';
}
});
expect(themeManager.getName()).toBe('Test');
});
it('param', () => {
spyOn(preferences, 'get').and.callFake(name => {
if (name === 'theme') {
return 'Espo';
}
});
expect(themeManager.getParam('someParam')).toBe('someValue');
});
it('parent param', () => {
spyOn(preferences, 'get').and.callFake(name => {
if (name === 'theme') {
return 'Test';
}
});
expect(themeManager.getParam('someParam')).toBe('someValue');
});
it('default param', () => {
spyOn(preferences, 'get').and.callFake(name => {
if (name === 'theme') {
return 'Test';
}
});
expect(themeManager.getParam('navbar')).toBe('side');
});
it('param from preferences', () => {
spyOn(preferences, 'get').and.callFake(name => {
if (name === 'theme') {
return 'Test';
}
if (name === 'themeParams') {
return {
navbar: 'top',
};
}
});
expect(themeManager.getParam('navbar')).toBe('top');
});
it('param from config', () => {
spyOn(preferences, 'get').and.callFake(name => {
if (name === 'theme') {
return null;
}
});
spyOn(config, 'get').and.callFake(name => {
if (name === 'themeParams') {
return {
navbar: 'top',
};
}
});
expect(themeManager.getParam('navbar')).toBe('top');
});
it('direction default', () => {
spyOn(preferences, 'get').and.callFake(name => {
if (name === 'theme') {
return 'Espo';
}
});
expect(themeManager.getDirection()).toBe('ltr');
});
it('direction from preferences', () => {
spyOn(preferences, 'get').and.callFake(name => {
if (name === 'theme') {
return 'Espo';
}
if (name === 'themeParams') {
return {
direction: 'rtl',
};
}
});
expect(themeManager.getDirection()).toBe('rtl');
});
it('invalid direction', () => {
spyOn(preferences, 'get').and.callFake(name => {
if (name === 'theme') {
return 'Espo';
}
if (name === 'themeParams') {
return {
direction: 'invalid',
};
}
});
expect(themeManager.getDirection()).toBe('ltr');
});
it('mapped param 1', () => {
spyOn(preferences, 'get').and.callFake(name => {
if (name === 'theme') {
return 'Test';
}
if (name === 'themeParams') {
return {
navbar: 'top',
};
}
});
expect(themeManager.getParam('navbarHeight')).toBe(43);
});
it('mapped param 2', () => {
spyOn(preferences, 'get').and.callFake(name => {
if (name === 'theme') {
return 'Test';
}
if (name === 'themeParams') {
return {
navbar: 'side',
};
}
});
expect(themeManager.getParam('navbarHeight')).toBe(30);
});
});

View File

@@ -0,0 +1,234 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
describe('utils', () => {
let Utils;
beforeEach(done => {
require(['utils'], UtilsD => {
Utils = UtilsD;
done();
});
});
it('#upperCaseFirst should make first letter uppercase', () => {
expect(Utils.upperCaseFirst('someTest')).toBe('SomeTest');
});
it('#checkAccessDataList should check access 1', () => {
const user = {
isPortal: () => {
},
getLinkMultipleIdList: () => {
},
isAdmin: () => {
}
};
const acl = {
check: () => {
},
checkScope: () => {
}
};
const entity = {
name: 'Account'
};
spyOn(user, 'getLinkMultipleIdList').and.callFake(link => {
if (link === 'teams') {
return ['team1', 'team2'];
}
if (link === 'portals') {
return ['portal1', 'portal2'];
}
});
spyOn(user, 'isPortal').and.returnValue(false);
spyOn(user, 'isAdmin').and.returnValue(false);
spyOn(acl, 'checkScope').and.callFake(function (scope) {
if (scope === 'Account') {
return true;
}
});
spyOn(acl, 'check').and.callFake(function (obj, action) {
if (obj === 'Account') {
return {
create: true,
read: true,
edit: true,
delete: false
}[action];
}
if (obj.name) {
return {
create: true,
read: true,
edit: false,
delete: false
}[action];
}
return false;
});
expect(Utils.checkAccessDataList([
], acl, user)).toBe(true);
expect(Utils.checkAccessDataList([
{
scope: 'Account'
}
], acl, user)).toBe(true);
expect(Utils.checkAccessDataList([
{
scope: 'Account',
action: 'delete'
}
], acl, user)).toBe(false);
expect(Utils.checkAccessDataList([
{
scope: 'Account',
action: 'read'
},
{
inPortalDisabled: true
}
], acl, user)).toBe(true);
expect(Utils.checkAccessDataList([
{
action: 'read'
},
{
teamIdList: ['team1', 'team3']
}
], acl, user, entity)).toBe(true);
expect(Utils.checkAccessDataList([
{
isPortalOnly: true
}
], acl, user)).toBe(false);
expect(Utils.checkAccessDataList([
{
isAdminOnly: true
}
], acl, user)).toBe(false);
expect(Utils.checkAccessDataList([
{
action: 'edit'
}
], acl, user, entity)).toBe(false);
});
it('#checkAccessDataList should check access 2', () => {
const user = {
isPortal: () => {
},
getLinkMultipleIdList: () => {
},
isAdmin: () => {
}
};
const acl = {
check: () => {
},
checkScope: () => {
}
};
spyOn(user, 'getLinkMultipleIdList').and.callFake(link => {
if (link === 'teams') {
return ['team1', 'team2'];
}
if (link === 'portals') {
return ['portal1', 'portal2'];
}
});
spyOn(user, 'isPortal').and.returnValue(true);
spyOn(user, 'isAdmin').and.returnValue(false);
expect(Utils.checkAccessDataList([
{
isPortalOnly: true
}
], acl, user)).toBe(true);
expect(Utils.checkAccessDataList([
{
portalIdList: ['portal2', 'portal3']
}
], acl, user)).toBe(true);
});
it('#checkAccessDataList should check access 3', () => {
const user = {
isPortal: function () {
},
getLinkMultipleIdList: function () {
},
isAdmin: function () {
}
};
const acl = {
check: function () {
},
checkScope: function () {
}
};
spyOn(user, 'getLinkMultipleIdList').and.callFake(link => {
if (link === 'teams') {
return ['team1', 'team2'];
}
if (link === 'portals') {
return ['portal1', 'portal2'];
}
});
spyOn(user, 'isPortal').and.returnValue(false);
spyOn(user, 'isAdmin').and.returnValue(true);
expect(Utils.checkAccessDataList([
{
portalIdList: ['portal3']
}
], acl, user, null, true)).toBe(true);
expect(Utils.checkAccessDataList([
{
teamIdList: ['team3']
}
], acl, user, null, true)).toBe(true);
});
});