javascriptpolymerpolymer-1.0web-component-tester

Polymer.js Web Component Tester Failing to Find Local DOM Element


Here is my property declaration:

  properties: {

    size: {
      type: String,
      value: ''
    }, 

    sizeName: {
      type: String,
      value: ''
    }
  }

My CSS:

<style>
  :root {
    --width: 20px;
    --height: 20px;
    --border-color: black;
    --font-size: 16px;
  }
  :host {
    display: inline-block;
    box-sizing: border-box;
    height: var(--height);
    width: var(--width);
  }
  #icon {
    position: relative;
    display: block;
    width: var(--width);
    height: var(--height);
    border-radius: 50%;
    border: 1px solid var(--border-color);
    font-size: var(--font-size);
    @apply(--size-icon);
  }
  #icon:before {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 100%;
    content: '?';
    text-align: center;
    @apply(--size-icon--before);
  }
  #icon.small:before {content: 's';}
  #icon.medium:before {content: 'm';}
  #icon.large:before {content: 'l';}

  #name {
    font-size: var(--font-size);
    @apply(--size-name);
  }

  .hidden {
    display: none;
  }
</style>

I have a test fixture as follows:

<test-fixture id="hasWrongSize">
  <template>
    <size-icon size="asdf"></size-icon>
  </template>
  <template>
    <size-icon size=""></size-icon>
  </template>
</test-fixture>

With a test suite as follows:

  suite('when no icon size or incorrect icon size is provided', function() {
    var el;
    setup(function() {
      el = fixture('hasWrongSize');
    });
    test('when the "size" property is incorrect the inner html equals "?"', function(done) {
      flush(function() {
        var domEl = Polymer.dom(el.root).querySelector('#icon:before');
        expect(domEl.innerHTML).to.equal('?');
        done();
      });
    });
  });

The Local DOM of the custom element is as follows:

<template>
  <template is="dom-if" if="{{ !sizeName }}">
    <span id="icon" class$="{{size}}"></span>
  </template>
  <span id="name">{{ sizeName }}</span>
</template>

I am trying to get the :before pseudo-element with the ID of #icon but WCT isn't able to find it and domEl in the test suite is equating to null.

What am I doing wrong?


Solution

  • UPDATE The question was updated to include the complete code of the test-fixture, which revealed the root cause.

    As you noted in your own answer:

    1. I have two template tags so it did not know which element to target.

    That alone is the cause of your problem, and the pseudo-element actually had nothing to do with it. You'll find that if you remove one of the templates from the test-fixture, your original code would've worked (and would've been cleaner than what you have now IMO).

    The test-fixture should only contain one template with a fixed state of your custom element as a baseline (the most common denominator) for your tests. In this case, that baseline is a single size-icon, but it seems you're adding a template for each possible test case, but that setup should really occur in the test script like this:

    <test-fixture id="basic">
      <template>
        <size-icon></size-icon>
      </template>
    </test-fixture>
    
    <script>
      suite('size-icon', function() {
        var el;
    
        setup(function() {
          el = fixture('basic');
        });
    
        function expectIconEquals(done, contents) {
          flush(function() {
            var domEl = Polymer.dom(el.root).querySelector('#icon');
            expect(domEl.innerHTML).to.equal(contents);
            done();
          });
        }
    
        test('when the "size" property is empty, the inner html equals "?"', function(done) {
          el.size = "";
          expectIconEquals(done, '?');
        });
    
        test('when the "size" property is invalid, the inner html equals "?"', function(done) {
          el.size = "asdf";
          expectIconEquals(done, '?');
        });
    
        test('when the "size" property is valid, the inner html equals "<some valid content>"', function(done) {
          el.size = "12%";
          expectIconEquals(done, '<some valid content>');
        });
      });
    </script>
    

    Answer to original question:

    In your code, the span#icon element is only stamped when sizeName is not undefined and is empty. The binding evaluation is similar to that of computed bindings (even though that's not explicitly stated for non-computed bindings):

    The computing function is not called until all dependent properties are defined (!=undefined). So each dependent properties should have a default value defined in properties (or otherwise be initialized to a non-undefined value) to ensure the function value is computed.

    To resolve the issue, set sizeName to the empty string in your test before flushing the template.

    suite('when no icon size or incorrect icon size is provided', function() {
      var el;
      setup(function() {
        el = fixture('hasWrongSize');
      });
      test('when the "size" property is incorrect the inner html equals "?"', function(done) {
    
        // #icon exists only when sizeName is empty string
        el.sizeName = "";
    
        flush(function() {
          var domEl = Polymer.dom(el.root).querySelector('#icon');
          expect(domEl).to.not.be.null;
          expect(domEl.innerHTML).to.equal('?');
          done();
        });
      });
    });
    

    Alternatively, you could initialize sizeName inside your dom-module:

    Polymer({
      is: 'size-icon',
      properties: {
        sizeName: {
          type: String,
          value: function() { return ""; }
        }
      }
    });