Skip to content
Open
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
53 changes: 53 additions & 0 deletions src/test/view/treeNodes/directoryTreeNode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,4 +206,57 @@ describe('DirectoryTreeNode', function () {
assert.strictEqual(rootDir.checkboxState?.state, vscode.TreeItemCheckboxState.Unchecked);
});
});

describe('parent pointer after phantom-root pull-up', function () {
// When a tree has multiple top-level directories, a temporary DirectoryTreeNode
// with label='' (phantom root) is used to build the tree, then its children are
// pulled up to the actual container. If children are not re-parented, getParent()
// still returns the phantom DirectoryTreeNode and processCheckboxUpdates fires
// refresh() on an invisible node, so checkbox state never updates visually.

it('getParent() returns undefined after child is re-parented to the container', function () {
const container = createMockParent();
const phantomRoot = new DirectoryTreeNode(container, '');
const subDir = new DirectoryTreeNode(phantomRoot, 'src');

// Before re-parenting: parent is the phantom root DirectoryTreeNode
assert.ok(subDir.getParent() instanceof DirectoryTreeNode, 'sanity: should point to phantom root before re-parenting');

// Simulate the pull-up re-parenting fix applied in filesCategoryNode / pullRequestNode / commitNode
subDir.parent = container;

// After re-parenting: container is not a TreeNode, so getParent() returns undefined.
// This means the ancestor walk in processCheckboxUpdates stops here and refresh()
// is called on the visible subDir node, not the invisible phantom root.
assert.strictEqual(subDir.getParent(), undefined, 'after re-parenting, getParent() should not return the phantom root');
});
Comment on lines +217 to +232
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

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

The new test simulates re-parenting to a non-TreeNode container and asserts getParent() becomes undefined. In the production fix, the pulled-up children are re-parented to this (e.g., PRNode / CommitNode / FilesCategoryNode), which is a TreeNode, so getParent() should return that container node (and, critically, should no longer return the phantom root). Consider adjusting the test to use a mock TreeNode container and assert that getParent() is not the phantom DirectoryTreeNode (and optionally equals the container), rather than asserting undefined.

Copilot uses AI. Check for mistakes.

it('ancestor walk stops at the topmost visible directory after re-parenting', function () {
const container = createMockParent();
const phantomRoot = new DirectoryTreeNode(container, '');
const topDir = new DirectoryTreeNode(phantomRoot, 'cloud');
const subDir = new DirectoryTreeNode(topDir, 'helm');
const file = new MockFileNode(subDir);
file.checkboxState = { state: vscode.TreeItemCheckboxState.Checked };

(subDir._children as any[]).push(file);
topDir._children.push(subDir);

// Re-parent: simulate fix
topDir.parent = container;

// Walk ancestors from file, mimicking processCheckboxUpdates
const ancestors: TreeNode[] = [];
let current = file.getParent();
while (current instanceof DirectoryTreeNode) {
ancestors.push(current);
current = current.getParent();
}

// Should find subDir and topDir, but NOT the phantom root
assert.strictEqual(ancestors.length, 2);
assert.strictEqual(ancestors[0], subDir);
assert.strictEqual(ancestors[1], topDir);
});
});
});
1 change: 1 addition & 0 deletions src/view/treeNodes/commitNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ export class CommitNode extends TreeNode implements vscode.TreeItem {
dirNode.finalize();
if (dirNode.label === '') {
// nothing on the root changed, pull children to parent
dirNode._children.forEach(child => { child.parent = this; });
result.push(...dirNode._children);
} else {
result.push(dirNode);
Expand Down
1 change: 1 addition & 0 deletions src/view/treeNodes/filesCategoryNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export class FilesCategoryNode extends TreeNode implements vscode.TreeItem {
if (dirNode.label === '') {
// nothing on the root changed, pull children to parent
this.directories = dirNode._children;
this.directories.forEach(child => { child.parent = this; });
} else {
this.directories = [dirNode];
}
Expand Down
1 change: 1 addition & 0 deletions src/view/treeNodes/pullRequestNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export class PRNode extends TreeNode implements vscode.CommentingRangeProvider2
dirNode.finalize();
if (dirNode.label === '') {
// nothing on the root changed, pull children to parent
dirNode._children.forEach(child => { child.parent = this; });
result.push(...dirNode._children);
} else {
result.push(dirNode);
Expand Down
Loading