Commit 2b4ded56 authored by Robert Knight's avatar Robert Knight

Convert `<group-list-item-out-of-scope>` to React

The tests have been modified to work in terms of the UI instead of
internal controller methods, since there is no controller instance any
more.
parent a3c6dd46
'use strict';
const classnames = require('classnames');
const { Fragment, createElement } = require('preact');
const { useState } = require('preact/hooks');
const propTypes = require('prop-types');
const {
orgName,
trackViewGroupActivity,
} = require('../util/group-list-item-common');
// @ngInject
function GroupListItemOutOfScopeController(analytics) {
// Track whether the group details are expanded.
this.isDetailsExpanded = false;
function GroupListItemOutOfScope({ analytics, group }) {
const [isExpanded, setExpanded] = useState(false);
/**
* Toggle the expanded setting on un-selectable groups.
*/
this.toggleGroupDetails = function(event) {
const toggleGroupDetails = event => {
event.stopPropagation();
this.isDetailsExpanded = !this.isDetailsExpanded;
};
this.orgName = function() {
return orgName(this.group);
setExpanded(!isExpanded);
};
this.trackViewGroupActivity = function() {
const groupOrgName = orgName(group);
const trackViewActivity = event => {
event.stopPropagation();
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,
})}
>
<svg
className="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
className="group-list-item__name-link"
href=""
title="Group not annotatable on this domain."
>
{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>
<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 = {
controller: GroupListItemOutOfScopeController,
controllerAs: 'vm',
bindings: {
group: '<',
},
template: require('../templates/group-list-item-out-of-scope.html'),
GroupListItemOutOfScope.propTypes = {
group: propTypes.object,
analytics: propTypes.object,
};
GroupListItemOutOfScope.injectedProps = ['analytics'];
module.exports = GroupListItemOutOfScope;
'use strict';
const angular = require('angular');
const { mount } = require('enzyme');
const preact = require('preact');
const { createElement } = require('preact');
const proxyquire = require('proxyquire');
const util = require('../../directive/test/util');
const { events } = require('../../services/analytics');
describe('groupListItemOutOfScope', () => {
describe('GroupListItemOutOfScope', () => {
let fakeAnalytics;
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(() => {
fakeGroupListItemCommon = {
......@@ -16,18 +34,14 @@ describe('groupListItemOutOfScope', () => {
trackViewGroupActivity: sinon.stub(),
};
// Return groupListItemOutOfScope with groupListItemCommon stubbed out.
const groupListItemOutOfScope = proxyquire(
'../group-list-item-out-of-scope',
{
GroupListItemOutOfScope = proxyquire('../group-list-item-out-of-scope', {
// Use same instance of Preact module in tests and mocked module.
// See https://robertknight.me.uk/posts/browserify-dependency-mocking/
preact,
'../util/group-list-item-common': fakeGroupListItemCommon,
'@noCallThru': true,
}
);
angular
.module('app', [])
.component('groupListItemOutOfScope', groupListItemOutOfScope);
});
});
beforeEach(() => {
......@@ -35,21 +49,23 @@ describe('groupListItemOutOfScope', () => {
track: sinon.stub(),
events,
};
angular.mock.module('app', { analytics: fakeAnalytics });
});
const createGroupListItemOutOfScope = fakeGroup => {
return util.createDirective(document, 'groupListItemOutOfScope', {
group: fakeGroup,
});
return mount(
<GroupListItemOutOfScope analytics={fakeAnalytics} group={fakeGroup} />
);
};
it('calls groupListItemCommon.trackViewGroupActivity when trackViewGroupActivity is called', () => {
const fakeGroup = { id: 'groupid' };
it('calls trackViewGroupActivity when "Go to group page" link is clicked', () => {
const wrapper = createGroupListItemOutOfScope(fakeGroup);
toggle(wrapper);
const element = createGroupListItemOutOfScope(fakeGroup);
element.ctrl.trackViewGroupActivity();
const link = wrapper
.find('a')
.filterWhere(link => link.text() === 'Go to group page');
link.simulate('click');
assert.calledWith(
fakeGroupListItemCommon.trackViewGroupActivity,
......@@ -57,50 +73,26 @@ describe('groupListItemOutOfScope', () => {
);
});
it('returns groupListItemCommon.orgName when orgName is called', () => {
const fakeGroup = { id: 'groupid', organization: { name: 'org' } };
it('sets alt text of logo', () => {
fakeGroupListItemCommon.orgName
.withArgs(fakeGroup)
.returns(fakeGroup.organization.name);
const element = createGroupListItemOutOfScope(fakeGroup);
const orgName = element.ctrl.orgName();
const wrapper = createGroupListItemOutOfScope(fakeGroup);
const orgName = wrapper.find('img').props().alt;
assert.calledWith(fakeGroupListItemCommon.orgName, fakeGroup);
assert.equal(orgName, fakeGroup.organization.name);
});
describe('toggleGroupDetails', () => {
it('sets the default expanded value to false', () => {
const fakeGroup = { id: 'groupid' };
it('toggles expanded state when clicked', () => {
const wrapper = createGroupListItemOutOfScope(fakeGroup);
const element = createGroupListItemOutOfScope(fakeGroup);
assert.isFalse(wrapper.exists('.expanded'));
assert.isFalse(element.ctrl.isDetailsExpanded);
});
it('toggles the expanded value', () => {
const fakeGroup = { id: 'groupid' };
const element = createGroupListItemOutOfScope(fakeGroup);
const fakeEvent = { stopPropagation: sinon.stub() };
element.ctrl.toggleGroupDetails(fakeEvent);
assert.isTrue(element.ctrl.isDetailsExpanded);
element.ctrl.toggleGroupDetails(fakeEvent);
assert.isFalse(element.ctrl.isDetailsExpanded);
});
it('stops the event from propagating when toggling', () => {
const fakeGroup = { id: 'groupid' };
const element = createGroupListItemOutOfScope(fakeGroup);
const fakeEvent = { stopPropagation: sinon.spy() };
toggle(wrapper);
assert.isTrue(wrapper.exists('.expanded'));
element.ctrl.toggleGroupDetails(fakeEvent);
sinon.assert.called(fakeEvent.stopPropagation);
});
toggle(wrapper);
assert.isFalse(wrapper.exists('.expanded'));
});
});
......@@ -38,6 +38,8 @@ require('autofill-event');
// Enable debugging checks for Preact.
require('preact/debug');
const wrapReactComponent = require('./util/wrap-react-component');
// Setup Angular integration for Raven
if (appConfig.raven) {
raven.angularModule(angular);
......@@ -157,7 +159,7 @@ function startAngularApp(config) {
.component('groupListItem', require('./components/group-list-item'))
.component(
'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('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