diff --git a/apps/remix-ide-e2e/src/local-plugin/src/app/app.tsx b/apps/remix-ide-e2e/src/local-plugin/src/app/app.tsx index d1a559764e..298316e6cd 100644 --- a/apps/remix-ide-e2e/src/local-plugin/src/app/app.tsx +++ b/apps/remix-ide-e2e/src/local-plugin/src/app/app.tsx @@ -33,7 +33,7 @@ function App () { useEffect(() => { client.onload(async () => { - const customProfiles = ['menuicons', 'tabs', 'solidityUnitTesting', 'hardhat-provider'] + const customProfiles = ['menuicons', 'tabs', 'solidityUnitTesting', 'hardhat-provider', 'notification'] client.testCommand = async (data: any) => { console.log(data) diff --git a/apps/remix-ide-e2e/src/tests/ballot.test.ts b/apps/remix-ide-e2e/src/tests/ballot.test.ts index cc2a32cb2f..794dee9775 100644 --- a/apps/remix-ide-e2e/src/tests/ballot.test.ts +++ b/apps/remix-ide-e2e/src/tests/ballot.test.ts @@ -84,9 +84,9 @@ module.exports = { .openFile('Untitled.sol') .clickLaunchIcon('udapp') .click('*[data-id="settingsWeb3Mode"]') - .waitForElementPresent('[data-id="udappNotify-modal-footer-ok-react"]') + .waitForElementPresent('[data-id="envNotification-modal-footer-ok-react"]') .execute(function () { - const modal = document.querySelector('[data-id="udappNotify-modal-footer-ok-react"]') as any + const modal = document.querySelector('[data-id="envNotification-modal-footer-ok-react"]') as any modal.click() }) diff --git a/apps/remix-ide-e2e/src/tests/ballot_0_4_11.test.ts b/apps/remix-ide-e2e/src/tests/ballot_0_4_11.test.ts index a8bfa626e4..28b03219fc 100644 --- a/apps/remix-ide-e2e/src/tests/ballot_0_4_11.test.ts +++ b/apps/remix-ide-e2e/src/tests/ballot_0_4_11.test.ts @@ -79,9 +79,9 @@ module.exports = { .openFile('Untitled.sol') .clickLaunchIcon('udapp') .click('*[data-id="settingsWeb3Mode"]') - .waitForElementPresent('[data-id="udappNotify-modal-footer-ok-react"]') + .waitForElementPresent('[data-id="envNotification-modal-footer-ok-react"]') .execute(function () { - const modal = document.querySelector('[data-id="udappNotify-modal-footer-ok-react"]') as any + const modal = document.querySelector('[data-id="envNotification-modal-footer-ok-react"]') as any modal.click() }) diff --git a/apps/remix-ide-e2e/src/tests/plugin_api.ts b/apps/remix-ide-e2e/src/tests/plugin_api.ts index fc09a9c8f1..c450228af4 100644 --- a/apps/remix-ide-e2e/src/tests/plugin_api.ts +++ b/apps/remix-ide-e2e/src/tests/plugin_api.ts @@ -64,12 +64,16 @@ const clearPayLoad = async (browser: NightwatchBrowser) => { }) } -const clickButton = async (browser: NightwatchBrowser, buttonText: string) => { +const clickButton = async (browser: NightwatchBrowser, buttonText: string, waitResult: boolean = true) => { return new Promise((resolve) => { browser.useXpath().waitForElementVisible(`//*[@data-id='${buttonText}']`).pause(100) .click(`//*[@data-id='${buttonText}']`, async () => { await checkForAcceptAndRemember(browser) - browser.waitForElementContainsText('//*[@id="callStatus"]', 'stop').perform(() => resolve(true)) + if (waitResult) { + browser.waitForElementContainsText('//*[@id="callStatus"]', 'stop').perform(() => resolve(true)) + } else { + resolve(true) + } }) }) } @@ -103,7 +107,7 @@ const checkForAcceptAndRemember = async function (browser: NightwatchBrowser) { * @return {Promise} */ -const clickAndCheckLog = async (browser: NightwatchBrowser, buttonText: string, methodResult: any, eventResult: any, payload: any) => { +const clickAndCheckLog = async (browser: NightwatchBrowser, buttonText: string, methodResult: any, eventResult: any, payload: any, waitResult: boolean = true) => { if (payload) { await setPayload(browser, payload) } else { @@ -112,10 +116,14 @@ const clickAndCheckLog = async (browser: NightwatchBrowser, buttonText: string, if (methodResult && typeof methodResult !== 'string') { methodResult = JSON.stringify(methodResult) } if (eventResult && typeof eventResult !== 'string') { eventResult = JSON.stringify(eventResult) } if (buttonText) { - await clickButton(browser, buttonText) + await clickButton(browser, buttonText, waitResult) + } + if (methodResult) { + await debugValues(browser, 'methods', methodResult) + } + if (eventResult) { + await debugValues(browser, 'events', eventResult) } - await debugValues(browser, 'methods', methodResult) - await debugValues(browser, 'events', eventResult) } const assertPluginIsActive = function (browser: NightwatchBrowser, id: string, shouldBeVisible: boolean) { @@ -364,5 +372,57 @@ module.exports = { const result = '{"jsonrpc":"2.0","result":true,"id":9999}' await clickAndCheckLog(browser, 'hardhat-provider:sendAsync', result, null, request) }) + }, + + // MODAL + + 'Should open 2 alert in a row and trigger 2 toaster in between #group9': function (browser: NightwatchBrowser) { + browser + .frameParent() + .useCss() + .addFile('test_modal.js', { content: testModalToasterApi }) + .executeScript('remix.execute(\'test_modal.js\')') + .clickLaunchIcon('localPlugin') + .useXpath() + // @ts-ignore + .frame(0) + .perform(async () => { + await clickAndCheckLog(browser, 'notification:toast', null, null, 'message toast from local plugin', false) // create a toast on behalf of the localplugin + await clickAndCheckLog(browser, 'notification:alert', null, null, { message: 'message from local plugin', id: 'test_id_1_local_plugin' }, false) // create an alert on behalf of the localplugin + }) + .frameParent() + .useCss() + // check the local plugin notifications + .waitForElementVisible('*[data-id="test_id_1_local_pluginModalDialogModalBody-react"]') + .assert.containsText('*[data-id="test_id_1_local_pluginModalDialogModalBody-react"]', 'message from local plugin') + .modalFooterOKClick('test_id_1_local_plugin') + // check the script runner notifications + .waitForElementVisible('*[data-id="test_id_1_ModalDialogModalBody-react"]') + .assert.containsText('*[data-id="test_id_1_ModalDialogModalBody-react"]', 'message 1') + .modalFooterOKClick('test_id_1_') + .waitForElementVisible('*[data-id="test_id_2_ModalDialogModalBody-react"]') + .assert.containsText('*[data-id="test_id_2_ModalDialogModalBody-react"]', 'message 2') + .modalFooterOKClick('test_id_2_') + // check the toasters + .waitForElementVisible('*[data-shared="tooltipPopup"]') + .waitForElementContainsText('*[data-shared="tooltipPopup"]', 'message toast from local plugin') + .waitForElementContainsText('*[data-shared="tooltipPopup"]', 'I am a toast') + .waitForElementContainsText('*[data-shared="tooltipPopup"]', 'I am a re-toast') } } + +const testModalToasterApi = ` +// Right click on the script name and hit "Run" to execute +(async () => { + try { + setTimeout(() => { + console.log('test .. ') + remix.call('notification', 'alert', { message: 'message 1', id: 'test_id_1_' }) + remix.call('notification', 'toast', 'I am a toast') + remix.call('notification', 'toast', 'I am a re-toast') + remix.call('notification', 'alert', { message: 'message 2', id: 'test_id_2_' }) + }, 500) + } catch (e) { + console.log(e.message) + } +})() ` diff --git a/apps/remix-ide-e2e/src/tests/terminal.test.ts b/apps/remix-ide-e2e/src/tests/terminal.test.ts index 69c3acc765..f56dccf174 100644 --- a/apps/remix-ide-e2e/src/tests/terminal.test.ts +++ b/apps/remix-ide-e2e/src/tests/terminal.test.ts @@ -51,7 +51,7 @@ module.exports = { .click('*[data-id="terminalClearConsole"]') // clear the terminal .clickLaunchIcon('udapp') .click('*[data-id="settingsWeb3Mode"]') - .modalFooterOKClick('udappNotify') + .modalFooterOKClick('envNotification') .executeScript('web3.eth.getAccounts()') .waitForElementContainsText('*[data-id="terminalJournal"]', '["', 60000) // we check if an array is present, don't need to check for the content .waitForElementContainsText('*[data-id="terminalJournal"]', '"]', 60000) diff --git a/apps/remix-ide/src/remixEngine.js b/apps/remix-ide/src/remixEngine.js index 1ad385146b..cbdf6c86c6 100644 --- a/apps/remix-ide/src/remixEngine.js +++ b/apps/remix-ide/src/remixEngine.js @@ -14,6 +14,7 @@ export class RemixEngine extends Engine { if (name === 'slither') return { queueTimeout: 60000 * 4 } // Requires when a solc version is installed if (name === 'hardhat') return { queueTimeout: 60000 * 4 } if (name === 'localPlugin') return { queueTimeout: 60000 * 4 } + if (name === 'notification') return { queueTimeout: 60000 * 4 } return { queueTimeout: 10000 } } diff --git a/libs/remix-ui/app/src/lib/remix-app/reducer/modals.ts b/libs/remix-ui/app/src/lib/remix-app/reducer/modals.ts index d83b9792c7..1572343a42 100644 --- a/libs/remix-ui/app/src/lib/remix-app/reducer/modals.ts +++ b/libs/remix-ui/app/src/lib/remix-app/reducer/modals.ts @@ -5,49 +5,68 @@ import { AppModal, ModalState } from '../interface' export const modalReducer = (state: ModalState = ModalInitialState, action: ModalAction) => { switch (action.type) { case modalActionTypes.setModal: { - let modalList:AppModal[] = state.modals - modalList.push(action.payload) - if (state.modals.length === 1 && state.focusModal.hide === true) { // if it's the first one show it - const focusModal: AppModal = { - id: modalList[0].id, - hide: false, - title: modalList[0].title, - message: modalList[0].message, - okLabel: modalList[0].okLabel, - okFn: modalList[0].okFn, - cancelLabel: modalList[0].cancelLabel, - cancelFn: modalList[0].cancelFn, - modalType: modalList[0].modalType, - defaultValue: modalList[0].defaultValue, - hideFn: modalList[0].hideFn, - resolve: modalList[0].resolve - } + const focusModal: AppModal = { + id: action.payload.id || Date.now().toString(), + hide: false, + title: action.payload.title, + message: action.payload.message, + okLabel: action.payload.okLabel, + okFn: action.payload.okFn, + cancelLabel: action.payload.cancelLabel, + cancelFn: action.payload.cancelFn, + modalType: action.payload.modalType, + defaultValue: action.payload.defaultValue, + hideFn: action.payload.hideFn, + resolve: action.payload.resolve + } + + const modalList: AppModal[] = state.modals.slice() + modalList.push(focusModal) - modalList = modalList.slice() - modalList.shift() - return { ...state, modals: modalList, focusModal: focusModal } + if (modalList.length === 1) { + return { ...state, modals: modalList, focusModal } + } else { + return { ...state, modals: modalList } } - return { ...state, modals: modalList } } - case modalActionTypes.handleHideModal: - if (state.focusModal.hideFn) { - state.focusModal.hideFn() - } else if (state.focusModal.resolve) { - state.focusModal.resolve(undefined) + case modalActionTypes.handleHideModal: { + setTimeout(() => { + if (state.focusModal.hideFn) { + state.focusModal.hideFn() + } + if (state.focusModal.resolve) { + state.focusModal.resolve(undefined) + } + }, 250) + const modalList: AppModal[] = state.modals.slice() + modalList.shift() // remove the current modal from the list + if (modalList.length) { + const focusModal = modalList[0] // extract the next modal from the list + return { ...state, modals: modalList, focusModal } + } else { + state.focusModal = { ...state.focusModal, hide: true, message: null } + return { ...state, modals: [] } } - state.focusModal = { ...state.focusModal, hide: true, message: null } - return { ...state } - - case modalActionTypes.setToast: - state.toasters.push(action.payload) - if (state.toasters.length > 0) { - const focus = state.toasters[0] - state.toasters.shift() - return { ...state, focusToaster: focus } + } + case modalActionTypes.setToast: { + const toasterList = state.toasters.slice() + const message = action.payload + toasterList.push(message) + if (toasterList.length === 1) { + return { ...state, toasters: toasterList, focusToaster: action.payload } + } else { + return { ...state, toasters: toasterList } } - return { ...state } - - case modalActionTypes.handleToaster: - return { ...state, focusToaster: '' } + } + case modalActionTypes.handleToaster: { + const toasterList = state.toasters.slice() + toasterList.shift() + if (toasterList.length) { + const toaster = toasterList[0] + return { ...state, toasters: toasterList, focusToaster: toaster } + } else { + return { ...state, toasters: [] } + } + } } }