sol-components 2.1.0
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/README.md +7 -0
- package/core/activate.js +27 -0
- package/core/adopt.js +71 -0
- package/core/auth-core.js +73 -0
- package/core/auth-fetch.js +154 -0
- package/core/component-mount.js +110 -0
- package/core/defaults.js +48 -0
- package/core/define.js +15 -0
- package/core/display-target.js +166 -0
- package/core/edit-placements.js +28 -0
- package/core/editor-self.js +127 -0
- package/core/editor.js +162 -0
- package/core/events.js +27 -0
- package/core/extension-points.js +189 -0
- package/core/form-utils.js +210 -0
- package/core/from-query.js +138 -0
- package/core/from-rdf.js +52 -0
- package/core/here.js +33 -0
- package/core/include-core.js +73 -0
- package/core/inrupt-global.js +18 -0
- package/core/menu-consumer.js +41 -0
- package/core/menu-rdf.js +154 -0
- package/core/pod-ops.js +392 -0
- package/core/pod-registry.js +82 -0
- package/core/popup-proxy.js +255 -0
- package/core/rdf-core.js +280 -0
- package/core/rdf-render.js +136 -0
- package/core/rdf-utils.js +411 -0
- package/core/rdf.js +154 -0
- package/core/services.js +106 -0
- package/core/shape-to-form.js +741 -0
- package/core/sparql-safety.js +20 -0
- package/core/utils.js +196 -0
- package/dist/importmap-cdn.json +49 -0
- package/dist/importmap-local.json +49 -0
- package/dist/sol-loader.manifest.json +140 -0
- package/dist/vendor/@comunica-query-sparql.js +137851 -0
- package/dist/vendor/@inrupt-solid-client-authn-browser.js +7503 -0
- package/dist/vendor/dompurify.js +1476 -0
- package/dist/vendor/ical.js.js +9739 -0
- package/dist/vendor/marked.js +85 -0
- package/dist/vendor/n3.js +14670 -0
- package/dist/vendor/rdf-validate-shacl.js +6970 -0
- package/dist/vendor/rdflib.js +35172 -0
- package/dist/vendor/solid-logic.js +6819 -0
- package/dist/vendor/solid-ui.js +21945 -0
- package/node/sol-form.js +133 -0
- package/node/sol-include.js +55 -0
- package/node/sol-login.js +632 -0
- package/node/sol-menu.js +639 -0
- package/node/sol-query.js +116 -0
- package/package.json +133 -0
- package/web/menu-from-rdf.js +23 -0
- package/web/scripts/prefs.js +25 -0
- package/web/sol-accordion.js +114 -0
- package/web/sol-basic.js +50 -0
- package/web/sol-breadcrumb.js +131 -0
- package/web/sol-button.js +244 -0
- package/web/sol-calendar.js +465 -0
- package/web/sol-default.js +118 -0
- package/web/sol-dropdown-button.js +222 -0
- package/web/sol-feed.js +1336 -0
- package/web/sol-form.js +949 -0
- package/web/sol-full.js +43 -0
- package/web/sol-gallery.js +303 -0
- package/web/sol-include.js +246 -0
- package/web/sol-live-edit.js +415 -0
- package/web/sol-login.js +856 -0
- package/web/sol-menu.js +593 -0
- package/web/sol-modal.js +377 -0
- package/web/sol-pod-extras.js +17 -0
- package/web/sol-pod-ops.js +680 -0
- package/web/sol-pod.js +1039 -0
- package/web/sol-query.js +546 -0
- package/web/sol-rolodex.js +95 -0
- package/web/sol-search.js +402 -0
- package/web/sol-settings.js +199 -0
- package/web/sol-solidos.js +93 -0
- package/web/sol-tabs.js +445 -0
- package/web/sol-time.js +194 -0
- package/web/sol-tree-edit.js +492 -0
- package/web/sol-wac.js +456 -0
- package/web/sol-weather.js +337 -0
- package/web/sol-window.js +142 -0
- package/web/styles/buttons-css.js +108 -0
- package/web/styles/help.css +242 -0
- package/web/styles/root.css +112 -0
- package/web/styles/sol-accordion-css.js +97 -0
- package/web/styles/sol-calendar-css.js +154 -0
- package/web/styles/sol-feed-css.js +475 -0
- package/web/styles/sol-form-css.js +471 -0
- package/web/styles/sol-gallery-css.js +181 -0
- package/web/styles/sol-include-css.js +95 -0
- package/web/styles/sol-live-edit-css.js +84 -0
- package/web/styles/sol-live-edit.css +101 -0
- package/web/styles/sol-login-css.js +116 -0
- package/web/styles/sol-menu-css.js +145 -0
- package/web/styles/sol-modal-css.js +134 -0
- package/web/styles/sol-pod-css.js +187 -0
- package/web/styles/sol-pod-modal-css.js +203 -0
- package/web/styles/sol-query-css.js +140 -0
- package/web/styles/sol-query-help.css +267 -0
- package/web/styles/sol-query-one-pager.css +67 -0
- package/web/styles/sol-search-css.js +157 -0
- package/web/styles/sol-solidos-css.js +7 -0
- package/web/styles/sol-tabs-css.js +114 -0
- package/web/styles/sol-time-css.js +30 -0
- package/web/styles/sol-wac-css.js +73 -0
- package/web/styles/sol-weather-css.js +59 -0
- package/web/styles/solid-logo.svg +9 -0
- package/web/styles/view-accordion-css.js +66 -0
- package/web/styles/view-anchorlist-css.js +22 -0
- package/web/styles/view-autocomplete-css.js +59 -0
- package/web/styles/view-rolodex-css.js +102 -0
- package/web/styles/view-select-css.js +21 -0
- package/web/utils/calendar-fetch.js +388 -0
- package/web/utils/code-mirror-editor.js +82 -0
- package/web/utils/commons-fetch.js +108 -0
- package/web/utils/feed-edit.js +159 -0
- package/web/utils/feed-edit.smoke.mjs +74 -0
- package/web/utils/feed-fetch.js +573 -0
- package/web/utils/live-edit-help/csv.js +64 -0
- package/web/utils/live-edit-help/graphviz.js +41 -0
- package/web/utils/live-edit-help/jsonld.js +55 -0
- package/web/utils/live-edit-help/markdown.js +52 -0
- package/web/utils/live-edit-help/mermaid.js +48 -0
- package/web/utils/live-edit-help/turtle.js +85 -0
- package/web/utils/rdf-config.js +125 -0
- package/web/utils/renderers/csv.js +124 -0
- package/web/utils/renderers/d3-force.js +82 -0
- package/web/utils/renderers/graphviz.js +13 -0
- package/web/utils/renderers/html.js +10 -0
- package/web/utils/renderers/jsonld.js +63 -0
- package/web/utils/renderers/markdown.js +19 -0
- package/web/utils/renderers/mermaid.js +54 -0
- package/web/utils/renderers/turtle.js +51 -0
- package/web/utils/sol-query-triple-patterns.js +151 -0
- package/web/utils/sol-query-ui.js +250 -0
- package/web/utils/sol-query-views.js +32 -0
- package/web/views/_helpers.js +34 -0
- package/web/views/accordion.js +133 -0
- package/web/views/anchorlist.js +59 -0
- package/web/views/auto-complete.js +183 -0
- package/web/views/dl.js +38 -0
- package/web/views/list.js +19 -0
- package/web/views/menu.js +56 -0
- package/web/views/rolodex.js +126 -0
- package/web/views/select.js +79 -0
- package/web/views/table.js +73 -0
- package/web/views/tabs.js +57 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* feed-edit.js — sol-feed's editing operations over a SKOS/DCAT feeds doc.
|
|
3
|
+
*
|
|
4
|
+
* Each operation is a *pure builder* returning `{ deletes, inserts }` arrays of
|
|
5
|
+
* Turtle triple strings; `patchDoc` turns one into a `application/sparql-update`
|
|
6
|
+
* body and PATCHes it to the feeds file (same-origin; no proxy — the CORS proxy
|
|
7
|
+
* is only for cross-origin RSS reads). Splitting build from send keeps the body
|
|
8
|
+
* construction unit-testable without a network.
|
|
9
|
+
*
|
|
10
|
+
* Model (matches libraries/news/feeds.ttl): topics are `skos:Concept`
|
|
11
|
+
* (`skos:prefLabel`, `skos:topConceptOf` the scheme); sources are
|
|
12
|
+
* `dcat:Dataset, rss:channel` with `dct:title` + `dcat:accessURL` +
|
|
13
|
+
* `dcat:theme` → a topic; the `dcat:Catalog` lists every source via
|
|
14
|
+
* `dcat:dataset`. Deleted sources are re-themed to a reserved `#Deleted`
|
|
15
|
+
* concept (NOT `topConceptOf` the scheme, so it never shows as a normal topic).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export const NS = {
|
|
19
|
+
rdf: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
|
|
20
|
+
dct: 'http://purl.org/dc/terms/',
|
|
21
|
+
dcat: 'http://www.w3.org/ns/dcat#',
|
|
22
|
+
skos: 'http://www.w3.org/2004/02/skos/core#',
|
|
23
|
+
rss: 'http://purl.org/rss/1.0/',
|
|
24
|
+
schema: 'http://schema.org/',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const DEFAULT_PREFIXES = {
|
|
28
|
+
rdf: NS.rdf, dct: NS.dct, dcat: NS.dcat, skos: NS.skos, rss: NS.rss, schema: NS.schema,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/** The reserved deleted-bin concept fragment. */
|
|
32
|
+
export const BIN_FRAG = 'Deleted';
|
|
33
|
+
export const binUriFor = (fileUri) => `${fileUri}#${BIN_FRAG}`;
|
|
34
|
+
|
|
35
|
+
/** A Turtle string literal (handles quotes / backslashes / newlines). */
|
|
36
|
+
export const lit = (s) => JSON.stringify(String(s));
|
|
37
|
+
|
|
38
|
+
/* ── pure builders → { deletes, inserts } ───────────────────────────────── */
|
|
39
|
+
|
|
40
|
+
export function renameTopicEdit(topicUri, oldLabel, newLabel) {
|
|
41
|
+
return {
|
|
42
|
+
deletes: [`<${topicUri}> skos:prefLabel ${lit(oldLabel)} .`],
|
|
43
|
+
inserts: [`<${topicUri}> skos:prefLabel ${lit(newLabel)} .`],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function recategorizeEdit(feedUri, fromTopicUri, toTopicUri) {
|
|
48
|
+
if (fromTopicUri === toTopicUri) return { deletes: [], inserts: [] };
|
|
49
|
+
return {
|
|
50
|
+
deletes: [`<${feedUri}> dcat:theme <${fromTopicUri}> .`],
|
|
51
|
+
inserts: [`<${feedUri}> dcat:theme <${toTopicUri}> .`],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function addFeedEdit(feedUri, { title, url, topicUri, catalogUri }) {
|
|
56
|
+
const inserts = [
|
|
57
|
+
`<${feedUri}> a dcat:Dataset, rss:channel ; ` +
|
|
58
|
+
`dct:title ${lit(title)} ; dcat:accessURL <${url}> ; dcat:theme <${topicUri}> .`,
|
|
59
|
+
];
|
|
60
|
+
if (catalogUri) inserts.push(`<${catalogUri}> dcat:dataset <${feedUri}> .`);
|
|
61
|
+
return { deletes: [], inserts };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Move a source to the bin (re-theme to #Deleted), creating the bin concept
|
|
65
|
+
* on first use. `binLabel` only matters when the concept is minted. */
|
|
66
|
+
export function deleteToBinEdit(feedUri, fromTopicUri, binUri, { ensureBin = true, binLabel = 'Deleted' } = {}) {
|
|
67
|
+
const inserts = [`<${feedUri}> dcat:theme <${binUri}> .`];
|
|
68
|
+
if (ensureBin) inserts.push(`<${binUri}> a skos:Concept ; skos:prefLabel ${lit(binLabel)} .`);
|
|
69
|
+
return { deletes: [`<${feedUri}> dcat:theme <${fromTopicUri}> .`], inserts };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Restore from the bin = re-categorize out of #Deleted to a chosen topic. */
|
|
73
|
+
export function restoreEdit(feedUri, binUri, toTopicUri) {
|
|
74
|
+
return recategorizeEdit(feedUri, binUri, toTopicUri);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Set/replace a single subject's ordering position. */
|
|
78
|
+
export function reorderEdit(subjectUri, oldPos, newPos) {
|
|
79
|
+
const deletes = oldPos == null ? [] : [`<${subjectUri}> schema:position ${Number(oldPos)} .`];
|
|
80
|
+
return { deletes, inserts: [`<${subjectUri}> schema:position ${Number(newPos)} .`] };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Re-number a whole ordered list (a topic's sources). Writes `schema:position`
|
|
85
|
+
* 0..n-1 in the new order; only emits triples for items whose position changed.
|
|
86
|
+
* @param {string[]} orderedUris subjects in their new order
|
|
87
|
+
* @param {Object<string,number>} [oldPos] uri → its current position (if any)
|
|
88
|
+
*/
|
|
89
|
+
export function setPositionsEdit(orderedUris, oldPos = {}) {
|
|
90
|
+
const deletes = [], inserts = [];
|
|
91
|
+
orderedUris.forEach((uri, i) => {
|
|
92
|
+
const had = oldPos[uri];
|
|
93
|
+
if (had === i) return; // already correct
|
|
94
|
+
if (had != null) deletes.push(`<${uri}> schema:position ${Number(had)} .`);
|
|
95
|
+
inserts.push(`<${uri}> schema:position ${i} .`);
|
|
96
|
+
});
|
|
97
|
+
return { deletes, inserts };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* ── slug / mint ─────────────────────────────────────────────────────────── */
|
|
101
|
+
|
|
102
|
+
/** Mint a unique `<fileUri>#feed-<slug>` not colliding with `existingUris`. */
|
|
103
|
+
export function mintFeedUri(fileUri, title, existingUris = []) {
|
|
104
|
+
const taken = new Set(existingUris);
|
|
105
|
+
const base = 'feed-' + String(title).trim().toLowerCase()
|
|
106
|
+
.replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 40) || 'feed';
|
|
107
|
+
let frag = base, n = 2;
|
|
108
|
+
while (taken.has(`${fileUri}#${frag}`)) frag = `${base}-${n++}`;
|
|
109
|
+
return `${fileUri}#${frag}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* ── serialise + send ────────────────────────────────────────────────────── */
|
|
113
|
+
|
|
114
|
+
export function patchBody({ deletes = [], inserts = [] }, prefixes = DEFAULT_PREFIXES) {
|
|
115
|
+
const pfx = Object.entries(prefixes).map(([k, v]) => `PREFIX ${k}: <${v}>`).join('\n');
|
|
116
|
+
const block = (kw, triples) => `${kw} {\n${triples.map((t) => ' ' + t).join('\n')}\n}`;
|
|
117
|
+
const parts = [];
|
|
118
|
+
if (deletes.length) parts.push(block('DELETE DATA', deletes));
|
|
119
|
+
if (inserts.length) parts.push(block('INSERT DATA', inserts));
|
|
120
|
+
return `${pfx}\n${parts.join(' ;\n')}\n`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* PATCH the feeds file with one edit. Same-origin, no proxy.
|
|
125
|
+
* @param {string} fileUri
|
|
126
|
+
* @param {{deletes?:string[],inserts?:string[]}} edit
|
|
127
|
+
* @param {{prefixes?:object, fetchImpl?:Function}} [opts]
|
|
128
|
+
*/
|
|
129
|
+
export async function patchDoc(fileUri, edit, { prefixes = DEFAULT_PREFIXES, fetchImpl } = {}) {
|
|
130
|
+
if (!edit || (!edit.deletes?.length && !edit.inserts?.length)) return;
|
|
131
|
+
const f = fetchImpl || fetch;
|
|
132
|
+
const resp = await f(fileUri, {
|
|
133
|
+
method: 'PATCH',
|
|
134
|
+
headers: { 'Content-Type': 'application/sparql-update' },
|
|
135
|
+
body: patchBody(edit, prefixes),
|
|
136
|
+
});
|
|
137
|
+
if (!resp.ok) throw new Error(`Save failed (HTTP ${resp.status}) — the feeds file must be writable.`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Permanent delete: remove ALL of a source's triples (DELETE … WHERE, so it
|
|
142
|
+
* works whatever predicates it has — title, accessURL, theme, position, …)
|
|
143
|
+
* plus its `dcat:dataset` catalog membership. Used to purge from the bin.
|
|
144
|
+
*/
|
|
145
|
+
export function purgeBody(feedUri, catalogUri, prefixes = DEFAULT_PREFIXES) {
|
|
146
|
+
const pfx = Object.entries(prefixes).map(([k, v]) => `PREFIX ${k}: <${v}>`).join('\n');
|
|
147
|
+
const cat = catalogUri ? `\n <${catalogUri}> dcat:dataset <${feedUri}> .` : '';
|
|
148
|
+
return `${pfx}\nDELETE {\n <${feedUri}> ?p ?o .${cat}\n} WHERE {\n <${feedUri}> ?p ?o .\n}\n`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export async function purgeFeed(fileUri, feedUri, { catalogUri, prefixes, fetchImpl } = {}) {
|
|
152
|
+
const f = fetchImpl || fetch;
|
|
153
|
+
const resp = await f(fileUri, {
|
|
154
|
+
method: 'PATCH',
|
|
155
|
+
headers: { 'Content-Type': 'application/sparql-update' },
|
|
156
|
+
body: purgeBody(feedUri, catalogUri, prefixes),
|
|
157
|
+
});
|
|
158
|
+
if (!resp.ok) throw new Error(`Permanent delete failed (HTTP ${resp.status}).`);
|
|
159
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import {
|
|
3
|
+
renameTopicEdit, recategorizeEdit, addFeedEdit, deleteToBinEdit, restoreEdit,
|
|
4
|
+
reorderEdit, mintFeedUri, patchBody, patchDoc, binUriFor, lit,
|
|
5
|
+
} from './feed-edit.js';
|
|
6
|
+
|
|
7
|
+
let n = 0; const ok = (m) => { console.log(' ✓ ' + m); n++; };
|
|
8
|
+
const F = 'http://ex/feeds.ttl';
|
|
9
|
+
|
|
10
|
+
// rename
|
|
11
|
+
let e = renameTopicEdit(F+'#News', 'News', 'World News');
|
|
12
|
+
assert.deepEqual(e.deletes, [`<${F}#News> skos:prefLabel "News" .`]);
|
|
13
|
+
assert.deepEqual(e.inserts, [`<${F}#News> skos:prefLabel "World News" .`]);
|
|
14
|
+
ok('renameTopicEdit swaps prefLabel');
|
|
15
|
+
|
|
16
|
+
// recategorize + no-op
|
|
17
|
+
e = recategorizeEdit(F+'#f1', F+'#News', F+'#Culture');
|
|
18
|
+
assert.ok(e.deletes[0].includes('#News') && e.inserts[0].includes('#Culture'));
|
|
19
|
+
assert.deepEqual(recategorizeEdit(F+'#f1', F+'#News', F+'#News'), {deletes:[],inserts:[]});
|
|
20
|
+
ok('recategorizeEdit moves dcat:theme (no-op when unchanged)');
|
|
21
|
+
|
|
22
|
+
// add (with + without catalog)
|
|
23
|
+
e = addFeedEdit(F+'#feed-x', { title:'X "Y"', url:'http://x/r.xml', topicUri:F+'#News', catalogUri:F+'#catalog' });
|
|
24
|
+
assert.ok(e.inserts[0].includes('a dcat:Dataset, rss:channel'));
|
|
25
|
+
assert.ok(e.inserts[0].includes('dct:title "X \\"Y\\""')); // quote escaped
|
|
26
|
+
assert.ok(e.inserts[0].includes('dcat:accessURL <http://x/r.xml>'));
|
|
27
|
+
assert.equal(e.inserts[1], `<${F}#catalog> dcat:dataset <${F}#feed-x> .`);
|
|
28
|
+
ok('addFeedEdit builds dataset + catalog membership, escapes literals');
|
|
29
|
+
|
|
30
|
+
// delete → bin (ensures bin concept)
|
|
31
|
+
const bin = binUriFor(F);
|
|
32
|
+
assert.equal(bin, F+'#Deleted');
|
|
33
|
+
e = deleteToBinEdit(F+'#f1', F+'#News', bin);
|
|
34
|
+
assert.deepEqual(e.deletes, [`<${F}#f1> dcat:theme <${bin}> .`].map(()=>`<${F}#f1> dcat:theme <${F}#News> .`));
|
|
35
|
+
assert.ok(e.inserts.some(t => t === `<${F}#f1> dcat:theme <${bin}> .`));
|
|
36
|
+
assert.ok(e.inserts.some(t => t.includes('skos:Concept') && t.includes('"Deleted"')));
|
|
37
|
+
ok('deleteToBinEdit re-themes to #Deleted and mints the bin concept');
|
|
38
|
+
|
|
39
|
+
// restore = recategorize out of bin
|
|
40
|
+
e = restoreEdit(F+'#f1', bin, F+'#Culture');
|
|
41
|
+
assert.ok(e.deletes[0].includes('#Deleted') && e.inserts[0].includes('#Culture'));
|
|
42
|
+
ok('restoreEdit re-files out of the bin');
|
|
43
|
+
|
|
44
|
+
// reorder
|
|
45
|
+
e = reorderEdit(F+'#f1', 3, 7);
|
|
46
|
+
assert.deepEqual(e.deletes, [`<${F}#f1> schema:position 3 .`]);
|
|
47
|
+
assert.deepEqual(e.inserts, [`<${F}#f1> schema:position 7 .`]);
|
|
48
|
+
ok('reorderEdit replaces schema:position');
|
|
49
|
+
|
|
50
|
+
// mint uniqueness
|
|
51
|
+
assert.equal(mintFeedUri(F, 'NY Times', []), F+'#feed-ny-times');
|
|
52
|
+
assert.equal(mintFeedUri(F, 'NY Times', [F+'#feed-ny-times']), F+'#feed-ny-times-2');
|
|
53
|
+
ok('mintFeedUri slugifies + avoids collisions');
|
|
54
|
+
|
|
55
|
+
// patchBody both sections + prefixes
|
|
56
|
+
const body = patchBody(recategorizeEdit(F+'#f1', F+'#News', F+'#Culture'));
|
|
57
|
+
assert.ok(body.includes('PREFIX dcat: <http://www.w3.org/ns/dcat#>'));
|
|
58
|
+
assert.ok(body.includes('DELETE DATA {') && body.includes('INSERT DATA {') && body.includes(' ;\n'));
|
|
59
|
+
assert.ok(patchBody({inserts:['<a> <b> <c> .']}).includes('INSERT DATA') && !patchBody({inserts:['<a> <b> <c> .']}).includes('DELETE'));
|
|
60
|
+
ok('patchBody emits prefixes + DELETE/INSERT DATA blocks');
|
|
61
|
+
|
|
62
|
+
// patchDoc wiring (fake fetch) + no-op skip
|
|
63
|
+
let captured = null;
|
|
64
|
+
await patchDoc(F, recategorizeEdit(F+'#f1', F+'#News', F+'#Culture'), { fetchImpl: async (u, o) => { captured = {u,o}; return {ok:true}; } });
|
|
65
|
+
assert.equal(captured.u, F);
|
|
66
|
+
assert.equal(captured.o.method, 'PATCH');
|
|
67
|
+
assert.equal(captured.o.headers['Content-Type'], 'application/sparql-update');
|
|
68
|
+
let called = false;
|
|
69
|
+
await patchDoc(F, {deletes:[],inserts:[]}, { fetchImpl: async () => { called = true; return {ok:true}; } });
|
|
70
|
+
assert.equal(called, false, 'no-op edit does not PATCH');
|
|
71
|
+
await assert.rejects(() => patchDoc(F, recategorizeEdit(F+'#f1',F+'#a',F+'#b'), { fetchImpl: async()=>({ok:false,status:403}) }), /403/);
|
|
72
|
+
ok('patchDoc PATCHes sparql-update, skips no-ops, throws on !ok');
|
|
73
|
+
|
|
74
|
+
console.log(`\n${n} feed-edit checks passed`);
|