From c23272db38a464378368c143740f0d8e95143e1f Mon Sep 17 00:00:00 2001 From: Donghyeon Kim <0916dhkim@gmail.com> Date: Mon, 23 Mar 2020 17:04:41 -0400 Subject: [PATCH] History In Most Recent Order --- .../users_view/history_edit_form.spec.tsx | 143 ++++++++++++++++++ src/renderer/users_view/history_edit_form.tsx | 51 ++++--- 2 files changed, 174 insertions(+), 20 deletions(-) create mode 100644 src/renderer/users_view/history_edit_form.spec.tsx diff --git a/src/renderer/users_view/history_edit_form.spec.tsx b/src/renderer/users_view/history_edit_form.spec.tsx new file mode 100644 index 0000000..3e61bb3 --- /dev/null +++ b/src/renderer/users_view/history_edit_form.spec.tsx @@ -0,0 +1,143 @@ +import * as React from "react"; +import { describe, it, afterEach } from "mocha"; +import { expect } from "chai"; +import { HistoryEditFormProps, HistoryEditForm } from "./history_edit_form"; +import { + render, + RenderResult, + queries, + cleanup, + within, + prettyDOM, + waitFor +} from "@testing-library/react"; +import { AppDataContext } from "../app_data_context"; +import { AppData, createAppData } from "../../common/persistence/app_data"; +import { User } from "../../common/persistence/user"; +import { Book } from "../../common/persistence/book"; +import { assertWrapper } from "../../common/assert_wrapper"; +import * as moment from "moment"; +import * as sinon from "sinon"; + +/** + * Find a history list element by matching regex. + * Throw if search failed or more than one element matches. + */ +export function getHistoryListElementByText( + container: HTMLElement, + text: RegExp +): HTMLElement { + const ret = within(container) + .queryAllByTestId("history-list-element") + .filter(historyListElement => historyListElement.textContent?.match(text)); + if (ret.length === 0) { + throw new Error( + [`No history list element matching ${text}`, prettyDOM(container)].join( + "\n\n" + ) + ); + } else if (ret.length > 1) { + throw new Error( + [ + `${ret.length} tag list elements matching ${text}`, + prettyDOM(container) + ].join("\n\n") + ); + } + return ret[0]; +} + +/** + * Find all history list elements. + */ +export function queryAllHistoryListElement( + container: HTMLElement +): Array { + return within(container).queryAllByTestId("history-list-element"); +} + +/** + * Wrapper function for render. + */ +export function renderHistoryEditForm( + appData: AppData, + setAppData: (x: AppData) => void, + props: HistoryEditFormProps +): RenderResult { + return render( + + + + ); +} + +describe("HistoryEditForm", function() { + afterEach(function() { + cleanup(); + }); + + it("Display Most Recent History First", async function() { + const user: User = { + id: 6, + firstName: "Natalie", + lastName: "Nason" + }; + const bookA: Book = { + id: 1, + title: "A", + author: "A" + }; + const bookB: Book = { + id: 2, + title: "B", + author: "B" + }; + const bookC: Book = { + id: 3, + title: "C", + author: "C" + }; + const appData = createAppData({ + users: [user], + books: [bookA, bookB, bookC], + views: [ + { + id: 1000, + userId: 6, + bookId: 2, + date: moment.utc("20070303").valueOf() + }, + { + id: 2000, + userId: 6, + bookId: 1, + date: moment.utc("20080303").valueOf() + }, + { + id: 3000, + userId: 6, + bookId: 3, + date: moment.utc("20090303").valueOf() + } + ] + }); + assertWrapper(appData); + const { container } = renderHistoryEditForm(appData, sinon.fake(), { + user: user + }); + return waitFor(() => { + const actual = queryAllHistoryListElement(container).map( + element => element.textContent + ); + expect(actual).to.have.lengthOf(3); + expect(actual[0]).to.match(/C/); + expect(actual[1]).to.match(/A/); + expect(actual[2]).to.match(/B/); + }); + }); +}); diff --git a/src/renderer/users_view/history_edit_form.tsx b/src/renderer/users_view/history_edit_form.tsx index fa9d118..d29b77d 100644 --- a/src/renderer/users_view/history_edit_form.tsx +++ b/src/renderer/users_view/history_edit_form.tsx @@ -17,6 +17,8 @@ import { import { Book } from "../../common/persistence/book"; import { filterBook } from "../../common/persistence/filter_book"; import { addView, deleteView } from "../../common/persistence/app_data"; +import { View } from "../../common/persistence/view"; +import { FunctionalIterable } from "../../common/persistence/functional_iterable"; /** * Convert a book to be presented inside dropdown menu. @@ -40,6 +42,12 @@ export function HistoryEditForm({ const [historyInputValue, setHistoryInputValue] = React.useState< number | null >(null); + const views = React.useMemo>(() => { + const ret = new FunctionalIterable(appData.views.values()).filter( + view => view.userId === user.id + ); + return Array.from(ret).reverse(); + }, [appData.views, user]); /** * Handle history add button click event. @@ -122,26 +130,29 @@ export function HistoryEditForm({ /> - {Array.from(appData.views.values()) - .filter(view => view.userId === user.id) - .map(view => { - const book = appData.books.get(view.bookId); - assertWrapper(!!book); - return ( - - { - const [nextAppData] = deleteView(appData, view.id); - setAppData(nextAppData); - }} - /> - {book.title} by {book.author} - - ); - })} + {Array.from(views).map(view => { + const book = appData.books.get(view.bookId); + assertWrapper(!!book); + return ( + + { + const [nextAppData] = deleteView(appData, view.id); + setAppData(nextAppData); + }} + /> + {book.title} by {book.author} + + ); + })}