jestjsjest-preset-angular

Sometimes when I update the snapshots I got an Attribute __ngContext__


Sometimes when I update the snapshots I got an Attribute ngContext and for fix this problem I've to clean and install my node_modules to "fix" this issue. I've to do this every time that I need to update a snapshot. I've already searched on multiple solutions and nothing worked.

snapshotSerializers: \[

'jest-preset-angular/build/serializers/no-ng-attributes',

'jest-preset-angular/build/serializers/ng-snapshot',

'jest-preset-angular/build/serializers/html-comment',

\],

Can someone help me with this, please?

Here is an image

I've updated the jest versions and also the jest-present-angular too but didn't work. I just want to have a solution that does not makes me clean install the node_modules every time


Solution

  • This is indeed annoying especially because it tends to change after upgrading angular version. My snapshots are now failing as well because of this difference :-/.

    -   __ngContext__={[Function LRootView]}
    +   __ngContext__="0"
    

    So, having look at the jest configuration, the snapshot serializers are being loaded from 'jest-preset-angular' module.

    The relevant plugin here is 'jest-preset-angular/build/serializers/ng-snapshot'. Now, they are two ways what to do to get rid of __ngContext__.

    1. replace the plugin entirely by a modified copy

    Create a copy of that file in the same directory and adapt it accordingly (line https://github.com/thymikee/jest-preset-angular/blob/40b769b8eba0b82913827793b6d9fe06d41808d9/src/serializers/ng-snapshot.ts#L69):

    const attributes = Object.keys(componentInstance).filter(key => key !== '__ngContext__');
    

    Adapt the configuration:

      snapshotSerializers: [
        'jest-preset-angular/build/serializers/no-ng-attributes',
        './custom-snapshot-serializer.ts',
        'jest-preset-angular/build/serializers/html-comment',
      ],
    

    The disadvantage of this solution is that you have to maintain the plugin although only one line has been changed.

    1. replace the plugin by a wrapper (preferred solution)

    This creates just a wrapper for the original implementation. The idea is to remove __ngContext__ before it moves on down the plugin chain. However, the logic of the original plugin is used for the fixture serialization.

    import type { ComponentRef, DebugNode, Type, ɵCssSelectorList } from '@angular/core';
    import type { ComponentFixture } from '@angular/core/testing';
    import type { Colors } from 'pretty-format';
    import { test as origTest, print as origPrint } from 'jest-preset-angular/build/serializers/ng-snapshot';
    
    /**
     * The follow interfaces are customized heavily inspired by @angular/core/core.d.ts
     */
    interface ComponentDef {
      selectors: ɵCssSelectorList;
    }
    interface IvyComponentType extends Type<unknown> {
      ɵcmp: ComponentDef;
    }
    interface NgComponentRef extends ComponentRef<unknown> {
      componentType: IvyComponentType;
      _elDef: any; // eslint-disable-line @typescript-eslint/no-explicit-any
      _view: any; // eslint-disable-line @typescript-eslint/no-explicit-any
    }
    interface NgComponentFixture extends ComponentFixture<unknown> {
      componentRef: NgComponentRef;
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      componentInstance: Record<string, any>;
    }
    
    /**
     * The following types haven't been exported by jest so temporarily we copy typings from 'pretty-format'
     */
    interface PluginOptions {
      edgeSpacing: string;
      min: boolean;
      spacing: string;
    }
    type Indent = (indentSpaces: string) => string;
    type Printer = (elementToSerialize: unknown) => string;
    
    export const print = (fixture: any, print: Printer, indent: Indent, opts: PluginOptions, colors: Colors): any => {
      const componentInstance = (fixture as NgComponentFixture).componentInstance;
      const instance = { ...componentInstance };
      delete instance.__ngContext__;
      const modifiedFixture = { ...fixture, componentInstance: { ...instance } };
      return origPrint(modifiedFixture, print, indent, opts, colors);
    };
    
    // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
    export const test = (val: any): boolean => {
      return origTest(val);
    };
    

    The configuration is adapted the same way as before.