diff --git a/app/main/views/organizations.py b/app/main/views/organizations.py index 6b769a91f..6e3496a7f 100644 --- a/app/main/views/organizations.py +++ b/app/main/views/organizations.py @@ -210,9 +210,7 @@ def _handle_delete_service(org_id, service_id): cached_service_user_ids = [user.id for user in service.active_users] service_api_client.archive_service(service_id, cached_service_user_ids) - create_archive_service_event( - service_id=service_id, archived_by_id=current_user.id - ) + create_archive_service_event(service_id=service_id, archived_by_id=current_user.id) cache.redis_client.delete("organizations") diff --git a/tests/app/main/views/test_jobs_activity.py b/tests/app/main/views/test_jobs_activity.py index 09fcbbf31..8d14a1add 100644 --- a/tests/app/main/views/test_jobs_activity.py +++ b/tests/app/main/views/test_jobs_activity.py @@ -52,6 +52,9 @@ def test_all_activity( "app.job_api_client.get_page_of_jobs", return_value=MOCK_JOBS ) mocker.patch("app.job_api_client.get_immediate_jobs", return_value=[]) + mock_s3_obj = mocker.Mock() + mock_s3_obj.content_length = 0 + mocker.patch("app.s3_client.get_s3_object", return_value=mock_s3_obj) mocker.patch("app.s3_client.check_s3_file_exists", return_value=False) mocker.patch("app.s3_client.s3_csv_client.get_csv_upload", return_value=None) @@ -140,6 +143,9 @@ def test_all_activity_no_jobs(client_request, mocker): }, ) mocker.patch("app.job_api_client.get_immediate_jobs", return_value=[]) + mock_s3_obj = mocker.Mock() + mock_s3_obj.content_length = 0 + mocker.patch("app.s3_client.get_s3_object", return_value=mock_s3_obj) mocker.patch("app.s3_client.check_s3_file_exists", return_value=False) mocker.patch("app.s3_client.s3_csv_client.get_csv_upload", return_value=None) response = client_request.get_response( @@ -195,6 +201,9 @@ def test_all_activity_pagination(client_request, mocker): }, ) mocker.patch("app.job_api_client.get_immediate_jobs", return_value=[]) + mock_s3_obj = mocker.Mock() + mock_s3_obj.content_length = 0 + mocker.patch("app.s3_client.get_s3_object", return_value=mock_s3_obj) mocker.patch("app.s3_client.check_s3_file_exists", return_value=False) mocker.patch("app.s3_client.s3_csv_client.get_csv_upload", return_value=None) @@ -234,6 +243,9 @@ def test_all_activity_filters(client_request, mocker, filter_type, expected_limi "app.job_api_client.get_page_of_jobs", return_value=MOCK_JOBS ) mocker.patch("app.job_api_client.get_immediate_jobs", return_value=[]) + mock_s3_obj = mocker.Mock() + mock_s3_obj.content_length = 0 + mocker.patch("app.s3_client.get_s3_object", return_value=mock_s3_obj) mocker.patch("app.s3_client.check_s3_file_exists", return_value=False) mocker.patch("app.s3_client.s3_csv_client.get_csv_upload", return_value=None) diff --git a/tests/javascripts/organizationDashboard.test.js b/tests/javascripts/organizationDashboard.test.js new file mode 100644 index 000000000..70e0cd620 --- /dev/null +++ b/tests/javascripts/organizationDashboard.test.js @@ -0,0 +1,223 @@ +/** + * @jest-environment jsdom + */ + +describe('organizationDashboard.js', () => { + let originalLocation; + + beforeEach(() => { + jest.resetModules(); + jest.useFakeTimers(); + + originalLocation = window.location; + Element.prototype.scrollIntoView = jest.fn(); + window.requestAnimationFrame = jest.fn((cb) => setTimeout(cb, 0)); + }); + + afterEach(() => { + jest.useRealTimers(); + document.body.innerHTML = ''; + }); + + describe('Delete Service Confirmation', () => { + beforeEach(() => { + document.body.innerHTML = ` + +
+ +
+ `; + + require('../../app/assets/javascripts/organizationDashboard.js'); + document.dispatchEvent(new Event('DOMContentLoaded')); + }); + + test('sets service name when delete button clicked', () => { + const deleteButton = document.querySelector('[data-service-id="service-123"]'); + const nameDisplay = document.getElementById('delete-service-name-display'); + + deleteButton.click(); + + expect(nameDisplay.textContent).toBe('Test Service'); + }); + + test('does not update name display if missing', () => { + document.getElementById('delete-service-name-display').remove(); + + const deleteButton = document.querySelector('[data-service-id="service-123"]'); + + expect(() => deleteButton.click()).not.toThrow(); + }); + + test('does nothing if required elements missing', () => { + document.body.innerHTML = '
No delete elements
'; + + require('../../app/assets/javascripts/organizationDashboard.js'); + + expect(() => document.dispatchEvent(new Event('DOMContentLoaded'))).not.toThrow(); + }); + }); + + describe('Edit Service Confirmation', () => { + beforeEach(() => { + document.body.innerHTML = ` + +
+ `; + + require('../../app/assets/javascripts/organizationDashboard.js'); + document.dispatchEvent(new Event('DOMContentLoaded')); + }); + + test('submits edit form when confirm button clicked', () => { + const confirmButton = document.getElementById('edit-service-confirm-btn'); + const editForm = document.getElementById('edit-service-form'); + editForm.submit = jest.fn(); + + confirmButton.click(); + + expect(editForm.submit).toHaveBeenCalled(); + }); + + test('does not throw if form missing when confirm clicked', () => { + document.getElementById('edit-service-form').remove(); + const confirmButton = document.getElementById('edit-service-confirm-btn'); + + expect(() => confirmButton.click()).not.toThrow(); + }); + + test('does nothing if confirm button missing', () => { + document.body.innerHTML = '
'; + + require('../../app/assets/javascripts/organizationDashboard.js'); + + expect(() => document.dispatchEvent(new Event('DOMContentLoaded'))).not.toThrow(); + }); + }); + + describe('Form Initialization', () => { + test('scrolls to and focuses create service form', () => { + document.body.innerHTML = ` +
+ +
+ `; + + const form = document.getElementById('create-service-form'); + const input = form.querySelector('input[type="text"]'); + + require('../../app/assets/javascripts/organizationDashboard.js'); + document.dispatchEvent(new Event('DOMContentLoaded')); + + jest.runAllTimers(); + + expect(form.scrollIntoView).toHaveBeenCalled(); + expect(document.activeElement).toBe(input); + }); + + test('scrolls to and focuses invite user form', () => { + document.body.innerHTML = ` +
+ +
+ `; + + const form = document.getElementById('invite-user-form'); + const input = form.querySelector('input[type="email"]'); + + require('../../app/assets/javascripts/organizationDashboard.js'); + document.dispatchEvent(new Event('DOMContentLoaded')); + + jest.runAllTimers(); + + expect(form.scrollIntoView).toHaveBeenCalled(); + expect(document.activeElement).toBe(input); + }); + + test('scrolls to and focuses edit service form', () => { + document.body.innerHTML = ` +
+ +
+ `; + + const form = document.getElementById('edit-service-form'); + const input = form.querySelector('input[type="text"]'); + + require('../../app/assets/javascripts/organizationDashboard.js'); + document.dispatchEvent(new Event('DOMContentLoaded')); + + jest.runAllTimers(); + + expect(form.scrollIntoView).toHaveBeenCalled(); + expect(document.activeElement).toBe(input); + }); + + test('does nothing when no relevant forms present', () => { + document.body.innerHTML = '
No forms here
'; + + require('../../app/assets/javascripts/organizationDashboard.js'); + document.dispatchEvent(new Event('DOMContentLoaded')); + + jest.runAllTimers(); + + expect(Element.prototype.scrollIntoView).not.toHaveBeenCalled(); + }); + }); + + describe('Service Highlighting', () => { + test('scrolls to and removes highlight from service', () => { + document.body.innerHTML = ` +
+ `; + + const serviceRow = document.getElementById('service-test-123'); + + require('../../app/assets/javascripts/organizationDashboard.js'); + document.dispatchEvent(new Event('DOMContentLoaded')); + + window.OrganizationDashboard.highlightAndScrollToService('test-123'); + + jest.advanceTimersByTime(300); + expect(serviceRow.scrollIntoView).toHaveBeenCalled(); + + expect(serviceRow.classList.contains('is-highlighted')).toBe(true); + + jest.advanceTimersByTime(3100); + expect(serviceRow.classList.contains('is-highlighted')).toBe(false); + }); + + test('removes class attribute when empty after highlight removal', () => { + document.body.innerHTML = ` +
+ `; + + const serviceRow = document.getElementById('service-test-123'); + + require('../../app/assets/javascripts/organizationDashboard.js'); + document.dispatchEvent(new Event('DOMContentLoaded')); + + window.OrganizationDashboard.highlightAndScrollToService('test-123'); + + jest.advanceTimersByTime(3400); + expect(serviceRow.hasAttribute('class')).toBe(false); + }); + + test('handles missing service element gracefully', () => { + document.body.innerHTML = '
No service here
'; + + require('../../app/assets/javascripts/organizationDashboard.js'); + document.dispatchEvent(new Event('DOMContentLoaded')); + + expect(() => { + window.OrganizationDashboard.highlightAndScrollToService('nonexistent'); + jest.runAllTimers(); + }).not.toThrow(); + }); + }); +});