From 821914925f78d752d4b834aa1ca2af6395b2e42a Mon Sep 17 00:00:00 2001 From: santhosh-7777 Date: Sat, 2 May 2026 17:51:00 +0000 Subject: [PATCH 1/3] feat(#614): warn when using deprecated countdown-timer note appearance --- .../validation/form/note-countdown-timer.js | 37 +++++++ test/lib/validate-forms.spec.js | 5 + .../form/note-countdown-timer.spec.js | 104 ++++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 src/lib/validation/form/note-countdown-timer.js create mode 100644 test/lib/validation/form/note-countdown-timer.spec.js diff --git a/src/lib/validation/form/note-countdown-timer.js b/src/lib/validation/form/note-countdown-timer.js new file mode 100644 index 00000000..b9644ccd --- /dev/null +++ b/src/lib/validation/form/note-countdown-timer.js @@ -0,0 +1,37 @@ +const semver = require('semver'); +const { getNodes, getBindNodes } = require('../../forms-utils'); + +const validateNoteCountdownTimer = async ({ xformPath, xmlDoc, apiVersion }) => { + if (!apiVersion) { + return { errors: [], warnings: [] }; + } + if (semver.lt(apiVersion, '4.7.0')) { + return { errors: [], warnings: [] }; + } + + const bindNodes = getBindNodes(xmlDoc); + + const warnings = getNodes(xmlDoc, '/h:html/h:body//*[@appearance]') + .filter(node => node.getAttribute('appearance').match(/(?:^|\s)countdown-timer(?:$|\s)/)) + .map(node => node.getAttribute('ref')) + .filter(ref => { + const bind = bindNodes.find(b => b.getAttribute('nodeset') === ref); + return bind && bind.hasAttribute('readonly'); + }) + .map(ref => ` - ${ref}`); + + if (warnings.length) { + warnings.unshift( + `Form at ${xformPath} contains fields with the deprecated countdown-timer note appearance. ` + + 'Please update the following fields to use trigger fields instead:' + ); + } + + return { errors: [], warnings }; +}; + +module.exports = { + requiresInstance: true, + skipFurtherValidation: false, + execute: validateNoteCountdownTimer +}; \ No newline at end of file diff --git a/test/lib/validate-forms.spec.js b/test/lib/validate-forms.spec.js index b720487d..5a754751 100644 --- a/test/lib/validate-forms.spec.js +++ b/test/lib/validate-forms.spec.js @@ -77,6 +77,11 @@ describe('validate-forms', () => { expect(noRequiredNotes.requiresInstance).to.equal(false); expect(noRequiredNotes.skipFurtherValidation).to.equal(false); + const noteCountdownTimer = validations.shift(); + expect(noteCountdownTimer.name).to.equal('note-countdown-timer.js'); + expect(noteCountdownTimer.requiresInstance).to.equal(true); + expect(noteCountdownTimer.skipFurtherValidation).to.equal(false); + expect(validations, 'Update this test if you have added a new form validation.').to.be.empty; }); diff --git a/test/lib/validation/form/note-countdown-timer.spec.js b/test/lib/validation/form/note-countdown-timer.spec.js new file mode 100644 index 00000000..ad03deaa --- /dev/null +++ b/test/lib/validation/form/note-countdown-timer.spec.js @@ -0,0 +1,104 @@ +const { expect } = require('chai'); +const { DOMParser } = require('@xmldom/xmldom'); +const noteCountdownTimer = require('../../../../src/lib/validation/form/note-countdown-timer'); + +const domParser = new DOMParser(); +const xformPath = 'test/form/path.xml'; + +const getXml = ({ deprecated = false, newStyle = false } = {}) => ` + + + + + + + ${deprecated ? '15' : ''} + ${newStyle ? '' : ''} + + + ${deprecated ? '' : ''} + ${newStyle ? '' : ''} + + + + ${deprecated ? '' : ''} + ${newStyle ? '' : ''} + +`; + +const getXmlDoc = (opts) => domParser.parseFromString(getXml(opts), 'text/xml'); + +const LATEST_VERSION = '999.99.99'; +const ERROR_HEADER = `Form at ${xformPath} contains fields with the deprecated countdown-timer note appearance. ` + + 'Please update the following fields to use trigger fields instead:'; + +describe('note-countdown-timer', () => { + it('resolves OK when no countdown-timer fields present', () => { + return noteCountdownTimer + .execute({ xformPath, xmlDoc: getXmlDoc(), apiVersion: LATEST_VERSION }) + .then(({ errors, warnings }) => { + expect(errors).is.empty; + expect(warnings).is.empty; + }); + }); + + it('returns warning for deprecated countdown-timer note field', () => { + return noteCountdownTimer + .execute({ xformPath, xmlDoc: getXmlDoc({ deprecated: true }), apiVersion: LATEST_VERSION }) + .then(({ errors, warnings }) => { + expect(errors).is.empty; + expect(warnings).to.have.length(2); + expect(warnings[0]).to.equal(ERROR_HEADER); + expect(warnings[1]).to.equal(' - /data/deprecated_timer'); + }); + }); + + it('resolves OK for new trigger style countdown-timer', () => { + return noteCountdownTimer + .execute({ xformPath, xmlDoc: getXmlDoc({ newStyle: true }), apiVersion: LATEST_VERSION }) + .then(({ errors, warnings }) => { + expect(errors).is.empty; + expect(warnings).is.empty; + }); + }); + + it('returns warning only for deprecated when both deprecated and new style present', () => { + return noteCountdownTimer + .execute({ xformPath, xmlDoc: getXmlDoc({ deprecated: true, newStyle: true }), apiVersion: LATEST_VERSION }) + .then(({ errors, warnings }) => { + expect(errors).is.empty; + expect(warnings).to.have.length(2); + expect(warnings[0]).to.equal(ERROR_HEADER); + expect(warnings[1]).to.equal(' - /data/deprecated_timer'); + }); + }); + + it('resolves OK when apiVersion is below 4.7.0', () => { + return noteCountdownTimer + .execute({ xformPath, xmlDoc: getXmlDoc({ deprecated: true }), apiVersion: '4.6.0' }) + .then(({ errors, warnings }) => { + expect(errors).is.empty; + expect(warnings).is.empty; + }); + }); + + it('resolves OK when no apiVersion provided', () => { + return noteCountdownTimer + .execute({ xformPath, xmlDoc: getXmlDoc({ deprecated: true }) }) + .then(({ errors, warnings }) => { + expect(errors).is.empty; + expect(warnings).is.empty; + }); + }); + + it('returns warning when apiVersion is exactly 4.7.0', () => { + return noteCountdownTimer + .execute({ xformPath, xmlDoc: getXmlDoc({ deprecated: true }), apiVersion: '4.7.0' }) + .then(({ errors, warnings }) => { + expect(errors).is.empty; + expect(warnings).to.have.length(2); + expect(warnings[0]).to.equal(ERROR_HEADER); + expect(warnings[1]).to.equal(' - /data/deprecated_timer'); + }); + }); +}); \ No newline at end of file From 2b674a5bc82cc7545ad4d49d391829de0c41540c Mon Sep 17 00:00:00 2001 From: santhosh-7777 Date: Sat, 2 May 2026 18:04:15 +0000 Subject: [PATCH 2/3] refactor(#614): use optional chaining for bind check --- src/lib/validation/form/note-countdown-timer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/validation/form/note-countdown-timer.js b/src/lib/validation/form/note-countdown-timer.js index b9644ccd..08c281e5 100644 --- a/src/lib/validation/form/note-countdown-timer.js +++ b/src/lib/validation/form/note-countdown-timer.js @@ -16,7 +16,7 @@ const validateNoteCountdownTimer = async ({ xformPath, xmlDoc, apiVersion }) => .map(node => node.getAttribute('ref')) .filter(ref => { const bind = bindNodes.find(b => b.getAttribute('nodeset') === ref); - return bind && bind.hasAttribute('readonly'); + return bind?.hasAttribute('readonly'); }) .map(ref => ` - ${ref}`); From 376bbaf40cafe940ed819dc5d5b5cac191b2d11d Mon Sep 17 00:00:00 2001 From: santhosh-7777 Date: Tue, 5 May 2026 13:30:00 +0000 Subject: [PATCH 3/3] fix: eslint issues --- src/lib/validation/form/note-countdown-timer.js | 2 +- test/lib/validation/form/note-countdown-timer.spec.js | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/lib/validation/form/note-countdown-timer.js b/src/lib/validation/form/note-countdown-timer.js index 08c281e5..bfd1dc27 100644 --- a/src/lib/validation/form/note-countdown-timer.js +++ b/src/lib/validation/form/note-countdown-timer.js @@ -34,4 +34,4 @@ module.exports = { requiresInstance: true, skipFurtherValidation: false, execute: validateNoteCountdownTimer -}; \ No newline at end of file +}; diff --git a/test/lib/validation/form/note-countdown-timer.spec.js b/test/lib/validation/form/note-countdown-timer.spec.js index ad03deaa..12347220 100644 --- a/test/lib/validation/form/note-countdown-timer.spec.js +++ b/test/lib/validation/form/note-countdown-timer.spec.js @@ -21,8 +21,10 @@ const getXml = ({ deprecated = false, newStyle = false } = {}) => ` - ${deprecated ? '' : ''} - ${newStyle ? '' : ''} + ${deprecated + ? '' : ''} + ${newStyle + ? '' : ''} `; @@ -101,4 +103,4 @@ describe('note-countdown-timer', () => { expect(warnings[1]).to.equal(' - /data/deprecated_timer'); }); }); -}); \ No newline at end of file +});