• Mark Florian's avatar
    Bump GitLab UI · e447cd3a
    Mark Florian authored
    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
    e447cd3a
This project manages its dependencies using Yarn. Learn more
yarn.lock 539 KB