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 664eaa1..c7cfef2 100644 Binary files a/test/mocks/testDatabase.sqlite and b/test/mocks/testDatabase.sqlite differ diff --git a/test/pluginDocs.spec.ts b/test/pluginDocs.spec.ts new file mode 100644 index 0000000..293845f --- /dev/null +++ b/test/pluginDocs.spec.ts @@ -0,0 +1,30 @@ +import fs from 'fs/promises'; +import path from 'path'; +import { describe, expect, test } from 'vitest'; + +describe('Plugin documentation', () => { + 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); + }); +})