Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add components and classes used by legacy support [URO-190] #176

Merged
merged 13 commits into from
Apr 4, 2024
Merged
27 changes: 27 additions & 0 deletions src/common/components/FallbackNotFound.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { render, waitFor } from '@testing-library/react';
import { Provider } from 'react-redux';
import { MemoryRouter, Route, Routes } from 'react-router-dom';
import { createTestStore } from '../../app/store';
import FallbackNotFound from './FallbackNotFound';

describe('FallbackNotFound Component', () => {
test('Displays the expected content', async () => {
const { container } = render(
<Provider store={createTestStore()}>
<MemoryRouter initialEntries={[`/fallback/foo`]}>
<Routes>
<Route path={`/fallback/*`} element={<FallbackNotFound />} />
</Routes>
</MemoryRouter>
</Provider>
);

await waitFor(
() => {
expect(container).toHaveTextContent('Page Not Found');
expect(container).toHaveTextContent('kbase-ui path "foo" not found');
},
{ timeout: 1000 }
);
});
});
19 changes: 19 additions & 0 deletions src/common/components/FallbackNotFound.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useParams } from 'react-router-dom';
import PageNotFound from '../../features/layout/PageNotFound';

/**
* 404s from the embedded kbase-ui are redirected from
* legacy.DOMAIN/[some/path/here] to DOMAIN/fallback/[some/path/here].
*
* See Routes.tsx for handling of specific routes. A catchall route
* `/fallback/*` will forward all unhandled fallback routes to this component.
* Such routes are considered "404s", or unsupported pages.
*/

const FallbackNotFound = () => {
const params = useParams();
const path = params['*'];
return <PageNotFound message={`kbase-ui path "${path}" not found`} />;
};

export default FallbackNotFound;
15 changes: 15 additions & 0 deletions src/common/components/OverlayContainer.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@import "../../common/colors";

.main {
align-items: center;
background-color: use-color("white");
bottom: 0;
display: flex;
flex-direction: column;
justify-content: flex-start;
left: 0;
padding-top: 10%;
position: absolute;
right: 0;
top: 0;
}
19 changes: 19 additions & 0 deletions src/common/components/OverlayContainer.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { render, screen } from '@testing-library/react';
import OverlayContainer from './OverlayContainer';

describe('OverlayContainer Module', () => {
test('renders with children', () => {
const message = 'My Loading Overlay';
render(
<OverlayContainer>
<div>{message}</div>
</OverlayContainer>
);
expect(screen.getByText(message)).toBeVisible();
});

test('renders without message, no text visible', () => {
const { container } = render(<OverlayContainer />);
expect(container).not.toHaveTextContent(/.+/);
});
});
10 changes: 10 additions & 0 deletions src/common/components/OverlayContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { PropsWithChildren } from 'react';
import classes from './OverlayContainer.module.scss';

export type OverlayContainerProps = PropsWithChildren;

const OverlayContainer = ({ children }: OverlayContainerProps) => {
return <div className={classes.main}>{children}</div>;
};

export default OverlayContainer;
69 changes: 69 additions & 0 deletions src/common/testUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Some commonly used test values
export const WAIT_FOR_TIMEOUT = 1000;
export const WAIT_FOR_INTERVAL = 100;
export const CHANNEL_ID = 'my_channel_id';
export const UI_ORIGIN = 'http://localhost';

/**
* Send a window message in the format supported by SendMessage and ReceiveMessage.
*
* Useful for sending messages in the format supported by SendMessage and ReceiveMessage.
*
* Note that we cannot use postMessage, as jsDOM has bugs with postMessage support.
* Important for these tests is that the origin is missing.
* See: https://github.com/jsdom/jsdom/issues/2745
*
* @param name The message name
* @param channel The channel id
* @param payload The payload, arbitrary
* @param origin The origin for the recipient window; optional, defaulting to current
* window origin.
*/
export function sendWindowMessage(
fromWindow: Window,
toWindow: Window,
name: string,
channel: string,
payload: unknown,
options?: SendMessageOptions
) {
const data = { name, envelope: { channel }, payload };
// Must use the following, due to jsDOM:
toWindow.dispatchEvent(
new MessageEvent('message', {
source: fromWindow,
origin: (options && options.targetOrigin) || fromWindow.origin,
data,
})
);
}

export interface SendMessageOptions {
targetOrigin?: string;
}

export function makeWindowMessageSender(fromWindow: Window, toWindow: Window) {
return (
name: string,
channel: string,
payload: unknown,
options?: SendMessageOptions
) => {
sendWindowMessage(fromWindow, toWindow, name, channel, payload, options);
};
}

/**
* Sends a window message with arbitrary payload (data).
*
* Useful for modeling the sending of invalid messages.
*
* @param data The message payload, arbitrary
* @param origin The origin for the recipient window; optional, no default is provided,
* so it should adopt the DOM default, which is '/' which indicates same-origin.
*/
export function genericRawPostMessage(data: unknown, origin?: string) {
window.dispatchEvent(
new MessageEvent('message', { source: window, origin, data })
);
}
33 changes: 33 additions & 0 deletions src/features/layout/PageNotFound.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { render, screen } from '@testing-library/react';
import { Provider } from 'react-redux';
import { createTestStore } from '../../app/store';
import * as layoutSlice from '../layout/layoutSlice';
import PageNotFound, { TITLE } from './PageNotFound';

const PAGE_NOT_FOUND_TITLE = TITLE;

describe('PageNotFound Component', () => {
test('renders the default title and a message', () => {
const titleSpy = jest.spyOn(layoutSlice, 'usePageTitle');
const message = 'My Page Is Not Found';
render(
<Provider store={createTestStore()}>
<PageNotFound message={message} />
</Provider>
);
expect(screen.getByText(PAGE_NOT_FOUND_TITLE)).toBeVisible();
expect(screen.getByText(message)).toBeVisible();
expect(titleSpy).toHaveBeenCalledWith(PAGE_NOT_FOUND_TITLE);
});

test('renders the default title, even without a message.', () => {
const titleSpy = jest.spyOn(layoutSlice, 'usePageTitle');
render(
<Provider store={createTestStore()}>
<PageNotFound />
</Provider>
);
expect(screen.getByText(PAGE_NOT_FOUND_TITLE)).toBeVisible();
expect(titleSpy).toHaveBeenCalledWith(PAGE_NOT_FOUND_TITLE);
});
});
24 changes: 18 additions & 6 deletions src/features/layout/PageNotFound.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
import { FC } from 'react';
import { usePageTitle } from './layoutSlice';

const PageNotFound: FC = () => (
<>
export const TITLE = 'Page Not Found';

export interface PageNotFoundProps {
message?: string;
}
const PageNotFound = ({ message }: PageNotFoundProps) => {
function renderMessage() {
if (message) {
return <p>{message}</p>;
}
}
usePageTitle(TITLE);
return (
<section>
<h2>Page Not Found.</h2>
<h2>{TITLE}</h2>
{renderMessage()}
</section>
</>
);
);
};

export default PageNotFound;
Loading