3

I've been having a bear of a time with this. I'm using a third-part listbox widget (jqx-listbox) and trying to test it. Everything works fine under ng serve and even looking at the Karma output in Chrome, I can see the entire UI, with the listbox, and items, which is what I want to test.

The problem is that querying for the element within the test:

let openListbox = fixture.debugElement.query(By.css('.jqx-listitem-element')); returns null, while if I put a breakpoint on that line, and use jQuery in the Chrome console $('.jqx-listitem-element) then the DOM element is returned!

If I change the lookup in the test to the element as defined in the template (<jqxlistbox>) like so: let openListbox = fixture.debugElement.query(By.css('jqxlistbox')), that will return the debugElement, and I can even inspect inside that thing's native element right at that breakpoint and see it has stuff in it, but no matter what I've tried (returning that element then doing a query on it), I can't seem to query any deeper into the object, so my test keeps failing.

I've also tried using fakeAsync and fixture.whenStable but that just seems to get entirely skipped (or it never gets stable).

Here's the rendered template code generated by the widget at runtime (It's that innermost span that I'm trying to inspect)

<jqxlistbox _ngcontent-c0="" class="" style="" ng-reflect-attr-display-member="message" ng-reflect-attr-source="[object Object]"
  ng-reflect-attr-value-member="errorType" ng-reflect-attr-width="100%">
  <div class="ng-tns-c0-0 jqx-listbox jqx-reset jqx-rc-all jqx-widget jqx-widget-content jqx-disableselect" id="jqxWidgete238403f0fb8"
    style="width: 100%; height: 200px;" aria-multiselectable="false" role="listbox" tabindex="1">
    <div style="-webkit-appearance: none; background: transparent; outline: none; width:100%; height: 100%; align:left; border: 0px; padding: 0px; margin: 0px; left: 0px; top: 0px; valign:top; position: relative;">
      <div style="-webkit-appearance: none; border: none; background: transparent; outline: none; width:100%; height: 100%; padding: 0px; margin: 0px; align:left; left: 0px; top: 0px; valign:top; position: relative;">
        <div id="listBoxContentjqxWidgete238403f0fb8" style="-webkit-appearance: none; background: transparent; outline: none; border: none; padding: 0px; overflow: hidden; margin: 0px; left: 0px; top: 0px; position: absolute; width: 328px; height: 198px;">
          <div style="outline: none 0px; overflow: hidden; width: 329px; position: relative; height: 404px;">
            <div role="option" id="listitem0jqxWidgete238403f0fb8" class="jqx-listitem-element" style="height: 24px; top: 0px; left: 0px;"
              aria-selected="true">
              <span style="white-space: pre; display: block; visibility: inherit; width: 318px;" class="jqx-listitem-state-normal jqx-item jqx-rc-all jqx-listitem-state-selected jqx-fill-state-pressed">Open hard error</span>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</jqxlistbox>

EDIT I can get the outer element and it's innerText is the text of that span: "Open hard error" but that's just because it's the only text node in the whole element.

With this test code, the first assertion passes, the second fails with an error of "Cannot read property nativeElement of null" which is just so weird to me because if it can find the innerText, then it has the whole DOM element. I'm wondering if there's something wrong with querying a DebugElement returned by a query?:

fixture.detectChanges();
let openListbox = fixture.debugElement.query(By.css('#open jqxlistbox'));
expect(openListbox.nativeElement.innerText).toContain('Open hard error', 'item in listbox');

fixture.detectChanges();

let openListboxItem: HTMLElement = openListbox.query(By.css('span')).nativeElement;
expect(openListboxItem.textContent).toBe('Open hard error', 'item in listbox');
redOctober13
  • 3,662
  • 6
  • 34
  • 61

1 Answers1

1

I have a feeling that you're jqx-listitem-element's are being rendered based on some data provided to the jqxlistbox. In order to get access to those data-dependent children elements, you will need to query for them after the component has "initialized" (by calling fixture.detectChanges).

So, try the following:

let openListBox = fixture.debugElement.query(By.css('jqxlistbox'));
fixture.detectChanges();
let listItems = fixture.debugElement.queryAll(By.css('.jqx-listitem-element'));

That will initialize the component, causing the jqxlistbox to register it's data and render it's child list items, then you can query for those list items with the second queryAll line in the above snippet.


Edit in response to first comment & additional info:

I'm wondering if there's something wrong with querying a DebugElement returned by a query?

There's nothing wrong with querying a DebugElement, what you're doing is syntactically correct. You should be able to use the code I have added in the first version of this answer. I think it is probably about how you are populating those list items. Check your mock data / providers. I can't see your whole spec so I can't troubleshoot there.

vince
  • 7,808
  • 3
  • 34
  • 41
  • yeah, that was one of the first thing I tried, littering more `detectChanges` in there, including where you suggested, but the second query still returns null. Updating my original post with the HTML rendered during the test. Now, I did find that I could use `openListBox.innerText` since in this case the only text node is in that span, but it doesn't feel right; I *should* be able to get down to that DOM node. – redOctober13 Jan 19 '18 at 17:12
  • I'm having a similar issue with angular material table rows. Would be very interested in the answer. – miso Aug 06 '19 at 13:58