diff --git a/js/src/contracts/contracts.js b/js/src/contracts/contracts.js index a1a81a8ee..7aca3084f 100644 --- a/js/src/contracts/contracts.js +++ b/js/src/contracts/contracts.js @@ -34,13 +34,17 @@ export default class Contracts { this._signaturereg = new SignatureReg(api, this._registry); this._tokenreg = new TokenReg(api, this._registry); this._githubhint = new GithubHint(api, this._registry); - this.badgeReg = new BadgeReg(api, this._registry); + this._badgeReg = new BadgeReg(api, this._registry); } get registry () { return this._registry; } + get badgeReg () { + return this._badgeReg; + } + get dappReg () { return this._dappreg; } diff --git a/js/src/views/Dapps/dappStore.spec.js b/js/src/views/Dapps/dappStore.spec.js new file mode 100644 index 000000000..571099457 --- /dev/null +++ b/js/src/views/Dapps/dappStore.spec.js @@ -0,0 +1,220 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import BigNumber from 'bignumber.js'; +import sinon from 'sinon'; +import localStore from 'store'; + +import Contracts from '~/contracts'; + +import Store, { LS_KEY_DISPLAY } from './dappsStore'; + +const APPID_BASICCOIN = '0xf9f2d620c2e08f83e45555247146c62185e4ab7cf82a4b9002a265a0d020348f'; +const APPID_DAPPREG = '0x7bbc4f1a27628781b96213e781a1b8eec6982c1db8fac739af6e4c5a55862c03'; +const APPID_GHH = '0x058740ee9a5a3fb9f1cfa10752baec87e09cc45cd7027fd54708271aca300c75'; +const APPID_LOCALTX = '0xae74ad174b95cdbd01c88ac5b73a296d33e9088fc2a200e76bcedf3a94a7815d'; +const FETCH_OK = { + ok: true, + status: 200 +}; + +let globalContractsGet; +let globalFetch; + +function stubGlobals () { + globalContractsGet = Contracts.get; + globalFetch = global.fetch; + + Contracts.get = () => { + return { + dappReg: { + at: sinon.stub().resolves([[0, 1, 2, 3], 'appOwner']), + count: sinon.stub().resolves(new BigNumber(1)), + getContract: sinon.stub().resolves({}), + getContent: sinon.stub().resolves([0, 1, 2, 3]), + getImage: sinon.stub().resolves([0, 1, 2, 3]), + getManifest: sinon.stub().resolves([0, 1, 2, 3]) + } + }; + }; + + global.fetch = (url) => { + switch (url) { + case '/api/apps': + return Promise.resolve(Object.assign({}, FETCH_OK, { + json: sinon.stub().resolves([]) // TODO: Local stubs in here + })); + + default: + console.log('Unknown fetch stub endpoint', url); + return Promise.reject(); + } + }; +} + +function restoreGlobals () { + Contracts.get = globalContractsGet; + global.fetch = globalFetch; +} + +let api; +let store; + +function create () { + api = {}; + store = new Store(api); + + return store; +} + +describe('views/Dapps/DappStore', () => { + beforeEach(() => { + stubGlobals(); + }); + + afterEach(() => { + restoreGlobals(); + }); + + describe('@action', () => { + const defaultViews = { + [APPID_BASICCOIN]: { visible: false }, + [APPID_DAPPREG]: { visible: true } + }; + + describe('setDisplayApps', () => { + beforeEach(() => { + create(); + store.setDisplayApps(defaultViews); + }); + + it('sets from empty start', () => { + expect(store.displayApps).to.deep.equal(defaultViews); + }); + + it('overrides single keys, keeping existing', () => { + store.setDisplayApps({ [APPID_BASICCOIN]: { visible: true } }); + + expect(store.displayApps).to.deep.equal( + Object.assign({}, defaultViews, { [APPID_BASICCOIN]: { visible: true } }) + ); + }); + + it('extends with new keys, keeping existing', () => { + store.setDisplayApps({ 'test': { visible: true } }); + + expect(store.displayApps).to.deep.equal( + Object.assign({}, defaultViews, { 'test': { visible: true } }) + ); + }); + }); + + describe('hideApp/showApp', () => { + beforeEach(() => { + localStore.set(LS_KEY_DISPLAY, defaultViews); + + create().readDisplayApps(); + }); + + afterEach(() => { + localStore.set(LS_KEY_DISPLAY, {}); + }); + + it('disables visibility', () => { + store.hideApp(APPID_DAPPREG); + + expect(store.displayApps[APPID_DAPPREG].visible).to.be.false; + expect(localStore.get(LS_KEY_DISPLAY)).to.deep.equal( + Object.assign({}, defaultViews, { [APPID_DAPPREG]: { visible: false } }) + ); + }); + + it('enables visibility', () => { + store.showApp(APPID_BASICCOIN); + + expect(store.displayApps[APPID_BASICCOIN].visible).to.be.true; + expect(localStore.get(LS_KEY_DISPLAY)).to.deep.equal( + Object.assign({}, defaultViews, { [APPID_BASICCOIN]: { visible: true } }) + ); + }); + + it('keeps visibility state', () => { + store.hideApp(APPID_BASICCOIN); + store.showApp(APPID_DAPPREG); + + expect(store.displayApps[APPID_BASICCOIN].visible).to.be.false; + expect(store.displayApps[APPID_DAPPREG].visible).to.be.true; + expect(localStore.get(LS_KEY_DISPLAY)).to.deep.equal(defaultViews); + }); + }); + + describe('readDisplayApps/writeDisplayApps', () => { + beforeEach(() => { + localStore.set(LS_KEY_DISPLAY, defaultViews); + + create().readDisplayApps(); + }); + + afterEach(() => { + localStore.set(LS_KEY_DISPLAY, {}); + }); + + it('loads visibility from storage', () => { + expect(store.displayApps).to.deep.equal(defaultViews); + }); + + it('saves visibility to storage', () => { + store.setDisplayApps({ [APPID_BASICCOIN]: { visible: true } }); + store.writeDisplayApps(); + + expect(localStore.get(LS_KEY_DISPLAY)).to.deep.equal( + Object.assign({}, defaultViews, { [APPID_BASICCOIN]: { visible: true } }) + ); + }); + }); + }); + + describe('saved views', () => { + beforeEach(() => { + localStore.set(LS_KEY_DISPLAY, { + [APPID_BASICCOIN]: { visible: false }, + [APPID_DAPPREG]: { visible: true } + }); + + return create().loadAllApps(); + }); + + afterEach(() => { + localStore.set(LS_KEY_DISPLAY, {}); + }); + + it('disables based on saved keys', () => { + expect(store.displayApps[APPID_BASICCOIN].visible).to.be.false; + }); + + it('enables based on saved keys', () => { + expect(store.displayApps[APPID_DAPPREG].visible).to.be.true; + }); + + it('keeps non-sepcified disabled keys', () => { + expect(store.displayApps[APPID_GHH].visible).to.be.false; + }); + + it('keeps non-specified enabled keys', () => { + expect(store.displayApps[APPID_LOCALTX].visible).to.be.true; + }); + }); +}); diff --git a/js/src/views/Dapps/dappsStore.js b/js/src/views/Dapps/dappsStore.js index 16e0a0de0..6971d50db 100644 --- a/js/src/views/Dapps/dappsStore.js +++ b/js/src/views/Dapps/dappsStore.js @@ -39,7 +39,6 @@ export default class DappsStore extends EventEmitter { _api = null; _subscriptions = {}; - _cachedApps = {}; _manifests = {}; _registryAppsIds = null; @@ -246,12 +245,12 @@ export default class DappsStore extends EventEmitter { } @action hideApp = (id) => { - this.displayApps = Object.assign({}, this.displayApps, { [id]: { visible: false } }); + this.setDisplayApps({ [id]: { visible: false } }); this.writeDisplayApps(); } @action showApp = (id) => { - this.displayApps = Object.assign({}, this.displayApps, { [id]: { visible: true } }); + this.setDisplayApps({ [id]: { visible: true } }); this.writeDisplayApps(); } @@ -263,6 +262,10 @@ export default class DappsStore extends EventEmitter { store.set(LS_KEY_DISPLAY, this.displayApps); } + @action setDisplayApps = (displayApps) => { + this.displayApps = Object.assign({}, this.displayApps, displayApps); + }; + @action addApps = (_apps = []) => { transaction(() => { const apps = _apps.filter((app) => app); @@ -285,7 +288,11 @@ export default class DappsStore extends EventEmitter { } }); - this.displayApps = Object.assign({}, this.displayApps, visibility); + this.setDisplayApps(visibility); }); } } + +export { + LS_KEY_DISPLAY +};