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,27 +18,30 @@ Install the library into your virtual environment and project:
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
import { ShapesDataset} from 'shacl-tulip'
let shapesDS = new ShapesDataset();
const fileUrl = 'https://concepts.datalad.org/s/things/v1.shacl.ttl';
await shapesDS.loadRDF(fileUrl);
console.log(shapesDS.propertyGroups)
console.log(shapesDS.nodeShapes)
console.log(shapesDS.nodeShapeNames)
console.log(shapesDS.nodeShapeNamesArray)
console.log(shapesDS.nodeShapeIRIs)
console.log(shapesDS.prefixes)
console.log(shapesDS.serializedGraph)
console.log(shapesDS.graphLoaded)
console.log(shapesDS.prefixesLoaded)
console.log(shapesDS.graph)
console.log(shapesDS.graph.size)
// 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.nodeShapes)
console.log(shapesDS.nodeShapeNames)
console.log(shapesDS.nodeShapeNamesArray)
console.log(shapesDS.nodeShapeIRIs)
console.log(shapesDS.prefixes)
console.log(shapesDS.serializedGraph)
console.log(shapesDS.graphLoaded)
console.log(shapesDS.prefixesLoaded)
console.log(shapesDS.graph)
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.

View file

@ -16,6 +16,7 @@ export class ClassDataset extends RdfDataset {
quad.subject.termType !== 'BlankNode' &&
quad.object.termType !== 'BlankNode' ) {
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.
*/
export class RdfDataset {
export class RdfDataset extends EventTarget {
/**
* Create a wrapper object for an RDF dataset a.k.a. quad-store
*/
constructor() {
super()
this.rdfPretty = rdf.clone();
this.rdfPretty.formats.import(formatsPretty);
this.prefixes = {};
@ -70,9 +71,11 @@ export class RdfDataset {
*/
onPrefixFn(prefix, ns) {
this.prefixes[prefix] = ns.value;
this.dispatchEvent(new CustomEvent('prefix', { detail: { prefix, ns } }));
}
onPrefixEndFn() {
this.prefixesLoaded = true
this.dispatchEvent(new CustomEvent('prefixesLoaded', { detail: this.prefixes }));
}
/**
@ -81,10 +84,12 @@ export class RdfDataset {
*/
onDataFn(quad) {
this.addQuad(quad)
this.dispatchEvent(new CustomEvent('quad', { detail: quad }));
}
async onDataEndFn() {
this.serializedGraph = await this.serializeGraph()
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) {
this.propertyGroups[subject] = {};
}
this.dispatchEvent(new CustomEvent('quad', { detail: quad }));
}
async onDataEndFn() {
@ -86,6 +87,7 @@ export class ShapesDataset extends RdfDataset {
}
this.serializedGraph = await this.serializeGraph()
this.graphLoaded = true
this.dispatchEvent(new CustomEvent('graphLoaded', { detail: this.graph }));
}
getPropertyNodeKind(class_uri, property_uri, id_uri) {

View file

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

View file

@ -8,8 +8,6 @@ let server;
const PORT = 8080;
const HOST = 'localhost';
// import httpServer from 'http-server';
// Test the RdfDataset class
describe('RdfDataset', () => {
let dataset;
@ -83,34 +81,21 @@ describe('RdfDataset', () => {
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++}...`)
server = httpServer.createServer({ });
// server.listen(8080, 'localhost');
server.listen(PORT, HOST, (err) => {
if (err) {
if (err.code === 'EADDRINUSE') {
console.warn(`Port ${PORT} is already in use. Ignoring...`);
} else {
throw err; // Re-throw unexpected errors
}
} else {
if (err && err.code !== 'EADDRINUSE') throw err;
console.log(`Test server started on http://${HOST}:${PORT}`);
}
});
expect(dataset.graphLoaded).toBe(false);
expect(dataset.prefixesLoaded).toBe(false);
const fileUrl = `http://${HOST}:${PORT}/tests/mockData.ttl`
const response = await fetch(fileUrl);
expect(response.status).toBe(200);
const text = await response.text();
await dataset.loadRDF(fileUrl);
// Wait for event loop to process stream
await new Promise((resolve) => setTimeout(resolve, 500));
const graphLoadedHandler = vi.fn();
dataset.addEventListener('graphLoaded', graphLoadedHandler);
dataset.loadRDF(fileUrl);
await new Promise(resolve => dataset.addEventListener('graphLoaded', resolve));
expect(graphLoadedHandler).toHaveBeenCalledTimes(1);
expect(dataset.graph.size).toBe(2);
expect(dataset.prefixes['ex']).toBe('http://example.com/');
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', () => {
console.log(`Running RdfDataset Test ${i++}...`)
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 () => {
expect(dataset.graphLoaded).toBe(false);
expect(dataset.prefixesLoaded).toBe(false);
server = httpServer.createServer({ });
// server.listen(8080, 'localhost');
server.listen(PORT, HOST, (err) => {
if (err) {
if (err.code === 'EADDRINUSE') {
console.warn(`Port ${PORT} is already in use. Ignoring...`);
} else {
throw err; // Re-throw unexpected errors
}
} else {
if (err && err.code !== 'EADDRINUSE') throw err;
console.log(`Test server started on http://${HOST}:${PORT}`);
}
});
const fileUrl = `http://${HOST}:${PORT}/tests/mockShapes.ttl`
await dataset.loadRDF(fileUrl);
await new Promise((resolve) => setTimeout(resolve, 500)); // Wait for event loop to process stream
dataset.loadRDF(fileUrl);
await new Promise(resolve => dataset.addEventListener('graphLoaded', resolve));
expect(dataset.graphLoaded).toBe(true);
expect(dataset.prefixesLoaded).toBe(true);
expect(dataset.graph.size).toBe(318); // number of quads in the mockShapes.ttl file