` cannot be a child of ``. ` ` only allows these children: `<#text>`. The browser will 'repair' the HTML (by moving, removing, or inserting elements) which breaks Svelte's assumptions about the structure of your components.
https://svelte.dev/e/node_invalid_placement(node_invalid_placement) svelte/valid-compile
```
- Remove `span` in `option`.
Signed-off-by: David Dal Busco
chore: self-closing HTML tags for non-void elements are ambiguous (#6022)
Fix warning which becomes an error with Svelte v5 tooling (PR #6020).
```
19:5 error Self-closing HTML tags for non-void elements are ambiguous — use `
` rather than `
`
https://svelte.dev/e/element_invalid_self_closing_tag(element_invalid_self_closing_tag) svelte/valid-compile
```
- e.g. `
` -> `
`
Signed-off-by: David Dal Busco
chore: unrecognized attribute xmlns:svelte (#6021)
Fix warning which becomes an error with Svelte v5 tooling (PR #6020).
```
/Users/daviddalbusco/projects/dfinity/nns-dapp/frontend/src/lib/modals/neurons/SplitNnsNeuronModal.svelte
1:19 error Unrecognized attribute — should be one of `generics`, `lang` or `module`. If this exists for a preprocessor, ensure that the preprocessor removes it
https://svelte.dev/e/script_unknown_attribute(script_unknown_attribute) svelte/valid-compile
```
Signed-off-by: David Dal Busco
NNS1-3486: adds ReportingDateRangeSelector (#6019)
Users can filter their export by choosing one of three options: all,
this year, or last year.
A follow up PR will introduce a new component
`ReportingDateRangeSelector` that will onrchestrate the `dateRange`
filter by passing it down to both, the `ReportingDateRangeSelector` and
to `ReportingTransactionsButton`.
- Adds a new component called `ReportingDateRangeSelector` to change the
value of the date range filter.
- Unit tests
- [ ] Add entry to changelog (if necessary).
Not necessary
---
CHANGELOG-Nns-Dapp-unreleased.md | 5 -
CHANGELOG-Nns-Dapp.md | 15 ++
Cargo.lock | 72 +++---
Cargo.toml | 2 +-
dfx.json | 4 +-
frontend/package-lock.json | 64 +++--
frontend/package.json | 6 +-
.../accounts/SelectNetworkDropdown.svelte | 2 +-
.../accounts/WalletPageHeading.svelte | 2 +-
.../lib/components/common/EmptyCards.svelte | 10 +-
.../NnsNeuronVotingPowerSection.svelte | 15 +-
.../NeuronsTable/NeuronStakeCell.svelte | 8 +-
.../CommitmentProgressBar.svelte | 2 +-
.../ReportingDateRangeSelector.svelte | 94 +++++++
.../staking/ProjectStakeCell.svelte | 3 +-
.../TokensTable/TokenBalanceCell.svelte | 6 +-
.../components/ui/ProposalStatusTag.svelte | 2 +-
frontend/src/lib/i18n/en.json | 8 +-
.../modals/neurons/SplitNnsNeuronModal.svelte | 2 +-
frontend/src/lib/pages/SnsNeurons.svelte | 1 +
.../src/lib/services/reporting.services.ts | 55 +++-
...ons-table-order-sorted-neuron-ids-store.ts | 1 +
frontend/src/lib/stores/toasts.store.ts | 4 +-
frontend/src/lib/types/i18n.d.ts | 6 +
frontend/src/lib/types/neurons-table.ts | 1 +
frontend/src/lib/types/reporting.ts | 1 +
frontend/src/lib/types/toast.ts | 11 +-
frontend/src/lib/utils/i18n.utils.ts | 32 +++
frontend/src/lib/utils/neuron.utils.ts | 11 +
frontend/src/lib/utils/neurons-table.utils.ts | 52 +++-
frontend/src/tests/e2e/ckbtc.spec.ts | 2 +-
.../accounts/IcrcBalancesObserverTest.svelte | 2 +-
.../IcrcWalletTransactionsObserverTest.svelte | 2 +-
.../NnsNeuronVotingPowerSection.spec.ts | 62 ++++-
.../neurons/NeuronsTable/NeuronsTable.spec.ts | 51 ++++
.../ReportingDateRangeSelector.spec.ts | 44 ++++
.../lib/directives/IntersectionTest.svelte | 2 +-
.../lib/services/reporting.services.spec.ts | 242 +++++++++++++++++-
.../src/tests/lib/utils/neuron.utils.spec.ts | 62 +++++
.../lib/utils/neurons-table.utils.spec.ts | 27 ++
.../src/tests/mocks/icp-transactions.mock.ts | 7 +-
frontend/src/tests/mocks/neurons.mock.ts | 1 +
.../NeuronStakeCell.page-object.ts | 13 +
.../NeuronsTableRow.page-object.ts | 8 +
...NnsNeuronVotingPowerSection.page-object.ts | 4 +
.../ReportingDateRangeSelector.page-object.ts | 24 ++
46 files changed, 930 insertions(+), 120 deletions(-)
create mode 100644 frontend/src/lib/components/reporting/ReportingDateRangeSelector.svelte
create mode 100644 frontend/src/lib/types/reporting.ts
create mode 100644 frontend/src/tests/lib/components/reporting/ReportingDateRangeSelector.spec.ts
create mode 100644 frontend/src/tests/page-objects/ReportingDateRangeSelector.page-object.ts
diff --git a/CHANGELOG-Nns-Dapp-unreleased.md b/CHANGELOG-Nns-Dapp-unreleased.md
index c3e62df3013..2e827fdcbd1 100644
--- a/CHANGELOG-Nns-Dapp-unreleased.md
+++ b/CHANGELOG-Nns-Dapp-unreleased.md
@@ -14,13 +14,8 @@ proposal is successful, the changes it released will be moved from this file to
#### Added
-- Show USD values of tokens.
-- Reporting page for NNS Neurons and ICP transactions.
-
#### Changed
-- Sorts launched projects in the Launchpad page from newest to oldest.
-
#### Deprecated
#### Removed
diff --git a/CHANGELOG-Nns-Dapp.md b/CHANGELOG-Nns-Dapp.md
index 1cb21ceb65b..72881ed8a50 100644
--- a/CHANGELOG-Nns-Dapp.md
+++ b/CHANGELOG-Nns-Dapp.md
@@ -11,6 +11,21 @@ The NNS Dapp is released through proposals in the Network Nervous System. Theref
Unreleased changes are added to `CHANGELOG-Nns-Dapp-unreleased.md` and moved
here after a successful release.
+## Proposal 134484
+
+### Application
+
+#### Added
+
+- Show USD values of tokens.
+- Reporting page for NNS Neurons and ICP transactions.
+
+#### Changed
+
+- Sorts launched projects in the Launchpad page from newest to oldest.
+
+### Operations
+
## Proposal 134397
### Application
diff --git a/Cargo.lock b/Cargo.lock
index ee148065220..a202878ecfc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -500,9 +500,9 @@ dependencies = [
[[package]]
name = "candid"
-version = "0.10.10"
+version = "0.10.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c30ee7f886f296b6422c0ff017e89dd4f831521dfdcc76f3f71aae1ce817222"
+checksum = "d04aa85a9ba2542bded33d1eff0ffb17cb98b1be8117e0a25e1ad8c62bedc881"
dependencies = [
"anyhow",
"binread",
@@ -576,9 +576,9 @@ dependencies = [
[[package]]
name = "cc"
-version = "1.2.3"
+version = "1.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d"
+checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf"
dependencies = [
"shlex",
]
@@ -597,9 +597,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "chrono"
-version = "0.4.38"
+version = "0.4.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
+checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
dependencies = [
"android-tzdata",
"iana-time-zone",
@@ -1235,9 +1235,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
[[package]]
name = "fastrand"
-version = "2.2.0"
+version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4"
+checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "fe-derive"
@@ -1831,7 +1831,7 @@ dependencies = [
"hkdf",
"pem",
"rand",
- "thiserror 2.0.5",
+ "thiserror 2.0.7",
"zeroize",
]
@@ -2010,7 +2010,7 @@ dependencies = [
"serde_cbor",
"strum",
"strum_macros",
- "thiserror 2.0.5",
+ "thiserror 2.0.7",
"zeroize",
]
@@ -2089,7 +2089,7 @@ dependencies = [
"ic-protobuf",
"serde",
"serde_bytes",
- "thiserror 2.0.5",
+ "thiserror 2.0.7",
]
[[package]]
@@ -2143,7 +2143,7 @@ dependencies = [
"num-traits",
"serde",
"serde_bytes",
- "thiserror 2.0.5",
+ "thiserror 2.0.7",
]
[[package]]
@@ -2703,7 +2703,7 @@ dependencies = [
"candid",
"ic-base-types",
"serde",
- "thiserror 2.0.5",
+ "thiserror 2.0.7",
]
[[package]]
@@ -3095,7 +3095,7 @@ dependencies = [
"serde_with",
"strum",
"strum_macros",
- "thiserror 2.0.5",
+ "thiserror 2.0.7",
"thousands",
]
@@ -3616,9 +3616,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
[[package]]
name = "libc"
-version = "0.2.167"
+version = "0.2.168"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc"
+checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
[[package]]
name = "libflate"
@@ -3852,7 +3852,7 @@ checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
[[package]]
name = "nns-dapp"
-version = "2.0.97"
+version = "2.0.98"
dependencies = [
"anyhow",
"base64 0.22.1",
@@ -4083,7 +4083,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc"
dependencies = [
"memchr",
- "thiserror 2.0.5",
+ "thiserror 2.0.7",
"ucd-trie",
]
@@ -4344,7 +4344,7 @@ dependencies = [
[[package]]
name = "proposals"
-version = "2.0.97"
+version = "2.0.98"
dependencies = [
"anyhow",
"candid",
@@ -4534,9 +4534,9 @@ dependencies = [
[[package]]
name = "redox_syscall"
-version = "0.5.7"
+version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f"
+checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
dependencies = [
"bitflags",
]
@@ -4739,15 +4739,15 @@ dependencies = [
[[package]]
name = "rustix"
-version = "0.38.41"
+version = "0.38.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6"
+checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -4817,18 +4817,18 @@ dependencies = [
[[package]]
name = "semver"
-version = "1.0.23"
+version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
+checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba"
dependencies = [
"serde",
]
[[package]]
name = "serde"
-version = "1.0.215"
+version = "1.0.216"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
+checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e"
dependencies = [
"serde_derive",
]
@@ -4854,9 +4854,9 @@ dependencies = [
[[package]]
name = "serde_derive"
-version = "1.0.215"
+version = "1.0.216"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
+checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
dependencies = [
"proc-macro2",
"quote",
@@ -5028,7 +5028,7 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "sns_aggregator"
-version = "2.0.97"
+version = "2.0.98"
dependencies = [
"anyhow",
"base64 0.22.1",
@@ -5250,11 +5250,11 @@ dependencies = [
[[package]]
name = "thiserror"
-version = "2.0.5"
+version = "2.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "643caef17e3128658ff44d85923ef2d28af81bb71e0d67bbfe1d76f19a73e053"
+checksum = "93605438cbd668185516ab499d589afb7ee1859ea3d5fc8f6b0755e1c7443767"
dependencies = [
- "thiserror-impl 2.0.5",
+ "thiserror-impl 2.0.7",
]
[[package]]
@@ -5270,9 +5270,9 @@ dependencies = [
[[package]]
name = "thiserror-impl"
-version = "2.0.5"
+version = "2.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "995d0bbc9995d1f19d28b7215a9352b0fc3cd3a2d2ec95c2cadc485cdedbcdde"
+checksum = "e1d8749b4531af2117677a5fcd12b1348a3fe2b81e36e61ffeac5c4aa3273e36"
dependencies = [
"proc-macro2",
"quote",
diff --git a/Cargo.toml b/Cargo.toml
index 540208f471a..cb886ccfba6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,7 +7,7 @@ members = [
resolver = "2"
[workspace.package]
-version = "2.0.97"
+version = "2.0.98"
[workspace.dependencies]
ic-cdk = "0.17.0"
diff --git a/dfx.json b/dfx.json
index f8160b0cc23..37bcd2873d3 100644
--- a/dfx.json
+++ b/dfx.json
@@ -1,5 +1,5 @@
{
- "dfx": "0.24.2",
+ "dfx": "0.24.3",
"canisters": {
"nns-governance": {
"type": "custom",
@@ -441,7 +441,7 @@
"DIDC_VERSION": "didc 0.4.0",
"POCKETIC_VERSION": "3.0.1",
"CARGO_SORT_VERSION": "1.0.9",
- "SNSDEMO_RELEASE": "release-2024-12-04",
+ "SNSDEMO_RELEASE": "release-2024-12-11",
"IC_COMMIT_FOR_PROPOSALS": "release-2024-12-06_03-16-base",
"IC_COMMIT_FOR_SNS_AGGREGATOR": "release-2024-12-06_03-16-base"
},
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 8167e9e6895..d605267b1ca 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@dfinity/nns-dapp",
- "version": "2.0.97",
+ "version": "2.0.98",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@dfinity/nns-dapp",
- "version": "2.0.97",
+ "version": "2.0.98",
"license": "SEE LICENSE IN LICENSE.md",
"dependencies": {
"@dfinity/agent": "^2.1.3",
@@ -34,8 +34,8 @@
"@sveltejs/adapter-static": "^3.0.5",
"@sveltejs/kit": "^2.8.3",
"@sveltejs/vite-plugin-svelte": "^3.1.2",
- "@testing-library/jest-dom": "^6.6.2",
- "@testing-library/svelte": "^5.2.3",
+ "@testing-library/jest-dom": "^6.6.3",
+ "@testing-library/svelte": "^5.2.6",
"@testing-library/user-event": "^14.5.2",
"@types/wicg-file-system-access": "^2023.10.5",
"@typescript-eslint/eslint-plugin": "^6.8.0",
@@ -78,10 +78,11 @@
}
},
"node_modules/@adobe/css-tools": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.0.tgz",
- "integrity": "sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==",
- "dev": true
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.1.tgz",
+ "integrity": "sha512-12WGKBQzjUAI4ayyF4IAtfw2QR/IDoqk6jTddXDhtYTJF9ASmoE1zst7cVtP0aL/F1jUJL5r+JxKXKEgHNbEUQ==",
+ "dev": true,
+ "license": "MIT"
},
"node_modules/@ampproject/remapping": {
"version": "2.3.0",
@@ -1545,10 +1546,11 @@
}
},
"node_modules/@testing-library/jest-dom": {
- "version": "6.6.2",
- "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.2.tgz",
- "integrity": "sha512-P6GJD4yqc9jZLbe98j/EkyQDTPgqftohZF5FBkHY5BUERZmcf4HeO2k0XaefEg329ux2p21i1A1DmyQ1kKw2Jw==",
+ "version": "6.6.3",
+ "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz",
+ "integrity": "sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"@adobe/css-tools": "^4.4.0",
"aria-query": "^5.0.0",
@@ -1569,6 +1571,7 @@
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
"integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
@@ -1581,13 +1584,15 @@
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz",
"integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/@testing-library/svelte": {
- "version": "5.2.3",
- "resolved": "https://registry.npmjs.org/@testing-library/svelte/-/svelte-5.2.3.tgz",
- "integrity": "sha512-y5eaD2Vp3hb729dAv3dOYNoZ9uNM0bQ0rd5AfXDWPvI+HiGmjl8ZMOuKzBopveyAkci1Kplb6kS53uZhPGEK+w==",
+ "version": "5.2.6",
+ "resolved": "https://registry.npmjs.org/@testing-library/svelte/-/svelte-5.2.6.tgz",
+ "integrity": "sha512-1Y8cEg/BtV4J6g9irkY0ksz+ueDFYLiikjTLiqvQPkOUeDzR4gg2zECBf8yrOrCy3e2TAOYMcaysFa0bQMyk1w==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"@testing-library/dom": "^10.0.0"
},
@@ -1613,6 +1618,7 @@
"resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.5.2.tgz",
"integrity": "sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=12",
"npm": ">=6"
@@ -2689,7 +2695,8 @@
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
"integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/cssesc": {
"version": "3.0.0",
@@ -3734,6 +3741,7 @@
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
"integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=8"
}
@@ -4057,7 +4065,8 @@
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/lodash.merge": {
"version": "4.6.2",
@@ -4209,6 +4218,7 @@
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=4"
}
@@ -5021,6 +5031,7 @@
"resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
"integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"indent-string": "^4.0.0",
"strip-indent": "^3.0.0"
@@ -5448,6 +5459,7 @@
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
"integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"min-indent": "^1.0.0"
},
@@ -6498,9 +6510,9 @@
"dev": true
},
"@adobe/css-tools": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.0.tgz",
- "integrity": "sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==",
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.1.tgz",
+ "integrity": "sha512-12WGKBQzjUAI4ayyF4IAtfw2QR/IDoqk6jTddXDhtYTJF9ASmoE1zst7cVtP0aL/F1jUJL5r+JxKXKEgHNbEUQ==",
"dev": true
},
"@ampproject/remapping": {
@@ -7411,9 +7423,9 @@
}
},
"@testing-library/jest-dom": {
- "version": "6.6.2",
- "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.2.tgz",
- "integrity": "sha512-P6GJD4yqc9jZLbe98j/EkyQDTPgqftohZF5FBkHY5BUERZmcf4HeO2k0XaefEg329ux2p21i1A1DmyQ1kKw2Jw==",
+ "version": "6.6.3",
+ "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz",
+ "integrity": "sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==",
"dev": true,
"requires": {
"@adobe/css-tools": "^4.4.0",
@@ -7444,9 +7456,9 @@
}
},
"@testing-library/svelte": {
- "version": "5.2.3",
- "resolved": "https://registry.npmjs.org/@testing-library/svelte/-/svelte-5.2.3.tgz",
- "integrity": "sha512-y5eaD2Vp3hb729dAv3dOYNoZ9uNM0bQ0rd5AfXDWPvI+HiGmjl8ZMOuKzBopveyAkci1Kplb6kS53uZhPGEK+w==",
+ "version": "5.2.6",
+ "resolved": "https://registry.npmjs.org/@testing-library/svelte/-/svelte-5.2.6.tgz",
+ "integrity": "sha512-1Y8cEg/BtV4J6g9irkY0ksz+ueDFYLiikjTLiqvQPkOUeDzR4gg2zECBf8yrOrCy3e2TAOYMcaysFa0bQMyk1w==",
"dev": true,
"requires": {
"@testing-library/dom": "^10.0.0"
diff --git a/frontend/package.json b/frontend/package.json
index 6773a0d980c..832bb56d19b 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,6 +1,6 @@
{
"name": "@dfinity/nns-dapp",
- "version": "2.0.97",
+ "version": "2.0.98",
"private": true,
"license": "SEE LICENSE IN LICENSE.md",
"scripts": {
@@ -36,8 +36,8 @@
"@sveltejs/adapter-static": "^3.0.5",
"@sveltejs/kit": "^2.8.3",
"@sveltejs/vite-plugin-svelte": "^3.1.2",
- "@testing-library/jest-dom": "^6.6.2",
- "@testing-library/svelte": "^5.2.3",
+ "@testing-library/jest-dom": "^6.6.3",
+ "@testing-library/svelte": "^5.2.6",
"@testing-library/user-event": "^14.5.2",
"@types/wicg-file-system-access": "^2023.10.5",
"@typescript-eslint/eslint-plugin": "^6.8.0",
diff --git a/frontend/src/lib/components/accounts/SelectNetworkDropdown.svelte b/frontend/src/lib/components/accounts/SelectNetworkDropdown.svelte
index 4a1841e323b..796b6b1bfa8 100644
--- a/frontend/src/lib/components/accounts/SelectNetworkDropdown.svelte
+++ b/frontend/src/lib/components/accounts/SelectNetworkDropdown.svelte
@@ -59,7 +59,7 @@
testId="select-network-dropdown"
>
{$i18n.accounts.select_network} {$i18n.accounts.select_network}
{$i18n.accounts.network_icp}
-
+
{/if}
{accountName}
diff --git a/frontend/src/lib/components/common/EmptyCards.svelte b/frontend/src/lib/components/common/EmptyCards.svelte
index 5c22cb7b3ba..565531b1430 100644
--- a/frontend/src/lib/components/common/EmptyCards.svelte
+++ b/frontend/src/lib/components/common/EmptyCards.svelte
@@ -4,19 +4,19 @@
diff --git a/frontend/src/lib/components/neuron-detail/NnsNeuronVotingPowerSection.svelte b/frontend/src/lib/components/neuron-detail/NnsNeuronVotingPowerSection.svelte
index 82d505c87c1..a95bb966e8a 100644
--- a/frontend/src/lib/components/neuron-detail/NnsNeuronVotingPowerSection.svelte
+++ b/frontend/src/lib/components/neuron-detail/NnsNeuronVotingPowerSection.svelte
@@ -3,6 +3,7 @@
import { i18n } from "$lib/stores/i18n";
import { replacePlaceholders } from "$lib/utils/i18n.utils";
import {
+ activityMultiplier,
ageMultiplier,
dissolveDelayMultiplier,
formatVotingPower,
@@ -41,15 +42,22 @@
{$i18n.neuron_detail.calculated_as}
-
- {$i18n.neuron_detail.voting_power_section_calculation_generic}
+
+ {$ENABLE_PERIODIC_FOLLOWING_CONFIRMATION
+ ? $i18n.neuron_detail.voting_power_section_calculation_generic_new
+ : $i18n.neuron_detail.voting_power_section_calculation_generic}
{$i18n.neuron_detail.this_neuron_calculation}
{replacePlaceholders(
- $i18n.neuron_detail.voting_power_section_calculation_specific,
+ $ENABLE_PERIODIC_FOLLOWING_CONFIRMATION
+ ? $i18n.neuron_detail.voting_power_section_calculation_specific_new
+ : $i18n.neuron_detail.voting_power_section_calculation_specific,
{
$stake: formatTokenE8s({
value: neuronStake(neuron),
@@ -59,6 +67,7 @@
$dissolveMultiplier: dissolveDelayMultiplier(
neuron.dissolveDelaySeconds
).toFixed(2),
+ $activityMultiplier: activityMultiplier(neuron).toFixed(2),
$votingPower: formatVotingPower(neuron.votingPower),
}
)}
diff --git a/frontend/src/lib/components/neurons/NeuronsTable/NeuronStakeCell.svelte b/frontend/src/lib/components/neurons/NeuronsTable/NeuronStakeCell.svelte
index 97f2e8380a1..c956b021739 100644
--- a/frontend/src/lib/components/neurons/NeuronsTable/NeuronStakeCell.svelte
+++ b/frontend/src/lib/components/neurons/NeuronsTable/NeuronStakeCell.svelte
@@ -1,12 +1,18 @@
-
+ {#if $ENABLE_USD_VALUES_FOR_NEURONS}
+
+ {:else}
+
+ {/if}
diff --git a/frontend/src/lib/components/staking/ProjectStakeCell.svelte b/frontend/src/lib/components/staking/ProjectStakeCell.svelte
index bcf75d6c8b4..2443fac7019 100644
--- a/frontend/src/lib/components/staking/ProjectStakeCell.svelte
+++ b/frontend/src/lib/components/staking/ProjectStakeCell.svelte
@@ -11,8 +11,7 @@
{#if !(rowData.stake instanceof TokenAmountV2) || rowData.stake.toUlps() > 0}
{#if $ENABLE_USD_VALUES_FOR_NEURONS}
-
+
{:else}
{/if}
diff --git a/frontend/src/lib/components/tokens/TokensTable/TokenBalanceCell.svelte b/frontend/src/lib/components/tokens/TokensTable/TokenBalanceCell.svelte
index aab0522756f..4b9b28e8f4b 100644
--- a/frontend/src/lib/components/tokens/TokensTable/TokenBalanceCell.svelte
+++ b/frontend/src/lib/components/tokens/TokensTable/TokenBalanceCell.svelte
@@ -22,8 +22,10 @@
>
{:else if isUserTokenData(rowData)}
{#if $ENABLE_USD_VALUES}
-
+
{:else}
{/if}
diff --git a/frontend/src/lib/components/ui/ProposalStatusTag.svelte b/frontend/src/lib/components/ui/ProposalStatusTag.svelte
index 99e93d2e23e..f06a2cbf588 100644
--- a/frontend/src/lib/components/ui/ProposalStatusTag.svelte
+++ b/frontend/src/lib/components/ui/ProposalStatusTag.svelte
@@ -26,7 +26,7 @@
class="actionable-status-badge"
role="status"
transition:scale={{ duration: 250, easing: cubicOut }}
- />
+ >
{/if}
diff --git a/frontend/src/lib/i18n/en.json b/frontend/src/lib/i18n/en.json
index 67a1061465d..565336b7380 100644
--- a/frontend/src/lib/i18n/en.json
+++ b/frontend/src/lib/i18n/en.json
@@ -192,7 +192,11 @@
"error_csv_generation": "Failed to generate CSV file",
"error_file_system_access": "Unable to save file. Please check your permissions.",
"error_neurons": "There was an error exporting the neurons. Please try again.",
- "error_transactions": "There was an error exporting the transactions. Please try again."
+ "error_transactions": "There was an error exporting the transactions. Please try again.",
+ "range_filter_title": "Reporting Date Range",
+ "range_filter_all": "All transactions",
+ "range_last_year": "Last year",
+ "range_year_to_date": "Year to date"
},
"auth": {
"login": "Sign in with Internet Identity",
@@ -661,8 +665,10 @@
"voting_power_section_description_expanded_zero_nns": "The dissolve delay must be at least 6 months for the neuron to have voting power. Learn more about voting power on the dashboard .",
"calculated_as": "Calculated as:",
"voting_power_section_calculation_generic": "voting_power = (staked_amount + staked_maturity) × (1 + age_bonus) × (1 + dissolve_delay_bonus)",
+ "voting_power_section_calculation_generic_new": "voting_power = (staked_amount + staked_maturity) × (1 + age_bonus) × (1 + dissolve_delay_bonus) × activity_multiplier",
"this_neuron_calculation": "For this neuron, the calculation is:",
"voting_power_section_calculation_specific": "voting_power = ($stake + $maturityStaked) × $ageMultiplier × $dissolveMultiplier = $votingPower",
+ "voting_power_section_calculation_specific_new": "voting_power = ($stake + $maturityStaked) × $ageMultiplier × $dissolveMultiplier × $activityMultiplier = $votingPower",
"maturity_section_description": "Earn rewards by voting on proposals and/or following active neurons.",
"staked_description": "Staked",
"nns_staked_maturity_tooltip": "Staked maturity contributes to the neuron’s voting power, but cannot be spawned into a new neuron.",
diff --git a/frontend/src/lib/modals/neurons/SplitNnsNeuronModal.svelte b/frontend/src/lib/modals/neurons/SplitNnsNeuronModal.svelte
index e2fe445378d..4a2d1b0c8ca 100644
--- a/frontend/src/lib/modals/neurons/SplitNnsNeuronModal.svelte
+++ b/frontend/src/lib/modals/neurons/SplitNnsNeuronModal.svelte
@@ -1,4 +1,4 @@
-
-
+
diff --git a/frontend/src/tests/lib/services/reporting.services.spec.ts b/frontend/src/tests/lib/services/reporting.services.spec.ts
index ed0a9e10a86..75170720fa0 100644
--- a/frontend/src/tests/lib/services/reporting.services.spec.ts
+++ b/frontend/src/tests/lib/services/reporting.services.spec.ts
@@ -9,7 +9,10 @@ import {
mockMainAccount,
mockSubAccount,
} from "$tests/mocks/icp-accounts.store.mock";
-import { createTransactionWithId } from "$tests/mocks/icp-transactions.mock";
+import {
+ createTransactionWithId,
+ dateToNanoSeconds,
+} from "$tests/mocks/icp-transactions.mock";
import { mockNeuron } from "$tests/mocks/neurons.mock";
import type { SignIdentity } from "@dfinity/agent";
@@ -114,14 +117,14 @@ describe("reporting service", () => {
it("should handle errors and return accumulated transactions", async () => {
const firstBatch = [
- createTransactionWithId({ id: 1n }),
+ createTransactionWithId({ id: 3n }),
createTransactionWithId({ id: 2n }),
];
spyGetTransactions
.mockResolvedValueOnce({
transactions: firstBatch,
- oldestTxId: 2000n,
+ oldestTxId: 1n,
})
.mockRejectedValueOnce(new Error("API Error"));
@@ -133,6 +136,239 @@ describe("reporting service", () => {
expect(result).toEqual(firstBatch);
expect(spyGetTransactions).toHaveBeenCalledTimes(2);
});
+
+ it('should filter "to" the provided date', async () => {
+ const allTransactions = [
+ createTransactionWithId({
+ id: 3n,
+ timestamp: new Date("2023-01-02T00:00:00.000Z"),
+ }),
+ createTransactionWithId({
+ id: 2n,
+ timestamp: new Date("2023-01-01T00:00:00.000Z"),
+ }),
+ createTransactionWithId({
+ id: 1n,
+ timestamp: new Date("2022-12-31T00:00:00.000Z"),
+ }),
+ ];
+
+ spyGetTransactions.mockResolvedValue({
+ transactions: allTransactions,
+ oldestTxId: 1n,
+ });
+
+ const result = await getAllTransactionsFromAccountAndIdentity({
+ accountId: mockAccountId,
+ identity: mockSignInIdentity,
+ range: {
+ to: dateToNanoSeconds(new Date("2023-01-01T00:00:00.000Z")),
+ },
+ });
+
+ expect(result).toHaveLength(2);
+ expect(result).toEqual(allTransactions.slice(1));
+ expect(spyGetTransactions).toHaveBeenCalledTimes(1);
+ });
+
+ it('should filter "from" the provided date', async () => {
+ const allTransactions = [
+ createTransactionWithId({
+ id: 3n,
+ timestamp: new Date("2023-01-02T00:00:00.000Z"),
+ }),
+ createTransactionWithId({
+ id: 2n,
+ timestamp: new Date("2023-01-01T00:00:00.000Z"),
+ }),
+ createTransactionWithId({
+ id: 1n,
+ timestamp: new Date("2022-12-31T00:00:00.000Z"),
+ }),
+ ];
+
+ spyGetTransactions.mockResolvedValue({
+ transactions: allTransactions,
+ oldestTxId: 1n,
+ });
+
+ const result = await getAllTransactionsFromAccountAndIdentity({
+ accountId: mockAccountId,
+ identity: mockSignInIdentity,
+ range: {
+ from: dateToNanoSeconds(new Date("2023-01-01T00:00:00.000Z")),
+ },
+ });
+
+ expect(result).toHaveLength(2);
+ expect(result).toEqual(allTransactions.slice(0, 2));
+ expect(spyGetTransactions).toHaveBeenCalledTimes(1);
+ });
+
+ it("should handle date range where no transactions match", async () => {
+ const allTransactions = [
+ createTransactionWithId({
+ id: 3n,
+ timestamp: new Date("2023-01-02T00:00:00.000Z"),
+ }),
+ createTransactionWithId({
+ id: 2n,
+ timestamp: new Date("2022-12-30T00:00:00.000Z"),
+ }),
+ createTransactionWithId({
+ id: 1n,
+ timestamp: new Date("2022-12-29T00:00:00.000Z"),
+ }),
+ ];
+
+ spyGetTransactions.mockResolvedValue({
+ transactions: allTransactions,
+ oldestTxId: 1n,
+ });
+
+ const result = await getAllTransactionsFromAccountAndIdentity({
+ accountId: mockAccountId,
+ identity: mockSignInIdentity,
+ range: {
+ to: dateToNanoSeconds(new Date("2023-01-01T00:00:00.000Z")),
+ from: dateToNanoSeconds(new Date("2022-12-31T00:00:00.000Z")),
+ },
+ });
+
+ expect(result).toHaveLength(0);
+ expect(spyGetTransactions).toHaveBeenCalledTimes(1);
+ });
+
+ it('should return early if the last transaction is in the current page is older than "from" date', async () => {
+ const allTransactions = [
+ createTransactionWithId({
+ id: 3n,
+ timestamp: new Date("2023-01-02T00:00:00.000Z"),
+ }),
+ createTransactionWithId({
+ id: 2n,
+ timestamp: new Date("2022-12-31T00:00:00.000Z"),
+ }),
+ createTransactionWithId({
+ id: 1n,
+ timestamp: new Date("2022-12-30T00:00:00.000Z"),
+ }),
+ ];
+ const firstBatchOfMockTransactions = allTransactions.slice(0, 2);
+ const secondBatchOfMockTransactions = allTransactions.slice(2);
+
+ spyGetTransactions
+ .mockResolvedValueOnce({
+ transactions: firstBatchOfMockTransactions,
+ oldestTxId: 1n,
+ })
+ .mockResolvedValueOnce({
+ transactions: secondBatchOfMockTransactions,
+ oldestTxId: 1n,
+ });
+
+ const result = await getAllTransactionsFromAccountAndIdentity({
+ accountId: mockAccountId,
+ identity: mockSignInIdentity,
+ range: {
+ from: dateToNanoSeconds(new Date("2023-01-01T00:00:00.000Z")),
+ },
+ });
+
+ expect(result).toHaveLength(1);
+ expect(result).toEqual(allTransactions.slice(0, 1));
+ expect(spyGetTransactions).toHaveBeenCalledTimes(1);
+ });
+
+ it('should handle a range with both "from" and "to" dates', async () => {
+ const allTransactions = [
+ createTransactionWithId({
+ id: 6n,
+ timestamp: new Date("2023-02-02T00:00:00.000Z"),
+ }),
+ createTransactionWithId({
+ id: 5n,
+ timestamp: new Date("2023-01-01T00:00:00.000Z"),
+ }),
+ createTransactionWithId({
+ id: 4n,
+ timestamp: new Date("2022-12-31T10:00:00.000Z"),
+ }),
+ createTransactionWithId({
+ id: 3n,
+ timestamp: new Date("2022-12-31T00:00:00.000Z"),
+ }),
+ createTransactionWithId({
+ id: 2n,
+ timestamp: new Date("2022-12-30T00:00:00.000Z"),
+ }),
+ createTransactionWithId({
+ id: 1n,
+ timestamp: new Date("2022-11-20T00:00:00.000Z"),
+ }),
+ ];
+ const firstBatchOfMockTransactions = allTransactions.slice(0, 3);
+ const secondBatchOfMockTransactions = allTransactions.slice(3, 6);
+
+ spyGetTransactions
+ .mockResolvedValueOnce({
+ transactions: firstBatchOfMockTransactions,
+ oldestTxId: 1n,
+ })
+ .mockResolvedValueOnce({
+ transactions: secondBatchOfMockTransactions,
+ oldestTxId: 1n,
+ });
+
+ const result = await getAllTransactionsFromAccountAndIdentity({
+ accountId: mockAccountId,
+ identity: mockSignInIdentity,
+ range: {
+ to: dateToNanoSeconds(new Date("2023-01-02T00:00:00.000Z")),
+ from: dateToNanoSeconds(new Date("2022-11-30T00:00:00.000Z")),
+ },
+ });
+ expect(result).toHaveLength(4);
+ expect(result).toEqual(allTransactions.slice(1, -1));
+ expect(spyGetTransactions).toHaveBeenCalledTimes(2);
+ });
+
+ it("should filter the transactions even if one call fails", async () => {
+ const firstBatchOfMockTransactions = [
+ createTransactionWithId({
+ id: 6n,
+ timestamp: new Date("2023-02-02T00:00:00.000Z"),
+ }),
+ createTransactionWithId({
+ id: 5n,
+ timestamp: new Date("2023-01-01T00:00:00.000Z"),
+ }),
+ createTransactionWithId({
+ id: 4n,
+ timestamp: new Date("2022-12-31T10:00:00.000Z"),
+ }),
+ ];
+ spyGetTransactions
+ .mockResolvedValueOnce({
+ transactions: firstBatchOfMockTransactions,
+ oldestTxId: 3n,
+ })
+ .mockRejectedValueOnce(new Error("API Error"));
+ const result = await getAllTransactionsFromAccountAndIdentity({
+ accountId: mockAccountId,
+ identity: mockSignInIdentity,
+ range: {
+ to: dateToNanoSeconds(new Date("2023-01-02T00:00:00.000Z")),
+ from: dateToNanoSeconds(new Date("2022-11-30T00:00:00.000Z")),
+ },
+ });
+ expect(result).toHaveLength(2);
+ expect(result).toEqual([
+ firstBatchOfMockTransactions[1],
+ firstBatchOfMockTransactions[2],
+ ]);
+ expect(spyGetTransactions).toHaveBeenCalledTimes(2);
+ });
});
describe("getAccountTransactionsConcurrently", () => {
diff --git a/frontend/src/tests/lib/utils/neuron.utils.spec.ts b/frontend/src/tests/lib/utils/neuron.utils.spec.ts
index a61069968ff..66ada91ed88 100644
--- a/frontend/src/tests/lib/utils/neuron.utils.spec.ts
+++ b/frontend/src/tests/lib/utils/neuron.utils.spec.ts
@@ -18,6 +18,7 @@ import { neuronsStore } from "$lib/stores/neurons.store";
import { nowInSeconds } from "$lib/utils/date.utils";
import { enumValues } from "$lib/utils/enum.utils";
import {
+ activityMultiplier,
ageMultiplier,
allHaveSameFollowees,
ballotsWithDefinedProposal,
@@ -291,6 +292,67 @@ describe("neuron-utils", () => {
});
});
+ describe("activityMultiplier", () => {
+ it("returns 0 when decidingVotingPower is 0", () => {
+ expect(
+ activityMultiplier({
+ ...mockNeuron,
+ fullNeuron: {
+ ...mockFullNeuron,
+ decidingVotingPower: 0n,
+ potentialVotingPower: 0n,
+ },
+ })
+ ).toBe(0);
+ });
+
+ it("returns 0 when potentialVotingPower is 0", () => {
+ expect(
+ activityMultiplier({
+ ...mockNeuron,
+ fullNeuron: {
+ ...mockFullNeuron,
+ decidingVotingPower: 100_000_000n,
+ potentialVotingPower: 0n,
+ },
+ })
+ ).toBe(0);
+ });
+
+ it("calculates the multiplier", () => {
+ expect(
+ activityMultiplier({
+ ...mockNeuron,
+ fullNeuron: {
+ ...mockFullNeuron,
+ decidingVotingPower: 200_000_000n,
+ potentialVotingPower: 200_000_000n,
+ },
+ })
+ ).toBe(1);
+ expect(
+ activityMultiplier({
+ ...mockNeuron,
+ fullNeuron: {
+ ...mockFullNeuron,
+ decidingVotingPower: 100_000_000n,
+ potentialVotingPower: 200_000_000n,
+ },
+ })
+ ).toBe(0.5);
+ expect(
+ activityMultiplier({
+ ...mockNeuron,
+ fullNeuron: {
+ ...mockFullNeuron,
+ decidingVotingPower: 20_000n,
+ potentialVotingPower: 200_000_000n,
+ },
+ })
+ ).toBe(0.0001);
+ });
+ });
+
describe("bonusMultiplier", () => {
it("should return the multiplier", () => {
expect(
diff --git a/frontend/src/tests/lib/utils/neurons-table.utils.spec.ts b/frontend/src/tests/lib/utils/neurons-table.utils.spec.ts
index b5262e6d04f..decab7926af 100644
--- a/frontend/src/tests/lib/utils/neurons-table.utils.spec.ts
+++ b/frontend/src/tests/lib/utils/neurons-table.utils.spec.ts
@@ -1,3 +1,4 @@
+import { LEDGER_CANISTER_ID } from "$lib/constants/canister-ids.constants";
import { HOTKEY_PERMISSIONS } from "$lib/constants/sns-neurons.constants";
import {
compareByDissolveDelay,
@@ -16,11 +17,13 @@ import { mockNeuron, mockTableNeuron } from "$tests/mocks/neurons.mock";
import { createMockSnsNeuron } from "$tests/mocks/sns-neurons.mock";
import { mockSnsToken } from "$tests/mocks/sns-projects.mock";
import { NeuronState, type NeuronInfo } from "@dfinity/nns";
+import { Principal } from "@dfinity/principal";
import type { SnsNeuron } from "@dfinity/sns";
import { ICPToken, TokenAmountV2 } from "@dfinity/utils";
describe("neurons-table.utils", () => {
const now = new Date("2022-01-01T15:26:47Z");
+ const icpPrice = 10;
const makeStake = (amount: bigint) =>
TokenAmountV2.fromUlps({
@@ -28,6 +31,9 @@ describe("neurons-table.utils", () => {
token: ICPToken,
});
+ const makeUsdStake = (amount: bigint) =>
+ (Number(amount) * icpPrice) / 100_000_000;
+
beforeEach(() => {
vi.useFakeTimers().setSystemTime(now);
});
@@ -54,6 +60,7 @@ describe("neurons-table.utils", () => {
domKey: "42",
neuronId: "42",
stake: makeStake(defaultStake),
+ stakeInUsd: makeUsdStake(defaultStake),
availableMaturity: 0n,
stakedMaturity: 0n,
dissolveDelaySeconds: defaultDissolveDelaySeconds,
@@ -67,6 +74,9 @@ describe("neurons-table.utils", () => {
neuronInfos,
identity: mockIdentity,
accounts: mockAccountsStoreData,
+ icpSwapUsdPrices: {
+ [LEDGER_CANISTER_ID.toText()]: icpPrice,
+ },
i18n: en,
});
@@ -105,6 +115,7 @@ describe("neurons-table.utils", () => {
domKey: "42",
neuronId: "42",
stake: makeStake(stake1),
+ stakeInUsd: makeUsdStake(stake1),
},
{
...defaultExpectedTableNeuron,
@@ -112,6 +123,7 @@ describe("neurons-table.utils", () => {
domKey: "342",
neuronId: "342",
stake: makeStake(stake2),
+ stakeInUsd: makeUsdStake(stake2),
},
]);
});
@@ -131,6 +143,7 @@ describe("neurons-table.utils", () => {
{
...defaultExpectedTableNeuron,
stake: makeStake(stake),
+ stakeInUsd: makeUsdStake(stake),
},
]);
});
@@ -190,6 +203,7 @@ describe("neurons-table.utils", () => {
...defaultExpectedTableNeuron,
rowHref: undefined,
stake: makeStake(0n),
+ stakeInUsd: 0,
state: NeuronState.Spawning,
},
]);
@@ -226,6 +240,9 @@ describe("neurons-table.utils", () => {
neuronInfos: [hotkeyNeuronInfo],
identity: mockIdentity,
accounts: mockAccountsStoreData,
+ icpSwapUsdPrices: {
+ [LEDGER_CANISTER_ID.toText()]: icpPrice,
+ },
i18n: en,
});
expect(tableNeurons).toEqual([
@@ -239,10 +256,12 @@ describe("neurons-table.utils", () => {
describe("tableNeuronsFromSnsNeurons", () => {
const snsUniverseIdText = "br5f7-7uaaa-aaaaa-qaaca-cai";
+ const ledgerCanisterId = Principal.fromText("wxkl4-qiqaa-2q");
const neuronIdString = "123456789abcdef0";
const neuronId = hexStringToBytes(neuronIdString);
const stake = 300_000n;
const dissolveDelaySeconds = 8640000n;
+ const snsTokenPrice = 0.25;
const defaultCreateMockSnsNeuronParams = {
id: neuronId,
@@ -261,11 +280,15 @@ describe("neurons-table.utils", () => {
token: mockSnsToken,
});
+ const makeSnsUsdStake = (amount: bigint) =>
+ (Number(amount) * snsTokenPrice) / 100_000_000;
+
const expectedTableNeuron = {
rowHref: "/neuron/?u=br5f7-7uaaa-aaaaa-qaaca-cai&neuron=123456789abcdef0",
domKey: neuronIdString,
neuronId: neuronIdString,
stake: makeSnsStake(stake),
+ stakeInUsd: makeSnsUsdStake(stake),
availableMaturity: 0n,
stakedMaturity: 0n,
dissolveDelaySeconds,
@@ -280,6 +303,10 @@ describe("neurons-table.utils", () => {
universe: snsUniverseIdText,
token: mockSnsToken,
identity: mockIdentity,
+ icpSwapUsdPrices: {
+ [ledgerCanisterId.toText()]: snsTokenPrice,
+ },
+ ledgerCanisterId,
i18n: en,
});
diff --git a/frontend/src/tests/mocks/icp-transactions.mock.ts b/frontend/src/tests/mocks/icp-transactions.mock.ts
index a36627efe94..6fad5a6dc40 100644
--- a/frontend/src/tests/mocks/icp-transactions.mock.ts
+++ b/frontend/src/tests/mocks/icp-transactions.mock.ts
@@ -22,6 +22,10 @@ export const mockTransactionTransfer: Transaction = {
timestamp: [{ timestamp_nanos: 235n }],
};
+export const dateToNanoSeconds = (date: Date): bigint => {
+ return BigInt(date.getTime()) * BigInt(NANO_SECONDS_IN_MILLISECOND);
+};
+
const defaultTimestamp = new Date("2023-01-01T00:00:00.000Z");
export const createTransactionWithId = ({
memo,
@@ -35,8 +39,7 @@ export const createTransactionWithId = ({
id?: bigint;
}): TransactionWithId => {
const timestampNanos = {
- timestamp_nanos:
- BigInt(timestamp.getTime()) * BigInt(NANO_SECONDS_IN_MILLISECOND),
+ timestamp_nanos: dateToNanoSeconds(timestamp),
};
return {
id,
diff --git a/frontend/src/tests/mocks/neurons.mock.ts b/frontend/src/tests/mocks/neurons.mock.ts
index 4e04e5a4aaf..dbec2dead4a 100644
--- a/frontend/src/tests/mocks/neurons.mock.ts
+++ b/frontend/src/tests/mocks/neurons.mock.ts
@@ -115,6 +115,7 @@ export const mockTableNeuron: TableNeuron = {
amount: 1n,
token: ICPToken,
}),
+ stakeInUsd: 10,
availableMaturity: 0n,
stakedMaturity: 0n,
dissolveDelaySeconds: 1n,
diff --git a/frontend/src/tests/page-objects/NeuronStakeCell.page-object.ts b/frontend/src/tests/page-objects/NeuronStakeCell.page-object.ts
index 9aa98776f8b..25ec5177650 100644
--- a/frontend/src/tests/page-objects/NeuronStakeCell.page-object.ts
+++ b/frontend/src/tests/page-objects/NeuronStakeCell.page-object.ts
@@ -1,4 +1,5 @@
import { AmountDisplayPo } from "$tests/page-objects/AmountDisplay.page-object";
+import { AmountWithUsdPo } from "$tests/page-objects/AmountWithUsd.page-object";
import { BasePageObject } from "$tests/page-objects/base.page-object";
import type { PageObjectElement } from "$tests/types/page-object.types";
@@ -9,6 +10,10 @@ export class NeuronStakeCellPo extends BasePageObject {
return new NeuronStakeCellPo(element.byTestId(NeuronStakeCellPo.TID));
}
+ getAmountWithUsdPo(): AmountWithUsdPo {
+ return AmountWithUsdPo.under(this.root);
+ }
+
getAmountDisplayPo(): AmountDisplayPo {
return AmountDisplayPo.under(this.root);
}
@@ -20,4 +25,12 @@ export class NeuronStakeCellPo extends BasePageObject {
getStakeBalance(): Promise {
return this.getAmountDisplayPo().getAmount();
}
+
+ async getStakeInUsd(): Promise {
+ return await this.getAmountWithUsdPo().getAmountInUsd();
+ }
+
+ async hasStakeInUsd(): Promise {
+ return await this.getAmountWithUsdPo().isPresent();
+ }
}
diff --git a/frontend/src/tests/page-objects/NeuronsTableRow.page-object.ts b/frontend/src/tests/page-objects/NeuronsTableRow.page-object.ts
index 3f9302a5c65..d077bace992 100644
--- a/frontend/src/tests/page-objects/NeuronsTableRow.page-object.ts
+++ b/frontend/src/tests/page-objects/NeuronsTableRow.page-object.ts
@@ -46,6 +46,14 @@ export class NeuronsTableRowPo extends ResponsiveTableRowPo {
return this.getNeuronStakeCellPo().getStake();
}
+ getStakeInUsd(): Promise {
+ return this.getNeuronStakeCellPo().getStakeInUsd();
+ }
+
+ hasStakeInUsd(): Promise {
+ return this.getNeuronStakeCellPo().hasStakeInUsd();
+ }
+
// Stake without the currency symbol
getStakeBalance(): Promise {
return this.getNeuronStakeCellPo().getStakeBalance();
diff --git a/frontend/src/tests/page-objects/NnsNeuronVotingPowerSection.page-object.ts b/frontend/src/tests/page-objects/NnsNeuronVotingPowerSection.page-object.ts
index 86102a10af8..ea0839209ed 100644
--- a/frontend/src/tests/page-objects/NnsNeuronVotingPowerSection.page-object.ts
+++ b/frontend/src/tests/page-objects/NnsNeuronVotingPowerSection.page-object.ts
@@ -18,6 +18,10 @@ export class NnsNeuronVotingPowerSectionPo extends BasePageObject {
return this.getText("voting-power");
}
+ getGenericDescription(): Promise {
+ return this.getText("voting-power-generic-description");
+ }
+
getDescription(): Promise {
return this.getText("voting-power-description");
}
diff --git a/frontend/src/tests/page-objects/ReportingDateRangeSelector.page-object.ts b/frontend/src/tests/page-objects/ReportingDateRangeSelector.page-object.ts
new file mode 100644
index 00000000000..16a121e2dbb
--- /dev/null
+++ b/frontend/src/tests/page-objects/ReportingDateRangeSelector.page-object.ts
@@ -0,0 +1,24 @@
+import type { PageObjectElement } from "$tests/types/page-object.types";
+import { SimpleBasePageObject } from "./simple-base.page-object";
+
+export class ReportingDateRangeSelectorPo extends SimpleBasePageObject {
+ static readonly TID = "reporting-data-range-selector-component";
+
+ static under({
+ element,
+ }: {
+ element: PageObjectElement;
+ }): ReportingDateRangeSelectorPo {
+ return new ReportingDateRangeSelectorPo(
+ element.byTestId(ReportingDateRangeSelectorPo.TID)
+ );
+ }
+
+ getAllOptions() {
+ return this.getElement().querySelectorAll('input[type="radio"]');
+ }
+
+ getSelectedOption() {
+ return this.getElement().querySelector('input[type="radio"]:checked');
+ }
+}