|
1 | 1 | import React from 'react'; |
2 | 2 | import dayjs from 'dayjs'; |
3 | 3 | import userEvent from '@testing-library/user-event'; |
4 | | -import { render, screen } from '@testing-library/react'; |
| 4 | +import { render, screen, waitFor } from '@testing-library/react'; |
5 | 5 | import { |
6 | 6 | getDefaultsFromConfigSchema, |
7 | 7 | showSnackbar, |
@@ -114,10 +114,11 @@ describe('Immunizations Form', () => { |
114 | 114 | mockToDateObjectStrict.mockImplementation((dateString) => dayjs(dateString, isoFormat).toDate()); |
115 | 115 | }); |
116 | 116 |
|
117 | | - it('should render ImmunizationsForm component', () => { |
| 117 | + it('should render ImmunizationsForm component', async () => { |
118 | 118 | render(<ImmunizationsForm {...testProps} />); |
119 | 119 |
|
120 | | - expect(screen.getByLabelText(/vaccination date/i)).toBeInTheDocument(); |
| 120 | + await screen.findByLabelText(/vaccination date/i); |
| 121 | + |
121 | 122 | expect(screen.getByRole('combobox', { name: /Immunization/i })).toBeInTheDocument(); |
122 | 123 | expect(screen.getByRole('textbox', { name: /note/i })).toBeInTheDocument(); |
123 | 124 | expect(screen.getByText(/Vaccine Batch Information/i)).toBeInTheDocument(); |
@@ -230,10 +231,10 @@ describe('Immunizations Form', () => { |
230 | 231 | const immunizationToEdit = { |
231 | 232 | vaccineUuid: '886AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', |
232 | 233 | immunizationId: '0a6ca2bb-a317-49d8-bd6b-dabb658840d2', |
233 | | - vaccinationDate: new Date('2024-01-03').toString(), |
| 234 | + vaccinationDate: new Date('2024-01-03').toISOString(), |
234 | 235 | doseNumber: 2, |
235 | | - expirationDate: new Date('2024-05-19').toString(), |
236 | | - nextDoseDate: new Date('2024-01-03').toString(), |
| 236 | + expirationDate: new Date('2024-05-19').toISOString(), |
| 237 | + nextDoseDate: new Date('2024-01-03').toISOString(), |
237 | 238 | note: 'Given as part of routine schedule.', |
238 | 239 | lotNumber: 'A123456', |
239 | 240 | manufacturer: 'Merck & Co., Inc.', |
@@ -283,7 +284,7 @@ describe('Immunizations Form', () => { |
283 | 284 | expect.objectContaining({ |
284 | 285 | encounter: { reference: 'Encounter/ce589c9c-2f30-42ec-b289-a153f812ea5e', type: 'Encounter' }, |
285 | 286 | id: '0a6ca2bb-a317-49d8-bd6b-dabb658840d2', |
286 | | - expirationDate: dayjs(new Date('2024-05-19')).startOf('day').toDate().toISOString(), |
| 287 | + expirationDate: '2024-05-19', |
287 | 288 | extension: [ |
288 | 289 | { |
289 | 290 | url: FHIR_NEXT_DOSE_DATE_EXTENSION_URL, |
@@ -316,6 +317,150 @@ describe('Immunizations Form', () => { |
316 | 317 | title: 'Vaccination saved successfully', |
317 | 318 | }); |
318 | 319 | }); |
| 320 | + |
| 321 | + it('should save new immunization with expiration date in correct format', async () => { |
| 322 | + const user = userEvent.setup(); |
| 323 | + |
| 324 | + // Pre-populate form with expiration date using the form subscription (same pattern as edit tests) |
| 325 | + const immunizationWithExpiration = { |
| 326 | + vaccineUuid: '782AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', |
| 327 | + vaccinationDate: new Date('2024-06-15').toISOString(), |
| 328 | + doseNumber: 1, |
| 329 | + expirationDate: new Date('2025-12-31').toISOString(), |
| 330 | + manufacturer: 'Pfizer', |
| 331 | + lotNumber: 'LOT123', |
| 332 | + note: '', |
| 333 | + nextDoseDate: null, |
| 334 | + }; |
| 335 | + |
| 336 | + immunizationFormSub.next(immunizationWithExpiration); |
| 337 | + |
| 338 | + mockSavePatientImmunization.mockResolvedValue({ |
| 339 | + status: 201, |
| 340 | + ok: true, |
| 341 | + data: { |
| 342 | + id: 'new-immunization-id', |
| 343 | + }, |
| 344 | + }); |
| 345 | + |
| 346 | + render(<ImmunizationsForm {...testProps} />); |
| 347 | + |
| 348 | + // Verify the form is populated |
| 349 | + const expirationDateField = screen.getByRole('textbox', { name: /Expiration date/i }); |
| 350 | + expect(expirationDateField).toHaveValue('31/12/2025'); |
| 351 | + |
| 352 | + // Submit without making changes |
| 353 | + const saveButton = screen.getByRole('button', { name: /Save/i }); |
| 354 | + await user.click(saveButton); |
| 355 | + |
| 356 | + // Verify that expirationDate is formatted as YYYY-MM-DD without timezone |
| 357 | + expect(mockSavePatientImmunization).toHaveBeenCalledWith( |
| 358 | + expect.objectContaining({ |
| 359 | + expirationDate: '2025-12-31', // Date-only format, not ISO string with time/timezone |
| 360 | + lotNumber: 'LOT123', |
| 361 | + manufacturer: { display: 'Pfizer' }, |
| 362 | + }), |
| 363 | + undefined, |
| 364 | + expect.any(AbortController), |
| 365 | + ); |
| 366 | + }); |
| 367 | + |
| 368 | + it('should format expiration date as date-only string without timezone', async () => { |
| 369 | + const user = userEvent.setup(); |
| 370 | + |
| 371 | + // Regression test for O3-4970: |
| 372 | + // Previously, expiration dates were converted to ISO strings with timezone (e.g., "2025-12-31T00:00:00.000Z"), |
| 373 | + // causing a one-day shift for users in timezones ahead of UTC. This test ensures dates are sent as |
| 374 | + // date-only strings (e.g., "2025-12-31") per FHIR date type specification, preventing timezone conversion. |
| 375 | + |
| 376 | + // Setup immunization with expiration date |
| 377 | + const immunizationWithExpiration = { |
| 378 | + vaccineUuid: '782AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', |
| 379 | + immunizationId: 'test-immunization-with-expiration', |
| 380 | + vaccinationDate: new Date('2024-12-25').toISOString(), |
| 381 | + doseNumber: 1, |
| 382 | + expirationDate: new Date('2025-12-31').toISOString(), |
| 383 | + manufacturer: 'Test Manufacturer', |
| 384 | + lotNumber: 'LOT123', |
| 385 | + note: 'Test note', |
| 386 | + nextDoseDate: null, |
| 387 | + }; |
| 388 | + |
| 389 | + immunizationFormSub.next(immunizationWithExpiration); |
| 390 | + |
| 391 | + mockSavePatientImmunization.mockResolvedValue({ |
| 392 | + status: 201, |
| 393 | + ok: true, |
| 394 | + data: { |
| 395 | + id: immunizationWithExpiration.immunizationId, |
| 396 | + }, |
| 397 | + }); |
| 398 | + |
| 399 | + render(<ImmunizationsForm {...testProps} />); |
| 400 | + |
| 401 | + // Verify the expiration date is displayed correctly |
| 402 | + const expirationDateField = screen.getByRole('textbox', { name: /Expiration date/i }); |
| 403 | + expect(expirationDateField).toHaveValue('31/12/2025'); |
| 404 | + |
| 405 | + // Submit the form without changes to verify the date format is preserved |
| 406 | + const saveButton = screen.getByRole('button', { name: /Save/i }); |
| 407 | + await user.click(saveButton); |
| 408 | + |
| 409 | + // Verify that expirationDate is formatted as YYYY-MM-DD without timezone (not ISO string) |
| 410 | + expect(mockSavePatientImmunization).toHaveBeenCalledWith( |
| 411 | + expect.objectContaining({ |
| 412 | + expirationDate: '2025-12-31', // Date-only format, not ISO string with time/timezone |
| 413 | + }), |
| 414 | + immunizationWithExpiration.immunizationId, |
| 415 | + expect.any(AbortController), |
| 416 | + ); |
| 417 | + }); |
| 418 | + |
| 419 | + it('should preserve date format when submitting immunization with different expiration date', async () => { |
| 420 | + const user = userEvent.setup(); |
| 421 | + |
| 422 | + // Load existing immunization with a different expiration date |
| 423 | + const immunizationToEdit = { |
| 424 | + vaccineUuid: '782AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', |
| 425 | + immunizationId: 'existing-immunization-id', |
| 426 | + vaccinationDate: new Date('2024-06-15').toISOString(), |
| 427 | + doseNumber: 1, |
| 428 | + expirationDate: new Date('2026-06-15').toISOString(), |
| 429 | + manufacturer: 'Moderna', |
| 430 | + lotNumber: 'ABC123', |
| 431 | + note: 'Initial note', |
| 432 | + nextDoseDate: null, |
| 433 | + }; |
| 434 | + |
| 435 | + immunizationFormSub.next(immunizationToEdit); |
| 436 | + |
| 437 | + mockSavePatientImmunization.mockResolvedValue({ |
| 438 | + status: 201, |
| 439 | + ok: true, |
| 440 | + data: { |
| 441 | + id: immunizationToEdit.immunizationId, |
| 442 | + }, |
| 443 | + }); |
| 444 | + |
| 445 | + render(<ImmunizationsForm {...testProps} />); |
| 446 | + |
| 447 | + // Verify expiration date is displayed |
| 448 | + const expirationDateField = screen.getByRole('textbox', { name: /Expiration date/i }); |
| 449 | + expect(expirationDateField).toHaveValue('15/06/2026'); |
| 450 | + |
| 451 | + // Submit the form |
| 452 | + const saveButton = screen.getByRole('button', { name: /Save/i }); |
| 453 | + await user.click(saveButton); |
| 454 | + |
| 455 | + // Verify the date is sent in correct format (YYYY-MM-DD, not ISO string) |
| 456 | + expect(mockSavePatientImmunization).toHaveBeenCalledWith( |
| 457 | + expect.objectContaining({ |
| 458 | + expirationDate: '2026-06-15', // Date-only format, not ISO string with time/timezone |
| 459 | + }), |
| 460 | + immunizationToEdit.immunizationId, |
| 461 | + expect.any(AbortController), |
| 462 | + ); |
| 463 | + }); |
319 | 464 | }); |
320 | 465 |
|
321 | 466 | async function selectOption(dropdown: HTMLElement, optionLabel: string) { |
|
0 commit comments