nativeElement.getBoundingClientRect()始终在Angular测试中返回0

时间:2018-02-20 21:32:58

标签: angular typescript jestjs

我有一个Angular Component(5.2),它相对于另一个元素的nativeElement.getBoundingClientRect()输出定位。

在测试中,我创建了一个包含样式的包装器组件来模拟定位元素,但返回的ClientRect始终为零。

有没有办法让Angular 实际将我的元素放在DOM中?

举例说明,这是我的TestComponent。在内部,<popup>组件将使用anchor.nativeElement.getBoundingClientRect()计算其固定位置。

我不认为这是相关的,但我正在使用Jest来执行测试。

我已尝试使用而不使用 async()

@Component({
    selector: 'test',
    styles: [`
        :host {
            width: 1000px;
            height: 1000px;
            display: flex;
            justify-content: center;
            align-content: center;
        }
    `],
    template: `
        <button #button></button>
        <popup [anchor]="button">
            <div id="helloWorld">hello world</div>
        </popup>
    `
})
class TestComponent {
    @ViewChild('button')
    public buttonElement: ElementRef;
}

describe('test', () => {
    let fixture;
    beforeEach(async(() => {
        TestBed.configureTestingModule({
            declarations: [TestComponent],
            imports: [PopupModule]
        }).compileComponents().then(() => {
            fixture = TestBed.createComponent(TestComponent);
            fixture.detectChanges();
        });
    }));
    it('should be positioned', () => {
        const button = fixture.componentInstance.buttonElement.nativeElement;

        // Either of these always fail, because the button is not positioned.
        // I have also tried using fixed positioning of the button.

        // this will fail
        expect(button.getBoundingClientRect().top).not.toEqual(0);

        // this will fail, too
        return fixture.whenRenderingDone().then(() => {
            expect(button.getBoundingClientRect().top).not.toEqual(0);
        });
    });
});

2 个答案:

答案 0 :(得分:0)

对我来说,当我在viewChild元素上使用此方法时,一切都开始起作用,我这样声明:

@ViewChild('progress', { static: true }) progress; 

之后,可以使用getBoundingClientRect方法。

this.progress.nativeElement.getBoundingClientRect()

PS:当我使用ElementRef时,它对我没有用。仅使用ViewChild可以。

答案 1 :(得分:0)

长话短说:

您只需要在ngAfterViewInit()挂钩中使用nativeElement类型的ElementRef,您的测试就会“看到” DOM中的元素。

长时间阅读:

.getBoundingClientRect()仅在元素已经渲染并存在于DOM中时才起作用。今天,当我尝试访问被单击的元素时遇到了这种情况,并且使用[routerLink]时,它是由路由器操作动态重新渲染的-我无法从视觉上识别此行为。它会导致此元素消失,并且无法进行任何测量。接下来,呈现了一些相似的元素,但ClientRect中的值为0,0,0,0。

因此,如果您使用某些元素的动态重新渲染功能(例如菜单或其他功能),则element.getBoundingClientRect()仅在元素消失,重新渲染或位于“”时才返回0,0,0,0。动作时间”(点击等)在DOM中尚不存在,就像您的情况一样。

因此,您需要等待,否则它将被重新渲染,然后下一步-您可以通过.getBoundingClientRect()获得其度量值。对于@ViewChild()最佳等待方式是ngAfterViewInit()钩子,您可以在其中访问nativeElement(作为最佳方法)。另外-有一些技巧,例如:

private elementInited$ = new Subject();
public desiredElement: ElementRef;
@ViewChild('someElemRef') set setElementRef(el: ElementRef) {
  if (!!el) {
    this.desiredElement= el;
    this.elementInited$.next();
  }
}

ngOnInit() {
  this.elementInited$.subscribe((inited) => {
  // do stuff with nativeElement
  const values = this.desiredElement.nativeElement.getBoundingClientRect() // <== works!
  })
}

但是它太棘手了:)

另外,等待元素的好方法是@Directive()。 使用指令,您可以以“角度方式”正确侦听元素,并且仅当元素存在于DOM中时指令才会触发。

在Devtools中使用Chrome的渲染突出显示功能也可能非常有用-只需打开devtools菜单-More tools > rendering > Paint flashing复选框有助于识别要重新渲染的元素。