Home Reference Source Test

test/generic/SynchronousTransaction.spec.js

describe('SynchronousTransaction', () => {
    let objectStore;
    /** @type {SynchronousTransaction} */
    let tx;

    beforeEach((done) => {
        // Asynchronous backend.
        objectStore = new ObjectStore(new UnsynchronousBackend(new InMemoryBackend()), null);
        objectStore.createIndex('i');

        (async function () {
            // Add 10 objects.
            for (let i=0; i<10; ++i) {
                await objectStore.put(`key${i}`, { i: i, sub: `value${i}` });
            }

            tx = objectStore.synchronousTransaction();
            await tx.preload(['key0', 'key1', 'test', 'key2']);
        })().then(done, done.fail);
    });

    afterEach((done) => {
        (async function () {
            if (tx.state === Transaction.STATE.OPEN) {
                await tx.abort();
            }
        })().then(done, done.fail);
    });

    it('throws an error if a key is not cached', () => {
        const tx = objectStore.synchronousTransaction();
        expect(() => tx.getSync('key0')).toThrow();
    });

    it('can preload keys', (done) => {
        (async function () {
            const tx = objectStore.synchronousTransaction();
            await tx.preload(['key0', 'key1', 'test']);
            expect(tx.getSync('key0').sub).toBe('value0');
            expect(tx.getSync('key1').sub).toBe('value1');
            expect(tx.getSync('key2', { expectPresence: false })).toBeUndefined();
            expect(tx.getSync('test', { expectPresence: false })).toBeUndefined();

            expect((await tx.get('key0')).sub).toBe('value0');
            expect((await tx.get('key1')).sub).toBe('value1');
            expect((await tx.get('key2')).sub).toBe('value2');
            expect(await tx.get('test')).toBeUndefined();

            expect(tx.getSync('key2').sub).toBe('value2');
        })().then(done, done.fail);
    });

    it('can change values and commit', (done) => {
        (async function () {
            tx.putSync('key0', { i: 1337, sub: 'test' });
            tx.putSync('key5', { i: 1337, sub: 'test2' });
            await tx.put('async', { i: 1337, sub: 'test3' });
            tx.removeSync('key3');
            expect(tx.getSync('key0').sub).toBe('test');
            expect(tx.getSync('key5').sub).toBe('test2');
            expect(tx.getSync('key3', { expectPresence: false })).toBe(undefined);
            expect((await tx.get('key0')).sub).toBe('test');

            expect(await tx.commit()).toBe(true);
            expect((await objectStore.get('key0')).sub).toBe('test');
            expect((await objectStore.get('key5')).sub).toBe('test2');
            expect((await objectStore.get('key3'))).toBe(undefined);
            expect((await objectStore.get('async')).sub).toBe('test3');
            /** @type {IIndex} */
            const index = objectStore.index('i');
            expect(await index.count(KeyRange.only(0))).toBe(0);
            expect(await index.count(KeyRange.only(5))).toBe(0);
            expect(await index.count(KeyRange.only(1337))).toBe(3);
            expect(await index.count(KeyRange.only(3))).toBe(0);
        })().then(done, done.fail);
    });

    it('does apply nested transactions', (done) => {
        (async function () {
            const tx1 = tx.transaction();

            // Can retrieve values from underlying transactions.
            expect((await tx1.get('key0')).sub).toBe('value0');
            expect((await tx1.get('key3')).sub).toBe('value3');

            await tx1.put('test', 'foobar');
            expect(tx.getSync('test', { expectPresence: false })).toBeUndefined();
            expect(await tx1.commit()).toBe(true);
            expect(tx.getSync('test')).toBe('foobar');
        })().then(done, done.fail);
    });

    it('does apply nested synchronous transactions', (done) => {
        (async function () {
            const tx1 = tx.synchronousTransaction();

            // Can retrieve values from underlying transactions.
            expect(tx1.getSync('key0', { expectPresence: false }).sub).toBe('value0');
            expect(tx1.getSync('key3', { expectPresence: false })).toBeUndefined();

            tx1.putSync('test', 'foobar');
            expect(tx.getSync('test', { expectPresence: false })).toBeUndefined();
            expect(await tx1.commit()).toBe(true);
            expect(tx.getSync('test')).toBe('foobar');
        })().then(done, done.fail);
    });

    async function fill(store) {
        // Add 10 objects.
        for (let i=0; i<10; ++i) {
            await store.put(`key${i}`, `value${i}`);
        }
    }

    const backends = [
        TestRunner.nativeRunner('test', 1, jdb => jdb.createObjectStore('testStore', {cenableLruCache: false}), fill, 'without-cache'),
        TestRunner.volatileRunner(() => JungleDB.createVolatileObjectStore(), fill),
        TestRunner.nativeRunner('test', 1, jdb => jdb.createObjectStore('testStore', {enableLruCache: true, lruCacheSize: 1}), fill, 'with-value-cache')
    ];

    backends.forEach(/** @type {TestRunner} */ runner => {

        it(`caches asynchronously retrieved values (${runner.type})`, (done) => {
            (async function () {
                const st = await runner.init();
                const tx1 = st.synchronousTransaction();

                // Put value into cache.
                expect(await st.get('key3')).toBe('value3');

                // Can retrieve values from underlying transactions.
                expect(await tx1.get('key3')).toBe('value3');

                // Overwrite value in cache.
                expect(await st.get('key1')).toBe('value1');

                expect(tx1.getSync('key3', { expectPresence: false })).toBe('value3');

                await runner.destroy();
            })().then(done, done.fail);
        });

    });
});