diff --git a/.gitignore b/.gitignore index a9f4ed5..7cbe8e2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ lib -node_modules \ No newline at end of file +node_modules +.history +.vscode \ No newline at end of file diff --git a/src/ProfileView.ts b/src/ProfileView.ts index 98abbb0..3ea1139 100644 --- a/src/ProfileView.ts +++ b/src/ProfileView.ts @@ -12,6 +12,7 @@ import { presentProfile } from "./presenter"; import { presentCV } from './CVPresenter' // 20210527 import { ProfileCard } from "./ProfileCard"; import { CVCard } from "./CVCard"; +import { addMeToYourFriendsHtml } from "./addMeToYourFriends"; export const ProfileView = ( subject: NamedNode, @@ -37,6 +38,7 @@ export const ProfileView = (
${ProfileCard(profileBasics)} + ${addMeToYourFriendsHtml(subject, context)}
diff --git a/src/addMeToYourFriends.ts b/src/addMeToYourFriends.ts new file mode 100644 index 0000000..e020077 --- /dev/null +++ b/src/addMeToYourFriends.ts @@ -0,0 +1,95 @@ +import { html, TemplateResult } from "lit-html"; +import { styleMap } from "lit-html/directives/style-map"; +import { DataBrowserContext } from "pane-registry"; +import * as $rdf from "rdflib"; +import * as UI from "solid-ui"; +import { padding, textCenter } from "./baseStyles"; + +const styles = { + button: styleMap({ ...textCenter(), ...padding() }), +}; + +let buttonContainer = document.createElement("div"); +const messageDiv = document.createElement("div"); +messageDiv.setAttribute( + "style", + "margin: 0.1em; padding: 0.5em; border: 0.05em solid gray; background-color: #efe; color:black;" +); + +export const addMeToYourFriendsHtml = ( + subject: $rdf.NamedNode, + context: DataBrowserContext +): TemplateResult => { + buttonContainer = context.dom.createElement("div"); + buttonContainer.appendChild(createAddMeToYourFriendsButton(subject, context)); + return html`
${buttonContainer}
`; +}; + +const createAddMeToYourFriendsButton = ( + subject: $rdf.NamedNode, + context: DataBrowserContext +): HTMLButtonElement => { + const button = UI.widgets.button(context.dom, undefined, "Add me to your friends", undefined, { + needsBorder: true, + }); + button.addEventListener( + "click", + async () => + await saveNewFriend(subject, context) + .then(() => reportToFrontend(true, context, "Friend was added!")) + .catch((error) => reportToFrontend(false, context, error)), + false + ); + return button; +}; + +async function saveNewFriend(subject: $rdf.NamedNode, context: DataBrowserContext): Promise { + const loggedInContext = await UI.authn.logInLoadProfile(context).catch(() => { + //For error text consistency reasons, we need to chatch this here. + //Now it shows the same message like in the error upon clicking the chat button. + throw new Error("Current user not found! Not logged in?"); + }); + const me = loggedInContext.me; + const profileDoc = me.doc(); + const store = context.session.store; + const updater = store.updater; + const toBeInserted = [UI.rdf.st(me, UI.ns.foaf("knows"), subject, profileDoc)]; + try { + await updater.update([], toBeInserted); + } catch (error) { + let errorMessage = error; + if (errorMessage.toString().includes("Unauthenticated")) + errorMessage = "Current user not found! Not logged in?"; + throw new Error(errorMessage); + } +} + +function reportToFrontend(possitive: boolean, context: DataBrowserContext, message: string) { + clearMessage(); + if (possitive) reportPositive(context, message); + else complain(context, message); +} + +function complain(context: DataBrowserContext, error: string) { + buttonContainer.appendChild(UI.widgets.errorMessageBlock(context.dom, error)); +} + +function reportPositive(context: DataBrowserContext, message: string) { + messageDiv.innerHTML = message; + buttonContainer.appendChild(messageDiv); +} +function clearMessage() { + while (buttonContainer.childNodes.length > 1) { + buttonContainer.removeChild(buttonContainer.lastChild); + } +} +//Because the code has unhandled Promises we still want to signal the user a message. +//Console will contain actual error. +window.addEventListener("unhandledrejection", function () { + clearMessage(); + buttonContainer.appendChild( + UI.widgets.errorMessageBlock(window.document, "An internal error occured!") + ); +}); + +export { saveNewFriend, createAddMeToYourFriendsButton }; diff --git a/src/CVPresenter.spec.ts b/src/integration-tests/CVPresenter.spec.ts similarity index 94% rename from src/CVPresenter.spec.ts rename to src/integration-tests/CVPresenter.spec.ts index e8b23d9..6bab29a 100644 --- a/src/CVPresenter.spec.ts +++ b/src/integration-tests/CVPresenter.spec.ts @@ -1,4 +1,4 @@ -import { presentCV} from "./CVPresenter"; +import { presentCV} from "../CVPresenter"; import { blankNode, sym } from "rdflib"; import { ns, store } from "solid-ui"; diff --git a/src/integration-tests/add-me-to-friend-setup.ts b/src/integration-tests/add-me-to-friend-setup.ts new file mode 100644 index 0000000..44d4601 --- /dev/null +++ b/src/integration-tests/add-me-to-friend-setup.ts @@ -0,0 +1,28 @@ +import {DataBrowserContext, PaneRegistry} from "pane-registry"; +import {sym} from "rdflib"; +import {SolidLogic} from "solid-logic"; +import {store} from "solid-ui" + +export const subject = sym("https://testingsolidos.solidcommunity.net/profile/card#me"); +export const doc = subject.doc(); + +export const context = { + dom: document, + getOutliner: () => null, + session: { + paneRegistry: { + byName: (name: string) => { + return { + render: () => { + return document.createElement('div') + .appendChild( + document.createTextNode(`mock ${name} pane`) + ); + } + } + } + } as PaneRegistry, + store, + logic: {} as SolidLogic, + }, +} as unknown as DataBrowserContext; diff --git a/src/integration-tests/add-me-to-your-friends.test.ts b/src/integration-tests/add-me-to-your-friends.test.ts new file mode 100644 index 0000000..b949e3b --- /dev/null +++ b/src/integration-tests/add-me-to-your-friends.test.ts @@ -0,0 +1,56 @@ +import { context, doc, subject } from "./setup"; +import pane from "../"; +import { findByText, fireEvent } from "@testing-library/dom"; +import { parse } from "rdflib"; +import { ns } from "solid-ui"; + +describe("add-me-to-your-friends", () => { + describe("saveNewFriend with NO logged in user", () => { + let result; + beforeAll(() => { + result = pane.render(subject, context); + }); + + it("renders the Add me to friends button", async () => { + const button = await findByText(result, "ADD ME TO YOUR FRIENDS"); + expect(button).not.toBeNull(); + }); + + it("saveNewFriend with user NOT logged in", async () => { + const button = await findByText(result, "ADD ME TO YOUR FRIENDS"); + fireEvent.click(button); + const errorMessage = await findByText(result, "Current user not found! Not logged in?"); + expect(errorMessage).not.toBeNull(); + expect(button).toThrowError; + }); + }); + + /* describe("saveNewFriend with user logged in", () => { + let result; + beforeEach(() => { + context.session.store.removeDocument(doc); + const turtle = ` + @prefix : <#>. + @prefix foaf: . + @prefix vcard: . + :me foaf:name "Testing Solidos"; + . + `; + parse(turtle, context.session.store, doc.uri); + result = pane.render(subject, context); + }); + + it("renders the Add me to friends button", async () => { + const button = await findByText(result, "ADD ME TO YOUR FRIENDS"); + expect(button).not.toBeNull(); + }); + + it("verify added triple", async () => { + const button = await findByText(result, "ADD ME TO YOUR FRIENDS"); + fireEvent.click(button); + console.dir(context.session.store.statements) + const triple = context.session.store.any(subject, ns.foaf("knows"), null, doc); + expect(triple).not.toBeNull(); + }); + }); */ +}); diff --git a/src/presenter.spec.ts b/src/integration-tests/presenter.spec.ts similarity index 98% rename from src/presenter.spec.ts rename to src/integration-tests/presenter.spec.ts index c1ef7e8..ef85ebf 100644 --- a/src/presenter.spec.ts +++ b/src/integration-tests/presenter.spec.ts @@ -1,4 +1,4 @@ -import { presentProfile } from "./presenter"; +import { presentProfile } from "../presenter"; import { blankNode, sym } from "rdflib"; import { ns, store } from "solid-ui"; diff --git a/src/integration-tests/profile-card.spec.ts b/src/integration-tests/profile-card.spec.ts index 20a79eb..8e7016a 100644 --- a/src/integration-tests/profile-card.spec.ts +++ b/src/integration-tests/profile-card.spec.ts @@ -73,7 +73,7 @@ describe("profile-pane", () => { }); it("renders only a makeshift name based on URI", () => { - expect(card.textContent.trim()).toBe("janedoe.example"); + expect(card.textContent.trim()).toContain("janedoe.example"); }); it("does not render broken profile image", () => {