From ba358d38f2d731251320df972639f21d8c6c4485 Mon Sep 17 00:00:00 2001 From: Yogev Date: Tue, 30 Jul 2019 18:02:46 +0300 Subject: [PATCH] FetchAPI and Comparer --- Invoro.WebApp/Invoro/invoro/src/App.tsx | 2 +- .../FeaturesTable.component.test.tsx | 233 +++++++++++++++++- .../FeaturesTable/FeaturesTable.component.tsx | 22 +- .../Invoro/Invoro.component.test.tsx | 7 +- .../components/Invoro/Invoro.component.tsx | 10 +- .../src/lib/components/Status.component.tsx | 8 +- .../invoro/src/lib/dataModel/Feature.ts | 4 + .../src/lib/dataModel/FeaturesCategory.ts | 6 +- .../StatusToDisplayConverter.service.ts | 3 +- Invoro.WebApp/Invoro/invoro/tsconfig.json | 2 +- package-lock.json | 3 + 11 files changed, 275 insertions(+), 25 deletions(-) create mode 100644 package-lock.json diff --git a/Invoro.WebApp/Invoro/invoro/src/App.tsx b/Invoro.WebApp/Invoro/invoro/src/App.tsx index b70059f..0a1d7dd 100644 --- a/Invoro.WebApp/Invoro/invoro/src/App.tsx +++ b/Invoro.WebApp/Invoro/invoro/src/App.tsx @@ -6,6 +6,6 @@ export default class App extends React.Component { state = { userIdentifier: "AUserIdentity" }; render() { - return ; + return ; } } \ No newline at end of file diff --git a/Invoro.WebApp/Invoro/invoro/src/lib/components/FeaturesTable/FeaturesTable.component.test.tsx b/Invoro.WebApp/Invoro/invoro/src/lib/components/FeaturesTable/FeaturesTable.component.test.tsx index 335a856..d740321 100644 --- a/Invoro.WebApp/Invoro/invoro/src/lib/components/FeaturesTable/FeaturesTable.component.test.tsx +++ b/Invoro.WebApp/Invoro/invoro/src/lib/components/FeaturesTable/FeaturesTable.component.test.tsx @@ -2,8 +2,9 @@ import React from 'react'; import FeaturesTableComponent from './FeaturesTable.component'; import Feature from '../../dataModel/Feature'; import Status from '../../dataModel/Status'; -import { shallow } from 'enzyme'; +import { shallow, mount, ReactWrapper } from 'enzyme'; import FeatureCategory from '../../dataModel/FeaturesCategory'; +import { TableCell } from '@material-ui/core'; it('render one feature with the correct name', () => { @@ -17,12 +18,14 @@ it('render one feature with the correct name', () => { // Action let result = - shallow() - .find(".feature-row").shallow() + .find(`#${features[0].id}`) + .find(TableCell) + .first() .find("span"); // Assert @@ -41,12 +44,14 @@ it('render feature with the correct link as linkable', () => { // Action let result = - shallow() - .find(".feature-row").shallow() + .find(`#${features[0].id}`) + .find(TableCell) + .first() .find('a'); // Assert @@ -71,8 +76,222 @@ it('render multiple features', () => { featuresVoted={featuresVoted} featureVoteHandle={voteHandle} featureUnvoteHandle={unvoteHandle} />) - .find(".feature-row"); + .find(".featureRow") // Assert expect(tableRows).toHaveLength(features.length); -}); \ No newline at end of file +}); + +describe("Order features", () => { + let featuresVoted: Set = new Set(); + let voteHandle = (id: string) => { }; + let unvoteHandle = (id: string) => { }; + + describe('with default comparer', () => { + let expectedIdOrder: string[] = ["ID1", "ID2", "ID3"]; + + it('already ordered - render alphabetically,', () => { + + // Prepare + let features: Feature[] = [ + new Feature("ID1", "AFirst", Status.NotPlanned, "Link", new Date(), new Date()), + new Feature("ID2", "BSecond", Status.NotPlanned, "Link", new Date(), new Date()), + new Feature("ID3", "CThird", Status.NotPlanned, "Link", new Date(), new Date())]; + let featuresCategories: FeatureCategory[] = [new FeatureCategory("MyCategory", features)]; + + // Action + let tableRows = + mount(). + find("TableRow.featureRow"); + + expectIdOrder(tableRows, expectedIdOrder); + }); + + it('scrambled - render alphabetically,', () => { + + // Prepare + let features: Feature[] = [ + new Feature("ID2", "BSecond", Status.NotPlanned, "Link", new Date(), new Date()), + new Feature("ID1", "AFirst", Status.NotPlanned, "Link", new Date(), new Date()), + new Feature("ID3", "CThird", Status.NotPlanned, "Link", new Date(), new Date())]; + let featuresCategories: FeatureCategory[] = [new FeatureCategory("MyCategory", features)]; + + // Action + let tableRows = + mount(). + find("TableRow.featureRow"); + + expectIdOrder(tableRows, expectedIdOrder); + }); + }) + describe('with costume comparer', () => { + let costumeFeaturesComparerReverse = (a: Feature, b: Feature): number => { return a.name.localeCompare(b.name) * (-1)}; + let expectedIdOrder: string[] = ["ID1", "ID2", "ID3"]; + + it('already ordered - render alphabetically,', () => { + + // Prepare + let features: Feature[] = [ + new Feature("ID3", "AThird", Status.NotPlanned, "Link", new Date(), new Date()), + new Feature("ID2", "BSecond", Status.NotPlanned, "Link", new Date(), new Date()), + new Feature("ID1", "CFirst", Status.NotPlanned, "Link", new Date(), new Date())]; + let featuresCategories: FeatureCategory[] = [new FeatureCategory("MyCategory", features)]; + + // Action + let tableRows = + mount(). + find("TableRow.featureRow"); + + expectIdOrder(tableRows, expectedIdOrder); + }); + + it('scrambled - render alphabetically,', () => { + + // Prepare + let features: Feature[] = [ + new Feature("ID3", "AThird", Status.NotPlanned, "Link", new Date(), new Date()), + new Feature("ID2", "BSecond", Status.NotPlanned, "Link", new Date(), new Date()), + new Feature("ID1", "CFirst", Status.NotPlanned, "Link", new Date(), new Date())]; + let featuresCategories: FeatureCategory[] = [new FeatureCategory("MyCategory", features)]; + + // Action + let tableRows = + mount(). + find("TableRow.featureRow"); + + expectIdOrder(tableRows, expectedIdOrder); + }); + }) + +}); + +describe("Order categories", () => { + + let featuresVoted: Set = new Set(); + let voteHandle = (id: string) => { }; + let unvoteHandle = (id: string) => { }; + + let featuresOfCategoryOne: Feature[] = [ + new Feature("ID1", "Name", Status.NotPlanned, "Link", new Date(), new Date())]; + let featuresOfCategoryTwo: Feature[] = [ + new Feature("ID2", "Name", Status.NotPlanned, "Link", new Date(), new Date())]; + let featuresOfCategoryThree: Feature[] = [ + new Feature("ID3", "Name", Status.NotPlanned, "Link", new Date(), new Date())]; + + describe('with default comparer', () => { + let expectedIdOrder: string[] = ["A - FirstCategory", "B - SecondCategory", "C - ThirdCategory"]; + + it('already ordered - render alphabetically,', () => { + // Prepare + let featuresCategories: FeatureCategory[] = [ + new FeatureCategory("A - FirstCategory", featuresOfCategoryOne), + new FeatureCategory("B - SecondCategory", featuresOfCategoryTwo), + new FeatureCategory("C - ThirdCategory", featuresOfCategoryThree) + ]; + + // Action + let tableRows = + mount(). + find("TableRow.categoryRow"); + + expectIdOrder(tableRows, expectedIdOrder); + }); + + it('scrambled - render alphabetically,', () => { + // Prepare + let featuresCategories: FeatureCategory[] = [ + new FeatureCategory("B - SecondCategory", featuresOfCategoryTwo), + new FeatureCategory("A - FirstCategory", featuresOfCategoryOne), + new FeatureCategory("C - ThirdCategory", featuresOfCategoryThree) + ]; + + // Action + let tableRows = + mount(). + find("TableRow.categoryRow"); + + expectIdOrder(tableRows, expectedIdOrder); + }); + }) + describe('with costume comparer', () => { + let costumeCategoriesComparerReverse = (a: FeatureCategory, b: FeatureCategory): number => + { return a.name.localeCompare(b.name) * (-1)}; + let expectedIdOrder: string[] = ["C - FirstCategory", "B - SecondCategory", "A - ThirdCategory"]; + + it('already ordered - render alphabetically,', () => { + // Prepare + let featuresCategories: FeatureCategory[] = [ + new FeatureCategory("C - FirstCategory", featuresOfCategoryThree), + new FeatureCategory("B - SecondCategory", featuresOfCategoryTwo), + new FeatureCategory("A - ThirdCategory", featuresOfCategoryOne) + ]; + + // Action + let tableRows = + mount(). + find("TableRow.categoryRow"); + + expectIdOrder(tableRows, expectedIdOrder); + }); + + it('scrambled - render alphabetically,', () => { + + // Prepare + let featuresCategories: FeatureCategory[] = [ + new FeatureCategory("B - SecondCategory", featuresOfCategoryTwo), + new FeatureCategory("A - ThirdCategory", featuresOfCategoryOne), + new FeatureCategory("C - FirstCategory", featuresOfCategoryThree) + ]; + + // Action + let tableRows = + mount(). + find("TableRow.categoryRow"); + + expectIdOrder(tableRows, expectedIdOrder); + }); + }) + +}); + +function expectIdOrder(tableRows: ReactWrapper, expectedNameOrder: string[]) { + for (let i: number = 0; i < tableRows.length; i++) { + expect(tableRows.at(i).prop('id')).toEqual(expectedNameOrder[i]); + } +} diff --git a/Invoro.WebApp/Invoro/invoro/src/lib/components/FeaturesTable/FeaturesTable.component.tsx b/Invoro.WebApp/Invoro/invoro/src/lib/components/FeaturesTable/FeaturesTable.component.tsx index 6943f7a..c6606a0 100644 --- a/Invoro.WebApp/Invoro/invoro/src/lib/components/FeaturesTable/FeaturesTable.component.tsx +++ b/Invoro.WebApp/Invoro/invoro/src/lib/components/FeaturesTable/FeaturesTable.component.tsx @@ -1,4 +1,4 @@ -import React, { ReactElement } from "react"; +import React from "react"; import { Paper, Table, TableHead, TableRow, TableCell, TableBody } from "@material-ui/core"; import StatusComponent from "../Status.component"; import VoteComponent from "../Vote.component"; @@ -8,6 +8,8 @@ import FeaturesCategory from "../../dataModel/FeaturesCategory"; interface FeaturesTableProps { featuresCategories: FeaturesCategory[]; + featuresCategoryCompareFn?: (a: FeaturesCategory, b: FeaturesCategory) => number; + featuresCompareFn?: (a: Feature, b: Feature) => number; featuresVoted: Set; featureVoteHandle(featureId: string): void; featureUnvoteHandle(featureId: string): void; @@ -22,7 +24,7 @@ export default class FeaturesTableComponent extends React.Component - {this.props.featuresCategories.map(category => + {this.props.featuresCategories.sort(this.getFeaturesCategoryComparerFunc()).map(category => [this.CreateCategoryRow(category), this.CreateFeatureRow(category.features)])} @@ -43,7 +45,7 @@ export default class FeaturesTableComponent extends React.Component + return {category.name} @@ -51,8 +53,8 @@ export default class FeaturesTableComponent extends React.Component - + return features.sort(this.getFeaturesComparerFunc()).map(feature=> + {this.getLinkableFeatureTitle(feature)} @@ -62,7 +64,7 @@ export default class FeaturesTableComponent extends React.Component); + ); } private getLinkableFeatureTitle(feature: Feature): JSX.Element { @@ -98,4 +100,12 @@ export default class FeaturesTableComponent extends React.Component } } + + private getFeaturesCategoryComparerFunc(): (a: FeaturesCategory, b: FeaturesCategory) => number { + return this.props.featuresCategoryCompareFn ? this.props.featuresCategoryCompareFn : FeaturesCategory.Compare; + } + + private getFeaturesComparerFunc(): (a: Feature, b: Feature) => number { + return this.props.featuresCompareFn ? this.props.featuresCompareFn : Feature.Compare; + } } \ No newline at end of file diff --git a/Invoro.WebApp/Invoro/invoro/src/lib/components/Invoro/Invoro.component.test.tsx b/Invoro.WebApp/Invoro/invoro/src/lib/components/Invoro/Invoro.component.test.tsx index 5c276a1..6ffc63f 100644 --- a/Invoro.WebApp/Invoro/invoro/src/lib/components/Invoro/Invoro.component.test.tsx +++ b/Invoro.WebApp/Invoro/invoro/src/lib/components/Invoro/Invoro.component.test.tsx @@ -4,6 +4,9 @@ import Invoro from './Invoro.component'; it('renders without crashing', () => { const div = document.createElement('div'); - ReactDOM.render(, div); + const fetchMock = (input: RequestInfo, init?: RequestInit) => new Promise(() => null); + ReactDOM.render(, div); ReactDOM.unmountComponentAtNode(div); -}); \ No newline at end of file +}); + +// Check all APIs \ No newline at end of file diff --git a/Invoro.WebApp/Invoro/invoro/src/lib/components/Invoro/Invoro.component.tsx b/Invoro.WebApp/Invoro/invoro/src/lib/components/Invoro/Invoro.component.tsx index 16da696..67c4d84 100644 --- a/Invoro.WebApp/Invoro/invoro/src/lib/components/Invoro/Invoro.component.tsx +++ b/Invoro.WebApp/Invoro/invoro/src/lib/components/Invoro/Invoro.component.tsx @@ -1,5 +1,6 @@ import React from 'react'; import FeaturesCategory from '../../dataModel/FeaturesCategory' +import Feature from '../../dataModel/Feature'; import FeaturesApi from '../../services/FeaturesApi.service'; import FeaturesTableComponent from '../FeaturesTable/FeaturesTable.component' @@ -10,6 +11,9 @@ interface InvoroState { interface InvoroProps { userIdentifier: string; + fetchApi: (input: RequestInfo, init?: RequestInit) => Promise; + featuresCategoryCompareFn?: (a: FeaturesCategory, b: FeaturesCategory) => number; + featuresCompareFn?: (a: Feature, b: Feature) => number; } export default class Invoro extends React.Component { @@ -17,7 +21,7 @@ export default class Invoro extends React.Component { constructor(props: any) { super(props); - this.featuresApi = new FeaturesApi(props.userIdentifier, fetch.bind(window)); + this.featuresApi = new FeaturesApi(props.userIdentifier, props.fetchApi); } state: InvoroState = { @@ -40,7 +44,9 @@ export default class Invoro extends React.Component { featuresCategories={this.state.featuresCategories} featuresVoted={this.state.featuresVoted} featureVoteHandle={(featureId) => this.voteToFeature(featureId)} - featureUnvoteHandle={(featureId) => this.unvoteToFeature(featureId)} />; + featureUnvoteHandle={(featureId) => this.unvoteToFeature(featureId)} + featuresCategoryCompareFn={this.props.featuresCategoryCompareFn} + featuresCompareFn={this.props.featuresCompareFn} />; } else { return
Loading..
; diff --git a/Invoro.WebApp/Invoro/invoro/src/lib/components/Status.component.tsx b/Invoro.WebApp/Invoro/invoro/src/lib/components/Status.component.tsx index c286476..f815c08 100644 --- a/Invoro.WebApp/Invoro/invoro/src/lib/components/Status.component.tsx +++ b/Invoro.WebApp/Invoro/invoro/src/lib/components/Status.component.tsx @@ -1,6 +1,6 @@ -import React, { ReactComponentElement } from 'react'; +import React from 'react'; import Chip from '@material-ui/core/Chip'; -import withStyles, { CSSProperties } from '@material-ui/core/styles/withStyles'; +import withStyles from '@material-ui/core/styles/withStyles'; import { createStyles } from '@material-ui/core'; import { yellow, red, green, orange, lime } from '@material-ui/core/colors'; import Status from '../dataModel/Status'; @@ -48,9 +48,9 @@ class StatusComponent extends React.Component { return this.props.classes.readySoonStatus; case (Status.Released): return this.props.classes.releasedStatus; + default: + throw new Error("Bug - probably unhandled new Status"); } - - throw new Error("An unknown status"); } private getLabel(status: Status): string { diff --git a/Invoro.WebApp/Invoro/invoro/src/lib/dataModel/Feature.ts b/Invoro.WebApp/Invoro/invoro/src/lib/dataModel/Feature.ts index 0a5d18c..a188030 100644 --- a/Invoro.WebApp/Invoro/invoro/src/lib/dataModel/Feature.ts +++ b/Invoro.WebApp/Invoro/invoro/src/lib/dataModel/Feature.ts @@ -16,6 +16,10 @@ export default class Feature { this.creationTime = creationTime; this.lastTimeModified = lastTimeModified; } + + public static Compare(a: Feature, b: Feature): number { + return a.name.localeCompare(b.name); + } } export class FeatureDTO extends Feature { diff --git a/Invoro.WebApp/Invoro/invoro/src/lib/dataModel/FeaturesCategory.ts b/Invoro.WebApp/Invoro/invoro/src/lib/dataModel/FeaturesCategory.ts index eef3fc8..27d66aa 100644 --- a/Invoro.WebApp/Invoro/invoro/src/lib/dataModel/FeaturesCategory.ts +++ b/Invoro.WebApp/Invoro/invoro/src/lib/dataModel/FeaturesCategory.ts @@ -1,4 +1,4 @@ -import Feature, { FeatureDTO } from "./Feature" +import Feature from "./Feature" export default class FeatureCategory { public name: string; @@ -8,4 +8,8 @@ export default class FeatureCategory { this.name = name; this.features = [...features]; } + + public static Compare(a: FeatureCategory, b: FeatureCategory): number { + return a.name.localeCompare(b.name); + } } \ No newline at end of file diff --git a/Invoro.WebApp/Invoro/invoro/src/lib/services/StatusToDisplayConverter.service.ts b/Invoro.WebApp/Invoro/invoro/src/lib/services/StatusToDisplayConverter.service.ts index 79594be..b69be49 100644 --- a/Invoro.WebApp/Invoro/invoro/src/lib/services/StatusToDisplayConverter.service.ts +++ b/Invoro.WebApp/Invoro/invoro/src/lib/services/StatusToDisplayConverter.service.ts @@ -13,7 +13,8 @@ export default class StatusToDisplayConverter { return "Ready Soon"; case (Status.Released): return "Released"; + default: + throw new Error(`An unknown status: ${status}`); } - throw new Error(`An unknown status: ${status}`); } } \ No newline at end of file diff --git a/Invoro.WebApp/Invoro/invoro/tsconfig.json b/Invoro.WebApp/Invoro/invoro/tsconfig.json index 0198e1b..096b909 100644 --- a/Invoro.WebApp/Invoro/invoro/tsconfig.json +++ b/Invoro.WebApp/Invoro/invoro/tsconfig.json @@ -30,4 +30,4 @@ "include": [ "src" ] -} +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..48e341a --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3 @@ +{ + "lockfileVersion": 1 +}