Bug 1421806 - Create a mixin to subscribe to payment store changes. r=jaws

MozReview-Commit-ID: IGvvx7JDRtP

--HG--
extra : rebase_source : 33452110c0810194c825af76efa4201c047bfea5
This commit is contained in:
Matthew Noorenberghe 2017-11-30 14:37:09 -08:00
parent f5c90d1f80
commit f2930c7a77
5 changed files with 172 additions and 0 deletions

View File

@ -11,6 +11,7 @@ toolkit.jar:
% resource payments %res/payments/ % resource payments %res/payments/
res/payments (res/paymentRequest.*) res/payments (res/paymentRequest.*)
res/payments/components/ (res/components/*.js) res/payments/components/ (res/components/*.js)
res/payments/containers/ (res/containers/*.js)
res/payments/debugging.html (res/debugging.html) res/payments/debugging.html (res/debugging.html)
res/payments/debugging.js (res/debugging.js) res/payments/debugging.js (res/debugging.js)
res/payments/mixins/ (res/mixins/*.js) res/payments/mixins/ (res/mixins/*.js)

View File

@ -57,6 +57,9 @@ function ObservedPropertiesMixin(superClass) {
} }
attributeChangedCallback(attr, oldValue, newValue) { attributeChangedCallback(attr, oldValue, newValue) {
if (super.attributeChangedCallback) {
super.attributeChangedCallback(attr, oldValue, newValue);
}
if (oldValue === newValue) { if (oldValue === newValue) {
return; return;
} }

View File

@ -0,0 +1,82 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/* global PaymentsStore */
/**
* A mixin for a custom element to observe store changes to information about a payment request.
*/
/**
* State of the payment request dialog.
*/
let requestStore = new PaymentsStore({
request: {
tabId: null,
topLevelPrincipal: {URI: {displayHost: null}},
requestId: null,
paymentMethods: [],
paymentDetails: {
id: null,
totalItem: {label: null, amount: {currency: null, value: null}},
displayItems: [],
shippingOptions: [],
modifiers: null,
error: "",
},
paymentOptions: {
requestPayerName: false,
requestPayerEmail: false,
requestPayerPhone: false,
requestShipping: false,
shippingType: "shipping",
},
},
savedAddresses: [],
savedBasicCards: [],
});
/* exported PaymentStateSubscriberMixin */
/**
* A mixin to render UI based upon the requestStore and get updated when that store changes.
*
* Attaches `requestStore` to the element to give access to the store.
* @param {class} superClass The class to extend
* @returns {class}
*/
function PaymentStateSubscriberMixin(superClass) {
return class PaymentStateSubscriber extends superClass {
constructor() {
super();
this.requestStore = requestStore;
}
connectedCallback() {
this.requestStore.subscribe(this);
this.render(this.requestStore.getState());
if (super.connectedCallback) {
super.connectedCallback();
}
}
disconnectedCallback() {
this.requestStore.unsubscribe(this);
if (super.disconnectedCallback) {
super.disconnectedCallback();
}
}
/**
* Called by the store upon state changes.
* @param {object} state The current state
*/
stateChangeCallback(state) {
this.render(state);
}
};
}

View File

@ -1,10 +1,14 @@
[DEFAULT] [DEFAULT]
support-files = support-files =
../../../../../testing/modules/sinon-2.3.2.js
../../res/PaymentsStore.js
../../res/components/currency-amount.js ../../res/components/currency-amount.js
../../res/mixins/ObservedPropertiesMixin.js ../../res/mixins/ObservedPropertiesMixin.js
../../res/mixins/PaymentStateSubscriberMixin.js
../../res/vendor/custom-elements.min.js ../../res/vendor/custom-elements.min.js
../../res/vendor/custom-elements.min.js.map ../../res/vendor/custom-elements.min.js.map
payments_common.js payments_common.js
[test_currency_amount.html] [test_currency_amount.html]
[test_ObservedPropertiesMixin.html] [test_ObservedPropertiesMixin.html]
[test_PaymentStateSubscriberMixin.html]

View File

@ -0,0 +1,82 @@
<!DOCTYPE HTML>
<html>
<!--
Test the PaymentStateSubscriberMixin
-->
<head>
<meta charset="utf-8">
<title>Test the PaymentStateSubscriberMixin</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
<script src="sinon-2.3.2.js"></script>
<script src="payments_common.js"></script>
<script src="custom-elements.min.js"></script>
<script src="PaymentsStore.js"></script>
<script src="PaymentStateSubscriberMixin.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<p id="display">
<test-element id="el1"></test-element>
</p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
<script type="application/javascript">
/** Test the PaymentStateSubscriberMixin **/
/* global sinon */
/* import-globals-from payments_common.js */
/* import-globals-from ../../res/mixins/PaymentStateSubscriberMixin.js */
class TestElement extends PaymentStateSubscriberMixin(HTMLElement) {
render(state) {
this.textContent = JSON.stringify(state);
}
}
// We must spy on the prototype by creating the instance in order to test Custom Element reactions.
sinon.spy(TestElement.prototype, "disconnectedCallback");
customElements.define("test-element", TestElement);
let el1 = document.getElementById("el1");
sinon.spy(el1, "render");
sinon.spy(el1, "stateChangeCallback");
add_task(async function test_initialState() {
let parsedState = JSON.parse(el1.textContent);
ok(!!parsedState.request, "Check initial state contains `request`");
ok(!!parsedState.savedAddresses, "Check initial state contains `savedAddresses`");
ok(!!parsedState.savedBasicCards, "Check initial state contains `savedBasicCards`");
});
add_task(async function test_async_batched_render() {
el1.requestStore.setState({a: 1});
el1.requestStore.setState({b: 2});
await asyncElementRendered();
ok(el1.stateChangeCallback.calledOnce, "stateChangeCallback called once");
ok(el1.render.calledOnce, "render called once");
let parsedState = JSON.parse(el1.textContent);
is(parsedState.a, 1, "Check a");
is(parsedState.b, 2, "Check b");
});
add_task(async function test_disconnect() {
el1.disconnectedCallback.reset();
el1.render.reset();
el1.stateChangeCallback.reset();
el1.remove();
ok(el1.disconnectedCallback.calledOnce, "disconnectedCallback called once");
await el1.requestStore.setState({a: 3});
await asyncElementRendered();
ok(el1.stateChangeCallback.notCalled, "stateChangeCallback not called");
ok(el1.render.notCalled, "render not called");
});
</script>
</body>
</html>