refactoring(NetworkSelectionView): Update NetworkSelectionView and remove the backend dependency

This commit creates a new component NetworkSelectorView and it's implementation will replace the NetworkSelectionView
+ Adding the storybook page
+ Adding tests
This commit is contained in:
Alex Jbanca 2024-06-14 15:05:51 +03:00 committed by Alex Jbanca
parent 527654e579
commit b4a9df62e2
4 changed files with 594 additions and 0 deletions

View File

@ -0,0 +1,108 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ 0.1
import Models 1.0
import AppLayouts.Wallet.views 1.0
SplitView {
id: root
Pane {
id: mainPane
SplitView.fillWidth: true
SplitView.fillHeight: true
ColumnLayout {
anchors.fill: parent
Label {
text: "Radio Buttons"
font.bold: true
}
NetworkSelectorView {
id: networkSelectionView
Layout.fillWidth: true
Layout.fillHeight: true
model: NetworksModel.flatNetworks
selection: [420]
showIndicator: true
multiSelection: false
}
Label {
text: "Checkboxes"
font.bold: true
}
NetworkSelectorView {
id: networkSelectionView2
Layout.fillWidth: true
Layout.fillHeight: true
model: NetworksModel.flatNetworks
showIndicator: true
multiSelection: true
selection: [1, 420]
}
}
}
Pane {
id: controls
SplitView.preferredWidth: 300
SplitView.fillHeight: true
Column {
anchors.fill: parent
Label {
text: "Simulate backend state"
font.bold: true
}
Label {
text: "Radio buttons control"
}
Repeater {
model: NetworksModel.flatNetworks
delegate: CheckBox {
text: model.chainName
checked: networkSelectionView.selection.includes(model.chainId)
onToggled: {
if (checked) {
networkSelectionView.selection = [model.chainId]
}
}
}
}
Label {
text: "Checkboxes control"
}
Repeater {
model: NetworksModel.flatNetworks
delegate: CheckBox {
text: model.chainName
checked: networkSelectionView2.selection.includes(model.chainId)
onToggled: {
if (checked) {
const selection = networkSelectionView2.selection
selection.push(model.chainId)
networkSelectionView2.selection = selection
} else {
networkSelectionView2.selection = networkSelectionView2.selection.filter((id) => id !== model.chainId)
}
}
}
}
}
}
}
// category: Views

View File

@ -0,0 +1,343 @@
import QtQuick 2.15
import QtTest 1.15
import AppLayouts.Wallet.views 1.0
import utils 1.0
import Models 1.0
Item {
id: root
width: 600
height: 400
Component {
id: componentUnderTest
NetworkSelectorView {
anchors.centerIn: parent
model: NetworksModel.flatNetworks
}
}
SignalSpy {
id: toggleNetworkSpy
target: controlUnderTest
signalName: "toggleNetwork"
}
SignalSpy {
id: selectionChangedSpy
target: controlUnderTest
signalName: "onSelectionChanged"
}
property NetworkSelectorView controlUnderTest: null
TestCase {
name: "NetworkSelectorView"
when: windowShown
function init() {
controlUnderTest = createTemporaryObject(componentUnderTest, root)
toggleNetworkSpy.clear()
selectionChangedSpy.clear()
}
function test_basicGeometry() {
verify(!!controlUnderTest)
verify(controlUnderTest.width > 0)
verify(controlUnderTest.height > 0)
}
function test_defaultConfiguration() {
// Default configuration:
// - model is not empty
// - showIndicator is true
// - multiSelection is false
// - interactive is true
// - selection has length 1. This is because the single selection mode is enabled by default
verify(controlUnderTest.model.count > 0)
verify(controlUnderTest.showIndicator)
verify(!controlUnderTest.multiSelection)
verify(controlUnderTest.interactive)
verify(controlUnderTest.selection.length === 1)
}
function test_defaultDelegate() {
// iterate the model and check:
// - a delegate is created for each item
// - the delegate has the correct chain name
// - the delegate has the correct icon url
// - the delegate has the correct show indicator value
// - the delegate has the correct multi selection value
// - the delegate has the correct check state
for (var i = 0; i < controlUnderTest.model.count; i++) {
const model = controlUnderTest.model.get(i)
const delegate = findChild(controlUnderTest, "networkSelectorDelegate_" + model.chainName)
verify(!!delegate)
compare(delegate.title, model.chainName)
compare(delegate.iconUrl, Style.svg(model.iconUrl))
compare(delegate.showIndicator, controlUnderTest.showIndicator)
compare(delegate.multiSelection, controlUnderTest.multiSelection)
compare(delegate.checkState, controlUnderTest.selection.includes(model.chainId) ? Qt.Checked : Qt.Unchecked)
}
controlUnderTest = createTemporaryObject(componentUnderTest, root, {multiSelection: true})
for (var i = 0; i < controlUnderTest.model.count; i++) {
const model = controlUnderTest.model.get(i)
const delegate = findChild(controlUnderTest, "networkSelectorDelegate_" + model.chainName)
compare(delegate.showIndicator, controlUnderTest.showIndicator)
compare(delegate.multiSelection, controlUnderTest.multiSelection)
compare(delegate.checkState, Qt.Unchecked)
}
}
function test_selectionBindingsSingleSelection() {
// 1. toggle by click
// 2. toggle by updating the selection property
// 3. toggle by click
// 4. toggle by updating the selection property
let delegate = findChild(controlUnderTest, "networkSelectorDelegate_" + controlUnderTest.model.get(1).chainName)
// 1. toggle by click
mouseClick(delegate)
compare(toggleNetworkSpy.count, 1)
compare(selectionChangedSpy.count, 1)
compare(delegate.checkState, Qt.Checked)
// 2. toggle by updating the selection property
controlUnderTest.selection = [controlUnderTest.model.get(2).chainId]
compare(toggleNetworkSpy.count, 1)
compare(selectionChangedSpy.count, 2)
compare(controlUnderTest.selection.length, 1)
compare(controlUnderTest.selection[0], controlUnderTest.model.get(2).chainId)
compare(delegate.checkState, Qt.Unchecked)
delegate = findChild(controlUnderTest, "networkSelectorDelegate_" + controlUnderTest.model.get(2).chainName)
compare(delegate.checkState, Qt.Checked)
// 3. toggle by click
const newSelectionDelegate = findChild(controlUnderTest, "networkSelectorDelegate_" + controlUnderTest.model.get(1).chainName)
mouseClick(newSelectionDelegate)
compare(toggleNetworkSpy.count, 2)
compare(selectionChangedSpy.count, 3)
compare(delegate.checkState, Qt.Unchecked)
compare(newSelectionDelegate.checkState, Qt.Checked)
// 4. toggle by updating the selection property
controlUnderTest.selection = [controlUnderTest.model.get(2).chainId]
compare(toggleNetworkSpy.count, 2)
compare(selectionChangedSpy.count, 4)
compare(controlUnderTest.selection.length, 1)
compare(controlUnderTest.selection[0], controlUnderTest.model.get(2).chainId)
compare(delegate.checkState, Qt.Checked)
compare(newSelectionDelegate.checkState, Qt.Unchecked)
}
function test_selectionBindingMultiSelection() {
// 1. toggle by click
// 2. toggle by updating the selection property
// 3. toggle by click
// 4. toggle by updating the selection property
controlUnderTest.multiSelection = true
waitForItemPolished(controlUnderTest)
let delegate = findChild(controlUnderTest, "networkSelectorDelegate_" + controlUnderTest.model.get(1).chainName)
// 1. toggle by click
mouseClick(delegate)
compare(toggleNetworkSpy.count, 1)
compare(selectionChangedSpy.count, 1)
compare(delegate.checkState, Qt.Checked)
// 2. toggle by updating the selection property
controlUnderTest.selection = [controlUnderTest.model.get(1).chainId, controlUnderTest.model.get(2).chainId]
compare(toggleNetworkSpy.count, 1)
compare(selectionChangedSpy.count, 2)
compare(controlUnderTest.selection.length, 2)
compare(controlUnderTest.selection[0], controlUnderTest.model.get(1).chainId)
compare(controlUnderTest.selection[1], controlUnderTest.model.get(2).chainId)
compare(delegate.checkState, Qt.Checked)
delegate = findChild(controlUnderTest, "networkSelectorDelegate_" + controlUnderTest.model.get(2).chainName)
compare(delegate.checkState, Qt.Checked)
// 3. toggle by click
const newSelectionDelegate = findChild(controlUnderTest, "networkSelectorDelegate_" + controlUnderTest.model.get(1).chainName)
mouseClick(newSelectionDelegate)
compare(toggleNetworkSpy.count, 2)
compare(selectionChangedSpy.count, 3)
compare(delegate.checkState, Qt.Checked)
compare(newSelectionDelegate.checkState, Qt.Unchecked)
mouseClick(newSelectionDelegate)
compare(newSelectionDelegate.checkState, Qt.Checked)
// 4. toggle by updating the selection property
controlUnderTest.selection = [controlUnderTest.model.get(2).chainId]
compare(toggleNetworkSpy.count, 3)
compare(selectionChangedSpy.count, 5)
compare(controlUnderTest.selection.length, 1)
compare(controlUnderTest.selection[0], controlUnderTest.model.get(2).chainId)
compare(delegate.checkState, Qt.Checked)
compare(newSelectionDelegate.checkState, Qt.Unchecked)
mouseClick(delegate)
compare(toggleNetworkSpy.count, 4)
compare(delegate.checkState, Qt.Unchecked)
compare(controlUnderTest.selection.length, 0)
// 5. select all by click
for (var i = 0; i < controlUnderTest.model.count; i++) {
const delegate = findChild(controlUnderTest, "networkSelectorDelegate_" + controlUnderTest.model.get(i).chainName)
mouseClick(delegate)
}
compare(controlUnderTest.selection.length, controlUnderTest.model.count)
compare(toggleNetworkSpy.count, controlUnderTest.model.count + 4)
toggleNetworkSpy.clear()
selectionChangedSpy.clear()
for (var i = 0; i < controlUnderTest.model.count; i++) {
const delegate = findChild(controlUnderTest, "networkSelectorDelegate_" + controlUnderTest.model.get(i).chainName)
compare(delegate.checkState, Qt.PartiallyChecked)
}
// 6. set the selection to all selected
const selection = [...controlUnderTest.selection]
controlUnderTest.selection = selection
compare(toggleNetworkSpy.count, 0)
compare(selectionChangedSpy.count, 1)
for (var i = 0; i < controlUnderTest.model.count; i++) {
const delegate = findChild(controlUnderTest, "networkSelectorDelegate_" + controlUnderTest.model.get(i).chainName)
compare(delegate.checkState, Qt.PartiallyChecked)
}
// 7. deselect and select again the same item
mouseClick(findChild(controlUnderTest, "networkSelectorDelegate_" + controlUnderTest.model.get(0).chainName))
compare(toggleNetworkSpy.count, 1)
compare(selectionChangedSpy.count, 2)
compare(controlUnderTest.selection.length, controlUnderTest.model.count - 1)
compare(findChild(controlUnderTest, "networkSelectorDelegate_" + controlUnderTest.model.get(0).chainName).checkState, Qt.Unchecked)
mouseClick(findChild(controlUnderTest, "networkSelectorDelegate_" + controlUnderTest.model.get(0).chainName))
compare(toggleNetworkSpy.count, 2)
compare(selectionChangedSpy.count, 3)
compare(controlUnderTest.selection.length, controlUnderTest.model.count)
for (var i = 0; i < controlUnderTest.model.count; i++) {
const delegate = findChild(controlUnderTest, "networkSelectorDelegate_" + controlUnderTest.model.get(i).chainName)
compare(delegate.checkState, Qt.PartiallyChecked)
}
// 8. deselect one by setting the selection and select all again
let selection2 = [...controlUnderTest.selection]
const deletedId = selection2.splice(0, 1)
controlUnderTest.selection = selection2
compare(toggleNetworkSpy.count, 2)
compare(selectionChangedSpy.count, 4)
compare(controlUnderTest.selection.length, controlUnderTest.model.count - 1)
for (var i = 1; i < controlUnderTest.model.count; i++) {
const model = controlUnderTest.model.get(i)
const delegate = findChild(controlUnderTest, "networkSelectorDelegate_" + model.chainName)
compare(delegate.checkState, model.chainId === deletedId[0] ? Qt.Unchecked : Qt.Checked)
}
selection2 = [...controlUnderTest.selection, deletedId[0]]
controlUnderTest.selection = selection2
compare(toggleNetworkSpy.count, 2)
compare(selectionChangedSpy.count, 5)
compare(controlUnderTest.selection.length, controlUnderTest.model.count)
for (var i = 0; i < controlUnderTest.model.count; i++) {
const delegate = findChild(controlUnderTest, "networkSelectorDelegate_" + controlUnderTest.model.get(i).chainName)
compare(delegate.checkState, Qt.PartiallyChecked)
}
}
function test_noIndicatorConfig() {
controlUnderTest.showIndicator = false
waitForRendering(controlUnderTest)
waitForItemPolished(controlUnderTest)
for (let multiSelect = 0; multiSelect < 2; multiSelect++) {
controlUnderTest.multiSelection = multiSelect
waitForRendering(controlUnderTest)
waitForItemPolished(controlUnderTest)
for (var i = 0; i < controlUnderTest.model.count; i++) {
const model = controlUnderTest.model.get(i)
const delegate = findChild(controlUnderTest, "networkSelectorDelegate_" + model.chainName)
compare(delegate.showIndicator, controlUnderTest.showIndicator)
const checkBox = findChild(delegate, "networkSelectionCheckbox_" + model.chainName)
const radioButton = findChild(delegate, "networkSelectionRadioButton_" + model.chainName)
verify(!checkBox)
verify(!radioButton)
}
}
controlUnderTest.showIndicator = true
waitForRendering(controlUnderTest)
waitForItemPolished(controlUnderTest)
for (let multiSelect = 0; multiSelect < 2; multiSelect++) {
controlUnderTest.multiSelection = multiSelect
waitForRendering(controlUnderTest)
waitForItemPolished(controlUnderTest)
for (var i = 0; i < controlUnderTest.model.count; i++) {
const model = controlUnderTest.model.get(i)
const delegate = findChild(controlUnderTest, "networkSelectorDelegate_" + model.chainName)
compare(delegate.showIndicator, controlUnderTest.showIndicator)
const checkBox = findChild(delegate, "networkSelectionCheckbox_" + model.chainName)
const radioButton = findChild(delegate, "networkSelectionRadioButton_" + model.chainName)
if (multiSelect) {
verify(!!checkBox)
verify(!radioButton)
} else {
verify(!checkBox)
verify(!!radioButton)
}
}
}
}
function test_interactiveConfig() {
controlUnderTest.interactive = false
for (var i = 0; i < controlUnderTest.model.count; i++) {
const model = controlUnderTest.model.get(i)
const delegate = findChild(controlUnderTest, "networkSelectorDelegate_" + model.chainName)
mouseClick(delegate)
compare(toggleNetworkSpy.count, 0)
compare(selectionChangedSpy.count, 0)
}
controlUnderTest.interactive = true
for (var i = 0; i < controlUnderTest.model.count; i++) {
const model = controlUnderTest.model.get(i)
const delegate = findChild(controlUnderTest, "networkSelectorDelegate_" + model.chainName)
mouseClick(delegate)
compare(toggleNetworkSpy.count, i + 1)
compare(selectionChangedSpy.count, i + 1)
}
}
}
}

View File

@ -0,0 +1,142 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQml 2.15
import QtQml.Models 2.15
import StatusQ 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1
import utils 1.0
import "../controls"
StatusListView {
id: root
/**
Model is expected to be sorted by layer
Expected model structure:
chainName [string] - chain long name. e.g. "Ethereum" or "Optimism"
chainId [int] - chain unique identifier
iconUrl [string] - SVG icon name. e.g. "network/Network=Ethereum"
layer [int] - chain layer. e.g. 1 or 2
**/
property bool showIndicator: true
property bool multiSelection: false
property bool interactive: true
/**
The list selected of chain ids
It is a read/write property
WARNING: Update the array, not the internal content
**/
property var selection: []
signal toggleNetwork(int chainId, int index)
onSelectionChanged: {
if (!root.multiSelection && selection.length > 1) {
console.warn("Warning: Multi-selection is disabled, but multiple items are selected. Automatically selecting the last inserted item.")
selection = [selection[selection.length - 1]]
}
}
onMultiSelectionChanged: {
if (root.multiSelection) return;
// When changing the multi-selection mode, we need to ensure that the selection is valid
if (root.selection.length > 1) {
root.selection = [root.selection[0]]
}
// Single selection defaults to first item if no selection is made
if (root.selection.length === 0 && root.count > 0) {
root.selection = [ModelUtils.get(root.model, 0).chainId]
}
}
implicitWidth: 300
implicitHeight: contentHeight
spacing: 4
delegate: NetworkSelectItemDelegate {
id: delegateItem
required property var model
required property int index
readonly property bool inSelection: root.selection.includes(model.chainId)
objectName: "networkSelectorDelegate_" + model.chainName
height: 48
width: ListView.view.width
title: model.chainName
iconUrl: Style.svg(model.iconUrl)
showIndicator: root.showIndicator
multiSelection: root.multiSelection
interactive: root.interactive
checkState: inSelection ? (d.allSelected && root.interactive ? Qt.PartiallyChecked : Qt.Checked) : Qt.Unchecked
nextCheckState: checkState
onToggled: {
d.onToggled(checkState, model.chainId)
root.toggleNetwork(model.chainId, index)
}
Binding on checkState {
when: root.multiSelection && d.allSelected && root.interactive
value: Qt.PartiallyChecked
restoreMode: Binding.RestoreBindingOrValue
}
Binding on checkState {
value: inSelection ? (d.allSelected && root.interactive ? Qt.PartiallyChecked : Qt.Checked) : Qt.Unchecked
}
}
section {
property: "layer"
delegate: Loader {
required property int section
width: parent.width
height: active ? 44 : 0
sourceComponent: section === 2 ? layer2text: null
Component {
id: layer2text
StatusBaseText {
color: Theme.palette.baseColor1
text: qsTr("Layer 2")
leftPadding: 16
topPadding: 14
}
}
}
}
QtObject {
id: d
readonly property bool allSelected: root.selection.length === root.count
function onToggled(initialState, chainId) {
let selection = root.selection
if (initialState === Qt.Unchecked && initialState !== Qt.PartiallyChecked) {
if (!root.multiSelection)
selection = []
selection.push(chainId)
} else if (root.multiSelection) {
selection = selection.filter((id) => id !== chainId)
}
root.selection = [...selection]
}
}
Component.onCompleted: {
if(root.selection.length === 0 && !root.multiSelection && root.count > 0) {
const firstChain = ModelUtils.get(root.model, 0).chainId
root.selection = [firstChain]
}
}
}

View File

@ -1,6 +1,7 @@
AssetsDetailView 1.0 AssetsDetailView.qml AssetsDetailView 1.0 AssetsDetailView.qml
CollectiblesView 1.0 CollectiblesView.qml CollectiblesView 1.0 CollectiblesView.qml
NetworkSelectionView 1.0 NetworkSelectionView.qml NetworkSelectionView 1.0 NetworkSelectionView.qml
NetworkSelectorView 1.0 NetworkSelectorView.qml
SavedAddresses 1.0 SavedAddresses.qml SavedAddresses 1.0 SavedAddresses.qml
TokenSelectorAssetDelegate 1.0 TokenSelectorAssetDelegate.qml TokenSelectorAssetDelegate 1.0 TokenSelectorAssetDelegate.qml
TokenSelectorView 1.0 TokenSelectorView.qml TokenSelectorView 1.0 TokenSelectorView.qml