Adding
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
import { mount } from "enzyme";
|
||||
import React from "react";
|
||||
import AsyncState from "../../shared/enumerations/AsyncState";
|
||||
import AsyncGuard from "./AsyncGuard";
|
||||
|
||||
describe("A component for displaying async containers", () => {
|
||||
it("Displays an async status: Loading", () => {
|
||||
const container = {
|
||||
data: [],
|
||||
state: AsyncState.Loading
|
||||
};
|
||||
let wrapper = mount(
|
||||
<AsyncGuard container={container}>
|
||||
<>!!ERROR!!</>
|
||||
</AsyncGuard>
|
||||
);
|
||||
|
||||
expect(wrapper.html()).toBe("Loading...");
|
||||
});
|
||||
|
||||
it("Displays an async custom status: Error", () => {
|
||||
const container = {
|
||||
data: [],
|
||||
state: AsyncState.Failure
|
||||
};
|
||||
let wrapper = mount(
|
||||
<AsyncGuard container={container} error={<h1>OH NO</h1>}>
|
||||
<>!!ERROR!!</>
|
||||
</AsyncGuard>
|
||||
);
|
||||
|
||||
expect(wrapper.html()).toBe("<h1>OH NO</h1>");
|
||||
});
|
||||
|
||||
it("Displays an async child on Success", () => {
|
||||
const container = {
|
||||
data: [],
|
||||
state: AsyncState.Success
|
||||
};
|
||||
let wrapper = mount(
|
||||
<AsyncGuard container={container}>
|
||||
<>YES</>
|
||||
</AsyncGuard>
|
||||
);
|
||||
|
||||
expect(wrapper.html()).toBe("YES");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import React from "react";
|
||||
import AsyncState from "../../shared/enumerations/AsyncState";
|
||||
import IAsyncStateContainer from "../../shared/types/IAsyncStateContainer";
|
||||
|
||||
export interface IAsyncProps {
|
||||
container: IAsyncStateContainer<any>;
|
||||
children: JSX.Element;
|
||||
error?: JSX.Element;
|
||||
loading?: JSX.Element;
|
||||
notFound?: JSX.Element;
|
||||
inactive?: JSX.Element;
|
||||
}
|
||||
|
||||
const AsyncGuard: React.FC<IAsyncProps> = (props) => {
|
||||
switch (props.container.state) {
|
||||
case AsyncState.Inactive:
|
||||
default:
|
||||
return props.inactive ? props.inactive : <></>;
|
||||
case AsyncState.Loading:
|
||||
return props.loading ? props.loading : <>Loading...</>;
|
||||
case AsyncState.Invalid:
|
||||
case AsyncState.Failure:
|
||||
return props.error ? props.error : <>Data failed to load</>;
|
||||
case AsyncState.NotFound:
|
||||
return props.notFound ? props.notFound : <>Data was not found</>;
|
||||
case AsyncState.Success:
|
||||
return props.container.data ? (
|
||||
props.children
|
||||
) : (
|
||||
<>No Data Returned</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default AsyncGuard;
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { mount } from "enzyme";
|
||||
import React from "react";
|
||||
import BusinessLineDisplay from "./BusinessLineDisplay";
|
||||
import { getMockAppSettings } from "../../shared/testing/mocks/MockAppSettings";
|
||||
|
||||
describe("A component for displaying business line", () => {
|
||||
const useSelectorMock = jest.spyOn(React, "useContext");
|
||||
|
||||
beforeEach(() => {
|
||||
useSelectorMock.mockClear();
|
||||
});
|
||||
|
||||
it("Displays a valid business line name", () => {
|
||||
useSelectorMock.mockReturnValue(getMockAppSettings());
|
||||
let wrapper = mount(<BusinessLineDisplay businessLineCode="OPS" />);
|
||||
|
||||
expect(wrapper.html()).toBe("Operations");
|
||||
});
|
||||
|
||||
it("Displays an invalid business line name for a code", () => {
|
||||
useSelectorMock.mockReturnValue(getMockAppSettings());
|
||||
let wrapper = mount(<BusinessLineDisplay businessLineCode="XYZ" />);
|
||||
expect(wrapper.html()).toBe("Missing");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
import { SettingsContext } from "../../shared/providers/SettingsProvider";
|
||||
|
||||
interface IBusinessLineDisplayProps {
|
||||
businessLineCode: string;
|
||||
}
|
||||
|
||||
const BusinessLineDisplay: React.FC<IBusinessLineDisplayProps> = (props) => {
|
||||
const settings = React.useContext(SettingsContext);
|
||||
const businessName = settings?.businessLines.find(
|
||||
(x) => x.code === props.businessLineCode
|
||||
);
|
||||
|
||||
return props.businessLineCode && businessName ? (
|
||||
<>{businessName.name}</>
|
||||
) : (
|
||||
<>Missing</>
|
||||
);
|
||||
};
|
||||
|
||||
export default BusinessLineDisplay;
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
import { mount } from "enzyme";
|
||||
import React from "react";
|
||||
import DepartmentDisplay from "./DepartmentDisplay";
|
||||
import AsyncState from "../../shared/enumerations/AsyncState";
|
||||
import { IDepartmentState } from "../../reducers/DepartmentReducer";
|
||||
import { getMockAppSettings } from "../../shared/testing/mocks/MockAppSettings";
|
||||
import { getMockDepartments } from "../../shared/testing/mocks/MockDepartments";
|
||||
|
||||
describe("A component for displaying departments", () => {
|
||||
const useContextMock = jest.spyOn(React, "useContext");
|
||||
|
||||
beforeEach(() => {
|
||||
useContextMock.mockClear();
|
||||
});
|
||||
|
||||
it("Displays a valid department", () => {
|
||||
const departments: IDepartmentState = {
|
||||
departments: {
|
||||
data: [
|
||||
{
|
||||
id: 4,
|
||||
departmentId: 5,
|
||||
departmentName: "bad test",
|
||||
state: "Active",
|
||||
businessLineCode: "XY"
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
departmentId: 6,
|
||||
departmentName: "good test",
|
||||
state: "Active",
|
||||
businessLineCode: "XY"
|
||||
}
|
||||
],
|
||||
state: AsyncState.Success
|
||||
},
|
||||
submitState: {
|
||||
state: AsyncState.Success
|
||||
}
|
||||
};
|
||||
|
||||
const settings = getMockAppSettings();
|
||||
settings.departments = getMockDepartments();
|
||||
useContextMock.mockReturnValue(settings);
|
||||
const wrapper = mount(<DepartmentDisplay departmentId={55} />);
|
||||
const found = wrapper.find('[data-test="department-display-wrapper"]');
|
||||
expect(found.length).toBe(1);
|
||||
expect(found.children().first().props().title).toBe("55");
|
||||
expect(found.children().first().text()).toBe("WAUWATOSA");
|
||||
});
|
||||
|
||||
it("Displays a invalid department", () => {
|
||||
const wrapper = mount(<DepartmentDisplay departmentId={55} />);
|
||||
const found = wrapper.find('[data-test="department-display-wrapper"]');
|
||||
expect(found.length).toBe(1);
|
||||
expect(found.children().first().text()).toBe("Missing");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
import React from "react";
|
||||
import { SettingsContext } from "../../shared/providers/SettingsProvider";
|
||||
import Configuration from "../../shared/configuration/Configuration";
|
||||
|
||||
export enum DepartmentDisplayTargets {
|
||||
Detail = "detail",
|
||||
Dashboard = "dashboard"
|
||||
}
|
||||
interface IDepartmentDisplayProps {
|
||||
departmentId: number;
|
||||
target?: DepartmentDisplayTargets;
|
||||
nameOverride?: string;
|
||||
}
|
||||
|
||||
const DepartmentDisplay: React.FC<IDepartmentDisplayProps> = (props) => {
|
||||
const settings = React.useContext(SettingsContext);
|
||||
const department = settings?.departments.find(
|
||||
(x) => x.id === props.departmentId
|
||||
);
|
||||
|
||||
let href = Configuration.PrefixAppUrlWithBasename(
|
||||
Configuration.Get.appUrls.department
|
||||
).replace(":id", props.departmentId.toString());
|
||||
|
||||
if (props.target && props.target === DepartmentDisplayTargets.Dashboard) {
|
||||
href =
|
||||
Configuration.PrefixAppUrlWithBasename(
|
||||
Configuration.Get.appUrls.dashboard
|
||||
) +
|
||||
"?dashboard=department&departmentId=" +
|
||||
props.departmentId;
|
||||
}
|
||||
|
||||
return (
|
||||
<span data-test="department-display-wrapper">
|
||||
{props.departmentId && department ? (
|
||||
<a href={href} title={department.id + ""}>
|
||||
{props.nameOverride ?? department.departmentName}
|
||||
</a>
|
||||
) : (
|
||||
<>Missing</>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export default DepartmentDisplay;
|
||||
@@ -0,0 +1,17 @@
|
||||
import React from "react";
|
||||
import { IEmployee } from "../../shared/models/Employee";
|
||||
|
||||
export interface IEmployeeStatusGuardProps {
|
||||
employee: IEmployee;
|
||||
children: JSX.Element;
|
||||
}
|
||||
|
||||
const EmployeeStatusGuard: React.FC<IEmployeeStatusGuardProps> = (props) => {
|
||||
const shouldRenderChildren = () => {
|
||||
return props.employee && !props.employee.terminated;
|
||||
};
|
||||
|
||||
return shouldRenderChildren() ? props.children : <></>;
|
||||
};
|
||||
|
||||
export default EmployeeStatusGuard;
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
import { mount } from "enzyme";
|
||||
import React from "react";
|
||||
import AsyncState from "../../shared/enumerations/AsyncState";
|
||||
import * as reactRedux from "react-redux";
|
||||
import { IProfileState } from "../../reducers/ProfileReducer";
|
||||
import PermissionGuard from "./PermissionGuard";
|
||||
import moment from "moment";
|
||||
import { UserType } from "../../shared/models/Auditable";
|
||||
import PermissionCode from "../../shared/enumerations/PermissionCode";
|
||||
|
||||
describe("A component for requiring permissions", () => {
|
||||
const useSelectorMock = jest.spyOn(reactRedux, "useSelector");
|
||||
const useDispatchMock = jest.spyOn(reactRedux, "useDispatch");
|
||||
|
||||
beforeEach(() => {
|
||||
useSelectorMock.mockClear();
|
||||
useDispatchMock.mockClear();
|
||||
});
|
||||
|
||||
it("Displays children when role permission present", () => {
|
||||
jest.mock("react-router-dom", () => ({
|
||||
...jest.requireActual("react-router-dom"),
|
||||
useParams: () => ({
|
||||
employeeId: "11771"
|
||||
})
|
||||
}));
|
||||
|
||||
const profile: IProfileState = {
|
||||
profile: {
|
||||
data: {
|
||||
adEmail: "[email protected]",
|
||||
personId: 11,
|
||||
employeeId: "11771",
|
||||
homeDepartment: "HOME",
|
||||
baseRole: "QA",
|
||||
fullName: "John Tester",
|
||||
role: {
|
||||
name: "Admin",
|
||||
description: "admin",
|
||||
permissions: [
|
||||
{
|
||||
code: "VIEW_DASHBOARD",
|
||||
description: "View Dashboard"
|
||||
}
|
||||
],
|
||||
hasCommissions: false
|
||||
},
|
||||
additionalPermissions: [],
|
||||
departments: [
|
||||
{
|
||||
id: 1,
|
||||
role: {
|
||||
name: "Admin",
|
||||
description: "admin",
|
||||
permissions: [
|
||||
{
|
||||
code: "VIEW_DASHBOARD",
|
||||
description: "View Dashboard"
|
||||
}
|
||||
],
|
||||
hasCommissions: false
|
||||
},
|
||||
departmentId: 114,
|
||||
employeeId: "11771",
|
||||
isHome: true,
|
||||
note: "asdf",
|
||||
slidingPayout: 100,
|
||||
startDate: moment(),
|
||||
lastUpdated: moment(),
|
||||
createdByType: UserType.Associate,
|
||||
createdBy: "asdf",
|
||||
endDate: undefined,
|
||||
lastUpdatedByType: UserType.Associate,
|
||||
created: moment(),
|
||||
lastUpdatedBy: "asdf"
|
||||
}
|
||||
],
|
||||
assignments: []
|
||||
},
|
||||
state: AsyncState.Success
|
||||
},
|
||||
searchResults: {
|
||||
state: AsyncState.Success,
|
||||
data: []
|
||||
}
|
||||
};
|
||||
|
||||
const dummyDispatch = jest.fn();
|
||||
useDispatchMock.mockReturnValue(dummyDispatch);
|
||||
useSelectorMock.mockReturnValue(profile);
|
||||
let wrapper = mount(
|
||||
<PermissionGuard requiredPermissions={["VIEW_DASHBOARD"]}>
|
||||
<h1>Authorized</h1>
|
||||
</PermissionGuard>
|
||||
);
|
||||
|
||||
expect(wrapper.html()).toBe("<h1>Authorized</h1>");
|
||||
});
|
||||
|
||||
it("Displays children when additional permission present", () => {
|
||||
jest.mock("react-router-dom", () => ({
|
||||
...jest.requireActual("react-router-dom"),
|
||||
useParams: () => ({
|
||||
employeeId: "11771"
|
||||
})
|
||||
}));
|
||||
|
||||
const profile: IProfileState = {
|
||||
profile: {
|
||||
data: {
|
||||
adEmail: "[email protected]",
|
||||
personId: 11,
|
||||
employeeId: "11771",
|
||||
homeDepartment: "HOME",
|
||||
baseRole: "QA",
|
||||
fullName: "John Tester",
|
||||
role: {
|
||||
name: "Admin",
|
||||
description: "admin",
|
||||
permissions: [],
|
||||
hasCommissions: false
|
||||
},
|
||||
additionalPermissions: [
|
||||
{
|
||||
id: 1,
|
||||
created: moment().utc(),
|
||||
createdByType: UserType.System,
|
||||
createdBy: "Tester1",
|
||||
permission: {
|
||||
code: "VIEW_DASHBOARD",
|
||||
description: "View Dashboard"
|
||||
}
|
||||
}
|
||||
],
|
||||
departments: [],
|
||||
assignments: []
|
||||
},
|
||||
state: AsyncState.Success
|
||||
},
|
||||
searchResults: {
|
||||
state: AsyncState.Success,
|
||||
data: []
|
||||
}
|
||||
};
|
||||
|
||||
const dummyDispatch = jest.fn();
|
||||
useDispatchMock.mockReturnValue(dummyDispatch);
|
||||
useSelectorMock.mockReturnValue(profile);
|
||||
const wrapper = mount(
|
||||
<PermissionGuard
|
||||
requiredPermissions={[PermissionCode.VIEW_DASHBOARD]}
|
||||
>
|
||||
<h1>Authorized</h1>
|
||||
</PermissionGuard>
|
||||
);
|
||||
|
||||
expect(wrapper.html()).toBe("<h1>Authorized</h1>");
|
||||
});
|
||||
|
||||
it("Displays nothing when permission not present", () => {
|
||||
jest.mock("react-router-dom", () => ({
|
||||
...jest.requireActual("react-router-dom"),
|
||||
useParams: () => ({
|
||||
employeeId: "11771"
|
||||
})
|
||||
}));
|
||||
|
||||
const profile: IProfileState = {
|
||||
profile: {
|
||||
data: {
|
||||
adEmail: "[email protected]",
|
||||
personId: 11,
|
||||
employeeId: "11771",
|
||||
homeDepartment: "HOME",
|
||||
baseRole: "QA",
|
||||
fullName: "John Tester",
|
||||
role: {
|
||||
name: "Admin",
|
||||
description: "admin",
|
||||
permissions: [
|
||||
{
|
||||
code: "VIEW_DASHBOARD",
|
||||
description: "View Dashboard"
|
||||
}
|
||||
],
|
||||
hasCommissions: false
|
||||
},
|
||||
additionalPermissions: [],
|
||||
departments: [],
|
||||
assignments: []
|
||||
},
|
||||
state: AsyncState.Success
|
||||
},
|
||||
searchResults: {
|
||||
state: AsyncState.Success,
|
||||
data: []
|
||||
}
|
||||
};
|
||||
|
||||
const dummyDispatch = jest.fn();
|
||||
useDispatchMock.mockReturnValue(dummyDispatch);
|
||||
useSelectorMock.mockReturnValue(profile);
|
||||
let wrapper = mount(
|
||||
<PermissionGuard requiredPermissions={["ALL_THE_THINGS"]}>
|
||||
<h1>Authorized</h1>
|
||||
</PermissionGuard>
|
||||
);
|
||||
|
||||
expect(wrapper.html()).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
import React from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { IAppState } from "../../store/Store";
|
||||
import { IProfileState } from "../../reducers/ProfileReducer";
|
||||
import AsyncState from "../../shared/enumerations/AsyncState";
|
||||
import { Alert } from "reactstrap";
|
||||
import ProfileService from "../../shared/services/ProfileService";
|
||||
|
||||
export interface IPermissionProps {
|
||||
requiredPermissions: string[];
|
||||
showWarning?: boolean;
|
||||
children: JSX.Element;
|
||||
}
|
||||
|
||||
const PermissionGuard: React.FC<IPermissionProps> = (props) => {
|
||||
const profileState = useSelector<IAppState, IProfileState>(
|
||||
(state) => state.profileState
|
||||
);
|
||||
const profile = profileState.profile;
|
||||
|
||||
const shouldRenderChildren = () => {
|
||||
const requiredPermissions = props.requiredPermissions;
|
||||
if (!requiredPermissions || requiredPermissions.length < 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (profile.state !== AsyncState.Success || !profile.data) {
|
||||
return false;
|
||||
}
|
||||
const profileData = profile.data;
|
||||
return ProfileService.hasAnyPermission(
|
||||
profileData,
|
||||
requiredPermissions
|
||||
);
|
||||
};
|
||||
|
||||
if (shouldRenderChildren()) {
|
||||
return props.children;
|
||||
}
|
||||
return props.showWarning ? (
|
||||
<Alert color="danger">You do not have access.</Alert>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
};
|
||||
|
||||
export default PermissionGuard;
|
||||
@@ -0,0 +1,25 @@
|
||||
import { mount } from "enzyme";
|
||||
import React from "react";
|
||||
import ProductDisplay from "./ProductDisplay";
|
||||
import { getMockAppSettings } from "../../shared/testing/mocks/MockAppSettings";
|
||||
|
||||
describe("A component for displaying products", () => {
|
||||
const useSelectorMock = jest.spyOn(React, "useContext");
|
||||
|
||||
beforeEach(() => {
|
||||
useSelectorMock.mockClear();
|
||||
});
|
||||
|
||||
it("Displays a valid product", () => {
|
||||
useSelectorMock.mockReturnValue(getMockAppSettings());
|
||||
let wrapper = mount(<ProductDisplay productCode="CheckCard" />);
|
||||
|
||||
expect(wrapper.html()).toBe("Check Card");
|
||||
});
|
||||
|
||||
it("Displays an invalid product", () => {
|
||||
useSelectorMock.mockReturnValue(getMockAppSettings());
|
||||
let wrapper = mount(<ProductDisplay productCode="XYZ" />);
|
||||
expect(wrapper.html()).toBe("Missing");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import React from "react";
|
||||
import { SettingsContext } from "../../shared/providers/SettingsProvider";
|
||||
|
||||
interface IDepartmentDisplayProps {
|
||||
productCode: string;
|
||||
}
|
||||
|
||||
const ProductDisplay: React.FC<IDepartmentDisplayProps> = (props) => {
|
||||
const settings = React.useContext(SettingsContext);
|
||||
const product = settings?.productDetails.find(
|
||||
(x) => x.product === props.productCode
|
||||
);
|
||||
|
||||
return props.productCode && product ? <>{product.title}</> : <>Missing</>;
|
||||
};
|
||||
|
||||
export default ProductDisplay;
|
||||
@@ -0,0 +1,7 @@
|
||||
.term-user {
|
||||
text-decoration: line-through;
|
||||
color: darkred;
|
||||
}
|
||||
|
||||
.active-user {
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import React from "react";
|
||||
import { SettingsContext } from "../../shared/providers/SettingsProvider";
|
||||
import Configuration from "../../shared/configuration/Configuration";
|
||||
import { IUserSummary } from "../../shared/models/Settings";
|
||||
import "./UserDisplay.css";
|
||||
import moment from "moment";
|
||||
|
||||
export enum UserDisplayTargets {
|
||||
Profile = "profile",
|
||||
Dashboard = "dashboard"
|
||||
}
|
||||
|
||||
interface IUserDisplayProps {
|
||||
employeeId: string;
|
||||
target?: UserDisplayTargets;
|
||||
}
|
||||
|
||||
const UserDisplay: React.FC<IUserDisplayProps> = (props) => {
|
||||
const settings = React.useContext(SettingsContext);
|
||||
const user = settings?.employees.find(
|
||||
(x) => x.employeeId === props.employeeId
|
||||
);
|
||||
|
||||
function determineDecoration(user: IUserSummary) {
|
||||
return user.terminated ? "term-appUser" : "active-appUser";
|
||||
}
|
||||
|
||||
let terminated = "";
|
||||
if (user && user.terminated) {
|
||||
terminated = moment(user.terminated).format(" (MM/DD/YYYY)");
|
||||
}
|
||||
|
||||
const toTitleCase = (str: string) => {
|
||||
return str.replace(/\w\S*/g, function (txt) {
|
||||
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
|
||||
});
|
||||
};
|
||||
|
||||
let href = Configuration.PrefixAppUrlWithBasename(
|
||||
Configuration.Get.appUrls.profile
|
||||
).replace(":employeeId?", props.employeeId);
|
||||
|
||||
if (props.target && props.target === UserDisplayTargets.Dashboard) {
|
||||
href =
|
||||
Configuration.PrefixAppUrlWithBasename(
|
||||
Configuration.Get.appUrls.dashboard
|
||||
) +
|
||||
"?dashboard=employee&employeeId=" +
|
||||
props.employeeId;
|
||||
}
|
||||
|
||||
return (
|
||||
<span data-test="department-display-wrapper">
|
||||
{props.employeeId && user ? (
|
||||
<a
|
||||
href={href}
|
||||
title={user.employeeId + ", manager is " + user.managerKey}
|
||||
className={determineDecoration(user)}
|
||||
>
|
||||
{toTitleCase(user.fullName)}
|
||||
{terminated}
|
||||
</a>
|
||||
) : (
|
||||
<>Missing</>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserDisplay;
|
||||
Reference in New Issue
Block a user