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"; + +