Skip to content

Inheriting from an abstract class causes decorators of subclasses to mix #444

Closed
@davidbludlow

Description

@davidbludlow

If abstract class AbstractBase extends Vue and class A extends AbstractBase and class B also extends AbstractBase, then class B will receive the decorators from class A, which is super bad and confusing.

The reason why this is an issue for abstract super classes (as opposed to not-abstract) is because TypeScript doesn't let you put a @Component decorator on abstract classes. So, whether the super class is abstract or not doesn't really matter, it just matters that there will not be a @Component decorator on that super class, which situation will cause this bug.

See the following test case

it('even if super class is abstract, should not get super and sub class decorators mixed up', async function() {
  // Watch function/decorator copied from
  // https://github.com/kaorun343/vue-property-decorator/blob/master/src/vue-property-decorator.ts
  function Watch(path: string, options: WatchOptions = {}) {
    const { deep = false, immediate = false } = options;

    return createDecorator((componentOptions, handler) => {
      if (typeof componentOptions.watch !== 'object') {
        componentOptions.watch = Object.create(null);
      }

      const watch: any = componentOptions.watch;

      if (typeof watch[path] === 'object' && !Array.isArray(watch[path])) {
        watch[path] = [watch[path]];
      } else if (typeof watch[path] === 'undefined') {
        watch[path] = [];
      }

      watch[path].push({ handler, deep, immediate });
    });
  }

  const spyBase = td.function('BaseNotify');
  const spyA = td.function('ANotify');
  const spyB = td.function('BNotify');
  const spyC = td.function('CNotify');

  // Since this is abstract, TS will not let us add the @Component decorator,
  // like we would normally do
  abstract class Base extends Vue {
    count = 0;

    @Watch('count')
    notify() {
      spyBase();
    }
  }

  @Component({})
  class A extends Base {
    notify() {
      spyA();
    }
  }

  @Component({ components: {} })
  class B extends Base {
    // Oh no! The super class also has a @Watch decorator for "count"!
    @Watch('count')
    notify() {
      spyB();
    }
  }

  @Component({ components: {} })
  class C extends Base {
    notify() {
      spyC();
    }
  }

  const vmA = new A();
  const vmB = new B();
  const vmC = new C();
  td.verify(spyBase(), { times: 0 });
  td.verify(spyA(), { times: 0 });
  td.verify(spyB(), { times: 0 });
  td.verify(spyC(), { times: 0 });

  vmA.count++;
  await vmA.$nextTick();
  td.verify(spyBase(), { times: 0 });
  td.verify(spyA(), { times: 1 });
  td.verify(spyB(), { times: 0 });
  td.verify(spyC(), { times: 0 });

  vmB.count++;
  await vmB.$nextTick();
  td.verify(spyBase(), { times: 0 });
  td.verify(spyA(), { times: 1 });
  // Someday it would be nice if we could change the line
  // below so that it said `1` not `2`
  td.verify(spyB(), { times: 2 });
  td.verify(spyC(), { times: 0 });

  vmC.count++;
  await vmA.$nextTick();
  td.verify(spyBase(), { times: 0 });
  td.verify(spyA(), { times: 1 });
  td.verify(spyB(), { times: 2 });
  td.verify(spyC(), { times: 1 });
});

That test will fail on the second to last line (td.verify(spyC(), { times: 1 });) with the error:

     Error: Unsatisfied verification on test double `CNotify`.

  Wanted:
    - called with `()` 1 time.

  All calls of the test double, in order were:
    - called with `()`.
    - called with `()`.
      at Object.fail (webpack:///./node_modules/testdouble/lib/log.js?:16:15)
      at Object.eval [as verify] (webpack:///./node_modules/testdouble/lib/verify.js?:20:23)
      at Context.eval (webpack:///./test/test.ts?:358:58)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions