<NME> htmlwriter.jl
<BEF> module HTMLWriterTests

using Test

import Documenter.Writers.HTMLWriter: jsescape, generate_version_file

@testset "HTMLWriter" begin
    @test jsescape("abc123") == "abc123"
function verify_redirect_file(redirectfile, version)
    @test isfile(redirectfile)
    content = read(redirectfile, String)

    @test occursin("url=./$(version)/", content)
end

@testset "HTMLWriter" begin
    @test isdir(HTMLWriter.ASSETS)
    @test isdir(HTMLWriter.ASSETS_SASS)
    @test isdir(HTMLWriter.ASSETS_THEMES)

    @test jsescape("policy to  delete.") == "policy to\\u2028 delete."

    mktempdir() do tmpdir
        versions = ["stable", "dev", "v0.2.6", "v0.1.1", "v0.1.0"]
        cd(tmpdir) do
            mkdir("foobar")
            for version in versions
                mkdir(version)
            end
        end

        generate_version_file(tmpdir)

        versions_file = joinpath(tmpdir, "versions.js")
        @test isfile(versions_file)
        contents = String(read(versions_file))
        @test !occursin("foobar", contents) # only specific directories end up in the versions file
        # let's make sure they're in the right order -- they should be sorted in the output file
        last = 0:0
        for version in versions
            this = findfirst(version, contents)
            @test this !== nothing
            @test first(last) < first(this)
            last = this
        end
    end
end

        link = assetlink("my/sub/page", asset)
        @test link.node.name === :script
        # This actually leads to different results on Windows and Linux (on the former, it
        # gets treated as an absolute path).
        if Sys.iswindows()
            @test endswith(link.src, "example.com/foo.js")
        else
            @test link.src == "../../https:/example.com/foo.js"
        end

    end
    @test_logs (:error, "Local asset should not have an absolute URI: /foo/bar.ico") asset("/foo/bar.ico", islocal = true)

    let asset = asset("https://plausible.io/js/plausible.js"; class=:js, attributes=Dict(Symbol("data-domain")=>"example.com", :defer=>""))
        @test asset.uri == "https://plausible.io/js/plausible.js"
        @test asset.class == :js
        @test asset.islocal === false
        link = assetlink("my/sub/page", asset)
        @test link.node.name === :script
        @test link.src == "https://plausible.io/js/plausible.js"
        @test Base.getproperty(link, Symbol("data-domain")) == "example.com"
        @test link.defer == ""
    end

    # HTML format object
    @test Documenter.HTML() isa Documenter.HTML
    @test_throws ArgumentError Documenter.HTML(collapselevel=-200)
    @test_throws Exception Documenter.HTML(assets=["foo.js", 10])
    @test_throws ArgumentError Documenter.HTML(footer="foo\n\nbar")
    @test_throws ArgumentError Documenter.HTML(footer="# foo")
    @test_throws ArgumentError Documenter.HTML(footer="")
    @test Documenter.HTML(footer="foo bar [baz](https://github.com)") isa Documenter.HTML
    @test_throws ErrorException Documenter.HTML(edit_branch = nothing, edit_link=nothing)

    # MathEngine
    let katex = KaTeX()
        @test length(katex.config) == 1
        @test haskey(katex.config, :delimiters)
    end
    let katex = KaTeX(Dict(:foo => 1))
        @test length(katex.config) == 2
        @test haskey(katex.config, :delimiters)
        @test haskey(katex.config, :foo)
    end
    let katex = KaTeX(Dict(:delimiters => 1, :foo => 2))
        @test length(katex.config) == 2
        @test haskey(katex.config, :delimiters)
        @test katex.config[:delimiters] == 1
        @test haskey(katex.config, :foo)
    end

    let mathjax = MathJax2()
        @test length(mathjax.config) == 5
        @test haskey(mathjax.config, :tex2jax)
        @test haskey(mathjax.config, :config)
        @test haskey(mathjax.config, :jax)
        @test haskey(mathjax.config, :extensions)
        @test haskey(mathjax.config, :TeX)
    end
    let mathjax = MathJax2(Dict(:foo => 1))
        @test length(mathjax.config) == 6
        @test haskey(mathjax.config, :tex2jax)
        @test haskey(mathjax.config, :config)
        @test haskey(mathjax.config, :jax)
        @test haskey(mathjax.config, :extensions)
        @test haskey(mathjax.config, :TeX)
        @test haskey(mathjax.config, :foo)
    end
    let mathjax = MathJax2(Dict(:tex2jax => 1, :foo => 2))
        @test length(mathjax.config) == 6
        @test haskey(mathjax.config, :tex2jax)
        @test haskey(mathjax.config, :config)
        @test haskey(mathjax.config, :jax)
        @test haskey(mathjax.config, :extensions)
        @test haskey(mathjax.config, :TeX)
        @test haskey(mathjax.config, :foo)
        @test mathjax.config[:tex2jax] == 1
    end

    mktempdir() do tmpdir
        versionfile = joinpath(tmpdir, "versions.js")
        redirectfile = joinpath(tmpdir, "index.html")
        devurl = "dev"
        versions = ["stable", "dev",
                    "2.1.1", "v2.1.0", "v2.0.1", "v2.0.0",
                    "1.1.1", "v1.1.0", "v1.0.1", "v1.0.0",
                    "0.1.1", "v0.1.0"] # note no `v` on first ones

        # make dummy directories of versioned docs
        cd(tmpdir) do
            for version in versions
                mkdir(version)
            end
        end

        # case1: default versioning
        versions = ["stable" => "v^", "v#.#", "dev" => "dev"] # default to makedocs
        entries, symlinks = expand_versions(tmpdir, versions)
        @test entries == ["stable", "v2.1", "v2.0", "v1.1", "v1.0", "v0.1", "dev"]
        @test symlinks == ["stable"=>"2.1.1", "v2.1"=>"2.1.1", "v2.0"=>"v2.0.1",
                           "v1.1"=>"1.1.1", "v1.0"=>"v1.0.1", "v0.1"=>"0.1.1",
                           "v2"=>"2.1.1", "v1"=>"1.1.1", "v2.1.1"=>"2.1.1",
                           "v1.1.1"=>"1.1.1", "v0.1.1"=>"0.1.1"]
        generate_version_file(versionfile, entries)
        verify_version_file(versionfile, entries)
        generate_redirect_file(redirectfile, entries)
        verify_redirect_file(redirectfile, "stable")

        # case2: major released versions
        versions = ["v#"]
        entries, symlinks = expand_versions(tmpdir, versions)
        @test entries == ["v2.1", "v1.1"]
        @test symlinks == ["v2.1"=>"2.1.1", "v1.1"=>"1.1.1", "v2"=>"2.1.1", "v1"=>"1.1.1",
                           "v2.0"=>"v2.0.1", "v1.0"=>"v1.0.1", "v0.1"=>"0.1.1",
                           "v2.1.1"=>"2.1.1", "v1.1.1"=>"1.1.1", "v0.1.1"=>"0.1.1"]
        generate_version_file(versionfile, entries)
        verify_version_file(versionfile, entries)
        generate_redirect_file(redirectfile, entries)
        verify_redirect_file(redirectfile, "v2.1")

        # case3: all released versions
        versions = ["v#.#.#"]
        entries, symlinks = expand_versions(tmpdir, versions)
        @test entries == ["v2.1.1", "v2.1.0", "v2.0.1", "v2.0.0", "v1.1.1", "v1.1.0",
                          "v1.0.1", "v1.0.0", "v0.1.1", "v0.1.0"]
        @test symlinks == ["v2.1.1"=>"2.1.1", "v1.1.1"=>"1.1.1", "v0.1.1"=>"0.1.1",
                           "v2"=>"2.1.1", "v1"=>"1.1.1", "v2.1"=>"2.1.1",
                           "v2.0"=>"v2.0.1", "v1.1"=>"1.1.1", "v1.0"=>"v1.0.1", "v0.1"=>"0.1.1"]
        generate_version_file(versionfile, entries)
        verify_version_file(versionfile, entries)
        generate_redirect_file(redirectfile, entries)
        verify_redirect_file(redirectfile, "v2.1.1")

        # case4: invalid versioning
        versions = ["v^", "devel" => "dev", "foobar", "foo" => "bar"]
        entries, symlinks = @test_logs(
            (:warn, "no match for `versions` entry `\"foobar\"`"),
            (:warn, "no match for `versions` entry `\"foo\" => \"bar\"`"),
            expand_versions(tmpdir, versions)
        )
        @test entries == ["v2.1", "devel"]
        @test ("v2.1" => "2.1.1") in symlinks
        @test ("devel" => "dev") in symlinks
        generate_version_file(versionfile, entries)
        verify_version_file(versionfile, entries)
        generate_redirect_file(redirectfile, entries)
        verify_redirect_file(redirectfile, "v2.1")

        # case5: invalid versioning
        versions = ["stable" => "v^", "dev" => "stable"]
        @test_throws ArgumentError expand_versions(tmpdir, versions)

        # case6: default versioning (no released version)
        cd(tmpdir) do
            # remove dummy directories
            for dir in readdir(tmpdir)
                rm(dir)
            end
            mkdir("dev")
        end
        versions = ["stable" => "v^", "v#.#", "dev" => "dev"] # default to makedocs
        entries, symlinks = expand_versions(tmpdir, versions)
        @test entries == ["dev"]
        @test symlinks == []
        generate_version_file(versionfile, entries)
        verify_version_file(versionfile, entries)
        generate_redirect_file(redirectfile, entries)
        verify_redirect_file(redirectfile, "dev")

        # Case 7: no entries
        entries = String[]
        generate_version_file(versionfile, entries)
        verify_version_file(versionfile, entries)
        rm(redirectfile)
        generate_redirect_file(redirectfile, entries)
        @test !isfile(redirectfile)
    end
end
end

<MSG> Add `versions` argument to deploydocs. The `versions` argument decide (#813)

the content, and order, of the version selector. The argument is
a vector, defaulting to ["stable" => "v^", "v#.#", devurl => devurl].

Arguments to the selector can be:
 - "v^": expands to the latest release.
 - "v#": expands to latest doc per major release.
 - "v#.#": expands to latest doc per minor release.
 - "v#.#.#": expands to latest doc per patch release.
 - A pair p, which put p.first in the selector, and symlink it to p.second.
<DFF> @@ -2,7 +2,18 @@ module HTMLWriterTests
 
 using Test
 
-import Documenter.Writers.HTMLWriter: jsescape, generate_version_file
+import Documenter.Writers.HTMLWriter: jsescape, generate_version_file, expand_versions
+
+function verify_version_file(versionfile, entries)
+    @test isfile(versionfile)
+    content = read(versionfile, String)
+    idx = 1
+    for entry in entries
+        i = findnext(entry, content, idx)
+        @test i !== nothing
+        idx = last(i)
+    end
+end
 
 @testset "HTMLWriter" begin
     @test jsescape("abc123") == "abc123"
@@ -21,28 +32,54 @@ import Documenter.Writers.HTMLWriter: jsescape, generate_version_file
     @test jsescape("policy to  delete.") == "policy to\\u2028 delete."
 
     mktempdir() do tmpdir
-        versions = ["stable", "dev", "v0.2.6", "v0.1.1", "v0.1.0"]
+        versionfile = joinpath(tmpdir, "versions.js")
+        versions = ["stable", "dev",
+                    "2.1.1", "v2.1.0", "v2.0.1", "v2.0.0",
+                    "1.1.1", "v1.1.0", "v1.0.1", "v1.0.0",
+                    "0.1.1", "v0.1.0"] # note no `v` on first ones
         cd(tmpdir) do
-            mkdir("foobar")
             for version in versions
                 mkdir(version)
             end
         end
 
-        generate_version_file(tmpdir)
-
-        versions_file = joinpath(tmpdir, "versions.js")
-        @test isfile(versions_file)
-        contents = String(read(versions_file))
-        @test !occursin("foobar", contents) # only specific directories end up in the versions file
-        # let's make sure they're in the right order -- they should be sorted in the output file
-        last = 0:0
-        for version in versions
-            this = findfirst(version, contents)
-            @test this !== nothing
-            @test first(last) < first(this)
-            last = this
-        end
+        # expanding versions
+        versions = ["stable" => "v^", "v#.#", "dev" => "dev"] # default to makedocs
+        entries, symlinks = expand_versions(tmpdir, versions)
+        @test entries == ["stable", "v2.1", "v2.0", "v1.1", "v1.0", "v0.1", "dev"]
+        @test symlinks == ["stable"=>"2.1.1", "v2.1"=>"2.1.1", "v2.0"=>"v2.0.1",
+                           "v1.1"=>"1.1.1", "v1.0"=>"v1.0.1", "v0.1"=>"0.1.1",
+                           "v2"=>"2.1.1", "v1"=>"1.1.1", "v2.1.1"=>"2.1.1",
+                           "v1.1.1"=>"1.1.1", "v0.1.1"=>"0.1.1"]
+        generate_version_file(versionfile, entries)
+        verify_version_file(versionfile, entries)
+
+        versions = ["v#"]
+        entries, symlinks = expand_versions(tmpdir, versions)
+        @test entries == ["v2.1", "v1.1"]
+        @test symlinks == ["v2.1"=>"2.1.1", "v1.1"=>"1.1.1", "v2"=>"2.1.1", "v1"=>"1.1.1",
+                           "v2.0"=>"v2.0.1", "v1.0"=>"v1.0.1", "v0.1"=>"0.1.1",
+                           "v2.1.1"=>"2.1.1", "v1.1.1"=>"1.1.1", "v0.1.1"=>"0.1.1"]
+        generate_version_file(versionfile, entries)
+        verify_version_file(versionfile, entries)
+
+        versions = ["v#.#.#"]
+        entries, symlinks = expand_versions(tmpdir, versions)
+        @test entries == ["v2.1.1", "v2.1.0", "v2.0.1", "v2.0.0", "v1.1.1", "v1.1.0",
+                          "v1.0.1", "v1.0.0", "v0.1.1", "v0.1.0"]
+        @test symlinks == ["v2.1.1"=>"2.1.1", "v1.1.1"=>"1.1.1", "v0.1.1"=>"0.1.1",
+                           "v2"=>"2.1.1", "v1"=>"1.1.1", "v2.1"=>"2.1.1",
+                           "v2.0"=>"v2.0.1", "v1.1"=>"1.1.1", "v1.0"=>"v1.0.1", "v0.1"=>"0.1.1"]
+        generate_version_file(versionfile, entries)
+        verify_version_file(versionfile, entries)
+
+        versions = ["v^", "devel" => "dev", "foobar", "foo" => "bar"]
+        entries, symlinks = expand_versions(tmpdir, versions)
+        @test entries == ["v2.1", "devel"]
+        @test ("v2.1" => "2.1.1") in symlinks
+        @test ("devel" => "dev") in symlinks
+        generate_version_file(versionfile, entries)
+        verify_version_file(versionfile, entries)
     end
 end
 

Add `versions` argument to deploydocs. The `versions` argument decide (#813)