mirror of
https://github.com/wassname/talk.git
synced 2026-06-27 18:07:26 +08:00
[CORL-443] Add user drawer to the community section of the moderation area (#2401)
* Move the user history drawer into shared location that all admin routes access CORL-443 * Move the moderate card to the shared components for admin CORL-443 * Add a user drawer to the community area of the admin section CORL-443 * Touch up missing tabs in UserRow.css CORL-443 * Create unit tests around the user drawer CORL-443 * Move toxicity label to new shared component location to fix rebase CORL-443 * Update comment fixture generation to include reason metadata, action counts CORL-443 * Rename userDrawerID to userDrawerUserID CORL-443 * Clean up imports on user drawer unit tests CORL-443 * Add coral-test to the jest config paths CORL-443 * Add todo around creating predictable date times for test fixtures CORL-443 * Move testRenderer construction outside of act() operations CORL-443
This commit is contained in:
@@ -33,6 +33,7 @@ module.exports = {
|
||||
"^coral-stream/(.*)$": "<rootDir>/src/core/client/stream/$1",
|
||||
"^coral-framework/(.*)$": "<rootDir>/src/core/client/framework/$1",
|
||||
"^coral-common/(.*)$": "<rootDir>/src/core/common/$1",
|
||||
"^coral-test/(.*)$": "<rootDir>/src/core/client/test/$1",
|
||||
},
|
||||
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node", "ftl"],
|
||||
snapshotSerializers: ["enzyme-to-json/serializer"],
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.category {
|
||||
color: var(--palette-error-darkest);
|
||||
}
|
||||
+2
-3
@@ -6,15 +6,14 @@ import { Localized } from "fluent-react/compat";
|
||||
import React, { FunctionComponent, useCallback } from "react";
|
||||
import { graphql, RelayPaginationProp } from "react-relay";
|
||||
|
||||
import { Button, CallOut, Typography } from "coral-ui/components/";
|
||||
import { ModerateCardContainer } from "coral-admin/components/ModerateCard";
|
||||
import { Button, CallOut, Typography } from "coral-ui/components";
|
||||
|
||||
import { UserHistoryAllComments_settings } from "coral-admin/__generated__/UserHistoryAllComments_settings.graphql";
|
||||
import { UserHistoryAllComments_user } from "coral-admin/__generated__/UserHistoryAllComments_user.graphql";
|
||||
import { UserHistoryAllComments_viewer } from "coral-admin/__generated__/UserHistoryAllComments_viewer.graphql";
|
||||
import { UserHistoryAllCommentsPaginationQueryVariables } from "coral-admin/__generated__/UserHistoryAllCommentsPaginationQuery.graphql";
|
||||
|
||||
import { ModerateCardContainer } from "../ModerateCard";
|
||||
|
||||
import styles from "./UserHistoryAllComments.css";
|
||||
|
||||
interface Props {
|
||||
+1
-2
@@ -6,6 +6,7 @@ import { Localized } from "fluent-react/compat";
|
||||
import React, { FunctionComponent, useCallback } from "react";
|
||||
import { graphql, RelayPaginationProp } from "react-relay";
|
||||
|
||||
import { ModerateCardContainer } from "coral-admin/components/ModerateCard";
|
||||
import { Button, CallOut, Typography } from "coral-ui/components";
|
||||
|
||||
import { UserHistoryRejectedComments_settings } from "coral-admin/__generated__/UserHistoryRejectedComments_settings.graphql";
|
||||
@@ -13,8 +14,6 @@ import { UserHistoryRejectedComments_user } from "coral-admin/__generated__/User
|
||||
import { UserHistoryRejectedComments_viewer } from "coral-admin/__generated__/UserHistoryRejectedComments_viewer.graphql";
|
||||
import { UserHistoryRejectedCommentsPaginationQueryVariables } from "coral-admin/__generated__/UserHistoryRejectedCommentsPaginationQuery.graphql";
|
||||
|
||||
import { ModerateCardContainer } from "../ModerateCard";
|
||||
|
||||
import styles from "./UserHistoryRejectedComments.css";
|
||||
|
||||
interface Props {
|
||||
@@ -21,3 +21,11 @@
|
||||
.statusColumn {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.usernameButton {
|
||||
padding: 0px;
|
||||
font-family: var(--font-family-sans-serif);
|
||||
font-weight: var(--font-weight-regular);
|
||||
font-size: calc(14rem / var(--rem-base));
|
||||
line-height: calc(14em / 14);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { FunctionComponent } from "react";
|
||||
import React, { FunctionComponent, useCallback } from "react";
|
||||
|
||||
import NotAvailable from "coral-admin/components/NotAvailable";
|
||||
import { PropTypesOf } from "coral-framework/types";
|
||||
import { TableCell, TableRow, TextLink } from "coral-ui/components";
|
||||
import { Button, TableCell, TableRow, TextLink } from "coral-ui/components";
|
||||
|
||||
import UserRole from "./UserRole";
|
||||
import UserStatus from "./UserStatus";
|
||||
@@ -17,28 +17,47 @@ interface Props {
|
||||
user: PropTypesOf<typeof UserRole>["user"] &
|
||||
PropTypesOf<typeof UserStatus>["user"];
|
||||
viewer: PropTypesOf<typeof UserRole>["viewer"];
|
||||
onUsernameClicked?: (userID: string) => void;
|
||||
}
|
||||
|
||||
const UserRow: FunctionComponent<Props> = props => (
|
||||
<TableRow>
|
||||
<TableCell className={styles.usernameColumn}>
|
||||
{props.username || <NotAvailable />}
|
||||
</TableCell>
|
||||
<TableCell className={styles.emailColumn}>
|
||||
{<TextLink href={`mailto:${props.email}`}>{props.email}</TextLink> || (
|
||||
<NotAvailable />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className={styles.memberSinceColumn}>
|
||||
{props.memberSince}
|
||||
</TableCell>
|
||||
<TableCell className={styles.roleColumn}>
|
||||
<UserRole user={props.user} viewer={props.viewer} />
|
||||
</TableCell>
|
||||
<TableCell className={styles.statusColumn}>
|
||||
<UserStatus user={props.user} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
const UserRow: FunctionComponent<Props> = ({
|
||||
userID,
|
||||
username,
|
||||
email,
|
||||
memberSince,
|
||||
user,
|
||||
viewer,
|
||||
onUsernameClicked,
|
||||
}) => {
|
||||
const usernameClicked = useCallback(() => {
|
||||
if (!onUsernameClicked) {
|
||||
return;
|
||||
}
|
||||
|
||||
onUsernameClicked(userID);
|
||||
}, [userID, onUsernameClicked]);
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell className={styles.usernameColumn}>
|
||||
<Button onClick={usernameClicked} className={styles.usernameButton}>
|
||||
{username || <NotAvailable />}
|
||||
</Button>
|
||||
</TableCell>
|
||||
<TableCell className={styles.emailColumn}>
|
||||
{<TextLink href={`mailto:${email}`}>{email}</TextLink> || (
|
||||
<NotAvailable />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className={styles.memberSinceColumn}>{memberSince}</TableCell>
|
||||
<TableCell className={styles.roleColumn}>
|
||||
<UserRole user={user} viewer={viewer} />
|
||||
</TableCell>
|
||||
<TableCell className={styles.statusColumn}>
|
||||
<UserStatus user={user} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserRow;
|
||||
|
||||
@@ -11,6 +11,7 @@ import UserRow from "./UserRow";
|
||||
interface Props {
|
||||
user: UserData;
|
||||
viewer: ViewerData;
|
||||
onUsernameClicked?: (userID: string) => void;
|
||||
}
|
||||
|
||||
const UserRowContainer: FunctionComponent<Props> = props => {
|
||||
@@ -27,6 +28,7 @@ const UserRowContainer: FunctionComponent<Props> = props => {
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
}).format(new Date(props.user.createdAt))}
|
||||
onUsernameClicked={props.onUsernameClicked}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { FunctionComponent } from "react";
|
||||
import React, { FunctionComponent, useCallback, useState } from "react";
|
||||
|
||||
import { PropTypesOf } from "coral-framework/types";
|
||||
|
||||
import AutoLoadMore from "coral-admin/components/AutoLoadMore";
|
||||
import UserHistoryDrawerContainer from "coral-admin/components/UserHistoryDrawer/UserHistoryDrawerContainer";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
@@ -28,56 +29,86 @@ interface Props {
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
const UserTable: FunctionComponent<Props> = props => (
|
||||
<>
|
||||
<HorizontalGutter size="double">
|
||||
<Table fullWidth>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<Localized id="community-column-username">
|
||||
<TableCell className={styles.usernameColumn}>Username</TableCell>
|
||||
</Localized>
|
||||
<Localized id="community-column-email">
|
||||
<TableCell className={styles.emailColumn}>
|
||||
Email Address
|
||||
</TableCell>
|
||||
</Localized>
|
||||
<Localized id="community-column-memberSince">
|
||||
<TableCell className={styles.memberSinceColumn}>
|
||||
Member Since
|
||||
</TableCell>
|
||||
</Localized>
|
||||
<Localized id="community-column-role">
|
||||
<TableCell className={styles.roleColumn}>Role</TableCell>
|
||||
</Localized>
|
||||
<Localized id="community-column-status">
|
||||
<TableCell className={styles.statusColumn}>Status</TableCell>
|
||||
</Localized>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{!props.loading &&
|
||||
props.users.map(u => (
|
||||
<UserRowContainer key={u.id} user={u} viewer={props.viewer!} />
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
{!props.loading && props.users.length === 0 && <EmptyMessage />}
|
||||
{props.loading && (
|
||||
<Flex justifyContent="center">
|
||||
<Spinner />
|
||||
</Flex>
|
||||
)}
|
||||
{props.hasMore && (
|
||||
<Flex justifyContent="center">
|
||||
<AutoLoadMore
|
||||
disableLoadMore={props.disableLoadMore}
|
||||
onLoadMore={props.onLoadMore}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
</HorizontalGutter>
|
||||
</>
|
||||
);
|
||||
const UserTable: FunctionComponent<Props> = props => {
|
||||
const [userDrawerUserID, setUserDrawerUserID] = useState("");
|
||||
const [userDrawerVisible, setUserDrawerVisible] = useState(false);
|
||||
|
||||
const onShowUserDrawer = useCallback(
|
||||
(userID: string) => {
|
||||
setUserDrawerUserID(userID);
|
||||
setUserDrawerVisible(true);
|
||||
},
|
||||
[setUserDrawerUserID, setUserDrawerVisible]
|
||||
);
|
||||
|
||||
const onHideUserDrawer = useCallback(() => {
|
||||
setUserDrawerVisible(false);
|
||||
setUserDrawerUserID("");
|
||||
}, [setUserDrawerUserID, setUserDrawerVisible]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<HorizontalGutter size="double">
|
||||
<Table fullWidth>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<Localized id="community-column-username">
|
||||
<TableCell className={styles.usernameColumn}>
|
||||
Username
|
||||
</TableCell>
|
||||
</Localized>
|
||||
<Localized id="community-column-email">
|
||||
<TableCell className={styles.emailColumn}>
|
||||
Email Address
|
||||
</TableCell>
|
||||
</Localized>
|
||||
<Localized id="community-column-memberSince">
|
||||
<TableCell className={styles.memberSinceColumn}>
|
||||
Member Since
|
||||
</TableCell>
|
||||
</Localized>
|
||||
<Localized id="community-column-role">
|
||||
<TableCell className={styles.roleColumn}>Role</TableCell>
|
||||
</Localized>
|
||||
<Localized id="community-column-status">
|
||||
<TableCell className={styles.statusColumn}>Status</TableCell>
|
||||
</Localized>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{!props.loading &&
|
||||
props.users.map(u => (
|
||||
<UserRowContainer
|
||||
key={u.id}
|
||||
user={u}
|
||||
viewer={props.viewer!}
|
||||
onUsernameClicked={onShowUserDrawer}
|
||||
/>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
{!props.loading && props.users.length === 0 && <EmptyMessage />}
|
||||
{props.loading && (
|
||||
<Flex justifyContent="center">
|
||||
<Spinner />
|
||||
</Flex>
|
||||
)}
|
||||
{props.hasMore && (
|
||||
<Flex justifyContent="center">
|
||||
<AutoLoadMore
|
||||
disableLoadMore={props.disableLoadMore}
|
||||
onLoadMore={props.onLoadMore}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
<UserHistoryDrawerContainer
|
||||
userID={userDrawerUserID}
|
||||
open={userDrawerVisible}
|
||||
onClose={onHideUserDrawer}
|
||||
/>
|
||||
</HorizontalGutter>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserTable;
|
||||
|
||||
@@ -3,12 +3,11 @@ import React, { FunctionComponent, useCallback, useState } from "react";
|
||||
import { CSSTransition, TransitionGroup } from "react-transition-group";
|
||||
|
||||
import AutoLoadMore from "coral-admin/components/AutoLoadMore";
|
||||
import ModerateCardContainer from "coral-admin/components/ModerateCard";
|
||||
import UserHistoryDrawerContainer from "coral-admin/components/UserHistoryDrawer/UserHistoryDrawerContainer";
|
||||
import { Button, Flex, HorizontalGutter } from "coral-ui/components";
|
||||
import { PropTypesOf } from "coral-ui/types";
|
||||
|
||||
import ModerateCardContainer from "../ModerateCard";
|
||||
import UserHistoryDrawerContainer from "../UserHistoryDrawer/UserHistoryDrawerContainer";
|
||||
|
||||
import styles from "./Queue.css";
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -359,7 +359,18 @@ exports[`renders community 1`] = `
|
||||
<td
|
||||
className="TableCell-root UserRow-usernameColumn TableCell-body"
|
||||
>
|
||||
Markus
|
||||
<button
|
||||
className="BaseButton-root Button-root UserRow-usernameButton Button-sizeRegular Button-colorRegular Button-variantRegular"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Markus
|
||||
</button>
|
||||
</td>
|
||||
<td
|
||||
className="TableCell-root UserRow-emailColumn TableCell-body"
|
||||
@@ -421,7 +432,18 @@ exports[`renders community 1`] = `
|
||||
<td
|
||||
className="TableCell-root UserRow-usernameColumn TableCell-body"
|
||||
>
|
||||
Lukas
|
||||
<button
|
||||
className="BaseButton-root Button-root UserRow-usernameButton Button-sizeRegular Button-colorRegular Button-variantRegular"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Lukas
|
||||
</button>
|
||||
</td>
|
||||
<td
|
||||
className="TableCell-root UserRow-emailColumn TableCell-body"
|
||||
@@ -514,7 +536,18 @@ exports[`renders community 1`] = `
|
||||
<td
|
||||
className="TableCell-root UserRow-usernameColumn TableCell-body"
|
||||
>
|
||||
Huy
|
||||
<button
|
||||
className="BaseButton-root Button-root UserRow-usernameButton Button-sizeRegular Button-colorRegular Button-variantRegular"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Huy
|
||||
</button>
|
||||
</td>
|
||||
<td
|
||||
className="TableCell-root UserRow-emailColumn TableCell-body"
|
||||
@@ -607,7 +640,18 @@ exports[`renders community 1`] = `
|
||||
<td
|
||||
className="TableCell-root UserRow-usernameColumn TableCell-body"
|
||||
>
|
||||
Isabelle
|
||||
<button
|
||||
className="BaseButton-root Button-root UserRow-usernameButton Button-sizeRegular Button-colorRegular Button-variantRegular"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Isabelle
|
||||
</button>
|
||||
</td>
|
||||
<td
|
||||
className="TableCell-root UserRow-emailColumn TableCell-body"
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
import {
|
||||
createSettings,
|
||||
createStory,
|
||||
createUser,
|
||||
} from "coral-test/helpers/fixture";
|
||||
|
||||
import { pureMerge } from "coral-common/utils";
|
||||
import { GQLResolver, GQLUser, GQLUSER_ROLE } from "coral-framework/schema";
|
||||
import {
|
||||
act,
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
waitForElement,
|
||||
within,
|
||||
} from "coral-framework/testHelpers";
|
||||
|
||||
import create from "./create";
|
||||
|
||||
const viewer = createUser();
|
||||
viewer.role = GQLUSER_ROLE.ADMIN;
|
||||
const settings = createSettings();
|
||||
|
||||
async function createTestRenderer(
|
||||
user: GQLUser,
|
||||
params: CreateTestRendererParams<GQLResolver> = {}
|
||||
) {
|
||||
const { testRenderer, context, subscriptionHandler } = create(
|
||||
{
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settings,
|
||||
viewer: () => viewer,
|
||||
user: () => user,
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue(true, "loggedIn");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
},
|
||||
user
|
||||
);
|
||||
|
||||
return { testRenderer, context, subscriptionHandler };
|
||||
}
|
||||
|
||||
it("User drawer is open for user, user name is visible", async () => {
|
||||
const story = createStory();
|
||||
const user = story.comments.edges[0].node.author!;
|
||||
const { testRenderer } = await createTestRenderer(user);
|
||||
|
||||
await act(async () => {
|
||||
const { getByText } = within(testRenderer.root);
|
||||
await waitForElement(() => getByText(user.id, { exact: false }));
|
||||
});
|
||||
});
|
||||
|
||||
it("User drawer is open for user, user name is visible", async () => {
|
||||
const story = createStory();
|
||||
const user = story.comments.edges[0].node.author!;
|
||||
const { testRenderer } = await createTestRenderer(user);
|
||||
|
||||
await act(async () => {
|
||||
const { getByText } = within(testRenderer.root);
|
||||
await waitForElement(() => getByText(user.username!, { exact: false }));
|
||||
});
|
||||
});
|
||||
|
||||
it("All comments selected, comment is visible in all comments", async () => {
|
||||
const story = createStory();
|
||||
const user = story.comments.edges[0].node.author!;
|
||||
const comment = user.allComments.edges[0].node;
|
||||
const { testRenderer } = await createTestRenderer(user);
|
||||
|
||||
await act(async () => {
|
||||
const { getByText } = within(testRenderer.root);
|
||||
|
||||
await waitForElement(() => getByText(comment.body!, { exact: false }));
|
||||
});
|
||||
});
|
||||
|
||||
it("Select rejected comments, rejected comment is visible.", async () => {
|
||||
const story = createStory();
|
||||
const user = story.comments.edges[0].node.author!;
|
||||
const rejectedComment = user.rejectedComments.edges[0].node;
|
||||
const { testRenderer } = await createTestRenderer(user);
|
||||
|
||||
await act(async () => {
|
||||
const { getByText } = within(testRenderer.root);
|
||||
const rejectedTab = await waitForElement(() =>
|
||||
getByText("rejected", {
|
||||
selector: "button",
|
||||
exact: false,
|
||||
})
|
||||
);
|
||||
|
||||
rejectedTab.props.onClick();
|
||||
|
||||
await waitForElement(() =>
|
||||
getByText(rejectedComment.body!, { exact: false })
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
import React from "react";
|
||||
|
||||
import UserHistoryDrawerContainer from "coral-admin/components/UserHistoryDrawer/UserHistoryDrawerContainer";
|
||||
import { GQLUser } from "coral-framework/schema";
|
||||
import {
|
||||
createTestRenderer as createTestRendererGeneric,
|
||||
CreateTestRendererParams,
|
||||
} from "coral-framework/testHelpers";
|
||||
|
||||
export default function create(
|
||||
params: CreateTestRendererParams,
|
||||
user: GQLUser
|
||||
) {
|
||||
return createTestRendererGeneric(
|
||||
"userDrawer",
|
||||
<UserHistoryDrawerContainer
|
||||
userID={user.id}
|
||||
open
|
||||
onClose={() => {
|
||||
return;
|
||||
}}
|
||||
/>,
|
||||
{
|
||||
...params,
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
import {
|
||||
GQLComment,
|
||||
GQLCOMMENT_STATUS,
|
||||
GQLMODERATION_MODE,
|
||||
GQLSettings,
|
||||
GQLStory,
|
||||
GQLUser,
|
||||
GQLUSER_ROLE,
|
||||
GQLUSER_STATUS,
|
||||
} from "coral-framework/schema";
|
||||
import {
|
||||
createFixture,
|
||||
denormalizeComment,
|
||||
denormalizeStory,
|
||||
} from "coral-framework/testHelpers";
|
||||
import uuid from "uuid/v4";
|
||||
|
||||
// TODO: Look into a date/time provider that can create
|
||||
// predictable date/time (i.e. constantly increasing, or seeded)
|
||||
export function createDateInRange(start: Date, end: Date) {
|
||||
return new Date(
|
||||
start.getTime() + Math.random() * (end.getTime() - start.getTime())
|
||||
);
|
||||
}
|
||||
|
||||
export function randomDate() {
|
||||
return createDateInRange(new Date(2000, 0, 1), new Date());
|
||||
}
|
||||
|
||||
export function createUserStatus(banned: boolean = false) {
|
||||
return {
|
||||
current: [banned ? GQLUSER_STATUS.BANNED : GQLUSER_STATUS.ACTIVE],
|
||||
ban: {
|
||||
active: banned,
|
||||
history: [],
|
||||
},
|
||||
suspension: {
|
||||
active: false,
|
||||
until: null,
|
||||
history: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function createUser() {
|
||||
return createFixture<GQLUser>({
|
||||
id: uuid(),
|
||||
username: uuid(),
|
||||
role: GQLUSER_ROLE.COMMENTER,
|
||||
createdAt: randomDate().toISOString(),
|
||||
status: createUserStatus(),
|
||||
ignoredUsers: [],
|
||||
ignoreable: true,
|
||||
});
|
||||
}
|
||||
|
||||
export function createComment(author?: GQLUser) {
|
||||
const revision = uuid();
|
||||
const createdAt = randomDate();
|
||||
const editableUntil = new Date(createdAt.getTime() + 30 * 60000);
|
||||
|
||||
if (author === undefined) {
|
||||
author = createUser();
|
||||
author!.createdAt = new Date(
|
||||
createdAt.getTime() - 60 * 60000
|
||||
).toISOString();
|
||||
}
|
||||
|
||||
const comment = denormalizeComment(
|
||||
createFixture<GQLComment>({
|
||||
id: uuid(),
|
||||
author,
|
||||
body: uuid(),
|
||||
status: GQLCOMMENT_STATUS.NONE,
|
||||
statusHistory: {
|
||||
edges: [],
|
||||
pageInfo: { endCursor: null, hasNextPage: false },
|
||||
},
|
||||
createdAt: createdAt.toISOString(),
|
||||
replies: { edges: [], pageInfo: { endCursor: null, hasNextPage: false } },
|
||||
replyCount: 0,
|
||||
editing: {
|
||||
edited: false,
|
||||
editableUntil: editableUntil.toISOString(),
|
||||
},
|
||||
actionCounts: {
|
||||
reaction: {
|
||||
total: 0,
|
||||
},
|
||||
flag: {
|
||||
reasons: {
|
||||
COMMENT_DETECTED_TOXIC: 0,
|
||||
COMMENT_DETECTED_SPAM: 0,
|
||||
COMMENT_DETECTED_TRUST: 0,
|
||||
COMMENT_DETECTED_LINKS: 0,
|
||||
COMMENT_DETECTED_BANNED_WORD: 0,
|
||||
COMMENT_DETECTED_SUSPECT_WORD: 0,
|
||||
COMMENT_REPORTED_OFFENSIVE: 0,
|
||||
COMMENT_REPORTED_SPAM: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: [],
|
||||
permalink: "",
|
||||
flags: {
|
||||
edges: [],
|
||||
nodes: [],
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
},
|
||||
},
|
||||
viewerActionPresence: { reaction: false, dontAgree: false, flag: false },
|
||||
parent: undefined,
|
||||
})
|
||||
);
|
||||
|
||||
comment.revision = {
|
||||
id: revision,
|
||||
comment,
|
||||
metadata: {
|
||||
perspective: {
|
||||
score: 0,
|
||||
},
|
||||
},
|
||||
createdAt,
|
||||
actionCounts: {
|
||||
reaction: {
|
||||
total: 0,
|
||||
},
|
||||
dontAgree: {
|
||||
total: 0,
|
||||
},
|
||||
flag: {
|
||||
total: 0,
|
||||
reasons: {
|
||||
COMMENT_REPORTED_OFFENSIVE: 0,
|
||||
COMMENT_REPORTED_SPAM: 0,
|
||||
COMMENT_REPORTED_OTHER: 0,
|
||||
COMMENT_DETECTED_TOXIC: 0,
|
||||
COMMENT_DETECTED_SPAM: 0,
|
||||
COMMENT_DETECTED_TRUST: 0,
|
||||
COMMENT_DETECTED_LINKS: 0,
|
||||
COMMENT_DETECTED_BANNED_WORD: 0,
|
||||
COMMENT_DETECTED_SUSPECT_WORD: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return comment;
|
||||
}
|
||||
|
||||
export function createComments(count: number = 3) {
|
||||
const comments = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
comments.push(createComment());
|
||||
}
|
||||
|
||||
return comments;
|
||||
}
|
||||
|
||||
export function createStory() {
|
||||
const id = uuid();
|
||||
const comments = createComments();
|
||||
comments.forEach(c => {
|
||||
const edges = [{ node: c, cursor: c.createdAt }];
|
||||
c.author!.comments = {
|
||||
edges,
|
||||
nodes: [c],
|
||||
pageInfo: {
|
||||
hasPreviousPage: false,
|
||||
hasNextPage: false,
|
||||
},
|
||||
};
|
||||
c.author!.allComments = {
|
||||
edges,
|
||||
nodes: [c],
|
||||
pageInfo: {
|
||||
hasPreviousPage: false,
|
||||
hasNextPage: false,
|
||||
},
|
||||
};
|
||||
c.author!.rejectedComments = {
|
||||
edges,
|
||||
nodes: [c],
|
||||
pageInfo: {
|
||||
hasPreviousPage: false,
|
||||
hasNextPage: false,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const story = denormalizeStory(
|
||||
createFixture<GQLStory>({
|
||||
id,
|
||||
url: `http://localhost/stories/story-${id}`,
|
||||
comments: {
|
||||
edges: [
|
||||
{ node: comments[0], cursor: comments[0].createdAt },
|
||||
{ node: comments[1], cursor: comments[1].createdAt },
|
||||
{ node: comments[2], cursor: comments[2].createdAt },
|
||||
],
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
},
|
||||
},
|
||||
metadata: {
|
||||
title: uuid(),
|
||||
},
|
||||
isClosed: false,
|
||||
commentCounts: {
|
||||
totalVisible: 0,
|
||||
tags: {
|
||||
FEATURED: 0,
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
moderation: GQLMODERATION_MODE.POST,
|
||||
premodLinksEnable: false,
|
||||
messageBox: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
comments.forEach(c => (c.story = story));
|
||||
|
||||
return story;
|
||||
}
|
||||
|
||||
export function createSettings() {
|
||||
return createFixture<GQLSettings>({
|
||||
id: "settings",
|
||||
moderation: GQLMODERATION_MODE.POST,
|
||||
premodLinksEnable: false,
|
||||
live: {
|
||||
enabled: true,
|
||||
configurable: true,
|
||||
},
|
||||
wordList: {
|
||||
suspect: ["suspect"],
|
||||
banned: ["banned"],
|
||||
},
|
||||
charCount: {
|
||||
enabled: false,
|
||||
max: 1000,
|
||||
min: 3,
|
||||
},
|
||||
disableCommenting: {
|
||||
enabled: false,
|
||||
message: "Comments are closed on this story.",
|
||||
},
|
||||
closeCommenting: {
|
||||
auto: false,
|
||||
timeout: 604800,
|
||||
message: "Comments are closed on this story.",
|
||||
},
|
||||
email: {
|
||||
enabled: true,
|
||||
},
|
||||
customCSSURL: "",
|
||||
allowedDomains: ["localhost:8080"],
|
||||
editCommentWindowLength: 30000,
|
||||
communityGuidelines: {
|
||||
enabled: false,
|
||||
content: "",
|
||||
},
|
||||
organization: {
|
||||
name: "Coral",
|
||||
url: "https://test.com/",
|
||||
contactEmail: "coral@test.com",
|
||||
},
|
||||
integrations: {
|
||||
akismet: {
|
||||
enabled: false,
|
||||
},
|
||||
perspective: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
auth: {
|
||||
integrations: {
|
||||
local: {
|
||||
enabled: true,
|
||||
allowRegistration: true,
|
||||
targetFilter: {
|
||||
admin: true,
|
||||
stream: true,
|
||||
},
|
||||
},
|
||||
sso: {
|
||||
enabled: false,
|
||||
allowRegistration: true,
|
||||
targetFilter: {
|
||||
admin: true,
|
||||
stream: true,
|
||||
},
|
||||
key: "",
|
||||
keyGeneratedAt: null,
|
||||
},
|
||||
google: {
|
||||
enabled: false,
|
||||
allowRegistration: true,
|
||||
targetFilter: {
|
||||
admin: true,
|
||||
stream: true,
|
||||
},
|
||||
clientID: "",
|
||||
clientSecret: "",
|
||||
callbackURL: "http://localhost/google/callback",
|
||||
redirectURL: "http://localhost/google",
|
||||
},
|
||||
facebook: {
|
||||
enabled: false,
|
||||
allowRegistration: true,
|
||||
targetFilter: {
|
||||
admin: true,
|
||||
stream: true,
|
||||
},
|
||||
clientID: "",
|
||||
clientSecret: "",
|
||||
callbackURL: "http://localhost/facebook/callback",
|
||||
redirectURL: "http://localhost/facebook",
|
||||
},
|
||||
oidc: {
|
||||
enabled: false,
|
||||
allowRegistration: false,
|
||||
targetFilter: {
|
||||
admin: true,
|
||||
stream: true,
|
||||
},
|
||||
name: "OIDC",
|
||||
callbackURL: "http://localhost/oidc/callback",
|
||||
redirectURL: "http://localhost/oidc",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user