Repository: scala-steward-org/scala-steward (scala)

Issue:
Using scala-steward to update scalaVersion ?
Would it be possible to adapt scala-steward to update not only library dependencies but the version of Scala used to ? This would make sense between minor releases of scalac (e.g. 2.12.7 -> 2.12.8) but I'd be particularly interested in automating upgrades between Dotty releases (right now we manually bump a bunch of projects when we do a release, but that doesn't scale).

Pull request description:
Partially fixes #589 by attempting to create a draft PR for scala updates

## Description
Instead of `updates.includeScala` being strictly `true` or `false`, we introduce a third option called `"draft"` and make it the default option (and conveniently rename the other options to `"yes"` or `"no"`). When `updates.includeScala` is set to `"draft"`, we raise a github pull-request in draft mode and leave it to the project maintainer to further work on it if needed, mark it ready for review and then merge. Other options `"yes"` and `"no"` behave like in the previous revision. Thess draft PRs will ensure that scala projects do get a "notification" whenever there is an scala version update

## Downsides
- Sbt plugins may see a barrage of scala upgrades from 2.12 to 2.13 and will fail the builds
- Projects with scala cross builds will possibly see bad PRs

If we see too many downsides, we can set the default option to "no"

Patch:
diff --git a/docs/repo-specific-configuration.md b/docs/repo-specific-configuration.md
index bb2fe0e504..d9e23e365a 100644
--- a/docs/repo-specific-configuration.md
+++ b/docs/repo-specific-configuration.md
@@ -57,12 +57,13 @@ updates.ignore = [ { groupId = "org.acme", artifactId="foo", version = "1.0" } ]
 # Default: None
 updates.limit = 5
 
-# By default, Scala Steward does not update scala version since its tricky, error-prone
-# and results in bad PRs and/or failed builds
-# If set to true, Scala Steward will attempt to update the scala version
-# Since this feature is experimental, the default is set to false
-# Default: false
-updates.includeScala = true
+# If set to "yes", Scala Steward will create PR for scala updates
+# If set to "draft", Scala Steward will create draft PR for scala updates
+# If set to "no", Scala Steward will not create PR for scala updates
+# The default is set to "draft" since updating scala version is tricky and error-prone
+# and is left upto the repo maintainer to mark it ready for review and merge when satisfactory
+# Default: draft
+updates.includeScala = "yes" | "draft" | "no"
 
 # The extensions of files that should be updated.
 # Default: [".scala", ".sbt", ".sbt.shared", ".sc", ".yml", "pom.xml"]
diff --git a/modules/core/src/main/scala/org/scalasteward/core/repoconfig/IncludeScalaStrategy.scala b/modules/core/src/main/scala/org/scalasteward/core/repoconfig/IncludeScalaStrategy.scala
new file mode 100644
index 0000000000..02ed2baf6d
--- /dev/null
+++ b/modules/core/src/main/scala/org/scalasteward/core/repoconfig/IncludeScalaStrategy.scala
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2018-2021 Scala Steward contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.scalasteward.core.repoconfig
+
+import cats.Eq
+import io.circe.{Decoder, Encoder}
+
+sealed trait IncludeScalaStrategy {
+  def name: String
+}
+
+object IncludeScalaStrategy {
+  final case object Yes extends IncludeScalaStrategy { val name = "yes" }
+  final case object Draft extends IncludeScalaStrategy { val name = "draft" }
+  final case object No extends IncludeScalaStrategy { val name = "no" }
+
+  val default: IncludeScalaStrategy = Draft
+
+  def fromString(value: String): IncludeScalaStrategy =
+    value.trim.toLowerCase match {
+      case Yes.name   => Yes
+      case Draft.name => Draft
+      case No.name    => No
+      case _          => default
+    }
+
+  def fromBoolean(value: Boolean): IncludeScalaStrategy =
+    if (value) Yes else No
+
+  implicit val includeScalaStrategyDecoder: Decoder[IncludeScalaStrategy] =
+    Decoder[Boolean].map(fromBoolean).or(Decoder[String].map(fromString))
+
+  implicit val includeScalaStrategyEncoder: Encoder[IncludeScalaStrategy] =
+    Encoder[String].contramap(_.name)
+
+  implicit val includeScalaStrategyEq: Eq[IncludeScalaStrategy] =
+    Eq.fromUniversalEquals
+}
diff --git a/modules/core/src/main/scala/org/scalasteward/core/repoconfig/UpdatesConfig.scala b/modules/core/src/main/scala/org/scalasteward/core/repoconfig/UpdatesConfig.scala
index f803ccf11e..02ae787950 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/repoconfig/UpdatesConfig.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/repoconfig/UpdatesConfig.scala
@@ -37,11 +37,11 @@ final case class UpdatesConfig(
     allow: List[UpdatePattern] = List.empty,
     ignore: List[UpdatePattern] = List.empty,
     limit: Option[NonNegInt] = None,
-    includeScala: Option[Boolean] = None,
+    includeScala: Option[IncludeScalaStrategy] = None,
     fileExtensions: Option[List[String]] = None
 ) {
-  def includeScalaOrDefault: Boolean =
-    includeScala.getOrElse(UpdatesConfig.defaultIncludeScala)
+  def includeScalaOrDefault: IncludeScalaStrategy =
+    includeScala.getOrElse(IncludeScalaStrategy.default)
 
   def fileExtensionsOrDefault: Set[String] =
     fileExtensions.fold(UpdatesConfig.defaultFileExtensions)(_.toSet)
diff --git a/modules/core/src/main/scala/org/scalasteward/core/update/PruningAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/update/PruningAlg.scala
index b3a8b55168..6333b13fef 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/update/PruningAlg.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/update/PruningAlg.scala
@@ -23,7 +23,7 @@ import io.chrisdavenport.log4cats.Logger
 import org.scalasteward.core.data._
 import org.scalasteward.core.nurture.PullRequestRepository
 import org.scalasteward.core.repocache.RepoCache
-import org.scalasteward.core.repoconfig.{PullRequestFrequency, RepoConfig}
+import org.scalasteward.core.repoconfig.{IncludeScalaStrategy, PullRequestFrequency, RepoConfig}
 import org.scalasteward.core.update.PruningAlg._
 import org.scalasteward.core.update.data.UpdateState
 import org.scalasteward.core.update.data.UpdateState._
@@ -41,7 +41,8 @@ final class PruningAlg[F[_]](implicit
     F: Monad[F]
 ) {
   def needsAttention(data: RepoData): F[(Boolean, List[Update.Single])] = {
-    val ignoreScalaDependency = !data.config.updates.includeScalaOrDefault
+    val ignoreScalaDependency =
+      data.config.updates.includeScalaOrDefault === IncludeScalaStrategy.No
     val dependencies = data.cache.dependencyInfos
       .flatMap(_.sequence)
       .collect {
diff --git a/modules/core/src/main/scala/org/scalasteward/core/vcs/data/NewPullRequestData.scala b/modules/core/src/main/scala/org/scalasteward/core/vcs/data/NewPullRequestData.scala
index ec95f81400..bcef2371f3 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/vcs/data/NewPullRequestData.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/vcs/data/NewPullRequestData.scala
@@ -23,8 +23,9 @@ import org.http4s.Uri
 import org.scalasteward.core.data.{GroupId, ReleaseRelatedUrl, SemVer, Update, UpdateData}
 import org.scalasteward.core.git
 import org.scalasteward.core.git.Branch
-import org.scalasteward.core.repoconfig.RepoConfigAlg
+import org.scalasteward.core.repoconfig.{IncludeScalaStrategy, RepoConfigAlg}
 import org.scalasteward.core.scalafix.Migration
+import org.scalasteward.core.update.FilterAlg
 import org.scalasteward.core.util.Details
 
 final case class UpdateState(
@@ -38,7 +39,8 @@ final case class NewPullRequestData(
     title: String,
     body: String,
     head: String,
-    base: Branch
+    base: Branch,
+    draft: Boolean = false
 )
 
 object NewPullRequestData {
@@ -209,6 +211,8 @@ object NewPullRequestData {
         filesWithOldVersion
       ),
       head = branchName,
-      base = data.baseBranch
+      base = data.baseBranch,
+      draft = data.update.dependencies.exists(FilterAlg.isScalaDependency) &&
+        (data.repoData.config.updates.includeScalaOrDefault === IncludeScalaStrategy.Draft)
     )
 }


Test patch:
diff --git a/modules/core/src/test/scala/org/scalasteward/core/TestInstances.scala b/modules/core/src/test/scala/org/scalasteward/core/TestInstances.scala
index 1529b88c9a..f3147373be 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/TestInstances.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/TestInstances.scala
@@ -143,6 +143,15 @@ object TestInstances {
   private def smallListOf[A](maxSize: Int, genA: Gen[A]): Gen[List[A]] =
     Gen.choose(0, maxSize).flatMap(n => Gen.listOfN(n, genA))
 
+  implicit val includeScalaStrategyArbitrary: Arbitrary[IncludeScalaStrategy] =
+    Arbitrary(
+      Gen.oneOf(
+        IncludeScalaStrategy.Yes,
+        IncludeScalaStrategy.Draft,
+        IncludeScalaStrategy.No
+      )
+    )
+
   implicit val updatesConfigArbitrary: Arbitrary[UpdatesConfig] =
     Arbitrary(
       for {
@@ -150,7 +159,7 @@ object TestInstances {
         allow <- smallListOf(4, Arbitrary.arbitrary[UpdatePattern])
         ignore <- smallListOf(4, Arbitrary.arbitrary[UpdatePattern])
         limit <- Arbitrary.arbitrary[Option[NonNegInt]]
-        includeScala <- Arbitrary.arbitrary[Option[Boolean]]
+        includeScala <- Arbitrary.arbitrary[Option[IncludeScalaStrategy]]
         fileExtensions <- Arbitrary.arbitrary[Option[List[String]]]
       } yield UpdatesConfig(
         pin = pin,
diff --git a/modules/core/src/test/scala/org/scalasteward/core/repoconfig/RepoConfigAlgTest.scala b/modules/core/src/test/scala/org/scalasteward/core/repoconfig/RepoConfigAlgTest.scala
index 874e8a1cab..118bf695ff 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/repoconfig/RepoConfigAlgTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/repoconfig/RepoConfigAlgTest.scala
@@ -25,7 +25,7 @@ class RepoConfigAlgTest extends FunSuite {
          |               ]
          |updates.ignore = [ { groupId = "org.acme", version = "1.0" } ]
          |updates.limit = 4
-         |updates.includeScala = true
+         |updates.includeScala = "yes"
          |updates.fileExtensions = [ ".txt" ]
          |pullRequests.frequency = "@weekly"
          |commits.message = "Update ${artifactName} from ${currentVersion} to ${nextVersion}"
@@ -68,7 +68,7 @@ class RepoConfigAlgTest extends FunSuite {
           UpdatePattern(GroupId("org.acme"), None, Some(UpdatePattern.Version(Some("1.0"), None)))
         ),
         limit = Some(NonNegInt.unsafeFrom(4)),
-        includeScala = Some(true),
+        includeScala = Some(IncludeScalaStrategy.Yes),
         fileExtensions = Some(List(".txt"))
       ),
       commits = CommitsConfig(
@@ -79,6 +79,52 @@ class RepoConfigAlgTest extends FunSuite {
     assertEquals(config, expected)
   }
 
+  test("config with 'updates.includeScala = false'") {
+    val content = "updates.includeScala = false"
+    val config = RepoConfigAlg.parseRepoConfig(content)
+    val expected = RepoConfig(updates = UpdatesConfig(includeScala = Some(IncludeScalaStrategy.No)))
+    assertEquals(config, Right(expected))
+  }
+
+  test("config with 'updates.includeScala = true'") {
+    val content = "updates.includeScala = true"
+    val config = RepoConfigAlg.parseRepoConfig(content)
+    val expected =
+      RepoConfig(updates = UpdatesConfig(includeScala = Some(IncludeScalaStrategy.Yes)))
+    assertEquals(config, Right(expected))
+  }
+
+  test("config with 'updates.includeScala = yes'") {
+    val content = """updates.includeScala = "yes""""
+    val config = RepoConfigAlg.parseRepoConfig(content)
+    val expected =
+      RepoConfig(updates = UpdatesConfig(includeScala = Some(IncludeScalaStrategy.Yes)))
+    assertEquals(config, Right(expected))
+  }
+
+  test("config with 'updates.includeScala = no'") {
+    val content = """updates.includeScala = "no""""
+    val config = RepoConfigAlg.parseRepoConfig(content)
+    val expected = RepoConfig(updates = UpdatesConfig(includeScala = Some(IncludeScalaStrategy.No)))
+    assertEquals(config, Right(expected))
+  }
+
+  test("config with 'updates.includeScala = draft'") {
+    val content = """updates.includeScala = "draft""""
+    val config = RepoConfigAlg.parseRepoConfig(content)
+    val expected =
+      RepoConfig(updates = UpdatesConfig(includeScala = Some(IncludeScalaStrategy.Draft)))
+    assertEquals(config, Right(expected))
+  }
+
+  test("config with 'updates.includeScala = foo'") {
+    val content = """updates.includeScala = "foo""""
+    val config = RepoConfigAlg.parseRepoConfig(content)
+    val expected =
+      RepoConfig(updates = UpdatesConfig(includeScala = Some(IncludeScalaStrategy.Draft)))
+    assertEquals(config, Right(expected))
+  }
+
   test("config with 'updatePullRequests = false'") {
     val content = "updatePullRequests = false"
     val config = RepoConfigAlg.parseRepoConfig(content)
diff --git a/modules/core/src/test/scala/org/scalasteward/core/update/PruningAlgTest.scala b/modules/core/src/test/scala/org/scalasteward/core/update/PruningAlgTest.scala
index 0c027e779c..02dd5efd4f 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/update/PruningAlgTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/update/PruningAlgTest.scala
@@ -70,7 +70,7 @@ class PruningAlgTest extends FunSuite {
     assertEquals(state, expected)
   }
 
-  test("needsAttention: 0 updates when includeScala not specified in repo config") {
+  test("needsAttention: 0 updates when includeScala=no specified in repo config") {
     val repo = Repo("pruning-test", "repo2")
     val Right(repoCache) = decode[RepoCache](
       s"""|{
@@ -108,6 +108,9 @@ class PruningAlgTest extends FunSuite {
           |  "maybeRepoConfig": {
           |    "pullRequests": {
           |      "frequency": "@daily"
+          |    },
+          |    "updates" : {
+          |      "includeScala" : "no"
           |    }
           |  }
           |}""".stripMargin
@@ -173,7 +176,7 @@ class PruningAlgTest extends FunSuite {
     assertEquals(state, expected)
   }
 
-  test("needsAttention: update scala-library when includeScala=true in repo config") {
+  test("needsAttention: update scala-library when includeScala=yes in repo config") {
     val repo = Repo("pruning-test", "repo3")
     val Right(repoCache) = decode[RepoCache](
       s"""|{
@@ -210,7 +213,114 @@ class PruningAlgTest extends FunSuite {
           |  ],
           |  "maybeRepoConfig": {
           |    "updates" : {
-          |      "includeScala" : true
+          |      "includeScala" : "yes"
+          |    }
+          |  }
+          |}""".stripMargin
+    )
+    val pullRequestsFile =
+      config.workspace / s"store/pull_requests/v2/github/${repo.show}/pull_requests.json"
+    val pullRequestsContent =
+      s"""|{
+          |  "https://github.com/${repo.show}/pull/27" : {
+          |    "baseSha1" : "12def27a837ba6dc9e17406cbbe342fba3527c14",
+          |    "update" : {
+          |      "Single" : {
+          |        "crossDependency" : [
+          |          {
+          |            "groupId" : "org.scalatest",
+          |            "artifactId" : {
+          |              "name" : "scalatest",
+          |              "maybeCrossName" : "scalatest_2.12"
+          |            },
+          |            "version" : "3.0.8",
+          |            "sbtVersion" : null,
+          |            "scalaVersion" : null,
+          |            "configurations" : null
+          |          }
+          |        ],
+          |        "newerVersions" : [
+          |          "3.1.0"
+          |        ],
+          |        "newerGroupId" : null
+          |      }
+          |    },
+          |    "state" : "open",
+          |    "entryCreatedAt" : 1581969227183
+          |  }
+          |}""".stripMargin
+    val versionsFile =
+      config.workspace / "store/versions/v2/https/foobar.org/maven2/org/scala-lang/scala-library/versions.json"
+    val versionsContent =
+      s"""|{
+          |  "updatedAt" : 9999999999999,
+          |  "versions" : [
+          |    "2.12.9",
+          |    "2.12.10",
+          |    "2.12.11",
+          |    "2.13.0",
+          |    "2.13.1"
+          |  ]
+          |}
+          |""".stripMargin
+    val initial = MockState.empty
+      .addFiles(pullRequestsFile -> pullRequestsContent, versionsFile -> versionsContent)
+      .unsafeRunSync()
+    val data = RepoData(repo, repoCache, repoCache.maybeRepoConfig.getOrElse(RepoConfig.empty))
+    val state = pruningAlg.needsAttention(data).runS(initial).unsafeRunSync()
+    val expected = initial.copy(
+      trace = Vector(
+        Log(s"Find updates for ${repo.show}"),
+        Cmd("read", versionsFile.toString),
+        Cmd("read", pullRequestsFile.toString),
+        Cmd("read", versionsFile.toString),
+        Log("Found 1 update:\n  org.scala-lang:scala-library : 2.12.10 -> 2.12.11"),
+        Log(
+          s"${repo.show} is outdated:\n  new version: org.scala-lang:scala-library : 2.12.10 -> 2.12.11"
+        )
+      )
+    )
+    assertEquals(state, expected)
+  }
+
+  test("needsAttention: update scala-library when includeScala=draft in repo config") {
+    val repo = Repo("pruning-test", "repo4")
+    val Right(repoCache) = decode[RepoCache](
+      s"""|{
+          |  "sha1": "12def27a837ba6dc9e17406cbbe342fba3527c14",
+          |  "dependencyInfos" : [
+          |    {
+          |      "value" : [
+          |        {
+          |          "dependency" : {
+          |            "groupId" : "org.scala-lang",
+          |            "artifactId" : {
+          |              "name" : "scala-library",
+          |              "maybeCrossName" : null
+          |            },
+          |            "version" : "2.12.10",
+          |            "sbtVersion" : null,
+          |            "scalaVersion" : null,
+          |            "configurations" : null
+          |          },
+          |          "filesContainingVersion" : [
+          |            "build.sbt"
+          |          ]
+          |        }
+          |      ],
+          |      "resolvers" : [
+          |        {
+          |          "MavenRepository" : {
+          |            "name" : "public",
+          |            "location" : "https://foobar.org/maven2/"
+          |          }
+          |        }
+          |      ]
+          |    }
+          |  ],
+          |  "maybeRepoConfig": {
+          |    "updates" : {
+          |      "includeScala" : "draft"
           |    }
           |  }
           |}""".stripMargin
diff --git a/modules/core/src/test/scala/org/scalasteward/core/vcs/data/NewPullRequestDataTest.scala b/modules/core/src/test/scala/org/scalasteward/core/vcs/data/NewPullRequestDataTest.scala
index 7b162b0f83..49e5b25c05 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/vcs/data/NewPullRequestDataTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/vcs/data/NewPullRequestDataTest.scala
@@ -8,7 +8,7 @@ import org.scalasteward.core.TestSyntax._
 import org.scalasteward.core.buildtool.sbt.data.SbtVersion
 import org.scalasteward.core.data._
 import org.scalasteward.core.git.{Branch, Sha1}
-import org.scalasteward.core.repoconfig.RepoConfig
+import org.scalasteward.core.repoconfig.{IncludeScalaStrategy, RepoConfig, UpdatesConfig}
 import org.scalasteward.core.scalafix.Migration
 import org.scalasteward.core.util.Nel
 
@@ -31,7 +31,67 @@ class NewPullRequestDataTest extends FunSuite {
             |  "title" : "Update logback-classic to 1.2.3",
             |  "body" : "Updates ch.qos.logback:logback-classic from 1.2.0 to 1.2.3.\n\n\nI'll automatically update this PR to resolve conflicts as long as you don't change it yourself.\n\nIf you'd like to skip this version, you can just close this PR. If you have any feedback, just mention me in the comments below.\n\nConfigure Scala Steward for your repository with a [`.scala-steward.conf`](https://github.com/scala-steward-org/scala-steward/blob/${org.scalasteward.core.BuildInfo.gitHeadCommit}/docs/repo-specific-configuration.md) file.\n\nHave a fantastic day writing Scala!\n\n<details>\n<summary>Ignore future updates</summary>\n\nAdd this to your `.scala-steward.conf` file to ignore future updates of this dependency:\n```\nupdates.ignore = [ { groupId = \"ch.qos.logback\", artifactId = \"logback-classic\" } ]\n```\n</details>\n\nlabels: library-update, semver-patch",
             |  "head" : "scala-steward:update/logback-classic-1.2.3",
-            |  "base" : "master"
+            |  "base" : "master",
+            |  "draft" : false
+            |}""".stripMargin
+    assertEquals(obtained, expected)
+  }
+
+  test("asJson for scala update with includeScalaStrategy=draft") {
+    val data = UpdateData(
+      RepoData(
+        Repo("foo", "bar"),
+        dummyRepoCache,
+        RepoConfig(updates = UpdatesConfig(includeScala = Some(IncludeScalaStrategy.Draft)))
+      ),
+      Repo("scala-steward", "bar"),
+      Update.Single("org.scala-lang" % "scala-library" % "2.12.10", Nel.of("2.12.11")),
+      Branch("master"),
+      Sha1(Sha1.HexString.unsafeFrom("d6b6791d2ea11df1d156fe70979ab8c3a5ba3433")),
+      Branch("update/scala-library-2.12.11")
+    )
+    val obtained = NewPullRequestData
+      .from(data, "scala-steward:update/scala-library-2.12.11")
+      .asJson
+      .spaces2
+    val expected =
+      raw"""|{
+            |  "title" : "Update scala-library to 2.12.11",
+            |  "body" : "Updates org.scala-lang:scala-library from 2.12.10 to 2.12.11.\n\n\nI'll automatically update this PR to resolve conflicts as long as you don't change it yourself.\n\nIf you'd like to skip this version, you can just close this PR. If you have any feedback, just mention me in the comments below.\n\nConfigure Scala Steward for your repository with a [`.scala-steward.conf`](https://github.com/scala-steward-org/scala-steward/blob/${org.scalasteward.core.BuildInfo.gitHeadCommit}/docs/repo-specific-configuration.md) file.\n\nHave a fantastic day writing Scala!\n\n<details>\n<summary>Ignore future updates</summary>\n\nAdd this to your `.scala-steward.conf` file to ignore future updates of this dependency:\n```\nupdates.ignore = [ { groupId = \"org.scala-lang\", artifactId = \"scala-library\" } ]\n```\n</details>\n\nlabels: library-update, semver-patch",
+            |  "head" : "scala-steward:update/scala-library-2.12.11",
+            |  "base" : "master",
+            |  "draft" : true
+            |}""".stripMargin
+    assertEquals(obtained, expected)
+  }
+
+  test("asJson for group scala update with includeScalaStrategy=draft") {
+    val data = UpdateData(
+      RepoData(
+        Repo("foo", "bar"),
+        dummyRepoCache,
+        RepoConfig(updates = UpdatesConfig(includeScala = Some(IncludeScalaStrategy.Draft)))
+      ),
+      Repo("scala-steward", "bar"),
+      Update.Group(
+        "org.scala-lang" % Nel.of("scala-library", "scala-reflect") % "2.12.10",
+        Nel.of("2.12.11")
+      ),
+      Branch("master"),
+      Sha1(Sha1.HexString.unsafeFrom("d6b6791d2ea11df1d156fe70979ab8c3a5ba3433")),
+      Branch("update/scala-library-2.12.11")
+    )
+    val obtained = NewPullRequestData
+      .from(data, "scala-steward:update/scala-library-2.12.11")
+      .asJson
+      .spaces2
+    val expected =
+      raw"""|{
+            |  "title" : "Update scala-library, scala-reflect to 2.12.11",
+            |  "body" : "Updates \n* org.scala-lang:scala-library\n* org.scala-lang:scala-reflect\n\n from 2.12.10 to 2.12.11.\n\n\nI'll automatically update this PR to resolve conflicts as long as you don't change it yourself.\n\nIf you'd like to skip this version, you can just close this PR. If you have any feedback, just mention me in the comments below.\n\nConfigure Scala Steward for your repository with a [`.scala-steward.conf`](https://github.com/scala-steward-org/scala-steward/blob/${org.scalasteward.core.BuildInfo.gitHeadCommit}/docs/repo-specific-configuration.md) file.\n\nHave a fantastic day writing Scala!\n\n<details>\n<summary>Ignore future updates</summary>\n\nAdd this to your `.scala-steward.conf` file to ignore future updates of this dependency:\n```\nupdates.ignore = [ { groupId = \"org.scala-lang\" } ]\n```\n</details>\n\nlabels: library-update, semver-patch",
+            |  "head" : "scala-steward:update/scala-library-2.12.11",
+            |  "base" : "master",
+            |  "draft" : true
             |}""".stripMargin
     assertEquals(obtained, expected)
   }