Skip to content

Improve precision of IDB API and add tests #569

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Sep 9, 2021
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
198 changes: 130 additions & 68 deletions api-reports/2_12.txt

Large diffs are not rendered by default.

198 changes: 130 additions & 68 deletions api-reports/2_13.txt

Large diffs are not rendered by default.

358 changes: 243 additions & 115 deletions src/main/scala/org/scalajs/dom/IDBTypes.scala

Large diffs are not rendered by default.

25 changes: 18 additions & 7 deletions src/main/scala/org/scalajs/dom/idb.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,32 @@ package org.scalajs.dom

/** Short aliases of all the dom.IDBThing classes */
object idb {
type Cursor = IDBCursor
@inline def CursorDirection = IDBCursorDirection
type CursorWithValue = IDBCursorWithValue

type CreateIndexOptions = IDBCreateIndexOptions
type CreateObjectStoreOptions = IDBCreateObjectStoreOptions
type Cursor[+Source] = IDBCursor[Source]
type CursorReadOnly[+Source] = IDBCursorReadOnly[Source]
type CursorWithValue[+Source] = IDBCursorWithValue[Source]
type Database = IDBDatabase
type DatabaseInfo = IDBDatabaseInfo
type Event[+TargetResult] = IDBEvent[TargetResult]
type EventTarget[+Result] = IDBEventTarget[Result]
type Factory = IDBFactory
type Index = IDBIndex
type Key = IDBKey
type KeyPath = IDBKeyPath
type KeyRange = IDBKeyRange
@inline def KeyRange = IDBKeyRange
type ObjectStore = IDBObjectStore
type OpenDBRequest = IDBOpenDBRequest
type Request = IDBRequest
type OpenDBRequest[TargetResult] = IDBOpenDBRequest[TargetResult]
type Request[+Source, TargetResult] = IDBRequest[Source, TargetResult]
type Transaction = IDBTransaction
@inline def TransactionMode = IDBTransactionMode
type Value = IDBValue
type VersionChangeEvent = IDBVersionChangeEvent

@inline def CursorDirection = IDBCursorDirection
@inline def KeyRange = IDBKeyRange
@inline def TransactionMode = IDBTransactionMode

@deprecated(
"Removed. This feature is no longer recommended. Though some browsers might still support it, it may have already been removed from the relevant web standards, may be in the process of being dropped, or may only be kept for compatibility purposes. Avoid using it, and update existing code if possible. See https://developer.mozilla.org/en-US/docs/Web/API/IDBEnvironment",
"1.2.0")
Expand Down
12 changes: 12 additions & 0 deletions src/main/scala/org/scalajs/dom/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,16 @@ package object dom {

@deprecated("use NodeList[T] instead", "2.0.0")
type NodeListOf[+T <: Node] = NodeList[T]

type IDBKey = Any

/** A valid key path can include one of the following: an empty string, a JavaScript identifier, or multiple
* JavaScript identifiers separated by periods or an array containing any of those. It cannot include spaces.
*
* @see
* https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Basic_Terminology#key_path
*/
type IDBKeyPath = Any

type IDBValue = Any
}
9 changes: 5 additions & 4 deletions src/main/scala/org/scalajs/dom/raw.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ package org.scalajs.dom
import org.scalajs.dom
import scala.scalajs.js
import scala.scalajs.js.annotation._
import scala.scalajs.js.|

@deprecated("All the members of raw.* have been moved to dom.*", "2.0.0")
object raw {
Expand Down Expand Up @@ -479,10 +480,10 @@ object raw {
type HTMLVideoElement = dom.HTMLVideoElement

@deprecated("use dom.IDBCursor instead", "2.0.0")
type IDBCursor = dom.IDBCursor
type IDBCursor = dom.IDBCursor[IDBObjectStore | IDBIndex]

@deprecated("use dom.IDBCursorWithValue instead", "2.0.0")
type IDBCursorWithValue = dom.IDBCursorWithValue
type IDBCursorWithValue = dom.IDBCursorWithValue[Any]

@deprecated("use dom.IDBDatabase instead", "2.0.0")
type IDBDatabase = dom.IDBDatabase
Expand All @@ -506,10 +507,10 @@ object raw {
type IDBObjectStore = dom.IDBObjectStore

@deprecated("use dom.IDBOpenDBRequest instead", "2.0.0")
type IDBOpenDBRequest = dom.IDBOpenDBRequest
type IDBOpenDBRequest = dom.IDBOpenDBRequest[Any]

@deprecated("use dom.IDBRequest instead", "2.0.0")
type IDBRequest = dom.IDBRequest
type IDBRequest = dom.IDBRequest[Any, Any]

@deprecated("use dom.IDBTransaction instead", "2.0.0")
type IDBTransaction = dom.IDBTransaction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.scalajs.dom.tests.shared
import org.junit.Assert
import scala.concurrent._
import scala.util._
import scala.scalajs.js
import scala.scalajs.js.timers._

object AsyncTesting {
Expand All @@ -12,6 +13,12 @@ object AsyncTesting {
implicit def global: ExecutionContext =
ExecutionContext.global

def asyncPass: AsyncResult =
Future.successful(Success(()))

def asyncWhenDefined[A](o: js.UndefOr[A])(f: A => AsyncResult): AsyncResult =
o.fold(asyncPass)(f)

def async(run: => Future[Any]): AsyncResult = {
val p = Promise[Try[Unit]]()
val timeout = setTimeout(1200) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package org.scalajs.dom.tests.shared

import java.util.UUID
import org.junit.Assert._
import org.scalajs.dom._
import org.scalajs.dom.tests.shared.AsyncTesting._
import scala.concurrent._
import scala.scalajs.js
import scala.util.Try

/** Scala.js version of https://gist.github.com/JamesMessinger/a0d6389a5d0e3a24814b */
object IdbTest {

def apply(idb: js.UndefOr[IDBFactory]): AsyncResult =
asyncWhenDefined(idb)(apply(_))

def apply(idb: IDBFactory): AsyncResult = async {
open(idb).flatMap(use)
}

private def open(idb: IDBFactory): Future[IDBDatabase] = {
val p = Promise[IDBDatabase]()
val r = idb.open(UUID.randomUUID().toString)

r.onerror = (e: Event) => fail(p, "idb.open failed: " + r.error)

r.onupgradeneeded = (e: IDBEvent[IDBDatabase]) => {
val db = e.target.result
val opts = new IDBCreateObjectStoreOptions { override val keyPath = "id" }
val store = db.createObjectStore("MyObjectStore", opts)
store.createIndex("NameIndex", js.Array("name.last", "name.first"))
}

r.onsuccess = (e: IDBEvent[IDBDatabase]) => {
assertSame(r.result, e.target.result)
p.success(r.result)
}

p.future
}

private def fail(p: Promise[_], why: Any): Unit =
p.failure(new RuntimeException("" + why))

private def use(db: IDBDatabase): Future[Unit] = {
import js.Dynamic.{literal => obj}

val tx = db.transaction("MyObjectStore", IDBTransactionMode.readwrite)
val store = tx.objectStore("MyObjectStore")
val index = store.index("NameIndex")

// Add some data
store.put(obj(id = 12345, name = obj(first = "John", last = "Doe"), age = 42))
store.put(obj(id = 67890, name = obj(first = "Bob", last = "Smith"), age = 35))

// Query the data
val getJohn = store.get(12345)
val getBob = index.get(js.Array("Smith", "Bob"))

// Close the db when the transaction is done
tx.oncomplete = (e: Event) => {
db.close()
}

def getFirstName(r: IDBRequest[_, IDBValue]): Future[String] = {
val p = Promise[String]()
r.onerror = (e: Event) => fail(p, e)
r.onsuccess = (e: IDBEvent[IDBValue]) => {
p.complete(Try(e.target.result.asInstanceOf[js.Dynamic].name.first.asInstanceOf[String]))
}
p.future
}

for {
john <- getFirstName(getJohn)
bob <- getFirstName(getBob)
} yield {
assertEquals("John", john)
assertEquals("Bob", bob)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package org.scalajs.dom.tests.shared

import java.util.UUID
import org.scalajs.dom.tests.shared.AsyncTesting._
import org.junit.Test

trait SharedTests {
import SharedTests._

// ===================================================================================================================
// Tests WITHOUT org.scalajs.dom._ in scope

// This tests that ops are always implicitly available, no imports required
@Test final def NodeListOpsTest(): Unit =
Expand All @@ -18,7 +20,8 @@ trait SharedTests {
.map(_.classList.mkString)
}

// Don't move up
// ===================================================================================================================
// Tests WITH org.scalajs.dom._ in scope
import org.scalajs.dom._

// https://github.com/scala-js/scala-js-dom/issues/411 - console doesn't work in web workers
Expand All @@ -30,17 +33,6 @@ trait SharedTests {
val _ = crypto.HashAlgorithm
}

@Test final def WindowIdbTest(): Unit =
window.indexedDB.foreach(testIdb)

}

object SharedTests {
import org.scalajs.dom._

def testIdb(idb: IDBFactory): Unit = {
val open = idb.open(UUID.randomUUID().toString())
open.onerror = (e: Event) => sys.error("idb open failed: " + e)
// TODO: Test properly in a different PR
}
@Test final def WindowIdbTest(): AsyncResult =
IdbTest(window.indexedDB)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.scalajs.dom.tests.webworker

import org.scalajs.dom._
import scala.concurrent.ExecutionContext.Implicits.global

object Server extends ServerResponses {
import Protocol._
Expand All @@ -13,9 +14,11 @@ object Server extends ServerResponses {
val id = msgIn._1
val cmdId = msgIn._2
val cmd = WebWorkerCmd.byId(cmdId)
val output = respond(cmd)
val msgOut = Message(id, output)
ww.postMessage(msgOut)
respond(cmd).onComplete { t =>
val output = t.getOrElse(t.failed.get.toString)
val msgOut = Message(id, output)
ww.postMessage(msgOut)
}
}

ww.postMessage(Message(ServerStarted, ""))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.scalajs.dom.tests.webworker

import scala.language.implicitConversions
import scala.concurrent.Future
import org.junit.Assert._
import org.junit.Test
import org.scalajs.dom.tests.shared.AsyncTesting._
Expand Down Expand Up @@ -46,10 +48,21 @@ trait WebWorkerTests {
// =====================================================================================================================
trait ServerResponses {
import org.scalajs.dom._
import org.scalajs.dom.tests.shared.SharedTests._
import org.scalajs.dom.tests.shared._
import org.scalajs.dom.DedicatedWorkerGlobalScope.self

final val respond: WebWorkerCmd => String = {
private implicit def autoLift(s: => String): Future[String] =
Future(s)

private implicit class AsyncOps(r: AsyncResult) {
def andReturn(s: String): Future[String] =
r.map { t =>
t.get
s
}
}

final val respond: WebWorkerCmd => Future[String] = {

case SayHello =>
"hello"
Expand All @@ -60,7 +73,6 @@ trait ServerResponses {

case TestIdb =>
assertTrue(self.indexedDB.isDefined)
testIdb(self.indexedDB.get)
"ok"
IdbTest(self.indexedDB.get).andReturn("ok")
}
}