Skip to content
Merged
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
40 changes: 22 additions & 18 deletions Nextcloud.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
AA517B812D660EFE00F8D37C /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = AA517B7E2D660EFE00F8D37C /* Localizable.stringsdict */; };
AA517B822D660EFE00F8D37C /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = AA517B7E2D660EFE00F8D37C /* Localizable.stringsdict */; };
AA517B832D660EFE00F8D37C /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = AA517B7E2D660EFE00F8D37C /* Localizable.stringsdict */; };
AA52EB472D42AC9E0089C348 /* Placeholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA52EB452D42AC5A0089C348 /* Placeholder.swift */; };
AA62DF602D5DF1F1009E8894 /* PHAssetCollection+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F351D1A52D0AF24A00930F94 /* PHAssetCollection+Extension.swift */; };
AA74AA972D3172D100BE3458 /* UITestError.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA74AA962D3172CE00BE3458 /* UITestError.swift */; };
AA8D31532D41052300FE2775 /* NCManageDatabase+DownloadLimit.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8D31522D41052300FE2775 /* NCManageDatabase+DownloadLimit.swift */; };
Expand All @@ -40,8 +39,12 @@
AA8E041D2D300FDE00E7E89C /* NCShareNetworkingDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8E041C2D300FDE00E7E89C /* NCShareNetworkingDelegate.swift */; };
AAA7BC2E2D3E39F1008F1A22 /* CapabilitiesResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAA7BC2D2D3E39EC008F1A22 /* CapabilitiesResponse.swift */; };
AAA7BC302D3E3B88008F1A22 /* CapabilityResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAA7BC2F2D3E3B83008F1A22 /* CapabilityResponse.swift */; };
AABBCC0230A8000100F0A001 /* NCMediaPlaybackOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AABBCC0130A8000100F0A001 /* NCMediaPlaybackOptions.swift */; };
AABBCC0430A8000100F0A001 /* NCMediaPlaybackOptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AABBCC0330A8000100F0A001 /* NCMediaPlaybackOptionsTests.swift */; };
AABBCC0630A8000100F0A001 /* NCVideoPlaybackPresentationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = AABBCC0530A8000100F0A001 /* NCVideoPlaybackPresentationContext.swift */; };
AABBCC0830A8000100F0A001 /* NCVideoPlaybackPresentationContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AABBCC0730A8000100F0A001 /* NCVideoPlaybackPresentationContextTests.swift */; };
AABBCC0A30A8000100F0A001 /* NCMediaViewerModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AABBCC0930A8000100F0A001 /* NCMediaViewerModelTests.swift */; };
AABD0C8A2D5F67A400F009E6 /* XCUIElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = AABD0C892D5F67A200F009E6 /* XCUIElement.swift */; };
AABD0C9B2D5F73FC00F009E6 /* Placeholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = AABD0C9A2D5F73FA00F009E6 /* Placeholder.swift */; };
AAE330042D2ED20200B04903 /* NCShareNavigationTitleSetting.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE330032D2ED1FF00B04903 /* NCShareNavigationTitleSetting.swift */; };
AAFC0D042F9AA10000F0A001 /* NCFocusedAutoUploadIntroView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAFC0D012F9AA10000F0A001 /* NCFocusedAutoUploadIntroView.swift */; };
AAFC0D052F9AA10000F0A001 /* NCFocusedAutoUploadProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAFC0D022F9AA10000F0A001 /* NCFocusedAutoUploadProgressView.swift */; };
Expand Down Expand Up @@ -1261,7 +1264,6 @@
AA52EB2C2D4297570089C348 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Intent.strings; sourceTree = "<group>"; };
AA52EB2D2D4297570089C348 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Localizable.strings; sourceTree = "<group>"; };
AA52EB2E2D4297570089C348 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/InfoPlist.strings; sourceTree = "<group>"; };
AA52EB452D42AC5A0089C348 /* Placeholder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Placeholder.swift; sourceTree = "<group>"; };
AA74AA962D3172CE00BE3458 /* UITestError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestError.swift; sourceTree = "<group>"; };
AA8D31522D41052300FE2775 /* NCManageDatabase+DownloadLimit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+DownloadLimit.swift"; sourceTree = "<group>"; };
AA8D31652D411FA100FE2775 /* NCShareDateCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareDateCell.swift; sourceTree = "<group>"; };
Expand All @@ -1280,9 +1282,13 @@
AA9B6A902DF1D8F7009D805D /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/InfoPlist.strings; sourceTree = "<group>"; };
AAA7BC2D2D3E39EC008F1A22 /* CapabilitiesResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapabilitiesResponse.swift; sourceTree = "<group>"; };
AAA7BC2F2D3E3B83008F1A22 /* CapabilityResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapabilityResponse.swift; sourceTree = "<group>"; };
AABBCC0130A8000100F0A001 /* NCMediaPlaybackOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaPlaybackOptions.swift; sourceTree = "<group>"; };
AABBCC0330A8000100F0A001 /* NCMediaPlaybackOptionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaPlaybackOptionsTests.swift; sourceTree = "<group>"; };
AABBCC0530A8000100F0A001 /* NCVideoPlaybackPresentationContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCVideoPlaybackPresentationContext.swift; sourceTree = "<group>"; };
AABBCC0730A8000100F0A001 /* NCVideoPlaybackPresentationContextTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCVideoPlaybackPresentationContextTests.swift; sourceTree = "<group>"; };
AABBCC0930A8000100F0A001 /* NCMediaViewerModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaViewerModelTests.swift; sourceTree = "<group>"; };
AABD0C862D5F58C400F009E6 /* Server.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = Server.sh; sourceTree = "<group>"; };
AABD0C892D5F67A200F009E6 /* XCUIElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCUIElement.swift; sourceTree = "<group>"; };
AABD0C9A2D5F73FA00F009E6 /* Placeholder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Placeholder.swift; sourceTree = "<group>"; };
AACCAB522CFE041F00DA1786 /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/Intent.strings; sourceTree = "<group>"; };
AACCAB532CFE041F00DA1786 /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/Localizable.strings; sourceTree = "<group>"; };
AACCAB542CFE041F00DA1786 /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/InfoPlist.strings; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2179,9 +2185,11 @@
AA52EB442D42AC500089C348 /* NextcloudUnitTests */ = {
isa = PBXGroup;
children = (
AABBCC0330A8000100F0A001 /* NCMediaPlaybackOptionsTests.swift */,
AABBCC0730A8000100F0A001 /* NCVideoPlaybackPresentationContextTests.swift */,
AABBCC0930A8000100F0A001 /* NCMediaViewerModelTests.swift */,
F34BDB3B2F574A58007A222C /* BidiSafeFilenameTests.swift */,
C0DECA012F65000100C0D001 /* NCCameraRollTests.swift */,
AA52EB452D42AC5A0089C348 /* Placeholder.swift */,
);
path = NextcloudUnitTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -2234,18 +2242,9 @@
path = NextcloudUITests;
sourceTree = "<group>";
};
C04E2F212A17BB4D001BAD85 /* NextcloudIntegrationTests */ = {
isa = PBXGroup;
children = (
AABD0C9A2D5F73FA00F009E6 /* Placeholder.swift */,
);
path = NextcloudIntegrationTests;
sourceTree = "<group>";
};
F30A962A2A27A9C800D7BCFE /* Tests */ = {
isa = PBXGroup;
children = (
C04E2F212A17BB4D001BAD85 /* NextcloudIntegrationTests */,
AA52EB442D42AC500089C348 /* NextcloudUnitTests */,
C0046CDB2A17B98400D87C9D /* NextcloudUITests */,
AABD0C862D5F58C400F009E6 /* Server.sh */,
Expand Down Expand Up @@ -2595,6 +2594,8 @@
F749ED342FAF0EE200CE8DFA /* Core */ = {
isa = PBXGroup;
children = (
AABBCC0130A8000100F0A001 /* NCMediaPlaybackOptions.swift */,
AABBCC0530A8000100F0A001 /* NCVideoPlaybackPresentationContext.swift */,
F7CDB5BB2FA33CA300F72306 /* NCMediaViewerView.swift */,
F7CDB5B82FA33CA300F72306 /* NCMediaViewerModel.swift */,
);
Expand Down Expand Up @@ -4473,7 +4474,9 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
AA52EB472D42AC9E0089C348 /* Placeholder.swift in Sources */,
AABBCC0430A8000100F0A001 /* NCMediaPlaybackOptionsTests.swift in Sources */,
AABBCC0830A8000100F0A001 /* NCVideoPlaybackPresentationContextTests.swift in Sources */,
AABBCC0A30A8000100F0A001 /* NCMediaViewerModelTests.swift in Sources */,
F34BDB3C2F574A58007A222C /* BidiSafeFilenameTests.swift in Sources */,
C0DECA022F65000100C0D001 /* NCCameraRollTests.swift in Sources */,
F372087D2BAB4C0F006B5430 /* TestConstants.swift in Sources */,
Expand All @@ -4498,7 +4501,6 @@
F3374A962D6744A4002A38F9 /* BaseUIXCTestCase.swift in Sources */,
F37208812BAB5979006B5430 /* TestConstants.swift in Sources */,
F33D303E2D8B129600531D64 /* AutoUploadUITests.swift in Sources */,
AABD0C9B2D5F73FC00F009E6 /* Placeholder.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -4884,6 +4886,8 @@
F7CDB5C32FA33CA300F72306 /* NCMediaViewerPageView.swift in Sources */,
F7CDB5C42FA33CA300F72306 /* NCImageViewerContentView.swift in Sources */,
F7CDB5C52FA33CA300F72306 /* NCMediaViewerModel.swift in Sources */,
AABBCC0230A8000100F0A001 /* NCMediaPlaybackOptions.swift in Sources */,
AABBCC0630A8000100F0A001 /* NCVideoPlaybackPresentationContext.swift in Sources */,
F7CDB5C62FA33CA300F72306 /* NCMediaViewerView.swift in Sources */,
F7CDB5CC2FA33CA300F72306 /* NCNextcloudMediaViewerLoader.swift in Sources */,
F76341182EBE0BC60056F538 /* NCNetworking+NextcloudKitDelegate.swift in Sources */,
Expand Down Expand Up @@ -6319,7 +6323,7 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 2;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = NKUJUXUJ3B;
Expand Down Expand Up @@ -6387,7 +6391,7 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 2;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = NKUJUXUJ3B;
Expand Down
7 changes: 0 additions & 7 deletions Tests/NextcloudIntegrationTests/Placeholder.swift

This file was deleted.

5 changes: 3 additions & 2 deletions Tests/NextcloudUnitTests/NCCameraRollTests.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: Nextcloud GmbH
// SPDX-FileCopyrightText: 2026 Marino Faggiana
// SPDX-License-Identifier: GPL-3.0-or-later

import AVFoundation
import Foundation
Expand Down
77 changes: 77 additions & 0 deletions Tests/NextcloudUnitTests/NCMediaPlaybackOptionsTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// SPDX-FileCopyrightText: Nextcloud GmbH
// SPDX-FileCopyrightText: 2026 Marino Faggiana
// SPDX-License-Identifier: GPL-3.0-or-later

import Testing
@testable import Nextcloud

@Suite("Media playback completion options")
@MainActor
struct NCMediaPlaybackOptionsTests {
@Test("Playback stops when no completion option is enabled")
func stopsByDefault() {
let options = NCMediaPlaybackOptions(preferences: nil)

#expect(options.completionAction == .stop)
}

@Test("Automatic advance plays the next compatible item")
func advancesAutomatically() {
let options = NCMediaPlaybackOptions(preferences: nil)

options.toggleAutoAdvance()

#expect(options.completionAction == .playNextItem)
}

@Test("Disabling repeat restores normal stop behavior")
func disablingRepeatRestoresStop() {
let options = NCMediaPlaybackOptions(preferences: nil)

options.toggleRepeat()
#expect(options.completionAction == .repeatCurrentItem)

options.toggleRepeat()
#expect(options.completionAction == .stop)
}

@Test("Repeat takes precedence over automatic advance")
func repeatTakesPrecedence() {
let options = NCMediaPlaybackOptions(preferences: nil)

options.toggleAutoAdvance()
options.toggleRepeat()

#expect(options.completionAction == .repeatCurrentItem)

options.toggleRepeat()

#expect(options.completionAction == .playNextItem)
}

@Test("Playback options restore and save their preferences")
func persistsPlaybackOptions() {
let preferences = NCPreferences()
let originalRepeat = preferences.mediaViewerRepeatCurrentItem
let originalAutoAdvance = preferences.mediaViewerAutoAdvance

defer {
preferences.mediaViewerRepeatCurrentItem = originalRepeat
preferences.mediaViewerAutoAdvance = originalAutoAdvance
}

preferences.mediaViewerRepeatCurrentItem = true
preferences.mediaViewerAutoAdvance = false

let options = NCMediaPlaybackOptions(preferences: preferences)

#expect(options.isRepeatEnabled)
#expect(!options.isAutoAdvanceEnabled)

options.toggleRepeat()
options.toggleAutoAdvance()

#expect(!preferences.mediaViewerRepeatCurrentItem)
#expect(preferences.mediaViewerAutoAdvance)
}
}
129 changes: 129 additions & 0 deletions Tests/NextcloudUnitTests/NCMediaViewerModelTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// SPDX-FileCopyrightText: Nextcloud GmbH
// SPDX-FileCopyrightText: 2026 Marino Faggiana
// SPDX-License-Identifier: GPL-3.0-or-later

import Testing
@testable import Nextcloud

@Suite("Media viewer model")
@MainActor
struct NCMediaViewerModelTests {
@Test("Duplicate media identifiers produce a single viewer page")
func removesDuplicateIdentifiers() {
let metadata = tableMetadata()
metadata.ocId = "current"

let model = NCMediaViewerInitialModel(
currentMetadata: metadata,
ocIds: ["first", "current", "current", "last", "first"]
)

#expect(model.normalizedOcIds == ["first", "current", "last"])
#expect(model.currentSelectedIndex == 1)
}

@Test("Missing current media is inserted before the supplied identifiers")
func insertsMissingCurrentIdentifier() {
let metadata = tableMetadata()
metadata.ocId = "current"

let model = NCMediaViewerInitialModel(
currentMetadata: metadata,
ocIds: ["next", "next"]
)

#expect(model.normalizedOcIds == ["current", "next"])
#expect(model.currentSelectedIndex == 0)
}

@Test("Programmatic paging deactivates media until the target settles")
func programmaticPagingActivatesOnlySettledTarget() {
let model = makeViewerModel()

#expect(model.activePageIndex == 0)

model.beginPageTransition(
to: 1,
shouldAutoPlay: true
)

#expect(model.pageTransition == .programmatic(targetIndex: 1))
#expect(model.selectedIndex == 0)
#expect(model.activePageIndex == nil)
#expect(model.autoPlayTargetIndex == 1)

model.finishPageTransition(at: 1)

#expect(model.pageTransition == .idle)
#expect(model.selectedIndex == 1)
#expect(model.activePageIndex == 1)
#expect(model.autoPlayTargetIndex == 1)
}

@Test("Interactive paging keeps intermediate pages inactive")
func interactivePagingKeepsIntermediatePagesInactive() {
let model = makeViewerModel()

model.beginPageTransition(
to: nil,
shouldAutoPlay: false
)
model.updateSelectedIndexDuringTransition(1)

#expect(model.pageTransition == .interactive)
#expect(model.selectedIndex == 1)
#expect(model.activePageIndex == nil)

model.finishPageTransition(at: 1)

#expect(model.pageTransition == .idle)
#expect(model.activePageIndex == 1)
}

@Test("Settling on an unexpected page cancels pending autoplay")
func unexpectedSettledPageCancelsAutoPlay() {
let model = makeViewerModel()

model.beginPageTransition(
to: 2,
shouldAutoPlay: true
)
model.finishPageTransition(at: 1)

#expect(model.pageTransition == .idle)
#expect(model.activePageIndex == 1)
#expect(model.autoPlayTargetIndex == nil)
}

@Test("Selection updates are ignored outside an interactive transition")
func ignoresTransitionUpdatesWhileIdle() {
let model = makeViewerModel()

model.updateSelectedIndexDuringTransition(1)

#expect(model.selectedIndex == 0)
#expect(model.activePageIndex == 0)
}

@Test("Loading a page does not implicitly change the selection")
func loadingDoesNotSelectPage() async {
let model = makeViewerModel()

await model.displayPage(at: 1)

#expect(model.selectedIndex == 0)
#expect(model.activePageIndex == 0)
}

private func makeViewerModel() -> NCMediaViewerModel {
let metadata = tableMetadata()
metadata.ocId = "first"

return NCMediaViewerModel(
currentMetadata: metadata,
ocIds: ["first", "second", "third"],
session: NCSession().getSession(account: ""),
loader: NCMediaViewerLoader()
)
}
}
Loading
Loading