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/ChatWithMe.ts b/src/ChatWithMe.ts
index c59b72f..124cb7d 100644
--- a/src/ChatWithMe.ts
+++ b/src/ChatWithMe.ts
@@ -3,6 +3,7 @@ import {DataBrowserContext} from "pane-registry";
import {NamedNode} from "rdflib";
import {widgets} from "solid-ui";
import { asyncReplace } from "lit-html/directives/async-replace";
+import { chatWithMeButtonText, loadingMessage } from "./texts";
export const ChatWithMe = (subject: NamedNode, context: DataBrowserContext): TemplateResult => {
@@ -14,7 +15,7 @@ export const ChatWithMe = (subject: NamedNode, context: DataBrowserContext): Tem
let exists;
try {
- yield "Loading..."
+ yield loadingMessage,
exists = await logic.chat.getChat(subject, false);
} catch (e) {
exists = false;
@@ -26,7 +27,7 @@ export const ChatWithMe = (subject: NamedNode, context: DataBrowserContext): Tem
const button = widgets.button(
context.dom,
undefined,
- "Chat with me",
+ chatWithMeButtonText,
async () => {
try {
const chat: NamedNode = await logic.chat.getChat(subject);
diff --git a/src/ProfileView.ts b/src/ProfileView.ts
index 98abbb0..4a93d12 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 { addMeToYourFriendsDiv } from "./addMeToYourFriends";
export const ProfileView = (
subject: NamedNode,
@@ -37,6 +38,7 @@ export const ProfileView = (
${ProfileCard(profileBasics)}
+ ${addMeToYourFriendsDiv(subject, context)}
diff --git a/src/addMeToYourFriends.ts b/src/addMeToYourFriends.ts
new file mode 100644
index 0000000..a60302b
--- /dev/null
+++ b/src/addMeToYourFriends.ts
@@ -0,0 +1,141 @@
+import { html, TemplateResult } from "lit-html";
+import { styleMap } from "lit-html/directives/style-map";
+import { DataBrowserContext, LiveStore } from "pane-registry";
+import { rdf, widgets, authn, ns } from "solid-ui";
+import { complain, mention, clearPreviousMessage } from "./addMeToYourFriendsHelper";
+import { padding, textCenter } from "./baseStyles";
+import {
+ logInAddMeToYourFriendsButtonText,
+ friendExistsAlreadyButtonText,
+ addMeToYourFriendsButtonText,
+ friendWasAddedSuccesMessage,
+ userNotLoggedInErrorMessage,
+ friendExistsMessage,
+ internalErrorMessage,
+} from "./texts";
+
+let buttonContainer =
document.createElement("div");
+
+//panel local style
+const styles = {
+ button: styleMap({ ...textCenter(), ...padding() }),
+};
+
+const addMeToYourFriendsDiv = (
+ subject: rdf.NamedNode,
+ context: DataBrowserContext
+): TemplateResult => {
+ buttonContainer = context.dom.createElement("div");
+ const button = createAddMeToYourFriendsButton(subject, context);
+ buttonContainer.appendChild(button);
+ return html` ${buttonContainer}
`;
+};
+
+const createAddMeToYourFriendsButton = (
+ subject: rdf.NamedNode,
+ context: DataBrowserContext
+): HTMLButtonElement => {
+ const button = widgets.button(
+ context.dom,
+ undefined,
+ logInAddMeToYourFriendsButtonText,
+ setButtonHandler, //sets an onclick event listener
+ {
+ needsBorder: true,
+ }
+ );
+
+ function setButtonHandler(event) {
+ event.preventDefault();
+ saveNewFriend(subject, context)
+ .then(() => {
+ clearPreviousMessage(buttonContainer);
+ mention(buttonContainer, friendWasAddedSuccesMessage);
+ refreshButton();
+ })
+ .catch((error) => {
+ clearPreviousMessage(buttonContainer);
+ //else UI.widgets.complain(buttonContainer, message); //displays an error message at the top of the window
+ complain(buttonContainer, context, error);
+ });
+ }
+
+ button.refresh = refreshButton();
+
+ function refreshButton() {
+ const me = authn.currentUser();
+ const store = context.session.store;
+
+ if (checkIfAnyUserLoggedIn(me)) {
+ checkIfFriendExists(store, me, subject).then((friendExists) => {
+ if (friendExists) {
+ //logged in and friend exists or friend was just added
+ button.innerHTML = friendExistsAlreadyButtonText.toUpperCase();
+ button.setAttribute("class", "textButton-0-1-3"); //style of 'Primary' UI button with needsBorder=true
+ } else {
+ //logged in and friend does not exist yet
+ button.innerHTML = addMeToYourFriendsButtonText.toUpperCase();
+ button.setAttribute("class", "textButton-0-1-2"); //style of 'Primary' UI button with needsBorder=false
+ }
+ });
+ } else {
+ //not logged in
+ button.innerHTML = logInAddMeToYourFriendsButtonText.toUpperCase();
+ button.setAttribute("class", "textButton-0-1-3"); //style of 'Primary' UI button with needsBorder=false
+ }
+ }
+
+ return button;
+};
+
+async function saveNewFriend(subject: rdf.NamedNode, context: DataBrowserContext): Promise {
+ const me = authn.currentUser();
+ const store = context.session.store;
+
+ if (checkIfAnyUserLoggedIn(me)) {
+ if (!(await checkIfFriendExists(store, me, subject))) {
+ //if friend does not exist, we add her/him
+ await store.fetcher.load(me);
+ const updater = store.updater;
+ const toBeInserted = [rdf.st(me, ns.foaf("knows"), subject, me.doc())];
+ try {
+ await updater.update([], toBeInserted);
+ } catch (error) {
+ let errorMessage = error;
+ if (errorMessage.toString().includes("Unauthenticated"))
+ errorMessage = userNotLoggedInErrorMessage;
+ throw new Error(errorMessage);
+ }
+ } else throw new Error(friendExistsMessage);
+ } else throw new Error(userNotLoggedInErrorMessage);
+}
+
+function checkIfAnyUserLoggedIn(me: rdf.NamedNode): boolean {
+ if (me) return true;
+ else return false;
+}
+
+async function checkIfFriendExists(
+ store: LiveStore,
+ me: rdf.NamedNode,
+ subject: rdf.NamedNode
+): Promise {
+ await store.fetcher.load(me);
+ if (store.whether(me, ns.foaf("knows"), subject, me.doc()) === 0) return false;
+ else return true;
+}
+
+//Because the code has unhandled Promises we still want to signal the user a message.
+//Console will contain actual error.
+window.addEventListener("unhandledrejection", function () {
+ clearPreviousMessage(buttonContainer);
+ buttonContainer.appendChild(widgets.errorMessageBlock(window.document, internalErrorMessage));
+});
+
+export {
+ addMeToYourFriendsDiv,
+ createAddMeToYourFriendsButton,
+ saveNewFriend,
+ checkIfAnyUserLoggedIn,
+ checkIfFriendExists,
+};
diff --git a/src/addMeToYourFriendsHelper.ts b/src/addMeToYourFriendsHelper.ts
new file mode 100644
index 0000000..165f068
--- /dev/null
+++ b/src/addMeToYourFriendsHelper.ts
@@ -0,0 +1,30 @@
+import { DataBrowserContext } from "pane-registry";
+import { widgets } from "solid-ui";
+
+function complain(
+ buttonContainer: HTMLDivElement,
+ context: DataBrowserContext,
+ error: string
+): void {
+ buttonContainer.appendChild(widgets.errorMessageBlock(context.dom, error));
+}
+
+//TODO create positive frontend message component on UI
+function mention(buttonContainer: HTMLDivElement, message: string): void {
+ const positiveFrontendMessageDiv = document.createElement("div");
+ positiveFrontendMessageDiv.setAttribute(
+ "style",
+ "margin: 0.1em; padding: 0.5em; border: 0.05em solid gray; background-color: #efe; color:black;"
+ );
+ //positiveFrontendMessageDiv.setAttribute('style', UI.style.messageBodyStyle) -> using UI but missing green backgroung color
+ positiveFrontendMessageDiv.innerHTML = message;
+ buttonContainer.appendChild(positiveFrontendMessageDiv);
+}
+
+function clearPreviousMessage(buttonContainer: HTMLDivElement): void {
+ while (buttonContainer.childNodes.length > 1) {
+ buttonContainer.removeChild(buttonContainer.lastChild);
+ }
+}
+
+export { complain, mention, clearPreviousMessage };
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-your-friends-functions.test.ts b/src/integration-tests/add-me-to-your-friends-functions.test.ts
new file mode 100644
index 0000000..59caf3e
--- /dev/null
+++ b/src/integration-tests/add-me-to-your-friends-functions.test.ts
@@ -0,0 +1,57 @@
+import { context, subject } from "./setup";
+import { addMeToYourFriendsDiv, checkIfAnyUserLoggedIn, checkIfFriendExists, createAddMeToYourFriendsButton, saveNewFriend } from "../addMeToYourFriends";
+
+describe("add-me-to-your-friends functions", () => {
+ describe("addMeToYourFriendsDiv", () => {
+ it("exists", () => {
+ expect(addMeToYourFriendsDiv).toBeInstanceOf(Function);
+ });
+
+ it("runs", () => {
+ expect(addMeToYourFriendsDiv(subject, context)).toBeTruthy();
+ });
+ });
+
+ describe("createAddMeToYourFriendsButton", () => {
+ it("exists", () => {
+ expect(createAddMeToYourFriendsButton).toBeInstanceOf(Function);
+ });
+
+ it("runs", () => {
+ expect(createAddMeToYourFriendsButton(subject, context)).toBeTruthy();
+ });
+ });
+
+ describe("saveNewFriend", () => {
+ it("exists", () => {
+ expect(saveNewFriend).toBeInstanceOf(Function);
+ });
+
+ });
+
+ describe("checkIfAnyUserLoggedIn", () => {
+ it("exists", () => {
+ expect(checkIfAnyUserLoggedIn).toBeInstanceOf(Function);
+ });
+
+ it("runs", () => {
+ expect(checkIfAnyUserLoggedIn(subject)).toBe(true);
+ expect(checkIfAnyUserLoggedIn(null)).toBe(false);
+ expect(checkIfAnyUserLoggedIn(undefined)).toBe(false);
+ });
+ });
+
+ describe("checkIfFriendExists", () => {
+ it("exists", () => {
+ expect(checkIfFriendExists).toBeInstanceOf(Function);
+ });
+
+ it("runs", () => {
+ expect(checkIfFriendExists(context.session.store, subject, subject)).toBeTruthy();
+ expect(checkIfFriendExists(context.session.store, subject, subject)).toBeInstanceOf(Promise);
+ });
+ });
+
+ });
+
+
diff --git a/src/integration-tests/add-me-to-your-friends-helper.test.ts b/src/integration-tests/add-me-to-your-friends-helper.test.ts
new file mode 100644
index 0000000..95144c9
--- /dev/null
+++ b/src/integration-tests/add-me-to-your-friends-helper.test.ts
@@ -0,0 +1,46 @@
+import { clearPreviousMessage, complain, mention } from "../addMeToYourFriendsHelper";
+import { context } from "./setup";
+
+describe("add me to your friends helper functions", () => {
+ let buttonContainer: HTMLDivElement;
+ let error: string;
+
+ beforeAll(() => {
+ buttonContainer = context.dom.createElement("div");
+ const button = context.dom.createElement("button");
+ buttonContainer.appendChild(button);
+ error = "error";
+ });
+ describe("complain", () => {
+ it("exists", () => {
+ expect(complain).toBeInstanceOf(Function);
+ });
+
+ it("runs", () => {
+ expect(complain(buttonContainer, context, error)).toEqual(undefined);
+ expect(buttonContainer.childNodes.length).toBe(2);
+ });
+ });
+
+ describe("mention", () => {
+ it("exists", () => {
+ expect(mention).toBeInstanceOf(Function);
+ });
+
+ it("runs", () => {
+ expect(mention(buttonContainer, error)).toEqual(undefined);
+ expect(buttonContainer.childNodes.length).toBe(3);
+ });
+ });
+
+ describe("clearPreviousMessage", () => {
+ it("exists", () => {
+ expect(clearPreviousMessage).toBeInstanceOf(Function);
+ });
+
+ it("runs", () => {
+ expect(clearPreviousMessage(buttonContainer)).toEqual(undefined);
+ expect(buttonContainer.childNodes.length).toBe(1);
+ });
+ });
+});
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..662a0b2
--- /dev/null
+++ b/src/integration-tests/add-me-to-your-friends.test.ts
@@ -0,0 +1,27 @@
+import { context, subject } from "./setup";
+import pane from "../";
+import { findByText, fireEvent } from "@testing-library/dom";
+import { logInAddMeToYourFriendsButtonText, userNotLoggedInErrorMessage } from "../texts";
+
+describe("add-me-to-your-friends pane", () => {
+ 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, logInAddMeToYourFriendsButtonText.toUpperCase());
+ expect(button).not.toBeNull();
+ });
+
+ it("saveNewFriend with user NOT logged in", async () => {
+ const button = await findByText(result, logInAddMeToYourFriendsButtonText.toUpperCase());
+ fireEvent.click(button);
+ const errorMessage = await findByText(result, userNotLoggedInErrorMessage);
+ expect(errorMessage).not.toBeNull();
+ expect(button).toThrowError;
+ });
+ });
+});
+
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", () => {
diff --git a/src/texts.ts b/src/texts.ts
new file mode 100644
index 0000000..5828721
--- /dev/null
+++ b/src/texts.ts
@@ -0,0 +1,17 @@
+//ERRORS & SUCCESS
+//Same 'not logged in' error message like on 'Chat with me' button
+export const userNotLoggedInErrorMessage = "Current user not found! Not logged in?";
+export const internalErrorMessage = "An internal error occured!";
+export const friendWasAddedSuccesMessage = "Friend was added!";
+
+//OTHER
+export const friendExistsMessage = "Friend already exists";
+export const loadingMessage = "Loading...";
+
+//BUTTONS
+export const addMeToYourFriendsButtonText = "Add me to your friend list";
+export const logInAddMeToYourFriendsButtonText = "Login to add me to your friend list";
+export const friendExistsAlreadyButtonText = "Already part of friend list";
+export const chatWithMeButtonText = "Chat with me";
+
+