From 11d2cba3924113cb66ce3694d85c3fc97f29baf5 Mon Sep 17 00:00:00 2001 From: 1AhmedYasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Fri, 17 Jul 2026 00:01:43 +0300 Subject: [PATCH 1/2] fix(1099): Fixed service redirect --- .../Flow/NodeTypes/StepNode.test.tsx | 30 +++++++++++++++++++ GUI/src/i18n/en/common.json | 3 +- GUI/src/i18n/et/common.json | 3 +- GUI/src/utils/service-navigation-utils.ts | 7 +++++ 4 files changed, 41 insertions(+), 2 deletions(-) diff --git a/GUI/src/components/Flow/NodeTypes/StepNode.test.tsx b/GUI/src/components/Flow/NodeTypes/StepNode.test.tsx index b9864ad6..20bb95e5 100644 --- a/GUI/src/components/Flow/NodeTypes/StepNode.test.tsx +++ b/GUI/src/components/Flow/NodeTypes/StepNode.test.tsx @@ -1,6 +1,7 @@ import { fireEvent, render, screen } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import useServiceStore from 'store/new-services.store'; +import useToastStore from 'store/toasts.store'; import { StepType } from 'types'; import { NodeDataProps } from 'types/service-flow'; import { beforeEach, describe, expect, it, vi } from 'vitest'; @@ -11,6 +12,15 @@ vi.mock('react-i18next', () => ({ useTranslation: () => ({ t: (key: string) => key }), })); +vi.mock('i18next', () => { + const mockI18n = { + use: vi.fn().mockReturnThis(), + init: vi.fn().mockResolvedValue(undefined), + t: (key: string) => key, + }; + return { default: mockI18n, t: mockI18n.t }; +}); + const mockNavigate = vi.fn(); vi.mock('react-router-dom', async () => { const actual = await vi.importActual('react-router-dom'); @@ -33,6 +43,26 @@ describe('StepNode jump-to-service navigation', () => { beforeEach(() => { mockNavigate.mockClear(); useServiceStore.setState({ hasUnsavedChanges: false, nextLocation: null }); + useToastStore.setState({ toasts: [] }); + }); + + it('does not navigate away and shows a toast when the node references the currently open service (self-loop)', () => { + useServiceStore.setState({ serviceId: 'other-service-id', hasUnsavedChanges: false, nextLocation: null }); + const resetStateSpy = vi.spyOn(useServiceStore.getState(), 'resetState'); + + render( + + + , + ); + + fireEvent.click(screen.getByText(/Other Service/)); + + expect(mockNavigate).not.toHaveBeenCalled(); + expect(resetStateSpy).not.toHaveBeenCalled(); + expect(useServiceStore.getState().nextLocation).toBeNull(); + expect(useToastStore.getState().toasts).toHaveLength(1); + expect(useToastStore.getState().toasts[0].title).toBe('serviceFlow.element.jumpToService.alreadyOnService'); }); it('navigates directly to the target service when there are no unsaved changes', () => { diff --git a/GUI/src/i18n/en/common.json b/GUI/src/i18n/en/common.json index 365c5a70..fbc02e04 100644 --- a/GUI/src/i18n/en/common.json +++ b/GUI/src/i18n/en/common.json @@ -370,7 +370,8 @@ "selectService": "Select a service...", "noActiveServices": "No active services available", "parameters": "Parameters", - "navigateToService": "Open this service" + "navigateToService": "Open this service", + "alreadyOnService": "You are already viewing this service" } }, "popup": { diff --git a/GUI/src/i18n/et/common.json b/GUI/src/i18n/et/common.json index 77d2178f..8edd7367 100644 --- a/GUI/src/i18n/et/common.json +++ b/GUI/src/i18n/et/common.json @@ -370,7 +370,8 @@ "selectService": "Vali teenus...", "noActiveServices": "Aktiivseid teenuseid pole saadaval", "parameters": "Parameetrid", - "navigateToService": "Ava see teenus" + "navigateToService": "Ava see teenus", + "alreadyOnService": "Sa vaatad juba seda teenust" } }, "popup": { diff --git a/GUI/src/utils/service-navigation-utils.ts b/GUI/src/utils/service-navigation-utils.ts index 51133124..0d1047e1 100644 --- a/GUI/src/utils/service-navigation-utils.ts +++ b/GUI/src/utils/service-navigation-utils.ts @@ -1,8 +1,15 @@ +import { t } from 'i18next'; import { NavigateFunction } from 'react-router-dom'; import { ROUTES } from 'resources/routes-constants'; import useServiceStore from 'store/new-services.store'; +import useToastStore from 'store/toasts.store'; export const navigateToService = (serviceId: string, navigate: NavigateFunction) => { + if (serviceId === useServiceStore.getState().serviceId) { + useToastStore.getState().info({ title: t('serviceFlow.element.jumpToService.alreadyOnService') }); + return; + } + const target = ROUTES.replaceWithId(ROUTES.EDITSERVICE_ROUTE, serviceId); if (useServiceStore.getState().hasUnsavedChanges) { From 8d5737e1489f25e2691ae2c330dc7963066bf41e Mon Sep 17 00:00:00 2001 From: 1AhmedYasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Fri, 17 Jul 2026 00:07:20 +0300 Subject: [PATCH 2/2] fix(1099): Added toast title --- GUI/src/components/Flow/NodeTypes/StepNode.test.tsx | 3 ++- GUI/src/utils/service-navigation-utils.ts | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/GUI/src/components/Flow/NodeTypes/StepNode.test.tsx b/GUI/src/components/Flow/NodeTypes/StepNode.test.tsx index 20bb95e5..1fca742d 100644 --- a/GUI/src/components/Flow/NodeTypes/StepNode.test.tsx +++ b/GUI/src/components/Flow/NodeTypes/StepNode.test.tsx @@ -62,7 +62,8 @@ describe('StepNode jump-to-service navigation', () => { expect(resetStateSpy).not.toHaveBeenCalled(); expect(useServiceStore.getState().nextLocation).toBeNull(); expect(useToastStore.getState().toasts).toHaveLength(1); - expect(useToastStore.getState().toasts[0].title).toBe('serviceFlow.element.jumpToService.alreadyOnService'); + expect(useToastStore.getState().toasts[0].title).toBe('serviceFlow.element.jumpToService.title'); + expect(useToastStore.getState().toasts[0].message).toBe('serviceFlow.element.jumpToService.alreadyOnService'); }); it('navigates directly to the target service when there are no unsaved changes', () => { diff --git a/GUI/src/utils/service-navigation-utils.ts b/GUI/src/utils/service-navigation-utils.ts index 0d1047e1..1f133670 100644 --- a/GUI/src/utils/service-navigation-utils.ts +++ b/GUI/src/utils/service-navigation-utils.ts @@ -6,7 +6,10 @@ import useToastStore from 'store/toasts.store'; export const navigateToService = (serviceId: string, navigate: NavigateFunction) => { if (serviceId === useServiceStore.getState().serviceId) { - useToastStore.getState().info({ title: t('serviceFlow.element.jumpToService.alreadyOnService') }); + useToastStore.getState().info({ + title: t('serviceFlow.element.jumpToService.title'), + message: t('serviceFlow.element.jumpToService.alreadyOnService'), + }); return; }