From 345f1a0f714accdb285c8b55103953790e8a8f21 Mon Sep 17 00:00:00 2001 From: RemiBonnet Date: Thu, 9 Apr 2026 15:43:45 +0200 Subject: [PATCH 1/5] feat(variable-modal): implement variable value editor modal and associated tests --- .../create-update-variable-modal.spec.tsx | 110 ++++++++++++ .../create-update-variable-modal.tsx | 87 +++++++--- .../variable-value-editor-modal.spec.tsx | 130 +++++++++++++++ .../variable-value-editor-modal.tsx | 157 ++++++++++++++++++ 4 files changed, 459 insertions(+), 25 deletions(-) create mode 100644 libs/domains/variables/feature/src/lib/create-update-variable-modal/create-update-variable-modal.spec.tsx create mode 100644 libs/domains/variables/feature/src/lib/create-update-variable-modal/variable-value-editor-modal/variable-value-editor-modal.spec.tsx create mode 100644 libs/domains/variables/feature/src/lib/create-update-variable-modal/variable-value-editor-modal/variable-value-editor-modal.tsx diff --git a/libs/domains/variables/feature/src/lib/create-update-variable-modal/create-update-variable-modal.spec.tsx b/libs/domains/variables/feature/src/lib/create-update-variable-modal/create-update-variable-modal.spec.tsx new file mode 100644 index 00000000000..8a928feb39d --- /dev/null +++ b/libs/domains/variables/feature/src/lib/create-update-variable-modal/create-update-variable-modal.spec.tsx @@ -0,0 +1,110 @@ +import { type VariableResponse } from 'qovery-typescript-axios' +import { type ReactNode } from 'react' +import { renderWithProviders, screen } from '@qovery/shared/util-tests' +import { CreateUpdateVariableModal } from './create-update-variable-modal' + +jest.mock('@qovery/shared/ui', () => { + const actual = jest.requireActual('@qovery/shared/ui') + + return { + ...actual, + useModal: () => ({ + enableAlertClickOutside: jest.fn(), + }), + } +}) + +jest.mock('../dropdown-variable/dropdown-variable', () => ({ + __esModule: true, + default: ({ children }: { children: ReactNode }) => children, +})) + +jest.mock('./variable-value-editor-modal/variable-value-editor-modal', () => ({ + VariableValueEditorModal: ({ open }: { open: boolean }) => + open ?
: null, + getValueEditorLanguage: jest.fn(() => 'plaintext'), + isVariableValueEditorModalScope: jest.fn((scope: string | undefined) => + ['APPLICATION', 'CONTAINER', 'JOB', 'HELM', 'TERRAFORM'].includes(scope ?? '') + ), +})) + +jest.mock('../hooks/use-create-variable/use-create-variable', () => ({ + useCreateVariable: () => ({ + mutateAsync: jest.fn(), + }), +})) + +jest.mock('../hooks/use-create-variable-alias/use-create-variable-alias', () => ({ + useCreateVariableAlias: () => ({ + mutateAsync: jest.fn(), + }), +})) + +jest.mock('../hooks/use-create-variable-override/use-create-variable-override', () => ({ + useCreateVariableOverride: () => ({ + mutateAsync: jest.fn(), + }), +})) + +jest.mock('../hooks/use-edit-variable/use-edit-variable', () => ({ + useEditVariable: () => ({ + mutateAsync: jest.fn(), + }), +})) + +const closeModal = jest.fn() + +const baseProps = { + closeModal, + scope: 'APPLICATION' as const, + projectId: 'project-id', + environmentId: 'environment-id', + serviceId: 'service-id', +} + +const baseVariable = { + id: 'variable-id', + created_at: '2024-04-10T09:56:19.908145Z', + updated_at: '2024-04-10T09:56:19.908145Z', + key: 'MY_VARIABLE', + value: 'initial value', + mount_path: null, + scope: 'APPLICATION', + overridden_variable: null, + aliased_variable: null, + variable_type: 'VALUE', + variable_kind: 'Public', + service_id: 'service-id', + service_name: 'service-name', + service_type: 'APPLICATION', + owned_by: 'QOVERY', + is_secret: false, +} as VariableResponse + +describe('CreateUpdateVariableModal', () => { + beforeEach(() => { + closeModal.mockReset() + }) + + it('should render the open editor button when the value field is available', () => { + renderWithProviders() + + expect(screen.getByRole('button', { name: /open editor/i })).toBeInTheDocument() + }) + + it('should not render the open editor button for aliases', () => { + renderWithProviders() + + expect(screen.queryByRole('button', { name: /open editor/i })).not.toBeInTheDocument() + }) + + it('should open the fullscreen editor modal when clicking the button', async () => { + const { userEvent } = renderWithProviders( + + ) + + await userEvent.click(screen.getByRole('button', { name: /open editor/i })) + + expect(screen.getByTestId('mock-value-editor-modal')).toBeInTheDocument() + }) +}) diff --git a/libs/domains/variables/feature/src/lib/create-update-variable-modal/create-update-variable-modal.tsx b/libs/domains/variables/feature/src/lib/create-update-variable-modal/create-update-variable-modal.tsx index 5c81bcdc071..90b5aa12be7 100644 --- a/libs/domains/variables/feature/src/lib/create-update-variable-modal/create-update-variable-modal.tsx +++ b/libs/domains/variables/feature/src/lib/create-update-variable-modal/create-update-variable-modal.tsx @@ -25,6 +25,11 @@ import { useCreateVariableAlias } from '../hooks/use-create-variable-alias/use-c import { useCreateVariableOverride } from '../hooks/use-create-variable-override/use-create-variable-override' import { useCreateVariable } from '../hooks/use-create-variable/use-create-variable' import { useEditVariable } from '../hooks/use-edit-variable/use-edit-variable' +import { + VariableValueEditorModal, + getValueEditorLanguage, + isVariableValueEditorModalScope, +} from './variable-value-editor-modal/variable-value-editor-modal' type Scope = Exclude @@ -58,6 +63,7 @@ export function CreateUpdateVariableModal(props: CreateUpdateVariableModalProps) const _isFile = (variable && environmentVariableFile(variable)) || (isFile ?? false) const { enableAlertClickOutside } = useModal() const [loading, setLoading] = useState(false) + const [isValueEditorOpen, setIsValueEditorOpen] = useState(false) const { mutateAsync: createVariable } = useCreateVariable() const { mutateAsync: createVariableAlias } = useCreateVariableAlias() @@ -128,6 +134,11 @@ export function CreateUpdateVariableModal(props: CreateUpdateVariableModalProps) methods.watch(() => enableAlertClickOutside(methods.formState.isDirty)) const watchScope = methods.watch('scope') + const watchMountPath = methods.watch('mountPath') + const valueEditorLanguage = getValueEditorLanguage({ isFile: _isFile, mountPath: watchMountPath }) + const valueEditorServiceId = + 'serviceId' in props && isVariableValueEditorModalScope(watchScope) ? props.serviceId : undefined + const valueEditorScope = isVariableValueEditorModalScope(watchScope) ? watchScope : undefined const _onSubmit = methods.handleSubmit(async (data) => { const cloneData = { ...data } @@ -375,33 +386,59 @@ export function CreateUpdateVariableModal(props: CreateUpdateVariableModalProps) name="value" control={methods.control} render={({ field: { name, onChange, value }, fieldState: { error } }) => ( -
- - {'environmentId' in props && ( - handleInsertVariable({ variableKey, value: value || '', onChange })} + <> +
+ +
+
+ + {'environmentId' in props && ( + handleInsertVariable({ variableKey, value: value || '', onChange })} > - - - - )} -
+ +
+ )} +
+ + )} /> )} diff --git a/libs/domains/variables/feature/src/lib/create-update-variable-modal/variable-value-editor-modal/variable-value-editor-modal.spec.tsx b/libs/domains/variables/feature/src/lib/create-update-variable-modal/variable-value-editor-modal/variable-value-editor-modal.spec.tsx new file mode 100644 index 00000000000..829bd60afb3 --- /dev/null +++ b/libs/domains/variables/feature/src/lib/create-update-variable-modal/variable-value-editor-modal/variable-value-editor-modal.spec.tsx @@ -0,0 +1,130 @@ +import { renderWithProviders, screen, within } from '@qovery/shared/util-tests' +import { VariableValueEditorModal, getValueEditorLanguage } from './variable-value-editor-modal' + +jest.mock('@qovery/shared/ui', () => { + const actual = jest.requireActual('@qovery/shared/ui') + + return { + ...actual, + CodeEditor: ({ value, onChange }: { value?: string | null; onChange?: (value: string) => void }) => ( +