diff --git a/README.md b/README.md index 6bdf3dd..3108636 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,11 @@ Converts methods in file to ES6 method syntax. Helps you locate all the places where your source may trigger the "Using the same function as getter and setter" deprecation. +#### `ember watson:use-destroy-app-helper ` + +Convert (qunit or mocha flavored) acceptance tests to utilize the `destroyApp` +helper [introduced](https://github.com/ember-cli/ember-cli/pull/4772) in +Ember CLI 1.13.9. ### Specifying a file or path. diff --git a/lib/cli.js b/lib/cli.js index eb3af99..c191d4e 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -65,6 +65,13 @@ program watson.findOverloadedCPs(path).outputSummary(options.json ? 'json' : 'pretty'); }); +program + .command('use-destroy-app-helper [path]') + .description('Use destroy-app helper after acceptance tests.') + .action(function(path) { + path = path || 'tests/acceptance'; + watson.transformTestToUseDestroyApp(path); + }); module.exports = function init(args) { program.parse(args); diff --git a/lib/commands/index.js b/lib/commands/index.js index ca4a19d..e01e9ee 100644 --- a/lib/commands/index.js +++ b/lib/commands/index.js @@ -7,5 +7,6 @@ module.exports = { 'watson:convert-ember-data-async-false-relationships': require('./convert-ember-data-async-false-relationships'), 'watson:methodify': require('./methodify'), 'watson:convert-resource-router-mapping': require('./convert-resource-router-mapping'), - 'watson:find-overloaded-cps': require('./find-overloaded-cps') + 'watson:find-overloaded-cps': require('./find-overloaded-cps'), + 'watson:use-destroy-app-helper': require('./use-destroy-app-helper') }; diff --git a/lib/commands/use-destroy-app-helper.js b/lib/commands/use-destroy-app-helper.js new file mode 100644 index 0000000..ced0128 --- /dev/null +++ b/lib/commands/use-destroy-app-helper.js @@ -0,0 +1,17 @@ +'use strict'; + +var Watson = require('../../index'); +var watson = new Watson(); + +module.exports = { + name: 'watson:use-destroy-app-helper', + description: 'Use destroy-app helper after acceptance tests.', + works: 'insideProject', + anonymousOptions: [ + '' + ], + run: function(commandOptions, rawArgs) { + var path = rawArgs[0] || 'tests/acceptance'; + watson.transformTestToUseDestroyApp(path); + } +}; diff --git a/lib/formulas/destroy-app-transform.js b/lib/formulas/destroy-app-transform.js new file mode 100644 index 0000000..3e329cf --- /dev/null +++ b/lib/formulas/destroy-app-transform.js @@ -0,0 +1,77 @@ +var isImportFor = require('./helpers/is-import-for'); +var parseAst = require('../helpers/parse-ast'); +var recast = require('recast'); +var types = recast.types.namedTypes; +var builders = recast.types.builders; + +var addDefaultImport = require('./helpers/add-default-import'); + +function isEmberCall(node) { + return types.MemberExpression.check(node.callee) && + node.callee.object.name === 'Ember'; +} + +function isLegacyDestroy(node) { + return types.MemberExpression.check(node.callee) && + node.callee.object.name === 'Ember' && + node.callee.property.name === 'run' && + node.arguments[1].value === 'destroy'; +} + +function isStartAppAssignment(node) { + return types.CallExpression.check(node.right) && + node.right.callee.name === 'startApp'; +} + +module.exports = function transform(source) { + var ast = parseAst(source); + var appName = null; + var addDestroyAppImport = false; + var removeEmberImport = true; + var emberImport = null; + + recast.visit(ast, { + visitCallExpression: function(path) { + var node = path.node; + + if(isLegacyDestroy(node)) { + if (addDestroyAppImport !== true) { + addDestroyAppImport = true; + } + + path.replace(builders.callExpression( + builders.identifier('destroyApp'), + [appName] + )); + } else if (isEmberCall(node)) { + removeEmberImport = false; + } + + this.traverse(path); + }, + visitImportDeclaration: function(path) { + if (isImportFor('ember', path.node)) { + emberImport = path; + } + + this.traverse(path); + }, + visitAssignmentExpression: function(path) { + if (isStartAppAssignment(path.node)) { + appName = path.node.left; + } + + this.traverse(path); + } + }); + + if (addDestroyAppImport) { + addDefaultImport(ast, '../helpers/destroy-app', 'destroyApp'); + } + + if (removeEmberImport && emberImport) { + emberImport.prune(); + } + + return recast.print(ast, { tabWidth: 2, quote: 'single' }).code; +}; diff --git a/lib/watson.js b/lib/watson.js index 39a8ca2..6906a8c 100644 --- a/lib/watson.js +++ b/lib/watson.js @@ -1,5 +1,7 @@ -var chalk = require('chalk'); -var fs = require('fs'); +var chalk = require('chalk'); +var fs = require('fs'); +var existsSync = require('exists-sync'); +var EOL = require('os').EOL; var findFiles = require('./helpers/find-files'); @@ -10,6 +12,7 @@ var transformEmberDataAsyncFalseRelationships = require('./formulas/ember-data-a var transformResourceRouterMapping = require('./formulas/resource-router-mapping'); var transformMethodify = require('./formulas/methodify'); var FindOverloadedCPs = require('./formulas/find-overloaded-cps'); +var transformDestroyApp = require('./formulas/destroy-app-transform'); module.exports = EmberWatson; @@ -62,6 +65,28 @@ EmberWatson.prototype.transformResourceRouterMapping = function(routerPath) { transform([routerPath], this._transformResourceRouterMapping); }; +EmberWatson.prototype._transformDestroyApp = transformDestroyApp; + +EmberWatson.prototype.transformTestToUseDestroyApp = function(rootPath) { + if (!existsSync('tests/helpers/destroy-app.js')) { + console.log( + chalk.red('tests/helpers/destroy-app.js file is not present. ' + + 'You must either manually place the file or upgrade to an ' + + 'ember-cli version > 1.13.8.' + EOL + + 'For more info, visit ' + + 'https://gist.github.com/blimmer/35d3efbb64563029505a#create-your-own-destroy-app-helper' + ) + ); + return; + } + + var tests = findFiles(rootPath, '.js').filter(function(file){ + return file.indexOf('-test.js') > 0; + }); + + transform(tests, this._transformDestroyApp); +}; + EmberWatson.prototype.findOverloadedCPs = function(rootPath) { var searcher = new FindOverloadedCPs(); transform(findFiles(rootPath, '.js'), function(source, filename) { diff --git a/package.json b/package.json index 49939ab..7a44972 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "babel-core": "^5.8.22", "chalk": "^1.0.0", "commander": "^2.6.0", + "exists-sync": "0.0.3", "inflected": "^1.1.6", "recast": "^0.10.29", "walk-sync": "^0.1.3" diff --git a/tests/cli-test.js b/tests/cli-test.js index 412ea80..92a22ad 100644 --- a/tests/cli-test.js +++ b/tests/cli-test.js @@ -27,6 +27,10 @@ var commands = { 'convert-resource-router-mapping': { method: 'transformResourceRouterMapping', path: 'app/router.js' + }, + 'use-destroy-app-helper': { + method: 'transformTestToUseDestroyApp', + path: 'tests/acceptance' } }; diff --git a/tests/commands-test.js b/tests/commands-test.js index 81102ba..d983448 100644 --- a/tests/commands-test.js +++ b/tests/commands-test.js @@ -9,6 +9,7 @@ var transformPrototypeExtensionsCalledWith; var transformQUnitTestCalledWith; var transformResourceRouterMappingCalledWith; var transformMethodifyCalledWith; +var transformTestToUseDestroyAppCalledWith; proxyquire('../lib/commands/convert-ember-data-model-lookups', { '../../index': Mock @@ -34,6 +35,10 @@ proxyquire('../lib/commands/methodify', { '../../index': Mock }); +proxyquire('../lib/commands/use-destroy-app-helper', { + '../../index': Mock +}); + function Mock() {} Mock.prototype.transformEmberDataModelLookups = function() { @@ -60,6 +65,10 @@ Mock.prototype.transformMethodify = function () { transformMethodifyCalledWith = Array.prototype.slice.apply(arguments); }; +Mock.prototype.transformTestToUseDestroyApp = function() { + transformTestToUseDestroyAppCalledWith = Array.prototype.slice.apply(arguments); +}; + describe('Commands:', function () { afterEach(function () { @@ -69,6 +78,7 @@ describe('Commands:', function () { transformQUnitTestCalledWith = null; transformResourceRouterMappingCalledWith = null; transformMethodifyCalledWith = null; + transformTestToUseDestroyAppCalledWith = null; }); it('convert-ember-data-model-lookups calls the correct transform', function () { @@ -136,4 +146,14 @@ describe('Commands:', function () { Command.run({}, ['some-app']); assert.deepEqual(transformMethodifyCalledWith, ['some-app']); }); + + it('methodify calls the correct transform', function () { + var Command = require('../lib/commands/use-destroy-app-helper'); + + Command.run({}, []); + assert.deepEqual(transformTestToUseDestroyAppCalledWith, ['tests/acceptance']); + + Command.run({}, ['tests/my-crazy-other-thing']); + assert.deepEqual(transformTestToUseDestroyAppCalledWith, ['tests/my-crazy-other-thing']); + }); }); diff --git a/tests/destroy-app-transformation-test.js b/tests/destroy-app-transformation-test.js new file mode 100644 index 0000000..01ecfed --- /dev/null +++ b/tests/destroy-app-transformation-test.js @@ -0,0 +1,54 @@ +var Watson = require('../index.js'); +var fs = require('fs'); +var astEquality = require('./helpers/ast-equality'); +var recast = require('recast'); + +describe('convert acceptance tests to use destroy-app helper', function() { + it('makes the correct transformations - qunit', function() { + var source = fs.readFileSync('./tests/fixtures/destroy-app-transform/old-default-qunit.js'); + var watson = new Watson(); + var newSource = watson._transformDestroyApp(source); + + astEquality(newSource, fs.readFileSync('./tests/fixtures/destroy-app-transform/new-default-qunit.js')); + }); + + it('makes the correct transformations - mocha', function() { + var source = fs.readFileSync('./tests/fixtures/destroy-app-transform/old-default-mocha.js'); + var watson = new Watson(); + var newSource = watson._transformDestroyApp(source); + + astEquality(newSource, fs.readFileSync('./tests/fixtures/destroy-app-transform/new-default-mocha.js')); + }); + + it('does not remove ember import if otherwise used in test - qunit', function() { + var source = fs.readFileSync('./tests/fixtures/destroy-app-transform/old-with-ember-usage-qunit.js'); + var watson = new Watson(); + var newSource = watson._transformDestroyApp(source); + + astEquality(newSource, fs.readFileSync('./tests/fixtures/destroy-app-transform/new-with-ember-usage-qunit.js')); + }); + + it('does not remove ember import if otherwise used in test - mocha', function() { + var source = fs.readFileSync('./tests/fixtures/destroy-app-transform/old-with-ember-usage-mocha.js'); + var watson = new Watson(); + var newSource = watson._transformDestroyApp(source); + + astEquality(newSource, fs.readFileSync('./tests/fixtures/destroy-app-transform/new-with-ember-usage-mocha.js')); + }); + + it('can handle a non-standard application name - qunit', function() { + var source = fs.readFileSync('./tests/fixtures/destroy-app-transform/old-crazy-app-name-qunit.js'); + var watson = new Watson(); + var newSource = watson._transformDestroyApp(source); + + astEquality(newSource, fs.readFileSync('./tests/fixtures/destroy-app-transform/new-crazy-app-name-qunit.js')); + }); + + it('can handle a non-standard application name - mocha', function() { + var source = fs.readFileSync('./tests/fixtures/destroy-app-transform/old-crazy-app-name-mocha.js'); + var watson = new Watson(); + var newSource = watson._transformDestroyApp(source); + + astEquality(newSource, fs.readFileSync('./tests/fixtures/destroy-app-transform/new-crazy-app-name-mocha.js')); + }); +}); diff --git a/tests/fixtures/destroy-app-transform/new-crazy-app-name-mocha.js b/tests/fixtures/destroy-app-transform/new-crazy-app-name-mocha.js new file mode 100644 index 0000000..d6836c2 --- /dev/null +++ b/tests/fixtures/destroy-app-transform/new-crazy-app-name-mocha.js @@ -0,0 +1,30 @@ +import destroyApp from '../helpers/destroy-app'; +/* jshint expr:true */ +import { + describe, + it, + beforeEach, + afterEach +} from 'mocha'; +import { expect } from 'chai'; +import startApp from '../helpers/start-app'; + +describe('Acceptance: Index', function() { + var cahRayZeeName; + + beforeEach(function() { + cahRayZeeName = startApp(); + }); + + afterEach(function() { + destroyApp(cahRayZeeName); + }); + + it('can visit /index', function() { + visit('/index'); + + andThen(function() { + expect(currentPath()).to.equal('index'); + }); + }); +}); diff --git a/tests/fixtures/destroy-app-transform/new-crazy-app-name-qunit.js b/tests/fixtures/destroy-app-transform/new-crazy-app-name-qunit.js new file mode 100644 index 0000000..c434b65 --- /dev/null +++ b/tests/fixtures/destroy-app-transform/new-crazy-app-name-qunit.js @@ -0,0 +1,21 @@ +import destroyApp from '../helpers/destroy-app'; +import { module, test } from 'qunit'; +import startApp from 'ember-cli-example-app-for-github/tests/helpers/start-app'; + +module('Acceptance | index', { + beforeEach: function() { + this.fooBarBazQux = startApp(); + }, + + afterEach: function() { + destroyApp(this.fooBarBazQux); + } +}); + +test('visiting /index', function(assert) { + visit('/index'); + + andThen(function() { + assert.equal(currentURL(), '/index'); + }); +}); diff --git a/tests/fixtures/destroy-app-transform/new-default-mocha.js b/tests/fixtures/destroy-app-transform/new-default-mocha.js new file mode 100644 index 0000000..44bd9b2 --- /dev/null +++ b/tests/fixtures/destroy-app-transform/new-default-mocha.js @@ -0,0 +1,30 @@ +import destroyApp from '../helpers/destroy-app'; +/* jshint expr:true */ +import { + describe, + it, + beforeEach, + afterEach +} from 'mocha'; +import { expect } from 'chai'; +import startApp from '../helpers/start-app'; + +describe('Acceptance: Index', function() { + var application; + + beforeEach(function() { + application = startApp(); + }); + + afterEach(function() { + destroyApp(application); + }); + + it('can visit /index', function() { + visit('/index'); + + andThen(function() { + expect(currentPath()).to.equal('index'); + }); + }); +}); diff --git a/tests/fixtures/destroy-app-transform/new-default-qunit.js b/tests/fixtures/destroy-app-transform/new-default-qunit.js new file mode 100644 index 0000000..22b16cc --- /dev/null +++ b/tests/fixtures/destroy-app-transform/new-default-qunit.js @@ -0,0 +1,21 @@ +import destroyApp from '../helpers/destroy-app'; +import { module, test } from 'qunit'; +import startApp from 'ember-cli-example-app-for-github/tests/helpers/start-app'; + +module('Acceptance | index', { + beforeEach: function() { + this.application = startApp(); + }, + + afterEach: function() { + destroyApp(this.application); + } +}); + +test('visiting /index', function(assert) { + visit('/index'); + + andThen(function() { + assert.equal(currentURL(), '/index'); + }); +}); diff --git a/tests/fixtures/destroy-app-transform/new-with-ember-usage-mocha.js b/tests/fixtures/destroy-app-transform/new-with-ember-usage-mocha.js new file mode 100644 index 0000000..f9848a6 --- /dev/null +++ b/tests/fixtures/destroy-app-transform/new-with-ember-usage-mocha.js @@ -0,0 +1,34 @@ +import destroyApp from '../helpers/destroy-app'; +/* jshint expr:true */ +import { + describe, + it, + beforeEach, + afterEach +} from 'mocha'; +import { expect } from 'chai'; +import Ember from 'ember'; +import startApp from '../helpers/start-app'; + +describe('Acceptance: Index', function() { + var application; + + beforeEach(function() { + application = startApp(); + }); + + afterEach(function() { + destroyApp(application); + }); + + it('can visit /index', function() { + visit('/index'); + + andThen(function() { + Ember.run(this, function() { + // something + }); + expect(currentPath()).to.equal('index'); + }); + }); +}); diff --git a/tests/fixtures/destroy-app-transform/new-with-ember-usage-qunit.js b/tests/fixtures/destroy-app-transform/new-with-ember-usage-qunit.js new file mode 100644 index 0000000..bce6eb6 --- /dev/null +++ b/tests/fixtures/destroy-app-transform/new-with-ember-usage-qunit.js @@ -0,0 +1,25 @@ +import destroyApp from '../helpers/destroy-app'; +import Ember from 'ember'; +import { module, test } from 'qunit'; +import startApp from 'ember-cli-example-app-for-github/tests/helpers/start-app'; + +module('Acceptance | index', { + beforeEach: function() { + this.application = startApp(); + }, + + afterEach: function() { + destroyApp(this.application); + } +}); + +test('visiting /index', function(assert) { + visit('/index'); + + andThen(function() { + Ember.run(this, function() { + // something + }); + assert.equal(currentURL(), '/index'); + }); +}); diff --git a/tests/fixtures/destroy-app-transform/old-crazy-app-name-mocha.js b/tests/fixtures/destroy-app-transform/old-crazy-app-name-mocha.js new file mode 100644 index 0000000..b4b5225 --- /dev/null +++ b/tests/fixtures/destroy-app-transform/old-crazy-app-name-mocha.js @@ -0,0 +1,30 @@ +/* jshint expr:true */ +import { + describe, + it, + beforeEach, + afterEach +} from 'mocha'; +import { expect } from 'chai'; +import Ember from 'ember'; +import startApp from '../helpers/start-app'; + +describe('Acceptance: Index', function() { + var cahRayZeeName; + + beforeEach(function() { + cahRayZeeName = startApp(); + }); + + afterEach(function() { + Ember.run(cahRayZeeName, 'destroy'); + }); + + it('can visit /index', function() { + visit('/index'); + + andThen(function() { + expect(currentPath()).to.equal('index'); + }); + }); +}); diff --git a/tests/fixtures/destroy-app-transform/old-crazy-app-name-qunit.js b/tests/fixtures/destroy-app-transform/old-crazy-app-name-qunit.js new file mode 100644 index 0000000..0b9689a --- /dev/null +++ b/tests/fixtures/destroy-app-transform/old-crazy-app-name-qunit.js @@ -0,0 +1,21 @@ +import Ember from 'ember'; +import { module, test } from 'qunit'; +import startApp from 'ember-cli-example-app-for-github/tests/helpers/start-app'; + +module('Acceptance | index', { + beforeEach: function() { + this.fooBarBazQux = startApp(); + }, + + afterEach: function() { + Ember.run(this.fooBarBazQux, 'destroy'); + } +}); + +test('visiting /index', function(assert) { + visit('/index'); + + andThen(function() { + assert.equal(currentURL(), '/index'); + }); +}); diff --git a/tests/fixtures/destroy-app-transform/old-default-mocha.js b/tests/fixtures/destroy-app-transform/old-default-mocha.js new file mode 100644 index 0000000..8ca23e6 --- /dev/null +++ b/tests/fixtures/destroy-app-transform/old-default-mocha.js @@ -0,0 +1,30 @@ +/* jshint expr:true */ +import { + describe, + it, + beforeEach, + afterEach +} from 'mocha'; +import { expect } from 'chai'; +import Ember from 'ember'; +import startApp from '../helpers/start-app'; + +describe('Acceptance: Index', function() { + var application; + + beforeEach(function() { + application = startApp(); + }); + + afterEach(function() { + Ember.run(application, 'destroy'); + }); + + it('can visit /index', function() { + visit('/index'); + + andThen(function() { + expect(currentPath()).to.equal('index'); + }); + }); +}); diff --git a/tests/fixtures/destroy-app-transform/old-default-qunit.js b/tests/fixtures/destroy-app-transform/old-default-qunit.js new file mode 100644 index 0000000..13a2ef9 --- /dev/null +++ b/tests/fixtures/destroy-app-transform/old-default-qunit.js @@ -0,0 +1,21 @@ +import Ember from 'ember'; +import { module, test } from 'qunit'; +import startApp from 'ember-cli-example-app-for-github/tests/helpers/start-app'; + +module('Acceptance | index', { + beforeEach: function() { + this.application = startApp(); + }, + + afterEach: function() { + Ember.run(this.application, 'destroy'); + } +}); + +test('visiting /index', function(assert) { + visit('/index'); + + andThen(function() { + assert.equal(currentURL(), '/index'); + }); +}); diff --git a/tests/fixtures/destroy-app-transform/old-with-ember-usage-mocha.js b/tests/fixtures/destroy-app-transform/old-with-ember-usage-mocha.js new file mode 100644 index 0000000..6d8ca1f --- /dev/null +++ b/tests/fixtures/destroy-app-transform/old-with-ember-usage-mocha.js @@ -0,0 +1,33 @@ +/* jshint expr:true */ +import { + describe, + it, + beforeEach, + afterEach +} from 'mocha'; +import { expect } from 'chai'; +import Ember from 'ember'; +import startApp from '../helpers/start-app'; + +describe('Acceptance: Index', function() { + var application; + + beforeEach(function() { + application = startApp(); + }); + + afterEach(function() { + Ember.run(application, 'destroy'); + }); + + it('can visit /index', function() { + visit('/index'); + + andThen(function() { + Ember.run(this, function() { + // something + }); + expect(currentPath()).to.equal('index'); + }); + }); +}); diff --git a/tests/fixtures/destroy-app-transform/old-with-ember-usage-qunit.js b/tests/fixtures/destroy-app-transform/old-with-ember-usage-qunit.js new file mode 100644 index 0000000..54ab987 --- /dev/null +++ b/tests/fixtures/destroy-app-transform/old-with-ember-usage-qunit.js @@ -0,0 +1,24 @@ +import Ember from 'ember'; +import { module, test } from 'qunit'; +import startApp from 'ember-cli-example-app-for-github/tests/helpers/start-app'; + +module('Acceptance | index', { + beforeEach: function() { + this.application = startApp(); + }, + + afterEach: function() { + Ember.run(this.application, 'destroy'); + } +}); + +test('visiting /index', function(assert) { + visit('/index'); + + andThen(function() { + Ember.run(this, function() { + // something + }); + assert.equal(currentURL(), '/index'); + }); +});