Merge pull request #3441 from alphagov/change-checkboxes-to-govuk-frontend

Change checkboxes to GOVUK frontend
This commit is contained in:
Tom Byers
2020-05-14 15:23:14 +01:00
committed by GitHub
32 changed files with 719 additions and 303 deletions

View File

@@ -5,7 +5,7 @@
function Summary (module) {
this.module = module;
this.$el = module.$formGroup.find('.selection-summary');
this.$el = module.$formGroup.find('.selection-summary').first();
this.fieldLabel = module.fieldLabel;
this.total = module.total;
this.addContent();
@@ -25,6 +25,7 @@
if (this.fieldLabel === 'folder') { this.$text.addClass('selection-summary__text--folders'); }
this.$el.append(this.$text);
this.module.$formGroup.find('.govuk-hint').remove();
};
Summary.prototype.update = function(selection) {
let template;
@@ -86,12 +87,13 @@
.focus();
};
CollapsibleCheckboxes.prototype.start = function(component) {
this.$formGroup = $(component);
this.$fieldset = this.$formGroup.find('fieldset');
this.$component = $(component);
this.$formGroup = this.$component.find('.govuk-form-group').first();
this.$fieldset = this.$formGroup.find('fieldset').first();
this.$checkboxes = this.$fieldset.find('input[type=checkbox]');
this.fieldLabel = this.$formGroup.data('fieldLabel');
this.fieldLabel = this.$component.data('fieldLabel');
this.total = this.$checkboxes.length;
this.legendText = this.$fieldset.find('legend').text().trim();
this.legendText = this.$fieldset.find('legend').first().text().trim();
this.expanded = false;
this.addHeadingHideLegend();
@@ -113,7 +115,7 @@
};
CollapsibleCheckboxes.prototype.getSelection = function() { return this.$checkboxes.filter(':checked').length; };
CollapsibleCheckboxes.prototype.addHeadingHideLegend = function() {
const headingLevel = this.$formGroup.data('heading-level') || '2';
const headingLevel = this.$component.data('heading-level') || '2';
this.$heading = $(`<h${headingLevel} class="heading-small">${this.legendText}</h${headingLevel}>`);
this.$fieldset.before(this.$heading);

View File

@@ -1,3 +1,7 @@
// Taken from https://github.com/alphagov/govuk-frontend/blob/v2.13.0/src/components/checkboxes/_checkboxes.scss
$govuk-touch-target-size: 44px;
$govuk-checkboxes-size: 40px;
.selection-summary {
.selection-summary__text {
@@ -65,6 +69,30 @@
}
.govuk-form-group--nested {
$border-thickness: $govuk-touch-target-size - $govuk-checkboxes-size;
$border-indent: $govuk-touch-target-size / 2;
position: relative;
// To equalise the spacing between the line and the top/bottom of
// the radio
margin-top: govuk-spacing(1) + ($border-thickness / 2);
margin-bottom: govuk-spacing(1) * -1;
padding-left: govuk-spacing(2) + 2;
&:before {
content: "";
position: absolute;
bottom: 0;
left: $border-indent * -1;
width: $border-thickness;
height: 100%;
background: $govuk-border-colour;
}
}
.selection-content {
margin-bottom: govuk-spacing(4);

View File

@@ -24,6 +24,9 @@
}
}
$message-text-left-spacing: 22px;
$message-type-bottom-spacing: govuk-spacing(4);
.message {
&-name {
@@ -63,39 +66,18 @@
}
&-type {
color: $secondary-text-colour;
margin: 0 0 govuk-spacing(4) 0;
color: $govuk-secondary-text-colour;
margin: 0 0 $message-type-bottom-spacing 0;
padding-left: 0;
pointer-events: none;
}
}
#template-list {
margin-top: govuk-spacing(6);
&.top-gutter-5px {
margin-top: 5px;
}
}
.template-list {
&-item {
&-with-checkbox {
position: relative;
padding-left: govuk-spacing(9);
.multiple-choice {
position: absolute;
left: 0;
}
}
&-hidden-by-default {
display: none;
}
@@ -123,6 +105,22 @@
}
&-hint,
&-label {
padding-left: $message-text-left-spacing;
}
&-label {
padding-top: 0px;
padding-bottom: 0px;
}
// Fix for GOVUK Frontend selector with high precendence
// https://github.com/alphagov/govuk-frontend/blob/v2.13.0/src/components/hint/_hint.scss
&-label:not(.govuk-label--m):not(.govuk-label--l):not(.govuk-label--xl)+.template-list-item-hint {
margin-bottom: $message-type-bottom-spacing;
}
}
&-folder {

View File

@@ -26,6 +26,7 @@ $govuk-assets-path: "/static/";
@import 'components/button/_button';
@import 'components/details/_details';
@import 'components/radios/_radios';
@import 'components/checkboxes/_checkboxes';
@import "utilities/all";
@import "overrides/all";

View File

@@ -3,7 +3,7 @@ from datetime import datetime, timedelta
from itertools import chain
import pytz
from flask import request
from flask import Markup, render_template, request
from flask_login import current_user
from flask_wtf import FlaskForm as Form
from flask_wtf.file import FileAllowed
@@ -16,6 +16,7 @@ from notifications_utils.recipients import (
normalise_phone_number,
validate_phone_number,
)
from werkzeug.utils import cached_property
from wtforms import (
BooleanField,
DateField,
@@ -317,13 +318,16 @@ class RadioFieldWithNoneOption(FieldWithNoneOption, RadioField):
class NestedFieldMixin:
def children(self):
# start map with root option as a single child entry
child_map = {None: [option for option in self
if option.data == self.NONE_OPTION_VALUE]}
# add entries for all other children
for option in self:
# assign all options with a NONE_OPTION_VALUE (not always None) to the None key
if option.data == self.NONE_OPTION_VALUE:
child_ids = [
folder['id'] for folder in self.all_template_folders
@@ -339,6 +343,47 @@ class NestedFieldMixin:
return child_map
# to be used as the only version of .children once radios are converted
@cached_property
def _children(self):
return self.children()
def get_items_from_options(self, field):
items = []
for option in self._children[None]:
item = self.get_item_from_option(option)
if option.data in self._children:
item['children'] = self.render_children(field.name, option.label.text, self._children[option.data])
items.append(item)
return items
def render_children(self, name, label, options):
params = {
"name": name,
"fieldset": {
"legend": {
"text": label,
"classes": "govuk-visually-hidden"
}
},
"formGroup": {
"classes": "govuk-form-group--nested"
},
"asList": True,
"items": []
}
for option in options:
item = self.get_item_from_option(option)
if len(self._children[option.data]):
item['children'] = self.render_children(name, option.label.text, self._children[option.data])
params['items'].append(item)
return render_template('forms/fields/checkboxes/template.njk', params=params)
class NestedRadioField(RadioFieldWithNoneOption, NestedFieldMixin):
pass
@@ -487,12 +532,201 @@ class RegisterUserFromOrgInviteForm(StripWhitespaceForm):
auth_type = HiddenField('auth_type', validators=[DataRequired()])
PermissionsAbstract = type("PermissionsAbstract", (StripWhitespaceForm,), {
permission: BooleanField(label) for permission, label in permissions
})
class govukCheckboxesMixin:
def extend_params(self, params, extensions):
items = None
param_items = len(params['items']) if 'items' in params else 0
# split items off from params to make it a pure dict
if 'items' in extensions:
items = extensions['items']
del extensions['items']
# merge dicts
params.update(extensions)
# merge items
if items:
if 'items' not in params:
params['items'] = items
else:
for idx, _item in enumerate(items):
if idx >= param_items:
params['items'].append(items[idx])
else:
params['items'][idx].update(items[idx])
class PermissionsForm(PermissionsAbstract):
class govukCheckboxField(govukCheckboxesMixin, BooleanField):
def __init__(self, label='', validators=None, param_extensions=None, **kwargs):
super(govukCheckboxField, self).__init__(label, validators, false_values=None, **kwargs)
self.param_extensions = param_extensions
# self.__call__ renders the HTML for the field by:
# 1. delegating to self.meta.render_field which
# 2. calls field.widget
# this bypasses that by making self.widget a method with the same interface as widget.__call__
def widget(self, field, param_extensions=None, **kwargs):
# error messages
error_message = None
if field.errors:
error_message = {"text": " ".join(field.errors).strip()}
params = {
'name': field.name,
'errorMessage': error_message,
'items': [
{
"name": field.name,
"id": field.id,
"text": field.label.text,
"value": 'y',
"checked": field.data
}
]
}
# extend default params with any sent in during instantiation
if self.param_extensions:
self.extend_params(params, self.param_extensions)
# add any sent in though use in templates
if param_extensions:
self.extend_params(params, param_extensions)
return Markup(
render_template('forms/fields/checkboxes/macro.njk', params=params))
# based on work done by @richardjpope: https://github.com/richardjpope/recourse/blob/master/recourse/forms.py#L6
class govukCheckboxesField(govukCheckboxesMixin, SelectMultipleField):
render_as_list = False
def __init__(self, label='', validators=None, param_extensions=None, **kwargs):
super(govukCheckboxesField, self).__init__(label, validators, **kwargs)
self.param_extensions = param_extensions
def get_item_from_option(self, option):
return {
"name": option.name,
"id": option.id,
"text": option.label.text,
"value": str(option.data), # to protect against non-string types like uuids
"checked": option.checked
}
def get_items_from_options(self, field):
return [self.get_item_from_option(option) for option in field]
def extend_params(self, params, extensions):
items = None
param_items = len(params['items']) if 'items' in params else 0
# split items off from params to make it a pure dict
if 'items' in extensions:
items = extensions['items']
del extensions['items']
# merge dicts
params.update(extensions)
# merge items
if items:
if 'items' not in params:
params['items'] = items
else:
for idx, _item in enumerate(items):
if idx >= param_items:
params['items'].append(items[idx])
else:
params['items'][idx].update(items[idx])
# self.__call__ renders the HTML for the field by:
# 1. delegating to self.meta.render_field which
# 2. calls field.widget
# this bypasses that by making self.widget a method with the same interface as widget.__call__
def widget(self, field, param_extensions=None, **kwargs):
# error messages
error_message = None
if field.errors:
error_message = {"text": " ".join(field.errors).strip()}
# returns either a list or a hierarchy of lists
# depending on how get_items_from_options is implemented
items = self.get_items_from_options(field)
params = {
'name': field.name,
"fieldset": {
"attributes": {"id": field.name},
"legend": {
"text": field.label.text,
"classes": "govuk-fieldset__legend--s"
}
},
"asList": self.render_as_list,
'errorMessage': error_message,
'items': items
}
# extend default params with any sent in during instantiation
if self.param_extensions:
self.extend_params(params, self.param_extensions)
# add any sent in though use in templates
if param_extensions:
self.extend_params(params, param_extensions)
return Markup(
render_template('forms/fields/checkboxes/macro.njk', params=params))
# Extends fields using the govukCheckboxesField interface to wrap their render in HTML needed by the collapsible JS
class govukCollapsibleCheckboxesMixin:
def __init__(self, label='', validators=None, field_label='', param_extensions=None, **kwargs):
super(govukCollapsibleCheckboxesMixin, self).__init__(label, validators, param_extensions, **kwargs)
self.field_label = field_label
def widget(self, field, **kwargs):
# add a blank hint to act as an ARIA live-region
if self.param_extensions is not None:
self.param_extensions.update(
{"hint": {"html": "<div class=\"selection-summary\" role=\"region\" aria-live=\"polite\"></div>"}})
else:
self.param_extensions = \
{"hint": {"html": "<div class=\"selection-summary\" role=\"region\" aria-live=\"polite\"></div>"}}
# wrap the checkboxes HTML in the HTML needed by the collapisble JS
return Markup(
f'<div class="selection-wrapper"'
f' data-module="collapsible-checkboxes"'
f' data-field-label="{self.field_label}">'
f' {super(govukCollapsibleCheckboxesMixin, self).widget(field, **kwargs)}'
f'</div>'
)
class govukCollapsibleCheckboxesField(govukCollapsibleCheckboxesMixin, govukCheckboxesField):
pass
# govukCollapsibleCheckboxesMixin adds an ARIA live-region to the hint and wraps the render in HTML needed by the
# collapsible JS
# NestedFieldMixin puts the items into a tree hierarchy, pre-rendering the sub-trees of the top-level items
class govukCollapsibleNestedCheckboxesField(govukCollapsibleCheckboxesMixin, NestedFieldMixin, govukCheckboxesField):
NONE_OPTION_VALUE = None
render_as_list = True
class PermissionsForm(StripWhitespaceForm):
def __init__(self, all_template_folders=None, *args, **kwargs):
super().__init__(*args, **kwargs)
self.folder_permissions.choices = []
@@ -502,7 +736,9 @@ class PermissionsForm(PermissionsAbstract):
(item['id'], item['name']) for item in ([{'name': 'Templates', 'id': None}] + all_template_folders)
]
folder_permissions = NestedCheckboxesField('Folders this team member can see')
folder_permissions = govukCollapsibleNestedCheckboxesField(
'Folders this team member can see',
field_label='folder')
login_authentication = RadioField(
'Sign in using',
@@ -514,21 +750,23 @@ class PermissionsForm(PermissionsAbstract):
validators=[DataRequired()]
)
@property
def permissions(self):
return {role for role in roles.keys() if self[role].data is True}
@property
def permissions_fields(self):
return (getattr(self, permission) for permission, _ in permissions)
permissions_field = govukCheckboxesField(
'Permssions',
choices=[
(value, label) for value, label in permissions
],
param_extensions={
"hint": {"text": "All team members can see sent messages."}
}
)
@classmethod
def from_user(cls, user, service_id, **kwargs):
return cls(
**kwargs,
**{
role: user.has_permission_for_service(service_id, role)
for role in roles.keys()
"permissions_field": [
role for role in roles.keys() if user.has_permission_for_service(service_id, role)]
},
login_authentication=user.auth_type
)
@@ -1085,7 +1323,7 @@ class ServiceContactDetailsForm(StripWhitespaceForm):
class ServiceReplyToEmailForm(StripWhitespaceForm):
email_address = email_address(label='Reply-to email address', gov_user=False)
is_default = BooleanField("Make this email address the default")
is_default = govukCheckboxField("Make this email address the default")
class ServiceSmsSenderForm(StripWhitespaceForm):
@@ -1099,11 +1337,11 @@ class ServiceSmsSenderForm(StripWhitespaceForm):
DoesNotStartWithDoubleZero(),
]
)
is_default = BooleanField("Make this text message sender the default")
is_default = govukCheckboxField("Make this text message sender the default")
class ServiceEditInboundNumberForm(StripWhitespaceForm):
is_default = BooleanField("Make this text message sender the default")
is_default = govukCheckboxField("Make this text message sender the default")
class ServiceLetterContactBlockForm(StripWhitespaceForm):
@@ -1113,7 +1351,7 @@ class ServiceLetterContactBlockForm(StripWhitespaceForm):
NoCommasInPlaceHolders()
]
)
is_default = BooleanField("Set as your default address")
is_default = govukCheckboxField("Set as your default address")
def validate_letter_contact_block(self, field):
line_count = field.data.strip().count('\n')
@@ -1279,7 +1517,7 @@ class Whitelist(StripWhitespaceForm):
class DateFilterForm(StripWhitespaceForm):
start_date = DateField("Start Date", [validators.optional()])
end_date = DateField("End Date", [validators.optional()])
include_from_test_key = BooleanField("Include test keys", default="checked", false_values={"N"})
include_from_test_key = govukCheckboxField("Include test keys")
class RequiredDateFilterForm(StripWhitespaceForm):
@@ -1591,7 +1829,9 @@ class TemplateFolderForm(StripWhitespaceForm):
(item.id, item.name) for item in all_service_users
]
users_with_permission = MultiCheckboxField('Team members who can see this folder')
users_with_permission = govukCollapsibleCheckboxesField(
'Team members who can see this folder',
field_label='folder')
name = StringField('Folder name', validators=[DataRequired(message='Cannot be empty')])
@@ -1681,9 +1921,18 @@ class TemplateAndFoldersSelectionForm(Form):
return self.move_to_new_folder_name.data
return None
templates_and_folders = MultiCheckboxField('Choose templates or folders', validators=[
required_for_ops('move-to-new-folder', 'move-to-existing-folder')
])
templates_and_folders = govukCheckboxesField(
'Choose templates or folders',
validators=[required_for_ops('move-to-new-folder', 'move-to-existing-folder')],
choices=[], # added to keep order of arguments, added properly in __init__
param_extensions={
"fieldset": {
"legend": {
"classes": "govuk-visually-hidden"
}
}
}
)
# if no default set, it is set to None, which process_data transforms to '__NONE__'
# this means '__NONE__' (self.ALL_TEMPLATES option) is selected when no form data has been submitted
# set default to empty string so process_data method doesn't perform any transformation

View File

@@ -63,7 +63,7 @@ def invite_user(service_id):
current_user.id,
service_id,
email_address,
form.permissions,
set(form.permissions_field.data),
form.login_authentication.data,
form.folder_permissions.data,
)
@@ -102,7 +102,7 @@ def edit_user_permissions(service_id, user_id):
if form.validate_on_submit():
user.set_permissions(
service_id,
permissions=form.permissions,
permissions=set(form.permissions_field.data),
folder_permissions=form.folder_permissions.data,
)
if service_has_email_auth:

View File

@@ -168,9 +168,11 @@ def platform_admin_services():
# Default to True if the user hasnt done any filtering,
# otherwise respect their choice
form.include_from_test_key.data = True
include_from_test_key = form.include_from_test_key.data
api_args = {'detailed': True,
'only_active': False, # specifically DO get inactive services
'include_from_test_key': form.include_from_test_key.data,
'include_from_test_key': include_from_test_key,
}
if form.start_date.data:
@@ -184,7 +186,7 @@ def platform_admin_services():
return render_template(
'views/platform-admin/services.html',
include_from_test_key=form.include_from_test_key.data,
include_from_test_key=include_from_test_key,
form=form,
services=list(format_stats_by_service(services)),
page_title='{} services'.format(

View File

@@ -1,46 +0,0 @@
{% from "components/select-input.html" import select_nested, select %}
{% macro checkbox(
field,
hint=False,
width='2-3'
) %}
<div class="multiple-choice">
{{ checkbox_input(field.id, field.name, field.data) }}
<label for="{{ field.id }}">
{{ field.label.text }}
{% if hint %}
<div class="hint">
{{ hint }}
</div>
{% endif %}
</label>
</div>
{% endmacro %}
{% macro checkboxes_nested(field, child_map, hint=None, disable=[], option_hints={}, hide_legend=False, collapsible_opts={}, legend_style="text") %}
{{ select_nested(field, child_map, hint, disable, option_hints, hide_legend, collapsible_opts, legend_style, input="checkbox") }}
{% endmacro %}
{% macro checkboxes(field, hint=None, disable=[], option_hints={}, hide_legend=False, collapsible_opts={}) %}
{{ select(field, hint, disable, option_hints, hide_legend, collapsible_opts, input="checkbox") }}
{% endmacro %}
{% macro checkbox_input(id, name, data=None, value="y") %}
<input
id="{{ id }}" name="{{ name }}" type="checkbox" value="{{ value }}"
{% if data %}
checked
{% endif %}
>
{% endmacro %}
{% macro unlabelled_checkbox(id, name, data=None, value="y") %}
<div class="multiple-choice">
{{ checkbox_input(id, name, data, value) }}
<label></label>
</div>
{% endmacro %}

View File

@@ -0,0 +1,4 @@
{%- macro govukCheckboxes(params) %}
{%- include "./template.njk" -%}
{%- endmacro %}
{{ govukCheckboxes(params) }}

View File

@@ -0,0 +1,134 @@
{% from "components/error-message/macro.njk" import govukErrorMessage -%}
{% from "components/fieldset/macro.njk" import govukFieldset %}
{% from "components/hint/macro.njk" import govukHint %}
{% from "components/label/macro.njk" import govukLabel %}
{#- Copied from https://github.com/alphagov/govuk-frontend/blob/v2.13.0/src/components/checkboxes/template.njk
Changes:
- `classes` option added to `item` allow custom classes on the `.govuk-checkboxes__item` element
- `classes` option added to `item.hint` allow custom classes on the `.govuk-hint` element (added to GOVUK Frontend in v3.5.0 - remove when we update)
- `asList` option added the root `params` object to allow setting of the `.govuk-checkboxes` and `.govuk-checkboxes__item` element types
- `children` option added to `item` allowing the sending in of prerendered child checkboxes (allowing the creation of tree structures through recursion) -#}
{#- If an id 'prefix' is not passed, fall back to using the name attribute
instead. We need this for error messages and hints as well -#}
{% set idPrefix = params.idPrefix if params.idPrefix else params.name %}
{#- a record of other elements that we need to associate with the input using
aria-describedby for example hints or error messages -#}
{% set describedBy = params.describedBy if params.describedBy else "" %}
{% if params.fieldset.describedBy %}
{% set describedBy = params.fieldset.describedBy %}
{% endif %}
{#- set the types of element used for the checkboxes and their group based on
whether asList is set -#}
{% if params.asList %}
{% set groupElement = 'ul' %}
{% set groupItemElement = 'li' %}
{% else %}
{% set groupElement = 'div' %}
{% set groupItemElement = 'div' %}
{% endif %}
{% set isConditional = false %}
{% for item in params.items %}
{% if item.conditional %}
{% set isConditional = true %}
{% endif %}
{% endfor %}
{#- fieldset is false by default -#}
{% set hasFieldset = true if params.fieldset else false %}
{#- Capture the HTML so we can optionally nest it in a fieldset -#}
{% set innerHtml %}
{% if params.hint %}
{% set hintId = idPrefix + '-hint' %}
{% set describedBy = describedBy + ' ' + hintId if describedBy else hintId %}
{{ govukHint({
id: hintId,
classes: params.hint.classes,
attributes: params.hint.attributes,
html: params.hint.html,
text: params.hint.text
}) | indent(2) | trim }}
{% endif %}
{% if params.errorMessage %}
{% set errorId = idPrefix + '-error' %}
{% set describedBy = describedBy + ' ' + errorId if describedBy else errorId %}
{{ govukErrorMessage({
id: errorId,
classes: params.errorMessage.classes,
attributes: params.errorMessage.attributes,
html: params.errorMessage.html,
text: params.errorMessage.text,
visuallyHiddenText: params.errorMessage.visuallyHiddenText
}) | indent(2) | trim }}
{% endif %}
<{{ groupElement }} class="govuk-checkboxes {%- if params.classes %} {{ params.classes }}{% endif %}"
{%- for attribute, value in params.attributes %} {{ attribute }}="{{ value }}"{% endfor %}
{%- if isConditional %} data-module="checkboxes"{% endif -%}>
{% for item in params.items %}
{% set id = item.id if item.id else idPrefix + "-" + loop.index %}
{% set name = item.name if item.name else params.name %}
{% set conditionalId = "conditional-" + id %}
{% set hasHint = true if item.hint.text or item.hint.html %}
{% set itemHintId = id + "-item-hint" if hasHint else "" %}
{% set itemDescribedBy = describedBy if not hasFieldset else "" %}
{% set itemDescribedBy = (itemDescribedBy + " " + itemHintId) | trim %}
<{{ groupItemElement }} class="govuk-checkboxes__item {%- if item.classes %} {{ item.classes }}{% endif %}">
<input class="govuk-checkboxes__input" id="{{ id }}" name="{{ name }}" type="checkbox" value="{{ item.value }}"
{{-" checked" if item.checked }}
{{-" disabled" if item.disabled }}
{%- if item.conditional %} data-aria-controls="{{ conditionalId }}"{% endif -%}
{%- if itemDescribedBy %} aria-describedby="{{ itemDescribedBy }}"{% endif -%}
{%- for attribute, value in item.attributes %} {{ attribute }}="{{ value }}"{% endfor -%}>
{{ govukLabel({
html: item.html,
text: item.text,
classes: 'govuk-checkboxes__label' + (' ' + item.label.classes if item.label.classes),
attributes: item.label.attributes,
for: id
}) | indent(6) | trim }}
{%- if hasHint %}
{{ govukHint({
id: itemHintId,
classes: 'govuk-checkboxes__hint' + (' ' + item.hint.classes if item.hint.classes),
attributes: item.hint.attributes,
html: item.hint.html,
text: item.hint.text
}) | indent(6) | trim }}
{%- endif %}
{%- if item.children %}
{{ item.children | safe }}
{%- endif %}
{% if params.asList and item.conditional %}
<div class="govuk-checkboxes__conditional{% if not item.checked %} govuk-checkboxes__conditional--hidden{% endif %}" id="{{ conditionalId }}">
{{ item.conditional.html | safe }}
</div>
{% endif %}
</{{ groupItemElement }}>
{% if not params.asList and item.conditional %}
<div class="govuk-checkboxes__conditional{% if not item.checked %} govuk-checkboxes__conditional--hidden{% endif %}" id="{{ conditionalId }}">
{{ item.conditional.html | safe }}
</div>
{% endif %}
{% endfor %}
</{{ groupElement }}>
{% endset -%}
<div class="govuk-form-group {%- if params.errorMessage %} govuk-form-group--error{% endif %} {%- if params.formGroup.classes %} {{ params.formGroup.classes }}{% endif %}">
{% if params.fieldset %}
{% call govukFieldset({
describedBy: describedBy,
classes: params.fieldset.classes,
attributes: params.fieldset.attributes,
legend: params.fieldset.legend
}) %}
{{ innerHtml | trim | safe }}
{% endcall %}
{% else %}
{{ innerHtml | trim | safe }}
{% endif %}
</div>

View File

@@ -1,5 +1,4 @@
{% extends "withnav_template.html" %}
{% from "components/checkbox.html" import checkbox %}
{% from "components/textbox.html" import textbox %}
{% from "components/page-header.html" import page_header %}
{% from "components/page-footer.html" import page_footer %}

View File

@@ -1,20 +1,9 @@
{% from "components/checkbox.html" import checkbox, checkboxes_nested %}
{% from "components/radios.html" import radio, radios, conditional_radio_panel %}
<fieldset class="form-group">
<legend class="form-label heading-small">
Permissions
</legend>
<span class="hint">
All team members can see sent messages.
</span>
{% for field in form.permissions_fields %}
{{ checkbox(field) }}
{% endfor %}
</fieldset>
{{ form.permissions_field }}
{% if form.folder_permissions.all_template_folders %}
{{ checkboxes_nested(form.folder_permissions, form.folder_permissions.children(), hide_legend=True, collapsible_opts={ 'field': 'folder' }) }}
{{ form.folder_permissions }}
{% elif user and user.platform_admin %}
<p class="bottom-gutter">
Platform admin users can access all template folders.

View File

@@ -1,6 +1,5 @@
{% extends "withoutnav_template.html" %}
{% from "components/textbox.html" import textbox %}
{% from "components/checkbox.html" import checkbox %}
{% from "components/page-footer.html" import page_footer %}

View File

@@ -1,6 +1,5 @@
{% extends "views/platform-admin/_base_template.html" %}
{% from "components/textbox.html" import textbox %}
{% from "components/checkbox.html" import checkbox %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/big-number.html" import big_number, big_number_with_status %}
{% from "components/message-count-label.html" import message_count_label %}
@@ -108,8 +107,7 @@
{% call form_wrapper(method="get") %}
{{ textbox(form.start_date, hint="Enter start date in format YYYY-MM-DD") }}
{{ textbox(form.end_date, hint="Enter end date in format YYYY-MM-DD") }}
{{ checkbox(form.include_from_test_key) }}
</br>
{{ form.include_from_test_key }}
{{ govukButton({ "text": "Filter" }) }}
{% endcall %}
{% endset %}

View File

@@ -1,6 +1,5 @@
{% from "components/banner.html" import banner, banner_wrapper %}
{% from "components/textbox.html" import textbox %}
{% from "components/checkbox.html" import checkbox %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}
{% from "components/button/macro.njk" import govukButton %}
@@ -55,9 +54,7 @@
hint='This should be a shared inbox managed by your team, not your own email address'
) }}
{% if not first_email_address and not existing_is_default %}
<div class="form-group">
{{ checkbox(form.is_default) }}
</div>
{{ form.is_default }}
{% endif %}
{{ page_footer('Try again') }}
{% endcall %}

View File

@@ -1,6 +1,5 @@
{% extends "withnav_template.html" %}
{% from "components/textbox.html" import textbox %}
{% from "components/checkbox.html" import checkbox %}
{% from "components/page-header.html" import page_header %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}
@@ -24,9 +23,7 @@
safe_error_message=True
) }}
{% if not first_email_address %}
<div class="form-group">
{{ checkbox(form.is_default) }}
</div>
{{ form.is_default }}
{% endif %}
{{ page_footer('Add') }}
{% endcall %}

View File

@@ -1,7 +1,6 @@
{% extends "withnav_template.html" %}
{% from "components/banner.html" import banner_wrapper %}
{% from "components/textbox.html" import textbox %}
{% from "components/checkbox.html" import checkbox %}
{% from "components/page-header.html" import page_header %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}
@@ -29,9 +28,7 @@
</p>
{{ page_footer('Save') }}
{% else %}
<div class="form-group">
{{ checkbox(form.is_default) }}
</div>
{{ form.is_default }}
{{ page_footer(
'Save',
delete_link=url_for('.service_confirm_delete_email_reply_to', service_id=current_service.id, reply_to_email_id=reply_to_email_address_id),

View File

@@ -1,6 +1,5 @@
{% extends "withnav_template.html" %}
{% from "components/textbox.html" import textbox %}
{% from "components/checkbox.html" import checkbox %}
{% from "components/page-header.html" import page_header %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}

View File

@@ -1,6 +1,5 @@
{% extends "withnav_template.html" %}
{% from "components/textbox.html" import textbox %}
{% from "components/checkbox.html" import checkbox %}
{% from "components/page-header.html" import page_header %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}
@@ -27,9 +26,7 @@
highlight_placeholders=True
) }}
{% if not first_contact_block %}
<div class="form-group">
{{ checkbox(form.is_default) }}
</div>
{{ form.is_default }}
{% endif %}
{{ page_footer('Add') }}
{% endcall %}

View File

@@ -1,6 +1,5 @@
{% extends "withnav_template.html" %}
{% from "components/textbox.html" import textbox %}
{% from "components/checkbox.html" import checkbox %}
{% from "components/page-header.html" import page_header %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}
@@ -31,9 +30,7 @@
This is currently your default address for {{ current_service.name }}.
</p>
{% else %}
<div class="form-group">
{{ checkbox(form.is_default) }}
</div>
{{ form.is_default }}
{% endif %}
{{ page_footer(

View File

@@ -1,6 +1,5 @@
{% extends "withnav_template.html" %}
{% from "components/textbox.html" import textbox %}
{% from "components/checkbox.html" import checkbox %}
{% from "components/page-header.html" import page_header %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}
@@ -23,9 +22,7 @@
hint='Up to 11 characters, letters, numbers and spaces only'
) }}
{% if not first_sms_sender %}
<div class="form-group">
{{ checkbox(form.is_default) }}
</div>
{{ form.is_default }}
{% endif %}
{{ page_footer('Save') }}
{% endcall %}

View File

@@ -1,7 +1,6 @@
{% extends "withnav_template.html" %}
{% from "components/banner.html" import banner_wrapper %}
{% from "components/textbox.html" import textbox %}
{% from "components/checkbox.html" import checkbox %}
{% from "components/page-header.html" import page_header %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}
@@ -35,9 +34,7 @@
</p>
{{ page_footer('Save') }}
{% else %}
<div class="form-group">
{{ checkbox(form.is_default) }}
</div>
{{ form.is_default }}
{% if inbound_number %}
{{ page_footer('Save') }}
{% else %}

View File

@@ -1,5 +1,4 @@
{% extends "withoutnav_template.html" %}
{% from "components/checkbox.html" import checkbox %}
{% from "components/textbox.html" import textbox %}
{% from "components/page-footer.html" import sticky_page_footer %}
{% from "components/page-header.html" import page_header %}

View File

@@ -1,4 +1,3 @@
{% from "components/checkbox.html" import unlabelled_checkbox %}
{% from "components/message-count-label.html" import folder_contents_count, message_count_label %}
{% macro format_item_name(name) -%}
@@ -21,37 +20,58 @@
{% endif %}
</p>
{% else %}
<nav id="template-list" class="{{ 'top-gutter-5px' if (not show_template_nav and not show_search_box) else '' }}">
<nav id="template-list" class="{{ 'govuk-!-margin-top-1' if (not show_template_nav and not show_search_box) else 'govuk-!margin-top-6' }}">
{% set checkboxes_data = [] %}
{% for item in template_list %}
<div class="template-list-item {% if current_user.has_permissions('manage_templates') %}template-list-item-with-checkbox{% endif %} {% if item.ancestors %}template-list-item-hidden-by-default{% endif %} {% if not item.ancestors %}template-list-item-without-ancestors{% endif %}">
{% if current_user.has_permissions('manage_templates') %}
{{ unlabelled_checkbox(
id='templates-or-folder-{}'.format(item.id),
name='templates_and_folders',
data=templates_and_folders_form.is_selected(item.id),
value=item.id,
) }}
{% set item_label_content %}
{% for ancestor in item.ancestors %}
<a href="{{ url_for('.choose_template', service_id=current_service.id, template_type=template_type, template_folder_id=ancestor.id) }}" class="govuk-link govuk-link--no-visited-state template-list-folder">
{{- format_item_name(ancestor.name) -}}
</a> <span class="message-name-separator"></span>
{% endfor %}
{% if item.is_folder %}
<a href="{{ url_for('.choose_template', service_id=current_service.id, template_type=template_type, template_folder_id=item.id) }}" class="govuk-link govuk-link--no-visited-state template-list-folder">
<span class="live-search-relevant">{{ format_item_name(item.name) }}</span>
</a>
{% else %}
<a href="{{ url_for('.view_template', service_id=current_service.id, template_id=item.id) }}" class="govuk-link govuk-link--no-visited-state template-list-template">
<span class="live-search-relevant">{{ format_item_name(item.name) }}</span>
</a>
{% endif %}
<h2 class="message-name">
{% for ancestor in item.ancestors %}
<a href="{{ url_for('.choose_template', service_id=current_service.id, template_type=template_type, template_folder_id=ancestor.id) }}" class="govuk-link govuk-link--no-visited-state template-list-folder">
{{- format_item_name(ancestor.name) -}}
</a> <span class="message-name-separator"></span>
{% endfor %}
{% if item.is_folder %}
<a href="{{ url_for('.choose_template', service_id=current_service.id, template_type=template_type, template_folder_id=item.id) }}" class="govuk-link govuk-link--no-visited-state template-list-folder">
<span class="live-search-relevant">{{ format_item_name(item.name) }}</span>
</a>
{% else %}
<a href="{{ url_for('.view_template', service_id=current_service.id, template_id=item.id) }}" class="govuk-link govuk-link--no-visited-state template-list-template">
<span class="live-search-relevant">{{ format_item_name(item.name) }}</span>
</a>
{% endif %}
</h2>
<p class="message-type">
{{ item.hint }}
</p>
</div>
{% endset %}
{# create the item config now to include the label content -#}
{% set checkbox_config = {
"html": item_label_content,
"label": {
"classes": "template-list-item-label govuk-!-font-size-24 govuk-!-font-weight-bold"
},
"id": "templates-or-folder-" ~ item.id,
"hint": {
"text": item.hint,
"classes": "template-list-item-hint"
},
"classes": "template-list-item {}".format(
"template-list-item-hidden-by-default" if item.ancestors else "template-list-item-without-ancestors")
} %}
{% set _ = checkboxes_data.append(checkbox_config) %}
{% if not current_user.has_permissions('manage_templates') %}
<div class="template-list-item {%- if item.ancestors %} template-list-item-hidden-by-default {%- else %} template-list-item-without-ancestors{%- endif %}">
<h2 class="message-name">
{{ item_label_content }}
</h2>
<p class="message-type govuk-!-margin-bottom-4">
{{ item.hint }}
</p>
</div>
{% endif %}
{% endfor %}
{% if current_user.has_permissions('manage_templates') %}
{{ templates_and_folders_form.templates_and_folders(param_extensions={ "items": checkboxes_data }) }}
{% endif %}
</nav>
{% endif %}

View File

@@ -1,7 +1,6 @@
{% extends "withnav_template.html" %}
{% from "components/folder-path.html" import folder_path, page_title_folder_path %}
{% from "components/textbox.html" import textbox %}
{% from "components/checkbox.html" import checkbox, checkboxes %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}
@@ -26,7 +25,7 @@
{% call form_wrapper(action=url_for('main.manage_template_folder', service_id=current_service.id, template_folder_id=template_folder_id)) %}
{{ textbox(form.name, width='1-1') }}
{% if current_user.has_permissions("manage_service") and form.users_with_permission.all_service_users %}
{{ checkboxes(form.users_with_permission, collapsible_opts={ 'field': 'team member' }) }}
{{ form.users_with_permission }}
{% endif %}
{{ page_footer(

View File

@@ -1,6 +1,5 @@
{% extends "withoutnav_template.html" %}
{% from "components/textbox.html" import textbox %}
{% from "components/checkbox.html" import checkbox %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}

View File

@@ -65,7 +65,12 @@ const copy = {
'footer',
'back-link',
'details',
'button'
'button',
'error-message',
'fieldset',
'hint',
'label',
'checkboxes'
];
let done = 0;

View File

@@ -259,11 +259,11 @@ def test_service_without_caseworking_doesnt_show_admin_vs_caseworker(
service_id=SERVICE_ONE_ID,
**extra_args
)
assert page.select('input[type=checkbox]')[0]['name'] == 'view_activity'
assert page.select('input[type=checkbox]')[1]['name'] == 'send_messages'
assert page.select('input[type=checkbox]')[2]['name'] == 'manage_templates'
assert page.select('input[type=checkbox]')[3]['name'] == 'manage_service'
assert page.select('input[type=checkbox]')[4]['name'] == 'manage_api_keys'
assert page.select('input[type=checkbox]')[0]['value'] == 'view_activity'
assert page.select('input[type=checkbox]')[1]['value'] == 'send_messages'
assert page.select('input[type=checkbox]')[2]['value'] == 'manage_templates'
assert page.select('input[type=checkbox]')[3]['value'] == 'manage_service'
assert page.select('input[type=checkbox]')[4]['value'] == 'manage_api_keys'
@pytest.mark.parametrize('service_has_email_auth, displays_auth_type', [
@@ -370,8 +370,8 @@ def test_should_show_page_for_one_user(
assert len(checkboxes) == 5
for index, expected in enumerate(expected_checkboxes):
expected_input_name, expected_checked = expected
assert checkboxes[index]['name'] == expected_input_name
expected_input_value, expected_checked = expected
assert checkboxes[index]['value'] == expected_input_value
assert checkboxes[index].has_attr('checked') == expected_checked
@@ -413,11 +413,13 @@ def test_should_not_show_page_for_non_team_member(
@pytest.mark.parametrize('submitted_permissions, permissions_sent_to_api', [
(
{
'view_activity': 'y',
'send_messages': 'y',
'manage_templates': 'y',
'manage_service': 'y',
'manage_api_keys': 'y',
'permissions_field': [
'view_activity',
'send_messages',
'manage_templates',
'manage_service',
'manage_api_keys',
]
},
{
'view_activity',
@@ -429,13 +431,16 @@ def test_should_not_show_page_for_non_team_member(
),
(
{
'view_activity': 'y',
'send_messages': 'y',
'manage_templates': '',
'permissions_field': [
'view_activity',
'send_messages',
'manage_templates',
]
},
{
'view_activity',
'send_messages',
'manage_templates',
}
),
(
@@ -616,10 +621,12 @@ def test_edit_user_permissions_including_authentication_with_email_auth_service(
user_id=active_user_with_permissions['id'],
_data={
'email_address': active_user_with_permissions['email_address'],
'send_messages': 'y',
'manage_templates': 'y',
'manage_service': 'y',
'manage_api_keys': 'y',
'permissions_field': [
'send_messages',
'manage_templates',
'manage_service',
'manage_api_keys',
],
'login_authentication': auth_type,
},
_expected_status=302,
@@ -678,7 +685,7 @@ def test_should_show_folder_permission_form_if_service_has_folder_permissions_en
assert 'Invite a team member' in page.find('h1').text.strip()
folder_checkboxes = page.find('div', class_='checkboxes-nested').find_all('li')
folder_checkboxes = page.find('div', class_='selection-wrapper').find_all('li')
assert len(folder_checkboxes) == 3
@@ -707,11 +714,13 @@ def test_invite_user(
service_id=SERVICE_ONE_ID,
_data={
'email_address': email_address,
'view_activity': 'y',
'send_messages': 'y',
'manage_templates': 'y',
'manage_service': 'y',
'manage_api_keys': 'y',
'permissions_field': [
'view_activity',
'send_messages',
'manage_templates',
'manage_service',
'manage_api_keys',
]
},
_follow_redirects=True,
)
@@ -762,11 +771,13 @@ def test_invite_user_with_email_auth_service(
service_id=SERVICE_ONE_ID,
_data={
'email_address': email_address,
'view_activity': 'y',
'send_messages': 'y',
'manage_templates': 'y',
'manage_service': 'y',
'manage_api_keys': 'y',
'permissions_field': [
'view_activity',
'send_messages',
'manage_templates',
'manage_service',
'manage_api_keys',
],
'login_authentication': auth_type,
},
_follow_redirects=True,

View File

@@ -2202,8 +2202,8 @@ def test_incorrect_sms_sender_input(
@pytest.mark.parametrize('reply_to_addresses, data, api_default_args', [
([], {}, True),
(create_multiple_email_reply_to_addresses(), {}, False),
(create_multiple_email_reply_to_addresses(), {"is_default": "y"}, True)
(create_multiple_email_reply_to_addresses(), {"is_default": []}, False),
(create_multiple_email_reply_to_addresses(), {"is_default": ["y"]}, True)
])
def test_add_reply_to_email_address_sends_test_notification(
mocker, client_request, reply_to_addresses, data, api_default_args
@@ -2323,8 +2323,8 @@ def test_add_reply_to_email_address_fails_if_notification_not_delivered_in_45_se
@pytest.mark.parametrize('letter_contact_blocks, data, api_default_args', [
([], {}, True), # no existing letter contact blocks
(create_multiple_letter_contact_blocks(), {}, False),
(create_multiple_letter_contact_blocks(), {"is_default": "y"}, True)
(create_multiple_letter_contact_blocks(), {"is_default": []}, False),
(create_multiple_letter_contact_blocks(), {"is_default": ["y"]}, True)
])
def test_add_letter_contact(
letter_contact_blocks,
@@ -2399,8 +2399,8 @@ def test_add_letter_contact_when_coming_from_template(
@pytest.mark.parametrize('sms_senders, data, api_default_args', [
([], {}, True),
(create_multiple_sms_senders(), {}, False),
(create_multiple_sms_senders(), {"is_default": "y"}, True)
(create_multiple_sms_senders(), {"is_default": []}, False),
(create_multiple_sms_senders(), {"is_default": ["y"]}, True)
])
def test_add_sms_sender(
sms_senders,
@@ -2466,10 +2466,10 @@ def test_default_box_doesnt_show_on_first_letter_sender(
@pytest.mark.parametrize('reply_to_address, data, api_default_args', [
(create_reply_to_email_address(is_default=True), {"is_default": "y"}, True),
(create_reply_to_email_address(is_default=True), {}, True),
(create_reply_to_email_address(is_default=False), {}, False),
(create_reply_to_email_address(is_default=False), {"is_default": "y"}, True)
(create_reply_to_email_address(is_default=True), {"is_default": ["y"]}, True),
(create_reply_to_email_address(is_default=True), {"is_default": []}, True),
(create_reply_to_email_address(is_default=False), {"is_default": []}, False),
(create_reply_to_email_address(is_default=False), {"is_default": ["y"]}, True)
])
def test_edit_reply_to_email_address_sends_verification_notification_if_address_is_changed(
reply_to_address,
@@ -2494,10 +2494,10 @@ def test_edit_reply_to_email_address_sends_verification_notification_if_address_
@pytest.mark.parametrize('reply_to_address, data, api_default_args', [
(create_reply_to_email_address(), {"is_default": "y"}, True),
(create_reply_to_email_address(), {}, True),
(create_reply_to_email_address(is_default=False), {}, False),
(create_reply_to_email_address(is_default=False), {"is_default": "y"}, True)
(create_reply_to_email_address(), {"is_default": ["y"]}, True),
(create_reply_to_email_address(), {"is_default": []}, True),
(create_reply_to_email_address(is_default=False), {"is_default": []}, False),
(create_reply_to_email_address(is_default=False), {"is_default": ["y"]}, True)
])
def test_edit_reply_to_email_address_goes_straight_to_update_if_address_not_changed(
reply_to_address,
@@ -2554,7 +2554,7 @@ def test_add_edit_reply_to_email_address_goes_straight_to_update_if_address_not_
message=error_message
)]
)
data = {"is_default": "y", 'email_address': "reply_to@example.com"}
data = {"is_default": ["y"], 'email_address': "reply_to@example.com"}
page = client_request.post(
url,
service_id=SERVICE_ONE_ID,
@@ -2654,10 +2654,10 @@ def test_delete_reply_to_email_address(
@pytest.mark.parametrize('letter_contact_block, data, api_default_args', [
(create_letter_contact_block(), {"is_default": "y"}, True),
(create_letter_contact_block(), {}, True),
(create_letter_contact_block(is_default=False), {}, False),
(create_letter_contact_block(is_default=False), {"is_default": "y"}, True)
(create_letter_contact_block(), {"is_default": ["y"]}, True),
(create_letter_contact_block(), {"is_default": []}, True),
(create_letter_contact_block(is_default=False), {"is_default": []}, False),
(create_letter_contact_block(is_default=False), {"is_default": ["y"]}, True)
])
def test_edit_letter_contact_block(
letter_contact_block,
@@ -2731,10 +2731,10 @@ def test_delete_letter_contact_block(
@pytest.mark.parametrize('sms_sender, data, api_default_args', [
(create_sms_sender(), {"is_default": "y", "sms_sender": "test"}, True),
(create_sms_sender(), {"sms_sender": "test"}, True),
(create_sms_sender(is_default=False), {"sms_sender": "test"}, False),
(create_sms_sender(is_default=False), {"is_default": "y", "sms_sender": "test"}, True)
(create_sms_sender(), {"is_default": ["y"], "sms_sender": "test"}, True),
(create_sms_sender(), {"is_default": [], "sms_sender": "test"}, True),
(create_sms_sender(is_default=False), {"is_default": [], "sms_sender": "test"}, False),
(create_sms_sender(is_default=False), {"is_default": ["y"], "sms_sender": "test"}, True)
])
def test_edit_sms_sender(
sms_sender,

View File

@@ -395,7 +395,7 @@ def test_should_show_templates_folder_page(
assert links_in_page[index].text.strip() == expected_link
all_page_items = page.select('.template-list-item')
all_page_items_styled_with_checkboxes = page.select('.template-list-item-with-checkbox')
all_page_items_styled_with_checkboxes = page.select('.template-list-item.govuk-checkboxes__item')
assert len(all_page_items) == len(all_page_items_styled_with_checkboxes)
@@ -527,7 +527,7 @@ def test_get_manage_folder_viewing_permissions_for_users(
assert normalize_spaces(page.select_one('title').text) == (
'folder_two Templates service one GOV.UK Notify'
)
form_labels = page.select('legend[class=form-label]')
form_labels = page.select('legend.govuk-fieldset__legend')
assert normalize_spaces(form_labels[0].text) == "Team members who can see this folder"
checkboxes = page.select('input[name=users_with_permission]')
@@ -538,7 +538,7 @@ def test_get_manage_folder_viewing_permissions_for_users(
assert checkboxes[1]['value'] == team_member_2['id']
assert "checked" in checkboxes[1].attrs
assert "Test User" in page.find_all('label', {'for': 'users_with_permission-0'})[0].text
assert "Test User" in page.find_all('label', {'for': 'users_with_permission-1'})[0].text
def test_get_manage_folder_viewing_permissions_for_users_not_visible_when_no_manage_settings_permission(

View File

@@ -167,7 +167,7 @@ def test_should_show_page_for_choosing_a_template(
for index, expected_link in enumerate(expected_nav_links):
assert links_in_page[index].text.strip() == expected_link
template_links = page.select('.message-name a')
template_links = page.select('#template-list .govuk-label a, .message-name a')
assert len(template_links) == len(expected_templates)
@@ -236,7 +236,7 @@ def test_should_show_live_search_if_list_of_templates_taller_than_screen(
assert search['data-module'] == 'live-search'
assert search['data-targets'] == '#template-list .template-list-item'
assert len(page.select(search['data-targets'])) == len(page.select('.message-name')) == 14
assert len(page.select(search['data-targets'])) == len(page.select('#template-list .govuk-label')) == 14
def test_should_show_live_search_if_service_has_lots_of_folders(
@@ -257,7 +257,7 @@ def test_should_show_live_search_if_service_has_lots_of_folders(
service_id=SERVICE_ONE_ID,
)
count_of_templates_and_folders = len(page.select('.message-name'))
count_of_templates_and_folders = len(page.select('#template-list .govuk-label'))
count_of_folders = len(page.select('.template-list-folder:first-child'))
count_of_templates = count_of_templates_and_folders - count_of_folders

View File

@@ -14,49 +14,52 @@ afterAll(() => {
describe('Collapsible fieldset', () => {
const _checkboxes = (start, end) => {
result = '';
for (let num = start; num <= end; num++) {
let id = `folder-permissions-${num}`;
result += `<li class="govuk-checkboxes__item">
<input class="govuk-checkboxes__input" id="${id}" name="folder-permissions" type="checkbox" value="${id}">
<label class="govuk-label govuk-checkboxes__label" for="${id}">
Folder ${id}
</label>
</li>`;
}
return result;
};
let wrapper;
let formGroup;
let fieldset;
let checkboxesContainer;
let checkboxes;
beforeEach(() => {
const _checkboxes = (start, end, descendents) => {
result = '';
for (let num = start; num <= end; num++) {
let id = `folder-permissions-${num}`;
if (!descendents) { descendents = ''; }
result += `<li class="multiple-choice">
<input id="${id}" name="folder_permissions" type="checkbox" value="${id}">
<label class="block-label" for="{id}">
Folder 18
</label>
${descendents}
</li>`;
}
return result;
};
// set up DOM
document.body.innerHTML =
`<div class="form-group" data-module="collapsible-checkboxes" data-field-label="folder">
<div class="selection-summary"></div>
<fieldset id="folder_permissions">
<legend class="form-label heading-small">
Folders this team member can see
</legend>
<div class="checkboxes-nested">
<ul>
`<div class="selection-wrapper" data-module="collapsible-checkboxes" data-field-label="folder">
<div class="govuk-form-group">
<fieldset class="govuk-fieldset" id="folder_permissions">
<legend class="govuk-fieldset__legend govuk-fieldset__legend--s">
Folders this team member can see
<span class="govuk-hint">
<div class="selection-summary" role="region" aria-live="polite"></div>
</span>
</legend>
<ul class="govuk-checkboxes">
${_checkboxes(1, 10)}
</ul>
</div>
</fieldset>
</fieldset>
</div>
</div>`;
formGroup = document.querySelector('.form-group');
wrapper = document.querySelector('.selection-wrapper');
formGroup = wrapper.querySelector('.govuk-form-group');
fieldset = formGroup.querySelector('fieldset');
checkboxesContainer = fieldset.querySelector('.checkboxes-nested');
checkboxesContainer = fieldset.querySelector('.govuk-checkboxes');
checkboxes = checkboxesContainer.querySelectorAll('input[type=checkbox]');
});
@@ -141,6 +144,12 @@ describe('Collapsible fieldset', () => {
});
test("removes the hint", () => {
expect(document.querySelector('.govuk-hint')).toBeNull();
});
});
test('has the right summary text when started with no checkboxes selected', () => {
@@ -187,7 +196,7 @@ describe('Collapsible fieldset', () => {
test("the summary doesn't have a folder icon if fields aren't called 'folder'", () => {
formGroup.dataset.fieldLabel = 'team member';
wrapper.dataset.fieldLabel = 'team member';
// start module
window.GOVUK.modules.start();
@@ -277,35 +286,74 @@ describe('Collapsible fieldset', () => {
describe("the footer (that wraps the button)", () => {
beforeEach(() => {
describe("is inserted", () => {
// track calls to sticky JS
window.GOVUK.stickAtBottomWhenScrolling.recalculate = jest.fn(() => {});
test("after the fieldset", () => {
// start module
window.GOVUK.modules.start();
// start module
window.GOVUK.modules.start();
// show the checkboxes
helpers.triggerEvent(formGroup.querySelector('.govuk-button'), 'click');
// show the checkboxes
helpers.triggerEvent(formGroup.querySelector('.govuk-button'), 'click');
expect(formGroup.querySelector('.selection-footer').previousElementSibling.nodeName).toBe('FIELDSET');
});
test("after the root fieldset if the checkboxes are nested", () => {
// add a nested list of checkboxes to the first checkbox item
const nestedCheckboxes = document.createElement('div');
nestedCheckboxes.className = 'govuk-form-group govuk-form-group--nested';
nestedCheckboxes.innerHTML = _checkboxes(11, 20);
checkboxesContainer.querySelector('.govuk-checkboxes__item').appendChild(nestedCheckboxes);
// start module
window.GOVUK.modules.start();
// show the checkboxes
helpers.triggerEvent(formGroup.querySelector('.govuk-button'), 'click');
expect(formGroup.querySelector('.selection-footer').previousElementSibling.nodeName).toBe('FIELDSET');
});
});
test("is made sticky when the fieldset is expanded", () => {
describe("its stickiness", () => {
expect(formGroup.querySelector('.selection-footer').classList.contains('js-stick-at-bottom-when-scrolling')).toBe(true);
expect(window.GOVUK.stickAtBottomWhenScrolling.recalculate.mock.calls.length).toBe(1);
beforeEach(() => {
// track calls to sticky JS
window.GOVUK.stickAtBottomWhenScrolling.recalculate = jest.fn(() => {});
// start module
window.GOVUK.modules.start();
// show the checkboxes
helpers.triggerEvent(formGroup.querySelector('.govuk-button'), 'click');
});
test("is added when the fieldset is expanded", () => {
expect(formGroup.querySelector('.selection-footer').classList.contains('js-stick-at-bottom-when-scrolling')).toBe(true);
expect(window.GOVUK.stickAtBottomWhenScrolling.recalculate.mock.calls.length).toBe(1);
});
test("is removed when the fieldset is collapsed", () => {
// click the button to collapse the fieldset
helpers.triggerEvent(formGroup.querySelector('.govuk-button'), 'click');
expect(formGroup.querySelector('.selection-footer').classList.contains('js-stick-at-bottom-when-scrolling')).toBe(false);
expect(window.GOVUK.stickAtBottomWhenScrolling.recalculate.mock.calls.length).toBe(2);
});
});
test("has its stickiness removed when the fieldset is collapsed", () => {
// click the button to collapse the fieldset
helpers.triggerEvent(formGroup.querySelector('.govuk-button'), 'click');
expect(formGroup.querySelector('.selection-footer').classList.contains('js-stick-at-bottom-when-scrolling')).toBe(false);
expect(window.GOVUK.stickAtBottomWhenScrolling.recalculate.mock.calls.length).toBe(2);
});
});
describe("when the selection changes", () => {
@@ -339,7 +387,7 @@ describe('Collapsible fieldset', () => {
test("if fields are called 'folders'", () => {
formGroup.dataset.fieldLabel = 'folder';
wrapper.dataset.fieldLabel = 'folder';
checkFirstCheckbox();
@@ -359,7 +407,7 @@ describe('Collapsible fieldset', () => {
test("if fields are called 'team member'", () => {
formGroup.dataset.fieldLabel = 'team member';
wrapper.dataset.fieldLabel = 'team member';
checkFirstCheckbox();
@@ -379,7 +427,7 @@ describe('Collapsible fieldset', () => {
test("if fields are called 'arbitrary thing'", () => {
formGroup.dataset.fieldLabel = 'arbitrary thing';
wrapper.dataset.fieldLabel = 'arbitrary thing';
checkFirstCheckbox();
@@ -403,7 +451,7 @@ describe('Collapsible fieldset', () => {
test("if fields are called 'folder'", () => {
formGroup.dataset.fieldLabel = 'folder';
wrapper.dataset.fieldLabel = 'folder';
checkAllCheckboxes();
@@ -423,7 +471,7 @@ describe('Collapsible fieldset', () => {
test("if fields are called 'team member'", () => {
formGroup.dataset.fieldLabel = 'team member';
wrapper.dataset.fieldLabel = 'team member';
checkAllCheckboxes();
@@ -447,7 +495,7 @@ describe('Collapsible fieldset', () => {
test("if fields are called 'folder'", () => {
formGroup.dataset.fieldLabel = 'folder';
wrapper.dataset.fieldLabel = 'folder';
checkAllCheckboxesButTheLast();
@@ -466,7 +514,7 @@ describe('Collapsible fieldset', () => {
test("if fields are called 'team member'", () => {
formGroup.dataset.fieldLabel = 'team member';
wrapper.dataset.fieldLabel = 'team member';
checkAllCheckboxesButTheLast();