Merge pull request #30311 from csuich2/issue-30300

fix(techdocs): handle undefined defaultPath in techdocs initial redirect
This commit is contained in:
Ben Lambert
2025-06-19 10:20:01 +02:00
committed by GitHub
3 changed files with 79 additions and 4 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-techdocs': patch
---
Fixed an issue causing TechDocs to not properly handle initial redirect.
@@ -0,0 +1,72 @@
/*
* Copyright 2025 The Backstage Authors
*
* 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 } from '@testing-library/react';
// We need to mock react-router-dom hooks used by useInitialRedirect
import { useLocation, useNavigate, useParams } from 'react-router-dom';
// Import the module from which the hook is defined
import { useInitialRedirect } from './dom';
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useLocation: jest.fn(),
useNavigate: jest.fn(),
useParams: jest.fn(),
}));
describe('useInitialRedirect', () => {
const mockNavigate = jest.fn();
beforeEach(() => {
// Reset mocks before each test
mockNavigate.mockReset();
(useNavigate as jest.Mock).mockReturnValue(mockNavigate);
(useLocation as jest.Mock).mockReturnValue({
pathname: '/docs/default/Component/backstage-demo',
});
// Simulate that no current path is provided
(useParams as jest.Mock).mockReturnValue({ '*': '' });
});
const TestComponent: React.FC<{ defaultPath?: string }> = ({
defaultPath,
}) => {
// Call hook that should trigger a redirect on mount only if defaultPath is a non-empty string.
useInitialRedirect(defaultPath);
return <div>Test</div>;
};
it('should not navigate when defaultPath is undefined', () => {
render(<TestComponent defaultPath={undefined} />);
expect(mockNavigate).not.toHaveBeenCalled();
});
it('should navigate when defaultPath is a non-empty string', () => {
render(<TestComponent defaultPath="/overview" />);
expect(mockNavigate).toHaveBeenCalledWith(
'/docs/default/Component/backstage-demo/overview',
{ replace: true },
);
});
it('should not navigate if currPath is non-empty', () => {
// Override useParams to simulate a non-empty currPath
(useParams as jest.Mock).mockReturnValue({ '*': 'existing-path' });
render(<TestComponent defaultPath={undefined} />);
expect(mockNavigate).not.toHaveBeenCalled();
});
});
@@ -61,15 +61,13 @@ const MOBILE_MEDIA_QUERY = 'screen and (max-width: 76.1875em)';
// If a defaultPath is specified then we should navigate to that path replacing the
// current location in the history. This should only happen on the initial load so
// navigating to the root of the docs doesn't also redirect.
const useInitialRedirect = (defaultPath?: string) => {
// const hasRun = useRef(false);
export const useInitialRedirect = (defaultPath?: string) => {
const location = useLocation();
const navigate = useNavigate();
const { '*': currPath = '' } = useParams();
useLayoutEffect(() => {
if (currPath === '' && defaultPath !== '') {
if (currPath === '' && defaultPath) {
navigate(`${location.pathname}${defaultPath}`, { replace: true });
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps