diff --git a/app/assets/javascripts/apiKey.js b/app/assets/javascripts/copyToClipboard.js similarity index 55% rename from app/assets/javascripts/apiKey.js rename to app/assets/javascripts/copyToClipboard.js index 4d8658a7c..03d5325d4 100644 --- a/app/assets/javascripts/apiKey.js +++ b/app/assets/javascripts/copyToClipboard.js @@ -3,33 +3,33 @@ if (!document.queryCommandSupported('copy')) return; - Modules.ApiKey = function() { + Modules.CopyToClipboard = function() { const states = { - 'keyVisible': (options) => ` - - ${options.keyLabel ? '' + options.thing + ': ' : ''}${options.key} + 'valueVisible': (options) => ` + + ${options.valueLabel ? '' + options.thing + ': ' : ''}${options.value} - + ${options.onload ? '' : options.thing + ' returned to page, press button to copy to clipboard'} - `, - 'keyCopied': (options) => ` - + 'valueCopied': (options) => ` + ${options.thing} Copied to clipboard, press button to show in page - ` }; - this.getRangeFromElement = function (keyElement) { + this.getRangeFromElement = function (copyableElement) { const range = document.createRange(); - const childNodes = Array.prototype.slice.call(keyElement.childNodes); + const childNodes = Array.prototype.slice.call(copyableElement.childNodes); let prefixIndex = -1; childNodes.forEach((el, idx) => { @@ -38,15 +38,15 @@ } }); - range.selectNodeContents(keyElement); - if (prefixIndex !== -1) { range.setStart(keyElement, prefixIndex + 1); } + range.selectNodeContents(copyableElement); + if (prefixIndex !== -1) { range.setStart(copyableElement, prefixIndex + 1); } return range; }; - this.copyKey = function(keyElement, callback) { + this.copyValueToClipboard = function(copyableElement, callback) { var selection = window.getSelection ? window.getSelection() : document.selection, - range = this.getRangeFromElement(keyElement); + range = this.getRangeFromElement(copyableElement); selection.removeAllRanges(); selection.addRange(range); @@ -59,36 +59,36 @@ const $component = $(component), stateOptions = { - key: $component.data('key'), + value: $component.data('value'), thing: $component.data('thing') }, name = $component.data('name'); // if the name is distinct from the thing: // - it will be used in the rendering - // - the key won't be identified by a heading so needs its own label + // - the value won't be identified by a heading so needs its own label if (name !== stateOptions.thing) { stateOptions.name = name; - stateOptions.keyLabel = true; + stateOptions.valueLabel = true; } $component - .addClass('api-key') + .addClass('copy-to-clipboard') .css('min-height', $component.height()) - .html(states.keyVisible($.extend({ 'onload': true }, stateOptions))) + .html(states.valueVisible($.extend({ 'onload': true }, stateOptions))) .on( - 'click', '.api-key__button--copy', () => - this.copyKey( - $('.api-key__key', component)[0], () => + 'click', '.copy-to-clipboard__button--copy', () => + this.copyValueToClipboard( + $('.copy-to-clipboard__value', component)[0], () => $component - .html(states.keyCopied(stateOptions)) + .html(states.valueCopied(stateOptions)) .find('.govuk-button').focus() ) ) .on( - 'click', '.api-key__button--show', () => + 'click', '.copy-to-clipboard__button--show', () => $component - .html(states.keyVisible(stateOptions)) + .html(states.valueVisible(stateOptions)) .find('.govuk-button').focus() ); diff --git a/app/assets/stylesheets/components/api-key.scss b/app/assets/stylesheets/components/copy-to-clipboard.scss similarity index 95% rename from app/assets/stylesheets/components/api-key.scss rename to app/assets/stylesheets/components/copy-to-clipboard.scss index 8ea44a3f1..694df01d5 100644 --- a/app/assets/stylesheets/components/api-key.scss +++ b/app/assets/stylesheets/components/copy-to-clipboard.scss @@ -1,4 +1,4 @@ -.api-key { +.copy-to-clipboard { position: relative; padding-bottom: 38px; // height of button diff --git a/app/assets/stylesheets/main.scss b/app/assets/stylesheets/main.scss index 4331c416f..270558240 100644 --- a/app/assets/stylesheets/main.scss +++ b/app/assets/stylesheets/main.scss @@ -50,7 +50,7 @@ $path: '/static/images/'; @import 'components/file-upload'; @import 'components/browse-list'; @import 'components/email-message'; -@import 'components/api-key'; +@import 'components/copy-to-clipboard'; @import 'components/vendor/previous-next-navigation'; @import 'components/radios'; @import 'components/checkboxes'; diff --git a/app/config.py b/app/config.py index f92347d9d..487afa31a 100644 --- a/app/config.py +++ b/app/config.py @@ -1,3 +1,4 @@ +import json import os if os.environ.get('VCAP_APPLICATION'): @@ -78,6 +79,19 @@ class Config(object): NOTIFY_SERVICE_ID = 'd6aa2c68-a2d9-4437-ab19-3ae8eb202553' + NOTIFY_BILLING_DETAILS = json.loads( + os.environ.get('NOTIFY_BILLING_DETAILS') or 'null' + ) or { + 'account_number': '98765432', + 'sort_code': '01-23-45', + 'IBAN': 'GB33BUKB20201555555555', + 'swift': 'ABCDEF12', + 'notify_billing_email_addresses': [ + 'generic@digital.cabinet-office.gov.uk', + 'first.last@digital.cabinet-office.gov.uk', + ] + } + class Development(Config): NOTIFY_LOG_PATH = 'application.log' diff --git a/app/main/__init__.py b/app/main/__init__.py index ea2d62f89..eb9366e53 100644 --- a/app/main/__init__.py +++ b/app/main/__init__.py @@ -29,6 +29,7 @@ from app.main.views import ( # noqa isort:skip organisations, performance, platform_admin, + pricing, providers, register, returned_letters, @@ -37,8 +38,8 @@ from app.main.views import ( # noqa isort:skip sign_in, sign_out, templates, - two_factor, tour, + two_factor, uploads, user_profile, verify, diff --git a/app/main/views/index.py b/app/main/views/index.py index 1ca93b295..83d6399a4 100644 --- a/app/main/views/index.py +++ b/app/main/views/index.py @@ -7,20 +7,15 @@ from flask import ( url_for, ) from flask_login import current_user -from notifications_utils.international_billing_rates import ( - INTERNATIONAL_BILLING_RATES, -) from notifications_utils.template import HTMLEmailTemplate, LetterImageTemplate from app import email_branding_client, letter_branding_client, status_api_client from app.main import main -from app.main.forms import FieldWithNoneOption, SearchByNameForm +from app.main.forms import FieldWithNoneOption from app.main.views.sub_navigation_dictionaries import ( features_nav, - pricing_nav, using_notify_nav, ) -from app.models.feedback import QUESTION_TICKET_TYPE from app.utils import get_logo_cdn_domain, hide_from_search_engines @@ -58,29 +53,6 @@ def accessibility_statement(): return render_template('views/accessibility_statement.html') -@main.route('/pricing') -def pricing(): - return render_template( - 'views/pricing/index.html', - sms_rate=0.0158, - international_sms_rates=sorted([ - (cc, country['names'], country['billable_units']) - for cc, country in INTERNATIONAL_BILLING_RATES.items() - ], key=lambda x: x[0]), - search_form=SearchByNameForm(), - navigation_links=pricing_nav(), - ) - - -@main.route('/pricing/how-to-pay') -def how_to_pay(): - return render_template( - 'views/pricing/how-to-pay.html', - support_link=url_for('main.feedback', ticket_type=QUESTION_TICKET_TYPE), - navigation_links=pricing_nav(), - ) - - @main.route('/delivery-and-failure') @main.route('/features/messages-status') def delivery_and_failure(): diff --git a/app/main/views/pricing.py b/app/main/views/pricing.py new file mode 100644 index 000000000..436c815bd --- /dev/null +++ b/app/main/views/pricing.py @@ -0,0 +1,45 @@ +from flask import current_app, render_template +from flask_login import current_user +from notifications_utils.international_billing_rates import ( + INTERNATIONAL_BILLING_RATES, +) + +from app.main import main +from app.main.forms import SearchByNameForm +from app.main.views.sub_navigation_dictionaries import pricing_nav + + +@main.route('/pricing') +def pricing(): + return render_template( + 'views/pricing/index.html', + sms_rate=0.0158, + international_sms_rates=sorted([ + (cc, country['names'], country['billable_units']) + for cc, country in INTERNATIONAL_BILLING_RATES.items() + ], key=lambda x: x[0]), + search_form=SearchByNameForm(), + navigation_links=pricing_nav(), + ) + + +@main.route('/pricing/how-to-pay') +def how_to_pay(): + return render_template( + 'views/pricing/how-to-pay.html', + navigation_links=pricing_nav(), + ) + + +@main.route('/pricing/billing-details') +def billing_details(): + if current_user.is_authenticated: + return render_template( + 'views/pricing/billing-details.html', + billing_details=current_app.config['NOTIFY_BILLING_DETAILS'], + navigation_links=pricing_nav(), + ) + return render_template( + 'views/pricing/billing-details-signed-out.html', + navigation_links=pricing_nav(), + ) diff --git a/app/main/views/sub_navigation_dictionaries.py b/app/main/views/sub_navigation_dictionaries.py index 1d30f6606..7d42937be 100644 --- a/app/main/views/sub_navigation_dictionaries.py +++ b/app/main/views/sub_navigation_dictionaries.py @@ -47,6 +47,10 @@ def pricing_nav(): "name": "How to pay", "link": "main.how_to_pay", }, + { + "name": "Billing details", + "link": "main.billing_details", + }, ] diff --git a/app/navigation.py b/app/navigation.py index 2ceda0b15..15939c553 100644 --- a/app/navigation.py +++ b/app/navigation.py @@ -59,6 +59,7 @@ class HeaderNavigation(Navigation): 'pricing': { 'pricing', 'how_to_pay', + 'billing_details', }, 'documentation': { 'documentation', diff --git a/app/templates/admin_template.html b/app/templates/admin_template.html index 55f3a96d3..480a9770e 100644 --- a/app/templates/admin_template.html +++ b/app/templates/admin_template.html @@ -225,6 +225,10 @@ { "href": url_for("main.how_to_pay"), "text": "How to pay" + }, + { + "href": url_for("main.billing_details"), + "text": "Billing details" } ] }, diff --git a/app/templates/components/api-key.html b/app/templates/components/api-key.html deleted file mode 100644 index bc432b452..000000000 --- a/app/templates/components/api-key.html +++ /dev/null @@ -1,13 +0,0 @@ -{% macro api_key(key, name, thing="API key") %} - {% if name|lower == thing|lower %} -

- {{ name }} -

- {% endif %} -
- - {% if name|lower != thing|lower %}{{ thing }}: {% endif %}{{ key }} - - -
-{% endmacro %} diff --git a/app/templates/components/copy-to-clipboard.html b/app/templates/components/copy-to-clipboard.html new file mode 100644 index 000000000..8f67c1cfa --- /dev/null +++ b/app/templates/components/copy-to-clipboard.html @@ -0,0 +1,13 @@ +{% macro copy_to_clipboard(value, name, thing) %} + {% if name|lower == thing|lower %} +

+ {{ name }} +

+ {% endif %} +
+ + {% if name|lower != thing|lower %}{{ thing }}: {% endif %}{{ value }} + + +
+{% endmacro %} diff --git a/app/templates/views/api/keys/show.html b/app/templates/views/api/keys/show.html index 44765a387..890d68e84 100644 --- a/app/templates/views/api/keys/show.html +++ b/app/templates/views/api/keys/show.html @@ -1,6 +1,6 @@ {% extends "withnav_template.html" %} {% from "components/page-footer.html" import page_footer %} -{% from "components/api-key.html" import api_key %} +{% from "components/copy-to-clipboard.html" import copy_to_clipboard %} {% block service_page_title %} New API key @@ -22,7 +22,7 @@
- {{ api_key( + {{ copy_to_clipboard( '{}-{}-{}'.format(key_name, service_id, secret), name='API key', thing='API key' diff --git a/app/templates/views/pricing/billing-details-signed-out.html b/app/templates/views/pricing/billing-details-signed-out.html new file mode 100644 index 000000000..9bb6f4711 --- /dev/null +++ b/app/templates/views/pricing/billing-details-signed-out.html @@ -0,0 +1,26 @@ +{% extends "content_template.html" %} +{% from "components/page-header.html" import page_header %} + +{% block per_page_title %} + Billing details +{% endblock %} + +{% block content_column_content %} + + {{ page_header('Billing details') }} + +

+ Sign in to see our: +

+ +
    +
  • supplier details
  • +
  • bank details
  • +
  • invoice address
  • +
+ +

+ You can use this information to add the Cabinet Office as a supplier. Your organisation may need to do this before you can raise a purchase order. +

+ +{% endblock %} diff --git a/app/templates/views/pricing/billing-details.html b/app/templates/views/pricing/billing-details.html new file mode 100644 index 000000000..9482458d2 --- /dev/null +++ b/app/templates/views/pricing/billing-details.html @@ -0,0 +1,138 @@ +{% extends "content_template.html" %} +{% from "components/page-header.html" import page_header %} + +{% from "components/copy-to-clipboard.html" import copy_to_clipboard %} + +{% block per_page_title %} + Billing details +{% endblock %} + +{% block content_column_content %} + + {{ page_header('Billing details') }} + +

+ You can use the information on this page to add the Cabinet Office as a supplier. Your organisation may need to do this before you can raise a purchase order (PO). +

+ +

+ Contact us if you need any other details. +

+ +

+ Supplier details +

+ +

+ Cabinet Office +

+ +

+ The White Chapel Building
+ 10 Whitechapel High Street
+ London
+ E1 8QS +

+ +

+ Email addresses +

+ +
    + {% for email in billing_details['notify_billing_email_addresses'] %} +
  • + {{ email }} +
  • + {% endfor %} +
+ +

+ VAT number +

+ +
+ {{ copy_to_clipboard( + 'GB 88 88 010 80', + thing='VAT number', + ) }} +
+ +

+ Bank details +

+ +

+ National Westminster Bank PLC (part of RBS group)
+ Government Banking Services Branch
+ 2nd Floor
+ 280 Bishopsgate
+ London
+ EC2M 4RB +

+ +

+ Account name +

+ +

+ Cabinet Office +

+ +

+ Account number +

+ +
+ {{ copy_to_clipboard( + billing_details['account_number'], + thing='account number', + ) }} +
+ +

+ Sort code +

+ +
+ {{ copy_to_clipboard( + billing_details['sort_code'], + thing='sort code', + ) }} +
+ + +

+ IBAN +

+
+ {{ copy_to_clipboard( + billing_details['IBAN'], + thing='IBAN', + ) }} +
+ +

+ Swift code +

+ +
+ {{ copy_to_clipboard( + billing_details['swift'], + thing='Swift code', + ) }} +
+ +

+ Invoice address +

+ +

+ SSCL – Accounts Receivable
+ PO Box 221
+ Thornton-Cleveleys
+ Blackpool
+ Lancashire
+ FY1 9JN +

+ +{% endblock %} diff --git a/app/templates/views/pricing/how-to-pay.html b/app/templates/views/pricing/how-to-pay.html index f0569670a..b1d728287 100644 --- a/app/templates/views/pricing/how-to-pay.html +++ b/app/templates/views/pricing/how-to-pay.html @@ -14,7 +14,11 @@

- We’ll send your organisation an invoice each quarter if you: + GOV.UK Notify is run by the Government Digital Service (GDS), part of the Cabinet Office. +

+ +

+ The Cabinet Office will send your organisation an invoice each quarter if you:

    @@ -23,35 +27,39 @@

- If your organisation has more than one service, you'll see a breakdown of each service on your invoice. + If your organisation has more than one Notify service, you’ll see a breakdown of each service on your invoice.

- If the value of an invoice is less than £250 (before VAT), we’ll add it to the total for the next quarter to save time and effort. + If the value of an invoice is less than £250 (before VAT), it will be added to the total for the next quarter to save time and effort.

You can pay by BACS, debit card, or credit card.

+

+ Please start your payment reference with the invoice number. +

+

Purchase orders

- If your organisation’s estimated spend is more than £500 per quarter (before VAT), you need to send us a purchase order (PO). + If your organisation’s total estimated spend is more than £500 per quarter (before VAT), you need to raise a purchase order (PO).

- Your organisation should raise a single PO for the estimated cost of all its services. You can update the PO anytime if your usage increases. + Your organisation should raise a single PO for the estimated cost of all its services. You can update the PO any time if your usage increases.

- You may need to set up the Cabinet Office as a supplier before you can raise a PO. + Your organisation may need to add the Cabinet Office as a supplier before you can raise a PO.

- Contact us if you need help raising a purchase order. + Contact us if you need help raising a purchase order.

{% endblock %} diff --git a/app/templates/views/service-settings/email_reply_to.html b/app/templates/views/service-settings/email_reply_to.html index f60152fbd..b45b97d84 100644 --- a/app/templates/views/service-settings/email_reply_to.html +++ b/app/templates/views/service-settings/email_reply_to.html @@ -1,5 +1,5 @@ {% extends "withnav_template.html" %} -{% from "components/api-key.html" import api_key %} +{% from "components/copy-to-clipboard.html" import copy_to_clipboard %} {% from "components/page-header.html" import page_header %} {% from "components/table.html" import row_group, row, text_field, edit_field, field, boolean_field, list_table with context %} {% from "components/button/macro.njk" import govukButton %} @@ -46,7 +46,7 @@
{% if current_service.count_email_reply_to_addresses > 1 %} - {{ api_key(item.id, name=item.email_address, thing="ID") }} + {{ copy_to_clipboard(item.id, name=item.email_address, thing="ID") }} {% endif %}
diff --git a/app/templates/views/service-settings/letter-contact-details.html b/app/templates/views/service-settings/letter-contact-details.html index 677b66410..3ae2021c3 100644 --- a/app/templates/views/service-settings/letter-contact-details.html +++ b/app/templates/views/service-settings/letter-contact-details.html @@ -1,5 +1,5 @@ {% extends "withnav_template.html" %} -{% from "components/api-key.html" import api_key %} +{% from "components/copy-to-clipboard.html" import copy_to_clipboard %} {% from "components/page-header.html" import page_header %} {% from "components/table.html" import row_group, row, text_field, edit_field, field, boolean_field, list_table with context %} {% from "components/button/macro.njk" import govukButton %} @@ -59,7 +59,7 @@
{% if letter_contact_details|length > 1 %} {% set first_line_of_contact_block = item.contact_block|normalise_lines|first %} - {{ api_key(item.id, name=first_line_of_contact_block, thing="ID") }} + {{ copy_to_clipboard(item.id, name=first_line_of_contact_block, thing="ID") }} {% endif %} {% endfor %} diff --git a/app/templates/views/service-settings/sms-senders.html b/app/templates/views/service-settings/sms-senders.html index 507f7aef2..25158fa86 100644 --- a/app/templates/views/service-settings/sms-senders.html +++ b/app/templates/views/service-settings/sms-senders.html @@ -1,5 +1,5 @@ {% extends "withnav_template.html" %} -{% from "components/api-key.html" import api_key %} +{% from "components/copy-to-clipboard.html" import copy_to_clipboard %} {% from "components/page-header.html" import page_header %} {% from "components/table.html" import row_group, row, text_field, edit_field, field, boolean_field, list_table with context%} {% from "components/button/macro.njk" import govukButton %} @@ -46,7 +46,7 @@ {% if current_service.count_sms_senders > 1 %} - {{ api_key(item.id, name=item.sms_sender, thing="ID") }} + {{ copy_to_clipboard(item.id, name=item.sms_sender, thing="ID") }} {% endif %} {% endfor %} diff --git a/app/templates/views/templates/template.html b/app/templates/views/templates/template.html index 5baf3e2e2..5e05a9761 100644 --- a/app/templates/views/templates/template.html +++ b/app/templates/views/templates/template.html @@ -2,7 +2,7 @@ {% from "components/banner.html" import banner_wrapper %} {% from "components/folder-path.html" import folder_path, page_title_folder_path %} {% from "components/page-footer.html" import page_footer %} -{% from "components/api-key.html" import api_key %} +{% from "components/copy-to-clipboard.html" import copy_to_clipboard %} {% from "components/button/macro.njk" import govukButton %} {% block service_page_title %} @@ -47,7 +47,7 @@ {% if template.template_type != 'broadcast' %}
- {{ api_key(template.id, name="Template ID", thing='template ID') }} + {{ copy_to_clipboard(template.id, name="Template ID", thing='template ID') }}
{% endif %} diff --git a/gulpfile.js b/gulpfile.js index 4412a1ddd..22ebff748 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -163,7 +163,7 @@ const javascripts = () => { paths.src + 'javascripts/cookieMessage.js', paths.src + 'javascripts/cookieSettings.js', paths.src + 'javascripts/stick-to-window-when-scrolling.js', - paths.src + 'javascripts/apiKey.js', + paths.src + 'javascripts/copyToClipboard.js', paths.src + 'javascripts/autofocus.js', paths.src + 'javascripts/enhancedTextbox.js', paths.src + 'javascripts/fileUpload.js', diff --git a/manifest.yml.j2 b/manifest.yml.j2 index cdddb76d6..15bd72b05 100644 --- a/manifest.yml.j2 +++ b/manifest.yml.j2 @@ -60,3 +60,5 @@ applications: REDIS_ENABLED: '{{ REDIS_ENABLED }}' REDIS_URL: '{{ REDIS_URL }}' + + NOTIFY_BILLING_DETAILS: '{{ NOTIFY_BILLING_DETAILS | tojson }}' diff --git a/tests/app/main/views/test_api_integration.py b/tests/app/main/views/test_api_integration.py index dd405b920..521c52ce5 100644 --- a/tests/app/main/views/test_api_integration.py +++ b/tests/app/main/views/test_api_integration.py @@ -283,7 +283,7 @@ def test_should_create_api_key_with_type_normal( _expected_status=200, ) - assert page.select_one('span.api-key__key').text.strip() == ( + assert page.select_one('span.copy-to-clipboard__value').text.strip() == ( 'some_default_key_name_12-{}-{}'.format(SERVICE_ONE_ID, fake_uuid) ) diff --git a/tests/app/main/views/test_index.py b/tests/app/main/views/test_index.py index 998ef65d5..f338c2ff1 100644 --- a/tests/app/main/views/test_index.py +++ b/tests/app/main/views/test_index.py @@ -100,6 +100,7 @@ def test_hiding_pages_from_search_engines( 'guidance_index', 'branding_and_customisation', 'create_and_send_messages', 'edit_and_format_messages', 'send_files_by_email', 'upload_a_letter', 'who_can_use_notify', + 'billing_details', ]) def test_static_pages( client_request, diff --git a/tests/app/main/views/test_templates.py b/tests/app/main/views/test_templates.py index b178a2c65..a686b1997 100644 --- a/tests/app/main/views/test_templates.py +++ b/tests/app/main/views/test_templates.py @@ -975,7 +975,7 @@ def test_should_show_template_id_on_template_page( template_id=fake_uuid, _test_page_title=False, ) - assert fake_uuid in page.select('.api-key__key')[0].text + assert fake_uuid in page.select('.copy-to-clipboard__value')[0].text def test_should_hide_template_id_for_broadcast_templates( @@ -990,7 +990,7 @@ def test_should_hide_template_id_for_broadcast_templates( template_id=fake_uuid, _test_page_title=False, ) - assert not page.select('.api-key__key') + assert not page.select('.copy-to-clipboard__value') def test_should_show_sms_template_with_downgraded_unicode_characters( diff --git a/tests/app/test_navigation.py b/tests/app/test_navigation.py index 5488ea728..0f6e23116 100644 --- a/tests/app/test_navigation.py +++ b/tests/app/test_navigation.py @@ -31,6 +31,7 @@ EXCLUDED_ENDPOINTS = tuple(map(Navigation.get_endpoint_with_blueprint, { 'archive_user', 'bat_phone', 'begin_tour', + 'billing_details', 'branding_and_customisation', 'branding_request', 'broadcast', @@ -371,7 +372,11 @@ navigation_instances = ( ) -@pytest.mark.parametrize('navigation_instance', navigation_instances) +@pytest.mark.parametrize( + 'navigation_instance', + navigation_instances, + ids=(x.__class__.__name__ for x in navigation_instances) +) def test_navigation_items_are_properly_defined(navigation_instance): for endpoint in navigation_instance.endpoints_with_navigation: assert ( @@ -399,7 +404,11 @@ def test_excluded_navigation_items_are_properly_defined(): ), f'{endpoint} found more than once in EXCLUDED_ENDPOINTS' -@pytest.mark.parametrize('navigation_instance', navigation_instances) +@pytest.mark.parametrize( + 'navigation_instance', + navigation_instances, + ids=(x.__class__.__name__ for x in navigation_instances) +) def test_all_endpoints_are_covered(navigation_instance): covered_endpoints = ( navigation_instance.endpoints_with_navigation + @@ -411,7 +420,11 @@ def test_all_endpoints_are_covered(navigation_instance): assert endpoint in covered_endpoints, f'{endpoint} is not listed or excluded' -@pytest.mark.parametrize('navigation_instance', navigation_instances) +@pytest.mark.parametrize( + 'navigation_instance', + navigation_instances, + ids=(x.__class__.__name__ for x in navigation_instances) +) @pytest.mark.xfail(raises=KeyError) def test_raises_on_invalid_navigation_item( client_request, navigation_instance diff --git a/tests/javascripts/apiKey.test.js b/tests/javascripts/copyToClipboard.test.js similarity index 75% rename from tests/javascripts/apiKey.test.js rename to tests/javascripts/copyToClipboard.test.js index 08f974928..3757a7dfd 100644 --- a/tests/javascripts/apiKey.test.js +++ b/tests/javascripts/copyToClipboard.test.js @@ -9,7 +9,7 @@ afterAll(() => { }); -describe('API key', () => { +describe('copy to clipboard', () => { let apiKey = '00000000-0000-0000-0000-000000000000'; let thing; @@ -21,14 +21,14 @@ describe('API key', () => { // set up DOM document.body.innerHTML =` -

+

${options.thing}

-
- +
+ ${(options.name === options.thing) ? '' + options.thing + ': ' : ''}${apiKey} - +
`; }; @@ -58,11 +58,11 @@ describe('API key', () => { // fake support for the copy command not being available document.queryCommandSupported = jest.fn(command => false); - require('../../app/assets/javascripts/apiKey.js'); + require('../../app/assets/javascripts/copyToClipboard.js'); - setUpDOM({ 'thing': 'API key', 'name': 'API key' }); + setUpDOM({ 'thing': 'Some Thing', 'name': 'Some Thing' }); - component = document.querySelector('[data-module=api-key]'); + component = document.querySelector('[data-module=copy-to-clipboard]'); // start the module window.GOVUK.modules.start(); @@ -83,7 +83,7 @@ describe('API key', () => { // force module require to not come from cache jest.resetModules(); - require('../../app/assets/javascripts/apiKey.js'); + require('../../app/assets/javascripts/copyToClipboard.js'); }); @@ -97,13 +97,13 @@ describe('API key', () => { beforeEach(() => { - setUpDOM({ 'thing': 'API key', 'name': 'API key' }); + setUpDOM({ 'thing': 'Some Thing', 'name': 'Some Thing' }); - component = document.querySelector('[data-module=api-key]'); + component = document.querySelector('[data-module=copy-to-clipboard]'); // set default style for component height (queried by jQuery before checking DOM APIs) const stylesheet = document.createElement('style'); - stylesheet.innerHTML = '[data-module=api-key] { height: auto; }'; // set to browser default + stylesheet.innerHTML = '[data-module=copy-to-clipboard] { height: auto; }'; // set to browser default document.getElementsByTagName('head')[0].appendChild(stylesheet); componentHeightOnLoad = 50; @@ -126,15 +126,15 @@ describe('API key', () => { }); - test("It should add a button for copying the key to the clipboard", () => { + test("It should add a button for copying the thing to the clipboard", () => { expect(component.querySelector('button')).not.toBeNull(); }); - test("It should add the 'api-key' class", () => { + test("It should add the 'copy-to-clipboard' class", () => { - expect(component.classList.contains('api-key')).toBe(true); + expect(component.classList.contains('copy-to-clipboard')).toBe(true); }); @@ -162,7 +162,7 @@ describe('API key', () => { // different, it will be one of others called the same 'thing'. setUpDOM({ 'thing': 'ID', 'name': 'Default' }); - component = document.querySelector('[data-module=api-key]'); + component = document.querySelector('[data-module=copy-to-clipboard]'); // start the module window.GOVUK.modules.start(); @@ -173,9 +173,9 @@ describe('API key', () => { // and so needs some prefix text to label it test("The id should have a hidden prefix to label what it is", () => { - const keyPrefix = component.querySelector('.api-key__key .govuk-visually-hidden'); - expect(keyPrefix).not.toBeNull(); - expect(keyPrefix.textContent).toEqual('ID: '); + const value = component.querySelector('.copy-to-clipboard__value .govuk-visually-hidden'); + expect(value).not.toBeNull(); + expect(value.textContent).toEqual('ID: '); }); @@ -196,9 +196,9 @@ describe('API key', () => { // The heading is added if 'thing' (what the id is) has the same value as 'name' // (its specific identifier on the page) because this means it can assume it is // the only one of its type there - setUpDOM({ 'thing': 'API key', 'name': 'API key' }); + setUpDOM({ 'thing': 'Some Thing', 'name': 'Some Thing' }); - component = document.querySelector('[data-module=api-key]'); + component = document.querySelector('[data-module=copy-to-clipboard]'); // start the module window.GOVUK.modules.start(); @@ -207,9 +207,9 @@ describe('API key', () => { test("Its button and id shouldn't have extra hidden text to identify them", () => { - const keyPrefix = component.querySelector('.api-key__key .govuk-visually-hidden'); + const value = component.querySelector('.copy-to-clipboard__value .govuk-visually-hidden'); const buttonSuffix = component.querySelector('button .govuk-visually-hidden'); - expect(keyPrefix).toBeNull(); + expect(value).toBeNull(); expect(buttonSuffix).toBeNull(); }) @@ -218,7 +218,7 @@ describe('API key', () => { }); - describe("If you click the 'Copy API key to clipboard' button", () => { + describe("If you click the 'Copy Some Thing to clipboard' button", () => { describe("For all variations of the initial HTML", () => { @@ -226,13 +226,13 @@ describe('API key', () => { beforeEach(() => { - setUpDOM({ 'thing': 'API key', 'name': 'API key' }); + setUpDOM({ 'thing': 'Some Thing', 'name': 'Some Thing' }); // start the module window.GOVUK.modules.start(); - component = document.querySelector('[data-module=api-key]'); - keyEl = component.querySelector('.api-key__key'); + component = document.querySelector('[data-module=copy-to-clipboard]'); + keyEl = component.querySelector('.copy-to-clipboard__value'); helpers.triggerEvent(component.querySelector('button'), 'click'); @@ -240,7 +240,7 @@ describe('API key', () => { test("The live-region should be shown and its text should confirm the copy action", () => { - const liveRegion = component.querySelector('.api-key__notice'); + const liveRegion = component.querySelector('.copy-to-clipboard__notice'); expect(liveRegion.classList.contains('govuk-visually-hidden')).toBe(false); expect(liveRegion.textContent.trim()).toEqual( @@ -253,31 +253,31 @@ describe('API key', () => { // lower priority than the live-region test("The live-region should contain some hidden text giving context to the statement shown", () => { - const liveRegionHiddenText = component.querySelectorAll('.api-key__notice .govuk-visually-hidden'); + const liveRegionHiddenText = component.querySelectorAll('.copy-to-clipboard__notice .govuk-visually-hidden'); expect(liveRegionHiddenText.length).toEqual(2); - expect(liveRegionHiddenText[0].textContent).toEqual('API key '); + expect(liveRegionHiddenText[0].textContent).toEqual('Some Thing '); expect(liveRegionHiddenText[1].textContent).toEqual(', press button to show in page'); }); - test("It should swap the button for one to show the API key", () => { + test("It should swap the button for one to show the Some Thing", () => { expect(component.querySelector('button').textContent.trim()).toEqual( - expect.stringContaining('Show API key') + expect.stringContaining('Show Some Thing') ); }); test("It should remove the id from the page", () => { - expect(component.querySelector('.api-key__key')).toBeNull(); + expect(component.querySelector('.copy-to-clipboard__value')).toBeNull(); }); - test("It should copy the key to the clipboard", () => { + test("It should copy the thing to the clipboard", () => { - // it should make a selection (a range) from the contents of the element containing the API key + // it should make a selection (a range) from the contents of the element containing the Some Thing expect(rangeMock.selectNodeContents.mock.calls[0]).toEqual([keyEl]); // that selection (a range) should be added to that for the page (a selection) @@ -293,7 +293,7 @@ describe('API key', () => { }); - describe("If you then click the 'Show API key'", () => { + describe("If you then click the 'Show Some Thing'", () => { beforeEach(() => { @@ -301,16 +301,16 @@ describe('API key', () => { }); - test("It should change the text to show the API key", () => { + test("It should change the text to show the Some Thing", () => { - expect(component.querySelector('.api-key__key')).not.toBeNull(); + expect(component.querySelector('.copy-to-clipboard__value')).not.toBeNull(); }); - test("It should swap the button for one to copy the key to the clipboard", () => { + test("It should swap the button for one to copy the thing to the clipboard", () => { expect(component.querySelector('button').textContent.trim()).toEqual( - expect.stringContaining('Copy API key to clipboard') + expect.stringContaining('Copy Some Thing to clipboard') ); }) @@ -330,7 +330,7 @@ describe('API key', () => { // start the module window.GOVUK.modules.start(); - component = document.querySelector('[data-module=api-key]'); + component = document.querySelector('[data-module=copy-to-clipboard]'); helpers.triggerEvent(component.querySelector('button'), 'click'); @@ -368,12 +368,12 @@ describe('API key', () => { // The heading is added if 'thing' (what the id is) has the same value as 'name' // (its specific identifier on the page) because this means it can assume it is // the only one of its type there - setUpDOM({ 'thing': 'API key', 'name': 'API key' }); + setUpDOM({ 'thing': 'Some Thing', 'name': 'Some Thing' }); // start the module window.GOVUK.modules.start(); - component = document.querySelector('[data-module=api-key]'); + component = document.querySelector('[data-module=copy-to-clipboard]'); helpers.triggerEvent(component.querySelector('button'), 'click'); @@ -381,9 +381,9 @@ describe('API key', () => { test("Its button and id shouldn't have extra hidden text to identify them", () => { - const keyPrefix = component.querySelector('.api-key__key .govuk-visually-hidden'); + const prefix = component.querySelector('.copy-to-clipboard__value .govuk-visually-hidden'); const buttonSuffix = component.querySelector('button .govuk-visually-hidden'); - expect(keyPrefix).toBeNull(); + expect(prefix).toBeNull(); expect(buttonSuffix).toBeNull(); })