From 5890745ed2ec56e44024b8f85ac201ca57fa1fa8 Mon Sep 17 00:00:00 2001 From: sokol Date: Wed, 18 Feb 2026 17:25:50 +0300 Subject: [PATCH] fix: ConfigTemplate infinite re-render loop and add E2E test --- e2e/environment.spec.ts | 51 ++++++++++++++++++++++++ src/componets/content/ConfigTemplate.tsx | 12 +++--- src/componets/content/Content.tsx | 9 ++--- 3 files changed, 61 insertions(+), 11 deletions(-) diff --git a/e2e/environment.spec.ts b/e2e/environment.spec.ts index 31289fe..6d1d825 100644 --- a/e2e/environment.spec.ts +++ b/e2e/environment.spec.ts @@ -105,4 +105,55 @@ test.describe('Environment Management', () => { await page.waitForTimeout(200); await expect(page.locator('#environments')).toBeVisible(); }); + + test('should add params and edit template manually', async ({ page }) => { + await page.goto('/'); + await page.click('button:has-text("Create new")'); + + // Step 1: Add a param to DEFAULT + const nameInput = page.locator('input[placeholder="name"]').first(); + const valueInput = page.locator('input[placeholder="value"]').first(); + const addButton = page.locator('button.btn-success').first(); + + await nameInput.fill('host'); + await valueInput.fill('localhost:8080'); + await addButton.click(); + await page.waitForTimeout(500); + + // Add second param + await nameInput.fill('port'); + await valueInput.fill('9090'); + await addButton.click(); + await page.waitForTimeout(500); + + // Step 2: Switch to Content Template tab + await page.click('a:has-text("Content Template")'); + await page.waitForTimeout(500); + + // Verify the tab content is visible (check for Edit button) + await expect(page.locator('button:has-text("Edit")')).toBeVisible(); + + // Step 3: Click Edit button + await page.click('button:has-text("Edit")'); + await page.waitForTimeout(500); + + // Step 4: Verify textarea is visible + const textarea = page.locator('textarea'); + await expect(textarea).toBeVisible(); + + // Step 5: Edit the template manually - add a new key + await textarea.fill('{\n "!!! host": "@host@",\n "!!! port": "@port@",\n "!!! custom": "@custom@"\n}'); + await page.waitForTimeout(300); + + // Step 6: Click Save + await page.click('button:has-text("Save")'); + await page.waitForTimeout(500); + + // Step 7: Verify the template was saved (Edit button should be visible again) + await expect(page.locator('button:has-text("Edit")')).toBeVisible(); + + // Verify the content contains the new key + const pageContent = await page.content(); + expect(pageContent).toContain('!!! custom'); + }); }); diff --git a/src/componets/content/ConfigTemplate.tsx b/src/componets/content/ConfigTemplate.tsx index d0a8249..0a503d1 100644 --- a/src/componets/content/ConfigTemplate.tsx +++ b/src/componets/content/ConfigTemplate.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import Highlight from 'react-highlight'; import 'highlight.js/styles/far.css'; import { Config } from "../../models/Config"; @@ -14,10 +14,12 @@ export function ConfigTemplate(props: ConfigTemplateProps) { const [originalContent, setOriginalContent] = useState(props.config.template.content); const [jsonError, setJsonError] = useState(null); - // Sync draft when config changes (in view mode) - if (mode === 'view') { - setDraftContent(props.config.template.content); - } + // Sync draft when config changes (only in view mode) + useEffect(() => { + if (mode === 'view') { + setDraftContent(props.config.template.content); + } + }, [props.config.template.content, mode]); function handleEdit() { setOriginalContent(props.config.template.content); diff --git a/src/componets/content/Content.tsx b/src/componets/content/Content.tsx index 7628117..a7ee77a 100644 --- a/src/componets/content/Content.tsx +++ b/src/componets/content/Content.tsx @@ -12,7 +12,7 @@ export function Content(props: { config: Config, env: Env, onTemplateSaved: (new return ( <> - setTab(id)} /> + setTab(id)} selectedTab={selectTab} />
{selectTab == ContentType.Env ? () : ""} {selectTab == ContentType.Json ? () : ""} @@ -30,16 +30,13 @@ enum ContentType { Test = 3 } -function ContentTabs(props: { onSelected: (id: ContentType) => void }) { - const [selectTab, setSelect] = useState(ContentType.Env); - +function ContentTabs(props: { onSelected: (id: ContentType) => void, selectedTab: ContentType }) { function clickHandler(type: ContentType) { - setSelect(type); props.onSelected(type); } function isActive(type: ContentType): string { - return type == selectTab ? " active" : " "; + return type == props.selectedTab ? " active" : " "; } return (