Skip to content
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
11 changes: 8 additions & 3 deletions src/mixins/validation-mixin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ export default class ValidationMixin extends Vue {
case 'maxLength': obj[prop].push(() => this.maxLengthRule(model, prop)); break
case 'isCanada': obj[prop].push(() => this.isCanadaRule(model, prop)); break
case 'isBC': obj[prop].push(() => this.isBCRule(model, prop)); break
case 'validatePostalCode': obj[prop].push(() => this.postalCodeRule(model, prop)); break
case 'isValidPostalCode': obj[prop].push(() => this.isValidPostalCodeRule(model, prop)); break
case 'isRequiredPostalCode': obj[prop].push(() => this.isRequiredPostalCodeRule(model, prop)); break
// FUTURE: add extra validation functions here
default: break
}
Expand Down Expand Up @@ -89,7 +90,11 @@ export default class ValidationMixin extends Vue {
return Boolean(this.$v[prop] && this.$v[prop][key].isBC) || 'Address must be in BC'
}

protected postalCodeRule (prop: string, key: string): boolean | string {
return Boolean(this.$v[prop] && this.$v[prop][key].validatePostalCode) || 'Enter a valid postal code'
protected isValidPostalCodeRule (prop: string, key: string): boolean | string {
return Boolean(this.$v[prop] && this.$v[prop][key].isValidPostalCode) || 'Enter a valid postal code'
}

protected isRequiredPostalCodeRule (prop: string, key: string): boolean | string {
return Boolean(this.$v[prop] && this.$v[prop][key].isRequiredPostalCode) || 'This field is required'
}
}
3 changes: 2 additions & 1 deletion src/validators/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './validate-postal-code'
export * from './is-valid-postal-code'
export * from './is-required-postal-code'
25 changes: 25 additions & 0 deletions src/validators/is-required-postal-code.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Countries that do not use postal codes.
* Postal code is not required for addresses in these countries.
*/
const NO_POSTAL_CODE_COUNTRY_CODES = new Set([
'AO', 'AG', 'AW', 'BS', 'BZ', 'BJ', 'BM', 'BO', 'BQ', 'BW', 'BF', 'BI',
'CM', 'CF', 'TD', 'KM', 'CG', 'CD', 'CK', 'CI', 'CW', 'DJ', 'DM', 'GQ',
'ER', 'FJ', 'TF', 'GA', 'GM', 'GH', 'GD', 'GY', 'HM', 'HK',
'KI', 'KP', 'LY', 'MO', 'MW', 'ML', 'MR', 'NR',
'AN', 'NU', 'QA', 'RW', 'KN', 'ST', 'SC', 'SL', 'SX', 'SB', 'SO', 'SR', 'SY',
'TL', 'TG', 'TK', 'TO', 'TT', 'TV', 'UG', 'AE', 'VU', 'YE', 'ZW'
])

/**
* Custom validator for postal code required.
* Returns true if postal code has a value, or if the country doesn't use postal codes.
*/
export function isRequiredPostalCode (value: string, parentVm: any): boolean {
// If the country doesn't use postal codes, no value is required
if (NO_POSTAL_CODE_COUNTRY_CODES.has(parentVm.addressCountry)) {
return true
}
// Otherwise, postal code is required
return !!value?.trim()
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
const CanadaPostalCodeRegex = /^[ABCEGHJ-NPRSTVXY][0-9][ABCEGHJ-NPRSTV-Z][ ]?[0-9][ABCEGHJ-NPRSTV-Z][0-9]$/i

/** Custom validator for postal codes. */
export function validatePostalCode (value: string, parentVm: any): boolean {
export function isValidPostalCode (value: string, parentVm: any): boolean {
// if Canada, validate postal code format
// empty value is considered valid -- "required" validation is handled separately
if (parentVm.addressCountry === 'CA') return !value || CanadaPostalCodeRegex.test(value)
Expand Down
55 changes: 55 additions & 0 deletions tests/unit/is-required-postal-code.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import Vue from 'vue'
import { shallowMount } from '@vue/test-utils'
import { isRequiredPostalCode } from '@/validators'

const Dummy = Vue.component('DummyComponent', { template: '<div />' })

describe('Validate Postal Code Required', () => {
let vm: any

beforeAll(async () => {
// mount the component and wait for everything to stabilize
// (this can be any component since we are not really using it)
const wrapper = shallowMount(Dummy)
vm = wrapper.vm
await Vue.nextTick()
})

it.each([
'A1A 1A1',
'12345',
'some-postal-code',
' value '
])('returns true when postal code "%s" is provided for a country requiring postal codes', (postalCode) => {
expect(isRequiredPostalCode(postalCode, { addressCountry: 'CA' })).toBe(true)
expect(isRequiredPostalCode(postalCode, { addressCountry: 'US' })).toBe(true)
})

it.each([
'',
' ',
null,
undefined
])('returns false when postal code is "%s" for a country requiring postal codes', (postalCode) => {
expect(isRequiredPostalCode(postalCode as any, { addressCountry: 'CA' })).toBe(false)
expect(isRequiredPostalCode(postalCode as any, { addressCountry: 'US' })).toBe(false)
})

it.each([
'AO', 'AG', 'AW', 'BS', 'BZ', 'BJ', 'BM', 'BO', 'BQ', 'BW', 'BF', 'BI',
'CM', 'CF', 'TD', 'KM', 'CG', 'CD', 'CK', 'CI', 'CW', 'DJ', 'DM', 'GQ',
'ER', 'FJ', 'TF', 'GA', 'GM', 'GH', 'GD', 'GY', 'HM', 'HK',
'KI', 'KP', 'LY', 'MO', 'MW', 'ML', 'MR', 'NR',
'AN', 'NU', 'QA', 'RW', 'KN', 'ST', 'SC', 'SL', 'SX', 'SB', 'SO', 'SR', 'SY',
'TL', 'TG', 'TK', 'TO', 'TT', 'TV', 'UG', 'AE', 'VU', 'YE', 'ZW'
])('returns true for country "%s" even when postal code is empty', (countryCode) => {
expect(isRequiredPostalCode('', { addressCountry: countryCode })).toBe(true)
expect(isRequiredPostalCode(' ', { addressCountry: countryCode })).toBe(true)
})

it.each([
'AO', 'HK', 'AE', 'QA', 'ZW'
])('returns true for country "%s" when postal code is provided', (countryCode) => {
expect(isRequiredPostalCode('12345', { addressCountry: countryCode })).toBe(true)
})
})
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Vue from 'vue'
import { shallowMount } from '@vue/test-utils'
import { validatePostalCode } from '@/validators'
import { isValidPostalCode } from '@/validators'

const Dummy = Vue.component('DummyComponent', { template: '<div />' })

Expand All @@ -22,7 +22,7 @@ describe('Validate Postal Code', () => {
'a1a 1a1',
'a1a1a1'
])('accepts valid postal code "%s" for Canada', (postalCode) => {
expect(validatePostalCode(postalCode, { addressCountry: 'CA' })).toBe(true)
expect(isValidPostalCode(postalCode, { addressCountry: 'CA' })).toBe(true)
})

it.each([
Expand All @@ -40,7 +40,7 @@ describe('Validate Postal Code', () => {
'W1W 1W1', // W is not allowed in Canadian postal codes
'Z1Z 1Z1' // Z is not allowed in Canadian postal codes
])('rejects invalid postal code "%s" for Canada', (postalCode) => {
expect(validatePostalCode(postalCode, { addressCountry: 'CA' })).toBe(false)
expect(isValidPostalCode(postalCode, { addressCountry: 'CA' })).toBe(false)
})

it.each([
Expand All @@ -52,6 +52,20 @@ describe('Validate Postal Code', () => {
'W1W 1W1',
'Z1Z 1Z1'
])('accepts any postal code "%s" for non-Canada', (postalCode) => {
expect(validatePostalCode(postalCode, { addressCountry: 'XX' })).toBe(true)
expect(isValidPostalCode(postalCode, { addressCountry: 'XX' })).toBe(true)
})

it.each([
null,
undefined
])('accepts %s postal code for Canada', (postalCode) => {
expect(isValidPostalCode(postalCode as any, { addressCountry: 'CA' })).toBe(true)
})

it.each([
null,
undefined
])('accepts %s postal code for non-Canada', (postalCode) => {
expect(isValidPostalCode(postalCode as any, { addressCountry: 'XX' })).toBe(true)
})
})
Loading