Skip to content

Commit

Permalink
Merge branch '18nlinks-scrolling-to-hash' into staging
Browse files Browse the repository at this point in the history
  • Loading branch information
konzz committed Dec 13, 2024
2 parents 22ffd12 + f559e2f commit 2c1bded
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 54 deletions.
35 changes: 28 additions & 7 deletions app/react/I18N/components/I18NLink.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import { useNavigate, NavLink } from 'react-router-dom';
import { useNavigate, NavLink, useLocation } from 'react-router-dom';
import { omit } from 'lodash';

const defaultProps = {
Expand Down Expand Up @@ -33,17 +33,35 @@ const I18NLink = (props: I18NLinkProps) => {
} = props;

const navigate = useNavigate();
const location = useLocation();

const scrollToHashWithRetry = (hash: string, retries = 10, delay = 100) => {
if (!hash) return;
if (retries <= 0) return;
setTimeout(() => {
const element = document.getElementById(hash.substring(1));
if (element) {
element.scrollIntoView({ behavior: 'smooth' });
} else {
scrollToHashWithRetry(hash, retries - 1, delay);
}
}, delay);
};

const _navigate = () => {
navigate(to, { replace });
scrollToHashWithRetry(location.hash);
};

const onClickHandler = (e: { preventDefault: () => void }) => {
e.preventDefault();

if (disabled) return;

if (onClick && confirmTitle) {
props.mainContext.confirm({
accept: () => {
onClick(e);
navigate(to, { replace });
_navigate();
},
title: confirmTitle,
message: confirmMessage,
Expand All @@ -53,13 +71,16 @@ const I18NLink = (props: I18NLinkProps) => {

if (onClick) {
onClick(e);
navigate(to, { replace });
_navigate();
return;
}

navigate(to, { replace });
_navigate();
};

useEffect(() => {
scrollToHashWithRetry(location.hash);
}, [location]);

const newProps = omit(props, [
'dispatch',
'onClick',
Expand Down
112 changes: 66 additions & 46 deletions app/react/I18N/components/specs/I18NLink.spec.js
Original file line number Diff line number Diff line change
@@ -1,71 +1,91 @@
/**
* @jest-environment jsdom
*/
/* eslint-disable max-statements */
import React from 'react';
import { shallow } from 'enzyme';
import { NavLink } from 'react-router-dom';

import { I18NLink, mapStateToProps } from '../I18NLink';

jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useNavigate: () => () => {},
}));
import { render, screen, fireEvent } from '@testing-library/react';
import { I18NLink } from '../I18NLink';
import { MemoryRouter, Route, Routes } from 'react-router-dom';

describe('I18NLink', () => {
let component;
let props;
const clickAction = () => {};
const mouseOverAction = () => {};
const event = jasmine.createSpyObj(['preventDefault']);
const clickAction = jest.fn();

beforeEach(() => {
jest.clearAllMocks();
props = {
locale: 'es',
to: '/templates',
activeClass: 'is-active',
to: '/page/1#section1',
onClick: clickAction,
onMouseOver: mouseOverAction,
dispatch: () => {},
dispatch: jest.fn(),
};
});

const render = () => {
component = shallow(<I18NLink {...props} />);
const renderComponent = (initialEntries = ['/']) => {
render(
<MemoryRouter initialEntries={initialEntries}>
<Routes>
<Route path="/" element={<I18NLink {...props}>Section 1</I18NLink>} />
<Route
path="/page/1"
element={
<div>
<I18NLink {...props}>Section 1</I18NLink>
<p id="section0">Something to forget</p>
<p id="section1">Something to remember</p>
</div>
}
/>
</Routes>
</MemoryRouter>
);
};

describe('render', () => {
it('should pass other props, except for dispatch', () => {
spyOn(props, 'onClick');
render();
const link = component.find(NavLink);
expect(link.props().onMouseOver).toBe(mouseOverAction);
expect(link.props().dispatch).toBeUndefined();
component.simulate('click', event);
expect(props.onClick).toHaveBeenCalledWith(event);
expect(link.props().to).toBe(props.to);
it('should trigger onClick', () => {
renderComponent();
const link = screen.getByText('Section 1');
fireEvent.click(link);
expect(clickAction).toHaveBeenCalled();
expect(link).toHaveAttribute('href', props.to);
});
});

describe('when its disabled', () => {
it('should do nothing when clicked', () => {
I18NLink.navigate = jasmine.createSpy('navigate');
spyOn(props, 'onClick');
props.disabled = true;
render();
component.simulate('click', event);
expect(props.onClick).not.toHaveBeenCalled();
expect(event.preventDefault).toHaveBeenCalled();
expect(I18NLink.navigate).not.toHaveBeenCalled();
it('should navigate when clicked', () => {
renderComponent();
const link = screen.getByRole('link');
fireEvent.click(link);
expect(screen.getByText('Something to remember')).toBeInTheDocument();
});

it('should scroll to the hash element', () => {
renderComponent(['/page/1']);
const link = screen.getByRole('link');
const section = screen.getByText('Something to remember');
section.scrollIntoView = jest.fn();

jest.useFakeTimers();
fireEvent.click(link);
jest.runAllTimers();

expect(section.scrollIntoView).toHaveBeenCalled();
});
});

describe('mapStateToProps', () => {
it('should append the locale to the "to" url', () => {
expect(mapStateToProps({ locale: 'es' }, props).to).toBe('/es/templates');
describe('when its disabled', () => {
it('should not trigger onClick', () => {
props.disabled = true;
renderComponent();
const link = screen.getByText('Section 1');
fireEvent.click(link);
expect(clickAction).not.toHaveBeenCalled();
});

describe('when there is no locale', () => {
it('should pass the "to" url unchanged', () => {
expect(mapStateToProps({}, props).to).toBe('/templates');
});
it('should not navigate when clicked', () => {
props.disabled = true;
renderComponent();
const link = screen.getByRole('link');
fireEvent.click(link);
expect(screen.queryByText('Something to remember')).not.toBeInTheDocument();
});
});
});
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "uwazi",
"version": "1.194.0-rc7",
"version": "1.194.0-rc8",
"description": "Uwazi is a free, open-source solution for organising, analysing and publishing your documents.",
"keywords": [
"react"
Expand Down

0 comments on commit 2c1bded

Please sign in to comment.