Repository: kiwicom/mobile
Pull request #846 (2018-07-10 15:11:41)

MMB: Add boarding pass texts for future bookings


Patch:
diff --git a/app/MMB/src/components/MissingInformation.js b/app/MMB/src/components/MissingInformation.js
index 272a59e8..06b344fd 100644
--- a/app/MMB/src/components/MissingInformation.js
+++ b/app/MMB/src/components/MissingInformation.js
@@ -16,14 +16,26 @@ import {
 import { withNavigation } from 'react-navigation';
 
 import Alert from '../components/alert/Alert';
+import BookingDetailContext from '../context/BookingDetailContext';
 import type { MissingInformation as PassengerType } from './__generated__/MissingInformation.graphql';
 
-type Props = {|
-  +data: PassengerType,
-  +navigation: NavigationType,
+type PropsWithContext = {|
+  ...Props,
+  setIsMissingDocumentId: (isMissingDocumentId: boolean) => void,
 |};
 
-export class MissingInformation extends React.Component<Props> {
+export class MissingInformation extends React.Component<PropsWithContext> {
+  componentDidMount = () => {
+    this.props.setIsMissingDocumentId(this.isSomePassengerMissingId());
+  };
+
+  isSomePassengerMissingId = () => {
+    const passengers = idx(this.props.data, _ => _.passengers) || [];
+    return passengers.some(
+      passenger => idx(passenger, _ => _.travelDocument.idNumber) == null,
+    );
+  };
+
   fillInPassengerInfo = () => {
     this.props.navigation.navigate({
       routeName: 'TravelDocumentScreen',
@@ -32,12 +44,7 @@ export class MissingInformation extends React.Component<Props> {
   };
 
   render = () => {
-    const passengers = idx(this.props.data, _ => _.passengers) || [];
-    const isPassengerMissingId = passengers.some(
-      passenger => idx(passenger, _ => _.travelDocument.idNumber) == null,
-    );
-
-    if (!isPassengerMissingId) {
+    if (!this.isSomePassengerMissingId()) {
       return null;
     }
 
@@ -74,8 +81,24 @@ export class MissingInformation extends React.Component<Props> {
   };
 }
 
+type Props = {|
+  +data: PassengerType,
+  +navigation: NavigationType,
+|};
+
+const MissingInformationWithContext = (props: Props) => (
+  <BookingDetailContext.Consumer>
+    {({ actions: { setIsMissingDocumentId } }) => (
+      <MissingInformation
+        {...props}
+        setIsMissingDocumentId={setIsMissingDocumentId}
+      />
+    )}
+  </BookingDetailContext.Consumer>
+);
+
 export default createFragmentContainer(
-  withNavigation(MissingInformation),
+  withNavigation(MissingInformationWithContext),
   graphql`
     fragment MissingInformation on BookingInterface {
       passengers {
diff --git a/app/MMB/src/context/BookingDetailContext.js b/app/MMB/src/context/BookingDetailContext.js
index c8a446d8..abc38a4e 100644
--- a/app/MMB/src/context/BookingDetailContext.js
+++ b/app/MMB/src/context/BookingDetailContext.js
@@ -16,8 +16,10 @@ const defaultState = {
   arrivalCityId: '',
   arrivalTime: new Date(),
   departureTime: new Date(),
+  isMissingDocumentId: false,
   actions: {
     setBookingDetail: () => {},
+    setIsMissingDocumentId: () => {},
   },
 };
 
@@ -31,8 +33,10 @@ type Props = {|
 
 type State = {|
   ...BookingDetail,
+  +isMissingDocumentId: boolean,
   +actions: {|
     +setBookingDetail: (booking: BookingDetail) => void,
+    +setIsMissingDocumentId: (isMissingDocumentId: boolean) => void,
   |},
 |};
 
@@ -44,6 +48,7 @@ class Provider extends React.Component<Props, State> {
       ...defaultState,
       actions: {
         setBookingDetail: this.setBookingDetail,
+        setIsMissingDocumentId: this.setIsMissingDocumentId,
       },
     };
   }
@@ -52,6 +57,10 @@ class Provider extends React.Component<Props, State> {
     this.setState(booking);
   };
 
+  setIsMissingDocumentId = (isMissingDocumentId: boolean) => {
+    this.setState({ isMissingDocumentId });
+  };
+
   render = () => (
     <ContextProvider value={this.state}>{this.props.children}</ContextProvider>
   );
diff --git a/app/MMB/src/navigation/FillTravelDocumentScreen.js b/app/MMB/src/navigation/FillTravelDocumentScreen.js
index ac451245..dfa71a59 100644
--- a/app/MMB/src/navigation/FillTravelDocumentScreen.js
+++ b/app/MMB/src/navigation/FillTravelDocumentScreen.js
@@ -10,9 +10,8 @@ export default function FillTravelDocumentScreen() {
   return <FillTravelDocumentContainer />;
 }
 
-// TODO: All text is not showing on mobile. Find out how to enlarge surrounding view
 FillTravelDocumentScreen.navigationOptions = () => ({
-  title: (
+  headerTitle: (
     <HeaderTitle>
       <Translation id="mmb.fill_travel_document_screen.title" />
     </HeaderTitle>
diff --git a/app/MMB/src/scenes/tickets/__generated__/TicketRefetchQuery.graphql.js b/app/MMB/src/scenes/tickets/__generated__/TicketRefetchQuery.graphql.js
index 3960b0f6..b65540a2 100644
--- a/app/MMB/src/scenes/tickets/__generated__/TicketRefetchQuery.graphql.js
+++ b/app/MMB/src/scenes/tickets/__generated__/TicketRefetchQuery.graphql.js
@@ -1,6 +1,6 @@
 /**
  * @flow
- * @relayHash 139689c54df3e505bce2b712e7a0731b
+ * @relayHash eac9ee82045c76563965e57847337f5d
  */
 
 /* eslint-disable */
@@ -126,6 +126,11 @@ fragment DownloadButton on BoardingPass {
 fragment BoardingPassInformation on BoardingPass {
   availableAt
   boardingPassUrl
+  ...FutureBookingInformation
+}
+
+fragment FutureBookingInformation on BoardingPass {
+  boardingPassUrl
 }
 */
 
@@ -260,7 +265,7 @@ return {
   "operationKind": "query",
   "name": "TicketRefetchQuery",
   "id": null,
-  "text": "query TicketRefetchQuery(\n  $id: ID!\n) {\n  node(id: $id) {\n    __typename\n    ... on BookingInterface {\n      ...TicketRefetch\n    }\n    id\n  }\n}\n\nfragment TicketRefetch on BookingInterface {\n  id\n  ...BoardingPasses\n  assets {\n    ...ETicket\n  }\n}\n\nfragment BoardingPasses on Node {\n  __typename\n  ... on BookingReturn {\n    ...BoardingPassReturn\n  }\n  ... on BookingOneWay {\n    ...BoardingPassOneWay\n  }\n  ... on BookingMulticity {\n    ...BoardingPassMultiCity\n  }\n}\n\nfragment ETicket on BookingAssets {\n  ticketUrl\n}\n\nfragment BoardingPassReturn on BookingReturn {\n  outbound {\n    ...FlightSegments\n  }\n  inbound {\n    ...FlightSegments\n  }\n}\n\nfragment BoardingPassOneWay on BookingOneWay {\n  trip {\n    ...FlightSegments\n  }\n}\n\nfragment BoardingPassMultiCity on BookingMulticity {\n  trips {\n    arrival {\n      airport {\n        city {\n          name\n        }\n        id\n      }\n    }\n    ...FlightSegments\n  }\n}\n\nfragment FlightSegments on Trip {\n  legs {\n    id\n    ...FlightFromTo\n  }\n}\n\nfragment FlightFromTo on Leg {\n  departure {\n    localTime\n    airport {\n      city {\n        name\n      }\n      id\n    }\n  }\n  arrival {\n    airport {\n      city {\n        name\n      }\n      id\n    }\n  }\n  boardingPass {\n    ...DownloadButton\n  }\n}\n\nfragment DownloadButton on BoardingPass {\n  boardingPassUrl\n  ...BoardingPassInformation\n}\n\nfragment BoardingPassInformation on BoardingPass {\n  availableAt\n  boardingPassUrl\n}\n",
+  "text": "query TicketRefetchQuery(\n  $id: ID!\n) {\n  node(id: $id) {\n    __typename\n    ... on BookingInterface {\n      ...TicketRefetch\n    }\n    id\n  }\n}\n\nfragment TicketRefetch on BookingInterface {\n  id\n  ...BoardingPasses\n  assets {\n    ...ETicket\n  }\n}\n\nfragment BoardingPasses on Node {\n  __typename\n  ... on BookingReturn {\n    ...BoardingPassReturn\n  }\n  ... on BookingOneWay {\n    ...BoardingPassOneWay\n  }\n  ... on BookingMulticity {\n    ...BoardingPassMultiCity\n  }\n}\n\nfragment ETicket on BookingAssets {\n  ticketUrl\n}\n\nfragment BoardingPassReturn on BookingReturn {\n  outbound {\n    ...FlightSegments\n  }\n  inbound {\n    ...FlightSegments\n  }\n}\n\nfragment BoardingPassOneWay on BookingOneWay {\n  trip {\n    ...FlightSegments\n  }\n}\n\nfragment BoardingPassMultiCity on BookingMulticity {\n  trips {\n    arrival {\n      airport {\n        city {\n          name\n        }\n        id\n      }\n    }\n    ...FlightSegments\n  }\n}\n\nfragment FlightSegments on Trip {\n  legs {\n    id\n    ...FlightFromTo\n  }\n}\n\nfragment FlightFromTo on Leg {\n  departure {\n    localTime\n    airport {\n      city {\n        name\n      }\n      id\n    }\n  }\n  arrival {\n    airport {\n      city {\n        name\n      }\n      id\n    }\n  }\n  boardingPass {\n    ...DownloadButton\n  }\n}\n\nfragment DownloadButton on BoardingPass {\n  boardingPassUrl\n  ...BoardingPassInformation\n}\n\nfragment BoardingPassInformation on BoardingPass {\n  availableAt\n  boardingPassUrl\n  ...FutureBookingInformation\n}\n\nfragment FutureBookingInformation on BoardingPass {\n  boardingPassUrl\n}\n",
   "metadata": {},
   "fragment": {
     "kind": "Fragment",
diff --git a/app/MMB/src/scenes/tickets/__generated__/TicketSceneQuery.graphql.js b/app/MMB/src/scenes/tickets/__generated__/TicketSceneQuery.graphql.js
index cc878df0..fa0bb782 100644
--- a/app/MMB/src/scenes/tickets/__generated__/TicketSceneQuery.graphql.js
+++ b/app/MMB/src/scenes/tickets/__generated__/TicketSceneQuery.graphql.js
@@ -1,6 +1,6 @@
 /**
  * @flow
- * @relayHash e94ff767d11e8442e68e7673a8d9ac23
+ * @relayHash 104673d1b873f5cde374acbbcf33630f
  */
 
 /* eslint-disable */
@@ -126,6 +126,11 @@ fragment DownloadButton on BoardingPass {
 fragment BoardingPassInformation on BoardingPass {
   availableAt
   boardingPassUrl
+  ...FutureBookingInformation
+}
+
+fragment FutureBookingInformation on BoardingPass {
+  boardingPassUrl
 }
 */
 
@@ -260,7 +265,7 @@ return {
   "operationKind": "query",
   "name": "TicketSceneQuery",
   "id": null,
-  "text": "query TicketSceneQuery(\n  $bookingId: ID!\n) {\n  node(id: $bookingId) {\n    __typename\n    ... on BookingInterface {\n      ...TicketRefetch\n    }\n    id\n  }\n}\n\nfragment TicketRefetch on BookingInterface {\n  id\n  ...BoardingPasses\n  assets {\n    ...ETicket\n  }\n}\n\nfragment BoardingPasses on Node {\n  __typename\n  ... on BookingReturn {\n    ...BoardingPassReturn\n  }\n  ... on BookingOneWay {\n    ...BoardingPassOneWay\n  }\n  ... on BookingMulticity {\n    ...BoardingPassMultiCity\n  }\n}\n\nfragment ETicket on BookingAssets {\n  ticketUrl\n}\n\nfragment BoardingPassReturn on BookingReturn {\n  outbound {\n    ...FlightSegments\n  }\n  inbound {\n    ...FlightSegments\n  }\n}\n\nfragment BoardingPassOneWay on BookingOneWay {\n  trip {\n    ...FlightSegments\n  }\n}\n\nfragment BoardingPassMultiCity on BookingMulticity {\n  trips {\n    arrival {\n      airport {\n        city {\n          name\n        }\n        id\n      }\n    }\n    ...FlightSegments\n  }\n}\n\nfragment FlightSegments on Trip {\n  legs {\n    id\n    ...FlightFromTo\n  }\n}\n\nfragment FlightFromTo on Leg {\n  departure {\n    localTime\n    airport {\n      city {\n        name\n      }\n      id\n    }\n  }\n  arrival {\n    airport {\n      city {\n        name\n      }\n      id\n    }\n  }\n  boardingPass {\n    ...DownloadButton\n  }\n}\n\nfragment DownloadButton on BoardingPass {\n  boardingPassUrl\n  ...BoardingPassInformation\n}\n\nfragment BoardingPassInformation on BoardingPass {\n  availableAt\n  boardingPassUrl\n}\n",
+  "text": "query TicketSceneQuery(\n  $bookingId: ID!\n) {\n  node(id: $bookingId) {\n    __typename\n    ... on BookingInterface {\n      ...TicketRefetch\n    }\n    id\n  }\n}\n\nfragment TicketRefetch on BookingInterface {\n  id\n  ...BoardingPasses\n  assets {\n    ...ETicket\n  }\n}\n\nfragment BoardingPasses on Node {\n  __typename\n  ... on BookingReturn {\n    ...BoardingPassReturn\n  }\n  ... on BookingOneWay {\n    ...BoardingPassOneWay\n  }\n  ... on BookingMulticity {\n    ...BoardingPassMultiCity\n  }\n}\n\nfragment ETicket on BookingAssets {\n  ticketUrl\n}\n\nfragment BoardingPassReturn on BookingReturn {\n  outbound {\n    ...FlightSegments\n  }\n  inbound {\n    ...FlightSegments\n  }\n}\n\nfragment BoardingPassOneWay on BookingOneWay {\n  trip {\n    ...FlightSegments\n  }\n}\n\nfragment BoardingPassMultiCity on BookingMulticity {\n  trips {\n    arrival {\n      airport {\n        city {\n          name\n        }\n        id\n      }\n    }\n    ...FlightSegments\n  }\n}\n\nfragment FlightSegments on Trip {\n  legs {\n    id\n    ...FlightFromTo\n  }\n}\n\nfragment FlightFromTo on Leg {\n  departure {\n    localTime\n    airport {\n      city {\n        name\n      }\n      id\n    }\n  }\n  arrival {\n    airport {\n      city {\n        name\n      }\n      id\n    }\n  }\n  boardingPass {\n    ...DownloadButton\n  }\n}\n\nfragment DownloadButton on BoardingPass {\n  boardingPassUrl\n  ...BoardingPassInformation\n}\n\nfragment BoardingPassInformation on BoardingPass {\n  availableAt\n  boardingPassUrl\n  ...FutureBookingInformation\n}\n\nfragment FutureBookingInformation on BoardingPass {\n  boardingPassUrl\n}\n",
   "metadata": {},
   "fragment": {
     "kind": "Fragment",
diff --git a/app/MMB/src/scenes/tickets/boardingPasses/boardingPassInformation/BoardingPassInformation.js b/app/MMB/src/scenes/tickets/boardingPasses/boardingPassInformation/BoardingPassInformation.js
index 1acb49b7..eaeb1374 100644
--- a/app/MMB/src/scenes/tickets/boardingPasses/boardingPassInformation/BoardingPassInformation.js
+++ b/app/MMB/src/scenes/tickets/boardingPasses/boardingPassInformation/BoardingPassInformation.js
@@ -4,6 +4,7 @@ import * as React from 'react';
 import { graphql, createFragmentContainer } from '@kiwicom/mobile-relay';
 import idx from 'idx';
 
+import FutureBookingInformation from './FutureBookingInformation';
 import BookingDetailContext from '../../../../context/BookingDetailContext';
 import PastBookingInformation from './PastBookingInformation';
 import type { BoardingPassInformation as BoardingPass } from './__generated__/BoardingPassInformation.graphql';
@@ -29,7 +30,12 @@ const BoardingPassInformation = (props: PropsWithContext) => {
     );
   }
 
-  return null; // TODO: Future booking information
+  return (
+    <FutureBookingInformation
+      boardingPassAvailableDate={date}
+      data={props.data}
+    />
+  );
 };
 
 type Props = {|
@@ -50,6 +56,7 @@ export default createFragmentContainer(
     fragment BoardingPassInformation on BoardingPass {
       availableAt
       boardingPassUrl
+      ...FutureBookingInformation
     }
   `,
 );
diff --git a/app/MMB/src/scenes/tickets/boardingPasses/boardingPassInformation/FutureBookingInformation.js b/app/MMB/src/scenes/tickets/boardingPasses/boardingPassInformation/FutureBookingInformation.js
new file mode 100644
index 00000000..9297d361
--- /dev/null
+++ b/app/MMB/src/scenes/tickets/boardingPasses/boardingPassInformation/FutureBookingInformation.js
@@ -0,0 +1,61 @@
+// @flow strict
+
+import * as React from 'react';
+import { graphql, createFragmentContainer } from '@kiwicom/mobile-relay';
+import { Translation } from '@kiwicom/mobile-localization';
+import idx from 'idx';
+
+import BookingDetailContext from '../../../../context/BookingDetailContext';
+import type { FutureBookingInformation as BoardingPass } from './__generated__/FutureBookingInformation.graphql';
+
+type PropsWithContext = {|
+  ...Props,
+  +isMissingPassengerId: boolean,
+|};
+
+export const FutureBookingInformation = (props: PropsWithContext) => {
+  const isAvailable = Boolean(idx(props.data, _ => _.boardingPassUrl));
+
+  if (!isAvailable && props.boardingPassAvailableDate !== null) {
+    return (
+      <Translation
+        id="mmb.boarding_passes.future_booking_information.available_at_date"
+        values={{ date: props.boardingPassAvailableDate }}
+      />
+    );
+  }
+
+  if (props.isMissingPassengerId) {
+    return (
+      <Translation id="mmb.boarding_passes.future_booking_information.missing_information" />
+    );
+  }
+  return (
+    <Translation id="mmb.boarding_passes.future_booking_information.available" />
+  );
+};
+
+type Props = {|
+  +data: BoardingPass,
+  +boardingPassAvailableDate: Date | null,
+|};
+
+const FutureBookingInformationWithContext = (props: Props) => (
+  <BookingDetailContext.Consumer>
+    {({ isMissingDocumentId }) => (
+      <FutureBookingInformation
+        {...props}
+        isMissingPassengerId={isMissingDocumentId}
+      />
+    )}
+  </BookingDetailContext.Consumer>
+);
+
+export default createFragmentContainer(
+  FutureBookingInformationWithContext,
+  graphql`
+    fragment FutureBookingInformation on BoardingPass {
+      boardingPassUrl
+    }
+  `,
+);
diff --git a/app/MMB/src/scenes/tickets/boardingPasses/boardingPassInformation/__generated__/BoardingPassInformation.graphql.js b/app/MMB/src/scenes/tickets/boardingPasses/boardingPassInformation/__generated__/BoardingPassInformation.graphql.js
index 7266fbc6..80587e11 100644
--- a/app/MMB/src/scenes/tickets/boardingPasses/boardingPassInformation/__generated__/BoardingPassInformation.graphql.js
+++ b/app/MMB/src/scenes/tickets/boardingPasses/boardingPassInformation/__generated__/BoardingPassInformation.graphql.js
@@ -8,11 +8,13 @@
 
 /*::
 import type { ConcreteFragment } from 'relay-runtime';
+type FutureBookingInformation$ref = any;
 import type { FragmentReference } from "relay-runtime";
 declare export opaque type BoardingPassInformation$ref: FragmentReference;
 export type BoardingPassInformation = {|
   +availableAt: ?any,
   +boardingPassUrl: ?string,
+  +$fragmentRefs: FutureBookingInformation$ref,
   +$refType: BoardingPassInformation$ref,
 |};
 */
@@ -38,9 +40,14 @@ const node/*: ConcreteFragment*/ = {
       "name": "boardingPassUrl",
       "args": null,
       "storageKey": null
+    },
+    {
+      "kind": "FragmentSpread",
+      "name": "FutureBookingInformation",
+      "args": null
     }
   ]
 };
 // prettier-ignore
-(node/*: any*/).hash = '2b985b7bb0e155b5ac78777662292456';
+(node/*: any*/).hash = 'd49f7627c24455fa08c3d4115e0a45b9';
 module.exports = node;
diff --git a/app/MMB/src/scenes/tickets/boardingPasses/boardingPassInformation/__generated__/FutureBookingInformation.graphql.js b/app/MMB/src/scenes/tickets/boardingPasses/boardingPassInformation/__generated__/FutureBookingInformation.graphql.js
new file mode 100644
index 00000000..98edbfcb
--- /dev/null
+++ b/app/MMB/src/scenes/tickets/boardingPasses/boardingPassInformation/__generated__/FutureBookingInformation.graphql.js
@@ -0,0 +1,38 @@
+/**
+ * @flow
+ */
+
+/* eslint-disable */
+
+'use strict';
+
+/*::
+import type { ConcreteFragment } from 'relay-runtime';
+import type { FragmentReference } from "relay-runtime";
+declare export opaque type FutureBookingInformation$ref: FragmentReference;
+export type FutureBookingInformation = {|
+  +boardingPassUrl: ?string,
+  +$refType: FutureBookingInformation$ref,
+|};
+*/
+
+
+const node/*: ConcreteFragment*/ = {
+  "kind": "Fragment",
+  "name": "FutureBookingInformation",
+  "type": "BoardingPass",
+  "metadata": null,
+  "argumentDefinitions": [],
+  "selections": [
+    {
+      "kind": "ScalarField",
+      "alias": null,
+      "name": "boardingPassUrl",
+      "args": null,
+      "storageKey": null
+    }
+  ]
+};
+// prettier-ignore
+(node/*: any*/).hash = 'fbdbe16719fe589bc613056daeff751b';
+module.exports = node;
diff --git a/packages/localization/src/vocabularies/ManageMyBooking.js b/packages/localization/src/vocabularies/ManageMyBooking.js
index 42ce7602..3ca7634c 100644
--- a/packages/localization/src/vocabularies/ManageMyBooking.js
+++ b/packages/localization/src/vocabularies/ManageMyBooking.js
@@ -37,6 +37,12 @@ export default {
     'Open Printable Document',
   'mmb.boarding_passes.past_booking_information.missed_checkin':
     'Missed online check-in. It was available on our site until __date__',
+  'mmb.boarding_passes.future_booking_information.available_at_date':
+    'Your boarding pass will be available after online check-in on __date__.',
+  'mmb.boarding_passes.future_booking_information.missing_information':
+    'We need your passport/ID details to check you in online.',
+  'mmb.boarding_passes.future_booking_information.available':
+    'You have been provided with digital boarding pass, but you should print it out because some airlines require a physical copy',
 
   // MMB Flight overview
   'mmb.flight_overview.title': 'Flight Overview',


Test patch:
diff --git a/app/MMB/src/context/__tests__/BookingDetailContext.test.js b/app/MMB/src/context/__tests__/BookingDetailContext.test.js
index cb8d960e..e3595ebd 100644
--- a/app/MMB/src/context/__tests__/BookingDetailContext.test.js
+++ b/app/MMB/src/context/__tests__/BookingDetailContext.test.js
@@ -14,8 +14,15 @@ const getWrapper = () =>
   renderer.create(
     <BookingDetailContext.Provider>
       <BookingDetailContext.Consumer>
-        {({ actions: { setBookingDetail }, ...rest }) => (
-          <ContextConsumer {...rest} setBookingDetail={setBookingDetail} />
+        {({
+          actions: { setBookingDetail, setIsMissingDocumentId },
+          ...rest
+        }) => (
+          <ContextConsumer
+            {...rest}
+            setBookingDetail={setBookingDetail}
+            setIsMissingDocumentId={setIsMissingDocumentId}
+          />
         )}
       </BookingDetailContext.Consumer>
     </BookingDetailContext.Provider>,
@@ -33,9 +40,10 @@ describe('BookingDetailContext', () => {
     ).toEqual(DateFormatter(new Date()).formatForMachine());
     expect(instance.props.arrivalCityId).toBe('');
     expect(typeof instance.props.setBookingDetail).toBe('function');
+    expect(instance.props.isMissingDocumentId).toBe(false);
   });
 
-  it('it setsBookingDetail', () => {
+  it('setsBookingDetail', () => {
     const wrapper = getWrapper();
     const instance = wrapper.root.findByType(ContextConsumer);
 
@@ -51,4 +59,15 @@ describe('BookingDetailContext', () => {
     expect(instance.props.arrivalCityId).toBe('oslo_no');
     expect(instance.props.arrivalTime).toEqual(new Date(1));
   });
+
+  it('sets missing document id', () => {
+    const wrapper = getWrapper();
+    const instance = wrapper.root.findByType(ContextConsumer);
+
+    instance.props.setIsMissingDocumentId(true);
+    expect(instance.props.isMissingDocumentId).toBe(true);
+
+    instance.props.setIsMissingDocumentId(false);
+    expect(instance.props.isMissingDocumentId).toBe(false);
+  });
 });
diff --git a/app/MMB/src/scenes/tickets/boardingPasses/boardingPassInformation/__tests__/FutureBookingInformation.test.js b/app/MMB/src/scenes/tickets/boardingPasses/boardingPassInformation/__tests__/FutureBookingInformation.test.js
new file mode 100644
index 00000000..099cf6b7
--- /dev/null
+++ b/app/MMB/src/scenes/tickets/boardingPasses/boardingPassInformation/__tests__/FutureBookingInformation.test.js
@@ -0,0 +1,49 @@
+// @flow strict
+
+import * as React from 'react';
+import ShallowRenderer from 'react-test-renderer/shallow';
+
+import { FutureBookingInformation } from '../FutureBookingInformation';
+import type { FutureBookingInformation as BoardingPass } from '../__generated__/FutureBookingInformation.graphql';
+
+const renderer = new ShallowRenderer();
+
+const getWrapper = (
+  isMissingPassengerId: boolean,
+  data: BoardingPass,
+  boardingPassAvailableDate: Date | null,
+) =>
+  renderer.render(
+    <FutureBookingInformation
+      isMissingPassengerId={isMissingPassengerId}
+      data={data}
+      boardingPassAvailableDate={boardingPassAvailableDate}
+    />,
+  );
+
+describe('FutureBookingInformation', () => {
+  it('renders correctly', () => {
+    const wrapper = getWrapper(
+      false,
+      // $FlowRelayIssue: https://github.com/facebook/relay/issues/2394
+      { boardingPassUrl: null },
+      null,
+    );
+
+    expect(wrapper.props.id).toBe(
+      'mmb.boarding_passes.future_booking_information.available',
+    );
+
+    // $FlowRelayIssue: https://github.com/facebook/relay/issues/2394
+    const wrapper2 = getWrapper(false, { boardingPassUrl: null }, new Date());
+    expect(wrapper2.props.id).toBe(
+      'mmb.boarding_passes.future_booking_information.available_at_date',
+    );
+
+    // $FlowRelayIssue: https://github.com/facebook/relay/issues/2394
+    const wrapper3 = getWrapper(true, { boardingPassUrl: '123' }, new Date());
+    expect(wrapper3.props.id).toBe(
+      'mmb.boarding_passes.future_booking_information.missing_information',
+    );
+  });
+});