diff --git a/Project.toml b/Project.toml
index 2775d4a..e8b9800 100644
--- a/Project.toml
+++ b/Project.toml
@@ -1,6 +1,6 @@
 name = "DataGraphs"
 uuid = "b5a273c3-7e6c-41f6-98bd-8d7f1525a36a"
-version = "0.4.2"
+version = "0.4.3"
 authors = ["Matthew Fishman <mfishman@flatironinstitute.org> and contributors"]
 
 [workspace]
diff --git a/src/DataGraphs.jl b/src/DataGraphs.jl
index 6f93972..e201e5e 100644
--- a/src/DataGraphs.jl
+++ b/src/DataGraphs.jl
@@ -6,6 +6,9 @@ include("dataview.jl")
 include("abstractdatagraph.jl")
 include("indexing.jl")
 include("datagraph.jl")
+include("abstractedgeorvertexdatagraph.jl")
+include("vertexdatagraph.jl")
+include("edgedatagraph.jl")
 # TODO: Turn into an extension once `PartitionedGraphs` is excised.
 include("lib/DataGraphsPartitionedGraphsExt/src/DataGraphsPartitionedGraphsExt.jl")
 
diff --git a/src/abstractdatagraph.jl b/src/abstractdatagraph.jl
index 2fefa68..d1c87c6 100644
--- a/src/abstractdatagraph.jl
+++ b/src/abstractdatagraph.jl
@@ -6,7 +6,8 @@ using NamedGraphs.GraphsExtensions: GraphsExtensions, add_edges!, add_vertices!,
 using NamedGraphs.OrdinalIndexing: OrdinalSuffixedInteger
 using NamedGraphs.SimilarType: similar_type
 using NamedGraphs: NamedGraphs, AbstractEdges, AbstractNamedEdge, AbstractNamedGraph,
-    AbstractVertices, NamedDiGraph, NamedGraph, position_graph_type, similar_graph
+    AbstractVertices, NamedDiGraph, NamedGraph, Vertices, position_graph_type,
+    similar_graph, subgraph_edges
 using SimpleTraits: SimpleTraits, @traitfn, Not
 
 abstract type AbstractDataGraph{V, VD, ED} <: AbstractNamedGraph{V} end
@@ -21,12 +22,15 @@ edge_data_type(::Type{<:AbstractDataGraph{V, VD, ED}}) where {V, VD, ED} = ED
 # TODO: Define for `AbstractGraph` as a `DataGraphInterface`.
 underlying_graph(::AbstractDataGraph) = not_implemented()
 
+# `isassigned`
 is_vertex_assigned(::AbstractDataGraph, vertex) = not_implemented()
 is_edge_assigned(::AbstractDataGraph, edge) = not_implemented()
 
+# `getindex`
 get_vertex_data(::AbstractDataGraph, vertex) = not_implemented()
 get_edge_data(::AbstractDataGraph, edge) = not_implemented()
 
+# `setindex!`
 set_vertex_data!(::AbstractDataGraph, data, vertex) = not_implemented()
 set_edge_data!(::AbstractDataGraph, data, edge) = not_implemented()
 
@@ -41,12 +45,6 @@ function get_edges_data(g::AbstractGraph, edges)
     return map(e -> getindex(g, e), Indices(edges))
 end
 
-Graphs.has_vertex(g::AbstractDataGraph, vertex) = has_vertex(underlying_graph(g), vertex)
-Graphs.has_edge(g::AbstractDataGraph, edge) = has_edge(underlying_graph(g), edge)
-function Graphs.has_edge(g::AbstractDataGraph, edge::AbstractNamedEdge)
-    return has_edge(underlying_graph(g), edge)
-end
-
 vertex_data(dg::AbstractGraph) = VertexDataView(dg)
 edge_data(dg::AbstractGraph) = EdgeDataView(dg)
 
@@ -57,9 +55,7 @@ function assigned_edges(graph::AbstractGraph)
     return Indices(filter(e -> isassigned(graph, e), edges(graph)))
 end
 
-function Graphs.edgetype(graph::AbstractDataGraph)
-    return Graphs.edgetype(underlying_graph(graph))
-end
+Graphs.edgetype(graph::AbstractDataGraph) = edgetype(underlying_graph(graph))
 function Graphs.edgetype(graph_type::Type{<:AbstractDataGraph})
     return edgetype(underlying_graph_type(graph_type))
 end
@@ -75,9 +71,38 @@ function NamedGraphs.position_graph_type(type::Type{<:AbstractDataGraph})
     return position_graph_type(underlying_graph_type(type))
 end
 
-function Base.copyto!(dst_graph::AbstractDataGraph, src_graph::AbstractDataGraph)
-    vertex_data(dst_graph) .= vertex_data(src_graph)
-    edge_data(dst_graph) .= edge_data(src_graph)
+function Base.copy(graph::AbstractDataGraph)
+    copy_graph = similar_graph(graph)
+    # Allow copies of graphs with undefined data.
+    copyto!(copy_graph, graph, assigned_vertices(graph))
+    copyto!(copy_graph, graph, assigned_edges(graph))
+    return copy_graph
+end
+
+# Base method to specialize on.
+function Base.copyto!(graph_dst::AbstractDataGraph, src, keys)
+    for key in keys
+        graph_dst[key] = src[key]
+    end
+    return graph_dst
+end
+
+function Base.copyto!(graph_dst::AbstractDataGraph, src)
+    copyto!_datagraph(graph_dst, src)
+    return graph_dst
+end
+
+# To prevent method ambiguities
+function copyto!_datagraph(dst::AbstractDataGraph, src::AbstractDataGraph)
+    if !issetequal(vertices(dst), vertices(src))
+        throw(ArgumentError("destination and source graphs must have the same vertices"))
+    end
+    copyto!(dst, src, vertices(src))
+    copyto!(dst, src, edges(src))
+    return dst
+end
+function copyto!_datagraph(dst_graph::AbstractDataGraph, src)
+    copyto!(dst_graph, src, keys(src))
     return dst_graph
 end
 
@@ -110,8 +135,8 @@ GraphsExtensions.convert_vertextype(::Type, ::AbstractDataGraph) = not_implement
 
 function Base.:(==)(dg1::AbstractDataGraph, dg2::AbstractDataGraph)
     underlying_graph(dg1) == underlying_graph(dg2) || return false
-    vertex_data(dg1) == vertex_data(dg2) || return false
-    edge_data(dg1) == edge_data(dg2) || return false
+    assigned_vertex_data(dg1) == assigned_vertex_data(dg2) || return false
+    assigned_edge_data(dg1) == assigned_edge_data(dg2) || return false
     return true
 end
 
@@ -426,3 +451,8 @@ function Base.show(io::IO, mime::MIME"text/plain", graph::AbstractDataGraph)
 end
 
 Base.show(io::IO, graph::AbstractDataGraph) = show(io, MIME"text/plain"(), graph)
+
+function GraphsExtensions.forest_cover_edge_sequence(graph::AbstractDataGraph; kwargs...)
+    dummy_graph = add_edges!(NamedGraph(vertices(graph)), edges(graph))
+    return GraphsExtensions.forest_cover_edge_sequence(dummy_graph; kwargs...)
+end
diff --git a/src/abstractedgeorvertexdatagraph.jl b/src/abstractedgeorvertexdatagraph.jl
new file mode 100644
index 0000000..c48a8e5
--- /dev/null
+++ b/src/abstractedgeorvertexdatagraph.jl
@@ -0,0 +1,289 @@
+using Dictionaries: Dictionaries, Indices, isinsertable, set!
+using Graphs: edges, edgetype, has_edge, has_vertex, rem_edge!, rem_vertex!, vertices
+using NamedGraphs: NamedGraphs, Vertices, similar_graph, subgraph_edges, to_graph_index
+
+abstract type AbstractVertexOrEdgeDataGraph{T, V} <: AbstractDataGraph{V, T, T} end
+
+Graphs.edgetype(graph::AbstractVertexOrEdgeDataGraph) = edgetype(typeof(graph))
+
+function NamedGraphs.similar_graph(
+        graph::AbstractVertexOrEdgeDataGraph
+    )
+    return similar_graph(graph, valtype(graph))
+end
+
+function NamedGraphs.similar_graph(
+        graph::AbstractVertexOrEdgeDataGraph,
+        vertices
+    )
+    return similar_graph(graph, valtype(graph), vertices)
+end
+function NamedGraphs.similar_graph(
+        graph::AbstractVertexOrEdgeDataGraph,
+        T::Type,
+        vertices
+    )
+    return similar_graph(graph, T, Vertices(vertices))
+end
+
+# For ambiguity resolution only.
+function NamedGraphs.similar_graph(
+        graph::AbstractVertexOrEdgeDataGraph,
+        VD::Type,
+        ED::Type
+    )
+    # No notion of both vertex and edge data, will go to `AbstractDataGraph` fallback.
+    new_graph = similar_graph(graph, VD, ED, vertices(graph)) # -> DataGraph
+    add_edges!(new_graph, edges(graph))
+    return new_graph
+end
+
+function Base.copy(graph::AbstractVertexOrEdgeDataGraph)
+    graph_dst = similar_graph(graph)
+    # Allow copies of graphs with undefined data.
+    copyto!(graph_dst, graph, filter(key -> isassigned(graph, key), keys(graph)))
+    return graph_dst
+end
+
+function Base.copyto!(graph_dst::AbstractVertexOrEdgeDataGraph, src)
+    copyto!(graph_dst, src, keys(src))
+    return graph_dst
+end
+
+Base.iterate(graph::AbstractVertexOrEdgeDataGraph) = iterate(index_data(graph))
+function Base.iterate(graph::AbstractVertexOrEdgeDataGraph, state)
+    return iterate(index_data(graph), state)
+end
+
+Base.keytype(graph::AbstractVertexOrEdgeDataGraph) = keytype(typeof(graph))
+
+Base.valtype(graph::AbstractVertexOrEdgeDataGraph) = valtype(typeof(graph))
+Base.valtype(::Type{<:AbstractVertexOrEdgeDataGraph{T}}) where {T} = T
+
+Base.eltype(graph::AbstractVertexOrEdgeDataGraph) = eltype(typeof(graph))
+Base.eltype(::Type{<:AbstractVertexOrEdgeDataGraph{T}}) where {T} = T
+
+Base.length(graph::AbstractVertexOrEdgeDataGraph) = length(index_data(graph))
+Base.keys(graph::AbstractVertexOrEdgeDataGraph) = keys(index_data(graph))
+Base.values(graph::AbstractVertexOrEdgeDataGraph) = values(index_data(graph))
+
+Dictionaries.issettable(::AbstractVertexOrEdgeDataGraph) = true
+Dictionaries.isinsertable(::AbstractVertexOrEdgeDataGraph) = false
+
+function Base.insert!(graph::AbstractVertexOrEdgeDataGraph, ind, data)
+    isinsertable(graph) || throw(ArgumentError("Graph does not support insertion."))
+    insert!_datagraph(graph, to_graph_index(graph, ind), data)
+    return graph
+end
+
+function Base.delete!(graph::AbstractVertexOrEdgeDataGraph, ind)
+    delete!_datagraph(graph, to_graph_index(graph, ind))
+    return graph
+end
+
+function Dictionaries.set!(graph::AbstractVertexOrEdgeDataGraph, ind, data)
+    set!_datagraph(graph, to_graph_index(graph, ind), data)
+    return graph
+end
+
+# ================================== vertex data graph =================================== #
+
+abstract type AbstractVertexDataGraph{T, V} <: AbstractVertexOrEdgeDataGraph{T, V} end
+
+Base.keytype(::Type{<:AbstractVertexDataGraph{T, V}}) where {T, V} = V
+
+is_edge_assigned(::AbstractVertexDataGraph, _edge) = false
+
+# `setindex!`
+function set_index_data!(graph::AbstractVertexDataGraph, data, vertex)
+    if !has_vertex(graph, vertex)
+        throw(IndexError("Graph does not contain vertex $vertex"))
+    end
+    set_vertex_data!(graph, data, vertex)
+    return graph
+end
+
+# `insert!`
+function insert!_datagraph(graph::AbstractVertexDataGraph, vertex, data)
+    if has_vertex(graph, vertex)
+        throw(IndexError("Graph already contains vertex $vertex"))
+    end
+    insert_vertex_data!(graph, vertex, data)
+    return graph
+end
+
+# `delete!`
+function delete!_datagraph(graph::AbstractVertexDataGraph, vertex)
+    if !has_vertex(graph, vertex)
+        throw(IndexError("Graph does not contain vertex $vertex"))
+    end
+    rem_vertex!(graph, vertex)
+    return graph
+end
+
+# `set!`
+function set!_datagraph(graph::AbstractVertexDataGraph, vertex, data)
+    if has_vertex(graph, vertex)
+        graph[vertex] = data
+    else
+        insert!(graph, vertex, data)
+    end
+    return graph
+end
+
+function NamedGraphs.similar_graph(
+        graph::AbstractVertexDataGraph,
+        T::Type
+    )
+    new_graph = similar_graph(graph, T, vertices(graph))
+    # we can add edges to a `AbstractVertexDataGraph`.
+    add_edges!(new_graph, edges(graph))
+    return new_graph
+end
+
+# Base method to overload.
+function NamedGraphs.similar_graph(
+        ::AbstractVertexDataGraph,
+        T::Type,
+        vertices::Vertices
+    )
+    return similar_graph(VertexDataGraph{T}, vertices)
+end
+
+function NamedGraphs.induced_subgraph_from_vertices(
+        graph::AbstractVertexDataGraph,
+        subvertices
+    )
+    subnetwork = similar_graph(graph, subvertices)
+    add_edges!(subnetwork, subgraph_edges(graph, subvertices))
+    tensors = view(vertex_data(graph), Indices(subvertices))
+    copyto!(subnetwork, tensors)
+    return subnetwork, subvertices
+end
+
+# Internal
+index_data(graph::AbstractVertexDataGraph) = vertex_data(graph)
+
+function Base.show(io::IO, mime::MIME"text/plain", graph::AbstractVertexDataGraph)
+    println(io, "$(typeof(graph)) with $(nv(graph)) vertices:")
+    show(io, mime, vertices(graph))
+    println(io, "\n")
+    println(io, "and $(ne(graph)) edge(s):")
+    for e in edges(graph)
+        show(io, mime, e)
+        println(io)
+    end
+    println(io)
+    println(io, "with vertex data:")
+    show(io, mime, vertex_data(graph))
+    return nothing
+end
+
+# =================================== edge data graph ==================================== #
+
+abstract type AbstractEdgeDataGraph{T, V} <: AbstractVertexOrEdgeDataGraph{T, V} end
+
+Base.keytype(::Type{<:AbstractEdgeDataGraph{T, V}}) where {T, V} = NamedEdge{V}
+
+is_vertex_assigned(::AbstractEdgeDataGraph, _vertex) = false
+
+# `setindex!`
+function set_index_data!(graph::AbstractEdgeDataGraph, data, edge::AbstractEdge)
+    v1 = src(edge)
+    v2 = dst(edge)
+    if !has_vertex(graph, v1)
+        throw(IndexError("Graph does not contain vertex $v1"))
+    elseif !has_vertex(graph, v2)
+        throw(IndexError("Graph does not contain vertex $v2"))
+    end
+    set_edge_data!(graph, data, edge)
+    return graph
+end
+
+# `insert!`
+function insert!_datagraph(graph::AbstractEdgeDataGraph, edge::AbstractEdge, data)
+    v1 = src(edge)
+    v2 = dst(edge)
+    if has_vertex(graph, v1) && has_vertex(graph, v2)
+        throw(IndexError("Graph already contains vertices $v1 and $v2"))
+    end
+    insert_edge_data!(graph, edge, data)
+    return graph
+end
+function insert_edge_data!(graph::AbstractEdgeDataGraph, edge::AbstractEdge, data)
+    v1 = src(edge)
+    v2 = dst(edge)
+    has_vertex(graph, v1) || add_vertex!(graph, v1)
+    has_vertex(graph, v2) || add_vertex!(graph, v2)
+    set_edge_data!(graph, data, edge)
+    return graph
+end
+
+# `delete!`
+function delete!_datagraph(graph::AbstractEdgeDataGraph, edge)
+    if !has_edge(graph, edge)
+        throw(IndexError("Graph does not contain edge $edge"))
+    end
+    rem_edge!(graph, edge)
+    return graph
+end
+
+# `set!`
+function set!_datagraph(graph::AbstractEdgeDataGraph, edge::AbstractEdge, data)
+    if !has_vertex(graph, src(edge)) || !has_vertex(graph, dst(edge))
+        insert!(graph, edge, data)
+    else
+        graph[edge] = data
+    end
+    return graph
+end
+
+function NamedGraphs.similar_graph(
+        graph::AbstractEdgeDataGraph,
+        T::Type
+    )
+    new_graph = similar_graph(graph, T, vertices(graph))
+    # we can't generically add edges to an `AbstractEdgeDataGraph`.
+    return new_graph
+end
+
+# Base method to overload.
+function NamedGraphs.similar_graph(
+        ::AbstractEdgeDataGraph,
+        T::Type,
+        vertices::Vertices
+    )
+    return similar_graph(EdgeDataGraph{T}, collect(vertices))
+end
+
+function NamedGraphs.induced_subgraph_from_vertices(
+        graph::AbstractEdgeDataGraph,
+        subvertices
+    )
+    subnetwork = similar_graph(graph, subvertices)
+    subedges = subgraph_edges(graph, subvertices)
+
+    tensors = view(edge_data(graph), Indices(subedges))
+
+    copyto!(subnetwork, tensors)
+
+    return subnetwork, subvertices
+end
+
+# Internal
+index_data(graph::AbstractEdgeDataGraph) = edge_data(graph)
+
+function Base.show(io::IO, mime::MIME"text/plain", graph::AbstractEdgeDataGraph)
+    println(io, "$(typeof(graph)) with $(nv(graph)) vertices:")
+    show(io, mime, vertices(graph))
+    println(io, "\n")
+    println(io, "and $(ne(graph)) edge(s):")
+    for e in edges(graph)
+        show(io, mime, e)
+        println(io)
+    end
+    println(io)
+    println(io, "with edge data:")
+    show(io, mime, edge_data(graph))
+    return nothing
+end
diff --git a/src/dataview.jl b/src/dataview.jl
index 8eb055f..cd7eb63 100644
--- a/src/dataview.jl
+++ b/src/dataview.jl
@@ -90,6 +90,9 @@ function Base.setindex!(view::EdgeDataView, vals, keys::Indices{<:Pair})
 end
 
 function Base.setindex!(view::VertexOrEdgeDataView{K, V}, data::V, key::K) where {K, V}
+    if !haskey(view, key)
+        throw(IndexError("Dictionary does not contain index: $key"))
+    end
     setindex!(view.graph, data, key)
     return view
 end
diff --git a/src/edgedatagraph.jl b/src/edgedatagraph.jl
new file mode 100644
index 0000000..0dbaf41
--- /dev/null
+++ b/src/edgedatagraph.jl
@@ -0,0 +1,155 @@
+using Graphs: dst, has_edge, rem_edge!, rem_vertex!, src
+using NamedGraphs: NamedEdge, NamedGraph, ordered_vertices, position_graph, vertex_positions
+
+struct EdgeDataGraph{T, V} <: AbstractEdgeDataGraph{T, V}
+    underlying_graph::NamedGraph{V}
+    edge_data::Dictionary{NamedEdge{V}, T}
+    function EdgeDataGraph{T, V}(
+            ::UndefInitializer,
+            vertices
+        ) where {T, V}
+        graph = NamedGraph{V}(vertices)
+        edge_data = Dictionary{NamedEdge{V}, T}()
+        return new{T, V}(graph, edge_data)
+    end
+end
+
+Graphs.is_directed(::Type{<:EdgeDataGraph}) = false
+
+struct EdgeDataDiGraph{T, V} <: AbstractEdgeDataGraph{T, V}
+    underlying_graph::NamedDiGraph{V}
+    edge_data::Dictionary{NamedEdge{V}, T}
+    function EdgeDataDiGraph{T, V}(
+            ::UndefInitializer,
+            vertices
+        ) where {T, V}
+        graph = NamedDiGraph{V}(vertices)
+        edge_data = Dictionary{NamedEdge{V}, T}()
+        return new{T, V}(graph, edge_data)
+    end
+end
+
+Graphs.is_directed(::Type{<:EdgeDataDiGraph}) = true
+
+for GType in (:EdgeDataGraph, :EdgeDataDiGraph)
+    @eval begin
+        $GType(::UndefInitializer, vertices) = $GType{Any}(undef, vertices)
+        function $GType{T}(::UndefInitializer, vertices) where {T}
+            return $GType{T, eltype(vertices)}(undef, vertices)
+        end
+
+        $GType(data) = $GType{valtype(data)}(data)
+        $GType{T}(data) where {T} = $GType{T, vertextype(keytype(data))}(data)
+
+        function $GType{T, V}(data) where {T, V}
+            edges = NamedEdge{V}.(keys(data))
+            vertices = union(src.(edges), dst.(edges))
+            graph = $GType{T, V}(undef, vertices)
+            copyto!(graph, data)
+            return graph
+        end
+
+        function Base.:(==)(dg1::$GType, dg2::$GType)
+            return dg1.underlying_graph == dg2.underlying_graph &&
+                dg1.edge_data == dg2.edge_data
+        end
+    end
+end
+
+# ====================================== Graphs.jl ======================================= #
+
+for GType in (:EdgeDataGraph, :EdgeDataDiGraph)
+    @eval begin
+        Graphs.edgetype(::Type{<:$GType{T, V}}) where {T, V} = NamedEdge{V}
+
+        function Graphs.add_vertex!(graph::$GType, vertex)
+            return add_vertex!(graph.underlying_graph, vertex)
+        end
+
+        function Graphs.add_edge!(graph::$GType, edge)
+            G = esc($GType)
+            throw(
+                ArgumentError(
+                    "cannot add data-free edges to $G; use `insert!`, `setindex!` or `set!` instead"
+                )
+            )
+            return nothing
+        end
+
+        function Graphs.rem_vertex!(graph::$GType, vertex)
+            for edge in incident_edges(graph, vertex)
+                unset!(graph.edge_data, edge)
+            end
+            rem_vertex!(graph.underlying_graph, vertex)
+            return graph
+        end
+
+        function Graphs.rem_edge!(graph::$GType, edge)
+            unset!(graph.edge_data, edge)
+            rem_edge!(graph.underlying_graph, edge)
+            return graph
+        end
+
+        Graphs.vertices(graph::$GType) = vertices(graph.underlying_graph)
+    end
+end
+
+# ==================================== NamedGraphs.jl ==================================== #
+
+for GType in (:EdgeDataGraph, :EdgeDataDiGraph)
+    @eval begin
+        function NamedGraphs.vertex_positions(graph::$GType)
+            return vertex_positions(graph.underlying_graph)
+        end
+
+        function NamedGraphs.ordered_vertices(graph::$GType)
+            return ordered_vertices(graph.underlying_graph)
+        end
+
+        function NamedGraphs.position_graph(graph::$GType)
+            return position_graph(graph.underlying_graph)
+        end
+
+        function NamedGraphs.similar_graph(graph::$GType, T::Type, vertices::Vertices)
+            new_graph = $GType{T}(undef, collect(vertices))
+            return new_graph
+        end
+
+        # We know how to add edges keys for these particurly concrete types
+        function NamedGraphs.similar_graph(graph::$GType, T::Type)
+            new_graph = similar_graph(graph, T, vertices(graph))
+            add_edges!(new_graph.underlying_graph, edges(graph))
+            return new_graph
+        end
+
+        NamedGraphs.similar_graph(T::Type{<:$GType}, vertices) = T(undef, vertices)
+    end
+end
+
+# ==================================== DataGraphs.jl ===================================== #
+
+for GType in (:EdgeDataGraph, :EdgeDataDiGraph)
+    @eval begin
+        edge_data_type(::Type{<:$GType{T}}) where {T} = T
+
+        function set_edge_data!(graph::$GType, data, edge)
+            # Edges `upsert` if vertices are present.
+            has_edge(graph, edge) || add_edge!(graph.underlying_graph, edge)
+            set!(graph.edge_data, edge, data)
+            return graph
+        end
+
+        get_edge_data(graph::$GType, edge) = graph.edge_data[edge]
+
+        is_vertex_assigned(::$GType, _vertex) = false
+        is_edge_assigned(graph::$GType, edge) = isassigned(graph.edge_data, edge)
+    end
+end
+
+# =================================== Dictionaries.jl ==================================== #
+
+for GType in (:EdgeDataGraph, :EdgeDataDiGraph)
+    @eval begin
+        Dictionaries.isinsertable(::$GType) = true
+    end
+end
diff --git a/src/vertexdatagraph.jl b/src/vertexdatagraph.jl
new file mode 100644
index 0000000..0f83af5
--- /dev/null
+++ b/src/vertexdatagraph.jl
@@ -0,0 +1,148 @@
+using Dictionaries: Dictionary, set!
+using Graphs: Graphs, has_edge, rem_vertex!
+using NamedGraphs:
+    NamedDiGraph, NamedEdge, NamedGraph, ordered_vertices, position_graph, vertex_positions
+
+struct VertexDataGraph{T, V} <: AbstractVertexDataGraph{T, V}
+    underlying_graph::NamedGraph{V}
+    vertex_data::Dictionary{V, T}
+    function VertexDataGraph{T, V}(
+            ::UndefInitializer,
+            vertices
+        ) where {T, V}
+        graph = NamedGraph{V}(vertices)
+        vertex_data = Dictionary{V, T}()
+        return new{T, V}(graph, vertex_data)
+    end
+end
+
+struct VertexDataDiGraph{T, V} <: AbstractVertexDataGraph{T, V}
+    underlying_graph::NamedDiGraph{V}
+    vertex_data::Dictionary{V, T}
+    function VertexDataDiGraph{T, V}(
+            ::UndefInitializer,
+            vertices
+        ) where {T, V}
+        graph = NamedDiGraph{V}(vertices)
+        vertex_data = Dictionary{V, T}()
+        return new{T, V}(graph, vertex_data)
+    end
+end
+
+Graphs.is_directed(::Type{<:VertexDataGraph}) = false
+Graphs.is_directed(::Type{<:VertexDataDiGraph}) = true
+
+for GType in (:VertexDataGraph, :VertexDataDiGraph)
+    @eval begin
+        $GType(::UndefInitializer, vertices) = $GType{Any}(undef, vertices)
+        function $GType{T}(::UndefInitializer, vertices) where {T}
+            return $GType{T, eltype(vertices)}(undef, vertices)
+        end
+
+        $GType(data) = $GType{valtype(data)}(data)
+        $GType{T}(data) where {T} = $GType{T, keytype(data)}(data)
+
+        function $GType{T, V}(data) where {T, V}
+            vertices = keys(data)
+            cache = $GType{T, V}(undef, vertices)
+            return copyto!(cache, data)
+        end
+
+        function Base.:(==)(dg1::$GType, dg2::$GType)
+            return dg1.underlying_graph == dg2.underlying_graph &&
+                dg1.vertex_data == dg2.vertex_data
+        end
+    end
+end
+
+# ====================================== Graphs.jl ======================================= #
+
+for GType in (:VertexDataGraph, :VertexDataDiGraph)
+    @eval begin
+        Graphs.edgetype(::Type{<:$GType{T, V}}) where {T, V} = NamedEdge{V}
+
+        function Graphs.add_vertex!(graph::$GType, vertex)
+            return throw(
+                ArgumentError(
+                    "cannot add data-free vertices to $GType; use `insert!`, `setindex!` or `set!` instead"
+                )
+            )
+        end
+
+        function Graphs.add_edge!(graph::$GType, edge::NamedEdge)
+            return add_edge!(graph.underlying_graph, edge)
+        end
+
+        function Graphs.rem_vertex!(graph::$GType, vertex)
+            unset!(graph.vertex_data, vertex)
+            rem_vertex!(graph.underlying_graph, vertex)
+            return graph
+        end
+
+        function Graphs.rem_edge!(graph::$GType, vertex)
+            rem_edge!(graph.underlying_graph, vertex)
+            return graph
+        end
+
+        Graphs.vertices(graph::$GType) = vertices(graph.underlying_graph)
+    end
+end
+
+# ==================================== NamedGraphs.jl ==================================== #
+
+for GType in (:VertexDataGraph, :VertexDataDiGraph)
+    @eval begin
+        function NamedGraphs.vertex_positions(graph::$GType)
+            return vertex_positions(graph.underlying_graph)
+        end
+
+        function NamedGraphs.ordered_vertices(graph::$GType)
+            return ordered_vertices(graph.underlying_graph)
+        end
+
+        function NamedGraphs.position_graph(graph::$GType)
+            return position_graph(graph.underlying_graph)
+        end
+
+        function NamedGraphs.similar_graph(graph::$GType, T::Type, vertices::Vertices)
+            new_graph = $GType{T}(undef, collect(vertices))
+            return new_graph
+        end
+
+        NamedGraphs.similar_graph(T::Type{<:$GType}, vertices) = T(undef, vertices)
+    end
+end
+
+# ==================================== DataGraphs.jl ===================================== #
+
+for GType in (:VertexDataGraph, :VertexDataDiGraph)
+    @eval begin
+        vertex_data_type(::Type{<:$GType{T}}) where {T} = T
+
+        function set_vertex_data!(graph::$GType, data, vertex)
+            # We use an upsert here as we have already checked if the vertex (i.e. key) exists,
+            # but it might not exist in the internal `Dictionary`, so add it if not.
+            set!(graph.vertex_data, vertex, data)
+            return graph
+        end
+
+        get_vertex_data(graph::$GType, vertex) = graph.vertex_data[vertex]
+
+        is_vertex_assigned(graph::$GType, vertex) = isassigned(graph.vertex_data, vertex)
+        is_edge_assigned(::$GType, _edge) = false
+    end
+end
+
+# =================================== Dictionaries.jl ==================================== #
+
+for GType in (:VertexDataGraph, :VertexDataDiGraph)
+    @eval begin
+        Dictionaries.isinsertable(::$GType) = true
+
+        function insert_vertex_data!(graph::$GType, vertex, data)
+            add_vertex!(graph.underlying_graph, vertex)
+            insert!(graph.vertex_data, vertex, data)
+            return graph
+        end
+    end
+end
diff --git a/test/test_basics.jl b/test/test_basics.jl
index 727da8d..7d3fc77 100644
--- a/test/test_basics.jl
+++ b/test/test_basics.jl
@@ -620,8 +620,6 @@ using Test: @test, @test_broken, @testset
         @test g_copy["a" => "b"] == -1.0
         @test g_copy["b" => "c"] == -2.0
 
-        @test_throws IndexError copyto!(empty_graph(g_copy), g_copy)
-
         g2 = similar_graph(g, ["u", "v"])
         @test similar_graph(g2) isa typeof(g)
         @test has_vertex(g2, "u")
diff --git a/test/test_vertexoredgedatagraph.jl b/test/test_vertexoredgedatagraph.jl
new file mode 100644
index 0000000..778fad6
--- /dev/null
+++ b/test/test_vertexoredgedatagraph.jl
@@ -0,0 +1,510 @@
+using DataGraphs: DataGraphs, EdgeDataDiGraph, EdgeDataGraph, EdgeDataView,
+    VertexDataDiGraph, VertexDataGraph, VertexDataView, edge_data, edge_data_type,
+    underlying_graph, vertex_data, vertex_data_type
+using Dictionaries:
+    AbstractDictionary, Dictionary, IndexError, Indices, isinsertable, issettable, set!
+using Graphs: AbstractGraph, AbstractSimpleGraph, add_edge!, add_vertex!, dst, edges,
+    edgetype, has_edge, has_vertex, is_directed, ne, nv, rem_edge!, rem_vertex!, src,
+    vertices
+using NamedGraphs.GraphsExtensions: add_edge, subgraph, vertextype
+using NamedGraphs: NamedDiGraph, NamedEdge, NamedGraph, ordered_vertices, position_graph,
+    similar_graph, vertex_positions
+using Test: @test, @test_throws, @testset
+
+@testset "VertexDataGraph and EdgeDataGraph" begin
+    @testset "$GType basics" for GType in (
+            VertexDataGraph{String, Int},
+            VertexDataDiGraph{String, Int},
+        )
+        @testset "undef constructor" begin
+            g = GType(undef, [1, 2, 3])
+            @test g isa GType
+            @test nv(g) == 3
+            @test ne(g) == 0
+            @test has_vertex(g, 1)
+            @test has_vertex(g, 2)
+            @test has_vertex(g, 3)
+            @test !has_vertex(g, 4)
+            @test Set(collect(vertices(g))) == Set([1, 2, 3])
+        end
+
+        @testset "data constructor" begin
+            data = Dictionary([1, 2, 3], ["V1", "V2", "V3"])
+            g = GType(data)
+            @test g isa GType
+            @test nv(g) == 3
+            @test isassigned(g, 1)
+            @test isassigned(g, 2)
+            @test isassigned(g, 3)
+            @test g[1] == "V1"
+            @test g[2] == "V2"
+            @test g[3] == "V3"
+
+            data = Dictionary([1.0, 2.0, 3.0], SubString.(["V1", "V2", "V3"]))
+            g = GType(data)
+            @test g isa GType
+        end
+
+        @testset "copy" begin
+            data = Dictionary([1, 2, 3], ["V1", "V2", "V3"])
+            g = GType(data)
+            add_edge!(g, NamedEdge(1, 2))
+            g_copy = copy(g)
+
+            @test g_copy == g
+            @test g_copy !== g
+
+            # Test we can copy a graph with undefined data.
+            g = GType(undef, [1, 2, 3])
+            g[1] = "V1"
+            add_edge!(g, NamedEdge(2, 3))
+            g_copy = copy(g)
+
+            @test has_vertex(g_copy, 1)
+            @test has_vertex(g_copy, 2)
+            @test has_vertex(g_copy, 3)
+            @test has_edge(g_copy, 2 => 3)
+            @test isassigned(g_copy, 1)
+            @test g_copy[1] == "V1"
+            @test !isassigned(g_copy, 2)
+            @test !isassigned(g_copy, 3)
+        end
+
+        @testset "Graphs.jl interface" begin
+            g = GType(undef, [1, 2, 3])
+            @test vertextype(g) == Int
+            @test edgetype(g) == NamedEdge{Int}
+
+            add_edge!(g, NamedEdge(1, 2))
+            add_edge!(g, NamedEdge(2, 3))
+            @test ne(g) == 2
+            @test has_edge(g, NamedEdge(1, 2))
+            @test has_edge(g, NamedEdge(2, 3))
+            @test !has_edge(g, NamedEdge(1, 3))
+            @test length(collect(edges(g))) == 2
+
+            rem_edge!(g, NamedEdge(1, 2))
+            @test ne(g) == 1
+            @test !has_edge(g, NamedEdge(1, 2))
+        end
+
+        @testset "rem_vertex!" begin
+            g = GType(undef, [1, 2, 3])
+            add_edge!(g, NamedEdge(1, 2))
+            rem_vertex!(g, 1)
+            @test !has_vertex(g, 1)
+            @test nv(g) == 2
+            @test ne(g) == 0
+        end
+
+        @testset "DataGraphs interface" begin
+            g = GType(undef, [1, 2, 3])
+            @test vertex_data_type(g) == String
+            @test vertex_data_type(GType) == String
+            @test !isassigned(g, 1)
+            @test !isassigned(g, 2)
+            @test !isassigned(g, 3)
+            add_edge!(g, NamedEdge(1, 2))
+            @test !isassigned(g, 1 => 2)
+        end
+
+        @testset "setindex! and getindex" begin
+            g = GType(undef, [1, 2, 3])
+            g[1] = "V1"
+            g[2] = "V2"
+            g[3] = "V3"
+            @test isassigned(g, 1)
+            @test isassigned(g, 2)
+            @test isassigned(g, 3)
+            @test g[1] == "V1"
+            @test g[2] == "V2"
+            @test g[3] == "V3"
+        end
+
+        @testset "NamedGraphs interface" begin
+            g = GType(undef, [1, 2, 3])
+            @test position_graph(g) isa AbstractSimpleGraph{Int}
+            @test ordered_vertices(g) == [1, 2, 3]
+            @test keys(vertex_positions(g)) == vertices(g)
+
+            g = add_edge(g, NamedEdge(1, 2))
+            g[1] = "1"
+
+            gs = similar_graph(g)
+            @test gs isa GType
+            @test has_vertex(gs, 1)
+            @test has_vertex(gs, 2)
+            @test has_vertex(gs, 3)
+            @test has_edge(gs, 1 => 2)
+            @test !isassigned(gs, 1)
+
+            gs = similar_graph(g, vertices(g))
+            @test vertices(gs) == vertices(g)
+            @test ne(gs) == 0
+            @test !isassigned(gs, 1)
+
+            gs = similar_graph(g, [1, 2, 4])
+            @test has_vertex(gs, 1)
+            @test has_vertex(gs, 2)
+            @test has_vertex(gs, 4)
+            @test nv(gs) == 3
+            @test ne(gs) == 0
+            @test !isassigned(gs, 1)
+
+            gs = similar_graph(g, Char)
+            @test vertex_data_type(gs) === Char
+            @test nv(gs) == 3
+            @test ne(gs) == 1
+            gs[1] = 'C'
+            @test gs[1] == 'C'
+
+            gs = similar_graph(g, Float64, vertices(g))
+            @test ne(gs) == 0
+
+            gs = similar_graph(GType, [1.0, 2.0])
+            @test gs isa GType
+            @test has_vertex(gs, 1)
+            @test has_vertex(gs, 2)
+            @test ne(gs) == 0
+
+            g = GType(Dictionary([1, 2, 3], ["V1", "V2", "V3"]))
+            add_edge!(g, NamedEdge(1, 2))
+            add_edge!(g, NamedEdge(2, 3))
+            sg = subgraph(g, [1, 2])
+            @test sg isa GType
+            @test has_vertex(sg, 1)
+            @test has_vertex(sg, 2)
+            @test !has_vertex(sg, 3)
+            @test has_edge(sg, 1 => 2)
+            @test !has_edge(sg, 2 => 3)
+            @test sg[1] == "V1"
+            @test sg[2] == "V2"
+            @test !isassigned(sg, 3)
+        end
+
+        @testset "Dictionaries interface" begin
+            g = GType(undef, [1, 2, 3])
+            @test keytype(g) == Int
+            @test valtype(g) == String
+            @test keys(g) isa Indices
+            @test length(g) == 3
+            @test vertex_data(g) isa VertexDataView
+
+            @test issettable(g)
+            @test isinsertable(g)
+
+            insert!(g, 4, "V4")
+            @test_throws IndexError insert!(g, 4, "V4_again")
+            @test has_vertex(g, 4)
+            @test isassigned(g, 4)
+            @test g[4] == "V4"
+            @test nv(g) == 4
+
+            @test_throws IndexError g[5] = "V5"
+            @test !has_vertex(g, 5)
+            @test !isassigned(g, 5)
+
+            set!(g, 5, "V5")
+            @test has_vertex(g, 5)
+            @test g[5] == "V5"
+
+            g[5] = "V5_again"
+            @test g[5] == "V5_again"
+            @test nv(g) == 5
+        end
+
+        @testset "show" begin
+            g = GType(Dictionary([1, 2, 3], ["V1", "V2", "V3"]))
+            io = IOBuffer()
+            show(io, g)
+            str = String(take!(io))
+            @test occursin("$GType", str)
+            @test occursin("V1", str)
+            @test occursin("V2", str)
+            @test occursin("V3", str)
+            @test !occursin("edge data", str)
+            @test occursin("vertex data", str)
+        end
+    end
+
+    @testset "$GType basics" for GType in
+        (EdgeDataGraph{String, Int}, EdgeDataDiGraph{String, Int})
+        @testset "undef constructor" begin
+            g = GType(undef, [1, 2, 3])
+            @test g isa GType
+            @test nv(g) == 3
+            @test ne(g) == 0
+        end
+
+        @testset "data constructor" begin
+            data = Dictionary([NamedEdge(1, 2), NamedEdge(2, 3)], ["E12", "E23"])
+            g = GType(data)
+            @test g isa GType
+            @test nv(g) == 3
+            @test ne(g) == 2
+            @test isassigned(g, NamedEdge(1, 2))
+            @test isassigned(g, NamedEdge(2, 3))
+            @test g[NamedEdge(1, 2)] == "E12"
+            @test g[NamedEdge(2, 3)] == "E23"
+
+            # With pairs
+            data = Dictionary([1.0 => 2.0, 2.0 => 3.0], ["E12", "E23"])
+            g = GType(data)
+            @test g isa GType
+            @test nv(g) == 3
+            @test ne(g) == 2
+            @test isassigned(g, NamedEdge(1, 2))
+            @test isassigned(g, NamedEdge(2, 3))
+            @test g[NamedEdge(1, 2)] == "E12"
+            @test g[NamedEdge(2, 3)] == "E23"
+        end
+
+        @testset "copy" begin
+            data = Dictionary([1 => 2, 2 => 3], ["E12", "E23"])
+            g = GType(data)
+            g_copy = copy(g)
+
+            @test g_copy == g
+            @test g_copy !== g
+
+            # Test we can copy a graph with undefined data.
+            g = GType(undef, [1, 2, 3])
+            g[1 => 2] = "E12"
+            g_copy = copy(g)
+
+            @test has_vertex(g_copy, 1)
+            @test has_vertex(g_copy, 2)
+            @test has_vertex(g_copy, 3)
+            @test has_edge(g_copy, 1 => 2)
+            @test isassigned(g_copy, 1 => 2)
+            @test g_copy[1 => 2] == "E12"
+            @test !isassigned(g_copy, 2 => 3)
+        end
+
+        @testset "Graphs.jl interface" begin
+            g = GType(undef, [1, 2, 3])
+            @test has_vertex(g, 1)
+            @test has_vertex(g, 2)
+            @test !has_vertex(g, 4)
+            @test edgetype(g) == NamedEdge{Int}
+
+            @test_throws ArgumentError add_edge!(g, NamedEdge(1, 2))
+            @test_throws ArgumentError add_edge!(g, 2 => 3)
+
+            add_vertex!(g, 4)
+            @test has_vertex(g, 4)
+            @test nv(g) == 4
+        end
+
+        @testset "DataGraphs interface" begin
+            g = GType(undef, [1, 2, 3])
+            @test edge_data_type(GType) == String
+            @test edge_data_type(g) == String
+            @test !isassigned(g, 1)
+            @test !isassigned(g, NamedEdge(1, 2))
+        end
+
+        @testset "setindex! and getindex" begin
+            data = Dictionary([NamedEdge(1, 2), NamedEdge(2, 3)], ["E12", "E23"])
+            g = GType(data)
+            g[NamedEdge(1, 2)] = "E12_updated"
+            @test g[NamedEdge(1, 2)] == "E12_updated"
+            @test g[NamedEdge(2, 3)] == "E23"
+        end
+
+        @testset "rem_edge!" begin
+            data = Dictionary([NamedEdge(1, 2), NamedEdge(2, 3)], ["E12", "E23"])
+            g = GType(data)
+            rem_edge!(g, NamedEdge(1, 2))
+            @test !has_edge(g, NamedEdge(1, 2))
+            @test ne(g) == 1
+        end
+
+        @testset "rem_vertex!" begin
+            data = Dictionary([NamedEdge(1, 2), NamedEdge(2, 3)], ["E12", "E23"])
+            g = GType(data)
+            rem_vertex!(g, 1)
+            @test !has_vertex(g, 1)
+            @test !has_edge(g, NamedEdge(1, 2))
+            @test !isassigned(g, NamedEdge(1, 2))
+            @test ne(g) == 1
+            @test g[NamedEdge(2, 3)] == "E23"
+        end
+
+        @testset "NamedGraphs interface" begin
+            g = GType(Dictionary([1 => 2, 2 => 3], ["E12", "E23"]))
+            @test issetequal(vertices(g), [1, 2, 3])
+            @test position_graph(g) isa AbstractSimpleGraph{Int}
+            @test ordered_vertices(g) isa AbstractVector
+            @test vertex_positions(g) isa AbstractDictionary
+
+            gs = similar_graph(g)
+            @test gs isa GType
+            @test has_vertex(gs, 1)
+            @test has_vertex(gs, 2)
+            @test has_vertex(gs, 3)
+            @test has_edge(gs, 1 => 2)
+            @test !isassigned(gs, 1 => 2)
+
+            gs = similar_graph(g, vertices(g))
+            @test vertices(gs) == vertices(g)
+            @test ne(gs) == 0
+            @test !isassigned(gs, 1 => 2)
+
+            gs = similar_graph(g, [1, 2, 4])
+            @test has_vertex(gs, 1)
+            @test has_vertex(gs, 2)
+            @test has_vertex(gs, 4)
+            @test nv(gs) == 3
+            @test ne(gs) == 0
+            @test !isassigned(gs, 1 => 2)
+
+            gs = similar_graph(g, Char)
+            @test vertex_data_type(gs) === Char
+            @test nv(gs) == 3
+            @test ne(gs) == 2
+            gs[3 => 1] = 'C'
+            @test gs[3 => 1] == 'C'
+            @test ne(gs) == 3
+
+            gs = similar_graph(g, Float64, vertices(g))
+            @test ne(gs) == 0
+
+            gs = similar_graph(GType, [1.0, 2.0])
+            @test gs isa GType
+            @test has_vertex(gs, 1)
+            @test has_vertex(gs, 2)
+            @test ne(gs) == 0
+
+            g = GType(Dictionary([1 => 2, 2 => 3], ["E12", "E23"]))
+            sg = subgraph(g, [1, 2])
+            @test sg isa GType
+            @test has_vertex(sg, 1)
+            @test has_vertex(sg, 2)
+            @test !has_vertex(sg, 3)
+            @test has_edge(sg, 1 => 2)
+            @test !has_edge(sg, 2 => 3)
+            @test sg[1 => 2] == "E12"
+        end
+
+        @testset "Dictionaries interface" begin
+            g = GType(undef, [1, 2, 3])
+            @test keytype(g) == NamedEdge{Int}
+            @test valtype(g) == String
+            @test edge_data(g) isa EdgeDataView
+
+            @test issettable(g)
+            @test isinsertable(g)
+
+            g = GType(undef, [1.0, 2.0, 3.0])
+            @test keytype(g) == NamedEdge{Int}
+
+            g = GType(undef, [1, 2, 3])
+            insert!(g, 3 => 4, "E34")
+            @test has_vertex(g, 4)
+            @test has_edge(g, 3 => 4)
+            @test isassigned(g, 3 => 4)
+            @test g[3 => 4] == "E34"
+
+            g = GType(undef, [1, 2, 3])
+            insert!(g, 5 => 6, "E56")
+            @test has_vertex(g, 5)
+            @test has_vertex(g, 6)
+            @test has_edge(g, 5 => 6)
+            @test isassigned(g, 5 => 6)
+            @test g[5 => 6] == "E56"
+
+            g = GType(undef, [1, 2, 3])
+            @test_throws IndexError insert!(g, 2 => 3, "E23")
+            @test !has_edge(g, 2 => 3)
+
+            g = GType(undef, [1, 2, 3])
+            set!(g, 1 => 2, "E12")
+            @test has_edge(g, 1 => 2)
+            @test g[1 => 2] == "E12"
+
+            g = GType(undef, [1, 2, 3])
+            set!(g, 3 => 4, "E34")
+            @test has_vertex(g, 4)
+            @test has_edge(g, 3 => 4)
+            @test g[3 => 4] == "E34"
+
+            g = GType(undef, [1, 2, 3])
+            set!(g, 4 => 5, "E45")
+            @test has_vertex(g, 4)
+            @test has_vertex(g, 5)
+            @test has_edge(g, 4 => 5)
+            @test g[4 => 5] == "E45"
+            set!(g, 4 => 5, "E45_again")
+            @test g[4 => 5] == "E45_again"
+
+            g = GType(undef, [1, 2, 3])
+            g[2 => 3] = "E23"
+            @test has_edge(g, 2 => 3)
+            @test g[2 => 3] == "E23"
+            @test_throws IndexError g[3 => 4] = "E34"
+            @test_throws IndexError g[4 => 5] = "E45"
+
+            g = EdgeDataGraph{String, Int}(undef, [1, 2, 3])
+            @test_throws IndexError edge_data(g)[2 => 3] = "E23"
+        end
+
+        @testset "show" begin
+            g = GType(Dictionary([1 => 2, 2 => 3], ["E12", "E23"]))
+            io = IOBuffer()
+            show(io, g)
+            str = String(take!(io))
+            @test occursin("$GType", str)
+            @test occursin("edge data", str)
+            @test occursin("E12", str)
+            @test occursin("E23", str)
+            @test !occursin("vertex data", str)
+        end
+    end
+
+    @testset "(un)directed graph specific" begin
+        @testset "basics" begin
+            g = VertexDataGraph(undef, [1, 2, 3])
+            @test !is_directed(VertexDataGraph)
+            @test !is_directed(g)
+
+            g = VertexDataDiGraph(undef, [1, 2, 3])
+            @test is_directed(VertexDataDiGraph)
+            @test is_directed(g)
+
+            add_edge!(g, NamedEdge(1, 2))
+            @test has_edge(g, NamedEdge(1, 2))
+            @test !has_edge(g, NamedEdge(2, 1))
+            @test ne(g) == 1
+
+            add_edge!(g, NamedEdge(2, 1))
+            @test has_edge(g, NamedEdge(2, 1))
+            @test ne(g) == 2
+
+            g = EdgeDataGraph(undef, [1, 2, 3])
+            @test !is_directed(VertexDataGraph)
+            @test !is_directed(g)
+
+            g = EdgeDataDiGraph(undef, [1, 2, 3])
+            @test is_directed(VertexDataDiGraph)
+            @test is_directed(g)
+        end
+
+        @testset "undirected edge access" begin
+            data = Dictionary([NamedEdge(1, 2)], ["E12"])
+            g = EdgeDataGraph(data)
+            @test g[NamedEdge(1, 2)] == "E12"
+            @test g[NamedEdge(2, 1)] == "E12"
+            @test g[1 => 2] == "E12"
+            @test g[2 => 1] == "E12"
+        end
+
+        @testset "directed edge access" begin
+            data = Dictionary([NamedEdge(1, 2)], ["E12"])
+            g = EdgeDataDiGraph(data)
+            @test has_edge(g, NamedEdge(1, 2))
+            @test !has_edge(g, NamedEdge(2, 1))
+            @test g[NamedEdge(1, 2)] == "E12"
+        end
+    end
+end
