context('reloading')

is_module_loaded = function (path) {
    path %in% names(box:::loaded_mods)
}

tempfile_dir = function (...) {
    file = tempfile()
    dir.create(file)
    file
}

create_nested_test_module = function (dir) {
    mod = file.path(dir, 'mod', 'a')
    dir.create(mod, recursive = TRUE)
    writeLines("#' @export\nbox::use(./sub)", file.path(mod, '__init__.r'))
    writeLines("#' @export\nvalue = 1L", file.path(mod, 'sub.r'))
}

edit_nested_test_module = function (dir) {
    mod = file.path(dir, 'mod', 'a')
    writeLines("#' @export\nvalue = 2L", file.path(mod, 'sub.r'))
}

test_that('module can be reloaded', {
    # Required since other tests have side-effects.
    # Tear-down would be helpful here, but not supported by testthat.
    box::purge_cache()

    box::use(mod/a)
    expect_equal(length(box:::loaded_mods), 1L)
    counter = a$get_counter()
    a$inc()
    expect_equal(a$get_counter(), counter + 1L)

    box::reload(a)
    expect_true(is_module_loaded(box:::path(a)))
    expect_length(box:::loaded_mods, 1L)
    expect_equal(a$get_counter(), counter)
})

test_that('reload checks its arguments', {
    expect_error(box::reload(123))
    expect_error(box::reload(foo))
    box::use(mod/a)
    expect_error(box::reload((a)))
})

test_that('reload includes module dependencies', {
    # This test case actually edits a dependency and reloads the edit. The
    # purpose of this is to ensure that reloading doesn’t merely call `.on_load`
    # again, but actually does reload the changes from disk.
    dir = tempfile_dir()
    on.exit(unlink(dir, recursive = TRUE))

    old_path = options(box.path = dir)
    on.exit(options(old_path), add = TRUE)

    old_env = Sys.getenv('R_BOX_PATH', NA)
    if (!is.na(old_env)) {
        Sys.unsetenv('R_BOX_PATH')
        on.exit(Sys.setenv(R_BOX_PATH = old_env), add = TRUE)
    }

    create_nested_test_module(dir)

    box::use(mod/a)

    expect_equal(a$sub$value, 1L)

    edit_nested_test_module(dir)

    box::reload(a)

    expect_equal(a$sub$value, 2L)

    # To do:
    # * modules with compiled source,
    # * tricky packages loaded as modules, e.g. packages that call
    #   system.file(), and alike, and
    # * modules with S4 classes/object,
})

test_that('reload includes transitive dependencies', {
    # Unlike in the previous test, this test uses `.on_load` as an indicator of
    # reloading, to keep things simpler.
    box::use(mod/reload/a)
    expect_messages(
        box::reload(a),
        has = c('^a unloaded', '^c unloaded', '^c loaded')
    )
})

test_that('reload of transitive imports skips packages', {
    box::use(mod/reload/pkg)
    expect_error(box::reload(pkg), NA)
})

test_that('`reload` shows expected errors', {
    old_opts = options(useFancyQuotes = FALSE)
    on.exit(options(old_opts))

    expect_box_error(
        box::reload(mod/a),
        '"reload" expects a module object, got "mod/a"'
    )
    expect_box_error(
        box::reload(./a),
        '"reload" expects a module object, got "./a"'
    )
    expect_box_error(box::reload(na), 'object "na" not found')

    x = 1L
    expect_box_error(
        box::reload(x),
        '"reload" expects a module object, got "x", which is of type "integer" instead'
    )
})

test_that('reloading a module used twice does not cause a warning', {
    box::use(mod/reload/nested/c)

    expect_warning(box::reload(c), NA)
})
