[next] Save Comment Draft + Pym Storage (#1843)

* Implement pym storage

* Save comment draft + test

* Apply suggestions

* Use class for PymStorage implementation

* Add some comments
This commit is contained in:
Kiwi
2018-09-06 17:07:17 +00:00
committed by Wyatt Johnson
parent 0477d456a2
commit 53e548d77a
36 changed files with 727 additions and 283 deletions
+3
View File
@@ -7,6 +7,7 @@ import {
withClickEvent,
withEventEmitter,
withIOSSafariWidthWorkaround,
withPymStorage,
withSetCommentID,
} from "./decorators";
import PymControl from "./PymControl";
@@ -29,6 +30,8 @@ export function createPymControl(config: CreatePymControlConfig) {
withClickEvent,
withSetCommentID,
withEventEmitter(config.eventEmitter),
withPymStorage(localStorage, "localStorage"),
withPymStorage(sessionStorage, "sessionStorage"),
];
const query = qs.stringify({
@@ -0,0 +1,15 @@
// 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 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 2`] = `Object {}`;
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\\\\\\"}\\"}]"`;
+2 -4
View File
@@ -1,11 +1,9 @@
import pym from "pym.js";
export type CleanupCallback = () => void;
export type Decorator = (pym: pym.Parent) => CleanupCallback | void;
export { Decorator, CleanupCallback } from "./types";
export { default as withAutoHeight } from "./withAutoHeight";
export { default as withClickEvent } from "./withClickEvent";
export { default as withSetCommentID } from "./withSetCommentID";
export { default as withEventEmitter } from "./withEventEmitter";
export { default as withPymStorage } from "./withPymStorage";
export {
default as withIOSSafariWidthWorkaround,
} from "./withIOSSafariWidthWorkaround";
@@ -0,0 +1,4 @@
import pym from "pym.js";
export type CleanupCallback = () => void;
export type Decorator = (pym: pym.Parent) => CleanupCallback | void;
@@ -1,4 +1,4 @@
import { Decorator } from "./";
import { Decorator } from "./types";
const withAutoHeight: Decorator = pym => {
// Resize parent iframe height when child height changes
@@ -1,4 +1,4 @@
import { Decorator } from "./";
import { Decorator } from "./types";
const withClickEvent: Decorator = pym => {
const handleClick = () => pym.sendMessage("click", "");
@@ -1,6 +1,6 @@
import { EventEmitter2 } from "eventemitter2";
import { Decorator } from "./";
import { Decorator } from "./types";
const withEventEmitter = (eventEmitter: EventEmitter2): Decorator => pym => {
// Pass events from iframe to the event emitter.
@@ -1,4 +1,4 @@
import { Decorator } from "./";
import { Decorator } from "./types";
const withIOSSafariWidthWorkaround: Decorator = pym => {
// Workaround: IOS Safari ignores `width` but respects `min-width` value.
@@ -0,0 +1,104 @@
import sinon from "sinon";
import withPymStorage from "./withPymStorage";
// tslint:disable:max-classes-per-file
class FakeStorage {
public store: Record<string, string> = {};
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<string, ((msg: string) => void)> = {};
public messages: Array<{ key: string; value: string }> = [];
public type: string;
constructor(type: string) {
this.type = type;
}
public onMessage(key: string, callback: (msg: string) => void) {
this.listeners[key] = callback;
}
public sendMessage(key: string, value: string) {
this.messages.push({ key, value });
}
}
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
);
pym.listeners["pymStorage.localStorage.request"](
JSON.stringify({
id: "0",
method: "setItem",
parameters: { key: "key", value: "test" },
})
);
expect(storage.store).toMatchSnapshot();
pym.listeners["pymStorage.localStorage.request"](
JSON.stringify({
id: "1",
method: "getItem",
parameters: { key: "key" },
})
);
pym.listeners["pymStorage.localStorage.request"](
JSON.stringify({
id: "2",
method: "removeItem",
parameters: { key: "key" },
})
);
expect(storage.store).toMatchSnapshot();
expect(JSON.stringify(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
);
pym.listeners["pymStorage.localStorage.request"](
JSON.stringify({
id: "0",
method: "unknown",
parameters: {},
})
);
expect(JSON.stringify(pym.messages)).toMatchSnapshot();
});
it("should handle handle errors", () => {
const pym = new PymStub("localStorage");
const storage = new FakeStorage();
sinon
.mock(storage)
.expects("getItem")
.throws("error");
withPymStorage(storage as any, "localStorage", "talkPymStorage:")(
pym as any
);
pym.listeners["pymStorage.localStorage.request"](
JSON.stringify({
id: "0",
method: "getItem",
parameters: {},
})
);
expect(JSON.stringify(pym.messages)).toMatchSnapshot();
});
});
@@ -0,0 +1,52 @@
import { Decorator } from "./types";
const withPymStorage = (
storage: Storage,
type: "localStorage" | "sessionStorage",
prefix = "talkPymStorage:"
): Decorator => pym => {
pym.onMessage(`pymStorage.${type}.request`, (msg: any) => {
const { id, method, parameters } = JSON.parse(msg);
const { key, value } = parameters;
const prefixedKey = `${prefix}${key}`;
// Variable for the method return value.
let result;
const sendError = (error: string) => {
// tslint:disable-next-line:no-console
console.error(error);
pym.sendMessage(
`pymStorage.${type}.error`,
JSON.stringify({ id, error })
);
};
try {
switch (method) {
case "setItem":
result = storage.setItem(prefixedKey, value);
break;
case "getItem":
result = storage.getItem(prefixedKey);
break;
case "removeItem":
result = storage.removeItem(prefixedKey);
break;
default:
sendError(`Unknown method ${method}`);
return;
}
} catch (err) {
sendError(err.toString());
return;
}
pym.sendMessage(
`pymStorage.${type}.response`,
JSON.stringify({ id, result })
);
});
};
export default withPymStorage;
@@ -1,7 +1,7 @@
import qs from "query-string";
import { buildURL } from "../utils";
import { Decorator } from "./";
import { Decorator } from "./types";
function getCurrentCommentID() {
return qs.parse(location.search).commentID;