Skip to content

Commit

Permalink
Merge pull request #3087 from dusk-network/feature-3085
Browse files Browse the repository at this point in the history
web-wallet: Refactor Stake Component
  • Loading branch information
nortonandreev authored Nov 29, 2024
2 parents 04b020d + ea15576 commit bf5342f
Show file tree
Hide file tree
Showing 8 changed files with 1,174 additions and 337 deletions.
322 changes: 118 additions & 204 deletions web-wallet/src/lib/components/Stake/Stake.svelte

Large diffs are not rendered by default.

168 changes: 168 additions & 0 deletions web-wallet/src/lib/components/Stake/Unstake.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
<svelte:options immutable={true} />

<script>
import { createEventDispatcher, onMount } from "svelte";
import { fade } from "svelte/transition";
import { MESSAGES } from "$lib/constants";
import { areValidGasSettings } from "$lib/contracts";
import { luxToDusk } from "$lib/dusk/currency";
import {
AnchorButton,
Badge,
Stepper,
Wizard,
WizardStep,
} from "$lib/dusk/components";
import {
ContractStatusesList,
GasSettings,
OperationResult,
} from "$lib/components";
import StakeOverview from "./StakeOverview.svelte";
/** @type {(...args: any[]) => Promise<string>} */
export let execute;
/** @type {(amount: number) => string} */
export let formatter;
/** @type {GasStoreContent} */
export let gasLimits;
/** @type {ContractGasSettings} */
export let gasSettings;
/** @type {bigint} */
export let maxAmount;
/** @type {ContractStatus[]} */
export let statuses;
/** @type {string} */
export let operationCtaLabel;
/** @type {string} */
export let operationCtaIconPath;
/** @type {string} */
export let operationOverviewLabel;
let activeStep = 0;
let { gasLimit, gasPrice } = gasSettings;
let isGasValid = false;
/**
* We are forced to keep `amount`
* as number if we want to use
* Svelte's binding.
*/
const amount = luxToDusk(maxAmount);
const steps = [{ label: "Overview" }, { label: "Done" }];
const dispatch = createEventDispatcher();
const resetOperation = () => dispatch("operationChange", "");
/**
* @param {{detail: {price: bigint, limit: bigint}}} event
*/
const setGasValues = (event) => {
isGasValid = areValidGasSettings(event.detail.price, event.detail.limit);
if (isGasValid) {
gasPrice = event.detail.price;
gasLimit = event.detail.limit;
}
};
onMount(() => {
isGasValid = areValidGasSettings(gasPrice, gasLimit);
});
$: fee = gasLimit * gasPrice;
</script>

<div class="operation">
<Wizard steps={2} let:key>
<div slot="stepper">
<Stepper {activeStep} {steps} showStepLabelWhenInactive={false} />
</div>

<!-- OPERATION OVERVIEW STEP -->
<WizardStep
step={0}
{key}
backButton={{
action: () => resetOperation(),
disabled: false,
}}
nextButton={{
action: () => activeStep++,
disabled: !isGasValid,
icon: {
path: operationCtaIconPath,
position: "before",
},
label: operationCtaLabel,
variant: "primary",
}}
>
<div in:fade|global class="operation__unstake">
<ContractStatusesList {statuses} />
<Badge text="REVIEW TRANSACTION" variant="warning" />
<StakeOverview
label={operationOverviewLabel}
value={formatter(amount)}
/>

<GasSettings
{formatter}
{fee}
limit={gasSettings.gasLimit}
limitLower={gasLimits.gasLimitLower}
limitUpper={gasLimits.gasLimitUpper}
price={gasSettings.gasPrice}
priceLower={gasLimits.gasPriceLower}
on:gasSettings={setGasValues}
/>
</div>
</WizardStep>

<!-- OPERATION RESULT STEP -->
<WizardStep step={1} {key} showNavigation={false}>
<OperationResult
errorMessage="Transaction failed"
onBeforeLeave={resetOperation}
operation={execute(gasPrice, gasLimit)}
pendingMessage="Processing transaction"
successMessage="Transaction created"
>
<svelte:fragment slot="success-content" let:result={hash}>
<p>{MESSAGES.TRANSACTION_CREATED}</p>
{#if hash}
<AnchorButton
href={`/explorer/transactions/transaction?id=${hash}`}
on:click={resetOperation}
text="VIEW ON BLOCK EXPLORER"
rel="noopener noreferrer"
target="_blank"
/>
{/if}
</svelte:fragment>
</OperationResult>
</WizardStep>
</Wizard>
</div>

<style lang="postcss">
.operation {
&__unstake {
display: flex;
flex-direction: column;
gap: 1.2em;
}
}
</style>
109 changes: 0 additions & 109 deletions web-wallet/src/lib/components/__tests__/Stake.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,6 @@ describe("Stake", () => {

const baseProps = {
execute: vi.fn().mockResolvedValue(lastTxId),

/** @type {StakeType} */
flow: "stake",
formatter,
gasLimits: {
gasLimitLower: 10000000n,
Expand All @@ -55,9 +52,7 @@ describe("Stake", () => {
},
hideStakingNotice: true,
minAllowedStake: 1_234_000_000_000n,
rewards: 345_000_000_000n,
spendable: 10_000_000_000_000n,
staked: 278_000_000_000n,
statuses: [
{
label: "Spendable",
Expand Down Expand Up @@ -296,108 +291,4 @@ describe("Stake", () => {
expect(() => getByRole("link", { name: /explorer/i })).toThrow();
});
});

describe("Unstake operation", () => {
const expectedExplorerLink = `/explorer/transactions/transaction?id=${lastTxId}`;

beforeAll(() => {
vi.useFakeTimers();
});

afterAll(() => {
vi.useRealTimers();
});

it("should perform an ustake, give a success message and supply a link to see the transaction in the explorer", async () => {
/** @type {import("svelte").ComponentProps<Stake>} */
const props = { ...baseProps, flow: "unstake" };

const { getByRole, getByText } = render(Stake, props);

await vi.advanceTimersToNextTimerAsync();

await fireEvent.click(getByRole("button", { name: "Unstake" }));

expect(baseProps.execute).toHaveBeenCalledTimes(1);
expect(baseProps.execute).toHaveBeenCalledWith(
baseProps.gasSettings.gasPrice,
baseProps.gasSettings.gasLimit
);

const explorerLink = getByRole("link", { name: /explorer/i });

expect(getByText("Transaction created")).toBeInTheDocument();
expect(explorerLink).toHaveAttribute("target", "_blank");
expect(explorerLink).toHaveAttribute("href", expectedExplorerLink);
});

it("should not allow to unstake, if wrong gas settings are provided", async () => {
/** @type {import("svelte").ComponentProps<Stake>} */
const props = {
...baseProps,
flow: "unstake",
gasSettings: { gasLimit: 29000000090n, gasPrice: 1n },
};

const { getByRole } = render(Stake, props);

await vi.advanceTimersToNextTimerAsync();

const unstakeButton = getByRole("button", { name: "Unstake" });

expect(unstakeButton).toBeDisabled();
});
});

describe("Claim Rewards operation", () => {
const expectedExplorerLink = `/explorer/transactions/transaction?id=${lastTxId}`;

beforeAll(() => {
vi.useFakeTimers();
});

afterAll(() => {
vi.useRealTimers();
});

it("should perform a claim rewards, give a success message and supply a link to see the transaction in the explorer", async () => {
/** @type {import("svelte").ComponentProps<Stake>} */
const props = { ...baseProps, flow: "claim-rewards" };

const { getByRole, getByText } = render(Stake, props);

await vi.advanceTimersToNextTimerAsync();

await fireEvent.click(getByRole("button", { name: "Claim" }));

expect(baseProps.execute).toHaveBeenCalledTimes(1);
expect(baseProps.execute).toHaveBeenCalledWith(
baseProps.gasSettings.gasPrice,
baseProps.gasSettings.gasLimit
);

const explorerLink = getByRole("link", { name: /explorer/i });

expect(getByText("Transaction created")).toBeInTheDocument();
expect(explorerLink).toHaveAttribute("target", "_blank");
expect(explorerLink).toHaveAttribute("href", expectedExplorerLink);
});

it("should not allow to unstake, if wrong gas settings are provided", async () => {
/** @type {import("svelte").ComponentProps<Stake>} */
const props = {
...baseProps,
flow: "claim-rewards",
gasSettings: { gasLimit: 29000000090n, gasPrice: 1n },
};

const { getByRole } = render(Stake, props);

await vi.advanceTimersToNextTimerAsync();

const claimButton = getByRole("button", { name: "Claim" });

expect(claimButton).toBeDisabled();
});
});
});
Loading

0 comments on commit bf5342f

Please sign in to comment.