🔗 feat: Open Footer Site Links in a New Tab (#15019)

The footer's LibreChat site link navigated away from the chat in the
same tab. Footer content links (default version link and custom footer
markdown) now open in a new tab with noopener noreferrer.

Privacy policy and terms of service links keep same-tab navigation,
following the WCAG decision in #10997.
This commit is contained in:
Marco Beretta 2026-08-20 17:17:52 +02:00 committed by GitHub
parent 7569404a7c
commit f1fbaeb6d8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 58 additions and 1 deletions

View file

@ -63,7 +63,8 @@ function Footer({ className, startupConfig }: FooterProps) {
<a
className="text-text-secondary underline"
href={href}
rel="noreferrer"
target="_blank"
rel="noopener noreferrer"
{...otherProps}
>
{children}

View file

@ -0,0 +1,56 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import Footer from '../Footer';
jest.mock('react-gtm-module', () => ({
__esModule: true,
default: { initialize: jest.fn() },
}));
jest.mock('~/data-provider', () => ({
useGetStartupConfig: jest.fn(() => ({ data: undefined, isFetching: false, error: null })),
}));
const mockTranslations: Record<string, string> = {
com_ui_latest_footer: 'Every AI for Everyone.',
com_ui_privacy_policy: 'Privacy policy',
com_ui_terms_of_service: 'Terms of service',
};
jest.mock('~/hooks', () => ({
useLocalize: () => (key: string) => mockTranslations[key] ?? key,
}));
describe('Footer', () => {
test('opens the default LibreChat site link in a new tab', () => {
render(<Footer startupConfig={null} />);
const link = screen.getByRole('link', { name: /LibreChat/ });
expect(link).toHaveAttribute('href', 'https://librechat.ai');
expect(link).toHaveAttribute('target', '_blank');
expect(link).toHaveAttribute('rel', 'noopener noreferrer');
});
test('opens custom footer markdown links in a new tab', () => {
render(<Footer startupConfig={{ customFooter: '[Docs](https://example.com/docs)' }} />);
const link = screen.getByRole('link', { name: 'Docs' });
expect(link).toHaveAttribute('href', 'https://example.com/docs');
expect(link).toHaveAttribute('target', '_blank');
expect(link).toHaveAttribute('rel', 'noopener noreferrer');
});
test('keeps privacy policy and terms of service links in the same tab', () => {
render(
<Footer
startupConfig={{
interface: {
privacyPolicy: { externalUrl: 'https://example.com/privacy' },
termsOfService: { externalUrl: 'https://example.com/terms' },
},
}}
/>,
);
expect(screen.getByRole('link', { name: 'Privacy policy' })).not.toHaveAttribute('target');
expect(screen.getByRole('link', { name: 'Terms of service' })).not.toHaveAttribute('target');
});
});