mirror of
https://github.com/wassname/talk.git
synced 2026-07-04 00:34: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:
@@ -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\\\\\\"}\\"}]"`;
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { FormState } from "final-form";
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { StatelessComponent } from "react";
|
||||
import { Field, Form } from "react-final-form";
|
||||
import { Field, Form, FormSpy } from "react-final-form";
|
||||
|
||||
import { OnSubmit } from "talk-framework/lib/form";
|
||||
import { required } from "talk-framework/lib/validation";
|
||||
@@ -22,10 +23,12 @@ interface FormProps {
|
||||
|
||||
export interface PostCommentFormProps {
|
||||
onSubmit: OnSubmit<FormProps>;
|
||||
onChange?: (state: FormState) => void;
|
||||
initialValues?: FormProps;
|
||||
}
|
||||
|
||||
const PostCommentForm: StatelessComponent<PostCommentFormProps> = props => (
|
||||
<Form onSubmit={props.onSubmit}>
|
||||
<Form onSubmit={props.onSubmit} initialValues={props.initialValues}>
|
||||
{({ handleSubmit, submitting }) => (
|
||||
<form
|
||||
autoComplete="off"
|
||||
@@ -33,6 +36,7 @@ const PostCommentForm: StatelessComponent<PostCommentFormProps> = props => (
|
||||
className={styles.root}
|
||||
id="comments-postCommentForm-form"
|
||||
>
|
||||
<FormSpy onChange={props.onChange} />
|
||||
<HorizontalGutter>
|
||||
<Field name="body" validate={required}>
|
||||
{({ input, meta }) => (
|
||||
|
||||
@@ -213,7 +213,7 @@ exports[`when use is logged in renders correctly 1`] = `
|
||||
<withContext(createMutationContainer(withContext(createMutationContainer(withContext(createMutationContainer(withContext(withLocalStateContainer(Relay(UserBoxContainer)))))))))
|
||||
user={Object {}}
|
||||
/>
|
||||
<withContext(createMutationContainer(PostCommentFormContainer))
|
||||
<withContext(withContext(createMutationContainer(PostCommentFormContainer)))
|
||||
assetID="asset-id"
|
||||
/>
|
||||
</withPropsOnChange(HorizontalGutter)>
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import { shallow } from "enzyme";
|
||||
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 { PostCommentFormContainer } from "./PostCommentFormContainer";
|
||||
|
||||
const contextKey = "postCommentFormBody";
|
||||
|
||||
it("renders correctly", async () => {
|
||||
const props: PropTypesOf<typeof PostCommentFormContainer> = {
|
||||
// tslint:disable-next-line:no-empty
|
||||
createComment: (() => {}) as any,
|
||||
assetID: "asset-id",
|
||||
pymSessionStorage: createFakePymStorage(),
|
||||
};
|
||||
|
||||
const wrapper = shallow(<PostCommentFormContainer {...props} />);
|
||||
await timeout();
|
||||
wrapper.update();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders with initialValues", async () => {
|
||||
const props: PropTypesOf<typeof PostCommentFormContainer> = {
|
||||
// tslint:disable-next-line:no-empty
|
||||
createComment: (() => {}) as any,
|
||||
assetID: "asset-id",
|
||||
pymSessionStorage: createFakePymStorage(),
|
||||
};
|
||||
|
||||
await props.pymSessionStorage.setItem(contextKey, "Hello World!");
|
||||
|
||||
const wrapper = shallow(<PostCommentFormContainer {...props} />);
|
||||
await timeout();
|
||||
wrapper.update();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("save values", async () => {
|
||||
const props: PropTypesOf<typeof PostCommentFormContainer> = {
|
||||
// tslint:disable-next-line:no-empty
|
||||
createComment: (() => {}) as any,
|
||||
assetID: "asset-id",
|
||||
pymSessionStorage: createFakePymStorage(),
|
||||
};
|
||||
|
||||
await props.pymSessionStorage.setItem(contextKey, "Hello World!");
|
||||
|
||||
const wrapper = shallow(<PostCommentFormContainer {...props} />);
|
||||
await timeout();
|
||||
wrapper.update();
|
||||
wrapper
|
||||
.first()
|
||||
.props()
|
||||
.onChange({ values: { body: "changed" } });
|
||||
expect(await props.pymSessionStorage.getItem(contextKey)).toBe("changed");
|
||||
});
|
||||
|
||||
it("creates a comment", async () => {
|
||||
const assetID = "asset-id";
|
||||
const input = { body: "Hello World!" };
|
||||
const createCommentStub = sinon.stub();
|
||||
const form = { reset: noop };
|
||||
const formMock = sinon.mock(form);
|
||||
formMock
|
||||
.expects("reset")
|
||||
.withArgs({})
|
||||
.once();
|
||||
|
||||
const props: PropTypesOf<typeof PostCommentFormContainer> = {
|
||||
// tslint:disable-next-line:no-empty
|
||||
createComment: createCommentStub,
|
||||
assetID,
|
||||
pymSessionStorage: createFakePymStorage(),
|
||||
};
|
||||
|
||||
await props.pymSessionStorage.setItem(contextKey, "Hello World!");
|
||||
|
||||
const wrapper = shallow(<PostCommentFormContainer {...props} />);
|
||||
await timeout();
|
||||
wrapper.update();
|
||||
wrapper
|
||||
.first()
|
||||
.props()
|
||||
.onSubmit(input, form);
|
||||
expect(
|
||||
createCommentStub.calledWith({
|
||||
assetID,
|
||||
...input,
|
||||
})
|
||||
).toBeTruthy();
|
||||
await timeout();
|
||||
formMock.verify();
|
||||
});
|
||||
@@ -1,6 +1,8 @@
|
||||
import React, { Component, ReactNode } from "react";
|
||||
import React, { Component } from "react";
|
||||
|
||||
import { withContext } from "talk-framework/lib/bootstrap";
|
||||
import { BadUserInputError } from "talk-framework/lib/errors";
|
||||
import { PymStorage } from "talk-framework/lib/storage";
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
|
||||
import PostCommentForm, {
|
||||
@@ -11,17 +13,48 @@ import { CreateCommentMutation, withCreateCommentMutation } from "../mutations";
|
||||
interface InnerProps {
|
||||
createComment: CreateCommentMutation;
|
||||
assetID: string;
|
||||
children?: ReactNode;
|
||||
pymSessionStorage: PymStorage;
|
||||
}
|
||||
|
||||
class PostCommentFormContainer extends Component<InnerProps> {
|
||||
private onSubmit: PostCommentFormProps["onSubmit"] = async (input, form) => {
|
||||
interface State {
|
||||
initialValues?: PostCommentFormProps["initialValues"];
|
||||
initialized: boolean;
|
||||
}
|
||||
|
||||
const contextKey = "postCommentFormBody";
|
||||
|
||||
export class PostCommentFormContainer extends Component<InnerProps, State> {
|
||||
public state: State = { initialized: false };
|
||||
|
||||
constructor(props: InnerProps) {
|
||||
super(props);
|
||||
this.init();
|
||||
}
|
||||
|
||||
private async init() {
|
||||
const body = await this.props.pymSessionStorage.getItem(contextKey);
|
||||
if (body) {
|
||||
this.setState({
|
||||
initialValues: {
|
||||
body,
|
||||
},
|
||||
});
|
||||
}
|
||||
this.setState({
|
||||
initialized: true,
|
||||
});
|
||||
}
|
||||
|
||||
private handleOnSubmit: PostCommentFormProps["onSubmit"] = async (
|
||||
input,
|
||||
form
|
||||
) => {
|
||||
try {
|
||||
await this.props.createComment({
|
||||
assetID: this.props.assetID,
|
||||
...input,
|
||||
});
|
||||
form.reset();
|
||||
form.reset({});
|
||||
} catch (error) {
|
||||
if (error instanceof BadUserInputError) {
|
||||
return error.invalidArgsLocalized;
|
||||
@@ -31,11 +64,31 @@ class PostCommentFormContainer extends Component<InnerProps> {
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
private handleOnChange: PostCommentFormProps["onChange"] = state => {
|
||||
if (state.values.body) {
|
||||
this.props.pymSessionStorage.setItem(contextKey, state.values.body);
|
||||
} else {
|
||||
this.props.pymSessionStorage.removeItem(contextKey);
|
||||
}
|
||||
};
|
||||
|
||||
public render() {
|
||||
return <PostCommentForm onSubmit={this.onSubmit} />;
|
||||
if (!this.state.initialized) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<PostCommentForm
|
||||
onSubmit={this.handleOnSubmit}
|
||||
onChange={this.handleOnChange}
|
||||
initialValues={this.state.initialValues}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withCreateCommentMutation(PostCommentFormContainer);
|
||||
const enhanced = withContext(({ pymSessionStorage }) => ({
|
||||
pymSessionStorage,
|
||||
}))(withCreateCommentMutation(PostCommentFormContainer));
|
||||
export type PostCommentFormContainerProps = PropTypesOf<typeof enhanced>;
|
||||
export default enhanced;
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<PostCommentForm
|
||||
onChange={[Function]}
|
||||
onSubmit={[Function]}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`renders with initialValues 1`] = `
|
||||
<PostCommentForm
|
||||
initialValues={
|
||||
Object {
|
||||
"body": "Hello World!",
|
||||
}
|
||||
}
|
||||
onChange={[Function]}
|
||||
onSubmit={[Function]}
|
||||
/>
|
||||
`;
|
||||
@@ -19,12 +19,14 @@ beforeEach(() => {
|
||||
});
|
||||
|
||||
it("init local state", async () => {
|
||||
initLocalState(environment, { localStorage: createInMemoryStorage() } as any);
|
||||
await initLocalState(environment, {
|
||||
localStorage: createInMemoryStorage(),
|
||||
} as any);
|
||||
await timeout();
|
||||
expect(JSON.stringify(source.toJSON(), null, 2)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("set assetID from query", () => {
|
||||
it("set assetID from query", async () => {
|
||||
const assetID = "asset-id";
|
||||
const previousLocation = location.toString();
|
||||
const previousState = window.history.state;
|
||||
@@ -33,12 +35,14 @@ it("set assetID from query", () => {
|
||||
document.title,
|
||||
`http://localhost/?assetID=${assetID}`
|
||||
);
|
||||
initLocalState(environment, { localStorage: createInMemoryStorage() } as any);
|
||||
await initLocalState(environment, {
|
||||
localStorage: createInMemoryStorage(),
|
||||
} as any);
|
||||
expect(source.get(LOCAL_ID)!.assetID).toBe(assetID);
|
||||
window.history.replaceState(previousState, document.title, previousLocation);
|
||||
});
|
||||
|
||||
it("set commentID from query", () => {
|
||||
it("set commentID from query", async () => {
|
||||
const commentID = "comment-id";
|
||||
const previousLocation = location.toString();
|
||||
const previousState = window.history.state;
|
||||
@@ -47,16 +51,18 @@ it("set commentID from query", () => {
|
||||
document.title,
|
||||
`http://localhost/?commentID=${commentID}`
|
||||
);
|
||||
initLocalState(environment, { localStorage: createInMemoryStorage() } as any);
|
||||
await initLocalState(environment, {
|
||||
localStorage: createInMemoryStorage(),
|
||||
} as any);
|
||||
expect(source.get(LOCAL_ID)!.commentID).toBe(commentID);
|
||||
window.history.replaceState(previousState, document.title, previousLocation);
|
||||
});
|
||||
|
||||
it("set authToken from localStorage", () => {
|
||||
it("set authToken from localStorage", async () => {
|
||||
const authToken = "auth-token";
|
||||
const localStorage = createInMemoryStorage();
|
||||
localStorage.setItem("authToken", authToken);
|
||||
initLocalState(environment, { localStorage } as any);
|
||||
await initLocalState(environment, { localStorage } as any);
|
||||
expect(source.get(LOCAL_ID)!.authToken).toBe(authToken);
|
||||
localStorage.removeItem("authToken");
|
||||
});
|
||||
|
||||
@@ -22,6 +22,8 @@ export default async function initLocalState(
|
||||
environment: Environment,
|
||||
{ localStorage }: TalkContext
|
||||
) {
|
||||
const authToken = await localStorage.getItem("authToken");
|
||||
|
||||
commitLocalUpdate(environment, s => {
|
||||
// TODO: (cvle) move local, auth token and network initialization to framework.
|
||||
const root = s.getRoot();
|
||||
@@ -31,7 +33,7 @@ export default async function initLocalState(
|
||||
root.setLinkedRecord(localRecord, "local");
|
||||
|
||||
// Set auth token
|
||||
localRecord.setValue(localStorage.getItem("authToken") || "", "authToken");
|
||||
localRecord.setValue(authToken || "", "authToken");
|
||||
|
||||
// Set initial auth revision, this is increment whenenver auth state might have changed.
|
||||
localRecord.setValue(0, "authRevision");
|
||||
|
||||
@@ -194,10 +194,10 @@ exports[`post a comment 1`] = `
|
||||
</span>
|
||||
<time
|
||||
className="Timestamp-root RelativeTime-root"
|
||||
dateTime="2018-07-06T18:24:00.002Z"
|
||||
title="2018-07-06T18:24:00.002Z"
|
||||
dateTime="2018-07-06T18:24:00.000Z"
|
||||
title="2018-07-06T18:24:00.000Z"
|
||||
>
|
||||
2018-07-06T18:24:00.002Z
|
||||
2018-07-06T18:24:00.000Z
|
||||
</time>
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { IResolvers } from "graphql-tools";
|
||||
import React from "react";
|
||||
import TestRenderer from "react-test-renderer";
|
||||
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 AppContainer from "talk-stream/containers/AppContainer";
|
||||
|
||||
import createEnvironment from "./createEnvironment";
|
||||
import createFluentBundle from "./createFluentBundle";
|
||||
import createNodeMock from "./createNodeMock";
|
||||
|
||||
interface CreateParams {
|
||||
logNetwork?: boolean;
|
||||
resolvers: IResolvers<any, any>;
|
||||
initLocalState?: (
|
||||
local: RecordProxy,
|
||||
source: RecordSourceProxy,
|
||||
environment: Environment
|
||||
) => void;
|
||||
}
|
||||
|
||||
export default function create(params: CreateParams) {
|
||||
const environment = createEnvironment({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: params.logNetwork,
|
||||
resolvers: params.resolvers,
|
||||
initLocalState: (localRecord, source, env) => {
|
||||
localRecord.setValue(0, "authRevision");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, env);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const context: TalkContext = {
|
||||
relayEnvironment: environment,
|
||||
localeBundles: [createFluentBundle()],
|
||||
localStorage: createInMemoryStorage(),
|
||||
sessionStorage: createInMemoryStorage(),
|
||||
pymLocalStorage: createFakePymStorage(),
|
||||
pymSessionStorage: createFakePymStorage(),
|
||||
rest: new RestClient("http://localhost/api"),
|
||||
postMessage: new PostMessageService(),
|
||||
};
|
||||
|
||||
const testRenderer = TestRenderer.create(
|
||||
<TalkContextProvider value={context}>
|
||||
<AppContainer />
|
||||
</TalkContextProvider>,
|
||||
{ createNodeMock }
|
||||
);
|
||||
|
||||
return { context, testRenderer };
|
||||
}
|
||||
@@ -1,17 +1,9 @@
|
||||
import React from "react";
|
||||
import TestRenderer, { ReactTestRenderer } from "react-test-renderer";
|
||||
import { ReactTestRenderer } from "react-test-renderer";
|
||||
|
||||
import { 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 { createSinonStub } from "talk-framework/testHelpers";
|
||||
import AppContainer from "talk-stream/containers/AppContainer";
|
||||
|
||||
import createEnvironment from "./createEnvironment";
|
||||
import createFluentBundle from "./createFluentBundle";
|
||||
import createNodeMock from "./createNodeMock";
|
||||
import create from "./create";
|
||||
import { assets, comments } from "./fixtures";
|
||||
|
||||
let testRenderer: ReactTestRenderer;
|
||||
@@ -68,31 +60,14 @@ beforeEach(() => {
|
||||
},
|
||||
};
|
||||
|
||||
const environment = createEnvironment({
|
||||
({ testRenderer } = create({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
resolvers,
|
||||
initLocalState: (localRecord, source) => {
|
||||
localRecord.setValue(0, "authRevision");
|
||||
initLocalState: localRecord => {
|
||||
localRecord.setValue(assetStub.id, "assetID");
|
||||
},
|
||||
});
|
||||
|
||||
const context: TalkContext = {
|
||||
relayEnvironment: environment,
|
||||
localeBundles: [createFluentBundle()],
|
||||
localStorage: createInMemoryStorage(),
|
||||
sessionStorage: createInMemoryStorage(),
|
||||
rest: new RestClient("http://localhost/api"),
|
||||
postMessage: new PostMessageService(),
|
||||
};
|
||||
|
||||
testRenderer = TestRenderer.create(
|
||||
<TalkContextProvider value={context}>
|
||||
<AppContainer />
|
||||
</TalkContextProvider>,
|
||||
{ createNodeMock }
|
||||
);
|
||||
}));
|
||||
});
|
||||
|
||||
it("renders comment stream", async () => {
|
||||
|
||||
@@ -1,19 +1,10 @@
|
||||
import React from "react";
|
||||
import TestRenderer, { ReactTestRenderer } from "react-test-renderer";
|
||||
import { RecordProxy } from "relay-runtime";
|
||||
import { ReactTestRenderer } from "react-test-renderer";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { 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 { createSinonStub } from "talk-framework/testHelpers";
|
||||
import AppContainer from "talk-stream/containers/AppContainer";
|
||||
|
||||
import createEnvironment from "./createEnvironment";
|
||||
import createFluentBundle from "./createFluentBundle";
|
||||
import createNodeMock from "./createNodeMock";
|
||||
import create from "./create";
|
||||
import { assets, comments } from "./fixtures";
|
||||
|
||||
let testRenderer: ReactTestRenderer;
|
||||
@@ -50,32 +41,15 @@ beforeEach(() => {
|
||||
},
|
||||
};
|
||||
|
||||
const environment = createEnvironment({
|
||||
({ testRenderer } = create({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
resolvers,
|
||||
initLocalState: (localRecord: RecordProxy) => {
|
||||
localRecord.setValue(0, "authRevision");
|
||||
initLocalState: localRecord => {
|
||||
localRecord.setValue(assetStub.id, "assetID");
|
||||
localRecord.setValue(commentStub.id, "commentID");
|
||||
},
|
||||
});
|
||||
|
||||
const context: TalkContext = {
|
||||
relayEnvironment: environment,
|
||||
localeBundles: [createFluentBundle()],
|
||||
localStorage: createInMemoryStorage(),
|
||||
sessionStorage: createInMemoryStorage(),
|
||||
rest: new RestClient("http://localhost/api"),
|
||||
postMessage: new PostMessageService(),
|
||||
};
|
||||
|
||||
testRenderer = TestRenderer.create(
|
||||
<TalkContextProvider value={context}>
|
||||
<AppContainer />
|
||||
</TalkContextProvider>,
|
||||
{ createNodeMock }
|
||||
);
|
||||
}));
|
||||
});
|
||||
|
||||
it("renders permalink view", async () => {
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
import React from "react";
|
||||
import TestRenderer, { ReactTestRenderer } from "react-test-renderer";
|
||||
import { RecordProxy } from "relay-runtime";
|
||||
import { ReactTestRenderer } from "react-test-renderer";
|
||||
|
||||
import { 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 AppContainer from "talk-stream/containers/AppContainer";
|
||||
|
||||
import createEnvironment from "./createEnvironment";
|
||||
import createFluentBundle from "./createFluentBundle";
|
||||
import createNodeMock from "./createNodeMock";
|
||||
import create from "./create";
|
||||
|
||||
let testRenderer: ReactTestRenderer;
|
||||
beforeEach(() => {
|
||||
@@ -22,32 +13,15 @@ beforeEach(() => {
|
||||
},
|
||||
};
|
||||
|
||||
const environment = createEnvironment({
|
||||
({ testRenderer } = create({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
resolvers,
|
||||
initLocalState: (localRecord: RecordProxy) => {
|
||||
localRecord.setValue(0, "authRevision");
|
||||
initLocalState: localRecord => {
|
||||
localRecord.setValue("unknown-asset-id", "assetID");
|
||||
localRecord.setValue("unknown-comment-id", "commentID");
|
||||
},
|
||||
});
|
||||
|
||||
const context: TalkContext = {
|
||||
relayEnvironment: environment,
|
||||
localeBundles: [createFluentBundle()],
|
||||
localStorage: createInMemoryStorage(),
|
||||
sessionStorage: createInMemoryStorage(),
|
||||
rest: new RestClient("http://localhost/api"),
|
||||
postMessage: new PostMessageService(),
|
||||
};
|
||||
|
||||
testRenderer = TestRenderer.create(
|
||||
<TalkContextProvider value={context}>
|
||||
<AppContainer />
|
||||
</TalkContextProvider>,
|
||||
{ createNodeMock }
|
||||
);
|
||||
}));
|
||||
});
|
||||
|
||||
it("renders permalink view with unknown asset", async () => {
|
||||
|
||||
@@ -1,19 +1,10 @@
|
||||
import React from "react";
|
||||
import TestRenderer, { ReactTestRenderer } from "react-test-renderer";
|
||||
import { RecordProxy } from "relay-runtime";
|
||||
import { ReactTestRenderer } from "react-test-renderer";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { 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 { createSinonStub } from "talk-framework/testHelpers";
|
||||
import AppContainer from "talk-stream/containers/AppContainer";
|
||||
|
||||
import createEnvironment from "./createEnvironment";
|
||||
import createFluentBundle from "./createFluentBundle";
|
||||
import createNodeMock from "./createNodeMock";
|
||||
import create from "./create";
|
||||
import { assets, comments } from "./fixtures";
|
||||
|
||||
let testRenderer: ReactTestRenderer;
|
||||
@@ -47,32 +38,15 @@ beforeEach(() => {
|
||||
},
|
||||
};
|
||||
|
||||
const environment = createEnvironment({
|
||||
({ testRenderer } = create({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
resolvers,
|
||||
initLocalState: (localRecord: RecordProxy) => {
|
||||
initLocalState: localRecord => {
|
||||
localRecord.setValue(assetStub.id, "assetID");
|
||||
localRecord.setValue("unknown-comment-id", "commentID");
|
||||
localRecord.setValue(0, "authRevision");
|
||||
},
|
||||
});
|
||||
|
||||
const context: TalkContext = {
|
||||
relayEnvironment: environment,
|
||||
localeBundles: [createFluentBundle()],
|
||||
localStorage: createInMemoryStorage(),
|
||||
sessionStorage: createInMemoryStorage(),
|
||||
rest: new RestClient("http://localhost/api"),
|
||||
postMessage: new PostMessageService(),
|
||||
};
|
||||
|
||||
testRenderer = TestRenderer.create(
|
||||
<TalkContextProvider value={context}>
|
||||
<AppContainer />
|
||||
</TalkContextProvider>,
|
||||
{ createNodeMock }
|
||||
);
|
||||
}));
|
||||
});
|
||||
|
||||
it("renders permalink view with unknown comment", async () => {
|
||||
|
||||
@@ -1,19 +1,10 @@
|
||||
import React from "react";
|
||||
import TestRenderer, { ReactTestRenderer } from "react-test-renderer";
|
||||
import { RecordProxy } from "relay-runtime";
|
||||
import { ReactTestRenderer } from "react-test-renderer";
|
||||
import timekeeper from "timekeeper";
|
||||
|
||||
import { 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 { createSinonStub } from "talk-framework/testHelpers";
|
||||
import AppContainer from "talk-stream/containers/AppContainer";
|
||||
|
||||
import createEnvironment from "./createEnvironment";
|
||||
import createFluentBundle from "./createFluentBundle";
|
||||
import createNodeMock from "./createNodeMock";
|
||||
import create from "./create";
|
||||
import { assets, users } from "./fixtures";
|
||||
|
||||
let testRenderer: ReactTestRenderer;
|
||||
@@ -58,31 +49,14 @@ beforeEach(() => {
|
||||
},
|
||||
};
|
||||
|
||||
const environment = createEnvironment({
|
||||
({ testRenderer } = create({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
resolvers,
|
||||
initLocalState: (localRecord: RecordProxy) => {
|
||||
initLocalState: localRecord => {
|
||||
localRecord.setValue(assets[0].id, "assetID");
|
||||
localRecord.setValue(0, "authRevision");
|
||||
},
|
||||
});
|
||||
|
||||
const context: TalkContext = {
|
||||
relayEnvironment: environment,
|
||||
localeBundles: [createFluentBundle()],
|
||||
localStorage: createInMemoryStorage(),
|
||||
sessionStorage: createInMemoryStorage(),
|
||||
rest: new RestClient("http://localhost/api"),
|
||||
postMessage: new PostMessageService(),
|
||||
};
|
||||
|
||||
testRenderer = TestRenderer.create(
|
||||
<TalkContextProvider value={context}>
|
||||
<AppContainer />
|
||||
</TalkContextProvider>,
|
||||
{ createNodeMock }
|
||||
);
|
||||
}));
|
||||
});
|
||||
|
||||
it("renders comment stream", async () => {
|
||||
@@ -92,25 +66,26 @@ it("renders comment stream", async () => {
|
||||
});
|
||||
|
||||
it("post a comment", async () => {
|
||||
// Wait for loading.
|
||||
await timeout();
|
||||
testRenderer.root
|
||||
.findByProps({ inputId: "comments-postCommentForm-field" })
|
||||
.props.onChange({ html: "<strong>Hello world!</strong>" });
|
||||
|
||||
timekeeper.freeze(new Date("2018-07-06T18:24:00.002Z"));
|
||||
timekeeper.freeze(new Date("2018-07-06T18:24:00.000Z"));
|
||||
|
||||
testRenderer.root
|
||||
.findByProps({ id: "comments-postCommentForm-form" })
|
||||
.props.onSubmit();
|
||||
|
||||
timekeeper.reset();
|
||||
|
||||
// Test optimistic response.
|
||||
expect(testRenderer.toJSON()).toMatchSnapshot();
|
||||
|
||||
// Wait for loading.
|
||||
await timeout();
|
||||
|
||||
// Travel to the time where the "timeout" has executed.
|
||||
timekeeper.travel(new Date("2018-07-06T18:24:01.002Z"));
|
||||
|
||||
// Test after server response.
|
||||
expect(testRenderer.toJSON()).toMatchSnapshot();
|
||||
timekeeper.reset();
|
||||
});
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
import React from "react";
|
||||
import TestRenderer, { ReactTestRenderer } from "react-test-renderer";
|
||||
import { RecordProxy } from "relay-runtime";
|
||||
import { ReactTestRenderer } from "react-test-renderer";
|
||||
|
||||
import { 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 { createSinonStub } from "talk-framework/testHelpers";
|
||||
import AppContainer from "talk-stream/containers/AppContainer";
|
||||
|
||||
import createEnvironment from "./createEnvironment";
|
||||
import createFluentBundle from "./createFluentBundle";
|
||||
import createNodeMock from "./createNodeMock";
|
||||
import create from "./create";
|
||||
import { assetWithReplies } from "./fixtures";
|
||||
|
||||
let testRenderer: ReactTestRenderer;
|
||||
@@ -29,31 +20,14 @@ beforeEach(() => {
|
||||
},
|
||||
};
|
||||
|
||||
const environment = createEnvironment({
|
||||
({ testRenderer } = create({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
resolvers,
|
||||
initLocalState: (localRecord: RecordProxy) => {
|
||||
initLocalState: localRecord => {
|
||||
localRecord.setValue(assetWithReplies.id, "assetID");
|
||||
localRecord.setValue(0, "authRevision");
|
||||
},
|
||||
});
|
||||
|
||||
const context: TalkContext = {
|
||||
relayEnvironment: environment,
|
||||
localeBundles: [createFluentBundle()],
|
||||
localStorage: createInMemoryStorage(),
|
||||
sessionStorage: createInMemoryStorage(),
|
||||
rest: new RestClient("http://localhost/api"),
|
||||
postMessage: new PostMessageService(),
|
||||
};
|
||||
|
||||
testRenderer = TestRenderer.create(
|
||||
<TalkContextProvider value={context}>
|
||||
<AppContainer />
|
||||
</TalkContextProvider>,
|
||||
{ createNodeMock }
|
||||
);
|
||||
}));
|
||||
});
|
||||
|
||||
it("renders comment stream", async () => {
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
import React from "react";
|
||||
import TestRenderer, { ReactTestRenderer } from "react-test-renderer";
|
||||
import { RecordProxy } from "relay-runtime";
|
||||
import { ReactTestRenderer } from "react-test-renderer";
|
||||
|
||||
import { 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 { createSinonStub } from "talk-framework/testHelpers";
|
||||
import AppContainer from "talk-stream/containers/AppContainer";
|
||||
|
||||
import createEnvironment from "./createEnvironment";
|
||||
import createFluentBundle from "./createFluentBundle";
|
||||
import createNodeMock from "./createNodeMock";
|
||||
import create from "./create";
|
||||
import { assets } from "./fixtures";
|
||||
|
||||
let testRenderer: ReactTestRenderer;
|
||||
@@ -26,31 +17,14 @@ beforeEach(() => {
|
||||
},
|
||||
};
|
||||
|
||||
const environment = createEnvironment({
|
||||
({ testRenderer } = create({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
resolvers,
|
||||
initLocalState: (localRecord: RecordProxy) => {
|
||||
initLocalState: localRecord => {
|
||||
localRecord.setValue(assets[0].id, "assetID");
|
||||
localRecord.setValue(0, "authRevision");
|
||||
},
|
||||
});
|
||||
|
||||
const context: TalkContext = {
|
||||
relayEnvironment: environment,
|
||||
localeBundles: [createFluentBundle()],
|
||||
localStorage: createInMemoryStorage(),
|
||||
sessionStorage: createInMemoryStorage(),
|
||||
rest: new RestClient("http://localhost/api"),
|
||||
postMessage: new PostMessageService(),
|
||||
};
|
||||
|
||||
testRenderer = TestRenderer.create(
|
||||
<TalkContextProvider value={context}>
|
||||
<AppContainer />
|
||||
</TalkContextProvider>,
|
||||
{ createNodeMock }
|
||||
);
|
||||
}));
|
||||
});
|
||||
|
||||
it("renders comment stream", async () => {
|
||||
|
||||
@@ -1,19 +1,10 @@
|
||||
import React from "react";
|
||||
import TestRenderer, { ReactTestRenderer } from "react-test-renderer";
|
||||
import { RecordProxy } from "relay-runtime";
|
||||
import { ReactTestRenderer } from "react-test-renderer";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { 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 { createSinonStub } from "talk-framework/testHelpers";
|
||||
import AppContainer from "talk-stream/containers/AppContainer";
|
||||
|
||||
import createEnvironment from "./createEnvironment";
|
||||
import createFluentBundle from "./createFluentBundle";
|
||||
import createNodeMock from "./createNodeMock";
|
||||
import create from "./create";
|
||||
import { assets, comments } from "./fixtures";
|
||||
|
||||
let testRenderer: ReactTestRenderer;
|
||||
@@ -85,31 +76,14 @@ beforeEach(() => {
|
||||
},
|
||||
};
|
||||
|
||||
const environment = createEnvironment({
|
||||
({ testRenderer } = create({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
resolvers,
|
||||
initLocalState: (localRecord: RecordProxy) => {
|
||||
initLocalState: localRecord => {
|
||||
localRecord.setValue(assetStub.id, "assetID");
|
||||
localRecord.setValue(0, "authRevision");
|
||||
},
|
||||
});
|
||||
|
||||
const context: TalkContext = {
|
||||
relayEnvironment: environment,
|
||||
localeBundles: [createFluentBundle()],
|
||||
localStorage: createInMemoryStorage(),
|
||||
sessionStorage: createInMemoryStorage(),
|
||||
rest: new RestClient("http://localhost/api"),
|
||||
postMessage: new PostMessageService(),
|
||||
};
|
||||
|
||||
testRenderer = TestRenderer.create(
|
||||
<TalkContextProvider value={context}>
|
||||
<AppContainer />
|
||||
</TalkContextProvider>,
|
||||
{ createNodeMock }
|
||||
);
|
||||
}));
|
||||
});
|
||||
|
||||
it("renders comment stream", async () => {
|
||||
|
||||
Reference in New Issue
Block a user