Search code examples
web-component-tester

Trying to understand pathMappings


I have a Polymer project I am working on with a (partial) structure of

app/
app/elements/my-element/my-element.html
bower_components/

When coding up my-element.html, I want its html imports to reference urls of the form ../polymer/polymer.html . What this implies is that I need to map both app/elements and bower_components to the same url (with precedence going to app/elements if they exist, otherwise it should fall back to looking for them in the bower_components directory.

I have tried to set up my wct-conf.js file do do this. After a lot of playing around with the debugger, I got to this point, which I thought would do it,

var ret = {
  'suites': ['app/elements/**/test'],
  'plugins': ['local'],
  'webserver': {

    'pathMappings': [
      {'/components/<basename>/components': 'app/elements'},
      {'/components/<basename>/components': 'bower_components'},
      {'/components/<basename>/api': 'server/mock/fake.js'},
      {'/components/<basename>':'app'}
    ]
  }
};

module.exports = ret;

But as soon as I run gulp test:local to run them I get a load of strange urls reporting 404s. They are like this

404 GET /components/pasv5/app/elements/bower_components/webcomponentsjs/webcomponents.min.js
404 GET /components/pasv5/app/elements/bower_components/test-fixture/test-fixture-mocha.js
404 GET /components/pasv5/app/elements/bower_components/test-fixture/test-fixture-mocha.js

Which has managed to merge app/elements and bower_components in some strange way.

What am I doing wrong, and how should I do it?


Solution

  • I have now found the answer. There are two possible ways, but first let me explain what I have discovered about pathMappings.

    Firstly, although they are an array of mappings from url to file offset, its not a simple Array. Each array element is actually an object which my have many keys, and each key is a possible url prefix, and each value for that key is a path, relative to project root that that key represents. Web component tester uses a node module called serve-waterfall which actually defines a few entries of its own, specifically:-

      WEB_COMPONENT: [
        {'/components/<basename>': '.'},
        {'/components': 'bower_components'},
        {'/components': '..'},
        {'/': '.'},
      ],
    

    During the configuration stage, any array you provide via a module export in wct-conf.js (and you can locate this file in either your home directory or project root or both) is merged with the above list with the objects in the same array index being augmented with the keys you provide.

    Finally, Web Component Tester adds a path of its own:-

    {'/components': path.join(WCT_ROOT, 'bower_components')},
    

    where WCT_ROOT is where WCT is located in your node_modules directory although this is pushed into the array at the end. This ensures that mocha, chai and sinon are accessible for testing.

    Once the pathMappings have been built, they are flattened into a "waterfall" array, so each key in each index of the array becomes an object with two keys, prefix and target, and this waterfall array is important because its ordered. Thus is is also important how the various sources of pathMappings are merged together, and that could be more luck than judgement.

    Web component testing starts an Express web server with server root located at the project root.

    When the web server is running, and when a there is a url for which web-component-tester is solely in charge (such as the web runner component) this is served by an app.get() call. If its not caught by that, then the serve-waterfall component kicks in as middleware, using app.use(). In a fairly convoluted way, this steps through the "waterfall" array trying each matching prefix and the composing a url based around the Express server root and the target file you are trying to retrieve. If this attempt generates a "404" error, it catches it, and tries the next ,matching waterfall array entry.

    OK, so much for background, now for the solution. If you want to do something complicated, you can define a registerHook function in the wct-conf.js file and in it redefine the pathMappings completely.

    Something like the following:-

    var ret = {
      'suites': ['app/elements/**/test'],
      'plugins': ['local'],
      'registerHooks' : function(context) {
        var existingMapping = context.options.webserver.pathMappings;
        var pathMappings = [
          {'/components/<basename>/bower_components': 'app/elements'},
          {'/components/<basename>/app/elements': 'bower_components'},
        ]
        pathMappings = pathMappings.concat(existingMapping);
        context.options.webserver.pathMappings = pathMappings;
      }
    
    };
    
    module.exports = ret;
    

    However for my problem raised above, the following will do the job just as well...

    var ret = {
      'suites': ['app/elements/**/test'],
      'plugins': ['local'],
      'webserver': {
        'pathMappings': [
          {'/components/<basename>/bower_components': 'app/elements'},
          {'/components/<basename>/app/elements': 'bower_components'},
        ]
    
      }
    };
    
    module.exports = ret;
    

    And here is a brief explanation of what it does. The suites parameter defines a list of files in looking for tests, and a request for url /components/<basename>/app/elements/xxx/test/xxx-test.html (or similar) gets requested. That succeeds from the first element of the waterfall array.

    No doubt the test harness tries to import ../xxx.html and this is turned into request for /component/<basename>/app/elements/xxx.html and again succeeds from the first element of the array.

    Next the element under test will try and html import ..\polymer\polymer.html

    Again wct turns this into a request for /components/<basename>/app/elements/polymer/polymer.html, but this fails and so the next item in the Waterfall Array is checked. The only one that matches is with prefix /components/<basename>/app/elements and that is turned into a request of /components/<basename>/bower_components/polymer/polymer.html. And it works.

    With the configuration I supplied, you could also map the other way, the first item in the waterfall array would fail for /components/<basename>/bower_components/xxx/xxx.html and the only matching prefix after that is the /components/<basename>/app/elements one. I can't actually see this one being needed.