mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-08-24 00:07:02 -04:00
Compare commits
2 Commits
python_man
...
1871-searc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b1c1015d8 | ||
|
|
9c72c9bd62 |
53
app/assets/javascripts/searchActivity.js
Normal file
53
app/assets/javascripts/searchActivity.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const searchInput = document.getElementById('activity-search');
|
||||||
|
const tableRows = document.querySelectorAll('.job-table tbody .table-row');
|
||||||
|
const tableBody = document.querySelector('.job-table tbody');
|
||||||
|
const liveRegion = document.querySelector('.usa-table__announcement-region');
|
||||||
|
|
||||||
|
function filterTable() {
|
||||||
|
const filter = searchInput.value.toLowerCase();
|
||||||
|
let visibleRowCount = 0;
|
||||||
|
|
||||||
|
tableRows.forEach(row => {
|
||||||
|
const rowText = row.textContent.toLowerCase();
|
||||||
|
|
||||||
|
if (rowText.includes(filter)) {
|
||||||
|
row.style.display = 'table-row'; // Show the row
|
||||||
|
visibleRowCount++;
|
||||||
|
} else {
|
||||||
|
row.style.display = 'none'; // Hide the row
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update ARIA live region
|
||||||
|
liveRegion.textContent = `${visibleRowCount} result${visibleRowCount !== 1 ? 's' : ''} found`;
|
||||||
|
|
||||||
|
// Check if there are no visible rows
|
||||||
|
if (visibleRowCount === 0) {
|
||||||
|
let noResultsRow = document.querySelector('.no-results');
|
||||||
|
if (!noResultsRow) {
|
||||||
|
const newRow = document.createElement('tr');
|
||||||
|
newRow.classList.add('no-results');
|
||||||
|
newRow.innerHTML = `
|
||||||
|
<td colspan="7" class="table-empty-message">No results found</td>
|
||||||
|
`;
|
||||||
|
tableBody.appendChild(newRow);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const noResultsRow = document.querySelector('.no-results');
|
||||||
|
if (noResultsRow) {
|
||||||
|
noResultsRow.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for input events to catch when the "X" button is used
|
||||||
|
searchInput.addEventListener('input', function() {
|
||||||
|
filterTable();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Also handle the keyup event for other input changes
|
||||||
|
searchInput.addEventListener('keyup', function() {
|
||||||
|
filterTable();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -444,6 +444,20 @@ td.table-empty-message {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.usa-search .usa-button {
|
||||||
|
img {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-filter {
|
||||||
|
flex-direction: column;
|
||||||
|
input {
|
||||||
|
margin-top: units(1);
|
||||||
|
border-right: 1px solid color('gray-cool-60');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.usage-table {
|
.usage-table {
|
||||||
ul {
|
ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
|||||||
@@ -64,9 +64,20 @@
|
|||||||
<h1 class="usa-sr-only">All activity</h1>
|
<h1 class="usa-sr-only">All activity</h1>
|
||||||
<h2 class="font-body-2xl line-height-sans-2 margin-0">All activity</h2>
|
<h2 class="font-body-2xl line-height-sans-2 margin-0">All activity</h2>
|
||||||
<h2 class="margin-top-4 margin-bottom-1">Sent jobs</h2>
|
<h2 class="margin-top-4 margin-bottom-1">Sent jobs</h2>
|
||||||
|
<section aria-label="All activity search" class="margin-top-2">
|
||||||
|
<form class="usa-search usa-search--small table-filter" role="search">
|
||||||
|
<label class="usa-labe margin-top-0" for="activity-search">Search</label>
|
||||||
|
<input
|
||||||
|
class="usa-input"
|
||||||
|
id="activity-search"
|
||||||
|
type="search"
|
||||||
|
name="All activity search"
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
<div class="usa-table-container--scrollable-mobile">
|
<div class="usa-table-container--scrollable-mobile">
|
||||||
<table class="usa-table usa-table--compact job-table">
|
<table class="usa-table usa-table--compact job-table">
|
||||||
<caption></caption>
|
<caption class="usa-sr-only">All activity table</caption>
|
||||||
<thead class="table-field-headings">
|
<thead class="table-field-headings">
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" role="columnheader" class="table-field-heading-first" id="jobId">
|
<th scope="col" role="columnheader" class="table-field-heading-first" id="jobId">
|
||||||
@@ -125,7 +136,9 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div class="usa-sr-only usa-table__announcement-region" aria-live="polite"></div>
|
<div class="usa-table__announcement-region usa-sr-only" aria-live="polite" aria-atomic="true">
|
||||||
|
<!-- Live region content goes here -->
|
||||||
|
</div>
|
||||||
<p><b>Note: </b>Report data is only available for 7 days after your message has been sent</p>
|
<p><b>Note: </b>Report data is only available for 7 days after your message has been sent</p>
|
||||||
</div>
|
</div>
|
||||||
{{show_pagination}}
|
{{show_pagination}}
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ const javascripts = () => {
|
|||||||
paths.src + 'javascripts/main.js',
|
paths.src + 'javascripts/main.js',
|
||||||
paths.src + 'javascripts/totalMessagesChart.js',
|
paths.src + 'javascripts/totalMessagesChart.js',
|
||||||
paths.src + 'javascripts/activityChart.js',
|
paths.src + 'javascripts/activityChart.js',
|
||||||
|
paths.src + 'javascripts/searchActivity.js',
|
||||||
])
|
])
|
||||||
.pipe(plugins.prettyerror())
|
.pipe(plugins.prettyerror())
|
||||||
.pipe(plugins.babel({
|
.pipe(plugins.babel({
|
||||||
@@ -97,7 +98,7 @@ const copyGtmHead = () => {
|
|||||||
.pipe(dest(paths.dist + 'js/'));
|
.pipe(dest(paths.dist + 'js/'));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Task to copy images
|
// Task to copy imag
|
||||||
const copyImages = () => {
|
const copyImages = () => {
|
||||||
return src(paths.src + 'images/**/*', { encoding: false })
|
return src(paths.src + 'images/**/*', { encoding: false })
|
||||||
.pipe(dest(paths.dist + 'images/'));
|
.pipe(dest(paths.dist + 'images/'));
|
||||||
|
|||||||
189
tests/javascripts/searchActivity.test.js
Normal file
189
tests/javascripts/searchActivity.test.js
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
// Ensure your filterTable function is defined and accessible in your test environment
|
||||||
|
beforeEach(() => {
|
||||||
|
document.body.innerHTML = `
|
||||||
|
<section aria-label="All activity search" class="margin-top-2">
|
||||||
|
<form class="usa-search usa-search--small table-filter" role="search">
|
||||||
|
<label class="usa-label margin-top-0" for="all-activity-search">Search</label>
|
||||||
|
<input class="usa-input" id="all-activity-search" type="search" name="All activity search"/>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
<div class="usa-table-container--scrollable-mobile">
|
||||||
|
<table class="usa-table usa-table--compact job-table">
|
||||||
|
<thead class="table-field-headings">
|
||||||
|
<tr>
|
||||||
|
<th scope="col" class="table-field-heading-first" id="jobId"><span>Job ID#</span></th>
|
||||||
|
<th scope="col" class="table-field-heading"><span>Template</span></th>
|
||||||
|
<th scope="col" class="table-field-heading"><span>Time sent</span></th>
|
||||||
|
<th scope="col" class="table-field-heading"><span>Sender</span></th>
|
||||||
|
<th scope="col" class="table-field-heading"><span>Report</span></th>
|
||||||
|
<th scope="col" class="table-field-heading"><span>Delivered</span></th>
|
||||||
|
<th scope="col" class="table-field-heading"><span>Failed</span></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr class="table-row" style="display: table-row;">
|
||||||
|
<td class="table-field jobid"><a href="#">Job 1</a></td>
|
||||||
|
<td class="table-field template">Template 1</td>
|
||||||
|
<td class="table-field time-sent">2024-08-20 10:00</td>
|
||||||
|
<td class="table-field sender">Sender 1</td>
|
||||||
|
<td class="table-field report"><a href="#"><img src="#" alt="File Download Icon"></a></td>
|
||||||
|
<td class="table-field delivered">10</td>
|
||||||
|
<td class="table-field failed">2</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="table-row" style="display: table-row;">
|
||||||
|
<td class="table-field jobid"><a href="#">Job 2</a></td>
|
||||||
|
<td class="table-field template">Template 2</td>
|
||||||
|
<td class="table-field time-sent">2024-08-20 11:00</td>
|
||||||
|
<td class="table-field sender">Sender 2</td>
|
||||||
|
<td class="table-field report"><a href="#"><img src="#" alt="File Download Icon"></a></td>
|
||||||
|
<td class="table-field delivered">5</td>
|
||||||
|
<td class="table-field failed">0</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="table-row" style="display: table-row;">
|
||||||
|
<td class="table-field jobid"><a href="#">Job 3</a></td>
|
||||||
|
<td class="table-field template">Template 3</td>
|
||||||
|
<td class="table-field time-sent">2024-08-20 12:00</td>
|
||||||
|
<td class="table-field sender">Sender 3</td>
|
||||||
|
<td class="table-field report"><a href="#"><img src="#" alt="File Download Icon"></a></td>
|
||||||
|
<td class="table-field delivered">0</td>
|
||||||
|
<td class="table-field failed">1</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="usa-table__announcement-region usa-sr-only" aria-live="polite" aria-atomic="true">
|
||||||
|
<!-- Live region content goes here -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Attach the filterTable function to the window object for testing
|
||||||
|
window.filterTable = function() {
|
||||||
|
const searchInput = document.getElementById('all-activity-search');
|
||||||
|
const tableRows = document.querySelectorAll('.job-table tbody .table-row');
|
||||||
|
const tableBody = document.querySelector('.job-table tbody');
|
||||||
|
const liveRegion = document.querySelector('.usa-table__announcement-region');
|
||||||
|
|
||||||
|
const filter = searchInput.value.toLowerCase();
|
||||||
|
let visibleRowCount = 0;
|
||||||
|
|
||||||
|
tableRows.forEach(row => {
|
||||||
|
const rowText = row.textContent.toLowerCase();
|
||||||
|
|
||||||
|
if (rowText.includes(filter)) {
|
||||||
|
row.style.display = 'table-row'; // Show the row
|
||||||
|
visibleRowCount++;
|
||||||
|
} else {
|
||||||
|
row.style.display = 'none'; // Hide the row
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update ARIA live region
|
||||||
|
liveRegion.textContent = `${visibleRowCount} result${visibleRowCount !== 1 ? 's' : ''} found`;
|
||||||
|
|
||||||
|
// Check if there are no visible rows
|
||||||
|
if (visibleRowCount === 0) {
|
||||||
|
let noResultsRow = document.querySelector('.no-results');
|
||||||
|
if (!noResultsRow) {
|
||||||
|
const newRow = document.createElement('tr');
|
||||||
|
newRow.classList.add('no-results');
|
||||||
|
newRow.innerHTML = `
|
||||||
|
<td colspan="7" class="table-empty-message">No results found</td>
|
||||||
|
`;
|
||||||
|
tableBody.appendChild(newRow);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const noResultsRow = document.querySelector('.no-results');
|
||||||
|
if (noResultsRow) {
|
||||||
|
noResultsRow.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
test('filterTable function filters rows based on input', () => {
|
||||||
|
const searchInput = document.getElementById('all-activity-search');
|
||||||
|
searchInput.value = 'Job 2';
|
||||||
|
|
||||||
|
// Call the filterTable function directly
|
||||||
|
window.filterTable();
|
||||||
|
|
||||||
|
const tableRows = document.querySelectorAll('.job-table tbody .table-row');
|
||||||
|
expect(getComputedStyle(tableRows[0]).display).toBe('none');
|
||||||
|
expect(getComputedStyle(tableRows[1]).display).toBe('table-row');
|
||||||
|
expect(getComputedStyle(tableRows[2]).display).toBe('none');
|
||||||
|
|
||||||
|
const liveRegion = document.querySelector('.usa-table__announcement-region');
|
||||||
|
expect(liveRegion.textContent).toBe('1 result found');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('filterTable function shows "No results found" when no rows match', () => {
|
||||||
|
const searchInput = document.getElementById('all-activity-search');
|
||||||
|
searchInput.value = 'Non-existent Job';
|
||||||
|
|
||||||
|
// Call the filterTable function directly
|
||||||
|
window.filterTable();
|
||||||
|
|
||||||
|
const tableRows = document.querySelectorAll('.job-table tbody .table-row');
|
||||||
|
tableRows.forEach(row => {
|
||||||
|
expect(getComputedStyle(row).display).toBe('none');
|
||||||
|
});
|
||||||
|
|
||||||
|
const noResultsRow = document.querySelector('.no-results');
|
||||||
|
expect(noResultsRow).not.toBeNull();
|
||||||
|
expect(noResultsRow.textContent.trim()).toBe('No results found');
|
||||||
|
|
||||||
|
const liveRegion = document.querySelector('.usa-table__announcement-region');
|
||||||
|
expect(liveRegion.textContent).toBe('0 results found');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('filterTable function filters rows only after 2 characters are entered', () => {
|
||||||
|
const searchInput = document.getElementById('all-activity-search');
|
||||||
|
const tableRows = document.querySelectorAll('.job-table tbody .table-row');
|
||||||
|
|
||||||
|
// Set input value to 1 character and run filterTable
|
||||||
|
searchInput.value = 'J';
|
||||||
|
window.filterTable();
|
||||||
|
|
||||||
|
// Expect all rows to still be visible
|
||||||
|
tableRows.forEach(row => {
|
||||||
|
expect(getComputedStyle(row).display).toBe('table-row');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set input value to 2 characters and run filterTable
|
||||||
|
searchInput.value = 'Jo';
|
||||||
|
window.filterTable();
|
||||||
|
|
||||||
|
// Expect filtering to take place
|
||||||
|
expect(getComputedStyle(tableRows[0]).display).toBe('table-row');
|
||||||
|
expect(getComputedStyle(tableRows[1]).display).toBe('table-row');
|
||||||
|
expect(getComputedStyle(tableRows[2]).display).toBe('table-row');
|
||||||
|
|
||||||
|
// Set input value to filter down to one row
|
||||||
|
searchInput.value = 'Job 2';
|
||||||
|
window.filterTable();
|
||||||
|
|
||||||
|
// Expect only the second row to be visible
|
||||||
|
expect(getComputedStyle(tableRows[0]).display).toBe('none');
|
||||||
|
expect(getComputedStyle(tableRows[1]).display).toBe('table-row');
|
||||||
|
expect(getComputedStyle(tableRows[2]).display).toBe('none');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('filterTable function shows "No results found" when no rows match after 2 characters', () => {
|
||||||
|
const searchInput = document.getElementById('all-activity-search');
|
||||||
|
searchInput.value = 'Non-existent Job';
|
||||||
|
|
||||||
|
// Call the filterTable function directly
|
||||||
|
window.filterTable();
|
||||||
|
|
||||||
|
const tableRows = document.querySelectorAll('.job-table tbody .table-row');
|
||||||
|
tableRows.forEach(row => {
|
||||||
|
expect(getComputedStyle(row).display).toBe('none');
|
||||||
|
});
|
||||||
|
|
||||||
|
const noResultsRow = document.querySelector('.no-results');
|
||||||
|
expect(noResultsRow).not.toBeNull();
|
||||||
|
expect(noResultsRow.textContent.trim()).toBe('No results found');
|
||||||
|
|
||||||
|
const liveRegion = document.querySelector('.usa-table__announcement-region');
|
||||||
|
expect(liveRegion.textContent).toBe('0 results found');
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user