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
+}