Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
9 changes: 8 additions & 1 deletion apps/settings/lib/Controller/AppSettingsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
use OCP\Files\SimpleFS\ISimpleFile;
use OCP\Files\SimpleFS\ISimpleFolder;
use OCP\Http\Client\IClientService;
use OCP\IAppConfig;
use OCP\IConfig;
use OCP\IGroup;
use OCP\IL10N;
Expand Down Expand Up @@ -74,6 +75,7 @@ public function __construct(
private IInitialState $initialState,
private AppDiscoverFetcher $discoverFetcher,
private IClientService $clientService,
private IAppConfig $appConfig,
) {
parent::__construct($appName, $request);
$this->appData = $appDataFactory->get('appstore');
Expand All @@ -90,7 +92,12 @@ public function viewApps(): TemplateResponse {

$this->initialState->provideInitialState('appstoreEnabled', $this->config->getSystemValueBool('appstoreenabled', true));
$this->initialState->provideInitialState('appstoreBundles', $this->getBundles());
$this->initialState->provideInitialState('appstoreDeveloperDocs', $this->urlGenerator->linkToDocs('developer-manual'));

// Conditionally set developer docs link based on configuration
$displayDocumentationLink = $this->appConfig->getValueBool('settings', 'display_documentation_link', true);
$developerDocsUrl = $displayDocumentationLink ? $this->urlGenerator->linkToDocs('developer-manual') : '';
$this->initialState->provideInitialState('appstoreDeveloperDocs', $developerDocsUrl);

$this->initialState->provideInitialState('appstoreUpdateCount', count($this->getAppsWithUpdates()));

if ($this->appManager->isInstalled('app_api')) {
Expand Down
3 changes: 2 additions & 1 deletion apps/settings/src/views/AppStoreNavigation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@
</NcAppNavigationItem>
</template>

<NcAppNavigationItem id="app-developer-docs"
<NcAppNavigationItem v-if="developerDocsUrl"
id="app-developer-docs"
:name="t('settings', 'Developer documentation ↗')"
:href="developerDocsUrl" />
</template>
Expand Down
127 changes: 125 additions & 2 deletions apps/settings/tests/Controller/AppSettingsControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use OCP\AppFramework\Services\IInitialState;
use OCP\Files\AppData\IAppDataFactory;
use OCP\Http\Client\IClientService;
use OCP\IAppConfig;
use OCP\IConfig;
use OCP\IL10N;
use OCP\INavigationManager;
Expand All @@ -45,6 +46,8 @@ class AppSettingsControllerTest extends TestCase {
private $l10n;
/** @var IConfig|MockObject */
private $config;
/** @var IAppConfig|MockObject */
private $appConfig;
/** @var INavigationManager|MockObject */
private $navigationManager;
private AppManager&MockObject $appManager;
Expand Down Expand Up @@ -81,6 +84,7 @@ protected function setUp(): void {
->method('t')
->willReturnArgument(0);
$this->config = $this->createMock(IConfig::class);
$this->appConfig = $this->createMock(IAppConfig::class);
$this->navigationManager = $this->createMock(INavigationManager::class);
$this->appManager = $this->createMock(AppManager::class);
$this->categoryFetcher = $this->createMock(CategoryFetcher::class);
Expand Down Expand Up @@ -112,6 +116,7 @@ protected function setUp(): void {
$this->initialState,
$this->discoverFetcher,
$this->clientService,
$this->appConfig,
);
}

Expand Down Expand Up @@ -180,14 +185,31 @@ public function testViewApps(): void {
->method('getSystemValueBool')
->with('appstoreenabled', true)
->willReturn(true);
$this->appConfig
->expects($this->once())
->method('getValueBool')
->with('settings', 'display_documentation_link', true)
->willReturn(true);
$this->navigationManager
->expects($this->once())
->method('setActiveEntry')
->with('core_apps');

// Test that developer docs link is generated correctly
$this->urlGenerator
->expects($this->once())
->method('linkToDocs')
->with('developer-manual')
->willReturn('https://docs.nextcloud.com/server/latest/developer_manual/');

$this->initialState
->expects($this->exactly(4))
->method('provideInitialState');
->method('provideInitialState')
->willReturnCallback(function ($key, $value) {
if ($key === 'appstoreDeveloperDocs') {
$this->assertEquals('https://docs.nextcloud.com/server/latest/developer_manual/', $value);
}
});

$policy = new ContentSecurityPolicy();
$policy->addAllowedImageDomain('https://usercontent.apps.nextcloud.com');
Expand All @@ -213,14 +235,31 @@ public function testViewAppsAppstoreNotEnabled(): void {
->method('getSystemValueBool')
->with('appstoreenabled', true)
->willReturn(false);
$this->appConfig
->expects($this->once())
->method('getValueBool')
->with('settings', 'display_documentation_link', true)
->willReturn(true);
$this->navigationManager
->expects($this->once())
->method('setActiveEntry')
->with('core_apps');

// Test that developer docs link is still generated even when appstore is disabled
$this->urlGenerator
->expects($this->once())
->method('linkToDocs')
->with('developer-manual')
->willReturn('https://docs.nextcloud.com/server/latest/developer_manual/');

$this->initialState
->expects($this->exactly(4))
->method('provideInitialState');
->method('provideInitialState')
->willReturnCallback(function ($key, $value) {
if ($key === 'appstoreDeveloperDocs') {
$this->assertEquals('https://docs.nextcloud.com/server/latest/developer_manual/', $value);
}
});

$policy = new ContentSecurityPolicy();
$policy->addAllowedImageDomain('https://usercontent.apps.nextcloud.com');
Expand All @@ -235,4 +274,88 @@ public function testViewAppsAppstoreNotEnabled(): void {

$this->assertEquals($expected, $this->appSettingsController->viewApps());
}

public function testDeveloperDocumentationLinkHiddenWhenConfigured(): void {
$this->installer->expects($this->any())
->method('isUpdateAvailable')
->willReturn(false);
$this->bundleFetcher->expects($this->once())->method('getBundles')->willReturn([]);
$this->config
->expects($this->once())
->method('getSystemValueBool')
->with('appstoreenabled', true)
->willReturn(true);
$this->appConfig
->expects($this->once())
->method('getValueBool')
->with('settings', 'display_documentation_link', true)
->willReturn(false);
$this->navigationManager
->expects($this->once())
->method('setActiveEntry')
->with('core_apps');

// When display_documentation_link is false, linkToDocs should not be called
$this->urlGenerator
->expects($this->never())
->method('linkToDocs');

$providedStates = [];
$this->initialState
->expects($this->exactly(4))
->method('provideInitialState')
->willReturnCallback(function ($key, $value) use (&$providedStates) {
$providedStates[$key] = $value;
});
Comment on lines +307 to +309
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same callback pattern is duplicated in multiple test methods (lines 307-309, 351-353, 208-212, 258-262). Consider extracting this into a helper method to reduce duplication and improve maintainability.

Copilot uses AI. Check for mistakes.

$this->appSettingsController->viewApps();

// Assert that the developer docs state was provided with an empty string
$this->assertArrayHasKey('appstoreDeveloperDocs', $providedStates);
$this->assertEquals('', $providedStates['appstoreDeveloperDocs']);
}

public function testDeveloperDocumentationLinkShownByDefault(): void {
$this->installer->expects($this->any())
->method('isUpdateAvailable')
->willReturn(false);
$this->bundleFetcher->expects($this->once())->method('getBundles')->willReturn([]);
$this->config
->expects($this->once())
->method('getSystemValueBool')
->with('appstoreenabled', true)
->willReturn(true);
$this->appConfig
->expects($this->once())
->method('getValueBool')
->with('settings', 'display_documentation_link', true)
->willReturn(true);
$this->navigationManager
->expects($this->once())
->method('setActiveEntry')
->with('core_apps');

$developerDocsUrl = 'https://docs.nextcloud.com/server/latest/developer_manual/';

// When display_documentation_link is true (default), linkToDocs should be called
$this->urlGenerator
->expects($this->once())
->method('linkToDocs')
->with('developer-manual')
->willReturn($developerDocsUrl);

$providedStates = [];
$this->initialState
->expects($this->exactly(4))
->method('provideInitialState')
->willReturnCallback(function ($key, $value) use (&$providedStates) {
$providedStates[$key] = $value;
});

$this->appSettingsController->viewApps();

// Assert that the developer docs state was provided with the correct URL
$this->assertArrayHasKey('appstoreDeveloperDocs', $providedStates);
$this->assertEquals($developerDocsUrl, $providedStates['appstoreDeveloperDocs']);
}
}
Loading