Skip to content

Commit

Permalink
Refresh voting power button (#5990)
Browse files Browse the repository at this point in the history
# Motivation

If there are inactive neurons, the user should be able to reset their
voting-power-refreshed timestamp. In this PR, we add a button that makes
the necessary service calls to refresh the user’s neurons.

# Changes

- Add `ConfirmFollowingButton` component.

# Tests

- Added.
- Tested locally:


https://github.com/user-attachments/assets/6e71aace-4f5f-4426-9896-2de33bec290e

# Todos

- [ ] Add entry to changelog (if necessary).
Not necessary.
  • Loading branch information
mstrasinskis authored Dec 11, 2024
1 parent 96920de commit ac1e65b
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<script lang="ts">
import { i18n } from "$lib/stores/i18n";
import { startBusy, stopBusy } from "$lib/stores/busy.store";
import { refreshVotingPowerForNeurons } from "$lib/services/neurons.services";
import { createEventDispatcher } from "svelte";
export let neuronIds: bigint[];
const dispatcher = createEventDispatcher<{
nnsComplete: { successCount: number; totalCount: number };
}>();
const onClick = async () => {
startBusy({
initiator: "refresh-voting-power",
labelKey: "losing_rewards.confirming",
});
const totalCount = neuronIds.length;
const { successCount } = await refreshVotingPowerForNeurons({ neuronIds });
stopBusy("refresh-voting-power");
dispatcher("nnsComplete", { successCount, totalCount });
};
</script>

<button
on:click={onClick}
class="secondary"
data-tid="confirm-following-button-component"
>{$i18n.losing_rewards.confirm}</button
>
1 change: 1 addition & 0 deletions frontend/src/lib/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@
},
"losing_rewards": {
"description": "ICP neurons that are inactive for $period start losing voting rewards. In order to avoid losing rewards, vote manually, edit or confirm your following.",
"confirming": "Confirming following. This may take a moment.",
"confirm": "Confirm Following"
},
"losing_rewards_banner": {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/lib/stores/busy.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export type BusyStateInitiatorType =
| "import-token-importing"
| "import-token-removing"
| "import-token-updating"
| "refresh-voting-power"
| "change-neuron-visibility"
| "reporting-neurons"
| "reporting-transactions";
Expand Down
1 change: 1 addition & 0 deletions frontend/src/lib/types/i18n.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@ interface I18nNeurons {

interface I18nLosing_rewards {
description: string;
confirming: string;
confirm: string;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import * as api from "$lib/api/governance.api";
import ConfirmFollowingButton from "$lib/components/neuron-detail/actions/ConfirmFollowingButton.svelte";
import { neuronsStore } from "$lib/stores/neurons.store";
import { mockIdentity, resetIdentity } from "$tests/mocks/auth.store.mock";
import { mockFullNeuron, mockNeuron } from "$tests/mocks/neurons.mock";
import { ConfirmFollowingButtonPo } from "$tests/page-objects/ConfirmFollowingButton.page-object";
import { JestPageObjectElement } from "$tests/page-objects/jest.page-object";
import { allowLoggingInOneTestForDebugging } from "$tests/utils/console.test-utils";
import { runResolvedPromises } from "$tests/utils/timers.test-utils";
import { busyStore } from "@dfinity/gix-components";
import { nonNullish } from "@dfinity/utils";
import { render } from "@testing-library/svelte";
import { get } from "svelte/store";

describe("ConfirmFollowingButton", () => {
const neuronId1 = 1n;
const neuronId2 = 22n;
const neuronIds = [neuronId1, neuronId2];
const neurons = neuronIds.map((neuronId) => ({
...mockNeuron,
neuronId,
fullNeuron: {
...mockFullNeuron,
neuronId,
controller: mockIdentity.getPrincipal().toText(),
},
}));

let spyQueryNeurons;

beforeEach(() => {
resetIdentity();
allowLoggingInOneTestForDebugging();

neuronsStore.pushNeurons({ neurons, certified: true });
spyQueryNeurons = vi.spyOn(api, "queryNeurons").mockResolvedValue(neurons);
vi.spyOn(api, "refreshVotingPower").mockResolvedValue();
});

const renderComponent = async ({ nnsComplete = undefined, ...props }) => {
const { container, component } = render(ConfirmFollowingButton, props);

if (nonNullish(nnsComplete)) component.$on("nnsComplete", nnsComplete);

return ConfirmFollowingButtonPo.under(new JestPageObjectElement(container));
};

it("displays the button", async () => {
const po = await renderComponent({
neuronIds: [neuronId1, neuronId2],
});

expect(await po.getText()).toBe("Confirm Following");
});

it("returns the refreshed neuron count", async () => {
vi.spyOn(console, "error").mockReturnValue();

neuronsStore.pushNeurons({ neurons, certified: true });
let resolveRefreshVotingPower;
const spyRefreshVotingPower = vi
.spyOn(api, "refreshVotingPower")
.mockRejectedValueOnce(new Error())
.mockImplementationOnce(
() => new Promise((resolve) => (resolveRefreshVotingPower = resolve))
);

expect(spyQueryNeurons).toBeCalledTimes(0);
expect(spyRefreshVotingPower).toBeCalledTimes(0);

const nnsComplete = vi.fn();
const po = await renderComponent({
neuronIds: [neuronId1, neuronId2],
nnsComplete,
});

expect(get(busyStore)).toEqual([]);

await po.click();

// Wait for busy screen to show
await runResolvedPromises();
expect(get(busyStore)).toEqual([
{
initiator: "refresh-voting-power",
text: "Confirming following. This may take a moment.",
},
]);

resolveRefreshVotingPower();

// Wait for busy screen to hide
await runResolvedPromises();
expect(get(busyStore)).toEqual([]);

expect(spyQueryNeurons).toBeCalledTimes(2);
expect(spyRefreshVotingPower).toBeCalledTimes(2);

expect(nnsComplete).toBeCalledTimes(1);
expect(nnsComplete).toBeCalledWith(
expect.objectContaining({
detail: {
successCount: 1,
totalCount: 2,
},
})
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { BasePageObject } from "$tests/page-objects/base.page-object";
import type { PageObjectElement } from "$tests/types/page-object.types";

export class ConfirmFollowingButtonPo extends BasePageObject {
private static readonly TID = "confirm-following-button-component";

static under(element: PageObjectElement): ConfirmFollowingButtonPo {
return new ConfirmFollowingButtonPo(
element.byTestId(ConfirmFollowingButtonPo.TID)
);
}
}

0 comments on commit ac1e65b

Please sign in to comment.