diff --git a/js/src/test/scala/com/raquo/waypoint/BasePathSpec.scala b/js/src/test/scala/com/raquo/waypoint/BasePathSpec.scala index 954f0db..aca5d50 100644 --- a/js/src/test/scala/com/raquo/waypoint/BasePathSpec.scala +++ b/js/src/test/scala/com/raquo/waypoint/BasePathSpec.scala @@ -39,41 +39,41 @@ class BasePathSpec extends UnitSpec { basePath = basePath ) - val homeRouteTotal: Route.Total[HomePage.type, Unit] = Route.staticTotal( + val homeRouteTotal: Route[HomePage.type, Unit] = Route.staticTotal( HomePage, pattern = root / endOfSegments, basePath = basePath ) - val libraryRoute: Route.Total[LibraryPage, Int] = Route( + val libraryRoute: Route[LibraryPage, Int] = Route( encode = _.libraryId, decode = arg => LibraryPage(libraryId = arg), pattern = root / "app" / "library" / segment[Int] / endOfSegments, basePath = basePath ) - val textRoute: Route.Total[TextPage, String] = Route( + val textRoute: Route[TextPage, String] = Route( encode = _.text, decode = arg => TextPage(text = arg), pattern = root / "app" / "test" / segment[String] / endOfSegments, basePath = basePath ) - val noteRoute: Route.Total[NotePage, (Int, Int)] = Route( + val noteRoute: Route[NotePage, (Int, Int)] = Route( encode = page => (page.libraryId, page.noteId), decode = args => NotePage(libraryId = args._1, noteId = args._2, scrollPosition = 0), pattern = root / "app" / "library" / segment[Int] / "note" / segment[Int] / endOfSegments, basePath = basePath ) - val searchRoute: Route.Total[SearchPage, String] = Route.onlyQuery( + val searchRoute: Route[SearchPage, String] = Route.onlyQuery( encode = page => page.query, decode = arg => SearchPage(arg), pattern = (root / "search" / endOfSegments) ? param[String]("query"), basePath = basePath ) - val bigLegalRoute: Route.Total[BigLegalPage, FragmentPatternArgs[String, Unit, String]] = Route.withFragment( + val bigLegalRoute: Route[BigLegalPage, FragmentPatternArgs[String, Unit, String]] = Route.withFragment( encode = page => FragmentPatternArgs(path = page.page, (), fragment = page.section), decode = args => BigLegalPage(page = args.path, section = args.fragment), pattern = (root / "legal" / segment[String] / endOfSegments) withFragment fragment[String], @@ -274,7 +274,7 @@ class BasePathSpec extends UnitSpec { urlForPage(DocsPage(NumPage(0))) shouldBe Some(s"$basePath/docs/zero/0") urlForPage(DocsPage(NumPage(50))) shouldBe None } - + it("should not compile with non-singleton type for a staticTotal route") { assertTypeError( """|Route.staticTotal( @@ -285,7 +285,7 @@ class BasePathSpec extends UnitSpec { |""".stripMargin ) } - + it("should not compile with non-singleton type ascription for a staticTotal route") { assertTypeError( """|Route.staticTotal[AppPage]( diff --git a/js/src/test/scala/com/raquo/waypoint/DynamicRouteSpec.scala b/js/src/test/scala/com/raquo/waypoint/DynamicRouteSpec.scala index b1a2008..8c2159d 100644 --- a/js/src/test/scala/com/raquo/waypoint/DynamicRouteSpec.scala +++ b/js/src/test/scala/com/raquo/waypoint/DynamicRouteSpec.scala @@ -1,16 +1,13 @@ package com.raquo.waypoint -import com.raquo.waypoint.fixtures.{AppPage, UnitSpec} -import com.raquo.waypoint.fixtures.AppPage._ import com.raquo.airstream.ownership.Owner import com.raquo.laminar.api._ import com.raquo.waypoint.fixtures.AppPage.DocsSection._ -import com.raquo.waypoint.fixtures.AppPage.LibraryPage -import org.scalactic -import org.scalatest.Assertion +import com.raquo.waypoint.fixtures.AppPage._ +import com.raquo.waypoint.fixtures.{AppPage, UnitSpec} import upickle.default._ -import scala.util.{Success, Try} +import scala.util.Try class DynamicRouteSpec extends UnitSpec { @@ -18,49 +15,49 @@ class DynamicRouteSpec extends UnitSpec { val origin = "http://example.com" - val libraryRoute: Route.Total[LibraryPage, Int] = Route( + val libraryRoute: Route[LibraryPage, Int] = Route( encode = _.libraryId, decode = arg => LibraryPage(libraryId = arg), pattern = root / "app" / "library" / segment[Int] / endOfSegments ) - val textRoute: Route.Total[TextPage, String] = Route( + val textRoute: Route[TextPage, String] = Route( encode = _.text, decode = arg => TextPage(text = arg), pattern = root / "app" / "test" / segment[String] / endOfSegments ) - val noteRoute: Route.Total[NotePage, (Int, Int)] = Route( + val noteRoute: Route[NotePage, (Int, Int)] = Route( encode = page => (page.libraryId, page.noteId), decode = args => NotePage(libraryId = args._1, noteId = args._2, scrollPosition = 0), pattern = root / "app" / "library" / segment[Int] / "note" / segment[Int] / endOfSegments ) - val searchRoute: Route.Total[SearchPage, String] = Route.onlyQuery( + val searchRoute: Route[SearchPage, String] = Route.onlyQuery( encode = page => page.query, decode = arg => SearchPage(arg), pattern = (root / "search" / endOfSegments) ? param[String]("query") ) - val workspaceSearchRoute: Route.Total[WorkspaceSearchPage, PatternArgs[String, String]] = Route.withQuery( + val workspaceSearchRoute: Route[WorkspaceSearchPage, PatternArgs[String, String]] = Route.withQuery( encode = page => PatternArgs(page.workspaceId, page.query), decode = args => WorkspaceSearchPage(workspaceId = args.path, query = args.params), pattern = (root / "workspace" / segment[String] / endOfSegments) ? param[String]("query") ) - val legalRoute: Route.Total[LegalPage, String] = Route.onlyFragment( + val legalRoute: Route[LegalPage, String] = Route.onlyFragment( encode = page => page.section, decode = arg => LegalPage(arg), pattern = (root / "legal" / endOfSegments) withFragment fragment[String] ) - val bigLegalRoute: Route.Total[BigLegalPage, FragmentPatternArgs[String, Unit, String]] = Route.withFragment( + val bigLegalRoute: Route[BigLegalPage, FragmentPatternArgs[String, Unit, String]] = Route.withFragment( encode = page => FragmentPatternArgs(path = page.page, (), fragment = page.section), decode = args => BigLegalPage(page = args.path, section = args.fragment), pattern = (root / "legal" / segment[String] / endOfSegments) withFragment fragment[String] ) - val hugeLegalRoute: Route.Total[HugeLegalPage, FragmentPatternArgs[String, Int, String]] = Route.withQueryAndFragment( + val hugeLegalRoute: Route[HugeLegalPage, FragmentPatternArgs[String, Int, String]] = Route.withQueryAndFragment( encode = page => FragmentPatternArgs(path = page.page, query = page.version, fragment = page.section), decode = args => HugeLegalPage(page = args.path, version = args.query, section = args.fragment), pattern = (root / "legal" / segment[String] / endOfSegments) ? param[Int]("version") withFragment fragment[String] diff --git a/shared/src/main/scala/com/raquo/waypoint/Route.scala b/shared/src/main/scala/com/raquo/waypoint/Route.scala index ae5efee..5b3acb8 100644 --- a/shared/src/main/scala/com/raquo/waypoint/Route.scala +++ b/shared/src/main/scala/com/raquo/waypoint/Route.scala @@ -27,11 +27,11 @@ sealed abstract class Route[Page, Args] private[waypoint]( } /** @return None if the [[Route]] is partial and the given object does not match. */ - def argsFromPage(page: Page): Option[Args] = encode(page) + def argsFromPage(page: Page): Option[Args] = encodeOpt(page) /** @return None if the [[Route]] is partial and the given object does not match. */ def relativeUrlForPage[P >: Page](page: P): Option[String] = - encode(page).map(relativeUrlForArgs) + encodeOpt(page).map(relativeUrlForArgs) def relativeUrlForArgs(args: Args): String = basePath + createRelativeUrl(args) @@ -47,7 +47,7 @@ sealed abstract class Route[Page, Args] private[waypoint]( val originMatches = Utils.absoluteUrlMatchesOrigin(origin, url) val urlToMatch = if (originMatches) url.substring(origin.length) else url // @TODO[API] We evaluate the page unconditionally, as that will consistently throw in case of malformed URL - val maybePage = matchRelativeUrl(urlToMatch).flatMap(decode) // This just ignores the origin present in the url + val maybePage = matchRelativeUrl(urlToMatch).flatMap(decodeOpt) // This just ignores the origin present in the url if (originMatches) { maybePage } else { @@ -68,21 +68,21 @@ sealed abstract class Route[Page, Args] private[waypoint]( } if (url.startsWith(basePath)) { val urlWithoutBasePath = url.substring(basePath.length) - matchRelativeUrl(origin + urlWithoutBasePath).flatMap(decode) + matchRelativeUrl(origin + urlWithoutBasePath).flatMap(decodeOpt) } else if (Utils.basePathHasEmptyFragment(basePath) && url == Utils.basePathWithoutFragment(basePath)) { - matchRelativeUrl(origin).flatMap(decode) + matchRelativeUrl(origin).flatMap(decodeOpt) } else { None } } - private def encode(page: Any): Option[Args] = { + private def encodeOpt(page: Any): Option[Args] = { matchEncodePF .andThen(Some(_)) .applyOrElse(page, (_: Any) => None) } - private def decode(args: Args): Option[Page] = { + private def decodeOpt(args: Args): Option[Page] = { decodePF .andThen[Option[Page]](Some(_)) .applyOrElse(args, (_: Args) => None) @@ -119,7 +119,7 @@ object Route { */ class Total[Page, Args] private[waypoint]( encode: Page => Args, - decode: Args => Page, + decode: Args => Page, // #TODO[unused] Should we remove it? Or expose it? matchEncodePF: PartialFunction[Any, Args], decodePF: PartialFunction[Args, Page], createRelativeUrl: Args => String, @@ -135,16 +135,15 @@ object Route { relativeUrlForArgs(encode(page)) } object Total { - private[waypoint] def apply[Page, Args]( + private[waypoint] def apply[Page: ClassTag, Args]( encode: Page => Args, decode: Args => Page, createRelativeUrl: Args => String, matchRelativeUrl: String => Option[Args], - basePath: String, - klass: Class[Page] + basePath: String ): Total[Page, Args] = new Total( encode, decode, - matchEncodePF = { case p if klass.isInstance(p) => encode(klass.cast(p)) }, + matchEncodePF = { case p: Page => encode(p) }, decodePF = { case args => decode(args) }, createRelativeUrl = createRelativeUrl, matchRelativeUrl = matchRelativeUrl, @@ -169,8 +168,7 @@ object Route { encode, decode, createRelativeUrl = args => "/" + pattern.createPath(args), matchRelativeUrl = relativeUrl => pattern.matchRawUrl(relativeUrl).toOption, - basePath = basePath, - klass = classFromClassTag + basePath = basePath ) } @@ -210,8 +208,7 @@ object Route { encode, decode, createRelativeUrl = args => "/" + pattern.createUrlString(path = (), params = args), matchRelativeUrl = relativeUrl => pattern.matchRawUrl(relativeUrl).toOption.map(_.params), - basePath = basePath, - klass = classFromClassTag + basePath = basePath ) } @@ -250,8 +247,7 @@ object Route { encode, decode, createRelativeUrl = args => "/" + pattern.createUrlString(path = args.path, params = args.params), matchRelativeUrl = relativeUrl => pattern.matchRawUrl(relativeUrl).toOption, - basePath = basePath, - klass = classFromClassTag + basePath = basePath ) } @@ -290,8 +286,7 @@ object Route { encode, decode, createRelativeUrl = args => "/" + pattern.fragmentOnly.createPart(args), matchRelativeUrl = relativeUrl => pattern.matchRawUrl(relativeUrl).toOption.map(_.fragment), - basePath = basePath, - klass = classFromClassTag + basePath = basePath ) } @@ -333,8 +328,7 @@ object Route { "/" + pattern.createPart(patternArgs) }, matchRelativeUrl = relativeUrl => pattern.matchRawUrl(relativeUrl).toOption, - basePath = basePath, - klass = classFromClassTag + basePath = basePath ) } @@ -379,8 +373,7 @@ object Route { "/" + pattern.createPart(patternArgs) }, matchRelativeUrl = relativeUrl => pattern.matchRawUrl(relativeUrl).toOption, - basePath = basePath, - klass = classFromClassTag + basePath = basePath ) } @@ -425,8 +418,7 @@ object Route { "/" + pattern.createPart(patternArgs) }, matchRelativeUrl = relativeUrl => pattern.matchRawUrl(relativeUrl).toOption, - basePath = basePath, - klass = classFromClassTag + basePath = basePath ) } @@ -478,24 +470,21 @@ object Route { /** Create a route for a static page that does not encode any data in the URL. * * This only allows using singleton types, like `object Foo`. - * - * @see [[ValueOf]] + * + * @see [[ValueOf]] - evidence that `Page` is a singleton type * */ - def staticTotal[Page]( + def staticTotal[Page: ValueOf: ClassTag]( staticPage: Page, pattern: PathSegment[Unit, DummyError], basePath: String = "" - )(implicit valueOf: ValueOf[Page]): Total[Page, Unit] = { + ): Total[Page, Unit] = { Total[Page, Unit]( encode = _ => (), decode = _ => staticPage, createRelativeUrl = args => "/" + pattern.createPath(args), matchRelativeUrl = relativeUrl => pattern.matchRawUrl(relativeUrl).toOption, - basePath = basePath, - klass = staticPage.getClass.asInstanceOf[Class[Page]] + basePath = basePath ) } - private def classFromClassTag[A](implicit ct: ClassTag[A]): Class[A] = - ct.runtimeClass.asInstanceOf[Class[A]] }