Unverified Commit 5bf8b46c authored by Robert Knight's avatar Robert Knight Committed by GitHub

Merge pull request #1029 from hypothesis/group-list-item-oos-react

Convert `<group-list-item-out-of-scope>` to React
parents 14b2bd17 c2f99fdc
'use strict'; 'use strict';
const classnames = require('classnames');
const { Fragment, createElement } = require('preact');
const { useState } = require('preact/hooks');
const propTypes = require('prop-types');
const { const {
orgName, orgName,
trackViewGroupActivity, trackViewGroupActivity,
} = require('../util/group-list-item-common'); } = require('../util/group-list-item-common');
// @ngInject const outOfScopeIcon = (
function GroupListItemOutOfScopeController(analytics) { <svg
// Track whether the group details are expanded. className="svg-icon group-list-item-out-of-scope__icon--unavailable"
this.isDetailsExpanded = false; xmlns="http://www.w3.org/2000/svg"
width="100%"
height="100%"
viewBox="0 0 24 24"
>
<path fill="none" d="M0 0h24v24H0V0z" />
<path d="M11 15h2v2h-2zm0-8h2v6h-2zm.99-5C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z" />
</svg>
);
/** function GroupListItemOutOfScope({ analytics, group }) {
* Toggle the expanded setting on un-selectable groups. const [isExpanded, setExpanded] = useState(false);
*/
this.toggleGroupDetails = function(event) {
event.stopPropagation();
this.isDetailsExpanded = !this.isDetailsExpanded;
};
this.orgName = function() { const toggleGroupDetails = event => {
return orgName(this.group); event.stopPropagation();
setExpanded(!isExpanded);
}; };
this.trackViewGroupActivity = function() { const groupOrgName = orgName(group);
const trackViewActivity = event => {
event.stopPropagation();
trackViewGroupActivity(analytics); trackViewGroupActivity(analytics);
}; };
return (
<div
className="group-list-item__item group-list-item-out-of-scope__item"
onClick={toggleGroupDetails}
tabIndex="0"
>
{/* the group icon */}
<div className="group-list-item__icon-container">
{group.logo && (
<img
className="group-list-item__icon group-list-item__icon--organization"
alt={groupOrgName}
src={group.logo}
/>
)}
</div>
{/* the group name */}
<div
className={classnames({
'group-list-item-out-of-scope__details': true,
expanded: isExpanded,
})}
>
{outOfScopeIcon}
<a
className="group-list-item__name-link"
href=""
title="This URL cannot be annotated in this group."
>
{group.name}
</a>
<br />
{/* explanation of why group is not available */}
{!isExpanded && (
<p className="group-list-item-out-of-scope__details-toggle">
Why is this group unavailable?
</p>
)}
{isExpanded && (
<Fragment>
<p className="group-list-item-out-of-scope__details-unavailable-message">
This group has been restricted to selected URLs by its
administrators.
</p>
{group.links.html && (
<p className="group-list-item-out-of-scope__details-actions">
<a
className="button button--text group-list-item-out-of-scope__details-group-page-link"
href={group.links.html}
target="_blank"
onClick={trackViewActivity}
rel="noopener noreferrer"
>
Go to group page
</a>
</p>
)}
</Fragment>
)}
</div>
</div>
);
} }
module.exports = { GroupListItemOutOfScope.propTypes = {
controller: GroupListItemOutOfScopeController, group: propTypes.object,
controllerAs: 'vm',
bindings: { analytics: propTypes.object,
group: '<',
},
template: require('../templates/group-list-item-out-of-scope.html'),
}; };
GroupListItemOutOfScope.injectedProps = ['analytics'];
module.exports = GroupListItemOutOfScope;
'use strict'; 'use strict';
const angular = require('angular'); const { mount } = require('enzyme');
const preact = require('preact');
const { createElement } = require('preact');
const proxyquire = require('proxyquire'); const proxyquire = require('proxyquire');
const util = require('../../directive/test/util');
const { events } = require('../../services/analytics'); const { events } = require('../../services/analytics');
describe('groupListItemOutOfScope', () => { describe('GroupListItemOutOfScope', () => {
let fakeAnalytics; let fakeAnalytics;
let fakeGroupListItemCommon; let fakeGroupListItemCommon;
let GroupListItemOutOfScope;
const fakeGroup = {
id: 'groupid',
links: {
html: 'https://hypothes.is/groups/groupid',
},
logo: 'dummy://hypothes.is/logo.svg',
organization: { name: 'org' },
};
// Click on the item to expand or collapse it.
const toggle = wrapper =>
wrapper
.find('div')
.first()
.simulate('click');
before(() => { before(() => {
fakeGroupListItemCommon = { fakeGroupListItemCommon = {
...@@ -16,18 +34,14 @@ describe('groupListItemOutOfScope', () => { ...@@ -16,18 +34,14 @@ describe('groupListItemOutOfScope', () => {
trackViewGroupActivity: sinon.stub(), trackViewGroupActivity: sinon.stub(),
}; };
// Return groupListItemOutOfScope with groupListItemCommon stubbed out. GroupListItemOutOfScope = proxyquire('../group-list-item-out-of-scope', {
const groupListItemOutOfScope = proxyquire( // Use same instance of Preact module in tests and mocked module.
'../group-list-item-out-of-scope', // See https://robertknight.me.uk/posts/browserify-dependency-mocking/
{ preact,
'../util/group-list-item-common': fakeGroupListItemCommon, '../util/group-list-item-common': fakeGroupListItemCommon,
'@noCallThru': true, '@noCallThru': true,
} });
);
angular
.module('app', [])
.component('groupListItemOutOfScope', groupListItemOutOfScope);
}); });
beforeEach(() => { beforeEach(() => {
...@@ -35,21 +49,23 @@ describe('groupListItemOutOfScope', () => { ...@@ -35,21 +49,23 @@ describe('groupListItemOutOfScope', () => {
track: sinon.stub(), track: sinon.stub(),
events, events,
}; };
angular.mock.module('app', { analytics: fakeAnalytics });
}); });
const createGroupListItemOutOfScope = fakeGroup => { const createGroupListItemOutOfScope = fakeGroup => {
return util.createDirective(document, 'groupListItemOutOfScope', { return mount(
group: fakeGroup, <GroupListItemOutOfScope analytics={fakeAnalytics} group={fakeGroup} />
}); );
}; };
it('calls groupListItemCommon.trackViewGroupActivity when trackViewGroupActivity is called', () => { it('calls trackViewGroupActivity when "Go to group page" link is clicked', () => {
const fakeGroup = { id: 'groupid' }; const wrapper = createGroupListItemOutOfScope(fakeGroup);
const element = createGroupListItemOutOfScope(fakeGroup); toggle(wrapper);
element.ctrl.trackViewGroupActivity();
const link = wrapper
.find('a')
.filterWhere(link => link.text() === 'Go to group page');
link.simulate('click');
assert.calledWith( assert.calledWith(
fakeGroupListItemCommon.trackViewGroupActivity, fakeGroupListItemCommon.trackViewGroupActivity,
...@@ -57,50 +73,35 @@ describe('groupListItemOutOfScope', () => { ...@@ -57,50 +73,35 @@ describe('groupListItemOutOfScope', () => {
); );
}); });
it('returns groupListItemCommon.orgName when orgName is called', () => { it('does not show "Go to group page" link if the group has no HTML link', () => {
const fakeGroup = { id: 'groupid', organization: { name: 'org' } }; const group = { ...fakeGroup, links: {} };
const wrapper = createGroupListItemOutOfScope(group);
const link = wrapper
.find('a')
.filterWhere(link => link.text() === 'Go to group page');
assert.isFalse(link.exists());
});
it('sets alt text of logo', () => {
fakeGroupListItemCommon.orgName fakeGroupListItemCommon.orgName
.withArgs(fakeGroup) .withArgs(fakeGroup)
.returns(fakeGroup.organization.name); .returns(fakeGroup.organization.name);
const element = createGroupListItemOutOfScope(fakeGroup); const wrapper = createGroupListItemOutOfScope(fakeGroup);
const orgName = element.ctrl.orgName(); const orgName = wrapper.find('img').props().alt;
assert.calledWith(fakeGroupListItemCommon.orgName, fakeGroup);
assert.equal(orgName, fakeGroup.organization.name); assert.equal(orgName, fakeGroup.organization.name);
}); });
describe('toggleGroupDetails', () => { it('toggles expanded state when clicked', () => {
it('sets the default expanded value to false', () => { const wrapper = createGroupListItemOutOfScope(fakeGroup);
const fakeGroup = { id: 'groupid' };
const element = createGroupListItemOutOfScope(fakeGroup);
assert.isFalse(element.ctrl.isDetailsExpanded);
});
it('toggles the expanded value', () => {
const fakeGroup = { id: 'groupid' };
const element = createGroupListItemOutOfScope(fakeGroup); assert.isFalse(wrapper.exists('.expanded'));
const fakeEvent = { stopPropagation: sinon.stub() };
element.ctrl.toggleGroupDetails(fakeEvent); toggle(wrapper);
assert.isTrue(element.ctrl.isDetailsExpanded); assert.isTrue(wrapper.exists('.expanded'));
element.ctrl.toggleGroupDetails(fakeEvent); toggle(wrapper);
assert.isFalse(element.ctrl.isDetailsExpanded); assert.isFalse(wrapper.exists('.expanded'));
});
it('stops the event from propagating when toggling', () => {
const fakeGroup = { id: 'groupid' };
const element = createGroupListItemOutOfScope(fakeGroup);
const fakeEvent = { stopPropagation: sinon.spy() };
element.ctrl.toggleGroupDetails(fakeEvent);
sinon.assert.called(fakeEvent.stopPropagation);
});
}); });
}); });
...@@ -30,7 +30,6 @@ document.body.setAttribute('ng-csp', ''); ...@@ -30,7 +30,6 @@ document.body.setAttribute('ng-csp', '');
disableOpenerForExternalLinks(document.body); disableOpenerForExternalLinks(document.body);
const angular = require('angular'); const angular = require('angular');
const wrapReactComponent = require('./util/wrap-react-component');
// autofill-event relies on the existence of window.angular so // autofill-event relies on the existence of window.angular so
// it must be require'd after angular is first require'd // it must be require'd after angular is first require'd
...@@ -39,6 +38,8 @@ require('autofill-event'); ...@@ -39,6 +38,8 @@ require('autofill-event');
// Enable debugging checks for Preact. // Enable debugging checks for Preact.
require('preact/debug'); require('preact/debug');
const wrapReactComponent = require('./util/wrap-react-component');
// Setup Angular integration for Raven // Setup Angular integration for Raven
if (appConfig.raven) { if (appConfig.raven) {
raven.angularModule(angular); raven.angularModule(angular);
...@@ -161,7 +162,7 @@ function startAngularApp(config) { ...@@ -161,7 +162,7 @@ function startAngularApp(config) {
) )
.component( .component(
'groupListItemOutOfScope', 'groupListItemOutOfScope',
require('./components/group-list-item-out-of-scope') wrapReactComponent(require('./components/group-list-item-out-of-scope'))
) )
.component('groupListSection', require('./components/group-list-section')) .component('groupListSection', require('./components/group-list-section'))
.component('helpLink', require('./components/help-link')) .component('helpLink', require('./components/help-link'))
......
<div
class="group-list-item-out-of-scope__item"
ng-class="{'group-list-item__item': true, 'is-selected': false}"
ng-click="vm.toggleGroupDetails($event)"
tabindex="0"
>
<!-- the group icon !-->
<div class="group-list-item__icon-container">
<img
class="group-list-item__icon group-list-item__icon--organization"
alt="{{ vm.orgName() }}"
ng-src="{{ vm.group.logo }}"
ng-if="vm.group.logo"
/>
</div>
<!-- the group name and share link !-->
<div
ng-class="{'group-list-item-out-of-scope__details': true, expanded: vm.isDetailsExpanded}"
>
<svg
class="svg-icon group-list-item-out-of-scope__icon--unavailable"
xmlns="http://www.w3.org/2000/svg"
width="100%"
height="100%"
viewBox="0 0 24 24"
>
<path fill="none" d="M0 0h24v24H0V0z" />
<path
d="M11 15h2v2h-2zm0-8h2v6h-2zm.99-5C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"
/>
</svg>
<a
class="group-list-item__name-link"
href=""
title="Group not annotatable on this domain."
>
{{vm.group.name}} </a
><br />
<p
class="group-list-item-out-of-scope__details-toggle"
ng-if="!vm.isDetailsExpanded"
>
Why is this group unavailable?
</p>
<p
class="group-list-item-out-of-scope__details-unavailable-message"
ng-if="vm.isDetailsExpanded"
>
This group has been restricted to selected URLs by its administrators.
</p>
<p
class="group-list-item-out-of-scope__details-actions"
ng-if="vm.isDetailsExpanded"
>
<a
class="button button--text group-list-item-out-of-scope__details-group-page-link"
href="{{vm.group.links.html}}"
target="_blank"
ng-click="vm.trackViewGroupActivity()"
>Go to group page</a
>
</p>
</div>
</div>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment