`
        })
        class Cmp {
          w0: string|null = null;
          w1: string|null = null;
          w2: string|null = null;
        }
        TestBed.configureTestingModule(
            {declarations: [Cmp, DirThatSetsWidthDirective, AnotherDirThatSetsWidthDirective]});
        const fixture = TestBed.createComponent(Cmp);
        fixture.componentInstance.w0 = '100px';
        fixture.componentInstance.w1 = '200px';
        fixture.componentInstance.w2 = '300px';
        fixture.detectChanges();
        const element = fixture.nativeElement.querySelector('div');
        expect(element.style.width).toEqual('100px');
        fixture.componentInstance.w0 = null;
        fixture.detectChanges();
        expect(element.style.width).toEqual('200px');
        fixture.componentInstance.w1 = null;
        fixture.detectChanges();
        expect(element.style.width).toEqual('300px');
        fixture.componentInstance.w2 = null;
        fixture.detectChanges();
        expect(element.style.width).toBeFalsy();
        fixture.componentInstance.w2 = '400px';
        fixture.detectChanges();
        expect(element.style.width).toEqual('400px');
        fixture.componentInstance.w1 = '500px';
        fixture.componentInstance.w0 = '600px';
        fixture.detectChanges();
        expect(element.style.width).toEqual('600px');
      });
  onlyInIvy('ivy resolves styling across directives, components and templates in unison')
      .it('should only run stylingFlush once when there are no collisions between styling properties',
          () => {
            @Directive({selector: '[dir-with-styling]'})
            class DirWithStyling {
              @HostBinding('style.font-size') public fontSize = '100px';
            }
            @Component({selector: 'comp-with-styling'})
            class CompWithStyling {
              @HostBinding('style.width') public width = '900px';
              @HostBinding('style.height') public height = '900px';
            }
            @Component({
              template: `
        
...
      `
            })
            class Cmp {
              opacity: string|null = '0.5';
              @ViewChild(CompWithStyling, {static: true})
              compWithStyling: CompWithStyling|null = null;
              @ViewChild(DirWithStyling, {static: true}) dirWithStyling: DirWithStyling|null = null;
            }
            TestBed.configureTestingModule({declarations: [Cmp, DirWithStyling, CompWithStyling]});
            const fixture = TestBed.createComponent(Cmp);
            fixture.detectChanges();
            const component = fixture.componentInstance;
            const element = fixture.nativeElement.querySelector('comp-with-styling');
            const node = getDebugNode(element) !;
            const styles = node.styles !;
            const config = styles.context.config;
            expect(config.hasCollisions).toBeFalsy();
            expect(config.hasMapBindings).toBeFalsy();
            expect(config.hasPropBindings).toBeTruthy();
            expect(config.allowDirectStyling).toBeTruthy();
            expect(element.style.opacity).toEqual('0.5');
            expect(element.style.width).toEqual('900px');
            expect(element.style.height).toEqual('900px');
            expect(element.style.fontSize).toEqual('100px');
            // once for the template flush and again for the host bindings
            expect(ngDevMode !.flushStyling).toEqual(2);
            ngDevModeResetPerfCounters();
            component.opacity = '0.6';
            component.compWithStyling !.height = '100px';
            component.compWithStyling !.width = '100px';
            component.dirWithStyling !.fontSize = '50px';
            fixture.detectChanges();
            expect(element.style.opacity).toEqual('0.6');
            expect(element.style.width).toEqual('100px');
            expect(element.style.height).toEqual('100px');
            expect(element.style.fontSize).toEqual('50px');
            // there is no need to flush styling since the styles are applied directly
            expect(ngDevMode !.flushStyling).toEqual(0);
          });
  onlyInIvy('ivy resolves styling across directives, components and templates in unison')
      .it('should combine all styling across the template, directive and component host bindings',
          () => {
            @Directive({selector: '[dir-with-styling]'})
            class DirWithStyling {
              @HostBinding('style.color') public color = 'red';
              @HostBinding('style.font-size') public fontSize = '100px';
              @HostBinding('class.dir') public dirClass = true;
            }
            @Component({selector: 'comp-with-styling'})
            class CompWithStyling {
              @HostBinding('style.width') public width = '900px';
              @HostBinding('style.height') public height = '900px';
              @HostBinding('class.comp') public compClass = true;
            }
            @Component({
              template: `
        
...
      `
            })
            class Cmp {
              opacity: string|null = '0.5';
              width: string|null = 'auto';
              tplClass = true;
            }
            TestBed.configureTestingModule({declarations: [Cmp, DirWithStyling, CompWithStyling]});
            const fixture = TestBed.createComponent(Cmp);
            fixture.detectChanges();
            const element = fixture.nativeElement.querySelector('comp-with-styling');
            const node = getDebugNode(element) !;
            const styles = node.styles !;
            const classes = node.classes !;
            expect(styles.values).toEqual({
              'color': 'red',
              'width': 'auto',
              'opacity': '0.5',
              'height': '900px',
              'font-size': '100px'
            });
            expect(classes.values).toEqual({
              'dir': true,
              'comp': true,
              'tpl': true,
            });
            fixture.componentInstance.width = null;
            fixture.componentInstance.opacity = null;
            fixture.componentInstance.tplClass = false;
            fixture.detectChanges();
            expect(styles.values).toEqual({
              'color': 'red',
              'width': '900px',
              'opacity': null,
              'height': '900px',
              'font-size': '100px'
            });
            expect(classes.values).toEqual({
              'dir': true,
              'comp': true,
              'tpl': false,
            });
          });
  onlyInIvy('ivy resolves styling across directives, components and templates in unison')
      .it('should properly apply styling across sub and super class directive host bindings',
          () => {
            @Directive({selector: '[super-class-dir]'})
            class SuperClassDirective {
              @HostBinding('style.width') public w1 = '100px';
            }
            @Component({selector: '[sub-class-dir]'})
            class SubClassDirective extends SuperClassDirective {
              @HostBinding('style.width') public w2 = '200px';
            }
            @Component({
              template: `
          
      `
            })
            class Cmp {
              w3: string|null = '300px';
            }
            TestBed.configureTestingModule(
                {declarations: [Cmp, SuperClassDirective, SubClassDirective]});
            const fixture = TestBed.createComponent(Cmp);
            fixture.detectChanges();
            const element = fixture.nativeElement.querySelector('div');
            const node = getDebugNode(element) !;
            const styles = node.styles !;
            expect(styles.values).toEqual({
              'width': '300px',
            });
            fixture.componentInstance.w3 = null;
            fixture.detectChanges();
            expect(styles.values).toEqual({
              'width': '200px',
            });
          });
  onlyInIvy('only ivy has style/class bindings debugging support')
      .it('should support situations where there are more than 32 bindings', () => {
        const TOTAL_BINDINGS = 34;
        let bindingsHTML = '';
        let bindingsArr: any[] = [];
        for (let i = 0; i < TOTAL_BINDINGS; i++) {
          bindingsHTML += `[style.prop${i}]="bindings[${i}]" `;
          bindingsArr.push(null);
        }
        @Component({template: `
`})
        class Cmp {
          bindings = bindingsArr;
          updateBindings(value: string) {
            for (let i = 0; i < TOTAL_BINDINGS; i++) {
              this.bindings[i] = value + i;
            }
          }
        }
        TestBed.configureTestingModule({declarations: [Cmp]});
        const fixture = TestBed.createComponent(Cmp);
        let testValue = 'initial';
        fixture.componentInstance.updateBindings('initial');
        fixture.detectChanges();
        const element = fixture.nativeElement.querySelector('div');
        const node = getDebugNode(element) !;
        const styles = node.styles !;
        let values = styles.values;
        let props = Object.keys(values);
        expect(props.length).toEqual(TOTAL_BINDINGS);
        for (let i = 0; i < props.length; i++) {
          const prop = props[i];
          const value = values[prop] as string;
          const num = value.substr(testValue.length);
          expect(value).toEqual(`initial${num}`);
        }
        testValue = 'final';
        fixture.componentInstance.updateBindings('final');
        fixture.detectChanges();
        values = styles.values;
        props = Object.keys(values);
        expect(props.length).toEqual(TOTAL_BINDINGS);
        for (let i = 0; i < props.length; i++) {
          const prop = props[i];
          const value = values[prop] as string;
          const num = value.substr(testValue.length);
          expect(value).toEqual(`final${num}`);
        }
      });
  onlyInIvy('only ivy has style debugging support')
      .it('should apply map-based style and class entries', () => {
        @Component({template: '
'})
        class Cmp {
          public c: {[key: string]: any}|null = null;
          updateClasses(classes: string) {
            const c = this.c || (this.c = {});
            Object.keys(this.c).forEach(className => { c[className] = false; });
            classes.split(/\s+/).forEach(className => { c[className] = true; });
          }
          public s: {[key: string]: any}|null = null;
          updateStyles(prop: string, value: string|number|null) {
            const s = this.s || (this.s = {});
            Object.assign(s, {[prop]: value});
          }
          reset() {
            this.s = null;
            this.c = null;
          }
        }
        TestBed.configureTestingModule({declarations: [Cmp]});
        const fixture = TestBed.createComponent(Cmp);
        const comp = fixture.componentInstance;
        comp.updateStyles('width', '100px');
        comp.updateStyles('height', '200px');
        comp.updateClasses('abc');
        fixture.detectChanges();
        const element = fixture.nativeElement.querySelector('div');
        const node = getDebugNode(element) !;
        let styles = node.styles !;
        let classes = node.classes !;
        let stylesSummary = styles.summary;
        let widthSummary = stylesSummary['width'];
        expect(widthSummary.prop).toEqual('width');
        expect(widthSummary.value).toEqual('100px');
        let heightSummary = stylesSummary['height'];
        expect(heightSummary.prop).toEqual('height');
        expect(heightSummary.value).toEqual('200px');
        let classesSummary = classes.summary;
        let abcSummary = classesSummary['abc'];
        expect(abcSummary.prop).toEqual('abc');
        expect(abcSummary.value).toBeTruthy();
        comp.reset();
        comp.updateStyles('width', '500px');
        comp.updateStyles('height', null);
        comp.updateClasses('def');
        fixture.detectChanges();
        styles = node.styles !;
        classes = node.classes !;
        stylesSummary = styles.summary;
        widthSummary = stylesSummary['width'];
        expect(widthSummary.value).toEqual('500px');
        heightSummary = stylesSummary['height'];
        expect(heightSummary.value).toEqual(null);
        classesSummary = classes.summary;
        abcSummary = classesSummary['abc'];
        expect(abcSummary).toBeUndefined();
        let defSummary = classesSummary['def'];
        expect(defSummary.prop).toEqual('def');
        expect(defSummary.value).toBeTruthy();
      });
  onlyInIvy('ivy resolves styling across directives, components and templates in unison')
      .it('should resolve styling collisions across templates, directives and components for prop and map-based entries',
          () => {
            @Directive({selector: '[dir-that-sets-styling]'})
            class DirThatSetsStyling {
              @HostBinding('style') public map: any = {color: 'red', width: '777px'};
            }
            @Component({
              template: `
        
      `
            })
            class Cmp {
              map: any = {width: '111px', opacity: '0.5'};
              width: string|null = '555px';
              @ViewChild('dir', {read: DirThatSetsStyling, static: true})
              dir !: DirThatSetsStyling;
            }
            TestBed.configureTestingModule({declarations: [Cmp, DirThatSetsStyling]});
            const fixture = TestBed.createComponent(Cmp);
            const comp = fixture.componentInstance;
            fixture.detectChanges();
            const element = fixture.nativeElement.querySelector('div');
            const node = getDebugNode(element) !;
            const styles = node.styles !;
            expect(styles.values).toEqual({
              'width': '555px',
              'color': 'red',
              'font-size': '99px',
              'opacity': '0.5',
            });
            comp.width = null;
            fixture.detectChanges();
            expect(styles.values).toEqual({
              'width': '111px',
              'color': 'red',
              'font-size': '99px',
              'opacity': '0.5',
            });
            comp.map = null;
            fixture.detectChanges();
            expect(styles.values).toEqual({
              'width': '777px',
              'color': 'red',
              'font-size': '99px',
              'opacity': null,
            });
            comp.dir.map = null;
            fixture.detectChanges();
            expect(styles.values).toEqual({
              'width': '200px',
              'color': null,
              'font-size': '99px',
              'opacity': null,
            });
          });
  onlyInIvy('ivy resolves styling across directives, components and templates in unison')
      .it('should only apply each styling property once per CD across templates, components, directives',
          () => {
            @Directive({selector: '[dir-that-sets-styling]'})
            class DirThatSetsStyling {
              @HostBinding('style') public map: any = {width: '999px', height: '999px'};
            }
            @Component({
              template: `
                
              `
            })
            class Cmp {
              width: string|null = '111px';
              height: string|null = '111px';
              map: any = {width: '555px', height: '555px'};
              @ViewChild('dir', {read: DirThatSetsStyling, static: true})
              dir !: DirThatSetsStyling;
            }
            TestBed.configureTestingModule({declarations: [Cmp, DirThatSetsStyling]});
            const fixture = TestBed.createComponent(Cmp);
            const comp = fixture.componentInstance;
            ngDevModeResetPerfCounters();
            fixture.detectChanges();
            const element = fixture.nativeElement.querySelector('div');
            // both are applied because this is the first pass
            assertStyleCounters(2, 0);
            assertStyle(element, 'width', '111px');
            assertStyle(element, 'height', '111px');
            comp.width = '222px';
            ngDevModeResetPerfCounters();
            fixture.detectChanges();
            assertStyleCounters(1, 0);
            assertStyle(element, 'width', '222px');
            assertStyle(element, 'height', '111px');
            comp.height = '222px';
            ngDevModeResetPerfCounters();
            fixture.detectChanges();
            assertStyleCounters(1, 0);
            assertStyle(element, 'width', '222px');
            assertStyle(element, 'height', '222px');
            comp.width = null;
            ngDevModeResetPerfCounters();
            fixture.detectChanges();
            assertStyleCounters(1, 0);
            assertStyle(element, 'width', '555px');
            assertStyle(element, 'height', '222px');
            comp.width = '123px';
            comp.height = '123px';
            ngDevModeResetPerfCounters();
            fixture.detectChanges();
            assertStyle(element, 'width', '123px');
            assertStyle(element, 'height', '123px');
            comp.map = {};
            ngDevModeResetPerfCounters();
            fixture.detectChanges();
            // both are applied because the map was altered
            assertStyleCounters(2, 0);
            assertStyle(element, 'width', '123px');
            assertStyle(element, 'height', '123px');
            comp.width = null;
            ngDevModeResetPerfCounters();
            fixture.detectChanges();
            // the width is applied both in TEMPLATE and in HOST_BINDINGS mode
            assertStyleCounters(2, 0);
            assertStyle(element, 'width', '999px');
            assertStyle(element, 'height', '123px');
            comp.dir.map = null;
            ngDevModeResetPerfCounters();
            fixture.detectChanges();
            // the width is only applied once
            assertStyleCounters(1, 0);
            assertStyle(element, 'width', '0px');
            assertStyle(element, 'height', '123px');
            comp.dir.map = {width: '1000px', height: '1000px', color: 'red'};
            ngDevModeResetPerfCounters();
            fixture.detectChanges();
            // only the width and color have changed
            assertStyleCounters(2, 0);
            assertStyle(element, 'width', '1000px');
            assertStyle(element, 'height', '123px');
            assertStyle(element, 'color', 'red');
            comp.height = null;
            ngDevModeResetPerfCounters();
            fixture.detectChanges();
            // height gets applied twice and all other
            // values get applied
            assertStyleCounters(4, 0);
            assertStyle(element, 'width', '1000px');
            assertStyle(element, 'height', '1000px');
            assertStyle(element, 'color', 'red');
            comp.map = {color: 'blue', width: '2000px', opacity: '0.5'};
            ngDevModeResetPerfCounters();
            fixture.detectChanges();
            assertStyleCounters(5, 0);
            assertStyle(element, 'width', '2000px');
            assertStyle(element, 'height', '1000px');
            assertStyle(element, 'color', 'blue');
            assertStyle(element, 'opacity', '0.5');
            comp.map = {color: 'blue', width: '2000px'};
            ngDevModeResetPerfCounters();
            fixture.detectChanges();
            // all four are applied because the map was altered
            assertStyleCounters(4, 1);
            assertStyle(element, 'width', '2000px');
            assertStyle(element, 'height', '1000px');
            assertStyle(element, 'color', 'blue');
            assertStyle(element, 'opacity', '');
          });
  onlyInIvy('only ivy has style/class bindings debugging support')
      .it('should sanitize style values before writing them', () => {
        @Component({
          template: `
                
              `
        })
        class Cmp {
          widthExp = '';
          bgImageExp = '';
          styleMapExp: any = {};
        }
        TestBed.configureTestingModule({declarations: [Cmp]});
        const fixture = TestBed.createComponent(Cmp);
        const comp = fixture.componentInstance;
        fixture.detectChanges();
        const element = fixture.nativeElement.querySelector('div');
        const node = getDebugNode(element) !;
        const styles = node.styles !;
        const lastSanitizedProps: any[] = [];
        styles.overrideSanitizer((prop, value) => {
          lastSanitizedProps.push(prop);
          return value;
        });
        comp.bgImageExp = '123';
        fixture.detectChanges();
        expect(styles.values).toEqual({
          'background-image': '123',
          'width': null,
        });
        expect(lastSanitizedProps).toEqual(['background-image']);
        lastSanitizedProps.length = 0;
        comp.styleMapExp = {'clip-path': '456'};
        fixture.detectChanges();
        expect(styles.values).toEqual({
          'background-image': '123',
          'clip-path': '456',
          'width': null,
        });
        expect(lastSanitizedProps).toEqual(['background-image', 'clip-path']);
        lastSanitizedProps.length = 0;
        comp.widthExp = '789px';
        fixture.detectChanges();
        expect(styles.values).toEqual({
          'background-image': '123',
          'clip-path': '456',
          'width': '789px',
        });
        expect(lastSanitizedProps).toEqual(['background-image', 'clip-path']);
        lastSanitizedProps.length = 0;
      });
  onlyInIvy('only ivy has style/class bindings debugging support')
      .it('should apply a unit to a style before writing it', () => {
        @Component({
          template: `
            
          `
        })
        class Cmp {
          widthExp: string|number|null = '';
          heightExp: string|number|null = '';
        }
        TestBed.configureTestingModule({declarations: [Cmp]});
        const fixture = TestBed.createComponent(Cmp);
        const comp = fixture.componentInstance;
        fixture.detectChanges();
        const element = fixture.nativeElement.querySelector('div');
        const node = getDebugNode(element) !;
        const styles = node.styles !;
        comp.widthExp = '200';
        comp.heightExp = 10;
        fixture.detectChanges();
        expect(styles.values).toEqual({
          'width': '200px',
          'height': '10em',
        });
        comp.widthExp = 0;
        comp.heightExp = null;
        fixture.detectChanges();
        expect(styles.values).toEqual({
          'width': '0px',
          'height': null,
        });
      });
  it('should be able to bind a SafeValue to clip-path', () => {
    @Component({template: '
'})
    class Cmp {
      path !: SafeStyle;
    }
    TestBed.configureTestingModule({declarations: [Cmp]});
    const fixture = TestBed.createComponent(Cmp);
    const sanitizer: DomSanitizer = TestBed.inject(DomSanitizer);
    fixture.componentInstance.path = sanitizer.bypassSecurityTrustStyle('url("#test")');
    fixture.detectChanges();
    const html = fixture.nativeElement.innerHTML;
    // Note that check the raw HTML, because (at the time of writing) the Node-based renderer
    // that we use to run tests doesn't support `clip-path` in `CSSStyleDeclaration`.
    expect(html).toMatch(/style=["|']clip-path:\s*url\(.*#test.*\)/);
  });
  it('should handle values wrapped into SafeValue', () => {
    @Component({
      template: `
        
        
        
        
        
        
      `,
    })
    class MyComp {
      constructor(private sanitizer: DomSanitizer) {}
      public width: string = 'calc(20%)';
      public height: string = '10px';
      public background: string = '1.png';
      public color: string = 'red';
      private getSafeStyle(value: string) { return this.sanitizer.bypassSecurityTrustStyle(value); }
      getBackgroundSafe() { return this.getSafeStyle(`url("/${this.background}")`); }
      getWidthSafe() { return this.getSafeStyle(this.width); }
      getHeightSafe() { return this.getSafeStyle(this.height); }
      getColorUnsafe() { return this.color; }
    }
    TestBed.configureTestingModule({
      imports: [CommonModule],
      declarations: [MyComp],
    });
    const fixture = TestBed.createComponent(MyComp);
    fixture.detectChanges();
    const comp = fixture.componentInstance;
    const div = fixture.nativeElement.querySelector('div');
    const p = fixture.nativeElement.querySelector('p');
    const span = fixture.nativeElement.querySelector('span');
    expect(div.style.background).toContain('url("/1.png")');
    expect(p.style.width).toBe('calc(20%)');
    expect(p.style.height).toBe('10px');
    expect(span.style.color).toBe('red');
    comp.background = '2.png';
    comp.width = '5px';
    comp.height = '100%';
    comp.color = 'green';
    fixture.detectChanges();
    expect(div.style.background).toContain('url("/2.png")');
    expect(p.style.width).toBe('5px');
    expect(p.style.height).toBe('100%');
    expect(span.style.color).toBe('green');
  });
  onlyInIvy('only ivy has style/class bindings debugging support')
      .it('should evaluate follow-up [style] maps even if a former map is null', () => {
        @Directive({selector: '[dir-with-styling]'})
        class DirWithStyleMap {
          @HostBinding('style') public styleMap: any = {color: 'red'};
        }
        @Directive({selector: '[dir-with-styling-part2]'})
        class DirWithStyleMapPart2 {
          @HostBinding('style') public styleMap: any = {width: '200px'};
        }
        @Component({
          template: `
        
      `
        })
        class Cmp {
          map: any = null;
          @ViewChild('div', {read: DirWithStyleMap, static: true})
          dir1 !: DirWithStyleMap;
          @ViewChild('div', {read: DirWithStyleMapPart2, static: true})
          dir2 !: DirWithStyleMapPart2;
        }
        TestBed.configureTestingModule(
            {declarations: [Cmp, DirWithStyleMap, DirWithStyleMapPart2]});
        const fixture = TestBed.createComponent(Cmp);
        fixture.detectChanges();
        const element = fixture.nativeElement.querySelector('div');
        const node = getDebugNode(element) !;
        const styles = node.styles !;
        const values = styles.values;
        const props = Object.keys(values).sort();
        expect(props).toEqual(['color', 'width']);
        expect(values['width']).toEqual('200px');
        expect(values['color']).toEqual('red');
      });
  onlyInIvy('only ivy has style/class bindings debugging support')
      .it('should evaluate initial style/class values on a list of elements that changes', () => {
        @Component({
          template: `
            
              {{ item }}
            
          `
        })
        class Cmp {
          items = [1, 2, 3];
        }
        TestBed.configureTestingModule({declarations: [Cmp]});
        const fixture = TestBed.createComponent(Cmp);
        const comp = fixture.componentInstance;
        fixture.detectChanges();
        function getItemElements(): HTMLElement[] {
          return [].slice.call(fixture.nativeElement.querySelectorAll('div'));
        }
        function getItemClasses(): string[] {
          return getItemElements().map(e => e.className).sort().join(' ').split(' ');
        }
        expect(getItemElements().length).toEqual(3);
        expect(getItemClasses()).toEqual([
          'initial-class',
          'item-1',
          'initial-class',
          'item-2',
          'initial-class',
          'item-3',
        ]);
        comp.items = [2, 4, 6, 8];
        fixture.detectChanges();
        expect(getItemElements().length).toEqual(4);
        expect(getItemClasses()).toEqual([
          'initial-class',
          'item-2',
          'initial-class',
          'item-4',
          'initial-class',
          'item-6',
          'initial-class',
          'item-8',
        ]);
      });
  onlyInIvy('only ivy has style/class bindings debugging support')
      .it('should create and update multiple class bindings across multiple elements in a template',
          () => {
            @Component({
              template: `
            
            
              {{ item }}
            
            
          `
            })
            class Cmp {
              items = [1, 2, 3];
            }
            TestBed.configureTestingModule({declarations: [Cmp]});
            const fixture = TestBed.createComponent(Cmp);
            const comp = fixture.componentInstance;
            fixture.detectChanges();
            function getItemElements(): HTMLElement[] {
              return [].slice.call(fixture.nativeElement.querySelectorAll('div'));
            }
            function getItemClasses(): string[] {
              return getItemElements().map(e => e.className).sort().join(' ').split(' ');
            }
            const header = fixture.nativeElement.querySelector('header');
            expect(header.classList.contains('header'));
            const footer = fixture.nativeElement.querySelector('footer');
            expect(footer.classList.contains('footer'));
            expect(getItemElements().length).toEqual(3);
            expect(getItemClasses()).toEqual([
              'item',
              'item-1',
              'item',
              'item-2',
              'item',
              'item-3',
            ]);
          });
  onlyInIvy('only ivy has style/class bindings debugging support')
      .it('should understand multiple directives which contain initial classes', () => {
        @Directive({selector: 'dir-one'})
        class DirOne {
          @HostBinding('class') public className = 'dir-one';
        }
        @Directive({selector: 'dir-two'})
        class DirTwo {
          @HostBinding('class') public className = 'dir-two';
        }
        @Component({
          template: `
            
            
            
          `
        })
        class Cmp {
        }
        TestBed.configureTestingModule({declarations: [Cmp, DirOne, DirTwo]});
        const fixture = TestBed.createComponent(Cmp);
        fixture.detectChanges();
        const dirOne = fixture.nativeElement.querySelector('dir-one');
        const div = fixture.nativeElement.querySelector('div');
        const dirTwo = fixture.nativeElement.querySelector('dir-two');
        expect(dirOne.classList.contains('dir-one')).toBeTruthy();
        expect(dirTwo.classList.contains('dir-two')).toBeTruthy();
        expect(div.classList.contains('initial')).toBeTruthy();
      });
  onlyInIvy('only ivy has style/class bindings debugging support')
      .it('should evaluate styling across the template directives when there are multiple elements/sources of styling',
          () => {
            @Directive({selector: '[one]'})
            class DirOne {
              @HostBinding('class') public className = 'dir-one';
            }
            @Directive({selector: '[two]'})
            class DirTwo {
              @HostBinding('class') public className = 'dir-two';
            }
            @Component({
              template: `
                
                
                
              `
            })
            class Cmp {
              w = 100;
              h = 200;
              c = 'red';
            }
            TestBed.configureTestingModule({declarations: [Cmp, DirOne, DirTwo]});
            const fixture = TestBed.createComponent(Cmp);
            fixture.detectChanges();
            const divA = fixture.nativeElement.querySelector('.a');
            const divB = fixture.nativeElement.querySelector('.b');
            const divC = fixture.nativeElement.querySelector('.c');
            expect(divA.style.width).toEqual('100px');
            expect(divB.style.height).toEqual('200px');
            expect(divC.style.color).toEqual('red');
          });
  onlyInIvy('only ivy has style/class bindings debugging support')
      .it('should evaluate styling across the template and directives within embedded views',
          () => {
            @Directive({selector: '[some-dir-with-styling]'})
            class SomeDirWithStyling {
              @HostBinding('style')
              public styles = {
                width: '200px',
                height: '500px',
              };
            }
            @Component({
              template: `
                
                  {{ item }}
                
                
                
              `
            })
            class Cmp {
              items: any[] = [];
              c = 'red';
              w = 100;
              h = 100;
            }
            TestBed.configureTestingModule({declarations: [Cmp, SomeDirWithStyling]});
            const fixture = TestBed.createComponent(Cmp);
            const comp = fixture.componentInstance;
            comp.items = [1, 2, 3, 4];
            fixture.detectChanges();
            const items = fixture.nativeElement.querySelectorAll('.item');
            expect(items.length).toEqual(4);
            const [a, b, c, d] = items;
            expect(a.style.height).toEqual('0px');
            expect(b.style.height).toEqual('100px');
            expect(c.style.height).toEqual('200px');
            expect(d.style.height).toEqual('300px');
            const section = fixture.nativeElement.querySelector('section');
            const p = fixture.nativeElement.querySelector('p');
            expect(section.style['width']).toEqual('100px');
            expect(p.style['height']).toEqual('100px');
          });
  onlyInIvy('only ivy has style/class bindings debugging support')
      .it('should flush bindings even if any styling hasn\'t changed in a previous directive',
          () => {
            @Directive({selector: '[one]'})
            class DirOne {
              @HostBinding('style.width') w = '100px';
              @HostBinding('style.opacity') o = '0.5';
            }
            @Directive({selector: '[two]'})
            class DirTwo {
              @HostBinding('style.height') h = '200px';
              @HostBinding('style.color') c = 'red';
            }
            @Component({template: '
'})
            class Cmp {
              @ViewChild('target', {read: DirOne, static: true}) one !: DirOne;
              @ViewChild('target', {read: DirTwo, static: true}) two !: DirTwo;
            }
            TestBed.configureTestingModule({declarations: [Cmp, DirOne, DirTwo]});
            const fixture = TestBed.createComponent(Cmp);
            const comp = fixture.componentInstance;
            fixture.detectChanges();
            const div = fixture.nativeElement.querySelector('div');
            expect(div.style.opacity).toEqual('0.5');
            expect(div.style.color).toEqual('red');
            expect(div.style.width).toEqual('100px');
            expect(div.style.height).toEqual('200px');
            comp.two.h = '300px';
            fixture.detectChanges();
            expect(div.style.opacity).toEqual('0.5');
            expect(div.style.color).toEqual('red');
            expect(div.style.width).toEqual('100px');
            expect(div.style.height).toEqual('300px');
          });
  it('should work with NO_CHANGE values if they are applied to bindings ', () => {
    @Component({
      template: `
            
          `
    })
    class Cmp {
      w: any = null;
      h: any = null;
      o: any = null;
    }
    TestBed.configureTestingModule({declarations: [Cmp]});
    const fixture = TestBed.createComponent(Cmp);
    const comp = fixture.componentInstance;
    comp.w = '100px';
    comp.h = '200px';
    comp.o = '0.5';
    fixture.detectChanges();
    const div = fixture.nativeElement.querySelector('div');
    expect(div.style.width).toEqual('100px');
    expect(div.style.height).toEqual('200px');
    expect(div.style.opacity).toEqual('0.5');
    comp.w = '500px';
    comp.o = '1';
    fixture.detectChanges();
    expect(div.style.width).toEqual('500px');
    expect(div.style.height).toEqual('200px');
    expect(div.style.opacity).toEqual('1');
  });
  it('should allow [ngStyle] and [ngClass] to be used together', () => {
    @Component({
      template: `
            
          `
    })
    class Cmp {
      c: any = 'foo bar';
      s: any = {width: '200px'};
    }
    TestBed.configureTestingModule({declarations: [Cmp]});
    const fixture = TestBed.createComponent(Cmp);
    fixture.detectChanges();
    const div = fixture.nativeElement.querySelector('div');
    expect(div.style.width).toEqual('200px');
    expect(div.classList.contains('foo')).toBeTruthy();
    expect(div.classList.contains('bar')).toBeTruthy();
  });
  it('should allow to reset style property value defined using ngStyle', () => {
    @Component({
      template: `
        
      `
    })
    class Cmp {
      s: any = {opacity: '1'};
      clearStyle(): void { this.s = null; }
    }
    TestBed.configureTestingModule({declarations: [Cmp]});
    const fixture = TestBed.createComponent(Cmp);
    const comp = fixture.componentInstance;
    fixture.detectChanges();
    const div = fixture.nativeElement.querySelector('div');
    expect(div.style.opacity).toEqual('1');
    comp.clearStyle();
    fixture.detectChanges();
    expect(div.style.opacity).toEqual('');
  });
  it('should allow detectChanges to be run in a property change that causes additional styling to be rendered',
     () => {
       @Component({
         selector: 'child',
         template: `
          
        `,
       })
       class ChildCmp {
         readyTpl = false;
         @HostBinding('class.ready-host')
         readyHost = false;
       }
       @Component({
         selector: 'parent',
         template: `
        
      `,
         host: {
           '[style.color]': 'color',
         },
       })
       class ParentCmp {
         private _prop = '';
         @ViewChild('template', {read: ViewContainerRef})
         vcr: ViewContainerRef = null !;
         private child: ComponentRef
 = null !;
         @Input()
         set prop(value: string) {
           this._prop = value;
           if (this.child && value === 'go') {
             this.child.instance.readyHost = true;
             this.child.instance.readyTpl = true;
             this.child.changeDetectorRef.detectChanges();
           }
         }
         get prop() { return this._prop; }
         ngAfterViewInit() {
           const factory = this.componentFactoryResolver.resolveComponentFactory(ChildCmp);
           this.child = this.vcr.createComponent(factory);
         }
         constructor(private componentFactoryResolver: ComponentFactoryResolver) {}
       }
       @Component({
         template: ``,
       })
       class App {
         prop = 'a';
       }
       @NgModule({
         entryComponents: [ChildCmp],
         declarations: [ChildCmp],
       })
       class ChildCmpModule {
       }
       TestBed.configureTestingModule({declarations: [App, ParentCmp], imports: [ChildCmpModule]});
       const fixture = TestBed.createComponent(App);
       fixture.detectChanges(false);
       let readyHost = fixture.nativeElement.querySelector('.ready-host');
       let readyChild = fixture.nativeElement.querySelector('.ready-child');
       expect(readyHost).toBeFalsy();
       expect(readyChild).toBeFalsy();
       fixture.componentInstance.prop = 'go';
       fixture.detectChanges(false);
       readyHost = fixture.nativeElement.querySelector('.ready-host');
       readyChild = fixture.nativeElement.querySelector('.ready-child');
       expect(readyHost).toBeTruthy();
       expect(readyChild).toBeTruthy();
     });
  it('should allow detectChanges to be run in a hook that causes additional styling to be rendered',
     () => {
       @Component({
         selector: 'child',
         template: `
          
        `,
       })
       class ChildCmp {
         readyTpl = false;
         @HostBinding('class.ready-host')
         readyHost = false;
       }
       @Component({
         selector: 'parent',
         template: `
          
        `,
       })
       class ParentCmp {
         updateChild = false;
         @ViewChild('template', {read: ViewContainerRef})
         vcr: ViewContainerRef = null !;
         private child: ComponentRef = null !;
         ngDoCheck() {
           if (this.updateChild) {
             this.child.instance.readyHost = true;
             this.child.instance.readyTpl = true;
             this.child.changeDetectorRef.detectChanges();
           }
         }
         ngAfterViewInit() {
           const factory = this.componentFactoryResolver.resolveComponentFactory(ChildCmp);
           this.child = this.vcr.createComponent(factory);
         }
         constructor(private componentFactoryResolver: ComponentFactoryResolver) {}
       }
       @Component({
         template: ``,
       })
       class App {
         @ViewChild('parent', {static: true}) public parent: ParentCmp|null = null;
       }
       @NgModule({
         entryComponents: [ChildCmp],
         declarations: [ChildCmp],
       })
       class ChildCmpModule {
       }
       TestBed.configureTestingModule({declarations: [App, ParentCmp], imports: [ChildCmpModule]});
       const fixture = TestBed.createComponent(App);
       fixture.detectChanges(false);
       let readyHost = fixture.nativeElement.querySelector('.ready-host');
       let readyChild = fixture.nativeElement.querySelector('.ready-child');
       expect(readyHost).toBeFalsy();
       expect(readyChild).toBeFalsy();
       const parent = fixture.componentInstance.parent !;
       parent.updateChild = true;
       fixture.detectChanges(false);
       readyHost = fixture.nativeElement.querySelector('.ready-host');
       readyChild = fixture.nativeElement.querySelector('.ready-child');
       expect(readyHost).toBeTruthy();
       expect(readyChild).toBeTruthy();
     });
  onlyInIvy('only ivy allows for multiple styles/classes to be balanced across directives')
      .it('should allow various duplicate properties to be defined in various styling maps within the template and directive styling bindings',
          () => {
            @Component({
              template: `
           
         `
            })
            class Cmp {
              h = '100px';
              w = '100px';
              s1: any = {border: '10px solid black', width: '200px'};
              s2: any = {border: '10px solid red', width: '300px'};
            }
            @Directive({selector: '[dir-with-styling]'})
            class DirectiveExpectingStyling {
              @Input('dir-with-styling') @HostBinding('style') public styles: any = null;
            }
            TestBed.configureTestingModule({declarations: [Cmp, DirectiveExpectingStyling]});
            const fixture = TestBed.createComponent(Cmp);
            fixture.detectChanges();
            const element = fixture.nativeElement.querySelector('div');
            expect(element.style.border).toEqual('10px solid black');
            expect(element.style.width).toEqual('100px');
            expect(element.style.height).toEqual('100px');
            fixture.componentInstance.s1 = null;
            fixture.detectChanges();
            expect(element.style.border).toEqual('10px solid red');
            expect(element.style.width).toEqual('100px');
            expect(element.style.height).toEqual('100px');
          });
  it('should retrieve styles set via Renderer2', () => {
    let dirInstance: any;
    @Directive({
      selector: '[dir]',
    })
    class Dir {
      constructor(public elementRef: ElementRef, public renderer: Renderer2) { dirInstance = this; }
      setStyles() {
        const nativeEl = this.elementRef.nativeElement;
        this.renderer.setStyle(nativeEl, 'transform', 'translate3d(0px, 0px, 0px)');
        this.renderer.addClass(nativeEl, 'my-class');
      }
    }
    @Component({template: `
`})
    class App {
    }
    TestBed.configureTestingModule({
      declarations: [App, Dir],
    });
    const fixture = TestBed.createComponent(App);
    fixture.detectChanges();
    dirInstance.setStyles();
    const div = fixture.debugElement.children[0];
    expect(div.styles.transform).toMatch(/translate3d\(0px\s*,\s*0px\s*,\s*0px\)/);
    expect(div.classes['my-class']).toBe(true);
  });
  it('should not set classes when falsy value is passed while a sanitizer is present', () => {
    @Component({
      // Note that we use `background` here because it needs to be sanitized.
      template: `
        
        
      `,
    })
    class AppComponent {
      isDisabled = false;
      background = 'orange';
    }
    TestBed.configureTestingModule({declarations: [AppComponent]});
    const fixture = TestBed.createComponent(AppComponent);
    fixture.detectChanges();
    const span = fixture.nativeElement.querySelector('span');
    expect(span.classList).not.toContain('disabled');
    // The issue we're testing for happens after the second change detection.
    fixture.detectChanges();
    expect(span.classList).not.toContain('disabled');
  });
  it('should not set classes when falsy value is passed while a sanitizer from host bindings is present',
     () => {
       @Directive({selector: '[blockStyles]'})
       class StylesDirective {
         @HostBinding('style.border')
         border = '1px solid red';
         @HostBinding('style.background')
         background = 'white';
       }
       @Component({
         template: `
`,
       })
       class AppComponent {
         isDisabled = false;
       }
       TestBed.configureTestingModule({declarations: [AppComponent, StylesDirective]});
       const fixture = TestBed.createComponent(AppComponent);
       fixture.detectChanges();
       const div = fixture.nativeElement.querySelector('div');
       expect(div.classList.contains('disabled')).toBe(false);
       // The issue we're testing for happens after the second change detection.
       fixture.detectChanges();
       expect(div.classList.contains('disabled')).toBe(false);
     });
  it('should throw an error if a prop-based style/class binding value is changed during checkNoChanges',
     () => {
       @Component({
         template: `
        
      `
       })
       class Cmp {
         color = 'red';
         fooClass = true;
         ngAfterViewInit() {
           this.color = 'blue';
           this.fooClass = false;
         }
       }
       TestBed.configureTestingModule({declarations: [Cmp]});
       const fixture = TestBed.createComponent(Cmp);
       expect(() => {
         fixture.detectChanges();
       }).toThrowError(/ExpressionChangedAfterItHasBeenCheckedError/);
     });
  onlyInIvy('only ivy allows for map-based style AND class bindings')
      .it('should throw an error if a map-based style/class binding value is changed during checkNoChanges',
          () => {
            @Component({
              template: `
                
              `
            })
            class Cmp {
              style: any = {width: '100px'};
              klass: any = {foo: true, bar: false};
              ngAfterViewInit() {
                this.style = {height: '200px'};
                this.klass = {foo: false};
              }
            }
            TestBed.configureTestingModule({declarations: [Cmp]});
            const fixture = TestBed.createComponent(Cmp);
            expect(() => {
              fixture.detectChanges();
            }).toThrowError(/ExpressionChangedAfterItHasBeenCheckedError/);
          });
  it('should properly merge class interpolation with class-based directives', () => {
    @Component(
        {template: `
`})
    class MyComp {
      one = 'one';
    }
    const fixture =
        TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp);
    fixture.detectChanges();
    expect(fixture.debugElement.nativeElement.innerHTML).toContain('zero');
    expect(fixture.debugElement.nativeElement.innerHTML).toContain('one');
    expect(fixture.debugElement.nativeElement.innerHTML).toContain('two');
    expect(fixture.debugElement.nativeElement.innerHTML).toContain('three');
  });
  it('should allow to reset style property value defined using [style.prop.px] binding', () => {
    @Component({
      template: '
',
    })
    class MyComp {
      left = '';
    }
    TestBed.configureTestingModule({declarations: [MyComp]});
    const fixture = TestBed.createComponent(MyComp);
    fixture.detectChanges();
    const checks = [
      ['15', '15px'],
      [undefined, ''],
      [null, ''],
      ['', ''],
      ['0', '0px'],
    ];
    const div = fixture.nativeElement.querySelector('div');
    checks.forEach((check: any[]) => {
      const [fieldValue, expectedValue] = check;
      fixture.componentInstance.left = fieldValue;
      fixture.detectChanges();
      expect(div.style.left).toBe(expectedValue);
    });
  });
  onlyInIvy('only ivy treats [class] in concert with other class bindings')
      .it('should retain classes added externally', () => {
        @Component({template: `
`})
        class MyComp {
          exp = '';
        }
        const fixture =
            TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp);
        fixture.detectChanges();
        const div = fixture.nativeElement.querySelector('div') !;
        div.className += ' abc';
        expect(splitSortJoin(div.className)).toEqual('abc');
        fixture.componentInstance.exp = '1 2 3';
        fixture.detectChanges();
        expect(splitSortJoin(div.className)).toEqual('1 2 3 abc');
        fixture.componentInstance.exp = '4 5 6 7';
        fixture.detectChanges();
        expect(splitSortJoin(div.className)).toEqual('4 5 6 7 abc');
        function splitSortJoin(s: string) { return s.split(/\s+/).sort().join(' ').trim(); }
      });
  describe('ExpressionChangedAfterItHasBeenCheckedError', () => {
    it('should not throw when bound to SafeValue', () => {
      @Component({template: `
`})
      class MyComp {
        icon = 'https://i.imgur.com/4AiXzf8.jpg';
        get iconSafe() { return this.sanitizer.bypassSecurityTrustStyle(`url("${this.icon}")`); }
        constructor(private sanitizer: DomSanitizer) {}
      }
      const fixture =
          TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp);
      fixture.detectChanges(true /* Verify that check no changes does not cause an exception */);
      const div: HTMLElement = fixture.nativeElement.querySelector('div');
      expect(div.style.getPropertyValue('background-image'))
          .toEqual('url("https://i.imgur.com/4AiXzf8.jpg")');
    });
  });
  isBrowser && it('should process 
        
      `,
      styles: [
        'div { width: 100px; }',
      ]
    })
    class MyComp {
    }
    TestBed.configureTestingModule({
      declarations: [MyComp],
    });
    const fixture = TestBed.createComponent(MyComp);
    fixture.detectChanges();
    // `styles` array values are applied first, styles from