Commit e447cd3a authored by Mark Florian's avatar Mark Florian

Bump GitLab UI

Some Geo tests needed to be fixed to support this version bump. Details
below.

For the GeoNodeFormCapacities and GeoNodeFormCore components:

The tests set the input's value to the empty string, and try to assert
that the form displays an error message saying that the input can't be
empty. This doesn't work, because the underlying `BFormInput` does not
emit a model event in the case that the model value hasn't changed (the
value starts as an empty string, and ends up as an empty string!).

Prior to this, the tests were passing because the `input` event is not
the model event as far as the `BFormInput` is concerned, which it always
fires. So, the fix in this case is to do one of the following:

1. Listen for the update event in the component, rather than the input
   event.
2. In the test, set the input element's value to something else before
   making it the empty string, so the model event fires.

The first option seems best, since it's more "correct", but this change
is entirely for the sake of the tests. Real-world behaviour won't have
changed at all either way. This is because there's no way for a user to
set an input to the empty string when it's already the empty string, so
this edge case can't happen in reality.

For the GeoSettingsForm component:

This is actually a very subtle confluence of bugs, including implicit
type casting, unrealistic synchronous calls and inconsistent amounts of
behaviour mocking. Individually these are harmless, but all together
produced a failing test.

The crux of this is that `BFormInput` emits a model (`update`) Vue event
when a [`blur` DOM event is triggered][237] on the input element, **if**
the [_formatted_ input value doesn't strictly equal the `model`
value][161].  The _formatted_ value is [_stringified_][138] copy of the
model value (unless the `formatter` prop is given), which means that
when the model is a `number`, the `model` ends up getting overwritten,
because a string can't equal a number.

Prior to GitLab UI v18.7, that model (`update`) event was completely
ignored, and so triggering the `blur` *didn't* re-update the model value
with the old value.

But _with_ GitLab UI v18.7, that model (`update`) event _is_ listened
to, (via the remapping to the `input` event) and the model gets is
updated accordingly _because_ of the blur.

The reason the _old_ value is used by `BFormInput`'s `onBlur` method is
because it is called _synchronously_ after emitting the model event.
This means that the parent scope's model has been updated, but Vue
hasn't _yet_ passed it down to the component again. If you `await
wrapper.vm.$nextTick()` _before_ triggering the blur, the _new but
stringified_ value will be in the parent scope's model instead of the
_old_ stringified model value. (This is was [suggested][zcuddy] by
@zcuddyy, although _technically_ the model would a string instead of
a number with this approach.)

Finally, this can be reproduced with `BFormInput` itself. Below are some
tests that simplify and capture the problem with the way the Geo tests
were written:

```js
import { BFormInput } from 'bootstrap-vue';
import { mount } from '@vue/test-utils';

describe('number/text confusion', () => {
  const factory = (template, value) => {
    wrapper = mount({
      components: { BFormInput },
      data: () => ({ modelValue: value }),
      template,
    });
  };

  describe('given a number value without number prop', () => {
    beforeEach(() => {
      factory(`<b-form-input v-model="modelValue" />`, 5);
    });

    it('blur changes the model value to stringified original', () => {
      expect(wrapper.vm.modelValue).toBe(5);

      // BFormInput's model event is 'update'
      wrapper.find(BFormInput).vm.$emit('update', 20);
      expect(wrapper.vm.modelValue).toBe(20);

      wrapper.find('input').trigger('blur');

      // Note value has changed!
      expect(wrapper.vm.modelValue).toBe('5');
    });
  });

  describe('given a number value with number prop', () => {
    beforeEach(() => {
      factory(`<b-form-input v-model="modelValue" number />`, 5);
    });

    it('blur does not mess things up', () => {
      expect(wrapper.vm.modelValue).toBe(5);

      // BFormInput's model event is 'update'
      wrapper.find(BFormInput).vm.$emit('update', 20);
      expect(wrapper.vm.modelValue).toBe(20);

      wrapper.find('input').trigger('blur');

      // Note value has *not* changed
      expect(wrapper.vm.modelValue).toBe(20);
    });
  });
});
```

This explains why doing
`findGeoSettingsTimeoutField().vm.$emit('blur');` fixes the tests
(because this bypasses `BFormInput`'s `onBlur` handler), and why
`findGeoSettingsTimeoutField().setValue(data);` also fixes the tests
(because it correctly updates the _internal_ `vModelValue` of the
`BFormInput`, such that the `onBlur` handler uses that new value rather
than the old one).

Phew!

What all this means that this is not a bug in GitLab UI v18.7, and it's
not a bug in `bootstrap-vue`. It's *actually* a subtle bug in
`GeoSettingsForm`, due to weak typing of the model and our previously
broken `GlFormInput` model binding that GitLab UI v18.7 fixes.

Therefore, I think the _correct_ fix here is two-fold:
1. set the `number` prop to `true` on the `timeout` field
1. Use `setValue` in _both_ tests

Either one of these fixes the tests, but I think *both* should be done.
The first correctly casts the model to a number, and the second more
closely mirrors a user's action.

[138]: https://github.com/bootstrap-vue/bootstrap-vue/blob/028b880ea1ec168888368f3abc7e89f1f8f2f9cd/src/mixins/form-text.js#L138
[161]: https://github.com/bootstrap-vue/bootstrap-vue/blob/028b880ea1ec168888368f3abc7e89f1f8f2f9cd/src/mixins/form-text.js#L161
[237]: https://github.com/bootstrap-vue/bootstrap-vue/blob/028b880ea1ec168888368f3abc7e89f1f8f2f9cd/src/mixins/form-text.js#L237
[zcuddy]: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38824#note_394960100
parent 43aa71eb
...@@ -111,7 +111,7 @@ export default { ...@@ -111,7 +111,7 @@ export default {
:class="{ 'is-invalid': Boolean(formErrors[formGroup.key]) }" :class="{ 'is-invalid': Boolean(formErrors[formGroup.key]) }"
class="col-sm-3" class="col-sm-3"
type="number" type="number"
@input="checkCapacity(formGroup)" @update="checkCapacity(formGroup)"
/> />
</gl-form-group> </gl-form-group>
</div> </div>
......
...@@ -69,7 +69,7 @@ export default { ...@@ -69,7 +69,7 @@ export default {
:class="{ 'is-invalid': Boolean(formErrors.name) }" :class="{ 'is-invalid': Boolean(formErrors.name) }"
data-qa-selector="node_name_field" data-qa-selector="node_name_field"
type="text" type="text"
@input="checkName" @update="checkName"
/> />
<span class="gl-text-gray-700 m-n5 gl-z-index-2">{{ 255 - nodeData.name.length }}</span> <span class="gl-text-gray-700 m-n5 gl-z-index-2">{{ 255 - nodeData.name.length }}</span>
</div> </div>
...@@ -106,7 +106,7 @@ export default { ...@@ -106,7 +106,7 @@ export default {
:class="{ 'is-invalid': Boolean(formErrors.url) }" :class="{ 'is-invalid': Boolean(formErrors.url) }"
data-qa-selector="node_url_field" data-qa-selector="node_url_field"
type="text" type="text"
@input="checkUrl" @update="checkUrl"
/> />
<span class="gl-text-gray-700 m-n5 gl-z-index-2">{{ 255 - nodeData.url.length }}</span> <span class="gl-text-gray-700 m-n5 gl-z-index-2">{{ 255 - nodeData.url.length }}</span>
</div> </div>
......
...@@ -56,6 +56,7 @@ export default { ...@@ -56,6 +56,7 @@ export default {
v-model="timeout" v-model="timeout"
class="col-sm-2" class="col-sm-2"
type="number" type="number"
number
:class="{ 'is-invalid': Boolean(formErrors.timeout) }" :class="{ 'is-invalid': Boolean(formErrors.timeout) }"
@blur="checkTimeout" @blur="checkTimeout"
/> />
......
...@@ -107,7 +107,7 @@ describe('GeoSettingsForm', () => { ...@@ -107,7 +107,7 @@ describe('GeoSettingsForm', () => {
beforeEach(() => { beforeEach(() => {
createStore(); createStore();
createComponent(); createComponent();
findGeoSettingsTimeoutField().vm.$emit('input', data); findGeoSettingsTimeoutField().setValue(data);
findGeoSettingsTimeoutField().trigger('blur'); findGeoSettingsTimeoutField().trigger('blur');
}); });
...@@ -134,7 +134,7 @@ describe('GeoSettingsForm', () => { ...@@ -134,7 +134,7 @@ describe('GeoSettingsForm', () => {
beforeEach(() => { beforeEach(() => {
createStore(); createStore();
createComponent(); createComponent();
findGeoSettingsAllowedIpField().vm.$emit('input', data); findGeoSettingsAllowedIpField().setValue(data);
findGeoSettingsAllowedIpField().trigger('blur'); findGeoSettingsAllowedIpField().trigger('blur');
}); });
......
...@@ -848,10 +848,10 @@ ...@@ -848,10 +848,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.158.0.tgz#300d416184a2b0e05f15a96547f726e1825b08a1" resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.158.0.tgz#300d416184a2b0e05f15a96547f726e1825b08a1"
integrity sha512-5OJl+7TsXN9PJhY6/uwi+mTwmDZa9n/6119rf77orQ/joFYUypaYhBmy/1TcKVPsy5Zs6KCxE1kmGsfoXc1TYA== integrity sha512-5OJl+7TsXN9PJhY6/uwi+mTwmDZa9n/6119rf77orQ/joFYUypaYhBmy/1TcKVPsy5Zs6KCxE1kmGsfoXc1TYA==
"@gitlab/ui@18.6.2": "@gitlab/ui@18.7.0":
version "18.6.2" version "18.7.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-18.6.2.tgz#92d18dd36482ae412500820835e9d62f30f108d4" resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-18.7.0.tgz#aee0054d50e50aaf9e7c4ea4b9e36ca4b97102bf"
integrity sha512-3g22Q9RM1rmexipsZdroETJXd20+Fam1CHsC1h8vOWV4Fad5u3lgQp3KIQQlbmROIGTJ4PbiwE1Qldg+XAMsUw== integrity sha512-y1Gix1aCHvVO+zh6TCDmsCr97nLLHFnfEZRtg69EBnLBCLgwBcucC3mNeR4Q2EHTWjy/5U035UkyW6LDRX05mA==
dependencies: dependencies:
"@babel/standalone" "^7.0.0" "@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0" "@gitlab/vue-toasted" "^1.3.0"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment