From d9e894e1048dfae1a239718fd1f5ec0d1b936df4 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Sun, 28 Jul 2024 10:03:08 +1000 Subject: [PATCH 01/12] WIP: add full dataset functionality --- src/N3Store.js | 166 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 164 insertions(+), 2 deletions(-) diff --git a/src/N3Store.js b/src/N3Store.js index 660a25fe..ba1fa5a8 100644 --- a/src/N3Store.js +++ b/src/N3Store.js @@ -476,7 +476,7 @@ export default class N3Store { // Setting any field to `undefined` or `null` indicates a wildcard. forEach(callback, subject, predicate, object, graph) { this.some(quad => { - callback(quad); + callback(quad, this); return false; }, subject, predicate, object, graph); } @@ -488,7 +488,7 @@ export default class N3Store { let some = false; const every = !this.some(quad => { some = true; - return !callback(quad); + return !callback(quad, this); }, subject, predicate, object, graph); return some && every; } @@ -775,6 +775,168 @@ export default class N3Store { return lists; } + /** + * Returns `true` if the current instance is a superset of the given dataset; differently put: if the given dataset + * is a subset of, is contained in the current dataset. + * + * Blank Nodes will be normalized. + */ + addAll(quads) { + if (Array.isArray(quads)) + this.addQuads(quads); + else { + for (const quad of quads) + this.add(quad); + } + return this; + } + + + /** + * Returns `true` if the current instance is a superset of the given dataset; differently put: if the given dataset + * is a subset of, is contained in the current dataset. + * + * Blank Nodes will be normalized. + */ + contains(other) { + return other.every(quad => this.has(quad)); + } + + /** + * This method removes the quads in the current instance that match the given arguments. + * + * The logic described in {@link https://rdf.js.org/dataset-spec/#quad-matching|Quad Matching} is applied for each + * quad in this dataset to select the quads which will be deleted. + * + * @param subject The optional exact subject to match. + * @param predicate The optional exact predicate to match. + * @param object The optional exact object to match. + * @param graph The optional exact graph to match. + */ + deleteMatches(subject, predicate, object, graph) { + this.removeMatches(subject, predicate, object, graph); + return this; + } + + /** + * Returns a new dataset that contains all quads from the current dataset, not included in the given dataset. + */ + difference(other) { + const store = new N3Store(); + for (const quad of this) + if (!other.has(quad)) + store.add(quad); + return store; + } + + /** + * Returns true if the current instance contains the same graph structure as the given dataset. + * + * Blank Nodes will be normalized. + */ + equals(other) { + return this.size === other.size && this.contains(other); + } + + /** + * Creates a new dataset with all the quads that pass the test implemented by the provided `iteratee`. + * + * This method is aligned with Array.prototype.filter() in ECMAScript-262. + */ + filter(iteratee) { + const store = new N3Store(); + for (const quad of this) + if (iteratee(quad, this)) + store.add(quad); + return store; + } + + /** + * Returns a new dataset containing alls quads from the current dataset that are also included in the given dataset. + */ + intersection(other) { + const store = new N3Store(); + for (const quad of this) + if (other.has(quad)) + store.add(quad); + return store; + } + + /** + * Returns a new dataset containing all quads returned by applying `iteratee` to each quad in the current dataset. + */ + map(iteratee) { + const store = new N3Store(); + for (const quad of this) + store.add(iteratee(quad, this)); + return store; + } + + /** + * This method calls the `iteratee` on each `quad` of the `Dataset`. The first time the `iteratee` is called, the + * `accumulator` value is the `initialValue` or, if not given, equals to the first quad of the `Dataset`. The return + * value of the `iteratee` is used as `accumulator` value for the next calls. + * + * This method returns the return value of the last `iteratee` call. + * + * This method is aligned with `Array.prototype.reduce()` in ECMAScript-262. + */ + reduce(callback, initialValue) { + let accumulator = initialValue; + for (const quad of this) { + if (accumulator === undefined) + accumulator = quad; + else + accumulator = callback(accumulator, quad, this); + } + return accumulator; + } + + /** + * Returns the set of quads within the dataset as a host language native sequence, for example an `Array` in + * ECMAScript-262. + * + * Since a `Dataset` is an unordered set, the order of the quads within the returned sequence is arbitrary. + */ + toArray() { + return this.getQuads(); + } + + /** + * Returns an N-Quads string representation of the dataset, preprocessed with + * {@link https://json-ld.github.io/normalization/spec/|RDF Dataset Normalization} algorithm. + */ + toCanonical() { + throw new Error('not implemented'); + } + + /** + * Returns a stream that contains all quads of the dataset. + */ + toStream() { + return this.match(); + } + + /** + * Returns an N-Quads string representation of the dataset. + * + * No prior normalization is required, therefore the results for the same quads may vary depending on the `Dataset` + * implementation. + */ + toString() { + throw new Error('not implemented'); + } + + /** + * Returns a new `Dataset` that is a concatenation of this dataset and the quads given as an argument. + */ + union(quads) { + const store = new N3Store(); + store.addAll(this); + store.addAll(quads); + return store; + } + // ### Store is an iterable. // Can be used where iterables are expected: for...of loops, array spread operator, // `yield*`, and destructuring assignment (order is not guaranteed). From fdf8f81d6efe83e66d5b012d4d287240c68f87d0 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Sun, 28 Jul 2024 14:18:43 +1000 Subject: [PATCH 02/12] WIP --- src/N3Store.js | 9 ++++++-- src/N3Writer.js | 2 +- test/N3Store-test.js | 53 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/src/N3Store.js b/src/N3Store.js index ba1fa5a8..30ad9792 100644 --- a/src/N3Store.js +++ b/src/N3Store.js @@ -3,6 +3,7 @@ import { Readable } from 'readable-stream'; import { default as N3DataFactory, termToId, termFromId } from './N3DataFactory'; import namespaces from './IRIs'; import { isDefaultGraph } from './N3Util'; +import N3Writer from './N3Writer'; // ## Constructor export default class N3Store { @@ -799,7 +800,11 @@ export default class N3Store { * Blank Nodes will be normalized. */ contains(other) { - return other.every(quad => this.has(quad)); + console.log('containes called on') + return other.every(quad => { + console.log('every called on', quad); + return this.has(quad) + }); } /** @@ -924,7 +929,7 @@ export default class N3Store { * implementation. */ toString() { - throw new Error('not implemented'); + return (new N3Writer).quadsToString(this); } /** diff --git a/src/N3Writer.js b/src/N3Writer.js index cdce5e49..828d927a 100644 --- a/src/N3Writer.js +++ b/src/N3Writer.js @@ -15,7 +15,7 @@ const escape = /["\\\t\n\r\b\f\u0000-\u0019\ud800-\udbff]/, '\n': '\\n', '\r': '\\r', '\b': '\\b', '\f': '\\f', }; -// ## Placeholder class to represent already pretty-printed terms +// ## Placeholder class to represent already pretty-pnew Error('not implemented')rinted terms class SerializedTerm extends Term { // Pretty-printed nodes are not equal to any other node // (e.g., [] does not equal []) diff --git a/test/N3Store-test.js b/test/N3Store-test.js index 15b954d4..92468199 100644 --- a/test/N3Store-test.js +++ b/test/N3Store-test.js @@ -2011,6 +2011,59 @@ describe('Store', () => { } ); }); + + describe('RDF/JS Dataset Methods', () => { + let q1, q2, q3, store, store1, store2; + + beforeEach(() => { + q1 = new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o1')); + q2 = new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o2')); + q3 = new Quad(new NamedNode('s2'), new NamedNode('p2'), new NamedNode('03')); + store = new Store(q1); + store1 = new Store([ q1, q2 ]); + store2 = new Store([ q2, q3]); + }); + + describe('#contains', () => { + it('store is contained in store1 and store2', () => { + expect(store.contains(store)).toBe(true); + // expect(store1.contains(store)).toBe(true); + // expect(store2.contains(store)).toBe(true); + }); + + // it('should return false for a non-existing quad', () => { + // expect(store.contains(store1)).toBe(false); + // expect(store.contains(store2)).toBe(false); + // }); + }); + + describe('#union', () => { + it('should return a new store with the union of the quads', () => { + const store = store1.union(store2); + expect(store1.size).toEqual(2); + expect(store2.size).toEqual(2); + expect(store.size).toEqual(3); + }); + }); + + describe('#difference', () => { + it('should return a new store with the difference of the quads', () => { + const store = store1.difference(store2); + expect(store1.size).toEqual(2); + expect(store2.size).toEqual(2); + expect(store.size).toEqual(1); + }); + }); + + describe('#intersection', () => { + it('should return a new store with the intersection of the quads', () => { + const store = store1.intersection(store2); + expect(store1.size).toEqual(2); + expect(store2.size).toEqual(2); + expect(store.size).toEqual(1); + }); + }); + }); }); function alwaysTrue() { return true; } From 9b0367cac8a1358971c999039979b69d0f9541d7 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Sun, 28 Jul 2024 16:53:25 +1000 Subject: [PATCH 03/12] chore: add tests to datasetcore functionality --- src/N3Store.js | 176 +++++++++++++++++++++++----------- src/N3Writer.js | 7 +- test/N3Store-test.js | 224 ++++++++++++++++++++++++++++++++++++------- 3 files changed, 312 insertions(+), 95 deletions(-) diff --git a/src/N3Store.js b/src/N3Store.js index 30ad9792..a0eba9e6 100644 --- a/src/N3Store.js +++ b/src/N3Store.js @@ -486,12 +486,7 @@ export default class N3Store { // and returns `true` if it returns truthy for all them. // Setting any field to `undefined` or `null` indicates a wildcard. every(callback, subject, predicate, object, graph) { - let some = false; - const every = !this.some(quad => { - some = true; - return !callback(quad, this); - }, subject, predicate, object, graph); - return some && every; + return !this.some(quad => !callback(quad, this), subject, predicate, object, graph); } // ### `some` executes the callback on all quads, @@ -800,11 +795,7 @@ export default class N3Store { * Blank Nodes will be normalized. */ contains(other) { - console.log('containes called on') - return other.every(quad => { - console.log('every called on', quad); - return this.has(quad) - }); + return other.every(quad => this.has(quad)); } /** @@ -819,7 +810,8 @@ export default class N3Store { * @param graph The optional exact graph to match. */ deleteMatches(subject, predicate, object, graph) { - this.removeMatches(subject, predicate, object, graph); + for (const quad of this.match(subject, predicate, object, graph)) + this.removeQuad(quad); return this; } @@ -827,11 +819,7 @@ export default class N3Store { * Returns a new dataset that contains all quads from the current dataset, not included in the given dataset. */ difference(other) { - const store = new N3Store(); - for (const quad of this) - if (!other.has(quad)) - store.add(quad); - return store; + return this.filter(quad => !other.has(quad)); } /** @@ -840,7 +828,7 @@ export default class N3Store { * Blank Nodes will be normalized. */ equals(other) { - return this.size === other.size && this.contains(other); + return other === this || (this.size === other.size && this.contains(other)); } /** @@ -860,16 +848,12 @@ export default class N3Store { * Returns a new dataset containing alls quads from the current dataset that are also included in the given dataset. */ intersection(other) { - const store = new N3Store(); - for (const quad of this) - if (other.has(quad)) - store.add(quad); - return store; + return this.filter(quad => other.has(quad)); } - /** - * Returns a new dataset containing all quads returned by applying `iteratee` to each quad in the current dataset. - */ + /** + * Returns a new dataset containing all quads returned by applying `iteratee` to each quad in the current dataset. + */ map(iteratee) { const store = new N3Store(); for (const quad of this) @@ -877,15 +861,15 @@ export default class N3Store { return store; } - /** - * This method calls the `iteratee` on each `quad` of the `Dataset`. The first time the `iteratee` is called, the - * `accumulator` value is the `initialValue` or, if not given, equals to the first quad of the `Dataset`. The return - * value of the `iteratee` is used as `accumulator` value for the next calls. - * - * This method returns the return value of the last `iteratee` call. - * - * This method is aligned with `Array.prototype.reduce()` in ECMAScript-262. - */ + /** + * This method calls the `iteratee` on each `quad` of the `Dataset`. The first time the `iteratee` is called, the + * `accumulator` value is the `initialValue` or, if not given, equals to the first quad of the `Dataset`. The return + * value of the `iteratee` is used as `accumulator` value for the next calls. + * + * This method returns the return value of the last `iteratee` call. + * + * This method is aligned with `Array.prototype.reduce()` in ECMAScript-262. + */ reduce(callback, initialValue) { let accumulator = initialValue; for (const quad of this) { @@ -897,44 +881,44 @@ export default class N3Store { return accumulator; } - /** - * Returns the set of quads within the dataset as a host language native sequence, for example an `Array` in - * ECMAScript-262. - * - * Since a `Dataset` is an unordered set, the order of the quads within the returned sequence is arbitrary. - */ + /** + * Returns the set of quads within the dataset as a host language native sequence, for example an `Array` in + * ECMAScript-262. + * + * Since a `Dataset` is an unordered set, the order of the quads within the returned sequence is arbitrary. + */ toArray() { return this.getQuads(); } - /** - * Returns an N-Quads string representation of the dataset, preprocessed with - * {@link https://json-ld.github.io/normalization/spec/|RDF Dataset Normalization} algorithm. - */ + /** + * Returns an N-Quads string representation of the dataset, preprocessed with + * {@link https://json-ld.github.io/normalization/spec/|RDF Dataset Normalization} algorithm. + */ toCanonical() { throw new Error('not implemented'); } - /** - * Returns a stream that contains all quads of the dataset. - */ + /** + * Returns a stream that contains all quads of the dataset. + */ toStream() { return this.match(); } - /** - * Returns an N-Quads string representation of the dataset. - * - * No prior normalization is required, therefore the results for the same quads may vary depending on the `Dataset` - * implementation. - */ + /** + * Returns an N-Quads string representation of the dataset. + * + * No prior normalization is required, therefore the results for the same quads may vary depending on the `Dataset` + * implementation. + */ toString() { - return (new N3Writer).quadsToString(this); + return (new N3Writer()).quadsToString(this); } - /** - * Returns a new `Dataset` that is a concatenation of this dataset and the quads given as an argument. - */ + /** + * Returns a new `Dataset` that is a concatenation of this dataset and the quads given as an argument. + */ union(quads) { const store = new N3Store(); store.addAll(this); @@ -984,6 +968,82 @@ class DatasetCoreAndReadableStream extends Readable { this.push(null); } + addAll(quads) { + return this.filtered.addAll(quads); + } + + contains(other) { + return this.filtered.contains(other); + } + + deleteMatches(subject, predicate, object, graph) { + return this.filtered.deleteMatches(subject, predicate, object, graph); + } + + difference(other) { + return this.filtered.difference(other); + } + + equals(other) { + return this.filtered.equals(other); + } + + every(callback, subject, predicate, object, graph) { + return this.filtered.every(callback, subject, predicate, object, graph); + } + + filter(iteratee) { + return this.filtered.filter(iteratee); + } + + forEach(callback, subject, predicate, object, graph) { + return this.filtered.forEach(callback, subject, predicate, object, graph); + } + + import(stream) { + return this.filtered.import(stream); + } + + intersection(other) { + return this.filtered.intersection(other); + } + + map(iteratee) { + return this.filtered.map(iteratee); + } + + some(callback, subject, predicate, object, graph) { + return this.filtered.some(callback, subject, predicate, object, graph); + } + + toCanonical() { + return this.filtered.toCanonical(); + } + + toStream() { + return this._filtered ? + this._filtered.toStream() + : this.n3Store.match(this.subject, this.predicate, this.object, this.graph); + } + + union(quads) { + return this._filtered ? + this._filtered.union(quads) + : this.n3Store.match(this.subject, this.predicate, this.object, this.graph).addAll(quads); + } + + toArray() { + return this._filtered ? this._filtered.toArray() : this.n3Store.getQuads(this.subject, this.predicate, this.object, this.graph); + } + + reduce(callback, initialValue) { + return this.filtered.reduce(callback, initialValue); + } + + toString() { + return (new N3Writer()).quadsToString(this); + } + add(quad) { return this.filtered.add(quad); } diff --git a/src/N3Writer.js b/src/N3Writer.js index 828d927a..cd78e434 100644 --- a/src/N3Writer.js +++ b/src/N3Writer.js @@ -130,9 +130,10 @@ export default class N3Writer { // ### `quadsToString` serializes an array of quads as a string quadsToString(quads) { - return quads.map(t => { - return this.quadToString(t.subject, t.predicate, t.object, t.graph); - }).join(''); + let quadsString = ''; + for (const quad of quads) + quadsString += this.quadToString(quad.subject, quad.predicate, quad.object, quad.graph); + return quadsString; } // ### `_encodeSubject` represents a subject diff --git a/test/N3Store-test.js b/test/N3Store-test.js index 92468199..7a0872ff 100644 --- a/test/N3Store-test.js +++ b/test/N3Store-test.js @@ -34,19 +34,6 @@ describe('Store', () => { expect(store.getQuads()).toHaveLength(0); }); - describe('when importing a stream of 2 quads', () => { - beforeAll(done => { - const stream = new ArrayReader([ - new Quad(new NamedNode('s1'), new NamedNode('p2'), new NamedNode('o2')), - new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o1')), - ]); - const events = store.import(stream); - events.on('end', done); - }); - - it('should have size 2', () => { expect(store.size).toEqual(2); }); - }); - describe('when removing a stream of 2 quads', () => { beforeAll(done => { const stream = new ArrayReader([ @@ -88,13 +75,13 @@ describe('Store', () => { describe('every', () => { describe('with no parameters and a callback always returning true', () => { - it('should return false', () => { - expect(store.every(alwaysTrue, null, null, null, null)).toBe(false); + it('should return true on empty set', () => { + expect(store.every(alwaysTrue, null, null, null, null)).toBe(true); }); }); describe('with no parameters and a callback always returning false', () => { - it('should return false', () => { - expect(store.every(alwaysFalse, null, null, null, null)).toBe(false); + it('should return true on empty set', () => { + expect(store.every(alwaysFalse, null, null, null, null)).toBe(true); }); }); }); @@ -1227,6 +1214,19 @@ describe('Store', () => { let count = 3; function thirdTimeFalse() { return count-- !== 0; } + describe('empty store always returns false', () => { + const emptyStore = new Store(); + it('should return false', () => { + expect(emptyStore.some(alwaysTrue, null, null, null, null)).toBe(false); + expect(emptyStore.some(alwaysFalse, null, null, null, null)).toBe(false); + expect(emptyStore.some(alwaysTrue, new NamedNode('s3'), null, null, null)).toBe(false); + expect(emptyStore.some(alwaysFalse, new NamedNode('s3'), null, null, null)).toBe(false); + + expect(emptyStore.some(null, null, null, null, null)).toBe(false); + expect(emptyStore.some(null, new NamedNode('s3'), null, null, null)).toBe(false); + expect(emptyStore.some(null, new NamedNode('s3'), null, null, null)).toBe(false); + }); + }); describe('with no parameters and a callback always returning true', () => { it('should return true', () => { expect(store.some(alwaysTrue, null, null, null, null)).toBe(true); @@ -2012,31 +2012,46 @@ describe('Store', () => { ); }); - describe('RDF/JS Dataset Methods', () => { - let q1, q2, q3, store, store1, store2; + describe.each([true, false])('RDF/JS Dataset Methods [DatasetCoreAndReadableStream: %s]', match => { + let q1, q2, q3, store, store1, store2, empty; beforeEach(() => { q1 = new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o1')); q2 = new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o2')); - q3 = new Quad(new NamedNode('s2'), new NamedNode('p2'), new NamedNode('03')); - store = new Store(q1); - store1 = new Store([ q1, q2 ]); - store2 = new Store([ q2, q3]); + q3 = new Quad(new NamedNode('s2'), new NamedNode('p2'), new NamedNode('o3')); + empty = new Store(); + store = new Store([q1]); + store1 = new Store([q1, q2]); + store2 = new Store([q1, q3]); + + if (match) { + empty = store2.match(new NamedNode('sn')); + store = store2.match(new NamedNode('s1')); + store1 = store1.match(); + store2 = store2.match(); + } }); describe('#contains', () => { - it('store is contained in store1 and store2', () => { + it('empty set is contained in all sets', () => { + expect(empty.contains(empty)).toBe(true); + expect(store.contains(empty)).toBe(true); + expect(store1.contains(empty)).toBe(true); + expect(store2.contains(empty)).toBe(true); + }); + + it('store is contained in store, store1 and store2', () => { expect(store.contains(store)).toBe(true); - // expect(store1.contains(store)).toBe(true); - // expect(store2.contains(store)).toBe(true); + expect(store1.contains(store)).toBe(true); + expect(store2.contains(store)).toBe(true); + }); + + it('should return false for a non-existing quad', () => { + expect(store.contains(store1)).toBe(false); + expect(store.contains(store2)).toBe(false); }); - - // it('should return false for a non-existing quad', () => { - // expect(store.contains(store1)).toBe(false); - // expect(store.contains(store2)).toBe(false); - // }); }); - + describe('#union', () => { it('should return a new store with the union of the quads', () => { const store = store1.union(store2); @@ -2045,7 +2060,7 @@ describe('Store', () => { expect(store.size).toEqual(3); }); }); - + describe('#difference', () => { it('should return a new store with the difference of the quads', () => { const store = store1.difference(store2); @@ -2054,7 +2069,7 @@ describe('Store', () => { expect(store.size).toEqual(1); }); }); - + describe('#intersection', () => { it('should return a new store with the intersection of the quads', () => { const store = store1.intersection(store2); @@ -2063,6 +2078,147 @@ describe('Store', () => { expect(store.size).toEqual(1); }); }); + + describe('#deleteMatches', () => { + it('should delete all quads if pattern is null', () => { + store1.deleteMatches(null, null, null); + expect(store1.size).toEqual(0); + }); + + it('should delete matching quads', () => { + store1.deleteMatches(q1.subject, q1.predicate, q1.object); + expect(store1.size).toEqual(1); + }); + }); + + describe('#addAll', () => { + it('should add quads to the store', () => { + store1.addAll([q3]); + expect(store1.size).toEqual(3); + store1.addAll([q3]); + expect(store1.size).toEqual(3); + }); + }); + + describe('#deleteMatches', () => { + it('should delete matching quads', () => { + store1.deleteMatches(q1.subject, q1.predicate, q1.object); + expect(store1.size).toEqual(1); + }); + }); + + describe('#map', () => { + it('should map over quads', () => { + const quads = store1.map(quad => quad); + expect(quads.size).toEqual(2); + expect(quads.contains(store1)); + expect(store1.contains(quads)); + }); + }); + + describe('#reduce', () => { + it('should reduce over quads', () => { + expect(store1.reduce((acc, quad) => acc + 1, 0)).toEqual(2); + expect(store1.reduce((acc, quad) => acc + quad.subject.value.length, 0)).toEqual(4); + }); + }); + + describe('#toArray', () => { + it('should convert to an array', () => { + const quads = store1.toArray(); + expect(quads).toHaveLength(2); + expect(quads[0]).toEqual(q1); + expect(quads[1]).toEqual(q2); + }); + }); + + describe('#toStream', () => { + it('should convert to a stream', done => { + const stream = store1.toStream(); + stream.once('data', quad => { + expect(quad).toEqual(q1); + stream.once('data', quad => { + expect(quad).toEqual(q2); + done(); + }); + }); + }); + }); + + describe('#toString', () => { + it('should convert to a string', () => { + expect(store1.toString()).toEqual(' .\n .\n'); + }); + }); + + describe('#toCanonical', () => { + it('should convert to a canonical string', () => { + expect(() => store1.toCanonical()).toThrowError('not implemented'); + }); + }); + + describe('#filter', () => { + it('should filter quads by subject', () => { + const quads = store1.filter(quad => quad.subject.value === 's1'); + expect(quads.size).toEqual(2); + expect(quads.contains(store1)).toEqual(true); + expect(store1.contains(quads)).toEqual(true); + }); + + it('should filter quads by object', () => { + const quads = store1.filter(quad => quad.object.value === 'o1'); + expect(quads.size).toEqual(1); + expect(store1.contains(quads)).toEqual(true); + expect(quads.has(q1)).toEqual(true); + }); + }); + + describe('#import', () => { + beforeEach(done => { + const stream = new ArrayReader([ + new Quad(new NamedNode('s1'), new NamedNode('p2'), new NamedNode('o2')), + new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o1')), + ]); + const events = empty.import(stream); + events.on('end', done); + }); + + it('should have size 2', () => { expect(empty.size).toEqual(2); }); + }); + + describe('#forEach', () => { + it('should iterate over quads', () => { + let count = 0; + store1.forEach(quad => { + count++; + expect(quad).toEqual(count === 1 ? q1 : q2); + }); + expect(count).toEqual(2); + }); + }); + + describe('#equals', () => { + it('should be equal to itself', () => { + expect(empty.equals(empty)).toBe(true); + expect(store.equals(store)).toBe(true); + expect(store1.equals(store1)).toBe(true); + expect(store2.equals(store2)).toBe(true); + }); + + it('should be equal to a new store containing the same elements', () => { + expect(store.equals(new Store([q1]))).toBe(true); + expect(store1.equals(new Store([q1, q2]))).toBe(true); + expect(empty.equals(new Store())).toBe(true); + expect(store2.equals(new Store([q1, q3]))).toBe(true); + }); + + it('should not be equal to a store with different elements', () => { + expect(empty.equals(store)).toBe(false); + expect(store.equals(new Store([q2]))).toBe(false); + expect(store1.equals(new Store([q1]))).toBe(false); + expect(store2.equals(new Store([q1]))).toBe(false); + }); + }); }); }); From 23d9fdd0b832cde29e83e3a75ef7188742503ba2 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Sun, 28 Jul 2024 17:18:38 +1000 Subject: [PATCH 04/12] chore: improve reduce tests --- src/N3Store.js | 11 ++++------- test/N3Store-test.js | 19 ++++++++++++++++++- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/N3Store.js b/src/N3Store.js index a0eba9e6..495ecc35 100644 --- a/src/N3Store.js +++ b/src/N3Store.js @@ -871,13 +871,10 @@ export default class N3Store { * This method is aligned with `Array.prototype.reduce()` in ECMAScript-262. */ reduce(callback, initialValue) { - let accumulator = initialValue; - for (const quad of this) { - if (accumulator === undefined) - accumulator = quad; - else - accumulator = callback(accumulator, quad, this); - } + const iter = this.readQuads(); + let accumulator = initialValue === undefined ? iter.next().value : initialValue; + for (const quad of iter) + accumulator = callback(accumulator, quad, this); return accumulator; } diff --git a/test/N3Store-test.js b/test/N3Store-test.js index 7a0872ff..32f36e08 100644 --- a/test/N3Store-test.js +++ b/test/N3Store-test.js @@ -2118,8 +2118,10 @@ describe('Store', () => { describe('#reduce', () => { it('should reduce over quads', () => { - expect(store1.reduce((acc, quad) => acc + 1, 0)).toEqual(2); + expect(store1.reduce((acc, _) => acc + 1, 0)).toEqual(2); expect(store1.reduce((acc, quad) => acc + quad.subject.value.length, 0)).toEqual(4); + expect(store1.reduce((acc, _) => acc, 0)).toEqual(0); + expect(store.reduce((acc, _) => acc)).toEqual(q1); }); }); @@ -2219,6 +2221,21 @@ describe('Store', () => { expect(store2.equals(new Store([q1]))).toBe(false); }); }); + + describe('#some', () => { + it('should return true if any quad passes the test', () => { + expect(store1.some(quad => quad.subject.value === 's1')).toBe(true); + expect(store1.some(quad => quad.subject.value === 's2')).toBe(false); + }); + + it('should return false if no quad passes the test', () => { + expect(store1.some(quad => quad.subject.value === 's2')).toBe(false); + }); + + it('should return false on the empty set', () => { + expect(empty.some(quad => true)).toBe(false); + }); + }); }); }); From 7c98497cd26aa1753fd4b1deda88a7a7be6d3985 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Sun, 28 Jul 2024 17:23:28 +1000 Subject: [PATCH 05/12] chore: obtain 100% test coverage --- test/N3Store-test.js | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/test/N3Store-test.js b/test/N3Store-test.js index 32f36e08..744dd0a8 100644 --- a/test/N3Store-test.js +++ b/test/N3Store-test.js @@ -2012,7 +2012,7 @@ describe('Store', () => { ); }); - describe.each([true, false])('RDF/JS Dataset Methods [DatasetCoreAndReadableStream: %s]', match => { + describe.each([true, false, 'instantiated'])('RDF/JS Dataset Methods [DatasetCoreAndReadableStream: %s]', match => { let q1, q2, q3, store, store1, store2, empty; beforeEach(() => { @@ -2030,6 +2030,13 @@ describe('Store', () => { store1 = store1.match(); store2 = store2.match(); } + + if (match === 'instantiated') { + empty.size; + store.size; + store1.size; + store2.size; + } }); describe('#contains', () => { @@ -2135,15 +2142,11 @@ describe('Store', () => { }); describe('#toStream', () => { - it('should convert to a stream', done => { - const stream = store1.toStream(); - stream.once('data', quad => { - expect(quad).toEqual(q1); - stream.once('data', quad => { - expect(quad).toEqual(q2); - done(); - }); - }); + it('should convert to a stream', () => { + expect(arrayifyStream(store2.toStream())).resolves.toEqual([q1, q3]); + expect(arrayifyStream(store1.toStream())).resolves.toEqual([q1, q2]); + expect(arrayifyStream(store.toStream())).resolves.toEqual([q1]); + expect(arrayifyStream(empty.toStream())).resolves.toEqual([]); }); }); From 0f9959b705bd7180867f4ef3f4f0e5871900b423 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Sun, 28 Jul 2024 17:26:04 +1000 Subject: [PATCH 06/12] chore: update docs --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8ca5a6f7..ac76e32f 100644 --- a/README.md +++ b/README.md @@ -304,8 +304,8 @@ for (const quad of store.match(namedNode('http://ex.org/Mickey'), null, null)) console.log(quad); ``` -### [`DatasetCore` Interface](https://rdf.js.org/dataset-spec/#datasetcore-interface) -This store adheres to the `DatasetCore` interface which exposes the following properties +### [`Dataset` Interface](https://rdf.js.org/dataset-spec/#dataset-interface) +This store adheres to the `Datase` interface which exposes the following properties Attributes: - `size` — A non-negative integer that specifies the number of quads in the set. @@ -318,7 +318,7 @@ Methods: - `[Symbol.iterator]` — Implements the iterator protocol to allow iteration over all `quads` in the dataset as in the example above. ### Addition and deletion of quads -The store provides the following manipulation methods in addition to implementing the standard [`DatasetCore` Interface](https://rdf.js.org/dataset-spec/#datasetcore-interface) +The store provides the following manipulation methods in addition to implementing the standard [`Dataset` Interface](https://rdf.js.org/dataset-spec/#dataset-interface) ([documentation](http://rdfjs.github.io/N3.js/docs/N3Store.html)): - `addQuad` to insert one quad - `addQuads` to insert an array of quads From 174556b01e0d88bd0b8cf32d4251e9a155aecf9d Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Sun, 28 Jul 2024 17:35:49 +1000 Subject: [PATCH 07/12] chore: fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ac76e32f..df6b1854 100644 --- a/README.md +++ b/README.md @@ -305,7 +305,7 @@ for (const quad of store.match(namedNode('http://ex.org/Mickey'), null, null)) ``` ### [`Dataset` Interface](https://rdf.js.org/dataset-spec/#dataset-interface) -This store adheres to the `Datase` interface which exposes the following properties +This store adheres to the `Dataset` interface which exposes the following properties Attributes: - `size` — A non-negative integer that specifies the number of quads in the set. From 66a08b2bd395aabb112bfd02ebae47fa28f79c1a Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Sun, 28 Jul 2024 19:10:25 +1000 Subject: [PATCH 08/12] chore: fix docs breakage --- src/N3Writer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/N3Writer.js b/src/N3Writer.js index cd78e434..144bfb6c 100644 --- a/src/N3Writer.js +++ b/src/N3Writer.js @@ -15,7 +15,7 @@ const escape = /["\\\t\n\r\b\f\u0000-\u0019\ud800-\udbff]/, '\n': '\\n', '\r': '\\r', '\b': '\\b', '\f': '\\f', }; -// ## Placeholder class to represent already pretty-pnew Error('not implemented')rinted terms +// ## Placeholder class to represent already pretty-printed terms class SerializedTerm extends Term { // Pretty-printed nodes are not equal to any other node // (e.g., [] does not equal []) From bfac5321e1fc52b10ddd242d994b561b0cd6f776 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Tue, 30 Jul 2024 08:29:05 +1000 Subject: [PATCH 09/12] chore: editorial fix Co-authored-by: Ted Thibodeau Jr --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index df6b1854..fc9baa08 100644 --- a/README.md +++ b/README.md @@ -318,7 +318,7 @@ Methods: - `[Symbol.iterator]` — Implements the iterator protocol to allow iteration over all `quads` in the dataset as in the example above. ### Addition and deletion of quads -The store provides the following manipulation methods in addition to implementing the standard [`Dataset` Interface](https://rdf.js.org/dataset-spec/#dataset-interface) +The store implements the following manipulation methods in addition to the standard [`Dataset` Interface](https://rdf.js.org/dataset-spec/#dataset-interface) ([documentation](http://rdfjs.github.io/N3.js/docs/N3Store.html)): - `addQuad` to insert one quad - `addQuads` to insert an array of quads From 8e0a756129456ae0a81f15994b1d98d3d7ce3819 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Tue, 30 Jul 2024 08:29:38 +1000 Subject: [PATCH 10/12] chore: editorial fix Co-authored-by: Ted Thibodeau Jr --- src/N3Store.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/N3Store.js b/src/N3Store.js index 495ecc35..7f4878f7 100644 --- a/src/N3Store.js +++ b/src/N3Store.js @@ -772,8 +772,8 @@ export default class N3Store { } /** - * Returns `true` if the current instance is a superset of the given dataset; differently put: if the given dataset - * is a subset of, is contained in the current dataset. + * Returns `true` if the current dataset is a superset of the given dataset; in other words, returns `true` if + * the given dataset is a subset of, i.e., is contained within, the current dataset. * * Blank Nodes will be normalized. */ From c2048b9a10e93c57bf4f1047be2f56aa85124229 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Tue, 30 Jul 2024 08:30:00 +1000 Subject: [PATCH 11/12] chore: editorial fix Co-authored-by: Ted Thibodeau Jr --- src/N3Store.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/N3Store.js b/src/N3Store.js index 7f4878f7..b6e4e8c0 100644 --- a/src/N3Store.js +++ b/src/N3Store.js @@ -799,7 +799,7 @@ export default class N3Store { } /** - * This method removes the quads in the current instance that match the given arguments. + * This method removes the quads in the current dataset that match the given arguments. * * The logic described in {@link https://rdf.js.org/dataset-spec/#quad-matching|Quad Matching} is applied for each * quad in this dataset to select the quads which will be deleted. From b904d8bebb86d8773255ee75b69e5e216ec8cc1e Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Tue, 30 Jul 2024 08:40:58 +1000 Subject: [PATCH 12/12] chore: editorial fix Co-authored-by: Ted Thibodeau Jr --- src/N3Store.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/N3Store.js b/src/N3Store.js index b6e4e8c0..e74300d1 100644 --- a/src/N3Store.js +++ b/src/N3Store.js @@ -789,8 +789,8 @@ export default class N3Store { /** - * Returns `true` if the current instance is a superset of the given dataset; differently put: if the given dataset - * is a subset of, is contained in the current dataset. + * Returns `true` if the current dataset is a superset of the given dataset; in other words, returns `true` if + * the given dataset is a subset of, i.e., is contained within, the current dataset. * * Blank Nodes will be normalized. */ @@ -802,7 +802,7 @@ export default class N3Store { * This method removes the quads in the current dataset that match the given arguments. * * The logic described in {@link https://rdf.js.org/dataset-spec/#quad-matching|Quad Matching} is applied for each - * quad in this dataset to select the quads which will be deleted. + * quad in this dataset, to select the quads which will be deleted. * * @param subject The optional exact subject to match. * @param predicate The optional exact predicate to match. @@ -816,14 +816,14 @@ export default class N3Store { } /** - * Returns a new dataset that contains all quads from the current dataset, not included in the given dataset. + * Returns a new dataset that contains all quads from the current dataset that are not included in the given dataset. */ difference(other) { return this.filter(quad => !other.has(quad)); } /** - * Returns true if the current instance contains the same graph structure as the given dataset. + * Returns true if the current dataset contains the same graph structure as the given dataset. * * Blank Nodes will be normalized. */ @@ -845,7 +845,7 @@ export default class N3Store { } /** - * Returns a new dataset containing alls quads from the current dataset that are also included in the given dataset. + * Returns a new dataset containing all quads from the current dataset that are also included in the given dataset. */ intersection(other) { return this.filter(quad => other.has(quad)); @@ -862,9 +862,9 @@ export default class N3Store { } /** - * This method calls the `iteratee` on each `quad` of the `Dataset`. The first time the `iteratee` is called, the - * `accumulator` value is the `initialValue` or, if not given, equals to the first quad of the `Dataset`. The return - * value of the `iteratee` is used as `accumulator` value for the next calls. + * This method calls the `iteratee` method on each `quad` of the `Dataset`. The first time the `iteratee` method + * is called, the `accumulator` value is the `initialValue`, or, if not given, equals the first quad of the `Dataset`. + * The return value of each call to the `iteratee` method is used as the `accumulator` value for the next call. * * This method returns the return value of the last `iteratee` call. * @@ -879,7 +879,7 @@ export default class N3Store { } /** - * Returns the set of quads within the dataset as a host language native sequence, for example an `Array` in + * Returns the set of quads within the dataset as a host-language-native sequence, for example an `Array` in * ECMAScript-262. * * Since a `Dataset` is an unordered set, the order of the quads within the returned sequence is arbitrary. @@ -889,7 +889,7 @@ export default class N3Store { } /** - * Returns an N-Quads string representation of the dataset, preprocessed with + * Returns an N-Quads string representation of the dataset, preprocessed with the * {@link https://json-ld.github.io/normalization/spec/|RDF Dataset Normalization} algorithm. */ toCanonical() {