-
-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add cypress tests #509
Add cypress tests #509
Changes from 2 commits
bc4efd8
23ec132
3b5b5df
837a6f7
a3c115e
2a9a9c3
b28f9cb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,3 +28,5 @@ tmp.* | |
vignettes/*.R | ||
vignettes/*.html | ||
vignettes/*.md | ||
tests/node_modules | ||
tests/cypress/videos |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
const { defineConfig } = require("cypress"); | ||
|
||
module.exports = defineConfig({ | ||
video: true, | ||
viewportWidth: 1000, | ||
viewportHeight: 1200, | ||
e2e: { | ||
setupNodeEvents(on, config) { | ||
// implement node event listeners here | ||
}, | ||
}, | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
library(teal) | ||
|
||
data <- teal_data() |> | ||
within({ | ||
set.seed(123) | ||
size <- 20 | ||
data_frame <- data.frame( | ||
id = seq(size), | ||
numeric = round(rnorm(size, 25, 10)), | ||
logical = sample(c(TRUE, FALSE), size = size, replace = TRUE), | ||
factor = factor(sample(LETTERS[1:4], size = size, replace = TRUE)), | ||
character = sample(letters, size = size, replace = TRUE), | ||
datetime = sample( | ||
seq(as.POSIXct("2000/01/01"), as.POSIXct("2024/01/01"), by = "day"), | ||
size = size | ||
) | ||
) | ||
iris <- head(iris, 20) | ||
data_frame$date <- as.Date(data_frame$datetime) | ||
}) | ||
datanames(data) <- c("data_frame", "iris") | ||
app <- teal::init( | ||
data = data, | ||
modules = modules(example_module()), | ||
filter = teal_slices( | ||
teal_slice("data_frame", "numeric"), | ||
teal_slice("data_frame", "logical"), | ||
teal_slice("data_frame", "factor"), | ||
teal_slice("data_frame", "character"), | ||
teal_slice("data_frame", "datetime"), | ||
teal_slice("data_frame", "date") | ||
) | ||
) | ||
|
||
options(shiny.port = 5555) | ||
shinyApp(app$ui, app$server) |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,150 @@ | ||||||
describe("data.frame filters", () => { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So for cypress tests you assume that there is a specific class id/name, so that's why you put all those |
||||||
beforeEach(() => { | ||||||
cy.visit("http://127.0.0.1:5555/"); | ||||||
cy.waitForStabilityAndCatchError("body"); | ||||||
cy.get(".cy-active-summary-table tbody tr").as("activeSummary"); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't like the fact id is duplicated with
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍🏽 Let's try to find out if it's possible to implement the tests without any change to the classes in the package as we talked about. If unsuccessful, we can use this convention, I like it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I respect the idea of having a distinction between what's testable and what is not so app developer won't change this casually (I hope at least). It will be good to write down this convention somewhere that "testable" means don't touch it ;) Also, I think we need to review all html classes to follow some "template". They seem to be random There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Agreed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if |
||||||
}); | ||||||
|
||||||
it("should filter proper values for numeric filters", () => { | ||||||
cy.get(".cy-filter-card").contains("numeric").as("numericCard"); | ||||||
cy.get("@numericCard").click(); | ||||||
cy.waitForStabilityAndCatchError("body"); | ||||||
|
||||||
cy.get("@numericCard") | ||||||
.get(".cy-numeric-selection-inputs input:first") | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm thinking about parent-child naming convention for filters. In
Suggested change
Let's sit down and design a complete wireframe for teal/teal.slice and let's think about classes/ids of each element |
||||||
.clear() | ||||||
.type("10{enter}"); | ||||||
cy.waitForStabilityAndCatchError("body"); | ||||||
cy.get("@activeSummary") | ||||||
.should("contain", "data_frame") | ||||||
.should("contain", "19/20"); | ||||||
|
||||||
cy.get("@numericCard") | ||||||
.get(".cy-numeric-selection-inputs input:last") | ||||||
.clear() | ||||||
.type("20{enter}"); | ||||||
cy.waitForStabilityAndCatchError("body"); | ||||||
cy.get("@activeSummary") | ||||||
.should("contain", "data_frame") | ||||||
.should("contain", "5/20"); | ||||||
}); | ||||||
|
||||||
it("should filter proper values for logical filters", () => { | ||||||
cy.get(".cy-filter-card").contains("logical").as("logicalCard"); | ||||||
cy.get("@logicalCard").click(); | ||||||
cy.waitForStabilityAndCatchError("body"); | ||||||
|
||||||
cy.get("@logicalCard") | ||||||
.get(".cy-logical-selection-inputs .checkbox") | ||||||
.contains("TRUE") | ||||||
.as("trueCheckbox"); | ||||||
cy.get("@logicalCard") | ||||||
.get(".cy-logical-selection-inputs .checkbox") | ||||||
.contains("FALSE") | ||||||
.as("falseCheckbox"); | ||||||
|
||||||
cy.get("@trueCheckbox").click(); | ||||||
cy.waitForStabilityAndCatchError("body"); | ||||||
cy.get("@activeSummary") | ||||||
.should("contain", "data_frame") | ||||||
.should("contain", "7/20"); | ||||||
|
||||||
cy.get("@falseCheckbox").click(); | ||||||
cy.waitForStabilityAndCatchError("body"); | ||||||
cy.get("@activeSummary") | ||||||
.should("contain", "data_frame") | ||||||
.should("contain", "0/20"); | ||||||
|
||||||
cy.get("@trueCheckbox").click(); | ||||||
cy.waitForStabilityAndCatchError("body"); | ||||||
cy.get("@activeSummary") | ||||||
.should("contain", "data_frame") | ||||||
.should("contain", "13/20"); | ||||||
}); | ||||||
|
||||||
it("should filter proper values for factor filters", () => { | ||||||
cy.get(".cy-filter-card").contains("factor").as("factorCard"); | ||||||
cy.get("@factorCard").click(); | ||||||
cy.waitForStabilityAndCatchError("body"); | ||||||
|
||||||
cy.get("@factorCard") | ||||||
.get(".cy-factor-selection-inputs .checkbox") | ||||||
.contains("C") | ||||||
.click(); | ||||||
cy.waitForStabilityAndCatchError("body"); | ||||||
cy.get("@activeSummary") | ||||||
.should("contain", "data_frame") | ||||||
.should("contain", "15/20"); | ||||||
}); | ||||||
|
||||||
it("should filter proper values for character filters", () => { | ||||||
cy.get(".cy-filter-card").contains("character").as("characterCard").click(); | ||||||
cy.waitForStabilityAndCatchError("body"); | ||||||
|
||||||
cy.get("@characterCard") | ||||||
.get(".cy-character-selection-inputs .dropdown-toggle") | ||||||
.as("characterInputs") | ||||||
.click(); | ||||||
cy.waitForStabilityAndCatchError("body"); | ||||||
|
||||||
cy.get("@characterInputs").get("li").contains("b").click(); | ||||||
cy.waitForStabilityAndCatchError("body"); | ||||||
cy.get("body").type("{esc}"); | ||||||
cy.waitForStabilityAndCatchError("body"); | ||||||
cy.get("@activeSummary") | ||||||
.should("contain", "data_frame") | ||||||
.should("contain", "19/20"); | ||||||
}); | ||||||
|
||||||
it("should filter proper values for datetime filters", () => { | ||||||
cy.get(".cy-filter-card").contains("datetime").as("datetimeCard").click(); | ||||||
cy.waitForStabilityAndCatchError("body"); | ||||||
|
||||||
cy.get("@datetimeCard").get(".cy-datetime-from-input").click(); | ||||||
cy.waitForStabilityAndCatchError("body"); | ||||||
cy.get( | ||||||
'.air-datepicker-global-container .air-datepicker-cell[data-year="2001"][data-month="10"][data-date="29"]' | ||||||
).click(); | ||||||
cy.get("body").type("{esc}"); | ||||||
cy.waitForStabilityAndCatchError("body"); | ||||||
cy.get("@activeSummary") | ||||||
.should("contain", "data_frame") | ||||||
.should("contain", "19/20"); | ||||||
|
||||||
cy.get("@datetimeCard").get(".cy-datetime-to-input").click(); | ||||||
cy.get( | ||||||
'.air-datepicker-global-container .air-datepicker-cell[data-year="2021"][data-month="5"][data-date="1"]' | ||||||
).click(); | ||||||
cy.get("body").type("{esc}"); | ||||||
cy.waitForStabilityAndCatchError("body"); | ||||||
cy.get("@activeSummary") | ||||||
.should("contain", "data_frame") | ||||||
.should("contain", "18/20"); | ||||||
}); | ||||||
|
||||||
it("should filter proper values for date filters", () => { | ||||||
cy.get(".cy-filter-card") | ||||||
.contains(/^date$/) | ||||||
.as("dateCard") | ||||||
.click(); | ||||||
cy.waitForStabilityAndCatchError("body"); | ||||||
|
||||||
cy.get("@dateCard").get(".cy-date-inputs input:first").click(); | ||||||
cy.waitForStabilityAndCatchError("body"); | ||||||
|
||||||
cy.get(".datepicker-days table td").contains("16").click(), | ||||||
cy.waitForStabilityAndCatchError("body"); | ||||||
cy.get("@activeSummary") | ||||||
.should("contain", "data_frame") | ||||||
.should("contain", "19/20"); | ||||||
|
||||||
cy.get("@dateCard").get(".cy-date-inputs input:last").click(); | ||||||
cy.waitForStabilityAndCatchError("body"); | ||||||
|
||||||
cy.get(".datepicker-days table td").contains("5").click(), | ||||||
cy.waitForStabilityAndCatchError("body"); | ||||||
cy.get("@activeSummary") | ||||||
.should("contain", "data_frame") | ||||||
.should("contain", "18/20"); | ||||||
}); | ||||||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
describe("General filter panel features", () => { | ||
beforeEach(() => { | ||
cy.visit("http://127.0.0.1:5555/"); | ||
cy.waitForStabilityAndCatchError("body"); | ||
cy.get(".cy-active-summary-table tbody tr").as("activeSummary"); | ||
}); | ||
|
||
it("should initiate filter panel with the right values", () => { | ||
cy.get("@activeSummary") | ||
.should("contain", "data_frame") | ||
.should("contain", "20/20"); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
Cypress.Commands.add( | ||
"waitForStabilityAndCatchError", | ||
(selector, stabilityPeriod = 300) => { | ||
let lastInnerHTML = ""; | ||
let timesRun = 0; | ||
const checkInterval = 100; | ||
const maxTimesRun = stabilityPeriod / checkInterval; | ||
|
||
function checkForChanges() { | ||
cy.get(selector).then(($el) => { | ||
// Check for shiny-output-error class anywhere in the body | ||
if (Cypress.$("body").find(".shiny-output-error").length > 0) { | ||
throw new Error( | ||
"shiny-output-error class detected during stability check" | ||
); | ||
} | ||
|
||
const currentInnerHTML = $el.prop("innerHTML"); | ||
if (currentInnerHTML !== lastInnerHTML) { | ||
lastInnerHTML = currentInnerHTML; | ||
timesRun = 0; | ||
} else if (timesRun < maxTimesRun) { | ||
timesRun += 1; | ||
} else { | ||
return; | ||
} | ||
cy.wait(checkInterval).then(checkForChanges); | ||
}); | ||
} | ||
|
||
checkForChanges(); | ||
} | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be a generic shiny app instead of teal. Testing could be distorted by
teal
. I suggest to use shiny app from vignette. Should also test the output if and how data gets filtererd?