Skip to content

Commit

Permalink
Merge pull request #15 from theRealImy/add-me-to-your-friends
Browse files Browse the repository at this point in the history
implemneted feature 'add me to your friends' #8
  • Loading branch information
timbl authored Oct 7, 2021
2 parents 8dc3e57 + abe6d4d commit 26e8e2b
Show file tree
Hide file tree
Showing 12 changed files with 329 additions and 6 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
lib
node_modules
node_modules
.history
.vscode
5 changes: 3 additions & 2 deletions src/ChatWithMe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand All @@ -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;
Expand All @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions src/ProfileView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -37,6 +38,7 @@ export const ProfileView = (
<div>
<div data-testid="profile-card" style="${styles.card}">
${ProfileCard(profileBasics)}
${addMeToYourFriendsDiv(subject, context)}
</div>
</div>
<div data-testid="friend-list" style="${styles.card}">
Expand Down
141 changes: 141 additions & 0 deletions src/addMeToYourFriends.ts
Original file line number Diff line number Diff line change
@@ -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 = <HTMLDivElement>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` <div style="${styles.button}">${buttonContainer}</div> `;
};

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<void> {
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<boolean> {
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,
};
30 changes: 30 additions & 0 deletions src/addMeToYourFriendsHelper.ts
Original file line number Diff line number Diff line change
@@ -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 = <HTMLDivElement>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 };
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { presentCV} from "./CVPresenter";
import { presentCV} from "../CVPresenter";
import { blankNode, sym } from "rdflib";
import { ns, store } from "solid-ui";

Expand Down
57 changes: 57 additions & 0 deletions src/integration-tests/add-me-to-your-friends-functions.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});

});


46 changes: 46 additions & 0 deletions src/integration-tests/add-me-to-your-friends-helper.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
});
27 changes: 27 additions & 0 deletions src/integration-tests/add-me-to-your-friends.test.ts
Original file line number Diff line number Diff line change
@@ -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;
});
});
});

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { presentProfile } from "./presenter";
import { presentProfile } from "../presenter";
import { blankNode, sym } from "rdflib";
import { ns, store } from "solid-ui";

Expand Down
2 changes: 1 addition & 1 deletion src/integration-tests/profile-card.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down
17 changes: 17 additions & 0 deletions src/texts.ts
Original file line number Diff line number Diff line change
@@ -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";


0 comments on commit 26e8e2b

Please sign in to comment.