From fa76d53ba127252ebd4f996aed93fa95786f877a Mon Sep 17 00:00:00 2001 From: James Cocker Date: Tue, 24 Feb 2026 11:36:34 +0000 Subject: [PATCH 1/6] Refreshing 3270 tab working and tags set correctly Signed-off-by: James Cocker --- galasa-ui/src/actions/runsAction.ts | 17 +++++++++-- .../test-run-details/3270Tab/TabFor3270.tsx | 8 +++-- .../3270Tab/TableOfScreenshots.tsx | 1 - .../test-run-details/OverviewTab.tsx | 27 +++++------------ .../test-run-details/TestRunDetails.tsx | 30 +++++++++++++++++-- .../test-run-details/Tab3270.module.css | 5 ++-- 6 files changed, 58 insertions(+), 30 deletions(-) diff --git a/galasa-ui/src/actions/runsAction.ts b/galasa-ui/src/actions/runsAction.ts index a41e4225..feecbd42 100644 --- a/galasa-ui/src/actions/runsAction.ts +++ b/galasa-ui/src/actions/runsAction.ts @@ -9,9 +9,20 @@ import { ResultArchiveStoreAPIApi, TagsAPIApi } from '@/generated/galasaapi'; import { createAuthenticatedApiConfiguration } from '@/utils/api'; import { fetchRunDetailLogs } from '@/utils/testRuns'; import { CLIENT_API_VERSION } from '@/utils/constants/common'; +import { Configuration } from '@/generated/galasaapi'; + +// Cache for API configuration to avoid multiple cookie accesses in the same request +let cachedApiConfig: Configuration | null = null; + +const getCachedApiConfiguration = (): Configuration => { + if (!cachedApiConfig) { + cachedApiConfig = createAuthenticatedApiConfiguration(); + } + return cachedApiConfig; +}; export const downloadArtifactFromServer = async (runId: string, artifactUrl: string) => { - const apiConfig = createAuthenticatedApiConfiguration(); + const apiConfig = getCachedApiConfiguration(); const rasApiClient = new ResultArchiveStoreAPIApi(apiConfig); const artifactFile = await rasApiClient.getRasRunArtifactByPath( @@ -53,7 +64,7 @@ export const downloadArtifactFromServer = async (runId: string, artifactUrl: str export const updateRunTags = async (runId: string, tags: string[]) => { try { - const apiConfig = createAuthenticatedApiConfiguration(); + const apiConfig = getCachedApiConfiguration(); const rasApiClient = new ResultArchiveStoreAPIApi(apiConfig); // Note: Tags are already unique from the Set in the frontend, but is checked again by the rest api. @@ -73,7 +84,7 @@ export const updateRunTags = async (runId: string, tags: string[]) => { export const getExistingTagObjects = async () => { try { - const apiConfig = createAuthenticatedApiConfiguration(); + const apiConfig = getCachedApiConfiguration(); const tagsApiClient = new TagsAPIApi(apiConfig); const tagsResponse = await tagsApiClient.getTags(); diff --git a/galasa-ui/src/components/test-runs/test-run-details/3270Tab/TabFor3270.tsx b/galasa-ui/src/components/test-runs/test-run-details/3270Tab/TabFor3270.tsx index 972d9544..d34d54f2 100644 --- a/galasa-ui/src/components/test-runs/test-run-details/3270Tab/TabFor3270.tsx +++ b/galasa-ui/src/components/test-runs/test-run-details/3270Tab/TabFor3270.tsx @@ -19,14 +19,17 @@ export default function TabFor3270({ zos3270TerminalData, is3270CurrentlySelected, handleNavigateTo3270, + isLoading, + setIsLoading, }: { runId: string; zos3270TerminalData: TreeNodeData[]; is3270CurrentlySelected: boolean; handleNavigateTo3270: (highlightedRowId: string) => void; + isLoading: boolean; + setIsLoading: React.Dispatch>; }) { const [isError, setIsError] = useState(false); - const [isLoading, setIsLoading] = useState(true); const [imageData, setImageData] = useState(); const [moveImageSelection, setMoveImageSelection] = useState(0); const [cannotSwitchToPreviousImage, setCannotSwitchToPreviousImage] = useState(true); @@ -48,12 +51,13 @@ export default function TabFor3270({ useEffect(() => { if (is3270CurrentlySelected && highlightedRowId === '') { const url = new URL(window.location.href); + console.log('Hello Set to ' + url.searchParams.get('terminalScreen')); setHighlightedRowId(url.searchParams.get('terminalScreen') || ''); } // If you're adding extra state to this hook, make sure to review the dependency array due to the warning suppression: // eslint-disable-next-line - }, [is3270CurrentlySelected]); + }, [is3270CurrentlySelected, isLoading]); if (isError) { return ; diff --git a/galasa-ui/src/components/test-runs/test-run-details/3270Tab/TableOfScreenshots.tsx b/galasa-ui/src/components/test-runs/test-run-details/3270Tab/TableOfScreenshots.tsx index d5ed8cf1..b98578ee 100644 --- a/galasa-ui/src/components/test-runs/test-run-details/3270Tab/TableOfScreenshots.tsx +++ b/galasa-ui/src/components/test-runs/test-run-details/3270Tab/TableOfScreenshots.tsx @@ -147,7 +147,6 @@ export default function TableOfScreenshots({ // Ensure screenshots are only collected once. if (!screenshotsCollected.current?.valueOf() && flattenedZos3270TerminalData.length === 0) { screenshotsCollected.current = true; - setIsLoading(true); const fetchData = async () => { try { setFlattenedZos3270TerminalData([]); diff --git a/galasa-ui/src/components/test-runs/test-run-details/OverviewTab.tsx b/galasa-ui/src/components/test-runs/test-run-details/OverviewTab.tsx index 24559dab..f47d1474 100644 --- a/galasa-ui/src/components/test-runs/test-run-details/OverviewTab.tsx +++ b/galasa-ui/src/components/test-runs/test-run-details/OverviewTab.tsx @@ -16,21 +16,26 @@ import useHistoryBreadCrumbs from '@/hooks/useHistoryBreadCrumbs'; import { TEST_RUNS_QUERY_PARAMS } from '@/utils/constants/common'; import { TIME_TO_WAIT_BEFORE_CLOSING_TAG_EDIT_MODAL_MS } from '@/utils/constants/common'; import RenderTags from '@/components/test-runs/test-run-details/RenderTags'; -import { updateRunTags, getExistingTagObjects } from '@/actions/runsAction'; +import { updateRunTags } from '@/actions/runsAction'; type DisplayedTagType = { id: string; label: string; }; -const OverviewTab = ({ metadata }: { metadata: RunMetadata }) => { +const OverviewTab = ({ + metadata, + existingTagObjectNames, +}: { + metadata: RunMetadata; + existingTagObjectNames: string[]; +}) => { const translations = useTranslations('OverviewTab'); const { pushBreadCrumb } = useHistoryBreadCrumbs(); const [weekBefore, setWeekBefore] = useState(null); const [tags, setTags] = useState(metadata?.tags || []); - const [existingTagObjectNames, setExistingTagObjectNames] = useState([]); const [isTagsEditModalOpen, setIsTagsEditModalOpen] = useState(false); const [filterInput, setFilterInput] = useState(''); @@ -46,22 +51,6 @@ const OverviewTab = ({ metadata }: { metadata: RunMetadata }) => { const OTHER_RECENT_RUNS = `/test-runs?${TEST_RUNS_QUERY_PARAMS.TEST_NAME}=${fullTestName}&${TEST_RUNS_QUERY_PARAMS.BUNDLE}=${metadata?.bundle}&${TEST_RUNS_QUERY_PARAMS.PACKAGE}=${metadata?.package}&${TEST_RUNS_QUERY_PARAMS.DURATION}=60,0,0&${TEST_RUNS_QUERY_PARAMS.TAB}=results&${TEST_RUNS_QUERY_PARAMS.QUERY_NAME}=Recent runs of test ${metadata?.testName}`; const RETRIES_FOR_THIS_TEST_RUN = `/test-runs?${TEST_RUNS_QUERY_PARAMS.SUBMISSION_ID}=${metadata?.submissionId}&${TEST_RUNS_QUERY_PARAMS.FROM}=${weekBefore}&${TEST_RUNS_QUERY_PARAMS.TAB}=results&${TEST_RUNS_QUERY_PARAMS.QUERY_NAME}=All attempts of test run ${metadata?.runName}`; - useEffect(() => { - const fetchExistingTags = async () => { - try { - const result = await getExistingTagObjects(); - setExistingTagObjectNames(result.tags || []); - - if (!result.success) { - console.error('Failed to fetch existing tags:', result.error); - } - } catch (error) { - console.error('Error fetching existing tags:', error); - } - }; - fetchExistingTags(); - }, []); - useEffect(() => { const validateTime = () => { const validatedTime = getAWeekBeforeSubmittedTime(metadata?.rawSubmittedAt!); diff --git a/galasa-ui/src/components/test-runs/test-run-details/TestRunDetails.tsx b/galasa-ui/src/components/test-runs/test-run-details/TestRunDetails.tsx index d84d062b..87b01f9a 100644 --- a/galasa-ui/src/components/test-runs/test-run-details/TestRunDetails.tsx +++ b/galasa-ui/src/components/test-runs/test-run-details/TestRunDetails.tsx @@ -45,6 +45,7 @@ import { NotificationType } from '@/utils/types/common'; import { TreeNodeData } from '@/utils/functions/artifacts'; import { TEST_RUNS } from '@/utils/constants/breadcrumb'; import TestRunsSearch from '../TestRunsSearch'; +import { getExistingTagObjects } from '@/actions/runsAction'; interface TestRunDetailsProps { runId: string; @@ -74,9 +75,11 @@ const TestRunDetails = ({ const [isError, setIsError] = useState(false); const [isDownloading, setIsDownloading] = useState(false); const [notification, setNotification] = useState(null); + const [existingTagObjectNames, setExistingTagObjectNames] = useState([]); const { formatDate } = useDateTimeFormat(); const indexOf3270Tab = TEST_RUN_PAGE_TABS.indexOf('3270'); + const [is3270TabLoading, setIs3270TabLoading] = useState(true); const [is3270TabSelectedInURL, setIs3270TabSelectedInURL] = useState(false); const [zos3270TerminalFolderExists, setZos3270TerminalFolderExists] = useState(false); const [zos3270TerminalData, setZos3270TerminalData] = useState([]); @@ -100,12 +103,14 @@ const TestRunDetails = ({ const handleZos3270TerminalFolderCheck = (newZos3270TerminalFolderExists: boolean) => { setZos3270TerminalFolderExists(newZos3270TerminalFolderExists); + }; + useEffect(() => { // If 3270 tab has been selected in the URL, move them to the 3270 pannel from the overview page redirection - if (is3270TabSelectedInURL && newZos3270TerminalFolderExists) { + if (is3270TabSelectedInURL && zos3270TerminalFolderExists && !is3270TabLoading) { setSelectedTabIndex(indexOf3270Tab); } - }; + }, [is3270TabLoading]); const handleSetZos3270TerminalData = (newZos3270TerminalData: TreeNodeData[]) => { setZos3270TerminalData(newZos3270TerminalData); @@ -182,6 +187,23 @@ const TestRunDetails = ({ loadRunDetails(); }, [run, runDetailsPromise, runArtifactsPromise, runLogPromise, extractRunDetails]); + // Fetch existing tags once on component mount (persists across tab changes) + useEffect(() => { + const fetchExistingTags = async () => { + try { + const result = await getExistingTagObjects(); + if (result.success) { + setExistingTagObjectNames(result.tags || []); + } else { + console.error('Failed to fetch existing tags:', result.error); + } + } catch (error) { + console.error('Error fetching existing tags:', error); + } + }; + fetchExistingTags(); + }, []); + useEffect(() => { // If the 'Test Runs' breadcrumb is already in the items, skip. if (breadCrumbItems.length > 1) return; @@ -383,7 +405,7 @@ const TestRunDetails = ({ - + @@ -407,6 +429,8 @@ const TestRunDetails = ({ zos3270TerminalData={zos3270TerminalData} is3270CurrentlySelected={indexOf3270Tab === selectedTabIndex} handleNavigateTo3270={handleNavigateTo3270} + isLoading={is3270TabLoading} + setIsLoading={setIs3270TabLoading} /> )} diff --git a/galasa-ui/src/styles/test-runs/test-run-details/Tab3270.module.css b/galasa-ui/src/styles/test-runs/test-run-details/Tab3270.module.css index 842cb011..0be6416a 100644 --- a/galasa-ui/src/styles/test-runs/test-run-details/Tab3270.module.css +++ b/galasa-ui/src/styles/test-runs/test-run-details/Tab3270.module.css @@ -5,7 +5,7 @@ */ .tab3270Container { - height: min(calc(100vh - 400px), 550px); + height: min(calc(100vh - 400px), 600px); } .clickableRow { @@ -13,7 +13,8 @@ } .innerScreenshotTable { - max-height: min(calc(100vh - 450px), 550px); + /* Override Carbon's max-height for the Table component */ + max-height: min(calc(100vh - 450px), 550px) !important; overflow-y: auto; /* Stops sticky header overlapping table elements when scrollToTop() is called. */ scroll-padding-top: 48px; From 510a57568a850f66516213c9962b50bdcae3c0e6 Mon Sep 17 00:00:00 2001 From: James Cocker Date: Tue, 24 Feb 2026 14:06:59 +0000 Subject: [PATCH 2/6] Switches to correct ID on page load Signed-off-by: James Cocker --- .../test-run-details/3270Tab/TabFor3270.tsx | 12 ------------ .../3270Tab/TableOfScreenshots.tsx | 15 ++++++++------- 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/galasa-ui/src/components/test-runs/test-run-details/3270Tab/TabFor3270.tsx b/galasa-ui/src/components/test-runs/test-run-details/3270Tab/TabFor3270.tsx index d34d54f2..06a01225 100644 --- a/galasa-ui/src/components/test-runs/test-run-details/3270Tab/TabFor3270.tsx +++ b/galasa-ui/src/components/test-runs/test-run-details/3270Tab/TabFor3270.tsx @@ -47,18 +47,6 @@ export default function TabFor3270({ // eslint-disable-next-line }, [highlightedRowId, is3270CurrentlySelected]); - // Get the 'terminalScreen' parameter - useEffect(() => { - if (is3270CurrentlySelected && highlightedRowId === '') { - const url = new URL(window.location.href); - console.log('Hello Set to ' + url.searchParams.get('terminalScreen')); - setHighlightedRowId(url.searchParams.get('terminalScreen') || ''); - } - - // If you're adding extra state to this hook, make sure to review the dependency array due to the warning suppression: - // eslint-disable-next-line - }, [is3270CurrentlySelected, isLoading]); - if (isError) { return ; } diff --git a/galasa-ui/src/components/test-runs/test-run-details/3270Tab/TableOfScreenshots.tsx b/galasa-ui/src/components/test-runs/test-run-details/3270Tab/TableOfScreenshots.tsx index b98578ee..8c6c3d06 100644 --- a/galasa-ui/src/components/test-runs/test-run-details/3270Tab/TableOfScreenshots.tsx +++ b/galasa-ui/src/components/test-runs/test-run-details/3270Tab/TableOfScreenshots.tsx @@ -78,7 +78,6 @@ export default function TableOfScreenshots({ const [searchTerm, setSearchTerm] = useState(''); const [selectedTerminal, setSelectedTerminal] = useState(null); const [allImageData, setAllImageData] = useState([]); - const [initialHighlightedRowSet, setInitialHighlightedRowSet] = useState(false); const screenshotsCollected = useRef(false); @@ -116,12 +115,14 @@ export default function TableOfScreenshots({ useEffect(() => { // Highlight and display first element when the page loads, unless already set. const highlightFirstRowOnPageLoad = () => { - if (!initialHighlightedRowSet && filteredRows[0]) { - setInitialHighlightedRowSet(true); - if ( - highlightedRowId === '' || - !filteredRows.find((filteredRow) => filteredRow.id === highlightedRowId) - ) { + if (!highlightedRowId && filteredRows[0]) { + + const url = new URL(window.location.href); + const terminalScreen = url.searchParams.get('terminalScreen'); + + if (terminalScreen && filteredRows.find((filteredRow) => filteredRow.id === terminalScreen)) { + setHighlightedRowId(terminalScreen); + } else { setHighlightedRowId(filteredRows[0].id); } } From fd6551c2236bf1cab171cbfc48936fcd0571c863 Mon Sep 17 00:00:00 2001 From: James Cocker Date: Tue, 24 Feb 2026 14:07:22 +0000 Subject: [PATCH 3/6] Formatting functionality Signed-off-by: James Cocker --- .../test-run-details/3270Tab/TableOfScreenshots.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/galasa-ui/src/components/test-runs/test-run-details/3270Tab/TableOfScreenshots.tsx b/galasa-ui/src/components/test-runs/test-run-details/3270Tab/TableOfScreenshots.tsx index 8c6c3d06..10ff9934 100644 --- a/galasa-ui/src/components/test-runs/test-run-details/3270Tab/TableOfScreenshots.tsx +++ b/galasa-ui/src/components/test-runs/test-run-details/3270Tab/TableOfScreenshots.tsx @@ -116,11 +116,13 @@ export default function TableOfScreenshots({ // Highlight and display first element when the page loads, unless already set. const highlightFirstRowOnPageLoad = () => { if (!highlightedRowId && filteredRows[0]) { - const url = new URL(window.location.href); const terminalScreen = url.searchParams.get('terminalScreen'); - if (terminalScreen && filteredRows.find((filteredRow) => filteredRow.id === terminalScreen)) { + if ( + terminalScreen && + filteredRows.find((filteredRow) => filteredRow.id === terminalScreen) + ) { setHighlightedRowId(terminalScreen); } else { setHighlightedRowId(filteredRows[0].id); From 32ebed6ce1485b34f7e4ae5b04a5531918424526 Mon Sep 17 00:00:00 2001 From: James Cocker Date: Tue, 24 Feb 2026 14:19:22 +0000 Subject: [PATCH 4/6] Fixed broken tests Signed-off-by: James Cocker --- galasa-ui/src/actions/runsAction.ts | 5 + .../src/tests/actions/runActions.test.ts | 8 +- .../test-run-details/OverviewTab.test.tsx | 223 +++++++++++++++--- 3 files changed, 201 insertions(+), 35 deletions(-) diff --git a/galasa-ui/src/actions/runsAction.ts b/galasa-ui/src/actions/runsAction.ts index feecbd42..b9467096 100644 --- a/galasa-ui/src/actions/runsAction.ts +++ b/galasa-ui/src/actions/runsAction.ts @@ -21,6 +21,11 @@ const getCachedApiConfiguration = (): Configuration => { return cachedApiConfig; }; +// Reset the cached API configuration (useful for testing) +export const resetApiConfigCache = (): void => { + cachedApiConfig = null; +}; + export const downloadArtifactFromServer = async (runId: string, artifactUrl: string) => { const apiConfig = getCachedApiConfiguration(); const rasApiClient = new ResultArchiveStoreAPIApi(apiConfig); diff --git a/galasa-ui/src/tests/actions/runActions.test.ts b/galasa-ui/src/tests/actions/runActions.test.ts index 3117200e..532ecfcd 100644 --- a/galasa-ui/src/tests/actions/runActions.test.ts +++ b/galasa-ui/src/tests/actions/runActions.test.ts @@ -3,7 +3,11 @@ * * SPDX-License-Identifier: EPL-2.0 */ -import { downloadArtifactFromServer, updateRunTags } from '@/actions/runsAction'; +import { + downloadArtifactFromServer, + updateRunTags, + resetApiConfigCache, +} from '@/actions/runsAction'; import * as apiUtils from '@/utils/api'; import * as galasaapi from '@/generated/galasaapi'; import { CLIENT_API_VERSION } from '@/utils/constants/common'; @@ -30,6 +34,7 @@ describe('downloadArtifactFromServer', () => { afterEach(() => { jest.resetAllMocks(); + resetApiConfigCache(); }); it('parses valid JSON payload', async () => { @@ -122,6 +127,7 @@ describe('updateRunTags', () => { afterEach(() => { jest.resetAllMocks(); + resetApiConfigCache(); }); it('successfully updates tags', async () => { diff --git a/galasa-ui/src/tests/components/test-runs/test-run-details/OverviewTab.test.tsx b/galasa-ui/src/tests/components/test-runs/test-run-details/OverviewTab.test.tsx index ccdcef39..57766a22 100644 --- a/galasa-ui/src/tests/components/test-runs/test-run-details/OverviewTab.test.tsx +++ b/galasa-ui/src/tests/components/test-runs/test-run-details/OverviewTab.test.tsx @@ -219,7 +219,12 @@ const mockGetAWeekBeforeSubmittedTime = getAWeekBeforeSubmittedTime as jest.Mock describe('OverviewTab', () => { it('renders all top-level InlineText entries', () => { - render(); + render( + + ); // check each label/value pair [ @@ -240,7 +245,12 @@ describe('OverviewTab', () => { }); it('renders the timing fields in the infoContainer', () => { - render(); + render( + + ); ['Submitted:', 'Started:', 'Finished:', 'Duration:'].forEach((label) => { expect(screen.getByText(label as string, { selector: 'p' })).toBeInTheDocument(); @@ -249,7 +259,12 @@ describe('OverviewTab', () => { }); it('renders each tag when tags array is non-empty, sorted', () => { - render(); + render( + + ); // header - use getByText since h5 contains nested elements expect(screen.getByText('Tags', { selector: 'h5' })).toBeInTheDocument(); @@ -261,7 +276,12 @@ describe('OverviewTab', () => { it('shows fallback text when tags is empty or missing', () => { const noTags: RunMetadata = { ...completeMetadata, tags: [] }; - render(); + render( + + ); expect(screen.getByText('No tags were associated with this test run.')).toBeInTheDocument(); }); }); @@ -274,7 +294,12 @@ describe('OverviewTab - Time and Link Logic', () => { it('renders recent runs link with correct href', async () => { mockGetAWeekBeforeSubmittedTime.mockReturnValue('2025-06-03T09:00:00Z'); - render(); + render( + + ); await screen.findAllByTestId('mock-link'); @@ -292,7 +317,12 @@ describe('OverviewTab - Time and Link Logic', () => { mockGetAWeekBeforeSubmittedTime.mockReturnValue(mockWeekBefore); - render(); + render( + + ); await screen.findAllByTestId('mock-link'); @@ -310,7 +340,12 @@ describe('OverviewTab - Time and Link Logic', () => { it('renders only recent runs link when weekBefore is invalid', async () => { mockGetAWeekBeforeSubmittedTime.mockReturnValue(null); - render(); + render( + + ); await new Promise((resolve) => setTimeout(resolve, 0)); @@ -329,7 +364,12 @@ describe('OverviewTab - Time and Link Logic', () => { mockGetAWeekBeforeSubmittedTime.mockReturnValue('2025-06-03T09:00:00Z'); - render(); + render( + + ); expect(mockGetAWeekBeforeSubmittedTime).toHaveBeenCalledWith('2025-06-10T09:00:00Z'); expect(mockGetAWeekBeforeSubmittedTime).toHaveBeenCalledTimes(1); @@ -343,7 +383,12 @@ describe('OverviewTab - Time and Link Logic', () => { mockGetAWeekBeforeSubmittedTime.mockReturnValue('Invalid date'); - render(); + render( + + ); expect(mockGetAWeekBeforeSubmittedTime).toHaveBeenCalledWith(undefined); }); @@ -353,7 +398,12 @@ describe('OverviewTab - Time and Link Logic', () => { mockGetAWeekBeforeSubmittedTime.mockReturnValue(mockWeekBefore); - render(); + render( + + ); await screen.findAllByTestId('mock-link'); @@ -366,7 +416,12 @@ describe('OverviewTab - Time and Link Logic', () => { it('updates weekBefore state correctly when time is invalid', async () => { mockGetAWeekBeforeSubmittedTime.mockReturnValue(null); - render(); + render( + + ); await new Promise((resolve) => setTimeout(resolve, 0)); @@ -378,7 +433,12 @@ describe('OverviewTab - Time and Link Logic', () => { }); it('push link bread crumb when any of the links is clicked', () => { - render(); + render( + + ); const links = screen.getAllByTestId('mock-link'); links.forEach((link) => { @@ -405,7 +465,12 @@ describe('OverviewTab - Tags Edit Modal', () => { it('should open modal when edit icon is clicked', async () => { const user = userEvent.setup(); - render(); + render( + + ); const editIcon = screen.getByTestId('edit-icon'); await user.click(editIcon); @@ -417,7 +482,12 @@ describe('OverviewTab - Tags Edit Modal', () => { it('should display RenderTags component with dismissible tags in modal', async () => { const user = userEvent.setup(); - render(); + render( + + ); const editIcon = screen.getByTestId('edit-icon'); await user.click(editIcon); @@ -430,7 +500,12 @@ describe('OverviewTab - Tags Edit Modal', () => { it('should close modal when secondary button is clicked', async () => { const user = userEvent.setup(); - render(); + render( + + ); const editIcon = screen.getByTestId('edit-icon'); await user.click(editIcon); @@ -449,7 +524,12 @@ describe('OverviewTab - Tags Edit Modal', () => { it('should handle tag removal in modal', async () => { const user = userEvent.setup(); - render(); + render( + + ); const editIcon = screen.getByTestId('edit-icon'); await user.click(editIcon); @@ -478,7 +558,12 @@ describe('OverviewTab - Tags Edit Modal', () => { delete (window as any).location; (window as any).location = { reload: jest.fn() }; - render(); + render( + + ); const editIcon = screen.getByTestId('edit-icon'); await user.click(editIcon); @@ -505,7 +590,12 @@ describe('OverviewTab - Tags Edit Modal', () => { error: 'Server Error', }); - render(); + render( + + ); const editIcon = screen.getByTestId('edit-icon'); await user.click(editIcon); @@ -534,7 +624,12 @@ describe('OverviewTab - Tags Edit Modal', () => { delete (window as any).location; (window as any).location = { reload: jest.fn() }; - render(); + render( + + ); const editIcon = screen.getByTestId('edit-icon'); await user.click(editIcon); @@ -554,7 +649,12 @@ describe('OverviewTab - Tags Edit Modal', () => { it('should reset staged tags when modal is closed without saving', async () => { const user = userEvent.setup(); - render(); + render( + + ); const editIcon = screen.getByTestId('edit-icon'); await user.click(editIcon); @@ -592,7 +692,12 @@ describe('OverviewTab - Tags Edit Modal', () => { it('should display modal heading with run name', async () => { const user = userEvent.setup(); - render(); + render( + + ); const editIcon = screen.getByTestId('edit-icon'); await user.click(editIcon); @@ -604,7 +709,12 @@ describe('OverviewTab - Tags Edit Modal', () => { it('should initialise staged tags from current tags when modal opens', async () => { const user = userEvent.setup(); - render(); + render( + + ); const editIcon = screen.getByTestId('edit-icon'); await user.click(editIcon); @@ -618,7 +728,12 @@ describe('OverviewTab - Tags Edit Modal', () => { it('should display FilterableMultiSelect with sorted items alphabetically', async () => { const user = userEvent.setup(); - render(); + render( + + ); const editIcon = screen.getByTestId('edit-icon'); await user.click(editIcon); @@ -634,7 +749,12 @@ describe('OverviewTab - Tags Edit Modal', () => { it('should maintain alphabetical sorting when adding new tags via filter input', async () => { const user = userEvent.setup(); - render(); + render( + + ); const editIcon = screen.getByTestId('edit-icon'); await user.click(editIcon); @@ -655,7 +775,12 @@ describe('OverviewTab - Tags Edit Modal', () => { it('should keep selected items ticked in the dropdown', async () => { const user = userEvent.setup(); - render(); + render( + + ); const editIcon = screen.getByTestId('edit-icon'); await user.click(editIcon); @@ -682,7 +807,12 @@ describe('OverviewTab - Tags Edit Modal', () => { ...completeMetadata, tags: ['smoke'], }; - render(); + render( + + ); const editIcon = screen.getByTestId('edit-icon'); await user.click(editIcon); @@ -709,7 +839,12 @@ describe('OverviewTab - Tags Edit Modal', () => { it('should clear filter input when modal is closed', async () => { const user = userEvent.setup(); - render(); + render( + + ); const editIcon = screen.getByTestId('edit-icon'); await user.click(editIcon); @@ -747,7 +882,12 @@ describe('OverviewTab - Tags Edit Modal', () => { tags: ['smoke', 'regression', 'new-tag'], }); - render(); + render( + + ); const editIcon = screen.getByTestId('edit-icon'); await user.click(editIcon); @@ -786,22 +926,37 @@ describe('OverviewTab - Tags Edit Modal', () => { ); }); - it('should fetch existing tags on component mount', async () => { - render(); + it('should receive existing tags as props', async () => { + const existingTags = ['existing-tag-1', 'existing-tag-2']; + render(); // Wait for the component to render await waitFor(() => { expect(screen.getByText('Tags', { selector: 'h5' })).toBeInTheDocument(); }); - // The getExistingTagObjects should have been called - const { getExistingTagObjects } = require('@/actions/runsAction'); - expect(getExistingTagObjects).toHaveBeenCalled(); + // Open the modal to verify existing tags are available + const user = userEvent.setup(); + const editIcon = screen.getByTestId('edit-icon'); + await user.click(editIcon); + + await waitFor(() => { + expect(screen.getByTestId('mock-filterable-multiselect')).toBeInTheDocument(); + }); + + // Verify that the existing tags passed as props are available in the dropdown + expect(screen.getByTestId('multiselect-item-existing-tag-1')).toBeInTheDocument(); + expect(screen.getByTestId('multiselect-item-existing-tag-2')).toBeInTheDocument(); }); it('should include existing system tags in FilterableMultiSelect items', async () => { const user = userEvent.setup(); - render(); + render( + + ); const editIcon = screen.getByTestId('edit-icon'); await user.click(editIcon); From e142fa7c9700a69c6af3f66a25c3242896898d97 Mon Sep 17 00:00:00 2001 From: James Cocker Date: Tue, 24 Feb 2026 15:46:13 +0000 Subject: [PATCH 5/6] Removed user caching and important css flag Signed-off-by: James Cocker --- galasa-ui/src/actions/runsAction.ts | 22 +++---------------- .../3270Tab/TableOfScreenshots.tsx | 2 +- .../test-run-details/Tab3270.module.css | 2 +- .../src/tests/actions/runActions.test.ts | 8 +------ 4 files changed, 6 insertions(+), 28 deletions(-) diff --git a/galasa-ui/src/actions/runsAction.ts b/galasa-ui/src/actions/runsAction.ts index b9467096..a41e4225 100644 --- a/galasa-ui/src/actions/runsAction.ts +++ b/galasa-ui/src/actions/runsAction.ts @@ -9,25 +9,9 @@ import { ResultArchiveStoreAPIApi, TagsAPIApi } from '@/generated/galasaapi'; import { createAuthenticatedApiConfiguration } from '@/utils/api'; import { fetchRunDetailLogs } from '@/utils/testRuns'; import { CLIENT_API_VERSION } from '@/utils/constants/common'; -import { Configuration } from '@/generated/galasaapi'; - -// Cache for API configuration to avoid multiple cookie accesses in the same request -let cachedApiConfig: Configuration | null = null; - -const getCachedApiConfiguration = (): Configuration => { - if (!cachedApiConfig) { - cachedApiConfig = createAuthenticatedApiConfiguration(); - } - return cachedApiConfig; -}; - -// Reset the cached API configuration (useful for testing) -export const resetApiConfigCache = (): void => { - cachedApiConfig = null; -}; export const downloadArtifactFromServer = async (runId: string, artifactUrl: string) => { - const apiConfig = getCachedApiConfiguration(); + const apiConfig = createAuthenticatedApiConfiguration(); const rasApiClient = new ResultArchiveStoreAPIApi(apiConfig); const artifactFile = await rasApiClient.getRasRunArtifactByPath( @@ -69,7 +53,7 @@ export const downloadArtifactFromServer = async (runId: string, artifactUrl: str export const updateRunTags = async (runId: string, tags: string[]) => { try { - const apiConfig = getCachedApiConfiguration(); + const apiConfig = createAuthenticatedApiConfiguration(); const rasApiClient = new ResultArchiveStoreAPIApi(apiConfig); // Note: Tags are already unique from the Set in the frontend, but is checked again by the rest api. @@ -89,7 +73,7 @@ export const updateRunTags = async (runId: string, tags: string[]) => { export const getExistingTagObjects = async () => { try { - const apiConfig = getCachedApiConfiguration(); + const apiConfig = createAuthenticatedApiConfiguration(); const tagsApiClient = new TagsAPIApi(apiConfig); const tagsResponse = await tagsApiClient.getTags(); diff --git a/galasa-ui/src/components/test-runs/test-run-details/3270Tab/TableOfScreenshots.tsx b/galasa-ui/src/components/test-runs/test-run-details/3270Tab/TableOfScreenshots.tsx index 10ff9934..2be93d90 100644 --- a/galasa-ui/src/components/test-runs/test-run-details/3270Tab/TableOfScreenshots.tsx +++ b/galasa-ui/src/components/test-runs/test-run-details/3270Tab/TableOfScreenshots.tsx @@ -287,7 +287,7 @@ export default function TableOfScreenshots({ getRowProps: (options: any) => TableRowProps; getTableProps: () => TableBodyProps; }) => ( - +
{headers.map((header) => ( diff --git a/galasa-ui/src/styles/test-runs/test-run-details/Tab3270.module.css b/galasa-ui/src/styles/test-runs/test-run-details/Tab3270.module.css index 0be6416a..56d57f83 100644 --- a/galasa-ui/src/styles/test-runs/test-run-details/Tab3270.module.css +++ b/galasa-ui/src/styles/test-runs/test-run-details/Tab3270.module.css @@ -12,7 +12,7 @@ cursor: pointer; } -.innerScreenshotTable { +#innerScreenshotTable { /* Override Carbon's max-height for the Table component */ max-height: min(calc(100vh - 450px), 550px) !important; overflow-y: auto; diff --git a/galasa-ui/src/tests/actions/runActions.test.ts b/galasa-ui/src/tests/actions/runActions.test.ts index 532ecfcd..3117200e 100644 --- a/galasa-ui/src/tests/actions/runActions.test.ts +++ b/galasa-ui/src/tests/actions/runActions.test.ts @@ -3,11 +3,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -import { - downloadArtifactFromServer, - updateRunTags, - resetApiConfigCache, -} from '@/actions/runsAction'; +import { downloadArtifactFromServer, updateRunTags } from '@/actions/runsAction'; import * as apiUtils from '@/utils/api'; import * as galasaapi from '@/generated/galasaapi'; import { CLIENT_API_VERSION } from '@/utils/constants/common'; @@ -34,7 +30,6 @@ describe('downloadArtifactFromServer', () => { afterEach(() => { jest.resetAllMocks(); - resetApiConfigCache(); }); it('parses valid JSON payload', async () => { @@ -127,7 +122,6 @@ describe('updateRunTags', () => { afterEach(() => { jest.resetAllMocks(); - resetApiConfigCache(); }); it('successfully updates tags', async () => { From 9c2ea72508863d605bbe686b74cf5d040fd1e1fa Mon Sep 17 00:00:00 2001 From: James Cocker Date: Wed, 25 Feb 2026 10:22:50 +0000 Subject: [PATCH 6/6] Removed important flag and some build warnings Signed-off-by: James Cocker --- .../src/components/test-runs/test-run-details/LogTab.tsx | 4 ++-- .../components/test-runs/test-run-details/TestRunDetails.tsx | 2 ++ .../src/styles/test-runs/test-run-details/Tab3270.module.css | 3 +-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/galasa-ui/src/components/test-runs/test-run-details/LogTab.tsx b/galasa-ui/src/components/test-runs/test-run-details/LogTab.tsx index eb7c358a..42911cbb 100644 --- a/galasa-ui/src/components/test-runs/test-run-details/LogTab.tsx +++ b/galasa-ui/src/components/test-runs/test-run-details/LogTab.tsx @@ -536,7 +536,7 @@ export default function LogTab({ logs, initialLine, runId }: LogTabProps) { lineElement.scrollIntoView({ behavior: ANIMATION_BEHAVIOUR, block: 'start' }); } } - }, [initialLine, processedLines]); + }, [ANIMATION_BEHAVIOUR, initialLine, processedLines]); // Scroll to current match useEffect(() => { @@ -549,7 +549,7 @@ export default function LogTab({ logs, initialLine, runId }: LogTabProps) { }); } } - }, [currentMatchIndex]); + }, [ANIMATION_BEHAVIOUR, currentMatchIndex]); useEffect(() => { const matchCount = searchMatches.length; diff --git a/galasa-ui/src/components/test-runs/test-run-details/TestRunDetails.tsx b/galasa-ui/src/components/test-runs/test-run-details/TestRunDetails.tsx index 87b01f9a..08d59b3a 100644 --- a/galasa-ui/src/components/test-runs/test-run-details/TestRunDetails.tsx +++ b/galasa-ui/src/components/test-runs/test-run-details/TestRunDetails.tsx @@ -110,6 +110,8 @@ const TestRunDetails = ({ if (is3270TabSelectedInURL && zos3270TerminalFolderExists && !is3270TabLoading) { setSelectedTabIndex(indexOf3270Tab); } + // Ignore missing dependecies as they will be finalised by the time is3270TabLoading switches to false + // eslint-disable-next-line react-hooks/exhaustive-deps }, [is3270TabLoading]); const handleSetZos3270TerminalData = (newZos3270TerminalData: TreeNodeData[]) => { diff --git a/galasa-ui/src/styles/test-runs/test-run-details/Tab3270.module.css b/galasa-ui/src/styles/test-runs/test-run-details/Tab3270.module.css index 56d57f83..8c2a2ce2 100644 --- a/galasa-ui/src/styles/test-runs/test-run-details/Tab3270.module.css +++ b/galasa-ui/src/styles/test-runs/test-run-details/Tab3270.module.css @@ -13,8 +13,7 @@ } #innerScreenshotTable { - /* Override Carbon's max-height for the Table component */ - max-height: min(calc(100vh - 450px), 550px) !important; + max-height: min(calc(100vh - 450px), 550px); overflow-y: auto; /* Stops sticky header overlapping table elements when scrollToTop() is called. */ scroll-padding-top: 48px;