From e1320876823bf417e534b618662c1d43d26dd237 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Thu, 6 Sep 2018 23:53:29 +0200 Subject: [PATCH] Full PromisifiedStorage + Simplifications --- src/core/client/auth/test/navigation.spec.tsx | 6 +- src/core/client/auth/test/signIn.spec.tsx | 6 +- src/core/client/auth/test/signUp.spec.tsx | 6 +- .../__snapshots__/withPymStorage.spec.ts.snap | 50 +++++++-- .../embed/decorators/withPymStorage.spec.ts | 100 ++++++++++++------ .../client/embed/decorators/withPymStorage.ts | 22 ++-- .../embed/testUtils/InMemoryStorage.spec.ts | 34 ++++++ .../client/embed/testUtils/InMemoryStorage.ts | 49 +++++++++ .../InMemoryStorage.spec.ts.snap | 3 + src/core/client/embed/testUtils/index.ts | 1 + .../__snapshots__/prefixStorage.spec.ts.snap | 3 + src/core/client/embed/utils/index.ts | 2 + .../client/embed/utils/prefixStorage.spec.ts | 79 ++++++++++++++ src/core/client/embed/utils/prefixStorage.ts | 66 ++++++++++++ .../client/embed/utils/startsWith.spec.ts | 7 ++ src/core/client/embed/utils/startsWith.ts | 4 + .../framework/lib/bootstrap/TalkContext.tsx | 10 +- .../framework/lib/bootstrap/createContext.tsx | 14 ++- .../lib/storage/InMemoryStorage.spec.ts | 17 ++- .../framework/lib/storage/InMemoryStorage.ts | 24 ++--- .../framework/lib/storage/LocalStorage.ts | 2 +- .../lib/storage/PromisifiedStorage.spec.ts | 24 ++++- .../lib/storage/PromisifiedStorage.ts | 24 ++++- .../framework/lib/storage/PymStorage.spec.ts | 43 ++++++++ .../framework/lib/storage/PymStorage.ts | 11 +- .../framework/lib/storage/SessionStorage.ts | 2 +- .../InMemoryStorage.spec.ts.snap | 3 + .../__snapshots__/prefixStorage.spec.ts.snap | 3 + .../lib/storage/prefixStorage.spec.ts | 83 +++++++-------- .../framework/lib/storage/prefixStorage.ts | 35 ++++-- .../mutations/SetAuthTokenMutation.spec.ts | 21 ---- .../mutations/SetAuthTokenMutation.ts | 7 +- .../testHelpers/createFakePymStorage.ts | 8 -- .../client/framework/testHelpers/index.ts | 1 - .../PostCommentFormContainer.spec.tsx | 21 ++-- .../containers/PostCommentFormContainer.tsx | 12 +-- .../ReplyCommentFormContainer.spec.tsx | 33 +++--- .../containers/ReplyCommentFormContainer.tsx | 16 +-- .../stream/local/initLocalState.spec.ts | 15 ++- .../client/stream/local/initLocalState.ts | 4 +- src/core/client/stream/test/create.tsx | 9 +- 41 files changed, 642 insertions(+), 238 deletions(-) create mode 100644 src/core/client/embed/testUtils/InMemoryStorage.spec.ts create mode 100644 src/core/client/embed/testUtils/InMemoryStorage.ts create mode 100644 src/core/client/embed/testUtils/__snapshots__/InMemoryStorage.spec.ts.snap create mode 100644 src/core/client/embed/testUtils/index.ts create mode 100644 src/core/client/embed/utils/__snapshots__/prefixStorage.spec.ts.snap create mode 100644 src/core/client/embed/utils/prefixStorage.spec.ts create mode 100644 src/core/client/embed/utils/prefixStorage.ts create mode 100644 src/core/client/embed/utils/startsWith.spec.ts create mode 100644 src/core/client/embed/utils/startsWith.ts create mode 100644 src/core/client/framework/lib/storage/__snapshots__/InMemoryStorage.spec.ts.snap create mode 100644 src/core/client/framework/lib/storage/__snapshots__/prefixStorage.spec.ts.snap delete mode 100644 src/core/client/framework/testHelpers/createFakePymStorage.ts diff --git a/src/core/client/auth/test/navigation.spec.tsx b/src/core/client/auth/test/navigation.spec.tsx index 3d71208cf..858719901 100644 --- a/src/core/client/auth/test/navigation.spec.tsx +++ b/src/core/client/auth/test/navigation.spec.tsx @@ -8,7 +8,7 @@ import AppContainer from "talk-auth/containers/AppContainer"; import { TalkContext, TalkContextProvider } from "talk-framework/lib/bootstrap"; import { PostMessageService } from "talk-framework/lib/postMessage"; import { RestClient } from "talk-framework/lib/rest"; -import { createInMemoryStorage } from "talk-framework/lib/storage"; +import { createPromisifiedStorage } from "talk-framework/lib/storage"; import createEnvironment from "./createEnvironment"; import createFluentBundle from "./createFluentBundle"; @@ -23,8 +23,8 @@ function createTestRenderer(initialView: string): ReactTestRenderer { const context: TalkContext = { relayEnvironment: environment, localeBundles: [createFluentBundle()], - localStorage: createInMemoryStorage(), - sessionStorage: createInMemoryStorage(), + localStorage: createPromisifiedStorage(), + sessionStorage: createPromisifiedStorage(), rest: new RestClient("http://localhost/api"), postMessage: new PostMessageService(), browserInfo: { ios: false }, diff --git a/src/core/client/auth/test/signIn.spec.tsx b/src/core/client/auth/test/signIn.spec.tsx index ca96d90f5..2aaa46e72 100644 --- a/src/core/client/auth/test/signIn.spec.tsx +++ b/src/core/client/auth/test/signIn.spec.tsx @@ -11,7 +11,7 @@ import { animationFrame, timeout } from "talk-common/utils"; import { TalkContext, TalkContextProvider } from "talk-framework/lib/bootstrap"; import { PostMessageService } from "talk-framework/lib/postMessage"; import { RestClient } from "talk-framework/lib/rest"; -import { createInMemoryStorage } from "talk-framework/lib/storage"; +import { createPromisifiedStorage } from "talk-framework/lib/storage"; import createEnvironment from "./createEnvironment"; import createFluentBundle from "./createFluentBundle"; @@ -33,8 +33,8 @@ beforeEach(() => { context = { relayEnvironment: environment, localeBundles: [createFluentBundle()], - localStorage: createInMemoryStorage(), - sessionStorage: createInMemoryStorage(), + localStorage: createPromisifiedStorage(), + sessionStorage: createPromisifiedStorage(), rest: new RestClient("http://localhost/api"), postMessage: new PostMessageService(), browserInfo: { ios: false }, diff --git a/src/core/client/auth/test/signUp.spec.tsx b/src/core/client/auth/test/signUp.spec.tsx index 7a05e432e..20304dd28 100644 --- a/src/core/client/auth/test/signUp.spec.tsx +++ b/src/core/client/auth/test/signUp.spec.tsx @@ -11,7 +11,7 @@ import { animationFrame, timeout } from "talk-common/utils"; import { TalkContext, TalkContextProvider } from "talk-framework/lib/bootstrap"; import { PostMessageService } from "talk-framework/lib/postMessage"; import { RestClient } from "talk-framework/lib/rest"; -import { createInMemoryStorage } from "talk-framework/lib/storage"; +import { createPromisifiedStorage } from "talk-framework/lib/storage"; import createEnvironment from "./createEnvironment"; import createFluentBundle from "./createFluentBundle"; @@ -33,8 +33,8 @@ beforeEach(() => { context = { relayEnvironment: environment, localeBundles: [createFluentBundle()], - localStorage: createInMemoryStorage(), - sessionStorage: createInMemoryStorage(), + localStorage: createPromisifiedStorage(), + sessionStorage: createPromisifiedStorage(), rest: new RestClient("http://localhost/api"), postMessage: new PostMessageService(), browserInfo: { ios: false }, diff --git a/src/core/client/embed/decorators/__snapshots__/withPymStorage.spec.ts.snap b/src/core/client/embed/decorators/__snapshots__/withPymStorage.spec.ts.snap index 6cc6e2817..127601892 100644 --- a/src/core/client/embed/decorators/__snapshots__/withPymStorage.spec.ts.snap +++ b/src/core/client/embed/decorators/__snapshots__/withPymStorage.spec.ts.snap @@ -1,15 +1,51 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`withPymStorage should handle handle errors 1`] = `"[{\\"key\\":\\"pymStorage.localStorage.error\\",\\"value\\":\\"{\\\\\\"id\\\\\\":\\\\\\"0\\\\\\",\\\\\\"error\\\\\\":\\\\\\"error\\\\\\"}\\"}]"`; +exports[`withPymStorage should clear storage 1`] = `"{}"`; + +exports[`withPymStorage should clear storage 2`] = ` +Array [ + Object { + "key": "pymStorage.localStorage.response", + "value": "{\\"id\\":\\"0\\"}", + }, +] +`; + +exports[`withPymStorage should get key of storage 1`] = ` +Array [ + Object { + "key": "pymStorage.localStorage.response", + "value": "{\\"id\\":\\"0\\",\\"result\\":\\"b\\"}", + }, + Object { + "key": "pymStorage.localStorage.response", + "value": "{\\"id\\":\\"0\\",\\"result\\":null}", + }, +] +`; + +exports[`withPymStorage should get length of storage 1`] = ` +Array [ + Object { + "key": "pymStorage.localStorage.response", + "value": "{\\"id\\":\\"0\\",\\"result\\":3}", + }, +] +`; + +exports[`withPymStorage should handle handle errors 1`] = ` +Array [ + Object { + "key": "pymStorage.localStorage.error", + "value": "{\\"id\\":\\"0\\",\\"error\\":\\"error\\"}", + }, +] +`; exports[`withPymStorage should handle unknown method 1`] = `"[{\\"key\\":\\"pymStorage.localStorage.error\\",\\"value\\":\\"{\\\\\\"id\\\\\\":\\\\\\"0\\\\\\",\\\\\\"error\\\\\\":\\\\\\"Unknown method unknown\\\\\\"}\\"}]"`; -exports[`withPymStorage should set, get and remove item 1`] = ` -Object { - "talkPymStorage:key": "test", -} -`; +exports[`withPymStorage should set, get and remove item 1`] = `"{\\"talk:key\\":\\"test\\"}"`; -exports[`withPymStorage should set, get and remove item 2`] = `Object {}`; +exports[`withPymStorage should set, get and remove item 2`] = `"{}"`; exports[`withPymStorage should set, get and remove item 3`] = `"[{\\"key\\":\\"pymStorage.localStorage.response\\",\\"value\\":\\"{\\\\\\"id\\\\\\":\\\\\\"0\\\\\\"}\\"},{\\"key\\":\\"pymStorage.localStorage.response\\",\\"value\\":\\"{\\\\\\"id\\\\\\":\\\\\\"1\\\\\\",\\\\\\"result\\\\\\":\\\\\\"test\\\\\\"}\\"},{\\"key\\":\\"pymStorage.localStorage.response\\",\\"value\\":\\"{\\\\\\"id\\\\\\":\\\\\\"2\\\\\\"}\\"}]"`; diff --git a/src/core/client/embed/decorators/withPymStorage.spec.ts b/src/core/client/embed/decorators/withPymStorage.spec.ts index 60ab2ed0f..006c9bd39 100644 --- a/src/core/client/embed/decorators/withPymStorage.spec.ts +++ b/src/core/client/embed/decorators/withPymStorage.spec.ts @@ -1,23 +1,8 @@ import sinon from "sinon"; +import { createInMemoryStorage } from "../testUtils"; import withPymStorage from "./withPymStorage"; -// tslint:disable:max-classes-per-file - -class FakeStorage { - public store: Record = {}; - - public setItem(key: string, value: string) { - this.store[key] = value; - } - public removeItem(key: string) { - delete this.store[key]; - } - public getItem(key: string) { - return this.store[key]; - } -} - class PymStub { public listeners: Record void)> = {}; public messages: Array<{ key: string; value: string }> = []; @@ -38,10 +23,8 @@ class PymStub { describe("withPymStorage", () => { it("should set, get and remove item", () => { const pym = new PymStub("localStorage"); - const storage = new FakeStorage(); - withPymStorage(storage as any, "localStorage", "talkPymStorage:")( - pym as any - ); + const storage = createInMemoryStorage(); + withPymStorage(storage, "localStorage", "talk:")(pym as any); pym.listeners["pymStorage.localStorage.request"]( JSON.stringify({ id: "0", @@ -49,7 +32,7 @@ describe("withPymStorage", () => { parameters: { key: "key", value: "test" }, }) ); - expect(storage.store).toMatchSnapshot(); + expect(storage.toString()).toMatchSnapshot(); pym.listeners["pymStorage.localStorage.request"]( JSON.stringify({ id: "1", @@ -64,15 +47,72 @@ describe("withPymStorage", () => { parameters: { key: "key" }, }) ); - expect(storage.store).toMatchSnapshot(); + expect(storage.toString()).toMatchSnapshot(); expect(JSON.stringify(pym.messages)).toMatchSnapshot(); }); + it("should get key of storage", () => { + const pym = new PymStub("localStorage"); + const storage = createInMemoryStorage({ + a: "1", + b: "2", + c: "3", + }); + withPymStorage(storage, "localStorage", "")(pym as any); + pym.listeners["pymStorage.localStorage.request"]( + JSON.stringify({ + id: "0", + method: "key", + parameters: { n: 1 }, + }) + ); + pym.listeners["pymStorage.localStorage.request"]( + JSON.stringify({ + id: "0", + method: "key", + parameters: { n: 3 }, + }) + ); + expect(pym.messages).toMatchSnapshot(); + }); + it("should get length of storage", () => { + const pym = new PymStub("localStorage"); + const storage = createInMemoryStorage({ + a: "1", + b: "2", + c: "3", + }); + withPymStorage(storage, "localStorage", "")(pym as any); + pym.listeners["pymStorage.localStorage.request"]( + JSON.stringify({ + id: "0", + method: "length", + parameters: {}, + }) + ); + expect(pym.messages).toMatchSnapshot(); + }); + it("should clear storage", () => { + const pym = new PymStub("localStorage"); + const storage = createInMemoryStorage({ + a: "1", + b: "2", + c: "3", + }); + withPymStorage(storage, "localStorage", "")(pym as any); + pym.listeners["pymStorage.localStorage.request"]( + JSON.stringify({ + id: "0", + method: "clear", + parameters: {}, + }) + ); + expect(storage.toString()).toMatchSnapshot(); + expect(pym.messages).toMatchSnapshot(); + }); it("should handle unknown method", () => { const pym = new PymStub("localStorage"); - const storage = new FakeStorage(); - withPymStorage(storage as any, "localStorage", "talkPymStorage:")( - pym as any - ); + const storage = createInMemoryStorage(); + withPymStorage(storage, "localStorage", "talk:")(pym as any); pym.listeners["pymStorage.localStorage.request"]( JSON.stringify({ id: "0", @@ -84,14 +124,12 @@ describe("withPymStorage", () => { }); it("should handle handle errors", () => { const pym = new PymStub("localStorage"); - const storage = new FakeStorage(); + const storage = createInMemoryStorage(); sinon .mock(storage) .expects("getItem") .throws("error"); - withPymStorage(storage as any, "localStorage", "talkPymStorage:")( - pym as any - ); + withPymStorage(storage, "localStorage", "talk:")(pym as any); pym.listeners["pymStorage.localStorage.request"]( JSON.stringify({ id: "0", @@ -99,6 +137,6 @@ describe("withPymStorage", () => { parameters: {}, }) ); - expect(JSON.stringify(pym.messages)).toMatchSnapshot(); + expect(pym.messages).toMatchSnapshot(); }); }); diff --git a/src/core/client/embed/decorators/withPymStorage.ts b/src/core/client/embed/decorators/withPymStorage.ts index 2dcaff1c0..d104dc53e 100644 --- a/src/core/client/embed/decorators/withPymStorage.ts +++ b/src/core/client/embed/decorators/withPymStorage.ts @@ -1,14 +1,15 @@ +import { prefixStorage } from "../utils"; import { Decorator } from "./types"; const withPymStorage = ( storage: Storage, type: "localStorage" | "sessionStorage", - prefix = "talkPymStorage:" + prefix = "talk:" ): Decorator => pym => { pym.onMessage(`pymStorage.${type}.request`, (msg: any) => { const { id, method, parameters } = JSON.parse(msg); - const { key, value } = parameters; - const prefixedKey = `${prefix}${key}`; + const { n, key, value } = parameters; + const prefixedStorage = prefixStorage(storage, prefix); // Variable for the method return value. let result; @@ -25,13 +26,22 @@ const withPymStorage = ( try { switch (method) { case "setItem": - result = storage.setItem(prefixedKey, value); + result = prefixedStorage.setItem(key, value); break; case "getItem": - result = storage.getItem(prefixedKey); + result = prefixedStorage.getItem(key); break; case "removeItem": - result = storage.removeItem(prefixedKey); + result = prefixedStorage.removeItem(key); + break; + case "key": + result = prefixedStorage.key(n); + break; + case "length": + result = prefixedStorage.length; + break; + case "clear": + result = prefixedStorage.clear(); break; default: sendError(`Unknown method ${method}`); diff --git a/src/core/client/embed/testUtils/InMemoryStorage.spec.ts b/src/core/client/embed/testUtils/InMemoryStorage.spec.ts new file mode 100644 index 000000000..c3c7f60f1 --- /dev/null +++ b/src/core/client/embed/testUtils/InMemoryStorage.spec.ts @@ -0,0 +1,34 @@ +import createInMemoryStorage from "./InMemoryStorage"; + +it("should set and unset values", () => { + const storage = createInMemoryStorage(); + storage.setItem("test", "value"); + expect(storage.getItem("test")).toBe("value"); + storage.removeItem("test"); + expect(storage.getItem("test")).toBeNull(); +}); + +it("should return length", () => { + const storage = createInMemoryStorage(); + storage.setItem("a", "value"); + storage.setItem("b", "value"); + storage.setItem("c", "value"); + expect(storage.length).toBe(3); +}); + +it("should nth key", () => { + const storage = createInMemoryStorage(); + storage.setItem("a", "0"); + storage.setItem("b", "1"); + storage.setItem("c", "2"); + expect(storage.key(2)).toBe("c"); +}); + +it("accepts predefined data", () => { + const storage = createInMemoryStorage({ + a: "0", + b: "1", + c: "2", + }); + expect(storage.toString()).toMatchSnapshot(); +}); diff --git a/src/core/client/embed/testUtils/InMemoryStorage.ts b/src/core/client/embed/testUtils/InMemoryStorage.ts new file mode 100644 index 000000000..803323e2d --- /dev/null +++ b/src/core/client/embed/testUtils/InMemoryStorage.ts @@ -0,0 +1,49 @@ +/** + * InMemoryStorage is a dumb implementation of the Storage interface that will + * not persist the data at all. It implements the Storage interface found: + * + * https://developer.mozilla.org/en-US/docs/Web/API/Storage + */ +class InMemoryStorage implements Storage { + private data: Record; + + constructor(data: Record = {}) { + this.data = data; + } + + get length() { + return Object.keys(this.data).length; + } + + public clear() { + this.data = {}; + } + + public key(n: number) { + if (this.length <= n) { + return null; + } + + return Object.keys(this.data)[n]; + } + + public getItem(key: string) { + return this.data[key] || null; + } + + public setItem(key: string, value: string) { + this.data[key] = value; + } + + public removeItem(key: string) { + delete this.data[key]; + } + + public toString() { + return JSON.stringify(this.data); + } +} + +export default function createInMemoryStorage(data?: Record) { + return new InMemoryStorage(data); +} diff --git a/src/core/client/embed/testUtils/__snapshots__/InMemoryStorage.spec.ts.snap b/src/core/client/embed/testUtils/__snapshots__/InMemoryStorage.spec.ts.snap new file mode 100644 index 000000000..109b66739 --- /dev/null +++ b/src/core/client/embed/testUtils/__snapshots__/InMemoryStorage.spec.ts.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`accepts predefined data 1`] = `"{\\"a\\":\\"0\\",\\"b\\":\\"1\\",\\"c\\":\\"2\\"}"`; diff --git a/src/core/client/embed/testUtils/index.ts b/src/core/client/embed/testUtils/index.ts new file mode 100644 index 000000000..72296ca86 --- /dev/null +++ b/src/core/client/embed/testUtils/index.ts @@ -0,0 +1 @@ +export { default as createInMemoryStorage } from "./InMemoryStorage"; diff --git a/src/core/client/embed/utils/__snapshots__/prefixStorage.spec.ts.snap b/src/core/client/embed/utils/__snapshots__/prefixStorage.spec.ts.snap new file mode 100644 index 000000000..67b9effeb --- /dev/null +++ b/src/core/client/embed/utils/__snapshots__/prefixStorage.spec.ts.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should call clear 1`] = `"{\\"a\\":\\"0\\",\\"b\\":\\"1\\",\\"d\\":\\"3\\"}"`; diff --git a/src/core/client/embed/utils/index.ts b/src/core/client/embed/utils/index.ts index 0cf1aaa7f..9a373ce0b 100644 --- a/src/core/client/embed/utils/index.ts +++ b/src/core/client/embed/utils/index.ts @@ -1,2 +1,4 @@ export { default as buildURL } from "./buildURL"; export { default as ensureEndSlash } from "./ensureEndSlash"; +export { default as startsWith } from "./startsWith"; +export { default as prefixStorage } from "./prefixStorage"; diff --git a/src/core/client/embed/utils/prefixStorage.spec.ts b/src/core/client/embed/utils/prefixStorage.spec.ts new file mode 100644 index 000000000..ce9e7fb65 --- /dev/null +++ b/src/core/client/embed/utils/prefixStorage.spec.ts @@ -0,0 +1,79 @@ +import sinon from "sinon"; +import { createInMemoryStorage } from "../testUtils"; +import prefixStorage from "./prefixStorage"; + +it("should get nth key", () => { + const storage = createInMemoryStorage({ + a: "0", + b: "1", + "talk:c": "2", + d: "3", + "talk:e": "4", + }); + + const prefixed = prefixStorage(storage, "talk:"); + expect(prefixed.key(0)).toBe("talk:c"); + expect(prefixed.key(1)).toBe("talk:e"); + expect(prefixed.key(2)).toBeNull(); +}); + +it("should call clear", () => { + const storage = createInMemoryStorage({ + a: "0", + b: "1", + "talk:c": "2", + d: "3", + "talk:e": "4", + }); + + const prefixed = prefixStorage(storage, "talk:"); + prefixed.clear(); + expect(storage.toString()).toMatchSnapshot(); +}); + +it("should call length", () => { + const storage = createInMemoryStorage({ + a: "0", + b: "1", + "talk:c": "2", + d: "3", + "talk:e": "4", + }); + + const prefixed = prefixStorage(storage, "talk:"); + expect(prefixed.length).toBe(2); +}); + +it("should prefix setItem", () => { + const storage = { + setItem: sinon.mock().withArgs("talk:key", "value"), + }; + + const prefixed = prefixStorage(storage as any, "talk:"); + prefixed.setItem("key", "value"); + storage.setItem.verify(); +}); + +it("should prefix removeItem", () => { + const storage = { + removeItem: sinon.mock().withArgs("talk:key"), + }; + + const prefixed = prefixStorage(storage as any, "talk:"); + prefixed.removeItem("key"); + storage.removeItem.verify(); +}); + +it("should prefix getItem", () => { + const ret = "value"; + const storage = { + getItem: sinon + .mock() + .withArgs("talk:key") + .returns(ret), + }; + + const prefixed = prefixStorage(storage as any, "talk:"); + expect(prefixed.getItem("key")).toBe(ret); + (storage.getItem as any).verify(); +}); diff --git a/src/core/client/embed/utils/prefixStorage.ts b/src/core/client/embed/utils/prefixStorage.ts new file mode 100644 index 000000000..0972a6804 --- /dev/null +++ b/src/core/client/embed/utils/prefixStorage.ts @@ -0,0 +1,66 @@ +import startsWith from "./startsWith"; + +/** + * PrefixedStorage decorates a Storage and prefixes keys in + * getItem, setItem and removeItem with given prefix. + */ +class PrefixedStorage implements Storage { + private storage: Storage; + private prefix: string; + + constructor(storage: Storage, prefix: string) { + this.storage = storage; + this.prefix = prefix; + } + + get length() { + let count = 0; + for (let i = 0; i < this.storage.length; i++) { + if (startsWith(this.storage.key(i)!, this.prefix)) { + count++; + } + } + return count; + } + + public clear() { + const toBeDeleted = []; + for (let i = 0; i < this.storage.length; i++) { + const key = this.storage.key(i)!; + if (startsWith(key, this.prefix)) { + toBeDeleted.push(key); + } + } + toBeDeleted.forEach(key => this.storage.removeItem(key)); + } + + public key(n: number) { + let count = 0; + for (let i = 0; i < this.storage.length; i++) { + const key = this.storage.key(i)!; + if (startsWith(key, this.prefix)) { + if (count === n) { + return key; + } + count++; + } + } + return null; + } + + public getItem(key: string) { + return this.storage.getItem(`${this.prefix}${key}`); + } + + public setItem(key: string, value: string) { + return this.storage.setItem(`${this.prefix}${key}`, value); + } + + public removeItem(key: string) { + return this.storage.removeItem(`${this.prefix}${key}`); + } +} + +export default function prefixStorage(storage: Storage, prefix: string) { + return new PrefixedStorage(storage, prefix); +} diff --git a/src/core/client/embed/utils/startsWith.spec.ts b/src/core/client/embed/utils/startsWith.spec.ts new file mode 100644 index 000000000..7e4c5d16e --- /dev/null +++ b/src/core/client/embed/utils/startsWith.spec.ts @@ -0,0 +1,7 @@ +import startsWith from "./startsWith"; + +it("should work correctly", () => { + const str1 = "Saturday night plans"; + expect(startsWith(str1, "Sat")).toBe(true); + expect(startsWith(str1, "Sat", 3)).toBe(false); +}); diff --git a/src/core/client/embed/utils/startsWith.ts b/src/core/client/embed/utils/startsWith.ts new file mode 100644 index 000000000..fb9efe717 --- /dev/null +++ b/src/core/client/embed/utils/startsWith.ts @@ -0,0 +1,4 @@ +/** A substitute for string.startsWith */ +export default function startsWith(str: string, search: string, pos?: number) { + return str.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search; +} diff --git a/src/core/client/framework/lib/bootstrap/TalkContext.tsx b/src/core/client/framework/lib/bootstrap/TalkContext.tsx index 7edfcfc03..404910f16 100644 --- a/src/core/client/framework/lib/bootstrap/TalkContext.tsx +++ b/src/core/client/framework/lib/bootstrap/TalkContext.tsx @@ -24,16 +24,10 @@ export interface TalkContext { timeagoFormatter?: Formatter; /** Local Storage */ - localStorage: Storage; + localStorage: PromisifiedStorage; /** Session storage */ - sessionStorage: Storage; - - /** Session Storage over pym */ - pymLocalStorage?: PromisifiedStorage; - - /** Session storage over pym */ - pymSessionStorage?: PromisifiedStorage; + sessionStorage: PromisifiedStorage; /** media query values for testing purposes */ mediaQueryValues?: MediaQueryMatchers; diff --git a/src/core/client/framework/lib/bootstrap/createContext.tsx b/src/core/client/framework/lib/bootstrap/createContext.tsx index 14967ddcc..696ab8262 100644 --- a/src/core/client/framework/lib/bootstrap/createContext.tsx +++ b/src/core/client/framework/lib/bootstrap/createContext.tsx @@ -128,14 +128,12 @@ export default async function createContext({ registerClickFarAway, rest: new RestClient("/api", tokenGetter), postMessage: new PostMessageService(), - localStorage: createLocalStorage(), - sessionStorage: createSessionStorage(), - pymLocalStorage: - (pym && (inIframe && createPymStorage(pym, "localStorage"))) || - createPromisifiedStorage(createLocalStorage("talkPym")), - pymSessionStorage: - (pym && (inIframe && createPymStorage(pym, "sessionStorage"))) || - createPromisifiedStorage(createSessionStorage("talkPym")), + localStorage: + (pym && inIframe && createPymStorage(pym, "localStorage")) || + createPromisifiedStorage(createLocalStorage()), + sessionStorage: + (pym && inIframe && createPymStorage(pym, "sessionStorage")) || + createPromisifiedStorage(createSessionStorage()), browserInfo: getBrowserInfo(), }; diff --git a/src/core/client/framework/lib/storage/InMemoryStorage.spec.ts b/src/core/client/framework/lib/storage/InMemoryStorage.spec.ts index 78fd8a44c..c3c7f60f1 100644 --- a/src/core/client/framework/lib/storage/InMemoryStorage.spec.ts +++ b/src/core/client/framework/lib/storage/InMemoryStorage.spec.ts @@ -16,10 +16,19 @@ it("should return length", () => { expect(storage.length).toBe(3); }); -it("should nth value", () => { +it("should nth key", () => { const storage = createInMemoryStorage(); - storage.setItem("a", "a"); - storage.setItem("b", "b"); - storage.setItem("c", "c"); + storage.setItem("a", "0"); + storage.setItem("b", "1"); + storage.setItem("c", "2"); expect(storage.key(2)).toBe("c"); }); + +it("accepts predefined data", () => { + const storage = createInMemoryStorage({ + a: "0", + b: "1", + c: "2", + }); + expect(storage.toString()).toMatchSnapshot(); +}); diff --git a/src/core/client/framework/lib/storage/InMemoryStorage.ts b/src/core/client/framework/lib/storage/InMemoryStorage.ts index c6e0e876a..803323e2d 100644 --- a/src/core/client/framework/lib/storage/InMemoryStorage.ts +++ b/src/core/client/framework/lib/storage/InMemoryStorage.ts @@ -5,18 +5,18 @@ * https://developer.mozilla.org/en-US/docs/Web/API/Storage */ class InMemoryStorage implements Storage { - private storage: Record; + private data: Record; - constructor() { - this.storage = {}; + constructor(data: Record = {}) { + this.data = data; } get length() { - return Object.keys(this.storage).length; + return Object.keys(this.data).length; } public clear() { - this.storage = {}; + this.data = {}; } public key(n: number) { @@ -24,26 +24,26 @@ class InMemoryStorage implements Storage { return null; } - return this.storage[Object.keys(this.storage)[n]]; + return Object.keys(this.data)[n]; } public getItem(key: string) { - return this.storage[key] || null; + return this.data[key] || null; } public setItem(key: string, value: string) { - this.storage[key] = value; + this.data[key] = value; } public removeItem(key: string) { - delete this.storage[key]; + delete this.data[key]; } public toString() { - return JSON.stringify(this.storage); + return JSON.stringify(this.data); } } -export default function createInMemoryStorage() { - return new InMemoryStorage(); +export default function createInMemoryStorage(data?: Record) { + return new InMemoryStorage(data); } diff --git a/src/core/client/framework/lib/storage/LocalStorage.ts b/src/core/client/framework/lib/storage/LocalStorage.ts index 639acfc3f..91d8db2ad 100644 --- a/src/core/client/framework/lib/storage/LocalStorage.ts +++ b/src/core/client/framework/lib/storage/LocalStorage.ts @@ -1,5 +1,5 @@ import prefixStorage from "./prefixStorage"; -export default function createLocalStorage(prefix = "talk"): Storage { +export default function createLocalStorage(prefix = "talk:"): Storage { return prefixStorage(window.localStorage, prefix); } diff --git a/src/core/client/framework/lib/storage/PromisifiedStorage.spec.ts b/src/core/client/framework/lib/storage/PromisifiedStorage.spec.ts index eaba3f8c0..027e4ebf0 100644 --- a/src/core/client/framework/lib/storage/PromisifiedStorage.spec.ts +++ b/src/core/client/framework/lib/storage/PromisifiedStorage.spec.ts @@ -1,10 +1,26 @@ import createInMemoryStorage from "./InMemoryStorage"; import createPromisifiedStorage from "./PromisifiedStorage"; -it("should set and unset values", () => { +it("should set and unset values", async () => { const storage = createPromisifiedStorage(createInMemoryStorage()); - expect(storage.setItem("test", "value")).resolves.toBeUndefined(); - expect(storage.getItem("test")).resolves.toBe("value"); + await expect(storage.setItem("test", "value")).resolves.toBeUndefined(); + await expect(storage.getItem("test")).resolves.toBe("value"); storage.removeItem("test"); - expect(storage.getItem("test")).resolves.toBeUndefined(); + await expect(storage.getItem("test")).resolves.toBeNull(); +}); + +it("should return length", async () => { + const storage = createPromisifiedStorage(createInMemoryStorage()); + storage.setItem("a", "value"); + storage.setItem("b", "value"); + storage.setItem("c", "value"); + await expect(storage.length).resolves.toBe(3); +}); + +it("should nth value", async () => { + const storage = createPromisifiedStorage(createInMemoryStorage()); + storage.setItem("a", "a"); + storage.setItem("b", "b"); + storage.setItem("c", "c"); + await expect(storage.key(2)).resolves.toBe("c"); }); diff --git a/src/core/client/framework/lib/storage/PromisifiedStorage.ts b/src/core/client/framework/lib/storage/PromisifiedStorage.ts index b6e377214..8bdc9a7bc 100644 --- a/src/core/client/framework/lib/storage/PromisifiedStorage.ts +++ b/src/core/client/framework/lib/storage/PromisifiedStorage.ts @@ -1,4 +1,12 @@ +import createInMemoryStorage from "./InMemoryStorage"; + export interface PromisifiedStorage { + length: Promise; + + clear(): Promise; + + key(n: number): Promise; + /** * value = storage[key] */ @@ -23,6 +31,18 @@ class BackedPromisifedStorage implements PromisifiedStorage { this.storage = storage; } + get length() { + return Promise.resolve(this.storage.length); + } + + public clear() { + return Promise.resolve(this.storage.clear()); + } + + public key(n: number) { + return Promise.resolve(this.storage.key(n)); + } + public getItem(key: string) { return Promise.resolve(this.storage.getItem(key)); } @@ -36,6 +56,8 @@ class BackedPromisifedStorage implements PromisifiedStorage { } } -export default function createPromisifiedStorage(storage: Storage) { +export default function createPromisifiedStorage( + storage: Storage = createInMemoryStorage() +) { return new BackedPromisifedStorage(storage); } diff --git a/src/core/client/framework/lib/storage/PymStorage.spec.ts b/src/core/client/framework/lib/storage/PymStorage.spec.ts index 7a9c9a805..f4216bd2c 100644 --- a/src/core/client/framework/lib/storage/PymStorage.spec.ts +++ b/src/core/client/framework/lib/storage/PymStorage.spec.ts @@ -59,6 +59,49 @@ describe("PymStorage", () => { expect(promise).resolves.toBe("value"); }); + it("should get length", () => { + const pym = new PymStub("localStorage"); + const storage = createPymStorage(pym as any, "localStorage"); + const promise = storage.length; + const { key, value } = pym.messages.pop()!; + expect(key).toBe(`pymStorage.localStorage.request`); + const { id, method, parameters } = JSON.parse(value); + expect(method).toBe("length"); + expect(parameters).toEqual({}); + pym.listeners["pymStorage.localStorage.response"]( + JSON.stringify({ id, result: 3 }) + ); + expect(promise).resolves.toBe(3); + }); + + it("should get key", () => { + const pym = new PymStub("localStorage"); + const storage = createPymStorage(pym as any, "localStorage"); + const promise = storage.key(2); + const { key, value } = pym.messages.pop()!; + expect(key).toBe(`pymStorage.localStorage.request`); + const { id, method, parameters } = JSON.parse(value); + expect(method).toBe("key"); + expect(parameters).toEqual({ n: 2 }); + pym.listeners["pymStorage.localStorage.response"]( + JSON.stringify({ id, result: "myKey" }) + ); + expect(promise).resolves.toBe("myKey"); + }); + + it("should clear", () => { + const pym = new PymStub("localStorage"); + const storage = createPymStorage(pym as any, "localStorage"); + const promise = storage.clear(); + const { key, value } = pym.messages.pop()!; + expect(key).toBe(`pymStorage.localStorage.request`); + const { id, method, parameters } = JSON.parse(value); + expect(method).toBe("clear"); + expect(parameters).toEqual({}); + pym.listeners["pymStorage.localStorage.response"](JSON.stringify({ id })); + expect(promise).resolves.toBeUndefined(); + }); + describe("on error", () => { it("should reject set item", () => { const pym = new PymStub("localStorage"); diff --git a/src/core/client/framework/lib/storage/PymStorage.ts b/src/core/client/framework/lib/storage/PymStorage.ts index ea650b346..6a93d5035 100644 --- a/src/core/client/framework/lib/storage/PymStorage.ts +++ b/src/core/client/framework/lib/storage/PymStorage.ts @@ -20,7 +20,7 @@ class PymStorage implements PromisifiedStorage { /** Requests method with parameters over pym. */ private call( method: string, - parameters: { key: string; value?: string } + parameters: Record = {} ): Promise { const id = uuid(); return new Promise((resolve, reject) => { @@ -55,6 +55,15 @@ class PymStorage implements PromisifiedStorage { this.listen(); } + get length() { + return this.call("length"); + } + public key(n: number) { + return this.call("key", { n }); + } + public clear() { + return this.call("clear"); + } public setItem(key: string, value: string) { return this.call("setItem", { key, value }); } diff --git a/src/core/client/framework/lib/storage/SessionStorage.ts b/src/core/client/framework/lib/storage/SessionStorage.ts index 3675ccf5c..092c03edf 100644 --- a/src/core/client/framework/lib/storage/SessionStorage.ts +++ b/src/core/client/framework/lib/storage/SessionStorage.ts @@ -1,5 +1,5 @@ import prefixStorage from "./prefixStorage"; -export default function createSessionStorage(prefix = "talk"): Storage { +export default function createSessionStorage(prefix = "talk:"): Storage { return prefixStorage(window.sessionStorage, prefix); } diff --git a/src/core/client/framework/lib/storage/__snapshots__/InMemoryStorage.spec.ts.snap b/src/core/client/framework/lib/storage/__snapshots__/InMemoryStorage.spec.ts.snap new file mode 100644 index 000000000..109b66739 --- /dev/null +++ b/src/core/client/framework/lib/storage/__snapshots__/InMemoryStorage.spec.ts.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`accepts predefined data 1`] = `"{\\"a\\":\\"0\\",\\"b\\":\\"1\\",\\"c\\":\\"2\\"}"`; diff --git a/src/core/client/framework/lib/storage/__snapshots__/prefixStorage.spec.ts.snap b/src/core/client/framework/lib/storage/__snapshots__/prefixStorage.spec.ts.snap new file mode 100644 index 000000000..67b9effeb --- /dev/null +++ b/src/core/client/framework/lib/storage/__snapshots__/prefixStorage.spec.ts.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should call clear 1`] = `"{\\"a\\":\\"0\\",\\"b\\":\\"1\\",\\"d\\":\\"3\\"}"`; diff --git a/src/core/client/framework/lib/storage/prefixStorage.spec.ts b/src/core/client/framework/lib/storage/prefixStorage.spec.ts index 6e8e8088d..1c483ef33 100644 --- a/src/core/client/framework/lib/storage/prefixStorage.spec.ts +++ b/src/core/client/framework/lib/storage/prefixStorage.spec.ts @@ -1,54 +1,47 @@ import sinon from "sinon"; +import createInMemoryStorage from "./InMemoryStorage"; import prefixStorage from "./prefixStorage"; -it("should call clear", () => { - const storage = { - clear: sinon.mock().once(), - }; +it("should get nth key", () => { + const storage = createInMemoryStorage({ + a: "0", + b: "1", + "talk:c": "2", + d: "3", + "talk:e": "4", + }); - const prefixed = prefixStorage(storage as any, "talk"); + const prefixed = prefixStorage(storage, "talk:"); + expect(prefixed.key(0)).toBe("talk:c"); + expect(prefixed.key(1)).toBe("talk:e"); + expect(prefixed.key(2)).toBeNull(); +}); + +it("should call clear", () => { + const storage = createInMemoryStorage({ + a: "0", + b: "1", + "talk:c": "2", + d: "3", + "talk:e": "4", + }); + + const prefixed = prefixStorage(storage, "talk:"); prefixed.clear(); - storage.clear.verify(); + expect(storage.toString()).toMatchSnapshot(); }); it("should call length", () => { - const ret = 10; - const storage = { - get length() { - return ret; - }, - }; + const storage = createInMemoryStorage({ + a: "0", + b: "1", + "talk:c": "2", + d: "3", + "talk:e": "4", + }); - const prefixed = prefixStorage(storage as any, "talk"); - expect(prefixed.length).toBe(ret); -}); - -it("should call key", () => { - const ret = "value"; - const storage = { - key: sinon - .mock() - .withArgs(3) - .returns(ret), - }; - - const prefixed = prefixStorage(storage as any, "talk"); - expect(prefixed.key(3)).toBe(ret); - (storage.key as any).verify(); -}); - -it("should call key", () => { - const ret = "value"; - const storage = { - key: sinon - .mock() - .withArgs(3) - .returns(ret), - }; - - const prefixed = prefixStorage(storage as any, "talk"); - expect(prefixed.key(3)).toBe(ret); - (storage.key as any).verify(); + const prefixed = prefixStorage(storage, "talk:"); + expect(prefixed.length).toBe(2); }); it("should prefix setItem", () => { @@ -56,7 +49,7 @@ it("should prefix setItem", () => { setItem: sinon.mock().withArgs("talk:key", "value"), }; - const prefixed = prefixStorage(storage as any, "talk"); + const prefixed = prefixStorage(storage as any, "talk:"); prefixed.setItem("key", "value"); storage.setItem.verify(); }); @@ -66,7 +59,7 @@ it("should prefix removeItem", () => { removeItem: sinon.mock().withArgs("talk:key"), }; - const prefixed = prefixStorage(storage as any, "talk"); + const prefixed = prefixStorage(storage as any, "talk:"); prefixed.removeItem("key"); storage.removeItem.verify(); }); @@ -80,7 +73,7 @@ it("should prefix getItem", () => { .returns(ret), }; - const prefixed = prefixStorage(storage as any, "talk"); + const prefixed = prefixStorage(storage as any, "talk:"); expect(prefixed.getItem("key")).toBe(ret); (storage.getItem as any).verify(); }); diff --git a/src/core/client/framework/lib/storage/prefixStorage.ts b/src/core/client/framework/lib/storage/prefixStorage.ts index c44f7c364..9e360ecec 100644 --- a/src/core/client/framework/lib/storage/prefixStorage.ts +++ b/src/core/client/framework/lib/storage/prefixStorage.ts @@ -12,27 +12,50 @@ class PrefixedStorage implements Storage { } get length() { - return this.storage.length; + let count = 0; + for (let i = 0; i < this.storage.length; i++) { + if (this.storage.key(i)!.startsWith(this.prefix)) { + count++; + } + } + return count; } public clear() { - this.storage.clear(); + const toBeDeleted = []; + for (let i = 0; i < this.storage.length; i++) { + const key = this.storage.key(i)!; + if (key.startsWith(this.prefix)) { + toBeDeleted.push(key); + } + } + toBeDeleted.forEach(key => this.storage.removeItem(key)); } public key(n: number) { - return this.storage.key(n); + let count = 0; + for (let i = 0; i < this.storage.length; i++) { + const key = this.storage.key(i)!; + if (key.startsWith(this.prefix)) { + if (count === n) { + return key; + } + count++; + } + } + return null; } public getItem(key: string) { - return this.storage.getItem(`${this.prefix}:${key}`); + return this.storage.getItem(`${this.prefix}${key}`); } public setItem(key: string, value: string) { - return this.storage.setItem(`${this.prefix}:${key}`, value); + return this.storage.setItem(`${this.prefix}${key}`, value); } public removeItem(key: string) { - return this.storage.removeItem(`${this.prefix}:${key}`); + return this.storage.removeItem(`${this.prefix}${key}`); } } diff --git a/src/core/client/framework/mutations/SetAuthTokenMutation.spec.ts b/src/core/client/framework/mutations/SetAuthTokenMutation.spec.ts index 977dbb182..887e6fd84 100644 --- a/src/core/client/framework/mutations/SetAuthTokenMutation.spec.ts +++ b/src/core/client/framework/mutations/SetAuthTokenMutation.spec.ts @@ -33,24 +33,3 @@ it("Removes auth token from localStorage", () => { commit(environment, { authToken: null }, context as any); expect(context.localStorage.getItem("authToken")).toBeNull(); }); - -it("Sets auth token to pymLocalStorage", async () => { - const context = { - pymLocalStorage: createInMemoryStorage(), - localStorage: createInMemoryStorage(), - }; - const authToken = "auth token"; - commit(environment, { authToken }, context as any); - expect(source.get(LOCAL_ID)!.authToken).toEqual(authToken); - expect(await context.pymLocalStorage.getItem("authToken")).toEqual(authToken); -}); - -it("Removes auth token from pymLocalStorage", async () => { - const context = { - pymLocalStorage: createInMemoryStorage(), - localStorage: createInMemoryStorage(), - }; - localStorage.setItem("authToken", "tmp"); - commit(environment, { authToken: null }, context as any); - expect(await context.pymLocalStorage.getItem("authToken")).toBeNull(); -}); diff --git a/src/core/client/framework/mutations/SetAuthTokenMutation.ts b/src/core/client/framework/mutations/SetAuthTokenMutation.ts index f74c0a276..90e520155 100644 --- a/src/core/client/framework/mutations/SetAuthTokenMutation.ts +++ b/src/core/client/framework/mutations/SetAuthTokenMutation.ts @@ -13,16 +13,15 @@ export type SetAuthTokenMutation = (input: SetAuthTokenInput) => Promise; export async function commit( environment: Environment, input: SetAuthTokenInput, - { localStorage, pymLocalStorage }: TalkContext + { localStorage }: TalkContext ) { return commitLocalUpdate(environment, store => { const record = store.get(LOCAL_ID)!; record.setValue(input.authToken, "authToken"); - const storage = pymLocalStorage || localStorage; if (input.authToken) { - storage.setItem("authToken", input.authToken); + localStorage.setItem("authToken", input.authToken); } else { - storage.removeItem("authToken"); + localStorage.removeItem("authToken"); } // Increment auth revision to indicate a change in auth state. record.setValue(record.getValue("authRevision") + 1, "authRevision"); diff --git a/src/core/client/framework/testHelpers/createFakePymStorage.ts b/src/core/client/framework/testHelpers/createFakePymStorage.ts deleted file mode 100644 index e0d9c7800..000000000 --- a/src/core/client/framework/testHelpers/createFakePymStorage.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { - createInMemoryStorage, - createPromisifiedStorage, -} from "talk-framework/lib/storage"; - -export default function createFakePymStorage() { - return createPromisifiedStorage(createInMemoryStorage()); -} diff --git a/src/core/client/framework/testHelpers/index.ts b/src/core/client/framework/testHelpers/index.ts index 69e9c5cd0..345e7be68 100644 --- a/src/core/client/framework/testHelpers/index.ts +++ b/src/core/client/framework/testHelpers/index.ts @@ -4,7 +4,6 @@ export { } from "./createRelayEnvironment"; export { default as createFluentBundle } from "./createFluentBundle"; export { default as createSinonStub } from "./createSinonStub"; -export { default as createFakePymStorage } from "./createFakePymStorage"; export { default as removeFragmentRefs, NoFragmentRefs, diff --git a/src/core/client/stream/containers/PostCommentFormContainer.spec.tsx b/src/core/client/stream/containers/PostCommentFormContainer.spec.tsx index 53b104b3a..ff72be551 100644 --- a/src/core/client/stream/containers/PostCommentFormContainer.spec.tsx +++ b/src/core/client/stream/containers/PostCommentFormContainer.spec.tsx @@ -3,10 +3,9 @@ import { noop } from "lodash"; import React from "react"; import sinon from "sinon"; -import { PropTypesOf } from "talk-framework/types"; - import { timeout } from "talk-common/utils"; -import { createFakePymStorage } from "talk-framework/testHelpers"; +import { createPromisifiedStorage } from "talk-framework/lib/storage"; +import { PropTypesOf } from "talk-framework/types"; import { PostCommentFormContainer } from "./PostCommentFormContainer"; const contextKey = "postCommentFormBody"; @@ -16,7 +15,7 @@ it("renders correctly", async () => { // tslint:disable-next-line:no-empty createComment: (() => {}) as any, assetID: "asset-id", - pymSessionStorage: createFakePymStorage(), + sessionStorage: createPromisifiedStorage(), }; const wrapper = shallow(); @@ -30,10 +29,10 @@ it("renders with initialValues", async () => { // tslint:disable-next-line:no-empty createComment: (() => {}) as any, assetID: "asset-id", - pymSessionStorage: createFakePymStorage(), + sessionStorage: createPromisifiedStorage(), }; - await props.pymSessionStorage.setItem(contextKey, "Hello World!"); + await props.sessionStorage.setItem(contextKey, "Hello World!"); const wrapper = shallow(); await timeout(); @@ -46,10 +45,10 @@ it("save values", async () => { // tslint:disable-next-line:no-empty createComment: (() => {}) as any, assetID: "asset-id", - pymSessionStorage: createFakePymStorage(), + sessionStorage: createPromisifiedStorage(), }; - await props.pymSessionStorage.setItem(contextKey, "Hello World!"); + await props.sessionStorage.setItem(contextKey, "Hello World!"); const wrapper = shallow(); await timeout(); @@ -58,7 +57,7 @@ it("save values", async () => { .first() .props() .onChange({ values: { body: "changed" } }); - expect(await props.pymSessionStorage.getItem(contextKey)).toBe("changed"); + expect(await props.sessionStorage.getItem(contextKey)).toBe("changed"); }); it("creates a comment", async () => { @@ -76,10 +75,10 @@ it("creates a comment", async () => { // tslint:disable-next-line:no-empty createComment: createCommentStub, assetID, - pymSessionStorage: createFakePymStorage(), + sessionStorage: createPromisifiedStorage(), }; - await props.pymSessionStorage.setItem(contextKey, "Hello World!"); + await props.sessionStorage.setItem(contextKey, "Hello World!"); const wrapper = shallow(); await timeout(); diff --git a/src/core/client/stream/containers/PostCommentFormContainer.tsx b/src/core/client/stream/containers/PostCommentFormContainer.tsx index fe8b7d29c..882a3a07c 100644 --- a/src/core/client/stream/containers/PostCommentFormContainer.tsx +++ b/src/core/client/stream/containers/PostCommentFormContainer.tsx @@ -13,7 +13,7 @@ import { CreateCommentMutation, withCreateCommentMutation } from "../mutations"; interface InnerProps { createComment: CreateCommentMutation; assetID: string; - pymSessionStorage: PromisifiedStorage; + sessionStorage: PromisifiedStorage; } interface State { @@ -32,7 +32,7 @@ export class PostCommentFormContainer extends Component { } private async init() { - const body = await this.props.pymSessionStorage.getItem(contextKey); + const body = await this.props.sessionStorage.getItem(contextKey); if (body) { this.setState({ initialValues: { @@ -67,9 +67,9 @@ export class PostCommentFormContainer extends Component { private handleOnChange: PostCommentFormProps["onChange"] = state => { if (state.values.body) { - this.props.pymSessionStorage.setItem(contextKey, state.values.body); + this.props.sessionStorage.setItem(contextKey, state.values.body); } else { - this.props.pymSessionStorage.removeItem(contextKey); + this.props.sessionStorage.removeItem(contextKey); } }; @@ -87,8 +87,8 @@ export class PostCommentFormContainer extends Component { } } -const enhanced = withContext(({ pymSessionStorage }) => ({ - pymSessionStorage, +const enhanced = withContext(({ sessionStorage }) => ({ + sessionStorage, }))(withCreateCommentMutation(PostCommentFormContainer)); export type PostCommentFormContainerProps = PropTypesOf; export default enhanced; diff --git a/src/core/client/stream/containers/ReplyCommentFormContainer.spec.tsx b/src/core/client/stream/containers/ReplyCommentFormContainer.spec.tsx index 3b3c30096..e189de00d 100644 --- a/src/core/client/stream/containers/ReplyCommentFormContainer.spec.tsx +++ b/src/core/client/stream/containers/ReplyCommentFormContainer.spec.tsx @@ -3,13 +3,10 @@ import { noop } from "lodash"; import React from "react"; import sinon from "sinon"; -import { PropTypesOf } from "talk-framework/types"; - import { timeout } from "talk-common/utils"; -import { - createFakePymStorage, - removeFragmentRefs, -} from "talk-framework/testHelpers"; +import { createPromisifiedStorage } from "talk-framework/lib/storage"; +import { removeFragmentRefs } from "talk-framework/testHelpers"; +import { PropTypesOf } from "talk-framework/types"; import { ReplyCommentFormContainer } from "./ReplyCommentFormContainer"; const ReplyCommentFormContainerN = removeFragmentRefs( @@ -29,7 +26,7 @@ it("renders correctly", async () => { comment: { id: "comment-id", }, - pymSessionStorage: createFakePymStorage(), + sessionStorage: createPromisifiedStorage(), autofocus: false, }; @@ -48,11 +45,11 @@ it("renders with initialValues", async () => { comment: { id: "comment-id", }, - pymSessionStorage: createFakePymStorage(), + sessionStorage: createPromisifiedStorage(), autofocus: false, }; - await props.pymSessionStorage.setItem( + await props.sessionStorage.setItem( getContextKey(props.comment.id), "Hello World!" ); @@ -72,11 +69,11 @@ it("save values", async () => { comment: { id: "comment-id", }, - pymSessionStorage: createFakePymStorage(), + sessionStorage: createPromisifiedStorage(), autofocus: false, }; - await props.pymSessionStorage.setItem( + await props.sessionStorage.setItem( getContextKey(props.comment.id), "Hello World!" ); @@ -89,7 +86,7 @@ it("save values", async () => { .props() .onChange({ values: { body: "changed" } }); expect( - await props.pymSessionStorage.getItem(getContextKey(props.comment.id)) + await props.sessionStorage.getItem(getContextKey(props.comment.id)) ).toBe("changed"); }); @@ -108,12 +105,12 @@ it("creates a comment", async () => { comment: { id: "comment-id", }, - pymSessionStorage: createFakePymStorage(), + sessionStorage: createPromisifiedStorage(), onClose: onCloseStub, autofocus: false, }; - await props.pymSessionStorage.setItem( + await props.sessionStorage.setItem( getContextKey(props.comment.id), "Hello World!" ); @@ -146,12 +143,12 @@ it("closes on cancel", async () => { comment: { id: "comment-id", }, - pymSessionStorage: createFakePymStorage(), + sessionStorage: createPromisifiedStorage(), onClose: onCloseStub, autofocus: false, }; - await props.pymSessionStorage.setItem( + await props.sessionStorage.setItem( getContextKey(props.comment.id), "Hello World!" ); @@ -166,7 +163,7 @@ it("closes on cancel", async () => { // Removes saved value. expect( - await props.pymSessionStorage.getItem(getContextKey(props.comment.id)) + await props.sessionStorage.getItem(getContextKey(props.comment.id)) ).toBeNull(); }); @@ -181,7 +178,7 @@ it("autofocuses", async () => { comment: { id: "comment-id", }, - pymSessionStorage: createFakePymStorage(), + sessionStorage: createPromisifiedStorage(), autofocus: true, }; diff --git a/src/core/client/stream/containers/ReplyCommentFormContainer.tsx b/src/core/client/stream/containers/ReplyCommentFormContainer.tsx index 10f7cd6de..3c0379e30 100644 --- a/src/core/client/stream/containers/ReplyCommentFormContainer.tsx +++ b/src/core/client/stream/containers/ReplyCommentFormContainer.tsx @@ -17,7 +17,7 @@ import { CreateCommentMutation, withCreateCommentMutation } from "../mutations"; interface InnerProps { createComment: CreateCommentMutation; - pymSessionStorage: PromisifiedStorage; + sessionStorage: PromisifiedStorage; comment: CommentData; asset: AssetData; onClose?: () => void; @@ -45,7 +45,7 @@ export class ReplyCommentFormContainer extends Component { }; private async init() { - const body = await this.props.pymSessionStorage.getItem(this.contextKey); + const body = await this.props.sessionStorage.getItem(this.contextKey); if (body) { this.setState({ initialValues: { @@ -59,7 +59,7 @@ export class ReplyCommentFormContainer extends Component { } private handleOnCancel = () => { - this.props.pymSessionStorage.removeItem(this.contextKey); + this.props.sessionStorage.removeItem(this.contextKey); if (this.props.onClose) { this.props.onClose(); } @@ -76,7 +76,7 @@ export class ReplyCommentFormContainer extends Component { ...input, }); - this.props.pymSessionStorage.removeItem(this.contextKey); + this.props.sessionStorage.removeItem(this.contextKey); if (this.props.onClose) { this.props.onClose(); } @@ -92,9 +92,9 @@ export class ReplyCommentFormContainer extends Component { private handleOnChange: ReplyCommentFormProps["onChange"] = state => { if (state.values.body) { - this.props.pymSessionStorage.setItem(this.contextKey, state.values.body); + this.props.sessionStorage.setItem(this.contextKey, state.values.body); } else { - this.props.pymSessionStorage.removeItem(this.contextKey); + this.props.sessionStorage.removeItem(this.contextKey); } }; @@ -114,8 +114,8 @@ export class ReplyCommentFormContainer extends Component { ); } } -const enhanced = withContext(({ pymSessionStorage, browserInfo }) => ({ - pymSessionStorage, +const enhanced = withContext(({ sessionStorage, browserInfo }) => ({ + sessionStorage, // Disable autofocus on ios and enable for the rest. autofocus: !browserInfo.ios, }))( diff --git a/src/core/client/stream/local/initLocalState.spec.ts b/src/core/client/stream/local/initLocalState.spec.ts index 29ddcc61b..584c78559 100644 --- a/src/core/client/stream/local/initLocalState.spec.ts +++ b/src/core/client/stream/local/initLocalState.spec.ts @@ -3,10 +3,7 @@ import { Environment, RecordSource } from "relay-runtime"; import { timeout } from "talk-common/utils"; import { TalkContext } from "talk-framework/lib/bootstrap"; import { LOCAL_ID } from "talk-framework/lib/relay"; -import { - createInMemoryStorage, - createPromisifiedStorage, -} from "talk-framework/lib/storage"; +import { createPromisifiedStorage } from "talk-framework/lib/storage"; import { createRelayEnvironment } from "talk-framework/testHelpers"; import initLocalState from "./initLocalState"; @@ -24,7 +21,7 @@ beforeEach(() => { it("init local state", async () => { const context: Partial = { - pymLocalStorage: createPromisifiedStorage(createInMemoryStorage()), + localStorage: createPromisifiedStorage(), }; await initLocalState(environment, context as any); await timeout(); @@ -33,7 +30,7 @@ it("init local state", async () => { it("set assetID from query", async () => { const context: Partial = { - pymLocalStorage: createPromisifiedStorage(createInMemoryStorage()), + localStorage: createPromisifiedStorage(), }; const assetID = "asset-id"; const previousLocation = location.toString(); @@ -50,7 +47,7 @@ it("set assetID from query", async () => { it("set commentID from query", async () => { const context: Partial = { - pymLocalStorage: createPromisifiedStorage(createInMemoryStorage()), + localStorage: createPromisifiedStorage(), }; const commentID = "comment-id"; const previousLocation = location.toString(); @@ -67,10 +64,10 @@ it("set commentID from query", async () => { it("set authToken from localStorage", async () => { const context: Partial = { - pymLocalStorage: createPromisifiedStorage(createInMemoryStorage()), + localStorage: createPromisifiedStorage(), }; const authToken = "auth-token"; - context.pymLocalStorage!.setItem("authToken", authToken); + context.localStorage!.setItem("authToken", authToken); await initLocalState(environment, context as any); expect(source.get(LOCAL_ID)!.authToken).toBe(authToken); }); diff --git a/src/core/client/stream/local/initLocalState.ts b/src/core/client/stream/local/initLocalState.ts index 4666ab261..2d953e6ae 100644 --- a/src/core/client/stream/local/initLocalState.ts +++ b/src/core/client/stream/local/initLocalState.ts @@ -20,9 +20,9 @@ import { */ export default async function initLocalState( environment: Environment, - { pymLocalStorage }: TalkContext + { localStorage }: TalkContext ) { - const authToken = await pymLocalStorage!.getItem("authToken"); + const authToken = await localStorage!.getItem("authToken"); commitLocalUpdate(environment, s => { // TODO: (cvle) move local, auth token and network initialization to framework. diff --git a/src/core/client/stream/test/create.tsx b/src/core/client/stream/test/create.tsx index ab5e9bcfb..a49e46f9b 100644 --- a/src/core/client/stream/test/create.tsx +++ b/src/core/client/stream/test/create.tsx @@ -6,8 +6,7 @@ import { Environment, RecordProxy, RecordSourceProxy } from "relay-runtime"; import { TalkContext, TalkContextProvider } from "talk-framework/lib/bootstrap"; import { PostMessageService } from "talk-framework/lib/postMessage"; import { RestClient } from "talk-framework/lib/rest"; -import { createInMemoryStorage } from "talk-framework/lib/storage"; -import { createFakePymStorage } from "talk-framework/testHelpers"; +import { createPromisifiedStorage } from "talk-framework/lib/storage"; import AppContainer from "talk-stream/containers/AppContainer"; import createEnvironment from "./createEnvironment"; @@ -40,10 +39,8 @@ export default function create(params: CreateParams) { const context: TalkContext = { relayEnvironment: environment, localeBundles: [createFluentBundle()], - localStorage: createInMemoryStorage(), - sessionStorage: createInMemoryStorage(), - pymLocalStorage: createFakePymStorage(), - pymSessionStorage: createFakePymStorage(), + localStorage: createPromisifiedStorage(), + sessionStorage: createPromisifiedStorage(), rest: new RestClient("http://localhost/api"), postMessage: new PostMessageService(), browserInfo: { ios: false },