Initial JS tests for the Vue TOForm component.

This adds BeautifulSoup to the Python dev dependencies so that we can
render an entire page and then extract the Vue component we need.
Ideally, we should refactor all the Vue components so that they live in
Jinja macros and we can render those macros directly.
This commit is contained in:
dandds 2019-09-13 11:08:50 -04:00
parent 4ba3e6cd97
commit 112f0e0ab2
11 changed files with 538 additions and 68 deletions

View File

@ -3,7 +3,7 @@
"files": "^.secrets.baseline$",
"lines": null
},
"generated_at": "2019-09-13T17:44:56Z",
"generated_at": "2019-09-20T19:20:43Z",
"plugins_used": [
{
"base64_limit": 4.5,
@ -199,5 +199,5 @@
}
]
},
"version": "0.12.5"
"version": "0.12.6"
}

View File

@ -44,6 +44,7 @@ honcho = "*"
blinker = "*"
pytest-mock = "*"
detect-secrets = "*"
beautifulsoup4 = "*"
[requires]
python_version = "3.7.3"

136
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "bf3b598c052193f70249da97ac746bc53aeb72f45a4515e10945a4274aba7b18"
"sha256": "8eb62f8620ec951cbef680341d18a184e9661cd28e4c3761e9679dc840fd1888"
},
"pipfile-spec": 6,
"requires": {
@ -18,10 +18,10 @@
"default": {
"alembic": {
"hashes": [
"sha256:4a4811119efbdc5259d1f4c8f6de977b36ad3bcc919f59a29c2960c5ef9149e4"
"sha256:5609afbb2ab142a991b15ae436347c475f8a517f1610f2fd1b09cdca7c311f3f"
],
"index": "pypi",
"version": "==1.1.0"
"version": "==1.2.0"
},
"amqp": {
"hashes": [
@ -32,11 +32,11 @@
},
"apache-libcloud": {
"hashes": [
"sha256:8f133038710257d39f9092ccaea694e31f7f4fe02c11d7fcc2674bc60a9448b6",
"sha256:d876f8c4d8aecf32f41f3a4d6ed1cebdf33b8b8f73df0aebc5280789fa806c60"
"sha256:201751f738109f25d58dcdfb5804e17216e0dc8f68b522e9e26ac16e0b9ff2ea",
"sha256:40215db1bd489d17dc1abfdb289d7f035313c7297b6a7462c79d8287cbbeae91"
],
"index": "pypi",
"version": "==2.5.0"
"version": "==2.6.0"
},
"asn1crypto": {
"hashes": [
@ -85,18 +85,18 @@
},
"boto3": {
"hashes": [
"sha256:366a1f3ec37b9434f25247cbe876f9ca1b53d35e35af18f74c735445100b4bc4",
"sha256:e7718b48cd073ad59a99a33d14252319dfaf550be3682b0c6a58da052fb05fcc"
"sha256:0e4d047feb4d7d701e9b2107f10bb8d674952243385cd35d0b413a273c299751",
"sha256:67f957389cf56fb4c24c1093c6d58baebe6cf18139f6dca0f8a177239b0a4f8c"
],
"index": "pypi",
"version": "==1.9.217"
"version": "==1.9.232"
},
"botocore": {
"hashes": [
"sha256:68a0a22ca4e0e7e7ab482f63e21debfe402841fc49b8503dec0a7307b565d774",
"sha256:7a213b876e58b1b5380cf30faa05ba45073692ad4a3cc803ba763082a36436bb"
"sha256:724d2349198c6f15f3cee0c0e4d33ecf4435e6d0db311bb79a3a28f6cf5a4090",
"sha256:a57a8fd0145c68e31bb4baab549b27a12f6695068c8dd5f2901d8dc06572dbeb"
],
"version": "==1.12.217"
"version": "==1.12.232"
},
"celery": {
"hashes": [
@ -108,10 +108,10 @@
},
"certifi": {
"hashes": [
"sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939",
"sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695"
"sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50",
"sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef"
],
"version": "==2019.6.16"
"version": "==2019.9.11"
},
"cffi": {
"hashes": [
@ -237,10 +237,10 @@
},
"importlib-metadata": {
"hashes": [
"sha256:23d3d873e008a513952355379d93cbcab874c58f4f034ff657c7a87422fa64e8",
"sha256:80d2de76188eabfbfcf27e6a37342c2827801e59c4cc14b0371c56fed43820e3"
"sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26",
"sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af"
],
"version": "==0.19"
"version": "==0.23"
},
"itsdangerous": {
"hashes": [
@ -409,10 +409,10 @@
},
"pytzdata": {
"hashes": [
"sha256:c0c8316eaf6c25ba45816390a1a45c39790767069b3275c5f7de3ddf773eb810",
"sha256:e8a91952afd853642a49f0713caac3e15a5306855ff4a47af4ddec5b7dd23a09"
"sha256:84c52b9a47d097fcd483f047a544979de6c3a86e94c845e3569e9f8acd0fa071",
"sha256:fac06f7cdfa903188dc4848c655e4adaee67ee0f2fe08e7daf815cf2a761ee5e"
],
"version": "==2019.2"
"version": "==2019.3"
},
"pyyaml": {
"hashes": [
@ -480,11 +480,11 @@
},
"urllib3": {
"hashes": [
"sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1",
"sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232"
"sha256:2f3eadfea5d92bc7899e75b5968410b749a054b492d5a6379c1344a1481bc2cb",
"sha256:9c6c593cb28f52075016307fc26b0a0f8e82bc7d1ff19aaaa959b91710a56c47"
],
"markers": "python_version >= '3.4'",
"version": "==1.25.3"
"version": "==1.25.5"
},
"vine": {
"hashes": [
@ -502,11 +502,11 @@
},
"werkzeug": {
"hashes": [
"sha256:87ae4e5b5366da2347eb3116c0e6c681a0e939a33b2805e2c0cbd282664932c4",
"sha256:a13b74dd3c45f758d4ebdb224be8f1ab8ef58b3c0ffc1783a8c7d9f4f50227e6"
"sha256:7280924747b5733b246fe23972186c6b348f9ae29724135a6dfc1e53cea433e7",
"sha256:e5f4a1f98b52b18a93da705a7458e55afb26f32bff83ff5d19189f92462d65c4"
],
"index": "pypi",
"version": "==0.15.5"
"version": "==0.16.0"
},
"wtforms": {
"hashes": [
@ -582,6 +582,15 @@
"index": "pypi",
"version": "==1.6.2"
},
"beautifulsoup4": {
"hashes": [
"sha256:05668158c7b85b791c5abde53e50265e16f98ad601c402ba44d70f96c4159612",
"sha256:25288c9e176f354bf277c0a10aa96c782a6a18a17122dba2e8cec4a97e03343b",
"sha256:f040590be10520f2ea4c2ae8c3dae441c7cfff5308ec9d58a0ec0c1b8f81d469"
],
"index": "pypi",
"version": "==4.8.0"
},
"black": {
"hashes": [
"sha256:09a9dcb7c46ed496a9850b76e4e825d6049ecd38b611f1224857a79bd985a8cf",
@ -599,10 +608,10 @@
},
"certifi": {
"hashes": [
"sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939",
"sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695"
"sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50",
"sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef"
],
"version": "==2019.6.16"
"version": "==2019.9.11"
},
"chardet": {
"hashes": [
@ -666,11 +675,11 @@
},
"detect-secrets": {
"hashes": [
"sha256:33593cbf7099ced595e7371fcee31c815a237419de2869d571bbdb651a125d4c",
"sha256:7b8e6d521f5a164ff17dbe3d2691eb85f842133d4e6bb7a23eeb461a0ab4e215"
"sha256:7e1820a3c4ac412a7a2cec13075c274ae4bfc9167b4b831ad3c7f0e6208c9488",
"sha256:bacb5842f149f39799409039fafb1902554ac0c71a9764cc8a8ffab85f99efc1"
],
"index": "pypi",
"version": "==0.12.5"
"version": "==0.12.6"
},
"docopt": {
"hashes": [
@ -688,10 +697,10 @@
},
"faker": {
"hashes": [
"sha256:1d3f700e8dfcefd6e657118d71405d53e86974448aba78884f119bbd84c0cddf",
"sha256:d5366e120191c5610fceeebfe1c298dc46da0277096f639c6dd7e2eaee0fa547"
"sha256:45cc9cca3de8beba5a2da3bd82a6e5544f53da1a702645c8485f682366c15026",
"sha256:a6459ff518d1fc6ee2238a7209e6c899517872c7e1115510279033ffe6fe8ef3"
],
"version": "==2.0.1"
"version": "==2.0.2"
},
"flask": {
"hashes": [
@ -732,10 +741,10 @@
},
"importlib-metadata": {
"hashes": [
"sha256:23d3d873e008a513952355379d93cbcab874c58f4f034ff657c7a87422fa64e8",
"sha256:80d2de76188eabfbfcf27e6a37342c2827801e59c4cc14b0371c56fed43820e3"
"sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26",
"sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af"
],
"version": "==0.19"
"version": "==0.23"
},
"ipdb": {
"hashes": [
@ -746,11 +755,11 @@
},
"ipython": {
"hashes": [
"sha256:1d3a1692921e932751bc1a1f7bb96dc38671eeefdc66ed33ee4cbc57e92a410e",
"sha256:537cd0176ff6abd06ef3e23f2d0c4c2c8a4d9277b7451544c6cbf56d1c79a83d"
"sha256:c4ab005921641e40a68e405e286e7a1fcc464497e14d81b6914b4fd95e5dee9b",
"sha256:dd76831f065f17bddd7eaa5c781f5ea32de5ef217592cf019e34043b56895aa1"
],
"index": "pypi",
"version": "==7.7.0"
"version": "==7.8.0"
},
"ipython-genutils": {
"hashes": [
@ -872,10 +881,10 @@
},
"pbr": {
"hashes": [
"sha256:56e52299170b9492513c64be44736d27a512fa7e606f21942160b68ce510b4bc",
"sha256:9b321c204a88d8ab5082699469f52cc94c5da45c51f114113d01b3d993c24cdf"
"sha256:2c8e420cd4ed4cec4e7999ee47409e876af575d4c35a45840d59e8b5f3155ab8",
"sha256:b32c8ccaac7b1a20c0ce00ce317642e6cf231cf038f9875e0280e28af5bf7ac9"
],
"version": "==5.4.2"
"version": "==5.4.3"
},
"pexpect": {
"hashes": [
@ -894,10 +903,10 @@
},
"pluggy": {
"hashes": [
"sha256:0825a152ac059776623854c1543d65a4ad408eb3d33ee114dff91e57ec6ae6fc",
"sha256:b9817417e95936bf75d85d3f8767f7df6cdde751fc40aed3bb3074cbcb77757c"
"sha256:0db4b7601aae1d35b4a033282da476845aa19185c1e6964b25cf324b5e4ec3e6",
"sha256:fa5fa1622fa6dd5c030e9cad086fa19ef6a0cf6d7a2d12318e10cb49d6d68f34"
],
"version": "==0.12.0"
"version": "==0.13.0"
},
"prompt-toolkit": {
"hashes": [
@ -1039,19 +1048,26 @@
],
"version": "==2.0.5"
},
"soupsieve": {
"hashes": [
"sha256:8662843366b8d8779dec4e2f921bebec9afd856a5ff2e82cd419acc5054a1a92",
"sha256:a5a6166b4767725fd52ae55fee8c8b6137d9a51e9f1edea461a062a759160118"
],
"version": "==1.9.3"
},
"stevedore": {
"hashes": [
"sha256:7be098ff53d87f23d798a7ce7ae5c31f094f3deb92ba18059b1aeb1ca9fec0a0",
"sha256:7d1ce610a87d26f53c087da61f06f9b7f7e552efad2a7f6d2322632b5f932ea2"
"sha256:01d9f4beecf0fbd070ddb18e5efb10567801ba7ef3ddab0074f54e3cd4e91730",
"sha256:e0739f9739a681c7a1fda76a102b65295e96a144ccdb552f2ae03c5f0abe8a14"
],
"version": "==1.30.1"
"version": "==1.31.0"
},
"text-unidecode": {
"hashes": [
"sha256:5a1375bb2ba7968740508ae38d92e1f889a0832913cb1c447d5e2046061a396d",
"sha256:801e38bd550b943563660a91de8d4b6fa5df60a542be9093f7abf819f86050cc"
"sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8",
"sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"
],
"version": "==1.2"
"version": "==1.3"
},
"toml": {
"hashes": [
@ -1090,11 +1106,11 @@
},
"urllib3": {
"hashes": [
"sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1",
"sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232"
"sha256:2f3eadfea5d92bc7899e75b5968410b749a054b492d5a6379c1344a1481bc2cb",
"sha256:9c6c593cb28f52075016307fc26b0a0f8e82bc7d1ff19aaaa959b91710a56c47"
],
"markers": "python_version >= '3.4'",
"version": "==1.25.3"
"version": "==1.25.5"
},
"watchdog": {
"hashes": [
@ -1111,11 +1127,11 @@
},
"werkzeug": {
"hashes": [
"sha256:87ae4e5b5366da2347eb3116c0e6c681a0e939a33b2805e2c0cbd282664932c4",
"sha256:a13b74dd3c45f758d4ebdb224be8f1ab8ef58b3c0ffc1783a8c7d9f4f50227e6"
"sha256:7280924747b5733b246fe23972186c6b348f9ae29724135a6dfc1e53cea433e7",
"sha256:e5f4a1f98b52b18a93da705a7458e55afb26f32bff83ff5d19189f92462d65c4"
],
"index": "pypi",
"version": "==0.15.5"
"version": "==0.16.0"
},
"wrapt": {
"hashes": [

View File

@ -172,7 +172,7 @@ export default {
},
_emitChange: function(name, value, valid) {
emitEvent('field-change', this, { value, name })
emitEvent('field-change', this, { value, name, valid })
},
},

View File

@ -0,0 +1,67 @@
import { mount } from '@vue/test-utils'
import toForm from '../to_form'
import clinFields from '../../clin_fields'
import { makeTestWrapper } from '../../../test_utils/component_test_helpers'
const TOFormWrapper = makeTestWrapper({
components: { toForm },
templatePath: 'to_form.html',
})
describe('TOForm Test', () => {
it('should allow users to add new CLINs', () => {
const wrapper = mount(TOFormWrapper, {
propsData: {
initialData: {},
},
})
expect(wrapper.findAll(clinFields).length).toBe(1)
wrapper.find('#add-clin').trigger('click')
expect(wrapper.findAll(clinFields).length).toBe(2)
})
it('should not enable the save button until the form is complete and valid', () => {
const wrapper = mount(TOFormWrapper, {
propsData: {
initialData: {},
},
})
const submit = wrapper.find('input[type=submit]')
function expectSubmitIsDisabled() {
expect(submit.attributes('disabled')).toEqual('disabled')
}
expectSubmitIsDisabled()
// begin filling in the form; check at every submit button is disabled
wrapper.find('input#clins-0-number').setValue('0001')
expectSubmitIsDisabled()
wrapper.find('input#clins-0-obligated_amount').setValue('50000')
expectSubmitIsDisabled()
wrapper.find('input#clins-0-total_amount').setValue('60000')
expectSubmitIsDisabled()
wrapper.findAll('input[name="date-month"]').setValue('12')
expectSubmitIsDisabled()
wrapper.findAll('input[name="date-day"]').setValue('01')
expectSubmitIsDisabled()
wrapper
.findAll('input[name="date-year"]')
.at(0)
.setValue('2020')
expectSubmitIsDisabled()
wrapper
.findAll('input[name="date-year"]')
.at(1)
.setValue('2021')
expectSubmitIsDisabled()
// need to trigger the change function on the hidden date inputs so that
// the corresponding event fires to notify the parent form that it is valid
wrapper.find('input[name="clins-0-start_date"]').trigger('change')
wrapper.find('input[name="clins-0-end_date"]').trigger('change')
// check save button is enabled
expect(submit.attributes('disabled')).toBeUndefined()
})
})

View File

@ -3,9 +3,10 @@
inline-template
key='datafield'
v-bind:initial-checked='initialvalue'
v-bind:optional=false
>
<div>
<div class='usa-input '>
<div class='usa-input ' v-bind:class="[{ 'checked': isChecked }]">
<fieldset data-ally-disabled="true" v-on:change="onInput" class="usa-input__choices ">
<legend>
@ -16,6 +17,5 @@
</legend>
</fieldset>
</div>
</div>
</checkboxinput>

View File

@ -0,0 +1,341 @@
<to-form inline-template="">
<form action="/task_orders//form/step_3" autocomplete="off" enctype="multipart/form-data" id="to_form" method="POST">
<div class="sticky-cta" v-sticky='{ "stickyBitStickyOffset": 76 }'>
<div class="sticky-cta-container">
<div class="sticky-cta-text">
<h3>Add Task Order (step 3 of 5)</h3>
</div>
<div class="sticky-cta-buttons">
<span class="action-group">
<input :disabled="!canSave" class="usa-button usa-button-primary" form="to_form" tabindex="0" type="submit" value="Next: Review Task Order"/>
<a class="usa-button usa-button-secondary" href="/task_orders//form/step_2">
Previous
</a>
<a class="action-group__action icon-link" v-on:click="openModal('cancel')">
Cancel
</a>
</span>
</div>
</div>
</div>
<div v-cloak="" v-show="$root.activeModal === 'cancel'">
<div class="modal modal--dismissable" id="modal--cancel">
<div class="modal__container">
<div aria-modal="true" class="modal__dialog" role="dialog">
<div class="modal__body">
<div class="task-order__modal-cancel">
<h1>Do you want to save this draft?</h1>
<div class="task-order__modal-cancel_buttons">
<button class="usa-button usa-button-primary" formaction="/portfolios/e4edf994-04f4-4aaa-ba30-39507e1068a8/task_orders/form/cancel?save=False" type="submit">No, delete it</button>
<button class="usa-button usa-button-primary" formaction="/portfolios/e4edf994-04f4-4aaa-ba30-39507e1068a8/task_orders/form/cancel?save=True" type="submit">Yes, save for later</button>
</div>
</div>
<button class="icon-link modal__dismiss" type="button" v-on:click='closeModal("cancel")'>
<span aria-hidden="true" class="icon icon--x"><svg viewbox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M2 2l12 12M14 15c-.256 0-.512-.098-.707-.293l-12-12c-.391-.391-.391-1.023 0-1.414s1.023-.391 1.414 0l12 12c.391.391.391 1.023 0 1.414-.195.195-.451.293-.707.293zm0-13L2 14"></path><path d="M2 15c-.256 0-.512-.098-.707-.293-.391-.391-.391-1.023 0-1.414l12-12c.391-.391 1.023-.391 1.414 0s.391 1.023 0 1.414l-12 12C2.512 14.902 2.256 15 2 15z"></path></svg>
</span>
<span>
Close
</span>
</button>
</div>
</div>
</div>
</div>
</div>
<div class="task-order">
<div>
<div class="task-order__header">
<div class="h2">
Enter Contract Line Items
</div>
<p>
<strong>Task Order Number:</strong> 1234567890123
</p>
<p>
Refer to your task order to locate your Contract Line Item Numbers (CLINs).
</p>
</div>
<div v-for="clin in clins">
<clin-fields inline-template="" v-bind:contract-end="'2022-09-14'" v-bind:contract-start="'2019-09-14'" v-bind:initial-clin-index="clinIndex" v-bind:initial-clin-type="'JEDI_CLIN_1'">
<div class="clin-card" v-if="showClin">
<div class="card__title">
<span class="h4" v-html="clinTitle"></span>
<button class="icon-link icon-link__remove-clin" type="button" v-if="$parent.clinIndex &gt; 0" v-on:click="openModal(removeModalId)">
<span aria-hidden="true" class="icon icon--x"><svg viewbox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M2 2l12 12M14 15c-.256 0-.512-.098-.707-.293l-12-12c-.391-.391-.391-1.023 0-1.414s1.023-.391 1.414 0l12 12c.391.391.391 1.023 0 1.414-.195.195-.451.293-.707.293zm0-13L2 14"></path><path d="M2 15c-.256 0-.512-.098-.707-.293-.391-.391-.391-1.023 0-1.414l12-12c.391-.391 1.023-.391 1.414 0s.391 1.023 0 1.414l-12 12C2.512 14.902 2.256 15 2 15z"></path></svg>
</span>
</button>
</div>
<div class="card">
<div class="form-row">
<div class="h4 clin-card__title">
CLIN Details
</div>
</div>
<div class="form-row">
<div class="form-col">
<textinput :name="'clins-' + clinIndex + '-number'" :watch="true" inline-template="">
<div v-bind:class="['usa-input usa-input--validation--' + validation, { 'usa-input--error': showError, 'usa-input--success': showValid, 'usa-input--validation--paragraph': paragraph, 'no-max-width': noMaxWidth }]">
<label :for="name">
<span v-show="showError">
<span aria-hidden="true" class="icon icon--alert icon-validation"><svg fill="#fdb81e" viewbox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path d="M8 16c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8zM8 2C4.691 2 2 4.691 2 8s2.691 6 6 6 6-2.691 6-6-2.691-6-6-6zm0 8c-.552 0-1-.447-1-1V4c0-.552.448-1 1-1s1 .448 1 1v5c0 .553-.448 1-1 1zm0 3c-.26 0-.52-.11-.71-.29-.18-.19-.29-.45-.29-.71 0-.271.11-.521.29-.71.38-.37 1.05-.37 1.42 0 .18.189.29.45.29.71s-.11.52-.29.71c-.19.18-.45.29-.71.29z"></path>
</svg>
</span>
</span>
<span v-show="showValid">
<span aria-hidden="true" class="icon icon--ok icon-validation"><svg aria-hidden="true" class="svg-inline--fa fa-check-circle fa-w-16" data-icon="check-circle" data-prefix="fas" focusable="false" role="img" viewbox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z" fill="currentColor"></path></svg></span>
</span>
<div class="usa-input__title">CLIN</div>
</label>
<masked-input :id="name" ref="input" type="text" v-bind:aria-invalid="showError" v-bind:keep-char-positions="keepCharPositions" v-bind:mask="mask" v-bind:pipe="pipe" v-bind:value="value" v-on:blur="onBlur" v-on:change="onChange" v-on:input="onInput">
</masked-input>
<input :name="name" type="hidden" v-bind:value="rawValue">
<template v-if="showError">
<span class="usa-input__message" v-html="validationError"></span>
</template>
<template v-else="">
<span class="usa-input__message"></span>
</template>
</input></div>
</textinput>
</div>
</div>
<div class="form-row">
<div class="form-col">
<optionsinput :name="'clins-' + clinIndex + '-jedi_clin_type'" :optional="false" :watch="true" inline-template="">
<div v-bind:class="['usa-input', { 'usa-input--error': showError, 'usa-input--success': showValid }]">
<fieldset class="usa-input__choices" data-ally-disabled="true" v-on:change="onInput">
<legend>
<div class="usa-input__title">
Corresponding IDIQ CLIN
</div>
</legend>
<select :id="name" :name="name">
<option value="JEDI_CLIN_1">IaaS/PaaS (IDIQ CLIN 0001)</option>
<option value="JEDI_CLIN_2">IDIQ CLIN 0002</option>
<option value="JEDI_CLIN_3">IDIQ CLIN 0003</option>
<option value="JEDI_CLIN_4">IDIQ CLIN 0004</option>
</select>
</fieldset>
</div>
</optionsinput>
</div>
</div>
<hr/>
<div class="form-row">
<div class="h4 clin-card__title">
CLIN Funding
</div>
</div>
<div class="form-row">
<div class="form-col">
<clindollaramount :funding-valid="true" :key="'clins-' + clinIndex + '-' + 'total' + '_amount'" :name="'clins-' + clinIndex + '-' + 'total' + '_amount'" :watch="true" inline-template="" v-cloak="" validation="clinDollars">
<div v-bind:class="['usa-input usa-input--validation--dollars', { 'usa-input--error': showFundingError, 'usa-input--success': showFundingValid}]">
<label :for="name">
<div class="usa-input__title">Total CLIN Value</div>
<span v-show="showFundingError">
<span aria-hidden="true" class="icon icon--alert icon-validation"><svg fill="#fdb81e" viewbox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path d="M8 16c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8zM8 2C4.691 2 2 4.691 2 8s2.691 6 6 6 6-2.691 6-6-2.691-6-6-6zm0 8c-.552 0-1-.447-1-1V4c0-.552.448-1 1-1s1 .448 1 1v5c0 .553-.448 1-1 1zm0 3c-.26 0-.52-.11-.71-.29-.18-.19-.29-.45-.29-.71 0-.271.11-.521.29-.71.38-.37 1.05-.37 1.42 0 .18.189.29.45.29.71s-.11.52-.29.71c-.19.18-.45.29-.71.29z"></path>
</svg>
</span>
</span>
<span v-show="showFundingValid">
<span aria-hidden="true" class="icon icon--ok icon-validation"><svg aria-hidden="true" class="svg-inline--fa fa-check-circle fa-w-16" data-icon="check-circle" data-prefix="fas" focusable="false" role="img" viewbox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z" fill="currentColor"></path></svg></span>
</span>
</label>
<masked-input :id="name" ref="input" type="text" v-bind:aria-invalid="showError" v-bind:keep-char-positions="keepCharPositions" v-bind:mask="mask" v-bind:pipe="pipe" v-bind:value="value" v-on:blur="onBlur" v-on:change="onChange" v-on:input="onInput">
</masked-input>
<input :name="name" type="hidden" v-bind:value="rawValue"/>
<template v-if="!fundingValid">
<span class="usa-input__message">Obligated amount must be less than or equal to total amount</span>
</template>
<template v-else-if="showError">
<span class="usa-input__message" v-html="validationError"></span>
</template>
<template v-else="">
<span class="usa-input__message"></span>
</template>
</div>
</clindollaramount>
</div>
</div>
<div class="form-row">
<div class="form-col">
<clindollaramount :funding-valid="fundingValid" :key="'clins-' + clinIndex + '-' + 'obligated' + '_amount'" :name="'clins-' + clinIndex + '-' + 'obligated' + '_amount'" :watch="true" inline-template="" v-cloak="" validation="clinDollars">
<div v-bind:class="['usa-input usa-input--validation--dollars', { 'usa-input--error': showFundingError, 'usa-input--success': showFundingValid}]">
<label :for="name">
<div class="usa-input__title">Obligated Funds</div>
<span v-show="showFundingError">
<span aria-hidden="true" class="icon icon--alert icon-validation"><svg fill="#fdb81e" viewbox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path d="M8 16c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8zM8 2C4.691 2 2 4.691 2 8s2.691 6 6 6 6-2.691 6-6-2.691-6-6-6zm0 8c-.552 0-1-.447-1-1V4c0-.552.448-1 1-1s1 .448 1 1v5c0 .553-.448 1-1 1zm0 3c-.26 0-.52-.11-.71-.29-.18-.19-.29-.45-.29-.71 0-.271.11-.521.29-.71.38-.37 1.05-.37 1.42 0 .18.189.29.45.29.71s-.11.52-.29.71c-.19.18-.45.29-.71.29z"></path>
</svg>
</span>
</span>
<span v-show="showFundingValid">
<span aria-hidden="true" class="icon icon--ok icon-validation"><svg aria-hidden="true" class="svg-inline--fa fa-check-circle fa-w-16" data-icon="check-circle" data-prefix="fas" focusable="false" role="img" viewbox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z" fill="currentColor"></path></svg></span>
</span>
</label>
<masked-input :id="name" ref="input" type="text" v-bind:aria-invalid="showError" v-bind:keep-char-positions="keepCharPositions" v-bind:mask="mask" v-bind:pipe="pipe" v-bind:value="value" v-on:blur="onBlur" v-on:change="onChange" v-on:input="onInput">
</masked-input>
<input :name="name" type="hidden" v-bind:value="rawValue"/>
<template v-if="!fundingValid">
<span class="usa-input__message">Obligated amount must be less than or equal to total amount</span>
</template>
<template v-else-if="showError">
<span class="usa-input__message" v-html="validationError"></span>
</template>
<template v-else="">
<span class="usa-input__message"></span>
</template>
</div>
</clindollaramount>
</div>
</div>
<div class="h5 clin-card__title">Percent Obligated</div>
<p v-html="percentObligated"></p>
<hr/>
<div class="form-row">
<div class="h4 clin-card__title">
Period of Performance
</div>
</div>
<div class="form-row">
<div class="form-col">
<date-selector :name-tag="'clins-' + clinIndex + '-start_date'" :optional="false" :watch="true" inline-template="">
<fieldset class="usa-input date-picker" v-bind:class="{ 'usa-input--success': isDateValid &amp;&amp; showValidation }">
<legend>
<div class="usa-input__title">
Start Date
</div>
<p class="usa-input__help">
For example: 07 04 1776
</p>
</legend>
<div class="date-picker-component">
<input :name="name" type="hidden" v-bind:value="formattedDate" v-on:change="onInput"/>
<div class="usa-form-group usa-form-group-month">
<label>Month</label>
<input max="12" maxlength="2" min="1" name="date-month" type="number" v-bind:class="{ 'usa-input-error': (month &amp;&amp; !isMonthValid) }" v-model="month" v-on:change="onInput"/>
</div>
<div class="usa-form-group usa-form-group-day">
<label>Day</label>
<input maxlength="2" min="1" name="date-day" type="number" v-bind:class="{ 'usa-input-error': (day &amp;&amp; !isDayValid) }" v-bind:max="daysMaxCalculation" v-model="day" v-on:change="onInput"/>
</div>
<div class="usa-form-group usa-form-group-year">
<label>Year</label>
<input maxlength="4" name="date-year" type="number" v-model="year" v-on:change="onInput"/>
</div>
<div class="usa-form-group-date-ok" v-if="isDateValid">
<span aria-hidden="true" class="icon icon--ok icon--green"><svg aria-hidden="true" class="svg-inline--fa fa-check-circle fa-w-16" data-icon="check-circle" data-prefix="fas" focusable="false" role="img" viewbox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z" fill="currentColor"></path></svg></span>
</div>
</div>
</fieldset>
</date-selector>
</div>
</div>
<div class="form-row">
<div class="form-col">
<date-selector :name-tag="'clins-' + clinIndex + '-end_date'" :optional="false" :watch="true" inline-template="">
<fieldset class="usa-input date-picker" v-bind:class="{ 'usa-input--success': isDateValid &amp;&amp; showValidation }">
<legend>
<div class="usa-input__title">
End Date
</div>
<div aria-live="polite" class="usa-alert usa-alert-info" role="alert">
<div class="usa-alert-body">
<p class="usa-alert-text">
A CLIN's period of performance must end before September 14, 2022.
</p>
</div>
</div>
<p class="usa-input__help">
For example: 07 04 1776
</p>
</legend>
<div class="date-picker-component">
<input :name="name" type="hidden" v-bind:value="formattedDate" v-on:change="onInput"/>
<div class="usa-form-group usa-form-group-month">
<label>Month</label>
<input max="12" maxlength="2" min="1" name="date-month" type="number" v-bind:class="{ 'usa-input-error': (month &amp;&amp; !isMonthValid) }" v-model="month" v-on:change="onInput"/>
</div>
<div class="usa-form-group usa-form-group-day">
<label>Day</label>
<input maxlength="2" min="1" name="date-day" type="number" v-bind:class="{ 'usa-input-error': (day &amp;&amp; !isDayValid) }" v-bind:max="daysMaxCalculation" v-model="day" v-on:change="onInput"/>
</div>
<div class="usa-form-group usa-form-group-year">
<label>Year</label>
<input maxlength="4" name="date-year" type="number" v-model="year" v-on:change="onInput"/>
</div>
<div class="usa-form-group-date-ok" v-if="isDateValid">
<span aria-hidden="true" class="icon icon--ok icon--green"><svg aria-hidden="true" class="svg-inline--fa fa-check-circle fa-w-16" data-icon="check-circle" data-prefix="fas" focusable="false" role="img" viewbox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z" fill="currentColor"></path></svg></span>
</div>
</div>
</fieldset>
</date-selector>
</div>
</div>
<div class="form-row">
<div class="usa-input-error-message form-has-errors">
<p :key="error" v-for="error in popErrors" v-html="error"></p>
</div>
</div>
</div>
<div v-cloak="" v-show="$root.activeModal === removeModalId">
<div :id='"modal--" + removeModalId' class="modal modal--dismissable">
<div class="modal__container">
<div aria-modal="true" class="modal__dialog" role="dialog">
<div class="modal__body">
<div class="task-order__modal-cancel">
<h1 v-html='"Do you want to remove " + clinTitle + "?"'></h1>
<div class="task-order__modal-cancel_buttons">
<button class="usa-button usa-button-primary" type="button" v-on:click="closeModal(removeModalId)">
No, go back
</button>
<button class="usa-button usa-button-primary" type="button" v-on:click="removeClin()">
Yes, remove CLIN
</button>
</div>
</div>
<button class="icon-link modal__dismiss" type="button" v-on:click="closeModal(removeModalId)">
<span aria-hidden="true" class="icon icon--x"><svg viewbox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M2 2l12 12M14 15c-.256 0-.512-.098-.707-.293l-12-12c-.391-.391-.391-1.023 0-1.414s1.023-.391 1.414 0l12 12c.391.391.391 1.023 0 1.414-.195.195-.451.293-.707.293zm0-13L2 14"></path><path d="M2 15c-.256 0-.512-.098-.707-.293-.391-.391-.391-1.023 0-1.414l12-12c.391-.391 1.023-.391 1.414 0s.391 1.023 0 1.414l-12 12C2.512 14.902 2.256 15 2 15z"></path></svg>
</span>
<span>
Close
</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</clin-fields>
</div>
<button class="icon-link icon-link__add-another-clin" id="add-clin" type="button" v-on:click="addClin">
<span aria-hidden="true" class="icon icon--plus"><?xml version="1.0" encoding="UTF-8"?>
<svg height="16px" version="1.1" viewbox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 55.2 (78181) - https://sketchapp.com -->
<title>UI element / Icons / Plus / White</title>
<desc>Created with Sketch.</desc>
<g fill="none" fill-rule="evenodd" id="Wizard_Services" stroke="none" stroke-width="1">
<g fill="#0071BC" id="c5-Add-Services" transform="translate(-285.000000, -1059.000000)">
<g id="Group-3" transform="translate(285.000000, 1035.000000)">
<g id="Group-2" transform="translate(0.000000, 21.000000)">
<g id="UI-element-/-Icons-/-Plus-/-White" transform="translate(0.000000, 3.000000)">
<g id="Group">
<path d="M8,16 C3.589,16 0,12.411 0,8 C0,3.589 3.589,0 8,0 C12.411,0 16,3.589 16,8 C16,12.411 12.411,16 8,16 Z M8,2 C4.691,2 2,4.691 2,8 C2,11.309 4.691,14 8,14 C11.309,14 14,11.309 14,8 C14,4.691 11.309,2 8,2 Z" id="Style"></path>
<path d="M8,12 C7.448,12 7,11.4891429 7,10.8571429 L7,5.14285714 C7,4.512 7.448,4 8,4 C8.552,4 9,4.512 9,5.14285714 L9,10.8571429 C9,11.4891429 8.552,12 8,12 Z" id="Path"></path>
<path d="M8,12 C7.448,12 7,11.4891429 7,10.8571429 L7,5.14285714 C7,4.512 7.448,4 8,4 C8.552,4 9,4.512 9,5.14285714 L9,10.8571429 C9,11.4891429 8.552,12 8,12 Z" id="Path" transform="translate(8.000000, 8.000000) rotate(-270.000000) translate(-8.000000, -8.000000) "></path>
</g>
</g>
</g>
</g>
</g>
</g>
</svg></span>
<span>Add another CLIN</span>
</button>
</div>
</div>
</form>
</to-form>

View File

@ -18,7 +18,9 @@
</div>
<div v-show="hasAttachment === false" v-bind:class='{ "usa-input": true, "usa-input--error": showErrors }'>
<p>
</p>
<div v-if="!hideInput" class="upload-widget">
<label class="upload-label" for="pdf">
<span class="upload-button">

View File

@ -19,7 +19,9 @@
</div>
<div v-show="hasAttachment === false" v-bind:class='{ "usa-input": true, "usa-input--error": showErrors }'>
<p>
</p>
<div v-if="!hideInput" class="upload-widget">
<label class="upload-label" for="pdf">
<span class="upload-button">

View File

@ -340,6 +340,7 @@
</div>
<button
id="add-clin"
class="icon-link icon-link__add-another-clin"
v-on:click="addClin"
type="button">

View File

@ -1,10 +1,18 @@
import pytest
from bs4 import BeautifulSoup
from wtforms.widgets import CheckboxInput
from wtforms.fields import StringField
from wtforms.validators import InputRequired, URL
from wtforms.validators import InputRequired
from wtforms import Form, FormField
from atst.forms.task_order import TaskOrderForm
from atst.models import Permissions
from atst.routes.task_orders.new import render_task_orders_edit
from atst.utils.context_processors import user_can_view
from tests import factories
class InitialValueForm(Form):
datafield = StringField(label="initialvalue value", default="initialvalue")
@ -82,3 +90,35 @@ def test_make_upload_input_error_template(upload_input_macro, task_order_form):
task_order_form.validate()
rendered_upload_macro = upload_input_macro(task_order_form.pdf)
write_template(rendered_upload_macro, "upload_input_error_template.html")
def test_make_task_order_step3_template(app, request_ctx):
request_ctx.g.current_user = factories.UserFactory.create()
request_ctx.g.application = None
request_ctx.g.portfolio = None
# hard-code the portfolio ID so it does not change the fragment every time
# this is run
portfolio = factories.PortfolioFactory.create(
id="e4edf994-04f4-4aaa-ba30-39507e1068a8"
)
# hard-code the TO number for the same reason
task_order = factories.TaskOrderFactory.create(
portfolio=portfolio, number="1234567890123"
)
task_order_form = TaskOrderForm(obj=task_order)
step3 = render_task_orders_edit(
"task_orders/step_3.html",
form=task_order_form,
portfolio_id=task_order.portfolio_id,
extra_args={
"portfolio": task_order.portfolio,
"permissions": Permissions,
"user_can": user_can_view,
"task_order": task_order,
"contract_start": app.config.get("CONTRACT_START_DATE"),
"contract_end": app.config.get("CONTRACT_END_DATE"),
},
)
dom = BeautifulSoup(step3, "html.parser")
to_form = dom.find("to-form")
write_template(str(to_form), "to_form.html")