mirror of
https://github.com/bfloydd/streams.git
synced 2026-07-22 05:49:02 +00:00
add tests for which view to show
This commit is contained in:
parent
f8392091d4
commit
cec4bb2c45
7 changed files with 4726 additions and 46 deletions
133
__mocks__/obsidian.ts
Normal file
133
__mocks__/obsidian.ts
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
// Mock implementation of Obsidian API for Jest tests
|
||||
// This provides minimal implementations of the classes and types used in tests
|
||||
|
||||
export class App {
|
||||
plugins: any;
|
||||
commands: any;
|
||||
vault: any;
|
||||
workspace: any;
|
||||
|
||||
constructor() {
|
||||
this.plugins = {
|
||||
plugins: {},
|
||||
};
|
||||
this.commands = {
|
||||
commands: {},
|
||||
};
|
||||
this.vault = {};
|
||||
this.workspace = {};
|
||||
}
|
||||
}
|
||||
|
||||
export class Plugin {
|
||||
app: App;
|
||||
settings: any;
|
||||
|
||||
constructor(app: App) {
|
||||
this.app = app;
|
||||
this.settings = {};
|
||||
}
|
||||
}
|
||||
|
||||
export class TFile {
|
||||
path: string;
|
||||
name: string;
|
||||
extension: string;
|
||||
|
||||
constructor(path: string, name: string, extension: string) {
|
||||
this.path = path;
|
||||
this.name = name;
|
||||
this.extension = extension;
|
||||
}
|
||||
}
|
||||
|
||||
export class TFolder {
|
||||
path: string;
|
||||
name: string;
|
||||
|
||||
constructor(path: string, name: string) {
|
||||
this.path = path;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
export class WorkspaceLeaf {
|
||||
view: any;
|
||||
viewState: any;
|
||||
|
||||
constructor() {
|
||||
this.view = {
|
||||
getViewType: () => 'markdown',
|
||||
};
|
||||
this.viewState = {};
|
||||
}
|
||||
|
||||
async setViewState(state: any): Promise<void> {
|
||||
this.viewState = state;
|
||||
}
|
||||
|
||||
async openFile(file: TFile): Promise<void> {
|
||||
// Mock implementation
|
||||
}
|
||||
}
|
||||
|
||||
export class ItemView {
|
||||
navigation: boolean = false;
|
||||
contentEl: HTMLElement;
|
||||
leaf: WorkspaceLeaf;
|
||||
app: App;
|
||||
|
||||
constructor(leaf: WorkspaceLeaf, app: App) {
|
||||
this.leaf = leaf;
|
||||
this.app = app;
|
||||
// Use a simple mock element if document is not available
|
||||
this.contentEl = (typeof document !== 'undefined'
|
||||
? document.createElement('div')
|
||||
: { innerHTML: '', appendChild: () => {}, remove: () => {} } as any);
|
||||
}
|
||||
|
||||
getViewType(): string {
|
||||
return 'item-view';
|
||||
}
|
||||
|
||||
getDisplayText(): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
async onOpen(): Promise<void> {}
|
||||
async onClose(): Promise<void> {}
|
||||
}
|
||||
|
||||
export class MarkdownView extends ItemView {
|
||||
constructor(leaf: WorkspaceLeaf, app: App) {
|
||||
super(leaf, app);
|
||||
}
|
||||
|
||||
getViewType(): string {
|
||||
return 'markdown';
|
||||
}
|
||||
}
|
||||
|
||||
export function setIcon(el: HTMLElement, icon: string): void {
|
||||
// Mock implementation
|
||||
}
|
||||
|
||||
export class Notice {
|
||||
message: string;
|
||||
timeout: number;
|
||||
|
||||
constructor(message: string, timeout: number = 0) {
|
||||
this.message = message;
|
||||
this.timeout = timeout;
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizePath(path: string): string {
|
||||
return path.replace(/\\/g, '/');
|
||||
}
|
||||
|
||||
// Export other commonly used types/interfaces
|
||||
export interface Component {}
|
||||
export interface ViewState {}
|
||||
export interface WorkspaceItem {}
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testEnvironment: 'jsdom',
|
||||
roots: ['<rootDir>/src'],
|
||||
testMatch: ['**/__tests__/**/*.test.ts', '**/?(*.)+(spec|test).ts'],
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
|
||||
|
|
@ -10,10 +10,10 @@ module.exports = {
|
|||
'!src/**/__tests__/**',
|
||||
],
|
||||
moduleNameMapper: {
|
||||
'^obsidian$': '<rootDir>/node_modules/obsidian',
|
||||
'^obsidian$': '<rootDir>/__mocks__/obsidian.ts',
|
||||
},
|
||||
globals: {
|
||||
'ts-jest': {
|
||||
transform: {
|
||||
'^.+\\.ts$': ['ts-jest', {
|
||||
tsconfig: {
|
||||
module: 'ESNext',
|
||||
target: 'ES6',
|
||||
|
|
@ -21,7 +21,8 @@ module.exports = {
|
|||
esModuleInterop: true,
|
||||
allowSyntheticDefaultImports: true,
|
||||
},
|
||||
},
|
||||
}],
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
4190
package-lock.json
generated
4190
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -22,6 +22,7 @@
|
|||
"builtin-modules": "3.3.0",
|
||||
"esbuild": "0.17.3",
|
||||
"jest": "^29.5.0",
|
||||
"jest-environment-jsdom": "^29.5.0",
|
||||
"obsidian": "latest",
|
||||
"ts-jest": "^29.1.0",
|
||||
"tslib": "2.4.0",
|
||||
|
|
|
|||
117
src/slices/file-operations/__tests__/meldDetection.test.ts
Normal file
117
src/slices/file-operations/__tests__/meldDetection.test.ts
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
import { App } from 'obsidian';
|
||||
|
||||
/**
|
||||
* Helper function to check if Meld plugin is available
|
||||
* This mirrors the logic in streamUtils.ts
|
||||
*/
|
||||
function isMeldPluginAvailable(app: App): boolean {
|
||||
try {
|
||||
// Check if the Meld plugin is installed and enabled
|
||||
const plugins = (app as any).plugins?.plugins;
|
||||
if (!plugins) return false;
|
||||
|
||||
// Check for Meld plugin
|
||||
const meldPlugin = plugins['meld-encrypt'];
|
||||
if (!meldPlugin) return false;
|
||||
|
||||
// Check if the specific command exists
|
||||
const commands = (app as any).commands?.commands;
|
||||
if (!commands) return false;
|
||||
|
||||
return !!commands['meld-encrypt:meld-encrypt-convert-to-or-from-encrypted-note'];
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
describe('Meld Plugin Detection', () => {
|
||||
let mockApp: Partial<App>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockApp = {
|
||||
plugins: {
|
||||
plugins: {},
|
||||
},
|
||||
commands: {
|
||||
commands: {},
|
||||
},
|
||||
} as any;
|
||||
});
|
||||
|
||||
describe('isMeldPluginAvailable', () => {
|
||||
it('should return false when plugins object does not exist', () => {
|
||||
const app = {} as App;
|
||||
expect(isMeldPluginAvailable(app)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when meld-encrypt plugin is not installed', () => {
|
||||
const app = {
|
||||
plugins: {
|
||||
plugins: {
|
||||
'other-plugin': {},
|
||||
},
|
||||
},
|
||||
} as any as App;
|
||||
expect(isMeldPluginAvailable(app)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when meld-encrypt plugin exists but commands do not', () => {
|
||||
const app = {
|
||||
plugins: {
|
||||
plugins: {
|
||||
'meld-encrypt': {},
|
||||
},
|
||||
},
|
||||
commands: undefined,
|
||||
} as any as App;
|
||||
expect(isMeldPluginAvailable(app)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when meld-encrypt plugin exists but command is missing', () => {
|
||||
const app = {
|
||||
plugins: {
|
||||
plugins: {
|
||||
'meld-encrypt': {},
|
||||
},
|
||||
},
|
||||
commands: {
|
||||
commands: {
|
||||
'other-command': {},
|
||||
},
|
||||
},
|
||||
} as any as App;
|
||||
expect(isMeldPluginAvailable(app)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true when meld-encrypt plugin is installed and command exists', () => {
|
||||
const app = {
|
||||
plugins: {
|
||||
plugins: {
|
||||
'meld-encrypt': {},
|
||||
},
|
||||
},
|
||||
commands: {
|
||||
commands: {
|
||||
'meld-encrypt:meld-encrypt-convert-to-or-from-encrypted-note': {
|
||||
callback: jest.fn(),
|
||||
},
|
||||
},
|
||||
},
|
||||
} as any as App;
|
||||
expect(isMeldPluginAvailable(app)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when an error occurs during detection', () => {
|
||||
const app = {
|
||||
get plugins() {
|
||||
throw new Error('Test error');
|
||||
},
|
||||
} as any as App;
|
||||
expect(isMeldPluginAvailable(app)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
115
src/slices/file-operations/__tests__/viewSelection.test.ts
Normal file
115
src/slices/file-operations/__tests__/viewSelection.test.ts
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
import { Stream } from '../../../shared/types';
|
||||
import { CREATE_FILE_VIEW_TYPE } from '../CreateFileView';
|
||||
import { INSTALL_MELD_VIEW_TYPE } from '../InstallMeldView';
|
||||
import { CREATE_FILE_VIEW_ENCRYPTED_TYPE } from '../CreateFileViewEncrypted';
|
||||
|
||||
/**
|
||||
* Helper function to determine view type based on Meld availability and stream encryption
|
||||
* This mirrors the logic in streamUtils.ts openStreamDate function
|
||||
*/
|
||||
function determineViewType(
|
||||
isMeldAvailable: boolean,
|
||||
streamEncryptThisStream: boolean
|
||||
): string {
|
||||
if (!isMeldAvailable) {
|
||||
// Meld not available: show InstallMeldView only if stream has encryption enabled
|
||||
// Otherwise show CreateFileView (normal behavior)
|
||||
return streamEncryptThisStream ? INSTALL_MELD_VIEW_TYPE : CREATE_FILE_VIEW_TYPE;
|
||||
} else {
|
||||
// Meld available: choose based on stream encryption setting
|
||||
return streamEncryptThisStream ? CREATE_FILE_VIEW_ENCRYPTED_TYPE : CREATE_FILE_VIEW_TYPE;
|
||||
}
|
||||
}
|
||||
|
||||
describe('View Selection Logic for Meld Integration', () => {
|
||||
describe('when Meld is NOT available', () => {
|
||||
it('should show CreateFileView when stream encryption is disabled', () => {
|
||||
const stream: Stream = {
|
||||
id: 'test-stream',
|
||||
name: 'Test Stream',
|
||||
folder: 'test',
|
||||
icon: 'file-text',
|
||||
showTodayInRibbon: false,
|
||||
addCommand: false,
|
||||
encryptThisStream: false,
|
||||
disabled: false,
|
||||
};
|
||||
|
||||
const viewType = determineViewType(false, stream.encryptThisStream);
|
||||
expect(viewType).toBe(CREATE_FILE_VIEW_TYPE);
|
||||
});
|
||||
|
||||
it('should show InstallMeldView when stream encryption is enabled', () => {
|
||||
const stream: Stream = {
|
||||
id: 'test-stream',
|
||||
name: 'Test Stream',
|
||||
folder: 'test',
|
||||
icon: 'file-text',
|
||||
showTodayInRibbon: false,
|
||||
addCommand: false,
|
||||
encryptThisStream: true,
|
||||
disabled: false,
|
||||
};
|
||||
|
||||
const viewType = determineViewType(false, stream.encryptThisStream);
|
||||
expect(viewType).toBe(INSTALL_MELD_VIEW_TYPE);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when Meld IS available', () => {
|
||||
it('should show CreateFileViewEncrypted when stream encryption is enabled', () => {
|
||||
const stream: Stream = {
|
||||
id: 'test-stream',
|
||||
name: 'Test Stream',
|
||||
folder: 'test',
|
||||
icon: 'file-text',
|
||||
showTodayInRibbon: false,
|
||||
addCommand: false,
|
||||
encryptThisStream: true,
|
||||
disabled: false,
|
||||
};
|
||||
|
||||
const viewType = determineViewType(true, stream.encryptThisStream);
|
||||
expect(viewType).toBe(CREATE_FILE_VIEW_ENCRYPTED_TYPE);
|
||||
});
|
||||
|
||||
it('should show CreateFileView when stream encryption is disabled', () => {
|
||||
const stream: Stream = {
|
||||
id: 'test-stream',
|
||||
name: 'Test Stream',
|
||||
folder: 'test',
|
||||
icon: 'file-text',
|
||||
showTodayInRibbon: false,
|
||||
addCommand: false,
|
||||
encryptThisStream: false,
|
||||
disabled: false,
|
||||
};
|
||||
|
||||
const viewType = determineViewType(true, stream.encryptThisStream);
|
||||
expect(viewType).toBe(CREATE_FILE_VIEW_TYPE);
|
||||
});
|
||||
});
|
||||
|
||||
describe('spec compliance - all 4 scenarios from spec.md', () => {
|
||||
it('spec: Meld not installed + stream encryption disabled → CreateFileView', () => {
|
||||
const viewType = determineViewType(false, false);
|
||||
expect(viewType).toBe(CREATE_FILE_VIEW_TYPE);
|
||||
});
|
||||
|
||||
it('spec: Meld not installed + stream encryption enabled → InstallMeldView', () => {
|
||||
const viewType = determineViewType(false, true);
|
||||
expect(viewType).toBe(INSTALL_MELD_VIEW_TYPE);
|
||||
});
|
||||
|
||||
it('spec: Meld installed + stream encryption enabled → CreateFileViewEncrypted', () => {
|
||||
const viewType = determineViewType(true, true);
|
||||
expect(viewType).toBe(CREATE_FILE_VIEW_ENCRYPTED_TYPE);
|
||||
});
|
||||
|
||||
it('spec: Meld installed + stream encryption disabled → CreateFileView', () => {
|
||||
const viewType = determineViewType(true, false);
|
||||
expect(viewType).toBe(CREATE_FILE_VIEW_TYPE);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -0,0 +1,205 @@
|
|||
import { App } from 'obsidian';
|
||||
import { MeldDetectionService } from '../MeldDetectionService';
|
||||
import { Plugin } from 'obsidian';
|
||||
|
||||
describe('MeldDetectionService', () => {
|
||||
let service: MeldDetectionService;
|
||||
let mockPlugin: Partial<Plugin>;
|
||||
let mockApp: Partial<App>;
|
||||
|
||||
beforeEach(() => {
|
||||
service = new MeldDetectionService();
|
||||
|
||||
mockApp = {
|
||||
plugins: {
|
||||
plugins: {},
|
||||
},
|
||||
commands: {
|
||||
commands: {},
|
||||
},
|
||||
} as any;
|
||||
|
||||
mockPlugin = {
|
||||
app: mockApp as App,
|
||||
} as Plugin;
|
||||
|
||||
service.setPlugin(mockPlugin as Plugin);
|
||||
});
|
||||
|
||||
describe('isMeldPluginAvailable', () => {
|
||||
it('should return false when plugins object does not exist', async () => {
|
||||
await service.initialize();
|
||||
const app = { } as App;
|
||||
(mockPlugin as any).app = app;
|
||||
expect(service.isMeldPluginAvailable()).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when meld-encrypt plugin is not installed', async () => {
|
||||
await service.initialize();
|
||||
const app = {
|
||||
plugins: {
|
||||
plugins: {
|
||||
'other-plugin': {},
|
||||
},
|
||||
},
|
||||
} as any as App;
|
||||
(mockPlugin as any).app = app;
|
||||
expect(service.isMeldPluginAvailable()).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when meld-encrypt plugin exists but commands do not', async () => {
|
||||
await service.initialize();
|
||||
const app = {
|
||||
plugins: {
|
||||
plugins: {
|
||||
'meld-encrypt': {},
|
||||
},
|
||||
},
|
||||
commands: undefined,
|
||||
} as any as App;
|
||||
(mockPlugin as any).app = app;
|
||||
expect(service.isMeldPluginAvailable()).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when meld-encrypt plugin exists but command is missing', async () => {
|
||||
await service.initialize();
|
||||
const app = {
|
||||
plugins: {
|
||||
plugins: {
|
||||
'meld-encrypt': {},
|
||||
},
|
||||
},
|
||||
commands: {
|
||||
commands: {
|
||||
'other-command': {},
|
||||
},
|
||||
},
|
||||
} as any as App;
|
||||
(mockPlugin as any).app = app;
|
||||
expect(service.isMeldPluginAvailable()).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true when meld-encrypt plugin is installed and command exists', async () => {
|
||||
await service.initialize();
|
||||
const app = {
|
||||
plugins: {
|
||||
plugins: {
|
||||
'meld-encrypt': {},
|
||||
},
|
||||
},
|
||||
commands: {
|
||||
commands: {
|
||||
'meld-encrypt:meld-encrypt-convert-to-or-from-encrypted-note': {
|
||||
callback: jest.fn(),
|
||||
},
|
||||
},
|
||||
},
|
||||
} as any as App;
|
||||
(mockPlugin as any).app = app;
|
||||
expect(service.isMeldPluginAvailable()).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when an error occurs during detection', async () => {
|
||||
await service.initialize();
|
||||
const app = {
|
||||
get plugins() {
|
||||
throw new Error('Test error');
|
||||
},
|
||||
} as any as App;
|
||||
(mockPlugin as any).app = app;
|
||||
expect(service.isMeldPluginAvailable()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMeldCommandId', () => {
|
||||
it('should return the correct Meld command ID', async () => {
|
||||
await service.initialize();
|
||||
expect(service.getMeldCommandId()).toBe('meld-encrypt:meld-encrypt-convert-to-or-from-encrypted-note');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMeldUnavailableMessage', () => {
|
||||
it('should return a user-friendly error message', async () => {
|
||||
await service.initialize();
|
||||
const message = service.getMeldUnavailableMessage();
|
||||
expect(message).toBeTruthy();
|
||||
expect(typeof message).toBe('string');
|
||||
expect(message.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('executeMeldEncryption', () => {
|
||||
it('should throw error when Meld is not available', async () => {
|
||||
await service.initialize();
|
||||
const app = {
|
||||
plugins: {
|
||||
plugins: {},
|
||||
},
|
||||
} as any as App;
|
||||
(mockPlugin as any).app = app;
|
||||
|
||||
const result = await service.executeMeldEncryption();
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should execute command when Meld is available', async () => {
|
||||
await service.initialize();
|
||||
const mockCallback = jest.fn().mockResolvedValue(undefined);
|
||||
const app = {
|
||||
plugins: {
|
||||
plugins: {
|
||||
'meld-encrypt': {},
|
||||
},
|
||||
},
|
||||
commands: {
|
||||
commands: {
|
||||
'meld-encrypt:meld-encrypt-convert-to-or-from-encrypted-note': {
|
||||
callback: mockCallback,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as any as App;
|
||||
(mockPlugin as any).app = app;
|
||||
|
||||
const result = await service.executeMeldEncryption();
|
||||
expect(result).toBe(true);
|
||||
expect(mockCallback).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should return false when command is not found', async () => {
|
||||
await service.initialize();
|
||||
const app = {
|
||||
plugins: {
|
||||
plugins: {
|
||||
'meld-encrypt': {},
|
||||
},
|
||||
},
|
||||
commands: {
|
||||
commands: {},
|
||||
},
|
||||
} as any as App;
|
||||
(mockPlugin as any).app = app;
|
||||
|
||||
const result = await service.executeMeldEncryption();
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('initialize and cleanup', () => {
|
||||
it('should initialize successfully', async () => {
|
||||
await service.initialize();
|
||||
expect(service.isMeldPluginAvailable()).toBeDefined();
|
||||
});
|
||||
|
||||
it('should cleanup successfully', async () => {
|
||||
await service.initialize();
|
||||
service.cleanup();
|
||||
// Cleanup should not throw
|
||||
expect(() => service.cleanup()).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in a new issue