Skip to content

Commit 398977a

Browse files
authored
Merge pull request #21 from rwjblue/custom-functions-in-options
Support custom non-lifecycle methods in options.
2 parents 004e03e + 94d2a65 commit 398977a

5 files changed

Lines changed: 134 additions & 6 deletions

File tree

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { moduleFor, test } from 'ember-qunit';
2+
3+
moduleFor('stuff:here', {
4+
customFunction() {
5+
return stuff();
6+
}
7+
});
8+
9+
test('users customFunction', function(assert) {
10+
let custom = this.customFunction();
11+
});
12+
13+
moduleFor('stuff:here', {
14+
customFunction() {
15+
return stuff();
16+
},
17+
18+
otherThing(basedOn) {
19+
return this.blah(basedOn);
20+
}
21+
});
22+
23+
test('can have two', function(assert) {
24+
let custom = this.customFunction();
25+
let other = this.otherThing();
26+
});
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { module, test } from 'qunit';
2+
import { setupTest } from 'ember-qunit';
3+
4+
module('stuff:here', function(hooks) {
5+
setupTest(hooks);
6+
7+
hooks.beforeEach(function() {
8+
this.customFunction = function() {
9+
return stuff();
10+
};
11+
});
12+
13+
test('users customFunction', function(assert) {
14+
let custom = this.customFunction();
15+
});
16+
});
17+
18+
module('stuff:here', function(hooks) {
19+
setupTest(hooks);
20+
21+
hooks.beforeEach(function() {
22+
this.customFunction = function() {
23+
return stuff();
24+
};
25+
26+
this.otherThing = function(basedOn) {
27+
return this.blah(basedOn);
28+
};
29+
});
30+
31+
test('can have two', function(assert) {
32+
let custom = this.customFunction();
33+
let other = this.otherThing();
34+
});
35+
});

__testfixtures__/ember-qunit-codemod/subject.input.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,13 @@ test('has some thing', function (assert) {
5555
test('has another thing', function (assert) {
5656
let subject = this.subject({ size: 'big' });
5757
});
58+
59+
moduleFor('service:foo', {
60+
subject() {
61+
return derp();
62+
}
63+
});
64+
65+
test('can use custom subject', function(assert) {
66+
let subject = this.subject();
67+
});

__testfixtures__/ember-qunit-codemod/subject.output.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,17 @@ module('Unit | Component | FooBar', function(hooks) {
5656
let subject = this.owner.factoryFor('component:foo-bar').create({ size: 'big' });
5757
});
5858
});
59+
60+
module('service:foo', function(hooks) {
61+
setupTest(hooks);
62+
63+
hooks.beforeEach(function() {
64+
this.subject = function() {
65+
return derp();
66+
};
67+
});
68+
69+
test('can use custom subject', function(assert) {
70+
let subject = this.subject();
71+
});
72+
});

ember-qunit-codemod.js

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ module.exports = function(file, api, options) {
104104
function parseModule(p) {
105105
let calleeName = p.node.expression.callee.name;
106106
// Find the moduleName and the module's options
107-
let moduleName, subject, options;
107+
let moduleName, subject, options, hasCustomSubject;
108108
let calleeArguments = p.node.expression.arguments.slice();
109109
let lastArgument = calleeArguments[calleeArguments.length - 1];
110110
if (lastArgument.type === 'ObjectExpression') {
@@ -127,9 +127,11 @@ module.exports = function(file, api, options) {
127127
} else if (!hasIntegration) {
128128
subject = calleeArguments[0];
129129
}
130+
131+
hasCustomSubject = options.properties.some(p => p.key.name === 'subject');
130132
}
131133

132-
return [moduleName, options, setupIdentifier, subject];
134+
return [moduleName, options, setupIdentifier, subject, hasCustomSubject];
133135
}
134136

135137
function updateModuleForToNestedModule() {
@@ -143,6 +145,10 @@ module.exports = function(file, api, options) {
143145
return POSSIBLE_MODULES.some(matcher => j.match(nodePath, matcher));
144146
}
145147

148+
function isMethod(nodePath) {
149+
return j.match(nodePath, { value: { type: 'FunctionExpression' } });
150+
}
151+
146152
const LIFE_CYCLE_METHODS = [
147153
{ key: { name: 'before' }, value: { type: 'FunctionExpression' } },
148154
{ key: { name: 'beforeEach' }, value: { type: 'FunctionExpression' } },
@@ -155,7 +161,7 @@ module.exports = function(file, api, options) {
155161
}
156162

157163
function createModule(p) {
158-
let [moduleName, options, setupType, subject] = parseModule(p);
164+
let [moduleName, options, setupType, subject, hasCustomSubject] = parseModule(p);
159165

160166
// Create the new `module(moduleName, function(hooks) {});` invocation
161167
let callback = j.functionExpression(
@@ -170,6 +176,8 @@ module.exports = function(file, api, options) {
170176
);
171177

172178
if (options) {
179+
let customMethodBeforeEachBody;
180+
173181
options.properties.forEach(property => {
174182
if (isLifecycleHook(property)) {
175183
let lifecycleStatement = j.expressionStatement(
@@ -182,11 +190,45 @@ module.exports = function(file, api, options) {
182190
lifecycleStatement.comments = property.comments;
183191

184192
callback.body.body.push(lifecycleStatement);
193+
} else if (isMethod(property)) {
194+
if (!customMethodBeforeEachBody) {
195+
customMethodBeforeEachBody = j.blockStatement([]);
196+
197+
let beforeEachInvocation = j.expressionStatement(
198+
j.callExpression(
199+
j.memberExpression(j.identifier('hooks'), j.identifier('beforeEach')),
200+
[
201+
j.functionExpression(
202+
null,
203+
[
204+
/* no arguments */
205+
],
206+
customMethodBeforeEachBody
207+
),
208+
]
209+
)
210+
);
211+
212+
callback.body.body.push(beforeEachInvocation);
213+
}
214+
215+
let methodAssignment = j.expressionStatement(
216+
j.assignmentExpression(
217+
'=',
218+
j.memberExpression(j.thisExpression(), property.key),
219+
property.value
220+
)
221+
);
222+
223+
// preserve any comments that were present
224+
methodAssignment.comments = property.comments;
225+
226+
customMethodBeforeEachBody.body.push(methodAssignment);
185227
}
186228
});
187229
}
188230

189-
return [moduleInvocation, callback.body.body, setupType, subject];
231+
return [moduleInvocation, callback.body.body, setupType, subject, hasCustomSubject];
190232
}
191233

192234
function processRenderingTest(testExpression) {
@@ -301,7 +343,7 @@ module.exports = function(file, api, options) {
301343
let bodyPath = programPath.get('body');
302344

303345
let bodyReplacement = [];
304-
let currentModuleCallbackBody, currentTestType, currentSubject;
346+
let currentModuleCallbackBody, currentTestType, currentSubject, currentHasCustomSubject;
305347
bodyPath.each(expressionPath => {
306348
let expression = expressionPath.node;
307349
if (isModuleDefinition(expressionPath)) {
@@ -310,12 +352,13 @@ module.exports = function(file, api, options) {
310352
currentModuleCallbackBody = result[1];
311353
currentTestType = result[2];
312354
currentSubject = result[3];
355+
currentHasCustomSubject = result[4];
313356
} else if (currentModuleCallbackBody) {
314357
currentModuleCallbackBody.push(expression);
315358

316359
if (currentTestType === 'setupRenderingTest') {
317360
processRenderingTest(expression);
318-
} else if (currentTestType === 'setupTest') {
361+
} else if (currentTestType === 'setupTest' && !currentHasCustomSubject) {
319362
processSubject(expression, currentSubject);
320363
}
321364
} else {

0 commit comments

Comments
 (0)