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.

3179e6bb62dc91d29bb906ffef4fa2d4?s=128

Marco Otte-Witte

July 11, 2017
Tweet

Transcript

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

    of us
  2. Marco Otte-Witte @marcoow

  3. simplabs.com @simplabs

  4. None
  5. None
  6. How do we identify DOM elements in (acceptance and integration)

    tests?
  7. {{! templates/components/blog-post.hbs}} <h1>{{post.title}}</h1> <p>{{post.body}}</p>

  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, …" ); });
  9. None
  10. {{! templates/components/blog-post.hbs}} - <h1>{{post.title}}</h1> + <h2>{{post.title}}</h2> <p>{{post.body}}</p>

  11. None
  12. None
  13. {{! templates/components/blog-post.hbs}} <h2 class="js-post-title">{{post.title}}</h2> <p class="js-post-body">{{post.body}}</p>

  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, …" ); });
  15. None
  16. {{! templates/components/blog-post.hbs}} - <h2 class="js-post-title">{{post.title}}</h2> + <div class="title js-post-title">{{post.title}}</h2> -

    <p class="js-post-body">{{post.body}}</p> + <div class="body js-post-body">{{post.body}}</div>
  17. None
  18. .js-post-title { font-size: 20px; font-weight: bold; }

  19. - .js-post-title { + .js-post-header { font-size: 20px; font-weight: bold;

    } {{! templates/components/blog-post.hbs}} - <h2 class="js-post-title">{{post.title}}</h2> + <h2 class="js-post-headerd">{{post.title}}</h2> <p class="js-post-body">{{post.body}}</p>
  20. {{! templates/components/blog-post.hbs}} <div class="js-post-id_{{post.id}}"> <h2 class="js-post-title">{{post.title}}</h2> <p class="js-post-body">{{post.body}}</p> </div>

  21. None
  22. {{! templates/components/blog-post.hbs}} <h2 data-test-post-title>{{post.title}}</h2> <p data-test-post-body>{{post.body}}</p>

  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, …" ); });
  24. {{! templates/components/blog-post.hbs}} <div data-test-post-id={{post.id}}> <h2 data-test-post-title>{{post.title}}</h2> <p data-test-post-body>{{post.body}}</p> </div>

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

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

  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'), });
  28. data-test-* everywhere

  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'), });
  30. //app/components/blog-post.js import Ember from 'ember'; export default Ember.Component.extend({ post: null

    });
  31. {{! templates/components/blog-post.hbs}} <div data-test-post-id="{{post.id}}"> <h2 data-test-post-title>{{post.title}}</h2> <p data-test-post-body>{{post.body}}</p> </div>

  32. {{! templates/components/blog-post.hbs}} <div> <h2>{{post.title}}</h2> <p>{{post.body}}</p> </div>

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

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

  35. How does it all work?

  36. » ember install ember-test-selectors

  37. Thanks

  38. …there's more

  39. How does it all work internally?

  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'), });
  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); } }); })();
  42. included(app) { this._super.included.apply(this, arguments); … if (!this._stripTestSelectors) { app.import('vendor/ember-test-selectors/patch-component.js'); }

    }
  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'), });
  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') ); } } }
  45. let TEST_SELECTOR_PREFIX = /data-test-.*/; function StripDataTestPropertiesPlugin() { return { visitor:

    { Property(path) { if (TEST_SELECTOR_PREFIX.test(path.node.key.value)) { path.remove(); } }, }, }; }
  46. https://astexplorer.net

  47. None
  48. {{! templates/components/blog-post.hbs}} <div data-test-post-id="{{post.id}}"> <h2 data-test-post-title>{{post.title}}</h2> <p data-test-post-body>{{post.body}}</p> </div>

  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; } }); } … } }
  50. 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; };
  51. {{! templates/components/blog-post.hbs}} <div data-test-post-id="{{post.id}}"> <h2 data-test-post-title>{{post.title}}</h2> <p data-test-post-body>{{post.body}}</p> </div>

  52. {{! templates/components/blog-post.hbs}} <div> <h2>{{post.title}}</h2> <p>{{post.body}}</p> </div>

  53. 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; };
  54. {{blog-post data-test-post}} {{#blog-post data-test-post}} {{/blog-post}}

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

  56. 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; };
  57. {{blog-post data-test-post=post.id}} {{#blog-post data-test-post=post.id}} {{/blog-post}}

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

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

  60. None
  61. Thanks

  62. simplabs.com @simplabs