prettier all code and default prettier rules, fixes #182,#209 (#222)

* prettier all code and default prettier rules, fixes #182

* lint_problems.sh

* typos

* updateing readme
This commit is contained in:
Peter Bengtsson 2018-02-08 13:04:40 -05:00 коммит произвёл GitHub
Родитель 7a3b6e8ed0
Коммит e22ce7fa2b
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
20 изменённых файлов: 896 добавлений и 826 удалений

Просмотреть файл

@ -1,5 +0,0 @@
singleQuote: true
trailingComma: all
bracketSpacing: false
jsxBracketSameLine: false
parser: flow

Просмотреть файл

@ -10,14 +10,13 @@ displaying information from those in a centralized place.
## Table of Contents
- [Cloning and getting into the Project Dir](#cloning-and-getting-into-the-project-dir)
- [Setting up the development environment](#setting-up-the-development-environment)
- [Starting the dev server](#starting-the-dev-server)
- [Building](#building)
- [Deploying to gh-pages](#deploying-to-gh-pages)
- [Launching testsuite](#launching-testsuite)
* [Cloning and getting into the Project Dir](#cloning-and-getting-into-the-project-dir)
* [Setting up the development environment](#setting-up-the-development-environment)
* [Starting the dev server](#starting-the-dev-server)
* [Building](#building)
* [Deploying to gh-pages](#deploying-to-gh-pages)
* [Launching testsuite](#launching-testsuite)
* [Linting](#linting)
## Cloning and getting into the Project Dir
@ -60,3 +59,31 @@ https://[your-github-username].github.io/delivery-dashboard/
To run the testsuite, simply type:
$ yarn test
## Linting
We use [Prettier](https://prettier.io/) to format all `.js` and `.css` files
according to the default configuration of Prettier.
When contributing, it is your responsibility to make sure all files you
touch conform to the Prettier standard, but there are useful tools to make
this easier.
Linting is checked in continuous integration for every pull request and
build of `master`. If any file has any deviation from the Prettier output
it will "break the build" and you're expected to fix it.
To make it easier to see what the potential linting problems are run:
```sh
$ yarn lint
```
It will report any errors and explain which files need attention. To
make this more convenient you can simply run:
```sh
$ yarn lint-fix
```
which will directly fix the files that didn't pass.

Просмотреть файл

@ -30,14 +30,15 @@
"deploy": "yarn run build && gh-pages --add --dist build/",
"deploy-dev":
"PUBLIC_URL=$npm_package_homepage_dev yarn run deploy --dest dev/",
"lint": "prettier --list-different src/**/*.js",
"lint-fix": "yarn lint --write",
"lint":
"prettier --list-different src/**/*.js src/**/*.css | scripts/lint_problems.sh",
"lint-fix": "prettier --write src/**/*.js src/**/*.css",
"flow": "flow",
"flow-coverage":
"flow-coverage-report -i 'src/**/*.js' -x 'src/**/*.test.js' -t html -t text",
"check": "yarn lint && yarn flow",
"precheck": "yarn run version-file",
"version-file": "./update_version.sh"
"version-file": "./scripts/update_version.sh"
},
"devDependencies": {
"babel-plugin-import": "1.6.3",

3
scripts/README.md Normal file
Просмотреть файл

@ -0,0 +1,3 @@
Here goes miscellaneous scripts that help support the project
infrastructure. None of these scripts should be used or necessary for
runtime in production.

35
scripts/lint_problems.sh Executable file
Просмотреть файл

@ -0,0 +1,35 @@
#!/usr/bin/env bash
set -eo pipefail
prelude() {
echo "
You have prettier linting errors!
----------------------------------
The following files would turn out different if you process them with prettier.
"
}
any=false
first=true
while read line
do
$first && prelude
echo "To fix:"
echo " prettier --write ${line}"
echo "To see:"
echo " prettier ${line} | diff ${line} -"
echo ""
# echo "$line"
any=true
first=false
done < "${1:-/dev/stdin}"
$any && echo "
If you're not interested in how they're different, consider running:
yarn run lint-fix
"
$any && exit 1 || exit 0

Просмотреть файл

Просмотреть файл

@ -1,18 +1,18 @@
// @flow
import 'photon-ant';
import * as React from 'react';
import {Alert, Card, Icon, Layout, Spin, Tooltip} from 'antd';
import './App.css';
import {connect} from 'react-redux';
import type {MapStateToProps} from 'react-redux';
import "photon-ant";
import * as React from "react";
import { Alert, Card, Icon, Layout, Spin, Tooltip } from "antd";
import "./App.css";
import { connect } from "react-redux";
import type { MapStateToProps } from "react-redux";
import {
capitalize,
localUrlFromVersion,
requestOngoingVersions,
requestPollbotVersion,
refreshStatus,
requestStatus,
} from './actions';
requestStatus
} from "./actions";
import type {
APIVersionData,
CheckResult,
@ -23,18 +23,18 @@ import type {
Product,
ReleaseInfo,
State,
Status,
} from './types';
import {products} from './types';
Status
} from "./types";
import { products } from "./types";
const deliveryDashboardVersionData: APIVersionData = require('./version.json');
const deliveryDashboardVersionData: APIVersionData = require("./version.json");
function requestNotificationPermission(): void {
// Some browsers don't support Notification yet. I'm looking at you iOS Safari
if ('Notification' in window) {
if ("Notification" in window) {
if (
Notification.permission !== 'denied' &&
Notification.permission !== 'granted'
Notification.permission !== "denied" &&
Notification.permission !== "granted"
) {
Notification.requestPermission();
}
@ -42,8 +42,8 @@ function requestNotificationPermission(): void {
}
export const parseUrl = (
url: string,
): ?{service: string, product: Product, version: string} => {
url: string
): ?{ service: string, product: Product, version: string } => {
const re = /^#(\w+)\/(\w+)\/([^/]+)\/?/; // Eg: #pollbot/firefox/50.0
const parsed: ?(string[]) = url.match(re);
if (!parsed) {
@ -58,7 +58,7 @@ export const parseUrl = (
return {
service: service,
product: maybeProduct,
version: version,
version: version
};
};
@ -67,7 +67,7 @@ type AppProps = {
dispatch: Dispatch,
pollbotVersion: APIVersionData,
shouldRefresh: boolean,
errors: Error[],
errors: Error[]
};
export class App extends React.Component<AppProps, void> {
refreshIntervalId: ?IntervalID;
@ -85,7 +85,7 @@ export class App extends React.Component<AppProps, void> {
}
this.refreshIntervalId = setInterval(
() => this.props.dispatch(refreshStatus()),
60000,
60000
);
} else {
this.stopAutoRefresh();
@ -143,9 +143,9 @@ export class App extends React.Component<AppProps, void> {
</Layout.Content>
</Layout>
<footer>
Delivery dashboard version:{' '}
Delivery dashboard version:{" "}
<VersionLink versionData={deliveryDashboardVersionData} />
&nbsp;--&nbsp;Pollbot version:{' '}
&nbsp;--&nbsp;Pollbot version:{" "}
<VersionLink versionData={this.props.pollbotVersion} />
</footer>
</div>
@ -153,28 +153,28 @@ export class App extends React.Component<AppProps, void> {
}
}
const connectedAppMapStateToProps: MapStateToProps<*, *, *> = (
state: State,
state: State
) => ({
checkResults: state.checkResults,
pollbotVersion: state.pollbotVersion,
shouldRefresh: state.shouldRefresh,
errors: state.errors,
errors: state.errors
});
export const ConnectedApp = connect(
connectedAppMapStateToProps,
(dispatch: Dispatch) => ({dispatch: dispatch}),
(dispatch: Dispatch) => ({ dispatch: dispatch })
)(App);
const sideBarMapStateToProps: MapStateToProps<*, *, *> = (state: State) => ({
versions: state.productVersions,
versions: state.productVersions
});
const SideBar = connect(sideBarMapStateToProps)(ReleasesMenu);
type ReleasesMenuPropType = {
versions: ProductVersions,
versions: ProductVersions
};
export function ReleasesMenu({versions}: ReleasesMenuPropType) {
export function ReleasesMenu({ versions }: ReleasesMenuPropType) {
const getVersion = (product, channel) => {
const capitalizedChannel = capitalize(channel);
if (versions.hasOwnProperty(product) && versions[product][channel]) {
@ -195,30 +195,30 @@ export function ReleasesMenu({versions}: ReleasesMenuPropType) {
<div className="releasesMenu">
<h2>Firefox Releases</h2>
<ul>
<li>{getVersion('firefox', 'nightly')}</li>
<li>{getVersion('firefox', 'beta')}</li>
<li>{getVersion('devedition', 'devedition')}</li>
<li>{getVersion('firefox', 'release')}</li>
<li>{getVersion('firefox', 'esr')}</li>
<li>{getVersion("firefox", "nightly")}</li>
<li>{getVersion("firefox", "beta")}</li>
<li>{getVersion("devedition", "devedition")}</li>
<li>{getVersion("firefox", "release")}</li>
<li>{getVersion("firefox", "esr")}</li>
</ul>
</div>
);
}
const currentReleaseMapStateToProps: MapStateToProps<*, *, *> = (
state: State,
state: State
) => ({
checkResults: state.checkResults,
releaseInfo: state.releaseInfo,
productVersion: state.version,
productVersion: state.version
});
const CurrentRelease = connect(currentReleaseMapStateToProps)(Dashboard);
type ErrorsPropType = {
errors: Error[],
errors: Error[]
};
export function Errors({errors}: ErrorsPropType) {
export function Errors({ errors }: ErrorsPropType) {
if (!errors || errors.length === 0) {
return null;
}
@ -243,16 +243,16 @@ export function Errors({errors}: ErrorsPropType) {
type DashboardPropType = {
checkResults: CheckResults,
releaseInfo: ?ReleaseInfo,
productVersion: [Product, string],
productVersion: [Product, string]
};
export function Dashboard({
releaseInfo,
checkResults,
productVersion,
productVersion
}: DashboardPropType) {
const [product, version] = productVersion;
if (version === '') {
if (version === "") {
return (
<p>
Learn more about a specific version.
@ -262,12 +262,12 @@ export function Dashboard({
} else if (!releaseInfo) {
return <Spin />;
} else if (releaseInfo.message) {
return <Errors errors={[['Pollbot error', releaseInfo.message]]} />;
return <Errors errors={[["Pollbot error", releaseInfo.message]]} />;
} else {
return (
<div>
<h2 style={{marginBottom: '1em', display: 'flex', flexWrap: 'wrap'}}>
{capitalize(product)} {version}{' '}
<h2 style={{ marginBottom: "1em", display: "flex", flexWrap: "wrap" }}>
{capitalize(product)} {version}{" "}
<OverallStatus
releaseInfo={releaseInfo}
checkResults={checkResults}
@ -290,18 +290,18 @@ export function Dashboard({
type OverallStatusPropType = {
checkResults: CheckResults,
releaseInfo: ReleaseInfo,
releaseInfo: ReleaseInfo
};
export function OverallStatus({
releaseInfo,
checkResults,
checkResults
}: OverallStatusPropType) {
const checksStatus = releaseInfo.checks.map(
check => checkResults[check.title],
check => checkResults[check.title]
);
const allChecksCompleted = !checksStatus.some(
result => typeof result === 'undefined',
result => typeof result === "undefined"
);
if (!allChecksCompleted) {
return <Spin />;
@ -319,29 +319,34 @@ export function OverallStatus({
});
let type;
let message;
if (actionableChecks.some(status => status !== 'exists')) {
type = 'error';
message = 'Some checks failed';
if (actionableChecks.some(status => status !== "exists")) {
type = "error";
message = "Some checks failed";
} else {
type = 'success';
message = 'All checks are successful';
type = "success";
message = "All checks are successful";
}
return (
<Alert message={message} type={type} showIcon style={{marginLeft: '1em'}} />
<Alert
message={message}
type={type}
showIcon
style={{ marginLeft: "1em" }}
/>
);
}
type DisplayCheckResultProps = {
title: string,
actionable: boolean,
checkResult: CheckResult,
checkResult: CheckResult
};
export class DisplayCheckResult extends React.PureComponent<
DisplayCheckResultProps,
void,
void
> {
render() {
const {title, actionable, checkResult} = this.props;
const { title, actionable, checkResult } = this.props;
let titleContent = title;
if (!actionable) {
titleContent = (
@ -353,7 +358,7 @@ export class DisplayCheckResult extends React.PureComponent<
);
}
return (
<Card title={titleContent} style={{textAlign: 'center'}}>
<Card title={titleContent} style={{ textAlign: "center" }}>
{checkResult ? (
<DisplayStatus
status={checkResult.status}
@ -373,24 +378,24 @@ export function DisplayStatus({
status,
message,
url,
actionable,
actionable
}: {
status: Status,
message: string,
url: string,
actionable: boolean,
actionable: boolean
}) {
const getLabelClass = (status, actionable) => {
if (status === 'error') {
return 'error';
if (status === "error") {
return "error";
}
if (status === 'exists') {
return 'success';
if (status === "exists") {
return "success";
}
if (actionable) {
return 'warning';
return "warning";
}
return 'info'; // It's a non actionable item.
return "info"; // It's a non actionable item.
};
return (
<a title={message} href={url}>
@ -403,12 +408,12 @@ export function DisplayStatus({
);
}
function VersionLink({versionData}: {versionData: APIVersionData}) {
function VersionLink({ versionData }: { versionData: APIVersionData }) {
if (!versionData) {
return null;
}
const {commit, source, version} = versionData;
const sourceUrl = source.replace(/\.git/, '');
const { commit, source, version } = versionData;
const sourceUrl = source.replace(/\.git/, "");
const url = `${sourceUrl}/commit/${commit}`;
return <a href={url}>{version}</a>;
}

Просмотреть файл

@ -1,7 +1,7 @@
import React from 'react';
import renderer from 'react-test-renderer';
import {Provider} from 'react-redux';
import {mount, shallow} from 'enzyme';
import React from "react";
import renderer from "react-test-renderer";
import { Provider } from "react-redux";
import { mount, shallow } from "enzyme";
import {
App,
ConnectedApp,
@ -11,19 +11,19 @@ import {
Errors,
OverallStatus,
ReleasesMenu,
parseUrl,
} from './App';
import {Alert, Spin, Tooltip} from 'antd';
import createStore from './create-store';
import {SERVER} from './PollbotAPI';
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
parseUrl
} from "./App";
import { Alert, Spin, Tooltip } from "antd";
import createStore from "./create-store";
import { SERVER } from "./PollbotAPI";
import Enzyme from "enzyme";
import Adapter from "enzyme-adapter-react-16";
Enzyme.configure({adapter: new Adapter()});
Enzyme.configure({ adapter: new Adapter() });
// Mock the Notification API.
global.Notification = {
requestPermission: jest.fn(),
requestPermission: jest.fn()
};
// Mock the localStorage API.
@ -39,7 +39,7 @@ global.localStorage = (function() {
},
clear: function() {
store = {};
},
}
};
})();
@ -49,22 +49,22 @@ global.localStorage = (function() {
function fetchMocker(response) {
return jest
.fn()
.mockImplementation(() => Promise.resolve({json: () => response}));
.mockImplementation(() => Promise.resolve({ json: () => response }));
}
global.fetch = fetchMocker({
version: 'pollbot-version-number',
commit: 'pollbot-commit-hash',
source: 'https://github.com/mozilla/PollBot.git',
name: 'pollbot',
version: "pollbot-version-number",
commit: "pollbot-commit-hash",
source: "https://github.com/mozilla/PollBot.git",
name: "pollbot"
});
// Mock the delivery-dashboard version
jest.mock('./version', () => ({
version: 'version-number',
commit: 'commit-hash',
source: 'https://github.com/mozilla/delivery-dashboard.git',
name: 'delivery-dashboard',
jest.mock("./version", () => ({
version: "version-number",
commit: "commit-hash",
source: "https://github.com/mozilla/delivery-dashboard.git",
name: "delivery-dashboard"
}));
beforeAll(() => {
@ -75,35 +75,35 @@ afterAll(() => {
jest.clearAllTimers();
});
describe('<App />', () => {
it('renders without crashing', () => {
describe("<App />", () => {
it("renders without crashing", () => {
const app = renderer.create(
<Provider store={createStore()}>
<ConnectedApp />
</Provider>,
</Provider>
);
expect(app.toJSON()).toMatchSnapshot();
});
it('requests for Notification permissions', () => {
it("requests for Notification permissions", () => {
renderer.create(
<Provider store={createStore()}>
<ConnectedApp />
</Provider>,
</Provider>
);
expect(global.Notification.requestPermission).toHaveBeenCalled();
});
it('requests PollBot for its version', () => {
it("requests PollBot for its version", () => {
renderer.create(
<Provider store={createStore()}>
<ConnectedApp />
</Provider>,
</Provider>
);
expect(global.fetch).toHaveBeenCalledWith(`${SERVER}/__version__`);
});
it('calls requestStatus(version) with the version from the hash', () => {
global.window.location.hash = '#pollbot/firefox/123.0';
it("calls requestStatus(version) with the version from the hash", () => {
global.window.location.hash = "#pollbot/firefox/123.0";
const module = require('./actions');
const module = require("./actions");
module.requestStatus = jest.fn();
const store = createStore();
@ -114,12 +114,12 @@ describe('<App />', () => {
renderer.create(
<Provider store={store}>
<ConnectedApp />
</Provider>,
</Provider>
);
expect(module.requestStatus).toHaveBeenCalledWith('firefox', '123.0');
expect(module.requestStatus).toHaveBeenCalledWith("firefox", "123.0");
});
it('sets up auto-refresh', () => {
const module = require('./actions');
it("sets up auto-refresh", () => {
const module = require("./actions");
module.requestStatus = jest.fn();
module.refreshStatus = jest.fn();
@ -133,7 +133,7 @@ describe('<App />', () => {
// Shouldn't auto-refresh => stop auto refresh.
expect(app.stopAutoRefresh).toHaveBeenCalledTimes(0);
wrapper.setProps({shouldRefresh: false});
wrapper.setProps({ shouldRefresh: false });
expect(app.stopAutoRefresh).toHaveBeenCalledTimes(1);
expect(app.refreshIntervalId).toBeNull();
app.setUpAutoRefresh();
@ -145,7 +145,7 @@ describe('<App />', () => {
// Should auto-refresh => start auto refresh.
expect(app.refreshIntervalId).toBeNull();
wrapper.setProps({shouldRefresh: true});
wrapper.setProps({ shouldRefresh: true });
app.setUpAutoRefresh();
expect(app.stopAutoRefresh).toHaveBeenCalledTimes(2); // Not called again.
expect(setInterval).toHaveBeenCalledTimes(1);
@ -155,7 +155,7 @@ describe('<App />', () => {
expect(module.refreshStatus).toHaveBeenCalledTimes(1);
// Should auto-refresh, but already set up => don't start auto refresh.
wrapper.setProps({shouldRefresh: true});
wrapper.setProps({ shouldRefresh: true });
app.setUpAutoRefresh();
expect(app.stopAutoRefresh).toHaveBeenCalledTimes(2); // Not called again.
expect(setInterval).toHaveBeenCalledTimes(1); // Not called again.
@ -163,7 +163,7 @@ describe('<App />', () => {
expect(module.requestStatus).toHaveBeenCalledTimes(numCalledRequestStatus);
expect(module.refreshStatus).toHaveBeenCalledTimes(1); // Not called again.
});
it('stops auto-refresh', () => {
it("stops auto-refresh", () => {
const app = shallow(<App dispatch={jest.fn()} />).instance();
// Shouldn't call clearInterval if not needed.
@ -176,7 +176,7 @@ describe('<App />', () => {
expect(clearInterval).toHaveBeenCalledWith(123);
expect(app.refreshIntervalId).toBeNull();
});
it('stops the auto-refresh on unmount', () => {
it("stops the auto-refresh on unmount", () => {
const wrapper = shallow(<App dispatch={jest.fn()} />);
const app = wrapper.instance();
app.stopAutoRefresh = jest.fn();
@ -185,63 +185,63 @@ describe('<App />', () => {
});
});
describe('parseUrl', () => {
it('returns null for a non matching url', () => {
expect(parseUrl('')).toBeNull();
expect(parseUrl('#foobar')).toBeNull();
describe("parseUrl", () => {
it("returns null for a non matching url", () => {
expect(parseUrl("")).toBeNull();
expect(parseUrl("#foobar")).toBeNull();
});
it("returns null if the product isn't recognized", () => {
expect(parseUrl('#pollbot/foobar/50.0')).toBeNull();
expect(parseUrl("#pollbot/foobar/50.0")).toBeNull();
});
it('returns the proper structure for a matching url', () => {
expect(parseUrl('#pollbot/firefox/50.0')).toEqual({
service: 'pollbot',
product: 'firefox',
version: '50.0',
it("returns the proper structure for a matching url", () => {
expect(parseUrl("#pollbot/firefox/50.0")).toEqual({
service: "pollbot",
product: "firefox",
version: "50.0"
});
});
});
describe('<ReleasesMenu />', () => {
describe("<ReleasesMenu />", () => {
it("displays a list of channels with spinners if there's no product versions yet", () => {
const wrapper = mount(<ReleasesMenu versions={{}} />);
const textContent = wrapper.text();
expect(textContent).toContain('Nightly');
expect(textContent).toContain('Beta');
expect(textContent).toContain('Devedition');
expect(textContent).toContain('Release');
expect(textContent).toContain('Esr');
expect(textContent).toContain("Nightly");
expect(textContent).toContain("Beta");
expect(textContent).toContain("Devedition");
expect(textContent).toContain("Release");
expect(textContent).toContain("Esr");
});
it("displays a list of channels with version numbers if there's product versions", () => {
const wrapper = mount(
<ReleasesMenu
versions={{
firefox: {
nightly: '60.0a1',
beta: '59.0b4',
release: '58.0',
esr: '52.6.0esr',
nightly: "60.0a1",
beta: "59.0b4",
release: "58.0",
esr: "52.6.0esr"
},
devedition: {
devedition: '59.0b4',
},
devedition: "59.0b4"
}
}}
/>,
/>
);
const textContent = wrapper.text();
expect(textContent).toContain('Nightly: 60.0a1');
expect(textContent).toContain('Beta: 59.0b4');
expect(textContent).toContain('Devedition: 59.0b4');
expect(textContent).toContain('Release: 58.0');
expect(textContent).toContain('Esr: 52.6.0esr');
expect(textContent).toContain("Nightly: 60.0a1");
expect(textContent).toContain("Beta: 59.0b4");
expect(textContent).toContain("Devedition: 59.0b4");
expect(textContent).toContain("Release: 58.0");
expect(textContent).toContain("Esr: 52.6.0esr");
});
});
describe('<Errors />', () => {
it('displays a list of errors', () => {
const wrapper = mount(<Errors errors={[['foo', 'bar']]} />);
describe("<Errors />", () => {
it("displays a list of errors", () => {
const wrapper = mount(<Errors errors={[["foo", "bar"]]} />);
expect(wrapper.text()).toContain(
"Failed getting check result for 'foo': bar",
"Failed getting check result for 'foo': bar"
);
});
it("doesn't return anything if there's no errors", () => {
@ -250,58 +250,58 @@ describe('<Errors />', () => {
});
});
describe('<Dashboard />', () => {
describe("<Dashboard />", () => {
const releaseInfo = {
channel: 'nightly',
product: 'firefox',
version: '50.0',
channel: "nightly",
product: "firefox",
version: "50.0",
checks: [
{url: 'some-url', title: 'some title', actionable: true},
{url: 'some-url-2', title: 'some title 2', actionable: false},
],
{ url: "some-url", title: "some title", actionable: true },
{ url: "some-url-2", title: "some title 2", actionable: false }
]
};
const checkResults = {
'some title': {
status: 'exists',
message: 'check is successful',
link: 'some link',
},
'some title 2': {
status: 'exists',
message: 'check is successful',
link: 'some link',
"some title": {
status: "exists",
message: "check is successful",
link: "some link"
},
"some title 2": {
status: "exists",
message: "check is successful",
link: "some link"
}
};
it('displays a help text when no version is selected', () => {
const wrapper = shallow(<Dashboard productVersion={['firefox', '']} />);
it("displays a help text when no version is selected", () => {
const wrapper = shallow(<Dashboard productVersion={["firefox", ""]} />);
expect(wrapper.text()).toContain(
'Learn more about a specific version. Select a version number from the left menu.',
"Learn more about a specific version. Select a version number from the left menu."
);
});
it('displays a spinner when a version is selected', () => {
const wrapper = shallow(<Dashboard productVersion={['firefox', '50.0']} />);
it("displays a spinner when a version is selected", () => {
const wrapper = shallow(<Dashboard productVersion={["firefox", "50.0"]} />);
expect(wrapper.find(Spin).length).toBe(1);
});
it("displays an error when there's a Pollbot error", () => {
const wrapper = mount(
<Dashboard
productVersion={['firefox', '50.0']}
releaseInfo={{message: 'error from pollbot'}}
/>,
productVersion={["firefox", "50.0"]}
releaseInfo={{ message: "error from pollbot" }}
/>
);
const error = wrapper.find(Errors);
expect(error.length).toBe(1);
expect(error.text()).toEqual(
"Failed getting check result for 'Pollbot error': error from pollbot",
"Failed getting check result for 'Pollbot error': error from pollbot"
);
});
it('displays a list of check results when a release info is present', () => {
it("displays a list of check results when a release info is present", () => {
const wrapper = shallow(
<Dashboard
productVersion={['firefox', '50.0']}
productVersion={["firefox", "50.0"]}
releaseInfo={releaseInfo}
checkResults={checkResults}
/>,
/>
);
expect(wrapper.find(Spin).length).toBe(0);
expect(wrapper.find(DisplayCheckResult).length).toBe(2);
@ -309,106 +309,106 @@ describe('<Dashboard />', () => {
it("displays an extra icon and tooltip on the checks that aren't actionable", () => {
const wrapper = mount(
<Dashboard
productVersion={['firefox', '50.0']}
productVersion={["firefox", "50.0"]}
releaseInfo={releaseInfo}
checkResults={checkResults}
/>,
/>
);
const tooltip = wrapper.find(Tooltip);
expect(tooltip.length).toBe(1);
expect(tooltip.text()).toEqual(' some title 2');
expect(tooltip.prop('title')).toEqual('This check is not actionable');
expect(tooltip.text()).toEqual(" some title 2");
expect(tooltip.prop("title")).toEqual("This check is not actionable");
});
});
describe('<OverallStatus />', () => {
describe("<OverallStatus />", () => {
const releaseInfo = {
channel: 'nightly',
product: 'firefox',
version: '50.0',
channel: "nightly",
product: "firefox",
version: "50.0",
checks: [
{url: 'some-url', title: 'some title', actionable: true},
{url: 'some-url-2', title: 'some title 2', actionable: false},
],
{ url: "some-url", title: "some title", actionable: true },
{ url: "some-url-2", title: "some title 2", actionable: false }
]
};
const incompleteCheckResults = {
'some title': {
status: 'exists',
message: 'check is successful',
link: 'some link',
},
"some title": {
status: "exists",
message: "check is successful",
link: "some link"
}
};
const checkResults = {
'some title': {
status: 'exists',
message: 'check is successful',
link: 'some link',
},
'some title 2': {
status: 'exists',
message: 'check is successful',
link: 'some link',
"some title": {
status: "exists",
message: "check is successful",
link: "some link"
},
"some title 2": {
status: "exists",
message: "check is successful",
link: "some link"
}
};
it('displays a "success" label when all the results are successful', () => {
const wrapper = mount(
<OverallStatus releaseInfo={releaseInfo} checkResults={checkResults} />,
<OverallStatus releaseInfo={releaseInfo} checkResults={checkResults} />
);
const status = wrapper.find(Alert);
expect(status.prop('message')).toEqual('All checks are successful');
expect(status.prop('type')).toEqual('success');
expect(status.prop("message")).toEqual("All checks are successful");
expect(status.prop("type")).toEqual("success");
});
it('displays an "success" label if some non actionable check results are unsuccessful', () => {
const results = Object.assign({}, checkResults, {
'some title 2': Object.assign({}, checkResults['some title 2'], {
status: 'missing',
}),
"some title 2": Object.assign({}, checkResults["some title 2"], {
status: "missing"
})
});
const wrapper = mount(
<OverallStatus releaseInfo={releaseInfo} checkResults={results} />,
<OverallStatus releaseInfo={releaseInfo} checkResults={results} />
);
const status = wrapper.find(Alert);
expect(status.prop('message')).toEqual('All checks are successful');
expect(status.prop('type')).toEqual('success');
expect(status.prop("message")).toEqual("All checks are successful");
expect(status.prop("type")).toEqual("success");
});
it('displays an "error" label if some actionable check results are unsuccessful', () => {
const results = Object.assign({}, checkResults, {
'some title': Object.assign({}, checkResults['some title'], {
status: 'missing',
}),
"some title": Object.assign({}, checkResults["some title"], {
status: "missing"
})
});
const wrapper = mount(
<OverallStatus releaseInfo={releaseInfo} checkResults={results} />,
<OverallStatus releaseInfo={releaseInfo} checkResults={results} />
);
const status = wrapper.find(Alert);
expect(status.prop('message')).toEqual('Some checks failed');
expect(status.prop('type')).toEqual('error');
expect(status.prop("message")).toEqual("Some checks failed");
expect(status.prop("type")).toEqual("error");
});
it('displays an "error" label if some actionable check results are errored', () => {
const results = Object.assign({}, checkResults, {
'some title': Object.assign({}, checkResults['some title'], {
status: 'error',
}),
"some title": Object.assign({}, checkResults["some title"], {
status: "error"
})
});
const wrapper = mount(
<OverallStatus releaseInfo={releaseInfo} checkResults={results} />,
<OverallStatus releaseInfo={releaseInfo} checkResults={results} />
);
const status = wrapper.find(Alert);
expect(status.prop('message')).toEqual('Some checks failed');
expect(status.prop('type')).toEqual('error');
expect(status.prop("message")).toEqual("Some checks failed");
expect(status.prop("type")).toEqual("error");
});
it('displays a spinner for the overall status until all the checks results are received', () => {
it("displays a spinner for the overall status until all the checks results are received", () => {
const wrapper = shallow(
<OverallStatus
releaseInfo={releaseInfo}
checkResults={incompleteCheckResults}
/>,
/>
);
expect(wrapper.find(Spin).length).toBe(1);
});
});
describe('<DisplayStatus />', () => {
describe("<DisplayStatus />", () => {
const checkDisplayStatus = (status, actionable, label) => {
const wrapper = mount(
<DisplayStatus
@ -416,37 +416,37 @@ describe('<DisplayStatus />', () => {
actionable={actionable}
message="check message"
url="check url"
/>,
/>
);
const link = wrapper.find('a');
expect(link.prop('href')).toEqual('check url');
expect(link.prop('title')).toEqual('check message');
const link = wrapper.find("a");
expect(link.prop("href")).toEqual("check url");
expect(link.prop("title")).toEqual("check message");
const alert = wrapper.find(Alert);
expect(alert.prop('type')).toBe(label);
expect(link.text()).toEqual('check message');
expect(alert.prop("type")).toBe(label);
expect(link.text()).toEqual("check message");
};
it('displays the status when the status is exists', () => {
checkDisplayStatus('exists', true, 'success');
it("displays the status when the status is exists", () => {
checkDisplayStatus("exists", true, "success");
});
it('displays the status when the status is incomplete', () => {
checkDisplayStatus('incomplete', true, 'warning');
it("displays the status when the status is incomplete", () => {
checkDisplayStatus("incomplete", true, "warning");
});
it('displays the status when the status is missing', () => {
checkDisplayStatus('missing', true, 'warning');
it("displays the status when the status is missing", () => {
checkDisplayStatus("missing", true, "warning");
});
it('displays the error message when there an error', () => {
checkDisplayStatus('error', true, 'error');
it("displays the error message when there an error", () => {
checkDisplayStatus("error", true, "error");
});
it('displays the status when the status is exists and the item is not actionable', () => {
checkDisplayStatus('exists', false, 'success');
it("displays the status when the status is exists and the item is not actionable", () => {
checkDisplayStatus("exists", false, "success");
});
it('displays the status when the status is incomplete and the item is not actionable', () => {
checkDisplayStatus('incomplete', false, 'info');
it("displays the status when the status is incomplete and the item is not actionable", () => {
checkDisplayStatus("incomplete", false, "info");
});
it('displays the status when the status is missing and the item is not actionable', () => {
checkDisplayStatus('missing', false, 'info');
it("displays the status when the status is missing and the item is not actionable", () => {
checkDisplayStatus("missing", false, "info");
});
it('displays the error message when there an error and the item is not actionable', () => {
checkDisplayStatus('error', false, 'error');
it("displays the error message when there an error and the item is not actionable", () => {
checkDisplayStatus("error", false, "error");
});
});

Просмотреть файл

@ -4,13 +4,13 @@ import type {
CheckResult,
ProductVersions,
Product,
ReleaseInfo,
} from './types';
ReleaseInfo
} from "./types";
export const SERVER = 'https://pollbot.dev.mozaws.net/v1';
export const SERVER = "https://pollbot.dev.mozaws.net/v1";
export async function getOngoingVersions(
product: Product,
product: Product
): Promise<ProductVersions> {
const response = await fetch(`${SERVER}/${product}/ongoing-versions`);
return response.json();
@ -18,7 +18,7 @@ export async function getOngoingVersions(
export async function getReleaseInfo(
product: Product,
version: string,
version: string
): Promise<ReleaseInfo> {
const response = await fetch(`${SERVER}/${product}/${version}`);
return response.json();

Просмотреть файл

@ -4,75 +4,75 @@ import {
checkStatus,
getPollbotVersion,
getOngoingVersions,
getReleaseInfo,
} from './PollbotAPI';
getReleaseInfo
} from "./PollbotAPI";
describe('getOngoingVersions', () => {
it('retrieves the list of ongoing versions', async () => {
const channelVersions = await getOngoingVersions('firefox');
describe("getOngoingVersions", () => {
it("retrieves the list of ongoing versions", async () => {
const channelVersions = await getOngoingVersions("firefox");
expect(channelVersions).toMatchObject({
beta: expect.any(String),
esr: expect.any(String),
nightly: expect.any(String),
release: expect.any(String),
release: expect.any(String)
});
});
});
describe('getReleaseInfo', () => {
it('retrieves the release information for firefox', async () => {
const releaseInfo = await getReleaseInfo('firefox', '50.0');
describe("getReleaseInfo", () => {
it("retrieves the release information for firefox", async () => {
const releaseInfo = await getReleaseInfo("firefox", "50.0");
expect(releaseInfo).toMatchObject({
channel: expect.stringMatching(/nightly|beta|release|esr/),
checks: expect.any(Array),
product: 'firefox',
version: '50.0',
product: "firefox",
version: "50.0"
});
releaseInfo.checks.map(check => {
expect(check).toMatchObject({
title: expect.any(String),
url: expect.any(String),
url: expect.any(String)
});
});
});
it('retrieves the release information for devedition', async () => {
const releaseInfo = await getReleaseInfo('devedition', '59.0b3');
it("retrieves the release information for devedition", async () => {
const releaseInfo = await getReleaseInfo("devedition", "59.0b3");
expect(releaseInfo).toMatchObject({
channel: expect.stringMatching(/nightly|beta|release|esr|aurora/),
checks: expect.any(Array),
product: 'devedition',
version: '59.0b3',
product: "devedition",
version: "59.0b3"
});
releaseInfo.checks.map(check => {
expect(check).toMatchObject({
title: expect.any(String),
url: expect.any(String),
url: expect.any(String)
});
});
});
});
describe('checkStatus', () => {
it('retrieves the status of a given check', async () => {
describe("checkStatus", () => {
it("retrieves the status of a given check", async () => {
const status = await checkStatus(
'https://pollbot.dev.mozaws.net/v1/firefox/50.0/product-details',
"https://pollbot.dev.mozaws.net/v1/firefox/50.0/product-details"
);
expect(status).toEqual({
link: 'https://product-details.mozilla.org/1.0/firefox.json',
status: 'exists',
message: 'We found product-details information about version 50.0',
link: "https://product-details.mozilla.org/1.0/firefox.json",
status: "exists",
message: "We found product-details information about version 50.0"
});
});
});
describe('getPollbotVersion', () => {
it('retrieves the version from Pollbot', async () => {
describe("getPollbotVersion", () => {
it("retrieves the version from Pollbot", async () => {
const version = await getPollbotVersion();
expect(version).toMatchObject({
commit: expect.any(String),
name: 'pollbot',
name: "pollbot",
source: expect.any(String),
version: expect.any(String),
version: expect.any(String)
});
});
});

Просмотреть файл

@ -12,8 +12,8 @@ import {
REQUEST_POLLBOT_VERSION,
UPDATE_URL,
REFRESH_STATUS,
REQUEST_STATUS,
} from './types';
REQUEST_STATUS
} from "./types";
import type {
AddCheckResult,
AddServerError,
@ -31,8 +31,8 @@ import type {
UpdateProductVersions,
UpdatePollbotVersion,
UpdateReleaseInfo,
UpdateUrl,
} from './types';
UpdateUrl
} from "./types";
// Small utility function.
export const localUrlFromVersion = ([product, version]: [Product, string]) =>
@ -43,12 +43,12 @@ export const localUrlFromVersion = ([product, version]: [Product, string]) =>
*/
export function setVersion(product: Product, version: string): SetVersion {
return {type: SET_VERSION, product, version};
return { type: SET_VERSION, product, version };
}
export const sortByVersion = (a: string, b: string) => {
const partsA = a.split('.');
const partsB = b.split('.');
const partsA = a.split(".");
const partsB = b.split(".");
if (partsA.length < 2 || partsB.length < 2) {
// Bogus version, list it last.
return 1;
@ -93,61 +93,61 @@ export const capitalize = (item: string) =>
export const capitalizeChannel = ([channel, version]: [string, string]) => [
capitalize(channel),
version,
version
];
export function updateProductVersions(
product: Product,
versions: VersionsDict,
versions: VersionsDict
): UpdateProductVersions {
return {type: UPDATE_PRODUCT_VERSIONS, product, versions};
return { type: UPDATE_PRODUCT_VERSIONS, product, versions };
}
export function updatePollbotVersion(
version: APIVersionData,
version: APIVersionData
): UpdatePollbotVersion {
return {type: UPDATE_POLLBOT_VERSION, version};
return { type: UPDATE_POLLBOT_VERSION, version };
}
export function updateReleaseInfo(releaseInfo: ReleaseInfo): UpdateReleaseInfo {
return {type: UPDATE_RELEASE_INFO, releaseInfo};
return { type: UPDATE_RELEASE_INFO, releaseInfo };
}
export function addCheckResult(
title: string,
result: CheckResult,
result: CheckResult
): AddCheckResult {
return {type: ADD_CHECK_RESULT, title, result};
return { type: ADD_CHECK_RESULT, title, result };
}
export function refreshCheckResult(title: string): RefreshCheckResult {
return {type: REFRESH_CHECK_RESULT, title};
return { type: REFRESH_CHECK_RESULT, title };
}
export function addServerError(title: string, err: string): AddServerError {
return {type: ADD_SERVER_ERROR, title, err};
return { type: ADD_SERVER_ERROR, title, err };
}
// For sagas
export function requestPollbotVersion(): RequestPollbotVersion {
return {type: REQUEST_POLLBOT_VERSION};
return { type: REQUEST_POLLBOT_VERSION };
}
export function requestOngoingVersions(): RequestOngoingVersions {
return {type: REQUEST_ONGOING_VERSIONS};
return { type: REQUEST_ONGOING_VERSIONS };
}
export function updateUrl(): UpdateUrl {
return {type: UPDATE_URL};
return { type: UPDATE_URL };
}
export function refreshStatus(): RefreshStatus {
return {type: REFRESH_STATUS};
return { type: REFRESH_STATUS };
}
export function requestStatus(
product: Product,
version: string,
version: string
): RequestStatus {
return {type: REQUEST_STATUS, product, version};
return { type: REQUEST_STATUS, product, version };
}

Просмотреть файл

@ -10,8 +10,8 @@ import {
REQUEST_POLLBOT_VERSION,
UPDATE_URL,
REFRESH_STATUS,
REQUEST_STATUS,
} from './types';
REQUEST_STATUS
} from "./types";
import {
addCheckResult,
addServerError,
@ -26,169 +26,169 @@ import {
updateProductVersions,
updatePollbotVersion,
updateReleaseInfo,
updateUrl,
} from './actions';
updateUrl
} from "./actions";
describe('action creators', () => {
it('returns a UPDATE_VERSION_INPUT action for setVersion', () => {
expect(setVersion('firefox', '123')).toEqual({
describe("action creators", () => {
it("returns a UPDATE_VERSION_INPUT action for setVersion", () => {
expect(setVersion("firefox", "123")).toEqual({
type: SET_VERSION,
product: 'firefox',
version: '123',
product: "firefox",
version: "123"
});
});
it('returns a UPDATE_PRODUCT_VERSIONS action for updateProductVersions', () => {
it("returns a UPDATE_PRODUCT_VERSIONS action for updateProductVersions", () => {
const channelVersions = {
nightly: '57.0a1',
beta: '56.0b12',
release: '55.0.3',
esr: '52.3.0esr',
nightly: "57.0a1",
beta: "56.0b12",
release: "55.0.3",
esr: "52.3.0esr"
};
expect(updateProductVersions('firefox', channelVersions)).toEqual({
expect(updateProductVersions("firefox", channelVersions)).toEqual({
type: UPDATE_PRODUCT_VERSIONS,
product: 'firefox',
versions: channelVersions,
product: "firefox",
versions: channelVersions
});
});
it('returns a UPDATE_POLLBOT_VERSION action for updatePollbotVersion', () => {
it("returns a UPDATE_POLLBOT_VERSION action for updatePollbotVersion", () => {
const pollbotVersion = {
name: 'pollbot',
source: 'https://github.com/mozilla/PollBot.git',
version: '0.2.1-22-g8e09a0f',
commit: '8e09a0f8e995344ea24fbb940a6bddc17e0edaed',
name: "pollbot",
source: "https://github.com/mozilla/PollBot.git",
version: "0.2.1-22-g8e09a0f",
commit: "8e09a0f8e995344ea24fbb940a6bddc17e0edaed"
};
expect(updatePollbotVersion(pollbotVersion)).toEqual({
type: UPDATE_POLLBOT_VERSION,
version: pollbotVersion,
version: pollbotVersion
});
});
it('returns a UPDATE_RELEASE_INFO action for updateReleaseInfo', () => {
it("returns a UPDATE_RELEASE_INFO action for updateReleaseInfo", () => {
const releaseInfo = {
product: 'firefox',
product: "firefox",
checks: [
{
title: 'Archive Release',
url: 'https://pollbot.dev.mozaws.net/v1/firefox/55.0.3/archive',
title: "Archive Release",
url: "https://pollbot.dev.mozaws.net/v1/firefox/55.0.3/archive"
},
{
title: 'Balrog update rules',
url: 'https://pollbot.dev.mozaws.net/v1/firefox/55.0.3/balrog-rules',
title: "Balrog update rules",
url: "https://pollbot.dev.mozaws.net/v1/firefox/55.0.3/balrog-rules"
},
{
title: 'Download links',
title: "Download links",
url:
'https://pollbot.dev.mozaws.net/v1/firefox/55.0.3/bedrock/download-links',
"https://pollbot.dev.mozaws.net/v1/firefox/55.0.3/bedrock/download-links"
},
{
title: 'Product details',
title: "Product details",
url:
'https://pollbot.dev.mozaws.net/v1/firefox/55.0.3/product-details',
"https://pollbot.dev.mozaws.net/v1/firefox/55.0.3/product-details"
},
{
title: 'Release notes',
title: "Release notes",
url:
'https://pollbot.dev.mozaws.net/v1/firefox/55.0.3/bedrock/release-notes',
"https://pollbot.dev.mozaws.net/v1/firefox/55.0.3/bedrock/release-notes"
},
{
title: 'Security advisories',
title: "Security advisories",
url:
'https://pollbot.dev.mozaws.net/v1/firefox/55.0.3/bedrock/security-advisories',
},
"https://pollbot.dev.mozaws.net/v1/firefox/55.0.3/bedrock/security-advisories"
}
],
version: '55.0.3',
channel: 'release',
version: "55.0.3",
channel: "release"
};
expect(updateReleaseInfo(releaseInfo)).toEqual({
type: UPDATE_RELEASE_INFO,
releaseInfo: releaseInfo,
releaseInfo: releaseInfo
});
});
it('returns a ADD_CHECK_RESULT action for addCheckResult', () => {
it("returns a ADD_CHECK_RESULT action for addCheckResult", () => {
const checkResult = {
link: 'https://archive.mozilla.org/pub/firefox/releases/55.0.3/',
status: 'exists',
link: "https://archive.mozilla.org/pub/firefox/releases/55.0.3/",
status: "exists",
message:
'The archive exists at https://archive.mozilla.org/pub/firefox/releases/55.0.3/ and all 95 locales are present for all platforms (linux-i686, linux-x86_64, mac, win32, win64)',
"The archive exists at https://archive.mozilla.org/pub/firefox/releases/55.0.3/ and all 95 locales are present for all platforms (linux-i686, linux-x86_64, mac, win32, win64)"
};
expect(addCheckResult('some check', checkResult)).toEqual({
expect(addCheckResult("some check", checkResult)).toEqual({
type: ADD_CHECK_RESULT,
title: 'some check',
result: checkResult,
title: "some check",
result: checkResult
});
});
it('returns a REFRESH_CHECK_RESULT action for refreshCheckResult', () => {
expect(refreshCheckResult('some check')).toEqual({
it("returns a REFRESH_CHECK_RESULT action for refreshCheckResult", () => {
expect(refreshCheckResult("some check")).toEqual({
type: REFRESH_CHECK_RESULT,
title: 'some check',
title: "some check"
});
});
it('returns a ADD_SERVER_ERROR action for addServerError', () => {
expect(addServerError('some check', 'some error')).toEqual({
it("returns a ADD_SERVER_ERROR action for addServerError", () => {
expect(addServerError("some check", "some error")).toEqual({
type: ADD_SERVER_ERROR,
title: 'some check',
err: 'some error',
title: "some check",
err: "some error"
});
});
});
describe('sagas action creator', () => {
it('handles a REQUEST_ONGOING_VERSIONS action for requestOngoingVersions', () => {
describe("sagas action creator", () => {
it("handles a REQUEST_ONGOING_VERSIONS action for requestOngoingVersions", () => {
expect(requestOngoingVersions()).toEqual({
type: REQUEST_ONGOING_VERSIONS,
type: REQUEST_ONGOING_VERSIONS
});
});
it('handles a REQUEST_POLLBOT_VERSION action for requestPollbotVersion', () => {
expect(requestPollbotVersion()).toEqual({type: REQUEST_POLLBOT_VERSION});
it("handles a REQUEST_POLLBOT_VERSION action for requestPollbotVersion", () => {
expect(requestPollbotVersion()).toEqual({ type: REQUEST_POLLBOT_VERSION });
});
it('handles a UPDATE_URL action for updateUrl', () => {
expect(updateUrl()).toEqual({type: UPDATE_URL});
it("handles a UPDATE_URL action for updateUrl", () => {
expect(updateUrl()).toEqual({ type: UPDATE_URL });
});
it('handles a REFRESH_STATUS action for refreshStatus', () => {
expect(refreshStatus()).toEqual({type: REFRESH_STATUS});
it("handles a REFRESH_STATUS action for refreshStatus", () => {
expect(refreshStatus()).toEqual({ type: REFRESH_STATUS });
});
it('handles a REQUEST_STATUS action for requestStatus', () => {
expect(requestStatus('firefox', '50.0')).toEqual({
it("handles a REQUEST_STATUS action for requestStatus", () => {
expect(requestStatus("firefox", "50.0")).toEqual({
type: REQUEST_STATUS,
product: 'firefox',
version: '50.0',
product: "firefox",
version: "50.0"
});
});
});
describe('sortByVersion helper', () => {
it('sorts bogus versions', () => {
expect(sortByVersion('0', '56.0')).toEqual(1);
describe("sortByVersion helper", () => {
it("sorts bogus versions", () => {
expect(sortByVersion("0", "56.0")).toEqual(1);
});
it('sorts equal versions', () => {
expect(sortByVersion('56.0', '56.0')).toEqual(0);
it("sorts equal versions", () => {
expect(sortByVersion("56.0", "56.0")).toEqual(0);
});
it('sorts similar versions', () => {
expect(sortByVersion('56.0', '56.0.1')).toEqual(1);
it("sorts similar versions", () => {
expect(sortByVersion("56.0", "56.0.1")).toEqual(1);
});
it('sorts release versions', () => {
expect(sortByVersion('56.0', '57.0')).toEqual(1);
expect(sortByVersion('56.0.1', '56.0.2')).toEqual(1);
it("sorts release versions", () => {
expect(sortByVersion("56.0", "57.0")).toEqual(1);
expect(sortByVersion("56.0.1", "56.0.2")).toEqual(1);
});
it('sorts release and beta versions', () => {
expect(sortByVersion('56.0b1', '56.0')).toEqual(1);
expect(sortByVersion('56.0', '56.0b1')).toEqual(-1);
it("sorts release and beta versions", () => {
expect(sortByVersion("56.0b1", "56.0")).toEqual(1);
expect(sortByVersion("56.0", "56.0b1")).toEqual(-1);
});
it('sorts alpha and beta versions', () => {
expect(sortByVersion('56.0a1', '56.0b1')).toEqual(1);
it("sorts alpha and beta versions", () => {
expect(sortByVersion("56.0a1", "56.0b1")).toEqual(1);
});
it('sorts beta versions', () => {
expect(sortByVersion('56.0b1', '56.0b2')).toEqual(1);
it("sorts beta versions", () => {
expect(sortByVersion("56.0b1", "56.0b2")).toEqual(1);
});
it('sorts bogus sub versions', () => {
expect(sortByVersion('56.0', '56.a')).toEqual(1);
it("sorts bogus sub versions", () => {
expect(sortByVersion("56.0", "56.a")).toEqual(1);
});
it('sorts random versions', () => {
expect(sortByVersion('55.0.3', '57.0b3')).toBeGreaterThan(0);
it("sorts random versions", () => {
expect(sortByVersion("55.0.3", "57.0b3")).toBeGreaterThan(0);
});
});
describe('capitalizeChannel helper', () => {
it('uppercases the first letter of the channel', () => {
expect(capitalizeChannel(['foo', 'bar'])).toEqual(['Foo', 'bar']);
describe("capitalizeChannel helper", () => {
it("uppercases the first letter of the channel", () => {
expect(capitalizeChannel(["foo", "bar"])).toEqual(["Foo", "bar"]);
});
});

Просмотреть файл

@ -1,12 +1,12 @@
// @flow
import {createStore, applyMiddleware, compose} from 'redux';
import {deliveryDashboard} from './reducers';
import createSagaMiddleware from 'redux-saga';
import thunkMiddleware from 'redux-thunk';
import {createLogger} from 'redux-logger';
import type {Store} from './types';
import {rootSaga} from './sagas';
import { createStore, applyMiddleware, compose } from "redux";
import { deliveryDashboard } from "./reducers";
import createSagaMiddleware from "redux-saga";
import thunkMiddleware from "redux-thunk";
import { createLogger } from "redux-logger";
import type { Store } from "./types";
import { rootSaga } from "./sagas";
/**
* Isolate the store creation into a function, so that it can be used outside of the
@ -27,9 +27,9 @@ export default function initializeStore(): Store {
applyMiddleware(
sagaMiddleware,
thunkMiddleware, // lets us dispatch() functions
loggerMiddleware, // neat middleware that logs actions
),
),
loggerMiddleware // neat middleware that logs actions
)
)
);
sagaMiddleware.run(rootSaga);

Просмотреть файл

@ -1,19 +1,19 @@
// @flow
import ConnectedApp from './App';
import React from 'react';
import ReactDOM from 'react-dom';
import registerServiceWorker from './registerServiceWorker';
import {Provider} from 'react-redux';
import createStore from './create-store';
import ConnectedApp from "./App";
import React from "react";
import ReactDOM from "react-dom";
import registerServiceWorker from "./registerServiceWorker";
import { Provider } from "react-redux";
import createStore from "./create-store";
const root = document && document.getElementById('root');
const root = document && document.getElementById("root");
if (root) {
ReactDOM.render(
<Provider store={createStore()}>
<ConnectedApp />
</Provider>,
root,
root
);
registerServiceWorker();
}

Просмотреть файл

@ -7,23 +7,23 @@ import {
SET_VERSION,
UPDATE_PRODUCT_VERSIONS,
UPDATE_POLLBOT_VERSION,
UPDATE_RELEASE_INFO,
} from './types';
import type {Action, State} from './types';
UPDATE_RELEASE_INFO
} from "./types";
import type { Action, State } from "./types";
export const initialState: State = {
version: ['firefox', ''],
productVersions: {firefox: {}, devedition: {}},
version: ["firefox", ""],
productVersions: { firefox: {}, devedition: {} },
releaseInfo: null,
checkResults: {},
pollbotVersion: null,
shouldRefresh: false,
errors: [],
errors: []
};
export function deliveryDashboard(
state: State = initialState,
action: Action,
action: Action
): State {
let errors;
let updatedCheckResults;
@ -31,44 +31,44 @@ export function deliveryDashboard(
case ADD_CHECK_RESULT:
return Object.assign({}, state, {
checkResults: Object.assign({}, state.checkResults, {
[action.title]: action.result,
[action.title]: action.result
}),
shouldRefresh:
action.result.status !== 'exists' ? true : state.shouldRefresh,
action.result.status !== "exists" ? true : state.shouldRefresh
});
case REFRESH_CHECK_RESULT:
updatedCheckResults = Object.assign({}, state.checkResults);
delete updatedCheckResults[action.title];
return Object.assign({}, state, {
checkResults: updatedCheckResults,
checkResults: updatedCheckResults
});
case ADD_SERVER_ERROR:
errors = state.errors.slice();
errors.push([action.title, action.err]);
return Object.assign({}, state, {
errors: errors,
shouldRefresh: true,
shouldRefresh: true
});
case SET_VERSION:
return Object.assign({}, state, {
version: [action.product, action.version],
checkResults: {},
shouldRefresh: false,
errors: [],
errors: []
});
case UPDATE_PRODUCT_VERSIONS:
return Object.assign({}, state, {
productVersions: Object.assign({}, state.productVersions, {
[action.product]: action.versions,
}),
[action.product]: action.versions
})
});
case UPDATE_RELEASE_INFO:
return Object.assign({}, state, {
releaseInfo: action.releaseInfo,
releaseInfo: action.releaseInfo
});
case UPDATE_POLLBOT_VERSION:
return Object.assign({}, state, {
pollbotVersion: action.version,
pollbotVersion: action.version
});
default:
return state;

Просмотреть файл

@ -5,263 +5,267 @@ import {
SET_VERSION,
UPDATE_PRODUCT_VERSIONS,
UPDATE_POLLBOT_VERSION,
UPDATE_RELEASE_INFO,
} from './types';
import {deliveryDashboard, initialState} from './reducers';
UPDATE_RELEASE_INFO
} from "./types";
import { deliveryDashboard, initialState } from "./reducers";
const stateWith = stateCrumbs => Object.assign({}, initialState, stateCrumbs);
describe('deliveryDashboard reducer', () => {
it('returns the initial state', () => {
describe("deliveryDashboard reducer", () => {
it("returns the initial state", () => {
expect(deliveryDashboard(undefined, {})).toEqual(initialState);
});
it('handles ADD_CHECK_RESULT', () => {
it("handles ADD_CHECK_RESULT", () => {
const checkResult = {
status: 'exists',
message: 'successful test',
link: 'some url',
status: "exists",
message: "successful test",
link: "some url"
};
expect(
deliveryDashboard(undefined, {
type: ADD_CHECK_RESULT,
title: 'some test',
result: checkResult,
}),
title: "some test",
result: checkResult
})
).toEqual(
stateWith({
checkResults: {
'some test': checkResult,
"some test": checkResult
},
shouldRefresh: false,
}),
shouldRefresh: false
})
);
const otherCheckResult = {
status: 'exists',
message: 'successful test',
link: 'some url',
status: "exists",
message: "successful test",
link: "some url"
};
expect(
deliveryDashboard(
stateWith({
checkResults: {
'some test': checkResult,
},
"some test": checkResult
}
}),
{
type: ADD_CHECK_RESULT,
title: 'some other test',
result: otherCheckResult,
},
),
title: "some other test",
result: otherCheckResult
}
)
).toEqual(
stateWith({
checkResults: {
'some test': checkResult,
'some other test': otherCheckResult,
"some test": checkResult,
"some other test": otherCheckResult
},
shouldRefresh: false,
}),
shouldRefresh: false
})
);
const failingCheckResult = {
status: 'incomplete',
message: 'successful test',
link: 'some url',
status: "incomplete",
message: "successful test",
link: "some url"
};
expect(
deliveryDashboard(
stateWith({
checkResults: {
'some test': checkResult,
},
"some test": checkResult
}
}),
{
type: ADD_CHECK_RESULT,
title: 'some other test',
result: failingCheckResult,
},
),
title: "some other test",
result: failingCheckResult
}
)
).toEqual(
stateWith({
checkResults: {
'some test': checkResult,
'some other test': failingCheckResult,
"some test": checkResult,
"some other test": failingCheckResult
},
shouldRefresh: true,
}),
shouldRefresh: true
})
);
});
it('handles REFRESH_CHECK_RESULT', () => {
it("handles REFRESH_CHECK_RESULT", () => {
const checkResult = {
status: 'exists',
message: 'successful test',
link: 'some url',
status: "exists",
message: "successful test",
link: "some url"
};
expect(
deliveryDashboard(undefined, {
type: REFRESH_CHECK_RESULT,
title: 'some test',
}),
title: "some test"
})
).toEqual(
stateWith({
checkResults: {},
}),
checkResults: {}
})
);
const failingCheckResult = {
status: 'incomplete',
message: 'successful test',
link: 'some url',
status: "incomplete",
message: "successful test",
link: "some url"
};
expect(
deliveryDashboard(
stateWith({
checkResults: {
'some test': checkResult,
'some other test': failingCheckResult,
},
"some test": checkResult,
"some other test": failingCheckResult
}
}),
{
type: REFRESH_CHECK_RESULT,
title: 'some other test',
result: failingCheckResult,
},
),
title: "some other test",
result: failingCheckResult
}
)
).toEqual(
stateWith({
checkResults: {
'some test': checkResult,
},
}),
"some test": checkResult
}
})
);
});
it('handles ADD_SERVER_ERROR', () => {
it("handles ADD_SERVER_ERROR", () => {
expect(
deliveryDashboard(undefined, {
type: ADD_SERVER_ERROR,
title: 'some check',
err: 'some error',
}),
title: "some check",
err: "some error"
})
).toEqual(
stateWith({errors: [['some check', 'some error']], shouldRefresh: true}),
stateWith({ errors: [["some check", "some error"]], shouldRefresh: true })
);
expect(
deliveryDashboard(
stateWith({
errors: [['some check', 'some error']],
errors: [["some check", "some error"]]
}),
{
type: ADD_SERVER_ERROR,
title: 'some other check',
err: 'some other error',
},
),
title: "some other check",
err: "some other error"
}
)
).toEqual(
stateWith({
errors: [
['some check', 'some error'],
['some other check', 'some other error'],
["some check", "some error"],
["some other check", "some other error"]
],
shouldRefresh: true,
}),
shouldRefresh: true
})
);
});
it('handles SET_VERSION', () => {
it("handles SET_VERSION", () => {
expect(
deliveryDashboard(undefined, {
type: SET_VERSION,
product: 'firefox',
version: '50.0',
}),
).toEqual(stateWith({version: ['firefox', '50.0'], shouldRefresh: false}));
product: "firefox",
version: "50.0"
})
).toEqual(
stateWith({ version: ["firefox", "50.0"], shouldRefresh: false })
);
expect(
deliveryDashboard(
stateWith({
version: ['firefox', '50.0'],
version: ["firefox", "50.0"]
}),
{
type: SET_VERSION,
product: 'firefox',
version: '51.0',
},
),
).toEqual(stateWith({version: ['firefox', '51.0'], shouldRefresh: false}));
product: "firefox",
version: "51.0"
}
)
).toEqual(
stateWith({ version: ["firefox", "51.0"], shouldRefresh: false })
);
});
it('handles UPDATE_PRODUCT_VERSIONS', () => {
it("handles UPDATE_PRODUCT_VERSIONS", () => {
expect(
deliveryDashboard(undefined, {
type: UPDATE_PRODUCT_VERSIONS,
product: 'firefox',
versions: {release: '0.1.2'},
}),
product: "firefox",
versions: { release: "0.1.2" }
})
).toEqual(
stateWith({
productVersions: {
firefox: {release: '0.1.2'},
devedition: {},
},
}),
firefox: { release: "0.1.2" },
devedition: {}
}
})
);
expect(
deliveryDashboard(
stateWith({
productVersions: {
firefox: {release: '0.1.2'},
devedition: {},
},
firefox: { release: "0.1.2" },
devedition: {}
}
}),
{
type: UPDATE_PRODUCT_VERSIONS,
product: 'firefox',
versions: {nightly: '1.2.3', release: '0.1.3'},
},
),
product: "firefox",
versions: { nightly: "1.2.3", release: "0.1.3" }
}
)
).toEqual(
stateWith({
productVersions: {
firefox: {nightly: '1.2.3', release: '0.1.3'},
devedition: {},
},
}),
firefox: { nightly: "1.2.3", release: "0.1.3" },
devedition: {}
}
})
);
});
it('handles UPDATE_RELEASE_INFO', () => {
it("handles UPDATE_RELEASE_INFO", () => {
expect(
deliveryDashboard(undefined, {
type: UPDATE_RELEASE_INFO,
releaseInfo: 'some new release info',
}),
).toEqual(stateWith({releaseInfo: 'some new release info'}));
releaseInfo: "some new release info"
})
).toEqual(stateWith({ releaseInfo: "some new release info" }));
expect(
deliveryDashboard(
stateWith({
releaseInfo: 'some release info',
releaseInfo: "some release info"
}),
{
type: UPDATE_RELEASE_INFO,
releaseInfo: 'some new release info',
},
),
).toEqual(stateWith({releaseInfo: 'some new release info'}));
releaseInfo: "some new release info"
}
)
).toEqual(stateWith({ releaseInfo: "some new release info" }));
});
it('handles UPDATE_POLLBOT_VERSION', () => {
it("handles UPDATE_POLLBOT_VERSION", () => {
expect(
deliveryDashboard(undefined, {
type: UPDATE_POLLBOT_VERSION,
version: 'some new pollbot version',
}),
).toEqual(stateWith({pollbotVersion: 'some new pollbot version'}));
version: "some new pollbot version"
})
).toEqual(stateWith({ pollbotVersion: "some new pollbot version" }));
expect(
deliveryDashboard(
stateWith({
pollbotVersion: 'some pollbot version',
pollbotVersion: "some pollbot version"
}),
{
type: UPDATE_POLLBOT_VERSION,
version: 'some new pollbot version',
},
),
).toEqual(stateWith({pollbotVersion: 'some new pollbot version'}));
version: "some new pollbot version"
}
)
).toEqual(stateWith({ pollbotVersion: "some new pollbot version" }));
});
});

Просмотреть файл

@ -10,26 +10,26 @@
// This link also includes instructions on opting out of this behavior.
const isLocalhost: boolean = Boolean(
window.location.hostname === 'localhost' ||
window.location.hostname === "localhost" ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
window.location.hostname === "[::1]" ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/,
),
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export default function register() {
if (
process.env.NODE_ENV === 'production' &&
'serviceWorker' in navigator &&
process.env.NODE_ENV === "production" &&
"serviceWorker" in navigator &&
process.env.PUBLIC_URL &&
navigator.serviceWorker
) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(
process.env.PUBLIC_URL,
window.location.toString(),
window.location.toString()
);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
@ -38,7 +38,7 @@ export default function register() {
return;
}
window.addEventListener('load', () => {
window.addEventListener("load", () => {
if (process.env.PUBLIC_URL) {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
@ -62,7 +62,7 @@ function registerValidSW(swUrl) {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (installingWorker.state === "installed") {
if (
navigator.serviceWorker &&
navigator.serviceWorker.controller
@ -71,19 +71,19 @@ function registerValidSW(swUrl) {
// the fresh content will have been added to the cache.
// It's the perfect time to display a "New content is
// available; please refresh." message in your web app.
console.log('New content is available; please refresh.');
console.log("New content is available; please refresh.");
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
console.log("Content is cached for offline use.");
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
console.error("Error during service worker registration:", error);
});
}
@ -94,7 +94,7 @@ function checkValidServiceWorker(swUrl) {
// Ensure service worker exists, and that we really are getting a JS file.
if (
response.status === 404 ||
response.headers.get('content-type').indexOf('javascript') === -1
response.headers.get("content-type").indexOf("javascript") === -1
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker &&
@ -110,13 +110,13 @@ function checkValidServiceWorker(swUrl) {
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.',
"No internet connection found. App is running in offline mode."
);
});
}
export function unregister() {
if ('serviceWorker' in navigator && navigator.serviceWorker) {
if ("serviceWorker" in navigator && navigator.serviceWorker) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});

Просмотреть файл

@ -6,8 +6,8 @@ import {
UPDATE_URL,
REFRESH_STATUS,
REQUEST_STATUS,
products,
} from './types';
products
} from "./types";
import type {
APIVersionData,
CheckResult,
@ -15,15 +15,15 @@ import type {
Product,
ReleaseInfo,
RequestStatus,
State,
} from './types';
import {all, call, put, select, takeEvery} from 'redux-saga/effects';
State
} from "./types";
import { all, call, put, select, takeEvery } from "redux-saga/effects";
import {
checkStatus,
getOngoingVersions,
getPollbotVersion,
getReleaseInfo,
} from './PollbotAPI';
getReleaseInfo
} from "./PollbotAPI";
import {
addCheckResult,
addServerError,
@ -32,8 +32,8 @@ import {
setVersion,
updateProductVersions,
updatePollbotVersion,
updateReleaseInfo,
} from './actions';
updateReleaseInfo
} from "./actions";
type Saga = Generator<*, void, *>;
@ -43,7 +43,7 @@ export function* fetchPollbotVersion(): Saga {
const version: APIVersionData = yield call(getPollbotVersion);
yield put(updatePollbotVersion(version));
} catch (err) {
console.error('Failed getting the pollbot version', err);
console.error("Failed getting the pollbot version", err);
}
}
@ -53,8 +53,8 @@ export function* fetchAndUpdateVersions(product: Product): Saga {
yield put(updateProductVersions(product, versions));
} catch (err) {
console.error(
'Failed getting the latest channel versions for product: ' + product,
err,
"Failed getting the latest channel versions for product: " + product,
err
);
}
}
@ -73,10 +73,10 @@ export function* updateUrl(): Saga {
export function* checkResultAndUpdateAndNotify(
title: string,
url: string,
prevResult: CheckResult,
prevResult: CheckResult
): Saga {
const notifyChanges = (checkTitle, status) => {
if (Notification.permission === 'granted') {
if (Notification.permission === "granted") {
new Notification(`${checkTitle}: status changed (${status}).`);
}
};
@ -100,10 +100,10 @@ export function* refreshStatus(): Saga {
yield all(
state.releaseInfo.checks
// only refresh checks that were failing.
.filter(({title}) => state.checkResults[title].status !== 'exists')
.map(({url, title}) =>
call(checkResultAndUpdateAndNotify, title, url, prevResults[title]),
),
.filter(({ title }) => state.checkResults[title].status !== "exists")
.map(({ url, title }) =>
call(checkResultAndUpdateAndNotify, title, url, prevResults[title])
)
);
}
}
@ -120,8 +120,8 @@ export function* checkResultAndUpdate(title: string, url: string): Saga {
// Requesting a status for a new version.
export function* requestStatus(action: RequestStatus): Saga {
let {product, version} = action;
let {productVersions} = yield select();
let { product, version } = action;
let { productVersions } = yield select();
try {
if (
Object.keys(productVersions).length === 0 ||
@ -132,7 +132,7 @@ export function* requestStatus(action: RequestStatus): Saga {
const versions = yield call(getOngoingVersions, product);
yield put(updateProductVersions(product, versions));
// We now have the product channel versions.
({productVersions} = yield select());
({ productVersions } = yield select());
}
if (productVersions[product].hasOwnProperty(version)) {
version = productVersions[product][version];
@ -142,18 +142,18 @@ export function* requestStatus(action: RequestStatus): Saga {
const releaseInfo: ReleaseInfo = yield call(
getReleaseInfo,
product,
version,
version
);
yield put(updateReleaseInfo(releaseInfo));
yield all(
releaseInfo.checks.map(({url, title}) =>
call(checkResultAndUpdate, title, url),
),
releaseInfo.checks.map(({ url, title }) =>
call(checkResultAndUpdate, title, url)
)
);
} catch (err) {
console.error(
`Failed getting the release info for ${product} ${version}`,
err,
err
);
}
}
@ -165,6 +165,6 @@ export function* rootSaga(): Saga {
takeEvery(REQUEST_POLLBOT_VERSION, fetchPollbotVersion),
takeEvery(UPDATE_URL, updateUrl),
takeEvery(REFRESH_STATUS, refreshStatus),
takeEvery(REQUEST_STATUS, requestStatus),
takeEvery(REQUEST_STATUS, requestStatus)
]);
}

Просмотреть файл

@ -1,11 +1,11 @@
import {all, call, put, select, takeEvery} from 'redux-saga/effects';
import {cloneableGenerator} from 'redux-saga/utils';
import { all, call, put, select, takeEvery } from "redux-saga/effects";
import { cloneableGenerator } from "redux-saga/utils";
import {
checkStatus,
getOngoingVersions,
getPollbotVersion,
getReleaseInfo,
} from './PollbotAPI';
getReleaseInfo
} from "./PollbotAPI";
import {
addCheckResult,
addServerError,
@ -13,15 +13,15 @@ import {
setVersion,
updateProductVersions,
updatePollbotVersion,
updateReleaseInfo,
} from './actions';
updateReleaseInfo
} from "./actions";
import {
REFRESH_STATUS,
REQUEST_ONGOING_VERSIONS,
REQUEST_POLLBOT_VERSION,
REQUEST_STATUS,
UPDATE_URL,
} from './types';
UPDATE_URL
} from "./types";
import {
checkResultAndUpdate,
checkResultAndUpdateAndNotify,
@ -31,19 +31,19 @@ import {
refreshStatus,
requestStatus,
rootSaga,
updateUrl,
} from './sagas';
updateUrl
} from "./sagas";
describe('sagas', () => {
it('handles fetchPollbotVersion', () => {
describe("sagas", () => {
it("handles fetchPollbotVersion", () => {
const data = {};
data.saga = cloneableGenerator(fetchPollbotVersion)();
const pollbotVersion = {
name: 'pollbot',
source: 'https://github.com/mozilla/PollBot.git',
version: '0.2.1-22-g8e09a0f',
commit: '8e09a0f8e995344ea24fbb940a6bddc17e0edaed',
name: "pollbot",
source: "https://github.com/mozilla/PollBot.git",
version: "0.2.1-22-g8e09a0f",
commit: "8e09a0f8e995344ea24fbb940a6bddc17e0edaed"
};
expect(data.saga.next().value).toEqual(call(getPollbotVersion));
@ -51,99 +51,99 @@ describe('sagas', () => {
data.sagaThrow = data.saga.clone();
expect(data.saga.next(pollbotVersion).value).toEqual(
put(updatePollbotVersion(pollbotVersion)),
put(updatePollbotVersion(pollbotVersion))
);
expect(data.saga.next().done).toBe(true);
console.error = jest.fn();
data.sagaThrow.throw('error');
data.sagaThrow.throw("error");
expect(console.error).toHaveBeenCalledWith(
'Failed getting the pollbot version',
'error',
"Failed getting the pollbot version",
"error"
);
expect(data.sagaThrow.next().done).toBe(true);
});
it('handles fetchOngoingVersions', () => {
it("handles fetchOngoingVersions", () => {
const data = {};
data.saga = cloneableGenerator(fetchOngoingVersions)();
expect(data.saga.next().value).toEqual(
all([
call(fetchAndUpdateVersions, 'firefox'),
call(fetchAndUpdateVersions, 'devedition'),
]),
call(fetchAndUpdateVersions, "firefox"),
call(fetchAndUpdateVersions, "devedition")
])
);
expect(data.saga.next().done).toBe(true);
});
it('handles fetchAndUpdateVersions', () => {
it("handles fetchAndUpdateVersions", () => {
const data = {};
data.saga = cloneableGenerator(fetchAndUpdateVersions)('firefox');
data.saga = cloneableGenerator(fetchAndUpdateVersions)("firefox");
const channelVersions = {
nightly: '57.0a1',
beta: '56.0b12',
release: '55.0.3',
esr: '52.3.0esr',
nightly: "57.0a1",
beta: "56.0b12",
release: "55.0.3",
esr: "52.3.0esr"
};
expect(data.saga.next().value).toEqual(call(getOngoingVersions, 'firefox'));
expect(data.saga.next().value).toEqual(call(getOngoingVersions, "firefox"));
// Clone to test success and failure of getOngoingVersions.
data.sagaThrow = data.saga.clone();
expect(data.saga.next(channelVersions).value).toEqual(
put(updateProductVersions('firefox', channelVersions)),
put(updateProductVersions("firefox", channelVersions))
);
expect(data.saga.next().done).toBe(true);
console.error = jest.fn();
data.sagaThrow.throw('error');
data.sagaThrow.throw("error");
expect(console.error).toHaveBeenCalledWith(
'Failed getting the latest channel versions for product: firefox',
'error',
"Failed getting the latest channel versions for product: firefox",
"error"
);
expect(data.sagaThrow.next().done).toBe(true);
});
it('handles updateUrl', () => {
it("handles updateUrl", () => {
const saga = updateUrl();
expect(saga.next().value).toEqual(select());
expect(window.location.hash).not.toEqual('#pollbot/firefox/50.0');
saga.next({version: ['firefox', '50.0']});
expect(window.location.hash).toEqual('#pollbot/firefox/50.0');
expect(window.location.hash).not.toEqual("#pollbot/firefox/50.0");
saga.next({ version: ["firefox", "50.0"] });
expect(window.location.hash).toEqual("#pollbot/firefox/50.0");
});
it('notifies using checkResultAndUpdateAndNotify', () => {
it("notifies using checkResultAndUpdateAndNotify", () => {
// Mock the Notification API call.
global.Notification = jest.fn();
global.Notification.permission = 'granted';
global.Notification.permission = "granted";
const checkResult = {
status: 'exists',
message: 'check succesful',
link: 'some link',
status: "exists",
message: "check succesful",
link: "some link"
};
const checkResultFailing = {
status: 'incomplete',
message: 'check incomplete',
link: 'some link',
status: "incomplete",
message: "check incomplete",
link: "some link"
};
const data = {};
data.saga = cloneableGenerator(checkResultAndUpdateAndNotify)(
'some test',
'some url',
checkResultFailing,
"some test",
"some url",
checkResultFailing
);
expect(data.saga.next().value).toEqual(
put(refreshCheckResult('some test')),
put(refreshCheckResult("some test"))
);
expect(data.saga.next().value).toEqual(
call(checkResultAndUpdate, 'some test', 'some url'),
call(checkResultAndUpdate, "some test", "some url")
);
expect(data.saga.next().value).toEqual(select());
@ -153,133 +153,133 @@ describe('sagas', () => {
// No notification if the result hasn't changed.
expect(
data.sagaResultUnchanged.next({
checkResults: {'some test': checkResultFailing},
}).done,
checkResults: { "some test": checkResultFailing }
}).done
).toBe(true);
expect(global.Notification).toHaveBeenCalledTimes(0);
// Notify if the result has changed.
expect(
data.saga.next({
checkResults: {'some test': checkResult},
}).done,
checkResults: { "some test": checkResult }
}).done
).toBe(true);
expect(global.Notification).toHaveBeenCalledTimes(1);
expect(global.Notification).toHaveBeenCalledWith(
'some test: status changed (exists).',
"some test: status changed (exists)."
);
});
it('handles refreshStatus', () => {
it("handles refreshStatus", () => {
const data = {};
data.saga = cloneableGenerator(refreshStatus)();
const releaseInfo = {
channel: 'release',
product: 'firefox',
version: '50.0',
channel: "release",
product: "firefox",
version: "50.0",
checks: [
{
title: 'some test',
url: 'some url',
title: "some test",
url: "some url"
},
{
title: 'some other test',
url: 'some other url',
},
],
title: "some other test",
url: "some other url"
}
]
};
const checkResult = {
status: 'exists',
message: 'check succesful',
link: 'some link',
status: "exists",
message: "check succesful",
link: "some link"
};
const checkResultFailing = {
status: 'incomplete',
message: 'check incomplete',
link: 'some link',
status: "incomplete",
message: "check incomplete",
link: "some link"
};
expect(data.saga.next().value).toEqual(select());
expect(
data.saga.next({
version: '50.0',
version: "50.0",
releaseInfo: releaseInfo,
checkResults: {
'some test': checkResult,
'some other test': checkResultFailing,
},
}).value,
"some test": checkResult,
"some other test": checkResultFailing
}
}).value
).toEqual(
all([
call(
checkResultAndUpdateAndNotify,
'some other test',
'some other url',
checkResultFailing,
),
]),
"some other test",
"some other url",
checkResultFailing
)
])
);
expect(data.saga.next().done).toBe(true);
});
it('checks result and updates state using checkResultAndUpdate', () => {
it("checks result and updates state using checkResultAndUpdate", () => {
const data = {};
data.saga = cloneableGenerator(checkResultAndUpdate)(
'some test',
'some url',
"some test",
"some url"
);
const checkResult = {
status: 'exists',
message: 'check succesful',
link: 'some link',
status: "exists",
message: "check succesful",
link: "some link"
};
expect(data.saga.next().value).toEqual(call(checkStatus, 'some url'));
expect(data.saga.next().value).toEqual(call(checkStatus, "some url"));
// Clone to test success and failure of checkStatus.
data.sagaThrow = data.saga.clone();
// checkStatus throws an error.
console.error = jest.fn();
expect(data.sagaThrow.throw('error').value).toEqual(
put(addServerError('some test', 'error')),
expect(data.sagaThrow.throw("error").value).toEqual(
put(addServerError("some test", "error"))
);
expect(console.error).toHaveBeenCalledWith(
'Failed getting some test check result',
'error',
"Failed getting some test check result",
"error"
);
expect(data.sagaThrow.next().done).toBe(true);
// checkStatus completes correctly.
expect(data.saga.next(checkResult).value).toEqual(
put(addCheckResult('some test', checkResult)),
put(addCheckResult("some test", checkResult))
);
});
it('handles requestStatus', () => {
it("handles requestStatus", () => {
const data = {};
data.saga = cloneableGenerator(requestStatus)({
product: 'firefox',
version: '50.0',
product: "firefox",
version: "50.0"
});
const releaseInfo = {
channel: 'release',
product: 'firefox',
version: '50.0',
channel: "release",
product: "firefox",
version: "50.0",
checks: [
{
title: 'some test',
url: 'some url',
title: "some test",
url: "some url"
},
{
title: 'some other test',
url: 'some other url',
},
],
title: "some other test",
url: "some other url"
}
]
};
expect(data.saga.next().value).toEqual(select());
@ -287,14 +287,14 @@ describe('sagas', () => {
data.saga.next({
productVersions: {
firefox: {
release: '50.0',
},
},
}).value,
).toEqual(put(setVersion('firefox', '50.0')));
release: "50.0"
}
}
}).value
).toEqual(put(setVersion("firefox", "50.0")));
expect(data.saga.next().value).toEqual(call(updateUrl));
expect(data.saga.next().value).toEqual(
call(getReleaseInfo, 'firefox', '50.0'),
call(getReleaseInfo, "firefox", "50.0")
);
// Clone to test success and failure of getReleaseInfo.
@ -302,48 +302,48 @@ describe('sagas', () => {
// getReleaseInfo throws an error.
console.error = jest.fn();
data.sagaThrow.throw('error');
data.sagaThrow.throw("error");
expect(console.error).toHaveBeenCalledWith(
'Failed getting the release info for firefox 50.0',
'error',
"Failed getting the release info for firefox 50.0",
"error"
);
expect(data.sagaThrow.next().done).toBe(true);
// getReleaseInfo completes correctly.
expect(data.saga.next(releaseInfo).value).toEqual(
put(updateReleaseInfo(releaseInfo)),
put(updateReleaseInfo(releaseInfo))
);
expect(data.saga.next().value).toEqual(
all([
call(checkResultAndUpdate, 'some test', 'some url'),
call(checkResultAndUpdate, 'some other test', 'some other url'),
]),
call(checkResultAndUpdate, "some test", "some url"),
call(checkResultAndUpdate, "some other test", "some other url")
])
);
expect(data.saga.next().done).toBe(true);
});
it('handles requestStatus with a canonical url (using the channel)', () => {
it("handles requestStatus with a canonical url (using the channel)", () => {
const data = {};
// Request status for "release", it should in turn set version for "50.0".
data.saga = cloneableGenerator(requestStatus)({
product: 'firefox',
version: 'release',
product: "firefox",
version: "release"
});
const releaseInfo = {
channel: 'release',
product: 'firefox',
version: '50.0',
channel: "release",
product: "firefox",
version: "50.0",
checks: [
{
title: 'some test',
url: 'some url',
title: "some test",
url: "some url"
},
{
title: 'some other test',
url: 'some other url',
},
],
title: "some other test",
url: "some other url"
}
]
};
expect(data.saga.next().value).toEqual(select());
@ -351,14 +351,14 @@ describe('sagas', () => {
data.saga.next({
productVersions: {
firefox: {
release: '50.0',
},
},
}).value,
).toEqual(put(setVersion('firefox', '50.0')));
release: "50.0"
}
}
}).value
).toEqual(put(setVersion("firefox", "50.0")));
expect(data.saga.next().value).toEqual(call(updateUrl));
expect(data.saga.next().value).toEqual(
call(getReleaseInfo, 'firefox', '50.0'),
call(getReleaseInfo, "firefox", "50.0")
);
// Clone to test success and failure of getReleaseInfo.
@ -366,67 +366,67 @@ describe('sagas', () => {
// getReleaseInfo throws an error.
console.error = jest.fn();
data.sagaThrow.throw('error');
data.sagaThrow.throw("error");
expect(console.error).toHaveBeenCalledWith(
'Failed getting the release info for firefox 50.0',
'error',
"Failed getting the release info for firefox 50.0",
"error"
);
expect(data.sagaThrow.next().done).toBe(true);
// getReleaseInfo completes correctly.
expect(data.saga.next(releaseInfo).value).toEqual(
put(updateReleaseInfo(releaseInfo)),
put(updateReleaseInfo(releaseInfo))
);
expect(data.saga.next().value).toEqual(
all([
call(checkResultAndUpdate, 'some test', 'some url'),
call(checkResultAndUpdate, 'some other test', 'some other url'),
]),
call(checkResultAndUpdate, "some test", "some url"),
call(checkResultAndUpdate, "some other test", "some other url")
])
);
expect(data.saga.next().done).toBe(true);
});
it('handles requestStatus with a canonical url (using the channel) with a cold cache', () => {
it("handles requestStatus with a canonical url (using the channel) with a cold cache", () => {
const data = {};
// Request status for "release", it should in turn set version for "50.0".
data.saga = cloneableGenerator(requestStatus)({
product: 'firefox',
version: 'release',
product: "firefox",
version: "release"
});
const releaseInfo = {
channel: 'release',
product: 'firefox',
version: '50.0',
channel: "release",
product: "firefox",
version: "50.0",
checks: [
{
title: 'some test',
url: 'some url',
title: "some test",
url: "some url"
},
{
title: 'some other test',
url: 'some other url',
},
],
title: "some other test",
url: "some other url"
}
]
};
expect(data.saga.next().value).toEqual(select());
expect(data.saga.next({productVersions: {}}).value).toEqual(
call(getOngoingVersions, 'firefox'),
expect(data.saga.next({ productVersions: {} }).value).toEqual(
call(getOngoingVersions, "firefox")
);
expect(data.saga.next({release: '50.0'}).value).toEqual(
put(updateProductVersions('firefox', {release: '50.0'})),
expect(data.saga.next({ release: "50.0" }).value).toEqual(
put(updateProductVersions("firefox", { release: "50.0" }))
);
expect(data.saga.next().value).toEqual(select());
expect(
data.saga.next({
productVersions: {firefox: {release: '50.0'}},
}).value,
).toEqual(put(setVersion('firefox', '50.0')));
productVersions: { firefox: { release: "50.0" } }
}).value
).toEqual(put(setVersion("firefox", "50.0")));
expect(data.saga.next().value).toEqual(call(updateUrl));
expect(data.saga.next().value).toEqual(
call(getReleaseInfo, 'firefox', '50.0'),
call(getReleaseInfo, "firefox", "50.0")
);
// Clone to test success and failure of getReleaseInfo.
@ -434,29 +434,29 @@ describe('sagas', () => {
// getReleaseInfo throws an error.
console.error = jest.fn();
data.sagaThrow.throw('error');
data.sagaThrow.throw("error");
expect(console.error).toHaveBeenCalledWith(
'Failed getting the release info for firefox 50.0',
'error',
"Failed getting the release info for firefox 50.0",
"error"
);
expect(data.sagaThrow.next().done).toBe(true);
// getReleaseInfo completes correctly.
expect(data.saga.next(releaseInfo).value).toEqual(
put(updateReleaseInfo(releaseInfo)),
put(updateReleaseInfo(releaseInfo))
);
expect(data.saga.next().value).toEqual(
all([
call(checkResultAndUpdate, 'some test', 'some url'),
call(checkResultAndUpdate, 'some other test', 'some other url'),
]),
call(checkResultAndUpdate, "some test", "some url"),
call(checkResultAndUpdate, "some other test", "some other url")
])
);
expect(data.saga.next().done).toBe(true);
});
});
describe('rootSaga', () => {
it('uses takeEvery on each saga available', () => {
describe("rootSaga", () => {
it("uses takeEvery on each saga available", () => {
const saga = rootSaga();
expect(saga.next().value).toEqual(
all([
@ -464,8 +464,8 @@ describe('rootSaga', () => {
takeEvery(REQUEST_POLLBOT_VERSION, fetchPollbotVersion),
takeEvery(UPDATE_URL, updateUrl),
takeEvery(REFRESH_STATUS, refreshStatus),
takeEvery(REQUEST_STATUS, requestStatus),
]),
takeEvery(REQUEST_STATUS, requestStatus)
])
);
expect(saga.next().done).toBe(true);
});

Просмотреть файл

@ -3,28 +3,28 @@ import type {
Store as ReduxStore,
ThunkAction as ReduxThunkAction,
Dispatch as ReduxDispatch,
GetState as ReduxGetState,
} from 'redux';
GetState as ReduxGetState
} from "redux";
export const products = ['firefox', 'devedition'];
export const products = ["firefox", "devedition"];
/*
* state types
*/
export type Product = 'firefox' | 'devedition';
export type Status = 'missing' | 'exists' | 'incomplete' | 'error';
export type Product = "firefox" | "devedition";
export type Status = "missing" | "exists" | "incomplete" | "error";
export type ChannelVersion = [string, string];
export type ChannelVersions = ChannelVersion[];
export type VersionsDict = {[channel: string]: string};
export type VersionsDict = { [channel: string]: string };
export type ProductVersions = {
[product: Product]: VersionsDict,
[product: Product]: VersionsDict
};
export type CheckInfo = {
+url: string,
+title: string,
+actionable: boolean,
+actionable: boolean
};
export type ReleaseInfo = {
@ -33,24 +33,24 @@ export type ReleaseInfo = {
+version: string,
+checks: CheckInfo[],
+message: string,
+status: number,
+status: number
};
export type CheckResult = {
+status: Status,
+message: string,
+link: string,
+link: string
};
export type CheckResults = {
[check: string]: CheckResult,
[check: string]: CheckResult
};
export type APIVersionData = {
name: string,
version: string,
source: string,
commit: string,
commit: string
};
/* Error: [title, errorMessage] */
@ -63,82 +63,82 @@ export type State = {
+checkResults: CheckResults,
+pollbotVersion: ?APIVersionData,
+shouldRefresh: boolean,
+errors: Error[],
+errors: Error[]
};
/*
* action types
*/
export const ADD_CHECK_RESULT = 'ADD_CHECK_RESULT';
export const REFRESH_CHECK_RESULT = 'REFRESH_CHECK_RESULT';
export const ADD_SERVER_ERROR = 'ADD_SERVER_ERROR';
export const SET_VERSION = 'SET_VERSION';
export const UPDATE_PRODUCT_VERSIONS = 'UPDATE_PRODUCT_VERSIONS';
export const UPDATE_RELEASE_INFO = 'UPDATE_RELEASE_INFO';
export const UPDATE_POLLBOT_VERSION = 'UPDATE_POLLBOT_VERSION';
export const ADD_CHECK_RESULT = "ADD_CHECK_RESULT";
export const REFRESH_CHECK_RESULT = "REFRESH_CHECK_RESULT";
export const ADD_SERVER_ERROR = "ADD_SERVER_ERROR";
export const SET_VERSION = "SET_VERSION";
export const UPDATE_PRODUCT_VERSIONS = "UPDATE_PRODUCT_VERSIONS";
export const UPDATE_RELEASE_INFO = "UPDATE_RELEASE_INFO";
export const UPDATE_POLLBOT_VERSION = "UPDATE_POLLBOT_VERSION";
export type AddCheckResult = {|
type: 'ADD_CHECK_RESULT',
type: "ADD_CHECK_RESULT",
title: string,
result: CheckResult,
result: CheckResult
|};
export type RefreshCheckResult = {|
type: 'REFRESH_CHECK_RESULT',
title: string,
type: "REFRESH_CHECK_RESULT",
title: string
|};
export type AddServerError = {|
type: 'ADD_SERVER_ERROR',
type: "ADD_SERVER_ERROR",
title: string,
err: string,
err: string
|};
export type SetVersion = {|
type: 'SET_VERSION',
type: "SET_VERSION",
product: Product,
version: string,
version: string
|};
export type UpdateProductVersions = {|
type: 'UPDATE_PRODUCT_VERSIONS',
type: "UPDATE_PRODUCT_VERSIONS",
versions: VersionsDict,
product: Product,
product: Product
|};
export type UpdateReleaseInfo = {|
type: 'UPDATE_RELEASE_INFO',
releaseInfo: ReleaseInfo,
type: "UPDATE_RELEASE_INFO",
releaseInfo: ReleaseInfo
|};
export type UpdatePollbotVersion = {|
type: 'UPDATE_POLLBOT_VERSION',
version: APIVersionData,
type: "UPDATE_POLLBOT_VERSION",
version: APIVersionData
|};
/*
* saga types
*/
export const REQUEST_ONGOING_VERSIONS = 'REQUEST_ONGOING_VERSIONS';
export const REQUEST_POLLBOT_VERSION = 'REQUEST_POLLBOT_VERSION';
export const UPDATE_URL = 'UPDATE_URL';
export const REFRESH_STATUS = 'REFRESH_STATUS';
export const REQUEST_STATUS = 'REQUEST_STATUS';
export const REQUEST_ONGOING_VERSIONS = "REQUEST_ONGOING_VERSIONS";
export const REQUEST_POLLBOT_VERSION = "REQUEST_POLLBOT_VERSION";
export const UPDATE_URL = "UPDATE_URL";
export const REFRESH_STATUS = "REFRESH_STATUS";
export const REQUEST_STATUS = "REQUEST_STATUS";
export type RequestOngoingVersions = {|
type: 'REQUEST_ONGOING_VERSIONS',
type: "REQUEST_ONGOING_VERSIONS"
|};
export type RequestPollbotVersion = {|
type: 'REQUEST_POLLBOT_VERSION',
type: "REQUEST_POLLBOT_VERSION"
|};
export type UpdateUrl = {|
type: 'UPDATE_URL',
type: "UPDATE_URL"
|};
export type RefreshStatus = {|
type: 'REFRESH_STATUS',
type: "REFRESH_STATUS"
|};
export type RequestStatus = {|
type: 'REQUEST_STATUS',
type: "REQUEST_STATUS",
product: Product,
version: string,
version: string
|};
export type Action =