context('packages')

# Helper function to check for existence of an attached name (which is expected
# to be found in the parent environment of the calling environment).
local({
    pls = function () {
        ls(envir = parent.env(parent.frame()))
    }
}, .GlobalEnv)

test_that('R core packages can be used', {
    expect_not_in('methods', ls())
    box::use(methods)
    expect_true(isNamespaceLoaded('methods'))
    expect_in('methods', ls())
    expect_in('as', ls(methods))
    expect_identical(methods$as, methods::as)
})

# Use ‘devtools’ as a test package since it’s suggested by testthat and required
# for ‘box’/ development, so highly likely to be installed, but hopefully not
# loaded.

test_that('previously loaded user packages can be used', {
    loadNamespace('devtools')
    expect_not_in('devtools', ls())
    expect_true(isNamespaceLoaded('devtools'))
    box::use(devtools)
    expect_in('devtools', ls())
    expect_in('load_all', ls(devtools))
    expect_identical(devtools$load_all, devtools::load_all)
})

test_that('unloaded user packages can be used', {
    unloadNamespace('devtools')
    expect_not_in('devtools', ls())
    expect_false(isNamespaceLoaded('devtools'))
    box::use(devtools)
    expect_true(isNamespaceLoaded('devtools'))
    expect_in('devtools', ls())
    expect_in('load_all', ls(devtools))
    expect_identical(devtools$load_all, devtools::load_all)
})

test_that('packages can be aliased', {
    expect_not_in('methods', ls())
    expect_not_in('m', ls())
    box::use(m = methods)
    expect_not_in('methods', ls())
    expect_in('m', ls())
    expect_in('as', ls(m))
    expect_identical(m$as, methods::as)
})

test_that('packages can be used with aliases', {
    expect_not_in('dev', ls())
    box::use(dev = devtools)
    expect_in('dev', ls())
    expect_identical(dev$load_all, devtools::load_all)
})

test_that('things can be attached locally', {
    expect_not_in('load_all', pls())
    box::use(devtools[load_all])
    expect_in('load_all', pls())

    expect_not_in('unload', pls())
    expect_not_in('reload', pls())
    box::use(devtools[unload, reload])
    expect_in('unload', pls())
    expect_in('reload', pls())
})

test_that('all things can be attached locally', {
    devtools_exports = getNamespaceExports('devtools')
    expect_gt(length(devtools_exports), 0L) # Sanity check …
    box::use(devtools[...])
    expect_false(any(is.na(match(pls(), devtools_exports))))
})

test_that('things can be attached globally', {
    # Custom assertions are not attached, and are made available differently in
    # `devtools::test()` and `R CMD check`. In particular, the latter doesn’t
    # find them in the ‘box’ namespace either. That’s why the following code
    # computes all values in individual .GlobalEnv wrappers, rather than
    # wrapping the whole unit test in `in_globalenv`.

    objs = ls(envir = .GlobalEnv)
    on.exit(rm(list = setdiff(ls(envir = .GlobalEnv), objs), envir = .GlobalEnv))

    gpls = function () local(pls(), envir = .GlobalEnv)

    expect_not_in('load_all', gpls())
    local(box::use(devtools[load_all]), envir = .GlobalEnv)
    expect_in('load_all', gpls())
    expect_not_in('unload', gpls())
    expect_not_in('reload', gpls())
    local(box::use(devtools[unload, reload]), envir = .GlobalEnv)
    expect_in('unload', gpls())
    expect_in('reload', gpls())
})

test_that('all things can be attached globally', {
    in_globalenv({
        devtools_exports = getNamespaceExports('devtools')
        expect_gt(length(devtools_exports), 0L) # Sanity check …
        box::use(devtools[...])
        expect_false(any(is.na(match(pls(), devtools_exports))))
    })
})

test_that('attachments can be aliased', {
    expect_not_in('u', pls())
    expect_not_in('reload', pls())
    expect_not_in('la', pls())
    box::use(devtools[u = unload, reload, la = load_all])
    expect_in('u', pls())
    expect_in('reload', pls())
    expect_in('la', pls())
    expect_not_in('unload', pls())
    expect_not_in('load_all', pls())
})

test_that('wildcard attachments can contain aliases', {
    # Attach everything, and give some names aliases
    devtools_exports = getNamespaceExports('devtools')
    expected = c(
        setdiff(devtools_exports, c('test', 'r_env_vars')),
        'test_alias', 'ev'
    )
    box::use(devtools[..., test_alias = test, ev = r_env_vars])
    expect_equal(length(pls()), length(expected))
    expect_false(any(is.na(match(pls(), expected))))
})

test_that('non-existent aliases raise error', {
    expect_box_error(box::use(devtools[foobar123]))
    expect_box_error(box::use(devtools[test = foobar123]))
    expect_box_error(box::use(devtools[..., test = foobar123]))
})

test_that('only exported things can be attached', {
    expect_in('yesno', ls(getNamespace('devtools')))
    expect_box_error(box::use(devtools[yesno]), 'not exported')
})

test_that('packages that attach things are not aliased', {
    box::use(devtools[load_all])
    expect_not_in('devtools', ls())
    expect_in('load_all', pls())
})

test_that('packages that attach things are can be aliased', {
    expect_not_in('dev', ls())
    box::use(dev = devtools[load_all])
    expect_in('dev', ls())
    expect_in('load_all', pls())
})

test_that('S3 lookup works for partial exports', {
    # ‘devtools’ has no S3 exports at the time of writing but ‘roxygen2’ does,
    # and is also needed to compile this package — and hence likely available
    # when running these tests.
    box::use(roxygen2[parse = roxy_tag_parse])
    x = structure(list(raw = '{}'), class = 'roxy_tag_usage')
    actual = parse(x)
    expected = roxygen2::roxy_tag_parse(x)
    expect_equal(actual, expected)
})

test_that('packages with lazydata can be loaded', {
    # Data inside ‘datasets’ is available via `get`.
    expect_error(box::use(datasets), NA)
    expect_error(get('mtcars', asNamespace('datasets')), NA)

    # ‘stringr’ uses lazydata; `get` doesn’t work.
    expect_error(box::use(stringr), NA)
    expect_error(get('fruit', asNamespace('stringr')))
    expect_in('fruit', ls(stringr))
    expect_type(stringr$fruit, 'character')

    # Test that attach lists work
    expect_error(box::use(stringr[ff = fruit, ...]), NA)
    expect_equal(ff, stringr$fruit)
    expect_equal(words, stringr$words)
})
