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');
+ });
+});