diff --git a/src/disco/client.js b/src/disco/client.js index b71de4ef0a1..3a48ed62b04 100644 --- a/src/disco/client.js +++ b/src/disco/client.js @@ -1,9 +1,5 @@ -import React from 'react'; -import { render } from 'react-dom'; -import { Router, browserHistory } from 'react-router'; +import makeClient from 'core/client/base'; import routes from './routes'; +import createStore from './store'; -render( - , - document.getElementById('react-view') -); +makeClient(routes, createStore); diff --git a/src/disco/components/InstallButton.js b/src/disco/components/InstallButton.js index 1c211806425..1e3de48708e 100644 --- a/src/disco/components/InstallButton.js +++ b/src/disco/components/InstallButton.js @@ -1,16 +1,64 @@ -import React from 'react'; +import React, { PropTypes } from 'react'; import { gettext as _ } from 'core/utils'; import 'disco/css/InstallButton.scss'; +import { + DOWNLOADING, + INSTALLED, + INSTALLING, + UNINSTALLED, + UNINSTALLING, + UNKNOWN, +} from 'disco/constants'; +const validStates = [ + DOWNLOADING, + INSTALLED, + INSTALLING, + UNINSTALLED, + UNINSTALLING, + UNKNOWN, +]; + export default class InstallButton extends React.Component { + + static propTypes = { + addonState: PropTypes.oneOf(validStates), + downloadProgressPercent: PropTypes.number, + handleClick: PropTypes.func, + handleChange: PropTypes.func, + } + + static defaultProps = { + addonState: UNKNOWN, + downloadProgressPercent: 0, + } + render() { + const { addonState, downloadProgressPercent } = this.props; + + if (validStates.indexOf(addonState) === -1) { + throw new Error('Invalid addonState'); + } + + const isInstalled = addonState === INSTALLED; + const isDisabled = addonState === UNKNOWN; + const isDownloading = addonState === DOWNLOADING; + const switchClasses = `switch ${addonState}`; + return ( -
- +
+
diff --git a/src/disco/constants.js b/src/disco/constants.js new file mode 100644 index 00000000000..91059fff6bb --- /dev/null +++ b/src/disco/constants.js @@ -0,0 +1,6 @@ +export const DOWNLOADING = 'downloading'; +export const INSTALLED = 'installed'; +export const INSTALLING = 'installing'; +export const UNINSTALLED = 'uninstalled'; +export const UNINSTALLING = 'uninstalling'; +export const UNKNOWN = 'unknown'; diff --git a/src/disco/css/InstallButton.scss b/src/disco/css/InstallButton.scss index 20761594180..ff0085cf453 100644 --- a/src/disco/css/InstallButton.scss +++ b/src/disco/css/InstallButton.scss @@ -130,45 +130,21 @@ $installStripeColor2: #00C42E; } } - @keyframes download { - from { - width: 0; - } - to { - width: 50px; - } - } - &.downloading { overflow: hidden; position: relative; - .progress { - background: transparentize(#3ef827, 0.50); - bottom: 0; - left: 0; - position: absolute; - right: 0; - top: 0; - width: 80%; - } - input + label { - border: 1px solid $switchStrokeOn; + border: 1px solid $switchStrokeOff; overflow: hidden; - &:before { - background: $switchBackgroundOff; - } - &:after { - background: $switchHandleInactive; + background: $switchHandleActive; left: $switchHandleActivePosition; } } } - // Disabled State input:disabled + label, input:checked:disabled + label { @@ -190,3 +166,21 @@ $installStripeColor2: #00C42E; } } } + +.progress { + background: transparentize(#3ef827, 0.50); + bottom: 0; + left: 0; + position: absolute; + right: 0; + top: 0; + width: 100%; + transition: transform 1s; + transform: translateX(-100%); +} + +@for $i from 1 through 100 { + [data-download-progress="#{$i}"] .progress { + transform: translateX(#{percentage($i - 100) / 100}); + } +} diff --git a/tests/client/disco/components/TestInstallButton.js b/tests/client/disco/components/TestInstallButton.js index e69de29bb2d..52174904279 100644 --- a/tests/client/disco/components/TestInstallButton.js +++ b/tests/client/disco/components/TestInstallButton.js @@ -0,0 +1,78 @@ +import React from 'react'; +import { findDOMNode } from 'react-dom'; +import { Simulate, renderIntoDocument } from 'react-addons-test-utils'; + +import InstallButton from 'disco/components/InstallButton'; +import { + DOWNLOADING, + INSTALLED, + INSTALLING, + UNINSTALLED, + UNINSTALLING, + UNKNOWN, +} from 'disco/constants'; + + +describe('', () => { + function renderButton(props) { + return renderIntoDocument(); + } + + + it('should be disabled if isDisabled addonState is UNKNOWN', () => { + const button = renderButton({addonState: UNKNOWN}); + const root = findDOMNode(button); + const checkbox = root.querySelector('input[type=checkbox]'); + assert.equal(checkbox.hasAttribute('disabled'), true); + assert.ok(root.classList.contains('unknown')); + }); + + it('should reflect UNINSTALLED state', () => { + const button = renderButton({addonState: UNINSTALLED}); + const root = findDOMNode(button); + const checkbox = root.querySelector('input[type=checkbox]'); + assert.equal(checkbox.hasAttribute('disabled'), false); + assert.ok(root.classList.contains('uninstalled')); + }); + + it('should reflect INSTALLED state', () => { + const button = renderButton({addonState: INSTALLED}); + const root = findDOMNode(button); + const checkbox = root.querySelector('input[type=checkbox]'); + assert.equal(checkbox.checked, true, 'checked is true'); + assert.ok(root.classList.contains('installed')); + }); + + it('should reflect download progress', () => { + const button = renderButton({addonState: DOWNLOADING, downloadProgressPercent: 50}); + const root = findDOMNode(button); + assert.ok(root.classList.contains('downloading')); + assert.equal(root.getAttribute('data-download-progress'), 50); + }); + + it('should reflect installation', () => { + const button = renderButton({addonState: INSTALLING}); + const root = findDOMNode(button); + assert.ok(root.classList.contains('installing')); + }); + + it('should reflect uninstallation', () => { + const button = renderButton({addonState: UNINSTALLING}); + const root = findDOMNode(button); + assert.ok(root.classList.contains('uninstalling')); + }); + + it('should call click function passed via prop', () => { + const clickStub = sinon.stub(); + const button = renderButton({addonState: UNINSTALLED, handleClick: clickStub}); + const root = findDOMNode(button); + Simulate.click(root); + assert.ok(clickStub.called); + }); + + it('should throw on bogus state', () => { + assert.throws(() => { + renderButton({addonState: 'BOGUS'}); + }, Error, 'Invalid addonState'); + }); +});