+
+);
+
+describe('EntitiesTableStateRow', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockUseMetric.mockReturnValue({
+ metric: { id: 'github.open_prs', title: 'Open PRs' },
+ });
+ mockUseMetricDisplayLabels.mockReturnValue({
+ title: 'Open PRs',
+ description: '',
+ });
+ });
+
+ it('should render missing permission text when error contains NotAllowedError', () => {
+ render(
+
+
+ ,
+ );
+
+ expect(screen.getByText('Missing permission')).toBeInTheDocument();
+ });
+
+ it('should render no data found when noEntities is true and no error', () => {
+ render(
+
+
+ ,
+ );
+
+ expect(screen.getByText('No data found')).toBeInTheDocument();
+ });
+
+ it('should call setMetricTitle when metric title is resolved', () => {
+ const setMetricTitle = jest.fn();
+ mockUseMetricDisplayLabels.mockReturnValue({
+ title: 'Resolved Metric Title',
+ description: '',
+ });
+
+ render(
+
+
+ ,
+ );
+
+ expect(setMetricTitle).toHaveBeenCalledWith('Resolved Metric Title');
+ });
+
+ it('should render single cell with colSpan', () => {
+ const { container } = render(
+
+
+ ,
+ );
+
+ const cell = container.querySelector('td[colspan="6"]');
+ expect(cell).toBeInTheDocument();
+ });
+});
diff --git a/workspaces/scorecard/plugins/scorecard/src/components/ScorecardPage/EntitiesTable/__tests__/EntitiesTableWrapper.test.tsx b/workspaces/scorecard/plugins/scorecard/src/components/ScorecardPage/EntitiesTable/__tests__/EntitiesTableWrapper.test.tsx
new file mode 100644
index 00000000000..67fcfa86aa9
--- /dev/null
+++ b/workspaces/scorecard/plugins/scorecard/src/components/ScorecardPage/EntitiesTable/__tests__/EntitiesTableWrapper.test.tsx
@@ -0,0 +1,68 @@
+/*
+ * Copyright Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { render, screen } from '@testing-library/react';
+import { ThemeProvider, createTheme } from '@mui/material/styles';
+
+import { EntitiesTableWrapper } from '../EntitiesTableWrapper';
+
+jest.mock('../../../../hooks/useTranslation', () => ({
+ useTranslation: () => ({
+ t: (key: string) =>
+ key === 'metric.someEntitiesNotReportingValues'
+ ? 'Some entities are not reporting values'
+ : key,
+ }),
+}));
+
+const TestWrapper = ({ children }: { children: React.ReactNode }) => (
+ {children}
+);
+
+describe('EntitiesTableWrapper', () => {
+ it('should render title and children', () => {
+ render(
+
+
+
Table content
+
+ ,
+ );
+
+ expect(screen.getByText('Entities')).toBeInTheDocument();
+ expect(screen.getByTestId('table-content')).toHaveTextContent(
+ 'Table content',
+ );
+ });
+
+ it('should render warning icon with tooltip', () => {
+ render(
+
+
+ Content
+
+ ,
+ );
+
+ const icon = document.querySelector(
+ '[data-testid="ReportProblemOutlinedIcon"]',
+ );
+ expect(icon).toBeInTheDocument();
+ expect(
+ screen.getByLabelText('Some entities are not reporting values'),
+ ).toBeInTheDocument();
+ });
+});
diff --git a/workspaces/scorecard/plugins/scorecard/src/components/ScorecardPage/EntitiesTable/cells/EntityNameCell.tsx b/workspaces/scorecard/plugins/scorecard/src/components/ScorecardPage/EntitiesTable/cells/EntityNameCell.tsx
new file mode 100644
index 00000000000..ad5b56a8939
--- /dev/null
+++ b/workspaces/scorecard/plugins/scorecard/src/components/ScorecardPage/EntitiesTable/cells/EntityNameCell.tsx
@@ -0,0 +1,65 @@
+/*
+ * Copyright Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Link } from '@backstage/core-components';
+import { parseEntityRef } from '@backstage/catalog-model';
+import { useRouteRef } from '@backstage/core-plugin-api';
+import { entityRouteRef } from '@backstage/plugin-catalog-react';
+
+import Tooltip from '@mui/material/Tooltip';
+
+interface EntityNameCellProps {
+ entityRef: string;
+ entityMetadata?: {
+ title?: string;
+ kind?: string;
+ description?: string;
+ };
+}
+export const EntityNameCell = ({
+ entityRef,
+ entityMetadata,
+}: EntityNameCellProps) => {
+ const entityLink = useRouteRef(entityRouteRef);
+
+ const { kind, namespace, name } = parseEntityRef(entityRef);
+
+ const displayName = entityMetadata?.title ?? name ?? '--';
+
+ const tooltipTitle = [
+ entityRef,
+ entityMetadata?.kind ?? kind,
+ entityMetadata?.description,
+ ]
+ .filter(Boolean)
+ .join(' | ');
+
+ return (
+
+
+ {displayName}
+
+
+ );
+};
diff --git a/workspaces/scorecard/plugins/scorecard/src/components/ScorecardPage/EntitiesTable/cells/MetricStatusCell.tsx b/workspaces/scorecard/plugins/scorecard/src/components/ScorecardPage/EntitiesTable/cells/MetricStatusCell.tsx
new file mode 100644
index 00000000000..028e8f69e67
--- /dev/null
+++ b/workspaces/scorecard/plugins/scorecard/src/components/ScorecardPage/EntitiesTable/cells/MetricStatusCell.tsx
@@ -0,0 +1,39 @@
+/*
+ * Copyright Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { memo } from 'react';
+
+import Box from '@mui/material/Box';
+
+export const MetricStatusCell = memo(
+ ({ status, theme }: { status: string | undefined; theme: any }) => {
+ return (
+
+
+ {status || '--'}
+
+ );
+ },
+);
diff --git a/workspaces/scorecard/plugins/scorecard/src/components/ScorecardPage/EntitiesTable/cells/OwnerCell.tsx b/workspaces/scorecard/plugins/scorecard/src/components/ScorecardPage/EntitiesTable/cells/OwnerCell.tsx
new file mode 100644
index 00000000000..1fce827e261
--- /dev/null
+++ b/workspaces/scorecard/plugins/scorecard/src/components/ScorecardPage/EntitiesTable/cells/OwnerCell.tsx
@@ -0,0 +1,63 @@
+/*
+ * Copyright Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { memo } from 'react';
+
+import { Link } from '@backstage/core-components';
+import { parseEntityRef, stringifyEntityRef } from '@backstage/catalog-model';
+import { useRouteRef } from '@backstage/core-plugin-api';
+import {
+ entityRouteRef,
+ useEntityPresentation,
+} from '@backstage/plugin-catalog-react';
+
+import Tooltip from '@mui/material/Tooltip';
+
+export const OwnerCell = memo(({ ownerRef }: { ownerRef?: string }) => {
+ const entityLink = useRouteRef(entityRouteRef);
+
+ const parsedEntityRef = ownerRef
+ ? parseEntityRef(ownerRef, {
+ defaultKind: 'group',
+ defaultNamespace: 'default',
+ })
+ : null;
+ const stringifiedEntityRef = parsedEntityRef
+ ? stringifyEntityRef(parsedEntityRef)
+ : '';
+ const { primaryTitle, secondaryTitle } =
+ useEntityPresentation(stringifiedEntityRef);
+
+ if (!ownerRef) return <>-->;
+
+ const link = entityLink(parsedEntityRef!);
+
+ return (
+
+
+ {primaryTitle}
+
+
+ );
+});
diff --git a/workspaces/scorecard/plugins/scorecard/src/components/ScorecardPage/EntitiesTable/cells/__tests__/EntityNameCell.test.tsx b/workspaces/scorecard/plugins/scorecard/src/components/ScorecardPage/EntitiesTable/cells/__tests__/EntityNameCell.test.tsx
new file mode 100644
index 00000000000..42510dd6763
--- /dev/null
+++ b/workspaces/scorecard/plugins/scorecard/src/components/ScorecardPage/EntitiesTable/cells/__tests__/EntityNameCell.test.tsx
@@ -0,0 +1,120 @@
+/*
+ * Copyright Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { render, screen } from '@testing-library/react';
+
+import { EntityNameCell } from '../EntityNameCell';
+
+jest.mock('@backstage/core-components', () => {
+ const React = require('react');
+ return {
+ Link: React.forwardRef(
+ (
+ { to, children, ...props }: { to: string; children: React.ReactNode },
+ ref: React.Ref,
+ ) => (
+
+ {children}
+
+ ),
+ ),
+ };
+});
+
+const mockEntityLink = jest.fn();
+jest.mock('@backstage/core-plugin-api', () => ({
+ useRouteRef: () => mockEntityLink,
+}));
+
+jest.mock('@backstage/plugin-catalog-react', () => ({
+ entityRouteRef: { id: 'entity-route' },
+}));
+
+describe('EntityNameCell', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockEntityLink.mockImplementation(
+ (params: { kind: string; namespace: string; name: string }) =>
+ `/catalog/${params.namespace}/${params.kind}/${params.name}`,
+ );
+ });
+
+ it('should render entity name from parseEntityRef when no entityMetadata', () => {
+ render();
+
+ expect(screen.getByText('my-service')).toBeInTheDocument();
+ const link = screen.getByRole('link');
+ expect(link).toHaveAttribute(
+ 'href',
+ '/catalog/default/component/my-service',
+ );
+ expect(link).toHaveTextContent('my-service');
+ });
+
+ it('should prefer entityMetadata.title over parsed name', () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByText('My Service Display Name')).toBeInTheDocument();
+ });
+
+ it('should use parsed name when entityMetadata has no title', () => {
+ render(
+ ,
+ );
+ expect(screen.getByText('backend-service')).toBeInTheDocument();
+ });
+
+ it('should build tooltip from entityRef, kind, and description', () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByText('My Service')).toBeInTheDocument();
+ const link = screen.getByRole('link');
+ expect(link).toHaveTextContent('My Service');
+ expect(link).toHaveAttribute(
+ 'aria-label',
+ 'component:default/my-service | Component | A backend service',
+ );
+ });
+
+ it('should pass parsed kind, namespace, name to entityLink', () => {
+ mockEntityLink.mockReturnValue('/custom/path');
+
+ render();
+
+ expect(mockEntityLink).toHaveBeenCalledWith({
+ kind: 'component',
+ namespace: 'staging',
+ name: 'frontend-app',
+ });
+ });
+});
diff --git a/workspaces/scorecard/plugins/scorecard/src/components/ScorecardPage/EntitiesTable/cells/__tests__/MetricStatusCell.test.tsx b/workspaces/scorecard/plugins/scorecard/src/components/ScorecardPage/EntitiesTable/cells/__tests__/MetricStatusCell.test.tsx
new file mode 100644
index 00000000000..6f4c4a25a27
--- /dev/null
+++ b/workspaces/scorecard/plugins/scorecard/src/components/ScorecardPage/EntitiesTable/cells/__tests__/MetricStatusCell.test.tsx
@@ -0,0 +1,73 @@
+/*
+ * Copyright Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { createTheme, ThemeProvider } from '@mui/material/styles';
+import { render, screen } from '@testing-library/react';
+
+import { MetricStatusCell } from '../MetricStatusCell';
+
+const theme = createTheme();
+
+describe('MetricStatusCell', () => {
+ beforeEach(() => {
+ jest.spyOn(console, 'log').mockImplementation(() => {});
+ });
+
+ afterEach(() => {
+ jest.restoreAllMocks();
+ });
+
+ it('should render status text when status is provided', () => {
+ render(
+
+
+ ,
+ );
+
+ expect(screen.getByText('success')).toBeInTheDocument();
+ });
+
+ it('should render -- when status is empty string', () => {
+ render(
+
+
+ ,
+ );
+
+ expect(screen.getByText('--')).toBeInTheDocument();
+ });
+
+ it('should render a colored indicator box', () => {
+ const { container } = render(
+
+
+ ,
+ );
+
+ const box = container.querySelector('[class*="MuiBox"]');
+ expect(box).toBeInTheDocument();
+ });
+
+ it('should use theme fallback color when status has no palette key', () => {
+ render(
+
+
+ ,
+ );
+
+ expect(screen.getByText('customStatus')).toBeInTheDocument();
+ });
+});
diff --git a/workspaces/scorecard/plugins/scorecard/src/components/ScorecardPage/EntitiesTable/cells/__tests__/OwnerCell.test.tsx b/workspaces/scorecard/plugins/scorecard/src/components/ScorecardPage/EntitiesTable/cells/__tests__/OwnerCell.test.tsx
new file mode 100644
index 00000000000..c5299d03efa
--- /dev/null
+++ b/workspaces/scorecard/plugins/scorecard/src/components/ScorecardPage/EntitiesTable/cells/__tests__/OwnerCell.test.tsx
@@ -0,0 +1,105 @@
+/*
+ * Copyright Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { render, screen } from '@testing-library/react';
+
+import { OwnerCell } from '../OwnerCell';
+
+jest.mock('@backstage/core-components', () => {
+ const React = require('react');
+ return {
+ Link: React.forwardRef(
+ (
+ { to, children, ...props }: { to: string; children: React.ReactNode },
+ ref: React.Ref,
+ ) => (
+
+ {children}
+
+ ),
+ ),
+ };
+});
+
+const mockEntityLink = jest.fn();
+jest.mock('@backstage/core-plugin-api', () => ({
+ useRouteRef: () => mockEntityLink,
+}));
+
+jest.mock('@backstage/catalog-model', () =>
+ jest.requireActual('@backstage/catalog-model'),
+);
+
+const mockUseEntityPresentation = jest.fn();
+jest.mock('@backstage/plugin-catalog-react', () => ({
+ entityRouteRef: { id: 'entity-route' },
+ useEntityPresentation: (ref: string) => mockUseEntityPresentation(ref),
+}));
+
+describe('OwnerCell', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockEntityLink.mockReturnValue('/catalog/default/group/team-a');
+ mockUseEntityPresentation.mockReturnValue({
+ primaryTitle: 'Team A',
+ secondaryTitle: 'group:default/team-a',
+ });
+ });
+
+ it('should render -- when ownerRef is undefined', () => {
+ render();
+
+ expect(screen.getByText('--')).toBeInTheDocument();
+ });
+
+ it('should render -- when ownerRef is empty string', () => {
+ render();
+
+ expect(screen.getByText('--')).toBeInTheDocument();
+ });
+
+ it('should render link with primary title when ownerRef is provided', () => {
+ render();
+
+ expect(screen.getByText('Team A')).toBeInTheDocument();
+ const link = screen.getByRole('link');
+ expect(link).toHaveAttribute('href', '/catalog/default/group/team-a');
+ expect(link).toHaveTextContent('Team A');
+ });
+
+ it('should resolve short owner ref to group:default/ ref', () => {
+ mockUseEntityPresentation.mockReturnValue({
+ primaryTitle: 'Platform',
+ secondaryTitle: 'group:default/platform',
+ });
+ mockEntityLink.mockReturnValue('/catalog/default/group/platform');
+
+ render();
+
+ expect(screen.getByText('Platform')).toBeInTheDocument();
+ expect(mockUseEntityPresentation).toHaveBeenCalledWith(
+ 'group:default/platform',
+ );
+ });
+
+ it('should pass full ref when ownerRef already contains colon', () => {
+ render();
+
+ expect(mockUseEntityPresentation).toHaveBeenCalledWith(
+ 'group:default/team-a',
+ );
+ });
+});
diff --git a/workspaces/scorecard/plugins/scorecard/src/components/ScorecardPage/ScorecardPage.tsx b/workspaces/scorecard/plugins/scorecard/src/components/ScorecardPage/ScorecardPage.tsx
new file mode 100644
index 00000000000..d8cd0bde8f1
--- /dev/null
+++ b/workspaces/scorecard/plugins/scorecard/src/components/ScorecardPage/ScorecardPage.tsx
@@ -0,0 +1,106 @@
+/*
+ * Copyright Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { useState } from 'react';
+import { useParams } from 'react-router-dom';
+
+import { Content, Page } from '@backstage/core-components';
+
+import Box from '@mui/material/Box';
+import Divider from '@mui/material/Divider';
+
+import { ScorecardHomepageCard } from '../ScorecardHomepageSection/ScorecardHomepageCard';
+import NotFoundState from '../Common/NotFoundState';
+import { useTranslation } from '../../hooks/useTranslation';
+
+import { ScorecardPageHeader } from './ScorecardPageHeader';
+import { EntitiesTable } from './EntitiesTable/EntitiesTable';
+
+export const ScorecardPage = () => {
+ const { metricId } = useParams<{ metricId?: string }>();
+
+ const [metricTitle, setMetricTitle] = useState('');
+ const [metricNotFound, setMetricNotFound] = useState(false);
+
+ const { t } = useTranslation();
+
+ const titleKey = `metric.${metricId}.title`;
+ const title = t(titleKey as any, {});
+ const finalTitle = title === titleKey ? metricTitle : title;
+
+ if (metricNotFound) {
+ return (
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
+
+ *': { height: '100%' },
+ '& > div[class*="MuiCard-root"]': {
+ height: '100%',
+ overflow: 'hidden',
+ display: 'flex',
+ flexDirection: 'column',
+ },
+ '& div[class*="MuiCardContent-root"]': {
+ flex: 1,
+ minHeight: 0,
+ },
+ }}
+ >
+
+
+
+
+
+ );
+};
diff --git a/workspaces/scorecard/plugins/scorecard/src/components/ScorecardPage/ScorecardPageHeader.tsx b/workspaces/scorecard/plugins/scorecard/src/components/ScorecardPage/ScorecardPageHeader.tsx
new file mode 100644
index 00000000000..d487a9e8ade
--- /dev/null
+++ b/workspaces/scorecard/plugins/scorecard/src/components/ScorecardPage/ScorecardPageHeader.tsx
@@ -0,0 +1,21 @@
+/*
+ * Copyright Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Header as BackstageHeader } from '@backstage/core-components';
+
+export const ScorecardPageHeader = ({ title }: { title: string }) => {
+ return ;
+};
diff --git a/workspaces/scorecard/plugins/scorecard/src/components/ScorecardPage/__tests__/ScorecardPage.test.tsx b/workspaces/scorecard/plugins/scorecard/src/components/ScorecardPage/__tests__/ScorecardPage.test.tsx
new file mode 100644
index 00000000000..3d2b4cb0c20
--- /dev/null
+++ b/workspaces/scorecard/plugins/scorecard/src/components/ScorecardPage/__tests__/ScorecardPage.test.tsx
@@ -0,0 +1,176 @@
+/*
+ * Copyright Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { act, render, screen } from '@testing-library/react';
+import { ThemeProvider, createTheme } from '@mui/material/styles';
+
+import { ScorecardPage } from '../ScorecardPage';
+
+const mockUseParams = jest.fn();
+jest.mock('react-router-dom', () => ({
+ useParams: () => mockUseParams(),
+}));
+
+jest.mock('../../../hooks/useTranslation', () => ({
+ useTranslation: () => ({
+ t: (key: string) => {
+ if (key === 'entitiesPage.unknownMetric') return 'Unknown metric';
+ return key;
+ },
+ }),
+}));
+
+jest.mock('@backstage/core-components', () => ({
+ Page: ({ children }: { children: React.ReactNode }) => (
+