Skip to content

Commit

Permalink
feat(Redesign Servizi): [IARS-13] Isolate toggle components for servi…
Browse files Browse the repository at this point in the history
…ce contact preference handling (pagopa#3178)

* Extract and isolate new component for email

* WIP unit tests

Signed-off-by: Pietro Grandi <[email protected]>

* Add new components in separate parent

* Last updates before handing over

* Add feature flag

Signed-off-by: Pietro Grandi <[email protected]>

* Minor refactoring proposal

* Remove obsolete code

* Add unit test for PreferenceToggleRow

* Add unit test for ContactPreferencesToggles

Co-authored-by: CrisTofani <[email protected]>
Co-authored-by: fabriziofff <[email protected]>
  • Loading branch information
3 people authored Jul 1, 2021
1 parent c9a3f93 commit 00ae7b4
Show file tree
Hide file tree
Showing 6 changed files with 350 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import * as React from "react";
import { View, StyleSheet } from "react-native";
import Switch from "../../ui/Switch";
import { H4 } from "../../core/typography/H4";
import { IOStyles } from "../../core/variables/IOStyles";

type Props = {
label: string;
onPress: (value: boolean) => void;
value: boolean;
testID?: string;
};

const styles = StyleSheet.create({
row: {
flexDirection: "row",
flex: 1,
justifyContent: "space-between",
paddingVertical: 12
}
});

const PreferenceToggleRow = ({
label,
onPress,
value,
testID = "preference-toggle-row"
}: Props): React.ReactElement => (
<View style={[styles.row]}>
<View style={IOStyles.flex}>
<H4 weight={"Regular"} color={"bluegreyDark"}>
{label}
</H4>
</View>
<Switch value={value} onValueChange={onPress} testID={testID} />
</View>
);

export default PreferenceToggleRow;
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React from "react";
import { NavigationParams } from "react-navigation";
import { createStore } from "redux";
import { NotificationChannelEnum } from "../../../../../definitions/backend/NotificationChannel";
import { applicationChangeState } from "../../../../store/actions/application";
import { GlobalState } from "../../../../store/reducers/types";
import { renderScreenFakeNavRedux } from "../../../../utils/testWrapper";
import { appReducer } from "../../../../store/reducers";
import ROUTES from "../../../../navigation/routes";

import ContactPreferencesToggles from "../index";

jest.useFakeTimers();

describe("ContactPreferencesToggles component", () => {
describe("when channels are not defined", () => {
it("should render all the switches", () => {
const component = renderComponent({});
expect(
component.getByTestId("contact-preferences-inbox-switch")
).toBeDefined();
expect(
component.getByTestId("contact-preferences-webhook-switch")
).toBeDefined();
expect(
component.getByTestId("contact-preferences-email-switch")
).toBeDefined();
});
});

describe("when channels is an empty array", () => {
it("should render the INBOX switch", () => {
const component = renderComponent({ channels: [] });
expect(
component.getByTestId("contact-preferences-inbox-switch")
).toBeDefined();
});
it("should not render WEBHOOK and EMAIL switches", () => {
const component = renderComponent({ channels: [] });
expect(
component.queryByTestId("contact-preferences-webhook-switch")
).toBeNull();
expect(
component.queryByTestId("contact-preferences-email-switch")
).toBeNull();
});
});

describe("when channels contains all the items ", () => {
it("should render all the switches", () => {
const component = renderComponent({
channels: [
NotificationChannelEnum.EMAIL,
NotificationChannelEnum.WEBHOOK
]
});
expect(
component.getByTestId("contact-preferences-inbox-switch")
).toBeDefined();
expect(
component.getByTestId("contact-preferences-webhook-switch")
).toBeDefined();
expect(
component.getByTestId("contact-preferences-email-switch")
).toBeDefined();
});
});
});

function renderComponent(options: {
channels?: ReadonlyArray<NotificationChannelEnum>;
}) {
const globalState = appReducer(undefined, applicationChangeState("active"));
return renderScreenFakeNavRedux<GlobalState, NavigationParams>(
() => <ContactPreferencesToggles {...options} />,
ROUTES.WALLET_CHECKOUT_3DS_SCREEN,
{},
createStore(appReducer, globalState as any)
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from "react";
import { render, fireEvent } from "@testing-library/react-native";

import PreferenceToggleRow from "../PreferenceToggleRow";

describe("PreferenceToggleRow component", () => {
const options = {
label: "Push Notifications",
value: true,
onPress: jest.fn()
};
it("should match the snapshot", () => {
const component = renderComponent(options);
expect(component.toJSON()).toMatchSnapshot();
});

it("should show the label", () => {
const component = renderComponent(options);
expect(component.getByText("Push Notifications")).toBeDefined();
});
it("should expose a working switch", () => {
const component = renderComponent(options);
const switchComponent = component.getByRole("switch");
expect(switchComponent).toBeDefined();
fireEvent(switchComponent, "onValueChange", false);
expect(options.onPress).toHaveBeenCalledWith(false);
});
it("should use a default testID", () => {
const component = renderComponent(options);
expect(component.getByTestId("preference-toggle-row")).toBeDefined();
});
describe("when a testID is passed", () => {
it("should honour the property", () => {
const component = renderComponent({ ...options, testID: "new-test-id" });
expect(component.getByTestId("new-test-id")).toBeDefined();
});
});
});

function renderComponent(
options: Partial<Parameters<typeof PreferenceToggleRow>[0]>
) {
return render(
<PreferenceToggleRow
label="default label"
value={true}
// eslint-disable-next-line @typescript-eslint/no-empty-function
onPress={() => {}}
{...options}
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`PreferenceToggleRow component should match the snapshot 1`] = `
<View
style={
Array [
Object {
"flex": 1,
"flexDirection": "row",
"justifyContent": "space-between",
"paddingVertical": 12,
},
]
}
>
<View
style={
Object {
"flex": 1,
}
}
>
<Text
color="bluegreyDark"
font="TitilliumWeb"
fontStyle={
Object {
"fontSize": 16,
}
}
style={
Array [
Object {
"fontSize": 16,
},
Object {
"color": "#17324D",
"fontFamily": "Titillium Web",
"fontStyle": "normal",
"fontWeight": "400",
},
]
}
weight="Regular"
weightColorFactory={[Function]}
>
Push Notifications
</Text>
</View>
<RCTSwitch
accessibilityRole="switch"
onChange={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
onTintColor="#0073E6"
style={
Array [
Object {
"height": 31,
"width": 51,
},
Object {
"marginBottom": -5,
"marginTop": -5,
},
]
}
testID="preference-toggle-row"
thumbTintColor="default"
tintColor="default"
value={true}
/>
</View>
`;
91 changes: 91 additions & 0 deletions ts/components/services/ContactPreferencesToggles/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import React, { useState } from "react";
import { connect } from "react-redux";
import { fromNullable } from "fp-ts/lib/Option";

import { GlobalState } from "../../../store/reducers/types";
import I18n from "../../../i18n";
import ItemSeparatorComponent from "../../ItemSeparatorComponent";
import { NotificationChannelEnum } from "../../../../definitions/backend/NotificationChannel";
import PreferenceToggleRow from "./PreferenceToggleRow";

type Item = "email" | "push" | "inbox";

type Props = {
channels?: ReadonlyArray<NotificationChannelEnum>;
} & ReturnType<typeof mapStateToProps>;

const ContactPreferencesToggle: React.FC<Props> = (props: Props) => {
// functions from actions
const onValueChange: (item: Item, value: boolean) => void = (_item, _value) =>
undefined;

/*
* TODO State should be removed when data will be available from store
*/
const [inboxSwitched, setInboxSwitched] = useState(true);
const [pushSwitched, setPushSwitched] = useState(true);
const [emailSwitched, setEmailSwitched] = useState(true);

const onInboxValueChange = (value: boolean) => {
onValueChange("inbox", value);
setInboxSwitched(value);
};

const hasChannel = (channel: NotificationChannelEnum) =>
fromNullable(props.channels)
.map(anc => anc.indexOf(channel) !== -1)
.getOrElse(true);

const onPushValueChange = (value: boolean) => {
onValueChange("push", value);
setPushSwitched(value);
};

const onEmailValueChange = (value: boolean) => {
onValueChange("email", value);
setEmailSwitched(value);
};

return (
<>
<PreferenceToggleRow
label={
inboxSwitched
? I18n.t("services.serviceIsEnabled")
: I18n.t("services.serviceNotEnabled")
}
onPress={onInboxValueChange}
value={inboxSwitched}
testID={"contact-preferences-inbox-switch"}
/>
<ItemSeparatorComponent noPadded />
{hasChannel(NotificationChannelEnum.WEBHOOK) && (
<>
<PreferenceToggleRow
label={I18n.t("services.pushNotifications")}
onPress={onPushValueChange}
value={pushSwitched}
testID={"contact-preferences-webhook-switch"}
/>
<ItemSeparatorComponent noPadded />
</>
)}

{hasChannel(NotificationChannelEnum.EMAIL) && (
<>
<PreferenceToggleRow
label={I18n.t("services.emailForwarding")}
onPress={onEmailValueChange}
value={emailSwitched}
testID={"contact-preferences-email-switch"}
/>
<ItemSeparatorComponent noPadded />
</>
)}
</>
);
};

const mapStateToProps = (_state: GlobalState) => ({});

export default connect(mapStateToProps)(ContactPreferencesToggle);
18 changes: 14 additions & 4 deletions ts/screens/services/ServiceDetailsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ import {
import { showToast } from "../../utils/showToast";
import { capitalize, maybeNotNullyString } from "../../utils/strings";
import { handleItemOnPress, ItemAction } from "../../utils/url";
import { servicesRedesignEnabled } from "../../config";
import ContactPreferencesToggles from "../../components/services/ContactPreferencesToggles";

type NavigationParams = Readonly<{
service: ServicePublic;
Expand Down Expand Up @@ -663,11 +665,19 @@ class ServiceDetailsScreen extends React.Component<Props, State> {
<Content>
<Grid>
<OrganizationHeader service={service} />
<View spacer={true} large={true} />
{this.getInboxSwitchRow()}
{this.getPushSwitchRow()}
{this.getEmailSwitchRow()}
</Grid>
<View spacer={true} large={true} />
{servicesRedesignEnabled ? (
<ContactPreferencesToggles
channels={this.service.available_notification_channels}
/>
) : (
<>
{this.getInboxSwitchRow()}
{this.getPushSwitchRow()}
{this.getEmailSwitchRow()}
</>
)}

<View spacer={true} large={true} />
{this.renderItems(potServiceMetadata)}
Expand Down

0 comments on commit 00ae7b4

Please sign in to comment.