Skip to content
This repository has been archived by the owner on Jan 17, 2023. It is now read-only.

Initial work toward new component bundle directories #2859

Merged
merged 6 commits into from
Sep 22, 2017
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ rules:
comma-dangle: [2, never]
indent: [2, 2, {SwitchCase: 1}]
max-len: [0]
one-var: off
one-var-declaration-per-line: off
no-console: 1
no-multi-spaces: [0]
no-param-reassign: [0]
Expand All @@ -44,4 +46,8 @@ rules:
linebreak-style: [0]
"import/no-extraneous-dependencies":
- error
- { devDependencies: [ "./frontend/{stories,tasks,tests}/**/*.{js,jsx}" ] }
-
devDependencies:
- '**/tests.js'
- '**/stories.{js,jsx}'
- "./frontend/{stories,tasks,tests}/**/*.{js,jsx}"
2 changes: 2 additions & 0 deletions .storybook/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import '../frontend/build/static/styles/experiments.css';
import '../frontend/build/static/styles/main.css';

const req = require.context('../frontend/stories', true, /\-story\.jsx?$/);
const reqInSrcTree = require.context('../frontend/src/app', true, /\/stories.jsx?$/);

function loadStories() {
req.keys().forEach((filename) => req(filename));
reqInSrcTree.keys().forEach((filename) => reqInSrcTree(filename));
}

configure(loadStories, module);
23 changes: 23 additions & 0 deletions .storybook/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const path = require('path');

// Export a function. Accept the base config as the only param.
module.exports = (storybookBaseConfig, configType) => {
// configType has a value of 'DEVELOPMENT' or 'PRODUCTION'
// You can change the configuration based on that.
// 'PRODUCTION' is used when building the static version of storybook.

// Make whatever fine-grained changes you need
storybookBaseConfig.module.rules.push({
test: /\.s?css$/,
loaders: ["style-loader", "css-loader", "sass-loader"],
include: path.resolve(__dirname, '../')
});

storybookBaseConfig.module.rules.push({
test: /\.(png|jpg|gif|svg)$/,
loaders: ['url-loader']
});

// Return the altered config
return storybookBaseConfig;
};
5 changes: 5 additions & 0 deletions frontend/.flowconfig
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,9 @@
[libs]
../flow-typed

[options]
module.file_ext=.scss
module.file_ext=.js
module.file_ext=.jsx

module.name_mapper='.*\.scss$' -> 'empty/object'
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import classnames from 'classnames';
import { Localized } from 'fluent-react/compat';
import React from 'react';

import { buildSurveyURL, experimentL10nId } from '../lib/utils';
import { buildSurveyURL, experimentL10nId } from '../../lib/utils';

import type { InstalledExperiments } from '../reducers/addon';
import type { InstalledExperiments } from '../../reducers/addon';

import ExperimentPlatforms from './ExperimentPlatforms';
import ExperimentPlatforms from '../ExperimentPlatforms';

const ONE_DAY = 24 * 60 * 60 * 1000;
const ONE_WEEK = 7 * ONE_DAY;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { withKnobs, object, boolean } from '@storybook/addon-knobs';

import ExperimentRowCard from '../../../src/app/components/ExperimentRowCard';
import LayoutWrapper from '../../../src/app/components/LayoutWrapper';
import ExperimentRowCard from './index';
import LayoutWrapper from '../LayoutWrapper';

const ONE_DAY = 24 * 60 * 60 * 1000;
const ONE_WEEK = 7 * ONE_DAY;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
/* global describe, beforeEach, it */
import React from 'react';
import { expect } from 'chai';
import sinon from 'sinon';
import { shallow } from 'enzyme';
import { findLocalizedById } from '../util';
import moment from 'moment';
import { findLocalizedById } from '../../../../test/app/util';

import ExperimentRowCard from '../../../src/app/components/ExperimentRowCard';
import ExperimentRowCard from './index';

describe('app/components/ExperimentRowCard', () => {
let mockExperiment, mockClickEvent, props, subject;
Expand Down Expand Up @@ -112,7 +113,7 @@ describe('app/components/ExperimentRowCard', () => {

subject.setProps({
enabled: false,
getExperimentLastSeen: sinon.spy(experiment => moment().valueOf())
getExperimentLastSeen: sinon.spy(() => moment().valueOf())
});

expect(props.getExperimentLastSeen.called).to.be.true;
Expand All @@ -127,7 +128,7 @@ describe('app/components/ExperimentRowCard', () => {
props = { ...props,
enabled: false,
getExperimentLastSeen:
sinon.spy(experiment => moment().subtract(1.5, 'week').valueOf()),
sinon.spy(() => moment().subtract(1.5, 'week').valueOf()),
experiment: { ...mockExperiment,
modified: moment().subtract(1, 'week').utc()
}
Expand All @@ -145,7 +146,7 @@ describe('app/components/ExperimentRowCard', () => {

subject.setProps({
enabled: false,
getExperimentLastSeen: sinon.spy(experiment => moment().valueOf())
getExperimentLastSeen: sinon.spy(() => moment().valueOf())
});

expect(props.getExperimentLastSeen.called).to.be.true;
Expand All @@ -157,15 +158,15 @@ describe('app/components/ExperimentRowCard', () => {
expect(findLocalizedById(subject, 'experimentListEndingTomorrow')).to.have.property('length', 0);
subject.setProps({ experiment: { ...mockExperiment,
completed: moment().add(23, 'hours').utc()
}});
} });
expect(findLocalizedById(subject, 'experimentListEndingTomorrow')).to.have.property('length', 1);
});

it('should show a "soon" status message when ending in one week', () => {
expect(findLocalizedById(subject, 'experimentListEndingSoon')).to.have.property('length', 0);
subject.setProps({ experiment: { ...mockExperiment,
completed: moment().add(6, 'days').utc()
}});
} });
expect(findLocalizedById(subject, 'experimentListEndingSoon')).to.have.property('length', 1);
});

Expand Down Expand Up @@ -198,7 +199,7 @@ describe('app/components/ExperimentRowCard', () => {
hasAddon: true
});
expect(findLocalizedById(subject, 'experimentCardManage')).to.have.property('length', 1);
})
});

it('should ping GA when manage is clicked', () => {
subject.find('.experiment-summary').simulate('click', mockClickEvent);
Expand Down
59 changes: 59 additions & 0 deletions frontend/src/app/components/IncompatibleAddons/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// @flow

import React from 'react';
import { Localized } from 'fluent-react/compat';
import LocalizedHtml from '../LocalizedHtml';

import './index.scss';

type IncompatibleAddonsProps = {
experiment: Object,
installedAddons: Array<string>
};

export default class IncompatibleAddons extends React.Component {
props: IncompatibleAddonsProps

render() {
const { incompatible } = this.props.experiment;
const installed = this.getIncompatibleInstalled(incompatible);
if (installed.length === 0) return null;

const helpUrl = 'https://support.mozilla.org/kb/disable-or-remove-add-ons';

return (
<section className="incompatible-addons">
<header>
<Localized id="incompatibleHeader">
<h3>
This experiment may not be compatible with add-ons you have installed.
</h3>
</Localized>
<LocalizedHtml id="incompatibleSubheader">
<p>
We recommend <a href={helpUrl}>disabling these add-ons</a> before activating this experiment:
</p>
</LocalizedHtml>
</header>
<main>
<ul>
{installed.map(guid => (
<li key={guid}>{incompatible[guid]}</li>
))}
</ul>
</main>
</section>
);
}

getIncompatibleInstalled(incompatible: Object) {
if (!incompatible) {
return [];
}
const installed = this.props.installedAddons || [];
return Object.keys(incompatible).filter(guid => (
installed.indexOf(guid) !== -1
));
}

}
2 changes: 2 additions & 0 deletions frontend/src/app/components/IncompatibleAddons/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// TODO: migrate <IncompatibleAddons> styles from frontend/src/styles/components/_Warning.scss

44 changes: 44 additions & 0 deletions frontend/src/app/components/IncompatibleAddons/stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import IncompatibleAddons from './index';
import LayoutWrapper from '../LayoutWrapper';

const experiment = {
title: 'Sample experiment',
description: 'This is an example experiment',
subtitle: '',
slug: 'snooze-tabs',
survey_url: 'https://example.com',
created: '2010-06-21T12:12:12Z',
modified: '2010-06-21T12:12:12Z',
incompatible: {
'[email protected]': 'Foo from BarCorp'
}
};

const installedAddons = [
'[email protected]'
];

const baseProps = {
experiment,
installedAddons
};

storiesOf('IncompatibleAddons', module)
.addDecorator(story =>
<div className="blue" style={{ padding: 10 }} onClick={action('click')}>
<div className="stars" />
<LayoutWrapper flexModifier="card-list">
{story()}
</LayoutWrapper>
</div>
)
.add('base state', () =>
<IncompatibleAddons {...{ baseProps }} />
)
.add('none installed', () =>
<IncompatibleAddons {...{ baseProps, installedAddons: [] }} />
)
;
35 changes: 35 additions & 0 deletions frontend/src/app/components/IncompatibleAddons/tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/* global describe, beforeEach, it */
import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';

import IncompatibleAddons from './index.js';

describe('app/components/IncompatibleAddons', () => {
let mockExperiment, props, subject;
beforeEach(() => {
mockExperiment = {
slug: 'testing',
title: 'Testing',
incompatible: {}
};
props = {
experiment: mockExperiment,
installedAddons: []
};
subject = shallow(<IncompatibleAddons {...props} />);
});

it('should render a warning only if incompatible add-ons are installed', () => {
expect(subject.find('.incompatible-addons')).to.have.property('length', 0);

const experiment = { ...mockExperiment, incompatible: { foo: 1, bar: 2 } };
subject.setProps({ experiment });

subject.setProps({ installedAddons: ['baz'] });
expect(subject.find('.incompatible-addons')).to.have.property('length', 0);

subject.setProps({ installedAddons: ['baz', 'bar'] });
expect(subject.find('.incompatible-addons')).to.have.property('length', 1);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { Localized } from 'fluent-react/compat';
import moment from 'moment';
import React from 'react';

import LayoutWrapper from './LayoutWrapper';
import { newsUpdateL10nId } from '../lib/utils';
import LayoutWrapper from '../LayoutWrapper';
import { newsUpdateL10nId } from '../../lib/utils';

export function prettyDate(date: string) {
return moment(date).format('MMMM Do, YYYY');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { withKnobs } from '@storybook/addon-knobs';

import UpdateList from '../../../src/app/components/UpdateList';
import LayoutWrapper from '../../../src/app/components/LayoutWrapper';
import UpdateList from './index';
import LayoutWrapper from '../LayoutWrapper';

const time = Date.now();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
/* global describe, beforeEach, it */
import React from 'react';
import sinon from 'sinon';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import UpdateList, { Update, prettyDate } from '../../../src/app/components/UpdateList';

import UpdateList, { Update, prettyDate } from './index';

describe('app/components/UpdateList', () => {
it('should display nothing if no updates are available', () => {
Expand Down Expand Up @@ -73,7 +75,7 @@ describe('app/components/UpdateList', () => {
{ slug: 'foo', experimentSlug: 'exp1' },
{ slug: 'bar' }
],
staleNewsUpdates: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map(idx => ({ slug: `stale-${idx}` })),
staleNewsUpdates: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map(idx => ({ slug: `stale-${idx}` }))
};

const mockClickEvent = {
Expand All @@ -94,7 +96,6 @@ describe('app/components/UpdateList', () => {
});

describe('Update', () => {

it('should display expected basic content', () => {
const subject = shallow(<Update
update={{ title: 'foo', content: 'bar' }}
Expand Down Expand Up @@ -182,7 +183,5 @@ describe('app/components/UpdateList', () => {
eventLabel: `news-item-${slug}`
}]);
});

});

});
Loading