mirror of
https://github.com/wassname/talk.git
synced 2026-07-16 11:22:16 +08:00
[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:
@@ -8,6 +8,7 @@ import { Environment } from "relay-runtime";
|
||||
|
||||
import { PostMessageService } from "talk-framework/lib/postMessage";
|
||||
import { RestClient } from "talk-framework/lib/rest";
|
||||
import { PymStorage } from "talk-framework/lib/storage";
|
||||
import { UIContext } from "talk-ui/components";
|
||||
import { ClickFarAwayRegister } from "talk-ui/components/ClickOutside";
|
||||
|
||||
@@ -21,12 +22,18 @@ export interface TalkContext {
|
||||
/** formatter for timeago. */
|
||||
timeagoFormatter?: Formatter;
|
||||
|
||||
/** Session Storage */
|
||||
/** Local Storage */
|
||||
localStorage: Storage;
|
||||
|
||||
/** Session storage */
|
||||
sessionStorage: Storage;
|
||||
|
||||
/** Local Storage over pym */
|
||||
pymLocalStorage?: PymStorage;
|
||||
|
||||
/** Session storage over pym */
|
||||
pymSessionStorage?: PymStorage;
|
||||
|
||||
/** media query values for testing purposes */
|
||||
mediaQueryValues?: MediaQueryMatchers;
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Environment, Network, RecordSource, Store } from "relay-runtime";
|
||||
import { LOCAL_ID } from "talk-framework/lib/relay";
|
||||
import {
|
||||
createLocalStorage,
|
||||
createPymStorage,
|
||||
createSessionStorage,
|
||||
} from "talk-framework/lib/storage";
|
||||
|
||||
@@ -118,6 +119,8 @@ export default async function createContext({
|
||||
postMessage: new PostMessageService(),
|
||||
localStorage: createLocalStorage(),
|
||||
sessionStorage: createSessionStorage(),
|
||||
pymLocalStorage: pym && createPymStorage(pym, "localStorage"),
|
||||
pymSessionStorage: pym && createPymStorage(pym, "sessionStorage"),
|
||||
};
|
||||
|
||||
// Run custom initializations.
|
||||
|
||||
@@ -38,6 +38,10 @@ class InMemoryStorage implements Storage {
|
||||
public removeItem(key: string) {
|
||||
delete this.storage[key];
|
||||
}
|
||||
|
||||
public toString() {
|
||||
return JSON.stringify(this.storage);
|
||||
}
|
||||
}
|
||||
|
||||
export default function createInMemoryStorage() {
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import createPymStorage from "./PymStorage";
|
||||
|
||||
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("PymStorage", () => {
|
||||
it("should set item", () => {
|
||||
const pym = new PymStub("localStorage");
|
||||
const storage = createPymStorage(pym as any, "localStorage");
|
||||
const promise = storage.setItem("test", "value");
|
||||
const { key, value } = pym.messages.pop()!;
|
||||
expect(key).toBe(`pymStorage.localStorage.request`);
|
||||
const { id, method, parameters } = JSON.parse(value);
|
||||
expect(method).toBe("setItem");
|
||||
expect(parameters).toEqual({ key: "test", value: "value" });
|
||||
pym.listeners["pymStorage.localStorage.response"](JSON.stringify({ id }));
|
||||
expect(promise).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it("should remove item", () => {
|
||||
const pym = new PymStub("localStorage");
|
||||
const storage = createPymStorage(pym as any, "localStorage");
|
||||
const promise = storage.removeItem("test");
|
||||
const { key, value } = pym.messages.pop()!;
|
||||
expect(key).toBe(`pymStorage.localStorage.request`);
|
||||
const { id, method, parameters } = JSON.parse(value);
|
||||
expect(method).toBe("removeItem");
|
||||
expect(parameters).toEqual({ key: "test" });
|
||||
pym.listeners["pymStorage.localStorage.response"](JSON.stringify({ id }));
|
||||
expect(promise).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it("should get item", () => {
|
||||
const pym = new PymStub("localStorage");
|
||||
const storage = createPymStorage(pym as any, "localStorage");
|
||||
const promise = storage.getItem("test");
|
||||
const { key, value } = pym.messages.pop()!;
|
||||
expect(key).toBe(`pymStorage.localStorage.request`);
|
||||
const { id, method, parameters } = JSON.parse(value);
|
||||
expect(method).toBe("getItem");
|
||||
expect(parameters).toEqual({ key: "test" });
|
||||
pym.listeners["pymStorage.localStorage.response"](
|
||||
JSON.stringify({ id, result: "value" })
|
||||
);
|
||||
expect(promise).resolves.toBe("value");
|
||||
});
|
||||
|
||||
describe("on error", () => {
|
||||
it("should reject set item", () => {
|
||||
const pym = new PymStub("localStorage");
|
||||
const storage = createPymStorage(pym as any, "localStorage");
|
||||
const promise = storage.setItem("test", "value");
|
||||
const { key, value } = pym.messages.pop()!;
|
||||
expect(key).toBe(`pymStorage.localStorage.request`);
|
||||
const { id } = JSON.parse(value);
|
||||
pym.listeners["pymStorage.localStorage.error"](
|
||||
JSON.stringify({ id, error: "error" })
|
||||
);
|
||||
expect(promise).rejects.toThrow(new Error("error"));
|
||||
});
|
||||
it("should reject remove item", () => {
|
||||
const pym = new PymStub("localStorage");
|
||||
const storage = createPymStorage(pym as any, "localStorage");
|
||||
const promise = storage.removeItem("test");
|
||||
const { key, value } = pym.messages.pop()!;
|
||||
expect(key).toBe(`pymStorage.localStorage.request`);
|
||||
const { id } = JSON.parse(value);
|
||||
pym.listeners["pymStorage.localStorage.error"](
|
||||
JSON.stringify({ id, error: "error" })
|
||||
);
|
||||
expect(promise).rejects.toThrow(new Error("error"));
|
||||
});
|
||||
it("should reject get item", () => {
|
||||
const pym = new PymStub("localStorage");
|
||||
const storage = createPymStorage(pym as any, "localStorage");
|
||||
const promise = storage.getItem("test");
|
||||
const { key, value } = pym.messages.pop()!;
|
||||
expect(key).toBe(`pymStorage.localStorage.request`);
|
||||
const { id } = JSON.parse(value);
|
||||
pym.listeners["pymStorage.localStorage.error"](
|
||||
JSON.stringify({ id, error: "error" })
|
||||
);
|
||||
expect(promise).rejects.toThrow(new Error("error"));
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,94 @@
|
||||
import { Child, Parent } from "pym.js";
|
||||
import uuid from "uuid/v4";
|
||||
|
||||
type Pym = Child | Parent;
|
||||
|
||||
export interface PymStorage {
|
||||
/**
|
||||
* value = storage[key]
|
||||
*/
|
||||
getItem(key: string): Promise<string | null>;
|
||||
/**
|
||||
* delete storage[key]
|
||||
*/
|
||||
removeItem(key: string): Promise<void>;
|
||||
/**
|
||||
* storage[key] = value
|
||||
*/
|
||||
setItem(key: string, value: string): Promise<void>;
|
||||
}
|
||||
|
||||
class PymStorageImpl implements PymStorage {
|
||||
/** Instance to pym */
|
||||
private pym: Pym;
|
||||
|
||||
/** Requested storage type */
|
||||
private type: string;
|
||||
|
||||
/** A Map of requestID => {resolve, reject} */
|
||||
private requests: Record<
|
||||
string,
|
||||
{ resolve: ((v: any) => void); reject: ((v: any) => void) }
|
||||
> = {};
|
||||
|
||||
/** Requests method with parameters over pym. */
|
||||
private call<T>(
|
||||
method: string,
|
||||
parameters: { key: string; value?: string }
|
||||
): Promise<T> {
|
||||
const id = uuid();
|
||||
return new Promise((resolve, reject) => {
|
||||
this.requests[id] = { resolve, reject };
|
||||
this.pym.sendMessage(
|
||||
`pymStorage.${this.type}.request`,
|
||||
JSON.stringify({ id, method, parameters })
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/** Listen to pym responses */
|
||||
private listen() {
|
||||
// Receive successful responses.
|
||||
this.pym.onMessage(`pymStorage.${this.type}.response`, (msg: string) => {
|
||||
const { id, result } = JSON.parse(msg);
|
||||
this.requests[id].resolve(result);
|
||||
delete this.requests[id];
|
||||
});
|
||||
|
||||
// Receive error responses.
|
||||
this.pym.onMessage(`pymStorage.${this.type}.error`, (msg: string) => {
|
||||
const { id, error } = JSON.parse(msg);
|
||||
this.requests[id].reject(new Error(error));
|
||||
delete this.requests[id];
|
||||
});
|
||||
}
|
||||
|
||||
constructor(pym: Pym, type: string) {
|
||||
this.pym = pym;
|
||||
this.type = type;
|
||||
this.listen();
|
||||
}
|
||||
|
||||
public setItem(key: string, value: string) {
|
||||
return this.call<void>("setItem", { key, value });
|
||||
}
|
||||
public getItem(key: string) {
|
||||
return this.call<string | null>("getItem", { key });
|
||||
}
|
||||
public removeItem(key: string) {
|
||||
return this.call<void>("removeItem", { key });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a storage that put requests onto pym.
|
||||
* This is the counterpart of `connectStorageToPym`.
|
||||
* @param {string} pym pym
|
||||
* @return {Object} storage
|
||||
*/
|
||||
export default function createPymStorage(
|
||||
pym: Pym,
|
||||
type: "localStorage" | "sessionStorage"
|
||||
): PymStorage {
|
||||
return new PymStorageImpl(pym, type);
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
export { default as createInMemoryStorage } from "./InMemoryStorage";
|
||||
export { default as createLocalStorage } from "./LocalStorage";
|
||||
export { default as createSessionStorage } from "./SessionStorage";
|
||||
export { default as createPymStorage, PymStorage } from "./PymStorage";
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { PymStorage } from "talk-framework/lib/storage";
|
||||
|
||||
export class FakeStorage implements PymStorage {
|
||||
public store: Record<string, string> = {};
|
||||
|
||||
public setItem(key: string, value: string) {
|
||||
this.store[key] = value;
|
||||
return Promise.resolve();
|
||||
}
|
||||
public removeItem(key: string) {
|
||||
delete this.store[key];
|
||||
return Promise.resolve();
|
||||
}
|
||||
public getItem(key: string) {
|
||||
return Promise.resolve(this.store[key]);
|
||||
}
|
||||
}
|
||||
|
||||
export default function createFakePymStorage() {
|
||||
return new FakeStorage();
|
||||
}
|
||||
@@ -4,6 +4,7 @@ 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,
|
||||
|
||||
Reference in New Issue
Block a user