graphdb-workbench-tests 4.0.0-TR14 → 4.0.0-TR15

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.
@@ -5,12 +5,15 @@ import {MainMenuSteps} from '../../steps/main-menu-steps.js';
5
5
  import {YasqeSteps} from '../../steps/yasgui/yasqe-steps.js';
6
6
  import {YasrSteps} from '../../steps/yasgui/yasr-steps.js';
7
7
  import {ToasterSteps} from '../../steps/toaster-steps.js';
8
+ import {GraphNavigatorStubs} from '../../stubs/graph-navigator-stubs.js';
8
9
 
9
10
  const FILE_TO_IMPORT = 'resource-test-data.ttl';
10
11
  const SEED_RESOURCE_ENCODED = 'http:%2F%2Fexample.com%2Fontology%23CustomerLoyalty';
11
12
  const SEED_RESOURCE_LABEL = 'CustomerLoyalty';
12
13
  const SETTINGS_FILE_NAME = 'graph-navigator-settings.ttl';
13
14
  const SETTINGS_FILE = `fixtures/graph-navigator/${SETTINGS_FILE_NAME}`;
15
+ const INVALID_SETTINGS_FILE_NAME = 'graph-navigator-settings-invalid.ttl';
16
+ const INVALID_SETTINGS_FILE = `fixtures/graph-navigator/${INVALID_SETTINGS_FILE_NAME}`;
14
17
 
15
18
  const CONSTRUCT_QUERY = 'CONSTRUCT { <http://example.com/ontology#CustomerLoyalty> ?p ?o } WHERE { <http://example.com/ontology#CustomerLoyalty> ?p ?o }';
16
19
  const CONSTRUCT_TARGET_LABEL = 'Metric';
@@ -219,6 +222,43 @@ describe('Graph Navigator', () => {
219
222
  GraphNavigatorSteps.getWorkspace().should('exist');
220
223
  });
221
224
 
225
+ it('should show the validation errors of a rejected file on its row', () => {
226
+ // Given I have selected a settings file with invalid values and an unknown property.
227
+ GraphNavigatorSteps.visit();
228
+ GraphNavigatorSteps.openSettings();
229
+ GraphNavigatorSteps.selectSettingsFile(INVALID_SETTINGS_FILE);
230
+
231
+ // When I upload it.
232
+ GraphNavigatorSteps.clickUpload();
233
+ GraphNavigatorSteps.confirmDialog();
234
+
235
+ // Then I expect the errors in a table on the file row.
236
+ ToasterSteps.verifyNewToasterError('Could not upload the settings');
237
+ GraphNavigatorSteps.getSelectedSettingsFile(INVALID_SETTINGS_FILE_NAME).should('exist');
238
+ GraphNavigatorSteps.getUploadErrors().should('be.visible')
239
+ .and('contain', '3 validation errors')
240
+ .and('contain', 'Fix these properties in the file and upload it again.');
241
+ GraphNavigatorSteps.getUploadError('gn:acceptBlankNodes').should('contain', 'Must be of type boolean');
242
+ GraphNavigatorSteps.getUploadError('gn:openWorldProperties').should('contain', 'Must be of type boolean');
243
+ GraphNavigatorSteps.getUploadError('gn:unknownSetting')
244
+ .should('contain', 'Not a known property');
245
+
246
+ // When I close and reopen the settings.
247
+ GraphNavigatorSteps.closeSettings();
248
+ GraphNavigatorSteps.openSettings();
249
+
250
+ // Then I expect the file and its errors to be kept.
251
+ GraphNavigatorSteps.getSelectedSettingsFile(INVALID_SETTINGS_FILE_NAME).should('exist');
252
+ GraphNavigatorSteps.getUploadErrors().should('be.visible')
253
+ .and('contain', '3 validation errors');
254
+
255
+ // When I remove the file.
256
+ GraphNavigatorSteps.clickCancelFile();
257
+
258
+ // Then I expect the errors to be cleared.
259
+ GraphNavigatorSteps.getUploadErrors().should('not.exist');
260
+ });
261
+
222
262
  it('should ask for confirmation before resetting', () => {
223
263
  // Given the repository has uploaded settings.
224
264
  GraphNavigatorSteps.visit();
@@ -264,6 +304,48 @@ describe('Graph Navigator', () => {
264
304
  });
265
305
  });
266
306
 
307
+ describe('Autocomplete warning', () => {
308
+ beforeEach(() => {
309
+ GraphNavigatorStubs.stubSettingsWithAutocomplete();
310
+ });
311
+
312
+ it('should warn once that autocomplete is off', () => {
313
+ // Given the settings use autocomplete, but autocomplete is off.
314
+ GraphNavigatorStubs.spyEntityLookup(SEED_RESOURCE_LABEL);
315
+ GraphNavigatorSteps.visit();
316
+ cy.wait('@graph-navigator-settings');
317
+
318
+ // When I search for entities.
319
+ GraphNavigatorSteps.searchEntities(SEED_RESOURCE_LABEL);
320
+ cy.wait('@entity-lookup');
321
+
322
+ // Then I expect a warning that autocomplete is off.
323
+ ToasterSteps.getWarningToast().should('be.visible').and('contain', 'Autocomplete is OFF');
324
+
325
+ // When the warning is gone and I search for entities again.
326
+ ToasterSteps.getToast().should('not.exist');
327
+ GraphNavigatorSteps.searchEntities(SEED_RESOURCE_LABEL);
328
+ cy.wait('@entity-lookup');
329
+
330
+ // Then I expect no new warning.
331
+ ToasterSteps.getToast().should('not.exist');
332
+ });
333
+
334
+ it('should redirect to the autocomplete view when the warning is clicked', () => {
335
+ // Given the settings use autocomplete, but autocomplete is off.
336
+ GraphNavigatorSteps.visit();
337
+ cy.wait('@graph-navigator-settings');
338
+
339
+ // When I search for entities and click the warning.
340
+ GraphNavigatorSteps.searchEntities(SEED_RESOURCE_LABEL);
341
+ ToasterSteps.getWarningToast().should('be.visible').and('contain', 'Autocomplete is OFF');
342
+ ToasterSteps.getWarningToast().click();
343
+
344
+ // Then I expect to be redirected to the autocomplete view.
345
+ cy.url().should('include', '/autocomplete');
346
+ });
347
+ });
348
+
267
349
  describe('Repository switch', () => {
268
350
  let secondRepositoryId;
269
351
 
@@ -314,5 +396,38 @@ describe('Graph Navigator', () => {
314
396
  GraphNavigatorSteps.getElements().should('not.exist');
315
397
  GraphNavigatorSteps.verifyOnlyRepositoryParam(secondRepositoryId);
316
398
  });
399
+
400
+ it('should warn that autocomplete is off once per repository selection', () => {
401
+ // Given the settings use autocomplete, but autocomplete is off.
402
+ GraphNavigatorStubs.stubSettingsWithAutocomplete();
403
+ GraphNavigatorSteps.visit();
404
+ cy.wait('@graph-navigator-settings');
405
+
406
+ // When I search for entities.
407
+ GraphNavigatorSteps.searchEntities(SEED_RESOURCE_LABEL);
408
+
409
+ // Then I expect a warning that autocomplete is off.
410
+ ToasterSteps.getWarningToast().should('be.visible').and('contain', 'Autocomplete is OFF');
411
+
412
+ // When the warning is gone and I switch to another repository and search for entities.
413
+ ToasterSteps.getToast().should('not.exist');
414
+ RepositorySelectorSteps.selectRepository(secondRepositoryId);
415
+ GraphNavigatorSteps.confirmDialog();
416
+ cy.wait('@graph-navigator-settings');
417
+ GraphNavigatorSteps.searchEntities(SEED_RESOURCE_LABEL);
418
+
419
+ // Then I expect a new warning, for the new repository.
420
+ ToasterSteps.getWarningToast().should('be.visible').and('contain', 'Autocomplete is OFF');
421
+
422
+ // When the warning is gone and I switch back to the first repository and search for entities.
423
+ ToasterSteps.getToast().should('not.exist');
424
+ RepositorySelectorSteps.selectRepository(repositoryId);
425
+ GraphNavigatorSteps.confirmDialog();
426
+ cy.wait('@graph-navigator-settings');
427
+ GraphNavigatorSteps.searchEntities(SEED_RESOURCE_LABEL);
428
+
429
+ // Then I expect a new warning, because the repository was selected again.
430
+ ToasterSteps.getWarningToast().should('be.visible').and('contain', 'Autocomplete is OFF');
431
+ });
317
432
  });
318
433
  });
@@ -33,7 +33,9 @@ describe('Home page: without repositories', () => {
33
33
  HomeSteps.getRepositoryErrorsWidget().should('be.visible');
34
34
  RepositoryErrorsWidgetSteps.getInfoMessage().should('contain', 'Some functionalities are not available because you are not connected to any repository.');
35
35
  RepositoryErrorsWidgetSteps.getCreateRepositoryBtn().should('be.visible');
36
- RepositoryErrorsWidgetSteps.getShowRemoteLocationsBtn().should('be.visible');
36
+ // There are no repositories to filter.
37
+ RepositoryErrorsWidgetSteps.getRepositoryFilter().should('not.exist');
38
+ RepositoryErrorsWidgetSteps.getLocalOnlyCheckbox().should('not.exist');
37
39
  });
38
40
 
39
41
  it('should render license widget', () => {
@@ -29,7 +29,8 @@ describe('Home page: without selected repository', () => {
29
29
  HomeSteps.getRepositoryErrorsWidget().should('be.visible');
30
30
  RepositoryErrorsWidgetSteps.getInfoMessage().should('contain', 'Some functionalities are not available because you are not connected to any repository.');
31
31
  RepositoryErrorsWidgetSteps.getCreateRepositoryBtn().should('be.visible');
32
- RepositoryErrorsWidgetSteps.getShowRemoteLocationsBtn().should('be.visible');
32
+ RepositoryErrorsWidgetSteps.getRepositoryFilter().should('be.visible');
33
+ RepositoryErrorsWidgetSteps.getLocalOnlyCheckbox().should('be.visible').and('be.checked');
33
34
  });
34
35
 
35
36
  // This test is unreliable with the current implementation of the license widget.
@@ -22,7 +22,7 @@ describe('Plugins without selected repository', () => {
22
22
  const verifyInitialStateWithoutSelectedRepository = () => {
23
23
  ErrorSteps.verifyNoConnectedRepoMessage();
24
24
  PluginsSteps.getPluginsView().should('be.visible');
25
- PluginsSteps.getSearchPluginBar().should('not.be.visible');
25
+ PluginsSteps.getSearchPluginBar().should('not.exist');
26
26
  PluginsSteps.getPluginsList().should('not.exist');
27
27
  };
28
28
  })
@@ -0,0 +1,149 @@
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 "no";
135
+ gn:acceptBlankNodes "no";
136
+ gn:unknownSetting "x";
137
+ gn:blankNodeStatementLimit "20"^^<http://www.w3.org/2001/XMLSchema#int>;
138
+ gn:filterOnlyLanguages [ a <http://www.w3.org/1999/02/22-rdf-syntax-ns#List>;
139
+ <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "en";
140
+ <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil>
141
+ ];
142
+ gn:fullTextSearch [
143
+ gn:prefix "";
144
+ gn:queryPattern """?inst ${dataLabelProperty} ?search1
145
+ FILTER regex(COALESCE(str(?search1)), \"${text}\", \"i\")
146
+ BIND(0 as ?score)
147
+ """;
148
+ gn:extractLabel true
149
+ ] .
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "graphdb-workbench-tests",
3
- "version": "4.0.0-TR14",
3
+ "version": "4.0.0-TR15",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "graphdb-workbench-tests",
9
- "version": "4.0.0-TR14",
9
+ "version": "4.0.0-TR15",
10
10
  "license": "Apache-2.0",
11
11
  "devDependencies": {
12
12
  "@bahmutov/cypress-code-coverage": "^2.7.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphdb-workbench-tests",
3
- "version": "4.0.0-TR14",
3
+ "version": "4.0.0-TR15",
4
4
  "description": "Cypress tests for GraphDB workbench",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -1,7 +1,7 @@
1
1
  export class ErrorSteps {
2
2
 
3
3
  static getRepositoryErrors() {
4
- return cy.get('.card.repository-errors')
4
+ return cy.get('.repository-errors')
5
5
  }
6
6
 
7
7
  static verifyError(errorMessage) {
@@ -44,6 +44,20 @@ export class GraphNavigatorSteps extends BaseSteps {
44
44
  return GraphNavigatorSteps.getCanvas().find(`[data-element-id]`).contains(text).first();
45
45
  }
46
46
 
47
+ static getSearchInput() {
48
+ return GraphNavigatorSteps.getComponent().find('.reactodia-unified-search__search-input');
49
+ }
50
+
51
+ static getSearchSectionTab(name) {
52
+ return GraphNavigatorSteps.getComponent().find('.reactodia-unified-search__section-tab').contains(name);
53
+ }
54
+
55
+ static searchEntities(text) {
56
+ GraphNavigatorSteps.getSearchInput().click();
57
+ GraphNavigatorSteps.getSearchSectionTab('Entities').click();
58
+ GraphNavigatorSteps.getSearchInput().clear().type(`${text}{enter}`);
59
+ }
60
+
47
61
  static getSettingsButton() {
48
62
  return cy.get('[data-test="graph-navigator-settings-btn"]');
49
63
  }
@@ -73,6 +87,14 @@ export class GraphNavigatorSteps extends BaseSteps {
73
87
  return GraphNavigatorSteps.getSettingsPopover().contains(fileName);
74
88
  }
75
89
 
90
+ static getUploadErrors() {
91
+ return this.getByTestId('graph-navigator-settings-upload-errors');
92
+ }
93
+
94
+ static getUploadError(property) {
95
+ return GraphNavigatorSteps.getUploadErrors().contains('[data-test="graph-navigator-settings-upload-error"]', property);
96
+ }
97
+
76
98
  static getUploadButton() {
77
99
  return GraphNavigatorSteps.getSettingsPopover().find('.p-fileupload-upload-button');
78
100
  }
@@ -190,7 +190,7 @@ export class AclManagementSteps {
190
190
  }
191
191
 
192
192
  static getWarningIcon(index) {
193
- return this.getSavedRoleField(index).get('.ri-alert-line');
193
+ return this.getSavedRoleField(index).find('.ri-alert-line');
194
194
  }
195
195
 
196
196
  static getWarningIconTooltipText(index) {
@@ -3,6 +3,10 @@ export class ToasterSteps {
3
3
  return cy.get('.toast');
4
4
  }
5
5
 
6
+ static getWarningToast() {
7
+ return ToasterSteps.getToast().filter('.warning');
8
+ }
9
+
6
10
  static verifyTitle(title) {
7
11
  return ToasterSteps.getToast()
8
12
  .find('.toast-title')
@@ -7,8 +7,12 @@ export class RepositoryErrorsWidgetSteps {
7
7
  return RepositoryErrorsWidgetSteps.getWidget().find('.create-repository-btn');
8
8
  }
9
9
 
10
- static getShowRemoteLocationsBtn() {
11
- return RepositoryErrorsWidgetSteps.getWidget().find('.show-remote-locations-btn');
10
+ static getRepositoryFilter() {
11
+ return RepositoryErrorsWidgetSteps.getWidget().getByTestId('core-errors-repository-filter');
12
+ }
13
+
14
+ static getLocalOnlyCheckbox() {
15
+ return RepositoryErrorsWidgetSteps.getWidget().getByTestId('core-errors-local-only-checkbox');
12
16
  }
13
17
 
14
18
  static getInfoMessage() {
@@ -0,0 +1,27 @@
1
+ import {Stubs} from './stubs';
2
+
3
+ export class GraphNavigatorStubs extends Stubs {
4
+ /**
5
+ * Makes the full-text search of the Graph Navigator settings use the GraphDB autocomplete index.
6
+ */
7
+ static stubSettingsWithAutocomplete() {
8
+ cy.intercept('GET', '/rest/repositories/*/graph-navigator/settings', (req) => {
9
+ req.continue((res) => {
10
+ res.body.fullTextSearch.prefix = 'PREFIX auto: <http://www.ontotext.com/plugins/autocomplete#>';
11
+ });
12
+ }).as('graph-navigator-settings');
13
+ }
14
+
15
+ /**
16
+ * Aliases the SPARQL requests that look up entities by the given text as `entity-lookup`.
17
+ */
18
+ static spyEntityLookup(text) {
19
+ cy.intercept('POST', '/repositories/*', (req) => {
20
+ // The body is form-encoded (`query=...`), so decode it to match the SPARQL text.
21
+ const body = new URLSearchParams(String(req.body)).get('query') ?? '';
22
+ if (body.includes('SELECT ?inst ?class ?label WHERE') && body.includes(text)) {
23
+ req.alias = 'entity-lookup';
24
+ }
25
+ });
26
+ }
27
+ }