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
CollectiblesView 1.0 CollectiblesView.qml
NetworkSelectionView 1.0 NetworkSelectionView.qml
NetworkSelectorView 1.0 NetworkSelectorView.qml
SavedAddresses 1.0 SavedAddresses.qml
TokenSelectorAssetDelegate 1.0 TokenSelectorAssetDelegate.qml
TokenSelectorView 1.0 TokenSelectorView.qml