Skip to content

Wrap emit calls to nextTick

Illya Klymov requested to merge xanf-vtu-30-emit into master

What does this MR do?

This MR adds proper nextTick after every $emit call, if we are making assertions against component template / emitted events of the component

Closes #38114 (closed)

Why do we need this one?

We're preparing to upgrade @vue/test-utils to .beta.30 in &2291 (closed) which will drop sync mode. We want to make sure we're properly handling all the ways of changing component

How do you found all of them 🤔 ?

In order to reveal them I've monkey-patched @vue/test-utils to throw if we're trying to access component template after calling $emit

let poisoned = false;

const poisonMark = (obj, prop, state) => {
  const orig = obj[prop];
  obj[prop] = function p(...args) {
    const result = orig.apply(this, args);
    poisoned = state;
    return result;
  };
};

const ensureNotPoisoned = (obj, prop) => {
  const orig = obj[prop];
  obj[prop] = function p(...args) {
    if (poisoned) {
      throw new Error(`:${prop}: Dirty tree operation`);
    }
    return orig.apply(this, args);
  };
};

poisonMark(Vue.prototype, '$emit', true);
poisonMark(Vue.prototype, '$nextTick', false);
poisonMark(Vue, 'nextTick', false);
poisonMark(Vue.prototype, '$mount', false);
ensureNotPoisoned(vtu.Wrapper.prototype, 'attributes');
ensureNotPoisoned(vtu.Wrapper.prototype, 'classes');
ensureNotPoisoned(vtu.Wrapper.prototype, 'contains');
ensureNotPoisoned(vtu.Wrapper.prototype, 'emitted');
ensureNotPoisoned(vtu.Wrapper.prototype, 'emittedByOrder');
ensureNotPoisoned(vtu.Wrapper.prototype, 'find');
ensureNotPoisoned(vtu.Wrapper.prototype, 'findAll');
ensureNotPoisoned(vtu.Wrapper.prototype, 'html');
ensureNotPoisoned(vtu.Wrapper.prototype, 'isEmpty');
ensureNotPoisoned(vtu.Wrapper.prototype, 'isVisible');
ensureNotPoisoned(vtu.Wrapper.prototype, 'props');
ensureNotPoisoned(vtu.Wrapper.prototype, 'text');
ensureNotPoisoned(vtu.WrapperArray.prototype, 'at');
ensureNotPoisoned(vtu.WrapperArray.prototype, 'filter');
ensureNotPoisoned(vtu.WrapperArray.prototype, 'isEmpty');
expect.extend({
  toMatchSnapshot: function m(...args) {
    if (poisoned) {
      return {
        pass: false,
        message: () => 'Poisoned tree',
      };
    }
    return toMatchSnapshot.apply(this, args);
  },
});

Why are you fixing test cases which do not fail on upgrade to .beta-30?

Basically, when testing Vue component we do not want to know anything about it's internal structure. "Immediate" reaction to anything - either setData, setProps, trigger, $emit, whatever - is an implementation detail - for example we can perform some action in event handler, or we can update some state, which will trigger watch - former do not require nextTick, latter does. That's why we ensure all such calls are properly wrapped into nextTick

Does this MR meet the acceptance criteria?

Conformity

Edited by Illya Klymov

Merge request reports