Skip to content

Commit

Permalink
Add keyboard shortcuts for assigning
Browse files Browse the repository at this point in the history
  • Loading branch information
joshsmith committed Nov 18, 2017
1 parent 27b5dbe commit 06278ff
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 22 deletions.
82 changes: 66 additions & 16 deletions app/components/task-assignment.js
@@ -1,20 +1,25 @@
import Component from '@ember/component';
import { computed, get, getProperties, set } from '@ember/object';
import { alias } from '@ember/object/computed';
import { on } from '@ember/object/evented';
import { run } from '@ember/runloop';
import { inject as service } from '@ember/service';
import { set, getProperties, computed } from '@ember/object';
import { isEqual } from '@ember/utils';
import EmberCan from 'ember-can';
import createTaskUserOptions from 'code-corps-ember/utils/create-task-user-options';
import { EKMixin as EmberKeyboardMixin, keyDown } from 'ember-keyboard';

const TRIGGER_CLASS = '.ember-power-select-trigger';

export default Component.extend({
export default Component.extend(EmberKeyboardMixin, {
classNames: ['task-assignment'],

currentUser: service(),
store: service(),
taskAssignment: service(),

isAssigning: false,
canTriggerAssignment: null,
deferredRendering: null,
task: null,
taskUser: null,
Expand All @@ -26,6 +31,16 @@ export default Component.extend({
currentUserId: alias('currentUser.user.id'),
taskUserId: alias('taskUser.id'),

assignedToSelf: computed('currentUserId', 'taskUser.id', function() {
return isEqual(get(this, 'taskUser.id'), get(this, 'currentUserId'));
}),

init() {
this._super(...arguments);

set(this, 'keyboardActivated', true);
},

// TODO: this updates selection when it changes. However, it updates while
// the change is still processing, and rolls back if it fails.
// We should somehow skip the update if the change is processing
Expand All @@ -46,37 +61,72 @@ export default Component.extend({
userOptions: computed('currentUserId', 'taskUserId', 'users', function() {
let { currentUserId, taskUserId, users }
= getProperties(this, 'currentUserId', 'taskUserId', 'users');
if (users) {
return createTaskUserOptions(users, currentUserId, taskUserId);

return users
? createTaskUserOptions(users, currentUserId, taskUserId)
: [];
}),

changeUser(user) {
let { task, taskAssignment }
= getProperties(this, 'task', 'taskAssignment');

return user
? taskAssignment.assign(task, user)
: taskAssignment.unassign(task);
},

clickTrigger() {
this
.$('.ember-basic-dropdown-trigger')
.get(0)
.dispatchEvent(new MouseEvent('mousedown'));
},

selfAssign: on(keyDown('Space'), function(e) {
let { canAssign, canTriggerAssignment, isAssigning }
= getProperties(this, 'canAssign', 'canTriggerAssignment', 'isAssigning');
if (canAssign && !isAssigning && canTriggerAssignment) {
e.preventDefault();
this.toggleSelfAssignment();
}
}),

toggleSelfAssignment() {
if (get(this, 'assignedToSelf')) {
this.changeUser(); // unassign
} else {
return [];
let user = get(this, 'currentUser.user');
this.changeUser(user); // self-assign
}
},

triggerAssignment: on(keyDown('KeyA'), function(e) {
let { canAssign, canTriggerAssignment, isAssigning }
= getProperties(this, 'canAssign', 'canTriggerAssignment', 'isAssigning');
if (canAssign && !isAssigning && canTriggerAssignment) {
e.preventDefault();
run(() => this.clickTrigger());
}
}),

actions: {
buildSelection(option, select) {
if (option === select.selected) {
return null;
}
return option;
return option === select.selected ? null : option;
},

changeUser(user) {
let { task, taskAssignment } = getProperties(this, 'task', 'taskAssignment');

if (user) {
return taskAssignment.assign(task, user);
} else {
return taskAssignment.unassign(task);
}
this.changeUser(user);
},

closeDropdown() {
set(this, 'isAssigning', false);
this.sendAction('onclose');
run.next(() => this.$(TRIGGER_CLASS).trigger('blur'));
},

openDropdown() {
set(this, 'isAssigning', true);
this.sendAction('onopen');
},

Expand Down
1 change: 1 addition & 0 deletions app/templates/components/task-card.hbs
Expand Up @@ -36,6 +36,7 @@
</span>
</p>
{{task-assignment
canTriggerAssignment=hovering
deferredRendering=(not hasHovered)
onclose=(action assignmentClosed)
onopen=(action assignmentOpened)
Expand Down
10 changes: 4 additions & 6 deletions tests/acceptance/task-list-test.js
Expand Up @@ -48,19 +48,17 @@ test('member can assign/reassign/unassign tasks to user', function(assert) {
andThen(() => {
taskCard = page.taskBoard.taskLists(0).taskCards(0);
assert.ok(taskCard.taskAssignment.select.trigger.unassigned, 'Task is rendered unassigned.');
taskCard.taskAssignment.select.trigger.open();
});

andThen(() => {
taskCard.taskAssignment.select.dropdown.options(0).select();
taskCard.mouseenter();
taskCard.triggerKeyDown('Space');
});

andThen(() => {
// assert assignment went through
assert.ok(taskCard.taskAssignment.select.trigger.assigned, 'Task is rendered assigned.');
let userTask = server.schema.userTasks.first();
assert.equal(userTask.user.photoThumbUrl, taskCard.taskAssignment.assignedUser.icon.url, 'Assigned user is rendered.');
taskCard.taskAssignment.select.trigger.open();
taskCard.mouseenter();
taskCard.triggerKeyDown('KeyA');
});

andThen(() => {
Expand Down
89 changes: 89 additions & 0 deletions tests/integration/components/task-assignment-test.js
Expand Up @@ -17,6 +17,7 @@ let page = PageObject.create(taskAssignmentComponent);
function renderPage() {
page.render(hbs`
{{task-assignment
canTriggerAssignment=canTriggerAssignment
deferredRendering=deferredRendering
task=task
taskUser=taskUser
Expand Down Expand Up @@ -277,3 +278,91 @@ test('assignment dropdown typeahead', function(assert) {
done();
});
});

test('pressing Space assigns self to task when able', function(assert) {
let done = assert.async();
assert.expect(2);

let canTriggerAssignment = true;
let task = { id: 'task' };
let user = { id: 'user', username: 'testuser' };
let users = [user];

setProperties(this, { canTriggerAssignment, task, users });

stubService(this, 'current-user', { user });

stubService(this, 'task-assignment', {
assign(sentTask, sentUser) {
assert.deepEqual(sentTask, task, 'Correct task was sent.');
assert.deepEqual(sentUser, user, 'Correct user was sent.');
done();
return RSVP.resolve();
}
});

this.register('ability:task', Ability.extend({ canAssign: true }));

renderPage();

page.triggerKeyDown('Space');
});

test('pressing Space unassigns self from task when able', function(assert) {
let done = assert.async();
assert.expect(1);

let canTriggerAssignment = true;
let task = { id: 'task' };
let user = { id: 'user', username: 'testuser' };
let users = [user];
let taskUser = user;

setProperties(this, { canTriggerAssignment, task, taskUser, users });

stubService(this, 'current-user', { user });

stubService(this, 'task-assignment', {
unassign(sentTask) {
assert.deepEqual(sentTask, task, 'Correct task was sent.');
done();
return RSVP.resolve();
}
});

this.register('ability:task', Ability.extend({ canAssign: true }));

renderPage();

page.triggerKeyDown('Space');
});

test('pressing A when the user has ability', function(assert) {
assert.expect(2);

let canTriggerAssignment = true;
let task = { id: 'task' };
setProperties(this, { canTriggerAssignment, task });
this.register('ability:task', Ability.extend({ canAssign: true }));

renderPage();

page.triggerKeyDown('KeyA');
assert.ok(page.select.dropdown.isVisible, 'Dropdown renders.');
page.triggerKeyDown('KeyA');
assert.ok(page.select.dropdown.isVisible, 'Dropdown stays rendered.');
});

test('pressing A when the user does not have ability', function(assert) {
assert.expect(1);

let canTriggerAssignment = true;
let task = { id: 'task' };
setProperties(this, { canTriggerAssignment, task });
this.register('ability:task', Ability.extend({ canAssign: false }));

renderPage();

page.triggerKeyDown('KeyA');
assert.notOk(page.select.dropdown.isVisible, 'Dropdown does not render.');
});
3 changes: 3 additions & 0 deletions tests/pages/components/task-assignment.js
Expand Up @@ -2,6 +2,7 @@ import {
attribute
} from 'ember-cli-page-object';
import select from 'code-corps-ember/tests/pages/components/power-select';
import { triggerKeyDown } from 'ember-keyboard';

export default {
scope: '.task-assignment',
Expand All @@ -16,6 +17,8 @@ export default {

select,

triggerKeyDown,

unselectedItem: {
scope: '.select-inline__unselected-item'
}
Expand Down

0 comments on commit 06278ff

Please sign in to comment.