$30 off During Our Annual Pro Sale. View Details »

Leveraging the complete Ember Toolbelt

Leveraging the complete Ember Toolbelt

How can we identify elements in the DOM in Ember acceptance and integration tests? And how can we make that so that it doesn't negatively affect our apps in production?

In this talk I'm showing how we can leverage the Ember toolbelt to answer both questions for the complete Ember community at once.

Marco Otte-Witte

July 11, 2017
Tweet

More Decks by Marco Otte-Witte

Other Decks in Programming

Transcript

  1. Leveraging the complete
    Ember Toolbelt
    to improve things for all of us

    View Slide

  2. Marco Otte-Witte
    @marcoow

    View Slide

  3. simplabs.com
    @simplabs

    View Slide

  4. View Slide

  5. View Slide

  6. How do we identify DOM elements in
    (acceptance and integration) tests?

    View Slide

  7. {{! templates/components/blog-post.hbs}}
    {{post.title}}
    {{post.body}}

    View Slide

  8. import { moduleForComponent, test } from 'ember-qunit';
    import hbs from 'htmlbars-inline-precompile';
    moduleForComponent('blog-post', 'blog post component', {
    integration: true
    });
    test('it renders', function(assert) {

    this.render(hbs`{{blog-post post=post}}`);
    assert.equal(
    this.$('h1').text().trim(), 'My awesome post'
    );
    assert.equal(
    this.$('p').text().trim(), "You won't believe it, …"
    );
    });

    View Slide

  9. View Slide

  10. {{! templates/components/blog-post.hbs}}
    - {{post.title}}
    + {{post.title}}
    {{post.body}}

    View Slide

  11. View Slide

  12. View Slide

  13. {{! templates/components/blog-post.hbs}}
    {{post.title}}
    {{post.body}}

    View Slide

  14. import { moduleForComponent, test } from 'ember-qunit';
    import hbs from 'htmlbars-inline-precompile';
    moduleForComponent('blog-post', 'blog post component', {
    integration: true
    });
    test('it renders', function(assert) {

    this.render(hbs`{{blog-post post=post}}`);
    assert.equal(
    this.$('.js-post-title').text().trim(), 'My awesome post'
    );
    assert.equal(
    this.$('.js-post-body').text().trim(), "You won't believe it, …"
    );
    });

    View Slide

  15. View Slide

  16. {{! templates/components/blog-post.hbs}}
    - {{post.title}}
    + {{post.title}}
    - {{post.body}}
    + {{post.body}}

    View Slide

  17. View Slide

  18. .js-post-title {
    font-size: 20px;
    font-weight: bold;
    }

    View Slide

  19. - .js-post-title {
    + .js-post-header {
    font-size: 20px;
    font-weight: bold;
    }
    {{! templates/components/blog-post.hbs}}
    - {{post.title}}
    + {{post.title}}
    {{post.body}}

    View Slide

  20. {{! templates/components/blog-post.hbs}}

    {{post.title}}
    {{post.body}}

    View Slide

  21. View Slide

  22. {{! templates/components/blog-post.hbs}}
    {{post.title}}
    {{post.body}}

    View Slide

  23. import { moduleForComponent, test } from 'ember-qunit';
    import hbs from 'htmlbars-inline-precompile';
    moduleForComponent('blog-post', 'blog post component', {
    integration: true
    });
    test('it renders', function(assert) {

    this.render(hbs`{{blog-post post=post}}`);
    assert.equal(
    this.$('[data-test-post-title]').text().trim(), 'My awesome post'
    );
    assert.equal(
    this.$('[data-test-post-body]').text().trim(), "You won't believe it, …"
    );
    });

    View Slide

  24. {{! templates/components/blog-post.hbs}}

    {{post.title}}
    {{post.body}}

    View Slide

  25. ember-test-selectors
    https://github.com/simplabs/ember-test-selectors

    View Slide

  26. {{blog-post data-test-post-id=post.id}}

    View Slide

  27. //app/components/blog-post.js
    import Ember from 'ember';
    export default Ember.Component.extend({
    post: null,
    'data-test-post-id': Ember.computed.readOnly('post.id'),
    });

    View Slide

  28. data-test-* everywhere

    View Slide

  29. //app/components/blog-post.js
    import Ember from 'ember';
    export default Ember.Component.extend({
    post: null,
    'data-test-post-id': Ember.computed.readOnly('post.id'),
    });

    View Slide

  30. //app/components/blog-post.js
    import Ember from 'ember';
    export default Ember.Component.extend({
    post: null
    });

    View Slide

  31. {{! templates/components/blog-post.hbs}}

    {{post.title}}
    {{post.body}}

    View Slide

  32. {{! templates/components/blog-post.hbs}}

    {{post.title}}
    {{post.body}}

    View Slide

  33. {{blog-post data-test-post}}
    {{#blog-post data-test-post}}
    {{/blog-post}}

    View Slide

  34. {{blog-post}}
    {{#blog-post}}
    {{/blog-post}}

    View Slide

  35. How does it all work?

    View Slide

  36. » ember install ember-test-selectors

    View Slide

  37. Thanks

    View Slide

  38. …there's more

    View Slide

  39. How does it all work internally?

    View Slide

  40. //app/components/blog-post.js
    import Ember from 'ember';
    export default Ember.Component.extend({
    post: null,
    'data-test-post-id': Ember.computed.readOnly('post.id'),
    });

    View Slide

  41. /* global Ember */
    (function() {
    var bindDataTestAttributes;
    Ember.Component.reopen({
    init: function() {
    this._super.apply(this, arguments);
    if (!bindDataTestAttributes) {
    bindDataTestAttributes = require(
    'ember-test-selectors/utils/bind-data-test-attributes'
    )['default'];
    }
    bindDataTestAttributes(this);
    }
    });
    })();

    View Slide

  42. included(app) {
    this._super.included.apply(this, arguments);

    if (!this._stripTestSelectors) {
    app.import('vendor/ember-test-selectors/patch-component.js');
    }
    }

    View Slide

  43. //app/components/blog-post.js
    import Ember from 'ember';
    export default Ember.Component.extend({
    post: null,
    'data-test-post-id': Ember.computed.readOnly('post.id'),
    });

    View Slide

  44. included(app) {
    this._super.included.apply(this, arguments);

    if (this._stripTestSelectors) {

    if (checker.satisfies('^5.0.0')) {

    app.options.babel.plugins.push(
    require('./strip-data-test-properties-plugin')
    );
    } else if (checker.satisfies('^6.0.0-beta.1')) {

    app.options.babel6.plugins.push(
    require('./strip-data-test-properties-plugin6')
    );
    }
    }
    }

    View Slide

  45. let TEST_SELECTOR_PREFIX = /data-test-.*/;
    function StripDataTestPropertiesPlugin() {
    return {
    visitor: {
    Property(path) {
    if (TEST_SELECTOR_PREFIX.test(path.node.key.value)) {
    path.remove();
    }
    },
    },
    };
    }

    View Slide

  46. https://astexplorer.net

    View Slide

  47. View Slide

  48. {{! templates/components/blog-post.hbs}}

    {{post.title}}
    {{post.body}}

    View Slide

  49. included(app) {
    _setupPreprocessorRegistry(registry) {
    if (this._stripTestSelectors) {
    let StripTestSelectorsTransform = require(
    './strip-test-selectors'
    );
    registry.add('htmlbars-ast-plugin', {
    name: 'strip-test-selectors',
    plugin: StripTestSelectorsTransform,
    baseDir() { return __dirname; }
    });
    }

    }
    }

    View Slide


  50. View Slide

  51. StripTestSelectorsTransform.prototype.transform = function(ast) {
    let walker = new this.syntax.Walker();
    walker.visit(ast, function(node) {
    if (node.type === 'ElementNode') {
    node.attributes = node.attributes.filter(function(attribute) {
    return !isTestSelector(attribute.name);
    });
    } else if (node.type === 'MustacheStatement' || node.type === 'BlockStatement') {
    node.params = node.params.filter(function(param) {
    return !isTestSelector(param.original);
    });
    node.hash.pairs = node.hash.pairs.filter(function(pair) {
    return !isTestSelector(pair.key);
    });
    }
    });
    return ast;
    };

    View Slide

  52. {{! templates/components/blog-post.hbs}}

    {{post.title}}
    {{post.body}}

    View Slide

  53. {{! templates/components/blog-post.hbs}}

    {{post.title}}
    {{post.body}}

    View Slide

  54. StripTestSelectorsTransform.prototype.transform = function(ast) {
    let walker = new this.syntax.Walker();
    walker.visit(ast, function(node) {
    if (node.type === 'ElementNode') {
    node.attributes = node.attributes.filter(function(attribute) {
    return !isTestSelector(attribute.name);
    });
    } else if (node.type === 'MustacheStatement' || node.type === 'BlockStatement') {
    node.params = node.params.filter(function(param) {
    return !isTestSelector(param.original);
    });
    node.hash.pairs = node.hash.pairs.filter(function(pair) {
    return !isTestSelector(pair.key);
    });
    }
    });
    return ast;
    };

    View Slide

  55. {{blog-post data-test-post}}
    {{#blog-post data-test-post}}
    {{/blog-post}}

    View Slide

  56. {{blog-post}}
    {{#blog-post}}
    {{/blog-post}}

    View Slide

  57. StripTestSelectorsTransform.prototype.transform = function(ast) {
    let walker = new this.syntax.Walker();
    walker.visit(ast, function(node) {
    if (node.type === 'ElementNode') {
    node.attributes = node.attributes.filter(function(attribute) {
    return !isTestSelector(attribute.name);
    });
    } else if (node.type === 'MustacheStatement' || node.type === 'BlockStatement') {
    node.params = node.params.filter(function(param) {
    return !isTestSelector(param.original);
    });
    node.hash.pairs = node.hash.pairs.filter(function(pair) {
    return !isTestSelector(pair.key);
    });
    }
    });
    return ast;
    };

    View Slide

  58. {{blog-post data-test-post=post.id}}
    {{#blog-post data-test-post=post.id}}
    {{/blog-post}}

    View Slide

  59. {{blog-post}}
    {{#blog-post}}
    {{/blog-post}}

    View Slide

  60. https://embermap.com/video/ember-test-selectors

    View Slide

  61. View Slide

  62. Thanks

    View Slide

  63. simplabs.com
    @simplabs

    View Slide