Unverified Commit 05f003d3 authored by Robert Knight's avatar Robert Knight Committed by GitHub

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

Convert `<group-list-item>` component to React
parents ca1e0ed9 c1f7c449
'use strict'; 'use strict';
const { const classnames = require('classnames');
orgName, const propTypes = require('prop-types');
trackViewGroupActivity, const { createElement } = require('preact');
} = require('../util/group-list-item-common');
// @ngInject
function GroupListItemController(analytics, store) {
this.focusGroup = function() {
analytics.track(analytics.events.GROUP_SWITCH);
store.focusGroup(this.group.id);
};
this.isSelected = function() { const { orgName } = require('../util/group-list-item-common');
return this.group.id === store.focusedGroupId();
};
this.orgName = function() { function GroupListItem({ analytics, group, store }) {
return orgName(this.group); const focusGroup = () => {
analytics.track(analytics.events.GROUP_SWITCH);
store.focusGroup(group.id);
}; };
this.trackViewGroupActivity = function() { const isSelected = group.id === store.focusedGroupId();
trackViewGroupActivity(analytics); const groupOrgName = orgName(group);
};
return (
<div
className={classnames({
'group-list-item__item': true,
'is-selected': isSelected,
})}
onClick={focusGroup}
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="group-list-item__details">
<a
className="group-list-item__name-link"
href=""
title={
group.type === 'private'
? `Show and create annotations in ${group.name}`
: 'Show public annotations'
}
>
{group.name}
</a>
</div>
</div>
);
} }
module.exports = { GroupListItem.propTypes = {
controller: GroupListItemController, group: propTypes.object.isRequired,
controllerAs: 'vm',
bindings: { analytics: propTypes.object.isRequired,
group: '<', store: propTypes.object.isRequired,
},
template: require('../templates/group-list-item.html'),
}; };
GroupListItem.injectedProps = ['analytics', 'store'];
module.exports = GroupListItem;
'use strict'; 'use strict';
const angular = require('angular'); const { createElement } = require('preact');
const { mount } = require('enzyme');
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('groupListItem', () => { describe('GroupListItem', () => {
let fakeAnalytics; let fakeAnalytics;
let fakeStore; let fakeStore;
let fakeGroupListItemCommon; let fakeGroupListItemCommon;
let GroupListItem;
before(() => { before(() => {
fakeGroupListItemCommon = { fakeGroupListItemCommon = {
...@@ -17,13 +18,10 @@ describe('groupListItem', () => { ...@@ -17,13 +18,10 @@ describe('groupListItem', () => {
trackViewGroupActivity: sinon.stub(), trackViewGroupActivity: sinon.stub(),
}; };
// Return groupListItem with groupListItemCommon stubbed out. GroupListItem = proxyquire('../group-list-item', {
const groupListItem = proxyquire('../group-list-item', {
'../util/group-list-item-common': fakeGroupListItemCommon, '../util/group-list-item-common': fakeGroupListItemCommon,
'@noCallThru': true, '@noCallThru': true,
}); });
angular.module('app', []).component('groupListItem', groupListItem);
}); });
beforeEach(() => { beforeEach(() => {
...@@ -36,64 +34,54 @@ describe('groupListItem', () => { ...@@ -36,64 +34,54 @@ describe('groupListItem', () => {
track: sinon.stub(), track: sinon.stub(),
events, events,
}; };
angular.mock.module('app', {
analytics: fakeAnalytics,
store: fakeStore,
});
}); });
const createGroupListItem = fakeGroup => { const createGroupListItem = fakeGroup => {
return util.createDirective(document, 'groupListItem', { return mount(
group: fakeGroup, <GroupListItem
}); group={fakeGroup}
analytics={fakeAnalytics}
store={fakeStore}
/>
);
}; };
it('changes the focused group when group is clicked', () => { it('changes the focused group when group is clicked', () => {
const fakeGroup = { id: 'groupid' }; const fakeGroup = { id: 'groupid' };
const element = createGroupListItem(fakeGroup); const wrapper = createGroupListItem(fakeGroup);
const group = element.find('.group-list-item__item'); wrapper.find('.group-list-item__item').simulate('click');
group[0].click();
assert.calledWith(fakeStore.focusGroup, fakeGroup.id); assert.calledWith(fakeStore.focusGroup, fakeGroup.id);
assert.calledWith(fakeAnalytics.track, fakeAnalytics.events.GROUP_SWITCH); assert.calledWith(fakeAnalytics.track, fakeAnalytics.events.GROUP_SWITCH);
}); });
it('calls groupListItemCommon.trackViewGroupActivity when trackViewGroupActivity is called', () => { it('sets alt text for organization logo', () => {
const fakeGroup = { id: 'groupid' }; const fakeGroup = {
id: 'groupid',
const element = createGroupListItem(fakeGroup); // Dummy scheme to avoid actually trying to load image.
element.ctrl.trackViewGroupActivity(); logo: 'dummy://hypothes.is/logo.svg',
organization: { name: 'org' },
assert.calledWith( };
fakeGroupListItemCommon.trackViewGroupActivity,
fakeAnalytics
);
});
it('returns groupListItemCommon.orgName when orgName is called', () => {
const fakeGroup = { id: 'groupid', organization: { name: 'org' } };
fakeGroupListItemCommon.orgName fakeGroupListItemCommon.orgName
.withArgs(fakeGroup) .withArgs(fakeGroup)
.returns(fakeGroup.organization.name); .returns(fakeGroup.organization.name);
const element = createGroupListItem(fakeGroup); const wrapper = createGroupListItem(fakeGroup);
const orgName = element.ctrl.orgName(); const altText = wrapper.find('img').props().alt;
assert.equal(orgName, fakeGroup.organization.name); assert.equal(altText, fakeGroup.organization.name);
}); });
describe('isSelected', () => { describe('selected state', () => {
[ [
{ {
description: 'returns true if group is the focused group', description: 'is selected if group is the focused group',
focusedGroupId: 'groupid', focusedGroupId: 'groupid',
expectedIsSelected: true, expectedIsSelected: true,
}, },
{ {
description: 'returns false if group is not the focused group', description: 'is not selected if group is not the focused group',
focusedGroupId: 'other', focusedGroupId: 'other',
expectedIsSelected: false, expectedIsSelected: false,
}, },
...@@ -102,9 +90,12 @@ describe('groupListItem', () => { ...@@ -102,9 +90,12 @@ describe('groupListItem', () => {
fakeStore.focusedGroupId.returns(focusedGroupId); fakeStore.focusedGroupId.returns(focusedGroupId);
const fakeGroup = { id: 'groupid' }; const fakeGroup = { id: 'groupid' };
const element = createGroupListItem(fakeGroup); const wrapper = createGroupListItem(fakeGroup);
assert.equal(element.ctrl.isSelected(), expectedIsSelected); assert.equal(
wrapper.find('.group-list-item__item').hasClass('is-selected'),
expectedIsSelected
);
}); });
}); });
}); });
......
...@@ -30,6 +30,7 @@ document.body.setAttribute('ng-csp', ''); ...@@ -30,6 +30,7 @@ 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
...@@ -154,7 +155,10 @@ function startAngularApp(config) { ...@@ -154,7 +155,10 @@ function startAngularApp(config) {
.component('dropdownMenuBtn', require('./components/dropdown-menu-btn')) .component('dropdownMenuBtn', require('./components/dropdown-menu-btn'))
.component('excerpt', require('./components/excerpt')) .component('excerpt', require('./components/excerpt'))
.component('groupList', require('./components/group-list')) .component('groupList', require('./components/group-list'))
.component('groupListItem', require('./components/group-list-item')) .component(
'groupListItem',
wrapReactComponent(require('./components/group-list-item'))
)
.component( .component(
'groupListItemOutOfScope', 'groupListItemOutOfScope',
require('./components/group-list-item-out-of-scope') require('./components/group-list-item-out-of-scope')
......
<div
ng-class="{'group-list-item__item': true, 'is-selected': vm.isSelected()}"
ng-click="vm.focusGroup()"
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 class="group-list-item__details">
<a
class="group-list-item__name-link"
href=""
title="{{ vm.group.type === 'private' ? 'Show and create annotations in ' + vm.group.name : 'Show public annotations' }}"
>
{{vm.group.name}}
</a>
</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