diff --git a/app/components/DocumentViews.tsx b/app/components/DocumentViews.tsx index e78f8442d..62ec57d90 100644 --- a/app/components/DocumentViews.tsx +++ b/app/components/DocumentViews.tsx @@ -4,6 +4,7 @@ import { observer } from "mobx-react"; import * as React from "react"; import { useTranslation } from "react-i18next"; import Document from "~/models/Document"; +import User from "~/models/User"; import Avatar from "~/components/Avatar"; import ListItem from "~/components/List/Item"; import PaginatedList from "~/components/PaginatedList"; @@ -42,7 +43,7 @@ function DocumentViews({ document, isOpen }: Props) { { + renderItem={(item: User) => { const view = documentViews.find((v) => v.user.id === item.id); const isPresent = presentIds.includes(item.id); const isEditing = editingIds.includes(item.id); diff --git a/app/components/PaginatedDocumentList.tsx b/app/components/PaginatedDocumentList.tsx index a23f62f89..c0eeb07d3 100644 --- a/app/components/PaginatedDocumentList.tsx +++ b/app/components/PaginatedDocumentList.tsx @@ -6,7 +6,7 @@ import PaginatedList from "~/components/PaginatedList"; type Props = { documents: Document[]; - fetch: (options: any) => Promise; + fetch: (options: any) => Promise; options?: Record; heading?: React.ReactNode; empty?: React.ReactNode; @@ -40,7 +40,7 @@ const PaginatedDocumentList = React.memo(function PaginatedDocumentList({ heading={heading} fetch={fetch} options={options} - renderItem={(item, _index, compositeProps) => ( + renderItem={(item: Document, _index, compositeProps) => ( | null | undefined) => Promise; + fetch: (options: Record | undefined) => Promise; options?: Record; heading?: React.ReactNode; empty?: React.ReactNode; @@ -29,7 +29,7 @@ const PaginatedEventList = React.memo(function PaginatedEventList({ heading={heading} fetch={fetch} options={options} - renderItem={(item, index, compositeProps) => { + renderItem={(item: Event, index, compositeProps) => { return ( = WithTranslation & RootStore & { fetch?: ( - options: Record | null | undefined - ) => Promise | undefined; + options: Record | undefined + ) => Promise | undefined; options?: Record; heading?: React.ReactNode; empty?: React.ReactNode; loading?: React.ReactElement; - items?: any[]; + items?: T[]; renderItem: ( - item: any, + item: T, index: number, compositeProps: CompositeStateReturn ) => React.ReactNode; @@ -33,13 +39,14 @@ type Props = WithTranslation & }; @observer -class PaginatedList extends React.Component { +class PaginatedList extends React.Component> { @observable isFetchingMore = false; @observable isFetching = false; + @observable fetchCounter = 0; @observable @@ -55,7 +62,7 @@ class PaginatedList extends React.Component { this.fetchResults(); } - componentDidUpdate(prevProps: Props) { + componentDidUpdate(prevProps: Props) { if ( prevProps.fetch !== this.props.fetch || !isEqual(prevProps.options, this.props.options) @@ -125,76 +132,82 @@ class PaginatedList extends React.Component { }; render() { - const { items, heading, auth, empty, renderHeading, onEscape } = this.props; + const { + items = [], + heading, + auth, + empty = null, + renderHeading, + onEscape, + } = this.props; let previousHeading = ""; - const showList = !!items?.length; - const showEmpty = items?.length === 0; const showLoading = - this.isFetching && !this.isFetchingMore && !showList && !showEmpty; + this.isFetching && + !this.isFetchingMore && + (!items?.length || this.fetchCounter === 0); + + if (showLoading) { + return ( + this.props.loading || ( + + + + ) + ); + } + + if (items?.length === 0) { + return empty; + } return ( <> - {showEmpty && empty} - {showList && ( - <> - {heading} - - {(composite: CompositeStateReturn) => - items.slice(0, this.renderCount).map((item, index) => { - const children = this.props.renderItem( - item, - index, - composite - ); + {heading} + + {(composite: CompositeStateReturn) => + items.slice(0, this.renderCount).map((item, index) => { + const children = this.props.renderItem(item, index, composite); - // If there is no renderHeading method passed then no date - // headings are rendered - if (!renderHeading) { - return children; - } - - // Our models have standard date fields, updatedAt > createdAt. - // Get what a heading would look like for this item - const currentDate = - item.updatedAt || item.createdAt || previousHeading; - const currentHeading = dateToHeading( - currentDate, - this.props.t, - auth.user?.language - ); - - // If the heading is different to any previous heading then we - // should render it, otherwise the item can go under the previous - // heading - if (!previousHeading || currentHeading !== previousHeading) { - previousHeading = currentHeading; - return ( - - {renderHeading(currentHeading)} - {children} - - ); - } - - return children; - }) + // If there is no renderHeading method passed then no date + // headings are rendered + if (!renderHeading) { + return children; } - - {this.allowLoadMore && ( - - )} - + + // Our models have standard date fields, updatedAt > createdAt. + // Get what a heading would look like for this item + const currentDate = + item.updatedAt || item.createdAt || previousHeading; + const currentHeading = dateToHeading( + currentDate, + this.props.t, + auth.user?.language + ); + + // If the heading is different to any previous heading then we + // should render it, otherwise the item can go under the previous + // heading + if (!previousHeading || currentHeading !== previousHeading) { + previousHeading = currentHeading; + return ( + + {renderHeading(currentHeading)} + {children} + + ); + } + + return children; + }) + } + + {this.allowLoadMore && ( + )} - {showLoading && - (this.props.loading || ( - - - - ))} ); } diff --git a/app/components/SearchPopover.tsx b/app/components/SearchPopover.tsx index 86e4a0e8e..f6c34778f 100644 --- a/app/components/SearchPopover.tsx +++ b/app/components/SearchPopover.tsx @@ -9,9 +9,10 @@ import Empty from "~/components/Empty"; import { Outline } from "~/components/Input"; import InputSearch from "~/components/InputSearch"; import Placeholder from "~/components/List/Placeholder"; -import PaginatedList from "~/components/PaginatedList"; +import PaginatedList, { PaginatedItem } from "~/components/PaginatedList"; import Popover from "~/components/Popover"; import useStores from "~/hooks/useStores"; +import { SearchResult } from "~/types"; import SearchListItem from "./SearchListItem"; type Props = { shareId: string }; @@ -31,7 +32,7 @@ function SearchPopover({ shareId }: Props) { const [cachedQuery, setCachedQuery] = React.useState(query); const [cachedSearchResults, setCachedSearchResults] = React.useState< - Record[] | undefined + PaginatedItem[] | undefined >(searchResults); React.useEffect(() => { @@ -43,7 +44,7 @@ function SearchPopover({ shareId }: Props) { }, [searchResults, query, popover.show]); const performSearch = React.useCallback( - async ({ query, ...options }: Record) => { + async ({ query, ...options }) => { if (query?.length > 0) { return await documents.search(query, { shareId, ...options }); } @@ -161,7 +162,7 @@ function SearchPopover({ shareId }: Props) { {t("No results for {{query}}", { query })} } loading={} - renderItem={(item, index, compositeProps) => ( + renderItem={(item: SearchResult, index, compositeProps) => ( , store: any) { diff --git a/app/models/Collection.ts b/app/models/Collection.ts index a5a35a5af..65bba03c7 100644 --- a/app/models/Collection.ts +++ b/app/models/Collection.ts @@ -1,13 +1,13 @@ import { trim } from "lodash"; import { action, computed, observable } from "mobx"; import CollectionsStore from "~/stores/CollectionsStore"; -import BaseModel from "~/models/BaseModel"; import Document from "~/models/Document"; +import ParanoidModel from "~/models/ParanoidModel"; import { NavigationNode } from "~/types"; import { client } from "~/utils/ApiClient"; import Field from "./decorators/Field"; -export default class Collection extends BaseModel { +export default class Collection extends ParanoidModel { store: CollectionsStore; @observable @@ -57,12 +57,6 @@ export default class Collection extends BaseModel { documents: NavigationNode[]; - createdAt: string; - - updatedAt: string; - - deletedAt: string | null | undefined; - url: string; urlId: string; diff --git a/app/models/Document.ts b/app/models/Document.ts index 5a30393c1..75df7c992 100644 --- a/app/models/Document.ts +++ b/app/models/Document.ts @@ -4,9 +4,9 @@ import { action, computed, observable } from "mobx"; import parseTitle from "@shared/utils/parseTitle"; import unescape from "@shared/utils/unescape"; import DocumentsStore from "~/stores/DocumentsStore"; -import BaseModel from "~/models/BaseModel"; import User from "~/models/User"; import { NavigationNode } from "~/types"; +import ParanoidModel from "./ParanoidModel"; import View from "./View"; import Field from "./decorators/Field"; @@ -17,7 +17,7 @@ type SaveOptions = { lastRevision?: number; }; -export default class Document extends BaseModel { +export default class Document extends ParanoidModel { @observable isSaving = false; @@ -63,20 +63,14 @@ export default class Document extends BaseModel { collaboratorIds: string[]; - createdAt: string; - createdBy: User; - updatedAt: string; - updatedBy: User; publishedAt: string | undefined; archivedAt: string; - deletedAt: string | undefined; - url: string; urlId: string; diff --git a/app/models/ParanoidModel.ts b/app/models/ParanoidModel.ts new file mode 100644 index 000000000..4af7d9a69 --- /dev/null +++ b/app/models/ParanoidModel.ts @@ -0,0 +1,5 @@ +import BaseModel from "./BaseModel"; + +export default abstract class ParanoidModel extends BaseModel { + deletedAt: string | undefined; +} diff --git a/app/models/Pin.ts b/app/models/Pin.ts index 7a62270b2..a1488ada0 100644 --- a/app/models/Pin.ts +++ b/app/models/Pin.ts @@ -10,9 +10,6 @@ class Pin extends BaseModel { @observable @Field index: string; - - createdAt: string; - updatedAt: string; } export default Pin; diff --git a/app/models/Share.ts b/app/models/Share.ts index d13985704..ffa73e953 100644 --- a/app/models/Share.ts +++ b/app/models/Share.ts @@ -29,10 +29,6 @@ class Share extends BaseModel { url: string; createdBy: User; - - createdAt: string; - - updatedAt: string; } export default Share; diff --git a/app/models/User.ts b/app/models/User.ts index c22a56488..d920aec55 100644 --- a/app/models/User.ts +++ b/app/models/User.ts @@ -1,9 +1,9 @@ import { computed, observable } from "mobx"; import { Role } from "@shared/types"; -import BaseModel from "./BaseModel"; +import ParanoidModel from "./ParanoidModel"; import Field from "./decorators/Field"; -class User extends BaseModel { +class User extends ParanoidModel { @Field @observable id: string; @@ -34,8 +34,6 @@ class User extends BaseModel { isSuspended: boolean; - createdAt: string; - @computed get isInvited(): boolean { return !this.lastActiveAt; diff --git a/app/scenes/CollectionPermissions/AddGroupsToCollection.tsx b/app/scenes/CollectionPermissions/AddGroupsToCollection.tsx index 52e5f36c3..32981beb3 100644 --- a/app/scenes/CollectionPermissions/AddGroupsToCollection.tsx +++ b/app/scenes/CollectionPermissions/AddGroupsToCollection.tsx @@ -117,7 +117,7 @@ class AddGroupsToCollection extends React.Component { } items={groups.notInCollection(collection.id, this.query)} fetch={this.query ? undefined : groups.fetchPage} - renderItem={(item) => ( + renderItem={(item: Group) => ( { . - { } items={users.notInCollection(collection.id, this.query)} fetch={this.query ? undefined : users.fetchPage} - renderItem={(item) => ( + renderItem={(item: User) => ( ( + renderItem={(group: Group) => ( ( + renderItem={(item: User) => ( { } items={users.notInGroup(group.id, this.query)} fetch={this.query ? undefined : users.fetchPage} - renderItem={(item) => ( + renderItem={(item: User) => ( {t("This group has no members.")}} - renderItem={(item) => ( + renderItem={(item: User) => ( Recent exports } - renderItem={(item) => ( + renderItem={(item: FileOperation) => ( {t("No groups have been created yet")}} fetch={groups.fetchPage} - renderItem={(item) => ( + renderItem={(item: Group) => ( Recent imports } - renderItem={(item) => ( + renderItem={(item: FileOperation) => ( )} /> diff --git a/app/scenes/Settings/Tokens.tsx b/app/scenes/Settings/Tokens.tsx index 93d04fe72..a7a0d7c7a 100644 --- a/app/scenes/Settings/Tokens.tsx +++ b/app/scenes/Settings/Tokens.tsx @@ -2,6 +2,7 @@ import { observer } from "mobx-react"; import { CodeIcon } from "outline-icons"; import * as React from "react"; import { useTranslation, Trans } from "react-i18next"; +import ApiKey from "~/models/ApiKey"; import APITokenNew from "~/scenes/APITokenNew"; import { Action } from "~/components/Actions"; import Button from "~/components/Button"; @@ -59,7 +60,7 @@ function Tokens() { fetch={apiKeys.fetchPage} items={apiKeys.orderedData} heading={{t("Tokens")}} - renderItem={(token) => ( + renderItem={(token: ApiKey) => ( )} /> diff --git a/app/stores/BaseStore.ts b/app/stores/BaseStore.ts index dd39c8424..fdfd4c6fb 100644 --- a/app/stores/BaseStore.ts +++ b/app/stores/BaseStore.ts @@ -217,7 +217,7 @@ export default abstract class BaseStore { } @action - fetchPage = async (params: FetchPageParams | undefined): Promise => { + fetchPage = async (params: FetchPageParams | undefined): Promise => { if (!this.actions.includes(RPCAction.List)) { throw new Error(`Cannot list ${this.modelName}`); } diff --git a/app/stores/CollectionGroupMembershipsStore.ts b/app/stores/CollectionGroupMembershipsStore.ts index 43cc787d3..b6e88a0b1 100644 --- a/app/stores/CollectionGroupMembershipsStore.ts +++ b/app/stores/CollectionGroupMembershipsStore.ts @@ -16,18 +16,22 @@ export default class CollectionGroupMembershipsStore extends BaseStore< } @action - fetchPage = async (params: PaginationParams | undefined): Promise => { + fetchPage = async ( + params: PaginationParams | undefined + ): Promise => { this.isFetching = true; try { const res = await client.post(`/collections.group_memberships`, params); invariant(res?.data, "Data not available"); + + let models: CollectionGroupMembership[] = []; runInAction(`CollectionGroupMembershipsStore#fetchPage`, () => { res.data.groups.forEach(this.rootStore.groups.add); - res.data.collectionGroupMemberships.forEach(this.add); + models = res.data.collectionGroupMemberships.map(this.add); this.isLoaded = true; }); - return res.data.groups; + return models; } finally { this.isFetching = false; } diff --git a/app/stores/DocumentsStore.ts b/app/stores/DocumentsStore.ts index 53e291ad3..f224c4126 100644 --- a/app/stores/DocumentsStore.ts +++ b/app/stores/DocumentsStore.ts @@ -408,6 +408,7 @@ export default class DocumentsStore extends BaseStore { return null; } return { + id: document.id, ranking: result.ranking, context: result.context, document, diff --git a/app/stores/GroupMembershipsStore.ts b/app/stores/GroupMembershipsStore.ts index 9c45face1..259429e48 100644 --- a/app/stores/GroupMembershipsStore.ts +++ b/app/stores/GroupMembershipsStore.ts @@ -15,18 +15,22 @@ export default class GroupMembershipsStore extends BaseStore { } @action - fetchPage = async (params: PaginationParams | undefined): Promise => { + fetchPage = async ( + params: PaginationParams | undefined + ): Promise => { this.isFetching = true; try { const res = await client.post(`/groups.memberships`, params); invariant(res?.data, "Data not available"); + + let models: GroupMembership[] = []; runInAction(`GroupMembershipsStore#fetchPage`, () => { res.data.users.forEach(this.rootStore.users.add); - res.data.groupMemberships.forEach(this.add); + models = res.data.groupMemberships.map(this.add); this.isLoaded = true; }); - return res.data.users; + return models; } finally { this.isFetching = false; } diff --git a/app/stores/GroupsStore.ts b/app/stores/GroupsStore.ts index 22c2cb700..8da97a1ba 100644 --- a/app/stores/GroupsStore.ts +++ b/app/stores/GroupsStore.ts @@ -21,19 +21,21 @@ export default class GroupsStore extends BaseStore { } @action - fetchPage = async (params: FetchPageParams | undefined): Promise => { + fetchPage = async (params: FetchPageParams | undefined): Promise => { this.isFetching = true; try { const res = await client.post(`/groups.list`, params); invariant(res?.data, "Data not available"); + + let models: Group[] = []; runInAction(`GroupsStore#fetchPage`, () => { this.addPolicies(res.policies); - res.data.groups.forEach(this.add); + models = res.data.groups.map(this.add); res.data.groupMemberships.forEach(this.rootStore.groupMemberships.add); this.isLoaded = true; }); - return res.data.groups; + return models; } finally { this.isFetching = false; } @@ -72,7 +74,7 @@ export default class GroupsStore extends BaseStore { } function queriedGroups(groups: Group[], query: string) { - return filter(groups, (group) => + return groups.filter((group) => group.name.toLowerCase().match(query.toLowerCase()) ); } diff --git a/app/stores/MembershipsStore.ts b/app/stores/MembershipsStore.ts index 2f15701f1..5f3847bea 100644 --- a/app/stores/MembershipsStore.ts +++ b/app/stores/MembershipsStore.ts @@ -14,18 +14,22 @@ export default class MembershipsStore extends BaseStore { } @action - fetchPage = async (params: PaginationParams | undefined): Promise => { + fetchPage = async ( + params: PaginationParams | undefined + ): Promise => { this.isFetching = true; try { const res = await client.post(`/collections.memberships`, params); invariant(res?.data, "Data not available"); - runInAction(`/collections.memberships`, () => { + + let models: Membership[] = []; + runInAction(`MembershipsStore#fetchPage`, () => { res.data.users.forEach(this.rootStore.users.add); - res.data.memberships.forEach(this.add); + models = res.data.memberships.map(this.add); this.isLoaded = true; }); - return res.data.users; + return models; } finally { this.isFetching = false; } diff --git a/app/stores/PinsStore.ts b/app/stores/PinsStore.ts index a756a6579..09c32256a 100644 --- a/app/stores/PinsStore.ts +++ b/app/stores/PinsStore.ts @@ -14,18 +14,22 @@ export default class PinsStore extends BaseStore { } @action - fetchPage = async (params?: FetchParams | undefined): Promise => { + fetchPage = async (params?: FetchParams | undefined): Promise => { this.isFetching = true; try { const res = await client.post(`/pins.list`, params); invariant(res?.data, "Data not available"); + + let models: Pin[] = []; runInAction(`PinsStore#fetchPage`, () => { res.data.documents.forEach(this.rootStore.documents.add); - res.data.pins.forEach(this.add); + models = res.data.pins.map(this.add); this.addPolicies(res.policies); this.isLoaded = true; }); + + return models; } finally { this.isFetching = false; } diff --git a/app/stores/RevisionsStore.ts b/app/stores/RevisionsStore.ts index 5ab1a16d1..329383363 100644 --- a/app/stores/RevisionsStore.ts +++ b/app/stores/RevisionsStore.ts @@ -48,17 +48,21 @@ export default class RevisionsStore extends BaseStore { } @action - fetchPage = async (options: PaginationParams | undefined): Promise => { + fetchPage = async ( + options: PaginationParams | undefined + ): Promise => { this.isFetching = true; try { const res = await client.post("/revisions.list", options); invariant(res?.data, "Document revisions not available"); + + let models: Revision[] = []; runInAction("RevisionsStore#fetchPage", () => { - res.data.forEach(this.add); + models = res.data.map(this.add); this.isLoaded = true; }); - return res.data; + return models; } finally { this.isFetching = false; } diff --git a/app/stores/StarsStore.ts b/app/stores/StarsStore.ts index 2efc1f15f..fa01d7730 100644 --- a/app/stores/StarsStore.ts +++ b/app/stores/StarsStore.ts @@ -12,18 +12,23 @@ export default class StarsStore extends BaseStore { } @action - fetchPage = async (params?: PaginationParams | undefined): Promise => { + fetchPage = async ( + params?: PaginationParams | undefined + ): Promise => { this.isFetching = true; try { const res = await client.post(`/stars.list`, params); invariant(res?.data, "Data not available"); + + let models: Star[] = []; runInAction(`StarsStore#fetchPage`, () => { res.data.documents.forEach(this.rootStore.documents.add); - res.data.stars.forEach(this.add); + models = res.data.stars.map(this.add); this.addPolicies(res.policies); this.isLoaded = true; }); + return models; } finally { this.isFetching = false; } diff --git a/app/types.ts b/app/types.ts index bd985b225..ed74ee784 100644 --- a/app/types.ts +++ b/app/types.ts @@ -163,6 +163,7 @@ export type PaginationParams = { }; export type SearchResult = { + id: string; ranking: number; context: string; document: Document; diff --git a/server/models/User.test.ts b/server/models/User.test.ts index 3a4121a2b..7cf5f9052 100644 --- a/server/models/User.test.ts +++ b/server/models/User.test.ts @@ -4,6 +4,12 @@ import CollectionUser from "./CollectionUser"; import UserAuthentication from "./UserAuthentication"; beforeEach(() => flushdb()); +beforeAll(() => { + jest.useFakeTimers().setSystemTime(new Date("2018-01-02T00:00:00.000Z")); +}); +afterAll(() => { + jest.useRealTimers(); +}); describe("user model", () => { describe("destroy", () => { diff --git a/server/presenters/__snapshots__/user.test.ts.snap b/server/presenters/__snapshots__/user.test.ts.snap index 060ab777f..05e431ffd 100644 --- a/server/presenters/__snapshots__/user.test.ts.snap +++ b/server/presenters/__snapshots__/user.test.ts.snap @@ -11,6 +11,7 @@ Object { "isViewer": false, "lastActiveAt": undefined, "name": "Test User", + "updatedAt": undefined, } `; @@ -25,5 +26,6 @@ Object { "isViewer": false, "lastActiveAt": undefined, "name": "Test User", + "updatedAt": undefined, } `; diff --git a/server/presenters/apiKey.ts b/server/presenters/apiKey.ts index 26fe6595a..67d3cf246 100644 --- a/server/presenters/apiKey.ts +++ b/server/presenters/apiKey.ts @@ -6,5 +6,6 @@ export default function present(key: ApiKey) { name: key.name, secret: key.secret, createdAt: key.createdAt, + updatedAt: key.updatedAt, }; } diff --git a/server/presenters/fileOperation.ts b/server/presenters/fileOperation.ts index ddb14eee2..3e9438bc1 100644 --- a/server/presenters/fileOperation.ts +++ b/server/presenters/fileOperation.ts @@ -13,5 +13,6 @@ export default function present(data: FileOperation) { collectionId: data.collectionId, user: presentUser(data.user), createdAt: data.createdAt, + updatedAt: data.updatedAt, }; } diff --git a/server/presenters/user.ts b/server/presenters/user.ts index 0ca77b2ad..b7e7e165c 100644 --- a/server/presenters/user.ts +++ b/server/presenters/user.ts @@ -9,6 +9,7 @@ type UserPresentation = { name: string; avatarUrl: string | null | undefined; createdAt: Date; + updatedAt: Date; lastActiveAt: Date | null; color: string; isAdmin: boolean; @@ -31,6 +32,7 @@ export default ( isSuspended: user.isSuspended, isViewer: user.isViewer, createdAt: user.createdAt, + updatedAt: user.updatedAt, lastActiveAt: user.lastActiveAt, }; diff --git a/server/routes/api/__snapshots__/users.test.ts.snap b/server/routes/api/__snapshots__/users.test.ts.snap index 62fdbcc26..b40811bfc 100644 --- a/server/routes/api/__snapshots__/users.test.ts.snap +++ b/server/routes/api/__snapshots__/users.test.ts.snap @@ -14,6 +14,7 @@ Object { "language": "en_US", "lastActiveAt": null, "name": "User 1", + "updatedAt": "2018-01-02T00:00:00.000Z", }, "ok": true, "policies": Array [ @@ -68,6 +69,7 @@ Object { "language": "en_US", "lastActiveAt": null, "name": "User 1", + "updatedAt": "2018-01-02T00:00:00.000Z", }, "ok": true, "policies": Array [ @@ -104,6 +106,7 @@ Object { "language": "en_US", "lastActiveAt": null, "name": "User 1", + "updatedAt": "2018-01-02T00:00:00.000Z", }, "ok": true, "policies": Array [ @@ -140,6 +143,7 @@ Object { "language": "en_US", "lastActiveAt": null, "name": "User 1", + "updatedAt": "2018-01-02T00:00:00.000Z", }, "ok": true, "policies": Array [ @@ -194,6 +198,7 @@ Object { "language": "en_US", "lastActiveAt": null, "name": "User 1", + "updatedAt": "2018-01-02T00:00:00.000Z", }, "ok": true, "policies": Array [ @@ -257,6 +262,7 @@ Object { "language": "en_US", "lastActiveAt": null, "name": "User 1", + "updatedAt": "2018-01-02T00:00:00.000Z", }, "ok": true, "policies": Array [ diff --git a/server/routes/api/users.test.ts b/server/routes/api/users.test.ts index ff0db93e5..0c663c64c 100644 --- a/server/routes/api/users.test.ts +++ b/server/routes/api/users.test.ts @@ -6,7 +6,14 @@ import { flushdb, seed } from "@server/test/support"; const app = webService(); const server = new TestServer(app.callback()); beforeEach(() => flushdb()); -afterAll(() => server.close()); + +beforeAll(() => { + jest.useFakeTimers().setSystemTime(new Date("2018-01-02T00:00:00.000Z")); +}); +afterAll(() => { + jest.useRealTimers(); + return server.close(); +}); describe("#users.list", () => { it("should allow filtering by user name", async () => { diff --git a/server/test/factories.ts b/server/test/factories.ts index 3415abb49..e23234deb 100644 --- a/server/test/factories.ts +++ b/server/test/factories.ts @@ -135,7 +135,8 @@ export async function buildUser(overrides: Partial = {}) { name: `User ${count}`, username: `user${count}`, createdAt: new Date("2018-01-01T00:00:00.000Z"), - lastActiveAt: new Date("2018-01-01T00:00:00.000Z"), + updatedAt: new Date("2018-01-02T00:00:00.000Z"), + lastActiveAt: new Date("2018-01-03T00:00:00.000Z"), authentications: [ { authenticationProviderId: authenticationProvider!.id,