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
3 changes: 3 additions & 0 deletions src/commands/edge_audit/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ export const collect_graph_facts = (graph: NoteGraph): GraphFacts => {
.get_outgoing_edges(path)
.get_edges()
.forEach((edge: EdgeStruct) => {
// self_is_sibling self-loops would show up as dangling edges on
// unresolved notes and block mergeable-field matches (#785).
if (edge.is_self_loop()) return;
edges.push({
field: edge.edge_type,
source: edge.source_path(graph),
Expand Down
4 changes: 4 additions & 0 deletions src/commands/list_index/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ function edge_tree_to_list_index_inner(
current_nodes.forEach((datum) => {
const { edge, children, depth } = datum;

// self_is_sibling adds a note→itself edge for the views; following it here
// lists the note as its own child and repeats its siblings (#785).
if (edge.is_self_loop()) return;

const display = edge.stringify_target(graph, stringify_options);

const link = Links.ify(edge.target_path(graph), display, {
Expand Down
19 changes: 16 additions & 3 deletions src/utils/canvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,18 @@ export function build_canvas(

const data = result.data;

// self_is_sibling adds a note→itself edge for the views. Leave out each
// self-loop and the subtree the traversal followed back through it (#785).
const skipped = new Set<number>();
const skip = (index: number) => {
if (skipped.has(index)) return;
skipped.add(index);
for (const c of data[index].children) skip(c);
};
data.forEach((datum, i) => {
if (datum.edge.is_self_loop()) skip(i);
});

// Forest roots = data entries that are nobody's child (the first hops).
const is_child = new Set<number>();
for (const datum of data) {
Expand All @@ -121,7 +133,7 @@ export function build_canvas(
const existing = placements.get(path);
if (existing) return existing.cross;

const kids = Array.from(datum.children);
const kids = Array.from(datum.children).filter((c) => !skipped.has(c));
let cross: number;
if (kids.length === 0) {
cross = next_slot++;
Expand All @@ -137,7 +149,7 @@ export function build_canvas(

const root_crosses = data
.map((_, i) => i)
.filter((i) => !is_child.has(i))
.filter((i) => !is_child.has(i) && !skipped.has(i))
.map(layout);

// The source note sits at depth 0, centred over its roots.
Expand All @@ -164,7 +176,8 @@ export function build_canvas(
const edges: CanvasEdge[] = [];
const edge_ids = new Set<string>();

for (const datum of data) {
for (const [i, datum] of data.entries()) {
if (skipped.has(i)) continue;
const from = datum.edge.source_path(graph);
const to = datum.edge.target_path(graph);

Expand Down
39 changes: 38 additions & 1 deletion tests/commands/edge_audit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,14 @@ import {
type GraphFacts,
type NodeFact,
} from "src/commands/edge_audit/analyze";
import { describe, expect, test } from "vitest";
import { collect_graph_facts } from "src/commands/edge_audit";
import fs from "node:fs/promises";
import { beforeEach, describe, expect, test } from "vitest";
import init, {
create_graph,
GCEdgeData,
GCNodeData,
} from "wasm/pkg/breadcrumbs_graph_wasm";

const node = (path: string, resolved = true): NodeFact => ({ path, resolved });

Expand Down Expand Up @@ -270,3 +277,33 @@ describe("build_edge_audit", () => {
expect(report.dangling_edges).toStrictEqual([]);
});
});

describe("collect_graph_facts", () => {
beforeEach(async () => {
const wasmSource = await fs.readFile(
"wasm/pkg/breadcrumbs_graph_wasm_bg.wasm",
);
// @ts-ignore TS2345
const wasmModule = await WebAssembly.compile(wasmSource);
await init(wasmModule);
});

test("leaves out self_is_sibling self-loops (#785)", () => {
const graph = create_graph();
graph.build_graph(
[new GCNodeData("Hub.md", [], true, false, false)],
[
new GCEdgeData("Hub.md", "Missing.md", "same", ""),
new GCEdgeData("Hub.md", "Hub.md", "same", "implied"),
new GCEdgeData("Missing.md", "Missing.md", "same", "implied"),
],
[],
);

const facts = collect_graph_facts(graph);

expect(facts.edges.map((e) => `${e.source}→${e.target}`)).toEqual([
"Hub.md→Missing.md",
]);
});
});
27 changes: 27 additions & 0 deletions tests/commands/list_index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,31 @@ describe("build", () => {
].join("\n"),
);
});

test("self-loop edges are left out (#785)", () => {
const graph = create_graph();
graph.build_graph(
["Hub.md", "Sibling.md", "Twin.md"].map(
(p) => new GCNodeData(p, [], true, false, false),
),
[
new GCEdgeData("Hub.md", "Sibling.md", "same", ""),
new GCEdgeData("Hub.md", "Twin.md", "same", ""),
new GCEdgeData("Hub.md", "Hub.md", "same", "implied"),
],
[],
);

const list_index = build_list_index(graph, "Hub.md", undefined, {
indent: " ",
fields: ["same"],
show_attributes: [],
field_group_labels: [],
link_kind: "none",
edge_sort_id: { order: 1, field: "basename" },
show_node_options: { ext: false, alias: false, folder: false },
});

expect(list_index).toBe(["- Sibling", "- Twin", ""].join("\n"));
});
});
20 changes: 20 additions & 0 deletions tests/utils/canvas.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,24 @@ describe("build_canvas semantic sides", () => {
"a→c": "right/left",
});
});

test("self-loop edges are left out (#785)", () => {
const graph = graph_of([
["Hub", "Sibling", "same"],
["Hub", "Twin", "same"],
["Hub", "Hub", "same"],
]);
const result = traverse(graph, {
entry: ["Hub"],
depth: 5,
separateEdges: false,
flatten: true,
});
const canvas = build_canvas(graph, result, "Hub", "LR");
result.free();

expect(
canvas.edges.map((e) => `${e.fromNode}→${e.toNode}`).sort(),
).toEqual(["Hub→Sibling", "Hub→Twin"]);
});
});
Loading