add teardown and data-id for quick-dapp e2e

pull/5370/head
drafish 4 months ago committed by yann300
parent a7f01cfb45
commit a1565b7b69
  1. 42
      apps/quick-dapp/src/actions/index.ts
  2. 4
      apps/quick-dapp/src/components/ContractGUI/index.tsx
  3. 75
      apps/quick-dapp/src/components/DeployPanel/index.tsx
  4. 3
      apps/quick-dapp/src/components/DeployPanel/theme.tsx
  5. 2
      apps/quick-dapp/src/components/EditInstance/index.tsx
  6. 2
      apps/quick-dapp/src/components/ImageUpload/index.tsx
  7. 6
      apps/quick-dapp/src/components/MultipleContainers/components/Container/Container.tsx
  8. 5
      apps/quick-dapp/src/components/MultipleContainers/components/Item/Item.tsx
  9. 9
      apps/quick-dapp/src/components/MultipleContainers/index.tsx
  10. 2
      apps/remix-ide/src/app/tabs/locales/en/quickDapp.json
  11. 1
      libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx

@ -244,8 +244,50 @@ export const deploy = async (payload: any, callback: any) => {
} catch (error) {} } catch (error) {}
callback({ code: 'ERROR', error: 'deploy failed, please try again' }); callback({ code: 'ERROR', error: 'deploy failed, please try again' });
return; return;
}; };
export const teardown = async (payload: any, callback: any) => {
const surgeToken = localStorage.getItem('__SURGE_TOKEN');
const surgeEmail = localStorage.getItem('__SURGE_EMAIL');
let isLogin = false;
if (surgeToken && surgeEmail === payload.email) {
try {
await surgeClient.whoami();
isLogin = true;
} catch (error) {
/* empty */
}
}
if (!isLogin) {
try {
await surgeClient.login({
user: payload.email,
password: payload.password,
});
localStorage.setItem('__SURGE_EMAIL', payload.email);
localStorage.setItem('__SURGE_PASSWORD', payload.password);
localStorage.setItem('__DISQUS_SHORTNAME', payload.shortname);
} catch (error: any) {
callback({ code: 'ERROR', error: error.message });
return;
}
}
try {
await surgeClient.teardown(`${payload.subdomain}.surge.sh`);
} catch ({ message }: any) {
if (message === '403') {
callback({ code: 'ERROR', error: 'this domain belongs to someone else' });
} else {
callback({ code: 'ERROR', error: 'gateway timeout, please try again' });
}
return;
}
callback({ code: 'SUCCESS', error: '' });
return;
}
export const initInstance = async ({ export const initInstance = async ({
methodIdentifiers, methodIdentifiers,
devdoc, devdoc,

@ -12,7 +12,7 @@ const getFuncABIInputs = (funABI: any) => {
return txHelper.inputParametersDeclarationToString(funABI.inputs); return txHelper.inputParametersDeclarationToString(funABI.inputs);
}; };
export function ContractGUI(props: { funcABI: any }) { export function ContractGUI(props: { funcABI: any, funcId: any }) {
const intl = useIntl() const intl = useIntl()
const isConstant = const isConstant =
props.funcABI.constant !== undefined ? props.funcABI.constant : false; props.funcABI.constant !== undefined ? props.funcABI.constant : false;
@ -69,6 +69,7 @@ export function ContractGUI(props: { funcABI: any }) {
<div className={`d-inline-block`} style={{ width: '90%' }}> <div className={`d-inline-block`} style={{ width: '90%' }}>
<div className="p-2"> <div className="p-2">
<input <input
data-id={`functionTitle${props.funcId}`}
className="form-control" className="form-control"
placeholder={intl.formatMessage({ id: 'quickDapp.functionTitle' })} placeholder={intl.formatMessage({ id: 'quickDapp.functionTitle' })}
value={props.funcABI.title} value={props.funcABI.title}
@ -117,6 +118,7 @@ export function ContractGUI(props: { funcABI: any }) {
</div> </div>
<div className="p-2"> <div className="p-2">
<textarea <textarea
data-id={`functionInstructions${props.funcId}`}
className="form-control" className="form-control"
placeholder={intl.formatMessage({ id: 'quickDapp.functionInstructions' })} placeholder={intl.formatMessage({ id: 'quickDapp.functionInstructions' })}
value={props.funcABI.details} value={props.funcABI.details}

@ -1,8 +1,9 @@
import React, { useContext, useState } from 'react'; import React, { useContext, useState, useEffect } from 'react';
import { Form, Button, Alert, InputGroup } from 'react-bootstrap'; import { Form, Button, Alert, InputGroup } from 'react-bootstrap';
import { FormattedMessage, useIntl } from 'react-intl'; import { FormattedMessage, useIntl } from 'react-intl';
import { import {
deploy, deploy,
teardown,
emptyInstance, emptyInstance,
resetInstance, resetInstance,
getInfoFromNatSpec, getInfoFromNatSpec,
@ -36,10 +37,21 @@ function DeployPanel(): JSX.Element {
error: '', error: '',
loading: false, loading: false,
}); });
const [teardownState, setTeardownState] = useState({
code: '',
error: '',
loading: false,
});
useEffect(() => {
window.scrollTo(0, document.body.scrollHeight);
}, [deployState, teardownState])
return ( return (
<div className="col-3 d-inline-block"> <div className="col-3 d-inline-block">
<h3 className="mb-3">QuickDapp <FormattedMessage id="quickDapp.admin" /></h3> <h3 className="mb-3" data-id="quick-dapp-admin">QuickDapp <FormattedMessage id="quickDapp.admin" /></h3>
<Button <Button
data-id="resetFunctions"
onClick={() => { onClick={() => {
resetInstance(); resetInstance();
}} }}
@ -47,6 +59,7 @@ function DeployPanel(): JSX.Element {
<FormattedMessage id="quickDapp.resetFunctions" /> <FormattedMessage id="quickDapp.resetFunctions" />
</Button> </Button>
<Button <Button
data-id="deleteDapp"
className="ml-3" className="ml-3"
onClick={() => { onClick={() => {
emptyInstance(); emptyInstance();
@ -78,6 +91,7 @@ function DeployPanel(): JSX.Element {
<Form.Group className="mb-2" controlId="formEmail"> <Form.Group className="mb-2" controlId="formEmail">
<Form.Label className="text-uppercase mb-0"><FormattedMessage id="quickDapp.email" /></Form.Label> <Form.Label className="text-uppercase mb-0"><FormattedMessage id="quickDapp.email" /></Form.Label>
<Form.Control <Form.Control
data-id="surgeEmail"
type="email" type="email"
placeholder={intl.formatMessage({ id: 'quickDapp.surgeEmail' })} placeholder={intl.formatMessage({ id: 'quickDapp.surgeEmail' })}
required required
@ -90,6 +104,7 @@ function DeployPanel(): JSX.Element {
<Form.Group className="mb-2" controlId="formPassword"> <Form.Group className="mb-2" controlId="formPassword">
<Form.Label className="text-uppercase mb-0"><FormattedMessage id="quickDapp.password" /></Form.Label> <Form.Label className="text-uppercase mb-0"><FormattedMessage id="quickDapp.password" /></Form.Label>
<Form.Control <Form.Control
data-id="surgePassword"
type="password" type="password"
placeholder={intl.formatMessage({ id: 'quickDapp.surgePassword' })} placeholder={intl.formatMessage({ id: 'quickDapp.surgePassword' })}
required required
@ -104,6 +119,7 @@ function DeployPanel(): JSX.Element {
<InputGroup> <InputGroup>
<InputGroup.Text>https://</InputGroup.Text> <InputGroup.Text>https://</InputGroup.Text>
<Form.Control <Form.Control
data-id="surgeSubdomain"
type="subdomain" type="subdomain"
placeholder={intl.formatMessage({ id: 'quickDapp.uniqueSubdomain' })} placeholder={intl.formatMessage({ id: 'quickDapp.uniqueSubdomain' })}
required required
@ -133,7 +149,7 @@ function DeployPanel(): JSX.Element {
<br /> <br />
<div className="d-inline-flex align-items-center custom-control custom-checkbox"> <div className="d-inline-flex align-items-center custom-control custom-checkbox">
<input <input
id="inline-checkbox-1" id="shareToTwitter"
className="form-check-input custom-control-input" className="form-check-input custom-control-input"
type="checkbox" type="checkbox"
name="group1" name="group1"
@ -145,7 +161,7 @@ function DeployPanel(): JSX.Element {
/> />
<label <label
htmlFor="inline-checkbox-1" htmlFor="shareToTwitter"
className="m-0 form-check-label custom-control-label" className="m-0 form-check-label custom-control-label"
style={{ paddingTop: 1 }} style={{ paddingTop: 1 }}
> >
@ -154,7 +170,7 @@ function DeployPanel(): JSX.Element {
</div> </div>
<div className="d-inline-flex align-items-center custom-control custom-checkbox ml-3"> <div className="d-inline-flex align-items-center custom-control custom-checkbox ml-3">
<input <input
id="inline-checkbox-2" id="shareToFacebook"
className="form-check-input custom-control-input" className="form-check-input custom-control-input"
type="checkbox" type="checkbox"
name="group1" name="group1"
@ -166,7 +182,7 @@ function DeployPanel(): JSX.Element {
/> />
<label <label
htmlFor="inline-checkbox-2" htmlFor="shareToFacebook"
className="m-0 form-check-label custom-control-label" className="m-0 form-check-label custom-control-label"
style={{ paddingTop: 1 }} style={{ paddingTop: 1 }}
> >
@ -180,8 +196,8 @@ function DeployPanel(): JSX.Element {
</Form.Label> </Form.Label>
<br /> <br />
<span <span
data-id="remix_ai_switch" data-id="useNatSpec"
id="remix_ai_switch" id="useNatSpec"
className="btn ai-switch pl-0 py-0" className="btn ai-switch pl-0 py-0"
onClick={async () => { onClick={async () => {
getInfoFromNatSpec(!natSpec.checked); getInfoFromNatSpec(!natSpec.checked);
@ -207,7 +223,7 @@ function DeployPanel(): JSX.Element {
</Form.Label> </Form.Label>
<div className="d-flex py-1 align-items-center custom-control custom-checkbox"> <div className="d-flex py-1 align-items-center custom-control custom-checkbox">
<input <input
id="inline-checkbox-3" id="verifiedByEtherscan"
className="form-check-input custom-control-input" className="form-check-input custom-control-input"
type="checkbox" type="checkbox"
onChange={(e) => { onChange={(e) => {
@ -220,7 +236,7 @@ function DeployPanel(): JSX.Element {
/> />
<label <label
htmlFor="inline-checkbox-3" htmlFor="verifiedByEtherscan"
className="m-0 form-check-label custom-control-label" className="m-0 form-check-label custom-control-label"
style={{ paddingTop: 1 }} style={{ paddingTop: 1 }}
> >
@ -234,7 +250,7 @@ function DeployPanel(): JSX.Element {
</Form.Label> </Form.Label>
<div className="d-flex py-1 align-items-center custom-control custom-checkbox"> <div className="d-flex py-1 align-items-center custom-control custom-checkbox">
<input <input
id="inline-checkbox-4" id="noTerminal"
className="form-check-input custom-control-input" className="form-check-input custom-control-input"
type="checkbox" type="checkbox"
onChange={(e) => { onChange={(e) => {
@ -247,7 +263,7 @@ function DeployPanel(): JSX.Element {
/> />
<label <label
htmlFor="inline-checkbox-4" htmlFor="noTerminal"
className="m-0 form-check-label custom-control-label" className="m-0 form-check-label custom-control-label"
style={{ paddingTop: 1 }} style={{ paddingTop: 1 }}
> >
@ -257,6 +273,7 @@ function DeployPanel(): JSX.Element {
</Form.Group> </Form.Group>
<ThemeUI /> <ThemeUI />
<Button <Button
data-id="deployDapp"
variant="primary" variant="primary"
type="submit" type="submit"
className="mt-3" className="mt-3"
@ -267,21 +284,51 @@ function DeployPanel(): JSX.Element {
)} )}
<FormattedMessage id="quickDapp.deploy" /> <FormattedMessage id="quickDapp.deploy" />
</Button> </Button>
<Button
data-id="teardownDapp"
variant="primary"
className="mt-3 ml-3"
disabled={!formVal.email || !formVal.password || !formVal.subdomain}
// hide this button for now, just for e2e use
style={{ display: 'none' }}
onClick={() => {
setTeardownState({ code: '', error: '', loading: true });
teardown(formVal, (state) => {
setTeardownState({ ...state, loading: false });
})
}}
>
{teardownState.loading && (
<i className="fas fa-spinner fa-spin mr-1"></i>
)}
<FormattedMessage id="quickDapp.teardown" />
</Button>
{deployState.code === 'SUCCESS' && ( {deployState.code === 'SUCCESS' && (
<Alert variant="success" className="mt-4"> <Alert variant="success" className="mt-4" data-id="deploySuccess">
<FormattedMessage id="quickDapp.text4" /> <br /> <FormattedMessage id="quickDapp.text5" /> <FormattedMessage id="quickDapp.text4" /> <br /> <FormattedMessage id="quickDapp.text5" />
<br /> <br />
<a <a
data-id="dappUrl"
target="_blank" target="_blank"
href={`https://${formVal.subdomain}.surge.sh`} href={`https://${formVal.subdomain}.surge.sh`}
>{`https://${formVal.subdomain}.surge.sh`}</a> >{`https://${formVal.subdomain}.surge.sh`}</a>
</Alert> </Alert>
)} )}
{deployState.error && ( {deployState.error && (
<Alert variant="danger" className="mt-4"> <Alert variant="danger" className="mt-4" data-id="deployFail">
{deployState.error} {deployState.error}
</Alert> </Alert>
)} )}
{teardownState.code === 'SUCCESS' && (
<Alert variant="success" className="mt-4" data-id="teardownSuccess">
<FormattedMessage id="quickDapp.text6" />
</Alert>
)}
{teardownState.error && (
<Alert variant="danger" className="mt-4" data-id="teardownFail">
{teardownState.error}
</Alert>
)}
</Form> </Form>
</div> </div>
); );

@ -56,6 +56,8 @@ const CustomToggle = React.forwardRef(
e.preventDefault(); e.preventDefault();
onClick(e); onClick(e);
}} }}
id="dropdown-custom-components"
data-id="selectThemesOptions"
className={className.replace('dropdown-toggle', '')} className={className.replace('dropdown-toggle', '')}
> >
<div className="d-flex"> <div className="d-flex">
@ -126,7 +128,6 @@ export function ThemeUI() {
<Dropdown className="w-100"> <Dropdown className="w-100">
<Dropdown.Toggle <Dropdown.Toggle
as={CustomToggle} as={CustomToggle}
id="dropdown-custom-components"
className="btn btn-light btn-block w-100 d-inline-block border border-dark form-control" className="btn btn-light btn-block w-100 d-inline-block border border-dark form-control"
icon={''} icon={''}
> >

@ -17,6 +17,7 @@ function EditInstance(): JSX.Element {
<div className="col-9 pl-0"> <div className="col-9 pl-0">
<div className="my-2 p-3 bg-light"> <div className="my-2 p-3 bg-light">
<input <input
data-id="dappTitle"
className="form-control" className="form-control"
placeholder={intl.formatMessage({ id: 'quickDapp.dappTitle' })} placeholder={intl.formatMessage({ id: 'quickDapp.dappTitle' })}
value={title} value={title}
@ -36,6 +37,7 @@ function EditInstance(): JSX.Element {
</div> </div>
<div className="my-2 p-3 bg-light"> <div className="my-2 p-3 bg-light">
<textarea <textarea
data-id="dappInstructions"
className="form-control" className="form-control"
placeholder={intl.formatMessage({ id: 'quickDapp.dappInstructions' })} placeholder={intl.formatMessage({ id: 'quickDapp.dappInstructions' })}
value={details} value={details}

@ -30,7 +30,7 @@ const ImageUpload = () => {
return ( return (
<div className="col-3 pr-0"> <div className="col-3 pr-0">
<input className="d-none" type="file" accept="image/*" onChange={handleImageChange} id="upload-button" /> <input data-id="uploadLogo" className="d-none" type="file" accept="image/*" onChange={handleImageChange} id="upload-button" />
<CustomTooltip <CustomTooltip
placement="right" placement="right"
tooltipText={intl.formatMessage({ id: 'quickDapp.uploadLogoTooltip' })} tooltipText={intl.formatMessage({ id: 'quickDapp.uploadLogoTooltip' })}

@ -51,15 +51,15 @@ export const Container = forwardRef<HTMLDivElement, Props>(
> >
{label} {label}
<div className={`d-flex container-actions`}> <div className={`d-flex container-actions`}>
<Remove onClick={onRemove} /> <Remove onClick={onRemove} data-id={`remove${label.replace(/\s*/g,"")}`} />
<Handle {...handleProps} /> <Handle {...handleProps} data-id={`handle${label.replace(/\s*/g,"")}`} />
</div> </div>
</div> </div>
) : null} ) : null}
{placeholder ? ( {placeholder ? (
children children
) : ( ) : (
<ul className="p-0 m-0 list-unstyled" style={{ overflowY: 'auto' }}> <ul className="p-0 m-0 list-unstyled" style={{ overflowY: 'auto' }} data-id={`container${label.replace(/\s*/g,"")}`}>
{children} {children}
</ul> </ul>
)} )}

@ -20,6 +20,7 @@ export interface Props {
wrapperStyle?: React.CSSProperties; wrapperStyle?: React.CSSProperties;
children: React.ReactNode; children: React.ReactNode;
onRemove?(): void; onRemove?(): void;
id?: any;
} }
export const Item = React.memo( export const Item = React.memo(
@ -42,6 +43,7 @@ export const Item = React.memo(
transform, transform,
children, children,
wrapperStyle, wrapperStyle,
id,
...props ...props
}, },
ref ref
@ -93,9 +95,10 @@ export const Item = React.memo(
> >
<div className="border-dark bg-light d-flex"> <div className="border-dark bg-light d-flex">
{children} {children}
<Handle {...handleProps} {...listeners} /> <Handle {...handleProps} {...listeners} data-id={`handle${id}`} />
</div> </div>
<button <button
data-id={`remove${id}`}
className={`d-flex justify-content-center align-items-center position-absolute border-0 rounded-circle item-remove`} className={`d-flex justify-content-center align-items-center position-absolute border-0 rounded-circle item-remove`}
onClick={onRemove} onClick={onRemove}
> >

@ -508,6 +508,7 @@ export function MultipleContainers({
function renderSortableItemDragOverlay(id: UniqueIdentifier) { function renderSortableItemDragOverlay(id: UniqueIdentifier) {
return ( return (
<Item <Item
id={id}
handle={handle} handle={handle}
style={getItemStyles({ style={getItemStyles({
containerId: findContainer(id) as UniqueIdentifier, containerId: findContainer(id) as UniqueIdentifier,
@ -521,7 +522,7 @@ export function MultipleContainers({
wrapperStyle={wrapperStyle({ index: 0 })} wrapperStyle={wrapperStyle({ index: 0 })}
dragOverlay dragOverlay
> >
<ContractGUI funcABI={abi[id]} /> <ContractGUI funcABI={abi[id]} funcId={id} />
</Item> </Item>
); );
} }
@ -532,6 +533,7 @@ export function MultipleContainers({
{items[containerId].map((item, index) => ( {items[containerId].map((item, index) => (
<Item <Item
key={item} key={item}
id={item}
handle={handle} handle={handle}
style={getItemStyles({ style={getItemStyles({
containerId, containerId,
@ -544,7 +546,7 @@ export function MultipleContainers({
})} })}
wrapperStyle={wrapperStyle({ index })} wrapperStyle={wrapperStyle({ index })}
> >
<ContractGUI funcABI={abi[item]} /> <ContractGUI funcABI={abi[item]} funcId={item} />
</Item> </Item>
))} ))}
</Container> </Container>
@ -626,6 +628,7 @@ function SortableItem({
return ( return (
<Item <Item
id={id}
ref={disabled ? undefined : setNodeRef} ref={disabled ? undefined : setNodeRef}
dragging={isDragging} dragging={isDragging}
sorting={isSorting} sorting={isSorting}
@ -647,7 +650,7 @@ function SortableItem({
listeners={listeners} listeners={listeners}
onRemove={onRemove} onRemove={onRemove}
> >
<ContractGUI funcABI={abi[id]} /> <ContractGUI funcABI={abi[id]} funcId={id} />
</Item> </Item>
); );
} }

@ -28,8 +28,10 @@
"quickDapp.no": "No", "quickDapp.no": "No",
"quickDapp.themes": "Themes", "quickDapp.themes": "Themes",
"quickDapp.deploy": "Deploy", "quickDapp.deploy": "Deploy",
"quickDapp.teardown": "Teardown",
"quickDapp.text4": "Deployed successfully!", "quickDapp.text4": "Deployed successfully!",
"quickDapp.text5": "Click the link below to view your dapp", "quickDapp.text5": "Click the link below to view your dapp",
"quickDapp.text6": "Teardown successfully!",
"quickDapp.uploadLogoTooltip": "Click here to change logo", "quickDapp.uploadLogoTooltip": "Click here to change logo",
"quickDapp.dappTitle": "Dapp Title", "quickDapp.dappTitle": "Dapp Title",
"quickDapp.dappInstructions": "Dapp Instructions", "quickDapp.dappInstructions": "Dapp Instructions",

@ -300,6 +300,7 @@ export function UniversalDappUI(props: UdappProps) {
{props.exEnvironment && props.exEnvironment.startsWith('injected') && ( {props.exEnvironment && props.exEnvironment.startsWith('injected') && (
<CustomTooltip placement="top" tooltipClasses="text-nowrap" tooltipId="udapp_udappEditTooltip" tooltipText={<FormattedMessage id="udapp.tooltipTextEdit" />}> <CustomTooltip placement="top" tooltipClasses="text-nowrap" tooltipId="udapp_udappEditTooltip" tooltipText={<FormattedMessage id="udapp.tooltipTextEdit" />}>
<i <i
data-id="instanceEditIcon"
className="fas fa-edit pr-3" className="fas fa-edit pr-3"
onClick={() => { onClick={() => {
props.editInstance(props.instance) props.editInstance(props.instance)

Loading…
Cancel
Save