with Ember & Pretender
Michael Lange
@DingoEatingFuzz
Assert that our code does what we think it does
Serve as a living "spec" of a library
(https://youtu.be/zwoiwoVNaCQ Jacob Thornton)
Automate some QA
don't believe this either
Application
Tests
Infrastructure
75%
15%
10%
For realsies
Meta
75%
15%
10%
Concerns the user
No one even knows this exists
75%
15%
10%
*the Lytics product team
*the Lytics product team
Concept = Data Model
Feature = Services
Design = Controllers & Components
describe("when authenticated but unauthorized", function() {
beforeEach(invoke(setAccountContext, 'account2'));
beforeEach(invoke('authedVisit', discoveryURL));
it("should redirect to segments overview", function() {
expectAsync(currentURL).to.equal(indexURL);
});
});
ember-cli
tests invoker
testem
test runner
mocha
testing framework
ember-mocha-adapter
testing adapter
ember-cli-mocha
test helpers
chai
BDD assertion library
describe("when displaying the field selection dialog", function() {
beforeEach(invoke('click', '.action-select', '.segment-field-name'));
beforeEach(invoke('setFindContext', '#modal'));
afterEach(invoke('closeModal'));
it("should change the current field name when a field is selected and close the dialog", function() {
var newField = 'email';
var newSegment = buildInstance('segment', buildObject(segment));
var newColumn = fixtures.schema.columns.findBy('as', newField);
newSegment.children[0].val = newField;
patchApi('post /api/segment/' + segment.id, ok(buildResponse('segment', newSegment)));
click('.action-select', '.list.schema-field .item.schema-field:contains("' + newColumn.shortdesc + '")');
expectAsync(findWithoutContext, [ '#modal' ]).to.not.exist;
andThen(invoke('setFindContext', '#content'));
expectAsync(text, [ '.segment-field-name' ]).to.contain(newColumn.shortdesc);
expectAsync(find, [ '.action-save' ]).to.be.disabled;
click('.action-select', '.segment-value');
andThen(invoke('setFindContext', '#modal'));
fillIn('input[type="text"]', '.segment-value', 'the@dude.com');
click('.action-select');
andThen(invoke('setFindContext', '#content'));
expectAsync(find, [ '.action-save' ]).to.not.be.disabled;
click('.action-save');
expectAsync(requestBodiesForRoute, [ 'post /api/segment/' + segment.id ]).to.matchJSON({
'$.children[0].val' : newField
});
});
filteredSchemaFields(fixtures.schema, fixtures.fieldInfo);
});
export default function segmentEditPage() {
describe("when unauthenticated", function() {
var segment = fixtures.segments.findBy('id', 'segment1');
loginThenRedirect(editURL(segment));
});
describe("when authenticated and authorized", function() {
describe("when editing any segment", function() {
var segment = fixtures.segments.findBy('id', 'segment1');
beforeEach(invoke('authedVisit', editURL(segment)));
segmentsSubnav();
it("should display a button for updating that is disabled when there are no changes", function() {
expect(find('.action-save')).to.be.disabled;
Ember.run(currentModel(), 'set', 'name', 'new name');
expect(find('.action-save')).to.not.be.disabled;
});
describe("when clicking the update button", function() {
var newName = 'Mr. Chartreuse';
beforeEach(function() {
// Need to dirty the model before saving
Ember.run(currentModel(), 'set', 'name', newName);
});
it("should save the segment and display a success message", function() {
var newSegment = buildInstance('segment', buildObject(segment, { name: newName }));
patchApi('post /api/segment/' + segment.id, ok(buildResponse('segment', newSegment)));
click('.action-save');
expectAsync(currentURL).to.equal(summaryURL(segment));
expectAsync(findWithoutContext, [ '#alert' ]).to.have.class('alert-success');
});
it("should display an error message when updating failed", function() {
patchApi('post /api/segment/' + segment.id, bad());
click('.action-save');
expectAsync(currentURL).to.equal(editURL(segment));
expectAsync(findWithoutContext, [ '#alert' ]).to.have.class('alert-error');
});
});
discardConfirmDialog(function() {
Ember.run(currentModel(), 'set', 'name', 'new name');
});
});
describe("when editing an inverted rule segment", function() {
var segment = fixtures.segments.findBy('id', 'segment6');
beforeEach(invoke('authedVisit', editURL(segment)));
it("should show the true size in the heading and the inverted size in the rule definition", function() {
var segmentTotalSize = fixtures.totalSize;
var size = sizeForSegmentId(segment.id);
var negatedSize = segmentTotalSize - size;
expect(text('.invert')).to.include('excluded');
expect(noCommaText('.title-size')).to.contain(size);
expect(noCommaText('.segment-size')).to.contain(negatedSize);
});
});
describe("when editing a rule segment", function() {
describe("when the field is a any type", function() {
var segment = fixtures.segments.findBy('id', 'segment1');
beforeEach(invoke('authedVisit', editURL(segment)));
var fullField = segment.children.findBy('type', 'identifier').val;
var fieldName = fullField.split(".")[0];
var column = fixtures.schema.columns.findBy('as', fieldName);
it("should display the currently selected field's short description", function() {
expect(text('.segment-field-name')).to.contain(column.shortdesc);
});
it("should display a prepopulated select box for changing the operator", function() {
var newOperator = '=';
patchApi('post /api/segment/' + segment.id, ok(buildResponse('segment', buildObject(segment, { op: newOperator }))));
expect(text('.select-input', '.segment-operator')).to.equal('be greater than');
select('.select-input', operatorLabels[newOperator]);
expectAsync(text, [ '.select-input', '.segment-operator' ]).to.equal(operatorLabels[newOperator]);
expectAsync(find, [ '.action-save' ]).to.not.be.disabled;
click('.action-save');
expectAsync(currentURL).to.equal(summaryURL(segment));
expectAsync(requestBodiesForRoute, [ 'post /api/segment/' + segment.id ]).to.matchJSON({
'$.op' : newOperator
});
});
it("should display a button for selecting the field name that opens a dialog", function() {
click('.action-select', '.segment-field-name');
expectAsync(findWithoutContext, [ '#modal' ]).to.exist;
closeModal();
});
describe("when displaying the field selection dialog", function() {
beforeEach(invoke('click', '.action-select', '.segment-field-name'));
beforeEach(invoke('setFindContext', '#modal'));
afterEach(invoke('closeModal'));
it("should change the current field name when a field is selected and close the dialog", function() {
var newField = 'email';
var newSegment = buildInstance('segment', buildObject(segment));
var newColumn = fixtures.schema.columns.findBy('as', newField);
newSegment.children[0].val = newField;
patchApi('post /api/segment/' + segment.id, ok(buildResponse('segment', newSegment)));
click('.action-select', '.list.schema-field .item.schema-field:contains("' + newColumn.shortdesc + '")');
expectAsync(findWithoutContext, [ '#modal' ]).to.not.exist;
andThen(invoke('setFindContext', '#content'));
expectAsync(text, [ '.segment-field-name' ]).to.contain(newColumn.shortdesc);
expectAsync(find, [ '.action-save' ]).to.be.disabled;
click('.action-select', '.segment-value');
andThen(invoke('setFindContext', '#modal'));
fillIn('input[type="text"]', '.segment-value', 'the@dude.com');
click('.action-select');
andThen(invoke('setFindContext', '#content'));
expectAsync(find, [ '.action-save' ]).to.not.be.disabled;
click('.action-save');
expectAsync(requestBodiesForRoute, [ 'post /api/segment/' + segment.id ]).to.matchJSON({
'$.children[0].val' : newField
});
});
filteredSchemaFields(fixtures.schema, fixtures.fieldInfo);
});
it("should display a button that adds a new child segment when clicked");
it("should display a button to add an existing child segment that opens a dialog when clicked");
});
describe("when the field is a non-map type", function() {
var segment = fixtures.segments.findBy('id', 'segment1');
beforeEach(invoke('authedVisit', editURL(segment)));
it("should display the currently selected value");
it("should display a button for selecting the field value that opens a dialog", function() {
click('.action-select', '.segment-value');
expectAsync(findWithoutContext, [ '#modal' ]).to.exist;
closeModal();
});
describe("when displaying the value select dialog", function() {
beforeEach(invoke('click', '.action-select', '.segment-value'));
beforeEach(invoke('setFindContext', '#modal'));
it("should display a visualization of the field that changes the value when clicked");
it("should display the field name, short name and short description of the field");
it("should display the percentage of entities with values for the field");
it("should display a text field for changing the value and a button that selects the value and closes the dialog", function() {
var value = segment.children[1].val;
var newValue = 6;
expect(find('input[type="text"]', '.segment-value')).to.have.value(value);
fillIn('input[type="text"]', '.segment-value', newValue);
click('.action-select');
andThen(invoke('setFindContext', '#content'));
expectAsync(text, [ '.segment-value' ]).to.contain(newValue);
});
});
});
describe("when the field is a date type", function() {
var segment = fixtures.segments.findBy('id', 'segment6');
beforeEach(invoke('authedVisit', editURL(segment)));
var dateValue = segment.children[1].val;
var formatDateValue = function(value, format) {
return moment(+value).format(format);
};
it("should display the currently selected value formatted as a local date", function() {
expect(text('.segment-value')).to.contain(formatDateValue(dateValue, 'ddd MMM D, YYYY'));
});
it("should have labels that use time-based language", function() {
expect(text('.segment-operator')).to.contain(dateOperatorLabels[segment.op]);
});
describe("when displaying the value select dialog", function() {
beforeEach(invoke('click', '.action-select', '.segment-value'));
beforeEach(invoke('setFindContext', '#modal'));
it("should display a visualization of the field that changes the value when clicked");
it("should display a date picker input for changing the value", function() {
var newDate = new Date(2014, 10, 2);
var newDateValue = '' + (+newDate);
var newSegment = buildInstance('segment', buildObject(segment));
newSegment.children[1].val = newDateValue;
expect(find('.datepicker-input input')).to.have.value(formatDateValue(dateValue, 'M/DD/YYYY'));
fillIn('.datepicker-input input', formatDateValue(newDate, 'M/DD/YYYY'));
keyEvent('.datepicker-input input', 'keyup', 16); // Kludge to make the datepicker notice the change
click('.action-select');
andThen(invoke('setFindContext', '#content'));
expectAsync(text, [ '.segment-value' ]).to.contain(formatDateValue(newDate, 'ddd MMM D, YYYY'));
});
it("should have a relative date option", function() {
expect(find('input[name="relativevalue"]')).to.be.disabled;
expect(find('.select-input')).to.have.class('disabled');
click('.action-toggle.option-relative');
expectAsync(find, [ 'input[name="relativevalue"]' ]).to.not.be.disabled;
expectAsync(find, [ '.select-input' ]).to.not.have.class('disabled');
expectAsync(find, [ '.datepicker-input input' ]).to.be.disabled;
});
describe("when clicking the relative date button", function() {
beforeEach(invoke('click', '.action-toggle.option-relative'));
it("should display an input and select for inputting an integer number of date units", function() {
var newValue = 3;
var newUnits = 'M';
var dateExpression = 'now-' + newValue + newUnits;
var newSegment = buildInstance('segment', buildObject(segment));
newSegment.children[1].val = dateExpression;
patchApi('post /api/segment/' + segment.id, ok(buildResponse('segment', newSegment)));
fillIn('input[name="relativevalue"]', newValue);
select('.select-input', relativeDateLabels[newUnits]);
click('.action-select');
andThen(invoke('setFindContext', '#content'));
expectAsync(text, [ '.segment-value' ]).to.contain(relativeDateLabels[newUnits].toLowerCase());
click('.action-save');
expectAsync(currentURL).to.equal(summaryURL(segment));
expectAsync(requestBodiesForRoute, [ 'post /api/segment/' + segment.id ]).to.matchJSON({
'$.children[1].val' : dateExpression
});
});
});
});
});
describe("when the field is a set type", function() {
var segment = fixtures.segments.findBy('id', 'segment10');
beforeEach(invoke('authedVisit', editURL(segment)));
it("should display the all currently selected values", function() {
expect(text('.select-input .selection')).to.contain('foo@bar.com');
expect(text('.select-input .selection')).to.contain('baz@qux.com');
});
it("should display a button for selecting the field value that opens a dialog", function() {
click('.action-select');
expectAsync(findWithoutContext, [ '#modal' ]).to.be.visible;
});
describe("when displaying the value select dialog", function() {
beforeEach(invoke('click', '.action-select', '.segment-value'));
beforeEach(invoke('setFindContext', '#modal'));
it("should display a visualization of the field that adds a value when clicked");
it("should display a multi text field for adding values", function() {
var newValue = 'bob.loblaw@lawblog.com';
expect(find('.multi-text-input')).to.exist;
fillIn('.multi-text-input input[type=text]', newValue);
click('.multi-text-input .action-add');
click('.action-select');
andThen(invoke('setFindContext', '#content'));
expectAsync(text, [ '.segment-value' ]).to.contain(newValue);
click('.action-save');
expectAsync(currentURL).to.equal(summaryURL(segment));
expectAsync(requestBodiesForRoute, [ 'post /api/segment/' + segment.id ]).to.matchJSON({
'$.children[3].val' : newValue
});
});
it("should use fieldsuggest to suggest values", function() {
var suggestions = {
data: {
terms_counts: {
'from@api.com': 1
},
more_terms: false
}
};
patchApi('get /api/schema/user/fieldsuggest/' + segment.children[0].val, ok(suggestions));
fillIn('.multi-text-input input', 'fr');
triggerEvent('.multi-text-input input', 'keyup'); // this opens the options menu
expectAsync(find, [ '.multi-text-input .options li' ]).to.have.lengthOf(1);
expectAsync(text, [ '.multi-text-input .options li' ]).to.contain('from@api.com');
});
});
});
describe("when the field is a map type", function() {
var segment = fixtures.segments.findBy('id', 'segment2');
beforeEach(invoke('authedVisit', editURL(segment)));
var fieldParts = segment.children.findBy('type', 'identifier').val.split('.');
var fieldValue = fieldParts.shift();
var fieldKey = fieldParts.join('.');
it("should display a prepopulated select box for changing the field key", function() {
var newFieldKey = 'providers.index';
var newSegment = buildObject('segment', segment);
newSegment.children[0].val = newFieldKey;
patchApi('post /api/segment/' + segment.id, ok(buildResponse('segment', newSegment)));
expect(text('.segment-field-key')).to.contain(fieldKey);
select('.select-input', '.segment-field-key', newFieldKey);
expectAsync(text, [ '.segment-field-key' ]).to.contain(newFieldKey);
expectAsync(find, [ '.action-save' ]).to.not.be.disabled;
click('.action-save');
expectAsync(requestBodiesForRoute, [ 'post /api/segment/' + segment.id ]).to.matchJSON({
'$.children[0].val': fieldValue + '.' + newFieldKey
});
});
it("should display a text field for changing the value");
});
});
describe("when transforming a composite segment back to a rule segment", function() {
describe("using an existing segment", function() {
var segment = fixtures.segments.findBy('id', 'segment8');
beforeEach(invoke('authedVisit', editURL(segment)));
beforeEach(function() {
click('.action-remove:eq(1)', '.composite.segment.item');
});
it("should persist the operator label", function() {
var child = fixtures.segments.findBy('id', segment.children[0].val);
var operatorLabel = dateOperatorLabels[child.op];
expect(text('.segment-operator')).to.equal(operatorLabel);
});
describe("when clicking the save button", function() {
it("should redirect to the summary page when saved", function() {
var newSegment = buildInstance('segment', buildObject(segment));
var child = fixtures.segments.findBy('id', segment.children[0].val);
newSegment.op = child.op;
newSegment.children = Ember.copy(child.children, true);
patchApi('post /api/segment/' + segment.id, ok(buildResponse('segment', newSegment)));
click('.action-save');
expectAsync(requestBodiesForRoute, [ 'post /api/segment/' + segment.id ]).to.matchJSON({
'$.op': newSegment.op,
'$.children[0].type': newSegment.children[0].type,
'$.children[0].val': newSegment.children[0].val
});
expectAsync(currentURL).to.equal(summaryURL(segment));
});
});
});
});
describe("when editing a composite segment", function() {
var segment = fixtures.segments.findBy('id', 'segment3');
beforeEach(invoke('authedVisit', editURL(segment)));
it("should display a prepopulated toggle for changing the operator", function() {
expect(text('.action-toggle.segment-operator:first', '.composite.segment.item')).to.equal('And');
click('.action-toggle.segment-operator:first', '.composite.segment.item');
expectAsync(text, [ '.action-toggle.segment-operator:first', '.composite.segment.item' ]).to.equal('Or');
expectAsync(find, [ '.action-save' ]).to.not.be.disabled;
});
it("should display a list of child segments", function() {
expect(find('.composite.segment.item .segment.list')).to.exist;
expect(find('.segment.item', '.composite.segment.item .segment.list')).to.have.lengthOf(segment.children.length);
});
it("should display a button that adds a new child segment when clicked", function() {
click('.action-add-new', '.composite.segment.item');
expectAsync(find, [ '.segment.item', '.composite.segment.item .segment.list' ]).to.have.lengthOf(segment.children.length + 1);
});
it("should display a button to add an existing child segment that opens a dialog when clicked", function() {
click('.action-add-existing', '.composite.segment.item .segment-add');
expectAsync(findWithoutContext, [ '#modal' ]).to.exist;
closeModal();
});
it("should update the top-level size when toggling the top-level operator", function() {
var newTopLevelSize = 5000;
patchApi(/get \/api\/segment\/size\?ids=.*/, ok(buildResponse('segmentsize', [ newTopLevelSize ])));
click('.item.toggle:eq(0) .toggle-control', '.segment.item');
expectAsync(noCommaText, [ '.title-size' ]).to.contain(newTopLevelSize);
});
describe("when displaying the add existing segment dialog", function() {
beforeEach(invoke('click', '.action-add-existing', '.composite.segment.item .segment-add'));
beforeEach(invoke('setFindContext', '#modal'));
var childIds = segment.children.mapBy('val');
var availableSegments = fixtures.segments.filter(function(s) {
return s.id !== segment.id && s.name && !childIds.contains(s.id);
}).sortBy('name');
it("should display an alphabetized list of available segments to add", function() {
expect(find('.segment.list')).to.exist;
expect(find('.segment.item', '.segment.list')).to.have.lengthOf(availableSegments.length);
expect(text('.segment.list')).to.not.contain(segment.name);
expect(text('.segment.list li:eq(0)')).to.contain(availableSegments[0].name);
closeModal();
});
it("should allow for searching", function() {
var mrSegments = availableSegments.filter(function(segment) {
return segment.name.indexOf('Mr') !== -1;
});
fillIn('.filter.search input', 'Mr');
expectAsync(find, [ '.segment.item', '.segment.list' ]).to.have.lengthOf(mrSegments.length);
});
it("should display a button that adds the available segment to its children when clicked", function() {
var chosenSegment = fixtures.segments.findBy('id', 'segment2');
click('.action-add-existing:contains("' + chosenSegment.name + '")');
andThen(invoke('setFindContext', '#content'));
expectAsync(find, [ '.segment.item', '.composite.segment.item .segment.list' ]).to.have.lengthOf(segment.children.length + 1);
expectAsync(text, [ '.segment.item', '.composite.segment.item .segment.list' ]).to.contain(chosenSegment.name);
});
});
[ 'anonymous', 'named' ].forEach(function(type) {
describe("each " + type + " child segment", function() {
var segmentTotalSize = fixtures.totalSize;
var segmentChild = segment.children.find(function(child) {
var hasName = fixtures.segments.findBy('id', child.val).name;
return hasName && type === 'named' || !hasName && type === 'anonymous';
});
var childSegmentIndex = segment.children.indexOf(segmentChild);
var childSegment = fixtures.segments.findBy('id', segmentChild.val);
var childSegmentSize = sizeForSegmentId(childSegment.id);
it("should display the total number of entities in the segment", function() {
pushFindContext('.segment.list .segment.item:eq(' + childSegmentIndex + ')');
expect(noCommaText('.segment-size')).to.contain(childSegmentSize);
});
// Inverting anonymous and named segments is very different in the API
if (type === 'anonymous') {
it("should display a button that inverts the segment when clicked", function() {
var newTopLevelSize = 5000;
var newSize = 126;
patchApi('post /api/segment/' + childSegment.id, ok(buildResponse('segment', buildObject(childSegment, { negate: true }))));
patchApi(/get \/api\/segment\/size\?ids=\d+%2C\d+&.*/, ok(buildResponse('segmentsize', [ newTopLevelSize , newSize ])));
patchApi(/get \/api\/segment\/size\?ids=\d+&.*/, ok(buildResponse('segmentsize', [ newSize ])));
pushFindContext('.segment.list .segment.item:eq(' + childSegmentIndex + ')');
click('.action-toggle', '.invert');
expectAsync(text, [ '.invert' ]).to.contain('excluded');
expectAsync(noCommaText, [ '.segment-size' ]).to.contain(segmentTotalSize - newSize);
andThen(invoke('setFindContext', '#content'));
expectAsync(noCommaText, [ '.title-size' ]).to.contain(newTopLevelSize);
click('.action-save');
expectAsync(requestBodiesForRoute, [ 'post /api/segment/' + childSegment.id ]).to.matchJSON({
'$.negate': true
});
});
} else {
it("should display a button that inverts the segment when clicked", function() {
var newTopLevelSize = 5000;
var newSize = 126;
var newTopLevelSegment = buildObject(segment);
var newSegment = {
id: 'segment99',
name: '',
op: 'not',
children: [
{
type: 'segment',
val: childSegment.id
}
]
};
newTopLevelSegment.children[childSegmentIndex].val = newSegment.id;
var segmentUpdateJSONMatch = {};
segmentUpdateJSONMatch['$.children[' + childSegmentIndex + '].val'] = newSegment.id;
// patchApi('post /api/segment', ok(buildResponse('segment', newSegment)));
// patchApi('post /api/segment/' + segment.id, ok(buildResponse('segment', newTopLevelSegment)));
patchApi(/get \/api\/segment\/size\?ids=\d+%2C\d+&.*/, ok(buildResponse('segmentsize', [ newTopLevelSize , newSize ])));
patchApi(/get \/api\/segment\/size\?ids=\d+&.*/, ok(buildResponse('segmentsize', [ newSize ])));
pushFindContext('.segment.list .segment.item:eq(' + childSegmentIndex + ')');
click('.action-toggle', '.invert');
expectAsync(text, [ '.invert' ]).to.contain('excluded');
expectAsync(noCommaText, [ '.segment-size' ]).to.contain(segmentTotalSize - newSize);
andThen(invoke('setFindContext', '#content'));
expectAsync(noCommaText, [ '.title-size' ]).to.contain(newTopLevelSize);
// TODO dependency: this surfaces a bug in ember-data
// see:
// click('.action-save');
// expectAsync(requestBodiesForRoute, [ 'post /api/segment' ]).to.matchJSON({
// '$.op': 'not'
// });
// expectAsync(requestBodiesForRoute, [ 'post /api/segment/' + segment.id ]).to.matchJSON(segmentUpdateJSONMatch);
});
}
describe("when clicking the remove button", function() {
it("should convert the segment into a rule segment if the only remaining child is a rule segment");
});
});
});
});
});
}
var dashboardRespond = function() {
responses.suggestions();
responses.suggestionActions();
topActions.forEach(function(action) {
contentRespond(action);
});
topSuggestions.forEach(function(suggestion) {
contentRespond(suggestion);
});
topSegments.forEach(function(segment, index) {
responses.singleSize(segment, segmentSizes[index]);
});
};
this.get('/api/segment', allOrOne('segments'));
this.get('/api/segment/attribution', some('segmenttrends'));
this.get('/api/segment/:id/attribution', one('segmenttrends'));
this.get('/api/segment/:segment_id/scan', segmentScan());
this.get('/api/segment/:source_id/lookalike/:target_id', segmentLookalike());
this.get('/api/segment/size', size());
this.get('/api/segment/sizes', sizes());
this.get('/api/segment/:id/size', singleSize());
export function allOrOne(key, param) {
param || (param = 'id');
return patchableResponder(function(req) {
var id = req.params[param] || req.queryParams[param];
var type = key.toLowerCase().singularize();
if (id) {
return ok(buildResponse(type, fixtures[key].findBy('id', id)));
}
return ok(buildResponse(type, fixtures[key]));
});
}
export function patchableResponder(fn) {
return function(req) {
var patch = patchForRequest(req);
if (patch) { return patch; }
return fn(req);
};
}
register: function register(verb, path, handler, async){
if (!handler) {
throw new Error("The function you tried passing to Pretender to handle " + verb + " " + path + " is undefined or missing.");
}
handler.numberOfCalls = 0;
handler.async = async;
this.handlers.push(handler);
var registry = this.registry[verb];
registry.add([{path: path, handler: handler}]);
},
click('.action-save');
expectAsync(requestBodiesForRoute, [ 'post /api/segment/' + segment.id ]).to.matchJSON({
'$.children[0].val' : newField
});
Sam Selikoff
—James Hague (http://prog21.dadgum.com/177.html)
describe("when unauthenticated", function() {
loginThenRedirect(reportURL(/* some, args */));
});
describe("when authenticated but unauthorized", function() {
beforeEach(invoke(setAccountContext, 'account2'));
// Tests go here
});
describe("when authenticated and authorized", function() {
beforeEach(invoke('authedVisit', reportURL(/* some, args */)));
// Tests go here
});
describe("when clicking a transactional button", function() {
it("should do good things when good things happen");
it("should do bad things when bad things happen");
});
describe("when an account has only install works", function() {
beforeEach(function() {
patchApi('get /api/work', ok(buildResponse('work', [{
workflow_id: fixtures.workflows.findBy('verb', 'installation').id
}])));
});
it("should show the providers page content as well as a message for importing email data");
});
describe("when an account does not have enough users for predefiend segment features", function() {
beforeEach(function() {
patchApi(/get \/api\/segment\/size$/, ok(buildResponse('segmentsize', [ predefinedSegmentsUserThreshold - 1 ])));
});
it("should show an onboarding message on the integrations page");
it("should show an onboarding message in place of the segment flow diagram");
});
describe("when an account has no works but has users", function() {
beforeEach(function() {
patchApi(/get \/api\/segment\/size$/, ok(buildResponse('segmentsize', [ 1000000 ])));
patchApi('get /api/work', ok(buildResponse('work', [
{
workflow_id: fixtures.workflows.findBy('verb', 'import').id
}, {
workflow_id: fixtures.workflows.findBy('verb', 'installation').id
}
])));
});
describe("the providers page", function() {
beforeEach(invoke('authedVisit', providerIndexURL));
it("should not show onboarding content");
});
});
patches and Pretender don't have the same interface :sadtaco:
click('.action-save');
expectAsync(requestBodiesForRoute, [ 'post /api/segment/' + segment.id ]).to.matchJSON({
'$.children[0].val' : newField
});
Chai matchJSON
JavaScript, Go, Sales