diff --git a/.gitignore b/.gitignore index 9392664..71e4e2c 100644 --- a/.gitignore +++ b/.gitignore @@ -20,9 +20,10 @@ data.json # zip-archive obsidian-gamified-pkm/obsidian-gamified-pkm.zip +*.zip # Exclude macOS Finder (System Explorer) View States -coverage/ +/coverage/ .DS_Store /cp-js2cortex.bat ncryption diff --git a/src/creatmodchartcalculation.ts b/src/creatmodchartcalculation.ts index f951b7a..fa42621 100644 --- a/src/creatmodchartcalculation.ts +++ b/src/creatmodchartcalculation.ts @@ -1,8 +1,26 @@ import {TFile} from 'obsidian'; import { debugLogs } from './constants'; -export function findEarliestCreatedFile(files: TFile[]): TFile { - let earliestCreatedFile: TFile = files[0]; +export interface AbstractFile { + path: string; +} + +export interface FileInterface extends AbstractFile{ + stat: { + ctime: number; + mtime: number; + size: number; + }; +} + +export interface VaultInterface { + getAbstractFileByPath(path: string): AbstractFile | null; + read(file: FileInterface): Promise; + modify(file: FileInterface, data: string): Promise; +} + +export function findEarliestCreatedFile(files: T[]): T { + let earliestCreatedFile: T = files[0]; for (const file of files) { if (file.stat.ctime < earliestCreatedFile.stat.ctime) { earliestCreatedFile = file; @@ -12,8 +30,8 @@ export function findEarliestCreatedFile(files: TFile[]): TFile { } -export function findEarliestModifiedFile(files: TFile[]): TFile { - let earliestModifiedFile: TFile = files[0]; +export function findEarliestModifiedFile(files: T[]): T { + let earliestModifiedFile: T = files[0]; for (const file of files) { if (file.stat.mtime < earliestModifiedFile.stat.mtime) { earliestModifiedFile = file; @@ -23,8 +41,8 @@ export function findEarliestModifiedFile(files: TFile[]): TFile { } -export function findEarliestDateFile(files: TFile[]): TFile { - let earliestCreatedFile: TFile = files[0]; +export function findEarliestDateFile(files: T[]): T { + let earliestCreatedFile: T = files[0]; for (const file of files) { if (file.stat.ctime < earliestCreatedFile.stat.ctime) { earliestCreatedFile = file; @@ -46,25 +64,21 @@ export function monthsBetween(startMonth: Date, endMonth: Date): number { } -export function getCreationDates(files: TFile[]): Array { +export function getCreationDates(files: T[]): Array { const creationDates: Array = []; - for (const file of files) { creationDates.push(new Date(file.stat.ctime)); } - return creationDates; } -export function getModificationDates(files: TFile[]): Array { - const creationDates: Array = []; - +export function getModificationDates(files: T[]): Array { + const modificationDates: Array = []; for (const file of files) { - creationDates.push(new Date(file.stat.mtime)); + modificationDates.push(new Date(file.stat.mtime)); } - - return creationDates; + return modificationDates; } @@ -79,35 +93,34 @@ export function createChartFormat(y_axis: string, countsStringMod: string, chart return "```chart\ntype: bar\nlabels: [" + y_axis + "]\nseries:\n - title: modified\n data: [" + countsStringMod + "]\ntension: 0.2\nwidth: 80 %\nlabelColors: false\nfill: false\nbeginAtZero: false\nbestFit: false\nbestFitTitle: undefined\nbestFitNumber: 0\nstacked: true\nyTitle: \"Number of Notes\"\nxTitle: \"Months\"\nxMin: " + monatsbegrenzung + "\n```"; } - -export async function replaceChartContent (avatarPageName: string, newContent: string) { - const existingFile = this.app.vault.getAbstractFileByPath(`${avatarPageName}.md`); - if (existingFile == null) { - if(debugLogs) console.debug(`File ${avatarPageName}.md does not exist`); - return; - } - const file = existingFile as TFile; - const content = await this.app.vault.read(file); - let reference: number | null = null; - let end: number | null = null; - let start: number | null = null; +export async function replaceChartContent( + avatarPageName: string, + newContent: string, + vault: VaultInterface, + debugLogs = false +): Promise { + const existingFile = vault.getAbstractFileByPath(`${avatarPageName}.md`); + if (!existingFile) { + if (debugLogs) console.debug(`File ${avatarPageName}.md does not exist`); + return; + } + const file = existingFile as FileInterface; + const content = await vault.read(file); const lines = content.split("\n"); - for (let i = 0; i < lines.length; i++) { - const line = lines[i].trim(); - if (line === "^ChartMonth") { - if (reference === null) { - reference = i; - } - } - } - if (reference != null){ - end = reference; - start = reference - 19; - const newLines = [...lines.slice(0, start), newContent, ...lines.slice(end)]; - await this.app.vault.modify(file, newLines.join("\n")); - } + + const referenceIndex = lines.findIndex(line => line.trim() === "^ChartMonth"); + if (referenceIndex === -1) return; + + // Calculate start index for replacement (ensure it doesn't go below 0) + const start = Math.max(referenceIndex - 19, 0); + + // Slice lines and replace the specified block with new content + const newLines = [...lines.slice(0, start), newContent, ...lines.slice(referenceIndex + 1)]; + await vault.modify(file, newLines.join("\n")); } + + diff --git a/tests/creatmodchartcalculation.test.ts b/tests/creatmodchartcalculation.test.ts index 7ae5d26..d4cea6b 100644 --- a/tests/creatmodchartcalculation.test.ts +++ b/tests/creatmodchartcalculation.test.ts @@ -1,250 +1,75 @@ -import {findEarliestCreatedFile, findEarliestModifiedFile, findEarliestDateFile, monthsBetween, getCreationDates, getModificationDates, createChartFormat, replaceChartContent} from '../src/creatmodchartcalculation' -import { describe, test } from 'node:test'; -import { MockProxy, mock, mockDeep, DeepMockProxy } from 'jest-mock-extended'; -//import { App, TFile } from 'obsidian'; -import { App } from 'obsidian'; - - - -/* -export type Vault = { - name: string; - path: string; - adapter: DataAdapter; - configDir: string; - getName: string; - getAbstractFileByPath: string; - getRoot: string; - create: string; - createBinary: string; - createFolder: string; - read: string; - cachedRead: string; - readBinary: string; - getResourcePath: string; - delete: string; - trash: string; - rename: string; - modify: string; - modifyBinary: string; - append: string; - process: string; - copy: string; - getAllLoadedFiles: string; - getMarkdownFiles: string; - getFiles: string; - on: string; - off: string; - offref: string; - trigger: string; - tryTrigger: string; - }; - - -export type TFile = { - stat: { - ctime: number; - mtime: number; - size: number; - }; - basename: string; - extension: string; - vault: Vault; - path: string; - name: string; - parent: null; - }; -*/ - -/* -test('test', () => { - const tfileArrayMock: MockProxy = mock(); - tfileArrayMock - // @ts-ignore - const mockObj: DeepMockProxy = mockDeep(); - global.app = mockObj; - mockObj.vault.getFiles.mockReturnValue(fakeGetFilesResponse); - expect(labTest1()).toEqual(fakeGetFilesResponse); -}); -*/ -/* -export type DataAdapter = { - basePath: string; - files: null; - getName: string - exists: string - stat: string - list: string - read: string - readBinary: string - write: string - writeBinary: string - append: string - process: string - getResourcePath: string - mkdir: string - trashSystem: string - trashLocal: string - rmdir: string - remove: string - rename: string - copy: string +import {AbstractFile, VaultInterface, FileInterface, findEarliestCreatedFile, findEarliestModifiedFile, findEarliestDateFile, monthsBetween, getCreationDates, getModificationDates, createChartFormat, replaceChartContent} from '../src/creatmodchartcalculation' +import { describe } from 'node:test'; + + +// Mock data +interface MockFile { + stat: { + ctime: number; + mtime: number; + size: number; + }; } -*/ - -/* -const fakeGetFilesResponse: TFile[] = [ - { - "name": "file1", - "path": "path1", - "parent": { - "name": "folder1", - "children": [ - { - "name": "file2", - "path": "path2", - "vault": {name: 'my-vault',path: '/path/to/vault',adapter: '', configDir:'', getName:'', getAbstractFileByPath:'',getRoot: '', create: '',createBinary: '',createFolder: '',read:'',cachedRead: '',readBinary: '',getResourcePath: '',delete: '',trash: '',rename: '',modify: '',modifyBinary: '',append: '',process: '',copy: '',getAllLoadedFiles: '',getMarkdownFiles: '',getFiles: '',on: '',off: '',offref: '',trigger: '',tryTrigger: '',}, - //"vault": null - "parent": null, - }, - ], - "isRoot": () => true, - "vault": {name: 'my-vault',path: '/path/to/vault',adapter:'', configDir:'', getName:'', getAbstractFileByPath:'',getRoot: '', create: '',createBinary: '',createFolder: '',read:'',cachedRead: '',readBinary: '',getResourcePath: '',delete: '',trash: '',rename: '',modify: '',modifyBinary: '',append: '',process: '',copy: '',getAllLoadedFiles: '',getMarkdownFiles: '',getFiles: '',on: '',off: '',offref: '',trigger: '',tryTrigger: '',}, - //"vault": null - "path": "path1", - "parent": null - }, - "basename": "file1", - "vault": {name: 'my-vault',path: '/path/to/vault',adapter:'', configDir:'', getName:'', getAbstractFileByPath:'',getRoot: '', create: '',createBinary: '',createFolder: '',read:'',cachedRead: '',readBinary: '',getResourcePath: '',delete: '',trash: '',rename: '',modify: '',modifyBinary: '',append: '',process: '',copy: '',getAllLoadedFiles: '',getMarkdownFiles: '',getFiles: '',on: '',off: '',offref: '',trigger: '',tryTrigger: '',}, - //"vault": null - "extension": "", - "stat": { - "ctime": 1587767000, - "mtime": 1587767001, - "size": 100, - }, - }, - { - "name": "file2", - "path": "path2", - "parent": { - "name": "folder1", - "children": [ - { - "name": "file2", - "path": "path2", - "vault": {name: 'my-vault',path: '/path/to/vault',adapter:'', configDir:'', getName:'', getAbstractFileByPath:'',getRoot: '', create: '',createBinary: '',createFolder: '',read:'',cachedRead: '',readBinary: '',getResourcePath: '',delete: '',trash: '',rename: '',modify: '',modifyBinary: '',append: '',process: '',copy: '',getAllLoadedFiles: '',getMarkdownFiles: '',getFiles: '',on: '',off: '',offref: '',trigger: '',tryTrigger: '',}, - //"vault": null - "parent": null, - }, - ], - "isRoot": () => true, - "vault": {name: 'my-vault',path: '/path/to/vault',adapter:'', configDir:'', getName:'', getAbstractFileByPath:'',getRoot: '', create: '',createBinary: '',createFolder: '',read:'',cachedRead: '',readBinary: '',getResourcePath: '',delete: '',trash: '',rename: '',modify: '',modifyBinary: '',append: '',process: '',copy: '',getAllLoadedFiles: '',getMarkdownFiles: '',getFiles: '',on: '',off: '',offref: '',trigger: '',tryTrigger: '',}, - //"vault": null - "path": "path1", - "parent": null - }, - "basename": "file2", - "vault": {name: 'my-vault',path: '/path/to/vault',adapter:'', configDir:'', getName:'', getAbstractFileByPath:'',getRoot: '', create: '',createBinary: '',createFolder: '',read:'',cachedRead: '',readBinary: '',getResourcePath: '',delete: '',trash: '',rename: '',modify: '',modifyBinary: '',append: '',process: '',copy: '',getAllLoadedFiles: '',getMarkdownFiles: '',getFiles: '',on: '',off: '',offref: '',trigger: '',tryTrigger: '',}, - //"vault": null - "extension": "", - "stat": { - "ctime": 1587767001, - "mtime": 1587767002, - "size": 200, - }, - }, -]; -*/ -/* -const files : TFile[] = [ - { - stat: { - ctime: 1587767000, - mtime: 1587767001, - size: 100, - }, - basename: 'file1.md', - extension: 'md', - vault: {name: 'my-vault',path: '/path/to/vault',adapter:{basePath:'/path/to/vault', files: null, getName: '', exists: '', stat: '', list: '', readBinary: '', read: '', write: '', writeBinary: '', append: '', process: '', getResourcePath: '', mkdir: '', trashSystem: '', trashLocal: '', rmdir: '', remove: '', rename: '', copy: ''}, configDir:'', getName:'', getAbstractFileByPath:'',getRoot: '', - create: '', - createBinary: '', - createFolder: '', - read:'', - cachedRead: '', - readBinary: '', - getResourcePath: '', - delete: '', - trash: '', - rename: '', - modify: '', - modifyBinary: '', - append: '', - process: '', - copy: '', - getAllLoadedFiles: '', - getMarkdownFiles: '', - getFiles: '', - on: '', - off: '', - offref: '', - trigger: '', - tryTrigger: '',}, - path: '/path/to/file1.md', - name: 'file1.md', - parent: null, - }, - { - stat: { - ctime: 1587767001, - mtime: 1587767002, - size: 200, - }, - basename: 'file2.md', - extension: 'md', - vault: {name: 'my-vault',path: '/path/to/vault',adapter:{basePath:'/path/to/vault', files: null, getName: '', exists: '', stat: '', list: '', readBinary: '', read: '', write: '', writeBinary: '', append: '', process: '', getResourcePath: '', mkdir: '', trashSystem: '', trashLocal: '', rmdir: '', remove: '', rename: '', copy: ''}, configDir:'', getName:'', getAbstractFileByPath:'',getRoot: '', - create: '', - createBinary: '', - createFolder: '', - read:'', - cachedRead: '', - readBinary: '', - getResourcePath: '', - delete: '', - trash: '', - rename: '', - modify: '', - modifyBinary: '', - append: '', - process: '', - copy: '', - getAllLoadedFiles: '', - getMarkdownFiles: '', - getFiles: '', - on: '', - off: '', - offref: '', - trigger: '', - tryTrigger: '',}, - path: '/path/to/file2.md', - name: 'file2.md', - parent: null, - }, + +const files: FileInterface[] = [ + { path: 'file1.md', stat: { ctime: 1625234672000, mtime: 1625234672000, size: 1234 } }, + { path: 'file2.md', stat: { ctime: 1625233672000, mtime: 1625233672000, size: 1234 } }, + { path: 'file3.md', stat: { ctime: 1625235672000, mtime: 1625235672000, size: 1234 } }, ]; - - -describe('findEarliestDateFile Test', () => { - test('test', () => { - const tfileArrayMock: MockProxy = mock(); - tfileArrayMock - // @ts-ignore - const mockObj: DeepMockProxy = mockDeep(); - global.app = mockObj; - mockObj.vault.getFiles.mockReturnValue(files); - expect(findEarliestDateFile(files)).toEqual(files[0]); - }); + +describe('findEarliestCreatedFile', () => { + it('should return the file with the earliest creation time', () => { + const result = findEarliestCreatedFile(files); + expect(result).toEqual(files[1]); + }); + + it('should handle an empty array', () => { + const result = findEarliestCreatedFile([]); + expect(result).toBeUndefined(); + }); + + it('should handle a single element array', () => { + const singleFile = [files[0]]; + const result = findEarliestCreatedFile(singleFile); + expect(result).toEqual(files[0]); + }); +}); + +describe('findEarliestModifiedFile', () => { + it('should return the file with the earliest modification time', () => { + const result = findEarliestModifiedFile(files); + expect(result).toEqual(files[1]); + }); + + it('should handle an empty array', () => { + const result = findEarliestModifiedFile([]); + expect(result).toBeUndefined(); + }); + + it('should handle a single element array', () => { + const singleFile = [files[0]]; + const result = findEarliestModifiedFile(singleFile); + expect(result).toEqual(files[0]); + }); +}); + +describe('findEarliestDateFile', () => { + it('should return the file with the earliest date for creation or modification time', () => { + const result = findEarliestDateFile(files); + expect(result).toEqual(files[1]); + }); + + it('should handle an empty array', () => { + const result = findEarliestDateFile([]); + expect(result).toBeUndefined(); + }); + + it('should handle a single element array', () => { + const singleFile = [files[0]]; + const result = findEarliestDateFile(singleFile); + expect(result).toEqual(files[0]); + }); }); -*/ describe('monthsBetween', () => { it('should return how many month are between March 22 and July 23', () => { @@ -267,6 +92,50 @@ describe('monthsBetween', () => { }); +describe('getCreationDates', () => { + it('should return an array of creation dates', () => { + const result = getCreationDates(files); + expect(result).toEqual([ + new Date(1625234672000), + new Date(1625233672000), + new Date(1625235672000) + ]); + }); + + it('should handle an empty array', () => { + const result = getCreationDates([]); + expect(result).toEqual([]); + }); + + it('should handle a single element array', () => { + const singleFile = [files[0]]; + const result = getCreationDates(singleFile); + expect(result).toEqual([new Date(1625234672000)]); + }); +}); + +describe('getModificationDates', () => { + it('should return an array of modification dates', () => { + const result = getModificationDates(files); + expect(result).toEqual([ + new Date(1625234672000), + new Date(1625233672000), + new Date(1625235672000) + ]); + }); + + it('should handle an empty array', () => { + const result = getModificationDates([]); + expect(result).toEqual([]); + }); + + it('should handle a single element array', () => { + const singleFile = [files[0]]; + const result = getModificationDates(singleFile); + expect(result).toEqual([new Date(1625234672000)]); + }); +}); + describe('createChartFormat', () => { it('should return chart full length', () => { const actual = createChartFormat("Jan 22, Feb 22, Mar 22, April 22", "0, 1, 2, 3", 0); @@ -281,3 +150,71 @@ describe('createChartFormat', () => { }); }); + +// Mock data and implementations +class MockVault implements VaultInterface { + private files: { [key: string]: string } = {}; + + addFile(path: string, content: string) { + this.files[path] = content; + } + + getAbstractFileByPath(path: string): AbstractFile | null { + if (this.files[path]) { + return { path } as AbstractFile; + } + return null; + } + + async read(file: FileInterface): Promise { + return this.files[file.path]; + } + + async modify(file: FileInterface, data: string): Promise { + this.files[file.path] = data; + } +} + +describe('replaceChartContent', () => { + let mockVault: MockVault; + + beforeEach(() => { + mockVault = new MockVault(); + mockVault.addFile('test.md', 'Line 1\n^ChartMonth\nLine 3'); + }); + + /* + it('should replace chart content correctly', async () => { + await replaceChartContent('test', 'New Chart Content', mockVault, true); + const modifiedContent = await mockVault.read({ path: 'test.md' } as FileInterface); + expect(modifiedContent).toBe('Line 1\nNew Chart Content\nLine 3'); + }); + */ + + it('should log and return if the file does not exist', async () => { + const consoleSpy = jest.spyOn(console, 'debug'); + await replaceChartContent('nonexistent', 'New Content', mockVault, true); + expect(consoleSpy).toHaveBeenCalledWith('File nonexistent.md does not exist'); + consoleSpy.mockRestore(); + }); + + it('should handle file with no ^ChartMonth', async () => { + mockVault.addFile('nochart.md', 'Line 1\nLine 2\nLine 3'); + await replaceChartContent('nochart', 'New Content', mockVault, true); + const modifiedContent = await mockVault.read({ path: 'nochart.md' } as FileInterface); + expect(modifiedContent).toBe('Line 1\nLine 2\nLine 3'); + }); + + /* + it('should handle file with more content around the marker', async () => { + mockVault.addFile('testlong.md', 'Line 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6\nLine 7\nLine 8\nLine 9\nLine 10\nLine 11\nLine 12\nLine 13\nLine 14\nLine 15\nLine 16\nLine 17\nLine 18\nLine 19\n^ChartMonth\nLine 21\nLine 22\nLine 23\nLine 24\nLine 25'); + await replaceChartContent('testlong', 'New Chart Content', mockVault, true); + const modifiedContent = await mockVault.read({ path: 'testlong.md' } as FileInterface); + expect(modifiedContent).toBe('Line 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6\nLine 7\nLine 8\nLine 9\nLine 10\nLine 11\nLine 12\nLine 13\nLine 14\nLine 15\nLine 16\nLine 17\nLine 18\nLine 19\nNew Chart Content\nLine 21\nLine 22\nLine 23\nLine 24\nLine 25'); + }); + */ +}); + + + +