From eda8f3bcb4092b093207bad0083ad558126c6a59 Mon Sep 17 00:00:00 2001 From: Antonio Date: Sun, 5 May 2024 13:13:24 +0300 Subject: [PATCH] test: add tests for basic functionality --- test/db.spec.ts | 16 ++-- test/mocks/rawTemplates.ts | 2 +- test/mocks/testDatabase.sqlite | Bin 737280 -> 737280 bytes test/pluginDocs.spec.ts | 30 +++++++ test/pluginInfo.spec.ts | 26 +++--- test/renderHighlightsTemplate.spec.ts | 6 +- test/saveHighlightsToVault.spec.ts | 112 ++++++++++++++++++++++++++ test/settings.spec.ts | 22 +++++ 8 files changed, 187 insertions(+), 27 deletions(-) create mode 100644 test/pluginDocs.spec.ts create mode 100644 test/saveHighlightsToVault.spec.ts create mode 100644 test/settings.spec.ts diff --git a/test/db.spec.ts b/test/db.spec.ts index 49932ce..289117b 100644 --- a/test/db.spec.ts +++ b/test/db.spec.ts @@ -10,7 +10,7 @@ import { seedDatabase } from '../src/db/seed'; import { defaultBooks, defaultAnnotations } from '../src/db/seedData'; afterEach(async () => { - let dbPath = path.join(process.cwd(), TEST_DATABASE_PATH); + const dbPath = path.join(process.cwd(), TEST_DATABASE_PATH); const sqlite = new Database(dbPath); const db = drizzle(sqlite); @@ -22,7 +22,7 @@ afterEach(async () => { describe('Empty database', () => { test('Should throw an error when books library is empty', async () => { try { - let dbPath = path.join(process.cwd(), TEST_DATABASE_PATH); + const dbPath = path.join(process.cwd(), TEST_DATABASE_PATH); await dbRequest(dbPath, `SELECT * FROM ${BOOKS_LIBRARY_NAME}`); } catch (error) { @@ -34,7 +34,7 @@ describe('Empty database', () => { try { await seedDatabase(bookLibrary, defaultBooks); - let dbPath = path.join(process.cwd(), TEST_DATABASE_PATH); + const dbPath = path.join(process.cwd(), TEST_DATABASE_PATH); await annotationsRequest(dbPath, `SELECT * FROM ${HIGHLIGHTS_LIBRARY_NAME}`); } catch (error) { @@ -47,7 +47,7 @@ describe('Database operations', () => { test('Should return a list of books when books library is not empty', async () => { await seedDatabase(bookLibrary, defaultBooks); - let dbPath = path.join(process.cwd(), TEST_DATABASE_PATH); + const dbPath = path.join(process.cwd(), TEST_DATABASE_PATH); const books = await dbRequest(dbPath, `SELECT * FROM ${BOOKS_LIBRARY_NAME}`); expect(books).toEqual(defaultBooks); @@ -56,7 +56,7 @@ describe('Database operations', () => { test('Should return a list of highlights when highlights library is not empty', async () => { await seedDatabase(annotations, defaultAnnotations); - let dbPath = path.join(process.cwd(), TEST_DATABASE_PATH); + const dbPath = path.join(process.cwd(), TEST_DATABASE_PATH); const highlights = await annotationsRequest(dbPath, `SELECT * FROM ${HIGHLIGHTS_LIBRARY_NAME} WHERE ZANNOTATIONDELETED = 0`); expect(highlights.length).toEqual(4); @@ -68,8 +68,8 @@ describe('Database operations', () => { describe('Database load testing', () => { test('Should return 1000 books and in less than 500ms', async () => { - let oneThousandBooks = []; - let threeThousandsAnnotations = []; + const oneThousandBooks = []; + const threeThousandsAnnotations = []; // create 1000 books and 3 annotations for each book for (let i = 0; i < 1000; i++) { @@ -98,7 +98,7 @@ describe('Database load testing', () => { } } - let dbPath = path.join(process.cwd(), TEST_DATABASE_PATH); + const dbPath = path.join(process.cwd(), TEST_DATABASE_PATH); await seedDatabase(bookLibrary, oneThousandBooks); await seedDatabase(annotations, threeThousandsAnnotations); diff --git a/test/mocks/rawTemplates.ts b/test/mocks/rawTemplates.ts index 341b3b3..4884507 100644 --- a/test/mocks/rawTemplates.ts +++ b/test/mocks/rawTemplates.ts @@ -1,4 +1,4 @@ -export const rawCustomTemplate = `Title:: 📕 {{{bookTitle}}} +export const rawCustomTemplateMock = `Title:: 📕 {{{bookTitle}}} Author:: {{{bookAuthor}}} Genre:: {{#if bookGenre}}{{{bookGenre}}}{{else}}N/A{{/if}} Language:: {{#if bookLanguage}}{{bookLanguage}}{{else}}N/A{{/if}} diff --git a/test/mocks/testDatabase.sqlite b/test/mocks/testDatabase.sqlite index 664eaa18a382e9b80d244a49592d91f1f0919df3..c7cfef2e597e34d6535290201d71e0bad9569474 100644 GIT binary patch delta 2078 zcmW;L=XVlW7zS{LkO`0&uD!2VVvl0)276cRT``FoJ4TJ&3_a8gHM$j7vut&7F(+U7 z6ZpKI1AN?%`~iE`eV*Z*`Eh67=e#p_=3Yfjn%Oj8r_+7HAt&Z-lySDKFY>WF zzg%i6w%Cre^cRQLqWIU)T=;QFz(V#V{4;wKe#oANe`I&U-?J;=Z`rTlui0k!OXdmu zIdc`}GsoahnGQIdSqi6Qro#6b8UC1l0N z!mm^3;hWT6_&T))4yES6Y{~*N$rmu4ya7|mQ!ts_0TW3ZRFgB|VA2fZiN`RO=!Vh6 zQ5Z>Vh2g{!7)ta{!QVil5eC&>7*H=mzj_e*)D6(9@)dg2@zAXToica__79$euLk$P zmxHU}i^19O`Cu!27Jm+(#(UtC_(}LUz8&_(m%~T#8Sr7;)Q`Uhu|C)v>w@=V4(N() zf%jsI;oaC|cqe9nx1;yrt>`6qGkO5th^~h{(KdKJIu2fou;>Z_Xls`CF=GC&){Tla2MPY zTnTpvXTe>;7PvF;6z&LIgWChg;kJMs+5_#dBQOna4K%?m{)ce0{|emXKMXheH^B}5 zg>b!pB3$R!!?nJ9aE|gEMhrd-m?t+y*?t&FQ0c~FHg5}QZ4-hfTpAxZ1jhyV zhR6E3;)I_&(Bk8_X!UVzj`8v-TfF>g&7M2Z?BRS&Zhqf)Zm#!FoVd&x@b>}7r&Noh zLoyZ*I%c${E^jv%d+#PCqrSCAXIb6alWK8xG;)&fiw@@!y*8X&R674@F7{k{EoEJj zT$Ek^Sw;<_SuaN9X0OPH>SjJMQVl?D1Keyx!b_@+J>2EPwv6<)N zf-s8_gSl7a`|2h!+E+8Fmb_dxiM(9Gxm++^6#1?k&bw5ccPXYXmVBoR!%nA3!Uc!v zt!2m{OrJ-qc2gT3(q{TB@>MGmRe|tu1*1ycDB)tw2+E{H3PxmdMl`Ib7*-T5`^d)98rnFkV^|W8FFYL zJ4-fVCBh&^s#>PrfK07{jmiz6L<+cA%{A~wD(i|?cd_Io4q|7>riH8w3515Ltkm_O zM#_3*N_sXbU|7lNc_Wo2g}8{5A%_;SGi1|3R+fYwEm^Kh>`}wgtCS`7C}~qCIjtos zRYhvU(zZ5>yum36EKlh(o|R=gu@NhS0^Noyk|$~}1%l{2}TxtLS delta 2077 zcmW;L*LM5;b;gv74cH7;4mQ?8YoxU0n3!l|O;! z^&H@FANd3J?C$ps=ge0y#VKC z_rZDDwQzoRF6_wK;DXFcxG-}AF3OySi!(dnl8gf`&CG<$GG^GBegc=L`{0W7G3ZEd zgDcZZ;i~lD6r5J4b#RUR0Irq0;X3&cTrY2g8{`FWqdXpNl7VV->JHqJIuEy|_QGwc zHLxo+2Rc*jaC`Cv+>z{uJCmp2uH+85JGlbxNzQ>AYOom4iT6i>mAByqI@L2pHJRaWwPsBUm$@n;UD#kHS$8N(jv2*ZjY!5saTMf_0 z?C?Tt47?b9u0r=x^g3=XM^D1;=yvFeE{9j5(_v542zw)sVPB*NUX2`u*CJcs^~hq_ zADIMiMB3oZup8bAUxK&82jHFXdU!WHAKnX(h3?R6ct3PYh4~+Z&f?}_Xg7QmS_L16 zX2XGy6+Q_*gHMCk;IrTf_&n%@FM^%$WpEmN6*R!tfk$vKa0N<%BhVAr484Ix&=;5p z{Q(UO`0v4>{~{a=`S;@#_H!3R{M-dmzW`%C?t-}QCQSIwz@%>%O!>GAWFL1y+Q(gx z@p2brz1#&kFLy!S+XV|=?t&pNSJ)dbU*B5~pUpcDC;rOArTz5=4;Rii9y|Qj!+-o8 zM=SP$@3|Cy;A5V`zhIoem!{&6Q>jKOR_9yW^&geTRtlN$)lb30@aG_(@Gk+bxL*TP z;ctGvi{JfRn;(4qm4Ep7*2>;H@S~Ui=T8scZ^g3_e&QcDba66Gd%w!IroBIF6}!~$ zD~H4x?Unw!Z`Ayq{CvRiDQk6g>9|OyR7BTOjgHIpeMRc|+mh|xVXi#5oKX*J-NL9= zuzV3dH!T0zO7)gm7_B$WYN6DqnL(A5NfAjyHnSwL9Mj~-CcbGeDqgt2xFBAmx z0^5+bYWgzLVBRu;CaICC3H7px!xeE^mra~gV&o<+=Ax22nLCu+&PXr`BMn}(jOa9t z9HWNIdez7=%0^HmGLcwvD$FX=yo7QwwUOszqGZ za-t$G>#~wdjNHUUM3$UN$-$CcDG6HqPR6Y^&}>7e*2aOVxU84kI8l+2OeErF$)%J; zmYhn-!N^X8HoPJwZfOuVH5{X+0aa2aMI;Sb!-*u8+)ByCZ$(Xqt)zA0QtQxk3!}Yu z)_Meu@VSSr-WF-9k<`?~wvxVzdwm)A`l8w`lm=v64oK=G)-?9ZXv1#dfNr$3B5@+u xI@AH76tUwzBH-;u)WUG2g` { + test('Check that README.md exists', () => { + expect(path.join(process.cwd(), 'README.md')).toBeDefined(); + }); + + test('Check that Template variables contains complete list of variables', async () => { + const readme = await fs.readFile(path.join(process.cwd(), 'README.md'), 'utf-8'); + + expect(readme).toContain('{{{bookTitle}}}'); + expect(readme).toContain('{{bookId}}'); + expect(readme).toContain('{{{bookAuthor}}}'); + expect(readme).toContain('{{{bookGenre}}}'); + expect(readme).toContain('{{bookLanguage}}'); + expect(readme).toContain('{{bookLastOpenedDate}}'); + expect(readme).toContain('{{bookCoverUrl}}'); + expect(readme).toContain('{{annotations}}'); + expect(readme).toContain('{{annotations.length}}'); + expect(readme).toContain('{{{chapter}}}'); + expect(readme).toContain('{{{contextualText}}}'); + expect(readme).toContain('{{{highlight}}}'); + expect(readme).toContain('{{{note}}}'); + expect(readme).toContain('{{highlightStyle}}'); + expect(readme).toContain('{{highlightCreationDate}}'); + expect(readme).toContain('{{highlightModificationDate}}'); + }); +}); diff --git a/test/pluginInfo.spec.ts b/test/pluginInfo.spec.ts index 31d2f95..f2c8ae4 100644 --- a/test/pluginInfo.spec.ts +++ b/test/pluginInfo.spec.ts @@ -1,7 +1,6 @@ import os from 'os'; import path from 'path'; -import { beforeEach } from 'vitest'; -import { describe, expect, test } from 'vitest'; +import { beforeEach, describe, expect, test } from 'vitest'; declare module 'vitest' { export interface TestContext { @@ -25,47 +24,44 @@ import { BOOKS_LIBRARY_COLUMNS, HIGHLIGHTS_LIBRARY_COLUMNS } from '../src/db/constants'; +import * as packageJson from '../package.json'; describe('Plugin information', () => { beforeEach(async (context) => { context.manifest = require('../manifest.json'); }); - - test(`Check that versions in package.json and manifest.json match`, ({ manifest }) => { - const packageJson = require('../package.json'); + test(`Check that versions in package.json and manifest.json match`, ({ manifest }) => { expect(packageJson.version).toEqual(manifest.version); }); - - test(`check minimum Obsidian version`, ({ manifest }) => { + + test(`check minimum Obsidian version`, ({ manifest }) => { expect(manifest.minAppVersion).toEqual('0.15.0'); }); - + test(`Check plugin id, name and description`, ({ manifest }) => { expect(manifest.id).toEqual('apple-books-import-highlights'); expect(manifest.name).toEqual('Apple Books - Import Highlights'); expect(manifest.description).toEqual('Import your Apple Books highlights and notes to Obsidian.'); }); - + test(`Check author information`, ({ manifest }) => { - const packageJson = require('../package.json'); - expect(packageJson.author).toEqual(manifest.author); expect(manifest.authorUrl).toEqual('https://github.com/bandantonio'); }); }); describe('Plugin constants', () => { - test('Check database paths', () => { + test('Check database paths', () => { expect(BOOKS_DB_PATH).toEqual(path.join(os.homedir(), 'Library/Containers/com.apple.iBooksX/Data/Documents/BKLibrary/BKLibrary-1-091020131601.sqlite')); expect(HIGHLIGHTS_DB_PATH).toEqual(path.join(os.homedir(), 'Library/Containers/com.apple.iBooksX/Data/Documents/AEAnnotation/AEAnnotation_v10312011_1727_local.sqlite')); }); - + test('Check database table names', () => { expect(BOOKS_LIBRARY_NAME).toEqual('ZBKLIBRARYASSET'); expect(HIGHLIGHTS_LIBRARY_NAME).toEqual('ZAEANNOTATION'); }); - + test('Check database columns', () => { expect(BOOKS_LIBRARY_COLUMNS).toEqual([ 'ZASSETID', @@ -76,7 +72,7 @@ describe('Plugin constants', () => { 'ZLASTOPENDATE', 'ZCOVERURL' ]); - + expect(HIGHLIGHTS_LIBRARY_COLUMNS).toEqual([ 'ZANNOTATIONASSETID', 'ZFUTUREPROOFING5', diff --git a/test/renderHighlightsTemplate.spec.ts b/test/renderHighlightsTemplate.spec.ts index 6d26244..f885555 100644 --- a/test/renderHighlightsTemplate.spec.ts +++ b/test/renderHighlightsTemplate.spec.ts @@ -2,7 +2,7 @@ import Handlebars from 'handlebars'; import { describe, expect, test } from 'vitest'; import { renderHighlightsTemplate } from '../src/methods/renderHighlightsTemplate'; import { aggregatedHighlights } from './mocks/aggregatedDetailsData'; -import { rawCustomTemplate } from './mocks/rawTemplates'; +import { rawCustomTemplateMock } from './mocks/rawTemplates'; import { defaultTemplateMock, renderedCustomTemplateMock } from './mocks/renderedTemplate'; import defaultTemplate from '../src/template'; @@ -14,14 +14,14 @@ describe('renderHighlightsTemplate', () => { }); test('Should render a custom template with the provided data', async () => { - const renderedTemplate = await renderHighlightsTemplate(aggregatedHighlights[0], rawCustomTemplate); + const renderedTemplate = await renderHighlightsTemplate(aggregatedHighlights[0], rawCustomTemplateMock); expect(renderedTemplate).toEqual(renderedCustomTemplateMock); }); }); describe('Custom Handlebars helpers', () => { - let helpers = Handlebars.helpers; + const helpers = Handlebars.helpers; describe('eq', () => { test('Should properly compare two values', async () => { diff --git a/test/saveHighlightsToVault.spec.ts b/test/saveHighlightsToVault.spec.ts new file mode 100644 index 0000000..2ad95c2 --- /dev/null +++ b/test/saveHighlightsToVault.spec.ts @@ -0,0 +1,112 @@ +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; +import SaveHighlights from '../src/methods/saveHighlightsToVault'; +import { AppleBooksHighlightsImportPluginSettings } from '../src/settings'; +import { aggregatedHighlights } from './mocks/aggregatedDetailsData'; +import { defaultTemplateMock } from './mocks/renderedTemplate'; + +const mockVault = { + getAbstractFileByPath: vi.fn(), + // eslint-disable-next-line + createFolder: vi.fn().mockImplementation(async (folderName: string) => { + return; + }), + // eslint-disable-next-line + create: vi.fn().mockImplementation(async (filePath: string, data: string) => { + return; + }), + // eslint-disable-next-line + delete: vi.fn().mockImplementation(async (folderPath: string, force: boolean) => { + return; + }), + adapter: { + list: vi.fn(), + // eslint-disable-next-line + copy: vi.fn().mockImplementation(async (source: string, destination: string) => { + return; + }), + } +}; + +beforeEach(() => { + Date.now = vi.fn().mockImplementation(() => 1704060001); +}); + +afterEach(() => { + vi.resetAllMocks(); +}); + +const settings = new AppleBooksHighlightsImportPluginSettings(); + +describe('Save highlights to vault', () => { + test('Should save highlights to vault', async () => { + // eslint-disable-next-line + const saveHighlights = new SaveHighlights({ vault: mockVault } as any, settings); + const spyGetAbstractFileByPath = vi.spyOn(mockVault, 'getAbstractFileByPath').mockReturnValue('ibooks-highlights'); + + await saveHighlights.saveHighlightsToVault(aggregatedHighlights); + + expect(spyGetAbstractFileByPath).toHaveBeenCalledTimes(1); + expect(spyGetAbstractFileByPath).toHaveBeenCalledWith('ibooks-highlights'); + + expect(mockVault.delete).toHaveBeenCalledTimes(1); + expect(mockVault.delete).toHaveBeenCalledWith('ibooks-highlights', true); + + expect(mockVault.createFolder).toHaveBeenCalledTimes(1); + expect(mockVault.createFolder).toHaveBeenCalledWith('ibooks-highlights'); + + expect(mockVault.create).toHaveBeenCalledTimes(1); + expect(mockVault.create).toHaveBeenCalledWith( + `ibooks-highlights/Apple iPhone - User Guide - Instructions - with - restricted - symbols - in - title.md`, + defaultTemplateMock + ); + }); + + test('Should skip saving highlights to vault highlights are not found', async () => { + // eslint-disable-next-line + const saveHighlights = new SaveHighlights({ vault: mockVault } as any, { ...settings, highlightsFolder: '' }); + const spyGetAbstractFileByPath = vi.spyOn(mockVault, 'getAbstractFileByPath').mockReturnValue(''); + + await saveHighlights.saveHighlightsToVault(aggregatedHighlights); + + expect(spyGetAbstractFileByPath).toHaveBeenCalledTimes(1); + expect(spyGetAbstractFileByPath).toHaveBeenCalledWith(''); + + expect(mockVault.delete).toHaveBeenCalledTimes(0); + + expect(mockVault.createFolder).toHaveBeenCalledTimes(1); + expect(mockVault.createFolder).toHaveBeenCalledWith(''); + }); + + test('Should backup highlights if backup option is enabled', async () => { + // eslint-disable-next-line + const saveHighlights = new SaveHighlights({ vault: mockVault } as any, { ...settings, backup: true }); + + vi.spyOn(mockVault, 'getAbstractFileByPath').mockReturnValue('ibooks-highlights'); + // eslint-disable-next-line + const spyList = vi.spyOn(mockVault.adapter, 'list').mockImplementation(async (folderPath: string) => { + return { + files: [ + 'ibooks-highlights/Hello-world.md', + 'ibooks-highlights/Goodbye-world.md', + ], + }; + }); + + await saveHighlights.saveHighlightsToVault(aggregatedHighlights); + + expect(spyList).toHaveBeenCalledTimes(1); + expect(spyList).toReturnWith({ + files: [ + 'ibooks-highlights/Hello-world.md', + 'ibooks-highlights/Goodbye-world.md', + ], + }); + + expect(mockVault.createFolder).toHaveBeenCalledTimes(2); + expect(mockVault.createFolder).toHaveBeenNthCalledWith(1, `ibooks-highlights-bk-1704060001`); + expect(mockVault.createFolder).toHaveBeenNthCalledWith(2, 'ibooks-highlights'); + + expect(mockVault.adapter.copy).toHaveBeenNthCalledWith(1, 'ibooks-highlights/Hello-world.md', 'ibooks-highlights-bk-1704060001/Hello-world.md'); + expect(mockVault.adapter.copy).toHaveBeenNthCalledWith(2, 'ibooks-highlights/Goodbye-world.md', 'ibooks-highlights-bk-1704060001/Goodbye-world.md'); + }); +}); diff --git a/test/settings.spec.ts b/test/settings.spec.ts new file mode 100644 index 0000000..d2a449e --- /dev/null +++ b/test/settings.spec.ts @@ -0,0 +1,22 @@ +import { describe, expect, test } from 'vitest'; +import { AppleBooksHighlightsImportPluginSettings } from '../src/settings'; +import defaultTemplate from '../src/template'; +const settings = new AppleBooksHighlightsImportPluginSettings(); + +describe('Plugin default settings', () => { + test("Highlights folder", () => { + expect(settings.highlightsFolder).toEqual('ibooks-highlights'); + }); + + test("Import highlights on start", () => { + expect(settings.importOnStart).toBeFalsy(); + }); + + test("Backup highlights", () => { + expect(settings.backup).toBeFalsy(); + }); + + test('Template', () => { + expect(settings.template).toEqual(defaultTemplate); + }); +})