Introduce the use of EventTarget interface #3

Merged
jsheunis merged 1 commit from events into main 2025-03-12 22:52:21 +00:00
7 changed files with 52 additions and 68 deletions

View file

@ -18,15 +18,16 @@ Install the library into your virtual environment and project:
npm install --save <path> npm install --save <path>
``` ```
Import and use `shacl-tulip` in your JavaScript code, for example Import and use `shacl-tulip` in your JavaScript code, for example:
```javascript ```javascript
import { ShapesDataset} from 'shacl-tulip' import { ShapesDataset} from 'shacl-tulip'
let shapesDS = new ShapesDataset(); let shapesDS = new ShapesDataset();
const fileUrl = 'https://concepts.datalad.org/s/things/v1.shacl.ttl'; const fileUrl = 'https://concepts.datalad.org/s/things/v1.shacl.ttl';
await shapesDS.loadRDF(fileUrl); // Listen for and act on the 'graphLoaded' event
shapesDS.addEventListener('graphLoaded', (event) => {
console.log('Shapes graph fully loaded:', event.detail)
console.log(shapesDS.propertyGroups) console.log(shapesDS.propertyGroups)
console.log(shapesDS.nodeShapes) console.log(shapesDS.nodeShapes)
console.log(shapesDS.nodeShapeNames) console.log(shapesDS.nodeShapeNames)
@ -38,7 +39,9 @@ console.log(shapesDS.graphLoaded)
console.log(shapesDS.prefixesLoaded) console.log(shapesDS.prefixesLoaded)
console.log(shapesDS.graph) console.log(shapesDS.graph)
console.log(shapesDS.graph.size) console.log(shapesDS.graph.size)
});
// Load the RDF
shapesDS.loadRDF(fileUrl);
``` ```
[See here](src/index.js) for all `shacl-tulip` exports, and inspect the [class code](src/classes) for class-specific functionality. [See here](src/index.js) for all `shacl-tulip` exports, and inspect the [class code](src/classes) for class-specific functionality.

View file

@ -16,6 +16,7 @@ export class ClassDataset extends RdfDataset {
quad.subject.termType !== 'BlankNode' && quad.subject.termType !== 'BlankNode' &&
quad.object.termType !== 'BlankNode' ) { quad.object.termType !== 'BlankNode' ) {
this.addQuad(quad) this.addQuad(quad)
this.dispatchEvent(new CustomEvent('quad', { detail: quad }));
} }
} }
} }

View file

@ -7,11 +7,12 @@ import formatsPretty from '@rdfjs/formats/pretty.js'
/** /**
* A class wrapping an RDF dataset (quad-store) from the `rdf-ext` library. * A class wrapping an RDF dataset (quad-store) from the `rdf-ext` library.
*/ */
export class RdfDataset { export class RdfDataset extends EventTarget {
/** /**
* Create a wrapper object for an RDF dataset a.k.a. quad-store * Create a wrapper object for an RDF dataset a.k.a. quad-store
*/ */
constructor() { constructor() {
super()
this.rdfPretty = rdf.clone(); this.rdfPretty = rdf.clone();
this.rdfPretty.formats.import(formatsPretty); this.rdfPretty.formats.import(formatsPretty);
this.prefixes = {}; this.prefixes = {};
@ -70,9 +71,11 @@ export class RdfDataset {
*/ */
onPrefixFn(prefix, ns) { onPrefixFn(prefix, ns) {
this.prefixes[prefix] = ns.value; this.prefixes[prefix] = ns.value;
this.dispatchEvent(new CustomEvent('prefix', { detail: { prefix, ns } }));
} }
onPrefixEndFn() { onPrefixEndFn() {
this.prefixesLoaded = true this.prefixesLoaded = true
this.dispatchEvent(new CustomEvent('prefixesLoaded', { detail: this.prefixes }));
} }
/** /**
@ -81,10 +84,12 @@ export class RdfDataset {
*/ */
onDataFn(quad) { onDataFn(quad) {
this.addQuad(quad) this.addQuad(quad)
this.dispatchEvent(new CustomEvent('quad', { detail: quad }));
} }
async onDataEndFn() { async onDataEndFn() {
this.serializedGraph = await this.serializeGraph() this.serializedGraph = await this.serializeGraph()
this.graphLoaded = true this.graphLoaded = true
this.dispatchEvent(new CustomEvent('graphLoaded', { detail: this.graph }));
} }
/** /**

View file

@ -35,6 +35,7 @@ export class ShapesDataset extends RdfDataset {
if (predicate === RDF.type.value && object.value === SHACL.PropertyGroup.value) { if (predicate === RDF.type.value && object.value === SHACL.PropertyGroup.value) {
this.propertyGroups[subject] = {}; this.propertyGroups[subject] = {};
} }
this.dispatchEvent(new CustomEvent('quad', { detail: quad }));
} }
async onDataEndFn() { async onDataEndFn() {
@ -86,6 +87,7 @@ export class ShapesDataset extends RdfDataset {
} }
this.serializedGraph = await this.serializeGraph() this.serializedGraph = await this.serializeGraph()
this.graphLoaded = true this.graphLoaded = true
this.dispatchEvent(new CustomEvent('graphLoaded', { detail: this.graph }));
} }
getPropertyNodeKind(class_uri, property_uri, id_uri) { getPropertyNodeKind(class_uri, property_uri, id_uri) {

View file

@ -22,22 +22,13 @@ describe('ClassDataset', () => {
expect(dataset.prefixesLoaded).toBe(false); expect(dataset.prefixesLoaded).toBe(false);
server = httpServer.createServer({ }); server = httpServer.createServer({ });
// server.listen(8080, 'localhost');
server.listen(PORT, HOST, (err) => { server.listen(PORT, HOST, (err) => {
if (err) { if (err && err.code !== 'EADDRINUSE') throw err;
if (err.code === 'EADDRINUSE') {
console.warn(`Port ${PORT} is already in use. Ignoring...`);
} else {
throw err; // Re-throw unexpected errors
}
} else {
console.log(`Test server started on http://${HOST}:${PORT}`); console.log(`Test server started on http://${HOST}:${PORT}`);
}
}); });
const fileUrl = `http://${HOST}:${PORT}/tests/mockData.ttl` const fileUrl = `http://${HOST}:${PORT}/tests/mockData.ttl`
await dataset.loadRDF(fileUrl); dataset.loadRDF(fileUrl);
await new Promise((resolve) => setTimeout(resolve, 500)); // Wait for event loop to process stream await new Promise(resolve => dataset.addEventListener('graphLoaded', resolve));
expect(dataset.graph.size).toBe(1); expect(dataset.graph.size).toBe(1);
expect(dataset.graphLoaded).toBe(true); expect(dataset.graphLoaded).toBe(true);
expect(dataset.prefixesLoaded).toBe(true); expect(dataset.prefixesLoaded).toBe(true);

View file

@ -8,8 +8,6 @@ let server;
const PORT = 8080; const PORT = 8080;
const HOST = 'localhost'; const HOST = 'localhost';
// import httpServer from 'http-server';
// Test the RdfDataset class // Test the RdfDataset class
describe('RdfDataset', () => { describe('RdfDataset', () => {
let dataset; let dataset;
@ -83,34 +81,21 @@ describe('RdfDataset', () => {
expect(serializedGraph).toContain('"example"'); expect(serializedGraph).toContain('"example"');
}); });
it('should load RDF data from a Turtle file and run associated functions', async () => { it('should load RDF data from a Turtle file and run associated functions and catch emits', async () => {
console.log(`Running RdfDataset Test ${i++}...`) console.log(`Running RdfDataset Test ${i++}...`)
server = httpServer.createServer({ }); server = httpServer.createServer({ });
// server.listen(8080, 'localhost');
server.listen(PORT, HOST, (err) => { server.listen(PORT, HOST, (err) => {
if (err) { if (err && err.code !== 'EADDRINUSE') throw err;
if (err.code === 'EADDRINUSE') {
console.warn(`Port ${PORT} is already in use. Ignoring...`);
} else {
throw err; // Re-throw unexpected errors
}
} else {
console.log(`Test server started on http://${HOST}:${PORT}`); console.log(`Test server started on http://${HOST}:${PORT}`);
}
}); });
expect(dataset.graphLoaded).toBe(false); expect(dataset.graphLoaded).toBe(false);
expect(dataset.prefixesLoaded).toBe(false); expect(dataset.prefixesLoaded).toBe(false);
const fileUrl = `http://${HOST}:${PORT}/tests/mockData.ttl` const fileUrl = `http://${HOST}:${PORT}/tests/mockData.ttl`
const graphLoadedHandler = vi.fn();
const response = await fetch(fileUrl); dataset.addEventListener('graphLoaded', graphLoadedHandler);
expect(response.status).toBe(200); dataset.loadRDF(fileUrl);
const text = await response.text(); await new Promise(resolve => dataset.addEventListener('graphLoaded', resolve));
await dataset.loadRDF(fileUrl); expect(graphLoadedHandler).toHaveBeenCalledTimes(1);
// Wait for event loop to process stream
await new Promise((resolve) => setTimeout(resolve, 500));
expect(dataset.graph.size).toBe(2); expect(dataset.graph.size).toBe(2);
expect(dataset.prefixes['ex']).toBe('http://example.com/'); expect(dataset.prefixes['ex']).toBe('http://example.com/');
expect(dataset.graphLoaded).toBe(true); expect(dataset.graphLoaded).toBe(true);
@ -121,6 +106,15 @@ describe('RdfDataset', () => {
}); });
it('should emit events for prefixes', async () => {
console.log(`Running RdfDataset Test ${i++}...`)
const prefixHandler = vi.fn();
dataset.addEventListener('prefix', prefixHandler);
dataset.onPrefixFn('ex', rdf.namedNode('http://example.com/'));
expect(prefixHandler).toHaveBeenCalledTimes(1);
expect(dataset.prefixes['ex']).toBe('http://example.com/');
});
it('should resolve blank nodes correctly', () => { it('should resolve blank nodes correctly', () => {
console.log(`Running RdfDataset Test ${i++}...`) console.log(`Running RdfDataset Test ${i++}...`)
const blankNode = rdf.blankNode(); const blankNode = rdf.blankNode();

View file

@ -15,28 +15,16 @@ describe('ShapesDataset', () => {
}); });
it('should load RDF data from Turtle file and populate all shapes-related variables', async () => { it('should load RDF data from Turtle file and populate all shapes-related variables', async () => {
expect(dataset.graphLoaded).toBe(false); expect(dataset.graphLoaded).toBe(false);
expect(dataset.prefixesLoaded).toBe(false); expect(dataset.prefixesLoaded).toBe(false);
server = httpServer.createServer({ }); server = httpServer.createServer({ });
// server.listen(8080, 'localhost');
server.listen(PORT, HOST, (err) => { server.listen(PORT, HOST, (err) => {
if (err) { if (err && err.code !== 'EADDRINUSE') throw err;
if (err.code === 'EADDRINUSE') {
console.warn(`Port ${PORT} is already in use. Ignoring...`);
} else {
throw err; // Re-throw unexpected errors
}
} else {
console.log(`Test server started on http://${HOST}:${PORT}`); console.log(`Test server started on http://${HOST}:${PORT}`);
}
}); });
const fileUrl = `http://${HOST}:${PORT}/tests/mockShapes.ttl` const fileUrl = `http://${HOST}:${PORT}/tests/mockShapes.ttl`
await dataset.loadRDF(fileUrl); dataset.loadRDF(fileUrl);
await new Promise((resolve) => setTimeout(resolve, 500)); // Wait for event loop to process stream await new Promise(resolve => dataset.addEventListener('graphLoaded', resolve));
expect(dataset.graphLoaded).toBe(true); expect(dataset.graphLoaded).toBe(true);
expect(dataset.prefixesLoaded).toBe(true); expect(dataset.prefixesLoaded).toBe(true);
expect(dataset.graph.size).toBe(318); // number of quads in the mockShapes.ttl file expect(dataset.graph.size).toBe(318); // number of quads in the mockShapes.ttl file