[next] Tests for new story filter and combo box (#2288)

* test: add tests for changes in stories page

* test: search box and moderate specific story

* fix: remaining snapshot

* fix: test
This commit is contained in:
Kiwi
2019-04-27 01:04:16 +02:00
committed by GitHub
parent b1efdad981
commit 60c8f9a0d5
13 changed files with 764 additions and 159 deletions
@@ -1,9 +1,11 @@
import { BrowserProtocol, queryMiddleware } from "farce";
import { createFarceRouter, createRender } from "found";
import { createFarceRouter, ElementsRenderer } from "found";
import { Resolver } from "found-relay";
import React, { StatelessComponent } from "react";
import TransitionControl from "talk-framework/testHelpers/TransitionControl";
import { TalkContextConsumer } from "talk-framework/lib/bootstrap/TalkContext";
import routeConfig from "../routeConfig";
import NotFound from "../routes/NotFound";
@@ -11,11 +13,16 @@ const Router = createFarceRouter({
historyProtocol: new BrowserProtocol(),
historyMiddlewares: [queryMiddleware],
routeConfig,
render: createRender({
renderError: ({ error }) => (
<div>{error.status === 404 ? <NotFound /> : "Error"}</div>
),
}),
renderReady: ({ elements }) => (
<>
<ElementsRenderer elements={elements} />
{// this enables router transition control when writing tests.
process.env.NODE_ENV === "test" && <TransitionControl />}
</>
),
renderError: ({ error }) => (
<div>{error.status === 404 ? <NotFound /> : "Error"}</div>
),
});
const EntryContainer: StatelessComponent = () => (
@@ -27,7 +27,7 @@ const Moderate: StatelessComponent<Props> = ({
}) => (
<div data-testid="moderate-container">
<ModerateSearchBarContainer story={story} allStories={allStories} />
<SubBar data-testid="moderate-subBar-container">
<SubBar data-testid="moderate-tabBar-container">
<ModerateNavigationContainer
moderationQueues={moderationQueues}
story={story}
@@ -9,7 +9,7 @@ exports[`renders correctly 1`] = `
story={Object {}}
/>
<withPropsOnChange(SubBar)
data-testid="moderate-subBar-container"
data-testid="moderate-tabBar-container"
>
<Relay(ModerateNavigationContainer)
moderationQueues={Object {}}
@@ -1,131 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`navigation bar renders navigation bar (empty queues) 1`] = `
<div
className="SubBar-root"
data-testid="moderate-subBar-container"
>
<div
className="Flex-root SubBar-container Flex-flex Flex-justifyCenter Flex-alignCenter"
>
<nav
className="Navigation-root"
>
<ul
className="Navigation-ul"
>
<li
className="NavigationItem-root"
>
<a
className="NavigationItem-anchor NavigationItem-active"
href="/admin/moderate/reported"
onClick={[Function]}
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
flag
</span>
<span>
reported
</span>
<span
className="Counter-root Counter-colorInherit"
data-testid="moderate-navigation-reported-count"
>
<span
className="Counter-text"
>
0
</span>
</span>
</a>
</li>
<li
className="NavigationItem-root"
>
<a
className="NavigationItem-anchor"
href="/admin/moderate/pending"
onClick={[Function]}
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
access_time
</span>
<span>
Pending
</span>
<span
className="Counter-root Counter-colorInherit"
data-testid="moderate-navigation-pending-count"
>
<span
className="Counter-text"
>
0
</span>
</span>
</a>
</li>
<li
className="NavigationItem-root"
>
<a
className="NavigationItem-anchor"
href="/admin/moderate/unmoderated"
onClick={[Function]}
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
forum
</span>
<span>
unmoderated
</span>
<span
className="Counter-root Counter-colorInherit"
data-testid="moderate-navigation-unmoderated-count"
>
<span
className="Counter-text"
>
0
</span>
</span>
</a>
</li>
<li
className="NavigationItem-root"
>
<a
className="NavigationItem-anchor"
href="/admin/moderate/rejected"
onClick={[Function]}
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
cancel
</span>
<span>
rejected
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
`;
exports[`rejected queue accepts comment in rejected queue: count should be 1 1`] = `
<span
className="Counter-root Counter-colorInherit"
@@ -1524,6 +1398,260 @@ exports[`reported queue renders reported queue with comments and load more 1`] =
</div>
`;
exports[`search bar all stories active search with no results 1`] = `
<div
aria-expanded={true}
aria-haspopup="listbox"
aria-label="Search or jump to story"
aria-owns="moderate-searchBar-listBox"
className="SubBar-root Bar-root"
data-testid="moderate-searchBar-container"
role="combobox"
>
<div
className="Flex-root SubBar-container Flex-flex Flex-justifyCenter Flex-alignCenter"
>
<div
className="Backdrop-root Backdrop-active Bar-bumpZIndex"
/>
<form
aria-label="Stories"
className="Bar-bumpZIndex"
onMouseDown={[Function]}
onSubmit={[Function]}
role="search"
>
<div
className="Popover-root"
>
<div>
<div
className="Flex-root Field-root Flex-flex Flex-alignStretch"
>
<div
className="Flex-root Field-begin Flex-flex Flex-alignCenter"
>
<span
aria-hidden="true"
className="Icon-root Icon-md Field-searchIcon"
>
search
</span>
<div
className="Field-beginStories"
>
Stories:
</div>
</div>
<input
aria-autocomplete="list"
aria-controls="moderate-searchBar-listBox"
aria-label="Search or jump to story..."
autoComplete="off"
className="Field-input"
name="search"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
placeholder="Use quotation marks around each search term (e.g. “team”, “St. Louis”)"
spellCheck={false}
value=""
/>
<div
className="Flex-root Field-end Flex-flex Flex-alignCenter"
>
<button
className="BaseButton-root Field-searchButton"
disabled={true}
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
Search
</button>
</div>
</div>
</div>
<div
aria-hidden={false}
aria-labelledby="moderate-searchBar-popover-ariainfo"
id="moderate-searchBar-popover"
role="popup"
>
<div
className="Popover-popover Bar-popover Popover-bottom"
style={
Object {
"left": 0,
"opacity": 0,
"pointerEvents": "none",
"position": "absolute",
"top": 0,
}
}
>
<ul
className="Bar-listBox"
id="moderate-searchBar-listBox"
role="listbox"
>
<ul
aria-labelledby="moderate-searchBar-context-title"
className="Group-root"
id="moderate-searchBar-context"
role="group"
>
<li
className="Group-title"
id="moderate-searchBar-context-title"
>
Currently moderating
</li>
<li
aria-selected={false}
className="Option-root"
id="moderate-searchBar-listBoxOption-0"
onClick={[Function]}
role="option"
>
<a
className="Option-link"
href="/admin/moderate"
tabIndex={-1}
>
<div
className="Option-container"
>
<div
className="Option-title"
>
<div
className="AriaInfo-root"
>
Go to
</div>
<span>
All stories
</span>
</div>
<div
className="Option-details"
/>
</div>
</a>
</li>
</ul>
</ul>
</div>
</div>
</div>
</form>
</div>
</div>
`;
exports[`search bar all stories active search with too many results 1`] = `
<li
aria-selected={false}
className="SeeAllOption-root"
id="moderate-searchBar-listBoxOption-3"
onClick={[Function]}
role="option"
>
<a
className="SeeAllOption-link"
href="/admin/stories?q=InterestingStory"
tabIndex={-1}
>
<span>
See all results
</span>
<span
aria-hidden="true"
className="Icon-root Icon-sm SeeAllOption-icon"
>
arrow_forward
</span>
</a>
</li>
`;
exports[`search bar all stories renders search bar 1`] = `
<div
aria-expanded={false}
aria-haspopup="listbox"
aria-label="Search or jump to story"
aria-owns="moderate-searchBar-listBox"
className="SubBar-root Bar-root"
data-testid="moderate-searchBar-container"
role="combobox"
>
<div
className="Flex-root SubBar-container Flex-flex Flex-justifyCenter Flex-alignCenter"
>
<div
className="Backdrop-root Bar-bumpZIndex"
/>
<form
aria-label="Stories"
className="Bar-bumpZIndex"
onMouseDown={[Function]}
onSubmit={[Function]}
role="search"
>
<div
className="Popover-root"
>
<div>
<div
className="Flex-root Field-root Flex-flex Flex-alignStretch"
>
<div
className="Flex-root Field-begin Flex-flex Flex-alignCenter"
>
<span
aria-hidden="true"
className="Icon-root Icon-md Field-searchIcon"
>
search
</span>
</div>
<input
aria-autocomplete="list"
aria-controls="moderate-searchBar-listBox"
aria-label="Search or jump to story..."
autoComplete="off"
className="Field-input Field-inputWithTitle"
name="search"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
placeholder="All stories"
spellCheck={false}
value=""
/>
<div
className="Flex-root Field-end Flex-flex Flex-alignCenter"
/>
</div>
</div>
<div
aria-hidden={true}
aria-labelledby="moderate-searchBar-popover-ariainfo"
id="moderate-searchBar-popover"
role="popup"
/>
</div>
</form>
</div>
</div>
`;
exports[`single comment view accepts single comment 1`] = `
<div
className="Card-root ModerateCard-root"
@@ -1991,3 +2119,129 @@ exports[`single comment view renders single comment view 1`] = `
</div>
</div>
`;
exports[`tab bar renders tab bar (empty queues) 1`] = `
<div
className="SubBar-root"
data-testid="moderate-tabBar-container"
>
<div
className="Flex-root SubBar-container Flex-flex Flex-justifyCenter Flex-alignCenter"
>
<nav
className="Navigation-root"
>
<ul
className="Navigation-ul"
>
<li
className="NavigationItem-root"
>
<a
className="NavigationItem-anchor NavigationItem-active"
href="/admin/moderate/reported"
onClick={[Function]}
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
flag
</span>
<span>
reported
</span>
<span
className="Counter-root Counter-colorInherit"
data-testid="moderate-navigation-reported-count"
>
<span
className="Counter-text"
>
0
</span>
</span>
</a>
</li>
<li
className="NavigationItem-root"
>
<a
className="NavigationItem-anchor"
href="/admin/moderate/pending"
onClick={[Function]}
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
access_time
</span>
<span>
Pending
</span>
<span
className="Counter-root Counter-colorInherit"
data-testid="moderate-navigation-pending-count"
>
<span
className="Counter-text"
>
0
</span>
</span>
</a>
</li>
<li
className="NavigationItem-root"
>
<a
className="NavigationItem-anchor"
href="/admin/moderate/unmoderated"
onClick={[Function]}
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
forum
</span>
<span>
unmoderated
</span>
<span
className="Counter-root Counter-colorInherit"
data-testid="moderate-navigation-unmoderated-count"
>
<span
className="Counter-text"
>
0
</span>
</span>
</a>
</li>
<li
className="NavigationItem-root"
>
<a
className="NavigationItem-anchor"
href="/admin/moderate/rejected"
onClick={[Function]}
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
cancel
</span>
<span>
rejected
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
`;
@@ -12,20 +12,27 @@ import {
createQueryResolverStub,
createResolversStub,
CreateTestRendererParams,
findParentWithType,
replaceHistoryLocation,
toJSON,
wait,
waitForElement,
waitUntilThrow,
within,
} from "talk-framework/testHelpers";
import { noop } from "lodash";
import { ReactTestRenderer } from "react-test-renderer";
import create from "../create";
import {
emptyModerationQueues,
emptyRejectedComments,
emptyStories,
rejectedComments,
reportedComments,
settings,
stories,
storyConnection,
users,
} from "../fixtures";
@@ -38,7 +45,7 @@ beforeEach(async () => {
async function createTestRenderer(
params: CreateTestRendererParams<GQLResolver> = {}
) {
const { testRenderer } = create({
const { testRenderer, context } = create({
...params,
resolvers: pureMerge(
createResolversStub<GQLResolver>({
@@ -58,21 +65,236 @@ async function createTestRenderer(
}
},
});
return testRenderer;
return { testRenderer, context };
}
describe("navigation bar", () => {
it("renders navigation bar (empty queues)", async () => {
const testRenderer = await createTestRenderer();
describe("search bar", () => {
const openSearchBar = async (testRenderer: ReactTestRenderer) => {
const searchBar = await waitForElement(() =>
within(testRenderer.root).getByTestID("moderate-searchBar-container")
);
const textField = within(searchBar).getByLabelText(
"Search or jump to story..."
);
const form = findParentWithType(textField, "form")!;
textField.props.onFocus({});
return { searchBar, textField, form };
};
describe("all stories", () => {
it("renders search bar", async () => {
const { testRenderer } = await createTestRenderer();
const searchBar = await waitForElement(() =>
within(testRenderer.root).getByTestID("moderate-searchBar-container")
);
expect(within(searchBar).toJSON()).toMatchSnapshot();
});
describe("active", () => {
it("search with no results", async () => {
const query = "InterestingStory";
const { testRenderer } = await createTestRenderer({
resolvers: createResolversStub<GQLResolver>({
Query: {
stories: ({ variables }) => {
expectAndFail(variables.query).toBe(query);
return emptyStories;
},
},
}),
});
const { searchBar, textField, form } = await openSearchBar(
testRenderer
);
expect(within(searchBar).toJSON()).toMatchSnapshot();
// Search for sth.
textField.props.onChange(query);
form.props.onSubmit();
// Ensure no results message is shown.
await wait(() =>
within(searchBar).getByText("No results", { exact: false })
);
// Blurring should close the listbox.
textField.props.onBlur({});
expect(within(searchBar).queryByText("No results")).toBeNull();
});
it("search with actual results", async () => {
const query = "InterestingStory";
const {
testRenderer,
context: { transitionControl },
} = await createTestRenderer({
resolvers: createResolversStub<GQLResolver>({
Query: {
stories: ({ variables }) => {
expectAndFail(variables.query).toBe(query);
return storyConnection;
},
},
}),
});
transitionControl.allowTransition = false;
const { searchBar, textField, form } = await openSearchBar(
testRenderer
);
// Search for sth.
textField.props.onChange(query);
form.props.onSubmit();
const story = storyConnection.edges[0].node;
// Find the story in the search results.
const storyOption = findParentWithType(
await waitForElement(() =>
within(searchBar).getByText(story.metadata!.title!, {
exact: false,
})
),
"li"
)!;
// Go to story.
storyOption.props.onClick({ button: 0, preventDefault: noop });
// Expect a routing request was made to the right url.
expect(transitionControl.history[0].pathname).toBe(
`/admin/moderate/${story.id}`
);
});
it("search with too many results", async () => {
const query = "InterestingStory";
const {
testRenderer,
context: { transitionControl },
} = await createTestRenderer({
resolvers: createResolversStub<GQLResolver>({
Query: {
stories: ({ variables }) => {
expectAndFail(variables.query).toBe(query);
return pureMerge<typeof storyConnection>(storyConnection, {
pageInfo: { hasNextPage: true },
});
},
},
}),
});
transitionControl.allowTransition = false;
const { searchBar, textField, form } = await openSearchBar(
testRenderer
);
// Search for sth.
textField.props.onChange(query);
form.props.onSubmit();
// Find see all options in the search results.
const seeAllOption = findParentWithType(
await waitForElement(() =>
within(searchBar).getByText("See all results", { exact: false })
),
"li"
)!;
expect(within(seeAllOption).toJSON()).toMatchSnapshot();
// Go to story.
seeAllOption.props.onClick({ button: 0, preventDefault: noop });
// Expect a routing request was made to the right url.
expect(transitionControl.history[0].pathname).toBe("/admin/stories");
expect(transitionControl.history[0].search).toBe(`?q=${query}`);
});
});
});
describe("specified story", () => {
beforeEach(() => {
replaceHistoryLocation(
`http://localhost/admin/moderate/${stories[0].id}`
);
});
it("renders search bar", async () => {
const { testRenderer } = await createTestRenderer({
resolvers: createResolversStub<GQLResolver>({
Query: {
story: () => stories[0],
},
}),
});
const searchBar = await waitForElement(() =>
within(testRenderer.root).getByTestID("moderate-searchBar-container")
);
const textField = within(searchBar).getByLabelText(
"Search or jump to story..."
);
expect(textField.props.placeholder).toBe(stories[0].metadata!.title);
});
it("shows moderate all option", async () => {
const {
testRenderer,
context: { transitionControl },
} = await createTestRenderer({
resolvers: createResolversStub<GQLResolver>({
Query: {
story: () => stories[0],
},
}),
});
transitionControl.allowTransition = false;
const { searchBar } = await openSearchBar(testRenderer);
// Find see all options in the search results.
const moderateAllOptions = findParentWithType(
await waitForElement(() =>
within(searchBar).getByText("Moderate all", { exact: false })
),
"li"
)!;
// Activate moderate all.
moderateAllOptions.props.onClick({ button: 0, preventDefault: noop });
// Expect a routing request was made to the right url.
expect(transitionControl.history[0].pathname).toBe("/admin/moderate");
});
});
});
describe("tab bar", () => {
it("renders tab bar (empty queues)", async () => {
const { testRenderer } = await createTestRenderer();
const { getByTestID } = within(testRenderer.root);
await waitForElement(() => getByTestID("moderate-container"));
expect(toJSON(getByTestID("moderate-subBar-container"))).toMatchSnapshot();
expect(toJSON(getByTestID("moderate-tabBar-container"))).toMatchSnapshot();
});
});
describe("moderating specific story", () => {
it("passes storyID to the endpoints", async () => {
replaceHistoryLocation(`http://localhost/admin/moderate/${stories[0].id}`);
await createTestRenderer({
resolvers: createResolversStub<GQLResolver>({
Query: {
moderationQueues: ({ variables }) => {
expectAndFail(variables.storyID).toBe(stories[0].id);
return emptyModerationQueues;
},
comments: ({ variables }) => {
expectAndFail(variables.storyID).toBe(stories[0].id);
return emptyRejectedComments;
},
},
}),
});
});
});
describe("reported queue", () => {
it("renders empty reported queue", async () => {
const testRenderer = await createTestRenderer();
const { testRenderer } = await createTestRenderer();
const { getByTestID } = within(testRenderer.root);
await waitForElement(() => getByTestID("moderate-container"));
@@ -81,14 +303,14 @@ describe("reported queue", () => {
it("renders empty pending queue", async () => {
replaceHistoryLocation("http://localhost/admin/moderate/pending");
const testRenderer = await createTestRenderer();
const { testRenderer } = await createTestRenderer();
const { getByText } = within(testRenderer.root);
await waitForElement(() => getByText("no more pending", { exact: false }));
});
it("renders empty unmoderated queue", async () => {
replaceHistoryLocation("http://localhost/admin/moderate/unmoderated");
const testRenderer = await createTestRenderer();
const { testRenderer } = await createTestRenderer();
const { getByText } = within(testRenderer.root);
await waitForElement(() =>
getByText("comments have been moderated", { exact: false })
@@ -97,7 +319,7 @@ describe("reported queue", () => {
it("renders empty rejected queue", async () => {
replaceHistoryLocation("http://localhost/admin/moderate/rejected");
const testRenderer = await createTestRenderer();
const { testRenderer } = await createTestRenderer();
const { getByText } = within(testRenderer.root);
await waitForElement(() =>
getByText("no rejected comments", { exact: false })
@@ -105,7 +327,7 @@ describe("reported queue", () => {
});
it("renders reported queue with comments", async () => {
const testRenderer = await createTestRenderer({
const { testRenderer } = await createTestRenderer({
resolvers: createResolversStub<GQLResolver>({
Query: {
moderationQueues: () =>
@@ -191,7 +413,7 @@ describe("reported queue", () => {
},
});
const testRenderer = await createTestRenderer({
const { testRenderer } = await createTestRenderer({
resolvers: createResolversStub<GQLResolver>({
Query: {
moderationQueues: () => moderationQueuesStub,
@@ -278,7 +500,7 @@ describe("reported queue", () => {
},
});
const testRenderer = await createTestRenderer({
const { testRenderer } = await createTestRenderer({
resolvers: createResolversStub<GQLResolver>({
Query: {
moderationQueues: () => moderationQueuesStub,
@@ -333,7 +555,7 @@ describe("reported queue", () => {
};
});
const testRenderer = await createTestRenderer({
const { testRenderer } = await createTestRenderer({
resolvers: createResolversStub<GQLResolver>({
Query: {
moderationQueues: () =>
@@ -400,7 +622,7 @@ describe("rejected queue", () => {
});
it("renders rejected queue with comments", async () => {
const testRenderer = await createTestRenderer({
const { testRenderer } = await createTestRenderer({
resolvers: createResolversStub<GQLResolver>({
Query: {
comments: ({ variables }) => {
@@ -435,7 +657,7 @@ describe("rejected queue", () => {
});
it("renders rejected queue with comments and load more", async () => {
const testRenderer = await createTestRenderer({
const { testRenderer } = await createTestRenderer({
resolvers: createResolversStub<GQLResolver>({
Query: {
comments: ({ variables, callCount }) => {
@@ -540,7 +762,7 @@ describe("rejected queue", () => {
};
});
const testRenderer = await createTestRenderer({
const { testRenderer } = await createTestRenderer({
resolvers: createResolversStub<GQLResolver>({
Query: {
comments: ({ variables }) => {
@@ -613,7 +835,7 @@ describe("single comment view", () => {
});
it("renders single comment view", async () => {
const testRenderer = await createTestRenderer({
const { testRenderer } = await createTestRenderer({
resolvers: {
Query: {
comment: commentStub,
@@ -644,7 +866,7 @@ describe("single comment view", () => {
};
});
const testRenderer = await createTestRenderer({
const { testRenderer } = await createTestRenderer({
resolvers: {
Query: {
comment: commentStub,
@@ -683,7 +905,7 @@ describe("single comment view", () => {
};
});
const testRenderer = await createTestRenderer({
const { testRenderer } = await createTestRenderer({
resolvers: {
Query: {
comment: commentStub,
@@ -1,3 +1,4 @@
import { noop } from "lodash";
import TestRenderer from "react-test-renderer";
import { pureMerge } from "talk-common/utils";
@@ -36,7 +37,7 @@ beforeEach(async () => {
async function createTestRenderer(
params: CreateTestRendererParams<GQLResolver> = {}
) {
const { testRenderer } = create({
const { testRenderer, context } = create({
...params,
resolvers: pureMerge(
createResolversStub<GQLResolver>({
@@ -62,7 +63,7 @@ async function createTestRenderer(
const container = await waitForElement(() =>
within(testRenderer.root).getByTestID("stories-container")
);
return { testRenderer, container };
return { testRenderer, container, context };
}
it("renders stories", async () => {
@@ -81,6 +82,26 @@ it("renders empty stories", async () => {
expect(within(container).toJSON()).toMatchSnapshot();
});
it("goes to moderation when clicking on title", async () => {
const {
container,
context: { transitionControl },
} = await createTestRenderer();
// Prevent router transitions.
transitionControl.allowTransition = false;
const story = storyConnection.edges[0].node;
within(container)
.getByText(story.metadata!.title!)
.props.onClick({ button: 0, preventDefault: noop });
// Expect a routing request was made to the right url.
expect(transitionControl.history[0].pathname).toBe(
`/admin/moderate/${story.id}`
);
});
it("filter by status", async () => {
const { container } = await createTestRenderer({
resolvers: createResolversStub<GQLResolver>({
@@ -268,3 +289,24 @@ it("filter by search", async () => {
within(container).getByText("could not find any", { exact: false })
);
});
it("use searchFilter from url", async () => {
const searchFilter = "CandyMountain";
replaceHistoryLocation(`http://localhost/admin/stories?q=${searchFilter}`);
const { container } = await createTestRenderer({
resolvers: createResolversStub<GQLResolver>({
Query: {
stories: ({ variables }) => {
expectAndFail(variables.query).toBe(searchFilter);
return emptyStories;
},
},
}),
});
const searchField = within(container).getByLabelText(
"Search by story title",
{ exact: false }
);
expect(searchField.props.value).toBe(searchFilter);
});
@@ -11,6 +11,7 @@ import { BrowserInfo } from "talk-framework/lib/browserInfo";
import { PostMessageService } from "talk-framework/lib/postMessage";
import { RestClient } from "talk-framework/lib/rest";
import { PromisifiedStorage } from "talk-framework/lib/storage";
import { TransitionControlData } from "talk-framework/testHelpers";
import { UIContext } from "talk-ui/components";
import { ClickFarAwayRegister } from "talk-ui/components/ClickOutside";
@@ -62,6 +63,9 @@ export interface TalkContext {
/** Clear session data. */
clearSession: () => Promise<void>;
/** Controls router transitions (for tests) */
transitionControl?: TransitionControlData;
}
export const TalkReactContext = React.createContext<TalkContext>({} as any);
@@ -0,0 +1,43 @@
import { Location, Match, Router, withRouter } from "found";
import React, { useEffect } from "react";
import { withContext } from "talk-framework/lib/bootstrap";
interface Props {
/** router is injected by `withRouter` HOC */
router: Router;
/** match is injected by `withRouter` HOC */
match: Match;
/** transitionControl is injected by `withContext` HOC */
transitionControl: TransitionControlData | undefined;
}
/**
* TransitionControlData allows controlling router transition.
*/
export interface TransitionControlData {
/** allowTransition if set to false, will prevent router transitions from happening. */
allowTransition: boolean;
/** history contains all records of router transition requests. */
history: Location[];
}
const TransitionControl: React.FunctionComponent<Props> = props => {
useEffect(() => {
return props.router.addTransitionHook(location => {
if (props.transitionControl) {
props.transitionControl.history.push(location);
if (!props.transitionControl.allowTransition) {
return false;
}
}
return;
});
}, []);
return null;
};
const enhanced = withContext(({ transitionControl }) => ({
transitionControl,
}))(withRouter(TransitionControl));
export default enhanced;
@@ -6,6 +6,7 @@ import React from "react";
import TestRenderer, { ReactTestRenderer } from "react-test-renderer";
import { Environment, RecordProxy, RecordSourceProxy } from "relay-runtime";
import { RequireProperty } from "talk-common/types";
import { TalkContext, TalkContextProvider } from "talk-framework/lib/bootstrap";
import { PostMessageService } from "talk-framework/lib/postMessage";
import { RestClient } from "talk-framework/lib/rest";
@@ -77,7 +78,7 @@ export default function createTestRenderer<
},
});
const context: TalkContext = {
const context: RequireProperty<TalkContext, "transitionControl"> = {
relayEnvironment: environment,
locales: ["en-US"],
localeBundles: [
@@ -94,6 +95,10 @@ export default function createTestRenderer<
uuidGenerator: createUUIDGenerator(),
eventEmitter: new EventEmitter2({ wildcard: true, maxListeners: 20 }),
clearSession: () => Promise.resolve(),
transitionControl: {
allowTransition: true,
history: [],
},
};
let testRenderer: ReactTestRenderer;
@@ -36,3 +36,7 @@ export {
CreateTestRendererParams,
} from "./createTestRenderer";
export { default as createResolversStub } from "./createResolversStub";
export {
TransitionControlData,
default as TransitionControl,
} from "./TransitionControl";
+17
View File
@@ -9,3 +9,20 @@ jest.mock("react-dom", () => ({
...require.requireActual("react-dom"),
createPortal: (node: any) => node,
}));
jest.mock("popper.js", () => {
const PopperJS = require.requireActual("popper.js");
return class Popper {
public static placements = PopperJS.placements;
constructor() {
return {
// tslint:disable-next-line:no-empty
destroy: () => {},
// tslint:disable-next-line:no-empty
scheduleUpdate: () => {},
};
}
};
});
+1 -1
View File
@@ -7,7 +7,7 @@ export type Overwrite<T, U> = Pick<T, Diff<keyof T, keyof U>> & U;
export type Sub<T, U> = Pick<T, Diff<keyof T, keyof U>>;
export type RequireProperty<T, P extends keyof T> = Omit<Partial<T>, P> &
export type RequireProperty<T, P extends keyof T> = Omit<T, P> &
Required<Pick<T, P>>;
/**
+7
View File
@@ -0,0 +1,7 @@
import { FunctionComponent } from "react";
declare module "found" {
const ElementsRenderer: FunctionComponent<{
elements: ReactElementOrGroup[];
}>;
}