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

feat: unit tests #12

Merged
merged 8 commits into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion example/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.ts"></script>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>
25 changes: 1 addition & 24 deletions example/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,8 @@ import {

import "./index.css";

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { useState } from "react";

const appQueryClient = new QueryClient({
defaultOptions: {
queries: {
// With this config, the query will be re-fetched when this tab/window
// is refocused and the data has not been fetched for at least 1 minute
refetchOnWindowFocus: true,
refetchOnMount: false,
refetchOnReconnect: false,
staleTime: 1000 * 60 * 1,
gcTime: Infinity,
},
},
});

const passportEmbedParams = {
apiKey: import.meta.env.VITE_API_KEY,
scorerId: import.meta.env.VITE_SCORER_ID,
Expand Down Expand Up @@ -130,10 +115,6 @@ const Dashboard = () => {
{...passportEmbedParams}
address={address}
collapseMode={collapseMode}
// Generally you would not provide this, the widget has its own QueryClient.
// But this can be used to override query parameters or to share a QueryClient
// with the wider app and/or multiple widgets
queryClient={appQueryClient}
connectWalletCallback={async () => {
const address = await connectWallet();
setAddress(address);
Expand All @@ -152,11 +133,7 @@ const Dashboard = () => {
);
};

const App = () => (
<QueryClientProvider client={appQueryClient}>
<Dashboard />
</QueryClientProvider>
);
const App = () => <Dashboard />;

const rootElement = document.getElementById("root");
if (rootElement) {
Expand Down
256 changes: 249 additions & 7 deletions example/yarn.lock

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { JestConfigWithTsJest } from "ts-jest";

const config: JestConfigWithTsJest = {
preset: "ts-jest",
testEnvironment: "jsdom",
transform: {
"^.+\\.(ts|tsx)$": ["ts-jest", {}],
},
moduleNameMapper: {
"\\.(css|less|scss|sass)$": "identity-obj-proxy",
},
setupFilesAfterEnv: ["<rootDir>/src/jest.setup.ts"],
};

export default config;
20 changes: 17 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@passportxyz/passport-embed",
"version": "0.0.2",
"version": "0.0.3",
"description": "Passport XYZ Embeddable Widgets",
"repository": "https://github.com/passportxyz/passport-embed",
"license": "AGPL-3.0-or-later",
Expand Down Expand Up @@ -33,6 +33,7 @@
"dist"
],
"scripts": {
"test": "jest",
"build": "npm run build:bundle && npm run build:types",
"build:bundle": "webpack",
"build:types": "tsc --declaration --declarationDir dist --emitDeclarationOnly",
Expand All @@ -52,25 +53,38 @@
"@babel/preset-typescript": "^7.26.0",
"@babel/runtime": "^7.26.7",
"@babel/runtime-corejs3": "^7.26.7",
"@testing-library/react": "^16.0.1",
"@jest/globals": "^29.7.0",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.2.0",
"@types/jest": "^29.5.14",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"autoprefixer": "^10.4.20",
"babel-loader": "^9.2.1",
"core-js": "3",
"css-loader": "^7.1.2",
"identity-obj-proxy": "^3.0.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"postcss": "^8.4.47",
"postcss-cli": "^11.0.0",
"postcss-loader": "^8.1.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"style-loader": "^4.0.0",
"terser-webpack-plugin": "^5.3.11",
"ts-jest": "^29.2.5",
"ts-node": "^10.9.2",
"typescript": "^5.6.3",
"webpack": "^5.97.1",
"webpack-cli": "^6.0.1"
},
"dependencies": {
"@tanstack/react-query": "^5.62.11",
"axios": "^1.7.7",
"buffer": "^6.0.3"
"buffer": "^6.0.3",
"dompurify": "^3.2.4",
"html-react-parser": "^5.2.2"
}
}
4 changes: 2 additions & 2 deletions src/components/Body/ErrorBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import styles from "./Body.module.css";
import utilStyles from "../../utilStyles.module.css";
import { useHeaderControls } from "../../contexts/HeaderContext";
import { Button } from "../Button";
import { useResetPassportScore } from "../../hooks/usePassportScore";
import { useResetWidgetPassportScore } from "../../hooks/usePassportScore";

export const ErrorBody = ({ errorMessage }: { errorMessage: string }) => {
const { setSubtitle } = useHeaderControls();
const { resetPassportScore } = useResetPassportScore();
const { resetPassportScore } = useResetWidgetPassportScore();

useEffect(() => {
setSubtitle("ERROR");
Expand Down
14 changes: 8 additions & 6 deletions src/components/Body/PlatformVerification.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import utilStyles from "../../utilStyles.module.css";
import { useEffect, useState } from "react";
import { Buffer } from "buffer";
import { Button } from "../Button";
import { Hyperlink, usePlatformStatus } from "./ScoreTooLowBody";
import { Platform } from "../../hooks/useStampPages";
import { Hyperlink } from "./ScoreTooLowBody";
import { ScrollableDiv } from "../ScrollableDiv";
import {
useWidgetIsQuerying,
useWidgetVerifyCredentials,
} from "../../hooks/usePassportScore";
import { useQueryContext } from "../../contexts/QueryContext";
import { usePlatformStatus } from "../../hooks/usePlatformStatus";
import { Platform } from "../../hooks/useStampPages";

const DEFAULT_CHALLENGE_URL =
"https://iam.review.passport.xyz/api/v0.0.0/challenge";
Expand Down Expand Up @@ -98,6 +99,7 @@ export const PlatformVerification = ({
onClick={onClose}
className={styles.closeButton}
disabled={isQuerying}
data-testid="close-platform-button"
>
<CloseIcon />
</button>
Expand All @@ -115,7 +117,7 @@ export const PlatformVerification = ({
and come back after.
</div>
) : (
<div dangerouslySetInnerHTML={{ __html: platform.description }} />
<div>{platform.description}</div>
)}
</ScrollableDiv>
<Button
Expand All @@ -127,7 +129,7 @@ export const PlatformVerification = ({

console.log("DEBUG THIS ON CLICK VERIFY CREDENTIALS platform");
let signature, credential;
if (platform.requireSignature) {
if (platform.requiresSignature) {
// get the challenge and sign it
if (!queryProps.address) {
console.error("No address found");
Expand Down Expand Up @@ -156,10 +158,10 @@ export const PlatformVerification = ({
signature = await generateSignatureCallback(challengeToSign);
}

if (platform.requiresPopup && platform.popUpUrl) {
if (platform.requiresPopup && platform.popupUrl) {
// open the popup
const oAuthPopUpUrl = `${
platform.popUpUrl
platform.popupUrl
}?address=${encodeURIComponent(
queryProps.address || ""
)}&scorerId=${encodeURIComponent(
Expand Down
45 changes: 7 additions & 38 deletions src/components/Body/ScoreTooLowBody.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { QueryClient } from "@tanstack/react-query";
import styles from "./Body.module.css";
import utilStyles from "../../utilStyles.module.css";
import { Button } from "../Button";
import { useEffect, useMemo, useState } from "react";
import { useEffect, useState } from "react";
import { useHeaderControls } from "../../contexts/HeaderContext";
import { useWidgetPassportScore } from "../../hooks/usePassportScore";
import { usePaginatedStampPages, Platform } from "../../hooks/useStampPages";
Expand All @@ -11,16 +10,7 @@ import { RightArrow } from "../../assets/rightArrow";
import { ScrollableDiv } from "../ScrollableDiv";
import { PlatformVerification } from "./PlatformVerification";
import { useQueryContext } from "../../contexts/QueryContext";

type Credential = {
id: string;
weight: string;
};

type StampPage = {
header: string;
platforms: Platform[];
};
import { usePlatformStatus } from "../../hooks/usePlatformStatus";

export const Hyperlink = ({
href,
Expand Down Expand Up @@ -102,40 +92,19 @@ const PlatformButton = ({
);
};

export const usePlatformStatus = ({ platform }: { platform: Platform }) => {
const { data } = useWidgetPassportScore();

const claimedCredentialIds = Object.entries(data?.stamps || {}).reduce(
(claimedIds, [id, { score }]) => {
if (score > 0) {
claimedIds.push(id);
}
return claimedIds;
},
[] as string[]
);

const claimed = platform.credentials.some((credential) =>
claimedCredentialIds.includes(credential.id)
);

return useMemo(() => ({ claimed }), [claimed]);
};

const AddStamps = ({
export const AddStamps = ({
generateSignatureCallback,
}: {
generateSignatureCallback: (message: string) => Promise<string | undefined>;
}) => {
const { setSubtitle } = useHeaderControls();
const queryProps = useQueryContext();
const { scorerId, apiKey, queryClient, overrideIamUrl } = queryProps;
const { scorerId, apiKey, overrideIamUrl } = queryProps;
const { page, nextPage, prevPage, isFirstPage, isLastPage, loading, error } =
usePaginatedStampPages({
apiKey: apiKey,
scorerId: scorerId,
overrideIamUrl: overrideIamUrl,
queryClient: queryClient,
apiKey,
scorerId,
overrideIamUrl,
});
const [openPlatform, setOpenPlatform] = useState<Platform | null>(null);

Expand Down
28 changes: 28 additions & 0 deletions src/components/SanitizedHTMLComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useMemo } from "react";
import DOMPurify, { ElementHook } from "dompurify";
import parse from "html-react-parser";

const passportPurify = DOMPurify();

const afterSanitizeAttributes: ElementHook = (node) => {
// set all elements owning target to target=_blank and rel=noopener
// otherwise DOMPurify removes the target attribute
if ("target" in node) {
node.setAttribute("target", "_blank");
node.setAttribute("rel", "noopener");
}
};

const sanitize = (html: string) => {
passportPurify.addHook("afterSanitizeAttributes", afterSanitizeAttributes);

const sanitizedHTML = passportPurify.sanitize(html);

passportPurify.removeHook("afterSanitizeAttributes");

return sanitizedHTML;
};

export const SanitizedHTMLComponent = ({ html }: { html: string }) => {
return useMemo(() => html && parse(sanitize(html)), [html]);
};
15 changes: 5 additions & 10 deletions src/contexts/QueryContext.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { createContext, useContext, useMemo } from "react";
import { PassportEmbedProps } from "../hooks/usePassportScore";
import { QueryClient } from "@tanstack/react-query";
import { widgetQueryClient } from "../widgets/Widget";

type QueryContextValue = Pick<
PassportEmbedProps,
Expand All @@ -10,14 +8,12 @@ type QueryContextValue = Pick<
| "scorerId"
| "overrideIamUrl"
| "challengeSignatureUrl"
> & {
queryClient: QueryClient; // This makes queryClient required
};
| "oAuthPopUpUrl"
>;

export const QueryContext = createContext<QueryContextValue>({
apiKey: "",
scorerId: "",
queryClient: widgetQueryClient,
});

export const QueryContextProvider = ({
Expand All @@ -27,7 +23,7 @@ export const QueryContextProvider = ({
scorerId,
overrideIamUrl,
challengeSignatureUrl,
queryClient,
oAuthPopUpUrl,
}: {
children: React.ReactNode;
} & PassportEmbedProps) => {
Expand All @@ -38,16 +34,15 @@ export const QueryContextProvider = ({
scorerId,
overrideIamUrl,
challengeSignatureUrl,
// Use override if passed in, otherwise use the widget query client
queryClient: queryClient || widgetQueryClient,
oAuthPopUpUrl,
}),
[
apiKey,
address,
scorerId,
overrideIamUrl,
challengeSignatureUrl,
queryClient,
oAuthPopUpUrl,
]
);

Expand Down
24 changes: 24 additions & 0 deletions src/hooks/usePassportQueryClient.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { QueryClient } from "@tanstack/react-query";

let passportQueryClient: QueryClient;

const DEFAULT_OPTIONS = {
queries: {
refetchOnWindowFocus: true,
refetchOnMount: true,
staleTime: 60000,
gcTime: 86400000,
retry: 2,
},
};

const getQueryClient = () => {
if (!passportQueryClient) {
passportQueryClient = new QueryClient({
defaultOptions: DEFAULT_OPTIONS,
});
}
return passportQueryClient;
};

export const usePassportQueryClient = () => getQueryClient();
Loading