describe("S7 classes", { it("possess expected properties", { foo <- new_class("foo", package = "S7", validator = function(self) NULL) expect_equal(prop_names(foo), setdiff(names(attributes(foo)), "class")) expect_type(foo@name, "character") expect_equal(foo@parent, S7_object) expect_type(foo@constructor, "closure") expect_type(foo@validator, "closure") expect_type(foo@properties, "list") }) it("print nicely", { foo1 <- new_class("foo1", properties = list(x = class_integer, y = class_integer), package = NULL) foo2 <- new_class("foo2", foo1, package = NULL) expect_snapshot({ foo2 str(foo2) # Omit details when nested str(list(foo2)) }) }) it("prints @package and @abstract details", { foo <- new_class("foo", package = "S7", abstract = TRUE) expect_snapshot(foo) }) it("checks inputs", { expect_snapshot(error = TRUE, { new_class(1) new_class("foo", 1) new_class("foo", package = 1) new_class("foo", constructor = 1) new_class("foo", constructor = function() {}) new_class("foo", validator = function() {}) }) }) it("can't inherit from S4 or class unions", { parentS4 <- methods::setClass("parentS4", slots = c(x = "numeric")) expect_snapshot(error = TRUE, { new_class("test", parent = parentS4) new_class("test", parent = new_union("character")) }) }) it("can't inherit from an environment", { expect_snapshot(error = TRUE, { new_class("test", parent = class_environment) }) }) }) describe("inheritance", { it("combines properties for parent classes", { foo1 <- new_class("foo1", properties = list(x = class_double)) foo2 <- new_class("foo2", foo1, properties = list(y = class_double)) expect_equal(names(foo2@properties), c("x", "y")) }) it("child properties override parent", { foo1 <- new_class("foo1", properties = list(x = class_numeric)) foo2 <- new_class("foo2", foo1, properties = list(x = class_double)) expect_equal(names(foo2@properties), "x") expect_equal(foo2@properties$x$class, class_double) }) }) describe("abstract classes", { it("can't be instantiated", { expect_snapshot(error = TRUE, { foo <- new_class("foo", abstract = TRUE) foo() }) }) it("can't inherit from concrete class", { expect_snapshot(error = TRUE, { foo1 <- new_class("foo1") new_class("foo2", parent = foo1, abstract = TRUE) }) }) it("can construct concrete subclasses", { foo1 <- new_class("foo1", abstract = TRUE, package = NULL) foo2 <- new_class("foo2", parent = foo1, package = NULL) expect_s3_class(foo2(), "foo2") }) it("can use inherited validator from abstract class", { foo1 <- new_class( "foo1", properties = list(x = class_double), abstract = TRUE, validator = function(self) { if (self@x == 2) "@x has bad value" }, package = NULL ) foo2 <- new_class("foo2", parent = foo1, package = NULL) expect_no_error(foo2(x = 1)) expect_snapshot(foo2(x = 2), error = TRUE) }) }) describe("new_object()", { it("gives useful error if called directly",{ expect_snapshot(new_object(), error = TRUE) }) it("validates object", { foo <- new_class("foo", properties = list(x = new_property(class_double)), validator = function(self) if (self@x < 0) "x must be positive", package = NULL ) expect_snapshot(error = TRUE, { foo("x") foo(-1) }) }) it("runs each parent validator exactly once", { A <- new_class("A", validator = function(self) cat("A ")) B <- new_class("B", parent = A, validator = function(self) cat("B ")) C <- new_class("C", parent = B, validator = function(self) cat("C ")) expect_snapshot({ . <- A() . <- B() . <- C() }) }) }) describe("S7 object", { it("has an S7 and S3 class", { foo <- new_class("foo", package = NULL) x <- foo() expect_equal(S7_class(x), foo) expect_equal(class(x), c("foo", "S7_object")) }) it("displays nicely", { expect_snapshot({ foo <- new_class("foo", properties = list(x = class_double, y = class_double), package = NULL) foo() str(list(foo())) }) }) it("displays objects with data nicely", { expect_snapshot({ text <- new_class("text", class_character, package = NULL) text("x") str(list(text("x"))) }) }) it("displays list objects nicely", { foo1 <- new_class( "foo1", parent = class_list, properties = list(x = class_double, y = class_list), package = NULL ) expect_snapshot( foo1( list( x = 1, y = list(a = 21, b = 22) ), x = 3, y = list(a = 41, b = 42) ) ) }) }) describe("default constructor", { it("initializes properties with defaults", { foo1 <- new_class("foo1", properties = list(x = class_double)) expect_equal(props(foo1()), list(x = double())) foo2 <- new_class("foo2", foo1, properties = list(y = class_double)) expect_equal(props(foo2()), list(x = double(), y = double())) }) it("overrides properties with arguments", { foo1 <- new_class("foo1", properties = list(x = class_double)) foo2 <- new_class("foo2", foo1, properties = list(y = class_double)) expect_equal(props(foo2(x = 1)), list(x = 1, y = double())) expect_equal(props(foo2(x = 1, y = 2)), list(x = 1, y = 2)) }) it("can initialise a property to NULL", { foo <- new_class("foo", properties = list( x = new_property(default = 10) )) x <- foo(x = NULL) expect_equal(x@x, NULL) }) it("initializes data with defaults", { text1 <- new_class("text1", parent = class_character) obj <- text1() expect_equal(S7_data(obj), character()) }) it("overrides data with defaults", { text1 <- new_class("text1", parent = class_character) expect_equal(S7_data(text1("x")), "x") }) it("initializes property with S7 object", { foo1 <- new_class("foo1", package = NULL) foo2 <- new_class("foo2", properties = list(x = foo1), package = NULL) x <- foo2() expect_s3_class(x@x, "foo1") }) }) test_that("c(, ...) gives error", { foo1 <- new_class("foo1") expect_snapshot(error = TRUE, { c(foo1, foo1) }) }) test_that("can round trip to disk and back", { eval(quote({ foo1 <- new_class("foo1", properties = list(y = class_integer)) foo2 <- new_class("foo2", properties = list(x = foo1)) f <- foo2(x = foo1(y = 1L)) }), globalenv()) f <- globalenv()[["f"]] path <- tempfile() saveRDS(f, path) f2 <- readRDS(path) expect_equal(f, f2) rm(foo1, foo2, f, envir = globalenv()) }) test_that("can't create class with reserved property names", { expect_snapshot(error = TRUE, { new_class("foo", properties = list(names = class_character)) new_class("foo", properties = list(dim = NULL | class_integer)) new_class("foo", properties = list(dim = NULL | class_integer, dimnames = class_list)) }) })