test(my-joy-beta): test list, metadata, tags, and home

This commit is contained in:
Sérgio Ramos 2017-12-21 00:12:42 +00:00
parent 8f7694f7f4
commit a62b95072f
59 changed files with 61986 additions and 13992 deletions

View File

@ -69,6 +69,7 @@
}, },
"workspaces": ["packages/*", "prototypes/*"], "workspaces": ["packages/*", "prototypes/*"],
"resolutions": { "resolutions": {
"graphql": "0.12.3",
"hoist-non-react-statics": "2.3.1", "hoist-non-react-statics": "2.3.1",
"react": "16.2.0", "react": "16.2.0",
"react-dom": "16.2.0", "react-dom": "16.2.0",

View File

@ -51,15 +51,7 @@
"babel-preset-joyent-portal": "^6.0.1", "babel-preset-joyent-portal": "^6.0.1",
"eslint": "^4.13.1", "eslint": "^4.13.1",
"eslint-config-joyent-portal": "^3.2.0", "eslint-config-joyent-portal": "^3.2.0",
"jest": "^21.2.1",
"jest-alias-preprocessor": "^1.1.1",
"jest-cli": "^21.2.1",
"jest-diff": "^21.2.1",
"jest-junit": "^3.4.0",
"jest-matcher-utils": "^21.2.1",
"jest-snapshot": "^21.2.1",
"jest-styled-components": "^4.9.0", "jest-styled-components": "^4.9.0",
"jest-transform-graphql": "^2.1.0",
"joyent-react-scripts": "^6.5.1", "joyent-react-scripts": "^6.5.1",
"react-test-renderer": "^16.2.0", "react-test-renderer": "^16.2.0",
"redrun": "^5.10.0", "redrun": "^5.10.0",

View File

@ -8,8 +8,18 @@ import { client, store } from '@state/store';
import Router from '@root/router'; import Router from '@root/router';
import remcalc from 'remcalc'; import remcalc from 'remcalc';
const { NODE_ENV } = process.env;
const IS_PRODUCTION = NODE_ENV === 'production';
const fullTheme = { const fullTheme = {
...theme, ...theme,
font: {
...theme.font,
href: !IS_PRODUCTION
? theme.font.href
: () =>
'https://fonts.googleapis.com/css?family=Libre+Franklin:400,500,600'
},
flexboxgrid: { flexboxgrid: {
gridSize: 12, // rem gridSize: 12, // rem
gutterWidth: 1.25, // rem gutterWidth: 1.25, // rem

View File

@ -1,478 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders <CopiableField /> without throwing 1`] = `
.c0 {
box-sizing: border-box;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex: 0 1 auto;
-ms-flex: 0 1 auto;
flex: 0 1 auto;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
margin-right: -0.5rem;
margin-left: -0.5rem;
}
.c1 {
box-sizing: border-box;
-webkit-flex: 0 0 auto;
-ms-flex: 0 0 auto;
flex: 0 0 auto;
padding-right: 0.5rem;
padding-left: 0.5rem;
}
.c4 {
box-sizing: border-box;
font-family: 'Libre Franklin', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, Helvetica, sans-serif;
width: 100%;
height: 3rem;
min-height: 3rem;
margin-bottom: 0.5rem;
margin-top: 0.5rem;
padding: 0.8125rem 1.125rem;
border-radius: 0.25rem;
border: 0.0625rem solid;
max-width: 100%;
font-size: 0.9375rem;
line-height: normal !important;
font-weight: 400;
font-style: normal;
font-stretch: normal;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
outline: 0;
}
.c4:invalid {
box-shadow: none;
}
.c4:focus {
outline: 0;
}
.c2 {
font-weight: 400;
font-size: 0.9375rem;
font-style: normal;
font-stretch: normal;
display: block;
text-align: left;
margin-right: 0.75rem;
font-weight: bold;
white-space: pre;
font-size: 0.8125rem;
}
.c3 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
margin-bottom: 0.625rem;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.c3 input {
padding-right: 1.875rem;
}
.c3 div {
position: relative;
left: -1.625rem;
}
.c5 {
cursor: pointer;
}
@media only screen and (min-width:0em) {
.c1 {
-webkit-flex-basis: 100%;
-ms-flex-preferred-size: 100%;
flex-basis: 100%;
max-width: 100%;
display: block;
}
}
@media only screen and (min-width:64em) {
.c1 {
-webkit-flex-basis: 58.333333333333336%;
-ms-flex-preferred-size: 58.333333333333336%;
flex-basis: 58.333333333333336%;
max-width: 58.333333333333336%;
display: block;
}
}
<div
className="c0"
>
<div
className="c1"
>
<label
className="c2"
htmlFor=""
>
test
</label>
<div
className="c3"
>
<input
className="c4"
disabled={false}
id=""
value="test"
/>
<div
onClick={undefined}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
tag={false}
>
<svg
className="c5 "
height="16"
onClick={[Function]}
style={
Object {
"transform": "rotate(0deg)",
}
}
version="1.1"
width="13"
xmlnsXlink="http://www.w3.org/1999/xlink"
>
<g
transform="translate(-2367 443)"
>
<use
fill="#464646"
transform="translate(2370 -437)"
xlinkHref="#e"
/>
<use
fill="#464646"
transform="translate(2367 -443)"
xlinkHref="#f"
/>
</g>
<defs>
<path
d="M6 3L4 5V0H3v5L1 3 0 4l3.5 3L7 4 6 3z"
id="e"
/>
<path
d="M12 1h-2V0H3v1H1c-.6 0-1 .4-1 1v13c0 .6.4 1 1 1h11c.6 0 1-.4 1-1V2c0-.6-.4-1-1-1zM4 1h5v2H4V1zm8 14H1V2h2v2h7V2h2v13z"
id="f"
/>
</defs>
</svg>
</div>
</div>
</div>
</div>
`;
exports[`renders <CopyToClipboardTooltip /> without throwing 1`] = `
.c0 {
cursor: pointer;
}
<div
onClick={undefined}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
tag={false}
>
<svg
className="c0 "
height="16"
onClick={[Function]}
style={
Object {
"transform": "rotate(0deg)",
}
}
version="1.1"
width="13"
xmlnsXlink="http://www.w3.org/1999/xlink"
>
<g
transform="translate(-2367 443)"
>
<use
fill="#464646"
transform="translate(2370 -437)"
xlinkHref="#e"
/>
<use
fill="#464646"
transform="translate(2367 -443)"
xlinkHref="#f"
/>
</g>
<defs>
<path
d="M6 3L4 5V0H3v5L1 3 0 4l3.5 3L7 4 6 3z"
id="e"
/>
<path
d="M12 1h-2V0H3v1H1c-.6 0-1 .4-1 1v13c0 .6.4 1 1 1h11c.6 0 1-.4 1-1V2c0-.6-.4-1-1-1zM4 1h5v2H4V1zm8 14H1V2h2v2h7V2h2v13z"
id="f"
/>
</defs>
</svg>
</div>
`;
exports[`renders <Meta /> without throwing 1`] = `
Array [
.c0 {
box-sizing: border-box;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex: 0 1 auto;
-ms-flex: 0 1 auto;
flex: 0 1 auto;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
margin-right: -0.5rem;
margin-left: -0.5rem;
}
.c1 {
box-sizing: border-box;
-webkit-flex: 0 0 auto;
-ms-flex: 0 0 auto;
flex: 0 0 auto;
padding-right: 0.5rem;
padding-left: 0.5rem;
}
.c2 {
margin: 0;
font-weight: 400;
line-height: 1.875rem;
font-size: 1.5rem;
}
.c2 + p,
.c2 + small,
.c2 + h1,
.c2 + h2,
.c2 + label,
.c2 + h3,
.c2 + h4,
.c2 + h5,
.c2 + div,
.c2 + span {
margin-top: 1.5rem;
}
@media only screen and (min-width:0em) {
.c0 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
}
@media only screen and (min-width:0em) {
.c1 {
-webkit-flex-basis: 100%;
-ms-flex-preferred-size: 100%;
flex-basis: 100%;
max-width: 100%;
display: block;
}
}
<div
className="c0"
>
<div
className="c1"
>
<h2
className="c2"
/>
</div>
</div>,
.c0 {
margin-top: 0.5rem;
margin-bottom: 1rem;
}
.c5 {
margin-top: 0.25rem;
}
.c2 {
font-weight: 400;
font-size: 0.9375rem;
font-style: normal;
font-stretch: normal;
display: block;
text-align: left;
}
.c4 {
width: 0.375rem;
height: 0.375rem;
border-radius: 0.1875rem;
display: inline-block;
margin-top: 0.0625rem;
margin-right: 0.375rem;
height: 0.6875rem;
width: 0.6875rem;
border-radius: 50%;
}
.c6 {
opacity: 0.5;
padding-right: 0.1875rem;
}
.c1 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
}
.c3 {
width: 0.0625rem;
height: 1.5rem;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-align-self: flex-end;
-ms-flex-item-align: end;
align-self: flex-end;
margin: 0 1.125rem;
}
@media (max-width:47.9375rem) {
.c1 {
display: block;
}
}
@media (max-width:47.9375rem) {
.c3 {
display: none;
}
}
<div
className="c0"
>
<div
className="c1"
>
<label
className="c2"
>
</label>
<div
className="c3"
/>
<label
className="c2"
>
Hardware Virtual Machine
</label>
<div
className="c3"
/>
<div
className="c1"
>
<span
className="c4"
color="green"
height="0.6875rem"
width="0.6875rem"
/>
Running
</div>
</div>
<div
className="c5"
>
<div
className="c1"
>
<div
className="c1"
>
<label
className="c6 c2"
>
Created:
</label>
<label
className="c2"
>
about 2 months
ago
</label>
</div>
<div
className="c3"
/>
<div
className="c1"
>
<label
className="c6 c2"
>
Updated:
</label>
<label
className="c2"
>
1 day
ago
</label>
</div>
</div>
</div>
</div>,
]
`;

View File

@ -1,92 +0,0 @@
import React from 'react';
import renderer from 'react-test-renderer';
import Store from '@mocks/store';
import 'jest-styled-components';
import Home, { CopyToClipboardTooltip, CopiableField, Meta } from '../home';
it('renders <CopiableField /> without throwing', () => {
const tree = renderer
.create(
<Store>
<CopiableField label="test" text="test" />
</Store>
)
.toJSON();
expect(tree).toMatchSnapshot();
});
it('renders <Home /> without throwing', () => {
const tree = renderer
.create(
<Store>
<Home
instance={{
id: '2252839a-e698-ceec-afac-9549ad0c6624',
state: 'RUNNING',
name: '2252839a',
created: '2017-10-13T11:36:04.463Z',
updated: '2017-11-27T13:46:28.000Z',
primary_ip: '72.2.119.146',
ips: ['ahgsjdasdhjas', 'ajshdajkdhk'],
docker: null,
dns_names: {},
compute_node: '70bb1cee-dba3-11e3-a799-002590e4f2b0',
image: {},
package: {},
__typename: 'Machine'
}}
starting={true}
stopping={false}
rebooting={true}
deleteing={false}
onAction={() => {}}
/>
</Store>
)
.toJSON();
expect(tree).toMatchSnapshot();
});
it('renders <Meta /> without throwing', () => {
const tree = renderer
.create(
<Store>
<Meta
{...{
id: '2252839a-e698-ceec-afac-9549ad0c6624',
state: 'RUNNING',
name: '2252839a',
created: '2017-10-13T11:36:04.463Z',
updated: '2017-11-27T13:46:28.000Z',
primary_ip: '72.2.119.146',
ips: {},
docker: null,
dns_names: {},
compute_node: '70bb1cee-dba3-11e3-a799-002590e4f2b0',
image: {},
package: {},
__typename: 'Machine'
}}
onAction={() => console.log('sup??')}
/>
</Store>
)
.toJSON();
expect(tree).toMatchSnapshot();
});
it('renders <CopyToClipboardTooltip /> without throwing', () => {
const tree = renderer
.create(
<Store>
<CopyToClipboardTooltip>{'test'}</CopyToClipboardTooltip>
</Store>
)
.toJSON();
expect(tree).toMatchSnapshot();
});

View File

@ -1,64 +1,110 @@
import React from 'react'; import React from 'react';
import renderer from 'react-test-renderer'; import renderer from 'react-test-renderer';
import 'jest-styled-components'; import 'jest-styled-components';
import Theme from '@mocks/theme';
import { KeyValue } from '../key-value'; import { KeyValue } from '../key-value';
import Theme from '@mocks/theme';
// KeyValue.propTypes = { it('renders <KeyValue /> without throwing', () => {
// input: PropTypes.oneOf(['input', 'textarea']).isRequired,
// type: PropTypes.string.isRequired,
// method: PropTypes.oneOf(['add', 'edit']).isRequired,
// removing: PropTypes.bool.isRequired,
// expanded: PropTypes.bool.isRequired,
// onToggleExpanded: PropTypes.func,
// onCancel: PropTypes.func,
// onRemove: PropTypes.func
// };
it('renders <KeyValue /> without throwing', () => expect(renderer.create(<Theme><KeyValue /></Theme>).toJSON()).toMatchSnapshot());
it('renders <KeyValue input="input" /> without throwing', () =>
expect( expect(
renderer.create(<Theme><KeyValue input="input" /></Theme>).toJSON() renderer
).toMatchSnapshot()); .create(
<Theme>
<KeyValue />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <KeyValue input="textarea" /> without throwing', () => it('renders <KeyValue expanded={false} /> without throwing', () => {
expect( expect(
renderer.create(<Theme><KeyValue input="textarea" /></Theme>).toJSON() renderer
).toMatchSnapshot()); .create(
<Theme>
<KeyValue expanded={false} />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <KeyValue type="tag" /> without throwing', () => it('renders <KeyValue input="input" /> without throwing', () => {
expect(renderer.create(<Theme><KeyValue type="tag" /></Theme>).toJSON()).toMatchSnapshot());
it('renders <KeyValue method="add" /> without throwing', () =>
expect( expect(
renderer.create(<Theme><KeyValue method="add" /></Theme>).toJSON() renderer
).toMatchSnapshot()); .create(
<Theme>
<KeyValue input="input" />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <KeyValue method="edit" /> without throwing', () => it('renders <KeyValue input="textarea" /> without throwing', () => {
expect( expect(
renderer.create(<Theme><KeyValue method="edit" /></Theme>).toJSON() renderer
).toMatchSnapshot()); .create(
<Theme>
<KeyValue input="textarea" />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <KeyValue removing /> without throwing', () => it('renders <KeyValue type="tag" /> without throwing', () => {
expect(renderer.create(<Theme><KeyValue removing /></Theme>).toJSON()).toMatchSnapshot());
it('renders <KeyValue submitting removing /> without throwing', () =>
expect( expect(
renderer.create(<Theme><KeyValue submitting removing /></Theme>).toJSON() renderer
).toMatchSnapshot()); .create(
<Theme>
<KeyValue type="tag" />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <KeyValue expanded /> without throwing', () => it('renders <KeyValue method="add" /> without throwing', () => {
expect(renderer.create(<Theme><KeyValue expanded /></Theme>).toJSON()).toMatchSnapshot());
it('renders <KeyValue expanded removing /> without throwing', () =>
expect( expect(
renderer.create(<Theme><KeyValue expanded removing /></Theme>).toJSON() renderer
).toMatchSnapshot()); .create(
<Theme>
<KeyValue method="add" />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <KeyValue expanded submitting removing /> without throwing', () => it('renders <KeyValue method="edit" /> without throwing', () => {
expect( expect(
renderer.create(<Theme><KeyValue expanded submitting removing /></Theme>).toJSON() renderer
).toMatchSnapshot()); .create(
<Theme>
<KeyValue method="edit" />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <KeyValue removing /> without throwing', () => {
expect(
renderer.create(
<Theme>
<KeyValue removing />
</Theme>
)
).toMatchSnapshot();
});
it('renders <KeyValue submitting /> without throwing', () => {
expect(
renderer.create(
<Theme>
<KeyValue submitting />
</Theme>
)
).toMatchSnapshot();
});

View File

@ -1,137 +0,0 @@
import 'jest-styled-components';
import React from 'react';
import renderer from 'react-test-renderer';
import ReduxForm from 'declarative-redux-form';
import Store from '@mocks/store';
import Theme from '@mocks/theme';
import ListForm, { MenuForm, Actions, Item } from '../list';
it('renders <MenuForm /> without throwing', () => {
expect(
renderer
.create(
<Store>
<Theme>
<ReduxForm form="instance-list-form">{MenuForm}</ReduxForm>
</Theme>
</Store>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <MenuForm searchable /> without throwing', () => {
expect(
renderer
.create(
<Store>
<Theme>
<ReduxForm form="instance-list-form" searchable>
{MenuForm}
</ReduxForm>
</Theme>
</Store>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Actions /> without throwing', () => {
expect(
renderer
.create(
<Store>
<Theme>
<Actions />
</Theme>
</Store>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Actions submitting /> without throwing', () => {
expect(
renderer
.create(
<Store>
<Theme>
<Actions submitting />
</Theme>
</Store>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Actions submitting allowedActions /> without throwing', () => {
expect(
renderer
.create(
<Store>
<Theme>
<Actions submitting allowedActions={{ stop: true }} />
</Theme>
</Store>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Actions allowedActions /> without throwing', () => {
expect(
renderer
.create(
<Store>
<Theme>
<Actions allowedActions={{ stop: true }} />
</Theme>
</Store>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Actions allowedActions /> without throwing', () => {
expect(
renderer
.create(
<Store>
<Theme>
<Actions allowedActions={{ stop: true }} />
</Theme>
</Store>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <ListForm /> without throwing', () => {
expect(
renderer
.create(
<Store>
<Theme>
<ReduxForm form="instance-list-form">{ListForm}</ReduxForm>
</Theme>
</Store>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Item /> without throwing', () => {
expect(
renderer
.create(
<Store>
<Theme>
<ReduxForm form="instance-list-item-form">{Item}</ReduxForm>
</Theme>
</Store>
)
.toJSON()
).toMatchSnapshot();
});

View File

@ -0,0 +1,217 @@
import React from 'react';
import renderer from 'react-test-renderer';
import 'jest-styled-components';
import { Table, TableTbody } from 'joyent-ui-toolkit';
import InstanceList, { Actions, Item } from '../list';
import Theme from '@mocks/theme';
it('renders <Actions /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Actions />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Actions submitting /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Actions submitting />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Actions submitting statuses /> without throwing', () => {
const statuses = {
starting: true,
stopping: true,
restarting: true,
deleting: true
};
expect(
renderer
.create(
<Theme>
<Actions submitting statuses={statuses} />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Actions allowedActions /> without throwing', () => {
const allowedActions = {
start: true,
stop: true,
reboot: true
};
expect(
renderer
.create(
<Theme>
<Actions allowedActions={allowedActions} />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Item /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Table>
<TableTbody>
<Item />
</TableTbody>
</Table>
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Item mutating /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Table>
<TableTbody>
<Item mutating />
</TableTbody>
</Table>
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Item allowedActions /> without throwing', () => {
const allowedActions = {
start: true,
stop: true
};
expect(
renderer
.create(
<Theme>
<Table>
<TableTbody>
<Item allowedActions={allowedActions} />
</TableTbody>
</Table>
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Item {...item} /> without throwing', () => {
const item = {
id: 'id',
name: 'name',
state: 'PROVISIONING'
};
expect(
renderer
.create(
<Theme>
<Table>
<TableTbody>
<Item {...item} />
</TableTbody>
</Table>
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <InstanceList /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<InstanceList />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <InstanceList sortBy /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<InstanceList sortBy="state" />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <InstanceList sortBy sortOrder /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<InstanceList sortBy="state" sortOrder="asc" />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <InstanceList submitting /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<InstanceList submitting />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <InstanceList allSelected /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<InstanceList allSelected />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <InstanceList>{children}</InstanceList> without throwing', () => {
expect(
renderer
.create(
<Theme>
<InstanceList>
<span>children</span>
</InstanceList>
</Theme>
)
.toJSON()
).toMatchSnapshot();
});

View File

@ -0,0 +1,30 @@
import React from 'react';
import renderer from 'react-test-renderer';
import 'jest-styled-components';
import { AddForm, EditForm } from '../metadata';
import Theme from '@mocks/theme';
it('renders <AddForm /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<AddForm />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <EditForm /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<EditForm />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});

View File

@ -0,0 +1,115 @@
import React from 'react';
import renderer from 'react-test-renderer';
import 'jest-styled-components';
import Summary from '../summary';
import Theme from '@mocks/theme';
it('renders <Summary /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Summary />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Summary starting stopping rebooting deleting /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Summary starting stopping rebooting deleting />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Summary state /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Summary instance={{ state: 'RUNNING' }} />
</Theme>
)
.toJSON()
).toMatchSnapshot();
expect(
renderer
.create(
<Theme>
<Summary instance={{ state: 'STOPPED' }} />
</Theme>
)
.toJSON()
).toMatchSnapshot();
expect(
renderer
.create(
<Theme>
<Summary instance={{ state: 'PROVISIONING' }} />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Summary instance /> without throwing', () => {
const instance1 = {
id: '2252839a-e698-ceec-afac-9549ad0c6624',
compute_node: '70bb1cee-dba3-11e3-a799-002590e4f2b0',
image: {
id: '19aa3328-0025-11e7-a19a-c39077bfd4cf',
name: 'Alpine 3'
},
primary_ip: '72.2.119.146',
ips: ['72.2.119.146', '10.112.5.63'],
package: {
name: 'g4-highcpu-128M'
},
brand: 'KVM',
state: 'RUNNING'
};
expect(
renderer
.create(
<Theme>
<Summary instance={instance1} />
</Theme>
)
.toJSON()
).toMatchSnapshot();
const instance2 = {
id: '2252839a-e698-ceec-afac-9549ad0c6624',
compute_node: '70bb1cee-dba3-11e3-a799-002590e4f2b0',
image: {
id: '19aa3328-0025-11e7-a19a-c39077bfd4cf'
},
primary_ip: '72.2.119.146',
ips: ['72.2.119.146', '10.112.5.63'],
package: {
name: 'g4-highcpu-128M'
},
brand: 'LX',
state: 'RUNNING'
};
expect(
renderer
.create(
<Theme>
<Summary instance={instance2} />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});

View File

@ -1,18 +0,0 @@
import React from 'react';
import renderer from 'react-test-renderer';
import Store from '@mocks/store';
import 'jest-styled-components';
import Tags from '../tags';
it('renders <Tags /> without throwing', () => {
const tree = renderer
.create(
<Store>
<Tags />
</Store>
)
.toJSON();
expect(tree).toMatchSnapshot();
});

View File

@ -0,0 +1,54 @@
import React from 'react';
import renderer from 'react-test-renderer';
import 'jest-styled-components';
import Tag, { AddForm, EditForm } from '../tags';
import Theme from '@mocks/theme';
it('renders <AddForm /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<AddForm />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <EditForm /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<EditForm />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Tag /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Tag />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Tag name value/> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Tag name="name" value="value" />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});

View File

@ -0,0 +1,90 @@
import React from 'react';
import renderer from 'react-test-renderer';
import 'jest-styled-components';
import { Toolbar } from '../toolbar';
import Theme from '@mocks/theme';
it('renders <Toolbar /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Toolbar />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Toolbar searchLabel /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Toolbar searchLabel="Search label" />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Toolbar searchPlaceholder /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Toolbar searchPlaceholder="Search placeholder" />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Toolbar searchable /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Toolbar searchable={false} />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Toolbar actionLabel /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Toolbar actionLabel="Action label" />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Toolbar actionable /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Toolbar actionable={false} />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Toolbar onActionClick /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Toolbar onActionClick={() => null} />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});

View File

@ -1,285 +0,0 @@
import React, { Component } from 'react';
import { Row, Col } from 'react-styled-flexboxgrid';
import styled, { withTheme } from 'styled-components';
import { Margin, Padding } from 'styled-components-spacing';
import remcalc from 'remcalc';
import is from 'styled-is';
import titleCase from 'title-case';
import distanceInWordsToNow from 'date-fns/distance_in_words_to_now';
import copy from 'clipboard-copy';
import {
Card,
CardOutlet,
Divider,
ResetIcon,
Button,
FormLabel,
Input,
H2,
Label,
DotIcon,
DeleteIcon,
StartIcon,
StopIcon,
TooltipContainer,
TooltipTarget,
Tooltip,
ClipboardIcon
} from 'joyent-ui-toolkit';
const stateColor = {
PROVISIONING: 'primary',
RUNNING: 'green',
STOPPING: 'grey',
STOPPED: 'grey',
DELETED: 'secondaryActive',
FAILED: 'red'
};
const GreyLabel = styled(Label)`
opacity: 0.5;
padding-right: ${remcalc(3)};
`;
const Flex = styled.div`
align-items: center;
display: flex;
justify-content: flex-start;
@media (max-width: ${remcalc(767)}) {
display: block;
}
`;
const FlexEnd = styled.div`
justify-content: flex-end;
align-items: center;
display: flex;
flex-grow: 1;
`;
const InputIconWrapper = styled.div`
display: flex;
margin-bottom: ${remcalc(10)};
align-items: center;
${is('noMargin')`
margin-bottom: ${remcalc(0)};
`};
input {
padding-right: ${remcalc(30)};
}
div {
position: relative;
left: ${remcalc(-26)};
}
`;
const HorizontalDivider = styled.div`
width: ${remcalc(1)};
background: ${props => props.theme.grey};
height: ${remcalc(24)};
display: flex;
align-self: flex-end;
margin: 0 ${remcalc(18)};
@media (max-width: ${remcalc(767)}) {
display: none;
}
`;
const ClipboardIconActionable = styled(ClipboardIcon)`
cursor: pointer;
`;
export class CopyToClipboardTooltip extends Component {
constructor() {
super();
this.state = {
copied: false
};
}
handleClick = () => {
const { children: text } = this.props;
copy(text);
this.setState(
{
copied: true
},
() => {
setTimeout(() => {
this.setState({
copied: false
});
}, 4000);
}
);
};
render = () => (
<TooltipContainer hoverable>
<TooltipTarget>
<ClipboardIconActionable onClick={this.handleClick} />
</TooltipTarget>
<Tooltip placement="top" success={Boolean(this.state.copied)}>
{this.state.copied ? 'Copied To Clipboard' : 'Copy To Clipboard'}
</Tooltip>
</TooltipContainer>
);
}
export const CopiableField = ({ label, text, ...rest }) => (
<Row>
<Col xs={12} md={7}>
<FormLabel>{label}</FormLabel>
<InputIconWrapper {...rest}>
<Input fluid value={text} />
<CopyToClipboardTooltip>{text}</CopyToClipboardTooltip>
</InputIconWrapper>
</Col>
</Row>
);
export const Meta = ({
created,
updated,
state,
brand,
image,
...instance
}) => [
<Row middle="xs">
<Col xs={12}>
<H2>{instance.package.name}</H2>
</Col>
</Row>,
<Margin top={2} bottom={3}>
<Flex>
<Label>{image ? titleCase(image.name) : 'Custom Image'}</Label>
<HorizontalDivider />
<Label>
{brand === 'LX'
? 'Infrastructure Container'
: 'Hardware Virtual Machine'}
</Label>
<HorizontalDivider />
<Flex>
<DotIcon
borderRadius="50%"
marginRight={remcalc(6)}
marginTop={remcalc(1)}
width={remcalc(11)}
height={remcalc(11)}
color={stateColor[state]}
/>
{titleCase(state)}
</Flex>
</Flex>
<Margin top={1}>
<Flex>
<Flex>
<GreyLabel>Created: </GreyLabel>
<Label> {distanceInWordsToNow(created)} ago</Label>
</Flex>
<HorizontalDivider />
<Flex>
<GreyLabel>Updated: </GreyLabel>
<Label> {distanceInWordsToNow(updated)} ago</Label>
</Flex>
</Flex>
</Margin>
</Margin>
];
export default withTheme(
({ instance, starting, stopping, rebooting, deleteing, onAction, theme }) => (
<Row>
<Col xs={12} sm={12} md={9}>
<Card>
<CardOutlet big>
<Meta {...instance} />
<Flex>
<Button
secondary
bold
icon
loading={starting}
disabled={instance.state === 'RUNNING'}
onClick={() => onAction('start')}
>
<StartIcon disabled={instance.state === 'RUNNING'} />
<Padding left={1}>Start</Padding>
</Button>
<Button
secondary
bold
icon
loading={stopping}
disabled={instance.state === 'STOPPED'}
onClick={() => onAction('stop')}
>
<StopIcon disabled={instance.state === 'STOPPED'} />
<Padding left={1}>Stop</Padding>
</Button>
<Button
secondary
bold
icon
loading={rebooting}
disabled={instance.state === 'PROVISIONING'}
onClick={() => onAction('reboot')}
>
<ResetIcon disabled={instance.state === 'PROVISIONING'} />
<Padding left={1}>Restart</Padding>
</Button>
</Flex>
<FlexEnd>
<Button
error
bold
icon
loading={deleteing}
disabled={instance.state === 'PROVISIONING'}
onClick={() => onAction('delete')}
>
<DeleteIcon
fill={theme.red}
disabled={instance.state === 'PROVISIONING'}
/>
<Padding left={1}>Delete</Padding>
</Button>
</FlexEnd>
<Margin bottom={5} top={4}>
<Divider height={remcalc(1)} />
</Margin>
<CopiableField text={instance.id.split('-')[0]} label="Short ID" />
<CopiableField text={instance.id} label="ID" />
<CopiableField text={instance.compute_node} label="CN UUID" />
{instance.image && (
<CopiableField text={instance.image.id} label="Image UUID" />
)}
<CopiableField
text={`$ ssh root@${instance.primary_ip}`}
label="Login"
/>
{instance.ips.map((ip, i) => (
<CopiableField
key={i}
noMargin={i === instance.ips.length - 1}
text={ip}
label={`IP address ${i + 1}`}
/>
))}
</CardOutlet>
</Card>
</Col>
</Row>
)
);

View File

@ -132,77 +132,79 @@ export const KeyValue = ({
name="name" name="name"
type="text" type="text"
component={({ input = {} }) => component={({ input = {} }) =>
expanded ? `${input.value}: ` : <b>{`${input.value}: `}</b> !expanded ? `${input.value}: ` : <b>{`${input.value}: `}</b>
} }
/> />
<Field <Field
name="value" name="value"
type="text" type="text"
component={({ input = {} }) => input.value} component={({ input = {} }) => <span>{input.value}</span>}
/> />
</CollapsedKeyValue> </CollapsedKeyValue>
)} )}
</CardHeaderMeta> </CardHeaderMeta>
</CardHeader> </CardHeader>
<CardOutlet> {expanded ? (
<Padding all={1}> <CardOutlet>
{error && !submitting ? ( <Padding all={1}>
<Row> {error && !submitting ? (
<Col xs={12}> <Row>
<Message error> <Col xs={12}>
<MessageTitle>Ooops!</MessageTitle> <Message error>
<MessageDescription>{error}</MessageDescription> <MessageTitle>Ooops!</MessageTitle>
</Message> <MessageDescription>{error}</MessageDescription>
</Message>
</Col>
</Row>
) : null}
{input === 'input' ? (
<InputKeyValue type={type} submitting={submitting} />
) : (
<TextareaKeyValue type={type} submitting={submitting} />
)}
<Row between="xs" middle="xs">
<Col xs={method === 'add' ? 12 : 7}>
<Button
type="button"
onClick={onCancel}
disabled={submitting}
secondary
marginless
>
<span>Cancel</span>
</Button>
<Button
type="submit"
disabled={pristine}
loading={submitting && !removing}
marginless
>
<span>{method === 'add' ? 'Create' : 'Save'}</span>
</Button>
</Col>
<Col xs={method === 'add' ? false : 5}>
<Button
type="button"
onClick={onRemove}
disabled={submitting}
loading={removing}
secondary
right
icon
error
marginless
>
<DeleteIcon
disabled={submitting}
fill={submitting ? undefined : theme.red}
/>
<span>Delete</span>
</Button>
</Col> </Col>
</Row> </Row>
) : null} </Padding>
{input === 'input' ? ( </CardOutlet>
<InputKeyValue type={type} submitting={submitting} /> ) : null}
) : (
<TextareaKeyValue type={type} submitting={submitting} />
)}
<Row between="xs" middle="xs">
<Col xs={method === 'add' ? 12 : 7}>
<Button
type="button"
onClick={onCancel}
disabled={submitting}
secondary
marginless
>
<span>Cancel</span>
</Button>
<Button
type="submit"
disabled={pristine}
loading={submitting && !removing}
marginless
>
<span>{method === 'add' ? 'Create' : 'Save'}</span>
</Button>
</Col>
<Col xs={method === 'add' ? false : 5}>
<Button
type="button"
onClick={onRemove}
disabled={submitting}
loading={removing}
secondary
right
icon
error
marginless
>
<DeleteIcon
disabled={submitting}
fill={submitting ? undefined : theme.red}
/>
<span>Delete</span>
</Button>
</Col>
</Row>
</Padding>
</CardOutlet>
</Card> </Card>
); );
}; };

View File

@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import distanceInWordsToNow from 'date-fns/distance_in_words_to_now'; import distanceInWordsToNow from 'date-fns/distance_in_words_to_now';
import Value from 'react-redux-values';
import remcalc from 'remcalc'; import remcalc from 'remcalc';
import titleCase from 'title-case'; import titleCase from 'title-case';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
@ -11,8 +10,6 @@ import {
Col, Col,
Anchor, Anchor,
FormGroup, FormGroup,
Input,
FormLabel,
Checkbox, Checkbox,
Button, Button,
Table, Table,
@ -23,9 +20,6 @@ import {
TableTbody, TableTbody,
Footer, Footer,
StatusLoader, StatusLoader,
Message,
MessageTitle,
MessageDescription,
Popover, Popover,
PopoverContainer, PopoverContainer,
PopoverTarget, PopoverTarget,
@ -51,33 +45,9 @@ const stateColor = {
FAILED: 'red' FAILED: 'red'
}; };
export const MenuForm = ({ handleSubmit, searchable }) => (
<form onSubmit={handleSubmit}>
<Row>
<Col xs={7} sm={5}>
<FormGroup name="filter" fluid field={Field}>
<FormLabel>Filter instances</FormLabel>
<Input
placeholder="Search for name, state, tags, etc..."
disabled={!searchable}
fluid
/>
</FormGroup>
</Col>
<Col xs={5} sm={7}>
<FormGroup right>
<FormLabel>&#8291;</FormLabel>
<Button type="submit" small icon fluid>
Create Instance
</Button>
</FormGroup>
</Col>
</Row>
</form>
);
export const Actions = ({ export const Actions = ({
submitting = false, submitting = false,
statuses = {},
allowedActions = {}, allowedActions = {},
onStart = () => null, onStart = () => null,
onStop = () => null, onStop = () => null,
@ -87,138 +57,114 @@ export const Actions = ({
<Footer fixed bottom> <Footer fixed bottom>
<Row between="xs" middle="xs"> <Row between="xs" middle="xs">
<Col xs={7}> <Col xs={7}>
<Value name="instance-list-starting"> <SmallOnly key="small-only">
{({ value: starting }) => [ <Button
<SmallOnly key="small-only"> type="button"
<Button onClick={onStart}
type="button" disabled={submitting || !allowedActions.start}
onClick={onStart} loading={submitting && statuses.starting}
disabled={submitting || !allowedActions.start} secondary
loading={submitting && starting} small
secondary icon
small >
icon <StartIcon disabled={submitting || !allowedActions.start} />
rect </Button>
> </SmallOnly>
<StartIcon disabled={submitting || !allowedActions.start} /> <Medium key="medium">
</Button> <Button
</SmallOnly>, type="button"
<Medium key="medium"> onClick={onStart}
<Button disabled={submitting || !allowedActions.start}
type="button" loading={submitting && statuses.starting}
onClick={onStart} secondary
disabled={submitting || !allowedActions.start} icon
loading={submitting && starting} >
secondary <StartIcon disabled={submitting || !allowedActions.start} />
icon <span>Start</span>
rect </Button>
> </Medium>
<StartIcon disabled={submitting || !allowedActions.start} /> <SmallOnly key="small-only">
<span>Start</span> <Button
</Button> type="button"
</Medium> onClick={onStop}
]} disabled={submitting || !allowedActions.stop}
</Value> loading={submitting && statuses.stopping}
<Value name="instance-list-stoping"> secondary
{({ value: stoping }) => [ small
<SmallOnly key="small-only"> icon
<Button >
type="button" <StopIcon disabled={submitting || !allowedActions.stop} />
onClick={onStop} </Button>
disabled={submitting || !allowedActions.stop} </SmallOnly>
loading={submitting && stoping} <Medium key="medium">
secondary <Button
small type="button"
icon onClick={onStop}
rect disabled={submitting || !allowedActions.stop}
> loading={submitting && statuses.stopping}
<StopIcon disabled={submitting || !allowedActions.stop} /> secondary
</Button> icon
</SmallOnly>, >
<Medium key="medium"> <StopIcon disabled={submitting || !allowedActions.stop} />
<Button <span>Stop</span>
type="button" </Button>
onClick={onStop} </Medium>
disabled={submitting || !allowedActions.stop} <SmallOnly key="small-only">
loading={submitting && stoping} <Button
secondary type="button"
icon onClick={onReboot}
rect disabled={submitting || !allowedActions.reboot}
> loading={submitting && statuses.restarting}
<StopIcon disabled={submitting || !allowedActions.stop} /> secondary
<span>Stop</span> small
</Button> icon
</Medium> >
]} <ResetIcon disabled={submitting || !allowedActions.reboot} />
</Value> </Button>
<Value name="instance-list-restarting"> </SmallOnly>
{({ value: restarting }) => [ <Medium key="medium">
<SmallOnly key="small-only"> <Button
<Button type="button"
type="button" onClick={onReboot}
onClick={onReboot} disabled={submitting || !allowedActions.reboot}
disabled={submitting || !allowedActions.reboot} loading={submitting && statuses.restarting}
loading={submitting && restarting} secondary
secondary icon
small >
icon <ResetIcon disabled={submitting || !allowedActions.reboot} />
rect <span>Reboot</span>
> </Button>
<ResetIcon disabled={submitting || !allowedActions.reboot} /> </Medium>
</Button>
</SmallOnly>,
<Medium key="medium">
<Button
type="button"
onClick={onReboot}
disabled={submitting || !allowedActions.reboot}
loading={submitting && restarting}
secondary
icon
rect
>
<ResetIcon disabled={submitting || !allowedActions.reboot} />
<span>Reboot</span>
</Button>
</Medium>
]}
</Value>
</Col> </Col>
<Col xs={5}> <Col xs={5}>
<Value name="instance-list-deleting"> <SmallOnly key="small-only">
{({ value: deleting }) => [ <Button
<SmallOnly key="small-only"> type="button"
<Button onClick={onDelete}
type="button" disabled={submitting}
onClick={onDelete} loading={submitting && statuses.deleting}
disabled={submitting} secondary
loading={submitting && deleting} right
secondary small
right icon
small >
icon <DeleteIcon disabled={submitting} />
rect </Button>
> </SmallOnly>
<DeleteIcon disabled={submitting} /> <Medium key="medium">
</Button> <Button
</SmallOnly>, type="button"
<Medium key="medium"> onClick={onDelete}
<Button disabled={submitting}
type="button" loading={submitting && statuses.deleting}
onClick={onDelete} secondary
disabled={submitting} right
loading={submitting && deleting} icon
secondary >
right <DeleteIcon disabled={submitting} />
icon <span>Delete</span>
rect </Button>
> </Medium>
<DeleteIcon disabled={submitting} />
<span>Delete</span>
</Button>
</Medium>
]}
</Value>
</Col> </Col>
</Row> </Row>
</Footer> </Footer>
@ -227,10 +173,10 @@ export const Actions = ({
export const Item = ({ export const Item = ({
id = '', id = '',
name, name,
state, state = 'RUNNING',
created, created,
allowedActions = {}, allowedActions = {},
submitting, mutating = false,
onStart, onStart,
onStop, onStop,
onReboot, onReboot,
@ -248,23 +194,19 @@ export const Item = ({
</Anchor> </Anchor>
</TableTd> </TableTd>
<TableTd middle left> <TableTd middle left>
<Value name={`${id}-mutating`}> {mutating ? (
{({ value: mutating }) => <StatusLoader small />
mutating ? ( ) : (
<StatusLoader small /> <span>
) : ( <DotIcon
<span> width={remcalc(11)}
<DotIcon height={remcalc(11)}
width={remcalc(11)} borderRadius={remcalc(11)}
height={remcalc(11)} color={stateColor[state]}
borderRadius={remcalc(11)} />{' '}
color={stateColor[state]} {titleCase(state)}
/>{' '} </span>
{titleCase(state)} )}
</span>
)
}
</Value>
</TableTd> </TableTd>
<TableTd xs="0" sm="160" middle left> <TableTd xs="0" sm="160" middle left>
{distanceInWordsToNow(created)} {distanceInWordsToNow(created)}
@ -272,56 +214,43 @@ export const Item = ({
<TableTd xs="0" sm="130" middle left> <TableTd xs="0" sm="130" middle left>
<code>{id.substring(0, 7)}</code> <code>{id.substring(0, 7)}</code>
</TableTd> </TableTd>
<PopoverContainer clickable> {!mutating ? (
<TableTd padding="0" hasBorder="left"> <PopoverContainer clickable>
<PopoverTarget box> <TableTd padding="0" hasBorder="left">
<ActionsIcon /> <PopoverTarget box>
</PopoverTarget> <ActionsIcon />
<Popover placement="right"> </PopoverTarget>
<PopoverItem <Popover placement="bottom">
disabled={!allowedActions.start} <PopoverItem disabled={!allowedActions.start} onClick={onStart}>
onClick={() => onStart({ id })} Start
> </PopoverItem>
Start <PopoverItem disabled={!allowedActions.stop} onClick={onStop}>
</PopoverItem> Stop
<PopoverItem </PopoverItem>
disabled={!allowedActions.stop} <PopoverItem onClick={onReboot}>Reboot</PopoverItem>
onClick={() => onStop({ id })} <PopoverDivider />
> <PopoverItem onClick={onDelete}>Delete</PopoverItem>
Stop </Popover>
</PopoverItem> </TableTd>
<PopoverItem onClick={() => onReboot({ id })}>Reboot</PopoverItem> </PopoverContainer>
<PopoverDivider /> ) : (
<PopoverItem onClick={() => onDelete({ id })}>Delete</PopoverItem> <TableTd padding="0" hasBorder="left" center middle>
</Popover> <ActionsIcon disabled />
</TableTd> </TableTd>
</PopoverContainer> )}
</TableTr> </TableTr>
); );
export default ({ export default ({
items = [],
allowedActions = {},
sortBy = 'name', sortBy = 'name',
sortOrder = 'desc', sortOrder = 'desc',
error = null,
submitting = false, submitting = false,
actionable = false,
allSelected = false, allSelected = false,
toggleSelectAll = () => null, toggleSelectAll = () => null,
onStart = () => null, onSortBy = () => null,
onStop = () => null, children
onReboot = () => null,
onDelete = () => null,
onSortBy = () => null
}) => ( }) => (
<form> <form>
{error ? (
<Message error>
<MessageTitle>Ooops!</MessageTitle>
<MessageDescription>{error}</MessageDescription>
</Message>
) : null}
<Table> <Table>
<TableThead> <TableThead>
<TableTr> <TableTr>
@ -383,30 +312,7 @@ export default ({
<TableTh xs="60" padding="0" /> <TableTh xs="60" padding="0" />
</TableTr> </TableTr>
</TableThead> </TableThead>
<TableTbody> <TableTbody>{children}</TableTbody>
{items.map(({ id, ...rest }) => (
<Item
key={id}
id={id}
{...rest}
submitting={submitting}
onStart={onStart}
onStop={onStop}
onReboot={onReboot}
onDelete={onDelete}
/>
))}
</TableTbody>
</Table> </Table>
{actionable ? (
<Actions
allowedActions={allowedActions}
submitting={submitting}
onStart={onStart}
onStop={onStop}
onReboot={onReboot}
onDelete={onDelete}
/>
) : null}
</form> </form>
); );

View File

@ -0,0 +1,365 @@
import React, { Component } from 'react';
import { Row, Col } from 'react-styled-flexboxgrid';
import styled, { withTheme } from 'styled-components';
import { Margin } from 'styled-components-spacing';
import remcalc from 'remcalc';
import is from 'styled-is';
import titleCase from 'title-case';
import distanceInWordsToNow from 'date-fns/distance_in_words_to_now';
import copy from 'clipboard-copy';
import {
Card,
CardOutlet,
Divider,
ResetIcon,
Button,
FormLabel,
Input,
H2,
Label,
QueryBreakpoints,
DotIcon,
DeleteIcon,
StartIcon,
StopIcon,
TooltipContainer,
TooltipTarget,
Tooltip,
ClipboardIcon
} from 'joyent-ui-toolkit';
const { SmallOnly, Medium } = QueryBreakpoints;
const stateColor = {
PROVISIONING: 'primary',
RUNNING: 'green',
STOPPING: 'grey',
STOPPED: 'grey',
DELETED: 'secondaryActive',
FAILED: 'red'
};
const GreyLabel = styled(Label)`
opacity: 0.5;
padding-right: ${remcalc(3)};
`;
const Flex = styled.div`
align-items: center;
display: flex;
justify-content: flex-start;
@media (max-width: ${remcalc(767)}) {
display: block;
}
`;
const InputIconWrapper = styled.div`
display: flex;
margin-bottom: ${remcalc(10)};
align-items: center;
${is('noMargin')`
margin-bottom: ${remcalc(0)};
`};
input {
padding-right: ${remcalc(30)};
}
div {
position: relative;
left: ${remcalc(-26)};
}
`;
const VerticalDivider = styled.div`
width: ${remcalc(1)};
background: ${props => props.theme.grey};
height: ${remcalc(24)};
display: flex;
align-self: flex-end;
margin: 0 ${remcalc(18)};
@media (max-width: ${remcalc(767)}) {
display: none;
}
`;
const ClipboardIconActionable = styled(ClipboardIcon)`
cursor: pointer;
`;
export class CopyToClipboardTooltip extends Component {
constructor() {
super();
this.state = {
copied: false
};
}
handleClick = () => {
const { children: text } = this.props;
copy(text);
this.setState(
{
copied: true
},
() => {
setTimeout(() => {
this.setState({
copied: false
});
}, 4000);
}
);
};
render = () => (
<TooltipContainer hoverable>
<TooltipTarget>
<ClipboardIconActionable onClick={this.handleClick} />
</TooltipTarget>
<Tooltip placement="top" success={Boolean(this.state.copied)}>
{this.state.copied ? 'Copied To Clipboard' : 'Copy To Clipboard'}
</Tooltip>
</TooltipContainer>
);
}
export const CopiableField = ({ label, text, ...rest }) => (
<Row>
<Col xs={12} md={7}>
<FormLabel>{label}</FormLabel>
<InputIconWrapper {...rest}>
<Input fluid value={text} />
<CopyToClipboardTooltip>{text}</CopyToClipboardTooltip>
</InputIconWrapper>
</Col>
</Row>
);
export const Meta = ({
created,
updated,
state,
brand,
image,
...instance
}) => [
<Row middle="xs">
<Col xs={12}>
<H2>{(instance.package || {}).name}</H2>
</Col>
</Row>,
<Margin top={2} bottom={3}>
<Flex>
<Label>{image ? titleCase(image.name) : 'Custom Image'}</Label>
<VerticalDivider />
<Label>
{brand === 'LX'
? 'Infrastructure Container'
: 'Hardware Virtual Machine'}
</Label>
<VerticalDivider />
<Flex>
<DotIcon
borderRadius="50%"
marginRight={remcalc(6)}
marginTop={remcalc(1)}
width={remcalc(11)}
height={remcalc(11)}
color={stateColor[state]}
/>
{titleCase(state)}
</Flex>
</Flex>
<Margin top={1}>
<Flex>
<Flex>
<GreyLabel>Created: </GreyLabel>
<Label> {distanceInWordsToNow(created)} ago</Label>
</Flex>
<VerticalDivider />
<Flex>
<GreyLabel>Updated: </GreyLabel>
<Label> {distanceInWordsToNow(updated)} ago</Label>
</Flex>
</Flex>
</Margin>
</Margin>
];
export default withTheme(
({
instance = {},
starting = false,
stopping = false,
rebooting = false,
deleting = false,
onAction,
theme = {}
}) => (
<Row>
<Col xs={12} sm={12} md={9}>
<Card>
<CardOutlet big>
<Meta {...instance} />
<Row between="xs">
<Col xs={9}>
<SmallOnly>
<Button
type="button"
loading={starting}
disabled={instance.state === 'RUNNING'}
onClick={() => onAction('start')}
secondary
bold
icon
>
<StartIcon disabled={instance.state === 'RUNNING'} />
</Button>
</SmallOnly>
<Medium>
<Button
type="button"
loading={starting}
disabled={instance.state === 'RUNNING'}
onClick={() => onAction('start')}
secondary
bold
icon
>
<StartIcon disabled={instance.state === 'RUNNING'} />
<span>Start</span>
</Button>
</Medium>
<SmallOnly>
<Button
type="button"
loading={stopping}
disabled={instance.state === 'STOPPED'}
onClick={() => onAction('stop')}
secondary
bold
icon
>
<StopIcon disabled={instance.state === 'STOPPED'} />
</Button>
</SmallOnly>
<Medium>
<Button
type="button"
loading={stopping}
disabled={instance.state === 'STOPPED'}
onClick={() => onAction('stop')}
secondary
bold
icon
>
<StopIcon disabled={instance.state === 'STOPPED'} />
<span>Stop</span>
</Button>
</Medium>
<SmallOnly>
<Button
type="button"
loading={rebooting}
disabled={instance.state === 'PROVISIONING'}
onClick={() => onAction('reboot')}
secondary
bold
icon
>
<ResetIcon disabled={instance.state === 'PROVISIONING'} />
</Button>
</SmallOnly>
<Medium>
<Button
type="button"
loading={rebooting}
disabled={instance.state === 'PROVISIONING'}
onClick={() => onAction('reboot')}
secondary
bold
icon
>
<ResetIcon disabled={instance.state === 'PROVISIONING'} />
<span>Reboot</span>
</Button>
</Medium>
</Col>
<Col xs={3}>
<SmallOnly>
<Button
type="button"
loading={deleting}
disabled={instance.state === 'PROVISIONING'}
onClick={() => onAction('delete')}
secondary
bold
right
icon
error
>
<DeleteIcon
fill={theme.red}
disabled={instance.state === 'PROVISIONING'}
/>
</Button>
</SmallOnly>
<Medium>
<Button
type="button"
loading={deleting}
disabled={instance.state === 'PROVISIONING'}
onClick={() => onAction('delete')}
secondary
bold
right
icon
error
>
<DeleteIcon
fill={theme.red}
disabled={instance.state === 'PROVISIONING'}
/>
<span>Remove</span>
</Button>
</Medium>
</Col>
</Row>
<Margin bottom={5} top={4}>
<Divider height={remcalc(1)} />
</Margin>
<CopiableField
text={(instance.id || '').split('-')[0]}
label="Short ID"
/>
<CopiableField text={instance.id} label="ID" />
<CopiableField text={instance.compute_node} label="CN UUID" />
{instance.image && (
<CopiableField text={instance.image.id} label="Image UUID" />
)}
<CopiableField
text={`ssh root@${instance.primary_ip}`}
label="Login"
/>
{(instance.ips || []).map((ip, i) => (
<CopiableField
key={i}
noMargin={i === instance.ips.length - 1}
text={ip}
label={`IP address ${i + 1}`}
/>
))}
</CardOutlet>
</Card>
</Col>
</Row>
)
);

View File

@ -1,46 +1,8 @@
import React from 'react'; import React from 'react';
import { Row, Col } from 'react-styled-flexboxgrid';
import { Margin } from 'styled-components-spacing'; import { Margin } from 'styled-components-spacing';
import { KeyValue } from '@components/instances'; import { KeyValue } from '@components/instances';
import { Field } from 'redux-form';
import { import { TagItem, TagItemContainer } from 'joyent-ui-toolkit';
FormGroup,
FormLabel,
Input,
Button,
TagItem,
TagList,
TagItemContainer
} from 'joyent-ui-toolkit';
export const MenuForm = ({ addable = true, searchable, onAdd }) => (
<form>
<Row>
<Col xs={7} sm={5}>
<FormGroup name="filter" field={Field} fluid>
<FormLabel>Filter</FormLabel>
<Input disabled={!searchable} fluid />
</FormGroup>
</Col>
<Col xs={5} sm={7}>
<FormGroup right>
<FormLabel>&#8291;</FormLabel>
<Button
type="button"
disabled={!searchable || !addable}
onClick={onAdd}
small
icon
fluid
>
Add tag
</Button>
</FormGroup>
</Col>
</Row>
</form>
);
export const AddForm = props => ( export const AddForm = props => (
<KeyValue {...props} method="add" input="input" type="tag" expanded /> <KeyValue {...props} method="add" input="input" type="tag" expanded />
@ -50,7 +12,7 @@ export const EditForm = props => (
<KeyValue {...props} method="edit" input="input" type="tag" expanded /> <KeyValue {...props} method="edit" input="input" type="tag" expanded />
); );
const Tag = ({ name, value, onClick }) => ( export default ({ name, value, onClick }) => (
<Margin right={1} bottom={1} key={`${name}-${value}`}> <Margin right={1} bottom={1} key={`${name}-${value}`}>
<TagItem onClick={onClick}> <TagItem onClick={onClick}>
<TagItemContainer> <TagItemContainer>
@ -59,17 +21,3 @@ const Tag = ({ name, value, onClick }) => (
</TagItem> </TagItem>
</Margin> </Margin>
); );
export default ({ values, onToggleEditing, ...rest }) => (
<TagList {...rest}>
{values.map(({ id, name, ...tag }) => (
<Tag
key={id}
id={id}
name={name}
{...tag}
onClick={onToggleEditing && (() => onToggleEditing(name))}
/>
))}
</TagList>
);

View File

@ -0,0 +1,50 @@
import React from 'react';
import { Field } from 'redux-form';
import {
Row,
Col,
FormGroup,
Input,
FormLabel,
Button
} from 'joyent-ui-toolkit';
export const Toolbar = ({
searchLabel = 'Filter',
searchPlaceholder = '',
searchable = true,
actionLabel = 'Create',
actionable = true,
onActionClick
}) => (
<Row>
<Col xs={7} sm={5}>
<FormGroup name="filter" fluid field={Field}>
<FormLabel>{searchLabel}</FormLabel>
<Input placeholder={searchPlaceholder} disabled={!searchable} fluid />
</FormGroup>
</Col>
<Col xs={5} sm={7}>
<FormGroup right>
<FormLabel>&#8291;</FormLabel>
<Button
type={onActionClick ? 'button' : 'submit'}
disabled={!searchable || !actionable}
onClick={onActionClick}
small
icon
fluid
>
{actionLabel}
</Button>
</FormGroup>
</Col>
</Row>
);
export default ({ handleSubmit, ...rest }) => (
<form onSubmit={handleSubmit}>
<Toolbar {...rest} />
</form>
);

View File

@ -1,30 +1,45 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders <Header /> without throwing 1`] = ` exports[`renders <Header /> without throwing 1`] = `
.c8 { .c6 {
font-weight: 400; background-color: transparent;
text-decoration-skip: objects;
}
.c5 {
color: rgb(59,70,204);
-webkit-text-fill-color: currentcolor;
}
.c5:hover {
text-decoration: none;
}
.c10 {
color: rgba(73,73,73,1);
line-height: 1.5rem; line-height: 1.5rem;
font-size: 0.9375rem; font-size: 0.9375rem;
margin: 0; margin: 0;
text-align: center; text-align: center;
color: rgb(255,255,255); color: rgb(255,255,255);
-webkit-text-fill-color: currentcolor;
margin: 0; margin: 0;
} }
.c8 + p, .c10 + p,
.c8 + small, .c10 + small,
.c8 + h1, .c10 + h1,
.c8 + h2, .c10 + h2,
.c8 + label, .c10 + label,
.c8 + h3, .c10 + h3,
.c8 + h4, .c10 + h4,
.c8 + h5, .c10 + h5,
.c8 + div, .c10 + div,
.c8 + span { .c10 + span {
padding-bottom: 2.25rem; padding-bottom: 2.25rem;
} }
.c7 { .c9 {
-webkit-flex: 0 1 auto; -webkit-flex: 0 1 auto;
-ms-flex: 0 1 auto; -ms-flex: 0 1 auto;
flex: 0 1 auto; flex: 0 1 auto;
@ -45,52 +60,25 @@ exports[`renders <Header /> without throwing 1`] = `
padding: 0 1.125rem; padding: 0 1.125rem;
} }
.c7 svg { .c9 svg {
margin-right: 0.375rem; margin-right: 0.375rem;
} }
.c7:not(:last-of-type) { .c9:not(:last-of-type) {
border-right: 0.0625rem solid rgba(255,255,255,0.15); border-right: 0.0625rem solid rgba(255,255,255,0.15);
} }
.c7:first-of-type { .c9:first-of-type {
margin-left: auto; margin-left: auto;
} }
.c7:hover, .c9:hover,
.c7.active { .c9.active {
background: rgba(255,255,255,0.15); background: rgba(255,255,255,0.15);
} }
.c9 {
background-color: transparent;
text-decoration-skip: objects;
padding: 0.9375rem;
line-height: 1.5rem;
font-size: 0.9375rem;
color: rgb(255,255,255);
text-decoration: none;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-transition: all 200ms ease;
transition: all 200ms ease;
max-height: 3.3125rem;
min-height: 3.3125rem;
box-sizing: border-box;
}
.c4 { .c4 {
padding: 0.9375rem; padding: 0.9375rem 0;
line-height: 1.5rem; line-height: 1.5rem;
font-size: 0.9375rem; font-size: 0.9375rem;
color: rgb(255,255,255); color: rgb(255,255,255);
@ -115,14 +103,14 @@ exports[`renders <Header /> without throwing 1`] = `
} }
.c3 { .c3 {
margin: 0; color: rgba(73,73,73,1);
font-weight: 400;
line-height: 1.875rem; line-height: 1.875rem;
font-size: 1.5rem; font-size: 1.5rem;
margin: 0;
text-transform: uppercase; text-transform: uppercase;
color: rgb(255,255,255); color: rgb(255,255,255);
font-size: 1.8125rem; font-size: 1.8125rem;
margin: 0; margin: 0 auto;
} }
.c3 + p, .c3 + p,
@ -157,7 +145,7 @@ exports[`renders <Header /> without throwing 1`] = `
background: rgba(255,255,255,0.15); background: rgba(255,255,255,0.15);
} }
.c6 { .c8 {
padding: 0; padding: 0;
margin: 0; margin: 0;
display: -webkit-box; display: -webkit-box;
@ -165,36 +153,8 @@ exports[`renders <Header /> without throwing 1`] = `
display: -ms-flexbox; display: -ms-flexbox;
display: flex; display: flex;
list-style: none; list-style: none;
}
.c6 a {
padding: 0.9375rem;
line-height: 1.5rem;
font-size: 0.9375rem;
color: rgb(255,255,255); color: rgb(255,255,255);
text-decoration: none; -webkit-text-fill-color: currentcolor;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-transition: all 200ms ease;
transition: all 200ms ease;
max-height: 3.3125rem;
min-height: 3.3125rem;
box-sizing: border-box;
}
.c6 a:hover,
.c6 a.active {
background: rgba(255,255,255,0.15);
} }
.c1 { .c1 {
@ -202,6 +162,7 @@ exports[`renders <Header /> without throwing 1`] = `
margin-left: auto; margin-left: auto;
padding-right: 2rem; padding-right: 2rem;
padding-left: 2rem; padding-left: 2rem;
box-sizing: border-box;
width: 100%; width: 100%;
padding-left: 0; padding-left: 0;
padding-right: 0; padding-right: 0;
@ -219,8 +180,6 @@ exports[`renders <Header /> without throwing 1`] = `
-webkit-box-align: stretch; -webkit-box-align: stretch;
-ms-flex-align: stretch; -ms-flex-align: stretch;
align-items: stretch; align-items: stretch;
max-height: 3.3125rem;
min-height: 3.3125rem;
} }
.c0 { .c0 {
@ -247,7 +206,7 @@ exports[`renders <Header /> without throwing 1`] = `
line-height: 1.5625rem; line-height: 1.5625rem;
} }
.c5 { .c7 {
padding-top: 0.6875rem; padding-top: 0.6875rem;
} }
@ -264,98 +223,119 @@ exports[`renders <Header /> without throwing 1`] = `
className="c3" className="c3"
> >
<a <a
className="c4" className="c4 c5 c6"
href="/" to="/"
onClick={[Function]}
> >
<svg <svg
alt="Triton" alt="Triton"
className="c5" className="c7 "
height="30" height="30"
style={
Object {
"transform": "rotate(0deg)",
}
}
version="1.1"
viewBox="0 0 105 30" viewBox="0 0 105 30"
width="105" width="105"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink" xmlnsXlink="http://www.w3.org/1999/xlink"
> >
<title> <g
Group transform="translate(26223 1404)"
</title> >
<desc> <g>
Created using Figma <g>
</desc> <use
<use fill="rgb(255, 255, 255)"
fill="#FFF" transform="translate(-26177 -1383)"
transform="translate(46 21)" xlinkHref="#U"
xlinkHref="#a" />
/> </g>
<use <g>
fill="#FDFDFD" <use
xlinkHref="#b" fill="rgb(255, 255, 255)"
/> transform="translate(-26223 -1404)"
xlinkHref="#V"
/>
</g>
</g>
</g>
<defs> <defs>
<path <path
d="M33.758.147h2.219c1.042 0 1.795.156 2.26.468.466.311.699.804.699 1.477 0 .465-.13.85-.392 1.155-.258.3-.636.496-1.133.585v.054c1.192.204 1.788.83 1.788 1.88 0 .701-.238 1.25-.714 1.643-.473.394-1.135.591-1.987.591h-2.74V.147zm.913 3.363h1.504c.645 0 1.108-.1 1.391-.301.283-.204.425-.546.425-1.026 0-.44-.158-.757-.473-.95-.315-.198-.816-.296-1.504-.296h-1.343V3.51zm0 .773v2.938h1.639c.633 0 1.11-.122 1.428-.365.322-.247.484-.632.484-1.155 0-.487-.165-.845-.495-1.074-.325-.23-.823-.344-1.493-.344h-1.563zm8.573 3.824c-.87 0-1.558-.265-2.063-.794-.501-.53-.752-1.266-.752-2.208 0-.949.233-1.703.698-2.261.47-.559 1.098-.838 1.886-.838.737 0 1.321.243 1.75.73.43.484.645 1.123.645 1.918v.564h-4.055c.018.69.192 1.215.521 1.574.333.358.8.537 1.402.537.634 0 1.26-.133 1.88-.398v.795a4.558 4.558 0 0 1-.897.29c-.28.061-.618.091-1.015.091zm-.242-5.355c-.473 0-.85.154-1.133.462-.28.308-.444.734-.495 1.279h3.078c0-.563-.125-.992-.376-1.29-.25-.3-.609-.45-1.074-.45zm5.833 4.62c.157 0 .31-.011.456-.033.147-.025.264-.05.35-.075v.682a1.58 1.58 0 0 1-.43.113 2.986 2.986 0 0 1-.505.048c-1.139 0-1.708-.6-1.708-1.799V2.806h-.843v-.43l.843-.37.376-1.257h.516v1.364h1.708v.693H47.89v3.465c0 .354.084.626.252.816.168.19.4.285.693.285zM54.442 8l-.177-.838h-.043c-.294.369-.587.62-.88.752-.29.129-.654.193-1.091.193-.584 0-1.042-.15-1.375-.45-.33-.302-.494-.73-.494-1.284 0-1.19.95-1.812 2.852-1.87l.999-.032v-.365c0-.462-.1-.802-.3-1.02-.198-.223-.515-.334-.952-.334-.49 0-1.045.15-1.665.452l-.274-.683a4.2 4.2 0 0 1 .951-.37 4.16 4.16 0 0 1 1.042-.134c.702 0 1.221.155 1.558.467.34.311.51.81.51 1.498V8h-.66zm-2.014-.628c.555 0 .99-.153 1.305-.457.32-.304.478-.73.478-1.278v-.532l-.891.038c-.71.025-1.221.136-1.536.333-.312.193-.468.496-.468.907 0 .323.097.568.29.736.197.168.471.253.822.253z" d="M 33.7583 0.147461L 35.9766 0.147461C 37.0186 0.147461 37.7723 0.303223 38.2378 0.614746C 38.7033 0.92627 38.936 1.41862 38.936 2.0918C 38.936 2.55729 38.8053 2.94222 38.5439 3.24658C 38.2861 3.54736 37.9084 3.74251 37.4106 3.83203L 37.4106 3.88574C 38.603 4.08984 39.1992 4.71647 39.1992 5.76562C 39.1992 6.46745 38.9611 7.0153 38.4849 7.40918C 38.0122 7.80306 37.3498 8 36.4976 8L 33.7583 8L 33.7583 0.147461ZM 34.6714 3.50977L 36.1753 3.50977C 36.8198 3.50977 37.2835 3.40951 37.5664 3.20898C 37.8493 3.00488 37.9907 2.66292 37.9907 2.18311C 37.9907 1.74268 37.8332 1.42578 37.5181 1.23242C 37.203 1.03548 36.7017 0.937012 36.0142 0.937012L 34.6714 0.937012L 34.6714 3.50977ZM 34.6714 4.2832L 34.6714 7.22119L 36.3096 7.22119C 36.9434 7.22119 37.4196 7.09945 37.7383 6.85596C 38.0605 6.60889 38.2217 6.22396 38.2217 5.70117C 38.2217 5.21419 38.057 4.85612 37.7275 4.62695C 37.4017 4.39779 36.904 4.2832 36.2344 4.2832L 34.6714 4.2832ZM 43.2437 8.10742C 42.3735 8.10742 41.686 7.84245 41.1812 7.3125C 40.6799 6.78255 40.4292 6.04671 40.4292 5.10498C 40.4292 4.15609 40.6619 3.40234 41.1274 2.84375C 41.5965 2.28516 42.2249 2.00586 43.0127 2.00586C 43.7503 2.00586 44.334 2.24935 44.7637 2.73633C 45.1934 3.21973 45.4082 3.85889 45.4082 4.65381L 45.4082 5.21777L 41.353 5.21777C 41.3709 5.90885 41.5446 6.43343 41.874 6.7915C 42.207 7.14958 42.6743 7.32861 43.2759 7.32861C 43.9097 7.32861 44.5363 7.19613 45.1558 6.93115L 45.1558 7.72607C 44.8407 7.86214 44.5417 7.95882 44.2588 8.01611C 43.9795 8.07699 43.6411 8.10742 43.2437 8.10742ZM 43.002 2.75244C 42.5293 2.75244 42.1515 2.90641 41.8687 3.21436C 41.5894 3.5223 41.4246 3.94841 41.3745 4.49268L 44.4521 4.49268C 44.4521 3.9305 44.3268 3.50081 44.0762 3.20361C 43.8255 2.90283 43.4674 2.75244 43.002 2.75244ZM 48.835 7.37158C 48.9925 7.37158 49.1447 7.36084 49.2915 7.33936C 49.4383 7.31429 49.5547 7.28923 49.6406 7.26416L 49.6406 7.94629C 49.5439 7.99284 49.4007 8.03044 49.2109 8.05908C 49.0247 8.09131 48.8564 8.10742 48.7061 8.10742C 47.5674 8.10742 46.998 7.50765 46.998 6.30811L 46.998 2.80615L 46.1548 2.80615L 46.1548 2.37646L 46.998 2.00586L 47.374 0.749023L 47.8896 0.749023L 47.8896 2.11328L 49.5977 2.11328L 49.5977 2.80615L 47.8896 2.80615L 47.8896 6.27051C 47.8896 6.625 47.9738 6.89714 48.1421 7.08691C 48.3104 7.27669 48.5413 7.37158 48.835 7.37158ZM 54.4424 8L 54.2651 7.16211L 54.2222 7.16211C 53.9285 7.53092 53.6349 7.78158 53.3413 7.91406C 53.0513 8.04297 52.6878 8.10742 52.251 8.10742C 51.6673 8.10742 51.209 7.95703 50.876 7.65625C 50.5465 7.35547 50.3818 6.92757 50.3818 6.37256C 50.3818 5.18376 51.3325 4.56071 53.2339 4.50342L 54.2329 4.47119L 54.2329 4.10596C 54.2329 3.64404 54.1326 3.30387 53.9321 3.08545C 53.7352 2.86344 53.4183 2.75244 52.9814 2.75244C 52.4909 2.75244 51.9359 2.90283 51.3164 3.20361L 51.0425 2.52148C 51.3325 2.36393 51.6494 2.2404 51.9932 2.15088C 52.3405 2.06136 52.6878 2.0166 53.0352 2.0166C 53.737 2.0166 54.2562 2.17236 54.5928 2.48389C 54.9329 2.79541 55.103 3.29492 55.103 3.98242L 55.103 8L 54.4424 8ZM 52.4282 7.37158C 52.9832 7.37158 53.4183 7.2194 53.7334 6.91504C 54.0521 6.61068 54.2114 6.18457 54.2114 5.63672L 54.2114 5.10498L 53.3198 5.14258C 52.6108 5.16764 52.0988 5.27865 51.7837 5.47559C 51.4722 5.66895 51.3164 5.97152 51.3164 6.3833C 51.3164 6.70557 51.4131 6.95085 51.6064 7.11914C 51.8034 7.28743 52.0773 7.37158 52.4282 7.37158Z"
id="a" id="U"
/> />
<path <path
d="M81 9.498C81 4.252 76.746 0 71.5 0 66.252-.002 62 4.252 62 9.498c0 5.245 4.254 9.498 9.5 9.498S81 14.744 81 9.498zM16.227.996H.773A.773.773 0 0 0 0 1.768v3.094a.767.767 0 0 0 .418.686.769.769 0 0 0 .355.086h4.61c.422 0 .765.336.773.756v10.834a.77.77 0 0 0 .518.728l.115.031a.755.755 0 0 0 .14.013h3.092a.773.773 0 0 0 .774-.772V6.39c0-.426.346-.772.773-.772h4.68a.77.77 0 0 0 .6-.313.76.76 0 0 0 .152-.447v-3.09a.773.773 0 0 0-.773-.772zM33 1.768v3.094c0 .426-.35.772-.783.772h-6.723a.777.777 0 0 0-.783.772v10.818a.778.778 0 0 1-.783.772h-3.14a.79.79 0 0 1-.558-.225.772.772 0 0 1-.23-.547V5.63c.002-2.56 2.107-4.634 4.703-4.634h7.514c.433 0 .783.346.783.772zm7.172-.772h-3.338a.872.872 0 0 0-.684.327l-.056.086a.748.748 0 0 0-.094.36v15.455c0 .426.373.772.834.772h3.332c.461 0 .834-.346.834-.772V1.768c0-.426-.373-.772-.828-.772zm3.6 0H59.22a.777.777 0 0 1 .78.772l-.001 3.077a.772.772 0 0 1-.772.773h-4.662a.772.772 0 0 0-.771.772v10.833a.77.77 0 0 1-.773.773H49.93a.775.775 0 0 1-.785-.773V6.39a.771.771 0 0 0-.772-.756h-4.601A.772.772 0 0 1 43 4.862V1.768c0-.426.346-.772.772-.772zM84 1.768a.77.77 0 0 1 .77-.772h11.61c2.55 0 4.618 2.074 4.62 4.634v11.594a.77.77 0 0 1-.77.772h-3.08a.765.765 0 0 1-.662-.378.771.771 0 0 1-.107-.394V6.39a.77.77 0 0 0-.766-.772H89.39a.772.772 0 0 0-.77.772v10.834a.77.77 0 0 1-.77.772h-3.08a.773.773 0 0 1-.77-.772l.001-15.456zM71.5 4.996a4.5 4.5 0 1 0 .001 9.001 4.5 4.5 0 0 0-.001-9.001zm31.113-3.854H103v.84h.314V1.228l.551.767.254-.015.551-.763V1.98h.33V.996h-.445l-.553.766-.512-.766H102v.146h.387v.854h.23l-.004-.854z" d="M 81 9.49793C 81 4.25232 76.7461 5.90949e-07 71.5 5.90949e-07C 66.252 -0.00183046 62 4.25171 62 9.49793C 62 14.7435 66.2539 18.9958 71.5 18.9958C 76.7461 18.9958 81 14.7435 81 9.49793ZM 16.2266 0.99585L 0.773438 0.99585C 0.345703 0.99585 0 1.34168 0 1.76819L 0 4.86169C 0 5.09143 0.0996094 5.29773 0.259766 5.43921C 0.308594 5.48181 0.361328 5.51856 0.417969 5.54822C 0.523438 5.60303 0.644531 5.63403 0.773438 5.63403L 5.38281 5.63403C 5.80469 5.63391 6.14844 5.96997 6.15625 6.39026L 6.15625 17.2235C 6.15625 17.4979 6.29883 17.739 6.51562 17.876C 6.56445 17.9071 6.61719 17.9327 6.67383 17.9523L 6.78906 17.983C 6.83398 17.9915 6.88086 17.9958 6.92969 17.9958L 10.0215 17.9958C 10.4492 17.9958 10.7949 17.65 10.7949 17.2235L 10.7949 6.39026C 10.7949 5.96362 11.1406 5.61792 11.5684 5.61792L 16.248 5.61792C 16.4941 5.61145 16.7109 5.48962 16.8477 5.30469C 16.9414 5.17932 16.998 5.0249 17 4.85767L 17 1.76819C 17 1.34168 16.6543 0.99585 16.2266 0.99585ZM 33 1.76819L 33 4.86169C 33 5.28821 32.6504 5.63403 32.2168 5.63403L 25.4941 5.63403C 25.0605 5.63403 24.7109 5.97986 24.7109 6.40637L 24.7109 17.2235C 24.7109 17.65 24.3594 17.9958 23.9277 17.9958L 20.7871 17.9958C 20.5781 17.9969 20.3789 17.916 20.2305 17.771C 20.084 17.6261 20 17.4291 20 17.2235L 20 5.63001C 20.002 3.06995 22.1074 0.99585 24.7031 0.99585L 32.2168 0.99585C 32.6504 0.99585 33 1.34168 33 1.76819ZM 40.1719 0.99585L 36.834 0.99585C 36.6113 0.99585 36.4102 1.07593 36.2598 1.20654C 36.2188 1.24182 36.1816 1.28076 36.1504 1.32275L 36.0938 1.40906C 36.0352 1.51636 36 1.63855 36 1.76819L 36 17.2235C 36 17.65 36.373 17.9958 36.834 17.9958L 40.166 17.9958C 40.627 17.9958 41 17.65 41 17.2235L 41 1.76819C 41 1.34168 40.627 0.99585 40.1719 0.99585ZM 43.7715 0.99585L 59.2207 0.99585C 59.3535 0.99585 59.4785 1.02844 59.5879 1.08594C 59.7109 1.15112 59.8145 1.24829 59.8867 1.36633C 59.959 1.4834 60 1.62097 60 1.76819L 60 4.84546C 60 5.2721 59.6543 5.6178 59.2285 5.6178L 54.5664 5.6178C 54.1406 5.6178 53.7949 5.96362 53.7949 6.39026L 53.7949 17.2234C 53.7949 17.4678 53.6816 17.6857 53.5039 17.8271C 53.373 17.9326 53.2051 17.9957 53.0215 17.9957L 49.9297 17.9957C 49.7227 17.999 49.5234 17.9191 49.375 17.7739C 49.3105 17.7108 49.2598 17.6377 49.2207 17.5582C 49.1719 17.4547 49.1445 17.3405 49.1445 17.2234L 49.1445 6.39026C 49.1367 5.96997 48.793 5.63391 48.373 5.63403L 43.7715 5.63403C 43.3457 5.63403 43 5.28821 43 4.86169L 43 1.76819C 43 1.34168 43.3457 0.99585 43.7715 0.99585ZM 84 1.76819C 84 1.34168 84.3438 0.99585 84.7695 0.99585L 96.3809 0.99585C 98.9297 0.99585 100.998 3.06995 101 5.63001L 101 17.2235C 101 17.65 100.656 17.9958 100.23 17.9958L 97.1504 17.9958C 97.0039 17.9958 96.8691 17.9552 96.752 17.8846C 96.7148 17.8619 96.6797 17.8362 96.6465 17.8076C 96.584 17.7534 96.5312 17.6895 96.4883 17.618C 96.4199 17.5026 96.3809 17.3677 96.3809 17.2235L 96.3809 6.39026C 96.3809 5.96521 96.0391 5.62012 95.6152 5.61792L 89.3887 5.61792C 88.9648 5.61792 88.6191 5.96362 88.6191 6.39026L 88.6191 17.2235C 88.6191 17.65 88.2754 17.9958 87.8496 17.9958L 84.7695 17.9958C 84.5996 17.9958 84.4434 17.9408 84.3164 17.8474C 84.125 17.7069 84 17.4799 84 17.2235L 84 1.76819ZM 71.5 4.99585C 69.0156 4.99585 67 7.01062 67 9.49585C 67 11.9811 69.0156 13.9959 71.5 13.9959C 73.9844 13.9959 76 11.9811 76 9.49585C 75.998 7.01148 73.9844 4.99817 71.5 4.99585ZM 102.613 1.14209L 103 1.14209L 103 1.98145L 103.314 1.98145L 103.314 1.40503L 103.314 1.22925L 103.865 1.99585L 104.119 1.98145L 104.67 1.21777L 104.67 1.40503L 104.67 1.98145L 105 1.98145L 105 0.99585L 104.555 0.99585L 104.002 1.76245L 103.49 0.99585L 103 0.99585L 102 0.99585L 102 1.14209L 102.387 1.14209L 102.387 1.99585L 102.617 1.99585L 102.613 1.14209Z"
fillRule="evenodd" fillRule="evenodd"
id="b" id="V"
/> />
</defs> </defs>
</svg> </svg>
</a> </a>
</h2> </h2>
</div> </div>
<ul <span
className="c6" name="react-responsive-mock"
query="only screen and (min-width: 48rem)"
> >
<li> <ul
<a
className="c4"
href="/"
onClick={[Function]}
>
Compute
</a>
</li>
</ul>
<section
className="c7"
>
<p
className="c8" className="c8"
> >
<a <li>
className="c9" <a
href="https://my.joyent.com" className="c4 c5 c6"
to="/"
>
Compute
</a>
</li>
</ul>
</span>
<span
name="react-responsive-mock"
query="only screen and (min-width: 48rem)"
>
<section
className="c9"
>
<p
className="c10"
> >
Return to existing portal <a
</a> className="c4 c5 c6"
</p> href="https://my.joyent.com"
</section> >
Return to existing portal
</a>
</p>
</section>
</span>
<section <section
className="c7" className="c9"
> >
<p <p
className="c8" className="c10"
> >
<a <a
className="c9" className="c4 c5 c6"
> >
<svg <svg
className="" className=""
height="13" height="13"
style={
Object {
"transform": "rotate(0deg)",
}
}
viewBox="0 0 9 13" viewBox="0 0 9 13"
width="9" width="9"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<title>
icon: data center
</title>
<path <path
d="M0 13h9V0H0v13zm2-2h5V2H2v9zm1-7h3.001V3H3v1zm0 2h3.001V5H3v1zm0 2h3.001V7H3v1z" d="M0 13h9V0H0v13zm2-2h5V2H2v9zm1-7h3.001V3H3v1zm0 2h3.001V5H3v1zm0 2h3.001V7H3v1z"
fill="#FFF" fill="rgb(255, 255, 255)"
fillRule="evenodd" fillRule="evenodd"
/> />
</svg> </svg>
@ -363,33 +343,41 @@ exports[`renders <Header /> without throwing 1`] = `
</a> </a>
</p> </p>
</section> </section>
<section <span
className="c7" name="react-responsive-mock"
query="only screen and (min-width: 48rem)"
> >
<p <section
className="c8" className="c9"
> >
<a <p
className="c9" className="c10"
> >
<svg <a
height="12" className="c4 c5 c6"
viewBox="0 0 12 12"
width="12"
xmlns="http://www.w3.org/2000/svg"
> >
<title> <svg
Shape className=""
</title> height="12"
<path style={
d="M12 12H0a5.958 5.958 0 0 1 1.485-3.552 1.368 1.368 0 0 1 1.726-.296 4.83 4.83 0 0 0 5.201-.248 1.384 1.384 0 0 1 1.75.152A5.968 5.968 0 0 1 12 12zM5.619 0a3.205 3.205 0 0 0-3.211 3.2c0 1.768 1.42 4 3.21 4s3.211-2.232 3.211-4A3.204 3.204 0 0 0 5.62 0z" Object {
fill="#fff" "transform": "rotate(0deg)",
/> }
</svg> }
Nicola viewBox="0 0 12 12"
</a> width="12"
</p> xmlns="http://www.w3.org/2000/svg"
</section> >
<path
d="M12 12H0a5.958 5.958 0 0 1 1.485-3.552 1.368 1.368 0 0 1 1.726-.296 4.83 4.83 0 0 0 5.201-.248 1.384 1.384 0 0 1 1.75.152A5.968 5.968 0 0 1 12 12zM5.619 0a3.205 3.205 0 0 0-3.211 3.2c0 1.768 1.42 4 3.21 4s3.211-2.232 3.211-4A3.204 3.204 0 0 0 5.62 0z"
fill="rgb(255, 255, 255)"
/>
</svg>
Nicola
</a>
</p>
</section>
</span>
</div> </div>
</div> </div>
`; `;

View File

@ -0,0 +1,163 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders <Menu /> without throwing 1`] = `null`;
exports[`renders <Menu links /> without throwing 1`] = `
.c1 {
margin-bottom: 8rem;
}
.c6 {
background-color: transparent;
text-decoration-skip: objects;
}
.c5 {
color: rgb(59,70,204);
-webkit-text-fill-color: currentcolor;
}
.c5:hover {
text-decoration: none;
}
.c0 {
margin-right: auto;
margin-left: auto;
box-sizing: border-box;
width: 100%;
max-width: 62.5rem;
}
.c3 {
display: inline-block;
font-size: 0.9375rem;
line-height: 1.6;
margin-right: 1.4375rem;
}
.c4 {
color: rgb(70,70,70);
text-decoration: none;
cursor: pointer;
}
.c4.active {
color: rgb(59,70,204);
cursor: default;
}
.c2 {
list-style-type: none;
padding: 0;
margin: 1.125rem 0 0 0;
}
@media only screen and (min-width:48em) {
.c0 {
width: 46rem;
}
}
@media only screen and (min-width:64em) {
.c0 {
width: 61rem;
}
}
@media only screen and (min-width:75em) {
.c0 {
width: 76rem;
}
}
@media only screen and (max-width:47.9375rem) {
.c0 {
padding-left: 0.375rem;
padding-right: 0.375rem;
}
}
<div
className="c0"
>
<div
className="c1"
>
<ul
className="c2"
>
<li
className="c3"
>
<a
className="c4 c5 c6"
to="/instances/:name/summary"
>
summary
</a>
</li>
<li
className="c3"
>
<a
className="c4 c5 c6"
to="/instances/:name/tags"
>
tags
</a>
</li>
<li
className="c3"
>
<a
className="c4 c5 c6"
to="/instances/:name/metadata"
>
metadata
</a>
</li>
<li
className="c3"
>
<a
className="c4 c5 c6"
to="/instances/:name/networks"
>
networks
</a>
</li>
<li
className="c3"
>
<a
className="c4 c5 c6"
to="/instances/:name/firewall"
>
firewall
</a>
</li>
<li
className="c3"
>
<a
className="c4 c5 c6"
to="/instances/:name/dns"
>
dns
</a>
</li>
<li
className="c3"
>
<a
className="c4 c5 c6"
to="/instances/:name/snapshots"
>
snapshots
</a>
</li>
</ul>
</div>
</div>
`;

View File

@ -2,21 +2,17 @@ import React from 'react';
import renderer from 'react-test-renderer'; import renderer from 'react-test-renderer';
import 'jest-styled-components'; import 'jest-styled-components';
import Theme from '@mocks/theme';
import Router from '@mocks/router';
import Header from '../header'; import Header from '../header';
import Theme from '@mocks/theme';
it('renders <Header /> without throwing', () => { it('renders <Header /> without throwing', () => {
const tree = renderer expect(
.create( renderer
<Router> .create(
<Theme> <Theme>
<Header /> <Header />
</Theme> </Theme>
</Router> )
) .toJSON()
.toJSON(); ).toMatchSnapshot();
expect(tree).toMatchSnapshot();
}); });

View File

@ -0,0 +1,61 @@
import React from 'react';
import renderer from 'react-test-renderer';
import 'jest-styled-components';
import Menu from '../menu';
import Theme from '@mocks/theme';
it('renders <Menu /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Menu />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Menu links /> without throwing', () => {
const links = [
{
name: 'summary',
pathname: '/instances/:name/summary'
},
{
name: 'tags',
pathname: '/instances/:name/tags'
},
{
name: 'metadata',
pathname: '/instances/:name/metadata'
},
{
name: 'networks',
pathname: '/instances/:name/networks'
},
{
name: 'firewall',
pathname: '/instances/:name/firewall'
},
{
name: 'dns',
pathname: '/instances/:name/dns'
},
{
name: 'snapshots',
pathname: '/instances/:name/snapshots'
}
];
expect(
renderer
.create(
<Theme>
<Menu links={links} />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});

View File

@ -0,0 +1,161 @@
import React from 'react';
import renderer from 'react-test-renderer';
import 'jest-styled-components';
import { List } from '../list';
import Theme from '@mocks/theme';
it('renders <List /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<List />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <List loading /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<List loading />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <List error /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<List error />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <List instances /> without throwing', () => {
const instances = [
{
name: '2252839a',
status: 'RUNNING'
},
{
name: 'f1bd1730',
status: 'STOPPED'
}
];
expect(
renderer
.create(
<Theme>
<List instances={instances} />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <List instances selected /> without throwing', () => {
const instances = [
{
id: '2252839a-e698-ceec-afac-9549ad0c6624',
name: '2252839a',
status: 'RUNNING'
},
{
id: 'f1bd1730-e8a6-4956-e738-d8e85cc6aa04',
name: 'f1bd1730',
status: 'STOPPED'
}
];
const selected = ['2252839a-e698-ceec-afac-9549ad0c6624'];
expect(
renderer
.create(
<Theme>
<List instances={instances} selected={selected} />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <List instances selected=all /> without throwing', () => {
const instances = [
{
id: '2252839a-e698-ceec-afac-9549ad0c6624',
name: '2252839a',
status: 'RUNNING'
},
{
id: 'f1bd1730-e8a6-4956-e738-d8e85cc6aa04',
name: 'f1bd1730',
status: 'STOPPED'
}
];
const selected = [
'2252839a-e698-ceec-afac-9549ad0c6624',
'f1bd1730-e8a6-4956-e738-d8e85cc6aa04'
];
expect(
renderer
.create(
<Theme>
<List instances={instances} selected={selected} />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <List instances selected=all allowedActions /> without throwing', () => {
const instances = [
{
id: '2252839a-e698-ceec-afac-9549ad0c6624',
name: '2252839a',
status: 'RUNNING'
},
{
id: 'f1bd1730-e8a6-4956-e738-d8e85cc6aa04',
name: 'f1bd1730',
status: 'STOPPED'
}
];
const selected = [
'2252839a-e698-ceec-afac-9549ad0c6624',
'f1bd1730-e8a6-4956-e738-d8e85cc6aa04'
];
const allowedActions = {
start: true,
stop: false
};
expect(
renderer
.create(
<Theme>
<List
instances={instances}
selected={selected}
allowedActions={allowedActions}
/>
</Theme>
)
.toJSON()
).toMatchSnapshot();
});

View File

@ -1,17 +1,83 @@
import React from 'react'; import React from 'react';
import renderer from 'react-test-renderer'; import renderer from 'react-test-renderer';
import Store from '@mocks/store';
import 'jest-styled-components'; import 'jest-styled-components';
import Metadata from '../metadata'; import { Metadata } from '../metadata';
import Theme from '@mocks/theme';
it('renders <Metadata /> without throwing', () => { it('renders <Metadata /> without throwing', () => {
const tree = renderer expect(
.create( renderer
<Store> .create(
<Metadata /> <Theme>
</Store> <Metadata />
) </Theme>
.toJSON(); )
expect(tree).toMatchSnapshot(); .toJSON()
).toMatchSnapshot();
}); });
it('renders <Metadata loading /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Metadata loading />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Metadata error /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Metadata error />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Metadata addOpen /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Metadata addOpen />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Metadata metadata /> without throwing', () => {
const metadata = [{
name: 'name1',
value: 'value1',
id: 'name1-value1'
}, {
name: 'name2',
value: 'value2',
id: 'name2-value2',
expanded: true
}, {
name: 'name3',
value: 'value3',
id: 'name3-value3',
expanded: true,
removing: true
}];
expect(
renderer
.create(
<Theme>
<Metadata metadata={metadata} />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});

View File

@ -0,0 +1,120 @@
import React from 'react';
import renderer from 'react-test-renderer';
import 'jest-styled-components';
import { Summary } from '../summary';
import Theme from '@mocks/theme';
it('renders <Summary /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Summary />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Summary loading /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Summary loading />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Summary loadingError /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Summary loadingError />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Summary mutationError /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Summary mutationError="some mutation error" />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Summary starting stopping rebooting deleting /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Summary starting stopping rebooting deleting />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Summary starting stopping rebooting deleting /> without throwing', () => {
const instance1 = {
id: '2252839a-e698-ceec-afac-9549ad0c6624',
compute_node: '70bb1cee-dba3-11e3-a799-002590e4f2b0',
image: {
id: '19aa3328-0025-11e7-a19a-c39077bfd4cf',
name: 'Alpine 3'
},
primary_ip: '72.2.119.146',
ips: ['72.2.119.146', '10.112.5.63'],
package: {
name: 'g4-highcpu-128M'
},
brand: 'KVM',
state: 'RUNNING'
};
expect(
renderer
.create(
<Theme>
<Summary instance={instance1} />
</Theme>
)
.toJSON()
).toMatchSnapshot();
const instance2 = {
id: '2252839a-e698-ceec-afac-9549ad0c6624',
compute_node: '70bb1cee-dba3-11e3-a799-002590e4f2b0',
image: {
id: '19aa3328-0025-11e7-a19a-c39077bfd4cf'
},
primary_ip: '72.2.119.146',
ips: ['72.2.119.146', '10.112.5.63'],
package: {
name: 'g4-highcpu-128M'
},
brand: 'LX',
state: 'RUNNING'
};
expect(
renderer
.create(
<Theme>
<Summary instance={instance2} />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});

View File

@ -0,0 +1,119 @@
import React from 'react';
import renderer from 'react-test-renderer';
import 'jest-styled-components';
import { Tags } from '../tags';
import Theme from '@mocks/theme';
it('renders <Tags /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Tags />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Tags loading /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Tags loading />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Tags error /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Tags error />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Tags addOpen /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Tags addOpen />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Tags editing /> without throwing', () => {
const editing = {
name: 'name1',
value: 'value1',
id: 'name1-value1',
form: 'editing-form'
};
expect(
renderer
.create(
<Theme>
<Tags editing={editing} />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Tags editing.removing /> without throwing', () => {
const editing = {
name: 'name1',
value: 'value1',
id: 'name1-value1',
form: 'editing-form',
removing: true
};
expect(
renderer
.create(
<Theme>
<Tags editing={editing} />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Tags tags /> without throwing', () => {
const tags = [{
name: 'name1',
value: 'value1',
id: 'name1-value1'
}, {
name: 'name2',
value: 'value2',
id: 'name2-value2'
}, {
name: 'name3',
value: 'value3',
id: 'name3-value3'
}];
expect(
renderer
.create(
<Theme>
<Tags tags={tags} />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});

View File

@ -1,5 +1,5 @@
export { default as List } from './list'; export { default as List } from './list';
export { default as Home } from './home'; export { default as Summary } from './summary';
export { default as Tags } from './tags'; export { default as Tags } from './tags';
export { default as Metadata } from './metadata'; export { default as Metadata } from './metadata';
export { default as Networks } from './networks'; export { default as Networks } from './networks';

View File

@ -29,25 +29,29 @@ import EnableInstanceFw from '@graphql/enable-instance-fw.gql';
import DisableInstanceFw from '@graphql/disable-instance-fw.gql'; import DisableInstanceFw from '@graphql/disable-instance-fw.gql';
import CreateSnapshot from '@graphql/create-snapshot.gql'; import CreateSnapshot from '@graphql/create-snapshot.gql';
import StartSnapshot from '@graphql/start-from-snapshot.gql'; import StartSnapshot from '@graphql/start-from-snapshot.gql';
import ToolbarForm from '@components/instances/toolbar';
import Index from '@state/gen-index'; import Index from '@state/gen-index';
import parseError from '@state/parse-error'; import parseError from '@state/parse-error';
import { import {
default as InstanceList, default as InstanceList,
MenuForm as InstanceListMenuForm Item as InstanceListItem,
Actions as InstanceListActions
} from '@components/instances/list'; } from '@components/instances/list';
const TABLE_FORM_NAME = 'instance-list-table'; const TABLE_FORM_NAME = 'instance-list-table';
const MENU_FORM_NAME = 'instance-list-menu'; const MENU_FORM_NAME = 'instance-list-menu';
const List = ({ export const List = ({
instances = [], instances = [],
selected = [], selected = [],
allowedActions, allowedActions,
statuses,
sortBy = 'name', sortBy = 'name',
sortOrder = 'desc', sortOrder = 'desc',
loading = false, loading = false,
error = null, error = null,
submitting,
handleAction, handleAction,
toggleSelectAll, toggleSelectAll,
handleSortBy handleSortBy
@ -72,43 +76,70 @@ const List = ({
</Message> </Message>
) : null; ) : null;
const handleStart = selected => handleAction({ name: 'start', selected });
const handleStop = selected => handleAction({ name: 'stop', selected });
const handleReboot = selected => handleAction({ name: 'restart', selected });
const handleDelete = selected => handleAction({ name: 'delete', selected });
const _table = !loading ? ( const _table = !loading ? (
<ReduxForm <ReduxForm form={TABLE_FORM_NAME}>
form={TABLE_FORM_NAME} {props => (
items={_instances} <InstanceList
actionable={selected.length} {...props}
allowedActions={allowedActions} allSelected={instances.length && selected.length === instances.length}
allSelected={instances.length && selected.length === instances.length} sortBy={sortBy}
sortBy={sortBy} sortOrder={sortOrder}
sortOrder={sortOrder} toggleSelectAll={toggleSelectAll}
toggleSelectAll={toggleSelectAll} onSortBy={handleSortBy}
onSortBy={handleSortBy} >
onStart={({ id } = {}) => {_instances.map(({ id, ...rest }) => (
handleAction({ name: 'start', selected: id ? [{ id }] : selected }) <InstanceListItem
} key={id}
onStop={({ id } = {}) => id={id}
handleAction({ name: 'stop', selected: id ? [{ id }] : selected }) {...rest}
} onStart={() => handleStart([id])}
onReboot={({ id } = {}) => onStop={() => handleStop([id])}
handleAction({ name: 'restart', selected: id ? [{ id }] : selected }) onReboot={() => handleReboot([id])}
} onDelete={() => handleDelete([id])}
onDelete={({ id } = {}) => />
handleAction({ name: 'delete', selected: id ? [{ id }] : selected }) ))}
} </InstanceList>
> )}
{InstanceList}
</ReduxForm> </ReduxForm>
) : null; ) : null;
const _footer =
!loading && selected.length ? (
<InstanceListActions
allowedActions={allowedActions}
statuses={statuses}
submitting={submitting}
onStart={() => handleStart(selected)}
onStop={() => handleStop(selected)}
onReboot={() => handleReboot(selected)}
onDelete={() => handleDelete(selected)}
/>
) : null;
return ( return (
<ViewContainer main> <ViewContainer main>
<Divider height={remcalc(30)} transparent /> <Divider height={remcalc(30)} transparent />
<ReduxForm form={MENU_FORM_NAME} searchable={!_loading}> <ReduxForm form={MENU_FORM_NAME}>
{InstanceListMenuForm} {props => (
<ToolbarForm
{...props}
searchLabel="Filter instances"
searchPlaceholder="Search for name, state, tags, etc..."
searchable={!_loading}
actionLabel="Create Instance"
actionable={false}
/>
)}
</ReduxForm> </ReduxForm>
{_error} {_error}
{_loading} {_loading}
{_table} {_table}
{_footer}
</ViewContainer> </ViewContainer>
); );
}; };
@ -146,11 +177,15 @@ export default compose(
} }
}), }),
connect( connect(
({ form, values }, { index, instances = [] }) => { ({ form, values }, { index, error, instances = [] }) => {
// get search value // get search value
const filter = get(form, `${MENU_FORM_NAME}.values.filter`, false); const filter = get(form, `${MENU_FORM_NAME}.values.filter`, false);
// check checked items ids // check checked items ids
const checked = get(form, `${TABLE_FORM_NAME}.values`, {}); const checked = get(form, `${TABLE_FORM_NAME}.values`, {});
// check whether the main form is submitting
const submitting = get(form, `${TABLE_FORM_NAME}.submitting`, false);
// check whether the main form has an error
const _error = get(form, `${TABLE_FORM_NAME}.error`, null);
// get sort values // get sort values
const sortBy = get(values, 'instance-list-sort-by', 'name'); const sortBy = get(values, 'instance-list-sort-by', 'name');
const sortOrder = get(values, 'instance-list-sort-order', 'asc'); const sortOrder = get(values, 'instance-list-sort-order', 'asc');
@ -161,7 +196,12 @@ export default compose(
: instances; : instances;
// from filtered instances, sort asc // from filtered instances, sort asc
const ascSorted = sort(filtered, [sortBy]); // set's mutating flag
const ascSorted = sort(filtered, [sortBy]).map(({ id, ...item }) => ({
...item,
id,
mutating: get(values, `${id}-mutating`, false)
}));
// if "select-all" is checked, all the instances are selected // if "select-all" is checked, all the instances are selected
// otherwise, map through the checked ids and get the instance value // otherwise, map through the checked ids and get the instance value
@ -175,11 +215,22 @@ export default compose(
stop: selected.some(({ state }) => state === 'RUNNING') stop: selected.some(({ state }) => state === 'RUNNING')
}; };
// get mutating statuses
const statuses = {
starting: get(values, 'instance-list-starting', false),
stopping: get(values, 'instance-list-stoping', false),
restarting: get(values, 'instance-list-restarting', false),
deleting: get(values, 'instance-list-deleteing', false)
};
return { return {
// is sortOrder !== asc, reverse order // is sortOrder !== asc, reverse order
instances: sortOrder === 'asc' ? ascSorted : ascSorted.reverse(), instances: sortOrder === 'asc' ? ascSorted : ascSorted.reverse(),
allowedActions, allowedActions,
selected, selected,
statuses,
submitting,
error: _error || error,
index, index,
sortOrder, sortOrder,
sortBy sortBy

View File

@ -25,9 +25,9 @@ import GetMetadata from '@graphql/list-metadata.gql';
import UpdateMetadata from '@graphql/update-metadata.gql'; import UpdateMetadata from '@graphql/update-metadata.gql';
import DeleteMetadata from '@graphql/delete-metadata.gql'; import DeleteMetadata from '@graphql/delete-metadata.gql';
import parseError from '@state/parse-error'; import parseError from '@state/parse-error';
import ToolbarForm from '@components/instances/toolbar';
import { import {
MenuForm as MetadataMenuForm,
AddForm as MetadataAddForm, AddForm as MetadataAddForm,
EditForm as MetadataEditForm EditForm as MetadataEditForm
} from '@components/instances/metadata'; } from '@components/instances/metadata';
@ -36,8 +36,7 @@ const MENU_FORM_NAME = 'instance-metadata-list-menu';
const ADD_FORM_NAME = 'instance-metadata-add-new'; const ADD_FORM_NAME = 'instance-metadata-add-new';
const METADATA_FORM_KEY = field => `instance-metadata-${paramCase(field)}`; const METADATA_FORM_KEY = field => `instance-metadata-${paramCase(field)}`;
const Metadata = ({ export const Metadata = ({
instance,
metadata = [], metadata = [],
addOpen, addOpen,
loading, loading,
@ -52,12 +51,13 @@ const Metadata = ({
const _loading = !(loading && !metadata.length) ? null : <StatusLoader />; const _loading = !(loading && !metadata.length) ? null : <StatusLoader />;
const _add = addOpen ? ( const _add = addOpen ? (
<ReduxForm <ReduxForm form={ADD_FORM_NAME} onSubmit={handleCreate}>
form={ADD_FORM_NAME} {props => (
onSubmit={handleCreate} <MetadataAddForm
onCancel={() => handleToggleAddOpen(false)} {...props}
> onCancel={() => handleToggleAddOpen(false)}
{MetadataAddForm} />
)}
</ReduxForm> </ReduxForm>
) : null; ) : null;
@ -83,13 +83,17 @@ const Metadata = ({
initialValues={initialValues} initialValues={initialValues}
destroyOnUnmount={false} destroyOnUnmount={false}
onSubmit={handleUpdate} onSubmit={handleUpdate}
onToggleExpanded={() => handleUpdateExpanded(form, !expanded)}
onCancel={() => handleCancel(form)}
onRemove={() => handleRemove(form)}
expanded={expanded}
removing={removing}
> >
{MetadataEditForm} {props => (
<MetadataEditForm
{...props}
onToggleExpanded={() => handleUpdateExpanded(form, !expanded)}
onCancel={() => handleCancel(form)}
onRemove={() => handleRemove(form)}
expanded={expanded}
removing={removing}
/>
)}
</ReduxForm> </ReduxForm>
)); ));
@ -105,12 +109,16 @@ const Metadata = ({
return ( return (
<ViewContainer main> <ViewContainer main>
<ReduxForm <ReduxForm form={MENU_FORM_NAME}>
form={MENU_FORM_NAME} {props => (
searchable={!_loading} <ToolbarForm
onAdd={() => handleToggleAddOpen(!addOpen)} {...props}
> searchable={!_loading}
{MetadataMenuForm} actionLabel="Add metadata"
actionable={!_loading}
onActionClick={() => handleToggleAddOpen(!addOpen)}
/>
)}
</ReduxForm> </ReduxForm>
<Divider height={remcalc(11)} transparent /> <Divider height={remcalc(11)} transparent />
{_line} {_line}

View File

@ -20,10 +20,10 @@ import StartInstance from '@graphql/start-instance.gql';
import StopInstance from '@graphql/stop-instance.gql'; import StopInstance from '@graphql/stop-instance.gql';
import RebootInstance from '@graphql/reboot-instance.gql'; import RebootInstance from '@graphql/reboot-instance.gql';
import DeleteInstance from '@graphql/delete-instance.gql'; import DeleteInstance from '@graphql/delete-instance.gql';
import HomeScreen from '@components/instances/home'; import SummaryScreen from '@components/instances/summary';
import parseError from '@state/parse-error'; import parseError from '@state/parse-error';
const Home = ({ export const Summary = ({
instance, instance,
loading, loading,
loadingError, loadingError,
@ -32,19 +32,19 @@ const Home = ({
starting, starting,
stopping, stopping,
rebooting, rebooting,
deleteing deleting
}) => { }) => {
const { name } = instance || {}; const { name } = instance || {};
const _loading = loading && !name && <StatusLoader />; const _loading = loading && !name && <StatusLoader />;
const _summary = !_loading && const _summary = !_loading &&
instance && ( instance && (
<HomeScreen <SummaryScreen
instance={instance} instance={instance}
starting={starting} starting={starting}
stopping={stopping} stopping={stopping}
rebooting={rebooting} rebooting={rebooting}
deleteing={deleteing} deleting={deleting}
onAction={handleAction} onAction={handleAction}
/> />
); );
@ -77,10 +77,6 @@ const Home = ({
); );
}; };
Home.propTypes = {
loading: PropTypes.bool
};
export default compose( export default compose(
graphql(StopInstance, { name: 'stop' }), graphql(StopInstance, { name: 'stop' }),
graphql(StartInstance, { name: 'start' }), graphql(StartInstance, { name: 'start' }),
@ -110,11 +106,11 @@ export default compose(
return { return {
...ownProps, ...ownProps,
starting: state.values[`${id}-home-starting`], starting: state.values[`${id}-summary-starting`],
stopping: state.values[`${id}-home-stoping`], stopping: state.values[`${id}-summary-stoping`],
rebooting: state.values[`${id}-home-rebooting`], rebooting: state.values[`${id}-summary-rebooting`],
deleteing: state.values[`${id}-home-deleteing`], deleting: state.values[`${id}-summary-deleteing`],
mutationError: state.values[`${id}-home-mutation-error`] mutationError: state.values[`${id}-summary-mutation-error`]
}; };
}, },
(disptach, ownProps) => ({ (disptach, ownProps) => ({
@ -123,7 +119,7 @@ export default compose(
const { id } = instance; const { id } = instance;
const gerund = `${action}ing`; const gerund = `${action}ing`;
const name = `${id}-home-${gerund}`; const name = `${id}-summary-${gerund}`;
// sets loading to true // sets loading to true
disptach( disptach(
@ -155,7 +151,7 @@ export default compose(
const mutationError = const mutationError =
err && err &&
set({ set({
name: `${id}-home-mutation-error`, name: `${id}-summary-mutation-error`,
value: parseError(err) value: parseError(err)
}); });
@ -163,4 +159,4 @@ export default compose(
} }
}) })
) )
)(Home); )(Summary);

View File

@ -11,11 +11,16 @@ import get from 'lodash.get';
import intercept from 'apr-intercept'; import intercept from 'apr-intercept';
import remcalc from 'remcalc'; import remcalc from 'remcalc';
import { ViewContainer, StatusLoader, Divider, H3 } from 'joyent-ui-toolkit'; import {
ViewContainer,
StatusLoader,
Divider,
H3,
TagList
} from 'joyent-ui-toolkit';
import { import {
default as TagList, default as Tag,
MenuForm as TagsMenuForm,
AddForm as TagsAddForm, AddForm as TagsAddForm,
EditForm as TagsEditForm EditForm as TagsEditForm
} from '@components/instances/tags'; } from '@components/instances/tags';
@ -25,12 +30,13 @@ import UpdateTags from '@graphql/update-tags.gql';
import DeleteTag from '@graphql/delete-tag.gql'; import DeleteTag from '@graphql/delete-tag.gql';
import Index from '@state/gen-index'; import Index from '@state/gen-index';
import parseError from '@state/parse-error'; import parseError from '@state/parse-error';
import ToolbarForm from '@components/instances/toolbar';
const MENU_FORM_NAME = 'instance-tags-list-menu'; const MENU_FORM_NAME = 'instance-tags-list-menu';
const ADD_FORM_NAME = 'instance-tags-add-new'; const ADD_FORM_NAME = 'instance-tags-add-new';
const EDIT_FORM_KEY = field => `instance-tags-${paramCase(field)}`; const EDIT_FORM_KEY = field => `instance-tags-${paramCase(field)}`;
const Tags = ({ export const Tags = ({
tags = [], tags = [],
addOpen, addOpen,
editing, editing,
@ -68,7 +74,17 @@ const Tags = ({
) : null; ) : null;
const _tags = !_loading ? ( const _tags = !_loading ? (
<TagList values={tags} onToggleEditing={!editing && handleToggleEditing} /> <TagList>
{tags.map(({ id, name, value }) => (
<Tag
key={id}
id={id}
name={name}
value={value}
onClick={!editing && (() => handleToggleEditing(name))}
/>
))}
</TagList>
) : null; ) : null;
const _edit = editing ? ( const _edit = editing ? (
@ -87,13 +103,16 @@ const Tags = ({
return ( return (
<ViewContainer main> <ViewContainer main>
<ReduxForm <ReduxForm form={MENU_FORM_NAME}>
form={MENU_FORM_NAME} {props => (
searchable={!_loading} <ToolbarForm
addable={!editing} {...props}
onAdd={() => handleToggleAddOpen(!addOpen)} searchable={!_loading}
> actionLabel="Add tag"
{TagsMenuForm} actionable={!editing}
onActionClick={() => handleToggleAddOpen(!addOpen)}
/>
)}
</ReduxForm> </ReduxForm>
<Divider height={remcalc(11)} transparent /> <Divider height={remcalc(11)} transparent />
{_line} {_line}
@ -165,7 +184,7 @@ export default compose(
handleToggleEditing: value => handleToggleEditing: value =>
dispatch(set({ name: `editing-tag`, value })), dispatch(set({ name: `editing-tag`, value })),
handleEdit: async ({ name, value }, _, { form, initialValues }) => { handleEdit: async ({ name, value }, _, { form, initialValues }) => {
const { tags, instance, deleteTag, updateTags, refetch } = ownProps; const { instance, deleteTag, updateTags, refetch } = ownProps;
// call mutations // call mutations
const [err] = await intercept( const [err] = await intercept(

View File

@ -32,24 +32,16 @@ exports[`renders <Breadcrumb /> without throwing 1`] = `
.c1 { .c1 {
margin-right: auto; margin-right: auto;
margin-left: auto; margin-left: auto;
box-sizing: border-box;
width: 100%;
max-width: 62.5rem; max-width: 62.5rem;
} }
.c6 {
text-decoration: none;
cursor: pointer;
}
.c6:visited {
color: inherit;
}
.c5 { .c5 {
margin: 0; color: rgba(73,73,73,1);
font-weight: 400;
line-height: 1.875rem; line-height: 1.875rem;
font-size: 1.5rem; font-size: 1.5rem;
font-weight: 400; margin: 0;
color: rgb(59,70,204); color: rgb(59,70,204);
margin: 0.75rem 0; margin: 0.75rem 0;
} }
@ -67,7 +59,7 @@ exports[`renders <Breadcrumb /> without throwing 1`] = `
margin-top: 1.5rem; margin-top: 1.5rem;
} }
.c7 { .c6 {
margin: 0.5rem 0.625rem 0.1875rem 0.625rem; margin: 0.5rem 0.625rem 0.1875rem 0.625rem;
} }
@ -87,6 +79,7 @@ exports[`renders <Breadcrumb /> without throwing 1`] = `
margin-left: auto; margin-left: auto;
padding-right: 2rem; padding-right: 2rem;
padding-left: 2rem; padding-left: 2rem;
box-sizing: border-box;
width: 100%; width: 100%;
padding-left: 0; padding-left: 0;
padding-right: 0; padding-right: 0;
@ -95,6 +88,7 @@ exports[`renders <Breadcrumb /> without throwing 1`] = `
@media only screen and (min-width:0em) { @media only screen and (min-width:0em) {
.c3 { .c3 {
-webkit-flex-basis: 100%;
-ms-flex-preferred-size: 100%; -ms-flex-preferred-size: 100%;
flex-basis: 100%; flex-basis: 100%;
max-width: 100%; max-width: 100%;
@ -147,24 +141,221 @@ exports[`renders <Breadcrumb /> without throwing 1`] = `
className="c5" className="c5"
name="breadcrum-item" name="breadcrum-item"
> >
<a Instances
className="c6"
href="/instances"
onClick={[Function]}
>
/
</a>
</h2> </h2>
<svg <svg
className="c7 " className="c6 "
height="10" height="10"
style={
Object {
"transform": "rotate(0deg)",
}
}
viewBox="0 0 6 10" viewBox="0 0 6 10"
width="6" width="6"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M1.12 0L0 1.36 3.496 4.8 0 8.24 1.12 9.6 6 4.8 1.12 0z" d="M1.12 0L0 1.36 3.496 4.8 0 8.24 1.12 9.6 6 4.8 1.12 0z"
fill="#494949" fill="rgba(73, 73, 73, 1)"
fillRule="evenodd"
opacity=".5"
/>
</svg>
</div>
</div>
</div>
</div>
</div>
`;
exports[`renders <Breadcrumb match /> without throwing 1`] = `
.c2 {
box-sizing: border-box;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex: 0 1 auto;
-ms-flex: 0 1 auto;
flex: 0 1 auto;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
margin-right: -0.5rem;
margin-left: -0.5rem;
}
.c3 {
box-sizing: border-box;
-webkit-flex: 0 0 auto;
-ms-flex: 0 0 auto;
flex: 0 0 auto;
padding-right: 0.5rem;
padding-left: 0.5rem;
}
.c1 {
margin-right: auto;
margin-left: auto;
box-sizing: border-box;
width: 100%;
max-width: 62.5rem;
}
.c5 {
color: rgba(73,73,73,1);
line-height: 1.875rem;
font-size: 1.5rem;
margin: 0;
color: rgb(59,70,204);
margin: 0.75rem 0;
}
.c5 + p,
.c5 + small,
.c5 + h1,
.c5 + h2,
.c5 + label,
.c5 + h3,
.c5 + h4,
.c5 + h5,
.c5 + div,
.c5 + span {
margin-top: 1.5rem;
}
.c6 {
margin: 0.5rem 0.625rem 0.1875rem 0.625rem;
}
.c4 {
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: -ms-inline-flexbox;
display: inline-flex;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.c0 {
margin-right: auto;
margin-left: auto;
padding-right: 2rem;
padding-left: 2rem;
box-sizing: border-box;
width: 100%;
padding-left: 0;
padding-right: 0;
border-bottom: 0.0625rem solid rgb(216,216,216);
}
@media only screen and (min-width:0em) {
.c3 {
-webkit-flex-basis: 100%;
-ms-flex-preferred-size: 100%;
flex-basis: 100%;
max-width: 100%;
display: block;
}
}
@media only screen and (min-width:48em) {
.c1 {
width: 46rem;
}
}
@media only screen and (min-width:64em) {
.c1 {
width: 61rem;
}
}
@media only screen and (min-width:75em) {
.c1 {
width: 76rem;
}
}
@media only screen and (max-width:47.9375rem) {
.c1 {
padding-left: 0.375rem;
padding-right: 0.375rem;
}
}
<div
className="c0"
>
<div
className="c1"
>
<div
className="c2"
name="breadcrum"
>
<div
className="c3"
>
<div
className="c4"
>
<h2
className="c5"
name="breadcrum-item"
>
Instances
</h2>
<svg
className="c6 "
height="10"
style={
Object {
"transform": "rotate(0deg)",
}
}
viewBox="0 0 6 10"
width="6"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M1.12 0L0 1.36 3.496 4.8 0 8.24 1.12 9.6 6 4.8 1.12 0z"
fill="rgba(73, 73, 73, 1)"
fillRule="evenodd"
opacity=".5"
/>
</svg>
</div>
<div
className="c4"
>
<h2
className="c5"
name="breadcrum-item"
>
name
</h2>
<svg
className="c6 "
height="10"
style={
Object {
"transform": "rotate(0deg)",
}
}
viewBox="0 0 6 10"
width="6"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M1.12 0L0 1.36 3.496 4.8 0 8.24 1.12 9.6 6 4.8 1.12 0z"
fill="rgba(73, 73, 73, 1)"
fillRule="evenodd" fillRule="evenodd"
opacity=".5" opacity=".5"
/> />

View File

@ -2,24 +2,35 @@ import React from 'react';
import renderer from 'react-test-renderer'; import renderer from 'react-test-renderer';
import 'jest-styled-components'; import 'jest-styled-components';
import Theme from '@mocks/theme';
import Router from '@mocks/router';
import Store from '@mocks/store';
import Breadcrumb from '../breadcrumb'; import Breadcrumb from '../breadcrumb';
import Theme from '@mocks/theme';
it('renders <Breadcrumb /> without throwing', () => { it('renders <Breadcrumb /> without throwing', () => {
const tree = renderer expect(
.create( renderer
<Store> .create(
<Router> <Theme>
<Theme> <Breadcrumb />
<Breadcrumb /> </Theme>
</Theme> )
</Router> .toJSON()
</Store> ).toMatchSnapshot();
) });
.toJSON();
it('renders <Breadcrumb match /> without throwing', () => {
expect(tree).toMatchSnapshot(); const match = {
params: {
instance: 'name'
}
};
expect(
renderer
.create(
<Theme>
<Breadcrumb match={match} />
</Theme>
)
.toJSON()
).toMatchSnapshot();
}); });

View File

@ -1,4 +1,7 @@
module.exports = { module.exports = {
'^joyent-ui-toolkit/dist/es/editor$': '<rootDir>/src/mocks/editor', '^joyent-ui-toolkit/dist/es/editor$': '<rootDir>/src/mocks/editor',
'^redux-form$': '<rootDir>/src/mocks/redux-form' '^redux-form$': '<rootDir>/src/mocks/redux-form',
'^react-responsive$': '<rootDir>/src/mocks/react-responsive',
'^react-router-dom$': '<rootDir>/src/mocks/react-router-dom',
'^declarative-redux-form$': '<rootDir>/src/mocks/declarative-redux-form'
}; };

View File

@ -0,0 +1,3 @@
import React from 'react';
export default ({ children, ...props }) => React.createElement(children, props);

View File

@ -1 +1,3 @@
export default () => <span>joyent-maifest-editor</span> import React from 'react';
export default () => <span>joyent-maifest-editor</span>;

View File

@ -0,0 +1,7 @@
import React from 'react';
export default ({ query, children }) => (
<span name="react-responsive-mock" query={query}>
{children}
</span>
);

View File

@ -0,0 +1,4 @@
import React from 'react';
export const Field = ({ children, ...rest }) =>
React.createElement('a', rest, children);

View File

@ -1,3 +1,4 @@
import React from 'react'; import React from 'react';
export const Field = ({ component = "input", children, ...rest }) => React.createElement(component, rest, children); export const Field = ({ component = 'input', children, ...rest }) =>
React.createElement(component, rest, children);

View File

@ -9,7 +9,7 @@ import { Header } from '@components/navigation';
import { import {
List as Instances, List as Instances,
Home as InstanceHome, Summary as InstanceSummary,
Tags as InstanceTags, Tags as InstanceTags,
Metadata as InstanceMetadata, Metadata as InstanceMetadata,
Networks as InstanceNetworks, Networks as InstanceNetworks,
@ -55,9 +55,9 @@ export default () => (
component={() => null} component={() => null}
/> />
<Route <Route
path="/instances/:instance/home" path="/instances/:instance/summary"
exact exact
component={InstanceHome} component={InstanceSummary}
/> />
<Route <Route
path="/instances/:instance/tags" path="/instances/:instance/tags"
@ -89,7 +89,9 @@ export default () => (
path="/instances/:instance" path="/instances/:instance"
exact exact
component={({ match }) => ( component={({ match }) => (
<Redirect to={`/instances/${get(match, 'params.instance')}/home`} /> <Redirect
to={`/instances/${get(match, 'params.instance')}/summary`}
/>
)} )}
/> />
</Switch> </Switch>

View File

@ -2,7 +2,7 @@ export default {
ui: { ui: {
sections: { sections: {
instances: [ instances: [
'home', 'summary',
'tags', 'tags',
'metadata', 'metadata',
'networks', 'networks',

View File

@ -222,8 +222,6 @@ const Button = styled(BaseButton)`
${is('icon')` ${is('icon')`
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
min-width: ${remcalc(0)};
& svg + span { & svg + span {
margin-left: ${remcalc(12)}; margin-left: ${remcalc(12)};

View File

@ -17,17 +17,12 @@ const handleBreakpoint = bp => props => {
const width = remcalc(props[bp]); const width = remcalc(props[bp]);
if (!hidden && Number.isNaN(num)) { if (!hidden && Number.isNaN(num)) {
return null; return '';
} }
return ` return `
width: ${width}; width: ${width};
display: table-cell; display: ${hidden ? 'none' : ''};
${hidden &&
`
display: none;
`};
`; `;
}; };

File diff suppressed because it is too large Load Diff

View File

@ -54,15 +54,6 @@
"eslint-config-joyent-portal": "3.2.0", "eslint-config-joyent-portal": "3.2.0",
"eslint-config-prettier": "^2.7.0", "eslint-config-prettier": "^2.7.0",
"eslint-plugin-markdown": "^1.0.0-beta.6", "eslint-plugin-markdown": "^1.0.0-beta.6",
"jest": "^21.2.1",
"jest-alias-preprocessor": "^1.1.1",
"jest-cli": "^21.2.1",
"jest-diff": "^21.2.1",
"jest-junit": "^3.2.1",
"jest-matcher-utils": "^21.2.1",
"jest-snapshot": "^21.2.1",
"jest-styled-components": "^4.9.0",
"jest-transform-graphql": "^2.1.0",
"joyent-react-scripts": "^6.5.1", "joyent-react-scripts": "^6.5.1",
"lodash.sortby": "^4.7.0", "lodash.sortby": "^4.7.0",
"mz": "^2.7.0", "mz": "^2.7.0",

View File

@ -47,15 +47,6 @@
"cross-env": "^5.1.1", "cross-env": "^5.1.1",
"eslint": "^4.11.0", "eslint": "^4.11.0",
"eslint-config-joyent-portal": "^3.2.0", "eslint-config-joyent-portal": "^3.2.0",
"jest": "^21.2.1",
"jest-alias-preprocessor": "^1.1.1",
"jest-cli": "^21.2.1",
"jest-diff": "^21.2.1",
"jest-junit": "^3.1.0",
"jest-matcher-utils": "^21.2.1",
"jest-snapshot": "^21.2.1",
"jest-styled-components": "^4.9.0",
"jest-transform-graphql": "^2.1.0",
"joyent-react-scripts": "^6.5.1", "joyent-react-scripts": "^6.5.1",
"lodash.sortby": "^4.7.0", "lodash.sortby": "^4.7.0",
"mz": "^2.7.0", "mz": "^2.7.0",

View File

@ -186,12 +186,6 @@ acorn-globals@^3.1.0:
dependencies: dependencies:
acorn "^4.0.4" acorn "^4.0.4"
acorn-jsx@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-2.0.1.tgz#0edf9878a5866bca625f52955a1ed9e7d8c5117e"
dependencies:
acorn "^2.0.1"
acorn-jsx@^3.0.0, acorn-jsx@^3.0.1: acorn-jsx@^3.0.0, acorn-jsx@^3.0.1:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b"
@ -204,14 +198,6 @@ acorn5-object-spread@^4.0.0:
dependencies: dependencies:
acorn "^5.1.2" acorn "^5.1.2"
acorn@^1.0.3:
version "1.2.2"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-1.2.2.tgz#c8ce27de0acc76d896d2b1fad3df588d9e82f014"
acorn@^2.0.1:
version "2.7.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-2.7.0.tgz#ab6e7d9d886aaca8b085bc3312b79a198433f0e7"
acorn@^3.0.4: acorn@^3.0.4:
version "3.3.0" version "3.3.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a"
@ -4035,15 +4021,6 @@ facet@0.3.0:
version "0.3.0" version "0.3.0"
resolved "https://registry.yarnpkg.com/facet/-/facet-0.3.0.tgz#1bf949d8a112e2045dda84259407def3deb62c1b" resolved "https://registry.yarnpkg.com/facet/-/facet-0.3.0.tgz#1bf949d8a112e2045dda84259407def3deb62c1b"
falafel@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/falafel/-/falafel-1.2.0.tgz#c18d24ef5091174a497f318cd24b026a25cddab4"
dependencies:
acorn "^1.0.3"
foreach "^2.0.5"
isarray "0.0.1"
object-keys "^1.0.6"
fast-deep-equal@^1.0.0: fast-deep-equal@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff"
@ -5643,13 +5620,6 @@ jessy@^2.0.0:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/jessy/-/jessy-2.0.1.tgz#2114b42a51caa40dd48caa8689cc8ffca25abe47" resolved "https://registry.yarnpkg.com/jessy/-/jessy-2.0.1.tgz#2114b42a51caa40dd48caa8689cc8ffca25abe47"
jest-alias-preprocessor@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/jest-alias-preprocessor/-/jest-alias-preprocessor-1.1.1.tgz#1db15080db4a7cf7fc1b43a7b23501415fd22a80"
dependencies:
lodash.flatten "^4.4.0"
transform-jest-deps "^2.2.0"
jest-changed-files@^20.0.3: jest-changed-files@^20.0.3:
version "20.0.3" version "20.0.3"
resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-20.0.3.tgz#9394d5cc65c438406149bef1bf4d52b68e03e3f8" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-20.0.3.tgz#9394d5cc65c438406149bef1bf4d52b68e03e3f8"
@ -5695,7 +5665,7 @@ jest-cli@^20.0.4:
worker-farm "^1.3.1" worker-farm "^1.3.1"
yargs "^7.0.2" yargs "^7.0.2"
jest-cli@^21.0.1, jest-cli@^21.2.1: jest-cli@^21.0.1:
version "21.2.1" version "21.2.1"
resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-21.2.1.tgz#9c528b6629d651911138d228bdb033c157ec8c00" resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-21.2.1.tgz#9c528b6629d651911138d228bdb033c157ec8c00"
dependencies: dependencies:
@ -5869,14 +5839,6 @@ jest-jasmine2@^21.2.1:
jest-snapshot "^21.2.1" jest-snapshot "^21.2.1"
p-cancelable "^0.3.0" p-cancelable "^0.3.0"
jest-junit@^3.1.0, jest-junit@^3.2.1, jest-junit@^3.4.0:
version "3.4.1"
resolved "https://registry.yarnpkg.com/jest-junit/-/jest-junit-3.4.1.tgz#0f0aea65551290cabdf9a29a1681edb4eba418c5"
dependencies:
mkdirp "^0.5.1"
strip-ansi "^4.0.0"
xml "^1.0.1"
jest-matcher-utils@^20.0.3: jest-matcher-utils@^20.0.3:
version "20.0.3" version "20.0.3"
resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-20.0.3.tgz#b3a6b8e37ca577803b0832a98b164f44b7815612" resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-20.0.3.tgz#b3a6b8e37ca577803b0832a98b164f44b7815612"
@ -6098,12 +6060,6 @@ jest@20.0.4:
dependencies: dependencies:
jest-cli "^20.0.4" jest-cli "^20.0.4"
jest@^21.2.1:
version "21.2.1"
resolved "https://registry.yarnpkg.com/jest/-/jest-21.2.1.tgz#c964e0b47383768a1438e3ccf3c3d470327604e1"
dependencies:
jest-cli "^21.2.1"
joy-react-broadcast@^0.6.9: joy-react-broadcast@^0.6.9:
version "0.6.9" version "0.6.9"
resolved "https://registry.yarnpkg.com/joy-react-broadcast/-/joy-react-broadcast-0.6.9.tgz#52c1d80c6f0d02de0806f7dd1bdc6fcd7050256d" resolved "https://registry.yarnpkg.com/joy-react-broadcast/-/joy-react-broadcast-0.6.9.tgz#52c1d80c6f0d02de0806f7dd1bdc6fcd7050256d"
@ -6703,7 +6659,7 @@ lodash@4.17.2:
version "4.17.4" version "4.17.4"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
lodash@^3.10.1, lodash@^3.3.1: lodash@^3.3.1:
version "3.10.1" version "3.10.1"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
@ -7363,7 +7319,7 @@ object-hash@^1.1.4:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.2.0.tgz#e96af0e96981996a1d47f88ead8f74f1ebc4422b" resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.2.0.tgz#e96af0e96981996a1d47f88ead8f74f1ebc4422b"
object-keys@^1.0.11, object-keys@^1.0.6, object-keys@^1.0.7, object-keys@^1.0.8, object-keys@^1.0.9: object-keys@^1.0.11, object-keys@^1.0.7, object-keys@^1.0.8, object-keys@^1.0.9:
version "1.0.11" version "1.0.11"
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d"
@ -10541,14 +10497,6 @@ tr46@~0.0.3:
version "0.0.3" version "0.0.3"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
transform-jest-deps@^2.2.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/transform-jest-deps/-/transform-jest-deps-2.2.1.tgz#8acbd3eba514dc17700c8fb6db7114f2f72a3e74"
dependencies:
acorn-jsx "^2.0.0"
falafel "^1.2.0"
lodash "^3.10.1"
treeify@^1.0.1: treeify@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/treeify/-/treeify-1.0.1.tgz#69b3cd022022a168424e7cfa1ced44c939d3eb2f" resolved "https://registry.yarnpkg.com/treeify/-/treeify-1.0.1.tgz#69b3cd022022a168424e7cfa1ced44c939d3eb2f"
@ -11404,10 +11352,6 @@ xml-name-validator@^2.0.1:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635"
xml@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5"
xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1:
version "4.0.1" version "4.0.1"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"