javascript-solid-server 0.0.184 → 0.0.185

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "javascript-solid-server",
3
- "version": "0.0.184",
3
+ "version": "0.0.185",
4
4
  "description": "A minimal, fast Solid server",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/rdf/turtle.js CHANGED
Binary file
@@ -153,6 +153,95 @@ describe('turtle converter — unit (#320 follow-ups)', () => {
153
153
  `node n3 should appear:\n${content}`);
154
154
  });
155
155
 
156
+ it('emits a space before `;` and `.` terminators (#419)', async () => {
157
+ // The de-facto convention in W3C spec examples and Apache Jena
158
+ // RIOT is to separate the statement-terminator from the previous
159
+ // token by a space. n3.js packs them; JSS post-processes the
160
+ // output to add the space.
161
+ //
162
+ // Use TWO predicates on the same subject so n3.js emits a `;`
163
+ // continuation (multiple triples on one subject). If a future
164
+ // N3 upgrade ever switched to "one triple per statement" style
165
+ // and dropped `;` entirely, this test would otherwise pass
166
+ // vacuously. The presence-of-terminator assertions below pin
167
+ // that behavior.
168
+ const doc = {
169
+ '@context': { 'foaf': 'http://xmlns.com/foaf/0.1/' },
170
+ '@id': 'https://example.test/alice',
171
+ 'foaf:name': 'Alice',
172
+ 'foaf:age': 30,
173
+ };
174
+ const { content } = await fromJsonLd(doc, 'text/turtle', 'https://example.test/', true);
175
+ // Pin "at least one ` ;` and one ` .` exists" with a literal
176
+ // SPACE — not just any whitespace. The intended output style
177
+ // (matching W3C Turtle 1.1 spec examples) is a single space
178
+ // separator: `value ;` / `value .`. Allowing `\n;` or `\t;`
179
+ // would let the test pass on visually-different output.
180
+ assert.match(content, / ;/, `output must contain at least one " ;" terminator (space-prefixed), got:\n${content}`);
181
+ assert.match(content, / \.(?:\s|$)/, `output must contain at least one " ." terminator (space-prefixed), got:\n${content}`);
182
+ // Every `;` must be preceded by a literal space. Same for `.`
183
+ // at end-of-statement. Reject anything else (newline, tab,
184
+ // packed-against-token).
185
+ const offendingSemi = /[^ ];/.test(content);
186
+ const offendingDot = /[^ ]\.\s*$/m.test(content) || /[^ ]\.\n/.test(content);
187
+ assert.ok(!offendingSemi, `every ; must be preceded by a single space, got:\n${content}`);
188
+ assert.ok(!offendingDot, `every . at line/doc end must be preceded by a single space, got:\n${content}`);
189
+ });
190
+
191
+ it('does NOT add a space inside literals containing `;` or `.` (#419 safety)', async () => {
192
+ // Critical correctness test: the post-pass must NOT corrupt
193
+ // literal values. A literal "foo;bar" with the post-pass naïvely
194
+ // applied would become "foo ;bar" — silent data corruption.
195
+ //
196
+ // Assert by VALUE, not by lexical form. A quote-agnostic parser
197
+ // round-trip survives any future N3 writer style change
198
+ // (single vs double quotes, long-string `"""..."""`, etc.).
199
+ const doc = {
200
+ '@context': { 'ex': 'https://example.test/ns#' },
201
+ '@id': 'https://example.test/s',
202
+ 'ex:semicolonInside': 'foo;bar',
203
+ 'ex:dotInside': 'has.dot',
204
+ 'ex:both': 'a;b.c',
205
+ };
206
+ const { content } = await fromJsonLd(doc, 'text/turtle', 'https://example.test/', true);
207
+ // Round-trip: parse the emitted Turtle, walk the quads, assert
208
+ // the literal values came back exactly as authored.
209
+ const { Parser } = await import('n3');
210
+ const parser = new Parser({ baseIRI: 'https://example.test/' });
211
+ const quads = parser.parse(content);
212
+ const objectsByPredicate = new Map();
213
+ for (const q of quads) {
214
+ if (q.object.termType !== 'Literal') continue;
215
+ objectsByPredicate.set(q.predicate.value, q.object.value);
216
+ }
217
+ assert.strictEqual(objectsByPredicate.get('https://example.test/ns#semicolonInside'), 'foo;bar',
218
+ `literal value for ex:semicolonInside must be "foo;bar"`);
219
+ assert.strictEqual(objectsByPredicate.get('https://example.test/ns#dotInside'), 'has.dot',
220
+ `literal value for ex:dotInside must be "has.dot"`);
221
+ assert.strictEqual(objectsByPredicate.get('https://example.test/ns#both'), 'a;b.c',
222
+ `literal value for ex:both must be "a;b.c"`);
223
+ });
224
+
225
+ it('does NOT add a space inside an IRI containing `;` (#419 safety)', async () => {
226
+ // An IRI's content is bracketed by `<...>` — the post-pass
227
+ // shouldn't touch what's inside. Assert by value via parser
228
+ // round-trip so we don't depend on N3 writer formatting.
229
+ const doc = {
230
+ '@context': { 'ex': 'https://example.test/ns#' },
231
+ '@id': 'https://example.test/s',
232
+ 'ex:rel': { '@id': 'https://example.test/path;with;semis' },
233
+ };
234
+ const { content } = await fromJsonLd(doc, 'text/turtle', 'https://example.test/', true);
235
+ const { Parser } = await import('n3');
236
+ const parser = new Parser({ baseIRI: 'https://example.test/' });
237
+ const quads = parser.parse(content);
238
+ const rels = quads
239
+ .filter(q => q.predicate.value === 'https://example.test/ns#rel')
240
+ .map(q => q.object.value);
241
+ assert.deepStrictEqual(rels, ['https://example.test/path;with;semis'],
242
+ `IRI value must round-trip with internal ; intact`);
243
+ });
244
+
156
245
  it('cyclical nested node reference does not hang', async () => {
157
246
  // Two nested nodes reference each other. BFS must not loop.
158
247
  const a = { '@id': 'https://example.test/a', 'ex:knows': null };