Skip to content

Commit

Permalink
Add create and update functionality to the shop client
Browse files Browse the repository at this point in the history
  • Loading branch information
Mina Smart committed Feb 25, 2016
1 parent 43b1aa9 commit 9fe1c6c
Show file tree
Hide file tree
Showing 5 changed files with 308 additions and 0 deletions.
9 changes: 9 additions & 0 deletions lib/models/checkout-model.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import BaseModel from 'buy-button-sdk/models/base-model';

const CartModel = BaseModel.extend({
constructor() {
this.super(...arguments);
}
});

export default CartModel;
39 changes: 39 additions & 0 deletions lib/serializers/checkout-serializer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import CoreObject from 'buy-button-sdk/metal/core-object';
import CheckoutModel from 'buy-button-sdk/models/checkout-model';

const CheckoutSerializer = CoreObject.extend({
constructor() {
},

rootKeyForType(type) {
return type.slice(0, -1);
},

modelForType(/* type */) {
return CheckoutModel;
},

deserializeSingle(type, singlePayload, metaAttrs) {
const modelAttrs = singlePayload[this.rootKeyForType(type)];
const model = this.modelFromAttrs(type, modelAttrs, metaAttrs);

return model;
},

modelFromAttrs(type, attrs, metaAttrs) {
const Model = this.modelForType(type);

return new Model(attrs, metaAttrs);
},

serialize(type, model) {
const root = this.rootKeyForType(type);
const payload = {};

payload[root] = model.attrs;

return payload;
}
});

export default CheckoutSerializer;
21 changes: 21 additions & 0 deletions lib/shop-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,27 @@ const ShopClient = CoreObject.extend({
});
},

create(type, attrs = {}) {
const adapter = new this.adapters[type](this.config);

return adapter.create(type, attrs).then(payload => {
return this.deserialize(type, payload, adapter, { single: true });
});
},

update(type, updatedModel) {
const adapter = updatedModel.adapter;
const serializer = updatedModel.serializer;
const serializedModel = serializer.serialize(type, updatedModel);
const id = updatedModel.attrs[adapter.idKeyForType(type)];

return adapter.update(type, id, serializedModel).then(payload => {
const meta = { shopClient: this, adapter, serializer, type };

return serializer.deserializeSingle(type, payload, meta);
});
},

fetchQuery(type, query) {
const adapter = new this.adapters[type](this.config);

Expand Down
164 changes: 164 additions & 0 deletions tests/unit/api/shop-client-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { step, resetStep } from 'buy-button-sdk/tests/helpers/assert-step';
import ShopClient from 'buy-button-sdk/shop-client';
import Config from 'buy-button-sdk/config';
import Promise from 'promise';
import CheckoutModel from 'buy-button-sdk/models/checkout-model';

const configAttrs = {
myShopifyDomain: 'buckets-o-stuff',
Expand All @@ -25,6 +26,16 @@ function FakeAdapter() {
resolve({});
});
};
this.create = function () {
return new Promise(function (resolve) {
resolve({});
});
};
this.update = function () {
return new Promise(function (resolve) {
resolve({});
});
};
}

function FakeSerializer() {
Expand All @@ -34,6 +45,9 @@ function FakeSerializer() {
this.deserializeMultiple = function () {
return [{}];
};
this.serialize = function () {
return {};
};
}

module('Unit | ShopClient', {
Expand Down Expand Up @@ -394,3 +408,153 @@ test('it forwards "fetchQueryNouns" to "fetchQuery(\'nouns\', ...)"', function (

shopClient.fetchQueryCollections(fetchedQuery);
});

test('it inits a type\'s adapter with the config during #create', function (assert) {
assert.expect(2);

const done = assert.async();

shopClient.adapters = {
checkouts: function (localConfig) {
assert.equal(localConfig, config);
FakeAdapter.apply(this, arguments);
}
};
shopClient.serializers = {
checkouts: FakeSerializer
};

shopClient.create('checkouts').then(() => {
assert.ok(true, 'it resolves the promise');
done();
}).catch(() => {
assert.ok(false);
done();
});
});

test('it inits a type\'s serializer with the config during #create', function (assert) {
assert.expect(2);

const done = assert.async();

shopClient.adapters = {
checkouts: FakeAdapter
};

shopClient.serializers = {
checkouts: function (localConfig) {
assert.equal(localConfig, config);
FakeSerializer.apply(this, arguments);
}
};

shopClient.create('checkouts').then(() => {
assert.ok(true);
done();
}).catch(() => {
assert.ok(false);
done();
});
});

test('it chains the result of the adapter\'s create through the type\'s serializer on #create', function (assert) {
assert.expect(6);

const done = assert.async();

const inputAttrs = { someProps: 'prop' };
const rawModel = { props: 'some-object' };
const serializedModel = { attrs: 'serialized-model' };

shopClient.adapters = {
checkouts: function () {
this.create = function (type, attrs) {
step(1, 'calls create on the adapter', assert);

assert.equal(attrs, inputAttrs);

return new Promise(function (resolve) {
resolve(rawModel);
});
};
}
};

shopClient.serializers = {
checkouts: function () {
this.deserializeSingle = function (type, results) {
step(2, 'calls deserializeSingle', assert);

assert.equal(results, rawModel);

return serializedModel;
};
}
};

shopClient.create('checkouts', inputAttrs).then(products => {
step(3, 'resolves after fetch and serialize', assert);
assert.equal(products, serializedModel);

done();
}).catch(() => {
assert.ok(false, 'promise should not reject');
done();
});
});

test('it utilizes the model\'s adapter and serializer during #update', function (assert) {
assert.expect(9);

const done = assert.async();
const serializedModel = { serializedProps: 'some-values' };
const updatedPayload = { rawUpdatedProps: 'updated-values' };
const updatedModel = { updatedProps: 'updated-values' };
const model = new CheckoutModel({
token: 'abc123',
someProp: 'some-prop'
}, {
shopClient,
adapter: {
update(type, id, payload) {
step(2, 'calls update on the models adapter', assert);
assert.equal(id, model.attrs.token, 'client extracts the token');
assert.equal(payload, serializedModel);

return new Promise(function (resolve) {
resolve(updatedPayload);
});
},
idKeyForType() {
return 'token';
}
},
serializer: {
serialize(type, localModel) {
step(1, 'calls serialize on the models serializer', assert);
assert.equal(localModel, model, 'serializer recieves model');

return serializedModel;
},
deserializeSingle(type, singlePayload) {
step(3, 'calls deserializeSingle with the result of adapter#update', assert);
assert.equal(singlePayload, updatedPayload);

return updatedModel;
}
}
});

// shopClient.adapters = { checkouts: FakeAdapter };
// shopClient.serializers = { products: FakeSerializer };

shopClient.update('checkouts', model).then(localUpdatedModel => {
step(4, 'resolves update with the deserialized model', assert);
assert.equal(localUpdatedModel, updatedModel);
done();
}).catch(e => {
assert.ok(false);
done();
});
});
75 changes: 75 additions & 0 deletions tests/unit/serializers/checkout-serializer-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { module, test } from 'qunit';
import CheckoutSerializer from 'buy-button-sdk/serializers/checkout-serializer';
import CheckoutModel from 'buy-button-sdk/models/checkout-model';

let serializer;

module('Unit | CheckoutSerializer', {
setup() {
serializer = new CheckoutSerializer();
},
teardown() {
serializer = null;
}
});


const checkoutFixture = {
checkout: {
line_items: []
}
};

test('it discovers the root key from the type', function (assert) {
assert.expect(1);

assert.equal(serializer.rootKeyForType('checkouts'), 'checkout');
});

test('it returns CheckoutModel for checkout type', function (assert) {
assert.expect(1);

assert.equal(serializer.modelForType('checkouts'), CheckoutModel);
});

test('it transforms a single item payload into a checkout object.', function (assert) {
assert.expect(2);

const model = serializer.deserializeSingle('checkouts', checkoutFixture);

assert.notOk(Array.isArray(model), 'should not be an array');
assert.deepEqual(model.attrs, checkoutFixture.checkout);
});

test('it attaches a reference of the passed serializer to the model on #deserializeSingle', function (assert) {
assert.expect(1);

const model = serializer.deserializeSingle('checkouts', checkoutFixture, { serializer });

assert.deepEqual(model.serializer, serializer);
});

test('it attaches a reference of the passed shopClient to the model on #deserializeSingle', function (assert) {
assert.expect(1);

const shopClient = 'some-shop-client';

const model = serializer.deserializeSingle('checkouts', checkoutFixture, { shopClient });

assert.equal(model.shopClient, shopClient);
});

test('it transforms a model into a payload on #serialize using the root key', function (assert) {
const updatedModel = new CheckoutModel({
line_items: [
{
variant_id: 123456789,
quantity: 1
}
]
});

const payload = serializer.serialize('checkouts', updatedModel);

assert.deepEqual(payload, { checkout: updatedModel.attrs });
});

0 comments on commit 9fe1c6c

Please sign in to comment.