Skip to content

Commit

Permalink
Add basic testing coverage (#931)
Browse files Browse the repository at this point in the history
* wip

* wip

* prettier

* added more tests

* fixed linting issues

* ci fix due to node version

* prettier

* tested telemetry
  • Loading branch information
danielisaacgeslin authored Jul 16, 2024
1 parent 0ec1bd4 commit 103020b
Show file tree
Hide file tree
Showing 15 changed files with 515 additions and 236 deletions.
3 changes: 2 additions & 1 deletion apps/connect/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ module.exports = {
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'@typescript-eslint/no-explicit-any': 0,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}
}
21 changes: 20 additions & 1 deletion apps/connect/jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,25 @@ export default {
".(ts|tsx)": "ts-jest",
},
moduleNameMapper: {
"@env": "<rootDir>/src/env/index.ts"
"@env": "<rootDir>/src/env/index.ts",
"uuid": require.resolve('uuid'),
},
collectCoverageFrom: [
"<rootDir>/src/**/*.{ts,tsx,js,jsx}",
"!<rootDir>/src/**/*.d.ts",
"!<rootDir>/src/main.tsx",
"!<rootDir>/src/utils/constants.ts",
"!<rootDir>/src/utils/styles.ts",
"!<rootDir>/src/env/**/*.*",
"!<rootDir>/src/theme/**/*.*",
],
coverageThreshold: {
global: {
// statements: 100,
// branches: 100,
// functions: 100,
// lines: 100
},
},
coverageReporters: ["lcov", "text", "text-summary"],
} as Config;
4 changes: 2 additions & 2 deletions apps/connect/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
"preview": "cross-env VITE_APP_CLUSTER=mainnet vite preview",
"format": "npm run prettier -- --write ./src",
"ts:check": "tsc --project ./tsconfig.json --noEmit --skipLibCheck",
"test": "cross-env VITE_APP_CLUSTER=testnet jest",
"test:watch": "npm run test -- --watch"
"test": "cross-env VITE_APP_CLUSTER=testnet jest --coverage",
"test:watch": "cross-env VITE_APP_CLUSTER=testnet jest --coverage --watchAll=true"
},
"dependencies": {
"@certusone/wormhole-sdk": "^0.10.10",
Expand Down
3 changes: 1 addition & 2 deletions apps/connect/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@ import { ENV } from "@env";
const defaultConfig: WormholeConnectConfig = {
...ENV.wormholeConnectConfig,
...((isPreview || isProduction) && {
eventHandler: eventHandler,
eventHandler,
}),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
isRouteSupportedHandler: async (td: any) => {
// Disable manual NTT for Lido wstETH
if (
Expand Down
7 changes: 4 additions & 3 deletions apps/connect/src/components/atoms/NewsBar.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import NewBarButton from "./NewsBarButton";
import useBannerMessageConfig, {
useMessages,
import {
useBannerMessageConfig,
type Message,
} from "../../hooks/useBannerMessage";
} from "../../hooks/useBannerMessageConfig";
import { useMessages } from "../../hooks/useMessages";
import Bar from "./Bar";

export type NewsBarProps = {
Expand Down
99 changes: 0 additions & 99 deletions apps/connect/src/hooks/useBannerMessage.ts

This file was deleted.

57 changes: 57 additions & 0 deletions apps/connect/src/hooks/useBannerMessageConfig.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { renderHook } from "@testing-library/react";
import { Message, useBannerMessageConfig } from "./useBannerMessageConfig";

describe("useBannerMessageConfig", () => {
let messages: Message[];

beforeEach(() => {
messages = [
{ start: new Date(Date.now() + 3000) },
{
start: new Date(Date.now() - 2000),
ends: new Date(Date.now() + 2000),
},
{ ends: new Date(Date.now() + 3000) },
{ start: new Date(Date.now() - 3000) },
{
start: new Date(Date.now() + 2000),
ends: new Date(Date.now() + 4000),
},
{ ends: new Date(Date.now() + 4000) },
{ ends: new Date(Date.now() - 4000) },
{ start: new Date(Date.now() + 4000) },
].map((m, i) => ({
...m,
background: `bg_${i}`,
content: <p>index {i}</p>,
}));
});

it("should get as a first priority a message that has no start date and ends in the future", () => {
const { result } = renderHook(() => useBannerMessageConfig(messages));
expect(result.current).toEqual(messages[2]);
});

it("should get as a second priority a message that has not started and ends in the future", () => {
const { result } = renderHook(() =>
useBannerMessageConfig([
messages[0],
messages[1],
/** messages[2] this one would be the priority if set */
messages[3],
messages[4],
messages[5],
messages[6],
messages[7],
])
);
expect(result.current).toEqual(messages[5]);
});

it("should return null when no message matches the criteria", () => {
const { result } = renderHook(() =>
useBannerMessageConfig([messages[0], messages[6]])
);
expect(result.current).toEqual(null);
});
});
36 changes: 36 additions & 0 deletions apps/connect/src/hooks/useBannerMessageConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useMemo } from "react";

export interface Message {
background: string;
button?: {
href: string;
label?: string;
background: string;
};
content: JSX.Element;
start?: Date;
ends?: Date;
}

const criteria = (left: Message, right: Message): number => {
if (left.start && right.start) {
return right.start.getTime() - left.start.getTime();
}
if (left.start) return 1;
if (right.start) return -1;
return 0;
};

export const useBannerMessageConfig = (messages: Message[]): Message | null => {
return useMemo(() => {
const now = new Date();
const message = [...messages]
.sort(criteria)
.find(
(message) =>
(!message.start || message.start < now) &&
(!message.ends || message.ends > now)
);
return message || null;
}, [messages]);
};
64 changes: 64 additions & 0 deletions apps/connect/src/hooks/useMessages.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { renderHook, waitFor } from "@testing-library/react";
import { useMessages } from "./useMessages";
import { PropsWithChildren } from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const Wrapper = ({ children }: PropsWithChildren) => (
<QueryClientProvider client={new QueryClient()}>
{children}
</QueryClientProvider>
);

describe("useMessages", () => {
beforeEach(() => {
global.fetch = jest
.fn()
.mockReturnValue({ status: 200, json: async () => [] });
});

it("should get relevant parsed messages", async () => {
const irrelevant = {
id: "first",
background: "yellow",
content: { color: "black", text: "first message" },
since: Date.now() - 5000,
until: Date.now() - 4000,
};
const relevant = {
id: "second",
background: "green",
content: { color: "white", text: "second message" },
since: Date.now() - 1000,
until: Date.now() + 1000,
};
global.fetch = jest.fn().mockResolvedValue({
status: 200,
json: async () => [irrelevant, relevant],
});
const { result } = renderHook(() => useMessages(), { wrapper: Wrapper });

expect(result.current).toEqual(undefined);
await waitFor(() => {
expect(result.current).toEqual([
{
background: "green",
button: undefined,
content: { color: "white", size: undefined, text: "second message" },
id: "second",
since: expect.any(Date),
until: expect.any(Date),
},
]);
});
});

it("should fallback to an empty array when fetching fails", async () => {
global.fetch = jest.fn().mockResolvedValue({ status: 500 });
const { result } = renderHook(() => useMessages(), { wrapper: Wrapper });

expect(result.current).toEqual(undefined);
await waitFor(() => {
expect(result.current).toEqual([]);
});
});
});
52 changes: 52 additions & 0 deletions apps/connect/src/hooks/useMessages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { useQuery } from "@tanstack/react-query";

export interface Banner {
id: string;
background: string;
button?: {
href: string;
label?: string;
background: string;
};
content: {
text: string;
color?: string;
size?: string;
};
since: Date;
until: Date;
}

const parse = (banner: Record<string, any>): Banner => {
const id = banner.id;
const background = banner.background;
const since = new Date(banner.since);
const until = new Date(banner.until);
const content = {
text: banner.content.text,
color: banner.content.color,
size: banner.content.size,
};
const button = banner.button;
return { id, background, since, until, content, button };
};

const fetchMessages = async (
location: string = "/data/banners.json"
): Promise<Banner[]> => {
const response = await fetch(location);
if (response.status !== 200) return [];
const json = await response.json();
return json.map((banner: Record<string, any>) => parse(banner));
};

export const useMessages = () => {
const now = new Date();
const allMessages = useQuery({
queryKey: ["messages"],
queryFn: () => fetchMessages(),
});
return allMessages.data?.filter(
({ until, since }: Banner) => since < now && until > now
);
};
Loading

0 comments on commit 103020b

Please sign in to comment.