Skip to content
This repository has been archived by the owner on Nov 27, 2022. It is now read-only.

Commit

Permalink
Add DislikeButton and FollowButton.
Browse files Browse the repository at this point in the history
  • Loading branch information
RubenVerborgh committed Mar 20, 2019
1 parent 70db965 commit 3ca5ac9
Show file tree
Hide file tree
Showing 10 changed files with 543 additions and 23 deletions.
2 changes: 1 addition & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@
max-len: off,
max-lines: off,
max-nested-callbacks: error,
max-params: error,
max-params: [ error, { max: 5 } ],
max-statements: off,
max-statements-per-line: error,
multiline-comment-style: off,
Expand Down
7 changes: 6 additions & 1 deletion demo/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,14 @@ body {
.solid.activity:hover {
text-decoration: underline;
}
.solid.activity::before {
display: inline-block;
}
.solid.like::before {
content: '👍 ';
display: inline-block;
}
.solid.dislike::before {
content: '👎 ';
}

img.profile {
Expand Down
26 changes: 22 additions & 4 deletions src/components/ActivityButton.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,14 @@ const { as } = data.context;
export default function ActivityButton({
activityType = `${as}Like`,
object = `[${window.location.href}]`,
children,
shortName = /\w*$/.exec(activityType)[0],
className = `solid activity ${shortName.toLowerCase()}`,
suggestActivity = shortName,
displayActivity = suggestActivity,
activateText = shortName,
deactivateText = activateText,
activateLabel = children ? [activateText, ' ', children] : activateText,
deactivateLabel = children ? [deactivateText, ' ', children] : deactivateText,
...props
}) {
// Look up a possibly existing activity
object = srcToLDflex(object);
Expand Down Expand Up @@ -45,8 +49,22 @@ export default function ActivityButton({
// Return the activity button
className = `${className} ${exists ? 'performed' : ''}`;
return (
<button className={className} onClick={toggleActivity}>
{ exists ? displayActivity : suggestActivity }
<button className={className} onClick={toggleActivity} {...props}>
{ exists ? deactivateLabel : activateLabel }
</button>
);
}

// Internal helper for creating custom activity buttons
export function customActivityButton(type, activate, deactivate, deactivateNoChildren) {
const activityType = `${as}${type}`;
return ({
object,
children = object ? null : 'this page',
activateText = activate,
deactivateText = children ? deactivate : deactivateNoChildren,
...props
}) =>
<ActivityButton {...props}
{...{ activityType, object, children, activateText, deactivateText }} />;
}
4 changes: 4 additions & 0 deletions src/components/DislikeButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { customActivityButton } from './ActivityButton';

/** Button to view and perform a "Dislike" action on an item. */
export default customActivityButton('Dislike', 'Dislike', 'You disliked', 'Disliked');
4 changes: 4 additions & 0 deletions src/components/FollowButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { customActivityButton } from './ActivityButton';

/** Button to view and perform a "Follow" action on an item. */
export default customActivityButton('Follow', 'Follow', 'You follow', 'Following');
19 changes: 2 additions & 17 deletions src/components/LikeButton.jsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,4 @@
import React from 'react';
import data from '@solid/query-ldflex';
import ActivityButton from './ActivityButton';

const { as } = data.context;
import { customActivityButton } from './ActivityButton';

/** Button to view and perform a "Like" action on an item. */
export default function LikeButton({
object,
children = object ? null : 'this page',
likeText = 'Like',
likedText = children ? 'You liked' : 'Liked',
likeLabel = children ? [likeText, ' ', children] : likeText,
likedLabel = children ? [likedText, ' ', children] : likedText,
...props
}) {
return <ActivityButton activityType={`${as}Like`} object={object}
suggestActivity={likeLabel} displayActivity={likedLabel} {...props} />;
}
export default customActivityButton('Like', 'Like', 'You liked', 'Liked');
6 changes: 6 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import List from './components/List';
import LiveUpdate from './components/LiveUpdate';
import ActivityButton from './components/ActivityButton';
import LikeButton from './components/LikeButton';
import DislikeButton from './components/DislikeButton';
import FollowButton from './components/FollowButton';

import UpdateContext from './UpdateContext';

Expand Down Expand Up @@ -57,6 +59,10 @@ export {
ActivityButton,
LikeButton,
LikeButton as Like,
DislikeButton,
DislikeButton as Dislike,
FollowButton,
FollowButton as Follow,

UpdateContext,
};
247 changes: 247 additions & 0 deletions test/components/DislikeButton-test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
import React from 'react';
import { DislikeButton } from '../../src/';
import { render, fireEvent, cleanup } from 'react-testing-library';
import MockPromise from 'jest-mock-promise';
import data from '@solid/query-ldflex';
import useLDflex from '../../src/hooks/useLDflex';

jest.mock('../../src/hooks/useLDflex', () => require('../__mocks__/useLDflex'));

const currentUrl = 'https://example.org/page/#fragment';
const dislike = 'https://www.w3.org/ns/activitystreams#Dislike';

describe('A DislikeButton', () => {
let button;
beforeAll(() => {
window.location.href = currentUrl;
});
afterEach(cleanup);

describe('without attributes', () => {
const findExpression = `[${currentUrl}].findActivity("${dislike}")`;
const createExpression = `[${currentUrl}].createActivity("${dislike}")`;
const deleteExpression = `[${currentUrl}].deleteActivity("${dislike}")`;

beforeEach(() => {
data.resolve.mockClear();
const { container } = render(<DislikeButton/>);
button = container.firstChild;
});

it('has the "solid" class', () => {
expect(button).toHaveClass('solid');
});

it('has the "activity" class', () => {
expect(button).toHaveClass('solid');
});

it('has the "dislike" class', () => {
expect(button).toHaveClass('dislike');
});

it('does not have the "performed" class', () => {
expect(button).not.toHaveClass('performed');
});

it('has "Dislike this page" as a label', () => {
expect(button).toHaveProperty('innerHTML', 'Dislike this page');
});

describe('when no activity exists', () => {
beforeEach(() => {
useLDflex.resolve(findExpression, undefined);
});

it('has "Dislike this page" as a label', () => {
expect(button).toHaveProperty('innerHTML', 'Dislike this page');
});

it('does not have the "performed" class', () => {
expect(button).not.toHaveClass('performed');
});

describe('when clicked', () => {
let activity;
beforeEach(() => {
activity = new MockPromise();
data.resolve.mockReturnValue(activity);
fireEvent.click(button);
});

it('has "You disliked this page" as a label', () => {
expect(button).toHaveProperty('innerHTML', 'You disliked this page');
});

it('has the "performed" class', () => {
expect(button).toHaveClass('performed');
});

it('creates an activity', () => {
expect(data.resolve).toHaveBeenCalledWith(createExpression);
});

describe('when activity creation succeeds', () => {
beforeEach(() => {
// mute `act` warning caused by asynchronous `reject`,
// since no workaround currently exists
// https://github.com/facebook/jest/issues/7151
console.mute();
activity.resolve({});
});
afterEach(() => {
console.unmute();
});

it('has "You disliked this page" as a label', () => {
expect(button).toHaveProperty('innerHTML', 'You disliked this page');
});

it('has the "performed" class', () => {
expect(button).toHaveClass('performed');
});
});

describe('when activity creation fails', () => {
beforeEach(() => {
console.mute();
activity.reject(new Error());
});
afterEach(() => {
console.unmute();
});

it('has "Dislike this page" as a label', () => {
expect(button).toHaveProperty('innerHTML', 'Dislike this page');
});

it('does not have the "performed" class', () => {
expect(button).not.toHaveClass('performed');
});
});
});
});

describe('when an activity exists', () => {
beforeEach(() => {
useLDflex.resolve(findExpression, {});
});

it('has "You disliked this page" as a label', () => {
expect(button).toHaveProperty('innerHTML', 'You disliked this page');
});

it('has the "performed" class', () => {
expect(button).toHaveClass('performed');
});

describe('when clicked', () => {
let activity;
beforeEach(() => {
activity = new MockPromise();
data.resolve.mockReturnValue(activity);
fireEvent.click(button);
});

it('has "Dislike this page" as a label', () => {
expect(button).toHaveProperty('innerHTML', 'Dislike this page');
});

it('does not have the "performed" class', () => {
expect(button).not.toHaveClass('performed');
});

it('creates an activity', () => {
expect(data.resolve).toHaveBeenCalledWith(deleteExpression);
});

describe('when activity creation succeeds', () => {
beforeEach(() => {
console.mute();
activity.resolve({});
});
afterEach(() => {
console.unmute();
});

it('has "Dislike this page" as a label', () => {
expect(button).toHaveProperty('innerHTML', 'Dislike this page');
});

it('does not have the "performed" class', () => {
expect(button).not.toHaveClass('performed');
});
});

describe('when activity creation fails', () => {
beforeEach(() => {
console.mute();
activity.reject(new Error());
});
afterEach(() => {
console.unmute();
});

it('has "You disliked this page" as a label', () => {
expect(button).toHaveProperty('innerHTML', 'You disliked this page');
});

it('has the "performed" class', () => {
expect(button).toHaveClass('performed');
});
});
});
});
});

describe('with an object', () => {
const object = 'https://example.org/#thing';
const findExpression = `["${object}"].findActivity("${dislike}")`;
const createExpression = `["${object}"].createActivity("${dislike}")`;

beforeEach(() => {
data.resolve.mockClear();
const { container } = render(<DislikeButton object={object}/>);
button = container.firstChild;
});

describe('when no activity exists', () => {
beforeEach(() => {
useLDflex.resolve(findExpression, undefined);
});

it('has "Dislike" as a label', () => {
expect(button).toHaveProperty('innerHTML', 'Dislike');
});

describe('when clicked', () => {
let activity;
beforeEach(() => {
activity = new MockPromise();
data.resolve.mockReturnValue(activity);
fireEvent.click(button);
});

it('has "Disliked" as a label', () => {
expect(button).toHaveProperty('innerHTML', 'Disliked');
});

it('creates an activity', () => {
expect(data.resolve).toHaveBeenCalledWith(createExpression);
});
});
});
});

describe('with children', () => {
beforeEach(() => {
data.resolve.mockClear();
const { container } = render(<DislikeButton>this thing</DislikeButton>);
button = container.firstChild;
});

it('has "Dislike this thing" as a label', () => {
expect(button).toHaveProperty('innerHTML', 'Dislike this thing');
});
});
});

0 comments on commit 3ca5ac9

Please sign in to comment.