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
17 changes: 14 additions & 3 deletions src/components/base-address/BaseAddress.vue
Original file line number Diff line number Diff line change
Expand Up @@ -454,13 +454,24 @@
return this.addressLocal
}

/**
* Whether the Postal Code model value is valid per the schema.
* NB: Vuelidate validates the model continuously, even while the Vuetify postal code
* rules are disabled (ie, before the field's first blur), so this reflects the actual
* validity of the value rather than whether errors are being displayed.
*/
get isPostalCodeValid (): boolean {
return !this.$v?.addressLocal?.postalCode?.$invalid
}

/** Watches for changes to the address form validity and emits the updated state. */
@Watch('addressFormValid')
@Watch('postalCodeRulesEnabled')
@Watch('isPostalCodeValid')
@Emit('valid')
onAddressFormValidChanged (): boolean {
// form is valid only if postal code rules are enabled
return (this.addressFormValid && this.postalCodeRulesEnabled)
// NB: addressFormValid alone can be a false positive while the Vuetify postal code
// rules are still disabled, so also check the postal code's Vuelidate state
return (this.addressFormValid && this.isPostalCodeValid)
}

/**
Expand Down Expand Up @@ -527,7 +538,7 @@
/**
* Callback to update the address data after the user chooses a suggested address.
* @param address the data object returned by the AddressComplete Retrieve API
* UNDOCUMENTED! 2 args must be passed. address_ref contains first item in addresses, addresses contains entire return array.

Check warning on line 541 in src/components/base-address/BaseAddress.vue

View workflow job for this annotation

GitHub Actions / linting (24)

This line has a length of 127. Maximum allowed is 120
*/
addressCompletePopulate (addressRef: object, addresses: object[]): void {
// Select the first occurrence of the address only containing latin-1
Expand Down
2 changes: 1 addition & 1 deletion src/components/base-address/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@bcrs-shared-components/base-address",
"version": "2.2.8",
"version": "2.2.9",
"publishConfig": {
"access": "public"
},
Expand Down
88 changes: 88 additions & 0 deletions tests/unit/BaseAddress.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import Vue from 'vue'
import Vuetify from 'vuetify'
import { mount, Wrapper } from '@vue/test-utils'
import { required, maxLength } from 'vuelidate/lib/validators'
import { BaseAddress } from '@/components/base-address'
import { isRequiredPostalCode, isValidPostalCode } from '@bcrs-shared-components/validators'

const vuetify = new Vuetify({})

Expand Down Expand Up @@ -106,3 +108,89 @@ describe('BaseAddress - isSchemaRequired', () => {
expect(wrapper.vm.isSchemaRequired('streetAddress')).toBe(false)
})
})

describe('BaseAddress - valid event (#34469)', () => {
// schema mirroring the consuming apps' address schemas
const schema = {
streetAddress: { required, maxLength: maxLength(50) },
streetAddressAdditional: { maxLength: maxLength(105) },
addressCity: { required, maxLength: maxLength(40) },
addressCountry: { required },
addressRegion: { maxLength: maxLength(2) },
postalCode: { isRequiredPostalCode, maxLength: maxLength(15), isValidPostalCode },
deliveryInstructions: { maxLength: maxLength(80) }
}

const address = {
streetAddress: '123 Main St',
streetAddressAdditional: 'Suite 200',
addressCity: 'Victoria',
addressRegion: 'BC',
postalCode: 'V8V 1V1',
addressCountry: 'CA',
deliveryInstructions: ''
}

let wrapper: Wrapper<any>

afterEach(() => {
wrapper.destroy()
})

it('emits valid=true when an optional field is edited and the postal code was never blurred', async () => {
wrapper = mount(BaseAddress, {
vuetify,
propsData: { schema, editing: true, address }
})
await Vue.nextTick()

// clear Additional Street Address without ever touching the postal code field
// (the reported bug: this emitted valid=false and nothing ever re-emitted true)
const line2 = wrapper.find('.street-address-additional textarea')
await line2.setValue('')
await Vue.nextTick()

const validEvents = wrapper.emitted('valid')
if (validEvents) {
expect(validEvents[validEvents.length - 1][0]).toBe(true)
}
// the model itself must agree
expect(wrapper.vm.isPostalCodeValid).toBe(true)
expect(wrapper.vm.addressFormValid).toBe(true)
})

it('emits valid=false when the postal code value is invalid, even before its rules are enabled', async () => {
wrapper = mount(BaseAddress, {
vuetify,
propsData: { schema, editing: true, address: { ...address, postalCode: '' } }
})
await Vue.nextTick()

// editing another field must not mask the invalid (empty, required) postal code
const line2 = wrapper.find('.street-address-additional textarea')
await line2.setValue('')
await Vue.nextTick()

expect(wrapper.vm.isPostalCodeValid).toBe(false)

const validEvents = wrapper.emitted('valid')
if (validEvents) {
expect(validEvents[validEvents.length - 1][0]).toBe(false)
}
})

it('emits valid=true after validate() on a fully valid address', async () => {
wrapper = mount(BaseAddress, {
vuetify,
propsData: { schema, editing: true, address }
})
await Vue.nextTick()

await wrapper.vm.validate()
await Vue.nextTick()

const validEvents = wrapper.emitted('valid')
expect(validEvents).toBeTruthy()
expect(validEvents[validEvents.length - 1][0]).toBe(true)
})
})
Loading