typescriptoopts-jestcocoscreator

Is it possible to implement Typescript class which has a protected property?


I'm trying to mock a class of a third party library which contains a protected property and have got an error message.

here's a small reproducible example:

class WithProtected {
    protected property: number = 1
}

class Derived implements WithProtected {
    property = 3
}

which outputs the following error message:

Class 'Derived' incorrectly implements class 'WithProtected'. Did you mean to extend 'WithProtected' and inherit its members as a subclass?
  Property 'property' is protected but type 'Derived' is not a class derived from 'WithProtected'.(2720)

I'm kinda stuck to this mocking because the third party lib is Cocos Creator and doesn't live in the node_modules library. The only way Typescript is working with it is because it is linked via a path entry in the tsconfig.json file of the project.

So I'm not able to use the mocking out of the box from jest.

Also, as I cannot import directly from the lib itself, I have to write a fair amount of mock without importing members directly from 'cc' but with the import type {} from 'cc' which doesn't allow me to extend said classes and implement only the mocks.

Maybe too much details on this but if I could at least find a way to implement a class which contains a private prop, this would probably save me!


Solution

  • Classes with private or protected methods are intentionally incompatible with other classes or types, even if they are structurally identical. This is one of the few places in TypeScript where types are treated nominally instead of structurally, and so the problem you're facing is considered a feature. If you need compatibility with a private/protected property, you need your type to originate from the same declaration site.

    This implies that you'll never get another class statement to work directly.


    Instead, you could use type assertions on a class expression to claim that your new class is compatible with the other one. Maybe like this:

    import type { WithProtected } from "./theLib";
    
    const MyWithProtected = (class {
      property = 2;
    } as any) as new () => WithProtected;
    

    That compiles with no error (although the compiler is resistant and you need to use two type assertions to overcome its objections). Now you have a MyWithProtected class constructor that the compiler believes makes instances of your imported WithProtected type.

    And then if you need to make subclasses via extends you can do so:

    class Derived extends MyWithProtected {
      property = 3;
    }
    

    Codesandbox link to code