diff --git a/platform/whatsNew/intellij.platform.whatsNew.iml b/platform/whatsNew/intellij.platform.whatsNew.iml
index 15ce108ed47b..0743acaf1813 100644
--- a/platform/whatsNew/intellij.platform.whatsNew.iml
+++ b/platform/whatsNew/intellij.platform.whatsNew.iml
@@ -40,7 +40,8 @@
-
+
+
\ No newline at end of file
diff --git a/platform/whatsNew/src/com/intellij/platform/whatsNew/WhatsNewContent.kt b/platform/whatsNew/src/com/intellij/platform/whatsNew/WhatsNewContent.kt
index 53dda6c7d2b4..0cdf8008a42d 100644
--- a/platform/whatsNew/src/com/intellij/platform/whatsNew/WhatsNewContent.kt
+++ b/platform/whatsNew/src/com/intellij/platform/whatsNew/WhatsNewContent.kt
@@ -27,7 +27,7 @@ import java.net.HttpURLConnection
import java.net.URL
import java.nio.charset.StandardCharsets
-internal sealed class WhatsNewContent {
+internal abstract class WhatsNewContent {
companion object {
private val DataContext.project: Project?
get() = CommonDataKeys.PROJECT.getData(this)
diff --git a/platform/whatsNew/src/com/intellij/platform/whatsNew/WhatsNewShowOnStartCheckService.kt b/platform/whatsNew/src/com/intellij/platform/whatsNew/WhatsNewShowOnStartCheckService.kt
index f7e6aabc34a0..e0065ee46c6c 100644
--- a/platform/whatsNew/src/com/intellij/platform/whatsNew/WhatsNewShowOnStartCheckService.kt
+++ b/platform/whatsNew/src/com/intellij/platform/whatsNew/WhatsNewShowOnStartCheckService.kt
@@ -8,7 +8,6 @@ import com.intellij.internal.performanceTests.ProjectInitializationDiagnosticSer
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.client.ClientKind
import com.intellij.openapi.client.ClientSessionsManager
-import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.startup.ProjectActivity
@@ -18,32 +17,55 @@ import com.intellij.util.application
import kotlinx.coroutines.withContext
import java.util.concurrent.atomic.AtomicBoolean
-internal class WhatsNewShowOnStartCheckService : ProjectActivity {
- private val ourStarted = AtomicBoolean(false)
+internal interface WhatsNewEnvironmentAccessor {
+ val isForceDisabled: Boolean
+ suspend fun getWhatsNewContent(): WhatsNewContent?
+ fun findAction(): WhatsNewAction?
+ suspend fun showWhatsNew(project: Project, action: WhatsNewAction)
+}
+
+private class WhatsNewEnvironmentAccessorImpl : WhatsNewEnvironmentAccessor {
+
private val isPlaybackMode = SystemProperties.getBooleanProperty("idea.is.playback", false)
+ override val isForceDisabled: Boolean
+ get() = application.isHeadlessEnvironment
+ || application.isUnitTestMode
+ || isPlaybackMode
+ // TODO: disable for remdev since lux is not ready to open what's new
+ || AppMode.isRemoteDevHost()
+ // TODO: disable for UI tests since UI tests are not ready for What's new
+ || Registry.`is`("expose.ui.hierarchy.url", false)
+
+ override suspend fun getWhatsNewContent() = WhatsNewContent.getWhatsNewContent()
+ override fun findAction() = ActionManager.getInstance().getAction("WhatsNewAction") as? WhatsNewAction
+ override suspend fun showWhatsNew(project: Project, action: WhatsNewAction) {
+ action.openWhatsNew(project)
+ }
+}
+
+internal class WhatsNewShowOnStartCheckService(private val environment: WhatsNewEnvironmentAccessor) : ProjectActivity {
+ @Suppress("unused") // used by the component container
+ constructor() : this(WhatsNewEnvironmentAccessorImpl())
+
+ private val wasStarted = AtomicBoolean(false)
+
override suspend fun execute(project: Project) {
- if (ourStarted.getAndSet(true)) return
- if (application.isHeadlessEnvironment
- || application.isUnitTestMode
- || isPlaybackMode
- // TODO: disable for remdev since lux is not ready to open what's new
- || AppMode.isRemoteDevHost()
- // TODO: disable for UI tests since UI tests are not ready for What's new
- || Registry.`is`("expose.ui.hierarchy.url", false)) return
+ if (wasStarted.getAndSet(true)) return
+ if (environment.isForceDisabled) return
logger.info("Checking whether to show the What's New page on startup.")
// a bit hacky workaround but now we don't have any tools to forward local startup activities to a controller
val clientId = ClientSessionsManager.getAppSessions(ClientKind.CONTROLLER).firstOrNull()?.clientId ?: ClientId.localId
withContext(clientId.asContextElement()) {
- val content = WhatsNewContent.getWhatsNewContent()
+ val content = environment.getWhatsNewContent()
logger.info("Got What's New content: $content")
if (content != null) {
if (WhatsNewContentVersionChecker.isNeedToShowContent(content).also { logger.info("Should show What's New: $it") }) {
- val whatsNewAction = service().getAction("WhatsNewAction") as? WhatsNewAction
+ val whatsNewAction = environment.findAction()
if (whatsNewAction != null) {
- val activityTracker = ProjectInitializationDiagnosticService.registerTracker(project, "OpenWhatsNewOnStart");
- whatsNewAction.openWhatsNew(project)
+ val activityTracker = ProjectInitializationDiagnosticService.registerTracker(project, "OpenWhatsNewOnStart")
+ environment.showWhatsNew(project, whatsNewAction)
activityTracker.activityFinished()
}
}
@@ -52,4 +74,4 @@ internal class WhatsNewShowOnStartCheckService : ProjectActivity {
}
}
-private val logger = logger()
\ No newline at end of file
+private val logger = logger()
diff --git a/platform/whatsNew/testSrc/com/intellij/platform/whatsNew/OnStartCheckServiceTest.kt b/platform/whatsNew/testSrc/com/intellij/platform/whatsNew/OnStartCheckServiceTest.kt
new file mode 100644
index 000000000000..be822250ab2a
--- /dev/null
+++ b/platform/whatsNew/testSrc/com/intellij/platform/whatsNew/OnStartCheckServiceTest.kt
@@ -0,0 +1,59 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.platform.whatsNew
+
+import com.intellij.openapi.actionSystem.DataContext
+import com.intellij.openapi.project.Project
+import com.intellij.testFramework.junit5.TestApplication
+import com.intellij.testFramework.junit5.fixture.projectFixture
+import kotlinx.coroutines.runBlocking
+import org.junit.jupiter.api.Assertions.assertFalse
+import org.junit.jupiter.api.Assertions.assertTrue
+import org.junit.jupiter.api.Test
+import java.util.concurrent.atomic.AtomicBoolean
+
+@TestApplication
+class OnStartCheckServiceTest {
+
+ private val project = projectFixture()
+
+ private val mockContent = object : WhatsNewContent() {
+ override fun getRequest(dataContext: DataContext?) = error("Mock object, do not call")
+ override fun getActionWhiteList() = error("Mock object, do not call")
+ override fun getVersion() = error("Mock object, do not call")
+ override suspend fun isAvailable() = error("Mock object, do not call")
+ }
+
+ @Test
+ fun `WhatsNew should be disabled`() {
+ val (environment, isCalled) = mockAccessor(isDisabled = true)
+ val service = WhatsNewShowOnStartCheckService(environment)
+ runBlocking {
+ service.execute(project.get())
+ assertFalse(isCalled.get(), "What's New page should not be shown")
+ }
+ }
+
+ @Test
+ fun `WhatsNew should be shown`() {
+ val (environment, isCalled) = mockAccessor(isDisabled = false)
+ val service = WhatsNewShowOnStartCheckService(environment)
+ runBlocking {
+ service.execute(project.get())
+ assertTrue(isCalled.get(), "What's New page should be shown")
+ }
+ }
+
+ private fun mockAccessor(isDisabled: Boolean): Pair {
+ val isCalled = AtomicBoolean(false)
+ val environment = object : WhatsNewEnvironmentAccessor {
+ override val isForceDisabled = isDisabled
+ override suspend fun getWhatsNewContent() = mockContent
+ override fun findAction() = WhatsNewAction()
+ override suspend fun showWhatsNew(project: Project, action: WhatsNewAction) {
+ isCalled.set(true)
+ }
+ }
+
+ return environment to isCalled
+ }
+}