graphdb-workbench-tests 4.0.0-TR11 → 4.0.0-TR13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/e2e-legacy/graph-navigator/graph-navigator-restrictions.spec.js +14 -0
- package/e2e-legacy/graph-navigator/graph-navigator.spec.js +22 -7
- package/e2e-legacy/import/import-server-files-batch-operations.spec.js +33 -0
- package/e2e-legacy/import/import-server-files.spec.js +18 -0
- package/e2e-legacy/import/import-user-data-settings-dialog.spec.js +81 -10
- package/e2e-legacy/import/import-user-data-url.spec.js +9 -8
- package/e2e-legacy/monitor/global-operation-statuses-component.spec.js +13 -17
- package/e2e-legacy/repository/repository-picker-tooltip.spec.js +72 -0
- package/e2e-legacy/repository/repository-selector-tooltip.spec.js +55 -0
- package/e2e-legacy/setup/users-and-access/user-and-access.spec.js +6 -0
- package/fixtures/graph-navigator/graph-navigator-settings.ttl +148 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/steps/cookie-policy/cookie-consent-banner-steps.js +2 -2
- package/steps/graph-navigator-steps.js +3 -4
- package/steps/import/import-server-files-steps.js +8 -0
- package/steps/import/import-settings-dialog-steps.js +16 -0
- package/steps/import/import-steps.js +16 -3
- package/steps/import/import-user-data-steps.js +24 -24
- package/steps/repository-picker-list-steps.js +16 -0
- package/steps/repository-selector-steps.js +16 -0
- package/stubs/browser-stubs.js +40 -0
- package/stubs/repositories/repositories-stubs.js +40 -0
|
@@ -8,10 +8,24 @@ const NO_REPOSITORY_MESSAGE = 'you are not connected to any repository';
|
|
|
8
8
|
const INVALID_LICENSE_MESSAGE = 'your license is not valid';
|
|
9
9
|
const SELECT_OR_CREATE_REPOSITORY_MESSAGE = 'Select a repository below or create a new one.';
|
|
10
10
|
const SELECT_REPOSITORY_MESSAGE = 'Select a repository below.';
|
|
11
|
+
// TODO: Remove once GraphDB creates the system repository itself.
|
|
12
|
+
// The graph-navigator settings endpoint requires this repository.
|
|
13
|
+
const SYSTEM_REPOSITORY_ID = 'SystemRepository';
|
|
11
14
|
|
|
12
15
|
describe('Graph Navigator view restrictions', () => {
|
|
13
16
|
let repositoryId;
|
|
14
17
|
|
|
18
|
+
// TODO: Remove once GraphDB creates the system repository itself.
|
|
19
|
+
before(() => {
|
|
20
|
+
cy.deleteRepository(SYSTEM_REPOSITORY_ID);
|
|
21
|
+
cy.createRepository({id: SYSTEM_REPOSITORY_ID});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// TODO: Remove once GraphDB creates the system repository itself.
|
|
25
|
+
after(() => {
|
|
26
|
+
cy.deleteRepository(SYSTEM_REPOSITORY_ID);
|
|
27
|
+
});
|
|
28
|
+
|
|
15
29
|
beforeEach(() => {
|
|
16
30
|
repositoryId = 'graph-navigator-view-restrictions-' + Date.now();
|
|
17
31
|
cy.createRepository({id: repositoryId});
|
|
@@ -9,13 +9,29 @@ import {ToasterSteps} from '../../steps/toaster-steps.js';
|
|
|
9
9
|
const FILE_TO_IMPORT = 'resource-test-data.ttl';
|
|
10
10
|
const SEED_RESOURCE_ENCODED = 'http:%2F%2Fexample.com%2Fontology%23CustomerLoyalty';
|
|
11
11
|
const SEED_RESOURCE_LABEL = 'CustomerLoyalty';
|
|
12
|
+
const SETTINGS_FILE_NAME = 'graph-navigator-settings.ttl';
|
|
13
|
+
const SETTINGS_FILE = `fixtures/graph-navigator/${SETTINGS_FILE_NAME}`;
|
|
12
14
|
|
|
13
15
|
const CONSTRUCT_QUERY = 'CONSTRUCT { <http://example.com/ontology#CustomerLoyalty> ?p ?o } WHERE { <http://example.com/ontology#CustomerLoyalty> ?p ?o }';
|
|
14
16
|
const CONSTRUCT_TARGET_LABEL = 'Metric';
|
|
17
|
+
// TODO: Remove once GraphDB creates the system repository itself.
|
|
18
|
+
// The graph-navigator settings endpoint requires this repository.
|
|
19
|
+
const SYSTEM_REPOSITORY_ID = 'SystemRepository';
|
|
15
20
|
|
|
16
21
|
describe('Graph Navigator', () => {
|
|
17
22
|
let repositoryId;
|
|
18
23
|
|
|
24
|
+
// TODO: Remove once GraphDB creates the system repository itself.
|
|
25
|
+
before(() => {
|
|
26
|
+
cy.deleteRepository(SYSTEM_REPOSITORY_ID);
|
|
27
|
+
cy.createRepository({id: SYSTEM_REPOSITORY_ID});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// TODO: Remove once GraphDB creates the system repository itself.
|
|
31
|
+
after(() => {
|
|
32
|
+
cy.deleteRepository(SYSTEM_REPOSITORY_ID);
|
|
33
|
+
});
|
|
34
|
+
|
|
19
35
|
beforeEach(() => {
|
|
20
36
|
repositoryId = 'repository-' + Date.now();
|
|
21
37
|
cy.createRepository({id: repositoryId});
|
|
@@ -124,7 +140,6 @@ describe('Graph Navigator', () => {
|
|
|
124
140
|
GraphNavigatorSteps.getElements().should('not.exist');
|
|
125
141
|
});
|
|
126
142
|
|
|
127
|
-
// These run against the in-code REST stub. TODO: GDB-15242 switch to intercepts when the endpoints are available.
|
|
128
143
|
describe('Settings', () => {
|
|
129
144
|
it('should open the settings popover', () => {
|
|
130
145
|
// Given I open the Graph Navigator view.
|
|
@@ -152,14 +167,14 @@ describe('Graph Navigator', () => {
|
|
|
152
167
|
// Given I have selected a settings file.
|
|
153
168
|
GraphNavigatorSteps.visit();
|
|
154
169
|
GraphNavigatorSteps.openSettings();
|
|
155
|
-
GraphNavigatorSteps.selectSettingsFile();
|
|
156
|
-
GraphNavigatorSteps.getSelectedSettingsFile().should('exist');
|
|
170
|
+
GraphNavigatorSteps.selectSettingsFile(SETTINGS_FILE);
|
|
171
|
+
GraphNavigatorSteps.getSelectedSettingsFile(SETTINGS_FILE_NAME).should('exist');
|
|
157
172
|
|
|
158
173
|
// When I cancel it.
|
|
159
174
|
GraphNavigatorSteps.clickCancelFile();
|
|
160
175
|
|
|
161
176
|
// Then I expect the file to be removed.
|
|
162
|
-
GraphNavigatorSteps.getSettingsPopover().should('not.contain',
|
|
177
|
+
GraphNavigatorSteps.getSettingsPopover().should('not.contain', SETTINGS_FILE_NAME);
|
|
163
178
|
GraphNavigatorSteps.getUploadButton().should('be.disabled');
|
|
164
179
|
});
|
|
165
180
|
|
|
@@ -167,7 +182,7 @@ describe('Graph Navigator', () => {
|
|
|
167
182
|
// Given I have selected a settings file.
|
|
168
183
|
GraphNavigatorSteps.visit();
|
|
169
184
|
GraphNavigatorSteps.openSettings();
|
|
170
|
-
GraphNavigatorSteps.selectSettingsFile();
|
|
185
|
+
GraphNavigatorSteps.selectSettingsFile(SETTINGS_FILE);
|
|
171
186
|
|
|
172
187
|
// When I upload it.
|
|
173
188
|
GraphNavigatorSteps.clickUpload();
|
|
@@ -181,7 +196,7 @@ describe('Graph Navigator', () => {
|
|
|
181
196
|
|
|
182
197
|
// Then I expect the popover to stay open with the file still selected.
|
|
183
198
|
GraphNavigatorSteps.getSettingsPopover().should('be.visible');
|
|
184
|
-
GraphNavigatorSteps.getSelectedSettingsFile().should('exist');
|
|
199
|
+
GraphNavigatorSteps.getSelectedSettingsFile(SETTINGS_FILE_NAME).should('exist');
|
|
185
200
|
|
|
186
201
|
// When I upload and confirm.
|
|
187
202
|
GraphNavigatorSteps.clickUpload();
|
|
@@ -196,7 +211,7 @@ describe('Graph Navigator', () => {
|
|
|
196
211
|
// Given the repository has uploaded settings.
|
|
197
212
|
GraphNavigatorSteps.visit();
|
|
198
213
|
GraphNavigatorSteps.openSettings();
|
|
199
|
-
GraphNavigatorSteps.selectSettingsFile();
|
|
214
|
+
GraphNavigatorSteps.selectSettingsFile(SETTINGS_FILE);
|
|
200
215
|
GraphNavigatorSteps.clickUpload();
|
|
201
216
|
GraphNavigatorSteps.confirmDialog();
|
|
202
217
|
ToasterSteps.verifySuccess('The settings were uploaded');
|
|
@@ -87,6 +87,39 @@ describe('Import server files - Batch operations', () => {
|
|
|
87
87
|
ImportServerFilesSteps.getSelectedResources().should('have.length', 0);
|
|
88
88
|
});
|
|
89
89
|
|
|
90
|
+
it('Should select all displayed resources with the select all checkbox', () => {
|
|
91
|
+
// When I check the select all checkbox
|
|
92
|
+
ImportServerFilesSteps.toggleSelectAllResources();
|
|
93
|
+
// Then I should see all files selected
|
|
94
|
+
ImportServerFilesSteps.getSelectedResources().should('have.length', SERVER_FILES_COUNT);
|
|
95
|
+
ImportServerFilesSteps.getSelectAllCheckbox().should('be.checked');
|
|
96
|
+
// When I uncheck the select all checkbox
|
|
97
|
+
ImportServerFilesSteps.toggleSelectAllResources();
|
|
98
|
+
// Then I should see no files selected
|
|
99
|
+
ImportServerFilesSteps.getSelectedResources().should('have.length', 0);
|
|
100
|
+
ImportServerFilesSteps.getSelectAllCheckbox().should('not.be.checked');
|
|
101
|
+
// When I select a directory via the checkbox on the row
|
|
102
|
+
ImportServerFilesSteps.selectFileByName('more-files');
|
|
103
|
+
// Then the select all checkbox should be partially selected
|
|
104
|
+
ImportServerFilesSteps.getSelectAllCheckbox().should('have.prop', 'indeterminate', true);
|
|
105
|
+
// When I check the select all checkbox
|
|
106
|
+
ImportServerFilesSteps.toggleSelectAllResources();
|
|
107
|
+
// Then I should see all files selected
|
|
108
|
+
ImportServerFilesSteps.getSelectedResources().should('have.length', SERVER_FILES_COUNT);
|
|
109
|
+
ImportServerFilesSteps.getSelectAllCheckbox().should('be.checked').and('have.prop', 'indeterminate', false);
|
|
110
|
+
// When I deselect all and filter the files by name
|
|
111
|
+
ImportServerFilesSteps.toggleSelectAllResources();
|
|
112
|
+
ImportServerFilesSteps.typeInFilterField('bnodes');
|
|
113
|
+
ImportServerFilesSteps.getResources().should('have.length', 1);
|
|
114
|
+
// and check the select all checkbox
|
|
115
|
+
ImportServerFilesSteps.toggleSelectAllResources();
|
|
116
|
+
// Then only the displayed file should be selected
|
|
117
|
+
ImportServerFilesSteps.clearFilterField();
|
|
118
|
+
ImportServerFilesSteps.getResources().should('have.length', SERVER_FILES_COUNT);
|
|
119
|
+
ImportServerFilesSteps.getSelectedResources().should('have.length', 1);
|
|
120
|
+
ImportServerFilesSteps.getSelectedResourceName(0).should('contain', 'bnodes.ttl');
|
|
121
|
+
});
|
|
122
|
+
|
|
90
123
|
it('Should perform batch import and reset of selected server files with changing settings', () => {
|
|
91
124
|
// Given I have opened the server files tab
|
|
92
125
|
// When I select couple of files for import
|
|
@@ -206,4 +206,22 @@ describe('Import server files', () => {
|
|
|
206
206
|
// Then: I expect the import button be enabled, because the "JSON-LD Context" is not mandatory field.
|
|
207
207
|
ImportSettingsDialogSteps.getImportButton().should('be.enabled');
|
|
208
208
|
});
|
|
209
|
+
|
|
210
|
+
it('should send the DOCTYPE declarations option with the import settings', () => {
|
|
211
|
+
// Given: The server files tab is loaded and the import requests are intercepted
|
|
212
|
+
ImportServerFilesSteps.interceptServerFilesImport(repositoryId);
|
|
213
|
+
|
|
214
|
+
// When: I open the import dialog for a file,
|
|
215
|
+
ImportServerFilesSteps.importResourceByName('bnodes.ttl');
|
|
216
|
+
ImportSettingsDialogSteps.expandAdvancedSettings();
|
|
217
|
+
// Then: I expect DOCTYPE declarations to be disallowed by default.
|
|
218
|
+
ImportSettingsDialogSteps.getDisallowDoctypeDeclCheckbox().should('be.checked');
|
|
219
|
+
|
|
220
|
+
// When: I allow DOCTYPE declarations and start the import
|
|
221
|
+
ImportSettingsDialogSteps.allowDoctypeDeclarations();
|
|
222
|
+
ImportSettingsDialogSteps.import();
|
|
223
|
+
// Then: I expect DOCTYPE declarations to be allowed in the request.
|
|
224
|
+
ImportServerFilesSteps.waitForServerFilesImportSettings()
|
|
225
|
+
.its('parserSettings.disallowDoctypeDecl').should('equal', false);
|
|
226
|
+
});
|
|
209
227
|
});
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import {ImportUserDataSteps} from "../../steps/import/import-user-data-steps";
|
|
2
|
+
import {ImportSettingsDialogSteps} from "../../steps/import/import-settings-dialog-steps";
|
|
2
3
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"ab:email": "richard491@hotmail.com"
|
|
12
|
-
});
|
|
4
|
+
const RDF_TEXT_SNIPPET = '<urn:s1> <urn:p1> <urn:o1>.';
|
|
5
|
+
const RDF_XML_WITH_DOCTYPE_SNIPPET = '<?xml version="1.0"?>\n' +
|
|
6
|
+
'<!DOCTYPE rdf:RDF [<!ENTITY ex "http://example.com/">]>\n' +
|
|
7
|
+
'<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:ex="http://example.com/">\n' +
|
|
8
|
+
' <rdf:Description rdf:about="&ex;subject"><ex:predicate>object</ex:predicate></rdf:Description>\n' +
|
|
9
|
+
'</rdf:RDF>';
|
|
10
|
+
const RDF_XML_FORMAT = 'RDF/XML';
|
|
11
|
+
const TEXT_SNIPPET = 'Text snippet';
|
|
13
12
|
|
|
14
13
|
describe('Import user data: Import settings dialog', () => {
|
|
15
14
|
|
|
@@ -31,6 +30,78 @@ describe('Import user data: Import settings dialog', () => {
|
|
|
31
30
|
cy.deleteRepository(repositoryId);
|
|
32
31
|
});
|
|
33
32
|
|
|
33
|
+
it('Should disallow DOCTYPE declarations by default', () => {
|
|
34
|
+
// Given I have opened the import settings dialog for a text snippet
|
|
35
|
+
ImportUserDataSteps.interceptTextSnippetImport(repositoryId);
|
|
36
|
+
ImportUserDataSteps.openTextSnippetImportSettings(RDF_TEXT_SNIPPET);
|
|
37
|
+
// When I expand the advanced settings
|
|
38
|
+
ImportSettingsDialogSteps.expandAdvancedSettings();
|
|
39
|
+
// Then the DOCTYPE declarations option should be checked
|
|
40
|
+
ImportSettingsDialogSteps.getDisallowDoctypeDeclCheckbox().should('be.checked');
|
|
41
|
+
// When I start the import
|
|
42
|
+
ImportSettingsDialogSteps.import();
|
|
43
|
+
// Then DOCTYPE declarations should be disallowed in the request
|
|
44
|
+
ImportUserDataSteps.waitForTextSnippetImportSettings()
|
|
45
|
+
.its('parserSettings.disallowDoctypeDecl').should('equal', true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('Should allow DOCTYPE declarations when the option is unchecked', () => {
|
|
49
|
+
// Given I have opened the import settings dialog for a text snippet
|
|
50
|
+
ImportUserDataSteps.interceptTextSnippetImport(repositoryId);
|
|
51
|
+
ImportUserDataSteps.openTextSnippetImportSettings(RDF_TEXT_SNIPPET);
|
|
52
|
+
ImportSettingsDialogSteps.expandAdvancedSettings();
|
|
53
|
+
// When I uncheck the DOCTYPE declarations option and start the import
|
|
54
|
+
ImportSettingsDialogSteps.allowDoctypeDeclarations();
|
|
55
|
+
ImportSettingsDialogSteps.import();
|
|
56
|
+
// Then DOCTYPE declarations should be allowed in the request
|
|
57
|
+
ImportUserDataSteps.waitForTextSnippetImportSettings()
|
|
58
|
+
.its('parserSettings.disallowDoctypeDecl').should('equal', false);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('Should restore the DOCTYPE declarations option to its default', () => {
|
|
62
|
+
// Given I have opened the import settings dialog for a text snippet
|
|
63
|
+
ImportUserDataSteps.openTextSnippetImportSettings(RDF_TEXT_SNIPPET);
|
|
64
|
+
ImportSettingsDialogSteps.expandAdvancedSettings();
|
|
65
|
+
// And I have unchecked the DOCTYPE declarations option
|
|
66
|
+
ImportSettingsDialogSteps.allowDoctypeDeclarations();
|
|
67
|
+
ImportSettingsDialogSteps.getDisallowDoctypeDeclCheckbox().should('not.be.checked');
|
|
68
|
+
// When I restore the default settings
|
|
69
|
+
ImportSettingsDialogSteps.restoreDefaults();
|
|
70
|
+
// Then the DOCTYPE declarations option should be checked again
|
|
71
|
+
ImportSettingsDialogSteps.getDisallowDoctypeDeclCheckbox().should('be.checked');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('Should reject RDF/XML with a DOCTYPE declaration by default', () => {
|
|
75
|
+
// Given I have opened the import settings dialog for an RDF/XML snippet with a DOCTYPE declaration
|
|
76
|
+
ImportUserDataSteps.openTextSnippetImportSettings(RDF_XML_WITH_DOCTYPE_SNIPPET, RDF_XML_FORMAT);
|
|
77
|
+
// When I start the import with the default settings
|
|
78
|
+
ImportSettingsDialogSteps.import();
|
|
79
|
+
// Then the import should fail, because DOCTYPE declarations are disallowed
|
|
80
|
+
ImportUserDataSteps.checkImportedResourceByIndex(0, TEXT_SNIPPET, 'DOCTYPE is disallowed');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('Should import RDF/XML with a DOCTYPE declaration when DOCTYPE declarations are allowed', () => {
|
|
84
|
+
// Given I have opened the import settings dialog for an RDF/XML snippet with a DOCTYPE declaration
|
|
85
|
+
ImportUserDataSteps.openTextSnippetImportSettings(RDF_XML_WITH_DOCTYPE_SNIPPET, RDF_XML_FORMAT);
|
|
86
|
+
ImportSettingsDialogSteps.expandAdvancedSettings();
|
|
87
|
+
// When I allow DOCTYPE declarations and start the import
|
|
88
|
+
ImportSettingsDialogSteps.allowDoctypeDeclarations();
|
|
89
|
+
ImportSettingsDialogSteps.import();
|
|
90
|
+
// Then the import should succeed
|
|
91
|
+
ImportUserDataSteps.checkImportedResourceByIndex(0, TEXT_SNIPPET);
|
|
92
|
+
|
|
93
|
+
// When I open the import settings dialog for the same snippet again
|
|
94
|
+
ImportUserDataSteps.importFile(0);
|
|
95
|
+
ImportSettingsDialogSteps.expandAdvancedSettings();
|
|
96
|
+
// Then the saved DOCTYPE declarations option should be restored
|
|
97
|
+
ImportSettingsDialogSteps.getDisallowDoctypeDeclCheckbox().should('not.be.checked');
|
|
98
|
+
|
|
99
|
+
// When I restore the default settings
|
|
100
|
+
ImportSettingsDialogSteps.restoreDefaults();
|
|
101
|
+
// Then the DOCTYPE declarations option should be reset to the backend default, not to the saved value
|
|
102
|
+
ImportSettingsDialogSteps.getDisallowDoctypeDeclCheckbox().should('be.checked');
|
|
103
|
+
});
|
|
104
|
+
|
|
34
105
|
it.skip('Should prevent import when there are missing or invalid settings', () => {
|
|
35
106
|
// Given I have uploaded a file
|
|
36
107
|
|
|
@@ -9,7 +9,6 @@ describe('Import user data: URL import', () => {
|
|
|
9
9
|
const JSONLD_FORMAT = 'JSON-LD';
|
|
10
10
|
const VALID_URL_RDF_FORMAT = 'RDF/XML';
|
|
11
11
|
const RDF_ERROR_MESSAGE = 'RDF Parse Error:';
|
|
12
|
-
const IMPORT_JSONLD_URL = 'https://example.com/0007-context.jsonld';
|
|
13
12
|
|
|
14
13
|
beforeEach(() => {
|
|
15
14
|
repositoryId = 'user-import-' + Date.now();
|
|
@@ -54,17 +53,17 @@ describe('Import user data: URL import', () => {
|
|
|
54
53
|
});
|
|
55
54
|
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
it.skip('Test import file via URL successfully with Auto format selected', () => {
|
|
56
|
+
it('Test import file via URL successfully with Auto format selected', () => {
|
|
59
57
|
ImportUserDataSteps.openImportURLDialog(IMPORT_URL);
|
|
60
58
|
ImportUserDataSteps.clickImportUrlButton();
|
|
61
|
-
//
|
|
59
|
+
// wine.rdf contains a DOCTYPE declaration, which is disallowed by default
|
|
60
|
+
ImportSettingsDialogSteps.expandAdvancedSettings();
|
|
61
|
+
ImportSettingsDialogSteps.allowDoctypeDeclarations();
|
|
62
62
|
ImportSettingsDialogSteps.import();
|
|
63
63
|
ImportUserDataSteps.checkImportedResourceByIndex(0, IMPORT_URL);
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
it.skip('Test import file via URL with invalid RDF format selected', () => {
|
|
66
|
+
it('Test import file via URL with invalid RDF format selected', () => {
|
|
68
67
|
ImportUserDataSteps.openImportURLDialog(IMPORT_URL);
|
|
69
68
|
ImportUserDataSteps.selectRDFFormat(JSONLD_FORMAT);
|
|
70
69
|
ImportUserDataSteps.clickImportUrlButton();
|
|
@@ -72,11 +71,13 @@ describe('Import user data: URL import', () => {
|
|
|
72
71
|
ImportUserDataSteps.checkImportedResourceByIndex(0, IMPORT_URL, RDF_ERROR_MESSAGE);
|
|
73
72
|
});
|
|
74
73
|
|
|
75
|
-
|
|
76
|
-
it.skip('Test import file via URL successfully with valid RDF format selected', () => {
|
|
74
|
+
it('Test import file via URL successfully with valid RDF format selected', () => {
|
|
77
75
|
ImportUserDataSteps.openImportURLDialog(IMPORT_URL);
|
|
78
76
|
ImportUserDataSteps.selectRDFFormat(VALID_URL_RDF_FORMAT);
|
|
79
77
|
ImportUserDataSteps.clickImportUrlButton();
|
|
78
|
+
// wine.rdf contains a DOCTYPE declaration, which is disallowed by default
|
|
79
|
+
ImportSettingsDialogSteps.expandAdvancedSettings();
|
|
80
|
+
ImportSettingsDialogSteps.allowDoctypeDeclarations();
|
|
80
81
|
ImportSettingsDialogSteps.import();
|
|
81
82
|
ImportUserDataSteps.checkImportedResourceByIndex(0, IMPORT_URL);
|
|
82
83
|
});
|
|
@@ -63,7 +63,7 @@ describe('Operations Status Component', () => {
|
|
|
63
63
|
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
-
it('should
|
|
66
|
+
it('should link the queries operation element to its view in a new window', () => {
|
|
67
67
|
// When I visit some page and there are running operations.
|
|
68
68
|
GlobalOperationsStatusesStub.stubGlobalOperationsStatusesResponse(repositoryId);
|
|
69
69
|
HomeSteps.visitAndWaitLoader();
|
|
@@ -71,14 +71,13 @@ describe('Operations Status Component', () => {
|
|
|
71
71
|
OperationsStatusesComponentSteps.getOperationsStatusesComponent().should('exist');
|
|
72
72
|
|
|
73
73
|
OperationsStatusesComponentSteps.openOperationStatusesDialog();
|
|
74
|
-
//
|
|
75
|
-
// Then I expect to be redirected to "Query and Update monitoring" view.
|
|
74
|
+
// Then I expect "Running query" operation element to link to "Query and Update monitoring" view in a new window.
|
|
76
75
|
checkOperationElementUrl('monitor/queries', 3);
|
|
77
76
|
// Then I expect "Global Operations Component" to still be displayed.
|
|
78
77
|
OperationsStatusesComponentSteps.getOperationsStatusesComponent().should('exist');
|
|
79
78
|
});
|
|
80
79
|
|
|
81
|
-
it('should
|
|
80
|
+
it('should link the updates operation element to its view in a new window', () => {
|
|
82
81
|
// When I visit some page and there are running operations.
|
|
83
82
|
GlobalOperationsStatusesStub.stubGlobalOperationsStatusesResponse(repositoryId);
|
|
84
83
|
HomeSteps.visitAndWaitLoader();
|
|
@@ -86,14 +85,13 @@ describe('Operations Status Component', () => {
|
|
|
86
85
|
OperationsStatusesComponentSteps.getOperationsStatusesComponent().should('exist');
|
|
87
86
|
|
|
88
87
|
OperationsStatusesComponentSteps.openOperationStatusesDialog();
|
|
89
|
-
//
|
|
90
|
-
// Then I expect to be redirected to "Query and Update monitoring" view.
|
|
88
|
+
// Then I expect "Running updates" operation element to link to "Query and Update monitoring" view in a new window.
|
|
91
89
|
checkOperationElementUrl('monitor/queries', 4);
|
|
92
90
|
// Then I expect "Global Operations Component" to still be displayed.
|
|
93
91
|
OperationsStatusesComponentSteps.getOperationsStatusesComponent().should('exist');
|
|
94
92
|
});
|
|
95
93
|
|
|
96
|
-
it('should
|
|
94
|
+
it('should link the imports operation element to its view in a new window', () => {
|
|
97
95
|
// When I visit some page and there are running operations.
|
|
98
96
|
GlobalOperationsStatusesStub.stubGlobalOperationsStatusesResponse(repositoryId);
|
|
99
97
|
HomeSteps.visitAndWaitLoader();
|
|
@@ -101,14 +99,13 @@ describe('Operations Status Component', () => {
|
|
|
101
99
|
OperationsStatusesComponentSteps.getOperationsStatusesComponent().should('exist');
|
|
102
100
|
|
|
103
101
|
OperationsStatusesComponentSteps.openOperationStatusesDialog();
|
|
104
|
-
//
|
|
105
|
-
// Then I expect to be redirected to "Query and Update monitoring" view.
|
|
102
|
+
// Then I expect "Running imports" operation element to link to "Import" view in a new window.
|
|
106
103
|
checkOperationElementUrl('import', 2);
|
|
107
104
|
// Then I expect "Global Operations Component" to still be displayed.
|
|
108
105
|
OperationsStatusesComponentSteps.getOperationsStatusesComponent().should('exist');
|
|
109
106
|
});
|
|
110
107
|
|
|
111
|
-
it('should
|
|
108
|
+
it('should link the backup and restore operation element to its view in a new window', () => {
|
|
112
109
|
// When I visit some page and there are running operations.
|
|
113
110
|
GlobalOperationsStatusesStub.stubGlobalOperationsStatusesResponse(repositoryId);
|
|
114
111
|
HomeSteps.visitAndWaitLoader();
|
|
@@ -116,15 +113,14 @@ describe('Operations Status Component', () => {
|
|
|
116
113
|
OperationsStatusesComponentSteps.getOperationsStatusesComponent().should('exist');
|
|
117
114
|
|
|
118
115
|
OperationsStatusesComponentSteps.openOperationStatusesDialog();
|
|
119
|
-
//
|
|
120
|
-
// Then I expect to be redirected to "Backup and Restore" view.
|
|
116
|
+
// Then I expect "Creating backup" operation element to link to "Backup and Restore" view in a new window.
|
|
121
117
|
checkOperationElementUrl('monitor/backup-and-restore', 1);
|
|
122
118
|
// Then I expect "Global Operations Component" to still be displayed.
|
|
123
119
|
OperationsStatusesComponentSteps.getOperationsStatusesComponent().should('exist');
|
|
124
120
|
|
|
125
121
|
});
|
|
126
122
|
|
|
127
|
-
it('should
|
|
123
|
+
it('should link the cluster operation element to its view in a new window', () => {
|
|
128
124
|
// When I visit some page and there are running operations.
|
|
129
125
|
GlobalOperationsStatusesStub.stubGlobalOperationsStatusesResponse(repositoryId);
|
|
130
126
|
HomeSteps.visitAndWaitLoader();
|
|
@@ -132,8 +128,7 @@ describe('Operations Status Component', () => {
|
|
|
132
128
|
OperationsStatusesComponentSteps.getOperationsStatusesComponent().should('exist');
|
|
133
129
|
|
|
134
130
|
OperationsStatusesComponentSteps.openOperationStatusesDialog();
|
|
135
|
-
//
|
|
136
|
-
// Then I expect to be redirected to "Cluster Monitoring" view.
|
|
131
|
+
// Then I expect "Unavailable nodes" operation element to link to "Cluster Monitoring" view in a new window.
|
|
137
132
|
checkOperationElementUrl('cluster', 0);
|
|
138
133
|
// Then I expect "Global Operations Component" to still be displayed.
|
|
139
134
|
OperationsStatusesComponentSteps.getOperationsStatusesComponent().should('exist');
|
|
@@ -141,8 +136,9 @@ describe('Operations Status Component', () => {
|
|
|
141
136
|
});
|
|
142
137
|
|
|
143
138
|
function checkOperationElementUrl(expectedUrl, operationIndex = 0) {
|
|
144
|
-
BrowserStubs.
|
|
139
|
+
BrowserStubs.captureLinkClicks();
|
|
145
140
|
OperationsStatusesComponentSteps.getOperationStatuses().eq(operationIndex).click();
|
|
146
|
-
|
|
141
|
+
// The page must leave the click to the browser, which opens the link in a new window.
|
|
142
|
+
cy.get(BrowserStubs.LINK_CLICKS_ALIAS()).should('deep.equal', [{href: expectedUrl, target: '_blank', defaultPrevented: false}]);
|
|
147
143
|
}
|
|
148
144
|
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import {GraphNavigatorSteps} from '../../steps/graph-navigator-steps.js';
|
|
2
|
+
import {RepositoryPickerListSteps} from '../../steps/repository-picker-list-steps.js';
|
|
3
|
+
import {RepositoriesStubs} from '../../stubs/repositories/repositories-stubs.js';
|
|
4
|
+
|
|
5
|
+
describe('Repository picker repository information', () => {
|
|
6
|
+
let runningRepositoryId;
|
|
7
|
+
let notRunningRepositoryId;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
runningRepositoryId = 'picker-tooltip-running-' + Date.now();
|
|
11
|
+
notRunningRepositoryId = 'picker-tooltip-not-running-' + Date.now();
|
|
12
|
+
cy.createRepository({id: runningRepositoryId});
|
|
13
|
+
cy.createRepository({id: notRunningRepositoryId});
|
|
14
|
+
// A new repository is not running. Requesting its size starts it.
|
|
15
|
+
cy.initializeRepository(runningRepositoryId);
|
|
16
|
+
RepositoriesStubs.spyGetRepositorySize(runningRepositoryId, 'runningRepositorySize');
|
|
17
|
+
RepositoriesStubs.spyGetRepositorySizeCalls(notRunningRepositoryId, 'notRunningRepositorySize');
|
|
18
|
+
// No repository is selected, so the view shows the repository picker with both repositories.
|
|
19
|
+
GraphNavigatorSteps.visit();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
cy.deleteRepository(runningRepositoryId);
|
|
24
|
+
cy.deleteRepository(notRunningRepositoryId);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should show the repository id and the statement counts of a running repository', () => {
|
|
28
|
+
// When I move the keyboard focus to a running repository in the picker
|
|
29
|
+
RepositoryPickerListSteps.focusRepository(runningRepositoryId);
|
|
30
|
+
|
|
31
|
+
// Then I expect the repository information to show the repository id
|
|
32
|
+
RepositoryPickerListSteps.getRepositoryInfoPart('title').should('have.text', runningRepositoryId);
|
|
33
|
+
// And the statement counts, loaded from the server
|
|
34
|
+
cy.wait('@runningRepositorySize');
|
|
35
|
+
RepositoryPickerListSteps.getRepositoryInfoPart('total').should('contain.text', 'Total statements');
|
|
36
|
+
RepositoryPickerListSteps.getRepositoryInfoPart('explicit').should('be.visible');
|
|
37
|
+
RepositoryPickerListSteps.getRepositoryInfoPart('inferred').should('be.visible');
|
|
38
|
+
RepositoryPickerListSteps.getRepositoryInfoPart('expansion-ratio').should('be.visible');
|
|
39
|
+
RepositoryPickerListSteps.getRepositoryInfoPart('inactive').should('not.exist');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should show that the statement counts are not available when the size request fails', () => {
|
|
43
|
+
// Given the server fails to return the size of the running repository
|
|
44
|
+
RepositoriesStubs.stubGetRepositorySizeError(runningRepositoryId, 'runningRepositorySizeError');
|
|
45
|
+
|
|
46
|
+
// When I move the keyboard focus to the running repository in the picker
|
|
47
|
+
RepositoryPickerListSteps.focusRepository(runningRepositoryId);
|
|
48
|
+
|
|
49
|
+
// Then I expect the repository information to show the repository id
|
|
50
|
+
RepositoryPickerListSteps.getRepositoryInfoPart('title').should('have.text', runningRepositoryId);
|
|
51
|
+
// And, after the request has failed, that the statement counts are not available
|
|
52
|
+
cy.wait('@runningRepositorySizeError');
|
|
53
|
+
RepositoryPickerListSteps.getRepositoryInfoPart('not-available')
|
|
54
|
+
.should('contain.text', 'Size info not available.');
|
|
55
|
+
// And neither the spinner nor the statement counts
|
|
56
|
+
RepositoryPickerListSteps.getRepositoryInfoPart('loading').should('not.exist');
|
|
57
|
+
RepositoryPickerListSteps.getRepositoryInfoPart('total').should('not.exist');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should show the activation message and not request the size of a repository that is not running', () => {
|
|
61
|
+
// When I move the keyboard focus to a repository that is not running in the picker
|
|
62
|
+
RepositoryPickerListSteps.focusRepository(notRunningRepositoryId);
|
|
63
|
+
|
|
64
|
+
// Then I expect the repository information to show the repository id and the activation message
|
|
65
|
+
RepositoryPickerListSteps.getRepositoryInfoPart('title').should('have.text', notRunningRepositoryId);
|
|
66
|
+
RepositoryPickerListSteps.getRepositoryInfoPart('inactive')
|
|
67
|
+
.should('contain.text', 'Activate the repository to view statement information');
|
|
68
|
+
RepositoryPickerListSteps.getRepositoryInfoPart('total').should('not.exist');
|
|
69
|
+
// And the size of the repository is not requested, because that would start the repository
|
|
70
|
+
cy.get('@notRunningRepositorySize').should('not.have.been.called');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import HomeSteps from '../../steps/home-steps.js';
|
|
2
|
+
import {RepositorySelectorSteps} from '../../steps/repository-selector-steps.js';
|
|
3
|
+
import {RepositoriesStubs} from '../../stubs/repositories/repositories-stubs.js';
|
|
4
|
+
|
|
5
|
+
describe('Repository selector tooltip', () => {
|
|
6
|
+
let runningRepositoryId;
|
|
7
|
+
let notRunningRepositoryId;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
runningRepositoryId = 'selector-tooltip-running-' + Date.now();
|
|
11
|
+
notRunningRepositoryId = 'selector-tooltip-not-running-' + Date.now();
|
|
12
|
+
cy.createRepository({id: runningRepositoryId});
|
|
13
|
+
cy.createRepository({id: notRunningRepositoryId});
|
|
14
|
+
// A new repository is not running. Requesting its size starts it.
|
|
15
|
+
cy.initializeRepository(runningRepositoryId);
|
|
16
|
+
RepositoriesStubs.spyGetRepositorySize(runningRepositoryId, 'runningRepositorySize');
|
|
17
|
+
RepositoriesStubs.spyGetRepositorySizeCalls(notRunningRepositoryId, 'notRunningRepositorySize');
|
|
18
|
+
// No repository is selected, so both repositories are in the menu of the selector.
|
|
19
|
+
HomeSteps.visit();
|
|
20
|
+
RepositorySelectorSteps.openRepositorySelectors();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
cy.deleteRepository(runningRepositoryId);
|
|
25
|
+
cy.deleteRepository(notRunningRepositoryId);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should show the repository id and the statement counts of a running repository', () => {
|
|
29
|
+
// When I hover a running repository in the selector
|
|
30
|
+
RepositorySelectorSteps.hoverRepositoryItem(runningRepositoryId);
|
|
31
|
+
|
|
32
|
+
// Then I expect the tooltip to show the repository id
|
|
33
|
+
RepositorySelectorSteps.getRepositoryTooltipPart('title').should('have.text', runningRepositoryId);
|
|
34
|
+
// And the statement counts, loaded from the server
|
|
35
|
+
cy.wait('@runningRepositorySize');
|
|
36
|
+
RepositorySelectorSteps.getRepositoryTooltipPart('total').should('contain.text', 'Total statements');
|
|
37
|
+
RepositorySelectorSteps.getRepositoryTooltipPart('explicit').should('be.visible');
|
|
38
|
+
RepositorySelectorSteps.getRepositoryTooltipPart('inferred').should('be.visible');
|
|
39
|
+
RepositorySelectorSteps.getRepositoryTooltipPart('expansion-ratio').should('be.visible');
|
|
40
|
+
RepositorySelectorSteps.getRepositoryTooltipPart('inactive').should('not.exist');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should show the activation message and not request the size of a repository that is not running', () => {
|
|
44
|
+
// When I hover a repository that is not running in the selector
|
|
45
|
+
RepositorySelectorSteps.hoverRepositoryItem(notRunningRepositoryId);
|
|
46
|
+
|
|
47
|
+
// Then I expect the tooltip to show the repository id and the activation message
|
|
48
|
+
RepositorySelectorSteps.getRepositoryTooltipPart('title').should('have.text', notRunningRepositoryId);
|
|
49
|
+
RepositorySelectorSteps.getRepositoryTooltipPart('inactive')
|
|
50
|
+
.should('contain.text', 'Activate the repository to view statement information');
|
|
51
|
+
RepositorySelectorSteps.getRepositoryTooltipPart('total').should('not.exist');
|
|
52
|
+
// And the size of the repository is not requested, because that would start the repository
|
|
53
|
+
cy.get('@notRunningRepositorySize').should('not.have.been.called');
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -690,6 +690,8 @@ describe('User and Access', () => {
|
|
|
690
690
|
// 2. Read permission for one repository.
|
|
691
691
|
UserAndAccessSteps.openEditUserPage(maintainerUser);
|
|
692
692
|
cy.wait('@get-user');
|
|
693
|
+
// Wait until the loaded user is bound to the form, otherwise toggling a right fails.
|
|
694
|
+
UserAndAccessSteps.getUsernameField().should('have.value', maintainerUser);
|
|
693
695
|
setUserAuths({repo: readRepositoryId, read: true});
|
|
694
696
|
// 3. Write permission for one repository.
|
|
695
697
|
setUserAuths({repo: writeRepositoryId, write: true});
|
|
@@ -700,6 +702,8 @@ describe('User and Access', () => {
|
|
|
700
702
|
// When I log in as a user with repository maintain permission.
|
|
701
703
|
UserAndAccessSteps.toggleSecurity();
|
|
702
704
|
LoginSteps.loginWithUser(maintainerUser, PASSWORD);
|
|
705
|
+
// Wait for the login to complete, otherwise visiting another page aborts it.
|
|
706
|
+
cy.url().should('not.include', '/login');
|
|
703
707
|
// And navigate to the repository list view.
|
|
704
708
|
RepositorySteps.visit(false);
|
|
705
709
|
|
|
@@ -760,6 +764,8 @@ describe('User and Access', () => {
|
|
|
760
764
|
createUser(maintainerUser, PASSWORD, ROLE_USER, {maintain: true, repoName: maintainedRepositoryId});
|
|
761
765
|
UserAndAccessSteps.toggleSecurity();
|
|
762
766
|
LoginSteps.loginWithUser(maintainerUser, PASSWORD);
|
|
767
|
+
// Wait for the login to complete, otherwise visiting another page aborts it.
|
|
768
|
+
cy.url().should('not.include', '/login');
|
|
763
769
|
RepositorySteps.visit(false);
|
|
764
770
|
|
|
765
771
|
// When the user opens the edit page of the repository they can maintain.
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
@prefix gn: <http://www.ontotext.com/graph-navigator#> .
|
|
2
|
+
|
|
3
|
+
gn:settings gn:defaultPrefix """PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
|
4
|
+
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
|
|
5
|
+
PREFIX owl: <http://www.w3.org/2002/07/owl#>
|
|
6
|
+
""";
|
|
7
|
+
gn:schemaLabelProperty "rdfs:label";
|
|
8
|
+
gn:dataLabelProperty "rdfs:label";
|
|
9
|
+
gn:classTreeQuery """
|
|
10
|
+
SELECT ?class ?label ?parent
|
|
11
|
+
WHERE {
|
|
12
|
+
{
|
|
13
|
+
?class a rdfs:Class
|
|
14
|
+
} UNION {
|
|
15
|
+
?class a owl:Class
|
|
16
|
+
}
|
|
17
|
+
FILTER ISIRI(?class)
|
|
18
|
+
OPTIONAL {
|
|
19
|
+
?class rdfs:label ?label
|
|
20
|
+
${labelLanguageFilter}
|
|
21
|
+
}
|
|
22
|
+
OPTIONAL {?class rdfs:subClassOf ?parent. FILTER ISIRI(?parent)}
|
|
23
|
+
}
|
|
24
|
+
""";
|
|
25
|
+
gn:classInfoQuery """SELECT ?class ?label ?instcount WHERE {
|
|
26
|
+
VALUES(?class) {${ids}}
|
|
27
|
+
OPTIONAL {
|
|
28
|
+
?class ${schemaLabelProperty} ?label
|
|
29
|
+
${labelLanguageFilter}
|
|
30
|
+
}
|
|
31
|
+
BIND(\"\" as ?instcount)
|
|
32
|
+
}""";
|
|
33
|
+
gn:linkTypesQuery """SELECT DISTINCT ?link ?instcount ?label WHERE {
|
|
34
|
+
${linkTypesPattern}
|
|
35
|
+
OPTIONAL {
|
|
36
|
+
?link ${schemaLabelProperty} ?label
|
|
37
|
+
${labelLanguageFilter}
|
|
38
|
+
}
|
|
39
|
+
}""";
|
|
40
|
+
gn:linkTypesPattern """
|
|
41
|
+
{ ?link a rdf:Property }
|
|
42
|
+
UNION
|
|
43
|
+
{ ?link a owl:ObjectProperty }
|
|
44
|
+
BIND('' as ?instcount)
|
|
45
|
+
""";
|
|
46
|
+
gn:linkTypesInfoQuery """SELECT ?link ?label WHERE {
|
|
47
|
+
VALUES(?link) {${ids}}
|
|
48
|
+
OPTIONAL {
|
|
49
|
+
?link ${schemaLabelProperty} ?label
|
|
50
|
+
${labelLanguageFilter}
|
|
51
|
+
}
|
|
52
|
+
}""";
|
|
53
|
+
gn:propertyInfoQuery """SELECT ?property ?label WHERE {
|
|
54
|
+
VALUES(?property) {${ids}}
|
|
55
|
+
OPTIONAL {
|
|
56
|
+
?property ${schemaLabelProperty} ?label
|
|
57
|
+
${labelLanguageFilter}
|
|
58
|
+
}
|
|
59
|
+
}""";
|
|
60
|
+
gn:elementInfoQuery """
|
|
61
|
+
CONSTRUCT {
|
|
62
|
+
?inst <urn:reactodia:sparql:type> ?class .
|
|
63
|
+
?inst <urn:reactodia:sparql:label> ?label .
|
|
64
|
+
?inst ?propType ?propValue.
|
|
65
|
+
} WHERE {
|
|
66
|
+
VALUES (?inst) {${ids}}
|
|
67
|
+
OPTIONAL { ?inst a ?class }
|
|
68
|
+
OPTIONAL {
|
|
69
|
+
?inst ${dataLabelProperty} ?label
|
|
70
|
+
${labelLanguageFilter}
|
|
71
|
+
}
|
|
72
|
+
OPTIONAL {
|
|
73
|
+
${propertyConfigurations}
|
|
74
|
+
FILTER (isLiteral(?propValue))
|
|
75
|
+
${valueLanguageFilter}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
""";
|
|
79
|
+
gn:linksInfoQuery """SELECT ?source ?type ?target
|
|
80
|
+
WHERE {
|
|
81
|
+
${linkConfigurations}
|
|
82
|
+
VALUES (?source) {${sourceIris}}
|
|
83
|
+
VALUES (?target) {${targetIris}}
|
|
84
|
+
}""";
|
|
85
|
+
gn:imageQueryPattern "{ ?inst ?linkType ?image } UNION { [] ?linkType ?inst. BIND(?inst as ?image) }";
|
|
86
|
+
gn:linkTypesOfQuery """
|
|
87
|
+
SELECT DISTINCT ?link ?direction
|
|
88
|
+
WHERE {
|
|
89
|
+
${linkConfigurations}
|
|
90
|
+
}
|
|
91
|
+
""";
|
|
92
|
+
gn:linkTypesStatisticsQuery """
|
|
93
|
+
SELECT ?link ?outCount ?inCount
|
|
94
|
+
WHERE {
|
|
95
|
+
{
|
|
96
|
+
SELECT (${linkId} as ?link) (count(?outObject) as ?outCount) WHERE {
|
|
97
|
+
${linkConfigurationOut}
|
|
98
|
+
${navigateElementFilterOut}
|
|
99
|
+
} LIMIT 101
|
|
100
|
+
} {
|
|
101
|
+
SELECT (${linkId} as ?link) (count(?inObject) as ?inCount) WHERE {
|
|
102
|
+
${linkConfigurationIn}
|
|
103
|
+
${navigateElementFilterIn}
|
|
104
|
+
} LIMIT 101
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
""";
|
|
108
|
+
gn:lookupQuery """SELECT ${outerProjection} WHERE {
|
|
109
|
+
${filterInnerPrelude}
|
|
110
|
+
{
|
|
111
|
+
SELECT DISTINCT ${innerProjection} WHERE {
|
|
112
|
+
${filterByType}
|
|
113
|
+
${filterByRefElementLink}
|
|
114
|
+
${filterByText}
|
|
115
|
+
${filterAdditionalRestriction}
|
|
116
|
+
} ${orderBy} ${limit}
|
|
117
|
+
}
|
|
118
|
+
${queryTypes}
|
|
119
|
+
${queryElementInfo}
|
|
120
|
+
} ${orderBy}""";
|
|
121
|
+
gn:filterInnerPrelude "";
|
|
122
|
+
gn:filterRefElementLinkPattern "";
|
|
123
|
+
gn:filterTypePattern "?inst a ?instType. ?instType rdfs:subClassOf* ?class";
|
|
124
|
+
gn:filterElementInfoPattern """
|
|
125
|
+
OPTIONAL {?inst rdf:type ?foundClass}
|
|
126
|
+
BIND (coalesce(?foundClass, owl:Thing) as ?class)
|
|
127
|
+
OPTIONAL {
|
|
128
|
+
?inst ${dataLabelProperty} ?label
|
|
129
|
+
${labelLanguageFilter}
|
|
130
|
+
}
|
|
131
|
+
""";
|
|
132
|
+
gn:filterAdditionalRestriction "";
|
|
133
|
+
gn:openWorldLinks false;
|
|
134
|
+
gn:openWorldProperties false;
|
|
135
|
+
gn:acceptBlankNodes false;
|
|
136
|
+
gn:blankNodeStatementLimit "20"^^<http://www.w3.org/2001/XMLSchema#int>;
|
|
137
|
+
gn:filterOnlyLanguages [ a <http://www.w3.org/1999/02/22-rdf-syntax-ns#List>;
|
|
138
|
+
<http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "en";
|
|
139
|
+
<http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil>
|
|
140
|
+
];
|
|
141
|
+
gn:fullTextSearch [
|
|
142
|
+
gn:prefix "";
|
|
143
|
+
gn:queryPattern """?inst ${dataLabelProperty} ?search1
|
|
144
|
+
FILTER regex(COALESCE(str(?search1)), \"${text}\", \"i\")
|
|
145
|
+
BIND(0 as ?score)
|
|
146
|
+
""";
|
|
147
|
+
gn:extractLabel true
|
|
148
|
+
] .
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "graphdb-workbench-tests",
|
|
3
|
-
"version": "4.0.0-
|
|
3
|
+
"version": "4.0.0-TR13",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "graphdb-workbench-tests",
|
|
9
|
-
"version": "4.0.0-
|
|
9
|
+
"version": "4.0.0-TR13",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"devDependencies": {
|
|
12
12
|
"@bahmutov/cypress-code-coverage": "^2.7.2",
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@ export class CookieConsentBannerSteps {
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
static getCookieConsentButton() {
|
|
7
|
-
return this.getCookieConsentBanner().find('
|
|
7
|
+
return this.getCookieConsentBanner().find('#accept-cookie-policy');
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
static giveCookieConsent() {
|
|
@@ -12,7 +12,7 @@ export class CookieConsentBannerSteps {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
static getCookiePolicyLink() {
|
|
15
|
-
return cy.get('
|
|
15
|
+
return cy.get('#cookie-policy-link');
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
static clickCookiePolicyLink() {
|
|
@@ -64,13 +64,12 @@ export class GraphNavigatorSteps extends BaseSteps {
|
|
|
64
64
|
return this.getByTestId('graph-navigator-settings-upload').find('.p-fileupload-choose-button input[type=file]');
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
static selectSettingsFile(
|
|
67
|
+
static selectSettingsFile(file) {
|
|
68
68
|
// PrimeNG hides the file input (display: none), so force is needed to select a file on it.
|
|
69
|
-
this.getSettingsFileInput()
|
|
70
|
-
.selectFile({contents: Cypress.Buffer.from(contents), fileName, mimeType: 'text/turtle'}, {force: true});
|
|
69
|
+
this.getSettingsFileInput().selectFile(file, {force: true});
|
|
71
70
|
}
|
|
72
71
|
|
|
73
|
-
static getSelectedSettingsFile(fileName
|
|
72
|
+
static getSelectedSettingsFile(fileName) {
|
|
74
73
|
return GraphNavigatorSteps.getSettingsPopover().contains(fileName);
|
|
75
74
|
}
|
|
76
75
|
|
|
@@ -10,6 +10,14 @@ export class ImportServerFilesSteps extends ImportSteps {
|
|
|
10
10
|
return this.getView().find('.server-files-import-help');
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
static interceptServerFilesImport(repositoryId) {
|
|
14
|
+
cy.intercept('POST', `/rest/repositories/${repositoryId}/import/server`).as('importServerFiles');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static waitForServerFilesImportSettings() {
|
|
18
|
+
return cy.wait('@importServerFiles').its('request.body.importSettings');
|
|
19
|
+
}
|
|
20
|
+
|
|
13
21
|
static getSelectedTypeFilterButton() {
|
|
14
22
|
return this.getResourcesTable().find('.import-resource-type-filter .active');
|
|
15
23
|
}
|
|
@@ -64,6 +64,22 @@ export class ImportSettingsDialogSteps extends ModalDialogSteps {
|
|
|
64
64
|
this.getSettingsForm().find('input[name="preserveBNodeIDs"]').check();
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
static getDisallowDoctypeDeclCheckbox() {
|
|
68
|
+
return this.getSettingsForm().find('input[name="disallowDoctypeDecl"]');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
static allowDoctypeDeclarations() {
|
|
72
|
+
this.getDisallowDoctypeDeclCheckbox().uncheck();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
static getRestoreDefaultsButton() {
|
|
76
|
+
return this.getDialog().find('.restore-defaults-btn');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
static restoreDefaults() {
|
|
80
|
+
this.getRestoreDefaultsButton().click();
|
|
81
|
+
}
|
|
82
|
+
|
|
67
83
|
static fillContextLink(contextLink) {
|
|
68
84
|
this.getSettingsForm().find('input[name="contextLink"]').type(contextLink).should('have.value', contextLink);
|
|
69
85
|
}
|
|
@@ -92,6 +92,10 @@ class ImportSteps {
|
|
|
92
92
|
return this.getFilterField().type(query);
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
static clearFilterField() {
|
|
96
|
+
return this.getFilterField().clear();
|
|
97
|
+
}
|
|
98
|
+
|
|
95
99
|
static getTabs() {
|
|
96
100
|
return this.getView().find('.nav-tabs .nav-item');
|
|
97
101
|
}
|
|
@@ -266,7 +270,16 @@ class ImportSteps {
|
|
|
266
270
|
}
|
|
267
271
|
|
|
268
272
|
static openStatusSelectMenu() {
|
|
269
|
-
|
|
273
|
+
this.getStatusSelectMenuToggle().click();
|
|
274
|
+
return this.getStatusSelectMenu().find('.dropdown-menu .dropdown-item');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
static getSelectAllCheckbox() {
|
|
278
|
+
return this.getStatusSelectMenu().find('.select-all-checkbox');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
static toggleSelectAllResources() {
|
|
282
|
+
this.getSelectAllCheckbox().click();
|
|
270
283
|
}
|
|
271
284
|
|
|
272
285
|
static selectAllResources() {
|
|
@@ -339,7 +352,7 @@ class ImportSteps {
|
|
|
339
352
|
}
|
|
340
353
|
|
|
341
354
|
static openFileUploadDialog() {
|
|
342
|
-
this.getUploadRdfFilesButton().
|
|
355
|
+
this.getUploadRdfFilesButton().click();
|
|
343
356
|
}
|
|
344
357
|
|
|
345
358
|
static selectFile(files) {
|
|
@@ -518,7 +531,7 @@ class ImportSteps {
|
|
|
518
531
|
}
|
|
519
532
|
|
|
520
533
|
static orderBySize() {
|
|
521
|
-
ImportSteps.getSizeCell().click();
|
|
534
|
+
ImportSteps.getSizeCell().find('button').click();
|
|
522
535
|
}
|
|
523
536
|
}
|
|
524
537
|
|
|
@@ -15,9 +15,9 @@ export class ImportUserDataSteps extends ImportSteps {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
static openImportTextSnippetDialog() {
|
|
18
|
-
cy.get('#import-user .import-rdf-snippet-btn').click()
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
cy.get('#import-user .import-rdf-snippet-btn').click();
|
|
19
|
+
// Forces the popover to disappear as it covers the modal and Cypress refuses to continue
|
|
20
|
+
cy.get('#import-user .import-rdf-snippet-btn').trigger('mouseleave', {force: true});
|
|
21
21
|
this.getModal().find('#wb-import-textarea').should('be.visible');
|
|
22
22
|
return this;
|
|
23
23
|
}
|
|
@@ -34,7 +34,8 @@ export class ImportUserDataSteps extends ImportSteps {
|
|
|
34
34
|
static selectRDFFormat(rdfFormat) {
|
|
35
35
|
cy.get('.modal-footer .import-format-dropdown').within(() => {
|
|
36
36
|
cy.get('.import-format-dropdown-btn').click();
|
|
37
|
-
cy.get('.dropdown-item').contains(rdfFormat).click()
|
|
37
|
+
cy.get('.dropdown-item').contains(rdfFormat).click();
|
|
38
|
+
cy.get('.dropdown-item').contains(rdfFormat).should('not.be.visible');
|
|
38
39
|
});
|
|
39
40
|
return this;
|
|
40
41
|
}
|
|
@@ -48,6 +49,24 @@ export class ImportUserDataSteps extends ImportSteps {
|
|
|
48
49
|
cy.intercept('POST', `/rest/repositories/${repositoryId}/import/upload/url`).as('postJsonldUrl');
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
static openTextSnippetImportSettings(snippet, rdfFormat) {
|
|
53
|
+
this.openImportTextSnippetDialog();
|
|
54
|
+
this.pasteRDFTextSnippet(snippet);
|
|
55
|
+
if (rdfFormat) {
|
|
56
|
+
this.selectRDFFormat(rdfFormat);
|
|
57
|
+
}
|
|
58
|
+
this.clickImportTextSnippetButton();
|
|
59
|
+
return this;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static interceptTextSnippetImport(repositoryId) {
|
|
63
|
+
cy.intercept('POST', `/rest/repositories/${repositoryId}/import/upload/text`).as('importTextSnippet');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
static waitForTextSnippetImportSettings() {
|
|
67
|
+
return cy.wait('@importTextSnippet').its('request.body');
|
|
68
|
+
}
|
|
69
|
+
|
|
51
70
|
static getErrors() {
|
|
52
71
|
return cy.get('.text-danger');
|
|
53
72
|
}
|
|
@@ -64,30 +83,11 @@ export class ImportUserDataSteps extends ImportSteps {
|
|
|
64
83
|
return this.getModal().find('#wb-import-textarea');
|
|
65
84
|
}
|
|
66
85
|
|
|
67
|
-
static getImportFromDataRadioButton() {
|
|
68
|
-
return this.getModal().find('#wb-import-from-data');
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
static getImportInDefaultGraphRadioButton() {
|
|
72
|
-
return this.getModal().find('#wb-import-in-default-graph');
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
static getExistingDataReplacementCheckbox() {
|
|
76
|
-
return this.getModal().find('#wb-import-existing-data-replacement');
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
static getReplacedGraphsInputField() {
|
|
80
|
-
return this.getModal().find('#wb-import-replaced-graphs');
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
static getAddGraphToReplaceButton() {
|
|
84
|
-
return this.getModal().find('#wb-import-add-graph-to-replace');
|
|
85
|
-
}
|
|
86
|
-
|
|
87
86
|
//verifies that the data has been inserted in the given graph and that the new data has replaced the old one.
|
|
88
87
|
static verifyGraphData(graphName, s, p, o, c, checkForReplacedData, oldData) {
|
|
89
88
|
cy.visit('/graphs');
|
|
90
89
|
// wait a bit to give chance page loaded.
|
|
90
|
+
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
|
91
91
|
cy.wait(1000);
|
|
92
92
|
cy.get(`#export-graphs td a:contains(${graphName})`).click();
|
|
93
93
|
cy.get(`.uri-cell:contains(${s})`).should('be.visible');
|
|
@@ -24,6 +24,22 @@ export class RepositoryPickerListSteps extends BaseSteps {
|
|
|
24
24
|
return this.getRepositoryRow(repositoryId).find(this.buildTestIdAttr('repository-picker-repository-location'));
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
static getSelectRepositoryButton(repositoryId) {
|
|
28
|
+
return this.getRepositoryRow(repositoryId).find(this.buildTestIdAttr('repository-picker-select-btn'));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static focusRepository(repositoryId) {
|
|
32
|
+
this.getSelectRepositoryButton(repositoryId).focus();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static getRepositoryInfo() {
|
|
36
|
+
return cy.get('.repository-info-popover-content onto-repository-info');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static getRepositoryInfoPart(name) {
|
|
40
|
+
return this.getRepositoryInfo().find(this.buildTestIdAttr(`repository-info-${name}`));
|
|
41
|
+
}
|
|
42
|
+
|
|
27
43
|
static selectRepository(repositoryId) {
|
|
28
44
|
this.getRepositoryRow(repositoryId).click();
|
|
29
45
|
}
|
|
@@ -23,4 +23,20 @@ export class RepositorySelectorSteps {
|
|
|
23
23
|
static getSelectedRepository() {
|
|
24
24
|
return RepositorySelectorSteps.getRepositorySelectorDropdown().find('.active-repository');
|
|
25
25
|
}
|
|
26
|
+
|
|
27
|
+
static getRepositoryItem(repositoryId) {
|
|
28
|
+
return RepositorySelectorSteps.getRepositorySelectorButton(repositoryId).closest('.onto-dropdown-menu-item');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static hoverRepositoryItem(repositoryId) {
|
|
32
|
+
RepositorySelectorSteps.getRepositoryItem(repositoryId).realHover();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static getRepositoryTooltip() {
|
|
36
|
+
return cy.get('.onto-tooltip-box onto-repository-info');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static getRepositoryTooltipPart(name) {
|
|
40
|
+
return RepositorySelectorSteps.getRepositoryTooltip().find(`[data-test="repository-info-${name}"]`);
|
|
41
|
+
}
|
|
26
42
|
}
|
package/stubs/browser-stubs.js
CHANGED
|
@@ -7,6 +7,10 @@ export class BrowserStubs {
|
|
|
7
7
|
return isDefinition ? 'navigateToUrl' : '@navigateToUrl';
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
static LINK_CLICKS_ALIAS = (isDefinition = false) => {
|
|
11
|
+
return isDefinition ? 'linkClicks' : '@linkClicks';
|
|
12
|
+
}
|
|
13
|
+
|
|
10
14
|
static stubWindowOpen() {
|
|
11
15
|
cy.window().then((win) => {
|
|
12
16
|
cy.stub(win, 'open').as(BrowserStubs.WINDOW_OPEN_ALIAS(true))
|
|
@@ -18,4 +22,40 @@ export class BrowserStubs {
|
|
|
18
22
|
cy.spy(win.singleSpa, 'navigateToUrl').as(BrowserStubs.NAVIGATE_TO_URL_ALIAS(true))
|
|
19
23
|
})
|
|
20
24
|
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Checks what the browser would do with a clicked link, without following it.
|
|
28
|
+
*
|
|
29
|
+
* Use it for plain links (`<a href target="_blank">`) that the browser opens by itself. Such links do not call
|
|
30
|
+
* `window.open`, so {@link stubWindowOpen} cannot see them, and Cypress cannot follow a new window.
|
|
31
|
+
*
|
|
32
|
+
* For every clicked link it records:
|
|
33
|
+
* - `href`: the link address;
|
|
34
|
+
* - `target`: the link target, `_blank` for a new window;
|
|
35
|
+
* - `defaultPrevented`: `true` when the page handled the click itself, for example to navigate in the same window.
|
|
36
|
+
* The browser opens the link only when this is `false`.
|
|
37
|
+
*
|
|
38
|
+
* Then it stops the click, so the test stays on the same page.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* BrowserStubs.captureLinkClicks();
|
|
42
|
+
* cy.get('a.my-link').click();
|
|
43
|
+
* // The browser would open "cluster" in a new window.
|
|
44
|
+
* cy.get(BrowserStubs.LINK_CLICKS_ALIAS())
|
|
45
|
+
* .should('deep.equal', [{href: 'cluster', target: '_blank', defaultPrevented: false}]);
|
|
46
|
+
*/
|
|
47
|
+
static captureLinkClicks() {
|
|
48
|
+
cy.window().then((win) => {
|
|
49
|
+
const clicks = [];
|
|
50
|
+
win.document.addEventListener('click', (event) => {
|
|
51
|
+
const link = event.target.closest('a');
|
|
52
|
+
if (!link) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
clicks.push({href: link.getAttribute('href'), target: link.target, defaultPrevented: event.defaultPrevented});
|
|
56
|
+
event.preventDefault();
|
|
57
|
+
});
|
|
58
|
+
cy.wrap(clicks).as(BrowserStubs.LINK_CLICKS_ALIAS(true));
|
|
59
|
+
});
|
|
60
|
+
}
|
|
21
61
|
}
|
|
@@ -3,6 +3,14 @@ import {REPOSITORIES_URL} from '../../support/repository-commands';
|
|
|
3
3
|
import {GlobalOperationsStatusesStub} from '../global-operations-statuses-stub.js';
|
|
4
4
|
import repoTemplate from "../../fixtures/repo-template.json";
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Returns the pattern that matches the request for the size of a repository, on any host and with any query.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} repositoryId The id of the repository.
|
|
10
|
+
* @return {string} The pattern.
|
|
11
|
+
*/
|
|
12
|
+
const getRepositorySizeUrl = (repositoryId) => `**${REPOSITORIES_URL}/${repositoryId}/size*`;
|
|
13
|
+
|
|
6
14
|
export class RepositoriesStubs extends Stubs {
|
|
7
15
|
static stubRepositories(withDelay = 0, fixture = '/repositories/get-repositories.json') {
|
|
8
16
|
RepositoriesStubs.stubGetQueryResponse('/rest/repositories/all', fixture, 'get-all-repositories', withDelay);
|
|
@@ -24,6 +32,38 @@ export class RepositoriesStubs extends Stubs {
|
|
|
24
32
|
cy.intercept(`GET`, `${REPOSITORIES_URL}/${id}?location=`).as('getRepository');
|
|
25
33
|
}
|
|
26
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Spies on the request for the size of a repository, without changing its response.
|
|
37
|
+
*
|
|
38
|
+
* @param {string} repositoryId The id of the repository.
|
|
39
|
+
* @param {string} alias The alias of the request, to wait for it with <code>cy.wait('@alias')</code>.
|
|
40
|
+
*/
|
|
41
|
+
static spyGetRepositorySize(repositoryId, alias = 'getRepositorySize') {
|
|
42
|
+
cy.intercept('GET', getRepositorySizeUrl(repositoryId)).as(alias);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Records the requests for the size of a repository in a spy, without changing their response. Use it to check
|
|
47
|
+
* that the size was not requested: <code>cy.get('@alias').should('not.have.been.called')</code>.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} repositoryId The id of the repository.
|
|
50
|
+
* @param {string} alias The alias of the spy.
|
|
51
|
+
*/
|
|
52
|
+
static spyGetRepositorySizeCalls(repositoryId, alias = 'getRepositorySizeCalls') {
|
|
53
|
+
cy.intercept('GET', getRepositorySizeUrl(repositoryId), cy.spy().as(alias));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Makes the request for the size of a repository fail with a server error.
|
|
58
|
+
*
|
|
59
|
+
* @param {string} repositoryId The id of the repository.
|
|
60
|
+
* @param {string} alias The alias of the request, to wait for it with <code>cy.wait('@alias')</code>.
|
|
61
|
+
*/
|
|
62
|
+
static stubGetRepositorySizeError(repositoryId, alias = 'getRepositorySizeError') {
|
|
63
|
+
cy.intercept('GET', getRepositorySizeUrl(repositoryId),
|
|
64
|
+
{statusCode: 500, body: 'Internal Server Error'}).as(alias);
|
|
65
|
+
}
|
|
66
|
+
|
|
27
67
|
static stubLocations(withDelay = 0) {
|
|
28
68
|
RepositoriesStubs.stubQueryResponse('/rest/locations', '/repositories/get-locations.json', 'get-locations', withDelay);
|
|
29
69
|
}
|