From d80d131a928fa915df439872364302fd57f1bf51 Mon Sep 17 00:00:00 2001 From: Eric Kwoka <43540491+ekwoka@users.noreply.github.com> Date: Sun, 9 Jun 2024 20:20:03 +0400 Subject: [PATCH 1/4] :recycle: Refactors away complexity --- packages/mask/builds/module.js | 4 +- packages/mask/src/index.js | 149 ++++-------------- .../cypress/integration/plugins/mask.spec.js | 12 +- 3 files changed, 35 insertions(+), 130 deletions(-) diff --git a/packages/mask/builds/module.js b/packages/mask/builds/module.js index e09509cde..c49f3a1c7 100644 --- a/packages/mask/builds/module.js +++ b/packages/mask/builds/module.js @@ -1,5 +1,5 @@ -import mask, { stripDown, buildUp } from '../src/index.js' +import mask, { stripDown } from '../src/index.js' export default mask -export { mask, stripDown, buildUp } +export { mask, stripDown } diff --git a/packages/mask/src/index.js b/packages/mask/src/index.js index 3d14e43e0..5d451c15d 100644 --- a/packages/mask/src/index.js +++ b/packages/mask/src/index.js @@ -74,7 +74,7 @@ export default function (Alpine) { } let setInput = () => { - lastInputValue = el.value = formatInput(input, template) + lastInputValue = el.value = formatInput(template,input) } if (shouldRestoreCursor) { @@ -88,16 +88,6 @@ export default function (Alpine) { setInput() } } - - function formatInput(input, template) { - // Let empty inputs be empty inputs. - if (input === '') return '' - - let strippedDownInput = stripDown(template, input) - let rebuiltInput = buildUp(template, strippedDownInput) - - return rebuiltInput - } }).before('model') } @@ -109,137 +99,52 @@ export function restoreCursorPosition(el, template, callback) { let beforeLeftOfCursorBeforeFormatting = unformattedValue.slice(0, cursorPosition) - let newPosition = buildUp( - template, stripDown( + let newPosition = formatInput( template, beforeLeftOfCursorBeforeFormatting - ) ).length el.setSelectionRange(newPosition, newPosition) } +let regexes = { + '9': /[0-9]/, + 'a': /[a-zA-Z]/, + '*': /[a-zA-Z0-9]/, +}; export function stripDown(template, input) { - let inputToBeStripped = input let output = '' - let regexes = { - '9': /[0-9]/, - '¤': /[0-9]/, - 'a': /[a-zA-Z]/, - '*': /[a-zA-Z0-9]/, - } - let wildcardTemplate = ""; - - // Check for money format and skip over autocomplete cases (as sufficiently handled by formatMoney) - if (!template.includes('¤')) { - - // Compile wildcard template - for (let i = 0; i < template.length; i++) { - if (["9", "a", "*"].includes(template[i])) { - wildcardTemplate += template[i]; - } - } - - //Case 1: template and input are the same length (Autocomplete case 1) - if (template.length === input.length) { - for (let i = 0; i < template.length; i++) { - - if (["9", "a", "*"].includes(template[i]) && regexes[template[i]].test(input[i])) { - - output += input[i]; - } - } - - if(output.length === wildcardTemplate.length) { - return output; - } - - } - - // Case 2: We have a match on the wildcard template length to the input length (Autocomplete case 2) - - if (wildcardTemplate.length === input.length) { - - for (let i = 0; i < wildcardTemplate.length; i++) { - - if (regexes[wildcardTemplate[i]].test(input[i])) { - - output += input[i]; - - continue; - - } - - // If any character does not match, the autocomplete does not match the template - break; - } - - if(output.length === wildcardTemplate.length) { - return output; - } - - } - } - - wildcardTemplate = '' - output = '' - - - // Strip away non wildcard template characters. for (let i = 0; i < template.length; i++) { - if (['9', 'a', '*', '¤'].includes(template[i])) { - wildcardTemplate += template[i] - continue; - } - - for (let j = 0; j < inputToBeStripped.length; j++) { - if (inputToBeStripped[j] === template[i]) { - inputToBeStripped = inputToBeStripped.slice(0, j) + inputToBeStripped.slice(j+1) - - break; - } - } - } - - for (let i = 0; i < wildcardTemplate.length; i++) { - let found = false - - for (let j = 0; j < inputToBeStripped.length; j++) { - if (regexes[wildcardTemplate[i]].test(inputToBeStripped[j])) { - output += inputToBeStripped[j] - inputToBeStripped = inputToBeStripped.slice(j+1) - - found = true - break; - } - } - - if (! found) break; + if (template[i] in regexes) output += input[i] } return output } -export function buildUp(template, input) { - let clean = Array.from(input) +export function formatInput (template, input) { let output = '' - - for (let i = 0; i < template.length; i++) { - if (!['9', 'a', '*', '¤'].includes(template[i])) { - output += template[i] - continue; + let imark = 0 + let tmark = 0 + while (tmark < template.length && imark < input.length) { + const char = template[tmark] + const ichar = input[imark] + if (char in regexes) { + if (regexes[char].test(ichar)) { + output += ichar + tmark++ + } + imark++ + } else { + output += char + tmark++ + if (char === input[imark]) imark++ } - - if (clean.length === 0) break; - - output += clean.shift() } - return output } export function formatMoney(input, delimiter = '.', thousands, precision = 2) { if (input === '-') return '-' - if (/^\D+$/.test(input)) return '¤' + if (/^\D+$/.test(input)) return '9' if (thousands === null || thousands === undefined) { thousands = delimiter === "," ? "." : "," @@ -266,12 +171,12 @@ export function formatMoney(input, delimiter = '.', thousands, precision = 2) { let minus = input.startsWith('-') ? '-' : '' let strippedInput = input.replaceAll(new RegExp(`[^0-9\\${delimiter}]`, 'g'), '') - let template = Array.from({length: strippedInput.split(delimiter)[0].length}).fill('¤').join('') + let template = Array.from({length: strippedInput.split(delimiter)[0].length}).fill('9').join('') template = `${minus}${addThousands(template, thousands)}` if (precision > 0 && input.includes(delimiter)) - template += `${delimiter}` + '¤'.repeat(precision) + template += `${delimiter}` + '9'.repeat(precision) queueMicrotask(() => { if (this.el.value.endsWith(delimiter)) return diff --git a/tests/cypress/integration/plugins/mask.spec.js b/tests/cypress/integration/plugins/mask.spec.js index 1046e3e0b..847be90b3 100644 --- a/tests/cypress/integration/plugins/mask.spec.js +++ b/tests/cypress/integration/plugins/mask.spec.js @@ -5,7 +5,7 @@ test('x-mask', ({ get }) => { // Type a phone number: get('input').type('12').should(haveValue('(12')) - get('input').type('3').should(haveValue('(123) ')) + get('input').type('3 ').should(haveValue('(123) ')) get('input').type('4567890').should(haveValue('(123) 456-7890')) // Clear it & paste formatted version in: get('input').type('{selectAll}{backspace}') @@ -43,7 +43,7 @@ test('x-mask with x-model', // Type a phone number: get('#1').type('12').should(haveValue('(12')) get('#2').should(haveValue('(12')) - get('#1').type('3').should(haveValue('(123) ')) + get('#1').type('3 ').should(haveValue('(123) ')) get('#2').should(haveValue('(123) ')) get('#1').type('4567890').should(haveValue('(123) 456-7890')) get('#2').should(haveValue('(123) 456-7890')) @@ -119,15 +119,15 @@ test('x-mask with non wildcard alpha-numeric characters (b)', get('input').type('a').should(haveValue('ba')) get('input').type('a').should(haveValue('ba')) get('input').type('3').should(haveValue('ba3')) - get('input').type('z').should(haveValue('ba3zb')) - get('input').type('{backspace}{backspace}4').should(haveValue('ba34b')) + get('input').type('z ').should(haveValue('ba3zb')) + get('input').type('{backspace}{backspace}4 ').should(haveValue('ba34b')) } ) test('x-mask:dynamic', [html``], ({ get }) => { - get('input').type('123').should(haveValue('(123)')) + get('input').type('123 ').should(haveValue('(123)')) } ) @@ -146,7 +146,7 @@ test('$money', get('input').type('{leftArrow}7').should(haveValue('1,234,567.87')) get('input').type('{leftArrow}{leftArrow}{leftArrow}89').should(haveValue('123,456,789.87')) get('input').type('{leftArrow}{leftArrow}{leftArrow}{leftArrow}12').should(haveValue('12,345,612,789.87')) - get('input').type('{leftArrow}3').should(haveValue('123,456,123,789.87')) + get('input').type('3').should(haveValue('123,456,123,789.87')) // Clear it & paste formatted version in: get('input').type('{selectAll}{backspace}') get('input').invoke('val', '123,456,132,789.87').trigger('blur') From 2a3b933fde5f9a34d72884bf9217f387e71e84bf Mon Sep 17 00:00:00 2001 From: Robert Marney Date: Tue, 11 Jun 2024 08:02:24 -0600 Subject: [PATCH 2/4] =?UTF-8?q?=E2=9C=85=20-=20Define=20new=20jest=20spec?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/jest/mask.spec.js | 145 ++++++++++++---------------------------- 1 file changed, 43 insertions(+), 102 deletions(-) diff --git a/tests/jest/mask.spec.js b/tests/jest/mask.spec.js index 90c217825..ee844d784 100644 --- a/tests/jest/mask.spec.js +++ b/tests/jest/mask.spec.js @@ -1,104 +1,45 @@ -let { stripDown, formatMoney, buildUp } = require('../../packages/mask/dist/module.cjs'); +let { formatInput } = require('../../packages/mask/dist/module.cjs'); + +test('format-input functionality', async () => { + + expect(formatInput('(***) ***-****', '7162256108')).toEqual('(716) 225-6108') + expect(formatInput('(999) 999-9999', '7162256108')).toEqual('(716) 225-6108') + expect(formatInput('999) 999-9999', '7162256108')).toEqual('716) 225-6108') + expect(formatInput('999 999-9999', '7162256108')).toEqual('716 225-6108') + expect(formatInput('999999-9999', '7162256108')).toEqual('716225-6108') + expect(formatInput('9999999999', '7162256108')).toEqual('7162256108') + expect(formatInput('(999) 999-9999', '716 2256108')).toEqual('(716) 225-6108') + expect(formatInput('(999) 999-9999', '(716) 2256108')).toEqual('(716) 225-6108') + expect(formatInput('(999) 999-9999', '(716) 2-25--6108')).toEqual('(716) 225-6108') + expect(formatInput('+1 (999) 999-9999', '7162256108')).toEqual('+1 (716) 225-6108') + expect(formatInput('+1 (999) 999-9999', '+1 (716) 225-6108')).toEqual('+1 (716) 225-6108') + expect(formatInput('ABC (999) 999-9999', '7162256108')).toEqual('ABC (716) 225-6108') + expect(formatInput('ABC (999) 999-9999', 'ABC (716) 225-6108')).toEqual('ABC (716) 225-6108') + expect(formatInput('999.999.9999', '7162256108')).toEqual('716.225.6108') + expect(formatInput('999.999.9999', '716.2256108')).toEqual('716.225.6108') + expect(formatInput('999.999.9999', '716.225 6108')).toEqual('716.225.6108') + + expect(formatInput('aaaa aaaa aaaa aaaa', 'abcd9abcd9abcd9abcd9')).toEqual('abcd abcd abcd abcd') + expect(formatInput('aaaa aaaa aaaa aaaa', 'abcd abcd abcd abcd')).toEqual('abcd abcd abcd abcd') + expect(formatInput('aaaa aaaa aaaa aaaa', 'abcdabcdabcdabcd')).toEqual('abcd abcd abcd abcd') + expect(formatInput('aaaa aaaa aaaa aaaa', 'abcd9abcd9abcd9abc')).toEqual('abcd abcd abcd abc') + expect(formatInput('### aaaa aaaa aaaa aaaa', 'abcd abcd abcd abcd')).toEqual('### abcd abcd abcd abcd') + expect(formatInput('### aaaa aaaa aaaa aaaa', '### abcd abcd abcd abcd')).toEqual('### abcd abcd abcd abcd') + expect(formatInput('### aaaa aaaa aaaa aaaa', '### abcd abcd #### abcd')).toEqual('### abcd abcd abcd') + + expect(formatInput('#### #### #### 9999', '1234 5678 9101 2345')).toEqual('#### #### #### 1234') + + expect(formatInput('ba9*b', 'a')).toEqual('ba') + expect(formatInput('ba9*b', 'aa')).toEqual('ba') + expect(formatInput('ba9*b', 'aa3')).toEqual('ba3') + expect(formatInput('ba9*b', 'aa3z')).toEqual('ba3z') + expect(formatInput('ba9*b', 'aa3z4')).toEqual('ba3zb') + + expect(formatInput('a', 'a9a')).toEqual('a') + expect(formatInput('aa', 'b9a')).toEqual('ba') + expect(formatInput('aa', 'bb')).toEqual('bb') + expect(formatInput('aab', 'aba')).toEqual('abb') + expect(formatInput('abb', 'ab')).toEqual('ab') + expect(formatInput('abb', 'ab9')).toEqual('abb') -test('strip-down functionality', async () => { - expect(stripDown('(***) ***-****', '7162256108')).toEqual('7162256108') - expect(stripDown('(999) 999-9999', '7162256108')).toEqual('7162256108') - expect(stripDown('999) 999-9999', '7162256108')).toEqual('7162256108') - expect(stripDown('999 999-9999', '7162256108')).toEqual('7162256108') - expect(stripDown('999999-9999', '7162256108')).toEqual('7162256108') - expect(stripDown('9999999999', '7162256108')).toEqual('7162256108') - expect(stripDown('9999999999', '7162256108')).toEqual('7162256108') - expect(stripDown('(999) 999-9999', '716 2256108')).toEqual('7162256108') - expect(stripDown('(999) 999-9999', '(716) 2256108')).toEqual('7162256108') - expect(stripDown('(999) 999-9999', '(716) 2-25--6108')).toEqual('7162256108') - expect(stripDown('+1 (999) 999-9999', '7162256108')).toEqual('7162256108') - expect(stripDown('+1 (999) 999-9999', '+1 (716) 225-6108')).toEqual('7162256108') - expect(stripDown('ABC (999) 999-9999', '7162256108')).toEqual('7162256108') - expect(stripDown('ABC (999) 999-9999', 'ABC (716) 225-6108')).toEqual('7162256108') - expect(stripDown('999.999.9999', '7162256108')).toEqual('7162256108') - expect(stripDown('999.999.9999', '716.2256108')).toEqual('7162256108') - expect(stripDown('999.999.9999', '716.225 6108')).toEqual('7162256108') - - expect(stripDown('aaaa aaaa aaaa aaaa', 'abcd9abcd9abcd9abcd9')).toEqual('abcdabcdabcdabcd') - expect(stripDown('aaaa aaaa aaaa aaaa', 'abcd abcd abcd abcd')).toEqual('abcdabcdabcdabcd') - expect(stripDown('aaaa aaaa aaaa aaaa', 'abcdabcdabcdabcd')).toEqual('abcdabcdabcdabcd') - expect(stripDown('aaaa aaaa aaaa aaaa', 'abcd9abcd9abcd9abc')).toEqual('abcdabcdabcdabc') - expect(stripDown('### aaaa aaaa aaaa aaaa', 'abcd abcd abcd abcd')).toEqual('abcdabcdabcdabcd') - expect(stripDown('### aaaa aaaa aaaa aaaa', '### abcd abcd abcd abcd')).toEqual('abcdabcdabcdabcd') - expect(stripDown('### aaaa aaaa aaaa aaaa', '### abcd abcd #### abcd')).toEqual('abcdabcdabcd') - expect(stripDown('#### #### #### 9999', '1234 5678 9101 2345')).toEqual('2345') - - expect(stripDown('ba9*b', 'a')).toEqual('a') - expect(stripDown('ba9*b', 'aa')).toEqual('a') - expect(stripDown('ba9*b', 'aa3')).toEqual('a3') - expect(stripDown('ba9*b', 'aa3z')).toEqual('a3z') - expect(stripDown('ba9*b', 'aa3z4')).toEqual('a3z') - - expect(stripDown('a', 'a9a')).toEqual('a') - expect(stripDown('aa', 'b9a')).toEqual('ba') - expect(stripDown('aa', 'bb')).toEqual('bb') - expect(stripDown('aab', 'aba')).toEqual('ab') - expect(stripDown('abb', 'ab')).toEqual('a') - expect(stripDown('abb', 'ab9')).toEqual('a') - expect(stripDown('abcd', 'aba')).toEqual('a') - expect(stripDown('baba', 'a9a')).toEqual('aa') - expect(stripDown('baba', 'aa')).toEqual('aa') - expect(stripDown('bbbba', 'a')).toEqual('a') - expect(stripDown('bbbba', 'aa')).toEqual('a') -}) - -test('build-up functionality', async () => { - expect(buildUp('(***) ***-****', '7162256108')).toEqual('(716) 225-6108') - expect(buildUp('(999) 999-9999', '7162256108')).toEqual('(716) 225-6108') - expect(buildUp('999) 999-9999', '7162256108')).toEqual('716) 225-6108') - expect(buildUp('999 999-9999', '7162256108')).toEqual('716 225-6108') - expect(buildUp('999999-9999', '7162256108')).toEqual('716225-6108') - expect(buildUp('9999999999', '7162256108')).toEqual('7162256108') - - expect(buildUp('ba9*b', 'a')).toEqual('ba') - expect(buildUp('ba9*b', 'a3')).toEqual('ba3') - expect(buildUp('ba9*b', 'a3z')).toEqual('ba3zb') - -}) - -test('teardown and build-up functionality', async () => { - let template = 'ba9*b'; - - expect(buildUp(template, stripDown(template, 'a'))).toEqual('ba') - expect(buildUp(template, stripDown(template, 'aa'))).toEqual('ba') - expect(buildUp(template, stripDown(template, 'aa3'))).toEqual('ba3') - expect(buildUp(template, stripDown(template, 'aa3z'))).toEqual('ba3zb') - expect(buildUp(template, stripDown(template, 'aa4'))).toEqual('ba4') - -}); - -test('formatMoney functionality', async () => { - // Default arguments implicit and explicit - expect(formatMoney('123456')).toEqual('123,456'); - expect(formatMoney('9900900')).toEqual('9,900,900'); - expect(formatMoney('5600.40')).toEqual('5,600.40'); - expect(formatMoney('123456', '.')).toEqual('123,456'); - expect(formatMoney('9900900', '.')).toEqual('9,900,900'); - expect(formatMoney('5600.40', '.')).toEqual('5,600.40'); - expect(formatMoney('123456', '.', ',')).toEqual('123,456'); - expect(formatMoney('9900900', '.', ',')).toEqual('9,900,900'); - expect(formatMoney('5600.40', '.', ',')).toEqual('5,600.40'); - - // Switch decimal separator - expect(formatMoney('123456', ',')).toEqual('123.456'); - expect(formatMoney('9900900', ',')).toEqual('9.900.900'); - expect(formatMoney('5600.40', ',')).toEqual('5.600,40'); - expect(formatMoney('123456', '/')).toEqual('123.456'); - expect(formatMoney('9900900', '/')).toEqual('9.900.900'); - expect(formatMoney('5600.40', '/')).toEqual('5.600/40'); - - // Switch thousands separator - expect(formatMoney('123456', '.', ' ')).toEqual('123 456'); - expect(formatMoney('9900900', '.', ' ')).toEqual('9 900 900'); - expect(formatMoney('5600.40', '.', ' ')).toEqual('5 600.40'); - - // Switch decimal and thousands separator - expect(formatMoney('123456', '#', ' ')).toEqual('123 456'); - expect(formatMoney('9900900', '#', ' ')).toEqual('9 900 900'); - expect(formatMoney('5600.40', '#', ' ')).toEqual('5 600#40'); }); From 827b7b01dbb4fd43e1440d5f614d5be9fd070821 Mon Sep 17 00:00:00 2001 From: Robert Marney Date: Tue, 11 Jun 2024 08:08:42 -0600 Subject: [PATCH 3/4] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20Remove=20stripDown=20f?= =?UTF-8?q?unctionality?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/mask/builds/module.js | 4 ++-- packages/mask/src/index.js | 9 --------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/packages/mask/builds/module.js b/packages/mask/builds/module.js index c49f3a1c7..1bd83bbaf 100644 --- a/packages/mask/builds/module.js +++ b/packages/mask/builds/module.js @@ -1,5 +1,5 @@ -import mask, { stripDown } from '../src/index.js' +import mask, { formatInput } from '../src/index.js' export default mask -export { mask, stripDown } +export { mask, formatInput } diff --git a/packages/mask/src/index.js b/packages/mask/src/index.js index 5d451c15d..f80ee8bc3 100644 --- a/packages/mask/src/index.js +++ b/packages/mask/src/index.js @@ -111,15 +111,6 @@ let regexes = { 'a': /[a-zA-Z]/, '*': /[a-zA-Z0-9]/, }; -export function stripDown(template, input) { - let output = '' - for (let i = 0; i < template.length; i++) { - if (template[i] in regexes) output += input[i] - } - - return output -} - export function formatInput (template, input) { let output = '' let imark = 0 From ed461b30723ae138daeecda7f2e3259471954ba5 Mon Sep 17 00:00:00 2001 From: Robert Marney Date: Tue, 11 Jun 2024 08:25:25 -0600 Subject: [PATCH 4/4] =?UTF-8?q?=E2=9C=85Add=20additional=20test=20case?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cypress/integration/plugins/mask.spec.js | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/cypress/integration/plugins/mask.spec.js b/tests/cypress/integration/plugins/mask.spec.js index 847be90b3..716a13a19 100644 --- a/tests/cypress/integration/plugins/mask.spec.js +++ b/tests/cypress/integration/plugins/mask.spec.js @@ -32,6 +32,30 @@ test('x-mask', }, ) +test('x-mask autocomplete', + [html``], + ({ get }) => { + // Type a phone number: + get('input').type('21').should(haveValue('+1 (21')) + get('input').type('3 ').should(haveValue('+1 (213) ')) + get('input').type('4567890').should(haveValue('+1 (213) 456-7890')) + // Clear it & paste formatted version in: + get('input').type('{selectAll}{backspace}') + get('input').invoke('val', '+1 (213) 456-7890').trigger('blur') + get('input').should(haveValue('+1 (213) 456-7890')) + // Clear it & paste un-formatted version in: + get('input').type('{selectAll}{backspace}') + get('input').invoke('val', '2134567890').trigger('blur') + get('input').should(haveValue('+1 (213) 456-7890')) + // Clear it and start with an area code starting with 1: + get('input').type('{selectAll}{backspace}') + get('input').type('1 ').should(haveValue('+1 ')) + get('input').type('2').should(haveValue('+1 (2')) + get('input').type('13 ').should(haveValue('+1 (213) ')) + get('input').type('456 78-90').should(haveValue('+1 (213) 456-7890')) + }, +) + test('x-mask with x-model', [html`