From 6f509c133fe6c79466849803d6a1ea8918f98044 Mon Sep 17 00:00:00 2001 From: srh-sloan Date: Thu, 17 Oct 2024 13:49:30 +0100 Subject: [PATCH] Bugfix multi input field (#73) * WIP - adding support for multi-input-field * Adding children and schema to component definition * Add options to page --- .../~2024_10_16_1347-da30746cec39_.py | 40 ++ app/db/models/application_config.py | 4 + app/export_config/generate_form.py | 7 +- .../generate_fund_round_form_jsons.py | 6 +- app/import_config/load_form_json.py | 3 + tests/test_config_import.py | 25 +- tests/test_data/funding-required-cof-25.json | 631 ++++++++++++++++++ tests/test_data/multi-input-exported.json | 123 ++++ tests/test_data/multi_input.json | 283 ++++++++ tests/{ => test_data}/test-import-form.json | 0 tests/test_generate_form.py | 65 ++ tests/test_integration.py | 6 +- 12 files changed, 1185 insertions(+), 8 deletions(-) create mode 100644 app/db/migrations/versions/~2024_10_16_1347-da30746cec39_.py create mode 100644 tests/test_data/funding-required-cof-25.json create mode 100644 tests/test_data/multi-input-exported.json create mode 100644 tests/test_data/multi_input.json rename tests/{ => test_data}/test-import-form.json (100%) diff --git a/app/db/migrations/versions/~2024_10_16_1347-da30746cec39_.py b/app/db/migrations/versions/~2024_10_16_1347-da30746cec39_.py new file mode 100644 index 0000000..187a0db --- /dev/null +++ b/app/db/migrations/versions/~2024_10_16_1347-da30746cec39_.py @@ -0,0 +1,40 @@ +"""Add MultiInputField to ComponentType Enum and columns children and schema to Component. Add column options to Page + +Revision ID: da30746cec39 +Revises: ca61d3b746f6 +Create Date: 2024-10-16 13:47:16.968837 + +""" +from alembic import op +import sqlalchemy as sa + +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = 'da30746cec39' +down_revision = 'ca61d3b746f6' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + + op.execute("ALTER TYPE componenttype ADD VALUE 'MULTI_INPUT_FIELD';") + with op.batch_alter_table('component', schema=None) as batch_op: + batch_op.add_column(sa.Column('children', postgresql.JSON(none_as_null=True, astext_type=sa.Text()), nullable=True)) + batch_op.add_column(sa.Column('schema', postgresql.JSON(astext_type=sa.Text()), nullable=True)) + with op.batch_alter_table('page', schema=None) as batch_op: + batch_op.add_column(sa.Column('options', postgresql.JSON(none_as_null=True, astext_type=sa.Text()), nullable=True)) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('component', schema=None) as batch_op: + batch_op.drop_column('children') + batch_op.drop_column('schema') + with op.batch_alter_table('page', schema=None) as batch_op: + batch_op.drop_column('options') + # ### end Alembic commands ### diff --git a/app/db/models/application_config.py b/app/db/models/application_config.py index 83e8e51..bfea5ac 100644 --- a/app/db/models/application_config.py +++ b/app/db/models/application_config.py @@ -53,6 +53,7 @@ class ComponentType(Enum): FILE_UPLOAD_FIELD = "FileUploadField" MONTH_YEAR_FIELD = "MonthYearField" TIME_FIELD = "TimeField" + MULTI_INPUT_FIELD="MultiInputField" READ_ONLY_COMPONENTS = [ @@ -164,6 +165,7 @@ class Page(BaseModel): ) source_template_id = Column(UUID(as_uuid=True), nullable=True) controller = Column(String(), nullable=True) + options=Column(JSON(none_as_null=True)) def __repr__(self): return f"Page(/{self.display_path} - {self.name_in_apply_json['en']}, Components: {self.components})" @@ -220,10 +222,12 @@ class Component(BaseModel): content = Column(String(), nullable=True) hint_text = Column(String(), nullable=True) options = Column(JSON(none_as_null=False)) + schema = Column(JSON(none_as_null=False)) type = Column(ENUM(ComponentType)) template_name = Column(String(), nullable=True) is_template = Column(Boolean, default=False, nullable=False) audit_info = Column(JSON(none_as_null=True)) + children = Column(JSON(none_as_null=True)) # TODO model this as a proper hierarchy page_index = Column(Integer()) theme_index = Column(Integer()) conditions = Column(JSON(none_as_null=True)) diff --git a/app/export_config/generate_form.py b/app/export_config/generate_form.py index 31bc9d3..172c5b6 100644 --- a/app/export_config/generate_form.py +++ b/app/export_config/generate_form.py @@ -93,7 +93,7 @@ def build_component(component: Component) -> dict: "type": component.type.value, "title": component.title, "hint": component.hint_text or "", - "schema": {}, + "schema": component.schema or {}, "name": component.runner_component_name, "metadata": { # "fund_builder_id": str(component.component_id) TODO why do we need this? @@ -108,6 +108,9 @@ def build_component(component: Component) -> dict: built_component["metadata"].update({"fund_builder_list_id": str(component.list_id)}) built_component.update({"values": {"type": "listRef"}}) + if component.type is ComponentType.MULTI_INPUT_FIELD: + built_component.update({"children":component.children}) + return built_component @@ -124,6 +127,8 @@ def build_page(page: Page = None) -> dict: "title": page.name_in_apply_json["en"], } ) + if page.options: + built_page.update({"options": page.options}) # Having a 'null' controller element breaks the form-json, needs to not be there if blank if page.controller: built_page["controller"] = page.controller diff --git a/app/export_config/generate_fund_round_form_jsons.py b/app/export_config/generate_fund_round_form_jsons.py index 46368e6..dbac694 100644 --- a/app/export_config/generate_fund_round_form_jsons.py +++ b/app/export_config/generate_fund_round_form_jsons.py @@ -21,6 +21,7 @@ "properties": { "path": {"type": "string"}, "title": {"type": "string"}, + "options":{"type":"object"}, "components": { "type": "array", "items": { @@ -34,11 +35,14 @@ "title": {"type": ["string", "null"]}, "content": {"type": ["string", "null"]}, "hint": {"type": "string"}, - "schema": {"type": "object"}, + "schema": { + "type": "object", + }, "name": {"type": "string"}, "metadata": { "type": "object", }, + "children": {"type": "array"} }, }, }, diff --git a/app/import_config/load_form_json.py b/app/import_config/load_form_json.py index 1753cae..5136c76 100644 --- a/app/import_config/load_form_json.py +++ b/app/import_config/load_form_json.py @@ -129,6 +129,8 @@ def insert_component_as_template(component, page_id, page_index, lizts): # theme_index=component.get('theme_index', None), TODO: add theme_index to json runner_component_name=component.get("name", None), list_id=list_id, + children=component.get("children", None), + schema=component.get("schema", None) ) try: db.session.add(new_component) @@ -147,6 +149,7 @@ def insert_page_as_template(page, form_id): controller=page.get("controller", None), is_template=True, template_name=page.get("title", None), + options = page.get("options", None), ) try: db.session.add(new_page) diff --git a/tests/test_config_import.py b/tests/test_config_import.py index e27bdef..f6a5255 100644 --- a/tests/test_config_import.py +++ b/tests/test_config_import.py @@ -1,11 +1,13 @@ import json import os +from pathlib import Path import pytest from app.db.models import Component from app.db.models import Form from app.db.models import Page +from app.db.models.application_config import ComponentType from app.import_config.load_form_json import load_form_jsons from app.import_config.load_form_json import load_json_from_file @@ -49,8 +51,7 @@ def test_generate_config_for_round_valid_input(seed_dynamic_data, _db, filename) def test_generate_config_for_round_valid_input_file(seed_dynamic_data, _db): filename = "test-import-form.json" template_name = "test-template" - script_dir = os.path.dirname(__file__) - file_path = os.path.join(script_dir, filename) + file_path = Path("tests") / "test_data" / filename with open(file_path, "r") as json_file: form = json.load(json_file) form["filename"] = filename @@ -70,3 +71,23 @@ def test_generate_config_for_round_valid_input_file(seed_dynamic_data, _db): _db.session.query(Component).filter(Component.page_id == page.page_id).count() for page in pages ) assert total_components_count == expected_component_count_for_form + + +def test_import_multi_input_field(seed_dynamic_data, _db): + with open(Path("tests") / "test_data" / "multi_input.json", "r") as json_file: + form = json.load(json_file) + form["filename"] = "test_mult_input" + + load_json_from_file(form, template_name="test_input_multi_input") + forms = _db.session.query(Form).filter(Form.template_name == "test_input_multi_input") + assert forms.count() == 1 + pages = _db.session.query(Page).filter(Page.form_id == forms.first().form_id) + assert pages.count() == 3 + page_with_multi_input = next(p for p in pages if p.display_path=='capital-costs-for-your-project') + assert page_with_multi_input + assert page_with_multi_input.options + multi_input_component = next(c for c in page_with_multi_input.components if c.title=='Capital costs') + assert multi_input_component + assert multi_input_component.type == ComponentType.MULTI_INPUT_FIELD + assert len(multi_input_component.children) == 4 + diff --git a/tests/test_data/funding-required-cof-25.json b/tests/test_data/funding-required-cof-25.json new file mode 100644 index 0000000..a0bd7b9 --- /dev/null +++ b/tests/test_data/funding-required-cof-25.json @@ -0,0 +1,631 @@ +{ + "metadata": {}, + "startPage": "/funding-required", + + "pages": [ + { + "title": "Funding required", + "path": "/funding-required", + "components": [ + { + "name": "RgwAoN", + "options": {}, + "type": "Para", + "content": "\n\n" + } + ], + "next": [ + { + "path": "/capital-funding-request" + } + ], + "controller": "./pages/start.js" + }, + { + "path": "/capital-funding-request", + "title": "Capital funding request", + "components": [ + { + "name": "ngpQfE", + "options": {}, + "type": "Details", + "title": "Help with management case", + "content": "We are looking to understand:\n
\n
\n\n

We'll ask you to upload a business plan to support the answers you give us in this section.

\n
" + }, + { + "name": "iPBHAo", + "options": {}, + "type": "Html", + "content": "

Capital costs are the costs of buying or leasing your asset and paying for refurbishment.

\n\n

Community Ownership Fund 2025 (COF25) will fund up to 80% of the total capital costs of your project, up to a maximum of £2 million.

\n\n

At least 20% of the total capital costs of your project must be covered by other sources (match funding). For example:

\n\n\n\n

(In exceptional circumstances, you may only need to secure 10% in match funding. Your development support provider will have already confirmed this with you if you’re eligible.)

\n\n

If successful, you must spend all the funding within 12 months.

" + }, + { + "name": "ABROnB", + "options": { + "prefix": "£", + "classes": "govuk-!-width-one-third", + "hideTitle": true + }, + "type": "NumberField", + "title": "Capital funding request", + "hint": "" + }, + { + "name": "hJkmBS", + "options": {}, + "type": "YesNoField", + "title": "If successful, will you use your funding in the next 12 months?" + } + ], + "next": [ + { + "path": "/capital-costs-for-your-project" + } + ], + "section": "bgUGuD" + }, + { + "title": "Check your answers", + "path": "/summary", + "controller": "./pages/summary.js", + "components": [], + "next": [], + "section": "bgUGuD" + }, + { + "path": "/capital-costs-for-your-project", + "title": "Capital costs for your project", + "controller": "RepeatingFieldPageController", + "options": { + "summaryDisplayMode": { + "samePage": true, + "separatePage": false, + "hideRowTitles": false + }, + "customText": { + "samePageTitle": "Your capital costs" + } + }, + "components": [ + { + "name": "GaAAPb", + "options": {}, + "type": "Details", + "content": "We are looking to understand:\n
\n
\n\n

We'll ask you to upload a business plan to support the answers you give us in this section.

\n
", + "title": "Help with management case" + }, + { + "name": "EaxCoo", + "options": {}, + "type": "Para", + "content": "

Capital costs

\n\n

Tell us about the total capital costs of your project.

\n\n

This should be for the whole project, not just what you're requesting from the fund.

\n\n

Capital costs can be used to:\n

\n

\n

Remember, you can apply for up to 80% (or 90% if the development support provider has confirmed you're eligible to do this) of your capital costs, up to a maximum of £2 million.

\n

You can use your business plan to provide information that supports your answers.

\n\n\n\n" + }, + { + "name": "qQLyXL", + "options": { + "prefix": "£", + "columnTitles": [ + "Description", + "Amount", + "Money from COF25 grant", + "Match funding amount", + "Action" + ], + "required": true + }, + "type": "MultiInputField", + "title": "Capital costs", + "hint": "The MultiInputField needed", + "schema": {}, + "children": [ + { + "name": "GLQlOh", + "options": {}, + "type": "TextField", + "title": "Describe the cost" + }, + { + "name": "JtwkMy", + "options": { + "prefix": "£", + "classes": "govuk-!-width-one-half" + }, + "type": "NumberField", + "title": "Amount", + "hint": "", + "schema": {} + }, + { + "name": "LeTLDo", + "options": { + "prefix": "£", + "classes": "govuk-!-width-one-half" + }, + "type": "NumberField", + "title": "How much money from the COF25 grant will you use to pay for this cost?", + "hint": "", + "schema": {} + }, + { + "name": "pHZDWT", + "options": { + "prefix": "£", + "classes": "govuk-!-width-one-half" + }, + "type": "NumberField", + "title": "How much of the match funding will you use to pay for this cost?", + "hint": "", + "schema": {} + } + ] + } + ], + "next": [ + { + "path": "/if-youve-secured-match-funding" + } + ], + "section": "bgUGuD" + }, + { + "path": "/if-youve-secured-match-funding", + "title": "If you've secured match funding", + "components": [ + { + "name": "LVKBMP", + "options": {}, + "type": "Para", + "content": "

At least 20% of your total capital costs must be covered by your organisation or another funder (match funding).

\n\n

Match funding sources could include:\n

\n

\n

You do not need to have secured all of your match funding yet, but you should have identified potential funders.

\n\n" + }, + { + "name": "DOvZvB", + "options": {}, + "type": "YesNoField", + "title": "Have you secured any match funding yet?" + } + ], + "next": [ + { + "path": "/secured-match-funding", + "condition": "QyNLbn" + }, + { + "path": "/unsecured-match-funding", + "condition": "nzrkpu" + } + ], + "section": "bgUGuD" + }, + { + "path": "/secured-match-funding", + "title": "Secured match funding", + "controller": "RepeatingFieldPageController", + "options": { + "summaryDisplayMode": { + "samePage": true, + "separatePage": false, + "hideRowTitles": false + }, + "customText": { + "samePageTitle": "Your secured match funding" + } + }, + "components": [ + { + "name": "LZUkqw", + "options": {}, + "type": "Para", + "content": "

Secured match funding

\n\n

Tell us which sources of funding you have already secured as match funding.

\n\n

Match funding sources can include:\n

\n

" + }, + { + "name": "MopCmv", + "options": { + "prefix": "£", + "columnTitles": ["Source", "Amount", "Action"], + "required": true + }, + "type": "MultiInputField", + "title": "Secured match funding", + "hint": "The MultiInputField needed", + "schema": {}, + "children": [ + { + "name": "JKqLWU", + "options": {}, + "type": "TextField", + "title": "Source of secured funding" + }, + { + "name": "LVJcDC", + "options": { + "prefix": "£", + "classes": "govuk-!-width-one-half" + }, + "type": "NumberField", + "title": "Amount", + "hint": "", + "schema": {} + } + ] + } + ], + "next": [ + { + "path": "/have-you-already-spent-the-match-funding-you-have-secured" + } + ], + "section": "bgUGuD" + }, + { + "path": "/if-youve-identified-further-match-funding", + "title": "If you’ve identified further match funding", + "components": [ + { + "name": "DmgsiG", + "options": {}, + "type": "YesNoField", + "title": "Have you identified, but not yet secured, any additional match funding?" + } + ], + "next": [ + { + "path": "/revenue-funding", + "condition": "nPKnjf" + }, + { + "path": "/unsecured-match-funding", + "condition": "DrELrt" + } + ], + "section": "bgUGuD" + }, + { + "path": "/unsecured-match-funding", + "title": "Unsecured match funding", + "controller": "RepeatingFieldPageController", + "options": { + "summaryDisplayMode": { + "samePage": true, + "separatePage": false, + "hideRowTitles": false + }, + "customText": { + "samePageTitle": "Your unsecured match funding" + } + }, + "components": [ + { + "name": "fixtAm", + "options": {}, + "type": "Para", + "content": "\n\n

Tell us which sources of funding you intend to secure as match funding.

\n

You can use your business plan to:

\n" + }, + { + "name": "vEOdBS", + "options": { + "prefix": "£", + "columnTitles": ["Source", "Amount", "Action"], + "required": true + }, + "type": "MultiInputField", + "title": "Unsecured match funding", + "hint": "The MultiInputField needed", + "schema": {}, + "children": [ + { + "name": "iMJdfs", + "options": {}, + "type": "TextField", + "title": "Source of unsecured match funding" + }, + { + "name": "THOdae", + "options": { + "prefix": "£", + "classes": "govuk-!-width-one-half" + }, + "type": "NumberField", + "title": "Amount", + "hint": "", + "schema": {} + } + ] + } + ], + "next": [ + { + "path": "/revenue-funding" + } + ], + "section": "bgUGuD" + }, + { + "path": "/revenue-funding", + "title": "Revenue funding", + "components": [ + { + "name": "matkNH", + "options": {}, + "type": "YesNoField", + "title": "Are you applying for revenue funding from the Community Ownership Fund 2025? (optional)", + "hint": "Revenue costs go towards running the asset once you've taken ownership of it. This might include general costs, including for:\n\n\n

Remember, you can apply for up to fifty thousand pounds (£50,000) of your revenue costs (no more than 20% of your capital funding).

\n

You can use your business plan to provide any information that supports your answers

", + "values": { + "type": "listRef" + } + } + ], + "next": [ + { + "path": "/revenue-costs-optional", + "condition": "iegbPT" + }, + { + "path": "/summary", + "condition": "zVdAqG" + } + ], + "section": "bgUGuD" + }, + { + "path": "/revenue-costs-optional", + "title": "Revenue costs (optional)", + "controller": "RepeatingFieldPageController", + "options": { + "summaryDisplayMode": { + "samePage": true, + "separatePage": false, + "hideRowTitles": false + }, + "customText": { + "samePageTitle": "Your revenue costs" + } + }, + "components": [ + { + "name": "FdWhuX", + "options": {}, + "type": "Details", + "title": "Help with management case", + "content": "We are looking to understand:\n
\n
\n\n

We'll ask you to upload a business plan to support the answers you give us in this section.

\n\n

This fund can offer up to 50% of your capital costs to renovate and repair the asset.

\n
" + }, + { + "name": "XSwCIQ", + "options": {}, + "type": "Para", + "content": "\n\n

Tell us about the total revenue costs of your project.

\n

These are the running costs of the project. This might include general costs, including for:

\n

\n

Remember, you can apply for up to £50,000 of your revenue costs (no more than 20% of your capital funding).\n\nYou can use your business plan to:

\n" + }, + { + "name": "tSKhQQ", + "options": { + "prefix": "£", + "columnTitles": ["Description", "Amount", "Action"], + "required": true + }, + "type": "MultiInputField", + "title": "Revenue costs (optional)", + "hint": "The MultiInputField needed", + "schema": {}, + "children": [ + { + "name": "hGsUaZ", + "options": {}, + "type": "TextField", + "title": "Describe the cost" + }, + { + "name": "UyaAHw", + "options": { + "prefix": "£", + "classes": "govuk-!-width-one-half" + }, + "type": "NumberField", + "title": "Amount", + "hint": "", + "schema": {} + } + ] + } + ], + "next": [ + { + "path": "/how-youll-use-revenue-funding" + } + ], + "section": "bgUGuD" + }, + { + "path": "/have-you-already-spent-the-match-funding-you-have-secured", + "title": "Have you already spent the match funding you have secured?", + "components": [ + { + "name": "HgpNUe", + "options": { + "hideTitle": true + }, + "type": "YesNoField", + "hint": "Note that any funding that you have already spent will not be eligible as match funding", + "title": "Have you already spent the match funding you have secured?" + } + ], + "next": [ + { + "path": "/if-youve-identified-further-match-funding" + } + ], + "section": "bgUGuD" + }, + { + "path": "/how-youll-use-revenue-funding", + "title": "How you'll use revenue funding", + "components": [ + { + "name": "XPDbsl", + "options": { + "maxWords": "500" + }, + "type": "FreeTextField", + "title": "Tell us how the revenue funding you've requested will help run the asset", + "hint": "Include the specific needs it will meet, and how you'll meet these needs once you've spent the funding." + } + ], + "next": [ + { + "path": "/summary" + } + ], + "section": "bgUGuD" + } + ], + "lists": [], + "sections": [ + { + "name": "bgUGuD", + "title": "Funding required" + } + ], + "conditions": [ + { + "displayName": "Have you secured any match funding yet-yes", + "name": "QyNLbn", + "value": { + "name": "Have you secured any match funding yet-yes", + "conditions": [ + { + "field": { + "name": "DOvZvB", + "type": "YesNoField", + "display": "Have you secured any match funding yet?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "true", + "display": "true" + } + } + ] + } + }, + { + "displayName": "Have you secured any match funding yet-no", + "name": "nzrkpu", + "value": { + "name": "Have you secured any match funding yet-no", + "conditions": [ + { + "field": { + "name": "DOvZvB", + "type": "YesNoField", + "display": "Have you secured any match funding yet?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "false", + "display": "false" + } + } + ] + } + }, + { + "displayName": "Do you have any match funding identified but not yet secured-yes", + "name": "DrELrt", + "value": { + "name": "Do you have any match funding identified but not yet secured-yes", + "conditions": [ + { + "field": { + "name": "DmgsiG", + "type": "YesNoField", + "display": "Do you have any match funding identified but not yet secured?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "true", + "display": "true" + } + } + ] + } + }, + { + "displayName": "Do you have any match funding identified but not yet secured-no", + "name": "nPKnjf", + "value": { + "name": "Do you have any match funding identified but not yet secured-no", + "conditions": [ + { + "field": { + "name": "DmgsiG", + "type": "YesNoField", + "display": "Do you have any match funding identified but not yet secured?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "false", + "display": "false" + } + } + ] + } + }, + { + "displayName": "Are you applying for revenue funding from the Community Ownership Fund 2025-yes", + "name": "iegbPT", + "value": { + "name": "Are you applying for revenue funding from the Community Ownership Fund 2025-yes", + "conditions": [ + { + "field": { + "name": "matkNH", + "type": "YesNoField", + "display": "Are you applying for revenue funding from the Community Ownership Fund 2025? (optional)" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "true", + "display": "true" + } + } + ] + } + }, + { + "displayName": "Are you applying for revenue funding from the Community Ownership Fund 2025-no", + "name": "zVdAqG", + "value": { + "name": "Are you applying for revenue funding from the Community Ownership Fund 2025-no", + "conditions": [ + { + "field": { + "name": "matkNH", + "type": "YesNoField", + "display": "Are you applying for revenue funding from the Community Ownership Fund 2025? (optional)" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "false", + "display": "false" + } + } + ] + } + } + ], + "fees": [], + "outputs": [], + "version": 2, + "skipSummary": false, + "name": "funding required", + "feedback": { + "feedbackForm": false, + "url": "" + }, + "phaseBanner": { + "phase": "beta" + } +} diff --git a/tests/test_data/multi-input-exported.json b/tests/test_data/multi-input-exported.json new file mode 100644 index 0000000..a6c377a --- /dev/null +++ b/tests/test_data/multi-input-exported.json @@ -0,0 +1,123 @@ +{ + "startPage": "/funding-required", + "pages": [ + { + "path": "/summary", + "title": "Check your answers", + "components": [], + "next": [], + "controller": "./pages/summary.js" + }, + { + "path": "/funding-required", + "title": "Funding required", + "components": [ + { + "type": "Para", + "content": "\n\n", + "options": {}, + "schema": {}, + "name": "RgwAoN" + } + ], + "next": [ + { + "path": "/capital-costs-for-your-project" + } + ], + "controller": "start.js" + }, + { + "path": "/capital-costs-for-your-project", + "title": "Capital costs for your project", + "components": [ + { + "type": "Details", + "content": "We are looking to understand:\n
\n
\n\n

We'll ask you to upload a business plan to support the answers you give us in this section.

\n
", + "options": {}, + "schema": {}, + "title": "Help with management case", + "name": "GaAAPb" + }, + { + "type": "Para", + "content": "

Capital costs

\n\n

Tell us about the total capital costs of your project.

\n\n

This should be for the whole project, not just what you're requesting from the fund.

\n\n

Capital costs can be used to:\n

\n

\n

Remember, you can apply for up to 80% (or 90% if the development support provider has confirmed you're eligible to do this) of your capital costs, up to a maximum of \u00a32 million.

\n

You can use your business plan to provide information that supports your answers.

\n\n\n\n", + "options": {}, + "schema": {}, + "name": "EaxCoo" + }, + { + "options": { + "prefix": "\u00a3", + "columnTitles": [ + "Description", + "Amount", + "Money from COF25 grant", + "Match funding amount", + "Action" + ], + "required": true + }, + "type": "MultiInputField", + "title": "Capital costs", + "hint": "The MultiInputField needed", + "schema": {}, + "name": "qQLyXL", + "children": [ + { + "name": "GLQlOh", + "options": {}, + "type": "TextField", + "title": "Describe the cost" + }, + { + "name": "JtwkMy", + "options": { + "prefix": "\u00a3", + "classes": "govuk-!-width-one-half" + }, + "type": "NumberField", + "title": "Amount", + "hint": "", + "schema": {} + }, + { + "name": "LeTLDo", + "options": { + "prefix": "\u00a3", + "classes": "govuk-!-width-one-half" + }, + "type": "NumberField", + "title": "How much money from the COF25 grant will you use to pay for this cost?", + "hint": "", + "schema": {} + }, + { + "name": "pHZDWT", + "options": { + "prefix": "\u00a3", + "classes": "govuk-!-width-one-half" + }, + "type": "NumberField", + "title": "How much of the match funding will you use to pay for this cost?", + "hint": "", + "schema": {} + } + ] + } + ], + "next": [ + { + "path": "/summary" + } + ], + "controller": "RepeatingFieldPageController" + } + ], + "lists": [], + "conditions": [], + "sections": [], + "outputs": [], + "skipSummary": false, + "name": "Apply for funding to save an asset in your community" +} \ No newline at end of file diff --git a/tests/test_data/multi_input.json b/tests/test_data/multi_input.json new file mode 100644 index 0000000..bc87c02 --- /dev/null +++ b/tests/test_data/multi_input.json @@ -0,0 +1,283 @@ +{ + "metadata": {}, + "startPage": "/funding-required", + + "pages": [ + { + "title": "Funding required", + "path": "/funding-required", + "components": [ + { + "name": "RgwAoN", + "options": {}, + "type": "Para", + "content": "\n\n" + } + ], + "next": [ + { + "path": "/capital-costs-for-your-project" + } + ], + "controller": "./pages/start.js" + }, + + { + "title": "Check your answers", + "path": "/summary", + "controller": "./pages/summary.js", + "components": [], + "next": [], + "section": "bgUGuD" + }, + { + "path": "/capital-costs-for-your-project", + "title": "Capital costs for your project", + "controller": "RepeatingFieldPageController", + "options": { + "summaryDisplayMode": { + "samePage": true, + "separatePage": false, + "hideRowTitles": false + }, + "customText": { + "samePageTitle": "Your capital costs" + } + }, + "components": [ + { + "name": "GaAAPb", + "options": {}, + "type": "Details", + "content": "We are looking to understand:\n
\n
\n\n

We'll ask you to upload a business plan to support the answers you give us in this section.

\n
", + "title": "Help with management case" + }, + { + "name": "EaxCoo", + "options": {}, + "type": "Para", + "content": "

Capital costs

\n\n

Tell us about the total capital costs of your project.

\n\n

This should be for the whole project, not just what you're requesting from the fund.

\n\n

Capital costs can be used to:\n

\n

\n

Remember, you can apply for up to 80% (or 90% if the development support provider has confirmed you're eligible to do this) of your capital costs, up to a maximum of £2 million.

\n

You can use your business plan to provide information that supports your answers.

\n\n\n\n" + }, + { + "name": "qQLyXL", + "options": { + "prefix": "£", + "columnTitles": [ + "Description", + "Amount", + "Money from COF25 grant", + "Match funding amount", + "Action" + ], + "required": true + }, + "type": "MultiInputField", + "title": "Capital costs", + "hint": "The MultiInputField needed", + "schema": {}, + "children": [ + { + "name": "GLQlOh", + "options": {}, + "type": "TextField", + "title": "Describe the cost" + }, + { + "name": "JtwkMy", + "options": { + "prefix": "£", + "classes": "govuk-!-width-one-half" + }, + "type": "NumberField", + "title": "Amount", + "hint": "", + "schema": {} + }, + { + "name": "LeTLDo", + "options": { + "prefix": "£", + "classes": "govuk-!-width-one-half" + }, + "type": "NumberField", + "title": "How much money from the COF25 grant will you use to pay for this cost?", + "hint": "", + "schema": {} + }, + { + "name": "pHZDWT", + "options": { + "prefix": "£", + "classes": "govuk-!-width-one-half" + }, + "type": "NumberField", + "title": "How much of the match funding will you use to pay for this cost?", + "hint": "", + "schema": {} + } + ] + } + ], + "next": [ + { + "path": "/summary" + } + ], + "section": "bgUGuD" + } + + ], + "lists": [], + "sections": [ + { + "name": "bgUGuD", + "title": "Funding required" + } + ], + "conditions": [ + { + "displayName": "Have you secured any match funding yet-yes", + "name": "QyNLbn", + "value": { + "name": "Have you secured any match funding yet-yes", + "conditions": [ + { + "field": { + "name": "DOvZvB", + "type": "YesNoField", + "display": "Have you secured any match funding yet?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "true", + "display": "true" + } + } + ] + } + }, + { + "displayName": "Have you secured any match funding yet-no", + "name": "nzrkpu", + "value": { + "name": "Have you secured any match funding yet-no", + "conditions": [ + { + "field": { + "name": "DOvZvB", + "type": "YesNoField", + "display": "Have you secured any match funding yet?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "false", + "display": "false" + } + } + ] + } + }, + { + "displayName": "Do you have any match funding identified but not yet secured-yes", + "name": "DrELrt", + "value": { + "name": "Do you have any match funding identified but not yet secured-yes", + "conditions": [ + { + "field": { + "name": "DmgsiG", + "type": "YesNoField", + "display": "Do you have any match funding identified but not yet secured?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "true", + "display": "true" + } + } + ] + } + }, + { + "displayName": "Do you have any match funding identified but not yet secured-no", + "name": "nPKnjf", + "value": { + "name": "Do you have any match funding identified but not yet secured-no", + "conditions": [ + { + "field": { + "name": "DmgsiG", + "type": "YesNoField", + "display": "Do you have any match funding identified but not yet secured?" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "false", + "display": "false" + } + } + ] + } + }, + { + "displayName": "Are you applying for revenue funding from the Community Ownership Fund 2025-yes", + "name": "iegbPT", + "value": { + "name": "Are you applying for revenue funding from the Community Ownership Fund 2025-yes", + "conditions": [ + { + "field": { + "name": "matkNH", + "type": "YesNoField", + "display": "Are you applying for revenue funding from the Community Ownership Fund 2025? (optional)" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "true", + "display": "true" + } + } + ] + } + }, + { + "displayName": "Are you applying for revenue funding from the Community Ownership Fund 2025-no", + "name": "zVdAqG", + "value": { + "name": "Are you applying for revenue funding from the Community Ownership Fund 2025-no", + "conditions": [ + { + "field": { + "name": "matkNH", + "type": "YesNoField", + "display": "Are you applying for revenue funding from the Community Ownership Fund 2025? (optional)" + }, + "operator": "is", + "value": { + "type": "Value", + "value": "false", + "display": "false" + } + } + ] + } + } + ], + "fees": [], + "outputs": [], + "version": 2, + "skipSummary": false, + "name": "Apply for funding to save an asset in your community", + "feedback": { + "feedbackForm": false, + "url": "" + }, + "phaseBanner": { + "phase": "beta" + } +} diff --git a/tests/test-import-form.json b/tests/test_data/test-import-form.json similarity index 100% rename from tests/test-import-form.json rename to tests/test_data/test-import-form.json diff --git a/tests/test_generate_form.py b/tests/test_generate_form.py index 5e00ed4..0e6c9a8 100644 --- a/tests/test_generate_form.py +++ b/tests/test_generate_form.py @@ -182,6 +182,17 @@ def test_build_page_controller_not_specified(): components=[], ) ), + ( + Page( + page_id=uuid4(), + form_id=uuid4(), + display_path="page-with-options", + name_in_apply_json={"en": "Page with Options Name"}, + form_index=1, + components=[], + options = {"first": "option"} + ) + ), ], ) def test_build_page(input_page): @@ -190,6 +201,8 @@ def test_build_page(input_page): assert result_page assert mock_build_component.call_count == len(input_page.components) assert len(result_page["components"]) == len(input_page.components) + if input_page.options: + assert result_page["options"] == input_page.options id = uuid4() @@ -311,6 +324,58 @@ def test_build_conditions( "values": {"type": "listRef"}, }, ), + ( + Component( + component_id=uuid4(), + type=ComponentType.MULTI_INPUT_FIELD, + title="Test Title", + hint_text="This must be a hint", + page_id=None, + page_index=1, + theme_id=None, + runner_component_name="test-name", + options={}, + lizt=None, + list_id=None, + children=[ + {"name": "GLQlOh", "options": {}, "type": "TextField", "title": "Describe the cost"}, + { + "name": "JtwkMy", + "options": {"prefix": "£", "classes": "govuk-!-width-one-half"}, + "type": "NumberField", + "title": "Amount", + "hint": "", + "schema": {}, + }, + { + "name": "LeTLDo", + "options": {"prefix": "£", "classes": "govuk-!-width-one-half"}, + "type": "NumberField", + "title": "How much money from the COF25 grant will you use to pay for this cost?", + "hint": "", + "schema": {}, + }, + { + "name": "pHZDWT", + "options": {"prefix": "£", "classes": "govuk-!-width-one-half"}, + "type": "NumberField", + "title": "How much of the match funding will you use to pay for this cost?", + "hint": "", + "schema": {}, + }, + ], + ), + { + "name": "test-name", + "options": {}, + "type": "MultiInputField", + "title": "Test Title", + "hint": "This must be a hint", + "schema": {}, + "metadata": {}, + "children":[{'name': 'GLQlOh', 'options': {}, 'type': 'TextField', 'title': 'Describe the cost'}, {'name': 'JtwkMy', 'options': {'prefix': '£', 'classes': 'govuk-!-width-one-half'}, 'type': 'NumberField', 'title': 'Amount', 'hint': '', 'schema': {}}, {'name': 'LeTLDo', 'options': {'prefix': '£', 'classes': 'govuk-!-width-one-half'}, 'type': 'NumberField', 'title': 'How much money from the COF25 grant will you use to pay for this cost?', 'hint': '', 'schema': {}}, {'name': 'pHZDWT', 'options': {'prefix': '£', 'classes': 'govuk-!-width-one-half'}, 'type': 'NumberField', 'title': 'How much of the match funding will you use to pay for this cost?', 'hint': '', 'schema': {}}] + }, + ), ], ) def test_build_component(component_to_build, exp_result): diff --git a/tests/test_integration.py b/tests/test_integration.py index d2576cb..d736468 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -284,8 +284,6 @@ def test_list_relationship(seed_dynamic_data): assert result.lizt assert result.lizt.name == "classifications_list" - -# add files in /test_data t orun the below test against each file @pytest.mark.parametrize( "input_filename, output_filename,,expected_page_count_for_form,expected_component_count_for_form", [ @@ -293,8 +291,8 @@ def test_list_relationship(seed_dynamic_data): ("optional-all-components.json", "optional.json", 8, 27), ("required-all-components.json", "required.json", 8, 27), ("favourite-colours-sarah.json", "colours.json", 4, 1), - # TODO see why this fails - # ("Organisation-and-local-authority-information-template.json", "local-authority-and-other-organisation-information.json",16, 24), # noqa: E501 + ("funding-required-cof-25.json", "funding-required.json", 12, 21), + ("Organisation-and-local-authority-information-template.json", "local-authority-and-other-organisation-information.json",16, 24), # noqa: E501 ], ) def test_generate_config_for_round_valid_input(