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
2 changes: 1 addition & 1 deletion shepherd.js/src/step.ts
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ export class Step extends Evented {

if (isHTMLElement(this.el)) {
this.el.remove();
this.el = null;
this.el = undefined;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve getElement() contract while fixing the guard logic

Line 450 changes the destroyed sentinel to undefined, but getElement() docs/type still indicate destroyed steps return null. That silently changes external behavior. Prefer fixing the guard in _setupElements() to treat both null and undefined as “not mounted,” and keep teardown assigning null.

Suggested patch
diff --git a/shepherd.js/src/step.ts b/shepherd.js/src/step.ts
@@
-    if (isHTMLElement(this.el)) {
-      this.el.remove();
-      this.el = undefined;
-    }
+    if (isHTMLElement(this.el)) {
+      this.el.remove();
+      this.el = null;
+    }
@@
-    if (!isUndefined(this.el)) {
+    if (this.el != null) {
       this.destroy();
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@shepherd.js/src/step.ts` at line 450, The teardown at line 450 assigns
`undefined` to this.el, but the getElement() method's contract and type
signature indicate it returns null for destroyed steps. Revert line 450 to
assign null instead of undefined to maintain the documented contract. Then
update the guard logic in _setupElements() to check if this.el is either null or
undefined (treating both as "not mounted") to handle any edge cases where
undefined might be present from other code paths.

}

this._updateStepTargetOnHide();
Expand Down
13 changes: 13 additions & 0 deletions shepherd.js/test/unit/step.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,19 @@ describe('Tour | Step', () => {
).toBeTruthy();
});

it('not calls destroy on the step if the step was destroyed', () => {
const step = new Step(tour, {});
let destroyCalled = false;
step.el = document.createElement('a');
step.destroy()
step.destroy = () => (destroyCalled = true);
step._setupElements();
expect(
destroyCalled,
'_setupElements method not called destroy if the step was destroyed'
).toBeFalsy();
});

it('calls destroy on the tooltip if it already exists', () => {
const step = new Step(tour, {});
let destroyCalled = false;
Expand Down