Skip to content
Draft
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
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,13 @@ elseif(BUILD_TESTING)
message(STATUS "Skipping tests: BUILD_TESTING requires BUILD_GUI to be enabled.")
endif()

if(APPLE AND TARGET nextcloudCore)
add_subdirectory(test/docassets EXCLUDE_FROM_ALL)
if(BUILD_DOC_ASSETS_TESTS)
enable_testing()
endif()
endif()

configure_file(config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h)
configure_file(version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h)

Expand Down
89 changes: 89 additions & 0 deletions doc/documentation-assets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<!--
SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
SPDX-License-Identifier: GPL-2.0-or-later
-->

# Documentation asset capture

Connect NextcloudDev to the intended test server, let it sync, and quit all
Nextcloud clients. From this checkout, run:

```sh
./test/docassets/run.sh
```

No arguments or environment variables are required. The script expects
`/Applications/NextcloudDev.app/Contents/MacOS/NextcloudDev`. The capture tool
reads the server and user from the existing `nextclouddev.cfg` and reuses its
saved login. The profile must contain exactly one account. You are responsible
for ensuring it is the intended test account.

The tool locates the configuration in the NextcloudDev preferences directory,
including the macOS application container. It refuses ambiguous duplicate
profiles. It does not provision another account or copy credentials.

Assets are always exported by the script to a new
`~/Downloads/Nextcloud-doc-assets-<UTC time>-<UUID>` folder. Completed assets
from a partially failed export are retained in a folder containing
`-incomplete-`.

Each run also saves terminal diagnostics to `~/Downloads/Nextcloud-doc-assets-log-*`.
The script prints the log path before building. Attach that file when reporting
a capture failure; it can contain account names, server addresses and share data.

## Build prerequisite

The script finds the configured CMake build under this checkout's `build`
directory and builds the standalone `DocAssetsCapture` target. Build
NextcloudDev from this checkout first if no configured build exists.
The capture target must use NextcloudDev branding to read the same configuration
and Keychain credentials. The installed application alone does not contain
the capture executable.

## Screens and test data

The capture target is a separate `DocAssetsCapture.app` in the build directory.
Its bundle identity is required by the macOS notification API used during account
startup. The script checks notification initialization before exporting. Always
use `run.sh`; an older loose `bin/DocAssetsCapture` executable may still exist
after upgrading the build and must not be used.

The catalogue in `test/docassets/catalogue.cpp` covers wizard states, Settings,
Activities, Search, Sharing, User Status, and Assistant. Wizard states use local
fixtures. Account screens open the production windows with the saved account.

For Sharing, the capture reads the first page of existing server shares (up to
100), selects a file source, and opens the production sharing window using its
server file ID and display name. No local file or classic sync folder is needed.
The server must support unified sharing and have an existing file share.
The capture does not create or edit shares. Search uses the term
`Project`. The account needs representative activities and enabled Search,
User Status features. Assistant is captured only when enabled for the account;
otherwise it is reported as skipped and does not fail the export.
Missing data is reported for the affected
capture. Sharing discovery works independently of File Provider domain access.

The exporter also renders 18 bundled sync status and tray icons as PNG files.
Browser login pages, OS file pickers, and OS File Provider indicators are
outside the catalogue.

## Runtime behavior

Account captures start the real application with the existing NextcloudDev
profile. Normal startup, configuration writes, account requests, synchronization,
and platform integration can therefore occur. This is reuse of the dev client
profile, not an isolated disposable profile. Use a test account.

The normal client must be stopped because the macOS single instance socket
is shared. Capture workers block physical input and external HTTP/HTTPS URL
opening while taking screenshots.

## Validation

The standalone `DocAssetsCaptureTest` target checks notification initialization in
the bundled runner and covers configuration loading,
invalid and ambiguous accounts, server/user matching, wizard capture and icons.
Enable it with `BUILD_DOC_ASSETS_TESTS=ON` in the configured build.
A complete live export requires the prepared test account and a logged-in
macOS desktop session. Inspect the output for clipping, correct data, and
native control rendering; successful PNG decoding is not visual validation.
62 changes: 62 additions & 0 deletions test/docassets/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
# SPDX-License-Identifier: GPL-2.0-or-later

find_package(Qt6 ${REQUIRED_QT_VERSION} REQUIRED COMPONENTS Svg)

add_library(docAssetsSupport STATIC
arguments.cpp arguments.h
capture.cpp capture.h
captureguard.cpp captureguard.h
offlinenetworkaccessmanager.cpp offlinenetworkaccessmanager.h
offlinenetworkfactory.cpp offlinenetworkfactory.h
outputdirectory.cpp outputdirectory.h
setup.cpp setup.h
accountwizardcontrollertestaccess.h
catalogue.cpp catalogue.h
capturescene.cpp capturescene.h
exporter.cpp exporter.h
iconexport.cpp iconexport.h
livecapture.cpp livecapture.h
liveprofile.cpp liveprofile.h
sharingsource.cpp sharingsource.h
wizardfixtures.cpp wizardfixtures.h
fixtureimageprovider.cpp fixtureimageprovider.h
)
target_link_libraries(docAssetsSupport PUBLIC
nextcloudCore
nextcloudGuiSearch
nextcloudGuiSearchplugin
nextcloudGuiSharing
nextcloudGuiSharingplugin
Qt::Svg
)
target_compile_definitions(docAssetsSupport PRIVATE QT_NO_KEYWORDS)
target_include_directories(docAssetsSupport PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/.." "${CMAKE_SOURCE_DIR}/src")
set_target_properties(docAssetsSupport PROPERTIES AUTOMOC ON)

add_executable(DocAssetsCapture MACOSX_BUNDLE main.cpp)
target_link_libraries(DocAssetsCapture PRIVATE docAssetsSupport)
target_compile_definitions(DocAssetsCapture PRIVATE QT_NO_KEYWORDS)
set_target_properties(DocAssetsCapture PROPERTIES
MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in"
RUNTIME_OUTPUT_DIRECTORY "${BIN_OUTPUT_DIRECTORY}"
)

option(BUILD_DOC_ASSETS_TESTS "Build tests for the standalone documentation capture tool" OFF)
if(BUILD_DOC_ASSETS_TESTS)
find_package(Qt6 ${REQUIRED_QT_VERSION} REQUIRED COMPONENTS Test)
add_executable(DocAssetsCaptureTest testcapture.cpp)
target_link_libraries(DocAssetsCaptureTest PRIVATE docAssetsSupport Qt::Test)
target_compile_definitions(DocAssetsCaptureTest PRIVATE QT_NO_KEYWORDS DOC_ASSETS_EXECUTABLE="$<TARGET_FILE:DocAssetsCapture>")
add_dependencies(DocAssetsCaptureTest DocAssetsCapture)
ecm_mark_nongui_executable(DocAssetsCaptureTest)
set_target_properties(DocAssetsCaptureTest PROPERTIES
AUTOMOC ON
EXCLUDE_FROM_ALL FALSE
MACOSX_BUNDLE FALSE
RUNTIME_OUTPUT_DIRECTORY "${BIN_OUTPUT_DIRECTORY}"
)
add_test(NAME DocAssetsCaptureTest COMMAND DocAssetsCaptureTest)
set_tests_properties(DocAssetsCaptureTest PROPERTIES TIMEOUT 600)
endif()
32 changes: 32 additions & 0 deletions test/docassets/Info.plist.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
SPDX-License-Identifier: GPL-2.0-or-later
-->
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>DocAssetsCapture</string>
<key>CFBundleIdentifier</key>
<string>com.nextcloud.docassets.capture</string>
<key>CFBundleName</key>
<string>Nextcloud Documentation Capture</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>LSUIElement</key>
<true/>
<key>NSLocalNetworkUsageDescription</key>
<string>Connect to the configured Nextcloud test server to capture documentation screenshots.</string>
</dict>
</plist>
42 changes: 42 additions & 0 deletions test/docassets/accountwizardcontrollertestaccess.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: GPL-2.0-or-later
*/

#pragma once
#include "wizard/accountwizardcontroller.h"

namespace OCC
{
// Uses the controller's existing test friend without changing its production interface.
class AccountWizardControllerTestAccess
{
public:
static void prepare(AccountWizardController &controller, AccountWizardController::Step step)
{
controller._initialLocalSyncFolderPromptShown = true;
controller._localSyncFolderSelected = true;
controller._localSyncFolder = QStringLiteral("/Users/alex/Nextcloud");
controller._localSyncFolderValid = true;
controller.setUserDisplayName(QStringLiteral("Alex Morgan"));
controller.setServerDisplayName(QStringLiteral("cloud.example.com"));
controller.setServerUrl(QStringLiteral("https://cloud.example.com"));
controller.setBasicAuthUser(QStringLiteral("alex"));
controller.setBasicAuthPassword(QStringLiteral("documentation-only"));
controller.setLoginUrl(QUrl(QStringLiteral("https://cloud.example.com/login/v2/flow/documentation")));
controller.setAuthPolling(step == AccountWizardController::BrowserAuthStep);
controller.setNeedsSyncOptions(step == AccountWizardController::SyncOptionsStep);
controller.setCurrentStep(step);
}

static bool hasLiveServices(const AccountWizardController &controller)
{
return hasAccount(controller) || controller._flow2Auth || controller._localSyncFolderPickerOpen;
}

static bool hasAccount(const AccountWizardController &controller)
{
return !controller._account.isNull();
}
};
}
43 changes: 43 additions & 0 deletions test/docassets/arguments.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "arguments.h"

namespace OCC::DocAssets
{
QStringList normalizedCaptureArguments(const QStringList &arguments, QString *error)
{
error->clear();
if (arguments.isEmpty()) {
*error = QStringLiteral("Missing executable name");
return {};
}
QStringList result{arguments.first()};
for (auto index = 1; index < arguments.size(); ++index) {
const auto &argument = arguments.at(index);
if (argument == QStringLiteral("-ApplePersistenceIgnoreState") || argument == QStringLiteral("-NSDocumentRevisionsDebugMode")
|| argument == QStringLiteral("--NSDocumentRevisionsDebugMode")) {
if (index + 1 >= arguments.size()) {
*error = QStringLiteral("Missing boolean value for %1").arg(argument);
return {};
}
const auto value = arguments.at(index + 1).toLower();
if (value != QStringLiteral("yes") && value != QStringLiteral("no") && value != QStringLiteral("true") && value != QStringLiteral("false")
&& value != QStringLiteral("1") && value != QStringLiteral("0")) {
*error = QStringLiteral("Invalid boolean value for %1").arg(argument);
return {};
}
++index;
continue;
}
result.append(argument);
// Keep capture operands intact, even if an invalid operand resembles an option.
if (argument == QStringLiteral("--output") && index + 1 < arguments.size()) {
result.append(arguments.at(++index));
}
}
return result;
}
}
12 changes: 12 additions & 0 deletions test/docassets/arguments.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: GPL-2.0-or-later
*/

#pragma once
#include <QStringList>

namespace OCC::DocAssets
{
QStringList normalizedCaptureArguments(const QStringList &arguments, QString *error);
}
Loading