diff --git a/__mocks__/obsidian.js b/__mocks__/obsidian.js index d9e0e5be..319f4b79 100644 --- a/__mocks__/obsidian.js +++ b/__mocks__/obsidian.js @@ -82,7 +82,7 @@ module.exports = { }; // Mock the global app object -global.app = { +window.app = { vault: { getAbstractFileByPath: jest.fn().mockReturnValue({ name: "test-file.md", diff --git a/eslint.config.mjs b/eslint.config.mjs index 1b7ad4e4..31c25ac5 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -103,9 +103,6 @@ export default [ // no-deprecated: defer — surface the warnings, but don't fail CI yet "@typescript-eslint/no-deprecated": "off", - // obsidianmd/no-global-this fires on jest globalThis usage in tests and - // a small number of legit cases — defer triage. - "obsidianmd/no-global-this": "off", // rule-custom-message wraps no-console; we don't enable no-console in this PR // and the codebase enforces logInfo/logWarn/logError via CLAUDE.md. "obsidianmd/rule-custom-message": "off", diff --git a/jest.setup.js b/jest.setup.js index c36b0e17..bc3f3fb2 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -1,15 +1,15 @@ import "web-streams-polyfill/dist/polyfill.min.js"; import { TextEncoder, TextDecoder } from "util"; -global.TextEncoder = TextEncoder; -global.TextDecoder = TextDecoder; +window.TextEncoder = TextEncoder; +window.TextDecoder = TextDecoder; // Polyfill Obsidian's Node.doc / Node.win augmentation so plugin code that // reads `element.doc` / `element.win` works under jsdom. if (typeof Node !== "undefined" && !Object.prototype.hasOwnProperty.call(Node.prototype, "doc")) { Object.defineProperty(Node.prototype, "doc", { get() { - return this.ownerDocument ?? global.document; + return this.ownerDocument ?? window.document; }, configurable: true, }); @@ -17,7 +17,7 @@ if (typeof Node !== "undefined" && !Object.prototype.hasOwnProperty.call(Node.pr if (typeof Node !== "undefined" && !Object.prototype.hasOwnProperty.call(Node.prototype, "win")) { Object.defineProperty(Node.prototype, "win", { get() { - return this.ownerDocument?.defaultView ?? global.window; + return this.ownerDocument?.defaultView ?? window; }, configurable: true, }); diff --git a/scripts/printPromptDebugEntry.ts b/scripts/printPromptDebugEntry.ts index f79ab885..b89e5e3f 100644 --- a/scripts/printPromptDebugEntry.ts +++ b/scripts/printPromptDebugEntry.ts @@ -94,6 +94,7 @@ export async function run(args: string[]): Promise { } const app = createHeadlessApp(); + // eslint-disable-next-line obsidianmd/no-global-this -- node-only debug script, no window available (global as unknown as { app: unknown }).app = app; initializeBuiltinTools(); diff --git a/src/LLMProviders/chainRunner/utils/imageExtraction.test.ts b/src/LLMProviders/chainRunner/utils/imageExtraction.test.ts index aa7a32bc..58ba1b5e 100644 --- a/src/LLMProviders/chainRunner/utils/imageExtraction.test.ts +++ b/src/LLMProviders/chainRunner/utils/imageExtraction.test.ts @@ -9,7 +9,7 @@ describe("Image extraction from content", () => { }, }; - (global as any).app = mockApp; + (window as any).app = mockApp; beforeEach(() => { jest.clearAllMocks(); diff --git a/src/LLMProviders/selfHostServices.test.ts b/src/LLMProviders/selfHostServices.test.ts index 6030a9e0..d4657e28 100644 --- a/src/LLMProviders/selfHostServices.test.ts +++ b/src/LLMProviders/selfHostServices.test.ts @@ -19,7 +19,7 @@ jest.mock("@/logger", () => ({ // Mock global fetch const mockFetch = jest.fn(); -global.fetch = mockFetch; +window.fetch = mockFetch; beforeEach(() => { jest.clearAllMocks(); diff --git a/src/commands/customCommandUtils.test.ts b/src/commands/customCommandUtils.test.ts index 726a28a9..3e1b3df7 100644 --- a/src/commands/customCommandUtils.test.ts +++ b/src/commands/customCommandUtils.test.ts @@ -670,7 +670,7 @@ describe("parseCustomCommandFile", () => { beforeEach(() => { // Save and mock global app - originalApp = global.app; + originalApp = window.app; mockFrontmatter = { "copilot-command-context-menu-enabled": true, "copilot-command-slash-enabled": false, @@ -679,7 +679,7 @@ describe("parseCustomCommandFile", () => { "copilot-command-last-used": 1234567890, }; mockMetadata = { frontmatter: mockFrontmatter }; - global.app = { + window.app = { vault: { read: jest .fn() @@ -699,7 +699,7 @@ describe("parseCustomCommandFile", () => { }); afterEach(() => { - global.app = originalApp; + window.app = originalApp; }); it("parses a custom command file with frontmatter and content", async () => { @@ -719,8 +719,8 @@ describe("parseCustomCommandFile", () => { }); it("uses EMPTY_COMMAND defaults if frontmatter is missing", async () => { - (global.app.vault as any).read.mockResolvedValue("Prompt content only, no frontmatter."); - (global.app.metadataCache as any).getFileCache.mockReturnValue({}); + (window.app.vault as any).read.mockResolvedValue("Prompt content only, no frontmatter."); + (window.app.metadataCache as any).getFileCache.mockReturnValue({}); const { parseCustomCommandFile } = await import("@/commands/customCommandUtils"); const result = await parseCustomCommandFile(mockFile); expect(result).toEqual({ diff --git a/src/components/chat-components/utils/lexicalTextUtils.test.ts b/src/components/chat-components/utils/lexicalTextUtils.test.ts index e567b8aa..7c9d17b9 100644 --- a/src/components/chat-components/utils/lexicalTextUtils.test.ts +++ b/src/components/chat-components/utils/lexicalTextUtils.test.ts @@ -27,7 +27,7 @@ const mockApp = { }; // Mock global app -Object.defineProperty(global, "app", { +Object.defineProperty(window, "app", { value: mockApp, writable: true, }); diff --git a/src/contextProcessor.dataview.test.ts b/src/contextProcessor.dataview.test.ts index bdb2df7a..0f4a3cb2 100644 --- a/src/contextProcessor.dataview.test.ts +++ b/src/contextProcessor.dataview.test.ts @@ -12,7 +12,7 @@ import { ContextProcessor } from "@/contextProcessor"; import { DATAVIEW_BLOCK_TAG } from "@/constants"; // Mock the global app object for Dataview plugin access -global.app = { +window.app = { plugins: { plugins: {}, }, @@ -25,7 +25,7 @@ describe("ContextProcessor - Dataview Integration", () => { beforeEach(() => { contextProcessor = ContextProcessor.getInstance(); // Reset plugins for each test - (global.app as any).plugins.plugins = {}; + (window.app as any).plugins.plugins = {}; // Save and mock console.error to suppress expected error messages originalConsoleError = console.error; console.error = jest.fn(); @@ -44,7 +44,7 @@ describe("ContextProcessor - Dataview Integration", () => { }); it("should return content unchanged when Dataview API is not available", async () => { - (global.app as any).plugins.plugins.dataview = {}; + (window.app as any).plugins.plugins.dataview = {}; const content = "```dataview\nLIST\n```"; const result = await contextProcessor.processDataviewBlocks(content, "test.md"); expect(result).toBe(content); @@ -54,7 +54,7 @@ describe("ContextProcessor - Dataview Integration", () => { describe("processDataviewBlocks - Regex Pattern Matching", () => { beforeEach(() => { // Mock successful Dataview plugin with API - (global.app as any).plugins.plugins.dataview = { + (window.app as any).plugins.plugins.dataview = { api: { query: jest.fn().mockResolvedValue({ successful: true, @@ -105,7 +105,7 @@ describe("ContextProcessor - Dataview Integration", () => { describe("processDataviewBlocks - Multiple Blocks", () => { beforeEach(() => { - (global.app as any).plugins.plugins.dataview = { + (window.app as any).plugins.plugins.dataview = { api: { query: jest .fn() @@ -172,7 +172,7 @@ LIST describe("processDataviewBlocks - Query Execution", () => { it("should include both original query and executed results", async () => { - (global.app as any).plugins.plugins.dataview = { + (window.app as any).plugins.plugins.dataview = { api: { query: jest.fn().mockResolvedValue({ successful: true, @@ -194,7 +194,7 @@ LIST }); it("should handle query timeout gracefully", async () => { - (global.app as any).plugins.plugins.dataview = { + (window.app as any).plugins.plugins.dataview = { api: { query: jest.fn().mockImplementation( () => @@ -213,7 +213,7 @@ LIST }, 10000); // Increase test timeout to 10s it("should handle query execution errors", async () => { - (global.app as any).plugins.plugins.dataview = { + (window.app as any).plugins.plugins.dataview = { api: { query: jest.fn().mockResolvedValue({ successful: false, @@ -229,7 +229,7 @@ LIST }); it("should handle dataviewjs with unsupported message", async () => { - (global.app as any).plugins.plugins.dataview = { + (window.app as any).plugins.plugins.dataview = { api: { query: jest.fn(), }, @@ -244,7 +244,7 @@ LIST describe("processDataviewBlocks - Result Formatting", () => { it("should format LIST results correctly", async () => { - (global.app as any).plugins.plugins.dataview = { + (window.app as any).plugins.plugins.dataview = { api: { query: jest.fn().mockResolvedValue({ successful: true, @@ -265,7 +265,7 @@ LIST }); it("should format TABLE results correctly", async () => { - (global.app as any).plugins.plugins.dataview = { + (window.app as any).plugins.plugins.dataview = { api: { query: jest.fn().mockResolvedValue({ successful: true, @@ -291,7 +291,7 @@ LIST }); it("should format TASK results correctly", async () => { - (global.app as any).plugins.plugins.dataview = { + (window.app as any).plugins.plugins.dataview = { api: { query: jest.fn().mockResolvedValue({ successful: true, @@ -314,7 +314,7 @@ LIST }); it("should handle empty results", async () => { - (global.app as any).plugins.plugins.dataview = { + (window.app as any).plugins.plugins.dataview = { api: { query: jest.fn().mockResolvedValue({ successful: true, @@ -333,7 +333,7 @@ LIST }); it("should handle null values gracefully", async () => { - (global.app as any).plugins.plugins.dataview = { + (window.app as any).plugins.plugins.dataview = { api: { query: jest.fn().mockResolvedValue({ successful: true, @@ -354,7 +354,7 @@ LIST }); it("should handle arrays in values", async () => { - (global.app as any).plugins.plugins.dataview = { + (window.app as any).plugins.plugins.dataview = { api: { query: jest.fn().mockResolvedValue({ successful: true, @@ -375,7 +375,7 @@ LIST describe("processDataviewBlocks - Edge Cases", () => { beforeEach(() => { - (global.app as any).plugins.plugins.dataview = { + (window.app as any).plugins.plugins.dataview = { api: { query: jest.fn().mockResolvedValue({ successful: true, diff --git a/src/contextProcessor.embeds.test.ts b/src/contextProcessor.embeds.test.ts index 9da915bb..8fb83e92 100644 --- a/src/contextProcessor.embeds.test.ts +++ b/src/contextProcessor.embeds.test.ts @@ -39,7 +39,7 @@ describe("ContextProcessor - Embedded Notes", () => { getFileCache: jest.fn((file: TFile) => fileCaches[file.path] ?? {}), }; - (global as any).app = { + (window as any).app = { metadataCache: metadataCacheMock, }; diff --git a/src/encryptionService.test.ts b/src/encryptionService.test.ts index d9e42257..cb455075 100644 --- a/src/encryptionService.test.ts +++ b/src/encryptionService.test.ts @@ -15,8 +15,8 @@ const mockElectron = { jest.mock("electron", () => mockElectron); -global.TextEncoder = TextEncoder as any; -global.TextDecoder = TextDecoder as any; +window.TextEncoder = TextEncoder as any; +window.TextDecoder = TextDecoder as any; // Now we can import our modules import { encryptAllKeys, getDecryptedKey, getEncryptedKey } from "@/encryptionService"; @@ -25,8 +25,8 @@ import { Platform } from "obsidian"; import { Buffer } from "buffer"; // Mock window.btoa and window.atob for base64 encoding/decoding -global.btoa = jest.fn().mockImplementation((str) => Buffer.from(str).toString("base64")); -global.atob = jest.fn().mockImplementation((str) => Buffer.from(str, "base64").toString()); +window.btoa = jest.fn().mockImplementation((str) => Buffer.from(str).toString("base64")); +window.atob = jest.fn().mockImplementation((str) => Buffer.from(str, "base64").toString()); const mockSubtle = { importKey: jest.fn().mockResolvedValue("mockCryptoKey"), @@ -43,7 +43,7 @@ const mockSubtle = { }; // Mock crypto.subtle instead of the entire crypto object -Object.defineProperty(global.crypto, "subtle", { +Object.defineProperty(window.crypto, "subtle", { value: mockSubtle, configurable: true, }); diff --git a/src/projects/projectUtils.test.ts b/src/projects/projectUtils.test.ts index 131a5c71..f494c84d 100644 --- a/src/projects/projectUtils.test.ts +++ b/src/projects/projectUtils.test.ts @@ -35,7 +35,7 @@ function makeMockFile(path: string): TFile { // Helper: set up the global `app` mock used by parseProjectConfigFile function setupAppMock(rawContent: string, frontmatter: Record | null) { - (global as Record).app = { + (window as unknown as Record).app = { vault: { read: jest.fn().mockResolvedValue(rawContent), // Reason: parseProjectConfigFile uses `cachedFile instanceof TFile` to detect synthetic TFiles. diff --git a/src/search/findRelevantNotes.test.ts b/src/search/findRelevantNotes.test.ts index 8b5a5662..4eef4888 100644 --- a/src/search/findRelevantNotes.test.ts +++ b/src/search/findRelevantNotes.test.ts @@ -133,7 +133,7 @@ describe("findRelevantNotes", () => { ["linked-only.md", linkedOnly], ]); - (global.app.vault.getAbstractFileByPath as jest.Mock).mockImplementation((path: string) => { + (window.app.vault.getAbstractFileByPath as jest.Mock).mockImplementation((path: string) => { return filesByPath.get(path) ?? null; }); diff --git a/src/search/searchUtils.test.ts b/src/search/searchUtils.test.ts index 483dde8f..38bcd0a6 100644 --- a/src/search/searchUtils.test.ts +++ b/src/search/searchUtils.test.ts @@ -68,12 +68,12 @@ jest.mock("@/settings/model", () => ({ describe("searchUtils", () => { beforeAll(() => { // @ts-ignore - global.app = mockApp; + window.app = mockApp; }); afterAll(() => { // @ts-ignore - delete global.app; + delete window.app; }); beforeEach(() => { diff --git a/src/system-prompts/migration.test.ts b/src/system-prompts/migration.test.ts index 41ef2949..2adf8d11 100644 --- a/src/system-prompts/migration.test.ts +++ b/src/system-prompts/migration.test.ts @@ -75,14 +75,14 @@ describe("migrateSystemPromptsFromSettings", () => { } as unknown as Vault; // Mock global app - originalApp = global.app; - global.app = { + originalApp = window.app; + window.app = { vault: mockVault, } as any; }); afterEach(() => { - global.app = originalApp; + window.app = originalApp; }); it("skips migration when userSystemPrompt is empty", async () => { diff --git a/src/system-prompts/systemPromptManager.test.ts b/src/system-prompts/systemPromptManager.test.ts index fbc4da8b..5cbd7d76 100644 --- a/src/system-prompts/systemPromptManager.test.ts +++ b/src/system-prompts/systemPromptManager.test.ts @@ -66,8 +66,8 @@ describe("SystemPromptManager", () => { } as unknown as Vault; // Mock global app - originalApp = global.app; - global.app = { + originalApp = window.app; + window.app = { vault: mockVault, fileManager: { processFrontMatter: jest.fn(), @@ -81,7 +81,7 @@ describe("SystemPromptManager", () => { }); afterEach(() => { - global.app = originalApp; + window.app = originalApp; }); describe("getInstance", () => { diff --git a/src/system-prompts/systemPromptUtils.test.ts b/src/system-prompts/systemPromptUtils.test.ts index e5eea6eb..b0bc930b 100644 --- a/src/system-prompts/systemPromptUtils.test.ts +++ b/src/system-prompts/systemPromptUtils.test.ts @@ -250,14 +250,14 @@ describe("parseSystemPromptFile", () => { let mockFile: TFile; beforeEach(() => { - originalApp = global.app; + originalApp = window.app; mockFile = mockTFile({ basename: "Test Prompt", path: "SystemPrompts/Test Prompt.md", extension: "md", }); - global.app = { + window.app = { vault: { read: jest.fn(), }, @@ -268,7 +268,7 @@ describe("parseSystemPromptFile", () => { }); afterEach(() => { - global.app = originalApp; + window.app = originalApp; }); it("parses a file with frontmatter and content", async () => { diff --git a/src/tests/projectContextCache.test.ts b/src/tests/projectContextCache.test.ts index c50fd394..f5ce6d6d 100644 --- a/src/tests/projectContextCache.test.ts +++ b/src/tests/projectContextCache.test.ts @@ -127,7 +127,7 @@ describe("ProjectContextCache", () => { jest.clearAllMocks(); // Mock globals - global.app = mockApp as any; + window.app = mockApp as any; // Set up mock vault file retrieval mockApp.vault.getFiles.mockReturnValue([mockMarkdownFile, mockPdfFile]); diff --git a/src/tools/ReadNoteTool.test.ts b/src/tools/ReadNoteTool.test.ts index 858492f2..c18bd5dd 100644 --- a/src/tools/ReadNoteTool.test.ts +++ b/src/tools/ReadNoteTool.test.ts @@ -35,14 +35,14 @@ describe("readNoteTool", () => { beforeEach(async () => { jest.resetModules(); - originalApp = global.app; + originalApp = window.app; getAbstractFileByPathMock = jest.fn(); getMarkdownFilesMock = jest.fn().mockReturnValue([]); getFirstLinkpathDestMock = jest.fn().mockReturnValue(null); mockRead.mockReset(); mockRead.mockResolvedValue(""); - global.app = { + window.app = { vault: { getAbstractFileByPath: getAbstractFileByPathMock, getMarkdownFiles: getMarkdownFilesMock, @@ -62,7 +62,7 @@ describe("readNoteTool", () => { }); afterEach(() => { - global.app = originalApp; + window.app = originalApp; }); it("returns the first chunk with follow-up metadata", async () => { diff --git a/src/utils.test.ts b/src/utils.test.ts index 13edc895..9b027630 100644 --- a/src/utils.test.ts +++ b/src/utils.test.ts @@ -265,7 +265,7 @@ describe("getNotesFromPath", () => { describe("getNotesFromTags", () => { beforeAll(() => { // @ts-ignore - global.app = mockApp; + window.app = mockApp; // Setup metadata cache mock mockMetadataCache.getFileCache.mockImplementation((file: TFile) => { @@ -275,7 +275,7 @@ describe("getNotesFromTags", () => { afterAll(() => { // @ts-ignore - delete global.app; + delete window.app; }); beforeEach(() => {