I am currently learning some very basic JS and Cypress in order to be able to write some UI test for the shiny R markdown application I am developing (very new to this!!). I have managed to write a simple test that asserts that a given value is selected by default when the app starts up. However, I would like to be able to select a specific element and more importantly, loop over the entire list of elements available in order to assert that the output is as expected.
I'm stuck figuring out how to select a specific element from the dropdown and also don't know how to implement a loop yet. Any pointers will be much appreciated!
Here is a simple shiny app that contains only a dropdown with 3 options (app.R file) as I cannot share the original code due to proprietary reasons:
library(shiny)
ui <- fluidPage(
selectizeInput(inputId = 'element1', label = "Test input",
choices = c('descriptive', 'comparative', 'neutral'),
selected = 'descriptive')
)
server <- function(input, output) {}
# Run the application
shinyApp(ui = ui, server = server)
In my simple UI test, I have managed to write the assertions similar to this:
cy.get('select#element1 option:selected')
.should('have.value', 'descriptive')
But when I use the web inspector to inspect the structure of the selectizeInput, I don't understand why the options are not listed as select options. That seems to be the reason for why I cannot use something like cy.get('select#element1').select('comparative') to select a different value from the dropdown. I thought maybe the other options were "hidden", but I then found them in a separate div further down in the DOM:
I'm really just starting to learn all of this, so any pointers would be super helpful. Even just to help me google the right things, because googling how to work with select elements in cypress was apparently not the right thing?
You can use the eq() command if you want to select any items from the dropdown:
//Click to open the dropdown
cy.get('div.option').eq(1).click() //selects comparative
You can also use a combination of text and selector using contains() and select the item from drop-down:
//Click to open the dropdown
cy.contains('div.option', 'neutral').click() //selects neutral
You can also use the filter() command:
//Click to open the dropdown
cy.get('div.option').filter(':contains("neutral")').click() //selects neutral
If you want to loop through the elements and then select you can do something like:
Using the index Position
//Click to open the dropdown
cy.get('div.option').each(($ele, index) => {
if (index == 1) {
cy.wrap($ele).eq(index).click()
}
})
Using the element text
//Click to open the dropdown
cy.get('div.option').each(($ele, index) => {
if($ele.text() == "comparative") {
cy.wrap($ele).click()
}
})
Component libraries often enhance the select control in order to give a better look, additional features like multi-select. The plain HTML5 select is limited in how you can style and control it.
In the case of Shiny library, they have added both a <select> to hold the value plus a set of nested <div> to provide the enhancements (the next element block after <select>).
The <select> has style="display: none;" which means the user cannot interact with it directly, but it is updated in the javascript event handlers.
Doing e2e tests we want interact as a user would, but test the hidden <select> value since this is what is used by the R framework.
// Test the initial value of hidden select
cy.get('select#element1')
.should('have.value', 'descriptive')
.find('option')
.should('have.length', 1) // only one option on the select
cy.get('select#e1')
.next() // access the UI block
.click() // open the "select"
.contains('comparative') // chose an option
.click()
// Test the new value of hidden select
cy.get('select#e1')
.should('have.value', 'comparative')
.find('option')
.should('have.length', 1) // still only one option
Looping
// Test the initial value of hidden select
cy.get('select#element1')
.should('have.value', 'descriptive')
.find('option')
.should('have.length', 1) // only one option on the select
const choices = ['descriptive', 'comparative', 'neutral']
choices.forEach(choice => {
cy.get('select#e1')
.next() // access the UI block
.click() // open the "select"
.contains(choice) // chose this choice
.click()
// Test the new value of hidden select
cy.get('select#e1')
.should('have.value', choice)
})
Related
I would like to have the Shiny app remember which tab was selected at the time of a session$reload() (refreshing the page) to clear all inputs except the value of the current tab selected in a navbarPage().
The only idea that comes to mind, though I don't know how I could implement this, would be to assign a global variable tabIndicator as the value of the current tab selected (how do you get this value?), then on refresh, rm() all variables in global environment except tabIndicator, and set selected = tabIndicator in navbarPage().
How can I accomplish this?
You have two options, both are beyond the basics of shiny.
You could use cookies. Every time a new tab is entered, send a message to javascript and set a cookie. When the app initializes, check for the presence of the cookie, and if it's set then read its value and change to the appropriate tab
Use the shinyStore package, which gives you the ability of leveraging HTML5's local storage (but this won't work on older browsers because HTML5 is relatively new)
I don't have time to provide full code right now, but hopefully this helps
It's doable, I think, but tricky. The issue is that session$reload() is equivalent to hitting the refresh button in the browser, so you're creating a new session, and losing all of the context. I guess you could use cookies somehow to do it, but I'd recommend using an actionButton that resets all of your inputs using updateWhatever, instead of actually doing the session$reload().
I was able to figure it out using a global variable.
I put this code at the top of my app (before ui and server)
# Clear global environment except 'currentTab'
rm(list=setdiff(ls(), 'currentTab'))
# If current tab exists, restore to that tab. Otherwise, start at home screen
if(!exists('currentTab')){
currentTab <- 'HOMEPAGE'
}
And this code at the bottom of my server
# Current tab
observe({
currentTab <<- input$navbar
})
Works like a charm. Thanks to all for the help.
I have a selectInput with all the possible choices displayed. I want to implement it such that if a user types something in the box and hits enter, that will also be added as a choice.
observeEvent(input$mykeypress, {
if(input$mykeypress == 13){
print(input$selectBox)
}
I tried the above code to see if my input changed, but it seems that no matter what extra stuff I type in the selectbox, none of it is recognized.
Anyone have ideas as to have I can recognize anything extra that the user types?
Edit: One other idea I am considering is creating a textInput, and prepopulating it with all the values that I used in my selectbox. Is something like that possible?
I ended up figuring it out, because the selectizeInput just wraps around selectize.js, in your selectizeInput you can use the argument: options = list(create = TRUE), and the user can add words easily.
Using Domino Designer 8.5. If I have a form with a radio button field, is it possible to disable that field in LotusScript, possibly in the Postopen even of the form?
The only way I can, so far, see of achieving this is by using the Input Enabled formula of the field itself, but I am struggling to understand when this is triggered... if I try to put a #StatusBar or #Prompt formula call in there as well then there is never any notification of Input Enabled being triggered.
If Input Enabled is the way to achieve this, not LotusScript, then is there a way I can have a) a formula that sets the Input Enabled condition plus b) a way of getting some visual output, either to the Status Bar or a Message Box, to either just indicate the formula has been triggered or - even better - to let me know the value of some variable I'd like to check?
The Input Enabled Formula is triggered on every refresh of the document (F9, Save, NotesUIDocument.Refresh, etc.), it can not contain any code "interacting" with the user.
But you could do something like this:
Create a Field "InputEnabled"
The best way to have a "controlling" field is to make it "Computed for Display". That way it is not stored in the document. As a formula you simply enter #ThisValue, then it does not change its value by itself and can be set using LotusScript. If you want to have an "initial" value, then the formula would be: #If( #ThisValue = "" ; "YES" ; #ThisValue )
In the "Input enabled" Formula of your other field write the code:
InputEnabled = "YES"
Fill the "InputEnabled"- Field using a LotusScript (NotesDocument.ReplaceItemValue( "InputInabled", "YES" ), or using a Formula directly in the field.
Like that you can easily "see" what the conditions are (in the field InputEnabled) and change it using formula or script.
Old school way to Disabling the radio button field:
You will have to use the **Hide paragraph if formula is true". Define a field: MyButEnabled accorting to which you enable (show) or disable (hide) the field. for UI convience display a complementary line that only display the value of your radio button (you can also use a computed radio button but it's not very pretty).
In the post open set the appropriated value for the field MyButEnabled. Dont forget to call uidoc.RefreshHideFormulas method.
The notes input enabled way: (see also: http://www-01.ibm.com/support/docview.wss?uid=swg21173862)
the notes input enabled formula is (for example)
#if(MyButEnabled="Y" ; 1 ; 0)
When you form is openned, the formula is computed, if the result is 0 the field won't be editable, 1 will allow field to be edited.
If you change the value of MyButEnabled, then you need to make a UIdocument.refresh in order to change to be reflected in the UI.
I have written a GUI extension which adds an additional tab to many of the Item views in the SDL Tridion CME (e.g. Component, Page and Schema etc.). I have also written some JavaScript which loads that tab directly if when the view is loaded with a tab name is specified in the URL.
The result is that if a page is loaded with the tab name added as follows:
http://localhost/WebUI/item.aspx?tcm=64#id=tcm:1-48-64&tab=InfoTab
Rather than the default of
http://localhost/WebUI/item.aspx?tcm=64#id=tcm:1-48-64
The Info Tab will be loaded on top, instead of the General Tab. This is performed with the following code snippet and works very well:
$evt.addEventHandler($display, "start", onDisplayStarted);
// This callback is called when any view has finished loading
function onDisplayStarted() {
$evt.removeEventHandler($display, "start", onDisplayStarted);
var tabname = $url.getHashParam("tab");
if (tabname != '') {
var tabControl = $controls.getControl($("#MasterTabControl"), "Tridion.Controls.TabControl");
tabControl.selectItem(tabname);
}
}
Now I would like to make a context menu item to open items and link to the tabs using my new functionality. My first thought was to construct the Item URL myself and simply open a new window in my execute method. So I looked at the default functionality in the standard Open.prototype_execute() functionality of the GUI. This is stored in the navigation.js file of the CME, and is performed by the Tridion.Cme.Commands.Open.prototype._execute method. The code is a lot more complicated than I had anticipated as it deals with shared items, and permissions etc.
Rather than just copying all of this code to my own function, I was wondering if there is a way to elegantly extend the existing Open.prototype_execute() function and append my “&tab=MyTab” to the $cme.Popups.OPEN_ITEM_OPTIONS.URL constant for my own functions.
Any advice would be greatly appreciated.
At the end the Open command uses $config.getEditorUrl(item_type) to get the url for the item view (item_type - $const.ItemType.COMPONENT, etc). There are no extension points for this part of the functionality, but you could always try to overwrite it on your own risk.
I have seen answers for capturing events from user-created controls on wizard pages, but how do you do it for controls created as a result of a call to CreateInputOptionPage?
For example:
InputOptionPage := CreateInputOptionPage (wpWelcome,
'Options',
'Select your option',
'Please choose from one of the three options below:'
True, False);
InputOptionPage.Add ('Option 1') ;
InputOptionPage.Add ('Option 2') ;
InputOptionPage.Add ('Option 3') ;
will create an option page with a radio group on it. I don't intend selecting any of the options by default and want to force the user to do so. As a visual clue I want to gray out the "Next" button while none are selected.
How do I add an OnClick handler for the radio buttons?
Although not directly the same question, the answer I just provided to this question shows how to do this for a Check Box but it's identical for a radio box.
Short extract from Robert Love's answer:
procedure YourControlClick(Sender: TObject);
begin
MsgBox('yep', mbError, 0);
end;
YourControl.OnClick := #YourControlClick;
I.e. everything is similar to usual Delphi style except for # symbol. Omitting it results in confusing "Invalid number of parameters" error during compile.