Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tests for tag utils #83

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/utils/tags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export interface Tag {
}

export const getAllTagsWithTitle = async (title: string): Promise<Tag[]> => {
if(!title) return [];

return (await fetchAllItems(["search"], { query: title, type: "tag" })).map(tag => {
return {
id: tag.id,
Expand All @@ -17,6 +19,8 @@ export const getAllTagsWithTitle = async (title: string): Promise<Tag[]> => {
}

export const getAnyTagWithTitle = async (title: string): Promise<Tag> => {
if(!title) throw new Error("empty title");

const existingTags = await getAllTagsWithTitle(title);
if (existingTags.length) {
return existingTags[0];
Expand All @@ -30,9 +34,14 @@ export const getAnyTagWithTitle = async (title: string): Promise<Tag> => {
}

export const getAllNotesWithTag = async (tagId: string): Promise<Note[]> => {
if(!tagId) return [];

return fetchAllItems(["tags", tagId, "notes"], { fields: ["id", "title", "body"] });
}

export const applyTagToNote = async (tagId: string, noteId: string): Promise<void> => {
if(!tagId) throw new Error("empty tag id");
if(!noteId) throw new Error("empty note id");

await joplin.data.post(["tags", tagId, "notes"], null, { id: noteId });
}
18 changes: 18 additions & 0 deletions tests/mock-joplin-api-response.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export class Tag {
id: string;
title: string;
created_time: number;
updated_time: number;
user_created_time: number;
user_updated_time: number;
encryption_cipher_text: string;
encryption_applied: number;
is_shared: number;
parent_id: string;
user_data: string;

constructor(id: string, title: string){
this.id=id;
this.title=title;
}
}
7 changes: 6 additions & 1 deletion tests/mock-joplin-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@
settings: {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
globalValue: async (setting: string): Promise<string> => { return ""; },
value: async (setting: string): Promise<string> => { return ""; }

Check warning on line 18 in tests/mock-joplin-api.ts

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

'setting' is defined but never used
},
require: (): unknown => { return ""; }
require: (): unknown => { return ""; },
data: {
post: async (path: unknown, query?: any, body?: any, files?: any[]): Promise<any> => {

Check warning on line 22 in tests/mock-joplin-api.ts

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

'path' is defined but never used

Check warning on line 22 in tests/mock-joplin-api.ts

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

Argument 'query' should be typed with a non-any type

Check warning on line 22 in tests/mock-joplin-api.ts

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

'query' is defined but never used

Check warning on line 22 in tests/mock-joplin-api.ts

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

Unexpected any. Specify a different type
return Promise.resolve({});
}
}
};
172 changes: 172 additions & 0 deletions tests/utils/tags.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import joplin from "api";
import * as dataApi from "@templates/utils/dataApi";
import { Note } from "@templates/utils/templates";
import * as tagUtils from "@templates/utils/tags";
import * as mockApiResponse from "../mock-joplin-api-response";


const testExpectedCalls = (mockedFunction: any, expectedCalls: number): void => {
expect(mockedFunction.mock.calls.length).toBe(expectedCalls);
}

beforeEach(() => {
jest.clearAllMocks();
});


describe("getAllTagsWithTitle", () => {

test("should return empty list if title is empty", async () => {
jest.spyOn(dataApi, "fetchAllItems").mockResolvedValue([]);

await expect(tagUtils.getAllTagsWithTitle(""))
.resolves.toHaveLength(0);
testExpectedCalls(dataApi.fetchAllItems, 0)
});

test("should return empty list when no tag found", async () => {
jest.spyOn(dataApi, "fetchAllItems").mockResolvedValue([]);

await expect(tagUtils.getAllTagsWithTitle("notexistingtag"))
.resolves.toHaveLength(0);
testExpectedCalls(dataApi.fetchAllItems, 1)
});

test("should return all matched tags", async () => {
const tags: tagUtils.Tag[] = [
{id: "1", title: "tag1"},
{id: "2", title: "tag22"},
{id: "3", title: "tag345"}
];

const fetchResponse: mockApiResponse.Tag[] = [];
for(const tag of tags){
fetchResponse
.push(new mockApiResponse.Tag(tag.id, tag.title));
}

jest.spyOn(dataApi, "fetchAllItems")
.mockResolvedValue(fetchResponse);

await expect(tagUtils.getAllTagsWithTitle("tag*"))
.resolves.toEqual(tags);
testExpectedCalls(dataApi.fetchAllItems, 1)
});
});

describe("getAnyTagWithTitle", () => {
test("should reject promise if title is empty", async () => {
jest.spyOn(tagUtils, "getAllTagsWithTitle")
.mockResolvedValue([]);
jest.spyOn(joplin.data, "post").mockResolvedValue({});

await expect(tagUtils.getAnyTagWithTitle(""))
.rejects.toThrow();
testExpectedCalls(tagUtils.getAllTagsWithTitle, 0)
testExpectedCalls(joplin.data.post, 0)
});

test("should return first tag if tag[s] exist", async () => {
const mockTags: tagUtils.Tag[] = [
{ id: "1", title: "existingtag1" },
{ id: "2", title: "existingtag2" },
{ id: "3", title: "existingtag3" }
];

jest.spyOn(tagUtils, "getAllTagsWithTitle")
.mockResolvedValue(mockTags);
jest.spyOn(joplin.data, "post").mockResolvedValue({});

await expect(tagUtils.getAnyTagWithTitle("existingtag*"))
.resolves.toEqual(mockTags[0]);
testExpectedCalls(tagUtils.getAllTagsWithTitle, 1);
testExpectedCalls(joplin.data.post, 0);
});

test("should create new tag if tag does not exist", async () => {
const tagTitle = "unexistingtag";

jest.spyOn(tagUtils, "getAllTagsWithTitle")
.mockResolvedValue([]);
jest.spyOn(joplin.data, "post").mockImplementation(
async (path: unknown, query?: any, body?: any, files?: any[]): Promise<any> => {
return new mockApiResponse.Tag("1", body.title);
}
);

await expect(tagUtils.getAnyTagWithTitle(tagTitle))
.resolves.toHaveProperty("title", tagTitle);
testExpectedCalls(tagUtils.getAllTagsWithTitle, 1);
testExpectedCalls(joplin.data.post, 1);
});
});

describe("getAllNotesWithTag", () => {
test("should return empty list if title is empty", async () => {
jest.spyOn(dataApi, "fetchAllItems").mockResolvedValue([]);

await expect(tagUtils.getAllNotesWithTag(""))
.resolves.toHaveLength(0);
testExpectedCalls(dataApi.fetchAllItems, 0);
});

test("should return empty list if no note exist", async () => {
jest.spyOn(dataApi, "fetchAllItems").mockResolvedValue([]);

await expect(tagUtils.getAllNotesWithTag("sometag"))
.resolves.toHaveLength(0);
testExpectedCalls(dataApi.fetchAllItems, 1);
});

test("should return all notes with the given tag", async () => {
const mockNotes: Note[] = [
{id: "1", title: "C", body: "# Functions\n"},
{id: "2", title: "Go", body: "# Garbage Collection\n"},
{id: "3", title: "Python", body: "# Interpreter\n"},
{id: "4", title: "Java", body: "# OOP\n"}
];

jest.spyOn(dataApi, "fetchAllItems")
.mockResolvedValue(mockNotes);

await expect(tagUtils.getAllNotesWithTag("programming"))
.resolves.toEqual(mockNotes);
testExpectedCalls(dataApi.fetchAllItems, 1);
});
});

describe("applyTagToNote", () => {
test("should reject promise if either or both tag id and note id is empty", async () => {
jest.spyOn(joplin.data, "post")
.mockRejectedValue("mocked error");

await expect(tagUtils.applyTagToNote("", ""))
.rejects.toThrow();

await expect(tagUtils.applyTagToNote("45", ""))
.rejects.toThrow();

await expect(tagUtils.applyTagToNote("", "87"))
.rejects.toThrow();

testExpectedCalls(joplin.data.post, 0);
});

test("should call the post api with given arguments", async () => {
const tagId = "54";
const noteId = "23";

const mockPost = jest.spyOn(joplin.data, "post");
mockPost.mockResolvedValue({});

await tagUtils.applyTagToNote(tagId, noteId);

// TODO: use less stricter equality
// with expect.addEqualityTesters or jest-extended matchers
expect(mockPost).toBeCalledWith(
["tags", tagId, "notes"],
null,
{ id: noteId }
);
});
});
Loading