// META: global=window,worker
// META: title=IDBObjectStore.put()
// META: script=resources/support.js
// @author Microsoft <https://www.microsoft.com>
// @author Intel <http://www.intel.com>

'use strict';

async_test(t => {
    let db;
    const record = { key: 1, property: "data" };

    const open_rq = createdb(t);
    open_rq.onupgradeneeded = function(e) {
        db = e.target.result;
        const objStore = db.createObjectStore("store", { keyPath: "key" });

        objStore.put(record);
    };

    open_rq.onsuccess = function(e) {
        const rq = db.transaction("store", "readonly")
                   .objectStore("store")
                   .get(record.key);

        rq.onsuccess = t.step_func(function(e) {
            assert_equals(e.target.result.property, record.property);
            assert_equals(e.target.result.key, record.key);
            t.done();
        });
    };
}, 'put() with an inline key');

async_test(t => {
    let db;
    const key = 1;
    const record = { property: "data" };

    const open_rq = createdb(t);
    open_rq.onupgradeneeded = function(e) {
        db = e.target.result;
        const objStore = db.createObjectStore("store");

        objStore.put(record, key);
    };

    open_rq.onsuccess = function(e) {
        const rq = db.transaction("store", "readonly")
                   .objectStore("store")
                   .get(key);

        rq.onsuccess = t.step_func(function(e) {
            assert_equals(e.target.result.property, record.property);

            t.done();
        });
    };
},'put() with an out-of-line key');

async_test(t => {
    let db;
    let success_event;
    const record = { key: 1, property: "data" };
    const record_put = { key: 1, property: "changed", more: ["stuff", 2] };

    const open_rq = createdb(t);
    open_rq.onupgradeneeded = function(e) {
        db = e.target.result;
        const objStore = db.createObjectStore("store", { keyPath: "key" });
        objStore.put(record);

        const rq = objStore.put(record_put);
        rq.onerror = fail(t, "error on put");

        rq.onsuccess = t.step_func(function(e) {
            success_event = true;
        });
    };

    open_rq.onsuccess = function(e) {
        assert_true(success_event);

        const rq = db.transaction("store", "readonly")
                   .objectStore("store")
                   .get(1);

        rq.onsuccess = t.step_func(function(e) {
            const rec = e.target.result;

            assert_equals(rec.key, record_put.key);
            assert_equals(rec.property, record_put.property);
            assert_array_equals(rec.more, record_put.more);

            t.done();
        });
    };
}, 'put() record with key already exists');

async_test(t => {
    const record = { key: 1, property: "data" };

    const open_rq = createdb(t);
    open_rq.onupgradeneeded = function(e) {
        let db = e.target.result;
        const objStore = db.createObjectStore("store", {
             autoIncrement: true });
        objStore.createIndex("i1", "property", { unique: true });
        objStore.put(record);

        const rq = objStore.put(record);
        rq.onsuccess = fail(t, "success on putting duplicate indexed record");

        rq.onerror = t.step_func(function(e) {
            assert_equals(rq.error.name, "ConstraintError");
            assert_equals(e.target.error.name, "ConstraintError");

            assert_equals(e.type, "error");

            e.preventDefault();
            e.stopPropagation();
        });
    };

    // Defer done, giving a spurious rq.onsuccess a chance to run
    open_rq.onsuccess = function(e) {
        t.done();
    };
}, 'put() where an index has unique:true specified');

async_test(t => {
    let db;
    const record = { test: { obj: { key: 1 } }, property: "data" };

    const open_rq = createdb(t);
    open_rq.onupgradeneeded = function(e) {
        db = e.target.result;
        const objStore = db.createObjectStore("store",
        { keyPath: "test.obj.key" });
        objStore.put(record);
    };

    open_rq.onsuccess = function(e) {
        const rq = db.transaction("store", "readonly")
                   .objectStore("store")
                   .get(record.test.obj.key);

        rq.onsuccess = t.step_func(function(e) {
            assert_equals(e.target.result.property, record.property);

            t.done();
        });
    };
}, 'Object store\'s key path is an object attribute');

async_test(t => {
    let db;
    const record = { property: "data" };
    const expected_keys = [1, 2, 3, 4];

    const open_rq = createdb(t);
    open_rq.onupgradeneeded = function(e) {
      db = e.target.result;
      const objStore = db.createObjectStore("store", { keyPath: "key",
      autoIncrement: true });

      objStore.put(record);
      objStore.put(record);
      objStore.put(record);
      objStore.put(record);
    };

    open_rq.onsuccess = function(e) {
      let actual_keys = [];
      const rq = db.transaction("store", "readonly")
          .objectStore("store")
          .openCursor();

      rq.onsuccess = t.step_func(function(e) {
        const cursor = e.target.result;

        if (cursor) {
          actual_keys.push(cursor.value.key);
          cursor.continue();
        } else {
          assert_array_equals(actual_keys, expected_keys);
          t.done();
        }
      });
    };
  }, 'autoIncrement and inline keys');

async_test(t => {
    let db;
    const record = { property: "data" };
    const expected_keys = [1, 2, 3, 4];

    const open_rq = createdb(t);
    open_rq.onupgradeneeded = function(e) {
        db = e.target.result;
        const objStore = db.createObjectStore("store", { keyPath: "key",
        autoIncrement: true });

        objStore.put(record);
        objStore.put(record);
        objStore.put(record);
        objStore.put(record);
    };

    open_rq.onsuccess = function(e) {
        const actual_keys = [];
        const rq = db.transaction("store", "readonly")
                   .objectStore("store")
                   .openCursor();

        rq.onsuccess = t.step_func(function(e) {
            const cursor = e.target.result;

            if(cursor) {
                actual_keys.push(cursor.value.key);
                cursor.continue();
            } else {
                assert_array_equals(actual_keys, expected_keys);
                t.done();
            }
        });
    };
}, 'autoIncrement and out-of-line keys');

async_test(t => {
    let db;
    const record = { property: "data" };
    const expected_keys = [1, 2, 3, 4];

    const open_rq = createdb(t);
    open_rq.onupgradeneeded = function(e) {
        db = e.target.result;
        const objStore = db.createObjectStore("store",
        { keyPath: "test.obj.key", autoIncrement: true });

        objStore.put(record);
        objStore.put(record);
        objStore.put(record);
        objStore.put(record);
    };

    open_rq.onsuccess = function(e) {
        const actual_keys = [];
        const rq = db.transaction("store", "readonly")
            .objectStore("store")
            .openCursor();

        rq.onsuccess = t.step_func(function(e) {
            const cursor = e.target.result;

            if (cursor) {
                actual_keys.push(cursor.value.test.obj.key);
                cursor.continue();
            } else {
                assert_array_equals(actual_keys, expected_keys);
                t.done();
            }
        });
    };
}, 'Object store has autoIncrement:true and the key path is an object \
attribute');

async_test(t => {
    const record = { key: 1, property: "data" };

    const open_rq = createdb(t);
    open_rq.onupgradeneeded = function(e) {
        let rq;
        const db = e.target.result;
        const objStore = db.createObjectStore("store", { keyPath: "key" });

        assert_throws_dom("DataError", function() {
            rq = objStore.put(record, 1);
        });

        assert_equals(rq, undefined);
        t.done();
    };
}, 'Attempt to put() a record that does not meet the constraints of an object \
store\'s inline key requirements');

async_test(t => {
    const record = { property: "data" };

    const open_rq = createdb(t);
    open_rq.onupgradeneeded = function(e) {
        let db = e.target.result;

        let rq;
        const objStore = db.createObjectStore("store", { keyPath: "key" });

        assert_throws_dom("DataError", function() {
            rq = objStore.put(record);
        });

        assert_equals(rq, undefined);
        t.done();
    };
}, 'Attempt to call put() without an key parameter when the object store uses \
out-of-line keys');

async_test(t => {
    const record = { key: { value: 1 }, property: "data" };

    const open_rq = createdb(t);
    open_rq.onupgradeneeded = function(e) {
        const db = e.target.result;

        let rq;
        const objStore = db.createObjectStore("store", { keyPath: "key" });

        assert_throws_dom("DataError", function() {
            rq = objStore.put(record);
        });

        assert_equals(rq, undefined);
        t.done();
    };
}, 'Attempt to put() a record where the record\'s key does not meet the \
constraints of a valid key');

async_test(t => {
    const record = { property: "data" };

    const open_rq = createdb(t);
    open_rq.onupgradeneeded = function(e) {
        const db = e.target.result;

        let rq;
        const objStore = db.createObjectStore("store", { keyPath: "key" });

        assert_throws_dom("DataError", function() {
            rq = objStore.put(record);
        });

        assert_equals(rq, undefined);
        t.done();
    };
}, 'Attempt to put() a record where the record\'s in-line key is not defined');

async_test(t => {
    const record = { property: "data" };

    const open_rq = createdb(t);
    open_rq.onupgradeneeded = function(e) {
        const db = e.target.result;

        let rq;
        const objStore = db.createObjectStore("store");

        assert_throws_dom("DataError", function() {
            rq = objStore.put(record, { value: 1 });
        });

        assert_equals(rq, undefined);
        t.done();
    };
}, 'Attempt to put() a record where the out of line key provided does not \
meet the constraints of a valid key');

async_test(t => {
    const record = { key: 1, indexedProperty: { property: "data" } };

    const open_rq = createdb(t);
    open_rq.onupgradeneeded = function(e) {
        const db = e.target.result;

        let rq;
        const objStore = db.createObjectStore("store", { keyPath: "key" });

        objStore.createIndex("index", "indexedProperty");

        rq = objStore.put(record);

        assert_true(rq instanceof IDBRequest);
        rq.onsuccess = function() {
            t.done();
        };
    };
}, 'put() a record where a value being indexed does not meet the constraints \
of a valid key');

async_test(t => {
    let db;
    const open_rq = createdb(t);
    open_rq.onupgradeneeded = function(event) {
        db = event.target.result;
        db.createObjectStore("store", { keyPath: "pKey" });
    };

    open_rq.onsuccess = function(event) {
        const txn = db.transaction("store", "readonly");
        const ostore = txn.objectStore("store");
        t.step(function() {
            assert_throws_dom("ReadOnlyError", function() {
                ostore.put({ pKey: "primaryKey_0" });
            });
        });
        t.done();
    };
}, 'If the transaction this IDBObjectStore belongs to has its mode set to \
readonly, throw ReadOnlyError');

async_test(t => {
    let ostore;
    const open_rq = createdb(t);
    open_rq.onupgradeneeded = function(event) {
        const db = event.target.result;
        ostore = db.createObjectStore("store", { keyPath: "pKey" });
        db.deleteObjectStore("store");
        assert_throws_dom("InvalidStateError", function() {
            ostore.put({ pKey: "primaryKey_0" });
        });
        t.done();
    };
}, 'If the object store has been deleted, the implementation must throw a \
DOMException of type InvalidStateError');
