Test context menu appears after firing right click event in React Testing Library - contextmenu

I would like to test that a context menu has appeared after firing off a right click event with React Testing Library.
I don't know if there are timing difficulties or not, but the following test fails because it can't find the autoscale button (in the context menu) that should exist after a right click:
it('a context menu appears after a right click', async () => {
model = await makeModel(
['Channel 1'], {
laneHeaders: 'none'
}
);
const { getByTestId, getByText } = render(
<DataPreview
model={model}
/>
);
// node dealing with the right click event
const eventNode = getByTestId('data-preview-container');
// see https://testing-library.com/docs/dom-testing-library/api-events
const rightClickEvent = createEvent.click(eventNode, { button: 2 });
fireEvent.click(eventNode, rightClickEvent);
//Error: Unable to find an element with the text: Autoscale.
const autoScaleContextMenuButton = getByText('Autoscale');
expect(
autoScaleContextMenuButton
).not.toBeNull();
});

fireEvent.contextMenu(eventNode)
works for me

What you can do is:
// get the test component
const testComp = screen.getByTestId(some_test_id_here);
// create a contextMenu event
const contextMenuEvent = createEvent.contextMenu(testComp);
// fire the event with the contextMenu event
fireEvent(testComp, contextMenuEvent);
// do more checking
If you don't need to check the context menu event, i.e., propagation, prevent default, bubble, etc, you can omit the event creation and call fireEvent directly.
Reference: React - Jest - Test preventDefault Action

Related

invokeAction is not firing Twilio.FlexWebChat.Actions.on action

I'm opening my chat window programtically using ToggleChatVisibility which works great, but it does not fire the relevent FlexWebChat.Action
<script>
const operatingHoursCheckMsg = async function () {
Twilio.FlexWebChat.Actions.on("afterToggleChatVisibility", (payload) => {
console.log('Not Working');
});
}
await initateWebChat.init();
}
function Test() {
operatingHoursCheckMsg();
Twilio.FlexWebChat.Actions.invokeAction("ToggleChatVisibility");
}
</script>
<button type="button" onclick="Test()">Click to open and close chat window</button>
the afterToggleChatVisibility event fires if I close and reopen the chat using the chat box ui, but not if I click my button.
How can I trigger this event properly?
I think you have a race condition causing this issue. You defined the operatingHoursCheckMsg function as async even though there isn't an asynchronous call involved (though maybe there is in your full script) but in your Test function you do not wait for the promise to resolve before invoking the action. I think this means that JavaScript placed the promise on a queue to be handled asynchronously by the event loop, and then ran the next synchronous line of code. So you invoke the action before the event listener is registered.
It also looks as though you want to use the button to continue toggling the chat open and closed, so you should probably not be attaching a new listener every time the button is clicked.
I'd recommend you set up the one listener after you have initiated the webchat, like this:
<script>
const operatingHoursCheckMsg = async function () {
// Do operating hours check
}
await initateWebChat.init();
Twilio.FlexWebChat.Actions.on("afterToggleChatVisibility", (payload) => {
console.log('Chat toggled!');
});
}
async function Test() {
await operatingHoursCheckMsg();
Twilio.FlexWebChat.Actions.invokeAction("ToggleChatVisibility");
}
</script>
<button type="button" onclick="Test()">Click to open and close chat window</button>

How can I access multiple instances of mainWindow in an Electron App?

How do I create a functioning electron app with multiple instances of the mainWindow? Here's a very simple app with a mainWindow that just has two buttons. One to create a new mainWindow instance, and one to close the current window.
// main.js
const { app, ipcMain, BrowserWindow } = require('electron')
let mainWindow;
function main () {
mainWindow = new BrowserWindow({
width: 500,
height: 400,
tabbingIdentifier: 'todoTab',
show: false,
webPreferences: {
nodeIntegration: true
}
})
mainWindow.loadFile('renderer/index.html');
mainWindow.once('ready-to-show', () => {
mainWindow.setTitle("Todo-" + mainWindow.id)
mainWindow.show();
})
mainWindow.mergeAllWindows();
}
app.on('ready', main);
ipcMain.on('newListWindow', main);
ipcMain.on('closeWindow', function(event){
mainWindow.close();
});
In the above file I set mainWindow as a global variable.
Adding a tabbingIdentifier property and chaining the mergeAllWindows() method will automatically create multiple tabs in the display if more than one window is opened.
Each mainWindow instance is assigned an id by Electron. If only one instance is open the id is 1. For simplicity I set the mainWindow title to be "Todo-" + the mainWindow.id (so Todo-1 for the first window, Todo-2 if I open a second).
When the newListWindow button is clicked the "main" function gets called creating a new instances of mainWindow.
When the closeWindow button is pressed the mainWindow instance is closed.
The HTML file with the two buttons (abbreviated to just the body element)
// renderer/index.html
<body>
<h1 class="text-center">Todo List</h1>
<button id="new-list-btn">New Todo List</button>
<button id="close-btn">Close List</button>
<script src="./index.js"></script>
</body>
The ipcRenderer. Listens for the button clicks and sends a message to main.js.
// renderer/index.js
const { ipcRenderer } = require('electron')
document.getElementById('new-list-btn').addEventListener('click', () => {
ipcRenderer.send('newListWindow');
});
document.getElementById('close-btn').addEventListener('click', () => {
ipcRenderer.send('closeWindow');
});
The above code will create multiple Todo windows and display them on different tabs. Each list title (Todo-1, Todo-2, etc) is displayed. The problem is, the last one opened is the only active one. So if I open three todos, then go to any one of them and click the close button, only the third window will close, regardless of which one I was in. Then the other two will throw an error if I try to close them saying the object was destroyed. Which makes sense. So how do I code this so if that whichever instance tab I am in is the one that I close. And when I close it the next tab I am in becomes the valid mainWindow object?
You may want to deal only with the focused BrowserWindow in 'closeWindow' callback
Use BrowserWindow.getFocusedWindow static method
ipcMain.on('closeWindow', function(event) {
const current = BrowserWindow.getFocusedWindow()
if (current) current.close()
})
Pergy's answer works as requested but I'll post this answer as well since it's what I'm actually using, and there's no other documentation on how to do this that I could find. The difference is small but this seems more direct:
ipcMain.on('closeWindow', function(event) {
mainWindow = BrowserWindow.getFocusedWindow();
mainWindow.close();
})

How to share data between Main/Renderer Processes

I'm a complete Electron beginner. Let's say you are creating a simple memopad-like app and want to save something you type into a textarea in the browser window by clicking the app's [File > Save] menu, which should be a very common feature.
Menu handler should be implemented in the Main process, and the textarea is clearly in the Renderer process. I can't figure out how to access what's in the textarea from the Main process.
In electron applications, communications between Main and Renderer processes is performed via ipc. Electron has ipcMain and ipcRenderer modules used in Main and Renderer processes respectively.
For the task you have, you can send a message to the renderer process whenever the user clicked on File > Save, which will trigger saving the textarea to a file. One implementation might be like this:
// main process
const { app } = require('electron')
// reference to the browser window
let mainWindow
app.on('ready', () => {
// here create your browser window and assign it to mainWindow
mainWindow = createMainWindow()
})
// clicking File > Save menu triggers following function
const saveClicked = () => {
// Check mainWindow exists
if (mainWindow != null) {
mainWindow.webContents.send('clicked::file:save')
}
}
// renderer process (preload.js)
const { ipcRenderer } = require('electron')
// Now you need to listen for the event you send from the main process
ipcRenderer.on('clicked::file:save', () => {
// IMPLEMENT YOUR LOGIC HERE
})

Blank Firefox addon panel page with multiple windows

I've followed MDN's document to create a toggle button addon.
Everything works fine except one problem:
Open a second browser window (cmd+n or ctrl+n) and click on the toggle button there
Click on the toggle button on the original browser window without closing the toggle button on the second window
the toggle button's panel becomes blank with the following error message:
JavaScript error: resource:///modules/WindowsPreviewPerTab.jsm, line 406: NS_ERR
OR_FAILURE: Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsIT
askbarTabPreview.invalidate]
// ./lib/main.js
var { ToggleButton } = require("sdk/ui/button/toggle");
var panels = require("sdk/panel");
var self = require("sdk/self");
var buttonIndex = 0;
var lastKnownButtonIndex = 0;
var button = ToggleButton({
id: "button",
label: "my button",
icon: {
"16": "./icon-16.png"
},
onClick: handleChange,
});
var panel = panels.Panel({
contentURL: self.data.url("menu.html"),
onHide: handleHide
});
function handleChange(state) {
if (state.checked) {
panel.show({
position: button
});
}
}
function handleHide() {
button.state('window', {checked: false});
}
function assignButtonIndex() {
return (buttonIndex++).toString();
}
The complete addon is here: https://goo.gl/9N3jle
To reproduce: Extract the zip file and $ cd testButton; cfx run and follow the above steps.
I really hope someone can help me with this. Thank you in advance!
It's a bug; you're not doing anything wrong. It's a racing condition between the windows' focus events, and the panel's event, that prevent somehow the panel's hidden event to be emitted properly.
You can try to mitigate with a workaround the issue until is properly fixed. I added some explanation in the bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1174425#c2 but in short, you can try to add a setTimeout to delay a bit when the panel is shown, in order to avoid the racing condition with the window's focus. Something like:
const { setTimeout } = require("sdk/timers");
/* ... your code here ... */
function handleChange(state) {
if (state.checked) {
setTimeout(() => panel.show({ position: button }), 100);
}
}
I am currently using a workaround where I dynamically create a new Panel every time the user presses the toolbar button.
It is faster than the 100ms workaround and also handles a scenario where the user outright closes one of the browser windows while the panel is open. (The 100ms workaround fails in this case and a blank panel is still displayed.)
It works like this:
let myPanel = null;
const toolbarButton = ToggleButton({
...,
onChange: function (state) {
if (state.checked) {
createPanel();
}
}
});
function createPanel(){
// Prevent memory leaks
if(myPanel){
myPanel.destroy();
}
// Create a new instance of the panel
myPanel = Panel({
...,
onHide: function(){
// Destroy the panel instead of just hiding it.
myPanel.destroy();
}
});
// Display the panel immediately
myPanel.show();
}

set bootbox dialog window focus through select2-open event

I am opening a bootbox dialog box when the user click inside select-2 input box using the following code
$("#categoryfinder").on("select2-open", function () {
bootbox.confirm("Are you sure?", function (result) {
Example.show("Confirm result: " + result);
});
});
Once it is opened, the popup window is not active and I have to click twice to close or to trigger any event on the popup
Please let me know if there is any solution for this.
EDIT: when I click inside 1, the popup is not active at 1,2,3, I have to click once to make it active.
I fixed this issue by doing the following changes
$("#allergenfinder").on("select2-opening", function (e) {
e.preventDefault();
excludeAllergenPrompt();
});

Resources