diff --git a/CHANGELOG.md b/CHANGELOG.md index c801b520..2644cc1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.1.0] - 2025-09-25 + +### Added + +- Screencast for the Onto-DESIDE use case (#228). +- Presentation as presented during the SemDev Workhop co-located with SEMANTiCS 2025 (#234). + +### Fixed + +- Link in table result header is no longer arbitrary if more than one predicate has the same object (#230.) +- Avoided "Error getting variable options..." in templated queries with indirect sources to which the user has no read access (#231). +- Corrected fetch status in templated queries with indirect sources to which the user has no read access (#232). + ## [2.0.0] - 2025-05-29 ### Added @@ -270,4 +283,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [1.6.0]: https://github.com/SolidLabResearch/miravi-a-linked-data-viewer/releases/tag/v1.6.0 [1.7.0]: https://github.com/SolidLabResearch/miravi-a-linked-data-viewer/releases/tag/v1.7.0 [2.0.0]: https://github.com/SolidLabResearch/miravi-a-linked-data-viewer/releases/tag/v2.0.0 -[Unreleased]: https://github.com/SolidLabResearch/miravi-a-linked-data-viewer/compare/v2.0.0...HEAD +[2.1.0]: https://github.com/SolidLabResearch/miravi-a-linked-data-viewer/releases/tag/v2.1.0 +[Unreleased]: https://github.com/SolidLabResearch/miravi-a-linked-data-viewer/compare/v2.1.0...HEAD diff --git a/README.md b/README.md index ea66a7e5..acd04a81 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,13 @@ Rooted in the Latin *mirari* ("to look with wonder"), it turns fragmented knowle +Miravi is fully configurable. In the illustration below, [this configuration](./main/configs/onto-deside/config.json) is at work. + +![A screencast about configs/onto-deside](doc/screencast-onto-deside.gif) + +For a more complete presentation of Miravi and description of the design choices, please have a look at our +[presentation as presented during the SemDev Workhop co-located with SEMANTiCS 2025](./doc/slides-miravi-semdev-2025.pdf). + Table of contents: * [Preface](#preface) @@ -27,9 +34,10 @@ Table of contents: * [Custom queries](#custom-queries) * [Representation Mapper](#representation-mapper) * [Advanced topics](#advanced-topics) + * [Adding your own configuration](#adding-your-own-configuration) * [Converting custom queries into common queries](#converting-custom-queries-into-common-queries) +* [Illustrations](#illustrations) * [For developers](#for-developers) - * [Adding your own configuration](#adding-your-own-configuration) * [Testing](#testing) * [Additional prerequisites](#additional-prerequisites) * [Testing the production version](#testing-the-production-version) @@ -366,6 +374,18 @@ They've already got styling matching that of `react-admin` and are easy to use. ## Advanced topics +### Adding your own configuration + +The easiest way to add your own configuration is: + +1. Get inspired by the configuration in `main/configs/demo`. +2. Choose your ``: a string obeying regex `[a-z0-9-]+`; directory `main/configs/` should not yet be in use. +3. Add your own queries in the `main/configs//public/queries` directory and in general, your own resources in the `main/configs//public` directory. +4. Add your own additional resources in `main/configs//public`, if the defaults you'll get from `main/config-defaults/public` are not satisfactory for you. +5. Write your own `main/configs//config.json` file, following the [configuration file documentation above](#configuration-file). +6. Run or build as documented above for the `demo` configuration, of course now using ``. +7. Consider a pull request to add your configuration to this repo. + ### Converting custom queries into common queries Once you have your basic configuration working, you may extend it with custom queries interactively with the query editor @@ -384,19 +404,12 @@ Follow these steps to get started: 5. **Adapt any other properties** according to your preferences. 6. **Save `main/configs//config.json`**, rerun or rebuild and refresh your browser to test. -## For developers +## Illustrations -### Adding your own configuration +* [A screencast about configs/onto-deside](doc/screencast-onto-deside.gif) +* [Presentation as presented during the SemDev Workhop co-located with SEMANTiCS 2025](./doc/slides-miravi-semdev-2025.pdf) -The easiest way to add your own configuration is: - -1. Get inspired by the configuration in `main/configs/demo`. -2. Choose your ``: a string obeying regex `[a-z0-9-]+`; directory `main/configs/` should not yet be in use. -3. Add your own queries in the `main/configs//public/queries` directory and in general, your own resources in the `main/configs//public` directory. -4. Add your own additional resources in `main/configs//public`, if the defaults you'll get from `main/config-defaults/public` are not satisfactory for you. -5. Write your own `main/configs//config.json` file, following the [configuration file documentation above](#configuration-file). -6. Run or build as documented above for the `demo` configuration, of course now using ``. -7. Consider a pull request to add your configuration to this repo. +## For developers ### Testing diff --git a/doc/screencast-onto-deside.gif b/doc/screencast-onto-deside.gif new file mode 100644 index 00000000..15e2ee41 Binary files /dev/null and b/doc/screencast-onto-deside.gif differ diff --git a/doc/slides-miravi-semdev-2025.pdf b/doc/slides-miravi-semdev-2025.pdf new file mode 100644 index 00000000..938a7bf2 Binary files /dev/null and b/doc/slides-miravi-semdev-2025.pdf differ diff --git a/main/configs/test/config.json b/main/configs/test/config.json index 286171ef..4fd5b2ce 100644 --- a/main/configs/test/config.json +++ b/main/configs/test/config.json @@ -444,6 +444,38 @@ "queryLocation": "/sourceQueries/index_example_common_lt.rq" } }, + { + "id": "9081", + "queryGroupId": "gr-test", + "queryLocation": "component_material_one_variable.rq", + "name": "Component and materials - 1 variable (indirect source & indirect variables; one unauthorized source)", + "description": "Query components (including details about materials) with the sources obtained from index files and variables from the sources. One unauthorized indirect source, to check lenient while getting variable options.", + "indirectVariables": { + "queryLocations": [ + "variableQueries/components_name_variable.rq" + ] + }, + "sourcesIndex": { + "url": "http://localhost:8080/example/index-example-with-unauthorized-source-lt", + "queryLocation": "/sourceQueries/index_example_common_lt.rq" + } + }, + { + "id": "9082", + "queryGroupId": "gr-test", + "queryLocation": "component_material_one_variable.rq", + "name": "Component and materials - 1 variable (indirect source & indirect variables; no indirect sources found)", + "description": "Query components (including details about materials) with zero sources obtained from index files and variables from the sources.", + "indirectVariables": { + "queryLocations": [ + "variableQueries/components_name_variable.rq" + ] + }, + "sourcesIndex": { + "url": "http://localhost:8080/example/index-example-texon-only-lt", + "queryLocation": "/sourceQueries/index_example_common_lt_bad.rq" + } + }, { "id": "9090", "queryGroupId": "gr-test", @@ -549,6 +581,32 @@ "http://localhost:8080/example/favourite-musicians-file-does-not-exist" ] } + }, + { + "id": "9200", + "queryGroupId": "gr-test", + "queryLocation": "schema_name.rq", + "name": "A query that looks for names that are the object of predicate schema:name", + "description": "Tests a single link in the 'name' column header.", + "comunicaContext": { + "sources": [ + "http://localhost:8080/example/names-labels" + ], + "lenient": true + } + }, + { + "id": "9201", + "queryGroupId": "gr-test", + "queryLocation": "schema_name_rdfs_label.rq", + "name": "A query that looks for names that are both the objects of predicates schema:name and rdfs:label", + "description": "Tests two links in the 'name' column header.", + "comunicaContext": { + "sources": [ + "http://localhost:8080/example/names-labels" + ], + "lenient": true + } } ] } \ No newline at end of file diff --git a/main/configs/test/public/queries/schema_name.rq b/main/configs/test/public/queries/schema_name.rq new file mode 100644 index 00000000..2f393ab0 --- /dev/null +++ b/main/configs/test/public/queries/schema_name.rq @@ -0,0 +1,6 @@ +PREFIX schema: +PREFIX rdfs: + +SELECT ?name WHERE { + ?s schema:name ?name. +} \ No newline at end of file diff --git a/main/configs/test/public/queries/schema_name_rdfs_label.rq b/main/configs/test/public/queries/schema_name_rdfs_label.rq new file mode 100644 index 00000000..1cf2ce2b --- /dev/null +++ b/main/configs/test/public/queries/schema_name_rdfs_label.rq @@ -0,0 +1,7 @@ +PREFIX schema: +PREFIX rdfs: + +SELECT ?name WHERE { + ?s schema:name ?name. + ?s rdfs:label ?name. +} \ No newline at end of file diff --git a/main/configs/test/public/queries/sourceQueries/index_example_common_lt_bad.rq b/main/configs/test/public/queries/sourceQueries/index_example_common_lt_bad.rq new file mode 100644 index 00000000..fee1195e --- /dev/null +++ b/main/configs/test/public/queries/sourceQueries/index_example_common_lt_bad.rq @@ -0,0 +1,5 @@ +PREFIX rdfs: + +SELECT DISTINCT ?source WHERE { + ?s rdfs:seeAlso_hihihahahoho ?source. +} diff --git a/main/src/components/ActionBar/SourceFetchStatusIcon/SourceFetchStatusIcon.jsx b/main/src/components/ActionBar/SourceFetchStatusIcon/SourceFetchStatusIcon.jsx index 1cfd3786..82278640 100644 --- a/main/src/components/ActionBar/SourceFetchStatusIcon/SourceFetchStatusIcon.jsx +++ b/main/src/components/ActionBar/SourceFetchStatusIcon/SourceFetchStatusIcon.jsx @@ -1,3 +1,4 @@ +import PendingIcon from '@mui/icons-material/Pending'; import CheckIcon from '@mui/icons-material/Check'; import CancelIcon from "@mui/icons-material/Cancel"; import RemoveCircleOutlineIcon from '@mui/icons-material/RemoveCircleOutline'; @@ -14,7 +15,13 @@ import comunicaEngineWrapper from '../../../comunicaEngineWrapper/comunicaEngine function SourceFetchStatusIcon({ source }) { const status = comunicaEngineWrapper.getFetchStatusNumber(source); - if (comunicaEngineWrapper.getFetchSuccess(source)) { + if (comunicaEngineWrapper.getFetchSuccess(source) === undefined) { + return ( + + + + ); + } else if (comunicaEngineWrapper.getFetchSuccess(source)) { return ( diff --git a/main/src/components/ListResultTable/QueryResultList/TableHeader/TableHeader.jsx b/main/src/components/ListResultTable/QueryResultList/TableHeader/TableHeader.jsx index 75fff512..c8fb5acf 100644 --- a/main/src/components/ListResultTable/QueryResultList/TableHeader/TableHeader.jsx +++ b/main/src/components/ListResultTable/QueryResultList/TableHeader/TableHeader.jsx @@ -60,16 +60,18 @@ function TableHeader({ children }) { } {!!variableOntology && variableOntology[child.props.source] && ( - *": { verticalAlign: "middle" } }} - > - - + variableOntology[child.props.source].map((link) => ( + *": { verticalAlign: "middle" } }} + > + + + )) )} {sort.field === child.props.source && ( <> diff --git a/main/src/comunicaEngineWrapper/comunicaEngineWrapper.js b/main/src/comunicaEngineWrapper/comunicaEngineWrapper.js index 9c7686a4..1bd45173 100644 --- a/main/src/comunicaEngineWrapper/comunicaEngineWrapper.js +++ b/main/src/comunicaEngineWrapper/comunicaEngineWrapper.js @@ -36,6 +36,7 @@ class ComunicaEngineWrapper { this._fetchSuccess = {}; this._fetchStatusNumber = {}; this._underlyingFetchFunction = undefined; + // LOG console.log(`Comunica engines reset`); } getFetchSuccess(arg) { @@ -168,13 +169,14 @@ class ComunicaEngineWrapper { * @param {array} httpProxies - array of httpProxy definitions */ _prepareQuery(context, httpProxies) { - // avoid faulty fetch status for sources cached in Comunica - for (const source of context.sources) { - this._fetchSuccess[source] = true; - } - this._underlyingFetchFunction = fetch; + // note: there is no need to preset this._fetchSuccess[source] here; + // if Comunica caches, we still have the previous value if (getDefaultSession().info.isLoggedIn) { this._underlyingFetchFunction = authFetch; + // LOG console.log(`Using authFetch as underlying fetch function`); + } else { + this._underlyingFetchFunction = fetch; + // LOG console.log(`Using fetch as underlying fetch function`); } context.fetch = ComunicaEngineWrapper._getWrappedFetchFunction(this._underlyingFetchFunction, httpProxies, this); } diff --git a/main/src/dataProvider/SparqlDataProvider.js b/main/src/dataProvider/SparqlDataProvider.js index 5af53290..00e54ffa 100644 --- a/main/src/dataProvider/SparqlDataProvider.js +++ b/main/src/dataProvider/SparqlDataProvider.js @@ -189,10 +189,10 @@ function replaceVariables(rawText, variableValues) { } /** - * Given a query and an object, this function returns the predicate of the object in the query. + * Given a query and an object, this function returns the predicates of the object in the query. * * @param {object} query - the parsed query in which the predicate is to be looked for. - * @returns {object} an object with the variable as key and the predicate as value. + * @returns {object} an object with the variable as key and as value an array of predicates. */ function findPredicates(query) { const ontologyMapper = {}; @@ -204,7 +204,11 @@ function findPredicates(query) { if (part.triples) { for (const triple of part.triples) { if (triple.predicate.termType !== "Variable") { - ontologyMapper[triple.object.value] = triple.predicate.value; + if (!ontologyMapper[triple.object.value]) { + ontologyMapper[triple.object.value] = [triple.predicate.value]; + } else if (!ontologyMapper[triple.object.value].includes(triple.predicate.value)) { + ontologyMapper[triple.object.value].push(triple.predicate.value); + } } } } @@ -293,22 +297,19 @@ async function getSourcesFromSourcesIndex(sourcesIndex, httpProxies) { const bindingsStream = await comunicaEngineWrapper.queryBindings(queryStringIndexSource, { lenient: true, sources: [sourcesIndex.url] }, httpProxies, { engine: "link-traversal" }); - await new Promise((resolve, reject) => { - bindingsStream.on('data', (bindings) => { - // LOG console.log(`getSourcesFromSourcesIndex bindings: ${bindings.toString()}`); - for (const term of bindings.values()) { // check for 1st value - const source = term.value; - if (!sourcesList.includes(source)) { - // LOG console.log(`getSourcesFromSourcesIndex adding source: ${source}`); - sourcesList.push(source); - } - // we only want the first term, whatever the variable's name is (note: a for ... of loop seems the only way to access it) - break; + const bindingsArray = await bindingsStream.toArray(); + for (const bindings of bindingsArray) { + // LOG console.log(`getSourcesFromSourcesIndex bindings: ${bindings.toString()}`); + for (const term of bindings.values()) { // check for 1st value + const source = term.value; + if (!sourcesList.includes(source)) { + // LOG console.log(`getSourcesFromSourcesIndex adding source: ${source}`); + sourcesList.push(source); } - }); - bindingsStream.on('end', resolve); - bindingsStream.on('error', reject); - }); + // we only want the first term, whatever the variable's name is (note: a for ... of loop seems the only way to access it) + break; + } + } } catch (error) { throw new Error(`Error adding sources from index: ${error.message}`); @@ -380,6 +381,9 @@ async function getVariableOptions(query) { } // END duplicated chunk of code + if (query.comunicaContext.sources.length === 0) { + throw new Error(`Error getting variable options... No sources found.`); + } let variableOptions; let queryStringList = []; @@ -417,35 +421,35 @@ async function getVariableOptions(query) { try { for (const queryString of queryStringList) { + // queryBindings with lenient true to avoid errors with unauthorized sources const bindingsStream = await comunicaEngineWrapper.queryBindings(queryString, - { sources: query.comunicaContext.sources }, query.httpProxies); - await new Promise((resolve, reject) => { - bindingsStream.on('data', (bindings) => { - // LOG console.log(`getVariableOptions bindings: ${bindings.toString()}`); - for (const [variable, term] of bindings) { - const name = variable.value; - if (!variableOptions[name]) { - variableOptions[name] = []; - } - const variableValue = termToSparqlCompatibleString(term); - if (variableValue && !variableOptions[name].includes(variableValue)) { - // LOG console.log(`getVariableOptions adding variable option for '${name}': ${variableValue}`); - variableOptions[name].push(variableValue); - } + { lenient: true, sources: query.comunicaContext.sources }, query.httpProxies); + // convert stream to array (works when no bindings found - handling events 'data', 'end' and 'error' does not work when no bindints found) + const bindingsArray = await bindingsStream.toArray(); + for (const bindings of bindingsArray) { + // LOG console.log(`getVariableOptions bindings: ${bindings.toString()}`); + for (const [variable, term] of bindings) { + const name = variable.value; + if (!variableOptions[name]) { + variableOptions[name] = []; } - }); - bindingsStream.on('end', resolve); - bindingsStream.on('error', reject); - }); + const variableValue = termToSparqlCompatibleString(term); + if (variableValue && !variableOptions[name].includes(variableValue)) { + // LOG console.log(`getVariableOptions adding variable option for '${name}': ${variableValue}`); + variableOptions[name].push(variableValue); + } + } + } } } catch (error) { throw new Error(`Error getting variable options... ${error.message}`); } - if (variableOptions == {}) { - throw new Error(`Error getting variable options... The variable options are empty`); + if (Object.keys(variableOptions).length === 0) { + throw new Error(`Error getting variable options... No variable options found.`); } + return variableOptions; } diff --git a/main/src/version.js b/main/src/version.js index c76f51c2..1eb7321a 100644 --- a/main/src/version.js +++ b/main/src/version.js @@ -1,5 +1,5 @@ // Maintain the version string on the development branch: // - during development: set to unreleased; // - right before merging to the main branch for making a new release: set to the new release tag (see https://github.com/SolidLabResearch/miravi-a-linked-data-viewer/tags). -const version = "v2.0.0"; +const version = "v2.1.0"; export default version; diff --git a/test/cypress/e2e/column-header.cy.js b/test/cypress/e2e/column-header.cy.js index c1d41d91..048488db 100644 --- a/test/cypress/e2e/column-header.cy.js +++ b/test/cypress/e2e/column-header.cy.js @@ -1,9 +1,23 @@ describe("Column header", () => { - it("Variables link to ontology", () => { + it("One link to ontology", () => { cy.visit("/"); - cy.contains("Example queries").click(); - cy.contains("A query about musicians").click(); + cy.contains("For testing only").click(); + cy.contains("A query that looks for names that are the object of predicate schema:name").click(); cy.contains("Finished in:"); - cy.get('a[href="http://schema.org/name"]'); - }) + cy.get('th').contains("name").parent().within(() => { + cy.get('a[href="http://schema.org/name"]'); + }); + }); + + it("Two links to ontology", () => { + cy.visit("/"); + cy.contains("For testing only").click(); + cy.contains("A query that looks for names that are both the objects of predicates schema:name and rdfs:label").click(); + cy.contains("Finished in:"); + cy.get('th').contains("name").parent().within(() => { + cy.get('a[href="http://schema.org/name"]'); + cy.get('a[href="http://www.w3.org/2000/01/rdf-schema#label"]'); + }); + }); + }); diff --git a/test/cypress/e2e/fetch-status.cy.js b/test/cypress/e2e/fetch-status.cy.js index b30edeca..c29422bb 100644 --- a/test/cypress/e2e/fetch-status.cy.js +++ b/test/cypress/e2e/fetch-status.cy.js @@ -11,16 +11,16 @@ describe("Fetch Status", () => { // Check if the public and restricted sources appear cy.get('[aria-label="Sources info"]').click(); - cy.contains("http://localhost:8080/example/favourite-books"); - cy.contains("http://localhost:8080/example/wish-list"); - // Check if the correct icons appear - cy.get('[aria-label="Authentication required"]').should("exist"); - cy.get('[aria-label="Unauthorized"]').should("exist"); - - cy.get('[aria-label="No authentication required"]').should("exist"); - cy.get('[aria-label="Fetch was successful"]').should("exist"); - + cy.contains("http://localhost:8080/example/wish-list").parent().within(() => { + cy.get('[aria-label="No authentication required"]').should("exist"); + cy.get('[aria-label="Fetch was successful"]').should("exist"); + }); + cy.contains("http://localhost:8080/example/favourite-books").parent().within(() => { + cy.get('[aria-label="Authentication required"]').should("exist"); + cy.get('[aria-label="Unauthorized"]').should("exist"); + }); + cy.get('[aria-label="Not fetched"]').should("not.exist"); // Checking that a non-authorized book is not appearing cy.contains("It Ends With Us").should("not.exist"); @@ -58,16 +58,16 @@ describe("Fetch Status", () => { // Check if the public and restricted sources appear cy.get('[aria-label="Sources info"]').click(); - cy.contains("http://localhost:8080/example/favourite-books"); - cy.contains("http://localhost:8080/example/wish-list"); - // Check if the correct icons appear - cy.get('[aria-label="Authentication required"]').should("exist"); - cy.get('[aria-label="Fetch Failed"]').should("not.exist"); - cy.get('[aria-label="Unauthorized"]').should("not.exist"); - - cy.get('[aria-label="No authentication required"]').should("exist"); - cy.get('[aria-label="Fetch was successful"]').should("exist"); + cy.contains("http://localhost:8080/example/wish-list").parent().within(() => { + cy.get('[aria-label="No authentication required"]').should("exist"); + cy.get('[aria-label="Fetch was successful"]').should("exist"); + }); + cy.contains("http://localhost:8080/example/favourite-books").parent().within(() => { + cy.get('[aria-label="Authentication required"]').should("exist"); + cy.get('[aria-label="Fetch was successful"]').should("exist"); + }); + cy.get('[aria-label="Not fetched"]').should("not.exist"); // Checking that you see authorized books cy.contains("It Ends With Us"); @@ -85,15 +85,45 @@ describe("Fetch Status", () => { // Check if the good and bad sources appear cy.get('[aria-label="Sources info"]').click(); - // First fetch should be a success - cy.contains("http://localhost:8080/example/favourite-musicians"); - cy.get('[aria-label="No authentication required"]').should("exist"); - cy.get('[aria-label="Unauthorized"]').should("not.exist"); - cy.get('[aria-label="Fetch was successful"]').should("exist"); - - // the bad source should fail to fetch - cy.contains("http://www.example.com/fetch-failure-but-query-success"); - cy.get('[aria-label="Fetch failed"]').should("exist"); + // Check if the correct icons appear + cy.contains("http://localhost:8080/example/favourite-musicians").parent().within(() => { + cy.get('[aria-label="No authentication required"]').should("exist"); + cy.get('[aria-label="Fetch was successful"]').should("exist"); + }); + cy.contains("http://www.example.com/fetch-failure-but-query-success").parent().within(() => { + cy.get('[aria-label="Uncertain if authentication is required"]').should("exist"); + cy.get('[aria-label="Fetch failed"]').should("exist"); + }); + cy.get('[aria-label="Not fetched"]').should("not.exist"); + + }); + + it("Fetch data with no authenticated user, indirect source & indirect variables and one unauthorized source", () => { + + cy.visit("/"); + cy.contains("For testing only").click(); + cy.contains("Component and materials - 1 variable (indirect source & indirect variables; one unauthorized source)").click(); + + // Fill in the form + cy.get('.ra-input-componentName').click(); + cy.get('li').contains('Component 1').click(); + + // Comfirm query + cy.get('button[type="submit"]').click(); + + // Check if the public and restricted sources appear + cy.get('[aria-label="Sources info"]').click(); + + // Check if the correct icons appear + cy.contains("http://localhost:8080/example/boms").parent().within(() => { + cy.get('[aria-label="No authentication required"]').should("exist"); + cy.get('[aria-label="Fetch was successful"]').should("exist"); + }); + cy.contains("http://localhost:8080/example/favourite-books").parent().within(() => { + cy.get('[aria-label="Authentication required"]').should("exist"); + cy.get('[aria-label="Unauthorized"]').should("exist"); + }); + cy.get('[aria-label="Not fetched"]').should("not.exist"); }); diff --git a/test/cypress/e2e/templated-query.cy.js b/test/cypress/e2e/templated-query.cy.js index b7046b26..0ffd443b 100644 --- a/test/cypress/e2e/templated-query.cy.js +++ b/test/cypress/e2e/templated-query.cy.js @@ -265,6 +265,41 @@ describe("Templated query", () => { }); + it("Indirect with 1 variable and sources from indexfile, with one unauthorized source", () => { + + cy.visit("/"); + cy.contains("For testing only").click(); + cy.contains("Component and materials - 1 variable (indirect source & indirect variables; one unauthorized source)").click(); + + // Fill in the form + cy.get('.ra-input-componentName').click(); + cy.get('li').contains('Component 1').click(); + + // Comfirm query + cy.get('button[type="submit"]').click(); + + // Check that it is correctly loaded with and only the correct data appears + cy.contains("Finished in:"); + + cy.get('.column-componentName').find('span').contains("Component 1").should("exist"); + cy.get('.column-materialName').find('span').contains("Material 2").should("exist"); + cy.get('.column-materialName').find('span').contains("Material 1").should("exist"); + + cy.get('.column-componentName').find('span').contains("Component 2").should("not.exist"); + cy.get('.column-componentName').find('span').contains("Component 3").should("not.exist"); + cy.get('.column-materialName').find('span').contains("Material 6").should("not.exist"); + + }); + + it("Indirect with 1 variable and sources from indexfile, no indirect sources found", () => { + + cy.visit("/"); + cy.contains("For testing only").click(); + cy.contains("Component and materials - 1 variable (indirect source & indirect variables; no indirect sources found)").click(); + + cy.contains("Error getting variable options...").should("exist");; + }); + it("Indirect with 2 variables and sources from indexfile", () => { cy.visit("/"); cy.contains("For testing only").click(); diff --git a/test/initial-pod-data/names-labels$.ttl b/test/initial-pod-data/names-labels$.ttl new file mode 100644 index 00000000..c84ca716 --- /dev/null +++ b/test/initial-pod-data/names-labels$.ttl @@ -0,0 +1,8 @@ +PREFIX schema: +PREFIX rdfs: +PREFIX ex: + +ex:1 schema:name "A name, given with predicates schema:name and rdfs:label" ; + rdfs:label "A name, given with predicates schema:name and rdfs:label" . +ex:2 schema:name "A name, given with predicate schema:name" ; + rdfs:label "A name, given with predicate rdfs:label" . diff --git a/test/initial-pod-data/names-labels.acl b/test/initial-pod-data/names-labels.acl new file mode 100644 index 00000000..bd3221fe --- /dev/null +++ b/test/initial-pod-data/names-labels.acl @@ -0,0 +1,14 @@ +@prefix acl: . +@prefix foaf: . + +<#public> + a acl:Authorization; + acl:accessTo <./names-labels>; + acl:agentClass foaf:Agent; + acl:mode acl:Read. + +<#owner> + a acl:Authorization; + acl:accessTo <./names-labels>; + acl:agent ; + acl:mode acl:Read, acl:Write, acl:Control.