tests/testthat/test-clean_url_columns.R

describe("clean_url_columns basic functionality", {
  it("cleans URLs in specified columns", {
    df <- data.frame(
      from = c("http://Example.com/path", "https://Example.com/PATH"),
      to = c("http://www.another.com?q=1#frag", "HTTP://another.com/?q=1&b=2")
    )
    cleaned <- clean_url_columns(df, columns = c("from", "to"))
    expect_equal(nrow(cleaned), 2)
    # rurl normalizes scheme, lowercases the host, preserves path case, adds a
    # trailing slash, and drops the fragment. Contentful params stay in the
    # key under the profile's `query_handling = "filter"`.
    expect_equal(
      cleaned$from,
      c("http://example.com/path", "https://example.com/PATH")
    )
    expect_equal(
      cleaned$to,
      c("http://www.another.com/?q=1", "http://another.com/?q=1&b=2")
    )
  })

  it("handles NA values correctly", {
    df <- data.frame(
      url = c("http://example.com", NA, "http://test.com/page#ref")
    )
    cleaned <- clean_url_columns(df, columns = "url")
    expect_equal(nrow(cleaned), 3)
    # Assuming rurl::get_clean_url adds trailing slash and drops fragment
    expect_equal(
      cleaned$url,
      c("http://example.com/", NA, "http://test.com/page")
    )
  })

  it(paste(
    "applies rurl::get_clean_url parameters via ...",
    "(if supported by the rurl version)"
  ), {
    df <- data.frame(
      link = c(
        "http://www.Example.com/path#fragment",
        "HTTPS://google.com/?q=test"
      )
    )
    # Test default behavior if no extra params passed or if params unsupported
    cleaned_default <- clean_url_columns(df, columns = "link")
    expect_equal(nrow(cleaned_default), 2)
    # Default: lowercase host, preserve path case, normalize scheme, drop
    # the fragment, keep contentful params, add trailing slash
    expect_equal(
      cleaned_default$link,
      c("http://www.example.com/path", "https://google.com/?q=test")
    )
  })

  it("correctly handles custom column names", {
    df <- data.frame(
      source_url = "Http://MySite.com/One",
      target_url = "https://theirsite.com/TWO?param=foo"
    )
    cleaned <- clean_url_columns(df, columns = c("source_url", "target_url"))
    expect_equal(nrow(cleaned), 1)
    # Lowercase host, preserve path case, normalize scheme, keep a
    # contentful param
    expect_equal(cleaned$source_url, "http://mysite.com/One")
    expect_equal(cleaned$target_url, "https://theirsite.com/TWO?param=foo")
  })

  it("processes only specified columns", {
    withr::with_options(list(pagerankr.verbose = FALSE), {
      df <- data.frame(
        col_to_clean = "HTTP://Domain.com/Page",
        col_to_ignore = "HTTP://AnotherDomain.com/Path?val=1"
      )
      cleaned <- clean_url_columns(df, columns = "col_to_clean")
      expect_equal(nrow(cleaned), 1)
      expect_equal(cleaned$col_to_clean, "http://domain.com/Page") # Cleaned
      # Ignored, as is
      expect_equal(
        cleaned$col_to_ignore,
        "HTTP://AnotherDomain.com/Path?val=1"
      )
    })
  })

  it("handles empty data frame", {
    df_empty <- data.frame(from = character(0), to = character(0))
    cleaned <- clean_url_columns(df_empty, columns = c("from", "to"))
    expect_equal(nrow(cleaned), 0)
    expect_named(cleaned, c("from", "to"))
  })

  it("handles data frame with specified columns but no rows", {
    df_no_rows <- data.frame(link = character(0))
    cleaned <- clean_url_columns(df_no_rows, columns = "link")
    expect_equal(nrow(cleaned), 0)
    expect_named(cleaned, "link")
  })

  it(paste(
    "handles columns not specified if default",
    "c('from', 'to') are not present"
  ), {
    df_no_default_cols <- data.frame(
      link_source = "HTTP://EXAMPLE.com/",
      link_target = "example.net/resource?id=2"
    )
    # If 'columns' is not specified and 'from'/'to' do not exist, it should
    # do nothing or warn. Current behavior with no default cols and no
    # 'columns' param: it tries to find 'from' and 'to', fails, and returns
    # the original df with a warning. If 'columns' IS specified, it uses those.
    cleaned <- clean_url_columns(df_no_default_cols, columns = "link_source")
    expect_equal(cleaned$link_source, "http://example.com/")
    # Unchanged
    expect_equal(cleaned$link_target, "example.net/resource?id=2")
  })

  it("errors if specified column does not exist", {
    df <- data.frame(a = "http://example.com")
    expect_error(clean_url_columns(df, columns = "non_existent_col"))
  })
})

describe("clean_url_columns memoization (conceptual)", {
  # These tests are conceptual as true memoization testing requires
  # inspecting the cache or observing performance, which is harder in unit
  # tests. We test for consistent output, which is a prerequisite.

  it(paste(
    "produces consistent results for identical inputs",
    "implying memoization effectiveness"
  ), {
    df_repeated <- data.frame(
      urls = rep(
        c(
          "HTTPS://Example.Com/Page?utm_source=1#Frag",
          "http://sub.example.com/another%20path"
        ),
        2
      )
    )
    # Test with default behavior (no extra params)
    cleaned_1 <- clean_url_columns(df_repeated, columns = "urls")
    # Fragment and tracking param dropped, host lowercased, path case kept
    expect_equal(cleaned_1$urls[1], "https://example.com/Page")
    # Path case preserved; under the pinned profile (`path_encoding = "keep"`,
    # `url_standard = "whatwg"`) the percent-encoded space is preserved as
    # written rather than decoded into a literal space.
    expect_equal(cleaned_1$urls[2], "http://sub.example.com/another%20path")
    expect_equal(cleaned_1$urls[3], cleaned_1$urls[1])
    expect_equal(cleaned_1$urls[4], cleaned_1$urls[2])

    # Test attempt with an argument that rurl might not support or pass
    # through. If drop_fragments=FALSE is not supported, the output should be
    # the same as default (fragment dropped). The "unused argument" error
    # confirms it is not used by rurl::get_clean_url in this context, so we
    # test that the function still runs and produces a default-like output.
    cleaned_2_default_behavior <- clean_url_columns(
      df_repeated,
      columns = "urls"
    )
    expect_equal(cleaned_2_default_behavior$urls[1], "https://example.com/Page")
    # This test previously failed because it passed an unsupported argument.
    # By removing the argument, we test default behavior which should pass.
  })
})

describe("clean_url_columns unparseable-token handling", {
  it("preserves dotless bare tokens rurl cannot parse as URLs", {
    # Newer rurl normalizes dotless labels (e.g. "A") to NA. clean_url_columns
    # must keep them as opaque node identities rather than dropping them.
    df <- data.frame(from = c("A", "B"), to = c("B", "A"))
    cleaned <- clean_url_columns(df, columns = c("from", "to"))
    expect_equal(cleaned$from, c("A", "B"))
    expect_equal(cleaned$to, c("B", "A"))
  })

  it("cleans real URLs while preserving unparseable tokens together", {
    df <- data.frame(url = c("A", "Http://Example.COM/x", NA))
    cleaned <- clean_url_columns(df, columns = "url")
    expect_equal(cleaned$url[1], "A")
    expect_equal(cleaned$url[2], "http://example.com/x")
    expect_true(is.na(cleaned$url[3]))
  })

  it("keeps pagerank() scoring a graph built from bare labels", {
    edges <- data.frame(from = c("A", "B"), to = c("B", "A"))
    pr <- pagerank(edges)
    expect_equal(nrow(pr), 2)
    expect_equal(sum(pr$pagerank), 1, tolerance = 1e-9)
  })
})

describe("clean_url_columns validation coverage", {
  it("errors when input is not a data frame", {
    expect_error(clean_url_columns("not_a_df"), "data frame")
  })

  it("errors when specified columns are missing", {
    df <- data.frame(x = "http://example.com")
    expect_error(
      clean_url_columns(df, columns = c("from", "to")),
      "not found"
    )
  })

  it("errors when columns is not a character vector even if values match", {
    df <- data.frame(
      from = "http://example.com", to = "http://b.com"
    )
    # Factor with matching values -> triggers the type guard
    expect_error(
      clean_url_columns(df, columns = factor("from")),
      "character vector"
    )
  })
})

Try the pagerankr package in your browser

Any scripts or data that you put into this service are public.

pagerankr documentation built on Oct. 1, 2026, 5:09 p.m.