diff --git a/test/client/unit/specs/components/tasks/column.js b/test/client/unit/specs/components/tasks/column.js new file mode 100644 index 0000000000..32f2caa7e0 --- /dev/null +++ b/test/client/unit/specs/components/tasks/column.js @@ -0,0 +1,127 @@ +// VueJS produces following warnings if the corresponding imports are not used +// [Vue warn]: You are using the runtime-only build of Vue where the template option is not available. Either pre-compile the templates into render functions, or use the compiler-included build. +import Vue from 'vue/dist/vue'; + +import TaskColumn from 'client/components/tasks/column.vue'; + +describe('Task Column Component', () => { + let vm, Ctor, tasks; + + describe('Task Filtering', () => { + beforeEach(() => { + Ctor = Vue.extend(TaskColumn); + vm = new Ctor({ + propsData: { + type: 'habit', + }, + }).$mount(); + }); + + describe('by Tags', () => { + beforeEach(() => { + tasks = [ + { tags: [3, 4] }, + { tags: [2, 3] }, + { tags: [] }, + { tags: [1, 3] }, + ]; + }); + + it('returns all tasks when given no tags', () => { + let returnedTasks = vm.filterByTagList(tasks); + expect(returnedTasks).to.have.lengthOf(tasks.length); + tasks.forEach((task, i) => { + expect(returnedTasks[i]).to.eq(task); + }); + }); + + it('returns all tasks with given single tag', () => { + let returnedTasks = vm.filterByTagList(tasks, [3]); + + expect(returnedTasks).to.have.lengthOf(3); + expect(returnedTasks[0]).to.eq(tasks[0]); + expect(returnedTasks[1]).to.eq(tasks[1]); + expect(returnedTasks[2]).to.eq(tasks[3]); + }); + + it('returns all tasks with given multiple tags', () => { + let returnedTasks = vm.filterByTagList(tasks, [2, 3]); + + expect(returnedTasks).to.have.lengthOf(1); + expect(returnedTasks[0]).to.eq(tasks[1]); + }); + }); + + describe('by Search Text', () => { + beforeEach(() => { + tasks = [ + { + text: 'Hello world 1', + note: '', + checklist: [], + }, + { + text: 'Hello world 2', + note: '', + checklist: [], + }, + { + text: 'Generic Task Title', + note: '', + checklist: [ + { text: 'Check 1' }, + { text: 'Check 2' }, + { text: 'Check 3' }, + ], + }, + { + text: 'Hello world 3', + note: 'Generic Task Note', + checklist: [ + { text: 'Checkitem 1' }, + { text: 'Checkitem 2' }, + { text: 'Checkitem 3' }, + ], + }, + ]; + }); + + it('should return all tasks with empty search term', () => { + let returnedTasks = vm.filterBySearchText(tasks); + expect(returnedTasks).to.have.lengthOf(tasks.length); + tasks.forEach((task, i) => { + expect(returnedTasks[i]).to.eq(task); + }); + }); + + it('should return tasks with search term in title /i', () => { + ['Title', 'TITLE', 'title', 'tItLe'].forEach((term) => { + expect(vm.filterBySearchText(tasks, term)[0]).to.eq(tasks[2]); + }); + }); + + it('should return tasks with search term in note /i', () => { + ['Note', 'NOTE', 'note', 'nOtE'].forEach((term) => { + expect(vm.filterBySearchText(tasks, term)[0]).to.eq(tasks[3]); + }); + }); + + it('should return tasks with search term in checklist title /i', () => { + ['Check', 'CHECK', 'check', 'cHeCK'].forEach((term) => { + let returnedTasks = vm.filterBySearchText(tasks, term); + + expect(returnedTasks[0]).to.eq(tasks[2]); + expect(returnedTasks[1]).to.eq(tasks[3]); + }); + + ['Checkitem', 'CHECKITEM', 'checkitem', 'cHeCKiTEm'].forEach((term) => { + expect(vm.filterBySearchText(tasks, term)[0]).to.eq(tasks[3]); + }); + }); + }); + + afterEach(() => { + vm.$destroy(); + }); + }); +}); diff --git a/test/client/unit/specs/libs/store/helpers/filterTasks.js b/test/client/unit/specs/libs/store/helpers/filterTasks.js new file mode 100644 index 0000000000..6537f122b2 --- /dev/null +++ b/test/client/unit/specs/libs/store/helpers/filterTasks.js @@ -0,0 +1,62 @@ +import { + getTypeLabel, + getFilterLabels, + getActiveFilter, +} from 'client/libs/store/helpers/filterTasks.js'; + +describe('Filter Category for Tasks', () => { + describe('getTypeLabel', () => { + it('should return correct task type labels', () => { + expect(getTypeLabel('habit')).to.eq('habits'); + expect(getTypeLabel('daily')).to.eq('dailies'); + expect(getTypeLabel('todo')).to.eq('todos'); + expect(getTypeLabel('reward')).to.eq('rewards'); + }); + }); + + describe('getFilterLabels', () => { + let habit, daily, todo, reward; + beforeEach(() => { + habit = ['all', 'yellowred', 'greenblue']; + daily = ['all', 'due', 'notDue']; + todo = ['remaining', 'scheduled', 'complete2']; + reward = ['all', 'custom', 'wishlist']; + }); + + it('should return all task type filter labels by type', () => { + // habits + getFilterLabels('habit').forEach((item, i) => { + expect(item).to.eq(habit[i]); + }); + // dailys + getFilterLabels('daily').forEach((item, i) => { + expect(item).to.eq(daily[i]); + }); + // todos + getFilterLabels('todo').forEach((item, i) => { + expect(item).to.eq(todo[i]); + }); + // rewards + getFilterLabels('reward').forEach((item, i) => { + expect(item).to.eq(reward[i]); + }); + }); + }); + + describe('getActiveFilter', () => { + it('should return single function by default', () => { + let activeFilter = getActiveFilter('habit'); + expect(activeFilter).to.be.an('object'); + expect(activeFilter).to.have.all.keys('label', 'filterFn', 'default'); + expect(activeFilter.default).to.be.true; + }); + + it('should return single function for given filter type', () => { + let activeFilterLabel = 'yellowred'; + let activeFilter = getActiveFilter('habit', activeFilterLabel); + expect(activeFilter).to.be.an('object'); + expect(activeFilter).to.have.all.keys('label', 'filterFn'); + expect(activeFilter.label).to.eq(activeFilterLabel); + }); + }); +}); diff --git a/test/client/unit/specs/libs/store/helpers/orderTasks.js b/test/client/unit/specs/libs/store/helpers/orderTasks.js new file mode 100644 index 0000000000..e293fc0f32 --- /dev/null +++ b/test/client/unit/specs/libs/store/helpers/orderTasks.js @@ -0,0 +1,43 @@ +import { + orderSingleTypeTasks, + // orderMultipleTypeTasks, +} from 'client/libs/store/helpers/orderTasks.js'; + +import shuffle from 'lodash/shuffle'; + +describe('Task Order Helper Function', () => { + let tasks, shuffledTasks, taskOrderList; + beforeEach(() => { + taskOrderList = [1, 2, 3, 4]; + tasks = []; + taskOrderList.forEach(i => tasks.push({ _id: i, id: i })); + shuffledTasks = shuffle(tasks); + }); + + it('should return tasks as is for no task order', () => { + expect(orderSingleTypeTasks(shuffledTasks)).to.eq(shuffledTasks); + }); + + it('should return tasks in expected order', () => { + let newOrderedTasks = orderSingleTypeTasks(shuffledTasks, taskOrderList); + newOrderedTasks.forEach((item, index) => { + expect(item).to.eq(tasks[index]); + }); + }); + + it('should return new tasks at end of expected order', () => { + let newTaskIds = [10, 15, 20]; + newTaskIds.forEach(i => tasks.push({ _id: i, id: i })); + shuffledTasks = shuffle(tasks); + + let newOrderedTasks = orderSingleTypeTasks(shuffledTasks, taskOrderList); + // checking tasks with order + newOrderedTasks.slice(0, taskOrderList.length).forEach((item, index) => { + expect(item).to.eq(tasks[index]); + }); + // check for new task ids + newOrderedTasks.slice(-3).forEach(item => { + expect(item.id).to.be.oneOf(newTaskIds); + }); + }); +}); diff --git a/test/client/unit/specs/store/getters/tasks/getTaskList.js b/test/client/unit/specs/store/getters/tasks/getTaskList.js new file mode 100644 index 0000000000..1de50b15cc --- /dev/null +++ b/test/client/unit/specs/store/getters/tasks/getTaskList.js @@ -0,0 +1,118 @@ +import generateStore from 'client/store'; + +describe('Store Getters for Tasks', () => { + let store, habits, dailys, todos, rewards; + + beforeEach(() => { + store = generateStore(); + // Get user preference data and user tasks order data + store.state.user.data = { + preferences: {}, + tasksOrder: { + habits: [], + dailys: [], + todos: [], + rewards: [], + }, + }; + }); + + describe('Task List', () => { + beforeEach(() => { + habits = [ + { id: 1 }, + { id: 2 }, + ]; + dailys = [ + { id: 3 }, + { id: 4 }, + ]; + todos = [ + { id: 5 }, + { id: 6 }, + ]; + rewards = [ + { id: 7 }, + { id: 8 }, + ]; + store.state.tasks.data = { + habits, + dailys, + todos, + rewards, + }; + }); + + it('should returns all tasks by task type', () => { + let returnedTasks = store.getters['tasks:getUnfilteredTaskList']('habit'); + expect(returnedTasks).to.eq(habits); + + returnedTasks = store.getters['tasks:getUnfilteredTaskList']('daily'); + expect(returnedTasks).to.eq(dailys); + + returnedTasks = store.getters['tasks:getUnfilteredTaskList']('todo'); + expect(returnedTasks).to.eq(todos); + + returnedTasks = store.getters['tasks:getUnfilteredTaskList']('reward'); + expect(returnedTasks).to.eq(rewards); + }); + }); + + // @TODO add task filter check for rewards and dailys + describe('Task Filters', () => { + beforeEach(() => { + habits = [ + // weak habit + { value: 0 }, + // strong habit + { value: 2 }, + ]; + todos = [ + // scheduled todos + { completed: false, date: 'Mon, 15 Jan 2018 12:18:29 GMT' }, + // completed todos + { completed: true }, + ]; + store.state.tasks.data = { + habits, + todos, + }; + }); + + it('should return weak habits', () => { + let returnedTasks = store.getters['tasks:getFilteredTaskList']({ + type: 'habit', + filterType: 'yellowred', + }); + + expect(returnedTasks[0]).to.eq(habits[0]); + }); + + it('should return strong habits', () => { + let returnedTasks = store.getters['tasks:getFilteredTaskList']({ + type: 'habit', + filterType: 'greenblue', + }); + + expect(returnedTasks[0]).to.eq(habits[1]); + }); + + it('should return scheduled todos', () => { + let returnedTasks = store.getters['tasks:getFilteredTaskList']({ + type: 'todo', + filterType: 'scheduled', + }); + + expect(returnedTasks[0]).to.eq(todos[0]); + }); + + it('should return completed todos', () => { + let returnedTasks = store.getters['tasks:getFilteredTaskList']({ + type: 'todo', + filterType: 'complete2', + }); + + expect(returnedTasks[0]).to.eq(todos[1]); + }); + }); +}); diff --git a/website/client/components/tasks/column.vue b/website/client/components/tasks/column.vue index d479466eff..810cdf9cf2 100644 --- a/website/client/components/tasks/column.vue +++ b/website/client/components/tasks/column.vue @@ -8,14 +8,14 @@ v-if='type === "reward"') .d-flex h2.tasks-column-title - | {{ $t(types[type].label) }} + | {{ $t(typeLabel) }} .badge.badge-pill.badge-purple.column-badge(v-if="badgeCount > 0") {{ badgeCount }} .filters.d-flex.justify-content-end .filter.small-text( - v-for="filter in types[type].filters", - :class="{active: activeFilters[type].label === filter.label}", + v-for="filter in typeFilters", + :class="{active: activeFilter.label === filter}", @click="activateFilter(type, filter)", - ) {{ $t(filter.label) }} + ) {{ $t(filter) }} .tasks-list(ref="tasksWrapper") textarea.quick-add( :rows="quickAddRows", @@ -26,19 +26,19 @@ ) transition(name="quick-add-tip-slide") .quick-add-tip.small-text(v-show="quickAddFocused", v-html="$t('addMultipleTip')") - clear-completed-todos(v-if="activeFilters[type].label === 'complete2'") + clear-completed-todos(v-if="activeFilter.label === 'complete2'") .column-background( v-if="isUser === true", :class="{'initial-description': initialColumnDescription}", ref="columnBackground", ) .svg-icon(v-html="icons[type]", :class="`icon-${type}`", v-once) - h3(v-once) {{$t('theseAreYourTasks', {taskType: $t(types[type].label)})}} + h3(v-once) {{$t('theseAreYourTasks', {taskType: $t(typeLabel)})}} .small-text {{$t(`${type}sDesc`)}} draggable.sortable-tasks( ref="tasksList", @update='sorted', - :options='{disabled: activeFilters[type].label === "scheduled"}', + :options='{disabled: activeFilter.label === "scheduled"}', ) task( v-for="task in taskList", @@ -245,10 +245,10 @@