Skip to content

Commit

Permalink
Shortname (#7655)
Browse files Browse the repository at this point in the history
* feat: Add alias property to task model
  • Loading branch information
crookedneighbor authored Jun 16, 2016
1 parent c34c211 commit b7b61e6
Show file tree
Hide file tree
Showing 21 changed files with 536 additions and 97 deletions.
3 changes: 2 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"habitrpg/babel"
],
"globals": {
"Promise": true
"Promise": true,
"Set": false
}
}
5 changes: 5 additions & 0 deletions common/locales/en/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
"extraNotes": "Extra Notes",
"direction/Actions": "Direction/Actions",
"advancedOptions": "Advanced Options",
"taskAlias": "Task Alias",
"taskAliasPopover": "This task alias can be used when integrating with 3rd party integrations. Only dashes, underscores, and alphanumeric characters are supported. The task alias must be unique among all your tasks.",
"taskAliasPlaceholder": "your-task-alias-here",
"taskAliasPopoverWarning": "WARNING: Changing this value will break any 3rd party integrations that rely on the task alias.",
"difficulty": "Difficulty",
"difficultyHelpTitle": "How difficult is this task?",
"difficultyHelpContent": "The harder a task, the more Experience and Gold it awards you when you check it off... but the more it damages you if it is a Daily or Bad Habit!",
Expand Down Expand Up @@ -113,6 +117,7 @@
"rewardHelp4": "Don't be afraid to set custom Rewards! Check out <a href='http://habitica.wikia.com/wiki/Sample_Custom_Rewards' target='_blank'>some samples here</a>.",
"clickForHelp": "Click for help",
"taskIdRequired": "\"taskId\" must be a valid UUID.",
"taskAliasAlreadyUsed": "Task alias already used on another task.",
"taskNotFound": "Task not found.",
"invalidTaskType": "Task type must be one of \"habit\", \"daily\", \"todo\", \"reward\".",
"cantDeleteChallengeTasks": "A task belonging to a challenge can't be deleted.",
Expand Down
13 changes: 12 additions & 1 deletion test/api/v3/integration/tasks/DELETE-tasks_id.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
describe('DELETE /tasks/:id', () => {
let user;

before(async () => {
beforeEach(async () => {
user = await generateUser();
});

Expand All @@ -17,6 +17,7 @@ describe('DELETE /tasks/:id', () => {
task = await user.post('/tasks/user', {
text: 'test habit',
type: 'habit',
alias: 'task-to-be-deleted',
});
});

Expand All @@ -29,6 +30,16 @@ describe('DELETE /tasks/:id', () => {
message: t('taskNotFound'),
});
});

it('can use a alias to delete a task', async () => {
await user.del(`/tasks/${task.alias}`);

await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('taskNotFound'),
});
});
});

context('task cannot be deleted', () => {
Expand Down
10 changes: 9 additions & 1 deletion test/api/v3/integration/tasks/GET-tasks_id.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { v4 as generateUUID } from 'uuid';
describe('GET /tasks/:id', () => {
let user;

before(async () => {
beforeEach(async () => {
user = await generateUser();
});

Expand All @@ -18,11 +18,19 @@ describe('GET /tasks/:id', () => {
task = await user.post('/tasks/user', {
text: 'test habit',
type: 'habit',
alias: 'alias',
});
});

it('gets specified task', async () => {
let getTask = await user.get(`/tasks/${task._id}`);

expect(getTask).to.eql(task);
});

it('can use alias to retrieve task', async () => {
let getTask = await user.get(`/tasks/${task.alias}`);

expect(getTask).to.eql(task);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,28 @@ describe('POST /tasks/:id/score/:direction', () => {
});

context('all', () => {
it('requires a task id', async () => {
await expect(user.post('/tasks/123/score/up')).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
it('can use an id to identify the task', async () => {
let todo = await user.post('/tasks/user', {
text: 'test todo',
type: 'todo',
alias: 'alias',
});

let res = await user.post(`/tasks/${todo._id}/score/up`);

expect(res).to.be.ok;
});

it('can use a alias in place of the id', async () => {
let todo = await user.post('/tasks/user', {
text: 'test todo',
type: 'todo',
alias: 'alias',
});

let res = await user.post(`/tasks/${todo.alias}/score/up`);

expect(res).to.be.ok;
});

it('requires a task direction', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,6 @@ describe('POST /tasks/:taskId/move/to/:position', () => {
user = await generateUser();
});

it('requires a valid taskId', async () => {
await expect(user.post('/tasks/123/move/to/1')).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});

it('requires a numeric position parameter', async () => {
await expect(user.post(`/tasks/${generateUUID()}/move/to/notANumber`)).to.eventually.be.rejected.and.eql({
code: 400,
Expand Down Expand Up @@ -53,6 +45,24 @@ describe('POST /tasks/:taskId/move/to/:position', () => {
expect(newOrder.length).to.equal(5);
});

it('can move task to new position using alias', async () => {
let tasks = await user.post('/tasks/user', [
{type: 'habit', text: 'habit 1'},
{type: 'habit', text: 'habit 2', alias: 'move'},
{type: 'daily', text: 'daily 1'},
{type: 'habit', text: 'habit 3'},
{type: 'habit', text: 'habit 4'},
{type: 'todo', text: 'todo 1'},
{type: 'habit', text: 'habit 5'},
]);

let taskToMove = tasks[1];
expect(taskToMove.text).to.equal('habit 2');
let newOrder = await user.post(`/tasks/${taskToMove.alias}/move/to/3`);
expect(newOrder[3]).to.equal(taskToMove._id);
expect(newOrder.length).to.equal(5);
});

it('can\'t move completed todo', async () => {
let task = await user.post('/tasks/user', {type: 'todo', text: 'todo 1'});
await user.post(`/tasks/${task._id}/score/up`);
Expand Down
77 changes: 74 additions & 3 deletions test/api/v3/integration/tasks/POST-tasks_user.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ describe('POST /tasks/user', () => {
let originalHabitsOrder = (await user.get('/user')).tasksOrder.habits;
await expect(user.post('/tasks/user', {
type: 'habit',
})).to.eventually.be.rejected.and.eql({ // this block is necessary
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: 'habit validation failed',
Expand All @@ -72,7 +72,7 @@ describe('POST /tasks/user', () => {
await expect(user.post('/tasks/user', [
{type: 'habit'}, // Missing text
{type: 'habit', text: 'valid'}, // Valid
])).to.eventually.be.rejected.and.eql({ // this block is necessary
])).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: 'habit validation failed',
Expand All @@ -87,7 +87,7 @@ describe('POST /tasks/user', () => {
await expect(user.post('/tasks/user', [
{type: 'habit'}, // Missing text
{type: 'habit', text: 'valid'}, // Valid
])).to.eventually.be.rejected.and.eql({ // this block is necessary
])).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: 'habit validation failed',
Expand Down Expand Up @@ -142,6 +142,67 @@ describe('POST /tasks/user', () => {

expect(task).not.to.have.property('notValid');
});

it('errors if alias already exists on another task', async () => {
await user.post('/tasks/user', { // first task that will succeed
type: 'habit',
text: 'todo text',
alias: 'alias',
});

await expect(user.post('/tasks/user', {
type: 'todo',
text: 'todo text',
alias: 'alias',
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: 'todo validation failed',
});
});

it('errors if alias contains invalid values', async () => {
await expect(user.post('/tasks/user', {
type: 'todo',
text: 'todo text',
alias: 'short name!',
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: 'todo validation failed',
});
});

it('errors if alias is a valid uuid', async () => {
await expect(user.post('/tasks/user', {
type: 'todo',
text: 'todo text',
alias: generateUUID(),
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: 'todo validation failed',
});
});

it('errors if the same shortname is used on 2 or more tasks', async () => {
await expect(user.post('/tasks/user', [{
type: 'habit',
text: 'habit text',
alias: 'alias',
}, {
type: 'todo',
text: 'todo text',
}, {
type: 'todo',
text: 'todo text',
alias: 'alias',
}])).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('taskAliasAlreadyUsed'),
});
});
});

context('all types', () => {
Expand All @@ -163,6 +224,16 @@ describe('POST /tasks/user', () => {
expect(task.reminders[0].startDate).to.be.a('string'); // json doesn't have dates
expect(task.reminders[0].time).to.be.a('string');
});

it('can create a task with a alias', async () => {
let task = await user.post('/tasks/user', {
text: 'test habit',
type: 'habit',
alias: 'a_alias012',
});

expect(task.alias).to.eql('a_alias012');
});
});

context('habits', () => {
Expand Down
43 changes: 41 additions & 2 deletions test/api/v3/integration/tasks/PUT-tasks_id.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { v4 as generateUUID } from 'uuid';
describe('PUT /tasks/:id', () => {
let user;

before(async () => {
beforeEach(async () => {
user = await generateUser();
});

Expand Down Expand Up @@ -59,7 +59,7 @@ describe('PUT /tasks/:id', () => {
expect(savedTask.notValid).to.be.undefined;
});

it(`only allows setting streak, reminders, checklist, notes, attribute, tags
it(`only allows setting streak, alias, reminders, checklist, notes, attribute, tags
fields for challenge tasks owned by a user`, async () => {
let guild = await generateGroup(user);
let challenge = await generateChallenge(user, guild);
Expand Down Expand Up @@ -87,6 +87,7 @@ describe('PUT /tasks/:id', () => {
_id: 123,
type: 'daily',
userId: 123,
alias: 'a-short-task-name',
history: [123],
createdAt: 'yesterday',
updatedAt: 'tomorrow',
Expand Down Expand Up @@ -176,6 +177,44 @@ describe('PUT /tasks/:id', () => {
expect(savedDaily.reminders[0].id).to.equal(id1);
expect(savedDaily.reminders[1].id).to.equal(id2);
});

it('can set a alias if no other task has that alias', async () => {
let savedDaily = await user.put(`/tasks/${daily._id}`, {
alias: 'alias',
});

expect(savedDaily.alias).to.eql('alias');
});

it('does not set alias to a alias that is already in use', async () => {
await user.post('/tasks/user', {
type: 'todo',
text: 'a todo',
alias: 'some-alias',
});

await expect(user.put(`/tasks/${daily._id}`, {
alias: 'some-alias',
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: 'daily validation failed',
});
});

it('can use alias to update a task', async () => {
daily = await user.put(`/tasks/${daily._id}`, {
alias: 'alias',
});

await user.put(`/tasks/${daily.alias}`, {
text: 'saved',
});

let fetchedDaily = await user.get(`/tasks/${daily._id}`);

expect(fetchedDaily.text).to.eql('saved');
});
});

context('habits', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,18 @@ describe('POST /tasks/challenge/:challengeId', () => {
});
});

it('returns error when user tries to create task with a alias', async () => {
await expect(user.post(`/tasks/challenge/${challenge._id}`, {
text: 'test habit',
type: 'habit',
alias: 'a-alias',
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: 'habit validation failed',
});
});

it('returns error when non leader tries to edit challenge', async () => {
let userThatIsNotLeaderOfChallenge = await generateUser({
challenges: [challenge._id],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
generateGroup,
generateChallenge,
translate as t,
} from '../../../../helpers/api-integration/v3';
} from '../../../../../helpers/api-integration/v3';
import { v4 as generateUUID } from 'uuid';

describe('PUT /tasks/:id', () => {
Expand Down Expand Up @@ -54,6 +54,16 @@ describe('PUT /tasks/:id', () => {
message: t('onlyChalLeaderEditTasks'),
});
});

it('returns error when user attempts to update task with a alias', async () => {
await expect(user.put(`/tasks/${task._id}`, {
alias: 'a-alias',
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: 'habit validation failed',
});
});
});

context('validates params', () => {
Expand Down
Loading

0 comments on commit b7b61e6

Please sign in to comment.