From 7584ec847051e769b73f43f4617a8ff57f882de8 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Mon, 22 Apr 2024 13:59:17 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=A8=20test:=20#107=20Working=20simple?= =?UTF-8?q?=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __mocks__/obsidian.ts | 11 ++ __mocks__/obsidian/App.ts | 25 +++++ __mocks__/obsidian/Modal.ts | 34 ++++++ __mocks__/obsidian/Plugin.ts | 127 ++++++++++++++++++++++ __mocks__/obsidian/PluginSettingTab.ts | 17 +++ __mocks__/queue.ts | 81 ++++++++++++++ eslint.config.mjs | 11 +- jest.config.mjs | 4 +- package-lock.json | 141 +++++++++++++++++++++++++ package.json | 1 + src/plugin.spec.ts | 16 ++- src/settings/types.ts | 2 +- tsconfig.json | 3 +- 13 files changed, 466 insertions(+), 7 deletions(-) create mode 100644 __mocks__/obsidian.ts create mode 100644 __mocks__/obsidian/App.ts create mode 100644 __mocks__/obsidian/Modal.ts create mode 100644 __mocks__/obsidian/Plugin.ts create mode 100644 __mocks__/obsidian/PluginSettingTab.ts create mode 100644 __mocks__/queue.ts diff --git a/__mocks__/obsidian.ts b/__mocks__/obsidian.ts new file mode 100644 index 0000000..d3b833c --- /dev/null +++ b/__mocks__/obsidian.ts @@ -0,0 +1,11 @@ +import { AppMock } from "./obsidian/App"; +import { PluginMock } from "./obsidian/Plugin"; +import { ModalMock } from "./obsidian/Modal"; +import { PluginSettingTabMock } from "./obsidian/PluginSettingTab"; + +module.exports = { + App: AppMock, + Plugin: PluginMock, + Modal: ModalMock, + PluginSettingTab: PluginSettingTabMock, +}; diff --git a/__mocks__/obsidian/App.ts b/__mocks__/obsidian/App.ts new file mode 100644 index 0000000..792f353 --- /dev/null +++ b/__mocks__/obsidian/App.ts @@ -0,0 +1,25 @@ +import type { App, FileManager, Keymap, MetadataCache, Scope, UserEvent, Vault, Workspace } from "obsidian"; + +export class AppMock implements App { + get keymap(): Keymap { + throw new Error("Not implemented."); + } + get scope(): Scope { + throw new Error("Not implemented."); + } + get workspace(): Workspace { + throw new Error("Not implemented."); + } + get vault(): Vault { + throw new Error("Not implemented."); + } + get metadataCache(): MetadataCache { + throw new Error("Not implemented."); + } + get fileManager(): FileManager { + throw new Error("Not implemented."); + } + get lastEvent(): UserEvent | null { + throw new Error("Not implemented."); + } +} diff --git a/__mocks__/obsidian/Modal.ts b/__mocks__/obsidian/Modal.ts new file mode 100644 index 0000000..cf6e54b --- /dev/null +++ b/__mocks__/obsidian/Modal.ts @@ -0,0 +1,34 @@ +import type { App, Scope } from "obsidian"; +import type { Modal } from "obsidian"; + +export class ModalMock implements Modal { + constructor(public app: App) {} + get scope(): Scope { + throw new Error("Not implemented."); + } + get containerEl(): HTMLElement { + throw new Error("Not implemented."); + } + get modalEl(): HTMLElement { + throw new Error("Not implemented."); + } + get titleEl(): HTMLElement { + throw new Error("Not implemented."); + } + get contentEl(): HTMLElement { + throw new Error("Not implemented."); + } + shouldRestoreSelection: boolean = false; + open(): void { + throw new Error("Method not implemented."); + } + close(): void { + throw new Error("Method not implemented."); + } + onOpen(): void { + throw new Error("Method not implemented."); + } + onClose(): void { + throw new Error("Method not implemented."); + } +} diff --git a/__mocks__/obsidian/Plugin.ts b/__mocks__/obsidian/Plugin.ts new file mode 100644 index 0000000..e08a0e8 --- /dev/null +++ b/__mocks__/obsidian/Plugin.ts @@ -0,0 +1,127 @@ +/* eslint-disable unused-imports/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import type { Extension } from "@codemirror/state"; +import type { Editor } from "codemirror"; +import type { + App, + Command, + Component, + EditorSuggest, + EventRef, + HoverLinkSource, + MarkdownPostProcessor, + MarkdownPostProcessorContext, + ObsidianProtocolHandler, + Plugin, + PluginManifest, + PluginSettingTab, + ViewCreator, +} from "obsidian"; + +export class PluginMock implements Plugin { + constructor( + public app: App, + public manifest: PluginManifest, + ) {} + + addRibbonIcon(icon: string, title: string, callback: (evt: MouseEvent) => any): HTMLElement { + throw new Error("Method not implemented."); + } + addStatusBarItem(): HTMLElement { + throw new Error("Method not implemented."); + } + addCommand(command: Command): Command { + throw new Error("Method not implemented."); + } + addSettingTab(settingTab: PluginSettingTab): void { + throw new Error("Method not implemented."); + } + registerView(type: string, viewCreator: ViewCreator): void { + throw new Error("Method not implemented."); + } + registerHoverLinkSource(id: string, info: HoverLinkSource): void { + throw new Error("Method not implemented."); + } + registerExtensions(extensions: string[], viewType: string): void { + throw new Error("Method not implemented."); + } + registerMarkdownPostProcessor( + postProcessor: MarkdownPostProcessor, + sortOrder?: number | undefined, + ): MarkdownPostProcessor { + throw new Error("Method not implemented."); + } + registerMarkdownCodeBlockProcessor( + language: string, + handler: (source: string, el: HTMLElement, ctx: MarkdownPostProcessorContext) => void | Promise, + sortOrder?: number | undefined, + ): MarkdownPostProcessor { + throw new Error("Method not implemented."); + } + registerCodeMirror(callback: (cm: Editor) => any): void { + throw new Error("Method not implemented."); + } + registerEditorExtension(extension: Extension): void { + throw new Error("Method not implemented."); + } + registerObsidianProtocolHandler(action: string, handler: ObsidianProtocolHandler): void { + throw new Error("Method not implemented."); + } + registerEditorSuggest(editorSuggest: EditorSuggest): void { + throw new Error("Method not implemented."); + } + loadData(): Promise { + throw new Error("Method not implemented."); + } + saveData(data: any): Promise { + throw new Error("Method not implemented."); + } + load(): void { + throw new Error("Method not implemented."); + } + onload(): void { + throw new Error("Method not implemented."); + } + unload(): void { + throw new Error("Method not implemented."); + } + onunload(): void { + throw new Error("Method not implemented."); + } + addChild(component: T): T { + throw new Error("Method not implemented."); + } + removeChild(component: T): T { + throw new Error("Method not implemented."); + } + register(cb: () => any): void { + throw new Error("Method not implemented."); + } + registerEvent(eventRef: EventRef): void { + throw new Error("Method not implemented."); + } + registerDomEvent( + el: Window, + type: K, + callback: (this: HTMLElement, ev: WindowEventMap[K]) => any, + options?: boolean | AddEventListenerOptions | undefined, + ): void; + registerDomEvent( + el: Document, + type: K, + callback: (this: HTMLElement, ev: DocumentEventMap[K]) => any, + options?: boolean | AddEventListenerOptions | undefined, + ): void; + registerDomEvent( + el: HTMLElement, + type: K, + callback: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, + options?: boolean | AddEventListenerOptions | undefined, + ): void; + registerDomEvent(el: unknown, type: unknown, callback: unknown, options?: unknown): void { + throw new Error("Method not implemented."); + } + registerInterval(id: number): number { + throw new Error("Method not implemented."); + } +} diff --git a/__mocks__/obsidian/PluginSettingTab.ts b/__mocks__/obsidian/PluginSettingTab.ts new file mode 100644 index 0000000..90ce54e --- /dev/null +++ b/__mocks__/obsidian/PluginSettingTab.ts @@ -0,0 +1,17 @@ +import type { App, PluginSettingTab, Plugin } from "obsidian"; + +export class PluginSettingTabMock implements PluginSettingTab { + constructor( + public app: App, + public plugin: Plugin, + ) {} + get containerEl(): HTMLElement { + throw new Error("Not implemented."); + } + display() { + throw new Error("Method not implemented."); + } + hide() { + throw new Error("Method not implemented."); + } +} diff --git a/__mocks__/queue.ts b/__mocks__/queue.ts new file mode 100644 index 0000000..eb577eb --- /dev/null +++ b/__mocks__/queue.ts @@ -0,0 +1,81 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import type { EventListenerOrEventListenerObject, EventsMap, QueueEvent, QueueWorker } from "queue"; +import type Queue from "queue"; +import type { Options } from "queue"; + +export default class QueueMock implements Queue { + concurrency: number; + timeout: number; + autostart: boolean; + results: any[] | null; + length: number = 0; + + constructor(options: Options) { + this.concurrency = options.concurrency ?? 1; + this.timeout = options.timeout ?? -1; + this.autostart = options.autostart ?? false; + this.results = options.results ?? []; + } + + push(...workers: QueueWorker[]): number { + for (const worker of workers) { + void worker(); + } + return 0; + } + unshift(...workers: QueueWorker[]): number { + throw new Error("Method not implemented."); + } + splice(start: number, deleteCount?: number | undefined): Queue; + splice(start: number, deleteCount: number, ...workers: QueueWorker[]): Queue; + splice(start: unknown, deleteCount?: unknown, ...rest: unknown[]): Queue { + throw new Error("Method not implemented."); + } + pop(): QueueWorker | undefined { + throw new Error("Method not implemented."); + } + shift(): QueueWorker | undefined { + throw new Error("Method not implemented."); + } + slice(start?: number | undefined, end?: number | undefined): Queue { + throw new Error("Method not implemented."); + } + reverse(): Queue { + throw new Error("Method not implemented."); + } + indexOf(searchElement: QueueWorker, fromIndex?: number | undefined): number { + throw new Error("Method not implemented."); + } + lastIndexOf(searchElement: QueueWorker, fromIndex?: number | undefined): number { + throw new Error("Method not implemented."); + } + start(callback: (error?: Error | undefined, results?: any[] | null | undefined) => void): void; + start(): Promise<{ error?: Error | undefined; results?: any[] | null | undefined }>; + start(): void; + start(callback?: unknown): void | Promise<{ error?: Error | undefined; results?: any[] | null | undefined }> { + throw new Error("Method not implemented."); + } + stop(): void { + throw new Error("Method not implemented."); + } + end(error?: Error | undefined): void { + throw new Error("Method not implemented."); + } + addEventListener( + name: Event, + callback: EventListenerOrEventListenerObject>, + options?: boolean | AddEventListenerOptions | undefined, + ): void { + throw new Error("Method not implemented."); + } + dispatchEvent(event: QueueEvent): boolean { + throw new Error("Method not implemented."); + } + removeEventListener( + name: Event, + callback: EventListenerOrEventListenerObject>, + options?: boolean | EventListenerOptions | undefined, + ): void { + throw new Error("Method not implemented."); + } +} diff --git a/eslint.config.mjs b/eslint.config.mjs index 9595e3d..eb12a00 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -4,10 +4,11 @@ import eslint from "@eslint/js"; import tseslint from "typescript-eslint"; import UnusedImportsPlugin from "eslint-plugin-unused-imports"; import PrettierConfig from "eslint-config-prettier"; +import JestPlugin from "eslint-plugin-jest"; export default tseslint.config( { - ignores: ["node_modules/", "main.js", "eslint.config.mjs"], + ignores: ["node_modules/", "main.js", "esbuild.config.mjs", "eslint.config.mjs", "jest.config.mjs"], }, eslint.configs.recommended, tseslint.configs.eslintRecommended, @@ -21,6 +22,7 @@ export default tseslint.config( }, plugins: { "unused-imports": UnusedImportsPlugin, + jest: JestPlugin, }, rules: { "@typescript-eslint/restrict-template-expressions": "warn", @@ -48,4 +50,11 @@ export default tseslint.config( }, }, PrettierConfig, + { + files: ["**/__tests__/**/*.[jt]s?(x)", "**/?(*.)+(spec|test).[tj]s?(x)"], + ...JestPlugin.configs["flat/recommended"], + rules: { + "@typescript-eslint/no-floating-promises": "off", + }, + }, ); diff --git a/jest.config.mjs b/jest.config.mjs index c1cf9a4..f35d323 100644 --- a/jest.config.mjs +++ b/jest.config.mjs @@ -88,7 +88,9 @@ const config = { // ], // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module - // moduleNameMapper: {}, + moduleNameMapper: { + "src/(.*)": "/src/$1", + }, // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader // modulePathIgnorePatterns: [], diff --git a/package-lock.json b/package-lock.json index 9d95fd5..1c64876 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "esbuild": "0.20.0", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", + "eslint-plugin-jest": "^28.2.0", "eslint-plugin-unused-imports": "^3.1.0", "jest": "^29.7.0", "obsidian": "latest", @@ -3179,6 +3180,146 @@ "eslint": ">=7.0.0" } }, + "node_modules/eslint-plugin-jest": { + "version": "28.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.2.0.tgz", + "integrity": "sha512-yRDti/a+f+SMSmNTiT9/M/MzXGkitl8CfzUxnpoQcTyfq8gUrXMriVcWU36W1X6BZSUoyUCJrDAWWUA2N4hE5g==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "^6.0.0" + }, + "engines": { + "node": "^16.10.0 || ^18.12.0 || >=20.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^6.0.0 || ^7.0.0", + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0", + "jest": "*" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-jest/node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-plugin-jest/node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-plugin-jest/node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-jest/node_modules/@typescript-eslint/utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-plugin-jest/node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-plugin-jest/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/eslint-plugin-unused-imports": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-3.1.0.tgz", diff --git a/package.json b/package.json index e56fa84..9235259 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "esbuild": "0.20.0", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", + "eslint-plugin-jest": "^28.2.0", "eslint-plugin-unused-imports": "^3.1.0", "jest": "^29.7.0", "obsidian": "latest", diff --git a/src/plugin.spec.ts b/src/plugin.spec.ts index 530d43f..b539e81 100644 --- a/src/plugin.spec.ts +++ b/src/plugin.spec.ts @@ -1,5 +1,15 @@ -import { expect, test } from "@jest/globals"; +import { expect, jest, test } from "@jest/globals"; +import { GithubLinkPlugin } from "./plugin"; +import { describe } from "node:test"; +import { App } from "obsidian"; +import * as manifest from "../manifest.json"; -test("A test!", () => { - expect("a").toEqual("a"); +jest.mock("./settings/settings-tab"); + +describe("GithubLinkPlugin", () => { + test("Should create", () => { + const app = new App(); + const plugin = new GithubLinkPlugin(app, manifest); + expect(plugin).toBeTruthy(); + }); }); diff --git a/src/settings/types.ts b/src/settings/types.ts index 0f7f4b5..0d89dbb 100644 --- a/src/settings/types.ts +++ b/src/settings/types.ts @@ -1,4 +1,4 @@ -import { LogLevel } from "src/logger"; +import { LogLevel } from "../logger"; export interface GithubAccount { id: string; diff --git a/tsconfig.json b/tsconfig.json index 453d0a8..fa59e7b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,7 +12,8 @@ "isolatedModules": true, "strict": true, "strictNullChecks": true, - "lib": ["DOM", "ES5", "ES6", "ES7"] + "lib": ["DOM", "ES5", "ES6", "ES7"], + "resolveJsonModule": true }, "include": ["**/*.ts"] }