Resolve merge conflicts

This commit is contained in:
Preston Doman 2019-03-06 21:16:40 -08:00
commit 729cce46b7
13 changed files with 695 additions and 280 deletions

View file

@ -1,4 +1,4 @@
EMAIL_HOST_USER=accountemail@youremail.com EMAIL_HOST_USER=accountemail@yourmail.com
EMAIL_HOST_PASSWORD=yourpassword EMAIL_HOST_PASSWORD=accountpasswordhere
SUBMIT_REPORT_DESTINATION_EMAIL=administratoremail@yourmail.com SUBMIT_REPORT_DESTINATION_EMAIL=to-address@yourmail.com
SUBMIT_REPORT_FROM_EMAIL=from-address@yourmail.com SUBMIT_REPORT_FROM_EMAIL=from-address@yourmail.com

View file

@ -1,8 +1,12 @@
from datetime import date import datetime
import datetime as mydate
#### Classes for policy, sections. Do not edit these. #### Classes for policy, sections. Do not edit these.
##################################################### #####################################################
def to_date(iso8601):
return mydate.datetime.strptime(iso8601, "%Y-%m-%d")
class Policy(): class Policy():
""" """
Represents the policy for the company/organization. Represents the policy for the company/organization.
@ -97,6 +101,7 @@ planning_section = Section(
"preferred_flight_fare": {"number": 6, "label": "Fare of your preferred flight", "field_type": "decimal"}, "preferred_flight_fare": {"number": 6, "label": "Fare of your preferred flight", "field_type": "decimal"},
"preferred_flight_duration": {"number": 7, "label": "Flight duration of your preferred flight (hours)", "field_type": "decimal"}, "preferred_flight_duration": {"number": 7, "label": "Flight duration of your preferred flight (hours)", "field_type": "decimal"},
"international_flight": {"number": 8, "label": "Is this an international flight?", "field_type": "boolean"}, "international_flight": {"number": 8, "label": "Is this an international flight?", "field_type": "boolean"},
"economy_class": {"number": 9, "label": "Is your ticket in economy/coach?", "field_type": "boolean"},
} }
) )
@ -127,21 +132,27 @@ def lowest_fare_rule(report, fields):
maximum = lowest_fare + 350 maximum = lowest_fare + 350
else: else:
maximum = lowest_fare + 600 maximum = lowest_fare + 600
if fields['preferred_fare'] > maximum: if fields['preferred_flight_fare'] > maximum:
return "For the lowest fare you have provided, your maximum in-policy fare amount is {} USD.".format(maximum) return "For the lowest fare you have provided, your maximum in-policy fare amount is {} USD.".format(maximum)
return None return None
planning_section.add_rule(title="Lowest fare check", rule=lowest_fare_rule) planning_section.add_rule(title="Lowest fare check", rule=lowest_fare_rule)
def departure_date_limit_rule(report, fields): def departure_date_limit_rule(report, fields):
days_to_departure = date(fields['departure_date']) - date(fields['screenshot_date']) days_to_departure = to_date(fields['departure_date']) - to_date(fields['screenshot_date'])
if days_to_departure < 14: if days_to_departure < datetime.timedelta(days=14):
return "Flights must be booked at least 14 days in advance." return "Flights must be booked at least 14 days in advance."
if days_to_departure > 365: if days_to_departure > datetime.timedelta(days=365):
return "Flights must be booked no more than 365 days in advance." return "Flights must be booked no more than 365 days in advance."
return None return None
planning_section.add_rule(title="Departure date limit", rule=departure_date_limit_rule) planning_section.add_rule(title="Departure date limit", rule=departure_date_limit_rule)
def economy_class_rule(report, fields):
if not fields['economy_class']:
return "Only economy or coach class tickets are within policy."
planning_section.add_rule(title="Economy class check", rule=economy_class_rule)
pol.add_section(planning_section) pol.add_section(planning_section)
#### Flight Info #### Flight Info
@ -173,9 +184,9 @@ def actual_fare_limit_rule(report, fields):
flight_section.add_rule(title="Fare limits", rule=actual_fare_limit_rule) flight_section.add_rule(title="Fare limits", rule=actual_fare_limit_rule)
def request_date_rule(report, fields): def request_date_rule(report, fields):
now = date.today() now = datetime.datetime.now()
last_travel = date(fields['return_date']) last_travel = to_date(fields['return_date'])
if now - last_travel > 90: if now - last_travel > datetime.timedelta(days=90):
return "Reimbursement requests must be made within 90 days of the last day of travel." return "Reimbursement requests must be made within 90 days of the last day of travel."
return None return None
@ -198,9 +209,9 @@ lodging_section = Section(
) )
def nightly_rate_check(report, fields): def nightly_rate_check(report, fields):
checkin_date = date(fields['check_in_date']) check_in_date = to_date(fields['check_in_date'])
checkout_date = date(fields['check_out_date']) check_out_date = to_date(fields['check_out_date'])
duration = checkout_date - checkin_date duration = (check_out_date - check_in_date).days
if fields['cost'] > duration * fields['per_diem_rate']: if fields['cost'] > duration * fields['per_diem_rate']:
return "The average nightly rate cannot exceed the U.S. GSA rate." return "The average nightly rate cannot exceed the U.S. GSA rate."
return None return None

View file

@ -1,3 +0,0 @@
from policy import pol
print(pol)

View file

@ -0,0 +1,84 @@
from django.test import TestCase
from .policy import pol
from unittest import mock
import datetime
class PolicyTests(TestCase):
report = {"key":"value"}
def test_general_section_pre_post_trip_check(self):
fields = {'after_trip':True}
result = pol.sections[0].rules[0]['rule'](self.report, fields)
self.assertEqual(result, "If you have already take the trip your request will require special approval by the administrator. You may skip the following 'Pre-trip Planning' section.")
def test_pre_flight_section_fare_limit_domestic(self):
fields = {'preferred_flight_fare':751,'international_flight':False}
result = pol.sections[1].rules[0]['rule'](self.report, fields)
self.assertEqual(result, "Fares for domestic flights over 750 USD require Conservancy pre-approval, even if other policy conditions have been met.")
def test_pre_flight_section_fare_limit_international(self):
fields = {'preferred_flight_fare':1651,'international_flight':True}
result = pol.sections[1].rules[0]['rule'](self.report, fields)
self.assertEqual(result, "Fares for international flights over 1,650 USD require Conservancy pre-approval, even if other policy conditions have been met.")
def test_pre_flight_section_lowest_fare_check_less_than_zero(self):
fields = {'lowest_fare_duration':10,'preferred_flight_duration':11,'lowest_fare':100,'preferred_flight_fare':1000}
result = pol.sections[1].rules[1]['rule'](self.report, fields)
self.assertEqual(result, "For the lowest fare you have provided, your maximum in-policy fare amount is 200 USD.")
def test_pre_flight_section_lowest_fare_check_less_than_three(self):
fields = {'lowest_fare_duration':10,'preferred_flight_duration':8,'lowest_fare':100,'preferred_flight_fare':1000}
result = pol.sections[1].rules[1]['rule'](self.report, fields)
self.assertEqual(result, "For the lowest fare you have provided, your maximum in-policy fare amount is 200 USD.")
def test_pre_flight_section_lowest_fare_check_less_than_six(self):
fields = {'lowest_fare_duration':10,'preferred_flight_duration':5,'lowest_fare':100,'preferred_flight_fare':1000}
result = pol.sections[1].rules[1]['rule'](self.report, fields)
self.assertEqual(result, "For the lowest fare you have provided, your maximum in-policy fare amount is 300 USD.")
def test_pre_flight_section_lowest_fare_check_less_than_ten(self):
fields = {'lowest_fare_duration':10,'preferred_flight_duration':2,'lowest_fare':100,'preferred_flight_fare':1000}
result = pol.sections[1].rules[1]['rule'](self.report, fields)
self.assertEqual(result, "For the lowest fare you have provided, your maximum in-policy fare amount is 450 USD.")
def test_pre_flight_section_lowest_fare_check_other(self):
fields = {'lowest_fare_duration':12,'preferred_flight_duration':1,'lowest_fare':100,'preferred_flight_fare':1000}
result = pol.sections[1].rules[1]['rule'](self.report, fields)
self.assertEqual(result, "For the lowest fare you have provided, your maximum in-policy fare amount is 700 USD.")
def test_pre_flight_section_departure_date_too_late(self):
fields = {'departure_date':'2019-03-13','screenshot_date':'2019-03-01'}
result = pol.sections[1].rules[2]['rule'](self.report, fields)
self.assertEqual(result, "Flights must be booked at least 14 days in advance.")
@mock.patch('datetime.date')
def test_pre_flight_section_departure_date_too_early(self, mocked_date):
mocked_date.today = mock.Mock(return_value=datetime.date(2019,1,1))
fields = {'departure_date':'2020-03-10','screenshot_date':'2019-03-01'}
result = pol.sections[1].rules[2]['rule'](self.report, fields)
self.assertEqual(result, "Flights must be booked no more than 365 days in advance.")
def test_flight_info_section_fare_limit_domestic(self):
fields = {'fare':751,'international_flight':False}
result = pol.sections[2].rules[0]['rule'](self.report, fields)
self.assertEqual(result, "Fares for domestic flights over 750 USD require Conservancy pre-approval, even if other policy conditions have been met.")
def test_flight_info_section_fare_limit_international(self):
fields = {'fare':1651,'international_flight':True}
result = pol.sections[2].rules[0]['rule'](self.report, fields)
self.assertEqual(result, "Fares for international flights over 1,650 USD require Conservancy pre-approval, even if other policy conditions have been met.")
def test_flight_info_section_request_date(self):
fields = {'return_date':'2018-01-01'}
result = pol.sections[2].rules[1]['rule'](self.report, fields)
self.assertEqual(result, "Reimbursement requests must be made within 90 days of the last day of travel.")
def test_hotels_lodging_section_average_nightly_rate(self):
fields = {'check_in_date':'2019-03-01','check_out_date':'2019-03-11','cost':1100,'per_diem_rate':100}
result = pol.sections[3].rules[0]['rule'](self.report, fields)
self.assertEqual(result, "The average nightly rate cannot exceed the U.S. GSA rate.")
def test_other_expenses_section_per_diem_check(self):
fields = {'rate':100,'full_days':3,'partial_days':2,'cost':451}
result = pol.sections[5].rules[0]['rule'](self.report, fields)
self.assertEqual(result, "You may only request a maximum of 450.0 USD for the rate and trip duration provided.")

View file

@ -0,0 +1,59 @@
from django.test import TestCase
from rest_framework.test import APIRequestFactory, force_authenticate
from backend.models import Report
from users.models import CustomUser
from unittest.mock import Mock, patch
from datetime import date
from backend.views import *
class ReportTests(TestCase):
def create_test_user(self, email, first, last, password):
user = CustomUser.objects.create_user(username=email, email=email, first_name=first, last_name=last, password=password)
return user
def mock_report():
r = Mock()
r.report_pk = 1
r.title = 'Report Title'
r.date_created = '2019-03-01'
r.date_submitted = '2019-03-01'
r.submitted = False
r.reference_number = '12345'
return r
def setUp(self):
self.test_user_1 = self.create_test_user('one@one.com', 'One', 'Mr. One', '1password')
self.test_user_1.save()
def test_create_report_logged_in(self):
factory = APIRequestFactory()
request = factory.post('/api/v1/report', {'title':'Test Report', 'reference':'12345'})
user = CustomUser.objects.get(email='one@one.com')
force_authenticate(request, user=user)
response = create_report(request)
self.assertEqual(response.status_code, 200)
report = Report.objects.get(user_id=user)
self.assertEqual(report.title, 'Test Report')
def test_create_report_logged_out(self):
factory = APIRequestFactory()
request = factory.post('/api/v1/report', {'title':'Test Report', 'reference':'12345'})
response = create_report(request)
self.assertEqual(response.status_code, 401)
@patch('backend.models.Report.objects.filter', Mock(return_value=[mock_report()]))
@patch('backend.views.get_sections', Mock(return_value={}))
def test_get_report(self):
result = get_report(1)
self.assertEqual(
result,
{
'date_created':'2019-03-01',
'reference_number':'12345',
'report_pk':1,
'title':'Report Title',
'date_submitted':'2019-03-01',
'submitted':False
}
)

View file

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View file

@ -5,9 +5,10 @@ from rest_framework.urlpatterns import format_suffix_patterns
from . import views from . import views
urlpatterns = [ urlpatterns = [
path('report', views.report), path('report', views.create_report),
path('reports', views.reports), path('reports', views.reports),
path('report/<int:report_pk>', views.report_detail), path('report/<int:report_pk>', views.report_detail),
path('report/<int:report_pk>/final', views.finalize_report),
path('report/<int:report_pk>/section/<int:section_pk>', views.section), path('report/<int:report_pk>/section/<int:section_pk>', views.section),
] ]

View file

@ -3,8 +3,6 @@ from django.http import JsonResponse
from .models import * from .models import *
from .policy import pol from .policy import pol
import os import os
from rest_framework.exceptions import ParseError
from rest_framework.parsers import FileUploadParser, MultiPartParser
from django.core.mail import EmailMultiAlternatives from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string from django.template.loader import render_to_string
from decouple import config from decouple import config
@ -99,7 +97,6 @@ def get_fields(s_id):
return field_set return field_set
def generate_named_fields_for_section(fields): def generate_named_fields_for_section(fields):
""" """
Prepares a dictionary of key-value pairs based on the raw fields Prepares a dictionary of key-value pairs based on the raw fields
@ -115,7 +112,7 @@ def generate_named_fields_for_section(fields):
return result return result
@api_view(['POST']) @api_view(['POST'])
def report(request): def create_report(request):
""" """
Generates a new empty report for the current user and returns it Generates a new empty report for the current user and returns it
in json format. The title of the report should be provided as in json format. The title of the report should be provided as
@ -205,17 +202,11 @@ def report_detail(request, report_pk):
return JsonResponse(data) return JsonResponse(data)
# PUT: Submits a report to the administrator for review, # PUT: Submits a report to the administrator for review,
# and marks it as "submitted", after which changes may # but is still allowed to make further changes
# not be made.
elif request.method == 'PUT': elif request.method == 'PUT':
rep = Report.objects.get(id=report_pk)
if rep.submitted == True:
return JsonResponse({"message": "Cannot submit a report that has already been submitted."}, status=409)
rep.submitted = True;
rep.save()
# Send email # Send email
send_report_to_admin(request, report_pk) send_report_to_admin(request, report_pk, status="REVIEW")
return JsonResponse({"message": "Report submitted."}) return JsonResponse({"message": "Request for review is submitted."})
# DELETE: Deletes a report from the user's account. # DELETE: Deletes a report from the user's account.
elif request.method == 'DELETE': elif request.method == 'DELETE':
@ -237,6 +228,26 @@ def report_detail(request, report_pk):
r.delete() r.delete()
return JsonResponse({"message": "Deleted report: {0}.".format(title)}) return JsonResponse({"message": "Deleted report: {0}.".format(title)})
@api_view(['PUT'])
def finalize_report(request, report_pk):
"""
This function serves as an API endpoint for submitting
the final report.
:param request: incoming request packet
:param report_pk: report ID
:return: JSON response containing user message
"""
r = Report.objects.get(id=report_pk)
if r.submitted:
return JsonResponse({"message": "Cannot submit a report that has already been submitted."}, status=409)
r.submitted = True
r.save()
# Send email
send_report_to_admin(request, report_pk, status="FINAL")
return JsonResponse({"message": "Final report submitted."})
def user_owns_section(user, section): def user_owns_section(user, section):
""" """
Returns true if the specified user is owner of the section. Returns true if the specified user is owner of the section.
@ -383,7 +394,7 @@ def section_complete(section_pk):
return True return True
return False return False
def send_report_to_admin(request, report_pk): def send_report_to_admin(request, report_pk, status):
""" """
Sends an email message to admin with html/txt version of report, Sends an email message to admin with html/txt version of report,
along with file attachments. Cc sent to user. along with file attachments. Cc sent to user.
@ -400,7 +411,7 @@ def send_report_to_admin(request, report_pk):
message = None message = None
if params['reference_number'] == '': if params['reference_number'] == '':
message = EmailMultiAlternatives( message = EmailMultiAlternatives(
"{}".format(params['title']), "{} ({})".format(params['title'], status),
msg_plain, msg_plain,
from_email, from_email,
[to_email], [to_email],
@ -408,7 +419,7 @@ def send_report_to_admin(request, report_pk):
) )
else: else:
message = EmailMultiAlternatives( message = EmailMultiAlternatives(
"[RT - Request Tracker #{}] {}".format(params['reference_number'], params['title']), "[Reimbursinator #{}] {} ({})".format(params['reference_number'], params['title'], status),
msg_plain, msg_plain,
from_email, from_email,
[to_email], [to_email],

Binary file not shown.

View file

@ -232,7 +232,7 @@ function createCollapsibleCard(sectionIdStr, sectionTitle, sectionCompleted, rul
return card; return card;
} }
function createCollapsibleCardBody(form, type, sectionIdStr, sectionDescription, sectionCompleted, ruleViolations) { function createCollapsibleCardBody(form, sectionIdStr, sectionDescription, sectionCompleted, ruleViolations) {
// Create wrapper div // Create wrapper div
const collapseDiv = document.createElement("div"); const collapseDiv = document.createElement("div");
collapseDiv.id = sectionIdStr + "collapse"; collapseDiv.id = sectionIdStr + "collapse";
@ -342,13 +342,6 @@ function createReportForm(parsedData, type) {
// Traverse the fields of this section // Traverse the fields of this section
let fields = sections[i].fields; let fields = sections[i].fields;
for (let j = 0; j < fields.length; j++) { for (let j = 0; j < fields.length; j++) {
/*
console.log("Field label: " + fields[j].label);
console.log("Field type: " + fields[j].field_type);
console.log("Field value: " + fields[j].value);
*/
// Create a form group for each field and add it to the form // Create a form group for each field and add it to the form
form.appendChild(createFormGroup(sectionIdStr, fields[j])); form.appendChild(createFormGroup(sectionIdStr, fields[j]));
} }
@ -361,7 +354,7 @@ function createReportForm(parsedData, type) {
form.appendChild(saveButton); form.appendChild(saveButton);
// Create collapsible card body, append form to it, append card to accordion // Create collapsible card body, append form to it, append card to accordion
let cardBody = createCollapsibleCardBody(form, type, sectionIdStr, let cardBody = createCollapsibleCardBody(form, sectionIdStr,
sections[i].html_description, sections[i].completed, sections[i].rule_violations); sections[i].html_description, sections[i].completed, sections[i].rule_violations);
let cardFooter = createCardFooter(sections[i].rule_violations); let cardFooter = createCardFooter(sections[i].rule_violations);
if (cardFooter) { if (cardFooter) {

View file

@ -1,233 +0,0 @@
<html>
<head>
<meta charset="utf-8">
<title>QUnit Tests</title>
<link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.9.2.css">
<script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<script src="../js/viewHistory.js"></script>
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<script src="https://code.jquery.com/qunit/qunit-2.9.2.js"></script>
<script>
// BEGIN createFormGroup unit tests
QUnit.module("createFormGroup");
// BEGIN: Test rendering of fields with type boolean
QUnit.test("boolean input group false renders", function(assert) {
let sectionIdStr = "section-1-";
let field = {
"label": "Have you taken this trip already?",
"field_name": "after_trip",
"field_type": "boolean",
"value": false
};
let formGroup = createFormGroup(sectionIdStr, field);
let expectedHTML = `<div class="form-group row"><label class="col-sm-4 col-form" for="section-1-after_trip">Have you taken this trip already?: </label><div class="col-sm-6"><select name="after_trip" id="section-1-after_trip" class="form-control"><option value="true">Yes</option><option value="false" selected="selected">No</option></select></div></div>`
assert.deepEqual(formGroup.outerHTML, expectedHTML, "boolean false renders as no option selected");
});
QUnit.test("boolean input group true renders", function(assert) {
let sectionIdStr = "section-1-";
let field = {
"label": "Have you taken this trip already?",
"field_name": "after_trip",
"field_type": "boolean",
"value": true
};
let formGroup = createFormGroup(sectionIdStr, field);
let expectedHTML = `<div class="form-group row"><label class="col-sm-4 col-form" for="section-1-after_trip">Have you taken this trip already?: </label><div class="col-sm-6"><select name="after_trip" id="section-1-after_trip" class="form-control"><option value="true" selected="selected">Yes</option><option value="false">No</option></select></div></div>`
assert.deepEqual(formGroup.outerHTML, expectedHTML, "boolean true renders as yes option selected");
});
// END: Test rendering of fields with type boolean
// BEGIN: Test rendering of fields with type date
QUnit.test("date input group renders", function(assert) {
let sectionIdStr = "section-1-";
let field = {
"label": "Departure date",
"field_name": "departure_date",
"field_type": "date",
"value": "None"
};
let formGroup = createFormGroup(sectionIdStr, field);
let expectedHTML = `<div class="form-group row"><label class="col-sm-4 col-form" for="section-1-departure_date">Departure date: </label><div class="col-sm-6"><input name="departure_date" id="section-1-departure_date" type="date" placeholder="mm-dd-yyyy" class="form-control"></div></div>`
assert.deepEqual(formGroup.outerHTML, expectedHTML, "formGroup string matches expectedHTML string");
});
QUnit.test("date value None", function(assert) {
let sectionIdStr = "section-1-";
let field = {
"label": "Departure date",
"field_name": "departure_date",
"field_type": "date",
"value": "None"
};
let formGroup = createFormGroup(sectionIdStr, field);
let value = formGroup.querySelector("#section-1-departure_date").value;
assert.deepEqual(value, "", "date initialized to None has null value");
});
QUnit.test("date value assignment", function(assert) {
let sectionIdStr = "section-1-";
let field = {
"label": "Departure date",
"field_name": "departure_date",
"field_type": "date",
"value": "2019-02-28"
};
let formGroup = createFormGroup(sectionIdStr, field);
let value = formGroup.querySelector("#section-1-departure_date").value;
assert.deepEqual(value, field.value, "date input initialized to a value is rendered with that value");
});
// END: Test rendering of fields with type date
// BEGIN: Test rendering of fields with type string
QUnit.test("string input group renders", function(assert) {
let sectionIdStr = "section-1-";
let field = {
"label": "City",
"field_name": "city",
"field_type": "string",
"value": "Portland"
};
let formGroup = createFormGroup(sectionIdStr, field);
let expectedHTML = `<div class="form-group row"><label class="col-sm-4 col-form" for="section-1-city">City: </label><div class="col-sm-6"><input name="city" id="section-1-city" type="text" class="form-control"></div></div>`
assert.deepEqual(formGroup.outerHTML, expectedHTML, "formGroup string matches expectedHTML string");
});
QUnit.test("string value assignment", function(assert) {
let sectionIdStr = "section-1-";
let field = {
"label": "City",
"field_name": "city",
"field_type": "string",
"value": "Portland"
};
let formGroup = createFormGroup(sectionIdStr, field);
let value = formGroup.querySelector("#section-1-city").value;
assert.deepEqual(value, field.value, "text input initialized to a value is rendered with that value");
});
// END: Test rendering of fields with type date
// BEGIN: Test rendering of fields with type decimal
QUnit.test("decimal input group renders", function(assert) {
let sectionIdStr = "section-1-";
let field = {
"value": "0.00",
"field_type": "decimal",
"label": "Lowest fare",
"field_name": "lowest_fare"
};
let formGroup = createFormGroup(sectionIdStr, field);
let expectedHTML = `<div class="form-group row"><label class="col-sm-4 col-form" for="section-1-lowest_fare">Lowest fare: </label><div class="col-sm-6"><input name="lowest_fare" id="section-1-lowest_fare" type="number" class="form-control" step="0.01" min="0"></div></div>`
assert.deepEqual(formGroup.outerHTML, expectedHTML, "formGroup string matches expectedHTML string");
});
QUnit.test("decimal input group initialized to default", function(assert) {
let sectionIdStr = "section-1-";
let field = {
"value": "0.00",
"field_type": "decimal",
"label": "Lowest fare",
"field_name": "lowest_fare"
};
let formGroup = createFormGroup(sectionIdStr, field);
let value = formGroup.querySelector("#section-1-lowest_fare").value;
assert.deepEqual(value, "", "decimal input initialized to 0.00 has null value");
});
QUnit.test("decimal input group initialized to value", function(assert) {
let sectionIdStr = "section-1-";
let field = {
"value": "1337",
"field_type": "decimal",
"label": "Lowest fare",
"field_name": "lowest_fare"
};
let formGroup = createFormGroup(sectionIdStr, field);
let value = formGroup.querySelector("#section-1-lowest_fare").value;
assert.deepEqual(value, field.value, "decimal input initialized to 1337 has value 1337");
});
// END: Test rendering of fields with type decimal
// BEGIN: Test rendering of fields with type integer
QUnit.test("integer input group renders", function(assert) {
let sectionIdStr = "section-1-";
let field = {
"value": 0,
"field_type": "integer",
"label": "Number of full days of travel",
"field_name": "full_days"
};
let formGroup = createFormGroup(sectionIdStr, field);
let expectedHTML = `<div class="form-group row"><label class="col-sm-4 col-form" for="section-1-full_days">Number of full days of travel: </label><div class="col-sm-6"><input name="full_days" id="section-1-full_days" type="number" class="form-control" step="1" min="0"></div></div>`
assert.deepEqual(formGroup.outerHTML, expectedHTML, "formGroup string matches expectedHTML string");
});
QUnit.test("integer input group initialized to default", function(assert) {
let sectionIdStr = "section-1-";
let field = {
"value": 0,
"field_type": "integer",
"label": "Number of full days of travel",
"field_name": "full_days"
};
let formGroup = createFormGroup(sectionIdStr, field);
let value = formGroup.querySelector("#section-1-full_days").value;
assert.deepEqual(value, "", "integer input initialized to 0 has null value");
});
QUnit.test("integer input group initialized to value", function(assert) {
let sectionIdStr = "section-1-";
let field = {
"value": 1234,
"field_type": "integer",
"label": "Number of full days of travel",
"field_name": "full_days"
};
let formGroup = createFormGroup(sectionIdStr, field);
let value = formGroup.querySelector("#section-1-full_days").value;
assert.deepEqual(value, field.value.toString(), "integer input initialized to 1234 has string value 1234");
});
// END: Test rendering of fields with type integer
// BEGIN: Test rendering of fields with type file
QUnit.test("file input group renders", function(assert) {
let sectionIdStr = "section-1-";
let field = {
"value": "",
"field_type": "file",
"label": "Screenshot of invoice",
"field_name": "invoice_screenshot"
};
let formGroup = createFormGroup(sectionIdStr, field);
let expectedHTML = `<div class="form-group row"><label class="col-sm-4 col-form" for="section-1-invoice_screenshot">Screenshot of invoice: </label><div class="col-sm-6"><input name="invoice_screenshot" id="section-1-invoice_screenshot" type="file" class="form-control-file"><p class="form-text"></p></div></div>`
assert.deepEqual(formGroup.outerHTML, expectedHTML, "formGroup string matches expectedHTML string");
});
QUnit.test("file input group form text assignment", function(assert) {
let sectionIdStr = "section-1-";
let field = {
"value": "screenshot.jpg",
"field_type": "file",
"label": "Screenshot of invoice",
"field_name": "invoice_screenshot"
};
let formGroup = createFormGroup(sectionIdStr, field);
let value = formGroup.querySelector(".form-text").innerHTML;
assert.deepEqual(value, field.value, "file input initialized to screenshot.jpg has string value screenshot.jpg");
});
// END: Test rendering of fields with type file
</script>
</body>
</html>

View file

@ -0,0 +1,451 @@
<html>
<head>
<meta charset="utf-8">
<title>QUnit Tests</title>
<link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.9.2.css">
<script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<script src="../static/js/viewHistory.js"></script>
<script src="testObjects.js"></script>
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture">
<div class="card-body">
<div class="text-center">
<i class="fas fa-spinner fa-3x fa-spin"></i>
</div>
<table class="table table-striped table-responsive-sm" style="visibility:hidden">
<thead>
<tr>
<th>Title</th>
<th>Date Created</th>
<th class="d-none d-md-table-cell">Date Submitted</th>
<th>Action</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<div class="modal fade" id="newReportModal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="newReportModalLabel"></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body" id="newReportModalBody">
<div class="text-center">
<i class="fas fa-spinner fa-3x fa-spin"></i>
<br>
<br>
<h5>Creating Report ...</h5>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary submit-report-button">Submit Report</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="editReportModal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="editReportModalLabel"></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body" id="editReportModalBody">
<div class="text-center">
<i class="fas fa-spinner fa-3x fa-spin"></i>
<br>
<br>
<h5>Loading Report ...</h5>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger delete-report">Delete Report</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary submit-report-button">Submit Report</button>
</div>
</div>
</div>
</div>
</div>
<script src="https://code.jquery.com/qunit/qunit-2.9.2.js"></script>
<script>
// BEGIN createFormGroup unit tests
QUnit.module("createFormGroup");
// BEGIN: Test rendering of fields with type boolean
QUnit.test("boolean input group false renders", function(assert) {
let sectionIdStr = "section-1-";
let field = {
"label": "Have you taken this trip already?",
"field_name": "after_trip",
"field_type": "boolean",
"value": false
};
let formGroup = createFormGroup(sectionIdStr, field);
let expectedHTML = `<div class="form-group row"><label class="col-sm-4 col-form" for="section-1-after_trip">Have you taken this trip already?: </label><div class="col-sm-6"><select name="after_trip" id="section-1-after_trip" class="form-control"><option value="true">Yes</option><option value="false" selected="selected">No</option></select></div></div>`
assert.deepEqual(formGroup.outerHTML, expectedHTML, "boolean false renders as no option selected");
});
QUnit.test("boolean input group true renders", function(assert) {
let sectionIdStr = "section-1-";
let field = {
"label": "Have you taken this trip already?",
"field_name": "after_trip",
"field_type": "boolean",
"value": true
};
let formGroup = createFormGroup(sectionIdStr, field);
let expectedHTML = `<div class="form-group row"><label class="col-sm-4 col-form" for="section-1-after_trip">Have you taken this trip already?: </label><div class="col-sm-6"><select name="after_trip" id="section-1-after_trip" class="form-control"><option value="true" selected="selected">Yes</option><option value="false">No</option></select></div></div>`
assert.deepEqual(formGroup.outerHTML, expectedHTML, "boolean true renders as yes option selected");
});
// BEGIN: Test rendering of fields with type date
QUnit.test("date input group renders", function(assert) {
let sectionIdStr = "section-1-";
let field = {
"label": "Departure date",
"field_name": "departure_date",
"field_type": "date",
"value": "None"
};
let formGroup = createFormGroup(sectionIdStr, field);
let expectedHTML = `<div class="form-group row"><label class="col-sm-4 col-form" for="section-1-departure_date">Departure date: </label><div class="col-sm-6"><input name="departure_date" id="section-1-departure_date" type="date" placeholder="mm-dd-yyyy" class="form-control"></div></div>`
assert.deepEqual(formGroup.outerHTML, expectedHTML, "formGroup string matches expectedHTML string");
});
QUnit.test("date value None", function(assert) {
let sectionIdStr = "section-1-";
let field = {
"label": "Departure date",
"field_name": "departure_date",
"field_type": "date",
"value": "None"
};
let formGroup = createFormGroup(sectionIdStr, field);
let value = formGroup.querySelector("#section-1-departure_date").value;
assert.deepEqual(value, "", "date initialized to None has null value");
});
QUnit.test("date value assignment", function(assert) {
let sectionIdStr = "section-1-";
let field = {
"label": "Departure date",
"field_name": "departure_date",
"field_type": "date",
"value": "2019-02-28"
};
let formGroup = createFormGroup(sectionIdStr, field);
let value = formGroup.querySelector("#section-1-departure_date").value;
assert.deepEqual(value, field.value, "date input initialized to a value is rendered with that value");
});
// BEGIN: Test rendering of fields with type string
QUnit.test("string input group renders", function(assert) {
let sectionIdStr = "section-1-";
let field = {
"label": "City",
"field_name": "city",
"field_type": "string",
"value": "Portland"
};
let formGroup = createFormGroup(sectionIdStr, field);
let expectedHTML = `<div class="form-group row"><label class="col-sm-4 col-form" for="section-1-city">City: </label><div class="col-sm-6"><input name="city" id="section-1-city" type="text" class="form-control"></div></div>`
assert.deepEqual(formGroup.outerHTML, expectedHTML, "formGroup string matches expectedHTML string");
});
QUnit.test("string value assignment", function(assert) {
let sectionIdStr = "section-1-";
let field = {
"label": "City",
"field_name": "city",
"field_type": "string",
"value": "Portland"
};
let formGroup = createFormGroup(sectionIdStr, field);
let value = formGroup.querySelector("#section-1-city").value;
assert.deepEqual(value, field.value, "text input initialized to a value is rendered with that value");
});
// BEGIN: Test rendering of fields with type decimal
QUnit.test("decimal input group renders", function(assert) {
let sectionIdStr = "section-1-";
let field = {
"value": "0.00",
"field_type": "decimal",
"label": "Lowest fare",
"field_name": "lowest_fare"
};
let formGroup = createFormGroup(sectionIdStr, field);
let expectedHTML = `<div class="form-group row"><label class="col-sm-4 col-form" for="section-1-lowest_fare">Lowest fare: </label><div class="col-sm-6"><input name="lowest_fare" id="section-1-lowest_fare" type="number" class="form-control" step="0.01" min="0"></div></div>`
assert.deepEqual(formGroup.outerHTML, expectedHTML, "formGroup string matches expectedHTML string");
});
QUnit.test("decimal input group initialized to default", function(assert) {
let sectionIdStr = "section-1-";
let field = {
"value": "0.00",
"field_type": "decimal",
"label": "Lowest fare",
"field_name": "lowest_fare"
};
let formGroup = createFormGroup(sectionIdStr, field);
let value = formGroup.querySelector("#section-1-lowest_fare").value;
assert.deepEqual(value, "", "decimal input initialized to 0.00 has null value");
});
QUnit.test("decimal input group initialized to value", function(assert) {
let sectionIdStr = "section-1-";
let field = {
"value": "1337",
"field_type": "decimal",
"label": "Lowest fare",
"field_name": "lowest_fare"
};
let formGroup = createFormGroup(sectionIdStr, field);
let value = formGroup.querySelector("#section-1-lowest_fare").value;
assert.deepEqual(value, field.value, "decimal input initialized to 1337 has value 1337");
});
// BEGIN: Test rendering of fields with type integer
QUnit.test("integer input group renders", function(assert) {
let sectionIdStr = "section-1-";
let field = {
"value": 0,
"field_type": "integer",
"label": "Number of full days of travel",
"field_name": "full_days"
};
let formGroup = createFormGroup(sectionIdStr, field);
let expectedHTML = `<div class="form-group row"><label class="col-sm-4 col-form" for="section-1-full_days">Number of full days of travel: </label><div class="col-sm-6"><input name="full_days" id="section-1-full_days" type="number" class="form-control" step="1" min="0"></div></div>`
assert.deepEqual(formGroup.outerHTML, expectedHTML, "formGroup string matches expectedHTML string");
});
QUnit.test("integer input group initialized to default", function(assert) {
let sectionIdStr = "section-1-";
let field = {
"value": 0,
"field_type": "integer",
"label": "Number of full days of travel",
"field_name": "full_days"
};
let formGroup = createFormGroup(sectionIdStr, field);
let value = formGroup.querySelector("#section-1-full_days").value;
assert.deepEqual(value, "", "integer input initialized to 0 has null value");
});
QUnit.test("integer input group initialized to value", function(assert) {
let sectionIdStr = "section-1-";
let field = {
"value": 1234,
"field_type": "integer",
"label": "Number of full days of travel",
"field_name": "full_days"
};
let formGroup = createFormGroup(sectionIdStr, field);
let value = formGroup.querySelector("#section-1-full_days").value;
assert.deepEqual(value, field.value.toString(), "integer input initialized to 1234 has string value 1234");
});
// BEGIN: Test rendering of fields with type file
QUnit.test("file input group renders", function(assert) {
let sectionIdStr = "section-1-";
let field = {
"value": "",
"field_type": "file",
"label": "Screenshot of invoice",
"field_name": "invoice_screenshot"
};
let formGroup = createFormGroup(sectionIdStr, field);
let expectedHTML = `<div class="form-group row"><label class="col-sm-4 col-form" for="section-1-invoice_screenshot">Screenshot of invoice: </label><div class="col-sm-6"><input name="invoice_screenshot" id="section-1-invoice_screenshot" type="file" class="form-control-file"><p class="form-text"></p></div></div>`
assert.deepEqual(formGroup.outerHTML, expectedHTML, "formGroup string matches expectedHTML string");
});
QUnit.test("file input group form text assignment", function(assert) {
let sectionIdStr = "section-1-";
let field = {
"value": "screenshot.jpg",
"field_type": "file",
"label": "Screenshot of invoice",
"field_name": "invoice_screenshot"
};
let formGroup = createFormGroup(sectionIdStr, field);
let value = formGroup.querySelector(".form-text").innerHTML;
assert.deepEqual(value, field.value, "file input initialized to screenshot.jpg has string value screenshot.jpg");
});
// BEGIN createCollapsibleCard unit tests
QUnit.module("createCollapsibleCard");
QUnit.test("incomplete section renders", function(assert) {
let sectionIdStr = "section-1-";
let sectionTitle = "General Info";
let sectionCompleted = false;
let ruleViolations = [];
let card = createCollapsibleCard(sectionIdStr, sectionTitle, sectionCompleted, ruleViolations);
let expectedHTML = `<div class="card"><div class="card-header"><h2 class="mb-0"><button class="btn btn-link" type="button" data-toggle="collapse" data-target="#section-1-collapse">General Info</button><i id="section-1-state"></i></h2></div></div>`;
assert.deepEqual(card.outerHTML, expectedHTML, "card html and expectedHTML are identical");
});
QUnit.test("complete section with no rule violations renders", function(assert) {
let sectionIdStr = "section-1-";
let sectionTitle = "General Info";
let sectionCompleted = true;
let ruleViolations = [];
let card = createCollapsibleCard(sectionIdStr, sectionTitle, sectionCompleted, ruleViolations);
let expectedHTML = `<div class="card"><div class="card-header"><h2 class="mb-0"><button class="btn btn-link" type="button" data-toggle="collapse" data-target="#section-1-collapse">General Info</button><i id="section-1-state" class="fas fa-check-square"></i></h2></div></div>`
assert.deepEqual(card.outerHTML, expectedHTML, "card html and expectedHTML are identical");
});
QUnit.test("complete section with a violation renders", function(assert) {
let sectionIdStr = "section-1-";
let sectionTitle = "General Info";
let sectionCompleted = true;
let ruleViolations = [{"label": "Fare limits", "rule_break_text": "You did a bad thing"}]
let card = createCollapsibleCard(sectionIdStr, sectionTitle, sectionCompleted, ruleViolations);
let expectedHTML = `<div class="card"><div class="card-header"><h2 class="mb-0"><button class="btn btn-link" type="button" data-toggle="collapse" data-target="#section-1-collapse">General Info</button><i id="section-1-state" class="fas fa-exclamation-triangle"></i></h2></div></div>`
assert.deepEqual(card.outerHTML, expectedHTML, "card html and expectedHTML are identical");
});
// BEGIN createCollapsibleCard unit tests
QUnit.module("createCollapsibleCardBody");
QUnit.test("incomplete section renders", function(assert) {
let form = document.createElement("form");
let sectionIdStr = "section-1-";
let sectionDescription = "<p>Section Description</p>";
let sectionCompleted = false;
let ruleViolations = [];
let collapseDiv = createCollapsibleCardBody(form, sectionIdStr, sectionDescription, sectionCompleted, ruleViolations);
let expectedHTML = `<div id="section-1-collapse" class="collapse show"><div class="card-body"><div class="alert alert-danger section-alert">This section is not complete</div><p>Section Description</p><form></form></div></div>`;
assert.deepEqual(collapseDiv.outerHTML, expectedHTML, "collapseDiv html and expectedHTML are identical");
});
QUnit.test("complete section with no rule violations renders", function(assert) {
let form = document.createElement("form");
let sectionIdStr = "section-1-";
let sectionDescription = "<p>Section Description</p>";
let sectionCompleted = true;
let ruleViolations = [];
let collapseDiv = createCollapsibleCardBody(form, sectionIdStr, sectionDescription, sectionCompleted, ruleViolations);
let expectedHTML = `<div id="section-1-collapse" class="collapse"><div class="card-body"><div></div><p>Section Description</p><form></form></div></div>`;
assert.deepEqual(collapseDiv.outerHTML, expectedHTML, "collapseDiv html and expectedHTML are identical");
});
QUnit.test("complete section with rule violation renders", function(assert) {
let form = document.createElement("form");
let sectionIdStr = "section-1-";
let sectionDescription = "<p>Section Description</p>";
let sectionCompleted = true;
let ruleViolations = [{"label": "Fare limits", "rule_break_text": "You did a bad thing"}]
let collapseDiv = createCollapsibleCardBody(form, sectionIdStr, sectionDescription, sectionCompleted, ruleViolations);
let expectedHTML = `<div id="section-1-collapse" class="collapse show"><div class="card-body"><div></div><p>Section Description</p><form></form></div></div>`;
assert.deepEqual(collapseDiv.outerHTML, expectedHTML, "collapseDiv html and expectedHTML are identical");
});
// BEGIN createCardFooter unit tests
QUnit.module("createCardFooter");
QUnit.test("card footer no rule violations does not render", function(assert) {
let ruleViolations = [];
let cardFooter = createCardFooter(ruleViolations);
assert.strictEqual(cardFooter, null, "cardFooter is null");
});
QUnit.test("card footer with one rule violation renders", function(assert) {
let ruleViolations = [{"label": "Fare limits", "rule_break_text": "You did a bad thing"}];
let cardFooter = createCardFooter(ruleViolations);
let expectedHTML = `<div class="card-footer"><div class="alert alert-danger"><div class="alert-heading">Rule Violations</div><hr><p><strong>Fare limits</strong><br>You did a bad thing</p></div></div>`;
assert.deepEqual(cardFooter.outerHTML, expectedHTML, "cardFooter html and expectedHTML are identical");
});
QUnit.test("card footer with multiple rule violation renders", function(assert) {
let ruleViolations = [{"label": "Fare limits", "rule_break_text": "You did a bad thing"}, {"label": "Fare limits", "rule_break_text": "Now you've done it"}];
let cardFooter = createCardFooter(ruleViolations);
let expectedHTML = `<div class="card-footer"><div class="alert alert-danger"><div class="alert-heading">Rule Violations</div><hr><p><strong>Fare limits</strong><br>You did a bad thing</p><p><strong>Fare limits</strong><br>Now you've done it</p></div></div>`;
assert.deepEqual(cardFooter.outerHTML, expectedHTML, "cardFooter html and expectedHTML are identical");
});
// BEGIN createReportForm unit tests
QUnit.module("createReportForm");
QUnit.test("new report renders", function(assert) {
createReportForm(testReport, reportType.NEW);
let newReportModal = document.getElementById("newReportModal");
let expectedHTML = typeNewExpectedHTML;
assert.deepEqual(newReportModal.outerHTML, expectedHTML, "new report form and expectedHTML are identical")
});
QUnit.test("edit report renders", function(assert) {
createReportForm(testReport, reportType.EDIT);
let editReportModal = document.getElementById("editReportModal");
let expectedHTML = typeEditExpectedHTML;
assert.deepEqual(editReportModal.outerHTML, expectedHTML, "edit report form and expectedHTML are identical")
});
QUnit.test("undefined report does not render", function(assert) {
let qunitFixture = document.getElementById("qunit-fixture");
let expectedHTML = qunitFixture.outerHTML;
createReportForm(testReport, undefined);
assert.deepEqual(qunitFixture.outerHTML, expectedHTML, "report forms and expectedHTML are identical")
});
// BEGIN displayListOfReports unit tests
QUnit.module("displayListOfReports");
QUnit.test("empty reports", function(assert) {
let expectedHTML = `<div class="card-body"><h5 class="text-center">No reports found.</h5></div>`;
let parsedData = {"reports": []};
displayListOfReports(parsedData);
let cardBody = document.querySelector(".card-body");
assert.deepEqual(cardBody.outerHTML.replace(/>\s+</g, "><"), expectedHTML, "card body and expectedHTML are identical");
});
QUnit.test("one editable report", function(assert) {
let parsedData = {"reports": [{"date_created": "2019-03-05T08:00:00Z", "title": "TEST1", "reference_number": "1234", "date_submitted": "2019-03-05T08:00:00Z", "user_id": 2, "submitted": false, "report_pk": 4}]};
let expectedHTML = displayReportsOneReportExpected;
displayListOfReports(parsedData);
let cardBody = document.querySelector(".card-body");
assert.deepEqual(cardBody.outerHTML.replace(/>\s+</g, "><"), expectedHTML, "card body and expectedHTML are identical");
});
QUnit.test("two editable reports", function(assert) {
let parsedData = {"reports": [{"date_created": "2019-03-05T08:00:00Z", "title": "TEST1", "reference_number": "1234", "date_submitted": "2019-03-05T08:00:00Z", "user_id": 2, "submitted": false, "report_pk": 4}, {"date_created": "2019-03-05T08:00:00Z", "title": "TEST2", "reference_number": "12345", "date_submitted": "2019-03-05T08:00:00Z", "user_id": 2, "submitted": false, "report_pk": 5}]};
let expectedHTML = displayReportsTwoReportsExpected;
displayListOfReports(parsedData);
let cardBody = document.querySelector(".card-body");
assert.deepEqual(cardBody.outerHTML.replace(/>\s+</g, "><"), expectedHTML, "card body and expectedHTML are identical");
});
QUnit.test("one viewable report", function(assert) {
let parsedData = {"reports": [{"date_created": "2019-03-05T08:00:00Z", "title": "TEST2", "reference_number": "12345", "date_submitted": "2019-03-05T08:00:00Z", "user_id": 2, "submitted": true, "report_pk": 5}]};
let expectedHTML = displayReportsOneViewableExpected;
displayListOfReports(parsedData);
let cardBody = document.querySelector(".card-body");
assert.deepEqual(cardBody.outerHTML.replace(/>\s+</g, "><"), expectedHTML, "card body and expectedHTML are identical");
});
</script>
</body>
</html>

File diff suppressed because one or more lines are too long