Skip to content

Commit 0dfe79d

Browse files
cpsievertclaude
andcommitted
Add tests for bug fixes and update NEWS
- Add test-issue-fixes.R with tests for #2458, #2420, #2462, #2446 - Update NEWS.md with bug fix entries Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 0581ef5 commit 0dfe79d

File tree

2 files changed

+154
-0
lines changed

2 files changed

+154
-0
lines changed

NEWS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ See the [plotly.js releases page](https://github.com/plotly/plotly.js/releases)
2626
## Bug fixes
2727

2828
* `plotly_build()` now works with `ggmatrix` objects (e.g., from `GGally::ggpairs()`). (#2447)
29+
* Cross-trace layout attributes (`bargroupgap`, `boxmode`, `violingap`, etc.) no longer produce errant warnings. (#2458)
30+
* `ggplotly()` now correctly uses custom legend labels from `scale_*_manual(labels = ...)`. (#2420)
31+
* `ggplotly()` with `dynamicTicks = TRUE` no longer errors on grouped `geom_line()` plots. (#2462)
32+
* `plot_ly()` with color mapping no longer resets Date/POSIXct x-axis values to 1970. (#2446)
2933

3034
# plotly 4.11.0
3135

tests/testthat/test-issue-fixes.R

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# Tests for specific issue fixes
2+
3+
# Issue #2458: bargroupgap and other layout attributes should not warn
4+
test_that("Cross-trace layout attributes do not produce warnings", {
5+
p <- plot_ly(x = 1:3, y = 1:3, type = "bar")
6+
7+
# Bar attributes
8+
expect_silent(plotly_build(layout(p, bargroupgap = 0.1)))
9+
expect_silent(plotly_build(layout(p, barnorm = "fraction")))
10+
11+
# Box attributes
12+
expect_silent(plotly_build(layout(p, boxmode = "group")))
13+
expect_silent(plotly_build(layout(p, boxgap = 0.1)))
14+
expect_silent(plotly_build(layout(p, boxgroupgap = 0.1)))
15+
16+
# Violin attributes
17+
expect_silent(plotly_build(layout(p, violinmode = "group")))
18+
expect_silent(plotly_build(layout(p, violingap = 0.1)))
19+
expect_silent(plotly_build(layout(p, violingroupgap = 0.1)))
20+
21+
# Funnel attributes
22+
expect_silent(plotly_build(layout(p, funnelmode = "group")))
23+
expect_silent(plotly_build(layout(p, funnelgap = 0.1)))
24+
expect_silent(plotly_build(layout(p, funnelgroupgap = 0.1)))
25+
26+
# Waterfall attributes
27+
expect_silent(plotly_build(layout(p, waterfallmode = "group")))
28+
expect_silent(plotly_build(layout(p, waterfallgap = 0.1)))
29+
expect_silent(plotly_build(layout(p, waterfallgroupgap = 0.1)))
30+
})
31+
32+
# Issue #2420: ggplotly legend should use scale labels
33+
test_that("ggplotly legend uses custom scale labels", {
34+
d <- data.frame(X = 1:5, Y = 1:5)
35+
36+
# Test with scale_color_manual labels
37+
38+
gg <- ggplot(d, aes(x = X, y = Y, col = "value1")) +
39+
geom_point() +
40+
scale_color_manual(values = c("blue"), labels = c("Custom Label"))
41+
42+
p <- ggplotly(gg)
43+
built <- plotly_build(p)
44+
45+
# The trace name should be "Custom Label", not "value1"
46+
expect_equal(built$x$data[[1]]$name, "Custom Label")
47+
expect_equal(built$x$data[[1]]$legendgroup, "Custom Label")
48+
})
49+
50+
test_that("ggplotly legend uses custom labels with multiple values", {
51+
d <- data.frame(X = 1:10, Y = (1:10)^2, grp = rep(c("a", "b"), 5))
52+
53+
gg <- ggplot(d, aes(x = X, y = Y, col = grp)) +
54+
geom_point() +
55+
scale_color_manual(
56+
values = c("a" = "red", "b" = "blue"),
57+
labels = c("a" = "Group A", "b" = "Group B")
58+
)
59+
60+
p <- ggplotly(gg)
61+
built <- plotly_build(p)
62+
63+
# Get trace names
64+
trace_names <- sapply(built$x$data, function(tr) tr$name)
65+
trace_names <- trace_names[!is.na(trace_names)]
66+
67+
expect_true("Group A" %in% trace_names)
68+
expect_true("Group B" %in% trace_names)
69+
expect_false("a" %in% trace_names)
70+
expect_false("b" %in% trace_names)
71+
})
72+
73+
# Issue #2462: dynamicTicks with grouped geom_line should not error
74+
test_that("dynamicTicks works with grouped geom_line", {
75+
df <- data.frame(
76+
time = factor(rep(c("t1", "t2"), 4)),
77+
value = c(1.25, 1.5, 2, 1.75, 1.25, 0.25, 3, 3.5),
78+
grp = factor(rep(1:4, each = 2))
79+
)
80+
81+
p <- ggplot(df, aes(x = time, y = value)) +
82+
geom_line(aes(group = grp))
83+
84+
# This should not error (previously failed with "attempt to select less than one element")
85+
expect_silent(built <- plotly_build(ggplotly(p, dynamicTicks = TRUE)))
86+
87+
# Verify the data contains NA values (from group2NA) that are preserved
88+
trace_x <- built$x$data[[1]]$x
89+
expect_true(any(is.na(trace_x)))
90+
91+
# Non-NA values should be categorical labels
92+
non_na_x <- trace_x[!is.na(trace_x)]
93+
expect_true(all(non_na_x %in% c("t1", "t2")))
94+
})
95+
96+
# Issue #2446: Date class should be preserved in colorbar trace
97+
test_that("Date class is preserved in colorbar trace", {
98+
df <- data.frame(
99+
y = 1:10,
100+
rank = sample(1:100, 10),
101+
datetime = seq(as.Date("2022-01-01"), by = "day", length.out = 10)
102+
)
103+
104+
p <- plot_ly(df, type = "scatter", mode = "markers") %>%
105+
add_trace(x = ~datetime, y = ~y, color = ~rank)
106+
107+
built <- plotly_build(p)
108+
109+
110+
# Find the main data trace (not the empty first trace or colorbar)
111+
data_trace <- NULL
112+
for (tr in built$x$data) {
113+
if (!is.null(tr[["x"]]) && length(tr[["x"]]) > 2) {
114+
data_trace <- tr
115+
break
116+
}
117+
}
118+
119+
expect_false(is.null(data_trace))
120+
expect_s3_class(data_trace[["x"]], "Date")
121+
122+
# The x values should be in 2022, not 1970
123+
expect_true(all(data_trace[["x"]] >= as.Date("2022-01-01")))
124+
expect_true(all(data_trace[["x"]] <= as.Date("2022-12-31")))
125+
})
126+
127+
test_that("POSIXct class is preserved in colorbar trace", {
128+
df <- data.frame(
129+
y = 1:10,
130+
rank = sample(1:100, 10),
131+
datetime = seq(as.POSIXct("2022-01-01"), by = "day", length.out = 10)
132+
)
133+
134+
p <- plot_ly(df, type = "scatter", mode = "markers") %>%
135+
add_trace(x = ~datetime, y = ~y, color = ~rank)
136+
137+
built <- plotly_build(p)
138+
139+
# Find the main data trace
140+
data_trace <- NULL
141+
for (tr in built$x$data) {
142+
if (!is.null(tr[["x"]]) && length(tr[["x"]]) > 2) {
143+
data_trace <- tr
144+
break
145+
}
146+
}
147+
148+
expect_false(is.null(data_trace))
149+
expect_s3_class(data_trace[["x"]], "POSIXt")
150+
})

0 commit comments

Comments
 (0)