From a4e4125cca8644703d7cff527a39c1a5a9842eba Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Fri, 21 Jun 2019 19:18:36 +0200 Subject: idb: tests working --- packages/idb-bridge/src/util/FakeEventTarget.ts | 262 +++++++++++---------- packages/idb-bridge/src/util/getIndexKeys.test.ts | 24 ++ packages/idb-bridge/src/util/getIndexKeys.ts | 28 +++ .../idb-bridge/src/util/makeStoreKeyValue.test.ts | 42 ++++ packages/idb-bridge/src/util/makeStoreKeyValue.ts | 24 +- 5 files changed, 247 insertions(+), 133 deletions(-) create mode 100644 packages/idb-bridge/src/util/getIndexKeys.test.ts create mode 100644 packages/idb-bridge/src/util/getIndexKeys.ts create mode 100644 packages/idb-bridge/src/util/makeStoreKeyValue.test.ts (limited to 'packages/idb-bridge/src/util') diff --git a/packages/idb-bridge/src/util/FakeEventTarget.ts b/packages/idb-bridge/src/util/FakeEventTarget.ts index 3c7eaf468..f20432df0 100644 --- a/packages/idb-bridge/src/util/FakeEventTarget.ts +++ b/packages/idb-bridge/src/util/FakeEventTarget.ts @@ -14,164 +14,172 @@ permissions and limitations under the License. */ - import { InvalidStateError } from "./errors"; import FakeEvent from "./FakeEvent"; import { EventCallback, EventType } from "./types"; type EventTypeProp = - | "onabort" - | "onblocked" - | "oncomplete" - | "onerror" - | "onsuccess" - | "onupgradeneeded" - | "onversionchange"; + | "onabort" + | "onblocked" + | "oncomplete" + | "onerror" + | "onsuccess" + | "onupgradeneeded" + | "onversionchange"; interface Listener { - callback: EventCallback; - capture: boolean; - type: EventType; + callback: EventCallback; + capture: boolean; + type: EventType; } const stopped = (event: FakeEvent, listener: Listener) => { - return ( - event.immediatePropagationStopped || - (event.eventPhase === event.CAPTURING_PHASE && - listener.capture === false) || - (event.eventPhase === event.BUBBLING_PHASE && listener.capture === true) - ); + return ( + event.immediatePropagationStopped || + (event.eventPhase === event.CAPTURING_PHASE && + listener.capture === false) || + (event.eventPhase === event.BUBBLING_PHASE && listener.capture === true) + ); }; // http://www.w3.org/TR/dom/#concept-event-listener-invoke const invokeEventListeners = (event: FakeEvent, obj: FakeEventTarget) => { - event.currentTarget = obj; - - // The callback might cause obj.listeners to mutate as we traverse it. - // Take a copy of the array so that nothing sneaks in and we don't lose - // our place. - for (const listener of obj.listeners.slice()) { - if (event.type !== listener.type || stopped(event, listener)) { - continue; - } - - // @ts-ignore - listener.callback.call(event.currentTarget, event); + event.currentTarget = obj; + + // The callback might cause obj.listeners to mutate as we traverse it. + // Take a copy of the array so that nothing sneaks in and we don't lose + // our place. + for (const listener of obj.listeners.slice()) { + if (event.type !== listener.type || stopped(event, listener)) { + continue; } - const typeToProp: { [key in EventType]: EventTypeProp } = { - abort: "onabort", - blocked: "onblocked", - complete: "oncomplete", - error: "onerror", - success: "onsuccess", - upgradeneeded: "onupgradeneeded", - versionchange: "onversionchange", + console.log(`invoking ${event.type} event listener`, listener); + // @ts-ignore + listener.callback.call(event.currentTarget, event); + } + + const typeToProp: { [key in EventType]: EventTypeProp } = { + abort: "onabort", + blocked: "onblocked", + complete: "oncomplete", + error: "onerror", + success: "onsuccess", + upgradeneeded: "onupgradeneeded", + versionchange: "onversionchange", + }; + const prop = typeToProp[event.type]; + if (prop === undefined) { + throw new Error(`Unknown event type: "${event.type}"`); + } + + const callback = event.currentTarget[prop]; + if (callback) { + const listener = { + callback, + capture: false, + type: event.type, }; - const prop = typeToProp[event.type]; - if (prop === undefined) { - throw new Error(`Unknown event type: "${event.type}"`); - } - - const callback = event.currentTarget[prop]; - if (callback) { - const listener = { - callback, - capture: false, - type: event.type, - }; - if (!stopped(event, listener)) { - // @ts-ignore - listener.callback.call(event.currentTarget, event); - } + if (!stopped(event, listener)) { + console.log(`invoking on${event.type} event listener`, listener); + // @ts-ignore + listener.callback.call(event.currentTarget, event); } + } }; abstract class FakeEventTarget { - public readonly listeners: Listener[] = []; - - // These will be overridden in individual subclasses and made not readonly - public readonly onabort: EventCallback | null | undefined; - public readonly onblocked: EventCallback | null | undefined; - public readonly oncomplete: EventCallback | null | undefined; - public readonly onerror: EventCallback | null | undefined; - public readonly onsuccess: EventCallback | null | undefined; - public readonly onupgradeneeded: EventCallback | null | undefined; - public readonly onversionchange: EventCallback | null | undefined; - - public addEventListener( - type: EventType, - callback: EventCallback, - capture = false, - ) { - this.listeners.push({ - callback, - capture, - type, - }); - } - - public removeEventListener( - type: EventType, - callback: EventCallback, - capture = false, - ) { - const i = this.listeners.findIndex(listener => { - return ( - listener.type === type && - listener.callback === callback && - listener.capture === capture - ); - }); - - this.listeners.splice(i, 1); + public readonly listeners: Listener[] = []; + + // These will be overridden in individual subclasses and made not readonly + public readonly onabort: EventCallback | null | undefined; + public readonly onblocked: EventCallback | null | undefined; + public readonly oncomplete: EventCallback | null | undefined; + public readonly onerror: EventCallback | null | undefined; + public readonly onsuccess: EventCallback | null | undefined; + public readonly onupgradeneeded: EventCallback | null | undefined; + public readonly onversionchange: EventCallback | null | undefined; + + static enableTracing: boolean = true; + + public addEventListener( + type: EventType, + callback: EventCallback, + capture = false, + ) { + this.listeners.push({ + callback, + capture, + type, + }); + } + + public removeEventListener( + type: EventType, + callback: EventCallback, + capture = false, + ) { + const i = this.listeners.findIndex(listener => { + return ( + listener.type === type && + listener.callback === callback && + listener.capture === capture + ); + }); + + this.listeners.splice(i, 1); + } + + // http://www.w3.org/TR/dom/#dispatching-events + public dispatchEvent(event: FakeEvent) { + if (event.dispatched || !event.initialized) { + throw new InvalidStateError("The object is in an invalid state."); } + event.isTrusted = false; - // http://www.w3.org/TR/dom/#dispatching-events - public dispatchEvent(event: FakeEvent) { - if (event.dispatched || !event.initialized) { - throw new InvalidStateError("The object is in an invalid state."); - } - event.isTrusted = false; + event.dispatched = true; + event.target = this; + // NOT SURE WHEN THIS SHOULD BE SET event.eventPath = []; - event.dispatched = true; - event.target = this; - // NOT SURE WHEN THIS SHOULD BE SET event.eventPath = []; + event.eventPhase = event.CAPTURING_PHASE; + if (FakeEventTarget.enableTracing) { + console.log( + `dispatching '${event.type}' event along path with ${event.eventPath.length} elements`, + ); + } + for (const obj of event.eventPath) { + if (!event.propagationStopped) { + invokeEventListeners(event, obj); + } + } - event.eventPhase = event.CAPTURING_PHASE; - for (const obj of event.eventPath) { - if (!event.propagationStopped) { - invokeEventListeners(event, obj); - } - } + event.eventPhase = event.AT_TARGET; + if (!event.propagationStopped) { + invokeEventListeners(event, event.target); + } - event.eventPhase = event.AT_TARGET; + if (event.bubbles) { + event.eventPath.reverse(); + event.eventPhase = event.BUBBLING_PHASE; + if (event.eventPath.length === 0 && event.type === "error") { + console.error("Unhandled error event: ", event.target); + } + for (const obj of event.eventPath) { if (!event.propagationStopped) { - invokeEventListeners(event, event.target); - } - - if (event.bubbles) { - event.eventPath.reverse(); - event.eventPhase = event.BUBBLING_PHASE; - if (event.eventPath.length === 0 && event.type === "error") { - console.error("Unhandled error event: ", event.target); - } - for (const obj of event.eventPath) { - if (!event.propagationStopped) { - invokeEventListeners(event, obj); - } - } + invokeEventListeners(event, obj); } + } + } - event.dispatched = false; - event.eventPhase = event.NONE; - event.currentTarget = null; + event.dispatched = false; + event.eventPhase = event.NONE; + event.currentTarget = null; - if (event.canceled) { - return false; - } - return true; + if (event.canceled) { + return false; } + return true; + } } export default FakeEventTarget; diff --git a/packages/idb-bridge/src/util/getIndexKeys.test.ts b/packages/idb-bridge/src/util/getIndexKeys.test.ts new file mode 100644 index 000000000..e1bc9dd00 --- /dev/null +++ b/packages/idb-bridge/src/util/getIndexKeys.test.ts @@ -0,0 +1,24 @@ +import test from "ava"; +import { getIndexKeys } from "./getIndexKeys"; + +test("basics", (t) => { + t.deepEqual(getIndexKeys({foo: 42}, "foo", false), [42]); + t.deepEqual(getIndexKeys({foo: {bar: 42}}, "foo.bar", false), [42]); + t.deepEqual(getIndexKeys({foo: [42, 43]}, "foo.0", false), [42]); + t.deepEqual(getIndexKeys({foo: [42, 43]}, "foo.1", false), [43]); + + t.deepEqual(getIndexKeys([1, 2, 3], "", false), [[1, 2, 3]]); + + t.throws(() => { + getIndexKeys({foo: 42}, "foo.bar", false); + }); + + t.deepEqual(getIndexKeys({foo: 42}, "foo", true), [42]); + t.deepEqual(getIndexKeys({foo: 42, bar: 10}, ["foo", "bar"], true), [42, 10]); + t.deepEqual(getIndexKeys({foo: 42, bar: 10}, ["foo", "bar"], false), [[42, 10]]); + t.deepEqual(getIndexKeys({foo: 42, bar: 10}, ["foo", "bar", "spam"], true), [42, 10]); + + t.throws(() => { + getIndexKeys({foo: 42, bar: 10}, ["foo", "bar", "spam"], false); + }); +}); diff --git a/packages/idb-bridge/src/util/getIndexKeys.ts b/packages/idb-bridge/src/util/getIndexKeys.ts new file mode 100644 index 000000000..416cf9ea2 --- /dev/null +++ b/packages/idb-bridge/src/util/getIndexKeys.ts @@ -0,0 +1,28 @@ +import { Key, Value, KeyPath } from "./types"; +import extractKey from "./extractKey"; +import valueToKey from "./valueToKey"; + +export function getIndexKeys( + value: Value, + keyPath: KeyPath, + multiEntry: boolean, +): Key[] { + if (multiEntry && Array.isArray(keyPath)) { + const keys = []; + for (const subkeyPath of keyPath) { + const key = extractKey(subkeyPath, value); + try { + const k = valueToKey(key); + keys.push(k); + } catch { + // Ignore invalid subkeys + } + } + return keys; + } else { + let key = extractKey(keyPath, value); + return [valueToKey(key)]; + } +} + +export default getIndexKeys; diff --git a/packages/idb-bridge/src/util/makeStoreKeyValue.test.ts b/packages/idb-bridge/src/util/makeStoreKeyValue.test.ts new file mode 100644 index 000000000..7820875c3 --- /dev/null +++ b/packages/idb-bridge/src/util/makeStoreKeyValue.test.ts @@ -0,0 +1,42 @@ +import test from 'ava'; +import { makeStoreKeyValue } from "./makeStoreKeyValue"; + +test("basics", (t) => { + let result; + + result = makeStoreKeyValue({ name: "Florian" }, undefined, 42, true, "id"); + t.is(result.updatedKeyGenerator, 43); + t.is(result.key, 42); + t.is(result.value.name, "Florian"); + t.is(result.value.id, 42); + + result = makeStoreKeyValue({ name: "Florian", id: 10 }, undefined, 5, true, "id"); + t.is(result.updatedKeyGenerator, 11); + t.is(result.key, 10); + t.is(result.value.name, "Florian"); + t.is(result.value.id, 10); + + result = makeStoreKeyValue({ name: "Florian", id: 5 }, undefined, 10, true, "id"); + t.is(result.updatedKeyGenerator, 10); + t.is(result.key, 5); + t.is(result.value.name, "Florian"); + t.is(result.value.id, 5); + + result = makeStoreKeyValue({ name: "Florian", id: "foo" }, undefined, 10, true, "id"); + t.is(result.updatedKeyGenerator, 10); + t.is(result.key, "foo"); + t.is(result.value.name, "Florian"); + t.is(result.value.id, "foo"); + + result = makeStoreKeyValue({ name: "Florian" }, "foo", 10, true, null); + t.is(result.updatedKeyGenerator, 10); + t.is(result.key, "foo"); + t.is(result.value.name, "Florian"); + t.is(result.value.id, undefined); + + result = makeStoreKeyValue({ name: "Florian" }, undefined, 10, true, null); + t.is(result.updatedKeyGenerator, 11); + t.is(result.key, 10); + t.is(result.value.name, "Florian"); + t.is(result.value.id, undefined); +}); diff --git a/packages/idb-bridge/src/util/makeStoreKeyValue.ts b/packages/idb-bridge/src/util/makeStoreKeyValue.ts index 4850cec26..4f45e0d8a 100644 --- a/packages/idb-bridge/src/util/makeStoreKeyValue.ts +++ b/packages/idb-bridge/src/util/makeStoreKeyValue.ts @@ -63,10 +63,14 @@ export function makeStoreKeyValue( updatedKeyGenerator = currentKeyGenerator + 1; } else if (typeof maybeInlineKey === "number") { key = maybeInlineKey; - updatedKeyGenerator = maybeInlineKey; + if (maybeInlineKey >= currentKeyGenerator) { + updatedKeyGenerator = maybeInlineKey + 1; + } else { + updatedKeyGenerator = currentKeyGenerator; + } } else { key = maybeInlineKey; - updatedKeyGenerator = currentKeyGenerator + 1; + updatedKeyGenerator = currentKeyGenerator; } return { key: key, @@ -84,9 +88,17 @@ export function makeStoreKeyValue( }; } } else { - // (no, no, yes) - // (no, no, no) - throw new DataError(); + if (autoIncrement) { + // (no, no, yes) + return { + key: currentKeyGenerator, + value: value, + updatedKeyGenerator: currentKeyGenerator + 1, + } + } else { + // (no, no, no) + throw new DataError(); + } } } -} \ No newline at end of file +} -- cgit v1.2.3