mirror of
https://github.com/asyouplz/SpeechNote.git
synced 2026-07-22 06:43:33 +00:00
fix(review): resolve obsidian bot validation errors (metadata, lint, ui) (#58)
This commit is contained in:
parent
2aeab4ff8f
commit
c6df377362
5 changed files with 73 additions and 55 deletions
|
|
@ -28,8 +28,7 @@
|
|||
"esbuild.config.mjs",
|
||||
"node_modules/**",
|
||||
"dist/**",
|
||||
"build/**",
|
||||
"src/testing/**"
|
||||
"build/**"
|
||||
],
|
||||
"rules": {
|
||||
"unused-imports/no-unused-imports": "error",
|
||||
|
|
@ -61,8 +60,8 @@
|
|||
"extendDefaults": true
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-explicit-any": "warn",
|
||||
"@typescript-eslint/no-non-null-assertion": "warn",
|
||||
"@typescript-eslint/no-explicit-any": "error",
|
||||
"@typescript-eslint/no-non-null-assertion": "error",
|
||||
"@typescript-eslint/no-empty-function": "warn",
|
||||
"@typescript-eslint/no-var-requires": "warn",
|
||||
"@typescript-eslint/no-inferrable-types": "warn",
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"id": "speech-to-text",
|
||||
"id": "speechnote",
|
||||
"name": "Speech to Text",
|
||||
"version": "4.0.1",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Convert audio recordings to text using multiple AI providers (OpenAI Whisper, Deepgram)",
|
||||
"description": "Convert audio recordings to text in Obsidian using multiple AI providers (OpenAI Whisper, Deepgram)",
|
||||
"author": "Taesun Lee",
|
||||
"authorUrl": "https://github.com/asyouplz",
|
||||
"fundingUrl": "",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "obsidian-speech-to-text",
|
||||
"name": "obsidian-speechnote",
|
||||
"version": "4.0.1",
|
||||
"description": "Convert audio recordings to text in Obsidian using multiple AI providers (OpenAI Whisper, Deepgram)",
|
||||
"main": "main.js",
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
"keywords": [
|
||||
"obsidian",
|
||||
"plugin",
|
||||
"speech-to-text",
|
||||
"speechnote",
|
||||
"whisper",
|
||||
"transcription",
|
||||
"audio",
|
||||
|
|
@ -95,4 +95,4 @@
|
|||
"prettier --write"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,8 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
|
||||
import { App, Plugin } from 'obsidian';
|
||||
import { DependencyContainer } from '../architecture/DependencyContainer';
|
||||
|
||||
|
|
@ -19,18 +23,18 @@ export class MockFactory {
|
|||
delete: jest.fn(),
|
||||
rename: jest.fn(),
|
||||
getAbstractFileByPath: jest.fn(),
|
||||
} as any,
|
||||
} as unknown as App['vault'],
|
||||
workspace: {
|
||||
getActiveViewOfType: jest.fn(),
|
||||
openLinkText: jest.fn(),
|
||||
onLayoutReady: jest.fn((callback) => callback()),
|
||||
layoutReady: true,
|
||||
activeLeaf: null as any,
|
||||
} as any,
|
||||
activeLeaf: null,
|
||||
} as unknown as App['workspace'],
|
||||
metadataCache: {
|
||||
getFileCache: jest.fn(),
|
||||
getCache: jest.fn(),
|
||||
} as any,
|
||||
} as unknown as App['metadataCache'],
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -40,7 +44,15 @@ export class MockFactory {
|
|||
static createMockPlugin(): Partial<Plugin> {
|
||||
return {
|
||||
app: MockFactory.createMockApp() as App,
|
||||
manifest: { version: '1.0.0' } as any,
|
||||
manifest: {
|
||||
version: '1.0.0',
|
||||
id: 'test',
|
||||
name: 'Test',
|
||||
author: 'Test',
|
||||
description: 'Test',
|
||||
minAppVersion: '0.0.0',
|
||||
isDesktopOnly: false,
|
||||
},
|
||||
addCommand: jest.fn(),
|
||||
addSettingTab: jest.fn(),
|
||||
addStatusBarItem: jest.fn().mockReturnValue({
|
||||
|
|
@ -59,8 +71,10 @@ export class MockFactory {
|
|||
*/
|
||||
static createMockStatusBarItem(): HTMLElement {
|
||||
const element = createEl('div');
|
||||
(element as any).setText = jest.fn();
|
||||
(element as any).remove = jest.fn();
|
||||
Object.assign(element, {
|
||||
setText: jest.fn(),
|
||||
remove: jest.fn(),
|
||||
});
|
||||
return element;
|
||||
}
|
||||
|
||||
|
|
@ -101,6 +115,7 @@ export class TestEnvironment {
|
|||
* 테스트 환경 설정
|
||||
*/
|
||||
async setup(): Promise<void> {
|
||||
await Promise.resolve(); // Ensure async for interface consistency
|
||||
// 의존성 등록
|
||||
this.container.registerInstance('App', this.mockApp);
|
||||
this.container.registerInstance('Plugin', this.mockPlugin);
|
||||
|
|
@ -142,6 +157,7 @@ export class TestEnvironment {
|
|||
* 테스트 환경 정리
|
||||
*/
|
||||
async teardown(): Promise<void> {
|
||||
await Promise.resolve(); // Ensure async for interface consistency
|
||||
this.container.dispose();
|
||||
jest.clearAllMocks();
|
||||
}
|
||||
|
|
@ -255,13 +271,14 @@ export class IntegrationTestUtils {
|
|||
* 플러그인 초기화 테스트
|
||||
*/
|
||||
static async testPluginInitialization(
|
||||
pluginClass: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
pluginClass: new (...args: any[]) => any,
|
||||
options: { expectSuccess: boolean } = { expectSuccess: true }
|
||||
): Promise<void> {
|
||||
const env = new TestEnvironment();
|
||||
await env.setup();
|
||||
|
||||
const plugin = new pluginClass();
|
||||
const plugin = new pluginClass(env.getMockApp(), env.getMockPlugin());
|
||||
plugin.app = env.getMockApp() as App;
|
||||
|
||||
if (options.expectSuccess) {
|
||||
|
|
@ -278,8 +295,11 @@ export class IntegrationTestUtils {
|
|||
* UI 컴포넌트 테스트
|
||||
*/
|
||||
static async testUIComponent(
|
||||
componentClass: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
componentClass: new (...args: any[]) => any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
setupFn?: (component: any) => void
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
): Promise<any> {
|
||||
const env = new TestEnvironment();
|
||||
await env.setup();
|
||||
|
|
@ -299,8 +319,11 @@ export class IntegrationTestUtils {
|
|||
* 서비스 테스트
|
||||
*/
|
||||
static async testService(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
serviceClass: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
dependencies: Record<string, any> = {}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
): Promise<any> {
|
||||
const env = new TestEnvironment();
|
||||
await env.setup();
|
||||
|
|
@ -310,6 +333,7 @@ export class IntegrationTestUtils {
|
|||
env.getContainer().registerInstance(key, value);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
const service = env.getContainer().resolve(serviceClass);
|
||||
|
||||
return { service, env };
|
||||
|
|
|
|||
|
|
@ -4,6 +4,11 @@
|
|||
* Toast, Modal, StatusBar, Sound 알림을 통합 관리합니다.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
|
||||
import {
|
||||
INotificationAPI,
|
||||
NotificationOptions,
|
||||
|
|
@ -28,30 +33,6 @@ interface NotificationChannel {
|
|||
dismissAll(): void;
|
||||
}
|
||||
|
||||
const createEl = (
|
||||
tag: string,
|
||||
options?: { cls?: string; text?: string; attr?: Record<string, string> }
|
||||
): HTMLElement => {
|
||||
const globalCreateEl = (globalThis as { createEl?: typeof createEl }).createEl;
|
||||
if (typeof globalCreateEl === 'function') {
|
||||
return globalCreateEl(tag, options);
|
||||
}
|
||||
|
||||
const element = document.createElement(tag);
|
||||
if (options?.cls) {
|
||||
element.className = options.cls;
|
||||
}
|
||||
if (options?.text) {
|
||||
element.textContent = options.text;
|
||||
}
|
||||
if (options?.attr) {
|
||||
Object.entries(options.attr).forEach(([key, value]) => {
|
||||
element.setAttribute(key, value);
|
||||
});
|
||||
}
|
||||
return element;
|
||||
};
|
||||
|
||||
const DOM_AVAILABLE = typeof document !== 'undefined';
|
||||
const AUDIO_AVAILABLE = typeof Audio !== 'undefined';
|
||||
|
||||
|
|
@ -60,9 +41,13 @@ class NoopChannel implements NotificationChannel {
|
|||
return Promise.resolve();
|
||||
}
|
||||
|
||||
dismiss(): void {}
|
||||
dismiss(): void {
|
||||
// No-op
|
||||
}
|
||||
|
||||
dismissAll(): void {}
|
||||
dismissAll(): void {
|
||||
// No-op
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -80,7 +65,8 @@ class PriorityQueue<T> {
|
|||
if (this.heap.length === 0) return undefined;
|
||||
|
||||
const result = this.heap[0];
|
||||
const end = this.heap.pop()!;
|
||||
const end = this.heap.pop();
|
||||
if (end === undefined) return result.item;
|
||||
|
||||
if (this.heap.length > 0) {
|
||||
this.heap[0] = end;
|
||||
|
|
@ -568,9 +554,11 @@ class StatusBarChannel implements NotificationChannel {
|
|||
const id = this.generateId();
|
||||
this.currentNotification = id;
|
||||
|
||||
this.statusBar!.className = `status-bar status-bar--${notification.type}`;
|
||||
this.statusBar!.textContent = notification.message;
|
||||
this.statusBar!.classList.add('status-bar--show');
|
||||
if (this.statusBar) {
|
||||
this.statusBar.className = `status-bar status-bar--${notification.type}`;
|
||||
this.statusBar.textContent = notification.message;
|
||||
this.statusBar.classList.add('status-bar--show');
|
||||
}
|
||||
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout);
|
||||
|
|
@ -659,7 +647,10 @@ class SoundChannel implements NotificationChannel {
|
|||
|
||||
private getAudio(url: string): Promise<HTMLAudioElement> {
|
||||
if (this.audioCache.has(url)) {
|
||||
return Promise.resolve(this.audioCache.get(url)!);
|
||||
const audioUrl = this.audioCache.get(url);
|
||||
if (audioUrl) {
|
||||
return Promise.resolve(audioUrl);
|
||||
}
|
||||
}
|
||||
|
||||
const audio = new Audio(url);
|
||||
|
|
@ -1117,8 +1108,10 @@ export class NotificationManager implements INotificationAPI {
|
|||
*/
|
||||
setSoundFile(type: NotificationType, soundUrl: string): void {
|
||||
const sound = this.channels.get('sound');
|
||||
if (sound instanceof SoundChannel) {
|
||||
sound.setSoundFile(type, soundUrl);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
if (sound && (sound as any).setSoundFile) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(sound as any).setSoundFile(type, soundUrl);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1143,8 +1136,10 @@ export class NotificationManager implements INotificationAPI {
|
|||
subscribe(event: 'dismiss', listener: (id: string) => void): Unsubscribe;
|
||||
subscribe(event: 'action', listener: (id: string, action: string) => void): Unsubscribe;
|
||||
subscribe(event: string, listener: (...args: any[]) => void): Unsubscribe {
|
||||
this.emitter.on(event, listener);
|
||||
return () => this.emitter.off(event, listener);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
this.emitter.on(event, listener as any);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return () => this.emitter.off(event, listener as any);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in a new issue