Merge branch 'master' of https://github.com/ethereum/remix-project into desktope2e

pull/5370/head
bunsenstraat 9 months ago
commit 80863a719d
  1. 104
      .circleci/config.yml
  2. 3
      .eslintrc.json
  3. 4
      .github/workflows/pr-reminder.yml
  4. 2
      Dockerfile.dev
  5. 11
      apps/circuit-compiler/src/app/app.tsx
  6. 41
      apps/circuit-compiler/src/app/components/container.tsx
  7. 14
      apps/circuit-compiler/src/app/components/feedback.tsx
  8. 9
      apps/circuit-compiler/src/app/components/feedbackAlert.tsx
  9. 10
      apps/circuit-compiler/src/app/components/witness.tsx
  10. 145
      apps/circuit-compiler/src/app/services/circomPluginClient.ts
  11. 7
      apps/circuit-compiler/src/app/types/index.ts
  12. 3
      apps/circuit-compiler/src/index.html
  13. 2
      apps/circuit-compiler/src/snarkjs.min.js
  14. 1
      apps/etherscan/src/app/utils/networks.ts
  15. 134
      apps/learneth/README.md
  16. 58
      apps/learneth/project.json
  17. 19
      apps/learneth/src/App.css
  18. 41
      apps/learneth/src/App.tsx
  19. 28
      apps/learneth/src/components/BackButton/index.scss
  20. 87
      apps/learneth/src/components/BackButton/index.tsx
  21. 17
      apps/learneth/src/components/LoadingScreen/index.css
  22. 16
      apps/learneth/src/components/LoadingScreen/index.tsx
  23. 4
      apps/learneth/src/components/RepoImporter/index.css
  24. 120
      apps/learneth/src/components/RepoImporter/index.tsx
  25. 21
      apps/learneth/src/components/SlideIn/index.css
  26. 18
      apps/learneth/src/components/SlideIn/index.tsx
  27. 13
      apps/learneth/src/index.css
  28. 18
      apps/learneth/src/index.html
  29. 13
      apps/learneth/src/main.tsx
  30. 24
      apps/learneth/src/pages/Home/index.css
  31. 96
      apps/learneth/src/pages/Home/index.tsx
  32. 20
      apps/learneth/src/pages/Logo/index.tsx
  33. 54
      apps/learneth/src/pages/StepDetail/index.scss
  34. 228
      apps/learneth/src/pages/StepDetail/index.tsx
  35. 144
      apps/learneth/src/pages/StepList/index.scss
  36. 41
      apps/learneth/src/pages/StepList/index.tsx
  37. 7
      apps/learneth/src/polyfills.ts
  38. 21
      apps/learneth/src/profile.json
  39. 5
      apps/learneth/src/redux/hooks.ts
  40. 14
      apps/learneth/src/redux/models/loading.ts
  41. 229
      apps/learneth/src/redux/models/remixide.ts
  42. 164
      apps/learneth/src/redux/models/workshop.ts
  43. 97
      apps/learneth/src/redux/store.ts
  44. 38
      apps/learneth/src/remix-client.ts
  45. 23
      apps/learneth/tsconfig.app.json
  46. 16
      apps/learneth/tsconfig.json
  47. 90
      apps/learneth/webpack.config.js
  48. 4
      apps/remix-ide-e2e/package.json
  49. 6
      apps/remix-ide-e2e/src/commands/addFile.ts
  50. 15
      apps/remix-ide-e2e/src/commands/addLocalPlugin.ts
  51. 32
      apps/remix-ide-e2e/src/commands/checkTerminalFilter.ts
  52. 12
      apps/remix-ide-e2e/src/commands/clickLaunchIcon.ts
  53. 68
      apps/remix-ide-e2e/src/commands/connectToExternalHttpProvider.ts
  54. 4
      apps/remix-ide-e2e/src/commands/createContract.ts
  55. 2
      apps/remix-ide-e2e/src/commands/getLastTransactionHash.ts
  56. 10
      apps/remix-ide-e2e/src/commands/hideToolTips.ts
  57. 8
      apps/remix-ide-e2e/src/commands/journalChildIncludes.ts
  58. 2
      apps/remix-ide-e2e/src/commands/openFile.ts
  59. 6
      apps/remix-ide-e2e/src/examples/example-contracts.ts
  60. 1
      apps/remix-ide-e2e/src/helpers/init.ts
  61. 5
      apps/remix-ide-e2e/src/tests/ballot.test.ts
  62. 3
      apps/remix-ide-e2e/src/tests/ballot_0_4_14.test.ts
  63. 4
      apps/remix-ide-e2e/src/tests/circom.test.ts
  64. 10
      apps/remix-ide-e2e/src/tests/compiler_api.test.ts
  65. 80
      apps/remix-ide-e2e/src/tests/fileExplorer.test.ts
  66. 30
      apps/remix-ide-e2e/src/tests/file_explorer_context_menu.test.ts
  67. 178
      apps/remix-ide-e2e/src/tests/file_explorer_dragdrop.test.ts
  68. 42
      apps/remix-ide-e2e/src/tests/gist.test.ts
  69. 64
      apps/remix-ide-e2e/src/tests/pluginManager.test.ts
  70. 9
      apps/remix-ide-e2e/src/tests/plugin_api.ts
  71. 353
      apps/remix-ide-e2e/src/tests/proxy_oz_v5_non_shanghai_runtime.test.ts
  72. 2
      apps/remix-ide-e2e/src/tests/publishContract.test.ts
  73. 112
      apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts
  74. 60
      apps/remix-ide-e2e/src/tests/stressEditor.test.ts
  75. 24
      apps/remix-ide-e2e/src/tests/transactionExecution.test.ts
  76. 72
      apps/remix-ide-e2e/src/tests/url.test.ts
  77. 4
      apps/remix-ide-e2e/src/tests/verticalIconsPanel.test.ts
  78. 101
      apps/remix-ide-e2e/src/tests/vyper_api.test.ts
  79. 25
      apps/remix-ide-e2e/src/tests/workspace.test.ts
  80. 118
      apps/remix-ide-e2e/src/tests/workspace_git.test.ts
  81. 5
      apps/remix-ide-e2e/src/types/index.d.ts
  82. 34
      apps/remix-ide-e2e/yarn.lock
  83. 2
      apps/remix-ide/Dockerfile.dev
  84. 4
      apps/remix-ide/ci/makeMockCompiler.js
  85. 4
      apps/remix-ide/ci/sauceDisconnect.js
  86. 2
      apps/remix-ide/contracts/foundry/out/Script.sol/Script.json
  87. 10
      apps/remix-ide/meetings.md
  88. 2
      apps/remix-ide/project.json
  89. 13
      apps/remix-ide/src/app.js
  90. 30
      apps/remix-ide/src/app/components/preload.tsx
  91. 9
      apps/remix-ide/src/app/editor/editor.js
  92. 4
      apps/remix-ide/src/app/files/dgitProvider.ts
  93. 33
      apps/remix-ide/src/app/files/fileManager.ts
  94. 1
      apps/remix-ide/src/app/files/fileProvider.ts
  95. 13
      apps/remix-ide/src/app/files/workspaceFileProvider.js
  96. 22
      apps/remix-ide/src/app/panels/file-panel.js
  97. 16
      apps/remix-ide/src/app/panels/tab-proxy.js
  98. 41
      apps/remix-ide/src/app/panels/terminal.tsx
  99. 2
      apps/remix-ide/src/app/plugins/code-format.ts
  100. 2
      apps/remix-ide/src/app/plugins/compile-details.tsx
  101. Some files were not shown because too many files have changed in this diff Show More

@ -175,10 +175,83 @@ jobs:
key: remixdesktop-windows-deps-{{ checksum "apps/remixdesktop/yarn.lock" }}
paths:
- apps/remixdesktop/node_modules
- persist_to_workspace:
root: apps/remixdesktop
paths:
- "release"
# see https://docs.digicert.com/en/software-trust-manager/ci-cd-integrations/script-integrations/github-integration-ksp.html
sign-remixdesktop-windows:
executor: win/default # executor type
working_directory: ~/remix-project
steps:
- checkout
- attach_workspace:
at: .
- run:
name: "Certificate-Setup"
shell: powershell.exe
command: |
cd C:\
New-Item C:\CERT_FILE.p12.b64
Set-Content -Path C:\CERT_FILE.p12.b64 -Value $env:SM_CLIENT_CERT_FILE_B64
certutil -decode CERT_FILE.p12.b64 Certificate_pkcs12.p12
cat Certificate_pkcs12.p12
- restore_cache:
name: Restore smtools-windows-x64.msi
keys:
- dl-smtools-windows-x64.msi
- run:
name: "Client-Tool-Download"
shell: powershell.exe
command: |
cd C:\
if (Test-Path 'c:\smtools-windows-x64.msi') {
echo 'File exists, skipping download...'
} else {
echo 'Downloading smtools-windows-x64.msi ...'
curl.exe -X GET https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download -H "x-api-key:$env:SM_API_KEY" -o smtools-windows-x64.msi
}
- save_cache:
key: dl-smtools-windows-x64.msi
paths:
- c:\smtools-windows-x64.msi
- run:
name: "Client-Tool-Setup"
shell: powershell.exe
command: |
cd C:\
msiexec.exe /i smtools-windows-x64.msi /quiet /qn | Wait-Process
& $env:SSM\smksp_cert_sync.exe
& $env:SSM\smctl.exe healthcheck
- run:
name: "Find Signtool"
shell: powershell.exe
command: |
Get-ChildItem -Path 'C:\Program Files (x86)\Windows Kits\10\App Certification Kit' -Filter signtool.exe -Recurse
- run:
name: "Signtool-Signing"
shell: powershell.exe
command: |
& $env:Signtool sign /sha1 $env:SM_CODE_SIGNING_CERT_SHA1_HASH /tr http://timestamp.digicert.com /td SHA256 /fd SHA256 $env:RemixSetupExe
- run:
name: "Signtool-Verification"
shell: powershell.exe
command: |
$verify_output = $(& $env:Signtool verify /v /pa $env:RemixSetupExe)
echo ${verify_output}
if (!$verify_output.Contains("Number of files successfully Verified: 1")) {
echo 'Verification failed'
exit 1
}
- store_artifacts:
path: apps/remixdesktop/release/
path: ~/remix-project/release/
destination: remixdesktop-windows
environment:
SM_CLIENT_CERT_FILE: 'C:\Certificate_pkcs12.p12'
Signtool: 'C:\Program Files (x86)\Windows Kits\10\App Certification Kit\signtool.exe'
SSM: 'C:\Program Files\DigiCert\DigiCert One Signing Manager Tools'
RemixSetupExe: 'C:\Users\circleci\remix-project\release\Remix IDE.exe'
build-remixdesktop-mac:
macos:
xcode: 14.2.0
@ -315,16 +388,25 @@ jobs:
- run: yarn install --cwd ./apps/remix-ide-e2e --modules-folder ../../node_modules
- run: mkdir node_modules/hardhat && wget https://unpkg.com/hardhat/console.sol -O node_modules/hardhat/console.sol
- run: ls -la ./dist/apps/remix-ide/assets/js
- run: yarn run selenium-install || yarn run selenium-install
- run: yarn run selenium-install --singleDriverInstall=firefox
- when:
condition:
equal: [ "chrome", << parameters.browser >> ]
steps:
- run: mkdir -p node_modules/selenium-standalone/.selenium/chromedriver/latest-x64/
- run: cp ~/bin/chromedriver /home/circleci/remix-project/node_modules/selenium-standalone/.selenium/chromedriver/latest-x64/
- run:
name: Start Selenium
command: yarn run selenium
background: true
- run:
name: run selenium
command: yarn selenium-standalone start --singleDriverStart=chrome
background: true
- when:
condition:
equal: [ "firefox", << parameters.browser >> ]
steps:
- run:
name: run selenium
command: yarn selenium-standalone start --singleDriverStart=firefox
background: true
- run: ./apps/remix-ide/ci/<< parameters.script >> << parameters.browser >> << parameters.jobsize >> << parameters.job >>
- store_test_results:
path: ./reports/tests
@ -366,11 +448,12 @@ jobs:
- run: unzip ./persist/dist.zip
- run: unzip ./persist/plugin-<< parameters.plugin >>.zip
- run: yarn install --cwd ./apps/remix-ide-e2e --modules-folder ../../node_modules
- run: yarn run selenium-install || yarn run selenium-install
- run: yarn run selenium-install --singleDriverInstall=firefox
- run: mkdir -p node_modules/selenium-standalone/.selenium/chromedriver/latest-x64/
- run: cp ~/bin/chromedriver /home/circleci/remix-project/node_modules/selenium-standalone/.selenium/chromedriver/latest-x64/
- run:
name: Start Selenium
command: yarn run selenium
command: yarn run selenium --singleDriverStart=chrome
background: true
- run: ./apps/remix-ide/ci/browser_test_plugin.sh << parameters.plugin >>
- store_test_results:
@ -452,6 +535,9 @@ workflows:
- build-remixdesktop-windows:
requires:
- build-desktop
- sign-remixdesktop-windows:
requires:
- build-remixdesktop-windows
- build-remixdesktop-linux:
requires:
- build-desktop

@ -29,7 +29,8 @@
}
]
}
]
],
"keyword-spacing": "error"
}
},
{

@ -2,7 +2,7 @@ name: PRs reviews reminder
on:
schedule:
- cron: "0 8-17/8 * * 1-5"
- cron: "0 8 * * 1-5"
workflow_dispatch:
jobs:
@ -14,4 +14,4 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
freeze-date: '2024-01-08T12:00:00Z'
freeze-date: '2024-03-11T18:00:00Z'

@ -1,4 +1,4 @@
# This dockerfile is to build each branch seperately (for dev purposes)
# This dockerfile is to build each branch separately (for dev purposes)
FROM node:10
# Create Remix user, don't use root!
# RUN yes | adduser --disabled-password remix && mkdir /app

@ -29,8 +29,6 @@ function App() {
if (filePath.endsWith('.circom')) {
dispatch({ type: 'SET_FILE_PATH', payload: filePath })
plugin.parse(filePath)
} else {
dispatch({ type: 'SET_FILE_PATH', payload: '' })
}
})
// @ts-ignore
@ -70,11 +68,13 @@ function App() {
dispatch({ type: 'SET_FILE_PATH_TO_ID', payload: filePathToId })
dispatch({ type: 'SET_COMPILER_FEEDBACK', payload: null })
})
plugin.internalEvents.on('circuit_parsing_errored', (report) => {
plugin.internalEvents.on('circuit_parsing_errored', (report, filePathToId) => {
dispatch({ type: 'SET_FILE_PATH_TO_ID', payload: filePathToId })
dispatch({ type: 'SET_COMPILER_STATUS', payload: 'errored' })
dispatch({ type: 'SET_COMPILER_FEEDBACK', payload: report })
})
plugin.internalEvents.on('circuit_parsing_warning', (report) => {
plugin.internalEvents.on('circuit_parsing_warning', (report, filePathToId) => {
dispatch({ type: 'SET_FILE_PATH_TO_ID', payload: filePathToId })
dispatch({ type: 'SET_COMPILER_STATUS', payload: 'warning' })
dispatch({ type: 'SET_COMPILER_FEEDBACK', payload: report })
})
@ -100,6 +100,9 @@ function App() {
(async () => {
if (appState.autoCompile) await compileCircuit(plugin, appState)
})()
dispatch({ type: 'SET_SIGNAL_INPUTS', payload: [] })
dispatch({ type: 'SET_COMPILER_STATUS', payload: 'idle' })
dispatch({ type: 'SET_COMPILER_FEEDBACK', payload: null })
}
}, [appState.filePath])

@ -10,7 +10,7 @@ import { CircuitActions } from './actions'
import { WitnessToggler } from './witnessToggler'
import { WitnessSection } from './witness'
import { CompilerFeedback } from './feedback'
import { PrimeValue } from '../types'
import { CompilerReport, PrimeValue } from '../types'
export function Container () {
const circuitApp = useContext(CircuitAppContext)
@ -58,6 +58,43 @@ export function Container () {
circuitApp.dispatch({ type: 'SET_HIDE_WARNINGS', payload: value })
}
const askGPT = async (report: CompilerReport) => {
if (report.labels.length > 0) {
const location = circuitApp.appState.filePathToId[report.labels[0].file_id]
const error = report.labels[0].message
if (location) {
const fullPathLocation = await circuitApp.plugin.resolveReportPath(location)
const content = await circuitApp.plugin.call('fileManager', 'readFile', fullPathLocation)
const message = `
circom code: ${content}
error message: ${error}
full circom error: ${JSON.stringify(report, null, 2)}
explain why the error occurred and how to fix it.
`
// @ts-ignore
await circuitApp.plugin.call('openaigpt', 'message', message)
} else {
const message = `
error message: ${error}
full circom error: ${JSON.stringify(report, null, 2)}
explain why the error occurred and how to fix it.
`
// @ts-ignore
await circuitApp.plugin.call('openaigpt', 'message', message)
}
} else {
const error = report.message
const message = `
error message: ${error}
full circom error: ${JSON.stringify(report, null, 2)}
explain why the error occurred and how to fix it.
`
// @ts-ignore
await circuitApp.plugin.call('openaigpt', 'message', message)
}
}
return (
<section>
<article>
@ -86,7 +123,7 @@ export function Container () {
</WitnessToggler>
</RenderIf>
<RenderIf condition={(circuitApp.appState.status !== 'compiling') && (circuitApp.appState.status !== 'computing') && (circuitApp.appState.status !== 'generating')}>
<CompilerFeedback feedback={circuitApp.appState.feedback} filePathToId={circuitApp.appState.filePathToId} openErrorLocation={handleOpenErrorLocation} hideWarnings={circuitApp.appState.hideWarnings} />
<CompilerFeedback feedback={circuitApp.appState.feedback} filePathToId={circuitApp.appState.filePathToId} openErrorLocation={handleOpenErrorLocation} hideWarnings={circuitApp.appState.hideWarnings} askGPT={askGPT} />
</RenderIf>
</div>
</div>

@ -4,7 +4,7 @@ import { RenderIf } from '@remix-ui/helper'
import {CopyToClipboard} from '@remix-ui/clipboard'
import { FeedbackAlert } from './feedbackAlert'
export function CompilerFeedback ({ feedback, filePathToId, hideWarnings, openErrorLocation }: CompilerFeedbackProps) {
export function CompilerFeedback ({ feedback, filePathToId, hideWarnings, openErrorLocation, askGPT }: CompilerFeedbackProps) {
const [ showException, setShowException ] = useState<boolean>(true)
const handleCloseException = () => {
@ -17,6 +17,10 @@ export function CompilerFeedback ({ feedback, filePathToId, hideWarnings, openEr
}
}
const handleAskGPT = (report: CompilerReport) => {
askGPT(report)
}
return (
<div>
<div className="circuit_errors_box py-4">
@ -40,12 +44,16 @@ export function CompilerFeedback ({ feedback, filePathToId, hideWarnings, openEr
<div key={index} onClick={() => handleOpenError(response)}>
<RenderIf condition={response.type === 'Error'}>
<div className={`circuit_feedback ${response.type.toLowerCase()} alert alert-danger`} data-id="circuit_feedback">
<FeedbackAlert message={response.message} location={ response.labels[0] ? response.labels[0].message + ` ${filePathToId[response.labels[0].file_id]}:${response.labels[0].range.start}:${response.labels[0].range.end}` : null} />
<FeedbackAlert
message={response.message + (response.labels[0] ? ": " + response.labels[0].message + ` ${filePathToId[response.labels[0].file_id]}:${response.labels[0].range.start}:${response.labels[0].range.end}` : '')}
askGPT={ () => handleAskGPT(response) } />
</div>
</RenderIf>
<RenderIf condition={(response.type === 'Warning') && !hideWarnings}>
<div className={`circuit_feedback ${response.type.toLowerCase()} alert alert-warning`} data-id="circuit_feedback">
<FeedbackAlert message={response.message} location={null} />
<FeedbackAlert
message={response.message}
askGPT={() => { handleAskGPT(response) }} />
</div>
</RenderIf>
</div>

@ -3,7 +3,7 @@ import { FeedbackAlertProps } from '../types'
import { RenderIf } from '@remix-ui/helper'
import {CopyToClipboard} from '@remix-ui/clipboard'
export function FeedbackAlert ({ message, location }: FeedbackAlertProps) {
export function FeedbackAlert ({ message, askGPT }: FeedbackAlertProps) {
const [ showAlert, setShowAlert] = useState<boolean>(true)
const handleCloseAlert = () => {
@ -14,9 +14,6 @@ export function FeedbackAlert ({ message, location }: FeedbackAlertProps) {
<RenderIf condition={showAlert}>
<>
<span> { message } </span>
<RenderIf condition={location !== null}>
<span> { location }</span>
</RenderIf>
<div className="close" data-id="renderer" onClick={handleCloseAlert}>
<i className="fas fa-times"></i>
</div>
@ -24,6 +21,10 @@ export function FeedbackAlert ({ message, location }: FeedbackAlertProps) {
<span className="ml-3 pt-1 py-1" >
<CopyToClipboard content={message} className="p-0 m-0 far fa-copy error" direction={'top'} />
</span>
<span className="border border-success text-success btn-sm" onClick={(e) => {
e.stopPropagation()
askGPT()
}}>ASK GPT</span>
</div>
</>
</RenderIf>

@ -4,14 +4,22 @@ import { CompilerStatus } from "../types";
import { computeWitness } from "../actions";
import { useState } from "react";
import type { CircomPluginClient } from "../services/circomPluginClient";
import * as remixLib from '@remix-project/remix-lib'
export function WitnessSection ({ plugin, signalInputs, status }: {plugin: CircomPluginClient, signalInputs: string[], status: CompilerStatus}) {
const [witnessValues, setWitnessValues] = useState<Record<string, string>>({})
const handleSignalInput = (e: any) => {
let value = e.target.value
try {
value = remixLib.execution.txFormat.parseFunctionParams(value)
} catch (e) {
// do nothing
}
setWitnessValues({
...witnessValues,
[e.target.name]: e.target.value
[e.target.name]: value[0]
})
}

@ -21,7 +21,7 @@ export class CircomPluginClient extends PluginClient {
constructor() {
super()
this.methods = ['init', 'parse', 'compile', 'generateR1cs']
this.methods = ['init', 'parse', 'compile', 'generateR1cs', 'resolveDependencies']
createClient(this)
this.internalEvents = new EventManager()
this.onload()
@ -53,7 +53,7 @@ export class CircomPluginClient extends PluginClient {
// @ts-ignore
fileContent = await this.call('fileManager', 'readFile', path)
}
this.lastParsedFiles = await this.resolveDependencies(path, fileContent, { [path]: { content: fileContent, parent: null } })
this.lastParsedFiles = await this.resolveDependencies(path, fileContent)
const parsedOutput = this.compiler.parse(path, this.lastParsedFiles)
try {
@ -123,14 +123,18 @@ export class CircomPluginClient extends PluginClient {
async compile(path: string, compilationConfig?: CompilationConfig): Promise<void> {
this.internalEvents.emit('circuit_compiling_start')
// @ts-ignore
this.call('terminal', 'log', { type: 'log', value: 'Compiling ' + path })
const [parseErrors, filePathToId] = await this.parse(path)
if (parseErrors && (parseErrors.length > 0)) {
if (parseErrors[0].type === 'Error') {
this.internalEvents.emit('circuit_parsing_errored', parseErrors)
this.internalEvents.emit('circuit_parsing_errored', parseErrors, filePathToId)
this.logCompilerReport(parseErrors)
return
} else if (parseErrors[0].type === 'Warning') {
this.internalEvents.emit('circuit_parsing_warning', parseErrors)
this.internalEvents.emit('circuit_parsing_warning', parseErrors, filePathToId)
this.logCompilerReport(parseErrors)
}
} else {
this.internalEvents.emit('circuit_parsing_done', parseErrors, filePathToId)
@ -147,6 +151,7 @@ export class CircomPluginClient extends PluginClient {
if (circuitProgram.length < 1) {
const circuitErrors = circuitApi.report()
this.logCompilerReport(circuitErrors)
throw new Error(circuitErrors)
} else {
this.lastCompiledFile = path
@ -166,19 +171,28 @@ export class CircomPluginClient extends PluginClient {
} else {
this.internalEvents.emit('circuit_compiling_done', [])
}
circuitApi.log().map(log => {
log && this.call('terminal', 'log', { type: 'log', value: log })
})
// @ts-ignore
this.call('terminal', 'log', { type: 'typewritersuccess', value: 'Everything went okay, circom safe' })
}
}
async generateR1cs (path: string, compilationConfig?: CompilationConfig): Promise<void> {
this.internalEvents.emit('circuit_generating_r1cs_start')
// @ts-ignore
this.call('terminal', 'log', { type: 'log', value: 'Generating R1CS for ' + path })
const [parseErrors, filePathToId] = await this.parse(path)
if (parseErrors && (parseErrors.length > 0)) {
if (parseErrors[0].type === 'Error') {
this.internalEvents.emit('circuit_parsing_errored', parseErrors)
this.logCompilerReport(parseErrors)
return
} else if (parseErrors[0].type === 'Warning') {
this.internalEvents.emit('circuit_parsing_warning', parseErrors)
this.logCompilerReport(parseErrors)
}
} else {
this.internalEvents.emit('circuit_parsing_done', parseErrors, filePathToId)
@ -195,6 +209,7 @@ export class CircomPluginClient extends PluginClient {
if (r1csProgram.length < 1) {
const r1csErrors = r1csApi.report()
this.logCompilerReport(r1csErrors)
throw new Error(r1csErrors)
} else {
this.internalEvents.emit('circuit_generating_r1cs_done')
@ -203,6 +218,11 @@ export class CircomPluginClient extends PluginClient {
// @ts-ignore
await this.call('fileManager', 'writeFile', writePath, r1csProgram, true)
r1csApi.log().map(log => {
log && this.call('terminal', 'log', { type: 'log', value: log })
})
// @ts-ignore
this.call('terminal', 'log', { type: 'typewritersuccess', value: 'Everything went okay, circom safe' })
}
}
@ -220,15 +240,17 @@ export class CircomPluginClient extends PluginClient {
this.internalEvents.emit('circuit_computing_witness_done')
}
async resolveDependencies(filePath: string, fileContent: string, output: ResolverOutput = {}, depPath: string = '', parent: string = ''): Promise<Record<string, string>> {
async resolveDependencies(filePath: string, fileContent: string, output?: Record<string, string>, depPath: string = '', blackPath: string[] = []): Promise<Record<string, string>> {
if (!output) output = { [filePath]: fileContent }
// extract all includes
const includes = (fileContent.match(/include ['"].*['"]/g) || []).map((include) => include.replace(/include ['"]/g, '').replace(/['"]/g, ''))
await Promise.all(
includes.map(async (include) => {
// fix for endless recursive includes
if (blackPath.includes(include)) return
let dependencyContent = ''
let path = include
let path = include.replace(/(\.\.\/)+/g, '')
// @ts-ignore
const pathExists = await this.call('fileManager', 'exists', path)
@ -248,23 +270,35 @@ export class CircomPluginClient extends PluginClient {
if (relativePathExists) {
// fetch file content if include import exists as a relative path
path = relativePath
dependencyContent = await this.call('fileManager', 'readFile', relativePath)
} else {
if (include.startsWith('circomlib')) {
// try to resolve include import from github if it is a circomlib dependency
const splitInclude = include.split('/')
const version = splitInclude[1].match(/v[0-9]+.[0-9]+.[0-9]+/g)
if (version && version[0]) {
path = `https://raw.githubusercontent.com/iden3/circomlib/${version[0]}/circuits/${splitInclude.slice(2).join('/')}`
dependencyContent = await this.call('contentImport', 'resolveAndSave', path, null)
} else {
path = `https://raw.githubusercontent.com/iden3/circomlib/master/circuits/${splitInclude.slice(1).join('/')}`
dependencyContent = await this.call('contentImport', 'resolveAndSave', path, null)
try {
// try to resolve include import from .deps folder
if (version && version[0]) {
path = `.deps/https/raw.githubusercontent.com/iden3/circomlib/${version[0]}/${splitInclude.slice(2).join('/')}`
dependencyContent = await this.call('contentImport', 'resolveAndSave', path, null)
} else {
path = `.deps/https/raw.githubusercontent.com/iden3/circomlib/master/${splitInclude.slice(1).join('/')}`
dependencyContent = await this.call('contentImport', 'resolveAndSave', path, null)
}
} catch (e) {
// try to resolve include import from github if it is a circomlib dependency
if (version && version[0]) {
path = `https://raw.githubusercontent.com/iden3/circomlib/${version[0]}/${splitInclude.slice(2).join('/')}`
dependencyContent = await this.call('contentImport', 'resolveAndSave', path, null)
} else {
path = `https://raw.githubusercontent.com/iden3/circomlib/master/${splitInclude.slice(1).join('/')}`
dependencyContent = await this.call('contentImport', 'resolveAndSave', path, null)
}
}
} else {
if (depPath) {
// if depPath is provided, try to resolve include import from './deps' folder in remix
// resolves relative dependencies for .deps folder
path = pathModule.resolve(depPath.slice(0, depPath.lastIndexOf('/')), include)
path = path.replace('https:/', 'https://')
if (path.indexOf('/') === 0) path = path.slice(1)
@ -276,43 +310,52 @@ export class CircomPluginClient extends PluginClient {
}
}
}
const fileNameToInclude = extractNameFromKey(include)
const similarFile = Object.keys(output).find(path => {
return path.indexOf(fileNameToInclude) > -1
})
const isDuplicateContent = similarFile && output[similarFile] ? output[similarFile].content === dependencyContent : false
if (output[include] && output[include].parent) {
// if include import already exists, remove the include import from the parent file
const regexPattern = new RegExp(`include ['"]${include}['"];`, 'g')
output[output[include].parent].content = output[output[include].parent].content.replace(regexPattern, "")
} else if (isDuplicateContent) {
// if include import has the same content as another file, replace the include import with the file name of the other file (similarFile)
if (output[similarFile].parent) output[output[similarFile].parent].content = output[output[similarFile].parent].content.replace(similarFile, include)
if (include !== similarFile) {
output[include] = output[similarFile]
delete output[similarFile]
}
if (path.indexOf('https://') === 0) {
// Regular expression to match include statements and make deps imports uniform
const includeRegex = /include "(.+?)";/g
const replacement = 'include "circomlib/circuits/$1";'
dependencyContent = dependencyContent.replace(includeRegex, replacement)
} else {
// extract all includes from the dependency content
const dependencyIncludes = (dependencyContent.match(/include ['"].*['"]/g) || []).map((include) => include.replace(/include ['"]/g, '').replace(/['"]/g, ''))
output[include] = {
content: dependencyContent,
parent
if (!include.startsWith('circomlib') && !pathModule.isAbsolute(filePath) && !pathModule.isAbsolute(path)) {
// if include is not absolute, resolve it using the parent path of the current file opened in editor
const absIncludePath = pathModule.resolve('/' + filePath.slice(0, filePath.lastIndexOf('/')), '/' + path)
output[filePath] = output[filePath].replace(`${include}`, `${absIncludePath}`)
include = absIncludePath
}
// recursively resolve all dependencies of the dependency
if (dependencyIncludes.length > 0) await this.resolveDependencies(filePath, dependencyContent, output, path, include)
}
// extract all includes from the dependency content
const dependencyIncludes = (dependencyContent.match(/include ['"].*['"]/g) || []).map((childInclude) => {
const includeName = childInclude.replace(/include ['"]/g, '').replace(/['"]/g, '')
let absFilePath = pathModule.resolve(include.slice(0, include.lastIndexOf('/')), includeName)
absFilePath = include.startsWith('circomlib') ? absFilePath.substring(1) : absFilePath
if (!blackPath.includes(absFilePath)) {
if(!includeName.startsWith('circomlib')) {
dependencyContent = dependencyContent.replace(`${includeName}`, `${absFilePath}`)
return absFilePath
}
return includeName
} else {
// if include already exists in output, remove it from the dependency content
const includePattern = new RegExp(`include "\\s*${includeName}\\s*";`, 'g')
dependencyContent = dependencyContent.replace(includePattern, '')
return
}
}).filter((childInclude) => childInclude)
blackPath.push(include)
// recursively resolve all dependencies of the dependency
if (dependencyIncludes.length > 0) {
await this.resolveDependencies(filePath, dependencyContent, output, path, blackPath)
output[include] = dependencyContent
} else {
output[include] = dependencyContent
}
})
)
const result: Record<string, string> = {}
Object.keys(output).forEach((key) => {
result[key] = output[key].content
})
return result
return output
}
async resolveReportPath (path: string): Promise<string> {
@ -335,9 +378,9 @@ export class CircomPluginClient extends PluginClient {
const version = splitInclude[1].match(/v[0-9]+.[0-9]+.[0-9]+/g)
if (version && version[0]) {
path = `/.deps/https/raw.githubusercontent.com/iden3/circomlib/${version[0]}/circuits/${splitInclude.slice(2).join('/')}`
path = `/.deps/https/raw.githubusercontent.com/iden3/circomlib/${version[0]}/${splitInclude.slice(2).join('/')}`
} else {
path = `/.deps/https/raw.githubusercontent.com/iden3/circomlib/master/circuits/${splitInclude.slice(1).join('/')}`
path = `/.deps/https/raw.githubusercontent.com/iden3/circomlib/master/${splitInclude.slice(1).join('/')}`
}
// @ts-ignore
const exists = await this.call('fileManager', 'exists', path)
@ -350,4 +393,10 @@ export class CircomPluginClient extends PluginClient {
}
}
}
async logCompilerReport (report: CompilerReport[]): Promise<void> {
this.call('terminal', 'log', { type: 'log', value: JSON.stringify(report, null, 2) })
if (report[0].type === 'Error') this.call('terminal', 'log', { type: 'error', value: 'previous errors were found' })
if (report[0].type === 'Warning') this.call('terminal', 'log', { type: 'log', value: 'previous warnings were found' })
}
}

@ -1,6 +1,6 @@
import { compiler_list } from 'circom_wasm'
import {Dispatch} from 'react'
import { CircomPluginClient } from '../services/circomPluginClient'
import type { CircomPluginClient } from '../services/circomPluginClient'
export type CompilerStatus = "compiling" | "generating" | "computing" | "idle" | "errored" | "warning"
export interface ICircuitAppContext {
@ -51,7 +51,8 @@ export type CompilerFeedbackProps = {
feedback: string | CompilerReport[],
filePathToId: Record<string, string>,
openErrorLocation: (location: string, startRange: string) => void,
hideWarnings: boolean
hideWarnings: boolean,
askGPT: (report: CompilerReport) => void
}
export type CompilerReport = {
@ -71,7 +72,7 @@ export type CompilerReport = {
export type FeedbackAlertProps = {
message: string,
location: string
askGPT: () => void
}
export type ConfigurationsProps = {

@ -12,6 +12,9 @@
crossorigin="anonymous" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css">
</head>
<body>
<script>
var global = window
</script>
<div id="root"></div>
<script src="snarkjs.min.js"> </script>
<script src="https://kit.fontawesome.com/41dd021e94.js" crossorigin="anonymous"></script>

File diff suppressed because one or more lines are too long

@ -34,6 +34,7 @@ export const scanAPIurls = {
44787: 'https://api-alfajores.celoscan.io/api',
2888: 'https://api-testnet.bobascan.com/api',
84531: 'https://api-goerli.basescan.org/api',
84532: "https://api-sepolia.basescan.org/api",
1442: 'https://api-testnet-zkevm.polygonscan.com/api',
59140: 'https://api-testnet.lineascan.build/api',
}

@ -0,0 +1,134 @@
# Remix LearnEth Plugin
## Available Scripts
In the project directory, you can run:
### `npm run serve:plugin --plugin=learneth`
Runs the app in the development mode.\
Open [http://localhost:2024](http://localhost:2024) to view it in the browser.
The page will reload if you make edits.\
You will also see any lint errors in the console.
### `npm run build:plugin --plugin=learneth`
Builds the app for production to the `dist/apps/learneth` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
## Loading the plugin in remix
When testing with localhost you should use the HTTP version of either REMIX or REMIX ALPHA. Click on the plugin manager icon and
add the plugin 'Connect to a local plugin'. Your plugin will be at http://localhost:2024/.
## Setting up the REMIX IDE for working with the plugin
The plugin only works when a compiler environment is loaded as well, for example on the home screen of the IDE you select 'Solidity' or 'Vyper'. Without this the plugin
cannot compile and test files in the workshops.
## Setting up your Github workshops repo
You can create your own workshops that can be imported in the plugin.
When importing a github repo the plugin will look for a directory structure describing the workshops.
For example: https://github.com/ethereum/remix-workshops
### Root directories
Root directories are individual workshops, the name used will be the name of the workshop unless you override this with the name property in the config.yml.
### README.md
The readme in each directry contains an explanation of what the workshop is about. If an additional summary property is provided in the config.yml that will be used in the overview section of the plugin.
### config.yml
This config file contains meta data describing some properties of your workshop, for example
```
---
id: someid
name: my workshop name
summary: something about this workshop
level: 4
tags:
- solidity
- beginner
```
Level: a level of difficulty indicator ( 1 - 5 )
Tags: an array of tags
id: this is used by the system to let REMIX call startTutorial(repo,branch,id). See below for more instructions.
### Steps
Each workshop contains what we call steps.
Each step is a directory containing:
- a readme describing the step, what to do.
- sol files:
- these can be sol files and test sol files. The test files should be name yoursolname_test.sol
- ANSWER files: these are named yoursolname_answer.sol and can be used to show the solution or the correct answer. The plugin will load the
file in the IDE when a user clicks on 'Show Answer'
- js files
- vyper files
## Functions to call the plugin from the IDE
### Add a repository:
```
addRepository(repoName, branch)
```
### Start a tutorial
```
startTutorial(repoName,branch,id)
```
You don't need to add a seperate addRepository before calling startTutorial, this call will also add the repo.
_Parameters_
id: this can be two things:
- type of number, it specifies the n-th tutorial in the list
- type of string, this refers to the ID parameter in the config.yml file in the tutorial
for example:
```
---
id: basics
name: 1 Basics of Solidity
summary: Some basic functions explained
level: 4
tags:
- solidity
```
### How to call these functions in the REMIX IDE
```
(function () {
try {
// You don't need to add a seperate addRepository before calling startTutorial, this is just an example
remix.call('LearnEth', 'addRepository', "ethereum/remix-workshops", "master")
remix.call('LearnEth', 'startTutorial', "ethereum/remix-workshops", "master", "basics")
remix.call('LearnEth', 'startTutorial', "ethereum/remix-workshops", "master", 2)
} catch (e) {
console.log(e.message)
}
})()
```
Then call this in the REMIX console
```
remix.exeCurrent()
```

@ -0,0 +1,58 @@
{
"name": "learneth",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/learneth/src",
"projectType": "application",
"implicitDependencies": [],
"targets": {
"build": {
"executor": "@nrwl/webpack:webpack",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "development",
"options": {
"compiler": "babel",
"outputPath": "dist/apps/learneth",
"index": "apps/learneth/src/index.html",
"baseHref": "./",
"main": "apps/learneth/src/main.tsx",
"polyfills": "apps/learneth/src/polyfills.ts",
"tsConfig": "apps/learneth/tsconfig.app.json",
"assets": ["apps/learneth/src/profile.json"],
"styles": ["apps/learneth/src/index.css"],
"scripts": [],
"webpackConfig": "apps/learneth/webpack.config.js"
},
"configurations": {
"development": {
},
"production": {
"fileReplacements": [
{
"replace": "apps/learneth/src/environments/environment.ts",
"with": "apps/learneth/src/environments/environment.prod.ts"
}
]
}
}
},
"serve": {
"executor": "@nrwl/webpack:dev-server",
"defaultConfiguration": "development",
"options": {
"buildTarget": "learneth:build",
"hmr": true,
"baseHref": "/"
},
"configurations": {
"development": {
"buildTarget": "learneth:build:development",
"port": 2024
},
"production": {
"buildTarget": "learneth:build:production"
}
}
}
},
"tags": []
}

@ -0,0 +1,19 @@
/* You can add global styles to this file, and also import other style files */
h1{
font-size: 1.2rem !important;
font-weight: 700;
}
h2{
font-size: 1rem !important;
font-weight: 700;
}
h3{
font-size: 1rem !important;
}
p {
font-size: 0.9rem;
}

@ -0,0 +1,41 @@
import React from 'react'
import {createHashRouter, RouterProvider} from 'react-router-dom'
import {ToastContainer} from 'react-toastify'
import LoadingScreen from './components/LoadingScreen'
import LogoPage from './pages/Logo'
import HomePage from './pages/Home'
import StepListPage from './pages/StepList'
import StepDetailPage from './pages/StepDetail'
import 'react-toastify/dist/ReactToastify.css'
import './App.css'
export const router = createHashRouter([
{
path: '/',
element: <LogoPage />,
},
{
path: '/home',
element: <HomePage />,
},
{
path: '/list',
element: <StepListPage />,
},
{
path: '/detail',
element: <StepDetailPage />,
},
])
function App(): JSX.Element {
return (
<>
<RouterProvider router={router} />
<LoadingScreen />
<ToastContainer position="bottom-right" newestOnTop closeOnClick rtl={false} pauseOnFocusLoss draggable pauseOnHover autoClose={false} theme="colored" />
</>
)
}
export default App

@ -0,0 +1,28 @@
a {
.arrow {
display: inline-block;
opacity: 0;
transform: scale(0.5);
transition: all 0.3s;
}
span {
display: inline-block;
padding-left: 5px;
transform: translateX(-0.875em); // size of icon
transition: transform 0.3s;
}
}
.workshoptitle{
text-decoration: none;
}
a:hover {
fa-icon {
opacity: 1;
transform: scale(1);
}
span {
transform: translateX(0);
}
}

@ -0,0 +1,87 @@
import React, {useState} from 'react'
import {Link, useLocation, useNavigate} from 'react-router-dom'
import {Button, Modal, Tooltip, OverlayTrigger} from 'react-bootstrap'
import './index.scss'
function BackButton({entity}: any) {
const navigate = useNavigate()
const location = useLocation()
const [show, setShow] = useState(false)
const isDetailPage = location.pathname === '/detail'
const queryParams = new URLSearchParams(location.search)
const stepId = Number(queryParams.get('stepId'))
return (
<nav className="navbar navbar-light bg-light justify-content-between pt-1 pb-1 pl-1">
<ul className="nav mr-auto">
<li className="nav-item">
<div
className="btn back"
onClick={() => {
setShow(true)
}}
role="button"
>
<OverlayTrigger placement="right" overlay={<Tooltip id="tooltip-right">Leave tutorial</Tooltip>}>
<i className="fas fa-home pl-1" />
</OverlayTrigger>
</div>
</li>
{isDetailPage && (
<li className="nav-item">
<Link className="btn" to={`/list?id=${entity.id}`} title="Tutorial menu">
<i className="fas fa-bars" />
</Link>
</li>
)}
</ul>
{isDetailPage && (
<form className="form-inline">
{stepId > 0 && (
<Link to={`/detail?id=${entity.id}&stepId=${stepId - 1}`}>
<i className="fas fa-chevron-left pr-1" />
</Link>
)}
{stepId + 1}/{entity && <div className="">{entity.steps.length}</div>}
{stepId < entity.steps.length - 1 && (
<Link to={`/detail?id=${entity.id}&stepId=${stepId + 1}`}>
<i className="fas fa-chevron-right pl-1" />
</Link>
)}
</form>
)}
<Modal
show={show}
onHide={() => {
setShow(false)
}}
>
<Modal.Header placeholder={''} closeButton>
<Modal.Title>Leave tutorial</Modal.Title>
</Modal.Header>
<Modal.Body>Are you sure you want to leave the tutorial?</Modal.Body>
<Modal.Footer>
<Button
variant="secondary"
onClick={() => {
setShow(false)
}}
>
No
</Button>
<Button
variant="success"
onClick={() => {
setShow(false)
navigate('/home')
}}
>
Yes
</Button>
</Modal.Footer>
</Modal>
</nav>
)
}
export default BackButton

@ -0,0 +1,17 @@
.spinnersOverlay {
background-color: rgba(51, 51, 51, 0.8);
z-index: 99;
opacity: 1;
height: 100%;
left: 0;
position: fixed;
top: 0;
width: 100%;
}
.spinnersLoading {
left: 50%;
margin: 0;
position: absolute;
top: 50%;
transform: translate(-50%,-50%);
}

@ -0,0 +1,16 @@
import React from 'react'
import BounceLoader from 'react-spinners/BounceLoader'
import './index.css'
import {useAppSelector} from '../../redux/hooks'
const LoadingScreen: React.FC = () => {
const loading = useAppSelector((state) => state.loading.screen)
return loading ? (
<div className="spinnersOverlay">
<BounceLoader color="#a7b0ae" size={100} className="spinnersLoading" />
</div>
) : null
}
export default LoadingScreen

@ -0,0 +1,4 @@
.arrow-icon{
width: 3px;
display: inline-block;
}

@ -0,0 +1,120 @@
import React, {useState, useEffect} from 'react'
import {Button, Dropdown, Form, Tooltip, OverlayTrigger} from 'react-bootstrap'
import {useAppDispatch} from '../../redux/hooks'
import './index.css'
function RepoImporter({list, selectedRepo}: any): JSX.Element {
const [open, setOpen] = useState(false)
const [name, setName] = useState('')
const [branch, setBranch] = useState('')
const dispatch = useAppDispatch()
useEffect(() => {
setName(selectedRepo.name)
setBranch(selectedRepo.branch)
}, [selectedRepo])
const panelChange = () => {
setOpen(!open)
}
const selectRepo = (repo: {name: string; branch: string}) => {
dispatch({type: 'workshop/loadRepo', payload: repo})
}
const importRepo = (event: {preventDefault: () => void}) => {
event.preventDefault()
dispatch({type: 'workshop/loadRepo', payload: {name, branch}})
}
const resetAll = () => {
dispatch({type: 'workshop/resetAll'})
setName('')
setBranch('')
}
return (
<>
{selectedRepo.name && (
<div className="container-fluid mb-3 small mt-3">
Tutorials from:
<h4 className="mb-1">{selectedRepo.name}</h4>
<span className="">Date modified: {new Date(selectedRepo.datemodified).toLocaleString()}</span>
</div>
)}
<div onClick={panelChange} style={{cursor: 'pointer'}} className="container-fluid d-flex mb-3 small">
<div className="d-flex pr-2 pl-2">
<i className={`arrow-icon pt-1 fas fa-xs ${open ? 'fa-chevron-down' : 'fa-chevron-right'}`}></i>
</div>
<div className="d-flex">Import another tutorial repo</div>
</div>
{open && (
<div className="container-fluid">
<Dropdown className="w-100">
<Dropdown.Toggle className="btn btn-secondary w-100" id="dropdownBasic1">
Select a repo
</Dropdown.Toggle>
<Dropdown.Menu className="w-100">
{list.map((item: any) => (
<Dropdown.Item
key={`${item.name}/${item.branch}`}
onClick={() => {
selectRepo(item)
}}
>
{item.name}-{item.branch}
</Dropdown.Item>
))}
</Dropdown.Menu>
</Dropdown>
<div onClick={resetAll} className="small mb-3" style={{cursor: 'pointer'}}>
reset list
</div>
</div>
)}
<div className="container-fluid mt-3">
{open && (
<Form onSubmit={importRepo}>
<Form.Group className="form-group">
<Form.Label className="mr-2" htmlFor="name">
REPO
</Form.Label>
<OverlayTrigger placement="right" overlay={<Tooltip id="tooltip-right">ie username/repository</Tooltip>}>
<i className="fas fa-question-circle" />
</OverlayTrigger>
<Form.Control
id="name"
required
onChange={(e) => {
setName(e.target.value)
}}
value={name}
/>
<Form.Label htmlFor="branch">BRANCH</Form.Label>
<Form.Control
id="branch"
required
onChange={(e) => {
setBranch(e.target.value)
}}
value={branch}
/>
</Form.Group>
<Button className="btn btn-success start w-100" type="submit" disabled={!name || !branch}>
Import {name}
</Button>
<a href="https://github.com/bunsenstraat/remix-learneth-plugin/blob/master/README.md" className="d-none" target="_blank" rel="noreferrer">
<i className="fas fa-info-circle" /> how to setup your repo
</a>
</Form>
)}
<hr />
</div>
</>
)
}
export default RepoImporter

@ -0,0 +1,21 @@
.slide-enter {
transform: translateY(100px);
opacity: 0;
}
.slide-enter-active {
transform: translateY(0);
opacity: 1;
transition: opacity 400ms, transform 400ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.slide-exit {
transform: translateY(0);
opacity: 1;
}
.slide-exit-active {
transform: translateY(100px);
opacity: 0;
transition: opacity 400ms, transform 400ms cubic-bezier(0.6, 0.04, 0.98, 0.335);
}

@ -0,0 +1,18 @@
import React, {type ReactNode, useEffect, useState} from 'react'
import {CSSTransition} from 'react-transition-group'
import './index.css'
const SlideIn: React.FC<{children: ReactNode}> = ({children}) => {
const [show, setShow] = useState(false)
useEffect(() => {
setShow(true)
}, [])
return (
<CSSTransition in={show} timeout={400} classNames="slide" unmountOnExit>
{children}
</CSSTransition>
)
}
export default SlideIn

@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Learn ETH</title>
<base href="./" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous"/> -->
<!-- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous"> -->
<link rel="stylesheet" integrity="ha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf"
crossorigin="anonymous" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css">
</head>
<body>
<div id="root"></div>
<script src="https://kit.fontawesome.com/41dd021e94.js" crossorigin="anonymous"></script>
</body>
</html>

@ -0,0 +1,13 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import {Provider} from 'react-redux'
import './index.css'
import App from './App'
import {store} from './redux/store'
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
root.render(
<Provider store={store}>
<App />
</Provider>
)

@ -0,0 +1,24 @@
.description-collapsed{
height: 0px;
overflow: hidden;
word-wrap: break-word;
padding: 0px !important;
margin: 0px !important;
}
.tag{
display: inline;
}
.arrow-icon{
width: 12px;
display: inline-block;
cursor: pointer;
}
.workshop-link {
cursor: pointer;
}
.workshop-link:hover {
text-decoration: underline;
}

@ -0,0 +1,96 @@
import React, {useEffect} from 'react'
import {Link} from 'react-router-dom'
import Markdown from 'react-markdown'
import rehypeRaw from 'rehype-raw'
import remarkGfm from 'remark-gfm'
import {useAppDispatch, useAppSelector} from '../../redux/hooks'
import RepoImporter from '../../components/RepoImporter'
import './index.css'
function HomePage(): JSX.Element {
const [openKeys, setOpenKeys] = React.useState<string[]>([])
const isOpen = (key: string) => openKeys.includes(key)
const handleClick = (key: string) => {
setOpenKeys(isOpen(key) ? openKeys.filter((item) => item !== key) : [...openKeys, key])
}
const dispatch = useAppDispatch()
const {list, detail, selectedId} = useAppSelector((state) => state.workshop)
const selectedRepo = detail[selectedId]
const levelMap: any = {
1: 'Beginner',
2: 'Intermediate',
3: 'Advanced',
}
useEffect(() => {
dispatch({
type: 'workshop/init',
})
}, [])
return (
<div className="App">
<RepoImporter list={list} selectedRepo={selectedRepo || {}} />
{selectedRepo && (
<div className="container-fluid">
{Object.keys(selectedRepo.group).map((level) => (
<div key={level}>
<div className="mb-2 border-bottom small">{levelMap[level]}:</div>
{selectedRepo.group[level].map((item: any) => (
<div key={item.id}>
<div>
<span
className="arrow-icon"
onClick={() => {
handleClick(item.id)
}}
>
<i className={`fas fa-xs ${isOpen(item.id) ? 'fa-chevron-down' : 'fa-chevron-right'}`} />
</span>
<span
className="workshop-link"
onClick={() => {
handleClick(item.id)
}}
>
{selectedRepo.entities[item.id].name}
</span>
<Link to={`/list?id=${item.id}`} className="text-decoration-none float-right">
<i className="fas fa-play-circle fa-lg" />
</Link>
</div>
<div className={`container-fluid bg-light pt-3 mt-2 ${isOpen(item.id) ? '' : 'description-collapsed'}`}>
{levelMap[level] && <p className="tag pt-2 pr-1 font-weight-bold small text-uppercase">{levelMap[level]}</p>}
{selectedRepo.entities[item.id].metadata.data.tags?.map((tag: string) => (
<p key={tag} className="tag pr-1 font-weight-bold small text-uppercase">
{tag}
</p>
))}
{selectedRepo.entities[item.id].steps && <div className="d-none">{selectedRepo.entities[item.id].steps.length} step(s)</div>}
<div className="workshop-list_description pb-3 pt-3">
<Markdown rehypePlugins={[rehypeRaw]} remarkPlugins={[remarkGfm]}>
{selectedRepo.entities[item.id].description?.content}
</Markdown>
</div>
<div className="actions"></div>
</div>
<div className="mb-3"></div>
</div>
))}
</div>
))}
</div>
)}
</div>
)
}
export default HomePage

@ -0,0 +1,20 @@
import React, {useEffect} from 'react'
import {useAppDispatch} from '../../redux/hooks'
const LogoPage: React.FC = () => {
const dispatch = useAppDispatch()
useEffect(() => {
dispatch({type: 'remixide/connect'})
}, [])
return (
<div>
<div>
<img className="w-100" src="https://remix.ethereum.org/assets/img/remixLogo.webp" />
</div>
</div>
)
}
export default LogoPage

@ -0,0 +1,54 @@
step-view {
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
overflow: hidden;
}
header, footer {
padding: 10px 5px;
display: flex;
justify-content: space-between;
align-items: center;
}
.errorloadingspacer{
padding-top: 44px;
}
.title{
pointer-events: none;
}
h1 {
text-align: left;
font-size: 1.2rem !important;
word-break: break-word;
}
markdown {
display: block;
flex: 1;
overflow: auto;
padding: 0px;
h1 {
font-size: 1.2rem !important;
}
h2 {
font-size: 1rem;
}
h3 {
font-size: 1rem;
}
h4 {
font-size: 1rem;
}
}

@ -0,0 +1,228 @@
import React, {useEffect} from 'react'
import {useLocation, useNavigate} from 'react-router-dom'
import Markdown from 'react-markdown'
import rehypeRaw from 'rehype-raw'
import BackButton from '../../components/BackButton'
import {useAppSelector, useAppDispatch} from '../../redux/hooks'
import './index.scss'
function StepDetailPage() {
const navigate = useNavigate()
const location = useLocation()
const dispatch = useAppDispatch()
const queryParams = new URLSearchParams(location.search)
const id = queryParams.get('id') as string
const stepId = Number(queryParams.get('stepId'))
const {
workshop: {detail, selectedId},
remixide: {errorLoadingFile, errors, success},
} = useAppSelector((state: any) => state)
const entity = detail[selectedId].entities[id]
const steps = entity.steps
const step = steps[stepId]
console.log(step)
useEffect(() => {
dispatch({
type: 'remixide/displayFile',
payload: step,
})
dispatch({
type: 'remixide/save',
payload: {errors: [], success: false},
})
window.scrollTo(0, 0)
}, [step])
useEffect(() => {
if (errors.length > 0 || success) {
window.scrollTo(0, document.documentElement.scrollHeight)
}
}, [errors, success])
return (
<>
<div className="fixed-top">
<div className="bg-light">
<BackButton entity={entity} />
</div>
</div>
<div id="top"></div>
{errorLoadingFile ? (
<>
<div className="errorloadingspacer"></div>
<h1 className="pl-3 pr-3 pt-3 pb-1">{step.name}</h1>
<button
className="w-100nav-item rounded-0 nav-link btn btn-success test"
onClick={() => {
dispatch({
type: 'remixide/displayFile',
payload: step,
})
}}
>
Load the file
</button>
<div className="mb-4"></div>
</>
) : (
<>
<div className="menuspacer"></div>
<h1 className="pr-3 pl-3 pt-3 pb-1">{step.name}</h1>
</>
)}
<div className="container-fluid">
<Markdown rehypePlugins={[rehypeRaw]}>{step.markdown?.content}</Markdown>
</div>
{step.test?.content ? (
<>
<nav className="nav nav-pills nav-fill">
{errorLoadingFile ? (
<button
className="nav-item rounded-0 nav-link btn btn-warning test"
onClick={() => {
dispatch({
type: 'remixide/displayFile',
payload: step,
})
}}
>
Load the file
</button>
) : (
<>
{!errorLoadingFile ? (
<>
<button
className="nav-item rounded-0 nav-link btn btn-info test"
onClick={() => {
dispatch({
type: 'remixide/testStep',
payload: step,
})
}}
>
Check Answer
</button>
{step.answer?.content && (
<button
className="nav-item rounded-0 nav-link btn btn-warning test"
onClick={() => {
dispatch({
type: 'remixide/showAnswer',
payload: step,
})
}}
>
Show answer
</button>
)}
</>
) : (
<>
{!errorLoadingFile && (
<>
<button
className="nav-item rounded-0 nav-link btn btn-success test"
onClick={() => {
navigate(stepId === steps.length - 1 ? `/list?id=${id}` : `/detail?id=${id}&stepId=${stepId + 1}`)
}}
>
Next
</button>
{step.answer?.content && (
<button
className="nav-item rounded-0 nav-link btn btn-warning test"
onClick={() => {
dispatch({
type: 'remixide/showAnswer',
payload: step,
})
}}
>
Show answer
</button>
)}
</>
)}
</>
)}
</>
)}
</nav>
{success && (
<button
className="w-100 rounded-0 nav-item nav-link btn btn-success"
onClick={() => {
navigate(stepId === steps.length - 1 ? `/list?id=${id}` : `/detail?id=${id}&stepId=${stepId + 1}`)
}}
>
Next
</button>
)}
<div id="errors">
{success && (
<div className="alert rounded-0 alert-success mb-0 mt-0" role="alert">
Well done! No errors.
</div>
)}
{errors.length > 0 && (
<>
{!success && (
<div className="alert rounded-0 alert-danger mb-0 mt-0" role="alert">
Errors
</div>
)}
{errors.map((error: string, index: number) => (
<div key={index} className="alert rounded-0 alert-warning mb-0 mt-0">
{error}
</div>
))}
</>
)}
</div>
</>
) : (
<>
<nav className="nav nav-pills nav-fill">
{!errorLoadingFile && step.answer?.content && (
<button
className="nav-item rounded-0 nav-link btn btn-warning test"
onClick={() => {
dispatch({
type: 'remixide/showAnswer',
payload: step,
})
}}
>
Show answer
</button>
)}
</nav>
{stepId < steps.length - 1 && (
<button
className="w-100 btn btn-success"
onClick={() => {
navigate(`/detail?id=${id}&stepId=${stepId + 1}`)
}}
>
Next
</button>
)}
{stepId === steps.length - 1 && (
<button
className="w-100 btn btn-success"
onClick={() => {
navigate(`/list?id=${id}`)
}}
>
Finish tutorial
</button>
)}
</>
)}
</>
)
}
export default StepDetailPage

@ -0,0 +1,144 @@
:host {
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
}
header {
padding: 10px 5px;
}
.menuspacer{
margin-top: 52px;
}
.steplink {
text-decoration: none;
}
.title{
pointer-events: none;
}
h1 {
text-align: left;
font-size: 1.2rem !important;
word-break: break-word;
}
section {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
.start {
padding: 5px 25px;
animation: jittery 2s 0.5s infinite;
box-shadow: 0px 0px 1px 0px rgba(0, 0, 0, 0.5);
color: white;
cursor: pointer;
}
}
footer {
padding: 10px;
display: flex;
justify-content: space-between;
align-items: center;
}
@keyframes jittery {
5%,
50% {
transform: scale(1);
box-shadow: 0px 0px 1px 0px rgba(0, 0, 0, 0.5);
}
10% {
transform: scale(0.9);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
}
15% {
transform: scale(1.15);
box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22);
}
20% {
transform: scale(1.15) rotate(-5deg);
box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22);
}
25% {
transform: scale(1.15) rotate(5deg);
box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22);
}
30% {
transform: scale(1.15) rotate(-3deg);
box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22);
}
35% {
transform: scale(1.15) rotate(2deg);
box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22);
}
40% {
transform: scale(1.15) rotate(0);
box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22);
}
}
.slide-in {
animation: slideIn 0.5s forwards;
visibility: hidden;
}
@keyframes slideIn {
0% {
transform: translateY(-100%);
visibility: visible;
}
100% {
transform: translateY(0);
visibility: visible;
}
}
@-moz-keyframes slideIn {
0% {
transform: translateY(-100%);
visibility: visible;
}
100% {
transform: translateY(0);
visibility: visible;
}
}
@-webkit-keyframes slideIn {
0% {
transform: translateY(-100%);
visibility: visible;
}
100% {
transform: translateY(0);
visibility: visible;
}
}
@-o-keyframes slideIn {
0% {
transform: translateY(-100%);
visibility: visible;
}
100% {
transform: translateY(0);
visibility: visible;
}
}
@-ms-keyframes slideIn {
0% {
transform: translateY(-100%);
visibility: visible;
}
100% {
transform: translateY(0);
visibility: visible;
}
}

@ -0,0 +1,41 @@
import React from 'react'
import {Link, useLocation} from 'react-router-dom'
import Markdown from 'react-markdown'
import BackButton from '../../components/BackButton'
import SlideIn from '../../components/SlideIn'
import {useAppSelector} from '../../redux/hooks'
import './index.scss'
function StepListPage(): JSX.Element {
const location = useLocation()
const queryParams = new URLSearchParams(location.search)
const id = queryParams.get('id') as string
const {detail, selectedId} = useAppSelector((state) => state.workshop)
const entity = detail[selectedId].entities[id]
return (
<>
<div className="fixed-top">
<div className="bg-light">
<BackButton />
</div>
</div>
<div id="top"></div>
<h1 className="pl-3 pr-3 pt-2 pb-1 menuspacer">{entity.name}</h1>
<div className="container-fluid">
<Markdown>{entity.text}</Markdown>
</div>
<SlideIn>
<article className="list-group m-3">
{entity.steps.map((step: any, i: number) => (
<Link key={i} to={`/detail?id=${id}&stepId=${i}`} className="rounded-0 btn btn-light border-bottom text-left steplink">
{step.name} »
</Link>
))}
</article>
</SlideIn>
</>
)
}
export default StepListPage

@ -0,0 +1,7 @@
/**
* Polyfill stable language features. These imports will be optimized by `@babel/preset-env`.
*
* See: https://github.com/zloirock/core-js#babel
*/
import 'core-js/stable'
import 'regenerator-runtime/runtime'

@ -0,0 +1,21 @@
{
"name": "LearnEth",
"displayName": "LearnEth",
"description": "Learn Ethereum with Remix!",
"documentation": "https://remix-learneth-plugin.readthedocs.io/en/latest/index.html",
"version": "0.1.0",
"methods": [
"startTutorial",
"addRepository"
],
"kind": "none",
"icon": "assets/img/learnEthLogo.webp",
"location": "sidePanel",
"url": "plugins/learneth/index.html",
"repo": "https://github.com/ethereum/remix-project/tree/master/apps/learneth",
"maintainedBy": "Remix",
"authorContact": "",
"targets": [
"remix"
]
}

@ -0,0 +1,5 @@
import {useDispatch, type TypedUseSelectorHook, useSelector} from 'react-redux'
import {type AppDispatch, type RootState} from './store'
export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

@ -0,0 +1,14 @@
import {type ModelType} from '../store'
const Model: ModelType = {
namespace: 'loading',
state: {screen: true},
reducers: {
save(state, {payload}) {
return {...state, ...payload}
},
},
effects: {},
}
export default Model

@ -0,0 +1,229 @@
import {toast} from 'react-toastify'
import {type ModelType} from '../store'
import remixClient from '../../remix-client'
import {router} from '../../App'
function getFilePath(file: string): string {
const name = file.split('/')
return name.length > 1 ? `${name[name.length - 1]}` : ''
}
const Model: ModelType = {
namespace: 'remixide',
state: {
errors: [],
success: false,
errorLoadingFile: false,
// theme: '',
},
reducers: {
save(state, {payload}) {
return {...state, ...payload}
},
},
effects: {
*connect(_, {put}) {
toast.info('connecting to the REMIX IDE')
yield put({
type: 'loading/save',
payload: {
screen: true,
},
})
yield remixClient.onload()
toast.dismiss()
yield put({
type: 'loading/save',
payload: {
screen: false,
},
})
yield router.navigate('/home')
},
*displayFile({payload: step}, {select, put}) {
let content = ''
let path = ''
if (step.solidity?.file) {
content = step.solidity.content
path = getFilePath(step.solidity.file)
}
if (step.js?.file) {
content = step.js.content
path = getFilePath(step.js.file)
}
if (step.vy?.file) {
content = step.vy.content
path = getFilePath(step.vy.file)
}
if (!content) {
return
}
toast.info(`loading ${path} into IDE`)
yield put({
type: 'loading/save',
payload: {
screen: true,
},
})
const {detail, selectedId} = yield select((state) => state.workshop)
const workshop = detail[selectedId]
console.log('loading ', step, workshop)
path = `.learneth/${workshop.name}/${step.name}/${path}`
try {
const isExist = yield remixClient.call('fileManager', 'exists' as any, path)
if (!isExist) {
yield remixClient.call('fileManager', 'setFile', path, content)
}
yield remixClient.call('fileManager', 'switchFile', `${path}`)
yield put({
type: 'remixide/save',
payload: {errorLoadingFile: false},
})
toast.dismiss()
} catch (error) {
toast.dismiss()
toast.error('File could not be loaded. Please try again.')
yield put({
type: 'remixide/save',
payload: {errorLoadingFile: true},
})
}
yield put({
type: 'loading/save',
payload: {
screen: false,
},
})
},
*testStep({payload: step}, {select, put}) {
yield put({
type: 'loading/save',
payload: {
screen: true,
},
})
try {
yield put({
type: 'remixide/save',
payload: {success: false},
})
const {detail, selectedId} = yield select((state) => state.workshop)
const workshop = detail[selectedId]
let path: string
if (step.solidity.file) {
path = getFilePath(step.solidity.file)
path = `.learneth/${workshop.name}/${step.name}/${path}`
yield remixClient.call('fileManager', 'switchFile', `${path}`)
}
console.log('testing ', step.test.content)
path = getFilePath(step.test.file)
path = `.learneth/${workshop.name}/${step.name}/${path}`
yield remixClient.call('fileManager', 'setFile', path, step.test.content)
const result = yield remixClient.call('solidityUnitTesting', 'testFromPath', path)
console.log('result ', result)
if (!result) {
yield put({
type: 'remixide/save',
payload: {errors: ['Compiler failed to test this file']},
})
} else {
const success = result.totalFailing === 0
if (success) {
yield put({
type: 'remixide/save',
payload: {errors: [], success: true},
})
} else {
yield put({
type: 'remixide/save',
payload: {
errors: result.errors.map((error: {message: any}) => error.message),
},
})
}
}
} catch (err) {
console.log('TESTING ERROR', err)
yield put({
type: 'remixide/save',
payload: {errors: [String(err)]},
})
}
yield put({
type: 'loading/save',
payload: {
screen: false,
},
})
},
*showAnswer({payload: step}, {select, put}) {
yield put({
type: 'loading/save',
payload: {
screen: true,
},
})
toast.info('loading answer into IDE')
try {
console.log('loading ', step)
const content = step.answer.content
let path = getFilePath(step.answer.file)
const {detail, selectedId} = yield select((state) => state.workshop)
const workshop = detail[selectedId]
path = `.learneth/${workshop.name}/${step.name}/${path}`
yield remixClient.call('fileManager', 'setFile', path, content)
yield remixClient.call('fileManager', 'switchFile', `${path}`)
} catch (err) {
yield put({
type: 'remixide/save',
payload: {errors: [String(err)]},
})
}
toast.dismiss()
yield put({
type: 'loading/save',
payload: {
screen: false,
},
})
},
*testSolidityCompiler(_, {put, select}) {
try {
yield remixClient.call('solidity', 'getCompilationResult')
} catch (err) {
const errors = yield select((state) => state.remixide.errors)
yield put({
type: 'remixide/save',
payload: {
errors: [...errors, "The `Solidity Compiler` is not yet activated.<br>Please activate it using the `SOLIDITY` button in the `Featured Plugins` section of the homepage.<img class='img-thumbnail mt-3' src='assets/activatesolidity.png'>"],
},
})
}
},
},
}
export default Model

@ -0,0 +1,164 @@
import axios from 'axios'
import {toast} from 'react-toastify'
import groupBy from 'lodash/groupBy'
import pick from 'lodash/pick'
import {type ModelType} from '../store'
import remixClient from '../../remix-client'
import {router} from '../../App'
// const apiUrl = 'http://localhost:3001';
const apiUrl = 'https://static.220.14.12.49.clients.your-server.de:3000'
const Model: ModelType = {
namespace: 'workshop',
state: {
list: [],
detail: {},
selectedId: '',
},
reducers: {
save(state, {payload}) {
return {...state, ...payload}
},
},
effects: {
*init(_, {put}) {
const cache = localStorage.getItem('workshop.state')
if (cache) {
const workshopState = JSON.parse(cache)
yield put({
type: 'workshop/save',
payload: workshopState,
})
} else {
yield put({
type: 'workshop/loadRepo',
payload: {
name: 'ethereum/remix-workshops',
branch: 'master',
},
})
}
},
*loadRepo({payload}, {put, select}) {
toast.info(`loading ${payload.name}/${payload.branch}`)
yield put({
type: 'loading/save',
payload: {
screen: true,
},
})
const {list, detail} = yield select((state) => state.workshop)
const url = `${apiUrl}/clone/${encodeURIComponent(payload.name)}/${payload.branch}?${Math.random()}`
console.log('loading ', url)
const {data} = yield axios.get(url)
console.log(data)
const repoId = `${payload.name}-${payload.branch}`
for (let i = 0; i < data.ids.length; i++) {
const {
steps,
metadata: {
data: {steps: metadataSteps},
},
} = data.entities[data.ids[i]]
let newSteps = []
if (metadataSteps) {
newSteps = metadataSteps.map((step: any) => {
return {
...steps.find((item: any) => item.name === step.path),
name: step.name,
}
})
} else {
newSteps = steps.map((step: any) => ({
...step,
name: step.name.replace('_', ' '),
}))
}
const stepKeysWithFile = ['markdown', 'solidity', 'test', 'answer', 'js', 'vy']
for (let j = 0; j < newSteps.length; j++) {
const step = newSteps[j]
for (let k = 0; k < stepKeysWithFile.length; k++) {
const key = stepKeysWithFile[k]
if (step[key]) {
try {
step[key].content = (yield remixClient.call('contentImport', 'resolve', step[key].file)).content
} catch (error) {
console.error(error)
}
}
}
}
data.entities[data.ids[i]].steps = newSteps
}
const workshopState = {
detail: {
...detail,
[repoId]: {
...data,
group: groupBy(
data.ids.map((id: string) => pick(data.entities[id], ['level', 'id'])),
(item: any) => item.level
),
...payload,
},
},
list: detail[repoId] ? list : [...list, payload],
selectedId: repoId,
}
yield put({
type: 'workshop/save',
payload: workshopState,
})
localStorage.setItem('workshop.state', JSON.stringify(workshopState))
toast.dismiss()
yield put({
type: 'loading/save',
payload: {
screen: false,
},
})
if (payload.id) {
const {detail, selectedId} = workshopState
const {ids, entities} = detail[selectedId]
for (let i = 0; i < ids.length; i++) {
const entity = entities[ids[i]]
if (entity.metadata.data.id === payload.id || i + 1 === payload.id) {
yield router.navigate(`/list?id=${ids[i]}`)
break
}
}
}
},
*resetAll(_, {put}) {
yield put({
type: 'workshop/save',
payload: {
list: [],
detail: {},
selectedId: '',
},
})
localStorage.removeItem('workshop.state')
yield put({
type: 'workshop/init',
})
},
},
}
export default Model

@ -0,0 +1,97 @@
import {configureStore, createSlice, type PayloadAction, type Reducer} from '@reduxjs/toolkit'
import createSagaMiddleware from 'redux-saga'
import {call, put, takeEvery, delay, select, all, fork, type ForkEffect} from 'redux-saga/effects'
// @ts-expect-error
const context = require.context('./models', false, /\.ts$/)
const models = context.keys().map((key: any) => context(key).default)
export type StateType = Record<string, any>
export interface ModelType {
namespace: string
state: StateType
reducers: Record<string, (state: StateType, action: PayloadAction<any>) => StateType>
effects: Record<
string,
(
action: PayloadAction<any>,
effects: {
call: typeof call
put: typeof put
delay: typeof delay
select: typeof select
}
) => Generator<any, void, any>
>
}
function createReducer(model: ModelType): Reducer {
const reducers = model.reducers
Object.keys(model.effects).forEach((key) => {
reducers[key] = (state: StateType, action: PayloadAction<any>) => state
})
const slice = createSlice({
name: model.namespace,
initialState: model.state,
reducers,
})
return slice.reducer
}
const rootReducer = models.reduce((prev: any, model: ModelType) => {
return {...prev, [model.namespace]: createReducer(model)}
}, {})
function watchEffects(model: ModelType): ForkEffect {
return fork(function* () {
for (const key in model.effects) {
const effect = model.effects[key]
yield takeEvery(`${model.namespace}/${key}`, function* (action: PayloadAction) {
yield put({
type: 'loading/save',
payload: {
[`${model.namespace}/${key}`]: true,
},
})
yield effect(action, {
call,
put,
delay,
select,
})
yield put({
type: 'loading/save',
payload: {
[`${model.namespace}/${key}`]: false,
},
})
})
}
})
}
function* rootSaga(): Generator {
yield all(models.map((model: ModelType) => watchEffects(model)))
}
const configureAppStore = (initialState = {}) => {
const reduxSagaMonitorOptions = {}
const sagaMiddleware = createSagaMiddleware(reduxSagaMonitorOptions)
const middleware = [sagaMiddleware]
const store = configureStore({
reducer: rootReducer,
middleware: (gDM) => gDM().concat([...middleware]),
preloadedState: initialState,
devTools: process.env.NODE_ENV !== 'production',
})
sagaMiddleware.run(rootSaga)
return store
}
export const store = configureAppStore()
export type AppDispatch = typeof store.dispatch
export type RootState = ReturnType<typeof store.getState>

@ -0,0 +1,38 @@
import {PluginClient} from '@remixproject/plugin'
import {createClient} from '@remixproject/plugin-webview'
import {store} from './redux/store'
import {router} from './App'
class RemixClient extends PluginClient {
constructor() {
super()
createClient(this)
}
startTutorial(name: any, branch: any, id: any): void {
console.log('start tutorial', name, branch, id)
void router.navigate('/home')
store.dispatch({
type: 'workshop/loadRepo',
payload: {
name,
branch,
id,
},
})
}
addRepository(name: any, branch: any) {
console.log('add repo', name, branch)
void router.navigate('/home')
store.dispatch({
type: 'workshop/loadRepo',
payload: {
name,
branch,
},
})
}
}
export default new RemixClient()

@ -0,0 +1,23 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": ["node"]
},
"files": [
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
"../../node_modules/@nrwl/react/typings/image.d.ts"
],
"exclude": [
"jest.config.ts",
"**/*.spec.ts",
"**/*.test.ts",
"**/*.spec.tsx",
"**/*.test.tsx",
"**/*.spec.js",
"**/*.test.js",
"**/*.spec.jsx",
"**/*.test.jsx"
],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
}

@ -0,0 +1,16 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"jsx": "react-jsx",
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json"
}
]
}

@ -0,0 +1,90 @@
const {composePlugins, withNx} = require('@nrwl/webpack')
const webpack = require('webpack')
const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
// Nx plugins for webpack.
module.exports = composePlugins(withNx(), (config) => {
// Update the webpack config as needed here.
// e.g. `config.plugins.push(new MyPlugin())`
// add fallback for node modules
config.resolve.fallback = {
...config.resolve.fallback,
crypto: require.resolve('crypto-browserify'),
stream: require.resolve('stream-browserify'),
path: require.resolve('path-browserify'),
http: require.resolve('stream-http'),
https: require.resolve('https-browserify'),
constants: require.resolve('constants-browserify'),
os: false, //require.resolve("os-browserify/browser"),
timers: false, // require.resolve("timers-browserify"),
zlib: require.resolve('browserify-zlib'),
fs: false,
module: false,
tls: false,
net: false,
readline: false,
child_process: false,
buffer: require.resolve('buffer/'),
vm: require.resolve('vm-browserify'),
}
// add externals
config.externals = {
...config.externals,
solc: 'solc',
}
// add public path
config.output.publicPath = './'
// add copy & provide plugin
config.plugins.push(
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
url: ['url', 'URL'],
process: 'process/browser',
})
)
// set the define plugin to load the WALLET_CONNECT_PROJECT_ID
config.plugins.push(
new webpack.DefinePlugin({
WALLET_CONNECT_PROJECT_ID: JSON.stringify(process.env.WALLET_CONNECT_PROJECT_ID),
})
)
// souce-map loader
config.module.rules.push({
test: /\.js$/,
use: ['source-map-loader'],
enforce: 'pre',
})
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
// set minimizer
config.optimization.minimizer = [
new TerserPlugin({
parallel: true,
terserOptions: {
ecma: 2015,
compress: false,
mangle: false,
format: {
comments: false,
},
},
extractComments: false,
}),
new CssMinimizerPlugin(),
]
config.watchOptions = {
ignored: /node_modules/,
}
config.experiments.syncWebAssembly = true
return config
})

@ -6,8 +6,8 @@
"npm": "^6.14.15"
},
"dependencies": {
"@openzeppelin/contracts": "^5.0.0",
"@openzeppelin/contracts-upgradeable": "^5.0.0",
"@openzeppelin/contracts": "^5.0.2",
"@openzeppelin/contracts-upgradeable": "^5.0.2",
"@openzeppelin/upgrades-core": "^1.30.0",
"@openzeppelin/wizard": "^0.4.0",
"@remix-project/remixd": "../../dist/libs/remixd",

@ -44,9 +44,9 @@ function addFile(browser: NightwatchBrowser, name: string, content: NightwatchCo
})
} else {
browser.click('[data-id="fileExplorerNewFilecreateNewFile"]')
.waitForElementContainsText('*[data-id$="/blank"]', '', 60000)
.sendKeys('*[data-id$="/blank"] .remixui_items', name)
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
// isvisible is protocol action called isDisplayed https://www.selenium.dev/selenium/docs/api/java/org/openqa/selenium/WebElement.html#isDisplayed--
.isVisible({
selector: `li[data-id="treeViewLitreeViewItem${name}"]`,

@ -3,9 +3,9 @@ import EventEmitter from 'events'
import { ExternalProfile, LocationProfile, Profile } from '@remixproject/plugin-utils'
class AddLocalPlugin extends EventEmitter {
command (this: NightwatchBrowser, profile: Profile & LocationProfile & ExternalProfile): NightwatchBrowser {
command (this: NightwatchBrowser, profile: Profile & LocationProfile & ExternalProfile, focus: boolean): NightwatchBrowser {
this.api.perform((done) => {
addLocalPlugin(this.api, profile, () => {
addLocalPlugin(this.api, profile, focus, () => {
done()
this.emit('complete')
})
@ -14,7 +14,7 @@ class AddLocalPlugin extends EventEmitter {
}
}
function addLocalPlugin (browser: NightwatchBrowser, profile: Profile & LocationProfile & ExternalProfile, callback: VoidFunction) {
function addLocalPlugin (browser: NightwatchBrowser, profile: Profile & LocationProfile & ExternalProfile, focus: boolean, callback: VoidFunction) {
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]')
.pause(3000).element('css selector', '*[data-id="pluginManagerComponentPluginManager"]', function (result) {
if (result.status === 0) {
@ -38,8 +38,13 @@ function addLocalPlugin (browser: NightwatchBrowser, profile: Profile & Location
.click(profile.location === 'sidePanel' ? '*[data-id="localPluginRadioButtonsidePanel"]' : '*[data-id="localPluginRadioButtonmainPanel"]')
.click('*[data-id="pluginManagerLocalPluginModalDialogModalDialogModalFooter-react"]')
.click('*[data-id="pluginManagerLocalPluginModalDialog-modal-footer-ok-react')
.waitForElementVisible('[data-id="verticalIconsKindlocalPlugin"]')
.click('[data-id="verticalIconsKindlocalPlugin"]')
.perform((done) => {
if (focus) {
browser.waitForElementVisible(`[data-id="verticalIconsKind${profile.name}"]`)
.click(`[data-id="verticalIconsKind${profile.name}"]`)
}
done()
})
.perform(function () { callback() })
}

@ -2,9 +2,9 @@ import EventEmitter from 'events'
import { NightwatchBrowser } from 'nightwatch'
class CheckTerminalFilter extends EventEmitter {
command (this: NightwatchBrowser, filter: string, test: string): NightwatchBrowser {
command (this: NightwatchBrowser, filter: string, test: string, notContain: boolean): NightwatchBrowser {
this.api.perform((done) => {
checkFilter(this.api, filter, test, () => {
checkFilter(this.api, filter, test, notContain, () => {
done()
this.emit('complete')
})
@ -13,20 +13,30 @@ class CheckTerminalFilter extends EventEmitter {
}
}
function checkFilter (browser: NightwatchBrowser, filter: string, test: string, done: VoidFunction) {
if (browser.options.desiredCapabilities.browserName === 'chrome') { // nightwatch deos not handle well that part.... works locally
function checkFilter (browser: NightwatchBrowser, filter: string, inputTest: string, notContain: boolean, done: VoidFunction) {
/*if (browser.options.desiredCapabilities.browserName === 'chrome') { // nightwatch deos not handle well that part.... works locally
done()
return
}
const filterClass = '[data-id="terminalInputSearch"]'
browser.setValue(filterClass, filter, function () {
}*/
const filterClass = '[data-id="terminalInputSearchTerminal"]'
browser.clearValue(filterClass).setValue(filterClass, filter, function () {
browser.execute(function () {
return document.querySelector('[data-id="terminalJournal"]').innerHTML === test || ''
return document.querySelector('[data-id="terminalJournal"]').innerHTML
}, [], function (result) {
browser.clearValue(filterClass).setValue(filterClass, '', function () {
if (!result.value) {
browser.assert.fail('useFilter on ' + filter + ' ' + test, 'info about error', '')
console.log(notContain, result.value, filter)
if (!notContain) {
// the input text should be contained in the result
if ((result.value as string).indexOf(filter) === -1) {
browser.assert.fail('useFilter on ' + filter + ' ' + test, 'the input text should be contained in the result', '')
}
}
if (notContain) {
// the input text should not be contained in the result
if ((result.value as string).indexOf(filter) !== -1) {
browser.assert.fail('useFilter on ' + filter + ' ' + test, 'the input text should not be contained in the result', '')
}
}
browser.clearValue(filterClass).perform(() => {
done()
})
})

@ -4,12 +4,12 @@ import EventEmitter from 'events'
class ClickLaunchIcon extends EventEmitter {
command (this: NightwatchBrowser, icon: string): NightwatchBrowser {
this.api
.waitForElementVisible('#icon-panel div[plugin="' + icon + '"]')
.click('#icon-panel div[plugin="' + icon + '"]')
.perform((done) => {
done()
this.emit('complete')
})
.waitForElementVisible('#icon-panel div[plugin="' + icon + '"]')
.click('#icon-panel div[plugin="' + icon + '"]')
.perform((done) => {
done()
this.emit('complete')
})
return this
}
}

@ -2,40 +2,40 @@ import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events'
class ConnectToExternalHttpProvider extends EventEmitter {
command(this: NightwatchBrowser, url: string, identifier: string): NightwatchBrowser {
this.api.element('xpath', `//*[@class='udapp_environment' and contains(.,'${identifier}')]`,
(result) => {
if (result.status as any === -1) {
console.log("No connection to external provider found. Adding one.", url)
browser
.click({
locateStrategy: 'css selector',
selector: '[data-id="basic-http-provider-modal-footer-ok-react"]',
abortOnFailure: false,
suppressNotFoundErrors: true,
timeout: 5000
})
.switchEnvironment('basic-http-provider')
.waitForElementPresent('[data-id="basic-http-provider-modal-footer-ok-react"]')
.execute(() => {
(document.querySelector('*[data-id="basic-http-providerModalDialogContainer-react"] input[data-id="modalDialogCustomPromp"]') as any).focus()
}, [], () => { })
.setValue('[data-id="modalDialogCustomPromp"]', url)
.modalFooterOKClick('basic-http-provider')
.perform((done) => {
done()
this.emit('complete')
})
} else {
this.api.perform((done) => {
done()
this.emit('complete')
})
}
}
)
return this
}
command(this: NightwatchBrowser, url: string, identifier: string): NightwatchBrowser {
this.api.element('xpath', `//*[@class='udapp_environment' and contains(.,'${identifier}')]`,
(result) => {
if (result.status as any === -1) {
console.log("No connection to external provider found. Adding one.", url)
browser
.click({
locateStrategy: 'css selector',
selector: '[data-id="basic-http-provider-modal-footer-ok-react"]',
abortOnFailure: false,
suppressNotFoundErrors: true,
timeout: 5000
})
.switchEnvironment('basic-http-provider')
.waitForElementPresent('[data-id="basic-http-provider-modal-footer-ok-react"]')
.execute(() => {
(document.querySelector('*[data-id="basic-http-providerModalDialogContainer-react"] input[data-id="modalDialogCustomPromp"]') as any).focus()
}, [], () => { })
.setValue('[data-id="modalDialogCustomPromp"]', url)
.modalFooterOKClick('basic-http-provider')
.perform((done) => {
done()
this.emit('complete')
})
} else {
this.api.perform((done) => {
done()
this.emit('complete')
})
}
}
)
return this
}
}
module.exports = ConnectToExternalHttpProvider

@ -16,7 +16,9 @@ class CreateContract extends EventEmitter {
function createContract (browser: NightwatchBrowser, inputParams: string, callback: VoidFunction) {
if (inputParams) {
browser.setValue('.udapp_contractActionsContainerSingle > input', inputParams, function () {
browser.click('.udapp_contractActionsContainerSingle > div').pause(500).perform(function () { callback() })
browser
.waitForElementVisible('.udapp_contractActionsContainerSingle > div')
.click('.udapp_contractActionsContainerSingle > div').pause(500).perform(function () { callback() })
})
} else {
browser

@ -15,7 +15,7 @@ class GetLastTransactionHash extends EventEmitter {
}
function getLastTransactionHash (browser: NightwatchBrowser, callback: (hash: string) => void) {
browser.waitForElementPresent('*[data-shared="universalDappUiInstance"]')
browser.waitForElementPresent('*[data-id="terminalJournal"]')
.execute(function () {
const deployedContracts = document.querySelectorAll('*[data-id="terminalJournal"] > div')
for (let i = deployedContracts.length - 1; i >= 0; i--) {

@ -5,7 +5,6 @@ class HideToolTips extends EventEmitter {
command(this: NightwatchBrowser) {
browser
.perform((done) => {
//if (hideToolTips) {
browser.execute(function () {
// hide tooltips
function addStyle(styleString) {
@ -13,12 +12,11 @@ class HideToolTips extends EventEmitter {
style.textContent = styleString
document.head.append(style)
}
addStyle(`
.popover {
display:none !important;
}
`)
.popover {
display:none !important;
}
`)
}, [], done())
})
.perform((done) => {

@ -5,10 +5,10 @@ import EventEmitter from 'events'
Checks if any child elements of journal (console) contains a matching value.
*/
class JournalChildIncludes extends EventEmitter {
command (this: NightwatchBrowser, val: string, opts = { shouldHaveOnlyOneOccurence: false }): NightwatchBrowser {
command (this: NightwatchBrowser, val: string, opts = { shouldHaveOnlyOneOccurrence: false }): NightwatchBrowser {
let isTextFound = false
const browser = this.api
let occurence = 0
let occurrence = 0
this.api.elements('css selector', '*[data-id="terminalJournal"]', (res) => {
Array.isArray(res.value) && res.value.forEach(function (jsonWebElement) {
const jsonWebElementId = jsonWebElement[ELEMENT_KEY] || jsonWebElement[Object.keys(jsonWebElement)[0]]
@ -18,14 +18,14 @@ class JournalChildIncludes extends EventEmitter {
if (typeof text === 'string' && text.indexOf(val) !== -1) {
isTextFound = true
occurence++
occurrence++
}
})
})
})
browser.perform(() => {
browser.assert.ok(isTextFound, isTextFound ? `<*[data-id="terminalJournal"]> contains ${val}.` : `${val} not found in <*[data-id="terminalJournal"]> div:last-child>`)
if (opts.shouldHaveOnlyOneOccurence) browser.assert.ok(occurence === 1, `${occurence} occurence found of "${val}"`)
if (opts.shouldHaveOnlyOneOccurrence) browser.assert.ok(occurrence === 1, `${occurrence} occurrence found of "${val}"`)
this.emit('complete')
})
return this

@ -21,7 +21,7 @@ function openFile (browser: NightwatchBrowser, name: string, done: VoidFunction)
// if side panel is shown, check this is the file panel
browser.element('css selector', '[data-id="verticalIconsKindfilePanel"] img[data-id="selected"]', (result) => {
if (result.status === 0) {
done()
done()
} else browser.clickLaunchIcon('filePanel').perform(() => {
done()
})

@ -4,7 +4,7 @@ const storage = `pragma solidity >=0.7.0 <0.9.0;
/**
* @title Storage
* @dev Store & retreive value in a variable
* @dev Store & retrieve value in a variable
*/
contract Storage {
@ -22,7 +22,7 @@ contract Storage {
* @dev Return value
* @return value of 'number'
*/
function retreive() public view returns (uint256){
function retrieve() public view returns (uint256){
return number;
}
}`
@ -301,7 +301,7 @@ contract BallotTest {
Assert.equal(ballotToTest.winnerName(), bytes32("candidate1"), "candidate1 should be the winner name");
}
function checkWinninProposalWithReturnValue () public view returns (bool) {
function checkWinningProposalWithReturnValue () public view returns (bool) {
return ballotToTest.winningProposal() == 0;
}
}

@ -57,6 +57,7 @@ export default function (browser: NightwatchBrowser, callback: VoidFunction, url
if (preloadPlugins) {
initModules(browser, () => {
browser
.pause(4000)
.clickLaunchIcon('solidity')
.waitForElementVisible('[for="autoCompile"]')
.click('[for="autoCompile"]')

@ -75,7 +75,7 @@ module.exports = {
abortOnFailure: false,
suppressNotFoundErrors: true,
})
// we are not changing the visibility for not checksumed contracts
// we are not changing the visibility for not checksummed contracts
// .addAtAddressInstance('0x692a70D2e424a56D2C6C27aA97D1a86395877b3B', true, false)
.clickLaunchIcon('filePanel')
.addAtAddressInstance('0x692a70D2e424a56D2C6C27aA97D1a86395877b3A', true, true)
@ -118,7 +118,6 @@ module.exports = {
.connectToExternalHttpProvider('http://localhost:8545', 'Custom')
.clickLaunchIcon('solidity')
.clickLaunchIcon('udapp')
.pause(2000)
.clearValue('input[placeholder="bytes32[] proposalNames"]')
.setValue('input[placeholder="bytes32[] proposalNames"]', '["0x48656c6c6f20576f726c64210000000000000000000000000000000000000000"]')
.click('*[data-id="Deploy - transact (not payable)"]')
@ -555,4 +554,4 @@ contract Retriever is Storage {
return number;
}
}
`
`

@ -71,7 +71,7 @@ module.exports = {
abortOnFailure: false,
suppressNotFoundErrors: true,
})
// we are not changing the visibility for not checksumed contracts
// we are not changing the visibility for not checksummed contracts
// .addAtAddressInstance('0x692a70D2e424a56D2C6C27aA97D1a86395877b3B', true, false)
.clickLaunchIcon('filePanel')
.addAtAddressInstance('0x692a70D2e424a56D2C6C27aA97D1a86395877b3A', true, true)
@ -94,7 +94,6 @@ module.exports = {
.connectToExternalHttpProvider('http://localhost:8545', 'Custom')
.clickLaunchIcon('solidity')
.clickLaunchIcon('udapp')
.pause(2000)
.clearValue('input[placeholder="uint8 _numProposals"]')
.setValue('input[placeholder="uint8 _numProposals"]', '2')
.click('*[data-id="Deploy - transact (not payable)"]')

@ -32,6 +32,8 @@ module.exports = {
.click('[data-id="treeViewLitreeViewItemcircuits/simple.circom"]')
.waitForElementPresent('[data-path="Semaphore - 1/circuits/simple.circom"]')
.waitForElementVisible('[data-path="Semaphore - 1/circuits/simple.circom"]')
.waitForElementPresent('[data-id="verticalIconsKindcircuit-compiler"]')
.waitForElementVisible('[data-id="verticalIconsKindcircuit-compiler"]')
.click('[data-id="play-editor"]')
.waitForElementPresent('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.wasm"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.wasm"]')
@ -85,6 +87,8 @@ module.exports = {
.click('[data-id="treeViewLitreeViewItemcircuits/simple.circom"]')
.waitForElementPresent('[data-path="Semaphore - 1/circuits/simple.circom"]')
.waitForElementVisible('[data-path="Semaphore - 1/circuits/simple.circom"]')
.waitForElementPresent('[data-id="verticalIconsKindcircuit-compiler"]')
.waitForElementVisible('[data-id="verticalIconsKindcircuit-compiler"]')
.perform(function () {
const actions = this.actions({async: true})

@ -18,7 +18,7 @@ module.exports = {
return sources
},
'Should compile using "compileWithParamaters" API #group1': function (browser: NightwatchBrowser) {
'Should compile using "compileWithParameters" API #group1': function (browser: NightwatchBrowser) {
browser
.addFile('test_jsCompile.js', { content: jsCompile })
.executeScriptInTerminal('remix.exeCurrent()')
@ -26,7 +26,7 @@ module.exports = {
.click('*[data-id="terminalClearConsole"]')
},
'Should compile using "compileWithParamaters" API with optimization On #group2': function (browser: NightwatchBrowser) {
'Should compile using "compileWithParameters" API with optimization On #group2': function (browser: NightwatchBrowser) {
browser
.addFile('test_jsCompileWithOptimization.js', { content: jsCompileWithOptimization })
.executeScriptInTerminal('remix.exeCurrent()')
@ -34,7 +34,7 @@ module.exports = {
.click('*[data-id="terminalClearConsole"]')
},
'Should compile using "compileWithParamaters" API with optimization off check default runs #group3': function (browser: NightwatchBrowser) {
'Should compile using "compileWithParameters" API with optimization off check default runs #group3': function (browser: NightwatchBrowser) {
browser
.addFile('test_jsCompileWithOptimizationDefault.js', { content: jsCompileWithOptimizationDefault })
.executeScriptInTerminal('remix.exeCurrent()')
@ -65,7 +65,7 @@ const simpleContract = `pragma solidity >=0.4.22 <0.9.1;
/**
* @title Storage
* @dev Store & retreive value in a variable
* @dev Store & retrieve value in a variable
*/
contract StorageTestUpdateConfiguration {
@ -83,7 +83,7 @@ contract StorageTestUpdateConfiguration {
* @dev Return value
* @return value of 'number'
*/
function retreive() public view returns (uint256){
function retrieve() public view returns (uint256){
return number;
}
}

@ -22,9 +22,9 @@ module.exports = {
.click('li[data-id="treeViewLitreeViewItemREADME.txt"]') // focus on root directory
.click('*[data-id="fileExplorerNewFilecreateNewFile"]')
.pause(1000)
.waitForElementVisible('*[data-id$="/blank"]')
.sendKeys('*[data-id$="/blank"] .remixui_items', '5_New_contract.sol')
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', '5_New_contract.sol')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItem5_New_contract.sol"]', 7000)
},
@ -49,9 +49,9 @@ module.exports = {
.click('li[data-id="treeViewLitreeViewItemREADME.txt"]') // focus on root directory
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.pause(1000)
.waitForElementVisible('*[data-id$="/blank"]')
.sendKeys('*[data-id$="/blank"] .remixui_items', 'Browser_Tests')
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'Browser_Tests')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemBrowser_Tests"]')
},
@ -113,5 +113,73 @@ module.exports = {
.waitForElementVisible('[data-id="treeViewLitreeViewItemfileExplorer.test.js"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItemgeneralSettings.test.js"]')
.end()
},
'Should add deep tree with buttons #group3': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('div[data-id="remixIdeSidePanel"]')
.clickLaunchIcon('filePanel')
.waitForElementVisible('*[data-id="filePanelFileExplorerTree"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFolder"]')
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep1')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemdeep1"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFolder"]')
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep2')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemdeep1/deep2"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFolder"]')
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep3')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemdeep1/deep2/deep3"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFile"]')
.click('[data-id="fileExplorerNewFilecreateNewFile"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep4.sol')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemdeep1/deep2/deep3/deep4.sol"]')
// click on root to focus
.click('li[data-id="treeViewLitreeViewItemREADME.txt"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFolder"]')
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep5')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemdeep5"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFolder"]')
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep6')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemdeep5/deep6"]')
// focus on contracts
.click('li[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFolder"]')
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep7')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/deep7"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFolder"]')
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep8')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/deep7/deep8"]')
.waitForElementVisible('[data-id="fileExplorerNewFilecreateNewFile"]')
.click('[data-id="fileExplorerNewFilecreateNewFile"]')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'deep9.sol')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/deep7/deep8/deep9.sol"]')
.end()
}
}

@ -17,10 +17,10 @@ module.exports = {
.click('[data-id="contextMenuItemcopyFileName"]')
.click('*[data-id="fileExplorerNewFilecreateNewFile"]')
.pause(1000)
.waitForElementVisible('*[data-id$="/blank"] .remixui_items')
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.CONTROL + 'v')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.CONTROL + 'v')
.pause(1000)
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemREADME1.txt"]', 7000)
},
'Should copy file name and paste in another folder with new file button and it will contain a new file #group1 ': function (browser: NightwatchBrowser) {
@ -32,10 +32,10 @@ module.exports = {
.click('[data-id="treeViewLitreeViewItemcontracts"]')
.click('*[data-id="fileExplorerNewFilecreateNewFile"]')
.pause(1000)
.waitForElementVisible('*[data-id$="/blank"] .remixui_items')
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.CONTROL + 'v')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.CONTROL + 'v')
.pause(1000)
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/README.txt"]', 7000)
},
'Should copy file name and paste in another folder that has the same filename with new file button and it will contain a new file #group1 ': function (browser: NightwatchBrowser) {
@ -47,10 +47,10 @@ module.exports = {
.click('[data-id="treeViewLitreeViewItemcontracts"]')
.click('*[data-id="fileExplorerNewFilecreateNewFile"]')
.pause(1000)
.waitForElementVisible('*[data-id$="/blank"] .remixui_items')
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.CONTROL + 'v')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.CONTROL + 'v')
.pause(1000)
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/README1.txt"]', 7000)
},
'Should copy file name and paste in root with right click and it will contain a new file #group1 ': function (browser: NightwatchBrowser) {
@ -62,10 +62,10 @@ module.exports = {
.rightClick('*[data-id="treeViewUltreeViewMenu"]')
.click('*[data-id="contextMenuItemnewFile"]')
.pause(1000)
.waitForElementVisible('*[data-id$="/blank"] .remixui_items')
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.CONTROL + 'v')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.CONTROL + 'v')
.pause(1000)
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemREADME2.txt"]', 7000)
},
'Should copy file name and paste in contracts with right click and it will contain a new file #group1 ': function (browser: NightwatchBrowser) {
@ -77,10 +77,10 @@ module.exports = {
.rightClick('*[data-id="treeViewLitreeViewItemcontracts"]')
.click('*[data-id="contextMenuItemnewFile"]')
.pause(1000)
.waitForElementVisible('*[data-id$="/blank"] .remixui_items')
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.CONTROL + 'v')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.CONTROL + 'v')
.pause(1000)
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemREADME2.txt"]', 7000)
},
// file copy paste tests

@ -3,101 +3,101 @@ import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
const checkBrowserIsChrome = function (browser: NightwatchBrowser) {
return browser.browserName.indexOf('chrome') > -1
return browser.browserName.indexOf('chrome') > -1
}
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
'drag and drop file from root to contracts #group1 ': function (browser: NightwatchBrowser) {
if (checkBrowserIsChrome(browser)) {
browser
.clickLaunchIcon('filePanel')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.findElement('*[data-id="treeViewLitreeViewItemcontracts"]', (el) => {
console.log((el as any).value.getId())
const id = (el as any).value.getId()
browser
.waitForElementVisible('li[data-id="treeViewLitreeViewItemREADME.txt"]')
.dragAndDrop('li[data-id="treeViewLitreeViewItemREADME.txt"]', id)
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts/README.txt"]')
})
}
},
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
'drag and drop file from root to contracts #group1 ': function (browser: NightwatchBrowser) {
if (checkBrowserIsChrome(browser)) {
browser
.clickLaunchIcon('filePanel')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.findElement('*[data-id="treeViewLitreeViewItemcontracts"]', (el) => {
console.log((el as any).value.getId())
const id = (el as any).value.getId()
browser
.waitForElementVisible('li[data-id="treeViewLitreeViewItemREADME.txt"]')
.dragAndDrop('li[data-id="treeViewLitreeViewItemREADME.txt"]', id)
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts/README.txt"]')
})
}
},
'drag and drop file from contracts to root #group1': function (browser: NightwatchBrowser) {
if (checkBrowserIsChrome(browser)) {
browser.findElement('*[data-id="treeViewUltreeViewMenu"]', (el) => {
console.log((el as any).value.getId())
const id = (el as any).value.getId()
browser
.waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]')
.dragAndDrop('li[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]', id)
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
})
browser.pause(1000)
.waitForElementVisible('li[data-id="treeViewLitreeViewItem1_Storage.sol"]')
}
},
'drag and drop scripts from root to contracts #group1': function (browser: NightwatchBrowser) {
if (checkBrowserIsChrome(browser)) {
browser
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.findElement('*[data-id="treeViewLitreeViewItemcontracts"]', (el) => {
console.log((el as any).value.getId())
const id = (el as any).value.getId()
browser
.waitForElementVisible('li[data-id="treeViewLitreeViewItemscripts"]')
.dragAndDrop('li[data-id="treeViewLitreeViewItemscripts"]', id)
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts/scripts"]')
})
}
},
'drag scripts from contracts to root #group1': function (browser: NightwatchBrowser) {
if (checkBrowserIsChrome(browser)) {
browser.findElement('*[data-id="treeViewUltreeViewMenu"]', (el) => {
console.log((el as any).value.getId())
const id = (el as any).value.getId()
browser
.waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts/scripts"]')
.dragAndDrop('li[data-id="treeViewLitreeViewItemcontracts/scripts"]', id)
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
})
browser.pause(1000)
.waitForElementVisible('li[data-id="treeViewLitreeViewItemscripts"]')
}
},
'drag into nested folder': function (browser: NightwatchBrowser) {
if (checkBrowserIsChrome(browser)) {
browser.waitForElementVisible('li[data-id="treeViewLitreeViewItemscripts"]')
.rightClick('li[data-id="treeViewLitreeViewItemscripts"]')
.waitForElementPresent('[data-id="contextMenuItemnewFolder')
.click('[data-id="contextMenuItemnewFolder')
.waitForElementVisible('*[data-id$="/blank"] .remixui_items')
.sendKeys('*[data-id$="/blank"] .remixui_items', 'nested')
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.findElement('*[data-id="treeViewLitreeViewItemscripts/nested"]', (el) => {
console.log((el as any).value.getId())
const id = (el as any).value.getId()
browser
.waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts/README.txt"]')
.dragAndDrop('li[data-id="treeViewLitreeViewItemcontracts/README.txt"]', id)
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.waitForElementVisible('li[data-id="treeViewLitreeViewItemscripts/nested/README.txt"]')
})
}
'drag and drop file from contracts to root #group1': function (browser: NightwatchBrowser) {
if (checkBrowserIsChrome(browser)) {
browser.findElement('*[data-id="treeViewUltreeViewMenu"]', (el) => {
console.log((el as any).value.getId())
const id = (el as any).value.getId()
browser
.waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]')
.dragAndDrop('li[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]', id)
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
})
browser.pause(1000)
.waitForElementVisible('li[data-id="treeViewLitreeViewItem1_Storage.sol"]')
}
},
'drag and drop scripts from root to contracts #group1': function (browser: NightwatchBrowser) {
if (checkBrowserIsChrome(browser)) {
browser
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.findElement('*[data-id="treeViewLitreeViewItemcontracts"]', (el) => {
console.log((el as any).value.getId())
const id = (el as any).value.getId()
browser
.waitForElementVisible('div[data-id="treeViewDivDraggableItemscripts"]')
.dragAndDrop('div[data-id="treeViewDivDraggableItemscripts"]', id)
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts/scripts"]')
})
}
},
'drag scripts from contracts to root #group1': function (browser: NightwatchBrowser) {
if (checkBrowserIsChrome(browser)) {
browser.findElement('*[data-id="treeViewUltreeViewMenu"]', (el) => {
console.log((el as any).value.getId())
const id = (el as any).value.getId()
browser
.waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts/scripts"]')
.dragAndDrop('li[data-id="treeViewLitreeViewItemcontracts/scripts"]', id)
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
})
browser.pause(1000)
.waitForElementVisible('li[data-id="treeViewLitreeViewItemscripts"]')
}
},
'drag into nested folder': function (browser: NightwatchBrowser) {
if (checkBrowserIsChrome(browser)) {
browser.waitForElementVisible('li[data-id="treeViewLitreeViewItemscripts"]')
.rightClick('li[data-id="treeViewLitreeViewItemscripts"]')
.waitForElementPresent('[data-id="contextMenuItemnewFolder')
.click('[data-id="contextMenuItemnewFolder')
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'nested')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.findElement('*[data-id="treeViewLitreeViewItemscripts/nested"]', (el) => {
console.log((el as any).value.getId())
const id = (el as any).value.getId()
browser
.waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts/README.txt"]')
.dragAndDrop('li[data-id="treeViewLitreeViewItemcontracts/README.txt"]', id)
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.waitForElementVisible('li[data-id="treeViewLitreeViewItemscripts/nested/README.txt"]')
})
}
}
}
}

@ -30,16 +30,16 @@ module.exports = {
.waitForElementVisible('*[data-id="fileExplorerNewFilecreateNewFolder"]')
.click('[data-id="fileExplorerNewFilecreateNewFolder"]')
.pause(1000)
.waitForElementVisible('*[data-id$="/blank"]')
.sendKeys('*[data-id$="/blank"] .remixui_items', 'Browser_Tests')
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', 'Browser_Tests')
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemBrowser_Tests"]')
.addFile('File.sol', { content: '' })
.executeScriptInTerminal(`remix.loadgist('${gistid}')`)
// .perform((done) => { if (runtimeBrowser === 'chrome') { browser.openFile('gists') } done() })
.waitForElementVisible(`[data-id="treeViewLitreeViewItemgist-${gistid}"]`)
.openFile(`gist-${gistid}/README.txt`)
.waitForElementVisible(`[data-id="treeViewLitreeViewItemREADME.txt"]`)
.openFile(`README.txt`)
// Remix publish to gist
/* .click('*[data-id="fileExplorerNewFilepublishToGist"]')
.pause(2000)
@ -110,8 +110,8 @@ module.exports = {
.waitForElementVisible('[data-id="settingsTabRemoveGistToken"]')
.click('[data-id="settingsTabRemoveGistToken"]')
.clickLaunchIcon('filePanel')
.waitForElementVisible('*[data-id="fileExplorerNewFilepublishToGist"]')
.click('*[data-id="fileExplorerNewFilepublishToGist"]')
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacepublishToGist"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.pause(10000)
@ -142,9 +142,27 @@ module.exports = {
.setValue('*[data-id="gisthandlerModalDialogModalBody-react"] input[data-id="modalDialogCustomPromp"]', testData.validGistId)
.modalFooterOKClick('gisthandler')
.pause(10000)
.openFile(`gist-${testData.validGistId}/README.txt`)
.waitForElementVisible(`div[data-path='default_workspace/gist-${testData.validGistId}/README.txt']`)
.assert.containsText(`div[data-path='default_workspace/gist-${testData.validGistId}/README.txt'] > span`, 'README.txt')
.end()
.openFile(`README.txt`)
.waitForElementVisible(`div[data-path='gist ${testData.validGistId}/README.txt']`)
.assert.containsText(`div[data-path='gist ${testData.validGistId}/README.txt'] > span`, 'README.txt')
},
'Load Gist from URL and verify truncated files are loaded #group3': function (browser: NightwatchBrowser) {
const gistId = '1b179bf1b92c8b0664b4cbe61774e15d'
browser
.url('http://127.0.0.1:8080/#gist=' + gistId) // loading the gist
.refreshPage()
.currentWorkspaceIs('gist ' + gistId)
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 15000)
.waitForElementVisible(`#fileExplorerView li[data-path='contracts']`, 30000)
.openFile(`contracts/2_Owner.sol`)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf('contract Owner {') !== -1)
})
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacepublishToGist"]')
.modalFooterOKClick('fileSystem')
.waitForElementVisible('*[data-shared="tooltipPopup"]', 5000)
.assert.containsText('*[data-shared="tooltipPopup"]', 'Saving gist (' + gistId + ') ...')
}
}

@ -12,12 +12,21 @@ const testData = {
pluginUrl: 'https://zokrates.github.io/zokrates-remix-plugin/'
}
const localPluginData = {
name: testData.pluginName,
displayName: testData.pluginDisplayName,
canActivate: [],
url: testData.pluginUrl,
location: 'sidePanel'
}
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', false)
},
'Should Load Plugin Manager': function (browser: NightwatchBrowser) {
'Should Load Plugin Manager #group1': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]')
.pause(3000)
.click('*[plugin="pluginManager"]')
@ -26,7 +35,7 @@ module.exports = {
.assert.containsText('*[data-id="sidePanelSwapitTitle"]', 'PLUGIN MANAGER')
},
'Should Search for plugins': function (browser: NightwatchBrowser) {
'Should Search for plugins #group1': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="pluginManagerComponentPluginManager"]')
.click('*[data-id="pluginManagerComponentSearchInput"]')
.keys('debugger')
@ -45,7 +54,7 @@ module.exports = {
.keys(browser.Keys.BACK_SPACE)
},
'Should activate plugins': function (browser: NightwatchBrowser) {
'Should activate plugins #group1': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="pluginManagerComponentPluginManager"]')
.click('*[data-id="pluginManagerComponentPluginManager"]')
.scrollAndClick('*[data-id="pluginManagerComponentActivateButtondebugger"]')
@ -57,7 +66,7 @@ module.exports = {
.waitForElementVisible('*[data-id="pluginManagerComponentDeactivateButtonZoKrates"]', 60000)
},
'Should deactivate plugins': function (browser: NightwatchBrowser) {
'Should deactivate plugins #group1': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="pluginManagerComponentPluginManager"]')
.click('*[data-id="pluginManagerComponentPluginManager"]')
.waitForElementVisible('*[data-id="pluginManagerComponentDeactivateButtondebugger"]', 60000)
@ -69,45 +78,23 @@ module.exports = {
.waitForElementVisible('*[data-id="pluginManagerComponentActivateButtonvyper"]', 60000)
},
'Should connect a local plugin': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="pluginManagerComponentPluginManager"]')
.execute(function () {
window.testmode = true
})
.click('*[data-id="pluginManagerComponentPluginSearchButton"]')
.waitForElementVisible('*[data-id="pluginManagerLocalPluginModalDialogModalDialogContainer-react"]')
.click('*[data-id="pluginManagerLocalPluginModalDialogModalDialogModalBody-react"]')
.waitForElementVisible('*[data-id="localPluginName"]')
.clearValue('*[data-id="localPluginName"]').setValue('*[data-id="localPluginName"]', testData.pluginName)
.clearValue('*[data-id="localPluginDisplayName"]').setValue('*[data-id="localPluginDisplayName"]', testData.pluginDisplayName)
.clearValue('*[data-id="localPluginUrl"]').setValue('*[data-id="localPluginUrl"]', testData.pluginUrl)
.click('*[data-id="localPluginRadioButtoniframe"]')
.click('*[data-id="localPluginRadioButtonsidePanel"]')
.click('*[data-id="pluginManagerLocalPluginModalDialogModalDialogModalFooter-react"]')
.click('*[data-id="pluginManagerLocalPluginModalDialog-modal-footer-ok-react')
'Should connect a local plugin #group2': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]')
.pause(3000)
.click('*[plugin="pluginManager"]')
.clickLaunchIcon('filePanel')
.addLocalPlugin(localPluginData, false)
.pause(2000)
},
'Should display error message for creating already existing plugin': function (browser: NightwatchBrowser) {
'Should display error message for creating already existing plugin #group2': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="pluginManagerComponentPluginManager"]')
.click('*[data-id="pluginManagerComponentPluginSearchButton"]')
.waitForElementVisible('*[data-id="pluginManagerLocalPluginModalDialogModalDialogContainer-react"]')
.click('*[data-id="pluginManagerLocalPluginModalDialogModalDialogModalBody-react"]')
.waitForElementVisible('*[data-id="localPluginName"]')
.clearValue('*[data-id="localPluginName"]').setValue('*[data-id="localPluginName"]', testData.pluginName)
.clearValue('*[data-id="localPluginDisplayName"]').setValue('*[data-id="localPluginDisplayName"]', testData.pluginDisplayName)
.clearValue('*[data-id="localPluginUrl"]').setValue('*[data-id="localPluginUrl"]', testData.pluginUrl)
.click('*[data-id="localPluginRadioButtoniframe"]')
.click('*[data-id="localPluginRadioButtonsidePanel"]')
.waitForElementVisible('*[data-id="pluginManagerLocalPluginModalDialog-modal-footer-ok-react"]', 60000)
.click('*[data-id="pluginManagerLocalPluginModalDialog-modal-footer-ok-react"]')
// .modalFooterOKClick()
// .pause(2000)
.waitForElementVisible('*[data-shared="tooltipPopup"]', 60000)
.pause(5000)
.assert.containsText('*[data-shared="tooltipPopup"]', 'Cannot create Plugin : This name has already been used')
.clickLaunchIcon('filePanel')
.addLocalPlugin(localPluginData, false)
.waitForElementContainsText('*[data-shared="tooltipPopup"]', 'Cannot create Plugin : This name has already been used')
},
'Should load back installed plugins after reload': function (browser: NightwatchBrowser) {
'Should load back installed plugins after reload #group2': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="remixIdeSidePanel"]',3000)
.waitForElementVisible('*[data-id="pluginManagerComponentPluginManager"]')
@ -124,6 +111,5 @@ module.exports = {
done()
})
})
.end()
}
}

@ -128,6 +128,7 @@ const checkForAcceptAndRemember = async function (browser: NightwatchBrowser) {
*/
const clickAndCheckLog = async (browser: NightwatchBrowser, buttonText: string, methodResult: any, eventResult: any, payload: any, waitResult: boolean = true) => { // eslint-disable-line
console.log('clickAndCheckLog', buttonText, methodResult, eventResult, payload, waitResult)
if (payload) {
await setPayload(browser, payload)
} else {
@ -167,7 +168,7 @@ module.exports = {
},
'Should connect a local plugin': function (browser: NightwatchBrowser) {
browser.addLocalPlugin(localPluginData)
browser.addLocalPlugin(localPluginData, true)
// @ts-ignore
.frame(0).useXpath()
},
@ -295,7 +296,7 @@ module.exports = {
}, null, '/')
},
'Should get all workspaces #group2': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:getWorkspaces', [{name:"default_workspace",isGitRepo:false,hasGitSubmodules:false}, {name:"emptyworkspace",isGitRepo:false,hasGitSubmodules:false}, {name:"testspace",isGitRepo:false,hasGitSubmodules:false}], null, null)
await clickAndCheckLog(browser, 'filePanel:getWorkspaces', [{name:"default_workspace",isGitRepo:false,hasGitSubmodules:false,isGist:null}, {name:"emptyworkspace",isGitRepo:false,hasGitSubmodules:false,isGist:null}, {name:"testspace",isGitRepo:false,hasGitSubmodules:false,isGist:null}], null, null)
},
'Should have set workspace event #group2': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:createWorkspace', null, { event: 'setWorkspace', args: [{ name: 'newspace', isLocalhost: false }] }, 'newspace')
@ -309,11 +310,11 @@ module.exports = {
'Should rename workspace #group2': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:renameWorkspace', null, null, ['default_workspace', 'renamed'])
await clickAndCheckLog(browser, 'filePanel:getWorkspaces', [{name:"emptyworkspace",isGitRepo:false,hasGitSubmodules:false},{name:"testspace",isGitRepo:false,hasGitSubmodules:false},{name:"newspace",isGitRepo:false,hasGitSubmodules:false},{name:"renamed",isGitRepo:false,hasGitSubmodules:false}], null, null)
await clickAndCheckLog(browser, 'filePanel:getWorkspaces', [{name:"emptyworkspace",isGitRepo:false,hasGitSubmodules:false,isGist:null},{name:"testspace",isGitRepo:false,hasGitSubmodules:false,isGist:null},{name:"newspace",isGitRepo:false,hasGitSubmodules:false,isGist:null},{name:"renamed",isGitRepo:false,hasGitSubmodules:false,isGist:null}], null, null)
},
'Should delete workspace #group2': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:deleteWorkspace', null, null, ['testspace'])
await clickAndCheckLog(browser, 'filePanel:getWorkspaces', [{name:"emptyworkspace",isGitRepo:false,hasGitSubmodules:false},{name:"newspace",isGitRepo:false,hasGitSubmodules:false},{name:"renamed",isGitRepo:false,hasGitSubmodules:false}], null, null)
await clickAndCheckLog(browser, 'filePanel:getWorkspaces', [{name:"emptyworkspace",isGitRepo:false,hasGitSubmodules:false,isGist:null},{name:"newspace",isGitRepo:false,hasGitSubmodules:false,isGist:null},{name:"renamed",isGitRepo:false,hasGitSubmodules:false,isGist:null}], null, null)
},
// DGIT
'Should have changes on new workspace #group3': async function (browser: NightwatchBrowser) {

@ -0,0 +1,353 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
let firstProxyAddress: string
let lastProxyAddress: string
let shortenedFirstAddress: string
let shortenedLastAddress: string
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
'@sources': function () {
return sources
},
'Should show deploy proxy option for UUPS upgradeable contract #group1': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('udapp')
.switchEnvironment('vm-merge') // this runtime doesn't have the PUSH0 opcode.
.clickLaunchIcon('solidity')
.click('.remixui_compilerConfigSection')
.setValue('#evmVersionSelector', 'paris') // set an evm version which doesn't have PUSH0 opcode.
.clickLaunchIcon('filePanel')
.addFile('myTokenV1.sol', sources[0]['myTokenV1.sol'])
.clickLaunchIcon('solidity')
.pause(2000)
// because the compilatiom imports are slow and sometimes stop loading (not sure why, it's bug) we need to recompile and check to see if the files are really in de FS
.click('[data-id="compilerContainerCompileBtn"]')
.clickLaunchIcon('filePanel')
.isVisible({
selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"]',
timeout: 120000,
suppressNotFoundErrors: true
})
.clickLaunchIcon('solidity')
.click('[data-id="compilerContainerCompileBtn"]')
.clickLaunchIcon('filePanel')
.isVisible({
selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"]',
timeout: 120000,
suppressNotFoundErrors: true
})
.clickLaunchIcon('solidity')
.click('[data-id="compilerContainerCompileBtn"]')
.clickLaunchIcon('filePanel')
.waitForElementVisible({
selector: '*[data-id="treeViewDivtreeViewItem.deps/npm/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"]',
timeout: 120000,
})
.clickLaunchIcon('solidity')
.waitForElementPresent('select[id="compiledContracts"] option[value=MyToken]', 60000)
.clickLaunchIcon('udapp')
.click('select.udapp_contractNames')
.click('select.udapp_contractNames option[value=MyToken]')
.waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]')
.waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]')
},
'Should show upgrade proxy option for child contract inheriting UUPS parent contract #group1': function (browser: NightwatchBrowser) {
browser
.addFile('myTokenV2.sol', sources[1]['myTokenV2.sol'])
.clickLaunchIcon('solidity')
.assert.visible('[data-id="compilerContainerCompileBtn"]')
.click('[data-id="compilerContainerCompileBtn"]')
.waitForElementPresent('select[id="compiledContracts"] option[value=MyTokenV2]', 60000)
.clickLaunchIcon('udapp')
.click('select.udapp_contractNames')
.click('select.udapp_contractNames option[value=MyTokenV2]')
.waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]')
.waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]')
},
'Should deploy proxy without initialize parameters #group1': function (browser: NightwatchBrowser) {
browser
.openFile('myTokenV1.sol')
.clickLaunchIcon('solidity')
.assert.visible('[data-id="compilerContainerCompileBtn"]')
.click('[data-id="compilerContainerCompileBtn"]')
.waitForElementPresent('select[id="compiledContracts"] option[value=MyToken]', 60000)
.clickLaunchIcon('udapp')
.click('select.udapp_contractNames')
.click('select.udapp_contractNames option[value=MyToken]')
.verify.visible('[data-id="contractGUIDeployWithProxyLabel"]')
.waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]')
.click('[data-id="contractGUIDeployWithProxyLabel"]')
.setValue('[data-id="initializeInputs-initialOwner"]', '0x5B38Da6a701c568545dCfcB03FcB875f56beddC4')
.createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Proxy (ERC1967)')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
.click('[data-id="udappNotify-modal-footer-ok-react"]')
.waitForElementContainsText('[data-id="confirmProxyDeploymentModalDialogModalTitle-react"]', 'Confirm Deploy Proxy (ERC1967)')
.waitForElementVisible('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.click('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Deploying ERC1967 >= 5.0.0 as proxy...')
},
'Should interact with deployed contract via ERC1967 (proxy) #group1': function (browser: NightwatchBrowser) {
browser
.getAddressAtPosition(1, (address) => {
firstProxyAddress = address
shortenedFirstAddress = address.slice(0, 5) + '...' + address.slice(address.length - 5, address.length)
})
.clickInstance(1)
.perform((done) => {
browser.testConstantFunction(firstProxyAddress, 'name - call', null, '0:\nstring: MyToken').perform(() => {
done()
})
})
.perform((done) => {
browser.testConstantFunction(firstProxyAddress, 'symbol - call', null, '0:\nstring: MTK').perform(() => {
done()
})
})
},
'Should deploy proxy with initialize parameters #group1': function (browser: NightwatchBrowser) {
browser
.waitForElementPresent('[data-id="deployAndRunClearInstances"]')
.click('[data-id="deployAndRunClearInstances"]')
.addFile('initializeProxy.sol', sources[2]['initializeProxy.sol'])
.clickLaunchIcon('solidity')
.assert.visible('[data-id="compilerContainerCompileBtn"]')
.click('[data-id="compilerContainerCompileBtn"]')
.waitForElementPresent('select[id="compiledContracts"] option[value=MyInitializedToken]', 60000)
.clickLaunchIcon('udapp')
.click('select.udapp_contractNames')
.click('select.udapp_contractNames option[value=MyInitializedToken]')
.waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]')
.click('[data-id="contractGUIDeployWithProxyLabel"]')
.useXpath()
.waitForElementPresent('//*[@id="runTabView"]/div/div[2]/div[3]/div[1]/div/div[1]/div[4]/div/div[1]/input')
.waitForElementPresent('//*[@id="runTabView"]/div/div[2]/div[3]/div[1]/div/div[1]/div[4]/div/div[2]/input')
.setValue('//*[@id="runTabView"]/div/div[2]/div[3]/div[1]/div/div[1]/div[4]/div/div[1]/input', 'Remix')
.setValue('//*[@id="runTabView"]/div/div[2]/div[3]/div[1]/div/div[1]/div[4]/div/div[2]/input', "R")
.useCss()
.setValue('[data-id="initializeInputs-initialOwner"]', '0x5B38Da6a701c568545dCfcB03FcB875f56beddC4')
.createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Proxy (ERC1967)')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
.click('[data-id="udappNotify-modal-footer-ok-react"]')
.waitForElementContainsText('[data-id="confirmProxyDeploymentModalDialogModalTitle-react"]', 'Confirm Deploy Proxy (ERC1967)')
.waitForElementVisible('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.click('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Deploying ERC1967 >= 5.0.0 as proxy...')
},
'Should interact with initialized contract to verify parameters #group1': function (browser: NightwatchBrowser) {
browser
.getAddressAtPosition(1, (address) => {
lastProxyAddress = address
shortenedLastAddress = address.slice(0, 5) + '...' + address.slice(address.length - 5, address.length)
})
.clickInstance(1)
.perform((done) => {
browser.testConstantFunction(lastProxyAddress, 'name - call', null, '0:\nstring: Remix').perform(() => {
done()
})
})
.perform((done) => {
browser.testConstantFunction(lastProxyAddress, 'symbol - call', null, '0:\nstring: R').perform(() => {
done()
})
})
},
'Should upgrade contract by selecting a previously deployed proxy address from dropdown (MyTokenV1 to MyTokenV2) #group1': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="terminalClearConsole"]')
.waitForElementPresent('[data-id="deployAndRunClearInstances"]')
.click('[data-id="deployAndRunClearInstances"]')
.openFile('myTokenV2.sol')
.clickLaunchIcon('solidity')
.assert.visible('[data-id="compilerContainerCompileBtn"]')
.click('[data-id="compilerContainerCompileBtn"]')
.waitForElementPresent('select[id="compiledContracts"] option[value=MyTokenV2]', 60000)
.clickLaunchIcon('udapp')
.click('select.udapp_contractNames')
.click('select.udapp_contractNames option[value=MyTokenV2]')
.waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]')
.click('[data-id="contractGUIUpgradeImplementationLabel"]')
.waitForElementPresent('[data-id="toggleProxyAddressDropdown"]')
.click('[data-id="toggleProxyAddressDropdown"]')
.waitForElementVisible('[data-id="proxy-dropdown-items"]')
.assert.textContains('[data-id="proxy-dropdown-items"]', shortenedFirstAddress)
.assert.textContains('[data-id="proxy-dropdown-items"]', shortenedLastAddress)
.click('[data-id="proxyAddress1"]')
.createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Update Proxy')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
.click('[data-id="udappNotify-modal-footer-ok-react"]')
.waitForElementContainsText('[data-id="confirmProxyDeploymentModalDialogModalTitle-react"]', 'Confirm Update Proxy (ERC1967)')
.waitForElementVisible('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.click(
{
selector: '[data-id="confirmProxyDeployment-modal-footer-ok-react"]',
})
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Using ERC1967 >= 5.0.0 for the proxy upgrade...')
},
'Should interact with upgraded function in contract MyTokenV2 #group1': function (browser: NightwatchBrowser) {
browser
.clickInstance(1)
.perform((done) => {
browser.testConstantFunction(lastProxyAddress, 'version - call', null, '0:\nstring: MyTokenV2!').perform(() => {
done()
})
})
},
'Should upgrade contract by providing proxy address in input field (MyTokenV1 to MyTokenV2) #group1': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="terminalClearConsole"]')
.waitForElementPresent('[data-id="deployAndRunClearInstances"]')
.click('[data-id="deployAndRunClearInstances"]')
.openFile('myTokenV2.sol')
.clickLaunchIcon('solidity')
.assert.visible('[data-id="compilerContainerCompileBtn"]')
.click('[data-id="compilerContainerCompileBtn"]')
.waitForElementPresent('select[id="compiledContracts"] option[value=MyTokenV2]', 60000)
.clickLaunchIcon('udapp')
.click('select.udapp_contractNames')
.click('select.udapp_contractNames option[value=MyTokenV2]')
.waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]')
.waitForElementPresent('[data-id="toggleProxyAddressDropdown"]')
.clearValue('[data-id="ERC1967AddressInput"]')
.setValue('[data-id="ERC1967AddressInput"]', firstProxyAddress)
.createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Update Proxy')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
.click('[data-id="udappNotify-modal-footer-ok-react"]')
.waitForElementContainsText('[data-id="confirmProxyDeploymentModalDialogModalTitle-react"]', 'Confirm Update Proxy (ERC1967)')
.waitForElementVisible('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.click('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Using ERC1967 >= 5.0.0 for the proxy upgrade...')
},
'Should interact with upgraded contract through provided proxy address #group1': function (browser: NightwatchBrowser) {
browser
.clearConsole()
.clickInstance(1)
.perform((done) => {
browser.testConstantFunction(firstProxyAddress, 'version - call', null, '0:\nstring: MyTokenV2!').perform(() => {
done()
})
})
},
'Should debug the call': function(browser: NightwatchBrowser) {
browser
.debugTransaction(0)
.waitForElementVisible({
locateStrategy: 'xpath',
selector: '//*[@data-id="treeViewLivm trace step" and contains(.,"7")]',
timeout: 60000
})
.goToVMTraceStep(129)
.waitForElementContainsText('*[data-id="functionPanel"]', 'version()', 60000)
.end()
}
}
const sources = [
{
'myTokenV1.sol': {
content: `
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
contract MyToken is Initializable, ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize(address initialOwner) initializer public {
__ERC721_init("MyToken", "MTK");
__Ownable_init(initialOwner);
__UUPSUpgradeable_init();
}
function _authorizeUpgrade(address newImplementation)
internal
onlyOwner
override
{}
}
`
}
}, {
'myTokenV2.sol': {
content: `
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "./myTokenV1.sol";
contract MyTokenV2 is MyToken {
function version () public view returns (string memory) {
return "MyTokenV2!";
}
}
`
}
}, {
'initializeProxy.sol': {
content: `
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
contract MyInitializedToken is Initializable, ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize(string memory tokenName, string memory tokenSymbol, address initialOwner) initializer public {
__ERC721_init(tokenName, tokenSymbol);
__Ownable_init(initialOwner);
__UUPSUpgradeable_init();
}
function _authorizeUpgrade(address newImplementation)
internal
onlyOwner
override
{}
}
`
}
}
]

@ -38,7 +38,7 @@ module.exports = {
.openFile('ipfs/QmXYUS1ueS22EqNVRaKuZa31EgHLjKZ8uTM8vWhQLxa3pw')
},
/* Disableing the test untill refactoring and the new swarm usage
/* Disabling the test until refactoring and the new swarm usage
'Publish on Swarm': '' + function (browser: NightwatchBrowser) {
browser
.click('#publishOnSwarm')

@ -80,12 +80,23 @@ module.exports = {
.pause(1000)
.getAddressAtPosition(1, (address) => {
instanceAddress = address
console.log('instanceAddress', instanceAddress)
browser
.waitForElementVisible(`#instance${instanceAddress} [data-id="instanceContractBal"]`)
.waitForElementContainsText(`#instance${instanceAddress} [data-id="instanceContractBal"]`, 'Balance: 0.000000000000000111 ETH', 10000)
.clickFunction('sendSomeEther - transact (not payable)', { types: 'uint256 num', values: '2' })
.pause(1000)
.waitForElementContainsText(`#instance${instanceAddress} [data-id="instanceContractBal"]`, 'Balance: 0.000000000000000109 ETH', 10000)
.waitForElementVisible(`#instance${instanceAddress} [data-id="instanceContractBal"]`)
//*[@id="instance0xbBF289D846208c16EDc8474705C748aff07732dB" and contains(.,"Balance") and contains(.,'0.000000000000000111')]
.waitForElementVisible({
locateStrategy: 'xpath',
selector: `//*[@id="instance${instanceAddress}" and contains(.,"Balance") and contains(.,'0.000000000000000111')]`,
timeout: 60000
})
//.waitForElementContainsText(`#instance${instanceAddress} [data-id="instanceContractBal"]`, 'Balance: 0.000000000000000111 ETH', 60000)
.clickFunction('sendSomeEther - transact (not payable)', { types: 'uint256 num', values: '2' })
.pause(1000)
.waitForElementVisible({
locateStrategy: 'xpath',
selector: `//*[@id="instance${instanceAddress}" and contains(.,"Balance") and contains(.,'0.000000000000000109')]`,
timeout: 60000
})
})
},
@ -191,7 +202,7 @@ module.exports = {
},
/*
* This test is using 3 differents services:
* This test is using 3 different services:
* - Metamask for getting the transaction
* - Source Verifier service for fetching the contract code
* - Ropsten node for retrieving the trace and storage
@ -227,6 +238,95 @@ module.exports = {
.executeScriptInTerminal('web3.eth.getAccounts()')
.journalLastChildIncludes('[ "0x76a3ABb5a12dcd603B52Ed22195dED17ee82708f" ]')
.end()
},
'Should ensure that save environment state is checked by default #group4 #group5': function (browser: NightwatchBrowser) {
browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]')
.clickLaunchIcon('settings')
.waitForElementPresent('[data-id="settingsEnableSaveEnvStateLabel"]')
.scrollInto('[data-id="settingsEnableSaveEnvStateLabel"]')
.verify.elementPresent('[data-id="settingsEnableSaveEnvState"]:checked')
},
'Should deploy default storage contract; store value and ensure that state is saved. #group4 #group5': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('filePanel')
.click('*[data-id="treeViewLitreeViewItemcontracts"]')
.openFile('contracts/1_Storage.sol')
.pause(5000)
.clickLaunchIcon('udapp')
.waitForElementPresent('*[data-id="Deploy - transact (not payable)"]')
.click('*[data-id="Deploy - transact (not payable)"]')
.waitForElementPresent('#instance0xd9145CCE52D386f254917e481eB44e9943F39138')
.clickInstance(0)
.clickFunction('store - transact (not payable)', { types: 'uint256 num', values: '10' })
.clickFunction('retrieve - call')
.waitForElementContainsText('[data-id="treeViewLi0"]', 'uint256: 10')
.clickLaunchIcon('filePanel')
.openFile('.states/vm-shanghai/state.json')
.getEditorValue((content) => {
browser
.assert.ok(content.includes('"latestBlockNumber": "0x02"'), 'State is saved')
})
},
'Should load state after page refresh #group4': function (browser: NightwatchBrowser) {
browser.refreshPage()
.waitForElementVisible('*[data-id="remixIdeSidePanel"]')
.click('*[data-id="treeViewLitreeViewItemcontracts"]')
.openFile('contracts/1_Storage.sol')
.addAtAddressInstance('0xd9145CCE52D386f254917e481eB44e9943F39138', true, true, false)
.clickInstance(0)
.clickFunction('retrieve - call')
.waitForElementContainsText('[data-id="treeViewLi0"]', 'uint256: 10')
},
'Should save state after running web3 script #group4': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('settings')
.waitForElementPresent('[data-id="settingsTabGenerateContractMetadataLabel"]')
.click('[data-id="settingsTabGenerateContractMetadataLabel"]')
.verify.elementPresent('[data-id="settingsTabGenerateContractMetadata"]:checked')
.clickLaunchIcon('solidity')
.click('.remixui_compilerConfigSection')
.setValue('#evmVersionSelector', 'london')
.click('*[data-id="compilerContainerCompileBtn"]')
.pause(5000)
.clickLaunchIcon('udapp')
.switchEnvironment('vm-london')
.clickLaunchIcon('filePanel')
.click('*[data-id="treeViewLitreeViewItemscripts"]')
.openFile('scripts/deploy_with_web3.ts')
.click('[data-id="play-editor"]')
.waitForElementPresent('[data-id="treeViewDivDraggableItem.states/vm-london/state.json"]')
.click('[data-id="treeViewDivDraggableItem.states/vm-london/state.json"]')
.pause(100000)
.getEditorValue((content) => {
browser
.assert.ok(content.includes('"latestBlockNumber": "0x01"'), 'State is saved')
})
},
'Should ensure that .states is not updated when save env option is unchecked #group5': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('settings')
.waitForElementPresent('[data-id="settingsEnableSaveEnvStateLabel"]')
.click('[data-id="settingsEnableSaveEnvStateLabel"]')
.verify.elementNotPresent('[data-id="settingsEnableSaveEnvState"]:checked')
.clickLaunchIcon('filePanel')
.openFile('contracts/1_Storage.sol')
.pause(5000)
.clickLaunchIcon('udapp')
.waitForElementPresent('*[data-id="Deploy - transact (not payable)"]')
.click('*[data-id="Deploy - transact (not payable)"]')
.pause(5000)
.clickLaunchIcon('filePanel')
.openFile('.states/vm-shanghai/state.json')
.getEditorValue((content) => {
browser
.assert.ok(content.includes('"latestBlockNumber": "0x02"'), 'State is unchanged')
})
.end()
}
}

@ -36,9 +36,9 @@ module.exports = {
contents[i] = localContent
const name = 'test_' + i + '.sol'
browser.click('[data-id="fileExplorerNewFilecreateNewFile"]')
.waitForElementContainsText('*[data-id$="/blank"]', '', 60000)
.sendKeys('*[data-id$="/blank"] .remixui_items', name)
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.getText('.remix_ui_terminal_block', (result) => {
console.log(result)
})
@ -51,9 +51,9 @@ module.exports = {
contents[i] = localContent
const name = 'test_' + i + '.sol'
browser.click('[data-id="fileExplorerNewFilecreateNewFile"]')
.waitForElementContainsText('*[data-id$="/blank"]', '', 60000)
.sendKeys('*[data-id$="/blank"] .remixui_items', name)
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.getText('.remix_ui_terminal_block', (result) => {
console.log(result)
})
@ -66,9 +66,9 @@ module.exports = {
contents[i] = localContent
const name = 'test_' + i + '.sol'
browser.click('[data-id="fileExplorerNewFilecreateNewFile"]')
.waitForElementContainsText('*[data-id$="/blank"]', '', 60000)
.sendKeys('*[data-id$="/blank"] .remixui_items', name)
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.getText('.remix_ui_terminal_block', (result) => {
console.log(result)
})
@ -81,9 +81,9 @@ module.exports = {
contents[i] = localContent
const name = 'test_' + i + '.sol'
browser.click('[data-id="fileExplorerNewFilecreateNewFile"]')
.waitForElementContainsText('*[data-id$="/blank"]', '', 60000)
.sendKeys('*[data-id$="/blank"] .remixui_items', name)
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.getText('.remix_ui_terminal_block', (result) => {
console.log(result)
})
@ -96,9 +96,9 @@ module.exports = {
contents[i] = localContent
const name = 'test_' + i + '.sol'
browser.click('[data-id="fileExplorerNewFilecreateNewFile"]')
.waitForElementContainsText('*[data-id$="/blank"]', '', 60000)
.sendKeys('*[data-id$="/blank"] .remixui_items', name)
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.getText('.remix_ui_terminal_block', (result) => {
console.log(result)
})
@ -111,9 +111,9 @@ module.exports = {
contents[i] = localContent
const name = 'test_' + i + '.sol'
browser.click('[data-id="fileExplorerNewFilecreateNewFile"]')
.waitForElementContainsText('*[data-id$="/blank"]', '', 60000)
.sendKeys('*[data-id$="/blank"] .remixui_items', name)
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.getText('.remix_ui_terminal_block', (result) => {
console.log(result)
})
@ -126,9 +126,9 @@ module.exports = {
contents[i] = localContent
const name = 'test_' + i + '.sol'
browser.click('[data-id="fileExplorerNewFilecreateNewFile"]')
.waitForElementContainsText('*[data-id$="/blank"]', '', 60000)
.sendKeys('*[data-id$="/blank"] .remixui_items', name)
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.getText('.remix_ui_terminal_block', (result) => {
console.log(result)
})
@ -141,9 +141,9 @@ module.exports = {
contents[i] = localContent
const name = 'test_' + i + '.sol'
browser.click('[data-id="fileExplorerNewFilecreateNewFile"]')
.waitForElementContainsText('*[data-id$="/blank"]', '', 60000)
.sendKeys('*[data-id$="/blank"] .remixui_items', name)
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.getText('.remix_ui_terminal_block', (result) => {
console.log(result)
})
@ -156,9 +156,9 @@ module.exports = {
contents[i] = localContent
const name = 'test_' + i + '.sol'
browser.click('[data-id="fileExplorerNewFilecreateNewFile"]')
.waitForElementContainsText('*[data-id$="/blank"]', '', 60000)
.sendKeys('*[data-id$="/blank"] .remixui_items', name)
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.getText('.remix_ui_terminal_block', (result) => {
console.log(result)
})
@ -171,9 +171,9 @@ module.exports = {
contents[i] = localContent
const name = 'test_' + i + '.sol'
browser.click('[data-id="fileExplorerNewFilecreateNewFile"]')
.waitForElementContainsText('*[data-id$="/blank"]', '', 60000)
.sendKeys('*[data-id$="/blank"] .remixui_items', name)
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
.getText('.remix_ui_terminal_block', (result) => {
console.log(result)
})

@ -42,7 +42,7 @@ module.exports = {
.clickLaunchIcon('udapp')
.click('.udapp_contractActionsContainerSingle > div')
.clickInstance(0)
.clickFunction('retunValues1 - transact (not payable)')
.clickFunction('returnValues1 - transact (not payable)')
.testFunction('last',
{
status: '0x1 Transaction mined and execution succeed',
@ -53,7 +53,7 @@ module.exports = {
3: 'address: _a 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c'
}
})
.clickFunction('retunValues2 - transact (not payable)')
.clickFunction('returnValues2 - transact (not payable)')
.testFunction('last',
{
status: '0x1 Transaction mined and execution succeed',
@ -70,7 +70,7 @@ module.exports = {
9: 'bytes32: _b32 0x0325235325325235325235325235320000000000000000000000000000000000'
}
}).pause(500)
.clickFunction('retunValues3 - transact (not payable)')
.clickFunction('returnValues3 - transact (not payable)')
.testFunction('last',
{
status: '0x1 Transaction mined and execution succeed',
@ -150,6 +150,14 @@ module.exports = {
.click('*[data-id="deployAndRunClearInstances"]')
},
'Should filter displayed transactions #group2': function (browser: NightwatchBrowser) {
browser
// it should contain: 0xd9145CCE52D386f254917e481eB44e9943F39138
.checkTerminalFilter('0xd9145CCE52D386f254917e481eB44e9943F39138', '0xd9145CCE52D386f254917e481eB44e9943F39138', false)
// it should not contain: 0xd9145CCE52D386f254917e481eB44e9943F39140 (it ends with 40)
.checkTerminalFilter('0xd9145CCE52D386f254917e481eB44e9943F39140', '0xd9145CCE52D386f254917e481eB44e9943F39138', true)
},
'Should Compile and Deploy a contract which define a custom error, the error should be logged in the terminal #group3': function (browser: NightwatchBrowser) {
browser.testContracts('customError.sol', sources[4]['customError.sol'], ['C'])
.clickLaunchIcon('udapp')
@ -312,10 +320,10 @@ const sources = [
contract TestContract { function f() public returns (uint) { return 8; }
function g() public returns (uint, string memory, bool, uint) {
uint payment = 345;
bool payed = true;
bool paid = true;
string memory comment = "comment_comment_";
uint month = 4;
return (payment, comment, payed, month); } }`
return (payment, comment, paid, month); } }`
}
},
{
@ -323,14 +331,14 @@ const sources = [
content: `
contract testReturnValues {
enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
function retunValues1 () public returns (bool _b, uint _u, int _i, address _a) {
function returnValues1 () public returns (bool _b, uint _u, int _i, address _a) {
_b = true;
_u = 345;
_i = -345;
_a = msg.sender;
}
function retunValues2 () public returns (bytes1 _b, bytes2 _b2, bytes3 _b3, bytes memory _blit, bytes5 _b5, bytes6 _b6, string memory _str, bytes7 _b7, bytes22 _b22, bytes32 _b32) {
function returnValues2 () public returns (bytes1 _b, bytes2 _b2, bytes3 _b3, bytes memory _blit, bytes5 _b5, bytes6 _b6, string memory _str, bytes7 _b7, bytes22 _b22, bytes32 _b32) {
_b = 0x12;
_b2 = 0x1223;
_b5 = hex"043245";
@ -342,7 +350,7 @@ const sources = [
_str = "this is a long string _ this is a long string _ this is a long string _ this is a long string _ this is a long string _ this is a long string _ this is a long string _ this is a long string _ this is a long string _ this is a long string _ this is a long string _ this is a long string _ this is a long string _ this is a long string _ this is a long string";
}
function retunValues3 () public returns (ActionChoices _en, int[5][] memory _a1) {
function returnValues3 () public returns (ActionChoices _en, int[5][] memory _a1) {
_en = ActionChoices.GoStraight;
int[5][] memory a = new int[5][](3);
a[0] = [int(1),-45,-78,56,60];

@ -91,28 +91,30 @@ module.exports = {
})
},
'Should load Etherscan verified contracts from URL "address" param)': !function (browser: NightwatchBrowser) {
'Should load Etherscan verified contracts from URL "address" param) #group1': function (browser: NightwatchBrowser) {
browser
.url('http://127.0.0.1:8080/#address=0x56db08fb78bc6689a1ef66efd079083fed0e4915')
.url('http://127.0.0.1:8080/#address=0xdac17f958d2ee523a2206206994597c13d831ec7')
.refreshPage()
.currentWorkspaceIs('etherscan-code-sample')
.assert.elementPresent('*[data-id=treeViewLitreeViewItemropsten]')
.assert.elementPresent('*[data-id=treeViewLitreeViewItemrinkeby]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemrinkeby/0x56db08fb78bc6689a1ef66efd079083fed0e4915"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemrinkeby/0x56db08fb78bc6689a1ef66efd079083fed0e4915/Sample.sol"]')
.pause(7000)
.currentWorkspaceIs('code-sample')
.waitForElementVisible('*[data-id=treeViewLitreeViewItemmainnet]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemmainnet/0xdac17f958d2ee523a2206206994597c13d831ec7"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemmainnet/0xdac17f958d2ee523a2206206994597c13d831ec7/TetherToken.sol"]')
.getEditorValue((content) => {
browser.assert.ok(content && content.indexOf(
'contract Sample {') !== -1)
'contract TetherToken is Pausable, StandardToken, BlackList {') !== -1)
})
.url('http://127.0.0.1:8080/#address=0xdac17f958d2ee523a2206206994597c13d831ec7')
},
'Should load Blockscout verified contracts from URL "address" and "blockscout" params (single source)': function (browser: NightwatchBrowser) {
browser
.url('http://127.0.0.1:8080/#address=0xdAC17F958D2ee523a2206206994597C13D831ec7&blockscout=eth.blockscout.com')
.refreshPage()
.pause(7000)
.currentWorkspaceIs('etherscan-code-sample')
.assert.elementPresent('*[data-id=treeViewLitreeViewItemmainnet]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemmainnet/0xdac17f958d2ee523a2206206994597c13d831ec7"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemmainnet/0xdac17f958d2ee523a2206206994597c13d831ec7/TetherToken.sol"]')
.currentWorkspaceIs('code-sample')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemeth.blockscout.com"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemeth.blockscout.com/0xdAC17F958D2ee523a2206206994597C13D831ec7"]')
.getEditorValue((content) => {
browser.assert.ok(content && content.indexOf(
'contract TetherToken is Pausable, StandardToken, BlackList {') !== -1)
@ -120,6 +122,31 @@ module.exports = {
})
},
'Should load Blockscout verified contracts from URL "address" and "blockscout" params (multiple sources)': function (browser: NightwatchBrowser) {
browser
.url('http://127.0.0.1:8080/#address=0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9&blockscout=eth.blockscout.com')
.refreshPage()
.pause(7000)
.currentWorkspaceIs('code-sample')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemeth.blockscout.com"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemeth.blockscout.com/0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemeth.blockscout.com/0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9/contracts"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemeth.blockscout.com/0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9/contracts/open-zeppelin"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemeth.blockscout.com/0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9/contracts/open-zeppelin/Address.sol"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemeth.blockscout.com/0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9/contracts/open-zeppelin/BaseAdminUpgradeabilityProxy.sol"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemeth.blockscout.com/0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9/contracts/open-zeppelin/BaseUpgradeabilityProxy.sol"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemeth.blockscout.com/0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9/contracts/open-zeppelin/InitializableAdminUpgradeabilityProxy.sol"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemeth.blockscout.com/0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9/contracts/open-zeppelin/InitializableUpgradeabilityProxy.sol"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemeth.blockscout.com/0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9/contracts/open-zeppelin/Proxy.sol"]')
.assert.elementPresent('*[data-id="treeViewLitreeViewItemeth.blockscout.com/0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9/contracts/open-zeppelin/UpgradeabilityProxy.sol"]')
.openFile('eth.blockscout.com/0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9/contracts/open-zeppelin/InitializableAdminUpgradeabilityProxy.sol')
.getEditorValue((content) => {
browser.assert.ok(content && content.indexOf(
'contract InitializableAdminUpgradeabilityProxy is BaseAdminUpgradeabilityProxy, InitializableUpgradeabilityProxy {') !== -1)
})
},
'Should load the code from URL & code params #group1': function (browser: NightwatchBrowser) {
browser
.url('http://127.0.0.1:8080/#optimize=true&runs=300&url=https://github.com/ethereum/remix-project/blob/master/apps/remix-ide/contracts/app/solidity/mode.sol&code=cHJhZ21hIHNvbGlkaXR5ID49MC42LjAgPDAuNy4wOwoKaW1wb3J0ICJodHRwczovL2dpdGh1Yi5jb20vT3BlblplcHBlbGluL29wZW56ZXBwZWxpbi1jb250cmFjdHMvYmxvYi9tYXN0ZXIvY29udHJhY3RzL2FjY2Vzcy9Pd25hYmxlLnNvbCI7Cgpjb250cmFjdCBHZXRQYWlkIGlzIE93bmFibGUgewogIGZ1bmN0aW9uIHdpdGhkcmF3KCkgZXh0ZXJuYWwgb25seU93bmVyIHsKICB9Cn0')
@ -137,6 +164,13 @@ module.exports = {
'proposals.length = _numProposals;') !== -1,
'code has been loaded')
})
.url('http://127.0.0.1:8080') // refresh without loading the code sample
.currentWorkspaceIs('default_workspace')
.execute(() => {
return document.querySelector('[data-id="dropdown-item-code-sample"]') === null
}, [], (result) => {
browser.assert.ok((result as any).value, 'sample template has not be persisted.') // code-sample should not be kept.
})
},
'Should load the code from language & code params #group1': function (browser: NightwatchBrowser) {
@ -276,12 +310,8 @@ module.exports = {
browser
.url('http://127.0.0.1:8080/#optimize=false&runs=200&url=https://raw.githubusercontent.com/EthVM/evm-source-verification/main/contracts/1/0x011e5846975c6463a8c6337eecf3cbf64e328884/input.json')
.refreshPage()
.switchWorkspace('code-sample')
.openFile('@openzeppelin')
.openFile('@openzeppelin/contracts')
.openFile('@openzeppelin/contracts/access')
.openFile('@openzeppelin/contracts/access/AccessControl.sol')
.currentWorkspaceIs('code-sample')
.waitForElementVisible('*[data-id="treeViewDivtreeViewItem@openzeppelin/contracts/access/AccessControl.sol"]')
.openFile('contracts')
.openFile('contracts/governance')
.openFile('contracts/governance/UnionGovernor.sol')

@ -7,7 +7,7 @@ module.exports = {
init(browser, done, 'http://127.0.0.1:8080', false)
},
'Checks vertical icons panelcontex menu': function (browser: NightwatchBrowser) {
'Checks vertical icons panel context menu': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('div[data-id="remixIdeIconPanel"]', 10000)
.waitForElementVisible('*[data-id="verticalIconsKindpluginManager"]')
.click('*[data-id="verticalIconsKindpluginManager"]')
@ -19,7 +19,7 @@ module.exports = {
.click('*[data-id="remixIdeIconPanel"]')
},
'Checks vertical icons panel contex menu deactivate': function (browser: NightwatchBrowser) {
'Checks vertical icons panel context menu deactivate': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('div[data-id="remixIdeIconPanel"]', 10000)
.waitForElementVisible('*[data-id="verticalIconsKinddebugger"]', 7000)
.pause(5000)

@ -9,7 +9,7 @@ declare global {
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
init(browser, done, 'http://localhost:8080')
},
'Should connect to vyper plugin #group1': function (browser: NightwatchBrowser) {
@ -41,33 +41,80 @@ module.exports = {
.openFile('examples/auctions/blind_auction.vy')
},
'Context menu click to compile blind_auction should succeed #group1': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="treeViewLitreeViewItemexamples/auctions/blind_auction.vy"]')
.rightClick('*[data-id="treeViewLitreeViewItemexamples/auctions/blind_auction.vy"]')
.waitForElementPresent('[data-id="contextMenuItemvyper"]')
.click('[data-id="contextMenuItemvyper"]')
.clickLaunchIcon('vyper')
// @ts-ignore
.frame(0)
.waitForElementVisible({
selector:'[data-id="compilation-details"]',
timeout: 120000
})
.click('[data-id="compilation-details"]')
.frameParent()
.waitForElementVisible('[data-id="copy-abi"]')
.waitForElementVisible({
selector: "//*[@class='variable-value' and contains(.,'highestBidder')]",
locateStrategy: 'xpath',
})
},
'Compile blind_auction should success #group1': function (browser: NightwatchBrowser) {
browser.clickLaunchIcon('vyper')
browser
// @ts-ignore
.frame(0)
.click('[data-id="remote-compiler"]')
.click('[data-id="compile"]')
.isVisible({
selector: '[data-id="copy-abi"]',
timeout: 4000,
abortOnFailure: false,
suppressNotFoundErrors: true
}, (okVisible) => {
if (okVisible.value === null) {
console.log('retrying compilation...')
browser.click('[data-id="compile"]').waitForElementVisible('[data-id="copy-abi"]')
} else{
browser.assert.ok(okVisible.value === true, 'ABI should be visible')
}
.waitForElementVisible({
selector:'[data-id="compilation-details"]',
timeout: 120000
})
.click('[data-id="compilation-details"]')
.frameParent()
.waitForElementVisible('[data-id="copy-abi"]')
.waitForElementVisible({
selector: "//*[@class='variable-value' and contains(.,'highestBidder')]",
locateStrategy: 'xpath',
})
},
'Should copy abi after blind_auction compile #group1': function (browser: NightwatchBrowser) {
if (browser.browserName.indexOf('chrome') > -1) {
const chromeBrowser = (browser as any).chrome
chromeBrowser.setPermission('clipboard-read', 'granted')
chromeBrowser.setPermission('clipboard-write', 'granted')
browser
.frame(0)
.click('[data-id="compile"]')
.waitForElementVisible({
selector:'[data-id="compilation-details"]',
timeout: 120000
})
.click('[data-id="compilation-details"]')
.frameParent()
.waitForElementVisible('[data-id="copy-abi"]')
.click('[data-id="copy-abi"]')
.executeAsyncScript(function (done) {
navigator.clipboard.readText()
.then(function (clippedText) {
done(clippedText)
}).catch(function (error) {
console.log('Failed to read clipboard contents: ', error)
done()
})
}, [], function (result) {
console.log('clipboard result: ' + result)
browser.assert.ok((result as any).value.length > 1, 'abi copied to clipboard')
})
}
},
'Compile test contract and deploy to remix VM #group1': function (browser: NightwatchBrowser) {
let contractAddress
browser
.frameParent()
.clickLaunchIcon('filePanel')
.switchWorkspace('default_workspace')
.addFile('test.vy', { content: testContract })
@ -75,20 +122,13 @@ module.exports = {
// @ts-ignore
.frame(0)
.click('[data-id="compile"]')
.isVisible({
selector: '[data-id="copy-abi"]',
timeout: 4000,
abortOnFailure: false,
suppressNotFoundErrors: true
}, (okVisible) => {
if (okVisible.value === null) {
console.log('retrying compilation...')
browser.click('[data-id="compile"]').waitForElementVisible('[data-id="copy-abi"]')
} else{
browser.assert.ok(okVisible.value === true, 'ABI should be visible')
}
.waitForElementVisible({
selector:'[data-id="compilation-details"]',
timeout: 60000
})
.click('[data-id="compilation-details"]')
.frameParent()
.waitForElementVisible('[data-id="copy-abi"]')
.clickLaunchIcon('udapp')
.createContract('')
.clickInstance(0)
@ -105,7 +145,6 @@ module.exports = {
}
const testContract = `
# @version >=0.2.4 <0.3.0
DNA_DIGITS: constant(uint256) = 16
DNA_MODULUS: constant(uint256) = 10 ** DNA_DIGITS
@ -136,4 +175,4 @@ def _createPokemon(_name: String[32], _dna: uint256, _HP: uint256):
matches: 0,
wins: 0
})
self.totalPokemonCount += 1`
self.totalPokemonCount += 1`

@ -501,7 +501,7 @@ module.exports = {
.pause(2000)
},
'Should change the current workspace in localstorage to a non existant value, reload the page and see the workspace created #group2': function (browser: NightwatchBrowser) {
'Should change the current workspace in localstorage to a non existent value, reload the page and see the workspace created #group2': function (browser: NightwatchBrowser) {
browser
.execute(function () {
localStorage.setItem('currentWorkspace', 'non_existing_workspace')
@ -540,6 +540,29 @@ module.exports = {
},
'Should create a cookbook workspace #group3': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('filePanel')
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=uniswapV4HookBookMultiSigSwapHook]')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'multisig cookbook' })
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.waitForElementVisible('[data-id="PermissionHandler-modal-footer-ok-react"]', 300000)
.click('[data-id="PermissionHandler-modal-footer-ok-react"]')
// click on lib to close it
.waitForElementVisible('*[data-id="treeViewLitreeViewItemlib"]')
.click('*[data-id="treeViewLitreeViewItemlib"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemsrc"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemsrc/MULTI_SIG"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemsrc/MULTI_SIG/MultiSigSwapHook.sol"]')
},
tearDown: sauce
}

@ -209,7 +209,7 @@ module.exports = {
.expect.element('[data-id="workspaceGit-newLocalBranch"]').text.to.contain('✓ ')
},
'Should checkout to an exisiting local branch #group3': function (browser: NightwatchBrowser) {
'Should checkout to an existing local branch #group3': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('[data-id="custom-dropdown-menu"]')
.waitForElementPresent('[data-id="workspaceGitInput"]')
@ -239,7 +239,7 @@ module.exports = {
.expect.element('[data-id="workspaceGit-main"]').text.to.contain('✓ ')
},
'Should force checkout to a branch with exisiting local changes #group3': function (browser: NightwatchBrowser) {
'Should force checkout to a branch with existing local changes #group3': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('[data-id="workspaceGit-dev"]')
.click('[data-id="workspaceGit-dev"]')
@ -271,7 +271,7 @@ module.exports = {
.waitForElementPresent('.fa-spinner')
.waitForElementVisible({
selector: '*[data-id="treeViewLitreeViewItem.git"]',
timeout: 60000
timeout: 240000
})
.waitForElementContainsText('[data-id="workspacesSelect"]', 'test-branch-submodule')
.waitForElementVisible('[data-id="updatesubmodules"]')
@ -279,32 +279,47 @@ module.exports = {
.waitForElementPresent('.fa-spinner')
.waitForElementVisible({
selector: '*[data-id="treeViewLitreeViewItem.git"]',
timeout: 60000
timeout: 240000,
abortOnFailure: false,
suppressNotFoundErrors: true
})
.waitForElementVisible('[data-id="treeViewDivtreeViewItemplugins"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemwebsite"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemrecursive"]')
.pause(2000)
.click('[data-id="treeViewDivtreeViewItemwebsite"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemwebsite/index.html"]')
.click('[data-id="treeViewDivtreeViewItemplugins"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemplugins/README.md"]')
.click('[data-id="treeViewDivtreeViewItemrecursive"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemrecursive/README.md"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemrecursive/plugins"]')
.click('[data-id="treeViewDivtreeViewItemrecursive/plugins"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemrecursive/plugins/build"]')
// check recursive submodule
.waitForElementVisible('[data-id="treeViewDivtreeViewItemtest-branch-submodule-recursive"]')
.click('[data-id="treeViewDivtreeViewItemtest-branch-submodule-recursive"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemtest-branch-submodule-recursive/test-branch-submodule-2"]')
.click('[data-id="treeViewDivtreeViewItemtest-branch-submodule-recursive/test-branch-submodule-2"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemtest-branch-submodule-recursive/test-branch-submodule-2/submodule2.ts"]')
// check test-branch-submodule-2 submodule
.waitForElementVisible('[data-id="treeViewDivtreeViewItemtest-branch-submodule-2"]')
.click('[data-id="treeViewDivtreeViewItemtest-branch-submodule-2"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemtest-branch-submodule-2/submodule2.ts"]')
// check libdeep submodule
.waitForElementVisible('[data-id="treeViewDivtreeViewItemlibdeep"]')
.click('[data-id="treeViewDivtreeViewItemlibdeep"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemlibdeep/test-branch-submodule-2"]')
.click('[data-id="treeViewDivtreeViewItemlibdeep/test-branch-submodule-2"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemlibdeep/test-branch-submodule-2/submodule2.ts"]')
// check libdeep2 submodule
.waitForElementVisible('[data-id="treeViewDivtreeViewItemlibdeep2"]')
.click('[data-id="treeViewDivtreeViewItemlibdeep2"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemlibdeep2/recursive"]')
.click('[data-id="treeViewDivtreeViewItemlibdeep2/recursive"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemlibdeep2/recursive/test-branch-submodule-2"]')
.click('[data-id="treeViewDivtreeViewItemlibdeep2/recursive/test-branch-submodule-2"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemlibdeep2/recursive/test-branch-submodule-2/submodule2.ts"]')
},
'When switching branches the submodules should dissappear #group4': function (browser: NightwatchBrowser) {
'When switching branches the submodules should disappear #group4': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('[data-id="workspaceGitBranchesDropdown"]')
.click('[data-id="workspaceGitBranchesDropdown"]')
.waitForElementVisible('[data-id="custom-dropdown-menu"]')
.waitForElementContainsText('[data-id="custom-dropdown-items"]', 'origin/second')
.waitForElementPresent('[data-id="workspaceGit-origin/second"]')
.click('[data-id="workspaceGit-origin/second"]')
.waitForElementNotPresent('[data-id="treeViewDivtreeViewItemplugins"]')
.waitForElementNotPresent('[data-id="treeViewDivtreeViewItemwebsite"]')
.waitForElementContainsText('[data-id="custom-dropdown-items"]', 'origin/empty')
.waitForElementPresent('[data-id="workspaceGit-origin/empty"]')
.click('[data-id="workspaceGit-origin/empty"]')
.waitForElementNotPresent('[data-id="treeViewDivtreeViewItemlibdeep"]')
.waitForElementNotPresent('[data-id="treeViewDivtreeViewItemtest-branch-submodule-recursive"]')
.waitForElementNotPresent('[data-id="treeViewDivtreeViewItemtest-branch-submodule-2"]')
},
'When switching to main update the modules #group4': function (browser: NightwatchBrowser) {
browser
@ -319,48 +334,59 @@ module.exports = {
.waitForElementPresent('.fa-spinner')
.waitForElementVisible({
selector: '*[data-id="treeViewLitreeViewItem.git"]',
timeout: 60000
timeout: 240000
})
.waitForElementVisible('[data-id="treeViewDivtreeViewItemplugins"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemwebsite"]')
.pause(2000)
.click('[data-id="treeViewDivtreeViewItemwebsite"]')
.click('[data-id="treeViewDivtreeViewItemwebsite"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemwebsite/index.html"]')
.click('[data-id="treeViewDivtreeViewItemplugins"]')
.click('[data-id="treeViewDivtreeViewItemplugins"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemplugins/README.md"]')
.click('[data-id="treeViewDivtreeViewItemrecursive"]')
.click('[data-id="treeViewDivtreeViewItemrecursive"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemrecursive/README.md"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemrecursive/plugins"]')
.click('[data-id="treeViewDivtreeViewItemrecursive/plugins"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemrecursive/plugins/build"]')
// check recursive submodule
.waitForElementVisible('[data-id="treeViewDivtreeViewItemtest-branch-submodule-recursive"]')
.click('[data-id="treeViewDivtreeViewItemtest-branch-submodule-recursive"]')
.click('[data-id="treeViewDivtreeViewItemtest-branch-submodule-recursive"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemtest-branch-submodule-recursive/test-branch-submodule-2"]')
.click('[data-id="treeViewDivtreeViewItemtest-branch-submodule-recursive/test-branch-submodule-2"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemtest-branch-submodule-recursive/test-branch-submodule-2/submodule2.ts"]')
// check test-branch-submodule-2 submodule
.waitForElementVisible('[data-id="treeViewDivtreeViewItemtest-branch-submodule-2"]')
.click('[data-id="treeViewDivtreeViewItemtest-branch-submodule-2"]')
.click('[data-id="treeViewDivtreeViewItemtest-branch-submodule-2"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemtest-branch-submodule-2/submodule2.ts"]')
// check libdeep submodule
.waitForElementVisible('[data-id="treeViewDivtreeViewItemlibdeep"]')
.click('[data-id="treeViewDivtreeViewItemlibdeep"]')
.click('[data-id="treeViewDivtreeViewItemlibdeep"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemlibdeep/test-branch-submodule-2"]')
.click('[data-id="treeViewDivtreeViewItemlibdeep/test-branch-submodule-2"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemlibdeep/test-branch-submodule-2/submodule2.ts"]')
// check libdeep2 submodule
.waitForElementVisible('[data-id="treeViewDivtreeViewItemlibdeep2"]')
.click('[data-id="treeViewDivtreeViewItemlibdeep2"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemlibdeep2/recursive"]')
.click('[data-id="treeViewDivtreeViewItemlibdeep2/recursive"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemlibdeep2/recursive/test-branch-submodule-2"]')
.click('[data-id="treeViewDivtreeViewItemlibdeep2/recursive/test-branch-submodule-2"]')
.waitForElementVisible('[data-id="treeViewDivtreeViewItemlibdeep2/recursive/test-branch-submodule-2/submodule2.ts"]')
},
// GIT SUBMODULES E2E ENDS
// GIT WORKSPACE E2E STARTS
'Should create a git workspace (uniswapV4Periphery) #group4': function (browser: NightwatchBrowser) {
'Should create a git workspace (uniswapV4Template) #group4': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=uniswapV4Periphery]')
.click('select[id="wstemplate"] option[value=uniswapV4Template]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.pause(100)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.openFile('contracts')
.openFile('contracts/hooks')
.openFile('contracts/hooks/examples')
.openFile('contracts/hooks/examples/FullRange.sol')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemsrc"]')
.openFile('src')
.openFile('src/Counter.sol')
.pause(1000)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`contract FullRange is BaseHook`) !== -1,
browser.assert.ok(content.indexOf(`contract Counter is BaseHook {`) !== -1,
'Incorrect content')
})
},
@ -378,4 +404,4 @@ url = https://github.com/bunsenstraat/empty3
[submodule "testactionsub"]
path = testactionsub
url = https://github.com/bunsenstraat/testactions
`
`

@ -15,6 +15,7 @@ declare module 'nightwatch' {
verifyContracts(compiledContractNames: string[], opts?: {wait: number; version?: string; runs?: string}): NightwatchBrowser
selectAccount(account?: string): NightwatchBrowser
clickFunction(fnFullName: string, expectedInput?: NightwatchClickFunctionExpectedInput): NightwatchBrowser
checkClipboard(): NightwatchBrowser
testFunction(txHash: string, expectedInput: NightwatchTestFunctionExpectedInput): NightwatchBrowser
goToVMTraceStep(step: number, incr?: number): NightwatchBrowser
checkVariableDebug(id: string, debugValue: NightwatchCheckVariableDebugValue): NightwatchBrowser
@ -53,14 +54,14 @@ declare module 'nightwatch' {
notContainsText(cssSelector: string, text: string): NightwatchBrowser
sendLowLevelTx(address: string, value: string, callData: string): NightwatchBrowser
journalLastChild(val: string): NightwatchBrowser
checkTerminalFilter(filter: string, test: string): NightwatchBrowser
checkTerminalFilter(filter: string, test: string, notContain: boolean): NightwatchBrowser
noWorkerErrorFor(version: string): NightwatchBrowser
validateValueInput(selector: string, valueTosSet: string[], expectedValue: string): NightwatchBrowser
checkAnnotations(type: string): NightwatchBrowser
checkAnnotationsNotPresent(type: string): NightwatchBrowser
getLastTransactionHash(callback: (hash: string) => void)
currentWorkspaceIs(name: string): NightwatchBrowser
addLocalPlugin(this: NightwatchBrowser, profile: Profile & LocationProfile & ExternalProfile): NightwatchBrowser
addLocalPlugin(this: NightwatchBrowser, profile: Profile & LocationProfile & ExternalProfile, focus: boolean): NightwatchBrowser
acceptAndRemember(this: NightwatchBrowser, remember: boolean, accept: boolean): NightwatchBrowser
clearConsole(this: NightwatchBrowser): NightwatchBrowser
clearTransactions(this: NightwatchBrowser): NightwatchBrowser

@ -14,15 +14,15 @@
pathval "1.1.1"
type-detect "4.0.8"
"@openzeppelin/contracts-upgradeable@^5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-5.0.0.tgz#859c00c55f04b6dda85b3c88bce507d65019888f"
integrity sha512-D54RHzkOKHQ8xUssPgQe2d/U92mwaiBDY7qCCVGq6VqwQjsT3KekEQ3bonev+BLP30oZ0R1U6YC8/oLpizgC5Q==
"@openzeppelin/contracts-upgradeable@^5.0.2":
version "5.0.2"
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-5.0.2.tgz#3e5321a2ecdd0b206064356798c21225b6ec7105"
integrity sha512-0MmkHSHiW2NRFiT9/r5Lu4eJq5UJ4/tzlOgYXNAIj/ONkQTVnz22pLxDvp4C4uZ9he7ZFvGn3Driptn1/iU7tQ==
"@openzeppelin/contracts@^5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-5.0.0.tgz#ee0e4b4564f101a5c4ee398cd4d73c0bd92b289c"
integrity sha512-bv2sdS6LKqVVMLI5+zqnNrNU/CA+6z6CmwFXm/MzmOPBRSO5reEJN7z0Gbzvs0/bv/MZZXNklubpwy3v2+azsw==
"@openzeppelin/contracts@^5.0.2":
version "5.0.2"
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-5.0.2.tgz#b1d03075e49290d06570b2fd42154d76c2a5d210"
integrity sha512-ytPc6eLGcHHnapAZ9S+5qsdomhjo6QBHTDRRBFfTxXIpsicMhVPouPgmUPebZZZGX7vt9USA+Z+0M0dSVtSUEA==
"@openzeppelin/upgrades-core@^1.30.0":
version "1.30.1"
@ -1437,9 +1437,9 @@ flat@^5.0.2:
integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==
follow-redirects@^1.0.0, follow-redirects@^1.15.0:
version "1.15.2"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
version "1.15.4"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.4.tgz#cdc7d308bf6493126b17ea2191ea0ccf3e535adf"
integrity sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==
for-each@^0.3.3:
version "0.3.3"
@ -1910,14 +1910,14 @@ internal-slot@^1.0.5:
side-channel "^1.0.4"
ip@^1.1.8:
version "1.1.8"
resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.8.tgz#ae05948f6b075435ed3307acce04629da8cdbf48"
integrity sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==
version "1.1.9"
resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.9.tgz#8dfbcc99a754d07f425310b86a99546b1151e396"
integrity sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==
ip@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da"
integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==
version "2.0.1"
resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.1.tgz#e8f3595d33a3ea66490204234b77636965307105"
integrity sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==
is-accessor-descriptor@^0.1.6:
version "0.1.6"

@ -1,4 +1,4 @@
# This dockerfile is to build each branch seperately (for dev purpouses)
# This dockerfile is to build each branch separately (for dev purpouses)
FROM node:10
# Create Remix user, don't use root!
# RUN yes | adduser --disabled-password remix && mkdir /app

@ -2,8 +2,8 @@
var fs = require('fs')
var compiler = require('solc')
var compilerInput = require('@remix-project/remix-solidity').CompilerInput
var defaultVersion = 'soljson-v0.8.22+commit.4fc1097e.js'
var compilerInput = require('@remix-project/remix-solidity').compilerInputFactory
var defaultVersion = 'soljson-v0.8.24+commit.e11b9ed9.js'
const path = require('path')
compiler.loadRemoteVersion(defaultVersion, (error, solcSnapshot) => {

@ -18,7 +18,7 @@ function removeTunnel () {
retrieveTunnel(data[k], function (error, result) {
if (error) {
console.log(error)
} else if (result.identtifier === tunnelName) {
} else if (result.identifier === tunnelName) {
deleteTunnel(result.id, function () {
console.log('tunnel deleted ' + data[k] + ' ' + tunnelName)
})
@ -35,7 +35,7 @@ function retrieveTunnel (tunnelid, callback) {
if (error) {
callback(error)
} else {
callback(null, {'identtifier': JSON.parse(result).tunnel_identifier, 'id': tunnelid})
callback(null, {'identifier': JSON.parse(result).tunnel_identifier, 'id': tunnelid})
}
})
}

@ -3355,7 +3355,7 @@
"id": 1818,
"nodeType": "StructuredDocumentation",
"src": "361:227:1",
"text": "@dev Compute the address a contract will be deployed at for a given deployer address and nonce\n @notice adapated from Solmate implementation (https://github.com/transmissions11/solmate/blob/main/src/utils/LibRLP.sol)"
"text": "@dev Compute the address a contract will be deployed at for a given deployer address and nonce\n @notice adapted from Solmate implementation (https://github.com/transmissions11/solmate/blob/main/src/utils/LibRLP.sol)"
},
"implemented": true,
"kind": "function",

@ -21,8 +21,8 @@ we move the documentation to the remix-ide repository
## medium post policy
Any post that relates to Ethereum could be put in the remix plublication.
Although that is not mandatory and left up to the writter.
Any post that relates to Ethereum could be put in the remix publication.
Although that is not mandatory and left up to the writer.
## guided tour
@ -32,7 +32,7 @@ It will work as a native plugin, started by default.
Each other native plugin can request a guided tour with:
`this.call('guidedtour', 'start', 'debugger')`
Other type of plugin may be able to the native plugin guided tour but we won't push this if the integration is not working out of the box.
We rather update the remix-plugin doc saying that `guided tour framework name` is the prefferred one.
We rather update the remix-plugin doc saying that `guided tour framework name` is the preferred one.
## web site
@ -49,7 +49,7 @@ it will be set of file:
- solidity contract
- test contract
we only support md for now and move to supporting other format if needded.
we only support md for now and move to supporting other format if needed.
It requires the "test" native plugin to extend its API.
@rob/@francois are managing that.
@ -89,7 +89,7 @@ RemixProject/IDE
## browser test
a PR (https://github.com/ethereum/remix-ide/pull/1961) is waiting for merging @yann
It iwll be followed by other PRs, aiming to improve process of writting browser tests.
It will be followed by other PRs, aiming to improve process of writing browser tests.
## desktop version

@ -3,7 +3,7 @@
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/remix-ide/src",
"projectType": "application",
"implicitDependencies": ["doc-gen", "doc-viewer", "etherscan", "vyper", "solhint", "walletconnect", "circuit-compiler"],
"implicitDependencies": ["doc-gen", "doc-viewer", "etherscan", "vyper", "solhint", "walletconnect", "circuit-compiler", "learneth"],
"targets": {
"build": {
"executor": "@nrwl/webpack:webpack",

@ -47,6 +47,7 @@ import { FileDecorator } from './app/plugins/file-decorator'
import { CodeFormat } from './app/plugins/code-format'
import { SolidityUmlGen } from './app/plugins/solidity-umlgen'
import { CompilationDetailsPlugin } from './app/plugins/compile-details'
import { VyperCompilationDetailsPlugin } from './app/plugins/vyper-compilation-details'
import { ContractFlattener } from './app/plugins/contractFlattener'
import { TemplatesPlugin } from './app/plugins/remix-templates'
import { fsPlugin } from './app/plugins/electron/fsPlugin'
@ -56,6 +57,7 @@ import { electronTemplates } from './app/plugins/electron/templatesPlugin'
import { xtermPlugin } from './app/plugins/electron/xtermPlugin'
import { ripgrepPlugin } from './app/plugins/electron/ripgrepPlugin'
import { compilerLoaderPlugin, compilerLoaderPluginDesktop } from './app/plugins/electron/compilerLoaderPlugin'
import {OpenAIGpt} from './app/plugins/openaigpt'
const isElectron = require('is-electron')
@ -163,11 +165,11 @@ class AppComponent {
'remix.ethereum.org': 23,
'6fd22d6fe5549ad4c4d8fd3ca0b7816b.mod': 35 // remix desktop
}
this.matomoConfAlreadySet = Registry.getInstance().get('config').api.exists('settings/matomo-analytics')
this.matomoCurrentSetting = Registry.getInstance().get('config').api.get('settings/matomo-analytics')
this.showMatamo = matomoDomains[window.location.hostname] && !this.matomoConfAlreadySet
this.walkthroughService = new WalkthroughService(appManager)
this.platform = isElectron() ? 'desktop' : 'web'
@ -225,7 +227,7 @@ class AppComponent {
// ----------------- Compilation Details ----------------------------
const compilationDetails = new CompilationDetailsPlugin(appManager)
const vyperCompilationDetails = new VyperCompilationDetailsPlugin(appManager)
// ----------------- ContractFlattener ----------------------------
const contractFlattener = new ContractFlattener()
@ -353,6 +355,7 @@ class AppComponent {
search,
solidityumlgen,
compilationDetails,
vyperCompilationDetails,
contractFlattener,
solidityScript,
templates,
@ -457,7 +460,7 @@ class AppComponent {
} catch (e) {
console.log("couldn't register iframe plugins", e.message)
}
if(isElectron()){
if (isElectron()){
await this.appManager.activatePlugin(['fs'])
}
await this.appManager.activatePlugin(['layout'])
@ -496,7 +499,7 @@ class AppComponent {
await this.appManager.activatePlugin(['walkthrough', 'storage', 'search', 'compileAndRun', 'recorder'])
await this.appManager.activatePlugin(['solidity-script', 'remix-templates'])
if(isElectron()){
if (isElectron()){
await this.appManager.activatePlugin(['isogit', 'electronconfig', 'electronTemplates', 'xterm', 'ripgrep'])
}

@ -1,4 +1,5 @@
import {RemixApp} from '@remix-ui/app'
import axios from 'axios'
import React, {useEffect, useRef, useState} from 'react'
import { createRoot } from 'react-dom/client'
import * as packageJson from '../../../../../package.json'
@ -11,6 +12,7 @@ import isElectron from 'is-electron'
const _paq = (window._paq = window._paq || [])
export const Preload = (props: any) => {
const [tip, setTip] = useState<string>('')
const [supported, setSupported] = useState<boolean>(true)
const [error, setError] = useState<boolean>(false)
const [showDownloader, setShowDownloader] = useState<boolean>(false)
@ -80,7 +82,7 @@ export const Preload = (props: any) => {
}
}
useEffect(() => {
useEffect (() => {
if(isElectron()){
loadAppComponent()
return
@ -95,6 +97,24 @@ export const Preload = (props: any) => {
!remixIndexedDB.current.loaded && (await setFileSystems())
}
loadStorage()
const abortController = new AbortController()
const signal = abortController.signal
async function showRemixTips() {
const response = await axios.get('https://raw.githubusercontent.com/remix-project-org/remix-dynamics/main/ide/tips.json', { signal })
if (signal.aborted) return
const tips = response.data
const index = Math.floor(Math.random() * (tips.length - 1))
setTip(tips[index])
}
try {
showRemixTips()
} catch (e) {
console.log(e)
}
return () => {
abortController.abort();
};
}, [])
return (
@ -157,7 +177,13 @@ export const Preload = (props: any) => {
) : null}
{supported && !error && !showDownloader ? (
<div>
<i className="fas fa-spinner fa-spin fa-2x"></i>
<div className='text-center'>
<i className="fas fa-spinner fa-spin fa-2x"></i>
</div>
{ tip && <div className='remix_tips text-center mt-3'>
<div><b>DID YOU KNOW</b></div>
<span>{tip}</span>
</div> }
</div>
) : null}
</div>

@ -52,7 +52,8 @@ class Editor extends Plugin {
cairo: 'cairo',
ts: 'typescript',
move: 'move',
circom: 'circom'
circom: 'circom',
nr: 'rust'
}
this.activated = false
@ -360,7 +361,7 @@ class Editor extends Plugin {
/**
* Path of the currently editing file
* returns `undefined` if no session is being editer
* returns `undefined` if no session is being edited
* @return {String} path of the current session
*/
current () {
@ -449,7 +450,7 @@ class Editor extends Plugin {
}
/**
* Clears all the decorations for the given @arg filePath and @arg plugin, if none is given, the current sesssion is used.
* Clears all the decorations for the given @arg filePath and @arg plugin, if none is given, the current session is used.
* An annotation has the following shape:
column: -1
row: -1
@ -502,7 +503,7 @@ class Editor extends Plugin {
}
/**
* Clears all the annotations for the given @arg filePath, the plugin name is retrieved from the context, if none is given, the current sesssion is used.
* Clears all the annotations for the given @arg filePath, the plugin name is retrieved from the context, if none is given, the current session is used.
* An annotation has the following shape:
column: -1
row: -1

@ -577,7 +577,7 @@ class DGitProvider extends Plugin {
dir
})
} catch (e) {
this.call('terminal', 'log', { type: 'error', value: `[Cloning]: Error occured! ${e}` })
this.call('terminal', 'log', { type: 'error', value: `[Cloning]: Error occurred! ${e}` })
console.log(e)
}
}
@ -587,7 +587,7 @@ class DGitProvider extends Plugin {
}, 1000)
}
} catch (e) {
this.call('terminal', 'log', { type: 'error', value: `[Cloning]: Error occured! ${e}` })
this.call('terminal', 'log', { type: 'error', value: `[Cloning]: Error occurred! ${e}` })
// do nothing
}
}

@ -24,7 +24,7 @@ const profile = {
methods: ['closeAllFiles', 'closeFile', 'file', 'exists', 'open', 'writeFile', 'writeMultipleFiles', 'writeFileNoRewrite',
'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir', 'readdir', 'dirList', 'fileList', 'remove', 'getCurrentFile', 'getFile',
'getFolder', 'setFile', 'switchFile', 'refresh', 'getProviderOf', 'getProviderByName', 'getPathFromUrl', 'getUrlFromPath',
'saveCurrentFile', 'setBatchFiles', 'isGitRepo', 'isFile', 'isDirectory', 'hasGitSubmodule'
'saveCurrentFile', 'setBatchFiles', 'isGitRepo', 'isFile', 'isDirectory', 'hasGitSubmodule', 'copyFolderToJson'
],
kind: 'file-system'
}
@ -152,9 +152,9 @@ class FileManager extends Plugin {
refresh() {
const provider = this.fileProviderOf('/')
// emit rootFolderChanged so that File Explorer reloads the file tree
if(Registry.getInstance().get('platform').api.isDesktop()){
if (Registry.getInstance().get('platform').api.isDesktop()) {
provider.event.emit('refresh')
}else{
} else {
provider.event.emit('rootFolderChanged', provider.workspace || '/')
this.emit('rootFolderChanged', provider.workspace || '/')
}
@ -192,8 +192,8 @@ class FileManager extends Plugin {
path = this.normalize(path)
path = this.limitPluginScope(path)
path = this.getPathFromUrl(path).file
//await this._handleExists(path, `Cannot open file ${path}`)
//await this._handleIsFile(path, `Cannot open file ${path}`)
await this._handleExists(path, `Cannot open file ${path}`)
await this._handleIsFile(path, `Cannot open file ${path}`)
await this.openFile(path)
}
@ -560,7 +560,6 @@ class FileManager extends Plugin {
}
}
}
// TODO: Only keep `this.emit` (issue#2210)
this.emit('fileRenamed', oldName, newName, isFolder)
}
@ -577,7 +576,6 @@ class FileManager extends Plugin {
}
async closeAllFiles() {
// TODO: Only keep `this.emit` (issue#2210)
this.emit('filesAllClosed')
for (const file in this.openedFiles) {
await this.closeFile(file)
@ -588,10 +586,8 @@ class FileManager extends Plugin {
delete this.openedFiles[name]
if (!Object.keys(this.openedFiles).length) {
this._deps.config.set('currentFile', '')
// TODO: Only keep `this.emit` (issue#2210)
this.emit('noFileSelected')
}
// TODO: Only keep `this.emit` (issue#2210)
this.emit('fileClosed', name)
}
@ -614,7 +610,7 @@ class FileManager extends Plugin {
return new Promise((resolve, reject) => {
if (this.currentFile() === path) {
const editorContent = this.editor.currentContent()
if(editorContent) resolve(editorContent)
if (editorContent) resolve(editorContent)
}
provider.get(path, (err, content) => {
if (err) reject(err)
@ -694,7 +690,6 @@ class FileManager extends Plugin {
}
this.editor.discard(path)
delete this.openedFiles[path]
// TODO: Only keep `this.emit` (issue#2210)
this.emit('fileRemoved', path)
if (path === this._deps.config.get('currentFile')) {
this.openFile(this._deps.config.get('currentFile'))
@ -704,7 +699,6 @@ class FileManager extends Plugin {
async unselectCurrentFile() {
await this.saveCurrentFile()
this._deps.config.set('currentFile', '')
// TODO: Only keep `this.emit` (issue#2210)
this.emit('noFileSelected')
}
@ -742,7 +736,6 @@ class FileManager extends Plugin {
} else {
await this.editor.open(file, content)
}
// TODO: Only keep `this.emit` (issue#2210)
this.emit('currentFileChanged', file)
return true
}
@ -783,7 +776,7 @@ class FileManager extends Plugin {
return this._deps.filesProviders.localhost
}
if(Registry.getInstance().get('platform').api.isDesktop()){
if (Registry.getInstance().get('platform').api.isDesktop()) {
return this._deps.filesProviders.electron
}
return this._deps.filesProviders.workspace
@ -908,7 +901,7 @@ class FileManager extends Plugin {
}
currentWorkspace() {
if(Registry.getInstance().get('platform').api.isDesktop()){
if (Registry.getInstance().get('platform').api.isDesktop()) {
return ''
}
@ -988,7 +981,7 @@ class FileManager extends Plugin {
/**
* Moves a file to a new folder
* @param {string} src path of the source file
* @param {string} dest path of the destrination file
* @param {string} dest path of the destination file
* @returns {void}
*/
@ -1048,6 +1041,14 @@ class FileManager extends Plugin {
throw new Error(e)
}
}
async copyFolderToJson(folder: string) {
const provider = this.currentFileProvider()
if (provider && provider.copyFolderToJson) {
return await provider.copyFolderToJson(folder)
}
throw new Error('copyFolderToJson not available')
}
}
module.exports = FileManager

@ -287,7 +287,6 @@ export default class FileProvider {
// ^ ret does not accept path starting with '/'
}
}
//console.log(`resolveDirectory ${path} took ${Date.now() - startTime} ms`)
if (cb) cb(null, ret)
return ret
} catch (error) {

@ -9,6 +9,19 @@ class WorkspaceFileProvider extends FileProvider {
this.workspacesPath = '.workspaces'
this.workspace = null
this.event = new EventManager()
try {
// make sure "code-sample" has been removed
window.remixFileSystem.exists(this.workspacesPath + '/code-sample').then((exist) => {
if (exist) window.remixFileSystem.unlink(this.workspacesPath + '/code-sample').catch((e) => {
console.log(e)
})
}).catch((e) => {
console.log(e)
})
} catch (e) {
// we don't need to log error if this throws an error
}
}
setWorkspace (workspace) {

@ -46,6 +46,7 @@ const profile = {
'loadTemplate',
'clone',
'isExpanded',
'isGist'
],
events: ['setWorkspace', 'workspaceRenamed', 'workspaceDeleted', 'workspaceCreated'],
icon: 'assets/img/fileManager.webp',
@ -131,6 +132,20 @@ module.exports = class Filepanel extends ViewPlugin {
})
}
/**
* return the gist id if the current workspace is a gist workspace, otherwise returns null
* @argument {String} workspaceName - the name of the workspace to check against. default to the current workspace.
* @returns {string} gist id or null
*/
isGist (workspaceName) {
workspaceName = workspaceName || this.currentWorkspaceMetadata && this.currentWorkspaceMetadata.name
const isGist = workspaceName.startsWith('gist')
if (isGist) {
return workspaceName.split(' ')[1]
}
return null
}
getCurrentWorkspace() {
return this.currentWorkspaceMetadata
}
@ -139,10 +154,10 @@ module.exports = class Filepanel extends ViewPlugin {
return this.workspaces
}
getAvailableWorkspaceName(name) {
getAvailableWorkspaceName(name) {
if (!this.workspaces) return name
let index = 1
let workspace = this.workspaces.find((workspace) => workspace.name === name + ' - ' + index)
let workspace = this.workspaces.find((workspace) => workspace.name === name + ' - ' + index)
while (workspace) {
index++
workspace = this.workspaces.find((workspace) => workspace.name === name + ' - ' + index)
@ -200,6 +215,7 @@ module.exports = class Filepanel extends ViewPlugin {
}
saveRecent(workspaceName) {
if (workspaceName === 'code-sample') return
if (!localStorage.getItem('recentWorkspaces')) {
localStorage.setItem('recentWorkspaces', JSON.stringify([ workspaceName ]))
} else {
@ -250,6 +266,8 @@ module.exports = class Filepanel extends ViewPlugin {
isExpanded(path) {
if(path === '/') return true
// remove leading slash
path = path.replace(/^\/+/, '')
return this.expandPath.includes(path)
}

@ -224,9 +224,23 @@ export class TabProxy extends Plugin {
this.removeTab(oldName)
}
/**
*
* @param {string} name
* @param {string} title
* @param {Function} switchTo
* @param {Function} close
* @param {string} icon
* @param {string} description
* @returns
*/
addTab (name, title, switchTo, close, icon, description = '') {
if (this._handlers[name]) return this.renderComponent()
if ((name.endsWith('.vy') && icon === undefined) || title.includes('Vyper')) {
icon = 'assets/img/vyperLogo2.webp'
}
var slash = name.split('/')
const tabPath = slash.reverse()
const tempTitle = []
@ -292,7 +306,7 @@ export class TabProxy extends Plugin {
if (!previous && tab.name === name) {
if(index - 1 >= 0 && this.loadedTabs[index - 1])
previous = this.loadedTabs[index - 1]
else if (index + 1 && this.loadedTabs[index + 1])
else if (index + 1 && this.loadedTabs[index + 1])
previous = this.loadedTabs[index + 1]
}
return tab.name !== name

@ -1,12 +1,12 @@
/* global Node, requestAnimationFrame */ // eslint-disable-line
import React from 'react' // eslint-disable-line
import { RemixUiTerminal } from '@remix-ui/terminal' // eslint-disable-line
import { RemixUiTerminal, RemixUITerminalWrapper } from '@remix-ui/terminal' // eslint-disable-line
import { Plugin } from '@remixproject/engine'
import * as packageJson from '../../../../../package.json'
import {Registry} from '@remix-project/remix-lib'
import { PluginViewWrapper } from '@remix-ui/helper'
import vm from 'vm'
const EventManager = require('../../lib/events')
import EventManager from '../../lib/events'
import { CompilerImports } from '@remix-project/core-plugin' // eslint-disable-line
import { RemixUiXterminals } from '@remix-ui/xterm'
@ -26,6 +26,34 @@ const profile = {
}
class Terminal extends Plugin {
fileImport: CompilerImports
event: any
globalRegistry: Registry
element: HTMLDivElement
eventsDecoder: any
txListener: any
_deps: { fileManager: any; editor: any; compilersArtefacts: any; offsetToLineColumnConverter: any }
commandHelp: { 'remix.loadgist(id)': string; 'remix.loadurl(url)': string; 'remix.execute(filepath)': string; 'remix.exeCurrent()': string; 'remix.help()': string }
blockchain: any
vm: typeof vm
_api: any
_opts: any
config: any
version: string
data: {
lineLength: any // ????
session: any[]; activeFilters: { commands: any; input: string }; filterFns: any
}
_view: { el: any; bar: any; input: any; term: any; journal: any; cli: any }
_components: any
_commands: any
commands: any
_JOURNAL: any[]
_jobs: any[]
_INDEX: any
_shell: any
dispatch: any
terminalApi: any
constructor(opts, api) {
super(profile)
this.fileImport = new CompilerImports()
@ -75,7 +103,7 @@ class Terminal extends Plugin {
this._INDEX.commandsMain = {}
if (opts.shell) this._shell = opts.shell // ???
register(this)
this.event.register('debuggingRequested', async (hash) => {
this.event.register('debuggingRequested', async (hash: any) => {
// TODO should probably be in the run module
if (!await this._opts.appManager.isActive('debugger')) await this._opts.appManager.activatePlugin('debugger')
this.call('menuicons', 'select', 'debugger')
@ -114,13 +142,12 @@ class Terminal extends Plugin {
}
updateComponent(state) {
return (Registry.getInstance().get('platform').api.isDesktop()) ? <RemixUiXterminals onReady={state.onReady} plugin={state.plugin}>
</RemixUiXterminals>
: <RemixUiTerminal
return(
<RemixUITerminalWrapper
plugin={state.plugin}
onReady={state.onReady}
visible={true}
/>
/>)
}
renderComponent() {

@ -9,7 +9,7 @@ import { filePathFilter, AnyFilter } from '@jsdevtools/file-path-filter'
const profile = {
name: 'codeFormatter',
desciption: 'prettier plugin for Remix',
description: 'prettier plugin for Remix',
methods: ['format'],
events: [''],
version: '0.0.1'

@ -45,7 +45,7 @@ export class CompilationDetailsPlugin extends ViewPlugin {
async showDetails(sentPayload: any) {
await this.call('tabs', 'focus', 'compilationDetails')
setTimeout(() => {
// TODO: use the react API to render when the tab is focused and tbe plugin in the view.
// TODO: use the react API to render when the tab is focused and the plugin in the view.
this.payload = sentPayload
this.renderComponent()
}, 2000)

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save