Skip to content
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { fireEvent, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { ApiXTdhGrantStatus } from "@/generated/models/ApiXTdhGrantStatus";
import GroupCreateXtdhGrantModal from "@/components/groups/page/create/config/xtdh-grant/GroupCreateXtdhGrantModal";
import type { ApiXTdhGrant } from "@/generated/models/ApiXTdhGrant";

jest.mock("react-dom", () => ({
...jest.requireActual("react-dom"),
createPortal: (node: any) => node,
}));

const mockUseXtdhGrantsSearchQuery = jest.fn();
jest.mock("@/hooks/useXtdhGrantsSearchQuery", () => ({
useXtdhGrantsSearchQuery: (...args: unknown[]) =>
mockUseXtdhGrantsSearchQuery(...args),
}));

const identitySearchMock = jest.fn(
(props: { setIdentity: (value: string) => void }) => (
<button
type="button"
data-testid="grantor-select"
onClick={() => props.setIdentity("0xAbC123")}
>
Select Grantor
</button>
)
);

jest.mock("@/components/utils/input/identity/IdentitySearch", () => ({
__esModule: true,
IdentitySearchSize: {
SM: "SM",
MD: "MD",
},
default: (props: { setIdentity: (value: string) => void }) =>
identitySearchMock(props),
}));

const queryState = () => ({
grants: [],
totalCount: 0,
isLoading: false,
isError: false,
errorMessage: undefined,
refetch: jest.fn().mockResolvedValue(undefined),
hasNextPage: false,
fetchNextPage: jest.fn(),
isFetchingNextPage: false,
});

const renderModal = ({
isOpen = true,
onClose = jest.fn(),
onGrantSelect = () => undefined,
}: {
isOpen?: boolean;
onClose?: () => void;
onGrantSelect?: (grant: ApiXTdhGrant) => void;
} = {}) =>
render(
<GroupCreateXtdhGrantModal
isOpen={isOpen}
selectedGrantId={null}
onClose={onClose}
onGrantSelect={onGrantSelect}
/>
);

describe("GroupCreateXtdhGrantModal", () => {
beforeEach(() => {
jest.clearAllMocks();
mockUseXtdhGrantsSearchQuery.mockReturnValue(queryState());
});

it("does not send grantor filter until an identity is selected", () => {
renderModal();

expect(mockUseXtdhGrantsSearchQuery).toHaveBeenCalledWith(
expect.objectContaining({
grantor: null,
targetCollectionName: null,
statuses: [ApiXTdhGrantStatus.Granted],
enabled: true,
pageSize: 20,
})
);
});

it("uses the selected identity wallet as grantor filter", async () => {
const user = userEvent.setup();
renderModal();

await user.click(screen.getByTestId("grantor-select"));

expect(mockUseXtdhGrantsSearchQuery).toHaveBeenLastCalledWith(
expect.objectContaining({
grantor: "0xabc123",
})
);
});

it("clears selected grantor when filters are reset", async () => {
const user = userEvent.setup();
renderModal();

await user.click(screen.getByTestId("grantor-select"));
expect(mockUseXtdhGrantsSearchQuery).toHaveBeenLastCalledWith(
expect.objectContaining({
grantor: "0xabc123",
})
);

await user.click(screen.getByRole("button", { name: "Clear filters" }));
expect(mockUseXtdhGrantsSearchQuery).toHaveBeenLastCalledWith(
expect.objectContaining({
grantor: null,
})
);
});

it("keeps collection input accessible by name without visible label", () => {
renderModal();

expect(
screen.getByRole("textbox", { name: "Collection name" })
).toBeInTheDocument();
});

it("closes when Escape is pressed while open", () => {
const onClose = jest.fn();
renderModal({ onClose });

fireEvent.keyDown(window, { key: "Escape" });

expect(onClose).toHaveBeenCalledTimes(1);
});

it("does not close when Escape is pressed while closed", () => {
const onClose = jest.fn();
renderModal({ isOpen: false, onClose });

fireEvent.keyDown(window, { key: "Escape" });

expect(onClose).not.toHaveBeenCalled();
});
});
23 changes: 18 additions & 5 deletions components/groups/page/create/GroupCreate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,14 @@ export default function GroupCreate({

const [isFetching, setIsFetching] = useState(
loadingOriginalGroup ||
loadingOriginalGroupWallets ||
loadingOriginalGroupExcludedWallets
loadingOriginalGroupWallets ||
loadingOriginalGroupExcludedWallets
);
useEffect(() => {
setIsFetching(
loadingOriginalGroup ||
loadingOriginalGroupWallets ||
loadingOriginalGroupExcludedWallets
loadingOriginalGroupWallets ||
loadingOriginalGroupExcludedWallets
);
}, [
loadingOriginalGroup,
Expand Down Expand Up @@ -115,6 +115,7 @@ export default function GroupCreate({
owns_nfts: [],
identity_addresses: null,
excluded_identity_addresses: null,
is_beneficiary_of_grant_id: null,
},
is_private: false,
});
Expand Down Expand Up @@ -155,6 +156,8 @@ export default function GroupCreate({
owns_nfts: originalGroup.group.owns_nfts,
identity_addresses: originalGroupWallets ?? [],
excluded_identity_addresses: originalGroupExcludedWallets ?? [],
is_beneficiary_of_grant_id:
originalGroup.group.is_beneficiary_of_grant_id ?? null,
},
is_private: originalGroup.is_private ?? false,
});
Expand Down Expand Up @@ -211,7 +214,7 @@ export default function GroupCreate({
<div className="tw-flex tw-flex-col tw-gap-y-6 sm:tw-gap-y-8">
<div className="tw-space-y-4 sm:tw-space-y-5">
<GroupCreateHeader />
<div className="tw-grid tw-grid-cols-1 lg:tw-grid-cols-2 tw-gap-6">
<div className="tw-grid tw-grid-cols-1 tw-gap-6 lg:tw-grid-cols-2">
<GroupCreateName
name={groupConfig.name}
setName={(name) =>
Expand Down Expand Up @@ -244,6 +247,7 @@ export default function GroupCreate({
wallets={groupConfig.group.identity_addresses}
excludeWallets={groupConfig.group.excluded_identity_addresses}
nfts={groupConfig.group.owns_nfts}
beneficiaryGrantId={groupConfig.group.is_beneficiary_of_grant_id}
iAmIncluded={iAmIncluded}
setLevel={(level) =>
setGroupConfig((prev) => ({
Expand Down Expand Up @@ -293,6 +297,15 @@ export default function GroupCreate({
group: { ...prev.group, owns_nfts: nfts },
}))
}
setBeneficiaryGrantId={(grantId) =>
setGroupConfig((prev) => ({
...prev,
group: {
...prev.group,
is_beneficiary_of_grant_id: grantId ?? null,
},
}))
}
/>
<GroupCreateActions
originalGroup={originalGroup ?? null}
Expand Down
15 changes: 13 additions & 2 deletions components/groups/page/create/config/GroupCreateConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import GroupCreateRep from "./GroupCreateRep";
import GroupCreateTDH from "./GroupCreateTDH";
import GroupCreateCollections from "./nfts/GroupCreateCollections";
import GroupCreateNfts from "./nfts/GroupCreateNfts";
import GroupCreateXtdhGrant from "./xtdh-grant/GroupCreateXtdhGrant";
import GroupCreateWallets, {
GroupCreateWalletsType,
} from "./wallets/GroupCreateWallets";
Expand All @@ -18,6 +19,7 @@ export default function GroupCreateConfig({
wallets,
excludeWallets,
nfts,
beneficiaryGrantId,
iAmIncluded,
setLevel,
setTDH,
Expand All @@ -26,6 +28,7 @@ export default function GroupCreateConfig({
setWallets,
setExcludeWallets,
setNfts,
setBeneficiaryGrantId,
}: {
readonly level: ApiCreateGroupDescription["level"];
readonly tdh: ApiCreateGroupDescription["tdh"];
Expand All @@ -34,6 +37,7 @@ export default function GroupCreateConfig({
readonly wallets: ApiCreateGroupDescription["identity_addresses"];
readonly excludeWallets: ApiCreateGroupDescription["excluded_identity_addresses"];
readonly nfts: ApiCreateGroupDescription["owns_nfts"];
readonly beneficiaryGrantId: ApiCreateGroupDescription["is_beneficiary_of_grant_id"];
readonly iAmIncluded: boolean;
readonly setLevel: (level: ApiCreateGroupDescription["level"]) => void;
readonly setTDH: (tdh: ApiCreateGroupDescription["tdh"]) => void;
Expand All @@ -46,18 +50,25 @@ export default function GroupCreateConfig({
wallets: ApiCreateGroupDescription["excluded_identity_addresses"]
) => void;
readonly setNfts: (nfts: ApiCreateGroupDescription["owns_nfts"]) => void;
readonly setBeneficiaryGrantId: (
grantId: ApiCreateGroupDescription["is_beneficiary_of_grant_id"]
) => void;
}) {
return (
<div className="tw-grid tw-grid-cols-2 tw-gap-x-6 tw-gap-y-6 sm:tw-gap-y-8">
<div className="tw-space-y-4 sm:tw-space-y-5 tw-col-span-full">
<div className="tw-col-span-full tw-space-y-4 sm:tw-space-y-5">
<GroupCreateConfigHeader />
<div className="tw-gap-x-6 tw-grid tw-grid-cols-1 lg:tw-grid-cols-2 tw-gap-y-4 sm:tw-gap-y-6">
<div className="tw-grid tw-grid-cols-1 tw-gap-x-6 tw-gap-y-4 sm:tw-gap-y-6 lg:tw-grid-cols-2">
<GroupCreateLevel level={level} setLevel={setLevel} />
<GroupCreateTDH tdh={tdh} setTDH={setTDH} />
<GroupCreateCIC cic={cic} setCIC={setCIC} />
<GroupCreateRep rep={rep} setRep={setRep} />
<GroupCreateNfts nfts={nfts} setNfts={setNfts} />
<GroupCreateCollections nfts={nfts} setNfts={setNfts} />
<GroupCreateXtdhGrant
beneficiaryGrantId={beneficiaryGrantId}
setBeneficiaryGrantId={setBeneficiaryGrantId}
/>
</div>
</div>

Expand Down
Loading