From fb8c18798c8acf48de7f27d87ec7eadc7ae1178b Mon Sep 17 00:00:00 2001 From: Joseph Izang Date: Tue, 2 May 2023 18:29:59 +0100 Subject: [PATCH 1/3] add sol2uml test skeleton --- apps/remix-ide-e2e/nightwatch.ts | 2 +- apps/remix-ide-e2e/src/tests/sol2uml.test.ts | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 apps/remix-ide-e2e/src/tests/sol2uml.test.ts diff --git a/apps/remix-ide-e2e/nightwatch.ts b/apps/remix-ide-e2e/nightwatch.ts index 3f59c1549e..25fdaaaa5b 100644 --- a/apps/remix-ide-e2e/nightwatch.ts +++ b/apps/remix-ide-e2e/nightwatch.ts @@ -1,7 +1,7 @@ module.exports = { src_folders: ['dist/apps/remix-ide-e2e/src/tests'], output_folder: './reports/tests', - custom_commands_path: ['dist/apps/remix-ide-e2e/src/commands'], + custom_commands_path: ['dist/apps/remix-ide-e 2e/src/commands'], custom_assertions_path: '', page_objects_path: '', globals_path: '', diff --git a/apps/remix-ide-e2e/src/tests/sol2uml.test.ts b/apps/remix-ide-e2e/src/tests/sol2uml.test.ts new file mode 100644 index 0000000000..4706a1f68b --- /dev/null +++ b/apps/remix-ide-e2e/src/tests/sol2uml.test.ts @@ -0,0 +1,9 @@ +'use strict' +import { NightwatchBrowser } from 'nightwatch' +import init from '../helpers/init' + +module.exports = { + before: function (browser: NightwatchBrowser, done: VoidFunction) { + init(browser, done) + } +} \ No newline at end of file From 1384042b409cb8fbc4401df49d12a181795982aa Mon Sep 17 00:00:00 2001 From: Joseph Izang Date: Mon, 22 May 2023 10:23:01 +0100 Subject: [PATCH 2/3] add data-ids for e2e --- apps/remix-ide-e2e/nightwatch.ts | 2 +- apps/remix-ide-e2e/src/tests/sol2uml.test.ts | 213 +++++++++++++++++- .../src/lib/components/UmlDownload.tsx | 2 + .../src/lib/solidity-uml-gen.tsx | 3 + 4 files changed, 217 insertions(+), 3 deletions(-) diff --git a/apps/remix-ide-e2e/nightwatch.ts b/apps/remix-ide-e2e/nightwatch.ts index 25fdaaaa5b..3f59c1549e 100644 --- a/apps/remix-ide-e2e/nightwatch.ts +++ b/apps/remix-ide-e2e/nightwatch.ts @@ -1,7 +1,7 @@ module.exports = { src_folders: ['dist/apps/remix-ide-e2e/src/tests'], output_folder: './reports/tests', - custom_commands_path: ['dist/apps/remix-ide-e 2e/src/commands'], + custom_commands_path: ['dist/apps/remix-ide-e2e/src/commands'], custom_assertions_path: '', page_objects_path: '', globals_path: '', diff --git a/apps/remix-ide-e2e/src/tests/sol2uml.test.ts b/apps/remix-ide-e2e/src/tests/sol2uml.test.ts index 4706a1f68b..4f29da084d 100644 --- a/apps/remix-ide-e2e/src/tests/sol2uml.test.ts +++ b/apps/remix-ide-e2e/src/tests/sol2uml.test.ts @@ -3,7 +3,216 @@ import { NightwatchBrowser } from 'nightwatch' import init from '../helpers/init' module.exports = { + '@disabled': true, before: function (browser: NightwatchBrowser, done: VoidFunction) { init(browser, done) - } -} \ No newline at end of file + }, + '@sources': () => sources, + 'Generate uml diagram from contract #group1': function (browser: NightwatchBrowser) { + browser.addFile('TestBallot.sol', sources[0]['TestBallot.sol']) + .waitForElementVisible('*[data-id="treeViewLitreeViewItemTestBallot.sol"') + .rightClick('*[data-id="treeViewLitreeViewItemTestBallot.sol"]') + .click('*[id="menuitemgeneratecustomaction"') + .waitForElementVisible('*[id="sol-uml-gen"]') + }, + 'Generate uml for contracts with imports #group1': function (browser: NightwatchBrowser) { + browser.addFile('secondContract.sol', sources[1]['secondContract.sol']) + .waitForElementVisible('*[data-id="treeViewLitreeViewItemsecondContract.sol"') + .pause(3000) + .rightClick('*[data-id="treeViewLitreeViewItemsecondContract.sol"]') + .click('*[id="menuitemgeneratecustomaction"') + .waitForElementVisible('*[id="sol-uml-gen"]') + .isVisible('*[data-id="treeViewLitreeViewItemsecondContract_flattened.sol"]') + }, + 'Zoom into uml diagram #group1': function (browser: NightwatchBrowser) { + browser.addFile('secondContract.sol', sources[1]['secondContract.sol']) + .waitForElementVisible('*[data-id="treeViewLitreeViewItemsecondContract.sol"') + .pause(3000) + .rightClick('*[data-id="treeViewLitreeViewItemsecondContract.sol"]') + .click('*[id="menuitemgeneratecustomaction"') + .waitForElementVisible('*[id="sol-uml-gen"]') + .click('*[id="umlZoominbtn"]') + } +} + +const sources = [ + { + 'TestBallot.sol': { + content: ` + // SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.7.0 <0.9.0; + +/** + * @title Ballot + * @dev Implements voting process along with vote delegation + */ +contract Ballot { + + struct Voter { + uint weight; // weight is accumulated by delegation + bool voted; // if true, that person already voted + address delegate; // person delegated to + uint vote; // index of the voted proposal + } + + struct Proposal { + // If you can limit the length to a certain number of bytes, + // always use one of bytes1 to bytes32 because they are much cheaper + bytes32 name; // short name (up to 32 bytes) + uint voteCount; // number of accumulated votes + } + + address public chairperson; + + mapping(address => Voter) public voters; + + Proposal[] public proposals; + + /** + * @dev Create a new ballot to choose one of 'proposalNames'. + * @param proposalNames names of proposals + */ + constructor(bytes32[] memory proposalNames) { + chairperson = msg.sender; + voters[chairperson].weight = 1; + + for (uint i = 0; i < proposalNames.length; i++) { + // 'Proposal({...})' creates a temporary + // Proposal object and 'proposals.push(...)' + // appends it to the end of 'proposals'. + proposals.push(Proposal({ + name: proposalNames[i], + voteCount: 0 + })); + } + } + + /** + * @dev Give 'voter' the right to vote on this ballot. May only be called by 'chairperson'. + * @param voter address of voter + */ + function giveRightToVote(address voter) public { + require( + msg.sender == chairperson, + "Only chairperson can give right to vote." + ); + require( + !voters[voter].voted, + "The voter already voted." + ); + require(voters[voter].weight == 0); + voters[voter].weight = 1; + } + + /** + * @dev Delegate your vote to the voter 'to'. + * @param to address to which vote is delegated + */ + function delegate(address to) public { + Voter storage sender = voters[msg.sender]; + require(!sender.voted, "You already voted."); + require(to != msg.sender, "Self-delegation is disallowed."); + + while (voters[to].delegate != address(0)) { + to = voters[to].delegate; + + // We found a loop in the delegation, not allowed. + require(to != msg.sender, "Found loop in delegation."); + } + sender.voted = true; + sender.delegate = to; + Voter storage delegate_ = voters[to]; + if (delegate_.voted) { + // If the delegate already voted, + // directly add to the number of votes + proposals[delegate_.vote].voteCount += sender.weight; + } else { + // If the delegate did not vote yet, + // add to her weight. + delegate_.weight += sender.weight; + } + } + + /** + * @dev Give your vote (including votes delegated to you) to proposal 'proposals[proposal].name'. + * @param proposal index of proposal in the proposals array + */ + function vote(uint proposal) public { + Voter storage sender = voters[msg.sender]; + require(sender.weight != 0, "Has no right to vote"); + require(!sender.voted, "Already voted."); + sender.voted = true; + sender.vote = proposal; + + // If 'proposal' is out of the range of the array, + // this will throw automatically and revert all + // changes. + proposals[proposal].voteCount += sender.weight; + } + + /** + * @dev Computes the winning proposal taking all previous votes into account. + * @return winningProposal_ index of winning proposal in the proposals array + */ + function winningProposal() public view + returns (uint winningProposal_) + { + uint winningVoteCount = 0; + for (uint p = 0; p < proposals.length; p++) { + if (proposals[p].voteCount > winningVoteCount) { + winningVoteCount = proposals[p].voteCount; + winningProposal_ = p; + } + } + } + + /** + * @dev Calls winningProposal() function to get the index of the winner contained in the proposals array and then + * @return winnerName_ the name of the winner + */ + function winnerName() public view + returns (bytes32 winnerName_) + { + winnerName_ = proposals[winningProposal()].name; + } +} +`} + }, + { + 'secondContract.sol': { + content: ` + // SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.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() initializer public { + __ERC721_init("MyToken", "MTK"); + __Ownable_init(); + __UUPSUpgradeable_init(); + } + + function safeMint(address to, uint256 tokenId) public onlyOwner { + _safeMint(to, tokenId); + 1 + 1; + } + + function _authorizeUpgrade(address newImplementation) + internal + onlyOwner + override + {} +} +`} +} +] \ No newline at end of file diff --git a/libs/remix-ui/solidity-uml-gen/src/lib/components/UmlDownload.tsx b/libs/remix-ui/solidity-uml-gen/src/lib/components/UmlDownload.tsx index 6872e1a08e..9e57337d22 100644 --- a/libs/remix-ui/solidity-uml-gen/src/lib/components/UmlDownload.tsx +++ b/libs/remix-ui/solidity-uml-gen/src/lib/components/UmlDownload.tsx @@ -91,6 +91,7 @@ export default function UmlDownload(props: UmlDownloadProps) { ]); props.download("png") }} + data-id="umlPngDownload" >