Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/gui/wizard/qml/SyncOptionsPage.qml
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ Item {

WizardButton {
text: qsTr("Choose")
Layout.preferredWidth: 96
Layout.minimumWidth: Style.wizardInlineButtonMinimumWidth
Layout.preferredHeight: Style.wizardFooterButtonHeight
enabled: root.controller.canUseClassicSync
onClicked: root.controller.chooseLocalSyncFolder()
Expand Down
154 changes: 154 additions & 0 deletions test/qml/accountwizard/tst_accountwizardwindow.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: GPL-2.0-or-later
*/

import QtQuick
import QtTest
import com.nextcloud.desktopclient
import Style

import "../../../src/gui/wizard/qml"

Item {
id: testRoot

width: 200
height: 100

Component {
id: controllerComponent

AccountWizardController {}
}

Component {
id: windowComponent

AccountWizardWindow {}
}

TestCase {
id: testCase

property var wizard: null

name: "AccountWizardWindow"
when: windowShown

function createWizard(step) {
const controller = createTemporaryObject(controllerComponent, testRoot, { currentStep: step })
verify(controller)
wizard = windowComponent.createObject(testRoot, { controller: controller, visible: true })
verify(wizard)
return wizard
}

// The window must go before its temporary controller, which is destroyed right after cleanup().
function cleanup() {
if (wizard) {
wizard.destroy()
wizard = null
wait(0)
}
}

function findVisibleButton(item, text) {
if (item.visible && item.text === text && item.hasOwnProperty("clicked")) {
return item
}
for (let i = 0; i < item.children.length; ++i) {
const found = findVisibleButton(item.children[i], text)
if (found) {
return found
}
}
return null
}

function buttonLabel(button) {
for (let i = 0; i < button.contentItem.children.length; ++i) {
const child = button.contentItem.children[i]
if (child.objectName === "wizardButtonText") {
return child
}
}
return null
}

function relabel(wizard, text, newText) {
const button = findVisibleButton(wizard.contentItem, text)
verify(button, "no visible button labelled " + text)
button.text = newText
return button
}

function test_widthIsConstantAcrossSteps() {
const wizard = createWizard(AccountWizardController.ServerStep)
const steps = [
AccountWizardController.BrowserAuthStep,
AccountWizardController.BasicAuthStep,
AccountWizardController.SyncOptionsStep,
AccountWizardController.ServerStep
]

compare(wizard.minimumWidth, Style.accountWizardWidth)
compare(wizard.width, Style.accountWizardWidth)
for (const step of steps) {
wizard.controller.currentStep = step
compare(wizard.width, Style.accountWizardWidth, "width changed at step " + step)
compare(wizard.minimumWidth, Style.accountWizardWidth)
}
compare(wizard.height, Style.compactDialogHeight)
}

function test_syncOptionsFooterFitsGermanLabels() {
if (Qt.platform.os !== "osx") {
skip("Style.accountWizardWidth was measured with the macOS system font")
}

const wizard = createWizard(AccountWizardController.SyncOptionsStep)
const buttons = [
relabel(wizard, "Cancel", "Abbrechen"),
relabel(wizard, "Set up later", "Später einrichten"),
relabel(wizard, "Advanced", "Fortgeschritten"),
relabel(wizard, "Done", "Erledigt")
]

for (const button of buttons) {
const label = buttonLabel(button)
verify(label)
tryVerify(() => label.width > 0)
verify(!label.truncated, button.text + " is truncated")
}
}

function test_chooseButtonGrowsWithLabel() {
const wizard = createWizard(AccountWizardController.SyncOptionsStep)
const button = relabel(wizard, "Choose", "Auswählen")
const label = buttonLabel(button)

tryVerify(() => button.implicitWidth > Style.wizardInlineButtonMinimumWidth)
tryCompare(button, "width", button.implicitWidth)
verify(!label.truncated)
}

function test_chooseButtonKeepsMinimumWidthForShortLabel() {
const wizard = createWizard(AccountWizardController.SyncOptionsStep)
const button = relabel(wizard, "Choose", "OK")

tryVerify(() => button.implicitWidth < Style.wizardInlineButtonMinimumWidth)
tryCompare(button, "width", Style.wizardInlineButtonMinimumWidth)
}

function test_overlongFooterLabelElidesInsideWindow() {
const wizard = createWizard(AccountWizardController.SyncOptionsStep)
const button = relabel(wizard, "Set up later", "Später einrichten ".repeat(20))
const label = buttonLabel(button)

tryVerify(() => label.truncated)
const rightEdge = button.mapToItem(wizard.contentItem, button.width, 0).x
verify(rightEdge <= wizard.width - Style.wizardWindowMargin, "button overflows to " + rightEdge)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: GPL-2.0-or-later
*/

import QtQml

// Test replacement for the production C++ controller; provides its enums and the API the wizard QML reads.
QtObject {
enum Step {
ServerStep,
BrowserAuthStep,
BasicAuthStep,
SyncOptionsStep,
CompletedStep
}

enum SyncMode {
SyncEverything,
SelectiveSync,
VirtualFiles
}

property int currentStep: AccountWizardController.ServerStep
property int syncMode: AccountWizardController.SelectiveSync
property bool busy: false
property bool authPolling: false
property bool canFinish: true
property bool canUseVirtualFiles: false
property bool canUseClassicSync: true
property bool isUsingFileProvider: false
property bool hasAdvancedOptions: true
property bool localSyncFolderRequired: true
property bool proxySettingsAvailable: true
property bool basicAuthValid: false
property bool serverUrlEditable: true
property bool overrideServerSelectionRequired: false
property bool startLoginFlowAutomatically: false
property bool publicShareSetup: false
property bool showLargeFolderConfirmation: false
property bool showExternalStorageConfirmation: false
property bool askBeforeLargeFolders: false
property bool askBeforeExternalStorage: false
property int largeFolderThresholdMb: 500
property var overrideServerNames: []
property int overrideServerIndex: 0
property string appName: "Nextcloud"
property string serverUrl: ""
property string serverUrlPlaceholder: ""
property string errorText: ""
property url loginUrl: ""
property string authStatusText: ""
property string userDisplayName: "Test User"
property string serverDisplayName: "cloud.example.com"
property string avatarUrl: ""
property string syncEverythingDescription: ""
property string localSyncFolderDisplay: "/Users/test/Nextcloud/"
property string localSyncFolderError: ""
property string localSyncFolderFreeSpace: ""
property string basicAuthUser: ""
property string basicAuthPassword: ""
property int proxyMode: 0
property int manualProxyType: 0
property string proxyHost: ""
property int proxyPort: 0
property bool proxyAuthenticationRequired: false
property string proxyUser: ""
property string proxyPassword: ""
property bool proxySettingsValid: true
property bool showProxyLocalhostWarning: false
property string clientCertificatePath: ""
property string clientCertificatePassword: ""
property string clientCertificateError: ""
property bool clientCertificateValid: false

signal finished(int result)
signal advancedOptionsRequested()
signal proxySettingsRequested()
signal clientCertificateDialogRequested()
signal secureConnectionFailed(string host, bool retryHttpOnly)

function cancel() {}
function goBack() {}
function finish() {}
function skipFolderConfiguration() {}
function openAdvancedOptions() {}
function openProxySettings() {}
function openSignup() {}
function openSelfHostedServerGuide() {}
function openBrowserLogin() {}
function openSelectiveSync() {}
function copyLoginLink() {}
function submitServerUrl() {}
function submitBasicAuth() {}
function submitClientCertificate() {}
function chooseClientCertificate() {}
function clearClientCertificateInput() {}
function retrySecureConnectionWithoutTls() {}
function useClientCertificateForSecureConnection() {}
function chooseLocalSyncFolder() {}
function setSyncMode(mode) {}
function setAskBeforeLargeFolders(ask) {}
function setLargeFolderThresholdMb(thresholdMb) {}
function setAskBeforeExternalStorage(ask) {}
}
1 change: 1 addition & 0 deletions test/qml/imports/com/nextcloud/desktopclient/qmldir
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# SPDX-License-Identifier: GPL-2.0-or-later

module com.nextcloud.desktopclient
AccountWizardController 1.0 AccountWizardController.qml
singleton Systray 1.0 Systray.qml
singleton Theme 1.0 Theme.qml
singleton UserModel 1.0 UserModel.qml
Expand Down