mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-08-20 22:40:31 -04:00
Compare commits
13 Commits
2595-state
...
2489-clean
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2905fa9426 | ||
|
|
f98884b858 | ||
|
|
19bc1b9aee | ||
|
|
53034b7e89 | ||
|
|
81d4fc0b8c | ||
|
|
0b441e36f1 | ||
|
|
1bf7e58445 | ||
|
|
487866d81c | ||
|
|
b71a941634 | ||
|
|
c599407850 | ||
|
|
42ac6912b0 | ||
|
|
6f6fd01ea2 | ||
|
|
9921809f90 |
@@ -1,27 +0,0 @@
|
||||
(function(Modules) {
|
||||
"use strict";
|
||||
|
||||
Modules.Autofocus = function() {
|
||||
this.start = function(component) {
|
||||
var $component = $(component),
|
||||
forceFocus = $component.data('forceFocus');
|
||||
|
||||
// if the page loads with a scroll position, we can't assume the item to focus onload
|
||||
// is still where users intend to start
|
||||
if (($(window).scrollTop() > 0) && !forceFocus) { return; }
|
||||
|
||||
// See if the component itself is something we want to send focus to
|
||||
var target = $component.filter('input, textarea, select');
|
||||
|
||||
// Otherwise look inside the component to see if there are any elements
|
||||
// we want to send focus to
|
||||
if (target.length === 0) {
|
||||
target = $('input, textarea, select', $component);
|
||||
}
|
||||
|
||||
target.eq(0).trigger('focus');
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
})(window.GOVUK.Modules);
|
||||
@@ -1,29 +0,0 @@
|
||||
(function(Modules) {
|
||||
"use strict";
|
||||
|
||||
let isSixDigitHex = value => value.match(/^#[0-9A-F]{6}$/i);
|
||||
let colourOrWhite = value => isSixDigitHex(value) ? value : '#FFFFFF';
|
||||
|
||||
Modules.ColourPreview = function() {
|
||||
|
||||
this.start = component => {
|
||||
|
||||
this.$input = $(component);
|
||||
|
||||
this.$input.closest('.usa-form-group').append(
|
||||
this.$preview = $('<span class="textbox-colour-preview"></span>')
|
||||
);
|
||||
|
||||
this.$input
|
||||
.on('input', this.update)
|
||||
.trigger('input');
|
||||
|
||||
};
|
||||
|
||||
this.update = () => this.$preview.css(
|
||||
'background', colourOrWhite(this.$input.val())
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
})(window.GOVUK.Modules);
|
||||
@@ -1,124 +1,41 @@
|
||||
(function(Modules) {
|
||||
"use strict";
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
Modules.FullscreenTable = function() {
|
||||
function initFullscreenTables() {
|
||||
const tables = document.querySelectorAll('.fullscreen-table');
|
||||
|
||||
this.start = function(component) {
|
||||
tables.forEach((component) => {
|
||||
const table = component.querySelector('table');
|
||||
const wrapper = component.querySelector('.table-wrapper');
|
||||
const rightShadow = component.querySelector('.fullscreen-right-shadow');
|
||||
|
||||
this.$component = $(component);
|
||||
this.$table = this.$component.find('table');
|
||||
this.nativeHeight = this.$component.innerHeight() + 20; // 20px to allow room for scrollbar
|
||||
this.topOffset = this.$component.offset().top;
|
||||
if (!table || !wrapper) return;
|
||||
|
||||
this.insertShims();
|
||||
this.maintainWidth();
|
||||
this.maintainHeight();
|
||||
this.toggleShadows();
|
||||
const toggleShadows = () => {
|
||||
const scrollLeft = wrapper.scrollLeft;
|
||||
const maxScrollLeft = wrapper.scrollWidth - wrapper.clientWidth;
|
||||
|
||||
$(window)
|
||||
.on('scroll resize', this.maintainHeight)
|
||||
.on('resize', this.maintainWidth);
|
||||
wrapper.classList.toggle('scrolled', scrollLeft > 0);
|
||||
|
||||
this.$scrollableTable
|
||||
.on('scroll', this.toggleShadows)
|
||||
.on('scroll', this.maintainHeight)
|
||||
.on('focus blur', () => this.$component.toggleClass('js-focus-style'));
|
||||
if (rightShadow) {
|
||||
rightShadow.classList.toggle('visible', scrollLeft < maxScrollLeft);
|
||||
}
|
||||
};
|
||||
|
||||
if (
|
||||
window.GOVUK.stickAtBottomWhenScrolling &&
|
||||
window.GOVUK.stickAtBottomWhenScrolling.recalculate
|
||||
) {
|
||||
window.GOVUK.stickAtBottomWhenScrolling.recalculate();
|
||||
}
|
||||
wrapper.addEventListener('scroll', toggleShadows);
|
||||
toggleShadows(); // init state
|
||||
});
|
||||
}
|
||||
|
||||
this.maintainWidth();
|
||||
// Auto-init on page load in browser
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initFullscreenTables);
|
||||
} else {
|
||||
initFullscreenTables();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
this.insertShims = () => {
|
||||
|
||||
const attributesForFocus = 'role aria-labelledby tabindex';
|
||||
let captionId = this.$table.find('caption').text().toLowerCase().replace(/[^A-Za-z]+/g, '');
|
||||
|
||||
this.$table.find('caption').attr('id', captionId);
|
||||
this.$table.wrap(`<div class="fullscreen-scrollable-table" role="region" aria-labelledby="${captionId}" tabindex="0"/>`);
|
||||
|
||||
this.$component
|
||||
.append(
|
||||
this.$component.find('.fullscreen-scrollable-table')
|
||||
.clone()
|
||||
.addClass('fullscreen-fixed-table')
|
||||
.removeClass('fullscreen-scrollable-table')
|
||||
.removeAttr(attributesForFocus)
|
||||
.attr('aria-hidden', true)
|
||||
.find('caption')
|
||||
.removeAttr('id')
|
||||
.closest('.fullscreen-fixed-table')
|
||||
)
|
||||
.append(
|
||||
'<div class="fullscreen-right-shadow" />'
|
||||
)
|
||||
.after(
|
||||
$("<div class='fullscreen-shim'/>").css({
|
||||
'height': this.nativeHeight,
|
||||
'top': this.topOffset
|
||||
})
|
||||
)
|
||||
.css('position', 'absolute');
|
||||
|
||||
this.$scrollableTable = this.$component.find('.fullscreen-scrollable-table');
|
||||
this.$fixedTable = this.$component.find('.fullscreen-fixed-table');
|
||||
|
||||
};
|
||||
|
||||
this.maintainHeight = () => {
|
||||
|
||||
let height = Math.min(
|
||||
$(window).height() - this.topOffset + $(window).scrollTop(),
|
||||
this.nativeHeight
|
||||
);
|
||||
|
||||
this.$scrollableTable.outerHeight(height);
|
||||
this.$fixedTable.outerHeight(height);
|
||||
|
||||
};
|
||||
|
||||
this.maintainWidth = () => {
|
||||
|
||||
let indexColumnWidth = this.$fixedTable.find('.table-field-index').outerWidth();
|
||||
|
||||
this.$scrollableTable
|
||||
.css({
|
||||
'width': this.$component.parent('main').width() - indexColumnWidth,
|
||||
'margin-left': indexColumnWidth
|
||||
});
|
||||
|
||||
this.$fixedTable
|
||||
.width(indexColumnWidth + 4);
|
||||
|
||||
};
|
||||
|
||||
this.toggleShadows = () => {
|
||||
|
||||
this.$fixedTable
|
||||
.toggleClass(
|
||||
'fullscreen-scrolled-table',
|
||||
this.$scrollableTable.scrollLeft() > 0
|
||||
);
|
||||
|
||||
this.$component.find('.fullscreen-right-shadow')
|
||||
.toggleClass(
|
||||
'visible',
|
||||
this.$scrollableTable.scrollLeft() < (this.$table.width() - this.$scrollableTable.width())
|
||||
);
|
||||
|
||||
setTimeout(
|
||||
() => this.$component.find('.fullscreen-right-shadow').addClass('with-transition'),
|
||||
3000
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
})(window.GOVUK.Modules);
|
||||
// Expose it globally in test environments
|
||||
if (typeof window !== 'undefined') {
|
||||
window.initFullscreenTables = initFullscreenTables;
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
(function(window) {
|
||||
"use strict";
|
||||
|
||||
window.GOVUK.Modules.Homepage = function() {
|
||||
|
||||
this.start = function(component) {
|
||||
|
||||
let $component = $(component);
|
||||
let iterations = 0;
|
||||
let timeout = null;
|
||||
|
||||
$component.on('click', () => {
|
||||
if (++iterations == 5) {
|
||||
$component.toggleClass('product-page-intro-wrapper--alternative');
|
||||
}
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => iterations = 0, 1500);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
})(window);
|
||||
@@ -1,27 +1,38 @@
|
||||
window.GOVUK.Frontend.initAll();
|
||||
|
||||
$(() => $("time.timeago").timeago());
|
||||
// Initialize GOV.UK Frontend components (modern init)
|
||||
window.GOVUK.Frontend?.initAll?.();
|
||||
|
||||
var showHideContent = new GOVUK.ShowHideContent();
|
||||
showHideContent.init();
|
||||
|
||||
$(() => GOVUK.modules.start());
|
||||
|
||||
$(() => $('.error-message, .usa-error-message').eq(0).parent('label').next('input').trigger('focus'));
|
||||
|
||||
$(() => $('.govuk-header__container').on('click', function() {
|
||||
$(this).css('border-color', '#005ea5');
|
||||
}));
|
||||
|
||||
// Applies our expanded focus style to the siblings of links when that link is wrapped in a heading.
|
||||
//
|
||||
// This will be possible in CSS in the future, using the :has pseudo-class. When :has is available
|
||||
// in the browsers we support, this code can be replaced with a CSS-only solution.
|
||||
$('.js-mark-focus-on-parent').on('focus blur', '*', e => {
|
||||
$target = $(e.target);
|
||||
if (e.type === 'focusin') {
|
||||
$target.parent().addClass('js-child-has-focus');
|
||||
} else {
|
||||
$target.parent().removeClass('js-child-has-focus');
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Autofocus the first error input
|
||||
const errorEl = document.querySelector('.error-message, .usa-error-message');
|
||||
if (errorEl) {
|
||||
const label = errorEl.closest('label');
|
||||
if (label) {
|
||||
const input = label.nextElementSibling;
|
||||
if (input && input.focus) {
|
||||
input.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Optional: highlight header on click
|
||||
const header = document.querySelector('.govuk-header__container');
|
||||
if (header) {
|
||||
header.addEventListener('click', () => {
|
||||
header.style.borderColor = '#005ea5';
|
||||
});
|
||||
}
|
||||
|
||||
// Focus styling for heading-wrapped links
|
||||
const containers = document.querySelectorAll('.js-mark-focus-on-parent');
|
||||
containers.forEach(container => {
|
||||
container.addEventListener('focusin', e => {
|
||||
e.target.parentElement.classList.add('js-child-has-focus');
|
||||
});
|
||||
container.addEventListener('focusout', e => {
|
||||
e.target.parentElement.classList.remove('js-child-has-focus');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -174,8 +174,10 @@ h2.recipient-list {
|
||||
}
|
||||
|
||||
.spreadsheet {
|
||||
|
||||
margin-bottom: units (1);
|
||||
margin-bottom: units(1);
|
||||
overflow-x: auto;
|
||||
position: relative;
|
||||
padding-left: 0;
|
||||
|
||||
.table {
|
||||
margin-bottom: 0;
|
||||
@@ -186,27 +188,56 @@ h2.recipient-list {
|
||||
}
|
||||
|
||||
th,
|
||||
.table-field-index {
|
||||
background: color('gray-cool-10');
|
||||
td {
|
||||
padding-inline: 10px;
|
||||
border: 1px solid color('gray-cool-10');
|
||||
background: white;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
td {
|
||||
min-width: calc(194px - 11px);
|
||||
|
||||
&:first-child {
|
||||
min-width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
th.table-field-heading {
|
||||
background: color('gray-cool-10');
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
border: 1px solid color('gray-cool-10');
|
||||
}
|
||||
.table-field-index,
|
||||
.table-field-heading-first {
|
||||
position: sticky;
|
||||
left: 0;
|
||||
z-index: 10;
|
||||
background: white;
|
||||
text-align: center;
|
||||
width: units(2);
|
||||
|
||||
td {
|
||||
border-top: 0;
|
||||
// 194 is the width of the table * 1/3.5, so the overflow cuts off
|
||||
// at 3.5 columns wide.
|
||||
// 11 accounts for the padding of the table cell
|
||||
min-width: 194px - 11px;
|
||||
&:first-child {
|
||||
min-width: auto;
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 0px;
|
||||
z-index: 11;
|
||||
outline: 1px solid color('gray-cool-10');
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
width: 1px;
|
||||
background: color('gray-cool-10');
|
||||
z-index: 11;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,3 +375,9 @@ h2.recipient-list {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.usa-search__input, [type=search] {
|
||||
border-right: 1px solid;
|
||||
height: 40px;
|
||||
margin-top: units(1);
|
||||
}
|
||||
|
||||
@@ -722,7 +722,6 @@ details form {
|
||||
.placeholder,
|
||||
.placeholder-conditional {
|
||||
background-color: #face00;
|
||||
border: 1px solid #face00;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
show=False,
|
||||
form=None,
|
||||
label=None,
|
||||
autofocus=False
|
||||
autofocus=True
|
||||
) %}
|
||||
{%- set search_label = label or form.search.label.text %}
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
} %}
|
||||
|
||||
{% if autofocus %}
|
||||
{% set x=param_extensions.__setitem__("attributes", {"data-module": "autofocus"}) %}
|
||||
{% set _ = param_extensions.__setitem__("attributes", {"autofocus": "autofocus"}) %}
|
||||
{% endif %}
|
||||
|
||||
{% if show %}
|
||||
<div class="live-search js-header margin-top-0" data-module="live-search" data-targets="{{ target_selector }}">
|
||||
<div class="live-search js-header" data-targets="{{ target_selector }}">
|
||||
{{ form.search(param_extensions=param_extensions) }}
|
||||
<div aria-live="polite" class="live-search__status usa-sr-only"></div>
|
||||
</div>
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
{{notification.key_name}}
|
||||
</span>
|
||||
<span class="grid-col-6 api-notifications-item__meta-time">
|
||||
<time class="timeago" datetime="{{ notification.created_at }}">
|
||||
<time datetime="{{ notification.created_at }}">
|
||||
{{ notification.created_at|format_delta }}
|
||||
</time>
|
||||
</span>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{% extends "withnav_template.html" %}
|
||||
{% from "components/banner.html" import banner_wrapper %}
|
||||
{% from "components/radios.html" import radio_select %}
|
||||
{% from "components/table.html" import list_table, field, text_field, index_field, hidden_field_heading %}
|
||||
{% from "components/table.html" import list_table, field, text_field, index_field, index_field_heading %}
|
||||
{% from "components/file-upload.html" import file_upload %}
|
||||
{% from "components/components/back-link/macro.njk" import usaBackLink %}
|
||||
|
||||
@@ -161,18 +161,15 @@ recipients.column_headers %}
|
||||
|
||||
<h2 class="font-body-lg" id="file-preview">{{ original_file_name }}</h2>
|
||||
|
||||
<div class="fullscreen-content" data-module="fullscreen-table">
|
||||
<div class="table-wrapper spreadsheet" data-module="fullscreen-table">
|
||||
{% call(item, row_number) list_table(
|
||||
recipients.displayed_rows,
|
||||
caption=original_file_name,
|
||||
caption_visible=False,
|
||||
field_headings=[
|
||||
'<span class="usa-sr-only">Row in file</span><span aria-hidden="true">1</span>'|safe
|
||||
] + column_headers
|
||||
) %}
|
||||
field_headings=[''] + column_headers )%}
|
||||
{% call index_field() %}
|
||||
<span>
|
||||
{% set displayed_index = item.index + 2 %}
|
||||
{% set displayed_index = item.index + 1 %}
|
||||
{{ displayed_index }}
|
||||
</span>
|
||||
{% endcall %}
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fullscreen-content" data-module="fullscreen-table">
|
||||
<div class="table-wrapper spreadsheet" data-module="fullscreen-table">
|
||||
{% call(item, row_number) mapping_table(
|
||||
caption="Errors in " + original_file_name,
|
||||
caption_visible=False,
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
<p class="hint">This person has never logged in</p>
|
||||
{% else %}
|
||||
<p>Last logged in
|
||||
<time class="timeago" datetime="{{ user.logged_in_at }}">
|
||||
<time datetime="{{ user.logged_in_at }}">
|
||||
{{ user.logged_in_at|format_delta }}
|
||||
</time>
|
||||
</p>
|
||||
|
||||
@@ -30,13 +30,13 @@
|
||||
</div>
|
||||
|
||||
{% if show_search_box %}
|
||||
<div data-module="autofocus">
|
||||
<div>
|
||||
{{ live_search(target_selector='.user-list-item', show=True, form=form) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="user-list">
|
||||
{% for user in users %}
|
||||
<div class="user-list">
|
||||
{% if user.status != 'cancelled' %}
|
||||
<div class="user-list-item">
|
||||
<h2 class="user-list-item-heading font-body-lg margin-top-0" title="{{ user.email_address }}">
|
||||
@@ -101,7 +101,7 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -24,13 +24,13 @@
|
||||
</h1>
|
||||
|
||||
{% if show_search_box %}
|
||||
<div data-module="autofocus">
|
||||
<div>
|
||||
{{ live_search(target_selector='.user-list-item', show=True, form=form) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="user-list">
|
||||
{% for user in users %}
|
||||
<div class="user-list">
|
||||
<div class="user-list-item">
|
||||
<div class="grid-row">
|
||||
<div class="grid-col-9">
|
||||
@@ -60,8 +60,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="js-stick-at-bottom-when-scrolling">
|
||||
{{ usaButton({
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
{% from "components/table.html" import list_table, text_field, index_field, index_field_heading %}
|
||||
{% from "components/components/back-link/macro.njk" import usaBackLink %}
|
||||
<script type="text/javascript" src="{{ asset_url('js/setTimezone.js') }}"></script>
|
||||
import { FullscreenTable } from './fullscreenTable.js';
|
||||
|
||||
{% block service_page_title %}
|
||||
Upload your bulk-sending spreadsheet
|
||||
@@ -71,7 +72,7 @@
|
||||
|
||||
<h2 class="font-body-lg">A spreadsheet is available to use</h2>
|
||||
|
||||
<div class="spreadsheet" data-module="fullscreen-table">
|
||||
<div class="table-wrapper spreadsheet" data-module="fullscreen-table">
|
||||
{% call(item, row_number) list_table(
|
||||
example,
|
||||
caption="Example",
|
||||
|
||||
@@ -47,7 +47,6 @@ const javascripts = () => {
|
||||
paths.npm + 'hogan.js/dist/hogan-3.0.2.js',
|
||||
paths.npm + 'jquery/dist/jquery.min.js',
|
||||
paths.npm + 'query-command-supported/dist/queryCommandSupported.min.js',
|
||||
paths.npm + 'timeago/jquery.timeago.js',
|
||||
paths.npm + 'textarea-caret/index.js',
|
||||
paths.npm + 'cbor-js/cbor.js',
|
||||
paths.npm + 'd3/dist/d3.min.js',
|
||||
@@ -58,7 +57,6 @@ const javascripts = () => {
|
||||
paths.toolkit + 'javascripts/govuk/modules.js',
|
||||
paths.toolkit + 'javascripts/govuk/show-hide-content.js',
|
||||
paths.src + 'javascripts/copyToClipboard.js',
|
||||
paths.src + 'javascripts/autofocus.js',
|
||||
paths.src + 'javascripts/enhancedTextbox.js',
|
||||
paths.src + 'javascripts/fileUpload.js',
|
||||
paths.src + 'javascripts/radioSelect.js',
|
||||
@@ -68,13 +66,11 @@ const javascripts = () => {
|
||||
paths.src + 'javascripts/errorTracking.js',
|
||||
paths.src + 'javascripts/preventDuplicateFormSubmissions.js',
|
||||
paths.src + 'javascripts/fullscreenTable.js',
|
||||
paths.src + 'javascripts/colourPreview.js',
|
||||
paths.src + 'javascripts/templateFolderForm.js',
|
||||
paths.src + 'javascripts/collapsibleCheckboxes.js',
|
||||
paths.src + 'javascripts/radioSlider.js',
|
||||
paths.src + 'javascripts/updateStatus.js',
|
||||
paths.src + 'javascripts/errorBanner.js',
|
||||
paths.src + 'javascripts/homepage.js',
|
||||
paths.src + 'javascripts/timeoutPopup.js',
|
||||
paths.src + 'javascripts/date.js',
|
||||
paths.src + 'javascripts/loginAlert.js',
|
||||
|
||||
9
package-lock.json
generated
9
package-lock.json
generated
@@ -26,7 +26,6 @@
|
||||
"query-command-supported": "1.0.0",
|
||||
"sass-embedded": "^1.86.3",
|
||||
"textarea-caret": "3.1.0",
|
||||
"timeago": "1.6.7",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"vinyl-source-stream": "^2.0.0"
|
||||
},
|
||||
@@ -12680,14 +12679,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/timeago": {
|
||||
"version": "1.6.7",
|
||||
"resolved": "https://registry.npmjs.org/timeago/-/timeago-1.6.7.tgz",
|
||||
"integrity": "sha512-FikcjN98+ij0siKH4VO4dZ358PR3oDDq4Vdl1+sN9gWz1/+JXGr3uZbUShYH/hL7bMhcTpPbplJU5Tej4b4jbQ==",
|
||||
"dependencies": {
|
||||
"jquery": ">=1.5.0 <4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tmpl": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
|
||||
|
||||
@@ -42,7 +42,6 @@
|
||||
"query-command-supported": "1.0.0",
|
||||
"sass-embedded": "^1.86.3",
|
||||
"textarea-caret": "3.1.0",
|
||||
"timeago": "1.6.7",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"vinyl-source-stream": "^2.0.0"
|
||||
},
|
||||
|
||||
2
poetry.lock
generated
2
poetry.lock
generated
@@ -18,7 +18,6 @@ version = "5.0.1"
|
||||
description = "Timeout context manager for asyncio programs"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"},
|
||||
{file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"},
|
||||
@@ -1551,7 +1550,6 @@ version = "3.0.2"
|
||||
description = "Safely add untrusted strings to HTML/XML markup."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main", "dev"]
|
||||
files = [
|
||||
{file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"},
|
||||
{file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"},
|
||||
|
||||
@@ -18,6 +18,12 @@ Object.defineProperty(HTMLElement.prototype, 'clientWidth', {
|
||||
});
|
||||
|
||||
// beforeAll hook to set up the DOM and load D3.js script
|
||||
beforeAll(() => {
|
||||
jest.spyOn(Intl, 'DateTimeFormat').mockImplementation(() => ({
|
||||
resolvedOptions: () => ({ timeZone: 'UTC' })
|
||||
}));
|
||||
});
|
||||
|
||||
beforeAll(done => {
|
||||
// Set up the DOM with the D3 script included
|
||||
document.body.innerHTML = `
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
const helpers = require('./support/helpers.js');
|
||||
|
||||
beforeAll(() => {
|
||||
require('../../app/assets/javascripts/autofocus.js');
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
require('./support/teardown.js');
|
||||
});
|
||||
|
||||
describe('Autofocus', () => {
|
||||
|
||||
const labelText = 'Search by name';
|
||||
let focusHandler;
|
||||
let search;
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
document.title = 'Find services by name - GOV.UK Notify';
|
||||
|
||||
// set up DOM
|
||||
document.body.innerHTML =
|
||||
`<div id="wrapper">
|
||||
<label class="form-label" for="search">
|
||||
${labelText}
|
||||
</label>
|
||||
<input autocomplete="off" class="form-control form-control-1-1" id="search" name="search" type="search" value="" data-module="autofocus">
|
||||
</div>`;
|
||||
|
||||
focusHandler = jest.fn();
|
||||
search = document.getElementById('search');
|
||||
search.addEventListener('focus', focusHandler, false);
|
||||
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
document.body.innerHTML = '';
|
||||
search.removeEventListener('focus', focusHandler);
|
||||
focusHandler = null;
|
||||
|
||||
});
|
||||
|
||||
test('is focused when modules start', () => {
|
||||
|
||||
// start module
|
||||
window.GOVUK.modules.start();
|
||||
|
||||
expect(focusHandler).toHaveBeenCalled();
|
||||
|
||||
});
|
||||
|
||||
test('is focused when attribute is set on outer element', () => {
|
||||
|
||||
document.getElementById('search').removeAttribute('data-module');
|
||||
document.getElementById('wrapper').setAttribute('data-module', 'autofocus');
|
||||
|
||||
// start module
|
||||
window.GOVUK.modules.start();
|
||||
|
||||
expect(focusHandler).toHaveBeenCalled();
|
||||
|
||||
});
|
||||
|
||||
test('is not focused if the window has scrolled', () => {
|
||||
|
||||
// mock the window being scrolled 25px
|
||||
$.prototype.scrollTop = jest.fn(() => 25);
|
||||
|
||||
// start module
|
||||
window.GOVUK.modules.start();
|
||||
|
||||
expect(focusHandler).not.toHaveBeenCalled();
|
||||
|
||||
});
|
||||
|
||||
test('is focused if the window has scrolled but the force-focus flag is set', () => {
|
||||
|
||||
// mock the window being scrolled 25px
|
||||
$.prototype.scrollTop = jest.fn(() => 25);
|
||||
|
||||
// set the force-focus flag
|
||||
document.querySelector('#search').setAttribute('data-force-focus', true);
|
||||
|
||||
// start module
|
||||
window.GOVUK.modules.start();
|
||||
|
||||
expect(focusHandler).toHaveBeenCalled();
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,139 +0,0 @@
|
||||
const helpers = require('./support/helpers.js');
|
||||
|
||||
beforeAll(() => {
|
||||
require('../../app/assets/javascripts/colourPreview.js');
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
require('./support/teardown.js');
|
||||
});
|
||||
|
||||
describe('Colour preview', () => {
|
||||
|
||||
let field;
|
||||
let textbox;
|
||||
let swatchEl;
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
// set up DOM
|
||||
document.body.innerHTML = `
|
||||
<div class="usa-form-group">
|
||||
<label class="govuk-form-label" for="colour">
|
||||
Colour
|
||||
</label>
|
||||
<input class="govuk-input govuk-input--width-6" id="colour" name="colour" rows="8" type="text" value="" data-module="colour-preview">
|
||||
</div>`;
|
||||
|
||||
field = document.querySelector('.usa-form-group');
|
||||
textbox = document.querySelector('input[type=text]');
|
||||
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
document.body.innerHTML = '';
|
||||
|
||||
});
|
||||
|
||||
describe("When the page loads", () => {
|
||||
|
||||
test("It should add a swatch element for the preview", () => {
|
||||
|
||||
// start the module
|
||||
window.GOVUK.modules.start();
|
||||
|
||||
swatchEl = document.querySelector('.textbox-colour-preview');
|
||||
|
||||
expect(swatchEl).not.toBeNull();
|
||||
|
||||
});
|
||||
|
||||
test("If the textbox is empty it should make the swatch white", () => {
|
||||
|
||||
// start the module
|
||||
window.GOVUK.modules.start();
|
||||
|
||||
swatchEl = document.querySelector('.textbox-colour-preview');
|
||||
|
||||
// textbox defaults to empty
|
||||
// colours are output in RGB
|
||||
expect(swatchEl.style.background).toEqual('rgb(255, 255, 255)');
|
||||
|
||||
});
|
||||
|
||||
test("If the textbox has a value which is a hex code it should add that colour to the swatch", () => {
|
||||
|
||||
textbox.setAttribute('value', '#00FF00');
|
||||
|
||||
// start the module
|
||||
window.GOVUK.modules.start();
|
||||
|
||||
swatchEl = document.querySelector('.textbox-colour-preview');
|
||||
|
||||
// colours are output in RGB
|
||||
expect(swatchEl.style.background).toEqual('rgb(0, 255, 0)');
|
||||
|
||||
});
|
||||
|
||||
test("If the textbox has a value which isn't a hex code it should make the swatch white", () => {
|
||||
|
||||
textbox.setAttribute('value', 'green');
|
||||
|
||||
// start the module
|
||||
window.GOVUK.modules.start();
|
||||
|
||||
swatchEl = document.querySelector('.textbox-colour-preview');
|
||||
|
||||
// colours are output in RGB
|
||||
expect(swatchEl.style.background).toEqual('rgb(255, 255, 255)');
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("When input is added to the textbox", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
// start the module
|
||||
window.GOVUK.modules.start();
|
||||
|
||||
swatchEl = document.querySelector('.textbox-colour-preview');
|
||||
|
||||
});
|
||||
|
||||
test("If the textbox is empty it should make the swatch white", () => {
|
||||
|
||||
helpers.triggerEvent(document.querySelector('input[type=text]'), 'input');
|
||||
|
||||
// textbox defaults to empty
|
||||
expect(swatchEl.style.background).toEqual('rgb(255, 255, 255)');
|
||||
|
||||
});
|
||||
|
||||
test("If the textbox has a value which is a hex code it should add that colour to the swatch", () => {
|
||||
|
||||
textbox.setAttribute('value', '#00FF00');
|
||||
|
||||
helpers.triggerEvent(document.querySelector('input[type=text]'), 'input');
|
||||
|
||||
// textbox defaults to empty
|
||||
expect(swatchEl.style.background).toEqual('rgb(0, 255, 0)');
|
||||
|
||||
});
|
||||
|
||||
test("If the textbox has a value which isn't a hex code it should make the swatch white", () => {
|
||||
|
||||
textbox.setAttribute('value', 'green');
|
||||
|
||||
helpers.triggerEvent(document.querySelector('input[type=text]'), 'input');
|
||||
|
||||
// textbox defaults to empty
|
||||
expect(swatchEl.style.background).toEqual('rgb(255, 255, 255)');
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,370 +1,88 @@
|
||||
const helpers = require('./support/helpers');
|
||||
require('../../app/assets/javascripts/fullscreenTable.js');
|
||||
|
||||
beforeAll(() => {
|
||||
// TODO: remove this when tests for sticky JS are written
|
||||
require('../../app/assets/javascripts/stick-to-window-when-scrolling.js');
|
||||
describe('fullscreenTable', () => {
|
||||
let wrapper, rightShadow;
|
||||
|
||||
require('../../app/assets/javascripts/fullscreenTable.js');
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
require('./support/teardown.js');
|
||||
});
|
||||
|
||||
describe('FullscreenTable', () => {
|
||||
let screenMock;
|
||||
let container;
|
||||
let tableFrame;
|
||||
let table;
|
||||
let numberColumnFrame;
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
const tableHeadings = () => {
|
||||
let result = '';
|
||||
const headings = ['1', 'name', 'email address', 'age', 'building number', 'address line 1', 'address line 2', 'postcode'];
|
||||
|
||||
headings.forEach((heading, idx) => {
|
||||
if (idx === 0) {
|
||||
result += `<th scope="col" class="table-field-heading-first">
|
||||
<span class="visually-hidden">Row in file</span><span aria-hidden="true" class="table-field-invisible-error">${heading}</span>
|
||||
</th>`;
|
||||
} else {
|
||||
result += `<th scope="col" class="table-field-heading">
|
||||
${heading}
|
||||
</th>`;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const rowCells = (cells) => {
|
||||
let result = '';
|
||||
|
||||
Object.keys(cells).forEach((key, idx) => {
|
||||
if (idx === 0) {
|
||||
result += `<td class="table-field-index">
|
||||
<span class="table-field-error">
|
||||
${key}
|
||||
</span>
|
||||
</td>`;
|
||||
} else {
|
||||
result += `<td class="table-field-left-aligned ">
|
||||
<div class="table-field-status-default">
|
||||
${key}
|
||||
</div>
|
||||
</td>`;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const tableRows = () => {
|
||||
let result = '';
|
||||
|
||||
const rows = [
|
||||
['John Logie Baird', 'johnlbaird@gmail.com', '37', '22', 'Frith Street', 'Soho, London', 'W1D 4RF'],
|
||||
['Guglielmo Marconi', 'gmarconi@hotmail.com', '21', 'Pontecchio Marconi', 'Via Celestini 1', 'Bologna', ''],
|
||||
['Louis Braille', 'louisbraille@yahoo.co.uk', '', '56', 'Boulevard des Invalides', 'Paris', '75007'],
|
||||
['Ray Tomlinson', 'hedy.lamarr@msn.com', '25', '870', '870 Winter Street', 'Waltham', 'MA 02451']
|
||||
];
|
||||
|
||||
rows.forEach(row => {
|
||||
result += `<tr class="table-row">${rowCells(row)}</tr>`;
|
||||
});
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
screenMock = new helpers.ScreenMock(jest);
|
||||
screenMock.setWindow({
|
||||
width: 1990,
|
||||
height: 940,
|
||||
scrollTop: 0
|
||||
});
|
||||
|
||||
// set up DOM
|
||||
document.body.innerHTML =
|
||||
`<main>
|
||||
<div class="fullscreen-content" data-module="fullscreen-table">
|
||||
<table class="table table-font-xsmall">
|
||||
<caption class="heading-medium table-heading visuallyhidden">
|
||||
people.csv
|
||||
</caption>
|
||||
<thead class="table-field-headings-visible">
|
||||
<tr>
|
||||
${tableHeadings()}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${tableRows()}
|
||||
</tbody>
|
||||
const setupDOM = (scrollLeft = 0, scrollWidth = 1000, clientWidth = 500) => {
|
||||
document.body.innerHTML = `
|
||||
<div class="fullscreen-table">
|
||||
<div class="table-wrapper" style="overflow-x: scroll;">
|
||||
<table>
|
||||
<tr><td>Test</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</main>`;
|
||||
<div class="fullscreen-right-shadow"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
container = document.querySelector('.fullscreen-content');
|
||||
wrapper = document.querySelector('.table-wrapper');
|
||||
rightShadow = document.querySelector('.fullscreen-right-shadow');
|
||||
|
||||
});
|
||||
// Mock scroll values
|
||||
Object.defineProperty(wrapper, 'scrollLeft', {
|
||||
get: () => scrollLeft,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(wrapper, 'scrollWidth', {
|
||||
get: () => scrollWidth,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(wrapper, 'clientWidth', {
|
||||
get: () => clientWidth,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
// 👇 Now that DOM is ready, initialize the logic
|
||||
window.initFullscreenTables();
|
||||
|
||||
// Force trigger scroll event if needed
|
||||
wrapper.dispatchEvent(new Event('scroll'));
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
document.body.innerHTML = '';
|
||||
|
||||
});
|
||||
|
||||
describe("when it loads", () => {
|
||||
|
||||
test("it fixes the number column for each row without changing the semantics", () => {
|
||||
|
||||
// start module
|
||||
window.GOVUK.modules.start();
|
||||
|
||||
tableFrame = document.querySelector('.fullscreen-scrollable-table');
|
||||
numberColumnFrame = document.querySelector('.fullscreen-fixed-table');
|
||||
|
||||
expect(tableFrame).not.toBeNull();
|
||||
expect(numberColumnFrame).not.toBeNull();
|
||||
expect(numberColumnFrame.getAttribute('aria-hidden')).toEqual('true');
|
||||
|
||||
});
|
||||
|
||||
test("it calls the sticky JS to update any cached dimensions", () => {
|
||||
|
||||
const stickyJSSpy = jest.spyOn(window.GOVUK.stickAtBottomWhenScrolling, 'recalculate');
|
||||
|
||||
// start module
|
||||
window.GOVUK.modules.start();
|
||||
|
||||
expect(stickyJSSpy.mock.calls.length).toBe(1);
|
||||
|
||||
stickyJSSpy.mockClear();
|
||||
|
||||
});
|
||||
|
||||
test("the scrolling section is focusable and has an accessible name matching the table caption", () => {
|
||||
|
||||
// start module
|
||||
window.GOVUK.modules.start();
|
||||
|
||||
tableFrame = document.querySelector('.fullscreen-scrollable-table');
|
||||
caption = tableFrame.querySelector('caption');
|
||||
|
||||
expect(tableFrame.hasAttribute('role')).toBe(true);
|
||||
expect(tableFrame.getAttribute('role')).toEqual('region');
|
||||
expect(tableFrame.hasAttribute('aria-labelledby')).toBe(true);
|
||||
expect(tableFrame.getAttribute('aria-labelledby')).toEqual(caption.getAttribute('id'));
|
||||
|
||||
});
|
||||
|
||||
test("the section providing the fixed row headers is not focusable and is hidden from assistive tech'", () => {
|
||||
|
||||
// start module
|
||||
window.GOVUK.modules.start();
|
||||
|
||||
fixedRowHeaders = document.querySelector('.fullscreen-fixed-table');
|
||||
|
||||
expect(fixedRowHeaders.hasAttribute('role')).toBe(false);
|
||||
expect(fixedRowHeaders.hasAttribute('aria-labelledby')).toBe(false);
|
||||
expect(fixedRowHeaders.hasAttribute('tabindex')).toBe(false);
|
||||
expect(fixedRowHeaders.hasAttribute('aria-hidden')).toBe(true);
|
||||
expect(fixedRowHeaders.getAttribute('aria-hidden')).toEqual('true');
|
||||
|
||||
});
|
||||
|
||||
test('adds right shadow on initial load if scrollable', () => {
|
||||
setupDOM(0, 1000, 500);
|
||||
expect(wrapper.classList.contains('scrolled')).toBe(false);
|
||||
expect(rightShadow.classList.contains('visible')).toBe(true);
|
||||
});
|
||||
|
||||
describe("the height of the table should fit the vertical space available to it", () => {
|
||||
|
||||
let containerBoundingClientRectSpy;
|
||||
let containerClientRectsSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
// set the height and offset of the window and table container from the top of the document
|
||||
// so just the top 268px of it appears on-screen
|
||||
screenMock.window.setHeightTo(768);
|
||||
screenMock.mockPositionAndDimension('container', container, {
|
||||
'offsetHeight': 1000,
|
||||
'offsetWidth': 641,
|
||||
'offsetTop': 500
|
||||
});
|
||||
|
||||
// start module
|
||||
window.GOVUK.modules.start();
|
||||
|
||||
tableFrame = document.querySelector('.fullscreen-scrollable-table');
|
||||
numberColumnFrame = document.querySelector('.fullscreen-fixed-table');
|
||||
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
screenMock.reset();
|
||||
|
||||
});
|
||||
|
||||
test("when the page has loaded", () => {
|
||||
|
||||
// the frames should crop to the top 268px of the table that is visible
|
||||
expect(window.getComputedStyle(tableFrame)['height']).toEqual('268px');
|
||||
expect(window.getComputedStyle(numberColumnFrame)['height']).toEqual('268px');
|
||||
|
||||
});
|
||||
|
||||
test("when the page has scrolled", () => {
|
||||
|
||||
// scroll the window so the table fills the height of the window (768px)
|
||||
screenMock.window.scrollTo(500);
|
||||
|
||||
// the frames should crop to the window height
|
||||
expect(window.getComputedStyle(tableFrame)['height']).toEqual('768px');
|
||||
expect(window.getComputedStyle(numberColumnFrame)['height']).toEqual('768px');
|
||||
|
||||
});
|
||||
|
||||
test("when the page has resized", () => {
|
||||
|
||||
// resize the window by 232px (from 768px to 1000px)
|
||||
screenMock.window.resizeTo({ height: 1000, width: 1024 });
|
||||
|
||||
// the frames should crop to the top 500px of the table now visible
|
||||
expect(window.getComputedStyle(tableFrame)['height']).toEqual('500px');
|
||||
expect(window.getComputedStyle(numberColumnFrame)['height']).toEqual('500px');
|
||||
|
||||
});
|
||||
|
||||
test('hides right shadow when scrolled to max', () => {
|
||||
setupDOM(500, 1000, 500);
|
||||
expect(wrapper.classList.contains('scrolled')).toBe(true);
|
||||
expect(rightShadow.classList.contains('visible')).toBe(false);
|
||||
});
|
||||
|
||||
describe("the width of the table should fit the horizontal space available to it", () => {
|
||||
let rowNumberColumnCell;
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
rowNumberColumnCell = container.querySelector('.table-field-index');
|
||||
|
||||
// set main content column width (used as module as gauge for table width)
|
||||
screenMock.window.setWidthTo(1024);
|
||||
document.querySelector('main').setAttribute('style', 'width: 742px');
|
||||
|
||||
// set total width of column for row numbers in table to 40px
|
||||
rowNumberColumnCell.setAttribute('style', 'width: 40px');
|
||||
|
||||
// start module
|
||||
window.GOVUK.modules.start();
|
||||
|
||||
tableFrame = document.querySelector('.fullscreen-scrollable-table');
|
||||
numberColumnFrame = document.querySelector('.fullscreen-fixed-table');
|
||||
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
screenMock.reset();
|
||||
|
||||
});
|
||||
|
||||
test("when the page has loaded", () => {
|
||||
|
||||
// table should set its width to be that of `<main>`, minus margin-left for the row numbers column
|
||||
expect(window.getComputedStyle(tableFrame)['width']).toEqual('702px'); // width of content column - numbers column
|
||||
expect(window.getComputedStyle(tableFrame)['margin-left']).toEqual('40px'); // width of numbers column
|
||||
|
||||
// table for number column has 4px extra to allow space for drop shadow
|
||||
expect(window.getComputedStyle(numberColumnFrame)['width']).toEqual('44px');
|
||||
|
||||
});
|
||||
|
||||
test("when the page has resized", () => {
|
||||
|
||||
// resize window and content column
|
||||
document.querySelector('main').setAttribute('style', 'width: 720px');
|
||||
screenMock.window.resizeTo({ height: 768, width: 960 });
|
||||
|
||||
// table should set its width to be that of `<main>`, minus margin-left for the row numbers column
|
||||
expect(window.getComputedStyle(tableFrame)['width']).toEqual('680px'); // width of content column - numbers column
|
||||
expect(window.getComputedStyle(tableFrame)['margin-left']).toEqual('40px'); // width of numbers column
|
||||
|
||||
// table for number column has 4px extra to allow space for drop shadow
|
||||
expect(window.getComputedStyle(numberColumnFrame)['width']).toEqual('44px');
|
||||
|
||||
});
|
||||
|
||||
test('handles mid-scroll: left + right shadows active', () => {
|
||||
setupDOM(250, 1000, 500);
|
||||
expect(wrapper.classList.contains('scrolled')).toBe(true);
|
||||
expect(rightShadow.classList.contains('visible')).toBe(true);
|
||||
});
|
||||
|
||||
describe("when the table scrolls horizontally", () => {
|
||||
let rightEdgeShadow;
|
||||
test('gracefully handles missing right shadow', () => {
|
||||
document.body.innerHTML = `
|
||||
<div class="fullscreen-table">
|
||||
<div class="table-wrapper">
|
||||
<table>
|
||||
<tr><td>Only content</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = document.querySelector('.table-wrapper');
|
||||
|
||||
// start module
|
||||
window.GOVUK.modules.start();
|
||||
Object.defineProperty(wrapper, 'scrollLeft', { get: () => 100, configurable: true });
|
||||
Object.defineProperty(wrapper, 'scrollWidth', { get: () => 500, configurable: true });
|
||||
Object.defineProperty(wrapper, 'clientWidth', { get: () => 200, configurable: true });
|
||||
|
||||
tableFrame = document.querySelector('.fullscreen-scrollable-table');
|
||||
table = tableFrame.querySelector('table');
|
||||
numberColumnFrame = document.querySelector('.fullscreen-fixed-table');
|
||||
rightEdgeShadow = container.querySelector('.fullscreen-right-shadow');
|
||||
|
||||
tableFrame.setAttribute('style', 'width: 742px');
|
||||
table.setAttribute('style', 'width: 1000px');
|
||||
|
||||
});
|
||||
|
||||
test("the right edge of the table scroll area should have a drop-shadow if it isn't scrolled", () => {
|
||||
|
||||
tableFrame.scrollLeft = 0;
|
||||
helpers.triggerEvent(tableFrame, 'scroll');
|
||||
|
||||
expect(numberColumnFrame.classList.contains('fullscreen-scrolled-table')).toBe(false);
|
||||
expect(rightEdgeShadow.classList.contains('visible')).toBe(true);
|
||||
|
||||
});
|
||||
|
||||
test("the left edge of the table scroll area should have a drop-shadow if the table is scrolled to 100%", () => {
|
||||
|
||||
// scroll to end of table
|
||||
tableFrame.scrollLeft = 258;
|
||||
helpers.triggerEvent(tableFrame, 'scroll');
|
||||
|
||||
expect(numberColumnFrame.classList.contains('fullscreen-scrolled-table')).toBe(true);
|
||||
expect(rightEdgeShadow.classList.contains('visible')).toBe(false);
|
||||
|
||||
});
|
||||
|
||||
test("both edges of the table scroll area should have a drop-shadow if the table is scrolled between 0% and 100%", () => {
|
||||
|
||||
// scroll to middle of table
|
||||
tableFrame.scrollLeft = 129;
|
||||
helpers.triggerEvent(tableFrame, 'scroll');
|
||||
|
||||
expect(numberColumnFrame.classList.contains('fullscreen-scrolled-table')).toBe(true);
|
||||
expect(rightEdgeShadow.classList.contains('visible')).toBe(true);
|
||||
|
||||
});
|
||||
// 👇 Call after building DOM
|
||||
window.initFullscreenTables();
|
||||
wrapper.dispatchEvent(new Event('scroll'));
|
||||
|
||||
expect(wrapper.classList.contains('scrolled')).toBe(true);
|
||||
// No error thrown for missing .fullscreen-right-shadow
|
||||
});
|
||||
|
||||
describe("when the table is focused", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
// start module
|
||||
window.GOVUK.modules.start();
|
||||
|
||||
tableFrame = document.querySelector('.fullscreen-scrollable-table');
|
||||
tableFrame.focus();
|
||||
|
||||
});
|
||||
|
||||
test("it should make the parent frame a focus style", () => {
|
||||
|
||||
expect(container.classList.contains('js-focus-style')).toBe(true);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
108
tests/javascripts/main.test.js
Normal file
108
tests/javascripts/main.test.js
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
|
||||
describe('Main init script', () => {
|
||||
beforeEach(() => {
|
||||
// Clear DOM
|
||||
document.body.innerHTML = '';
|
||||
jest.resetModules();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('calls GOVUK.Frontend.initAll if available', () => {
|
||||
const initAllMock = jest.fn();
|
||||
const showHideInitMock = jest.fn();
|
||||
|
||||
global.GOVUK = {
|
||||
Frontend: { initAll: initAllMock },
|
||||
ShowHideContent: jest.fn().mockImplementation(() => ({
|
||||
init: showHideInitMock
|
||||
}))
|
||||
};
|
||||
|
||||
require('../../app/assets/javascripts/main.js');
|
||||
|
||||
expect(initAllMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('initializes GOVUK.ShowHideContent', () => {
|
||||
const initMock = jest.fn();
|
||||
const ShowHideContentMock = jest.fn().mockImplementation(() => ({
|
||||
init: initMock
|
||||
}));
|
||||
global.GOVUK = {
|
||||
ShowHideContent: ShowHideContentMock
|
||||
};
|
||||
|
||||
require('../../app/assets/javascripts/main.js');
|
||||
|
||||
expect(ShowHideContentMock).toHaveBeenCalled();
|
||||
expect(initMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('focuses input next to error message inside label', () => {
|
||||
document.body.innerHTML = `
|
||||
<form>
|
||||
<label>
|
||||
<span class="error-message">Required</span>
|
||||
</label>
|
||||
<input id="name" />
|
||||
</form>
|
||||
`;
|
||||
const focusMock = jest.fn();
|
||||
const input = document.querySelector('input');
|
||||
input.focus = focusMock;
|
||||
|
||||
require('../../app/assets/javascripts/main.js');
|
||||
|
||||
// simulate DOMContentLoaded
|
||||
document.dispatchEvent(new Event('DOMContentLoaded'));
|
||||
|
||||
expect(focusMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('adds border color on header click', () => {
|
||||
document.body.innerHTML = `
|
||||
<div class="govuk-header__container"></div>
|
||||
`;
|
||||
|
||||
global.GOVUK = {
|
||||
ShowHideContent: jest.fn().mockImplementation(() => ({ init: jest.fn() })),
|
||||
Frontend: { initAll: jest.fn() }
|
||||
};
|
||||
|
||||
require('../../app/assets/javascripts/main.js');
|
||||
document.dispatchEvent(new Event('DOMContentLoaded'));
|
||||
|
||||
const header = document.querySelector('.govuk-header__container');
|
||||
header.click();
|
||||
|
||||
expect(header.style.borderColor).toBe('#005ea5');
|
||||
});
|
||||
|
||||
it('adds and removes class on parent during focusin and focusout', () => {
|
||||
document.body.innerHTML = `
|
||||
<div class="js-mark-focus-on-parent">
|
||||
<input type="text" />
|
||||
</div>
|
||||
`;
|
||||
|
||||
global.GOVUK = {
|
||||
ShowHideContent: jest.fn().mockImplementation(() => ({ init: jest.fn() })),
|
||||
Frontend: { initAll: jest.fn() }
|
||||
};
|
||||
|
||||
require('../../app/assets/javascripts/main.js');
|
||||
document.dispatchEvent(new Event('DOMContentLoaded'));
|
||||
|
||||
const container = document.querySelector('.js-mark-focus-on-parent');
|
||||
const input = container.querySelector('input');
|
||||
|
||||
input.dispatchEvent(new FocusEvent('focusin', { bubbles: true }));
|
||||
expect(container.classList.contains('js-child-has-focus')).toBe(true);
|
||||
|
||||
input.dispatchEvent(new FocusEvent('focusout', { bubbles: true }));
|
||||
expect(container.classList.contains('js-child-has-focus')).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user