Skip to content

Commit

Permalink
Merge pull request #54 from googleinterns/add-trip-functionality
Browse files Browse the repository at this point in the history
Add Trip Functionality
  • Loading branch information
zghera authored Jul 21, 2020
2 parents 2db03bc + 9d2b0b7 commit 63ad790
Show file tree
Hide file tree
Showing 11 changed files with 474 additions and 125 deletions.
72 changes: 72 additions & 0 deletions frontend/src/components/Utils/filter-input.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import * as firebase from 'firebase/app';

import { getCurUserEmail, getUserUidFromUserEmail } from './temp-auth-utils.js'
import { getTimestampFromDateString } from './time.js'

/**
* Return a string containing the cleaned text input.
*
* @param {string} rawInput String containing raw form input.
* @return {string} Cleaned string.
*/
export function getCleanedTextInput(rawInput, defaultValue) {
return rawInput === '' ? defaultValue : rawInput;
}

/**
* Return an array of collaborator uids given the emails provided in the
* add trip form.
*
* TODO(#72 & #67): Remove 'remove empty fields' once there is better way to
* remove collaborators (#72) and there is email validation (#67).
*
* @param {!Array{string}} collaboratorEmailsArr Array of emails corresponding
* to the collaborators of the trip (not including the trip creator email).
* @return {!Array{string}} Array of all collaborator uids (including trip
* creator uid).
*/
export function getCollaboratorUidArray(collaboratorEmailArr) {
collaboratorEmailArr = [getCurUserEmail()].concat(collaboratorEmailArr);

// Removes empty fields (temporary until fix #67 & #72).
while (collaboratorEmailArr.includes('')) {
const emptyStrIdx = collaboratorEmailArr.indexOf('');
collaboratorEmailArr.splice(emptyStrIdx, 1);
}
return collaboratorEmailArr
.map(userEmail => getUserUidFromUserEmail(userEmail));
}

/**
* Returns a formatted and cleaned trip object that will be used as the data
* for the created Trip document.
*
* We know that rawTripObj will contain all of the necessary fields because each
* key-value pair is explicitly included. This means, only the value
* corresponding to each key needs to be checked.
* For text element inputs, React has built in protections against injection/XSS
* attacks. Thus, no sanitization is needed for text inputs besides providing a
* default value in a Trip field where applicable.
*
* @param {Object} rawTripObj A JS Object containing the raw form data from the
* add trip form.
* @return {Object} Formatted/cleaned version of `rawTripObj` holding the data
* for the new Trip document that is to be created.
*/
export function formatTripData(rawTripObj) {
const defaultName = "Untitled Trip";
const defaultDestination = "No Destination"

const formattedTripObj = {
trip_creation_time: firebase.firestore.Timestamp.now(),
name: getCleanedTextInput(rawTripObj.name, defaultName),
description: rawTripObj.description,
destination: getCleanedTextInput(rawTripObj.destination,
defaultDestination),
start_date: getTimestampFromDateString(rawTripObj.startDate),
end_date: getTimestampFromDateString(rawTripObj.endDate),
collaborators: getCollaboratorUidArray(rawTripObj.collaboratorEmails),
};

return formattedTripObj;
}
67 changes: 67 additions & 0 deletions frontend/src/components/Utils/filter-input.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { getUserUidFromUserEmail } from './temp-auth-utils';
import { getCleanedTextInput, getCollaboratorUidArray } from './filter-input.js';

describe('getCleanedTextInput tests', () => {
test('No input entered in form (empty string)', () => {
const testDefaultValue = 'Untitled Trip';
const testRawName = '';
const expectedTripName = testDefaultValue;

const testTripName = getCleanedTextInput(testRawName, testDefaultValue);

expect(testTripName).toEqual(expectedTripName);
});

test('Input entered into form', () => {
const testDefaultValue = 'Untitled Trip';
const testRawName = 'Trip to No Man\'s Land';
const expectedTripName = testRawName;

const testTripName = getCleanedTextInput(testRawName, testDefaultValue);

expect(testTripName).toEqual(expectedTripName);
});
});

const mockCurUserEmail = '[email protected]';
// TODO(Issue #55): Replace mock with real auth file once integrated.
jest.mock('./temp-auth-utils.js', () => ({
getCurUserEmail: () => mockCurUserEmail,
getUserUidFromUserEmail: (userEmail) => '_' + userEmail + '_',
}));
describe('getCollaboratorUidArray tests', () => {
test('No collaborators entered', () => {
const expectedUidArr = [getUserUidFromUserEmail(mockCurUserEmail)];
// This is the list that is created when there are no collaborators added
// (automatically one empty string from the constructor created ref).
const testEmailArr = [''];

const testUidArr = getCollaboratorUidArray(testEmailArr);

expect(testUidArr).toEqual(expectedUidArr);
});

test('Some added collaborators', () => {
const person1Email = '[email protected]';
const person2Email = '[email protected]';
const expectedUidArr = [getUserUidFromUserEmail(mockCurUserEmail),
getUserUidFromUserEmail(person1Email), getUserUidFromUserEmail(person2Email)];
const testEmailArr = [person1Email, person2Email];

const testUidArr = getCollaboratorUidArray(testEmailArr);

expect(testUidArr).toEqual(expectedUidArr);
});

test('Some added collaborators and some blank entries', () => {
const person1Email = '[email protected]';
const person2Email = '[email protected]';
const expectedUidArr = [getUserUidFromUserEmail(mockCurUserEmail),
getUserUidFromUserEmail(person1Email), getUserUidFromUserEmail(person2Email)];
const testEmailArr = ['', person1Email, '', person2Email, ''];

const testUidArr = getCollaboratorUidArray(testEmailArr);

expect(testUidArr).toEqual(expectedUidArr);
});
});
58 changes: 58 additions & 0 deletions frontend/src/components/Utils/temp-auth-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* @fileoverview This is a temporary file that is used to implement 'fake'
* versions of the Auth utility functions used in the ViewTrips components.
*
* TODO(Issue 55): Remove this whole file function and replace any imports to
* this file with Auth utils.
*/


/**
* Temporary hardcoded function that returns the current users email.
*
* @return Hardcoded user email string.
*/
export function getCurUserEmail() {
return 'matt.murdock';
}

/**
* Temporary hardcoded function that returns the current users uid.
*
* @return Hardcoded user uid string.
*/
export function getCurUserUid() {
return getUserUidFromUserEmail(getCurUserEmail());
}

/**
* Temporary hardcoded function that returns the user's uid given the user's
* email.
*
* @param {string} userEmail A users email.
* @return {string} The 'fake' uid associated with the user email that is
* created with the form '_`userEmail`_'.
*/
export function getUserUidFromUserEmail(userEmail) {
return '_' + userEmail + '_';
}

/**
* Temporary hardcoded function that returns the a user's email given the
* fake uid that was stored in the Trip document.
*
* @param {string} uid Fake string uid that is in the form '_userEmail_'.
* @return {string} The email corresponding to the fake uid.
*/
export function getUserEmailFromUid(uid) {
return uid.substring(1, uid.length - 1);
}

const authUtils = {
getCurUserEmail,
getCurUserUid,
getUserUidFromUserEmail,
getUserEmailFromUid
}

export default authUtils;
66 changes: 42 additions & 24 deletions frontend/src/components/Utils/time.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import * as firebase from 'firebase/app';

/**
* Format a timestamp (in milliseconds) into a pretty string with just the time.
*
* @param {int} msTimestamp
* @param {string} timezone
* @param {int} msTimestamp
* @param {string} timezone
* @returns {string} Time formatted into a string like '10:19 AM'.
*/
export function timestampToTimeFormatted(msTimestamp, timezone = 'America/New_York') {
const date = new Date(msTimestamp);
const formatOptions = {
hour: 'numeric',
minute: '2-digit',
const formatOptions = {
hour: 'numeric',
minute: '2-digit',
timeZone: timezone
};
return date.toLocaleTimeString('en-US', formatOptions);;
Expand All @@ -18,40 +20,56 @@ export function timestampToTimeFormatted(msTimestamp, timezone = 'America/New_Yo
/**
* Format a timestamp (in milliseconds) into a pretty string with just the date.
*
* @param {int} msTimestamp
* @param {string} timezone
* @param {int} msTimestamp
* @param {string} timezone
* @returns {string} Time formatted into a string like 'Monday, January 19, 1970'.
*/
export function timestampToDateFormatted(msTimestamp, timezone='America/New_York') {
const date = new Date(msTimestamp);
const formatOptions = {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
const formatOptions = {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
timeZone: timezone
};
return date.toLocaleDateString('en-US', formatOptions);
}

/**
/**
* Format a timestamp (in milliseconds) into a pretty string.
*
* @param {int} msTimestamp
* @param {string} timezone
* @returns {string} Time formatted into a string like
*
* @param {int} msTimestamp
* @param {string} timezone
* @returns {string} Time formatted into a string like
* "Monday, January 19, 1970, 02:48 AM"
*/
export function timestampToFormatted(msTimestamp, timezone = "America/New_York") {
let date = new Date(msTimestamp);
let formatOptions = {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
let formatOptions = {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
timeZone: timezone
};
return date.toLocaleString('en-US', formatOptions);
}

/**
* Return a Firestore Timestamp corresponding to the date in `dateStr`.
*
* @param {string} dateStr String containing a date in the form 'yyyy-mm-dd'.
* @return {firebase.firestore.Timestamp} Firestore timestamp object created.
*/
export function getTimestampFromDateString(dateStr) {
const dateParts = dateStr.split('-').map(str => +str);
if (dateParts.length === 1 && dateParts[0] === 0) {
return firebase.firestore.Timestamp.now();
}

const date = new Date(dateParts[0], dateParts[1] - 1, dateParts[2]);
return firebase.firestore.Timestamp.fromDate(date);
}
37 changes: 36 additions & 1 deletion frontend/src/components/Utils/time.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import * as firebase from 'firebase/app';
import 'firebase/firebase-firestore';

import * as utils from './time.js';

const TZ_CHICAGO = 'America/Chicago';
const TZ_SINGAPORE = 'Asia/Singapore';
const TZ_SINGAPORE = 'Asia/Singapore';

test('new york date timestamp format', () => {
// Month parameter is zero indexed so it's actually the 10th month.
Expand Down Expand Up @@ -56,3 +59,35 @@ test('other full timestamp format', () => {
expect(actualCentral).toEqual(expectedCentral);
expect(actualSingapore).toEqual(expectedSingapore);
})

const mockTimeNow = 0;
jest.mock('firebase/app', () => ({
firestore: {
Timestamp: {
now: () => mockTimeNow,
fromDate: (date) => date,
}
}
}));
describe('getTimeStampFromDateString tests', () => {
test('No date entered in form', () => {
const expectedTimestamp = mockTimeNow;
const testRawDate = '';

const testTimestamp = utils.getTimestampFromDateString(testRawDate);

expect(testTimestamp).toEqual(expectedTimestamp);
});

test('Date entered in form', () => {
const testDate = new Date(2020, 5, 4); // July 4, 2020
const expectedTimestamp = firebase.firestore.Timestamp.fromDate(testDate);

// This is the type of string (yyyy-mm-dd) that is returned from the form
// input type 'date'.
const testRawDate = testDate.toISOString().substring(0,10);
const testTimestamp = utils.getTimestampFromDateString(testRawDate);

expect(testTimestamp).toEqual(expectedTimestamp);
});
});
Loading

0 comments on commit 63ad790

Please sign in to comment.