Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/dfinity/nns-dapp into NNS1-…
Browse files Browse the repository at this point in the history
…3349-add-neuron-visibility-toggle
  • Loading branch information
coskucinkilic committed Sep 25, 2024
2 parents ae1f141 + 6fa25c4 commit 77487ec
Show file tree
Hide file tree
Showing 10 changed files with 183 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
type UserTokenData,
type UserTokenLoading,
} from "$lib/types/tokens-page";
import { isUserTokenData } from "$lib/utils/user-token.utils";
import { isUserTokenLoading } from "$lib/utils/user-token.utils";
import GoToDashboardButton from "./actions/GoToDashboardButton.svelte";
import GoToDetailIcon from "./actions/GoToDetailIcon.svelte";
import ReceiveButton from "./actions/ReceiveButton.svelte";
import SendButton from "./actions/SendButton.svelte";
Expand All @@ -16,15 +17,18 @@
const actionMapper: Record<
UserTokenAction,
ComponentType<SvelteComponent<{ userToken: UserTokenData }>>
ComponentType<
SvelteComponent<{ userToken: UserTokenData | UserTokenFailed }>
>
> = {
[UserTokenAction.GoToDetail]: GoToDetailIcon,
[UserTokenAction.Receive]: ReceiveButton,
[UserTokenAction.Send]: SendButton,
[UserTokenAction.GoToDashboard]: GoToDashboardButton,
};
let userToken: UserTokenData | undefined;
$: userToken = isUserTokenData(rowData) ? rowData : undefined;
let userToken: UserTokenData | UserTokenFailed | undefined;
$: userToken = isUserTokenLoading(rowData) ? undefined : rowData;
</script>

{#if nonNullish(userToken)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<script lang="ts">
import type { UserTokenData, UserTokenFailed } from "$lib/types/tokens-page";
import type { Principal } from "@dfinity/principal";
import LinkToDashboardCanister from "$lib/components/tokens/LinkToDashboardCanister.svelte";
export let userToken: UserTokenData | UserTokenFailed;
let canisterId: Principal;
$: canisterId = userToken.universeId;
</script>

<LinkToDashboardCanister {canisterId} />
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<script lang="ts">
import type { UserTokenData } from "$lib/types/tokens-page";
import type { UserTokenData, UserTokenFailed } from "$lib/types/tokens-page";
import { IconRight } from "@dfinity/gix-components";
// svelte-ignore unused-export-let
export let userToken: UserTokenData;
export let userToken: UserTokenData | UserTokenFailed;
</script>

<span data-tid="go-to-detail-icon-component">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
<script lang="ts">
import { ActionType } from "$lib/types/actions";
import type { UserTokenData } from "$lib/types/tokens-page";
import type { UserTokenData, UserTokenFailed } from "$lib/types/tokens-page";
import { IconQRCodeScanner } from "@dfinity/gix-components";
import { createEventDispatcher } from "svelte";
import { isUserTokenData } from "$lib/utils/user-token.utils";
export let userToken: UserTokenData;
// The UserTokenFailed type was added to unify the action types. However, this action only works with UserTokenData.
export let userToken: UserTokenData | UserTokenFailed;
const dispatcher = createEventDispatcher();
</script>

<button
class="icon-only"
data-tid="receive-button-component"
on:click|preventDefault|stopPropagation={() => {
dispatcher("nnsAction", { type: ActionType.Receive, data: userToken });
}}
>
<IconQRCodeScanner />
</button>
{#if isUserTokenData(userToken)}
<button
class="icon-only"
data-tid="receive-button-component"
on:click|preventDefault|stopPropagation={() => {
dispatcher("nnsAction", { type: ActionType.Receive, data: userToken });
}}
>
<IconQRCodeScanner />
</button>
{/if}
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
<script lang="ts">
import { ActionType } from "$lib/types/actions";
import type { UserTokenData } from "$lib/types/tokens-page";
import type { UserTokenData, UserTokenFailed } from "$lib/types/tokens-page";
import { IconUp } from "@dfinity/gix-components";
import { createEventDispatcher } from "svelte";
import { isUserTokenData } from "$lib/utils/user-token.utils";
export let userToken: UserTokenData;
// The UserTokenFailed type was added to unify the action types. Works only with UserTokenData.
export let userToken: UserTokenData | UserTokenFailed;
const dispatcher = createEventDispatcher();
</script>

<button
class="icon-only"
data-tid="send-button-component"
on:click|stopPropagation|preventDefault={() => {
dispatcher("nnsAction", { type: ActionType.Send, data: userToken });
}}
>
<IconUp />
</button>
{#if isUserTokenData(userToken)}
<button
class="icon-only"
data-tid="send-button-component"
on:click|stopPropagation|preventDefault={() => {
dispatcher("nnsAction", { type: ActionType.Send, data: userToken });
}}
>
<IconUp />
</button>
{/if}
1 change: 1 addition & 0 deletions frontend/src/lib/types/tokens-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export enum UserTokenAction {
Send = "send",
GoToDetail = "goToDetail",
Receive = "receive",
GoToDashboard = "goToDashboard",
}

export type UserTokenBase = {
Expand Down
11 changes: 6 additions & 5 deletions frontend/src/lib/utils/user-token.utils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import UNKNOWN_LOGO from "$lib/assets/question-mark.svg";
import type {
UserTokenData,
UserTokenFailed,
UserTokenLoading,
import {
UserTokenAction,
type UserTokenData,
type UserTokenFailed,
type UserTokenLoading,
} from "$lib/types/tokens-page";
import { Principal } from "@dfinity/principal";

Expand Down Expand Up @@ -33,5 +34,5 @@ export const toUserTokenFailed = (
logo: UNKNOWN_LOGO,
balance: "failed",
domKey: ledgerCanisterIdText,
actions: [],
actions: [UserTokenAction.GoToDashboard],
});
89 changes: 86 additions & 3 deletions frontend/src/tests/lib/pages/NnsProposals.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import NnsProposals from "$lib/pages/NnsProposals.svelte";
import { actionableNnsProposalsStore } from "$lib/stores/actionable-nns-proposals.store";
import { actionableProposalsSegmentStore } from "$lib/stores/actionable-proposals-segment.store";
import { authStore, type AuthStoreData } from "$lib/stores/auth.store";
import { overrideFeatureFlagsStore } from "$lib/stores/feature-flags.store";
import { neuronsStore } from "$lib/stores/neurons.store";
import {
proposalsFiltersStore,
Expand All @@ -24,7 +23,7 @@ import {
advanceTime,
runResolvedPromises,
} from "$tests/utils/timers.test-utils";
import type { ProposalInfo } from "@dfinity/nns";
import { Topic, type ProposalInfo } from "@dfinity/nns";
import { isNullish } from "@dfinity/utils";
import { waitFor } from "@testing-library/svelte";
import type { Subscriber } from "svelte/store";
Expand Down Expand Up @@ -64,7 +63,6 @@ describe("NnsProposals", () => {
describe("logged in user", () => {
describe("Matching results", () => {
beforeEach(() => {
overrideFeatureFlagsStore.reset();
vi.spyOn(governanceApi, "queryNeurons").mockResolvedValue([]);
actionableProposalsSegmentStore.set("all");
});
Expand Down Expand Up @@ -229,6 +227,91 @@ describe("NnsProposals", () => {
);
});

it("should not show old certified data if there is newer uncertified data", async () => {
// The test first loads proposals with a filter.
// While proposals are displayed based on the uncertified response,
// before the certified response arrives, the filter is removed.
// If the certified response for the filtered proposals arrives after
// the uncertified response for the unfiltered proposals, this should
// not result in displaying old data which doesn't match the current
// filters.
proposalsFiltersStore.filterTopics([Topic.Governance]);

const proposalRequests = [];
vi.spyOn(proposalsApi, "queryProposals").mockImplementation((args) => {
return new Promise<ProposalInfo[]>((resolve) => {
proposalRequests.push({
args,
resolve,
});
});
});

const matchingProposal: ProposalInfo = {
...mockProposals[0],
topic: Topic.Governance,
};
const nonMatchingProposal: ProposalInfo = {
...mockProposals[1],
topic: Topic.ProtocolCanisterManagement,
};

const po = await renderComponent();

expect(proposalRequests).toHaveLength(2);
expect(proposalRequests[0].args.certified).toBe(false);
expect(proposalRequests[0].args.includeTopics).toEqual([
Topic.Governance,
]);
expect(proposalRequests[1].args.certified).toBe(true);
expect(proposalRequests[1].args.includeTopics).toEqual([
Topic.Governance,
]);
// We resolve the uncertified request but not yet the certified request.
proposalRequests[0].resolve([matchingProposal]);

await runResolvedPromises();
// There is 1 proposal that matches the filter.
expect(await po.getProposalCardPos()).toHaveLength(1);

// Remove the filter.
const filters = po.getNnsProposalFiltersPo();
await filters.clickFiltersByTopicsButton();
const filterModal = filters.getFilterModalPo();
await filterModal.clickClearSelectionButton();
await filterModal.clickConfirmButton();

// Finish the fade transition of the modal.
await advanceTime(25);
await filterModal.waitForAbsent();

// Stop waiting for the debounce to reload proposals.
await advanceTime(500);

expect(proposalRequests).toHaveLength(4);
expect(proposalRequests[2].args.certified).toBe(false);
expect(proposalRequests[2].args.includeTopics).toEqual([]);
expect(proposalRequests[3].args.certified).toBe(true);
expect(proposalRequests[3].args.includeTopics).toEqual([]);

// We resolve the second *uncertified* request before the first
// *certified* request. Now there are 2 proposals.
proposalRequests[2].resolve([matchingProposal, nonMatchingProposal]);
await runResolvedPromises();
expect(await po.getProposalCardPos()).toHaveLength(2);

// When the old certified request (with 1 proposal) resolves, this
// should not result in displaying old data which doesn't match the
// current filters.
proposalRequests[1].resolve([matchingProposal]);
//await advanceTime(500);
await runResolvedPromises();
// We should still see both proposals from the request from after the
// filter was removed.
// TODO: Change this to 2 when the bug is fixed.
expect(await po.getProposalCardPos()).toHaveLength(1);
});

it("should not have actionable parameter in a proposal card href", async () => {
const po = await renderComponent();
const firstCardPos = (await po.getProposalCardPos())[0];
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/tests/page-objects/TokensTableRow.page-object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { PageObjectElement } from "$tests/types/page-object.types";
import { nonNullish } from "@dfinity/utils";
import { AmountDisplayPo } from "./AmountDisplay.page-object";
import { HashPo } from "./Hash.page-object";
import { LinkToDashboardCanisterPo } from "./LinkToDashboardCanister.page-object";
import { TooltipPo } from "./Tooltip.page-object";

export type TokensTableRowData = {
Expand Down Expand Up @@ -134,6 +135,10 @@ export class TokensTableRowPo extends ResponsiveTableRowPo {
return this.root.byTestId("go-to-detail-icon-component");
}

getGoToDashboardButton(): LinkToDashboardCanisterPo {
return LinkToDashboardCanisterPo.under({ element: this.root });
}

hasGoToDetailIcon(): Promise<boolean> {
return this.getGoToDetailIcon().isPresent();
}
Expand Down
33 changes: 33 additions & 0 deletions frontend/src/tests/routes/app/tokens/page.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -789,6 +789,39 @@ describe("Tokens route", () => {
}
}
});

it("should not display goto dashboard for not failed tokens", async () => {
const po = await renderPage();
const tokensPagePo = po.getTokensPagePo();
const ckBTCTokenRow = await tokensPagePo
.getTokensTable()
.getRowByName("ckBTC");
const notFailedTokenRow = await tokensPagePo
.getTokensTable()
.getRowByName("ZTOKEN1");

expect(
await ckBTCTokenRow.getGoToDashboardButton().isPresent()
).toEqual(false);
expect(
await notFailedTokenRow.getGoToDashboardButton().isPresent()
).toEqual(false);
});

it("should have view on dashboard action button", async () => {
const po = await renderPage();
const tokensPagePo = po.getTokensPagePo();
const failedTokenRow = await tokensPagePo
.getTokensTable()
.getRowByName(failedImportedTokenIdText);

expect(
await failedTokenRow.getGoToDashboardButton().isPresent()
).toEqual(true);
expect(await failedTokenRow.getGoToDashboardButton().getHref()).toEqual(
`https://dashboard.internetcomputer.org/canister/${failedImportedTokenIdText}`
);
});
});

describe("when logged out", () => {
Expand Down

0 comments on commit 77487ec

Please sign in to comment.