mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-08-24 00:07:02 -04:00
Compare commits
3 Commits
f55d47e3d1
...
032b9927aa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
032b9927aa | ||
|
|
06c238269f | ||
|
|
2064fc864a |
@@ -1,8 +1,31 @@
|
||||
function announceUploadStatusFromElement() {
|
||||
const srRegion = document.getElementById('upload-status-live');
|
||||
const success = document.getElementById('upload-success');
|
||||
const error = document.getElementById('upload-error');
|
||||
|
||||
if (!srRegion) return;
|
||||
|
||||
const message = error?.textContent || success?.textContent;
|
||||
|
||||
if (message) {
|
||||
srRegion.textContent = '';
|
||||
setTimeout(() => {
|
||||
srRegion.textContent = message;
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
|
||||
// Exported for use in tests
|
||||
function initUploadStatusAnnouncer() {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
announceUploadStatusFromElement();
|
||||
});
|
||||
}
|
||||
|
||||
(function(Modules) {
|
||||
"use strict";
|
||||
|
||||
Modules.FileUpload = function() {
|
||||
|
||||
this.submit = () => this.$form.trigger('submit');
|
||||
|
||||
this.showCancelButton = () => {
|
||||
@@ -11,39 +34,30 @@
|
||||
Uploading<span class="dot-anim" aria-hidden="true"></span>
|
||||
</button>
|
||||
`);
|
||||
|
||||
const $srStatus = $('#upload-status-live');
|
||||
if ($srStatus.length) {
|
||||
// Clear and re-set the content to ensure it's treated as a change
|
||||
$srStatus.html('');
|
||||
setTimeout(() => {
|
||||
$srStatus.html('<span>File is uploading</span>');
|
||||
}, 50);
|
||||
}
|
||||
};
|
||||
|
||||
this.start = function(component) {
|
||||
|
||||
this.$form = $(component);
|
||||
|
||||
// Handle "Upload your file" button click — CSP-safe version
|
||||
this.$form.on('click', '[data-module="upload-trigger"]', function () {
|
||||
const inputId = $(this).data('file-input-id');
|
||||
const fileInput = document.getElementById(inputId);
|
||||
if (fileInput) fileInput.click();
|
||||
});
|
||||
|
||||
// Clear the form if the user navigates back to the page
|
||||
$(window).on("pageshow", () => this.$form[0].reset());
|
||||
|
||||
// Watch for file input changes
|
||||
this.$form.on('change', '.file-upload-field', () => {
|
||||
this.submit();
|
||||
this.showCancelButton();
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
})(window.GOVUK.Modules);
|
||||
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = {
|
||||
announceUploadStatusFromElement,
|
||||
initUploadStatusAnnouncer
|
||||
};
|
||||
}
|
||||
|
||||
@@ -11,6 +11,11 @@
|
||||
{% include "components/head.html" %}
|
||||
</head>
|
||||
<body class="usa-template__body {{ bodyClasses }}">
|
||||
<div id="upload-status-live"
|
||||
class="usa-sr-only"
|
||||
aria-live="assertive"
|
||||
role="status">
|
||||
</div>
|
||||
<script nonce="{{ csp_nonce() }}">document.body.className = ((document.body.className) ? document.body.className + ' js-enabled' : 'js-enabled');</script>
|
||||
{% block bodyStart %}
|
||||
{% block extra_javascripts_before_body %}
|
||||
|
||||
@@ -4,10 +4,6 @@
|
||||
{% macro banner(body, type=None, with_tick=False, delete_button=None, subhead=None, context=None, action=None, id=None, thing=None) %}
|
||||
<div
|
||||
class='banner{% if type %}-{{ type }}{% endif %}{% if with_tick %}-with-tick{% endif %}'
|
||||
{% if type == 'dangerous' %}
|
||||
role='group'
|
||||
tabindex='-1'
|
||||
{% endif %}
|
||||
{% if id %}
|
||||
id={{ id }}
|
||||
{% endif %}
|
||||
|
||||
@@ -23,6 +23,9 @@ Error
|
||||
<div class="">
|
||||
{% call banner_wrapper(type='dangerous') %}
|
||||
|
||||
{# Alert for users of AT #}
|
||||
<span id="upload-error" class="usa-sr-only">File upload failed</span>
|
||||
|
||||
{% if recipients.too_many_rows %}
|
||||
|
||||
<div class="usa-alert usa-alert--error" role="alert">
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
|
||||
{{ template|string }}
|
||||
<div class="bottom-gutter-3-2">
|
||||
{# Alert for users of AT #}
|
||||
<span id="upload-success" class="usa-sr-only">File upload successful</span>
|
||||
<form method="post" enctype="multipart/form-data" action="{{url_for('main.preview_job', service_id=current_service.id, template_id=template_id, upload_id=upload_id)}}" class='page-footer'>
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
{% if choose_time_form %}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
|
||||
<div class="">
|
||||
{% call banner_wrapper(type='dangerous') %}
|
||||
{# Alert for users of AT #}
|
||||
<span id="upload-failed" class="usa-sr-only">File upload failed</span>
|
||||
{% if row_errors|length == 1 %}
|
||||
<div class="usa-alert usa-alert--error" role="alert">
|
||||
<div class="usa-alert__body">
|
||||
|
||||
@@ -12,7 +12,9 @@
|
||||
|
||||
|
||||
{% block backLink %}
|
||||
{{ usaBackLink(params) }}
|
||||
{{ usaBackLink({
|
||||
"href": url_for('main.send_one_off_step', service_id=current_service.id, template_id=template.id, step_index=0)
|
||||
}) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block maincolumn_content %}
|
||||
|
||||
190
poetry.lock
generated
190
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
26
tests/javascripts/fileUpload.domLoad.test.js
Normal file
26
tests/javascripts/fileUpload.domLoad.test.js
Normal file
@@ -0,0 +1,26 @@
|
||||
// This will use the real function and listener
|
||||
const {
|
||||
initUploadStatusAnnouncer
|
||||
} = require('../../app/assets/javascripts/fileUpload.js');
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
test('writes upload message to the live region on DOMContentLoaded', () => {
|
||||
// Setup the DOM
|
||||
document.body.innerHTML = `
|
||||
<div id="upload-status-live" aria-live="assertive" role="status" class="usa-sr-only">Old message</div>
|
||||
<span id="upload-success" class="usa-sr-only">File upload successful</span>
|
||||
`;
|
||||
|
||||
// Register the listener (same as page load does)
|
||||
initUploadStatusAnnouncer();
|
||||
|
||||
// Simulate the page load event
|
||||
document.dispatchEvent(new Event('DOMContentLoaded'));
|
||||
|
||||
// Live region will be cleared first, then updated
|
||||
jest.advanceTimersByTime(50);
|
||||
|
||||
const srRegion = document.getElementById('upload-status-live');
|
||||
expect(srRegion.textContent).toBe('File upload successful');
|
||||
});
|
||||
@@ -1,13 +1,12 @@
|
||||
const helpers = require('./support/helpers.js');
|
||||
|
||||
beforeAll(() => {
|
||||
require('../../app/assets/javascripts/fileUpload.js');
|
||||
});
|
||||
const { announceUploadStatusFromElement } = require('../../app/assets/javascripts/fileUpload.js');
|
||||
|
||||
afterAll(() => {
|
||||
require('./support/teardown.js');
|
||||
});
|
||||
|
||||
|
||||
describe('File upload', () => {
|
||||
|
||||
let form;
|
||||
@@ -82,3 +81,106 @@ describe('File upload', () => {
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('File upload "upload-trigger" click handler', () => {
|
||||
let form;
|
||||
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = `
|
||||
<form method="post" enctype="multipart/form-data" data-module="file-upload">
|
||||
<button type="button" data-module="upload-trigger" data-file-input-id="test-file-input">Upload your file</button>
|
||||
<input type="file" id="test-file-input" style="display:none;">
|
||||
</form>
|
||||
`;
|
||||
|
||||
form = document.querySelector('form');
|
||||
|
||||
// Register the module
|
||||
window.GOVUK.modules.start();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
document.body.innerHTML = '';
|
||||
});
|
||||
|
||||
test('clicking upload trigger simulates file input click', () => {
|
||||
const uploadButton = form.querySelector('[data-module="upload-trigger"]');
|
||||
const fileInput = document.getElementById('test-file-input');
|
||||
|
||||
// Spy on fileInput.click
|
||||
const clickSpy = jest.spyOn(fileInput, 'click').mockImplementation(() => {});
|
||||
|
||||
// Trigger the click
|
||||
helpers.triggerEvent(uploadButton, 'click');
|
||||
|
||||
expect(clickSpy).toHaveBeenCalled();
|
||||
|
||||
clickSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('announceUploadStatusFromElement', () => {
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
document.body.innerHTML = `
|
||||
<div id="upload-status-live" aria-live="assertive" role="status" class="usa-sr-only"></div>
|
||||
`;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
document.body.innerHTML = '';
|
||||
});
|
||||
|
||||
test('announces error message from #upload-error', () => {
|
||||
document.body.innerHTML += `
|
||||
<span id="upload-error" class="usa-sr-only">File upload failed</span>
|
||||
`;
|
||||
|
||||
const srRegion = document.getElementById('upload-status-live');
|
||||
|
||||
// Call the function
|
||||
announceUploadStatusFromElement();
|
||||
|
||||
// Confirm it clears first
|
||||
expect(srRegion.textContent).toBe('');
|
||||
|
||||
// Fast-forward the timer
|
||||
jest.advanceTimersByTime(50);
|
||||
|
||||
// Confirm it updates after delay
|
||||
expect(srRegion.textContent).toBe('File upload failed');
|
||||
});
|
||||
|
||||
test('announces success message from #upload-success if no error is present', () => {
|
||||
document.body.innerHTML += `
|
||||
<span id="upload-success" class="usa-sr-only">File upload successful</span>
|
||||
`;
|
||||
|
||||
const srRegion = document.getElementById('upload-status-live');
|
||||
|
||||
announceUploadStatusFromElement();
|
||||
|
||||
expect(srRegion.textContent).toBe('');
|
||||
|
||||
jest.advanceTimersByTime(50);
|
||||
|
||||
expect(srRegion.textContent).toBe('File upload successful');
|
||||
});
|
||||
|
||||
test('does nothing if neither success nor error is present', () => {
|
||||
const srRegion = document.getElementById('upload-status-live');
|
||||
|
||||
srRegion.textContent = 'Old message';
|
||||
|
||||
announceUploadStatusFromElement();
|
||||
|
||||
// Should not clear or update if no message element is found
|
||||
expect(srRegion.textContent).toBe('Old message');
|
||||
|
||||
jest.advanceTimersByTime(50);
|
||||
|
||||
// Still unchanged
|
||||
expect(srRegion.textContent).toBe('Old message');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user