Repository: kiwicom/mobile
Pull request #1347 (2018-11-21 09:43:29)

Hotels: Refactor state logic from HotelDetailScreen to context
We need this context when adding offline screen to hotel details


Patch:
diff --git a/app/hotels/src/HotelsContext.js b/app/hotels/src/HotelsContext.js
index b6f822ad..fb4247b4 100644
--- a/app/hotels/src/HotelsContext.js
+++ b/app/hotels/src/HotelsContext.js
@@ -58,6 +58,7 @@ type Props = {|
   +hotelId: ?string,
   +apiProvider: ApiProvider,
   +closeHotels: () => void,
+  +guestCount: number,
 |};
 
 type State = {|
@@ -138,16 +139,7 @@ class Provider extends React.Component<Props, State> {
 
   setHotelId = (hotelId: string) => this.setState({ hotelId });
 
-  getGuestCount = () => {
-    if (this.state.roomsConfiguration == null) {
-      return 0;
-    }
-    return this.state.roomsConfiguration.reduce((sum, current) => {
-      const adults = current.adultsCount;
-      const children = current.children?.length ?? 0;
-      return sum + adults + children;
-    }, 0);
-  };
+  getGuestCount = () => this.props.guestCount;
 
   setPaymentLink = (paymentLink: ?string) => this.setState({ paymentLink });
 
diff --git a/app/hotels/src/appRegistry/RootComponent.js b/app/hotels/src/appRegistry/RootComponent.js
index 19676038..a0999614 100644
--- a/app/hotels/src/appRegistry/RootComponent.js
+++ b/app/hotels/src/appRegistry/RootComponent.js
@@ -15,6 +15,7 @@ import HotelsContext, {
 } from '../HotelsContext';
 import SearchResultsContext from '../navigation/allHotels/SearchResultsContext';
 import type { Coordinates } from '../CoordinatesType';
+import HotelDetailScreenContext from '../singleHotel/HotelDetailScreenContext';
 
 type Props = {|
   +dataSaverEnabled: boolean,
@@ -44,32 +45,49 @@ export default class RootComponent extends React.Component<Props> {
     }
   };
 
+  getGuestCount = () => {
+    if (this.props.roomsConfiguration == null) {
+      return 0;
+    }
+    return this.props.roomsConfiguration.reduce((sum, current) => {
+      const adults = current.adultsCount;
+      const children = current.children?.length ?? 0;
+      return sum + adults + children;
+    }, 0);
+  };
+
   render() {
+    const guestCount = this.getGuestCount();
     return (
-      <SearchResultsContext.Provider>
-        <ConfigContext.Provider dataSaverEnabled={this.props.dataSaverEnabled}>
-          <HotelsFilterContext.Provider>
-            <HotelsContext.Provider
-              version={this.props.version}
-              cityId={this.props.cityId}
-              checkin={this.props.checkin}
-              checkout={this.props.checkout}
-              roomsConfiguration={this.props.roomsConfiguration}
-              currency={this.props.currency}
-              cityName={this.props.cityName}
-              latitude={this.props.coordinates?.latitude ?? null}
-              longitude={this.props.coordinates?.longitude ?? null}
-              hotelId={this.props.hotelId}
-              apiProvider={this.props.apiProvider}
-              closeHotels={this.onClosePress}
-            >
-              <Dimensions.Provider dimensions={this.props.dimensions}>
-                {this.props.children}
-              </Dimensions.Provider>
-            </HotelsContext.Provider>
-          </HotelsFilterContext.Provider>
-        </ConfigContext.Provider>
-      </SearchResultsContext.Provider>
+      <HotelDetailScreenContext guestCount={guestCount}>
+        <SearchResultsContext.Provider>
+          <ConfigContext.Provider
+            dataSaverEnabled={this.props.dataSaverEnabled}
+          >
+            <HotelsFilterContext.Provider>
+              <HotelsContext.Provider
+                version={this.props.version}
+                cityId={this.props.cityId}
+                checkin={this.props.checkin}
+                checkout={this.props.checkout}
+                roomsConfiguration={this.props.roomsConfiguration}
+                currency={this.props.currency}
+                cityName={this.props.cityName}
+                latitude={this.props.coordinates?.latitude ?? null}
+                longitude={this.props.coordinates?.longitude ?? null}
+                hotelId={this.props.hotelId}
+                apiProvider={this.props.apiProvider}
+                closeHotels={this.onClosePress}
+                guestCount={guestCount}
+              >
+                <Dimensions.Provider dimensions={this.props.dimensions}>
+                  {this.props.children}
+                </Dimensions.Provider>
+              </HotelsContext.Provider>
+            </HotelsFilterContext.Provider>
+          </ConfigContext.Provider>
+        </SearchResultsContext.Provider>
+      </HotelDetailScreenContext>
     );
   }
 }
diff --git a/app/hotels/src/singleHotel/HotelDetailScreen.js b/app/hotels/src/singleHotel/HotelDetailScreen.js
index 62d51212..7d824d2a 100644
--- a/app/hotels/src/singleHotel/HotelDetailScreen.js
+++ b/app/hotels/src/singleHotel/HotelDetailScreen.js
@@ -15,7 +15,6 @@ import {
 } from '@kiwicom/mobile-shared';
 import { createFragmentContainer, graphql } from '@kiwicom/mobile-relay';
 import { Translation } from '@kiwicom/mobile-localization';
-import isEqual from 'react-fast-compare';
 import { SafeAreaView } from 'react-navigation';
 import { defaultTokens } from '@kiwicom/mobile-orbit';
 
@@ -27,7 +26,10 @@ import BookNow from './bookNow/BookNow';
 import BrandLabel from './brandLabel/BrandLabel';
 import type { RoomsConfiguration } from '../singleHotel/AvailableHotelSearchInput';
 import type { HotelDetailScreen_availableHotel } from './__generated__/HotelDetailScreen_availableHotel.graphql';
-import countBookingPrice from './bookNow/countBookingPrice';
+import {
+  withHotelDetailScreenContext,
+  type HotelDetailScreenState,
+} from './HotelDetailScreenContext';
 import {
   withHotelsContext,
   type HotelsContextState,
@@ -40,10 +42,6 @@ type PropsWithContext = {|
 |};
 
 type State = {|
-  selected: {
-    [string]: number, // originalId: count
-  },
-  maxPersons: number,
   barStyle: BarStyle,
 |};
 
@@ -63,8 +61,6 @@ export class HotelDetailScreen extends React.Component<
     super(props);
 
     this.state = {
-      selected: {},
-      maxPersons: 0,
       barStyle: Platform.select({
         android: 'default',
         ios: this.isNarrowLayout() ? 'light-content' : 'dark-content',
@@ -88,12 +84,9 @@ export class HotelDetailScreen extends React.Component<
     this.props.setPaymentLink(this.props.paymentLink);
   }
 
-  shouldComponentUpdate = (nextProps: PropsWithContext, nextState: State) => {
-    const isPropsEqual = isEqual(nextProps, this.props);
-    const isStateEqual = isEqual(nextState, this.state);
-
-    return !isPropsEqual || !isStateEqual;
-  };
+  componentWillUnmount() {
+    this.props.reset();
+  }
 
   isNarrowLayout = () => {
     return this.props.width < Device.DEVICE_THRESHOLD;
@@ -121,55 +114,9 @@ export class HotelDetailScreen extends React.Component<
     }
   };
 
-  updateSelectedCount = (
-    availabilityOriginalId: string,
-    amount: number,
-    maxPersons: number,
-  ) => {
-    this.setState(state => {
-      const previousCount = state.selected[availabilityOriginalId] ?? 0;
-
-      return {
-        ...state,
-        selected: {
-          ...state.selected,
-          [availabilityOriginalId]: previousCount + amount,
-        },
-        maxPersons: state.maxPersons + maxPersons,
-      };
-    });
-  };
-
-  selectRoom = (availabilityOriginalId: string, maxPersons: number) => {
-    this.updateSelectedCount(availabilityOriginalId, 1, maxPersons);
-  };
-
-  deselectRoom = (availabilityOriginalId: string, maxPersons: number) => {
-    this.updateSelectedCount(availabilityOriginalId, -1, -maxPersons);
-  };
-
-  getNumberOfRooms = () =>
-    Object.keys(this.state.selected).reduce((sum, currentItem) => {
-      return this.state.selected[currentItem] + sum;
-    }, 0);
-
-  getPersonCount = () => {
-    const guestTotal = this.props.getGuestCount();
-
-    return this.state.maxPersons > guestTotal
-      ? guestTotal
-      : this.state.maxPersons;
-  };
-
   render() {
     const { availableHotel } = this.props;
-    const { selected } = this.state;
 
-    const disabled = this.props.getGuestCount() <= this.getNumberOfRooms();
-    const price = countBookingPrice(
-      this.props.availableHotel?.availableRooms,
-      selected,
-    );
     if (!availableHotel) {
       return (
         <GeneralError
@@ -192,22 +139,12 @@ export class HotelDetailScreen extends React.Component<
             <LayoutSingleColumn barStyle={this.state.barStyle}>
               <Header hotel={availableHotel.hotel} />
               <HotelInformation hotel={availableHotel.hotel} />
-              <RoomList
-                data={availableHotel.availableRooms}
-                select={this.selectRoom}
-                deselect={this.deselectRoom}
-                selected={selected}
-                disabled={disabled}
-              />
+              <RoomList data={availableHotel.availableRooms} />
               <BrandLabel />
             </LayoutSingleColumn>
           </ScrollView>
           <View style={styles.buttonContainer}>
-            <RoomSummary
-              guestCount={this.getPersonCount()}
-              roomCount={this.getNumberOfRooms()}
-              price={price}
-            />
+            <RoomSummary availableRooms={availableHotel.availableRooms} />
             <View style={styles.row}>
               <AdaptableLayout
                 renderOnNarrow={
@@ -216,9 +153,9 @@ export class HotelDetailScreen extends React.Component<
                   </View>
                 }
               />
-              {this.state.maxPersons > 0 && (
+              {this.props.maxPersons > 0 && (
                 <View style={styles.bookNowWrapper}>
-                  <BookNow selected={selected} hotel={availableHotel.hotel} />
+                  <BookNow hotel={availableHotel.hotel} />
                 </View>
               )}
             </View>
@@ -237,6 +174,8 @@ type Props = {|
   +paymentLink?: ?string,
   +setPaymentLink: (?string) => void,
   +apiProvider: ApiProvider,
+  +maxPersons: number,
+  +reset: () => void,
 |};
 
 class HotelDetailScreenWithContext extends React.Component<Props> {
@@ -259,8 +198,20 @@ const select = ({
   apiProvider,
 });
 
+const selectHotelDetailScreen = ({
+  maxPersons,
+  actions: { reset },
+}: HotelDetailScreenState) => ({
+  maxPersons,
+  reset,
+});
+
 export default createFragmentContainer(
-  withHotelsContext(select)(HotelDetailScreenWithContext),
+  withHotelsContext(select)(
+    withHotelDetailScreenContext(selectHotelDetailScreen)(
+      HotelDetailScreenWithContext,
+    ),
+  ),
   graphql`
     fragment HotelDetailScreen_availableHotel on HotelAvailabilityInterface {
       hotel {
diff --git a/app/hotels/src/singleHotel/HotelDetailScreenContext.js b/app/hotels/src/singleHotel/HotelDetailScreenContext.js
new file mode 100644
index 00000000..c3ac379e
--- /dev/null
+++ b/app/hotels/src/singleHotel/HotelDetailScreenContext.js
@@ -0,0 +1,120 @@
+// @flow
+
+import * as React from 'react';
+import { withContext } from '@kiwicom/mobile-shared';
+
+const noop = () => {};
+
+const defaultState = {
+  selected: {},
+  maxPersons: 0,
+  numberOfRooms: 0,
+  getPersonCount: () => 0,
+  actions: {
+    selectRoom: noop,
+    deselectRoom: noop,
+    reset: noop,
+  },
+};
+
+const { Provider: ContextProvider, Consumer } = React.createContext<State>(
+  defaultState,
+);
+
+type Props = {|
+  +children: React.Node,
+  +guestCount: number,
+|};
+
+type State = {|
+  selected: {
+    [string]: number, // originalId: count
+  },
+  maxPersons: number,
+  numberOfRooms: number,
+  +getPersonCount: () => number,
+  +actions: {|
+    +selectRoom: (id: string, maxPersons: number) => void,
+    +deselectRoom: (id: string, maxPersons: number) => void,
+    +reset: () => void,
+  |},
+|};
+
+export class Provider extends React.Component<Props, State> {
+  constructor() {
+    super();
+
+    this.state = {
+      ...defaultState,
+      getPersonCount: this.getPersonCount,
+      actions: {
+        selectRoom: this.selectRoom,
+        deselectRoom: this.deselectRoom,
+        reset: this.reset,
+      },
+    };
+  }
+
+  selectRoom = (id: string, maxPersons: number) => {
+    this.updateSelectedCount(id, 1, maxPersons);
+  };
+
+  deselectRoom = (id: string, maxPersons: number) => {
+    this.updateSelectedCount(id, -1, -maxPersons);
+  };
+
+  updateSelectedCount = (
+    availabilityOriginalId: string,
+    amount: number,
+    maxPersons: number,
+  ) => {
+    this.setState(state => {
+      const previousCount = state.selected[availabilityOriginalId] ?? 0;
+      const selected = {
+        ...state.selected,
+        [availabilityOriginalId]: previousCount + amount,
+      };
+      return {
+        ...state,
+        selected,
+        maxPersons: state.maxPersons + maxPersons,
+        numberOfRooms: this.getNumberOfRooms(selected),
+      };
+    });
+  };
+
+  getNumberOfRooms = (selected: $PropertyType<State, 'selected'>) =>
+    Object.keys(selected).reduce((sum, currentItem) => {
+      return selected[currentItem] + sum;
+    }, 0);
+
+  getPersonCount = () => {
+    return this.state.maxPersons > this.props.guestCount
+      ? this.props.guestCount
+      : this.state.maxPersons;
+  };
+
+  reset = () => {
+    this.setState({
+      selected: {},
+      maxPersons: 0,
+      numberOfRooms: 0,
+    });
+  };
+
+  render() {
+    return (
+      <ContextProvider value={this.state}>
+        {this.props.children}
+      </ContextProvider>
+    );
+  }
+}
+
+export const withHotelDetailScreenContext = (
+  select: (state: State) => Object,
+) => withContext(select, Consumer);
+
+export type HotelDetailScreenState = State;
+
+export default Provider;
diff --git a/app/hotels/src/singleHotel/RoomSummary.js b/app/hotels/src/singleHotel/RoomSummary.js
index 339067c1..bd37cad5 100644
--- a/app/hotels/src/singleHotel/RoomSummary.js
+++ b/app/hotels/src/singleHotel/RoomSummary.js
@@ -6,21 +6,30 @@ import { Text, StyleSheet, Price } from '@kiwicom/mobile-shared';
 import { defaultTokens } from '@kiwicom/mobile-orbit';
 import { View } from 'react-native';
 
-type PriceType = {|
-  amount: number,
-  currency: string,
-|};
+import {
+  withHotelDetailScreenContext,
+  type HotelDetailScreenState,
+} from './HotelDetailScreenContext';
+import type { HotelDetailScreen_availableHotel } from './__generated__/HotelDetailScreen_availableHotel.graphql';
+import countBookingPrice from './bookNow/countBookingPrice';
 
 type Props = {|
-  +guestCount: number,
-  +roomCount: number,
-  +price: ?PriceType,
+  +getPersonCount: () => number,
+  +numberOfRooms: number,
+  +availableRooms: ?$PropertyType<
+    HotelDetailScreen_availableHotel,
+    'availableRooms',
+  >,
+  +selected: $PropertyType<HotelDetailScreenState, 'selected'>,
 |};
 
-export default function RoomSummary(props: Props) {
-  if (props.roomCount === 0 || props.price == null) {
+function RoomSummary(props: Props) {
+  const price = countBookingPrice(props.availableRooms, props.selected);
+
+  if (props.numberOfRooms === 0 || price == null) {
     return null;
   }
+
   return (
     <View style={styles.container}>
       <View style={styles.content}>
@@ -31,11 +40,11 @@ export default function RoomSummary(props: Props) {
           <Translation
             id="single_hotel.book_now.description"
             values={{
-              personCount: props.guestCount,
-              numberOfRooms: props.roomCount,
+              personCount: props.getPersonCount(),
+              numberOfRooms: props.numberOfRooms,
             }}
           />
-          <Price price={props.price} style={styles.currency} />
+          <Price price={price} style={styles.currency} />
         </View>
       </View>
     </View>
@@ -68,3 +77,15 @@ const styles = StyleSheet.create({
     fontWeight: '800',
   },
 });
+
+const select = ({
+  numberOfRooms,
+  selected,
+  getPersonCount,
+}: HotelDetailScreenState) => ({
+  numberOfRooms,
+  selected,
+  getPersonCount,
+});
+
+export default withHotelDetailScreenContext(select)(RoomSummary);
diff --git a/app/hotels/src/singleHotel/bookNow/BookNow.js b/app/hotels/src/singleHotel/bookNow/BookNow.js
index e50c0ae0..af702839 100644
--- a/app/hotels/src/singleHotel/bookNow/BookNow.js
+++ b/app/hotels/src/singleHotel/bookNow/BookNow.js
@@ -16,6 +16,10 @@ import {
   withHotelsContext,
   type HotelsContextState,
 } from '../../HotelsContext';
+import {
+  withHotelDetailScreenContext,
+  type HotelDetailScreenState,
+} from '../HotelDetailScreenContext';
 
 type Props = {
   +selected: {
@@ -51,9 +55,14 @@ export class BookNow extends React.Component<Props> {
 }
 
 const select = ({ currency }: HotelsContextState) => ({ currency });
+const selectHotelDetailScreen = ({ selected }: HotelDetailScreenState) => ({
+  selected,
+});
 
 export default createFragmentContainer(
-  withHotelsContext(select)(withNavigation(BookNow)),
+  withHotelDetailScreenContext(selectHotelDetailScreen)(
+    withHotelsContext(select)(withNavigation(BookNow)),
+  ),
   graphql`
     fragment BookNow_hotel on HotelInterface {
       id
diff --git a/app/hotels/src/singleHotel/bookNow/countBookingPrice.js b/app/hotels/src/singleHotel/bookNow/countBookingPrice.js
index f164cfce..4366ebc7 100644
--- a/app/hotels/src/singleHotel/bookNow/countBookingPrice.js
+++ b/app/hotels/src/singleHotel/bookNow/countBookingPrice.js
@@ -1,6 +1,6 @@
 // @flow
 
-import type { BookNow_availableRooms } from '../__generated__/HotelDetailScreen_availableHotel.graphql';
+import type { HotelDetailScreen_availableHotel } from '../__generated__/HotelDetailScreen_availableHotel.graphql';
 
 type Money = {|
   amount: number,
@@ -8,7 +8,10 @@ type Money = {|
 |};
 
 export default (
-  availableRooms: ?BookNow_availableRooms,
+  availableRooms: ?$PropertyType<
+    HotelDetailScreen_availableHotel,
+    'availableRooms',
+  >,
   selected: {
     [string]: number,
   },
@@ -29,14 +32,14 @@ export default (
       const [id, count] = selection;
 
       if (availableRooms) {
-        const room = availableRooms.find(room => room.id === id);
+        const room = availableRooms.find(room => room?.id === id);
         return room?.incrementalPrice?.[count - 1]?.amount;
       }
     })
     .reduce((a, b) => a + b, 0);
 
   const currency =
-    availableRooms.find(room => room.id === positiveSelections[0][0])
+    availableRooms.find(room => room?.id === positiveSelections[0][0])
       ?.incrementalPrice?.[0]?.currency ?? '';
 
   return {
diff --git a/app/hotels/src/singleHotel/roomList/RoomList.js b/app/hotels/src/singleHotel/roomList/RoomList.js
index ed8c32db..0ad02f3b 100644
--- a/app/hotels/src/singleHotel/roomList/RoomList.js
+++ b/app/hotels/src/singleHotel/roomList/RoomList.js
@@ -14,12 +14,6 @@ import type { RoomList as RoomListType } from './__generated__/RoomList.graphql'
 
 type ContainerProps = {|
   +data: any,
-  +select: (availabilityId: string, maxPersons: number) => void,
-  +deselect: (availabilityId: string, maxPersons: number) => void,
-  +selected: {
-    [string]: number,
-  },
-  +disabled: boolean,
 |};
 
 type Props = {|
@@ -31,8 +25,7 @@ class RoomList extends React.Component<Props> {
   shouldComponentUpdate = (nextProps: Props) => !isEqual(nextProps, this.props);
 
   render() {
-    const { select, deselect, selected, disabled } = this.props;
-    const data = this.props.data || [];
+    const data = this.props.data ?? [];
 
     return (
       <View style={styles.container}>
@@ -52,10 +45,6 @@ class RoomList extends React.Component<Props> {
             )}
             <RoomRow
               availableRoom={availableRoom}
-              select={select}
-              deselect={deselect}
-              selected={selected}
-              disabled={disabled}
               testID={index === data.length - 1 ? 'lastAvailableRoom' : ''}
             />
           </React.Fragment>
diff --git a/app/hotels/src/singleHotel/roomList/RoomRow.js b/app/hotels/src/singleHotel/roomList/RoomRow.js
index 8c3ed65b..103fecaa 100644
--- a/app/hotels/src/singleHotel/roomList/RoomRow.js
+++ b/app/hotels/src/singleHotel/roomList/RoomRow.js
@@ -9,7 +9,6 @@ import {
   withNavigation,
 } from '@kiwicom/mobile-navigation';
 import { Alert } from '@kiwicom/mobile-localization';
-import isEqual from 'react-fast-compare';
 
 import RoomPicker from '../roomPicker/RoomPicker';
 import RoomImage from './RoomImage';
@@ -17,15 +16,17 @@ import BeddingInfo from './BeddingInfo';
 import RoomRowTitle from './RoomRowTitle';
 import RoomBadges from './RoomBadges';
 import type { RoomRow_availableRoom } from './__generated__/RoomRow_availableRoom.graphql';
+import {
+  withHotelDetailScreenContext,
+  type HotelDetailScreenState,
+} from '../HotelDetailScreenContext';
+import {
+  withHotelsContext,
+  type HotelsContextState,
+} from '../../HotelsContext';
 
 type ContainerProps = {|
   +availableRoom: ?Object,
-  +select: (availabilityId: string, maxPersons: number) => void,
-  +deselect: (availabilityId: string, maxPersons: number) => void,
-  +selected: {
-    +[string]: number,
-  },
-  +disabled: boolean,
   +testID?: string,
 |};
 
@@ -33,12 +34,20 @@ type Props = {|
   ...ContainerProps,
   +availableRoom: ?RoomRow_availableRoom,
   +navigation: NavigationType,
+  +select: (availabilityId: string, maxPersons: number) => void,
+  +deselect: (availabilityId: string, maxPersons: number) => void,
+  +selected: {
+    +[string]: number,
+  },
+  +numberOfRooms: number,
+  +getGuestCount: () => number,
 |};
 
 export class RoomRow extends React.Component<Props> {
-  shouldComponentUpdate = (nextProps: Props) => !isEqual(nextProps, this.props);
+  isDisabled = () => this.props.getGuestCount() <= this.props.numberOfRooms;
+
   select = () => {
-    if (this.props.disabled) {
+    if (this.isDisabled()) {
       Alert.translatedAlert(null, {
         id: 'single_hotel.alert.cannot_book_more_rooms_than_guests',
       });
@@ -122,7 +131,7 @@ export class RoomRow extends React.Component<Props> {
             increment={this.select}
             decrement={this.deselect}
             testID={this.props.testID}
-            disabled={this.props.disabled}
+            disabled={this.isDisabled()}
           />
         </View>
       </View>
@@ -130,8 +139,27 @@ export class RoomRow extends React.Component<Props> {
   }
 }
 
+const select = ({
+  selected,
+  numberOfRooms,
+  actions: { selectRoom, deselectRoom },
+}: HotelDetailScreenState) => ({
+  select: selectRoom,
+  deselect: deselectRoom,
+  selected,
+  numberOfRooms,
+});
+
+const selectHotelsContext = ({ getGuestCount }: HotelsContextState) => ({
+  getGuestCount,
+});
+
 export default (createFragmentContainer(
-  withNavigation(RoomRow),
+  withNavigation(
+    withHotelDetailScreenContext(select)(
+      withHotelsContext(selectHotelsContext)(RoomRow),
+    ),
+  ),
   graphql`
     fragment RoomRow_availableRoom on HotelRoomAvailabilityInterface {
       id


Test patch:
diff --git a/app/hotels/src/__tests__/HotelsContext.test.js b/app/hotels/src/__tests__/HotelsContext.test.js
index 659e44c3..80d4be5d 100644
--- a/app/hotels/src/__tests__/HotelsContext.test.js
+++ b/app/hotels/src/__tests__/HotelsContext.test.js
@@ -1,40 +1,6 @@
 // @flow strict
 
-import * as React from 'react';
-import renderer from 'react-test-renderer';
-
-import HotelsContext, {
-  getAsUtcDate,
-  type RoomConfigurationType,
-} from '../HotelsContext';
-
-const props = {
-  version: 'test',
-  currency: 'EUR',
-  latitude: null,
-  longitude: null,
-  checkin: null,
-  checkout: null,
-  cityId: null,
-  cityName: null,
-  apiProvider: 'booking',
-  hotelId: '',
-  closeHotels: jest.fn(),
-};
-
-const Child = () => null;
-
-const getWrapper = (roomsConfiguration: ?RoomConfigurationType) =>
-  renderer
-    .create(
-      <HotelsContext.Provider
-        {...props}
-        roomsConfiguration={roomsConfiguration}
-      >
-        <Child />
-      </HotelsContext.Provider>,
-    )
-    .getInstance();
+import { getAsUtcDate } from '../HotelsContext';
 
 describe('getAsUtcDate', () => {
   it('should return null for invalid date string', () => {
@@ -46,19 +12,3 @@ describe('getAsUtcDate', () => {
     expect(getAsUtcDate('2018-01-01')).toEqual(new Date(Date.UTC(2018, 0, 1)));
   });
 });
-
-describe('HotelsContext', () => {
-  it('calculates number of guests correctly with undefined roomsConfig', () => {
-    const wrapper = getWrapper();
-    expect(wrapper.getGuestCount()).toBe(0);
-  });
-
-  it('calculates number of guests correctly', () => {
-    const wrapper = getWrapper([
-      { adultsCount: 2, children: [] },
-      { adultsCount: 1 },
-      { adultsCount: 1, children: [{ age: 0 }, { age: 2 }, { age: 17 }] },
-    ]);
-    expect(wrapper.getGuestCount()).toBe(7);
-  });
-});
diff --git a/app/hotels/src/appRegistry/__tests__/RootComponent.test.js b/app/hotels/src/appRegistry/__tests__/RootComponent.test.js
new file mode 100644
index 00000000..e3a5f0b3
--- /dev/null
+++ b/app/hotels/src/appRegistry/__tests__/RootComponent.test.js
@@ -0,0 +1,22 @@
+// @flow
+
+import RootComponent from '../RootComponent';
+
+it('calculates number of guests correctly with undefined roomsConfig', () => {
+  // $FlowExpectedError: Intentionally testing with bad input
+  const Component = new RootComponent({});
+  expect(Component.getGuestCount()).toBe(0);
+});
+
+it('calculates number of guests correctly', () => {
+  // $FlowExpectedError: Passing only props needed for test to run
+  const Component = new RootComponent({
+    roomsConfiguration: [
+      { adultsCount: 2, children: [] },
+      { adultsCount: 1 },
+      { adultsCount: 1, children: [{ age: 0 }, { age: 2 }, { age: 17 }] },
+    ],
+  });
+
+  expect(Component.getGuestCount()).toBe(7);
+});
diff --git a/app/hotels/src/singleHotel/__tests__/HotelDetailScreenContext.test.js b/app/hotels/src/singleHotel/__tests__/HotelDetailScreenContext.test.js
new file mode 100644
index 00000000..94aa61a0
--- /dev/null
+++ b/app/hotels/src/singleHotel/__tests__/HotelDetailScreenContext.test.js
@@ -0,0 +1,73 @@
+// @flow
+
+import * as React from 'react';
+import renderer from 'react-test-renderer';
+
+import { Provider } from '../HotelDetailScreenContext';
+
+const Child = () => null;
+
+const getWrapper = (guestCount = 4) =>
+  renderer
+    .create(
+      <Provider guestCount={guestCount}>
+        <Child />
+      </Provider>,
+    )
+    .getInstance();
+
+describe('HotelDetailScreenContext', () => {
+  it('selects and deslects rooms correctly', () => {
+    const wrapper = getWrapper();
+    wrapper.selectRoom('abc', 2);
+    wrapper.selectRoom('abcd', 4);
+    expect(wrapper.state.selected.abc).toBe(1);
+    expect(wrapper.state.selected.abcd).toBe(1);
+    wrapper.selectRoom('abcd', 4);
+    expect(wrapper.state.selected.abcd).toBe(2);
+
+    wrapper.deselectRoom('abcd', 4);
+    expect(wrapper.state.selected.abcd).toBe(1);
+  });
+
+  it('updateSelectedCount', () => {
+    const wrapper = getWrapper();
+    wrapper.updateSelectedCount('abc', 1, 3);
+    expect(wrapper.state.maxPersons).toBe(3);
+    expect(wrapper.state.numberOfRooms).toBe(1);
+
+    wrapper.updateSelectedCount('abcd', 1, 2);
+    expect(wrapper.state.maxPersons).toBe(5);
+    expect(wrapper.state.numberOfRooms).toBe(2);
+
+    wrapper.updateSelectedCount('abcd', -1, -2);
+    expect(wrapper.state.maxPersons).toBe(3);
+    expect(wrapper.state.numberOfRooms).toBe(1);
+  });
+
+  it('getPersonCount', () => {
+    const wrapper = getWrapper(2);
+
+    wrapper.setState({ maxPersons: 3 });
+    expect(wrapper.getPersonCount()).toBe(2);
+
+    wrapper.setState({ maxPersons: 1 });
+    expect(wrapper.getPersonCount()).toBe(1);
+  });
+
+  it('resets state', () => {
+    const wrapper = getWrapper();
+    wrapper.setState({
+      selected: {
+        abc: 3,
+      },
+      maxPersons: 5,
+      numberOfRooms: 2,
+    });
+
+    wrapper.reset();
+    expect(wrapper.state.selected).toEqual({});
+    expect(wrapper.state.maxPersons).toEqual(0);
+    expect(wrapper.state.numberOfRooms).toEqual(0);
+  });
+});
diff --git a/app/hotels/src/singleHotel/bookNow/__tests__/countBookingPrice.test.js b/app/hotels/src/singleHotel/bookNow/__tests__/countBookingPrice.test.js
index 6e5d43bc..60a1b465 100644
--- a/app/hotels/src/singleHotel/bookNow/__tests__/countBookingPrice.test.js
+++ b/app/hotels/src/singleHotel/bookNow/__tests__/countBookingPrice.test.js
@@ -2,8 +2,10 @@
 
 import countBookingPrice from '../countBookingPrice';
 
+const $fragmentRefs: any = null;
 const availableRooms = [
   {
+    $fragmentRefs,
     id: 'aaa',
     incrementalPrice: [
       {
@@ -17,6 +19,7 @@ const availableRooms = [
     ],
   },
   {
+    $fragmentRefs,
     id: 'bbb',
     incrementalPrice: [
       {
@@ -51,10 +54,12 @@ it('returns sum of selected rooms', () => {
 it('takes a currency from the first selected room', () => {
   const czechRooms = [
     {
+      $fragmentRefs,
       id: 'aaa',
       incrementalPrice: [],
     },
     {
+      $fragmentRefs,
       id: 'bbb',
       incrementalPrice: [
         {
diff --git a/app/hotels/src/singleHotel/roomList/__tests__/RoomRow.test.js b/app/hotels/src/singleHotel/roomList/__tests__/RoomRow.test.js
index 6e57b75b..4f951cab 100644
--- a/app/hotels/src/singleHotel/roomList/__tests__/RoomRow.test.js
+++ b/app/hotels/src/singleHotel/roomList/__tests__/RoomRow.test.js
@@ -30,7 +30,8 @@ it('renders without crashing', () => {
       incrementalPrice: [],
     },
     navigation,
-    disabled: false,
+    getGuestCount: () => 2,
+    numberOfRooms: 2,
   };
 
   renderer.render(<RoomRow {...props} />);
@@ -43,7 +44,8 @@ it('renders without crashing with missing data', () => {
     selected: {},
     availableRoom: undefined,
     navigation,
-    disabled: false,
+    getGuestCount: () => 2,
+    numberOfRooms: 2,
   };
 
   renderer.render(<RoomRow {...props} />);