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';
const {
orgName,
trackViewGroupActivity,
} = 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);
};
const classnames = require('classnames');
const propTypes = require('prop-types');
const { createElement } = require('preact');
this.isSelected = function() {
return this.group.id === store.focusedGroupId();
};
const { orgName } = require('../util/group-list-item-common');
this.orgName = function() {
return orgName(this.group);
function GroupListItem({ analytics, group, store }) {
const focusGroup = () => {
analytics.track(analytics.events.GROUP_SWITCH);
store.focusGroup(group.id);
};
this.trackViewGroupActivity = function() {
trackViewGroupActivity(analytics);
};
const isSelected = group.id === store.focusedGroupId();
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 = {
controller: GroupListItemController,
controllerAs: 'vm',
bindings: {
group: '<',
},
template: require('../templates/group-list-item.html'),
GroupListItem.propTypes = {
group: propTypes.object.isRequired,
analytics: propTypes.object.isRequired,
store: propTypes.object.isRequired,
};
GroupListItem.injectedProps = ['analytics', 'store'];
module.exports = GroupListItem;
'use strict';
const angular = require('angular');
const { createElement } = require('preact');
const { mount } = require('enzyme');
const proxyquire = require('proxyquire');
const util = require('../../directive/test/util');
const { events } = require('../../services/analytics');
describe('groupListItem', () => {
describe('GroupListItem', () => {
let fakeAnalytics;
let fakeStore;
let fakeGroupListItemCommon;
let GroupListItem;
before(() => {
fakeGroupListItemCommon = {
......@@ -17,13 +18,10 @@ describe('groupListItem', () => {
trackViewGroupActivity: sinon.stub(),
};
// Return groupListItem with groupListItemCommon stubbed out.
const groupListItem = proxyquire('../group-list-item', {
GroupListItem = proxyquire('../group-list-item', {
'../util/group-list-item-common': fakeGroupListItemCommon,
'@noCallThru': true,
});
angular.module('app', []).component('groupListItem', groupListItem);
});
beforeEach(() => {
......@@ -36,64 +34,54 @@ describe('groupListItem', () => {
track: sinon.stub(),
events,
};
angular.mock.module('app', {
analytics: fakeAnalytics,
store: fakeStore,
});
});
const createGroupListItem = fakeGroup => {
return util.createDirective(document, 'groupListItem', {
group: fakeGroup,
});
return mount(
<GroupListItem
group={fakeGroup}
analytics={fakeAnalytics}
store={fakeStore}
/>
);
};
it('changes the focused group when group is clicked', () => {
const fakeGroup = { id: 'groupid' };
const element = createGroupListItem(fakeGroup);
const group = element.find('.group-list-item__item');
group[0].click();
const wrapper = createGroupListItem(fakeGroup);
wrapper.find('.group-list-item__item').simulate('click');
assert.calledWith(fakeStore.focusGroup, fakeGroup.id);
assert.calledWith(fakeAnalytics.track, fakeAnalytics.events.GROUP_SWITCH);
});
it('calls groupListItemCommon.trackViewGroupActivity when trackViewGroupActivity is called', () => {
const fakeGroup = { id: 'groupid' };
const element = createGroupListItem(fakeGroup);
element.ctrl.trackViewGroupActivity();
assert.calledWith(
fakeGroupListItemCommon.trackViewGroupActivity,
fakeAnalytics
);
});
it('returns groupListItemCommon.orgName when orgName is called', () => {
const fakeGroup = { id: 'groupid', organization: { name: 'org' } };
it('sets alt text for organization logo', () => {
const fakeGroup = {
id: 'groupid',
// Dummy scheme to avoid actually trying to load image.
logo: 'dummy://hypothes.is/logo.svg',
organization: { name: 'org' },
};
fakeGroupListItemCommon.orgName
.withArgs(fakeGroup)
.returns(fakeGroup.organization.name);
const element = createGroupListItem(fakeGroup);
const orgName = element.ctrl.orgName();
const wrapper = createGroupListItem(fakeGroup);
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',
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',
expectedIsSelected: false,
},
......@@ -102,9 +90,12 @@ describe('groupListItem', () => {
fakeStore.focusedGroupId.returns(focusedGroupId);
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', '');
disableOpenerForExternalLinks(document.body);
const angular = require('angular');
const wrapReactComponent = require('./util/wrap-react-component');
// autofill-event relies on the existence of window.angular so
// it must be require'd after angular is first require'd
......@@ -154,7 +155,10 @@ function startAngularApp(config) {
.component('dropdownMenuBtn', require('./components/dropdown-menu-btn'))
.component('excerpt', require('./components/excerpt'))
.component('groupList', require('./components/group-list'))
.component('groupListItem', require('./components/group-list-item'))
.component(
'groupListItem',
wrapReactComponent(require('./components/group-list-item'))
)
.component(
'groupListItemOutOfScope',
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