Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions src/sqlalchemy_erd/html_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,8 @@ def render_html(
const e = document.createElementNS(NS, tag);
for (const [k,v] of Object.entries(attrs||{{}})) {{
if (k === 'textContent') e.textContent = v;
// SVG ignores the title attribute: the native tooltip needs a <title> child.
else if (k === 'title') el('title', {{ textContent: v }}, e);
else e.setAttribute(k, v);
}}
if (parent) parent.appendChild(e);
Expand Down Expand Up @@ -353,18 +355,24 @@ def render_html(
const sy = HEADER_H + PAD_ + i * FIELD_H;
el('line', {{ x1:'10', y1:sy, x2:NODE_W-10, y2:sy, stroke: THEME.separator, 'stroke-width':'1' }}, g);
}}
el('text', {{
const nameAttrs = {{
x:'10', y:fy, 'font-size':'10',
'font-family':"'Courier New', Courier, monospace",
fill: field.nameColor, 'font-weight': field.nameWeight,
'dominant-baseline':'middle', textContent: field.name
}}, g);
el('text', {{
}};
const kindAttrs = {{
x: NODE_W-8, y:fy, 'font-size':'9',
'font-family':"'Courier New', Courier, monospace",
fill: field.kindColor, 'text-anchor':'end',
'dominant-baseline':'middle', opacity:'0.9', textContent: field.kindLabel
}}, g);
}};
if (field.comment) {{
nameAttrs.title = field.comment;
kindAttrs.title = field.comment;
}}
el('text', nameAttrs, g);
el('text', kindAttrs, g);
}}
Comment on lines -367 to 376

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The column-comment tooltip doesn't work in the browser. The comment is assigned as title, and since el() does setAttribute('title', ...) on an SVG , it ends up as a title attribute. In SVG, the native tooltip is NOT produced by the title attribute it requires a child <title> element, so Chrome shows nothing on hover.


g.addEventListener('pointerdown', e => {{
Expand Down
2 changes: 2 additions & 0 deletions src/sqlalchemy_erd/introspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class ColumnInfo:
nullable: bool
is_pk: bool
is_fk: bool
comment: str | None = None


@dataclass
Expand Down Expand Up @@ -147,6 +148,7 @@ def _build_table(
nullable=col.nullable or False,
is_pk=is_pk,
is_fk=is_fk,
comment=col.comment,
))

display_name = class_names.get(table_key, table.name)
Expand Down
1 change: 1 addition & 0 deletions src/sqlalchemy_erd/serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def build_entities_json(tables: list[TableInfo], theme: Theme) -> str:
"nameWeight": name_weight,
"kindColor": kind_color,
"kindLabel": kind_label,
"comment": col.comment,
})

entities.append({
Expand Down
16 changes: 16 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,3 +307,19 @@ def multi_fk_base():
@pytest.fixture
def cardinality_metadata_fixture():
return cardinality_metadata


# -- Column comments schema ---------------------------------------------------

comments_metadata = MetaData()

Table(
"accounts", comments_metadata,
Column("id", Integer, primary_key=True),
Column("email", String(200), comment="Primary login email"),
)


@pytest.fixture
def comments_metadata_fixture():
return comments_metadata
15 changes: 15 additions & 0 deletions tests/test_html_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ def test_per_table_color_override_applied(self, blog_base):
entities = json.loads(_build_entities_json(tables, theme))
assert _entity(entities, "users")["headerColor"] == "#ff0000"

def test_column_comment_is_serialized(self, comments_metadata_fixture):
tables, _ = introspect_models(comments_metadata_fixture)
entities = json.loads(_build_entities_json(tables, get_theme("default")))
email = _field(_entity(entities, "accounts"), "email")
assert email["comment"] == "Primary login email"


# ── _build_relations_json ────────────────────────────────────────────────────

Expand Down Expand Up @@ -170,3 +176,12 @@ def test_empty_schema_still_renders(self, empty_base):
html = render_html(tables, rels, positions, theme)
assert "<!DOCTYPE html>" in html
assert _extract_js_object(html, "ENTITIES") == []

def test_html_contains_tooltip_binding(self, comments_metadata_fixture):
tables, rels = introspect_models(comments_metadata_fixture)
positions = force_directed_layout(tables, rels)
html = render_html(tables, rels, positions, get_theme("default"))
assert "field.comment" in html
assert "Primary login email" in html
# A title attribute renders no tooltip in SVG; el() must emit a <title> child.
assert "el('title', { textContent: v }, e)" in html
10 changes: 10 additions & 0 deletions tests/test_introspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,3 +373,13 @@ def test_nullable_fk_marks_optional_parent(self, cardinality_metadata_fixture):
rel = next(r for r in rels if r.to_table == "tasks")
assert rel.from_card == "0..1"
assert rel.to_card == "N"


# -- Column comments ----------------------------------------------------------

class TestIntrospectColumnComments:
def test_column_comments_are_preserved(self, comments_metadata_fixture):
tables, _ = introspect_models(comments_metadata_fixture)
accounts = next(t for t in tables if t.name == "accounts")
email = next(c for c in accounts.columns if c.name == "email")
assert email.comment == "Primary login email"
Loading