Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI: Tax Number and Tax Residency #48

Merged
merged 6 commits into from
Nov 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,57 +1,64 @@
describe('forms -> preferred name -> validate that the preferred name component work inside example form', () => {
let en: any

beforeEach(() => {
// load the English version of the language file
cy.readFile('lang/en.json').then((json) => {
en = json
})

// navigate to index page and check footer and header exist
cy.visit('/examples/form')
})

it('test the validation rule for the maximum name length', () => {
cy.on('uncaught:exception', (err) => {
expect(err.message).to.include('The legal name must not exceed 150 characters')
expect(err.message).to.include(en.errors.validation.fullName.maxLengthExceeded)
return false
})

const invalidLongName = 'a'.repeat(151)
const validLongName = ' ' + 'a'.repeat(150) + ' '

cy.get('#testFullName').type(invalidLongName).blur()
cy.contains('The legal name must not exceed 150 characters').should('exist')
cy.contains(en.errors.validation.fullName.maxLengthExceeded).should('exist')

cy.get('#testFullName').clear().type(validLongName).blur()
cy.contains('The legal name must not exceed 150 characters').should('not.exist')
cy.contains(en.errors.validation.fullName.maxLengthExceeded).should('not.exist')
})

it('test the validation rule for the minimum name length', () => {
cy.on('uncaught:exception', (err) => {
expect(err.message).to.include('The legal name should contain at least one character')
expect(err.message).to.include(en.errors.validation.fullName.empty)
return false
})

const singleCharacter = 'a'
cy.get('#testFullName').type(singleCharacter).blur()
cy.contains('The legal name should contain at least one character').should('not.exist')
cy.contains(en.errors.validation.fullName.empty).should('not.exist')

cy.get('#testFullName').clear().blur()
cy.contains('The legal name should contain at least one character').should('exist')
cy.contains(en.errors.validation.fullName.empty).should('exist')
})

it('test the validation rule for special character', () => {
cy.on('uncaught:exception', (err) => {
expect(err.message).to.include('The legal name should not contain special character')
expect(err.message).to.include(en.errors.validation.fullName.specialCharacter)
return false
})

const invalidName = 'first - last'
const validName = 'first last'

cy.get('#testFullName').type(invalidName).blur()
cy.contains('The legal name should not contain special character').should('exist')
kialj876 marked this conversation as resolved.
Show resolved Hide resolved
cy.contains(en.errors.validation.fullName.specialCharacter).should('exist')

cy.get('#testFullName').clear().type(validName).blur()
cy.contains('The legal name should not contain special character').should('not.exist')
cy.contains(en.errors.validation.fullName.specialCharacter).should('not.exist')
})

it('the full name field should accept UTF-8 characters', () => {
cy.contains('Full Legal Name:')
cy.contains(en.labels.fullName)
const email = 'abc@email.com'
cy.get('#testEmail').type(email)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,39 @@
describe('forms -> preferred name -> validate that the preferred name component work inside example form', () => {
let en: any

beforeEach(() => {
// load the English version of the language file
cy.readFile('lang/en.json').then((json) => {
en = json
})

// navigate to index page and check footer and header exist
cy.visit('/examples/form')
})

it('test the validation rule for the maximum length of the preferred name', () => {
cy.on('uncaught:exception', (err) => {
expect(err.message).to.include('The preferred name must not exceed 150 characters')
expect(err.message).to.include(en.errors.validation.preferredName.maxLengthExceeded)
return false
})

const invalidLongName = 'a'.repeat(151)
const validLongName = ' ' + 'a'.repeat(150) + ' '

cy.get('#testPreferredName').type(invalidLongName).blur()
cy.contains('The preferred name must not exceed 150 characters').should('exist')
cy.contains(en.errors.validation.preferredName.maxLengthExceeded).should('exist')

cy.get('#testPreferredName').clear().type(validLongName).blur()
cy.contains('The preferred name must not exceed 150 characters').should('not.exist')
cy.contains(en.errors.validation.preferredName.maxLengthExceeded).should('not.exist')
})

it('preferred name can be empty', () => {
cy.on('uncaught:exception', (err) => {
expect(err.message).to.include('The preferred name should contain at least one character')
return false
})

cy.get('#testPreferredName').clear().blur()
cy.contains('The preferred name should contain at least one character').should('not.exist')
cy.get('#testPreferredName').type('a').clear().blur()
})

it('test the validation rule for special character', () => {
cy.on('uncaught:exception', (err) => {
expect(err.message).to.include('The preferred name should not contain special character')
expect(err.message).to.include(en.errors.validation.preferredName.specialCharacter)
return false
})

Expand All @@ -42,16 +43,16 @@ describe('forms -> preferred name -> validate that the preferred name component
const unicodeName2 = 'José 玛丽'

cy.get('#testPreferredName').type(invalidName).blur()
cy.contains('The preferred name should not contain special character').should('exist')
cy.contains(en.errors.validation.preferredName.specialCharacter).should('exist')

cy.get('#testPreferredName').clear().type(validName).blur()
cy.contains('The preferred name should not contain special character').should('not.exist')
cy.contains(en.errors.validation.preferredName.specialCharacter).should('not.exist')

cy.get('#testPreferredName').clear().type(unicodeName1).blur()
cy.contains('The preferred name should not contain special character').should('not.exist')
cy.contains(en.errors.validation.preferredName.specialCharacter).should('not.exist')

cy.get('#testPreferredName').clear().type(unicodeName2).blur()
cy.contains('The preferred name should not contain special character').should('not.exist')
cy.contains(en.errors.validation.preferredName.specialCharacter).should('not.exist')
})

it('the displayed name should be normalized', () => {
Expand Down
4 changes: 2 additions & 2 deletions btr-web/btr-common-components/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@
"fullName": {
"empty": "The legal name should contain at least one character",
"maxLengthExceeded": "The legal name must not exceed 150 characters",
"specialCharacter": "The legal name should not contain special character"
"specialCharacter": "The legal name should not contain special characters"
},
"preferredName": {
"maxLengthExceeded": "The preferred name must not exceed 150 characters",
"specialCharacter": "The preferred name should not contain special character"
"specialCharacter": "The preferred name should not contain special characters"
}
}
},
Expand Down
6 changes: 4 additions & 2 deletions btr-web/btr-common-components/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export { validateEmailRfc6532Regex, validateNameCharacters, validatePreferredName, normalizeName }
from './validation/form_inputs'
export {
validateEmailRfc6532Regex, validateNameCharacters, validatePreferredName, normalizeName,
checkSpecialCharacters, checkTaxNumberLength, validateTaxNumber
} from './validation/form_inputs'

// canada post retrieve api
export type { CanadaPostRetrieveItemI, CanadaPostApiRetrieveParamsI } from './canadaPostAddressApi/retrieve-v2.11'
Expand Down
50 changes: 50 additions & 0 deletions btr-web/btr-common-components/utils/validation/form_inputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,53 @@ export const normalizeName = (name?: string): string => {
}
return name.trim().replace(/\s+/g, ' ')
}

/**
* Check a tax number to ensure that it only consists of digits and whitespace.
* @param {string} taxNumber - string representation of the tax number input
*/
export const checkSpecialCharacters = (taxNumber: string): boolean => {
return /^[\d\s]*$/.test(taxNumber)
}

/**
* Check if the tax number has 9 digits
* @param {string} taxNumber - string representation of the tax number input
*/
export const checkTaxNumberLength = (taxNumber: string): boolean => {
const digits = taxNumber.replace(/\s+/g, '')
return digits.length === 9
}

/**
* Check if the tax number is valid
* @param {string} taxNumber - string representation of the tax number input
*/
export const validateTaxNumber = (taxNumber: string): boolean => {
// TODO: To confirm the validation algorithms for SIN, ITN, and TTN
// SIN Validation rule used: https://en.wikipedia.org/wiki/Social_insurance_number

const digits = taxNumber.replace(/\s+/g, '')

if (digits.length !== 9) {
return false
}

let checkSum = 0

for (let i = 0; i < 9; i++) {
const digit = parseInt(digits[i])
if (i % 2 === 0) {
checkSum += digit
} else {
const product = digit * 2
if (product < 10) {
checkSum += product
} else {
checkSum += product - 9
}
}
}

return checkSum % 10 === 0
}
66 changes: 63 additions & 3 deletions btr-web/btr-main-app/components/individual-person/AddNew.vue
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
/>
</div>
</UForm>

<div class="text-blue-700 py-5 align-middle">
<a
id="add-person-manually-toggle"
Expand Down Expand Up @@ -127,12 +128,44 @@
v-model:citizenships="citizenships"
/>
</div>
<UForm
:schema="schema"
:state="state"
>
<div>
<p class="font-bold py-5">
{{ $t('labels.taxNumber') }}
</p>
<p class="text-justify">
{{ $t('texts.taxNumber') }}
</p>
<IndividualPersonTaxInfoTaxNumber
id="addNewPersonTaxNumber"
v-model="taxInfoModel"
name="taxNumber"
data-cy="testTaxNumber"
/>
</div>
</UForm>
<div>
<p class="font-bold py-5">
{{ $t('labels.taxResidency') }}
</p>
<p class="text-justify">
{{ $t('texts.taxResidency') }}
</p>
<IndividualPersonTaxInfoTaxResidency
id="addNewPersonTaxResidency"
v-model="isTaxResident"
data-cy="testTaxResidency"
/>
</div>
</template>
</div>
</template>

<script setup lang="ts">
import { Ref, ref } from 'vue'
import { Ref, ref, computed } from 'vue'
import { z } from 'zod'

const showAddInfoManually = ref(false)
Expand Down Expand Up @@ -182,14 +215,41 @@ const schema = z.object({
email: z.string()
.min(1, t('errors.validation.email.empty'))
.max(254, 'errors.validation.email.maxLengthExceeded')
.refine(validateEmailRfc6532Regex, t('errors.validation.email.invalid'))
.refine(validateEmailRfc6532Regex, t('errors.validation.email.invalid')),
hasTaxNumber: z.boolean(),
taxNumber: z.union([
z.undefined(),
z.string()
.refine(checkSpecialCharacters, t('errors.validation.taxNumber.specialCharacter'))
.refine(checkTaxNumberLength, t('errors.validation.taxNumber.invalidLength'))
.refine(validateTaxNumber, t('errors.validation.taxNumber.invalidNumber'))
])
})

const state = reactive({
email: undefined,
fullName: undefined,
preferredName: undefined
preferredName1: undefined,
hasTaxNumber: undefined,
taxNumber: undefined
})

// tax number input
const taxInfoModel = computed({
get () {
return {
hasTaxNumber: state.hasTaxNumber,
taxNumber: state.taxNumber
}
},
set (value) {
state.hasTaxNumber = value.hasTaxNumber
state.taxNumber = value.taxNumber
}
})

// tax residency
const isTaxResident: Ref<string | undefined> = ref(undefined)
</script>

<style scoped></style>
Loading
Loading