context("nonportable")

test_that("initialization", {
  AC <- R6Class("AC",
    portable = FALSE,
    public = list(
      x = 1,
      initialize = function(x, y) {
        self$x <- getx() + x    # Assign to self; also access a method
        private$y <- y          # Assign to private
      },
      getx = function() x,
      gety = function() private$y
    ),
    private = list(
      y = 2
    )
  )
  A <- AC$new(2, 3)
  expect_identical(A$x, 3)
  expect_identical(A$gety(), 3)

  # No initialize method: throw error if arguments are passed in
  AC <- R6Class("AC", portable = FALSE, public = list(x = 1))
  expect_error(AC$new(3))
})

test_that("empty members and methods are allowed", {
  # No initialize method: throw error if arguments are passed in
  AC <- R6Class("AC", portable = FALSE)
  expect_no_error(AC$new())
})


test_that("Private members are private, and self/private environments", {
  AC <- R6Class("AC",
    portable = FALSE,
    public = list(
      x = 1,
      gety = function() private$y,
      gety2 = function() y,
      getx = function() self$x,
      getx2 = function() x,
      getx3 = function() getx_priv3(),
      getx4 = function() getx_priv4()
    ),
    private = list(
      y = 2,
      getx_priv3 = function() self$x,
      getx_priv4 = function() x
    )
  )
  A <- AC$new()

  # Environment structure
  expect_identical(A$self, A)
  expect_identical(A$private, parent.env(A))

  # Enclosing env for fublic and private methods is the public env
  expect_identical(A, environment(A$getx))
  expect_identical(A, environment(A$private$getx_priv3))

  # Behavioral tests
  expect_identical(A$x, 1)
  expect_null(A$y)
  expect_error(A$getx_priv3())
  expect_identical(A$gety(), 2)  # Explicit access: private$y
  expect_identical(A$gety2(), 2) # Implicit access: y
  expect_identical(A$getx(), 1)  # Explicit access: self$x
  expect_identical(A$getx2(), 1) # Implicit access: x
  expect_identical(A$getx3(), 1) # Call private method, which has explicit: self$x
  expect_identical(A$getx4(), 1) # Call private method, which has implicit: x
})


test_that("Active bindings work", {
  AC <- R6Class("AC",
    portable = FALSE,
    public = list(
      x = 5
    ),
    active = list(
      x2 = function(value) {
        if (missing(value)) return(x * 2)
        else x <<- value/2
      }
    )
  )
  A <- AC$new()

  expect_identical(A$x2, 10)
  A$x <- 20
  expect_identical(A$x2, 40)
  A$x2 <- 60
  expect_identical(A$x2, 60)
  expect_identical(A$x, 30)
})


test_that("Locking objects", {
  AC <- R6Class("AC",
    portable = FALSE,
    public = list(x = 1, getx = function() x),
    private = list(y = 2, gety = function() y),
    lock_objects = TRUE
  )
  A <- AC$new()

  # Can modify fields
  expect_no_error(A$x <- 5)
  expect_identical(A$x, 5)
  expect_no_error(A$private$y <- 5)
  expect_identical(A$private$y, 5)

  # Can't modify methods
  expect_error(A$getx <- function() 1)
  expect_error(A$gety <- function() 2)

  # Can't add members
  expect_error(A$z <- 1)
  expect_error(A$private$z <- 1)


  # Not locked
  AC <- R6Class("AC",
    portable = FALSE,
    public = list(x = 1, getx = function() x),
    private = list(y = 2, gety = function() y),
    lock_objects = FALSE
  )
  A <- AC$new()

  # Can modify fields
  expect_no_error(A$x <- 5)
  expect_identical(A$x, 5)
  expect_no_error(A$private$y <- 5)
  expect_identical(A$private$y, 5)

  # Can't modify methods
  expect_error(A$getx <- function() 1)
  expect_error(A$private$gety <- function() 2)

  # Can add members
  expect_no_error(A$z <- 1)
  expect_identical(A$z, 1)
  expect_no_error(A$private$z <- 1)
  expect_identical(A$private$z, 1)
})


test_that("Validity checks on creation", {
  fun <- function() 1  # Dummy function for tests

  # All arguments must be named
  expect_error(R6Class("AC", public = list(1)))
  expect_error(R6Class("AC", private = list(1)))
  expect_error(R6Class("AC", active = list(fun)))

  # Names can't be duplicated
  expect_error(R6Class("AC", public = list(a=1, a=2)))
  expect_error(R6Class("AC", public = list(a=1), private = list(a=1)))
  expect_error(R6Class("AC", private = list(a=1), active = list(a=fun)))

  # Reserved names
  expect_error(R6Class("AC", public = list(self = 1)))
  expect_error(R6Class("AC", private = list(private = 1)))
  expect_error(R6Class("AC", active = list(super = 1)))

  # `initialize` only allowed in public
  expect_error(R6Class("AC", private = list(initialize = fun)))
  expect_error(R6Class("AC", active = list(initialize = fun)))
})


test_that("default print method has a trailing newline", {
  ## This is kind of hackish, because both capture.output and
  ## expect_output drop the trailing newline. This function
  ## does not work in the general case, but it is good enough
  ## for this test.

  expect_output_n <- function(object) {
    tmp <- tempfile()
    on.exit(unlink(tmp))
    sink(tmp)
    print(object)
    sink(NULL)
    output <- readChar(tmp, nchars = 10000)
    last_char <- substr(output, nchar(output), nchar(output))
    expect_identical(last_char, "\n")
  }

  AC <- R6Class("AC")
  expect_output_n(print(AC))

  A <- AC$new()
  expect_output_n(print(A))

  AC <- R6Class("AC", private = list( x = 2 ))
  expect_output_n(print(AC))

  A <- AC$new()
  expect_output_n(print(A))
})
