diff --git a/tests/e2e/default/reports/sidebar-filter.wdio-spec.js b/tests/e2e/default/reports/sidebar-filter.wdio-spec.js
index ad00de036e3..0dc9e89c489 100644
--- a/tests/e2e/default/reports/sidebar-filter.wdio-spec.js
+++ b/tests/e2e/default/reports/sidebar-filter.wdio-spec.js
@@ -197,8 +197,39 @@ describe('Reports Sidebar Filter', () => {
await reportsPage.openSidebarFilter();
await reportsPage.openSidebarFilterDateAccordion();
+
+ // 1. Max Date parity - verify future dates are disabled
+ await $('#fromDateFilter').click();
+ await reportsPage.waitForNepaliDatePickerDisplayed();
+
+ // Future dates in the current month should be disabled
+ const disableCells = await reportsPage.getDisabledNepaliDateCells();
+ expect(disableCells.length).to.be.greaterThan(0);
+
+ // 2. Escape Dismissal - press Escape and verify picker is dismissed
+ await browser.keys(['Escape']);
+ const picker = reportsPage.getNepaliDatePicker();
+ expect(await picker.isDisplayed()).to.be.false;
+
+ // 3. Dismiss by clicking outside (e.g. the accordion header)
+ await $('#fromDateFilter').click();
+ expect(await picker.isDisplayed()).to.be.true;
+ const accordionHeader = await $('#date-filter-accordion mat-expansion-panel-header');
+ await accordionHeader.click();
+ expect(await picker.isDisplayed()).to.be.false;
+
+ // 4. Select From and To dates
await reportsPage.setSidebarFilterBikFromDate();
await reportsPage.setSidebarFilterBikToDate();
+
+ // 5. Reopen active selection verification
+ await $('#toDateFilter').click();
+ // Verify that the active selected date is highlighted correctly
+ expect(await reportsPage.isNepaliDatePickerActiveCellDisplayed()).to.be.true;
+
+ // Dismiss it
+ await browser.keys(['Escape']);
+
await commonPage.waitForPageLoaded();
expect(await reportsPage.leftPanelSelectors.allReports().length).to.equal(2);
diff --git a/tests/page-objects/default/common/common.wdio.page.js b/tests/page-objects/default/common/common.wdio.page.js
index db3bb30e709..365b6c533cb 100644
--- a/tests/page-objects/default/common/common.wdio.page.js
+++ b/tests/page-objects/default/common/common.wdio.page.js
@@ -178,7 +178,7 @@ const waitForLoaders = async (timeout = 5000) => {
});
};
-const waitForAngularLoaded = async (timeout = 40000) => {
+const waitForAngularLoaded = async (timeout = 60000) => {
await hamburgerMenuSelectors.hamburgerMenu().waitForDisplayed({ timeout });
};
diff --git a/tests/page-objects/default/reports/reports.wdio.page.js b/tests/page-objects/default/reports/reports.wdio.page.js
index 17d706cd2c8..a8aff48e7d3 100644
--- a/tests/page-objects/default/reports/reports.wdio.page.js
+++ b/tests/page-objects/default/reports/reports.wdio.page.js
@@ -237,6 +237,24 @@ const filterByDate = async (startDate, endDate) => {
await $('#freetext').click(); // blur the datepicker
};
+const clickSidebarFilterFromDate = async () => {
+ const el = sidebarFilterSelectors.fromDate();
+ await el.waitForClickable();
+ await el.click();
+};
+
+const clickSidebarFilterToDate = async () => {
+ const el = sidebarFilterSelectors.toDate();
+ await el.waitForClickable();
+ await el.click();
+};
+
+const clickSidebarFilterDateAccordionHeader = async () => {
+ const el = sidebarFilterSelectors.dateAccordionHeader();
+ await el.waitForClickable();
+ await el.click();
+};
+
const openSidebarFilter = async () => {
if (!await sidebarFilterSelectors.resetBtn().isDisplayed()) {
await sidebarFilterSelectors.openBtn().click();
@@ -488,7 +506,34 @@ const waitForReportsLoaded = async (timeout) => {
);
};
+const getNepaliDatePicker = () => $('.nepali-date-picker');
+const getNepaliDatePickerOverlay = () => $('.nepali-date-picker-overlay');
+
+const waitForNepaliDatePickerDisplayed = async () => {
+ const picker = getNepaliDatePicker();
+ await browser.waitUntil(async () => {
+ return await picker.isDisplayed();
+ }, { timeout: 5000, timeoutMsg: 'Nepali date picker not displayed' });
+ return picker;
+};
+
+const getDisabledNepaliDateCells = async () => {
+ const picker = getNepaliDatePicker();
+ return await picker.$$('table tbody td.current-month-date.disable');
+};
+
+const isNepaliDatePickerActiveCellDisplayed = async () => {
+ const picker = getNepaliDatePicker();
+ const activeCell = picker.$('table tbody td.current-month-date.active');
+ return await activeCell.isDisplayed();
+};
+
module.exports = {
+ getNepaliDatePicker,
+ getNepaliDatePickerOverlay,
+ waitForNepaliDatePickerDisplayed,
+ getDisabledNepaliDateCells,
+ isNepaliDatePickerActiveCellDisplayed,
leftPanelSelectors,
rightPanelSelectors,
deleteDialogSelectors,
@@ -503,6 +548,9 @@ module.exports = {
setSidebarFilterToDate,
setSidebarFilterBikFromDate,
setSidebarFilterBikToDate,
+ clickSidebarFilterFromDate,
+ clickSidebarFilterToDate,
+ clickSidebarFilterDateAccordionHeader,
filterByForm,
filterByFacility,
filterByStatus,
diff --git a/webapp/tests/karma/js/enketo/widgets/bikram-sambat-datepicker.spec.ts b/webapp/tests/karma/js/enketo/widgets/bikram-sambat-datepicker.spec.ts
index 18309b9aa26..50943aeca5a 100644
--- a/webapp/tests/karma/js/enketo/widgets/bikram-sambat-datepicker.spec.ts
+++ b/webapp/tests/karma/js/enketo/widgets/bikram-sambat-datepicker.spec.ts
@@ -277,4 +277,71 @@ describe('Enketo: Bikram Sambat Datepicker Widget', () => {
expect($('.nepali-date-picker').is(':visible')).to.be.true;
});
+
+ it('highlights the correct active day corresponding to the input value when reopened', async () => {
+ await initWidget();
+ dayInput().val('१५');
+ monthInput().val('३');
+ yearInput().val('२०८१');
+
+ // Click to open calendar
+ $('.calendar-btn').click();
+
+ // Verify the correct day (15) is highlighted with the "active" class
+ let activeCell = $('.nepali-date-picker table tbody td.current-month-date.active');
+ expect(activeCell.text()).to.equal('१५');
+
+ // Close and change day to 20
+ $('.nepali-date-picker-overlay').click();
+ dayInput().val('२०').trigger('change');
+
+ // Reopen
+ $('.calendar-btn').click();
+
+ // Verify the new day (20) is highlighted with the "active" class
+ activeCell = $('.nepali-date-picker table tbody td.current-month-date.active');
+ expect(activeCell.text()).to.equal('२०');
+ });
+
+ it('updates the position of the calendar popup on window scroll when positioned as anchored', () => {
+ const { setupNepaliDatePicker } = require('../../../../../src/js/enketo/widgets/bikram-sambat-picker-shared');
+
+ // Set up a test input and trigger in the DOM
+ const html = `
+
+
+
+
+ `;
+ $('body').append(html);
+
+ const hiddenInput = $('#scroll-hidden-input');
+
+ // Call setupNepaliDatePicker with position: 'anchored'
+ setupNepaliDatePicker(hiddenInput, {
+ position: 'anchored',
+ closeOnDateSelect: false
+ });
+
+ // Open the picker
+ hiddenInput.click();
+
+ const picker = $('.nepali-date-picker');
+ expect(picker).to.have.lengthOf(1);
+
+ // Set initial style
+ picker.css({ top: '100px', left: '50px' });
+
+ // Simulate scroll event
+ const event = new Event('scroll');
+ window.dispatchEvent(event);
+
+ // Position should be updated/recalculated
+ expect(picker.css('top')).to.not.equal('100px');
+
+ // Clean up
+ hiddenInput.remove();
+ $('#scroll-test-wrapper').remove();
+ $('.nepali-date-picker').remove();
+ });
});
diff --git a/webapp/tests/karma/ts/components/filters/date-filter.component.spec.ts b/webapp/tests/karma/ts/components/filters/date-filter.component.spec.ts
index 5ad7c327000..a801b37727c 100644
--- a/webapp/tests/karma/ts/components/filters/date-filter.component.spec.ts
+++ b/webapp/tests/karma/ts/components/filters/date-filter.component.spec.ts
@@ -16,6 +16,7 @@ import { Selectors } from '@mm-selectors/index';
import { ResponsiveService } from '@mm-services/responsive.service';
import { LanguageService } from '@mm-services/language.service';
import { FormatDateService } from '@mm-services/format-date.service';
+import { toBik_dev } from 'bikram-sambat';
describe('Date Filter Component', () => {
let component: DateFilterComponent;
@@ -195,7 +196,8 @@ describe('Date Filter Component', () => {
expect(nepaliDatePickerStub.callCount).to.equal(1);
const maxDateVal = nepaliDatePickerStub.args[0][0].maxDate;
- expect(maxDateVal).to.match(/^[०-९-]+$/);
+ const expectedMaxDate = toBik_dev(moment().clone().locale('en').format('YYYY-MM-DD'));
+ expect(maxDateVal).to.equal(expectedMaxDate);
const hiddenInput = $('#bikram-sambat-test-wrapper .nepali-datepicker-input');
expect(hiddenInput).to.have.lengthOf(1);
@@ -254,7 +256,7 @@ describe('Date Filter Component', () => {
expect(setFilter.callCount).to.equal(1);
const appliedDate = setFilter.args[0][0].date.from;
- expect(moment(appliedDate).format('YYYY-MM-DD')).to.equal('2024-07-24');
+ expect(moment(appliedDate).format('YYYY-MM-DD HH:mm:ss.SSS')).to.equal('2024-07-24 00:00:00.000');
setFilter.restore();
});
@@ -300,6 +302,133 @@ describe('Date Filter Component', () => {
expect($('.nepali-date-picker-overlay')).to.have.lengthOf(0);
});
+ it('should maintain independent picker elements for From and To components and leave each other unchanged', () => {
+ // Create From component
+ const fixtureFrom = TestBed.createComponent(DateFilterComponent);
+ const compFrom = fixtureFrom.componentInstance;
+ compFrom.isStartDate = true;
+ compFrom.fieldId = 'from-test-field';
+
+ const htmlFrom = ``;
+ document.body.insertAdjacentHTML('afterbegin', htmlFrom);
+ fixtureFrom.detectChanges();
+
+ // Create To component
+ const fixtureTo = TestBed.createComponent(DateFilterComponent);
+ const compTo = fixtureTo.componentInstance;
+ compTo.isStartDate = false;
+ compTo.fieldId = 'to-test-field';
+
+ const htmlTo = ``;
+ document.body.insertAdjacentHTML('afterbegin', htmlTo);
+ fixtureTo.detectChanges();
+
+ // Verify each appended its own hidden input
+ const fromHiddenInput = $('#from-test-wrapper .nepali-datepicker-input');
+ const toHiddenInput = $('#to-test-wrapper .nepali-datepicker-input');
+ expect(fromHiddenInput).to.have.lengthOf(1);
+ expect(toHiddenInput).to.have.lengthOf(1);
+
+ // Simulate date selection on From component
+ const setFilter = sinon.stub(GlobalActions.prototype, 'setFilter');
+ const eventFrom = $.Event('dateSelect');
+ (eventFrom as any).datePickerData = { bsYear: 2081, bsMonth: 4, bsDate: 9 };
+ fromHiddenInput.trigger(eventFrom);
+
+ // Verify From component's selection was registered
+ expect(setFilter.callCount).to.equal(1);
+ expect(setFilter.firstCall.args[0].date.from).to.not.be.undefined;
+ expect(setFilter.firstCall.args[0].date.to).to.be.undefined;
+
+ // Clear call history
+ setFilter.resetHistory();
+
+ // Simulate date selection on To component
+ const eventTo = $.Event('dateSelect');
+ (eventTo as any).datePickerData = { bsYear: 2081, bsMonth: 4, bsDate: 20 };
+ toHiddenInput.trigger(eventTo);
+
+ // Verify To component's selection was registered independently
+ expect(setFilter.callCount).to.equal(1);
+ expect(setFilter.firstCall.args[0].date.to).to.not.be.undefined;
+ expect(setFilter.firstCall.args[0].date.from).to.be.undefined;
+
+ setFilter.restore();
+
+ // Clean up DOM
+ compFrom.ngOnDestroy();
+ compTo.ngOnDestroy();
+ $('#from-test-wrapper').remove();
+ $('#to-test-wrapper').remove();
+ });
+
+ it('clearing/resetting the date filter should set the date to undefined', () => {
+ component.ngAfterViewInit();
+ const setFilter = sinon.stub(GlobalActions.prototype, 'setFilter');
+ component.dateRange = { from: moment('2024-07-24').valueOf(), to: undefined };
+
+ component.clear();
+
+ expect(setFilter.callCount).to.equal(1);
+ expect(setFilter.args[0]).to.deep.equal([{ date: undefined }]);
+ setFilter.restore();
+ });
+
+ it('ngOnDestroy should clean up multiple pickers and overlays completely', () => {
+ // Create two DOM wrappers
+ const html1 = ``;
+ const html2 = ``;
+ document.body.insertAdjacentHTML('afterbegin', html1);
+ document.body.insertAdjacentHTML('afterbegin', html2);
+
+ // Instantiate and setup Component B
+ const fixtureB = TestBed.createComponent(DateFilterComponent);
+ const compB = fixtureB.componentInstance;
+ compB.fieldId = 'field-2';
+ compB.ngOnInit();
+
+ // Setup Component A
+ component.fieldId = 'field-1';
+
+ // Initialize both components (which creates hidden inputs)
+ component.ngAfterViewInit();
+ compB.ngAfterViewInit();
+
+ // Create the pickers and overlay in the DOM
+ const pickerA = $('').appendTo('body');
+ const pickerB = $('').appendTo('body');
+ $('').appendTo('body');
+
+ // Bind pickers to their respective hidden inputs
+ $('#test-wrapper-1 .nepali-datepicker-input').data('picker', pickerA);
+ $('#test-wrapper-2 .nepali-datepicker-input').data('picker', pickerB);
+
+ // 1. Destroy Component A
+ component.ngOnDestroy();
+
+ // Assert Component A's elements are removed
+ expect($('#test-wrapper-1 .nepali-datepicker-input')).to.have.lengthOf(0);
+ expect($('#picker-a')).to.have.lengthOf(0);
+
+ // Assert Component B's elements and overlay still survive
+ expect($('#test-wrapper-2 .nepali-datepicker-input')).to.have.lengthOf(1);
+ expect($('#picker-b')).to.have.lengthOf(1);
+ expect($('.nepali-date-picker-overlay')).to.have.lengthOf(1);
+
+ // 2. Destroy Component B
+ compB.ngOnDestroy();
+
+ // Assert Component B's elements and overlay are removed
+ expect($('#test-wrapper-2 .nepali-datepicker-input')).to.have.lengthOf(0);
+ expect($('#picker-b')).to.have.lengthOf(0);
+ expect($('.nepali-date-picker-overlay')).to.have.lengthOf(0);
+
+ // Clean up wrapper DOM nodes
+ $('#test-wrapper-1').remove();
+ $('#test-wrapper-2').remove();
+ });
+
+
it('setLabel should use formatDateService.dayMonth', () => {
component.isStartDate = true;
component.setLabel({ from: moment('2024-07-24').valueOf(), to: undefined });
@@ -320,5 +449,6 @@ describe('Date Filter Component', () => {
expect(formatDateService.dayMonth.args[0][0]).to.equal(startOfDayVal);
expect(component.inputLabel).to.equal('Formatted Date');
});
+
});
});