diff --git a/src/GitHub/branch.test.ts b/src/GitHub/branch.test.ts new file mode 100644 index 00000000..5874f9ab --- /dev/null +++ b/src/GitHub/branch.test.ts @@ -0,0 +1,72 @@ +import { GithubBranch } from './branch'; +import { app } from '../__mocks__/obsidian'; +import { Octokit } from '@octokit/core'; + +jest.mock('obsidian'); + +describe('GithubBranch', () => { + let octokit: Octokit; + let githubBranch: GithubBranch; + + beforeEach(() => { + octokit = new Octokit(); + githubBranch = new GithubBranch(octokit, app); + }); + + describe('newBranch', () => { + it('should create a new branch for each repository', async () => { + const prop = [ + { owner: 'owner1', repo: 'repo1', branch: 'main' }, + { owner: 'owner2', repo: 'repo2', branch: 'main' }, + ]; + + const newBranchOnRepoSpy = jest.spyOn(githubBranch, 'newBranchOnRepo').mockResolvedValue(true); + + await githubBranch.newBranch(prop); + + expect(newBranchOnRepoSpy).toHaveBeenCalledTimes(2); + expect(newBranchOnRepoSpy).toHaveBeenCalledWith(prop[0]); + expect(newBranchOnRepoSpy).toHaveBeenCalledWith(prop[1]); + }); + }); + + describe('pullRequestOnRepo', () => { + it('should create a pull request for the repository', async () => { + const prop = { owner: 'owner', repo: 'repo', branch: 'main' }; + + const requestSpy = jest.spyOn(octokit, 'request').mockResolvedValue({ + data: { number: 1 }, + } as any); + + const prNumber = await githubBranch.pullRequestOnRepo(prop); + + expect(requestSpy).toHaveBeenCalledWith('POST /repos/{owner}/{repo}/pulls', { + owner: 'owner', + repo: 'repo', + title: 'PUBLISHER: Merge branch', + body: '', + head: 'branch', + base: 'main', + }); + expect(prNumber).toBe(1); + }); + }); + + describe('deleteBranchOnRepo', () => { + it('should delete the branch for the repository', async () => { + const prop = { owner: 'owner', repo: 'repo', branch: 'main' }; + + const requestSpy = jest.spyOn(octokit, 'request').mockResolvedValue({ + status: 200, + } as any); + + const result = await githubBranch.deleteBranchOnRepo(prop); + + expect(requestSpy).toHaveBeenCalledWith('DELETE /repos/{owner}/{repo}/git/refs/heads/branch', { + owner: 'owner', + repo: 'repo', + }); + expect(result).toBe(true); + }); + }); +}); diff --git a/src/GitHub/delete.test.ts b/src/GitHub/delete.test.ts new file mode 100644 index 00000000..89cc76b7 --- /dev/null +++ b/src/GitHub/delete.test.ts @@ -0,0 +1,100 @@ +import { deleteFromGithub, filterGithubFile } from './delete'; +import { app } from '../__mocks__/obsidian'; +import { Octokit } from '@octokit/core'; +import { FilesManagement } from './files'; + +jest.mock('obsidian'); + +describe('deleteFromGithub', () => { + let octokit: Octokit; + let filesManagement: FilesManagement; + + beforeEach(() => { + octokit = new Octokit(); + filesManagement = new FilesManagement(octokit, app); + }); + + describe('deleteFromGithub', () => { + it('should delete files from GitHub for each repository', async () => { + const repoProperties = { + frontmatter: [ + { owner: 'owner1', repo: 'repo1', branch: 'main' }, + { owner: 'owner2', repo: 'repo2', branch: 'main' }, + ], + repository: null, + }; + + const deleteFromGithubOneRepoSpy = jest.spyOn(filesManagement, 'deleteFromGithubOneRepo').mockResolvedValue({ + success: true, + deleted: ['file1', 'file2'], + undeleted: [], + }); + + const result = await deleteFromGithub(false, 'branchName', filesManagement, repoProperties); + + expect(deleteFromGithubOneRepoSpy).toHaveBeenCalledTimes(2); + expect(deleteFromGithubOneRepoSpy).toHaveBeenCalledWith(false, 'branchName', filesManagement, { + frontmatter: repoProperties.frontmatter[0], + repository: repoProperties.repository, + convert: undefined, + }); + expect(deleteFromGithubOneRepoSpy).toHaveBeenCalledWith(false, 'branchName', filesManagement, { + frontmatter: repoProperties.frontmatter[1], + repository: repoProperties.repository, + convert: undefined, + }); + expect(result).toEqual({ + success: true, + deleted: ['file1', 'file2'], + undeleted: [], + }); + }); + }); + + describe('filterGithubFile', () => { + it('should filter GitHub files based on settings and properties', async () => { + const fileInRepo = [ + { file: 'file1.md', sha: 'sha1' }, + { file: 'file2.md', sha: 'sha2' }, + { file: 'file3.png', sha: 'sha3' }, + ]; + + const settings = { + upload: { + behavior: 'default', + rootFolder: 'root', + defaultName: 'default', + attachment: { + folder: 'attachments', + }, + autoclean: { + includeAttachments: true, + excluded: [], + }, + }, + embed: { + unHandledObsidianExt: [], + }, + }; + + const prop = { + path: { + type: 'default', + rootFolder: 'root', + defaultName: 'default', + attachment: { + folder: 'attachments', + }, + }, + }; + + const result = await filterGithubFile(fileInRepo, settings, prop); + + expect(result).toEqual([ + { file: 'file1.md', sha: 'sha1' }, + { file: 'file2.md', sha: 'sha2' }, + { file: 'file3.png', sha: 'sha3' }, + ]); + }); + }); +}); diff --git a/src/GitHub/files.test.ts b/src/GitHub/files.test.ts new file mode 100644 index 00000000..400e1d08 --- /dev/null +++ b/src/GitHub/files.test.ts @@ -0,0 +1,75 @@ +import { FilesManagement } from './files'; +import { app } from '../__mocks__/obsidian'; +import { Octokit } from '@octokit/core'; +import { TFile, TFolder } from 'obsidian'; + +jest.mock('obsidian'); + +describe('FilesManagement', () => { + let octokit: Octokit; + let filesManagement: FilesManagement; + + beforeEach(() => { + octokit = new Octokit(); + filesManagement = new FilesManagement(octokit, app); + }); + + describe('getSharedFiles', () => { + it('should return shared files', () => { + const sharedFiles = [ + new TFile('file1.md', 'file1.md', 0, 0, 0, 0, 0, 0, 0, 0), + new TFile('file2.md', 'file2.md', 0, 0, 0, 0, 0, 0, 0, 0), + ]; + + app.vault.getMarkdownFiles.mockReturnValue(sharedFiles); + app.metadataCache.getCache.mockReturnValue({ frontmatter: { share: true } }); + + const result = filesManagement.getSharedFiles(null); + + expect(result).toEqual(sharedFiles); + }); + }); + + describe('getAllFileWithPath', () => { + it('should return all files with their paths', () => { + const files = [ + new TFile('file1.md', 'file1.md', 0, 0, 0, 0, 0, 0, 0, 0), + new TFile('file2.md', 'file2.md', 0, 0, 0, 0, 0, 0, 0, 0, 0), + ]; + + app.vault.getFiles.mockReturnValue(files); + app.metadataCache.getCache.mockReturnValue({ frontmatter: { share: true } }); + + const result = filesManagement.getAllFileWithPath(null, {}); + + expect(result).toEqual([ + { converted: 'file1.md', real: files[0], otherPaths: undefined }, + { converted: 'file2.md', real: files[1], otherPaths: undefined }, + ]); + }); + }); + + describe('getLinkedByEmbedding', () => { + it('should return linked files by embedding', () => { + const file = new TFile('file1.md', 'file1.md', 0, 0, 0, 0, 0, 0, 0, 0); + const linkedFile = new TFile('file2.md', 'file2.md', 0, 0, 0, 0, 0, 0, 0, 0); + + app.metadataCache.getFileCache.mockReturnValue({ + embeds: [{ link: 'file2.md', displayText: 'file2', position: { start: { offset: 0 }, end: { offset: 0 } } }], + }); + app.metadataCache.getFirstLinkpathDest.mockReturnValue(linkedFile); + + const result = filesManagement.getLinkedByEmbedding(file); + + expect(result).toEqual([ + { + linked: linkedFile, + linkFrom: 'file2.md', + altText: 'file2', + type: 'embed', + position: { start: 0, end: 0 }, + }, + ]); + }); + }); +}); diff --git a/src/GitHub/upload.test.ts b/src/GitHub/upload.test.ts new file mode 100644 index 00000000..2df3f9a5 --- /dev/null +++ b/src/GitHub/upload.test.ts @@ -0,0 +1,94 @@ +import Publisher from './upload'; +import { app } from '../__mocks__/obsidian'; +import { Octokit } from '@octokit/core'; +import { TFile } from 'obsidian'; + +jest.mock('obsidian'); + +describe('Publisher', () => { + let octokit: Octokit; + let publisher: Publisher; + + beforeEach(() => { + octokit = new Octokit(); + publisher = new Publisher(octokit, app); + }); + + describe('publish', () => { + it('should publish a file to GitHub', async () => { + const file = new TFile('file1.md', 'file1.md', 0, 0, 0, 0, 0, 0, 0, 0); + const repoProperties = { + frontmatter: { owner: 'owner', repo: 'repo', branch: 'main' }, + repository: null, + }; + + const uploadTextSpy = jest.spyOn(publisher, 'uploadText').mockResolvedValue({ + isUpdated: true, + file: 'file1.md', + }); + + const result = await publisher.publish(file, false, repoProperties, [], true, null); + + expect(uploadTextSpy).toHaveBeenCalledWith(expect.any(String), 'file1.md', 'file1.md', repoProperties.frontmatter); + expect(result).toEqual({ + deleted: [], + uploaded: [{ isUpdated: true, file: 'file1.md' }], + error: [], + }); + }); + }); + + describe('uploadOnMultipleRepo', () => { + it('should upload a file to multiple repositories', async () => { + const file = new TFile('file1.md', 'file1.md', 0, 0, 0, 0, 0, 0, 0, 0); + const text = 'file content'; + const path = 'file1.md'; + const embedFiles = []; + const fileHistory = []; + const deepScan = true; + const shareFiles = { getSharedEmbed: jest.fn(), getMetadataLinks: jest.fn(), getLinkedByEmbedding: jest.fn() }; + const autoclean = false; + const properties = { + frontmatter: { owner: 'owner', repo: 'repo', branch: 'main' }, + repository: null, + }; + + const uploadTextSpy = jest.spyOn(publisher, 'uploadText').mockResolvedValue({ + isUpdated: true, + file: 'file1.md', + }); + + const result = await publisher.uploadOnMultipleRepo(file, text, path, embedFiles, fileHistory, deepScan, shareFiles, autoclean, properties); + + expect(uploadTextSpy).toHaveBeenCalledWith(text, path, 'file1.md', properties.frontmatter); + expect(result).toEqual({ + deleted: [], + uploaded: [{ isUpdated: true, file: 'file1.md' }], + error: [], + }); + }); + }); + + describe('uploadImage', () => { + it('should upload an image to GitHub', async () => { + const imageFile = new TFile('image.png', 'image.png', 0, 0, 0, 0, 0, 0, 0, 0); + const properties = { + frontmatter: { owner: 'owner', repo: 'repo', branch: 'main' }, + repository: null, + }; + + const uploadSpy = jest.spyOn(publisher, 'upload').mockResolvedValue({ + isUpdated: true, + file: 'image.png', + }); + + const result = await publisher.uploadImage(imageFile, properties); + + expect(uploadSpy).toHaveBeenCalledWith(expect.any(String), 'image.png', '', properties.frontmatter); + expect(result).toEqual({ + isUpdated: true, + file: 'image.png', + }); + }); + }); +}); diff --git a/src/__mocks__/obsidian.ts b/src/__mocks__/obsidian.ts new file mode 100644 index 00000000..2dc9285b --- /dev/null +++ b/src/__mocks__/obsidian.ts @@ -0,0 +1,49 @@ +export const app = { + vault: { + getMarkdownFiles: jest.fn(), + getAbstractFileByPath: jest.fn(), + create: jest.fn(), + createFolder: jest.fn(), + createBinary: jest.fn(), + readBinary: jest.fn(), + read: jest.fn(), + modify: jest.fn(), + modifyBinary: jest.fn(), + trash: jest.fn(), + adapter: { + exists: jest.fn(), + read: jest.fn(), + write: jest.fn(), + mkdir: jest.fn(), + trashSystem: jest.fn(), + }, + }, + metadataCache: { + getFileCache: jest.fn(), + getCache: jest.fn(), + getFirstLinkpathDest: jest.fn(), + getBacklinksForFile: jest.fn(), + }, + workspace: { + getActiveFile: jest.fn(), + }, + plugins: { + enabledPlugins: new Set(), + plugins: { + dataview: { + api: { + page: jest.fn(), + evaluateInline: jest.fn(), + executeJs: jest.fn(), + tryQueryMarkdown: jest.fn(), + settings: { + dataviewJsKeyword: "dataviewjs", + inlineQueryPrefix: "=", + inlineJsQueryPrefix: "$=", + renderNullAs: "null", + }, + }, + }, + }, + }, +};