mirror of
https://github.com/mryanb/obsidian-asana.git
synced 2026-07-22 05:38:24 +00:00
test: add initial test suite for plugin functionality
- Add Jest configuration and TypeScript support - Create tests for main plugin functionality (initialization, settings) - Add tests for Asana API integration (workspaces, projects, tasks) - Set up mock files for Obsidian API and test environment - Add test scripts to package.json This commit sets up a basic test infrastructure to ensure the plugin's core functionality works as expected and to catch potential regressions in future changes.
This commit is contained in:
parent
e80e621e56
commit
b425a7b952
7 changed files with 4187 additions and 5 deletions
21
jest.config.js
Normal file
21
jest.config.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
module.exports = {
|
||||
testEnvironment: 'node',
|
||||
transform: {
|
||||
'^.+\\.tsx?$': ['ts-jest', {
|
||||
tsconfig: 'tsconfig.json'
|
||||
}]
|
||||
},
|
||||
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$',
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
|
||||
moduleNameMapper: {
|
||||
'^obsidian$': '<rootDir>/tests/__mocks__/obsidian.js'
|
||||
},
|
||||
setupFiles: ['<rootDir>/tests/setup.js'],
|
||||
transformIgnorePatterns: [
|
||||
'node_modules/(?!(obsidian)/)'
|
||||
],
|
||||
testPathIgnorePatterns: [
|
||||
'/node_modules/',
|
||||
'/dist/'
|
||||
]
|
||||
};
|
||||
3827
package-lock.json
generated
3827
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -5,7 +5,9 @@
|
|||
"scripts": {
|
||||
"dev": "node esbuild.config.mjs",
|
||||
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
|
||||
"version": "node version-bump.mjs && git add manifest.json versions.json"
|
||||
"version": "node version-bump.mjs && git add manifest.json versions.json",
|
||||
"test": "jest --config jest.config.js",
|
||||
"test:watch": "jest --watch --config jest.config.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "Ryan Bantz",
|
||||
|
|
@ -13,7 +15,9 @@
|
|||
"devDependencies": {
|
||||
"builtin-modules": "^4.0.0",
|
||||
"esbuild": "0.17.3",
|
||||
"jest": "^29.7.0",
|
||||
"obsidian": "^1.7.2",
|
||||
"ts-jest": "^29.1.1",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^4.0.0"
|
||||
}
|
||||
|
|
|
|||
115
tests/__mocks__/obsidian.js
Normal file
115
tests/__mocks__/obsidian.js
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
class App {
|
||||
constructor() {
|
||||
this.workspace = {
|
||||
activeLeaf: {
|
||||
view: {
|
||||
editor: {
|
||||
getCursor: () => ({ line: 0, ch: 0 }),
|
||||
getLine: () => '',
|
||||
replaceRange: jest.fn()
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class Editor {
|
||||
constructor() {
|
||||
this.getCursor = jest.fn();
|
||||
this.getLine = jest.fn();
|
||||
this.replaceRange = jest.fn();
|
||||
}
|
||||
}
|
||||
|
||||
class MarkdownView {
|
||||
constructor() {
|
||||
this.editor = new Editor();
|
||||
}
|
||||
}
|
||||
|
||||
class Notice {
|
||||
constructor(message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
|
||||
class Plugin {
|
||||
constructor(app, manifest) {
|
||||
this.app = app;
|
||||
this.manifest = manifest;
|
||||
}
|
||||
|
||||
loadData() {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
|
||||
saveData(data) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
class PluginSettingTab {
|
||||
constructor(app, plugin) {
|
||||
this.app = app;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
}
|
||||
|
||||
class Setting {
|
||||
constructor(containerEl) {
|
||||
this.containerEl = containerEl;
|
||||
}
|
||||
|
||||
setName(name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
setDesc(desc) {
|
||||
this.desc = desc;
|
||||
return this;
|
||||
}
|
||||
|
||||
addText(callback) {
|
||||
callback({ setValue: jest.fn(), onChange: jest.fn() });
|
||||
return this;
|
||||
}
|
||||
|
||||
addToggle(callback) {
|
||||
callback({ setValue: jest.fn(), onChange: jest.fn() });
|
||||
return this;
|
||||
}
|
||||
|
||||
addButton(callback) {
|
||||
callback({ setButtonText: jest.fn(), onClick: jest.fn() });
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
class FuzzySuggestModal {
|
||||
constructor(app, items, resolve) {
|
||||
this.app = app;
|
||||
this.items = items;
|
||||
this.resolve = resolve;
|
||||
}
|
||||
|
||||
onOpen() {}
|
||||
getItems() { return this.items; }
|
||||
getItemText(item) { return item.name; }
|
||||
onChooseItem(item) { this.resolve(item); }
|
||||
}
|
||||
|
||||
const requestUrl = jest.fn();
|
||||
|
||||
module.exports = {
|
||||
App,
|
||||
Editor,
|
||||
MarkdownView,
|
||||
Notice,
|
||||
Plugin,
|
||||
PluginSettingTab,
|
||||
Setting,
|
||||
FuzzySuggestModal,
|
||||
requestUrl
|
||||
};
|
||||
155
tests/api.test.js
Normal file
155
tests/api.test.js
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
const { requestUrl } = require('obsidian');
|
||||
const {
|
||||
fetchAsanaWorkspaces,
|
||||
fetchAsanaProjects,
|
||||
fetchAsanaSections,
|
||||
createTaskInAsana
|
||||
} = require('../src/api/asanaApi');
|
||||
|
||||
jest.mock('obsidian', () => ({
|
||||
requestUrl: jest.fn()
|
||||
}));
|
||||
|
||||
describe('Asana API', () => {
|
||||
const mockSettings = {
|
||||
asanaToken: 'test-token',
|
||||
showArchivedProjects: false
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('fetchAsanaWorkspaces handles successful response', async () => {
|
||||
const mockWorkspaces = [{ id: '1', name: 'Workspace 1' }];
|
||||
requestUrl.mockResolvedValueOnce({
|
||||
status: 200,
|
||||
json: { data: mockWorkspaces }
|
||||
});
|
||||
|
||||
const result = await fetchAsanaWorkspaces(mockSettings);
|
||||
expect(result).toEqual(mockWorkspaces);
|
||||
expect(requestUrl).toHaveBeenCalledWith({
|
||||
url: 'https://app.asana.com/api/1.0/workspaces',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: 'Bearer test-token'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('fetchAsanaWorkspaces handles error response', async () => {
|
||||
requestUrl.mockRejectedValueOnce(new Error('API Error'));
|
||||
|
||||
await expect(fetchAsanaWorkspaces(mockSettings)).rejects.toThrow('Failed to retrieve workspaces from Asana');
|
||||
});
|
||||
|
||||
test('fetchAsanaProjects handles successful response', async () => {
|
||||
const mockProjects = [{ id: '1', name: 'Project 1' }];
|
||||
requestUrl.mockResolvedValueOnce({
|
||||
status: 200,
|
||||
json: { data: mockProjects }
|
||||
});
|
||||
|
||||
const result = await fetchAsanaProjects('workspace-1', mockSettings);
|
||||
expect(result).toEqual(mockProjects);
|
||||
expect(requestUrl).toHaveBeenCalledWith({
|
||||
url: 'https://app.asana.com/api/1.0/workspaces/workspace-1/projects?is_archived=false',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: 'Bearer test-token'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('createTaskInAsana handles successful response', async () => {
|
||||
// Mock initial task creation
|
||||
const mockTaskGid = '1234';
|
||||
requestUrl.mockResolvedValueOnce({
|
||||
status: 201,
|
||||
json: { data: { gid: mockTaskGid, name: 'Test Task' } }
|
||||
});
|
||||
|
||||
// Mock section addition
|
||||
requestUrl.mockResolvedValueOnce({
|
||||
status: 200,
|
||||
json: { data: { gid: mockTaskGid } }
|
||||
});
|
||||
|
||||
// Mock task details fetch
|
||||
const mockTaskDetails = {
|
||||
gid: mockTaskGid,
|
||||
name: 'Test Task',
|
||||
permalink_url: 'https://app.asana.com/0/1/1'
|
||||
};
|
||||
requestUrl.mockResolvedValueOnce({
|
||||
status: 200,
|
||||
json: { data: mockTaskDetails }
|
||||
});
|
||||
|
||||
const result = await createTaskInAsana(
|
||||
'Test Task',
|
||||
'workspace-1',
|
||||
'project-1',
|
||||
'section-1',
|
||||
mockSettings
|
||||
);
|
||||
|
||||
expect(result).toEqual(mockTaskDetails);
|
||||
expect(requestUrl).toHaveBeenCalledTimes(3);
|
||||
|
||||
// Verify task creation call
|
||||
expect(requestUrl).toHaveBeenNthCalledWith(1, {
|
||||
url: 'https://app.asana.com/api/1.0/tasks',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: 'Bearer test-token',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
data: {
|
||||
name: 'Test Task',
|
||||
workspace: 'workspace-1',
|
||||
projects: ['project-1'],
|
||||
assignee: undefined
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Verify section addition call
|
||||
expect(requestUrl).toHaveBeenNthCalledWith(2, {
|
||||
url: 'https://app.asana.com/api/1.0/sections/section-1/addTask',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: 'Bearer test-token',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
data: {
|
||||
task: mockTaskGid
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Verify task details fetch call
|
||||
expect(requestUrl).toHaveBeenNthCalledWith(3, {
|
||||
url: `https://app.asana.com/api/1.0/tasks/${mockTaskGid}`,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: 'Bearer test-token'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('createTaskInAsana handles error response', async () => {
|
||||
requestUrl.mockRejectedValueOnce(new Error('API Error'));
|
||||
|
||||
await expect(createTaskInAsana(
|
||||
'Test Task',
|
||||
'workspace-1',
|
||||
'project-1',
|
||||
'section-1',
|
||||
mockSettings
|
||||
)).rejects.toThrow('Failed to create task in Asana');
|
||||
});
|
||||
});
|
||||
52
tests/main.test.js
Normal file
52
tests/main.test.js
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
const { App, Plugin } = require('obsidian');
|
||||
const AsanaPlugin = require('../main').default;
|
||||
|
||||
describe('Asana Plugin', () => {
|
||||
let plugin;
|
||||
let app;
|
||||
|
||||
beforeEach(async () => {
|
||||
app = new App();
|
||||
plugin = new AsanaPlugin(app, {});
|
||||
await plugin.loadSettings();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (plugin.onunload) {
|
||||
plugin.onunload();
|
||||
}
|
||||
});
|
||||
|
||||
test('Plugin loads successfully', () => {
|
||||
expect(plugin).toBeDefined();
|
||||
expect(plugin.settings).toBeDefined();
|
||||
});
|
||||
|
||||
test('Default settings are set correctly', () => {
|
||||
expect(plugin.settings.asanaToken).toBe('');
|
||||
expect(plugin.settings.markTaskAsCompleted).toBe(false);
|
||||
expect(plugin.settings.pinnedProjects).toEqual([]);
|
||||
expect(plugin.settings.enableMarkdownLink).toBe(true);
|
||||
expect(plugin.settings.showArchivedProjects).toBe(false);
|
||||
expect(plugin.settings.pinMyTasks).toBe(true);
|
||||
});
|
||||
|
||||
test('Settings can be saved and loaded', async () => {
|
||||
const testSettings = {
|
||||
asanaToken: 'test-token',
|
||||
markTaskAsCompleted: true,
|
||||
pinnedProjects: ['Project 1', 'Project 2'],
|
||||
enableMarkdownLink: false,
|
||||
showArchivedProjects: true,
|
||||
pinMyTasks: false
|
||||
};
|
||||
|
||||
// Mock the loadData method to return our test settings
|
||||
plugin.loadData = jest.fn().mockResolvedValue(testSettings);
|
||||
|
||||
// Load the settings
|
||||
await plugin.loadSettings();
|
||||
|
||||
expect(plugin.settings).toEqual(testSettings);
|
||||
});
|
||||
});
|
||||
16
tests/setup.js
Normal file
16
tests/setup.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// Mock the global window object
|
||||
global.window = {
|
||||
document: {
|
||||
createElement: jest.fn(),
|
||||
createDocumentFragment: jest.fn()
|
||||
}
|
||||
};
|
||||
|
||||
// Mock the global document object
|
||||
global.document = {
|
||||
createElement: jest.fn(),
|
||||
createDocumentFragment: jest.fn()
|
||||
};
|
||||
|
||||
// Mock console.error to prevent test output pollution
|
||||
console.error = jest.fn();
|
||||
Loading…
Reference in a new issue