Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1871 search activity page #1893

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions app/assets/javascripts/searchActivity.js
Original file line number Diff line number Diff line change
@@ -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();
});
});
14 changes: 14 additions & 0 deletions app/assets/sass/uswds/_uswds-theme-custom-styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
ul {
list-style: none;
Expand Down
17 changes: 15 additions & 2 deletions app/templates/views/activity/all-activity.html
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,20 @@
<h1 class="usa-sr-only">All activity</h1>
<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>
<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">
<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">
<tr>
<th scope="col" role="columnheader" class="table-field-heading-first" id="jobId">
Expand Down Expand Up @@ -125,7 +136,9 @@ <h2 class="margin-top-4 margin-bottom-1">Sent jobs</h2>
{% endif %}
</tbody>
</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>
</div>
{{show_pagination}}
Expand Down
3 changes: 2 additions & 1 deletion gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ const javascripts = () => {
paths.src + 'javascripts/main.js',
paths.src + 'javascripts/totalMessagesChart.js',
paths.src + 'javascripts/activityChart.js',
paths.src + 'javascripts/searchActivity.js',
])
.pipe(plugins.prettyerror())
.pipe(plugins.babel({
Expand All @@ -97,7 +98,7 @@ const copyGtmHead = () => {
.pipe(dest(paths.dist + 'js/'));
};

// Task to copy images
// Task to copy imag
const copyImages = () => {
return src(paths.src + 'images/**/*', { encoding: false })
.pipe(dest(paths.dist + 'images/'));
Expand Down
189 changes: 189 additions & 0 deletions tests/javascripts/searchActivity.test.js
Original file line number Diff line number Diff line change
@@ -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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is the same code that is in searchActivity regarding business logic, it might make sense to extract this logic to a generic helper file so that we can reuse that same logic in the javascript tests. This way if future updates are needed, it only needs to be done in one place instead of multiple.

I am still learning context and might be missing something here, so ill go ahead and approve, but if you think its a good idea as well then you can make that change!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alexjanousekGSA - we're actually waiting on some back end work to provide an endpoint to search against, so this might change. When that happens I'll reach back out to see if it's worth editing for testing-sake.

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');
});
Loading