diff --git a/src/__tests__/__mock__/recurrence.txt b/src/__tests__/__mock__/recurrence.txt index 73f47194..39c10123 100644 --- a/src/__tests__/__mock__/recurrence.txt +++ b/src/__tests__/__mock__/recurrence.txt @@ -1,15 +1,15 @@ -2024-04-04 Line 1 rec:1d due:2024-04-05 -2024-04-04 Line 1 rec:w due:2024-04-11 -2024-04-04 Line 1 rec:2m due:2024-06-04 -2024-04-04 Line 1 rec:+1d due:2024-04-06 -2024-04-04 Line 1 rec:7w due:2024-05-23 -2024-04-04 Line 1 due:2023-07-24 rec:+1b -2024-04-04 taxes are due in one year t:2022-03-30 due:2022-04-30 rec:+1y -2024-04-04 Water plants @home +quick due:2024-04-11 t:2024-04-01 rec:1w -2024-04-04 Line 1 rec:+1d t:2023-09-20 -2024-04-04 Line 1 rec:1d pri:A due:2024-04-05 -2024-04-04 (A) Do something rec:d t:2024-04-05 @SomeContext -2024-04-04 Do something rec:0d -2024-04-04 Do something rec:0d due:2024-04-04 -2024-04-04 Do something rec:0d due:2024-04-04 t:2024-04-04 \ No newline at end of file +2024-04-05 Line 1 rec:1d due:2024-04-06 +2024-04-05 Line 1 rec:w due:2024-04-12 +2024-04-05 Line 1 rec:2m due:2024-06-05 +2024-04-05 Line 1 rec:+1d due:2024-04-07 +2024-04-05 Line 1 rec:7w due:2024-05-24 +2024-04-05 Line 1 due:2023-07-24 rec:+1b +2024-04-05 taxes are due in one year t:2022-03-30 due:2022-04-30 rec:+1y +2024-04-05 Water plants @home +quick due:2024-04-12 t:2024-04-02 rec:1w +2024-04-05 Line 1 rec:+1d t:2023-09-20 +2024-04-05 Line 1 rec:1d pri:A due:2024-04-06 +2024-04-05 (A) Do something rec:d t:2024-04-06 @SomeContext +2024-04-05 Do something rec:0d +2024-04-05 Do something rec:0d due:2024-04-05 +2024-04-05 Do something rec:0d due:2024-04-05 t:2024-04-05 \ No newline at end of file diff --git a/src/__tests__/main/Attributes.tsx b/src/__tests__/main/Attributes.tsx index ffb97511..5fe06ae9 100644 --- a/src/__tests__/main/Attributes.tsx +++ b/src/__tests__/main/Attributes.tsx @@ -1,14 +1,14 @@ import { attributes, updateAttributes } from '../../main/modules/Attributes'; const todoObjects = [ - { id: 1, created: null, priority: 'A', projects: ['Project 1'], contexts: ['Context 1'], due: '2023-01-01', dueString: '2023-01-01', complete: false, completed: null, t: '2024-02-01', tString: '2024-02-01', rec: null, pm: null, body: null, hidden: false, string: '', notify: false, visible: true, }, - { id: 2, created: '2026-01-01', priority: null, projects: ['Project 2'], contexts: null, due: '2023-02-01', dueString: '2023-02-01', complete: false, completed: null, t: null, tString: null, rec: null, pm: null, body: null, hidden: false, string: '', notify: false, visible: true, }, - { id: 3, created: null, priority: null, projects: ['Project 1'], contexts: null, due: '2023-03-01', dueString: '2023-03-01', complete: false, completed: null, t: null, tString: null, rec: '2b', pm: null, body: null, hidden: false, string: '', notify: true, visible: true, }, - { id: 4, created: '2026-01-01', priority: 'C', projects: ['Project 2'], contexts: ['Context 1'], due: '2023-04-01', dueString: '2023-04-01', complete: false, completed: null, t: '2024-02-01', tString: '2024-02-01', rec: null, pm: null, body: null, hidden: false, string: '', notify: false, visible: true, }, + { lineNumber: 1, created: null, priority: 'A', projects: ['Project 1'], contexts: ['Context 1'], due: '2023-01-01', dueString: '2023-01-01', complete: false, completed: null, t: '2024-02-01', tString: '2024-02-01', rec: null, pm: null, body: null, hidden: false, string: '', notify: false, }, + { lineNumber: 2, created: '2026-01-01', priority: null, projects: ['Project 2'], contexts: null, due: '2023-02-01', dueString: '2023-02-01', complete: false, completed: null, t: null, tString: null, rec: null, pm: null, body: null, hidden: false, string: '', notify: false, }, + { lineNumber: 3, created: null, priority: null, projects: ['Project 1'], contexts: null, due: '2023-03-01', dueString: '2023-03-01', complete: false, completed: null, t: null, tString: null, rec: '2b', pm: null, body: null, hidden: false, string: '', notify: true, }, + { lineNumber: 4, created: '2026-01-01', priority: 'C', projects: ['Project 2'], contexts: ['Context 1'], due: '2023-04-01', dueString: '2023-04-01', complete: false, completed: null, t: '2024-02-01', tString: '2024-02-01', rec: null, pm: null, body: null, hidden: false, string: '', notify: false, }, ]; describe('Set of filters must create a respective set of attributes and its counts', () => { - test('Should create attributes based on todo objects', async () => { + test('Should create attributes based on todo objects', () => { const sorting = [ { id: '1', value: 'priority', invert: false }, @@ -33,7 +33,7 @@ describe('Set of filters must create a respective set of attributes and its coun created: { '2026-01-01': { count: 2, notify: false} }, completed: {}, }; - await updateAttributes(todoObjects, sorting, false); + updateAttributes(todoObjects, sorting, false); expect(attributes).toEqual(expectedAttributes); }); diff --git a/src/__tests__/main/ChangeCompleteState.tsx b/src/__tests__/main/ChangeCompleteState.tsx index 57b5e359..8e9bfce0 100644 --- a/src/__tests__/main/ChangeCompleteState.tsx +++ b/src/__tests__/main/ChangeCompleteState.tsx @@ -1,9 +1,9 @@ -import { changeCompleteState } from '../../main/modules/ProcessDataRequest/ChangeCompleteState'; +import { changeCompleteState } from '../../main/modules/DataRequest/ChangeCompleteState'; import dayjs from 'dayjs'; const date: string = dayjs(new Date()).format('YYYY-MM-DD'); -jest.mock('../../main/modules/ProcessDataRequest/CreateRecurringTodo', () => ({ +jest.mock('../../main/modules/DataRequest/CreateRecurringTodo', () => ({ createRecurringTodo: jest.fn(), })); diff --git a/src/__tests__/main/CreateRecurringTodo.tsx b/src/__tests__/main/CreateRecurringTodo.tsx index d8fbddb6..961ed3c0 100644 --- a/src/__tests__/main/CreateRecurringTodo.tsx +++ b/src/__tests__/main/CreateRecurringTodo.tsx @@ -1,8 +1,8 @@ import fs from 'fs'; -import { createRecurringTodo } from '../../main/modules/ProcessDataRequest/CreateRecurringTodo'; +import { createRecurringTodo } from '../../main/modules/DataRequest/CreateRecurringTodo'; import dayjs from 'dayjs'; -jest.mock('../../main/modules/ProcessDataRequest/CreateTodoObjects', () => ({ +jest.mock('../../main/modules/DataRequest/CreateTodoObjects', () => ({ linesInFile: [''], })); diff --git a/src/__tests__/main/CreateTodoObjects.tsx b/src/__tests__/main/CreateTodoObjects.tsx index 8b72a024..10cf5c22 100644 --- a/src/__tests__/main/CreateTodoObjects.tsx +++ b/src/__tests__/main/CreateTodoObjects.tsx @@ -1,4 +1,4 @@ -import { createTodoObjects } from '../../main/modules/ProcessDataRequest/CreateTodoObjects'; +import { createTodoObjects } from '../../main/modules/DataRequest/CreateTodoObjects'; jest.mock('electron', () => ({ app: { @@ -28,7 +28,7 @@ describe('Create todo objects', () => { test('should create a todo object', async () => { const todoObjects = await createTodoObjects(fileContent); expect(todoObjects[0]).toEqual({ - id: 0, + lineNumber: 0, body: 'Test +project @context todo 1 due:2023-12-31 t:2024-03-24 h:1 test @anotherContext pm:4 and a strict rec:+2w', created: null, complete: false, @@ -45,21 +45,20 @@ describe('Create todo objects', () => { pm: "4", string: '(B) Test +project @context todo 1 due:2023-12-31 t:2024-03-24 h:1 test @anotherContext pm:4 and a strict rec:+2w', notify: false, - visible: true, }); }); test('should create a finished todo object', async () => { const todoObjects = await createTodoObjects(fileContent); expect(todoObjects[1]).toEqual({ - id: 1, + lineNumber: 1, body: 'Test todo 2', created: '2023-07-21', complete: true, completed: '2023-07-23', priority: null, - contexts: [], - projects: [], + contexts: null, + projects: null, due: null, dueString: null, t: null, @@ -69,21 +68,20 @@ describe('Create todo objects', () => { pm: null, string: 'x 2023-07-23 2023-07-21 Test todo 2', notify: false, - visible: true, }); }); test('should create a todo object with speaking due date', async () => { const todoObjects = await createTodoObjects(fileContent); expect(todoObjects[2]).toEqual({ - id: 2, + lineNumber: 2, body: 'Test todo 3 due:end of the year', created: null, complete: false, completed: null, priority: null, - contexts: [], - projects: [], + contexts: null, + projects: null, due: '2024-12-31', dueString: 'end of the year', t: null, @@ -93,21 +91,20 @@ describe('Create todo objects', () => { pm: null, string: 'Test todo 3 due:end of the year', notify: false, - visible: true, }); }); test('should create a todo object with speaking t date', async () => { const todoObjects = await createTodoObjects(fileContent); expect(todoObjects[3]).toEqual({ - id: 3, + lineNumber: 3, body: 'Test todo 4 t:first day of next year', created: null, complete: false, completed: null, priority: null, - contexts: [], - projects: [], + contexts: null, + projects: null, due: null, dueString: null, t: '2025-01-01', @@ -117,7 +114,6 @@ describe('Create todo objects', () => { pm: null, string: 'Test todo 4 t:first day of next year', notify: false, - visible: true, }); }); }); diff --git a/src/__tests__/main/DataRequest.tsx b/src/__tests__/main/DataRequest.tsx new file mode 100644 index 00000000..eb03be67 --- /dev/null +++ b/src/__tests__/main/DataRequest.tsx @@ -0,0 +1,224 @@ +import fs from 'fs'; +import { createTodoObjects } from '../../main/modules/DataRequest/CreateTodoObjects'; +import { applySearchString } from '../../main/modules/Filters/Search'; +import { sortAndGroupTodoObjects } from '../../main/modules/DataRequest/SortAndGroup'; + +jest.mock('../../main/config', () => ({ + config: { + get: jest.fn().mockReturnValue([ + { + active: true, + path: './src/__tests__/__mock__', + todoFilePath: 'recurrence.txt', + todoFileBookmark: null, + doneFilePath: 'done.txt', + doneFileBookmark: null, + }, + ]), + }, +})); + +jest.mock('../../main/config', () => ({ + config: { + get: jest.fn() + }, +})); + +jest.mock('electron', () => ({ + app: { + setBadgeCount: jest.fn(), + }, +})); + +const sorting = [ + { + "id": "1", + "value": "priority", + "invert": false + }, + { + "id": "2", + "value": "due", + "invert": false + }, + { + "id": "3", + "value": "projects", + "invert": false + }, + { + "id": "4", + "value": "contexts", + "invert": false + }, + { + "id": "5", + "value": "created", + "invert": false + }, + { + "id": "6", + "value": "t", + "invert": false + }, + { + "id": "7", + "value": "completed", + "invert": false + } +] + +let todoObjects: TodoObject[]; +let fileContent: string; + +describe('Process todo.txt objects', () => { + + beforeAll(() => { + jest.clearAllMocks(); + fileContent = fs.readFileSync('./src/__tests__/__mock__/todo.txt', 'utf8'); + todoObjects = createTodoObjects(fileContent); + }); + + beforeEach(() => { + todoObjects = createTodoObjects(fileContent); + }); + + test('Search for "test3"', () => { + const searchString: string = 'test3'; + todoObjects = applySearchString(searchString, todoObjects); + // const visibleTodoObjects: TodoObject[] = todoObjects.filter((todoObject: TodoObject) => { + // return todoObject.visible; + // }); + expect(todoObjects.length).toEqual(4); + }); + + test('Search for "lorem"', () => { + const searchString: string = 'lorem'; + todoObjects = applySearchString(searchString, todoObjects); + // const visibleTodoObjects: TodoObject[] = todoObjects.filter((todoObject: TodoObject) => { + // return todoObject.visible; + // }); + expect(todoObjects.length).toEqual(0); + }); + + test('Advanced search for "+testProject4 or +testProject2"', () => { + const searchString: string = '+testProject4 or +testProject2'; + todoObjects = applySearchString(searchString, todoObjects); + // const visibleTodoObjects: TodoObject[] = todoObjects.filter((todoObject: TodoObject) => { + // return todoObject.visible; + // }); + expect(todoObjects.length).toEqual(2); + }); + + test('Advanced search for "+testProject4 and +testProject2" result in 0 found objects', () => { + const searchString:string = '+testProject4 and +testProject2'; + todoObjects = applySearchString(searchString, todoObjects); + // const visibleTodoObjects: TodoObject[] = todoObjects.filter((todoObject: TodoObject) => { + // return todoObject.visible; + // }); + expect(todoObjects.length).toEqual(0); + }); + + test('Advanced search for "+testProject4 and !+testProject2" result in 1 found objects', () => { + const searchString:string = '+testProject4 and !+testProject2'; + todoObjects = applySearchString(searchString, todoObjects); + // const visibleTodoObjects: TodoObject[] = todoObjects.filter((todoObject: TodoObject) => { + // return todoObject.visible; + // }); + expect(todoObjects.length).toEqual(1); + }); + + test('Function creates 3 top level groups (A, B, C)', () => { + const sortedAndGroupedTodoObjects = sortAndGroupTodoObjects(todoObjects, sorting); + const groups = Object.keys(sortedAndGroupedTodoObjects); + expect(groups).toEqual(['A', 'B', 'C']); + }); + + test('Top level group sorted asc', () => { + sorting[0].invert = true; + const sortedAndGroupedTodoObjects = sortAndGroupTodoObjects(todoObjects, sorting); + const groups = Object.keys(sortedAndGroupedTodoObjects); + expect(groups).toEqual(['C', 'B', 'A']); + }); + + // test('Sorting: Priority -> Due dates ', () => { + // sorting[0].invert = false; + // const sortedAndGrouped: any = sortAndGroupTodoObjects(todoObjects, sorting); + // const flattenedObjects: any = flattenTodoObjects(sortedAndGrouped, "priority") + // expect(flattenedObjects[2].due).toContain('2023-01-01'); + // expect(flattenedObjects[3].due).toContain('2023-01-02'); + // expect(flattenedObjects[4].due).toContain('2023-01-03'); + // expect(flattenedObjects[5].due).toContain('2023-01-04'); + // expect(flattenedObjects[6].due).toContain('2025-01-05'); + // }); + + // test('Sorting: Priority -> Projects ', () => { + // sorting[0].invert = false; + // sorting[1].value = 'projects'; + // const sortedAndGrouped: any = sortAndGroupTodoObjects(todoObjects, sorting); + // const flattenedObjects: any = flattenTodoObjects(sortedAndGrouped, "priority") + + // expect(flattenedObjects[2].projects[0]).toContain('testProject1'); + // expect(flattenedObjects[3].projects[0]).toContain('testProject2'); + // expect(flattenedObjects[4].projects[0]).toContain('testProject3'); + // expect(flattenedObjects[5].projects[0]).toContain('testProject4'); + // expect(flattenedObjects[6].projects[0]).toContain('testProject5'); + // }); + + // test('Sorting: Priority -> Projects, but inverted ', () => { + // sorting[1].invert = true; + // sorting[1].value = 'projects'; + // const sortedAndGrouped: any = sortAndGroupTodoObjects(todoObjects, sorting); + // const flattenedObjects: any = flattenTodoObjects(sortedAndGrouped, "priority") + + // expect(flattenedObjects[2].projects[0]).toContain('testProject5'); + // expect(flattenedObjects[3].projects[0]).toContain('testProject4'); + // expect(flattenedObjects[4].projects[0]).toContain('testProject3'); + // expect(flattenedObjects[5].projects[0]).toContain('testProject2'); + // expect(flattenedObjects[6].projects[0]).toContain('testProject1'); + // }); + + // test('Sorting: Priority -> Contexts -> Creation', () => { + // sorting[1].invert = false; + // sorting[1].value = 'contexts'; + // sorting[2].value = 'creation'; + // const sortedAndGrouped: any = sortAndGroupTodoObjects(todoObjects, sorting); + // const flattenedObjects: any = flattenTodoObjects(sortedAndGrouped, "priority") + + // expect(flattenedObjects[3].created).toContain('2025-12-05'); + // expect(flattenedObjects[4].created).toContain('2025-12-06'); + // expect(flattenedObjects[5].created).toContain('2025-12-07'); + // expect(flattenedObjects[6].created).toContain('2025-12-09'); + // }); + + // test('Sorting: Priority -> Contexts -> Projects (inverted)', () => { + // sorting[1].invert = false; + // sorting[1].value = 'contexts'; + // sorting[2].value = 'projects'; + // sorting[2].invert = true; + // const sortedAndGrouped: any = sortAndGroupTodoObjects(todoObjects, sorting); + // const flattenedObjects: any = flattenTodoObjects(sortedAndGrouped, "priority") + + // expect(flattenedObjects[2].projects[0]).toContain('testProject5'); + // expect(flattenedObjects[3].projects[0]).toContain('testProject4'); + // expect(flattenedObjects[4].projects[0]).toContain('testProject3'); + // expect(flattenedObjects[5].projects[0]).toContain('testProject2'); + // expect(flattenedObjects[6].projects[0]).toContain('testProject1'); + // }); + + // test('Sorting: Contexts -> Projects, but inverted ', () => { + // sorting[0].invert = false; + // sorting[0].value = 'contexts'; + // sorting[1].invert = true; + // sorting[1].value = 'projects'; + // const sortedAndGrouped: any = sortAndGroupTodoObjects(todoObjects, sorting); + // const flattenedObjects: any = flattenTodoObjects(sortedAndGrouped, "contexts") + + // expect(flattenedObjects[0].projects[0]).toContain('testProject5'); + // expect(flattenedObjects[1].projects[0]).toContain('testProject4'); + // expect(flattenedObjects[2].projects[0]).toContain('testProject3'); + // expect(flattenedObjects[3].projects[0]).toContain('testProject2'); + // expect(flattenedObjects[4].projects[0]).toContain('testProject1'); + // }); + +}); \ No newline at end of file diff --git a/src/__tests__/main/Filters.tsx b/src/__tests__/main/Filters.tsx index ebdb1e51..b4fec8cb 100644 --- a/src/__tests__/main/Filters.tsx +++ b/src/__tests__/main/Filters.tsx @@ -8,9 +8,9 @@ jest.mock('../../main/config', () => ({ describe('Should filter todos based on passed filters', () => { const todoObjects = [ - { id: 1, body: 'Test', created: null, complete: false, completed: null, priority: null, contexts: null, projects: ['Project 1'], due: '2023-01-01', dueString: '2023-01-01', t: null, tString: null, rec: null, hidden: false, pm: null, visible: true, string: '' }, - { id: 2, body: 'Test', created: null, complete: true, completed: null, priority: null, contexts: null, projects: ['Project 2'], due: '2023-02-01', dueString: '2023-02-01', t: null, tString: null, rec: null, hidden: false, pm: null, visible: true, string: '' }, - { id: 3, body: 'Test', created: null, complete: false, completed: null, priority: null, contexts: null, projects: ['Project 1'], due: '2023-03-01', dueString: '2023-03-01', t: null, tString: null, rec: null, hidden: false, pm: null, visible: true, string: '' }, + { lineNumber: 1, body: 'Test', created: null, complete: false, completed: null, priority: null, contexts: null, projects: ['Project 1'], due: '2023-01-01', dueString: '2023-01-01', t: null, tString: null, rec: null, hidden: false, pm: null, string: '' }, + { lineNumber: 2, body: 'Test', created: null, complete: true, completed: null, priority: null, contexts: null, projects: ['Project 2'], due: '2023-02-01', dueString: '2023-02-01', t: null, tString: null, rec: null, hidden: false, pm: null, string: '' }, + { lineNumber: 3, body: 'Test', created: null, complete: false, completed: null, priority: null, contexts: null, projects: ['Project 1'], due: '2023-03-01', dueString: '2023-03-01', t: null, tString: null, rec: null, hidden: false, pm: null, string: '' }, ]; test('should return all todo objects if no filters are provided', () => { @@ -24,9 +24,9 @@ describe('Should filter todos based on passed filters', () => { projects: [ { values: ['Project 1'], exclude: false } ] } const expected = [ - { id: 1, body: 'Test', created: null, complete: false, completed: null, priority: null, contexts: null, projects: ['Project 1'], due: '2023-01-01', dueString: '2023-01-01', t: null, tString: null, rec: null, hidden: false, pm: null, visible: true, string: '' }, - { id: 2, body: 'Test', created: null, complete: true, completed: null, priority: null, contexts: null, projects: ['Project 2'], due: '2023-02-01', dueString: '2023-02-01', t: null, tString: null, rec: null, hidden: false, pm: null, visible: false, string: '' }, - { id: 3, body: 'Test', created: null, complete: false, completed: null, priority: null, contexts: null, projects: ['Project 1'], due: '2023-03-01', dueString: '2023-03-01', t: null, tString: null, rec: null, hidden: false, pm: null, visible: true, string: '' }, + { lineNumber: 1, body: 'Test', created: null, complete: false, completed: null, priority: null, contexts: null, projects: ['Project 1'], due: '2023-01-01', dueString: '2023-01-01', t: null, tString: null, rec: null, hidden: false, pm: null, string: '' }, + { lineNumber: 2, body: 'Test', created: null, complete: true, completed: null, priority: null, contexts: null, projects: ['Project 2'], due: '2023-02-01', dueString: '2023-02-01', t: null, tString: null, rec: null, hidden: false, pm: null, string: '' }, + { lineNumber: 3, body: 'Test', created: null, complete: false, completed: null, priority: null, contexts: null, projects: ['Project 1'], due: '2023-03-01', dueString: '2023-03-01', t: null, tString: null, rec: null, hidden: false, pm: null, string: '' }, ]; const result = applyAttributes(todoObjects, filters); expect(result).toEqual(expected); diff --git a/src/__tests__/main/Notifications.tsx b/src/__tests__/main/Notifications.tsx index 34138d99..b62696fc 100644 --- a/src/__tests__/main/Notifications.tsx +++ b/src/__tests__/main/Notifications.tsx @@ -1,6 +1,6 @@ import { handleNotification } from '../../main/modules/Notifications'; import { config } from '../../main/config'; -import { badge } from '../../main/modules/ProcessDataRequest/CreateTodoObjects'; +import { badge } from '../../main/modules/DataRequest/CreateTodoObjects'; import { Notification } from 'electron'; import dayjs from 'dayjs'; @@ -10,7 +10,7 @@ const dateTomorrowString = dateToday.add(1, 'day').format('YYYY-MM-DD'); const dateInSevenDaysString = dateToday.add(7, 'day').format('YYYY-MM-DD'); const dateInTwentyDaysString = dateToday.add(20, 'day').format('YYYY-MM-DD'); -jest.mock('../../main/modules/ProcessDataRequest/CreateTodoObjects', () => ({ +jest.mock('../../main/modules/DataRequest/CreateTodoObjects', () => ({ badge: { count: 0, }, diff --git a/src/__tests__/main/ProcessTodoObjects.tsx b/src/__tests__/main/ProcessTodoObjects.tsx deleted file mode 100644 index cbfab1d2..00000000 --- a/src/__tests__/main/ProcessTodoObjects.tsx +++ /dev/null @@ -1,224 +0,0 @@ -import fs from 'fs'; -import { createTodoObjects } from '../../main/modules/ProcessDataRequest/CreateTodoObjects'; -import { applySearchString } from '../../main/modules/Filters/Search'; -import { sortAndGroupTodoObjects, flattenTodoObjects } from '../../main/modules/ProcessDataRequest/ProcessTodoObjects'; - -jest.mock('../../main/config', () => ({ - config: { - get: jest.fn().mockReturnValue([ - { - active: true, - path: './src/__tests__/__mock__', - todoFilePath: 'recurrence.txt', - todoFileBookmark: null, - doneFilePath: 'done.txt', - doneFileBookmark: null, - }, - ]), - }, -})); - -jest.mock('../../main/config', () => ({ - config: { - get: jest.fn() - }, -})); - -jest.mock('electron', () => ({ - app: { - setBadgeCount: jest.fn(), - }, -})); - -const sorting = [ - { - "id": "1", - "value": "priority", - "invert": false - }, - { - "id": "2", - "value": "due", - "invert": false - }, - { - "id": "3", - "value": "projects", - "invert": false - }, - { - "id": "4", - "value": "contexts", - "invert": false - }, - { - "id": "5", - "value": "created", - "invert": false - }, - { - "id": "6", - "value": "t", - "invert": false - }, - { - "id": "7", - "value": "completed", - "invert": false - } -] - -let todoObjects: TodoObject[]; -let fileContent: string; - -describe('Process todo.txt objects', () => { - - beforeAll(() => { - jest.clearAllMocks(); - fileContent = fs.readFileSync('./src/__tests__/__mock__/todo.txt', 'utf8'); - todoObjects = createTodoObjects(fileContent); - }); - - beforeEach(() => { - todoObjects = createTodoObjects(fileContent); - }); - - test('Search for "test3"', () => { - const searchString: string = 'test3'; - todoObjects = applySearchString(searchString, todoObjects); - const visibleTodoObjects: TodoObject[] = todoObjects.filter((todoObject: TodoObject) => { - return todoObject.visible; - }); - expect(visibleTodoObjects.length).toEqual(4); - }); - - test('Search for "lorem"', () => { - const searchString: string = 'lorem'; - todoObjects = applySearchString(searchString, todoObjects); - const visibleTodoObjects: TodoObject[] = todoObjects.filter((todoObject: TodoObject) => { - return todoObject.visible; - }); - expect(visibleTodoObjects.length).toEqual(0); - }); - - test('Advanced search for "+testProject4 or +testProject2"', () => { - const searchString: string = '+testProject4 or +testProject2'; - todoObjects = applySearchString(searchString, todoObjects); - const visibleTodoObjects: TodoObject[] = todoObjects.filter((todoObject: TodoObject) => { - return todoObject.visible; - }); - expect(visibleTodoObjects.length).toEqual(2); - }); - - test('Advanced search for "+testProject4 and +testProject2" result in 0 found objects', () => { - const searchString:string = '+testProject4 and +testProject2'; - todoObjects = applySearchString(searchString, todoObjects); - const visibleTodoObjects: TodoObject[] = todoObjects.filter((todoObject: TodoObject) => { - return todoObject.visible; - }); - expect(visibleTodoObjects.length).toEqual(0); - }); - - test('Advanced search for "+testProject4 and !+testProject2" result in 1 found objects', () => { - const searchString:string = '+testProject4 and !+testProject2'; - todoObjects = applySearchString(searchString, todoObjects); - const visibleTodoObjects: TodoObject[] = todoObjects.filter((todoObject: TodoObject) => { - return todoObject.visible; - }); - expect(visibleTodoObjects.length).toEqual(1); - }); - - test('Function creates 3 top level groups (A, B, C)', () => { - const sortedAndGroupedTodoObjects = sortAndGroupTodoObjects(todoObjects, sorting); - const groups = Object.keys(sortedAndGroupedTodoObjects); - expect(groups).toEqual(['A', 'B', 'C']); - }); - - test('Top level group sorted asc', () => { - sorting[0].invert = true; - const sortedAndGroupedTodoObjects = sortAndGroupTodoObjects(todoObjects, sorting); - const groups = Object.keys(sortedAndGroupedTodoObjects); - expect(groups).toEqual(['C', 'B', 'A']); - }); - - test('Sorting: Priority -> Due dates ', () => { - sorting[0].invert = false; - const sortedAndGrouped: any = sortAndGroupTodoObjects(todoObjects, sorting); - const flattenedObjects: any = flattenTodoObjects(sortedAndGrouped, "priority") - expect(flattenedObjects[2].due).toContain('2023-01-01'); - expect(flattenedObjects[3].due).toContain('2023-01-02'); - expect(flattenedObjects[4].due).toContain('2023-01-03'); - expect(flattenedObjects[5].due).toContain('2023-01-04'); - expect(flattenedObjects[6].due).toContain('2025-01-05'); - }); - - test('Sorting: Priority -> Projects ', () => { - sorting[0].invert = false; - sorting[1].value = 'projects'; - const sortedAndGrouped: any = sortAndGroupTodoObjects(todoObjects, sorting); - const flattenedObjects: any = flattenTodoObjects(sortedAndGrouped, "priority") - - expect(flattenedObjects[2].projects[0]).toContain('testProject1'); - expect(flattenedObjects[3].projects[0]).toContain('testProject2'); - expect(flattenedObjects[4].projects[0]).toContain('testProject3'); - expect(flattenedObjects[5].projects[0]).toContain('testProject4'); - expect(flattenedObjects[6].projects[0]).toContain('testProject5'); - }); - - test('Sorting: Priority -> Projects, but inverted ', () => { - sorting[1].invert = true; - sorting[1].value = 'projects'; - const sortedAndGrouped: any = sortAndGroupTodoObjects(todoObjects, sorting); - const flattenedObjects: any = flattenTodoObjects(sortedAndGrouped, "priority") - - expect(flattenedObjects[2].projects[0]).toContain('testProject5'); - expect(flattenedObjects[3].projects[0]).toContain('testProject4'); - expect(flattenedObjects[4].projects[0]).toContain('testProject3'); - expect(flattenedObjects[5].projects[0]).toContain('testProject2'); - expect(flattenedObjects[6].projects[0]).toContain('testProject1'); - }); - - test('Sorting: Priority -> Contexts -> Creation', () => { - sorting[1].invert = false; - sorting[1].value = 'contexts'; - sorting[2].value = 'creation'; - const sortedAndGrouped: any = sortAndGroupTodoObjects(todoObjects, sorting); - const flattenedObjects: any = flattenTodoObjects(sortedAndGrouped, "priority") - - expect(flattenedObjects[3].created).toContain('2025-12-05'); - expect(flattenedObjects[4].created).toContain('2025-12-06'); - expect(flattenedObjects[5].created).toContain('2025-12-07'); - expect(flattenedObjects[6].created).toContain('2025-12-09'); - }); - - test('Sorting: Priority -> Contexts -> Projects (inverted)', () => { - sorting[1].invert = false; - sorting[1].value = 'contexts'; - sorting[2].value = 'projects'; - sorting[2].invert = true; - const sortedAndGrouped: any = sortAndGroupTodoObjects(todoObjects, sorting); - const flattenedObjects: any = flattenTodoObjects(sortedAndGrouped, "priority") - - expect(flattenedObjects[2].projects[0]).toContain('testProject5'); - expect(flattenedObjects[3].projects[0]).toContain('testProject4'); - expect(flattenedObjects[4].projects[0]).toContain('testProject3'); - expect(flattenedObjects[5].projects[0]).toContain('testProject2'); - expect(flattenedObjects[6].projects[0]).toContain('testProject1'); - }); - - test('Sorting: Contexts -> Projects, but inverted ', () => { - sorting[0].invert = false; - sorting[0].value = 'contexts'; - sorting[1].invert = true; - sorting[1].value = 'projects'; - const sortedAndGrouped: any = sortAndGroupTodoObjects(todoObjects, sorting); - const flattenedObjects: any = flattenTodoObjects(sortedAndGrouped, "contexts") - - expect(flattenedObjects[0].projects[0]).toContain('testProject5'); - expect(flattenedObjects[1].projects[0]).toContain('testProject4'); - expect(flattenedObjects[2].projects[0]).toContain('testProject3'); - expect(flattenedObjects[3].projects[0]).toContain('testProject2'); - expect(flattenedObjects[4].projects[0]).toContain('testProject1'); - }); - -}); \ No newline at end of file diff --git a/src/__tests__/main/Write.tsx b/src/__tests__/main/Write.tsx index f9d397a2..8f066c03 100644 --- a/src/__tests__/main/Write.tsx +++ b/src/__tests__/main/Write.tsx @@ -28,7 +28,7 @@ jest.mock('../../main/config', () => ({ }, })); -jest.mock('../../main/modules/ProcessDataRequest/CreateTodoObjects', () => ({ +jest.mock('../../main/modules/DataRequest/CreateTodoObjects', () => ({ linesInFile: ['Line 1', 'Line 2', 'Line 3'], })); diff --git a/src/main/config.tsx b/src/main/config.tsx index 944b2974..4fe35972 100644 --- a/src/main/config.tsx +++ b/src/main/config.tsx @@ -142,7 +142,7 @@ filter.onDidChange('attributes', () => { try { const requestedData = dataRequest(searchString); mainWindow!.webContents.send('requestData', requestedData); - } catch(error: Error) { + } catch(error: any) { handleError(error); } }); @@ -152,7 +152,7 @@ config.onDidAnyChange((settings) => { const requestedData = dataRequest(searchString); mainWindow!.webContents.send('requestData', requestedData); mainWindow!.webContents.send('settingsChanged', settings); - } catch(error: Error) { + } catch(error: any) { handleError(error); } }); @@ -162,7 +162,7 @@ config.onDidChange('files', (newValue: FileObject[] | undefined) => { if (newValue !== undefined) { createFileWatcher(newValue); } - } catch(error: Error) { + } catch(error: any) { handleError(error); } }); @@ -172,7 +172,7 @@ config.onDidChange('colorTheme', (colorTheme) => { if(colorTheme === 'system' || colorTheme === 'light' || colorTheme === 'dark') { nativeTheme.themeSource = colorTheme; } - } catch(error: Error) { + } catch(error: any) { handleError(error); } }); diff --git a/src/main/main.tsx b/src/main/main.tsx index 32165713..054a3deb 100644 --- a/src/main/main.tsx +++ b/src/main/main.tsx @@ -5,7 +5,6 @@ import { config } from './config'; import { createMenu } from './modules/Menu'; import { getAssetPath, resolveHtmlPath } from './util'; import { createFileWatcher, watcher } from './modules/File/Watcher'; -import { addFile } from './modules/File/File'; import { createTray } from './modules/Tray'; import './modules/IpcMain'; diff --git a/src/main/modules/DataRequest/ChangeCompleteState.tsx b/src/main/modules/DataRequest/ChangeCompleteState.tsx index 2b512308..34344f9b 100644 --- a/src/main/modules/DataRequest/ChangeCompleteState.tsx +++ b/src/main/modules/DataRequest/ChangeCompleteState.tsx @@ -6,6 +6,7 @@ function changeCompleteState(string: string, state: boolean): string { let content = string.replaceAll(/[\x10\r\n]/g, ' [LB] '); + // todo: use createTodoObject() instead const JsTodoTxtObject = new Item(content); JsTodoTxtObject.setComplete(state); diff --git a/src/main/modules/DataRequest/CreateRecurringTodo.tsx b/src/main/modules/DataRequest/CreateRecurringTodo.tsx index 9e047340..e8eed332 100644 --- a/src/main/modules/DataRequest/CreateRecurringTodo.tsx +++ b/src/main/modules/DataRequest/CreateRecurringTodo.tsx @@ -44,6 +44,7 @@ const createRecurringTodo = (string: string, recurrence: string): string => { let updatedString = (string || '').replaceAll(/[\x10\r\n]/g, ` ${String.fromCharCode(16)} `); + // todo: use createTodoObject instead const JsTodoTxtObject = new Item(updatedString); const creationDate = new Date(); diff --git a/src/main/modules/DataRequest/CreateTodoObjects.tsx b/src/main/modules/DataRequest/CreateTodoObjects.tsx index 4a3479a4..19546210 100644 --- a/src/main/modules/DataRequest/CreateTodoObjects.tsx +++ b/src/main/modules/DataRequest/CreateTodoObjects.tsx @@ -8,7 +8,7 @@ import dayjs from 'dayjs'; let linesInFile: string[]; export const badge: Badge = { count: 0 }; -function createTodoObject(row: number, string: string, attributeType?: string, attributeValue?: string): TodoObject { +function createTodoObject(lineNumber: number, string: string, attributeType?: string, attributeValue?: string): TodoObject { let content = string.replaceAll(/[\x10\r\n]/g, ' [LB] '); let JsTodoTxtObject = new Item(content); @@ -40,17 +40,20 @@ function createTodoObject(row: number, string: string, attributeType?: string, a const hidden = extensions.some(extension => extension.key === 'h' && extension.value === '1'); const pm: string | number | null = extensions.find(extension => extension.key === 'pm')?.value || null; const rec = extensions.find(extension => extension.key === 'rec')?.value || null; - const creation = dayjs(JsTodoTxtObject.created()).isValid() ? dayjs(JsTodoTxtObject.created()).format('YYYY-MM-DD') : null; + const created = dayjs(JsTodoTxtObject.created()).isValid() ? dayjs(JsTodoTxtObject.created()).format('YYYY-MM-DD') : null; const completed = dayjs(JsTodoTxtObject.completed()).isValid() ? dayjs(JsTodoTxtObject.completed()).format('YYYY-MM-DD') : null; + const projects = JsTodoTxtObject.projects().length > 0 ? JsTodoTxtObject.projects() : null; + const contexts = JsTodoTxtObject.contexts().length > 0 ? JsTodoTxtObject.contexts() : null; + return { - row, + lineNumber, body, - created: creation, + created, complete: JsTodoTxtObject.complete(), completed: completed, priority: JsTodoTxtObject.priority(), - contexts: JsTodoTxtObject.contexts(), - projects: JsTodoTxtObject.projects(), + contexts: contexts, + projects: projects, due, dueString, notify, @@ -70,20 +73,23 @@ function createTodoObjects(fileContent: string | null): TodoObject[] | [] { } badge.count = 0; linesInFile = fileContent.split(/[\r\n]+/).filter(line => line.trim() !== ''); + + // todo: might causes problems due to index offset created by it const excludeLinesWithPrefix: string[] = config.get('excludeLinesWithPrefix') || []; - const todoObjects: TodoObject[] = linesInFile.map((line, i) => { + const todoObjects: TodoObject[] = linesInFile.map((line, index) => { if (excludeLinesWithPrefix.some(prefix => line.startsWith(prefix))) { return null; } - const todoObject: TodoObject = createTodoObject(i, line); + const todoObject: TodoObject = createTodoObject(index, line); if (todoObject.body && !todoObject.complete) { handleNotification(todoObject.due, todoObject.body, badge); } return todoObject; + }).filter(Boolean) as TodoObject[]; app.setBadgeCount(badge.count); diff --git a/src/main/modules/DataRequest/DataRequest.tsx b/src/main/modules/DataRequest/DataRequest.tsx index bc69b921..56c14cbe 100644 --- a/src/main/modules/DataRequest/DataRequest.tsx +++ b/src/main/modules/DataRequest/DataRequest.tsx @@ -2,10 +2,9 @@ import { getActiveFile } from '../File/Active'; import { readFileContent } from '../File/File'; import { config, filter } from '../../config'; import { applySearchString } from '../Filters/Search'; -import { applyAttributes, handleHiddenTodoObjects, handleCompletedTodoObjects, handleTodoObjectsDates } from '../Filters/Filters'; +import { applyAttributes, handleCompletedTodoObjects, handleTodoObjectsDates } from '../Filters/Filters'; import { updateAttributes, attributes } from '../Attributes'; import { createTodoObjects } from './CreateTodoObjects'; -import { mainWindow } from '../../main'; import { sortAndGroupTodoObjects } from './SortAndGroup'; let searchString: string; @@ -35,6 +34,7 @@ function dataRequest(search?: string): RequestedData { todoObjects = handleTodoObjectsDates(todoObjects); + // todo: should this be improved const completedTodoObjects: TodoObject[] = todoObjects.filter((todoObject: TodoObject) => { return todoObject.complete; }); @@ -51,7 +51,7 @@ function dataRequest(search?: string): RequestedData { updateAttributes(todoObjects, sorting, false); - const todoData = sortAndGroupTodoObjects(todoObjects, sorting); + const todoData: TodoDate = sortAndGroupTodoObjects(todoObjects, sorting); const requestedData: RequestedData = { todoData, diff --git a/src/main/modules/DataRequest/SortAndGroup.tsx b/src/main/modules/DataRequest/SortAndGroup.tsx index e0329f43..c3b717d5 100644 --- a/src/main/modules/DataRequest/SortAndGroup.tsx +++ b/src/main/modules/DataRequest/SortAndGroup.tsx @@ -1,16 +1,27 @@ import { config } from '../../config'; -function sortAndGroupTodoObjects(todoObjects: TodoObject[], sorting: Sorting[], settings: Settings): TodoObject[] { - let rows; +function sortAndGroupTodoObjects(todoObjects: TodoObject[], sorting: Sorting[]): TodoGroup { const fileSorting: boolean = config.get('fileSorting'); const showHidden: boolean = config.get('showHidden'); function compareValues(a: any, b: any, invert: boolean): number { - const comparison = String(a).localeCompare(String(b), undefined, { sensitivity: 'base' }); - return invert ? -comparison : comparison; + if (a === null && b === null) return 0; + if (a === null) return 1; + if (b === null) return -1; + + const strA = String(a); + const strB = String(b); + + if (strA < strB) { + return invert ? 1 : -1; + } + if (strA > strB) { + return invert ? -1 : 1; + } + return 0; } - function applySorting(a: TodoObject, b: TodoObject, sorting): number { + function applySorting(a: TodoObject, b: TodoObject, sorting: Sorting[]): number { for (const { value, invert } of sorting) { const compareResult = compareValues(a[value], b[value], invert); if (compareResult !== 0) { @@ -20,10 +31,10 @@ function sortAndGroupTodoObjects(todoObjects: TodoObject[], sorting: Sorting[], return 0; } - function groupTodoObjectsByKey(todoObjects, attributeKey) { - const grouped = {}; + function groupTodoObjectsByKey(todoObjects: TodoObject[], attributeKey: string) { + const grouped: TodoGroup = {}; for (const todoObject of todoObjects) { - const groupKey = todoObject[attributeKey] || ''; + const groupKey = todoObject[attributeKey] || null; if (!grouped[groupKey]) { grouped[groupKey] = { title: groupKey, @@ -31,8 +42,7 @@ function sortAndGroupTodoObjects(todoObjects: TodoObject[], sorting: Sorting[], visible: false }; } - rows++; - todoObject.row = rows; + grouped[groupKey].todoObjects.push(todoObject); grouped[groupKey].visible = grouped[groupKey].todoObjects.some(todoObject => { return !todoObject.hidden || (showHidden && todoObject.hidden); @@ -41,22 +51,23 @@ function sortAndGroupTodoObjects(todoObjects: TodoObject[], sorting: Sorting[], return Object.values(grouped); } - function sortTodoObjects(todoObjects: TodoObject[], sorting): any { + function sortTodoObjects(todoObjects: TodoObject[], sorting: Sorting[]): any { const { value } = sorting[0]; const grouped = groupTodoObjectsByKey(todoObjects, value); return grouped; } - if(fileSorting) { + if (fileSorting) { return [{ title: null, todoObjects: todoObjects, visible: true }] } - rows = 0; + const sortedTodoObjects = [...todoObjects].sort((a, b) => applySorting(a, b, sorting)); - return sortTodoObjects(sortedTodoObjects, sorting); + const sortedAndGroupedTodoObjects = sortTodoObjects(sortedTodoObjects, sorting); + return sortedAndGroupedTodoObjects; } export { sortAndGroupTodoObjects }; \ No newline at end of file diff --git a/src/main/modules/File/Dialog.tsx b/src/main/modules/File/Dialog.tsx index be3f67ee..d7bfc124 100644 --- a/src/main/modules/File/Dialog.tsx +++ b/src/main/modules/File/Dialog.tsx @@ -2,7 +2,6 @@ import { app, dialog, OpenDialogReturnValue, SaveDialogReturnValue } from 'elect import path from 'path'; import { addFile, addDoneFile } from './File'; import { writeToFile } from './Write'; -import { handleError } from '../../util'; const dialogFilters = [ { diff --git a/src/main/modules/File/Watcher.tsx b/src/main/modules/File/Watcher.tsx index d13a2a1f..1a38c761 100644 --- a/src/main/modules/File/Watcher.tsx +++ b/src/main/modules/File/Watcher.tsx @@ -29,7 +29,7 @@ function createFileWatcher(files: FileObject[]): void { const requestedData = dataRequest(searchString); mainWindow!.webContents.send('requestData', requestedData); console.log(`${file} has been changed`); - } catch(error: Error) { + } catch(error: any) { handleError(error); } }) diff --git a/src/main/modules/File/Write.tsx b/src/main/modules/File/Write.tsx index 7a06a1cc..f19142ba 100644 --- a/src/main/modules/File/Write.tsx +++ b/src/main/modules/File/Write.tsx @@ -52,15 +52,11 @@ function prepareContentForWriting(lineNumber: number, string: string) { linesInFile[lineNumber] = linesToAdd.join('\n'); } else { for (let i = 0; i < linesToAdd.length; i++) { - if(config.get('appendCreationDate')) { - const JsTodoTxtObject = new Item(linesToAdd[i]); - if(!JsTodoTxtObject.created()) { - JsTodoTxtObject.setCreated(new Date()); - } - linesInFile.push(JsTodoTxtObject.toString()); - } else { - linesInFile.push(linesToAdd[i]); + const JsTodoTxtObject = new Item(linesToAdd[i]); + if(config.get('appendCreationDate') && !JsTodoTxtObject.created()) { + JsTodoTxtObject.setCreated(new Date()); } + linesInFile.push(JsTodoTxtObject.toString()); } } diff --git a/src/main/modules/Filters/Filters.tsx b/src/main/modules/Filters/Filters.tsx index 46292206..a2ff6dc2 100644 --- a/src/main/modules/Filters/Filters.tsx +++ b/src/main/modules/Filters/Filters.tsx @@ -52,4 +52,4 @@ function handleTodoObjectsDates(todoObjects: TodoObject[]): TodoObject[] { }); } -export { applyAttributes, handleCompletedTodoObjects, handleHiddenTodoObjects, handleTodoObjectsDates }; \ No newline at end of file +export { applyAttributes, handleCompletedTodoObjects, handleTodoObjectsDates }; \ No newline at end of file diff --git a/src/main/modules/Filters/Search.tsx b/src/main/modules/Filters/Search.tsx index 9d1bb1e1..0fb17ae5 100644 --- a/src/main/modules/Filters/Search.tsx +++ b/src/main/modules/Filters/Search.tsx @@ -16,7 +16,7 @@ function applySearchString(searchString: string, todoObjects: TodoObject[]): Tod function checkForSearchMatches(todoString: string, searchString: string) { try { - const todoObject = createTodoObject(todoString); + const todoObject = createTodoObject(-1, todoString); const query = FilterLang.parse(searchString); return runQuery(todoObject, query); } catch (error) { diff --git a/src/main/modules/IpcMain.tsx b/src/main/modules/IpcMain.tsx index 63066618..5bbb9d9a 100644 --- a/src/main/modules/IpcMain.tsx +++ b/src/main/modules/IpcMain.tsx @@ -13,7 +13,7 @@ function handleDataRequest(event: IpcMainEvent, searchString: string) { try { const requestedData = dataRequest(searchString); event.reply('requestData', requestedData); - } catch(error: Error) { + } catch(error: any) { handleError(error); } } @@ -22,7 +22,7 @@ function handleUpdateAttributeFields(event: IpcMainEvent, index: number, string: try { const todoObject = createTodoObject(index, string); event.reply('updateAttributeFields', todoObject); - } catch(error: Error) { + } catch(error: any) { handleError(error); } } @@ -31,7 +31,7 @@ function handleUpdateTodoObject(event: IpcMainEvent, index: number, string: stri try { const todoObject = createTodoObject(index, string, attributeType, attributeValue); event.reply('updateTodoObject', todoObject); - } catch(error: Error) { + } catch(error: any) { handleError(error); } } @@ -46,7 +46,7 @@ function handleWriteTodoToFile(event: IpcMainEvent, index: number, string: strin if(state !== undefined && index >= 0) updatedString = changeCompleteState(string, state) prepareContentForWriting(index, updatedString); } - } catch(error: Error) { + } catch(error: any) { handleError(error); } } @@ -54,7 +54,7 @@ function handleWriteTodoToFile(event: IpcMainEvent, index: number, string: strin function handleStoreGetConfig(event: IpcMainEvent, value: string) { try { event.returnValue = config.get(value); - } catch(error: Error) { + } catch(error: any) { handleError(error); } } @@ -63,7 +63,7 @@ function handleStoreSetConfig(event: IpcMainEvent, key: string, value: any) { try { config.set(key, value); console.log(`Set ${key} to ${value}`); - } catch(error: Error) { + } catch(error: any) { handleError(error); } } @@ -71,7 +71,7 @@ function handleStoreSetConfig(event: IpcMainEvent, key: string, value: any) { function handleStoreSetFilters(event: IpcMainEvent, key: string, value: any): void { try { filter.set(key, value); - } catch(error: Error) { + } catch(error: any) { handleError(error); } } @@ -79,7 +79,7 @@ function handleStoreSetFilters(event: IpcMainEvent, key: string, value: any): vo function handleStoreGetFilters(event: IpcMainEvent, value: string): void { try { event.returnValue = filter.get(value); - } catch(error: Error) { + } catch(error: any) { handleError(error); } } @@ -87,7 +87,7 @@ function handleStoreGetFilters(event: IpcMainEvent, value: string): void { function handleStoreSetNotifiedTodoObjects(event: IpcMainEvent, value: any): void { try { notifiedTodoObjectsStorage.set('notifiedTodoObjects', value); - } catch(error: Error) { + } catch(error: any) { handleError(error); } } @@ -95,7 +95,7 @@ function handleStoreSetNotifiedTodoObjects(event: IpcMainEvent, value: any): voi function handleSetFile(event: IpcMainEvent, index: number): void { try { setFile(index); - } catch(error: Error) { + } catch(error: any) { handleError(error); } } @@ -103,7 +103,7 @@ function handleSetFile(event: IpcMainEvent, index: number): void { function handleRemoveFile(event: IpcMainEvent, index: number): void { try { removeFile(index); - } catch(error: Error) { + } catch(error: any) { handleError(error); } } @@ -111,7 +111,7 @@ function handleRemoveFile(event: IpcMainEvent, index: number): void { function handleAddFile(event: IpcMainEvent, filePath: string): void { try { addFile(filePath, null); - } catch(error: Error) { + } catch(error: any) { handleError(error); } } @@ -119,7 +119,7 @@ function handleAddFile(event: IpcMainEvent, filePath: string): void { function handleDroppedFile(event: IpcMainEvent, filePath: string): void { try { addFile(filePath, null); - } catch(error: Error) { + } catch(error: any) { handleError(error); } } @@ -127,15 +127,15 @@ function handleDroppedFile(event: IpcMainEvent, filePath: string): void { function handleRevealInFileManager(event: IpcMainEvent, pathToReveal: string): void { try { shell.showItemInFolder(pathToReveal); - } catch(error: Error) { + } catch(error: any) { handleError(error); } } async function handleOpenFile(event: IpcMainEvent, setDoneFile: boolean): Promise { try { - openFile(setDoneFile); - } catch(error: Error) { + await openFile(setDoneFile); + } catch(error: any) { handleError(error); } } @@ -143,7 +143,7 @@ async function handleOpenFile(event: IpcMainEvent, setDoneFile: boolean): Promis async function handleCreateFile(event: IpcMainEvent, setDoneFile: boolean): Promise { try { await createFile(setDoneFile); - } catch(error: Error) { + } catch(error: any) { handleError(error); } } @@ -151,16 +151,16 @@ async function handleCreateFile(event: IpcMainEvent, setDoneFile: boolean): Prom function handleRemoveLineFromFile(event: IpcMainEvent, index: number) { try { removeLineFromFile(index); - } catch(error: Error) { + } catch(error: any) { handleError(error); } } -function handleArchiveTodos(event: IpcMainEvent): Promise { +function handleArchiveTodos(event: IpcMainEvent): void { try { const archivingResult = archiveTodos(); event.reply('responseFromMainProcess', archivingResult); - } catch(error: Error) { + } catch(error: any) { handleError(error); } } @@ -169,15 +169,15 @@ function handleSaveToClipboard(event: IpcMainEvent, string: string): void { try { clipboard.writeText(string); event.reply('responseFromMainProcess', 'Copied to clipboard: ' + string); - } catch(error: Error) { + } catch(error: any) { handleError(error); } } -async function handleOpenInBrowser(event: IpcMainEvent, url: string): Promise { +function handleOpenInBrowser(event: IpcMainEvent, url: string): void { try { - await shell?.openExternal(url); - } catch(error: Error) { + shell?.openExternal(url); + } catch(error: any) { handleError(error); } } diff --git a/src/main/modules/Notifications.tsx b/src/main/modules/Notifications.tsx index fb2c3278..d9d9dd67 100644 --- a/src/main/modules/Notifications.tsx +++ b/src/main/modules/Notifications.tsx @@ -1,7 +1,6 @@ import crypto from 'crypto'; import { Notification } from 'electron'; import { config, filter, notifiedTodoObjectsStorage } from '../config'; -import { createTodoObject } from './DataRequest/CreateTodoObjects'; import { checkForSearchMatches } from './Filters/Search'; import dayjs, { Dayjs } from "dayjs"; import isToday from 'dayjs/plugin/isToday'; diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 330f04b7..9f4968af 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -65,10 +65,6 @@ const App = () => { }, [settings.language]); useEffect(() => { - // const windowHeight = window.innerHeight; - // const calculatedRowCount = Math.floor(windowHeight / 35) * 2; - // setVisibleRowCount(calculatedRowCount); - // setLoadMoreRows(true); ipcRenderer.send('requestData'); }, []); @@ -146,6 +142,8 @@ const App = () => { setContextMenu={setContextMenu} setPromptItem={setPromptItem} settings={settings} + headers={headers} + searchString={searchString} /> )} diff --git a/src/renderer/Dialog/Dialog.tsx b/src/renderer/Dialog/Dialog.tsx index c1e1a5cf..2480c6f8 100644 --- a/src/renderer/Dialog/Dialog.tsx +++ b/src/renderer/Dialog/Dialog.tsx @@ -57,7 +57,7 @@ const DialogComponent: React.FC = memo(({ const handleAdd = () => { try { if(textFieldValue) { - const index = (todoObject) ? todoObject.id : -1; + const index = (todoObject) ? todoObject.lineNumber : -1; const string = textFieldValue.replaceAll(/\n/g, String.fromCharCode(16)); ipcRenderer.send('writeTodoToFile', index, string); handleClose(); @@ -106,7 +106,7 @@ const DialogComponent: React.FC = memo(({ updatedValue = value; } - ipcRenderer.send('updateTodoObject', todoObject?.id, textFieldValue, type, updatedValue); + ipcRenderer.send('updateTodoObject', todoObject?.lineNumber, textFieldValue, type, updatedValue); } catch(error: any) { console.error(error); @@ -122,7 +122,7 @@ const DialogComponent: React.FC = memo(({ useEffect(() => { const handleTextFieldValue = () => { - if(textFieldValue) ipcRenderer.send('updateAttributeFields', todoObject?.id, textFieldValue) + if(textFieldValue) ipcRenderer.send('updateAttributeFields', todoObject?.lineNumber, textFieldValue) }; const delayedSearch: NodeJS.Timeout = setTimeout(handleTextFieldValue, 100); return () => { @@ -192,7 +192,7 @@ const DialogComponent: React.FC = memo(({ onClick={handleAdd} data-testid="dialog-button-add-update" > - {todoObject && todoObject.id >= 0 + {todoObject && todoObject.lineNumber >= 0 ? t('todoDialog.footer.update') : settings.bulkTodoCreation ? `${t('todoDialog.footer.add')} (${numRowsWithContent || 0})` diff --git a/src/renderer/Drawer/Attributes.tsx b/src/renderer/Drawer/Attributes.tsx index bb11b18f..e674997a 100644 --- a/src/renderer/Drawer/Attributes.tsx +++ b/src/renderer/Drawer/Attributes.tsx @@ -63,9 +63,9 @@ const DrawerAttributes: React.FC = memo(({ } }); const sorting = [t('drawer.attributes.overdue'), t('drawer.attributes.elapsed'), t('drawer.attributes.lastWeek'), t('drawer.attributes.yesterday'), t('drawer.attributes.today'), t('drawer.attributes.tomorrow'), t('drawer.attributes.thisWeek'), t('drawer.attributes.nextWeek'), t('drawer.attributes.thisMonth'), t('drawer.attributes.nextMonth')] - const sortedAttributes = Object.fromEntries( - Object.entries(processedAttributes) - .sort(([aKey, a], [bKey, b]) => { + const sortedAttributes = Object.fromEntries( + // todo: fix types + Object.entries(processedAttributes).sort((a: any, b: any) => { const indexA = sorting.indexOf(a.name); const indexB = sorting.indexOf(b.name); return (indexA !== -1 ? indexA : Number.MAX_SAFE_INTEGER) - (indexB !== -1 ? indexB : Number.MAX_SAFE_INTEGER); diff --git a/src/renderer/Grid/DatePickerInline.tsx b/src/renderer/Grid/DatePickerInline.tsx index 947bf4f6..644dcc01 100644 --- a/src/renderer/Grid/DatePickerInline.tsx +++ b/src/renderer/Grid/DatePickerInline.tsx @@ -39,7 +39,7 @@ const DatePickerInline: React.FC = ({ const handleChange = (date: dayjs.Dayjs | null) => { try { - ipcRenderer.send('writeTodoToFile', todoObject.id, todoObject.string, false, type, dayjs(date).format('YYYY-MM-DD')); + ipcRenderer.send('writeTodoToFile', todoObject.lineNumber, todoObject.string, false, type, dayjs(date).format('YYYY-MM-DD')); } catch(error: any) { console.error(error); } diff --git a/src/renderer/Grid/Grid.tsx b/src/renderer/Grid/Grid.tsx index 09dd4e47..516a1acb 100644 --- a/src/renderer/Grid/Grid.tsx +++ b/src/renderer/Grid/Grid.tsx @@ -15,6 +15,8 @@ interface GridComponentProps { setTodoObject: React.Dispatch>; setPromptItem: React.Dispatch>; settings: Settings; + headers: Headers; + searchString: string; } const GridComponent: React.FC = memo(({ @@ -25,12 +27,14 @@ const GridComponent: React.FC = memo(({ setTodoObject, setPromptItem, settings, + headers, + searchString, }) => { - let renderedRows = 0; + let renderedRows: number[] = []; const list = document.getElementById('grid'); - const [loadMoreRows, setLoadMoreRows] = useState(true); - const [maxRows, setMaxRows] = useState(50); + const [loadMoreRows, setLoadMoreRows] = useState(false); + const [maxRows, setMaxRows] = useState(Math.floor(window.innerHeight / 35) * 2); const handleButtonClick = (key: string, name: string, values: string[]) => { handleFilterSelect(key, name, values, filters, false); @@ -67,17 +71,20 @@ const GridComponent: React.FC = memo(({ } }; - const handleScroll = () => { - if(list && loadMoreRows) { - const scrollPos = list.scrollTop; - const totalHeight = list.scrollHeight; - const clientHeight = list.clientHeight; - if(totalHeight - scrollPos <= clientHeight * 3) { - + const handleScroll = () => { + if(list) { + const a: number = list.scrollTop; + const b: number = list.scrollHeight - list.clientHeight; + const c: number = a / b; + + if(c >= 0.85 && renderedRows.length < headers.availableObjects) { + setLoadMoreRows(true); + } + + if(loadMoreRows) { setLoadMoreRows(false); setMaxRows((maxRows) => maxRows + 30); - ipcRenderer.send('requestData'); - + ipcRenderer.send('requestData', searchString); } } }; @@ -85,13 +92,9 @@ const GridComponent: React.FC = memo(({ return ( {todoData?.map(group => { - - if ((group.row <= renderedRows && renderedRows >= maxRows) || !group.visible) { + if (!group.visible) { return null; } - - renderedRows++; - return ( = memo(({ filters={filters} onClick={handleButtonClick} /> - {group.todoObjects.map(todoObject => { + {(() => { + const rows = []; + for (let i = 0; i < group.todoObjects.length; i++) { + const todoObject = group.todoObjects[i]; - if (!settings.showHidden && todoObject.hidden || todoObject.row <= renderedRows && renderedRows >= maxRows) { - return null; - } + if(renderedRows.length >= maxRows) { + break; + } else if(renderedRows.includes(todoObject.lineNumber)) { + continue; + } - renderedRows++; + renderedRows.push(todoObject.lineNumber); + + rows.push( + + ); + } + return rows; - return ( - - ); - })} + })()} ); })} diff --git a/src/renderer/Grid/Row.tsx b/src/renderer/Grid/Row.tsx index 0568df0f..2759e946 100644 --- a/src/renderer/Grid/Row.tsx +++ b/src/renderer/Grid/Row.tsx @@ -36,7 +36,7 @@ const Row: React.FC = memo(({ }) => { const handleConfirmDelete = () => { - if(todoObject) ipcRenderer.send('removeLineFromFile', todoObject?.id); + if(todoObject) ipcRenderer.send('removeLineFromFile', todoObject?.lineNumber); }; const handleSaveToClipboard = () => { @@ -68,7 +68,7 @@ const Row: React.FC = memo(({ }; const handleCheckboxChange = (event: React.ChangeEvent) => { - ipcRenderer.send('writeTodoToFile', todoObject.id, todoObject.string, event.target.checked, false); + ipcRenderer.send('writeTodoToFile', todoObject.lineNumber, todoObject.string, event.target.checked, false); }; const handleRowClick = (event: any) => { @@ -110,7 +110,7 @@ const Row: React.FC = memo(({ <> = ({ const handleResponse = function (response: Error | string) { const severity = response instanceof Error ? 'error' : 'success'; setSnackBarSeverity(severity); - setSnackBarContent(response instanceof Error ? response.message : response); + if(response instanceof Error) { + setSnackBarContent(response.message); + console.error(response); + } else { + setSnackBarContent(response); + console.info(response); + } }; const handleDrop = (event: any) => { diff --git a/src/renderer/Navigation.scss b/src/renderer/Navigation.scss index f8d60d4f..bce7c634 100644 --- a/src/renderer/Navigation.scss +++ b/src/renderer/Navigation.scss @@ -63,9 +63,9 @@ svg { color: $dark-grey; } - &:hover, &:active, &.active { - background: $light-grey; - } + // &:hover, &:active, &.active { + // background: $light-grey; + // } &:focus-visible { background: $mid-grey; border-color: $mid-grey; diff --git a/src/renderer/Settings/LanguageSelector.tsx b/src/renderer/Settings/LanguageSelector.tsx index 251d0384..2cd636f6 100644 --- a/src/renderer/Settings/LanguageSelector.tsx +++ b/src/renderer/Settings/LanguageSelector.tsx @@ -66,7 +66,7 @@ i18n .init(options) .then(() => { if(!store.getConfig('language')) { - store.setConfig('language', navigator.language.toLowerCase().substr(0, 2)); + store.setConfig('language', navigator.language.toLowerCase().slice(0, 2)); } i18n.on('missingKey', (key: string) => { console.warn(`Missing translation key: ${key}`); diff --git a/src/types.tsx b/src/types.tsx index 278f85ef..93cc853e 100644 --- a/src/types.tsx +++ b/src/types.tsx @@ -100,7 +100,7 @@ declare global { } interface TodoObject { - row: number; + lineNumber: number; body: string | null; created: string | null; complete: boolean; @@ -117,6 +117,7 @@ declare global { pm: number | string | null; string: string | null; notify?: boolean; + [key: string]: any; } interface TodoGroup { @@ -204,7 +205,7 @@ declare global { }; interface RequestedData { - todoObjects: TodoObject[], + todoData: TodoDate, attributes: Attributes, headers: HeadersObject, filters: Filters,