diff --git a/apps/server-e2e/src/ai_settings.spec.ts b/apps/server-e2e/src/ai_settings.spec.ts deleted file mode 100644 index 2a75e8158a..0000000000 --- a/apps/server-e2e/src/ai_settings.spec.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { test, expect } from "@playwright/test"; -import App from "./support/app"; - -test.describe("AI Settings", () => { - test("Should access settings page", async ({ page, context }) => { - page.setDefaultTimeout(15_000); - - const app = new App(page, context); - await app.goto(); - - // Go to settings - await app.goToSettings(); - - // Wait for navigation to complete - await page.waitForTimeout(1000); - - // Verify we're in settings by checking for common settings elements - const settingsElements = page.locator('.note-split, .options-section, .component'); - await expect(settingsElements.first()).toBeVisible({ timeout: 10000 }); - - // Look for any content in the main area - const mainContent = page.locator('.note-split:not(.hidden-ext)'); - await expect(mainContent).toBeVisible(); - - // Basic test passes - settings are accessible - expect(true).toBe(true); - }); - - test("Should handle AI features if available", async ({ page, context }) => { - const app = new App(page, context); - await app.goto(); - - await app.goToSettings(); - - // Look for AI-related elements anywhere in settings - const aiElements = page.locator('[class*="ai-"], [data-option*="ai"], input[name*="ai"]'); - const aiElementsCount = await aiElements.count(); - - if (aiElementsCount > 0) { - // AI features are present, test basic interaction - const firstAiElement = aiElements.first(); - await expect(firstAiElement).toBeVisible(); - - // If it's a checkbox, test toggling - const elementType = await firstAiElement.getAttribute('type'); - if (elementType === 'checkbox') { - const initialState = await firstAiElement.isChecked(); - await firstAiElement.click(); - - // Wait a moment for any async operations - await page.waitForTimeout(500); - - const newState = await firstAiElement.isChecked(); - expect(newState).toBe(!initialState); - - // Restore original state - await firstAiElement.click(); - await page.waitForTimeout(500); - } - } else { - // AI features not available - this is acceptable in test environment - console.log("AI features not found in settings - this may be expected in test environment"); - } - - // Test always passes - we're just checking if AI features work when present - expect(true).toBe(true); - }); - - test("Should handle AI provider configuration if available", async ({ page, context }) => { - const app = new App(page, context); - await app.goto(); - - await app.goToSettings(); - - // Look for provider-related selects or inputs - const providerSelects = page.locator('select[class*="provider"], select[name*="provider"]'); - const apiKeyInputs = page.locator('input[type="password"][class*="api"], input[type="password"][name*="key"]'); - - const hasProviderConfig = await providerSelects.count() > 0 || await apiKeyInputs.count() > 0; - - if (hasProviderConfig) { - // Provider configuration is available - if (await providerSelects.count() > 0) { - const firstSelect = providerSelects.first(); - await expect(firstSelect).toBeVisible(); - - // Test selecting different options if available - const options = await firstSelect.locator('option').count(); - if (options > 1) { - const firstOptionValue = await firstSelect.locator('option').nth(1).getAttribute('value'); - if (firstOptionValue) { - await firstSelect.selectOption(firstOptionValue); - await expect(firstSelect).toHaveValue(firstOptionValue); - } - } - } - - if (await apiKeyInputs.count() > 0) { - const firstApiKeyInput = apiKeyInputs.first(); - await expect(firstApiKeyInput).toBeVisible(); - - // Test input functionality (without actually setting sensitive data) - await firstApiKeyInput.fill('test-key-placeholder'); - await expect(firstApiKeyInput).toHaveValue('test-key-placeholder'); - - // Clear the test value - await firstApiKeyInput.fill(''); - } - } else { - console.log("AI provider configuration not found - this may be expected in test environment"); - } - - // Test always passes - expect(true).toBe(true); - }); - - test("Should handle model configuration if available", async ({ page, context }) => { - const app = new App(page, context); - await app.goto(); - - await app.goToSettings(); - - // Look for model-related configuration - const modelSelects = page.locator('select[class*="model"], select[name*="model"]'); - const temperatureInputs = page.locator('input[name*="temperature"], input[class*="temperature"]'); - - if (await modelSelects.count() > 0) { - const firstModelSelect = modelSelects.first(); - await expect(firstModelSelect).toBeVisible(); - } - - if (await temperatureInputs.count() > 0) { - const temperatureInput = temperatureInputs.first(); - await expect(temperatureInput).toBeVisible(); - - // Test temperature setting (common AI parameter) - await temperatureInput.fill('0.7'); - await expect(temperatureInput).toHaveValue('0.7'); - } - - // Test always passes - expect(true).toBe(true); - }); - - test("Should display settings interface correctly", async ({ page, context }) => { - const app = new App(page, context); - await app.goto(); - - await app.goToSettings(); - - // Wait for navigation to complete - await page.waitForTimeout(1000); - - // Verify basic settings interface elements exist - const mainContent = page.locator('.note-split:not(.hidden-ext)'); - await expect(mainContent).toBeVisible({ timeout: 10000 }); - - // Look for common settings elements - const forms = page.locator('form, .form-group, .options-section, .component'); - const inputs = page.locator('input, select, textarea'); - const labels = page.locator('label, .form-label'); - - // Wait for content to load - await page.waitForTimeout(2000); - - // Settings should have some form elements or components - const formCount = await forms.count(); - const inputCount = await inputs.count(); - const labelCount = await labels.count(); - - // At least one of these should be present in settings - expect(formCount + inputCount + labelCount).toBeGreaterThan(0); - - // Basic UI structure test passes - expect(true).toBe(true); - }); -}); \ No newline at end of file diff --git a/apps/server/src/services/hidden_subtree.spec.ts b/apps/server/src/services/hidden_subtree.spec.ts index b6b6b284de..3596c54b27 100644 --- a/apps/server/src/services/hidden_subtree.spec.ts +++ b/apps/server/src/services/hidden_subtree.spec.ts @@ -2,6 +2,7 @@ import { deferred, LOCALES } from "@triliumnext/commons"; import { beforeAll, describe, expect, it } from "vitest"; import becca from "../becca/becca.js"; +import becca_loader from "../becca/becca_loader.js"; import branches from "./branches.js"; import cls from "./cls.js"; import hiddenSubtreeService from "./hidden_subtree.js"; @@ -172,22 +173,24 @@ describe("Hidden Subtree", () => { it("cleans up item to be deleted", async () => { const noteId = "_lbLlmChat"; let llmNote = becca.getNote(noteId); - if (!llmNote) { - llmNote = notes.createNewNote({ - parentNoteId: "_lbVisibleLaunchers", - noteId, - title: "LLM chat", - type: "launcher", - content: "" - }).note; - } - cls.init(() => hiddenSubtreeService.checkHiddenSubtree()); - expect(llmNote.isDeleted).toBeTruthy(); + cls.init(() => { + if (!llmNote) { + llmNote = notes.createNewNote({ + parentNoteId: "_lbVisibleLaunchers", + noteId, + title: "LLM chat", + type: "launcher", + content: "" + }).note; + } - // Check twice for idempotency. - cls.init(() => hiddenSubtreeService.checkHiddenSubtree()); - expect(llmNote.isDeleted).toBeTruthy(); + hiddenSubtreeService.checkHiddenSubtree(); + becca_loader.reload("test"); + }); + + llmNote = becca.getNote(noteId); + expect(llmNote).toBeFalsy(); }); }); }); diff --git a/apps/server/src/services/hidden_subtree.ts b/apps/server/src/services/hidden_subtree.ts index aae3ffa043..96ae485af6 100644 --- a/apps/server/src/services/hidden_subtree.ts +++ b/apps/server/src/services/hidden_subtree.ts @@ -4,6 +4,7 @@ import { t } from "i18next"; import becca from "../becca/becca.js"; import BAttribute from "../becca/entities/battribute.js"; import BBranch from "../becca/entities/bbranch.js"; +import BNote from "../becca/entities/bnote.js"; import buildLaunchBarConfig from "./hidden_subtree_launcherbar.js"; import buildHiddenSubtreeTemplates from "./hidden_subtree_templates.js"; import { cleanUpHelp, getHelpHiddenSubtreeData } from "./in_app_help.js"; @@ -246,7 +247,7 @@ function buildHiddenSubtreeDefinition(helpSubtree: HiddenSubtreeItem[]): HiddenS { id: "_optionsEtapi", title: t("hidden-subtree.etapi-title"), type: "contentWidget", icon: "bx-extension" }, { id: "_optionsBackup", title: t("hidden-subtree.backup-title"), type: "contentWidget", icon: "bx-data" }, { id: "_optionsSync", title: t("hidden-subtree.sync-title"), type: "contentWidget", icon: "bx-wifi" }, - + { id: "_optionsAi", title: "AI Chat", type: "contentWidget", enforceDeleted: true }, { id: "_optionsOther", title: t("hidden-subtree.other"), type: "contentWidget", icon: "bx-dots-horizontal" }, { id: "_optionsLocalization", title: t("hidden-subtree.localization"), type: "contentWidget", icon: "bx-world" }, { id: "_optionsAdvanced", title: t("hidden-subtree.advanced-title"), type: "contentWidget" } @@ -341,8 +342,13 @@ function checkHiddenSubtreeRecursively(parentNoteId: string, item: HiddenSubtree throw new Error(`ID has to start with underscore, given '${item.id}'`); } - let note = becca.notes[item.id]; - let branch; + let note = becca.notes[item.id] as BNote | undefined; + let branch: BBranch | undefined; + + if (item.enforceDeleted) { + note?.deleteNote(); + return; + } if (!note) { // Missing item, add it. @@ -363,9 +369,7 @@ function checkHiddenSubtreeRecursively(parentNoteId: string, item: HiddenSubtree note.setContent(item.content); } - if (item.enforceDeleted) { - note.deleteNote(); - } else if (item.enforceBranches || item.id.startsWith("_help")) { + if (item.enforceBranches || item.id.startsWith("_help")) { // Clean up any branches that shouldn't exist according to the meta definition // For hidden subtree notes, we want to ensure they only exist in their designated locations