diff --git a/bookmarks/vanilla/README.md b/bookmarks/vanilla/README.md index 54f9cb0..20b66cc 100644 --- a/bookmarks/vanilla/README.md +++ b/bookmarks/vanilla/README.md @@ -1,5 +1,9 @@ # Bookmarks Data Module using vanilla JavaScript +# How to use this module + +[Please add docs about how this data module uses the type index etc.] + ## Development ```bash git clone https://github.com/solid-contrib/data-modules diff --git a/bookmarks/vanilla/src/modules/Bookmark.ts b/bookmarks/vanilla/src/modules/Bookmark.ts index da2bb2f..e4bc8fe 100644 --- a/bookmarks/vanilla/src/modules/Bookmark.ts +++ b/bookmarks/vanilla/src/modules/Bookmark.ts @@ -1,7 +1,9 @@ import { + ThingPersisted, buildThing, createThing, getLiteral, + getNamedNode, getPodUrlAll, getSolidDataset, getThing, @@ -14,7 +16,9 @@ import { getThingAll, removeThing } from "@inrupt/solid-client"; import { BOOKMARK, DCTERMS, - RDF + FOAF, + RDF, + RDFS } from "@inrupt/vocab-common-rdf"; @@ -22,6 +26,10 @@ export interface IBookmark { url: string title: string link: string + created?: string + updated?: string + creator?: string + topic?: string } export class Bookmark { @@ -79,15 +87,7 @@ export class Bookmark { const thing = getThing(ds, url) - if (thing) { - return { - url: thing.url, - title: getLiteral(thing, DCTERMS.title)?.value, - link: getLiteral(thing, BOOKMARK.recalls)?.value - } as IBookmark - } - - return undefined + return thing ? this.mapBookmark(thing) : undefined } /** @@ -187,4 +187,62 @@ export class Bookmark { }; + + private static mapBookmark(thing: ThingPersisted): Bookmark { + const obj: IBookmark = { + url: thing.url, + title: this.mapTitle(thing), + link: this.mapLink(thing) + }; + if (this.mapCreated(thing)) { + obj.created = this.mapCreated(thing); + } + if (this.mapUpdated(thing)) { + obj.updated = this.mapUpdated(thing); + } + if (this.mapCreator(thing)) { + obj.creator = this.mapCreator(thing); + } + if (this.mapTopic(thing)) { + obj.topic = this.mapTopic(thing); + } + return obj; + } + private static mapTitle(thing: ThingPersisted): string { + return ( + getLiteral(thing, DCTERMS.title)?.value ?? + getLiteral(thing, RDFS.label)?.value ?? + "" + ); + } + private static mapLink(thing: ThingPersisted): string { + return ( + getLiteral(thing, BOOKMARK.recalls)?.value ?? + getNamedNode(thing, BOOKMARK.recalls)?.value ?? + "" + ); + } + private static mapCreated(thing: ThingPersisted): string { + return getLiteral(thing, DCTERMS.created)?.value ?? "" + } + private static mapUpdated(thing: ThingPersisted): string { + return ( + getLiteral(thing, "http://purl.org/dc/terms/updated")?.value ?? + "" + ); + } + private static mapCreator(thing: ThingPersisted): string { + return ( + getNamedNode(thing, DCTERMS.creator)?.value ?? + getNamedNode(thing, FOAF.maker)?.value ?? + "" + ); + } + private static mapTopic(thing: ThingPersisted): string { + return ( + getNamedNode(thing, BOOKMARK.hasTopic)?.value ?? + "" + ); + } + } diff --git a/bookmarks/vanilla/test/unit/Bookmark.test.ts b/bookmarks/vanilla/test/unit/Bookmark.test.ts index fcf6f21..1315530 100644 --- a/bookmarks/vanilla/test/unit/Bookmark.test.ts +++ b/bookmarks/vanilla/test/unit/Bookmark.test.ts @@ -29,6 +29,9 @@ describe("Bookmark", () => { } as unknown as jest.Mocked; }); + // TODO: Improve existing tests to mock fetch and not getSolidDataset etc + // See https://github.com/solid-contrib/data-modules/issues/23 + it("should return index url", async () => { const podUrls = ["https://fake-pod.net/"]; const typeIndexUrl = "https://fake-pod.net/bookmarks/index.ttl"; @@ -154,6 +157,112 @@ describe("Bookmark", () => { expect(Bookmark.getIndexUrl).toHaveBeenCalled(); + expect(res).toEqual(expected); + }); + it("should parse bookmarks in format one", async () => { + const indexUrl = "https://fake-pod.net/bookmarks/index.ttl"; + + const url = 'https://fake-pod.net/bookmarks/index.ttl#one'; + + const expected = { + url: 'https://fake-pod.net/bookmarks/index.ttl#one', + title: 'one', + link: 'http://example.com', + created: '2023-10-21T14:16:16Z', + updated: '2023-11-21T14:16:16Z', + creator: 'https://michielbdejong.solidcommunity.net/profile/card#me' + } + + const responseObject: any = { + status: 200, + ok: true, + headers: { + get: (h: string) => (h == "Content-Type" ? "text/turtle" : undefined) + }, + text: () => { + return Promise.resolve(loadFixture("bookmark-formats.ttl")); + } + }; + + jest.spyOn(Bookmark, "getIndexUrl").mockReturnValue(Promise.resolve(indexUrl)); + + jest.spyOn(session, "fetch").mockReturnValue(Promise.resolve(responseObject)); + // jest.spyOn(inruptClient, "getThing").mockReturnValue(JSON.parse(loadFixture("things/one.json"))); + + const res = await Bookmark.get(url, session); + + expect(Bookmark.getIndexUrl).toHaveBeenCalled(); + + expect(res).toEqual(expected); + + }); + it("should parse bookmarks in format two", async () => { + const indexUrl = "https://fake-pod.net/bookmarks/index.ttl"; + + const url = 'https://fake-pod.net/bookmarks/index.ttl#two'; + + const expected = { + url: 'https://fake-pod.net/bookmarks/index.ttl#two', + title: 'two', + link: 'http://example.com', + creator: 'https://michielbdejong.solidcommunity.net/profile/card#me' + } + + const responseObject: any = { + status: 200, + ok: true, + headers: { + get: (h: string) => (h == "Content-Type" ? "text/turtle" : undefined) + }, + text: () => { + return Promise.resolve(loadFixture("bookmark-formats.ttl")); + } + }; + + jest.spyOn(Bookmark, "getIndexUrl").mockReturnValue(Promise.resolve(indexUrl)); + + jest.spyOn(session, "fetch").mockReturnValue(Promise.resolve(responseObject)); + // jest.spyOn(inruptClient, "getThing").mockReturnValue(JSON.parse(loadFixture("things/one.json"))); + + const res = await Bookmark.get(url, session); + + expect(Bookmark.getIndexUrl).toHaveBeenCalled(); + + expect(res).toEqual(expected); + }); + // FIXME: https://github.com/solid-contrib/data-modules/issues/24 + it.only("should parse bookmarks in format three", async () => { + const indexUrl = "https://fake-pod.net/bookmarks/index.ttl"; + + const url = 'https://fake-pod.net/bookmarks/index.ttl#three'; + + const expected = { + url: 'https://fake-pod.net/bookmarks/index.ttl#three', + title: 'three', + link: 'http://example.com', + topic: 'http://wikipedia.org/sdfg' + } + + const responseObject: any = { + status: 200, + ok: true, + headers: { + get: (h: string) => (h == "Content-Type" ? "text/turtle" : undefined) + }, + text: () => { + return Promise.resolve(loadFixture("bookmark-formats.ttl")); + } + }; + + jest.spyOn(Bookmark, "getIndexUrl").mockReturnValue(Promise.resolve(indexUrl)); + + jest.spyOn(session, "fetch").mockReturnValue(Promise.resolve(responseObject)); + // jest.spyOn(inruptClient, "getThing").mockReturnValue(JSON.parse(loadFixture("things/one.json"))); + + const res = await Bookmark.get(url, session); + + expect(Bookmark.getIndexUrl).toHaveBeenCalled(); + expect(res).toEqual(expected); }); }); diff --git a/bookmarks/vanilla/test/unit/fixtures/bookmark-formats.ttl b/bookmarks/vanilla/test/unit/fixtures/bookmark-formats.ttl new file mode 100644 index 0000000..a1c7027 --- /dev/null +++ b/bookmarks/vanilla/test/unit/fixtures/bookmark-formats.ttl @@ -0,0 +1,40 @@ +@prefix : . +@prefix rdfs: . +@prefix bookm: <./>. +@prefix boo: . +@prefix dc: . +@prefix foaf: . +@prefix crdt: . +@prefix xsd: . + +:one + a boo:Bookmark; + rdfs:label "one"; + dc:created "2023-10-21T14:16:16Z"^^; + dc:updated "2023-11-21T14:16:16Z"^^; + dc:creator ; + boo:recalls . + +:two + a boo:Bookmark; + dc:title "two"; + foaf:maker ; + boo:recalls "http://example.com". + +:three + a boo:Bookmark; + rdfs:label "three"; + boo:hasTopic ; + boo:recalls . + +bookm:b93d9944-d54d-42f6-a39b-6ea3f9217763 + a boo:Bookmark; + rdfs:label "sdf"; + boo:hasTopic "sdfg"; + boo:id "b93d9944-d54d-42f6-a39b-6ea3f9217763"; + boo:recalls . +bookm:b93d9944-d54d-42f6-a39b-6ea3f9217763-metadata + a crdt:Metadata; + crdt:createdAt "2023-11-21T12:50:32.051Z"^^xsd:dateTime; + crdt:resource bookm:b93d9944-d54d-42f6-a39b-6ea3f9217763; + crdt:updatedAt "2023-11-21T12:50:32.051Z"^^xsd:dateTime. \ No newline at end of file