Skip to content

Commit

Permalink
Add unit tests for todo date functionalities
Browse files Browse the repository at this point in the history
  • Loading branch information
arnellebalane committed Oct 30, 2024
1 parent 546514f commit 5ea5362
Show file tree
Hide file tree
Showing 12 changed files with 513 additions and 17 deletions.
39 changes: 39 additions & 0 deletions src/components/Badge.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import Badge from './Badge.svelte';

describe('Badge', () => {
it('renders as li when variant=li', () => {
cy.mount(Badge, {
props: {
variant: 'li',
'data-cy': 'badge',
},
});

cy.get('li[data-cy="badge"]').should('be.visible');
cy.get('li[data-cy="badge"] span').should('not.exist');
});

it('renders as span when variant=span', () => {
cy.mount(Badge, {
props: {
variant: 'span',
'data-cy': 'badge',
},
});

cy.get('span[data-cy="badge"]').should('be.visible');
cy.get('span[data-cy="badge"] span').should('not.exist');
});

it('renders icon slot when icon=true', () => {
cy.mount(Badge, {
props: {
variant: 'span',
icon: true,
'data-cy': 'badge',
},
});

cy.get('span[data-cy="badge"] span').should('be.visible');
});
});
21 changes: 20 additions & 1 deletion src/features/settings/store.spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { THEME_SYSTEM } from '@features/themes/constants';
import { COLOR_GREEN, THEME_SYSTEM } from '@features/themes/constants';
import { STORAGE_KEY_SETTINGS } from '@lib/constants';

import { settings } from './store';
Expand Down Expand Up @@ -55,6 +55,25 @@ describe('settings store', () => {
});
});

describe('settings.saveKey', () => {
it('sets specified key in settings', () => {
const data = {
theme: THEME_SYSTEM,
};
settings.set(data);

const settingsSpy = cy.spy();
settings.subscribe(settingsSpy);

settings.saveKey('color', COLOR_GREEN);

cy.wrap(settingsSpy).should('have.been.calledWith', {
theme: THEME_SYSTEM,
color: COLOR_GREEN,
});
});
});

describe('settings.saveInStorage', () => {
it('saves settings in localStorage', () => {
const data = {
Expand Down
45 changes: 45 additions & 0 deletions src/features/todos/components/TodoFormOptionalFields.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import TodoFormOptionalFields from './TodoFormOptionalFields.svelte';

import { settings } from '@features/settings/store';
import { generateTodo } from '../utils/test-helpers';

describe('TodoFormOptionalFields', () => {
beforeEach(() => {
cy.viewport(500, 500);

settings.set({});
});

it('hides fields when settings.openOptionalFields=false', () => {
settings.set({ openOptionalFields: false });
const data = generateTodo();

cy.mount(TodoFormOptionalFields, {
props: { data },
});

cy.get('[data-cy="todo-form-optional-fields"]').should('not.have.attr', 'open');
});

it('displays fields when settings.openOptionalFields=true', () => {
settings.set({ openOptionalFields: true });
const data = generateTodo();

cy.mount(TodoFormOptionalFields, {
props: { data },
});

cy.get('[data-cy="todo-form-optional-fields"]').should('have.attr', 'open');
});

it('displays fields when any optional field is set in the todo item', () => {
settings.set({ openOptionalFields: false });
const data = generateTodo({ date: '2024-10-27' });

cy.mount(TodoFormOptionalFields, {
props: { data },
});

cy.get('[data-cy="todo-form-optional-fields"]').should('have.attr', 'open');
});
});
11 changes: 5 additions & 6 deletions src/features/todos/components/TodoFormOptionalFields.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,16 @@ import { tags } from '@features/tags/store';
export let data = {};
let isOptionalFieldsToggled = false;
const handleToggle = () => (isOptionalFieldsToggled = true);
$: tagsChoices = orderBy($tags, (tag) => tag.label.toUpperCase());
$: hasOptionalFields = data.date || data.tags?.length;
$: shouldOpenOptionalFields = hasOptionalFields || $settings.openOptionalFields || isOptionalFieldsToggled;
let isOptionalFieldsToggled = false;
const handleToggle = () => (isOptionalFieldsToggled = true);
</script>

<details open={shouldOpenOptionalFields} on:toggle={handleToggle}>
<summary data-cy="todo-form-optional-fields">
<details open={shouldOpenOptionalFields} on:toggle={handleToggle} data-cy="todo-form-optional-fields">
<summary>
<span>Optional fields</span>
</summary>

Expand Down
5 changes: 4 additions & 1 deletion src/features/todos/components/TodoItem.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ describe('TodoItem', () => {
settings.set({});
});

it('displays todo details and tags', () => {
it('displays todo details and optional fields', () => {
cy.clock(new Date(2024, 0, 1));
const todo = generateTodo({
done: false,
tags: ['one', 'two'],
date: '2024-01-27',
});

cy.mount(TodoItem, {
Expand All @@ -24,6 +26,7 @@ describe('TodoItem', () => {

cy.get('[data-cy="todo-item"]').should('not.have.class', 'done').and('not.have.class', 'private');
cy.get('[data-cy="todo-item-done"]').should('not.be.checked');
cy.get('[data-cy="todo-item-date"]').should('contain.text', 'Jan 27');
cy.get('[data-cy="todo-item-details"]').should('contain.text', todo.body);
for (const tag of todo.tags) {
cy.get('[data-cy="todo-item-tag"]').contains(tag).should('be.visible');
Expand Down
35 changes: 35 additions & 0 deletions src/features/todos/components/TodoItemDate.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import TodoItemDate from './TodoItemDate.svelte';

import { TODOS_DATE_ABSOLUTE, TODOS_DATE_RELATIVE } from '@features/todos/constants';

describe('TodoItemDate', () => {
beforeEach(() => {
cy.clock(new Date(2024, 0, 1));
});

it('displays date in absolute format', () => {
const date = '2024-01-10';

cy.mount(TodoItemDate, {
props: {
date,
variant: TODOS_DATE_ABSOLUTE,
},
});

cy.get('[data-cy="todo-item-date"]').should('contain.text', 'Jan 10');
});

it('displays date in relative format', () => {
const date = '2024-01-10';

cy.mount(TodoItemDate, {
props: {
date,
variant: TODOS_DATE_RELATIVE,
},
});

cy.get('[data-cy="todo-item-date"]').should('contain.text', 'In 9 days');
});
});
2 changes: 1 addition & 1 deletion src/features/todos/components/TodoItemDate.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ $: dateDisplay = (() => {
})();
</script>

<Badge icon>
<Badge icon data-cy="todo-item-date">
<svg slot="icon" width="14" height="14" viewBox="0 0 24 24">
<path
fill="currentColor"
Expand Down
4 changes: 3 additions & 1 deletion src/features/todos/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { settings } from '@features/settings/store';
import { TODOS_THIS_WEEK, TODOS_TODAY } from '@features/todos/constants';

import { getDifferenceInDays, isDateThisWeek, isDateToday, today } from './lib/date';
import { getDifferenceInDays, getToday, isDateThisWeek, isDateToday } from './lib/date';
import { todos } from './store';

export function initializeTodos() {
const today = getToday();

settings.subscribe((settingsData) => {
if (!settingsData.moveTodosAutomatically) return;

Expand Down
103 changes: 103 additions & 0 deletions src/features/todos/index.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { settings } from '@features/settings/store';

import { initializeTodos } from '.';
import { TODOS_EVENTUALLY, TODOS_THIS_WEEK, TODOS_TODAY } from './constants';
import { todos } from './store';
import { generateTodo } from './utils/test-helpers';

describe('initializeTodos', () => {
const yesterday = new Date(2024, 0, 2); // tuesday
const today = new Date(2024, 0, 3); // wednesday

beforeEach(() => {
cy.clock(today);

settings.set({});
todos.set([]);
});

it('skips moving todos when settings.moveTodosAutomatically is false', () => {
settings.set({ moveTodosAutomatically: false });

const todosSpy = cy.spy();
todos.subscribe(todosSpy);

initializeTodos();

cy.wrap(todosSpy).should('have.always.been.calledWith', []);
});

it('skips moving todos when it was already done for the current day', () => {
settings.set({
moveTodosAutomatically: true,
moveTodosLastUpdated: today.getTime(),
});

const todosSpy = cy.spy();
todos.subscribe(todosSpy);

initializeTodos();

cy.wrap(todosSpy).should('have.always.been.calledWith', []);
});

it('moves todos to today list when their date is the current day', () => {
settings.set({
moveTodosAutomatically: true,
moveTodosLastUpdated: yesterday.getTime(),
});

const todo = generateTodo({
list: TODOS_EVENTUALLY,
date: '2024-01-03',
});
todos.set([todo]);

const todosSpy = cy.spy();
todos.subscribe(todosSpy);

initializeTodos();

cy.wrap(todosSpy).should('have.been.calledWith', [{ ...todo, list: TODOS_TODAY }]);
});

it('moves todos to this week list when their date is within the current week', () => {
settings.set({
moveTodosAutomatically: true,
moveTodosLastUpdated: yesterday.getTime(),
});

const todo = generateTodo({
list: TODOS_EVENTUALLY,
date: '2024-01-05',
});
todos.set([todo]);

const todosSpy = cy.spy();
todos.subscribe(todosSpy);

initializeTodos();

cy.wrap(todosSpy).should('have.been.calledWith', [{ ...todo, list: TODOS_THIS_WEEK }]);
});

it('skips moving todos when their date is within the current week but has already past', () => {
settings.set({
moveTodosAutomatically: true,
moveTodosLastUpdated: yesterday.getTime(),
});

const todo = generateTodo({
list: TODOS_EVENTUALLY,
date: '2024-01-02',
});
todos.set([todo]);

const todosSpy = cy.spy();
todos.subscribe(todosSpy);

initializeTodos();

cy.wrap(todosSpy).should('have.always.been.calledWith', [todo]);
});
});
21 changes: 14 additions & 7 deletions src/features/todos/lib/date.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,46 @@
export const today = new Date();
today.setHours(0, 0, 0, 0);
export const getToday = () => {
const today = new Date();
today.setHours(0, 0, 0, 0);
return today;
};

export const getDifferenceInDays = (base, to) => {
return Math.ceil((to.getTime() - base.getTime()) / 1000 / 60 / 60 / 24);
};

export const isDateToday = (date) => {
const today = getToday();
return getDifferenceInDays(today, date) === 0;
};

export const isDateThisWeek = (date) => {
const today = getToday();
const sun = new Date(today);
const sat = new Date(today);
sun.setDate(today.getDate() - today.getDay());
sat.setDate(today.getDate() + (7 - today.getDay() - 1));
return sun <= date && date <= sat;
};

export const getRelativeDateParams = (date) => {
const days = getDifferenceInDays(today, date);
if (days < 31) {
export const getRelativeDateParams = (from, date) => {
const days = getDifferenceInDays(from, date);
if (-14 < days && days < 14) {
return [days, 'day'];
}
const weeks = Math.round(days / 7);
if (weeks < 4) {
if (-8 < weeks && weeks < 8) {
return [weeks, 'week'];
}
const months = Math.round(weeks / 4);
if (months < 12) {
if (-12 < months && months < 12) {
return [months, 'month'];
}
const years = Math.round(months / 12);
return [years, 'year'];
};

export const formatAbsoluteDate = (date) => {
const today = getToday();
const formatterOptions = {
month: 'short',
day: 'numeric',
Expand All @@ -47,6 +53,7 @@ export const formatAbsoluteDate = (date) => {
};

export const formatRelativeDate = (date) => {
const today = getToday();
const params = getRelativeDateParams(today, date);
const formatter = new Intl.RelativeTimeFormat(undefined, {
style: 'long',
Expand Down
Loading

0 comments on commit 5ea5362

Please sign in to comment.